From 52b41fee06ca59542ddd3e948bf7d5d53703e9e5 Mon Sep 17 00:00:00 2001 From: Roy-043 <70520633+Roy-043@users.noreply.github.com> Date: Sun, 21 May 2023 16:15:55 +0200 Subject: [PATCH] Draft: fix props_changed_placement_only for attached clones and similar (#9623) For an attached object whose Shape depends on one or more source objects props_changed_placement_only should always return False. --- src/Mod/Draft/draftobjects/array.py | 2 +- src/Mod/Draft/draftobjects/base.py | 24 ++++++++++++++++++- src/Mod/Draft/draftobjects/block.py | 2 +- src/Mod/Draft/draftobjects/clone.py | 2 +- src/Mod/Draft/draftobjects/facebinder.py | 2 +- src/Mod/Draft/draftobjects/hatch.py | 2 +- src/Mod/Draft/draftobjects/patharray.py | 2 +- .../Draft/draftobjects/pathtwistedarray.py | 2 +- src/Mod/Draft/draftobjects/pointarray.py | 2 +- src/Mod/Draft/draftobjects/shape2dview.py | 2 +- src/Mod/Draft/draftobjects/wire.py | 2 +- src/Mod/Draft/drafttests/test_modification.py | 23 ++++++++++++++++++ 12 files changed, 56 insertions(+), 11 deletions(-) diff --git a/src/Mod/Draft/draftobjects/array.py b/src/Mod/Draft/draftobjects/array.py index 5b3a170fe8..646cdf37d3 100644 --- a/src/Mod/Draft/draftobjects/array.py +++ b/src/Mod/Draft/draftobjects/array.py @@ -396,7 +396,7 @@ class Array(DraftLink): def execute(self, obj): """Execute when the object is created or recomputed.""" - if self.props_changed_placement_only() \ + if self.props_changed_placement_only(obj) \ or not obj.Base: self.props_changed_clear() return diff --git a/src/Mod/Draft/draftobjects/base.py b/src/Mod/Draft/draftobjects/base.py index c248b83d42..f9121b2c20 100644 --- a/src/Mod/Draft/draftobjects/base.py +++ b/src/Mod/Draft/draftobjects/base.py @@ -182,12 +182,34 @@ class DraftObject(object): if hasattr(self, "props_changed"): delattr(self, "props_changed") - def props_changed_placement_only(self): + def props_changed_placement_only(self, obj=None): """Return `True` if the self.props_changed list, after removing `Shape` and `_LinkTouched` items, only contains `Placement` items. + + Parameters + ---------- + obj : the scripted object. Need only be supplied if the Shape of obj + is, or can be, derived from other objects. + """ if not hasattr(self, "props_changed"): return False + + # For an attached object whose Shape depends on one or more source + # objects the situation can occur that when those source objects + # change its Placement is also changed, but for no apparent reason: + # the new Placement is identical to the old. For such an object a + # `placement_only` change cannot be detected reliably and therefore + # this function should return `False`. + # https://github.com/FreeCAD/FreeCAD/issues/8771 + if obj is not None \ + and obj.OutList \ + and hasattr(obj, "Support") \ + and hasattr(obj, "MapMode") \ + and obj.Support \ + and obj.MapMode != "Deactivated": + return False + props = set(self.props_changed) if "Shape" in props: props.remove("Shape") diff --git a/src/Mod/Draft/draftobjects/block.py b/src/Mod/Draft/draftobjects/block.py index 361e29e619..ec7b85f358 100644 --- a/src/Mod/Draft/draftobjects/block.py +++ b/src/Mod/Draft/draftobjects/block.py @@ -43,7 +43,7 @@ class Block(DraftObject): obj.addProperty("App::PropertyLinkList","Components", "Draft", _tip) def execute(self, obj): - if self.props_changed_placement_only(): + if self.props_changed_placement_only(obj): obj.positionBySupport() self.props_changed_clear() return diff --git a/src/Mod/Draft/draftobjects/clone.py b/src/Mod/Draft/draftobjects/clone.py index 567f164307..538eedb57a 100644 --- a/src/Mod/Draft/draftobjects/clone.py +++ b/src/Mod/Draft/draftobjects/clone.py @@ -86,7 +86,7 @@ class Clone(DraftObject): return Part.makeCompound(shapes) def execute(self,obj): - if self.props_changed_placement_only(): + if self.props_changed_placement_only(obj): if hasattr(obj,"positionBySupport"): obj.positionBySupport() self.props_changed_clear() diff --git a/src/Mod/Draft/draftobjects/facebinder.py b/src/Mod/Draft/draftobjects/facebinder.py index 4540f575a6..9cc9c4d7b5 100644 --- a/src/Mod/Draft/draftobjects/facebinder.py +++ b/src/Mod/Draft/draftobjects/facebinder.py @@ -59,7 +59,7 @@ class Facebinder(DraftObject): obj.setEditorMode("Area",1) def execute(self,obj): - if self.props_changed_placement_only(): + if self.props_changed_placement_only(obj): self.props_changed_clear() return diff --git a/src/Mod/Draft/draftobjects/hatch.py b/src/Mod/Draft/draftobjects/hatch.py index 13272b9a59..53c7c1156f 100644 --- a/src/Mod/Draft/draftobjects/hatch.py +++ b/src/Mod/Draft/draftobjects/hatch.py @@ -77,7 +77,7 @@ class Hatch(DraftObject): def execute(self,obj): - if self.props_changed_placement_only() \ + if self.props_changed_placement_only(obj) \ or not obj.Base \ or not obj.File \ or not obj.Pattern \ diff --git a/src/Mod/Draft/draftobjects/patharray.py b/src/Mod/Draft/draftobjects/patharray.py index d23a75477b..0d8116b38c 100644 --- a/src/Mod/Draft/draftobjects/patharray.py +++ b/src/Mod/Draft/draftobjects/patharray.py @@ -296,7 +296,7 @@ class PathArray(DraftLink): def execute(self, obj): """Execute when the object is created or recomputed.""" - if self.props_changed_placement_only() \ + if self.props_changed_placement_only(obj) \ or not obj.Base \ or not obj.PathObject: self.props_changed_clear() diff --git a/src/Mod/Draft/draftobjects/pathtwistedarray.py b/src/Mod/Draft/draftobjects/pathtwistedarray.py index 1f0708c6df..efdec97a69 100644 --- a/src/Mod/Draft/draftobjects/pathtwistedarray.py +++ b/src/Mod/Draft/draftobjects/pathtwistedarray.py @@ -130,7 +130,7 @@ class PathTwistedArray(DraftLink): def execute(self, obj): """Execute when the object is created or recomputed.""" - if self.props_changed_placement_only() \ + if self.props_changed_placement_only(obj) \ or not obj.Base \ or not obj.PathObject: self.props_changed_clear() diff --git a/src/Mod/Draft/draftobjects/pointarray.py b/src/Mod/Draft/draftobjects/pointarray.py index 7fa088a713..fd53cb4ac4 100644 --- a/src/Mod/Draft/draftobjects/pointarray.py +++ b/src/Mod/Draft/draftobjects/pointarray.py @@ -105,7 +105,7 @@ class PointArray(DraftLink): def execute(self, obj): """Run when the object is created or recomputed.""" - if self.props_changed_placement_only() \ + if self.props_changed_placement_only(obj) \ or not obj.Base \ or not obj.PointObject: self.props_changed_clear() diff --git a/src/Mod/Draft/draftobjects/shape2dview.py b/src/Mod/Draft/draftobjects/shape2dview.py index 1559745d4f..623ae4ba91 100644 --- a/src/Mod/Draft/draftobjects/shape2dview.py +++ b/src/Mod/Draft/draftobjects/shape2dview.py @@ -196,7 +196,7 @@ class Shape2DView(DraftObject): return objs def execute(self, obj): - if self.props_changed_placement_only() \ + if self.props_changed_placement_only(obj) \ or not getattr(obj, "AutoUpdate", True): obj.positionBySupport() self.props_changed_clear() diff --git a/src/Mod/Draft/draftobjects/wire.py b/src/Mod/Draft/draftobjects/wire.py index 1b5c7b30d9..f321185d19 100644 --- a/src/Mod/Draft/draftobjects/wire.py +++ b/src/Mod/Draft/draftobjects/wire.py @@ -96,7 +96,7 @@ class Wire(DraftObject): obj.Closed = False def execute(self, obj): - if self.props_changed_placement_only(): + if self.props_changed_placement_only(obj): # Supplying obj is required because of `Base` and `Tool`. obj.positionBySupport() self.update_start_end(obj) self.props_changed_clear() diff --git a/src/Mod/Draft/drafttests/test_modification.py b/src/Mod/Draft/drafttests/test_modification.py index 55c6682628..cb0b2cfcf5 100644 --- a/src/Mod/Draft/drafttests/test_modification.py +++ b/src/Mod/Draft/drafttests/test_modification.py @@ -573,6 +573,29 @@ class DraftModification(unittest.TestCase): self.assertTrue(obj.hasExtension("Part::AttachExtension"), "'{}' failed".format(operation)) + def test_attached_clone_behavior(self): + """Check if an attached clone behaves correctly. + + Test for https://github.com/FreeCAD/FreeCAD/issues/8771. + """ + operation = "Check attached Draft Clone behavior" + _msg(" Test '{}'".format(operation)) + + box1 = App.ActiveDocument.addObject("Part::Box") + box1.Length = 10 + box2 = App.ActiveDocument.addObject("Part::Box") + App.ActiveDocument.recompute() + + obj = Draft.make_clone(box1) + obj.MapMode = "ObjectXY" + obj.Support = [(box2, ("",))] + App.ActiveDocument.recompute() + + box1.Length = 1 + App.ActiveDocument.recompute() + + self.assertTrue(obj.Shape.BoundBox.XLength == 1, "'{}' failed".format(operation)) + def test_draft_to_techdraw(self): """Create a solid, and then a DraftView on a TechDraw page.""" operation = "TechDraw DraftView (relies on Draft code)"