From 87f73263fcbd6549033fa8722c694d8be2bd6f9b Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Thu, 21 Nov 2013 01:25:15 -0500 Subject: [PATCH] Made CGAL_Nef_polyhedron a Geometry subtype, implemented 3D transforms, implemented projection, implemented render --- src/CGALCache.cc | 8 +- src/CGALEvaluator.cc | 22 +- src/CGALRenderer.cc | 8 +- src/CGAL_Nef_polyhedron.cc | 2 +- src/CGAL_Nef_polyhedron.h | 14 +- src/CGAL_Nef_polyhedron_DxfData.cc | 86 ++++-- src/GeometryEvaluator.cc | 431 +++++++++++++++++++++++++---- src/GeometryEvaluator.h | 4 + src/PolySetCGALEvaluator.cc | 6 +- src/cgalutils.cc | 23 +- src/cgalutils.h | 2 +- src/mainwin.cc | 8 +- src/openscad.cc | 6 +- 13 files changed, 489 insertions(+), 131 deletions(-) diff --git a/src/CGALCache.cc b/src/CGALCache.cc index 1fc864ab..75d83c09 100644 --- a/src/CGALCache.cc +++ b/src/CGALCache.cc @@ -12,17 +12,17 @@ const CGAL_Nef_polyhedron &CGALCache::get(const std::string &id) const { const CGAL_Nef_polyhedron &N = *this->cache[id]; #ifdef DEBUG - PRINTB("CGAL Cache hit: %s (%d bytes)", id.substr(0, 40) % N.weight()); + PRINTB("CGAL Cache hit: %s (%d bytes)", id.substr(0, 40) % N.memsize()); #endif return N; } bool CGALCache::insert(const std::string &id, const CGAL_Nef_polyhedron &N) { - bool inserted = this->cache.insert(id, new CGAL_Nef_polyhedron(N), N.weight()); + bool inserted = this->cache.insert(id, new CGAL_Nef_polyhedron(N), N.memsize()); #ifdef DEBUG - if (inserted) PRINTB("CGAL Cache insert: %s (%d bytes)", id.substr(0, 40) % N.weight()); - else PRINTB("CGAL Cache insert failed: %s (%d bytes)", id.substr(0, 40) % N.weight()); + if (inserted) PRINTB("CGAL Cache insert: %s (%d bytes)", id.substr(0, 40) % N.memsize()); + else PRINTB("CGAL Cache insert failed: %s (%d bytes)", id.substr(0, 40) % N.memsize()); #endif return inserted; } diff --git a/src/CGALEvaluator.cc b/src/CGALEvaluator.cc index 8ca203a2..688a158f 100644 --- a/src/CGALEvaluator.cc +++ b/src/CGALEvaluator.cc @@ -61,12 +61,12 @@ bool CGALEvaluator::isCached(const AbstractNode &node) const */ void CGALEvaluator::process(CGAL_Nef_polyhedron &target, const CGAL_Nef_polyhedron &src, OpenSCADOperator op) { - if (target.dim != 2 && target.dim != 3) { + if (target.getDimension() != 2 && target.getDimension() != 3) { assert(false && "Dimension of Nef polyhedron must be 2 or 3"); } if (src.isEmpty()) return; // Empty polyhedron. This can happen for e.g. square([0,0]) if (target.isEmpty() && op != OPENSCAD_UNION) return; // empty op => empty - if (target.dim != src.dim) return; // If someone tries to e.g. union 2d and 3d objects + if (target.getDimension() != src.getDimension()) return; // If someone tries to e.g. union 2d and 3d objects CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); try { @@ -136,11 +136,11 @@ CGAL_Nef_polyhedron CGALEvaluator::applyHull(const CgaladvNode &node) const CGAL_Nef_polyhedron &chN = item.second; // FIXME: Don't use deep access to modinst members if (chnode->modinst->isBackground()) continue; - if (chN.dim == 0) continue; // Ignore object with dimension 0 (e.g. echo) + if (chN.getDimension() == 0) continue; // Ignore object with dimension 0 (e.g. echo) if (dim == 0) { - dim = chN.dim; + dim = chN.getDimension(); } - else if (dim != chN.dim) { + else if (dim != chN.getDimension()) { PRINT("WARNING: hull() does not support mixing 2D and 3D objects."); continue; } @@ -214,7 +214,7 @@ CGAL_Nef_polyhedron CGALEvaluator::applyResize(const CgaladvNode &node) CGAL_Iso_cuboid_3 bb; - if ( N.dim == 2 ) { + if ( N.getDimension() == 2 ) { CGAL_Iso_rectangle_2e bbox = bounding_box( *N.p2 ); CGAL_Point_2e min2(bbox.min()), max2(bbox.max()); CGAL_Point_3 min3(CGAL::to_double(min2.x()), CGAL::to_double(min2.y()), 0), @@ -231,7 +231,7 @@ CGAL_Nef_polyhedron CGALEvaluator::applyResize(const CgaladvNode &node) bbox_size.push_back( bb.ymax()-bb.ymin() ); bbox_size.push_back( bb.zmax()-bb.zmin() ); int newsizemax_index = 0; - for (int i=0;i geom = this->geomevaluator.evaluateGeometry(node); - if (geom) N = createNefPolyhedronFromGeometry(*geom); + if (geom) { + CGAL_Nef_polyhedron *Nptr = createNefPolyhedronFromGeometry(*geom); + N = *Nptr; + delete Nptr; + } node.progress_report(); } else { diff --git a/src/CGALRenderer.cc b/src/CGALRenderer.cc index 0a75266f..a85ed496 100644 --- a/src/CGALRenderer.cc +++ b/src/CGALRenderer.cc @@ -47,7 +47,7 @@ CGALRenderer::CGALRenderer(const CGAL_Nef_polyhedron &root) : root(root) this->polyhedron = NULL; this->polyset = NULL; } - else if (root.dim == 2) { + else if (root.getDimension() == 2) { DxfData *dd = root.convertToDxfData(); this->polyhedron = NULL; this->polyset = new PolySet(); @@ -55,7 +55,7 @@ CGALRenderer::CGALRenderer(const CGAL_Nef_polyhedron &root) : root(root) dxf_tesselate(this->polyset, *dd, 0, Vector2d(1,1), true, false, 0); delete dd; } - else if (root.dim == 3) { + else if (root.getDimension() == 3) { this->polyset = NULL; this->polyhedron = new Polyhedron(); // FIXME: Make independent of Preferences @@ -82,7 +82,7 @@ CGALRenderer::~CGALRenderer() void CGALRenderer::draw(bool showfaces, bool showedges) const { if (this->root.isNull()) return; - if (this->root.dim == 2) { + if (this->root.getDimension() == 2) { // Draw 2D polygons glDisable(GL_LIGHTING); // FIXME: const QColor &col = Preferences::inst()->color(Preferences::CGAL_FACE_2D_COLOR); @@ -135,7 +135,7 @@ void CGALRenderer::draw(bool showfaces, bool showedges) const glEnable(GL_DEPTH_TEST); } - else if (this->root.dim == 3) { + else if (this->root.getDimension() == 3) { if (showfaces) this->polyhedron->set_style(SNC_BOUNDARY); else this->polyhedron->set_style(SNC_SKELETON); diff --git a/src/CGAL_Nef_polyhedron.cc b/src/CGAL_Nef_polyhedron.cc index ea9accdc..6d6f62c2 100644 --- a/src/CGAL_Nef_polyhedron.cc +++ b/src/CGAL_Nef_polyhedron.cc @@ -59,7 +59,7 @@ CGAL_Nef_polyhedron &CGAL_Nef_polyhedron::minkowski(const CGAL_Nef_polyhedron &o return *this; } -int CGAL_Nef_polyhedron::weight() const +size_t CGAL_Nef_polyhedron::memsize() const { if (this->isNull()) return 0; diff --git a/src/CGAL_Nef_polyhedron.h b/src/CGAL_Nef_polyhedron.h index 7f59861e..fcfa51ca 100644 --- a/src/CGAL_Nef_polyhedron.h +++ b/src/CGAL_Nef_polyhedron.h @@ -1,12 +1,13 @@ #ifndef CGAL_NEF_POLYHEDRON_H_ #define CGAL_NEF_POLYHEDRON_H_ +#include "Geometry.h" #include "cgal.h" #include "memory.h" #include #include "linalg.h" -class CGAL_Nef_polyhedron +class CGAL_Nef_polyhedron : public Geometry { public: CGAL_Nef_polyhedron(int dim = 0) : dim(dim) {} @@ -14,6 +15,11 @@ public: CGAL_Nef_polyhedron(CGAL_Nef_polyhedron3 *p); ~CGAL_Nef_polyhedron() {} + virtual size_t memsize() const; + virtual BoundingBox getBoundingBox() const {}; // FIXME: Implement + virtual std::string dump() const; + virtual unsigned int getDimension() const { return this->dim; } + // Empty means it is a geometric node which has zero area/volume bool isEmpty() const { return (dim > 0 && !p2 && !p3); } // Null means the node doesn't contain any geometry (for whatever reason) @@ -24,14 +30,14 @@ public: CGAL_Nef_polyhedron &operator-=(const CGAL_Nef_polyhedron &other); CGAL_Nef_polyhedron &minkowski(const CGAL_Nef_polyhedron &other); CGAL_Nef_polyhedron copy() const; - std::string dump() const; - int weight() const; class PolySet *convertToPolyset(); class DxfData *convertToDxfData() const; + class Polygon2d *convertToPolygon2d() const; void transform( const Transform3d &matrix ); - int dim; shared_ptr p2; shared_ptr p3; +protected: + int dim; }; #endif diff --git a/src/CGAL_Nef_polyhedron_DxfData.cc b/src/CGAL_Nef_polyhedron_DxfData.cc index 27856420..3b700d95 100644 --- a/src/CGAL_Nef_polyhedron_DxfData.cc +++ b/src/CGAL_Nef_polyhedron_DxfData.cc @@ -25,6 +25,7 @@ */ #include "dxfdata.h" +#include "Polygon2d.h" #include "grid.h" #include "CGAL_Nef_polyhedron.h" #include "cgal.h" @@ -38,49 +39,74 @@ #ifdef ENABLE_CGAL DxfData *CGAL_Nef_polyhedron::convertToDxfData() const +{ + assert(this->dim == 2); + DxfData *dxfdata = new DxfData(); + Grid2d grid(GRID_COARSE); + + typedef CGAL_Nef_polyhedron2::Explorer Explorer; + typedef Explorer::Face_const_iterator fci_t; + typedef Explorer::Halfedge_around_face_const_circulator heafcc_t; + Explorer E = this->p2->explorer(); + + for (fci_t fit = E.faces_begin(), facesend = E.faces_end(); fit != facesend; ++fit) + { + heafcc_t fcirc(E.halfedge(fit)), fend(fcirc); + int first_point = -1, last_point = -1; + CGAL_For_all(fcirc, fend) { + if (E.is_standard(E.target(fcirc))) { + Explorer::Point ep = E.point(E.target(fcirc)); + double x = to_double(ep.x()), y = to_double(ep.y()); + int this_point = -1; + if (grid.has(x, y)) { + this_point = grid.align(x, y); + } else { + this_point = grid.align(x, y) = dxfdata->points.size(); + dxfdata->points.push_back(Vector2d(x, y)); + } + if (first_point < 0) { + dxfdata->paths.push_back(DxfData::Path()); + first_point = this_point; + } + if (this_point != last_point) { + dxfdata->paths.back().indices.push_back(this_point); + last_point = this_point; + } + } + } + if (first_point >= 0) { + dxfdata->paths.back().is_closed = 1; + dxfdata->paths.back().indices.push_back(first_point); + } + } + + dxfdata->fixup_path_direction(); + return dxfdata; +} + +Polygon2d *CGAL_Nef_polyhedron::convertToPolygon2d() const { assert(this->dim == 2); - DxfData *dxfdata = new DxfData(); - Grid2d grid(GRID_COARSE); + Polygon2d *poly = new Polygon2d; typedef CGAL_Nef_polyhedron2::Explorer Explorer; typedef Explorer::Face_const_iterator fci_t; typedef Explorer::Halfedge_around_face_const_circulator heafcc_t; Explorer E = this->p2->explorer(); - for (fci_t fit = E.faces_begin(), facesend = E.faces_end(); fit != facesend; ++fit) - { + for (fci_t fit = E.faces_begin(), facesend = E.faces_end(); fit != facesend; ++fit) { heafcc_t fcirc(E.halfedge(fit)), fend(fcirc); - int first_point = -1, last_point = -1; + Outline2d outline; CGAL_For_all(fcirc, fend) { if (E.is_standard(E.target(fcirc))) { Explorer::Point ep = E.point(E.target(fcirc)); - double x = to_double(ep.x()), y = to_double(ep.y()); - int this_point = -1; - if (grid.has(x, y)) { - this_point = grid.align(x, y); - } else { - this_point = grid.align(x, y) = dxfdata->points.size(); - dxfdata->points.push_back(Vector2d(x, y)); - } - if (first_point < 0) { - dxfdata->paths.push_back(DxfData::Path()); - first_point = this_point; - } - if (this_point != last_point) { - dxfdata->paths.back().indices.push_back(this_point); - last_point = this_point; - } + outline.push_back(Vector2d(to_double(ep.x()), + to_double(ep.y()))); } } - if (first_point >= 0) { - dxfdata->paths.back().is_closed = 1; - dxfdata->paths.back().indices.push_back(first_point); - } + if (outline.size() > 0) poly->addOutline(outline); } - - dxfdata->fixup_path_direction(); - return dxfdata; + return poly; } std::string CGAL_Nef_polyhedron::dump() const @@ -125,7 +151,9 @@ void CGAL_Nef_polyhedron::transform( const Transform3d &matrix ) ps.is2d = true; dxf_tesselate(&ps, *dd, 0, Vector2d(1,1), true, false, 0); - CGAL_Nef_polyhedron N = createNefPolyhedronFromGeometry(ps); + CGAL_Nef_polyhedron *Nptr = createNefPolyhedronFromGeometry(ps); + CGAL_Nef_polyhedron N = *Nptr; + delete Nptr; if (N.p2) this->p2.reset(new CGAL_Nef_polyhedron2(*N.p2)); delete dd; } diff --git a/src/GeometryEvaluator.cc b/src/GeometryEvaluator.cc index 6452bc2c..53697c36 100644 --- a/src/GeometryEvaluator.cc +++ b/src/GeometryEvaluator.cc @@ -10,11 +10,18 @@ #include "rotateextrudenode.h" #include "csgnode.h" #include "cgaladvnode.h" +#include "projectionnode.h" +#include "CGAL_Nef_polyhedron.h" +#include "cgalutils.h" +#include +#include "rendernode.h" #include "clipper-utils.h" #include "CGALEvaluator.h" #include "CGALCache.h" #include "PolySet.h" #include "openscad.h" // get_fragments_from_r() +#include "printutils.h" +#include "svg.h" #include @@ -66,37 +73,136 @@ shared_ptr GeometryEvaluator::evaluateGeometry(const AbstractNod return GeometryCache::instance()->get(this->tree.getIdString(node)); } -/*! - -*/ -Geometry *GeometryEvaluator::applyToChildren2D(const AbstractNode &node, OpenSCADOperator op) +Geometry *GeometryEvaluator::applyToChildren(const AbstractNode &node, OpenSCADOperator op) { - // FIXME: Support other operators than UNION - - Polygon2d sum; + unsigned int dim = 0; + BOOST_FOREACH(const ChildItem &item, this->visitedchildren[node.index()]) { + if (item.second) { + if (!dim) dim = item.second->getDimension(); + else if (dim != item.second->getDimension()) { + return NULL; + } + } + } + if (dim == 2) return applyToChildren2D(node, op); + else if (dim == 3) return applyToChildren3D(node, op); + return NULL; +} + +/*! + Modifies target by applying op to target and src: + target = target [op] src + */ +static void process(CGAL_Nef_polyhedron &target, const CGAL_Nef_polyhedron &src, OpenSCADOperator op) +{ + if (target.getDimension() != 2 && target.getDimension() != 3) { + assert(false && "Dimension of Nef polyhedron must be 2 or 3"); + } + if (src.isEmpty()) return; // Empty polyhedron. This can happen for e.g. square([0,0]) + if (target.isEmpty() && op != OPENSCAD_UNION) return; // empty op => empty + if (target.getDimension() != src.getDimension()) return; // If someone tries to e.g. union 2d and 3d objects + + CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); + try { + switch (op) { + case OPENSCAD_UNION: + if (target.isEmpty()) target = src.copy(); + else target += src; + break; + case OPENSCAD_INTERSECTION: + target *= src; + break; + case OPENSCAD_DIFFERENCE: + target -= src; + break; + case OPENSCAD_MINKOWSKI: + target.minkowski(src); + break; + } + } + catch (const CGAL::Failure_exception &e) { + // union && difference assert triggered by testdata/scad/bugs/rotate-diff-nonmanifold-crash.scad and testdata/scad/bugs/issue204.scad + std::string opstr = op == OPENSCAD_UNION ? "union" : op == OPENSCAD_INTERSECTION ? "intersection" : op == OPENSCAD_DIFFERENCE ? "difference" : op == OPENSCAD_MINKOWSKI ? "minkowski" : "UNKNOWN"; + PRINTB("CGAL error in CGAL_Nef_polyhedron's %s operator: %s", opstr % e.what()); + + // Errors can result in corrupt polyhedrons, so put back the old one + target = src; + } + CGAL::set_error_behaviour(old_behaviour); +} + +Geometry *GeometryEvaluator::applyToChildren3D(const AbstractNode &node, OpenSCADOperator op) +{ + CGAL_Nef_polyhedron *N = new CGAL_Nef_polyhedron; BOOST_FOREACH(const ChildItem &item, this->visitedchildren[node.index()]) { const AbstractNode *chnode = item.first; const shared_ptr &chgeom = item.second; // FIXME: Don't use deep access to modinst members if (chnode->modinst->isBackground()) continue; + + shared_ptr chN = dynamic_pointer_cast(chgeom); + if (!chN) { + shared_ptr chP = dynamic_pointer_cast(chgeom); + if (chP) chN.reset(createNefPolyhedronFromGeometry(*chP)); + } // NB! We insert into the cache here to ensure that all children of // a node is a valid object. If we inserted as we created them, the // cache could have been modified before we reach this point due to a large // sibling object. if (!isCached(*chnode)) { - GeometryCache::instance()->insert(this->tree.getIdString(*chnode), chgeom); + GeometryCache::instance()->insert(this->tree.getIdString(*chnode), chN); } - if (chgeom->getDimension() == 2) { - shared_ptr polygons = dynamic_pointer_cast(chgeom); - assert(polygons); - BOOST_FOREACH(const Outline2d &o, polygons->outlines()) { - sum.addOutline(o); + if (chgeom) { + if (chgeom->getDimension() == 3) { + // Initialize N on first iteration with first expected geometric object + if (N->isNull() && !N->isEmpty()) *N = chN->copy(); + else process(*N, *chN, op); + } + else { + // FIXME: Fix error message + PRINT("ERROR: this operation is not defined for 2D child objects!"); } } - else { - PRINT("ERROR: linear_extrude() is not defined for 3D child objects!"); + chnode->progress_report(); + } + return N; +} + + +/*! + +*/ +Geometry *GeometryEvaluator::applyToChildren2D(const AbstractNode &node, OpenSCADOperator op) +{ + Polygon2d sum; + BOOST_FOREACH(const ChildItem &item, this->visitedchildren[node.index()]) { + const AbstractNode *chnode = item.first; + const shared_ptr &chgeom = item.second; + // FIXME: Don't use deep access to modinst members + if (chnode->modinst->isBackground()) continue; + + // NB! We insert into the cache here to ensure that all children of + // a node is a valid object. If we inserted as we created them, the + // cache could have been modified before we reach this point due to a large + // sibling object. + if (!isCached(*chnode)) { + GeometryCache::instance()->insert(this->tree.getIdString(*chnode), chgeom); + } + + if (chgeom) { + if (chgeom->getDimension() == 2) { + shared_ptr polygons = dynamic_pointer_cast(chgeom); + assert(polygons); + BOOST_FOREACH(const Outline2d &o, polygons->outlines()) { + sum.addOutline(o); + } + } + else { + // FIXME: Wrong error message + PRINT("ERROR: linear_extrude() is not defined for 3D child objects!"); + } } chnode->progress_report(); } @@ -137,6 +243,8 @@ Geometry *GeometryEvaluator::applyToChildren2D(const AbstractNode &node, OpenSCA Usually, this should be called from the postfix stage, but for some nodes, we defer traversal letting other components (e.g. CGAL) render the subgraph, and we'll then call this from prefix and prune further traversal. + + The added geometry can be NULL if it wasn't possible to evaluate it. */ void GeometryEvaluator::addToParent(const State &state, const AbstractNode &node, @@ -166,16 +274,13 @@ Response GeometryEvaluator::visit(State &state, const AbstractNode &node) if (state.isPostfix()) { shared_ptr geom; if (!isCached(node)) { - const Geometry *geometry = applyToChildren2D(node, OPENSCAD_UNION); - const Polygon2d *polygons = dynamic_cast(geometry); - assert(polygons); + const Geometry *geometry = applyToChildren(node, OPENSCAD_UNION); geom.reset(geometry); } else { geom = GeometryCache::instance()->get(this->tree.getIdString(node)); } addToParent(state, node, geom); - // FIXME: if 3d node, CGAL? } return ContinueTraversal; } @@ -205,6 +310,9 @@ Response GeometryEvaluator::visit(State &state, const AbstractNode &node) /*! Leaf nodes can create their own geometry, so let them do that + + input: None + output: PolySet or Polygon2d */ Response GeometryEvaluator::visit(State &state, const LeafNode &node) { @@ -219,26 +327,36 @@ Response GeometryEvaluator::visit(State &state, const LeafNode &node) return PruneTraversal; } +/*! + input: List of 2D or 3D objects (not mixed) + output: Polygon2d or 3D PolySet + operation: + o Perform csg op on children + */ Response GeometryEvaluator::visit(State &state, const CsgNode &node) { if (state.isPrefix() && isCached(node)) return PruneTraversal; if (state.isPostfix()) { shared_ptr geom; if (!isCached(node)) { - const Geometry *geometry = applyToChildren2D(node, node.type); - const Polygon2d *polygons = dynamic_cast(geometry); - assert(polygons); + const Geometry *geometry = applyToChildren(node, node.type); geom.reset(geometry); } else { geom = GeometryCache::instance()->get(this->tree.getIdString(node)); } addToParent(state, node, geom); - // FIXME: if 3d node, CGAL? } return ContinueTraversal; } +/*! + input: List of 2D or 3D objects (not mixed) + output: Polygon2d or 3D PolySet + operation: + o Union all children + o Perform transform + */ Response GeometryEvaluator::visit(State &state, const TransformNode &node) { if (state.isPrefix() && isCached(node)) return PruneTraversal; @@ -251,20 +369,25 @@ Response GeometryEvaluator::visit(State &state, const TransformNode &node) } else { // First union all children - Geometry *geometry = applyToChildren2D(node, OPENSCAD_UNION); - Polygon2d *polygons = dynamic_cast(geometry); - //FIXME: Handle 2D vs. 3D - if (polygons) { - Transform2d mat2; - mat2.matrix() << - node.matrix(0,0), node.matrix(0,1), node.matrix(0,3), - node.matrix(1,0), node.matrix(1,1), node.matrix(1,3), - node.matrix(3,0), node.matrix(3,1), node.matrix(3,3); - polygons->transform(mat2); - geom.reset(polygons); - } - else { - // FIXME: Handle 3D transform + Geometry *geometry = applyToChildren(node, OPENSCAD_UNION); + if (geometry) { + if (geometry->getDimension() == 2) { + Polygon2d *polygons = dynamic_cast(geometry); + assert(polygons); + Transform2d mat2; + mat2.matrix() << + node.matrix(0,0), node.matrix(0,1), node.matrix(0,3), + node.matrix(1,0), node.matrix(1,1), node.matrix(1,3), + node.matrix(3,0), node.matrix(3,1), node.matrix(3,3); + polygons->transform(mat2); + geom.reset(polygons); + } + else if (geometry->getDimension() == 3) { + CGAL_Nef_polyhedron *N = dynamic_cast(geometry); + assert(N); + N->transform(node.matrix); + geom.reset(N); + } } } } @@ -382,6 +505,13 @@ static Geometry *extrudePolygon(const LinearExtrudeNode &node, const Polygon2d & return ps; } +/*! + input: List of 2D objects + output: 3D PolySet + operation: + o Union all children + o Perform extrude + */ Response GeometryEvaluator::visit(State &state, const LinearExtrudeNode &node) { if (state.isPrefix() && isCached(node)) return PruneTraversal; @@ -451,6 +581,13 @@ static Geometry *rotatePolygon(const RotateExtrudeNode &node, const Polygon2d &p return ps; } +/*! + input: List of 2D objects + output: 3D PolySet + operation: + o Union all children + o Perform extrude + */ Response GeometryEvaluator::visit(State &state, const RotateExtrudeNode &node) { if (state.isPrefix() && isCached(node)) return PruneTraversal; @@ -474,31 +611,178 @@ Response GeometryEvaluator::visit(State &state, const RotateExtrudeNode &node) } /*! - Handles non-leaf PolyNodes; extrusions, projection + Handles non-leaf PolyNodes; projection */ Response GeometryEvaluator::visit(State &state, const AbstractPolyNode &node) { - assert(false && "Implement"); - if (state.isPrefix() && isCached(node)) return PruneTraversal; - if (state.isPrefix()) { - /* - shared_ptr t1; - if (this->psevaluator) { - shared_ptr geom = this->psevaluator->getGeometry(node, true); - if (geom) { - t1 = evaluate_csg_term_from_geometry(state, this->highlights, this->background, - geom, node.modinst, node); - node.progress_report(); - } - } - this->stored_term[node.index()] = t1; - addToParent(state, node); - */ - return PruneTraversal; - } - return ContinueTraversal; + assert(false); } +static CGAL_Nef_polyhedron project_node(const ProjectionNode &node, + const CGAL_Nef_polyhedron &N) +{ + CGAL_Nef_polyhedron &inputN = const_cast(N); + + logstream log(5); + CGAL_Nef_polyhedron nef_poly(2); + if (inputN.getDimension() != 3) return nef_poly; + + CGAL_Nef_polyhedron newN; + if (node.cut_mode) { + CGAL::Failure_behaviour old_behaviour = CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); + try { + CGAL_Nef_polyhedron3::Plane_3 xy_plane = CGAL_Nef_polyhedron3::Plane_3(0,0,1,0); + newN.p3.reset(new CGAL_Nef_polyhedron3(inputN.p3->intersection(xy_plane, CGAL_Nef_polyhedron3::PLANE_ONLY))); + } + catch (const CGAL::Failure_exception &e) { + PRINTB("CGAL error in projection node during plane intersection: %s", e.what()); + try { + PRINT("Trying alternative intersection using very large thin box: "); + std::vector pts; + // dont use z of 0. there are bugs in CGAL. + double inf = 1e8; + double eps = 0.001; + CGAL_Point_3 minpt( -inf, -inf, -eps ); + CGAL_Point_3 maxpt( inf, inf, eps ); + CGAL_Iso_cuboid_3 bigcuboid( minpt, maxpt ); + for ( int i=0;i<8;i++ ) pts.push_back( bigcuboid.vertex(i) ); + CGAL_Polyhedron bigbox; + CGAL::convex_hull_3(pts.begin(), pts.end(), bigbox); + CGAL_Nef_polyhedron3 nef_bigbox( bigbox ); + newN.p3.reset(new CGAL_Nef_polyhedron3(nef_bigbox.intersection(*inputN.p3))); + } + catch (const CGAL::Failure_exception &e) { + PRINTB("CGAL error in projection node during bigbox intersection: %s", e.what()); + } + } + + if (!newN.p3 || newN.p3->is_empty()) { + CGAL::set_error_behaviour(old_behaviour); + PRINT("WARNING: projection() failed."); + return nef_poly; + } + + log << OpenSCAD::svg_header( 480, 100000 ) << "\n"; + try { + ZRemover zremover; + CGAL_Nef_polyhedron3::Volume_const_iterator i; + CGAL_Nef_polyhedron3::Shell_entry_const_iterator j; + CGAL_Nef_polyhedron3::SFace_const_handle sface_handle; + for ( i = newN.p3->volumes_begin(); i != newN.p3->volumes_end(); ++i ) { + log << "\n"; + for ( j = i->shells_begin(); j != i->shells_end(); ++j ) { + log << "\n"; + sface_handle = CGAL_Nef_polyhedron3::SFace_const_handle( j ); + newN.p3->visit_shell_objects( sface_handle , zremover ); + log << "\n"; + } + log << "\n"; + } + nef_poly.p2 = zremover.output_nefpoly2d; + } catch (const CGAL::Failure_exception &e) { + PRINTB("CGAL error in projection node while flattening: %s", e.what()); + } + log << "\n"; + + CGAL::set_error_behaviour(old_behaviour); + } + // In projection mode all the triangles are projected manually into the XY plane + else { + PolySet *ps3 = inputN.convertToPolyset(); + if (!ps3) return nef_poly; + for (size_t i = 0; i < ps3->polygons.size(); i++) { + int min_x_p = -1; + double min_x_val = 0; + for (size_t j = 0; j < ps3->polygons[i].size(); j++) { + double x = ps3->polygons[i][j][0]; + if (min_x_p < 0 || x < min_x_val) { + min_x_p = j; + min_x_val = x; + } + } + int min_x_p1 = (min_x_p+1) % ps3->polygons[i].size(); + int min_x_p2 = (min_x_p+ps3->polygons[i].size()-1) % ps3->polygons[i].size(); + double ax = ps3->polygons[i][min_x_p1][0] - ps3->polygons[i][min_x_p][0]; + double ay = ps3->polygons[i][min_x_p1][1] - ps3->polygons[i][min_x_p][1]; + double at = atan2(ay, ax); + double bx = ps3->polygons[i][min_x_p2][0] - ps3->polygons[i][min_x_p][0]; + double by = ps3->polygons[i][min_x_p2][1] - ps3->polygons[i][min_x_p][1]; + double bt = atan2(by, bx); + + double eps = 0.000001; + if (fabs(at - bt) < eps || (fabs(ax) < eps && fabs(ay) < eps) || + (fabs(bx) < eps && fabs(by) < eps)) { + // this triangle is degenerated in projection + continue; + } + + std::list plist; + for (size_t j = 0; j < ps3->polygons[i].size(); j++) { + double x = ps3->polygons[i][j][0]; + double y = ps3->polygons[i][j][1]; + CGAL_Nef_polyhedron2::Point p = CGAL_Nef_polyhedron2::Point(x, y); + if (at > bt) + plist.push_front(p); + else + plist.push_back(p); + } + // FIXME: Should the CGAL_Nef_polyhedron2 be cached? + if (nef_poly.isEmpty()) { + nef_poly.p2.reset(new CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED)); + } + else { + (*nef_poly.p2) += CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED); + } + } + delete ps3; + } + return nef_poly; +} + +/*! + input: List of 3D objects + output: Polygon2d + operation: + o Union all children + o Perform projection + */ +Response GeometryEvaluator::visit(State &state, const ProjectionNode &node) +{ + if (state.isPrefix() && isCached(node)) return PruneTraversal; + if (state.isPostfix()) { + shared_ptr geom; + if (!isCached(node)) { + const Geometry *geometry = applyToChildren3D(node, OPENSCAD_UNION); + if (geometry) { + const CGAL_Nef_polyhedron *Nptr = dynamic_cast(geometry); + if (!Nptr) { + // FIXME: delete this object + Nptr = createNefPolyhedronFromGeometry(*geometry); + } + if (!Nptr->isNull()) { + CGAL_Nef_polyhedron nef_poly = project_node(node, *Nptr); + Polygon2d *poly = nef_poly.convertToPolygon2d(); + assert(poly); + poly->setConvexity(node.convexity); + geom.reset(poly); + delete geometry; + } + } + } + else { + geom = GeometryCache::instance()->get(this->tree.getIdString(node)); + } + addToParent(state, node, geom); + } + return ContinueTraversal; +} + +/*! + input: List of 2D or 3D objects (not mixed) + output: PolySet (FIXME: implement Polygon2d) + operation: + o Perform cgal operation + */ Response GeometryEvaluator::visit(State &state, const CgaladvNode &node) { if (state.isPrefix() && isCached(node)) return PruneTraversal; @@ -525,3 +809,36 @@ Response GeometryEvaluator::visit(State &state, const CgaladvNode &node) return ContinueTraversal; } +/*! + input: List of 2D or 3D objects (not mixed) + output: PolySet (FIXME: implement Polygon2d?) + operation: + o Render to PolySet (using CGAL or Clipper) + */ +Response GeometryEvaluator::visit(State &state, const RenderNode &node) +{ + if (state.isPrefix() && isCached(node)) return PruneTraversal; + if (state.isPostfix()) { + shared_ptr geom; + if (!isCached(node)) { + // FIXME: Handle 2D nodes separately + CGAL_Nef_polyhedron N = this->cgalevaluator->evaluateCGALMesh(node); + PolySet *ps = NULL; + if (!N.isNull()) { + if (N.getDimension() == 3 && !N.p3->is_simple()) { + PRINT("WARNING: Body of render() isn't valid 2-manifold!"); + } + else { + ps = N.convertToPolyset(); + if (ps) ps->setConvexity(node.convexity); + } + } + geom.reset(ps); + } + else { + geom = GeometryCache::instance()->get(this->tree.getIdString(node)); + } + addToParent(state, node, geom); + } + return ContinueTraversal; +} diff --git a/src/GeometryEvaluator.h b/src/GeometryEvaluator.h index 39710dcb..ece969ef 100644 --- a/src/GeometryEvaluator.h +++ b/src/GeometryEvaluator.h @@ -26,12 +26,16 @@ public: virtual Response visit(State &state, const TransformNode &node); virtual Response visit(State &state, const CsgNode &node); virtual Response visit(State &state, const CgaladvNode &node); + virtual Response visit(State &state, const RenderNode &node); + virtual Response visit(State &state, const ProjectionNode &node); const Tree &getTree() const { return this->tree; } private: bool isCached(const AbstractNode &node) const; Geometry *applyToChildren2D(const AbstractNode &node, OpenSCADOperator op); + Geometry *applyToChildren3D(const AbstractNode &node, OpenSCADOperator op); + Geometry *applyToChildren(const AbstractNode &node, OpenSCADOperator op); void addToParent(const State &state, const AbstractNode &node, const shared_ptr &geom); typedef std::pair > ChildItem; diff --git a/src/PolySetCGALEvaluator.cc b/src/PolySetCGALEvaluator.cc index 09025308..13cf3ca0 100644 --- a/src/PolySetCGALEvaluator.cc +++ b/src/PolySetCGALEvaluator.cc @@ -41,7 +41,7 @@ Geometry *PolySetCGALEvaluator::evaluateGeometry(const ProjectionNode &node) BOOST_FOREACH (AbstractNode * v, node.getChildren()) { if (v->modinst->isBackground()) continue; CGAL_Nef_polyhedron N = this->cgalevaluator.evaluateCGALMesh(*v); - if (N.dim == 3) { + if (N.getDimension() == 3) { if (sum.isNull()) sum = N.copy(); else sum += N; } @@ -500,7 +500,7 @@ Geometry *PolySetCGALEvaluator::evaluateGeometry(const RotateExtrudeNode &node) if (v->modinst->isBackground()) continue; CGAL_Nef_polyhedron N = this->cgalevaluator.evaluateCGALMesh(*v); if (!N.isNull()) { - if (N.dim != 2) { + if (N.getDimension() != 2) { PRINT("ERROR: rotate_extrude() is not defined for 3D child objects!"); } else { @@ -538,7 +538,7 @@ Geometry *PolySetCGALEvaluator::evaluateGeometry(const RenderNode &node) CGAL_Nef_polyhedron N = this->cgalevaluator.evaluateCGALMesh(node); PolySet *ps = NULL; if (!N.isNull()) { - if (N.dim == 3 && !N.p3->is_simple()) { + if (N.getDimension() == 3 && !N.p3->is_simple()) { PRINT("WARNING: Body of render() isn't valid 2-manifold!"); } else { diff --git a/src/cgalutils.cc b/src/cgalutils.cc index 8bc3628d..4bb53db3 100644 --- a/src/cgalutils.cc +++ b/src/cgalutils.cc @@ -224,9 +224,9 @@ void ZRemover::visit( CGAL_Nef_polyhedron3::Halffacet_const_handle hfacet ) log << " \n"; } -static CGAL_Nef_polyhedron createNefPolyhedronFromPolySet(const PolySet &ps) +static CGAL_Nef_polyhedron *createNefPolyhedronFromPolySet(const PolySet &ps) { - if (ps.empty()) return CGAL_Nef_polyhedron(ps.is2d ? 2 : 3); + if (ps.empty()) return new CGAL_Nef_polyhedron(ps.is2d ? 2 : 3); if (ps.is2d) { @@ -259,7 +259,7 @@ static CGAL_Nef_polyhedron createNefPolyhedronFromPolySet(const PolySet &ps) } CGAL_Nef_polyhedron2 N(pdata.begin(), pdata.end(), CGAL_Nef_polyhedron2::POLYGONS); - return CGAL_Nef_polyhedron(N); + return new CGAL_Nef_polyhedron(N); #endif #if 0 // This version of the code works fine but is pretty slow. @@ -284,7 +284,7 @@ static CGAL_Nef_polyhedron createNefPolyhedronFromPolySet(const PolySet &ps) N += CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED); } - return CGAL_Nef_polyhedron(N); + return new CGAL_Nef_polyhedron(N); #endif #if 1 // This version of the code does essentially the same thing as the 2nd @@ -440,7 +440,7 @@ static CGAL_Nef_polyhedron createNefPolyhedronFromPolySet(const PolySet &ps) PolyReducer pr(ps); pr.reduce(); - return CGAL_Nef_polyhedron(pr.toNef()); + return new CGAL_Nef_polyhedron(pr.toNef()); #endif #if 0 // This is another experimental version. I should run faster than the above, @@ -471,7 +471,7 @@ static CGAL_Nef_polyhedron createNefPolyhedronFromPolySet(const PolySet &ps) N ^= CGAL_Nef_polyhedron2(plist.begin(), plist.end(), CGAL_Nef_polyhedron2::INCLUDED); } - return CGAL_Nef_polyhedron(N); + return new CGAL_Nef_polyhedron(N); #endif } @@ -490,19 +490,18 @@ static CGAL_Nef_polyhedron createNefPolyhedronFromPolySet(const PolySet &ps) PRINTB("CGAL error in CGAL_Nef_polyhedron3(): %s", e.what()); } CGAL::set_error_behaviour(old_behaviour); - return CGAL_Nef_polyhedron(N); + return new CGAL_Nef_polyhedron(N); } - return CGAL_Nef_polyhedron(); + return NULL; } - -static CGAL_Nef_polyhedron createNefPolyhedronFromPolygon2d(const Polygon2d &polygon) +static CGAL_Nef_polyhedron *createNefPolyhedronFromPolygon2d(const Polygon2d &polygon) { shared_ptr ps(polygon.tessellate()); return createNefPolyhedronFromPolySet(*ps); } -CGAL_Nef_polyhedron createNefPolyhedronFromGeometry(const Geometry &geom) +CGAL_Nef_polyhedron *createNefPolyhedronFromGeometry(const Geometry &geom) { const PolySet *ps = dynamic_cast(&geom); if (ps) { @@ -513,7 +512,7 @@ CGAL_Nef_polyhedron createNefPolyhedronFromGeometry(const Geometry &geom) if (poly2d) return createNefPolyhedronFromPolygon2d(*poly2d); } assert(false && "CGALEvaluator::evaluateCGALMesh(): Unsupported geometry type"); - return CGAL_Nef_polyhedron(); + return NULL; } #endif /* ENABLE_CGAL */ diff --git a/src/cgalutils.h b/src/cgalutils.h index 57dc19c2..9c885081 100644 --- a/src/cgalutils.h +++ b/src/cgalutils.h @@ -5,7 +5,7 @@ #include "polyset.h" #include "CGAL_Nef_polyhedron.h" -CGAL_Nef_polyhedron createNefPolyhedronFromGeometry(const class Geometry &geom); +CGAL_Nef_polyhedron *createNefPolyhedronFromGeometry(const class Geometry &geom); bool createPolySetFromPolyhedron(const CGAL_Polyhedron &p, PolySet &ps); bool createPolyhedronFromPolySet(const PolySet &ps, CGAL_Polyhedron &p); CGAL_Iso_cuboid_3 bounding_box( const CGAL_Nef_polyhedron3 &N ); diff --git a/src/mainwin.cc b/src/mainwin.cc index 5aec0c07..3366565d 100644 --- a/src/mainwin.cc +++ b/src/mainwin.cc @@ -1315,7 +1315,7 @@ void MainWindow::actionRenderCGALDone(CGAL_Nef_polyhedron *root_N) CGALCache::instance()->print(); #endif if (!root_N->isNull()) { - if (root_N->dim == 2) { + if (root_N->getDimension() == 2) { PRINT(" Top level object is a 2D object:"); PRINTB(" Empty: %6s", (root_N->p2->is_empty() ? "yes" : "no")); PRINTB(" Plane: %6s", (root_N->p2->is_plane() ? "yes" : "no")); @@ -1327,7 +1327,7 @@ void MainWindow::actionRenderCGALDone(CGAL_Nef_polyhedron *root_N) PRINTB(" ConnComp: %6d", root_N->p2->explorer().number_of_connected_components()); } - if (root_N->dim == 3) { + if (root_N->getDimension() == 3) { PRINT(" Top level object is a 3D object:"); PRINTB(" Simple: %6s", (root_N->p3->is_simple() ? "yes" : "no")); PRINTB(" Valid: %6s", (root_N->p3->is_valid() ? "yes" : "no")); @@ -1441,7 +1441,7 @@ void MainWindow::actionExportSTLorOFF(bool) return; } - if (this->root_N->dim != 3) { + if (this->root_N->getDimension() != 3) { PRINT("Current top level object is not a 3D object."); clearCurrentOutput(); return; @@ -1501,7 +1501,7 @@ void MainWindow::actionExportDXF() return; } - if (this->root_N->dim != 2) { + if (this->root_N->getDimension() != 2) { PRINT("Current top level object is not a 2D object."); clearCurrentOutput(); return; diff --git a/src/openscad.cc b/src/openscad.cc index b9aa12ba..06696f1d 100644 --- a/src/openscad.cc +++ b/src/openscad.cc @@ -359,7 +359,7 @@ int cmdline(const char *deps_output_file, const std::string &filename, Camera &c } if (stl_output_file) { - if (root_N.dim != 3) { + if (root_N.getDimension() != 3) { PRINT("Current top level object is not a 3D object.\n"); return 1; } @@ -378,7 +378,7 @@ int cmdline(const char *deps_output_file, const std::string &filename, Camera &c } if (off_output_file) { - if (root_N.dim != 3) { + if (root_N.getDimension() != 3) { PRINT("Current top level object is not a 3D object.\n"); return 1; } @@ -397,7 +397,7 @@ int cmdline(const char *deps_output_file, const std::string &filename, Camera &c } if (dxf_output_file) { - if (root_N.dim != 2) { + if (root_N.getDimension() != 2) { PRINT("Current top level object is not a 2D object.\n"); return 1; }