# SPDX-License-Identifier: LGPL-2.1-or-later # *************************************************************************** # * Copyright (C) 2020 Stefano Chiaro * # * * # * This library is free software; you can redistribute it and/or * # * modify it under the terms of the GNU Lesser General Public * # * License as published by the Free Software Foundation; either * # * version 2.1 of the License, or (at your option) any later version. * # * * # * This library 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 Lesser General Public * # * License along with this library; if not, write to the Free Software * # * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * # * 02110-1301 USA * # *************************************************************************** # HEDENHAIN Post-Processor for FreeCAD import argparse import Path.Post.Utils as PostUtils import PathScripts.PathUtils as PathUtils import Path import PathScripts import shlex import math from builtins import open as pyopen # **************************************************************************# # USER EDITABLE STUFF HERE # # # # THESE VALUES SHOULD BE CHANGED TO FIT MACHINE TYPE AND LANGUAGE # MACHINE_SKIP_PARAMS = False # Print R F M values # possible values: # 'True' Skip to print a parameter if already active # 'False' Print parameter on ALL line # Old machines need these values on every lines MACHINE_USE_FMAX = False # Usage of FMAX # possible values: # 'True' Print FMAX # 'False' Print the value set on FEED_MAX_SPEED # Old machines don't accept FMAX and need a feed value FEED_MAX_SPEED = 8000 # Max machine speed for FMAX # possible values: # integer >= 0 AXIS_DECIMALS = 3 # machine axis precision # possible values: # integer >= 0 FEED_DECIMALS = 0 # machine feed precision # possible values: # integer >= 0 SPINDLE_DECIMALS = 0 # machine spindle precision # possible values: # integer >= 0 FIRST_LBL = 1 # first LBL number for LBLIZE function # possible values: # integer >= 0 # TEMPLATES FOR CYCLE DEFINITION # MACHINE_CYCLE_DEF = { 1: "CYCL DEF 1.0 FORATURA PROF." + "\n" + "CYCL DEF 1.1 DIST" + "{DIST}\n" + "CYCL DEF 1.2 PROF" + "{DEPTH}\n" + "CYCL DEF 1.3 INCR" + "{INCR}\n" + "CYCL DEF 1.4 SOSTA" + "{DWELL}\n" + "CYCL DEF 1.5 F" + "{FEED}", 2: "", 7: "", } # OPTIONAL COMPUTING # SOLVE_COMPENSATION_ACTIVE = False # Use the internal path compensation # possible values: # 'True' Try to solve compensation # 'False' Use original FreeCAD path # try to use RL and RR to get the real path where possible SHOW_EDITOR = True # Open the editor # possible values: # 'True' before the file is written it is shown for inspection # 'False' the file is written directly SKIP_WARNS = False # Skip post-processor warnings # possible values: # 'True' never prompt warning from post-processing problems # 'False' prompt active # STANDARD HEIDENHAIN VALUES FOR POSTPROCESSOR # UNITS = "MM" # post-processor units # possible values: # 'INCH' for inches # 'MM' for metric units # actually FreeCAD use only metric values LINENUMBERS = True # line numbers # possible values: # 'True' Add line numbers (Standard) # 'False' No line numbers STARTLINENR = 0 # first line number used # possible values: # any integer value >= 0 (Standard is 0) LINENUMBER_INCREMENT = 1 # line number increment # possible values: # any integer value > 0 (Standard is 1) # POSTPROCESSOR VARIABLES AND CODE # # **************************************************************************# # don't edit the stuff below this line unless you know what you're doing # # **************************************************************************# # GCODE VARIABLES AND FUNCTIONS # POSTGCODE = [] # Output string array G_FUNCTION_STORE = { "G90": False, "G91": False, "G98": False, "G99": False, "G81": False, "G82": False, "G83": False, } # HEIDENHAIN MACHINE PARAMETERS # MACHINE_WORK_AXIS = 2 # 0=X ; 1=Y ; 2=Z usually Z MACHINE_SPINDLE_DIRECTION = 3 # CW = 3 ; CCW = 4 MACHINE_LAST_POSITION = { # axis initial values to overwrite "X": 99999, "Y": 99999, "Z": 99999, "A": 99999, "B": 99999, "C": 99999, } MACHINE_LAST_CENTER = { # CC initial values to overwrite "X": 99999, "Y": 99999, "Z": 99999, } MACHINE_TRASL_ROT = [0, 0, 0, 0] # [X, Y, Z , Angle] !not implemented MACHINE_STORED_PARAMS = ["", -1, ""] # Store R F M parameter to skip # POSTPROCESSOR VARIABLES STORAGE # STORED_COMPENSATED_OBJ = () # Store a copy of compensated path STORED_CANNED_PARAMS = { # Store canned cycles for match "DIST": 99999, "DEPTH": 99999, "INCR": 99999, "DWELL": 99999, "FEED": 99999, } STORED_LBL = [] # Store array of LBL for match # POSTPROCESSOR SPECIAL VARIABLES # COMPENSATION_DIFF_STATUS = [False, True] # Check compensation, Check Diff LBLIZE_ACTIVE = False # Check if search for LBL LBLIZE_STAUS = False # Activated by path type LBLIZE_PATH_LEVEL = 99999 # Save milling level of actual LBL # END OF POSTPROCESSOR VARIABLES # # **************************************************************************# 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 heidenhain 3 axis mill. 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 heidenhain_post heidenhain.export(object,"/path/to/file.ncc","") """ parser = argparse.ArgumentParser(prog="heidenhain", add_help=False) parser.add_argument( "--skip-params", action="store_true", help="suppress R F M parameters where already stored", ) parser.add_argument( "--use-fmax", action="store_true", help="suppress feedrate and use FMAX instead with rapid movements", ) parser.add_argument( "--fmax-value", default="8000", help="feedrate to use instead of FMAX, default=8000" ) parser.add_argument( "--axis-decimals", default="3", help="number of digits of axis precision, default=3" ) parser.add_argument( "--feed-decimals", default="0", help="number of digits of feedrate precision, default=0", ) parser.add_argument( "--spindle-decimals", default="0", help="number of digits of spindle precision, default=0", ) parser.add_argument( "--solve-comp", action="store_true", help="try to get RL or RR real path where compensation is active", ) parser.add_argument( "--solve-lbl", action="store_true", help="try to replace repetitive movements with LBL", ) parser.add_argument("--first-lbl", default="1", help="change the first LBL number, default=1") parser.add_argument( "--no-show-editor", action="store_true", help="don't pop up editor before writing output", ) parser.add_argument("--no-warns", action="store_true", help="don't pop up post-processor warnings") TOOLTIP_ARGS = parser.format_help() def processArguments(argstring): global MACHINE_SKIP_PARAMS global MACHINE_USE_FMAX global FEED_MAX_SPEED global AXIS_DECIMALS global FEED_DECIMALS global SPINDLE_DECIMALS global SOLVE_COMPENSATION_ACTIVE global LBLIZE_ACTIVE global FIRST_LBL global SHOW_EDITOR global SKIP_WARNS try: args = parser.parse_args(shlex.split(argstring)) if args.skip_params: MACHINE_SKIP_PARAMS = True if args.use_fmax: MACHINE_USE_FMAX = True if args.fmax_value: FEED_MAX_SPEED = float(args.fmax_value) if args.axis_decimals: AXIS_DECIMALS = int(args.axis_decimals) if args.feed_decimals: FEED_DECIMALS = int(args.feed_decimals) if args.spindle_decimals: SPINDLE_DECIMALS = int(args.spindle_decimals) if args.solve_comp: SOLVE_COMPENSATION_ACTIVE = True if args.solve_lbl: LBLIZE_ACTIVE = True if args.first_lbl: FIRST_LBL = int(args.first_lbl) if args.no_show_editor: SHOW_EDITOR = False if args.no_warns: SKIP_WARNS = True except Exception: return False return True def export(objectslist, filename, argstring): if not processArguments(argstring): return None global UNITS global POSTGCODE global G_FUNCTION_STORE global MACHINE_WORK_AXIS global MACHINE_LAST_POSITION global MACHINE_TRASL_ROT global STORED_COMPENSATED_OBJ global COMPENSATION_DIFF_STATUS global LBLIZE_STAUS Object_Kind = None Feed = 0 Spindle_Active = False Compensation = "0" params = [ "X", "Y", "Z", "A", "B", "C", "I", "J", "K", "F", "H", "S", "T", "Q", "R", "L", ] 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 POSTGCODE.append(HEIDEN_Begin(objectslist)) # add header for obj in objectslist: Cmd_Count = 0 # command line number LBLIZE_STAUS = False if not hasattr(obj, "Proxy"): continue # useful to get idea of object kind if isinstance(obj.Proxy, Path.Tool.Controller.ToolController): Object_Kind = "TOOL" # like we go to change tool position MACHINE_LAST_POSITION["X"] = 99999 MACHINE_LAST_POSITION["Y"] = 99999 MACHINE_LAST_POSITION["Z"] = 99999 elif isinstance(obj.Proxy, Path.Op.ProfileEdges.ObjectProfile): Object_Kind = "PROFILE" if LBLIZE_ACTIVE: LBLIZE_STAUS = True elif isinstance(obj.Proxy, Path.Op.MillFace.ObjectFace): Object_Kind = "FACE" if LBLIZE_ACTIVE: LBLIZE_STAUS = True elif isinstance(obj.Proxy, Path.Op.Helix.ObjectHelix): Object_Kind = "HELIX" commands = PathUtils.getPathWithPlacement(obj).Commands # If used compensated path, store, recompute and diff when asked if not hasattr(obj, "UseComp") or not SOLVE_COMPENSATION_ACTIVE: continue if not obj.UseComp: continue if hasattr(obj.Path, "Commands") and Object_Kind == "PROFILE": # Take a copy of compensated path STORED_COMPENSATED_OBJ = commands # Find mill compensation if hasattr(obj, "Side") and hasattr(obj, "Direction"): if obj.Side == "Outside" and obj.Direction == "CW": Compensation = "L" elif obj.Side == "Outside" and obj.Direction == "CCW": Compensation = "R" elif obj.Side != "Outside" and obj.Direction == "CW": Compensation = "R" else: Compensation = "L" # set obj.UseComp to false and recompute() to get uncompensated path obj.UseComp = False obj.recompute() commands = PathUtils.getPathWithPlacement(obj).Commands # small edges could be skipped and movements joints can add edges NameStr = "" if hasattr(obj, "Label"): NameStr = str(obj.Label) if len(commands) != len(STORED_COMPENSATED_OBJ): # not same number of edges obj.UseComp = True obj.recompute() commands = PathUtils.getPathWithPlacement(obj).Commands POSTGCODE.append("; MISSING EDGES UNABLE TO GET COMPENSATION") if not SKIP_WARNS: ( PostUtils.editor( "--solve-comp command ACTIVE\n\n" + "UNABLE to solve " + NameStr + " compensation\n\n" + "Some edges are missing\n" + "try to change Join Type to Miter or Square\n" + "try to use a smaller Tool Diameter\n" + "Internal Path could have too small corners\n\n" + "use --no-warns to not prompt this message" ) ) else: if not SKIP_WARNS: ( PostUtils.editor( "--solve-comp command ACTIVE\n\n" + "BE CAREFUL with solved " + NameStr + " compensation\n\n" + "USE AT YOUR OWN RISK\n" + "Simulate it before use\n" + "Offset Extra ignored use DR+ on TOOL CALL\n" + "Path could be different and/or give tool radius errors\n\n" + "use --no-warns to not prompt this message" ) ) # we can try to solve compensation POSTGCODE.append("; COMPENSATION ACTIVE") COMPENSATION_DIFF_STATUS[0] = True for c in commands: Cmd_Count += 1 command = c.Name if command != "G0": command = command.replace("G0", "G") # normalize: G01 -> G1 for param in params: if param in c.Parameters: if param == "F": Feed = c.Parameters["F"] if command == "G90": G_FUNCTION_STORE["G90"] = True G_FUNCTION_STORE["G91"] = False if command == "G91": G_FUNCTION_STORE["G91"] = True G_FUNCTION_STORE["G90"] = False if command == "G98": G_FUNCTION_STORE["G98"] = True G_FUNCTION_STORE["G99"] = False if command == "G99": G_FUNCTION_STORE["G99"] = True G_FUNCTION_STORE["G98"] = False # Rapid movement if command == "G0": Spindle_Status = "" if Spindle_Active == False: # At first rapid movement we turn on spindle Spindle_Status += str(MACHINE_SPINDLE_DIRECTION) # Activate spindle Spindle_Active = True else: # At last rapid movement we turn off spindle if Cmd_Count == len(commands): Spindle_Status += "5" # Deactivate spindle Spindle_Active = False else: Spindle_Status += "" # Spindle still active parsedElem = HEIDEN_Line( c.Parameters, Compensation, Feed, True, Spindle_Status, Cmd_Count ) if parsedElem is not None: POSTGCODE.append(parsedElem) # Linear movement if command == "G1": parsedElem = HEIDEN_Line(c.Parameters, Compensation, Feed, False, "", Cmd_Count) if parsedElem is not None: POSTGCODE.append(parsedElem) # Arc movement if command == "G2" or command == "G3": parsedElem = HEIDEN_Arc( c.Parameters, command, Compensation, Feed, False, "", Cmd_Count ) if parsedElem is not None: POSTGCODE.extend(parsedElem) if command == "G80": # Reset Canned Cycles G_FUNCTION_STORE["G81"] = False G_FUNCTION_STORE["G82"] = False G_FUNCTION_STORE["G83"] = False # Drilling, Dwell Drilling, Peck Drilling if command == "G81" or command == "G82" or command == "G83": parsedElem = HEIDEN_Drill(obj, c.Parameters, command, Feed) if parsedElem is not None: POSTGCODE.extend(parsedElem) # Tool change if command == "M6": parsedElem = HEIDEN_ToolCall(obj) if parsedElem is not None: POSTGCODE.append(parsedElem) if COMPENSATION_DIFF_STATUS[0]: # Restore the compensation if removed obj.UseComp = True obj.recompute() COMPENSATION_DIFF_STATUS[0] = False if LBLIZE_STAUS: HEIDEN_LBL_Replace() LBLIZE_STAUS = False if LBLIZE_ACTIVE: if not SKIP_WARNS: ( PostUtils.editor( "--solve-lbl command ACTIVE\n\n" + "BE CAREFUL with LBL replacements\n\n" + "USE AT YOUR OWN RISK\n" + "Simulate it before use\n" + "Path could be different and/or give errors\n\n" + "use --no-warns to not prompt this message" ) ) POSTGCODE.append(HEIDEN_End(objectslist)) # add footer Program_Out = HEIDEN_Numberize(POSTGCODE) # add line number if SHOW_EDITOR: PostUtils.editor(Program_Out) gfile = pyopen(filename, "w") gfile.write(Program_Out) gfile.close() def HEIDEN_Begin(ActualJob): # use Label for program name global UNITS # JobParent = PathUtils.findParentJob(ActualJob[0]) # if hasattr(JobParent, "Label"): # program_id = JobParent.Label # else: # program_id = "NEW" return "BEGIN PGM {}".format(UNITS) def HEIDEN_End(ActualJob): # use Label for program name global UNITS # JobParent = PathUtils.findParentJob(ActualJob[0]) # if hasattr(JobParent, "Label"): # program_id = JobParent.Label # else: # program_id = "NEW" return "END PGM {}".format(UNITS) # def HEIDEN_ToolDef(tool_id, tool_length, tool_radius): # old machines don't have tool table, need tooldef list # return "TOOL DEF " + tool_id + " R" + "{:.3f}".format(tool_length) + " L" + "{:.3f}".format(tool_radius) def HEIDEN_ToolCall(tool_Params): global MACHINE_SPINDLE_DIRECTION global MACHINE_WORK_AXIS H_Tool_Axis = ["X", "Y", "Z"] H_Tool_ID = "0" H_Tool_Speed = 0 H_Tool_Comment = "" if hasattr(tool_Params, "Label"): # use Label as tool comment H_Tool_Comment = tool_Params.Label if hasattr(tool_Params, "SpindleDir"): # get spindle direction for this tool if tool_Params.SpindleDir == "Forward": MACHINE_SPINDLE_DIRECTION = 3 else: MACHINE_SPINDLE_DIRECTION = 4 if hasattr(tool_Params, "SpindleSpeed"): # get tool speed for spindle H_Tool_Speed = tool_Params.SpindleSpeed if hasattr(tool_Params, "ToolNumber"): # use ToolNumber for tool id H_Tool_ID = tool_Params.ToolNumber if H_Tool_ID == "0" and H_Tool_Speed == 0 and H_Tool_Comment == "": return None else: return ( "TOOL CALL " + str(H_Tool_ID) + " " + H_Tool_Axis[MACHINE_WORK_AXIS] + HEIDEN_Format(" S", H_Tool_Speed) + " ;" + str(H_Tool_Comment) ) # create a linear movement def HEIDEN_Line(line_Params, line_comp, line_feed, line_rapid, line_M_funct, Cmd_Number): global FEED_MAX_SPEED global COMPENSATION_DIFF_STATUS global G_FUNCTION_STORE global MACHINE_WORK_AXIS global MACHINE_LAST_POSITION global MACHINE_STORED_PARAMS global MACHINE_SKIP_PARAMS global MACHINE_USE_FMAX H_Line_New = { # axis initial values to overwrite "X": 99999, "Y": 99999, "Z": 99999, "A": 99999, "B": 99999, "C": 99999, } H_Line = "L" H_Line_Params = [["X", "Y", "Z"], ["R0", line_feed, line_M_funct]] # check and hide duplicated axis movements, not last, update with new ones for i in H_Line_New: if G_FUNCTION_STORE["G91"]: # incremental H_Line_New[i] = 0 if i in line_Params: if line_Params[i] != 0 or line_M_funct != "": H_Line += " I" + HEIDEN_Format(i, line_Params[i]) # print incremental # update to absolute position H_Line_New[i] = MACHINE_LAST_POSITION[i] + H_Line_New[i] else: # absolute H_Line_New[i] = MACHINE_LAST_POSITION[i] if i in line_Params: if line_Params[i] != H_Line_New[i] or line_M_funct != "": H_Line += " " + HEIDEN_Format(i, line_Params[i]) H_Line_New[i] = line_Params[i] if H_Line == "L": # No movements no line return None if COMPENSATION_DIFF_STATUS[0]: # Diff from compensated ad not compensated path if COMPENSATION_DIFF_STATUS[1]: # skip if already compensated, not active by now Cmd_Number -= 1 # align # initialize like true, set false if not same point compensated and not compensated i = True for j in H_Line_Params[0]: if j in STORED_COMPENSATED_OBJ[Cmd_Number].Parameters and j in line_Params: if STORED_COMPENSATED_OBJ[Cmd_Number].Parameters[j] != line_Params[j]: i = False if i == False: H_Line_Params[1][0] = "R" + line_comp # we can skip this control if already in compensation # COMPENSATION_DIFF_STATUS[1] = False else: H_Line_Params[1][0] = "R" + line_comp # not used by now # check if we need to skip already active parameters # R parameter if MACHINE_SKIP_PARAMS == False or H_Line_Params[1][0] != MACHINE_STORED_PARAMS[0]: MACHINE_STORED_PARAMS[0] = H_Line_Params[1][0] H_Line += " " + H_Line_Params[1][0] # F parameter (check rapid o feed) if line_rapid: H_Line_Params[1][1] = FEED_MAX_SPEED if MACHINE_USE_FMAX and line_rapid: H_Line += " FMAX" else: if MACHINE_SKIP_PARAMS == False or H_Line_Params[1][1] != MACHINE_STORED_PARAMS[1]: MACHINE_STORED_PARAMS[1] = H_Line_Params[1][1] H_Line += HEIDEN_Format(" F", H_Line_Params[1][1]) # M parameter if MACHINE_SKIP_PARAMS == False or H_Line_Params[1][2] != MACHINE_STORED_PARAMS[2]: MACHINE_STORED_PARAMS[2] = H_Line_Params[1][2] H_Line += " M" + H_Line_Params[1][2] # LBLIZE check and array creation if LBLIZE_STAUS: i = H_Line_Params[0][MACHINE_WORK_AXIS] # to skip reposition movements rapid or not if MACHINE_LAST_POSITION[i] == H_Line_New[i] and line_rapid == False: HEIDEN_LBL_Get(MACHINE_LAST_POSITION, H_Line_New[i]) else: HEIDEN_LBL_Get() # update machine position with new values for i in H_Line_New: MACHINE_LAST_POSITION[i] = H_Line_New[i] return H_Line # create a arc movement def HEIDEN_Arc(arc_Params, arc_direction, arc_comp, arc_feed, arc_rapid, arc_M_funct, Cmd_Number): global FEED_MAX_SPEED global COMPENSATION_DIFF_STATUS global G_FUNCTION_STORE global MACHINE_WORK_AXIS global MACHINE_LAST_POSITION global MACHINE_LAST_CENTER global MACHINE_STORED_PARAMS global MACHINE_SKIP_PARAMS global MACHINE_USE_FMAX Cmd_Number -= 1 H_ArcSameCenter = False H_ArcIncr = "" H_ArcCenter = "CC " H_ArcPoint = "C" H_Arc_Params = [["X", "Y", "Z"], ["R0", arc_feed, arc_M_funct], ["I", "J", "K"]] H_Arc_CC = {"X": 99999, "Y": 99999, "Z": 99999} # CC initial values to overwrite H_Arc_P_NEW = { # end point initial values to overwrite "X": 99999, "Y": 99999, "Z": 99999, } # get command values if G_FUNCTION_STORE["G91"]: # incremental H_ArcIncr = "I" for i in range(0, 3): a = H_Arc_Params[0][i] b = H_Arc_Params[2][i] # X Y Z if a in arc_Params: H_Arc_P_NEW[a] = arc_Params[a] else: H_Arc_P_NEW[a] = 0 # I J K skip update for machine work axis if i != MACHINE_WORK_AXIS: if b in arc_Params: H_Arc_CC[a] = arc_Params[b] else: H_Arc_CC[a] = 0 else: # absolute for i in range(0, 3): a = H_Arc_Params[0][i] b = H_Arc_Params[2][i] # X Y Z H_Arc_P_NEW[a] = MACHINE_LAST_POSITION[a] if a in arc_Params: H_Arc_P_NEW[a] = arc_Params[a] # I J K skip update for machine work axis if i != MACHINE_WORK_AXIS: H_Arc_CC[a] = MACHINE_LAST_POSITION[a] if b in arc_Params: # to change if I J K are not always incremental H_Arc_CC[a] = H_Arc_CC[a] + arc_Params[b] def Axis_Select(a, b, c, incr): if a in arc_Params and b in arc_Params: _H_ArcCenter = ( incr + HEIDEN_Format(a, H_Arc_CC[a]) + " " + incr + HEIDEN_Format(b, H_Arc_CC[b]) ) if c in arc_Params and arc_Params[c] != MACHINE_LAST_POSITION[c]: # if there are 3 axis movements it need to be polar arc _H_ArcPoint = HEIDEN_PolarArc( H_Arc_CC[a], H_Arc_CC[b], H_Arc_P_NEW[a], H_Arc_P_NEW[b], arc_Params[c], c, incr, ) else: _H_ArcPoint = ( " " + incr + HEIDEN_Format(a, H_Arc_P_NEW[a]) + " " + incr + HEIDEN_Format(b, H_Arc_P_NEW[b]) ) return [_H_ArcCenter, _H_ArcPoint] else: return ["", ""] # set the right work plane based on tool direction if MACHINE_WORK_AXIS == 0: # tool on X axis Axis_Result = Axis_Select("Y", "Z", "X", H_ArcIncr) elif MACHINE_WORK_AXIS == 1: # tool on Y axis Axis_Result = Axis_Select("X", "Z", "Y", H_ArcIncr) elif MACHINE_WORK_AXIS == 2: # tool on Z axis Axis_Result = Axis_Select("X", "Y", "Z", H_ArcIncr) # and fill with values H_ArcCenter += Axis_Result[0] H_ArcPoint += Axis_Result[1] if H_ArcCenter == "CC ": # No movements no circle return None if arc_direction == "G2": # set the right arc direction H_ArcPoint += " DR-" else: H_ArcPoint += " DR+" if COMPENSATION_DIFF_STATUS[0]: # Diff from compensated ad not compensated path if COMPENSATION_DIFF_STATUS[1]: # skip if already compensated Cmd_Number -= 1 # align i = True for j in H_Arc_Params[0]: if j in STORED_COMPENSATED_OBJ[Cmd_Number].Parameters and j in arc_Params: if STORED_COMPENSATED_OBJ[Cmd_Number].Parameters[j] != arc_Params[j]: i = False if i == False: H_Arc_Params[1][0] = "R" + arc_comp # COMPENSATION_DIFF_STATUS[1] = False # we can skip this control if already in compensation else: H_Arc_Params[1][0] = "R" + arc_comp # not used by now # check if we need to skip already active parameters # R parameter if MACHINE_SKIP_PARAMS == False or H_Arc_Params[1][0] != MACHINE_STORED_PARAMS[0]: MACHINE_STORED_PARAMS[0] = H_Arc_Params[1][0] H_ArcPoint += " " + H_Arc_Params[1][0] # F parameter if arc_rapid: H_Arc_Params[1][1] = FEED_MAX_SPEED if MACHINE_USE_FMAX and arc_rapid: H_ArcPoint += " FMAX" else: if MACHINE_SKIP_PARAMS == False or H_Arc_Params[1][1] != MACHINE_STORED_PARAMS[1]: MACHINE_STORED_PARAMS[1] = H_Arc_Params[1][1] H_ArcPoint += HEIDEN_Format(" F", H_Arc_Params[1][1]) # M parameter if MACHINE_SKIP_PARAMS == False or H_Arc_Params[1][2] != MACHINE_STORED_PARAMS[2]: MACHINE_STORED_PARAMS[2] = H_Arc_Params[1][2] H_ArcPoint += " M" + H_Arc_Params[1][2] # update values to absolute if are incremental before store if G_FUNCTION_STORE["G91"]: # incremental for i in H_Arc_Params[0]: H_Arc_P_NEW[i] = MACHINE_LAST_POSITION[i] + H_Arc_P_NEW[i] H_Arc_CC[i] = MACHINE_LAST_POSITION[i] + H_Arc_CC[i] # check if we can skip CC print if ( MACHINE_LAST_CENTER["X"] == H_Arc_CC["X"] and MACHINE_LAST_CENTER["Y"] == H_Arc_CC["Y"] and MACHINE_LAST_CENTER["Z"] == H_Arc_CC["Z"] ): H_ArcSameCenter = True # LBLIZE check and array creation if LBLIZE_STAUS: i = H_Arc_Params[0][MACHINE_WORK_AXIS] # to skip reposition movements if MACHINE_LAST_POSITION[i] == H_Arc_P_NEW[i]: if H_ArcSameCenter: HEIDEN_LBL_Get(MACHINE_LAST_POSITION, H_Arc_P_NEW[i]) else: HEIDEN_LBL_Get(MACHINE_LAST_POSITION, H_Arc_P_NEW[i], 1) else: HEIDEN_LBL_Get() # update machine position with new values for i in H_Arc_Params[0]: MACHINE_LAST_CENTER[i] = H_Arc_CC[i] MACHINE_LAST_POSITION[i] = H_Arc_P_NEW[i] # if the circle center is already the same we don't need to print it if H_ArcSameCenter: return [H_ArcPoint] else: return [H_ArcCenter, H_ArcPoint] def HEIDEN_PolarArc(pol_cc_X, pol_cc_Y, pol_X, pol_Y, pol_Z, pol_Axis, pol_Incr): pol_Angle = 0 pol_Result = "" # get delta distance form point to circle center delta_X = pol_X - pol_cc_X delta_Y = pol_Y - pol_cc_Y # prevent undefined result of atan if delta_X != 0 or delta_Y != 0: pol_Angle = math.degrees(math.atan2(delta_X, delta_Y)) # set the appropriate zero and direction of angle # with X axis zero have the Y+ direction if pol_Axis == "X": pol_Angle = 90 - pol_Angle # with Y axis zero have the Z+ direction elif pol_Axis == "Y": pass # with Z axis zero have the X+ direction elif pol_Axis == "Z": pol_Angle = 90 - pol_Angle # set inside +0° +360° range if pol_Angle > 0: if pol_Angle >= 360: pol_Angle = pol_Angle - 360 elif pol_Angle < 0: pol_Angle = pol_Angle + 360 if pol_Angle < 0: pol_Angle = pol_Angle + 360 pol_Result = ( "P" + HEIDEN_Format(" PA+", pol_Angle) + " " + pol_Incr + HEIDEN_Format(pol_Axis, pol_Z) ) return pol_Result def HEIDEN_Drill( drill_Obj, drill_Params, drill_Type, drill_feed ): # create a drill cycle and movement global FEED_MAX_SPEED global MACHINE_WORK_AXIS global MACHINE_LAST_POSITION global MACHINE_USE_FMAX global G_FUNCTION_STORE global STORED_CANNED_PARAMS drill_Defs = {"DIST": 0, "DEPTH": 0, "INCR": 0, "DWELL": 0, "FEED": drill_feed} drill_Output = [] drill_Surface = None drill_SafePoint = 0 drill_StartPoint = 0 # try to get the distance from Clearance Height and Start Depth if hasattr(drill_Obj, "StartDepth") and hasattr(drill_Obj.StartDepth, "Value"): drill_Surface = drill_Obj.StartDepth.Value # initialize if "R" in drill_Params: # SafePoint equals to R position if G_FUNCTION_STORE["G91"]: # incremental drill_SafePoint = drill_Params["R"] + MACHINE_LAST_POSITION["Z"] else: drill_SafePoint = drill_Params["R"] # Surface equals to theoric start point of drilling if drill_Surface is not None and drill_SafePoint > drill_Surface: drill_Defs["DIST"] = drill_Surface - drill_SafePoint drill_StartPoint = drill_Surface else: drill_Defs["DIST"] = 0 drill_StartPoint = drill_SafePoint if "Z" in drill_Params: if G_FUNCTION_STORE["G91"]: # incremental drill_Defs["DEPTH"] = drill_SafePoint + drill_Params["Z"] else: drill_Defs["DEPTH"] = drill_Params["Z"] - drill_StartPoint if drill_Defs["DEPTH"] > 0: drill_Output.append("; WARNING START DEPTH LOWER THAN FINAL DEPTH") drill_Defs["INCR"] = drill_Defs["DEPTH"] # overwrite if "P" in drill_Params: drill_Defs["DWELL"] = drill_Params["P"] if "Q" in drill_Params: drill_Defs["INCR"] = drill_Params["Q"] # set the parameters for rapid movements if MACHINE_USE_FMAX: if MACHINE_SKIP_PARAMS: drill_Rapid = " FMAX" else: drill_Rapid = " R0 FMAX M" else: if MACHINE_SKIP_PARAMS: drill_Rapid = HEIDEN_Format(" F", FEED_MAX_SPEED) else: drill_Rapid = " R0" + HEIDEN_Format(" F", FEED_MAX_SPEED) + " M" # move to drill location drill_Movement = "L" if G_FUNCTION_STORE["G91"]: # incremental # update Z value to R + actual_Z if first call if G_FUNCTION_STORE[drill_Type] == False: MACHINE_LAST_POSITION["Z"] = drill_Defs["DIST"] + MACHINE_LAST_POSITION["Z"] drill_Movement = "L" + HEIDEN_Format(" Z", MACHINE_LAST_POSITION["Z"]) + drill_Rapid drill_Output.append(drill_Movement) # update X and Y position if "X" in drill_Params and drill_Params["X"] != 0: MACHINE_LAST_POSITION["X"] = drill_Params["X"] + MACHINE_LAST_POSITION["X"] drill_Movement += HEIDEN_Format(" X", MACHINE_LAST_POSITION["X"]) if "Y" in drill_Params and drill_Params["Y"] != 0: MACHINE_LAST_POSITION["Y"] = drill_Params["Y"] + MACHINE_LAST_POSITION["Y"] drill_Movement += HEIDEN_Format(" Y", MACHINE_LAST_POSITION["Y"]) if drill_Movement != "L": # same location drill_Output.append(drill_Movement + drill_Rapid) else: # not incremental # check if R is higher than actual Z and move if needed if drill_SafePoint > MACHINE_LAST_POSITION["Z"]: drill_Movement = "L" + HEIDEN_Format(" Z", drill_SafePoint) + drill_Rapid drill_Output.append(drill_Movement) MACHINE_LAST_POSITION["Z"] = drill_SafePoint # update X and Y position if "X" in drill_Params and drill_Params["X"] != MACHINE_LAST_POSITION["X"]: MACHINE_LAST_POSITION["X"] = drill_Params["X"] drill_Movement += HEIDEN_Format(" X", MACHINE_LAST_POSITION["X"]) if "Y" in drill_Params and drill_Params["Y"] != MACHINE_LAST_POSITION["Y"]: MACHINE_LAST_POSITION["Y"] = drill_Params["Y"] drill_Movement += HEIDEN_Format(" Y", MACHINE_LAST_POSITION["Y"]) if drill_Movement != "L": # same location drill_Output.append(drill_Movement + drill_Rapid) # check if R is not than actual Z and move if needed if drill_SafePoint != MACHINE_LAST_POSITION["Z"]: drill_Movement = "L" + HEIDEN_Format(" Z", drill_SafePoint) + drill_Rapid drill_Output.append(drill_Movement) MACHINE_LAST_POSITION["Z"] = drill_SafePoint # check if cycle is already stored i = True for j in drill_Defs: if drill_Defs[j] != STORED_CANNED_PARAMS[j]: i = False if i == False: # not same cycle, update and print for j in drill_Defs: STORED_CANNED_PARAMS[j] = drill_Defs[j] # get the DEF template and replace the strings drill_CycleDef = MACHINE_CYCLE_DEF[1].format( DIST=str("{:.3f}".format(drill_Defs["DIST"])), DEPTH=str("{:.3f}".format(drill_Defs["DEPTH"])), INCR=str("{:.3f}".format(drill_Defs["INCR"])), DWELL=str("{:.0f}".format(drill_Defs["DWELL"])), FEED=str("{:.0f}".format(drill_Defs["FEED"])), ) drill_Output.extend(drill_CycleDef.split("\n")) # add the cycle call to do the drill if MACHINE_SKIP_PARAMS: drill_Output.append("CYCL CALL") else: drill_Output.append("CYCL CALL M") # set already active cycle, to do: check changes and movements until G80 G_FUNCTION_STORE[drill_Type] = True return drill_Output def HEIDEN_Format(formatType, formatValue): global SPINDLE_DECIMALS global FEED_DECIMALS global AXIS_DECIMALS returnString = "" formatType = str(formatType) if formatType == "S" or formatType == " S": returnString = "%.*f" % (SPINDLE_DECIMALS, formatValue) elif formatType == "F" or formatType == " F": returnString = "%.*f" % (FEED_DECIMALS, formatValue) else: returnString = "%.*f" % (AXIS_DECIMALS, formatValue) return formatType + str(returnString) def HEIDEN_Numberize(GCodeStrings): # add line numbers and concatenation global LINENUMBERS global STARTLINENR global LINENUMBER_INCREMENT if LINENUMBERS: linenr = STARTLINENR result = "" for s in GCodeStrings: result += str(linenr) + " " + s + "\n" linenr += LINENUMBER_INCREMENT return result else: return "\n".join(GCodeStrings) def HEIDEN_LBL_Get(GetPoint=None, GetLevel=None, GetAddit=0): global POSTGCODE global STORED_LBL global LBLIZE_PATH_LEVEL if GetPoint is not None: if LBLIZE_PATH_LEVEL != GetLevel: LBLIZE_PATH_LEVEL = GetLevel if len(STORED_LBL) != 0 and len(STORED_LBL[-1][-1]) == 0: STORED_LBL[-1].pop() STORED_LBL.append([[len(POSTGCODE) + GetAddit, len(POSTGCODE) + GetAddit]]) else: if len(STORED_LBL[-1][-1]) == 0: STORED_LBL[-1][-1][0] = len(POSTGCODE) + GetAddit STORED_LBL[-1][-1][1] = len(POSTGCODE) + GetAddit else: if len(STORED_LBL) != 0 and len(STORED_LBL[-1][-1]) != 0: STORED_LBL[-1].append([]) return def HEIDEN_LBL_Replace(): global POSTGCODE global STORED_LBL global FIRST_LBL Gcode_Lenght = len(POSTGCODE) # this function look inside strings to find and replace it with LBL # the array is bi-dimensional every "Z" step down create a column # until the "Z" axis is moved or rapid movements are performed # these "planar with feed movements" create a new row with # the index of start and end POSTGCODE line # to LBLize we need to diff with the first column backward # from the last row in the last column of indexes Cols = len(STORED_LBL) - 1 if Cols > 0: FIRST_LBL += len(STORED_LBL[0]) # last LBL number to print for Col in range(Cols, 0, -1): LBL_Shift = 0 # LBL number iterator Rows = len(STORED_LBL[0]) - 1 for Row in range(Rows, -1, -1): LBL_Shift += 1 # get the indexes from actual column CellStart = STORED_LBL[Col][Row][1] CellStop = STORED_LBL[Col][Row][0] - 1 # get the indexes from first column RefStart = STORED_LBL[0][Row][1] i = 0 Remove = True # initial value True, be False on error for j in range(CellStart, CellStop, -1): # diff from actual to first index column/row if POSTGCODE[j] != POSTGCODE[RefStart - i]: Remove = False break i += 1 if Remove: # we can remove duplicate movements for j in range(CellStart, CellStop, -1): POSTGCODE.pop(j) # add the LBL calls POSTGCODE.insert(CellStop + 1, "CALL LBL " + str(FIRST_LBL - LBL_Shift)) if Gcode_Lenght != len(POSTGCODE): LBL_Shift = 0 Rows = len(STORED_LBL[0]) - 1 for Row in range(Rows, -1, -1): LBL_Shift += 1 RefEnd = STORED_LBL[0][Row][1] + 1 RefStart = STORED_LBL[0][Row][0] POSTGCODE.insert(RefEnd, "LBL 0") POSTGCODE.insert(RefStart, "LBL " + str(FIRST_LBL - LBL_Shift)) STORED_LBL.clear() return