Merge pull request #20833 from LarryWoestman/tests

CAM:  added three command line arguments, with tests
This commit is contained in:
sliptonic
2025-05-20 08:35:30 -05:00
committed by GitHub
6 changed files with 669 additions and 122 deletions

View File

@@ -20,13 +20,19 @@
# * USA *
# * *
# ***************************************************************************
from os import linesep, path, remove
import tempfile
from unittest.mock import mock_open, patch
import FreeCAD
import Path
import CAMTests.PathTestUtils as PathTestUtils
from Path.Post.Command import CommandPathPost
from Path.Post.Processor import PostProcessorFactory
from Path.Post.Utils import FilenameGenerator
from PySide.QtCore import QT_TRANSLATE_NOOP # type: ignore
Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule())
Path.Log.trackModule(Path.Log.thisModule())
@@ -109,6 +115,7 @@ class TestRefactoredTestPost(PathTestUtils.PathTestBase):
def multi_compare(self, path, expected, args, debug=False):
"""Perform a test with multiple lines of gcode comparison."""
nl = "\n"
self.job.PostProcessorArgs = args
# replace the original path (that came with the job and operation) with our path
self.profile_op.Path = Path.Path(path)
@@ -422,6 +429,436 @@ G54
#############################################################################
def test00135(self) -> None:
"""Test enabling and disabling coolant."""
args: str
expected: str
gcode: str
nl = "\n"
save_CoolantMode = self.profile_op.CoolantMode
c = Path.Command("G0 X10 Y20 Z30")
self.profile_op.Path = Path.Path([c])
# Test Flood coolant enabled
self.profile_op.CoolantMode = "Flood"
expected = """(Begin preamble)
G90
G21
(Begin operation)
G54
(Finish operation)
(Begin operation)
(TC: Default Tool)
(Begin toolchange)
(M6 T1)
(Finish operation)
(Begin operation)
(Coolant On: Flood)
M8
G0 X10.000 Y20.000 Z30.000
(Finish operation)
(Coolant Off: Flood)
M9
(Begin postamble)
"""
self.job.PostProcessorArgs = "--enable_coolant --comments"
gcode = self.post.export()[0][1]
# print("--------\n" + gcode + "--------\n")
self.assertEqual(gcode, expected)
# Test Mist coolant enabled
self.profile_op.CoolantMode = "Mist"
expected = """(Begin preamble)
G90
G21
(Begin operation)
G54
(Finish operation)
(Begin operation)
(TC: Default Tool)
(Begin toolchange)
(M6 T1)
(Finish operation)
(Begin operation)
(Coolant On: Mist)
M7
G0 X10.000 Y20.000 Z30.000
(Finish operation)
(Coolant Off: Mist)
M9
(Begin postamble)
"""
self.job.PostProcessorArgs = "--enable_coolant --comments"
gcode = self.post.export()[0][1]
# print("--------\n" + gcode + "--------\n")
self.assertEqual(gcode, expected)
# Test None coolant enabled with CoolantMode property
self.profile_op.CoolantMode = "None"
expected = """(Begin preamble)
G90
G21
(Begin operation)
G54
(Finish operation)
(Begin operation)
(TC: Default Tool)
(Begin toolchange)
(M6 T1)
(Finish operation)
(Begin operation)
G0 X10.000 Y20.000 Z30.000
(Finish operation)
(Begin postamble)
"""
self.job.PostProcessorArgs = "--enable_coolant --comments"
gcode = self.post.export()[0][1]
# print("--------\n" + gcode + "--------\n")
self.assertEqual(gcode, expected)
# Test Flood coolant disabled
self.profile_op.CoolantMode = "Flood"
expected = """(Begin preamble)
G90
G21
(Begin operation)
G54
(Finish operation)
(Begin operation)
(TC: Default Tool)
(Begin toolchange)
(M6 T1)
(Finish operation)
(Begin operation)
G0 X10.000 Y20.000 Z30.000
(Finish operation)
(Begin postamble)
"""
self.job.PostProcessorArgs = "--disable_coolant --comments"
gcode = self.post.export()[0][1]
# print("--------\n" + gcode + "--------\n")
self.assertEqual(gcode, expected)
# Test Mist coolant disabled
self.profile_op.CoolantMode = "Mist"
expected = """(Begin preamble)
G90
G21
(Begin operation)
G54
(Finish operation)
(Begin operation)
(TC: Default Tool)
(Begin toolchange)
(M6 T1)
(Finish operation)
(Begin operation)
G0 X10.000 Y20.000 Z30.000
(Finish operation)
(Begin postamble)
"""
self.job.PostProcessorArgs = "--disable_coolant --comments"
gcode = self.post.export()[0][1]
# print("--------\n" + gcode + "--------\n")
self.assertEqual(gcode, expected)
# Test None coolant disabled with CoolantMode property
self.profile_op.CoolantMode = "None"
expected = """(Begin preamble)
G90
G21
(Begin operation)
G54
(Finish operation)
(Begin operation)
(TC: Default Tool)
(Begin toolchange)
(M6 T1)
(Finish operation)
(Begin operation)
G0 X10.000 Y20.000 Z30.000
(Finish operation)
(Begin postamble)
"""
self.job.PostProcessorArgs = "--disable_coolant --comments"
gcode = self.post.export()[0][1]
# print("--------\n" + gcode + "--------\n")
self.assertEqual(gcode, expected)
# Test Flood coolant configured but no coolant argument (default)
self.profile_op.CoolantMode = "Flood"
expected = """(Begin preamble)
G90
G21
(Begin operation)
G54
(Finish operation)
(Begin operation)
(TC: Default Tool)
(Begin toolchange)
(M6 T1)
(Finish operation)
(Begin operation)
G0 X10.000 Y20.000 Z30.000
(Finish operation)
(Begin postamble)
"""
self.job.PostProcessorArgs = "--comments"
gcode = self.post.export()[0][1]
# print("--------\n" + gcode + "--------\n")
self.assertEqual(gcode, expected)
# Test coolant enabled without a CoolantMode property
self.profile_op.removeProperty("CoolantMode")
expected = """(Begin preamble)
G90
G21
(Begin operation)
G54
(Finish operation)
(Begin operation)
(TC: Default Tool)
(Begin toolchange)
(M6 T1)
(Finish operation)
(Begin operation)
G0 X10.000 Y20.000 Z30.000
(Finish operation)
(Begin postamble)
"""
self.job.PostProcessorArgs = "--enable_coolant --comments"
gcode = self.post.export()[0][1]
# print("--------\n" + gcode + "--------\n")
self.assertEqual(gcode, expected)
# Test coolant disabled without a CoolantMode property
expected = """(Begin preamble)
G90
G21
(Begin operation)
G54
(Finish operation)
(Begin operation)
(TC: Default Tool)
(Begin toolchange)
(M6 T1)
(Finish operation)
(Begin operation)
G0 X10.000 Y20.000 Z30.000
(Finish operation)
(Begin postamble)
"""
self.job.PostProcessorArgs = "--disable_coolant --comments"
gcode = self.post.export()[0][1]
# print("--------\n" + gcode + "--------\n")
self.assertEqual(gcode, expected)
# re-create the original CoolantMode property
self.profile_op.addProperty(
"App::PropertyEnumeration",
"CoolantMode",
"Path",
QT_TRANSLATE_NOOP("App::Property", "Coolant option for this operation"),
)
self.profile_op.CoolantMode = ["None", "Flood", "Mist"]
self.profile_op.CoolantMode = save_CoolantMode
#############################################################################
def test00137(self) -> None:
"""Test enabling/disabling machine specific commands."""
# test with machine specific commands enabled
self.multi_compare(
"(MC_RUN_COMMAND: blah)",
"""(Begin preamble)
G90
G21
(Begin operation)
G54
(Finish operation)
(Begin operation)
(TC: Default Tool)
(Begin toolchange)
(M6 T1)
(Finish operation)
(Begin operation)
(MC_RUN_COMMAND: blah)
blah
(Finish operation)
(Begin postamble)
""",
"--enable_machine_specific_commands --comments",
)
# test with machine specific commands disabled
self.multi_compare(
"(MC_RUN_COMMAND: blah)",
"""(Begin preamble)
G90
G21
(Begin operation)
G54
(Finish operation)
(Begin operation)
(TC: Default Tool)
(Begin toolchange)
(M6 T1)
(Finish operation)
(Begin operation)
(MC_RUN_COMMAND: blah)
(Finish operation)
(Begin postamble)
""",
"--disable_machine_specific_commands --comments",
)
# test with machine specific commands default
self.multi_compare(
"(MC_RUN_COMMAND: blah)",
"""(Begin preamble)
G90
G21
(Begin operation)
G54
(Finish operation)
(Begin operation)
(TC: Default Tool)
(Begin toolchange)
(M6 T1)
(Finish operation)
(Begin operation)
(MC_RUN_COMMAND: blah)
(Finish operation)
(Begin postamble)
""",
"--comments",
)
# test with odd characters and spaces in the machine specific command
self.multi_compare(
"(MC_RUN_COMMAND: These are odd characters:!@#$%^&*?/)",
"""(Begin preamble)
G90
G21
(Begin operation)
G54
(Finish operation)
(Begin operation)
(TC: Default Tool)
(Begin toolchange)
(M6 T1)
(Finish operation)
(Begin operation)
(MC_RUN_COMMAND: These are odd characters:!@#$%^&*?/)
These are odd characters:!@#$%^&*?/
(Finish operation)
(Begin postamble)
""",
"--enable_machine_specific_commands --comments",
)
#############################################################################
def test00138(self) -> None:
"""Test end of line characters."""
class MockWriter:
def __init__(self):
self.contents = ""
def write(self, data):
self.contents = data
writer = MockWriter()
opener = mock_open()
opener.return_value.write = writer.write
output_file_pattern = path.join(tempfile.gettempdir(), "test_postprocessor_write.nc")
self.job.PostProcessorOutputFile = output_file_pattern
Path.Preferences.setOutputFileDefaults(output_file_pattern, "Append Unique ID on conflict")
policy = Path.Preferences.defaultOutputPolicy()
generator = FilenameGenerator(job=self.job)
generated_filename = generator.generate_filenames()
generator.set_subpartname("")
fname = next(generated_filename)
self.profile_op.Path = Path.Path([])
# Test with whatever end-of-line characters the system running the test happens to use
expected = """G90
G21
G54
"""
self.job.PostProcessorArgs = ""
gcode = self.post.export()[0][1]
self.assertEqual(gcode, expected)
# also test what is written to a mock file
with patch("builtins.open", opener) as m:
CommandPathPost._write_file(self, fname, gcode, policy)
if m.call_args.kwargs["newline"] is None:
mocked_output = writer.contents.replace("\n", linesep)
else:
mocked_output = writer.contents
expected = expected.replace("\n", linesep)
self.assertEqual(expected, mocked_output)
# Test with a new line
expected = "\n\nG90\nG21\nG54\n"
self.job.PostProcessorArgs = "--end_of_line_characters='\n'"
gcode = self.post.export()[0][1]
self.assertEqual(gcode, expected)
# also test what is written to a mock file
with patch("builtins.open", opener) as m:
CommandPathPost._write_file(self, fname, gcode, policy)
if m.call_args.kwargs["newline"] is None:
mocked_output = writer.contents.replace("\n", linesep)
else:
mocked_output = writer.contents
expected = expected[2:]
self.assertEqual(expected, mocked_output)
# Test with a carriage return followed by a new line
expected = "G90\r\nG21\r\nG54\r\n"
self.job.PostProcessorArgs = "--end_of_line_characters='\r\n'"
gcode = self.post.export()[0][1]
self.assertEqual(gcode, expected)
# also test what is written to a mock file
with patch("builtins.open", opener) as m:
CommandPathPost._write_file(self, fname, gcode, policy)
if m.call_args.kwargs["newline"] is None:
mocked_output = writer.contents.replace("\n", linesep)
else:
mocked_output = writer.contents
self.assertEqual(expected, mocked_output)
# Test with a carriage return
expected = "G90\rG21\rG54\r"
self.job.PostProcessorArgs = "--end_of_line_characters='\r'"
gcode = self.post.export()[0][1]
self.assertEqual(gcode, expected)
# also test what is written to a mock file
with patch("builtins.open", opener) as m:
CommandPathPost._write_file(self, fname, gcode, policy)
if m.call_args.kwargs["newline"] is None:
mocked_output = writer.contents.replace("\n", linesep)
else:
mocked_output = writer.contents
self.assertEqual(expected, mocked_output)
# Test writing a mock file with a zero-length string for gcode
expected = ""
gcode = ""
with patch("builtins.open", opener) as m:
CommandPathPost._write_file(self, fname, gcode, policy)
if m.call_args.kwargs["newline"] is None:
mocked_output = writer.contents.replace("\n", linesep)
else:
mocked_output = writer.contents
self.assertEqual(expected, mocked_output)
#############################################################################
def test00140(self):
"""Test feed-precision."""
nl = "\n"
@@ -606,6 +1043,17 @@ G54
--no-comments Suppress comment output
--comment_symbol COMMENT_SYMBOL
The character used to start a comment, default is "("
--enable_coolant Enable coolant
--disable_coolant Disable coolant (default)
--enable_machine_specific_commands
Enable machine specific commands of the form
(MC_RUN_COMMAND: blah)
--disable_machine_specific_commands
Disable machine specific commands (default)
--end_of_line_characters END_OF_LINE_CHARACTERS
The character(s) to use at the end of each line in the
output file, default is whatever the system uses, may
also use '\\n', '\\r', or '\\r\\n'
--feed-precision FEED_PRECISION
Number of digits of precision for feed rate, default
is 3

