Merge pull request #5330 from sliptonic/bug/translation-post

[Path] Bug/translation post
This commit is contained in:
sliptonic
2022-01-04 17:34:29 -06:00
committed by GitHub
5 changed files with 1076 additions and 837 deletions

View File

@@ -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")

View File

@@ -1,34 +1,36 @@
# -*- coding: utf-8 -*-
#***************************************************************************
#* Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> *
#* *
#* 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 <yorik@uncreated.net> *
# * *
# * 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

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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: