algorithmic modeling for Rhino
MeshMachine, a part of Kangaroo 1, is prone to blowing up as you add more iterations, assuming you can get it to work at all in an hour or more. It has ability to relax a mesh, and to fix in place sharp edges fairly well, but since it has such kinetic behavior, seemingly optimized for speed on small test systems, it doesn't give the most uniform mesh, most of the time. If you take the dual of the triangular mesh, you see lots of squares and octagons.
I also had to rely on MeshMachine to refine Cocoon marching cubes organic surfaces, since the refine component of Cocoon blows up even worse than MeshMachine, which it is black box based on, with five completely undocumented parameters.
So I searched for many days for various scriptable libraries, all of them in C++, and not only did few work well as software, they gave lots of squares too, meaning they are poor at dividing up a surface evenly, since those four triangles per square dual shape are so small of an area. I want something more like a beehive or a fly's eye.
The standard library for geometry out there is CGAL, and it would be nearly impossible for most Grasshopper users to install it, since there are no binaries of the latest versions, and you have to compile several smaller libraries as you spend upwards of many full days searching forums for answers to errors in just installing it. And who knows how good of meshes it makes? I can only test it in C++, fairly easy enough, and may be able to compile a remeshing function that I can call from the command line which I can upload as a working binary, that will write the output to disk. That means I could call it from Python, anybody could, since Python is so simple. But what I can't do after the installation is get Python bindings to work on Windows. That's just broken completely.
The breakthrough, after struggling through truly terrible Windows utility programs, was finding OpenFlipper, a geometry plug-in development platform. It even has a Grasshopper-like nodal editor to build scripts, but that's so far limited. The normal scripting commands are easy to pick up on though, so I wrote Grasshopper wrappers for three remeshing strategies that result in no squares or even octagons and above, only pentagons, hexagons and septagons in the resulting dual of the triangle mesh. I used Python to write an input mesh to disk as an STL, then I create an OpenFlipper script on the fly, also written to disk, then I have OpenFlipper run and I read in the resulting STL file back into Python and spit out a Rhino/Grasshopper mesh again. It briefly brings up the GUI of OpenFlipper then closes it to put you back in Grasshopper, since the command-line-only option seems to be broken and this allows all commands to run, not just blind capable ones.
The Python scripts are simple enough to modify on your own to add more OpenFlipper commands.
Just download the Windows program here, the "Staging" version being the desired beta version with more features:
Install it in the normal Programs Folder. In the future you will have to edit the path in Python with updated OpenFlipper version numbers, in line 35 below. [See troubleshooting posts below about right clicking on Rhino.exe and OpenFlipper.exe to set the Compatibility tab checkbox in Properties to "Open this program as administrator." and to also check that OpenFlipper's directory matches what's in the Python code that you can view by double clicking the Python component on the Grasshopper canvas.]
None of the three strategies automatically preserves hard edges, so for those the adaptive strategy is often best.
Use Weaverbird Dual to gain quick access to this blissfully better distribution of cells on a surface than the "alien slime" of random Voronoi diagrams.
These will not smooth out original large facets from crude meshes, so subdivide those first using Weaverbird. I included a source meshing group, to apply to NURBS polysurfaces, too, since OpenFlipper won't import surfaces, only meshes.
Such ideal meshing that lack tight little square areas in the dual will also afford highest quality 3D tetrahedral meshes. I ported Tetgen to Grasshopper too, in the past, for that, and that also affords 3D polyhedral cells.
This is extremely helpful. Thank you so much for sharing!
I also had to run Rhino and OpenFlipper as administrator on Windows 10.
There are convoluted ways to demand administrator status from the Python code but it would make the script rather confusing and long instead of one line per action.
I cleaned up the script(s) in the original post enclosure to remove unworking directory selection that will fail with spaces, in favor of just hard wiring it to the C: drive and fixed a wrongly named variable. It's a short enough script for normal users, along with Stackoverflow and Google English language searches to debug in the future. Just try isolating the problem in a copy of the script, paring it down to a few lines that still fail the same way, then ask around, since then you'll likely only have a basic Python issue to figure out.
# TO MAKE THIS WORK, RIGHT CLICK RHINO.EXE AND OPENFLIPPER.EXE TO ACCESS THE PROPERTIES>COMPATIBILITY TAB AND CHECK "RUN THIS PROGRAM AS ADMINISTRATOR."
# ALSO MAKE SURE TO EDIT THE OPENFLIPPER CORRECT VERSION NUMBER AND INSTALLATION PATH THAT I HAVE HARD-WIRED TO VERSION 3.1, BELOW.
# Write an ASCII format STL file for the input mesh, for OpenFlipper to operate on:
stl_file = open("C:/NIKS_OPENFLIPPER_PARSER_TEMP.stl","w")
Input_Mesh = Mesh # Let's not use the Rhino term Mesh in the script itself.
Input_Mesh.Normals.ComputeNormals() # NITM ("Not In The Manual") but this is needed first.
verts = Input_Mesh.Vertices # Get all vertices of the mesh from Rhino.
for i,face in enumerate (Input_Mesh.Faces): # Rhino gives faces by vertex index number.
stl_file.write(" facet normal %s\n" % str(Input_Mesh.FaceNormals[i]).replace(",", " ")) # Rhino gives normals!
stl_file.write(" outer loop\n")
stl_file.write(" vertex %s\n" % str(verts[face.A]).replace(",", " ")) # Rhino has ABCD properties for face vertex index numbers.
stl_file.write(" vertex %s\n" % str(verts[face.B]).replace(",", " "))
stl_file.write(" vertex %s\n" % str(verts[face.C]).replace(",", " "))
# Generate an OpenFlipper script on the fly in order to alter settings not accepted on its command line:
openflipper_script = open("C:/NIKS_OPENFLIPPER_PARSER_TEMP.ofs", "w")
openflipper_script.write("id = core.getObjectId(\"NIKS_OPENFLIPPER_PARSER_TEMP.stl\")\n")
openflipper_script.write("remesher.uniformRemeshing(id,%s,%s,%s,true)\n" % (TargetEdgeLength, Iterations, AreaInterations))
# Windows command line execution of OpenFlipper with argument to run our script:
OFS = "C:/NIKS_OPENFLIPPER_PARSER_TEMP.ofs"
subprocess.call(['C:\Program Files\OpenFlipper 3.1\OpenFlipper.exe',OFS]);
# Read in the OpenFlipper output STL file back into Grasshopper:
mesh = Rhino.Geometry.Mesh() # Initialize "mesh" variable as a blank mesh object to hold each face.
MESH = Rhino.Geometry.Mesh() # Initialize "MESH" variable as a blank mesh object to accumulate faces.
with open("C:/Out.stl") as f:
for line in f:
if "vertex" in line:
q = line.replace("vertex ", "")
q = q.replace("\n", "")
q = [float(x) for x in q.split()]
mesh.Vertices.Add(q,q,q) # Fill mesh with three vertex lines in a row.
if "endloop" in line: # File itself tells us we are done with three vertices for one face.
mesh.Faces.AddFace(0,1,2) # Create a single face mesh.
MESH.Append(mesh) # Magically build a real multi-face mesh while removing redundant vertices.
mesh = Rhino.Geometry.Mesh() # Reinitialize empty mesh object for each mesh face.
MESH.Normals.ComputeNormals() # Gives nice mesh and preview but makes the script take longer.
I used the same inline creation of program scripts to similarly port ZBrush to Grasshopper, which is the best *quad* remesher on the planet:
Some OpenFlipper failures to be perfect along edges and corners of open meshes, here:
This is incredible, Nik! Thanks so much for finding and implementing it.