PD: Make involute gear's root fillet radius configurable

The original implementation always took 150% of the addendum/dedendum
difference as fillet radius. For a standard full-depth system this
results in a normalized value 0f 0.375, which is pretty close to the 0.38
definded by the basic ISO rack. However, when using much shorter teeth as
e.g. required for a splined shaft, the fillet becomes way too large.

In addition, I don't understand the approximation to calculate the
distance between the gear's center and the top of the fillet yet. It was
only refactored to allow the custom fillet radii, but it retuns the same
values as the original implementation.
However, with high pressure angles, up to 45° used for splines, this
approximation comes to its limits.
This commit is contained in:
Jonas Bähr
2023-01-10 23:24:31 +01:00
committed by Uwe
parent 0a7fa862d9
commit 2d497424c1
3 changed files with 35 additions and 14 deletions

View File

@@ -76,14 +76,14 @@ class _InvoluteGear:
"The InvoluteGear object"
def __init__(self,obj):
self.Type = "InvoluteGear"
self._ensure_properties(obj)
self._ensure_properties(obj, is_restore=False)
obj.Proxy = self
def onDocumentRestored(self, obj):
"""hook used to migrate older versions of this object"""
self._ensure_properties(obj)
self._ensure_properties(obj, is_restore=True)
def _ensure_properties(self, obj):
def _ensure_properties(self, obj, is_restore):
def ensure_property(type_, name, doc, default):
if not hasattr(obj, name):
obj.addProperty(type_, name, "Gear", doc)
@@ -113,12 +113,16 @@ class _InvoluteGear:
ensure_property("App::PropertyFloat","DedendumCoefficient",
doc="The height of the tooth from the pitch circle down to its root, normalized by the module.",
default=1.25)
ensure_property("App::PropertyFloat","RootFilletCoefficient",
doc="The radius of the fillet at the root of the tooth, normalized by the module.",
default=lambda: 0.375 if is_restore else 0.38)
def execute(self,obj):
w = fcgear.FCWireBuilder()
generator_func = involute.CreateExternalGear if obj.ExternalGear else involute.CreateInternalGear
generator_func(w, obj.Modules.Value, obj.NumberOfTeeth, obj.PressureAngle.Value,
split=obj.HighPrecision, addCoeff=obj.AddendumCoefficient, dedCoeff=obj.DedendumCoefficient)
split=obj.HighPrecision, addCoeff=obj.AddendumCoefficient, dedCoeff=obj.DedendumCoefficient,
filletCoeff=obj.RootFilletCoefficient)
gearw = Part.Wire([o.toShape() for o in w.wire])
obj.Shape = gearw
obj.positionBySupport();

View File

@@ -92,6 +92,7 @@ class TestInvoluteGear(unittest.TestCase):
spline.PressureAngle = '30 deg'
spline.AddendumCoefficient = add_coef
spline.DedendumCoefficient = ded_coef
spline.RootFilletCoefficient = 0.4
self.assertSuccessfulRecompute(spline)
self.assertClosedWire(spline.Shape)
pitch_diameter = m * z
@@ -116,6 +117,7 @@ class TestInvoluteGear(unittest.TestCase):
hub.PressureAngle = '30 deg'
hub.AddendumCoefficient = add_coef
hub.DedendumCoefficient = ded_coef
hub.RootFilletCoefficient = 0.4
self.assertSuccessfulRecompute(hub)
self.assertClosedWire(hub.Shape)
pitch_diameter = m * z

View File

