From 4d127347dc316b31174adc0450998bea92772f5e Mon Sep 17 00:00:00 2001 From: jim Date: Fri, 21 May 2021 21:43:25 -0700 Subject: [PATCH 01/10] PathArray support for multiple paths as base --- src/Mod/Path/PathScripts/PathArray.py | 112 +++++++++++++++----------- 1 file changed, 63 insertions(+), 49 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathArray.py b/src/Mod/Path/PathScripts/PathArray.py index e33ca1a33d..5e64536ea2 100644 --- a/src/Mod/Path/PathScripts/PathArray.py +++ b/src/Mod/Path/PathScripts/PathArray.py @@ -24,6 +24,7 @@ import FreeCAD import FreeCADGui import Path import PathScripts +from PathScripts import PathLog from PySide import QtCore import math @@ -36,8 +37,8 @@ def translate(context, text, disambig=None): class ObjectArray: def __init__(self, obj): - obj.addProperty("App::PropertyLink", "Base", - "Path", "The path to array") + obj.addProperty("App::PropertyLinkList", "Base", + "Path", "The path(s) to array") obj.addProperty("App::PropertyEnumeration", "Type", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Pattern method")) obj.addProperty("App::PropertyVectorDistance", "Offset", @@ -156,53 +157,66 @@ class ObjectArray: return newPath def execute(self, obj): - if obj.Base: - if not obj.Base.isDerivedFrom("Path::Feature"): - return - if not obj.Base.Path: - return - if not obj.Base.ToolController: - return - obj.ToolController = obj.Base.ToolController + if isinstance(obj.Base, list): + base = obj.Base + else: + base = [obj.Base] + + + if len(base)>0: + + obj.ToolController = base[0].ToolController + + for b in base: + if not b.isDerivedFrom("Path::Feature"): + return + if not b.Path: + return + if not b.ToolController: + return + if b.ToolController != obj.ToolController: + # this may be important if Job output is split by tool controller + PathLog.warning('Arrays of paths having different tool controllers are handled according to the tool controller of the first path.') # build copies - basepath = obj.Base.Path output = "" if obj.Type == 'Linear1D': for i in range(obj.Copies): - pl = FreeCAD.Placement() - pos = FreeCAD.Vector(obj.Offset.x * (i + 1), obj.Offset.y * (i + 1), 0) - pl.move(pos) - np = Path.Path([cm.transform(pl) - for cm in basepath.Commands]) - output += np.toGCode() + for b in base: + pl = FreeCAD.Placement() + pos = FreeCAD.Vector(obj.Offset.x * (i + 1), obj.Offset.y * (i + 1), 0) + pl.move(pos) + np = Path.Path([cm.transform(pl) + for cm in b.Path.Commands]) + output += np.toGCode() elif obj.Type == 'Linear2D': for i in range(obj.CopiesX + 1): for j in range(obj.CopiesY + 1): - pl = FreeCAD.Placement() - # do not process the index 0,0. It will be processed at basepath - if not (i == 0 and j == 0): - if (i % 2) == 0: - pos = FreeCAD.Vector(obj.Offset.x * i, obj.Offset.y * j, 0) - else: - pos = FreeCAD.Vector(obj.Offset.x * i, obj.Offset.y * (obj.CopiesY - j), 0) - - pl.move(pos) - np = Path.Path([cm.transform(pl) - for cm in basepath.Commands]) - output += np.toGCode() + for b in base: + pl = FreeCAD.Placement() + # do not process the index 0,0. It will be processed at basepath + if not (i == 0 and j == 0): + if (i % 2) == 0: + pos = FreeCAD.Vector(obj.Offset.x * i, obj.Offset.y * j, 0) + else: + pos = FreeCAD.Vector(obj.Offset.x * i, obj.Offset.y * (obj.CopiesY - j), 0) + + pl.move(pos) + np = Path.Path([cm.transform(pl) + for cm in b.Path.Commands]) + output += np.toGCode() else: for i in range(obj.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) + output += np.toGCode() - ang = 360 - if obj.Copies > 0: - ang = obj.Angle / obj.Copies * (1 + i) - - np = self.rotatePath(basepath, ang, obj.Centre) - output += np.toGCode() # print output path = Path.Path(output) obj.Path = path @@ -237,7 +251,7 @@ class CommandPathArray: def GetResources(self): return {'Pixmap': 'Path_Array', 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Array", "Array"), - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Array", "Creates an array from a selected path")} + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Array", "Creates an array from selected path(s)")} def IsActive(self): if bool(FreeCADGui.Selection.getSelection()) is False: @@ -252,26 +266,26 @@ class CommandPathArray: # check that the selection contains exactly what we want selection = FreeCADGui.Selection.getSelection() - if len(selection) != 1: - FreeCAD.Console.PrintError( - translate("Path_Array", "Please select exactly one path object")+"\n") - return - if not(selection[0].isDerivedFrom("Path::Feature")): - FreeCAD.Console.PrintError( - translate("Path_Array", "Please select exactly one path object")+"\n") - return + + for sel in selection: + if not(sel.isDerivedFrom("Path::Feature")): + FreeCAD.Console.PrintError( + translate("Path_Array", "Arrays can be created only from Path operations.")+"\n") + return # if everything is ok, execute and register the transaction in the # undo/redo stack FreeCAD.ActiveDocument.openTransaction("Create Array") FreeCADGui.addModule("PathScripts.PathArray") FreeCADGui.addModule("PathScripts.PathUtils") - FreeCADGui.doCommand( - 'obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Array")') + + FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Array")') + FreeCADGui.doCommand('PathScripts.PathArray.ObjectArray(obj)') - FreeCADGui.doCommand( - 'obj.Base = (FreeCAD.ActiveDocument.' + selection[0].Name + ')') - # FreeCADGui.doCommand('PathScripts.PathArray.ViewProviderArray(obj.ViewObject)') + + baseString = "[%s]" % ','.join(["FreeCAD.ActiveDocument.%s" % sel.Name for sel in selection]) + FreeCADGui.doCommand('obj.Base = %s' % baseString) + FreeCADGui.doCommand('obj.ViewObject.Proxy = 0') FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)') FreeCAD.ActiveDocument.commitTransaction() From e8e1b4ce94e3eb351cd108dba570549095bd056c Mon Sep 17 00:00:00 2001 From: jim Date: Fri, 21 May 2021 21:43:25 -0700 Subject: [PATCH 02/10] PathArray support for multiple paths as base --- src/Mod/Path/PathScripts/PathArray.py | 112 +++++++++++++++----------- 1 file changed, 63 insertions(+), 49 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathArray.py b/src/Mod/Path/PathScripts/PathArray.py index e33ca1a33d..5e64536ea2 100644 --- a/src/Mod/Path/PathScripts/PathArray.py +++ b/src/Mod/Path/PathScripts/PathArray.py @@ -24,6 +24,7 @@ import FreeCAD import FreeCADGui import Path import PathScripts +from PathScripts import PathLog from PySide import QtCore import math @@ -36,8 +37,8 @@ def translate(context, text, disambig=None): class ObjectArray: def __init__(self, obj): - obj.addProperty("App::PropertyLink", "Base", - "Path", "The path to array") + obj.addProperty("App::PropertyLinkList", "Base", + "Path", "The path(s) to array") obj.addProperty("App::PropertyEnumeration", "Type", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Pattern method")) obj.addProperty("App::PropertyVectorDistance", "Offset", @@ -156,53 +157,66 @@ class ObjectArray: return newPath def execute(self, obj): - if obj.Base: - if not obj.Base.isDerivedFrom("Path::Feature"): - return - if not obj.Base.Path: - return - if not obj.Base.ToolController: - return - obj.ToolController = obj.Base.ToolController + if isinstance(obj.Base, list): + base = obj.Base + else: + base = [obj.Base] + + + if len(base)>0: + + obj.ToolController = base[0].ToolController + + for b in base: + if not b.isDerivedFrom("Path::Feature"): + return + if not b.Path: + return + if not b.ToolController: + return + if b.ToolController != obj.ToolController: + # this may be important if Job output is split by tool controller + PathLog.warning('Arrays of paths having different tool controllers are handled according to the tool controller of the first path.') # build copies - basepath = obj.Base.Path output = "" if obj.Type == 'Linear1D': for i in range(obj.Copies): - pl = FreeCAD.Placement() - pos = FreeCAD.Vector(obj.Offset.x * (i + 1), obj.Offset.y * (i + 1), 0) - pl.move(pos) - np = Path.Path([cm.transform(pl) - for cm in basepath.Commands]) - output += np.toGCode() + for b in base: + pl = FreeCAD.Placement() + pos = FreeCAD.Vector(obj.Offset.x * (i + 1), obj.Offset.y * (i + 1), 0) + pl.move(pos) + np = Path.Path([cm.transform(pl) + for cm in b.Path.Commands]) + output += np.toGCode() elif obj.Type == 'Linear2D': for i in range(obj.CopiesX + 1): for j in range(obj.CopiesY + 1): - pl = FreeCAD.Placement() - # do not process the index 0,0. It will be processed at basepath - if not (i == 0 and j == 0): - if (i % 2) == 0: - pos = FreeCAD.Vector(obj.Offset.x * i, obj.Offset.y * j, 0) - else: - pos = FreeCAD.Vector(obj.Offset.x * i, obj.Offset.y * (obj.CopiesY - j), 0) - - pl.move(pos) - np = Path.Path([cm.transform(pl) - for cm in basepath.Commands]) - output += np.toGCode() + for b in base: + pl = FreeCAD.Placement() + # do not process the index 0,0. It will be processed at basepath + if not (i == 0 and j == 0): + if (i % 2) == 0: + pos = FreeCAD.Vector(obj.Offset.x * i, obj.Offset.y * j, 0) + else: + pos = FreeCAD.Vector(obj.Offset.x * i, obj.Offset.y * (obj.CopiesY - j), 0) + + pl.move(pos) + np = Path.Path([cm.transform(pl) + for cm in b.Path.Commands]) + output += np.toGCode() else: for i in range(obj.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) + output += np.toGCode() - ang = 360 - if obj.Copies > 0: - ang = obj.Angle / obj.Copies * (1 + i) - - np = self.rotatePath(basepath, ang, obj.Centre) - output += np.toGCode() # print output path = Path.Path(output) obj.Path = path @@ -237,7 +251,7 @@ class CommandPathArray: def GetResources(self): return {'Pixmap': 'Path_Array', 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Array", "Array"), - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Array", "Creates an array from a selected path")} + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Array", "Creates an array from selected path(s)")} def IsActive(self): if bool(FreeCADGui.Selection.getSelection()) is False: @@ -252,26 +266,26 @@ class CommandPathArray: # check that the selection contains exactly what we want selection = FreeCADGui.Selection.getSelection() - if len(selection) != 1: - FreeCAD.Console.PrintError( - translate("Path_Array", "Please select exactly one path object")+"\n") - return - if not(selection[0].isDerivedFrom("Path::Feature")): - FreeCAD.Console.PrintError( - translate("Path_Array", "Please select exactly one path object")+"\n") - return + + for sel in selection: + if not(sel.isDerivedFrom("Path::Feature")): + FreeCAD.Console.PrintError( + translate("Path_Array", "Arrays can be created only from Path operations.")+"\n") + return # if everything is ok, execute and register the transaction in the # undo/redo stack FreeCAD.ActiveDocument.openTransaction("Create Array") FreeCADGui.addModule("PathScripts.PathArray") FreeCADGui.addModule("PathScripts.PathUtils") - FreeCADGui.doCommand( - 'obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Array")') + + FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Array")') + FreeCADGui.doCommand('PathScripts.PathArray.ObjectArray(obj)') - FreeCADGui.doCommand( - 'obj.Base = (FreeCAD.ActiveDocument.' + selection[0].Name + ')') - # FreeCADGui.doCommand('PathScripts.PathArray.ViewProviderArray(obj.ViewObject)') + + baseString = "[%s]" % ','.join(["FreeCAD.ActiveDocument.%s" % sel.Name for sel in selection]) + FreeCADGui.doCommand('obj.Base = %s' % baseString) + FreeCADGui.doCommand('obj.ViewObject.Proxy = 0') FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)') FreeCAD.ActiveDocument.commitTransaction() From db8a6116da104644bd866ccc9407ea79ab8d046d Mon Sep 17 00:00:00 2001 From: jim Date: Sun, 23 May 2021 19:25:52 -0700 Subject: [PATCH 03/10] Path: Add PathArray "SwapDirection" property to process copies along X axis before Y axis --- src/Mod/Path/PathScripts/PathArray.py | 463 +++++++++++++------------- 1 file changed, 240 insertions(+), 223 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathArray.py b/src/Mod/Path/PathScripts/PathArray.py index 5e64536ea2..cbfd03fb84 100644 --- a/src/Mod/Path/PathScripts/PathArray.py +++ b/src/Mod/Path/PathScripts/PathArray.py @@ -1,23 +1,23 @@ # -*- coding: utf-8 -*- # *************************************************************************** -# * Copyright (c) 2015 Yorik van Havre * -# * * +# * Copyright (c) 2015 Yorik van Havre * +# * * # * This program is free software; you can redistribute it and/or modify * -# * it under the terms of the GNU Lesser General Public License (LGPL) * -# * as published by the Free Software Foundation; either version 2 of * -# * the License, or (at your option) any later version. * -# * for detail see the LICENCE text file. * -# * * -# * This program is distributed in the hope that it will be useful, * -# * but WITHOUT ANY WARRANTY; without even the implied warranty of * -# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * -# * GNU Library General Public License for more details. * -# * * -# * You should have received a copy of the GNU Library General Public * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * # * License along with this program; if not, write to the Free Software * # * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * -# * USA * -# * * +# * USA * +# * * # *************************************************************************** import FreeCAD @@ -32,266 +32,283 @@ __doc__ = """Path Array object and FreeCAD command""" # Qt translation handling def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) + return QtCore.QCoreApplication.translate(context, text, disambig) class ObjectArray: - def __init__(self, obj): - obj.addProperty("App::PropertyLinkList", "Base", - "Path", "The path(s) to array") - obj.addProperty("App::PropertyEnumeration", "Type", - "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Pattern method")) - obj.addProperty("App::PropertyVectorDistance", "Offset", - "Path", "The spacing between the array copies in Linear pattern") - obj.addProperty("App::PropertyInteger", "CopiesX", - "Path", "The number of copies in X direction in Linear pattern") - obj.addProperty("App::PropertyInteger", "CopiesY", - "Path", "The number of copies in Y direction in Linear pattern") - obj.addProperty("App::PropertyAngle", "Angle", - "Path", "Total angle in Polar pattern") - obj.addProperty("App::PropertyInteger", "Copies", - "Path", "The number of copies in Linear 1D and Polar pattern") - obj.addProperty("App::PropertyVector", "Centre", - "Path", "The centre of rotation in Polar pattern") - obj.addProperty("App::PropertyLink", "ToolController", - "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "The tool controller that will be used to calculate the path")) + def __init__(self, obj): + obj.addProperty("App::PropertyLinkList", "Base", + "Path", "The path(s) to array") + obj.addProperty("App::PropertyEnumeration", "Type", + "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Pattern method")) + obj.addProperty("App::PropertyVectorDistance", "Offset", + "Path", "The spacing between the array copies in Linear pattern") + obj.addProperty("App::PropertyInteger", "CopiesX", + "Path", "The number of copies in X direction in Linear pattern") + obj.addProperty("App::PropertyInteger", "CopiesY", + "Path", "The number of copies in Y direction in Linear pattern") + obj.addProperty("App::PropertyAngle", "Angle", + "Path", "Total angle in Polar pattern") + obj.addProperty("App::PropertyInteger", "Copies", + "Path", "The number of copies in Linear 1D and Polar pattern") + obj.addProperty("App::PropertyVector", "Centre", + "Path", "The centre of rotation in Polar pattern") + obj.addProperty("App::PropertyBool", "SwapDirection", + "Path", "Make copies in X direction before Y in Linear 2D pattern") + obj.addProperty("App::PropertyLink", "ToolController", + "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "The tool controller that will be used to calculate the path")) - obj.Type = ['Linear1D', 'Linear2D', 'Polar'] + obj.Type = ['Linear1D', 'Linear2D', 'Polar'] - self.setEditorProperties(obj) - obj.Proxy = self + self.setEditorProperties(obj) + obj.Proxy = self - def __getstate__(self): - return None + def __getstate__(self): + return None - def __setstate__(self, state): - return None + def __setstate__(self, state): + return None - def setEditorProperties(self, obj): - if obj.Type == 'Linear2D': - obj.setEditorMode('Angle', 2) - obj.setEditorMode('Copies', 2) - obj.setEditorMode('Centre', 2) + def setEditorProperties(self, obj): + 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) - elif obj.Type == 'Polar': - obj.setEditorMode('Angle', 0) - obj.setEditorMode('Copies', 0) - obj.setEditorMode('Centre', 0) + 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', 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) + obj.setEditorMode('CopiesX', 2) + obj.setEditorMode('CopiesY', 2) + obj.setEditorMode('Offset', 0) - def onChanged(self, obj, prop): - if prop == "Type": - self.setEditorProperties(obj) + def onChanged(self, obj, prop): + if prop == "Type": + self.setEditorProperties(obj) - def rotatePath(self, path, angle, centre): - ''' - Rotates Path around given centre vector - Only X and Y is considered - ''' - CmdMoveRapid = ['G0', 'G00'] - CmdMoveStraight = ['G1', 'G01'] - CmdMoveCW = ['G2', 'G02'] - CmdMoveCCW = ['G3', 'G03'] - CmdDrill = ['G81', 'G82', 'G83'] - CmdMoveArc = CmdMoveCW + CmdMoveCCW - CmdMove = CmdMoveStraight + CmdMoveArc + def rotatePath(self, path, angle, centre): + ''' + Rotates Path around given centre vector + Only X and Y is considered + ''' + CmdMoveRapid = ['G0', 'G00'] + CmdMoveStraight = ['G1', 'G01'] + CmdMoveCW = ['G2', 'G02'] + CmdMoveCCW = ['G3', 'G03'] + CmdDrill = ['G81', 'G82', 'G83'] + CmdMoveArc = CmdMoveCW + CmdMoveCCW + CmdMove = CmdMoveStraight + CmdMoveArc - commands = [] - ang = angle / 180 * math.pi - currX = 0 - currY = 0 - for cmd in path.Commands: - if (cmd.Name in CmdMoveRapid) or (cmd.Name in CmdMove) or (cmd.Name in CmdDrill): - params = cmd.Parameters - x = params.get("X") - if x is None: - x = currX - currX = x - y = params.get("Y") - if y is None: - y = currY - currY = y + commands = [] + ang = angle / 180 * math.pi + currX = 0 + currY = 0 + for cmd in path.Commands: + if (cmd.Name in CmdMoveRapid) or (cmd.Name in CmdMove) or (cmd.Name in CmdDrill): + params = cmd.Parameters + x = params.get("X") + if x is None: + x = currX + currX = x + y = params.get("Y") + if y is None: + y = currY + currY = y - # "move" the centre to origin - x = x - centre.x - y = y - centre.y + # "move" the centre to origin + x = x - centre.x + y = y - centre.y - # rotation around origin: - nx = x * math.cos(ang) - y * math.sin(ang) - ny = y * math.cos(ang) + x * math.sin(ang) + # rotation around origin: + nx = x * math.cos(ang) - y * math.sin(ang) + ny = y * math.cos(ang) + x * math.sin(ang) - # "move" the centre back and update - params.update({'X': nx + centre.x, 'Y': ny + centre.y}) + # "move" the centre back and update + params.update({'X': nx + centre.x, 'Y': ny + centre.y}) - # Arcs need to have the I and J params rotated as well - if cmd.Name in CmdMoveArc: - i = params.get("I") - if i is None: - i = 0 - j = params.get("J") - if j is None: - j = 0 + # Arcs need to have the I and J params rotated as well + if cmd.Name in CmdMoveArc: + i = params.get("I") + if i is None: + i = 0 + j = params.get("J") + if j is None: + j = 0 - ni = i * math.cos(ang) - j * math.sin(ang) - nj = j * math.cos(ang) + i * math.sin(ang) - params.update({'I': ni, 'J': nj}) + ni = i * math.cos(ang) - j * math.sin(ang) + nj = j * math.cos(ang) + i * math.sin(ang) + params.update({'I': ni, 'J': nj}) - cmd.Parameters = params - commands.append(cmd) - newPath = Path.Path(commands) + cmd.Parameters = params + commands.append(cmd) + newPath = Path.Path(commands) - return newPath + return newPath - def execute(self, obj): + def execute(self, obj): - if isinstance(obj.Base, list): - base = obj.Base - else: - base = [obj.Base] + if isinstance(obj.Base, list): + base = obj.Base + else: + base = [obj.Base] - if len(base)>0: + if len(base)>0: - obj.ToolController = base[0].ToolController + obj.ToolController = base[0].ToolController - for b in base: - if not b.isDerivedFrom("Path::Feature"): - return - if not b.Path: - return - if not b.ToolController: - return - if b.ToolController != obj.ToolController: - # this may be important if Job output is split by tool controller - PathLog.warning('Arrays of paths having different tool controllers are handled according to the tool controller of the first path.') + for b in base: + if not b.isDerivedFrom("Path::Feature"): + return + if not b.Path: + return + if not b.ToolController: + return + if b.ToolController != obj.ToolController: + # this may be important if Job output is split by tool controller + PathLog.warning('Arrays of paths having different tool controllers are handled according to the tool controller of the first path.') - # build copies - output = "" - if obj.Type == 'Linear1D': - for i in range(obj.Copies): - for b in base: - pl = FreeCAD.Placement() - pos = FreeCAD.Vector(obj.Offset.x * (i + 1), obj.Offset.y * (i + 1), 0) - pl.move(pos) - np = Path.Path([cm.transform(pl) - for cm in b.Path.Commands]) - output += np.toGCode() + # build copies + output = "" + if obj.Type == 'Linear1D': + for i in range(obj.Copies): + for b in base: + pl = FreeCAD.Placement() + pos = FreeCAD.Vector(obj.Offset.x * (i + 1), obj.Offset.y * (i + 1), 0) + pl.move(pos) + np = Path.Path([cm.transform(pl) + for cm in b.Path.Commands]) + output += np.toGCode() - elif obj.Type == 'Linear2D': - for i in range(obj.CopiesX + 1): - for j in range(obj.CopiesY + 1): - for b in base: - pl = FreeCAD.Placement() - # do not process the index 0,0. It will be processed at basepath - if not (i == 0 and j == 0): - if (i % 2) == 0: - pos = FreeCAD.Vector(obj.Offset.x * i, obj.Offset.y * j, 0) - else: - pos = FreeCAD.Vector(obj.Offset.x * i, obj.Offset.y * (obj.CopiesY - j), 0) - - pl.move(pos) - np = Path.Path([cm.transform(pl) - 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): + for b in base: + pl = FreeCAD.Placement() + # do not process the index 0,0. It will be processed by the base Paths themselves + if not (i == 0 and j == 0): + if (i % 2) == 0: + pos = FreeCAD.Vector(obj.Offset.x * j, obj.Offset.y * i, 0) + else: + pos = FreeCAD.Vector(obj.Offset.x * (obj.CopiesX - j), obj.Offset.y * i, 0) + pl.move(pos) + 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 b in base: + pl = FreeCAD.Placement() + # do not process the index 0,0. It will be processed by the base Paths themselves + if not (i == 0 and j == 0): + if (i % 2) == 0: + pos = FreeCAD.Vector(obj.Offset.x * i, obj.Offset.y * j, 0) + else: + pos = FreeCAD.Vector(obj.Offset.x * i, obj.Offset.y * (obj.CopiesY - j), 0) + pl.move(pos) + np = Path.Path([cm.transform(pl) for cm in b.Path.Commands]) + output += np.toGCode() - else: - for i in range(obj.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) - output += np.toGCode() - # print output - path = Path.Path(output) - obj.Path = path + else: + for i in range(obj.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) + output += np.toGCode() + + # print output + path = Path.Path(output) + obj.Path = path class ViewProviderArray: - def __init__(self, vobj): - self.Object = vobj.Object - vobj.Proxy = self + def __init__(self, vobj): + self.Object = vobj.Object + vobj.Proxy = self - def attach(self, vobj): - self.Object = vobj.Object - return + def attach(self, vobj): + self.Object = vobj.Object + return - def __getstate__(self): - return None + def __getstate__(self): + return None - def __setstate__(self, state): - return None + def __setstate__(self, state): + return None - def claimChildren(self): - if hasattr(self, "Object"): - if hasattr(self.Object, "Base"): - if self.Object.Base: - return self.Object.Base - return [] + def claimChildren(self): + if hasattr(self, "Object"): + if hasattr(self.Object, "Base"): + if self.Object.Base: + return self.Object.Base + return [] class CommandPathArray: - def GetResources(self): - return {'Pixmap': 'Path_Array', - 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Array", "Array"), - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Array", "Creates an array from selected path(s)")} + def GetResources(self): + return {'Pixmap': 'Path_Array', + 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Array", "Array"), + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Array", "Creates an array from selected path(s)")} - def IsActive(self): - if bool(FreeCADGui.Selection.getSelection()) is False: - return False - try: - obj = FreeCADGui.Selection.getSelectionEx()[0].Object - return isinstance(obj.Proxy, PathScripts.PathOp.ObjectOp) - except(IndexError, AttributeError): - return False + def IsActive(self): + if bool(FreeCADGui.Selection.getSelection()) is False: + return False + try: + obj = FreeCADGui.Selection.getSelectionEx()[0].Object + return isinstance(obj.Proxy, PathScripts.PathOp.ObjectOp) + except(IndexError, AttributeError): + return False - def Activated(self): + def Activated(self): - # check that the selection contains exactly what we want - selection = FreeCADGui.Selection.getSelection() + # check that the selection contains exactly what we want + selection = FreeCADGui.Selection.getSelection() - for sel in selection: - if not(sel.isDerivedFrom("Path::Feature")): - FreeCAD.Console.PrintError( - translate("Path_Array", "Arrays can be created only from Path operations.")+"\n") - return + for sel in selection: + if not(sel.isDerivedFrom("Path::Feature")): + FreeCAD.Console.PrintError( + translate("Path_Array", "Arrays can be created only from Path operations.")+"\n") + return - # if everything is ok, execute and register the transaction in the - # undo/redo stack - FreeCAD.ActiveDocument.openTransaction("Create Array") - FreeCADGui.addModule("PathScripts.PathArray") - FreeCADGui.addModule("PathScripts.PathUtils") + # if everything is ok, execute and register the transaction in the + # undo/redo stack + FreeCAD.ActiveDocument.openTransaction("Create Array") + FreeCADGui.addModule("PathScripts.PathArray") + FreeCADGui.addModule("PathScripts.PathUtils") - FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Array")') + FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Array")') - FreeCADGui.doCommand('PathScripts.PathArray.ObjectArray(obj)') + FreeCADGui.doCommand('PathScripts.PathArray.ObjectArray(obj)') - baseString = "[%s]" % ','.join(["FreeCAD.ActiveDocument.%s" % sel.Name for sel in selection]) - FreeCADGui.doCommand('obj.Base = %s' % baseString) + baseString = "[%s]" % ','.join(["FreeCAD.ActiveDocument.%s" % sel.Name for sel in selection]) + FreeCADGui.doCommand('obj.Base = %s' % baseString) - FreeCADGui.doCommand('obj.ViewObject.Proxy = 0') - FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)') - FreeCAD.ActiveDocument.commitTransaction() - FreeCAD.ActiveDocument.recompute() + FreeCADGui.doCommand('obj.ViewObject.Proxy = 0') + FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)') + FreeCAD.ActiveDocument.commitTransaction() + FreeCAD.ActiveDocument.recompute() if FreeCAD.GuiUp: - # register the FreeCAD command - FreeCADGui.addCommand('Path_Array', CommandPathArray()) + # register the FreeCAD command + FreeCADGui.addCommand('Path_Array', CommandPathArray()) From 0495310984615748aef11c43b65058e6f7c584c7 Mon Sep 17 00:00:00 2001 From: jim Date: Sun, 23 May 2021 19:28:36 -0700 Subject: [PATCH 04/10] fix whitespace --- src/Mod/Path/PathScripts/PathArray.py | 478 +++++++++++++------------- 1 file changed, 239 insertions(+), 239 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathArray.py b/src/Mod/Path/PathScripts/PathArray.py index cbfd03fb84..7d7cf0131e 100644 --- a/src/Mod/Path/PathScripts/PathArray.py +++ b/src/Mod/Path/PathScripts/PathArray.py @@ -1,23 +1,23 @@ # -*- coding: utf-8 -*- # *************************************************************************** -# * Copyright (c) 2015 Yorik van Havre * -# * * +# * Copyright (c) 2015 Yorik van Havre * +# * * # * This program is free software; you can redistribute it and/or modify * -# * it under the terms of the GNU Lesser General Public License (LGPL) * -# * as published by the Free Software Foundation; either version 2 of * -# * the License, or (at your option) any later version. * -# * for detail see the LICENCE text file. * -# * * -# * This program is distributed in the hope that it will be useful, * -# * but WITHOUT ANY WARRANTY; without even the implied warranty of * -# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * -# * GNU Library General Public License for more details. * -# * * -# * You should have received a copy of the GNU Library General Public * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * # * License along with this program; if not, write to the Free Software * # * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * -# * USA * -# * * +# * USA * +# * * # *************************************************************************** import FreeCAD @@ -32,283 +32,283 @@ __doc__ = """Path Array object and FreeCAD command""" # Qt translation handling def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) + return QtCore.QCoreApplication.translate(context, text, disambig) class ObjectArray: - def __init__(self, obj): - obj.addProperty("App::PropertyLinkList", "Base", - "Path", "The path(s) to array") - obj.addProperty("App::PropertyEnumeration", "Type", - "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Pattern method")) - obj.addProperty("App::PropertyVectorDistance", "Offset", - "Path", "The spacing between the array copies in Linear pattern") - obj.addProperty("App::PropertyInteger", "CopiesX", - "Path", "The number of copies in X direction in Linear pattern") - obj.addProperty("App::PropertyInteger", "CopiesY", - "Path", "The number of copies in Y direction in Linear pattern") - obj.addProperty("App::PropertyAngle", "Angle", - "Path", "Total angle in Polar pattern") - obj.addProperty("App::PropertyInteger", "Copies", - "Path", "The number of copies in Linear 1D and Polar pattern") - obj.addProperty("App::PropertyVector", "Centre", - "Path", "The centre of rotation in Polar pattern") - obj.addProperty("App::PropertyBool", "SwapDirection", - "Path", "Make copies in X direction before Y in Linear 2D pattern") - obj.addProperty("App::PropertyLink", "ToolController", - "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "The tool controller that will be used to calculate the path")) + def __init__(self, obj): + obj.addProperty("App::PropertyLinkList", "Base", + "Path", "The path(s) to array") + obj.addProperty("App::PropertyEnumeration", "Type", + "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Pattern method")) + obj.addProperty("App::PropertyVectorDistance", "Offset", + "Path", "The spacing between the array copies in Linear pattern") + obj.addProperty("App::PropertyInteger", "CopiesX", + "Path", "The number of copies in X direction in Linear pattern") + obj.addProperty("App::PropertyInteger", "CopiesY", + "Path", "The number of copies in Y direction in Linear pattern") + obj.addProperty("App::PropertyAngle", "Angle", + "Path", "Total angle in Polar pattern") + obj.addProperty("App::PropertyInteger", "Copies", + "Path", "The number of copies in Linear 1D and Polar pattern") + obj.addProperty("App::PropertyVector", "Centre", + "Path", "The centre of rotation in Polar pattern") + obj.addProperty("App::PropertyBool", "SwapDirection", + "Path", "Make copies in X direction before Y in Linear 2D pattern") + obj.addProperty("App::PropertyLink", "ToolController", + "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "The tool controller that will be used to calculate the path")) - obj.Type = ['Linear1D', 'Linear2D', 'Polar'] + obj.Type = ['Linear1D', 'Linear2D', 'Polar'] - self.setEditorProperties(obj) - obj.Proxy = self + self.setEditorProperties(obj) + obj.Proxy = self - def __getstate__(self): - return None + def __getstate__(self): + return None - def __setstate__(self, state): - return None + def __setstate__(self, state): + return None - def setEditorProperties(self, obj): - if obj.Type == 'Linear2D': - obj.setEditorMode('Angle', 2) - obj.setEditorMode('Copies', 2) - obj.setEditorMode('Centre', 2) + def setEditorProperties(self, obj): + 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', 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', 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) + obj.setEditorMode('CopiesX', 2) + obj.setEditorMode('CopiesY', 2) + obj.setEditorMode('Offset', 0) - def onChanged(self, obj, prop): - if prop == "Type": - self.setEditorProperties(obj) + def onChanged(self, obj, prop): + if prop == "Type": + self.setEditorProperties(obj) - def rotatePath(self, path, angle, centre): - ''' - Rotates Path around given centre vector - Only X and Y is considered - ''' - CmdMoveRapid = ['G0', 'G00'] - CmdMoveStraight = ['G1', 'G01'] - CmdMoveCW = ['G2', 'G02'] - CmdMoveCCW = ['G3', 'G03'] - CmdDrill = ['G81', 'G82', 'G83'] - CmdMoveArc = CmdMoveCW + CmdMoveCCW - CmdMove = CmdMoveStraight + CmdMoveArc + def rotatePath(self, path, angle, centre): + ''' + Rotates Path around given centre vector + Only X and Y is considered + ''' + CmdMoveRapid = ['G0', 'G00'] + CmdMoveStraight = ['G1', 'G01'] + CmdMoveCW = ['G2', 'G02'] + CmdMoveCCW = ['G3', 'G03'] + CmdDrill = ['G81', 'G82', 'G83'] + CmdMoveArc = CmdMoveCW + CmdMoveCCW + CmdMove = CmdMoveStraight + CmdMoveArc - commands = [] - ang = angle / 180 * math.pi - currX = 0 - currY = 0 - for cmd in path.Commands: - if (cmd.Name in CmdMoveRapid) or (cmd.Name in CmdMove) or (cmd.Name in CmdDrill): - params = cmd.Parameters - x = params.get("X") - if x is None: - x = currX - currX = x - y = params.get("Y") - if y is None: - y = currY - currY = y + commands = [] + ang = angle / 180 * math.pi + currX = 0 + currY = 0 + for cmd in path.Commands: + if (cmd.Name in CmdMoveRapid) or (cmd.Name in CmdMove) or (cmd.Name in CmdDrill): + params = cmd.Parameters + x = params.get("X") + if x is None: + x = currX + currX = x + y = params.get("Y") + if y is None: + y = currY + currY = y - # "move" the centre to origin - x = x - centre.x - y = y - centre.y + # "move" the centre to origin + x = x - centre.x + y = y - centre.y - # rotation around origin: - nx = x * math.cos(ang) - y * math.sin(ang) - ny = y * math.cos(ang) + x * math.sin(ang) + # rotation around origin: + nx = x * math.cos(ang) - y * math.sin(ang) + ny = y * math.cos(ang) + x * math.sin(ang) - # "move" the centre back and update - params.update({'X': nx + centre.x, 'Y': ny + centre.y}) + # "move" the centre back and update + params.update({'X': nx + centre.x, 'Y': ny + centre.y}) - # Arcs need to have the I and J params rotated as well - if cmd.Name in CmdMoveArc: - i = params.get("I") - if i is None: - i = 0 - j = params.get("J") - if j is None: - j = 0 + # Arcs need to have the I and J params rotated as well + if cmd.Name in CmdMoveArc: + i = params.get("I") + if i is None: + i = 0 + j = params.get("J") + if j is None: + j = 0 - ni = i * math.cos(ang) - j * math.sin(ang) - nj = j * math.cos(ang) + i * math.sin(ang) - params.update({'I': ni, 'J': nj}) + ni = i * math.cos(ang) - j * math.sin(ang) + nj = j * math.cos(ang) + i * math.sin(ang) + params.update({'I': ni, 'J': nj}) - cmd.Parameters = params - commands.append(cmd) - newPath = Path.Path(commands) + cmd.Parameters = params + commands.append(cmd) + newPath = Path.Path(commands) - return newPath + return newPath - def execute(self, obj): + def execute(self, obj): - if isinstance(obj.Base, list): - base = obj.Base - else: - base = [obj.Base] + if isinstance(obj.Base, list): + base = obj.Base + else: + base = [obj.Base] - if len(base)>0: + if len(base)>0: - obj.ToolController = base[0].ToolController + obj.ToolController = base[0].ToolController - for b in base: - if not b.isDerivedFrom("Path::Feature"): - return - if not b.Path: - return - if not b.ToolController: - return - if b.ToolController != obj.ToolController: - # this may be important if Job output is split by tool controller - PathLog.warning('Arrays of paths having different tool controllers are handled according to the tool controller of the first path.') + for b in base: + if not b.isDerivedFrom("Path::Feature"): + return + if not b.Path: + return + if not b.ToolController: + return + if b.ToolController != obj.ToolController: + # this may be important if Job output is split by tool controller + PathLog.warning('Arrays of paths having different tool controllers are handled according to the tool controller of the first path.') - # build copies - output = "" - if obj.Type == 'Linear1D': - for i in range(obj.Copies): - for b in base: - pl = FreeCAD.Placement() - pos = FreeCAD.Vector(obj.Offset.x * (i + 1), obj.Offset.y * (i + 1), 0) - pl.move(pos) - np = Path.Path([cm.transform(pl) - for cm in b.Path.Commands]) - output += np.toGCode() + # build copies + output = "" + if obj.Type == 'Linear1D': + for i in range(obj.Copies): + for b in base: + pl = FreeCAD.Placement() + pos = FreeCAD.Vector(obj.Offset.x * (i + 1), obj.Offset.y * (i + 1), 0) + pl.move(pos) + np = Path.Path([cm.transform(pl) + 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): - for b in base: - pl = FreeCAD.Placement() - # do not process the index 0,0. It will be processed by the base Paths themselves - if not (i == 0 and j == 0): - if (i % 2) == 0: - pos = FreeCAD.Vector(obj.Offset.x * j, obj.Offset.y * i, 0) - else: - pos = FreeCAD.Vector(obj.Offset.x * (obj.CopiesX - j), obj.Offset.y * i, 0) - pl.move(pos) - 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 b in base: - pl = FreeCAD.Placement() - # do not process the index 0,0. It will be processed by the base Paths themselves - if not (i == 0 and j == 0): - if (i % 2) == 0: - pos = FreeCAD.Vector(obj.Offset.x * i, obj.Offset.y * j, 0) - else: - pos = FreeCAD.Vector(obj.Offset.x * i, obj.Offset.y * (obj.CopiesY - j), 0) - pl.move(pos) - np = Path.Path([cm.transform(pl) 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): + for b in base: + pl = FreeCAD.Placement() + # do not process the index 0,0. It will be processed by the base Paths themselves + if not (i == 0 and j == 0): + if (i % 2) == 0: + pos = FreeCAD.Vector(obj.Offset.x * j, obj.Offset.y * i, 0) + else: + pos = FreeCAD.Vector(obj.Offset.x * (obj.CopiesX - j), obj.Offset.y * i, 0) + pl.move(pos) + 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 b in base: + pl = FreeCAD.Placement() + # do not process the index 0,0. It will be processed by the base Paths themselves + if not (i == 0 and j == 0): + if (i % 2) == 0: + pos = FreeCAD.Vector(obj.Offset.x * i, obj.Offset.y * j, 0) + else: + pos = FreeCAD.Vector(obj.Offset.x * i, obj.Offset.y * (obj.CopiesY - j), 0) + pl.move(pos) + np = Path.Path([cm.transform(pl) for cm in b.Path.Commands]) + output += np.toGCode() - else: - for i in range(obj.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) - output += np.toGCode() + else: + for i in range(obj.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) + output += np.toGCode() - # print output - path = Path.Path(output) - obj.Path = path + # print output + path = Path.Path(output) + obj.Path = path class ViewProviderArray: - def __init__(self, vobj): - self.Object = vobj.Object - vobj.Proxy = self + def __init__(self, vobj): + self.Object = vobj.Object + vobj.Proxy = self - def attach(self, vobj): - self.Object = vobj.Object - return + def attach(self, vobj): + self.Object = vobj.Object + return - def __getstate__(self): - return None + def __getstate__(self): + return None - def __setstate__(self, state): - return None + def __setstate__(self, state): + return None - def claimChildren(self): - if hasattr(self, "Object"): - if hasattr(self.Object, "Base"): - if self.Object.Base: - return self.Object.Base - return [] + def claimChildren(self): + if hasattr(self, "Object"): + if hasattr(self.Object, "Base"): + if self.Object.Base: + return self.Object.Base + return [] class CommandPathArray: - def GetResources(self): - return {'Pixmap': 'Path_Array', - 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Array", "Array"), - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Array", "Creates an array from selected path(s)")} + def GetResources(self): + return {'Pixmap': 'Path_Array', + 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Array", "Array"), + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Array", "Creates an array from selected path(s)")} - def IsActive(self): - if bool(FreeCADGui.Selection.getSelection()) is False: - return False - try: - obj = FreeCADGui.Selection.getSelectionEx()[0].Object - return isinstance(obj.Proxy, PathScripts.PathOp.ObjectOp) - except(IndexError, AttributeError): - return False + def IsActive(self): + if bool(FreeCADGui.Selection.getSelection()) is False: + return False + try: + obj = FreeCADGui.Selection.getSelectionEx()[0].Object + return isinstance(obj.Proxy, PathScripts.PathOp.ObjectOp) + except(IndexError, AttributeError): + return False - def Activated(self): + def Activated(self): - # check that the selection contains exactly what we want - selection = FreeCADGui.Selection.getSelection() + # check that the selection contains exactly what we want + selection = FreeCADGui.Selection.getSelection() - for sel in selection: - if not(sel.isDerivedFrom("Path::Feature")): - FreeCAD.Console.PrintError( - translate("Path_Array", "Arrays can be created only from Path operations.")+"\n") - return + for sel in selection: + if not(sel.isDerivedFrom("Path::Feature")): + FreeCAD.Console.PrintError( + translate("Path_Array", "Arrays can be created only from Path operations.")+"\n") + return - # if everything is ok, execute and register the transaction in the - # undo/redo stack - FreeCAD.ActiveDocument.openTransaction("Create Array") - FreeCADGui.addModule("PathScripts.PathArray") - FreeCADGui.addModule("PathScripts.PathUtils") + # if everything is ok, execute and register the transaction in the + # undo/redo stack + FreeCAD.ActiveDocument.openTransaction("Create Array") + FreeCADGui.addModule("PathScripts.PathArray") + FreeCADGui.addModule("PathScripts.PathUtils") - FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Array")') + FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Array")') - FreeCADGui.doCommand('PathScripts.PathArray.ObjectArray(obj)') + FreeCADGui.doCommand('PathScripts.PathArray.ObjectArray(obj)') - baseString = "[%s]" % ','.join(["FreeCAD.ActiveDocument.%s" % sel.Name for sel in selection]) - FreeCADGui.doCommand('obj.Base = %s' % baseString) + baseString = "[%s]" % ','.join(["FreeCAD.ActiveDocument.%s" % sel.Name for sel in selection]) + FreeCADGui.doCommand('obj.Base = %s' % baseString) - FreeCADGui.doCommand('obj.ViewObject.Proxy = 0') - FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)') - FreeCAD.ActiveDocument.commitTransaction() - FreeCAD.ActiveDocument.recompute() + FreeCADGui.doCommand('obj.ViewObject.Proxy = 0') + FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)') + FreeCAD.ActiveDocument.commitTransaction() + FreeCAD.ActiveDocument.recompute() if FreeCAD.GuiUp: - # register the FreeCAD command - FreeCADGui.addCommand('Path_Array', CommandPathArray()) + # register the FreeCAD command + FreeCADGui.addCommand('Path_Array', CommandPathArray()) From daddf550aad86637cebf185e209ded9aad0fb5e2 Mon Sep 17 00:00:00 2001 From: jim Date: Mon, 24 May 2021 21:00:45 -0700 Subject: [PATCH 05/10] Add PathArray "Jitter" capability to offset each copy by a random distance. Helps create an imperfect "hand-crafted" appearance. Adds properties: - JitterPercent: % of copies that will be randomly offset - JitterMagnitude: Maximum distance to offset copies --- src/Mod/Path/PathScripts/PathArray.py | 44 +++++++++++++++++++-------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathArray.py b/src/Mod/Path/PathScripts/PathArray.py index 7d7cf0131e..7b4fe67218 100644 --- a/src/Mod/Path/PathScripts/PathArray.py +++ b/src/Mod/Path/PathScripts/PathArray.py @@ -27,6 +27,7 @@ import PathScripts from PathScripts import PathLog from PySide import QtCore import math +import random __doc__ = """Path Array object and FreeCAD command""" @@ -55,6 +56,10 @@ class ObjectArray: "Path", "The centre of rotation in Polar pattern") obj.addProperty("App::PropertyBool", "SwapDirection", "Path", "Make copies in X direction before Y in Linear 2D pattern") + obj.addProperty("App::PropertyInteger", "JitterPercent", + "Path", "Percent of copies to randomly offset") + obj.addProperty("App::PropertyVectorDistance", "JitterMagnitude", + "Path", "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")) @@ -70,6 +75,8 @@ class ObjectArray: return None def setEditorProperties(self, obj): + obj.setEditorMode('JitterPercent', 0) + obj.setEditorMode('JitterMagnitude', 0) if obj.Type == 'Linear2D': obj.setEditorMode('Angle', 2) obj.setEditorMode('Copies', 2) @@ -159,18 +166,22 @@ class ObjectArray: return newPath - def execute(self, obj): + 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): if isinstance(obj.Base, list): base = obj.Base else: base = [obj.Base] - if len(base)>0: - obj.ToolController = base[0].ToolController - for b in base: if not b.isDerivedFrom("Path::Feature"): return @@ -184,11 +195,14 @@ class ObjectArray: # 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) + for b in base: pl = FreeCAD.Placement() - pos = FreeCAD.Vector(obj.Offset.x * (i + 1), obj.Offset.y * (i + 1), 0) pl.move(pos) np = Path.Path([cm.transform(pl) for cm in b.Path.Commands]) @@ -198,28 +212,32 @@ class ObjectArray: if obj.SwapDirection: for i in range(obj.CopiesY + 1): for j in range(obj.CopiesX + 1): + if (i % 2) == 0: + pos = FreeCAD.Vector(obj.Offset.x * i, obj.Offset.y * j, 0) + else: + pos = FreeCAD.Vector(obj.Offset.x * i, obj.Offset.y * (obj.CopiesY - j), 0) + pos = self.calculateJitter(obj, pos) + for b in base: pl = FreeCAD.Placement() # do not process the index 0,0. It will be processed by the base Paths themselves if not (i == 0 and j == 0): - if (i % 2) == 0: - pos = FreeCAD.Vector(obj.Offset.x * j, obj.Offset.y * i, 0) - else: - pos = FreeCAD.Vector(obj.Offset.x * (obj.CopiesX - j), obj.Offset.y * i, 0) pl.move(pos) 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): + if (i % 2) == 0: + pos = FreeCAD.Vector(obj.Offset.x * i, obj.Offset.y * j, 0) + else: + pos = FreeCAD.Vector(obj.Offset.x * i, obj.Offset.y * (obj.CopiesY - j), 0) + pos = self.calculateJitter(obj, pos) + for b in base: pl = FreeCAD.Placement() # do not process the index 0,0. It will be processed by the base Paths themselves if not (i == 0 and j == 0): - if (i % 2) == 0: - pos = FreeCAD.Vector(obj.Offset.x * i, obj.Offset.y * j, 0) - else: - pos = FreeCAD.Vector(obj.Offset.x * i, obj.Offset.y * (obj.CopiesY - j), 0) pl.move(pos) np = Path.Path([cm.transform(pl) for cm in b.Path.Commands]) output += np.toGCode() From a3f37025b199e1b7b9a19a99c3e6850d92dafd1d Mon Sep 17 00:00:00 2001 From: jim Date: Mon, 24 May 2021 21:21:17 -0700 Subject: [PATCH 06/10] Fix row-ordering logic when SwapDirection==True --- src/Mod/Path/PathScripts/PathArray.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathArray.py b/src/Mod/Path/PathScripts/PathArray.py index 7b4fe67218..b06a380e1e 100644 --- a/src/Mod/Path/PathScripts/PathArray.py +++ b/src/Mod/Path/PathScripts/PathArray.py @@ -213,9 +213,9 @@ class ObjectArray: for i in range(obj.CopiesY + 1): for j in range(obj.CopiesX + 1): if (i % 2) == 0: - pos = FreeCAD.Vector(obj.Offset.x * i, obj.Offset.y * j, 0) + pos = FreeCAD.Vector(obj.Offset.x * j, obj.Offset.y * i, 0) else: - pos = FreeCAD.Vector(obj.Offset.x * i, obj.Offset.y * (obj.CopiesY - j), 0) + pos = FreeCAD.Vector(obj.Offset.x * (obj.CopiesX - j), obj.Offset.y * i, 0) pos = self.calculateJitter(obj, pos) for b in base: From 4e2137b35896556f860cbb84e55dcedb1c6819c6 Mon Sep 17 00:00:00 2001 From: jim Date: Tue, 25 May 2021 18:11:15 -0700 Subject: [PATCH 07/10] Wrap strings in QT_TRANSLATE_NOOP --- src/Mod/Path/PathScripts/PathArray.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathArray.py b/src/Mod/Path/PathScripts/PathArray.py index b06a380e1e..d70759040a 100644 --- a/src/Mod/Path/PathScripts/PathArray.py +++ b/src/Mod/Path/PathScripts/PathArray.py @@ -39,27 +39,27 @@ class ObjectArray: def __init__(self, obj): obj.addProperty("App::PropertyLinkList", "Base", - "Path", "The path(s) to array") + "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","The path(s) to array")) obj.addProperty("App::PropertyEnumeration", "Type", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Pattern method")) obj.addProperty("App::PropertyVectorDistance", "Offset", - "Path", "The spacing between the array copies in Linear pattern") + "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","The spacing between the array copies in Linear pattern")) obj.addProperty("App::PropertyInteger", "CopiesX", - "Path", "The number of copies in X direction in Linear pattern") + "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","The number of copies in X direction in Linear pattern")) obj.addProperty("App::PropertyInteger", "CopiesY", - "Path", "The number of copies in Y direction in Linear pattern") + "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","The number of copies in Y direction in Linear pattern")) obj.addProperty("App::PropertyAngle", "Angle", - "Path", "Total angle in Polar pattern") + "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","Total angle in Polar pattern")) obj.addProperty("App::PropertyInteger", "Copies", - "Path", "The number of copies in Linear 1D and Polar pattern") + "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","The number of copies in Linear 1D and Polar pattern")) obj.addProperty("App::PropertyVector", "Centre", - "Path", "The centre of rotation in Polar pattern") + "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","The centre of rotation in Polar pattern")) obj.addProperty("App::PropertyBool", "SwapDirection", - "Path", "Make copies in X direction before Y in Linear 2D pattern") + "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","Make copies in X direction before Y in Linear 2D pattern")) obj.addProperty("App::PropertyInteger", "JitterPercent", - "Path", "Percent of copies to randomly offset") + "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","Percent of copies to randomly offset")) obj.addProperty("App::PropertyVectorDistance", "JitterMagnitude", - "Path", "Maximum random offset of copies") + "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")) @@ -191,7 +191,7 @@ class ObjectArray: return if b.ToolController != obj.ToolController: # this may be important if Job output is split by tool controller - PathLog.warning('Arrays of paths having different tool controllers are handled according to the tool controller of the first path.') + 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.')) # build copies output = "" From 9234c33db1ba216b20bdecb924f6b72e4842dd6a Mon Sep 17 00:00:00 2001 From: jim Date: Tue, 25 May 2021 18:24:50 -0700 Subject: [PATCH 08/10] Hide ToolController property. TC can be chosen by the first operation in the PathArray Base property. --- src/Mod/Path/PathScripts/PathArray.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Mod/Path/PathScripts/PathArray.py b/src/Mod/Path/PathScripts/PathArray.py index d70759040a..1316331f5e 100644 --- a/src/Mod/Path/PathScripts/PathArray.py +++ b/src/Mod/Path/PathScripts/PathArray.py @@ -77,6 +77,7 @@ class ObjectArray: def setEditorProperties(self, obj): obj.setEditorMode('JitterPercent', 0) obj.setEditorMode('JitterMagnitude', 0) + obj.setEditorMode('ToolController', 2) if obj.Type == 'Linear2D': obj.setEditorMode('Angle', 2) obj.setEditorMode('Copies', 2) From b0ab46878bf18f62f6ae3c77665cc26c69f5f1ce Mon Sep 17 00:00:00 2001 From: jim Date: Tue, 25 May 2021 18:33:31 -0700 Subject: [PATCH 09/10] Clean up logic that tests whether there any base Paths to process --- src/Mod/Path/PathScripts/PathArray.py | 140 +++++++++++++------------- 1 file changed, 72 insertions(+), 68 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathArray.py b/src/Mod/Path/PathScripts/PathArray.py index 1316331f5e..b14a298cff 100644 --- a/src/Mod/Path/PathScripts/PathArray.py +++ b/src/Mod/Path/PathScripts/PathArray.py @@ -176,86 +176,90 @@ class ObjectArray: 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: - obj.ToolController = base[0].ToolController - for b in base: - if not b.isDerivedFrom("Path::Feature"): - return - if not b.Path: - return - if not b.ToolController: - return - if b.ToolController != obj.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.')) + if len(base)==0: + return - # 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) + obj.ToolController = base[0].ToolController + for b in base: + if not b.isDerivedFrom("Path::Feature"): + return + if not b.Path: + return + if not b.ToolController: + return + if b.ToolController != obj.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.')) - for b in base: - pl = FreeCAD.Placement() - pl.move(pos) - np = Path.Path([cm.transform(pl) - for cm in b.Path.Commands]) - output += np.toGCode() + # 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) - elif obj.Type == 'Linear2D': - if obj.SwapDirection: - for i in range(obj.CopiesY + 1): - for j in range(obj.CopiesX + 1): - if (i % 2) == 0: - pos = FreeCAD.Vector(obj.Offset.x * j, obj.Offset.y * i, 0) - else: - pos = FreeCAD.Vector(obj.Offset.x * (obj.CopiesX - j), obj.Offset.y * i, 0) - pos = self.calculateJitter(obj, pos) - - for b in base: - pl = FreeCAD.Placement() - # do not process the index 0,0. It will be processed by the base Paths themselves - if not (i == 0 and j == 0): - pl.move(pos) - 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): - if (i % 2) == 0: - pos = FreeCAD.Vector(obj.Offset.x * i, obj.Offset.y * j, 0) - else: - pos = FreeCAD.Vector(obj.Offset.x * i, obj.Offset.y * (obj.CopiesY - j), 0) - pos = self.calculateJitter(obj, pos) - - for b in base: - pl = FreeCAD.Placement() - # do not process the index 0,0. It will be processed by the base Paths themselves - if not (i == 0 and j == 0): - pl.move(pos) - np = Path.Path([cm.transform(pl) for cm in b.Path.Commands]) - output += np.toGCode() + for b in base: + pl = FreeCAD.Placement() + pl.move(pos) + np = Path.Path([cm.transform(pl) + 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): + if (i % 2) == 0: + pos = FreeCAD.Vector(obj.Offset.x * j, obj.Offset.y * i, 0) + else: + pos = FreeCAD.Vector(obj.Offset.x * (obj.CopiesX - j), obj.Offset.y * i, 0) + pos = self.calculateJitter(obj, pos) + for b in base: + pl = FreeCAD.Placement() + # do not process the index 0,0. It will be processed by the base Paths themselves + if not (i == 0 and j == 0): + pl.move(pos) + np = Path.Path([cm.transform(pl) for cm in b.Path.Commands]) + output += np.toGCode() else: - for i in range(obj.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) - output += np.toGCode() + for i in range(obj.CopiesX + 1): + for j in range(obj.CopiesY + 1): + if (i % 2) == 0: + pos = FreeCAD.Vector(obj.Offset.x * i, obj.Offset.y * j, 0) + else: + pos = FreeCAD.Vector(obj.Offset.x * i, obj.Offset.y * (obj.CopiesY - j), 0) + pos = self.calculateJitter(obj, pos) - # print output - path = Path.Path(output) - obj.Path = path + for b in base: + pl = FreeCAD.Placement() + # do not process the index 0,0. It will be processed by the base Paths themselves + if not (i == 0 and j == 0): + pl.move(pos) + np = Path.Path([cm.transform(pl) for cm in b.Path.Commands]) + output += np.toGCode() + + + else: + for i in range(obj.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) + output += np.toGCode() + + # print output + path = Path.Path(output) + obj.Path = path class ViewProviderArray: From 815679a487cd0e61e8e5b4f5e81aad3c1f338b1c Mon Sep 17 00:00:00 2001 From: jim Date: Tue, 25 May 2021 18:36:51 -0700 Subject: [PATCH 10/10] fix whitespace --- src/Mod/Path/PathScripts/PathArray.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathArray.py b/src/Mod/Path/PathScripts/PathArray.py index b14a298cff..ed892318cd 100644 --- a/src/Mod/Path/PathScripts/PathArray.py +++ b/src/Mod/Path/PathScripts/PathArray.py @@ -177,14 +177,14 @@ class ObjectArray: def execute(self, obj): - # backwards compatibility for PathArrays created before support for multiple bases + # 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: - return + return obj.ToolController = base[0].ToolController for b in base: