diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index df56358cc9..a3c3f94f33 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -745,7 +745,7 @@ public: /** Refine the input shape by merging faces/edges that share the same geometry * - * @param source: input shape + * @param shape: input shape * @param op: optional string to be encoded into topo naming for indicating * the operation * @param no_fail: if throwException, throw exception if failed to refine. Or else, @@ -756,7 +756,7 @@ public: * itself as a self reference so that multiple operations can be * carried out for the same shape in the same line of code. */ - TopoShape& makeElementRefine(const TopoShape& source, + TopoShape& makeElementRefine(const TopoShape& shape, const char* op = nullptr, RefineFail no_fail = RefineFail::throwException); @@ -865,6 +865,83 @@ public: double tolBound = 0.0, double tolAngluar = 0.0); + /** Make shape using generalized fusion and return the modified sub shapes + * + * @param sources: the source shapes + * @param modified: return the modified sub shapes + * @param tol: tolerance + * @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& makeElementGeneralFuse(const std::vector& sources, + std::vector>& modified, + double tol = 0, + const char* op = nullptr); + + /** Make a fusion of input shapes + * + * @param sources: the source shapes + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * @param tol: tolerance for the fusion + * + * @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& makeElementFuse(const std::vector& sources, + const char* op = nullptr, + double tol = 0); + /** Make a fusion of this shape and an input shape + * + * @param source: the source shape + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * @param tol: tolerance for the fusion + * + * @return Return the new shape. The TopoShape itself is not modified. + */ + TopoShape + makeElementFuse(const TopoShape& source, const char* op = nullptr, double tol = 0) const + { + return TopoShape(0, Hasher).makeElementFuse({*this, source}, op, tol); + } + + /** Make a boolean cut of this shape with an input shape + * + * @param source: the source shape + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * @param tol: tolerance for the fusion + * + * @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& + makeElementCut(const std::vector& sources, const char* op = nullptr, double tol = 0); + /** Make a boolean cut of this shape with an input shape + * + * @param source: the source shape + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * @param tol: tolerance for the fusion + * + * @return Return the new shape. The TopoShape itself is not modified. + */ + TopoShape + makeElementCut(const TopoShape& source, const char* op = nullptr, double tol = 0) const + { + return TopoShape(0, Hasher).makeElementCut({*this, source}, op, tol); + } + /** Try to simplify geometry of any linear/planar subshape to line/plane * * @return Return true if the shape is modified @@ -1315,6 +1392,22 @@ public: return makeElementShellFromWires(getSubTopoShapes(TopAbs_WIRE), silent, op); } + /** Make a planar face with the input wires or edges + * + * @param shapes: input shapes. Can be either edges, wires, or compound of + * those two types + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * @param maker: optional type name of the face maker. If not given, + * default to "Part::FaceMakerBullseye" + * @param plane: optional plane of the face. + * + * @return The function creates a planar face. The original content of this + * TopoShape is discarded and replaced with the new shape. The + * function returns the TopoShape itself as a reference so that + * multiple operations can be carried out for the same shape in the + * same line of code. + */ TopoShape& makeElementFace(const std::vector& shapes, const char* op = nullptr, const char* maker = nullptr, diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index cd5f6b9bdd..a68ff8a85d 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -1220,7 +1220,7 @@ TopoShape& TopoShape::makeShapeWithElementMap(const TopoDS_Shape& shape, } int newShapeIndex = newInfo.find(newShape); if (newShapeIndex == 0) { - // This warning occurs in makERevolve. It generates + // This warning occurs in makeElementRevolve. It generates // some shape from a vertex that never made into the // final shape. There may be incomingShape cases there. if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { @@ -2459,11 +2459,7 @@ TopoShape::makeElementCopy(const TopoShape& shape, const char* op, bool copyGeom } TopoShape tmp(shape); -#if OCC_VERSION_HEX >= 0x070000 tmp.setShape(BRepBuilderAPI_Copy(shape.getShape(), copyGeom, copyMesh).Shape(), false); -#else - tmp.setShape(BRepBuilderAPI_Copy(shape.getShape()).Shape(), false); -#endif if (op || (shape.Tag && shape.Tag != Tag)) { setShape(tmp._Shape); initCache(); @@ -2544,6 +2540,74 @@ struct MapperThruSections: MapperMaker } }; +TopoShape& TopoShape::makeElementGeneralFuse(const std::vector& _shapes, + std::vector>& modifies, + double tol, + const char* op) +{ + if (!op) { + op = Part::OpCodes::GeneralFuse; + } + + if (_shapes.empty()) { + FC_THROWM(NullShapeException, "Null input shape"); + } + + std::vector shapes(_shapes); + + BRepAlgoAPI_BuilderAlgo mkGFA; + mkGFA.SetRunParallel(true); + TopTools_ListOfShape GFAArguments; + for (auto& shape : shapes) { + if (shape.isNull()) { + FC_THROWM(NullShapeException, "Null input shape"); + } + if (tol > 0.0) { + // workaround for http://dev.opencascade.org/index.php?q=node/1056#comment-520 + shape = shape.makeElementCopy(); + } + GFAArguments.Append(shape.getShape()); + } + mkGFA.SetArguments(GFAArguments); + if (tol > 0.0) { + mkGFA.SetFuzzyValue(tol); + } +#if OCC_VERSION_HEX >= 0x070000 + mkGFA.SetNonDestructive(Standard_True); +#endif + mkGFA.Build(); + if (!mkGFA.IsDone()) { + FC_THROWM(Base::CADKernelError, "GeneralFuse failed"); + } + makeElementShape(mkGFA, shapes, op); + modifies.resize(shapes.size()); + int index = 0; + for (auto& shape : shapes) { + auto& mod = modifies[index++]; + for (TopTools_ListIteratorOfListOfShape it(mkGFA.Modified(shape.getShape())); it.More(); + it.Next()) { + TopoShape res(Tag); + res.setShape(it.Value()); + mod.push_back(res); + } + mapSubElementsTo(mod); + } + return *this; +} + +TopoShape& +TopoShape::makeElementFuse(const std::vector& shapes, const char* op, double tol) +{ + return makeElementBoolean(Part::OpCodes::Fuse, shapes, op, tol); +} + +TopoShape& +TopoShape::makeElementCut(const std::vector& shapes, const char* op, double tol) +{ + return makeElementBoolean(Part::OpCodes::Cut, shapes, op, tol); +} + + TopoShape& TopoShape::makeElementShape(BRepBuilderAPI_MakeShape& mkShape, const TopoShape& source, const char* op) @@ -2734,7 +2798,7 @@ TopoShape& TopoShape::makeElementFace(const std::vector& shapes, // Update: one of the cause is related to OCC bug in // BRepBuilder_FindPlane, A possible call sequence is, // - // makEOffset2D() -> TopoShape::findPlane() -> BRepLib_FindSurface + // makeElementOffset2D() -> TopoShape::findPlane() -> BRepLib_FindSurface // // See code comments in findPlane() for the description of the bug and // work around. diff --git a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp index 994c4c0e8c..4be1750e9c 100644 --- a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -1148,10 +1148,29 @@ TEST_F(TopoShapeExpansionTest, makeElementShellIntersecting) EXPECT_THROW(topoShape1.makeElementShell(false, nullptr), Base::CADKernelError); } -// TEST_F(TopoShapeExpansionTest, makeElementShellFromWires) -// { -// // Arrange -// } +TEST_F(TopoShapeExpansionTest, makeElementShellFromWires) +{ + // Arrange + auto [cube1, cube2] = CreateTwoCubes(); + TopoShape topoShape {cube1}; + std::vector shapes; + for (const auto& face : topoShape.getSubShapes(TopAbs_WIRE)) { + shapes.emplace_back(face); + } + // Act + TopoShape topoShape1 {1L}; + topoShape1.makeElementCompound(shapes, "D"); + // Assert + TopoShape result = topoShape1.makeElementShellFromWires(shapes); +#if OCC_VERSION_HEX >= 0x070400 + EXPECT_EQ(result.getShape().NbChildren(), 6); +#endif + EXPECT_EQ(result.countSubElements("Vertex"), 8); + EXPECT_EQ(result.countSubElements("Edge"), 32); + EXPECT_EQ(result.countSubElements("Face"), 20); + EXPECT_STREQ(result.shapeName().c_str(), "Shell"); +} + TEST_F(TopoShapeExpansionTest, makeElementBooleanImpossibleCommon) { // Arrange @@ -1162,9 +1181,10 @@ TEST_F(TopoShapeExpansionTest, makeElementBooleanImpossibleCommon) TopoShape& result = topoShape1.makeElementBoolean(Part::OpCodes::Common, {topoShape1, topoShape2}); auto elements = elementMap(result); - // Assert - EXPECT_EQ(elements.size(), 0); + // Assert shape is correct EXPECT_FLOAT_EQ(getVolume(result.getShape()), 0); + // Assert elementMap is correct + EXPECT_EQ(elements.size(), 0); } TEST_F(TopoShapeExpansionTest, makeElementBooleanCommon) @@ -1180,11 +1200,12 @@ TEST_F(TopoShapeExpansionTest, makeElementBooleanCommon) TopoShape& result = topoShape1.makeElementBoolean(Part::OpCodes::Common, {topoShape1, topoShape2}); auto elements = elementMap(result); - // Assert + // Assert shape is correct + EXPECT_FLOAT_EQ(getVolume(result.getShape()), 0.25); + // Assert elementMap is correct EXPECT_EQ(elements.size(), 26); EXPECT_EQ(elements.count(IndexedName("Face", 1)), 1); EXPECT_EQ(elements[IndexedName("Face", 1)], MappedName("Face3;:M;CMN;:H1:7,F")); - EXPECT_FLOAT_EQ(getVolume(result.getShape()), 0.25); } TEST_F(TopoShapeExpansionTest, makeElementBooleanCut) @@ -1199,7 +1220,9 @@ TEST_F(TopoShapeExpansionTest, makeElementBooleanCut) // Act TopoShape& result = topoShape1.makeElementBoolean(Part::OpCodes::Cut, {topoShape1, topoShape2}); auto elements = elementMap(result); - // Assert + // Assert shape is correct + EXPECT_FLOAT_EQ(getVolume(result.getShape()), 0.75); + // Assert elementMap is correct EXPECT_EQ(elements.size(), 38); EXPECT_EQ(elements.count(IndexedName("Face", 1)), 1); EXPECT_EQ( @@ -1208,7 +1231,6 @@ TEST_F(TopoShapeExpansionTest, makeElementBooleanCut) "Face3;:M;CUT;:H1:7,F;:U;CUT;:H1:7,E;:L(Face5;:M;CUT;:H1:7,F;:U2;CUT;:H1:8,E|Face5;:M;" "CUT;:H1:7,F;:U2;CUT;:H1:8,E;:U;CUT;:H1:7,V;:L(Face6;:M;CUT;:H1:7,F;:U2;CUT;:H1:8,E;:U;" "CUT;:H1:7,V);CUT;:H1:3c,E|Face6;:M;CUT;:H1:7,F;:U2;CUT;:H1:8,E);CUT;:H1:cb,F")); - EXPECT_FLOAT_EQ(getVolume(result.getShape()), 0.75); } TEST_F(TopoShapeExpansionTest, makeElementBooleanFuse) @@ -1224,7 +1246,9 @@ TEST_F(TopoShapeExpansionTest, makeElementBooleanFuse) TopoShape& result = topoShape1.makeElementBoolean(Part::OpCodes::Fuse, {topoShape1, topoShape2}); auto elements = elementMap(result); - // Assert + // Assert shape is correct + EXPECT_FLOAT_EQ(getVolume(result.getShape()), 1.75); + // Assert element map is correct EXPECT_EQ(elements.size(), 66); EXPECT_EQ(elements.count(IndexedName("Face", 1)), 1); EXPECT_EQ( @@ -1233,7 +1257,6 @@ TEST_F(TopoShapeExpansionTest, makeElementBooleanFuse) "Face3;:M;FUS;:H1:7,F;:U;FUS;:H1:7,E;:L(Face5;:M;FUS;:H1:7,F;:U2;FUS;:H1:8,E|Face5;:M;" "FUS;:H1:7,F;:U2;FUS;:H1:8,E;:U;FUS;:H1:7,V;:L(Face6;:M;FUS;:H1:7,F;:U2;FUS;:H1:8,E;:U;" "FUS;:H1:7,V);FUS;:H1:3c,E|Face6;:M;FUS;:H1:7,F;:U2;FUS;:H1:8,E);FUS;:H1:cb,F")); - EXPECT_FLOAT_EQ(getVolume(result.getShape()), 1.75); } TEST_F(TopoShapeExpansionTest, makeElementDraft) @@ -1251,8 +1274,10 @@ TEST_F(TopoShapeExpansionTest, makeElementDraft) TopoShape& result = cube1TS.makeElementDraft(cube1TS, faces, pullDirection, angle, plane); auto elements = elementMap(result); // Assert - EXPECT_EQ(elements.size(), 26); // Cubes have 6 Faces, 12 Edges, 8 Vertexes EXPECT_NEAR(getVolume(result.getShape()), 4.3333333333, 1e-06); // Truncated pyramid + // Assert elementMap is correct + EXPECT_EQ(elements.size(), 26); // Cubes have 6 Faces, 12 Edges, 8 Vertexes + EXPECT_EQ(elements[IndexedName("Edge", 1)], MappedName("Face1;:G;DFT;:H1:7,F;:U;DFT;:H1:7,E")); } TEST_F(TopoShapeExpansionTest, makeElementDraftTopoShapes) @@ -1289,7 +1314,7 @@ TEST_F(TopoShapeExpansionTest, makeElementDraftTopoShapes) EXPECT_EQ(result3.getElementMap().size(), 0); // No element map in non reference call. } -TEST_F(TopoShapeExpansionTest, makeElementLinearizeEdge) +TEST_F(TopoShapeExpansionTest, linearizeEdge) { // Arrange TColgp_Array1OfPnt points {1, 2}; @@ -1309,7 +1334,7 @@ TEST_F(TopoShapeExpansionTest, makeElementLinearizeEdge) EXPECT_EQ(curve2.GetType(), GeomAbs_Line); } -TEST_F(TopoShapeExpansionTest, makeElementLinearizeFace) +TEST_F(TopoShapeExpansionTest, linearizeFace) { TColgp_Array2OfPnt points2 {1, 2, 1, 2}; points2.SetValue(1, 1, gp_Pnt(0.0, 0.0, 0.0)); @@ -1341,7 +1366,7 @@ TEST_F(TopoShapeExpansionTest, makeElementRuledSurfaceEdges) // Act topoShape.makeElementRuledSurface({edge1ts, edge2ts}, 0); // TODO: orientation as enum? auto elements = elementMap(topoShape); - // Assert + // Assert shape is correct EXPECT_EQ(topoShape.countSubElements("Wire"), 1); EXPECT_FLOAT_EQ(getArea(topoShape.getShape()), 20); // Assert that we're creating a correct element map @@ -1466,4 +1491,82 @@ TEST_F(TopoShapeExpansionTest, makeElementThickSolid) EXPECT_EQ(elements[IndexedName("Edge", 1)], MappedName("Edge11;THK;:H1:4,E")); } + +TEST_F(TopoShapeExpansionTest, makeElementGeneralFuse) +{ + // Arrange + auto [cube1, cube2] = CreateTwoCubes(); + auto tr {gp_Trsf()}; + tr.SetTranslation(gp_Vec(gp_XYZ(-0.5, -0.5, 0))); + cube2.Move(TopLoc_Location(tr)); + TopoShape topoShape1 {cube1, 1L}; + TopoShape topoShape2 {cube2, 2L}; + // Act + std::vector> modified {{}}; + TopoShape& result = topoShape1.makeElementGeneralFuse({topoShape1, topoShape2}, modified); + + auto elements = elementMap(result); + // Assert shape is correct + EXPECT_FLOAT_EQ(getVolume(result.getShape()), 1.75); + // Assert elementMap is correct + EXPECT_EQ(elements.size(), 72); + EXPECT_EQ(elements.count(IndexedName("Face", 1)), 1); + EXPECT_EQ( + elements[IndexedName("Face", 1)], + MappedName( + "Face3;:M;GFS;:H1:7,F;:U;GFS;:H1:7,E;:L(Face5;:M;GFS;:H1:7,F;:U2;GFS;:H1:8,E|Face5;:M;" + "GFS;:H1:7,F;:U2;GFS;:H1:8,E;:U;GFS;:H1:7,V;:L(Face6;:M;GFS;:H1:7,F;:U2;GFS;:H1:8,E;:U;" + "GFS;:H1:7,V);GFS;:H1:3c,E|Face6;:M;GFS;:H1:7,F;:U2;GFS;:H1:8,E);GFS;:H1:cb,F")); +} + +TEST_F(TopoShapeExpansionTest, makeElementFuse) +{ + // Arrange + auto [cube1, cube2] = CreateTwoCubes(); + auto tr {gp_Trsf()}; + tr.SetTranslation(gp_Vec(gp_XYZ(-0.5, -0.5, 0))); + cube2.Move(TopLoc_Location(tr)); + TopoShape topoShape1 {cube1, 1L}; + TopoShape topoShape2 {cube2, 2L}; + // Act + TopoShape& result = topoShape1.makeElementFuse({topoShape1, topoShape2}); // op, tolerance + auto elements = elementMap(result); + // Assert shape is correct + EXPECT_FLOAT_EQ(getVolume(result.getShape()), 1.75); + // Assert elementMap is correct + EXPECT_EQ(elements.size(), 66); + EXPECT_EQ(elements.count(IndexedName("Face", 1)), 1); + EXPECT_EQ( + elements[IndexedName("Face", 1)], + MappedName( + "Face3;:M;FUS;:H1:7,F;:U;FUS;:H1:7,E;:L(Face5;:M;FUS;:H1:7,F;:U2;FUS;:H1:8,E|Face5;:M;" + "FUS;:H1:7,F;:U2;FUS;:H1:8,E;:U;FUS;:H1:7,V;:L(Face6;:M;FUS;:H1:7,F;:U2;FUS;:H1:8,E;:U;" + "FUS;:H1:7,V);FUS;:H1:3c,E|Face6;:M;FUS;:H1:7,F;:U2;FUS;:H1:8,E);FUS;:H1:cb,F")); +} +TEST_F(TopoShapeExpansionTest, makeElementCut) +{ + // Arrange + auto [cube1, cube2] = CreateTwoCubes(); + auto tr {gp_Trsf()}; + tr.SetTranslation(gp_Vec(gp_XYZ(-0.5, -0.5, 0))); + cube2.Move(TopLoc_Location(tr)); + TopoShape topoShape1 {cube1, 1L}; + TopoShape topoShape2 {cube2, 2L}; + // Act + TopoShape& result = topoShape1.makeElementCut( + {topoShape1, topoShape2}); //, const char* op = nullptr, double tol = 0); + auto elements = elementMap(result); + // Assert shape is correct + EXPECT_FLOAT_EQ(getVolume(result.getShape()), 0.75); + // Assert elementMap is correct + EXPECT_EQ(elements.size(), 38); + EXPECT_EQ(elements.count(IndexedName("Face", 1)), 1); + EXPECT_EQ( + elements[IndexedName("Face", 1)], + MappedName( + "Face3;:M;CUT;:H1:7,F;:U;CUT;:H1:7,E;:L(Face5;:M;CUT;:H1:7,F;:U2;CUT;:H1:8,E|Face5;:M;" + "CUT;:H1:7,F;:U2;CUT;:H1:8,E;:U;CUT;:H1:7,V;:L(Face6;:M;CUT;:H1:7,F;:U2;CUT;:H1:8,E;:U;" + "CUT;:H1:7,V);CUT;:H1:3c,E|Face6;:M;CUT;:H1:7,F;:U2;CUT;:H1:8,E);CUT;:H1:cb,F")); +} + // NOLINTEND(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers)