Draft: fix issue with undo (#8267)

This commit is contained in:
Roy-043
2023-01-31 21:38:19 +01:00
committed by GitHub
parent 28df3265cc
commit f9cdaaf3d9
25 changed files with 249 additions and 117 deletions

View File

@@ -87,16 +87,7 @@ def move(objectslist, vector, copy=False):
else:
real_vector = vector
if utils.get_type(obj) == "Point":
if copy:
newobj = make_copy.make_copy(obj)
else:
newobj = obj
newobj.X = obj.X.Value + real_vector.x
newobj.Y = obj.Y.Value + real_vector.y
newobj.Z = obj.Z.Value + real_vector.z
elif obj.isDerivedFrom("App::DocumentObjectGroup"):
if obj.isDerivedFrom("App::DocumentObjectGroup"):
if copy:
newobj = newgroups[obj.Name]
else:

View File

@@ -121,19 +121,6 @@ def rotate(objectslist, angle, center=App.Vector(0, 0, 0),
newobj.ViewObject.RotationAxis = "Z"
newobj.ViewObject.Rotation = -angle
elif utils.get_type(obj) == "Point":
if copy:
newobj = make_copy.make_copy(obj)
else:
newobj = obj
v = App.Vector(newobj.X, newobj.Y, newobj.Z)
rv = v.sub(real_center)
rv = DraftVecUtils.rotate(rv, math.radians(angle), real_axis)
v = real_center.add(rv)
newobj.X = v.x
newobj.Y = v.y
newobj.Z = v.z
elif obj.isDerivedFrom("App::DocumentObjectGroup"):
if copy:
newobj = newgroups[obj.Name]

View File

@@ -191,7 +191,13 @@ class DraftTool:
pass
self.call = None
if self.commitList:
todo.ToDo.delayCommit(self.commitList)
last_cmd = self.commitList[-1][1][-1]
if last_cmd.find("recompute") >= 0:
self.commitList[-1] = (self.commitList[-1][0], self.commitList[-1][1][:-1])
todo.ToDo.delayCommit(self.commitList)
todo.ToDo.delayAfter(Gui.doCommand, last_cmd)
else:
todo.ToDo.delayCommit(self.commitList)
self.commitList = []
def commit(self, name, func):

View File

@@ -75,6 +75,7 @@ def make_point(X=0, Y=0, Z=0, color=None, name="Point", point_size=5):
X = X.x
Point(obj, X, Y, Z)
obj.Placement.Base = App.Vector(X, Y, Z)
if App.GuiUp:
ViewProviderPoint(obj.ViewObject)

View File

@@ -381,7 +381,9 @@ class Array(DraftLink):
def execute(self, obj):
"""Execute when the object is created or recomputed."""
if not obj.Base:
if self.props_changed_placement_only() \
or not obj.Base:
self.props_changed_clear()
return
pl = obj.Placement
@@ -419,7 +421,9 @@ class Array(DraftLink):
axis, center,
obj.NumberCircles, obj.Symmetry)
return super(Array, self).buildShape(obj, pl, pls)
self.buildShape(obj, pl, pls)
self.props_changed_clear()
return (not self.use_link)
# Alias for compatibility with v0.18 and earlier

View File

@@ -81,6 +81,10 @@ class DraftObject(object):
obj.Proxy = self
self.Type = tp
def onDocumentRestored(self,obj):
# Object properties are updated when the document is opened.
self.props_changed_clear()
def __getstate__(self):
"""Return a tuple of all serializable objects or None.
@@ -155,6 +159,42 @@ class DraftObject(object):
"""
pass
def props_changed_store(self, prop):
"""Store the name of the property that will be changed in the
self.props_changed list.
The function should be called at the start of onChanged or onBeforeChange.
The list is used to detect Placement-only changes. In such cases the
Shape of an object does not need to be updated. Not updating the Shape
avoids Uno/Redo issues for the Windows version and is also more efficient.
"""
if not hasattr(self, "props_changed"):
self.props_changed = [prop]
else:
self.props_changed.append(prop)
def props_changed_clear(self):
"""Remove the self.props_changed attribute if it exists.
The function should be called just before execute returns.
"""
if hasattr(self, "props_changed"):
delattr(self, "props_changed")
def props_changed_placement_only(self):
"""Return `True` if the self.props_changed list, after removing `Shape`
and `_LinkTouched` items, only contains `Placement` items.
"""
if not hasattr(self, "props_changed"):
return False
props = set(self.props_changed)
if "Shape" in props:
props.remove("Shape")
if "_LinkTouched" in props:
props.remove("_LinkTouched")
return props == {"Placement"}
# Alias for compatibility with v0.18 and earlier
_DraftObject = DraftObject

View File

@@ -77,8 +77,14 @@ class BezCurve(DraftObject):
obj.setEditorMode("Continuity", 1)
def execute(self, fp):
if self.props_changed_placement_only():
fp.positionBySupport()
self.props_changed_clear()
return
self.createGeometry(fp)
fp.positionBySupport()
self.props_changed_clear()
def _segpoleslst(self,fp):
"""Split the points into segments."""
@@ -99,6 +105,8 @@ class BezCurve(DraftObject):
#fp.Continuity = [0]*numsegments
def onChanged(self, fp, prop):
self.props_changed_store(prop)
if prop == 'Closed':
# if remove the last entry when curve gets opened
oldlen = len(fp.Continuity)

View File

@@ -43,6 +43,11 @@ class Block(DraftObject):
obj.addProperty("App::PropertyLinkList","Components", "Draft", _tip)
def execute(self, obj):
if self.props_changed_placement_only():
obj.positionBySupport()
self.props_changed_clear()
return
import Part
plm = obj.Placement
shps = []
@@ -53,6 +58,10 @@ class Block(DraftObject):
obj.Shape = shape
obj.Placement = plm
obj.positionBySupport()
self.props_changed_clear()
def onChanged(self, obj, prop):
self.props_changed_store(prop)
# Alias for compatibility with v0.18 and earlier

View File

@@ -83,6 +83,8 @@ class BSpline(DraftObject):
return params
def onChanged(self, fp, prop):
self.props_changed_store(prop)
if prop == "Parameterization":
if fp.Parameterization < 0.:
fp.Parameterization = 0.
@@ -90,13 +92,16 @@ class BSpline(DraftObject):
fp.Parameterization = 1.0
def execute(self, obj):
if self.props_changed_placement_only() \
or not obj.Points:
obj.positionBySupport()
self.props_changed_clear()
return
import Part
self.assureProperties(obj)
if not obj.Points:
obj.positionBySupport()
self.knotSeq = self.parameterization(obj.Points, obj.Parameterization, obj.Closed)
plm = obj.Placement
if obj.Closed and (len(obj.Points) > 2):
@@ -131,6 +136,7 @@ class BSpline(DraftObject):
obj.Area = shape.Area
obj.Placement = plm
obj.positionBySupport()
self.props_changed_clear()
# Alias for compatibility with v0.18 and earlier

View File

@@ -64,9 +64,13 @@ class Circle(DraftObject):
obj.MakeFace = utils.get_param("fillmode", True)
def execute(self, obj):
"""This method is run when the object is created or recomputed."""
if self.props_changed_placement_only():
obj.positionBySupport()
self.props_changed_clear()
return
import Part
plm = obj.Placement
@@ -88,8 +92,11 @@ class Circle(DraftObject):
obj.Area = shape.Area
obj.Placement = plm
obj.positionBySupport()
self.props_changed_clear()
def onChanged(self, obj, prop):
self.props_changed_store(prop)
# Alias for compatibility with v0.18 and earlier

View File

@@ -86,6 +86,12 @@ class Clone(DraftObject):
return Part.makeCompound(shapes)
def execute(self,obj):
if self.props_changed_placement_only():
if hasattr(obj,"positionBySupport"):
obj.positionBySupport()
self.props_changed_clear()
return
import Part
pl = obj.Placement
shapes = []
@@ -118,6 +124,10 @@ class Clone(DraftObject):
obj.Placement = pl
if hasattr(obj,"positionBySupport"):
obj.positionBySupport()
self.props_changed_clear()
def onChanged(self, obj, prop):
self.props_changed_store(prop)
def getSubVolume(self,obj,placement=None):
# this allows clones of arch windows to return a subvolume too

View File

@@ -181,6 +181,9 @@ class DraftLink(DraftObject):
else:
self.execute(obj)
# Object properties are updated when the document is opened.
self.props_changed_clear()
def buildShape(self, obj, pl, pls):
"""Build the shape of the link object."""
if self.use_link:
@@ -229,6 +232,8 @@ class DraftLink(DraftObject):
def onChanged(self, obj, prop):
"""Execute when a property changes."""
self.props_changed_store(prop)
if not getattr(self, 'use_link', False):
return

View File

@@ -63,6 +63,11 @@ class Ellipse(DraftObject):
obj.MakeFace = utils.get_param("fillmode",True)
def execute(self, obj):
if self.props_changed_placement_only():
obj.positionBySupport()
self.props_changed_clear()
return
import Part
plm = obj.Placement
if obj.MajorRadius.Value < obj.MinorRadius.Value:
@@ -91,6 +96,10 @@ class Ellipse(DraftObject):
obj.Area = shape.Area
obj.Placement = plm
obj.positionBySupport()
self.props_changed_clear()
def onChanged(self, obj, prop):
self.props_changed_store(prop)
# Alias for compatibility with v0.18 and earlier

View File

@@ -59,6 +59,10 @@ class Facebinder(DraftObject):
obj.setEditorMode("Area",1)
def execute(self,obj):
if self.props_changed_placement_only():
self.props_changed_clear()
return
import Part
pl = obj.Placement
if not obj.Faces:
@@ -117,6 +121,10 @@ class Facebinder(DraftObject):
obj.Shape = sh
obj.Placement = pl
obj.Area = area
self.props_changed_clear()
def onChanged(self, obj, prop):
self.props_changed_store(prop)
def addSubobjects(self,obj,facelinks):
"""adds facelinks to this facebinder"""

View File

@@ -28,9 +28,10 @@ import FreeCAD as App
from draftutils.translate import translate, QT_TRANSLATE_NOOP
from draftgeoutils.general import geomType
from draftobjects.base import DraftObject
class Hatch:
class Hatch(DraftObject):
def __init__(self,obj):
@@ -76,24 +77,20 @@ class Hatch:
def execute(self,obj):
if self.props_changed_placement_only() \
or not obj.Base \
or not obj.File \
or not obj.Pattern \
or not obj.Scale \
or not obj.Pattern in self.getPatterns(obj.File) \
or not obj.Base.isDerivedFrom("Part::Feature") \
or not obj.Base.Shape.Faces:
self.props_changed_clear()
return
import Part
import TechDraw
if not obj.Base:
return
if not obj.File:
return
if not obj.Pattern:
return
if not obj.Scale:
return
if not obj.Pattern in self.getPatterns(obj.File):
return
if not obj.Base.isDerivedFrom("Part::Feature"):
return
if not obj.Base.Shape.Faces:
return
shapes = []
for face in obj.Base.Shape.Faces:
if face.findPlane(): # Only planar faces.
@@ -132,6 +129,11 @@ class Hatch:
shapes.append(shape)
if shapes:
obj.Shape = Part.makeCompound(shapes)
self.props_changed_clear()
def onChanged(self, obj, prop):
self.props_changed_store(prop)
def getPatterns(self,filename):

View File

@@ -272,7 +272,10 @@ class PathArray(DraftLink):
def execute(self, obj):
"""Execute when the object is created or recomputed."""
if not obj.Base or not obj.PathObject:
if self.props_changed_placement_only() \
or not obj.Base \
or not obj.PathObject:
self.props_changed_clear()
return
# placement of entire PathArray object
@@ -303,9 +306,9 @@ class PathArray(DraftLink):
obj.ForceVertical,
obj.VerticalVector)
return super(PathArray, self).buildShape(obj,
array_placement,
copy_placements)
self.buildShape(obj, array_placement, copy_placements)
self.props_changed_clear()
return (not self.use_link)
def get_wires(self, path_object, subelements):
"""Get wires from the path object."""
@@ -375,20 +378,9 @@ class PathArray(DraftLink):
Add properties that don't exist.
"""
super(PathArray, self).migrate_attributes(obj)
self.set_properties(obj)
self.migrate_properties_0v19(obj)
if self.use_link:
self.linkSetup(obj)
else:
obj.setPropertyStatus('Shape', '-Transient')
if obj.Shape.isNull():
if getattr(obj, 'PlacementList', None):
self.buildShape(obj, obj.Placement, obj.PlacementList)
else:
self.execute(obj)
super(PathArray, self).onDocumentRestored(obj)
def migrate_properties_0v19(self, obj):
"""Migrate properties of this class, not from the parent class."""

View File

@@ -120,31 +120,20 @@ class PathTwistedArray(DraftLink):
super(PathTwistedArray, self).linkSetup(obj)
obj.configLinkProperty(ElementCount='Count')
def onChanged(self, obj, prop):
"""Execute when a property is changed."""
super(PathTwistedArray, self).onChanged(obj, prop)
def onDocumentRestored(self, obj):
"""Execute code when the document is restored.
Add properties that don't exist.
"""
self.set_properties(obj)
if self.use_link:
self.linkSetup(obj)
else:
obj.setPropertyStatus('Shape', '-Transient')
if obj.Shape.isNull():
if getattr(obj, 'PlacementList', None):
self.buildShape(obj, obj.Placement, obj.PlacementList)
else:
self.execute(obj)
super(PathTwistedArray, self).onDocumentRestored(obj)
def execute(self, obj):
"""Execute when the object is created or recomputed."""
if not obj.Base or not obj.PathObject:
if self.props_changed_placement_only() \
or not obj.Base \
or not obj.PathObject:
self.props_changed_clear()
return
# placement of entire PathArray object
@@ -158,8 +147,8 @@ class PathTwistedArray(DraftLink):
count=count,
rot_factor=rot_factor)
return super(PathTwistedArray, self).buildShape(obj,
array_placement,
copy_placements)
self.buildShape(obj, array_placement, copy_placements)
self.props_changed_clear()
return (not self.use_link)
## @}

View File

@@ -57,19 +57,25 @@ class Point(DraftObject):
obj.setPropertyStatus('Placement', 'Hidden')
def execute(self, obj):
base = obj.Placement.Base
xyz_vec = App.Vector(obj.X.Value, obj.Y.Value, obj.Z.Value)
if self.props_changed_placement_only():
if base != xyz_vec:
obj.X = base.x
obj.Y = base.y
obj.Z = base.z
self.props_changed_clear()
return
import Part
shape = Part.Vertex(App.Vector(0, 0, 0))
obj.Shape = shape
if obj.Placement.Base != App.Vector(obj.X, obj.Y, obj.Z):
obj.Placement.Base = App.Vector(obj.X, obj.Y, obj.Z)
obj.Shape = Part.Vertex(App.Vector(0, 0, 0))
if base != xyz_vec:
obj.Placement.Base = xyz_vec
self.props_changed_clear()
def onChanged(self, obj, prop):
if prop == "Placement" \
and obj.Placement.Base != App.Vector(obj.X, obj.Y, obj.Z):
base = obj.Placement.Base
obj.X = base.x
obj.Y = base.y
obj.Z = base.z
self.props_changed_store(prop)
# Alias for compatibility with v0.18 and earlier

View File

@@ -105,12 +105,19 @@ class PointArray(DraftLink):
def execute(self, obj):
"""Run when the object is created or recomputed."""
if self.props_changed_placement_only() \
or not obj.Base \
or not obj.PointObject:
self.props_changed_clear()
return
pt_list = get_point_list(obj.PointObject)
obj.Count = len(pt_list)
pls = build_placements(obj.Base, pt_list, obj.ExtraPlacement)
return super(PointArray, self).buildShape(obj, obj.Placement, pls)
self.buildShape(obj, obj.Placement, pls)
self.props_changed_clear()
return (not self.use_link)
def onDocumentRestored(self, obj):
"""Execute code when the document is restored.
@@ -134,7 +141,7 @@ class PointArray(DraftLink):
self.set_properties(obj)
self.migrate_properties_0v19(obj)
return super(PointArray, self).onDocumentRestored(obj)
super(PointArray, self).onDocumentRestored(obj)
def migrate_properties_0v19(self, obj):
"""Migrate properties."""