@@ -27,7 +27,10 @@ from math import cos, sin, pi, acos, atan, sqrt
xrange = range
def CreateExternalGear(w, m, Z, phi, split=True, addCoeff=1.0, dedCoeff=1.25):
def CreateExternalGear(w, m, Z, phi,
split=True,
addCoeff=1.0, dedCoeff=1.25,
filletCoeff=0.375):
"""
Create an external gear
@@ -37,6 +40,9 @@ def CreateExternalGear(w, m, Z, phi, split=True, addCoeff=1.0, dedCoeff=1.25):
phi is the gear's pressure angle
addCoeff is the addendum coefficient (addendum normalized by module)
dedCoeff is the dedendum coefficient (dedendum normalized by module)
filletCoeff is the root fillet radius, normalized by the module.
The default of 0.375 matches the hard-coded value (1.5 * 0.25) of the implementation
up to v0.20. The ISO Rack specified 0.38, though.
if split is True, each profile of a teeth will consist in 2 Bezier
curves of degree 3, otherwise it will be made of one Bezier curve
@@ -45,18 +51,16 @@ def CreateExternalGear(w, m, Z, phi, split=True, addCoeff=1.0, dedCoeff=1.25):
# ****** external gear specifications
addendum = addCoeff * m # distance from pitch circle to tip circle
dedendum = dedCoeff * m # pitch circle to root, sets clearance
clearance = dedendum - addendum # strictily speaking, for the clearence the addendum of the
# *mating* gear is required. Let's assume them identical.
# Calculate radii
Rpitch = Z * m / 2 # pitch circle radius
Rb = Rpitch*cos(phi * pi / 180) # base circle radius
Ra = Rpitch + addendum # tip (addendum) circle radius
Rroot = Rpitch - dedendum # root circle radius
fRad = 1.5 * clearance # fillet radius, max 1.5*clearance
fRad = filletCoeff * m # fillet radius, max 1.5*clearance
Rf = sqrt((Rroot + fRad)**2 - fRad**2) # radius at top of fillet
if (Rb < Rf):
Rf = Rroot + clearance
Rf = Rroot + fRad/1.5 # fRad/1.5=clerance, with crearance=0.25*m
# ****** calculate angles (all in radians)
pitchAngle = 2 * pi / Z # angle subtended by whole tooth (rads)
@@ -133,7 +137,10 @@ def CreateExternalGear(w, m, Z, phi, split=True, addCoeff=1.0, dedCoeff=1.25):
w.close()
return w
def CreateInternalGear(w, m, Z, phi, split=True, addCoeff=0.6, dedCoeff=1.25):
def CreateInternalGear(w, m, Z, phi,
split=True,
addCoeff=0.6, dedCoeff=1.25,
filletCoeff=0.375):
"""
Create an internal gear
@@ -149,6 +156,9 @@ def CreateInternalGear(w, m, Z, phi, split=True, addCoeff=0.6, dedCoeff=1.25):
And it's only required for a small number of teeth and/or a relatively large mating gear.
Anyways, it's kept here as this was the hard-coded value of the implementation up to v0.20.
dedCoeff is the dedendum coefficient (dedendum normalized by module)
filletCoeff is the root fillet radius, normalized by the module.
The default of 0.375 matches the hard-coded value (1.5 * 0.25) of the implementation
up to v0.20. The ISO Rack specified 0.38, though.
if split is True, each profile of a teeth will consist in 2 Bezier
curves of degree 3, otherwise it will be made of one Bezier curve
@@ -157,15 +167,16 @@ def CreateInternalGear(w, m, Z, phi, split=True, addCoeff=0.6, dedCoeff=1.25):
# ****** external gear specifications
addendum = addCoeff * m # distance from pitch circle to tip circle
dedendum = dedCoeff * m # pitch circle to root, sets clearance
clearance = 0.25 * m # this assumes an addendum coefficient of 1 for the mating gear
# Calculate radii
Rpitch = Z * m / 2 # pitch circle radius
Rb = Rpitch*cos(phi * pi / 180) # base circle radius
Ra = Rpitch - addendum # tip (addendum) circle radius
Rroot = Rpitch + dedendum # root circle radius
fRad = 1.5 * clearance # fillet radius, max 1.5*clearance
Rf = Rroot - clearance # radius at top of fillet (end of profile)
fRad = filletCoeff * m # fillet radius, max 1.5*clearance
Rf = Rroot - fRad/1.5 # radius at top of fillet (end of profile)
# No idea where this formula for Rf comes from.
# Just kept it to generate identical curves as the v0.20
# ****** calculate angles (all in radians)
pitchAngle = 2 * pi / Z # angle subtended by whole tooth (rads)
@@ -174,7 +185,11 @@ def CreateInternalGear(w, m, Z, phi, split=True, addCoeff=0.6, dedCoeff=1.25):
if (Ra > Rb): # start profile at top of fillet (if its greater)
tipToPitchAngle -= genInvolutePolar(Rb, Ra)
pitchToFilletAngle = genInvolutePolar(Rb, Rf) - baseToPitchAngle;
filletAngle = 1.414*clearance/Rf # // to make fillet tangential to root
filletAngle = 1.414*(fRad/1.5)/Rf # to make fillet tangential to root
# TODO: This and/or the Rf calculation doesn't seem quite
# correct. Keep it for compat, though. In the future, may
# use the special filletCoeff of 0.375 as marker to switch
# between "compat" or "correct/truly tangential"
# ****** generate Higuchi involute approximation
fe = 1 # fraction of profile length at end of approx