From aef4364d941bab27597d57d03b25ade77d9dcf55 Mon Sep 17 00:00:00 2001 From: Marius Kintel Date: Tue, 5 Mar 2024 21:35:48 -0500 Subject: [PATCH] More PolySet Refactoring (#5029) * Manifold constness fixes * Added utility: PolySet::createEmpty() * Renamed private members to have underscore suffix --- src/RenderStatistic.cc | 2 +- src/core/ImportNode.cc | 2 +- src/core/primitives.cc | 18 +++-- src/geometry/GeometryEvaluator.cc | 70 +++++++++---------- src/geometry/PolySet.cc | 18 ++--- src/geometry/PolySet.h | 14 ++-- src/geometry/PolySetUtils.cc | 2 +- src/geometry/boolean_utils.cc | 2 +- .../cgalutils-applyops-hybrid-minkowski.cc | 2 +- src/geometry/cgal/cgalutils-hybrid.cc | 4 +- src/geometry/cgal/cgalutils.cc | 4 +- src/geometry/manifold/ManifoldGeometry.cc | 15 ++-- src/geometry/manifold/ManifoldGeometry.h | 2 +- .../manifold/manifold-applyops-minkowski.cc | 2 +- src/geometry/manifold/manifoldutils.cc | 4 +- src/io/export.cc | 2 +- src/io/import_3mf.cc | 44 ++++++------ src/io/import_amf.cc | 4 +- src/io/import_obj.cc | 20 +++--- src/io/import_off.cc | 38 +++++----- src/io/import_stl.cc | 23 +++--- src/openscad.cc | 26 +++---- ...olyhedron-self-touch-face-nonmanifold.scad | 2 + ...yhedron-self-touch-vertex-nonmanifold.scad | 2 + .../3D/misc/polyhedron-self-touch-vertex.scad | 2 + .../scad/3D/misc/polyhedrons-touch-edge.scad | 2 + .../polyhedrons-touch-face-nonmanifold.scad | 2 + 27 files changed, 171 insertions(+), 157 deletions(-) diff --git a/src/RenderStatistic.cc b/src/RenderStatistic.cc index 07e3744545c..b9da310b629 100644 --- a/src/RenderStatistic.cc +++ b/src/RenderStatistic.cc @@ -354,7 +354,7 @@ void StreamVisitor::visit(const PolySet& ps) assert(ps.getDimension() == 3); nlohmann::json geometryJson; geometryJson["dimensions"] = 3; - geometryJson["convex"] = ps.is_convex(); + geometryJson["convex"] = ps.isConvex(); geometryJson["facets"] = ps.numFacets(); if (is_enabled(RenderStatistic::BOUNDING_BOX)) { geometryJson["bounding_box"] = getBoundingBox3(ps); diff --git a/src/core/ImportNode.cc b/src/core/ImportNode.cc index 5376335abed..0d5bb11dfe2 100644 --- a/src/core/ImportNode.cc +++ b/src/core/ImportNode.cc @@ -211,7 +211,7 @@ std::unique_ptr ImportNode::createGeometry() const #endif default: LOG(message_group::Error, "Unsupported file format while trying to import file '%1$s', import() at line %2$d", this->filename, loc.firstLine()); - g = std::make_unique(3); + g = PolySet::createEmpty(); } g->setConvexity(this->convexity); diff --git a/src/core/primitives.cc b/src/core/primitives.cc index 9bfa77240bc..dc4a864d336 100644 --- a/src/core/primitives.cc +++ b/src/core/primitives.cc @@ -111,7 +111,7 @@ std::unique_ptr CubeNode::createGeometry() const || this->y <= 0 || !std::isfinite(this->y) || this->z <= 0 || !std::isfinite(this->z) ) { - return std::make_unique(3, true); + return PolySet::createEmpty(); } double x1, x2, y1, y2, z1, z2; @@ -184,7 +184,7 @@ static std::shared_ptr builtin_cube(const ModuleInstantiation *ins std::unique_ptr SphereNode::createGeometry() const { if (this->r <= 0 || !std::isfinite(this->r)) { - return std::make_unique(3, true); + return PolySet::createEmpty(); } auto num_fragments = Calc::get_fragments_from_r(r, fn, fs, fa); @@ -263,7 +263,7 @@ std::unique_ptr CylinderNode::createGeometry() const || this->r2 < 0 || !std::isfinite(this->r2) || (this->r1 <= 0 && this->r2 <= 0) ) { - return std::make_unique(3, true); + return PolySet::createEmpty(); } auto num_fragments = Calc::get_fragments_from_r(std::fmax(this->r1, this->r2), this->fn, this->fs, this->fa); @@ -277,7 +277,7 @@ std::unique_ptr CylinderNode::createGeometry() const z2 = this->h; } - auto polyset = std::make_unique(3, true); + auto polyset = std::make_unique(3, /*convex*/true); polyset->vertices.reserve(2 * num_fragments); generate_circle(std::back_inserter(polyset->vertices), r1, z1, num_fragments); @@ -404,12 +404,17 @@ std::string PolyhedronNode::toString() const std::unique_ptr PolyhedronNode::createGeometry() const { - auto p = std::make_unique(3); + auto p = PolySet::createEmpty(); p->setConvexity(this->convexity); p->vertices=this->points; p->indices=this->faces; - for (auto &poly : p->indices) + p->isTriangular = true; + for (auto &poly : p->indices) { std::reverse(poly.begin(),poly.end()); + if (p->isTriangular && poly.size() > 3) { + p->isTriangular = false; + } + } return p; } @@ -474,6 +479,7 @@ static std::shared_ptr builtin_polyhedron(const ModuleInstantiatio } pointIndexIndex++; } + // FIXME: Print an error message if < 3 vertices are specified if (face.size() >= 3) { node->faces.push_back(std::move(face)); } diff --git a/src/geometry/GeometryEvaluator.cc b/src/geometry/GeometryEvaluator.cc index c11e5c50e88..55424cc63ae 100644 --- a/src/geometry/GeometryEvaluator.cc +++ b/src/geometry/GeometryEvaluator.cc @@ -256,7 +256,7 @@ std::unique_ptr GeometryEvaluator::applyHull3D(const AbstractNode& nod { Geometry::Geometries children = collectChildren3D(node); - auto P = std::make_unique(3); + auto P = PolySet::createEmpty(); return applyHull(children); } @@ -704,10 +704,10 @@ Response GeometryEvaluator::visit(State& state, const TransformNode& node) std::shared_ptr newpoly; if (res.isConst()) { newpoly = std::make_shared(*polygons); - } + } else { newpoly = std::dynamic_pointer_cast(res.ptr()); - } + } Transform2d mat2; mat2.matrix() << @@ -715,7 +715,7 @@ Response GeometryEvaluator::visit(State& state, const TransformNode& node) node.matrix(1, 0), node.matrix(1, 1), node.matrix(1, 3), node.matrix(3, 0), node.matrix(3, 1), node.matrix(3, 3); newpoly->transform(mat2); - // FIXME: We lose the transform if we copied a const geometry above. Probably similar issue in multiple places + // FIXME: We lose the transform if we copied a const geometry above. Probably similar issue in multiple places // A 2D transformation may flip the winding order of a polygon. // If that happens with a sanitized polygon, we need to reverse // the winding order for it to be correct. @@ -842,29 +842,29 @@ static void add_slice(PolySetBuilder &builder, const Polygon2d& poly, // unless at top for a 0-scaled axis (which can create 0 thickness "ears") if (splitfirst xor any_zero) { builder.appendPoly({ - Vector3d(curr1[0], curr1[1], h1), - Vector3d(curr2[0], curr2[1], h2), - Vector3d(prev1[0], prev1[1], h1) - }); + Vector3d(curr1[0], curr1[1], h1), + Vector3d(curr2[0], curr2[1], h2), + Vector3d(prev1[0], prev1[1], h1) + }); if (!any_zero || (any_non_zero && prev2 != curr2)) { builder.appendPoly({ - Vector3d(prev2[0], prev2[1], h2), - Vector3d(prev1[0], prev1[1], h1), - Vector3d(curr2[0], curr2[1], h2) - }); + Vector3d(prev2[0], prev2[1], h2), + Vector3d(prev1[0], prev1[1], h1), + Vector3d(curr2[0], curr2[1], h2) + }); } } else { builder.appendPoly({ - Vector3d(curr1[0], curr1[1], h1), - Vector3d(prev2[0], prev2[1], h2), - Vector3d(prev1[0], prev1[1], h1) - }); + Vector3d(curr1[0], curr1[1], h1), + Vector3d(prev2[0], prev2[1], h2), + Vector3d(prev1[0], prev1[1], h1) + }); if (!any_zero || (any_non_zero && prev2 != curr2)) { builder.appendPoly({ - Vector3d(curr1[0], curr1[1], h1), - Vector3d(curr2[0], curr2[1], h2), - Vector3d(prev2[0], prev2[1], h2) - }); + Vector3d(curr1[0], curr1[1], h1), + Vector3d(curr2[0], curr2[1], h2), + Vector3d(prev2[0], prev2[1], h2) + }); } } prev1 = curr1; @@ -1041,7 +1041,7 @@ static std::unique_ptr extrudePolygon(const LinearExtrudeNode& node, c if (isConvex && non_linear) isConvex = unknown; PolySetBuilder builder(0, 0, 3, isConvex); builder.setConvexity(node.convexity); - if (node.height <= 0) return std::make_unique(3); + if (node.height <= 0) return PolySet::createEmpty(); size_t slices; if (node.has_slices) { @@ -1312,16 +1312,16 @@ static std::unique_ptr rotatePolygon(const RotateExtrudeNode& node, co for (size_t i = 0; i < o.vertices.size(); ++i) { builder.appendPoly({ - rings[j % 2][(i + 1) % o.vertices.size()], - rings[(j + 1) % 2][(i + 1) % o.vertices.size()], - rings[j % 2][i] - }); + rings[j % 2][(i + 1) % o.vertices.size()], + rings[(j + 1) % 2][(i + 1) % o.vertices.size()], + rings[j % 2][i] + }); builder.appendPoly({ - rings[(j + 1) % 2][(i + 1) % o.vertices.size()], - rings[(j + 1) % 2][i], - rings[j % 2][i] - }); + rings[(j + 1) % 2][(i + 1) % o.vertices.size()], + rings[(j + 1) % 2][i], + rings[j % 2][i] + }); } } } @@ -1351,7 +1351,7 @@ Response GeometryEvaluator::visit(State& state, const RotateExtrudeNode& node) geometry = applyToChildren2D(node, OpenSCADOperator::UNION); } if (geometry) { - geom = rotatePolygon(node, *geometry); + geom = rotatePolygon(node, *geometry); } } else { geom = smartCacheGet(node, false); @@ -1404,8 +1404,8 @@ std::shared_ptr GeometryEvaluator::projectionNoCut(const Project // project chgeom -> polygon2d if (auto chPS = PolySetUtils::getGeometryAsPolySet(chgeom)) { if (auto poly = PolySetUtils::project(*chPS)) { - bounds.extend(poly->getBoundingBox()); - tmp_geom.push_back(std::move(poly)); + bounds.extend(poly->getBoundingBox()); + tmp_geom.push_back(std::move(poly)); } } } @@ -1478,7 +1478,7 @@ Response GeometryEvaluator::visit(State& state, const CgalAdvNode& node) geom = res.constptr(); // If we added convexity, we need to pass it on if (geom && geom->getConvexity() != node.convexity) { - std::shared_ptr editablegeom; + std::shared_ptr editablegeom; // If we got a const object, make a copy if (res.isConst()) editablegeom = geom->copy(); else editablegeom = res.ptr(); @@ -1561,13 +1561,13 @@ Response GeometryEvaluator::visit(State& state, const RoofNode& node) if (!isSmartCached(node)) { const auto polygon2d = applyToChildren2D(node, OpenSCADOperator::UNION); if (polygon2d) { - std::unique_ptr roof; + std::unique_ptr roof; try { roof = roofOverPolygon(node, *polygon2d); } catch (RoofNode::roof_exception& e) { LOG(message_group::Error, node.modinst->location(), this->tree.getDocumentPath(), "Skeleton computation error. " + e.message()); - roof = std::make_unique(3); + roof = PolySet::createEmpty(); } assert(roof); geom = std::move(roof); diff --git a/src/geometry/PolySet.cc b/src/geometry/PolySet.cc index cd4e3a6c4f1..3361f3ba8d2 100644 --- a/src/geometry/PolySet.cc +++ b/src/geometry/PolySet.cc @@ -46,7 +46,7 @@ */ -PolySet::PolySet(unsigned int dim, boost::tribool convex) : dim(dim), convex(convex) +PolySet::PolySet(unsigned int dim, boost::tribool convex) : dim_(dim), convex_(convex) { } @@ -58,7 +58,7 @@ std::string PolySet::dump() const { std::ostringstream out; out << "PolySet:" - << "\n dimensions:" << this->dim + << "\n dimensions:" << dim_ << "\n convexity:" << this->convexity << "\n num polygons: " << indices.size() << "\n polygons data:"; @@ -74,12 +74,12 @@ std::string PolySet::dump() const BoundingBox PolySet::getBoundingBox() const { - if (this->bbox.isNull()) { + if (bbox_.isNull()) { for (const auto& v : vertices) { - this->bbox.extend(v); + bbox_.extend(v); } } - return this->bbox; + return bbox_; } size_t PolySet::memsize() const @@ -102,12 +102,12 @@ void PolySet::transform(const Transform3d& mat) for (auto& p : this->indices) { std::reverse(p.begin(), p.end()); } - this->bbox.setNull(); + bbox_.setNull(); } -bool PolySet::is_convex() const { - if (convex || this->isEmpty()) return true; - if (!convex) return false; +bool PolySet::isConvex() const { + if (convex_ || this->isEmpty()) return true; + if (!convex_) return false; return PolySetUtils::is_approximately_convex(*this); } diff --git a/src/geometry/PolySet.h b/src/geometry/PolySet.h index 0a6ce665796..cca728ee382 100644 --- a/src/geometry/PolySet.h +++ b/src/geometry/PolySet.h @@ -23,7 +23,7 @@ class PolySet : public Geometry size_t memsize() const override; BoundingBox getBoundingBox() const override; std::string dump() const override; - unsigned int getDimension() const override { return this->dim; } + unsigned int getDimension() const override { return dim_; } bool isEmpty() const override { return indices.empty(); } std::unique_ptr copy() const override; @@ -32,12 +32,14 @@ class PolySet : public Geometry void transform(const Transform3d& mat) override; void resize(const Vector3d& newsize, const Eigen::Matrix& autosize) override; - bool is_convex() const; - boost::tribool convexValue() const { return this->convex; } + bool isConvex() const; + boost::tribool convexValue() const { return convex_; } bool isTriangular = false; + static std::unique_ptr createEmpty() { return std::make_unique(3); } + private: - unsigned int dim; - mutable boost::tribool convex; - mutable BoundingBox bbox; + unsigned int dim_; + mutable boost::tribool convex_; + mutable BoundingBox bbox_; }; diff --git a/src/geometry/PolySetUtils.cc b/src/geometry/PolySetUtils.cc index 0789958a853..ebdda61a01e 100644 --- a/src/geometry/PolySetUtils.cc +++ b/src/geometry/PolySetUtils.cc @@ -164,7 +164,7 @@ std::shared_ptr getGeometryAsPolySet(const std::shared_ptrPolySet failed."); } - return std::make_unique(3); + return PolySet::createEmpty(); } if (auto hybrid = std::dynamic_pointer_cast(geom)) { return hybrid->toPolySet(); diff --git a/src/geometry/boolean_utils.cc b/src/geometry/boolean_utils.cc index e89624da6c2..7e62bb85182 100644 --- a/src/geometry/boolean_utils.cc +++ b/src/geometry/boolean_utils.cc @@ -137,7 +137,7 @@ std::shared_ptr applyMinkowski(const Geometry::Geometries& child else if (nef && nef->p3->is_simple()) CGALUtils::convertNefToPolyhedron(*nef->p3, poly); else throw 0; - if ((ps && ps->is_convex()) || + if ((ps && ps->isConvex()) || (!ps && CGALUtils::is_weakly_convex(poly))) { PRINTDB("Minkowski: child %d is convex and %s", i % (ps?"PolySet":"Nef")); P[i].push_back(poly); diff --git a/src/geometry/cgal/cgalutils-applyops-hybrid-minkowski.cc b/src/geometry/cgal/cgalutils-applyops-hybrid-minkowski.cc index c876989ad47..8bdaf45891d 100644 --- a/src/geometry/cgal/cgalutils-applyops-hybrid-minkowski.cc +++ b/src/geometry/cgal/cgalutils-applyops-hybrid-minkowski.cc @@ -66,7 +66,7 @@ std::shared_ptr applyMinkowskiHybrid(const Geometry::Geometries& else throw 0; } else throw 0; - if ((ps && ps->is_convex()) || + if ((ps && ps->isConvex()) || (!ps && CGALUtils::is_weakly_convex(*poly))) { PRINTDB("Minkowski: child %d is convex and %s", i % (ps?"PolySet":"Hybrid")); P[i].push_back(poly); diff --git a/src/geometry/cgal/cgalutils-hybrid.cc b/src/geometry/cgal/cgalutils-hybrid.cc index 0f592ecf20a..1503dd8a9cb 100644 --- a/src/geometry/cgal/cgalutils-hybrid.cc +++ b/src/geometry/cgal/cgalutils-hybrid.cc @@ -49,7 +49,7 @@ std::shared_ptr createHybridPolyhedronFromPolySet(const Po std::vector points3d; psq.quantizeVertices(&points3d); auto ps_tri = PolySetUtils::tessellate_faces(psq); - if (ps_tri->is_convex()) { + if (ps_tri->isConvex()) { using K = CGAL::Epick; // Collect point cloud std::vector points(points3d.size()); @@ -70,7 +70,7 @@ std::shared_ptr createHybridPolyhedronFromPolySet(const Po if (createMeshFromPolySet(*ps_tri, *mesh)) { assert(false && "Error from createMeshFromPolySet"); } - if (!ps_tri->is_convex()) { + if (!ps_tri->isConvex()) { if (isClosed(*mesh)) { // Note: PMP::orient can corrupt models and cause cataclysmic memory leaks // (try testdata/scad/3D/issues/issue1105d.scad for instance), but diff --git a/src/geometry/cgal/cgalutils.cc b/src/geometry/cgal/cgalutils.cc index 777113198cb..1264a163b1f 100644 --- a/src/geometry/cgal/cgalutils.cc +++ b/src/geometry/cgal/cgalutils.cc @@ -46,7 +46,7 @@ std::unique_ptr createNefPolyhedronFromPolySet(const PolySe std::vector points3d; psq.quantizeVertices(&points3d); auto ps_tri = PolySetUtils::tessellate_faces(psq); - if (ps_tri->is_convex()) { + if (ps_tri->isConvex()) { using K = CGAL::Epick; // Collect point cloud std::vector points(points3d.size()); @@ -375,7 +375,7 @@ std::unique_ptr createPolySetFromNefPolyhedron3(const CGAL::Nef_polyhed LOG(message_group::Error, "Non-manifold mesh created: %1$d unconnected edges", unconnected2); } - auto polyset = std::make_unique(3); + auto polyset = PolySet::createEmpty(); polyset->vertices.reserve(verts.size()); for (const auto& v : verts) { polyset->vertices.emplace_back(v.cast()); diff --git a/src/geometry/manifold/ManifoldGeometry.cc b/src/geometry/manifold/ManifoldGeometry.cc index 2303f6b6cb2..702625ee024 100644 --- a/src/geometry/manifold/ManifoldGeometry.cc +++ b/src/geometry/manifold/ManifoldGeometry.cc @@ -17,9 +17,9 @@ Result vector_convert(V const& v) { } -ManifoldGeometry::ManifoldGeometry() : manifold_(std::make_shared()) {} +ManifoldGeometry::ManifoldGeometry() : manifold_(std::make_shared()) {} -ManifoldGeometry::ManifoldGeometry(const std::shared_ptr& mani) : manifold_(mani) { +ManifoldGeometry::ManifoldGeometry(const std::shared_ptr& mani) : manifold_(mani) { assert(manifold_); if (!manifold_) clear(); } @@ -98,17 +98,18 @@ std::shared_ptr ManifoldGeometry::toPolySet() const { ps->vertices.reserve(mesh.NumVert()); ps->indices.reserve(mesh.NumTri()); ps->setConvexity(convexity); + // first 3 channels are xyz coordinate for (size_t i = 0; i < mesh.vertProperties.size(); i += mesh.numProp) - ps->vertices.push_back({ + ps->vertices.emplace_back( mesh.vertProperties[i], - mesh.vertProperties[i+1], - mesh.vertProperties[i+2]}); + mesh.vertProperties[i + 1], + mesh.vertProperties[i + 2]); for (size_t i = 0; i < mesh.triVerts.size(); i += 3) ps->indices.push_back({ static_cast(mesh.triVerts[i]), - static_cast(mesh.triVerts[i+1]), - static_cast(mesh.triVerts[i+2])}); + static_cast(mesh.triVerts[i + 1]), + static_cast(mesh.triVerts[i + 2])}); return ps; } diff --git a/src/geometry/manifold/ManifoldGeometry.h b/src/geometry/manifold/ManifoldGeometry.h index a65e4db3cb6..c9a8a1fb2e7 100644 --- a/src/geometry/manifold/ManifoldGeometry.h +++ b/src/geometry/manifold/ManifoldGeometry.h @@ -16,7 +16,7 @@ class ManifoldGeometry : public Geometry VISITABLE_GEOMETRY(); ManifoldGeometry(); - ManifoldGeometry(const std::shared_ptr& object); + ManifoldGeometry(const std::shared_ptr& object); ManifoldGeometry(const ManifoldGeometry& other) = default; ManifoldGeometry& operator=(const ManifoldGeometry& other); diff --git a/src/geometry/manifold/manifold-applyops-minkowski.cc b/src/geometry/manifold/manifold-applyops-minkowski.cc index 8970511fcd6..0c5d61e88cc 100644 --- a/src/geometry/manifold/manifold-applyops-minkowski.cc +++ b/src/geometry/manifold/manifold-applyops-minkowski.cc @@ -31,7 +31,7 @@ std::shared_ptr applyMinkowskiManifold(const Geometry::Geometrie if (ps) { auto poly = std::make_shared(); CGALUtils::createPolyhedronFromPolySet(*ps, *poly); - if (pIsConvexOut) *pIsConvexOut = ps->is_convex(); + if (pIsConvexOut) *pIsConvexOut = ps->isConvex(); return poly; } else { if (auto mani = std::dynamic_pointer_cast(geom)) { diff --git a/src/geometry/manifold/manifoldutils.cc b/src/geometry/manifold/manifoldutils.cc index 9b2ed4809bc..dcb83702054 100644 --- a/src/geometry/manifold/manifoldutils.cc +++ b/src/geometry/manifold/manifoldutils.cc @@ -116,7 +116,7 @@ std::shared_ptr createManifoldFromPolySet(const PolySet& CGAL_DoubleMesh m; - if (ps_tri->is_convex()) { + if (ps_tri->isConvex()) { using K = CGAL::Epick; // Collect point cloud std::vector points(points3d.size()); @@ -133,7 +133,7 @@ std::shared_ptr createManifoldFromPolySet(const PolySet& CGALUtils::createMeshFromPolySet(*ps_tri, m); } - if (!ps_tri->is_convex()) { + if (!ps_tri->isConvex()) { if (CGALUtils::isClosed(m)) { CGALUtils::orientToBoundAVolume(m); } else { diff --git a/src/io/export.cc b/src/io/export.cc index bc5dda2e2a0..8f731212197 100644 --- a/src/io/export.cc +++ b/src/io/export.cc @@ -195,7 +195,7 @@ struct LexographicLess { std::unique_ptr createSortedPolySet(const PolySet& ps) { - auto out = std::make_unique(3); + auto out = PolySet::createEmpty(); std::map vertexMap; diff --git a/src/io/import_3mf.cc b/src/io/import_3mf.cc index 63461185650..1f3cd33f6fb 100644 --- a/src/io/import_3mf.cc +++ b/src/io/import_3mf.cc @@ -65,7 +65,7 @@ static std::unique_ptr import_3mf_error(PLib3MFModel *model = nullptr, lib3mf_release(object_it); } - return std::make_unique(3); + return PolySet::createEmpty(); } std::unique_ptr import_3mf(const std::string& filename, const Location& loc) @@ -74,14 +74,14 @@ std::unique_ptr import_3mf(const std::string& filename, const Location HRESULT result = lib3mf_getinterfaceversion(&interfaceVersionMajor, &interfaceVersionMinor, &interfaceVersionMicro); if (result != LIB3MF_OK) { LOG(message_group::Error, "Error reading 3MF library version"); - return std::make_unique(3); + return PolySet::createEmpty(); } if ((interfaceVersionMajor != NMR_APIVERSION_INTERFACE_MAJOR)) { LOG(message_group::Error, "Invalid 3MF library major version %1$d.%2$d.%3$d, expected %4$d.%5$d.%6$d", interfaceVersionMajor, interfaceVersionMinor, interfaceVersionMicro, NMR_APIVERSION_INTERFACE_MAJOR, NMR_APIVERSION_INTERFACE_MINOR, NMR_APIVERSION_INTERFACE_MICRO); - return std::make_unique(3); + return PolySet::createEmpty(); } PLib3MFModel *model; @@ -173,7 +173,7 @@ std::unique_ptr import_3mf(const std::string& filename, const Location lib3mf_release(model); if (!first_mesh) { - return std::make_unique(3); + return PolySet::createEmpty(); } else if (meshes.empty()) { return first_mesh; } else { @@ -187,10 +187,10 @@ std::unique_ptr import_3mf(const std::string& filename, const Location if (auto ps = PolySetUtils::getGeometryAsPolySet(CGALUtils::applyUnion3D(children.begin(), children.end()))) { p = std::make_unique(*ps); } else { - p = std::make_unique(3); + p = PolySet::createEmpty(); } #else - p = std::make_unique(3); + p = PolySet::createEmpty(); #endif // ifdef ENABLE_CGAL return p; } @@ -237,11 +237,11 @@ std::unique_ptr import_3mf(const std::string& filename, const Location LOG(message_group::Error, "Invalid 3MF library major version %1$d.%2$d.%3$d, expected %4$d.%5$d.%6$d", interfaceVersionMajor, interfaceVersionMinor, interfaceVersionMicro, LIB3MF_VERSION_MAJOR, LIB3MF_VERSION_MINOR, LIB3MF_VERSION_MICRO); - return std::make_unique(3); + return PolySet::createEmpty(); } } catch (const Lib3MF::ELib3MFException& e) { LOG(message_group::Export_Error, e.what()); - return std::make_unique(3); + return PolySet::createEmpty(); } Lib3MF::PModel model; @@ -249,11 +249,11 @@ std::unique_ptr import_3mf(const std::string& filename, const Location model = wrapper->CreateModel(); if (!model) { LOG(message_group::Error, "Could not create model"); - return std::make_unique(3); + return PolySet::createEmpty(); } } catch (const Lib3MF::ELib3MFException& e) { LOG(message_group::Export_Error, e.what()); - return std::make_unique(3); + return PolySet::createEmpty(); } Lib3MF::PReader reader; @@ -261,24 +261,24 @@ std::unique_ptr import_3mf(const std::string& filename, const Location reader = model->QueryReader("3mf"); if (!reader) { LOG(message_group::Error, "Could not create 3MF reader"); - return std::make_unique(3); + return PolySet::createEmpty(); } } catch (const Lib3MF::ELib3MFException& e) { LOG(message_group::Export_Error, "Could create 3MF reader, import() at line %1$d: %2$s", loc.firstLine(), e.what()); - return std::make_unique(3); + return PolySet::createEmpty(); } try { reader->ReadFromFile(filename); } catch (const Lib3MF::ELib3MFException& e) { LOG(message_group::Warning, "Could not read file '%1$s', import() at line %2$d: %3$s", filename.c_str(), loc.firstLine(), e.what()); - return std::make_unique(3); + return PolySet::createEmpty(); } Lib3MF::PMeshObjectIterator object_it; object_it = model->GetMeshObjects(); if (!object_it) { - return std::make_unique(3); + return PolySet::createEmpty(); } std::unique_ptr first_mesh; @@ -290,20 +290,20 @@ std::unique_ptr import_3mf(const std::string& filename, const Location try { object = object_it->GetCurrentMeshObject(); if (!object) { - return std::make_unique(3); + return PolySet::createEmpty(); } } catch (const Lib3MF::ELib3MFException& e) { LOG(message_group::Error, e.what()); - return std::make_unique(3); + return PolySet::createEmpty(); } Lib3MF_uint64 vertex_count = object->GetVertexCount(); if (!vertex_count) { - return std::make_unique(3); + return PolySet::createEmpty(); } Lib3MF_uint64 triangle_count = object->GetTriangleCount(); if (!triangle_count) { - return std::make_unique(3); + return PolySet::createEmpty(); } PRINTDB("%s: mesh %d, vertex count: %lu, triangle count: %lu", filename.c_str() % mesh_idx % vertex_count % triangle_count); @@ -330,7 +330,7 @@ std::unique_ptr import_3mf(const std::string& filename, const Location } if (first_mesh == 0) { - return std::make_unique(3); + return PolySet::createEmpty(); } else if (meshes.empty()) { return first_mesh; } else { @@ -347,10 +347,10 @@ std::unique_ptr import_3mf(const std::string& filename, const Location // FIXME: unnecessary copy p = std::make_unique(*ps); } else { - p = std::make_unique(3); + p = PolySet::createEmpty(); } #else - p = std::make_unique(3); + p = PolySet::createEmpty(); #endif // ifdef ENABLE_CGAL return p; } @@ -368,7 +368,7 @@ const std::string get_lib3mf_version() { std::unique_ptr import_3mf(const std::string&, const Location& loc) { LOG(message_group::Warning, "Import from 3MF format was not enabled when building the application, import() at line %1$d", loc.firstLine()); - return std::make_unique(3); + return PolySet::createEmpty(); } #endif // ENABLE_LIB3MF diff --git a/src/io/import_amf.cc b/src/io/import_amf.cc index 309916ff03f..b11d0fa56b8 100644 --- a/src/io/import_amf.cc +++ b/src/io/import_amf.cc @@ -254,7 +254,7 @@ std::unique_ptr AmfImporter::read(const std::string& filename) vertex_list.clear(); if (polySets.empty()) { - return std::make_unique(3); + return PolySet::createEmpty(); } if (polySets.size() == 1) { return std::move(polySets[0]); @@ -274,7 +274,7 @@ std::unique_ptr AmfImporter::read(const std::string& filename) #endif // ENABLE_CGAL LOG(message_group::Error, "Error importing multi-object AMF file '%1$s', import() at line %2$d", filename, this->loc.firstLine()); } - return std::make_unique(3); + return PolySet::createEmpty(); } #ifdef ENABLE_LIBZIP diff --git a/src/io/import_obj.cc b/src/io/import_obj.cc index 73d2a99443a..bbd4e422072 100644 --- a/src/io/import_obj.cc +++ b/src/io/import_obj.cc @@ -16,7 +16,7 @@ std::unique_ptr import_obj(const std::string& filename, const Location& LOG(message_group::Warning, "Can't open import file '%1$s', import() at line %2$d", filename, loc.firstLine()); - return std::make_unique(3); + return PolySet::createEmpty(); } boost::regex ex_comment(R"(^\s*#)"); boost::regex ex_v( R"(^\s*v\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s*$)"); @@ -36,7 +36,7 @@ std::unique_ptr import_obj(const std::string& filename, const Location& "OBJ File line %1$s, %2$s line '%3$s' importing file '%4$s'", lineno, errstr, line, filename); }; - std::vector vertex_map; + std::vector vertex_map; while (!f.eof()) { lineno++; @@ -48,14 +48,14 @@ std::unique_ptr import_obj(const std::string& filename, const Location& continue; } else if (boost::regex_search(line, results, ex_v) && results.size() >= 4) { try { - Vector3d v; + Vector3d v; for (int i = 0; i < 3; i++) { v[i]= boost::lexical_cast(results[i + 1]); } vertex_map.push_back(builder.vertexIndex(v)); } catch (const boost::bad_lexical_cast& blc) { AsciiError("can't parse vertex"); - return std::make_unique(3); + return PolySet::createEmpty(); } } else if (boost::regex_search(line, results, ex_f) && results.size() >= 2) { std::string args=results[1]; @@ -65,16 +65,16 @@ std::unique_ptr import_obj(const std::string& filename, const Location& for (const std::string& word : words) { std::vector wordindex; boost::split(wordindex, word, boost::is_any_of("/")); - if(wordindex.size() < 1) + if(wordindex.size() < 1) LOG(message_group::Warning, "Invalid Face index in File %1$s in Line %2$d", filename, lineno); - else { - int ind=boost::lexical_cast(wordindex[0]); + else { + int ind=boost::lexical_cast(wordindex[0]); if(ind >= 1 && ind <= vertex_map.size()) { builder.appendVertex(vertex_map[ind-1]); - } else { + } else { LOG(message_group::Warning, "Index %1$d out of range in Line %2$d", filename, lineno); - } - } + } + } } } else if (boost::regex_search(line, results, ex_vt)) { // ignore texture coords diff --git a/src/io/import_off.cc b/src/io/import_off.cc index 01998ef7293..048082df61e 100644 --- a/src/io/import_off.cc +++ b/src/io/import_off.cc @@ -54,7 +54,7 @@ std::unique_ptr import_off(const std::string& filename, const Location& if (!f.good()) { AsciiError("File error"); - return std::make_unique(3); + return PolySet::createEmpty(); } bool got_magic = false; @@ -67,7 +67,7 @@ std::unique_ptr import_off(const std::string& filename, const Location& unsigned int dimension = 3; if (line.empty() && !getline_clean("bad header: end of file")) { - return std::make_unique(3); + return PolySet::createEmpty(); } if (boost::regex_search(line, results, ex_magic) > 0) { @@ -86,26 +86,26 @@ std::unique_ptr import_off(const std::string& filename, const Location& // TODO: handle binary format if (is_binary) { AsciiError("binary OFF format not supported"); - return std::make_unique(3); + return PolySet::createEmpty(); } std::vector words; if (has_ndim) { if (line.empty() && !getline_clean("bad header: end of file")) { - return std::make_unique(3); + return PolySet::createEmpty(); } boost::split(words, line, boost::is_any_of(" \t"), boost::token_compress_on); if (f.eof() || words.size() < 1) { AsciiError("bad header: missing Ndim"); - return std::make_unique(3); + return PolySet::createEmpty(); } line = line.erase(0, words[0].length() + ((words.size() > 1) ? 1 : 0)); try { dimension = boost::lexical_cast(words[0]) + dimension - 3; } catch (const boost::bad_lexical_cast& blc) { AsciiError("bad header: bad data for Ndim"); - return std::make_unique(3); + return PolySet::createEmpty(); } } @@ -113,17 +113,17 @@ std::unique_ptr import_off(const std::string& filename, const Location& if (dimension != 3) { AsciiError((boost::format("unhandled vertex dimensions (%d)") % dimension).str().c_str()); - return std::make_unique(3); + return PolySet::createEmpty(); } if (line.empty() && !getline_clean("bad header: end of file")) { - return std::make_unique(3); + return PolySet::createEmpty(); } boost::split(words, line, boost::is_any_of(" \t"), boost::token_compress_on); if (f.eof() || words.size() < 3) { AsciiError("bad header: missing data"); - return std::make_unique(3); + return PolySet::createEmpty(); } unsigned long vertices_count; @@ -138,29 +138,29 @@ std::unique_ptr import_off(const std::string& filename, const Location& (void)edges_count; // ignored } catch (const boost::bad_lexical_cast& blc) { AsciiError("bad header: bad data"); - return std::make_unique(3); + return PolySet::createEmpty(); } if (f.eof() || vertices_count < 1 || faces_count < 1) { AsciiError("bad header: not enough data"); - return std::make_unique(3); + return PolySet::createEmpty(); } PRINTDB("%d vertices, %d faces, %d edges.", vertices_count % faces_count % edges_count); - auto ps = std::make_unique(3); + auto ps = PolySet::createEmpty(); ps->vertices.reserve(vertices_count); ps->indices.reserve(faces_count); while ((!f.eof()) && (vertex++ < vertices_count)) { if (!getline_clean("reading vertices: end of file")) { - return std::make_unique(3); + return PolySet::createEmpty(); } boost::split(words, line, boost::is_any_of(" \t"), boost::token_compress_on); if (words.size() < 3) { AsciiError("can't parse vertex: not enough data"); - return std::make_unique(3); + return PolySet::createEmpty(); } try { @@ -184,19 +184,19 @@ std::unique_ptr import_off(const std::string& filename, const Location& ps->vertices.push_back(v); } catch (const boost::bad_lexical_cast& blc) { AsciiError("can't parse vertex: bad data"); - return std::make_unique(3); + return PolySet::createEmpty(); } } while (!f.eof() && (face++ < faces_count)) { if (!getline_clean("reading faces: end of file")) { - return std::make_unique(3); + return PolySet::createEmpty(); } boost::split(words, line, boost::is_any_of(" \t"), boost::token_compress_on); if (words.size() < 1) { AsciiError("can't parse face: not enough data"); - return std::make_unique(3); + return PolySet::createEmpty(); } try { @@ -204,7 +204,7 @@ std::unique_ptr import_off(const std::string& filename, const Location& unsigned long i; if (words.size() - 1 < face_size) { AsciiError("can't parse face: missing indices"); - return std::make_unique(3); + return PolySet::createEmpty(); } ps->indices.emplace_back().reserve(face_size); //PRINTDB("Index[%d] [%d] = { ", face % n); @@ -228,7 +228,7 @@ std::unique_ptr import_off(const std::string& filename, const Location& } } catch (const boost::bad_lexical_cast& blc) { AsciiError("can't parse face: bad data"); - return std::make_unique(3); + return PolySet::createEmpty(); } } diff --git a/src/io/import_stl.cc b/src/io/import_stl.cc index bb19a8c7618..b269eb55fe0 100644 --- a/src/io/import_stl.cc +++ b/src/io/import_stl.cc @@ -71,7 +71,7 @@ std::unique_ptr import_stl(const std::string& filename, const Location& LOG(message_group::Warning, "Can't open import file '%1$s', import() at line %2$d", filename, loc.firstLine()); - return std::make_unique(3); + return PolySet::createEmpty(); } uint32_t facenum = 0; @@ -136,7 +136,7 @@ std::unique_ptr import_stl(const std::string& filename, const Location& break; } else if (i >= 3) { AsciiError("extra vertex"); - return std::make_unique(3); + return PolySet::createEmpty(); } else if (boost::regex_search(line, results, ex_vertices) && results.size() >= 4) { try { @@ -146,12 +146,13 @@ std::unique_ptr import_stl(const std::string& filename, const Location& } if (++i == 3) { builder.appendPoly(3); - for(int j=0;j<3;j++) - builder.appendVertex(Vector3d(vdata[j][0], vdata[j][1], vdata[j][2])); + for (int j=0;j<3;j++) { + builder.appendVertex(Vector3d(vdata[j][0], vdata[j][1], vdata[j][2])); + } } } catch (const boost::bad_lexical_cast& blc) { AsciiError("can't parse vertex"); - return std::make_unique(3); + return PolySet::createEmpty(); } } } @@ -170,10 +171,10 @@ std::unique_ptr import_stl(const std::string& filename, const Location& throw; } builder.appendPoly({ - Vector3d(facet.data.x1, facet.data.y1, facet.data.z1), - Vector3d(facet.data.x2, facet.data.y2, facet.data.z2), - Vector3d(facet.data.x3, facet.data.y3, facet.data.z3) - }); + Vector3d(facet.data.x1, facet.data.y1, facet.data.z1), + Vector3d(facet.data.x2, facet.data.y2, facet.data.z2), + Vector3d(facet.data.x3, facet.data.y3, facet.data.z3) + }); } } catch (const std::ios_base::failure& ex) { int64_t offset = -1; @@ -187,12 +188,12 @@ std::unique_ptr import_stl(const std::string& filename, const Location& "Binary STL '%1$s' error at byte %2$s: %3$s", filename, offset, ex.what()); } - return std::make_unique(3); + return PolySet::createEmpty(); } } else { LOG(message_group::Error, loc, "", "STL format not recognized in '%1$s'.", filename); - return std::make_unique(3); + return PolySet::createEmpty(); } return builder.build(); } diff --git a/src/openscad.cc b/src/openscad.cc index b3442aefb6d..e89c523cc84 100644 --- a/src/openscad.cc +++ b/src/openscad.cc @@ -578,26 +578,20 @@ int do_export(const CommandLine& cmd, const RenderVariables& render_variables, F constexpr bool allownef = true; root_geom = geomevaluator.evaluateGeometry(*tree.root(), allownef); - if (root_geom) { - if (cmd.viewOptions.renderer == RenderType::BACKEND_SPECIFIC && root_geom->getDimension() == 3) { - if (auto geomlist = std::dynamic_pointer_cast(root_geom)) { - auto flatlist = geomlist->flatten(); - for (auto& child : flatlist) { - if (child.second->getDimension() == 3) { - child.second = GeometryUtils::getBackendSpecificGeometry(child.second); - } + if (!root_geom) root_geom = std::make_shared(3); + if (cmd.viewOptions.renderer == RenderType::BACKEND_SPECIFIC && root_geom->getDimension() == 3) { + if (auto geomlist = std::dynamic_pointer_cast(root_geom)) { + auto flatlist = geomlist->flatten(); + for (auto& child : flatlist) { + if (child.second->getDimension() == 3) { + child.second = GeometryUtils::getBackendSpecificGeometry(child.second); } - root_geom = std::make_shared(flatlist); - } else { - root_geom = GeometryUtils::getBackendSpecificGeometry(root_geom); } - LOG("Converted to backend-specific geometry"); - } - } else { - root_geom = std::make_shared(3); - if (cmd.viewOptions.renderer == RenderType::BACKEND_SPECIFIC) { + root_geom = std::make_shared(flatlist); + } else { root_geom = GeometryUtils::getBackendSpecificGeometry(root_geom); } + LOG("Converted to backend-specific geometry"); } } if (is3D(export_format)) { diff --git a/tests/data/scad/3D/misc/polyhedron-self-touch-face-nonmanifold.scad b/tests/data/scad/3D/misc/polyhedron-self-touch-face-nonmanifold.scad index 3dd3c670624..4b447891c03 100644 --- a/tests/data/scad/3D/misc/polyhedron-self-touch-face-nonmanifold.scad +++ b/tests/data/scad/3D/misc/polyhedron-self-touch-face-nonmanifold.scad @@ -1,3 +1,5 @@ +// A single volume touching itself (three pairs of coincident vertices forming a triangle). +// This is a non-manifold object since the collapsed vertices are not distinct. polyhedron( points=[ [0, 0, 0], diff --git a/tests/data/scad/3D/misc/polyhedron-self-touch-vertex-nonmanifold.scad b/tests/data/scad/3D/misc/polyhedron-self-touch-vertex-nonmanifold.scad index ea1e9ce9f1e..db54e0b0deb 100644 --- a/tests/data/scad/3D/misc/polyhedron-self-touch-vertex-nonmanifold.scad +++ b/tests/data/scad/3D/misc/polyhedron-self-touch-vertex-nonmanifold.scad @@ -1,3 +1,5 @@ +// A single volume touching itself (two coincident vertices). +// This is a non-manifold object since the collapsed vertices are not distinct. polyhedron( points=[ [0, 0, 0], diff --git a/tests/data/scad/3D/misc/polyhedron-self-touch-vertex.scad b/tests/data/scad/3D/misc/polyhedron-self-touch-vertex.scad index f8e0dc91753..673d04f3081 100644 --- a/tests/data/scad/3D/misc/polyhedron-self-touch-vertex.scad +++ b/tests/data/scad/3D/misc/polyhedron-self-touch-vertex.scad @@ -1,3 +1,5 @@ +// A single volume touching itself (two coincident vertices), +// but with a manifold topology (the coincident vertices are distinct). polyhedron( points=[ [0, 0, 0], diff --git a/tests/data/scad/3D/misc/polyhedrons-touch-edge.scad b/tests/data/scad/3D/misc/polyhedrons-touch-edge.scad index f40f314301e..2ded10926d0 100644 --- a/tests/data/scad/3D/misc/polyhedrons-touch-edge.scad +++ b/tests/data/scad/3D/misc/polyhedrons-touch-edge.scad @@ -1,3 +1,5 @@ +// Two cubes sharing an edge, +// but with a manifold topology (the coincident vertices are distinct). polyhedron( points=[ [0, 0, 0], diff --git a/tests/data/scad/3D/misc/polyhedrons-touch-face-nonmanifold.scad b/tests/data/scad/3D/misc/polyhedrons-touch-face-nonmanifold.scad index 4cdb11848ed..f5b6fa17dfc 100644 --- a/tests/data/scad/3D/misc/polyhedrons-touch-face-nonmanifold.scad +++ b/tests/data/scad/3D/misc/polyhedrons-touch-face-nonmanifold.scad @@ -1,3 +1,5 @@ +// Two cubes sharing a face. +// This is a non-manifold object since the collapsed vertices are not distinct. polyhedron( points=[ [0, 0, 0],