# -*- coding: utf-8 -*- # *************************************************************************** # * Copyright (c) 2014 Yorik van Havre * # * Copyright (c) 2014 sliptonic * # * Copyright (c) 2015 Dan Falck * # * Copyright (c) 2018, 2019 Gauthier Briere * # * Copyright (c) 2019, 2020 Schildkroet * # * Copyright (c) 2022 Larry Woestman * # * * # * 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 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 # Define some types that are used throughout this file Gcode = List[str] Values = Dict[str, Any] 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"] 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.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" values["UNIT_SPEED_FORMAT"] = "mm/min" elif "G20" in values["PREAMBLE"] or "G20" in values["SAFETYBLOCK"]: values["UNITS"] = "G20" values["UNIT_FORMAT"] = "in" values["UNIT_SPEED_FORMAT"] = "in/min" else: 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) 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) final = "".join(gcode) if FreeCAD.GuiUp and values["SHOW_EDITOR"]: if len(final) > 100000: print("Skipping editor since output is greater than 100kb") else: dia = PostUtils.GCodeEditorDialog() dia.editor.setText(final) result = dia.exec_() if result: final = dia.editor.toPlainText() print("done postprocessing.") if not filename == "-": with open( filename, "w", encoding="utf-8", newline=values["END_OF_LINE_CHARACTERS"] ) as gfile: gfile.write(final) return final