From 9aeb396d006ab1ddb8f54868e21736b8fdb00856 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Thu, 25 Jan 2024 11:00:01 -0500 Subject: [PATCH 1/2] Transfer in MakeElementShell --- src/Mod/Part/App/TopoShape.h | 41 +++++++ src/Mod/Part/App/TopoShapeExpansion.cpp | 139 ++++++++++++++++++++++++ 2 files changed, 180 insertions(+) diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index 06439746a9..573e4eb322 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -676,6 +676,47 @@ public: */ TopoShape &makeElementCompound(const std::vector &shapes, const char *op=nullptr, bool force=true); + /* Make a shell using this shape + * @param silent: whether to throw exception on failure + * @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& makeElementShell(bool silent = true, const char* op = nullptr); + + /* Make a shell with input wires + * + * @param wires: input wires + * @param silent: whether to throw exception on failure + * @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& makeElementShellFromWires(const std::vector& wires, + // bool silent = true, + // const char* op = nullptr); + /* Make a shell with input wires + * + * @param wires: input wires + * @param silent: whether to throw exception on failure + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * + * @return Return the new shape. The TopoShape itself is not modified. + */ + // TopoShape& makeElementShellFromWires(bool silent = true, const char* op = nullptr) + // { + // return makeElementShellFromWires(getSubTopoShapes(TopAbs_WIRE), silent, op); + // } + 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 f6abf55423..e7554ebaa8 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -35,6 +35,9 @@ #endif +#include +#include +#include #include "TopoShape.h" #include "TopoShapeCache.h" #include "FaceMaker.h" @@ -796,5 +799,141 @@ TopoShape TopoShape::splitWires(std::vector* inner, SplitWireReorient return TopoShape(); } +struct MapperFill: Part::TopoShape::Mapper +{ + BRepFill_Generator& maker; + explicit MapperFill(BRepFill_Generator& maker) + : maker(maker) + {} + const std::vector& generated(const TopoDS_Shape& s) const override + { + _res.clear(); + try { + TopTools_ListIteratorOfListOfShape it; + for (it.Initialize(maker.GeneratedShapes(s)); it.More(); it.Next()) { + _res.push_back(it.Value()); + } + } + catch (const Standard_Failure& e) { + if (FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) { + FC_WARN("Exception on shape mapper: " << e.GetMessageString()); + } + } + return _res; + } +}; + +// topo naming counterpart of TopoShape::makeShell() +TopoShape& TopoShape::makeElementShell(bool silent, const char* op) +{ + if (silent) { + if (isNull()) { + return *this; + } + + if (shapeType(true) != TopAbs_COMPOUND) { + return *this; + } + + // we need a compound that consists of only faces + TopExp_Explorer it; + // no shells + if (hasSubShape(TopAbs_SHELL)) { + return *this; + } + + // no wires outside a face + it.Init(_Shape, TopAbs_WIRE, TopAbs_FACE); + if (it.More()) { + return *this; + } + + // no edges outside a wire + it.Init(_Shape, TopAbs_EDGE, TopAbs_WIRE); + if (it.More()) { + return *this; + } + + // no vertexes outside an edge + it.Init(_Shape, TopAbs_VERTEX, TopAbs_EDGE); + if (it.More()) { + return *this; + } + } + else if (!hasSubShape(TopAbs_FACE)) { + FC_THROWM(Base::CADKernelError, "Cannot make shell without face"); + } + + BRep_Builder builder; + TopoDS_Shape shape; + TopoDS_Shell shell; + builder.MakeShell(shell); + + try { + for (const auto& face : getSubShapes(TopAbs_FACE)) { + builder.Add(shell, face); + } + + TopoShape tmp(Tag, Hasher, shell); + tmp.resetElementMap(); + tmp.mapSubElement(*this, op); + + shape = shell; + BRepCheck_Analyzer check(shell); + if (!check.IsValid()) { + ShapeUpgrade_ShellSewing sewShell; + shape = sewShell.ApplySewing(shell); + // TODO confirm the above won't change OCCT topological naming + } + + if (shape.IsNull()) { + if (silent) { + return *this; + } + FC_THROWM(NullShapeException, "Failed to make shell"); + } + + if (shape.ShapeType() != TopAbs_SHELL) { + if (silent) { + return *this; + } + FC_THROWM(Base::CADKernelError, + "Failed to make shell: unexpected output shape type " + << shapeType(shape.ShapeType(), true)); + } + + setShape(shape); + resetElementMap(tmp.elementMap()); + } + catch (Standard_Failure& e) { + if (!silent) { + FC_THROWM(Base::CADKernelError, "Failed to make shell: " << e.GetMessageString()); + } + } + + return *this; +} + +// TopoShape& TopoShape::makeElementShellFromWires(const std::vector& wires, +// bool silent, +// const char* op) +// { +// BRepFill_Generator maker; +// for (auto& w : wires) { +// if (w.shapeType(silent) == TopAbs_WIRE) { +// maker.AddWire(TopoDS::Wire(w.getShape())); +// } +// } +// if (wires.empty()) { +// if (silent) { +// _Shape.Nullify(); +// return *this; +// } +// FC_THROWM(NullShapeException, "No input shapes"); +// } +// maker.Perform(); +// this->makeShapeWithElementMap(maker.Shell(), MapperFill(maker), wires, op); +// return *this; +// } } // namespace Part From 1aff0ca7f5dec308e93670bf89062d662eb3a7c5 Mon Sep 17 00:00:00 2001 From: bgbsww Date: Thu, 25 Jan 2024 11:13:00 -0500 Subject: [PATCH 2/2] Tests for makeElementShell --- tests/src/Mod/Part/App/TopoShapeExpansion.cpp | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp index 95f5026bad..ff976589d6 100644 --- a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -584,4 +584,109 @@ TEST_F(TopoShapeExpansionTest, mapSubElementFindAncestors) EXPECT_TRUE(ancestorShapeList.back().IsEqual(topoShape6.getShape())); } +TEST_F(TopoShapeExpansionTest, makeElementShellInvalid) +{ + // Arrange + Part::TopoShape topoShape {1L}; + // Act / Assert + EXPECT_THROW(topoShape.makeElementShell(false, nullptr), Base::CADKernelError); +} + +TEST_F(TopoShapeExpansionTest, makeElementShellSingle) +{ + // Arrange + const double Len = 3, Wid = 2; + auto [face1, wire1, edge1, edge2, edge3, _] = CreateRectFace(Len, Wid); + Part::TopoShape topoShape {face1, 1L}; + // Act + Part::TopoShape result = topoShape.makeElementShell(false, nullptr); + // Assert +#if OCC_VERSION_HEX >= 0x070400 + EXPECT_EQ(result.getShape().NbChildren(), 1); +#endif + EXPECT_EQ(result.countSubElements("Vertex"), 4); + EXPECT_EQ(result.countSubElements("Edge"), 4); + EXPECT_EQ(result.countSubElements("Face"), 1); + EXPECT_STREQ(result.shapeName().c_str(), "Shell"); +} + +TEST_F(TopoShapeExpansionTest, makeElementShellOpen) +{ + // Arrange + const double Len = 3, Wid = 2; + auto [face1, wire1, edge1, edge2, edge3, edge4] = CreateRectFace(Len, Wid); + auto transform {gp_Trsf()}; + transform.SetRotation(gp_Ax1(gp_Pnt(0, 0, 0), gp_Dir(1, 0, 0)), M_PI / 2); + auto face2 = face1; // Shallow copy + face2.Move(TopLoc_Location(transform)); + TopoDS_Compound compound1; + TopoDS_Builder builder {}; + builder.MakeCompound(compound1); + builder.Add(compound1, face1); + builder.Add(compound1, face2); + Part::TopoShape topoShape {compound1, 1L}; + // Act + Part::TopoShape result = topoShape.makeElementShell(true, nullptr); + // Assert +#if OCC_VERSION_HEX >= 0x070400 + EXPECT_EQ(result.getShape().NbChildren(), 2); +#endif + EXPECT_EQ(result.countSubElements("Vertex"), 6); + EXPECT_EQ(result.countSubElements("Edge"), 7); + EXPECT_EQ(result.countSubElements("Face"), 2); + EXPECT_STREQ(result.shapeName().c_str(), "Shell"); +} + +TEST_F(TopoShapeExpansionTest, makeElementShellClosed) +{ + // Arrange + auto [cube1, cube2] = CreateTwoCubes(); + Part::TopoShape topoShape {cube1}; + std::vector shapes; + for (const auto& face : topoShape.getSubShapes(TopAbs_FACE)) { + shapes.push_back(Part::TopoShape {face}); + } + // Act + Part::TopoShape topoShape1 {1L}; + topoShape1.makeElementCompound(shapes, "D"); + // Assert + Part::TopoShape result = topoShape1.makeElementShell(false, "SH1"); +#if OCC_VERSION_HEX >= 0x070400 + EXPECT_EQ(result.getShape().NbChildren(), 6); +#endif + EXPECT_EQ(result.countSubElements("Vertex"), 8); + EXPECT_EQ(result.countSubElements("Edge"), 12); + EXPECT_EQ(result.countSubElements("Face"), 6); + EXPECT_STREQ(result.shapeName().c_str(), "Shell"); +} + +TEST_F(TopoShapeExpansionTest, makeElementShellIntersecting) +{ + // Arrange + auto [cube1, cube2] = CreateTwoCubes(); + auto transform {gp_Trsf()}; + transform.SetTranslation(gp_Pnt(0.0, 0.0, 0.0), gp_Pnt(0.5, 0.5, 0.0)); + cube2.Move(TopLoc_Location(transform)); + // Arrange + Part::TopoShape topoShape {cube1}; + std::vector shapes; + for (const auto& face : topoShape.getSubShapes(TopAbs_FACE)) { + shapes.push_back(Part::TopoShape {face}); + } + topoShape.setShape(cube2); + for (const auto& face : topoShape.getSubShapes(TopAbs_FACE)) { + shapes.push_back(Part::TopoShape {face}); + } + // Act + Part::TopoShape topoShape1 {1L}; + topoShape1.makeElementCompound(shapes, "D"); + // Act / Assert + EXPECT_THROW(topoShape1.makeElementShell(false, nullptr), Base::CADKernelError); +} + +// TEST_F(TopoShapeExpansionTest, makeElementShellFromWires) +// { +// // Arrange +// } + // NOLINTEND(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers)