diff --git a/src/Mod/Part/App/FaceMaker.h b/src/Mod/Part/App/FaceMaker.h index 3f80639165..fc40e243a1 100644 --- a/src/Mod/Part/App/FaceMaker.h +++ b/src/Mod/Part/App/FaceMaker.h @@ -156,10 +156,10 @@ class PartExport FaceMakerSimple : public FaceMakerPublic { TYPESYSTEM_HEADER_WITH_OVERRIDE(); public: - virtual std::string getUserFriendlyName() const override; - virtual std::string getBriefExplanation() const override; + std::string getUserFriendlyName() const override; + std::string getBriefExplanation() const override; protected: - virtual void Build_Essence() override; + void Build_Essence() override; }; diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index 7a5faa1156..b5bf372a93 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -81,7 +81,7 @@ class PartExport ShapeSegment: public Data::Segment TYPESYSTEM_HEADER_WITH_OVERRIDE(); public: - ShapeSegment(const TopoDS_Shape& ShapeIn) + explicit ShapeSegment(const TopoDS_Shape& ShapeIn) : Shape(ShapeIn) {} ShapeSegment() = default; diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index 7bf252fdf0..c59ada479b 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -26,12 +26,12 @@ #include "PreCompiled.h" #ifndef _PreComp_ #include -# include +#include #include #include -# include -# include +#include +#include #endif @@ -480,7 +480,7 @@ void TopoShape::mapCompoundSubElements(const std::vector& shapes, con ++count; auto subshape = getSubShape(TopAbs_SHAPE, count, /*silent = */ true); if (!subshape.IsPartner(topoShape._Shape)) { - return; // Not a partner shape, don't do any mapping at all + return; // Not a partner shape, don't do any mapping at all } } auto children {createChildMap(count, shapes, op)}; @@ -549,49 +549,59 @@ TopoShape::makeElementCompound(const std::vector& shapes, const char* } -TopoShape &TopoShape::makeElementFace(const TopoShape &shape, - const char *op, - const char *maker, - const gp_Pln *pln) +TopoShape& TopoShape::makeElementFace(const TopoShape& shape, + const char* op, + const char* maker, + const gp_Pln* pln) { std::vector shapes; - if (shape.isNull()) - FC_THROWM(NullShapeException, "Null shape"); - if(shape.getShape().ShapeType() == TopAbs_COMPOUND) + if (shape.isNull()) { + FC_THROWM(NullShapeException, "Null shape"); + } + if (shape.getShape().ShapeType() == TopAbs_COMPOUND) { shapes = shape.getSubTopoShapes(); - else + } + else { shapes.push_back(shape); - return makeElementFace(shapes,op,maker,pln); + } + return makeElementFace(shapes, op, maker, pln); } -TopoShape &TopoShape::makeElementFace(const std::vector &shapes, - const char *op, - const char *maker, - const gp_Pln *pln) +TopoShape& TopoShape::makeElementFace(const std::vector& shapes, + const char* op, + const char* maker, + const gp_Pln* pln) { - if(!maker || !maker[0]) maker = "Part::FaceMakerBullseye"; + if (!maker || !maker[0]) { + maker = "Part::FaceMakerBullseye"; + } std::unique_ptr mkFace = FaceMaker::ConstructFromType(maker); mkFace->MyHasher = Hasher; mkFace->MyOp = op; - if (pln) + if (pln) { mkFace->setPlane(*pln); + } - for(auto &s : shapes) { - if (s.getShape().ShapeType() == TopAbs_COMPOUND) + for (auto& s : shapes) { + if (s.getShape().ShapeType() == TopAbs_COMPOUND) { mkFace->useTopoCompound(s); - else + } + else { mkFace->addTopoShape(s); + } } mkFace->Build(); - const auto &ret = mkFace->getTopoShape(); + const auto& ret = mkFace->getTopoShape(); setShape(ret._Shape); Hasher = ret.Hasher; resetElementMap(ret.elementMap()); if (!isValid()) { ShapeFix_ShapeTolerance aSFT; aSFT.LimitTolerance(getShape(), - Precision::Confusion(), Precision::Confusion(), TopAbs_SHAPE); + Precision::Confusion(), + Precision::Confusion(), + TopAbs_SHAPE); // In some cases, the OCC reports the returned shape having invalid // tolerance. Not sure about the real cause. @@ -608,24 +618,39 @@ TopoShape &TopoShape::makeElementFace(const std::vector &shapes, fixer.Perform(); setShape(fixer.Shape(), false); - if (!isValid()) + if (!isValid()) { FC_WARN("makeElementFace: resulting face is invalid"); + } } return *this; } -Data::MappedName TopoShape::setElementComboName(const Data::IndexedName & element, - const std::vector &names, - const char *marker, - const char *op, - const Data::ElementIDRefs *_sids) +/** + * Encode and set an element name in the elementMap. If a hasher is defined, apply it to the name. + * + * @param element The element name(type) that provides 1 one character suffix to the name IF . + * @param names The subnames to build the name from. If empty, return the TopoShape MappedName. + * @param marker The elementMap name or suffix to start the name with. If null, use the + * elementMapPrefix. + * @param op The op text passed to the element name encoder along with the TopoShape Tag + * @param _sids If defined, records the sub ids processed. + * + * @return The encoded, possibly hashed name. + */ +Data::MappedName TopoShape::setElementComboName(const Data::IndexedName& element, + const std::vector& names, + const char* marker, + const char* op, + const Data::ElementIDRefs* _sids) { - if(names.empty()) + if (names.empty()) { return Data::MappedName(); + } std::string _marker; - if(!marker) + if (!marker) { marker = elementMapPrefix().c_str(); - else if(!boost::starts_with(marker,elementMapPrefix())){ + } + else if (!boost::starts_with(marker, elementMapPrefix())) { _marker = elementMapPrefix() + marker; marker = _marker.c_str(); } @@ -633,36 +658,50 @@ Data::MappedName TopoShape::setElementComboName(const Data::IndexedName & elemen Data::MappedName newName = *it; std::ostringstream ss; Data::ElementIDRefs sids; - if (_sids) + if (_sids) { sids = *_sids; - if(names.size() == 1) + } + if (names.size() == 1) { ss << marker; + } else { bool first = true; ss.str(""); - if(!Hasher) + if (!Hasher) { ss << marker; + } ss << '('; - for(++it;it!=names.end();++it) { - if(first) + for (++it; it != names.end(); ++it) { + if (first) { first = false; - else + } + else { ss << '|'; + } ss << *it; } ss << ')'; - if(Hasher) { + if (Hasher) { sids.push_back(Hasher->getID(ss.str().c_str())); ss.str(""); ss << marker << sids.back().toString(); } } - elementMap()->encodeElementName(element[0],newName,ss,&sids,Tag,op); - return elementMap()->setElementName(element,newName, Tag, &sids); + elementMap()->encodeElementName(element[0], newName, ss, &sids, Tag, op); + return elementMap()->setElementName(element, newName, Tag, &sids); } -TopoShape TopoShape::splitWires(std::vector *inner, - SplitWireReorient reorient) const +/** + * Reorient the outer and inner wires of the TopoShape + * + * @param inner If this is not a nullptr, then any inner wires processed will be returned in this + * vector. + * @param reorient One of NoReorient, Reorient ( Outer forward, inner reversed ), + * ReorientForward ( all forward ), or ReorientReversed ( all reversed ) + * @return The outer wire, or an empty TopoShape if this isn't a Face, has no Face subShapes, or the + * outer wire isn't found. + */ +TopoShape TopoShape::splitWires(std::vector* inner, SplitWireReorient reorient) const { // ShapeAnalysis::OuterWire() is un-reliable for some reason. OCC source // code shows it works by creating face using each wire, and then test using @@ -677,31 +716,33 @@ TopoShape TopoShape::splitWires(std::vector *inner, // reliable method, especially so for a planar face. TopoDS_Shape tmp; - if (shapeType(true) == TopAbs_FACE) + if (shapeType(true) == TopAbs_FACE) { tmp = BRepTools::OuterWire(TopoDS::Face(_Shape)); - else if (countSubShapes(TopAbs_FACE) == 1) - tmp = BRepTools::OuterWire( - TopoDS::Face(getSubShape(TopAbs_FACE, 1))); - if (tmp.IsNull()) + } + else if (countSubShapes(TopAbs_FACE) == 1) { + tmp = BRepTools::OuterWire(TopoDS::Face(getSubShape(TopAbs_FACE, 1))); + } + if (tmp.IsNull()) { return TopoShape(); - const auto & wires = getSubTopoShapes(TopAbs_WIRE); + } + const auto& wires = getSubTopoShapes(TopAbs_WIRE); auto it = wires.begin(); TopAbs_Orientation orientOuter, orientInner; - switch(reorient) { - case ReorientReversed: - orientOuter = orientInner = TopAbs_REVERSED; - break; - case ReorientForward: - orientOuter = orientInner = TopAbs_FORWARD; - break; - default: - orientOuter = TopAbs_FORWARD; - orientInner = TopAbs_REVERSED; - break; + switch (reorient) { + case ReorientReversed: + orientOuter = orientInner = TopAbs_REVERSED; + break; + case ReorientForward: + orientOuter = orientInner = TopAbs_FORWARD; + break; + default: + orientOuter = TopAbs_FORWARD; + orientInner = TopAbs_REVERSED; + break; } - auto doReorient = [](TopoShape &s, TopAbs_Orientation orient) { + auto doReorient = [](TopoShape& s, TopAbs_Orientation orient) { // Special case of single edge wire. Make sure the edge is in the // required orientation. This is necessary because BRepFill_OffsetWire // has special handling of circular edge offset, which seem to only @@ -710,36 +751,43 @@ TopoShape TopoShape::splitWires(std::vector *inner, if (s.countSubShapes(TopAbs_EDGE) == 1) { TopoDS_Shape e = s.getSubShape(TopAbs_EDGE, 1); if (e.Orientation() == orient) { - if (s._Shape.Orientation() == orient) + if (s._Shape.Orientation() == orient) { return; - } else + } + } + else { e = e.Oriented(orient); + } BRepBuilderAPI_MakeWire mkWire(TopoDS::Edge(e)); s.setShape(mkWire.Shape(), false); } - else if (s._Shape.Orientation() != orient) + else if (s._Shape.Orientation() != orient) { s.setShape(s._Shape.Oriented(orient), false); + } }; for (; it != wires.end(); ++it) { - auto & wire = *it; + auto& wire = *it; if (wire.getShape().IsSame(tmp)) { if (inner) { for (++it; it != wires.end(); ++it) { inner->push_back(*it); - if (reorient) + if (reorient) { doReorient(inner->back(), orientInner); + } } } auto res = wire; - if (reorient) + if (reorient) { doReorient(res, orientOuter); + } return res; } if (inner) { inner->push_back(wire); - if (reorient) + if (reorient) { doReorient(inner->back(), orientInner); + } } } return TopoShape(); diff --git a/tests/src/Mod/Part/App/PartTestHelpers.cpp b/tests/src/Mod/Part/App/PartTestHelpers.cpp index 02a3d33d04..24d106f9f8 100644 --- a/tests/src/Mod/Part/App/PartTestHelpers.cpp +++ b/tests/src/Mod/Part/App/PartTestHelpers.cpp @@ -14,6 +14,21 @@ double getVolume(const TopoDS_Shape& shape) return prop.Mass(); } +double getArea(const TopoDS_Shape& shape) +{ + GProp_GProps prop; + BRepGProp::SurfaceProperties(shape, prop); + return prop.Mass(); +} + +double getLength(const TopoDS_Shape& shape) +{ + GProp_GProps prop; + BRepGProp::LinearProperties(shape, prop); + return prop.Mass(); +} + + void PartTestHelperClass::createTestDoc() { _docName = App::GetApplication().getUniqueDocumentName("test"); @@ -48,7 +63,8 @@ _getFilletEdges(const std::vector& edges, double startRadius, double endRad return filletElements; } -void executePython(const std::vector& python) + +void ExecutePython(const std::vector& python) { Base::InterpreterSingleton is = Base::InterpreterSingleton(); @@ -68,16 +84,9 @@ void rectangle(double height, double width, char* name) boost::str(boost::format("V4 = FreeCAD.Vector(0, %d, 0)") % width), "P1 = Part.makePolygon([V1, V2, V3, V4],True)", "F1 = Part.Face(P1)", // Make the face or the volume calc won't work right. - // "L1 = Part.LineSegment(V1, V2)", - // "L2 = Part.LineSegment(V2, V3)", - // "L3 = Part.LineSegment(V3, V4)", - // "L4 = Part.LineSegment(V4, V1)", - // "S1 = Part.Shape([L1,L2,L3,L4])", - // "W1 = Part.Wire(S1.Edges)", - // "F1 = Part.Face(W1)", // Make the face or the volume calc won't work right. boost::str(boost::format("Part.show(F1,'%s')") % name), }; - executePython(rectstring); + ExecutePython(rectstring); } testing::AssertionResult diff --git a/tests/src/Mod/Part/App/PartTestHelpers.h b/tests/src/Mod/Part/App/PartTestHelpers.h index d651951c2f..742eaf26bc 100644 --- a/tests/src/Mod/Part/App/PartTestHelpers.h +++ b/tests/src/Mod/Part/App/PartTestHelpers.h @@ -1,21 +1,25 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "gtest/gtest.h" +#include #include #include +#include "Base/Interpreter.h" #include #include "Mod/Part/App/FeaturePartBox.h" #include "Mod/Part/App/FeaturePartFuse.h" #include "Mod/Part/App/FeatureFillet.h" #include -#include "Base/Interpreter.h" -#include namespace PartTestHelpers { double getVolume(const TopoDS_Shape& shape); +double getArea(const TopoDS_Shape& shape); + +double getLength(const TopoDS_Shape& shape); + std::vector _getFilletEdges(const std::vector& edges, double startRadius, double endRadius); diff --git a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp index 964f346f88..bb793a326b 100644 --- a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -3,12 +3,18 @@ #include "gtest/gtest.h" #include "src/App/InitApplication.h" #include +#include + +#include "PartTestHelpers.h" -#include #include +#include +#include #include +#include +#include +#include #include -#include // NOLINTBEGIN(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers) @@ -34,6 +40,7 @@ protected: App::GetApplication().closeDocument(_docName.c_str()); } + private: std::string _docName; Data::ElementIDRefs _sid; @@ -161,4 +168,272 @@ TEST_F(TopoShapeExpansionTest, makeElementCompoundTwoCubes) // 26 subshapes each } +std::tuple +CreateRectFace(float len = 2.0, float wid = 3.0) +{ + auto edge1 = BRepBuilderAPI_MakeEdge(gp_Pnt(0.0, 0.0, 0.0), gp_Pnt(len, 0.0, 0.0)).Edge(); + auto edge2 = BRepBuilderAPI_MakeEdge(gp_Pnt(len, 0.0, 0.0), gp_Pnt(len, wid, 0.0)).Edge(); + auto edge3 = BRepBuilderAPI_MakeEdge(gp_Pnt(len, wid, 0.0), gp_Pnt(0.0, wid, 0.0)).Edge(); + auto edge4 = BRepBuilderAPI_MakeEdge(gp_Pnt(0.0, wid, 0.0), gp_Pnt(0.0, 0.0, 0.0)).Edge(); + auto wire1 = BRepBuilderAPI_MakeWire({edge1, edge2, edge3, edge4}).Wire(); + auto face1 = BRepBuilderAPI_MakeFace(wire1).Face(); + return {face1, wire1, edge1, edge2, edge3, edge4}; +} + +std::tuple +CreateFaceWithRoundHole(float len = 2.0, float wid = 3.0, float radius = 1.0) +{ + auto [face1, wire1, edge1, edge2, edge3, edge4] = CreateRectFace(len, wid); + auto circ1 = + GC_MakeCircle(gp_Pnt(len / 2.0, wid / 2.0, 0), gp_Dir(0.0, 0.0, 1.0), radius).Value(); + auto edge5 = BRepBuilderAPI_MakeEdge(circ1).Edge(); + auto wire2 = BRepBuilderAPI_MakeWire(edge5).Wire(); + auto face2 = BRepBuilderAPI_MakeFace(face1, wire2).Face(); + return {face2, wire1, wire2}; +} + +TEST_F(TopoShapeExpansionTest, makeElementFaceNull) +{ + // Arrange + const double L = 3, W = 2, R = 1; + auto [face1, wire1, wire2] = CreateFaceWithRoundHole(L, W, R); + Part::TopoShape topoShape {face1}; + double area = PartTestHelpers::getArea(face1); + double area1 = PartTestHelpers::getArea(topoShape.getShape()); + // Act + Part::TopoShape newFace = topoShape.makeElementFace(nullptr); + double area2 = PartTestHelpers::getArea(newFace.getShape()); + double area3 = PartTestHelpers::getArea(topoShape.getShape()); + // Assert + EXPECT_FALSE(face1.IsEqual(newFace.getShape())); + EXPECT_FLOAT_EQ(area, L * W + M_PI * R * R); + EXPECT_FLOAT_EQ(area1, L * W + M_PI * R * R); + EXPECT_FLOAT_EQ(area2, L * W - M_PI * R * R); + EXPECT_FLOAT_EQ(area3, L * W + M_PI * R * R); + EXPECT_STREQ(newFace.shapeName().c_str(), "Face"); +} + +TEST_F(TopoShapeExpansionTest, makeElementFaceSimple) +{ + // Arrange + const double L = 3, W = 2, R = 1; + auto [face1, wire1, wire2] = CreateFaceWithRoundHole(L, W, R); + Part::TopoShape topoShape {face1}; + double area = PartTestHelpers::getArea(face1); + double area1 = PartTestHelpers::getArea(topoShape.getShape()); + // Act + Part::TopoShape newFace = topoShape.makeElementFace(wire1); + double area2 = PartTestHelpers::getArea(newFace.getShape()); + double area3 = PartTestHelpers::getArea(topoShape.getShape()); + // Assert + EXPECT_TRUE(newFace.getShape().IsEqual(topoShape.getShape())); // topoShape was altered + EXPECT_FALSE(face1.IsEqual(newFace.getShape())); + EXPECT_FLOAT_EQ(area, L * W + M_PI * R * R); + EXPECT_FLOAT_EQ(area1, L * W + M_PI * R * R); + EXPECT_FLOAT_EQ(area2, L * W); + EXPECT_FLOAT_EQ(area3, L * W); + EXPECT_STREQ(newFace.shapeName().c_str(), "Face"); +} + +TEST_F(TopoShapeExpansionTest, makeElementFaceParams) +{ + // Arrange + const double L = 3, W = 2, R = 1; + auto [face1, wire1, wire2] = CreateFaceWithRoundHole(L, W, R); + Part::TopoShape topoShape {face1, 1L}; + double area = PartTestHelpers::getArea(face1); + double area1 = PartTestHelpers::getArea(topoShape.getShape()); + // Act + Part::TopoShape newFace = + topoShape.makeElementFace(wire1, "Cut", "Part::FaceMakerBullseye", nullptr); + double area2 = PartTestHelpers::getArea(newFace.getShape()); + double area3 = PartTestHelpers::getArea(topoShape.getShape()); + // Assert + EXPECT_TRUE(newFace.getShape().IsEqual(topoShape.getShape())); // topoShape was altered + EXPECT_FALSE(face1.IsEqual(newFace.getShape())); + EXPECT_FLOAT_EQ(area, L * W + M_PI * R * R); + EXPECT_FLOAT_EQ(area1, L * W + M_PI * R * R); + EXPECT_FLOAT_EQ(area2, L * W); + EXPECT_FLOAT_EQ(area3, L * W); + EXPECT_STREQ(newFace.shapeName().c_str(), "Face"); +} + +TEST_F(TopoShapeExpansionTest, makeElementFaceFromFace) +{ + // Arrange + const double L = 3, W = 2, R = 1; + auto [face1, wire1, wire2] = CreateFaceWithRoundHole(L, W, R); + Part::TopoShape topoShape {face1, 1L}; + double area = PartTestHelpers::getArea(face1); + double area1 = PartTestHelpers::getArea(topoShape.getShape()); + // Act + Part::TopoShape newFace = + topoShape.makeElementFace(face1, "Cut", "Part::FaceMakerBullseye", nullptr); + double area2 = PartTestHelpers::getArea(newFace.getShape()); + double area3 = PartTestHelpers::getArea(topoShape.getShape()); + // Assert + EXPECT_TRUE(newFace.getShape().IsEqual(topoShape.getShape())); // topoShape was altered + EXPECT_FALSE(face1.IsEqual(newFace.getShape())); + EXPECT_FLOAT_EQ(area, L * W + M_PI * R * R); + EXPECT_FLOAT_EQ(area1, L * W + M_PI * R * R); + EXPECT_FLOAT_EQ(area2, L * W - M_PI * R * R); + EXPECT_FLOAT_EQ(area3, L * W - M_PI * R * R); + EXPECT_STREQ(newFace.shapeName().c_str(), "Face"); +} + + +TEST_F(TopoShapeExpansionTest, makeElementFaceOpenWire) +{ + // Arrange + const double L = 3, W = 2, R = 1; + auto [face1, wire1, wire2] = CreateFaceWithRoundHole(L, W, R); + Part::TopoShape topoShape {wire1, 1L}; + double area = PartTestHelpers::getArea(face1); + double area1 = PartTestHelpers::getArea(topoShape.getShape()); + // Act + Part::TopoShape newFace = topoShape.makeElementFace(wire1, "Cut", nullptr, nullptr); + double area2 = PartTestHelpers::getArea(newFace.getShape()); + double area3 = PartTestHelpers::getArea(topoShape.getShape()); + // Assert + EXPECT_TRUE(newFace.getShape().IsEqual(topoShape.getShape())); // topoShape was altered + EXPECT_FALSE(face1.IsEqual(newFace.getShape())); + EXPECT_FLOAT_EQ(area, L * W + M_PI * R * R); + EXPECT_FLOAT_EQ(area1, 0); // L * W - M_PI * R * R); + EXPECT_FLOAT_EQ(area2, L * W); + EXPECT_FLOAT_EQ(area3, L * W); + EXPECT_STREQ(newFace.shapeName().c_str(), "Face"); +} + + +TEST_F(TopoShapeExpansionTest, makeElementFaceClosedWire) +{ + // Arrange + const double L = 3, W = 2, R = 1; + auto [face1, wire1, wire2] = CreateFaceWithRoundHole(L, W, R); + Part::TopoShape topoShape {wire2, 1L}; + double area = PartTestHelpers::getArea(face1); + double area1 = PartTestHelpers::getArea(topoShape.getShape()); + // Act + Part::TopoShape newFace = + topoShape.makeElementFace(wire2, "Cut", "Part::FaceMakerBullseye", nullptr); + double area2 = PartTestHelpers::getArea(newFace.getShape()); + double area3 = PartTestHelpers::getArea(topoShape.getShape()); + // Assert + EXPECT_TRUE(newFace.getShape().IsEqual(topoShape.getShape())); // topoShape was altered + EXPECT_FALSE(face1.IsEqual(newFace.getShape())); + EXPECT_FLOAT_EQ(area, L * W + M_PI * R * R); + EXPECT_FLOAT_EQ(area1, 0); // L * W - M_PI * R * R); + EXPECT_FLOAT_EQ(area2, M_PI * R * R); + EXPECT_FLOAT_EQ(area3, M_PI * R * R); + EXPECT_STREQ(newFace.shapeName().c_str(), "Face"); +} + +// Possible future makeElementFace tests: +// Overlapping wire +// Compound of wires +// Compound of faces +// Compound of other shape types + + +TEST_F(TopoShapeExpansionTest, setElementComboNameNothing) +{ + // Arrange + Part::TopoShape topoShape(1L); + // Act + Data::MappedName result = topoShape.setElementComboName(Data::IndexedName(), {}); + // ASSERT + EXPECT_STREQ(result.toString().c_str(), ""); +} + + +TEST_F(TopoShapeExpansionTest, setElementComboNameSimple) +{ + // Arrange + auto edge1 = BRepBuilderAPI_MakeEdge(gp_Pnt(0.0, 0.0, 0.0), gp_Pnt(1.0, 0.0, 0.0)).Edge(); + Part::TopoShape topoShape(edge1, 1L); + topoShape.setElementMap({}); // Initialize the map to avoid a segfault. + // Also, maybe the end of TopoShape::mapSubElementTypeForShape should enforce that elementMap() + // isn't nullptr to eliminate the segfault. + Data::MappedName edgeName("testname"); + // Act + Data::MappedName result = + topoShape.setElementComboName(Data::IndexedName::fromConst("Edge", 1), {edgeName}); + // Assert + EXPECT_STREQ(result.toString().c_str(), "testname;"); +} + + +TEST_F(TopoShapeExpansionTest, setElementComboName) +{ + // Arrange + Part::TopoShape topoShape(2L); + topoShape.setElementMap({}); + Data::MappedName edgeName = + topoShape.getMappedName(Data::IndexedName::fromConst("Edge", 1), true); + Data::MappedName faceName = + topoShape.getMappedName(Data::IndexedName::fromConst("Face", 7), true); + Data::MappedName faceName2 = + topoShape.getMappedName(Data::IndexedName::fromConst("Face", 8), true); + char* op = "Copy"; + // Act + Data::MappedName result = topoShape.setElementComboName(Data::IndexedName::fromConst("Edge", 1), + {edgeName, faceName, faceName2}, + Part::OpCodes::Common, + op); + // Assert + EXPECT_STREQ(result.toString().c_str(), "Edge1;CMN(Face7|Face8);Copy"); + // The detailed forms of names are covered in encodeElementName tests +} + +TEST_F(TopoShapeExpansionTest, setElementComboNameCompound) +{ + // Arrange + auto edge1 = BRepBuilderAPI_MakeEdge(gp_Pnt(0.0, 0.0, 0.0), gp_Pnt(1.0, 0.0, 0.0)).Edge(); + auto wire1 = BRepBuilderAPI_MakeWire({edge1}).Wire(); + auto wire2 = BRepBuilderAPI_MakeWire({edge1}).Wire(); + Part::TopoShape topoShape(2L); + topoShape.makeElementCompound({wire1, wire2}); // Quality of shape doesn't matter + Data::MappedName edgeName = + topoShape.getMappedName(Data::IndexedName::fromConst("Edge", 1), true); + Data::MappedName faceName = + topoShape.getMappedName(Data::IndexedName::fromConst("Face", 7), true); + Data::MappedName faceName2 = + topoShape.getMappedName(Data::IndexedName::fromConst("Face", 8), true); + char* op = "Copy"; + // Act + Data::MappedName result = topoShape.setElementComboName(Data::IndexedName::fromConst("Edge", 1), + {edgeName, faceName, faceName2}, + Part::OpCodes::Common, + op); + // ASSERT + EXPECT_STREQ(result.toString().c_str(), "Edge1;:H,E;CMN(Face7|Face8);Copy"); + // The detailed forms of names are covered in encodeElementName tests +} + +TEST_F(TopoShapeExpansionTest, splitWires) +{ + // Arrange + const double L = 3, W = 2, R = 1; + auto [face1, wire1, wire2] = CreateFaceWithRoundHole(L, W, R); + Part::TopoShape topoShape {face1, 1L}; + std::vector inner; + // Act + EXPECT_EQ(topoShape.getShape().Orientation(), TopAbs_FORWARD); + Part::TopoShape wire = + topoShape.splitWires(&inner, Part::TopoShape::SplitWireReorient::ReorientReversed); + // Assert + EXPECT_EQ(inner.size(), 1); + EXPECT_FLOAT_EQ(PartTestHelpers::getLength(wire.getShape()), 2 + 2 + 3 + 3); + EXPECT_FLOAT_EQ(PartTestHelpers::getLength(inner.front().getShape()), M_PI * R * 2); + EXPECT_EQ(wire.getShape().Orientation(), TopAbs_REVERSED); + for (Part::TopoShape ts : inner) { + EXPECT_EQ(ts.getShape().Orientation(), TopAbs_FORWARD); + } +} + +// Possible future tests: +// splitWires without inner Wires +// splitWires with allfour reorientation values NoReorient, ReOrient, ReorientForward, +// ReorientRevesed + // NOLINTEND(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers)