From dd6a6d1d6cb75a4eb491e5e7c6e5a30c9d5bb532 Mon Sep 17 00:00:00 2001 From: LarryWoestman Date: Sun, 24 Jul 2022 15:52:36 -0700 Subject: [PATCH] Path: Major refactoring of the export_common function. Minor refactors to parse_a_path. Removed "message" command handling. --- src/Mod/Path/Path/Post/UtilsArguments.py | 4 - src/Mod/Path/Path/Post/UtilsExport.py | 480 ++++++---- src/Mod/Path/Path/Post/UtilsParse.py | 889 +++++++++++------- .../Post/scripts/refactored_centroid_post.py | 4 - 4 files changed, 860 insertions(+), 517 deletions(-) diff --git a/src/Mod/Path/Path/Post/UtilsArguments.py b/src/Mod/Path/Path/Post/UtilsArguments.py index 2adcb76109df..3019821afd85 100644 --- a/src/Mod/Path/Path/Post/UtilsArguments.py +++ b/src/Mod/Path/Path/Post/UtilsArguments.py @@ -504,10 +504,6 @@ def init_shared_values(values): # values["RAPID_MOVES"] = ["G0", "G00"] # - # If True suppress any messages. - # - values["REMOVE_MESSAGES"] = True - # # Any commands in this value are output after the operation(s) # and post_operation commands are output but before the # TOOLRETURN, SAFETYBLOCK, and POSTAMBLE. diff --git a/src/Mod/Path/Path/Post/UtilsExport.py b/src/Mod/Path/Path/Post/UtilsExport.py index b34b63ae1231..ac753130ac1e 100644 --- a/src/Mod/Path/Path/Post/UtilsExport.py +++ b/src/Mod/Path/Path/Post/UtilsExport.py @@ -29,108 +29,269 @@ import datetime import os +from typing import Any, Dict, List import FreeCAD import Path.Post.Utils as PostUtils import Path.Post.UtilsParse as PostUtilsParse import Path.Tool.Controller as PathToolController -# -# This routine processes things in the following order: -# -# OUTPUT_HEADER -# SAFETYBLOCK -# LIST_TOOLS_IN_PREAMBLE -# PREAMBLE -# OUTPUT_BCNC -# SHOW_OPERATION_LABELS -# SHOW_MACHINE_UNITS -# PRE_OPERATION -# ENABLE_COOLANT (coolant on) -# operation(s) -# POST_OPERATION -# ENABLE_COOLANT (coolant off) -# RETURN_TO -# OUTPUT_BCNC -# TOOLRETURN -# SAFETYBLOCK -# POSTAMBLE -# SHOW_EDITOR -# -# The names in all caps may be enabled/disabled/modified by setting -# the corresponding value in the postprocessor. -# - - -def export_common(values, objectslist, filename): - """Do the common parts of postprocessing the objects in objectslist to filename.""" - # - nl = "\n" - - for obj in objectslist: - if not hasattr(obj, "Path"): - print( - f"The object {obj.Name} is not a path. Please select only path and Compounds." - ) - return None - - # for obj in objectslist: - # print(obj.Name) +# Define some types that are used throughout this file +Gcode = List[str] +Values = Dict[str, Any] - print(f'PostProcessor: {values["POSTPROCESSOR_FILE_NAME"]} postprocessing...') - gcode = "" - # write header - if values["OUTPUT_HEADER"]: - comment = PostUtilsParse.create_comment(values, "Exported by FreeCAD") - gcode += f"{PostUtilsParse.linenumber(values)}{comment}{nl}" - comment = PostUtilsParse.create_comment( - values, f'Post Processor: {values["POSTPROCESSOR_FILE_NAME"]}' - ) - gcode += f"{PostUtilsParse.linenumber(values)}{comment}{nl}" - if FreeCAD.ActiveDocument: - cam_file = os.path.basename(FreeCAD.ActiveDocument.FileName) - else: - cam_file = "" - comment = PostUtilsParse.create_comment(values, f"Cam File: {cam_file}") - gcode += f"{PostUtilsParse.linenumber(values)}{comment}{nl}" - comment = PostUtilsParse.create_comment( - values, f"Output Time: {str(datetime.datetime.now())}" - ) - gcode += f"{PostUtilsParse.linenumber(values)}{comment}{nl}" - - # Check canned cycles for drilling +def check_canned_cycles(values: Values) -> None: + """Check canned cycles for drilling.""" if values["TRANSLATE_DRILL_CYCLES"]: if len(values["SUPPRESS_COMMANDS"]) == 0: values["SUPPRESS_COMMANDS"] = ["G99", "G98", "G80"] else: values["SUPPRESS_COMMANDS"] += ["G99", "G98", "G80"] - for line in values["SAFETYBLOCK"].splitlines(False): - gcode += f"{PostUtilsParse.linenumber(values)}{line}{nl}" - # Write the preamble - if values["OUTPUT_COMMENTS"]: - if values["LIST_TOOLS_IN_PREAMBLE"]: - for item in objectslist: - if hasattr(item, "Proxy") and isinstance( - item.Proxy, PathToolController.ToolController - ): - comment = PostUtilsParse.create_comment( - values, f"T{item.ToolNumber}={item.Name}" - ) - gcode += f"{PostUtilsParse.linenumber(values)}{comment}{nl}" - comment = PostUtilsParse.create_comment(values, "Begin preamble") - gcode += f"{PostUtilsParse.linenumber(values)}{comment}{nl}" - for line in values["PREAMBLE"].splitlines(False): - gcode += f"{PostUtilsParse.linenumber(values)}{line}{nl}" - # verify if PREAMBLE or SAFETYBLOCK have changed MOTION_MODE or UNITS +def determine_coolant_mode(obj) -> str: + """Determine the coolant mode.""" + if ( + hasattr(obj, "CoolantMode") + or hasattr(obj, "Base") + and hasattr(obj.Base, "CoolantMode") + ): + if hasattr(obj, "CoolantMode"): + return obj.CoolantMode + return obj.Base.CoolantMode + return "None" + + +def output_coolant_off(values: Values, gcode: Gcode, coolant_mode: str) -> None: + """Output the commands to turn coolant off if necessary.""" + comment: str + nl: str = "\n" + + if values["ENABLE_COOLANT"] and coolant_mode != "None": + if values["OUTPUT_COMMENTS"]: + comment = PostUtilsParse.create_comment( + values, f"Coolant Off: {coolant_mode}" + ) + gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}{nl}") + gcode.append(f"{PostUtilsParse.linenumber(values)}M9{nl}") + + +def output_coolant_on(values: Values, gcode: Gcode, coolant_mode: str) -> None: + """Output the commands to turn coolant on if necessary.""" + comment: str + nl: str = "\n" + + if values["ENABLE_COOLANT"]: + if values["OUTPUT_COMMENTS"] and coolant_mode != "None": + comment = PostUtilsParse.create_comment( + values, f"Coolant On: {coolant_mode}" + ) + gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}{nl}") + if coolant_mode == "Flood": + gcode.append(f"{PostUtilsParse.linenumber(values)}M8{nl}") + elif coolant_mode == "Mist": + gcode.append(f"{PostUtilsParse.linenumber(values)}M7{nl}") + + +def output_end_bcnc(values: Values, gcode: Gcode) -> None: + """Output the ending BCNC header.""" + comment: str + nl: str = "\n" + + if values["OUTPUT_BCNC"]: + comment = PostUtilsParse.create_comment(values, "Block-name: post_amble") + gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}{nl}") + comment = PostUtilsParse.create_comment(values, "Block-expand: 0") + gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}{nl}") + comment = PostUtilsParse.create_comment(values, "Block-enable: 1") + gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}{nl}") + + +def output_header(values: Values, gcode: Gcode) -> None: + """Output the header.""" + cam_file: str + comment: str + nl: str = "\n" + + if not values["OUTPUT_HEADER"]: + return + comment = PostUtilsParse.create_comment(values, "Exported by FreeCAD") + gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}{nl}") + comment = PostUtilsParse.create_comment( + values, f'Post Processor: {values["POSTPROCESSOR_FILE_NAME"]}' + ) + gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}{nl}") + if FreeCAD.ActiveDocument: + cam_file = os.path.basename(FreeCAD.ActiveDocument.FileName) + else: + cam_file = "" + comment = PostUtilsParse.create_comment(values, f"Cam File: {cam_file}") + gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}{nl}") + comment = PostUtilsParse.create_comment( + values, f"Output Time: {str(datetime.datetime.now())}" + ) + gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}{nl}") + + +def output_motion_mode(values: Values, gcode: Gcode) -> None: + """Verify if PREAMBLE or SAFETYBLOCK have changed MOTION_MODE.""" + nl: str = "\n" + if "G90" in values["PREAMBLE"] or "G90" in values["SAFETYBLOCK"]: values["MOTION_MODE"] = "G90" elif "G91" in values["PREAMBLE"] or "G91" in values["SAFETYBLOCK"]: values["MOTION_MODE"] = "G91" else: - gcode += f'{PostUtilsParse.linenumber(values)}{values["MOTION_MODE"]}{nl}' + gcode.append(f'{PostUtilsParse.linenumber(values)}{values["MOTION_MODE"]}{nl}') + + +def output_postamble_header(values: Values, gcode: Gcode) -> None: + """Output the postamble header.""" + comment: str = "" + nl: str = "\n" + + if values["OUTPUT_COMMENTS"]: + comment = PostUtilsParse.create_comment(values, "Begin postamble") + gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}{nl}") + + +def output_postamble(values: Values, gcode: Gcode) -> None: + """Output the postamble.""" + line: str + nl: str = "\n" + + for line in values["POSTAMBLE"].splitlines(False): + gcode.append(f"{PostUtilsParse.linenumber(values)}{line}{nl}") + + +def output_postop(values: Values, gcode: Gcode, obj) -> None: + """Output the post-operation information.""" + comment: str + line: str + nl: str = "\n" + + if values["OUTPUT_COMMENTS"]: + comment = PostUtilsParse.create_comment( + values, f'{values["FINISH_LABEL"]} operation: {obj.Label}' + ) + gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}{nl}") + for line in values["POST_OPERATION"].splitlines(False): + gcode.append(f"{PostUtilsParse.linenumber(values)}{line}{nl}") + + +def output_preamble(values: Values, gcode: Gcode) -> None: + """Output the preamble.""" + comment: str + line: str + nl: str = "\n" + + if values["OUTPUT_COMMENTS"]: + comment = PostUtilsParse.create_comment(values, "Begin preamble") + gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}{nl}") + for line in values["PREAMBLE"].splitlines(False): + gcode.append(f"{PostUtilsParse.linenumber(values)}{line}{nl}") + + +def output_preop(values: Values, gcode: Gcode, obj) -> None: + """Output the pre-operation information.""" + comment: str + line: str + nl: str = "\n" + + if values["OUTPUT_COMMENTS"]: + if values["SHOW_OPERATION_LABELS"]: + comment = PostUtilsParse.create_comment( + values, f"Begin operation: {obj.Label}" + ) + else: + comment = PostUtilsParse.create_comment(values, "Begin operation") + gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}{nl}") + if values["SHOW_MACHINE_UNITS"]: + comment = PostUtilsParse.create_comment( + values, f'Machine units: {values["UNIT_SPEED_FORMAT"]}' + ) + gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}{nl}") + if values["OUTPUT_MACHINE_NAME"]: + comment = PostUtilsParse.create_comment( + values, + f'Machine: {values["MACHINE_NAME"]}, {values["UNIT_SPEED_FORMAT"]}', + ) + gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}{nl}") + for line in values["PRE_OPERATION"].splitlines(False): + gcode.append(f"{PostUtilsParse.linenumber(values)}{line}{nl}") + + +def output_return_to(values: Values, gcode: Gcode) -> None: + """Output the RETURN_TO command.""" + cmd: str + nl: str = "\n" + num_x: str + num_y: str + num_z: str + + if values["RETURN_TO"]: + num_x = values["RETURN_TO"][0] + num_y = values["RETURN_TO"][1] + num_z = values["RETURN_TO"][2] + cmd = PostUtilsParse.format_command_line( + values, ["G0", f"X{num_x}", f"Y{num_y}", f"Z{num_z}"] + ) + gcode.append(f"{PostUtilsParse.linenumber(values)}{cmd}{nl}") + + +def output_safetyblock(values: Values, gcode: Gcode) -> None: + """Output the safety block.""" + line: str + nl: str = "\n" + + for line in values["SAFETYBLOCK"].splitlines(False): + gcode.append(f"{PostUtilsParse.linenumber(values)}{line}{nl}") + + +def output_start_bcnc(values: Values, gcode: Gcode, obj) -> None: + """Output the starting BCNC header.""" + comment: str + nl: str = "\n" + + if values["OUTPUT_BCNC"]: + comment = PostUtilsParse.create_comment(values, f"Block-name: {obj.Label}") + gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}{nl}") + comment = PostUtilsParse.create_comment(values, "Block-expand: 0") + gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}{nl}") + comment = PostUtilsParse.create_comment(values, "Block-enable: 1") + gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}{nl}") + + +def output_tool_list(values: Values, gcode: Gcode, objectslist) -> None: + """Output a list of the tools used in the objects.""" + comment: str + nl: str = "\n" + + if values["OUTPUT_COMMENTS"] and values["LIST_TOOLS_IN_PREAMBLE"]: + for item in objectslist: + if hasattr(item, "Proxy") and isinstance( + item.Proxy, PathToolController.ToolController + ): + comment = PostUtilsParse.create_comment( + values, f"T{item.ToolNumber}={item.Name}" + ) + gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}{nl}") + + +def output_tool_return(values: Values, gcode: Gcode) -> None: + """Output the tool return block.""" + line: str + nl: str = "\n" + + for line in values["TOOLRETURN"].splitlines(False): + gcode.append(f"{PostUtilsParse.linenumber(values)}{line}{nl}") + + +def output_units(values: Values, gcode: Gcode) -> None: + """Verify if PREAMBLE or SAFETYBLOCK have changed UNITS.""" + nl: str = "\n" + if "G21" in values["PREAMBLE"] or "G21" in values["SAFETYBLOCK"]: values["UNITS"] = "G21" values["UNIT_FORMAT"] = "mm" @@ -140,132 +301,79 @@ def export_common(values, objectslist, filename): values["UNIT_FORMAT"] = "in" values["UNIT_SPEED_FORMAT"] = "in/min" else: - gcode += f'{PostUtilsParse.linenumber(values)}{values["UNITS"]}{nl}' + gcode.append(f'{PostUtilsParse.linenumber(values)}{values["UNITS"]}{nl}') + + +def export_common(values: Values, objectslist, filename: str) -> str: + """Do the common parts of postprocessing the objects in objectslist to filename.""" + coolant_mode: str + dia: PostUtils.GCodeEditorDialog + final: str + gcode: Gcode = [] + result: bool for obj in objectslist: + if not hasattr(obj, "Path"): + print(f"The object {obj.Name} is not a path.") + print("Please select only path and Compounds.") + return "" + + print(f'PostProcessor: {values["POSTPROCESSOR_FILE_NAME"]} postprocessing...') + check_canned_cycles(values) + output_header(values, gcode) + output_safetyblock(values, gcode) + output_tool_list(values, gcode, objectslist) + output_preamble(values, gcode) + output_motion_mode(values, gcode) + output_units(values, gcode) + + for obj in objectslist: # Skip inactive operations if hasattr(obj, "Active") and not obj.Active: continue if hasattr(obj, "Base") and hasattr(obj.Base, "Active") and not obj.Base.Active: continue + coolant_mode = determine_coolant_mode(obj) + output_start_bcnc(values, gcode, obj) + output_preop(values, gcode, obj) + output_coolant_on(values, gcode, coolant_mode) + # output the G-code for the group (compound) or simple path + PostUtilsParse.parse_a_group(values, gcode, obj) + output_postop(values, gcode, obj) + output_coolant_off(values, gcode, coolant_mode) - # do the pre_op - if values["OUTPUT_BCNC"]: - comment = PostUtilsParse.create_comment(values, f"Block-name: {obj.Label}") - gcode += f"{PostUtilsParse.linenumber(values)}{comment}{nl}" - comment = PostUtilsParse.create_comment(values, "Block-expand: 0") - gcode += f"{PostUtilsParse.linenumber(values)}{comment}{nl}" - comment = PostUtilsParse.create_comment(values, "Block-enable: 1") - gcode += f"{PostUtilsParse.linenumber(values)}{comment}{nl}" - if values["OUTPUT_COMMENTS"]: - if values["SHOW_OPERATION_LABELS"]: - comment = PostUtilsParse.create_comment( - values, f"Begin operation: {obj.Label}" - ) - else: - comment = PostUtilsParse.create_comment(values, "Begin operation") - gcode += f"{PostUtilsParse.linenumber(values)}{comment}{nl}" - if values["SHOW_MACHINE_UNITS"]: - comment = PostUtilsParse.create_comment( - values, f'Machine units: {values["UNIT_SPEED_FORMAT"]}' - ) - gcode += f"{PostUtilsParse.linenumber(values)}{comment}{nl}" - if values["OUTPUT_MACHINE_NAME"]: - comment = PostUtilsParse.create_comment( - values, - f'Machine: {values["MACHINE_NAME"]}, {values["UNIT_SPEED_FORMAT"]}', - ) - gcode += f"{PostUtilsParse.linenumber(values)}{comment}{nl}" - for line in values["PRE_OPERATION"].splitlines(False): - gcode += f"{PostUtilsParse.linenumber(values)}{line}{nl}" - - # get coolant mode - coolantMode = "None" - if ( - hasattr(obj, "CoolantMode") - or hasattr(obj, "Base") - and hasattr(obj.Base, "CoolantMode") - ): - if hasattr(obj, "CoolantMode"): - coolantMode = obj.CoolantMode - else: - coolantMode = obj.Base.CoolantMode - - # turn coolant on if required - if values["ENABLE_COOLANT"]: - if values["OUTPUT_COMMENTS"] and coolantMode != "None": - comment = PostUtilsParse.create_comment( - values, f"Coolant On: {coolantMode}" - ) - gcode += f"{PostUtilsParse.linenumber(values)}{comment}{nl}" - if coolantMode == "Flood": - gcode += f"{PostUtilsParse.linenumber(values)}M8{nl}" - elif coolantMode == "Mist": - gcode += f"{PostUtilsParse.linenumber(values)}M7{nl}" - - # process the operation gcode - gcode += PostUtilsParse.parse_a_group(values, obj) - - # do the post_op - if values["OUTPUT_COMMENTS"]: - comment = PostUtilsParse.create_comment( - values, f'{values["FINISH_LABEL"]} operation: {obj.Label}' - ) - gcode += f"{PostUtilsParse.linenumber(values)}{comment}{nl}" - for line in values["POST_OPERATION"].splitlines(False): - gcode += f"{PostUtilsParse.linenumber(values)}{line}{nl}" - - # turn coolant off if required - if values["ENABLE_COOLANT"] and coolantMode != "None": - if values["OUTPUT_COMMENTS"]: - comment = PostUtilsParse.create_comment( - values, f"Coolant Off: {coolantMode}" - ) - gcode += f"{PostUtilsParse.linenumber(values)}{comment}{nl}" - gcode += f"{PostUtilsParse.linenumber(values)}M9{nl}" - - if values["RETURN_TO"]: - num_x = values["RETURN_TO"][0] - num_y = values["RETURN_TO"][1] - num_z = values["RETURN_TO"][2] - gcode += f"{PostUtilsParse.linenumber(values)}G0 X{num_x} Y{num_y} Z{num_z}{nl}" + output_return_to(values, gcode) + # + # This doesn't make sense to me. It seems that both output_start_bcnc and + # output_end_bcnc should be in the for loop or both should be out of the + # for loop. However, that is the way that grbl post code was written, so + # for now I will leave it that way until someone has time to figure it out. + # + output_end_bcnc(values, gcode) + output_postamble_header(values, gcode) + output_tool_return(values, gcode) + output_safetyblock(values, gcode) + output_postamble(values, gcode) - # do the post_amble - if values["OUTPUT_BCNC"]: - comment = PostUtilsParse.create_comment(values, "Block-name: post_amble") - gcode += f"{PostUtilsParse.linenumber(values)}{comment}{nl}" - comment = PostUtilsParse.create_comment(values, "Block-expand: 0") - gcode += f"{PostUtilsParse.linenumber(values)}{comment}{nl}" - comment = PostUtilsParse.create_comment(values, "Block-enable: 1") - gcode += f"{PostUtilsParse.linenumber(values)}{comment}{nl}" - if values["OUTPUT_COMMENTS"]: - comment = PostUtilsParse.create_comment(values, "Begin postamble") - gcode += f"{PostUtilsParse.linenumber(values)}{comment}{nl}" - for line in values["TOOLRETURN"].splitlines(False): - gcode += f"{PostUtilsParse.linenumber(values)}{line}{nl}" - for line in values["SAFETYBLOCK"].splitlines(False): - gcode += f"{PostUtilsParse.linenumber(values)}{line}{nl}" - for line in values["POSTAMBLE"].splitlines(False): - gcode += f"{PostUtilsParse.linenumber(values)}{line}{nl}" + final = "".join(gcode) if FreeCAD.GuiUp and values["SHOW_EDITOR"]: - final = gcode - if len(gcode) > 100000: + if len(final) > 100000: print("Skipping editor since output is greater than 100kb") else: dia = PostUtils.GCodeEditorDialog() - dia.editor.setText(gcode) + dia.editor.setText(final) result = dia.exec_() if result: final = dia.editor.toPlainText() - else: - final = gcode print("done postprocessing.") if not filename == "-": - with open(filename, "w", newline=values["END_OF_LINE_CHARACTERS"]) as gfile: + with open( + filename, "w", encoding="utf-8", newline=values["END_OF_LINE_CHARACTERS"] + ) as gfile: gfile.write(final) return final diff --git a/src/Mod/Path/Path/Post/UtilsParse.py b/src/Mod/Path/Path/Post/UtilsParse.py index d83dd7b0652d..b267874e75aa 100644 --- a/src/Mod/Path/Path/Post/UtilsParse.py +++ b/src/Mod/Path/Path/Post/UtilsParse.py @@ -28,34 +28,211 @@ # *************************************************************************** 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 -import PathScripts.PathUtils as PathUtils -def create_comment(values, comment_string): +# 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 + nl: str = "\n" + 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){nl}") + 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 + nl: str = "\n" + + 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, + values["COMMAND_SPACE"] + + format_command_line(values, command_line) + + values["COMMAND_SPACE"], + ) + gcode += f"{linenumber(values)}{comment}{nl}" + # 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{nl}") + 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 + nl: str = "\n" + 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 += f"{linenumber(values)}{raw_command}{nl}" + + +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 + nl: str = "\n" + + if values["SPINDLE_WAIT"] > 0 and command in ("M3", "M03", "M4", "M04"): + gcode += f"{linenumber(values)}{format_command_line(values, command_line)}{nl}" + cmd = format_command_line(values, ["G4", f'P{values["SPINDLE_WAIT"]}']) + gcode += f"{linenumber(values)}{cmd}{nl}" + + +def check_for_suppressed_commands( + values: Values, gcode: Gcode, command: str, command_line: CommandLine +) -> bool: + """Check for commands that will be suppressed.""" + comment: str + nl: str = "\n" + + if command in values["SUPPRESS_COMMANDS"]: + if values["OUTPUT_COMMENTS"]: + # convert the command to a comment + comment = create_comment( + values, + values["COMMAND_SPACE"] + + format_command_line(values, command_line) + + values["COMMAND_SPACE"], + ) + gcode += f"{linenumber(values)}{comment}{nl}" + # 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.""" + nl: str = "\n" + + if command in ("M6", "M06") and values["USE_TLO"]: + cmd = format_command_line(values, ["G43", f'H{str(int(params["T"]))}']) + gcode += f"{linenumber(values)}{cmd}{nl}" + + +def check_for_tool_change( + values: Values, gcode: Gcode, command: str, command_line: CommandLine +) -> bool: + """Check for a tool change.""" + nl: str = "\n" + + if command in ("M6", "M06"): + if values["OUTPUT_COMMENTS"]: + comment = create_comment(values, "Begin toolchange") + gcode += f"{linenumber(values)}{comment}{nl}" + if values["OUTPUT_TOOL_CHANGE"]: + if values["STOP_SPINDLE_FOR_TOOL_CHANGE"]: + # stop the spindle + gcode += f"{linenumber(values)}M5{nl}" + for line in values["TOOL_CHANGE"].splitlines(False): + gcode += f"{linenumber(values)}{line}{nl}" + elif values["OUTPUT_COMMENTS"]: + # convert the tool change to a comment + comment = create_comment( + values, + values["COMMAND_SPACE"] + + format_command_line(values, command_line) + + values["COMMAND_SPACE"], + ) + gcode += f"{linenumber(values)}{comment}{nl}" + 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})" - else: - return values["COMMENT_SYMBOL"] + comment_string + return values["COMMENT_SYMBOL"] + comment_string -def default_axis_parameter(values, command, param, param_value, currLocation): +def default_axis_parameter( + values: Values, + command: str, # pylint: disable=unused-argument + param: str, + param_value: PathParameter, + current_location: PathParameters, +) -> str: """Process an axis parameter.""" if ( not values["OUTPUT_DOUBLES"] - and param in currLocation - and currLocation[param] == param_value + and param in current_location + and current_location[param] == param_value ): - return None + return "" return format_for_axis(values, Units.Quantity(param_value, Units.Length)) -def default_D_parameter(values, command, param, param_value, currLocation): +def default_D_parameter( + values: Values, + command: str, + param: str, # pylint: disable=unused-argument + param_value: PathParameter, + current_location: PathParameters, # pylint: disable=unused-argument +) -> str: """Process the D parameter.""" if command in ("G41", "G42"): return str(int(param_value)) @@ -67,37 +244,61 @@ def default_D_parameter(values, command, param, param_value, currLocation): return str(float(param_value)) -def default_F_parameter(values, command, param, param_value, currLocation): +def default_F_parameter( + values: Values, + command: str, + param: str, + param_value: PathParameter, + current_location: PathParameters, +) -> str: """Process the F parameter.""" if ( not values["OUTPUT_DOUBLES"] - and param in currLocation - and currLocation[param] == param_value + and param in current_location + and current_location[param] == param_value ): - return None + 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 None + return "" feed = Units.Quantity(param_value, Units.Velocity) if feed.getValueAs(values["UNIT_SPEED_FORMAT"]) <= 0.0: - return None + return "" return format_for_feed(values, feed) -def default_int_parameter(values, command, param, param_value, currLocation): +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, + 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, command, param, param_value, currLocation): +def default_length_parameter( + values: Values, + command: str, # pylint: disable=unused-argument + param: str, # pylint: disable=unused-argument + param_value: PathParameter, + 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, command, param, param_value, currLocation): +def default_P_parameter( + values: Values, + command: str, + param: str, # pylint: disable=unused-argument + param_value: PathParameter, + 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)) @@ -109,159 +310,179 @@ def default_P_parameter(values, command, param, param_value, currLocation): return str(param_value) -def default_Q_parameter(values, command, param, param_value, currLocation): +def default_Q_parameter( + values: Values, + command: str, + param: str, # pylint: disable=unused-argument + param_value: PathParameter, + 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_S_parameter(values, command, param, param_value, currLocation): +def default_S_parameter( + values: Values, + command: str, # pylint: disable=unused-argument + param: str, # pylint: disable=unused-argument + param_value: PathParameter, + current_location: PathParameters, # pylint: disable=unused-argument +) -> str: """Process the S parameter.""" return format_for_spindle(values, param_value) -def drill_translate(values, cmd, params): - """Translate drill cycles.""" - trBuff = "" +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 - # 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"], 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) - # R less than Z is error - if RETRACT_Z < drill_Z: + 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 + nl: str = "\n" + 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{nl}") + + 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") - trBuff += f"{linenumber(values)}{comment}{nl}" - 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"], Units.Velocity) - if cmd in ("G73", "G83"): - 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 - 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": - # force absolute coordinates during cycles - trBuff += f"{linenumber(values)}G90{nl}" - strG0_RETRACT_Z = f"G0 Z{format_for_axis(values, RETRACT_Z)}{nl}" - strF_Feedrate = f" F{format_for_feed(values, drill_feedrate)}{nl}" - # print(strF_Feedrate) - - # preliminary movement(s) - if values["CURRENT_Z"] < RETRACT_Z: - trBuff += f"{linenumber(values)}{strG0_RETRACT_Z}" - num_x = format_for_axis(values, drill_X) - num_y = format_for_axis(values, drill_Y) - trBuff += f"{linenumber(values)}G0 X{num_x} Y{num_y}{nl}" - 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 ! - num_z = format_for_axis(values, RETRACT_Z) - trBuff += f"{linenumber(values)}G1 Z{num_z}{strF_Feedrate}" - last_Stop_Z = RETRACT_Z + gcode.append(f"{linenumber(values)}{comment}{nl}") + 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}{nl}" + cmd = format_for_feed(values, Units.Quantity(params["F"], Units.Velocity)) + F_feedrate = f'{values["COMMAND_SPACE"]}F{cmd}{nl}' + + # 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}{nl}") + 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 cmd in ("G81", "G82"): - num_z = format_for_axis(values, drill_Z) - trBuff += f"{linenumber(values)}G1 Z{num_z}{strF_Feedrate}" - # pause where applicable - if cmd == "G82": - trBuff += f"{linenumber(values)}G4 P{str(drill_DwellTime)}{nl}" - trBuff += f"{linenumber(values)}{strG0_RETRACT_Z}" - elif cmd in ("G73", "G83"): - if params["Q"] != 0: - while 1: - if last_Stop_Z != RETRACT_Z: - # rapid move to just short of last drilling depth - clearance_depth = last_Stop_Z + a_bit - num_z = format_for_axis(values, clearance_depth) - trBuff += f"{linenumber(values)}G0 Z{num_z}{nl}" - next_Stop_Z = last_Stop_Z - drill_Step - if next_Stop_Z > drill_Z: - num_z = format_for_axis(values, next_Stop_Z) - trBuff += f"{linenumber(values)}G1 Z{num_z}{strF_Feedrate}" - if cmd == "G73": - # Rapid up "a small amount". - chip_breaker_height = ( - next_Stop_Z + values["CHIPBREAKING_AMOUNT"] - ) - num_z = format_for_axis(values, chip_breaker_height) - trBuff += f"{linenumber(values)}G0 Z{num_z}{nl}" - elif cmd == "G83": - # Rapid up to the retract height - trBuff += f"{linenumber(values)}{strG0_RETRACT_Z}" - last_Stop_Z = next_Stop_Z - else: - num_z = format_for_axis(values, drill_Z) - trBuff += f"{linenumber(values)}G1 Z{num_z}{strF_Feedrate}" - trBuff += f"{linenumber(values)}{strG0_RETRACT_Z}" - break - except Exception as err: - print("exception occurred", err) + 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 + ) - if values["MOTION_MODE"] == "G91": - trBuff += f"{linenumber(values)}G91{nl}" # Restore if changed - return trBuff +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, number): +def format_for_axis(values: Values, number) -> str: """Format a number using the precision for an axis value.""" - return format( - float(number.getValueAs(values["UNIT_FORMAT"])), - f'.{str(values["AXIS_PRECISION"])}f', + return str( + format( + float(number.getValueAs(values["UNIT_FORMAT"])), + f'.{str(values["AXIS_PRECISION"])}f', + ) ) -def format_for_feed(values, number): +def format_for_feed(values: Values, number) -> str: """Format a number using the precision for a feed rate.""" - return format( - float(number.getValueAs(values["UNIT_SPEED_FORMAT"])), - f'.{str(values["FEED_PRECISION"])}f', + return str( + format( + float(number.getValueAs(values["UNIT_SPEED_FORMAT"])), + f'.{str(values["FEED_PRECISION"])}f', + ) ) -def format_for_spindle(values, number): +def format_for_spindle(values: Values, number) -> str: """Format a number using the precision for a spindle speed.""" - return format(float(number), f'.{str(values["SPINDLE_DECIMALS"])}f') - - -def format_outstring(values, strTable): - """Construct the line for the final output.""" - s = "" - for w in strTable: - s += f'{w}{values["COMMAND_SPACE"]}' - s = s.strip() - return s + return str(format(float(number), f'.{str(values["SPINDLE_DECIMALS"])}f')) -def init_parameter_functions(parameter_functions): +def init_parameter_functions(parameter_functions: Dict[str, ParameterFunction]) -> None: """Initialize a list of parameter functions. - These functions are called in the PostUtilsParse.parse_a_path + 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_axis_parameter, "B": default_axis_parameter, @@ -291,226 +512,248 @@ def init_parameter_functions(parameter_functions): "Z": default_axis_parameter, # "$" is used by LinuxCNC (and others?) to designate which spindle } - parameter_functions.update(default_parameter_functions) + for ( + parameter + ) in default_parameter_functions: # pylint: disable=consider-using-dict-items + parameter_functions[parameter] = default_parameter_functions[parameter] -def linenumber(values, space=None): +def linenumber(values: Values, space: Union[str, None] = None) -> str: """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 f"N{line_num}{space}" - return "" - - -def parse_a_group(values, pathobj): + 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 + nl: str = "\n" + + 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}{nl}") + 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}{nl}") + 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 + nl: str = "\n" + + 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}{nl}") + 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).""" - nl = "\n" - out = "" + comment: str + nl: str = "\n" if hasattr(pathobj, "Group"): # We have a compound or project. if values["OUTPUT_COMMENTS"]: comment = create_comment(values, f"Compound: {pathobj.Label}") - out += f"{linenumber(values)}{comment}{nl}" + gcode += f"{linenumber(values)}{comment}{nl}" for p in pathobj.Group: - out += parse_a_group(values, p) + parse_a_group(values, gcode, p) else: # parsing simple path # groups might contain non-path things like stock. if not hasattr(pathobj, "Path"): - return out + return if values["OUTPUT_PATH_LABELS"] and values["OUTPUT_COMMENTS"]: comment = create_comment(values, f"Path: {pathobj.Label}") - out += f"{linenumber(values)}{comment}{nl}" - out += parse_a_path(values, pathobj) - return out + gcode += f"{linenumber(values)}{comment}{nl}" + parse_a_path(values, gcode, pathobj) -def parse_a_path(values, pathobj): +def parse_a_path(values: Values, gcode: Gcode, pathobj) -> None: """Parse a simple Path.""" - nl = "\n" - out = "" - - adaptiveOp = False - opHorizRapid = 0 - opVertRapid = 0 - - lastcommand = None - firstmove = Path.Command("G0", {"X": -1, "Y": -1, "Z": -1, "F": 0.0}) - currLocation = {} # keep track for no doubles - currLocation.update(firstmove.Parameters) # set First location Parameters - - 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}" - ) - - for c in PathUtils.getPathWithPlacement(pathobj).Commands: + 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 + nl: str = "\n" + parameter: str + parameter_value: str + + current_location.update( + Path.Command("G0", {"X": -1, "Y": -1, "Z": -1, "F": 0.0}).Parameters + ) + adaptive_op_variables = determine_adaptive_op(values, pathobj) - # List of elements in the command, code, and params. - outstring = [] - # command may contain M code, G code, comment string, etc. + for c in pathobj.Path.Commands: command = c.Name + command_line = [] + + # Modify the command name if necessary if command[0] == "(": if not values["OUTPUT_COMMENTS"]: continue if values["COMMENT_SYMBOL"] != "(" and len(command) > 2: command = create_comment(values, command[1:-1]) - - if ( - values["OUTPUT_ADAPTIVE"] - and adaptiveOp - and command in values["RAPID_MOVES"] - ): - if opHorizRapid and opVertRapid: - command = "G1" - else: - outstring.append(f"(Tool Controller Rapid Values are unset){nl}") - - outstring.append(command) - + 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: - outstring.pop(0) + 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], currLocation + values, + command, + parameter, + c.Parameters[parameter], + current_location, ) if parameter_value: - outstring.append(f"{parameter}{parameter_value}") - - if ( - values["OUTPUT_ADAPTIVE"] - and adaptiveOp - and command in values["RAPID_MOVES"] - and opHorizRapid - and opVertRapid - ): - if "Z" not in c.Parameters: - param_num = format_for_feed(values, opHorizRapid) - else: - param_num = format_for_feed(values, opVertRapid) - outstring.append(f"F{param_num}") + command_line.append(f"{parameter}{parameter_value}") - # store the latest command + set_adaptive_op_speed( + values, command, command_line, c.Parameters, adaptive_op_variables + ) + # Remember the current 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"], Units.Length) - if "Y" in c.Parameters: - values["CURRENT_Y"] = Units.Quantity(c.Parameters["Y"], Units.Length) - if "Z" in c.Parameters: - values["CURRENT_Z"] = Units.Quantity(c.Parameters["Z"], Units.Length) - - if command in ("G98", "G99"): - values["DRILL_RETRACT_MODE"] = command - elif command in ("G90", "G91"): + # Remember the current location + current_location.update(c.Parameters) + if command in ("G90", "G91"): + # Remember the motion mode values["MOTION_MODE"] = command - - if ( - values["TRANSLATE_DRILL_CYCLES"] - and command in values["DRILL_CYCLES_TO_TRANSLATE"] + 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, ): - if values["OUTPUT_COMMENTS"]: # Comment the original command - comment = create_comment( - values, - values["COMMAND_SPACE"] - + format_outstring(values, outstring) - + values["COMMAND_SPACE"], - ) - out += f"{linenumber(values)}{comment}{nl}" - out += drill_translate(values, command, c.Parameters) - # Erase the line we just translated - outstring = [] - - if values["SPINDLE_WAIT"] > 0 and command in ("M3", "M03", "M4", "M04"): - out += f"{linenumber(values)}{format_outstring(values, outstring)}{nl}" - num = format_outstring(values, ["G4", f'P{values["SPINDLE_WAIT"]}']) - out += f"{linenumber(values)}{num}{nl}" - outstring = [] - - # Check for Tool Change: - if command in ("M6", "M06"): - if values["OUTPUT_COMMENTS"]: - comment = create_comment(values, "Begin toolchange") - out += f"{linenumber(values)}{comment}{nl}" - if values["OUTPUT_TOOL_CHANGE"]: - if values["STOP_SPINDLE_FOR_TOOL_CHANGE"]: - # stop the spindle - out += f"{linenumber(values)}M5{nl}" - for line in values["TOOL_CHANGE"].splitlines(False): - out += f"{linenumber(values)}{line}{nl}" - elif values["OUTPUT_COMMENTS"]: - # convert the tool change to a comment - comment = create_comment( - values, - values["COMMAND_SPACE"] - + format_outstring(values, outstring) - + values["COMMAND_SPACE"], - ) - out += f"{linenumber(values)}{comment}{nl}" - 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, - values["COMMAND_SPACE"] - + format_outstring(values, outstring) - + values["COMMAND_SPACE"], - ) - out += f"{linenumber(values)}{comment}{nl}" - # 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 += f"{nl}" - - # add height offset - if command in ("M6", "M06") and values["USE_TLO"]: - out += f'{linenumber(values)}G43 H{str(int(c.Parameters["T"]))}{nl}' - - # 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 += f"{linenumber(values)}{raw_command}{nl}" - return out + 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 = [] + # Add a line number to the front and a newline to the end of the command line + if command_line: + gcode += ( + f"{linenumber(values)}{format_command_line(values, command_line)}{nl}" + ) + 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}") diff --git a/src/Mod/Path/Path/Post/scripts/refactored_centroid_post.py b/src/Mod/Path/Path/Post/scripts/refactored_centroid_post.py index a517b30e5390..fd076f7f5002 100644 --- a/src/Mod/Path/Path/Post/scripts/refactored_centroid_post.py +++ b/src/Mod/Path/Path/Post/scripts/refactored_centroid_post.py @@ -131,10 +131,6 @@ def init_values(values): # values["PREAMBLE"] = """G53 G00 G17""" # - # Output any messages. - # - values["REMOVE_MESSAGES"] = False - # # Any commands in this value are output after the header but before the preamble, # then again after the TOOLRETURN but before the POSTAMBLE. #