diff --git a/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py b/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py index 66b9cda060..ef4b864736 100644 --- a/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py +++ b/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py @@ -1,621 +1,619 @@ -# *************************************************************************** -# * Copyright (c) 2014 sliptonic * -# * Copyright (c) 2022 luvtofish * -# * * -# * 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 * -# * * -# * This file has been modified from Sliptonic original Linux CNC post * -# * for use with Dynapath Delta 40M,50M,60M controllers. All changes * -# * and modifications (c) luvtofish (luvtofish@gmail.com) 2022 * -# *************************************************************************** - -from __future__ import print_function -import FreeCAD -from FreeCAD import Units -import Path -import argparse -import datetime -import shlex -import Path.Post.Utils as PostUtils - -TOOLTIP = """ -This is a postprocessor file for the FreeCAD Path workbench. It is used to -take a pseudo-gcode fragment outputted by a Path object, and output -real GCode suitable for Dynapath Delta 40,50, & 60 Controls. It has been written -and tested on FreeCAD Path workbench bundled with FreeCAD v20. -This postprocessor, once placed in the appropriate PathScripts folder, can be -used directly from inside FreeCAD, via the GUI importer or via python scripts with: - -import delta_post -delta_post.export(object,"/path/to/file.ncc","") -""" - -parser = argparse.ArgumentParser(prog="delta_post", add_help=False) -parser.add_argument("--no-header", action="store_true", help="suppress header output") -parser.add_argument( - "--no-comments", action="store_true", help="suppress comment output" -) -parser.add_argument( - "--line-numbers", action="store_true", help="prefix with line numbers" -) -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( - "--preamble", - help='set commands to be issued before the first command, default="G17\nG90\nG80\nG40"', -) -parser.add_argument( - "--postamble", - help='set commands to be issued after the last command, default="M09\nM05\nG80\nG40\nG17\nG90\nM30"', -) -parser.add_argument( - "--inches", action="store_true", help="Convert output for US imperial mode (G70)" -) -parser.add_argument( - "--modal", - action="store_true", - help="Suppress outputting the Same G-command", -) -parser.add_argument( - "--axis-modal", - action="store_true", - help="Suppress outputting identical axis position", -) - -TOOLTIP_ARGS = parser.format_help() - -now = datetime.datetime.now() - -# These globals set common customization preferences -OUTPUT_COMMENTS = True -OUTPUT_HEADER = True -OUTPUT_LINE_NUMBERS = False -SHOW_EDITOR = True -MODAL = False # if true commands are suppressed if the same as previous line. -USE_TLO = False # if true G43 will be output following tool changes -OUTPUT_DOUBLES = ( - True # if false duplicate axis values are suppressed if the same as previous line. -) -COMMAND_SPACE = "" -LINENR = 0 # line number starting value -DWELL_TIME = 1 # Number of seconds to allow spindle to come up to speed. -RETRACT_MODE = False -QCYCLE_RANGE = ( - "G81", - "G82", - "G83", - "G84", - "G85", -) # Quill Cycle range for conditional to enable 2nd reference plane. -SPINDLE_SPEED = 0 -# These globals will be reflected in the Machine configuration of the project -UNITS = "G71" # G71 for metric, G70 for US standard -UNIT_SPEED_FORMAT = "mm/min" -UNIT_FORMAT = "mm" -MACHINE_NAME = "TreeCNC" -CORNER_MIN = {"x": 0, "y": 0, "z": 0} -CORNER_MAX = {"x": 660, "y": 355, "z": 152} -PRECISION = 3 -ABSOLUTE_CIRCLE_CENTER = True -# possible values: -# True use absolute values for the circle center in commands G2, G3 -# False values for I & J are incremental from last point - -# Create Map/ for renaming Fixture Commands from Gxx to Exx -GCODE_MAP = { - "G54": "E01", - "G55": "E02", - "G56": "E03", - "G57": "E04", - "G58": "E05", - "G59": "E06", -} - -# Preamble text will appear at the beginning of the GCODE output file. -PREAMBLE = """G17 -G90 -G80 -G40 -""" - -# Postamble text will appear following the last operation. -POSTAMBLE = """M05 -G80 -G40 -G17 -G90 -M30 -""" -clearanceHeight = None - -# to distinguish python built-in open function from the one declared below -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 MODAL - global USE_TLO - global OUTPUT_DOUBLES - - try: - args = parser.parse_args(shlex.split(argstring)) - if args.no_header: - OUTPUT_HEADER = False - if args.no_comments: - OUTPUT_COMMENTS = False - if args.line_numbers: - OUTPUT_LINE_NUMBERS = True - if args.no_show_editor: - SHOW_EDITOR = False - print("Show editor = %r" % (SHOW_EDITOR)) - if args.precision is not None: - PRECISION = args.precision - if args.preamble is not None: - PREAMBLE = args.preamble - if args.postamble is not None: - POSTAMBLE = args.postamble - if args.inches: - UNITS = "G70" - UNIT_SPEED_FORMAT = "in/min" - UNIT_FORMAT = "in" - PRECISION = 3 - if args.modal: - MODAL = True - print("Command duplicates suppressed") - if args.axis_modal: - OUTPUT_DOUBLES = False - print("Doubles suppressed") - - except Exception: - return False - - return True - - -def export(objectslist, filename, argstring): - if not processArguments(argstring): - return None - global UNITS - global UNIT_FORMAT - global UNIT_SPEED_FORMAT - global clearanceHeight - - for obj in objectslist: - if not hasattr(obj, "Path"): - print( - "the object " - + obj.Name - + " is not a path. Please select only path and Compounds." - ) - return None - - print("postprocessing...") - gcode = "" - - # write header - if OUTPUT_HEADER: - gcode += "(%s)\n" % str.upper( - obj.Document.Label[0:8] - ) # Added program name entry and limited to 8 chars. - gcode += linenumber() + "(T)" + "EXPORTED BY FREECAD$\n" - gcode += linenumber() + "(T)" + str.upper("Post Processor: " + __name__) + "$\n" - gcode += linenumber() + "(T)" + str.upper("Output Time:") + str(now) + "$\n" - - # Write the preamble - if OUTPUT_COMMENTS: - gcode += linenumber() + "(T)" + "BEGIN PREAMBLE$\n" - for line in PREAMBLE.splitlines(True): - gcode += linenumber() + line - gcode += linenumber() + UNITS + "\n" - - for obj in objectslist: - - # Skip inactive operations - if hasattr(obj, "Active"): - if not obj.Active: - continue - if hasattr(obj, "Base") and hasattr(obj.Base, "Active"): - if not obj.Base.Active: - continue - if hasattr(obj, "ClearanceHeight"): - clearanceHeight = obj.ClearanceHeight.Value - - # Fix fixture Offset label... - if obj.Label in GCODE_MAP: - obj.Label = GCODE_MAP[obj.Label] - - # do the pre_op - if OUTPUT_COMMENTS: - gcode += ( - linenumber() - + "(T)" - + str.upper("begin operation: " + obj.Label) - + "$\n" - ) - gcode += ( - linenumber() - + "(T)" - + str.upper("machine units: ") - + "%s/%s$\n" - % (str.upper(UNIT_SPEED_FORMAT[0:2]), str.upper(UNIT_SPEED_FORMAT[3:6])) - ) - - # 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() - + "(T)" - + str.upper("Coolant On:" + coolantMode) - + "$\n" - ) - if coolantMode == "Flood": - gcode += linenumber() + "M8" + "\n" - # if coolantMode == "Mist": - # gcode += linenumber() + "M7" + "\n" - - # process the operation gcode - gcode += parse(obj) - - # do the post_op - if OUTPUT_COMMENTS: - gcode += ( - linenumber() - + "(T)" - + str.upper("finish operation: " + obj.Label) - + "$\n" - ) - - # turn coolant off if required - if not coolantMode == "None": - if OUTPUT_COMMENTS: - gcode += ( - linenumber() - + "(T)" - + str.upper("Coolant Off:" + coolantMode) - + "$\n" - ) - gcode += linenumber() + "M9" + "\n" - - # do the post_amble - if OUTPUT_COMMENTS: - gcode += linenumber() + "(T)" + "BEGIN POSTAMBLE$\n" - for line in POSTAMBLE.splitlines(True): - gcode += linenumber() + line - gcode += "E\n" - - 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.") - - if not filename == "-": - gfile = pythonopen(filename, "w") - gfile.write(final) - gfile.close() - - return final - - -def linenumber(): - global LINENR - if OUTPUT_LINE_NUMBERS is True: - LINENR += 1 - return "N" + "%04d" % ( - LINENR - ) # + " " # Added formating for 4 digit line number. - return "" - - -def parse(pathobj): - global PRECISION - global MODAL - global OUTPUT_DOUBLES - global UNIT_FORMAT - global UNIT_SPEED_FORMAT - global RETRACT_MODE - global DWELL_TIME - global clearanceHeight - - lastX = 0 - lastY = 0 - lastZ = 0 - out = "" - lastcommand = None - precision_string = "." + str(PRECISION) + "f" - currLocation = {} # keep track for no doubles - - # the order of parameters - # params = ['X','Y','Z','A','B','I','J','K','F','S'] #This list control the order of parameters - params = [ - "X", - "Y", - "Z", - "A", - "B", - "C", - "I", - "J", - "F", - "S", - "T", - "Q", - "R", - "L", - "K", - "P", - "O", - ] - - firstmove = Path.Command("G0", {"X": -1, "Y": -1, "Z": -1, "F": 0.0}) - currLocation.update(firstmove.Parameters) # set first location parameters - - if hasattr(pathobj, "Group"): # We have a compound or project. - if OUTPUT_COMMENTS: - out += ( - linenumber() + "(T)" + str.upper("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"): - return out - - for c in pathobj.Path.Commands: - outstring = [] - command = c.Name - # Convert G54-G59 Fixture offsets to E01-E06 for Dynapath Delta Control - if command in GCODE_MAP: - command = GCODE_MAP[command] - - if command.startswith("("): - if OUTPUT_COMMENTS: - command = "(T)" + str.upper(command) + "$" - - outstring.append(command) - - # if modal: suppress the command if it is the same as the last one - if MODAL is True: - if command == lastcommand: - outstring.pop(0) - - if c.Name[0] == "(" and not OUTPUT_COMMENTS: # command is a comment - continue - - # Now add the remaining parameters in order - for param in params: - if param in c.Parameters: - if param == "F" and ( - currLocation[param] != c.Parameters[param] or OUTPUT_DOUBLES - ): - if c.Name not in [ - "G0", - "G00", - ]: - 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, - ) - ) - else: - continue - elif param == "Z" and ( - c.Parameters["Z"] == clearanceHeight - and c.Parameters["Z"] != lastZ - ): - x = 0 - y = 0 - outstring.insert( - 1, - "X" - + PostUtils.fmt(x, PRECISION, UNITS) - + "Y" - + PostUtils.fmt(y, PRECISION, UNITS), - ) - outstring.append( - param + PostUtils.fmt(c.Parameters["Z"], PRECISION, UNITS) - ) - elif param == "X" and (command in QCYCLE_RANGE): - pos = Units.Quantity(c.Parameters["X"], FreeCAD.Units.Length) - outstring.append( - param - + format( - float(pos.getValueAs(UNIT_FORMAT)), precision_string - ) - ) - elif param == "Y" and (command in QCYCLE_RANGE): - pos = Units.Quantity(c.Parameters["Y"], FreeCAD.Units.Length) - outstring.append( - param - + format( - float(pos.getValueAs(UNIT_FORMAT)), precision_string - ) - ) - # Remove X and Y betwen QCYCLE's since we already included them. - elif lastcommand in QCYCLE_RANGE and (param == "X" or "Y"): - outstring = [] - elif param == "S": - SPINDLE_SPEED = c.Parameters["S"] - outstring.append( - param + "{:.0f}".format(c.Parameters["S"]) - ) # Added formating to strip trailing .000 from RPM - elif param == "T": - outstring.append( - param + "{:.0f}".format(c.Parameters["T"]) - ) # Added formating to strip trailing .000 from Tool number - elif param == "I" and (command == "G2" or command == "G3"): - # Convert incremental arc center to absolute in I and J - i = c.Parameters["I"] - if ABSOLUTE_CIRCLE_CENTER: - i += lastX - outstring.append(param + PostUtils.fmt(i, PRECISION, UNITS)) - elif param == "J" and (command == "G2" or command == "G3"): - # Convert incremental arc center to absolute in I and J - j = c.Parameters["J"] - if ABSOLUTE_CIRCLE_CENTER: - j += lastY - outstring.append(param + PostUtils.fmt(j, PRECISION, UNITS)) - elif param == "K" and (command == "G2" or command == "G3"): - # Convert incremental arc center to absolute in K (Z axis arc) - k = c.Parameters["K"] - if ABSOLUTE_CIRCLE_CENTER: - k += lastZ - if command == ("G18" or "G19"): - outstring.append( - param + PostUtils.fmt(k, PRECISION, UNITS) - ) - elif param == "Q": - pos = Units.Quantity(c.Parameters["Q"], FreeCAD.Units.Length) - outstring.append( - "K" - + format( - float(pos.getValueAs(UNIT_FORMAT)), precision_string - ) - ) - elif (param == "R") and ((command in QCYCLE_RANGE)): - pos = Units.Quantity( - pathobj.ClearanceHeight.Value, FreeCAD.Units.Length - ) - outstring.insert( - 6, - "O" - + format( - float(pos.getValueAs(UNIT_FORMAT)), precision_string - ), - ) # Insert "O" param for 2nd reference plane (Clearance Height) - pos = Units.Quantity(c.Parameters["R"], FreeCAD.Units.Length) - outstring.append( - param - + format( - float(pos.getValueAs(UNIT_FORMAT)), precision_string - ) - ) # First Reference plan (Safe Height) - elif param == "P": - outstring.append( - "L" + format(c.Parameters[param], precision_string) - ) - else: - if ( - (not OUTPUT_DOUBLES) - and (param in currLocation) - and (currLocation[param] == c.Parameters[param]) - ): - continue - else: - pos = Units.Quantity( - c.Parameters[param], FreeCAD.Units.Length - ) - outstring.append( - param - + format( - float(pos.getValueAs(UNIT_FORMAT)), precision_string - ) - ) - - # save the last X, Y values - if "X" in c.Parameters: - lastX = c.Parameters["X"] - if "Y" in c.Parameters: - lastY = c.Parameters["Y"] - if "Z" in c.Parameters: - lastZ = c.Parameters["Z"] - - # store the latest command - lastcommand = command - currLocation.update(c.Parameters) - - # Check for Tool Change: - if command == "M6": - if OUTPUT_COMMENTS: - out += linenumber() + "(T)" + "BEGIN TOOLCHANGE$\n" - - if command == "message": - if OUTPUT_COMMENTS is False: - out = [] - else: - outstring.pop(0) # remove the command - # G98 is not used by dynapath and G99 is not used in Drilling/Boring/Tapping. - if c.Name == "G98" or (c.Name == "G99" and pathobj.Label == "Drilling"): - outstring = [] - - # prepend a line number and append a newline - if len(outstring) >= 1: - if OUTPUT_LINE_NUMBERS: - outstring.insert(0, (linenumber())) - - # append the line to the final output - for w in outstring: - out += w.strip() + COMMAND_SPACE - out += "\n" - - # Add a DWELL after spindle start based on RPM. - if DWELL_TIME > 0.0: - if command in ["M3", "M03", "M4", "M04"]: - DWELL_TIME = (SPINDLE_SPEED / 100) / 10 - out += linenumber() + "L%.1f\n" % DWELL_TIME - - return out - -print(__name__ + " gcode postprocessor loaded.") +# *************************************************************************** +# * Copyright (c) 2014 sliptonic * +# * Copyright (c) 2022 luvtofish * +# * * +# * 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 * +# * * +# * This file has been modified from Sliptonic original Linux CNC post * +# * for use with Dynapath Delta 40M,50M,60M controllers. All changes * +# * and modifications (c) luvtofish (luvtofish@gmail.com) 2022 * +# *************************************************************************** + +from __future__ import print_function +import FreeCAD +from FreeCAD import Units +import Path +import argparse +import datetime +import shlex +import Path.Post.Utils as PostUtils + +TOOLTIP = """ +This is a postprocessor file for the FreeCAD Path workbench. It is used to +take a pseudo-gcode fragment outputted by a Path object, and output +real GCode suitable for Dynapath Delta 40,50, & 60 Controls. It has been written +and tested on FreeCAD Path workbench bundled with FreeCAD v20. +This postprocessor, once placed in the appropriate PathScripts folder, can be +used directly from inside FreeCAD, via the GUI importer or via python scripts with: + +import delta_post +delta_post.export(object,"/path/to/file.ncc","") +""" + +parser = argparse.ArgumentParser(prog="delta_post", add_help=False) +parser.add_argument("--no-header", action="store_true", help="suppress header output") +parser.add_argument( + "--no-comments", action="store_true", help="suppress comment output" +) +parser.add_argument( + "--line-numbers", action="store_true", help="prefix with line numbers" +) +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( + "--preamble", + help='set commands to be issued before the first command, default="G17\nG90\nG80\nG40"', +) +parser.add_argument( + "--postamble", + help='set commands to be issued after the last command, default="M09\nM05\nG80\nG40\nG17\nG90\nM30"', +) +parser.add_argument( + "--inches", action="store_true", help="Convert output for US imperial mode (G70)" +) +parser.add_argument( + "--modal", + action="store_true", + help="Suppress outputting the Same G-command", +) +parser.add_argument( + "--axis-modal", + action="store_true", + help="Suppress outputting identical axis position", +) + +TOOLTIP_ARGS = parser.format_help() + +now = datetime.datetime.now() + +# These globals set common customization preferences +OUTPUT_COMMENTS = True +OUTPUT_HEADER = True +OUTPUT_LINE_NUMBERS = False +SHOW_EDITOR = True +MODAL = False # if true commands are suppressed if the same as previous line. +OUTPUT_DOUBLES = ( + True # if false duplicate axis values are suppressed if the same as previous line. +) +COMMAND_SPACE = "" +LINENR = 0 # line number starting value +DWELL_TIME = 1 # Number of seconds to allow spindle to come up to speed. +RETRACT_MODE = False +QCYCLE_RANGE = ( + "G81", + "G82", + "G83", + "G84", + "G85", +) # Quill Cycle range for conditional to enable 2nd reference plane. +SPINDLE_SPEED = 0 +# These globals will be reflected in the Machine configuration of the project +UNITS = "G71" # G71 for metric, G70 for US standard +UNIT_SPEED_FORMAT = "mm/min" +UNIT_FORMAT = "mm" +MACHINE_NAME = "TreeCNC" +CORNER_MIN = {"x": 0, "y": 0, "z": 0} +CORNER_MAX = {"x": 660, "y": 355, "z": 152} +PRECISION = 3 +ABSOLUTE_CIRCLE_CENTER = True +# possible values: +# True use absolute values for the circle center in commands G2, G3 +# False values for I & J are incremental from last point + +# Create Map/ for renaming Fixture Commands from Gxx to Exx +GCODE_MAP = { + "G54": "E01", + "G55": "E02", + "G56": "E03", + "G57": "E04", + "G58": "E05", + "G59": "E06", +} + +# Preamble text will appear at the beginning of the GCODE output file. +PREAMBLE = """G17 +G90 +G80 +G40 +""" + +# Postamble text will appear following the last operation. +POSTAMBLE = """M05 +G80 +G40 +G17 +G90 +M30 +""" +clearanceHeight = None + +# to distinguish python built-in open function from the one declared below +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 MODAL + global OUTPUT_DOUBLES + + try: + args = parser.parse_args(shlex.split(argstring)) + if args.no_header: + OUTPUT_HEADER = False + if args.no_comments: + OUTPUT_COMMENTS = False + if args.line_numbers: + OUTPUT_LINE_NUMBERS = True + if args.no_show_editor: + SHOW_EDITOR = False + print("Show editor = %r" % (SHOW_EDITOR)) + if args.precision is not None: + PRECISION = args.precision + if args.preamble is not None: + PREAMBLE = args.preamble + if args.postamble is not None: + POSTAMBLE = args.postamble + if args.inches: + UNITS = "G70" + UNIT_SPEED_FORMAT = "in/min" + UNIT_FORMAT = "in" + PRECISION = 3 + if args.modal: + MODAL = True + print("Command duplicates suppressed") + if args.axis_modal: + OUTPUT_DOUBLES = False + print("Doubles suppressed") + + except Exception: + return False + + return True + + +def export(objectslist, filename, argstring): + if not processArguments(argstring): + return None + global UNITS + global UNIT_FORMAT + global UNIT_SPEED_FORMAT + global clearanceHeight + + for obj in objectslist: + if not hasattr(obj, "Path"): + print( + "the object " + + obj.Name + + " is not a path. Please select only path and Compounds." + ) + return None + + print("postprocessing...") + gcode = "" + + # write header + if OUTPUT_HEADER: + gcode += "(%s)\n" % str.upper( + obj.Document.Label[0:8] + ) # Added program name entry and limited to 8 chars. + gcode += linenumber() + "(T)" + "EXPORTED BY FREECAD$\n" + gcode += linenumber() + "(T)" + str.upper("Post Processor: " + __name__) + "$\n" + gcode += linenumber() + "(T)" + str.upper("Output Time:") + str(now) + "$\n" + + # Write the preamble + if OUTPUT_COMMENTS: + gcode += linenumber() + "(T)" + "BEGIN PREAMBLE$\n" + for line in PREAMBLE.splitlines(True): + gcode += linenumber() + line + gcode += linenumber() + UNITS + "\n" + + for obj in objectslist: + + # Skip inactive operations + if hasattr(obj, "Active"): + if not obj.Active: + continue + if hasattr(obj, "Base") and hasattr(obj.Base, "Active"): + if not obj.Base.Active: + continue + if hasattr(obj, "ClearanceHeight"): + clearanceHeight = obj.ClearanceHeight.Value + + # Fix fixture Offset label... + if obj.Label in GCODE_MAP: + obj.Label = GCODE_MAP[obj.Label] + + # do the pre_op + if OUTPUT_COMMENTS: + gcode += ( + linenumber() + + "(T)" + + str.upper("begin operation: " + obj.Label) + + "$\n" + ) + gcode += ( + linenumber() + + "(T)" + + str.upper("machine units: ") + + "%s/%s$\n" + % (str.upper(UNIT_SPEED_FORMAT[0:2]), str.upper(UNIT_SPEED_FORMAT[3:6])) + ) + + # 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() + + "(T)" + + str.upper("Coolant On:" + coolantMode) + + "$\n" + ) + if coolantMode == "Flood": + gcode += linenumber() + "M8" + "\n" + # if coolantMode == "Mist": + # gcode += linenumber() + "M7" + "\n" + + # process the operation gcode + gcode += parse(obj) + + # do the post_op + if OUTPUT_COMMENTS: + gcode += ( + linenumber() + + "(T)" + + str.upper("finish operation: " + obj.Label) + + "$\n" + ) + + # turn coolant off if required + if not coolantMode == "None": + if OUTPUT_COMMENTS: + gcode += ( + linenumber() + + "(T)" + + str.upper("Coolant Off:" + coolantMode) + + "$\n" + ) + gcode += linenumber() + "M9" + "\n" + + # do the post_amble + if OUTPUT_COMMENTS: + gcode += linenumber() + "(T)" + "BEGIN POSTAMBLE$\n" + for line in POSTAMBLE.splitlines(True): + gcode += linenumber() + line + gcode += "E\n" + + 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.") + + if not filename == "-": + gfile = pythonopen(filename, "w") + gfile.write(final) + gfile.close() + + return final + + +def linenumber(): + global LINENR + if OUTPUT_LINE_NUMBERS is True: + LINENR += 1 + return "N" + "%04d" % ( + LINENR + ) # + " " # Added formating for 4 digit line number. + return "" + + +def parse(pathobj): + global PRECISION + global MODAL + global OUTPUT_DOUBLES + global UNIT_FORMAT + global UNIT_SPEED_FORMAT + global RETRACT_MODE + global DWELL_TIME + global clearanceHeight + + lastX = 0 + lastY = 0 + lastZ = 0 + out = "" + lastcommand = None + precision_string = "." + str(PRECISION) + "f" + currLocation = {} # keep track for no doubles + + # the order of parameters + # params = ['X','Y','Z','A','B','I','J','K','F','S'] #This list control the order of parameters + params = [ + "X", + "Y", + "Z", + "A", + "B", + "C", + "I", + "J", + "F", + "S", + "T", + "Q", + "R", + "L", + "K", + "P", + "O", + ] + + firstmove = Path.Command("G0", {"X": -1, "Y": -1, "Z": -1, "F": 0.0}) + currLocation.update(firstmove.Parameters) # set first location parameters + + if hasattr(pathobj, "Group"): # We have a compound or project. + if OUTPUT_COMMENTS: + out += ( + linenumber() + "(T)" + str.upper("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"): + return out + + for c in pathobj.Path.Commands: + outstring = [] + command = c.Name + # Convert G54-G59 Fixture offsets to E01-E06 for Dynapath Delta Control + if command in GCODE_MAP: + command = GCODE_MAP[command] + + if command.startswith("("): + if OUTPUT_COMMENTS: + command = "(T)" + str.upper(command) + "$" + + outstring.append(command) + + # if modal: suppress the command if it is the same as the last one + if MODAL is True: + if command == lastcommand: + outstring.pop(0) + + if c.Name[0] == "(" and not OUTPUT_COMMENTS: # command is a comment + continue + + # Now add the remaining parameters in order + for param in params: + if param in c.Parameters: + if param == "F" and ( + currLocation[param] != c.Parameters[param] or OUTPUT_DOUBLES + ): + if c.Name not in [ + "G0", + "G00", + ]: + 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, + ) + ) + else: + continue + elif param == "Z" and ( + c.Parameters["Z"] == clearanceHeight + and c.Parameters["Z"] != lastZ + ): + x = 0 + y = 0 + outstring.insert( + 1, + "X" + + PostUtils.fmt(x, PRECISION, UNITS) + + "Y" + + PostUtils.fmt(y, PRECISION, UNITS), + ) + outstring.append( + param + PostUtils.fmt(c.Parameters["Z"], PRECISION, UNITS) + ) + elif param == "X" and (command in QCYCLE_RANGE): + pos = Units.Quantity(c.Parameters["X"], FreeCAD.Units.Length) + outstring.append( + param + + format( + float(pos.getValueAs(UNIT_FORMAT)), precision_string + ) + ) + elif param == "Y" and (command in QCYCLE_RANGE): + pos = Units.Quantity(c.Parameters["Y"], FreeCAD.Units.Length) + outstring.append( + param + + format( + float(pos.getValueAs(UNIT_FORMAT)), precision_string + ) + ) + # Remove X and Y betwen QCYCLE's since we already included them. + elif lastcommand in QCYCLE_RANGE and (param == "X" or "Y"): + outstring = [] + elif param == "S": + SPINDLE_SPEED = c.Parameters["S"] + outstring.append( + param + "{:.0f}".format(c.Parameters["S"]) + ) # Added formating to strip trailing .000 from RPM + elif param == "T": + outstring.append( + param + "{:.0f}".format(c.Parameters["T"]) + ) # Added formating to strip trailing .000 from Tool number + elif param == "I" and (command == "G2" or command == "G3"): + # Convert incremental arc center to absolute in I and J + i = c.Parameters["I"] + if ABSOLUTE_CIRCLE_CENTER: + i += lastX + outstring.append(param + PostUtils.fmt(i, PRECISION, UNITS)) + elif param == "J" and (command == "G2" or command == "G3"): + # Convert incremental arc center to absolute in I and J + j = c.Parameters["J"] + if ABSOLUTE_CIRCLE_CENTER: + j += lastY + outstring.append(param + PostUtils.fmt(j, PRECISION, UNITS)) + elif param == "K" and (command == "G2" or command == "G3"): + # Convert incremental arc center to absolute in K (Z axis arc) + k = c.Parameters["K"] + if ABSOLUTE_CIRCLE_CENTER: + k += lastZ + if command == ("G18" or "G19"): + outstring.append( + param + PostUtils.fmt(k, PRECISION, UNITS) + ) + elif param == "Q": + pos = Units.Quantity(c.Parameters["Q"], FreeCAD.Units.Length) + outstring.append( + "K" + + format( + float(pos.getValueAs(UNIT_FORMAT)), precision_string + ) + ) + elif (param == "R") and ((command in QCYCLE_RANGE)): + pos = Units.Quantity( + pathobj.ClearanceHeight.Value, FreeCAD.Units.Length + ) + outstring.insert( + 6, + "O" + + format( + float(pos.getValueAs(UNIT_FORMAT)), precision_string + ), + ) # Insert "O" param for 2nd reference plane (Clearance Height) + pos = Units.Quantity(c.Parameters["R"], FreeCAD.Units.Length) + outstring.append( + param + + format( + float(pos.getValueAs(UNIT_FORMAT)), precision_string + ) + ) # First Reference plan (Safe Height) + elif param == "P": + outstring.append( + "L" + format(c.Parameters[param], precision_string) + ) + else: + if ( + (not OUTPUT_DOUBLES) + and (param in currLocation) + and (currLocation[param] == c.Parameters[param]) + ): + continue + else: + pos = Units.Quantity( + c.Parameters[param], FreeCAD.Units.Length + ) + outstring.append( + param + + format( + float(pos.getValueAs(UNIT_FORMAT)), precision_string + ) + ) + + # save the last X, Y values + if "X" in c.Parameters: + lastX = c.Parameters["X"] + if "Y" in c.Parameters: + lastY = c.Parameters["Y"] + if "Z" in c.Parameters: + lastZ = c.Parameters["Z"] + + # store the latest command + lastcommand = command + currLocation.update(c.Parameters) + + # Check for Tool Change: + if command == "M6": + if OUTPUT_COMMENTS: + out += linenumber() + "(T)" + "BEGIN TOOLCHANGE$\n" + + if command == "message": + if OUTPUT_COMMENTS is False: + out = [] + else: + outstring.pop(0) # remove the command + # G98 is not used by dynapath and G99 is not used in Drilling/Boring/Tapping. + if c.Name == "G98" or (c.Name == "G99" and pathobj.Label == "Drilling"): + outstring = [] + + # prepend a line number and append a newline + if len(outstring) >= 1: + if OUTPUT_LINE_NUMBERS: + outstring.insert(0, (linenumber())) + + # append the line to the final output + for w in outstring: + out += w.strip() + COMMAND_SPACE + out += "\n" + + # Add a DWELL after spindle start based on RPM. + if DWELL_TIME > 0.0: + if command in ["M3", "M03", "M4", "M04"]: + DWELL_TIME = (SPINDLE_SPEED / 100) / 10 + out += linenumber() + "L%.1f\n" % DWELL_TIME + + return out + +print(__name__ + " gcode postprocessor loaded.")