diff --git a/src/Mod/CAM/CAMTests/PostTestMocks.py b/src/Mod/CAM/CAMTests/PostTestMocks.py index c0b7506de0..a112b77cfb 100644 --- a/src/Mod/CAM/CAMTests/PostTestMocks.py +++ b/src/Mod/CAM/CAMTests/PostTestMocks.py @@ -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 [] diff --git a/src/Mod/CAM/CAMTests/TestFanucPost.py b/src/Mod/CAM/CAMTests/TestFanucPost.py index d1f320d3f5..12724e7227 100644 --- a/src/Mod/CAM/CAMTests/TestFanucPost.py +++ b/src/Mod/CAM/CAMTests/TestFanucPost.py @@ -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): """ diff --git a/src/Mod/CAM/Path/Post/scripts/fanuc_post.py b/src/Mod/CAM/Path/Post/scripts/fanuc_post.py index 2d39558af1..62bb9eb1c8 100644 --- a/src/Mod/CAM/Path/Post/scripts/fanuc_post.py +++ b/src/Mod/CAM/Path/Post/scripts/fanuc_post.py @@ -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)