diff --git a/src/Mod/Part/App/TopoShape.cpp b/src/Mod/Part/App/TopoShape.cpp index 9ccf4cf1c7..431fb74645 100644 --- a/src/Mod/Part/App/TopoShape.cpp +++ b/src/Mod/Part/App/TopoShape.cpp @@ -346,6 +346,7 @@ TopoDS_Shape TopoShape::getSubShape(TopAbs_ShapeEnum type, int index, bool silen if(index <= 0) { if(silent) return {}; + // TODO: Is this message clear? Should we complain about the negative index instead Standard_Failure::Raise("Unsupported sub-shape type"); } diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index 0aaa5d9fe7..2a0a730621 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -224,9 +224,40 @@ public: std::vector& PointNormals, std::vector& faces) const override; //@} - /// get the Topo"sub"Shape with the given name + /** + * Locate the TopoDS_Shape associated with a Topo"sub"Shape of the given name + * @param Type The complete name of the subshape - for example "Face2" + * @param silent True to suppress the exception throw if the shape isn't found + * @return The shape or a null TopoDS_Shape + */ TopoDS_Shape getSubShape(const char* Type, bool silent = false) const; + /** + * Locate a subshape's TopoDS_Shape by type enum and index. See doc above. + * @param type Shape type enum value + * @param idx Index number of the subshape within the shape + * @param silent True to suppress the exception throw + * @return The shape, or a null TopoShape. + */ TopoDS_Shape getSubShape(TopAbs_ShapeEnum type, int idx, bool silent = false) const; + /** + * Locate a subshape by name within this shape. If null or empty Type specified, try my own + * shapeType; if I'm not a COMPOUND OR COMPSOLID, return myself; otherwise, look to see if I + * have any singular SOLID, SHELL, FACE, WIRE, EDGE or VERTEX and return that. + * If a Type is specified, then treat it as the complete name of the subshape - for example + * "Face3" and try to find and return that shape. + * @param Type The Shape name + * @param silent True to suppress the exception throw if the shape isn't found. + * @return The shape or a null TopoShape. + */ + TopoShape getSubTopoShape(const char* Type, bool silent = false) const; + /** + * Locate a subshape by type enum and index. See doc above. + * @param type Shape type enum value + * @param idx Index number of the subshape within the shape + * @param silent True to suppress the exception throw + * @return The shape, or a null TopoShape. + */ + TopoShape getSubTopoShape(TopAbs_ShapeEnum type, int idx, bool silent = false) const; std::vector getSubTopoShapes(TopAbs_ShapeEnum type = TopAbs_SHAPE) const; std::vector getSubShapes(TopAbs_ShapeEnum type = TopAbs_SHAPE) const; unsigned long countSubShapes(const char* Type) const; @@ -697,6 +728,7 @@ public: void mapSubElementsTo(std::vector& shapes, const char* op = nullptr) const; bool hasPendingElementMap() const; + /** Helper class to return the generated and modified shape given an input shape * * Shape history information is extracted using OCCT APIs diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index 3194b0b76f..3acd96d0de 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -1318,6 +1318,91 @@ void addShapesToBuilder(const std::vector& shapes, } } // namespace +// TODO: Can this be consolidated with getSubShape()? Empty Parm Logic is a little different. +TopoShape TopoShape::getSubTopoShape(const char* Type, bool silent) const +{ + if (!Type || !Type[0]) { + switch (shapeType(true)) { + case TopAbs_COMPOUND: + case TopAbs_COMPSOLID: + if (countSubShapes(TopAbs_SOLID) == 1) { + return getSubTopoShape(TopAbs_SOLID, 1); + } + if (countSubShapes(TopAbs_SHELL) == 1) { + return getSubTopoShape(TopAbs_SHELL, 1); + } + if (countSubShapes(TopAbs_FACE) == 1) { + return getSubTopoShape(TopAbs_FACE, 1); + } + if (countSubShapes(TopAbs_WIRE) == 1) { + return getSubTopoShape(TopAbs_WIRE, 1); + } + if (countSubShapes(TopAbs_EDGE) == 1) { + return getSubTopoShape(TopAbs_EDGE, 1); + } + if (countSubShapes(TopAbs_VERTEX) == 1) { + return getSubTopoShape(TopAbs_VERTEX, 1); + } + break; + default: + break; + } + return *this; + } + + Data::MappedElement mapped = getElementName(Type); + if (!mapped.index && boost::starts_with(Type, elementMapPrefix())) { + if (!silent) { + FC_THROWM(Base::CADKernelError, "Mapped element not found: " << Type); + } + return TopoShape(); + } + + auto res = shapeTypeAndIndex(Type); + if (res.second <= 0) { + if (!silent) { + FC_THROWM(Base::ValueError, "Invalid shape name " << (Type ? Type : "")); + } + return TopoShape(); + } + return getSubTopoShape(res.first, res.second, silent); +} + +// TODO: Can this be consolidated with getSubShape()? We use ancestry; other uses current shape. +TopoShape TopoShape::getSubTopoShape(TopAbs_ShapeEnum type, int idx, bool silent) const +{ + if (isNull()) { + if (!silent) { + FC_THROWM(NullShapeException, "null shape"); + } + return TopoShape(); + } + if (idx <= 0) { + if (!silent) { + FC_THROWM(Base::ValueError, "Invalid shape index " << idx); + } + return TopoShape(); + } + if (type < 0 || type > TopAbs_SHAPE) { + if (!silent) { + FC_THROWM(Base::ValueError, "Invalid shape type " << type); + } + return TopoShape(); + } + initCache(); + auto& shapeMap = _cache->getAncestry(type); + if (idx > shapeMap.count()) { + if (!silent) { + FC_THROWM(Base::IndexError, + "Shape index " << idx << " out of bound " << shapeMap.count()); + } + return TopoShape(); + } + + return shapeMap.getTopoShape(*this, idx); +} + + TopoShape& TopoShape::makeElementCompound(const std::vector& shapes, const char* op, SingleShapeCompoundCreationPolicy policy) diff --git a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp index b047f62eb0..e72288d45e 100644 --- a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -525,6 +525,74 @@ TEST_F(TopoShapeExpansionTest, splitWires) // splitWires with all four reorientation values NoReorient, ReOrient, ReorientForward, // ReorientReversed +TEST_F(TopoShapeExpansionTest, getSubTopoShapeByEnum) +{ + // Arrange + auto [cube1, cube2] = CreateTwoCubes(); + TopoShape cube1TS {cube1}; + cube1TS.Tag = 1L; + + // Act + auto subShape = cube1TS.getSubTopoShape(TopAbs_FACE, 1); + auto subShape2 = cube1TS.getSubTopoShape(TopAbs_FACE, 2); + auto subShape3 = cube1TS.getSubTopoShape(TopAbs_FACE, 6); + auto noshape1 = cube1TS.getSubTopoShape(TopAbs_FACE, 7, true); + // Assert + EXPECT_EQ(subShape.getShape().ShapeType(), TopAbs_FACE); + EXPECT_EQ(subShape2.getShape().ShapeType(), TopAbs_FACE); + EXPECT_EQ(subShape2.getShape().ShapeType(), TopAbs_FACE); + EXPECT_TRUE(noshape1.isNull()); + EXPECT_THROW(cube1TS.getSubTopoShape(TopAbs_FACE, 7), Base::IndexError); // Out of range +} + +TEST_F(TopoShapeExpansionTest, getSubTopoShapeByStringDefaults) +{ + // Arrange + auto [cube1, cube2] = CreateTwoCubes(); + Part::TopoShape cube1TS {cube1}; + cube1TS.Tag = 1L; + const float Len = 3; + const float Wid = 2; + auto [face1, wire1, edge1, edge2, edge3, edge4] = CreateRectFace(Len, Wid); + TopoDS_Compound compound1; + TopoDS_Builder builder {}; + builder.MakeCompound(compound1); + builder.Add(compound1, face1); + TopoShape topoShape {compound1, 2L}; + // Act + auto subShape = cube1TS.getSubTopoShape(nullptr); + auto subShape1 = cube1TS.getSubTopoShape(""); + auto subShape2 = topoShape.getSubTopoShape(nullptr); + // Assert + EXPECT_TRUE(subShape.getShape().IsEqual(cube1TS.getShape())); + EXPECT_EQ(subShape.getShape().ShapeType(), TopAbs_SOLID); + EXPECT_TRUE(subShape1.getShape().IsEqual(cube1TS.getShape())); + EXPECT_EQ(subShape1.getShape().ShapeType(), TopAbs_SOLID); + EXPECT_TRUE(subShape2.getShape().IsEqual(face1)); + EXPECT_EQ(subShape2.getShape().ShapeType(), TopAbs_FACE); +} + +TEST_F(TopoShapeExpansionTest, getSubTopoShapeByStringNames) +{ + // Arrange + auto [cube1, cube2] = CreateTwoCubes(); + TopoShape cube1TS {cube1}; + cube1TS.Tag = 1; + + // Act + auto subShape = cube1TS.getSubTopoShape("Face1"); + auto subShape2 = cube1TS.getSubTopoShape("Face2"); + auto subShape3 = cube1TS.getSubTopoShape("Face3"); + auto noshape1 = cube1TS.getSubTopoShape("Face7", true); // Out of range + // Assert + EXPECT_EQ(subShape.getShape().ShapeType(), TopAbs_FACE); + EXPECT_EQ(subShape2.getShape().ShapeType(), TopAbs_FACE); + EXPECT_EQ(subShape3.getShape().ShapeType(), TopAbs_FACE); + EXPECT_TRUE(noshape1.isNull()); + EXPECT_THROW(cube1TS.getSubTopoShape("Face7"), Base::IndexError); // Out of range + EXPECT_THROW(cube1TS.getSubTopoShape("WOOHOO", false), Base::ValueError); // Invalid +} + TEST_F(TopoShapeExpansionTest, mapSubElementInvalidParm) { // Arrange