View File

@@ -77,6 +77,11 @@ class Polygon(DraftObject):
obj.Radius = 1
def execute(self, obj):
if self.props_changed_placement_only():
obj.positionBySupport()
self.props_changed_clear()
return
if (obj.FacesNumber >= 3) and (obj.Radius.Value > 0):
import Part
plm = obj.Placement
@@ -115,6 +120,10 @@ class Polygon(DraftObject):
obj.Area = shape.Area
obj.Placement = plm
obj.positionBySupport()
self.props_changed_clear()
def onChanged(self, obj, prop):
self.props_changed_store(prop)
# Alias for compatibility with v0.18 and earlier

View File

@@ -74,6 +74,11 @@ class Rectangle(DraftObject):
def execute(self, obj):
"""This method is run when the object is created or recomputed."""
if self.props_changed_placement_only():
obj.positionBySupport()
self.props_changed_clear()
return
import Part
if (obj.Length.Value == 0) or (obj.Height.Value == 0):
@@ -164,8 +169,11 @@ class Rectangle(DraftObject):
obj.Area = shape.Area
obj.Placement = plm
obj.positionBySupport()
self.props_changed_clear()
def onChanged(self, obj, prop):
self.props_changed_store(prop)
# Alias for compatibility with v0.18 and earlier

