-
Notifications
You must be signed in to change notification settings - Fork 129
CSG
Geogram's CSG subsystem is implemented in
geogram/mesh/mesh_CSG.h. The main
class is the CSGBuilder. It applies
boolean operations to a set of meshes, with an API made compatible with OpenSCAD's .csg
file format. OpenSCAD is a 3D modeler with a programming language that lets you create
objects using basic primitives (spheres, boxes, ...), geometric transforms and boolean operations to
combine them. OpenSCAD supports two file formats:
-
.scad
, which is its native file format, with all the constructs of the programming language (for loops, conditional statements, functions, modules ...) -
.csg
, which is a file format with only a CSG tree (primitives, transforms and boolean operations).
Geogram's CSGBuilder follows (as closely as possible) OpenSCAD's .csg
file format. The example program
src/examples/geogram/compute_CSG has four examples ported from OpenSCAD.
The CSGBuilder
manipulates CSGMesh
es. A CSGMesh
is a geogram Mesh plus some additional information used by the CSGBuilder bookkeeping (bounding box, caching...). The type CSGMesh_var
corresponds to a smart pointer to a CSGMesh
(does memory management for you).
The C++ code to generate the first four OpenSCAD examples (see figure) is as follows. It uses curly-braces constructors for matrices, vectors and CSGScope
s (that is, a list of CSGMesh
es). The syntax is very close to OpenSCAD .csg
files (with the exception of union
that writes union_instr
in CSGBuilder
parlance, because union
is a reserved C++ keyword !).
GEO::CSGMesh_var example001() {
using namespace GEO;
CSGBuilder B;
return B.difference({
B.sphere(25.0),
B.multmatrix(
{{1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}},
{ B.cylinder(62.5, 12.5, 12.5) }
),
B.multmatrix(
{{1, 0, 0, 0}, {0, 0, -1, 0}, {0, 1, 0, 0}, {0, 0, 0, 1}},
{ B.cylinder(62.5, 12.5, 12.5) }
),
B.multmatrix(
{{0, 0, 1, 0}, {0, 1, 0, 0}, {-1, 0, 0, 0}, {0, 0, 0, 1}},
{ B.cylinder(62.5, 12.5, 12.5) }
)
});
}
GEO::CSGMesh_var example002() {
using namespace GEO;
CSGBuilder B;
return B.intersection({
B.difference({
B.union_instr({
B.cube({30,30,30}),
B.multmatrix(
{{1, 0, 0, 0},
{0, 1, 0, 0},
{0, 0, 1, -25},
{0, 0, 0, 1}},
{B.cube({15,15,40})}
)
}),
B.union_instr({
B.cube({50,10,10}),
B.cube({10,50,10}),
B.cube({10,10,50})
}),
}),
B.multmatrix(
{{1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 5}, {0, 0, 0, 1}},
{ B.cylinder(50, 20, 5) }
)
});
}
GEO::CSGMesh_var example003() {
using namespace GEO;
CSGBuilder B;
return B.difference({
B.union_instr({
B.cube({30, 30, 30}),
B.cube({40, 15, 15}),
B.cube({15, 40, 15}),
B.cube({15, 15, 40})
}),
B.union_instr({
B.cube({50, 10, 10}),
B.cube({10, 50, 10}),
B.cube({10, 10, 50})
})
});
}
GEO::CSGMesh_var example004() {
using namespace GEO;
CSGBuilder B;
return B.difference({
B.cube({30,30,30}),
B.sphere(20)
});
}
To generate these meshes, one can use:
$ compute_CSG example001
(resp. example002
,example003
,example004
). This will generate the result in out.meshb
. Then, to visualize the result, use:
$ vorpaview out.meshb
Geogram can interpret OpenSCAD's .csg
files, thanks to the CSGCompiler class. The CSGCompiler
reads a CSG tree either from a file or for a string, interprets it and generates a mesh.
One can use the compute_CSG
example program to interpret .csg
files:
$ compute_CSG file.csg outputfilename
where outputfilename
is in one of the formats supported by Geogram (.obj
, .mesh
, .meshb
, .geogram
...). If no output
filename is specified, then the default is out.meshb
. If OpenSCAD is installed in the system, one can directly call
compute_CSG
with a .scad
file (then compute_CSG
calls OpenSCAD to convert it into the .csg
format).
You can find example OpenSCAD files here. Some of them are used by the geogram testsuite.
Programatically, one can compile a CSG file as follows:
#include <geogram/mesh/mesh_CSG.h>
...
CSGCompiler CSG;
std::string input_filename = ...;
CSGMesh_var result = CSG.compile_file(input_filename);
...
Alternatively, one can compile a CSG tree given as a string, as follows:
std::string input_string = ...;
CSGMesh_var result = CSG.compile_string(input_string);
-
Delaunay (default) or non-Delaunay: When triangulating intersecting facets, the builder can use a constrained Delaunay triangulation (activated by default), or just a constrained triangulation. Without Delaunay, it can be a bit faster, but then handling of co-planar facets is no longuer guaranteed. It is recommended to keep this option on. See Builder::set_delaunay().
-
Simplifying co-planar facets: when computing intersections of objects that have co-planar facets, it is interesting to merge the co-planar facets and re-triangulate them. One can specify a tolerance (in degrees) for the deviation between the normal vectors of facets to merge. See Builder::set_simplify_coplanar_facets()
-
Detect intersection in neighboring facets: it is quite unlikely that two triangles that share an edge or a vertex have an interrsection. By default, all intersections are tested, but one can gain a bit of time by skipping the pair of adjacent triangles in intersection tests. See Builder::set_detect_intersecting_neighbors()
- Mesh intersection and boolean operations
- Want to learn more about how it works ? See this article.
To gain more speed and more robustness in the extreme cases, read about GeogramPlus.