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