From 17b18f20d68085584ae41b62efa785c9de614296 Mon Sep 17 00:00:00 2001 From: sliptonic Date: Mon, 31 May 2021 12:49:25 -0500 Subject: [PATCH] Revert "[PATH] CAMotics simulation. First Draft" --- src/Mod/Path/CMakeLists.txt | 1 - src/Mod/Path/Gui/Resources/Path.qrc | 2 - .../Gui/Resources/icons/Path_Camotics.svg | 644 ------------------ .../Gui/Resources/icons/camotics-logo.png | Bin 1568 -> 0 bytes .../Resources/panels/TaskPathCamoticsSim.ui | 103 --- src/Mod/Path/InitGui.py | 9 +- src/Mod/Path/PathScripts/PathCamoticsGui.py | 400 ----------- src/Mod/Path/PathScripts/PathPost.py | 394 +++++------ 8 files changed, 180 insertions(+), 1373 deletions(-) delete mode 100644 src/Mod/Path/Gui/Resources/icons/Path_Camotics.svg delete mode 100644 src/Mod/Path/Gui/Resources/icons/camotics-logo.png delete mode 100644 src/Mod/Path/Gui/Resources/panels/TaskPathCamoticsSim.ui delete mode 100644 src/Mod/Path/PathScripts/PathCamoticsGui.py diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 3735fc0419..ddfcaa519b 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -33,7 +33,6 @@ 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 cc3d6340fb..309906a1b1 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -14,7 +14,6 @@ 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 @@ -131,7 +130,6 @@ 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 deleted file mode 100644 index e9d4b73037..0000000000 --- a/src/Mod/Path/Gui/Resources/icons/Path_Camotics.svg +++ /dev/null @@ -1,644 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 deleted file mode 100644 index 652cd11b1424de5884c1c65f9be30c4c755a165f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 d02be4923e..d4011741ab 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -78,7 +78,6 @@ class PathWorkbench (Workbench): from PathScripts import PathToolBitLibraryCmd import PathCommands - PathGuiInit.Startup() # build commands list @@ -97,6 +96,8 @@ class PathWorkbench (Workbench): "Path_DressupLeadInOut", "Path_DressupRampEntry", "Path_DressupTag", "Path_DressupZCorrect"] extracmdlist = [] + # modcmdmore = ["Path_Hop",] + # remotecmdlist = ["Path_Remote"] specialcmdlist = [] @@ -121,12 +122,6 @@ 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 deleted file mode 100644 index 125ba47bfb..0000000000 --- a/src/Mod/Path/PathScripts/PathCamoticsGui.py +++ /dev/null @@ -1,400 +0,0 @@ -# -*- 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/PathPost.py b/src/Mod/Path/PathScripts/PathPost.py index 1a0699b801..3c8facd865 100644 --- a/src/Mod/Path/PathScripts/PathPost.py +++ b/src/Mod/Path/PathScripts/PathPost.py @@ -33,7 +33,6 @@ 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 @@ -57,187 +56,6 @@ 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): @@ -279,6 +97,73 @@ 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[0]: + filename = foo[0] + else: + filename = None + + return filename def resolvePostProcessor(self, job): if hasattr(job, "PostProcessor"): @@ -305,37 +190,6 @@ 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 @@ -349,7 +203,7 @@ class CommandPathPost: postname = self.resolvePostProcessor(job) filename = '-' if postname and needFilename: - filename = resolveFileName(job) + filename = self.resolveFileName(job) if postname and filename: print("post: %s(%s, %s)" % (postname, filename, postArgs)) @@ -407,9 +261,117 @@ class CommandPathPost: PathLog.debug("about to postprocess job: {}".format(job.Name)) - postlist = buildPostList(job) - filename = resolveFileName(job) + wcslist = job.Fixtures + orderby = job.OrderOutputBy + split = job.SplitOutput + 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 = ''