From 2d497424c104c39ff6203ccd1389d04a1afb7f06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20B=C3=A4hr?= Date: Tue, 10 Jan 2023 23:24:31 +0100 Subject: [PATCH] PD: Make involute gear's root fillet radius configurable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- src/Mod/PartDesign/InvoluteGearFeature.py | 12 ++++--- .../PartDesignTests/TestInvoluteGear.py | 2 ++ src/Mod/PartDesign/fcgear/involute.py | 35 +++++++++++++------ 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/Mod/PartDesign/InvoluteGearFeature.py b/src/Mod/PartDesign/InvoluteGearFeature.py index d2a8dbe1bc..a20a52a78e 100644 --- a/src/Mod/PartDesign/InvoluteGearFeature.py +++ b/src/Mod/PartDesign/InvoluteGearFeature.py @@ -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(); diff --git a/src/Mod/PartDesign/PartDesignTests/TestInvoluteGear.py b/src/Mod/PartDesign/PartDesignTests/TestInvoluteGear.py index 6327ec09b6..425adb77e5 100644 --- a/src/Mod/PartDesign/PartDesignTests/TestInvoluteGear.py +++ b/src/Mod/PartDesign/PartDesignTests/TestInvoluteGear.py @@ -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 diff --git a/src/Mod/PartDesign/fcgear/involute.py b/src/Mod/PartDesign/fcgear/involute.py index c6b105bdb5..4b54a39ef4 100644 --- a/src/Mod/PartDesign/fcgear/involute.py +++ b/src/Mod/PartDesign/fcgear/involute.py @@ -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