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 &center = 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 &center = 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 &center = 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