freecad-cam/Mod/CAM/Path/Post/scripts/heidenhain_post.py
2026-02-01 01:59:24 +01:00

1143 lines
41 KiB
Python

# -*- coding: UTF-8 -*-
# ***************************************************************************
# * Copyright (C) 2020 Stefano Chiaro <stefano.chiaro@yahoo.com> *
# * *
# * 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