From 68077de39b089ae68408888985835161c251a5fa Mon Sep 17 00:00:00 2001 From: drwho495 <70452450+drwho495@users.noreply.github.com> Date: Sun, 17 Aug 2025 20:35:46 -0500 Subject: [PATCH] Part: Fix toponaming issues. (#23151) * fix split apart and splice * fix hasher gen issue in extrusion * fix broken gen in fillet/chamfer * error when elements go missing in fillet/chamfer * fix hashing in some elements * fix compiler errors * fix sweep * remove hasher from mirror * remove old import * add clarifying comment * Linter cleanup --------- Co-authored-by: Chris Hennes --- src/Mod/Part/App/FeatureChamfer.cpp | 25 +++++++++++-- src/Mod/Part/App/FeatureExtrusion.cpp | 4 ++- src/Mod/Part/App/FeatureExtrusion.h | 1 + src/Mod/Part/App/FeatureFillet.cpp | 37 ++++++++++++++++---- src/Mod/Part/App/FeatureRevolution.cpp | 3 +- src/Mod/Part/App/PartFeatures.cpp | 10 +++--- src/Mod/Part/BOPTools/GeneralFuseResult.py | 4 +-- src/Mod/Part/BOPTools/JoinAPI.py | 6 ++-- src/Mod/Part/BOPTools/ShapeMerge.py | 2 +- src/Mod/Part/BOPTools/SplitAPI.py | 14 +++++--- src/Mod/Part/BOPTools/Utils.py | 4 +-- src/Mod/Part/CompoundTools/CompoundFilter.py | 3 ++ src/Mod/Part/Gui/CrossSections.cpp | 3 +- src/Mod/Part/MakeBottle.py | 8 ++--- 14 files changed, 89 insertions(+), 35 deletions(-) diff --git a/src/Mod/Part/App/FeatureChamfer.cpp b/src/Mod/Part/App/FeatureChamfer.cpp index f98ae44dd4..6942058164 100644 --- a/src/Mod/Part/App/FeatureChamfer.cpp +++ b/src/Mod/Part/App/FeatureChamfer.cpp @@ -49,22 +49,36 @@ App::DocumentObjectExecReturn *Chamfer::execute() try { TopoShape baseTopoShape = Feature::getTopoShape(link, ShapeOption::ResolveLink | ShapeOption::Transform); - auto baseShape = Feature::getShape(link, ShapeOption::ResolveLink | ShapeOption::Transform); + const auto & baseShape = baseTopoShape.getShape(); BRepFilletAPI_MakeChamfer mkChamfer(baseShape); TopTools_IndexedDataMapOfShapeListOfShape mapEdgeFace; TopExp::MapShapesAndAncestors(baseShape, TopAbs_EDGE, TopAbs_FACE, mapEdgeFace); TopTools_IndexedMapOfShape mapOfEdges; + std::vector edges = Edges.getValues(); TopExp::MapShapes(baseShape, TopAbs_EDGE, mapOfEdges); + std::string fullErrMsg; const auto &vals = EdgeLinks.getSubValues(); const auto &subs = EdgeLinks.getShadowSubs(); if(subs.size()!=(size_t)Edges.getSize()) return new App::DocumentObjectExecReturn("Edge link size mismatch"); size_t i=0; - for(const auto &info : Edges.getValues()) { + for(const auto &info : edges) { auto &sub = subs[i]; - auto &ref = sub.newName.size()?sub.newName:vals[i]; + auto &ref = sub.newName.empty() ? vals[i] : sub.newName; + auto &oldName = sub.oldName.empty() ? "" : sub.oldName; ++i; + + if (Data::hasMissingElement(ref.c_str()) || Data::hasMissingElement(oldName.c_str())) { + fullErrMsg.append("Missing edge link: "); + fullErrMsg.append(ref); + fullErrMsg.append("\n"); + + auto removeIt = std::remove(edges.begin(), edges.end(), info); + edges.erase(removeIt, edges.end()); + + continue; + } // Toponaming project March 2024: Replaced this code because it wouldn't work: // TopoDS_Shape edge; // try { @@ -80,6 +94,11 @@ App::DocumentObjectExecReturn *Chamfer::execute() mkChamfer.Add(radius1, radius2, TopoDS::Edge(edge), face); } + if (!fullErrMsg.empty()) { + return new App::DocumentObjectExecReturn(fullErrMsg); + } + Edges.setValues(edges); + TopoDS_Shape shape = mkChamfer.Shape(); if (shape.IsNull()) return new App::DocumentObjectExecReturn("Resulting shape is null"); diff --git a/src/Mod/Part/App/FeatureExtrusion.cpp b/src/Mod/Part/App/FeatureExtrusion.cpp index ec8cf1fb3e..c3f0793f07 100644 --- a/src/Mod/Part/App/FeatureExtrusion.cpp +++ b/src/Mod/Part/App/FeatureExtrusion.cpp @@ -37,6 +37,7 @@ # include #endif +#include #include #include @@ -374,7 +375,8 @@ App::DocumentObjectExecReturn* Extrusion::execute() try { ExtrusionParameters params = computeFinalParameters(); - TopoShape result(0); + TopoShape result(0, getDocument()->getStringHasher()); + extrudeShape(result, Feature::getTopoShape(link, ShapeOption::ResolveLink | ShapeOption::Transform), params); this->Shape.setValue(result); return App::DocumentObject::StdReturn; diff --git a/src/Mod/Part/App/FeatureExtrusion.h b/src/Mod/Part/App/FeatureExtrusion.h index 31e5fb8973..941e72fb20 100644 --- a/src/Mod/Part/App/FeatureExtrusion.h +++ b/src/Mod/Part/App/FeatureExtrusion.h @@ -26,6 +26,7 @@ #include #include +#include #include "FaceMakerCheese.h" #include "PartFeature.h" #include "ExtrusionHelper.h" diff --git a/src/Mod/Part/App/FeatureFillet.cpp b/src/Mod/Part/App/FeatureFillet.cpp index 9c10649ec6..13229cd652 100644 --- a/src/Mod/Part/App/FeatureFillet.cpp +++ b/src/Mod/Part/App/FeatureFillet.cpp @@ -54,22 +54,38 @@ App::DocumentObjectExecReturn *Fillet::execute() #if defined(__GNUC__) && defined (FC_OS_LINUX) Base::SignalException se; #endif - auto baseShape = Feature::getShape(link, ShapeOption::ResolveLink | ShapeOption::Transform); - TopoShape baseTopoShape = Feature::getTopoShape(link, ShapeOption::ResolveLink | ShapeOption::Transform); + TopoShape baseTopoShape = Feature::getTopoShape(link, ShapeOption::ResolveLink | ShapeOption::Transform); + auto baseShape = baseTopoShape.getShape(); BRepFilletAPI_MakeFillet mkFillet(baseShape); TopTools_IndexedMapOfShape mapOfShape; TopExp::MapShapes(baseShape, TopAbs_EDGE, mapOfShape); TopTools_IndexedMapOfShape mapOfEdges; TopExp::MapShapes(baseShape, TopAbs_EDGE, mapOfEdges); - const auto &vals = EdgeLinks.getSubValues(); + std::vector edges = Edges.getValues(); + std::string fullErrMsg; + + const auto &vals = EdgeLinks.getSubValues(true); const auto &subs = EdgeLinks.getShadowSubs(); if(subs.size()!=(size_t)Edges.getSize()) return new App::DocumentObjectExecReturn("Edge link size mismatch"); size_t i=0; - for(const auto &info : Edges.getValues()) { + for(const auto &info : edges) { auto &sub = subs[i]; - auto &ref = sub.newName.size()?sub.newName:vals[i]; + auto &ref = sub.newName.empty() ? vals[i] : sub.newName; + auto &oldName = sub.oldName.empty() ? "" : sub.oldName; ++i; + + if (Data::hasMissingElement(ref.c_str()) || Data::hasMissingElement(oldName.c_str())) { + fullErrMsg.append("Missing edge link: "); + fullErrMsg.append(ref); + fullErrMsg.append("\n"); + + auto removeIt = std::remove(edges.begin(), edges.end(), info); + edges.erase(removeIt, edges.end()); + + continue; + } + // Toponaming project March 2024: Replaced this code because it wouldn't work: // TopoDS_Shape edge; // try { @@ -77,19 +93,26 @@ App::DocumentObjectExecReturn *Fillet::execute() // }catch(...){} auto id = Data::MappedName(ref.c_str()).toIndexedName().getIndex(); const TopoDS_Edge& edge = TopoDS::Edge(mapOfEdges.FindKey(id)); + if(edge.IsNull()) - return new App::DocumentObjectExecReturn("Invalid edge link"); + return new App::DocumentObjectExecReturn("Invalid edge link"); + double radius1 = info.radius1; double radius2 = info.radius2; mkFillet.Add(radius1, radius2, TopoDS::Edge(edge)); } + if (!fullErrMsg.empty()) { + return new App::DocumentObjectExecReturn(fullErrMsg); + } + Edges.setValues(edges); + TopoDS_Shape shape = mkFillet.Shape(); if (shape.IsNull()) return new App::DocumentObjectExecReturn("Resulting shape is null"); TopoShape res(0); - this->Shape.setValue(res.makeElementShape(mkFillet,baseTopoShape,Part::OpCodes::Fillet)); + this->Shape.setValue(res.makeElementShape(mkFillet, baseTopoShape, Part::OpCodes::Fillet)); return Part::FilletBase::execute(); } catch (Standard_Failure& e) { diff --git a/src/Mod/Part/App/FeatureRevolution.cpp b/src/Mod/Part/App/FeatureRevolution.cpp index ce82686107..425bde2cb0 100644 --- a/src/Mod/Part/App/FeatureRevolution.cpp +++ b/src/Mod/Part/App/FeatureRevolution.cpp @@ -30,6 +30,7 @@ # include #endif +#include #include #include "FeatureRevolution.h" #include "FaceMaker.h" @@ -161,7 +162,7 @@ App::DocumentObjectExecReturn *Revolution::execute() TopLoc_Location loc(mov); sourceShape.setShape(sourceShape.getShape().Moved(loc)); } - TopoShape revolve(0); + TopoShape revolve(0, getDocument()->getStringHasher()); revolve.makeElementRevolve(sourceShape, revAx, angle, diff --git a/src/Mod/Part/App/PartFeatures.cpp b/src/Mod/Part/App/PartFeatures.cpp index be5de959f7..f241dfacd7 100644 --- a/src/Mod/Part/App/PartFeatures.cpp +++ b/src/Mod/Part/App/PartFeatures.cpp @@ -150,7 +150,7 @@ App::DocumentObjectExecReturn* RuledSurface::execute() return new App::DocumentObjectExecReturn("Invalid link."); } } - TopoShape res(0); + TopoShape res(0, getDocument()->getStringHasher()); res.makeElementRuledSurface(shapes, Orientation.getValue()); this->Shape.setValue(res); return Part::Feature::execute(); @@ -229,7 +229,7 @@ App::DocumentObjectExecReturn* Loft::execute() IsRuled isRuled = Ruled.getValue() ? IsRuled::ruled : IsRuled::notRuled; IsClosed isClosed = Closed.getValue() ? IsClosed::closed : IsClosed::notClosed; int degMax = MaxDegree.getValue(); - TopoShape result(0); + TopoShape result(0, getDocument()->getStringHasher()); result.makeElementLoft(shapes, isSolid, isRuled, isClosed, degMax); if (Linearize.getValue()) { result.linearize( LinearizeFace::linearizeFaces, LinearizeEdge::noEdges); @@ -312,7 +312,7 @@ App::DocumentObjectExecReturn* Sweep::execute() } spineShapes.push_back(shape); } - spine = TopoShape().makeElementCompound(spineShapes, 0, TopoShape::SingleShapeCompoundCreationPolicy::returnShape); + spine = TopoShape(0).makeElementCompound(spineShapes, 0, TopoShape::SingleShapeCompoundCreationPolicy::returnShape); } std::vector shapes; shapes.push_back(spine); @@ -326,7 +326,7 @@ App::DocumentObjectExecReturn* Sweep::execute() Standard_Boolean isFrenet = Frenet.getValue() ? Standard_True : Standard_False; auto transMode = static_cast(Transition.getValue()); try { - TopoShape result(0); + TopoShape result(0, getDocument()->getStringHasher()); result.makeElementPipeShell(shapes, isSolid, isFrenet, transMode, Part::OpCodes::Sweep); if (Linearize.getValue()) { result.linearize(LinearizeFace::linearizeFaces, LinearizeEdge::noEdges); @@ -427,7 +427,7 @@ App::DocumentObjectExecReturn* Thickness::execute() short mode = (short)Mode.getValue(); short join = (short)Join.getValue(); - this->Shape.setValue(TopoShape(0,getDocument()->getStringHasher()) + this->Shape.setValue(TopoShape(0, getDocument()->getStringHasher()) .makeElementThickSolid(base, shapes, thickness, diff --git a/src/Mod/Part/BOPTools/GeneralFuseResult.py b/src/Mod/Part/BOPTools/GeneralFuseResult.py index 80cb72794f..4aec629fe8 100644 --- a/src/Mod/Part/BOPTools/GeneralFuseResult.py +++ b/src/Mod/Part/BOPTools/GeneralFuseResult.py @@ -296,7 +296,7 @@ def myCustomFusionRoutine(list_of_shapes): new_children.append(new_piece) existing_pieces[hash] = (-1, new_piece) if changed: - return Part.Compound(new_children) + return Part.makeCompound(new_children) else: return None @@ -431,4 +431,4 @@ class GeneralFuseReturnBuilder(FrozenClass): self.pieces[piece_index] = new_shape def getGFReturn(self): - return (Part.Compound(self.pieces), [[self.pieces[iPiece] for iPiece in ilist] for ilist in self._pieces_from_source]) + return (Part.makeCompound(self.pieces), [[self.pieces[iPiece] for iPiece in ilist] for ilist in self._pieces_from_source]) diff --git a/src/Mod/Part/BOPTools/JoinAPI.py b/src/Mod/Part/BOPTools/JoinAPI.py index efce295512..bdd5054fb9 100644 --- a/src/Mod/Part/BOPTools/JoinAPI.py +++ b/src/Mod/Part/BOPTools/JoinAPI.py @@ -91,7 +91,7 @@ def connect(list_of_shapes, tolerance = 0.0): if largest is not None: keepers.append(largest) - touch_test_list = Part.Compound(keepers) + touch_test_list = Part.makeCompound(keepers) #add all intersection pieces that touch danglers, triple intersection pieces that touch duals, and so on for ii in range(2, ao.largestOverlapCount()+1): list_ii_pieces = [piece for piece in ao.pieces if len(ao.sourcesOfPiece(piece)) == ii] @@ -102,7 +102,7 @@ def connect(list_of_shapes, tolerance = 0.0): if len(keepers_2_add) == 0: break keepers.extend(keepers_2_add) - touch_test_list = Part.Compound(keepers_2_add) + touch_test_list = Part.makeCompound(keepers_2_add) #merge, and we are done! @@ -160,7 +160,7 @@ def cutout_legacy(shape_base, shape_tool, tolerance = 0.0): result = [] for sh in shapes_base: result.append(cutout(sh, shape_tool)) - return Part.Compound(result) + return Part.makeCompound(result) shape_base = shapes_base[0] pieces = compoundLeaves(shape_base.cut(shape_tool)) diff --git a/src/Mod/Part/BOPTools/ShapeMerge.py b/src/Mod/Part/BOPTools/ShapeMerge.py index 4cbd97b428..ee7f1b8f07 100644 --- a/src/Mod/Part/BOPTools/ShapeMerge.py +++ b/src/Mod/Part/BOPTools/ShapeMerge.py @@ -152,7 +152,7 @@ def mergeShells(list_of_faces_shells, flag_single = False, split_connections = [ return Part.makeShell(faces) else: groups = splitIntoGroupsBySharing(faces, lambda sh: sh.Edges, split_connections) - return Part.makeCompound([Part.Shell(group) for group in groups]) + return Part.makeCompound([Part.makeShell(group) for group in groups]) def mergeWires(list_of_edges_wires, flag_single = False, split_connections = []): edges = [] diff --git a/src/Mod/Part/BOPTools/SplitAPI.py b/src/Mod/Part/BOPTools/SplitAPI.py index 8a5009bfe0..e25386e690 100644 --- a/src/Mod/Part/BOPTools/SplitAPI.py +++ b/src/Mod/Part/BOPTools/SplitAPI.py @@ -55,7 +55,7 @@ def booleanFragments(list_of_shapes, mode, tolerance = 0.0): elif mode == "Split": gr = GeneralFuseResult(list_of_shapes, (pieces,map)) gr.splitAggregates() - return Part.Compound(gr.pieces) + return Part.makeCompound(gr.pieces) else: raise ValueError("Unknown mode: {mode}".format(mode= mode)) @@ -69,15 +69,16 @@ def slice(base_shape, tool_shapes, mode, tolerance = 0.0): "Split" - wires and shells will be split at intersections, too. "CompSolid" - slice a solid and glue it back together to make a compsolid""" - shapes = [base_shape] + [Part.Compound([tool_shape]) for tool_shape in tool_shapes] # hack: putting tools into compounds will prevent contamination of result with pieces of tools + shapes = [base_shape] + [Part.makeCompound([tool_shape]) for tool_shape in tool_shapes] # hack: putting tools into compounds will prevent contamination of result with pieces of tools if len(shapes) < 2: raise ValueError("No slicing objects supplied!") pieces, map = shapes[0].generalFuse(shapes[1:], tolerance) gr = GeneralFuseResult(shapes, (pieces,map)) + result = None if mode == "Standard": result = gr.piecesFromSource(shapes[0]) elif mode == "CompSolid": - solids = Part.Compound(gr.piecesFromSource(shapes[0])).Solids + solids = Part.makeCompound(gr.piecesFromSource(shapes[0])).Solids if len(solids) < 1: raise ValueError("No solids in the result. Can't make compsolid.") elif len(solids) == 1: @@ -86,7 +87,10 @@ def slice(base_shape, tool_shapes, mode, tolerance = 0.0): elif mode == "Split": gr.splitAggregates(gr.piecesFromSource(shapes[0])) result = gr.piecesFromSource(shapes[0]) - return result[0] if len(result) == 1 else Part.Compound(result) + if result != None: + return result[0] if len(result) == 1 else Part.makeCompound(result) + else: + return Part.Shape() def xor(list_of_shapes, tolerance = 0.0): """xor(list_of_shapes, tolerance = 0.0): boolean XOR operation.""" @@ -99,4 +103,4 @@ def xor(list_of_shapes, tolerance = 0.0): for piece in gr.pieces: if len(gr.sourcesOfPiece(piece)) % 2 == 1: pieces_to_keep.append(piece) - return Part.Compound(pieces_to_keep) + return Part.makeCompound(pieces_to_keep) diff --git a/src/Mod/Part/BOPTools/Utils.py b/src/Mod/Part/BOPTools/Utils.py index 159b6b7b44..88502730d2 100644 --- a/src/Mod/Part/BOPTools/Utils.py +++ b/src/Mod/Part/BOPTools/Utils.py @@ -105,11 +105,11 @@ def upgradeToAggregateIfNeeded(list_of_shapes, types = None): if "Wire" in types: list_of_shapes = [(Part.Wire([shape]) if shape.ShapeType == "Edge" else shape) for shape in list_of_shapes] if "Shell" in types: - list_of_shapes = [(Part.Shell([shape]) if shape.ShapeType == "Face" else shape) for shape in list_of_shapes] + list_of_shapes = [(Part.makeShell([shape]) if shape.ShapeType == "Face" else shape) for shape in list_of_shapes] if "CompSolid" in types: list_of_shapes = [(Part.CompSolid([shape]) if shape.ShapeType == "Solid" else shape) for shape in list_of_shapes] if "Compound" in types: - list_of_shapes = [(Part.Compound(upgradeToAggregateIfNeeded(shape.childShapes(), types)) if shape.ShapeType == "Compound" else shape) for shape in list_of_shapes] + list_of_shapes = [(Part.makeCompound(upgradeToAggregateIfNeeded(shape.childShapes(), types)) if shape.ShapeType == "Compound" else shape) for shape in list_of_shapes] return list_of_shapes # adapted from http://stackoverflow.com/a/3603824/6285007 diff --git a/src/Mod/Part/CompoundTools/CompoundFilter.py b/src/Mod/Part/CompoundTools/CompoundFilter.py index 7d53623dab..e63a023436 100644 --- a/src/Mod/Part/CompoundTools/CompoundFilter.py +++ b/src/Mod/Part/CompoundTools/CompoundFilter.py @@ -117,6 +117,9 @@ class _CompoundFilter: "filter: '{}'".format(obj.FilterType)) try: rst.append(shps[i]) + len(shps[i].ElementMap) # this calls flushElementMap on the c++ side, + # which allows for the element map to be usable + # for later use. except IndexError: raise ValueError("Item index '{}' is out of range for this filter: '{}'".format(i, obj.FilterType)) flags[i] = True diff --git a/src/Mod/Part/Gui/CrossSections.cpp b/src/Mod/Part/Gui/CrossSections.cpp index f388e94d27..296a3f1d45 100644 --- a/src/Mod/Part/Gui/CrossSections.cpp +++ b/src/Mod/Part/Gui/CrossSections.cpp @@ -294,7 +294,7 @@ bool CrossSections::apply() } Gui::Command::runCommand(Gui::Command::App, QStringLiteral( - "comp=Part.Compound(wires)\n" + "comp=Part.makeCompound(wires)\n" "slice=FreeCAD.getDocument(\"%1\").addObject(\"Part::Feature\",\"%2\")\n" "slice.Shape=comp\n" "slice.purgeTouched()\n" @@ -302,6 +302,7 @@ bool CrossSections::apply() .arg(QLatin1String(doc->getName()), QLatin1String(s.c_str())).toLatin1()); } + seq.next(); } catch (Base::Exception& e) { e.reportException(); QMessageBox::critical(Gui::getMainWindow(), tr("Cannot compute cross-sections"), QString::fromStdString(e.getMessage())); diff --git a/src/Mod/Part/MakeBottle.py b/src/Mod/Part/MakeBottle.py index 9876aafbb0..ddcc360225 100644 --- a/src/Mod/Part/MakeBottle.py +++ b/src/Mod/Part/MakeBottle.py @@ -61,7 +61,7 @@ def makeBottle(myWidth=50.0, myHeight=70.0, myThickness=30.0): myBody = myBody.makeThickness([faceToRemove],-myThickness/50 , 1.e-3) myThreading = Part.makeThread(myNeckHeight/10, myNeckRadius*0.06, myHeight/10, myNeckRadius*0.99) myThreading.translate(Base.Vector(0,0,myHeight)) - myCompound = Part.Compound([myBody, myThreading]) + myCompound = Part.makeCompound([myBody, myThreading]) return myCompound @@ -88,7 +88,7 @@ def makeBoreHole(): S1 = Part.Shape([C1,C2,L1,L2]) W=Part.Wire(S1.Edges) - F=Part.Face(W) + F=Part.makeFace(W) P=F.extrude(Base.Vector(0,0,5)) # add objects with the shape @@ -101,7 +101,7 @@ def makeBoreHole(): c=Part.Circle(Base.Vector(0,0,-1),Base.Vector(0,0,1),2.0) w=Part.Wire(c.toShape()) - f=Part.Face(w) + f=Part.makeFace(w) p=f.extrude(Base.Vector(0,0,7)) P=P.cut(p) @@ -113,7 +113,7 @@ def makeBoreHole(): c=Part.Circle(Base.Vector(0,-11,2.5),Base.Vector(0,1,0),1.0) w=Part.Wire(c.toShape()) - f=Part.Face(w) + f=Part.makeFace(w) p=f.extrude(Base.Vector(0,22,0)) P=P.cut(p)