diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 12538fbe55..13135a8a24 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -34,6 +34,7 @@ SET(PathScripts_SRCS PathScripts/PathComment.py PathScripts/PathCopy.py PathScripts/PathCustom.py + PathScripts/PathCustomGui.py PathScripts/PathDeburr.py PathScripts/PathDeburrGui.py PathScripts/PathDressup.py diff --git a/src/Mod/Path/Gui/Resources/Path.qrc b/src/Mod/Path/Gui/Resources/Path.qrc index 59eea1c06b..e328fca04c 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -98,6 +98,7 @@ panels/PageBaseLocationEdit.ui panels/PageDepthsEdit.ui panels/PageHeightsEdit.ui + panels/PageOpCustomEdit.ui panels/PageOpDeburrEdit.ui panels/PageOpDrillingEdit.ui panels/PageOpEngraveEdit.ui diff --git a/src/Mod/Path/Gui/Resources/panels/PageOpCustomEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageOpCustomEdit.ui new file mode 100644 index 0000000000..ac1d20e165 --- /dev/null +++ b/src/Mod/Path/Gui/Resources/panels/PageOpCustomEdit.ui @@ -0,0 +1,93 @@ + + + Form + + + + 0 + 0 + 424 + 376 + + + + Form + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + + + <html><head/><body><p>The tool and its settings to be used for this operation.</p></body></html> + + + + + + + ToolController + + + + + + + Coolant Mode + + + + + + + + + + + + + G Gode + + + + + + + QTextEdit::NoWrap + + + false + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/src/Mod/Path/PathScripts/PathCustom.py b/src/Mod/Path/PathScripts/PathCustom.py index a06b8b51ef..217ec244f0 100644 --- a/src/Mod/Path/PathScripts/PathCustom.py +++ b/src/Mod/Path/PathScripts/PathCustom.py @@ -22,84 +22,58 @@ # *************************************************************************** import FreeCAD -import FreeCADGui import Path + +import PathScripts.PathOp as PathOp +import PathScripts.PathLog as PathLog + from PySide import QtCore -from copy import copy +__title__ = "Path Custom Operation" +__author__ = "sliptonic (Brad Collette)" +__url__ = "http://www.freecadweb.org" +__doc__ = "Path Custom object and FreeCAD command" -__doc__ = """Path Custom object and FreeCAD command""" +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -movecommands = ['G0', 'G00', 'G1', 'G01', 'G2', 'G02', 'G3', 'G03'] # Qt translation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) -class ObjectCustom: - def __init__(self, obj): +class ObjectCustom(PathOp.ObjectOp): + def opFeatures(self, obj): + return PathOp.FeatureTool | PathOp.FeatureCoolant + + def initOperation(self, obj): obj.addProperty("App::PropertyStringList", "Gcode", "Path", QtCore.QT_TRANSLATE_NOOP("PathCustom", "The gcode to be inserted")) - obj.addProperty("App::PropertyLink", "ToolController", "Path", - QtCore.QT_TRANSLATE_NOOP("PathCustom", "The tool controller that will be used to calculate the path")) - obj.addProperty("App::PropertyPlacement", "Offset", "Path", - "Placement Offset") obj.Proxy = self - def __getstate__(self): - return None - - def __setstate__(self, state): - return None - - def execute(self, obj): - newpath = Path.Path() + def opExecute(self, obj): + self.commandlist.append(Path.Command("(Begin Custom)")) if obj.Gcode: for l in obj.Gcode: newcommand = Path.Command(str(l)) - if newcommand.Name in movecommands: - if 'X' in newcommand.Parameters: - newcommand.x += obj.Offset.Base.x - if 'Y' in newcommand.Parameters: - newcommand.y += obj.Offset.Base.y - if 'Z' in newcommand.Parameters: - newcommand.z += obj.Offset.Base.z + self.commandlist.append(newcommand) - newpath.insertCommand(newcommand) - - obj.Path=newpath + self.commandlist.append(Path.Command("(End Custom)")) - -class CommandPathCustom: - - def GetResources(self): - return {'Pixmap': 'Path-Custom', - 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Custom", "Custom"), - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Custom", "Creates a path object based on custom G-code")} - - def IsActive(self): - if FreeCAD.ActiveDocument is not None: - for o in FreeCAD.ActiveDocument.Objects: - if o.Name[:3] == "Job": - return True - return False - - def Activated(self): - FreeCAD.ActiveDocument.openTransaction("Create Custom Path") - FreeCADGui.addModule("PathScripts.PathCustom") - FreeCADGui.addModule("PathScripts.PathUtils") - FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "Custom")') - FreeCADGui.doCommand('PathScripts.PathCustom.ObjectCustom(obj)') - FreeCADGui.doCommand('obj.ViewObject.Proxy = 0') - FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)') - FreeCADGui.doCommand('obj.ToolController = PathScripts.PathUtils.findToolController(obj)') - FreeCAD.ActiveDocument.commitTransaction() - FreeCAD.ActiveDocument.recompute() +def SetupProperties(): + setup = [] + return setup -if FreeCAD.GuiUp: - # register the FreeCAD command - FreeCADGui.addCommand('Path_Custom', CommandPathCustom()) \ No newline at end of file +def Create(name, obj=None): + '''Create(name) ... Creates and returns a Custom operation.''' + if obj is None: + obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) + proxy = ObjectCustom(obj, name) + return obj diff --git a/src/Mod/Path/PathScripts/PathCustomGui.py b/src/Mod/Path/PathScripts/PathCustomGui.py new file mode 100644 index 0000000000..3ec21091f7 --- /dev/null +++ b/src/Mod/Path/PathScripts/PathCustomGui.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- + +# *************************************************************************** +# * * +# * Copyright (c) 2017 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.PathCustom as PathCustom +import PathScripts.PathOpGui as PathOpGui + +from PySide import QtCore + +__title__ = "Path Custom Operation UI" +__author__ = "sliptonic (Brad Collette)" +__url__ = "http://www.freecadweb.org" +__doc__ = "Custom operation page controller and command implementation." + + +# Qt translation handling +def translate(context, text, disambig=None): + return QtCore.QCoreApplication.translate(context, text, disambig) + + +# class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage): +# '''Page controller for the base geometry.''' + +# def getForm(self): +# return None + + +class TaskPanelOpPage(PathOpGui.TaskPanelPage): + '''Page controller class for the Custom operation.''' + + def getForm(self): + '''getForm() ... returns UI''' + return FreeCADGui.PySideUic.loadUi(":/panels/PageOpCustomEdit.ui") + + def getFields(self, obj): + '''getFields(obj) ... transfers values from UI to obj's properties''' + self.updateToolController(obj, self.form.toolController) + self.updateCoolant(obj, self.form.coolantController) + + def setFields(self, obj): + '''setFields(obj) ... transfers obj's property values to UI''' + self.setupToolController(obj, self.form.toolController) + self.form.txtGCode.setText("\n".join(obj.Gcode)) + self.setupCoolant(obj, self.form.coolantController) + + def getSignalsForUpdate(self, obj): + '''getSignalsForUpdate(obj) ... return list of signals for updating obj''' + signals = [] + signals.append(self.form.toolController.currentIndexChanged) + signals.append(self.form.coolantController.currentIndexChanged) + self.form.txtGCode.textChanged.connect(self.setGCode) + return signals + + def setGCode(self): + self.obj.Gcode = self.form.txtGCode.toPlainText().splitlines() + + +Command = PathOpGui.SetupOperation('Custom', PathCustom.Create, TaskPanelOpPage, + 'Path-Custom', + QtCore.QT_TRANSLATE_NOOP("Custom", "Custom"), + QtCore.QT_TRANSLATE_NOOP("Custom", "Create custom gcode snippet"), + PathCustom.SetupProperties) + +FreeCAD.Console.PrintLog("Loading PathCustomGui... done\n") diff --git a/src/Mod/Path/PathScripts/PathGuiInit.py b/src/Mod/Path/PathScripts/PathGuiInit.py index 21aadba1d5..81d6b911c2 100644 --- a/src/Mod/Path/PathScripts/PathGuiInit.py +++ b/src/Mod/Path/PathScripts/PathGuiInit.py @@ -43,7 +43,8 @@ def Startup(): from PathScripts import PathAdaptiveGui from PathScripts import PathArray from PathScripts import PathComment - from PathScripts import PathCustom + # from PathScripts import PathCustom + from PathScripts import PathCustomGui from PathScripts import PathDeburrGui from PathScripts import PathDressupAxisMap from PathScripts import PathDressupDogbone diff --git a/src/Mod/Path/PathScripts/PathOp.py b/src/Mod/Path/PathScripts/PathOp.py index 098c4bfc90..6c21ea8c9a 100644 --- a/src/Mod/Path/PathScripts/PathOp.py +++ b/src/Mod/Path/PathScripts/PathOp.py @@ -65,7 +65,7 @@ FeatureBasePanels = 0x0800 # Base FeatureLocations = 0x1000 # Locations FeatureCoolant = 0x2000 # Coolant -FeatureBaseGeometry = FeatureBaseVertexes | FeatureBaseFaces | FeatureBaseEdges | FeatureBasePanels | FeatureCoolant +FeatureBaseGeometry = FeatureBaseVertexes | FeatureBaseFaces | FeatureBaseEdges | FeatureBasePanels class ObjectOp(object): @@ -245,7 +245,7 @@ class ObjectOp(object): def opFeatures(self, obj): '''opFeatures(obj) ... returns the OR'ed list of features used and supported by the operation. - The default implementation returns "FeatureTool | FeatureDeptsh | FeatureHeights | FeatureStartPoint" + The default implementation returns "FeatureTool | FeatureDepths | FeatureHeights | FeatureStartPoint" Should be overwritten by subclasses.''' # pylint: disable=unused-argument return FeatureTool | FeatureDepths | FeatureHeights | FeatureStartPoint | FeatureBaseGeometry | FeatureFinishDepth | FeatureCoolant diff --git a/src/Mod/Path/PathScripts/PathOpGui.py b/src/Mod/Path/PathScripts/PathOpGui.py index 02561a47ba..4101cb6c87 100644 --- a/src/Mod/Path/PathScripts/PathOpGui.py +++ b/src/Mod/Path/PathScripts/PathOpGui.py @@ -91,6 +91,11 @@ class ViewProvider(object): PathLog.track() return hasattr(self, 'deleteOnReject') and self.deleteOnReject + def setDeleteObjectsOnReject(self, state=False): + PathLog.track() + self.deleteOnReject = state + return self.deleteOnReject + def setEdit(self, vobj=None, mode=0): '''setEdit(vobj, mode=0) ... initiate editing of receivers model.''' PathLog.track() @@ -198,7 +203,7 @@ class TaskPanelPage(object): Do not overwrite, implement initPage(obj) instead.''' self.obj = obj self.job = PathUtils.findParentJob(obj) - self.form = self.getForm() # pylint: disable=assignment-from-no-return + self.form = self.getForm() # pylint: disable=assignment-from-no-return self.signalDirtyChanged = None self.setClean() self.setTitle('-') @@ -285,32 +290,32 @@ class TaskPanelPage(object): Note that this function is invoked after all page controllers have been created. Should be overwritten by subclasses.''' # pylint: disable=unused-argument - pass # pylint: disable=unnecessary-pass + pass # pylint: disable=unnecessary-pass def cleanupPage(self, obj): '''cleanupPage(obj) ... overwrite to perform any cleanup tasks before page is destroyed. Can safely be overwritten by subclasses.''' - pass # pylint: disable=unnecessary-pass + pass # pylint: disable=unnecessary-pass def modifyStandardButtons(self, buttonBox): '''modifyStandardButtons(buttonBox) ... overwrite if the task panel standard buttons need to be modified. Can safely be overwritten by subclasses.''' - pass # pylint: disable=unnecessary-pass + pass # pylint: disable=unnecessary-pass def getForm(self): '''getForm() ... return UI form for this page. Must be overwritten by subclasses.''' - pass # pylint: disable=unnecessary-pass + pass # pylint: disable=unnecessary-pass def getFields(self, obj): '''getFields(obj) ... overwrite to transfer values from UI to obj's properties. Can safely be overwritten by subclasses.''' - pass # pylint: disable=unnecessary-pass + pass # pylint: disable=unnecessary-pass def setFields(self, obj): '''setFields(obj) ... overwrite to transfer obj's property values to UI. Can safely be overwritten by subclasses.''' - pass # pylint: disable=unnecessary-pass + pass # pylint: disable=unnecessary-pass def getSignalsForUpdate(self, obj): '''getSignalsForUpdate(obj) ... return signals which, when triggered, cause the receiver to update the model. @@ -326,7 +331,7 @@ class TaskPanelPage(object): manually. Can safely be overwritten by subclasses.''' # pylint: disable=unused-argument - pass # pylint: disable=unnecessary-pass + pass # pylint: disable=unnecessary-pass def updateData(self, obj, prop): '''updateData(obj, prop) ... overwrite if the receiver needs to react to property changes that might not have been caused by the receiver itself. @@ -339,13 +344,13 @@ class TaskPanelPage(object): In such a scenario the first property assignment will cause all changes in the UI of the other fields to be overwritten by setFields(obj). You have been warned.''' # pylint: disable=unused-argument - pass # pylint: disable=unnecessary-pass + pass # pylint: disable=unnecessary-pass def updateSelection(self, obj, sel): '''updateSelection(obj, sel) ... overwrite to customize UI depending on current selection. Can safely be overwritten by subclasses.''' # pylint: disable=unused-argument - pass # pylint: disable=unnecessary-pass + pass # pylint: disable=unnecessary-pass # helpers def selectInComboBox(self, name, combo): @@ -432,7 +437,6 @@ class TaskPanelBaseGeometryPage(TaskPanelPage): if len(availableOps) > 0: # Populate the operations list - addInputs = True panel.geometryImportList.blockSignals(True) panel.geometryImportList.clear() availableOps.sort() @@ -738,7 +742,7 @@ class TaskPanelHeightsPage(TaskPanelPage): self.safeHeight.updateProperty() self.clearanceHeight.updateProperty() - def setFields(self, obj): + def setFields(self, obj): self.safeHeight.updateSpinBox() self.clearanceHeight.updateSpinBox() @@ -933,7 +937,7 @@ class TaskPanel(object): else: self.featurePages.append(TaskPanelBaseLocationPage(obj, features)) - if PathOp.FeatureDepths & features or PathOp.FeatureStepDown: + if PathOp.FeatureDepths & features or PathOp.FeatureStepDown & features: if hasattr(opPage, 'taskPanelDepthsPage'): self.featurePages.append(opPage.taskPanelDepthsPage(obj, features)) else: @@ -1186,9 +1190,9 @@ class CommandPathOp: self.res = resources def GetResources(self): - ress = {'Pixmap': self.res.pixmap, + ress = {'Pixmap': self.res.pixmap, 'MenuText': self.res.menuText, - 'ToolTip': self.res.toolTip} + 'ToolTip': self.res.toolTip} if self.res.accelKey: ress['Accel'] = self.res.accelKey return ress @@ -1236,7 +1240,7 @@ def SetupOperation(name, command = CommandPathOp(res) FreeCADGui.addCommand("Path_%s" % name.replace(' ', '_'), command) - if not setupProperties is None: + if setupProperties is not None: PathSetupSheet.RegisterOperation(name, objFactory, setupProperties) return command diff --git a/src/Mod/Path/PathScripts/PathSelection.py b/src/Mod/Path/PathScripts/PathSelection.py index 943d6ae28a..ab00ae93ee 100644 --- a/src/Mod/Path/PathScripts/PathSelection.py +++ b/src/Mod/Path/PathScripts/PathSelection.py @@ -290,6 +290,10 @@ def probeselect(): FreeCADGui.Selection.addSelectionGate(PROBEGate()) FreeCAD.Console.PrintWarning("Probe Select Mode\n") +def customselect(): + FreeCAD.Console.PrintWarning("Custom Select Mode\n") + + def select(op): opsel = {} @@ -309,6 +313,7 @@ def select(op): opsel['Waterline'] = surfaceselect opsel['Adaptive'] = adaptiveselect opsel['Probe'] = probeselect + opsel['Custom'] = customselect return opsel[op] diff --git a/src/Mod/Path/PathScripts/post/example_pre.py b/src/Mod/Path/PathScripts/post/example_pre.py index 48ceb433bf..18e2abe79b 100644 --- a/src/Mod/Path/PathScripts/post/example_pre.py +++ b/src/Mod/Path/PathScripts/post/example_pre.py @@ -25,7 +25,8 @@ ''' This is an example preprocessor file for the Path workbench. Its aim is to open a gcode file, parse its contents, and create the appropriate objects -in FreeCAD. +in FreeCAD. This preprocessor will not add imported gcode to an existing +job. For a more useful preprocessor, look at the gcode_pre.py file Read the Path Workbench documentation to know how to create Path objects from GCode. @@ -76,8 +77,8 @@ def parse(inputstring): PathLog.track(inputstring) # split the input by line lines = inputstring.split("\n") - output = [] #"" - lastcommand = None + output = [] + lastcommand = None for lin in lines: # remove any leftover trailing and preceding spaces @@ -98,8 +99,7 @@ def parse(inputstring): continue if lin[0].upper() in ["G", "M"]: # found a G or M command: we store it - #output += lin + "\n" - output.append(Path.Command(str(lin))) # + "\n" + output.append(Path.Command(str(lin))) last = lin[0].upper() for c in lin[1:]: if not c.isdigit(): @@ -109,7 +109,7 @@ def parse(inputstring): lastcommand = last elif lastcommand: # no G or M command: we repeat the last one - output.append(Path.Command(str(lastcommand + " " + lin))) # + "\n" + output.append(Path.Command(str(lastcommand + " " + lin))) print("done preprocessing.") return output diff --git a/src/Mod/Path/PathScripts/post/gcode_pre.py b/src/Mod/Path/PathScripts/post/gcode_pre.py index 053c39c229..795192257b 100644 --- a/src/Mod/Path/PathScripts/post/gcode_pre.py +++ b/src/Mod/Path/PathScripts/post/gcode_pre.py @@ -27,16 +27,29 @@ This is an example preprocessor file for the Path workbench. Its aim is to open a gcode file, parse its contents, and create the appropriate objects in FreeCAD. +This preprocessor will split gcode on tool changes and create one or more +PathCustom objects in the job. Tool Change commands themselves are not +preserved. It is up to the user to create and assign appropriate tool +controllers. + +Only gcodes that are supported by Path are imported. Thus things like G43 +are suppressed. + +Importing gcode is inherently dangerous because context cannot be safely +assumed. The user should carefully examine the resulting gcode! + Read the Path Workbench documentation to know how to create Path objects from GCode. ''' import os -import Path import FreeCAD -import PathScripts.PathUtils +import PathScripts.PathUtils as PathUtils import PathScripts.PathLog as PathLog import re +import PathScripts.PathCustom as PathCustom +import PathScripts.PathCustomGui as PathCustomGui +import PathScripts.PathOpGui as PathOpGui # LEVEL = PathLog.Level.DEBUG LEVEL = PathLog.Level.INFO @@ -59,71 +72,109 @@ def open(filename): insert(filename, doc.Name) +def matchToolController(op, toolnumber): + """Try to match a tool controller in the job by number""" + toolcontrollers = PathUtils.getToolControllers(op) + for tc in toolcontrollers: + if tc.ToolNumber == toolnumber: + return tc + return toolcontrollers[0] + + def insert(filename, docname): "called when freecad imports a file" PathLog.track(filename) gfile = pythonopen(filename) gcode = gfile.read() gfile.close() - # split on tool changes - paths = re.split('(?=[mM]+\s?0?6)', gcode) - # if there are any tool changes combine the preamble with the default tool - if len(paths) > 1: - paths = ["\n".join(paths[0:2])] + paths[2:] + + # Regular expression to match tool changes in the format 'M6 Tn' + p = re.compile('[mM]+?\s?0?6\s?T\d*\s') + + # split the gcode on tool changes + paths = re.split('([mM]+?\s?0?6\s?T\d*\s)', gcode) + + # iterate the gcode sections and add customs for each + toolnumber = 0 + for path in paths: + + # if the section is a tool change, extract the tool number + m = p.match(path) + if m: + toolnumber = int(m.group().split('T')[-1]) + continue + + # Parse the gcode and throw away any empty lists gcode = parse(path) - doc = FreeCAD.getDocument(docname) - obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "Custom") - PathScripts.PathCustom.ObjectCustom(obj) - obj.ViewObject.Proxy = 0 + if len(gcode) == 0: + continue + + # Create a custom and viewobject + obj = PathCustom.Create("Custom") + res = PathOpGui.CommandResources('Custom', PathCustom.Create, PathCustomGui.TaskPanelOpPage, 'Path-Custom', 'Path-Custom', '', '') + obj.ViewObject.Proxy = PathOpGui.ViewProvider(obj.ViewObject, res) + obj.ViewObject.Proxy.setDeleteObjectsOnReject(False) + + # Set the gcode and try to match a tool controller obj.Gcode = gcode - PathScripts.PathUtils.addToJob(obj) - obj.ToolController = PathScripts.PathUtils.findToolController(obj) + obj.ToolController = matchToolController(obj, toolnumber) + FreeCAD.ActiveDocument.recompute() def parse(inputstring): "parse(inputstring): returns a parsed output string" + + supported = ['G0', 'G00', + 'G1', 'G01', + 'G2', 'G02', + 'G3', 'G03', + 'G81', 'G82', 'G83', + 'G90', 'G91'] + + axis = ["X", "Y", "Z", "A", "B", "C", "U", "V", "W"] + print("preprocessing...") PathLog.track(inputstring) # split the input by line - lines = inputstring.split("\n") - output = [] #"" - lastcommand = None + lines = inputstring.splitlines() + output = [] + lastcommand = None for lin in lines: # remove any leftover trailing and preceding spaces lin = lin.strip() + + # discard empty lines if not lin: - # discard empty lines continue + + # remove line numbers if lin[0].upper() in ["N"]: - # remove line numbers lin = lin.split(" ", 1) if len(lin) >= 1: lin = lin[1].strip() else: continue - if lin[0] in ["(", "%", "#", ";"]: - # discard comment and other non strictly gcode lines + # Anything else not a G/M code or an axis move is ignored. + if lin[0] not in ["G", "M", "X", "Y", "Z", "A", "B", "C", "U", "V", "W"]: continue - if lin[0].upper() in ["G", "M"]: - # found a G or M command: we store it - #output += lin + "\n" - output.append(lin) # + "\n" - last = lin[0].upper() - for c in lin[1:]: - if not c.isdigit(): - break - else: - last += c - lastcommand = last - elif lastcommand: - # no G or M command: we repeat the last one - output.append(lastcommand + " " + lin) # + "\n" + + # if the remaining line is supported, store it + currcommand = lin.split()[0] + + if currcommand in supported: + output.append(lin) + lastcommand = currcommand + + # modal commands have no G or M but have axis moves. append those too. + elif currcommand[0] in axis and lastcommand: + output.append(lastcommand + " " + lin) print("done preprocessing.") return output + print(__name__ + " gcode preprocessor loaded.")