Refactor marlin_post.py

Change G4 P to G4 S for Marlin
  Marlin uses P for milliseconds, S for seconds.
  FreeCAD uses P for seconds, so change P to S.

Remove code for G20 inch mode and G91 relative mode
  Due to the fundamentals of the FreeCAD pre-processor,
  this post processor can only operate in the following modes:
  G90 Absolute positions
  G21 Metric units (mm)
  G17 XY plane (3 axis vertical milling only)

Expand RETURN_TO option to float values for XYZ
  The RETURN_TO option previously only accepted integer values, and
  only for X and Y.
  This change allows float values for XY and optionally Z.

Add class: "Drill" within drill_translate()
  The nested functions within drill_translate() need to modify the
  variable that was named trBuff. To allow this, trBuff was changed
  to class: "Drill" with property "gcode".
  Within drill_translate(), including within the nested functions,
  modifying "trBuff" is now replaced with modifying "Drill.gcode".

Merge in updated portions of grbl_post.py
  Prior verson was derived from an older grbl_post.py.
  Merge in the portions of the newer grbl that seem improved.
  Refactor overall after the merge.

Add options for partial comments
  Add option:  --no-finish-comments
  Add option:  --no-path-comments
This commit is contained in:
GaryH
2020-10-22 21:07:05 +02:00
committed by Gary L Hasson
parent 0e0156a2c2
commit d632130831

View File

