| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| |
|
| |
|
| | import FreeCAD |
| | from FreeCAD import Units |
| | import Path |
| | import Path.Base.Util as PathUtil |
| | import PathScripts.PathUtils as PathUtils |
| | import argparse |
| | import datetime |
| |
|
| | |
| | import Path.Post.Utils as PostUtils |
| | from builtins import open as pyopen |
| |
|
| | VERSION = "0.0.4" |
| |
|
| | TOOLTIP = """ Post processor for UC-CNC. |
| | |
| | This is a postprocessor file for the Path workbench. It is used to |
| | take a pseudo-G-code fragment outputted by a Path object, and output |
| | real G-code. This postprocessor, once placed in the appropriate |
| | Path/Tool folder, can be used directly from inside FreeCAD, |
| | via the GUI importer or via python scripts with: |
| | |
| | import UCCNC_post |
| | UCCNC_post.export(object,"/path/to/file.ncc","") |
| | |
| | This postprocessor was tested on UC-CNC v1.2111, an UC100 and a Stepcraft 420. |
| | It was tested on FreeCAD v0.17, v0.18 and v0.19 |
| | |
| | Other (Stepcraft) machines using UC-CNC and UC* controllers should be easy to adapt. |
| | """ |
| |
|
| | |
| | |
| | |
| | PREAMBLE_DEFAULT = """G17 (Default: XY-plane) |
| | G54 (Default: First coordinate system) |
| | G40 (Default: Cutter radius compensation none) |
| | G49 (Default: Tool Length Offsets: cancel tool length) |
| | G90 (Default: Absolute distance mode selection) |
| | G80 (Cancel canned cycle) |
| | """ |
| |
|
| | PREAMBLE_DEFAULT_NO_COMMENT = """G17 |
| | G54 |
| | G40 |
| | G49 |
| | G90 |
| | G80 |
| | """ |
| |
|
| |
|
| | |
| | |
| | |
| | POSTAMBLE_DEFAULT = """M05 (stop spindle) |
| | G17 (Default: XY-plane) |
| | G54 (Default: First coordinate system) |
| | G40 (Default: Cutter radius compensation none) |
| | G90 (Default: Absolute distance mode selection) |
| | G80 (Cancel canned cycle) |
| | M30 (Stop program and rewind code) |
| | """ |
| |
|
| | POSTAMBLE_DEFAULT_NO_COMMENT = """M05 |
| | G17 |
| | G54 |
| | G40 |
| | G90 |
| | G80 |
| | M30 |
| | """ |
| |
|
| | |
| | PRE_OPERATION = """""" |
| |
|
| | |
| | POST_OPERATION = """""" |
| |
|
| | |
| | TOOL_CHANGE = """""" |
| |
|
| | |
| | |
| | |
| |
|
| | |
| | |
| | GCODE_PROCESSOR = "UC-CNC" |
| |
|
| | |
| | |
| | |
| | |
| | |
| | if FreeCAD.GuiUp: |
| | SHOW_EDITOR = True |
| | else: |
| | SHOW_EDITOR = False |
| |
|
| | |
| | |
| | |
| | PROG_NAME = "prog1" |
| |
|
| | |
| | |
| | |
| | |
| | |
| | OUTPUT_HEADER = True |
| |
|
| | |
| | |
| | |
| | |
| | |
| | OUTPUT_COMMENTS = True |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | OUTPUT_LINE_NUMBERS = False |
| |
|
| | |
| | |
| | |
| | LINE_NUMBER_START = 0 |
| |
|
| | |
| | |
| | |
| | LINE_NUMBER_STEP = 1 |
| |
|
| | |
| | |
| | |
| | |
| | PREAMBLE = PREAMBLE_DEFAULT |
| |
|
| | |
| | |
| | |
| | |
| | POSTAMBLE = POSTAMBLE_DEFAULT |
| |
|
| | |
| | |
| | |
| | |
| | |
| | MODAL = False |
| |
|
| | |
| | |
| | |
| | |
| | |
| | REPEAT_ARGUMENTS = False |
| |
|
| | |
| | |
| | |
| | |
| | |
| | USE_TLO = False |
| |
|
| | |
| | |
| | |
| | |
| | PRECISION = 3 |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | UNITS_US_IMP = "G20" |
| | UNITS_METRIC = "G21" |
| | UNITS = UNITS_METRIC |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | UNIT_FORMAT_US_IMP = "in" |
| | UNIT_FORMAT_METRIC = "mm" |
| | UNIT_FORMAT = UNIT_FORMAT_METRIC |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | UNIT_SPEED_FORMAT_US_IMP = "in/min" |
| | UNIT_SPEED_FORMAT_METRIC = "mm/min" |
| | UNIT_SPEED_FORMAT = UNIT_SPEED_FORMAT_METRIC |
| |
|
| | |
| | |
| | |
| |
|
| | |
| | parser = argparse.ArgumentParser(prog=__name__, add_help=False) |
| | parser.add_argument("--name", help="GCode program name") |
| | 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="suppress 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\\nG54\\G40\\nG49\\nG90\\nG80\\n"', |
| | ) |
| | parser.add_argument( |
| | "--postamble", |
| | help='set commands to be issued after the last command, default="M05\\nG17\\nG54\\nG0\\nG90\\nG80\\nM30\\n"', |
| | ) |
| | parser.add_argument("--inches", action="store_true", help="lengths in [in], G20") |
| | parser.add_argument("--metric", action="store_true", help="lengths in [mm], G21") |
| | parser.add_argument( |
| | "--modal", action="store_true", help="repeat/suppress repeated command arguments" |
| | ) |
| | parser.add_argument( |
| | "--tool-length-offset", |
| | action="store_true", |
| | help="suppress tool length offset G43 following tool changes", |
| | ) |
| | parser.add_argument("--repeat", action="store_true", help="repeat axis arguments") |
| | TOOLTIP_ARGS = parser.format_help() |
| |
|
| |
|
| | |
| | trace_gcode = False |
| |
|
| | now = datetime.datetime.now() |
| |
|
| | LINENR = 0 |
| | COMMAND_SPACE = " " |
| | UNIT_DEFAULT_CHANGED = False |
| |
|
| | |
| | |
| | warnings_count = 0 |
| | problems_count = 0 |
| |
|
| | HEADER = """(Exported by FreeCAD for {}) |
| | (Post Processor: {}, version {}) |
| | (CAM file: {}) |
| | (Output Time: {}) |
| | """ |
| |
|
| |
|
| | def processArguments(argstring): |
| | global SHOW_EDITOR |
| | global PROG_NAME |
| | global OUTPUT_HEADER |
| | global OUTPUT_COMMENTS |
| | global OUTPUT_LINE_NUMBERS |
| | global PREAMBLE |
| | global POSTAMBLE |
| | global MODAL |
| | global USE_TLO |
| | global PRECISION |
| | global UNITS |
| | global UNIT_FORMAT |
| | global UNIT_SPEED_FORMAT |
| | global UNIT_DEFAULT_CHANGED |
| | global REPEAT_ARGUMENTS |
| |
|
| | try: |
| | UNIT_DEFAULT_CHANGED = False |
| | args = parser.parse_args(argstring.split()) |
| |
|
| | if args.name is not None: |
| | PROG_NAME = args.name |
| |
|
| | 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 |
| |
|
| | PRECISION = args.precision |
| |
|
| | if args.preamble is not None: |
| | PREAMBLE = args.preamble.replace("\\n", "\n") |
| | elif OUTPUT_COMMENTS: |
| | PREAMBLE = PREAMBLE_DEFAULT |
| | else: |
| | PREAMBLE = PREAMBLE_DEFAULT_NO_COMMENT |
| |
|
| | if args.postamble is not None: |
| | POSTAMBLE = args.postamble.replace("\\n", "\n") |
| | elif OUTPUT_COMMENTS: |
| | POSTAMBLE = POSTAMBLE_DEFAULT |
| | else: |
| | POSTAMBLE = POSTAMBLE_DEFAULT_NO_COMMENT |
| |
|
| | if args.inches and (UNITS != UNITS_US_IMP): |
| | print("Units: US Imperial [inch], check your UC-CNC profile.") |
| | UNITS = UNITS_US_IMP |
| | UNIT_FORMAT = UNIT_FORMAT_US_IMP |
| | UNIT_SPEED_FORMAT = UNIT_SPEED_FORMAT_US_IMP |
| | UNIT_DEFAULT_CHANGED = True |
| |
|
| | if args.metric and (UNITS != UNITS_METRIC): |
| | print("Units: Metric [mm], check your UC-CNC profile.") |
| | UNITS = UNITS_METRIC |
| | UNIT_FORMAT = UNIT_FORMAT_METRIC |
| | UNIT_SPEED_FORMAT = UNIT_SPEED_FORMAT_METRIC |
| | UNIT_DEFAULT_CHANGED = True |
| |
|
| | if args.modal: |
| | MODAL = True |
| |
|
| | if args.tool_length_offset: |
| | USE_TLO = True |
| |
|
| | if args.repeat: |
| | REPEAT_ARGUMENTS = True |
| |
|
| | except Exception: |
| | return False |
| |
|
| | return True |
| |
|
| |
|
| | def append0(line): |
| | result = line |
| | if trace_gcode: |
| | print("export: >>" + result) |
| | return result |
| |
|
| |
|
| | def append(line): |
| | result = linenumber() + line |
| | if trace_gcode: |
| | print("export: >>" + result) |
| | return result |
| |
|
| |
|
| | def export(objectslist, filename, argstring): |
| |
|
| | if not processArguments(argstring): |
| | print("export: process arguments failed, '{}'".format(argstring)) |
| | return None |
| |
|
| | global warnings_count |
| | global problems_count |
| |
|
| | warnings_count = 0 |
| | problems_count = 0 |
| |
|
| | 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("export: postprocessing...") |
| | gcode = append0("%" + PROG_NAME + "\n") |
| | if not argstring: |
| | gcode += append("(" + __name__ + " with default settings)\n") |
| | else: |
| | gcode += append("({} {})\n".format(__name__, argstring)) |
| |
|
| | |
| | if OUTPUT_HEADER: |
| | for line in HEADER.format( |
| | GCODE_PROCESSOR, |
| | __name__, |
| | VERSION, |
| | FreeCAD.ActiveDocument.FileName, |
| | str(now), |
| | ).splitlines(False): |
| | if line: |
| | gcode += append(line + "\n") |
| |
|
| | |
| | |
| | gcode += append("(Units: '" + UNIT_FORMAT + "' and '" + UNIT_SPEED_FORMAT + "')\n") |
| | if UNIT_DEFAULT_CHANGED: |
| | gcode += append("(WARNING: Units default changed, check your UC-CNC profile)\n") |
| | warnings_count += 1 |
| |
|
| | if OUTPUT_COMMENTS: |
| | gcode += append("(preamble: begin)\n") |
| | |
| | |
| | |
| | |
| | for line in PREAMBLE.splitlines(): |
| | gcode += append(line + "\n") |
| | if OUTPUT_COMMENTS: |
| | gcode += append("(preamble: done)\n") |
| |
|
| | |
| | for obj in objectslist: |
| |
|
| | |
| | if not PathUtil.activeForOp(obj): |
| | continue |
| |
|
| | |
| | if OUTPUT_COMMENTS: |
| | gcode += append("(operation initialise: %s)\n" % obj.Label) |
| | for line in PRE_OPERATION.splitlines(True): |
| | gcode += append(line) |
| |
|
| | |
| | coolantMode = PathUtil.coolantModeForOp(obj) |
| | if coolantMode == "Mist": |
| | if OUTPUT_COMMENTS: |
| | gcode += append("M7 (coolant: mist on)\n") |
| | else: |
| | gcode += append("M7\n") |
| | if coolantMode == "Flood": |
| | if OUTPUT_COMMENTS: |
| | gcode += append("M8 (coolant: flood on)\n") |
| | else: |
| | gcode += append("M8\n") |
| |
|
| | |
| | if OUTPUT_COMMENTS: |
| | gcode += append("(operation start: %s)\n" % obj.Label) |
| | gcode += parse(obj) |
| | if OUTPUT_COMMENTS: |
| | gcode += append("(operation done: %s)\n" % obj.Label) |
| |
|
| | |
| | for line in POST_OPERATION.splitlines(True): |
| | gcode += append(line) |
| |
|
| | |
| | if not coolantMode == "None": |
| | if OUTPUT_COMMENTS: |
| | gcode += append("M9 (coolant: off)\n") |
| | else: |
| | gcode += append("M9\n") |
| |
|
| | if OUTPUT_COMMENTS: |
| | gcode += append("(operation finalised: %s)\n" % obj.Label) |
| |
|
| | |
| | if OUTPUT_COMMENTS: |
| | gcode += append("(postamble: begin)\n") |
| | for line in POSTAMBLE.splitlines(True): |
| | gcode += append(line) |
| | if OUTPUT_COMMENTS: |
| | gcode += append("(postamble: done)\n") |
| |
|
| | |
| | if SHOW_EDITOR: |
| | dia = PostUtils.GCodeEditorDialog() |
| | dia.editor.setPlainText(gcode) |
| | result = dia.exec_() |
| | if result: |
| | final = dia.editor.toPlainText() |
| | else: |
| | final = gcode |
| | else: |
| | final = gcode |
| |
|
| | if (0 < problems_count) or (0 < warnings_count): |
| | print( |
| | "export: postprocessing: done, warnings: {}, problems: {}, see GCode for details.".format( |
| | warnings_count, problems_count |
| | ) |
| | ) |
| | else: |
| | print("export: postprocessing: done (none of the problems detected).") |
| |
|
| | if not filename == "-": |
| | print("export: writing to '{}'".format(filename)) |
| | gfile = pyopen(filename, "w") |
| | gfile.write(final) |
| | gfile.close() |
| |
|
| | return final |
| |
|
| |
|
| | def linenumber(): |
| | global LINENR |
| |
|
| | if LINENR <= 0: |
| | LINENR = LINE_NUMBER_START |
| | if OUTPUT_LINE_NUMBERS is True: |
| | line = LINENR |
| | LINENR += LINE_NUMBER_STEP |
| | return "N{:03d} ".format(line) |
| | return "" |
| |
|
| |
|
| | def parse(pathobj): |
| | out = "" |
| | lastcommand = None |
| | precision_string = "." + str(PRECISION) + "f" |
| | currLocation = {} |
| |
|
| | |
| | mandatoryCmd = ("G81", "G82", "G83") |
| |
|
| | |
| | mandatoryParamInt = ("D", "H", "S", "T") |
| |
|
| | |
| | mandatoryParamFloat = ("I", "J", "K") |
| |
|
| | |
| | params = ("X", "Y", "Z", "A", "B", "C", "I", "J", "K", "R", "F", "S", "T", "H", "L", "Q") |
| | firstmove = Path.Command("G0", {"X": -1, "Y": -1, "Z": -1, "F": 0.0}) |
| | currLocation.update(firstmove.Parameters) |
| |
|
| | if hasattr(pathobj, "Group"): |
| | |
| |
|
| | |
| | |
| | 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: |
| | commandlist = [] |
| | command = c.Name.strip() |
| | commandlist.append(command) |
| |
|
| | |
| | if MODAL is True: |
| | if command == lastcommand: |
| | commandlist.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 REPEAT_ARGUMENTS |
| | ): |
| | if c.Name not in ["G0", "G00"]: |
| | speed = Units.Quantity(c.Parameters["F"], FreeCAD.Units.Velocity) |
| | if speed.getValueAs(UNIT_SPEED_FORMAT) > 0.0: |
| | commandlist.append( |
| | param |
| | + format( |
| | float(speed.getValueAs(UNIT_SPEED_FORMAT)), |
| | precision_string, |
| | ) |
| | ) |
| | else: |
| | continue |
| | elif param in mandatoryParamInt: |
| | commandlist.append(param + str(int(c.Parameters[param]))) |
| | elif param in mandatoryParamFloat: |
| | commandlist.append( |
| | param + format(float(c.Parameters[param]), precision_string) |
| | ) |
| | else: |
| | if ( |
| | (not REPEAT_ARGUMENTS and c.Name not in mandatoryCmd) |
| | and (param in currLocation) |
| | and (currLocation[param] == c.Parameters[param]) |
| | ): |
| | continue |
| | else: |
| | pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length) |
| | commandlist.append( |
| | param + format(float(pos.getValueAs(UNIT_FORMAT)), precision_string) |
| | ) |
| |
|
| | |
| | lastcommand = command |
| | currLocation.update(c.Parameters) |
| |
|
| | |
| | if command == "M6": |
| | for line in TOOL_CHANGE.splitlines(True): |
| | out += linenumber() + line |
| |
|
| | |
| | if USE_TLO: |
| | tool_height = "\nG43 H" + str(int(c.Parameters["T"])) |
| | commandlist.append(tool_height) |
| |
|
| | if command == "message": |
| | if OUTPUT_COMMENTS is False: |
| | out = [] |
| | else: |
| | commandlist.pop(0) |
| |
|
| | |
| | if len(commandlist) >= 1: |
| | if OUTPUT_LINE_NUMBERS: |
| | commandlist.insert(0, (linenumber())) |
| |
|
| | |
| | for w in commandlist: |
| | out += w.strip() + COMMAND_SPACE |
| | if trace_gcode: |
| | print("parse : >>{}".format(out)) |
| | out = out.strip() + "\n" |
| |
|
| | return out |
| |
|