View File

@@ -193,12 +193,14 @@ class Shape2DView(DraftObject):
objs = [o for o in objs if not(o.Name in obj.ExclusionNames)]
return objs
def execute(self,obj):
def execute(self, obj):
if self.props_changed_placement_only() \
or not getattr(obj, "AutoUpdate", True):
obj.positionBySupport()
self.props_changed_clear()
return
if not getattr(obj,"AutoUpdate", True):
return True
import Part, DraftGeomUtils
obj.positionBySupport()
pl = obj.Placement
if obj.Base:
if utils.get_type(obj.Base) in ["BuildingPart","SectionPlane"]:
@@ -352,8 +354,13 @@ class Shape2DView(DraftObject):
obj.Shape = Part.makeCompound(views)
else:
App.Console.PrintWarning(obj.ProjectionMode+" mode not implemented\n")
if not DraftGeomUtils.isNull(pl):
obj.Placement = pl
obj.Placement = pl
obj.positionBySupport()
self.props_changed_clear()
def onChanged(self, obj, prop):
self.props_changed_store(prop)
# Alias for compatibility with v0.18 and earlier

View File

@@ -58,6 +58,11 @@ class ShapeString(DraftObject):
obj.addProperty("App::PropertyBool", "MakeFace", "Draft", _tip).MakeFace = True
def execute(self, obj):
if self.props_changed_placement_only():
obj.positionBySupport()
self.props_changed_clear()
return
import Part
# import OpenSCAD2Dgeom
if obj.String and obj.FontFile:
@@ -109,6 +114,10 @@ class ShapeString(DraftObject):
if plm:
obj.Placement = plm
obj.positionBySupport()
self.props_changed_clear()
def onChanged(self, obj, prop):
self.props_changed_store(prop)
def makeFaces(self, wireChar):
import Part

