diff --git a/src/Mod/Path/PathScripts/PathPost.py b/src/Mod/Path/PathScripts/PathPost.py index 39abd1cec5..841872b996 100644 --- a/src/Mod/Path/PathScripts/PathPost.py +++ b/src/Mod/Path/PathScripts/PathPost.py @@ -20,7 +20,7 @@ # * * # *************************************************************************** -''' Post Process command that will make use of the Output File and Post Processor entries in PathJob ''' +""" Post Process command that will make use of the Output File and Post Processor entries in PathJob """ from __future__ import print_function @@ -37,17 +37,13 @@ import os from PathScripts.PathPostProcessor import PostProcessor from PySide import QtCore, QtGui from datetime import datetime +from PySide.QtCore import QT_TRANSLATE_NOOP LOG_MODULE = PathLog.thisModule() PathLog.setLevel(PathLog.Level.INFO, LOG_MODULE) -# Qt translation handling -def translate(context, text, disambig=None): - return QtCore.QCoreApplication.translate(context, text, disambig) - - class _TempObject: # pylint: disable=no-init Path = None @@ -57,14 +53,15 @@ class _TempObject: class DlgSelectPostProcessor: - def __init__(self, parent=None): # pylint: disable=unused-argument self.dialog = FreeCADGui.PySideUic.loadUi(":/panels/DlgSelectPostProcessor.ui") firstItem = None for post in PathPreferences.allEnabledPostProcessors(): item = QtGui.QListWidgetItem(post) - item.setFlags(QtCore.Qt.ItemFlag.ItemIsSelectable | QtCore.Qt.ItemFlag.ItemIsEnabled) + item.setFlags( + QtCore.Qt.ItemFlag.ItemIsSelectable | QtCore.Qt.ItemFlag.ItemIsEnabled + ) self.dialog.lwPostProcessor.addItem(item) if not firstItem: firstItem = item @@ -103,50 +100,52 @@ class CommandPathPost: path = job.PostProcessorOutputFile filename = path - if '%D' in filename: + 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 = '.' + D = "." else: - FreeCAD.Console.PrintError("Please save document in order to resolve output path!\n") + FreeCAD.Console.PrintError( + "Please save document in order to resolve output path!\n" + ) return None - filename = filename.replace('%D', D) + filename = filename.replace("%D", D) - if '%d' in filename: + if "%d" in filename: d = FreeCAD.ActiveDocument.Label - filename = filename.replace('%d', d) + filename = filename.replace("%d", d) - if '%j' in filename: + if "%j" in filename: j = job.Label - filename = filename.replace('%j', j) + filename = filename.replace("%j", j) - if '%M' in filename: + if "%M" in filename: pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Macro") M = pref.GetString("MacroPath", FreeCAD.getUserAppDataDir()) - filename = filename.replace('%M', M) + filename = filename.replace("%M", M) - if '%s' in filename: + if "%s" in filename: if job.SplitOutput: - filename = filename.replace('%s', '_'+str(self.subpart)) + filename = filename.replace("%s", "_" + str(self.subpart)) self.subpart += 1 else: - filename = filename.replace('%s', '') + filename = filename.replace("%s", "") policy = PathPreferences.defaultOutputPolicy() - openDialog = policy == 'Open File Dialog' + 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': + if policy == "Open File Dialog on conflict": openDialog = True - elif policy == 'Append Unique ID on conflict': + elif policy == "Append Unique ID on conflict": fn, ext = os.path.splitext(filename) nr = fn[-3:] n = 1 @@ -157,7 +156,9 @@ class CommandPathPost: filename = "%s%03d%s" % (fn, n, ext) if openDialog: - foo = QtGui.QFileDialog.getSaveFileName(QtGui.QApplication.activeWindow(), "Output File", filename) + foo = QtGui.QFileDialog.getSaveFileName( + QtGui.QApplication.activeWindow(), "Output File", filename + ) if foo[0]: filename = foo[0] else: @@ -176,10 +177,12 @@ class CommandPathPost: return dlg.exec_() def GetResources(self): - return {'Pixmap': 'Path_Post', - 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Post", "Post Process"), - 'Accel': "P, P", - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Post", "Post Process the selected Job")} + return { + "Pixmap": "Path_Post", + "MenuText": QT_TRANSLATE_NOOP("Path_Post", "Post Process"), + "Accel": "P, P", + "ToolTip": QT_TRANSLATE_NOOP("Path_Post", "Post Process the selected Job"), + } def IsActive(self): if FreeCAD.ActiveDocument is not None: @@ -198,10 +201,10 @@ class CommandPathPost: if hasattr(job, "PostProcessorArgs") and job.PostProcessorArgs: postArgs = job.PostProcessorArgs elif hasattr(job, "PostProcessor") and job.PostProcessor: - postArgs = '' + postArgs = "" postname = self.resolvePostProcessor(job) - filename = '-' + filename = "-" if postname and needFilename: filename = self.resolveFileName(job) @@ -211,12 +214,11 @@ class CommandPathPost: gcode = processor.export(objs, filename, postArgs) return (False, gcode, filename) else: - return (True, '', filename) + return (True, "", filename) def Activated(self): PathLog.track() - FreeCAD.ActiveDocument.openTransaction( - translate("Path_Post", "Post Process the Selected path(s)")) + FreeCAD.ActiveDocument.openTransaction("Post Process the Selected path(s)") FreeCADGui.addModule("PathScripts.PathPost") # Attempt to figure out what the user wants to post-process @@ -227,7 +229,9 @@ class CommandPathPost: selected = FreeCADGui.Selection.getSelectionEx() if len(selected) > 1: - FreeCAD.Console.PrintError("Please select a single job or other path object\n") + FreeCAD.Console.PrintError( + "Please select a single job or other path object\n" + ) return elif len(selected) == 1: sel = selected[0].Object @@ -267,7 +271,7 @@ class CommandPathPost: postlist = [] - if orderby == 'Fixture': + 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. @@ -279,7 +283,13 @@ class CommandPathPost: 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)) + 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] @@ -287,7 +297,7 @@ class CommandPathPost: # 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 is not None and PathUtil.opProperty(obj, "Active"): if tc.ToolNumber != currTool or split is True: sublist.append(tc) PathLog.debug("Appending TC: {}".format(tc.Name)) @@ -295,7 +305,7 @@ class CommandPathPost: sublist.append(obj) postlist.append(sublist) - elif orderby == 'Tool': + 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 @@ -307,7 +317,13 @@ class CommandPathPost: # 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)) + 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) @@ -319,16 +335,20 @@ class CommandPathPost: for idx, obj in enumerate(job.Operations.Group): # check if the operation is active - active = PathUtil.opProperty(obj, '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 + 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 + elif ( + tc.ToolNumber != currTool and currTool is not None and active + ): # TC for fixture in fixturelist: sublist.append(fixture) sublist.extend(curlist) @@ -343,7 +363,7 @@ class CommandPathPost: sublist.extend(curlist) postlist.append(sublist) - elif orderby == 'Operation': + elif orderby == "Operation": PathLog.debug("Ordering by Operation") # Order by operation means ops are done in each fixture in # sequence. @@ -352,7 +372,7 @@ class CommandPathPost: # Now generate the gcode for obj in job.Operations.Group: - if PathUtil.opProperty(obj, 'Active'): + if PathUtil.opProperty(obj, "Active"): sublist = [] PathLog.debug("obj: {}".format(obj.Name)) for f in wcslist: @@ -360,7 +380,13 @@ class CommandPathPost: 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)) + 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) @@ -374,7 +400,7 @@ class CommandPathPost: postlist.append(sublist) fail = True - rc = '' + rc = "" if split: for slist in postlist: (fail, rc, filename) = self.exportObjectsWith(slist, job) @@ -400,6 +426,6 @@ class CommandPathPost: if FreeCAD.GuiUp: # register the FreeCAD command - FreeCADGui.addCommand('Path_Post', CommandPathPost()) + FreeCADGui.addCommand("Path_Post", CommandPathPost()) FreeCAD.Console.PrintLog("Loading PathPost... done\n") diff --git a/src/Mod/Path/PathScripts/PostUtils.py b/src/Mod/Path/PathScripts/PostUtils.py index 06bd1914e0..4d554188f7 100644 --- a/src/Mod/Path/PathScripts/PostUtils.py +++ b/src/Mod/Path/PathScripts/PostUtils.py @@ -1,34 +1,36 @@ # -*- coding: utf-8 -*- -#*************************************************************************** -#* Copyright (c) 2014 Yorik van Havre * -#* * -#* This file is part of the FreeCAD CAx development system. * -#* * -#* 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. * -#* * -#* FreeCAD 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 Lesser General Public License for more details. * -#* * -#* You should have received a copy of the GNU Library General Public * -#* License along with FreeCAD; if not, write to the Free Software * -#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * -#* USA * -#* * -#*************************************************************************** +# *************************************************************************** +# * Copyright (c) 2014 Yorik van Havre * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * 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. * +# * * +# * FreeCAD 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 Lesser General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with FreeCAD; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** -''' +""" These are a common functions and classes for creating custom post processors. -''' +""" from PySide import QtCore, QtGui import FreeCAD +translate = FreeCAD.Qt.translate + FreeCADGui = None if FreeCAD.GuiUp: import FreeCADGui @@ -41,15 +43,16 @@ class GCodeHighlighter(QtGui.QSyntaxHighlighter): keywordFormat = QtGui.QTextCharFormat() keywordFormat.setForeground(QtCore.Qt.cyan) keywordFormat.setFontWeight(QtGui.QFont.Bold) - keywordPatterns = ["\\bG[0-9]+\\b", - "\\bM[0-9]+\\b"] + keywordPatterns = ["\\bG[0-9]+\\b", "\\bM[0-9]+\\b"] - self.highlightingRules = [(QtCore.QRegExp(pattern), keywordFormat) for pattern in keywordPatterns] + self.highlightingRules = [ + (QtCore.QRegExp(pattern), keywordFormat) for pattern in keywordPatterns + ] speedFormat = QtGui.QTextCharFormat() speedFormat.setFontWeight(QtGui.QFont.Bold) speedFormat.setForeground(QtCore.Qt.green) - self.highlightingRules.append((QtCore.QRegExp("\\bF[0-9\\.]+\\b"),speedFormat)) + self.highlightingRules.append((QtCore.QRegExp("\\bF[0-9\\.]+\\b"), speedFormat)) def highlightBlock(self, text): for pattern, hlFormat in self.highlightingRules: @@ -61,12 +64,11 @@ class GCodeHighlighter(QtGui.QSyntaxHighlighter): index = expression.indexIn(text, index + length) - class GCodeEditorDialog(QtGui.QDialog): - def __init__(self, parent = None): + def __init__(self, parent=None): if parent is None: parent = FreeCADGui.getMainWindow() - QtGui.QDialog.__init__(self,parent) + QtGui.QDialog.__init__(self, parent) layout = QtGui.QVBoxLayout(self) @@ -83,7 +85,9 @@ class GCodeEditorDialog(QtGui.QDialog): # OK and Cancel buttons self.buttons = QtGui.QDialogButtonBox( QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel, - QtCore.Qt.Horizontal, self) + QtCore.Qt.Horizontal, + self, + ) layout.addWidget(self.buttons) # restore placement and size @@ -111,45 +115,68 @@ class GCodeEditorDialog(QtGui.QDialog): def stringsplit(commandline): - returndict = {'command':None, 'X':None, 'Y':None, 'Z':None, 'A':None, 'B':None, 'F':None, 'T':None, 'S':None, 'I':None, 'J':None,'K':None, 'txt': None} + returndict = { + "command": None, + "X": None, + "Y": None, + "Z": None, + "A": None, + "B": None, + "F": None, + "T": None, + "S": None, + "I": None, + "J": None, + "K": None, + "txt": None, + } wordlist = [a.strip() for a in commandline.split(" ")] - if wordlist[0][0] == '(': - returndict['command'] = 'message' - returndict['txt'] = wordlist[0] + if wordlist[0][0] == "(": + returndict["command"] = "message" + returndict["txt"] = wordlist[0] else: - returndict['command'] = wordlist[0] + returndict["command"] = wordlist[0] for word in wordlist[1:]: returndict[word[0]] = word[1:] return returndict -def fmt(num,dec,units): - ''' used to format axis moves, feedrate, etc for decimal places and units''' - if units == 'G21': #metric - fnum = '%.*f' % (dec, num) - else: #inch - fnum = '%.*f' % (dec, num/25.4) #since FreeCAD uses metric units internally + +def fmt(num, dec, units): + """used to format axis moves, feedrate, etc for decimal places and units""" + if units == "G21": # metric + fnum = "%.*f" % (dec, num) + else: # inch + fnum = "%.*f" % (dec, num / 25.4) # since FreeCAD uses metric units internally return fnum + def editor(gcode): - '''pops up a handy little editor to look at the code output ''' + """pops up a handy little editor to look at the code output""" prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path") # default Max Highlighter Size = 512 Ko defaultMHS = 512 * 1024 - mhs = prefs.GetUnsigned('inspecteditorMaxHighlighterSize', defaultMHS) + mhs = prefs.GetUnsigned("inspecteditorMaxHighlighterSize", defaultMHS) dia = GCodeEditorDialog() dia.editor.setText(gcode) gcodeSize = len(dia.editor.toPlainText()) - if (gcodeSize <= mhs): + if gcodeSize <= mhs: # because of poor performance, syntax highlighting is # limited to mhs octets (default 512 KB). # It seems than the response time curve has an inflexion near 500 KB # beyond 500 KB, the response time increases exponentially. dia.highlighter = GCodeHighlighter(dia.editor.document()) else: - FreeCAD.Console.PrintMessage(translate("Path", "GCode size too big ({} o), disabling syntax highlighter.".format(gcodeSize))) + FreeCAD.Console.PrintMessage( + translate( + "Path", + "GCode size too big ({} o), disabling syntax highlighter.".format( + gcodeSize + ), + ) + ) result = dia.exec_() if result: # If user selected 'OK' get modified G Code final = dia.editor.toPlainText() @@ -157,14 +184,12 @@ def editor(gcode): final = gcode return final -def fcoms(string,commentsym): - ''' filter and rebuild comments with user preferred comment symbol''' - if len(commentsym)==1: - s1 = string.replace('(', commentsym) - comment = s1.replace(')', '') + +def fcoms(string, commentsym): + """filter and rebuild comments with user preferred comment symbol""" + if len(commentsym) == 1: + s1 = string.replace("(", commentsym) + comment = s1.replace(")", "") else: return string return comment - - - diff --git a/src/Mod/Path/PathScripts/post/gcode_pre.py b/src/Mod/Path/PathScripts/post/gcode_pre.py index e3e2303b3f..0c9c3f35ed 100644 --- a/src/Mod/Path/PathScripts/post/gcode_pre.py +++ b/src/Mod/Path/PathScripts/post/gcode_pre.py @@ -22,7 +22,7 @@ # ***************************************************************************/ -''' +""" 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. @@ -40,7 +40,7 @@ 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 FreeCAD @@ -50,23 +50,23 @@ import re import PathScripts.PathCustom as PathCustom import PathScripts.PathCustomGui as PathCustomGui import PathScripts.PathOpGui as PathOpGui -from PySide import QtCore +from PySide.QtCore import QT_TRANSLATE_NOOP -# LEVEL = PathLog.Level.DEBUG -LEVEL = PathLog.Level.INFO -PathLog.setLevel(LEVEL, PathLog.thisModule()) -if LEVEL == PathLog.Level.DEBUG: +if False: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) # to distinguish python built-in open function from the one declared below -if open.__module__ in ['__builtin__', 'io']: +if open.__module__ in ["__builtin__", "io"]: pythonopen = open def open(filename): - "called when freecad opens a file." + """called when freecad opens a file.""" PathLog.track(filename) docname = os.path.splitext(os.path.basename(filename))[0] doc = FreeCAD.newDocument(docname) @@ -83,17 +83,17 @@ def matchToolController(op, toolnumber): def insert(filename, docname): - "called when freecad imports a file" + """called when freecad imports a file""" PathLog.track(filename) gfile = pythonopen(filename) gcode = gfile.read() gfile.close() # Regular expression to match tool changes in the format 'M6 Tn' - p = re.compile('[mM]+?\s?0?6\s?T\d*\s') + 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) + paths = re.split("([mM]+?\s?0?6\s?T\d*\s)", gcode) # iterate the gcode sections and add customs for each toolnumber = 0 @@ -103,7 +103,7 @@ def insert(filename, docname): # if the section is a tool change, extract the tool number m = p.match(path) if m: - toolnumber = int(m.group().split('T')[-1]) + toolnumber = int(m.group().split("T")[-1]) continue # Parse the gcode and throw away any empty lists @@ -113,10 +113,15 @@ def insert(filename, docname): # Create a custom and viewobject obj = PathCustom.Create("Custom") - res = PathOpGui.CommandResources('Custom', PathCustom.Create, - PathCustomGui.TaskPanelOpPage, - 'Path_Custom', - QtCore.QT_TRANSLATE_NOOP('Path_Custom', 'Custom'), '', '') + res = PathOpGui.CommandResources( + "Custom", + PathCustom.Create, + PathCustomGui.TaskPanelOpPage, + "Path_Custom", + QT_TRANSLATE_NOOP("Path_Custom", "Custom"), + "", + "", + ) obj.ViewObject.Proxy = PathOpGui.ViewProvider(obj.ViewObject, res) obj.ViewObject.Proxy.setDeleteObjectsOnReject(False) @@ -127,20 +132,28 @@ def insert(filename, docname): 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'] + 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...") + FreeCAD.Console.PrintMessage("preprocessing...\n") PathLog.track(inputstring) # split the input by line lines = inputstring.splitlines() @@ -178,7 +191,7 @@ def parse(inputstring): elif currcommand[0] in axis and lastcommand: output.append(lastcommand + " " + lin) - print("done preprocessing.") + FreeCAD.Console.PrintMessage("done preprocessing.\n") return output diff --git a/src/Mod/Path/PathScripts/post/grbl_post.py b/src/Mod/Path/PathScripts/post/grbl_post.py index 08cabb85b1..66579c6b8e 100755 --- a/src/Mod/Path/PathScripts/post/grbl_post.py +++ b/src/Mod/Path/PathScripts/post/grbl_post.py @@ -33,11 +33,11 @@ import shlex import PathScripts.PathUtil as PathUtil -TOOLTIP = ''' +TOOLTIP = """ Generate g-code from a Path that is compatible with the grbl controller. import grbl_post grbl_post.export(object, "/path/to/file.ncc") -''' +""" # *************************************************************************** @@ -45,71 +45,133 @@ grbl_post.export(object, "/path/to/file.ncc") # *************************************************************************** # Default values for command line arguments: -OUTPUT_COMMENTS = True # default output of comments in output gCode file -OUTPUT_HEADER = True # default output header in output gCode file -OUTPUT_LINE_NUMBERS = False # default doesn't output line numbers in output gCode file -OUTPUT_BCNC = False # default doesn't add bCNC operation block headers in output gCode file -SHOW_EDITOR = True # default show the resulting file dialog output in GUI -PRECISION = 3 # Default precision for metric (see http://linuxcnc.org/docs/2.7/html/gcode/overview.html#_g_code_best_practices) -TRANSLATE_DRILL_CYCLES = False # If true, G81, G82 & G83 are translated in G0/G1 moves -PREAMBLE = '''G17 G90 -''' # default preamble text will appear at the beginning of the gCode output file. -POSTAMBLE = '''M5 +OUTPUT_COMMENTS = True # default output of comments in output gCode file +OUTPUT_HEADER = True # default output header in output gCode file +OUTPUT_LINE_NUMBERS = False # default doesn't output line numbers in output gCode file +OUTPUT_BCNC = ( + False # default doesn't add bCNC operation block headers in output gCode file +) +SHOW_EDITOR = True # default show the resulting file dialog output in GUI +PRECISION = 3 # Default precision for metric (see http://linuxcnc.org/docs/2.7/html/gcode/overview.html#_g_code_best_practices) +TRANSLATE_DRILL_CYCLES = False # If true, G81, G82 & G83 are translated in G0/G1 moves +PREAMBLE = """G17 G90 +""" # default preamble text will appear at the beginning of the gCode output file. +POSTAMBLE = """M5 G17 G90 M2 -''' # default postamble text will appear following the last operation. +""" # default postamble text will appear following the last operation. -SPINDLE_WAIT = 0 # no waiting after M3 / M4 by default -RETURN_TO = None # no movements after end of program +SPINDLE_WAIT = 0 # no waiting after M3 / M4 by default +RETURN_TO = None # no movements after end of program # Customisation with no command line argument -MODAL = False # if true commands are suppressed if the same as previous line. -LINENR = 100 # line number starting value -LINEINCR = 10 # line number increment -OUTPUT_TOOL_CHANGE = False # default don't output M6 tool changes (comment it) as grbl currently does not handle it -DRILL_RETRACT_MODE = 'G98' # Default value of drill retractations (CURRENT_Z) other possible value is G99 -MOTION_MODE = 'G90' # G90 for absolute moves, G91 for relative -UNITS = 'G21' # G21 for metric, G20 for us standard -UNIT_FORMAT = 'mm' -UNIT_SPEED_FORMAT = 'mm/min' -PRE_OPERATION = '''''' # Pre operation text will be inserted before every operation -POST_OPERATION = '''''' # Post operation text will be inserted after every operation -TOOL_CHANGE = '''''' # Tool Change commands will be inserted before a tool change +MODAL = False # if true commands are suppressed if the same as previous line. +LINENR = 100 # line number starting value +LINEINCR = 10 # line number increment +OUTPUT_TOOL_CHANGE = False # default don't output M6 tool changes (comment it) as grbl currently does not handle it +DRILL_RETRACT_MODE = "G98" # Default value of drill retractations (CURRENT_Z) other possible value is G99 +MOTION_MODE = "G90" # G90 for absolute moves, G91 for relative +UNITS = "G21" # G21 for metric, G20 for us standard +UNIT_FORMAT = "mm" +UNIT_SPEED_FORMAT = "mm/min" +PRE_OPERATION = """""" # Pre operation text will be inserted before every operation +POST_OPERATION = """""" # Post operation text will be inserted after every operation +TOOL_CHANGE = """""" # Tool Change commands will be inserted before a tool change # *************************************************************************** # * End of customization # *************************************************************************** # Parser arguments list & definition -parser = argparse.ArgumentParser(prog='grbl', add_help=False) -parser.add_argument('--comments', action='store_true', help='output comment (default)') -parser.add_argument('--no-comments', action='store_true', help='suppress comment output') -parser.add_argument('--header', action='store_true', help='output headers (default)') -parser.add_argument('--no-header', action='store_true', help='suppress header output') -parser.add_argument('--line-numbers', action='store_true', help='prefix with line numbers') -parser.add_argument('--no-line-numbers', action='store_true', help='don\'t prefix with line numbers (default)') -parser.add_argument('--show-editor', action='store_true', help='pop up editor before writing output (default)') -parser.add_argument('--no-show-editor', action='store_true', help='don\'t pop up editor before writing output') -parser.add_argument('--precision', default='3', help='number of digits of precision, default=3') -parser.add_argument('--translate_drill', action='store_true', help='translate drill cycles G81, G82 & G83 in G0/G1 movements') -parser.add_argument('--no-translate_drill', action='store_true', help='don\'t translate drill cycles G81, G82 & G83 in G0/G1 movements (default)') -parser.add_argument('--preamble', help='set commands to be issued before the first command, default="G17 G90"') -parser.add_argument('--postamble', help='set commands to be issued after the last command, default="M5\nG17 G90\n;M2"') -parser.add_argument('--inches', action='store_true', help='Convert output for US imperial mode (G20)') -parser.add_argument('--tool-change', action='store_true', help='Insert M6 for all tool changes') -parser.add_argument('--wait-for-spindle', type=int, default=0, help='Wait for spindle to reach desired speed after M3 / M4, default=0') -parser.add_argument('--return-to', default='', help='Move to the specified coordinates at the end, e.g. --return-to=0,0') -parser.add_argument('--bcnc', action='store_true', help='Add Job operations as bCNC block headers. Consider suppressing existing comments: Add argument --no-comments') -parser.add_argument('--no-bcnc', action='store_true', help='suppress bCNC block header output (default)') +parser = argparse.ArgumentParser(prog="grbl", add_help=False) +parser.add_argument("--comments", action="store_true", help="output comment (default)") +parser.add_argument( + "--no-comments", action="store_true", help="suppress comment output" +) +parser.add_argument("--header", action="store_true", help="output headers (default)") +parser.add_argument("--no-header", action="store_true", help="suppress header output") +parser.add_argument( + "--line-numbers", action="store_true", help="prefix with line numbers" +) +parser.add_argument( + "--no-line-numbers", + action="store_true", + help="don't prefix with line numbers (default)", +) +parser.add_argument( + "--show-editor", + action="store_true", + help="pop up editor before writing output (default)", +) +parser.add_argument( + "--no-show-editor", + action="store_true", + help="don't pop up editor before writing output", +) +parser.add_argument( + "--precision", default="3", help="number of digits of precision, default=3" +) +parser.add_argument( + "--translate_drill", + action="store_true", + help="translate drill cycles G81, G82 & G83 in G0/G1 movements", +) +parser.add_argument( + "--no-translate_drill", + action="store_true", + help="don't translate drill cycles G81, G82 & G83 in G0/G1 movements (default)", +) +parser.add_argument( + "--preamble", + help='set commands to be issued before the first command, default="G17 G90"', +) +parser.add_argument( + "--postamble", + help='set commands to be issued after the last command, default="M5\nG17 G90\n;M2"', +) +parser.add_argument( + "--inches", action="store_true", help="Convert output for US imperial mode (G20)" +) +parser.add_argument( + "--tool-change", action="store_true", help="Insert M6 for all tool changes" +) +parser.add_argument( + "--wait-for-spindle", + type=int, + default=0, + help="Wait for spindle to reach desired speed after M3 / M4, default=0", +) +parser.add_argument( + "--return-to", + default="", + help="Move to the specified coordinates at the end, e.g. --return-to=0,0", +) +parser.add_argument( + "--bcnc", + action="store_true", + help="Add Job operations as bCNC block headers. Consider suppressing existing comments: Add argument --no-comments", +) +parser.add_argument( + "--no-bcnc", action="store_true", help="suppress bCNC block header output (default)" +) TOOLTIP_ARGS = parser.format_help() # *************************************************************************** # * Internal global variables # *************************************************************************** -MOTION_COMMANDS = ['G0', 'G00', 'G1', 'G01', 'G2', 'G02', 'G3', 'G03'] # Motion gCode commands definition -RAPID_MOVES = ['G0', 'G00'] # Rapid moves gCode commands definition -SUPPRESS_COMMANDS = [] # These commands are ignored by commenting them out +MOTION_COMMANDS = [ + "G0", + "G00", + "G1", + "G01", + "G2", + "G02", + "G3", + "G03", +] # Motion gCode commands definition +RAPID_MOVES = ["G0", "G00"] # Rapid moves gCode commands definition +SUPPRESS_COMMANDS = [] # These commands are ignored by commenting them out COMMAND_SPACE = " " # Global variables storing current position CURRENT_X = 0 @@ -119,455 +181,549 @@ CURRENT_Z = 0 # *************************************************************************** # * to distinguish python built-in open function from the one declared below -if open.__module__ in ['__builtin__', 'io']: - pythonopen = open +if open.__module__ in ["__builtin__", "io"]: + pythonopen = open def processArguments(argstring): - global OUTPUT_HEADER - global OUTPUT_COMMENTS - global OUTPUT_LINE_NUMBERS - global SHOW_EDITOR - global PRECISION - global PREAMBLE - global POSTAMBLE - global UNITS - global UNIT_SPEED_FORMAT - global UNIT_FORMAT - global TRANSLATE_DRILL_CYCLES - global OUTPUT_TOOL_CHANGE - global SPINDLE_WAIT - global RETURN_TO - global OUTPUT_BCNC + global OUTPUT_HEADER + global OUTPUT_COMMENTS + global OUTPUT_LINE_NUMBERS + global SHOW_EDITOR + global PRECISION + global PREAMBLE + global POSTAMBLE + global UNITS + global UNIT_SPEED_FORMAT + global UNIT_FORMAT + global TRANSLATE_DRILL_CYCLES + global OUTPUT_TOOL_CHANGE + global SPINDLE_WAIT + global RETURN_TO + global OUTPUT_BCNC - try: - args = parser.parse_args(shlex.split(argstring)) - if args.no_header: - OUTPUT_HEADER = False - if args.header: - OUTPUT_HEADER = True - if args.no_comments: - OUTPUT_COMMENTS = False - if args.comments: - OUTPUT_COMMENTS = True - if args.no_line_numbers: - OUTPUT_LINE_NUMBERS = False - if args.line_numbers: - OUTPUT_LINE_NUMBERS = True - if args.no_show_editor: - SHOW_EDITOR = False - if args.show_editor: - SHOW_EDITOR = True - PRECISION = args.precision - if args.preamble is not None: - PREAMBLE = args.preamble - if args.postamble is not None: - POSTAMBLE = args.postamble - if args.no_translate_drill: - TRANSLATE_DRILL_CYCLES = False - if args.translate_drill: - TRANSLATE_DRILL_CYCLES = True - if args.inches: - UNITS = 'G20' - UNIT_SPEED_FORMAT = 'in/min' - UNIT_FORMAT = 'in' - PRECISION = 4 - if args.tool_change: - OUTPUT_TOOL_CHANGE = True - if args.wait_for_spindle > 0: - SPINDLE_WAIT = args.wait_for_spindle - if args.return_to != '': - RETURN_TO = [int(v) for v in args.return_to.split(',')] - if len(RETURN_TO) != 2: - RETURN_TO = None - print("--return-to coordinates must be specified as ,, ignoring") - if args.bcnc: - OUTPUT_BCNC = True - if args.no_bcnc: - OUTPUT_BCNC = False + try: + args = parser.parse_args(shlex.split(argstring)) + if args.no_header: + OUTPUT_HEADER = False + if args.header: + OUTPUT_HEADER = True + if args.no_comments: + OUTPUT_COMMENTS = False + if args.comments: + OUTPUT_COMMENTS = True + if args.no_line_numbers: + OUTPUT_LINE_NUMBERS = False + if args.line_numbers: + OUTPUT_LINE_NUMBERS = True + if args.no_show_editor: + SHOW_EDITOR = False + if args.show_editor: + SHOW_EDITOR = True + PRECISION = args.precision + if args.preamble is not None: + PREAMBLE = args.preamble + if args.postamble is not None: + POSTAMBLE = args.postamble + if args.no_translate_drill: + TRANSLATE_DRILL_CYCLES = False + if args.translate_drill: + TRANSLATE_DRILL_CYCLES = True + if args.inches: + UNITS = "G20" + UNIT_SPEED_FORMAT = "in/min" + UNIT_FORMAT = "in" + PRECISION = 4 + if args.tool_change: + OUTPUT_TOOL_CHANGE = True + if args.wait_for_spindle > 0: + SPINDLE_WAIT = args.wait_for_spindle + if args.return_to != "": + RETURN_TO = [int(v) for v in args.return_to.split(",")] + if len(RETURN_TO) != 2: + RETURN_TO = None + print("--return-to coordinates must be specified as ,, ignoring") + if args.bcnc: + OUTPUT_BCNC = True + if args.no_bcnc: + OUTPUT_BCNC = False + except Exception as e: + return False - except Exception as e: - return False - - return True + return True # For debug... def dump(obj): - for attr in dir(obj): - print("obj.%s = %s" % (attr, getattr(obj, attr))) + for attr in dir(obj): + print("obj.%s = %s" % (attr, getattr(obj, attr))) def export(objectslist, filename, argstring): - if not processArguments(argstring): - return None + if not processArguments(argstring): + return None - global UNITS - global UNIT_FORMAT - global UNIT_SPEED_FORMAT - global MOTION_MODE - global SUPPRESS_COMMANDS + global UNITS + global UNIT_FORMAT + global UNIT_SPEED_FORMAT + global MOTION_MODE + global SUPPRESS_COMMANDS - print("Post Processor: " + __name__ + " postprocessing...") - gcode = "" + print("Post Processor: " + __name__ + " postprocessing...") + gcode = "" - # write header - if OUTPUT_HEADER: - gcode += linenumber() + "(Exported by FreeCAD)\n" - gcode += linenumber() + "(Post Processor: " + __name__ + ")\n" - gcode += linenumber() + "(Output Time:" + str(datetime.datetime.now()) + ")\n" + # write header + if OUTPUT_HEADER: + gcode += linenumber() + "(Exported by FreeCAD)\n" + gcode += linenumber() + "(Post Processor: " + __name__ + ")\n" + gcode += linenumber() + "(Output Time:" + str(datetime.datetime.now()) + ")\n" - # Check canned cycles for drilling - if TRANSLATE_DRILL_CYCLES: - if len(SUPPRESS_COMMANDS) == 0: - SUPPRESS_COMMANDS = ['G99', 'G98', 'G80'] - else: - SUPPRESS_COMMANDS += ['G99', 'G98', 'G80'] - - # Write the preamble - if OUTPUT_COMMENTS: - gcode += linenumber() + "(Begin preamble)\n" - for line in PREAMBLE.splitlines(True): - gcode += linenumber() + line - # verify if PREAMBLE have changed MOTION_MODE or UNITS - if 'G90' in PREAMBLE: - MOTION_MODE = 'G90' - elif 'G91' in PREAMBLE: - MOTION_MODE = 'G91' - else: - gcode += linenumber() + MOTION_MODE + "\n" - if 'G21' in PREAMBLE: - UNITS = 'G21' - UNIT_FORMAT = 'mm' - UNIT_SPEED_FORMAT = 'mm/min' - elif 'G20' in PREAMBLE: - UNITS = 'G20' - UNIT_FORMAT = 'in' - UNIT_SPEED_FORMAT = 'in/min' - else: - gcode += linenumber() + UNITS + "\n" - - for obj in objectslist: - # Debug... - # print("\n" + "*"*70) - # dump(obj) - # print("*"*70 + "\n") - if not hasattr(obj, "Path"): - print("The object " + obj.Name + " is not a path. Please select only path and Compounds.") - return - - # Skip inactive operations - if PathUtil.opProperty(obj, 'Active') is False: - continue - - # do the pre_op - if OUTPUT_BCNC: - gcode += linenumber() + "(Block-name: " + obj.Label + ")\n" - gcode += linenumber() + "(Block-expand: 0)\n" - gcode += linenumber() + "(Block-enable: 1)\n" - if OUTPUT_COMMENTS: - gcode += linenumber() + "(Begin operation: " + obj.Label + ")\n" - for line in PRE_OPERATION.splitlines(True): - gcode += linenumber() + line - - # get coolant mode - coolantMode = 'None' - if hasattr(obj, "CoolantMode") or hasattr(obj, 'Base') and hasattr(obj.Base, "CoolantMode"): - if hasattr(obj, "CoolantMode"): - coolantMode = obj.CoolantMode + # Check canned cycles for drilling + if TRANSLATE_DRILL_CYCLES: + if len(SUPPRESS_COMMANDS) == 0: + SUPPRESS_COMMANDS = ["G99", "G98", "G80"] else: - coolantMode = obj.Base.CoolantMode + SUPPRESS_COMMANDS += ["G99", "G98", "G80"] - # turn coolant on if required + # Write the preamble if OUTPUT_COMMENTS: - if not coolantMode == 'None': - gcode += linenumber() + '(Coolant On:' + coolantMode + ')\n' - if coolantMode == 'Flood': - gcode += linenumber() + 'M8' + '\n' - if coolantMode == 'Mist': - gcode += linenumber() + 'M7' + '\n' - - # Parse the op - gcode += parse(obj) - - # do the post_op - if OUTPUT_COMMENTS: - gcode += linenumber() + "(Finish operation: " + obj.Label + ")\n" - for line in POST_OPERATION.splitlines(True): - gcode += linenumber() + line - - # turn coolant off if required - if not coolantMode == 'None': - if OUTPUT_COMMENTS: - gcode += linenumber() + '(Coolant Off:' + coolantMode + ')\n' - gcode += linenumber() +'M9' + '\n' - - if RETURN_TO: - gcode += linenumber() + "G0 X%s Y%s\n" % tuple(RETURN_TO) - - # do the post_amble - if OUTPUT_BCNC: - gcode += linenumber() + "(Block-name: post_amble)\n" - gcode += linenumber() + "(Block-expand: 0)\n" - gcode += linenumber() + "(Block-enable: 1)\n" - if OUTPUT_COMMENTS: - gcode += linenumber() + "(Begin postamble)\n" - for line in POSTAMBLE.splitlines(True): - gcode += linenumber() + line - - # show the gCode result dialog - if FreeCAD.GuiUp and SHOW_EDITOR: - dia = PostUtils.GCodeEditorDialog() - dia.editor.setText(gcode) - result = dia.exec_() - if result: - final = dia.editor.toPlainText() + gcode += linenumber() + "(Begin preamble)\n" + for line in PREAMBLE.splitlines(True): + gcode += linenumber() + line + # verify if PREAMBLE have changed MOTION_MODE or UNITS + if "G90" in PREAMBLE: + MOTION_MODE = "G90" + elif "G91" in PREAMBLE: + MOTION_MODE = "G91" else: - final = gcode - else: - final = gcode + gcode += linenumber() + MOTION_MODE + "\n" + if "G21" in PREAMBLE: + UNITS = "G21" + UNIT_FORMAT = "mm" + UNIT_SPEED_FORMAT = "mm/min" + elif "G20" in PREAMBLE: + UNITS = "G20" + UNIT_FORMAT = "in" + UNIT_SPEED_FORMAT = "in/min" + else: + gcode += linenumber() + UNITS + "\n" - print("Done postprocessing.") + for obj in objectslist: + # Debug... + # print("\n" + "*"*70) + # dump(obj) + # print("*"*70 + "\n") + if not hasattr(obj, "Path"): + print( + "The object " + + obj.Name + + " is not a path. Please select only path and Compounds." + ) + return - # write the file - gfile = pythonopen(filename, "w") - gfile.write(final) - gfile.close() + # Skip inactive operations + if PathUtil.opProperty(obj, "Active") is False: + continue + + # do the pre_op + if OUTPUT_BCNC: + gcode += linenumber() + "(Block-name: " + obj.Label + ")\n" + gcode += linenumber() + "(Block-expand: 0)\n" + gcode += linenumber() + "(Block-enable: 1)\n" + if OUTPUT_COMMENTS: + gcode += linenumber() + "(Begin operation: " + obj.Label + ")\n" + for line in PRE_OPERATION.splitlines(True): + gcode += linenumber() + line + + # get coolant mode + coolantMode = "None" + if ( + hasattr(obj, "CoolantMode") + or hasattr(obj, "Base") + and hasattr(obj.Base, "CoolantMode") + ): + if hasattr(obj, "CoolantMode"): + coolantMode = obj.CoolantMode + else: + coolantMode = obj.Base.CoolantMode + + # turn coolant on if required + if OUTPUT_COMMENTS: + if not coolantMode == "None": + gcode += linenumber() + "(Coolant On:" + coolantMode + ")\n" + if coolantMode == "Flood": + gcode += linenumber() + "M8" + "\n" + if coolantMode == "Mist": + gcode += linenumber() + "M7" + "\n" + + # Parse the op + gcode += parse(obj) + + # do the post_op + if OUTPUT_COMMENTS: + gcode += linenumber() + "(Finish operation: " + obj.Label + ")\n" + for line in POST_OPERATION.splitlines(True): + gcode += linenumber() + line + + # turn coolant off if required + if not coolantMode == "None": + if OUTPUT_COMMENTS: + gcode += linenumber() + "(Coolant Off:" + coolantMode + ")\n" + gcode += linenumber() + "M9" + "\n" + + if RETURN_TO: + gcode += linenumber() + "G0 X%s Y%s\n" % tuple(RETURN_TO) + + # do the post_amble + if OUTPUT_BCNC: + gcode += linenumber() + "(Block-name: post_amble)\n" + gcode += linenumber() + "(Block-expand: 0)\n" + gcode += linenumber() + "(Block-enable: 1)\n" + if OUTPUT_COMMENTS: + gcode += linenumber() + "(Begin postamble)\n" + for line in POSTAMBLE.splitlines(True): + gcode += linenumber() + line + + # show the gCode result dialog + if FreeCAD.GuiUp and SHOW_EDITOR: + dia = PostUtils.GCodeEditorDialog() + dia.editor.setText(gcode) + result = dia.exec_() + if result: + final = dia.editor.toPlainText() + else: + final = gcode + else: + final = gcode + + print("Done postprocessing.") + + # write the file + gfile = pythonopen(filename, "w") + gfile.write(final) + gfile.close() def linenumber(): - if not OUTPUT_LINE_NUMBERS: - return "" - global LINENR - global LINEINCR - s = "N" + str(LINENR) + " " - LINENR += LINEINCR - return s + if not OUTPUT_LINE_NUMBERS: + return "" + global LINENR + global LINEINCR + s = "N" + str(LINENR) + " " + LINENR += LINEINCR + return s def format_outstring(strTable): - global COMMAND_SPACE - # construct the line for the final output - s = "" - for w in strTable: - s += w + COMMAND_SPACE - s = s.strip() - return s + global COMMAND_SPACE + # construct the line for the final output + s = "" + for w in strTable: + s += w + COMMAND_SPACE + s = s.strip() + return s def parse(pathobj): - global DRILL_RETRACT_MODE - global MOTION_MODE - global CURRENT_X - global CURRENT_Y - global CURRENT_Z + global DRILL_RETRACT_MODE + global MOTION_MODE + global CURRENT_X + global CURRENT_Y + global CURRENT_Z - out = "" - lastcommand = None - precision_string = '.' + str(PRECISION) + 'f' + out = "" + lastcommand = None + precision_string = "." + str(PRECISION) + "f" - params = ['X', 'Y', 'Z', 'A', 'B', 'C', 'U', 'V', 'W', 'I', 'J', 'K', 'F', 'S', 'T', 'Q', 'R', 'L', 'P'] + params = [ + "X", + "Y", + "Z", + "A", + "B", + "C", + "U", + "V", + "W", + "I", + "J", + "K", + "F", + "S", + "T", + "Q", + "R", + "L", + "P", + ] - if hasattr(pathobj, "Group"): # We have a compound or project. - if OUTPUT_COMMENTS: - out += linenumber() + "(Compound: " + pathobj.Label + ")\n" - for p in pathobj.Group: - out += parse(p) - return out - - else: # parsing simple path - if not hasattr(pathobj, "Path"): # groups might contain non-path things like stock. - return out - - if OUTPUT_COMMENTS: - out += linenumber() + "(Path: " + pathobj.Label + ")\n" - - for c in pathobj.Path.Commands: - outstring = [] - command = c.Name - - outstring.append(command) - - # if modal: only print the command if it is not the same as the last one - if MODAL: - if command == lastcommand: - outstring.pop(0) - - # Now add the remaining parameters in order - for param in params: - if param in c.Parameters: - if param == 'F': - if command not in RAPID_MOVES: - speed = Units.Quantity(c.Parameters['F'], FreeCAD.Units.Velocity) - if speed.getValueAs(UNIT_SPEED_FORMAT) > 0.0: - outstring.append(param + format(float(speed.getValueAs(UNIT_SPEED_FORMAT)), precision_string)) - elif param in ['T', 'H', 'D', 'S', 'P', 'L']: - outstring.append(param + str(c.Parameters[param])) - elif param in ['A', 'B', 'C']: - outstring.append(param + format(c.Parameters[param], precision_string)) - else: # [X, Y, Z, U, V, W, I, J, K, R, Q] (Conversion eventuelle mm/inches) - pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length) - outstring.append(param + format(float(pos.getValueAs(UNIT_FORMAT)), precision_string)) - - # store the latest command - lastcommand = command - - # Memorizes the current position for calculating the related movements and the withdrawal plan - if command in MOTION_COMMANDS: - if 'X' in c.Parameters: - CURRENT_X = Units.Quantity(c.Parameters['X'], FreeCAD.Units.Length) - if 'Y' in c.Parameters: - CURRENT_Y = Units.Quantity(c.Parameters['Y'], FreeCAD.Units.Length) - if 'Z' in c.Parameters: - CURRENT_Z = Units.Quantity(c.Parameters['Z'], FreeCAD.Units.Length) - - if command in ('G98', 'G99'): - DRILL_RETRACT_MODE = command - - if command in ('G90', 'G91'): - MOTION_MODE = command - - - if TRANSLATE_DRILL_CYCLES: - if command in ('G81', 'G82', 'G83'): - out += drill_translate(outstring, command, c.Parameters) - # Erase the line we just translated - outstring = [] - - if SPINDLE_WAIT > 0: - if command in ('M3', 'M03', 'M4', 'M04'): - out += linenumber() + format_outstring(outstring) + "\n" - out += linenumber() + format_outstring(['G4', 'P%s' % SPINDLE_WAIT]) + "\n" - outstring = [] - - # Check for Tool Change: - if command in ('M6', 'M06'): + if hasattr(pathobj, "Group"): # We have a compound or project. if OUTPUT_COMMENTS: - out += linenumber() + "(Begin toolchange)\n" - if not OUTPUT_TOOL_CHANGE: - outstring.insert(0, "(" ) - outstring.append( ")" ) - else: - for line in TOOL_CHANGE.splitlines(True): - out += linenumber() + line + out += linenumber() + "(Compound: " + pathobj.Label + ")\n" + for p in pathobj.Group: + out += parse(p) + return out - if command == "message": - if OUTPUT_COMMENTS is False: - out = [] - else: - outstring.pop(0) # remove the command + else: # parsing simple path + if not hasattr( + pathobj, "Path" + ): # groups might contain non-path things like stock. + return out - if command in SUPPRESS_COMMANDS: - outstring.insert(0, "(" ) - outstring.append( ")" ) + if OUTPUT_COMMENTS: + out += linenumber() + "(Path: " + pathobj.Label + ")\n" - # prepend a line number and append a newline - if len(outstring) >= 1: - out += linenumber() + format_outstring(outstring) + "\n" + for c in pathobj.Path.Commands: + outstring = [] + command = c.Name - return out + outstring.append(command) + + # if modal: only print the command if it is not the same as the last one + if MODAL: + if command == lastcommand: + outstring.pop(0) + + # Now add the remaining parameters in order + for param in params: + if param in c.Parameters: + if param == "F": + if command not in RAPID_MOVES: + speed = Units.Quantity( + c.Parameters["F"], FreeCAD.Units.Velocity + ) + if speed.getValueAs(UNIT_SPEED_FORMAT) > 0.0: + outstring.append( + param + + format( + float(speed.getValueAs(UNIT_SPEED_FORMAT)), + precision_string, + ) + ) + elif param in ["T", "H", "D", "S", "P", "L"]: + outstring.append(param + str(c.Parameters[param])) + elif param in ["A", "B", "C"]: + outstring.append( + param + format(c.Parameters[param], precision_string) + ) + else: # [X, Y, Z, U, V, W, I, J, K, R, Q] (Conversion eventuelle mm/inches) + pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length) + outstring.append( + param + + format( + float(pos.getValueAs(UNIT_FORMAT)), precision_string + ) + ) + + # store the latest command + lastcommand = command + + # Memorizes the current position for calculating the related movements and the withdrawal plan + if command in MOTION_COMMANDS: + if "X" in c.Parameters: + CURRENT_X = Units.Quantity(c.Parameters["X"], FreeCAD.Units.Length) + if "Y" in c.Parameters: + CURRENT_Y = Units.Quantity(c.Parameters["Y"], FreeCAD.Units.Length) + if "Z" in c.Parameters: + CURRENT_Z = Units.Quantity(c.Parameters["Z"], FreeCAD.Units.Length) + + if command in ("G98", "G99"): + DRILL_RETRACT_MODE = command + + if command in ("G90", "G91"): + MOTION_MODE = command + + if TRANSLATE_DRILL_CYCLES: + if command in ("G81", "G82", "G83"): + out += drill_translate(outstring, command, c.Parameters) + # Erase the line we just translated + outstring = [] + + if SPINDLE_WAIT > 0: + if command in ("M3", "M03", "M4", "M04"): + out += linenumber() + format_outstring(outstring) + "\n" + out += ( + linenumber() + + format_outstring(["G4", "P%s" % SPINDLE_WAIT]) + + "\n" + ) + outstring = [] + + # Check for Tool Change: + if command in ("M6", "M06"): + if OUTPUT_COMMENTS: + out += linenumber() + "(Begin toolchange)\n" + if not OUTPUT_TOOL_CHANGE: + outstring.insert(0, "(") + outstring.append(")") + else: + for line in TOOL_CHANGE.splitlines(True): + out += linenumber() + line + + if command == "message": + if OUTPUT_COMMENTS is False: + out = [] + else: + outstring.pop(0) # remove the command + + if command in SUPPRESS_COMMANDS: + outstring.insert(0, "(") + outstring.append(")") + + # prepend a line number and append a newline + if len(outstring) >= 1: + out += linenumber() + format_outstring(outstring) + "\n" + + return out def drill_translate(outstring, cmd, params): - global DRILL_RETRACT_MODE - global MOTION_MODE - global CURRENT_X - global CURRENT_Y - global CURRENT_Z - global UNITS - global UNIT_FORMAT - global UNIT_SPEED_FORMAT + global DRILL_RETRACT_MODE + global MOTION_MODE + global CURRENT_X + global CURRENT_Y + global CURRENT_Z + global UNITS + global UNIT_FORMAT + global UNIT_SPEED_FORMAT - strFormat = '.' + str(PRECISION) + 'f' + strFormat = "." + str(PRECISION) + "f" - trBuff = "" + trBuff = "" - if OUTPUT_COMMENTS: # Comment the original command - outstring[0] = "(" + outstring[0] - outstring[-1] = outstring[-1] + ")" - trBuff += linenumber() + format_outstring(outstring) + "\n" + if OUTPUT_COMMENTS: # Comment the original command + outstring[0] = "(" + outstring[0] + outstring[-1] = outstring[-1] + ")" + trBuff += linenumber() + format_outstring(outstring) + "\n" + + # cycle conversion + # currently only cycles in XY are provided (G17) + # other plains ZX (G18) and YZ (G19) are not dealt with : Z drilling only. + drill_X = Units.Quantity(params["X"], FreeCAD.Units.Length) + drill_Y = Units.Quantity(params["Y"], FreeCAD.Units.Length) + drill_Z = Units.Quantity(params["Z"], FreeCAD.Units.Length) + RETRACT_Z = Units.Quantity(params["R"], FreeCAD.Units.Length) + # R less than Z is error + if RETRACT_Z < drill_Z: + trBuff += linenumber() + "(drill cycle error: R less than Z )\n" + return trBuff + + if MOTION_MODE == "G91": # G91 relative movements + drill_X += CURRENT_X + drill_Y += CURRENT_Y + drill_Z += CURRENT_Z + RETRACT_Z += CURRENT_Z + + if DRILL_RETRACT_MODE == "G98" and CURRENT_Z >= RETRACT_Z: + RETRACT_Z = CURRENT_Z + + # get the other parameters + drill_feedrate = Units.Quantity(params["F"], FreeCAD.Units.Velocity) + if cmd == "G83": + drill_Step = Units.Quantity(params["Q"], FreeCAD.Units.Length) + a_bit = ( + drill_Step * 0.05 + ) # NIST 3.5.16.4 G83 Cycle: "current hole bottom, backed off a bit." + elif cmd == "G82": + drill_DwellTime = params["P"] + + # wrap this block to ensure machine MOTION_MODE is restored in case of error + try: + if MOTION_MODE == "G91": + trBuff += linenumber() + "G90\n" # force absolute coordinates during cycles + + strG0_RETRACT_Z = ( + "G0 Z" + format(float(RETRACT_Z.getValueAs(UNIT_FORMAT)), strFormat) + "\n" + ) + strF_Feedrate = ( + " F" + + format(float(drill_feedrate.getValueAs(UNIT_SPEED_FORMAT)), ".2f") + + "\n" + ) + print(strF_Feedrate) + + # preliminary mouvement(s) + if CURRENT_Z < RETRACT_Z: + trBuff += linenumber() + strG0_RETRACT_Z + trBuff += ( + linenumber() + + "G0 X" + + format(float(drill_X.getValueAs(UNIT_FORMAT)), strFormat) + + " Y" + + format(float(drill_Y.getValueAs(UNIT_FORMAT)), strFormat) + + "\n" + ) + if CURRENT_Z > RETRACT_Z: + # NIST GCODE 3.5.16.1 Preliminary and In-Between Motion says G0 to RETRACT_Z. Here use G1 since retract height may be below surface ! + trBuff += ( + linenumber() + + "G1 Z" + + format(float(RETRACT_Z.getValueAs(UNIT_FORMAT)), strFormat) + + strF_Feedrate + ) + last_Stop_Z = RETRACT_Z + + # drill moves + if cmd in ("G81", "G82"): + trBuff += ( + linenumber() + + "G1 Z" + + format(float(drill_Z.getValueAs(UNIT_FORMAT)), strFormat) + + strF_Feedrate + ) + # pause where applicable + if cmd == "G82": + trBuff += linenumber() + "G4 P" + str(drill_DwellTime) + "\n" + trBuff += linenumber() + strG0_RETRACT_Z + else: # 'G83' + if params["Q"] != 0: + while 1: + if last_Stop_Z != RETRACT_Z: + clearance_depth = ( + last_Stop_Z + a_bit + ) # rapid move to just short of last drilling depth + trBuff += ( + linenumber() + + "G0 Z" + + format( + float(clearance_depth.getValueAs(UNIT_FORMAT)), + strFormat, + ) + + "\n" + ) + next_Stop_Z = last_Stop_Z - drill_Step + if next_Stop_Z > drill_Z: + trBuff += ( + linenumber() + + "G1 Z" + + format( + float(next_Stop_Z.getValueAs(UNIT_FORMAT)), strFormat + ) + + strF_Feedrate + ) + trBuff += linenumber() + strG0_RETRACT_Z + last_Stop_Z = next_Stop_Z + else: + trBuff += ( + linenumber() + + "G1 Z" + + format(float(drill_Z.getValueAs(UNIT_FORMAT)), strFormat) + + strF_Feedrate + ) + trBuff += linenumber() + strG0_RETRACT_Z + break + + except Exception as e: + pass + + if MOTION_MODE == "G91": + trBuff += linenumber() + "G91" # Restore if changed - # cycle conversion - # currently only cycles in XY are provided (G17) - # other plains ZX (G18) and YZ (G19) are not dealt with : Z drilling only. - drill_X = Units.Quantity(params['X'], FreeCAD.Units.Length) - drill_Y = Units.Quantity(params['Y'], FreeCAD.Units.Length) - drill_Z = Units.Quantity(params['Z'], FreeCAD.Units.Length) - RETRACT_Z = Units.Quantity(params['R'], FreeCAD.Units.Length) - # R less than Z is error - if RETRACT_Z < drill_Z : - trBuff += linenumber() + "(drill cycle error: R less than Z )\n" return trBuff - if MOTION_MODE == 'G91': # G91 relative movements - drill_X += CURRENT_X - drill_Y += CURRENT_Y - drill_Z += CURRENT_Z - RETRACT_Z += CURRENT_Z - - if DRILL_RETRACT_MODE == 'G98' and CURRENT_Z >= RETRACT_Z: - RETRACT_Z = CURRENT_Z - - # get the other parameters - drill_feedrate = Units.Quantity(params['F'], FreeCAD.Units.Velocity) - if cmd == 'G83': - drill_Step = Units.Quantity(params['Q'], FreeCAD.Units.Length) - a_bit = drill_Step * 0.05 # NIST 3.5.16.4 G83 Cycle: "current hole bottom, backed off a bit." - elif cmd == 'G82': - drill_DwellTime = params['P'] - - # wrap this block to ensure machine MOTION_MODE is restored in case of error - try: - if MOTION_MODE == 'G91': - trBuff += linenumber() + "G90\n" # force absolute coordinates during cycles - - strG0_RETRACT_Z = 'G0 Z' + format(float(RETRACT_Z.getValueAs(UNIT_FORMAT)), strFormat) + "\n" - strF_Feedrate = ' F' + format(float(drill_feedrate.getValueAs(UNIT_SPEED_FORMAT)), '.2f') + "\n" - print (strF_Feedrate) - - # preliminary mouvement(s) - if CURRENT_Z < RETRACT_Z: - trBuff += linenumber() + strG0_RETRACT_Z - trBuff += linenumber() + 'G0 X' + format(float(drill_X.getValueAs(UNIT_FORMAT)), strFormat) + ' Y' + format(float(drill_Y.getValueAs(UNIT_FORMAT)), strFormat) + "\n" - if CURRENT_Z > RETRACT_Z: - # NIST GCODE 3.5.16.1 Preliminary and In-Between Motion says G0 to RETRACT_Z. Here use G1 since retract height may be below surface ! - trBuff += linenumber() + 'G1 Z' + format(float(RETRACT_Z.getValueAs(UNIT_FORMAT)), strFormat) + strF_Feedrate - last_Stop_Z = RETRACT_Z - - # drill moves - if cmd in ('G81', 'G82'): - trBuff += linenumber() + 'G1 Z' + format(float(drill_Z.getValueAs(UNIT_FORMAT)), strFormat) + strF_Feedrate - # pause where applicable - if cmd == 'G82': - trBuff += linenumber() + 'G4 P' + str(drill_DwellTime) + "\n" - trBuff += linenumber() + strG0_RETRACT_Z - else: # 'G83' - if params['Q'] != 0 : - while 1: - if last_Stop_Z != RETRACT_Z : - clearance_depth = last_Stop_Z + a_bit # rapid move to just short of last drilling depth - trBuff += linenumber() + 'G0 Z' + format(float(clearance_depth.getValueAs(UNIT_FORMAT)) , strFormat) + "\n" - next_Stop_Z = last_Stop_Z - drill_Step - if next_Stop_Z > drill_Z: - trBuff += linenumber() + 'G1 Z' + format(float(next_Stop_Z.getValueAs(UNIT_FORMAT)), strFormat) + strF_Feedrate - trBuff += linenumber() + strG0_RETRACT_Z - last_Stop_Z = next_Stop_Z - else: - trBuff += linenumber() + 'G1 Z' + format(float(drill_Z.getValueAs(UNIT_FORMAT)), strFormat) + strF_Feedrate - trBuff += linenumber() + strG0_RETRACT_Z - break - - except Exception as e: - pass - - if MOTION_MODE == 'G91': - trBuff += linenumber() + 'G91' # Restore if changed - - return trBuff - # print(__name__ + ": GCode postprocessor loaded.") diff --git a/src/Mod/Path/PathScripts/post/marlin_post.py b/src/Mod/Path/PathScripts/post/marlin_post.py index f7cee65634..e80bb44da1 100644 --- a/src/Mod/Path/PathScripts/post/marlin_post.py +++ b/src/Mod/Path/PathScripts/post/marlin_post.py @@ -35,7 +35,7 @@ from FreeCAD import Units import PathScripts.PathUtil as PathUtil import PathScripts.PostUtils as PostUtils -Revised = '2020-11-03' # Revision date for this file. +Revised = "2020-11-03" # Revision date for this file. # ***************************************************************************** # * Due to the fundamentals of the FreeCAD pre-processor, * @@ -47,155 +47,148 @@ Revised = '2020-11-03' # Revision date for this file. # ***************************************************************************** -TOOLTIP = ''' +TOOLTIP = """ Generate g-code from a Path that is compatible with the Marlin controller. import marlin_post marlin_post.export(object, "/path/to/file.nc") -''' +""" # ***************************************************************************** # * Initial configuration, not changeable * # ***************************************************************************** -MOTION_MODE = 'G90' # G90 only, for absolute moves -WORK_PLANE = 'G17' # G17 only, XY plane, for vertical milling -UNITS = 'G21' # G21 only, for metric -UNIT_FORMAT = 'mm' -UNIT_FEED_FORMAT = 'mm/s' +MOTION_MODE = "G90" # G90 only, for absolute moves +WORK_PLANE = "G17" # G17 only, XY plane, for vertical milling +UNITS = "G21" # G21 only, for metric +UNIT_FORMAT = "mm" +UNIT_FEED_FORMAT = "mm/s" # ***************************************************************************** # * Initial configuration, changeable via command line arguments * # ***************************************************************************** -PRECISION = 3 # Decimal places displayed for metric -DRILL_RETRACT_MODE = 'G98' # End of drill-cycle retractation type. G99 +PRECISION = 3 # Decimal places displayed for metric +DRILL_RETRACT_MODE = "G98" # End of drill-cycle retractation type. G99 # is the alternative. -TRANSLATE_DRILL_CYCLES = True # If true, G81, G82, and G83 are translated +TRANSLATE_DRILL_CYCLES = True # If true, G81, G82, and G83 are translated # into G0/G1 moves -OUTPUT_TOOL_CHANGE = False # Do not output M6 tool change (comment it) -RETURN_TO = None # None = No movement at end of program -SPINDLE_WAIT = 3 # 0 == No waiting after M3 / M4 -MODAL = False # True: Commands are suppressed if they are +OUTPUT_TOOL_CHANGE = False # Do not output M6 tool change (comment it) +RETURN_TO = None # None = No movement at end of program +SPINDLE_WAIT = 3 # 0 == No waiting after M3 / M4 +MODAL = False # True: Commands are suppressed if they are # the same as the previous line -LINENR = 100 # Line number starting value -LINEINCR = 10 # Line number increment -PRE_OPERATION = '''''' # Pre operation text will be inserted before +LINENR = 100 # Line number starting value +LINEINCR = 10 # Line number increment +PRE_OPERATION = """""" # Pre operation text will be inserted before # every operation -POST_OPERATION = '''''' # Post operation text will be inserted after +POST_OPERATION = """""" # Post operation text will be inserted after # every operation -TOOL_CHANGE = '''''' # Tool Change commands will be inserted +TOOL_CHANGE = """""" # Tool Change commands will be inserted # before a tool change # ***************************************************************************** # * Initial gcode output options, changeable via command line arguments * # ***************************************************************************** -OUTPUT_HEADER = True # Output header in output gcode file -OUTPUT_COMMENTS = True # Comments in output gcode file -OUTPUT_FINISH = False # Include an operation finished comment -OUTPUT_PATH = False # Include a Path: comment -OUTPUT_MARLIN_CONFIG = False # Display expected #defines for Marlin config -OUTPUT_LINE_NUMBERS = False # Output line numbers in output gcode file -OUTPUT_BCNC = False # Add bCNC operation block headers in output +OUTPUT_HEADER = True # Output header in output gcode file +OUTPUT_COMMENTS = True # Comments in output gcode file +OUTPUT_FINISH = False # Include an operation finished comment +OUTPUT_PATH = False # Include a Path: comment +OUTPUT_MARLIN_CONFIG = False # Display expected #defines for Marlin config +OUTPUT_LINE_NUMBERS = False # Output line numbers in output gcode file +OUTPUT_BCNC = False # Add bCNC operation block headers in output # gcode file -SHOW_EDITOR = True # Display the resulting gcode file +SHOW_EDITOR = True # Display the resulting gcode file # ***************************************************************************** # * Command line arguments * # ***************************************************************************** -parser = argparse.ArgumentParser(prog='marlin', add_help=False) +parser = argparse.ArgumentParser(prog="marlin", add_help=False) +parser.add_argument("--header", action="store_true", help="output headers (default)") +parser.add_argument("--no-header", action="store_true", help="suppress header output") +parser.add_argument("--comments", action="store_true", help="output comment (default)") parser.add_argument( - '--header', - action='store_true', - help='output headers (default)') + "--no-comments", action="store_true", help="suppress comment output" +) parser.add_argument( - '--no-header', - action='store_true', - help='suppress header output') + "--finish-comments", action="store_true", help="output finish-comment" +) parser.add_argument( - '--comments', - action='store_true', - help='output comment (default)') + "--no-finish-comments", + action="store_true", + help="suppress finish-comment output (default)", +) +parser.add_argument("--path-comments", action="store_true", help="output path-comment") parser.add_argument( - '--no-comments', - action='store_true', - help='suppress comment output') + "--no-path-comments", + action="store_true", + help="suppress path-comment output (default)", +) parser.add_argument( - '--finish-comments', - action='store_true', - help='output finish-comment') + "--marlin-config", action="store_true", help="output #defines for Marlin" +) parser.add_argument( - '--no-finish-comments', - action='store_true', - help='suppress finish-comment output (default)') + "--no-marlin-config", + action="store_true", + help="suppress output #defines for Marlin (default)", +) parser.add_argument( - '--path-comments', - action='store_true', - help='output path-comment') + "--line-numbers", action="store_true", help="prefix with line numbers" +) parser.add_argument( - '--no-path-comments', - action='store_true', - help='suppress path-comment output (default)') + "--no-line-numbers", + action="store_true", + help="do not prefix with line numbers (default)", +) parser.add_argument( - '--marlin-config', - action='store_true', - help='output #defines for Marlin') + "--show-editor", + action="store_true", + help="pop up editor before writing output (default)", +) parser.add_argument( - '--no-marlin-config', - action='store_true', - help='suppress output #defines for Marlin (default)') + "--no-show-editor", + action="store_true", + help="do not pop up editor before writing output", +) parser.add_argument( - '--line-numbers', - action='store_true', - help='prefix with line numbers') + "--precision", default="3", help="number of digits of precision, default=3" +) parser.add_argument( - '--no-line-numbers', - action='store_true', - help='do not prefix with line numbers (default)') + "--translate_drill", + action="store_true", + help="translate drill cycles G81, G82, G83 into G0/G1 movements (default)", +) parser.add_argument( - '--show-editor', - action='store_true', - help='pop up editor before writing output (default)') + "--no-translate_drill", + action="store_true", + help="do not translate drill cycles G81, G82, G83 into G0/G1 movements", +) parser.add_argument( - '--no-show-editor', - action='store_true', - help='do not pop up editor before writing output') + "--preamble", help='set commands to be issued before the first command, default=""' +) parser.add_argument( - '--precision', - default='3', - help='number of digits of precision, default=3') + "--postamble", help='set commands to be issued after the last command, default="M5"' +) parser.add_argument( - '--translate_drill', - action='store_true', - help='translate drill cycles G81, G82, G83 into G0/G1 movements (default)') + "--tool-change", action="store_true", help="Insert M6 for all tool changes" +) parser.add_argument( - '--no-translate_drill', - action='store_true', - help='do not translate drill cycles G81, G82, G83 into G0/G1 movements') -parser.add_argument( - '--preamble', - help='set commands to be issued before the first command, default=""') -parser.add_argument( - '--postamble', - help='set commands to be issued after the last command, default="M5"') -parser.add_argument( - '--tool-change', action='store_true', - help='Insert M6 for all tool changes') -parser.add_argument( - '--wait-for-spindle', + "--wait-for-spindle", type=int, default=3, - help='Wait for spindle to reach desired speed after M3 or M4, default=0') + help="Wait for spindle to reach desired speed after M3 or M4, default=0", +) parser.add_argument( - '--return-to', - default='', - help='When done, move to, e.g. --return-to="3.175, 4.702, 50.915"') + "--return-to", + default="", + help='When done, move to, e.g. --return-to="3.175, 4.702, 50.915"', +) parser.add_argument( - '--bcnc', - action='store_true', - help='Add Job operations as bCNC block headers. \ - Consider suppressing existing comments: Add argument --no-comments') + "--bcnc", + action="store_true", + help="Add Job operations as bCNC block headers. \ + Consider suppressing existing comments: Add argument --no-comments", +) parser.add_argument( - '--no-bcnc', - action='store_true', - help='suppress bCNC block header output (default)') + "--no-bcnc", action="store_true", help="suppress bCNC block header output (default)" +) TOOLTIP_ARGS = parser.format_help() # ***************************************************************************** @@ -209,19 +202,19 @@ TOOLTIP_ARGS = parser.format_help() # ***************************************************************************** # Default preamble text will appear at the beginning of the gcode output file. -PREAMBLE = '''''' +PREAMBLE = """""" # Default postamble text will appear following the last operation. -POSTAMBLE = '''M5 -''' +POSTAMBLE = """M5 +""" # ***************************************************************************** # * Internal global variables * # ***************************************************************************** -MOTION_COMMANDS = ['G0', 'G00', 'G1', 'G01', 'G2', 'G02', 'G3', 'G03'] -RAPID_MOVES = ['G0', 'G00'] # Rapid moves gcode commands definition -SUPPRESS_COMMANDS = [''] # These commands are ignored by commenting them out -COMMAND_SPACE = ' ' +MOTION_COMMANDS = ["G0", "G00", "G1", "G01", "G2", "G02", "G3", "G03"] +RAPID_MOVES = ["G0", "G00"] # Rapid moves gcode commands definition +SUPPRESS_COMMANDS = [""] # These commands are ignored by commenting them out +COMMAND_SPACE = " " # Global variables storing current position (Use None for safety.) CURRENT_X = None CURRENT_Y = None @@ -290,9 +283,9 @@ def processArguments(argstring): OUTPUT_TOOL_CHANGE = True if args.return_to: RETURN_TO = args.return_to - if RETURN_TO.find(',') == -1: + if RETURN_TO.find(",") == -1: RETURN_TO = None - print('--return-to coordinates must be specified as:') + print("--return-to coordinates must be specified as:") print('--return-to "x.n,y.n,z.n"') if args.bcnc: OUTPUT_BCNC = True @@ -311,14 +304,14 @@ def processArguments(argstring): def dump(obj): for attr in dir(obj): try: - if attr.startswith('__'): + if attr.startswith("__"): continue - print('>' + attr + '<') + print(">" + attr + "<") attr_text = "%s = %s" % (attr, getattr(obj, attr)) - if attr in ['HorizFeed', 'VertFeed']: - print('==============\n', attr_text) - if 'mm/s' in attr_text: - print('===> metric values <===') + if attr in ["HorizFeed", "VertFeed"]: + print("==============\n", attr_text) + if "mm/s" in attr_text: + print("===> metric values <===") except Exception: # Insignificant errors # print('==>', obj, attr) pass @@ -334,128 +327,134 @@ def export(objectslist, filename, argstring): global MOTION_MODE global SUPPRESS_COMMANDS - print('Post Processor: ' + __name__ + ' postprocessing...') - gcode = '' + print("Post Processor: " + __name__ + " postprocessing...") + gcode = "" # Write header: if OUTPUT_HEADER: - gcode += linenumber() + '(Exported by FreeCAD)\n' - gcode += linenumber() + '(Post Processor: ' + __name__ - gcode += '.py, version: ' + Revised + ')\n' - gcode += linenumber() + '(Output Time:' + str(datetime.now()) + ')\n' + gcode += linenumber() + "(Exported by FreeCAD)\n" + gcode += linenumber() + "(Post Processor: " + __name__ + gcode += ".py, version: " + Revised + ")\n" + gcode += linenumber() + "(Output Time:" + str(datetime.now()) + ")\n" # Suppress drill-cycle commands: if TRANSLATE_DRILL_CYCLES: - SUPPRESS_COMMANDS += ['G80', 'G98', 'G99'] + SUPPRESS_COMMANDS += ["G80", "G98", "G99"] # Write the preamble: if OUTPUT_COMMENTS: - gcode += linenumber() + '(Begin preamble)\n' + gcode += linenumber() + "(Begin preamble)\n" for line in PREAMBLE.splitlines(True): gcode += linenumber() + line # Write these settings AFTER the preamble, # to prevent the preamble from changing these: if OUTPUT_COMMENTS: - gcode += linenumber() + '(Default Configuration)\n' - gcode += linenumber() + MOTION_MODE + '\n' - gcode += linenumber() + UNITS + '\n' - gcode += linenumber() + WORK_PLANE + '\n' + gcode += linenumber() + "(Default Configuration)\n" + gcode += linenumber() + MOTION_MODE + "\n" + gcode += linenumber() + UNITS + "\n" + gcode += linenumber() + WORK_PLANE + "\n" for obj in objectslist: # Debug... # print('\n' + '*'*70 + '\n') # dump(obj) # print('\n' + '*'*70 + '\n') - if not hasattr(obj, 'Path'): - print('The object ' + obj.Name + - ' is not a path. Please select only path and Compounds.') + if not hasattr(obj, "Path"): + print( + "The object " + + obj.Name + + " is not a path. Please select only path and Compounds." + ) return # Skip inactive operations: - if PathUtil.opProperty(obj, 'Active') is False: + if PathUtil.opProperty(obj, "Active") is False: continue # Do the pre_op: if OUTPUT_BCNC: - gcode += linenumber() + '(Block-name: ' + obj.Label + ')\n' - gcode += linenumber() + '(Block-expand: 0)\n' - gcode += linenumber() + '(Block-enable: 1)\n' + gcode += linenumber() + "(Block-name: " + obj.Label + ")\n" + gcode += linenumber() + "(Block-expand: 0)\n" + gcode += linenumber() + "(Block-enable: 1)\n" if OUTPUT_COMMENTS: - gcode += linenumber() + '(Begin operation: ' + obj.Label + ')\n' + gcode += linenumber() + "(Begin operation: " + obj.Label + ")\n" for line in PRE_OPERATION.splitlines(True): gcode += linenumber() + line # Get coolant mode: - coolantMode = 'None' # None is the word returned from the operation - if hasattr(obj, 'CoolantMode') or hasattr(obj, 'Base') and \ - hasattr(obj.Base, 'CoolantMode'): - if hasattr(obj, 'CoolantMode'): + coolantMode = "None" # None is the word returned from the operation + if ( + hasattr(obj, "CoolantMode") + or hasattr(obj, "Base") + and hasattr(obj.Base, "CoolantMode") + ): + if hasattr(obj, "CoolantMode"): coolantMode = obj.CoolantMode else: coolantMode = obj.Base.CoolantMode # Turn coolant on if required: if OUTPUT_COMMENTS: - if not coolantMode == 'None': - gcode += linenumber() + '(Coolant On:' + coolantMode + ')\n' - if coolantMode == 'Flood': - gcode += linenumber() + 'M8\n' - if coolantMode == 'Mist': - gcode += linenumber() + 'M7\n' + if not coolantMode == "None": + gcode += linenumber() + "(Coolant On:" + coolantMode + ")\n" + if coolantMode == "Flood": + gcode += linenumber() + "M8\n" + if coolantMode == "Mist": + gcode += linenumber() + "M7\n" # Parse the op: gcode += parse(obj) # Do the post_op: if OUTPUT_COMMENTS and OUTPUT_FINISH: - gcode += linenumber() + '(Finish operation: ' + obj.Label + ')\n' + gcode += linenumber() + "(Finish operation: " + obj.Label + ")\n" for line in POST_OPERATION.splitlines(True): gcode += linenumber() + line # Turn coolant off if previously enabled: - if not coolantMode == 'None': + if not coolantMode == "None": if OUTPUT_COMMENTS: - gcode += linenumber() + '(Coolant Off:' + coolantMode + ')\n' - gcode += linenumber() + 'M9\n' + gcode += linenumber() + "(Coolant Off:" + coolantMode + ")\n" + gcode += linenumber() + "M9\n" # Do the post_amble: if OUTPUT_BCNC: - gcode += linenumber() + '(Block-name: post_amble)\n' - gcode += linenumber() + '(Block-expand: 0)\n' - gcode += linenumber() + '(Block-enable: 1)\n' + gcode += linenumber() + "(Block-name: post_amble)\n" + gcode += linenumber() + "(Block-expand: 0)\n" + gcode += linenumber() + "(Block-enable: 1)\n" if OUTPUT_COMMENTS: - gcode += linenumber() + '(Begin postamble)\n' + gcode += linenumber() + "(Begin postamble)\n" for line in POSTAMBLE.splitlines(True): gcode += linenumber() + line # Optionally add a final XYZ position to the end of the gcode: if RETURN_TO: - first_comma = RETURN_TO.find(',') - last_comma = RETURN_TO.rfind(',') # == first_comma if only one comma - ref_X = ' X' + RETURN_TO[0: first_comma].strip() + first_comma = RETURN_TO.find(",") + last_comma = RETURN_TO.rfind(",") # == first_comma if only one comma + ref_X = " X" + RETURN_TO[0:first_comma].strip() # Z is optional: if last_comma != first_comma: - ref_Z = ' Z' + RETURN_TO[last_comma + 1:].strip() - ref_Y = ' Y' + RETURN_TO[first_comma + 1:last_comma].strip() + ref_Z = " Z" + RETURN_TO[last_comma + 1 :].strip() + ref_Y = " Y" + RETURN_TO[first_comma + 1 : last_comma].strip() else: - ref_Z = '' - ref_Y = ' Y' + RETURN_TO[first_comma + 1:].strip() + ref_Z = "" + ref_Y = " Y" + RETURN_TO[first_comma + 1 :].strip() - gcode += linenumber() + 'G0' + ref_X + ref_Y + ref_Z + '\n' + gcode += linenumber() + "G0" + ref_X + ref_Y + ref_Z + "\n" # Optionally add recommended Marlin 2.x configuration to gcode file: if OUTPUT_MARLIN_CONFIG: - gcode += linenumber() + '(Marlin 2.x Configuration)\n' - gcode += linenumber() + '(The following should be enabled in)\n' - gcode += linenumber() + '(the configuration files of Marlin 2.x)\n' - gcode += linenumber() + '(#define ARC_SUPPORT)\n' - gcode += linenumber() + '(#define CNC_COORDINATE_SYSTEMS)\n' - gcode += linenumber() + '(#define PAREN_COMMENTS)\n' - gcode += linenumber() + '(#define GCODE_MOTION_MODES)\n' - gcode += linenumber() + '(#define G0_FEEDRATE)\n' - gcode += linenumber() + '(define VARIABLE_G0_FEEDRATE)\n' + gcode += linenumber() + "(Marlin 2.x Configuration)\n" + gcode += linenumber() + "(The following should be enabled in)\n" + gcode += linenumber() + "(the configuration files of Marlin 2.x)\n" + gcode += linenumber() + "(#define ARC_SUPPORT)\n" + gcode += linenumber() + "(#define CNC_COORDINATE_SYSTEMS)\n" + gcode += linenumber() + "(#define PAREN_COMMENTS)\n" + gcode += linenumber() + "(#define GCODE_MOTION_MODES)\n" + gcode += linenumber() + "(#define G0_FEEDRATE)\n" + gcode += linenumber() + "(define VARIABLE_G0_FEEDRATE)\n" # Show the gcode result dialog: if FreeCAD.GuiUp and SHOW_EDITOR: @@ -469,26 +468,26 @@ def export(objectslist, filename, argstring): else: final = gcode - print('Done postprocessing.') + print("Done postprocessing.") # Write the file: - with open(filename, 'w') as fp: + with open(filename, "w") as fp: fp.write(final) def linenumber(): if not OUTPUT_LINE_NUMBERS: - return '' + return "" global LINENR global LINEINCR LINENR += LINEINCR - return 'N' + str(LINENR) + ' ' + return "N" + str(LINENR) + " " def format_outlist(strTable): # construct the line for the final output global COMMAND_SPACE - s = '' + s = "" for w in strTable: s += w + COMMAND_SPACE return s.strip() @@ -501,27 +500,46 @@ def parse(pathobj): global CURRENT_Y global CURRENT_Z - out = '' + out = "" lastcommand = None - precision_string = '.' + str(PRECISION) + 'f' + precision_string = "." + str(PRECISION) + "f" - params = ['X', 'Y', 'Z', 'A', 'B', 'C', 'U', 'V', 'W', 'I', 'J', 'K', 'F', - 'S', 'T', 'Q', 'R', 'L', 'P'] + params = [ + "X", + "Y", + "Z", + "A", + "B", + "C", + "U", + "V", + "W", + "I", + "J", + "K", + "F", + "S", + "T", + "Q", + "R", + "L", + "P", + ] - if hasattr(pathobj, 'Group'): # We have a compound or project. + if hasattr(pathobj, "Group"): # We have a compound or project. if OUTPUT_COMMENTS: - out += linenumber() + '(Compound: ' + pathobj.Label + ')\n' + out += linenumber() + "(Compound: " + pathobj.Label + ")\n" for p in pathobj.Group: out += parse(p) return out else: # Parsing simple path # groups might contain non-path things like stock. - if not hasattr(pathobj, 'Path'): + if not hasattr(pathobj, "Path"): return out if OUTPUT_COMMENTS and OUTPUT_PATH: - out += linenumber() + '(Path: ' + pathobj.Label + ')\n' + out += linenumber() + "(Path: " + pathobj.Label + ")\n" for c in pathobj.Path.Commands: outlist = [] @@ -538,96 +556,102 @@ def parse(pathobj): # Add the remaining parameters in order: for param in params: if param in c.Parameters: - if param == 'F': + if param == "F": if command not in RAPID_MOVES: feedRate = Units.Quantity( - c.Parameters['F'], FreeCAD.Units.Velocity) + c.Parameters["F"], FreeCAD.Units.Velocity + ) if feedRate.getValueAs(UNIT_FEED_FORMAT) > 0.0: - outlist.append(param + format(float( - feedRate.getValueAs(UNIT_FEED_FORMAT)), - precision_string)) - elif param in ['T', 'H', 'D', 'S', 'P', 'L']: + outlist.append( + param + + format( + float(feedRate.getValueAs(UNIT_FEED_FORMAT)), + precision_string, + ) + ) + elif param in ["T", "H", "D", "S", "P", "L"]: outlist.append(param + str(c.Parameters[param])) - elif param in ['A', 'B', 'C']: - outlist.append(param + format( - c.Parameters[param], precision_string)) + elif param in ["A", "B", "C"]: + outlist.append( + param + format(c.Parameters[param], precision_string) + ) # [X, Y, Z, U, V, W, I, J, K, R, Q] else: - pos = Units.Quantity( - c.Parameters[param], FreeCAD.Units.Length) - outlist.append(param + format(float( - pos.getValueAs(UNIT_FORMAT)), precision_string)) + pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length) + outlist.append( + param + + format( + float(pos.getValueAs(UNIT_FORMAT)), precision_string + ) + ) # Store the latest command: lastcommand = command # Capture the current position for subsequent calculations: if command in MOTION_COMMANDS: - if 'X' in c.Parameters: - CURRENT_X = Units.Quantity( - c.Parameters['X'], FreeCAD.Units.Length) - if 'Y' in c.Parameters: - CURRENT_Y = Units.Quantity( - c.Parameters['Y'], FreeCAD.Units.Length) - if 'Z' in c.Parameters: - CURRENT_Z = Units.Quantity( - c.Parameters['Z'], FreeCAD.Units.Length) + if "X" in c.Parameters: + CURRENT_X = Units.Quantity(c.Parameters["X"], FreeCAD.Units.Length) + if "Y" in c.Parameters: + CURRENT_Y = Units.Quantity(c.Parameters["Y"], FreeCAD.Units.Length) + if "Z" in c.Parameters: + CURRENT_Z = Units.Quantity(c.Parameters["Z"], FreeCAD.Units.Length) - if command in ('G98', 'G99'): + if command in ("G98", "G99"): DRILL_RETRACT_MODE = command if TRANSLATE_DRILL_CYCLES: - if command in ('G81', 'G82', 'G83'): + if command in ("G81", "G82", "G83"): out += drill_translate(outlist, command, c.Parameters) # Erase the line just translated: outlist = [] if SPINDLE_WAIT > 0: - if command in ('M3', 'M03', 'M4', 'M04'): - out += linenumber() + format_outlist(outlist) + '\n' + if command in ("M3", "M03", "M4", "M04"): + out += linenumber() + format_outlist(outlist) + "\n" # Marlin: P for milliseconds, S for seconds, change P to S out += linenumber() - out += format_outlist(['G4', 'S%s' % SPINDLE_WAIT]) - out += '\n' + out += format_outlist(["G4", "S%s" % SPINDLE_WAIT]) + out += "\n" outlist = [] # Check for Tool Change: - if command in ('M6', 'M06'): + if command in ("M6", "M06"): if OUTPUT_COMMENTS: - out += linenumber() + '(Begin toolchange)\n' + out += linenumber() + "(Begin toolchange)\n" if OUTPUT_TOOL_CHANGE: for line in TOOL_CHANGE.splitlines(True): out += linenumber() + line if not OUTPUT_TOOL_CHANGE and OUTPUT_COMMENTS: - outlist[0] = '(' + outlist[0] - outlist[-1] = outlist[-1] + ')' + outlist[0] = "(" + outlist[0] + outlist[-1] = outlist[-1] + ")" if not OUTPUT_TOOL_CHANGE and not OUTPUT_COMMENTS: outlist = [] - if command == 'message': + if command == "message": if OUTPUT_COMMENTS: outlist.pop(0) # remove the command else: out = [] if command in SUPPRESS_COMMANDS: - outlist[0] = '(' + outlist[0] - outlist[-1] = outlist[-1] + ')' + outlist[0] = "(" + outlist[0] + outlist[-1] = outlist[-1] + ")" # Remove embedded comments: if not OUTPUT_COMMENTS: tmplist = [] list_index = 0 while list_index < len(outlist): - left_index = outlist[list_index].find('(') + left_index = outlist[list_index].find("(") if left_index == -1: # Not a comment tmplist.append(outlist[list_index]) else: # This line contains a comment, and possibly more - right_index = outlist[list_index].find(')') - comment_area = outlist[list_index][ - left_index: right_index + 1] - line_minus_comment = outlist[list_index].replace( - comment_area, '').strip() + right_index = outlist[list_index].find(")") + comment_area = outlist[list_index][left_index : right_index + 1] + line_minus_comment = ( + outlist[list_index].replace(comment_area, "").strip() + ) if line_minus_comment: # Line contained more than just a comment tmplist.append(line_minus_comment) @@ -637,7 +661,7 @@ def parse(pathobj): # Prepend a line number and append a newline if len(outlist) >= 1: - out += linenumber() + format_outlist(outlist) + '\n' + out += linenumber() + format_outlist(outlist) + "\n" return out @@ -658,61 +682,56 @@ def drill_translate(outlist, cmd, params): global UNIT_FEED_FORMAT class Drill: # Using a class is necessary for the nested functions. - gcode = '' + gcode = "" - strFormat = '.' + str(PRECISION) + 'f' + strFormat = "." + str(PRECISION) + "f" if OUTPUT_COMMENTS: # Comment the original command - outlist[0] = '(' + outlist[0] - outlist[-1] = outlist[-1] + ')' - Drill.gcode += linenumber() + format_outlist(outlist) + '\n' + outlist[0] = "(" + outlist[0] + outlist[-1] = outlist[-1] + ")" + Drill.gcode += linenumber() + format_outlist(outlist) + "\n" # Cycle conversion only converts the cycles in the XY plane (G17). # --> ZX (G18) and YZ (G19) planes produce false gcode. - drill_X = Units.Quantity(params['X'], FreeCAD.Units.Length) - drill_Y = Units.Quantity(params['Y'], FreeCAD.Units.Length) - drill_Z = Units.Quantity(params['Z'], FreeCAD.Units.Length) - drill_R = Units.Quantity(params['R'], FreeCAD.Units.Length) - drill_F = Units.Quantity(params['F'], FreeCAD.Units.Velocity) - if cmd == 'G82': - drill_DwellTime = params['P'] - elif cmd == 'G83': - drill_Step = Units.Quantity(params['Q'], FreeCAD.Units.Length) + drill_X = Units.Quantity(params["X"], FreeCAD.Units.Length) + drill_Y = Units.Quantity(params["Y"], FreeCAD.Units.Length) + drill_Z = Units.Quantity(params["Z"], FreeCAD.Units.Length) + drill_R = Units.Quantity(params["R"], FreeCAD.Units.Length) + drill_F = Units.Quantity(params["F"], FreeCAD.Units.Velocity) + if cmd == "G82": + drill_DwellTime = params["P"] + elif cmd == "G83": + drill_Step = Units.Quantity(params["Q"], FreeCAD.Units.Length) # R less than Z is error if drill_R < drill_Z: - Drill.gcode += linenumber() + '(drill cycle error: R less than Z )\n' + Drill.gcode += linenumber() + "(drill cycle error: R less than Z )\n" return Drill.gcode # Z height to retract to when drill cycle is done: - if DRILL_RETRACT_MODE == 'G98' and CURRENT_Z > drill_R: + if DRILL_RETRACT_MODE == "G98" and CURRENT_Z > drill_R: RETRACT_Z = CURRENT_Z else: RETRACT_Z = drill_R # Z motion nested functions: def rapid_Z_to(new_Z): - Drill.gcode += linenumber() + 'G0 Z' - Drill.gcode += format( - float(new_Z.getValueAs(UNIT_FORMAT)), strFormat) + '\n' + Drill.gcode += linenumber() + "G0 Z" + Drill.gcode += format(float(new_Z.getValueAs(UNIT_FORMAT)), strFormat) + "\n" def feed_Z_to(new_Z): - Drill.gcode += linenumber() + 'G1 Z' - Drill.gcode += format( - float(new_Z.getValueAs(UNIT_FORMAT)), strFormat) + ' F' - Drill.gcode += format( - float(drill_F.getValueAs(UNIT_FEED_FORMAT)), '.2f') + '\n' + Drill.gcode += linenumber() + "G1 Z" + Drill.gcode += format(float(new_Z.getValueAs(UNIT_FORMAT)), strFormat) + " F" + Drill.gcode += format(float(drill_F.getValueAs(UNIT_FEED_FORMAT)), ".2f") + "\n" # Make sure that Z is not below RETRACT_Z: if CURRENT_Z < RETRACT_Z: rapid_Z_to(RETRACT_Z) # Rapid to hole position XY: - Drill.gcode += linenumber() + 'G0 X' - Drill.gcode += format( - float(drill_X.getValueAs(UNIT_FORMAT)), strFormat) + ' Y' - Drill.gcode += format( - float(drill_Y.getValueAs(UNIT_FORMAT)), strFormat) + '\n' + Drill.gcode += linenumber() + "G0 X" + Drill.gcode += format(float(drill_X.getValueAs(UNIT_FORMAT)), strFormat) + " Y" + Drill.gcode += format(float(drill_Y.getValueAs(UNIT_FORMAT)), strFormat) + "\n" # Rapid to R: rapid_Z_to(drill_R) @@ -728,13 +747,13 @@ def drill_translate(outlist, cmd, params): # * G99 After the hole has been drilled, retract to R height * # * Select G99 only if safe to move from hole to hole at the R height * # ************************************************************************* - if cmd in ('G81', 'G82'): + if cmd in ("G81", "G82"): feed_Z_to(drill_Z) # Drill hole in one step - if cmd == 'G82': # Dwell time delay at the bottom of the hole - Drill.gcode += linenumber() + 'G4 S' + str(drill_DwellTime) + '\n' + if cmd == "G82": # Dwell time delay at the bottom of the hole + Drill.gcode += linenumber() + "G4 S" + str(drill_DwellTime) + "\n" # Marlin uses P for milliseconds, S for seconds, change P to S - elif cmd == 'G83': # Peck drill cycle: + elif cmd == "G83": # Peck drill cycle: chip_Space = drill_Step * 0.5 next_Stop_Z = drill_R - drill_Step while next_Stop_Z >= drill_Z: