Merge pull request #6485 from mlampert/feature/external-thread-milling

Path: Feature/external thread milling
This commit is contained in:
sliptonic
2022-03-04 12:52:31 -06:00
committed by GitHub
15 changed files with 1273 additions and 278 deletions

View File

@@ -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():

View File

@@ -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()