Merge pull request #27860 from petterreinholdtsen/cam-fanuc-post-crash-fix

CAM: Implemented Fanuc thread tapping and fixed crash in post processor.
This commit is contained in:
sliptonic
2026-03-02 10:59:12 -06:00
committed by GitHub
3 changed files with 97 additions and 80 deletions
+5 -1
View File
@@ -30,7 +30,7 @@ import Path
class MockTool:
def __init__(self):
self.ShapeName = "endmill"
self.ShapeType = "endmill"
class MockToolController:
@@ -56,6 +56,7 @@ class MockToolController:
[Path.Command(f"M6 T{tool_number}"), Path.Command(f"M3 S{spindle_speed}")]
)
@property
def InList(self):
return []
@@ -72,6 +73,7 @@ class MockOperation:
# Create an empty path by default
self.Path = Path.Path()
@property
def InList(self):
"""Mock InList - operations belong to a job."""
return []
@@ -136,7 +138,9 @@ class MockJob:
self.Fixtures = ["G54"]
self.OrderOutputBy = "Tool"
self.SplitOutput = False
self.TypeId = "dummy"
@property
def InList(self):
"""Mock InList for fixture setup."""
return []
+9 -11
View File
@@ -95,7 +95,7 @@ class TestFanucPost(PathTestUtils.PathTestBase):
# Test without header
expected = """%
(BEGIN PREAMBLE)
G17 G54 G40 G49 G80 G90
G17 G54 G40 G49 G80 G90 G94
G21
(BEGIN OPERATION: TC: DEFAULT TOOL)
(MACHINE UNITS: MM/MIN)
@@ -130,7 +130,7 @@ M30
# test without comments
expected = """%
G17 G54 G40 G49 G80 G90
G17 G54 G40 G49 G80 G90 G94
G21
M05
G28 G91 Z0
@@ -168,7 +168,7 @@ M30
# Test without header
expected = """%
(BEGIN PREAMBLE)
G17 G54 G40 G49 G80 G90
G17 G54 G40 G49 G80 G90 G94
G21
(BEGIN OPERATION: TC: DEFAULT TOOL)
(MACHINE UNITS: MM/MIN)
@@ -207,7 +207,7 @@ M30
# test without comments
expected = """%
G17 G54 G40 G49 G80 G90
G17 G54 G40 G49 G80 G90 G94
G21
M05
G28 G91 Z0
@@ -336,18 +336,16 @@ M30
Test threading using drill cycle converted to tapping
"""
self.tool_controller.Tool.ShapeName = "tap"
self.tool_controller.Tool.ShapeType = "tap"
c = Path.Command("G0 X10 Y10")
c2 = Path.Command("G81 X10 Y10 Z-10 R20 F1 P1 Q1")
c2 = Path.Command("G84 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")
self.assertEqual(gcode.splitlines()[19], "M29 S1000")
self.assertEqual(gcode.splitlines()[20], "G84 Z-10.000 R20.000 F1000.000 P1.000 Q1.000")
self.assertEqual(gcode.splitlines()[21], "G80")
def test_comment(self):
"""
+83 -68
View File
@@ -50,7 +50,7 @@ fanuc_post.export(object,"/path/to/file.ncc","")
"""
# Preamble text will appear at the beginning of the GCODE output file.
DEFAULT_PREAMBLE = """G17 G54 G40 G49 G80 G90
DEFAULT_PREAMBLE = """G17 G54 G40 G49 G80 G90 G94
"""
# Postamble text will appear following the last operation.
@@ -152,7 +152,7 @@ TOOL_CHANGE = """G28 G91 Z0
# List of drill G codes where some parameters are required and their
# required parameters.
DRILL_OPERATION = ("G73", "G81", "G82", "G83", "G84", "G85")
DRILL_OPERATION = ("G73", "G81", "G82", "G83", "G85")
DRILL_PARAM_REQ = ("L", "P", "Q", "R", "Z")
@@ -507,85 +507,100 @@ def parse(pathobj):
if command == "G0":
continue
# if tool a tap, we thread tap, so stop the spindle for now.
# This only trigger when pathobj is a ToolController.
# If tool a tap, we will thread tap, so stop the spindle
# for now as there is no point in starting it to stop it
# in the G74/G84 operation after S29. This only trigger
# when pathobj is a ToolController.
if command == "M03" or command == "M3":
if hasattr(pathobj, "Tool") and pathobj.Tool.ShapeName.lower() == "tap":
if (
hasattr(pathobj, "Tool")
and getattr(pathobj.Tool, "ShapeType", "").lower() == "tap"
):
tapSpeed = int(pathobj.SpindleSpeed)
continue
# Convert drill cycles to tap cycles if tool is a tap.
# Handle thread tapping cycles. Uses rigid tapping.
# This only trigger when pathobj is a Operation.
if command == "G81" or command == "G83":
if (
hasattr(pathobj, "ToolController")
and pathobj.ToolController.Tool.ShapeName.lower() == "tap"
):
command = "G84"
out += linenumber() + "G95\n"
paramstring = ""
for param in ["X", "Y"]:
if param in c.Parameters:
if (
(not OUTPUT_DOUBLES)
and (param in currLocation)
and (currLocation[param] == c.Parameters[param])
):
continue
else:
pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
paramstring += (
" "
+ param
+ format(
float(pos.getValueAs(UNIT_FORMAT)),
precision_string,
)
)
if paramstring != "":
out += linenumber() + "G00" + paramstring + "\n"
if "S" in c.Parameters:
tapSpeed = int(c.Parameters["S"])
out += "M29 S" + str(tapSpeed) + "\n"
for param in ["Z", "R"]:
if param in c.Parameters:
if (
(not OUTPUT_DOUBLES)
and (param in currLocation)
and (currLocation[param] == c.Parameters[param])
):
continue
else:
pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
paramstring += (
" "
+ param
+ format(
float(pos.getValueAs(UNIT_FORMAT)),
precision_string,
)
)
# in this mode, F is the distance per revolution of the thread (pitch)
# P is the dwell time in seconds at the bottom of the thread
# Q is the peck depth of the threading operation
for param in ["F", "P", "Q"]:
if param in c.Parameters:
value = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
if command == "G74" or command == "G84":
pitch_mm = float(c.Parameters["F"])
# Convert pitch to inches if needed
if UNITS == "G20": # imperial
pitch = pitch_mm / 25.4
else:
pitch = pitch_mm
paramstring = ""
for param in ["X", "Y"]:
if param in c.Parameters:
if (
(not OUTPUT_DOUBLES)
and (param in currLocation)
and (currLocation[param] == c.Parameters[param])
):
continue
else:
pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
paramstring += (
" "
+ param
+ format(
float(value.getValueAs(UNIT_FORMAT)),
float(pos.getValueAs(UNIT_FORMAT)),
precision_string,
)
)
if paramstring != "":
out += linenumber() + "G00" + paramstring + "\n"
if "S" in c.Parameters:
tapSpeed = int(c.Parameters["S"])
out += "M29 S" + str(tapSpeed) + "\n"
for param in ["Z", "R"]:
if param in c.Parameters:
if (
(not OUTPUT_DOUBLES)
and (param in currLocation)
and (currLocation[param] == c.Parameters[param])
):
continue
else:
pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
paramstring += (
" "
+ param
+ format(
float(pos.getValueAs(UNIT_FORMAT)),
precision_string,
)
)
out += linenumber() + "G84" + paramstring + "\n"
out += linenumber() + "G80\n"
out += linenumber() + "G94\n"
continue
# Calculate feed rate as distance per minute
if tapSpeed is not None:
feed_rate = pitch * tapSpeed
speed = Units.Quantity(feed_rate, UNIT_SPEED_FORMAT)
paramstring += " F" + format(
float(speed.getValueAs(UNIT_SPEED_FORMAT)), precision_string
)
else:
# No spindle speed found, output pitch as F
paramstring += " F" + format(pitch, precision_string)
# P is the dwell time in seconds at the bottom of the thread
# Q is the peck depth of the threading operation
for param in ["P", "Q"]:
if param in c.Parameters:
value = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
paramstring += (
" "
+ param
+ format(
float(value.getValueAs(UNIT_FORMAT)),
precision_string,
)
)
out += linenumber() + command + paramstring + "\n"
out += linenumber() + "G80\n" # End tapping cycle
continue
outstring.append(command)