From 37d2b2fba15fb299216ebc77d604366d154ef83b Mon Sep 17 00:00:00 2001 From: jim Date: Fri, 21 May 2021 21:43:25 -0700 Subject: [PATCH 01/16] 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 c1e9f38b4e463554cd601c0216f7476fbedbbe10 Mon Sep 17 00:00:00 2001 From: jim Date: Fri, 21 May 2021 21:43:25 -0700 Subject: [PATCH 02/16] 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 fdd8126e39fb4c4440e6adecfaf35fc472a6e697 Mon Sep 17 00:00:00 2001 From: jim Date: Sun, 23 May 2021 19:25:52 -0700 Subject: [PATCH 03/16] 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 6c6d62b189ebf00aff1781f2b4107f03f7cd3727 Mon Sep 17 00:00:00 2001 From: jim Date: Sun, 23 May 2021 19:28:36 -0700 Subject: [PATCH 04/16] 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 c190fca4493c71d4e983c9b164aa9ac7b124910b Mon Sep 17 00:00:00 2001 From: jim Date: Mon, 24 May 2021 21:00:45 -0700 Subject: [PATCH 05/16] 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 276f8f5fab82c7bf34b605c00a9e7253deb46651 Mon Sep 17 00:00:00 2001 From: jim Date: Mon, 24 May 2021 21:21:17 -0700 Subject: [PATCH 06/16] 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 837f2c00f3715f0e1485cfda785466d941a9f965 Mon Sep 17 00:00:00 2001 From: jim Date: Tue, 25 May 2021 18:11:15 -0700 Subject: [PATCH 07/16] 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 592dba161030fe3c6c2ada464b3232355b794724 Mon Sep 17 00:00:00 2001 From: jim Date: Tue, 25 May 2021 18:24:50 -0700 Subject: [PATCH 08/16] 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 a62e01542796e45ee9fa291dddc91af429c42d18 Mon Sep 17 00:00:00 2001 From: jim Date: Tue, 25 May 2021 18:33:31 -0700 Subject: [PATCH 09/16] 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 631e1664720b57a546da924dd54768b2338a3424 Mon Sep 17 00:00:00 2001 From: jim Date: Tue, 25 May 2021 18:36:51 -0700 Subject: [PATCH 10/16] 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: From 3f9ca68c5ef53dae70d21a8e5468a1fc4fb34e5e Mon Sep 17 00:00:00 2001 From: sliptonic Date: Mon, 22 Mar 2021 19:06:58 -0500 Subject: [PATCH 11/16] Initial Commit --- src/Mod/Path/Gui/Resources/Path.qrc | 2 + .../Gui/Resources/icons/Path_Camotics.svg | 644 ++++++++++++++++++ .../Gui/Resources/icons/camotics-logo.png | Bin 0 -> 1568 bytes .../Resources/panels/TaskPathCamoticsSim.ui | 103 +++ src/Mod/Path/InitGui.py | 9 +- src/Mod/Path/PathScripts/PathCamoticsGui.py | 400 +++++++++++ src/Mod/Path/PathScripts/PathGuiInit.py | 1 + src/Mod/Path/PathScripts/PathPost.py | 394 ++++++----- 8 files changed, 1373 insertions(+), 180 deletions(-) create mode 100644 src/Mod/Path/Gui/Resources/icons/Path_Camotics.svg create mode 100644 src/Mod/Path/Gui/Resources/icons/camotics-logo.png create mode 100644 src/Mod/Path/Gui/Resources/panels/TaskPathCamoticsSim.ui create mode 100644 src/Mod/Path/PathScripts/PathCamoticsGui.py diff --git a/src/Mod/Path/Gui/Resources/Path.qrc b/src/Mod/Path/Gui/Resources/Path.qrc index 309906a1b1..cc3d6340fb 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -14,6 +14,7 @@ icons/Path_BStep.svg icons/Path_BStop.svg icons/Path_BaseGeometry.svg + icons/Path_Camotics.svg icons/Path_Comment.svg icons/Path_Compound.svg icons/Path_Contour.svg @@ -130,6 +131,7 @@ panels/ToolBitSelector.ui panels/ToolEditor.ui panels/ToolLibraryEditor.ui + panels/TaskPathCamoticsSim.ui panels/TaskPathSimulator.ui panels/ZCorrectEdit.ui preferences/Advanced.ui diff --git a/src/Mod/Path/Gui/Resources/icons/Path_Camotics.svg b/src/Mod/Path/Gui/Resources/icons/Path_Camotics.svg new file mode 100644 index 0000000000..e9d4b73037 --- /dev/null +++ b/src/Mod/Path/Gui/Resources/icons/Path_Camotics.svg @@ -0,0 +1,644 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + Path_Drilling + 2015-07-04 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Path/Gui/Resources/icons/Path_Drilling.svg + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + + + + + + + + C + + + + diff --git a/src/Mod/Path/Gui/Resources/icons/camotics-logo.png b/src/Mod/Path/Gui/Resources/icons/camotics-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..652cd11b1424de5884c1c65f9be30c4c755a165f GIT binary patch literal 1568 zcmV+*2H*LKP)z2op03OPi0Ofo@`v%2@FRNtwp@2e_&^!^9%Z61Pe`RH$Ih=_my00(qHerua@od*mo5m0U#m)5+DNN)i04YQ8e1o zQTE>ehz#wjnm_kljY=aDA|Vke6^0&_7Vi;JfJmF6tA@TcQJRkLq0(;#5DxX?pt*Pf z0Hhsit6iq7Q`(_6M0#t(p$}cdoqwT=`!(sbFr@7b6ZC6IZ_Z!Tivy(%x-8p$%cc{+D@Oo; zNPvKd!g50)9p(AMcT4qYbM`qJ*VQ%}O%UlW0J85kNB=-XFiE=i*rt=)=%H_#^B2Ca z7*N?X8tvGmKvLVs<`3TS)%mbI1Ts;YgQC}khNPZCe( zhD4~HO~#1Ot>-)}29(VuC}iKa>^mNMGrEVuUA;<*834WlK*YYhfZzqtq*G<=jc_>l z?(&$2+Klc;I}f6KhDfg1zxeczUIa+@U5S9g0VIGh1n+DtuSbA5)JqWt)MOK-%#8*6 z&bK83VO%7|Oi%%UNeZ{~oo;z7^#ueETAR2q$=JpP8M~F?ofP#Ah`3RKq){me;aV1+ zeT$?3h_nqMY%hyP@73uSXk(jAm2q2nipXs$-;MwzcmUzSBjIfuVFnIDYTK6p!Zu=G zmlJ*Vq;ami}QTR004C63N{*3JAP?cz8v*`7?`$XmY!Xo|I=! z*lW|6=xMscai+9W0K~&JKtOro;bZW=vn+61u|%j81O`Cn0DyqVLpN*|bI2Bxeb4i_ zt*mxvT*2`6<%wNUG~GYY?r%eX4bY+d>X3ik(Q^kN9Ku$>0D#&mn{>+;2DsTQ16+heYNb@^S)fHhVIa1Dx2lW&O-@5y?l8Mz+jy1D`p>@Z)O$CoBQQ z0CT^dn`Gj&z3$u+&mM)LY0jRDA1KO*6d>qy>P9miXCeXs0^BZ-LSNgR??$5;01yaq z&{w0+u&_@j?`#hL9_q`sTxesJjkXY|O?LlnH{SR4D)^q1jiSuNxw7$%+I?GY=AV(x zT)t=H(H)v9;oy_;_xj?jU!R6@UXjkH`=Bg0YSi<3T?d)ZMi=91c6jC zCS?HWh9=j|PO}6Ck^J#{5VGd@rYtu=T z7D`xiFdQrgEhvO6hLK$WLSskY|HGBzE@q0ib1)^np>0!nkWyEu3lJ^rUVN`JIj#5G!B%65EtJUaR~HgW8- zJow{y_GOe03WpFBsd8u#m5~q!ZvfE5l+W^f&x14}ytHvWd$iFG0QB!Z{QYmLU)D?C z!n?ot@Gm&_J*g64olg<=+AyrvXIo&B9Y{Kj1q@K!e!%^1}GYvC0Rz#fKLhWg)-s3Ti} SMQxe@0000 + + TaskPathSimulator + + + + 0 + 0 + 404 + 364 + + + + Path Simulator + + + + + + QAbstractItemView::ContiguousSelection + + + QAbstractItemView::SelectRows + + + + + + + + + Estimated time to run to the selected point in the job + + + Estimated run time: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + true + + + + + + + + + + + 23 + + + + + + + Stop + + + + + + + + + Launch Camotics + + + + :/icons/Path_Camotics.svg:/icons/Path_Camotics.svg + + + + + + + + + + + SimStop() + SimPlay() + SimPause() + SimStep() + SimFF() + + diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index d4011741ab..d02be4923e 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -78,6 +78,7 @@ class PathWorkbench (Workbench): from PathScripts import PathToolBitLibraryCmd import PathCommands + PathGuiInit.Startup() # build commands list @@ -96,8 +97,6 @@ class PathWorkbench (Workbench): "Path_DressupLeadInOut", "Path_DressupRampEntry", "Path_DressupTag", "Path_DressupZCorrect"] extracmdlist = [] - # modcmdmore = ["Path_Hop",] - # remotecmdlist = ["Path_Remote"] specialcmdlist = [] @@ -122,6 +121,12 @@ class PathWorkbench (Workbench): twodopcmdlist.append("Path_Slot") if PathPreferences.advancedOCLFeaturesEnabled(): + try: + import camotics + toolcmdlist.append("Path_Camotics") + except ImportError: + pass + try: import ocl # pylint: disable=unused-variable from PathScripts import PathSurfaceGui diff --git a/src/Mod/Path/PathScripts/PathCamoticsGui.py b/src/Mod/Path/PathScripts/PathCamoticsGui.py new file mode 100644 index 0000000000..125ba47bfb --- /dev/null +++ b/src/Mod/Path/PathScripts/PathCamoticsGui.py @@ -0,0 +1,400 @@ +# -*- coding: utf-8 -*- +# *************************************************************************** +# * Copyright (c) 2020 sliptonic * +# * * +# * 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 * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +import FreeCAD +import FreeCADGui +import PathScripts.PathLog as PathLog +# from pivy import coin +# from itertools import cycle +import json +import Mesh +import time +import camotics +import PathScripts.PathPost as PathPost +import io +import PathScripts +import queue +from threading import Thread, Lock +import subprocess +import PySide + +from PySide import QtCore, QtGui + +__title__ = "Camotics Simulator" +__author__ = "sliptonic (Brad Collette)" +__url__ = "https://www.freecadweb.org" +__doc__ = "Task panel for Camotics Simulation" + +PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +PathLog.trackModule(PathLog.thisModule()) + +# Qt translation handling +def translate(context, text, disambig=None): + return QtCore.QCoreApplication.translate(context, text, disambig) + + +class CAMoticsUI: + def __init__(self, simulation): + # this will create a Qt widget from our ui file + self.form = FreeCADGui.PySideUic.loadUi(":/panels/TaskPathCamoticsSim.ui") + self.simulation = simulation + self.opModel = PySide.QtGui.QStandardItemModel(0, 0)#len(self.columnNames())) + self.initializeUI() + self.lock = False + + def columnNames(self): + return ['Operation'] + + def loadData(self): + self.opModel.clear() + self.opModel.setHorizontalHeaderLabels(self.columnNames()) + ops = self.simulation.finalpostlist + for i, op in enumerate(ops): + libItem = PySide.QtGui.QStandardItem(op.Label) + libItem.setToolTip('op') + libItem.setData(op) + #libItem.setIcon(PySide.QtGui.QPixmap(':/icons/Path_ToolTable.svg')) + self.opModel.appendRow(libItem) + + + def initializeUI(self): + self.form.progressBar.reset() + self.updateEstimate("00:00:00") + self.form.btnRun.setText("Run") + self.form.btnLaunchCamotics.clicked.connect(self.launchCamotics) + self.simulation.progressUpdate.connect(self.calculating) + self.simulation.estimateChanged.connect(self.updateEstimate) + self.form.btnRun.clicked.connect(self.runButton) + self.form.lstPathObjects.setModel(self.opModel) + self.form.lstPathObjects.selectionModel().selectionChanged.connect(self.reSelect) + self.loadData() + self.selectAll() + + def selectAll(self): + selmodel = self.form.lstPathObjects.selectionModel() + index0 = self.opModel.index(0, 0) + index1 = self.opModel.index(self.opModel.rowCount()-1,0) + itemSelection = QtCore.QItemSelection(index0, index1) + selmodel.blockSignals(True) + selmodel.select(itemSelection, QtCore.QItemSelectionModel.Rows | QtCore.QItemSelectionModel.Select) + selmodel.blockSignals(False) + + + def reSelect(self): + selmodel = self.form.lstPathObjects.selectionModel() + item = selmodel.selection().indexes()[0] + + index0 = self.opModel.index(0, 0) + itemSelection = QtCore.QItemSelection(index0, item) + + selmodel.blockSignals(True) + selmodel.select(itemSelection, + QtCore.QItemSelectionModel.Rows | QtCore.QItemSelectionModel.Select) + selmodel.blockSignals(False) + + selectedObjs = [self.opModel.itemFromIndex(i).data() for i in selmodel.selection().indexes()] + + self.simulation.setSimulationPath(selectedObjs) + + def runButton(self): + if self.form.btnRun.text() == 'Run': + self.simulation.run() + else: + self.simulation.stopSim() + + + def updateEstimate(self, timestring): + self.form.txtRunEstimate.setText(timestring) + + def launchCamotics(self): + projfile = self.simulation.buildproject() + subprocess.Popen(["camotics", projfile]) + + def accept(self): + self.simulation.accept() + FreeCADGui.Control.closeDialog() + + def reject(self): + self.simulation.cancel() + FreeCADGui.Control.closeDialog() + + def calculating(self, progress=0.0): + if progress < 1.0: + self.form.btnRun.setText('Stop') + else: + self.form.btnRun.setText('Run') + self.form.progressBar.setValue(int(progress*100)) + + +class CamoticsSimulation(QtCore.QObject): + + SIM = camotics.Simulation() + q = queue.Queue() + progressUpdate = QtCore.Signal(object) + estimateChanged = QtCore.Signal(str) + + SHAPEMAP = {'ballend': 'Ballnose', + 'endmill': 'Cylindrical', + 'v-bit' : 'Conical', + 'chamfer': 'Snubnose'} + + cutMaterial = None + + def worker(self, lock): + while True: + item = self.q.get() + with lock: + if item['TYPE'] == 'STATUS': + if item['VALUE'] == 'DONE': + self.SIM.wait() + surface = self.SIM.get_surface('binary') + #surface = self.SIM.get_surface('python') + self.SIM.wait() + self.addMesh(surface) + #self.makeCoinMesh(surface) + elif item['TYPE'] == 'PROGRESS': + #self.taskForm.calculating(item['VALUE']) + msg = item['VALUE'] + self.progressUpdate.emit(msg) + self.q.task_done() + + + + def __init__(self): + super().__init__() # needed for QT signals + lock = Lock() + Thread(target=self.worker, daemon=True, args=(lock,)).start() + + def callback(self, status, progress): + self.q.put({'TYPE': 'PROGRESS', 'VALUE': progress}) + self.q.put({'TYPE': 'STATUS' , 'VALUE': status }) + + def isDone(self, success): + self.q.put({'TYPE': 'STATUS' , 'VALUE': 'DONE'}) + + def stopSim(self): + if self.SIM.is_running(): + self.SIM.interrupt() + self.SIM.wait() + return True + + + def addMesh(self, surface): + '''takes a binary stl and adds a Mesh to the current docuemnt''' + + if self.cutMaterial is None: + self.cutMaterial = FreeCAD.ActiveDocument.addObject("Mesh::Feature", "SimulationOutput") + buffer=io.BytesIO() + buffer.write(surface) + buffer.seek(0) + mesh=Mesh.Mesh() + mesh.read(buffer, "STL") + self.cutMaterial.Mesh = mesh + # Mesh.show(mesh) + + def setSimulationPath(self, postlist): + gcode, fname = PathPost.CommandPathPost().getGcodeSilently(postlist, self.job) + self.SIM.compute_path(gcode) + self.SIM.wait() + + tot = sum([step['time'] for step in self.SIM.get_path()]) + self.estimateChanged.emit(time.strftime("%H:%M:%S", time.gmtime(tot))) + + def Activate(self): + self.job = FreeCADGui.Selection.getSelectionEx()[0].Object + postlist = PathPost.buildPostList(self.job) + self.finalpostlist = [item for slist in postlist for item in slist] + + self.taskForm = CAMoticsUI(self) + FreeCADGui.Control.showDialog(self.taskForm) + + self.SIM.set_metric() + self.SIM.set_resolution('high') + + bb = self.job.Stock.Shape.BoundBox + self.SIM.set_workpiece(min = (bb.XMin, bb.YMin, bb.ZMin), max = (bb.XMax, bb.YMax, bb.ZMax)) + + for t in self.job.Tools.Group: + self.SIM.set_tool(t.ToolNumber, + metric = True, + shape = self.SHAPEMAP.get(t.Tool.ShapeName, 'Cylindrical'), + length = t.Tool.Length.Value, + diameter = t.Tool.Diameter.Value) + + + self.setSimulationPath(self.finalpostlist) + + def run(self): + self.SIM.start(self.callback, done=self.isDone) + + #def makeCoinMesh(self, surface): + # # this doesn't work yet + # #sg = FreeCADGui.ActiveDocument.ActiveView.getSceneGraph(); + # color = coin.SoBaseColor() + # color.rgb = (1, 0, 1) + # coords = coin.SoTransform() + # node = coin.SoSeparator() + # node.addChild(color) + # node.addChild(coords) + + # end = [-1] + # vertices = list(zip(*[iter(surface['vertices'])] * 3)) + # #polygons = list(zip(*[iter(vertices)] * 3, cycle(end))) + # polygons = list(zip(*[iter(range(len(vertices)))] * 3, cycle(end))) + + # print('verts {}'.format(len(vertices))) + # print('polygons {}'.format(len(polygons))) + + # data=coin.SoCoordinate3() + # face=coin.SoIndexedFaceSet() + # node.addChild(data) + # node.addChild(face) + + # # i = 0 + # # for i, v in enumerate(vertices): + # # print('i: {} v: {}'.format(i, v)) + # #data.point.set1Value(i, v[0], v[1], v[2]) + # # i += 1 + # # i = 0 + # # for p in polygons: + # # try: + # # face.coordIndex.set1Value(i, p) + # # i += 1 + # # except Exception as e: + # # print(e) + # # print(i) + # # print(p) + + # # sg.addChild(node) + + + def RemoveMaterial(self): + if self.cutMaterial is not None: + FreeCAD.ActiveDocument.removeObject(self.cutMaterial.Name) + self.cutMaterial = None + + def accept(self): + pass + + def cancel(self): + self.RemoveMaterial() + + def buildproject(self): + + job = self.job + gcode, fname = PathPost.CommandPathPost().getGcodeSilently(self.finalpostlist, self.job, temp=False) + + tooltemplate = { + "units": "metric", + "shape": "cylindrical", + "length": 10, + "diameter": 3.125, + "description": "" + } + + workpiecetemplate = { + "automatic": False, + "margin": 0, + "bounds": { + "min": [0, 0, 0], + "max": [0, 0, 0]} + } + + camoticstemplate = { + "units": "metric", + "resolution-mode": "high", + "resolution": 1, + "tools": {}, + "workpiece": {}, + "files": [] + } + + unitstring = "imperial" if FreeCAD.Units.getSchema() in [2,3,5,7] else "metric" + + camoticstemplate["units"] = unitstring + camoticstemplate["resolution-mode"] = "medium" + camoticstemplate["resolution"] = 1 + + toollist = {} + for t in job.Tools.Group: + tooltemplate["units"] = unitstring + if hasattr(t.Tool, 'Camotics'): + tooltemplate["shape"] = t.Tool.Camotics + else: + tooltemplate["shape"] = self.SHAPEMAP.get(t.Tool.ShapeName, 'Cylindrical') + + tooltemplate["length"] = t.Tool.Length.Value + tooltemplate["diameter"] = t.Tool.Diameter.Value + tooltemplate["description"] = t.Label + toollist[t.ToolNumber] = tooltemplate + + camoticstemplate['tools'] = toollist + + bb = job.Stock.Shape.BoundBox + + workpiecetemplate['bounds']['min'] = [bb.XMin, bb.YMin, bb.ZMin] + workpiecetemplate['bounds']['max'] = [bb.XMax, bb.YMax, bb.ZMax] + camoticstemplate['workpiece'] = workpiecetemplate + + camoticstemplate['files'] = [fname] + + foo = QtGui.QFileDialog.getSaveFileName(QtGui.QApplication.activeWindow(), "Camotics Project File") + if foo: + tfile = foo[0] + else: + return None + + with open(tfile, 'w') as t: + proj=json.dumps(camoticstemplate, indent=2) + t.write(proj) + + return tfile #json.dumps(camoticstemplate, indent=2) + + +class CommandCamoticsSimulate: + def GetResources(self): + return {'Pixmap': 'Path_Camotics', + 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Camotics", "Camotics"), + 'Accel': "P, C", + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Camotics", "Simulate using Camotics"), + 'CmdType': "ForEdit"} + + def IsActive(self): + if bool(FreeCADGui.Selection.getSelection()) is False: + return False + try: + job = FreeCADGui.Selection.getSelectionEx()[0].Object + return isinstance(job.Proxy, PathScripts.PathJob.ObjectJob) + except: + return False + + def Activated(self): + pathSimulation.Activate() + + +pathSimulation = CamoticsSimulation() + +if FreeCAD.GuiUp: + FreeCADGui.addCommand('Path_Camotics', CommandCamoticsSimulate()) + + +FreeCAD.Console.PrintLog("Loading PathCamoticsSimulateGui ... done\n") diff --git a/src/Mod/Path/PathScripts/PathGuiInit.py b/src/Mod/Path/PathScripts/PathGuiInit.py index 405dcb4fcc..bf13af4b9a 100644 --- a/src/Mod/Path/PathScripts/PathGuiInit.py +++ b/src/Mod/Path/PathScripts/PathGuiInit.py @@ -71,6 +71,7 @@ def Startup(): from PathScripts import PathSetupSheetGui from PathScripts import PathSimpleCopy from PathScripts import PathSimulatorGui + from PathScripts import PathCamoticsGui from PathScripts import PathSlotGui from PathScripts import PathStop # from PathScripts import PathSurfaceGui # Added in initGui.py due to OCL dependency diff --git a/src/Mod/Path/PathScripts/PathPost.py b/src/Mod/Path/PathScripts/PathPost.py index fec6ddd3a5..530151b8d7 100644 --- a/src/Mod/Path/PathScripts/PathPost.py +++ b/src/Mod/Path/PathScripts/PathPost.py @@ -33,6 +33,7 @@ import PathScripts.PathPreferences as PathPreferences import PathScripts.PathUtil as PathUtil import PathScripts.PathUtils as PathUtils import os +import tempfile from PathScripts.PathPostProcessor import PostProcessor from PySide import QtCore, QtGui @@ -56,6 +57,187 @@ class _TempObject: Label = "Fixture" +def resolveFileName(job): + + path = PathPreferences.defaultOutputFile() + if job.PostProcessorOutputFile: + path = job.PostProcessorOutputFile + filename = path + + if '%D' in filename: + D = FreeCAD.ActiveDocument.FileName + if D: + D = os.path.dirname(D) + # in case the document is in the current working directory + if not D: + D = '.' + else: + FreeCAD.Console.PrintError("Please save document in order to resolve output path!\n") + return None + filename = filename.replace('%D', D) + + if '%d' in filename: + d = FreeCAD.ActiveDocument.Label + filename = filename.replace('%d', d) + + if '%j' in filename: + j = job.Label + filename = filename.replace('%j', j) + + if '%M' in filename: + pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Macro") + M = pref.GetString("MacroPath", FreeCAD.getUserAppDataDir()) + filename = filename.replace('%M', M) + + + policy = PathPreferences.defaultOutputPolicy() + + openDialog = policy == 'Open File Dialog' + if os.path.isdir(filename) or not os.path.isdir(os.path.dirname(filename)): + # Either the entire filename resolves into a directory or the parent directory doesn't exist. + # Either way I don't know what to do - ask for help + openDialog = True + + if os.path.isfile(filename) and not openDialog: + if policy == 'Open File Dialog on conflict': + openDialog = True + elif policy == 'Append Unique ID on conflict': + fn, ext = os.path.splitext(filename) + nr = fn[-3:] + n = 1 + if nr.isdigit(): + n = int(nr) + while os.path.isfile("%s%03d%s" % (fn, n, ext)): + n = n + 1 + filename = "%s%03d%s" % (fn, n, ext) + + if openDialog: + foo = QtGui.QFileDialog.getSaveFileName(QtGui.QApplication.activeWindow(), "Output File", filename) + if foo: + filename = foo[0] + else: + filename = None + + return filename + +def buildPostList(job): + ''' Takes the job and determines the specific objects and order to + postprocess Returns a list of objects which can be passed to + exportObjectsWith() for final posting''' + wcslist = job.Fixtures + orderby = job.OrderOutputBy + + postlist = [] + + if orderby == 'Fixture': + PathLog.debug("Ordering by Fixture") + # Order by fixture means all operations and tool changes will be completed in one + # fixture before moving to the next. + + currTool = None + for index, f in enumerate(wcslist): + # create an object to serve as the fixture path + fobj = _TempObject() + c1 = Path.Command(f) + fobj.Path = Path.Path([c1]) + if index != 0: + c2 = Path.Command("G0 Z" + str(job.Stock.Shape.BoundBox.ZMax + job.SetupSheet.ClearanceHeightOffset.Value)) + fobj.Path.addCommands(c2) + fobj.InList.append(job) + sublist = [fobj] + + # Now generate the gcode + for obj in job.Operations.Group: + tc = PathUtil.toolControllerForOp(obj) + if tc is not None and PathUtil.opProperty(obj, 'Active'): + if tc.ToolNumber != currTool: + sublist.append(tc) + PathLog.debug("Appending TC: {}".format(tc.Name)) + currTool = tc.ToolNumber + sublist.append(obj) + postlist.append(sublist) + + elif orderby == 'Tool': + PathLog.debug("Ordering by Tool") + # Order by tool means tool changes are minimized. + # all operations with the current tool are processed in the current + # fixture before moving to the next fixture. + + currTool = None + fixturelist = [] + for f in wcslist: + # create an object to serve as the fixture path + fobj = _TempObject() + c1 = Path.Command(f) + c2 = Path.Command("G0 Z" + str(job.Stock.Shape.BoundBox.ZMax + job.SetupSheet.ClearanceHeightOffset.Value)) + fobj.Path = Path.Path([c1, c2]) + fobj.InList.append(job) + fixturelist.append(fobj) + + # Now generate the gcode + curlist = [] # list of ops for tool, will repeat for each fixture + sublist = [] # list of ops for output splitting + + for idx, obj in enumerate(job.Operations.Group): + + # check if the operation is active + active = PathUtil.opProperty(obj, 'Active') + + tc = PathUtil.toolControllerForOp(obj) + if tc is None or tc.ToolNumber == currTool and active: + curlist.append(obj) + elif tc.ToolNumber != currTool and currTool is None and active: # first TC + sublist.append(tc) + curlist.append(obj) + currTool = tc.ToolNumber + elif tc.ToolNumber != currTool and currTool is not None and active: # TC + for fixture in fixturelist: + sublist.append(fixture) + sublist.extend(curlist) + postlist.append(sublist) + sublist = [tc] + curlist = [obj] + currTool = tc.ToolNumber + + if idx == len(job.Operations.Group) - 1: # Last operation. + for fixture in fixturelist: + sublist.append(fixture) + sublist.extend(curlist) + postlist.append(sublist) + + elif orderby == 'Operation': + PathLog.debug("Ordering by Operation") + # Order by operation means ops are done in each fixture in + # sequence. + currTool = None + firstFixture = True + + # Now generate the gcode + for obj in job.Operations.Group: + if PathUtil.opProperty(obj, 'Active'): + sublist = [] + PathLog.debug("obj: {}".format(obj.Name)) + for f in wcslist: + fobj = _TempObject() + c1 = Path.Command(f) + fobj.Path = Path.Path([c1]) + if not firstFixture: + c2 = Path.Command("G0 Z" + str(job.Stock.Shape.BoundBox.ZMax + job.SetupSheet.ClearanceHeightOffset.Value)) + fobj.Path.addCommands(c2) + fobj.InList.append(job) + sublist.append(fobj) + firstFixture = False + tc = PathUtil.toolControllerForOp(obj) + if tc is not None: + if job.SplitOutput or (tc.ToolNumber != currTool): + sublist.append(tc) + currTool = tc.ToolNumber + sublist.append(obj) + postlist.append(sublist) + + return postlist + + class DlgSelectPostProcessor: def __init__(self, parent=None): @@ -97,73 +279,6 @@ class CommandPathPost: # pylint: disable=no-init subpart = 1 - def resolveFileName(self, job): - path = PathPreferences.defaultOutputFile() - if job.PostProcessorOutputFile: - path = job.PostProcessorOutputFile - filename = path - - if '%D' in filename: - D = FreeCAD.ActiveDocument.FileName - if D: - D = os.path.dirname(D) - # in case the document is in the current working directory - if not D: - D = '.' - else: - FreeCAD.Console.PrintError("Please save document in order to resolve output path!\n") - return None - filename = filename.replace('%D', D) - - if '%d' in filename: - d = FreeCAD.ActiveDocument.Label - filename = filename.replace('%d', d) - - if '%j' in filename: - j = job.Label - filename = filename.replace('%j', j) - - if '%M' in filename: - pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Macro") - M = pref.GetString("MacroPath", FreeCAD.getUserAppDataDir()) - filename = filename.replace('%M', M) - - if '%s' in filename: - if job.SplitOutput: - filename = filename.replace('%s', '_'+str(self.subpart)) - self.subpart += 1 - else: - filename = filename.replace('%s', '') - - policy = PathPreferences.defaultOutputPolicy() - - openDialog = policy == 'Open File Dialog' - if os.path.isdir(filename) or not os.path.isdir(os.path.dirname(filename)): - # Either the entire filename resolves into a directory or the parent directory doesn't exist. - # Either way I don't know what to do - ask for help - openDialog = True - - if os.path.isfile(filename) and not openDialog: - if policy == 'Open File Dialog on conflict': - openDialog = True - elif policy == 'Append Unique ID on conflict': - fn, ext = os.path.splitext(filename) - nr = fn[-3:] - n = 1 - if nr.isdigit(): - n = int(nr) - while os.path.isfile("%s%03d%s" % (fn, n, ext)): - n = n + 1 - filename = "%s%03d%s" % (fn, n, ext) - - if openDialog: - foo = QtGui.QFileDialog.getSaveFileName(QtGui.QApplication.activeWindow(), "Output File", filename) - if foo: - filename = foo[0] - else: - filename = None - - return filename def resolvePostProcessor(self, job): if hasattr(job, "PostProcessor"): @@ -190,6 +305,37 @@ class CommandPathPost: return False + def getGcodeSilently(self, objs, job, temp=True): + '''This method will postprocess the given objects without opening dialogs + or prompting the user for any input. gcode is returned as a list of lists + suitable for splitting''' + PathLog.track('objs: {}'.format(objs)) + + postArgs = PathPreferences.defaultPostProcessorArgs() + if hasattr(job, "PostProcessorArgs") and job.PostProcessorArgs: + postArgs = job.PostProcessorArgs + elif hasattr(job, "PostProcessor") and job.PostProcessor: + postArgs = '' + + if not '--no-show-editor' in postArgs: + postArgs += ' --no-show-editor' + + PathLog.track('postArgs: {}'.format(postArgs)) + + if temp: + tfile = tempfile.NamedTemporaryFile() + filename = tfile.name + else: + filename = resolveFileName(job) + tfile = open(filename, "w") + + with tfile: + postname = self.resolvePostProcessor(job) + processor = PostProcessor.load(postname) + gcode = processor.export(objs, filename, postArgs) + return gcode, filename + + def exportObjectsWith(self, objs, job, needFilename=True): PathLog.track() # check if the user has a project and has set the default post and @@ -203,7 +349,7 @@ class CommandPathPost: postname = self.resolvePostProcessor(job) filename = '-' if postname and needFilename: - filename = self.resolveFileName(job) + filename = resolveFileName(job) if postname and filename: print("post: %s(%s, %s)" % (postname, filename, postArgs)) @@ -261,117 +407,9 @@ class CommandPathPost: PathLog.debug("about to postprocess job: {}".format(job.Name)) - wcslist = job.Fixtures - orderby = job.OrderOutputBy - split = job.SplitOutput + postlist = buildPostList(job) + filename = resolveFileName(job) - postlist = [] - - if orderby == 'Fixture': - PathLog.debug("Ordering by Fixture") - # Order by fixture means all operations and tool changes will be completed in one - # fixture before moving to the next. - - currTool = None - for index, f in enumerate(wcslist): - # create an object to serve as the fixture path - fobj = _TempObject() - c1 = Path.Command(f) - fobj.Path = Path.Path([c1]) - if index != 0: - c2 = Path.Command("G0 Z" + str(job.Stock.Shape.BoundBox.ZMax + job.SetupSheet.ClearanceHeightOffset.Value)) - fobj.Path.addCommands(c2) - fobj.InList.append(job) - sublist = [fobj] - - # Now generate the gcode - for obj in job.Operations.Group: - tc = PathUtil.toolControllerForOp(obj) - if tc is not None and PathUtil.opProperty(obj, 'Active'): - if tc.ToolNumber != currTool: - sublist.append(tc) - PathLog.debug("Appending TC: {}".format(tc.Name)) - currTool = tc.ToolNumber - sublist.append(obj) - postlist.append(sublist) - - elif orderby == 'Tool': - PathLog.debug("Ordering by Tool") - # Order by tool means tool changes are minimized. - # all operations with the current tool are processed in the current - # fixture before moving to the next fixture. - - currTool = None - fixturelist = [] - for f in wcslist: - # create an object to serve as the fixture path - fobj = _TempObject() - c1 = Path.Command(f) - c2 = Path.Command("G0 Z" + str(job.Stock.Shape.BoundBox.ZMax + job.SetupSheet.ClearanceHeightOffset.Value)) - fobj.Path = Path.Path([c1, c2]) - fobj.InList.append(job) - fixturelist.append(fobj) - - # Now generate the gcode - curlist = [] # list of ops for tool, will repeat for each fixture - sublist = [] # list of ops for output splitting - - for idx, obj in enumerate(job.Operations.Group): - - # check if the operation is active - active = PathUtil.opProperty(obj, 'Active') - - tc = PathUtil.toolControllerForOp(obj) - if tc is None or tc.ToolNumber == currTool and active: - curlist.append(obj) - elif tc.ToolNumber != currTool and currTool is None and active: # first TC - sublist.append(tc) - curlist.append(obj) - currTool = tc.ToolNumber - elif tc.ToolNumber != currTool and currTool is not None and active: # TC - for fixture in fixturelist: - sublist.append(fixture) - sublist.extend(curlist) - postlist.append(sublist) - sublist = [tc] - curlist = [obj] - currTool = tc.ToolNumber - - if idx == len(job.Operations.Group) - 1: # Last operation. - for fixture in fixturelist: - sublist.append(fixture) - sublist.extend(curlist) - postlist.append(sublist) - - elif orderby == 'Operation': - PathLog.debug("Ordering by Operation") - # Order by operation means ops are done in each fixture in - # sequence. - currTool = None - firstFixture = True - - # Now generate the gcode - for obj in job.Operations.Group: - if PathUtil.opProperty(obj, 'Active'): - sublist = [] - PathLog.debug("obj: {}".format(obj.Name)) - for f in wcslist: - fobj = _TempObject() - c1 = Path.Command(f) - fobj.Path = Path.Path([c1]) - if not firstFixture: - c2 = Path.Command("G0 Z" + str(job.Stock.Shape.BoundBox.ZMax + job.SetupSheet.ClearanceHeightOffset.Value)) - fobj.Path.addCommands(c2) - fobj.InList.append(job) - sublist.append(fobj) - firstFixture = False - tc = PathUtil.toolControllerForOp(obj) - if tc is not None: - if job.SplitOutput or (tc.ToolNumber != currTool): - sublist.append(tc) - currTool = tc.ToolNumber - sublist.append(obj) - postlist.append(sublist) fail = True rc = '' From aae0392d2e46c010683370e31cd5b94166f3fee0 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Fri, 28 May 2021 10:29:06 -0500 Subject: [PATCH 12/16] add missing reference in cmakelists --- src/Mod/Path/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index ddfcaa519b..3735fc0419 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -33,6 +33,7 @@ SET(PathScripts_SRCS PathScripts/PathCircularHoleBaseGui.py PathScripts/PathComment.py PathScripts/PathCopy.py + PathScripts/PathCamoticsGui.py PathScripts/PathCustom.py PathScripts/PathCustomGui.py PathScripts/PathDeburr.py From 21a830a816bd345cdf86889af95ec54c1b2607a8 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Wed, 24 Mar 2021 08:54:05 -0500 Subject: [PATCH 13/16] removed PathUtils import from all post-processors to avoid the deleted proxy error I removed or commented out code using this import. In all cases, the import was used to get descriptive information from the Job --- src/Mod/Path/PathScripts/post/fanuc_post.py | 33 ++-------------- .../Path/PathScripts/post/heidenhain_post.py | 39 ++++++------------- .../Path/PathScripts/post/linuxcnc_post.py | 21 +--------- .../Path/PathScripts/post/mach3_mach4_post.py | 29 +------------- src/Mod/Path/PathScripts/post/philips_post.py | 18 ++++----- 5 files changed, 26 insertions(+), 114 deletions(-) diff --git a/src/Mod/Path/PathScripts/post/fanuc_post.py b/src/Mod/Path/PathScripts/post/fanuc_post.py index d2bb6acb0e..3eee41519d 100644 --- a/src/Mod/Path/PathScripts/post/fanuc_post.py +++ b/src/Mod/Path/PathScripts/post/fanuc_post.py @@ -30,7 +30,6 @@ import datetime import shlex import os.path from PathScripts import PostUtils -from PathScripts import PathUtils TOOLTIP = ''' This is a postprocessor file for the Path workbench. It is used to @@ -154,7 +153,7 @@ def processArguments(argstring): if args.no_tlo: USE_TLO = False if args.no_axis_modal: - OUTPUT_DOUBLES = true + OUTPUT_DOUBLES = True except Exception: # pylint: disable=broad-except return False @@ -171,7 +170,7 @@ def export(objectslist, filename, argstring): global UNIT_SPEED_FORMAT global HORIZRAPID global VERTRAPID - + for obj in objectslist: if not hasattr(obj, "Path"): print("the object " + obj.Name + " is not a path. Please select only path and Compounds.") @@ -206,34 +205,10 @@ def export(objectslist, filename, argstring): if not obj.Base.Active: continue - # fetch machine details - job = PathUtils.findParentJob(obj) - - myMachine = 'not set' - - if hasattr(job, "MachineName"): - myMachine = job.MachineName - - if hasattr(job, "MachineUnits"): - if job.MachineUnits == "Metric": - UNITS = "G21" - UNIT_FORMAT = 'mm' - UNIT_SPEED_FORMAT = 'mm/min' - else: - UNITS = "G20" - UNIT_FORMAT = 'in' - UNIT_SPEED_FORMAT = 'in/min' - - if hasattr(job, "SetupSheet"): - if hasattr(job.SetupSheet, "HorizRapid"): - HORIZRAPID = Units.Quantity(job.SetupSheet.HorizRapid, FreeCAD.Units.Velocity) - if hasattr(job.SetupSheet, "VertRapid"): - VERTRAPID = Units.Quantity(job.SetupSheet.HorizRapid, FreeCAD.Units.Velocity) - # do the pre_op if OUTPUT_COMMENTS: gcode += linenumber() + "(BEGIN OPERATION: %s)\n" % obj.Label.upper() - gcode += linenumber() + "(MACHINE: %s, %s)\n" % (myMachine.upper(), UNIT_SPEED_FORMAT.upper()) + gcode += linenumber() + "(MACHINE UNITS: %s)\n" % (UNIT_SPEED_FORMAT.upper()) for line in PRE_OPERATION.splitlines(True): gcode += linenumber() + line @@ -459,7 +434,6 @@ def parse(pathobj): outstring.append(param + str(int(c.Parameters['D']))) elif param == 'S': outstring.append(param + str(int(c.Parameters['S']))) - currentSpeed = int(c.Parameters['S']) else: if (not OUTPUT_DOUBLES) and (param in currLocation) and (currLocation[param] == c.Parameters[param]): continue @@ -482,7 +456,6 @@ def parse(pathobj): # Check for Tool Change: if command == 'M6': # stop the spindle - currentSpeed = 0 out += linenumber() + "M5\n" for line in TOOL_CHANGE.splitlines(True): out += linenumber() + line diff --git a/src/Mod/Path/PathScripts/post/heidenhain_post.py b/src/Mod/Path/PathScripts/post/heidenhain_post.py index 42219952b0..acfc1a9b22 100644 --- a/src/Mod/Path/PathScripts/post/heidenhain_post.py +++ b/src/Mod/Path/PathScripts/post/heidenhain_post.py @@ -20,14 +20,10 @@ # HEDENHAIN Post-Processor for FreeCAD -import FreeCAD -from FreeCAD import Units import argparse -import time import shlex import PathScripts from PathScripts import PostUtils -from PathScripts import PathUtils import math #**************************************************************************# @@ -261,11 +257,8 @@ def export(objectslist, filename, argstring): global LBLIZE_STAUS Object_Kind = None - Feed_Rapid = False Feed = 0 - Spindle_RPM = 0 Spindle_Active = False - ToolId = "" Compensation = "0" params = [ 'X', 'Y', 'Z', @@ -360,22 +353,14 @@ def export(objectslist, filename, argstring): for c in obj.Path.Commands: Cmd_Count += 1 - outstring = [] command = c.Name if command != 'G0': command = command.replace('G0','G') # normalize: G01 -> G1 - Feed_Rapid = False - else: - Feed_Rapid = True for param in params: if param in c.Parameters: if param == 'F': Feed = c.Parameters['F'] - elif param == 'S': - Spindle_RPM = c.Parameters['S'] # Could be deleted if tool it's OK - elif param == 'T': - ToolId = c.Parameters['T'] # Could be deleted if tool it's OK if command == 'G90': G_FUNCTION_STORE['G90'] = True @@ -474,21 +459,21 @@ def export(objectslist, filename, argstring): def HEIDEN_Begin(ActualJob): #use Label for program name global UNITS - JobParent = PathUtils.findParentJob(ActualJob[0]) - if hasattr(JobParent, "Label"): - program_id = JobParent.Label - else: - program_id = "NEW" - return "BEGIN PGM " + str(program_id) + " " + UNITS + # JobParent = PathUtils.findParentJob(ActualJob[0]) + # if hasattr(JobParent, "Label"): + # program_id = JobParent.Label + # else: + # program_id = "NEW" + return "BEGIN PGM {}".format(UNITS) def HEIDEN_End(ActualJob): #use Label for program name global UNITS - JobParent = PathUtils.findParentJob(ActualJob[0]) - if hasattr(JobParent, "Label"): - program_id = JobParent.Label - else: - program_id = "NEW" - return "END PGM " + program_id + " " + UNITS + # JobParent = PathUtils.findParentJob(ActualJob[0]) + # if hasattr(JobParent, "Label"): + # program_id = JobParent.Label + # else: + # program_id = "NEW" + return "END PGM {}".format(UNITS) #def HEIDEN_ToolDef(tool_id, tool_length, tool_radius): # old machines don't have tool table, need tooldef list # return "TOOL DEF " + tool_id + " R" + "{:.3f}".format(tool_length) + " L" + "{:.3f}".format(tool_radius) diff --git a/src/Mod/Path/PathScripts/post/linuxcnc_post.py b/src/Mod/Path/PathScripts/post/linuxcnc_post.py index 6f22d93142..c102528d6b 100644 --- a/src/Mod/Path/PathScripts/post/linuxcnc_post.py +++ b/src/Mod/Path/PathScripts/post/linuxcnc_post.py @@ -29,7 +29,6 @@ import argparse import datetime import shlex from PathScripts import PostUtils -from PathScripts import PathUtils TOOLTIP = ''' This is a postprocessor file for the Path workbench. It is used to @@ -193,28 +192,10 @@ def export(objectslist, filename, argstring): if not obj.Base.Active: continue - # fetch machine details - job = PathUtils.findParentJob(obj) - - myMachine = 'not set' - - if hasattr(job, "MachineName"): - myMachine = job.MachineName - - if hasattr(job, "MachineUnits"): - if job.MachineUnits == "Metric": - UNITS = "G21" - UNIT_FORMAT = 'mm' - UNIT_SPEED_FORMAT = 'mm/min' - else: - UNITS = "G20" - UNIT_FORMAT = 'in' - UNIT_SPEED_FORMAT = 'in/min' - # do the pre_op if OUTPUT_COMMENTS: gcode += linenumber() + "(begin operation: %s)\n" % obj.Label - gcode += linenumber() + "(machine: %s, %s)\n" % (myMachine, UNIT_SPEED_FORMAT) + gcode += linenumber() + "(machine units: %s)\n" % (UNIT_SPEED_FORMAT) for line in PRE_OPERATION.splitlines(True): gcode += linenumber() + line diff --git a/src/Mod/Path/PathScripts/post/mach3_mach4_post.py b/src/Mod/Path/PathScripts/post/mach3_mach4_post.py index f2ca58259d..b34f8cae81 100644 --- a/src/Mod/Path/PathScripts/post/mach3_mach4_post.py +++ b/src/Mod/Path/PathScripts/post/mach3_mach4_post.py @@ -28,7 +28,6 @@ import argparse import datetime import shlex from PathScripts import PostUtils -from PathScripts import PathUtils TOOLTIP = ''' This is a postprocessor file for the Path workbench. It is used to @@ -161,8 +160,6 @@ def export(objectslist, filename, argstring): global UNITS global UNIT_FORMAT global UNIT_SPEED_FORMAT - global HORIZRAPID - global VERTRAPID for obj in objectslist: if not hasattr(obj, "Path"): @@ -195,34 +192,10 @@ def export(objectslist, filename, argstring): if not obj.Base.Active: continue - # fetch machine details - job = PathUtils.findParentJob(obj) - - myMachine = 'not set' - - if hasattr(job, "MachineName"): - myMachine = job.MachineName - - if hasattr(job, "MachineUnits"): - if job.MachineUnits == "Metric": - UNITS = "G21" - UNIT_FORMAT = 'mm' - UNIT_SPEED_FORMAT = 'mm/min' - else: - UNITS = "G20" - UNIT_FORMAT = 'in' - UNIT_SPEED_FORMAT = 'in/min' - - if hasattr(job, "SetupSheet"): - if hasattr(job.SetupSheet, "HorizRapid"): - HORIZRAPID = Units.Quantity(job.SetupSheet.HorizRapid, FreeCAD.Units.Velocity) - if hasattr(job.SetupSheet, "VertRapid"): - VERTRAPID = Units.Quantity(job.SetupSheet.HorizRapid, FreeCAD.Units.Velocity) - # do the pre_op if OUTPUT_COMMENTS: gcode += linenumber() + "(begin operation: %s)\n" % obj.Label - gcode += linenumber() + "(machine: %s, %s)\n" % (myMachine, UNIT_SPEED_FORMAT) + gcode += linenumber() + "(machine: %s, %s)\n" % (MACHINE_NAME, UNIT_SPEED_FORMAT) for line in PRE_OPERATION.splitlines(True): gcode += linenumber() + line diff --git a/src/Mod/Path/PathScripts/post/philips_post.py b/src/Mod/Path/PathScripts/post/philips_post.py index f06d2d666d..d48a5265c9 100644 --- a/src/Mod/Path/PathScripts/post/philips_post.py +++ b/src/Mod/Path/PathScripts/post/philips_post.py @@ -20,15 +20,14 @@ #* * #*************************************************************************** -# reload in python console: -# import generic_post -# reload(generic_post) +# 03-24-2021 Sliptonic: I've removed teh PathUtils import and job lookup +# post processors shouldn't be reaching back to the job. This can cause a +# proxy error. import FreeCAD import argparse import time from PathScripts import PostUtils -from PathScripts import PathUtils import math TOOLTIP = '''Post processor for Maho M 600E mill @@ -232,16 +231,17 @@ def processArguments(argstring): SHOW_EDITOR = False def mkHeader(selection): - job = PathUtils.findParentJob(selection[0]) + # job = PathUtils.findParentJob(selection[0]) # this is within a function, because otherwise filename and time don't change when changing the FreeCAD project # now = datetime.datetime.now() now = time.strftime("%Y-%m-%d %H:%M") originfile = FreeCAD.ActiveDocument.FileName headerNoNumber = "%PM\n" # this line gets no linenumber - if hasattr(job, "Description"): - description = job.Description - else: - description = "" + # if hasattr(job, "Description"): + # description = job.Description + # else: + # description = "" + description = "" # this line gets no linenumber, it is already a specially numbered headerNoNumber += "N9XXX (" + description + ", " + now + ")\n" header = "" From c2cfba638f2ccec933fd43629b2471770b420400 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sat, 29 May 2021 13:50:06 -0500 Subject: [PATCH 14/16] Path: Alphabetize import list and remove comments therein --- src/Mod/Path/PathScripts/PathGuiInit.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathGuiInit.py b/src/Mod/Path/PathScripts/PathGuiInit.py index bf13af4b9a..5fb67ffb3a 100644 --- a/src/Mod/Path/PathScripts/PathGuiInit.py +++ b/src/Mod/Path/PathScripts/PathGuiInit.py @@ -39,17 +39,17 @@ def Startup(): PathLog.debug('Initializing PathGui') from PathScripts import PathAdaptiveGui from PathScripts import PathArray + from PathScripts import PathCamoticsGui from PathScripts import PathComment - # from PathScripts import PathCustom from PathScripts import PathCustomGui from PathScripts import PathDeburrGui from PathScripts import PathDressupAxisMap from PathScripts import PathDressupDogbone from PathScripts import PathDressupDragknife - from PathScripts import PathDressupRampEntry - from PathScripts import PathDressupPathBoundaryGui - from PathScripts import PathDressupTagGui from PathScripts import PathDressupLeadInOut + from PathScripts import PathDressupPathBoundaryGui + from PathScripts import PathDressupRampEntry + from PathScripts import PathDressupTagGui from PathScripts import PathDressupZCorrect from PathScripts import PathDrillingGui from PathScripts import PathEngraveGui @@ -62,26 +62,20 @@ def Startup(): from PathScripts import PathPocketShapeGui from PathScripts import PathPost from PathScripts import PathProbeGui - # from PathScripts import PathProfileContourGui - # from PathScripts import PathProfileEdgesGui - # from PathScripts import PathProfileFacesGui from PathScripts import PathProfileGui from PathScripts import PathPropertyBagGui from PathScripts import PathSanity from PathScripts import PathSetupSheetGui from PathScripts import PathSimpleCopy from PathScripts import PathSimulatorGui - from PathScripts import PathCamoticsGui from PathScripts import PathSlotGui from PathScripts import PathStop - # from PathScripts import PathSurfaceGui # Added in initGui.py due to OCL dependency from PathScripts import PathThreadMillingGui from PathScripts import PathToolController from PathScripts import PathToolControllerGui - from PathScripts import PathToolLibraryManager from PathScripts import PathToolLibraryEditor + from PathScripts import PathToolLibraryManager from PathScripts import PathUtilsGui - # from PathScripts import PathWaterlineGui # Added in initGui.py due to OCL dependency from PathScripts import PathVcarveGui Processed = True else: From 7373c4dfa831b21fb1b86d25899438db94e39436 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sat, 29 May 2021 13:52:00 -0500 Subject: [PATCH 15/16] Path: Handle unavailability of Camotics library/module --- src/Mod/Path/PathScripts/PathGuiInit.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathGuiInit.py b/src/Mod/Path/PathScripts/PathGuiInit.py index 5fb67ffb3a..d7afd21f49 100644 --- a/src/Mod/Path/PathScripts/PathGuiInit.py +++ b/src/Mod/Path/PathScripts/PathGuiInit.py @@ -39,7 +39,13 @@ def Startup(): PathLog.debug('Initializing PathGui') from PathScripts import PathAdaptiveGui from PathScripts import PathArray - from PathScripts import PathCamoticsGui + try: + import camotics + except ImportError: + import FreeCAD + FreeCAD.Console.PrintError("Camotics is not available.\n") + else: + from PathScripts import PathCamoticsGui from PathScripts import PathComment from PathScripts import PathCustomGui from PathScripts import PathDeburrGui From c307b0ce7ecb1d0ea6667aa1485d0bfb199b3805 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Sat, 13 Mar 2021 11:10:10 -0600 Subject: [PATCH 16/16] fix cancel bug --- src/Mod/Path/PathScripts/PathPost.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathPost.py b/src/Mod/Path/PathScripts/PathPost.py index 530151b8d7..1a0699b801 100644 --- a/src/Mod/Path/PathScripts/PathPost.py +++ b/src/Mod/Path/PathScripts/PathPost.py @@ -113,7 +113,7 @@ def resolveFileName(job): if openDialog: foo = QtGui.QFileDialog.getSaveFileName(QtGui.QApplication.activeWindow(), "Output File", filename) - if foo: + if foo[0]: filename = foo[0] else: filename = None @@ -416,6 +416,8 @@ class CommandPathPost: if split: for slist in postlist: (fail, rc, filename) = self.exportObjectsWith(slist, job) + if fail: + break else: finalpostlist = [item for slist in postlist for item in slist] (fail, rc, filename) = self.exportObjectsWith(finalpostlist, job)