View File

@@ -96,6 +96,12 @@ class Wire(DraftObject):
obj.Closed = False
def execute(self, obj):
if self.props_changed_placement_only():
obj.positionBySupport()
self.update_start_end(obj)
self.props_changed_clear()
return
import Part
plm = obj.Placement
if obj.Base and (not obj.Tool):
@@ -195,15 +201,19 @@ class Wire(DraftObject):
obj.Placement = plm
obj.positionBySupport()
self.onChanged(obj,"Placement")
self.update_start_end(obj)
self.props_changed_clear()
def onChanged(self, obj, prop):
self.props_changed_store(prop)
tol = 1e-7
if prop == "Start":
pts = obj.Points
invpl = App.Placement(obj.Placement).inverse()
realfpstart = invpl.multVec(obj.Start)
if pts:
if pts[0] != realfpstart:
if not pts[0].isEqual(realfpstart, tol):
pts[0] = realfpstart
obj.Points = pts
@@ -212,29 +222,31 @@ class Wire(DraftObject):
invpl = App.Placement(obj.Placement).inverse()
realfpend = invpl.multVec(obj.End)
if len(pts) > 1:
if pts[-1] != realfpend:
if not pts[-1].isEqual(realfpend, tol):
pts[-1] = realfpend
obj.Points = pts
elif prop == "Length":
if (len(obj.Points) == 2
and obj.Length.Value > 1e-7
and obj.Length.Value > tol
and obj.Shape
and (not obj.Shape.isNull())
and obj.Length.Value != obj.Shape.Length):
and abs(obj.Length.Value - obj.Shape.Length) > tol):
v = obj.Points[-1].sub(obj.Points[0])
v = DraftVecUtils.scaleTo(v, obj.Length.Value)
obj.Points = [obj.Points[0], obj.Points[0].add(v)]
elif prop == "Placement":
pl = App.Placement(obj.Placement)
if len(obj.Points) >= 2:
displayfpstart = pl.multVec(obj.Points[0])
displayfpend = pl.multVec(obj.Points[-1])
if obj.Start != displayfpstart:
obj.Start = displayfpstart
if obj.End != displayfpend:
obj.End = displayfpend
def update_start_end(self, obj):
tol = 1e-7
pl = App.Placement(obj.Placement)
if len(obj.Points) > 1:
displayfpstart = pl.multVec(obj.Points[0])
displayfpend = pl.multVec(obj.Points[-1])
if not obj.Start.isEqual(displayfpstart, tol):
obj.Start = displayfpstart
if not obj.End.isEqual(displayfpend, tol):
obj.End = displayfpend
# Alias for compatibility with v0.18 and earlier

View File

@@ -170,7 +170,6 @@ class ShapeStringTaskPanelCmd(ShapeStringTaskPanel):
y = App.Units.Quantity(self.form.sbY.text()).Value
z = App.Units.Quantity(self.form.sbZ.text()).Value
ssBase = App.Vector(x, y, z)
# this try block is almost identical to the one in DraftTools
try:
qr, sup, points, fil = self.sourceCmd.getStrings()
Gui.addModule("Draft")
@@ -181,7 +180,8 @@ class ShapeStringTaskPanelCmd(ShapeStringTaskPanel):
'plm.Rotation.Q=' + qr,
'ss.Placement=plm',
'ss.Support=' + sup,
'Draft.autogroup(ss)'])
'Draft.autogroup(ss)',
'FreeCAD.ActiveDocument.recompute()'])
except Exception:
_err("Draft_ShapeString: error delaying commit\n")