From 847035cbd227d1f7554a494c3cfe5fa8942f2b7c Mon Sep 17 00:00:00 2001 From: sliptonic Date: Sat, 19 May 2018 10:19:30 -0500 Subject: [PATCH] Path: New post for simple laser cutter. Initial Commit PATH: cmake to install jtech post --- src/Mod/Path/CMakeLists.txt | 1 + src/Mod/Path/PathScripts/post/jtech_post.py | 352 ++++++++++++++++++++ 2 files changed, 353 insertions(+) create mode 100644 src/Mod/Path/PathScripts/post/jtech_post.py diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 503b4db43d..b84625403a 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -119,6 +119,7 @@ SET(PathScripts_post_SRCS PathScripts/post/example_pre.py PathScripts/post/grbl_post.py PathScripts/post/grbl_G81_post.py + PathScripts/post/jtech_post.py PathScripts/post/linuxcnc_post.py PathScripts/post/opensbp_post.py PathScripts/post/opensbp_pre.py diff --git a/src/Mod/Path/PathScripts/post/jtech_post.py b/src/Mod/Path/PathScripts/post/jtech_post.py new file mode 100644 index 0000000000..4d54f74b95 --- /dev/null +++ b/src/Mod/Path/PathScripts/post/jtech_post.py @@ -0,0 +1,352 @@ +# *************************************************************************** +# * (c) sliptonic (shopinthewoods@gmail.com) 2018 * +# * * +# * 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 * +# * * +# ***************************************************************************/ +from __future__ import print_function +import FreeCAD +from FreeCAD import Units +import Path +import argparse +import datetime +import shlex +from PathScripts import PostUtils +from PathScripts import PathUtils + +TOOLTIP = ''' +This is a postprocessor file for the Path workbench. It is used to +take a pseudo-gcode fragment outputted by a Path object, and output +real GCode suitable for a jtech photonics laser. 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 jtech_post +jtech_post.export(object,"/path/to/file.ngc","") +''' + +now = datetime.datetime.now() + +parser = argparse.ArgumentParser(prog='jtech', 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="M05 S0\nG90"') +parser.add_argument('--postamble', help='set commands to be issued after the last command, default="M05 S0\nM2"') +parser.add_argument('--inches', action='store_true', help='Convert output for US imperial mode (G20)') +parser.add_argument('--modal', action='store_true', help='Output the Same G-command Name USE NonModal Mode') +parser.add_argument('--axis-modal', action='store_true', help='Output the Same Axis Value Mode') +#parser.add_argument('--power-max', help='set the max value for laser power default=255') +parser.add_argument('--power-on-delay', default='255', help='milliseconds - Add a delay after laser on before moving to pre-heat material. Default=0') + + +TOOLTIP_ARGS = parser.format_help() + +# 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 = 100 # line number starting value + +# These globals will be reflected in the Machine configuration of the project +UNITS = "G21" # G21 for metric, G20 for us standard +UNIT_SPEED_FORMAT = 'mm/min' +UNIT_FORMAT = 'mm' + +MACHINE_NAME = "JTECH Photonic Laser" +PRECISION = 3 + +# Preamble text will appear at the beginning of the GCODE output file. +PREAMBLE = '''M05 S0 +G90 +''' + +# Postamble text will appear following the last operation. +POSTAMBLE = '''M05 S0 +M2 +''' + +PRE_FEED = '''M03 +G4 P{} +''' + +POST_FEED = '''M05 +''' + +# Pre operation text will be inserted before every operation +PRE_OPERATION = '''''' + +# Post operation text will be inserted after every operation +POST_OPERATION = '''''' + +# Tool Change commands will be inserted before a tool change +TOOL_CHANGE = '''''' + +#POWER_MAX = 255 +POWER_ON_DELAY = 0 + +# to distinguish python built-in open function from the one declared below +if open.__module__ == '__builtin__': + 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 + global POWER_ON_DELAY + + 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) + 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 = 'G20' + UNIT_SPEED_FORMAT = 'in/min' + UNIT_FORMAT = 'in' + PRECISION = 4 + if args.modal: + MODAL = True + if args.axis_modal: + OUTPUT_DOUBLES = False + POWER_ON_DELAY = float(args.power_on_delay) / 1000 #milliseconds + + except: + return False + + return True + + +def export(objectslist, filename, argstring): + if not processArguments(argstring): + return None + global UNITS + global UNIT_FORMAT + global UNIT_SPEED_FORMAT + + 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 += linenumber() + "(Exported by FreeCAD)\n" + gcode += linenumber() + "(Post Processor: " + __name__ + ")\n" + gcode += linenumber() + "(Output Time:" + str(now) + ")\n" + + # Write the preamble + if OUTPUT_COMMENTS: + gcode += linenumber() + "(begin preamble)\n" + for line in PREAMBLE.splitlines(False): + gcode += linenumber() + line + "\n" + gcode += linenumber() + UNITS + "\n" + + for obj in objectslist: + + # do the pre_op + if OUTPUT_COMMENTS: + gcode += linenumber() + "(begin operation: %s)\n" % obj.Label + for line in PRE_OPERATION.splitlines(True): + gcode += linenumber() + line + + gcode += parse(obj) + + # do the post_op + if OUTPUT_COMMENTS: + gcode += linenumber() + "(finish operation: %s)\n" % obj.Label + for line in POST_OPERATION.splitlines(True): + gcode += linenumber() + line + + # do the post_amble + if OUTPUT_COMMENTS: + gcode += "(begin postamble)\n" + for line in POSTAMBLE.splitlines(True): + gcode += linenumber() + line + + 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, "wb") + gfile.write(final) + gfile.close() + + return final + + +def linenumber(): + global LINENR + if OUTPUT_LINE_NUMBERS is True: + LINENR += 10 + return "N" + str(LINENR) + " " + return "" + + +def parse(pathobj): + global PRECISION + global MODAL + global OUTPUT_DOUBLES + global UNIT_FORMAT + global UNIT_SPEED_FORMAT + global POWER_ON_DELAY + global PRE_FEED + global POST_FEED + + out = "" + lastcommand = None + precision_string = '.' + str(PRECISION) + 'f' + currLocation = {} # keep track for no doubles + RAPID_MOVES = ["G0", "G00"] + FEED_MOVES = ["G1", "G01", "G2", "G02", "G3", "G03"] + # the order of parameters + params = ['X', 'Y', 'Z', 'A', 'B', 'C', 'I', 'J', 'F', 'S', 'T', 'Q', 'R', 'L', 'H', 'D', 'P'] + 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. + 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 + + controlstring = "" + if command in FEED_MOVES and lastcommand in RAPID_MOVES: + controlstring = PRE_FEED.format(POWER_ON_DELAY) + elif command in RAPID_MOVES and lastcommand in FEED_MOVES: + controlstring = POST_FEED + if len(controlstring) > 0: + out += controlstring + + 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 RAPID_MOVES: # linuxcnc doesn't use rapid speeds + 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 == 'T': + outstring.append(param + str(int(c.Parameters['T']))) + elif param == 'H': + outstring.append(param + str(int(c.Parameters['H']))) + elif param == 'D': + outstring.append(param + str(int(c.Parameters['D']))) + elif param == 'S': + outstring.append(param + str(int(c.Parameters['S']))) + 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)) + + # store the latest command + lastcommand = command + currLocation.update(c.Parameters) + + # Check for Tool Change: + if command == 'M6': + continue + # if OUTPUT_COMMENTS: + # out += linenumber() + "(begin toolchange)\n" + for line in TOOL_CHANGE.splitlines(True): + out += linenumber() + line + + if command == "message": + if OUTPUT_COMMENTS is False: + out = [] + else: + outstring.pop(0) # remove the command + + # 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 + COMMAND_SPACE + out = out.strip() + "\n" + + return out + +print(__name__ + " gcode postprocessor loaded.")