Merge pull request #4818 from Russ4262/refactor/path_array
[Path] Refactor path array algorithm into independent `PathArray` class
This commit is contained in:
@@ -141,8 +141,10 @@ class _ToggleOperation:
|
||||
return False
|
||||
try:
|
||||
for sel in FreeCADGui.Selection.getSelectionEx():
|
||||
if not isinstance(PathScripts.PathDressup.baseOp(sel.Object).Proxy, PathScripts.PathOp.ObjectOp):
|
||||
return False
|
||||
selProxy = PathScripts.PathDressup.baseOp(sel.Object).Proxy
|
||||
if not isinstance(selProxy, PathScripts.PathOp.ObjectOp) and \
|
||||
not isinstance(selProxy, PathScripts.PathArray.ObjectArray):
|
||||
return False
|
||||
return True
|
||||
except(IndexError, AttributeError):
|
||||
return False
|
||||
|
||||
@@ -62,10 +62,13 @@ class ObjectArray:
|
||||
"Path", QtCore.QT_TRANSLATE_NOOP("App::Property","Maximum random offset of copies"))
|
||||
obj.addProperty("App::PropertyLink", "ToolController",
|
||||
"Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "The tool controller that will be used to calculate the path"))
|
||||
obj.addProperty("App::PropertyBool", "Active",
|
||||
"Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "Make False, to prevent operation from generating code"))
|
||||
|
||||
obj.Active = True
|
||||
obj.Type = ['Linear1D', 'Linear2D', 'Polar']
|
||||
|
||||
self.setEditorProperties(obj)
|
||||
self.setEditorModes(obj)
|
||||
obj.Proxy = self
|
||||
|
||||
def __getstate__(self):
|
||||
@@ -74,39 +77,41 @@ class ObjectArray:
|
||||
def __setstate__(self, state):
|
||||
return None
|
||||
|
||||
def setEditorProperties(self, obj):
|
||||
def setEditorModes(self, obj):
|
||||
if obj.Type == 'Linear1D':
|
||||
angleMode = centreMode = copiesXMode = copiesYMode = swapDirectionMode = 2
|
||||
copiesMode = offsetMode = 0
|
||||
elif obj.Type == 'Linear2D':
|
||||
angleMode = copiesMode = centreMode = 2
|
||||
copiesXMode = copiesYMode = offsetMode = swapDirectionMode = 0
|
||||
elif obj.Type == 'Polar':
|
||||
angleMode = copiesMode = centreMode = 0
|
||||
copiesXMode = copiesYMode = offsetMode = swapDirectionMode = 2
|
||||
|
||||
obj.setEditorMode('Angle', angleMode)
|
||||
obj.setEditorMode('Copies', copiesMode)
|
||||
obj.setEditorMode('Centre', centreMode)
|
||||
obj.setEditorMode('CopiesX', copiesXMode)
|
||||
obj.setEditorMode('CopiesY', copiesYMode)
|
||||
obj.setEditorMode('Offset', offsetMode)
|
||||
obj.setEditorMode('SwapDirection', swapDirectionMode)
|
||||
obj.setEditorMode('JitterPercent', 0)
|
||||
obj.setEditorMode('JitterMagnitude', 0)
|
||||
obj.setEditorMode('ToolController', 2)
|
||||
if obj.Type == 'Linear2D':
|
||||
obj.setEditorMode('Angle', 2)
|
||||
obj.setEditorMode('Copies', 2)
|
||||
obj.setEditorMode('Centre', 2)
|
||||
|
||||
obj.setEditorMode('CopiesX', 0)
|
||||
obj.setEditorMode('CopiesY', 0)
|
||||
obj.setEditorMode('Offset', 0)
|
||||
obj.setEditorMode('SwapDirection', False)
|
||||
elif obj.Type == 'Polar':
|
||||
obj.setEditorMode('Angle', 0)
|
||||
obj.setEditorMode('Copies', 0)
|
||||
obj.setEditorMode('Centre', 0)
|
||||
|
||||
obj.setEditorMode('CopiesX', 2)
|
||||
obj.setEditorMode('CopiesY', 2)
|
||||
obj.setEditorMode('Offset', 2)
|
||||
elif obj.Type == 'Linear1D':
|
||||
obj.setEditorMode('Angle', 2)
|
||||
obj.setEditorMode('Copies', 0)
|
||||
obj.setEditorMode('Centre', 2)
|
||||
|
||||
obj.setEditorMode('CopiesX', 2)
|
||||
obj.setEditorMode('CopiesY', 2)
|
||||
obj.setEditorMode('Offset', 0)
|
||||
|
||||
def onChanged(self, obj, prop):
|
||||
if prop == "Type":
|
||||
self.setEditorProperties(obj)
|
||||
self.setEditorModes(obj)
|
||||
|
||||
def onDocumentRestored(self, obj):
|
||||
"""onDocumentRestored(obj) ... Called automatically when document is restored."""
|
||||
|
||||
if not hasattr(obj, "Active"):
|
||||
obj.addProperty("App::PropertyBool", "Active",
|
||||
"Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "Make False, to prevent operation from generating code"))
|
||||
obj.Active = True
|
||||
|
||||
self.setEditorModes(obj)
|
||||
|
||||
def rotatePath(self, path, angle, centre):
|
||||
'''
|
||||
@@ -167,26 +172,80 @@ class ObjectArray:
|
||||
|
||||
return newPath
|
||||
|
||||
def calculateJitter(self, obj, pos):
|
||||
if random.randint(0,100) < obj.JitterPercent:
|
||||
pos.x = pos.x + random.uniform(-obj.JitterMagnitude.x, obj.JitterMagnitude.y)
|
||||
pos.y = pos.y + random.uniform(-obj.JitterMagnitude.y, obj.JitterMagnitude.y)
|
||||
pos.z = pos.z + random.uniform(-obj.JitterMagnitude.z, obj.JitterMagnitude.z)
|
||||
return pos
|
||||
|
||||
|
||||
def execute(self, obj):
|
||||
|
||||
# backwards compatibility for PathArrays created before support for multiple bases
|
||||
if isinstance(obj.Base, list):
|
||||
base = obj.Base
|
||||
else:
|
||||
base = [obj.Base]
|
||||
|
||||
if len(base)==0:
|
||||
if len(base) == 0:
|
||||
return
|
||||
|
||||
obj.ToolController = base[0].ToolController
|
||||
|
||||
# Do not generate paths and clear current Path data if operation not
|
||||
if not obj.Active:
|
||||
if obj.Path:
|
||||
obj.Path = Path.Path()
|
||||
return
|
||||
|
||||
pa = PathArray(obj.Base, obj.Type, obj.Copies, obj.Offset,
|
||||
obj.CopiesX, obj.CopiesY, obj.Angle, obj.Centre, obj.SwapDirection,
|
||||
obj.JitterMagnitude, obj.JitterPercent, obj.Name)
|
||||
obj.Path = pa.getPath()
|
||||
|
||||
|
||||
class PathArray:
|
||||
"""class PathArray ...
|
||||
This class receives one or more base operations and repeats those operations
|
||||
at set intervals based upon array type requested and the related settings for that type."""
|
||||
|
||||
def __init__(self, baseList, arrayType, copies, offsetVector,
|
||||
copiesX, copiesY, angle, centre, swapDirection,
|
||||
jitterMagnitude=FreeCAD.Vector(0, 0, 0), jitterPercent=0,
|
||||
seed='FreeCAD'):
|
||||
self.baseList = list()
|
||||
self.arrayType = arrayType # ['Linear1D', 'Linear2D', 'Polar']
|
||||
self.copies = copies
|
||||
self.offsetVector = offsetVector
|
||||
self.copiesX = copiesX
|
||||
self.copiesY = copiesY
|
||||
self.angle = angle
|
||||
self.centre = centre
|
||||
self.swapDirection = swapDirection
|
||||
self.jitterMagnitude = jitterMagnitude
|
||||
self.jitterPercent = jitterPercent
|
||||
self.seed = seed
|
||||
|
||||
if baseList:
|
||||
if isinstance(baseList, list):
|
||||
self.baseList = baseList
|
||||
else:
|
||||
self.baseList = [baseList]
|
||||
|
||||
# Private method
|
||||
def _calculateJitter(self, pos):
|
||||
"""_calculateJitter(pos) ...
|
||||
Returns the position argument with a random vector shift applied."""
|
||||
if self.jitterPercent == 0:
|
||||
pass
|
||||
elif random.randint(0,100) < self.jitterPercent:
|
||||
pos.x = pos.x + random.uniform(-self.jitterMagnitude.x, self.jitterMagnitude.y)
|
||||
pos.y = pos.y + random.uniform(-self.jitterMagnitude.y, self.jitterMagnitude.y)
|
||||
pos.z = pos.z + random.uniform(-self.jitterMagnitude.z, self.jitterMagnitude.z)
|
||||
return pos
|
||||
|
||||
# Public method
|
||||
def getPath(self):
|
||||
"""getPath() ... Call this method on an instance of the class to generate and return
|
||||
path data for the requested path array."""
|
||||
|
||||
if len(self.baseList) == 0:
|
||||
PathLog.error(translate("PathArray", "No base objects for PathArray."))
|
||||
return None
|
||||
|
||||
base = self.baseList
|
||||
for b in base:
|
||||
if not b.isDerivedFrom("Path::Feature"):
|
||||
return
|
||||
@@ -194,17 +253,18 @@ class ObjectArray:
|
||||
return
|
||||
if not b.ToolController:
|
||||
return
|
||||
if b.ToolController != obj.ToolController:
|
||||
if b.ToolController != base[0].ToolController:
|
||||
# this may be important if Job output is split by tool controller
|
||||
PathLog.warning(QtCore.QT_TRANSLATE_NOOP("App::Property",'Arrays of paths having different tool controllers are handled according to the tool controller of the first path.'))
|
||||
PathLog.warning(translate("PathArray", "Arrays of paths having different tool controllers are handled according to the tool controller of the first path."))
|
||||
|
||||
# build copies
|
||||
output = ""
|
||||
random.seed(obj.Name)
|
||||
if obj.Type == 'Linear1D':
|
||||
for i in range(obj.Copies):
|
||||
pos = FreeCAD.Vector(obj.Offset.x * (i + 1), obj.Offset.y * (i + 1), 0)
|
||||
pos = self.calculateJitter(obj, pos)
|
||||
random.seed(self.seed)
|
||||
|
||||
if self.arrayType == 'Linear1D':
|
||||
for i in range(self.copies):
|
||||
pos = FreeCAD.Vector(self.offsetVector.x * (i + 1), self.offsetVector.y * (i + 1), 0)
|
||||
pos = self._calculateJitter(pos)
|
||||
|
||||
for b in base:
|
||||
pl = FreeCAD.Placement()
|
||||
@@ -213,15 +273,15 @@ class ObjectArray:
|
||||
for cm in b.Path.Commands])
|
||||
output += np.toGCode()
|
||||
|
||||
elif obj.Type == 'Linear2D':
|
||||
if obj.SwapDirection:
|
||||
for i in range(obj.CopiesY + 1):
|
||||
for j in range(obj.CopiesX + 1):
|
||||
elif self.arrayType == 'Linear2D':
|
||||
if self.swapDirection:
|
||||
for i in range(self.copiesY + 1):
|
||||
for j in range(self.copiesX + 1):
|
||||
if (i % 2) == 0:
|
||||
pos = FreeCAD.Vector(obj.Offset.x * j, obj.Offset.y * i, 0)
|
||||
pos = FreeCAD.Vector(self.offsetVector.x * j, self.offsetVector.y * i, 0)
|
||||
else:
|
||||
pos = FreeCAD.Vector(obj.Offset.x * (obj.CopiesX - j), obj.Offset.y * i, 0)
|
||||
pos = self.calculateJitter(obj, pos)
|
||||
pos = FreeCAD.Vector(self.offsetVector.x * (self.copiesX - j), self.offsetVector.y * i, 0)
|
||||
pos = self._calculateJitter(pos)
|
||||
|
||||
for b in base:
|
||||
pl = FreeCAD.Placement()
|
||||
@@ -231,13 +291,13 @@ class ObjectArray:
|
||||
np = Path.Path([cm.transform(pl) for cm in b.Path.Commands])
|
||||
output += np.toGCode()
|
||||
else:
|
||||
for i in range(obj.CopiesX + 1):
|
||||
for j in range(obj.CopiesY + 1):
|
||||
for i in range(self.copiesX + 1):
|
||||
for j in range(self.copiesY + 1):
|
||||
if (i % 2) == 0:
|
||||
pos = FreeCAD.Vector(obj.Offset.x * i, obj.Offset.y * j, 0)
|
||||
pos = FreeCAD.Vector(self.offsetVector.x * i, self.offsetVector.y * j, 0)
|
||||
else:
|
||||
pos = FreeCAD.Vector(obj.Offset.x * i, obj.Offset.y * (obj.CopiesY - j), 0)
|
||||
pos = self.calculateJitter(obj, pos)
|
||||
pos = FreeCAD.Vector(self.offsetVector.x * i, self.offsetVector.y * (self.copiesY - j), 0)
|
||||
pos = self._calculateJitter(pos)
|
||||
|
||||
for b in base:
|
||||
pl = FreeCAD.Placement()
|
||||
@@ -246,20 +306,18 @@ class ObjectArray:
|
||||
pl.move(pos)
|
||||
np = Path.Path([cm.transform(pl) for cm in b.Path.Commands])
|
||||
output += np.toGCode()
|
||||
|
||||
|
||||
# Eif
|
||||
else:
|
||||
for i in range(obj.Copies):
|
||||
for i in range(self.copies):
|
||||
for b in base:
|
||||
ang = 360
|
||||
if obj.Copies > 0:
|
||||
ang = obj.Angle / obj.Copies * (1 + i)
|
||||
np = self.rotatePath(b.Path.Commands, ang, obj.Centre)
|
||||
if self.copies > 0:
|
||||
ang = self.angle / self.copies * (1 + i)
|
||||
np = self.rotatePath(b.Path.Commands, ang, self.centre)
|
||||
output += np.toGCode()
|
||||
|
||||
# print output
|
||||
path = Path.Path(output)
|
||||
obj.Path = path
|
||||
# return output
|
||||
return Path.Path(output)
|
||||
|
||||
|
||||
class ViewProviderArray:
|
||||
|
||||
Reference in New Issue
Block a user