View File

@@ -122,6 +122,34 @@ class CommandPathPost:
return self.candidate is not None
def _write_file(self, filename, gcode, policy):
#
# Up to this point the postprocessors have been using "\n" as the end-of-line
# characters in the gcode and using the process of writing out the file as a way
# to convert the "\n" into whatever end-of-line characters match the system
# running the postprocessor. This can be a problem if the controller which will
# run the gcode doesn't like the same end-of-line characters as the system that
# ran the postprocessor to generate the gcode.
# The refactored code base now allows for four possible types of end-of-line
# characters in the gcode.
#
if len(gcode) > 1 and gcode[0:2] == "\n\n":
# The gcode shouldn't normally start with "\n\n".
# This means that the gcode contains "\n" as the end-of-line characters and
# that the gcode should be written out exactly that way.
newline_handling = ""
gcode = gcode[2:]
elif "\r" in gcode:
# Write out the gcode with whatever end-of-line characters it already has,
# presumably either "\r" or "\r\n".
newline_handling = ""
else:
# The gcode is assumed to contain "\n" as the end-of-line characters (if
# there are any end-of-line characters in the gcode). This case also
# handles a zero-length gcode string.
# Write out the gcode but convert "\n" to whatever the system uses.
# This is also backwards compatible with the "previous" way of doing things.
newline_handling = None
if policy == "Open File Dialog":
dlg = QtGui.QFileDialog()
dlg.setFileMode(QtGui.QFileDialog.FileMode.AnyFile)
@@ -131,7 +159,7 @@ class CommandPathPost:
if dlg.exec_():
filename = dlg.selectedFiles()[0]
Path.Log.debug(filename)
with open(filename, "w") as f:
with open(filename, "w", encoding="utf-8", newline=newline_handling) as f:
f.write(gcode)
else:
return
@@ -140,7 +168,7 @@ class CommandPathPost:
while os.path.isfile(filename):
base, ext = os.path.splitext(filename)
filename = f"{base}-1{ext}"
with open(filename, "w") as f:
with open(filename, "w", encoding="utf-8", newline=newline_handling) as f:
f.write(gcode)
elif policy == "Open File Dialog on conflict":
@@ -153,16 +181,16 @@ class CommandPathPost:
if dlg.exec_():
filename = dlg.selectedFiles()[0]
Path.Log.debug(filename)
with open(filename, "w") as f:
with open(filename, "w", encoding="utf-8", newline=newline_handling) as f:
f.write(gcode)
else:
return
else:
with open(filename, "w") as f:
with open(filename, "w", encoding="utf-8", newline=newline_handling) as f:
f.write(gcode)
else: # Overwrite
with open(filename, "w") as f:
with open(filename, "w", encoding="utf-8", newline=newline_handling) as f:
f.write(gcode)
FreeCAD.Console.PrintMessage(f"File written to {filename}\n")

