From 79c393a16013bd4129de3208191bdb0a57c0d819 Mon Sep 17 00:00:00 2001 From: luvtofish Date: Tue, 6 Dec 2022 11:58:39 -0600 Subject: [PATCH 01/16] Add files via upload Initial upload --- .../Path/Post/scripts/dynapath_4060_post.py | 621 ++++++++++++++++++ 1 file changed, 621 insertions(+) create mode 100644 src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py diff --git a/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py b/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py new file mode 100644 index 0000000000..d5f185f7f4 --- /dev/null +++ b/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py @@ -0,0 +1,621 @@ +# *************************************************************************** +# * 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 = %d" % 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.") From 6247c357a938672f954a8eaa52fa1b2b071da9c5 Mon Sep 17 00:00:00 2001 From: luvtofish Date: Tue, 6 Dec 2022 15:05:52 -0600 Subject: [PATCH 02/16] Add files via upload Fixed format string on print statement. --- src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d5f185f7f4..66b9cda060 100644 --- a/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py +++ b/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py @@ -180,7 +180,7 @@ def processArguments(argstring): OUTPUT_LINE_NUMBERS = True if args.no_show_editor: SHOW_EDITOR = False - print("Show editor = %d" % SHOW_EDITOR) + print("Show editor = %r" % (SHOW_EDITOR)) if args.precision is not None: PRECISION = args.precision if args.preamble is not None: From 2911fad91a88a8ba2e94fad46f426692a1718aa7 Mon Sep 17 00:00:00 2001 From: luvtofish Date: Wed, 7 Dec 2022 10:19:24 -0600 Subject: [PATCH 03/16] Removed TLO since not used. --- .../Path/Post/scripts/dynapath_4060_post.py | 1240 ++++++++--------- 1 file changed, 619 insertions(+), 621 deletions(-) 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.") From d441b7aca9fd13eb93f3b0fbd7d721184913f55b Mon Sep 17 00:00:00 2001 From: luvtofish Date: Wed, 7 Dec 2022 10:24:01 -0600 Subject: [PATCH 04/16] removed print post processor print statement. --- src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py | 2 -- 1 file changed, 2 deletions(-) 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 ef4b864736..ff86a7b809 100644 --- a/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py +++ b/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py @@ -615,5 +615,3 @@ def parse(pathobj): out += linenumber() + "L%.1f\n" % DWELL_TIME return out - -print(__name__ + " gcode postprocessor loaded.") From 7f3dafaa31e7443b7934f1eb2ce3369b01cf17d3 Mon Sep 17 00:00:00 2001 From: luvtofish Date: Wed, 7 Dec 2022 20:49:40 -0600 Subject: [PATCH 05/16] Updated tooltip details --- src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 ff86a7b809..12222852c4 100644 --- a/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py +++ b/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py @@ -42,8 +42,8 @@ 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","") +import delta_4060_post +delta_4060_post.export(object,"/path/to/file.ncc","") """ parser = argparse.ArgumentParser(prog="delta_post", add_help=False) From ec5402dad3b625237be8dfe42b333b2f0ff74f6c Mon Sep 17 00:00:00 2001 From: luvtofish Date: Thu, 8 Dec 2022 08:47:10 -0600 Subject: [PATCH 06/16] tool tip changes --- src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 12222852c4..2a2a5a9259 100644 --- a/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py +++ b/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py @@ -38,7 +38,7 @@ 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. +and tested on FreeCAD Path workbench bundled with FreeCAD v21. 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: @@ -46,7 +46,7 @@ import delta_4060_post delta_4060_post.export(object,"/path/to/file.ncc","") """ -parser = argparse.ArgumentParser(prog="delta_post", add_help=False) +parser = argparse.ArgumentParser(prog="delta_4060", 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" @@ -113,7 +113,7 @@ SPINDLE_SPEED = 0 UNITS = "G71" # G71 for metric, G70 for US standard UNIT_SPEED_FORMAT = "mm/min" UNIT_FORMAT = "mm" -MACHINE_NAME = "TreeCNC" +MACHINE_NAME = "Delta 4060" CORNER_MIN = {"x": 0, "y": 0, "z": 0} CORNER_MAX = {"x": 660, "y": 355, "z": 152} PRECISION = 3 From 416872914cb806210b724092b6436560774a61a6 Mon Sep 17 00:00:00 2001 From: luvtofish Date: Tue, 6 Dec 2022 11:58:39 -0600 Subject: [PATCH 07/16] Add files via upload Initial upload --- .../Path/Post/scripts/dynapath_4060_post.py | 621 ++++++++++++++++++ 1 file changed, 621 insertions(+) create mode 100644 src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py diff --git a/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py b/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py new file mode 100644 index 0000000000..d5f185f7f4 --- /dev/null +++ b/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py @@ -0,0 +1,621 @@ +# *************************************************************************** +# * 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 = %d" % 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.") From f2c7e313536da05f5635b2d4bf860c850a0fcfa2 Mon Sep 17 00:00:00 2001 From: luvtofish Date: Tue, 6 Dec 2022 15:05:52 -0600 Subject: [PATCH 08/16] Add files via upload Fixed format string on print statement. --- src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d5f185f7f4..66b9cda060 100644 --- a/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py +++ b/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py @@ -180,7 +180,7 @@ def processArguments(argstring): OUTPUT_LINE_NUMBERS = True if args.no_show_editor: SHOW_EDITOR = False - print("Show editor = %d" % SHOW_EDITOR) + print("Show editor = %r" % (SHOW_EDITOR)) if args.precision is not None: PRECISION = args.precision if args.preamble is not None: From 85c70de31a09154f386363c4773652be50e05c9e Mon Sep 17 00:00:00 2001 From: luvtofish Date: Wed, 7 Dec 2022 10:19:24 -0600 Subject: [PATCH 09/16] Removed TLO since not used. --- .../Path/Post/scripts/dynapath_4060_post.py | 1240 ++++++++--------- 1 file changed, 619 insertions(+), 621 deletions(-) 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.") From 15dac5b12e6953471e349043f87c464fd49ddda5 Mon Sep 17 00:00:00 2001 From: luvtofish Date: Wed, 7 Dec 2022 10:24:01 -0600 Subject: [PATCH 10/16] removed print post processor print statement. --- src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py | 2 -- 1 file changed, 2 deletions(-) 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 ef4b864736..ff86a7b809 100644 --- a/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py +++ b/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py @@ -615,5 +615,3 @@ def parse(pathobj): out += linenumber() + "L%.1f\n" % DWELL_TIME return out - -print(__name__ + " gcode postprocessor loaded.") From 716d7c53acb50b6732b0ac1078bbe8b3fc8ac1c0 Mon Sep 17 00:00:00 2001 From: luvtofish Date: Wed, 7 Dec 2022 20:49:40 -0600 Subject: [PATCH 11/16] Updated tooltip details --- src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 ff86a7b809..12222852c4 100644 --- a/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py +++ b/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py @@ -42,8 +42,8 @@ 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","") +import delta_4060_post +delta_4060_post.export(object,"/path/to/file.ncc","") """ parser = argparse.ArgumentParser(prog="delta_post", add_help=False) From 4038ec8dee1654290165aa5cc4a742c761820a4b Mon Sep 17 00:00:00 2001 From: luvtofish Date: Thu, 8 Dec 2022 08:47:10 -0600 Subject: [PATCH 12/16] tool tip changes --- src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 12222852c4..2a2a5a9259 100644 --- a/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py +++ b/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py @@ -38,7 +38,7 @@ 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. +and tested on FreeCAD Path workbench bundled with FreeCAD v21. 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: @@ -46,7 +46,7 @@ import delta_4060_post delta_4060_post.export(object,"/path/to/file.ncc","") """ -parser = argparse.ArgumentParser(prog="delta_post", add_help=False) +parser = argparse.ArgumentParser(prog="delta_4060", 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" @@ -113,7 +113,7 @@ SPINDLE_SPEED = 0 UNITS = "G71" # G71 for metric, G70 for US standard UNIT_SPEED_FORMAT = "mm/min" UNIT_FORMAT = "mm" -MACHINE_NAME = "TreeCNC" +MACHINE_NAME = "Delta 4060" CORNER_MIN = {"x": 0, "y": 0, "z": 0} CORNER_MAX = {"x": 660, "y": 355, "z": 152} PRECISION = 3 From 170c7d862a8d1c0c1182354917e2a96bd8a2cb16 Mon Sep 17 00:00:00 2001 From: luvtofish Date: Thu, 8 Dec 2022 09:18:04 -0600 Subject: [PATCH 13/16] updated comment --- src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 2a2a5a9259..c1f9a2a8a4 100644 --- a/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py +++ b/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py @@ -20,7 +20,7 @@ # * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * # * USA * # * * -# * This file has been modified from Sliptonic original Linux CNC post * +# * This file has been modified from Sliptonic original LinuxCNC post * # * for use with Dynapath Delta 40M,50M,60M controllers. All changes * # * and modifications (c) luvtofish (luvtofish@gmail.com) 2022 * # *************************************************************************** From 6082ce76e05fb15eec9501be57184e92e5b48cb7 Mon Sep 17 00:00:00 2001 From: luvtofish Date: Sun, 11 Dec 2022 11:21:22 -0600 Subject: [PATCH 14/16] code cleanup --- .../Path/Post/scripts/dynapath_4060_post.py | 33 +++++++------------ 1 file changed, 12 insertions(+), 21 deletions(-) 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 c1f9a2a8a4..1d697c5225 100644 --- a/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py +++ b/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py @@ -442,23 +442,17 @@ def parse(pathobj): 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, - ) + 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 @@ -520,10 +514,8 @@ def parse(pathobj): k = c.Parameters["K"] if ABSOLUTE_CIRCLE_CENTER: k += lastZ - if command == ("G18" or "G19"): - outstring.append( - param + PostUtils.fmt(k, PRECISION, UNITS) - ) + 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( @@ -571,7 +563,6 @@ def parse(pathobj): float(pos.getValueAs(UNIT_FORMAT)), precision_string ) ) - # save the last X, Y values if "X" in c.Parameters: lastX = c.Parameters["X"] From 95dc53a0c8c04e7ec24d35cc486be8e2ec410738 Mon Sep 17 00:00:00 2001 From: luvtofish Date: Thu, 22 Dec 2022 12:13:44 -0600 Subject: [PATCH 15/16] Added multiple comments --- .../Path/Post/scripts/dynapath_4060_post.py | 48 ++++++++++++++----- 1 file changed, 36 insertions(+), 12 deletions(-) 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 1d697c5225..b92e3b35c2 100644 --- a/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py +++ b/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py @@ -118,11 +118,12 @@ CORNER_MIN = {"x": 0, "y": 0, "z": 0} CORNER_MAX = {"x": 660, "y": 355, "z": 152} PRECISION = 3 ABSOLUTE_CIRCLE_CENTER = True +# Dynapath requires absolute coordinates for arcs. # 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 +# Create Map / for renaming Fixture Commands from Gxx to Exx as needed by Dynapath. GCODE_MAP = { "G54": "E01", "G55": "E02", @@ -147,6 +148,7 @@ G17 G90 M30 """ +# Creat following variable for use with the 2nd reference plane. clearanceHeight = None # to distinguish python built-in open function from the one declared below @@ -227,7 +229,8 @@ def export(objectslist, filename, argstring): if OUTPUT_HEADER: gcode += "(%s)\n" % str.upper( obj.Document.Label[0:8] - ) # Added program name entry and limited to 8 chars. + ) # Added program name entry and limited to 8 chars as first line in program. + # Insert "T" in front of comments to signify "textfield" (comment). 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" @@ -251,11 +254,11 @@ def export(objectslist, filename, argstring): if hasattr(obj, "ClearanceHeight"): clearanceHeight = obj.ClearanceHeight.Value - # Fix fixture Offset label... + # Fix fixture Offset label. Needed by Dynapath. if obj.Label in GCODE_MAP: obj.Label = GCODE_MAP[obj.Label] - # do the pre_op + # do the pre_op. Inserts "(T)" in comment to signify textfield. if OUTPUT_COMMENTS: gcode += ( linenumber() @@ -294,8 +297,8 @@ def export(objectslist, filename, argstring): ) if coolantMode == "Flood": gcode += linenumber() + "M8" + "\n" - # if coolantMode == "Mist": - # gcode += linenumber() + "M7" + "\n" + if coolantMode == "Mist": + gcode += linenumber() + "M7" + "\n" # process the operation gcode gcode += parse(obj) @@ -325,6 +328,8 @@ def export(objectslist, filename, argstring): gcode += linenumber() + "(T)" + "BEGIN POSTAMBLE$\n" for line in POSTAMBLE.splitlines(True): gcode += linenumber() + line + # Following is required by Dyanpath Controls to signfiy "EOF" when loading in to control + # from external media. The control strips the "E" off as part of the load process. gcode += "E\n" if FreeCAD.GuiUp and SHOW_EDITOR: @@ -348,6 +353,7 @@ def export(objectslist, filename, argstring): return final +# Modified for Dynapath to Include a 4 digit field that begins with an "N". def linenumber(): global LINENR if OUTPUT_LINE_NUMBERS is True: @@ -377,6 +383,9 @@ def parse(pathobj): currLocation = {} # keep track for no doubles # the order of parameters + # Added O and L since these are needed by Dynapath. + # "O" is used as a 2nd reference plane in canned Cycles. + # "L" is equivelent to "P" for dwell entries. # params = ['X','Y','Z','A','B','I','J','K','F','S'] #This list control the order of parameters params = [ "X", @@ -387,15 +396,15 @@ def parse(pathobj): "C", "I", "J", + "K", "F", "S", "T", "Q", + "O", "R", "L", - "K", "P", - "O", ] firstmove = Path.Command("G0", {"X": -1, "Y": -1, "Z": -1, "F": 0.0}) @@ -422,6 +431,7 @@ def parse(pathobj): if command in GCODE_MAP: command = GCODE_MAP[command] + # Inserts "(T)" in front of comments to signify textfield (dyanapath) if command.startswith("("): if OUTPUT_COMMENTS: command = "(T)" + str.upper(command) + "$" @@ -453,6 +463,9 @@ def parse(pathobj): precision_string, ) ) + # Inserts "X0 Y0" in front of a G0 Z + clearanceHeight movement. + # This fixes an error thrown by Dynapath due to missing and + # required XYZ move after Toolchange. elif param == "Z" and ( c.Parameters["Z"] == clearanceHeight and c.Parameters["Z"] != lastZ @@ -486,19 +499,22 @@ def parse(pathobj): ) ) # Remove X and Y betwen QCYCLE's since we already included them. + # This is needed to prevent Path of inserting additional XY codes between + # Canned cycle holes. 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 + ) # Added formating to strip trailing .000 from RPM (needed by dynapath) elif param == "T": outstring.append( param + "{:.0f}".format(c.Parameters["T"]) - ) # Added formating to strip trailing .000 from Tool number + ) # Added formating to strip trailing .000 from Tool number (needed by dynapath) elif param == "I" and (command == "G2" or command == "G3"): # Convert incremental arc center to absolute in I and J + # Dynapath requires "absolute" arcs in (G2,G3) i = c.Parameters["I"] if ABSOLUTE_CIRCLE_CENTER: i += lastX @@ -514,8 +530,11 @@ def parse(pathobj): k = c.Parameters["K"] if ABSOLUTE_CIRCLE_CENTER: k += lastZ - if command == ("G18" or "G19"): + if command == ( + "G18" or "G19" + ): # Dynapath supports G18/G19 for Z axis arcs in Y or X. outstring.append(param + PostUtils.fmt(k, PRECISION, UNITS)) + # Converts "Q" to "K" as needed by Dynapath. elif param == "Q": pos = Units.Quantity(c.Parameters["Q"], FreeCAD.Units.Length) outstring.append( @@ -524,6 +543,10 @@ def parse(pathobj): float(pos.getValueAs(UNIT_FORMAT)), precision_string ) ) + # Following inserts a 2nd reference plane in all canned cycles (dynapath). + # This provides the ability to manually go in and bump up the "O" offset in + # order to avoid obstacles. The "O" overrides "R", so set them both equal if you + # don't need the 2nd reference plane. elif (param == "R") and ((command in QCYCLE_RANGE)): pos = Units.Quantity( pathobj.ClearanceHeight.Value, FreeCAD.Units.Length @@ -545,7 +568,7 @@ def parse(pathobj): elif param == "P": outstring.append( "L" + format(c.Parameters[param], precision_string) - ) + ) # Converts "P" to "L" for dynapath. else: if ( (not OUTPUT_DOUBLES) @@ -600,6 +623,7 @@ def parse(pathobj): out += "\n" # Add a DWELL after spindle start based on RPM. + # Used by Dynapath for implementing a scalable dwell time. if DWELL_TIME > 0.0: if command in ["M3", "M03", "M4", "M04"]: DWELL_TIME = (SPINDLE_SPEED / 100) / 10 From cc2dffb53d2990c2ba6e7750be46e3ebaa5506df Mon Sep 17 00:00:00 2001 From: luvtofish Date: Fri, 23 Dec 2022 18:13:01 -0600 Subject: [PATCH 16/16] Fixed some misspelling in comments. --- .../Path/Post/scripts/dynapath_4060_post.py | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) 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 b92e3b35c2..63113b188d 100644 --- a/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py +++ b/src/Mod/Path/Path/Post/scripts/dynapath_4060_post.py @@ -8,7 +8,7 @@ # * 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. * +# * for detail see the LICENSE text file. * # * * # * FreeCAD is distributed in the hope that it will be useful, * # * but WITHOUT ANY WARRANTY; without even the implied warranty of * @@ -35,11 +35,11 @@ import shlex import Path.Post.Utils as PostUtils TOOLTIP = """ -This is a postprocessor file for the FreeCAD Path workbench. It is used to +This is a post processor 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 v21. -This postprocessor, once placed in the appropriate PathScripts folder, can be +This post processor, 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_4060_post @@ -98,7 +98,7 @@ OUTPUT_DOUBLES = ( True # if false duplicate axis values are suppressed if the same as previous line. ) COMMAND_SPACE = "" -LINENR = 0 # line number starting value +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 = ( @@ -148,7 +148,7 @@ G17 G90 M30 """ -# Creat following variable for use with the 2nd reference plane. +# Create following variable for use with the 2nd reference plane. clearanceHeight = None # to distinguish python built-in open function from the one declared below @@ -328,7 +328,7 @@ def export(objectslist, filename, argstring): gcode += linenumber() + "(T)" + "BEGIN POSTAMBLE$\n" for line in POSTAMBLE.splitlines(True): gcode += linenumber() + line - # Following is required by Dyanpath Controls to signfiy "EOF" when loading in to control + # Following is required by Dynapath Controls to signify "EOF" when loading in to control # from external media. The control strips the "E" off as part of the load process. gcode += "E\n" @@ -358,9 +358,7 @@ def linenumber(): global LINENR if OUTPUT_LINE_NUMBERS is True: LINENR += 1 - return "N" + "%04d" % ( - LINENR - ) # + " " # Added formating for 4 digit line number. + return "N" + "%04d" % (LINENR) # Added formatting for 4 digit line number. return "" @@ -385,8 +383,7 @@ def parse(pathobj): # the order of parameters # Added O and L since these are needed by Dynapath. # "O" is used as a 2nd reference plane in canned Cycles. - # "L" is equivelent to "P" for dwell entries. - # params = ['X','Y','Z','A','B','I','J','K','F','S'] #This list control the order of parameters + # "L" is equivalent to "P" for dwell entries. params = [ "X", "Y", @@ -431,7 +428,7 @@ def parse(pathobj): if command in GCODE_MAP: command = GCODE_MAP[command] - # Inserts "(T)" in front of comments to signify textfield (dyanapath) + # Inserts "(T)" in front of comments to signify textfield (Dynapath) if command.startswith("("): if OUTPUT_COMMENTS: command = "(T)" + str.upper(command) + "$" @@ -465,7 +462,7 @@ def parse(pathobj): ) # Inserts "X0 Y0" in front of a G0 Z + clearanceHeight movement. # This fixes an error thrown by Dynapath due to missing and - # required XYZ move after Toolchange. + # required XYZ move after Tool change. elif param == "Z" and ( c.Parameters["Z"] == clearanceHeight and c.Parameters["Z"] != lastZ @@ -498,7 +495,7 @@ def parse(pathobj): float(pos.getValueAs(UNIT_FORMAT)), precision_string ) ) - # Remove X and Y betwen QCYCLE's since we already included them. + # Remove X and Y between QCYCLE's since we already included them. # This is needed to prevent Path of inserting additional XY codes between # Canned cycle holes. elif lastcommand in QCYCLE_RANGE and (param == "X" or "Y"): @@ -507,11 +504,11 @@ def parse(pathobj): SPINDLE_SPEED = c.Parameters["S"] outstring.append( param + "{:.0f}".format(c.Parameters["S"]) - ) # Added formating to strip trailing .000 from RPM (needed by dynapath) + ) # Added formatting to strip trailing .000 from RPM (needed by dynapath) elif param == "T": outstring.append( param + "{:.0f}".format(c.Parameters["T"]) - ) # Added formating to strip trailing .000 from Tool number (needed by dynapath) + ) # Added formatting to strip trailing .000 from Tool number (needed by dynapath) elif param == "I" and (command == "G2" or command == "G3"): # Convert incremental arc center to absolute in I and J # Dynapath requires "absolute" arcs in (G2,G3)