diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index a15c929dc5..9433f18a17 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -194,6 +194,14 @@ enum class MapElement map }; +/// Defines how to fill the holes that may appear after offset two adjacent faces +enum class JoinType +{ + Arc, + Tangent, + Intersection, +}; + /** The representation for a CAD Shape */ // NOLINTNEXTLINE cppcoreguidelines-special-member-functions @@ -780,6 +788,53 @@ public: } //@} + /** Make a hollowed solid by removing some faces from a given solid + * + * @param source: input shape + * @param faces: list of faces to remove, must be sub shape of the input shape + * @param offset: thickness of the walls + * @param tol: tolerance criterion for coincidence in generated shapes + * @param intersection: whether to check intersection in all generated parallel. + * @param selfInter: whether to eliminate self intersection. + * @param offsetMode: defines the construction type of parallels applied to free edges + * @param join: join type. Only support JoinType::Arc and JoinType::Intersection. + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * + * @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 &makeElementThickSolid(const TopoShape &source, const std::vector &faces, + double offset, double tol, bool intersection = false, bool selfInter = false, + short offsetMode = 0, JoinType join = JoinType::Arc, const char *op=nullptr); + + /** Make a hollowed solid by removing some faces from a given solid + * + * @param source: input shape + * @param faces: list of faces to remove, must be sub shape of the input shape + * @param offset: thickness of the walls + * @param tol: tolerance criterion for coincidence in generated shapes + * @param intersection: whether to check intersection in all generated parallel + * (OCCT document states the option is not fully implemented) + * @param selfInter: whether to eliminate self intersection + * (OCCT document states the option is not implemented) + * @param offsetMode: defines the construction type of parallels applied to free edges + * (OCCT document states the option is not implemented) + * @param join: join type. Only support JoinType::Arc and JoinType::Intersection. + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * + * @return Return the generated new shape. The TopoShape itself is not modified. + */ + TopoShape makeElementThickSolid(const std::vector &faces, + double offset, double tol, bool intersection = false, bool selfInter = false, + short offsetMode = 0, JoinType join = JoinType::Arc, const char *op=nullptr) const { + return TopoShape(0,Hasher).makeElementThickSolid(*this,faces,offset,tol,intersection,selfInter, + offsetMode,join,op); + } + /* Make a shell or solid by sweeping profile wire along a spine * * @params sources: source shapes. The first shape is used as spine. The diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index b1f739be01..f6b7141f65 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -52,6 +52,7 @@ #include #include #include +#include #include #include #include @@ -2072,6 +2073,76 @@ TopoShape& TopoShape::makeElementPipeShell(const std::vector& shapes, return makeElementShape(mkPipeShell, shapes, op); } +TopoShape& TopoShape::makeElementThickSolid(const TopoShape& shape, + const std::vector& faces, + double offset, + double tol, + bool intersection, + bool selfInter, + short offsetMode, + JoinType join, + const char* op) +{ + if (!op) { + op = Part::OpCodes::Thicken; + } + + // we do not offer tangent join type + switch (join) { + case JoinType::Arc: + case JoinType::Intersection: + break; + default: + join = JoinType::Intersection; + } + + if (shape.isNull()) { + FC_THROWM(NullShapeException, "Null shape"); + } + + if (faces.empty()) { + FC_THROWM(NullShapeException, "Null input shape"); + } + + if (fabs(offset) <= 2 * tol) { + *this = shape; + return *this; + } + + TopTools_ListOfShape remFace; + for (auto& face : faces) { + if (face.isNull()) { + FC_THROWM(NullShapeException, "Null input shape"); + } + if (!shape.findShape(face.getShape())) { + FC_THROWM(Base::CADKernelError, "face does not belong to the shape"); + } + remFace.Append(face.getShape()); + } +#if OCC_VERSION_HEX < 0x070200 + BRepOffsetAPI_MakeThickSolid mkThick(shape.getShape(), + remFace, + offset, + tol, + BRepOffset_Mode(offsetMode), + intersection ? Standard_True : Standard_False, + selfInter ? Standard_True : Standard_False, + GeomAbs_JoinType(join)); +#else + BRepOffsetAPI_MakeThickSolid mkThick; + mkThick.MakeThickSolidByJoin(shape.getShape(), + remFace, + offset, + tol, + BRepOffset_Mode(offsetMode), + intersection ? Standard_True : Standard_False, + selfInter ? Standard_True : Standard_False, + GeomAbs_JoinType(join)); +#endif + return makeElementShape(mkThick, shape, op); +} + + TopoShape& TopoShape::makeElementWires(const std::vector& shapes, const char* op, double tol, diff --git a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp index c8562e29ab..aa3bb21940 100644 --- a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -1231,15 +1231,24 @@ TEST_F(TopoShapeExpansionTest, makeElementLinearizeFace) TEST_F(TopoShapeExpansionTest, makeElementRuledSurfaceEdges) { // Arrange - auto [cube1, cube2] = CreateTwoCubes(); - TopoShape cube1TS {cube1, 1L}; - std::vector subEdges = cube1TS.getSubTopoShapes(TopAbs_EDGE); - std::vector shapes2 = {subEdges[0], subEdges[1]}; + auto edge1 = BRepBuilderAPI_MakeEdge(gp_Pnt(0.0, 0.0, 0.0), gp_Pnt(0.0, 0.0, 8.0)).Edge(); + auto edge2 = BRepBuilderAPI_MakeEdge(gp_Pnt(2.5, 0.0, 0.0), gp_Pnt(2.5, 0.0, 8.0)).Edge(); + TopoShape edge1ts {edge1, 2L}; + TopoShape edge2ts {edge2, 3L}; + TopoShape topoShape {1L}; // Act - TopoShape result2 = cube1TS.makeElementRuledSurface(shapes2, 0); // TODO: direction as enum? + topoShape.makeElementRuledSurface({edge1ts, edge2ts}, 0); // TODO: orientation as enum? + auto elements = elementMap(topoShape); // Assert - EXPECT_EQ(result2.countSubElements("Wire"), 1); - EXPECT_FLOAT_EQ(getArea(result2.getShape()), 0.32953611); + EXPECT_EQ(topoShape.countSubElements("Wire"), 1); + EXPECT_FLOAT_EQ(getArea(topoShape.getShape()), 20); + // Assert that we're creating a correct element map + EXPECT_TRUE(topoShape.getMappedChildElements().empty()); + // TODO: Revisit these when resetElementMap() is fully worked through. Suspect that last loop + // of makeElementRuledSurface is dependent on this to create elementMaps. + // 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")); } TEST_F(TopoShapeExpansionTest, makeElementRuledSurfaceWires) @@ -1248,12 +1257,19 @@ TEST_F(TopoShapeExpansionTest, makeElementRuledSurfaceWires) auto [cube1, cube2] = CreateTwoCubes(); TopoShape cube1TS {cube1, 1L}; std::vector subWires = cube1TS.getSubTopoShapes(TopAbs_WIRE); - std::vector shapes = {subWires[0], subWires[1]}; // Act - TopoShape result = cube1TS.makeElementRuledSurface(shapes, 0); // TODO: direction as enum? + cube1TS.makeElementRuledSurface({subWires[0], subWires[1]}, 0); // TODO: orientation as enum? + auto elements = elementMap(cube1TS); // Assert - EXPECT_EQ(result.countSubElements("Wire"), 4); - EXPECT_FLOAT_EQ(getArea(result.getShape()), 2.023056); + EXPECT_EQ(cube1TS.countSubElements("Wire"), 4); + EXPECT_FLOAT_EQ(getArea(cube1TS.getShape()), 2.023056); + // Assert that we're creating a correct element map + EXPECT_TRUE(cube1TS.getMappedChildElements().empty()); + // TODO: Revisit these when resetElementMap() is fully worked through. Suspect that last loop + // of makeElementRuledSurface is dependent on this to create elementMaps. + // 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")); } TEST_F(TopoShapeExpansionTest, makeElementLoft) @@ -1326,4 +1342,26 @@ TEST_F(TopoShapeExpansionTest, makeElementPipeShell) EXPECT_EQ(elements[IndexedName("Edge", 1)], MappedName("Vertex1;:G;PSH;:H2:7,E")); } +TEST_F(TopoShapeExpansionTest, makeElementThickSolid) +{ + // Arrange + auto [cube1, cube2] = CreateTwoCubes(); + TopoShape cube1TS {cube1, 1L}; + std::vector subFaces = cube1TS.getSubTopoShapes(TopAbs_FACE); + subFaces[0].Tag = 2L; + subFaces[1].Tag = 3L; + std::vector shapes = {subFaces[0], subFaces[1]}; + // Act + cube1TS.makeElementThickSolid(cube1TS, shapes, 0.1, 1e-07); + auto elements = elementMap(cube1TS); + // Assert + EXPECT_EQ(cube1TS.countSubElements("Wire"), 16); + EXPECT_FLOAT_EQ(getArea(cube1TS.getShape()), 9.4911509); + // Assert that we're creating a correct element map + EXPECT_TRUE(cube1TS.getMappedChildElements().empty()); + EXPECT_EQ(elements.size(), 74); + EXPECT_EQ(elements.count(IndexedName("Edge", 1)), 1); + EXPECT_EQ(elements[IndexedName("Edge", 1)], MappedName("Edge11;THK;:H1:4,E")); +} + // NOLINTEND(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers)