diff --git a/src/Mod/Path/PathScripts/PathThreadMilling.py b/src/Mod/Path/PathScripts/PathThreadMilling.py index 5dea982a93..6b82a595ee 100644 --- a/src/Mod/Path/PathScripts/PathThreadMilling.py +++ b/src/Mod/Path/PathScripts/PathThreadMilling.py @@ -115,10 +115,10 @@ def comment(path, msg): if False: path.append(Path.Command("(------- {} -------)".format(msg))) -class _ThreadInternal(object): +class _Thread(object): """Helper class for dealing with different thread types""" - def __init__(self, cmd, zStart, zFinal, pitch): + def __init__(self, cmd, zStart, zFinal, pitch, internal): self.cmd = cmd if zStart < zFinal: self.pitch = pitch @@ -127,6 +127,7 @@ class _ThreadInternal(object): self.hPitch = self.pitch / 2 self.zStart = zStart self.zFinal = zFinal + self.internal = internal def overshoots(self, z): """overshoots(z) ... returns true if adding another half helix goes beyond the thread bounds""" @@ -153,11 +154,21 @@ class _ThreadInternal(object): def isUp(self): """isUp() ... returns True if the thread goes from the bottom up""" return self.pitch > 0 + + def g4Opposite(self): + return 'G2' if self.isG3() else 'G3' + def g4LeadInOut(self): + if self.internal: + return self.cmd + return self.g4Opposite() -def threadCommands(center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator): + def g4Start2Elevator(self): + return self.g4Opposite() + +def threadCommands(center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator, start): """threadCommands(center, cmd, zStart, zFinal, pitch, radius) ... returns the g-code to mill the given internal thread""" - thread = _ThreadInternal(cmd, zStart, zFinal, pitch) + thread = _Thread(cmd, zStart, zFinal, pitch, radius > elevator) yMin = center.y - radius yMax = center.y + radius @@ -166,13 +177,21 @@ def threadCommands(center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevat # at this point the tool is at a safe heiht (depending on the previous thread), so we can move # into position first, and then drop to the start height. If there is any material in the way this # op hasn't been setup properly. - path.append(Path.Command("G0", {"X": center.x, "Y": center.y + elevator})) - path.append(Path.Command("G0", {"Z": thread.zStart})) if leadInOut: comment(path, 'lead-in') - path.append(Path.Command(thread.cmd, {"Y": yMax, "J": (yMax - (center.y + elevator)) / 2})) + if start is None: + path.append(Path.Command("G0", {"X": center.x, "Y": center.y + elevator})) + path.append(Path.Command("G0", {"Z": thread.zStart})) + else: + path.append(Path.Command(thread.g4Start2Elevator(), {"X": center.x, "Y": center.y + elevator, "Z" : thread.zStart, "I": (center.x - start.x) / 2, "J": (center.y + elevator - start.y) / 2, "K" : (thread.zStart - start.z) / 2})) + path.append(Path.Command(thread.g4LeadInOut(), {"Y": yMax, "J": (yMax - (center.y + elevator)) / 2})) comment(path, 'lead-in') else: + if start is None: + path.append(Path.Command("G0", {"X": center.x, "Y": center.y + elevator})) + path.append(Path.Command("G0", {"Z": thread.zStart})) + else: + path.append(Path.Command("G0", {"X": center.x, "Y": center.y + elevator, "Z": thread.zStart})) path.append(Path.Command("G1", {"Y": yMax})) z = thread.zStart @@ -220,7 +239,7 @@ def threadCommands(center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevat comment(path, 'lead-out') path.append( Path.Command( - thread.cmd, + thread.g4LeadInOut(), {"X": elevatorX, "Y": elevatorY, "I": -dx / 2, "J": -dy / 2}, ) ) @@ -228,7 +247,7 @@ def threadCommands(center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevat else: path.append(Path.Command("G1", {"X": elevatorX, "Y": elevatorY})) - return path + return (path, FreeCAD.Vector(elevatorX, elevatorY, thread.zFinal)) @@ -479,10 +498,10 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp): PathLog.track(obj.Label, loc, gcode, zStart, zFinal, pitch) elevator = elevatorRadius(obj, loc, self._isThreadInternal(obj), self.tool) - self.commandlist.append( - Path.Command("G0", {"Z": obj.ClearanceHeight.Value, "F": self.vertRapid}) - ) + move2clearance = Path.Command("G0", {"Z": obj.ClearanceHeight.Value, "F": self.vertRapid}) + self.commandlist.append(move2clearance) + start = None for radius in threadPasses( obj.Passes, threadRadii, @@ -492,7 +511,13 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp): float(self.tool.Diameter), float(self.tool.Crest), ): - commands = threadCommands(loc, gcode, zStart, zFinal, pitch, radius, obj.LeadInOut, elevator) + if not start is None and not self._isThreadInternal(obj) and not obj.LeadInOut: + # external thread without lead in/out have to go up and over + # in other words we need a move to clearance and not take any + # shortcuts when moving to the elevator position + self.commandlist.append(move2clearance) + start = None + commands, start = threadCommands(loc, gcode, zStart, zFinal, pitch, radius, obj.LeadInOut, elevator, start) for cmd in commands: p = cmd.Parameters diff --git a/src/Mod/Path/PathTests/TestPathThreadMilling.py b/src/Mod/Path/PathTests/TestPathThreadMilling.py index 5823bd23c6..0cf8ac46b7 100644 --- a/src/Mod/Path/PathTests/TestPathThreadMilling.py +++ b/src/Mod/Path/PathTests/TestPathThreadMilling.py @@ -87,7 +87,8 @@ class TestPathThreadMilling(PathTestBase): leadInOut = False elevator = 2 - path = PathThreadMilling.threadCommands(center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator) + path, start = PathThreadMilling.threadCommands(center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator, None) + print(start) gcode = [ 'G0 X0.000000 Y2.000000', @@ -98,6 +99,7 @@ class TestPathThreadMilling(PathTestBase): 'G1 X0.000000 Y2.000000', ] self.assertEqual([p.toGCode() for p in path], gcode) + self.assertCoincide(start, FreeCAD.Vector(0, 2, 0)) def test41(self): '''Verify thread commands for a thwo threads''' @@ -111,7 +113,7 @@ class TestPathThreadMilling(PathTestBase): leadInOut = False elevator = 2 - path = PathThreadMilling.threadCommands(center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator) + path, start = PathThreadMilling.threadCommands(center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator, None) gcode = [ 'G0 X0.000000 Y2.000000', @@ -124,6 +126,7 @@ class TestPathThreadMilling(PathTestBase): 'G1 X0.000000 Y2.000000', ] self.assertEqual([p.toGCode() for p in path], gcode) + self.assertCoincide(start, FreeCAD.Vector(0, 2, 0)) def test42(self): '''Verify thread commands for a one and a half threads''' @@ -137,7 +140,7 @@ class TestPathThreadMilling(PathTestBase): leadInOut = False elevator = 2 - path = PathThreadMilling.threadCommands(center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator) + path, start = PathThreadMilling.threadCommands(center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator, None) gcode = [ 'G0 X0.000000 Y2.000000', @@ -149,6 +152,7 @@ class TestPathThreadMilling(PathTestBase): 'G1 X0.000000 Y-2.000000', ] self.assertEqual([p.toGCode() for p in path], gcode) + self.assertCoincide(start, FreeCAD.Vector(0, -2, 0)) def test43(self): '''Verify thread commands for a one and 3 quarter threads''' @@ -162,7 +166,7 @@ class TestPathThreadMilling(PathTestBase): leadInOut = False elevator = 2 - path = PathThreadMilling.threadCommands(center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator) + path, start = PathThreadMilling.threadCommands(center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator, None) gcode = [ 'G0 X0.000000 Y2.000000', @@ -177,6 +181,7 @@ class TestPathThreadMilling(PathTestBase): 'G1 X-2.000000 Y0.000000', ] self.assertEqual([p.toGCode() for p in path], gcode) + self.assertCoincide(start, FreeCAD.Vector(-2, 0, 0)) def test44(self): '''Verify thread commands for a one and 3 quarter threads - CCW''' @@ -190,7 +195,7 @@ class TestPathThreadMilling(PathTestBase): leadInOut = False elevator = 2 - path = PathThreadMilling.threadCommands(center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator) + path, start = PathThreadMilling.threadCommands(center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator, None) gcode = [ 'G0 X0.000000 Y2.000000', @@ -205,6 +210,7 @@ class TestPathThreadMilling(PathTestBase): 'G1 X2.000000 Y0.000000', ] self.assertEqual([p.toGCode() for p in path], gcode) + self.assertCoincide(start, FreeCAD.Vector(2, 0, 0)) def test50(self): '''Verify lead in/out commands for a single thread''' @@ -218,7 +224,7 @@ class TestPathThreadMilling(PathTestBase): leadInOut = True elevator = 2 - path = PathThreadMilling.threadCommands(center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator) + path, start = PathThreadMilling.threadCommands(center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator, None) gcode = [ 'G0 X0.000000 Y2.000000', @@ -233,6 +239,7 @@ class TestPathThreadMilling(PathTestBase): #'(------- lead-out -------)', ] self.assertEqual([p.toGCode() for p in path], gcode) + self.assertCoincide(start, FreeCAD.Vector(0, 2, 0)) def test51(self): '''Verify lead in/out commands for one and a half threads''' @@ -246,7 +253,7 @@ class TestPathThreadMilling(PathTestBase): leadInOut = True elevator = 2 - path = PathThreadMilling.threadCommands(center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator) + path, start = PathThreadMilling.threadCommands(center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator, None) gcode = [ 'G0 X0.000000 Y2.000000', @@ -262,3 +269,5 @@ class TestPathThreadMilling(PathTestBase): #'(------- lead-out -------)', ] self.assertEqual([p.toGCode() for p in path], gcode) + self.assertCoincide(start, FreeCAD.Vector(0, -2, 0)) +