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 <chennes@pioneerlibrarysystem.org>
This commit is contained in:
drwho495
2025-08-17 20:35:46 -05:00
committed by GitHub
parent 84afc15c91
commit 68077de39b
14 changed files with 89 additions and 35 deletions

View File

@@ -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<Part::FilletElement> 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");

View File

@@ -37,6 +37,7 @@
# include <TopTools_IndexedMapOfShape.hxx>
#endif
#include <App/Document.h>
#include <Base/Exception.h>
#include <Base/Tools.h>
@@ -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;

View File

@@ -26,6 +26,7 @@
#include <App/PropertyStandard.h>
#include <App/PropertyUnits.h>
#include <App/Document.h>
#include "FaceMakerCheese.h"
#include "PartFeature.h"
#include "ExtrusionHelper.h"

View File

@@ -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<Part::FilletElement> 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) {

View File

@@ -30,6 +30,7 @@
# include <TopoDS.hxx>
#endif
#include <App/Document.h>
#include <Base/Tools.h>
#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,

View File

@@ -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<TopoShape> 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<TransitionMode>(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,

View File

@@ -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])

View File

@@ -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))

View File

@@ -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 = []

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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()));

View File

@@ -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)