diff --git a/src/Mod/PartDesign/InvoluteGearFeature.py b/src/Mod/PartDesign/InvoluteGearFeature.py index 7789b4dd92..d2a8dbe1bc 100644 --- a/src/Mod/PartDesign/InvoluteGearFeature.py +++ b/src/Mod/PartDesign/InvoluteGearFeature.py @@ -87,7 +87,10 @@ class _InvoluteGear: def ensure_property(type_, name, doc, default): if not hasattr(obj, name): obj.addProperty(type_, name, "Gear", doc) - setattr(obj, name, default) + if callable(default): + setattr(obj, name, default()) + else: + setattr(obj, name, default) ensure_property("App::PropertyInteger", "NumberOfTeeth", doc="Number of gear teeth", @@ -104,14 +107,18 @@ class _InvoluteGear: ensure_property("App::PropertyBool", "ExternalGear", doc="True=external Gear False=internal Gear", default=True) + ensure_property("App::PropertyFloat", "AddendumCoefficient", + doc="The height of the tooth from the pitch circle up to its tip, normalized by the module.", + default=lambda: 1.0 if obj.ExternalGear else 0.6) + 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) def execute(self,obj): - #print "_InvoluteGear.execute()" w = fcgear.FCWireBuilder() - if obj.ExternalGear: - involute.CreateExternalGear(w, obj.Modules.Value,obj.NumberOfTeeth, obj.PressureAngle.Value, obj.HighPrecision) - else: - involute.CreateInternalGear(w, obj.Modules.Value,obj.NumberOfTeeth, obj.PressureAngle.Value, obj.HighPrecision) + 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) 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 02a2518fc9..6327ec09b6 100644 --- a/src/Mod/PartDesign/PartDesignTests/TestInvoluteGear.py +++ b/src/Mod/PartDesign/PartDesignTests/TestInvoluteGear.py @@ -81,6 +81,53 @@ class TestInvoluteGear(unittest.TestCase): self.assertNoIntersection(gear.Shape, makeCircle(tip_diameter/2 + delta), "Teeth extent beyond tip circle") self.assertNoIntersection(gear.Shape, makeCircle(root_diameter/2 - delta), "Teeth extend below root circle") + def testCustomizedGearProfileForSplinedShaft(self): + spline = InvoluteGearFeature.makeInvoluteGear('InvoluteSplinedShaft') + z = 12 + m = 2 + add_coef = 0.5 + ded_coef = 0.9 + spline.NumberOfTeeth = z + spline.Modules = f'{m} mm' + spline.PressureAngle = '30 deg' + spline.AddendumCoefficient = add_coef + spline.DedendumCoefficient = ded_coef + self.assertSuccessfulRecompute(spline) + self.assertClosedWire(spline.Shape) + pitch_diameter = m * z + tip_diameter = pitch_diameter + 2 * add_coef * m + root_diameter = pitch_diameter - 2 * ded_coef * m + # the test purpose here is just to ensure the gear's parameters are used, + # not super precise profile verification. Thus a lax delta is just file here. + delta = 0.01 + self.assertIntersection(spline.Shape, makeCircle(pitch_diameter/2), "Expecting intersection at pitch circle") + self.assertNoIntersection(spline.Shape, makeCircle(tip_diameter/2 + delta), "Teeth extent beyond tip circle") + self.assertNoIntersection(spline.Shape, makeCircle(root_diameter/2 - delta), "Teeth extend below root circle") + + def testCustomizedGearProfileForSplinedHub(self): + hub = InvoluteGearFeature.makeInvoluteGear('InvoluteSplinedHub') + hub.ExternalGear = False + z = 12 + m = 2 + add_coef = 0.5 + ded_coef = 0.9 + hub.NumberOfTeeth = z + hub.Modules = f'{m} mm' + hub.PressureAngle = '30 deg' + hub.AddendumCoefficient = add_coef + hub.DedendumCoefficient = ded_coef + self.assertSuccessfulRecompute(hub) + self.assertClosedWire(hub.Shape) + pitch_diameter = m * z + tip_diameter = pitch_diameter - 2 * add_coef * m + root_diameter = pitch_diameter + 2 * ded_coef * m + # the test purpose here is just to ensure the gear's parameters are used, + # not super precise profile verification. Thus a lax delta is just file here. + delta = 0.1 # FIXME it seems that the top land arc is in the wrong direction, thus a larger tolerance. + self.assertIntersection(hub.Shape, makeCircle(pitch_diameter/2), "Expecting intersection at pitch circle") + self.assertNoIntersection(hub.Shape, makeCircle(tip_diameter/2 - delta), "Teeth extent below tip circle") + self.assertNoIntersection(hub.Shape, makeCircle(root_diameter/2 + delta), "Teeth extend beyond root circle") + def testUsagePadGearProfile(self): profile = InvoluteGearFeature.makeInvoluteGear('GearProfile') body = self.Doc.addObject('PartDesign::Body','GearBody') diff --git a/src/Mod/PartDesign/fcgear/involute.py b/src/Mod/PartDesign/fcgear/involute.py index 0e288ad467..c6b105bdb5 100644 --- a/src/Mod/PartDesign/fcgear/involute.py +++ b/src/Mod/PartDesign/fcgear/involute.py @@ -27,20 +27,26 @@ from math import cos, sin, pi, acos, atan, sqrt xrange = range -def CreateExternalGear(w, m, Z, phi, split=True): +def CreateExternalGear(w, m, Z, phi, split=True, addCoeff=1.0, dedCoeff=1.25): """ Create an external gear w is wirebuilder object (in which the gear will be constructed) + m is the gear's module (pitch diameter divided by the number of teeth) + Z is the number of teeth + 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) 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 of degree 4 """ # ****** external gear specifications - addendum = m # distance from pitch circle to tip circle - dedendum = 1.25 * m # pitch circle to root, sets clearance - clearance = dedendum - addendum + 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 @@ -127,20 +133,31 @@ def CreateExternalGear(w, m, Z, phi, split=True): w.close() return w -def CreateInternalGear(w, m, Z, phi, split=True): +def CreateInternalGear(w, m, Z, phi, split=True, addCoeff=0.6, dedCoeff=1.25): """ Create an internal gear w is wirebuilder object (in which the gear will be constructed) + m is the gear's module (pitch diameter divided by the number of teeth) + Z is the number of teeth + phi is the gear's pressure angle + addCoeff is the addendum coefficient (addendum normalized by module) + The default of 0.6 comes from the "Handbook of Gear Design" by Gitin M. Maitra, + with the goal to push the addendum circle beyond the base circle to avoid non-involute + flanks on the tips. + It in turn assumes, however, that the mating pinion usaes a larger value of 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) 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 of degree 4 """ # ****** external gear specifications - addendum = 0.6 * m # distance from pitch circle to tip circle (ref G.M.Maitra) - dedendum = 1.25 * m # pitch circle to root, sets clearance - clearance = 0.25 * m + 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