PD: Harmonize ExternalInvoluteGear and InternalInvoluteGear
There has been lots of duplicated code between those two, and the recent changed introduced even more copy/paste code. This commit consolidates both implementations. The two "public" entry points, `CreateExternalGear` and `CreateInternalGear` have been kept and now call a shared helper.
This commit is contained in:
@@ -23,6 +23,7 @@ import unittest
|
||||
import pathlib
|
||||
|
||||
import FreeCAD
|
||||
from FreeCAD import Vector
|
||||
from Part import makeCircle, Precision
|
||||
import InvoluteGearFeature
|
||||
|
||||
@@ -60,6 +61,25 @@ class TestInvoluteGear(unittest.TestCase):
|
||||
self.assertSuccessfulRecompute(gear)
|
||||
self.assertClosedWire(gear.Shape)
|
||||
|
||||
def testExternalGearProfileOrientation(self):
|
||||
gear = InvoluteGearFeature.makeInvoluteGear('TestGear')
|
||||
self.assertSuccessfulRecompute(gear)
|
||||
tip_diameter = (gear.NumberOfTeeth + 2 * gear.AddendumCoefficient) * gear.Modules
|
||||
delta = 0.01 # yes, we do not reach micrometer precision
|
||||
tip_probe = makeCircle(delta, Vector(tip_diameter/2, 0, 0))
|
||||
self.assertIntersection(gear.Shape, tip_probe,
|
||||
msg=f"First tooth tip does not lay on the positive X-axis")
|
||||
|
||||
def testInternalGearProfileOrientation(self):
|
||||
gear = InvoluteGearFeature.makeInvoluteGear('TestGear')
|
||||
gear.ExternalGear = False
|
||||
self.assertSuccessfulRecompute(gear)
|
||||
tip_diameter = (gear.NumberOfTeeth - 2 * gear.AddendumCoefficient) * gear.Modules
|
||||
delta = 0.01 # yes, we do not reach micrometer precision
|
||||
tip_probe = makeCircle(delta, Vector(tip_diameter/2, 0, 0))
|
||||
self.assertIntersection(gear.Shape, tip_probe,
|
||||
msg=f"First tooth tip does not lay on the positive X-axis")
|
||||
|
||||
def testCustomizedGearProfile(self):
|
||||
gear = InvoluteGearFeature.makeInvoluteGear('InvoluteGear')
|
||||
z = 12
|
||||
|
||||
@@ -34,7 +34,7 @@ def CreateExternalGear(w, m, Z, phi,
|
||||
"""
|
||||
Create an external gear
|
||||
|
||||
w is wirebuilder object (in which the gear will be constructed)
|
||||
w is wire builder 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, in degrees
|
||||
@@ -48,142 +48,15 @@ def CreateExternalGear(w, m, Z, phi,
|
||||
curves of degree 3, otherwise it will be made of one Bezier curve
|
||||
of degree 4
|
||||
"""
|
||||
# ****** external gear specifications
|
||||
addendum = addCoeff * m # distance from pitch circle to tip circle
|
||||
dedendum = dedCoeff * m # distance from pitch circle to root circle
|
||||
|
||||
# Calculate radii
|
||||
Rpitch = Z * m / 2 # pitch circle radius
|
||||
Rb = Rpitch*cos(radians(phi)) # base circle radius
|
||||
Ra = Rpitch + addendum # tip (addendum) circle radius
|
||||
Rroot = Rpitch - dedendum # root circle radius
|
||||
fRad = filletCoeff * m # fillet radius
|
||||
Rc = Rroot + fRad # radius at the center of the fillet circle
|
||||
Rf = Rc # radius at top of fillet, assuming fillet below involute
|
||||
|
||||
filletWithinInvolute = Rf > Rb # above the base circle we have the involute,
|
||||
# below we have a straight line towards the center.
|
||||
if (filletWithinInvolute and fRad > 0):
|
||||
# In this case we need tangency of the involute and the fillet circle.
|
||||
# It has to be somewhere between max(Rb,Rroot) and Rc.
|
||||
# So we need the radius r from the origin depending on the tangent angle, for both:
|
||||
# - the involute: ri(t) = Rb * sqrt(1 + t**2), rationale: see below.
|
||||
# - the fillet circle: a bit more complex, see below.
|
||||
# and then find the r where both tangents are equal.
|
||||
# As the tangent angle of the involute equals the parameter t, we can just take the
|
||||
# parametric polar representation of the involute as our first equation.
|
||||
# For a circle in parameter form, the tangent angle is pi/2 - t (for the first quadrant,
|
||||
# i.e. 0 <= t <= pi/2, and more than one quadrant is not of interest for us). Unfortunately,
|
||||
# the fillet circle is offset from the origin by (Rc,phi), so things becomes more messy:
|
||||
# rc(t) = sqrt((Rc*cos(phi) + fRad*cos(t))**2 + (Rc*sin(phi) + fRad*sin(t))**2)
|
||||
# To get rid of the sin(t) part we assume phi "very small", i.e. sin(phi) becomes 0.
|
||||
# This is justyfied because Rc is much larger than fRad and the parallax error is
|
||||
# neglectable. Next we substitute the parameter t by pi/2-q-pi. For one to account for the
|
||||
# tangent angle definitions (see above), and then to turn our cicle by 180° as for the
|
||||
# root fillet we need the third quadrant of the circle (NB: we are looking at the upper
|
||||
# half of the right most tooth, i.e. the involute grows downwards!). This results in
|
||||
# sqrt(2*fRad*Rc*cos(-1/2*pi - q) + fRad**2 + Rc**2) which simplifies to
|
||||
# rc(q) = sqrt(-2*fRad*Rc*sin(q) + fRad**2 + Rc**2)
|
||||
# which is the polar r for a given tangent angle q (with the simplification of phi being 0)
|
||||
# This can now be inverted to give the tangent angle at a given r:
|
||||
# qc(r) = asin((-r**2 + fRad**2 + Rc**2)/(2*fRad*Rc))
|
||||
# For the involute we have ri(q) = Rb * sqrt(1 + q**2), thus
|
||||
# qi(r) = +- sqrt(r**2 - Rb**2)/Rb
|
||||
# Now to find the r where the tangents match, our Rf, we set qc=qi and solve for r.
|
||||
# This doesn't seem to have an analytical solution, though, so let's apply
|
||||
# Newton's Method to find the root of q := qi-qc over Rb...Rc, or for larger number of
|
||||
# teeth, Rroot...Rc as in this case the base circle is below the root circle.
|
||||
# The graph of q is strictly monotnonous and very steep, no surprises to expect.
|
||||
# To simplify the above equations and to find the derivative, SageMath was used.
|
||||
q = lambda r: (sqrt(r**2 - Rb**2) / Rb
|
||||
- asin((-r**2 + fRad**2 + Rc**2) / (2 * fRad * Rc)))
|
||||
q_prime = lambda r: (r / (sqrt(-Rb**2 + r**2) * Rb)
|
||||
+ r / (fRad * Rc * sqrt(1 - 1/4 * (r**2 - fRad**2 - Rc**2)**2 / (fRad**2 * Rc**2))))
|
||||
Rf = findRootNewton(q, q_prime, x_min=max(Rb, Rroot), x_max=Rc)
|
||||
|
||||
# ****** calculate angles (all in radians)
|
||||
pitchAngle = 2 * pi / Z # angle subtended by whole tooth
|
||||
baseToPitchAngle = genInvolutePolar(Rb, Rpitch)
|
||||
pitchToFilletAngle = baseToPitchAngle # profile starts at base circle
|
||||
if (filletWithinInvolute): # start profile at top of fillet
|
||||
pitchToFilletAngle -= genInvolutePolar(Rb, Rf)
|
||||
|
||||
filletWidth = sqrt(fRad**2 - (Rc - Rf)**2)
|
||||
filletAngle = atan(filletWidth / Rf)
|
||||
|
||||
# ****** generate Higuchi involute approximation
|
||||
fe = 1 # fraction of profile length at end of approx
|
||||
fs = 0.01 # fraction of length offset from base to avoid singularity
|
||||
if (filletWithinInvolute):
|
||||
fs = (Rf**2 - Rb**2) / (Ra**2 - Rb**2) # offset start to top of fillet
|
||||
|
||||
if split:
|
||||
# approximate in 2 sections, split 25% along the involute
|
||||
fm = fs + (fe - fs) / 4 # fraction of length at junction (25% along profile)
|
||||
dedInv = BezCoeffs(Rb, Ra, 3, fs, fm)
|
||||
addInv = BezCoeffs(Rb, Ra, 3, fm, fe)
|
||||
|
||||
# join the 2 sets of coeffs (skip duplicate mid point)
|
||||
inv = dedInv + addInv[1:]
|
||||
else:
|
||||
inv = BezCoeffs(Rb, Ra, 4, fs, fe)
|
||||
|
||||
# rotate all points to make the tooth symetric to the X axis
|
||||
inv = [rotate(pt, -baseToPitchAngle - pitchAngle / 4) for pt in inv]
|
||||
|
||||
# create the back profile of tooth (mirror image on X axis)
|
||||
invR = [mirror(pt) for pt in inv]
|
||||
|
||||
# ****** calculate section junction points R=back of tooth, Next=front of next tooth)
|
||||
fillet = toCartesian(Rf, -pitchAngle / 4 - pitchToFilletAngle) # top of fillet
|
||||
filletR = mirror(fillet) # flip to make same point on back of tooth
|
||||
rootR = toCartesian(Rroot, pitchAngle / 4 + pitchToFilletAngle + filletAngle)
|
||||
rootNext = toCartesian(Rroot, 3 * pitchAngle / 4 - pitchToFilletAngle - filletAngle)
|
||||
filletNext = rotate(fillet, pitchAngle) # top of fillet, front of next tooth
|
||||
|
||||
# Build the shapes using the provided WireBuilder
|
||||
thetas = [x * pitchAngle for x in range(Z)]
|
||||
|
||||
# Make sure we begin *exactly* where our last curve ends.
|
||||
# In theory start == rotate(end, angle_of_last_tooth), but in practice we have limited
|
||||
# precision. Especially if we don't have a fillet, we end at rootNext, not filletNext.
|
||||
# And even though these two should also be equal, they are calculated differently,
|
||||
# which is enough for the resulting wire not being closed any more.
|
||||
# So to be on the save side, we begin at rotate(end, angle_of_last_tooth), not start.
|
||||
if fRad > 0:
|
||||
w.move(rotate(filletNext, thetas[-1])) # start at top of front profile
|
||||
else:
|
||||
w.move(rotate(rootNext, thetas[-1])) # start at top of front profile
|
||||
|
||||
for theta in thetas:
|
||||
w.theta = theta
|
||||
if (not filletWithinInvolute):
|
||||
w.line(inv[0]) # line from fillet up to base circle
|
||||
|
||||
if split:
|
||||
w.curve(inv[1], inv[2], inv[3])
|
||||
w.curve(inv[4], inv[5], inv[6])
|
||||
w.arc(invR[6], Ra, 1) # arc across addendum circle
|
||||
w.curve(invR[5], invR[4], invR[3])
|
||||
w.curve(invR[2], invR[1], invR[0])
|
||||
else:
|
||||
w.curve(*inv[1:])
|
||||
w.arc(invR[-1], Ra, 1) # arc across addendum circle
|
||||
w.curve(*invR[-2::-1])
|
||||
|
||||
if (not filletWithinInvolute):
|
||||
w.line(filletR) # line down to topof fillet
|
||||
|
||||
if (rootNext[1] > rootR[1]): # is there a section of root circle between fillets?
|
||||
if fRad > 0:
|
||||
w.arc(rootR, fRad, 0) # back fillet
|
||||
w.arc(rootNext, Rroot, 1) # root circle arc
|
||||
|
||||
if fRad > 0:
|
||||
w.arc(filletNext, fRad, 0)
|
||||
|
||||
w.close()
|
||||
return w
|
||||
_create_involute_profile(
|
||||
wire_builder=w,
|
||||
module=m,
|
||||
number_of_teeth=Z,
|
||||
pressure_angle=radians(phi),
|
||||
split_involute=split,
|
||||
outer_height_coefficient=addCoeff,
|
||||
inner_height_coefficient=dedCoeff,
|
||||
inner_fillet_coefficient=filletCoeff)
|
||||
|
||||
|
||||
def CreateInternalGear(w, m, Z, phi,
|
||||
@@ -193,7 +66,7 @@ def CreateInternalGear(w, m, Z, phi,
|
||||
"""
|
||||
Create an internal gear
|
||||
|
||||
w is wirebuilder object (in which the gear will be constructed)
|
||||
w is wire builder 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, in degrees
|
||||
@@ -201,7 +74,7 @@ def CreateInternalGear(w, m, Z, phi,
|
||||
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.
|
||||
It in turn assumes, however, that the mating pinion uses 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)
|
||||
@@ -213,138 +86,242 @@ def CreateInternalGear(w, m, Z, phi,
|
||||
curves of degree 3, otherwise it will be made of one Bezier curve
|
||||
of degree 4
|
||||
"""
|
||||
# ****** external gear specifications
|
||||
addendum = addCoeff * m # distance from pitch circle to tip circle
|
||||
dedendum = dedCoeff * m # distance from pitch circle to root circle
|
||||
_create_involute_profile(
|
||||
wire_builder=w,
|
||||
module=m,
|
||||
number_of_teeth=Z,
|
||||
pressure_angle=radians(phi),
|
||||
split_involute=split,
|
||||
rotation=pi/Z, # rotate by half a tooth to align the "inner" tooth with the X-axis
|
||||
outer_height_coefficient=dedCoeff,
|
||||
inner_height_coefficient=addCoeff,
|
||||
outer_fillet_coefficient=filletCoeff)
|
||||
|
||||
# Calculate radii
|
||||
Rpitch = Z * m / 2 # pitch circle radius
|
||||
Rb = Rpitch*cos(radians(phi)) # base circle radius
|
||||
Ra = Rpitch - addendum # tip (addendum) circle radius
|
||||
Rroot = Rpitch + dedendum # root circle radius
|
||||
fRad = filletCoeff * m # fillet radius
|
||||
Rc = Rroot - fRad # radius at the center of the fillet circle
|
||||
|
||||
tipWithinInvolute = Ra > Rb # above the base circle we have the involute,
|
||||
# below we have a straight line towards the center.
|
||||
def _create_involute_profile(
|
||||
wire_builder,
|
||||
module,
|
||||
number_of_teeth,
|
||||
pressure_angle=radians(20.0),
|
||||
split_involute=True,
|
||||
rotation=radians(0),
|
||||
outer_height_coefficient=1.0,
|
||||
inner_height_coefficient=1.0,
|
||||
outer_fillet_coefficient=0.0,
|
||||
inner_fillet_coefficient=0.0):
|
||||
"""
|
||||
Create an involute gear profile in the given wire builder
|
||||
|
||||
# Calculate Rf: The radius where the involute ends and the fillet begins.
|
||||
# For a detailed explanation see the external gear.
|
||||
# Here, however, we substitute t with pi/2-q, i.e. we omit the extra -pi, as we are now
|
||||
# interested in the first quadrant: Again, our involute grows downwards but now the fillet
|
||||
# circle needs to approach the involute "from inside the tooth" and at the far end of the
|
||||
# involute. The fillet is now
|
||||
# rc(q) = sqrt(2*fRad*Rc*sin(q) + fRad**2 + Rc**2)
|
||||
# which can be inversed to
|
||||
# qc(r) = asin((r**2 - fRad**2 - Rc**2)/(2*fRad*Rc))
|
||||
# The involute does not change in regard to the external gear.
|
||||
# However, the simplification of assuming phi=0 is more questionable here as phi doesn't have
|
||||
# an upper bound from fRad any more but gets larger as the involute angle grows. Having a
|
||||
# non-zero phi in the original rc(q) makes solving impossible, so lets apply another trick:
|
||||
# If we cannot apply a polar angle to the position of the circle, we could do it for the
|
||||
# involute. Such a rotation doesn't have any influence on ri, so not on qi, but changes the
|
||||
# interpretation of it: The tangent angle of the involute experiences the same rotation as the
|
||||
# involute itself. So its is just a simple offset: Our q(r) becomes qi(r) - qc(i) - phi_corr,
|
||||
# where phi_corr is the amount we (virtually) turn our involute around the origin.
|
||||
if (fRad > 0):
|
||||
phi_corr = genInvolutePolar(Rb, Rroot) + atan(fRad / Rroot)
|
||||
This method can be used to create external as well as internal gear and spline profiles.
|
||||
Thus this method does not use the terms "addednum" and "dedendum" or "tip" and "root",
|
||||
but refers to the elements below the reference circle (i.e. towards the center) as "inner",
|
||||
and those above the reference circle (i.e. away from the center) as "outer".
|
||||
|
||||
For an external gear, outer_height is the addendum, inner_height is the dedendum,
|
||||
and inner_fillet is the root fillet.
|
||||
For an internal gear, inner_height is the addendum, outer_height is the dedendum,
|
||||
and outer_fillet is the root fillet.
|
||||
|
||||
The "_coefficient" suffix denotes values normalized by the module.
|
||||
"""
|
||||
|
||||
outer_height = outer_height_coefficient * module # external: addendum, internal: dedendum
|
||||
inner_height = inner_height_coefficient * module # external: dedendum, internal: addednum
|
||||
|
||||
# ****** calculate radii
|
||||
# All distances from the center of the profile start with "R".
|
||||
# As those are mostly used in mathematical formulae, we use "symbols" rather than "speaking
|
||||
# names" to keep them short. Otherwise the math becomes unreadable.
|
||||
Rref = number_of_teeth * module / 2 # reference circle radius
|
||||
Rb = Rref * cos(pressure_angle) # base circle radius
|
||||
Ro = Rref + outer_height # radius of outer circle (tip for external, root for internal gears)
|
||||
Ri = Rref - inner_height # radius of inner circle (root for external, tip for internal gears)
|
||||
|
||||
fi = inner_fillet_coefficient * module # fillet radius at inner circle (ext: root fillet)
|
||||
Rci = Ri + fi # radius at which the center of the inner fillet circle sits
|
||||
Rfi = Rci # radius at which the inner fillet ends (for now assuming some non-involute flank)
|
||||
|
||||
fo = outer_fillet_coefficient * module # fillet radius at outer circle (int: root fillet)
|
||||
Rco = Ro - fo # radius at which the center of the outer fillet circle sits
|
||||
Rfo = Ro # radius at which the outer fillet ends (for now assuming no outer fillet)
|
||||
|
||||
has_non_involute_flank = Rfi < Rb # above the base circle we have the involute,
|
||||
# below we have a straight line towards the center.
|
||||
has_inner_fillet = fi > 0
|
||||
has_outer_fillet = fo > 0
|
||||
|
||||
if has_inner_fillet and not has_non_involute_flank:
|
||||
# In this case we need tangency of the involute's start and the inner fillet circle.
|
||||
# It has to be somewhere between max(Rb,Ri) and Rci.
|
||||
# So we need the radius R from the origin depending on the tangent angle, for both:
|
||||
# - the involute: Rinv(t) = Rb * sqrt(1 + t**2), rationale: see below.
|
||||
# - the fillet circle: Rcirc(t) = ... a bit more complex, see below.
|
||||
# and then find the R where both tangents are equal.
|
||||
# As the tangent angle of the involute equals the parameter t, we can just take the
|
||||
# parametric polar representation of the involute as our first equation.
|
||||
# For a circle in parameter form, the tangent angle is pi/2 - t (for the first quadrant,
|
||||
# i.e. 0 <= t <= pi/2, and more than one quadrant is not of interest for us). Unfortunately,
|
||||
# the fillet circle is offset from the origin by (Rci,phi), so things becomes more messy:
|
||||
# Rcirc(t) = sqrt((Rci*cos(phi) + fi*cos(t))**2 + (Rci*sin(phi) + fi*sin(t))**2)
|
||||
# To get rid of the sin(t) part we assume phi "very small", i.e. sin(phi) becomes 0.
|
||||
# This is justyfied because Rci is much larger than fi and the parallax error is
|
||||
# neglectable. Next we substitute the parameter t by pi/2-q-pi. For one to account for the
|
||||
# tangent angle definitions (see above), and then to turn our cicle by 180° as for the
|
||||
# inner fillet we need the third quadrant of the circle (NB: we are looking at the upper
|
||||
# half of the right most tooth, i.e. the involute grows downwards!). This results in
|
||||
# sqrt(2*fi*Rci*cos(-1/2*pi - q) + fi**2 + Rci**2) which simplifies to
|
||||
# Rcirc(q) = sqrt(-2*fi*Rci*sin(q) + fi**2 + Rci**2)
|
||||
# which is the polar r for a given tangent angle q (with the simplification of phi being 0)
|
||||
# This can now be inverted to give the tangent angle at a given r:
|
||||
# Qcirc(r) = asin((-r**2 + fi**2 + Rci**2)/(2*fi*Rci))
|
||||
# For the involute we have Rinv(q) = Rb * sqrt(1 + q**2), with q=t as the circle is already
|
||||
# adapted, thus: Qinv(r) = +- sqrt(r**2 - Rb**2)/Rb
|
||||
# Now to find the r where the tangents match, our Rfi, we set Qcirc=Qinv and solve for r.
|
||||
# This doesn't seem to have an analytical solution, though, so let's apply Newton's
|
||||
# Method to find the root of q := Qinv - Qcirc over Rb...Rci, or for larger number
|
||||
# of teeth, Ri...Rci as in this case the base circle is below the inner circle.
|
||||
# The graph of q is strictly monotnonous and very steep, no surprises to expect.
|
||||
# To simplify the above equations and to find the derivative, SageMath was used.
|
||||
q = lambda r: (sqrt(r**2 - Rb**2) / Rb
|
||||
- asin((r**2 - fRad**2 - Rc**2) / (2 * fRad * Rc))
|
||||
- asin((-r**2 + fi**2 + Rci**2) / (2 * fi * Rci)))
|
||||
q_prime = lambda r: (r / (sqrt(-Rb**2 + r**2) * Rb)
|
||||
+ r / (fi * Rci * sqrt(1 - 1/4 * (r**2 - fi**2 - Rci**2)**2 / (fi**2 * Rci**2))))
|
||||
Rfi = findRootNewton(q, q_prime, x_min=max(Rb, Ri), x_max=Rci)
|
||||
|
||||
if has_outer_fillet:
|
||||
# In this case we need tangency of the involute's end and the outer fillet circle.
|
||||
# For a detailed explanation refer to the inner fillet above.
|
||||
# Here, however, we substitute t with pi/2-q, i.e. we omit the extra -pi, as we are now
|
||||
# interested in the first quadrant: Again, our involute grows downwards but now the fillet
|
||||
# circle needs to approach the involute "from inside the tooth" and at the far end of the
|
||||
# involute. The fillet is now
|
||||
# Rcirc(q) = sqrt(2*fo*Rco*sin(q) + fo**2 + Rco**2)
|
||||
# which can be inversed to
|
||||
# Qcirc(r) = asin((r**2 - fo**2 - Rco**2)/(2*fo*Rco))
|
||||
# The involute is the very same as for the inner fillet.
|
||||
# However, the simplification of assuming phi=0 is more questionable here as phi doesn't
|
||||
# have an upper bound from the fillet's radius any more but gets larger as the involute
|
||||
# angle grows. Having a non-zero phi in the original Rcirc(q) makes solving impossible,
|
||||
# so lets apply another trick:
|
||||
# If we cannot apply a polar angle to the position of the circle, we could do it for the
|
||||
# involute. Such a rotation doesn't have any influence on Rinv, so not on Qinv, but changes
|
||||
# the interpretation of it: The tangent angle of the involute experiences the same rotation
|
||||
# as the involute itself. So it is just a simple offset:
|
||||
# Our q(r) becomes Qinv(r) - Qcirc(r) - phi_corr, where phi_corr is the amount we
|
||||
# (virtually) rotate our involute around the origin. To bring the fillet circle back on
|
||||
# X-axis, we assume the (real) polar angle of its center position being fo below the
|
||||
# involute's end at Ro.
|
||||
phi_corr = genInvolutePolar(Rb, Ro) + atan(fo / Ro)
|
||||
q = lambda r: (sqrt(r**2 - Rb**2) / Rb
|
||||
- asin((r**2 - fo**2 - Rco**2) / (2 * fo * Rco))
|
||||
- phi_corr)
|
||||
q_prime = lambda r: (r / (sqrt(-Rb**2 + r**2) * Rb)
|
||||
- r / (fRad * Rc * sqrt(1 - 1/4 * (r**2 - fRad**2 - Rc**2)**2 / (fRad**2 * Rc**2))))
|
||||
Rf = findRootNewton(q, q_prime, x_min=max(Rb, Rc), x_max=Rroot)
|
||||
else:
|
||||
Rf = Rroot # no fillet
|
||||
- r / (fo * Rco * sqrt(1 - 1/4 * (r**2 - fo**2 - Rco**2)**2 / (fo**2 * Rco**2))))
|
||||
Rfo = findRootNewton(q, q_prime, x_min=max(Rb, Rco), x_max=Ro)
|
||||
|
||||
# ****** calculate angles (all in radians)
|
||||
pitchAngle = 2 * pi / Z # angle subtended by whole tooth
|
||||
baseToPitchAngle = genInvolutePolar(Rb, Rpitch)
|
||||
tipToPitchAngle = baseToPitchAngle
|
||||
if (tipWithinInvolute): # start profile at tip, not base
|
||||
tipToPitchAngle -= genInvolutePolar(Rb, Ra)
|
||||
pitchToFilletAngle = genInvolutePolar(Rb, Rf) - baseToPitchAngle;
|
||||
angular_pitch = 2 * pi / number_of_teeth # angle subtended by complete tooth/space pair
|
||||
base_to_ref = genInvolutePolar(Rb, Rref) # angle between base and refernce circle
|
||||
ref_to_stop = genInvolutePolar(Rb, Rfo) - base_to_ref # angle between ref and involute end
|
||||
if has_non_involute_flank: # involute starts at base circle
|
||||
start_to_ref = base_to_ref
|
||||
else: # involute starts at top of inner fillet, i.e. somewhat above the base circle
|
||||
start_to_ref = base_to_ref - genInvolutePolar(Rb, Rfi)
|
||||
|
||||
filletWidth = sqrt(fRad**2 - (Rf - Rc)**2)
|
||||
filletAngle = atan(filletWidth / Rf)
|
||||
inner_fillet_width = sqrt(fi**2 - (Rci - Rfi)**2)
|
||||
inner_fillet_angle = atan(inner_fillet_width / Rfi)
|
||||
outer_fillet_width = sqrt(fo**2 - (Rfo - Rco)**2)
|
||||
outer_fillet_angle = atan(outer_fillet_width / Rfo)
|
||||
|
||||
# ****** generate Higuchi involute approximation
|
||||
fe = 1 # fraction of profile length at end of approx
|
||||
fe = 1 # fraction of involute length at end of approx
|
||||
fs = 0.01 # fraction of length offset from base to avoid singularity
|
||||
if (tipWithinInvolute):
|
||||
fs = (Ra**2 - Rb**2) / (Rf**2 - Rb**2) # offset start to tip
|
||||
if (not has_non_involute_flank):
|
||||
fs = (Rfi**2 - Rb**2) / (Rfo**2 - Rb**2) # offset start to top of fillet
|
||||
|
||||
if split:
|
||||
if split_involute:
|
||||
# approximate in 2 sections, split 25% along the involute
|
||||
fm = fs + (fe - fs) / 4 # fraction of length at junction (25% along profile)
|
||||
addInv = BezCoeffs(Rb, Rf, 3, fs, fm)
|
||||
dedInv = BezCoeffs(Rb, Rf, 3, fm, fe)
|
||||
|
||||
# join the 2 sets of coeffs (skip duplicate mid point)
|
||||
invR = addInv + dedInv[1:]
|
||||
fm = fs + (fe - fs) / 4 # fraction of length at junction (25% along involute)
|
||||
part1 = BezCoeffs(Rb, Rfo, 3, fs, fm)
|
||||
part2 = BezCoeffs(Rb, Rfo, 3, fm, fe)
|
||||
inv = part1 + part2[1:] # join the 2 sets of coeffs (skip duplicate mid point)
|
||||
else:
|
||||
invR = BezCoeffs(Rb, Rf, 4, fs, fe)
|
||||
inv = BezCoeffs(Rb, Rfo, 4, fs, fe)
|
||||
|
||||
# rotate all points to make the tooth symetric to the X axis
|
||||
invR = [rotate(pt, -baseToPitchAngle + pitchAngle / 4) for pt in invR]
|
||||
inv = [rotate(pt, -base_to_ref - angular_pitch / 4) for pt in inv]
|
||||
|
||||
# create the front profile of tooth (mirror image on X axis)
|
||||
inv = [mirror(pt) for pt in invR]
|
||||
# create the back profile of tooth (mirror image on X axis)
|
||||
invR = [mirror(pt) for pt in inv]
|
||||
|
||||
# ****** calculate section junction points R=back of tooth, Next=front of next tooth)
|
||||
fillet = inv[-1] # end of fillet, front of tooth; right where the involute starts
|
||||
tip = toCartesian(Ra, -pitchAngle / 4 + tipToPitchAngle) # tip, front of tooth
|
||||
tipR = mirror(tip)
|
||||
rootR = toCartesian(Rroot, pitchAngle / 4 + pitchToFilletAngle + filletAngle)
|
||||
rootNext = toCartesian(Rroot, 3 * pitchAngle / 4 - pitchToFilletAngle - filletAngle)
|
||||
filletNext = rotate(fillet, pitchAngle) # end of fillet, front of next tooth
|
||||
# ****** calculate section junction points.
|
||||
# Those are the points where the named element ends (start is the end of the previous element).
|
||||
# Suffix _back is back of this tooth, suffix _next is front of next tooth.
|
||||
inner_fillet = toCartesian(Rfi, -angular_pitch / 4 - start_to_ref) # top of fillet
|
||||
inner_fillet_back = mirror(inner_fillet) # flip to make same point on back of tooth
|
||||
inner_circle_back = toCartesian(Ri, angular_pitch / 4 + start_to_ref + inner_fillet_angle)
|
||||
inner_circle_next = toCartesian(Ri, 3 * angular_pitch / 4 - start_to_ref - inner_fillet_angle)
|
||||
inner_fillet_next = rotate(inner_fillet, angular_pitch) # top of fillet, front of next tooth
|
||||
outer_fillet = toCartesian(Ro, -angular_pitch / 4 + ref_to_stop + outer_fillet_angle)
|
||||
outer_circle = mirror(outer_fillet)
|
||||
|
||||
# Build the shapes using the provided WireBuilder
|
||||
thetas = [x * pitchAngle for x in range(Z)]
|
||||
# ****** build the gear profile using the provided wire builder
|
||||
thetas = [x * angular_pitch + rotation for x in range(number_of_teeth)]
|
||||
|
||||
# Make sure we begin *exactly* where our last curve ends.
|
||||
# In theory start == rotate(end, angle_of_last_tooth), but in practice we have limited
|
||||
# precision. Especially if we don't have a fillet, we end at rootNext, not filletNext.
|
||||
# And even though these two should also be equal, they are calculated differently,
|
||||
# which is enough for the resulting wire not being closed any more.
|
||||
# precision. Especially if we don't have an inner fillet, we end at inner_circle_next,
|
||||
# not inner_fillet_next.
|
||||
# And even though these two should also be equal (if no inner fillet), they are calculated
|
||||
# differently, which is enough for the resulting wire not being closed any more.
|
||||
# So to be on the save side, we begin at rotate(end, angle_of_last_tooth), not start.
|
||||
if fRad > 0:
|
||||
w.move(rotate(filletNext, thetas[-1])) # start at top of front profile
|
||||
if has_inner_fillet:
|
||||
wire_builder.move(rotate(inner_fillet_next, thetas[-1]))
|
||||
else:
|
||||
w.move(rotate(rootNext, thetas[-1])) # start at top of front profile
|
||||
wire_builder.move(rotate(inner_circle_next, thetas[-1]))
|
||||
|
||||
for theta in thetas:
|
||||
w.theta = theta
|
||||
if split:
|
||||
w.curve(inv[5], inv[4], inv[3])
|
||||
w.curve(inv[2], inv[1], inv[0])
|
||||
wire_builder.theta = theta
|
||||
|
||||
if (has_non_involute_flank):
|
||||
wire_builder.line(inv[0]) # line from inner fillet up to base circle
|
||||
|
||||
# front involute
|
||||
if split_involute:
|
||||
wire_builder.curve(inv[1], inv[2], inv[3])
|
||||
wire_builder.curve(inv[4], inv[5], inv[6])
|
||||
else:
|
||||
w.curve(*inv[-2::-1])
|
||||
wire_builder.curve(*inv[1:])
|
||||
|
||||
if (not tipWithinInvolute):
|
||||
w.line(tip) # line to tip down from base circle
|
||||
# is there a section of outer circle between outer fillets?
|
||||
if (outer_circle[1] > outer_fillet[1]):
|
||||
if has_outer_fillet:
|
||||
wire_builder.arc(outer_fillet, fo, 1) # outer fillet
|
||||
wire_builder.arc(outer_circle, Ro, 1) # arc across the outer circle
|
||||
|
||||
w.arc(tipR, Ra, 1) # arc across addendum circle
|
||||
if has_outer_fillet:
|
||||
wire_builder.arc(invR[-1], fo, 1) # outer fillet on back side
|
||||
|
||||
if (not tipWithinInvolute):
|
||||
w.line(invR[0]) # line up to the base circle
|
||||
|
||||
if split:
|
||||
w.curve(invR[1], invR[2], invR[3])
|
||||
w.curve(invR[4], invR[5], invR[6])
|
||||
# back involute
|
||||
if split_involute:
|
||||
wire_builder.curve(invR[5], invR[4], invR[3])
|
||||
wire_builder.curve(invR[2], invR[1], invR[0])
|
||||
else:
|
||||
w.curve(*invR[1:])
|
||||
wire_builder.curve(*invR[-2::-1])
|
||||
|
||||
if (rootNext[1] > rootR[1]): # is there a section of root circle between fillets?
|
||||
if fRad > 0:
|
||||
w.arc(rootR, fRad, 1) # back fillet
|
||||
w.arc(rootNext, Rroot, 1) # root circle arc
|
||||
if (has_non_involute_flank):
|
||||
wire_builder.line(inner_fillet_back) # line down to top of inner fillet
|
||||
|
||||
if fRad > 0:
|
||||
w.arc(filletNext, fRad, 1)
|
||||
# is there a section of inner circle between inner fillets?
|
||||
if (inner_circle_next[1] > inner_circle_back[1]):
|
||||
if has_inner_fillet:
|
||||
wire_builder.arc(inner_circle_back, fi, 0) # inner fillet on back side
|
||||
wire_builder.arc(inner_circle_next, Ri, 1) # arc across the inner circle
|
||||
|
||||
w.close()
|
||||
return w
|
||||
if has_inner_fillet:
|
||||
wire_builder.arc(inner_fillet_next, fi, 0)
|
||||
|
||||
wire_builder.close()
|
||||
|
||||
|
||||
def genInvolutePolar(Rb, R):
|
||||
@@ -380,7 +357,7 @@ def findRootNewton(f, f_prime, x_min, x_max):
|
||||
# As initial guess let's take the middle of our input range
|
||||
x = (x_min + x_max) / 2
|
||||
|
||||
# FreeCAD.Base.Precision.intersection() is 1e-9, but file doesn't depend on FreeCAD,
|
||||
# FreeCAD.Base.Precision.intersection() is 1e-9, but this file doesn't depend on FreeCAD,
|
||||
# a property very handy when testing in isolation, so let's keep it.
|
||||
PRECISION_INTERSECTION = 1e-9
|
||||
|
||||
|
||||
Reference in New Issue
Block a user