Merge pull request #20833 from LarryWoestman/tests
CAM: added three command line arguments, with tests
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user