diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index 756d3e5766..75d0b8f597 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -140,6 +140,24 @@ enum class LinearizeEdge linearizeEdges }; +enum class IsSolid +{ + notSolid, + solid +}; + +enum class IsRuled +{ + notRuled, + ruled +}; + +enum class IsClosed +{ + notClosed, + closed +}; + /** The representation for a CAD Shape */ // NOLINTNEXTLINE cppcoreguidelines-special-member-functions @@ -793,10 +811,10 @@ public: * type, but only wires are used. The first and last * section may be vertex. * @param isSolid: whether to make a solid - * @param isRuled: If true, then the faces generated between the edges of + * @param isRuled: if isRuled then the faces generated between the edges of * two consecutive section wires are ruled surfaces. If - * false, then they are smoothed out by approximation - * @param isClosed: If true, then the first section is duplicated to close + * notRuled, then they are smoothed out by approximation + * @param isClosed: If isClosed, then the first section is duplicated to close * the loft as the last section * @param maxDegree: define the maximal U degree of the result surface * @param op: optional string to be encoded into topo naming for indicating @@ -807,8 +825,8 @@ public: * a self reference so that multiple operations can be carried out * for the same shape in the same line of code. */ - TopoShape &makELoft(const std::vector &sources, - Standard_Boolean isSolid, Standard_Boolean isRuled, Standard_Boolean isClosed=Standard_False, + TopoShape &makeElementLoft(const std::vector &sources, + IsSolid isSolid, IsRuled isRuled, IsClosed isClosed=IsClosed::notClosed, Standard_Integer maxDegree=5, const char *op=nullptr); /** Make a ruled surface diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index d3bca4b2cf..4a3d3eb431 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -1737,8 +1737,8 @@ TopoShape& TopoShape::makeElementRuledSurface(const std::vector& shap if (orientation == 0) { // Automatic - Handle(Adaptor3d_Curve) a1; - Handle(Adaptor3d_Curve) a2; + Handle(Adaptor3d_HCurve) a1; + Handle(Adaptor3d_HCurve) a2; if (!isWire) { BRepAdaptor_HCurve adapt1(TopoDS::Edge(S1.getShape())); BRepAdaptor_HCurve adapt2(TopoDS::Edge(S2.getShape())); @@ -1840,35 +1840,43 @@ TopoShape& TopoShape::makeElementCompound(const std::vector& shapes, return *this; } -static std::vector prepareProfiles(const std::vector &shapes,size_t offset=0) { +static std::vector prepareProfiles(const std::vector& shapes, + size_t offset = 0) +{ std::vector ret; - for(size_t i=offset;i &shapes, - Standard_Boolean isSolid, - Standard_Boolean isRuled, - Standard_Boolean isClosed, - Standard_Integer maxDegree, - const char *op) +TopoShape& TopoShape::makeElementLoft(const std::vector& shapes, + IsSolid isSolid, + IsRuled isRuled, + IsClosed isClosed, + Standard_Integer maxDegree, + const char* op) { - if(!op) op = Part::OpCodes::Loft; + if (!op) { + op = Part::OpCodes::Loft; + } // http://opencascade.blogspot.com/2010/01/surface-modeling-part5.html - BRepOffsetAPI_ThruSections aGenerator (isSolid,isRuled); + BRepOffsetAPI_ThruSections aGenerator(isSolid == IsSolid::solid, isRuled == IsRuled::ruled); aGenerator.SetMaxDegree(maxDegree); auto profiles = prepareProfiles(shapes); - if (shapes.size() < 2) - FC_THROWM(Base::CADKernelError,"Need at least two vertices, edges or wires to create loft face"); + if (shapes.size() < 2) { + FC_THROWM(Base::CADKernelError, + "Need at least two vertices, edges or wires to create loft face"); + } - for(auto &sh : profiles) { - const auto &shape = sh.getShape(); - if(shape.ShapeType() == TopAbs_VERTEX) - aGenerator.AddVertex(TopoDS::Vertex (shape)); - else - aGenerator.AddWire(TopoDS::Wire (shape)); + for (auto& sh : profiles) { + const auto& shape = sh.getShape(); + if (shape.ShapeType() == TopAbs_VERTEX) { + aGenerator.AddVertex(TopoDS::Vertex(shape)); + } + else { + aGenerator.AddWire(TopoDS::Wire(shape)); + } } // close loft by duplicating initial profile as last profile. not perfect. - if (isClosed) { + if (isClosed == IsClosed::closed) { /* can only close loft in certain combinations of Vertex/Wire(Edge): - V1-W1-W2-W3-V2 ==> V1-W1-W2-W3-V2-V1 invalid closed - V1-W1-W2-W3 ==> V1-W1-W2-W3-V1 valid closed - W1-W2-W3-V1 ==> W1-W2-W3-V1-W1 invalid closed - W1-W2-W3 ==> W1-W2-W3-W1 valid closed*/ if (profiles.back().getShape().ShapeType() == TopAbs_VERTEX) { - Base::Console().Message("TopoShape::makeLoft: can't close Loft with Vertex as last profile. 'Closed' ignored.\n"); + Base::Console().Message("TopoShape::makeLoft: can't close Loft with Vertex as last " + "profile. 'Closed' ignored.\n"); } else { // repeat Add logic above for first profile const TopoDS_Shape& firstProfile = profiles.front().getShape(); - if (firstProfile.ShapeType() == TopAbs_VERTEX) { - aGenerator.AddVertex(TopoDS::Vertex (firstProfile)); + if (firstProfile.ShapeType() == TopAbs_VERTEX) { + aGenerator.AddVertex(TopoDS::Vertex(firstProfile)); } - else if (firstProfile.ShapeType() == TopAbs_EDGE) { + else if (firstProfile.ShapeType() == TopAbs_EDGE) { aGenerator.AddWire(BRepBuilderAPI_MakeWire(TopoDS::Edge(firstProfile)).Wire()); } - else if (firstProfile.ShapeType() == TopAbs_WIRE) { - aGenerator.AddWire(TopoDS::Wire (firstProfile)); + else if (firstProfile.ShapeType() == TopAbs_WIRE) { + aGenerator.AddWire(TopoDS::Wire(firstProfile)); } } } Standard_Boolean anIsCheck = Standard_True; - aGenerator.CheckCompatibility (anIsCheck); // use BRepFill_CompatibleWires on profiles. force #edges, orientation, "origin" to match. + aGenerator.CheckCompatibility(anIsCheck); // use BRepFill_CompatibleWires on profiles. force + // #edges, orientation, "origin" to match. aGenerator.Build(); - return makeShapeWithElementMap(aGenerator.Shape(),MapperThruSections(aGenerator,profiles),shapes,op); + return makeShapeWithElementMap(aGenerator.Shape(), + MapperThruSections(aGenerator, profiles), + shapes, + op); } TopoShape& TopoShape::makeElementDraft(const TopoShape& shape, diff --git a/tests/src/Mod/Part/App/PartTestHelpers.cpp b/tests/src/Mod/Part/App/PartTestHelpers.cpp index feb73e8d10..7e385bf465 100644 --- a/tests/src/Mod/Part/App/PartTestHelpers.cpp +++ b/tests/src/Mod/Part/App/PartTestHelpers.cpp @@ -1,8 +1,5 @@ // SPDX-License-Identifier: LGPL-2.1-or-later -#include -#include - #include "PartTestHelpers.h" // NOLINTBEGIN(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers) diff --git a/tests/src/Mod/Part/App/PartTestHelpers.h b/tests/src/Mod/Part/App/PartTestHelpers.h index a10e1f9e36..85a7958dda 100644 --- a/tests/src/Mod/Part/App/PartTestHelpers.h +++ b/tests/src/Mod/Part/App/PartTestHelpers.h @@ -9,12 +9,15 @@ #include "Mod/Part/App/FeaturePartBox.h" #include "Mod/Part/App/FeaturePartFuse.h" #include "Mod/Part/App/FeatureFillet.h" -#include #include #include +#include #include +#include +#include #include #include +#include namespace PartTestHelpers { diff --git a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp index aa86b7dab7..875f72ce54 100644 --- a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -1185,4 +1185,48 @@ TEST_F(TopoShapeExpansionTest, makeElementRuledSurfaceWires) EXPECT_FLOAT_EQ(getArea(result.getShape()), 2.023056); } +TEST_F(TopoShapeExpansionTest, makeElementLoft) +{ + // Loft must have either all open or all closed sections to work, we'll do two closed. + // Arrange + const float Len = 5; + const float Wid = 5; + auto [face1, wire1, edge1, edge2, edge3, edge4] = CreateRectFace(Len, Wid); + auto transform {gp_Trsf()}; + transform.SetTranslation(gp_Pnt(0.0, 0.0, 0.0), gp_Pnt(0.0, 0.0, 10.0)); + auto wire2 = wire1; // Shallow copy + wire2.Move(TopLoc_Location(transform)); + TopoShape wire1ts {wire1, + 1L}; // One of these shapes should have a tag or we won't get an Element Map + TopoShape wire2ts { + wire2, + 2L}; // If you change either tag or eliminate one it changes the resulting name. + std::vector shapes = {wire1ts, wire2ts}; + // Act + auto& topoShape = + (new TopoShape())->makeElementLoft(shapes, IsSolid::notSolid, IsRuled::notRuled); + auto& topoShape2 = + (new TopoShape())->makeElementLoft(shapes, IsSolid::solid, IsRuled::notRuled); + auto& topoShape3 = + (new TopoShape())->makeElementLoft(shapes, IsSolid::notSolid, IsRuled::ruled); + auto& topoShape4 = (new TopoShape())->makeElementLoft(shapes, IsSolid::solid, IsRuled::ruled); + auto& topoShape5 = + (new TopoShape()) + ->makeElementLoft(shapes, IsSolid::notSolid, IsRuled::notRuled, IsClosed::closed); + auto elements = elementMap((topoShape)); + // Assert that we haven't broken the basic Loft functionality + EXPECT_EQ(topoShape.countSubElements("Wire"), 4); + EXPECT_FLOAT_EQ(getArea(topoShape.getShape()), 200); + EXPECT_FLOAT_EQ(getVolume(topoShape.getShape()), 166.66667); + EXPECT_FLOAT_EQ(getVolume(topoShape2.getShape()), 250); + EXPECT_FLOAT_EQ(getVolume(topoShape3.getShape()), 166.66667); + EXPECT_FLOAT_EQ(getVolume(topoShape4.getShape()), 250); + EXPECT_NEAR(getVolume(topoShape5.getShape()), 0, 1e-07); + // Assert that we're creating a correct element map + EXPECT_TRUE(topoShape.getMappedChildElements().empty()); + EXPECT_EQ(elements.size(), 24); + EXPECT_EQ(elements.count(IndexedName("Edge", 1)), 1); + EXPECT_EQ(elements[IndexedName("Edge", 1)], MappedName("Edge1;LFT;:H1:4,E")); +} + // NOLINTEND(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers) diff --git a/tests/src/Mod/Part/App/TopoShapeMakeShape.cpp b/tests/src/Mod/Part/App/TopoShapeMakeShape.cpp index 11dd498309..559768402b 100644 --- a/tests/src/Mod/Part/App/TopoShapeMakeShape.cpp +++ b/tests/src/Mod/Part/App/TopoShapeMakeShape.cpp @@ -8,8 +8,6 @@ #include "PartTestHelpers.h" #include -#include - using namespace Data; using namespace Part; using namespace PartTestHelpers;