Toponaming: Add a plane matching based heuristic to TNP mitigation (#16803)
* Add a plane matching based heuristic to TNP mitigation * Improve comments, add test
This commit is contained in:
@@ -360,9 +360,21 @@ App::ElementNamePair Feature::getExportElementName(TopoShape shape,
|
||||
auto names =
|
||||
shape.decodeElementComboName(idxName, mapped.name, idxName.getType(), &postfix);
|
||||
std::vector<int> ancestors;
|
||||
// TODO: if names.empty() then the existing heuristic has failed to find anything
|
||||
// and we're going to flag this element as missing. This is the place to add
|
||||
// heuristics as we develop them.
|
||||
if ( names.empty() ) {
|
||||
// Naming based heuristic has failed to find the element. Let's see if we can
|
||||
// find it by matching either planes for faces or lines for edges.
|
||||
auto searchShape = this->Shape.getShape();
|
||||
// If we're still out at a Shell, Solid, CompSolid, or Compound drill in
|
||||
while (searchShape.getShape().ShapeType() < TopAbs_FACE ) {
|
||||
auto shapes = searchShape.getSubTopoShapes();
|
||||
if ( shapes.empty() ) // No more subshapes, so don't continue
|
||||
break;
|
||||
searchShape = shapes.front(); // After the break, so we stopped at innermost container
|
||||
}
|
||||
auto newMapped = TopoShape::chooseMatchingSubShapeByPlaneOrLine(shape, searchShape);
|
||||
if ( ! newMapped.name.empty() )
|
||||
mapped = newMapped;
|
||||
}
|
||||
for (auto& name : names) {
|
||||
auto index = shape.getIndexedName(name);
|
||||
if (!index) {
|
||||
|
||||
@@ -359,6 +359,8 @@ public:
|
||||
|
||||
/** @name Subelement management */
|
||||
//@{
|
||||
/// Search to see if a SubShape matches
|
||||
static Data::MappedElement chooseMatchingSubShapeByPlaneOrLine(const TopoShape& shapeToFind, const TopoShape& shapeToLookIn);
|
||||
/// Unlike \ref getTypeAndIndex() this function only handles the supported
|
||||
/// element types.
|
||||
static std::pair<std::string, unsigned long> getElementTypeAndIndex(const char* Name);
|
||||
|
||||
@@ -5820,4 +5820,36 @@ bool TopoShape::getRelatedElementsCached(const Data::MappedName& name,
|
||||
return true;
|
||||
}
|
||||
|
||||
Data::MappedElement TopoShape::chooseMatchingSubShapeByPlaneOrLine(const TopoShape& shapeToFind, const TopoShape& shapeToLookIn)
|
||||
{
|
||||
Data::MappedElement result;
|
||||
// See if we have a Face. If so, try to match using a plane.
|
||||
auto targetShape = shapeToFind.getSubTopoShape("Face", true);
|
||||
if ( ! targetShape.isNull() ) {
|
||||
int index = 0;
|
||||
for ( const auto& searchFace : shapeToLookIn.getSubTopoShapes(TopAbs_FACE)) {
|
||||
index++; // We have to generate the element index.
|
||||
if ( targetShape.isCoplanar(searchFace) ) {
|
||||
if ( ! result.name.empty() )
|
||||
return {}; // Found more than one, invalidate our guess. Future: return all matches to the UI?
|
||||
result = shapeToLookIn.getElementName(("Face"+std::to_string(index)).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
// Alternatively, try to locate an Edge, and try to match. Currently by exact equivalence; later can improve.
|
||||
targetShape = shapeToFind.getSubTopoShape("Edge", true);
|
||||
if ( ! targetShape.isNull() ) { // Try to match edges
|
||||
int index = 0;
|
||||
for ( const auto& searchEdge : shapeToLookIn.getSubTopoShapes(TopAbs_EDGE)) {
|
||||
index++;
|
||||
if ( targetShape.isSame(searchEdge) ) { // TODO: Test for edges that are collinear as really what we want
|
||||
if ( ! result.name.empty() )
|
||||
return {}; // Found more than one
|
||||
result = shapeToLookIn.getElementName(("Edge"+std::to_string(index)).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Part
|
||||
|
||||
@@ -2271,6 +2271,124 @@ class TestTopologicalNamingProblem(unittest.TestCase):
|
||||
matrix1.A34 = 10 # Z offset by 10
|
||||
self.assertTrue(doc.Sketch001.Placement.Matrix == matrix1)
|
||||
|
||||
def testPD_TNPSketchPadSketchSplit(self):
|
||||
"""Prove that a sketch attached to a padded sketch shape does not have a problem when the initial sketch has geometry split"""
|
||||
doc = App.ActiveDocument
|
||||
App.activeDocument().addObject("PartDesign::Body", "Body")
|
||||
doc.Body.newObject("Sketcher::SketchObject", "Sketch")
|
||||
doc.Sketch.AttachmentSupport = (doc.XY_Plane, [""])
|
||||
doc.Sketch.MapMode = "FlatFace"
|
||||
geoList = []
|
||||
geoList.append(Part.LineSegment(App.Vector(0, 0, 0), App.Vector(40, 0, 0)))
|
||||
geoList.append(Part.LineSegment(App.Vector(40, 0, 0), App.Vector(40, 20, 0)))
|
||||
geoList.append(Part.LineSegment(App.Vector(40, 20, 0), App.Vector(0, 20, 0)))
|
||||
geoList.append(Part.LineSegment(App.Vector(0, 20, 0), App.Vector(0, 0, 0)))
|
||||
doc.Sketch.addGeometry(geoList, False)
|
||||
constraintList = []
|
||||
constraintList.append(Sketcher.Constraint("Coincident", 0, 2, 1, 1))
|
||||
constraintList.append(Sketcher.Constraint("Coincident", 1, 2, 2, 1))
|
||||
constraintList.append(Sketcher.Constraint("Coincident", 2, 2, 3, 1))
|
||||
constraintList.append(Sketcher.Constraint("Coincident", 3, 2, 0, 1))
|
||||
# constraintList.append(Sketcher.Constraint("Horizontal", 0))
|
||||
constraintList.append(Sketcher.Constraint("Horizontal", 2))
|
||||
constraintList.append(Sketcher.Constraint("Vertical", 1))
|
||||
constraintList.append(Sketcher.Constraint("Vertical", 3))
|
||||
constraintList.append(Sketcher.Constraint("DistanceX", 0, 40))
|
||||
constraintList.append(Sketcher.Constraint("DistanceY", 1, 20))
|
||||
constraintList.append(Sketcher.Constraint("DistanceX", 0, 1, 0))
|
||||
constraintList.append(Sketcher.Constraint("DistanceY", 0, 1, 0))
|
||||
doc.Sketch.addConstraint(constraintList)
|
||||
doc.recompute()
|
||||
doc.Body.newObject("PartDesign::Pad", "Pad")
|
||||
doc.Pad.Profile = (
|
||||
doc.Sketch,
|
||||
[
|
||||
"",
|
||||
],
|
||||
)
|
||||
doc.Pad.Length = 10
|
||||
doc.Pad.ReferenceAxis = (doc.Sketch, ["N_Axis"])
|
||||
doc.Sketch.Visibility = False
|
||||
doc.Pad.Length = 10.000000
|
||||
doc.Pad.TaperAngle = 0.000000
|
||||
doc.Pad.UseCustomVector = 0
|
||||
doc.Pad.Direction = (0, 0, 1)
|
||||
doc.Pad.ReferenceAxis = (doc.Sketch, ["N_Axis"])
|
||||
doc.Pad.AlongSketchNormal = 1
|
||||
doc.Pad.Type = 0
|
||||
doc.Pad.UpToFace = None
|
||||
doc.Pad.Reversed = 0
|
||||
doc.Pad.Midplane = 0
|
||||
doc.Pad.Offset = 0
|
||||
doc.Pad.Refine = True
|
||||
doc.recompute()
|
||||
doc.Sketch.Visibility = False
|
||||
doc.Body.newObject("Sketcher::SketchObject", "Sketch001")
|
||||
doc.Sketch001.AttachmentSupport = (
|
||||
doc.Pad,
|
||||
[
|
||||
"Face6",
|
||||
],
|
||||
)
|
||||
doc.Sketch001.MapMode = "FlatFace"
|
||||
geoList = []
|
||||
geoList.append(Part.LineSegment(App.Vector(5, 5, 0), App.Vector(5, 10, 0)))
|
||||
geoList.append(Part.LineSegment(App.Vector(5, 10, 0), App.Vector(25, 10, 0)))
|
||||
geoList.append(Part.LineSegment(App.Vector(25, 10, 0), App.Vector(25, 5, 0)))
|
||||
geoList.append(Part.LineSegment(App.Vector(25, 5, 0), App.Vector(5, 5, 0)))
|
||||
doc.Sketch001.addGeometry(geoList, False)
|
||||
del geoList
|
||||
constraintList = []
|
||||
constraintList.append(Sketcher.Constraint("Coincident", 0, 2, 1, 1))
|
||||
constraintList.append(Sketcher.Constraint("Coincident", 1, 2, 2, 1))
|
||||
constraintList.append(Sketcher.Constraint("Coincident", 2, 2, 3, 1))
|
||||
constraintList.append(Sketcher.Constraint("Coincident", 3, 2, 0, 1))
|
||||
constraintList.append(Sketcher.Constraint("Vertical", 0))
|
||||
constraintList.append(Sketcher.Constraint("Vertical", 2))
|
||||
constraintList.append(Sketcher.Constraint("Horizontal", 1))
|
||||
constraintList.append(Sketcher.Constraint("Horizontal", 3))
|
||||
doc.Sketch001.addConstraint(constraintList)
|
||||
doc.recompute()
|
||||
doc.Body.newObject("PartDesign::Pad", "Pad001")
|
||||
doc.Pad001.Profile = (
|
||||
doc.Sketch001,
|
||||
[
|
||||
"",
|
||||
],
|
||||
)
|
||||
doc.Pad001.Length = 10
|
||||
doc.Pad001.ReferenceAxis = (doc.Sketch001, ["N_Axis"])
|
||||
doc.Sketch001.Visibility = False
|
||||
doc.Pad001.Length = 10.000000
|
||||
doc.Pad001.TaperAngle = 0.000000
|
||||
doc.Pad001.UseCustomVector = 0
|
||||
doc.Pad001.Direction = (0, 0, 1)
|
||||
doc.Pad001.ReferenceAxis = (doc.Sketch001, ["N_Axis"])
|
||||
doc.Pad001.AlongSketchNormal = 1
|
||||
doc.Pad001.Type = 0
|
||||
doc.Pad001.UpToFace = None
|
||||
doc.Pad001.Reversed = 0
|
||||
doc.Pad001.Midplane = 0
|
||||
doc.Pad001.Offset = 0
|
||||
doc.recompute()
|
||||
doc.Pad.Visibility = False
|
||||
doc.Sketch001.Visibility = False
|
||||
|
||||
self.assertAlmostEqual(doc.Pad.Shape.Volume,8000)
|
||||
|
||||
doc.Sketch.split(0, App.Vector(10,0,0)) # Geo 0 moves to Geo 3, create Geo4
|
||||
doc.Sketch.split(4, App.Vector(30,0,0)) # Create Geo5
|
||||
doc.Sketch.movePoint(4, 1, App.Vector(10,2,0), False)
|
||||
doc.Sketch.movePoint(4, 2, App.Vector(30, 2, 0), False)
|
||||
doc.recompute()
|
||||
self.assertAlmostEqual(doc.Pad.Shape.Volume,7400) # Prove the points moved
|
||||
self.assertTrue(doc.Sketch001.isValid()) # Check for a TNP fail.
|
||||
# If Sketch001 is still at the right start point, we are good.
|
||||
self.assertTrue(doc.Sketch001.AttachmentOffset.Matrix == App.Matrix())
|
||||
matrix1 = App.Matrix()
|
||||
matrix1.A34 = 10 # Z offset by 10.
|
||||
self.assertTrue(doc.Sketch001.Placement.Matrix == matrix1)
|
||||
|
||||
def testPD_TNPSketchPadSketchConstructionChange(self):
|
||||
"""Prove that a sketch attached to a padded sketch shape does not have a problem when the initial sketch has geometry changed from Construction"""
|
||||
pass # TODO
|
||||
|
||||
Reference in New Issue
Block a user