Merge pull request #6485 from mlampert/feature/external-thread-milling
Path: Feature/external thread milling
This commit is contained in:
@@ -28,6 +28,7 @@ import PathScripts.PathCircularHoleBase as PathCircularHoleBase
|
||||
import PathScripts.PathGeom as PathGeom
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathScripts.PathOp as PathOp
|
||||
import Generators.threadmilling_generator as threadmilling
|
||||
import math
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
@@ -36,6 +37,9 @@ __author__ = "sliptonic (Brad Collette)"
|
||||
__url__ = "http://www.freecadweb.org"
|
||||
__doc__ = "Path thread milling operation."
|
||||
|
||||
# math.sqrt(3)/2 ... 60deg triangle height
|
||||
SQRT_3_DIVIDED_BY_2 = 0.8660254037844386
|
||||
|
||||
if False:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
@@ -45,8 +49,8 @@ else:
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
|
||||
def radiiInternal(majorDia, minorDia, toolDia, toolCrest=None):
|
||||
"""internlThreadRadius(majorDia, minorDia, toolDia, toolCrest) ... returns the maximum radius for thread."""
|
||||
def threadRadii(internal, majorDia, minorDia, toolDia, toolCrest):
|
||||
"""threadRadii(majorDia, minorDia, toolDia, toolCrest) ... returns the minimum and maximum radius for thread."""
|
||||
PathLog.track(majorDia, minorDia, toolDia, toolCrest)
|
||||
if toolCrest is None:
|
||||
toolCrest = 0.0
|
||||
@@ -57,118 +61,61 @@ def radiiInternal(majorDia, minorDia, toolDia, toolCrest=None):
|
||||
# Since we already have the outer diameter it's simpler to just add 1/8 * H
|
||||
# to get the outer tip of the thread.
|
||||
H = ((majorDia - minorDia) / 2.0) * 1.6 # (D - d)/2 = 5/8 * H
|
||||
outerTip = majorDia / 2.0 + H / 8.0
|
||||
if internal:
|
||||
# mill inside out
|
||||
outerTip = majorDia / 2.0 + H / 8.0
|
||||
# Compensate for the crest of the tool
|
||||
toolTip = outerTip - toolCrest * SQRT_3_DIVIDED_BY_2
|
||||
return ((minorDia - toolDia) / 2.0, toolTip - toolDia / 2.0)
|
||||
# mill outside in
|
||||
innerTip = minorDia / 2.0 - H / 4.0
|
||||
# Compensate for the crest of the tool
|
||||
toolTip = (
|
||||
outerTip - toolCrest * 0.8660254037844386
|
||||
) # math.sqrt(3)/2 ... 60deg triangle height
|
||||
return ((minorDia - toolDia) / 2.0, toolTip - toolDia / 2.0)
|
||||
toolTip = innerTip - toolCrest * SQRT_3_DIVIDED_BY_2
|
||||
return ((majorDia + toolDia) / 2.0, toolTip + toolDia / 2.0)
|
||||
|
||||
|
||||
def threadPasses(count, radii, majorDia, minorDia, toolDia, toolCrest=None):
|
||||
PathLog.track(count, radii, majorDia, minorDia, toolDia, toolCrest)
|
||||
minor, major = radii(majorDia, minorDia, toolDia, toolCrest)
|
||||
dr = float(major - minor) / count
|
||||
return [major - dr * (count - (i + 1)) for i in range(count)]
|
||||
def threadPasses(count, radii, internal, majorDia, minorDia, toolDia, toolCrest):
|
||||
PathLog.track(count, radii, internal, majorDia, minorDia, toolDia, toolCrest)
|
||||
# the logic goes as follows, total area to be removed:
|
||||
# A = H * W ... where H is the depth and W is half the width of a thread
|
||||
# H = k * sin(30) = k * 1/2 -> k = 2 * H
|
||||
# W = k * cos(30) = k * sqrt(3)/2
|
||||
# -> W = (2 * H) * sqrt(3) / 2 = H * sqrt(3)
|
||||
# A = sqrt(3) * H^2
|
||||
# Each pass has to remove the same area
|
||||
# An = A / count = sqrt(3) * H^2 / count
|
||||
# Because each successive pass doesn't have to remove the aera of the previous
|
||||
# passes the result for the height:
|
||||
# Ai = (i + 1) * An = (i + 1) * sqrt(3) * Hi^2 = sqrt(3) * H^2 / count
|
||||
# Hi = sqrt(H^2 * (i + 1) / count)
|
||||
# Hi = H * sqrt((i + 1) / count)
|
||||
minor, major = radii(internal, majorDia, minorDia, toolDia, toolCrest)
|
||||
H = float(major - minor)
|
||||
Hi = [H * math.sqrt((i + 1) / count) for i in range(count)]
|
||||
PathLog.debug("threadPasses({}, {}) -> H={} : {}".format(minor, major, H, Hi))
|
||||
|
||||
if internal:
|
||||
return [minor + h for h in Hi]
|
||||
return [major - h for h in Hi]
|
||||
|
||||
|
||||
class _InternalThread(object):
|
||||
"""Helper class for dealing with different thread types"""
|
||||
def elevatorRadius(obj, center, internal, tool):
|
||||
"""elevatorLocation(obj, center, internal, tool) ... return suitable location for the tool elevator"""
|
||||
|
||||
def __init__(self, cmd, zStart, zFinal, pitch):
|
||||
self.cmd = cmd
|
||||
if zStart < zFinal:
|
||||
self.pitch = pitch
|
||||
else:
|
||||
self.pitch = -pitch
|
||||
self.hPitch = self.pitch / 2
|
||||
self.zStart = zStart
|
||||
self.zFinal = zFinal
|
||||
|
||||
def overshoots(self, z):
|
||||
"""overshoots(z) ... returns true if adding another half helix goes beyond the thread bounds"""
|
||||
if self.pitch < 0:
|
||||
return z + self.hPitch < self.zFinal
|
||||
return z + self.hPitch > self.zFinal
|
||||
|
||||
def adjustX(self, x, dx):
|
||||
"""adjustX(x, dx) ... move x by dx, the direction depends on the thread settings"""
|
||||
if self.isG3() == (self.pitch > 0):
|
||||
return x + dx
|
||||
return x - dx
|
||||
|
||||
def adjustY(self, y, dy):
|
||||
"""adjustY(y, dy) ... move y by dy, the direction depends on the thread settings"""
|
||||
if self.isG3():
|
||||
return y - dy
|
||||
return y - dy
|
||||
|
||||
def isG3(self):
|
||||
"""isG3() ... returns True if this is a G3 command"""
|
||||
return self.cmd in ["G3", "G03", "g3", "g03"]
|
||||
|
||||
def isUp(self):
|
||||
"""isUp() ... returns True if the thread goes from the bottom up"""
|
||||
return self.pitch > 0
|
||||
|
||||
|
||||
def internalThreadCommands(loc, cmd, zStart, zFinal, pitch, radius, leadInOut):
|
||||
"""internalThreadCommands(loc, cmd, zStart, zFinal, pitch, radius) ... returns the g-code to mill the given internal thread"""
|
||||
thread = _InternalThread(cmd, zStart, zFinal, pitch)
|
||||
|
||||
yMin = loc.y - radius
|
||||
yMax = loc.y + radius
|
||||
|
||||
path = []
|
||||
# at this point the tool is at a safe height (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": loc.x, "Y": loc.y}))
|
||||
path.append(Path.Command("G0", {"Z": thread.zStart}))
|
||||
if leadInOut:
|
||||
path.append(Path.Command(thread.cmd, {"Y": yMax, "J": (yMax - loc.y) / 2}))
|
||||
if internal:
|
||||
dy = float(obj.MinorDiameter - tool.Diameter) / 2 - 1
|
||||
if dy < 0:
|
||||
if obj.MinorDiameter < tool.Diameter:
|
||||
PathLog.error(
|
||||
"The selected tool is too big (d={}) for milling a thread with minor diameter D={}".format(
|
||||
tool.Diameter, obj.MinorDiameter
|
||||
)
|
||||
)
|
||||
dy = 0
|
||||
else:
|
||||
path.append(Path.Command("G1", {"Y": yMax}))
|
||||
dy = float(obj.MajorDiameter + tool.Diameter) / 2 + 1
|
||||
|
||||
z = thread.zStart
|
||||
r = -radius
|
||||
i = 0
|
||||
while True:
|
||||
z = thread.zStart + i * thread.hPitch
|
||||
if thread.overshoots(z):
|
||||
break
|
||||
if 0 == (i & 0x01):
|
||||
y = yMin
|
||||
else:
|
||||
y = yMax
|
||||
path.append(Path.Command(thread.cmd, {"Y": y, "Z": z + thread.hPitch, "J": r}))
|
||||
r = -r
|
||||
i = i + 1
|
||||
|
||||
z = thread.zStart + i * thread.hPitch
|
||||
if PathGeom.isRoughly(z, thread.zFinal):
|
||||
x = loc.x
|
||||
else:
|
||||
n = math.fabs(thread.zFinal - thread.zStart) / thread.hPitch
|
||||
k = n - int(n)
|
||||
dy = math.cos(k * math.pi)
|
||||
dx = math.sin(k * math.pi)
|
||||
y = thread.adjustY(loc.y, r * dy)
|
||||
x = thread.adjustX(loc.x, r * dx)
|
||||
path.append(
|
||||
Path.Command(thread.cmd, {"X": x, "Y": y, "Z": thread.zFinal, "J": r})
|
||||
)
|
||||
|
||||
if leadInOut:
|
||||
path.append(
|
||||
Path.Command(
|
||||
thread.cmd,
|
||||
{"X": loc.x, "Y": loc.y, "I": (loc.x - x) / 2, "J": (loc.y - y) / 2},
|
||||
)
|
||||
)
|
||||
else:
|
||||
path.append(Path.Command("G1", {"X": loc.x, "Y": loc.y}))
|
||||
return path
|
||||
return dy
|
||||
|
||||
|
||||
class ObjectThreadMilling(PathCircularHoleBase.ObjectOp):
|
||||
@@ -176,18 +123,55 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp):
|
||||
|
||||
LeftHand = "LeftHand"
|
||||
RightHand = "RightHand"
|
||||
ThreadTypeCustom = "Custom"
|
||||
ThreadTypeMetricInternal = "MetricInternal"
|
||||
ThreadTypeImperialInternal = "ImperialInternal"
|
||||
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]
|
||||
ThreadTypes = [
|
||||
ThreadTypeCustom,
|
||||
ThreadTypeMetricInternal,
|
||||
ThreadTypeImperialInternal,
|
||||
|
||||
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
|
||||
@@ -200,25 +184,68 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp):
|
||||
'raw' is list of (translated_text, data_string) tuples
|
||||
'translated' is list of translated string literals
|
||||
"""
|
||||
PathLog.track()
|
||||
|
||||
# Enumeration lists for App::PropertyEnumeration properties
|
||||
enums = {
|
||||
"ThreadType": [
|
||||
(translate("Path_ThreadMilling", "Custom"), "Custom"),
|
||||
(translate("Path_ThreadMilling", "Metric Internal"), "MetricInternal"),
|
||||
(
|
||||
translate("Path_ThreadMilling", "Imperial Internal"),
|
||||
"ImperialInternal",
|
||||
translate("Path_ThreadMilling", "Custom External"),
|
||||
ObjectThreadMilling.ThreadTypeCustomExternal,
|
||||
),
|
||||
], # this is the direction that the profile runs
|
||||
(
|
||||
translate("Path_ThreadMilling", "Custom Internal"),
|
||||
ObjectThreadMilling.ThreadTypeCustomInternal,
|
||||
),
|
||||
(
|
||||
translate("Path_ThreadMilling", "Imperial External (2A)"),
|
||||
ObjectThreadMilling.ThreadTypeImperialExternal2A,
|
||||
),
|
||||
(
|
||||
translate("Path_ThreadMilling", "Imperial External (3A)"),
|
||||
ObjectThreadMilling.ThreadTypeImperialExternal3A,
|
||||
),
|
||||
(
|
||||
translate("Path_ThreadMilling", "Imperial Internal (2B)"),
|
||||
ObjectThreadMilling.ThreadTypeImperialInternal2B,
|
||||
),
|
||||
(
|
||||
translate("Path_ThreadMilling", "Imperial Internal (3B)"),
|
||||
ObjectThreadMilling.ThreadTypeImperialInternal3B,
|
||||
),
|
||||
(
|
||||
translate("Path_ThreadMilling", "Metric External (4G6G)"),
|
||||
ObjectThreadMilling.ThreadTypeMetricExternal4G6G,
|
||||
),
|
||||
(
|
||||
translate("Path_ThreadMilling", "Metric External (6G)"),
|
||||
ObjectThreadMilling.ThreadTypeMetricExternal6G,
|
||||
),
|
||||
(
|
||||
translate("Path_ThreadMilling", "Metric Internal (6H)"),
|
||||
ObjectThreadMilling.ThreadTypeMetricInternal6H,
|
||||
),
|
||||
],
|
||||
"ThreadOrientation": [
|
||||
(translate("Path_ThreadMilling", "LeftHand"), "LeftHand"),
|
||||
(translate("Path_ThreadMilling", "RightHand"), "RightHand"),
|
||||
], # side of profile that cutter is on in relation to direction of profile
|
||||
(
|
||||
translate("Path_ThreadMilling", "LeftHand"),
|
||||
ObjectThreadMilling.LeftHand,
|
||||
),
|
||||
(
|
||||
translate("Path_ThreadMilling", "RightHand"),
|
||||
ObjectThreadMilling.RightHand,
|
||||
),
|
||||
],
|
||||
"Direction": [
|
||||
(translate("Path_ThreadMilling", "Climb"), "Climb"),
|
||||
(translate("Path_ThreadMilling", "Conventional"), "Conventional"),
|
||||
], # side of profile that cutter is on in relation to direction of profile
|
||||
(
|
||||
translate("Path_ThreadMilling", "Climb"),
|
||||
ObjectThreadMilling.DirectionClimb,
|
||||
),
|
||||
(
|
||||
translate("Path_ThreadMilling", "Conventional"),
|
||||
ObjectThreadMilling.DirectionConventional,
|
||||
),
|
||||
],
|
||||
}
|
||||
|
||||
if dataType == "raw":
|
||||
@@ -236,9 +263,11 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp):
|
||||
return data
|
||||
|
||||
def circularHoleFeatures(self, obj):
|
||||
PathLog.track()
|
||||
return PathOp.FeatureBaseGeometry
|
||||
|
||||
def initCircularHoleOperation(self, obj):
|
||||
PathLog.track()
|
||||
obj.addProperty(
|
||||
"App::PropertyEnumeration",
|
||||
"ThreadOrientation",
|
||||
@@ -333,48 +362,11 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp):
|
||||
for n in self.propertyEnumerations():
|
||||
setattr(obj, n[0], n[1])
|
||||
|
||||
def threadStartDepth(self, obj):
|
||||
if obj.ThreadOrientation == self.RightHand:
|
||||
if obj.Direction == self.DirectionClimb:
|
||||
PathLog.track(obj.Label, obj.FinalDepth)
|
||||
return obj.FinalDepth
|
||||
PathLog.track(obj.Label, obj.StartDepth)
|
||||
return obj.StartDepth
|
||||
if obj.Direction == self.DirectionClimb:
|
||||
PathLog.track(obj.Label, obj.StartDepth)
|
||||
return obj.StartDepth
|
||||
PathLog.track(obj.Label, obj.FinalDepth)
|
||||
return obj.FinalDepth
|
||||
def _isThreadInternal(self, obj):
|
||||
return obj.ThreadType in self.ThreadTypesInternal
|
||||
|
||||
def threadFinalDepth(self, obj):
|
||||
PathLog.track(obj.Label)
|
||||
if obj.ThreadOrientation == self.RightHand:
|
||||
if obj.Direction == self.DirectionClimb:
|
||||
PathLog.track(obj.Label, obj.StartDepth)
|
||||
return obj.StartDepth
|
||||
PathLog.track(obj.Label, obj.FinalDepth)
|
||||
return obj.FinalDepth
|
||||
if obj.Direction == self.DirectionClimb:
|
||||
PathLog.track(obj.Label, obj.FinalDepth)
|
||||
return obj.FinalDepth
|
||||
PathLog.track(obj.Label, obj.StartDepth)
|
||||
return obj.StartDepth
|
||||
|
||||
def threadDirectionCmd(self, obj):
|
||||
PathLog.track(obj.Label)
|
||||
if obj.ThreadOrientation == self.RightHand:
|
||||
if obj.Direction == self.DirectionClimb:
|
||||
PathLog.track(obj.Label, "G2")
|
||||
return "G2"
|
||||
PathLog.track(obj.Label, "G3")
|
||||
return "G3"
|
||||
if obj.Direction == self.DirectionClimb:
|
||||
PathLog.track(obj.Label, "G3")
|
||||
return "G3"
|
||||
PathLog.track(obj.Label, "G2")
|
||||
return "G2"
|
||||
|
||||
def threadSetup(self, obj):
|
||||
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:
|
||||
@@ -384,6 +376,18 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp):
|
||||
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
|
||||
@@ -398,22 +402,45 @@ 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)
|
||||
|
||||
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,
|
||||
radiiInternal,
|
||||
threadRadii,
|
||||
self._isThreadInternal(obj),
|
||||
obj.MajorDiameter.Value,
|
||||
obj.MinorDiameter.Value,
|
||||
float(self.tool.Diameter),
|
||||
float(self.tool.Crest),
|
||||
):
|
||||
commands = internalThreadCommands(
|
||||
loc, gcode, zStart, zFinal, pitch, radius, obj.LeadInOut
|
||||
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 = threadmilling.generate(
|
||||
loc,
|
||||
gcode,
|
||||
zStart,
|
||||
zFinal,
|
||||
pitch,
|
||||
radius,
|
||||
obj.LeadInOut,
|
||||
elevator,
|
||||
start,
|
||||
)
|
||||
|
||||
for cmd in commands:
|
||||
p = cmd.Parameters
|
||||
if cmd.Name in ["G0"]:
|
||||
@@ -454,18 +481,21 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp):
|
||||
PathLog.error("No suitable Tool found for thread milling operation")
|
||||
|
||||
def opSetDefaultValues(self, obj, job):
|
||||
PathLog.track()
|
||||
obj.ThreadOrientation = self.RightHand
|
||||
obj.ThreadType = self.ThreadTypeMetricInternal
|
||||
obj.ThreadType = self.ThreadTypeMetricInternal6H
|
||||
obj.ThreadFit = 50
|
||||
obj.Pitch = 1
|
||||
obj.TPI = 0
|
||||
obj.Passes = 1
|
||||
obj.Direction = self.DirectionClimb
|
||||
obj.LeadInOut = True
|
||||
obj.LeadInOut = False
|
||||
|
||||
def isToolSupported(self, obj, tool):
|
||||
"""Thread milling only supports thread milling cutters."""
|
||||
return hasattr(tool, "Diameter") and hasattr(tool, "Crest")
|
||||
support = hasattr(tool, "Diameter") and hasattr(tool, "Crest")
|
||||
PathLog.track(tool.Label, support)
|
||||
return support
|
||||
|
||||
|
||||
def SetupProperties():
|
||||
|
||||
@@ -49,17 +49,23 @@ else:
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
|
||||
def fillThreads(combo, dataFile):
|
||||
combo.blockSignals(True)
|
||||
combo.clear()
|
||||
def fillThreads(form, dataFile, defaultSelect):
|
||||
form.threadName.blockSignals(True)
|
||||
select = form.threadName.currentText()
|
||||
PathLog.debug("select = '{}'".format(select))
|
||||
form.threadName.clear()
|
||||
with open(
|
||||
"{}Mod/Path/Data/Threads/{}.csv".format(FreeCAD.getHomePath(), dataFile)
|
||||
"{}Mod/Path/Data/Threads/{}".format(FreeCAD.getHomePath(), dataFile)
|
||||
) as fp:
|
||||
reader = csv.DictReader(fp)
|
||||
for row in reader:
|
||||
combo.addItem(row["name"], row)
|
||||
combo.setEnabled(True)
|
||||
combo.blockSignals(False)
|
||||
form.threadName.addItem(row["name"], row)
|
||||
if select:
|
||||
form.threadName.setCurrentText(select)
|
||||
elif defaultSelect:
|
||||
form.threadName.setCurrentText(defaultSelect)
|
||||
form.threadName.setEnabled(True)
|
||||
form.threadName.blockSignals(False)
|
||||
|
||||
|
||||
class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage):
|
||||
@@ -74,10 +80,6 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage):
|
||||
)
|
||||
self.pitch = PathGui.QuantitySpinBox(self.form.threadPitch, obj, "Pitch")
|
||||
|
||||
# setupCombo(self.form.threadOrientation, obj.Proxy.ThreadOrientations)
|
||||
# setupCombo(self.form.threadType, obj.Proxy.ThreadTypes)
|
||||
# setupCombo(self.form.opDirection, obj.Proxy.Directions)
|
||||
|
||||
def getForm(self):
|
||||
"""getForm() ... return UI"""
|
||||
form = FreeCADGui.PySideUic.loadUi(":/panels/PageOpThreadMillingEdit.ui")
|
||||
@@ -134,25 +136,41 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage):
|
||||
self.pitch.updateSpinBox()
|
||||
|
||||
self.setupToolController(obj, self.form.toolController)
|
||||
self._updateFromThreadType()
|
||||
|
||||
def _isThreadMetric(self):
|
||||
return (
|
||||
self.form.threadType.currentData()
|
||||
== PathThreadMilling.ObjectThreadMilling.ThreadTypeMetricInternal
|
||||
)
|
||||
def _isThreadCustom(self):
|
||||
return self.form.threadType.currentData() in [
|
||||
PathThreadMilling.ObjectThreadMilling.ThreadTypeCustomInternal,
|
||||
PathThreadMilling.ObjectThreadMilling.ThreadTypeCustomExternal,
|
||||
]
|
||||
|
||||
def _isThreadImperial(self):
|
||||
return (
|
||||
self.form.threadType.currentData()
|
||||
== PathThreadMilling.ObjectThreadMilling.ThreadTypeImperialInternal
|
||||
in PathThreadMilling.ObjectThreadMilling.ThreadTypesImperial
|
||||
)
|
||||
|
||||
def _isThreadMetric(self):
|
||||
return (
|
||||
self.form.threadType.currentData()
|
||||
in PathThreadMilling.ObjectThreadMilling.ThreadTypesMetric
|
||||
)
|
||||
|
||||
def _isThreadInternal(self):
|
||||
return (
|
||||
self.form.threadType.currentData()
|
||||
in PathThreadMilling.ObjectThreadMilling.ThreadTypesInternal
|
||||
)
|
||||
|
||||
def _isThreadExternal(self):
|
||||
return (
|
||||
self.form.threadType.currentData()
|
||||
in PathThreadMilling.ObjectThreadMilling.ThreadTypesExternal
|
||||
)
|
||||
|
||||
def _updateFromThreadType(self):
|
||||
|
||||
if (
|
||||
self.form.threadType.currentData()
|
||||
== PathThreadMilling.ObjectThreadMilling.ThreadTypeCustom
|
||||
):
|
||||
if self._isThreadCustom():
|
||||
self.form.threadName.setEnabled(False)
|
||||
self.form.threadFit.setEnabled(False)
|
||||
self.form.threadFitLabel.setEnabled(False)
|
||||
@@ -160,49 +178,53 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage):
|
||||
self.form.threadPitchLabel.setEnabled(True)
|
||||
self.form.threadTPI.setEnabled(True)
|
||||
self.form.threadTPILabel.setEnabled(True)
|
||||
|
||||
if self._isThreadMetric():
|
||||
else:
|
||||
self.form.threadFit.setEnabled(True)
|
||||
self.form.threadFitLabel.setEnabled(True)
|
||||
self.form.threadPitch.setEnabled(True)
|
||||
self.form.threadPitchLabel.setEnabled(True)
|
||||
self.form.threadTPI.setEnabled(False)
|
||||
self.form.threadTPILabel.setEnabled(False)
|
||||
self.form.threadTPI.setValue(0)
|
||||
fillThreads(self.form.threadName, "metric-internal")
|
||||
|
||||
if self._isThreadImperial():
|
||||
self.form.threadFit.setEnabled(True)
|
||||
self.form.threadFitLabel.setEnabled(True)
|
||||
self.form.threadPitch.setEnabled(False)
|
||||
self.form.threadPitchLabel.setEnabled(False)
|
||||
self.form.threadTPI.setEnabled(True)
|
||||
self.form.threadTPILabel.setEnabled(True)
|
||||
self.pitch.updateSpinBox(0)
|
||||
fillThreads(self.form.threadName, "imperial-internal")
|
||||
if self._isThreadMetric():
|
||||
self.form.threadPitch.setEnabled(True)
|
||||
self.form.threadPitchLabel.setEnabled(True)
|
||||
self.form.threadTPI.setEnabled(False)
|
||||
self.form.threadTPILabel.setEnabled(False)
|
||||
self.form.threadTPI.setValue(0)
|
||||
else:
|
||||
self.form.threadPitch.setEnabled(False)
|
||||
self.form.threadPitchLabel.setEnabled(False)
|
||||
self.form.threadTPI.setEnabled(True)
|
||||
self.form.threadTPILabel.setEnabled(True)
|
||||
self.pitch.updateSpinBox(0)
|
||||
fillThreads(
|
||||
self.form,
|
||||
PathThreadMilling.ObjectThreadMilling.ThreadTypeData[
|
||||
self.form.threadType.currentData()
|
||||
],
|
||||
self.obj.ThreadName,
|
||||
)
|
||||
self._updateFromThreadName()
|
||||
|
||||
def _updateFromThreadName(self):
|
||||
thread = self.form.threadName.currentData()
|
||||
fit = float(self.form.threadFit.value()) / 100
|
||||
mamin = float(thread["dMajorMin"])
|
||||
mamax = float(thread["dMajorMax"])
|
||||
major = mamin + (mamax - mamin) * fit
|
||||
mimin = float(thread["dMinorMin"])
|
||||
mimax = float(thread["dMinorMax"])
|
||||
minor = mimin + (mimax - mimin) * fit
|
||||
if not self._isThreadCustom():
|
||||
thread = self.form.threadName.currentData()
|
||||
fit = float(self.form.threadFit.value()) / 100
|
||||
maxmin = float(thread["dMajorMin"])
|
||||
maxmax = float(thread["dMajorMax"])
|
||||
major = maxmin + (maxmax - maxmin) * fit
|
||||
minmin = float(thread["dMinorMin"])
|
||||
minmax = float(thread["dMinorMax"])
|
||||
minor = minmin + (minmax - minmin) * fit
|
||||
|
||||
if self._isThreadMetric():
|
||||
pitch = float(thread["pitch"])
|
||||
self.pitch.updateSpinBox(pitch)
|
||||
if self._isThreadMetric():
|
||||
pitch = float(thread["pitch"])
|
||||
self.pitch.updateSpinBox(pitch)
|
||||
|
||||
if self._isThreadImperial():
|
||||
tpi = int(thread["tpi"])
|
||||
self.form.threadTPI.setValue(tpi)
|
||||
minor = minor * 25.4
|
||||
major = major * 25.4
|
||||
if self._isThreadImperial():
|
||||
tpi = int(thread["tpi"])
|
||||
self.form.threadTPI.setValue(tpi)
|
||||
minor = minor * 25.4
|
||||
major = major * 25.4
|
||||
|
||||
self.majorDia.updateSpinBox(major)
|
||||
self.minorDia.updateSpinBox(minor)
|
||||
self.majorDia.updateSpinBox(major)
|
||||
self.minorDia.updateSpinBox(minor)
|
||||
|
||||
self.setDirty()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user