From d99bc1e9dfa776befb4437e50ba90d9457345487 Mon Sep 17 00:00:00 2001 From: Ajinkya Dahale Date: Thu, 11 Nov 2021 21:22:58 -0500 Subject: [PATCH] [PD] Refactor `Loft::execute()` and support points for sections This is a combination of 4 commits. Original commit messages follow. [PD] Initial support for point sections in loft This commit allows the last section in a loft to be a single vertex of a solid. Currently single vertices of sketches or datum points are NOT supported. [PD] Allow loft "profiles" to be points Most reliably done in dialog-based workflow. [PD] Allow loft last section to be sketch point [PD] Refactor `Loft::execute` Makes it easier to support adding a single-vertex sketch in profile or sections field when selecting the sketch in tree (i.e. without selecting subelements). [PD] Refactoring after PR #5176 is merged --- src/Mod/PartDesign/App/FeatureLoft.cpp | 164 +++++++++++++++++-------- 1 file changed, 112 insertions(+), 52 deletions(-) diff --git a/src/Mod/PartDesign/App/FeatureLoft.cpp b/src/Mod/PartDesign/App/FeatureLoft.cpp index 811fd972eb..11622296ea 100644 --- a/src/Mod/PartDesign/App/FeatureLoft.cpp +++ b/src/Mod/PartDesign/App/FeatureLoft.cpp @@ -74,17 +74,48 @@ short Loft::mustExecute() const App::DocumentObjectExecReturn *Loft::execute(void) { + auto getSectionShape = + [](App::DocumentObject* feature, const std::vector &subs) -> TopoDS_Shape { + if (!feature || + !feature->isDerivedFrom(Part::Feature::getClassTypeId())) + throw Base::TypeError("Loft: Invalid profile/section"); + + auto subName = subs.empty() ? "" : subs.front(); + + // only take the entire shape when we have a sketch selected, but + // not a point of the sketch + if (feature->isDerivedFrom(Part::Part2DObject::getClassTypeId()) && + !(subName.size() > 6 && subName.substr(0,6) == "Vertex")) + return static_cast(feature)->Shape.getValue(); + else { + if(subName.empty()) + throw Base::ValueError("No valid subelement linked in Part::Feature"); + return static_cast(feature)->Shape.getShape().getSubShape(subName.c_str()); + } + }; + + auto addWiresToWireSections = + [](TopoDS_Shape& section, + std::vector>& wiresections) -> size_t { + TopExp_Explorer ex; + size_t i=0; + bool initialWireSectionsEmpty = wiresections.empty(); + for (ex.Init(section, TopAbs_WIRE); ex.More(); ex.Next(), ++i) { + // if profile was just a point then this is where we can first set our list + if (i>=wiresections.size()) { + if (initialWireSectionsEmpty) + wiresections.emplace_back(1, ex.Current()); + else + throw Base::ValueError("Loft: Sections need to have the same amount of inner wires (except profile and last section, which can be points)"); + } + else + wiresections[i].push_back(TopoDS::Wire(ex.Current())); + } + return i; + }; std::vector wires; - try { - wires = getProfileWires(); - } catch (const Base::Exception& e) { - return new App::DocumentObjectExecReturn(e.what()); - } - - TopoDS_Shape sketchshape = getVerifiedFace(); - if (sketchshape.IsNull()) - return new App::DocumentObjectExecReturn("Loft: Creating a face from sketch failed"); + TopoDS_Shape profilePoint; // if the Base property has a valid shape, fuse the pipe into it TopoDS_Shape base; @@ -102,80 +133,109 @@ App::DocumentObjectExecReturn *Loft::execute(void) base.Move(invObjLoc); // build up multisections - auto multisections = Sections.getValues(); + auto multisections = Sections.getSubListValues(); if (multisections.empty()) return new App::DocumentObjectExecReturn("Loft: At least one section is needed"); - std::vector> wiresections; - for (TopoDS_Wire& wire : wires) - wiresections.emplace_back(1, wire); + TopoDS_Shape profileShape = getSectionShape(Profile.getValue(), + Profile.getSubValues()); + if (profileShape.IsNull()) + return new App::DocumentObjectExecReturn("Loft: Could not obtain profile shape"); - for (auto obj : multisections) { - if (!obj->isDerivedFrom(Part::Feature::getClassTypeId())) - return new App::DocumentObjectExecReturn("Loft: All sections need to be part features"); - - // if the section is an object's face then take just the face - TopoDS_Shape shape; - if (obj->isDerivedFrom(Part::Part2DObject::getClassTypeId())) - shape = static_cast(obj)->Shape.getValue(); - else { - auto subValues = Sections.getSubValues(obj); - if (subValues.empty()) - throw Base::ValueError("Loft: No valid subelement linked in Part::Feature"); - - shape = static_cast(obj)->Shape.getShape().getSubShape(subValues[0].c_str()); - } + std::vector> wiresections; + size_t numWires = addWiresToWireSections(profileShape, wiresections); + if (numWires == 0) { + // profileShape had no wires so only other valid option is point section TopExp_Explorer ex; - size_t i=0; - for (ex.Init(shape, TopAbs_WIRE); ex.More(); ex.Next(), ++i) { - if (i>=wiresections.size()) - return new App::DocumentObjectExecReturn("Loft: Sections need to have the same amount of inner wires as the base section"); - wiresections[i].push_back(TopoDS::Wire(ex.Current())); + size_t i = 0; + for (ex.Init(profileShape, TopAbs_VERTEX); ex.More(); ex.Next(), ++i) { + profilePoint = ex.Current(); } - if (i 1) + return new App::DocumentObjectExecReturn("Loft: Only one isolated point is needed if using a sketch with isolated points for section"); + } + bool isLastSectionVertex = false; + + for (auto &subSet : multisections) { + if (!subSet.first->isDerivedFrom(Part::Feature::getClassTypeId())) + return new App::DocumentObjectExecReturn("Loft: All sections need to be part features"); + + // if the selected subvalue is a point, pick that even if we have a sketch + TopoDS_Shape shape = getSectionShape(subSet.first, subSet.second); + if (shape.IsNull()) + return new App::DocumentObjectExecReturn("Loft: Could not obtain section shape"); + + size_t numWiresAdded = addWiresToWireSections(shape, wiresections); + if (numWiresAdded == 0) { + TopExp_Explorer ex; + size_t j = 0; + for (ex.Init(shape, TopAbs_VERTEX); ex.More(); ex.Next(), ++j) { + if (isLastSectionVertex) + return new App::DocumentObjectExecReturn("Loft: Only the profile and last section can be vertices"); + isLastSectionVertex = true; + for (auto &wires : wiresections) + wires.push_back(ex.Current()); + } + if (j > 1) + return new App::DocumentObjectExecReturn("Loft: Only one isolated point is needed if using a sketch with isolated points for section"); + } + if (!isLastSectionVertex && numWiresAdded < wiresections.size()) + return new App::DocumentObjectExecReturn("Loft: Sections need to have the same amount of inner wires as the base section"); } // build all shells std::vector shells; - for (std::vector& wires : wiresections) { + TopoDS_Shape copyProfilePoint(profilePoint); + if (!profilePoint.IsNull()) + copyProfilePoint.Move(invObjLoc); + + for (auto& wires : wiresections) { BRepOffsetAPI_ThruSections mkTS(false, Ruled.getValue(), Precision::Confusion()); - for (TopoDS_Wire& wire : wires) { - wire.Move(invObjLoc); - mkTS.AddWire(wire); + if (!profilePoint.IsNull()) + mkTS.AddVertex(TopoDS::Vertex(copyProfilePoint)); + + for (auto& shape : wires) { + shape.Move(invObjLoc); + if (shape.ShapeType() == TopAbs_VERTEX) + mkTS.AddVertex(TopoDS::Vertex(shape)); + else + mkTS.AddWire(TopoDS::Wire(shape)); } mkTS.Build(); if (!mkTS.IsDone()) return new App::DocumentObjectExecReturn("Loft could not be built"); - //build the shell use simulate to get the top and bottom wires in an easy way + // build the shell use simulate to get the top and bottom wires in an easy way shells.push_back(mkTS.Shape()); } - //build the top and bottom face, sew the shell and build the final solid - TopoDS_Shape front = getVerifiedFace(); - front.Move(invObjLoc); - std::vector backwires; - for (std::vector& wires : wiresections) - backwires.push_back(wires.back()); - - TopoDS_Shape back = Part::FaceMakerCheese::makeFace(backwires); - + // build the top and bottom faces (where possible), sew the shell, + // and build the final solid BRepBuilderAPI_Sewing sewer; sewer.SetTolerance(Precision::Confusion()); - sewer.Add(front); - sewer.Add(back); + if (profilePoint.IsNull()) { + TopoDS_Shape front = getVerifiedFace(); + front.Move(invObjLoc); + sewer.Add(front); + } + if (!isLastSectionVertex) { + std::vector backwires; + for (auto& wires : wiresections) + backwires.push_back(TopoDS::Wire(wires.back())); + TopoDS_Shape back = Part::FaceMakerCheese::makeFace(backwires); + sewer.Add(back); + } for (TopoDS_Shape& s : shells) sewer.Add(s); sewer.Perform(); - //build the solid + // build the solid BRepBuilderAPI_MakeSolid mkSolid; mkSolid.Add(TopoDS::Shell(sewer.SewedShape())); if (!mkSolid.IsDone())