Constructive Solid Geometry#

Constructive solid geometry (CSG) is the process of combining a set of initial, often simple geometries into more complex shapes through the use of boolean operations such as unions, intersections, and differences [LTH86]. In computer graphics applications, this may involve ray tracing or other approaches for efficient visualization, but MyMesh is primarily concerned with generating and/or modifying meshes. MyMesh offers two approaches to CSG: implicit and explicit.

../_images/csg-1_00_00.png
../_images/csg-1_01_00.png

Implicit CSG#

For implicit CSG, boolean operations are performed on implicit functions or a set of values evaluated at points in a grid or mesh (e.g. image data). Performing boolean operations in this way is quite efficient, but a contouring step is required to generate a mesh, which may result in a loss of detail at sharp features or interfaces. In MyMesh, implicit CSG operations rely on mymesh.implicit and mymesh.contour.

Boolean operations can be performed directly on the functions to obtain a new implicit function. implicit offers several sets of boolean operator functions depending on what type of data is being operated on: arrays of values (union(), intersection(), diff()), functions (unionf(), intersectionf(), difff()), or sympy-based functions (unions(), intersections(), diffs()). This new function can then be meshed as any other implicit function.

func1 = implicit.box(-.9,.9,-.9,.9,-.9,.9)
func2 = implicit.sphere([0,0,0],1)
func = implicit.difff(func1, func2)
diff = implicit.SurfaceMesh(func, [-1,1,-1,1,-1,1], .05)
diff.plot(bgcolor='w',view='trimetric')
../_images/csg-2.png

While this approach is straight forward and efficient, it suffers from some of the classic limitations of implicit meshing, particularly, poor resolution along the sharp edges introduced by the intersection of the two objects (though more advanced contouring methods can alleviate these).

Another approach is to generate a tetrahedral mesh of the first object and then contour the second function using the first mesh as a background mesh. Here, the threshold direction needs to be chosen appropriately to achieve the intended operation (and a union operations are more difficult to achieve).

func1 = implicit.box(-.9,.9,-.9,.9,-.9,.9)
func2 = implicit.sphere([0,0,0],1)
cube = implicit.TetMesh(func1, [-1,1,-1,1,-1,1], .05)
diff = implicit.TetMesh(func2, [-1,1,-1,1,-1,1], .05, background=cube, threshold_direction=1)
diff.plot(bgcolor='w',view='trimetric')
../_images/csg-3.png

This operation can equivalently be performed using the Contour() method.

m1.NodeData['func2'] = func2(m1.NodeCoords[:,0], m1.NodeCoords[:,1], m1.NodeCoords[:,2])
m2 = m1.Contour('func2', 0, threshold_direction=1, Type='vol')

This leads to much cleaner intersection edges between the two objects, however repeated contouring of a tetrahedral mesh can lead to low quality tetrahedra that may require improvement if being used for finite element applications.

Explicit CSG#

Explicit CSG operates directly on existing meshes, rather than functions or values. This involves calculating intersections between meshes (utilizing mymesh.rays and mymesh.octree) and then splitting and joining elements to create the new mesh. These operations are more computationally demanding and generally slower than implicit CSG, especially for large meshes, but and can be used when no functional representation of a mesh exists. Floating point errors in the identification of intersections and splitting of elements can result in mesh defects and unclosed surfaces, which may be problematic for some applications. If performing explicit CSG on surface meshes with the aim of producing models that require volumetric meshes, fTetWild may be useful for generating high quality tetrahedral meshes from imperfect surfaces [HSW+20]. Explicit CSG mesh boolean functions can be found in mymesh.booleans.

Since the vast majority of the computational effort is spent splitting and labeling elements, which is done identically regardless of which operation is being performed, MeshBooleans() returns the union, intersection, and difference meshes together (note that the difference is not symmetric, i.e. A-B ≠ B-A).

cube = primitives.Box([-.9,.9,-.9,.9,-.9,.9], .25, ElemType='tri')
sphere = primitives.Sphere([0,0,0], 1, ElemType='tri')

U,I,D = booleans.MeshBooleans(cube,sphere)

# Plotting:
fig1, ax1 = U.plot(show=False,return_fig=True,color='g',bgcolor='w',view='trimetric')
fig2, ax2 = I.plot(show=False,return_fig=True,color='b',bgcolor='w',view='trimetric')
fig3, ax3 = D.plot(show=False,return_fig=True,color='r',bgcolor='w',view='trimetric')
plt.close(fig1); plt.close(fig2); plt.close(fig3);

fig, (a1, a2, a3) = plt.subplots(1, 3, figsize=(12,4))
a1.imshow(ax1.get_images()[0].get_array())
a2.imshow(ax2.get_images()[0].get_array())
a3.imshow(ax3.get_images()[0].get_array())
a1.set_title('Union')
a1.set_axis_off()
a2.set_title('Intersection')
a2.set_axis_off()
a3.set_title('Difference')
a3.set_axis_off()

plt.tight_layout()
plt.show()
../_images/csg-4.png