Merge pull request #25850 from petterreinholdtsen/cam-fanuc-post-fixes
CAM: Get Fanuc post processor working and implement several improvements
This commit is contained in:
@@ -28,6 +28,11 @@ without requiring disk I/O or loading actual FreeCAD documents.
|
||||
import Path
|
||||
|
||||
|
||||
class MockTool:
|
||||
def __init__(self):
|
||||
self.ShapeName = "endmill"
|
||||
|
||||
|
||||
class MockToolController:
|
||||
"""Mock ToolController for operations."""
|
||||
|
||||
@@ -38,6 +43,7 @@ class MockToolController:
|
||||
spindle_speed=1000,
|
||||
spindle_dir="Forward",
|
||||
):
|
||||
self.Tool = MockTool()
|
||||
self.ToolNumber = tool_number
|
||||
self.Label = label
|
||||
self.SpindleSpeed = spindle_speed
|
||||
|
||||
369
src/Mod/CAM/CAMTests/TestFanucPost.py
Normal file
369
src/Mod/CAM/CAMTests/TestFanucPost.py
Normal file
@@ -0,0 +1,369 @@
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2022 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * Copyright (c) 2022 Larry Woestman <LarryWoestman2@gmail.com> *
|
||||
# * Copyright (c) 2025 Petter Reinholdtsen <pere@hungry.com> *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * This program is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this program; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
import Path
|
||||
from CAMTests import PathTestUtils
|
||||
from CAMTests import PostTestMocks
|
||||
from Path.Post.Processor import PostProcessorFactory
|
||||
|
||||
|
||||
Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule())
|
||||
Path.Log.trackModule(Path.Log.thisModule())
|
||||
|
||||
|
||||
class TestFanucPost(PathTestUtils.PathTestBase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""setUpClass()...
|
||||
This method is called upon instantiation of this test class. Add code
|
||||
and objects here that are needed for the duration of the test() methods
|
||||
in this class. In other words, set up the 'global' test environment
|
||||
here; use the `setUp()` method to set up a 'local' test environment.
|
||||
This method does not have access to the class `self` reference, but it
|
||||
is able to call static methods within this same class.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
"""tearDownClass()...
|
||||
This method is called prior to destruction of this test class. Add
|
||||
code and objects here that cleanup the test environment after the
|
||||
test() methods in this class have been executed. This method does
|
||||
not have access to the class `self` reference. This method is able
|
||||
to call static methods within this same class.
|
||||
"""
|
||||
|
||||
# Setup and tear down methods called before and after each unit test
|
||||
def setUp(self):
|
||||
"""setUp()...
|
||||
This method is called prior to each `test()` method. Add code and
|
||||
objects here that are needed for multiple `test()` methods.
|
||||
"""
|
||||
|
||||
# Create mock job with default operation and tool controller
|
||||
self.job, self.profile_op, self.tool_controller = (
|
||||
PostTestMocks.create_default_job_with_operation()
|
||||
)
|
||||
|
||||
# Create postprocessor using the mock job
|
||||
self.post = PostProcessorFactory.get_post_processor(self.job, "fanuc")
|
||||
|
||||
# allow a full length "diff" if an error occurs
|
||||
self.maxDiff = None
|
||||
# reinitialize the postprocessor data structures between tests
|
||||
self.post.reinitialize()
|
||||
|
||||
def tearDown(self):
|
||||
"""tearDown()...
|
||||
This method is called after each test() method. Add cleanup instructions here.
|
||||
Such cleanup instructions will likely undo those in the setUp() method.
|
||||
"""
|
||||
|
||||
def test_empty_path(self):
|
||||
"""Test Output Generation.
|
||||
Empty path. Produces only the preamble and postable.
|
||||
"""
|
||||
|
||||
self.profile_op.Path = Path.Path([])
|
||||
self.job.PostProcessorArgs = "--no-show-editor"
|
||||
|
||||
# Test generating with header
|
||||
# Header contains a time stamp that messes up unit testing.
|
||||
# Only test length of result.
|
||||
gcode = self.post.export()[0][1]
|
||||
self.assertEqual(28, len(gcode.splitlines()))
|
||||
# Test without header
|
||||
expected = """%
|
||||
(BEGIN PREAMBLE)
|
||||
G17 G54 G40 G49 G80 G90
|
||||
G21
|
||||
(BEGIN OPERATION: TC: DEFAULT TOOL)
|
||||
(MACHINE UNITS: MM/MIN)
|
||||
M05
|
||||
G28 G91 Z0
|
||||
M6 T1
|
||||
G91 G0 G43 G54 Z-[#[2000+#4120]] H#4120
|
||||
G90
|
||||
M3 S1000
|
||||
(FINISH OPERATION: TC: DEFAULT TOOL)
|
||||
(BEGIN OPERATION: FIXTURE)
|
||||
(MACHINE UNITS: MM/MIN)
|
||||
G54
|
||||
(FINISH OPERATION: FIXTURE)
|
||||
(BEGIN OPERATION: PROFILE)
|
||||
(MACHINE UNITS: MM/MIN)
|
||||
(FINISH OPERATION: PROFILE)
|
||||
(BEGIN POSTAMBLE)
|
||||
M05
|
||||
G17 G54 G90 G80 G40
|
||||
M30
|
||||
%
|
||||
"""
|
||||
|
||||
self.profile_op.Path = Path.Path([])
|
||||
self.job.PostProcessorArgs = (
|
||||
"--no-header --no-show-editor"
|
||||
# "--no-header --no-comments --no-show-editor --precision=2"
|
||||
)
|
||||
|
||||
gcode = self.post.export()[0][1]
|
||||
self.assertEqual(gcode, expected)
|
||||
|
||||
# test without comments
|
||||
expected = """%
|
||||
G17 G54 G40 G49 G80 G90
|
||||
G21
|
||||
M05
|
||||
G28 G91 Z0
|
||||
M6 T1
|
||||
G91 G0 G43 G54 Z-[#[2000+#4120]] H#4120
|
||||
G90
|
||||
M3 S1000
|
||||
G54
|
||||
M05
|
||||
G17 G54 G90 G80 G40
|
||||
M30
|
||||
%
|
||||
"""
|
||||
|
||||
self.profile_op.Path = Path.Path([])
|
||||
self.job.PostProcessorArgs = (
|
||||
"--no-header --no-comments --no-show-editor"
|
||||
# "--no-header --no-comments --no-show-editor --precision=2"
|
||||
)
|
||||
gcode = self.post.export()[0][1]
|
||||
self.assertEqual(gcode, expected)
|
||||
|
||||
def test_empty_path_spindle_empty(self):
|
||||
"""Test Output Generation.
|
||||
Empty path. Produces only the preamble and postable.
|
||||
"""
|
||||
|
||||
self.profile_op.Path = Path.Path([])
|
||||
self.job.PostProcessorArgs = "--no-show-editor --end-spindle-empty"
|
||||
|
||||
# Test generating with header
|
||||
# Header contains a time stamp that messes up unit testing.
|
||||
# Only test length of result.
|
||||
gcode = self.post.export()[0][1]
|
||||
self.assertEqual(32, len(gcode.splitlines()))
|
||||
# Test without header
|
||||
expected = """%
|
||||
(BEGIN PREAMBLE)
|
||||
G17 G54 G40 G49 G80 G90
|
||||
G21
|
||||
(BEGIN OPERATION: TC: DEFAULT TOOL)
|
||||
(MACHINE UNITS: MM/MIN)
|
||||
M05
|
||||
G28 G91 Z0
|
||||
M6 T1
|
||||
G91 G0 G43 G54 Z-[#[2000+#4120]] H#4120
|
||||
G90
|
||||
M3 S1000
|
||||
(FINISH OPERATION: TC: DEFAULT TOOL)
|
||||
(BEGIN OPERATION: FIXTURE)
|
||||
(MACHINE UNITS: MM/MIN)
|
||||
G54
|
||||
(FINISH OPERATION: FIXTURE)
|
||||
(BEGIN OPERATION: PROFILE)
|
||||
(MACHINE UNITS: MM/MIN)
|
||||
(FINISH OPERATION: PROFILE)
|
||||
(BEGIN MAKING SPINDLE EMPTY)
|
||||
M05
|
||||
G28 G91 Z0
|
||||
M6 T0
|
||||
(BEGIN POSTAMBLE)
|
||||
M05
|
||||
G17 G54 G90 G80 G40
|
||||
M30
|
||||
%
|
||||
"""
|
||||
|
||||
self.profile_op.Path = Path.Path([])
|
||||
self.job.PostProcessorArgs = (
|
||||
"--no-header --no-show-editor --end-spindle-empty"
|
||||
# "--no-header --no-comments --no-show-editor --precision=2"
|
||||
)
|
||||
|
||||
gcode = self.post.export()[0][1]
|
||||
self.assertEqual(gcode, expected)
|
||||
|
||||
# test without comments
|
||||
expected = """%
|
||||
G17 G54 G40 G49 G80 G90
|
||||
G21
|
||||
M05
|
||||
G28 G91 Z0
|
||||
M6 T1
|
||||
G91 G0 G43 G54 Z-[#[2000+#4120]] H#4120
|
||||
G90
|
||||
M3 S1000
|
||||
G54
|
||||
M05
|
||||
G28 G91 Z0
|
||||
M6 T0
|
||||
M05
|
||||
G17 G54 G90 G80 G40
|
||||
M30
|
||||
%
|
||||
"""
|
||||
|
||||
self.profile_op.Path = Path.Path([])
|
||||
self.job.PostProcessorArgs = (
|
||||
"--no-header --no-comments --no-show-editor --end-spindle-empty"
|
||||
# "--no-header --no-comments --no-show-editor --precision=2"
|
||||
)
|
||||
gcode = self.post.export()[0][1]
|
||||
self.assertEqual(gcode, expected)
|
||||
|
||||
def test_precision(self):
|
||||
"""Test command Generation.
|
||||
Test Precision
|
||||
"""
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
|
||||
self.profile_op.Path = Path.Path([c])
|
||||
self.job.PostProcessorArgs = "--no-header --no-show-editor"
|
||||
gcode = self.post.export()[0][1]
|
||||
result = gcode.splitlines()[19]
|
||||
expected = "G0 X10.000 Y20.000 Z30.000"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
self.job.PostProcessorArgs = "--no-header --precision=2 --no-show-editor"
|
||||
gcode = self.post.export()[0][1]
|
||||
result = gcode.splitlines()[19]
|
||||
expected = "G0 X10.00 Y20.00 Z30.00"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_line_numbers(self):
|
||||
"""
|
||||
Test Line Numbers
|
||||
"""
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
|
||||
self.profile_op.Path = Path.Path([c])
|
||||
self.job.PostProcessorArgs = "--no-header --line-numbers --no-show-editor"
|
||||
gcode = self.post.export()[0][1]
|
||||
result = gcode.splitlines()[19]
|
||||
expected = "N290 G0 X10.000 Y20.000 Z30.000"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_pre_amble(self):
|
||||
"""
|
||||
Test Pre-amble
|
||||
"""
|
||||
|
||||
self.profile_op.Path = Path.Path([])
|
||||
self.job.PostProcessorArgs = (
|
||||
"--no-header --no-comments --preamble='G18 G55' --no-show-editor"
|
||||
)
|
||||
gcode = self.post.export()[0][1]
|
||||
result = gcode.splitlines()[1]
|
||||
self.assertEqual(result, "G18 G55")
|
||||
|
||||
def test_post_amble(self):
|
||||
"""
|
||||
Test Post-amble
|
||||
"""
|
||||
self.profile_op.Path = Path.Path([])
|
||||
self.job.PostProcessorArgs = (
|
||||
"--no-header --no-comments --postamble='G0 Z50\nM30' --no-show-editor"
|
||||
)
|
||||
gcode = self.post.export()[0][1]
|
||||
self.assertEqual(gcode.splitlines()[-3], "G0 Z50")
|
||||
self.assertEqual(gcode.splitlines()[-2], "M30")
|
||||
self.assertEqual(gcode.splitlines()[-1], "%")
|
||||
|
||||
def test_inches(self):
|
||||
"""
|
||||
Test inches
|
||||
"""
|
||||
|
||||
c = Path.Command("G0 X10 Y20 Z30")
|
||||
self.profile_op.Path = Path.Path([c])
|
||||
self.job.PostProcessorArgs = "--no-header --inches --no-show-editor"
|
||||
gcode = self.post.export()[0][1]
|
||||
self.assertEqual(gcode.splitlines()[3], "G20")
|
||||
|
||||
result = gcode.splitlines()[19]
|
||||
expected = "G0 X0.3937 Y0.7874 Z1.1811"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
self.job.PostProcessorArgs = "--no-header --inches --precision=2 --no-show-editor"
|
||||
gcode = self.post.export()[0][1]
|
||||
result = gcode.splitlines()[19]
|
||||
expected = "G0 X0.39 Y0.79 Z1.18"
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_tool_change(self):
|
||||
"""
|
||||
Test tool change
|
||||
"""
|
||||
c = Path.Command("M6 T1")
|
||||
c2 = Path.Command("M3 S3000")
|
||||
self.profile_op.Path = Path.Path([c, c2])
|
||||
self.job.PostProcessorArgs = "--no-header --no-show-editor"
|
||||
gcode = self.post.export()[0][1]
|
||||
self.assertEqual(gcode.splitlines()[19], "M05")
|
||||
self.assertEqual(gcode.splitlines()[20], "G28 G91 Z0")
|
||||
self.assertEqual(gcode.splitlines()[21], "M6 T1")
|
||||
self.assertEqual(gcode.splitlines()[22], "G91 G0 G43 G54 Z-[#[2000+#4120]] H#4120")
|
||||
self.assertEqual(gcode.splitlines()[23], "G90")
|
||||
self.assertEqual(gcode.splitlines()[24], "M3 S3000")
|
||||
|
||||
# suppress TLO
|
||||
self.job.PostProcessorArgs = "--no-header --no-tlo --no-show-editor"
|
||||
gcode = self.post.export()[0][1]
|
||||
self.assertEqual(gcode.splitlines()[20], "M3 S3000")
|
||||
|
||||
def test_thread_tap(self):
|
||||
"""
|
||||
Test threading using drill cycle converted to tapping
|
||||
"""
|
||||
|
||||
self.tool_controller.Tool.ShapeName = "tap"
|
||||
c = Path.Command("G0 X10 Y10")
|
||||
c2 = Path.Command("G81 X10 Y10 Z-10 R20 F1 P1 Q1")
|
||||
self.profile_op.Path = Path.Path([c, c2])
|
||||
self.job.PostProcessorArgs = "--no-header --no-show-editor"
|
||||
gcode = self.post.export()[0][1]
|
||||
self.assertEqual(gcode.splitlines()[18], "G0 X10.000 Y10.000")
|
||||
self.assertEqual(gcode.splitlines()[19], "G95")
|
||||
self.assertEqual(gcode.splitlines()[20], "M29 S1000")
|
||||
self.assertEqual(gcode.splitlines()[21], "G84 Z-10.000 R20.000 F1.000 P1.000 Q1.000")
|
||||
self.assertEqual(gcode.splitlines()[22], "G80")
|
||||
self.assertEqual(gcode.splitlines()[23], "G94")
|
||||
|
||||
def test_comment(self):
|
||||
"""
|
||||
Test comment
|
||||
"""
|
||||
|
||||
c = Path.Command("(comment)")
|
||||
|
||||
self.profile_op.Path = Path.Path([c])
|
||||
self.job.PostProcessorArgs = "--no-header --no-show-editor"
|
||||
gcode = self.post.export()[0][1]
|
||||
result = gcode.splitlines()[19]
|
||||
expected = "(COMMENT)"
|
||||
self.assertEqual(result, expected)
|
||||
@@ -502,6 +502,7 @@ SET(Tests_SRCS
|
||||
CAMTests/TestCAMSanity.py
|
||||
CAMTests/TestCentroidPost.py
|
||||
CAMTests/TestCentroidLegacyPost.py
|
||||
CAMTests/TestFanucPost.py
|
||||
CAMTests/TestGenericPost.py
|
||||
CAMTests/TestGrblPost.py
|
||||
CAMTests/TestGrblLegacyPost.py
|
||||
|
||||
@@ -60,14 +60,14 @@ parser.add_argument(
|
||||
action="store_true",
|
||||
help="don't pop up editor before writing output",
|
||||
)
|
||||
parser.add_argument("--precision", default="3", help="number of digits of precision, default=3")
|
||||
parser.add_argument("--precision", help="number of digits of precision, default=3 (mm) or 4 (in)")
|
||||
parser.add_argument(
|
||||
"--preamble",
|
||||
help='set commands to be issued before the first command, default="G17 G54 G40 G49 G80 G90\\n"',
|
||||
)
|
||||
parser.add_argument(
|
||||
"--postamble",
|
||||
help='set commands to be issued after the last command, default="M05\\nG17 G54 G90 G80 G40\\nM6 T0\\nM2\\n"',
|
||||
help='set commands to be issued after the last command, default="M05\\nG17 G54 G90 G80 G40\\nM30\\n"',
|
||||
)
|
||||
parser.add_argument(
|
||||
"--inches", action="store_true", help="Convert output for US imperial mode (G20)"
|
||||
@@ -85,6 +85,11 @@ parser.add_argument(
|
||||
action="store_true",
|
||||
help="suppress tool length offset (G43) following tool changes",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--end-spindle-empty",
|
||||
action="store_true",
|
||||
help="place last tool in tool change carousel before postamble",
|
||||
)
|
||||
|
||||
TOOLTIP_ARGS = parser.format_help()
|
||||
|
||||
@@ -101,6 +106,8 @@ OUTPUT_DOUBLES = (
|
||||
COMMAND_SPACE = " "
|
||||
LINENR = 100 # line number starting value
|
||||
|
||||
END_SPINDLE_EMPTY = False
|
||||
|
||||
# These globals will be reflected in the Machine configuration of the project
|
||||
UNITS = "G21" # G21 for metric, G20 for us standard
|
||||
UNIT_SPEED_FORMAT = "mm/min"
|
||||
@@ -116,14 +123,13 @@ PRECISION = 3
|
||||
tapSpeed = 0
|
||||
|
||||
# Preamble text will appear at the beginning of the GCODE output file.
|
||||
PREAMBLE = """G17 G54 G40 G49 G80 G90
|
||||
DEFAULT_PREAMBLE = """G17 G54 G40 G49 G80 G90
|
||||
"""
|
||||
|
||||
# Postamble text will appear following the last operation.
|
||||
POSTAMBLE = """M05
|
||||
DEFAULT_POSTAMBLE = """M05
|
||||
G17 G54 G90 G80 G40
|
||||
M6 T0
|
||||
M2
|
||||
M30
|
||||
"""
|
||||
|
||||
# Pre operation text will be inserted before every operation
|
||||
@@ -133,7 +139,9 @@ PRE_OPERATION = """"""
|
||||
POST_OPERATION = """"""
|
||||
|
||||
# Tool Change commands will be inserted before a tool change
|
||||
TOOL_CHANGE = """"""
|
||||
# Move to tool change Z position
|
||||
TOOL_CHANGE = """G28 G91 Z0
|
||||
"""
|
||||
|
||||
|
||||
def processArguments(argstring):
|
||||
@@ -149,35 +157,66 @@ def processArguments(argstring):
|
||||
global UNIT_FORMAT
|
||||
global MODAL
|
||||
global USE_TLO
|
||||
global END_SPINDLE_EMPTY
|
||||
global OUTPUT_DOUBLES
|
||||
global LINENR
|
||||
|
||||
try:
|
||||
args = parser.parse_args(shlex.split(argstring))
|
||||
if args.no_header:
|
||||
OUTPUT_HEADER = False
|
||||
else:
|
||||
OUTPUT_HEADER = True
|
||||
if args.no_comments:
|
||||
OUTPUT_COMMENTS = False
|
||||
else:
|
||||
OUTPUT_COMMENTS = True
|
||||
if args.line_numbers:
|
||||
OUTPUT_LINE_NUMBERS = True
|
||||
LINENR = 100
|
||||
else:
|
||||
OUTPUT_LINE_NUMBERS = False
|
||||
if args.no_show_editor:
|
||||
SHOW_EDITOR = False
|
||||
print("Show editor = %d" % SHOW_EDITOR)
|
||||
PRECISION = args.precision
|
||||
else:
|
||||
SHOW_EDITOR = True
|
||||
print("Show editor = %s" % SHOW_EDITOR)
|
||||
if args.preamble is not None:
|
||||
PREAMBLE = args.preamble.replace("\\n", "\n")
|
||||
else:
|
||||
PREAMBLE = DEFAULT_PREAMBLE
|
||||
if args.postamble is not None:
|
||||
POSTAMBLE = args.postamble.replace("\\n", "\n")
|
||||
else:
|
||||
POSTAMBLE = DEFAULT_POSTAMBLE
|
||||
if args.inches:
|
||||
UNITS = "G20"
|
||||
UNIT_SPEED_FORMAT = "in/min"
|
||||
UNIT_FORMAT = "in"
|
||||
PRECISION = 4
|
||||
else:
|
||||
UNITS = "G21"
|
||||
UNIT_SPEED_FORMAT = "mm/min"
|
||||
UNIT_FORMAT = "mm"
|
||||
PRECISION = 3
|
||||
if args.precision:
|
||||
PRECISION = int(args.precision)
|
||||
if args.no_modal:
|
||||
MODAL = False
|
||||
else:
|
||||
MODAL = True
|
||||
if args.no_tlo:
|
||||
USE_TLO = False
|
||||
else:
|
||||
USE_TLO = True
|
||||
if args.no_axis_modal:
|
||||
OUTPUT_DOUBLES = True
|
||||
else:
|
||||
OUTPUT_DOUBLES = False
|
||||
if args.end_spindle_empty:
|
||||
END_SPINDLE_EMPTY = True
|
||||
else:
|
||||
END_SPINDLE_EMPTY = False
|
||||
|
||||
except Exception:
|
||||
return False
|
||||
@@ -204,20 +243,20 @@ def export(objectslist, filename, argstring):
|
||||
print("postprocessing...")
|
||||
gcode = ""
|
||||
|
||||
gcode += "%\n"
|
||||
|
||||
# write header
|
||||
if OUTPUT_HEADER:
|
||||
gcode += "%\n"
|
||||
gcode += ";\n"
|
||||
# Get current version info
|
||||
major = int(FreeCAD.ConfigGet("BuildVersionMajor"))
|
||||
minor = int(FreeCAD.ConfigGet("BuildVersionMinor"))
|
||||
|
||||
# the filename variable always contain "-", so unable to
|
||||
# provide more accurate information.
|
||||
gcode += "(" + "FREECAD-FILENAME-GOES-HERE" + ", " + "JOB-NAME-GOES-HERE" + ")\n"
|
||||
gcode += (
|
||||
os.path.split(filename)[-1]
|
||||
+ " ("
|
||||
+ "FREECAD-FILENAME-GOES-HERE"
|
||||
+ ", "
|
||||
+ "JOB-NAME-GOES-HERE"
|
||||
+ ")\n"
|
||||
linenumber() + "(POST PROCESSOR: FANUC USING FREECAD %d.%d" % (major, minor) + ")\n"
|
||||
)
|
||||
gcode += linenumber() + "(" + filename.upper() + ",EXPORTED BY FREECAD!)\n"
|
||||
gcode += linenumber() + "(POST PROCESSOR: " + __name__.upper() + ")\n"
|
||||
gcode += linenumber() + "(OUTPUT TIME:" + str(now).upper() + ")\n"
|
||||
|
||||
# Write the preamble
|
||||
@@ -229,8 +268,20 @@ def export(objectslist, filename, argstring):
|
||||
|
||||
for obj in objectslist:
|
||||
|
||||
# to stay compatible with FreeCAD 1.0
|
||||
def activeForOp(obj):
|
||||
# The activeForOp method is available since 2025-05-04 /
|
||||
# commit 1e87d8e6681b755b9757f94b1201e50eb84b28a2
|
||||
if hasattr(PathUtil, "activeForOp"):
|
||||
return PathUtil.activeForOp(obj)
|
||||
if hasattr(obj, "Active"):
|
||||
return obj.Active
|
||||
if hasattr(obj, "Base") and hasattr(obj.Base, "Active"):
|
||||
return obj.Base.Active
|
||||
return True
|
||||
|
||||
# Skip inactive operations
|
||||
if not PathUtil.activeForOp(obj):
|
||||
if not activeForOp(obj):
|
||||
continue
|
||||
|
||||
# do the pre_op
|
||||
@@ -240,8 +291,26 @@ def export(objectslist, filename, argstring):
|
||||
for line in PRE_OPERATION.splitlines(True):
|
||||
gcode += linenumber() + line
|
||||
|
||||
# to stay compatible with FreeCAD 1.0
|
||||
def coolantModeForOp(obj):
|
||||
# The coolantModeForOp method is available since
|
||||
# 2025-05-04 / commit
|
||||
# 1e87d8e6681b755b9757f94b1201e50eb84b28a2
|
||||
if hasattr(PathUtil, "coolantModeForOp"):
|
||||
return PathUtil.coolantModeForOp(obj)
|
||||
if (
|
||||
hasattr(obj, "CoolantMode")
|
||||
or hasattr(obj, "Base")
|
||||
and hasattr(obj.Base, "CoolantMode")
|
||||
):
|
||||
if hasattr(obj, "CoolantMode"):
|
||||
return obj.CoolantMode
|
||||
else:
|
||||
return obj.Base.CoolantMode
|
||||
return "None"
|
||||
|
||||
# get coolant mode
|
||||
coolantMode = PathUtil.coolantModeForOp(obj)
|
||||
coolantMode = coolantModeForOp(obj)
|
||||
|
||||
# turn coolant on if required
|
||||
if OUTPUT_COMMENTS:
|
||||
@@ -267,6 +336,13 @@ def export(objectslist, filename, argstring):
|
||||
gcode += linenumber() + "(COOLANT OFF:" + coolantMode.upper() + ")\n"
|
||||
gcode += linenumber() + "M9" + "\n"
|
||||
|
||||
if END_SPINDLE_EMPTY:
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += "(BEGIN MAKING SPINDLE EMPTY)\n"
|
||||
gcode += linenumber() + "M05\n"
|
||||
for line in TOOL_CHANGE.splitlines(True):
|
||||
gcode += linenumber() + line
|
||||
gcode += linenumber() + "M6 T0\n"
|
||||
# do the post_amble
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += "(BEGIN POSTAMBLE)\n"
|
||||
@@ -276,7 +352,13 @@ def export(objectslist, filename, argstring):
|
||||
|
||||
if FreeCAD.GuiUp and SHOW_EDITOR:
|
||||
dia = PostUtils.GCodeEditorDialog()
|
||||
dia.editor.setPlainText(gcode)
|
||||
|
||||
# Workaround for 1.1 while we wait for
|
||||
# https://github.com/FreeCAD/FreeCAD/pull/26008 to be merged.
|
||||
if hasattr(dia.editor, "setPlainText"):
|
||||
dia.editor.setPlainText(gcode)
|
||||
else:
|
||||
dia.editor.setText(gcode)
|
||||
result = dia.exec_()
|
||||
if result:
|
||||
final = dia.editor.toPlainText()
|
||||
@@ -392,6 +474,7 @@ def parse(pathobj):
|
||||
for index, c in enumerate(commands):
|
||||
|
||||
outstring = []
|
||||
outsuffix = []
|
||||
command = c.Name
|
||||
if index + 1 == len(commands):
|
||||
nextcommand = ""
|
||||
@@ -409,17 +492,19 @@ def parse(pathobj):
|
||||
if command == "G0":
|
||||
continue
|
||||
|
||||
# if it's a tap, we rigid tap, so don't start the spindle yet...
|
||||
# if tool a tap, we thread tap, so stop the spindle for now.
|
||||
# This only trigger when pathobj is a ToolController.
|
||||
if command == "M03" or command == "M3":
|
||||
if pathobj.Tool.ShapeID.lower() == "tap":
|
||||
if hasattr(pathobj, "Tool") and pathobj.Tool.ShapeName.lower() == "tap":
|
||||
tapSpeed = int(pathobj.SpindleSpeed)
|
||||
continue
|
||||
|
||||
# convert drill cycles to tap cycles if tool is a tap
|
||||
# Convert drill cycles to tap cycles if tool is a tap.
|
||||
# This only trigger when pathobj is a Operation.
|
||||
if command == "G81" or command == "G83":
|
||||
if (
|
||||
hasattr(pathobj, "ToolController")
|
||||
and pathobj.ToolController.Tool.ShapeID.lower() == "tap"
|
||||
and pathobj.ToolController.Tool.ShapeName.lower() == "tap"
|
||||
):
|
||||
command = "G84"
|
||||
out += linenumber() + "G95\n"
|
||||
@@ -569,14 +654,14 @@ def parse(pathobj):
|
||||
# Check for Tool Change:
|
||||
if command == "M6":
|
||||
# stop the spindle
|
||||
out += linenumber() + "M5\n"
|
||||
out += linenumber() + "M05\n"
|
||||
for line in TOOL_CHANGE.splitlines(True):
|
||||
out += linenumber() + line
|
||||
|
||||
# add height offset
|
||||
if USE_TLO:
|
||||
tool_height = "\nG43 H" + str(int(c.Parameters["T"]))
|
||||
outstring.append(tool_height)
|
||||
outsuffix.append("G91 G0 G43 G54 Z-[#[2000+#4120]] H#4120")
|
||||
outsuffix.append("G90")
|
||||
|
||||
if command == "message":
|
||||
if OUTPUT_COMMENTS is False:
|
||||
@@ -590,9 +675,11 @@ def parse(pathobj):
|
||||
outstring.insert(0, (linenumber()))
|
||||
|
||||
# append the line to the final output
|
||||
for w in outstring:
|
||||
out += w.upper() + COMMAND_SPACE
|
||||
out += COMMAND_SPACE.join(outstring).upper()
|
||||
out = out.strip() + "\n"
|
||||
if len(outsuffix) >= 1:
|
||||
for line in outsuffix:
|
||||
out += linenumber() + line + "\n"
|
||||
|
||||
return out
|
||||
|
||||
|
||||
@@ -101,6 +101,7 @@ from CAMTests.TestPathVoronoi import TestPathVoronoi
|
||||
|
||||
from CAMTests.TestGenericPost import TestGenericPost
|
||||
from CAMTests.TestLinuxCNCPost import TestLinuxCNCPost
|
||||
from CAMTests.TestFanucPost import TestFanucPost
|
||||
from CAMTests.TestGrblPost import TestGrblPost
|
||||
from CAMTests.TestMassoG3Post import TestMassoG3Post
|
||||
from CAMTests.TestCentroidPost import TestCentroidPost
|
||||
|
||||
Reference in New Issue
Block a user