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 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 0000000000..652cd11b14 Binary files /dev/null and b/src/Mod/Path/Gui/Resources/icons/camotics-logo.png differ diff --git a/src/Mod/Path/Gui/Resources/panels/TaskPathCamoticsSim.ui b/src/Mod/Path/Gui/Resources/panels/TaskPathCamoticsSim.ui new file mode 100644 index 0000000000..e73a4f179e --- /dev/null +++ b/src/Mod/Path/Gui/Resources/panels/TaskPathCamoticsSim.ui @@ -0,0 +1,103 @@ + + + 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/PathArray.py b/src/Mod/Path/PathScripts/PathArray.py index e33ca1a33d..ed892318cd 100644 --- a/src/Mod/Path/PathScripts/PathArray.py +++ b/src/Mod/Path/PathScripts/PathArray.py @@ -24,8 +24,10 @@ import FreeCAD import FreeCADGui import Path import PathScripts +from PathScripts import PathLog from PySide import QtCore import math +import random __doc__ = """Path Array object and FreeCAD command""" @@ -36,22 +38,28 @@ 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", 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", QtCore.QT_TRANSLATE_NOOP("App::Property","Make copies in X direction before Y in Linear 2D pattern")) + obj.addProperty("App::PropertyInteger", "JitterPercent", + "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","Percent of copies to randomly offset")) + obj.addProperty("App::PropertyVectorDistance", "JitterMagnitude", + "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")) @@ -67,6 +75,9 @@ class ObjectArray: return None 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) @@ -75,6 +86,7 @@ class ObjectArray: 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) @@ -155,57 +167,99 @@ class ObjectArray: return newPath + def calculateJitter(self, obj, pos): + if random.randint(0,100) < obj.JitterPercent: + pos.x = pos.x + random.uniform(-obj.JitterMagnitude.x, obj.JitterMagnitude.y) + pos.y = pos.y + random.uniform(-obj.JitterMagnitude.y, obj.JitterMagnitude.y) + pos.z = pos.z + random.uniform(-obj.JitterMagnitude.z, obj.JitterMagnitude.z) + return pos + + def execute(self, obj): - 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 + # backwards compatibility for PathArrays created before support for multiple bases + if isinstance(obj.Base, list): + base = obj.Base + else: + base = [obj.Base] - # build copies - basepath = obj.Base.Path - output = "" - if obj.Type == 'Linear1D': - for i in range(obj.Copies): + if len(base)==0: + return + + 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.')) + + # 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 basepath.Commands]) + for cm in b.Path.Commands]) output += np.toGCode() - elif obj.Type == 'Linear2D': + 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): - 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) + 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) - 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 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): + 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(basepath, ang, obj.Centre) + 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: @@ -237,7 +291,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 +306,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() 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..d7afd21f49 100644 --- a/src/Mod/Path/PathScripts/PathGuiInit.py +++ b/src/Mod/Path/PathScripts/PathGuiInit.py @@ -39,17 +39,23 @@ def Startup(): PathLog.debug('Initializing PathGui') from PathScripts import PathAdaptiveGui from PathScripts import PathArray + 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 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,9 +68,6 @@ 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 @@ -73,14 +76,12 @@ def Startup(): from PathScripts import PathSimulatorGui 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: diff --git a/src/Mod/Path/PathScripts/PathPost.py b/src/Mod/Path/PathScripts/PathPost.py index fec6ddd3a5..1a0699b801 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[0]: + 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,123 +407,17 @@ 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 = '' 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) 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 = ""