From ac1d862a3e224cbe2d5ca1aa97b25fc8fa441336 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Fri, 16 Feb 2024 12:11:43 -0500 Subject: [PATCH 1/2] Toponaming/Part: Transfer makEPipeShell --- src/Mod/Part/App/TopoShape.h | 51 ++++++++++++++++++++++++- src/Mod/Part/App/TopoShapeExpansion.cpp | 1 + 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index 75d0b8f597f7..f6437ddb1e2d 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -134,8 +134,7 @@ enum class LinearizeFace linearizeFaces }; -enum class LinearizeEdge -{ +enum class LinearizeEdge { noEdges, linearizeEdges }; @@ -158,6 +157,29 @@ enum class IsClosed closed }; +/// Option to manage discontinuity in pipe sweeping +enum class TransitionMode { + /** Discontinuities are treated by modification of the sweeping mode. + * The pipe is "transformed" at the fractures of the spine. This mode + * assumes building a self-intersected shell. + */ + Transformed, + + /** Discontinuities are treated like right corner. Two pieces of the + * pipe corresponding to two adjacent segments of the spine are + * extended and intersected at a fracture of the spine. + */ + RightCorner, + + /** Discontinuities are treated like round corner. The corner is + * treated as rotation of the profile around an axis which passes + * through the point of the spine's fracture. This axis is based on + * cross product of directions tangent to the adjacent segments of the + * spine at their common point. + */ + RoundCorner +}; + /** The representation for a CAD Shape */ // NOLINTNEXTLINE cppcoreguidelines-special-member-functions @@ -720,6 +742,31 @@ class PartExport TopoShape: public Data::ComplexGeoData } //@} + /* Make a shell or solid by sweeping profile wire along a spine + * + * @params sources: source shapes. The first shape is used as spine. The + * rest are used as section profiles. Can be of type edge, + * wire, face, or compound of those. For face, only outer + * wire is used. + * @params makeSolid: whether to create solid + * @param isFrenet: if true, then assume the profiles transition is Frenet, + * or else a corrected Frenet trihedron is used. + * @param transition: @sa TransitionMode + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * @param tol3d: 3D tolerance + * @param tolBound: boundary tolerance + * @param tolAngular: angular tolerance + * + * @return The original content of this TopoShape is discarded and replaced + * with the new shape. The function returns the TopoShape itself as + * a self reference so that multiple operations can be carried out + * for the same shape in the same line of code. + */ + TopoShape &makEPipeShell(const std::vector &sources, const Standard_Boolean makeSolid, + const Standard_Boolean isFrenet, TransitionMode transition=TransitionMode::Transformed, + const char *op=nullptr, double tol3d=0.0, double tolBound=0.0, double tolAngluar=0.0); + /** Try to simplify geometry of any linear/planar subshape to line/plane * * @return Return true if the shape is modified diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index 4a3d3eb431be..e1dec657bc1e 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -2592,6 +2592,7 @@ TopoShape& TopoShape::makeElementRefine(const TopoShape& shape, const char* op, return *this; } + /** * Encode and set an element name in the elementMap. If a hasher is defined, apply it to the name. * From abde135776d7d0e2690c96b6148c2f900d7d182d Mon Sep 17 00:00:00 2001 From: bgbsww Date: Fri, 16 Feb 2024 13:11:38 -0500 Subject: [PATCH 2/2] Toponaming/Part: Cleanup makeElementPipeShell, add tests, and some delinting --- src/Mod/Part/App/TopoShape.h | 33 +++- src/Mod/Part/App/TopoShapeExpansion.cpp | 181 +++++++++++++----- tests/src/Mod/Part/App/TopoShapeExpansion.cpp | 25 +++ tests/src/Mod/Part/App/TopoShapeMakeShape.cpp | 2 +- 4 files changed, 179 insertions(+), 62 deletions(-) diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index f6437ddb1e2d..4bdd9243c315 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -95,8 +95,8 @@ class PartExport ShapeSegment: public Data::Segment TYPESYSTEM_HEADER_WITH_OVERRIDE(); public: - explicit ShapeSegment(const TopoDS_Shape& ShapeIn) - : Shape(ShapeIn) + explicit ShapeSegment(TopoDS_Shape ShapeIn) + : Shape(std::move(ShapeIn)) {} ShapeSegment() = default; std::string getName() const override; @@ -134,7 +134,8 @@ enum class LinearizeFace linearizeFaces }; -enum class LinearizeEdge { +enum class LinearizeEdge +{ noEdges, linearizeEdges }; @@ -158,7 +159,8 @@ enum class IsClosed }; /// Option to manage discontinuity in pipe sweeping -enum class TransitionMode { +enum class TransitionMode +{ /** Discontinuities are treated by modification of the sweeping mode. * The pipe is "transformed" at the fractures of the spine. This mode * assumes building a self-intersected shell. @@ -176,10 +178,16 @@ enum class TransitionMode { * through the point of the spine's fracture. This axis is based on * cross product of directions tangent to the adjacent segments of the * spine at their common point. - */ + */ RoundCorner }; +enum class MakeSolid +{ + noSolid, + makeSolid +}; + /** The representation for a CAD Shape */ // NOLINTNEXTLINE cppcoreguidelines-special-member-functions @@ -374,11 +382,11 @@ class PartExport TopoShape: public Data::ComplexGeoData /// Returns true if the expansion of the shape is infinite, false otherwise bool isInfinite() const; /// Checks whether the shape is a planar face - bool isPlanar(double tol = 1.0e-7) const; + bool isPlanar(double tol = 1.0e-7) const; // NOLINT /// Check if this shape is a single linear edge, works on BSplineCurve and BezierCurve bool isLinearEdge(Base::Vector3d *dir = nullptr, Base::Vector3d *base = nullptr) const; /// Check if this shape is a single planar face, works on BSplineSurface and BezierSurface - bool isPlanarFace(double tol=1e-7) const; + bool isPlanarFace(double tol=1e-7) const; // NOLINT //@} /** @name Boolean operation*/ @@ -763,9 +771,14 @@ class PartExport TopoShape: public Data::ComplexGeoData * a self reference so that multiple operations can be carried out * for the same shape in the same line of code. */ - TopoShape &makEPipeShell(const std::vector &sources, const Standard_Boolean makeSolid, - const Standard_Boolean isFrenet, TransitionMode transition=TransitionMode::Transformed, - const char *op=nullptr, double tol3d=0.0, double tolBound=0.0, double tolAngluar=0.0); + TopoShape& makeElementPipeShell(const std::vector& sources, + const MakeSolid makeSolid, + const Standard_Boolean isFrenet, + TransitionMode transition = TransitionMode::Transformed, + const char* op = nullptr, + double tol3d = 0.0, + double tolBound = 0.0, + double tolAngluar = 0.0); /** Try to simplify geometry of any linear/planar subshape to line/plane * diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index e1dec657bc1e..19c0472c9046 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -249,27 +249,27 @@ std::vector TopoShape::findSubShapesWithSharedVertex(const TopoShape& return res; } double tol2 = tol * tol; - int i = 0; + int index = 0; TopAbs_ShapeEnum shapeType = subshape.shapeType(); switch (shapeType) { case TopAbs_VERTEX: // Vertex search will do comparison with tolerance to account for // rounding error inccured through transformation. - for (auto& s : getSubTopoShapes(TopAbs_VERTEX)) { - ++i; - if (BRep_Tool::Pnt(TopoDS::Vertex(s.getShape())) + for (auto& shape : getSubTopoShapes(TopAbs_VERTEX)) { + ++index; + if (BRep_Tool::Pnt(TopoDS::Vertex(shape.getShape())) .SquareDistance(BRep_Tool::Pnt(TopoDS::Vertex(subshape.getShape()))) <= tol2) { if (names) { - names->push_back(std::string("Vertex") + std::to_string(i)); + names->push_back(std::string("Vertex") + std::to_string(index)); } - res.push_back(s); + res.push_back(shape); } } break; case TopAbs_EDGE: case TopAbs_FACE: { - std::unique_ptr g; + std::unique_ptr geom; bool isLine = false; bool isPlane = false; @@ -284,16 +284,16 @@ std::vector TopoShape::findSubShapesWithSharedVertex(const TopoShape& } if (vertices.empty() || checkGeometry == CheckGeometry::checkGeometry) { - g = Geometry::fromShape(subshape.getShape()); - if (!g) { + geom = Geometry::fromShape(subshape.getShape()); + if (!geom) { return res; } if (shapeType == TopAbs_EDGE) { - isLine = (g->isDerivedFrom(GeomLine::getClassTypeId()) - || g->isDerivedFrom(GeomLineSegment::getClassTypeId())); + isLine = (geom->isDerivedFrom(GeomLine::getClassTypeId()) + || geom->isDerivedFrom(GeomLineSegment::getClassTypeId())); } else { - isPlane = g->isDerivedFrom(GeomPlane::getClassTypeId()); + isPlane = geom->isDerivedFrom(GeomPlane::getClassTypeId()); } } @@ -319,7 +319,7 @@ std::vector TopoShape::findSubShapesWithSharedVertex(const TopoShape& return false; } } - else if (!g2 || !g2->isSame(*g, tol, atol)) { + else if (!g2 || !g2->isSame(*geom, tol, atol)) { return false; } return true; @@ -328,13 +328,13 @@ std::vector TopoShape::findSubShapesWithSharedVertex(const TopoShape& if (vertices.empty()) { // Probably an infinite shape, so we have to search by geometry int idx = 0; - for (auto& s : getSubTopoShapes(shapeType)) { + for (auto& shape : getSubTopoShapes(shapeType)) { ++idx; - if (!s.countSubShapes(TopAbs_VERTEX) && compareGeometry(s, true)) { + if (!shape.countSubShapes(TopAbs_VERTEX) && compareGeometry(shape, true)) { if (names) { names->push_back(shapeName(shapeType) + std::to_string(idx)); } - res.push_back(s); + res.push_back(shape); } } break; @@ -345,18 +345,18 @@ std::vector TopoShape::findSubShapesWithSharedVertex(const TopoShape& // * Find the ancestor shape of the found vertex // * Compare each vertex of the ancestor shape and the input shape // * Perform geometry comparison of the ancestor and input shape. - // * For face, perform addition geometry comparison of each edges. + // * For face, perform addition geometry comparison of each edge. std::unordered_set shapeSet; - for (auto& v : findSubShapesWithSharedVertex(vertices[0], nullptr, checkGeometry, tol, atol)) { - for (auto idx : findAncestors(v.getShape(), shapeType)) { - auto s = getSubTopoShape(shapeType, idx); - if (!shapeSet.insert(s).second) { + for (auto& vert : findSubShapesWithSharedVertex(vertices[0], nullptr, checkGeometry, tol, atol)) { + for (auto idx : findAncestors(vert.getShape(), shapeType)) { + auto shape = getSubTopoShape(shapeType, idx); + if (!shapeSet.insert(shape).second) { continue; } TopoShape otherWire; std::vector otherVertices; if (shapeType == TopAbs_FACE) { - otherWire = s.splitWires(); + otherWire = shape.splitWires(); if (wire.countSubShapes(TopAbs_EDGE) != otherWire.countSubShapes(TopAbs_EDGE)) { continue; @@ -364,25 +364,25 @@ std::vector TopoShape::findSubShapesWithSharedVertex(const TopoShape& otherVertices = otherWire.getSubShapes(TopAbs_VERTEX); } else { - otherVertices = s.getSubShapes(TopAbs_VERTEX); + otherVertices = shape.getSubShapes(TopAbs_VERTEX); } if (otherVertices.size() != vertices.size()) { continue; } - if (checkGeometry == CheckGeometry::checkGeometry && !compareGeometry(s, false)) { + if (checkGeometry == CheckGeometry::checkGeometry && !compareGeometry(shape, false)) { continue; } - unsigned i = 0; + unsigned ind = 0; bool matched = true; - for (auto& v : vertices) { + for (auto& vertex : vertices) { bool found = false; - for (unsigned j = 0; j < otherVertices.size(); ++j) { - auto& v1 = otherVertices[i]; - if (++i == otherVertices.size()) { - i = 0; + for (unsigned inner = 0; inner < otherVertices.size(); ++inner) { + auto& vertex1 = otherVertices[ind]; + if (++ind == otherVertices.size()) { + ind = 0; } - if (BRep_Tool::Pnt(TopoDS::Vertex(v)) - .SquareDistance(BRep_Tool::Pnt(TopoDS::Vertex(v1))) + if (BRep_Tool::Pnt(TopoDS::Vertex(vertex)) + .SquareDistance(BRep_Tool::Pnt(TopoDS::Vertex(vertex1))) <= tol2) { found = true; break; @@ -403,22 +403,22 @@ std::vector TopoShape::findSubShapesWithSharedVertex(const TopoShape& auto otherEdges = otherWire.getSubShapes(TopAbs_EDGE); std::vector> geos; geos.resize(otherEdges.size()); - bool matched = true; + bool matched2 = true; unsigned i = 0; auto edges = wire.getSubShapes(TopAbs_EDGE); - for (auto& e : edges) { - std::unique_ptr g(Geometry::fromShape(e)); - if (!g) { - matched = false; + for (auto& edge : edges) { + std::unique_ptr geom2(Geometry::fromShape(edge)); + if (!geom2) { + matched2 = false; break; } - bool isLine = false; + bool isLine2 = false; gp_Pnt pt1, pt2; - if (g->isDerivedFrom(GeomLine::getClassTypeId()) - || g->isDerivedFrom(GeomLineSegment::getClassTypeId())) { - pt1 = BRep_Tool::Pnt(TopExp::FirstVertex(TopoDS::Edge(e))); - pt2 = BRep_Tool::Pnt(TopExp::LastVertex(TopoDS::Edge(e))); - isLine = true; + if (geom2->isDerivedFrom(GeomLine::getClassTypeId()) + || geom2->isDerivedFrom(GeomLineSegment::getClassTypeId())) { + pt1 = BRep_Tool::Pnt(TopExp::FirstVertex(TopoDS::Edge(edge))); + pt2 = BRep_Tool::Pnt(TopExp::LastVertex(TopoDS::Edge(edge))); + isLine2 = true; } // We will tolerate on edge reordering bool found = false; @@ -434,7 +434,7 @@ std::vector TopoShape::findSubShapesWithSharedVertex(const TopoShape& break; } } - if (isLine) { + if (isLine2) { if (g1->isDerivedFrom(GeomLine::getClassTypeId()) || g1->isDerivedFrom(GeomLineSegment::getClassTypeId())) { auto p1 = @@ -452,24 +452,24 @@ std::vector TopoShape::findSubShapesWithSharedVertex(const TopoShape& continue; } - if (g1->isSame(*g, tol, atol)) { + if (g1->isSame(*geom2, tol, atol)) { found = true; break; } } if (!found) { - matched = false; + matched2 = false; break; } } - if (!matched) { + if (!matched2) { continue; } } if (names) { names->push_back(shapeName(shapeType) + std::to_string(idx)); } - res.push_back(s); + res.push_back(shape); } } break; @@ -1880,6 +1880,77 @@ static std::vector prepareProfiles(const std::vector& shap return ret; } +TopoShape& TopoShape::makeElementPipeShell(const std::vector& shapes, + const MakeSolid make_solid, + const Standard_Boolean isFrenet, + TransitionMode transition, + const char* op, + double tol3d, + double tolBound, + double tolAngular) +{ + if (!op) { + op = Part::OpCodes::PipeShell; + } + + if (shapes.size() < 2) { + FC_THROWM(Base::CADKernelError, "Not enough input shapes"); + } + + auto spine = shapes.front().makeElementWires(); + if (spine.isNull()) { + FC_THROWM(NullShapeException, "Null input shape"); + } + if (spine.getShape().ShapeType() != TopAbs_WIRE) { + FC_THROWM(Base::CADKernelError, "Spine shape cannot form a single wire"); + } + + BRepOffsetAPI_MakePipeShell mkPipeShell(TopoDS::Wire(spine.getShape())); + BRepBuilderAPI_TransitionMode transMode; + switch (transition) { + case TransitionMode::RightCorner: + transMode = BRepBuilderAPI_RightCorner; + break; + case TransitionMode::RoundCorner: + transMode = BRepBuilderAPI_RoundCorner; + break; + default: + transMode = BRepBuilderAPI_Transformed; + break; + } + mkPipeShell.SetMode(isFrenet); + mkPipeShell.SetTransitionMode(transMode); + if (tol3d != 0.0 || tolBound != 0.0 || tolAngular != 0.0) { + if (tol3d == 0.0) { + tol3d = 1e-4; + } + if (tolBound == 0.0) { + tolBound = 1e-4; + } + if (tolAngular == 0.0) { + tolAngular = 1e-2; + } + mkPipeShell.SetTolerance(tol3d, tolBound, tolAngular); + } + + for (auto& sh : prepareProfiles(shapes, 1)) { + mkPipeShell.Add(sh.getShape()); + } + + if (!mkPipeShell.IsReady()) { + FC_THROWM(Base::CADKernelError, "shape is not ready to build"); + } + else { + mkPipeShell.Build(); + } + + if (make_solid == MakeSolid::makeSolid) { + mkPipeShell.MakeSolid(); + } + + return makeElementShape(mkPipeShell, shapes, op); +} + TopoShape& TopoShape::makeElementWires(const std::vector& shapes, const char* op, double tol, @@ -2348,6 +2419,14 @@ TopoShape& TopoShape::makeElementShape(BRepPrimAPI_MakeHalfSpace& mkShape, return makeShapeWithElementMap(mkShape.Solid(), MapperMaker(mkShape), {source}, op); } +TopoShape &TopoShape::makeElementShape(BRepOffsetAPI_MakePipeShell &mkShape, + const std::vector &source, const char *op) { + if (!op) { + op = Part::OpCodes::PipeShell; + } + return makeShapeWithElementMap(mkShape.Shape(), MapperMaker(mkShape), source, op); +} + TopoShape& TopoShape::makeElementLoft(const std::vector& shapes, IsSolid isSolid, IsRuled isRuled, @@ -2674,8 +2753,8 @@ TopoShape TopoShape::splitWires(std::vector* inner, SplitWireReorient { // ShapeAnalysis::OuterWire() is un-reliable for some reason. OCC source // code shows it works by creating face using each wire, and then test using - // BRepTopAdaptor_FClass2d::PerformInfinitePoint() to check if it is an out - // bound wire. And practice shows it sometimes returns the incorrect + // BRepTopAdaptor_FClass2d::PerformInfinitePoint() to check if it is an + // outbound wire. And practice shows it sometimes returns the incorrect // result. Need more investigation. Note that this may be related to // unreliable solid face orientation // (https://forum.freecadweb.org/viewtopic.php?p=446006#p445674) @@ -3258,7 +3337,7 @@ TopoShape& TopoShape::makeElementBoolean(const char* maker, *this = inputs[0]; if (shapes.size() == 1) { // _shapes has fewer items than shapes due to compound expansion. - // Only warn if the caller paseses one shape. + // Only warn if the caller passes one shape. FC_WARN("Boolean operation with only one shape input"); } return *this; diff --git a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp index 875f72ce5401..64bf46514092 100644 --- a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -1229,4 +1229,29 @@ TEST_F(TopoShapeExpansionTest, makeElementLoft) EXPECT_EQ(elements[IndexedName("Edge", 1)], MappedName("Edge1;LFT;:H1:4,E")); } +TEST_F(TopoShapeExpansionTest, makeElementPipeShell) +{ + // Arrange + const float Len = 5; + const float Wid = 5; + auto [face1, wire1, edge1, edge2, edge3, edge4] = CreateRectFace(Len, Wid); + auto edge5 = BRepBuilderAPI_MakeEdge(gp_Pnt(0.0, 0.0, 0.0), gp_Pnt(0.0, 0.0, -8.0)).Edge(); + auto wire2 = BRepBuilderAPI_MakeWire({edge5}).Wire(); + TopoShape face1ts {face1, 1L}; + TopoShape edge5ts {edge5, 2L}; + std::vector shapes = {face1ts, edge5ts}; + // Act + auto& topoShape = (new TopoShape())->makeElementPipeShell(shapes, MakeSolid::noSolid, false); + auto elements = elementMap((topoShape)); + // Assert that we haven't broken the basic Loft functionality + EXPECT_EQ(topoShape.countSubElements("Wire"), 4); + EXPECT_FLOAT_EQ(getArea(topoShape.getShape()), 160); + EXPECT_FLOAT_EQ(getVolume(topoShape.getShape()), 133.33334); + // Assert that we're creating a correct element map + EXPECT_TRUE(topoShape.getMappedChildElements().empty()); + EXPECT_EQ(elements.size(), 24); + EXPECT_EQ(elements.count(IndexedName("Edge", 1)), 1); + EXPECT_EQ(elements[IndexedName("Edge", 1)], MappedName("Vertex1;:G;PSH;:H2:7,E")); +} + // NOLINTEND(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers) diff --git a/tests/src/Mod/Part/App/TopoShapeMakeShape.cpp b/tests/src/Mod/Part/App/TopoShapeMakeShape.cpp index 559768402bde..bc291a6e3e50 100644 --- a/tests/src/Mod/Part/App/TopoShapeMakeShape.cpp +++ b/tests/src/Mod/Part/App/TopoShapeMakeShape.cpp @@ -97,7 +97,7 @@ TEST_F(TopoShapeMakeShapeTests, thruSections) // Assert EXPECT_EQ(elements.size(), 24); EXPECT_EQ(elements.count(IndexedName("Vertex", 1)), 1); - EXPECT_EQ(elements[IndexedName("Vertex", 1)], MappedName("Vertex1;TRU;:H1:4,V")); + EXPECT_EQ(elements[IndexedName("Vertex", 1)], MappedName("Vertex1;TRU;:H1:4,V")); // NOLINT EXPECT_EQ(getVolume(result.getShape()), 4); }