# -*- coding: utf-8 -*- # *************************************************************************** # * Copyright (c) 2014 Yorik van Havre * # * Copyright (c) 2014 sliptonic * # * Copyright (c) 2015 Dan Falck * # * Copyright (c) 2018, 2019 Gauthier Briere * # * Copyright (c) 2019, 2020 Schildkroet * # * Copyright (c) 2022 Larry Woestman * # * * # * 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 * # * * # *************************************************************************** import re import FreeCAD from FreeCAD import Units import Path from PathScripts import PostUtils def create_comment(comment_string, comment_symbol): """Create a comment from a string using the correct comment symbol.""" if comment_symbol == "(": comment_string = "(" + comment_string + ")" else: comment_string = comment_symbol + comment_string return comment_string def drill_translate(values, outstring, cmd, params): """Translate drill cycles.""" axis_precision_string = "." + str(values["AXIS_PRECISION"]) + "f" feed_precision_string = "." + str(values["FEED_PRECISION"]) + "f" trBuff = "" if values["OUTPUT_COMMENTS"]: # Comment the original command trBuff += ( linenumber(values) + create_comment( values["COMMAND_SPACE"] + format_outstring(values, outstring) + values["COMMAND_SPACE"], values["COMMENT_SYMBOL"], ) + "\n" ) # cycle conversion # currently only cycles in XY are provided (G17) # other plains ZX (G18) and YZ (G19) are not dealt with : Z drilling only. drill_X = Units.Quantity(params["X"], FreeCAD.Units.Length) drill_Y = Units.Quantity(params["Y"], FreeCAD.Units.Length) drill_Z = Units.Quantity(params["Z"], FreeCAD.Units.Length) RETRACT_Z = Units.Quantity(params["R"], FreeCAD.Units.Length) # R less than Z is error if RETRACT_Z < drill_Z: trBuff += ( linenumber(values) + create_comment( "Drill cycle error: R less than Z", values["COMMENT_SYMBOL"] ) + "\n" ) return trBuff if values["MOTION_MODE"] == "G91": # G91 relative movements drill_X += values["CURRENT_X"] drill_Y += values["CURRENT_Y"] drill_Z += values["CURRENT_Z"] RETRACT_Z += values["CURRENT_Z"] if values["DRILL_RETRACT_MODE"] == "G98" and values["CURRENT_Z"] >= RETRACT_Z: RETRACT_Z = values["CURRENT_Z"] # get the other parameters drill_feedrate = Units.Quantity(params["F"], FreeCAD.Units.Velocity) if cmd == "G83": drill_Step = Units.Quantity(params["Q"], FreeCAD.Units.Length) a_bit = ( drill_Step * 0.05 ) # NIST 3.5.16.4 G83 Cycle: "current hole bottom, backed off a bit." elif cmd == "G82": drill_DwellTime = params["P"] # wrap this block to ensure machine's values["MOTION_MODE"] is restored # in case of error try: if values["MOTION_MODE"] == "G91": trBuff += ( linenumber(values) + "G90\n" ) # force absolute coordinates during cycles strG0_RETRACT_Z = ( "G0 Z" + format( float(RETRACT_Z.getValueAs(values["UNIT_FORMAT"])), axis_precision_string, ) + "\n" ) strF_Feedrate = ( " F" + format( float(drill_feedrate.getValueAs(values["UNIT_SPEED_FORMAT"])), feed_precision_string, ) + "\n" ) # print(strF_Feedrate) # preliminary movement(s) if values["CURRENT_Z"] < RETRACT_Z: trBuff += linenumber(values) + strG0_RETRACT_Z trBuff += ( linenumber(values) + "G0 X" + format( float(drill_X.getValueAs(values["UNIT_FORMAT"])), axis_precision_string ) + " Y" + format( float(drill_Y.getValueAs(values["UNIT_FORMAT"])), axis_precision_string ) + "\n" ) if values["CURRENT_Z"] > RETRACT_Z: # NIST GCODE 3.5.16.1 Preliminary and In-Between Motion says G0 to RETRACT_Z # Here use G1 since retract height may be below surface ! trBuff += ( linenumber(values) + "G1 Z" + format( float(RETRACT_Z.getValueAs(values["UNIT_FORMAT"])), axis_precision_string, ) + strF_Feedrate ) last_Stop_Z = RETRACT_Z # drill moves if cmd in ("G81", "G82"): trBuff += ( linenumber(values) + "G1 Z" + format( float(drill_Z.getValueAs(values["UNIT_FORMAT"])), axis_precision_string, ) + strF_Feedrate ) # pause where applicable if cmd == "G82": trBuff += linenumber(values) + "G4 P" + str(drill_DwellTime) + "\n" trBuff += linenumber(values) + strG0_RETRACT_Z else: # 'G83' if params["Q"] != 0: while 1: if last_Stop_Z != RETRACT_Z: clearance_depth = ( last_Stop_Z + a_bit ) # rapid move to just short of last drilling depth trBuff += ( linenumber(values) + "G0 Z" + format( float( clearance_depth.getValueAs(values["UNIT_FORMAT"]) ), axis_precision_string, ) + "\n" ) next_Stop_Z = last_Stop_Z - drill_Step if next_Stop_Z > drill_Z: trBuff += ( linenumber(values) + "G1 Z" + format( float(next_Stop_Z.getValueAs(values["UNIT_FORMAT"])), axis_precision_string, ) + strF_Feedrate ) trBuff += linenumber(values) + strG0_RETRACT_Z last_Stop_Z = next_Stop_Z else: trBuff += ( linenumber(values) + "G1 Z" + format( float(drill_Z.getValueAs(values["UNIT_FORMAT"])), axis_precision_string, ) + strF_Feedrate ) trBuff += linenumber(values) + strG0_RETRACT_Z break except Exception: pass if values["MOTION_MODE"] == "G91": trBuff += linenumber(values) + "G91\n" # Restore if changed return trBuff def dump(obj): """For debug...""" for attr in dir(obj): print("obj.%s = %s" % (attr, getattr(obj, attr))) def format_outstring(values, strTable): """Construct the line for the final output.""" s = "" for w in strTable: s += w + values["COMMAND_SPACE"] s = s.strip() return s def linenumber(values, space=None): """Output the next line number if appropriate.""" if values["OUTPUT_LINE_NUMBERS"]: if space is None: space = values["COMMAND_SPACE"] line_num = str(values["line_number"]) values["line_number"] += values["LINE_INCREMENT"] return "N" + line_num + space return "" def parse(values, pathobj): """Parse a Path.""" out = "" lastcommand = None axis_precision_string = "." + str(values["AXIS_PRECISION"]) + "f" feed_precision_string = "." + str(values["FEED_PRECISION"]) + "f" currLocation = {} # keep track for no doubles 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 values["OUTPUT_COMMENTS"]: comment = create_comment( "Compound: " + pathobj.Label, values["COMMENT_SYMBOL"] ) out += linenumber(values) + comment + "\n" for p in pathobj.Group: out += parse(values, p) return out else: # parsing simple path # groups might contain non-path things like stock. if not hasattr(pathobj, "Path"): return out if values["OUTPUT_PATH_LABELS"] and values["OUTPUT_COMMENTS"]: comment = create_comment("Path: " + pathobj.Label, values["COMMENT_SYMBOL"]) out += linenumber(values) + comment + "\n" if values["OUTPUT_ADAPTIVE"]: adaptiveOp = False opHorizRapid = 0 opVertRapid = 0 if "Adaptive" in pathobj.Name: adaptiveOp = True if hasattr(pathobj, "ToolController"): if ( hasattr(pathobj.ToolController, "HorizRapid") and pathobj.ToolController.HorizRapid > 0 ): opHorizRapid = Units.Quantity( pathobj.ToolController.HorizRapid, FreeCAD.Units.Velocity ) else: FreeCAD.Console.PrintWarning( "Tool Controller Horizontal Rapid Values are unset" + "\n" ) if ( hasattr(pathobj.ToolController, "VertRapid") and pathobj.ToolController.VertRapid > 0 ): opVertRapid = Units.Quantity( pathobj.ToolController.VertRapid, FreeCAD.Units.Velocity ) else: FreeCAD.Console.PrintWarning( "Tool Controller Vertical Rapid Values are unset" + "\n" ) for c in pathobj.Path.Commands: # List of elements in the command, code, and params. outstring = [] # command M or G code or comment string command = c.Name if command[0] == "(": if values["OUTPUT_COMMENTS"]: if values["COMMENT_SYMBOL"] != "(": command = PostUtils.fcoms(command, values["COMMENT_SYMBOL"]) else: continue if values["OUTPUT_ADAPTIVE"]: if adaptiveOp and command in values["RAPID_MOVES"]: if opHorizRapid and opVertRapid: command = "G1" else: outstring.append( "(Tool Controller Rapid Values are unset)" + "\n" ) outstring.append(command) # if modal: suppress the command if it is the same as the last one if values["MODAL"]: if command == lastcommand: outstring.pop(0) # Now add the remaining parameters in order for param in values["PARAMETER_ORDER"]: if param in c.Parameters: if param == "F" and ( currLocation[param] != c.Parameters[param] or values["OUTPUT_DOUBLES"] ): # centroid and linuxcnc don't use rapid speeds if command not in values["RAPID_MOVES"]: speed = Units.Quantity( c.Parameters["F"], FreeCAD.Units.Velocity ) if speed.getValueAs(values["UNIT_SPEED_FORMAT"]) > 0.0: outstring.append( param + format( float( speed.getValueAs( values["UNIT_SPEED_FORMAT"] ) ), feed_precision_string, ) ) else: continue elif param in ["H", "L", "T"]: outstring.append(param + str(int(c.Parameters[param]))) elif param == "D": if command in ["G41", "G42"]: outstring.append(param + str(int(c.Parameters[param]))) elif command in ["G96", "G97"]: outstring.append( param + PostUtils.fmt( c.Parameters[param], values["SPINDLE_DECIMALS"], "G21", ) ) else: # anything else that is supported (G41.1?, G42.1?) outstring.append(param + str(float(c.Parameters[param]))) elif param == "P": if command in ["G2", "G02", "G3", "G03", "G5.2", "G5.3", "G10"]: outstring.append(param + str(int(c.Parameters[param]))) elif command in [ "G4", "G04", "G64", "G76", "G82", "G86", "G89", ]: outstring.append(param + str(float(c.Parameters[param]))) elif command in ["G5", "G05"]: pos = Units.Quantity( c.Parameters[param], FreeCAD.Units.Length ) outstring.append( param + format( float(pos.getValueAs(values["UNIT_FORMAT"])), axis_precision_string, ) ) else: # anything else that is supported outstring.append(param + str(c.Parameters[param])) elif param == "S": outstring.append( param + PostUtils.fmt( c.Parameters[param], values["SPINDLE_DECIMALS"], "G21" ) ) else: if ( (not values["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(values["UNIT_FORMAT"])), axis_precision_string, ) ) if values["OUTPUT_ADAPTIVE"]: if adaptiveOp and command in values["RAPID_MOVES"]: if opHorizRapid and opVertRapid: if "Z" not in c.Parameters: outstring.append( "F" + format( float( opHorizRapid.getValueAs( values["UNIT_SPEED_FORMAT"] ) ), axis_precision_string, ) ) else: outstring.append( "F" + format( float( opVertRapid.getValueAs( values["UNIT_SPEED_FORMAT"] ) ), axis_precision_string, ) ) # store the latest command lastcommand = command currLocation.update(c.Parameters) # Memorizes the current position for calculating the related movements # and the withdrawal plan if command in values["MOTION_COMMANDS"]: if "X" in c.Parameters: values["CURRENT_X"] = Units.Quantity( c.Parameters["X"], FreeCAD.Units.Length ) if "Y" in c.Parameters: values["CURRENT_Y"] = Units.Quantity( c.Parameters["Y"], FreeCAD.Units.Length ) if "Z" in c.Parameters: values["CURRENT_Z"] = Units.Quantity( c.Parameters["Z"], FreeCAD.Units.Length ) if command in ("G98", "G99"): values["DRILL_RETRACT_MODE"] = command if command in ("G90", "G91"): values["MOTION_MODE"] = command if values["TRANSLATE_DRILL_CYCLES"]: if command in values["DRILL_CYCLES_TO_TRANSLATE"]: out += drill_translate(values, outstring, command, c.Parameters) # Erase the line we just translated outstring = [] if values["SPINDLE_WAIT"] > 0: if command in ("M3", "M03", "M4", "M04"): out += ( linenumber(values) + format_outstring(values, outstring) + "\n" ) out += ( linenumber(values) + format_outstring( values, ["G4", "P%s" % values["SPINDLE_WAIT"]] ) + "\n" ) outstring = [] # Check for Tool Change: if command in ("M6", "M06"): if values["OUTPUT_COMMENTS"]: comment = create_comment( "Begin toolchange", values["COMMENT_SYMBOL"] ) out += linenumber(values) + comment + "\n" if values["OUTPUT_TOOL_CHANGE"]: if values["STOP_SPINDLE_FOR_TOOL_CHANGE"]: # stop the spindle out += linenumber(values) + "M5\n" for line in values["TOOL_CHANGE"].splitlines(False): out += linenumber(values) + line + "\n" else: if values["OUTPUT_COMMENTS"]: # convert the tool change to a comment comment = create_comment( values["COMMAND_SPACE"] + format_outstring(values, outstring) + values["COMMAND_SPACE"], values["COMMENT_SYMBOL"], ) out += linenumber(values) + comment + "\n" outstring = [] if command == "message" and values["REMOVE_MESSAGES"]: if values["OUTPUT_COMMENTS"] is False: out = [] else: outstring.pop(0) # remove the command if command in values["SUPPRESS_COMMANDS"]: if values["OUTPUT_COMMENTS"]: # convert the command to a comment comment = create_comment( values["COMMAND_SPACE"] + format_outstring(values, outstring) + values["COMMAND_SPACE"], values["COMMENT_SYMBOL"], ) out += linenumber(values) + comment + "\n" # remove the command outstring = [] # prepend a line number and append a newline if len(outstring) >= 1: if values["OUTPUT_LINE_NUMBERS"]: # In this case we don't want a space after the line number # because the space is added in the join just below. outstring.insert(0, (linenumber(values, ""))) # append the line to the final output out += values["COMMAND_SPACE"].join(outstring) # Note: Do *not* strip `out`, since that forces the allocation # of a contiguous string & thus quadratic complexity. out += "\n" # add height offset if command in ("M6", "M06") and values["USE_TLO"]: out += linenumber(values) + "G43 H" + str(int(c.Parameters["T"])) + "\n" # Check for comments containing machine-specific commands # to pass literally to the controller if values["ENABLE_MACHINE_SPECIFIC_COMMANDS"]: m = re.match(r"^\(MC_RUN_COMMAND: ([^)]+)\)$", command) if m: raw_command = m.group(1) out += linenumber(values) + raw_command + "\n" return out