diff --git a/src/Mod/Part/App/PartFeature.cpp b/src/Mod/Part/App/PartFeature.cpp index 7572d9b2796e..cf1ad0f40e37 100644 --- a/src/Mod/Part/App/PartFeature.cpp +++ b/src/Mod/Part/App/PartFeature.cpp @@ -315,16 +315,18 @@ App::ElementNamePair Feature::getExportElementName(TopoShape shape, std::vector ancestors; if ( names.empty() ) { // Naming based heuristic has failed to find the element. Let's see if we can - // find it by matching planes + // 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() ) + if ( shapes.empty() ) // No more subshapes, so don't continue break; - searchShape = shapes.front(); + searchShape = shapes.front(); // After the break, so we stopped at innermost container } - mapped = TopoShape::chooseMatchingSubShapeByPlane(shape, searchShape); + auto newMapped = TopoShape::chooseMatchingSubShapeByPlaneOrLine(shape, searchShape); + if ( ! newMapped.name.empty() ) + mapped = newMapped; } for (auto& name : names) { auto index = shape.getIndexedName(name); diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index 2cc5afa63b9b..0cc4dfd0e504 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -359,7 +359,7 @@ class PartExport TopoShape: public Data::ComplexGeoData /** @name Subelement management */ //@{ /// Search to see if a SubShape matches - static Data::MappedElement chooseMatchingSubShapeByPlane(const TopoShape& shapeToFind, const TopoShape& shapeToLookIn); + static Data::MappedElement chooseMatchingSubShapeByPlaneOrLine(const TopoShape& shapeToFind, const TopoShape& shapeToLookIn); /// Unlike \ref getTypeAndIndex() this function only handles the supported /// element types. static std::pair getElementTypeAndIndex(const char* Name); diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index 222a3698ae5d..f55f69b1b526 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -5816,27 +5816,29 @@ bool TopoShape::getRelatedElementsCached(const Data::MappedName& name, return true; } -Data::MappedElement TopoShape::chooseMatchingSubShapeByPlane(const TopoShape& shapeToFind, const TopoShape& shapeToLookIn) +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() ) { // Try to match faces using planes + 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, can't make a guess. Future: return them all to the user? + 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: edges that are collinearis really what we want + 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()); diff --git a/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py b/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py index c1a0f343285f..0b7e72c8be41 100644 --- a/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py +++ b/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py @@ -2222,6 +2222,124 @@ def testPD_TNPSketchPadSketchDelete(self): 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