GCC Code Coverage Report


Directory: ./
File: lib/geogram/mesh/mesh_CSG_builder.cpp
Date: 2026-09-07 02:36:43
Exec Total Coverage
Lines: 750 1135 66.1%
Functions: 79 123 64.2%
Branches: 736 1968 37.4%

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 #include <geogram/mesh/mesh_CSG_builder.h>
41 #include <geogram/mesh/mesh_surface_intersection.h>
42 #include <geogram/mesh/mesh_io.h>
43 #include <geogram/mesh/mesh_repair.h>
44 #include <geogram/mesh/mesh_fill_holes.h>
45 #include <geogram/mesh/mesh_convex_hull.h>
46 #include <geogram/mesh/mesh_minkowski.h>
47 #include <geogram/delaunay/parallel_delaunay_3d.h>
48 #include <geogram/delaunay/CDT_2d.h>
49 #include <geogram/image/image.h>
50 #include <geogram/image/image_library.h>
51 #include <geogram/basic/line_stream.h>
52 #include <geogram/basic/command_line.h>
53
54 namespace {
55 using namespace GEO;
56
57 /**
58 * \brief Tests whether the meshes in a scope may have intersections
59 * \param[in] scope a scope
60 * \retval true if there may be some intersections between the meshes
61 * \retval false if there is for sure no intersection between the meshes
62 */
63 39 bool may_have_intersections(const CSGScope& scope) {
64
1/2
✓ Branch 2 taken 39 times.
✗ Branch 3 not taken.
39 std::vector<Box3d> box(scope.size());
65
2/2
✓ Branch 1 taken 199 times.
✓ Branch 2 taken 39 times.
238 for(index_t i=0; i<scope.size(); ++i) {
66
2/4
✓ Branch 1 taken 199 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 199 times.
✗ Branch 5 not taken.
199 box[i] = CSGBuilder::get_bbox(scope[i]);
67 }
68
2/2
✓ Branch 1 taken 86 times.
✓ Branch 2 taken 16 times.
102 for(index_t i=0; i<scope.size(); ++i) {
69
2/2
✓ Branch 1 taken 130 times.
✓ Branch 2 taken 63 times.
193 for(index_t j=i+1; j<scope.size(); ++j) {
70
2/2
✓ Branch 3 taken 23 times.
✓ Branch 4 taken 107 times.
130 if(bboxes_overlap(box[i], box[j])) {
71 23 return true;
72 }
73 }
74 }
75 16 return false;
76 39 }
77 }
78
79
80 namespace GEO {
81
82 22 AbstractCSGBuilder::AbstractCSGBuilder() {
83
1/2
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
22 reset_defaults();
84
85 22 verbose_ = false;
86 22 detailed_verbose_ = false;
87 22 warnings_ = false;
88
89 #define DECLARE_OBJECT(obj) \
90 object_funcs_[#obj] = [this](const ArgList& args) { \
91 this->add_##obj(args); \
92 }
93
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
66 DECLARE_OBJECT(square);
94
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
68 DECLARE_OBJECT(circle);
95
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
91 DECLARE_OBJECT(cube);
96
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
80 DECLARE_OBJECT(sphere);
97
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
103 DECLARE_OBJECT(cylinder);
98
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
45 DECLARE_OBJECT(polyhedron);
99
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
77 DECLARE_OBJECT(polygon);
100
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
68 DECLARE_OBJECT(import);
101
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
46 DECLARE_OBJECT(surface);
102
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
44 DECLARE_OBJECT(text);
103
104 #define DECLARE_INSTRUCTION(instr) \
105 instruction_funcs_[#instr] = [this](const ArgList& args) { \
106 this->eval_##instr(args); \
107 }
108
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
298 DECLARE_INSTRUCTION(multmatrix);
109
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
44 DECLARE_INSTRUCTION(translate);
110
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
44 DECLARE_INSTRUCTION(rotate);
111
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
44 DECLARE_INSTRUCTION(scale);
112
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
44 DECLARE_INSTRUCTION(resize);
113
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
62 DECLARE_INSTRUCTION(intersection);
114
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
72 DECLARE_INSTRUCTION(difference);
115
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
152 DECLARE_INSTRUCTION(group);
116
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
45 DECLARE_INSTRUCTION(color);
117
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
45 DECLARE_INSTRUCTION(hull);
118
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
68 DECLARE_INSTRUCTION(linear_extrude);
119
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
47 DECLARE_INSTRUCTION(rotate_extrude);
120
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
44 DECLARE_INSTRUCTION(projection);
121
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
44 DECLARE_INSTRUCTION(minkowski);
122
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
57 DECLARE_INSTRUCTION(union);
123
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
48 DECLARE_INSTRUCTION(render);
124 22 }
125
126 44 AbstractCSGBuilder::~AbstractCSGBuilder() {
127 44 }
128
129 724 void AbstractCSGBuilder::reset_defaults() {
130 724 fa_ = DEFAULT_FA;
131 724 fs_ = DEFAULT_FS;
132 724 fn_ = DEFAULT_FN;
133 724 }
134
135 248 void AbstractCSGBuilder::add_object(
136 const std::string& object, const ArgList& args
137 ) {
138
1/2
✓ Branch 1 taken 248 times.
✗ Branch 2 not taken.
248 auto object_func = object_funcs_.find(object);
139
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 248 times.
248 if(object_func == object_funcs_.end()) {
140 error(object+ ": no such object");
141 }
142
1/2
✓ Branch 2 taken 248 times.
✗ Branch 3 not taken.
248 object_func->second(args);
143 248 }
144
145 454 void AbstractCSGBuilder::begin_instruction() {
146 454 }
147
148 454 void AbstractCSGBuilder::end_instruction(
149 const std::string& instruction, const ArgList& args
150 ) {
151
1/2
✓ Branch 1 taken 454 times.
✗ Branch 2 not taken.
454 auto instruction_func = instruction_funcs_.find(instruction);
152
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 454 times.
454 if(instruction_func == instruction_funcs_.end()) {
153 error(instruction+ ": no such instruction");
154 }
155
1/2
✓ Branch 2 taken 454 times.
✗ Branch 3 not taken.
454 instruction_func->second(args);
156 454 }
157
158 void AbstractCSGBuilder::add_square(const ArgList& args) {
159 geo_argused(args);
160 error("not implemented");
161 }
162
163 void AbstractCSGBuilder::add_circle(const ArgList& args) {
164 geo_argused(args);
165 error("not implemented");
166 }
167
168 void AbstractCSGBuilder::add_cube(const ArgList& args) {
169 geo_argused(args);
170 error("not implemented");
171 }
172
173 void AbstractCSGBuilder::add_sphere(const ArgList& args) {
174 geo_argused(args);
175 error("not implemented");
176 }
177
178 void AbstractCSGBuilder::add_cylinder(const ArgList& args) {
179 geo_argused(args);
180 error("not implemented");
181 }
182
183 void AbstractCSGBuilder::add_polyhedron(const ArgList& args) {
184 geo_argused(args);
185 error("not implemented");
186 }
187
188 void AbstractCSGBuilder::add_polygon(const ArgList& args) {
189 geo_argused(args);
190 error("not implemented");
191 }
192
193 void AbstractCSGBuilder::add_import(const ArgList& args) {
194 geo_argused(args);
195 error("not implemented");
196 }
197
198 void AbstractCSGBuilder::add_surface(const ArgList& args) {
199 geo_argused(args);
200 error("not implemented");
201 }
202
203 void AbstractCSGBuilder::add_text(const ArgList& args) {
204 geo_argused(args);
205 error("not implemented");
206 }
207
208 void AbstractCSGBuilder::eval_multmatrix(const ArgList& args) {
209 geo_argused(args);
210 error("not implemented");
211 }
212
213 void AbstractCSGBuilder::eval_translate(const ArgList& args) {
214 geo_argused(args);
215 error("not implemented");
216 }
217
218 void AbstractCSGBuilder::eval_rotate(const ArgList& args) {
219 geo_argused(args);
220 error("not implemented");
221 }
222
223 void AbstractCSGBuilder::eval_scale(const ArgList& args) {
224 geo_argused(args);
225 error("not implemented");
226 }
227
228 void AbstractCSGBuilder::eval_resize(const ArgList& args) {
229 geo_argused(args);
230 error("not implemented");
231 }
232
233 void AbstractCSGBuilder::eval_union(const ArgList& args) {
234 geo_argused(args);
235 error("not implemented");
236 }
237
238 void AbstractCSGBuilder::eval_intersection(const ArgList& args) {
239 geo_argused(args);
240 error("not implemented");
241 }
242
243 void AbstractCSGBuilder::eval_difference(const ArgList& args) {
244 geo_argused(args);
245 error("not implemented");
246 }
247
248 void AbstractCSGBuilder::eval_group(const ArgList& args) {
249 geo_argused(args);
250 error("not implemented");
251 }
252
253 void AbstractCSGBuilder::eval_color(const ArgList& args) {
254 geo_argused(args);
255 error("not implemented");
256 }
257
258 void AbstractCSGBuilder::eval_hull(const ArgList& args) {
259 geo_argused(args);
260 error("not implemented");
261 }
262
263 void AbstractCSGBuilder::eval_linear_extrude(const ArgList& args) {
264 geo_argused(args);
265 error("not implemented");
266 }
267
268 void AbstractCSGBuilder::eval_rotate_extrude(const ArgList& args) {
269 geo_argused(args);
270 error("not implemented");
271 }
272
273 void AbstractCSGBuilder::eval_projection(const ArgList& args) {
274 geo_argused(args);
275 error("not implemented");
276 }
277
278 void AbstractCSGBuilder::eval_minkowski(const ArgList& args) {
279 geo_argused(args);
280 error("not implemented");
281 }
282
283 void AbstractCSGBuilder::eval_render(const ArgList& args) {
284 geo_argused(args);
285 error("not implemented");
286 }
287
288 /************************************************************************/
289
290
1/2
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
22 CSGBuilder::CSGBuilder() {
291
1/2
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
22 reset_file_path();
292 22 STL_epsilon_ = 1e-6;
293 22 max_arity_ = 32;
294 22 simplify_coplanar_facets_ = true;
295 22 coplanar_angle_tolerance_ = 0.0;
296 22 delaunay_ = true;
297 22 detect_intersecting_neighbors_ = true;
298 22 fast_union_ = false;
299 22 noop_ = false;
300
1/2
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
22 empty_mesh_ = std::make_shared<Mesh>();
301
1/2
✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
22 empty_mesh_->vertices.set_dimension(2);
302 22 }
303
304 44 CSGBuilder::~CSGBuilder() {
305 44 }
306
307 /************************************************************/
308
309 22 std::shared_ptr<Mesh> CSGBuilder::square(vec2 size, bool center) {
310
311 22 double x1 = 0.0;
312 22 double y1 = 0.0;
313 22 double x2 = size.x;
314 22 double y2 = size.y;
315
316
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 13 times.
22 if(center) {
317 9 x1 -= size.x/2.0;
318 9 x2 -= size.x/2.0;
319 9 y1 -= size.y/2.0;
320 9 y2 -= size.y/2.0;
321 }
322
323
324
2/4
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 22 times.
22 if(size.x <= 0.0 || size.y <= 0.0) {
325 if(warnings_) {
326 Logger::warn("CSG")
327 << "square with negative size (returning empty shape)"
328 << std::endl;
329 }
330 return empty_mesh_;
331 }
332
333
1/2
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
22 std::shared_ptr<Mesh> M = std::make_shared<Mesh>();
334
1/2
✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
22 M->vertices.set_dimension(2);
335
336
1/2
✓ Branch 3 taken 22 times.
✗ Branch 4 not taken.
22 M->vertices.create_vertex(vec2(x1,y1));
337
1/2
✓ Branch 3 taken 22 times.
✗ Branch 4 not taken.
22 M->vertices.create_vertex(vec2(x2,y1));
338
1/2
✓ Branch 3 taken 22 times.
✗ Branch 4 not taken.
22 M->vertices.create_vertex(vec2(x1,y2));
339
1/2
✓ Branch 3 taken 22 times.
✗ Branch 4 not taken.
22 M->vertices.create_vertex(vec2(x2,y2));
340
341
1/2
✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
22 M->facets.create_triangle(0,1,3);
342
1/2
✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
22 M->facets.create_triangle(0,3,2);
343
344
1/2
✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
22 M->edges.create_edge(0,1);
345
1/2
✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
22 M->edges.create_edge(1,3);
346
1/2
✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
22 M->edges.create_edge(3,2);
347
1/2
✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
22 M->edges.create_edge(2,0);
348
349
1/2
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
22 finalize_mesh(M);
350
351 22 return M;
352 22 }
353
354 119 std::shared_ptr<Mesh> CSGBuilder::circle(double r, index_t nu) {
355
356
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 95 times.
119 if(nu == 0) {
357
1/2
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
24 nu = index_t(GEOCSG::get_fragments_from_r(r, fn_, fs_, fa_));
358 }
359 119 nu = std::max(nu, index_t(3));
360
361
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 119 times.
119 if(r <= 0.0) {
362 if(warnings_) {
363 Logger::warn("CSG")
364 << "circle with negative radius (returning empty shape)"
365 << std::endl;
366 }
367 return empty_mesh_;
368 }
369
370
1/2
✓ Branch 1 taken 119 times.
✗ Branch 2 not taken.
119 std::shared_ptr<Mesh> M = std::make_shared<Mesh>();
371
1/2
✓ Branch 2 taken 119 times.
✗ Branch 3 not taken.
119 M->vertices.set_dimension(2);
372
373
2/2
✓ Branch 0 taken 2916 times.
✓ Branch 1 taken 119 times.
3035 for(index_t u=0; u<nu; ++u) {
374 2916 double theta = double(u)*2.0*M_PI/double(nu);
375 2916 double ctheta = cos(theta);
376 2916 double stheta = sin(theta);
377 2916 double x = ctheta*r;
378 2916 double y = stheta*r;
379
1/2
✓ Branch 3 taken 2916 times.
✗ Branch 4 not taken.
2916 M->vertices.create_vertex(vec2(x,y));
380 }
381
382 // zigzag triangulation
383 {
384 119 index_t low = 0;
385 119 index_t high = nu-1;
386 for(;;) {
387
2/2
✓ Branch 0 taken 63 times.
✓ Branch 1 taken 1367 times.
1430 if(low+1==high) {
388 63 break;
389 }
390
1/2
✓ Branch 2 taken 1367 times.
✗ Branch 3 not taken.
1367 M->facets.create_triangle(low, low+1, high);
391 1367 ++low;
392
2/2
✓ Branch 0 taken 56 times.
✓ Branch 1 taken 1311 times.
1367 if(high-1==low) {
393 56 break;
394 }
395
1/2
✓ Branch 2 taken 1311 times.
✗ Branch 3 not taken.
1311 M->facets.create_triangle(low, high-1, high);
396 1311 --high;
397 }
398 }
399
400
2/2
✓ Branch 0 taken 2916 times.
✓ Branch 1 taken 119 times.
3035 for(index_t u=0; u<nu; ++u) {
401
1/2
✓ Branch 2 taken 2916 times.
✗ Branch 3 not taken.
2916 M->edges.create_edge(u, (u+1)%nu);
402 }
403
404
1/2
✓ Branch 1 taken 119 times.
✗ Branch 2 not taken.
119 finalize_mesh(M);
405
406 119 return M;
407 119 }
408
409 51 std::shared_ptr<Mesh> CSGBuilder::cube(vec3 size, bool center) {
410
411 51 double x1 = 0.0;
412 51 double y1 = 0.0;
413 51 double z1 = 0.0;
414 51 double x2 = size.x;
415 51 double y2 = size.y;
416 51 double z2 = size.z;
417
418
2/2
✓ Branch 0 taken 47 times.
✓ Branch 1 taken 4 times.
51 if(center) {
419 47 x1 -= size.x/2.0;
420 47 x2 -= size.x/2.0;
421 47 y1 -= size.y/2.0;
422 47 y2 -= size.y/2.0;
423 47 z1 -= size.z/2.0;
424 47 z2 -= size.z/2.0;
425 }
426
427
3/6
✓ Branch 0 taken 51 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 51 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 51 times.
51 if(size.x <= 0.0 || size.y <= 0.0 || size.z <= 0.0) {
428 if(warnings_) {
429 Logger::warn("CSG")
430 << "cube with negative size (returning empty shape)"
431 << std::endl;
432 }
433 return empty_mesh_;
434 }
435
436
1/2
✓ Branch 1 taken 51 times.
✗ Branch 2 not taken.
51 std::shared_ptr<Mesh> M = std::make_shared<Mesh>();
437
438
1/2
✓ Branch 3 taken 51 times.
✗ Branch 4 not taken.
51 M->vertices.create_vertex(vec3(x1,y1,z1));
439
1/2
✓ Branch 3 taken 51 times.
✗ Branch 4 not taken.
51 M->vertices.create_vertex(vec3(x2,y1,z1));
440
1/2
✓ Branch 3 taken 51 times.
✗ Branch 4 not taken.
51 M->vertices.create_vertex(vec3(x1,y2,z1));
441
1/2
✓ Branch 3 taken 51 times.
✗ Branch 4 not taken.
51 M->vertices.create_vertex(vec3(x2,y2,z1));
442
1/2
✓ Branch 3 taken 51 times.
✗ Branch 4 not taken.
51 M->vertices.create_vertex(vec3(x1,y1,z2));
443
1/2
✓ Branch 3 taken 51 times.
✗ Branch 4 not taken.
51 M->vertices.create_vertex(vec3(x2,y1,z2));
444
1/2
✓ Branch 3 taken 51 times.
✗ Branch 4 not taken.
51 M->vertices.create_vertex(vec3(x1,y2,z2));
445
1/2
✓ Branch 3 taken 51 times.
✗ Branch 4 not taken.
51 M->vertices.create_vertex(vec3(x2,y2,z2));
446
447
1/2
✓ Branch 2 taken 51 times.
✗ Branch 3 not taken.
51 M->facets.create_triangle(7,3,6);
448
1/2
✓ Branch 2 taken 51 times.
✗ Branch 3 not taken.
51 M->facets.create_triangle(6,3,2);
449
1/2
✓ Branch 2 taken 51 times.
✗ Branch 3 not taken.
51 M->facets.create_triangle(7,5,3);
450
1/2
✓ Branch 2 taken 51 times.
✗ Branch 3 not taken.
51 M->facets.create_triangle(3,5,1);
451
1/2
✓ Branch 2 taken 51 times.
✗ Branch 3 not taken.
51 M->facets.create_triangle(3,1,2);
452
1/2
✓ Branch 2 taken 51 times.
✗ Branch 3 not taken.
51 M->facets.create_triangle(2,1,0);
453
1/2
✓ Branch 2 taken 51 times.
✗ Branch 3 not taken.
51 M->facets.create_triangle(1,5,0);
454
1/2
✓ Branch 2 taken 51 times.
✗ Branch 3 not taken.
51 M->facets.create_triangle(0,5,4);
455
1/2
✓ Branch 2 taken 51 times.
✗ Branch 3 not taken.
51 M->facets.create_triangle(2,0,6);
456
1/2
✓ Branch 2 taken 51 times.
✗ Branch 3 not taken.
51 M->facets.create_triangle(6,0,4);
457
1/2
✓ Branch 2 taken 51 times.
✗ Branch 3 not taken.
51 M->facets.create_triangle(6,4,7);
458
1/2
✓ Branch 2 taken 51 times.
✗ Branch 3 not taken.
51 M->facets.create_triangle(7,4,5);
459
460
1/2
✓ Branch 2 taken 51 times.
✗ Branch 3 not taken.
51 M->facets.connect();
461
462
1/2
✓ Branch 1 taken 51 times.
✗ Branch 2 not taken.
51 finalize_mesh(M);
463
464 51 return M;
465 51 }
466
467 36 std::shared_ptr<Mesh> CSGBuilder::sphere(double r) {
468
1/2
✓ Branch 1 taken 36 times.
✗ Branch 2 not taken.
36 index_t nu = index_t(GEOCSG::get_fragments_from_r(r,fn_,fs_,fa_));
469 36 index_t nv = nu / 2;
470
2/4
✓ Branch 0 taken 36 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 36 times.
36 if(nu >= 5 && (nu & 1) != 0) {
471 ++nv;
472 }
473
474 36 nu = std::max(nu, index_t(3));
475 36 nv = std::max(nv, index_t(2));
476
477
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 36 times.
36 if(r <= 0.0) {
478 if(warnings_) {
479 Logger::warn("CSG")
480 << "sphere with negative radius (returning empty shape)"
481 << std::endl;
482 }
483 return empty_mesh_;
484 }
485
486
1/2
✓ Branch 1 taken 36 times.
✗ Branch 2 not taken.
36 std::shared_ptr<Mesh> result = circle(1.0, nu);
487
488
1/2
✓ Branch 1 taken 36 times.
✗ Branch 2 not taken.
36 GEOCSG::sweep(
489 result, nv,
490
1/2
✓ Branch 1 taken 36 times.
✗ Branch 2 not taken.
72 [&](index_t u, index_t v)->vec3 {
491 16200 double phi = (double(v) + 0.5)*M_PI/double(nv) - M_PI/2.0;
492 16200 double cphi = cos(phi);
493 16200 double sphi = sin(phi);
494 16200 double ctheta = result->vertices.point(u).x;
495 16200 double stheta = result->vertices.point(u).y;
496 return vec3(
497 32400 r*ctheta*cphi,
498 32400 r*stheta*cphi,
499 16200 r*sphi
500 16200 );
501 }
502 );
503
504
1/2
✓ Branch 2 taken 36 times.
✗ Branch 3 not taken.
36 result->facets.connect();
505
506
1/2
✓ Branch 1 taken 36 times.
✗ Branch 2 not taken.
36 finalize_mesh(result);
507 36 return result;
508 36 }
509
510 59 std::shared_ptr<Mesh> CSGBuilder::cylinder(
511 double h, double r1, double r2, bool center
512 ) {
513 index_t nu = index_t(
514
1/2
✓ Branch 2 taken 59 times.
✗ Branch 3 not taken.
59 GEOCSG::get_fragments_from_r(std::max(r1,r2),fn_,fs_,fa_)
515 59 );
516
517 59 double r[2] = { r1, r2 };
518
519 double z[2] = {
520
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 51 times.
59 center ? -h/2.0 : 0.0,
521 59 center ? h/2.0 : h
522
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 51 times.
59 };
523
524
2/4
✓ Branch 0 taken 59 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 59 times.
59 if(r1 < 0.0 || r2 < 0.0) {
525 if(warnings_) {
526 Logger::warn("CSG")
527 << "cylinder with negative radius (returning empty shape)"
528 << std::endl;
529 }
530 return empty_mesh_;
531 }
532
533 // If first side's capping is a pole, then flip sides
534 // (sweep() supposes that the pole is always on the second side).
535
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 59 times.
59 if(r[0] == 0.0) {
536 std::swap(r[0],r[1]);
537 std::swap(z[0],z[1]);
538 }
539
540
1/2
✓ Branch 1 taken 59 times.
✗ Branch 2 not taken.
59 std::shared_ptr<Mesh> result = circle(1.0, nu);
541
1/2
✓ Branch 1 taken 59 times.
✗ Branch 2 not taken.
118 GEOCSG::sweep(
542 result, 2,
543
1/2
✓ Branch 1 taken 59 times.
✗ Branch 2 not taken.
118 [&](index_t u, index_t v)->vec3 {
544
1/2
✓ Branch 2 taken 2561 times.
✗ Branch 3 not taken.
2561 vec3 p = result->vertices.point(u);
545 return vec3(
546 2561 r[v]*p.x, r[v]*p.y, z[v]
547 5122 );
548 },
549
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 58 times.
59 (r[1] == 0.0) ? GEOCSG::SWEEP_POLE : GEOCSG::SWEEP_CAP
550 );
551
552
1/2
✓ Branch 2 taken 59 times.
✗ Branch 3 not taken.
59 result->facets.connect();
553
554
1/2
✓ Branch 1 taken 59 times.
✗ Branch 2 not taken.
59 finalize_mesh(result);
555 59 return result;
556 59 }
557
558
559 24 std::shared_ptr<Mesh> CSGBuilder::import(
560 const std::filesystem::path& filename, const std::string& layer,
561 index_t timestamp, vec2 origin, vec2 scale
562 ) {
563 24 std::shared_ptr<Mesh> result;
564
1/2
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
24 std::filesystem::path full_filename = filename;
565
2/4
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 24 times.
24 if(!find_file(full_filename)) {
566 Logger::err("CSG") << filename << ": file not found"
567 << std::endl;
568 return empty_mesh_;
569 }
570
571 24 if(
572
8/16
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 5 times.
✓ Branch 8 taken 19 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 5 times.
✓ Branch 12 taken 24 times.
✗ Branch 13 not taken.
✓ Branch 15 taken 19 times.
✓ Branch 16 taken 5 times.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
53 full_filename.extension() == ".dxf" ||
573
7/14
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 5 times.
✓ Branch 8 taken 19 times.
✓ Branch 10 taken 5 times.
✓ Branch 11 taken 19 times.
✓ Branch 13 taken 24 times.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
29 full_filename.extension() == ".DXF"
574 ) {
575
1/2
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
19 result = import_with_openSCAD(full_filename, layer, timestamp);
576 } else {
577
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 result = std::make_shared<Mesh>();
578
3/6
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 5 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 5 times.
✗ Branch 9 not taken.
5 mesh_load(full_filename.string(),*result);
579 }
580
581 24 if(
582
8/16
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 19 times.
✓ Branch 8 taken 5 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 19 times.
✓ Branch 12 taken 24 times.
✗ Branch 13 not taken.
✓ Branch 15 taken 5 times.
✓ Branch 16 taken 19 times.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
67 full_filename.extension() == ".stl" ||
583
7/14
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 19 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 19 times.
✓ Branch 8 taken 5 times.
✓ Branch 10 taken 19 times.
✓ Branch 11 taken 5 times.
✓ Branch 13 taken 24 times.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
43 full_filename.extension() == ".STL"
584 ) {
585 5 MeshRepairMode mode = MESH_REPAIR_DEFAULT;
586
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if(!verbose_) {
587 5 mode = MeshRepairMode(mode | MESH_REPAIR_QUIET);
588 }
589
1/2
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 mesh_repair(*result, mode, STL_epsilon_);
590 }
591
592
3/4
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 19 times.
24 if(result->vertices.dimension() == 3) {
593 5 bool z_all_zero = true;
594
5/10
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 5 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 5 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 9 times.
✗ Branch 12 not taken.
✓ Branch 15 taken 9 times.
✗ Branch 16 not taken.
9 for(const vec3& p : result->vertices.points()) {
595
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 4 times.
9 if(p.z != 0.0) {
596 5 z_all_zero = false;
597 5 break;
598 }
599 }
600
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if(z_all_zero) {
601 result->vertices.set_dimension(2);
602 }
603 }
604
605
3/4
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 19 times.
24 if(result->vertices.dimension() == 3) {
606
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
5 if(!result->facets.are_simplices()) {
607 tessellate_facets(*result, 3);
608 }
609 }
610 // Apply origin and scale, triangulate
611
3/4
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 19 times.
✓ Branch 5 taken 5 times.
24 if(result->vertices.dimension() == 2) {
612
2/2
✓ Branch 5 taken 694 times.
✓ Branch 6 taken 19 times.
713 for(index_t v: result->vertices) {
613
1/2
✓ Branch 2 taken 694 times.
✗ Branch 3 not taken.
694 vec2& p = result->vertices.point<2>(v);
614 694 p = vec2(
615 1388 (p.x - origin.x) * scale.x,
616 694 (p.y - origin.y) * scale.y
617 694 );
618 }
619
1/2
✓ Branch 2 taken 19 times.
✗ Branch 3 not taken.
19 result->facets.compute_borders();
620
1/2
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
19 triangulate(result);
621 }
622
623
1/2
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
24 finalize_mesh(result);
624 24 return result;
625 24 }
626
627 2 std::shared_ptr<Mesh> CSGBuilder::surface(
628 const std::filesystem::path& filename, bool center, bool invert
629 ) {
630 2 return surface_with_OpenSCAD(filename, center, invert);
631 }
632
633 std::shared_ptr<Mesh> CSGBuilder::text(
634 const std::string& text,
635 double size,
636 const std::string& font,
637 const std::string& halign,
638 const std::string& valign,
639 double spacing,
640 const std::string& direction,
641 const std::string& language,
642 const std::string& script
643 ) {
644 return text_with_OpenSCAD(
645 text, size, font, halign, valign, spacing,
646 direction, language, script
647 );
648 }
649
650 std::shared_ptr<Mesh> CSGBuilder::text_with_OpenSCAD(
651 const std::string& text,
652 double size,
653 const std::string& font,
654 const std::string& halign,
655 const std::string& valign,
656 double spacing,
657 const std::string& direction,
658 const std::string& language,
659 const std::string& script
660 ) {
661 GEOCSG::ArgList args;
662 args.add_arg("text", text);
663 args.add_arg("size", size);
664 args.add_arg("font", font);
665 args.add_arg("halign", halign);
666 args.add_arg("valign", valign);
667 args.add_arg("spacing", spacing);
668 args.add_arg("direction", direction);
669 args.add_arg("language", language);
670 args.add_arg("script", script);
671 bool TWO_D=true;
672 std::shared_ptr<Mesh> result = GEOCSG::call_OpenSCAD(
673 current_path(), "text", args, TWO_D
674 );
675 result->facets.compute_borders();
676 finalize_mesh(result);
677 return result;
678 }
679
680 2 std::shared_ptr<Mesh> CSGBuilder::surface_with_OpenSCAD(
681 const std::filesystem::path& filename, bool center, bool invert
682 ) {
683 2 std::shared_ptr<Mesh> result;
684
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 std::filesystem::path full_filename = filename;
685
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
2 if(!find_file(full_filename)) {
686 Logger::err("CSG") << filename << ": file not found"
687 << std::endl;
688 return empty_mesh_;
689 }
690
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 GEOCSG::ArgList args;
691
3/6
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
6 args.add_arg("file", full_filename);
692
3/6
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
6 args.add_arg("center", center);
693
3/6
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
6 args.add_arg("invert", invert);
694
3/6
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
2 result = GEOCSG::call_OpenSCAD(current_path(), "surface", args);
695
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 finalize_mesh(result);
696 2 return result;
697 2 }
698
699 258 std::shared_ptr<Mesh> CSGBuilder::multmatrix(
700 const mat4& M, const CSGScope& scope
701 ) {
702 258 std::shared_ptr<Mesh> result = group(scope);
703
704
3/4
✓ Branch 2 taken 258 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 198 times.
✓ Branch 5 taken 60 times.
258 if(result->vertices.dimension() == 3) {
705
2/2
✓ Branch 5 taken 60123 times.
✓ Branch 6 taken 198 times.
60321 for(index_t v: result->vertices) {
706
2/4
✓ Branch 2 taken 60123 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 60123 times.
✗ Branch 7 not taken.
60123 vec4 p = M*vec4(result->vertices.point<3>(v), 1.0);
707
1/2
✓ Branch 4 taken 60123 times.
✗ Branch 5 not taken.
60123 result->vertices.point<3>(v) = (1.0 / p.w) * vec3(p.x, p.y, p.z);
708 }
709
2/4
✓ Branch 2 taken 60 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 60 times.
✗ Branch 5 not taken.
60 } else if(result->vertices.dimension() == 2) {
710
2/2
✓ Branch 5 taken 502 times.
✓ Branch 6 taken 60 times.
562 for(index_t v: result->vertices) {
711
2/4
✓ Branch 2 taken 502 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 502 times.
✗ Branch 7 not taken.
502 vec4 p = M*vec4(result->vertices.point<2>(v), 0.0, 1.0);
712
1/2
✓ Branch 4 taken 502 times.
✗ Branch 5 not taken.
502 result->vertices.point<2>(v) = (1.0 / p.w) * vec2(p.x, p.y);
713 }
714 } else {
715 geo_assert_not_reached;
716 }
717
718 // Preserve normals orientations if transform is left-handed
719
2/4
✓ Branch 1 taken 258 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 258 times.
258 if(det(M) < 0.0) {
720 for(index_t e: result->edges) {
721 result->edges.flip(e);
722 }
723 for(index_t t: result->facets) {
724 result->facets.flip(t);
725 }
726 }
727
1/2
✓ Branch 1 taken 258 times.
✗ Branch 2 not taken.
258 finalize_mesh(result);
728 258 return result;
729 }
730
731 431 std::shared_ptr<Mesh> CSGBuilder::union_instr(const CSGScope& scope) {
732
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 431 times.
431 if(scope.size() == 0) {
733 return empty_mesh_;
734 }
735
736
2/2
✓ Branch 1 taken 392 times.
✓ Branch 2 taken 39 times.
431 if(scope.size() == 1) {
737
1/2
✓ Branch 1 taken 392 times.
✗ Branch 2 not taken.
392 return scope[0];
738 }
739
740 // Boolean operations can handle no more than max_arity_ operands.
741 // For a union with more than max_arity_ operands, split it into two.
742 39 bool split = (scope.size() > max_arity_);
743
7/10
✓ Branch 0 taken 39 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 39 times.
✗ Branch 4 not taken.
✓ Branch 7 taken 39 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 28 times.
✓ Branch 10 taken 11 times.
✓ Branch 11 taken 28 times.
✓ Branch 12 taken 11 times.
39 if(fast_union_ && scope[0]->vertices.dimension() == 3) {
744 28 split = false; // do not split if fast_union_ is set and op is 3D
745 }
746
747
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 39 times.
39 if(split) {
748 CSGScope scope1;
749 CSGScope scope2;
750 index_t n1 = index_t(scope.size()/2);
751 for(index_t i=0; i<scope.size(); ++i) {
752 if(i < n1) {
753 scope1.emplace_back(scope[i]);
754 } else {
755 scope2.emplace_back(scope[i]);
756 }
757 }
758 CSGScope scope3;
759 scope3.emplace_back(union_instr(scope1));
760 scope3.emplace_back(union_instr(scope2));
761 return union_instr(scope3);
762 }
763
764
1/2
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
39 std::shared_ptr<Mesh> result = append(scope);
765
3/4
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 23 times.
✓ Branch 4 taken 16 times.
39 if(may_have_intersections(scope)) {
766
2/4
✓ Branch 1 taken 23 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 23 times.
✗ Branch 5 not taken.
69 do_CSG(result, "union");
767 }
768
1/2
✓ Branch 1 taken 39 times.
✗ Branch 2 not taken.
39 finalize_mesh(result);
769 39 return result;
770 39 }
771
772 18 std::shared_ptr<Mesh> CSGBuilder::intersection(const CSGScope& scope) {
773
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
18 if(scope.size() == 0) {
774 return empty_mesh_;
775 }
776
777
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
18 if(scope.size() == 1) {
778 return scope[0];
779 }
780
781 // Boolean operations can handle no more than max_arity_ operands.
782 // For a intersection with more than max_arity_ operands,
783 // split it into two.
784
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
18 if(scope.size() > max_arity_) {
785 CSGScope scope1;
786 CSGScope scope2;
787 index_t n1 = index_t(scope.size()/2);
788 for(index_t i=0; i<scope.size(); ++i) {
789 if(i < n1) {
790 scope1.emplace_back(scope[i]);
791 } else {
792 scope2.emplace_back(scope[i]);
793 }
794 }
795
796 CSGScope scope3;
797 scope3.emplace_back(intersection(scope1));
798 scope3.emplace_back(intersection(scope2));
799 return intersection(scope3);
800 }
801
802
1/2
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
18 std::shared_ptr<Mesh> result = append(scope);
803
2/4
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 18 times.
✗ Branch 5 not taken.
36 do_CSG(result, "intersection");
804
1/2
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
18 finalize_mesh(result);
805 18 return result;
806 18 }
807
808 30 std::shared_ptr<Mesh> CSGBuilder::difference(const CSGScope& scope) {
809
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 30 times.
30 if(scope.size() == 0) {
810 return empty_mesh_;
811 }
812
813
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 30 times.
30 if(scope.size() == 1) {
814 return scope[0];
815 }
816
817 30 bool no_difference = true;
818
1/2
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
30 for(index_t i=1; i<scope.size(); ++i) {
819
2/4
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 30 times.
✗ Branch 6 not taken.
30 if(scope[i]->vertices.nb() != 0) {
820 30 no_difference = false;
821 30 break;
822 }
823 }
824
825
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 30 times.
30 if(no_difference) {
826 return scope[0];
827 }
828
829 // Boolean operations can handle no more than max_arity_ operands.
830 // For a difference with more than max_arity_ operands, split it
831 // (by calling union_instr() that in turn splits the list if need be).
832
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 30 times.
30 if(scope.size() > max_arity_) {
833 CSGScope scope2;
834 for(index_t i=1; i<scope.size(); ++i) {
835 scope2.emplace_back(scope[i]);
836 }
837 CSGScope scope3;
838 scope3.emplace_back(scope[0]);
839 scope3.emplace_back(union_instr(scope2));
840 return difference(scope3);
841 }
842
843
1/2
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
30 std::shared_ptr<Mesh> result = append(scope);
844 // construct the expression x0-x1-x2...-xn
845
1/2
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
30 std::string expr = "x0";
846
2/2
✓ Branch 1 taken 38 times.
✓ Branch 2 taken 30 times.
68 for(index_t i=1; i<scope.size(); ++i) {
847
3/6
✓ Branch 1 taken 38 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 38 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 38 times.
✗ Branch 8 not taken.
38 expr += "-x" + String::to_string(i);
848 }
849
850
1/2
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
30 do_CSG(result, expr);
851
1/2
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
30 finalize_mesh(result);
852 30 return result;
853 30 }
854
855 371 std::shared_ptr<Mesh> CSGBuilder::group(const CSGScope& scope) {
856 371 return union_instr(scope);
857 }
858
859 1 std::shared_ptr<Mesh> CSGBuilder::color(vec4 color, const CSGScope& scope) {
860 1 geo_argused(color);
861 1 std::shared_ptr<Mesh> result = group(scope);
862
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 finalize_mesh(result);
863 1 return result;
864 }
865
866 1 std::shared_ptr<Mesh> CSGBuilder::hull(const CSGScope& scope) {
867 1 std::shared_ptr<Mesh> result = append(scope);
868
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 result->edges.clear();
869
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 result->facets.clear();
870 // Particular case: no vertex in scope (yes, this happens !)
871
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if(result->vertices.nb() == 0) {
872 return result;
873 }
874
2/4
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 if(result->vertices.dimension() == 3) {
875
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 compute_convex_hull_3d(*result);
876 } else {
877 compute_convex_hull_2d(*result);
878 triangulate(result);
879 }
880
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 finalize_mesh(result);
881 1 return result;
882 }
883
884 24 std::shared_ptr<Mesh> CSGBuilder::linear_extrude(
885 const CSGScope& scope, double height, bool center, vec2 scale,
886 index_t slices, double twist
887 ) {
888
1/2
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
24 std::shared_ptr<Mesh> result = union_instr(scope);
889
890
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 14 times.
24 double z1 = center ? -height/2.0 : 0.0;
891
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 14 times.
24 double z2 = center ? height/2.0 : height;
892
893
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
24 if(slices == 0) {
894 24 slices = index_t(
895
1/2
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
24 GEOCSG::get_linear_extrusion_slices(
896 result, height, scale, twist, fn_, fs_, fa_
897 )
898 );
899 }
900
901 24 index_t nv = slices+1;
902
903
1/2
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
48 GEOCSG::sweep(
904 result, nv,
905
1/2
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
48 [&](index_t u, index_t v)->vec3 {
906
1/2
✓ Branch 2 taken 6217 times.
✗ Branch 3 not taken.
6217 vec2 ref = result->vertices.point<2>(u);
907 6217 double t = double(v) / double(nv-1);
908
909 6217 double s = 1.0 - t;
910 6217 double z = s*z1 + t*z2;
911 6217 vec2 sz = s*vec2(1.0, 1.0) + t*scale;
912
913 6217 double x = ref.x * sz.x;
914 6217 double y = ref.y * sz.y;
915
916
2/2
✓ Branch 0 taken 4599 times.
✓ Branch 1 taken 1618 times.
6217 if(twist != 0.0) {
917 4599 double alpha = twist*t*M_PI/180.0;
918 4599 double ca = cos(alpha);
919 4599 double sa = sin(alpha);
920 4599 double x2 = ca*x+sa*y;
921 4599 double y2 = -sa*x+ca*y;
922 4599 x = x2;
923 4599 y = y2;
924 }
925
926 12434 return vec3(x,y,z);
927 },
928
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
24 ((scale.x == 0.0 && scale.y == 0.0)
929 ? GEOCSG::SWEEP_POLE : GEOCSG::SWEEP_CAP)
930 );
931
932
1/2
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
24 result->facets.connect();
933
934
1/2
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
24 finalize_mesh(result);
935 24 return result;
936 }
937
938 3 std::shared_ptr<Mesh> CSGBuilder::rotate_extrude(
939 const CSGScope& scope, double angle
940 ) {
941
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 std::shared_ptr<Mesh> result = union_instr(scope);
942
1/2
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 result->vertices.set_dimension(2);
943
944
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if(angle == 360.0) {
945
1/2
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 result->facets.clear();
946
1/2
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 result->vertices.remove_isolated();
947 }
948
949 // Remove edges that are co-linear with rotation axis.
950 // (as well as vertices dangling on rotation axis).
951 {
952 3 index_t nb_edges_to_remove = 0;
953
1/2
✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
3 vector<index_t> remove_edges(result->edges.nb(),0);
954
2/2
✓ Branch 6 taken 64 times.
✓ Branch 7 taken 3 times.
67 for(index_t e: result->edges) {
955
1/2
✓ Branch 2 taken 64 times.
✗ Branch 3 not taken.
64 index_t v1 = result->edges.vertex(e,0);
956
1/2
✓ Branch 2 taken 64 times.
✗ Branch 3 not taken.
64 index_t v2 = result->edges.vertex(e,1);
957 64 if(
958
5/6
✓ Branch 2 taken 64 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 9 times.
✓ Branch 5 taken 55 times.
✓ Branch 6 taken 6 times.
✓ Branch 7 taken 58 times.
73 (result->vertices.point<2>(v1).x == 0.0) &&
959
3/4
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✓ Branch 5 taken 3 times.
9 (result->vertices.point<2>(v2).x == 0.0)
960 ) {
961
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 remove_edges[e] = 1;
962 6 ++nb_edges_to_remove;
963 }
964 }
965
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if(nb_edges_to_remove != 0) {
966
1/2
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 result->edges.delete_elements(remove_edges);
967
1/2
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 result->vertices.remove_isolated();
968 }
969 3 }
970
971 3 double R = 0.0;
972
2/2
✓ Branch 5 taken 61 times.
✓ Branch 6 taken 3 times.
64 for(index_t v: result->vertices) {
973
1/2
✓ Branch 2 taken 61 times.
✗ Branch 3 not taken.
61 R = std::max(R, ::fabs(result->vertices.point<2>(v).x));
974 }
975 index_t slices = index_t(
976
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 GEOCSG::get_fragments_from_r_and_twist(R,angle,fn_,fs_,fa_)
977 3 );
978 3 index_t nv = slices+1;
979
980
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
6 GEOCSG::sweep(
981
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
6 result, nv, [&](index_t u, index_t v)->vec3 {
982
1/2
✓ Branch 2 taken 1830 times.
✗ Branch 3 not taken.
1830 vec2 ref = result->vertices.point<2>(u);
983 1830 double t = double(v) / double(nv-1);
984 1830 double alpha = t * 2.0 * M_PI * angle / 360.0;
985 return vec3(
986 3660 ref.x * cos(alpha),
987 1830 ref.x * sin(alpha),
988 ref.y
989 3660 );
990 },
991
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 (angle == 360.0) ? GEOCSG::SWEEP_PERIODIC : GEOCSG::SWEEP_CAP
992 );
993
994 // there may be duplicated points around the poles
995
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 mesh_repair(
996 3 *result, MeshRepairMode(MESH_REPAIR_DEFAULT | MESH_REPAIR_QUIET)
997 );
998
999 // Note: we flip when angle is *positive* because X,Y,rotate dir
1000 // is not a direct basis.
1001
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if(angle > 0) {
1002
2/2
✓ Branch 6 taken 3300 times.
✓ Branch 7 taken 3 times.
3303 for(index_t t: result->facets) {
1003
1/2
✓ Branch 2 taken 3300 times.
✗ Branch 3 not taken.
3300 result->facets.flip(t);
1004 }
1005 }
1006
1007
1/2
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 result->facets.connect();
1008
1009
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 finalize_mesh(result);
1010 3 return result;
1011 }
1012
1013 std::shared_ptr<Mesh> CSGBuilder::projection(
1014 const CSGScope& scope, bool cut
1015 ) {
1016 std::shared_ptr<Mesh> result = append(scope);
1017 if(result->vertices.dimension() != 3) {
1018 Logger::err("CSG") << "projection(): input mesh is not of dimenion 3"
1019 << std::endl;
1020 result->clear();
1021 return result;
1022 }
1023 if(cut) {
1024 // Cut mode: intersection between object and z=0 plane.
1025 // We reuse the intersection() function as follows:
1026 // Compute intersection with (enlarged) half bbox and
1027 // keep z=0 facets only.
1028 auto [pmin, pmax] = get_bbox_bounds(result);
1029 vec3 T(
1030 pmin.x - (pmax.x - pmin.x),
1031 pmin.y - (pmax.y - pmin.y),
1032 0.0
1033 );
1034 std::shared_ptr<Mesh> C = cube(3.0*(pmax-pmin), false);
1035 for(index_t v: C->vertices) {
1036 C->vertices.point<3>(v) += T;
1037 }
1038 CSGScope scope2;
1039 scope2.push_back(result);
1040 scope2.push_back(C);
1041 result = difference(scope2);
1042 GEOCSG::keep_z0_only(result);
1043 result->facets.compute_borders();
1044 triangulate(result);
1045 } else {
1046 // Union of all triangles projected in 2D. We project only
1047 // half of them (since we have a closed shape). We select
1048 // the orientation with the smallest number of triangles.
1049 result->edges.clear();
1050 vector<GEO::Sign> sign(result->facets.nb());
1051 index_t nb_negative = 0;
1052 index_t nb_positive = 0;
1053 for(index_t t: result->facets) {
1054 index_t v1 = result->facets.vertex(t,0);
1055 index_t v2 = result->facets.vertex(t,1);
1056 index_t v3 = result->facets.vertex(t,2);
1057 vec2 p1 = result->vertices.point<2>(v1);
1058 vec2 p2 = result->vertices.point<2>(v2);
1059 vec2 p3 = result->vertices.point<2>(v3);
1060 sign[t] = GEO::PCK::orient_2d(p1,p2,p3);
1061 nb_negative += (sign[t] == GEO::NEGATIVE);
1062 nb_positive += (sign[t] == GEO::POSITIVE);
1063 }
1064 GEO::Sign keep_sign =
1065 (nb_positive > nb_negative) ? GEO::NEGATIVE : GEO::POSITIVE;
1066
1067 Attribute<index_t> e_operand_bit(
1068 result->edges.attributes(),"operand_bit"
1069 );
1070 for(index_t t: result->facets) {
1071 if(sign[t] == keep_sign) {
1072 index_t v1 = result->facets.vertex(t,0);
1073 index_t v2 = result->facets.vertex(t,1);
1074 index_t v3 = result->facets.vertex(t,2);
1075 index_t e1 = result->edges.create_edge(v1,v2);
1076 index_t e2 = result->edges.create_edge(v2,v3);
1077 index_t e3 = result->edges.create_edge(v3,v1);
1078 // We are using the "cnstr_operand_bits_is_operand_id"
1079 // of the "union" operation in CDT2d. It makes it
1080 // possible to compute a union operation with more than
1081 // 32 operands (and here we got one operand per input
1082 // triangle !).
1083 e_operand_bit[e1] = t;
1084 e_operand_bit[e2] = t;
1085 e_operand_bit[e3] = t;
1086 }
1087 }
1088 result->vertices.set_dimension(2);
1089 triangulate(result,"union_cnstr_operand_bits_is_operand_id");
1090 result->facets.compute_borders();
1091 triangulate(result);
1092 }
1093 finalize_mesh(result);
1094 return result;
1095 }
1096
1097 std::shared_ptr<Mesh> CSGBuilder::minkowski(const CSGScope& scope) {
1098 if(scope.size() == 0) {
1099 return empty_mesh_;
1100 }
1101
1102 if(scope.size() == 1) {
1103 return scope[0];
1104 }
1105
1106 if(scope.size() != 2) {
1107 error("Minkowski: more than 2 operands");
1108 }
1109
1110 if(
1111 scope[0]->vertices.dimension() == 2 &&
1112 scope[1]->vertices.dimension() == 2
1113 ) {
1114 std::shared_ptr<Mesh> result = std::make_shared<Mesh>();
1115 compute_minkowski_sum_2d(*result, *scope[0], *scope[1]);
1116 triangulate(result);
1117 return result;
1118 }
1119
1120 if(
1121 scope[0]->vertices.dimension() == 3 &&
1122 scope[1]->vertices.dimension() == 3
1123 ) {
1124 std::shared_ptr<Mesh> result = std::make_shared<Mesh>();
1125 compute_minkowski_sum_3d(*result, *scope[0], *scope[1]);
1126 // TODO: simplify coplanar facets
1127 return result;
1128 }
1129
1130 error("Minkowski: invalid operand dimensions");
1131 }
1132
1133 88 std::shared_ptr<Mesh> CSGBuilder::append(const CSGScope& scope) {
1134
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 88 times.
88 if(scope.size() == 0) {
1135 return empty_mesh_;
1136 }
1137
1138
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 88 times.
88 if(scope.size() == 1) {
1139 return scope[0];
1140 }
1141
1142
4/6
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 86 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 88 times.
88 if(!fast_union_ && scope.size() > max_arity_) {
1143 Logger::warn("CSG") << "Scope with more than "
1144 << max_arity_
1145 << " children"
1146 << std::endl;
1147 }
1148
1149
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 std::shared_ptr<Mesh> a = std::make_shared<Mesh>();
1150 88 index_t dim = 2;
1151 88 index_t nb_v = 0;
1152 88 index_t nb_e = 0;
1153 88 index_t nb_f = 0;
1154
2/2
✓ Branch 2 taken 315 times.
✓ Branch 3 taken 88 times.
491 for(const std::shared_ptr<Mesh>& b: scope) {
1155
1/2
✓ Branch 2 taken 315 times.
✗ Branch 3 not taken.
315 dim = std::max(dim, b->vertices.dimension());
1156 315 nb_v += b->vertices.nb();
1157 315 nb_e += b->edges.nb();
1158 315 nb_f += b->facets.nb();
1159 }
1160
1/2
✓ Branch 2 taken 88 times.
✗ Branch 3 not taken.
88 a->vertices.set_dimension(dim);
1161
1/2
✓ Branch 2 taken 88 times.
✗ Branch 3 not taken.
88 a->vertices.create_vertices(nb_v);
1162
1/2
✓ Branch 2 taken 88 times.
✗ Branch 3 not taken.
88 a->edges.create_edges(nb_e);
1163
1/2
✓ Branch 2 taken 88 times.
✗ Branch 3 not taken.
88 a->facets.create_triangles(nb_f);
1164
1165 88 index_t v_ofs = 0;
1166 88 index_t e_ofs = 0;
1167 88 index_t f_ofs = 0;
1168
1169
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 Attribute<index_t> f_operand_bit;
1170
1/2
✓ Branch 1 taken 88 times.
✗ Branch 2 not taken.
88 Attribute<index_t> e_operand_bit;
1171
1172
3/4
✓ Branch 2 taken 88 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 59 times.
✓ Branch 5 taken 29 times.
88 if(a->vertices.dimension() == 3) {
1173
2/4
✓ Branch 1 taken 59 times.
✗ Branch 2 not taken.
✓ Branch 6 taken 59 times.
✗ Branch 7 not taken.
118 f_operand_bit.bind(a->facets.attributes(), "operand_bit");
1174 }
1175
1176
3/4
✓ Branch 2 taken 88 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 29 times.
✓ Branch 5 taken 59 times.
88 if(a->vertices.dimension() == 2) {
1177
2/4
✓ Branch 1 taken 29 times.
✗ Branch 2 not taken.
✓ Branch 6 taken 29 times.
✗ Branch 7 not taken.
58 e_operand_bit.bind(a->edges.attributes(), "operand_bit");
1178 }
1179
1180 88 index_t operand_bit = index_t(1);
1181
2/2
✓ Branch 2 taken 315 times.
✓ Branch 3 taken 88 times.
491 for(const std::shared_ptr<Mesh>& b : scope) {
1182
2/2
✓ Branch 6 taken 60734 times.
✓ Branch 7 taken 315 times.
61049 for(index_t v: b->vertices) {
1183
3/4
✓ Branch 2 taken 241622 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 180888 times.
✓ Branch 5 taken 60734 times.
241622 for(index_t coord=0; coord<a->vertices.dimension(); ++coord) {
1184
1/2
✓ Branch 2 taken 180888 times.
✗ Branch 3 not taken.
180888 a->vertices.point_ptr(v + v_ofs)[coord] =
1185
2/4
✓ Branch 2 taken 180888 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 180888 times.
✗ Branch 5 not taken.
180888 (coord < b->vertices.dimension() ?
1186
1/2
✓ Branch 2 taken 180888 times.
✗ Branch 3 not taken.
180888 b->vertices.point_ptr(v)[coord] : 0.0);
1187 }
1188 }
1189
1190
2/2
✓ Branch 6 taken 118980 times.
✓ Branch 7 taken 315 times.
119295 for(index_t f: b->facets) {
1191
2/8
✓ Branch 2 taken 118980 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 118980 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
118980 geo_debug_assert(b->facets.nb_vertices(f) == 3);
1192
1/2
✓ Branch 2 taken 118980 times.
✗ Branch 3 not taken.
118980 index_t v1 = b->facets.vertex(f,0);
1193
1/2
✓ Branch 2 taken 118980 times.
✗ Branch 3 not taken.
118980 index_t v2 = b->facets.vertex(f,1);
1194
1/2
✓ Branch 2 taken 118980 times.
✗ Branch 3 not taken.
118980 index_t v3 = b->facets.vertex(f,2);
1195
1/2
✓ Branch 2 taken 118980 times.
✗ Branch 3 not taken.
118980 a->facets.set_vertex(f + f_ofs, 0, v1 + v_ofs);
1196
1/2
✓ Branch 2 taken 118980 times.
✗ Branch 3 not taken.
118980 a->facets.set_vertex(f + f_ofs, 1, v2 + v_ofs);
1197
1/2
✓ Branch 2 taken 118980 times.
✗ Branch 3 not taken.
118980 a->facets.set_vertex(f + f_ofs, 2, v3 + v_ofs);
1198
1/2
✓ Branch 2 taken 118980 times.
✗ Branch 3 not taken.
118980 index_t f1 = b->facets.adjacent(f,0);
1199
1/2
✓ Branch 2 taken 118980 times.
✗ Branch 3 not taken.
118980 index_t f2 = b->facets.adjacent(f,1);
1200
1/2
✓ Branch 2 taken 118980 times.
✗ Branch 3 not taken.
118980 index_t f3 = b->facets.adjacent(f,2);
1201
2/2
✓ Branch 0 taken 118216 times.
✓ Branch 1 taken 764 times.
118980 if(f1 != NO_INDEX) {
1202 118216 f1 += f_ofs;
1203 }
1204
2/2
✓ Branch 0 taken 118165 times.
✓ Branch 1 taken 815 times.
118980 if(f2 != NO_INDEX) {
1205 118165 f2 += f_ofs;
1206 }
1207
2/2
✓ Branch 0 taken 118251 times.
✓ Branch 1 taken 729 times.
118980 if(f3 != NO_INDEX) {
1208 118251 f3 += f_ofs;
1209 }
1210
1/2
✓ Branch 2 taken 118980 times.
✗ Branch 3 not taken.
118980 a->facets.set_adjacent(f + f_ofs, 0, f1);
1211
1/2
✓ Branch 2 taken 118980 times.
✗ Branch 3 not taken.
118980 a->facets.set_adjacent(f + f_ofs, 1, f2);
1212
1/2
✓ Branch 2 taken 118980 times.
✗ Branch 3 not taken.
118980 a->facets.set_adjacent(f + f_ofs, 2, f3);
1213
2/2
✓ Branch 1 taken 117880 times.
✓ Branch 2 taken 1100 times.
118980 if(f_operand_bit.is_bound()) {
1214
1/2
✓ Branch 1 taken 117880 times.
✗ Branch 2 not taken.
117880 f_operand_bit[f + f_ofs] = operand_bit;
1215 }
1216 }
1217
1218
2/2
✓ Branch 6 taken 1314 times.
✓ Branch 7 taken 315 times.
1629 for(index_t e: b->edges) {
1219
1/2
✓ Branch 2 taken 1314 times.
✗ Branch 3 not taken.
1314 index_t v1 = b->edges.vertex(e,0);
1220
1/2
✓ Branch 2 taken 1314 times.
✗ Branch 3 not taken.
1314 index_t v2 = b->edges.vertex(e,1);
1221
1/2
✓ Branch 2 taken 1314 times.
✗ Branch 3 not taken.
1314 a->edges.set_vertex(e + e_ofs, 0, v1 + v_ofs);
1222
1/2
✓ Branch 2 taken 1314 times.
✗ Branch 3 not taken.
1314 a->edges.set_vertex(e + e_ofs, 1, v2 + v_ofs);
1223
1/2
✓ Branch 1 taken 1314 times.
✗ Branch 2 not taken.
1314 if(e_operand_bit.is_bound()) {
1224
1/2
✓ Branch 1 taken 1314 times.
✗ Branch 2 not taken.
1314 e_operand_bit[e + e_ofs] = operand_bit;
1225 }
1226 }
1227
1228 315 v_ofs += b->vertices.nb();
1229 315 e_ofs += b->edges.nb();
1230 315 f_ofs += b->facets.nb();
1231 315 operand_bit = operand_bit << 1;
1232 }
1233 88 return a;
1234 88 }
1235
1236 26 bool CSGBuilder::find_file(std::filesystem::path& filename) {
1237
1/2
✓ Branch 2 taken 52 times.
✗ Branch 3 not taken.
78 for(const std::filesystem::path& path: file_path_) {
1238
1/2
✓ Branch 1 taken 52 times.
✗ Branch 2 not taken.
52 std::filesystem::path current_path = path / filename;
1239
3/4
✓ Branch 1 taken 52 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 26 times.
✓ Branch 4 taken 26 times.
52 if(std::filesystem::is_regular_file(current_path)) {
1240
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 filename = current_path;
1241 26 return true;
1242 }
1243
2/2
✓ Branch 1 taken 26 times.
✓ Branch 2 taken 26 times.
52 }
1244 return false;
1245 }
1246
1247 19 std::shared_ptr<Mesh> CSGBuilder::import_with_openSCAD(
1248 const std::filesystem::path& filepath, const std::string& layer,
1249 index_t timestamp
1250 ) {
1251
1/2
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
19 GEOCSG::ArgList args;
1252
3/6
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 19 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 19 times.
✗ Branch 8 not taken.
57 args.add_arg("file", filepath);
1253
3/6
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 19 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 19 times.
✗ Branch 8 not taken.
57 args.add_arg("layer", layer);
1254
3/6
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 19 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 19 times.
✗ Branch 8 not taken.
57 args.add_arg("timestamp", int(timestamp));
1255 19 bool TWO_D = true;
1256 std::shared_ptr<Mesh> result = GEOCSG::call_OpenSCAD(
1257
1/2
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
38 current_path(), "import", args, TWO_D
1258
2/4
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 19 times.
✗ Branch 5 not taken.
38 );
1259
1/2
✓ Branch 2 taken 19 times.
✗ Branch 3 not taken.
19 result->facets.compute_borders();
1260
1/2
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
19 finalize_mesh(result);
1261 19 return result;
1262 19 }
1263
1264 71 void CSGBuilder::do_CSG(
1265 std::shared_ptr<Mesh>& mesh, const std::string& boolean_expr
1266 ) {
1267
2/2
✓ Branch 2 taken 27 times.
✓ Branch 3 taken 44 times.
71 if(mesh->vertices.dimension() == 2) {
1268 27 triangulate(mesh, boolean_expr); // has all intersections inside
1269 27 mesh->facets.clear(); // now keep edge borders only
1270 27 mesh->vertices.remove_isolated(); // then remove internal vertices
1271 27 triangulate(mesh); // re-triangulate border edges
1272 } else {
1273
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 44 times.
44 if(noop_) {
1274 return;
1275 }
1276
1/2
✓ Branch 2 taken 44 times.
✗ Branch 3 not taken.
44 MeshSurfaceIntersection I(*mesh);
1277 44 I.set_verbose(verbose_);
1278 44 I.set_fine_verbose(detailed_verbose_);
1279 44 I.set_delaunay(delaunay_);
1280 44 I.set_detect_intersecting_neighbors(detect_intersecting_neighbors_);
1281
1/2
✓ Branch 1 taken 44 times.
✗ Branch 2 not taken.
44 I.intersect();
1282
7/8
✓ Branch 0 taken 42 times.
✓ Branch 1 taken 2 times.
✓ Branch 3 taken 42 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 14 times.
✓ Branch 6 taken 28 times.
✓ Branch 7 taken 14 times.
✓ Branch 8 taken 30 times.
44 if(fast_union_ && boolean_expr == "union") {
1283
1/2
✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
14 I.remove_internal_shells();
1284 } else {
1285
1/2
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
30 I.classify(boolean_expr);
1286 }
1287
1/2
✓ Branch 0 taken 44 times.
✗ Branch 1 not taken.
44 if(simplify_coplanar_facets_) {
1288
1/2
✓ Branch 1 taken 44 times.
✗ Branch 2 not taken.
44 I.simplify_coplanar_facets(coplanar_angle_tolerance_);
1289 }
1290 44 }
1291 }
1292
1293 106 void CSGBuilder::triangulate(
1294 std::shared_ptr<Mesh>& mesh, const std::string& boolean_expr
1295 ) {
1296
2/8
✓ Branch 2 taken 106 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 106 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
106 geo_assert(mesh->vertices.dimension() == 2);
1297
1298 // Keep edges only
1299
1/2
✓ Branch 2 taken 106 times.
✗ Branch 3 not taken.
106 mesh->facets.clear();
1300
1/2
✓ Branch 2 taken 106 times.
✗ Branch 3 not taken.
106 mesh->vertices.remove_isolated();
1301
1302
1/2
✓ Branch 2 taken 106 times.
✗ Branch 3 not taken.
106 bool has_operand_bit = Attribute<index_t>::is_defined(
1303
1/2
✓ Branch 1 taken 106 times.
✗ Branch 2 not taken.
212 mesh->edges.attributes(), "operand_bit"
1304 );
1305
1306
1/6
✗ Branch 0 not taken.
✓ Branch 1 taken 106 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
106 geo_assert(has_operand_bit);
1307
1308 Attribute<index_t> e_operand_bit(
1309
1/2
✓ Branch 1 taken 106 times.
✗ Branch 2 not taken.
212 mesh->edges.attributes(), "operand_bit"
1310
1/2
✓ Branch 2 taken 106 times.
✗ Branch 3 not taken.
106 );
1311
1312 #ifdef GEO_DEBUG
1313 {
1314 106 bool all_zero = true;
1315
2/2
✓ Branch 6 taken 3127 times.
✓ Branch 7 taken 106 times.
3233 for(index_t e: mesh->edges) {
1316
5/10
✓ Branch 1 taken 3127 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3127 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 3127 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 3127 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 3127 times.
✗ Branch 11 not taken.
3127 if(e_operand_bit[e] != 0 && e_operand_bit[e] != NO_INDEX) {
1317 3127 all_zero = false;
1318 }
1319 }
1320
1/6
✗ Branch 0 not taken.
✓ Branch 1 taken 106 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
106 geo_debug_assert(!all_zero);
1321 }
1322 #endif
1323
1324
1/2
✓ Branch 1 taken 106 times.
✗ Branch 2 not taken.
106 auto [pmin, pmax] = get_bbox_bounds(mesh);
1325 106 vec3 D = pmax - pmin;
1326 106 double d = std::max(std::max(D.x,D.y)*10.0, 1.0);
1327 106 D = vec3(d,d,0.0);
1328 106 pmin -= D;
1329 106 pmax += D;
1330
1331
1/2
✓ Branch 1 taken 106 times.
✗ Branch 2 not taken.
106 GEO::ExactCDT2d CDT;
1332
1/2
✓ Branch 1 taken 106 times.
✗ Branch 2 not taken.
106 CDT.create_enclosing_rectangle(pmin.x, pmin.y, pmax.x, pmax.y);
1333
1334 // In case there are duplicated vertices, keep track of indexing
1335
1/2
✓ Branch 3 taken 106 times.
✗ Branch 4 not taken.
106 vector<index_t> vertex_id(mesh->vertices.nb());
1336
2/2
✓ Branch 5 taken 3127 times.
✓ Branch 6 taken 106 times.
3233 for(index_t v: mesh->vertices) {
1337
1/2
✓ Branch 2 taken 3127 times.
✗ Branch 3 not taken.
3127 vec2 p = mesh->vertices.point<2>(v);
1338
2/4
✓ Branch 1 taken 3127 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3127 times.
✗ Branch 5 not taken.
3127 vertex_id[v] = CDT.insert(
1339
1/2
✓ Branch 1 taken 3127 times.
✗ Branch 2 not taken.
6254 GEO::ExactCDT2d::ExactPoint(p.x, p.y, 1.0), v
1340 );
1341 }
1342
1343 // Memorize current number of vertices to detect vertices
1344 // coming from constraint intersections
1345 106 index_t nv0 = CDT.nv();
1346
1347 // Insert constraints
1348
2/2
✓ Branch 6 taken 3127 times.
✓ Branch 7 taken 106 times.
3233 for(index_t e: mesh->edges) {
1349
1/2
✓ Branch 2 taken 3127 times.
✗ Branch 3 not taken.
3127 index_t v1 = mesh->edges.vertex(e,0);
1350
1/2
✓ Branch 2 taken 3127 times.
✗ Branch 3 not taken.
3127 index_t v2 = mesh->edges.vertex(e,1);
1351 9381 CDT.insert_constraint(
1352
4/8
✓ Branch 1 taken 3127 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3127 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 3127 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 3127 times.
✗ Branch 11 not taken.
3127 vertex_id[v1], vertex_id[v2], e_operand_bit[e]
1353 );
1354 }
1355
1356
1/2
✓ Branch 1 taken 106 times.
✗ Branch 2 not taken.
106 CDT.classify_triangles(boolean_expr);
1357
1358 // Create vertices coming from constraint intersections
1359
2/2
✓ Branch 1 taken 81 times.
✓ Branch 2 taken 106 times.
187 for(index_t v=nv0; v<CDT.nv(); ++v) {
1360
2/4
✓ Branch 1 taken 81 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 81 times.
✗ Branch 5 not taken.
81 GEO::vec2 p = GEO::PCK::approximate(CDT.point(v));
1361
2/4
✓ Branch 1 taken 81 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 81 times.
✗ Branch 5 not taken.
81 CDT.set_vertex_id(
1362 v,
1363 162 mesh->vertices.create_vertex(vec2(p.x, p.y))
1364 );
1365 }
1366
1367 // Create triangles in target mesh
1368
3/4
✓ Branch 1 taken 2845 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2739 times.
✓ Branch 4 taken 106 times.
2845 for(index_t t=0; t<CDT.nT(); ++t) {
1369
7/14
✓ Branch 2 taken 2739 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 2739 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 2739 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 2739 times.
✗ Branch 12 not taken.
✓ Branch 14 taken 2739 times.
✗ Branch 15 not taken.
✓ Branch 17 taken 2739 times.
✗ Branch 18 not taken.
✓ Branch 20 taken 2739 times.
✗ Branch 21 not taken.
2739 mesh->facets.create_triangle(
1370 CDT.vertex_id(CDT.Tv(t,0)),
1371 CDT.vertex_id(CDT.Tv(t,1)),
1372 CDT.vertex_id(CDT.Tv(t,2))
1373 );
1374 }
1375
1376
1/2
✓ Branch 2 taken 106 times.
✗ Branch 3 not taken.
106 mesh->facets.connect();
1377
1/2
✓ Branch 2 taken 106 times.
✗ Branch 3 not taken.
106 mesh->facets.compute_borders();
1378 106 }
1379
1380 79 void CSGBuilder::triangulate(std::shared_ptr<Mesh>& mesh) {
1381 Attribute<index_t> e_operand_bit(
1382
1/2
✓ Branch 1 taken 79 times.
✗ Branch 2 not taken.
158 mesh->edges.attributes(),"operand_bit"
1383
1/2
✓ Branch 2 taken 79 times.
✗ Branch 3 not taken.
79 );
1384
2/2
✓ Branch 5 taken 1837 times.
✓ Branch 6 taken 79 times.
1916 for(index_t e: mesh->edges) {
1385
1/2
✓ Branch 1 taken 1837 times.
✗ Branch 2 not taken.
1837 e_operand_bit[e] = index_t(1);
1386 }
1387
2/4
✓ Branch 1 taken 79 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 79 times.
✗ Branch 5 not taken.
158 triangulate(mesh, "union");
1388 79 }
1389
1390 740 void CSGBuilder::finalize_mesh(std::shared_ptr<Mesh>& M) {
1391 740 geo_argused(M);
1392 740 }
1393
1394 305 Box3d CSGBuilder::get_bbox(const std::shared_ptr<Mesh>& M) {
1395 305 Box3d result;
1396
2/2
✓ Branch 0 taken 915 times.
✓ Branch 1 taken 305 times.
1220 for(index_t c=0; c<3; ++c) {
1397 915 result.xyz_min[c] = Numeric::max_float64();
1398 915 result.xyz_max[c] = -Numeric::max_float64();
1399 }
1400
2/2
✓ Branch 6 taken 35778 times.
✓ Branch 7 taken 305 times.
36083 for(index_t v: M->vertices) {
1401
1/2
✓ Branch 2 taken 35778 times.
✗ Branch 3 not taken.
35778 const double* pp = M->vertices.point_ptr(v);
1402 vec3 p(
1403
3/4
✓ Branch 2 taken 35778 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 32058 times.
✓ Branch 5 taken 3720 times.
35778 pp[0], pp[1], (M->vertices.dimension() == 3) ? pp[2] : 0.0
1404 35778 );
1405
2/2
✓ Branch 0 taken 107334 times.
✓ Branch 1 taken 35778 times.
143112 for(index_t c=0; c<3; ++c) {
1406
1/2
✓ Branch 1 taken 107334 times.
✗ Branch 2 not taken.
107334 result.xyz_min[c] = std::min(result.xyz_min[c], p[c]);
1407
1/2
✓ Branch 1 taken 107334 times.
✗ Branch 2 not taken.
107334 result.xyz_max[c] = std::max(result.xyz_max[c], p[c]);
1408 }
1409 }
1410 305 return result;
1411 }
1412
1413 /***************** AbstractCSGBuilder API implementation ***********/
1414
1415 248 void CSGBuilder::add_object(
1416 const std::string& object, const ArgList& args
1417 ) {
1418 248 AbstractCSGBuilder::add_object(object,args);
1419
1/6
✗ Branch 1 not taken.
✓ Branch 2 taken 248 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
248 geo_debug_assert(result_ != nullptr);
1420
1/2
✓ Branch 3 taken 248 times.
✗ Branch 4 not taken.
248 top_scope().push_back(result_);
1421 248 result_ = nullptr;
1422 248 }
1423
1424 454 void CSGBuilder::begin_instruction() {
1425 454 AbstractCSGBuilder::begin_instruction();
1426 454 push_scope();
1427 454 }
1428
1429 454 void CSGBuilder::end_instruction(
1430 const std::string& instruction, const ArgList& args
1431 ) {
1432 454 AbstractCSGBuilder::end_instruction(instruction, args);
1433 454 pop_scope();
1434
1/6
✗ Branch 1 not taken.
✓ Branch 2 taken 454 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
454 geo_debug_assert(result_ != nullptr);
1435
1/2
✓ Branch 3 taken 454 times.
✗ Branch 4 not taken.
454 top_scope().push_back(result_);
1436 454 result_ = nullptr;
1437 454 }
1438
1439 22 void CSGBuilder::add_square(const ArgList& args) {
1440
1/2
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
22 vec2 size{1.0, 1.0};
1441 // polymorphic size arg (painful...)
1442
3/6
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 22 times.
44 if(args.has_arg("size",0,Value::NUMBER)) {
1443 double s = 1.0;
1444 s = args.get_arg("size",s,0);
1445 size = {s,s};
1446 } else {
1447
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
44 size = args.get_arg("size",size,0);
1448 }
1449
2/4
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22 times.
✗ Branch 5 not taken.
22 bool center = args.get_arg("center", false, 1);
1450
1/2
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
22 result_ = square(size,center);
1451 22 }
1452
1453 24 void CSGBuilder::add_circle(const ArgList& args) {
1454 24 double r = 1.0;
1455
2/4
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
24 r = args.get_arg("r",r,0);
1456
1/2
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
24 result_ = circle(r);
1457 24 }
1458
1459 47 void CSGBuilder::add_cube(const ArgList& args) {
1460
1/2
✓ Branch 1 taken 47 times.
✗ Branch 2 not taken.
47 vec3 size{1.0, 1.0, 1.0};
1461 // polymorphic size arg (painful...)
1462
3/6
✓ Branch 1 taken 47 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 47 times.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 47 times.
94 if(args.has_arg("size",0,Value::NUMBER)) {
1463 double s = 1.0;
1464 s = args.get_arg("size",s,0);
1465 size = {s,s,s};
1466 } else {
1467
2/4
✓ Branch 1 taken 47 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 47 times.
✗ Branch 5 not taken.
94 size = args.get_arg("size",size,0);
1468 }
1469 47 bool center = false;
1470
2/4
✓ Branch 1 taken 47 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 47 times.
✗ Branch 5 not taken.
94 center = args.get_arg("center", center, 1);
1471
1/2
✓ Branch 1 taken 47 times.
✗ Branch 2 not taken.
47 result_ = cube(size,center);
1472 47 }
1473
1474 36 void CSGBuilder::add_sphere(const ArgList& args) {
1475
2/4
✓ Branch 1 taken 36 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 36 times.
✗ Branch 5 not taken.
36 double r = args.get_arg("r", 1.0, 0);
1476
1/2
✓ Branch 1 taken 36 times.
✗ Branch 2 not taken.
36 result_ = sphere(r);
1477 36 }
1478
1479 59 void CSGBuilder::add_cylinder(const ArgList& args) {
1480
2/4
✓ Branch 1 taken 59 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 59 times.
✗ Branch 5 not taken.
59 double h = args.get_arg("h", 1.0, 0);
1481 59 double r1 = 1.0;
1482 59 double r2 = 1.0;
1483 59 if(
1484
5/14
✓ Branch 1 taken 59 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 59 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 59 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 59 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 59 times.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 16 not taken.
✗ Branch 17 not taken.
295 args.has_arg("r1", 0, Value::NUMBER) &&
1485
6/16
✓ Branch 1 taken 59 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 59 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 59 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 59 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 59 times.
✗ Branch 12 not taken.
✓ Branch 13 taken 59 times.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
177 args.has_arg("r2", 1, Value::NUMBER)
1486 ) {
1487
2/4
✓ Branch 1 taken 59 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 59 times.
✗ Branch 5 not taken.
118 r1 = args.get_arg("r1", r1, 1);
1488
2/4
✓ Branch 1 taken 59 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 59 times.
✗ Branch 5 not taken.
118 r2 = args.get_arg("r2", r1, 2);
1489 } else {
1490 r1 = args.get_arg("r", r1, 1);
1491 r2 = r1;
1492 }
1493
2/4
✓ Branch 1 taken 59 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 59 times.
✗ Branch 5 not taken.
59 bool center = args.get_arg("center", false, 3);
1494
1/2
✓ Branch 1 taken 59 times.
✗ Branch 2 not taken.
59 result_ = cylinder(h,r1,r2,center);
1495 59 }
1496
1497 1 void CSGBuilder::add_polyhedron(const ArgList& args) {
1498
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 result_ = std::make_shared<Mesh>();
1499
11/30
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 1 times.
✗ Branch 10 not taken.
✓ Branch 12 taken 1 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✓ Branch 15 taken 1 times.
✓ Branch 16 taken 1 times.
✗ Branch 17 not taken.
✓ Branch 19 taken 1 times.
✗ Branch 20 not taken.
✓ Branch 21 taken 1 times.
✗ Branch 22 not taken.
✓ Branch 24 taken 1 times.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
✓ Branch 27 taken 1 times.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
✗ Branch 31 not taken.
✗ Branch 32 not taken.
✗ Branch 33 not taken.
✗ Branch 34 not taken.
✗ Branch 36 not taken.
✗ Branch 37 not taken.
5 if(!args.has_arg("points") || !args.has_arg("faces")) {
1500 error("polyhedron: missing points or facets");
1501 }
1502
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
2 const Value& points = args.get_arg_value("points");
1503
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 const Value& faces = args.get_arg_value("faces");
1504
1505
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if(points.type != Value::ARRAY2D || faces.type != Value::ARRAY2D) {
1506 error("polyhedron: wrong type (expected array)");
1507 }
1508
1509
1/2
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
1 result_->vertices.create_vertices(points.array_val.size());
1510
2/2
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 1 times.
6 for(index_t v=0; v<points.array_val.size(); ++v) {
1511
2/4
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 5 times.
5 if(points.array_val[v].size() != 3) {
1512 error("polyhedron: wrong vertex size (expected 3d)");
1513 }
1514
1/2
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 result_->vertices.point(v) = {
1515
2/4
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
5 points.array_val[v][0],
1516
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 points.array_val[v][1],
1517
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 points.array_val[v][2]
1518
3/6
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 5 times.
✗ Branch 8 not taken.
20 };
1519 }
1520
1521 1 vector<index_t> facet;
1522
2/2
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 1 times.
6 for(index_t f=0; f<faces.array_val.size(); ++f) {
1523
3/4
✓ Branch 1 taken 21 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 16 times.
✓ Branch 5 taken 5 times.
21 for(index_t lv=0; lv < faces.array_val[f].size(); ++lv) {
1524
2/4
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
16 double v = faces.array_val[f][lv];
1525
3/6
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 16 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 16 times.
16 if(v < 0.0 || v >= double(result_->vertices.nb())) {
1526 error(
1527 String::format(
1528 "polyhedron: invalid vertex index %d (max is %d)",
1529 int(v), int(result_->vertices.nb())-1
1530 )
1531 );
1532 }
1533
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
16 facet.push_back(index_t(v));
1534 }
1535
1/2
✓ Branch 3 taken 5 times.
✗ Branch 4 not taken.
5 index_t new_f = result_->facets.create_polygon(facet.size());
1536
2/2
✓ Branch 1 taken 16 times.
✓ Branch 2 taken 5 times.
21 for(index_t lv=0; lv < facet.size(); ++lv) {
1537
2/4
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 16 times.
✗ Branch 6 not taken.
16 result_->facets.set_vertex(new_f, lv, facet[lv]);
1538 }
1539
1/2
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
5 facet.resize(0);
1540 }
1541
1542
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 tessellate_facets(*result_,3);
1543
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 result_->facets.connect();
1544
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 finalize_mesh(result_);
1545 1 }
1546
1547 33 void CSGBuilder::add_polygon(const ArgList& args) {
1548
1/2
✓ Branch 1 taken 33 times.
✗ Branch 2 not taken.
33 result_ = std::make_shared<Mesh>();
1549 33 result_->vertices.set_dimension(2);
1550
1551
11/30
✓ Branch 1 taken 33 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 33 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 33 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 33 times.
✗ Branch 10 not taken.
✓ Branch 12 taken 33 times.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✓ Branch 15 taken 33 times.
✓ Branch 16 taken 33 times.
✗ Branch 17 not taken.
✓ Branch 19 taken 33 times.
✗ Branch 20 not taken.
✓ Branch 21 taken 33 times.
✗ Branch 22 not taken.
✓ Branch 24 taken 33 times.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
✓ Branch 27 taken 33 times.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
✗ Branch 31 not taken.
✗ Branch 32 not taken.
✗ Branch 33 not taken.
✗ Branch 34 not taken.
✗ Branch 36 not taken.
✗ Branch 37 not taken.
165 if(!args.has_arg("points") || !args.has_arg("paths")) {
1552 error("polygon: missing points or paths");
1553 }
1554
1555
2/4
✓ Branch 1 taken 33 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 33 times.
✗ Branch 5 not taken.
33 const Value& points = args.get_arg_value("points");
1556
1557
2/6
✗ Branch 0 not taken.
✓ Branch 1 taken 33 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 33 times.
33 if(points.type == Value::ARRAY1D && points.array_val.size() == 0) {
1558 // Special case, empty array, happens with BOSL2 scripts
1559 return;
1560 }
1561
1562
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 33 times.
33 if(points.type != Value::ARRAY2D) {
1563 error("polygon: wrong points type (expected array)");
1564 }
1565
1566 33 result_->vertices.create_vertices(points.array_val.size());
1567
2/2
✓ Branch 1 taken 209 times.
✓ Branch 2 taken 33 times.
242 for(index_t v=0; v<points.array_val.size(); ++v) {
1568
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 209 times.
209 if(points.array_val[v].size() != 2) {
1569 error("polyhedron: wrong vertex size (expected 2d)");
1570 }
1571
1/2
✓ Branch 2 taken 209 times.
✗ Branch 3 not taken.
209 result_->vertices.point<2>(v) = {
1572
2/4
✓ Branch 1 taken 209 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 209 times.
✗ Branch 5 not taken.
209 points.array_val[v][0],
1573
1/2
✓ Branch 1 taken 209 times.
✗ Branch 2 not taken.
209 points.array_val[v][1]
1574
2/4
✓ Branch 1 taken 209 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 209 times.
✗ Branch 5 not taken.
627 };
1575 }
1576
1577
2/4
✓ Branch 1 taken 33 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 33 times.
✗ Branch 5 not taken.
33 const Value& paths = args.get_arg_value("paths");
1578
1579
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 33 times.
33 if(paths.type == Value::ARRAY2D ) {
1580 for(const auto& P : paths.array_val) {
1581 for(double v: P) {
1582 if(v < 0.0 || v >= double(result_->vertices.nb())) {
1583 error(
1584 String::format(
1585 "polygon: invalid vertex index %d (max is %d)",
1586 int(v), int(result_->vertices.nb())-1
1587 )
1588 );
1589 }
1590 }
1591 for(index_t lv1=0; lv1 < P.size(); ++lv1) {
1592 index_t lv2 = (lv1+1)%P.size();
1593 index_t v1 = index_t(P[lv1]);
1594 index_t v2 = index_t(P[lv2]);
1595 // some files do [0,1,2], some others [0,1,2,0], so we need
1596 // to test here for null edges.
1597 if(v1 != v2) {
1598 result_->edges.create_edge(v1,v2);
1599 }
1600 }
1601 }
1602
1/2
✓ Branch 0 taken 33 times.
✗ Branch 1 not taken.
33 } else if(paths.type == Value::NONE) {
1603
2/2
✓ Branch 1 taken 209 times.
✓ Branch 2 taken 33 times.
242 for(index_t v1=0; v1 < points.array_val.size(); ++v1) {
1604 209 index_t v2 = (v1+1)%points.array_val.size();
1605 209 result_->edges.create_edge(v1,v2);
1606 }
1607 } else {
1608 error("polygon: wrong path type (expected array or undef)");
1609 }
1610 33 triangulate(result_);
1611 33 finalize_mesh(result_);
1612 }
1613
1614 24 void CSGBuilder::add_import(const ArgList& args) {
1615
3/6
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 24 times.
✗ Branch 8 not taken.
96 std::string filename = args.get_arg("file", std::string(""));
1616
3/6
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 24 times.
✗ Branch 8 not taken.
96 std::string layer = args.get_arg("layer", std::string(""));
1617
2/4
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
24 index_t timestamp = index_t(args.get_arg("timestamp", 0));
1618
2/4
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 24 times.
✗ Branch 6 not taken.
48 vec2 origin = args.get_arg("origin", vec2(0.0, 0.0));
1619
2/4
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 24 times.
✗ Branch 6 not taken.
48 vec2 scale = args.get_arg("scale", vec2(1.0, 1.0));
1620
1/2
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
48 result_ = import(
1621
1/2
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
48 filename,layer,timestamp,origin,scale
1622 24 );
1623
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
24 if(result_ == nullptr) {
1624 error((filename + ": could not load").c_str());
1625 }
1626 24 }
1627
1628 2 void CSGBuilder::add_surface(const ArgList& args) {
1629
3/6
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
8 std::string filename = args.get_arg("file", std::string(""));
1630
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
4 bool center = args.get_arg("center", false);
1631
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 bool invert = args.get_arg("invert", false);
1632
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 result_ = surface(filename, center, invert);
1633 2 }
1634
1635 void CSGBuilder::add_text(const ArgList& args) {
1636 std::string text = args.get_arg("text", "");
1637 if(text == "") {
1638 text = args.get_arg("arg_0", "");
1639 }
1640 if(text == "") {
1641 text = args.get_arg("t", "");
1642 }
1643 double size = args.get_arg("size", 10.0);
1644 std::string font = args.get_arg("font", "");
1645 std::string halign = args.get_arg("halign", "left");
1646 std::string valign = args.get_arg("valign", "baseline");
1647 double spacing = args.get_arg("spacing", 1.0);
1648 std::string direction = args.get_arg("direction", "ltr");
1649 std::string language = args.get_arg("language", "en");
1650 std::string script = args.get_arg("script", "latin");
1651 result_ = this->text(
1652 text, size, font, halign, valign,
1653 spacing, direction, language, script
1654 );
1655 }
1656
1657 254 void CSGBuilder::eval_multmatrix(const ArgList& args) {
1658
1/2
✓ Branch 1 taken 254 times.
✗ Branch 2 not taken.
254 mat4 xform;
1659 254 xform.load_identity();
1660
2/4
✓ Branch 1 taken 254 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 254 times.
✗ Branch 5 not taken.
254 xform = args.get_arg("m", xform, 0);
1661
2/4
✓ Branch 1 taken 254 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 254 times.
✗ Branch 5 not taken.
254 result_ = multmatrix(xform, top_scope());
1662 254 }
1663
1664 void CSGBuilder::eval_translate(const ArgList& args) {
1665 vec3 T{0.0, 0.0, 0.0};
1666 T = args.get_arg("v", T, 0);
1667 mat4 xform = translation_matrix(T);
1668 result_ = multmatrix(xform, top_scope());
1669 }
1670
1671 void CSGBuilder::eval_rotate(const ArgList& args) {
1672 double a(0.0);
1673 vec3 v{0.0, 0.0, 0.0};
1674 a = args.get_arg("a",a,0);
1675 v = args.get_arg("v",v,1);
1676 mat4 xform;
1677 if(a == 0.0) {
1678 // no angle specified, v is [rx, ry, rz]
1679 xform = rotation_matrix(v);
1680 } else {
1681 // v is rotation axis
1682 xform = rotation_matrix(a, v);
1683 }
1684 result_ = multmatrix(xform, top_scope());
1685 }
1686
1687 void CSGBuilder::eval_scale(const ArgList& args) {
1688 vec3 s{1.0, 1.0, 1.0};
1689 s = args.get_arg("v",s,0);
1690 mat4 xform = scaling_matrix(s.x, s.y, s.z);
1691 result_ = multmatrix(xform, top_scope());
1692 }
1693
1694 void CSGBuilder::eval_resize(const ArgList& args) {
1695 vec3 newsize(1.0, 1.0, 1.0);
1696 vec3 autosize(0.0, 0.0, 0.0);
1697 newsize = args.get_arg("newsize",newsize,0);
1698 autosize = args.get_arg("autosize",autosize,1);
1699
1700 result_ = union_instr(top_scope());
1701 vec3 scaling(1.0, 1.0, 1.0);
1702 double default_scaling = 1.0;
1703 Box3d B = get_bbox(result_);
1704
1705 for(index_t coord=0; coord<3; ++coord) {
1706 if(newsize[coord] != 0) {
1707 scaling[coord] = newsize[coord] / (
1708 B.xyz_max[coord] - B.xyz_min[coord]
1709 );
1710 default_scaling = scaling[coord];
1711 }
1712 }
1713
1714 for(index_t coord=0; coord<3; ++coord) {
1715 if(newsize[coord] == 0.0) {
1716 if(autosize[coord] == 1.0) {
1717 scaling[coord] = default_scaling;
1718 }
1719 }
1720 }
1721 for(index_t v: result_->vertices) {
1722 if(result_->vertices.dimension() == 3) {
1723 result_->vertices.point<3>(v).x *= scaling.x;
1724 result_->vertices.point<3>(v).y *= scaling.y;
1725 result_->vertices.point<3>(v).z *= scaling.z;
1726 } else {
1727 result_->vertices.point<2>(v).x *= scaling.x;
1728 result_->vertices.point<2>(v).y *= scaling.y;
1729 }
1730 }
1731 }
1732
1733 13 void CSGBuilder::eval_union(const ArgList& args) {
1734 13 geo_argused(args);
1735
2/4
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 13 times.
✗ Branch 5 not taken.
13 result_ = union_instr(top_scope());
1736 13 }
1737
1738 18 void CSGBuilder::eval_intersection(const ArgList& args) {
1739 18 geo_argused(args);
1740
2/4
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 18 times.
✗ Branch 5 not taken.
18 result_ = intersection(top_scope());
1741 18 }
1742
1743 28 void CSGBuilder::eval_difference(const ArgList& args) {
1744 28 geo_argused(args);
1745
2/4
✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 28 times.
✗ Branch 5 not taken.
28 result_ = difference(top_scope());
1746 28 }
1747
1748 112 void CSGBuilder::eval_group(const ArgList& args) {
1749 112 geo_argused(args);
1750
2/4
✓ Branch 1 taken 112 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 112 times.
✗ Branch 5 not taken.
112 result_ = group(top_scope());
1751 112 }
1752
1753 1 void CSGBuilder::eval_color(const ArgList& args) {
1754 1 vec4 C(1.0, 1.0, 1.0, 1.0);
1755
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 C = args.get_arg("arg_0",C);
1756
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 result_ = color(C,top_scope());
1757 1 }
1758
1759 1 void CSGBuilder::eval_hull(const ArgList& args) {
1760 1 geo_argused(args);
1761
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 result_ = hull(top_scope());
1762 1 }
1763
1764 24 void CSGBuilder::eval_linear_extrude(const ArgList& args) {
1765
2/4
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
48 double height = args.get_arg("height", 1.0);
1766
2/4
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
24 bool center = args.get_arg("center", false);
1767
2/4
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 24 times.
✗ Branch 6 not taken.
72 vec2 scale = args.get_arg("scale", vec2(1.0, 1.0));
1768
2/4
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
48 index_t slices = index_t(args.get_arg("slices",0));
1769
2/4
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
24 double twist = args.get_arg("twist",0.0);
1770 24 result_ = linear_extrude(
1771
2/4
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
24 top_scope(), height, center, scale, slices, twist
1772 24 );
1773 24 }
1774
1775 3 void CSGBuilder::eval_rotate_extrude(const ArgList& args) {
1776
2/4
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
3 double angle = args.get_arg("angle", 360.0);
1777
2/4
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
3 result_ = rotate_extrude(top_scope(),angle);
1778 3 }
1779
1780 void CSGBuilder::eval_projection(const ArgList& args) {
1781 bool cut = args.get_arg("cut", false);
1782 result_ = projection(top_scope(),cut);
1783 }
1784
1785 void CSGBuilder::eval_minkowski(const ArgList& args) {
1786 geo_argused(args);
1787 result_ = minkowski(top_scope());
1788 }
1789
1790 4 void CSGBuilder::eval_render(const ArgList& args) {
1791 4 eval_group(args);
1792 4 }
1793
1794 }
1795