1 /*
2 * OpenSCAD (www.openscad.org)
3 * Copyright (C) 2009-2011 Clifford Wolf <clifford@clifford.at> and
4 * Marius Kintel <marius@kintel.net>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * As a special exception, you have permission to link this program
12 * with the CGAL library and distribute executables, as long as you
13 * follow the requirements of the GNU GPL in regard to all of the
14 * software in the executable aside from CGAL.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 *
25 */
26
27 #include "module.h"
28 #include "node.h"
29 #include "polyset.h"
30 #include "evalcontext.h"
31 #include "Polygon2d.h"
32 #include "builtin.h"
33 #include "printutils.h"
34 #include "context.h"
35 #include "calc.h"
36 #include "degree_trig.h"
37 #include <sstream>
38 #include <assert.h>
39 #include <cmath>
40 #include <boost/assign/std/vector.hpp>
41 #include "ModuleInstantiation.h"
42 #include "boost-utils.h"
43 using namespace boost::assign; // bring 'operator+=()' into scope
44
45 #define F_MINIMUM 0.01
46
47 enum class primitive_type_e {
48 CUBE,
49 SPHERE,
50 CYLINDER,
51 POLYHEDRON,
52 SQUARE,
53 CIRCLE,
54 POLYGON
55 };
56
57 class PrimitiveModule : public AbstractModule
58 {
59 public:
60 primitive_type_e type;
PrimitiveModule(primitive_type_e type)61 PrimitiveModule(primitive_type_e type) : type(type) { }
62 AbstractNode *instantiate(const std::shared_ptr<Context>& ctx, const ModuleInstantiation *inst, const std::shared_ptr<EvalContext>& evalctx) const override;
63 private:
64 Value lookup_radius(const std::shared_ptr<Context> ctx, const Location &loc, const std::string &radius_var, const std::string &diameter_var) const;
65 };
66
67 class PrimitiveNode : public LeafNode
68 {
69 public:
70 VISITABLE();
PrimitiveNode(const ModuleInstantiation * mi,const std::shared_ptr<EvalContext> & ctx,primitive_type_e type,const std::string & docPath)71 PrimitiveNode(const ModuleInstantiation *mi, const std::shared_ptr<EvalContext> &ctx, primitive_type_e type, const std::string &docPath) : LeafNode(mi, ctx),
72 document_path(docPath), type(type), points(Value::undefined.clone()), paths(Value::undefined.clone()), faces(Value::undefined.clone()) { }
73 std::string toString() const override;
name() const74 std::string name() const override {
75 switch (this->type) {
76 case primitive_type_e::CUBE: return "cube";
77 case primitive_type_e::SPHERE: return "sphere";
78 case primitive_type_e::CYLINDER: return "cylinder";
79 case primitive_type_e::POLYHEDRON: return "polyhedron";
80 case primitive_type_e::SQUARE: return "square";
81 case primitive_type_e::CIRCLE: return "circle";
82 case primitive_type_e::POLYGON: return "polygon";
83 default: assert(false && "PrimitiveNode::name(): Unknown primitive type");
84 }
85 return "unknown";
86 }
87 const std::string document_path;
88
89 bool center;
90 double x, y, z, h, r1, r2;
91 double fn, fs, fa;
92 primitive_type_e type;
93 int convexity;
94 Value points, paths, faces;
95 const Geometry *createGeometry() const override;
96 };
97
98 /**
99 * Return a radius value by looking up both a diameter and radius variable.
100 * The diameter has higher priority, so if found an additionally set radius
101 * value is ignored.
102 *
103 * @param ctx data context with variable values.
104 * @param radius_var name of the variable to lookup for the radius value.
105 * @param diameter_var name of the variable to lookup for the diameter value.
106 * @return radius value of type Value::Type::NUMBER or Value::Type::UNDEFINED if both
107 * variables are invalid or not set.
108 */
lookup_radius(const std::shared_ptr<Context> ctx,const Location & loc,const std::string & diameter_var,const std::string & radius_var) const109 Value PrimitiveModule::lookup_radius(const std::shared_ptr<Context> ctx, const Location &loc, const std::string &diameter_var, const std::string &radius_var) const
110 {
111 const auto &d = ctx->lookup_variable(diameter_var, true);
112 const auto &r = ctx->lookup_variable(radius_var, true);
113 const auto r_defined = (r.type() == Value::Type::NUMBER);
114
115 if (d.type() == Value::Type::NUMBER) {
116 if (r_defined) {
117 LOG(message_group::Warning,loc,ctx->documentPath(),
118 "Ignoring radius variable '%1$s' as diameter '%2$s' is defined too.",radius_var,diameter_var);
119 }
120 return d.toDouble() / 2.0;
121 } else if (r_defined) {
122 return r.clone();
123 } else {
124 return Value::undefined.clone();
125 }
126 }
127
instantiate(const std::shared_ptr<Context> & ctx,const ModuleInstantiation * inst,const std::shared_ptr<EvalContext> & evalctx) const128 AbstractNode *PrimitiveModule::instantiate(const std::shared_ptr<Context>& ctx, const ModuleInstantiation *inst, const std::shared_ptr<EvalContext>& evalctx) const
129 {
130 auto node = new PrimitiveNode(inst, evalctx, this->type, ctx->documentPath());
131
132 node->center = false;
133 node->x = node->y = node->z = node->h = node->r1 = node->r2 = 1;
134
135 AssignmentList args;
136 AssignmentList optargs;
137 if(inst->scope.hasChildren()){
138 LOG(message_group::Warning,inst->location(),ctx->documentPath(),
139 "module %1$s() does not support child modules",node->name());
140 }
141
142 switch (this->type) {
143 case primitive_type_e::CUBE:
144 args += assignment("size"), assignment("center");
145 break;
146 case primitive_type_e::SPHERE:
147 args += assignment("r");
148 optargs += assignment("d");
149 break;
150 case primitive_type_e::CYLINDER:
151 args += assignment("h"), assignment("r1"), assignment("r2"), assignment("center");
152 optargs += assignment("r"), assignment("d"), assignment("d1"), assignment("d2");
153 break;
154 case primitive_type_e::POLYHEDRON:
155 args += assignment("points"), assignment("faces"), assignment("convexity");
156 optargs += assignment("triangles");
157 break;
158 case primitive_type_e::SQUARE:
159 args += assignment("size"), assignment("center");
160 break;
161 case primitive_type_e::CIRCLE:
162 args += assignment("r");
163 optargs += assignment("d");
164 break;
165 case primitive_type_e::POLYGON:
166 args += assignment("points"), assignment("paths"), assignment("convexity");
167 break;
168 default:
169 assert(false && "PrimitiveModule::instantiate(): Unknown node type");
170 }
171
172 ContextHandle<Context> c{Context::create<Context>(ctx)};
173 c->setVariables(evalctx, args, optargs);
174
175 node->fn = c->lookup_variable("$fn").toDouble();
176 node->fs = c->lookup_variable("$fs").toDouble();
177 node->fa = c->lookup_variable("$fa").toDouble();
178
179 if (node->fs < F_MINIMUM) {
180 LOG(message_group::Warning,inst->location(),ctx->documentPath(),
181 "$fs too small - clamping to %1$f",F_MINIMUM);
182 node->fs = F_MINIMUM;
183 }
184 if (node->fa < F_MINIMUM) {
185 LOG(message_group::Warning,inst->location(),ctx->documentPath(),
186 "$fa too small - clamping to %1$f",F_MINIMUM);
187 node->fa = F_MINIMUM;
188 }
189
190 switch (this->type) {
191 case primitive_type_e::CUBE: {
192 const auto &size = c->lookup_variable("size");
193 const auto ¢er = c->lookup_variable("center");
194 if (size.isDefined()) {
195 bool converted=false;
196 converted |= size.getDouble(node->x);
197 converted |= size.getDouble(node->y);
198 converted |= size.getDouble(node->z);
199 converted |= size.getVec3(node->x, node->y, node->z);
200 if (!converted) {
201 LOG(message_group::Warning,inst->location(),ctx->documentPath(),"Unable to convert cube(size=%1$s, ...) parameter to a number or a vec3 of numbers",size.toEchoString());
202 } else if(OpenSCAD::rangeCheck) {
203 bool ok = (node->x > 0) && (node->y > 0) && (node->z > 0);
204 ok &= std::isfinite(node->x) && std::isfinite(node->y) && std::isfinite(node->z);
205 if(!ok){
206 LOG(message_group::Warning,inst->location(),ctx->documentPath(),"cube(size=%1$s, ...)",size.toEchoString());
207 }
208 }
209 }
210 if (center.type() == Value::Type::BOOL) {
211 node->center = center.toBool();
212 }
213 break;
214 }
215 case primitive_type_e::SPHERE: {
216 const auto r = lookup_radius(c.ctx, inst->location(), "d", "r");
217 if (r.type() == Value::Type::NUMBER) {
218 node->r1 = r.toDouble();
219 if (OpenSCAD::rangeCheck && (node->r1 <= 0 || !std::isfinite(node->r1))){
220 LOG(message_group::Warning,inst->location(),ctx->documentPath(),
221 "sphere(r=%1$s)",r.toEchoString());
222 }
223 }
224 break;
225 }
226 case primitive_type_e::CYLINDER: {
227 const auto &h = c->lookup_variable("h");
228 if (h.type() == Value::Type::NUMBER) {
229 node->h = h.toDouble();
230 }
231
232 auto r = lookup_radius(c.ctx, inst->location(), "d", "r");
233 auto r1 = lookup_radius(c.ctx, inst->location(), "d1", "r1");
234 auto r2 = lookup_radius(c.ctx, inst->location(), "d2", "r2");
235 if(r.type() == Value::Type::NUMBER &&
236 (r1.type() == Value::Type::NUMBER || r2.type() == Value::Type::NUMBER)
237 ){
238 LOG(message_group::Warning,inst->location(),ctx->documentPath(),
239 "Cylinder parameters ambiguous");
240 }
241
242 if (r.type() == Value::Type::NUMBER) {
243 node->r1 = r.toDouble();
244 node->r2 = r.toDouble();
245 }
246 if (r1.type() == Value::Type::NUMBER) {
247 node->r1 = r1.toDouble();
248 }
249 if (r2.type() == Value::Type::NUMBER) {
250 node->r2 = r2.toDouble();
251 }
252
253 if(OpenSCAD::rangeCheck){
254 if (node->h <= 0 || !std::isfinite(node->h)){
255 LOG(message_group::Warning,inst->location(),ctx->documentPath(),"cylinder(h=%1$s, ...)",h.toEchoString());
256 }
257 if (node->r1 < 0 || node->r2 < 0 || (node->r1 == 0 && node->r2 == 0) || !std::isfinite(node->r1) || !std::isfinite(node->r2)){
258 LOG(message_group::Warning,inst->location(),ctx->documentPath(),
259 "cylinder(r1=%1$s, r2=%2$s, ...)",
260 (r1.type() == Value::Type::NUMBER ? r1.toEchoString() : r.toEchoString()),
261 (r2.type() == Value::Type::NUMBER ? r2.toEchoString() : r.toEchoString()));
262 }
263 }
264
265 const auto ¢er = c->lookup_variable("center");
266 if (center.type() == Value::Type::BOOL) {
267 node->center = center.toBool();
268 }
269 break;
270 }
271 case primitive_type_e::POLYHEDRON: {
272 node->points = c->lookup_variable("points").clone();
273 node->faces = c->lookup_variable("faces").clone();
274 if (node->faces.type() == Value::Type::UNDEFINED) {
275 // backwards compatible
276 node->faces = c->lookup_variable("triangles", true).clone();
277 if (node->faces.type() != Value::Type::UNDEFINED) {
278 LOG(message_group::Deprecated,Location::NONE,"","polyhedron(triangles=[]) will be removed in future releases. Use polyhedron(faces=[]) instead.");
279 }
280 }
281 break;
282 }
283 case primitive_type_e::SQUARE: {
284 const auto &size = c->lookup_variable("size");
285 const auto ¢er = c->lookup_variable("center");
286 if (size.isDefined()) {
287 bool converted=false;
288 converted |= size.getDouble(node->x);
289 converted |= size.getDouble(node->y);
290 converted |= size.getVec2(node->x, node->y);
291 if (!converted) {
292 LOG(message_group::Warning,inst->location(),ctx->documentPath(),"Unable to convert square(size=%1$s, ...) parameter to a number or a vec2 of numbers",size.toEchoString());
293 } else if (OpenSCAD::rangeCheck) {
294 bool ok = true;
295 ok &= (node->x > 0) && (node->y > 0);
296 ok &= std::isfinite(node->x) && std::isfinite(node->y);
297 if(!ok){
298 LOG(message_group::Warning,inst->location(),ctx->documentPath(),"square(size=%1$s, ...)",size.toEchoString());
299 }
300 }
301 }
302 if (center.type() == Value::Type::BOOL) {
303 node->center = center.toBool();
304 }
305 break;
306 }
307 case primitive_type_e::CIRCLE: {
308 const auto r = lookup_radius(c.ctx, inst->location(), "d", "r");
309 if (r.type() == Value::Type::NUMBER) {
310 node->r1 = r.toDouble();
311 if (OpenSCAD::rangeCheck && ((node->r1 <= 0) || !std::isfinite(node->r1))){
312 LOG(message_group::Warning,inst->location(),ctx->documentPath(),
313 "circle(r=%1$s)",r.toEchoString());
314 }
315 }
316 break;
317 }
318 case primitive_type_e::POLYGON: {
319 node->points = c->lookup_variable("points").clone();
320 node->paths = c->lookup_variable("paths").clone();
321 break;
322 }
323 }
324
325 node->convexity = (int)c->lookup_variable("convexity", true).toDouble();
326 if (node->convexity < 1) node->convexity = 1;
327
328 return node;
329 }
330
331 struct point2d {
332 double x, y;
333 };
334
generate_circle(point2d * circle,double r,int fragments)335 static void generate_circle(point2d *circle, double r, int fragments)
336 {
337 for (int i=0; i<fragments; ++i) {
338 double phi = (360.0 * i) / fragments;
339 circle[i].x = r * cos_degrees(phi);
340 circle[i].y = r * sin_degrees(phi);
341 }
342 }
343
344 /*!
345 Creates geometry for this node.
346 May return an empty Geometry creation failed, but will not return nullptr.
347 */
createGeometry() const348 const Geometry *PrimitiveNode::createGeometry() const
349 {
350 Geometry *g = nullptr;
351
352 switch (this->type) {
353 case primitive_type_e::CUBE: {
354 auto p = new PolySet(3,true);
355 g = p;
356 if (this->x > 0 && this->y > 0 && this->z > 0 &&
357 !std::isinf(this->x) && !std::isinf(this->y) && !std::isinf(this->z)) {
358 double x1, x2, y1, y2, z1, z2;
359 if (this->center) {
360 x1 = -this->x/2;
361 x2 = +this->x/2;
362 y1 = -this->y/2;
363 y2 = +this->y/2;
364 z1 = -this->z/2;
365 z2 = +this->z/2;
366 } else {
367 x1 = y1 = z1 = 0;
368 x2 = this->x;
369 y2 = this->y;
370 z2 = this->z;
371 }
372
373 p->append_poly(); // top
374 p->append_vertex(x1, y1, z2);
375 p->append_vertex(x2, y1, z2);
376 p->append_vertex(x2, y2, z2);
377 p->append_vertex(x1, y2, z2);
378
379 p->append_poly(); // bottom
380 p->append_vertex(x1, y2, z1);
381 p->append_vertex(x2, y2, z1);
382 p->append_vertex(x2, y1, z1);
383 p->append_vertex(x1, y1, z1);
384
385 p->append_poly(); // side1
386 p->append_vertex(x1, y1, z1);
387 p->append_vertex(x2, y1, z1);
388 p->append_vertex(x2, y1, z2);
389 p->append_vertex(x1, y1, z2);
390
391 p->append_poly(); // side2
392 p->append_vertex(x2, y1, z1);
393 p->append_vertex(x2, y2, z1);
394 p->append_vertex(x2, y2, z2);
395 p->append_vertex(x2, y1, z2);
396
397 p->append_poly(); // side3
398 p->append_vertex(x2, y2, z1);
399 p->append_vertex(x1, y2, z1);
400 p->append_vertex(x1, y2, z2);
401 p->append_vertex(x2, y2, z2);
402
403 p->append_poly(); // side4
404 p->append_vertex(x1, y2, z1);
405 p->append_vertex(x1, y1, z1);
406 p->append_vertex(x1, y1, z2);
407 p->append_vertex(x1, y2, z2);
408 }
409 }
410 break;
411 case primitive_type_e::SPHERE: {
412 auto p = new PolySet(3,true);
413 g = p;
414 if (this->r1 > 0 && !std::isinf(this->r1)) {
415 struct ring_s {
416 std::vector<point2d> points;
417 double z;
418 };
419
420 auto fragments = Calc::get_fragments_from_r(r1, fn, fs, fa);
421 int rings = (fragments+1)/2;
422 // Uncomment the following three lines to enable experimental sphere tesselation
423 // if (rings % 2 == 0) rings++; // To ensure that the middle ring is at phi == 0 degrees
424
425 auto ring = std::vector<ring_s>(rings);
426
427 // double offset = 0.5 * ((fragments / 2) % 2);
428 for (int i = 0; i < rings; ++i) {
429 // double phi = (180.0 * (i + offset)) / (fragments/2);
430 double phi = (180.0 * (i + 0.5)) / rings;
431 double r = r1 * sin_degrees(phi);
432 ring[i].z = r1 * cos_degrees(phi);
433 ring[i].points.resize(fragments);
434 generate_circle(ring[i].points.data(), r, fragments);
435 }
436
437 p->append_poly();
438 for (int i = 0; i < fragments; ++i)
439 p->append_vertex(ring[0].points[i].x, ring[0].points[i].y, ring[0].z);
440
441 for (int i = 0; i < rings-1; ++i) {
442 auto r1 = &ring[i];
443 auto r2 = &ring[i+1];
444 int r1i = 0, r2i = 0;
445 while (r1i < fragments || r2i < fragments) {
446 if (r1i >= fragments) goto sphere_next_r2;
447 if (r2i >= fragments) goto sphere_next_r1;
448 if ((double)r1i / fragments < (double)r2i / fragments) {
449 sphere_next_r1:
450 p->append_poly();
451 int r1j = (r1i+1) % fragments;
452 p->insert_vertex(r1->points[r1i].x, r1->points[r1i].y, r1->z);
453 p->insert_vertex(r1->points[r1j].x, r1->points[r1j].y, r1->z);
454 p->insert_vertex(r2->points[r2i % fragments].x, r2->points[r2i % fragments].y, r2->z);
455 r1i++;
456 } else {
457 sphere_next_r2:
458 p->append_poly();
459 int r2j = (r2i+1) % fragments;
460 p->append_vertex(r2->points[r2i].x, r2->points[r2i].y, r2->z);
461 p->append_vertex(r2->points[r2j].x, r2->points[r2j].y, r2->z);
462 p->append_vertex(r1->points[r1i % fragments].x, r1->points[r1i % fragments].y, r1->z);
463 r2i++;
464 }
465 }
466 }
467
468 p->append_poly();
469 for (int i = 0; i < fragments; ++i) {
470 p->insert_vertex(ring[rings-1].points[i].x,
471 ring[rings-1].points[i].y,
472 ring[rings-1].z);
473 }
474 }
475 }
476 break;
477 case primitive_type_e::CYLINDER: {
478 auto p = new PolySet(3,true);
479 g = p;
480 if (this->h > 0 && !std::isinf(this->h) &&
481 this->r1 >=0 && this->r2 >= 0 && (this->r1 > 0 || this->r2 > 0) &&
482 !std::isinf(this->r1) && !std::isinf(this->r2)) {
483 auto fragments = Calc::get_fragments_from_r(std::fmax(this->r1, this->r2), this->fn, this->fs, this->fa);
484
485 double z1, z2;
486 if (this->center) {
487 z1 = -this->h/2;
488 z2 = +this->h/2;
489 } else {
490 z1 = 0;
491 z2 = this->h;
492 }
493
494 auto circle1 = std::vector<point2d>(fragments);
495 auto circle2 = std::vector<point2d>(fragments);
496
497 generate_circle(circle1.data(), r1, fragments);
498 generate_circle(circle2.data(), r2, fragments);
499
500 for (int i=0; i<fragments; ++i) {
501 int j = (i+1) % fragments;
502 if (r1 == r2) {
503 p->append_poly();
504 p->insert_vertex(circle1[i].x, circle1[i].y, z1);
505 p->insert_vertex(circle2[i].x, circle2[i].y, z2);
506 p->insert_vertex(circle2[j].x, circle2[j].y, z2);
507 p->insert_vertex(circle1[j].x, circle1[j].y, z1);
508 } else {
509 if (r1 > 0) {
510 p->append_poly();
511 p->insert_vertex(circle1[i].x, circle1[i].y, z1);
512 p->insert_vertex(circle2[i].x, circle2[i].y, z2);
513 p->insert_vertex(circle1[j].x, circle1[j].y, z1);
514 }
515 if (r2 > 0) {
516 p->append_poly();
517 p->insert_vertex(circle2[i].x, circle2[i].y, z2);
518 p->insert_vertex(circle2[j].x, circle2[j].y, z2);
519 p->insert_vertex(circle1[j].x, circle1[j].y, z1);
520 }
521 }
522 }
523
524 if (this->r1 > 0) {
525 p->append_poly();
526 for (int i=0; i<fragments; ++i)
527 p->insert_vertex(circle1[i].x, circle1[i].y, z1);
528 }
529
530 if (this->r2 > 0) {
531 p->append_poly();
532 for (int i=0; i<fragments; ++i)
533 p->append_vertex(circle2[i].x, circle2[i].y, z2);
534 }
535 }
536 }
537 break;
538 case primitive_type_e::POLYHEDRON: {
539 auto p = new PolySet(3);
540 g = p;
541 p->setConvexity(this->convexity);
542 const auto &pts = this->points.toVector();
543 size_t face_i = 0;
544 for (const auto &face : this->faces.toVector()) {
545 p->append_poly();
546 size_t fp_i = 0;
547 for (const auto &pt_i_val : face.toVector()) {
548 size_t pt_i = (size_t)pt_i_val.toDouble();
549 if (pt_i < pts.size()) {
550 double px, py, pz;
551 if (!pts[pt_i].getVec3(px, py, pz, 0.0) ||
552 !std::isfinite(px) || !std::isfinite(py) || !std::isfinite(pz)) {
553 LOG(message_group::Error,this->modinst->location(),this->document_path,"Unable to convert points[%1$d] = %2$s to a vec3 of numbers",pt_i,pts[pt_i].toEchoString());
554 return p;
555 }
556 p->insert_vertex(px, py, pz);
557 } else {
558 LOG(message_group::Warning,this->modinst->location(),this->document_path,"Point index %1$d is out of bounds (from faces[%2$d][%3$d])",pt_i,face_i,fp_i);
559 }
560 ++fp_i;
561 }
562 ++face_i;
563 }
564 }
565 break;
566 case primitive_type_e::SQUARE: {
567 auto p = new Polygon2d();
568 g = p;
569 if (this->x > 0 && this->y > 0 &&
570 !std::isinf(this->x) && !std::isinf(this->y)) {
571 Vector2d v1(0, 0);
572 Vector2d v2(this->x, this->y);
573 if (this->center) {
574 v1 -= Vector2d(this->x/2, this->y/2);
575 v2 -= Vector2d(this->x/2, this->y/2);
576 }
577
578 Outline2d o;
579 o.vertices = {v1, {v2[0], v1[1]}, v2, {v1[0], v2[1]}};
580 p->addOutline(o);
581 }
582 p->setSanitized(true);
583 }
584 break;
585 case primitive_type_e::CIRCLE: {
586 auto p = new Polygon2d();
587 g = p;
588 if (this->r1 > 0 && !std::isinf(this->r1)) {
589 auto fragments = Calc::get_fragments_from_r(this->r1, this->fn, this->fs, this->fa);
590
591 Outline2d o;
592 o.vertices.resize(fragments);
593 for (int i=0; i < fragments; ++i) {
594 double phi = (360.0 * i) / fragments;
595 o.vertices[i] = {this->r1 * cos_degrees(phi), this->r1 * sin_degrees(phi)};
596 }
597 p->addOutline(o);
598 }
599 p->setSanitized(true);
600 }
601 break;
602 case primitive_type_e::POLYGON: {
603 auto p = new Polygon2d();
604 g = p;
605 Outline2d outline;
606 double x,y;
607 size_t i = 0;
608 for (const auto &val : this->points.toVector()) {
609 if (!val.getVec2(x, y) || std::isinf(x) || std::isinf(y)) {
610 LOG(message_group::Error,this->modinst->location(),this->document_path,"Unable to convert points[%1$d] = %2$s to a vec2 of numbers",i,val.toEchoString());
611 return p;
612 }
613 outline.vertices.emplace_back(x, y);
614 ++i;
615 }
616
617 if (this->paths.toVector().size() == 0 && outline.vertices.size() > 2) {
618 p->addOutline(outline);
619 }
620 else {
621 size_t path_i = 0;
622 for (const auto &polygon : this->paths.toVector()) {
623 Outline2d curroutline;
624 size_t path_pt_i = 0;
625 for (const auto &index : polygon.toVector()) {
626 unsigned int idx = (unsigned int)index.toDouble();
627 if (idx < outline.vertices.size()) {
628 curroutline.vertices.push_back(outline.vertices[idx]);
629 } else {
630 LOG(message_group::Warning,this->modinst->location(),this->document_path,"Point index %1$d is out of bounds (from paths[%2$d][%3$d])",idx , path_i , path_pt_i);
631 }
632 ++path_pt_i;
633 }
634 p->addOutline(curroutline);
635 ++path_i;
636 }
637 }
638
639 if (p->outlines().size() > 0) {
640 p->setConvexity(convexity);
641 }
642 }
643 }
644
645 return g;
646 }
647
toString() const648 std::string PrimitiveNode::toString() const
649 {
650 std::ostringstream stream;
651
652 stream << this->name();
653
654 switch (this->type) {
655 case primitive_type_e::CUBE:
656 stream << "(size = [" << this->x << ", " << this->y << ", " << this->z << "], "
657 << "center = " << (center ? "true" : "false") << ")";
658 break;
659 case primitive_type_e::SPHERE:
660 stream << "($fn = " << this->fn << ", $fa = " << this->fa
661 << ", $fs = " << this->fs << ", r = " << this->r1 << ")";
662 break;
663 case primitive_type_e::CYLINDER:
664 stream << "($fn = " << this->fn << ", $fa = " << this->fa
665 << ", $fs = " << this->fs << ", h = " << this->h << ", r1 = " << this->r1
666 << ", r2 = " << this->r2 << ", center = " << (center ? "true" : "false") << ")";
667 break;
668 case primitive_type_e::POLYHEDRON:
669 stream << "(points = ";
670 this->points.toStream(stream);
671 stream << ", faces = ";
672 this->faces.toStream(stream);
673 stream << ", convexity = " << this->convexity << ")";
674 break;
675 case primitive_type_e::SQUARE:
676 stream << "(size = [" << this->x << ", " << this->y << "], "
677 << "center = " << (center ? "true" : "false") << ")";
678 break;
679 case primitive_type_e::CIRCLE:
680 stream << "($fn = " << this->fn << ", $fa = " << this->fa
681 << ", $fs = " << this->fs << ", r = " << this->r1 << ")";
682 break;
683 case primitive_type_e::POLYGON:
684 stream << "(points = ";
685 this->points.toStream(stream);
686 stream << ", paths = ";
687 this->paths.toStream(stream);
688 stream << ", convexity = " << this->convexity << ")";
689 break;
690 default:
691 assert(false);
692 }
693
694 return stream.str();
695 }
696
register_builtin_primitives()697 void register_builtin_primitives()
698 {
699 Builtins::init("cube" , new PrimitiveModule(primitive_type_e::CUBE),
700 {
701 "cube(size)",
702 "cube([width, depth, height])",
703 "cube([width, depth, height], center = true)",
704 });
705
706 Builtins::init("sphere", new PrimitiveModule(primitive_type_e::SPHERE),
707 {
708 "sphere(radius)",
709 "sphere(r = radius)",
710 "sphere(d = diameter)",
711 });
712
713 Builtins::init("cylinder", new PrimitiveModule(primitive_type_e::CYLINDER),
714 {
715 "cylinder(h, r1, r2)",
716 "cylinder(h = height, r = radius, center = true)",
717 "cylinder(h = height, r1 = bottom, r2 = top, center = true)",
718 "cylinder(h = height, d = diameter, center = true)",
719 "cylinder(h = height, d1 = bottom, d2 = top, center = true)",
720 });
721
722 Builtins::init("polyhedron", new PrimitiveModule(primitive_type_e::POLYHEDRON),
723 {
724 "polyhedron(points, faces, convexity)",
725 });
726
727 Builtins::init("square", new PrimitiveModule(primitive_type_e::SQUARE),
728 {
729 "square(size, center = true)",
730 "square([width,height], center = true)",
731 });
732
733 Builtins::init("circle", new PrimitiveModule(primitive_type_e::CIRCLE),
734 {
735 "circle(radius)",
736 "circle(r = radius)",
737 "circle(d = diameter)",
738 });
739
740 Builtins::init("polygon", new PrimitiveModule(primitive_type_e::POLYGON),
741 {
742 "polygon([points])",
743 "polygon([points], [paths])",
744 });
745 }
746