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:
Jonas Bähr
2023-03-11 22:42:13 +01:00
committed by Uwe
parent b3ab9c1c3c
commit 4eafedb20f
2 changed files with 234 additions and 237 deletions

View File

@@ -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

View File

@@ -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