From 80abdc1da01daa15ccf8de678ed214d63961c736 Mon Sep 17 00:00:00 2001 From: Vitaliy Filippov Date: Wed, 25 Jun 2014 12:55:21 +0400 Subject: [PATCH] Experimental circular/spherical/cylindric bend modifier (issue openscad/openscad#815) --- openscad.pro | 2 + src/GeometryEvaluator.cc | 159 +++++++++++++++++++++++++++++++++++++++ src/GeometryEvaluator.h | 1 + src/bend.cc | 94 +++++++++++++++++++++++ src/bendnode.h | 32 ++++++++ src/builtin.cc | 2 + src/highlighter.cc | 2 +- src/visitor.h | 3 + 8 files changed, 294 insertions(+), 1 deletion(-) create mode 100644 src/bend.cc create mode 100644 src/bendnode.h diff --git a/openscad.pro b/openscad.pro index 08620f74..3f580d97 100644 --- a/openscad.pro +++ b/openscad.pro @@ -254,6 +254,7 @@ HEADERS += src/typedefs.h \ src/context.h \ src/modcontext.h \ src/evalcontext.h \ + src/bendnode.h \ src/csgterm.h \ src/csgtermnormalizer.h \ src/dxfdata.h \ @@ -359,6 +360,7 @@ SOURCES += src/version_check.cc \ src/polyset-gl.cc \ src/csgops.cc \ src/transform.cc \ + src/bend.cc \ src/color.cc \ src/primitives.cc \ src/projection.cc \ diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index 2b9e9567..2f15744e 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -8,6 +8,7 @@ #include "state.h" #include "offsetnode.h" #include "transformnode.h" +#include "bendnode.h" #include "linearextrudenode.h" #include "rotateextrudenode.h" #include "csgnode.h" @@ -532,6 +533,164 @@ Response GeometryEvaluator::visit(State &state, const CsgNode &node) return ContinueTraversal; } +/*! + input: List of 2D or 3D objects (not mixed) + output: Polygon2d or 3D PolySet + operation: + o Union all children + o Perform bend + */ +Response GeometryEvaluator::visit(State &state, const BendNode &node) +{ +#define EPS 1e-3 + if (state.isPrefix() && isSmartCached(node)) return PruneTraversal; + if (state.isPostfix()) { + shared_ptr geom; + if (!isSmartCached(node)) { + double b1[3], m, b2[3], b3[3], ov[3], R0, R, Y, L, a; + // First union all children + ResultObject res = applyToChildren(node, OPENSCAD_UNION); + if ((geom = res.constptr())) { + if (geom->getDimension() == 2) { + // 2D circular bend, ignoring Z coord + + // b1: radius + b1[0] = node.fixed_x-node.center_x; + b1[1] = node.fixed_y-node.center_y; + R0 = sqrt(b1[0]*b1[0] + b1[1]*b1[1]); + if (R0 < EPS) { + PRINT("Error: 2D bend center is equal to fixed point, skipping object."); + return ContinueTraversal; + } + b1[0] = b1[0]/R0; + b1[1] = b1[1]/R0; + + shared_ptr polygons = dynamic_pointer_cast(geom); + assert(polygons); + + // If we got a const object, make a copy + shared_ptr newpoly; + if (res.isConst()) newpoly.reset(new Polygon2d(*polygons)); + else newpoly = dynamic_pointer_cast(res.ptr()); + + BOOST_FOREACH(Outline2d &p, (Polygon2d::Outlines2d&)newpoly->outlines()) { + BOOST_FOREACH(Vector2d &v, p.vertices) { + ov[0] = v[0]-node.center_x; + ov[1] = v[1]-node.center_y; + R = ov[0]*b1[0] + ov[1]*b1[1]; + L = -ov[0]*b1[1] + ov[1]*b1[0]; + a = L/R0; + v[0] = node.center_x + R*b1[0]*cos(a) - R*b1[1]*sin(a); + v[1] = node.center_y + R*b1[1]*cos(a) + R*b1[0]*sin(a); + } + } + + geom = newpoly; + } + else if (geom->getDimension() == 3) { + shared_ptr ps = dynamic_pointer_cast(geom); + if (!ps) { + shared_ptr N = dynamic_pointer_cast(geom); + assert(N); + ps.reset(N->convertToPolyset()); + if (!ps) { + PRINT("ERROR: Failed to convert Nef polyhedron to PolySet; bend only works on PolySets. Is your object non-manifold?\n"); + } + } + if (ps) { + + // If we got a const object, make a copy + shared_ptr newps; + if (res.isConst()) newps.reset(new PolySet(*ps)); + else newps = dynamic_pointer_cast(res.ptr()); + + // b1: vertical radius + b1[0] = node.fixed_x-node.center_x; + b1[1] = node.fixed_y-node.center_y; + b1[2] = node.fixed_z-node.center_z; + R0 = sqrt(b1[0]*b1[0] + b1[1]*b1[1] + b1[2]*b1[2]); + if (R0 < EPS) { + PRINT("Error: 3D bend center is equal to fixed point, skipping object."); + return ContinueTraversal; + } + b1[0] = b1[0]/R0; + b1[1] = b1[1]/R0; + b1[2] = b1[2]/R0; + + // b2: parallel to the axis of bend cylinder + b2[0] = node.cyl_x-node.fixed_x; + b2[1] = node.cyl_y-node.fixed_y; + b2[2] = node.cyl_z-node.fixed_z; + m = b2[0]*b1[0] + b2[1]*b1[1] + b2[2]*b1[2]; + b2[0] = b2[0] - m*b1[0]; + b2[1] = b2[1] - m*b1[1]; + b2[2] = b2[2] - m*b1[2]; + m = sqrt(b2[0]*b2[0] + b2[1]*b2[1] + b2[2]*b2[2]); + + if (m < EPS) { + // center, fixed and cyl are laying on a single straight line => 3D spherical bend + BOOST_FOREACH(PolySet::Polygon &p, newps->polygons) { + BOOST_FOREACH(Vector3d &v, p) { + ov[0] = v[0]-node.center_x; + ov[1] = v[1]-node.center_y; + ov[2] = v[2]-node.center_z; + R = ov[0]*b1[0] + ov[1]*b1[1] + ov[2]*b1[2]; + b2[0] = ov[0]-R*b1[0]; + b2[1] = ov[1]-R*b1[1]; + b2[2] = ov[2]-R*b1[2]; + L = sqrt(b2[0]*b2[0] + b2[1]*b2[1] + b2[2]*b2[2]); + if (L > EPS) { + b2[0] /= L; + b2[1] /= L; + b2[2] /= L; + } + a = L/R0; + v[0] = node.center_x + R*b1[0]*cos(a) + R*b2[0]*sin(a); + v[1] = node.center_y + R*b1[1]*cos(a) + R*b2[1]*sin(a); + v[2] = node.center_z + R*b1[2]*cos(a) + R*b2[2]*sin(a); + } + } + } + else { + b2[0] = b2[0]/m; + b2[1] = b2[1]/m; + b2[2] = b2[2]/m; + // b3: third basis vector - cross product of b1, b2 + b3[0] = b1[1] * b2[2] - b1[2] * b2[1]; + b3[1] = b1[2] * b2[0] - b1[0] * b2[2]; + b3[2] = b1[0] * b2[1] - b1[1] * b2[0]; + + // 3D cylindric bend + BOOST_FOREACH(PolySet::Polygon &p, newps->polygons) { + BOOST_FOREACH(Vector3d &v, p) { + ov[0] = v[0]-node.center_x; + ov[1] = v[1]-node.center_y; + ov[2] = v[2]-node.center_z; + R = ov[0]*b1[0] + ov[1]*b1[1] + ov[2]*b1[2]; + Y = ov[0]*b2[0] + ov[1]*b2[1] + ov[2]*b2[2]; + L = ov[0]*b3[0] + ov[1]*b3[1] + ov[2]*b3[2]; + a = L/R0; + v[0] = node.center_x + Y*b2[0] + R*b1[0]*cos(a) + R*b3[0]*sin(a); + v[1] = node.center_y + Y*b2[1] + R*b1[1]*cos(a) + R*b3[1]*sin(a); + v[2] = node.center_z + Y*b2[2] + R*b1[2]*cos(a) + R*b3[2]*sin(a); + } + } + } + + geom = newps; + } + } + } + } + else { + geom = smartCacheGet(node); + } + addToParent(state, node, geom); + } + return ContinueTraversal; +#undef EPS +} + /*! input: List of 2D or 3D objects (not mixed) output: Polygon2d or 3D PolySet diff --git a/src/GeometryEvaluator.h b/src/GeometryEvaluator.h index dd4f2131..9b16409f 100644 --- a/src/GeometryEvaluator.h +++ b/src/GeometryEvaluator.h @@ -21,6 +21,7 @@ public: virtual Response visit(State &state, const AbstractNode &node); virtual Response visit(State &state, const AbstractIntersectionNode &node); virtual Response visit(State &state, const AbstractPolyNode &node); + virtual Response visit(State &state, const BendNode &node); virtual Response visit(State &state, const LinearExtrudeNode &node); virtual Response visit(State &state, const RotateExtrudeNode &node); virtual Response visit(State &state, const LeafNode &node); diff --git a/src/bend.cc b/src/bend.cc new file mode 100644 index 00000000..4d0f5b63 --- /dev/null +++ b/src/bend.cc @@ -0,0 +1,94 @@ +/* + * OpenSCAD (www.openscad.org) + * Copyright (C) 2009-2011 Clifford Wolf and + * Marius Kintel + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * As a special exception, you have permission to link this program + * with the CGAL library and distribute executables, as long as you + * follow the requirements of the GNU GPL in regard to all of the + * software in the executable aside from CGAL. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "bendnode.h" + +#include "module.h" +#include "evalcontext.h" +#include "printutils.h" +#include "fileutils.h" +#include "builtin.h" +#include "calc.h" +#include "polyset.h" +#include "mathc99.h" + +#include +#include +using namespace boost::assign; // bring 'operator+=()' into scope + +class BendModule : public AbstractModule +{ +public: + BendModule() { } + virtual AbstractNode *instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const; +}; + +AbstractNode *BendModule::instantiate(const Context *ctx, const ModuleInstantiation *inst, const EvalContext *evalctx) const +{ + BendNode *node = new BendNode(inst); + + AssignmentList args; + args += Assignment("center"), Assignment("fixed"), Assignment("cyl"); + + Context c(ctx); + c.setVariables(args, evalctx); + + Value convexity = c.lookup_variable("convexity", true); + Value center = c.lookup_variable("center"); + Value fixed = c.lookup_variable("fixed"); + Value cyl = c.lookup_variable("cyl"); + + node->convexity = (int)convexity.toDouble(); + center.getVec3(node->center_x, node->center_y, node->center_z); + fixed.getVec3(node->fixed_x, node->fixed_y, node->fixed_z); + cyl.getVec3(node->cyl_x, node->cyl_y, node->cyl_z); + + if (node->convexity <= 0) + node->convexity = 1; + + std::vector instantiatednodes = inst->instantiateChildren(evalctx); + node->children.insert(node->children.end(), instantiatednodes.begin(), instantiatednodes.end()); + + return node; +} + +std::string BendNode::toString() const +{ + std::stringstream stream; + + stream << this->name() + << "(center = [ " << center_x << ", " << center_y << ", " << center_z << " ]" + << ", fixed = [ " << fixed_x << ", " << fixed_y << ", " << fixed_z << " ]" + << ", cyl = [ " << cyl_x << ", " << cyl_y << ", " << cyl_z << " ]" + << ", convexity = " << this->convexity << ")"; + + return stream.str(); +} + +void register_builtin_bend() +{ + Builtins::init("bend", new BendModule()); +} diff --git a/src/bendnode.h b/src/bendnode.h new file mode 100644 index 00000000..629755fb --- /dev/null +++ b/src/bendnode.h @@ -0,0 +1,32 @@ +#pragma once + +#include "node.h" +#include "visitor.h" +#include "value.h" + +class BendNode: public AbstractPolyNode +{ +public: + BendNode(const ModuleInstantiation *mi): AbstractPolyNode(mi) { + convexity = 0; + center_x = center_y = center_z = 0; + fixed_x = fixed_y = fixed_z = 0; + is3d = false; + } + virtual Response accept(class State &state, Visitor &visitor) const { + return visitor.visit(state, *this); + } + virtual std::string toString() const; + virtual std::string name() const { return "bend"; } + + int convexity; + bool is3d; + // center is the center of bend circle/sphere + double center_x, center_y, center_z; + // fixed point, i.e. point that doesn't move after transformation + double fixed_x, fixed_y, fixed_z; + // this point specifies the orientation of bend cylinder. + // bend will be spherical if all 3 points (center, fixed and cyl) + // are laying on a single straight line + double cyl_x, cyl_y, cyl_z; +}; diff --git a/src/builtin.cc b/src/builtin.cc index e495f4a5..ac4e3c88 100644 --- a/src/builtin.cc +++ b/src/builtin.cc @@ -35,6 +35,7 @@ void Builtins::init(const char *name, class AbstractFunction *function) } extern void register_builtin_functions(); +extern void register_builtin_bend(); extern void register_builtin_csgops(); extern void register_builtin_transform(); extern void register_builtin_color(); @@ -76,6 +77,7 @@ void Builtins::initialize() register_builtin_dxf_linear_extrude(); register_builtin_dxf_rotate_extrude(); register_builtin_text(); + register_builtin_bend(); this->deprecations["dxf_linear_extrude"] = "linear_extrude()"; this->deprecations["dxf_rotate_extrude"] = "rotate_extrude()"; diff --git a/src/highlighter.cc b/src/highlighter.cc index 2db4b3da..cbbc8b6e 100644 --- a/src/highlighter.cc +++ b/src/highlighter.cc @@ -220,7 +220,7 @@ Highlighter::Highlighter(QTextDocument *parent) tokentypes["operator"] << "=" << "!" << "&&" << "||" << "+" << "-" << "*" << "/" << "%" << "!" << "#" << ";"; tokentypes["math"] << "abs" << "sign" << "acos" << "asin" << "atan" << "atan2" << "sin" << "cos" << "floor" << "round" << "ceil" << "ln" << "log" << "lookup" << "min" << "max" << "pow" << "sqrt" << "exp" << "rands"; tokentypes["keyword"] << "module" << "function" << "for" << "intersection_for" << "if" << "assign" << "echo"<< "search" << "str" << "let"; - tokentypes["transform"] << "scale" << "translate" << "rotate" << "multmatrix" << "color" << "projection" << "hull" << "resize" << "mirror" << "minkowski"; + tokentypes["transform"] << "bend" << "scale" << "translate" << "rotate" << "multmatrix" << "color" << "projection" << "hull" << "resize" << "mirror" << "minkowski"; tokentypes["csgop"] << "union" << "intersection" << "difference" << "render"; tokentypes["prim3d"] << "cube" << "cylinder" << "sphere" << "polyhedron"; tokentypes["prim2d"] << "square" << "polygon" << "circle"; diff --git a/src/visitor.h b/src/visitor.h index 523d4dce..440f471b 100644 --- a/src/visitor.h +++ b/src/visitor.h @@ -18,6 +18,9 @@ public: virtual Response visit(class State &state, const class LeafNode &node) { return visit(state, (const class AbstractPolyNode &)node); } + virtual Response visit(class State &state, const class BendNode &node) { + return visit(state, (const class AbstractPolyNode &)node); + } virtual Response visit(class State &state, const class CgaladvNode &node) { return visit(state, (const class AbstractNode &)node); }