From 020f721ec2d8442e6a615afaa511e1db208081d3 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Sun, 7 Aug 2022 16:55:22 -0700 Subject: [PATCH] Fixed external thread milling orientation and direction --- src/Mod/Path/PathScripts/PathThreadMilling.py | 221 ++++++++++-------- .../Path/PathScripts/PathThreadMillingGui.py | 14 +- .../Path/PathTests/TestPathThreadMilling.py | 53 +++++ 3 files changed, 180 insertions(+), 108 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathThreadMilling.py b/src/Mod/Path/PathScripts/PathThreadMilling.py index 7ce1ead63e..e62b6ecb97 100644 --- a/src/Mod/Path/PathScripts/PathThreadMilling.py +++ b/src/Mod/Path/PathScripts/PathThreadMilling.py @@ -48,6 +48,104 @@ else: translate = FreeCAD.Qt.translate +# Constants +LeftHand = "LeftHand" +RightHand = "RightHand" +ThreadTypeCustomExternal = "CustomExternal" +ThreadTypeCustomInternal = "CustomInternal" +ThreadTypeImperialExternal2A = "ImperialExternal2A" +ThreadTypeImperialExternal3A = "ImperialExternal3A" +ThreadTypeImperialInternal2B = "ImperialInternal2B" +ThreadTypeImperialInternal3B = "ImperialInternal3B" +ThreadTypeMetricExternal4G6G = "MetricExternal4G6G" +ThreadTypeMetricExternal6G = "MetricExternal6G" +ThreadTypeMetricInternal6H = "MetricInternal6H" +DirectionClimb = "Climb" +DirectionConventional = "Conventional" + +ThreadOrientations = [LeftHand, RightHand] + +ThreadTypeData = { + ThreadTypeImperialExternal2A: "imperial-external-2A.csv", + ThreadTypeImperialExternal3A: "imperial-external-3A.csv", + ThreadTypeImperialInternal2B: "imperial-internal-2B.csv", + ThreadTypeImperialInternal3B: "imperial-internal-3B.csv", + ThreadTypeMetricExternal4G6G: "metric-external-4G6G.csv", + ThreadTypeMetricExternal6G: "metric-external-6G.csv", + ThreadTypeMetricInternal6H: "metric-internal-6H.csv", +} + +ThreadTypesExternal = [ + ThreadTypeCustomExternal, + ThreadTypeImperialExternal2A, + ThreadTypeImperialExternal3A, + ThreadTypeMetricExternal4G6G, + ThreadTypeMetricExternal6G, +] +ThreadTypesInternal = [ + ThreadTypeCustomInternal, + ThreadTypeImperialInternal2B, + ThreadTypeImperialInternal3B, + ThreadTypeMetricInternal6H, +] +ThreadTypesImperial = [ + ThreadTypeImperialExternal2A, + ThreadTypeImperialExternal3A, + ThreadTypeImperialInternal2B, + ThreadTypeImperialInternal3B, +] +ThreadTypesMetric = [ + ThreadTypeMetricExternal4G6G, + ThreadTypeMetricExternal6G, + ThreadTypeMetricInternal6H, +] +ThreadTypes = ThreadTypesInternal + ThreadTypesExternal +Directions = [DirectionClimb, DirectionConventional] + +def _isThreadInternal(obj): + return obj.ThreadType in ThreadTypesInternal + +def threadSetupInternal(obj, zTop, zBottom): + PathLog.track() + if obj.ThreadOrientation == RightHand: + # Right hand thread, G2, top down -> conventional milling + if obj.Direction == DirectionConventional: + return ("G2", zTop, zBottom) + # For climb milling we need to cut the thread from the bottom up + # in the opposite direction -> G3 + return ("G3", zBottom, zTop) + # Left hand thread, G3, top down -> climb milling + if obj.Direction == DirectionClimb: + return ("G3", zTop, zBottom) + # for conventional milling, cut bottom up with G2 + return ("G2", zBottom, zTop) + +def threadSetupExternal(obj, zTop, zBottom): + PathLog.track() + if obj.ThreadOrientation == RightHand: + # right hand thread, G2, top down -> climb milling + if obj.Direction == DirectionClimb: + return ("G2", zTop, zBottom) + # for conventional, mill bottom up the other way around + return ("G3", zBottom, zTop) + # left hand thread, G3, top down -> conventional milling + if obj.Direction == DirectionConventional: + return ("G3", zTop, zBottom) + # for climb milling need to go bottom up and the other way + return ("G2", zBottom, zTop) + +def threadSetup(obj): + """Return (cmd, zbegin, zend) of thread milling operation""" + PathLog.track() + + zTop = obj.StartDepth.Value + zBottom = obj.FinalDepth.Value + + if _isThreadInternal(obj): + return threadSetupInternal(obj, zTop, zBottom) + else: + return threadSetupExternal(obj, zTop, zBottom) + def threadRadii(internal, majorDia, minorDia, toolDia, toolCrest): """threadRadii(majorDia, minorDia, toolDia, toolCrest) ... returns the minimum and maximum radius for thread.""" @@ -121,59 +219,6 @@ def elevatorRadius(obj, center, internal, tool): class ObjectThreadMilling(PathCircularHoleBase.ObjectOp): """Proxy object for thread milling operation.""" - LeftHand = "LeftHand" - RightHand = "RightHand" - ThreadTypeCustomExternal = "CustomExternal" - ThreadTypeCustomInternal = "CustomInternal" - ThreadTypeImperialExternal2A = "ImperialExternal2A" - ThreadTypeImperialExternal3A = "ImperialExternal3A" - ThreadTypeImperialInternal2B = "ImperialInternal2B" - ThreadTypeImperialInternal3B = "ImperialInternal3B" - ThreadTypeMetricExternal4G6G = "MetricExternal4G6G" - ThreadTypeMetricExternal6G = "MetricExternal6G" - ThreadTypeMetricInternal6H = "MetricInternal6H" - DirectionClimb = "Climb" - DirectionConventional = "Conventional" - - ThreadOrientations = [LeftHand, RightHand] - - ThreadTypeData = { - ThreadTypeImperialExternal2A: "imperial-external-2A.csv", - ThreadTypeImperialExternal3A: "imperial-external-3A.csv", - ThreadTypeImperialInternal2B: "imperial-internal-2B.csv", - ThreadTypeImperialInternal3B: "imperial-internal-3B.csv", - ThreadTypeMetricExternal4G6G: "metric-external-4G6G.csv", - ThreadTypeMetricExternal6G: "metric-external-6G.csv", - ThreadTypeMetricInternal6H: "metric-internal-6H.csv", - } - - ThreadTypesExternal = [ - ThreadTypeCustomExternal, - ThreadTypeImperialExternal2A, - ThreadTypeImperialExternal3A, - ThreadTypeMetricExternal4G6G, - ThreadTypeMetricExternal6G, - ] - ThreadTypesInternal = [ - ThreadTypeCustomInternal, - ThreadTypeImperialInternal2B, - ThreadTypeImperialInternal3B, - ThreadTypeMetricInternal6H, - ] - ThreadTypesImperial = [ - ThreadTypeImperialExternal2A, - ThreadTypeImperialExternal3A, - ThreadTypeImperialInternal2B, - ThreadTypeImperialInternal3B, - ] - ThreadTypesMetric = [ - ThreadTypeMetricExternal4G6G, - ThreadTypeMetricExternal6G, - ThreadTypeMetricInternal6H, - ] - ThreadTypes = ThreadTypesInternal + ThreadTypesExternal - Directions = [DirectionClimb, DirectionConventional] - @classmethod def propertyEnumerations(self, dataType="data"): """helixOpPropertyEnumerations(dataType="data")... return property enumeration lists of specified dataType. @@ -191,59 +236,59 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp): "ThreadType": [ ( translate("Path_ThreadMilling", "Custom External"), - ObjectThreadMilling.ThreadTypeCustomExternal, + ThreadTypeCustomExternal, ), ( translate("Path_ThreadMilling", "Custom Internal"), - ObjectThreadMilling.ThreadTypeCustomInternal, + ThreadTypeCustomInternal, ), ( translate("Path_ThreadMilling", "Imperial External (2A)"), - ObjectThreadMilling.ThreadTypeImperialExternal2A, + ThreadTypeImperialExternal2A, ), ( translate("Path_ThreadMilling", "Imperial External (3A)"), - ObjectThreadMilling.ThreadTypeImperialExternal3A, + ThreadTypeImperialExternal3A, ), ( translate("Path_ThreadMilling", "Imperial Internal (2B)"), - ObjectThreadMilling.ThreadTypeImperialInternal2B, + ThreadTypeImperialInternal2B, ), ( translate("Path_ThreadMilling", "Imperial Internal (3B)"), - ObjectThreadMilling.ThreadTypeImperialInternal3B, + ThreadTypeImperialInternal3B, ), ( translate("Path_ThreadMilling", "Metric External (4G6G)"), - ObjectThreadMilling.ThreadTypeMetricExternal4G6G, + ThreadTypeMetricExternal4G6G, ), ( translate("Path_ThreadMilling", "Metric External (6G)"), - ObjectThreadMilling.ThreadTypeMetricExternal6G, + ThreadTypeMetricExternal6G, ), ( translate("Path_ThreadMilling", "Metric Internal (6H)"), - ObjectThreadMilling.ThreadTypeMetricInternal6H, + ThreadTypeMetricInternal6H, ), ], "ThreadOrientation": [ ( translate("Path_ThreadMilling", "LeftHand"), - ObjectThreadMilling.LeftHand, + LeftHand, ), ( translate("Path_ThreadMilling", "RightHand"), - ObjectThreadMilling.RightHand, + RightHand, ), ], "Direction": [ ( translate("Path_ThreadMilling", "Climb"), - ObjectThreadMilling.DirectionClimb, + DirectionClimb, ), ( translate("Path_ThreadMilling", "Conventional"), - ObjectThreadMilling.DirectionConventional, + DirectionConventional, ), ], } @@ -274,14 +319,14 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp): "Thread", QT_TRANSLATE_NOOP("App::Property", "Set thread orientation"), ) - # obj.ThreadOrientation = self.ThreadOrientations + # obj.ThreadOrientation = ThreadOrientations obj.addProperty( "App::PropertyEnumeration", "ThreadType", "Thread", QT_TRANSLATE_NOOP("App::Property", "Currently only internal"), ) - # obj.ThreadType = self.ThreadTypes + # obj.ThreadType = ThreadTypes obj.addProperty( "App::PropertyString", "ThreadName", @@ -362,32 +407,6 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp): for n in self.propertyEnumerations(): setattr(obj, n[0], n[1]) - def _isThreadInternal(self, obj): - return obj.ThreadType in self.ThreadTypesInternal - - def _threadSetupInternal(self, obj): - PathLog.track() - # the thing to remember is that Climb, for an internal thread must always be G3 - if obj.Direction == self.DirectionClimb: - if obj.ThreadOrientation == self.RightHand: - return ("G3", obj.FinalDepth.Value, obj.StartDepth.Value) - return ("G3", obj.StartDepth.Value, obj.FinalDepth.Value) - if obj.ThreadOrientation == self.RightHand: - return ("G2", obj.StartDepth.Value, obj.FinalDepth.Value) - return ("G2", obj.FinalDepth.Value, obj.StartDepth.Value) - - def threadSetup(self, obj): - PathLog.track() - cmd, zbegin, zend = self._threadSetupInternal(obj) - - if obj.ThreadType in self.ThreadTypesInternal: - return (cmd, zbegin, zend) - - # need to reverse direction for external threads - if cmd == "G2": - return ("G3", zbegin, zend) - return ("G2", zbegin, zend) - def threadPassRadii(self, obj): PathLog.track(obj.Label) rMajor = (obj.MajorDiameter.Value - self.tool.Diameter) / 2.0 @@ -402,7 +421,7 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp): def executeThreadMill(self, obj, loc, gcode, zStart, zFinal, pitch): PathLog.track(obj.Label, loc, gcode, zStart, zFinal, pitch) - elevator = elevatorRadius(obj, loc, self._isThreadInternal(obj), self.tool) + elevator = elevatorRadius(obj, loc, _isThreadInternal(obj), self.tool) move2clearance = Path.Command( "G0", {"Z": obj.ClearanceHeight.Value, "F": self.vertRapid} @@ -413,7 +432,7 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp): for radius in threadPasses( obj.Passes, threadRadii, - self._isThreadInternal(obj), + _isThreadInternal(obj), obj.MajorDiameter.Value, obj.MinorDiameter.Value, float(self.tool.Diameter), @@ -421,7 +440,7 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp): ): if ( not start is None - and not self._isThreadInternal(obj) + and not _isThreadInternal(obj) and not obj.LeadInOut ): # external thread without lead in/out have to go up and over @@ -459,7 +478,7 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp): if self.isToolSupported(obj, self.tool): self.commandlist.append(Path.Command("(Begin Thread Milling)")) - (cmd, zStart, zFinal) = self.threadSetup(obj) + (cmd, zStart, zFinal) = threadSetup(obj) pitch = obj.Pitch.Value if obj.TPI > 0: pitch = 25.4 / obj.TPI @@ -482,13 +501,13 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp): def opSetDefaultValues(self, obj, job): PathLog.track() - obj.ThreadOrientation = self.RightHand - obj.ThreadType = self.ThreadTypeMetricInternal6H + obj.ThreadOrientation = RightHand + obj.ThreadType = ThreadTypeMetricInternal6H obj.ThreadFit = 50 obj.Pitch = 1 obj.TPI = 0 obj.Passes = 1 - obj.Direction = self.DirectionClimb + obj.Direction = DirectionClimb obj.LeadInOut = False def isToolSupported(self, obj, tool): diff --git a/src/Mod/Path/PathScripts/PathThreadMillingGui.py b/src/Mod/Path/PathScripts/PathThreadMillingGui.py index 7b98ebe34e..54ae450a15 100644 --- a/src/Mod/Path/PathScripts/PathThreadMillingGui.py +++ b/src/Mod/Path/PathScripts/PathThreadMillingGui.py @@ -140,32 +140,32 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): def _isThreadCustom(self): return self.form.threadType.currentData() in [ - PathThreadMilling.ObjectThreadMilling.ThreadTypeCustomInternal, - PathThreadMilling.ObjectThreadMilling.ThreadTypeCustomExternal, + PathThreadMilling.ThreadTypeCustomInternal, + PathThreadMilling.ThreadTypeCustomExternal, ] def _isThreadImperial(self): return ( self.form.threadType.currentData() - in PathThreadMilling.ObjectThreadMilling.ThreadTypesImperial + in PathThreadMilling.ThreadTypesImperial ) def _isThreadMetric(self): return ( self.form.threadType.currentData() - in PathThreadMilling.ObjectThreadMilling.ThreadTypesMetric + in PathThreadMilling.ThreadTypesMetric ) def _isThreadInternal(self): return ( self.form.threadType.currentData() - in PathThreadMilling.ObjectThreadMilling.ThreadTypesInternal + in PathThreadMilling.ThreadTypesInternal ) def _isThreadExternal(self): return ( self.form.threadType.currentData() - in PathThreadMilling.ObjectThreadMilling.ThreadTypesExternal + in PathThreadMilling.ThreadTypesExternal ) def _updateFromThreadType(self): @@ -195,7 +195,7 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): self.pitch.updateSpinBox(0) fillThreads( self.form, - PathThreadMilling.ObjectThreadMilling.ThreadTypeData[ + PathThreadMilling.ThreadTypeData[ self.form.threadType.currentData() ], self.obj.ThreadName, diff --git a/src/Mod/Path/PathTests/TestPathThreadMilling.py b/src/Mod/Path/PathTests/TestPathThreadMilling.py index fcfb49f1c1..1355214ef9 100644 --- a/src/Mod/Path/PathTests/TestPathThreadMilling.py +++ b/src/Mod/Path/PathTests/TestPathThreadMilling.py @@ -28,6 +28,14 @@ import math from PathTests.PathTestUtils import PathTestBase +class TestObject(object): + + def __init__(self, orientation, direction, zTop, zBottom): + self.ThreadOrientation = orientation + self.Direction = direction + self.StartDepth = FreeCAD.Units.Quantity(zTop, FreeCAD.Units.Length) + self.FinalDepth = FreeCAD.Units.Quantity(zBottom, FreeCAD.Units.Length) + def radii(internal, major, minor, toolDia, toolCrest): """test radii function for simple testing""" return (minor, major) @@ -45,6 +53,18 @@ class TestPathThreadMilling(PathTestBase): for i in range(len(have)): self.assertRoughly(have[i], want[i]) + def assertSetupInternal(self, obj, c, begin, end): + cmd, zBegin, zEnd = PathThreadMilling.threadSetupInternal(obj, obj.StartDepth.Value, obj.FinalDepth.Value) + self.assertEqual(cmd, c) + self.assertEqual(zBegin, begin) + self.assertEqual(zEnd, end) + + def assertSetupExternal(self, obj, c, begin, end): + cmd, zBegin, zEnd = PathThreadMilling.threadSetupExternal(obj, obj.StartDepth.Value, obj.FinalDepth.Value) + self.assertEqual(cmd, c) + self.assertEqual(zBegin, begin) + self.assertEqual(zEnd, end) + def test00(self): """Verify internal radii.""" self.assertRadii(PathThreadMilling.threadRadii(True, 20, 18, 2, 0), (8, 9.2)) @@ -92,3 +112,36 @@ class TestPathThreadMilling(PathTestBase): PathThreadMilling.threadPasses(5, radii, False, 10, 9, 0, 0), [9.552786, 9.367544, 9.225403, 9.105573, 9], ) + + def test40(self): + """Verify internal right hand thread setup.""" + + hand = PathThreadMilling.RightHand + + self.assertSetupInternal(TestObject(hand, PathThreadMilling.DirectionConventional, 1, 0), "G2", 1, 0) + self.assertSetupInternal(TestObject(hand, PathThreadMilling.DirectionClimb, 1, 0), "G3", 0, 1) + + def test41(self): + """Verify internal left hand thread setup.""" + + hand = PathThreadMilling.LeftHand + + self.assertSetupInternal(TestObject(hand, PathThreadMilling.DirectionConventional, 1, 0), "G2", 0, 1) + self.assertSetupInternal(TestObject(hand, PathThreadMilling.DirectionClimb, 1, 0), "G3", 1, 0) + + def test50(self): + """Verify exteranl right hand thread setup.""" + + hand = PathThreadMilling.RightHand + + self.assertSetupExternal(TestObject(hand, PathThreadMilling.DirectionClimb, 1, 0), "G2", 1, 0) + self.assertSetupExternal(TestObject(hand, PathThreadMilling.DirectionConventional, 1, 0), "G3", 0, 1) + + def test51(self): + """Verify exteranl left hand thread setup.""" + + hand = PathThreadMilling.LeftHand + + self.assertSetupExternal(TestObject(hand, PathThreadMilling.DirectionClimb, 1, 0), "G2", 0, 1) + self.assertSetupExternal(TestObject(hand, PathThreadMilling.DirectionConventional, 1, 0), "G3", 1, 0) +