View File

@@ -5,7 +5,7 @@
# * 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) 2022-2025 Larry Woestman <LarryWoestman2@gmail.com> *
# * *
# * This file is part of the FreeCAD CAx development system. *
# * *
@@ -74,6 +74,8 @@ def init_argument_defaults(argument_defaults: Dict[str, bool]) -> None:
argument_defaults["axis-modal"] = False
argument_defaults["bcnc"] = False
argument_defaults["comments"] = True
argument_defaults["enable_coolant"] = False
argument_defaults["enable_machine_specific_commands"] = False
argument_defaults["header"] = True
argument_defaults["line-numbers"] = False
argument_defaults["metric_inches"] = True
@@ -95,6 +97,9 @@ def init_arguments_visible(arguments_visible: Dict[str, bool]) -> None:
arguments_visible["command_space"] = False
arguments_visible["comments"] = True
arguments_visible["comment_symbol"] = False
arguments_visible["enable_coolant"] = False
arguments_visible["enable_machine_specific_commands"] = False
arguments_visible["end_of_line_characters"] = False
arguments_visible["feed-precision"] = True
arguments_visible["header"] = True
arguments_visible["line-numbers"] = True
@@ -212,6 +217,35 @@ def init_shared_arguments(
"--comment_symbol",
help=help_message,
)
add_flag_type_arguments(
shared,
argument_defaults["enable_coolant"],
"--enable_coolant",
"--disable_coolant",
"Enable coolant",
"Disable coolant",
arguments_visible["enable_coolant"],
)
add_flag_type_arguments(
shared,
argument_defaults["enable_machine_specific_commands"],
"--enable_machine_specific_commands",
"--disable_machine_specific_commands",
"Enable machine specific commands of the form (MC_RUN_COMMAND: blah)",
"Disable machine specific commands",
arguments_visible["enable_machine_specific_commands"],
)
if arguments_visible["end_of_line_characters"]:
help_message = (
"The character(s) to use at the end of each line in the output file, "
"default is whatever the system uses, may also use '\\n', '\\r', or '\\r\\n'"
)
else:
help_message = argparse.SUPPRESS
shared.add_argument(
"--end_of_line_characters",
help=help_message,
)
if arguments_visible["feed-precision"]:
help_message = (
f"Number of digits of precision for feed rate, "
@@ -412,10 +446,11 @@ def init_shared_values(values: Values) -> None:
#
# By default the line ending characters of the output file(s)
# are written to match the system that the postprocessor runs on.
# If you need to force the line ending characters to a specific
# value, set this variable to "\n" or "\r\n" instead.
# This is indicated by a value of "\n". If you need to force the line ending
# characters to a specific value, set this variable to "\n\n" (meaning "use \n"),
# "\r" or "\r\n" instead.
#
values["END_OF_LINE_CHARACTERS"] = os.linesep
values["END_OF_LINE_CHARACTERS"] = "\n"
#
# The starting precision for feed is also set to 3 digits after the decimal point.
#
@@ -658,7 +693,6 @@ def process_shared_arguments(
filename,
"w",
encoding="utf-8",
newline=values["END_OF_LINE_CHARACTERS"],
) as f:
f.write(argument_text)
return (False, argument_text)
@@ -669,7 +703,6 @@ def process_shared_arguments(
filename,
"w",
encoding="utf-8",
newline=values["END_OF_LINE_CHARACTERS"],
) as f:
f.write(argument_text)
return (False, argument_text)
@@ -726,6 +759,23 @@ def process_shared_arguments(
values["OUTPUT_COMMENTS"] = False
if args.comment_symbol:
values["COMMENT_SYMBOL"] = args.comment_symbol
if args.enable_coolant:
values["ENABLE_COOLANT"] = True
if args.disable_coolant:
values["ENABLE_COOLANT"] = False
if args.enable_machine_specific_commands:
values["ENABLE_MACHINE_SPECIFIC_COMMANDS"] = True
if args.disable_machine_specific_commands:
values["ENABLE_MACHINE_SPECIFIC_COMMANDS"] = False
if args.end_of_line_characters:
if args.end_of_line_characters == "\\n" or args.end_of_line_characters == "\n":
values["END_OF_LINE_CHARACTERS"] = "\n\n"
elif args.end_of_line_characters == "\\r" or args.end_of_line_characters == "\r":
values["END_OF_LINE_CHARACTERS"] = "\r"
elif args.end_of_line_characters == "\\r\\n" or args.end_of_line_characters == "\r\n":
values["END_OF_LINE_CHARACTERS"] = "\r\n"
else:
print("invalid end_of_line_characters, ignoring")
if args.header:
values["OUTPUT_HEADER"] = True
if args.no_header:
@@ -739,9 +789,9 @@ def process_shared_arguments(
if args.no_modal:
values["MODAL"] = False
if args.postamble is not None:
values["POSTAMBLE"] = args.postamble
values["POSTAMBLE"] = args.postamble.replace("\\n", "\n")
if args.preamble is not None:
values["PREAMBLE"] = args.preamble
values["PREAMBLE"] = args.preamble.replace("\\n", "\n")
if args.return_to != "":
values["RETURN_TO"] = [int(v) for v in args.return_to.split(",")]
if len(values["RETURN_TO"]) != 3:

View File

@@ -5,7 +5,7 @@
# * 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) 2022-2025 Larry Woestman <LarryWoestman2@gmail.com> *
# * *
# * This file is part of the FreeCAD CAx development system. *
# * *
@@ -54,104 +54,96 @@ def check_canned_cycles(values: Values) -> 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}")
gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}")
gcode.append(f"{PostUtilsParse.linenumber(values)}M9")
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}")
gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}")
if coolant_mode == "Flood":
gcode.append(f"{PostUtilsParse.linenumber(values)}M8{nl}")
gcode.append(f"{PostUtilsParse.linenumber(values)}M8")
elif coolant_mode == "Mist":
gcode.append(f"{PostUtilsParse.linenumber(values)}M7{nl}")
gcode.append(f"{PostUtilsParse.linenumber(values)}M7")
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}")
gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}")
comment = PostUtilsParse.create_comment(values, "Block-expand: 0")
gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}{nl}")
gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}")
comment = PostUtilsParse.create_comment(values, "Block-enable: 1")
gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}{nl}")
gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}")
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}")
gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}")
comment = PostUtilsParse.create_comment(
values, f'Post Processor: {values["POSTPROCESSOR_FILE_NAME"]}'
)
gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}{nl}")
gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}")
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}")
gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}")
comment = PostUtilsParse.create_comment(values, f"Output Time: {str(datetime.datetime.now())}")
gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}{nl}")
gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}")
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}')
gcode.append(f'{PostUtilsParse.linenumber(values)}{values["MOTION_MODE"]}')
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}")
gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}")
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}")
gcode.append(f"{PostUtilsParse.linenumber(values)}{line}")
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"]:
if values["SHOW_OPERATION_LABELS"]:
@@ -160,55 +152,52 @@ def output_postop(values: Values, gcode: Gcode, obj) -> None:
)
else:
comment = PostUtilsParse.create_comment(values, f'{values["FINISH_LABEL"]} operation')
gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}{nl}")
gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}")
for line in values["POST_OPERATION"].splitlines(False):
gcode.append(f"{PostUtilsParse.linenumber(values)}{line}{nl}")
gcode.append(f"{PostUtilsParse.linenumber(values)}{line}")
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}")
gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}")
for line in values["PREAMBLE"].splitlines(False):
gcode.append(f"{PostUtilsParse.linenumber(values)}{line}{nl}")
gcode.append(f"{PostUtilsParse.linenumber(values)}{line}")
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}")
gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}")
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}")
gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}")
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}")
gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}")
for line in values["PRE_OPERATION"].splitlines(False):
gcode.append(f"{PostUtilsParse.linenumber(values)}{line}{nl}")
gcode.append(f"{PostUtilsParse.linenumber(values)}{line}")
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
@@ -220,56 +209,51 @@ def output_return_to(values: Values, gcode: Gcode) -> None:
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}")
gcode.append(f"{PostUtilsParse.linenumber(values)}{cmd}")
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}")
gcode.append(f"{PostUtilsParse.linenumber(values)}{line}")
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}")
gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}")
comment = PostUtilsParse.create_comment(values, "Block-expand: 0")
gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}{nl}")
gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}")
comment = PostUtilsParse.create_comment(values, "Block-enable: 1")
gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}{nl}")
gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}")
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}")
gcode.append(f"{PostUtilsParse.linenumber(values)}{comment}")
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}")
gcode.append(f"{PostUtilsParse.linenumber(values)}{line}")
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"
@@ -280,7 +264,7 @@ def output_units(values: Values, gcode: Gcode) -> None:
values["UNIT_FORMAT"] = "in"
values["UNIT_SPEED_FORMAT"] = "in/min"
else:
gcode.append(f'{PostUtilsParse.linenumber(values)}{values["UNITS"]}{nl}')
gcode.append(f'{PostUtilsParse.linenumber(values)}{values["UNITS"]}')
def export_common(values: Values, objectslist, filename: str) -> str:
@@ -288,8 +272,8 @@ def export_common(values: Values, objectslist, filename: str) -> str:
coolant_mode: str
dia: PostUtils.GCodeEditorDialog
final: str
final_for_editor: str
gcode: Gcode = []
result: bool
for obj in objectslist:
if not hasattr(obj, "Path"):
@@ -333,24 +317,63 @@ def export_common(values: Values, objectslist, filename: str) -> str:
output_safetyblock(values, gcode)
output_postamble(values, gcode)
final = "".join(gcode)
# add the appropriate end-of-line characters to the gcode, including after the last line
gcode.append("")
if values["END_OF_LINE_CHARACTERS"] == "\n\n":
# flag that we want to use "\n" as the end-of-line characters
# by putting "\n\n" at the front of the gcode (which shouldn't otherwise happen)
final = "\n\n" + "\n".join(gcode)
else:
# the other possibilities are:
# "\n" means "use the end-of-line characters that match the system"
# "\r" means "use \r"
# "\r\n" means "use \r\n"
final = values["END_OF_LINE_CHARACTERS"].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()
# the editor expects lines to end in "\n", and returns lines ending in "\n"
if values["END_OF_LINE_CHARACTERS"] == "\n":
dia.editor.setText(final)
if dia.exec_():
final = dia.editor.toPlainText()
else:
final_for_editor = "\n".join(gcode)
dia.editor.setText(final_for_editor)
if dia.exec_():
final_for_editor = dia.editor.toPlainText()
# convert all "\n" to the appropriate end-of-line characters
if values["END_OF_LINE_CHARACTERS"] == "\n\n":
# flag that we want to use "\n" as the end-of-line characters
# by putting "\n\n" at the front of the gcode
# (which shouldn't otherwise happen)
final = "\n\n" + final_for_editor
else:
# the other possibilities are:
# "\r" means "use \r"
# "\r\n" means "use \r\n"
final = final_for_editor.replace("\n", values["END_OF_LINE_CHARACTERS"])
print("done postprocessing.")
if not filename == "-":
with open(
filename, "w", encoding="utf-8", newline=values["END_OF_LINE_CHARACTERS"]
) as gfile:
gfile.write(final)
if final[0:2] == "\n\n":
# write out the gcode using "\n" as the end-of-line characters
with open(filename, "w", encoding="utf-8", newline="") as gfile:
gfile.write(final[2:])
elif "\r" in final:
with open(filename, "w", encoding="utf-8", newline="") as gfile:
# write out the gcode with whatever end-of-line characters it already has,
# presumably either "\r" or "\r\n"
gfile.write(final)
else:
with open(filename, "w", encoding="utf-8", newline=None) as gfile:
# The gcode has "\n" as the end-of-line characters, which means
# "write out the gcode with whatever end-of-line characters the system
# that is running the postprocessor uses".
gfile.write(final)
return final

