Download src/Mod/CAM/Path/Post/UtilsParse.py from AbdulElahGwaith/FreeCAD: direct link, hf CLI and curl.
- Browser
- Download file 31 kB
-
https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/CAM/Path/Post/UtilsParse.py
- Command line
-
hf download hf://AbdulElahGwaith/FreeCAD/src/Mod/CAM/Path/Post/UtilsParse.py
-
curl -L -o UtilsParse.py https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/CAM/Path/Post/UtilsParse.py
31 kB
| # SPDX-License-Identifier: LGPL-2.1-or-later | |
| # *************************************************************************** | |
| # * 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> * | |
| # * Copyright (c) 2024 Carl Slater <CandLWorkshopLLC@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 math | |
| import re | |
| from typing import Any, Callable, Dict, List, Tuple, Union | |
| import FreeCAD | |
| from FreeCAD import Units | |
| import Path | |
| import Path.Post.Utils as PostUtils | |
| # Define some types that are used throughout this file | |
| CommandLine = List[str] | |
| Gcode = List[str] | |
| PathParameter = float | |
| PathParameters = Dict[str, PathParameter] | |
| Values = Dict[str, Any] | |
| ParameterFunction = Callable[[Values, str, str, PathParameter, PathParameters], str] | |
| def check_for_an_adaptive_op( | |
| values: Values, | |
| command: str, | |
| command_line: CommandLine, | |
| adaptive_op_variables: Tuple[bool, float, float], | |
| ) -> str: | |
| """Check to see if the current command is an adaptive op.""" | |
| adaptiveOp: bool | |
| opHorizRapid: float | |
| opVertRapid: float | |
| (adaptiveOp, opHorizRapid, opVertRapid) = adaptive_op_variables | |
| if values["OUTPUT_ADAPTIVE"] and adaptiveOp and command in values["RAPID_MOVES"]: | |
| if opHorizRapid and opVertRapid: | |
| return "G1" | |
| command_line.append(f"(Tool Controller Rapid Values are unset)") | |
| return "" | |
| def check_for_drill_translate( | |
| values: Values, | |
| gcode: Gcode, | |
| command: str, | |
| command_line: CommandLine, | |
| params: PathParameters, | |
| motion_location: PathParameters, | |
| drill_retract_mode: str, | |
| ) -> bool: | |
| """Check for drill commands to translate.""" | |
| comment: str | |
| if values["TRANSLATE_DRILL_CYCLES"] and command in values["DRILL_CYCLES_TO_TRANSLATE"]: | |
| if values["OUTPUT_COMMENTS"]: # Comment the original command | |
| comment = create_comment(values, format_command_line(values, command_line)) | |
| gcode.append(f"{linenumber(values)}{comment}") | |
| # wrap this block to ensure that the value of values["MOTION_MODE"] | |
| # is restored in case of error | |
| try: | |
| drill_translate( | |
| values, | |
| gcode, | |
| command, | |
| params, | |
| motion_location, | |
| drill_retract_mode, | |
| ) | |
| except (ArithmeticError, LookupError) as err: | |
| print("exception occurred", err) | |
| # drill_translate uses G90 mode internally, so need to | |
| # switch back to G91 mode if it was that way originally | |
| if values["MOTION_MODE"] == "G91": | |
| gcode.append(f"{linenumber(values)}G91") | |
| return True | |
| return False | |
| def check_for_machine_specific_commands(values: Values, gcode: Gcode, command: str) -> None: | |
| """Check for comments containing machine-specific commands.""" | |
| m: object | |
| raw_command: str | |
| if values["ENABLE_MACHINE_SPECIFIC_COMMANDS"]: | |
| m = re.match(r"^\(MC_RUN_COMMAND: ([^)]+)\)$", command) | |
| if m: | |
| raw_command = m.group(1) | |
| # pass literally to the controller | |
| gcode.append(f"{linenumber(values)}{raw_command}") | |
| def check_for_spindle_wait( | |
| values: Values, gcode: Gcode, command: str, command_line: CommandLine | |
| ) -> None: | |
| """Check for commands that might need a wait command after them.""" | |
| cmd: str | |
| if values["SPINDLE_WAIT"] > 0 and command in ("M3", "M03", "M4", "M04"): | |
| gcode.append(f"{linenumber(values)}{format_command_line(values, command_line)}") | |
| cmd = format_command_line(values, ["G4", f'P{values["SPINDLE_WAIT"]}']) | |
| gcode.append(f"{linenumber(values)}{cmd}") | |
| def check_for_suppressed_commands( | |
| values: Values, gcode: Gcode, command: str, command_line: CommandLine | |
| ) -> bool: | |
| """Check for commands that will be suppressed.""" | |
| comment: str | |
| if command in values["SUPPRESS_COMMANDS"]: | |
| if values["OUTPUT_COMMENTS"]: | |
| # convert the command to a comment | |
| comment = create_comment(values, format_command_line(values, command_line)) | |
| gcode.append(f"{linenumber(values)}{comment}") | |
| # remove the command | |
| return True | |
| return False | |
| def check_for_tlo(values: Values, gcode: Gcode, command: str, params: PathParameters) -> None: | |
| """Output a tool length command if USE_TLO is True.""" | |
| if command in ("M6", "M06") and values["USE_TLO"]: | |
| cmd = format_command_line(values, ["G43", f'H{str(int(params["T"]))}']) | |
| gcode.append(f"{linenumber(values)}{cmd}") | |
| def check_for_tool_change( | |
| values: Values, gcode: Gcode, command: str, command_line: CommandLine | |
| ) -> bool: | |
| """Check for a tool change.""" | |
| if command in ("M6", "M06"): | |
| if values["OUTPUT_COMMENTS"]: | |
| comment = create_comment(values, "Begin toolchange") | |
| gcode.append(f"{linenumber(values)}{comment}") | |
| if values["OUTPUT_TOOL_CHANGE"]: | |
| if values["STOP_SPINDLE_FOR_TOOL_CHANGE"]: | |
| # stop the spindle | |
| gcode.append(f"{linenumber(values)}M5") | |
| for line in values["TOOL_CHANGE"].splitlines(False): | |
| gcode.append(f"{linenumber(values)}{line}") | |
| return False | |
| if values["OUTPUT_COMMENTS"]: | |
| # convert the tool change to a comment | |
| comment = create_comment(values, format_command_line(values, command_line)) | |
| gcode.append(f"{linenumber(values)}{comment}") | |
| return True | |
| return False | |
| def create_comment(values: Values, comment_string: str) -> str: | |
| """Create a comment from a string using the correct comment symbol.""" | |
| if values["COMMENT_SYMBOL"] == "(": | |
| return f"({comment_string})" | |
| return values["COMMENT_SYMBOL"] + comment_string | |
| def default_axis_parameter( | |
| values: Values, | |
| command: str, # pylint: disable=unused-argument | |
| param: str, | |
| param_value: PathParameter, | |
| parameters: PathParameters, # pylint: disable=unused-argument | |
| current_location: PathParameters, | |
| ) -> str: | |
| """Process an axis parameter.""" | |
| # | |
| # used to compare two floating point numbers for "close-enough equality" | |
| # | |
| epsilon: float = 0.00001 | |
| if ( | |
| not values["OUTPUT_DOUBLES"] | |
| and param in current_location | |
| and math.fabs(current_location[param] - param_value) < epsilon | |
| ): | |
| return "" | |
| return format_for_axis(values, Units.Quantity(param_value, Units.Length)) | |
| def default_D_parameter( | |
| values: Values, | |
| command: str, | |
| param: str, # pylint: disable=unused-argument | |
| param_value: PathParameter, | |
| parameters: PathParameters, # pylint: disable=unused-argument | |
| current_location: PathParameters, # pylint: disable=unused-argument | |
| ) -> str: | |
| """Process the D parameter.""" | |
| if command in ("G41", "G42"): | |
| return str(int(param_value)) | |
| if command in ("G41.1", "G42.1"): | |
| return format_for_axis(values, Units.Quantity(param_value, Units.Length)) | |
| if command in ("G96", "G97"): | |
| return format_for_spindle(values, param_value) | |
| # anything else that is supported | |
| return str(float(param_value)) | |
| def default_F_parameter( | |
| values: Values, | |
| command: str, | |
| param: str, | |
| param_value: PathParameter, | |
| parameters: PathParameters, | |
| current_location: PathParameters, | |
| ) -> str: | |
| """Process the F parameter.""" | |
| # | |
| # used to compare two floating point numbers for "close-enough equality" | |
| # | |
| epsilon: float = 0.00001 | |
| found: bool | |
| if ( | |
| not values["OUTPUT_DOUBLES"] | |
| and param in current_location | |
| and math.fabs(current_location[param] - param_value) < epsilon | |
| ): | |
| return "" | |
| # Many posts don't use rapid speeds, but eventually | |
| # there will be refactored posts that do, so this | |
| # "if statement" is being kept separate to make it | |
| # more obvious where to put that check. | |
| if command in values["RAPID_MOVES"]: | |
| return "" | |
| feed = Units.Quantity(param_value, Units.Velocity) | |
| if feed.getValueAs(values["UNIT_SPEED_FORMAT"]) <= 0.0: | |
| return "" | |
| # if any of X, Y, Z, U, V, or W are in the parameters | |
| # and any of their values is different than where the device currently should be | |
| # then feed is in linear units | |
| found = False | |
| for key in ("X", "Y", "Z", "U", "V", "W"): | |
| if key in parameters and math.fabs(current_location[key] - parameters[key]) > epsilon: | |
| found = True | |
| if found: | |
| return format_for_feed(values, feed) | |
| # else if any of A, B, or C are in the parameters, the feed is in degrees, | |
| # which should not be converted when in --inches mode | |
| found = False | |
| for key in ("A", "B", "C"): | |
| if key in parameters: | |
| found = True | |
| if found: | |
| # converting from degrees per second to degrees per minute as well | |
| return format(float(feed * 60.0), f'.{str(values["FEED_PRECISION"])}f') | |
| # which leaves none of X, Y, Z, U, V, W, A, B, C, | |
| # which should not be valid but return a converted value just in case | |
| return format_for_feed(values, feed) | |
| def default_int_parameter( | |
| values: Values, # pylint: disable=unused-argument | |
| command: str, # pylint: disable=unused-argument | |
| param: str, # pylint: disable=unused-argument | |
| param_value: PathParameter, | |
| parameters: PathParameters, # pylint: disable=unused-argument | |
| current_location: PathParameters, # pylint: disable=unused-argument | |
| ) -> str: | |
| """Process a parameter that is treated like an integer.""" | |
| return str(int(param_value)) | |
| def default_length_parameter( | |
| values: Values, | |
| command: str, # pylint: disable=unused-argument | |
| param: str, # pylint: disable=unused-argument | |
| param_value: PathParameter, | |
| parameters: PathParameters, # pylint: disable=unused-argument | |
| current_location: PathParameters, # pylint: disable=unused-argument | |
| ) -> str: | |
| """Process a parameter that is treated like a length.""" | |
| return format_for_axis(values, Units.Quantity(param_value, Units.Length)) | |
| def default_P_parameter( | |
| values: Values, | |
| command: str, | |
| param: str, # pylint: disable=unused-argument | |
| param_value: PathParameter, | |
| parameters: PathParameters, # pylint: disable=unused-argument | |
| current_location: PathParameters, # pylint: disable=unused-argument | |
| ) -> str: | |
| """Process the P parameter.""" | |
| if command in ("G2", "G02", "G3", "G03", "G5.2", "G5.3", "G10", "G54.1", "G59"): | |
| return str(int(param_value)) | |
| if command in ("G4", "G04", "G76", "G82", "G86", "G89"): | |
| return str(float(param_value)) | |
| if command in ("G5", "G05", "G64"): | |
| return format_for_axis(values, Units.Quantity(param_value, Units.Length)) | |
| # anything else that is supported | |
| return str(param_value) | |
| def default_Q_parameter( | |
| values: Values, | |
| command: str, | |
| param: str, # pylint: disable=unused-argument | |
| param_value: PathParameter, | |
| parameters: PathParameters, # pylint: disable=unused-argument | |
| current_location: PathParameters, # pylint: disable=unused-argument | |
| ) -> str: | |
| """Process the Q parameter.""" | |
| if command == "G10": | |
| return str(int(param_value)) | |
| if command in ("G64", "G73", "G83"): | |
| return format_for_axis(values, Units.Quantity(param_value, Units.Length)) | |
| return "" | |
| def default_rotary_parameter( | |
| values: Values, | |
| command: str, # pylint: disable=unused-argument | |
| param: str, | |
| param_value: PathParameter, | |
| parameters: PathParameters, # pylint: disable=unused-argument | |
| current_location: PathParameters, | |
| ) -> str: | |
| """Process a rotarty parameter (such as A, B, and C).""" | |
| # | |
| # used to compare two floating point numbers for "close-enough equality" | |
| # | |
| epsilon: float = 0.00001 | |
| if ( | |
| not values["OUTPUT_DOUBLES"] | |
| and param in current_location | |
| and math.fabs(current_location[param] - param_value) < epsilon | |
| ): | |
| return "" | |
| # unlike other axis, rotary axis such as A, B, and C are always in degrees | |
| # and should not be converted when in --inches mode | |
| return str(format(float(param_value), f'.{str(values["AXIS_PRECISION"])}f')) | |
| def default_S_parameter( | |
| values: Values, | |
| command: str, # pylint: disable=unused-argument | |
| param: str, # pylint: disable=unused-argument | |
| param_value: PathParameter, | |
| parameters: PathParameters, # pylint: disable=unused-argument | |
| current_location: PathParameters, # pylint: disable=unused-argument | |
| ) -> str: | |
| """Process the S parameter.""" | |
| return format_for_spindle(values, param_value) | |
| def determine_adaptive_op(values: Values, pathobj) -> Tuple[bool, float, float]: | |
| """Determine if the pathobj contains an Adaptive operation.""" | |
| nl = "\n" | |
| adaptiveOp: bool = False | |
| opHorizRapid: float = 0.0 | |
| opVertRapid: float = 0.0 | |
| if values["OUTPUT_ADAPTIVE"] and "Adaptive" in pathobj.Name: | |
| adaptiveOp = True | |
| if hasattr(pathobj, "ToolController"): | |
| tc = pathobj.ToolController | |
| if hasattr(tc, "HorizRapid") and tc.HorizRapid > 0: | |
| opHorizRapid = Units.Quantity(tc.HorizRapid, Units.Velocity) | |
| else: | |
| FreeCAD.Console.PrintWarning( | |
| f"Tool Controller Horizontal Rapid Values are unset{nl}" | |
| ) | |
| if hasattr(tc, "VertRapid") and tc.VertRapid > 0: | |
| opVertRapid = Units.Quantity(tc.VertRapid, Units.Velocity) | |
| else: | |
| FreeCAD.Console.PrintWarning(f"Tool Controller Vertical Rapid Values are unset{nl}") | |
| return (adaptiveOp, opHorizRapid, opVertRapid) | |
| def drill_translate( | |
| values: Values, | |
| gcode: Gcode, | |
| command: str, | |
| params: PathParameters, | |
| motion_location: PathParameters, | |
| drill_retract_mode: str, | |
| ) -> None: | |
| """Translate drill cycles. | |
| Currently only cycles in XY are provided (G17). | |
| XZ (G18) and YZ (G19) are not dealt with. | |
| In other words only Z drilling can be translated. | |
| """ | |
| cmd: str | |
| comment: str | |
| drill_x: float | |
| drill_y: float | |
| drill_z: float | |
| motion_z: float | |
| retract_z: float | |
| F_feedrate: str | |
| G0_retract_z: str | |
| if values["MOTION_MODE"] == "G91": | |
| # force absolute coordinates during cycles | |
| gcode.append(f"{linenumber(values)}G90") | |
| drill_x = Units.Quantity(params["X"], Units.Length) | |
| drill_y = Units.Quantity(params["Y"], Units.Length) | |
| drill_z = Units.Quantity(params["Z"], Units.Length) | |
| retract_z = Units.Quantity(params["R"], Units.Length) | |
| if retract_z < drill_z: # R less than Z is error | |
| comment = create_comment(values, "Drill cycle error: R less than Z") | |
| gcode.append(f"{linenumber(values)}{comment}") | |
| return | |
| motion_z = Units.Quantity(motion_location["Z"], Units.Length) | |
| if values["MOTION_MODE"] == "G91": # relative movements | |
| drill_x += Units.Quantity(motion_location["X"], Units.Length) | |
| drill_y += Units.Quantity(motion_location["Y"], Units.Length) | |
| drill_z += motion_z | |
| retract_z += motion_z | |
| if drill_retract_mode == "G98" and motion_z >= retract_z: | |
| retract_z = motion_z | |
| cmd = format_command_line(values, ["G0", f"Z{format_for_axis(values, retract_z)}"]) | |
| G0_retract_z = f"{cmd}" | |
| cmd = format_for_feed(values, Units.Quantity(params["F"], Units.Velocity)) | |
| F_feedrate = f'{values["COMMAND_SPACE"]}F{cmd}' | |
| # preliminary movement(s) | |
| if motion_z < retract_z: | |
| gcode.append(f"{linenumber(values)}{G0_retract_z}") | |
| cmd = format_command_line( | |
| values, | |
| [ | |
| "G0", | |
| f"X{format_for_axis(values, drill_x)}", | |
| f"Y{format_for_axis(values, drill_y)}", | |
| ], | |
| ) | |
| gcode.append(f"{linenumber(values)}{cmd}") | |
| if motion_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 ! | |
| cmd = format_command_line(values, ["G1", f"Z{format_for_axis(values, retract_z)}"]) | |
| gcode.append(f"{linenumber(values)}{cmd}{F_feedrate}") | |
| # drill moves | |
| if command in ("G81", "G82"): | |
| output_G81_G82_drill_moves( | |
| values, gcode, command, params, drill_z, F_feedrate, G0_retract_z | |
| ) | |
| elif command in ("G73", "G83"): | |
| output_G73_G83_drill_moves( | |
| values, gcode, command, params, drill_z, retract_z, F_feedrate, G0_retract_z | |
| ) | |
| def format_command_line(values: Values, command_line: CommandLine) -> str: | |
| """Construct the command line for the final output.""" | |
| return values["COMMAND_SPACE"].join(command_line) | |
| def format_for_axis(values: Values, number) -> str: | |
| """Format a number using the precision for an axis value.""" | |
| return str( | |
| format( | |
| float(number.getValueAs(values["UNIT_FORMAT"])), | |
| f'.{str(values["AXIS_PRECISION"])}f', | |
| ) | |
| ) | |
| def format_for_feed(values: Values, number) -> str: | |
| """Format a number using the precision for a feed rate.""" | |
| return str( | |
| format( | |
| float(number.getValueAs(values["UNIT_SPEED_FORMAT"])), | |
| f'.{str(values["FEED_PRECISION"])}f', | |
| ) | |
| ) | |
| def format_for_spindle(values: Values, number) -> str: | |
| """Format a number using the precision for a spindle speed.""" | |
| return str(format(float(number), f'.{str(values["SPINDLE_DECIMALS"])}f')) | |
| def init_parameter_functions(parameter_functions: Dict[str, ParameterFunction]) -> None: | |
| """Initialize a list of parameter functions. | |
| These functions are called in the UtilsParse.parse_a_path | |
| function to return the appropriate parameter value. | |
| """ | |
| default_parameter_functions: Dict[str, ParameterFunction] | |
| parameter: str | |
| default_parameter_functions = { | |
| "A": default_rotary_parameter, | |
| "B": default_rotary_parameter, | |
| "C": default_rotary_parameter, | |
| "D": default_D_parameter, | |
| "E": default_length_parameter, | |
| "F": default_F_parameter, | |
| # "G" is reserved for G-code commands | |
| "H": default_int_parameter, | |
| "I": default_length_parameter, | |
| "J": default_length_parameter, | |
| "K": default_length_parameter, | |
| "L": default_int_parameter, | |
| # "M" is reserved for M-code commands | |
| # "N" is reserved for the line numbers | |
| # "O" is reserved for the line numbers for subroutines | |
| "P": default_P_parameter, | |
| "Q": default_Q_parameter, | |
| "R": default_length_parameter, | |
| "S": default_S_parameter, | |
| "T": default_int_parameter, | |
| "U": default_axis_parameter, | |
| "V": default_axis_parameter, | |
| "W": default_axis_parameter, | |
| "X": default_axis_parameter, | |
| "Y": default_axis_parameter, | |
| "Z": default_axis_parameter, | |
| # "$" is used by LinuxCNC (and others?) to designate which spindle | |
| } | |
| for parameter in default_parameter_functions: # pylint: disable=consider-using-dict-items | |
| parameter_functions[parameter] = default_parameter_functions[parameter] | |
| def linenumber(values: Values, space: Union[str, None] = None) -> str: | |
| """Output the next line number if appropriate.""" | |
| line_num: str | |
| if not values["OUTPUT_LINE_NUMBERS"]: | |
| return "" | |
| if space is None: | |
| space = values["COMMAND_SPACE"] | |
| line_num = str(values["line_number"]) | |
| values["line_number"] += values["LINE_INCREMENT"] | |
| return f"N{line_num}{space}" | |
| def output_G73_G83_drill_moves( | |
| values: Values, | |
| gcode: Gcode, | |
| command: str, | |
| params: PathParameters, | |
| drill_z: float, | |
| retract_z: float, | |
| F_feedrate: str, | |
| G0_retract_z: str, | |
| ) -> None: | |
| """Output the movement G code for G73 and G83.""" | |
| a_bit: float | |
| chip_breaker_height: float | |
| clearance_depth: float | |
| cmd: str | |
| drill_step: float | |
| last_stop_z: float | |
| next_stop_z: float | |
| last_stop_z = retract_z | |
| drill_step = Units.Quantity(params["Q"], Units.Length) | |
| # NIST 3.5.16.4 G83 Cycle: "current hole bottom, backed off a bit." | |
| a_bit = drill_step * 0.05 | |
| if drill_step != 0: | |
| while True: | |
| if last_stop_z != retract_z: | |
| # rapid move to just short of last drilling depth | |
| clearance_depth = last_stop_z + a_bit | |
| cmd = format_command_line( | |
| values, | |
| ["G0", f"Z{format_for_axis(values, clearance_depth)}"], | |
| ) | |
| gcode.append(f"{linenumber(values)}{cmd}") | |
| next_stop_z = last_stop_z - drill_step | |
| if next_stop_z > drill_z: | |
| cmd = format_command_line( | |
| values, ["G1", f"Z{format_for_axis(values, next_stop_z)}"] | |
| ) | |
| gcode.append(f"{linenumber(values)}{cmd}{F_feedrate}") | |
| if command == "G73": | |
| # Rapid up "a small amount". | |
| chip_breaker_height = next_stop_z + values["CHIPBREAKING_AMOUNT"] | |
| cmd = format_command_line( | |
| values, | |
| [ | |
| "G0", | |
| f"Z{format_for_axis(values, chip_breaker_height)}", | |
| ], | |
| ) | |
| gcode.append(f"{linenumber(values)}{cmd}") | |
| elif command == "G83": | |
| # Rapid up to the retract height | |
| gcode.append(f"{linenumber(values)}{G0_retract_z}") | |
| last_stop_z = next_stop_z | |
| else: | |
| cmd = format_command_line(values, ["G1", f"Z{format_for_axis(values, drill_z)}"]) | |
| gcode.append(f"{linenumber(values)}{cmd}{F_feedrate}") | |
| gcode.append(f"{linenumber(values)}{G0_retract_z}") | |
| break | |
| def output_G81_G82_drill_moves( | |
| values: Values, | |
| gcode: Gcode, | |
| command: str, | |
| params: PathParameters, | |
| drill_z: float, | |
| F_feedrate: str, | |
| G0_retract_z: str, | |
| ) -> None: | |
| """Output the movement G code for G81 and G82.""" | |
| cmd: str | |
| cmd = format_command_line(values, ["G1", f"Z{format_for_axis(values, drill_z)}"]) | |
| gcode.append(f"{linenumber(values)}{cmd}{F_feedrate}") | |
| # pause where applicable | |
| if command == "G82": | |
| cmd = format_command_line(values, ["G4", f'P{str(params["P"])}']) | |
| gcode.append(f"{linenumber(values)}{cmd}") | |
| gcode.append(f"{linenumber(values)}{G0_retract_z}") | |
| def parse_a_group(values: Values, gcode: Gcode, pathobj) -> None: | |
| """Parse a Group (compound, project, or simple path).""" | |
| comment: str | |
| if hasattr(pathobj, "Group"): # We have a compound or project. | |
| if values["OUTPUT_COMMENTS"]: | |
| comment = create_comment(values, f"Compound: {pathobj.Label}") | |
| gcode.append(f"{linenumber(values)}{comment}") | |
| for p in pathobj.Group: | |
| parse_a_group(values, gcode, p) | |
| else: # parsing simple path | |
| # groups might contain non-path things like stock. | |
| if not hasattr(pathobj, "Path"): | |
| return | |
| if values["OUTPUT_PATH_LABELS"] and values["OUTPUT_COMMENTS"]: | |
| comment = create_comment(values, f"Path: {pathobj.Label}") | |
| gcode.append(f"{linenumber(values)}{comment}") | |
| parse_a_path(values, gcode, pathobj) | |
| def parse_a_path(values: Values, gcode: Gcode, pathobj) -> None: | |
| """Parse a simple Path.""" | |
| adaptive_op_variables: Tuple[bool, float, float] | |
| cmd: str | |
| command: str | |
| command_line: CommandLine | |
| current_location: PathParameters = {} # keep track for no doubles | |
| drill_retract_mode: str = "G98" | |
| lastcommand: str = "" | |
| motion_location: PathParameters = {} # keep track of last motion location | |
| parameter: str | |
| parameter_value: str | |
| # Check to see if values["TOOL_BEFORE_CHANGE"] is set and value is true | |
| # doing it here to reduce the number of times it is checked | |
| swap_tool_change_order = False | |
| if "TOOL_BEFORE_CHANGE" in values and values["TOOL_BEFORE_CHANGE"]: | |
| swap_tool_change_order = True | |
| current_location.update( | |
| # the goal is to have initial values that aren't likely to match | |
| # any "real" first parameter values | |
| Path.Command( | |
| "G0", | |
| { | |
| "X": 123456789.0, | |
| "Y": 123456789.0, | |
| "Z": 123456789.0, | |
| "U": 123456789.0, | |
| "V": 123456789.0, | |
| "W": 123456789.0, | |
| "A": 123456789.0, | |
| "B": 123456789.0, | |
| "C": 123456789.0, | |
| "F": 123456789.0, | |
| }, | |
| ).Parameters | |
| ) | |
| adaptive_op_variables = determine_adaptive_op(values, pathobj) | |
| # Apply arc splitting if requested | |
| path_to_process = pathobj.Path | |
| if values["SPLIT_ARCS"]: | |
| path_to_process = PostUtils.splitArcs(path_to_process) | |
| for c in path_to_process.Commands: | |
| command = c.Name | |
| command_line = [] | |
| # Skip blank lines if requested | |
| if not command: | |
| if not values["OUTPUT_BLANK_LINES"]: | |
| continue | |
| # Modify the command name if necessary | |
| if command.startswith("("): | |
| if not values["OUTPUT_COMMENTS"]: | |
| continue | |
| if values["COMMENT_SYMBOL"] != "(" and len(command) > 2: | |
| command = create_comment(values, command[1:-1]) | |
| cmd = check_for_an_adaptive_op(values, command, command_line, adaptive_op_variables) | |
| if cmd: | |
| command = cmd | |
| # Add the command name to the command line | |
| command_line.append(command) | |
| # if modal: suppress the command if it is the same as the last one | |
| if values["MODAL"] and command == lastcommand: | |
| command_line.pop(0) | |
| # Now add the remaining parameters in order | |
| for parameter in values["PARAMETER_ORDER"]: | |
| if parameter in c.Parameters: | |
| parameter_value = values["PARAMETER_FUNCTIONS"][parameter]( | |
| values, | |
| command, | |
| parameter, | |
| c.Parameters[parameter], | |
| c.Parameters, | |
| current_location, | |
| ) | |
| if parameter_value: | |
| command_line.append(f"{parameter}{parameter_value}") | |
| set_adaptive_op_speed(values, command, command_line, c.Parameters, adaptive_op_variables) | |
| # Remember the current command | |
| lastcommand = command | |
| # Remember the current location | |
| current_location.update(c.Parameters) | |
| if command in ("G90", "G91"): | |
| # Remember the motion mode | |
| values["MOTION_MODE"] = command | |
| elif command in ("G98", "G99"): | |
| # Remember the drill retract mode for drill_translate | |
| drill_retract_mode = command | |
| if command in values["MOTION_COMMANDS"]: | |
| # Remember the current location for drill_translate | |
| motion_location.update(c.Parameters) | |
| if check_for_drill_translate( | |
| values, | |
| gcode, | |
| command, | |
| command_line, | |
| c.Parameters, | |
| motion_location, | |
| drill_retract_mode, | |
| ): | |
| command_line = [] | |
| check_for_spindle_wait(values, gcode, command, command_line) | |
| if check_for_tool_change(values, gcode, command, command_line): | |
| command_line = [] | |
| if check_for_suppressed_commands(values, gcode, command, command_line): | |
| command_line = [] | |
| if command_line: | |
| if command in ("M6", "M06") and swap_tool_change_order: | |
| swapped_command_line = [ | |
| command_line[1], | |
| command_line[0], | |
| ] # swap the order of the commands | |
| # Add a line number to the front of the command line | |
| gcode.append( | |
| f"{linenumber(values)}{format_command_line(values, swapped_command_line)}" | |
| ) | |
| else: | |
| # Add a line number to the front of the command line | |
| gcode.append(f"{linenumber(values)}{format_command_line(values, command_line)}") | |
| check_for_tlo(values, gcode, command, c.Parameters) | |
| check_for_machine_specific_commands(values, gcode, command) | |
| def set_adaptive_op_speed( | |
| values: Values, | |
| command: str, | |
| command_line: CommandLine, | |
| params: PathParameters, | |
| adaptive_op_variables: Tuple[bool, float, float], | |
| ) -> None: | |
| """Set the appropriate feed speed for an adaptive op.""" | |
| adaptiveOp: bool | |
| opHorizRapid: float | |
| opVertRapid: float | |
| param_num: str | |
| (adaptiveOp, opHorizRapid, opVertRapid) = adaptive_op_variables | |
| if ( | |
| values["OUTPUT_ADAPTIVE"] | |
| and adaptiveOp | |
| and command in values["RAPID_MOVES"] | |
| and opHorizRapid | |
| and opVertRapid | |
| ): | |
| if "Z" not in params: | |
| param_num = format_for_feed(values, opHorizRapid) | |
| else: | |
| param_num = format_for_feed(values, opVertRapid) | |
| command_line.append(f"F{param_num}") | |