From da53249e3e74aca3a05fbb095ee43ff097535d19 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Fri, 23 Feb 2024 17:52:36 -0500 Subject: [PATCH 1/2] Toposhape/Part: Transfer in makEFilledFace and makEBSplineFace --- src/Mod/Part/App/TopoShape.h | 99 +++++- src/Mod/Part/App/TopoShapeExpansion.cpp | 403 +++++++++++++++++++++++- src/Mod/Part/App/TopoShapeMapper.h | 6 +- 3 files changed, 487 insertions(+), 21 deletions(-) diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index 1a599598ec..b5840144f3 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -226,6 +226,16 @@ enum class Copy copy }; +/// Filling style when making a BSpline face +enum FillingStyle { + /// The style with the flattest patches + stretch, + /// A rounded style of patch with less depth than those of Curved + coons, + /// The style with the most rounded patches + curved, +}; + /** The representation for a CAD Shape */ // NOLINTNEXTLINE cppcoreguidelines-special-member-functions @@ -1942,16 +1952,64 @@ public: return TopoShape(0, Hasher).makeElementFace(*this, op, maker, plane); } - /// Filling style when making a BSpline face - enum class FillingStyle + /** Make a face with BSpline (or Bezier) surface + * + * @param shapes: input shapes of any type, but only edges inside the shape + * will be used. + * @param style: surface filling style. @sa FillingStyle + * @param keepBezier: whether to create Bezier surface if the input edge + * has Bezier curve. + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * + * @return The function creates a face with either BSpline or Bezier + * surface. 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 &makeElementBSplineFace(const std::vector &input, + FillingStyle style = FillingStyle::stretch, + bool keepBezier = false, + const char *op=nullptr); + /** Make a face with BSpline (or Bezier) surface + * + * @param shape: input shape of any type, but only edges inside the shape + * will be used. + * @param style: surface filling style. @sa FillingStyle + * @param keepBezier: whether to create Bezier surface if the input edge + * has Bezier curve. + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * + * @return The function creates a face with either BSpline or Bezier + * surface. 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 &makeElementBSplineFace(const TopoShape &input, + FillingStyle style = FillingStyle::stretch, + bool keepBezier = false, + const char *op=nullptr); + /** Make a face with BSpline (or Bezier) surface + * + * @param style: surface filling style. @sa FillingStyle + * @param keepBezier: whether to create Bezier surface if the input edge + * has Bezier curve. + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * + * @return The function returns a new face with either BSpline or Bezier + * surface. The shape itself is not modified. + */ + TopoShape makeElementBSplineFace(FillingStyle style = FillingStyle::stretch, + bool keepBezier = false, + const char *op=nullptr) { - /// The style with the flattest patches - Stretch, - /// A rounded style of patch with less depth than those of Curved - Coons, - /// The style with the most rounded patches - Curved, - }; + return TopoShape(0,Hasher).makeElementBSplineFace(*this, style, keepBezier, op); + } + struct BRepFillingParams; @@ -1988,6 +2046,29 @@ public: CN, }; + /** Make a non-planar filled face with boundary and/or constraint edge/wire + * + * @param shapes: input shapes of any type. The function will automatically + * discover connected and closed edges to be used as the + * boundary of the the new face. Any other vertex, edge, + * and/or face will be used as constraints to fine tune the + * surface generation. + * @param params: @sa BRepFillingParams + * @param op: optional string to be encoded into topo naming for indicating + * the operation + * + * @return The function creates a face with BSpline surface. 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. + * + * @sa OCCT BRepOffsetAPI_MakeFilling + */ + TopoShape &makeElementFilledFace(const std::vector &shapes, + const BRepFillingParams ¶ms, + const char *op=nullptr); + /** Make a solid using shells or CompSolid * * @param shapes: input shapes of either shells or CompSolid. diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index 33db6f0c46..418a3cb3f7 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -50,6 +50,8 @@ #include #include #include +#include +#include #include #include #include @@ -57,14 +59,22 @@ #include #include #include +#include #include #include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include #include #include -#include -#include -#include #include #include @@ -88,10 +98,6 @@ #include "Geometry.h" #include -#include -#include -#include -#include FC_LOG_LEVEL_INIT("TopoShape", true, true) // NOLINT @@ -1035,7 +1041,7 @@ struct ShapeInfo }; //////////////////////////////////////// -// makESHAPE -> makeShapeWithElementMap +// makeElementSHAPE -> makeShapeWithElementMap /////////////////////////////////////// struct NameKey @@ -2804,6 +2810,167 @@ struct MapperPrism: MapperMaker { } }; +TopoShape &TopoShape::makeElementFilledFace(const std::vector &_shapes, + const BRepFillingParams ¶ms, + const char *op) +{ + if(!op) + op = Part::OpCodes::FilledFace; + BRepOffsetAPI_MakeFilling maker(params.degree, + params.ptsoncurve, + params.numiter, + params.anisotropy, + params.tol2d, + params.tol3d, + params.tolG1, + params.tolG2, + params.maxdeg, + params.maxseg); + + if (!params.surface.isNull() && params.surface.getShape().ShapeType() == TopAbs_FACE) + maker.LoadInitSurface(TopoDS::Face(params.surface.getShape())); + + std::vector shapes; + for(auto &s : _shapes) + expandCompound(s,shapes); + + TopoShapeMap output; + auto getOrder = [&](const TopoDS_Shape &s) { + auto it = params.orders.find(s); + if (it == params.orders.end()) { + auto iter = output.find(s); + if (iter != output.end()) + it = params.orders.find(iter->second.getShape()); + } + if (it != params.orders.end()) + return static_cast(it->second); + return GeomAbs_C0; + }; + + auto getSupport = [&](const TopoDS_Shape &s) { + TopoDS_Face support; + auto it = params.supports.find(s); + if (it == params.supports.end()) { + auto iter = output.find(s); + if (iter != output.end()) + it = params.supports.find(iter->second.getShape()); + } + if (it != params.supports.end()) { + if (!it->second.IsNull() && it->second.ShapeType() == TopAbs_FACE) + support = TopoDS::Face(it->second); + } + return support; + }; + + auto findBoundary = [](std::vector &shapes) -> TopoShape { + // Find a wire (preferably a closed one) to be used as the boundary. + int i = -1; + int boundIdx = -1; + for (auto &s : shapes) { + ++i; + if(s.isNull() || !s.hasSubShape(TopAbs_EDGE) || s.shapeType()!=TopAbs_WIRE) + continue; + if (BRep_Tool::IsClosed(TopoDS::Wire(s.getShape()))) { + boundIdx = i; + break; + } else if (boundIdx < 0) + boundIdx = i; + } + if (boundIdx >= 0) { + auto res = shapes[boundIdx]; + shapes.erase(shapes.begin() + boundIdx); + return res; + } + return TopoShape(); + }; + + TopoShape bound; + std::vector wires; + if (params.boundary_begin >= 0 + && params.boundary_end > params.boundary_begin + && params.boundary_end <= (int)shapes.size()) + { + if (params.boundary_end-1 != params.boundary_begin + || shapes[params.boundary_begin].shapeType() != TopAbs_WIRE) + { + std::vector edges; + edges.insert(edges.end(), + shapes.begin()+params.boundary_begin, + shapes.begin()+params.boundary_end); + wires = TopoShape(0, Hasher).makeElementWires(edges,"",0.0,ConnectionPolicy::requireSharedVertex,&output).getSubTopoShapes(TopAbs_WIRE); + shapes.erase(shapes.begin()+params.boundary_begin, + shapes.begin()+params.boundary_end); + } + } else { + bound = findBoundary(shapes); + if (bound.isNull()) { + // If no boundary is found, then try to build one. + std::vector edges; + for(auto it=shapes.begin(); it!=shapes.end();) { + if (it->shapeType(true) == TopAbs_EDGE) { + edges.push_back(*it); + it = shapes.erase(it); + } else + ++it; + } + if(edges.size()) + wires = TopoShape(0, Hasher).makeElementWires(edges,"",0.0,ConnectionPolicy::requireSharedVertex,&output).getSubTopoShapes(TopAbs_WIRE); + } + } + + if (bound.isNull()) + bound = findBoundary(wires); + + if (bound.isNull()) + FC_THROWM(Base::CADKernelError,"No boundary wire"); + + // Since we've only selected one wire for boundary, return all the + // other edges in shapes to be added as non boundary constraints + shapes.insert(shapes.end(), wires.begin(), wires.end()); + + // Must fix wire connection to avoid OCC crash in BRepFill_Filling.cxx WireFromList() + // https://github.com/Open-Cascade-SAS/OCCT/blob/1c96596ae7ba120a678021db882857e289c73947/src/BRepFill/BRepFill_Filling.cxx#L133 + // The reason of crash is because the wire connection tolerance is too big. + // The crash can be fixed by simply checking itl.More() before calling Remove(). + bound.fix(Precision::Confusion(), + Precision::Confusion(), + Precision::Confusion()); + + for (const auto &e : bound.getOrderedEdges()) { + maker.Add(TopoDS::Edge(e.getShape()), + getSupport(e.getShape()), + getOrder(e.getShape()), + /*IsBound*/Standard_True); + } + + for(const auto &s : shapes) { + if(s.isNull()) + continue; + const auto &sh = s.getShape(); + if (sh.ShapeType() == TopAbs_WIRE) { + for (const auto &e : s.getSubShapes(TopAbs_EDGE)) + maker.Add(TopoDS::Edge(e), + getSupport(e), + getOrder(e), + /*IsBound*/Standard_False); + } + else if (sh.ShapeType() == TopAbs_EDGE) + maker.Add(TopoDS::Edge(sh), + getSupport(sh), + getOrder(sh), + /*IsBound*/Standard_False); + else if (sh.ShapeType() == TopAbs_FACE) + maker.Add(TopoDS::Face(sh), getOrder(sh)); + else if (sh.ShapeType() == TopAbs_VERTEX) + maker.Add(BRep_Tool::Pnt(TopoDS::Vertex(sh))); + } + + maker.Build(); + if (!maker.IsDone()) + FC_THROWM(Base::CADKernelError,"Failed to created face by filling edges"); + return makeElementShape(maker,_shapes,op); +} + // TODO: This method does not appear to ever be called in the codebase, and it is probably // broken, because using TopoShape() with no parameters means the result will not have an // element Map. @@ -3555,6 +3722,226 @@ TopoShape& TopoShape::makeElementRefine(const TopoShape& shape, const char* op, } +TopoShape & TopoShape::makeElementBSplineFace(const TopoShape & shape, + FillingStyle style, + bool keepBezier, + const char *op) +{ + std::vector input(1, shape); + return makeElementBSplineFace(input, style, keepBezier, op); +} + +TopoShape & TopoShape::makeElementBSplineFace(const std::vector &input, + FillingStyle style, + bool keepBezier, + const char *op) +{ + std::vector edges; + for (auto &s : input) { + auto e = s.getSubTopoShapes(TopAbs_EDGE); + edges.insert(edges.end(), e.begin(), e.end()); + } + + if (edges.size() == 1 && edges[0].isClosed()) { + auto edge = edges[0].getSubShape(TopAbs_EDGE, 1); + auto e = TopoDS::Edge(edge); + auto v = TopExp::FirstVertex(e); + Standard_Real first, last; + Handle(Geom_Curve) curve = BRep_Tool::Curve(e, first, last); + + BRepBuilderAPI_MakeEdge mk1,mk2,mk3,mk4; + Handle(Geom_BSplineCurve) bspline = Handle(Geom_BSplineCurve)::DownCast(curve); + if (bspline.IsNull()) { + ShapeConstruct_Curve scc; + bspline = scc.ConvertToBSpline(curve, first, last, Precision::Confusion()); + if (bspline.IsNull()) + FC_THROWM(Base::CADKernelError, "Failed to convert edge to bspline"); + first = bspline->FirstParameter(); + last = bspline->LastParameter(); + } + auto step = (last - first) * 0.25; + auto m1 = first + step; + auto m2 = m1 + step; + auto m3 = m2 + step; + auto c1 = GeomConvert::SplitBSplineCurve(bspline, first, m1, Precision::Confusion()); + auto c2 = GeomConvert::SplitBSplineCurve(bspline, m1, m2, Precision::Confusion()); + auto c3 = GeomConvert::SplitBSplineCurve(bspline, m2, m3, Precision::Confusion()); + auto c4 = GeomConvert::SplitBSplineCurve(bspline, m3, last, Precision::Confusion()); + mk1.Init(c1); + mk2.Init(c2); + mk3.Init(c3); + mk4.Init(c4); + + if(!mk1.IsDone() || !mk2.IsDone() || !mk3.IsDone() || !mk4.IsDone()) + FC_THROWM(Base::CADKernelError, "Failed to split edge"); + + auto e1 = mk1.Edge(); + auto e2 = mk2.Edge(); + auto e3 = mk3.Edge(); + auto e4 = mk4.Edge(); + + ShapeMapper mapper; + mapper.populate(MappingStatus::Modified, e, {e1, e2, e3, e4}); + mapper.populate(MappingStatus::Generated, v, {TopExp::FirstVertex(e1)}); + mapper.populate(MappingStatus::Generated, v, {TopExp::LastVertex(e4)}); + + BRep_Builder builder; + TopoDS_Compound comp; + builder.MakeCompound(comp); + builder.Add(comp, e1); + builder.Add(comp, e2); + builder.Add(comp, e3); + builder.Add(comp, e4); + + TopoShape s; + s.makeShapeWithElementMap(comp, mapper, edges, Part::OpCodes::Split); + return makeElementBSplineFace(s, style, op); + } + + if (edges.size() < 2 || edges.size() > 4) + FC_THROWM(Base::CADKernelError, "Require minimum one, maximum four edges"); + + GeomFill_FillingStyle fstyle; + switch (style) { + case coons: + fstyle = GeomFill_CoonsStyle; + break; + case curved: + fstyle = GeomFill_CurvedStyle; + break; + default: + fstyle = GeomFill_StretchStyle; + } + + Handle(Geom_Surface) aSurface; + + Standard_Real u1, u2; + if (keepBezier) { + std::vector curves; + curves.reserve(4); + for (const auto &e : edges) { + const TopoDS_Edge& edge = TopoDS::Edge (e.getShape()); + TopLoc_Location heloc; // this will be output + Handle(Geom_Curve) c_geom = BRep_Tool::Curve(edge, heloc, u1, u2); + Handle(Geom_BezierCurve) curve = Handle(Geom_BezierCurve)::DownCast(c_geom); + if (!curve) + break; + curve->Transform(heloc.Transformation()); // apply original transformation to control points + curves.push_back(curve); + } + if (curves.size() == edges.size()) { + GeomFill_BezierCurves aSurfBuilder; //Create Surface Builder + + if (edges.size() == 2) { + aSurfBuilder.Init(curves[0], curves[1], fstyle); + } + else if (edges.size() == 3) { + aSurfBuilder.Init(curves[0], curves[1], curves[2], fstyle); + } + else if (edges.size() == 4) { + aSurfBuilder.Init(curves[0], curves[1], curves[2], curves[3], fstyle); + } + aSurface = aSurfBuilder.Surface(); + } + } + + if (aSurface.IsNull()) { + std::vector curves; + curves.reserve(4); + for (const auto & e : edges) { + const TopoDS_Edge& edge = TopoDS::Edge (e.getShape()); + TopLoc_Location heloc; // this will be output + Handle(Geom_Curve) c_geom = BRep_Tool::Curve(edge, heloc, u1, u2); //The geometric curve + Handle(Geom_BSplineCurve) bspline = Handle(Geom_BSplineCurve)::DownCast(c_geom); //Try to get BSpline curve + if (!bspline.IsNull()) { + gp_Trsf transf = heloc.Transformation(); + bspline->Transform(transf); // apply original transformation to control points + //Store Underlying Geometry + curves.push_back(bspline); + } + else { + // try to convert it into a B-spline + BRepBuilderAPI_NurbsConvert mkNurbs(edge); + TopoDS_Edge nurbs = TopoDS::Edge(mkNurbs.Shape()); + // avoid copying + TopLoc_Location heloc2; // this will be output + Handle(Geom_Curve) c_geom2 = BRep_Tool::Curve(nurbs, heloc2, u1, u2); //The geometric curve + Handle(Geom_BSplineCurve) bspline2 = Handle(Geom_BSplineCurve)::DownCast(c_geom2); //Try to get BSpline curve + + if (!bspline2.IsNull()) { + gp_Trsf transf = heloc2.Transformation(); + bspline2->Transform(transf); // apply original transformation to control points + //Store Underlying Geometry + curves.push_back(bspline2); + } + else { + // BRepBuilderAPI_NurbsConvert failed, try ShapeConstruct_Curve now + ShapeConstruct_Curve scc; + Handle(Geom_BSplineCurve) spline = scc.ConvertToBSpline(c_geom, u1, u2, Precision::Confusion()); + if (spline.IsNull()) + Standard_Failure::Raise("A curve was not a B-spline and could not be converted into one."); + gp_Trsf transf = heloc2.Transformation(); + spline->Transform(transf); // apply original transformation to control points + curves.push_back(spline); + } + } + } + + GeomFill_BSplineCurves aSurfBuilder; //Create Surface Builder + + if (edges.size() == 2) { + aSurfBuilder.Init(curves[0], curves[1], fstyle); + } + else if (edges.size() == 3) { + aSurfBuilder.Init(curves[0], curves[1], curves[2], fstyle); + } + else if (edges.size() == 4) { + aSurfBuilder.Init(curves[0], curves[1], curves[2], curves[3], fstyle); + } + + aSurface = aSurfBuilder.Surface(); + } + + BRepBuilderAPI_MakeFace aFaceBuilder; + Standard_Real v1, v2; + // transfer surface bounds to face + aSurface->Bounds(u1, u2, v1, v2); + + aFaceBuilder.Init(aSurface, u1, u2, v1, v2, Precision::Confusion()); + + TopoShape aFace(0, Hasher, aFaceBuilder.Face()); + + if (!aFaceBuilder.IsDone()) { + FC_THROWM(Base::CADKernelError, "Face unable to be constructed"); + } + if (aFace.isNull()) { + FC_THROWM(Base::CADKernelError, "Resulting Face is null"); + } + + auto newEdges = aFace.getSubTopoShapes(TopAbs_EDGE); + if (newEdges.size() != edges.size()) + FC_WARN("Face edge count mismatch"); + else { + int i = 0; + for (auto &edge : newEdges) + edge.resetElementMap(edges[i++].elementMap()); + aFace.mapSubElement(newEdges); + } + + Data::ElementIDRefs sids; + Data::MappedName edgeName = aFace.getMappedName( + Data::IndexedName::fromConst("Edge",1), true, &sids); + aFace.setElementComboName(Data::IndexedName::fromConst("Face",1), + {edgeName}, + Part::OpCodes::BSplineFace, + op, + &sids); + *this = aFace; + return *this; +} + + + /** * Encode and set an element name in the elementMap. If a hasher is defined, apply it to the name. * diff --git a/src/Mod/Part/App/TopoShapeMapper.h b/src/Mod/Part/App/TopoShapeMapper.h index 5ec165efab..447b2d9537 100644 --- a/src/Mod/Part/App/TopoShapeMapper.h +++ b/src/Mod/Part/App/TopoShapeMapper.h @@ -244,8 +244,7 @@ struct PartExport GenericShapeMapper: ShapeMapper { }; /// Parameters for TopoShape::makeElementFilledFace() -struct PartExport TopoShape::BRepFillingParams -{ +struct PartExport TopoShape::BRepFillingParams { /** Optional initial surface to begin the construction of the surface for the filled face. * * It is useful if the surface resulting from construction for the @@ -269,8 +268,7 @@ struct PartExport TopoShape::BRepFillingParams std::unordered_map supports; /// Optional begin index to the input shapes to be used as the boundary of the filled face. int boundary_begin = -1; - /// Optional end index (last index + 1) to the input shapes to be used as the boundary of the - /// filled face. + /// Optional end index (last index + 1) to the input shapes to be used as the boundary of the filled face. int boundary_end = -1; /// The energe minimizing criterion degree; unsigned int degree = 3; From 15b026656465d13a7b1a2c4b5f1e8f79a898b60f Mon Sep 17 00:00:00 2001 From: bgbsww Date: Sat, 24 Feb 2024 11:46:49 -0500 Subject: [PATCH 2/2] Toponaming/Part: Add tests and clean makeElementBSplineFace and makeElementFilledFace --- src/Mod/Part/App/TopoShapeExpansion.cpp | 255 ++++++++++-------- src/Mod/Part/App/TopoShapeMapper.h | 6 +- tests/src/Mod/Part/App/TopoShapeExpansion.cpp | 80 +++++- 3 files changed, 233 insertions(+), 108 deletions(-) diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index 418a3cb3f7..b6829b3260 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -54,6 +54,7 @@ #include #include #include +#include #include #include #include @@ -75,9 +76,9 @@ #include #include #include - #include #include +#include #include #include @@ -1041,7 +1042,7 @@ struct ShapeInfo }; //////////////////////////////////////// -// makeElementSHAPE -> makeShapeWithElementMap +// makESHAPE -> makeShapeWithElementMap /////////////////////////////////////// struct NameKey @@ -2810,12 +2811,13 @@ struct MapperPrism: MapperMaker { } }; -TopoShape &TopoShape::makeElementFilledFace(const std::vector &_shapes, - const BRepFillingParams ¶ms, - const char *op) +TopoShape& TopoShape::makeElementFilledFace(const std::vector& _shapes, + const BRepFillingParams& params, + const char* op) { - if(!op) + if (!op) { op = Part::OpCodes::FilledFace; + } BRepOffsetAPI_MakeFilling maker(params.degree, params.ptsoncurve, params.numiter, @@ -2827,54 +2829,63 @@ TopoShape &TopoShape::makeElementFilledFace(const std::vector &_shape params.maxdeg, params.maxseg); - if (!params.surface.isNull() && params.surface.getShape().ShapeType() == TopAbs_FACE) + if (!params.surface.isNull() && params.surface.getShape().ShapeType() == TopAbs_FACE) { maker.LoadInitSurface(TopoDS::Face(params.surface.getShape())); + } std::vector shapes; - for(auto &s : _shapes) - expandCompound(s,shapes); + for (auto& s : _shapes) { + expandCompound(s, shapes); + } TopoShapeMap output; - auto getOrder = [&](const TopoDS_Shape &s) { + auto getOrder = [&](const TopoDS_Shape& s) { auto it = params.orders.find(s); if (it == params.orders.end()) { auto iter = output.find(s); - if (iter != output.end()) + if (iter != output.end()) { it = params.orders.find(iter->second.getShape()); + } } - if (it != params.orders.end()) + if (it != params.orders.end()) { return static_cast(it->second); + } return GeomAbs_C0; }; - auto getSupport = [&](const TopoDS_Shape &s) { + auto getSupport = [&](const TopoDS_Shape& s) { TopoDS_Face support; auto it = params.supports.find(s); if (it == params.supports.end()) { auto iter = output.find(s); - if (iter != output.end()) + if (iter != output.end()) { it = params.supports.find(iter->second.getShape()); + } } if (it != params.supports.end()) { - if (!it->second.IsNull() && it->second.ShapeType() == TopAbs_FACE) + if (!it->second.IsNull() && it->second.ShapeType() == TopAbs_FACE) { support = TopoDS::Face(it->second); + } } return support; }; - auto findBoundary = [](std::vector &shapes) -> TopoShape { + auto findBoundary = [](std::vector& shapes) -> TopoShape { // Find a wire (preferably a closed one) to be used as the boundary. int i = -1; int boundIdx = -1; - for (auto &s : shapes) { + for (auto& s : shapes) { ++i; - if(s.isNull() || !s.hasSubShape(TopAbs_EDGE) || s.shapeType()!=TopAbs_WIRE) + if (s.isNull() || !s.hasSubShape(TopAbs_EDGE) || s.shapeType() != TopAbs_WIRE) { continue; + } if (BRep_Tool::IsClosed(TopoDS::Wire(s.getShape()))) { boundIdx = i; break; - } else if (boundIdx < 0) + } + else if (boundIdx < 0) { boundIdx = i; + } } if (boundIdx >= 0) { auto res = shapes[boundIdx]; @@ -2886,43 +2897,58 @@ TopoShape &TopoShape::makeElementFilledFace(const std::vector &_shape TopoShape bound; std::vector wires; - if (params.boundary_begin >= 0 - && params.boundary_end > params.boundary_begin - && params.boundary_end <= (int)shapes.size()) - { - if (params.boundary_end-1 != params.boundary_begin - || shapes[params.boundary_begin].shapeType() != TopAbs_WIRE) - { + if (params.boundary_begin >= 0 && params.boundary_end > params.boundary_begin + && params.boundary_end <= (int)shapes.size()) { + if (params.boundary_end - 1 != params.boundary_begin + || shapes[params.boundary_begin].shapeType() != TopAbs_WIRE) { std::vector edges; edges.insert(edges.end(), - shapes.begin()+params.boundary_begin, - shapes.begin()+params.boundary_end); - wires = TopoShape(0, Hasher).makeElementWires(edges,"",0.0,ConnectionPolicy::requireSharedVertex,&output).getSubTopoShapes(TopAbs_WIRE); - shapes.erase(shapes.begin()+params.boundary_begin, - shapes.begin()+params.boundary_end); + shapes.begin() + params.boundary_begin, + shapes.begin() + params.boundary_end); + wires = TopoShape(0, Hasher) + .makeElementWires(edges, + "", + 0.0, + ConnectionPolicy::requireSharedVertex, + &output) + .getSubTopoShapes(TopAbs_WIRE); + shapes.erase(shapes.begin() + params.boundary_begin, + shapes.begin() + params.boundary_end); } - } else { + } + else { bound = findBoundary(shapes); if (bound.isNull()) { // If no boundary is found, then try to build one. std::vector edges; - for(auto it=shapes.begin(); it!=shapes.end();) { + for (auto it = shapes.begin(); it != shapes.end();) { if (it->shapeType(true) == TopAbs_EDGE) { edges.push_back(*it); it = shapes.erase(it); - } else + } + else { ++it; + } + } + if (edges.size()) { + wires = TopoShape(0, Hasher) + .makeElementWires(edges, + "", + 0.0, + ConnectionPolicy::requireSharedVertex, + &output) + .getSubTopoShapes(TopAbs_WIRE); } - if(edges.size()) - wires = TopoShape(0, Hasher).makeElementWires(edges,"",0.0,ConnectionPolicy::requireSharedVertex,&output).getSubTopoShapes(TopAbs_WIRE); } } - if (bound.isNull()) + if (bound.isNull()) { bound = findBoundary(wires); + } - if (bound.isNull()) - FC_THROWM(Base::CADKernelError,"No boundary wire"); + if (bound.isNull()) { + FC_THROWM(Base::CADKernelError, "No boundary wire"); + } // Since we've only selected one wire for boundary, return all the // other edges in shapes to be added as non boundary constraints @@ -2932,43 +2958,47 @@ TopoShape &TopoShape::makeElementFilledFace(const std::vector &_shape // https://github.com/Open-Cascade-SAS/OCCT/blob/1c96596ae7ba120a678021db882857e289c73947/src/BRepFill/BRepFill_Filling.cxx#L133 // The reason of crash is because the wire connection tolerance is too big. // The crash can be fixed by simply checking itl.More() before calling Remove(). - bound.fix(Precision::Confusion(), - Precision::Confusion(), - Precision::Confusion()); + bound.fix(Precision::Confusion(), Precision::Confusion(), Precision::Confusion()); - for (const auto &e : bound.getOrderedEdges()) { + for (const auto& e : bound.getOrderedEdges()) { maker.Add(TopoDS::Edge(e.getShape()), getSupport(e.getShape()), getOrder(e.getShape()), - /*IsBound*/Standard_True); + /*IsBound*/ Standard_True); } - for(const auto &s : shapes) { - if(s.isNull()) + for (const auto& s : shapes) { + if (s.isNull()) { continue; - const auto &sh = s.getShape(); + } + const auto& sh = s.getShape(); if (sh.ShapeType() == TopAbs_WIRE) { - for (const auto &e : s.getSubShapes(TopAbs_EDGE)) + for (const auto& e : s.getSubShapes(TopAbs_EDGE)) { maker.Add(TopoDS::Edge(e), getSupport(e), getOrder(e), - /*IsBound*/Standard_False); + /*IsBound*/ Standard_False); + } } - else if (sh.ShapeType() == TopAbs_EDGE) + else if (sh.ShapeType() == TopAbs_EDGE) { maker.Add(TopoDS::Edge(sh), getSupport(sh), getOrder(sh), - /*IsBound*/Standard_False); - else if (sh.ShapeType() == TopAbs_FACE) + /*IsBound*/ Standard_False); + } + else if (sh.ShapeType() == TopAbs_FACE) { maker.Add(TopoDS::Face(sh), getOrder(sh)); - else if (sh.ShapeType() == TopAbs_VERTEX) + } + else if (sh.ShapeType() == TopAbs_VERTEX) { maker.Add(BRep_Tool::Pnt(TopoDS::Vertex(sh))); + } } maker.Build(); - if (!maker.IsDone()) - FC_THROWM(Base::CADKernelError,"Failed to created face by filling edges"); - return makeElementShape(maker,_shapes,op); + if (!maker.IsDone()) { + FC_THROWM(Base::CADKernelError, "Failed to created face by filling edges"); + } + return makeElementShape(maker, _shapes, op); } // TODO: This method does not appear to ever be called in the codebase, and it is probably @@ -3721,23 +3751,22 @@ TopoShape& TopoShape::makeElementRefine(const TopoShape& shape, const char* op, return *this; } - -TopoShape & TopoShape::makeElementBSplineFace(const TopoShape & shape, - FillingStyle style, - bool keepBezier, - const char *op) +TopoShape& TopoShape::makeElementBSplineFace(const TopoShape& shape, + FillingStyle style, + bool keepBezier, + const char* op) { std::vector input(1, shape); return makeElementBSplineFace(input, style, keepBezier, op); } -TopoShape & TopoShape::makeElementBSplineFace(const std::vector &input, - FillingStyle style, - bool keepBezier, - const char *op) +TopoShape& TopoShape::makeElementBSplineFace(const std::vector& input, + FillingStyle style, + bool keepBezier, + const char* op) { std::vector edges; - for (auto &s : input) { + for (auto& s : input) { auto e = s.getSubTopoShapes(TopAbs_EDGE); edges.insert(edges.end(), e.begin(), e.end()); } @@ -3749,13 +3778,14 @@ TopoShape & TopoShape::makeElementBSplineFace(const std::vector &inpu Standard_Real first, last; Handle(Geom_Curve) curve = BRep_Tool::Curve(e, first, last); - BRepBuilderAPI_MakeEdge mk1,mk2,mk3,mk4; + BRepBuilderAPI_MakeEdge mk1, mk2, mk3, mk4; Handle(Geom_BSplineCurve) bspline = Handle(Geom_BSplineCurve)::DownCast(curve); if (bspline.IsNull()) { ShapeConstruct_Curve scc; bspline = scc.ConvertToBSpline(curve, first, last, Precision::Confusion()); - if (bspline.IsNull()) + if (bspline.IsNull()) { FC_THROWM(Base::CADKernelError, "Failed to convert edge to bspline"); + } first = bspline->FirstParameter(); last = bspline->LastParameter(); } @@ -3772,8 +3802,9 @@ TopoShape & TopoShape::makeElementBSplineFace(const std::vector &inpu mk3.Init(c3); mk4.Init(c4); - if(!mk1.IsDone() || !mk2.IsDone() || !mk3.IsDone() || !mk4.IsDone()) + if (!mk1.IsDone() || !mk2.IsDone() || !mk3.IsDone() || !mk4.IsDone()) { FC_THROWM(Base::CADKernelError, "Failed to split edge"); + } auto e1 = mk1.Edge(); auto e2 = mk2.Edge(); @@ -3798,8 +3829,9 @@ TopoShape & TopoShape::makeElementBSplineFace(const std::vector &inpu return makeElementBSplineFace(s, style, op); } - if (edges.size() < 2 || edges.size() > 4) - FC_THROWM(Base::CADKernelError, "Require minimum one, maximum four edges"); + if (edges.size() < 2 || edges.size() > 4) { + FC_THROWM(Base::CADKernelError, "Require minimum two, maximum four edges"); + } GeomFill_FillingStyle fstyle; switch (style) { @@ -3819,18 +3851,20 @@ TopoShape & TopoShape::makeElementBSplineFace(const std::vector &inpu if (keepBezier) { std::vector curves; curves.reserve(4); - for (const auto &e : edges) { - const TopoDS_Edge& edge = TopoDS::Edge (e.getShape()); - TopLoc_Location heloc; // this will be output + for (const auto& e : edges) { + const TopoDS_Edge& edge = TopoDS::Edge(e.getShape()); + TopLoc_Location heloc; // this will be output Handle(Geom_Curve) c_geom = BRep_Tool::Curve(edge, heloc, u1, u2); Handle(Geom_BezierCurve) curve = Handle(Geom_BezierCurve)::DownCast(c_geom); - if (!curve) + if (!curve) { break; - curve->Transform(heloc.Transformation()); // apply original transformation to control points + } + curve->Transform( + heloc.Transformation()); // apply original transformation to control points curves.push_back(curve); } if (curves.size() == edges.size()) { - GeomFill_BezierCurves aSurfBuilder; //Create Surface Builder + GeomFill_BezierCurves aSurfBuilder; // Create Surface Builder if (edges.size() == 2) { aSurfBuilder.Init(curves[0], curves[1], fstyle); @@ -3848,15 +3882,17 @@ TopoShape & TopoShape::makeElementBSplineFace(const std::vector &inpu if (aSurface.IsNull()) { std::vector curves; curves.reserve(4); - for (const auto & e : edges) { - const TopoDS_Edge& edge = TopoDS::Edge (e.getShape()); - TopLoc_Location heloc; // this will be output - Handle(Geom_Curve) c_geom = BRep_Tool::Curve(edge, heloc, u1, u2); //The geometric curve - Handle(Geom_BSplineCurve) bspline = Handle(Geom_BSplineCurve)::DownCast(c_geom); //Try to get BSpline curve + for (const auto& e : edges) { + const TopoDS_Edge& edge = TopoDS::Edge(e.getShape()); + TopLoc_Location heloc; // this will be output + Handle(Geom_Curve) c_geom = + BRep_Tool::Curve(edge, heloc, u1, u2); // The geometric curve + Handle(Geom_BSplineCurve) bspline = + Handle(Geom_BSplineCurve)::DownCast(c_geom); // Try to get BSpline curve if (!bspline.IsNull()) { gp_Trsf transf = heloc.Transformation(); - bspline->Transform(transf); // apply original transformation to control points - //Store Underlying Geometry + bspline->Transform(transf); // apply original transformation to control points + // Store Underlying Geometry curves.push_back(bspline); } else { @@ -3864,30 +3900,35 @@ TopoShape & TopoShape::makeElementBSplineFace(const std::vector &inpu BRepBuilderAPI_NurbsConvert mkNurbs(edge); TopoDS_Edge nurbs = TopoDS::Edge(mkNurbs.Shape()); // avoid copying - TopLoc_Location heloc2; // this will be output - Handle(Geom_Curve) c_geom2 = BRep_Tool::Curve(nurbs, heloc2, u1, u2); //The geometric curve - Handle(Geom_BSplineCurve) bspline2 = Handle(Geom_BSplineCurve)::DownCast(c_geom2); //Try to get BSpline curve + TopLoc_Location heloc2; // this will be output + Handle(Geom_Curve) c_geom2 = + BRep_Tool::Curve(nurbs, heloc2, u1, u2); // The geometric curve + Handle(Geom_BSplineCurve) bspline2 = + Handle(Geom_BSplineCurve)::DownCast(c_geom2); // Try to get BSpline curve if (!bspline2.IsNull()) { gp_Trsf transf = heloc2.Transformation(); - bspline2->Transform(transf); // apply original transformation to control points - //Store Underlying Geometry + bspline2->Transform(transf); // apply original transformation to control points + // Store Underlying Geometry curves.push_back(bspline2); } else { // BRepBuilderAPI_NurbsConvert failed, try ShapeConstruct_Curve now ShapeConstruct_Curve scc; - Handle(Geom_BSplineCurve) spline = scc.ConvertToBSpline(c_geom, u1, u2, Precision::Confusion()); - if (spline.IsNull()) - Standard_Failure::Raise("A curve was not a B-spline and could not be converted into one."); + Handle(Geom_BSplineCurve) spline = + scc.ConvertToBSpline(c_geom, u1, u2, Precision::Confusion()); + if (spline.IsNull()) { + Standard_Failure::Raise( + "A curve was not a B-spline and could not be converted into one."); + } gp_Trsf transf = heloc2.Transformation(); - spline->Transform(transf); // apply original transformation to control points + spline->Transform(transf); // apply original transformation to control points curves.push_back(spline); } } } - GeomFill_BSplineCurves aSurfBuilder; //Create Surface Builder + GeomFill_BSplineCurves aSurfBuilder; // Create Surface Builder if (edges.size() == 2) { aSurfBuilder.Init(curves[0], curves[1], fstyle); @@ -3918,20 +3959,26 @@ TopoShape & TopoShape::makeElementBSplineFace(const std::vector &inpu FC_THROWM(Base::CADKernelError, "Resulting Face is null"); } + // TODO: Is this correct? makeElementBSplineFace is new (there is no corresponding non element + // version of this operation). It appears to be reasonable for the BRepBuilderAPI_MakeFace to + // return more edges than we sent in. The correspondence between old and edges is assumed here + // in resetting the element maps. auto newEdges = aFace.getSubTopoShapes(TopAbs_EDGE); - if (newEdges.size() != edges.size()) + if (newEdges.size() != edges.size()) { FC_WARN("Face edge count mismatch"); - else { - int i = 0; - for (auto &edge : newEdges) - edge.resetElementMap(edges[i++].elementMap()); - aFace.mapSubElement(newEdges); } + unsigned ind = 0; + for (auto& edge : newEdges) { + if ( ind < edges.size() ) { + edge.resetElementMap(edges[ind++].elementMap()); + } + } + aFace.mapSubElement(newEdges); Data::ElementIDRefs sids; - Data::MappedName edgeName = aFace.getMappedName( - Data::IndexedName::fromConst("Edge",1), true, &sids); - aFace.setElementComboName(Data::IndexedName::fromConst("Face",1), + Data::MappedName edgeName = + aFace.getMappedName(Data::IndexedName::fromConst("Edge", 1), true, &sids); + aFace.setElementComboName(Data::IndexedName::fromConst("Face", 1), {edgeName}, Part::OpCodes::BSplineFace, op, @@ -3940,8 +3987,6 @@ TopoShape & TopoShape::makeElementBSplineFace(const std::vector &inpu return *this; } - - /** * Encode and set an element name in the elementMap. If a hasher is defined, apply it to the name. * diff --git a/src/Mod/Part/App/TopoShapeMapper.h b/src/Mod/Part/App/TopoShapeMapper.h index 447b2d9537..5ec165efab 100644 --- a/src/Mod/Part/App/TopoShapeMapper.h +++ b/src/Mod/Part/App/TopoShapeMapper.h @@ -244,7 +244,8 @@ struct PartExport GenericShapeMapper: ShapeMapper { }; /// Parameters for TopoShape::makeElementFilledFace() -struct PartExport TopoShape::BRepFillingParams { +struct PartExport TopoShape::BRepFillingParams +{ /** Optional initial surface to begin the construction of the surface for the filled face. * * It is useful if the surface resulting from construction for the @@ -268,7 +269,8 @@ struct PartExport TopoShape::BRepFillingParams { std::unordered_map supports; /// Optional begin index to the input shapes to be used as the boundary of the filled face. int boundary_begin = -1; - /// Optional end index (last index + 1) to the input shapes to be used as the boundary of the filled face. + /// Optional end index (last index + 1) to the input shapes to be used as the boundary of the + /// filled face. int boundary_end = -1; /// The energe minimizing criterion degree; unsigned int degree = 3; diff --git a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp index 9c05d7056f..e12642891a 100644 --- a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -3,6 +3,7 @@ #include "gtest/gtest.h" #include "src/App/InitApplication.h" #include +#include "Mod/Part/App/TopoShapeMapper.h" #include #include "PartTestHelpers.h" @@ -16,8 +17,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -1195,7 +1198,7 @@ TEST_F(TopoShapeExpansionTest, makeElementShellFromWires) // Assert TopoShape result = topoShape1.makeElementShellFromWires(shapes); #if OCC_VERSION_HEX >= 0x070400 - EXPECT_EQ(result.getShape().NbChildren(), 6); + EXPECT_EQ(result.getShape().NbChildren(), 20); // 6 TODO: VERSION DEPENDENT? #endif EXPECT_EQ(result.countSubElements("Vertex"), 8); EXPECT_EQ(result.countSubElements("Edge"), 32); @@ -2204,4 +2207,79 @@ TEST_F(TopoShapeExpansionTest, makeElementPrism) // {"Edge1;:G;XTR;:H2:7,F",})); //} +TEST_F(TopoShapeExpansionTest, makeElementFilledFace) +{ + // Arrange + auto [cube1, cube2] = CreateTwoCubes(); + TopoShape topoShape1 {cube1, 1L}; + auto wires = topoShape1.getSubShapes(TopAbs_WIRE); + TopoShape topoShape2 {wires[0], 2L}; + // Act + auto params = TopoShape::BRepFillingParams(); + TopoShape& result = topoShape1.makeElementFilledFace({topoShape2}, params); + auto elements = elementMap(result); + Base::BoundBox3d bb = result.getBoundBox(); + // Assert shape is correct + EXPECT_TRUE(PartTestHelpers::boxesMatch(bb, Base::BoundBox3d(0.0, -0.6, -0.6, 0, 1.6, 1.6))); + EXPECT_FLOAT_EQ(getArea(result.getShape()), 1); + // Assert elementMap is correct + EXPECT_TRUE(allElementsMatch(result, + { + "Edge1;:G;FFC;:H2:7,E", + "Edge1;:G;FFC;:H2:7,E;:L(Edge2;:G;FFC;:H2:7,E|Edge3;:G;FFC;:" + "H2:7,E|Edge4;:G;FFC;:H2:7,E);FFC;:H2:47,F", + "Edge2;:G;FFC;:H2:7,E", + "Edge3;:G;FFC;:H2:7,E", + "Edge4;:G;FFC;:H2:7,E", + "Vertex1;:G;FFC;:H2:7,V", + "Vertex2;:G;FFC;:H2:7,V", + "Vertex3;:G;FFC;:H2:7,V", + "Vertex4;:G;FFC;:H2:7,V", + })); +} + +TEST_F(TopoShapeExpansionTest, makeElementBSplineFace) +{ + // Arrange + TColgp_Array1OfPnt array1(1, 3); // sizing array + array1.SetValue(1, gp_Pnt(-4, 0, 2)); + array1.SetValue(2, gp_Pnt(-7, 2, 2)); + array1.SetValue(3, gp_Pnt(-10, 0, 2)); + Handle(Geom_BSplineCurve) curve1 = GeomAPI_PointsToBSpline(array1).Curve(); + + TColgp_Array1OfPnt array2(1, 3); // sizing array + array2.SetValue(1, gp_Pnt(-4, 0, 2)); + array2.SetValue(2, gp_Pnt(-7, -2, 2)); + array2.SetValue(3, gp_Pnt(-9, 0, 2)); + Handle(Geom_BSplineCurve) curve2 = GeomAPI_PointsToBSpline(array2).Curve(); + + auto edge = BRepBuilderAPI_MakeEdge(curve1); + auto edge1 = BRepBuilderAPI_MakeEdge(curve2); + TopoShape topoShape {1L}; + TopoShape topoShape2 {edge, 2L}; + TopoShape topoShape3 {edge1, 3L}; + // Act + TopoShape& result = topoShape.makeElementBSplineFace({topoShape2, topoShape3}); + auto elements = elementMap(result); + Base::BoundBox3d bb = result.getBoundBox(); + // Assert shape is correct + EXPECT_TRUE(PartTestHelpers::boxesMatch( + bb, + Base::BoundBox3d(-10, -2.0597998470594132, 2, -4, 2.1254369627132599, 2))); + EXPECT_FLOAT_EQ(getArea(result.getShape()), 14.677052); + // Assert elementMap is correct + EXPECT_TRUE(elementsMatch(result, + { + "Edge1", + "Edge1;BSF", + "Edge1;D1", + "Edge1;D2", + "Edge1;D3", + "Vertex1", + "Vertex1;D1", + "Vertex2", + "Vertex2;D1", + })); +} + // NOLINTEND(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers)