Files
create/src/Mod/Path/PathScripts/PostUtilsParse.py
2022-07-11 15:09:15 -07:00

580 lines
24 KiB
Python

# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> *
# * Copyright (c) 2014 sliptonic <shopinthewoods@gmail.com> *
# * Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
# * Copyright (c) 2018, 2019 Gauthier Briere *
# * Copyright (c) 2019, 2020 Schildkroet *
# * Copyright (c) 2022 Larry Woestman <LarryWoestman2@gmail.com> *
# * *
# * 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