GCC Code Coverage Report


Directory: ./
File: lib/geogram/mesh/mesh_CSG_builder.h
Date: 2026-09-07 02:25:23
Exec Total Coverage
Lines: 46 98 46.9%
Functions: 7 30 23.3%
Branches: 32 216 14.8%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2000-2023 Inria
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * * Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 * * Neither the name of the ALICE Project-Team nor the names of its
14 * contributors may be used to endorse or promote products derived from this
15 * software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
28 *
29 * Contact: Bruno Levy
30 *
31 * https://www.inria.fr/fr/bruno-levy
32 *
33 * Inria,
34 * Domaine de Voluceau,
35 * 78150 Le Chesnay - Rocquencourt
36 * FRANCE
37 *
38 */
39
40 #ifndef H_GEO_MESH_CSG_BUILDER_H
41 #define H_GEO_MESH_CSG_BUILDER_H
42
43 #include <geogram/basic/common.h>
44 #include <geogram/mesh/mesh_CSG_utils.h>
45 #include <memory>
46 #include <functional>
47 #include <stack>
48
49 namespace GEO {
50
51 /**
52 * \brief Base class for implementing CSG objects and instructions.
53 * \details AbstractCSGBuilder implement the basic mechanism for
54 * calling generic objects and CSG instructions that take an ArgList
55 * (name value pairs) as arguments. It dispatches objects/instructions
56 * from their name (as a string) and unpacks the arguments from the
57 * ArgList. It is interesting to have an abstract base class for that,
58 * because one can have a subclass that records the CSG
59 * abstract syntax tree, for subsequently optimizing it before playing
60 * it back.
61 */
62 class GEOGRAM_API AbstractCSGBuilder {
63 public:
64 typedef GEOCSG::ArgList ArgList;
65 typedef GEOCSG::Value Value;
66
67 /** \see set_fa() */
68 static constexpr double DEFAULT_FA = 12.0;
69
70 /** \see set_fs() */
71 static constexpr double DEFAULT_FS = 2.0;
72
73 /** \see set_fn() */
74 static constexpr double DEFAULT_FN = 0.0;
75
76 /**
77 * \brief AbstractCSGBuilder constructor
78 */
79 AbstractCSGBuilder();
80
81 /**
82 * \brief AbstractCSGBuilder destructor
83 */
84 virtual ~AbstractCSGBuilder();
85
86 /****** Parameters ******/
87
88 /**
89 * \brief Resets defaults value for fn, fs, fa
90 * \see set_fn(), set_fs(), set_fa()
91 */
92 void reset_defaults();
93
94 /**
95 * \brief Sets the number of fragments.
96 * \details This corresponds to the number of edges in a polygonal
97 * approximation of a circle. If left to 0, it is automatically
98 * computed from fs and fa
99 * \param[in] fn the number of fragments.
100 * \see set_fs(), set_fa()
101 */
102 void set_fn(double fn) {
103
2/4
✓ Branch 0 taken 454 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 248 times.
✗ Branch 3 not taken.
702 fn_ = std::max(fn, 0.0);
104 }
105
106 /**
107 * \brief Sets the minimum size for a fragment.
108 * \param[in] fs minimum size for a fragment.
109 * \details This determines the number of edges in a polygonal
110 * approximation of a circle.
111 */
112 void set_fs(double fs) {
113
2/4
✓ Branch 0 taken 454 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 248 times.
✗ Branch 3 not taken.
702 fs_ = std::max(fs,0.01);
114 }
115
116 /**
117 * \brief Sets the minimum angle for a fragment.
118 * \param[in] fa minimum angle for a fragment, in degrees.
119 * \details This determines the number of edges in a polygonal
120 * approximation of a circle.
121 */
122 void set_fa(double fa) {
123
2/4
✓ Branch 0 taken 454 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 248 times.
✗ Branch 3 not taken.
702 fa_ = std::max(fa,0.01);
124 }
125
126 /**
127 * \brief Displays (lots of) additional information
128 * \param[in] x whether additional information should be displayed.
129 * Default is off
130 */
131 void set_verbose(bool x) {
132 20 verbose_ = x;
133
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
20 if(verbose_) {
134 warnings_ = true;
135 }
136 }
137
138 /**
139 * \brief Displays (even more) additional information
140 * \param[in] x whether even more information should be displayed.
141 * Default is off
142 */
143 void set_detailed_verbose(bool x) {
144 20 detailed_verbose_ = x;
145
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
20 if(detailed_verbose_ && !verbose_) {
146 verbose_ = true;
147 }
148 }
149
150 /**
151 * \brief Tests wheter verbose mode is set.
152 * \retval true if additional information will be displayed.
153 * \retval false otherwise.
154 */
155 bool verbose() const {
156
4/8
✗ Branch 0 not taken.
✓ Branch 1 taken 454 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 248 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 702 times.
✓ Branch 7 taken 20 times.
✗ Branch 8 not taken.
1424 return verbose_;
157 }
158
159
160 /**
161 * \brief Adds a path to the file path
162 * \details The file path is where import() searches files. The default
163 * file path contains the current directory "."
164 * \param[in] path the file path to be added, without trailing '/'
165 */
166 void add_file_path(const std::filesystem::path& path) {
167 file_path_.push_back(path);
168 }
169
170 /**
171 * \brief Resets the file path to its default value, with only the
172 * current directory "."
173 */
174
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
22 void reset_file_path() {
175 file_path_.clear();
176 44 file_path_.push_back(std::filesystem::current_path());
177 22 }
178
179 /**
180 * \brief Adds a path to the file path
181 * \details The file path is where import() searches files. The default
182 * file path contains the current directory "."
183 * \param[in] path the file path to be added, without trailing '/'
184 */
185 void push_file_path(const std::filesystem::path& path) {
186
1/2
✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
20 file_path_.push_back(path);
187 20 }
188
189 /**
190 * \brief Removes the latest pushed file path
191 */
192
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
20 void pop_file_path() {
193
1/6
✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
20 geo_assert(file_path_.size() != 0);
194 file_path_.pop_back();
195 20 }
196
197 /**** Abstract API - objects ******/
198
199 virtual void add_object(
200 const std::string& object, const ArgList& args
201 );
202
203 virtual void begin_instruction();
204
205 virtual void end_instruction(
206 const std::string& instruction, const ArgList& args
207 );
208
209
210 bool is_object(const std::string& id) const {
211 return (object_funcs_.find(id) != object_funcs_.end());
212 }
213
214 bool is_instruction(const std::string& id) const {
215 return (instruction_funcs_.find(id) != instruction_funcs_.end());
216 }
217
218 protected:
219
220 /****** Objects ******************************************/
221
222 virtual void add_square(const ArgList& args);
223 virtual void add_circle(const ArgList& args);
224 virtual void add_cube(const ArgList& args);
225 virtual void add_sphere(const ArgList& args);
226 virtual void add_cylinder(const ArgList& args);
227 virtual void add_polyhedron(const ArgList& args);
228 virtual void add_polygon(const ArgList& args);
229 virtual void add_import(const ArgList& args);
230 virtual void add_surface(const ArgList& args);
231 virtual void add_text(const ArgList& args);
232
233 /****** Instructions ************************************/
234
235 virtual void eval_multmatrix(const ArgList& args);
236 virtual void eval_translate(const ArgList& args);
237 virtual void eval_rotate(const ArgList& args);
238 virtual void eval_scale(const ArgList& args);
239 virtual void eval_resize(const ArgList& args);
240 virtual void eval_union(const ArgList& args);
241 virtual void eval_intersection(const ArgList& args);
242 virtual void eval_difference(const ArgList& args);
243 virtual void eval_group(const ArgList& args);
244 virtual void eval_color(const ArgList& args);
245 virtual void eval_hull(const ArgList& args);
246 virtual void eval_linear_extrude(const ArgList& args);
247 virtual void eval_rotate_extrude(const ArgList& args);
248 virtual void eval_projection(const ArgList& args);
249 virtual void eval_minkowski(const ArgList& args);
250 virtual void eval_render(const ArgList& args);
251
252 /**************************/
253
254 [[noreturn]] void error(const char* str) {
255 throw(std::logic_error(str));
256 }
257
258 [[noreturn]] void error(const std::string& str) {
259 throw(std::logic_error(str.c_str()));
260 }
261
262 double fn_;
263 double fs_;
264 double fa_;
265
266 std::vector<std::filesystem::path> file_path_;
267 bool warnings_;
268 bool verbose_;
269 bool detailed_verbose_;
270
271 typedef std::function<void(const ArgList& args)> csg_builder_func;
272 std::map<std::string, csg_builder_func> object_funcs_;
273 std::map<std::string, csg_builder_func> instruction_funcs_;
274 };
275
276 /***********************************************************************/
277
278 /**
279 * \brief A Scope corresponds to a set of primitive between curly braces
280 * in OpenSCAD, arguments of an operation. It is implemented as a vector
281 * of meshes.
282 */
283
0/26
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
✗ Branch 30 not taken.
✗ Branch 31 not taken.
✗ Branch 32 not taken.
✗ Branch 33 not taken.
✗ Branch 34 not taken.
✗ Branch 35 not taken.
✗ Branch 38 not taken.
✗ Branch 39 not taken.
✗ Branch 40 not taken.
✗ Branch 41 not taken.
✗ Branch 42 not taken.
✗ Branch 43 not taken.
✗ Branch 44 not taken.
✗ Branch 45 not taken.
✗ Branch 46 not taken.
500 class CSGScope {
284 public:
285 /**
286 * \brief Constructs an empty CSGScope
287 */
288 CSGScope() {
289 }
290
291 /**
292 * \brief Constructs a CSGScope that contains a single mesh
293 * \param[in] M a shared_ptr to the mesh
294 */
295 CSGScope(std::shared_ptr<Mesh> M) {
296 emplace_back(M);
297 }
298
299 /**
300 * \brief Constructs a CSGScope from a list of meshes
301 * \param[in] Ms the list of meshes (specified between curly braces)
302 */
303 CSGScope(
304 const std::initializer_list<std::shared_ptr<Mesh>>& Ms
305
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 ) : meshes_(Ms) {
306 }
307
308 void push_back(std::shared_ptr<Mesh> M) {
309
2/8
✓ Branch 1 taken 454 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 248 times.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
702 meshes_.push_back(M);
310 702 }
311
312 void emplace_back(std::shared_ptr<Mesh> M) {
313
1/22
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
✗ Branch 31 not taken.
✗ Branch 32 not taken.
4 meshes_.emplace_back(M);
314 4 }
315
316 void pop_back() {
317 meshes_.pop_back();
318 3 }
319
320 index_t size() const {
321 return index_t(meshes_.size());
322 }
323
324 const std::shared_ptr<Mesh>& operator[](index_t i) const {
325 geo_debug_assert(i < size());
326 return meshes_[i];
327 }
328
329 auto begin() { return meshes_.begin(); }
330 auto end() { return meshes_.end(); }
331 auto begin() const { return meshes_.begin(); }
332 auto end() const { return meshes_.end(); }
333 auto rbegin() { return meshes_.rbegin(); }
334 auto rend() { return meshes_.rend(); }
335 auto rbegin() const { return meshes_.rbegin(); }
336 auto rend() const { return meshes_.rend(); }
337
338 4 void append(std::shared_ptr<Mesh> M) {
339 4 emplace_back(M);
340 4 }
341
342 4 template <typename...Types> void append(
343 std::shared_ptr<Mesh> head, Types... tail
344 ) {
345
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
8 append(head);
346
1/30
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
✗ Branch 30 not taken.
4 append(tail...);
347 4 }
348
349 private:
350 std::vector<std::shared_ptr<Mesh>> meshes_;
351 };
352
353 /**
354 * \brief Implements CSG objects and instructions.
355 * \details Can be used to construct volumes in C++ with a syntax
356 * very similar to OpenSCAD .csg files.
357 */
358 class GEOGRAM_API CSGBuilder : public AbstractCSGBuilder {
359 public:
360 CSGBuilder();
361 ~CSGBuilder() override;
362
363 /****** Objects **********/
364
365 virtual std::shared_ptr<Mesh> square(vec2 size, bool center=false);
366
367 std::shared_ptr<Mesh> square(double size=1.0, bool center=false) {
368 return square(vec2(size,size), center);
369 }
370
371 /**
372 * \param[in] nu number of fragments. If left unspecified, then it
373 * is deduced from radius and/or $fn variable.
374 */
375 virtual std::shared_ptr<Mesh> circle(double r=1.0, index_t nu=0);
376
377 virtual std::shared_ptr<Mesh> cube(vec3 size, bool center=false);
378
379 std::shared_ptr<Mesh> cube(double size=1.0, bool center=false) {
380 return cube(vec3(size,size,size), center);
381 }
382
383 virtual std::shared_ptr<Mesh> sphere(double r=1.0);
384
385 virtual std::shared_ptr<Mesh> cylinder(
386 double h, double r1, double r2, bool center=false
387 );
388
389 virtual std::shared_ptr<Mesh> import(
390 const std::filesystem::path& filename, const std::string& layer="",
391 index_t timestamp=0,
392 vec2 origin = vec2(0.0, 0.0), vec2 scale = vec2(1.0,1.0)
393 );
394
395 virtual std::shared_ptr<Mesh> surface(
396 const std::filesystem::path& filename, bool center, bool invert
397 );
398
399 virtual std::shared_ptr<Mesh> text(
400 const std::string& text,
401 double size = 10.0,
402 const std::string& font = "",
403 const std::string& halign = "left",
404 const std::string& valign = "baseline",
405 double spacing = 1.0,
406 const std::string& direction = "ltr",
407 const std::string& language = "en",
408 const std::string& script = "latin"
409 );
410
411
412 /****** Instructions ****/
413
414 /**
415 * \brief Groups several meshes into a single one and transforms
416 * them.
417 * \param[in] M the transformation matrix. It follows the same
418 * convention as OpenSCAD, that is, not the OpenGL convention.
419 * For instance, a translation matrix has the translation vector
420 * as its third column.
421 * \param[in] scope one or several meshes to be merged.
422 */
423 virtual std::shared_ptr<Mesh> multmatrix(
424 const mat4& M, const CSGScope& scope
425 );
426
427 /**
428 * \brief Computes the union of two or more meshes.
429 * \param[in] scope the meshes. One can use a CSGScope object, or a
430 * curly-braced list of meshes (list of shared_ptr to meshes in fact).
431 * \return a mesh with the union of the meshes in \p scope.
432 */
433 virtual std::shared_ptr<Mesh> union_instr(const CSGScope& scope);
434
435 /**
436 * \brief Computes the union of two or more meshes.
437 * \param[in] args the list of meshes to be unioned,
438 * as the function parameters (this function takes an arbitrary number
439 * of parameters).
440 * \return a mesh with the union of the meshes in \p args.
441 */
442 template <typename...Types> std::shared_ptr<Mesh> union_instr(Types...args) {
443 CSGScope scope;
444 scope.append(args...);
445 return union_instr(scope);
446 }
447
448 /**
449 * \brief Computes the intersection between two or more meshes.
450 * \param[in] scope the meshes
451 * \return a mesh with the intersection of the meshes in \p scope
452 */
453 virtual std::shared_ptr<Mesh> intersection(const CSGScope& scope);
454
455 /**
456 * \brief Computes the mutual intersection between meshes
457 * \param[in] args the list of meshes to be intersected
458 * as the function parameters (this function takes an arbitrary number
459 * of parameters).
460 * \return a mesh with the mutual intersection of the meshes in \p args
461 */
462 template <typename...Types> std::shared_ptr<Mesh>
463 intersection(Types...args) {
464 CSGScope scope;
465 scope.append(args...);
466 return intersection(scope);
467 }
468
469 /**
470 * \brief Computes the intersection between two meshes.
471 * \details If \p scope contains more than two meshes, it computes
472 * the difference between the first mesh and the union of the rest.
473 * \param[in] scope the meshes
474 */
475 virtual std::shared_ptr<Mesh> difference(const CSGScope& scope);
476
477 /**
478 * \brief Computes the difference between meshes
479 * \details If \p args contains more than two meshes, it computes
480 * the difference between the first mesh and the union of the rest.
481 * \param[in] args the list of meshes
482 * as the function parameters (this function takes an arbitrary number
483 * of parameters).
484 * \return a mesh with the difference between the meshes in \p args
485 */
486 template <typename...Types> std::shared_ptr<Mesh>
487 4 difference(Types...args) {
488 CSGScope scope;
489
2/34
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
✗ Branch 30 not taken.
✗ Branch 31 not taken.
✗ Branch 32 not taken.
✗ Branch 33 not taken.
✗ Branch 34 not taken.
8 scope.append(args...);
490
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
8 return difference(scope);
491 }
492
493 /**
494 * \brief synonym for union.
495 * \details Maybe there's something I did not understand in
496 * OpenSCAD, but I do not see the difference between group
497 * and union.
498 */
499 virtual std::shared_ptr<Mesh> group(const CSGScope& scope);
500
501 /**
502 * \brief Groups several meshes into a single one and sets their
503 * color.
504 * \param[in] color the color, as r,g,b,a.
505 * \details ignored for now, just behaves as group().
506 */
507 virtual std::shared_ptr<Mesh> color(vec4 color, const CSGScope& scope);
508
509 /**
510 * \brief Computes the convex hull of several meshes.
511 * \param[in] scope the meshes
512 */
513 virtual std::shared_ptr<Mesh> hull(const CSGScope& scope);
514
515 /**
516 * \brief Computes a 3D extrusion from a 2D shape
517 * \param[in] scope one or more 2D shapes
518 * \param[in] height total height of the extrusion
519 * \param[in] center if set, z will go from -height/2 to height/2,
520 * else from 0 to height
521 * \param[in] scale scaling factor to be applied to x and y coordinates
522 * when reaching \p height
523 * \param[in] slices number of slices along the z axis
524 * \param[in] twist rotation to be applied when sweeping, in degrees
525 */
526 virtual std::shared_ptr<Mesh> linear_extrude(
527 const CSGScope& scope,
528 double height = 1.0,
529 bool center = false,
530 vec2 scale = vec2(1.0,1.0),
531 index_t slices = 0,
532 double twist = 0.0
533 );
534
535 /**
536 * \brief Computes a 3D extrusion from a 2D shape
537 * \param[in] scope one or more 2D shapes. Everything should be on the
538 * same side of the Y axis, preferably the positive side.
539 * \param[in] angle optional angle
540 */
541 virtual std::shared_ptr<Mesh> rotate_extrude(
542 const CSGScope& scope, double angle = 360.0
543 );
544
545 /**
546 * \brief Creates a 2D mesh from 3D mesh.
547 * \param[in] cut if set, computes the boundary of the intersection
548 * between the object and the X,Y plane, else computes the boundary
549 * of the projection.
550 */
551 virtual std::shared_ptr<Mesh> projection(const CSGScope& scope, bool cut);
552
553 /**
554 * \brief Computes the Minkowski sum of meshes.
555 */
556 virtual std::shared_ptr<Mesh> minkowski(const CSGScope& scope);
557
558 /**
559 * \brief Appends all meshes in scope into a unique mesh,
560 * without testing for intersections.
561 * \details Prepares operand bits for a subsequent CSG operation.
562 */
563 virtual std::shared_ptr<Mesh> append(const CSGScope& scope);
564
565 /****** AbstractCSGBuilder API ********************************/
566
567 void add_object(const std::string& object, const ArgList& args) override;
568 void begin_instruction() override;
569 void end_instruction(
570 const std::string& instruction, const ArgList& args
571 ) override;
572
573 /****** Objects (AbstractCSGBuilder API) *********************/
574
575 void add_square(const ArgList& args) override;
576 void add_circle(const ArgList& args) override;
577 void add_cube(const ArgList& args) override;
578 void add_sphere(const ArgList& args) override;
579 void add_cylinder(const ArgList& args) override;
580 void add_polyhedron(const ArgList& args) override;
581 void add_polygon(const ArgList& args) override;
582 void add_import(const ArgList& args) override;
583 void add_surface(const ArgList& args) override;
584 void add_text(const ArgList& args) override;
585
586 /****** Instructions (AbstractCSGBuilder API) ****************/
587
588 void eval_multmatrix(const ArgList& args) override;
589 void eval_translate(const ArgList& args) override;
590 void eval_rotate(const ArgList& args) override;
591 void eval_scale(const ArgList& args) override;
592 void eval_resize(const ArgList& args) override;
593 void eval_union(const ArgList& args) override;
594 void eval_intersection(const ArgList& args) override;
595 void eval_difference(const ArgList& args) override;
596 void eval_group(const ArgList& args) override;
597 void eval_color(const ArgList& args) override;
598 void eval_hull(const ArgList& args) override;
599 void eval_linear_extrude(const ArgList& args) override;
600 void eval_rotate_extrude(const ArgList& args) override;
601 void eval_projection(const ArgList& args) override;
602 void eval_minkowski(const ArgList& args) override;
603 void eval_render(const ArgList& args) override;
604
605 /****** Commodity functions **********************************/
606
607 /**
608 * \brief Builds a translation matrix
609 * \param[in] T the translation matrix
610 * \return a 4x4 homogeneous coordinate translation matrix
611 */
612 static mat4 translation_matrix(const vec3& T) {
613 return mat4{{1, 0, 0, T.x},
614 {0, 1, 0, T.y},
615 {0, 0, 1, T.z},
616 {0, 0, 0, 1 }};
617 }
618
619 /**
620 * \brief Builds a scaling matrix
621 * \param[in] sx , sy , sz scaling along each axis
622 * \return a 4x4 homogeneous coordinate scaling matrix
623 */
624 static mat4 scaling_matrix(double sx, double sy, double sz) {
625 return mat4{{sx, 0, 0, 0},
626 {0, sy, 0, 0},
627 {0, 0, sz, 0},
628 {0, 0, 0, 1}};
629 }
630
631 /**
632 * \brief Builds a rotation matrix
633 * \param[in] axis rotation axis
634 * \param[in] angle rotation angle in degrees
635 * \return a 4x4 homogeneous coordinate rotation matrix
636 */
637 static mat4 rotation_matrix(double angle, vec3 axis = {0.0, 0.0, 0.0}) {
638 if(axis.x == 0.0 && axis.y == 0.0 && axis.z == 0.0) {
639 axis.z = 1.0; // special case, OpenSCAD convention
640 }
641 vec3 N = normalize(axis);
642 double x = N.x;
643 double y = N.y;
644 double z = N.z;
645 angle = angle * M_PI / 180;
646 double s = sin(angle);
647 double c = cos(angle);
648 double t = 1.0-c;
649 return mat4{{t*x*x + c, t*x*y - s*z, t*x*z + s*y, 0.0},
650 {t*x*y + s*z, t*y*y + c, t*y*z - s*x, 0.0},
651 {t*x*z - s*y, t*y*z + s*x, t*z*z + c, 0.0},
652 {0.0, 0.0, 0.0, 1.0}};
653 }
654
655 /**
656 * \brief Builds a rotation matrix
657 * \param[in] angles rotation angles around each axis in degrees
658 * \return a 4x4 homogeneous coordinate rotation matrix
659 */
660 static mat4 rotation_matrix(const vec3& angles) {
661 double a = angles[0] * M_PI / 180.0;
662 double b = angles[1] * M_PI / 180.0;
663 double c = angles[2] * M_PI / 180.0;
664 double sa = sin(a); double ca = cos(a);
665 double sb = sin(b); double cb = cos(b);
666 double sc = sin(c); double cc = cos(c);
667 return mat4{{cc*cb, cc*sb*sa - sc*ca, cc*sb*ca + sc*sa, 0.0},
668 {sc*cb, sc*sb*sa + cc*ca, sc*sb*ca - cc*sa, 0.0},
669 { -sb, cb*sa, cb*ca, 0.0},
670 { 0.0, 0.0, 0.0, 1.0}};
671 }
672
673 /**
674 * \brief Commodity function for translating a CSGScope using OpenSCAD syntax
675 * \param[in] T translation vector
676 * \param[in] scope a scope with the list of meshes to be transformed
677 * \return a mesh with the union of the transformed meshes in \p scope
678 * \see multmatrix
679 */
680 std::shared_ptr<Mesh> translate(const vec3& T, const CSGScope& scope) {
681 return multmatrix(translation_matrix(T),scope);
682 }
683
684 /**
685 * \brief Commodity function for 2D-rotating a CSGScope using OpenSCAD syntax
686 * \param[in] angle rotation angle in degrees
687 * \param[in] scope a scope with the list of meshes to be transformed
688 * \return a mesh with the union of the transformed meshes in \p scope
689 * \see multmatrix
690 */
691 std::shared_ptr<Mesh> rotate(double angle, const CSGScope& scope) {
692 return multmatrix(rotation_matrix(angle),scope);
693 }
694
695 /**
696 * \brief Commodity function for rotating a CSGScope using OpenSCAD syntax
697 * \details if \p angle is 0, then \p axis corresponds to the three rotation
698 * angles around x,y and z
699 * \param[in] angle rotation angle in degrees
700 * \param[in] axis rotation axis
701 * \param[in] scope a scope with the list of meshes to be transformed
702 * \return a mesh with the union of the transformed meshes in \p scope
703 * \see multmatrix
704 */
705 std::shared_ptr<Mesh> rotate(
706 double angle, const vec3& axis, const CSGScope& scope
707 ) {
708 return multmatrix(rotation_matrix(angle,axis),scope);
709 }
710
711 /**
712 * \brief Commodity function for rotating a CSGScope using OpenSCAD syntax
713 * \param[in] angles rotation angles along the three axes in degrees
714 * \param[in] scope a scope with the list of meshes to be transformed
715 * \return a mesh with the union of the transformed meshes in \p scope
716 * \see multmatrix
717 */
718 std::shared_ptr<Mesh> rotate(const vec3& angles, const CSGScope& scope) {
719 return multmatrix(rotation_matrix(angles),scope);
720 }
721
722 /**
723 * \brief Commodity function for scaling a CSGScope using OpenSCAD syntax
724 * \param[in] sx , sy , sz scaling factors along each axis
725 * \param[in] scope a scope with the list of meshes to be transformed
726 * \return a mesh with the union of the transformed meshes in \p scope
727 * \see multmatrix
728 */
729 std::shared_ptr<Mesh> scale(
730 double sx, double sy, double sz, const CSGScope& scope
731 ) {
732 return multmatrix(scaling_matrix(sx,sy,sz),scope);
733 }
734
735 /**************************/
736
737 /**
738 * \brief If set, compute constrained Delaunay triangulation
739 * in the intersected triangles. If there are intersections
740 * in coplanar facets, it guarantees uniqueness of their
741 * triangulation. Default is set.
742 */
743 void set_delaunay(bool x) {
744
1/2
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
20 delaunay_ = x;
745 }
746
747 /**
748 * \brief detect and compute intersections between facets that share
749 * a facet or an edge. Set to false if input is a set of conformal
750 * meshes. Default is set.
751 */
752 void set_detect_intersecting_neighbors(bool x) {
753
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
20 detect_intersecting_neighbors_ = x;
754 }
755
756 /**
757 * \brief Specifies whether coplanar facets should be simplified
758 * \param[in] x if set, coplanar facets are simplified, else they
759 * are kept as is (faster but generates many triangles). Default
760 * is set.
761 * \param[in] angle_tolerance (in degree) the pairs of
762 * adjacent facets with normals that make an angle smaller than
763 * this threshold as considered to be coplanar.
764 */
765 void set_simplify_coplanar_facets(bool x, double angle_tolerance=0.0) {
766 20 simplify_coplanar_facets_ = x;
767
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
20 coplanar_angle_tolerance_ = angle_tolerance;
768 }
769
770 /**
771 * \brief Sets fast union mode
772 * \details In fast union mode, all intersections are computed and the
773 * external shell is kept. It may give incorrect result if an object
774 * is floating inside another one (completely included).
775 * \param[in] x true if fast union mode should be used, false otherwise.
776 */
777 void set_fast_union(bool x) {
778
1/2
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
20 fast_union_ = x;
779 }
780
781
782 /***** misc ********/
783
784 /**
785 * \brief Sets noop mode
786 * \details In noop mode, all CSG operations (union, intersection,
787 * difference) are replaced with append. Useful for debugging CSG trees.
788 * \param[in] x whether noop mode should be set
789 */
790 void set_noop(bool x) {
791
1/2
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
20 noop_ = x;
792 }
793
794
795 /**
796 * \brief Computes the bounding box of a mesh
797 * \param[in] mesh the mesh
798 * \return the bounding box. If the mesh is 2D, then the z components
799 * of the bounding box are set to 0
800 */
801 static Box3d get_bbox(const std::shared_ptr<Mesh>& mesh);
802
803 /**
804 * \brief Computes the bounding box of a mesh
805 * \param[in] mesh the mesh
806 * \return the bounding box as a pair of minimum, maximum bounds.
807 * If the mesh is 2D, then the z components of the bounding box
808 * are set to 0
809 */
810 106 static std::pair<vec3, vec3> get_bbox_bounds(
811 const std::shared_ptr<Mesh>& mesh
812 ) {
813 106 Box3d result = get_bbox(mesh);
814 106 return std::make_pair(vec3(result.xyz_min), vec3(result.xyz_max));
815 }
816
817 protected:
818
819 std::shared_ptr<Mesh> surface_with_OpenSCAD(
820 const std::filesystem::path& filename, bool center, bool invert
821 );
822
823 std::shared_ptr<Mesh> text_with_OpenSCAD(
824 const std::string& text,
825 double size = 10.0,
826 const std::string& font = "",
827 const std::string& halign = "left",
828 const std::string& valign = "baseline",
829 double spacing = 1.0,
830 const std::string& direction = "ltr",
831 const std::string& language = "en",
832 const std::string& script = "latin"
833 );
834
835 /**** Lower-level functions ****/
836
837 /**
838 * \brief Finds a file in the path
839 * \param[in,out] filename the file to be found. On exit, the complete
840 * path to the file if found
841 * \retval true if the file could be found
842 * \retval false otherwise
843 */
844 bool find_file(std::filesystem::path& filename);
845
846 /**
847 * \brief Gets the current path
848 * \return the latest directory pushed onto the file path
849 */
850
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 21 times.
21 const std::filesystem::path& current_path() {
851
1/6
✗ Branch 0 not taken.
✓ Branch 1 taken 21 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
21 geo_assert(file_path_.size() != 0);
852 21 return *(file_path_.rbegin());
853 }
854
855 /**
856 * \brief For the file formats that are not supported by geogram,
857 * get help from OpenSCAD to convert them.
858 * \details Converts STEP files.
859 */
860 std::shared_ptr<Mesh> import_with_openSCAD(
861 const std::filesystem::path& filename, const std::string& layer="",
862 index_t timestamp=0
863 );
864
865 /**
866 * \brief Apply a CSG operation to a mesh
867 * \details Default implementation works in 2D (based on triangulate())
868 * and does nothing in 3D (to be overriden by user).
869 * Each triangle has an "operand bit" indicating to which input operand
870 * it belongs to, set to (1u << operand_id).
871 * \param[in,out] mesh the mesh
872 * \param[in] boolean_expr the operation to be applied
873 * - "union"
874 * - "intersection"
875 * - a general boolean expression, where:
876 * - variables are x0 ... x31 (they correspond to input operands)
877 * - operators are +,-,*
878 * - there can be parentheses
879 */
880 virtual void do_CSG(
881 std::shared_ptr<Mesh>& mesh, const std::string& boolean_expr
882 );
883
884 /**
885 * \brief Triangulates a 2D mesh.
886 * \details Computes a constrained Delaunay triangulation from the edges
887 * of the mesh, then classifies the triangles using a boolean expression.
888 * Each edge has an "operand bit" indicating to which input operand
889 * it belongs to, set to (1u << operand_id). Used to implement
890 * CSG operations in 2D.
891 * \param[in,out] mesh the input is a set of vertices and edges.
892 * The output has a set of triangles inside the polygons defined by
893 * the edges.
894 * \param[in] boolean_expr optional operation to be applied, can be one of
895 * - "union" (default)
896 * - "intersection"
897 * - a general boolean expression, where:
898 * - variables are x0 ... x31 (they correspond to input operands)
899 * - operators are +,-,*
900 * - there can be parentheses
901 * - "union_cnstr_operand_bits_is_operand_id", same as "union"
902 * but edges operand bits are set to
903 * operand_id (instead of 1u << operand_id). This allows for an
904 * unlimited number of operands (as opposed to operand bits where
905 * it is limited to 32). It is used to implement projection(cut=false).
906 */
907 virtual void triangulate(
908 std::shared_ptr<Mesh>& mesh, const std::string& boolean_expr
909 );
910
911 /**
912 * \brief Triangulates a 2D mesh
913 * \details This sets all edge operand bits to 1 and then computes a
914 * union using the other flavor of triangulate()
915 */
916 virtual void triangulate(std::shared_ptr<Mesh>& mesh);
917
918 /**
919 * \brief keeps only triangles and vertices embedded in the z=0 plane, and
920 * makes the mesh 2D.
921 * \details This also computes the border and re-triangulates it.
922 * \param[in,out] M a shared pointer to the mesh
923 */
924 void keep_z0_only(std::shared_ptr<Mesh>& M);
925
926 /**
927 * \brief Derived classes may override this function and compute
928 * some cached information, e.g. bounding boxes, stored in the
929 * mesh.
930 */
931 virtual void finalize_mesh(std::shared_ptr<Mesh>& mesh);
932
933 CSGScope& top_scope() {
934 geo_debug_assert(!scope_stack_.empty());
935 return scope_stack_.top();
936 }
937
938 void push_scope() {
939 scope_stack_.emplace();
940 20 }
941
942 void pop_scope() {
943 geo_debug_assert(!scope_stack_.empty());
944 scope_stack_.pop();
945 }
946
947 protected:
948 double STL_epsilon_;
949 index_t max_arity_;
950 bool detect_intersecting_neighbors_;
951 bool delaunay_;
952 bool simplify_coplanar_facets_;
953 double coplanar_angle_tolerance_;
954 bool fast_union_;
955 bool noop_;
956 std::shared_ptr<Mesh> empty_mesh_;
957 std::shared_ptr<Mesh> result_;
958 std::stack<CSGScope> scope_stack_;
959
960 friend class CSGCompiler;
961 };
962
963 }
964
965 #endif
966