A friend of mine, David, recently acquired a 3D printer and has been printing out the many freely available 3D models of D&D miniatures posted by Miguel Zavala on Shapeways. He was having some issues with certain models and I suggested using Netfabb Basic, the go to program for fixing issues with 3D models. One Iron Golem, was not going to be defeated so easily. I took a peek at the model and there were some non-manifold edges and flipped faces that Netfabb just wasn't able to fix automatically.
David methodically selected every face that was flipped and had Netfabb fix them. Who knows if he just missed a flipped face somewhere or if it was something else, but his slicer was not able to process the model correctly. I suggested that we fix it with brute force, that is convert the model into a voxel grid and then convert it back into a mesh. The result would be a nice, manifold, 3D model for printing. If you're familiar with the difference between a vector graphic and a raster image, you can make a decent 3D comparison with an STL file vs a voxel grid. An STL file has a set of points and faces, which represent a 3D shape using equations similar to how a vector graphic can represent 2D shapes. A voxel grid is a set of data points laid out in a 3D grid, similar to how a raster image (a JPG for example) is a set of pixels laid out in a 2D grid.
Now how do we go about this conversion process you ask? Luckily, I had done this before. In fact, I developed a command-line tool called stl_boolean that leverages Dreamworks Animation's OpenVDB library to perform this sort of conversion in addition to performing CSG operations with another model (check out stl_cmd to see other similar commands). We didn't need the CSG part, but I had the command at my disposable already, so we tried it out. In a terminal, I typed:
stl_boolean -a IronGolem.stl -b empty.stl -u -v .1 Voxelized.stl
Let's break down what this command does.
-b are used to specify two different STL files. The first is our Iron Golem, the second is an empty STL file that I explicitly created because we needed a second STL file (I used another stl_cmd I developed called stl_empty). The next flag specifies which CSG operation to perform on the two models. CSG operations include union, where you combine both models, intersection where you only want the parts of the two models that overlap, and difference where you subtract one model from the other. We used the
-u flag to specify union. The
-v .1 indicates the size of each voxel. In this case, I wanted to make sure I captured all the details in the model, so I used .1 which translates to 1/10 of a millimeter, the best detail any reasonably priced FDM 3D printer could achieve. The last argument,
Voxelized.stl is the output file name for the resulting mesh.
Now let's talk about why I consider this the "brute force" method. Turning a mesh into a voxel format (or a volume format), is a very computationally intensive operation, especially if you want to maintain a high level of detail. This voxel grid contained 4,643,803 active voxels. Luckily, OpenVDB is a sparse volume format, which means it only stores data in active voxels rather than storing every voxel in a given bounding box. Had we used a dense volume format, this model would have taken up 461 x 408 x 648 = 121,881,024 voxels. Even so, the resulting VDB file was 21MB on disk (stl_boolean doesn't actually provide this, but I wanted the actual vdb file so I could take a screenshot of the voxel representation above). The original model file was a measly 1.2MB, so we're already at about a 20x size increase. That's just the size of the VDB, though. Once converted back to a mesh, the resulting file was a whooping 106MB, with 2,220,856 triangles. Now, that's without any post processing of the mesh, and with a voxel size that small, there's going to be a ton of vertices that we don't need. I brought it into Blender:
Blender really chugged with that many triangles, but I was able to get a Decimate modifier on it and reduce it to 1% of the total triangles. In the end, I had a 1.1MB STL file, just under the size of the original mesh. Here it is pictured on the left, with the original model shown on the right.
I brought the model back into Netfabb Basic and it had no issues!
Have suggestions for other ways to fix a 3D model? Interested in trying out stl_boolean and the other commands in the stl_cmd suite? Have something else to say? Let me know in the comments below!