@@ -26,7 +26,16 @@
# * *
# ***************************************************************************/
# This file is derived from grbl_post.py.
Revised = "2020-10-22" # Revision date for this file.
'''
Due to the fundamentals of the FreeCAD pre-processor,
this post processor can only operate in the following modes:
G90 Absolute positions
G21 Metric units (mm)
G17 XY plane (3 axis vertical milling only)
'''
import FreeCAD
from FreeCAD import Units
@@ -44,66 +53,51 @@ marlin_post.export(object, "/path/to/file.nc")
'''
# ***************************************************************************
# * Globals set customization preferences
# * Initial configuration, not changeable
# ***************************************************************************
Revised = "2020-10-18" # Revision date for this file.
# Default values for command line arguments:
OUTPUT_COMMENTS = True # default output of comments in output gcode file
OUTPUT_FINISH = False # include an operation finished comment
OUTPUT_PATH = False # include a Path: comment
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 = True # If true, G81, G82 & G83 are translated into G0/G1 moves
# Marlin 2.x ignores commands that it does not implement.
# Marlin 2.x ignores the ENTIRE COMMAND LINE if there is more than one command per line.
# Default preamble text will appear at the beginning of the gcode output file.
PREAMBLE = '''G80
G17
G90
'''
# Default postamble text will appear following the last operation.
POSTAMBLE = '''G80
M5
G17
G90
'''
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 Marlin 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 USA standard
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_SPEED_FORMAT = 'mm/min'
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 is the alternative.
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 # 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 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
# * Initial gcode output options, changeable via command line arguments
# ***************************************************************************
OUTPUT_COMMENTS = True # Comments in output gcode file
OUTPUT_FINISH = False # Include an operation finished comment
OUTPUT_PATH = False # Include a Path: comment
OUTPUT_HEADER = True # Output header in output gcode file
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
# Parser arguments list & definition:
parser = argparse.ArgumentParser(prog='Marlin', add_help=False)
# ***************************************************************************
# * Command line arguments
# ***************************************************************************
parser = argparse.ArgumentParser(prog='marlin', 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('--finish-comments', action='store_true', help='output finish-comment')
parser.add_argument('--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-path-comments', action='store_true', help='suppress path-comment output (default)')
parser.add_argument('--finish-comments', action='store_true', help='output finish-comment')
parser.add_argument('--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-path-comments', action='store_true', help='suppress path-comment output (default)')
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')
@@ -111,33 +105,48 @@ parser.add_argument('--no-line-numbers', action='store_true', help='do not pr
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='do not 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, and G83 in G0/G1 movements')
parser.add_argument('--no-translate_drill', action='store_true', help='do not translate drill cycles G81, G82, and G83 in G0/G1 movements (default)')
parser.add_argument('--preamble', help='set commands to be issued before the first command, default="G80\nG17\nG90"')
parser.add_argument('--postamble', help='set commands to be issued after the last command, default="G80\nM5\nG17\nG90"')
parser.add_argument('--inches', action='store_true', help='Convert output for US imperial mode (G20)')
parser.add_argument('--translate_drill', action='store_true', help='translate drill cycles G81, G82, and G83 into G0/G1 movements (default)')
parser.add_argument('--no-translate_drill', action='store_true', help='do not translate drill cycles G81, G82, and G83 into G0/G1 movements')
parser.add_argument('--preamble', help='set commands to be issued before the first command, default="G80\nG17\nG90\nG21"')
parser.add_argument('--postamble', help='set commands to be issued after the last command, default="G80\nM5\nG17\nG90\nG21"')
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('--wait-for-spindle', type=int, default=0, help='Wait for spindle to reach desired speed after M3 or M4, default=0')
parser.add_argument('--return-to', default='', help='Move to the specified coordinates at the end, 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')
parser.add_argument('--no-bcnc', action='store_true', help='suppress bCNC block header output (default)')
TOOLTIP_ARGS = parser.format_help()
'''
Marlin 2.x:
Ignores commands that it does not implement.
Some machining-related commands may conflict with gcodes that Marlin has used for 3D printing.
Therefore, check FreeCAD gcodes for conflicts with Marlin.
Marlin 2.x ignores the ENTIRE COMMAND LINE if there is more than one command per line.
'''
# Default preamble text will appear at the beginning of the gcode output file.
PREAMBLE = '''
'''
# Default postamble text will appear following the last operation.
POSTAMBLE = '''M5
'''
# ***************************************************************************
# * Internal global variables
# ***************************************************************************
MOTION_COMMANDS = ['G0', 'G00', 'G1', 'G01', 'G2', 'G02', 'G3', 'G03'] # Motion gcode commands definition
MOTION_COMMANDS = ['G0', 'G00', 'G1', 'G01', 'G2', 'G02', 'G3', 'G03']
RAPID_MOVES = ['G0', 'G00'] # Rapid moves gcode commands definition
SUPPRESS_COMMANDS = ['M2'] # These commands are ignored by commenting them out
SUPPRESS_COMMANDS = [''] # These commands are ignored by commenting them out
COMMAND_SPACE = " "
# Global variables storing current position
CURRENT_X = 0
CURRENT_Y = 0
CURRENT_Z = 0
# Global variables storing current position (Use None for safety.)
CURRENT_X = None
CURRENT_Y = None
CURRENT_Z = None
# ***************************************************************************
# * to distinguish python built-in open function from the one declared below
# * To distinguish python built-in open function from the one declared below
# ***************************************************************************
if open.__module__ in ['__builtin__', 'io']:
pythonopen = open
@@ -154,7 +163,7 @@ def processArguments(argstring):
global PREAMBLE
global POSTAMBLE
global UNITS
global UNIT_SPEED_FORMAT
global UNIT_FEED_FORMAT
global UNIT_FORMAT
global TRANSLATE_DRILL_CYCLES
global OUTPUT_TOOL_CHANGE
@@ -197,20 +206,15 @@ def processArguments(argstring):
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:
if args.return_to:
RETURN_TO = args.return_to
if RETURN_TO.find(',') == -1:
RETURN_TO = None
print("--return-to coordinates must be specified as <x>,<y>, ignoring")
print('--return-to coordinates must be specified as:\n--return-to "x.n,y.n,z.n"')
if args.bcnc:
OUTPUT_BCNC = True
if args.no_bcnc:
@@ -226,8 +230,18 @@ def processArguments(argstring):
# For debug...
def dump(obj):
for attr in dir(obj):
print("obj.%s = %s" % (attr, getattr(obj, attr)))
try:
if attr.startswith('__'):
continue
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 <===')
except: # Insignificant errors
# print("==>", obj, attr)
pass
def export(objectslist, filename, argstring):
@@ -236,63 +250,49 @@ def export(objectslist, filename, argstring):
global UNITS
global UNIT_FORMAT
global UNIT_SPEED_FORMAT
global UNIT_FEED_FORMAT
global MOTION_MODE
global SUPPRESS_COMMANDS
print("Post Processor: " + __name__ + " postprocessing...")
gcode = ""
# write header
# Write header:
if OUTPUT_HEADER:
gcode += linenumber() + "(Exported by FreeCAD)\n"
gcode += linenumber() + "(Post Processor: " + __name__ + ".py, version: " + Revised + ")\n"
gcode += linenumber() + "(Output Time:" + str(datetime.datetime.now()) + ")\n"
# Check canned cycles for drilling
# Suppress drill-cycle commands:
if TRANSLATE_DRILL_CYCLES:
if len(SUPPRESS_COMMANDS) == 0:
SUPPRESS_COMMANDS = ['G98', 'G99', 'G80']
else:
SUPPRESS_COMMANDS += ['G98', 'G99', 'G80']
SUPPRESS_COMMANDS += ['G80', 'G98', 'G99']
# Write the preamble
# Write the preamble:
if OUTPUT_COMMENTS:
gcode += linenumber() + "(Begin preamble)\n"
for line in PREAMBLE.splitlines(True):
gcode += linenumber() + line
# verify if PREAMBLE has 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"
# Write these settings after the preamble to prevent the preamble from changing these:
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)
# print("\n" + "*"*70 + "\n")
# dump(obj)
# print("*"*70 + "\n")
# print("\n" + "*"*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
# Skip inactive operations:
if PathUtil.opProperty(obj, 'Active') is False:
continue
# do the pre_op
# Do the pre_op:
if OUTPUT_BCNC:
gcode += linenumber() + "(Block-name: " + obj.Label + ")\n"
gcode += linenumber() + "(Block-expand: 0)\n"
@@ -302,7 +302,7 @@ def export(objectslist, filename, argstring):
for line in PRE_OPERATION.splitlines(True):
gcode += linenumber() + line
# get coolant mode
# Get coolant mode:
coolantMode = 'None'
if hasattr(obj, "CoolantMode") or hasattr(obj, 'Base') and hasattr(obj.Base, "CoolantMode"):
if hasattr(obj, "CoolantMode"):
@@ -310,7 +310,7 @@ def export(objectslist, filename, argstring):
else:
coolantMode = obj.Base.CoolantMode
# turn coolant on if required
# Turn coolant on if required:
if OUTPUT_COMMENTS:
if not coolantMode == 'None':
gcode += linenumber() + '(Coolant On:' + coolantMode + ')\n'
@@ -319,22 +319,22 @@ def export(objectslist, filename, argstring):
if coolantMode == 'Mist':
gcode += linenumber() + 'M7' + '\n'
# Parse the op
# Parse the op:
gcode += parse(obj)
# do the post_op
# Do the post_op:
if OUTPUT_COMMENTS and OUTPUT_FINISH:
gcode += linenumber() + "(Finish operation: " + obj.Label + ")\n"
for line in POST_OPERATION.splitlines(True):
gcode += linenumber() + line
# turn coolant off if required
# Turn coolant off if required:
if not coolantMode == 'None':
if OUTPUT_COMMENTS:
gcode += linenumber() + '(Coolant Off:' + coolantMode + ')\n'
gcode += linenumber() +'M9' + '\n'
gcode += linenumber() +'M9\n'
# do the post_amble
# Do the post_amble:
if OUTPUT_BCNC:
gcode += linenumber() + "(Block-name: post_amble)\n"
gcode += linenumber() + "(Block-expand: 0)\n"
@@ -344,10 +344,23 @@ def export(objectslist, filename, argstring):
for line in POSTAMBLE.splitlines(True):
gcode += linenumber() + line
# Optionally add a final XYZ position to the end of the gcode:
if RETURN_TO:
gcode += linenumber() + "G0 X%s Y%s" % tuple(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()
# show the gcode result dialog:
# 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()
else:
ref_Z = ''
ref_Y = ' Y' + RETURN_TO[first_comma + 1:].strip()
gcode += linenumber() + "G0" + ref_X + ref_Y + ref_Z + '\n'
# Show the gcode result dialog:
if FreeCAD.GuiUp and SHOW_EDITOR:
dia = PostUtils.GCodeEditorDialog()
dia.editor.setText(gcode)
@@ -361,7 +374,7 @@ def export(objectslist, filename, argstring):
print("Done postprocessing.")
# write the file
# Write the file:
try:
gfile = pythonopen(filename, "w")
gfile.write(final)
@@ -372,20 +385,20 @@ def export(objectslist, filename, argstring):
def linenumber():
if not OUTPUT_LINE_NUMBERS:
return ""
global LINENR
global LINEINCR
if OUTPUT_LINE_NUMBERS:
s = "N" + str(LINENR) + " "
LINENR += LINEINCR
return s
return ""
s = "N" + str(LINENR) + " "
LINENR += LINEINCR
return s
def format_outstring(strTbl):
def format_outstring(strTable):
global COMMAND_SPACE
# construct the line for the final output
s = ""
for w in strTbl:
for w in strTable:
s += w + COMMAND_SPACE
s = s.strip()
return s
@@ -412,7 +425,7 @@ def parse(pathobj):
out += parse(p)
return out
else: # parsing simple path
else: # Parsing simple path
if not hasattr(pathobj, "Path"): # groups might contain non-path things like stock.
return out
@@ -422,34 +435,35 @@ def parse(pathobj):
for c in pathobj.Path.Commands:
outstring = []
command = c.Name
outstring.append(command)
# Debug:
# print('pathobj.Path.Commands:', c)
# if modal: only print the command if it is not the same as the last one
# If modal is True, delete duplicate commands:
if MODAL:
if command == lastcommand:
outstring.pop(0)
# Now add the remaining parameters in order
# 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))
feedRate = Units.Quantity(c.Parameters['F'], FreeCAD.Units.Velocity)
if feedRate.getValueAs(UNIT_FEED_FORMAT) > 0.0:
outstring.append(param + format(float(feedRate.getValueAs(UNIT_FEED_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)
else: # [X, Y, Z, U, V, W, I, J, K, R, Q] (Possible conversion 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
# Store the latest command:
lastcommand = command
# Memorizes the current position for calculating the related movements and the withdrawal plan
# Capture the current position for calculating the related movements:
if command in MOTION_COMMANDS:
if 'X' in c.Parameters:
CURRENT_X = Units.Quantity(c.Parameters['X'], FreeCAD.Units.Length)
@@ -461,21 +475,17 @@ def parse(pathobj):
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
del(outstring[:])
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"
del(outstring[:])
# Note: Marlin uses P for milliseconds, S for seconds. Change P to S.
out += linenumber() + format_outstring(['G4', 'S%s' % SPINDLE_WAIT]) + "\n"
outstring = []
# Check for Tool Change:
@@ -505,11 +515,12 @@ def parse(pathobj):
return out
''' As of Marlin 2.0.7.bugfix, canned drill cycles do not exist.
The following code converts FreeCAD's canned drill cycles into
gcode that Marlin can use.
'''
def drill_translate(outstring, cmd, params):
''' As of Marlin 2.0.7.bugfix, canned drill cycles do not exist.
The following code converts FreeCAD's canned drill cycles into
gcode that Marlin can use.
'''
global DRILL_RETRACT_MODE
global MOTION_MODE
global CURRENT_X
@@ -517,98 +528,94 @@ def drill_translate(outstring, cmd, params):
global CURRENT_Z
global UNITS
global UNIT_FORMAT
global UNIT_SPEED_FORMAT
global UNIT_FEED_FORMAT
class Drill: # Using a class is necessary for the nested functions
gcode = ''
strFormat = '.' + str(PRECISION) + 'f'
trBuff = ""
if OUTPUT_COMMENTS: # Comment the original command
outstring[0] = "(" + outstring[0]
outstring[-1] = outstring[-1] + ")"
trBuff += linenumber() + format_outstring(outstring) + "\n"
Drill.gcode += linenumber() + format_outstring(outstring) + "\n"
# Cycle conversion
# Only converts the cycles in the XY plane (G17).
# ZX (G18) and YZ (G19) planes are not processed: Calculations on Z only.
if MOTION_MODE == 'G90': # absolute positions:
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)
else: # G91 relative positions:
drill_X = CURRENT_X + Units.Quantity(params['X'], FreeCAD.Units.Length)
drill_Y = CURRENT_Y + Units.Quantity(params['Y'], FreeCAD.Units.Length)
drill_Z = CURRENT_Z + Units.Quantity(params['Z'], FreeCAD.Units.Length)
RETRACT_Z = CURRENT_Z + Units.Quantity(params['R'], FreeCAD.Units.Length)
# Get the values for various parameters:
drill_Speed = Units.Quantity(params['F'], FreeCAD.Units.Velocity)
if cmd == 'G83':
drill_Step = Units.Quantity(params['Q'], FreeCAD.Units.Length)
elif cmd == 'G82':
# 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)
if MOTION_MODE == 'G91':
# Use absolute coordinates during the drill cycle:
trBuff += linenumber() + "G90" + "\n"
# For G83, retract to Z_Rvalue after each peck:
Z_Rvalue = RETRACT_Z # RETRACT_Z might get changed below.
# R less than Z is error
if drill_R < drill_Z :
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 >= RETRACT_Z:
if DRILL_RETRACT_MODE == 'G98' and CURRENT_Z > drill_R:
RETRACT_Z = CURRENT_Z
# Preliminary movements:
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:
trBuff += linenumber() + 'G0 Z' + format(float(RETRACT_Z.getValueAs(UNIT_FORMAT)), strFormat) + "\n"
else:
trBuff += linenumber() + 'G0 Z' + format(float(CURRENT_Z.getValueAs(UNIT_FORMAT)), strFormat) + "\n"
RETRACT_Z = drill_R
# Z motion nested functions:
def rapid_Z_to(new_Z):
Drill.gcode += linenumber() + 'G0 Z' + format(float(new_Z.getValueAs(UNIT_FORMAT)), strFormat) + "\n"
def feed_Z_to(new_Z):
Drill.gcode += linenumber() + 'G1 Z' + format(float(new_Z.getValueAs(UNIT_FORMAT)), strFormat) + ' F' + 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' + format(float(drill_X.getValueAs(UNIT_FORMAT)), strFormat) + ' Y' + format(float(drill_Y.getValueAs(UNIT_FORMAT)), strFormat) + "\n"
# Rapid to R:
rapid_Z_to(drill_R)
""" Drill cycles:
G80 Cancel the drill cycle.
G81 Drill full depth in one pass.
G82 Drill full depth in one pass, and pause at the bottom.
G83 Drill in small pecks (steps), raising the drill to R height after each peck.
G98 After the hole has been drilled, retract to the initial Z height (clearance height).
G99 After the hole has been drilled, retract to R height.
In preparation for a rapid to the next hole position:
G98 After the hole has been drilled, retract to the initial Z value as the clearance height.
G99 After the hole has been drilled, retract to R height. G99 should only be selected if it is safe to move from hole to hole at the R height.
"""
if cmd in ('G81', 'G82'):
trBuff += linenumber() + 'G1 Z' + format(float(drill_Z.getValueAs(UNIT_FORMAT)), strFormat) + ' F' + format(float(drill_Speed.getValueAs(UNIT_SPEED_FORMAT)), '.2f') + "\n"
# G82 uses a dwell time delay:
if cmd == 'G82':
trBuff += linenumber() + 'G4 P' + str(drill_DwellTime) + "\n"
# Retract the drill:
trBuff += linenumber() + 'G0 Z' + format(float(RETRACT_Z.getValueAs(UNIT_FORMAT)), strFormat) + "\n"
else: # G83 Peck drill cycle:
next_Stop_Z = Z_Rvalue - drill_Step
while next_Stop_Z >= drill_Z:
# Drill one peck depth:
trBuff += linenumber() + 'G1 Z' + format(float(next_Stop_Z.getValueAs(UNIT_FORMAT)), strFormat) + ' F' + format(float(drill_Speed.getValueAs(UNIT_SPEED_FORMAT)), '.2f') + "\n"
# Rapid up to clear chips:
trBuff += linenumber() + 'G0 Z' + format(float(Z_Rvalue.getValueAs(UNIT_FORMAT)), strFormat) + "\n"
# Rapid down to just above the last peck depth:
rapidDownZ = next_Stop_Z + (drill_Step * 0.5)
trBuff += linenumber() + 'G0 Z' + format(float(rapidDownZ.getValueAs(UNIT_FORMAT)), strFormat) + "\n"
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"
# Note: Marlin uses P for milliseconds, S for seconds. Change P to S.
# Set the next depth:
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:
feed_Z_to(next_Stop_Z) # Drill one peck depth
# Set the next depth, next_Stop_Z is still at the current hole depth
if (next_Stop_Z - drill_Step) >= drill_Z:
next_Stop_Z -= drill_Step
rapid_Z_to(drill_R) # Rapid up to clear chips
rapid_Z_to(next_Stop_Z + chip_Space) # Rapid down above last peck depth
next_Stop_Z -= drill_Step # Update next_Stop_Z to the next depth
elif next_Stop_Z == drill_Z:
break # Done
else:
trBuff += linenumber() + 'G1 Z' + format(float(drill_Z.getValueAs(UNIT_FORMAT)), strFormat) + ' F' + format(float(drill_Speed.getValueAs(UNIT_SPEED_FORMAT)), '.2f') + "\n"
else: # More to drill, but less than drill_Step
rapid_Z_to(drill_R) # Rapid up to clear chips
rapid_Z_to(next_Stop_Z + chip_Space) # Rapid down above last peck depth
feed_Z_to(drill_Z) # Dril remainder of the hole depth
break # Done
# Retract the drill:
trBuff += linenumber() + 'G0 Z' + format(float(RETRACT_Z.getValueAs(UNIT_FORMAT)), strFormat) + "\n"
rapid_Z_to(RETRACT_Z) # Done, retract the drill
if MOTION_MODE == 'G91':
trBuff += linenumber() + 'G91' # Restore relative positioning mode
return trBuff
return Drill.gcode
print(__name__ + ": GCode postprocessor loaded.")