| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | import FreeCAD |
| | from FreeCAD import Units |
| | import Path |
| | import argparse |
| | import datetime |
| | import shlex |
| | import Path.Base.Util as PathUtil |
| | import Path.Post.Utils as PostUtils |
| | import PathScripts.PathUtils as PathUtils |
| | from builtins import open as pyopen |
| |
|
| | TOOLTIP = """ |
| | This is a post processor file for the FreeCAD Path workbench. It is used to |
| | take a pseudo-G-code fragment outputted by a Path object, and output |
| | real G-code suitable for Dynapath Delta 40,50, & 60 Controls. It has been written |
| | and tested on FreeCAD Path workbench bundled with FreeCAD v21. |
| | 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 |
| | delta_4060_post.export(object,"/path/to/file.ncc","") |
| | """ |
| |
|
| | 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") |
| | 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\\n"', |
| | ) |
| | parser.add_argument( |
| | "--postamble", |
| | help='set commands to be issued after the last command, default="M09\\nM05\\nG80\\nG40\\nG17\\nG90\\nM30\\n"', |
| | ) |
| | 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() |
| |
|
| | |
| | OUTPUT_COMMENTS = True |
| | OUTPUT_HEADER = True |
| | OUTPUT_LINE_NUMBERS = False |
| | SHOW_EDITOR = True |
| | MODAL = False |
| | OUTPUT_DOUBLES = True |
| | COMMAND_SPACE = "" |
| | LINENR = 0 |
| | DWELL_TIME = 1 |
| | RETRACT_MODE = False |
| | QCYCLE_RANGE = ( |
| | "G81", |
| | "G82", |
| | "G83", |
| | "G84", |
| | "G85", |
| | ) |
| | SPINDLE_SPEED = 0 |
| | |
| | UNITS = "G71" |
| | UNIT_SPEED_FORMAT = "mm/min" |
| | UNIT_FORMAT = "mm" |
| | MACHINE_NAME = "Delta 4060" |
| | CORNER_MIN = {"x": 0, "y": 0, "z": 0} |
| | CORNER_MAX = {"x": 660, "y": 355, "z": 152} |
| | PRECISION = 3 |
| | ABSOLUTE_CIRCLE_CENTER = True |
| | |
| | |
| | |
| | |
| |
|
| | |
| | GCODE_MAP = { |
| | "G54": "E01", |
| | "G55": "E02", |
| | "G56": "E03", |
| | "G57": "E04", |
| | "G58": "E05", |
| | "G59": "E06", |
| | } |
| |
|
| | |
| | PREAMBLE = """G17 |
| | G90 |
| | G80 |
| | G40 |
| | """ |
| |
|
| | |
| | POSTAMBLE = """M05 |
| | G80 |
| | G40 |
| | G17 |
| | G90 |
| | M30 |
| | """ |
| | |
| | clearanceHeight = None |
| |
|
| |
|
| | 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 = int(args.precision) |
| | if args.preamble is not None: |
| | PREAMBLE = args.preamble.replace("\\n", "\n") |
| | if args.postamble is not None: |
| | POSTAMBLE = args.postamble.replace("\\n", "\n") |
| | 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 = "" |
| |
|
| | |
| | if OUTPUT_HEADER: |
| | gcode += "(%s)\n" % str.upper( |
| | obj.Document.Label[0:8] |
| | ) |
| | |
| | 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" |
| |
|
| | |
| | if OUTPUT_COMMENTS: |
| | gcode += linenumber() + "(T)" + "BEGIN PREAMBLE$\n" |
| | for line in PREAMBLE.splitlines(): |
| | gcode += linenumber() + line + "\n" |
| | gcode += linenumber() + UNITS + "\n" |
| |
|
| | for obj in objectslist: |
| |
|
| | |
| | if not PathUtil.activeForOp(obj): |
| | continue |
| |
|
| | if hasattr(obj, "ClearanceHeight"): |
| | clearanceHeight = obj.ClearanceHeight.Value |
| |
|
| | |
| | if obj.Label in GCODE_MAP: |
| | obj.Label = GCODE_MAP[obj.Label] |
| |
|
| | |
| | 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])) |
| | ) |
| |
|
| | |
| | coolantMode = PathUtil.coolantModeForOp(obj) |
| |
|
| | |
| | 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" |
| |
|
| | |
| | gcode += parse(obj) |
| |
|
| | |
| | if OUTPUT_COMMENTS: |
| | gcode += linenumber() + "(T)" + str.upper("finish operation: " + obj.Label) + "$\n" |
| |
|
| | |
| | if not coolantMode == "None": |
| | if OUTPUT_COMMENTS: |
| | gcode += linenumber() + "(T)" + str.upper("Coolant Off:" + coolantMode) + "$\n" |
| | gcode += linenumber() + "M9" + "\n" |
| |
|
| | |
| | if OUTPUT_COMMENTS: |
| | gcode += linenumber() + "(T)" + "BEGIN POSTAMBLE$\n" |
| | for line in POSTAMBLE.splitlines(): |
| | gcode += linenumber() + line + "\n" |
| | |
| | |
| | gcode += "E\n" |
| |
|
| | if FreeCAD.GuiUp and SHOW_EDITOR: |
| | dia = PostUtils.GCodeEditorDialog() |
| | dia.editor.setPlainText(gcode) |
| | result = dia.exec_() |
| | if result: |
| | final = dia.editor.toPlainText() |
| | else: |
| | final = gcode |
| | else: |
| | final = gcode |
| |
|
| | print("done postprocessing.") |
| |
|
| | if not filename == "-": |
| | gfile = pyopen(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) |
| | 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 = {} |
| |
|
| | |
| | |
| | |
| | |
| | params = [ |
| | "X", |
| | "Y", |
| | "Z", |
| | "A", |
| | "B", |
| | "C", |
| | "I", |
| | "J", |
| | "K", |
| | "F", |
| | "S", |
| | "T", |
| | "Q", |
| | "O", |
| | "R", |
| | "L", |
| | "P", |
| | ] |
| |
|
| | firstmove = Path.Command("G0", {"X": -1, "Y": -1, "Z": -1, "F": 0.0}) |
| | currLocation.update(firstmove.Parameters) |
| |
|
| | if hasattr(pathobj, "Group"): |
| | if OUTPUT_COMMENTS: |
| | out += linenumber() + "(T)" + str.upper("compound: " + pathobj.Label) + "$\n" |
| | for p in pathobj.Group: |
| | out += parse(p) |
| | return out |
| | else: |
| |
|
| | |
| | if not hasattr(pathobj, "Path"): |
| | return out |
| |
|
| | for c in PathUtils.getPathWithPlacement(pathobj).Commands: |
| | outstring = [] |
| | command = c.Name |
| | |
| | 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 is True: |
| | if command == lastcommand: |
| | outstring.pop(0) |
| |
|
| | if c.Name.startswith("(") and not OUTPUT_COMMENTS: |
| | continue |
| |
|
| | |
| | for param in params: |
| | if param in c.Parameters: |
| | if param == "F" and ( |
| | currLocation[param] != c.Parameters[param] or OUTPUT_DOUBLES |
| | ): |
| | speed = Units.Quantity(c.Parameters["F"], FreeCAD.Units.Velocity) |
| | if speed.getValueAs(UNIT_SPEED_FORMAT) > 0.0: |
| | outstring.append( |
| | param |
| | + format( |
| | float(speed.getValueAs(UNIT_SPEED_FORMAT)), |
| | precision_string, |
| | ) |
| | ) |
| | |
| | |
| | |
| | elif param == "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) |
| | ) |
| | |
| | |
| | |
| | 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"]) |
| | ) |
| | elif param == "T": |
| | outstring.append( |
| | param + "{:.0f}".format(c.Parameters["T"]) |
| | ) |
| | elif param == "I" and (command == "G2" or command == "G3"): |
| | |
| | |
| | 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"): |
| | |
| | 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"): |
| | |
| | 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), |
| | ) |
| | pos = Units.Quantity(c.Parameters["R"], FreeCAD.Units.Length) |
| | outstring.append( |
| | param + format(float(pos.getValueAs(UNIT_FORMAT)), precision_string) |
| | ) |
| | 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) |
| | ) |
| | |
| | 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"] |
| |
|
| | |
| | lastcommand = command |
| | currLocation.update(c.Parameters) |
| |
|
| | |
| | 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) |
| | |
| | if c.Name == "G98" or (c.Name == "G99" and pathobj.Label == "Drilling"): |
| | outstring = [] |
| |
|
| | |
| | if len(outstring) >= 1: |
| | if OUTPUT_LINE_NUMBERS: |
| | outstring.insert(0, (linenumber())) |
| |
|
| | |
| | for w in outstring: |
| | out += w.strip() + COMMAND_SPACE |
| | out += "\n" |
| |
|
| | |
| | |
| | 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 |
| |
|