From 158a21b58dca4f302db1634e351037363e2c43cb Mon Sep 17 00:00:00 2001 From: sliptonic Date: Wed, 5 Jul 2017 14:58:18 -0500 Subject: [PATCH] Path: Centroid post re-write --- .../Path/PathScripts/post/centroid_post.py | 414 +++++++++++++----- 1 file changed, 305 insertions(+), 109 deletions(-) diff --git a/src/Mod/Path/PathScripts/post/centroid_post.py b/src/Mod/Path/PathScripts/post/centroid_post.py index 2977f9bc89..5c46077711 100644 --- a/src/Mod/Path/PathScripts/post/centroid_post.py +++ b/src/Mod/Path/PathScripts/post/centroid_post.py @@ -1,142 +1,338 @@ -# -*- coding: utf-8 -*- - -#*************************************************************************** -#* * -#* Copyright (c) 2015 Dan Falck * -#* * -#* 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. * -#* * -#* This program 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 Library General Public License for more details. * -#* * -#* You should have received a copy of the GNU Library General Public * -#* License along with this program; if not, write to the Free Software * -#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * -#* USA * -#* * -#*************************************************************************** +# *************************************************************************** +# * Copyright (c) 2015 Dan Falck * +# * * +# * 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 -TOOLTIP=''' example post for Centroid CNC mill''' +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 centroid 3 axis mill. 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 centroid_post +centroid_post.export(object,"/path/to/file.ncc","") +''' + +TOOLTIP_ARGS=''' +Arguments for centroid: + --header,--no-header ... output headers (--header) + --comments,--no-comments ... output comments (--comments) + --line-numbers,--no-line-numbers ... prefix with line numbers (--no-lin-numbers) + --show-editor, --no-show-editor ... pop up editor before writing output(--show-editor) + --feed-precision=1 ... number of digits of precision for feed rate. Default=1 + --axis-precision=4 ... number of digits of precision for axis moves. Default=4 +''' import FreeCAD +from FreeCAD import Units import datetime -now = datetime.datetime.now() +import PathScripts from PathScripts import PostUtils +#from PathScripts import PathUtils +now = datetime.datetime.now() -#*************************************************************************** -# user editable stuff here +# These globals set common customization preferences +OUTPUT_COMMENTS = True +OUTPUT_HEADER = True +OUTPUT_LINE_NUMBERS = False +if FreeCAD.GuiUp: + SHOW_EDITOR = True +else: + SHOW_EDITOR = False +MODAL = False # if true commands are suppressed if the same as previous line. -UNITS = "G20" #old style inch units for this shop -MACHINE_NAME = "BigMill" +COMMAND_SPACE = " " +LINENR = 100 # line number starting value + +# These globals will be reflected in the Machine configuration of the project +UNITS = "G20" # G21 for metric, G20 for us standard +UNIT_FORMAT = 'mm/min' +MACHINE_NAME = "Centroid" CORNER_MIN = {'x':-609.6, 'y':-152.4, 'z':0 } #use metric for internal units CORNER_MAX = {'x':609.6, 'y':152.4, 'z':304.8 } #use metric for internal units - -SHOW_EDITOR = True -MODAL = True -COMMENT= ';' #centroid control comment symbol - -HEADER = "" -HEADER += ";Exported by FreeCAD\n" -HEADER += ";Post Processor: " + __name__ +"\n" -HEADER += ";CAM file: %s\n" -HEADER += ";Output Time:"+str(now)+"\n" - -TOOLRETURN = '''M5 M25 -G49 H0\n''' #spindle off,height offset canceled,spindle retracted (M25 is a centroid command to retract spindle) - -ZAXISRETURN = '''G91 G28 X0 Z0 -G90\n''' - -SAFETYBLOCK = 'G90 G80 G40 G49\n' - -AXIS_DECIMALS = 4 -FEED_DECIMALS = 1 +AXIS_PRECISION=4 +FEED_PRECISION=1 SPINDLE_DECIMALS = 0 -FOOTER = 'M99'+'\n' +COMMENT = ";" -# don't edit with the stuff below the next line unless you know what you're doing :) -#*************************************************************************** +HEADER = ''' +;Exported by FreeCAD +;Post Processor: {} +;CAM file: {} +;Output Time: {} +'''.format(__name__, FreeCAD.ActiveDocument.FileName, str(now)) + +# Preamble text will appear at the beginning of the GCODE output file. +PREAMBLE = '''G53 G00 G17 +''' + +# Postamble text will appear following the last operation. +POSTAMBLE = '''M99 +''' + +TOOLRETURN = '''M5 M25 +G49 H0 +''' #spindle off,height offset canceled,spindle retracted (M25 is a centroid command to retract spindle) + +ZAXISRETURN = '''G91 G28 X0 Z0 +G90 +''' + +SAFETYBLOCK = '''G90 G80 G40 G49 +''' + +# 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 = '''''' +# to distinguish python built-in open function from the one declared below if open.__module__ == '__builtin__': pythonopen = open -def export(selection,filename,argstring): - params = ['X','Y','Z','A','B','I','J','F','H','S','T','Q','R','L'] #Using XY plane most of the time so skipping K - for obj in selection: - if not hasattr(obj,"Path"): - print("the object " + obj.Name + " is not a path. Please select only path and Compounds.") - return - myMachine = None - for pathobj in selection: - if hasattr(pathobj,"MachineName"): - myMachine = pathobj.MachineName - if hasattr(pathobj, "MachineUnits"): - if pathobj.MachineUnits == "Metric": - UNITS = "G21" - else: - UNITS = "G20" - if myMachine is None: - print("No machine found in this selection") +def processArguments(argstring): + global OUTPUT_HEADER + global OUTPUT_COMMENTS + global OUTPUT_LINE_NUMBERS + global SHOW_EDITOR + global AXIS_PRECISION + global FEED_PRECISION - gcode ='' - gcode+= HEADER % (FreeCAD.ActiveDocument.FileName) - gcode+= SAFETYBLOCK - gcode+= UNITS+'\n' + for arg in argstring.split(): + if arg == '--header': + OUTPUT_HEADER = True + elif arg == '--no-header': + OUTPUT_HEADER = False + elif arg == '--comments': + OUTPUT_COMMENTS = True + elif arg == '--no-comments': + OUTPUT_COMMENTS = False + elif arg == '--line-numbers': + OUTPUT_LINE_NUMBERS = True + elif arg == '--no-line-numbers': + OUTPUT_LINE_NUMBERS = False + elif arg == '--show-editor': + SHOW_EDITOR = True + elif arg == '--no-show-editor': + SHOW_EDITOR = False + elif arg.split('=')[0] == '--axis-precision': + AXIS_PRECISION = arg.split('=')[1] + elif arg.split('=')[0] == '--feed-precision': + FEED_PRECISION = arg.split('=')[1] +def export(objectslist, filename, argstring): + processArguments(argstring) + for i in objectslist: + print (i.Name) + global UNITS + global UNIT_FORMAT + + # ISJOB = (len(objectslist) == 1) and isinstance(objectslist[0].Proxy, PathScripts.PathJob.ObjectPathJob) + # print("isjob: {} {}".format(ISJOB, len(objectslist))) + + # if len(objectslist) > 1: + # 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 + + print("postprocessing...") + gcode = "" + + # write header + if OUTPUT_HEADER: + gcode += HEADER + + gcode += SAFETYBLOCK + + # Write the preamble + if OUTPUT_COMMENTS: + for item in objectslist: + if isinstance (item.Proxy, PathScripts.PathToolController.ToolController): + gcode += ";T{}={}\n".format(item.ToolNumber, item.Name) + gcode += linenumber() + ";begin preamble\n" + for line in PREAMBLE.splitlines(True): + gcode += linenumber() + line + + gcode += linenumber() + UNITS + "\n" + + for obj in objectslist: + #skip postprocessing tools + # if isinstance (obj.Proxy, PathScripts.PathToolController.ToolController): + # continue + + + # do the pre_op + if OUTPUT_COMMENTS: + gcode += linenumber() + ";begin operation\n" + for line in PRE_OPERATION.splitlines(True): + gcode += linenumber() + line + + gcode += parse(obj) + + # do the post_op + if OUTPUT_COMMENTS: + gcode += linenumber() + ";end 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 TOOLRETURN.splitlines(True): + gcode += linenumber() + line + for line in SAFETYBLOCK.splitlines(True): + gcode += linenumber() + line + for line in POSTAMBLE.splitlines(True): + gcode += linenumber() + line + + if 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 AXIS_PRECISION + global FEED_PRECISION + out = "" lastcommand = None - gcode+= COMMENT+ selection[0].Description +'\n' + axis_precision_string = '.' + str(AXIS_PRECISION) +'f' + feed_precision_string = '.' + str(FEED_PRECISION) +'f' + # params = ['X','Y','Z','A','B','I','J','K','F','S'] #This list control + # the order of parameters + # centroid doesn't want K properties on XY plane Arcs need work. + params = ['X', 'Y', 'Z', 'A', 'B', 'I', 'J', 'F', 'S', 'T', 'Q', 'R', 'L', 'H'] - gobjects = [] - for g in selection[0].Group: - gobjects.append(g) + if hasattr(pathobj, "Group"): # We have a compound or project. + # if OUTPUT_COMMENTS: + # out += linenumber() + "(compound: " + pathobj.Label + ")\n" + for p in pathobj.Group: + out += parse(p) + return out + else: # parsing simple path - for obj in gobjects: - for c in obj.Path.Commands: - outstring = [] - command = c.Name + # groups might contain non-path things like stock. + if not hasattr(pathobj, "Path"): + return out + + # if OUTPUT_COMMENTS: + # out += linenumber() + "(" + pathobj.Label + ")\n" + + for c in pathobj.Path.Commands: + commandlist = [] #list of elements in the command, code and params. + command = c.Name #command M or G code or comment string if command[0]=='(': command = PostUtils.fcoms(command, COMMENT) - outstring.append(command) - if MODAL == True: + commandlist.append(command) + # if modal: only print the command if it is not the same as the + # last one + if MODAL is True: if command == lastcommand: - outstring.pop(0) - if c.Parameters >= 1: - for param in params: - if param in c.Parameters: - if param == 'F': - outstring.append(param + PostUtils.fmt(c.Parameters['F'], FEED_DECIMALS,UNITS)) - elif param == 'H': - outstring.append(param + str(int(c.Parameters['H']))) - elif param == 'S': - outstring.append(param + PostUtils.fmt(c.Parameters['S'], SPINDLE_DECIMALS,'G21')) #rpm is unitless-therefore I had to 'fake it out' by using metric units which don't get converted from entered value - elif param == 'T': - outstring.append(param + str(int(c.Parameters['T']))) - else: - outstring.append(param + PostUtils.fmt(c.Parameters[param],AXIS_DECIMALS,UNITS)) - outstr = str(outstring) + commandlist.pop(0) + + # Now add the remaining parameters in order + for param in params: + if param in c.Parameters: + if param == 'F': + if c.Name not in ["G0", "G00"]: #centroid doesn't use rapid speeds + speed = Units.Quantity(c.Parameters['F'], FreeCAD.Units.Velocity) + commandlist.append( + param + format(float(speed.getValueAs(UNIT_FORMAT)), feed_precision_string) ) + elif param == 'H': + commandlist.append(param + str(int(c.Parameters['H']))) + elif param == 'S': + commandlist.append(param + PostUtils.fmt(c.Parameters['S'], SPINDLE_DECIMALS, "G21")) + elif param == 'T': + commandlist.append(param + str(int(c.Parameters['T']))) + else: + commandlist.append( + param + format(c.Parameters[param], axis_precision_string)) + outstr = str(commandlist) outstr =outstr.replace('[','') outstr =outstr.replace(']','') outstr =outstr.replace("'",'') outstr =outstr.replace(",",'') - gcode+= outstr + '\n' - lastcommand = c.Name - gcode+= TOOLRETURN - gcode+= SAFETYBLOCK - gcode+= FOOTER - if SHOW_EDITOR: - PostUtils.editor(gcode) - gfile = pythonopen(filename,"wb") - gfile.write(gcode) - gfile.close() + #out += outstr + '\n' + # store the latest command + lastcommand = command + + # Check for Tool Change: + if command == 'M6': + # 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: + # commandlist.pop(0) # remove the command + + # prepend a line number and append a newline + if len(commandlist) >= 1: + if OUTPUT_LINE_NUMBERS: + commandlist.insert(0, (linenumber())) + + # append the line to the final output + for w in commandlist: + out += w + COMMAND_SPACE + out = out.strip() + "\n" + + return out + + +print(__name__ + " gcode postprocessor loaded.")