View File

@@ -55,7 +55,6 @@ def check_for_an_adaptive_op(
) -> str:
"""Check to see if the current command is an adaptive op."""
adaptiveOp: bool
nl: str = "\n"
opHorizRapid: float
opVertRapid: float
@@ -63,7 +62,7 @@ def check_for_an_adaptive_op(
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}")
command_line.append(f"(Tool Controller Rapid Values are unset)")
return ""
@@ -78,12 +77,11 @@ def check_for_drill_translate(
) -> 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, format_command_line(values, command_line))
gcode.append(f"{linenumber(values)}{comment}{nl}")
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:
@@ -100,7 +98,7 @@ def check_for_drill_translate(
# 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}")
gcode.append(f"{linenumber(values)}G91")
return True
return False
@@ -108,7 +106,6 @@ def check_for_drill_translate(
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"]:
@@ -116,7 +113,7 @@ def check_for_machine_specific_commands(values: Values, gcode: Gcode, command: s
if m:
raw_command = m.group(1)
# pass literally to the controller
gcode.append(f"{linenumber(values)}{raw_command}{nl}")
gcode.append(f"{linenumber(values)}{raw_command}")
def check_for_spindle_wait(
@@ -124,12 +121,11 @@ def check_for_spindle_wait(
) -> 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.append(f"{linenumber(values)}{format_command_line(values, command_line)}{nl}")
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}{nl}")
gcode.append(f"{linenumber(values)}{cmd}")
def check_for_suppressed_commands(
@@ -137,13 +133,12 @@ def check_for_suppressed_commands(
) -> 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, format_command_line(values, command_line))
gcode.append(f"{linenumber(values)}{comment}{nl}")
gcode.append(f"{linenumber(values)}{comment}")
# remove the command
return True
return False
@@ -151,34 +146,32 @@ def check_for_suppressed_commands(
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.append(f"{linenumber(values)}{cmd}{nl}")
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."""
nl: str = "\n"
if command in ("M6", "M06"):
if values["OUTPUT_COMMENTS"]:
comment = create_comment(values, "Begin toolchange")
gcode.append(f"{linenumber(values)}{comment}{nl}")
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{nl}")
gcode.append(f"{linenumber(values)}M5")
for line in values["TOOL_CHANGE"].splitlines(False):
gcode.append(f"{linenumber(values)}{line}{nl}")
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}{nl}")
gcode.append(f"{linenumber(values)}{comment}")
return True
return False
@@ -425,14 +418,13 @@ def drill_translate(
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}")
gcode.append(f"{linenumber(values)}G90")
drill_x = Units.Quantity(params["X"], Units.Length)
drill_y = Units.Quantity(params["Y"], Units.Length)
@@ -440,7 +432,7 @@ def drill_translate(
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}{nl}")
gcode.append(f"{linenumber(values)}{comment}")
return
motion_z = Units.Quantity(motion_location["Z"], Units.Length)
if values["MOTION_MODE"] == "G91": # relative movements
@@ -452,9 +444,9 @@ def drill_translate(
retract_z = motion_z
cmd = format_command_line(values, ["G0", f"Z{format_for_axis(values, retract_z)}"])
G0_retract_z = f"{cmd}{nl}"
G0_retract_z = f"{cmd}"
cmd = format_for_feed(values, Units.Quantity(params["F"], Units.Velocity))
F_feedrate = f'{values["COMMAND_SPACE"]}F{cmd}{nl}'
F_feedrate = f'{values["COMMAND_SPACE"]}F{cmd}'
# preliminary movement(s)
if motion_z < retract_z:
@@ -467,7 +459,7 @@ def drill_translate(
f"Y{format_for_axis(values, drill_y)}",
],
)
gcode.append(f"{linenumber(values)}{cmd}{nl}")
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 !
@@ -588,7 +580,6 @@ def output_G73_G83_drill_moves(
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)
@@ -603,7 +594,7 @@ def output_G73_G83_drill_moves(
values,
["G0", f"Z{format_for_axis(values, clearance_depth)}"],
)
gcode.append(f"{linenumber(values)}{cmd}{nl}")
gcode.append(f"{linenumber(values)}{cmd}")
next_stop_z = last_stop_z - drill_step
if next_stop_z > drill_z:
cmd = format_command_line(
@@ -620,7 +611,7 @@ def output_G73_G83_drill_moves(
f"Z{format_for_axis(values, chip_breaker_height)}",
],
)
gcode.append(f"{linenumber(values)}{cmd}{nl}")
gcode.append(f"{linenumber(values)}{cmd}")
elif command == "G83":
# Rapid up to the retract height
gcode.append(f"{linenumber(values)}{G0_retract_z}")
@@ -643,26 +634,24 @@ def output_G81_G82_drill_moves(
) -> 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)}{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
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}")
gcode.append(f"{linenumber(values)}{comment}{nl}")
gcode.append(f"{linenumber(values)}{comment}")
for p in pathobj.Group:
parse_a_group(values, gcode, p)
else: # parsing simple path
@@ -671,7 +660,7 @@ def parse_a_group(values: Values, gcode: Gcode, pathobj) -> None:
return
if values["OUTPUT_PATH_LABELS"] and values["OUTPUT_COMMENTS"]:
comment = create_comment(values, f"Path: {pathobj.Label}")
gcode.append(f"{linenumber(values)}{comment}{nl}")
gcode.append(f"{linenumber(values)}{comment}")
parse_a_path(values, gcode, pathobj)
@@ -685,7 +674,6 @@ def parse_a_path(values: Values, gcode: Gcode, pathobj) -> None:
drill_retract_mode: str = "G98"
lastcommand: str = ""
motion_location: PathParameters = {} # keep track of last motion location
nl: str = "\n"
parameter: str
parameter_value: str
@@ -784,13 +772,13 @@ def parse_a_path(values: Values, gcode: Gcode, pathobj) -> None:
command_line[1],
command_line[0],
] # swap the order of the commands
# Add a line number to the front and a newline to the end of the command line
# Add a line number to the front of the command line
gcode.append(
f"{linenumber(values)}{format_command_line(values, swapped_command_line)}{nl}"
f"{linenumber(values)}{format_command_line(values, swapped_command_line)}"
)
else:
# Add a line number to the front and a newline to the end of the command line
gcode.append(f"{linenumber(values)}{format_command_line(values, command_line)}{nl}")
# 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)

View File

@@ -674,20 +674,22 @@ class Snapmaker(Path.Post.Processor.PostProcessor):
def output_header(self, gcode: List[str]):
"""custom method derived from Path.Post.UtilsExport.output_header"""
cam_file: str
comment: str
if not self.values["OUTPUT_HEADER"]:
return
def add_comment(text):
com = Path.Post.UtilsParse.create_comment(self.values, text)
gcode.append(
f'{Path.Post.UtilsParse.linenumber(self.values)}{com}{self.values["END_OF_LINE_CHARACTERS"]}'
)
gcode.append(f"{Path.Post.UtilsParse.linenumber(self.values)}{com}")
add_comment("Header Start")
add_comment("header_type: cnc")
add_comment(f'machine: {self.values["MACHINE_NAME"]}')
add_comment(f'Post Processor: {self.values["POSTPROCESSOR_FILE_NAME"]}')
comment = Path.Post.UtilsParse.create_comment(
self.values, f'Post Processor: {self.values["POSTPROCESSOR_FILE_NAME"]}'
)
gcode.append(f"{Path.Post.UtilsParse.linenumber(self.values)}{comment}")
if FreeCAD.ActiveDocument:
cam_file = os.path.basename(FreeCAD.ActiveDocument.FileName)
else:
@@ -803,20 +805,28 @@ class Snapmaker(Path.Post.Processor.PostProcessor):
if self.values["BOUNDARIES_CHECK"]:
self.check_boundaries(gcode)
final = "".join(gcode)
# add the appropriate end-of-line characters to the gcode, including after the last line
gcode.append("")
final = self.values["END_OF_LINE_CHARACTERS"].join(gcode)
if FreeCAD.GuiUp and self.values["SHOW_EDITOR"]:
# size limit removed as irrelevant on my computer - see if issues occur
dia = Path.Post.Utils.GCodeEditorDialog()
dia.editor.setText(final)
result = dia.exec_()
if result:
final = dia.editor.toPlainText()
dia = PostUtils.GCodeEditorDialog()
# the editor expects lines to end in "\n", and returns lines ending in "\n"
if self.values["END_OF_LINE_CHARACTERS"] == "\n":
dia.editor.setText(final)
if dia.exec_():
final = dia.editor.toPlainText()
else:
final_for_editor = "\n".join(gcode)
dia.editor.setText(final_for_editor)
if dia.exec_():
final_for_editor = dia.editor.toPlainText()
# convert all "\n" to the appropriate end-of-line characters
final = final_for_editor.replace("\n", self.values["END_OF_LINE_CHARACTERS"])
if not filename == "-":
with open(
filename, "w", encoding="utf-8", newline=self.values["END_OF_LINE_CHARACTERS"]
) as gfile:
with open(filename, "w", encoding="utf-8", newline="") as gfile:
gfile.write(final)
return final