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

364 lines
14 KiB
Python

# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> *
# * Copyright (c) 2014 sliptonic <shopinthewoods@gmail.com> *
# * Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
# * Copyright (c) 2018, 2019 Gauthier Briere *
# * Copyright (c) 2019, 2020 Schildkroet *
# * Copyright (c) 2022 Larry Woestman <LarryWoestman2@gmail.com> *
# * *
# * This file is part of the FreeCAD CAx development system. *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * FreeCAD is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with FreeCAD; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
import 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 = "<None>"
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