Of course writing a robust CSG library is a daunting undertaking. Fortunately there are existing libraries such as CGAL and Carve that handle this. In my opinion CGAL is the more robust of the two, however it currently has compilation issues under OS-X and is substantially slower than Carve.
Regardless, neither have the interface that I'm looking for, like the ability to directly load meshes, affine transformations and minimal-code ways to perform boolean operations on meshes. So I started work on a C++ wrapper for Carve that would give me the interface I wanted, with a wrapper for Python.
I'm pleased to say that it's coming along quite well and able to produce parts that are non-trivial. The interface is considerably cleaned up from before and I'm now starting to use it for projects. Here's two examples from (another) CNC project:
The code that generated these models is here:
from pyCSG import * def inch_to_mm( inches ): return inches*25.4 def mm_to_inch( mm ): return mm/25.4 def hole_compensation( diameter ): return diameter+1.0 mounting_hole_radius = 0.5*hole_compensation( inch_to_mm( 5.0/16.0 ) ) def axis_end(): obj = box( inch_to_mm( 4.5 ), inch_to_mm( 1.75 ), inch_to_mm( 0.75 ), True ) screw_hole = cylinder( mounting_hole_radius, inch_to_mm( 3.0 ), True, 20 ) shaft_hole = cylinder( 0.5*hole_compensation( inch_to_mm( 0.5 ) ), inch_to_mm(1.0), True, 20 ).rotate( 90.0, 0.0, 0.0 ) center_hole = cylinder( 0.5*hole_compensation( inch_to_mm( 1.0 ) ), inch_to_mm(1.0), True, 20 ).rotate( 90.0, 0.0, 0.0 ) mount_hole = cylinder( 0.5*hole_compensation( 4.0), inch_to_mm(1.0), True, 10 ).rotate( 90.0, 0.0, 0.0 ) notch = box( inch_to_mm( 1.5 ), 2.0, inch_to_mm( 1.0 ), True ) obj = obj - ( shaft_hole.translate( inch_to_mm( 1.5 ), 0.0, 0.0 ) + shaft_hole.translate( inch_to_mm( -1.5 ), 0.0, 0.0 ) ) obj = obj - ( notch.translate( inch_to_mm( 2.25 ), 0.0, 0.0 ) + notch.translate( inch_to_mm( -2.25 ), 0.0, 0.0 ) ) obj = obj - ( center_hole + mount_hole.translate( -15.5, -15.5, 0.0 ) + mount_hole.translate( 15.5, -15.5, 0.0 ) + mount_hole.translate( 15.5, 15.5, 0.0 ) + mount_hole.translate( -15.5, 15.5, 0.0 ) ) obj = obj - ( screw_hole.translate( inch_to_mm(1.0), 0.0, 0.0 ) + screw_hole.translate( inch_to_mm(-1.0), 0.0, 0.0 ) ) obj = obj - ( screw_hole.translate( inch_to_mm(2.0), 0.0, 0.0 ) + screw_hole.translate( inch_to_mm(-2.0), 0.0, 0.0 ) ) return obj def carriage(): obj = box( inch_to_mm( 5 ), inch_to_mm( 5 ), inch_to_mm( 1.0 ), True ) shaft_hole = cylinder( inch_to_mm( 0.75 )/2.0, inch_to_mm( 5.5 ), True ) screw_hole = cylinder( inch_to_mm( 0.5 )/2.0, inch_to_mm( 5.5 ), True ) leadnut_hole = cylinder( inch_to_mm(0.25)*0.5, inch_to_mm( 1.0 ), True ); leadnut_access = box( inch_to_mm( 1.5 ), inch_to_mm( 3.0/8.0 ), inch_to_mm( 1.0 ), True ) mhole = cylinder( mounting_hole_radius, inch_to_mm( 2.0 ), True ).rotate( 90.0, 0.0, 0.0 ) obj = obj - ( shaft_hole.translate( inch_to_mm( 1.5 ), 0.0, 0.0 ) + shaft_hole.translate( inch_to_mm( -1.5 ), 0.0, 0.0 ) + screw_hole ) obj = obj - ( leadnut_hole.translate( inch_to_mm( 0.5 ), inch_to_mm( -2.5 ), 0.0 ) + leadnut_hole.translate( inch_to_mm( -0.5 ), inch_to_mm( -2.5 ), 0.0 ) + leadnut_access.translate( 0.0, inch_to_mm( -2.0 ), inch_to_mm( 0.2 ) ) ) for i in range( -2, 3 ): for j in range( -2, 3 ): if i != 0 and j != 0: obj = obj - ( mhole.translate( inch_to_mm( 1.0*i ), inch_to_mm( 1.0*j ), 0.0 ) ) return obj axis_end().save_mesh("axis_end.obj" ) carriage().save_mesh("carriage.obj" )
As you can see, this approach gives lots of flexibility in terms of manipulating and patterning objects using custom code. The examples above are not great examples of parametric design, but I'm sure you can imagine the sort of stuff that can be done.
I still have to perform a bit of cleanup outside the library to get printable models. I just run each model through MeshLab's planar edge-flipping optimizer. This is a pretty simple step and I plan to integrate it into the library shortly, along with the ability to extrude custom profiles and build surfaces of revolution. When these features are finished I plan to release the code for the library and Python wrapper.