Planetary Gearbox
A hand-turned planetary (epicyclic) gear demonstrator: a sun gear with a knob drives three planet gears that roll inside a ring gear built into the housing, and the carrier plate holding the planet pins is the output, turning once for every four turns of the knob with the default 12 / 12 / 36 teeth (ratio 1 + ring / sun). Every tooth is a true involute, with undercut and root fillets cut by a simulated rack; the ring gear is an internal gear made by subtracting a negative gear whose teeth are the ring's tooth spaces. The ring tooth count follows from the sun and planet counts, the three planet phases are computed so each planet meshes with the sun and the ring at once, and the meshes are checked at four carrier positions plus a backlash test. Seven printed parts (housing with ring gear and center pin, sun gear with boss and square key, three planets, carrier with three pins and a pointer notch, knob); the assembly measures 85 x 85 x 30 mm. All parts print flat with no supports (part = print, or one part at a time). Parameters: module, sun and planet tooth counts, pressure angle, face width, backlash, pin clearance, and the carrier angle shown in the assembled view.
# Planetary Gearbox - sun, three planets, ring gear and carrier, from module and tooth counts
#
# A hand-turned planetary (epicyclic) gear demonstrator. The ring gear is
# part of the housing and stays fixed; a knob on the sun gear is the input;
# the carrier plate that holds the three planet pins is the output. With the
# default teeth (sun 12, planet 12, ring 36) the carrier turns once for every
# 4 turns of the knob (ratio 1 + zr / zs), in the same direction.
#
# Every tooth is a true involute. External gears are generated as in the
# spur gear set: involute flanks computed point by point, undercut and root
# fillet cut by a simulated rack. The ring gear is an internal gear: the
# housing bore minus a "negative" gear whose teeth are the ring's tooth
# spaces (involute flanks, thickness widened by the backlash instead of
# thinned). The ring tooth count follows from zr = zs + 2 zp, and three
# equally spaced planets need (zs + zr) divisible by 3 - both are checked.
# The planet phases are computed so each planet meshes with the sun and the
# ring at once; the meshes are verified at four carrier positions, and the
# backlash is verified by turning one planet until its flanks touch.
#
# Parts: housing (ring gear + floor + center pin), sun gear with a boss and
# a square key, three planet gears, carrier plate with three pins and a
# pointer notch, knob. Assembly: sun on the center pin, planets around it,
# carrier (pins down) into the planet bores, knob pressed on the square key.
# Print orientation: everything flat as laid out (part = "print"), gears
# tooth-side up, carrier pins up. No supports.
@param choices:["assembled", "print", "housing", "sun", "planet", "carrier", "knob"] desc:"Output: the assembled gearbox, the print layout, or one part"
part = "assembled"
@param 0..360 step:5 desc:"Carrier angle shown in the assembled view (deg)"
view_angle = 0
@param 1..3 step:0.25 desc:"Module m (mm) - tooth size, pitch diameter = m x z" group:"Gear"
m = 2.0
@param 8..30 desc:"Sun teeth zs" group:"Gear"
zs = 12
@param 8..30 desc:"Planet teeth zp (ring teeth = zs + 2 zp)" group:"Gear"
zp = 12
@param 14.5..25 step:0.5 desc:"Pressure angle (deg)" group:"Gear"
pa = 20
@param 4..20 desc:"Face width / gear thickness (mm)" group:"Gear"
face = 8
@param 0.1..0.8 step:0.05 desc:"Total circular backlash at each mesh (mm)" group:"Gear"
backlash = 0.3
@param 0.1..0.5 step:0.05 desc:"Running clearance of pins and bosses, per side (mm)" group:"Fit"
fit = 0.25
# ---------------------------------------------------------------- geometry
zr = zs + 2 * zp # ring teeth
rcar = m * (zs + zp) / 2 # planet center radius
n = 12 # involute points per flank
nr = 30 # rack cutter positions per side
wall = 4 # housing wall outside the ring root
floor_t = 3
carrier_t = 4
pin_r = 2.5 # center pin and carrier pins
boss_r = 5 # sun boss through the carrier
key = 6 # square key for the knob
knob_r = 12
assert (zs + zr) % 3 == 0 # three planets fit at 120 deg only then
assert zs >= 8 and zp >= 8
def rp(z) = m * z / 2 # pitch radius
def rb(z) = rp(z) * cos(pa) # base radius
def ra(z) = rp(z) + m # tip radius (addendum m)
def rf(z) = rp(z) - 1.25 * m # root radius (dedendum 1.25 m)
def r0(z) = max(rb(z), rf(z)) # involute starts here
def inv(z, r) = deg(tan(acos(rb(z) / r))) - acos(rb(z) / r)
def half(z, r) = 90 / z + inv(z, rp(z)) - inv(z, r) - deg(backlash / 4 / rp(z))
def fr(z, k) = r0(z) + (ra(z) - r0(z)) * k / n
def px(r, t) = r * cos(t)
def py(r, t) = r * sin(t)
# External tooth centered on +X (see the spur gear set for the derivation)
def tooth(z) = sketch [
(px(rf(z) - 0.3 * m, -half(z, r0(z))), py(rf(z) - 0.3 * m, -half(z, r0(z)))),
(px(r0(z), -half(z, r0(z))), py(r0(z), -half(z, r0(z)))),
spline [(px(fr(z, k), -half(z, fr(z, k))), py(fr(z, k), -half(z, fr(z, k)))) for k in range(1, n + 1)],
arc (px(ra(z), -half(z, ra(z))), py(ra(z), -half(z, ra(z)))) (ra(z), 0) (px(ra(z), half(z, ra(z))), py(ra(z), half(z, ra(z)))),
spline [(px(fr(z, k), half(z, fr(z, k))), py(fr(z, k), half(z, fr(z, k)))) for k in range(n - 1, -1, -1)],
(px(rf(z) - 0.3 * m, half(z, r0(z))), py(rf(z) - 0.3 * m, half(z, r0(z)))),
(px(rf(z) - 0.3 * m, -half(z, r0(z))), py(rf(z) - 0.3 * m, -half(z, r0(z))))
]
# Rack cutter rolled past the tooth: undercut on small gears and the root fillet
def d0(z) = rp(z) * sin(pa) ** 2 + 0.02
def hw(d) = (pi * m / 2 + backlash / 2) / 2 - d * tan(pa)
def vc(z, s) = s * pi * m / 2
def gx(u, v, f) = u * cos(deg(f)) + v * sin(deg(f))
def gy(u, v, f) = -u * sin(deg(f)) + v * cos(deg(f))
def rk(z, s, f, u, v) = (gx(u, vc(z, s) + v + rp(z) * f, f), gy(u, vc(z, s) + v + rp(z) * f, f))
def cutter(z, s, f) = polyline [
rk(z, s, f, rp(z) - d0(z), -hw(d0(z))),
rk(z, s, f, rp(z) - 1.25 * m, -hw(1.25 * m)),
rk(z, s, f, rp(z) - 1.25 * m, hw(1.25 * m)),
rk(z, s, f, rp(z) - d0(z), hw(d0(z)))
]
def fpos(z, i) = 2.5 * m / rp(z) * (i - nr) / nr
def rack(z) = (union [cutter(z, -1, fpos(z, i)) for i in range(2 * nr + 1)]) + (union [cutter(z, 1, fpos(z, i)) for i in range(2 * nr + 1)])
def cut_tooth(z) = if d0(z) < 1.25 * m - 0.05 then (tooth(z) - rack(z)) else tooth(z)
def gear2d(z) = cut_tooth(z) | polar z 0 orient:true | union (circle rf(z))
# Internal (ring) gear: the negative gear whose teeth are the ring's spaces.
# Ring addendum m (tip at rp - m), dedendum 1.25 m (root at rp + 1.25 m),
# space widened by backlash / 2 at the pitch circle.
def rfi(z) = rp(z) - m
def rai(z) = rp(z) + 1.25 * m
def r0i(z) = max(rb(z), rfi(z))
def halfi(z, r) = 90 / z + inv(z, rp(z)) - inv(z, r) + deg(backlash / 4 / rp(z))
def fri(z, k) = r0i(z) + (rai(z) - r0i(z)) * k / n
def itooth(z) = sketch [
(px(rfi(z) - 0.3 * m, -halfi(z, r0i(z))), py(rfi(z) - 0.3 * m, -halfi(z, r0i(z)))),
(px(r0i(z), -halfi(z, r0i(z))), py(r0i(z), -halfi(z, r0i(z)))),
spline [(px(fri(z, k), -halfi(z, fri(z, k))), py(fri(z, k), -halfi(z, fri(z, k)))) for k in range(1, n + 1)],
arc (px(rai(z), -halfi(z, rai(z))), py(rai(z), -halfi(z, rai(z)))) (rai(z), 0) (px(rai(z), halfi(z, rai(z))), py(rai(z), halfi(z, rai(z)))),
spline [(px(fri(z, k), halfi(z, fri(z, k))), py(fri(z, k), halfi(z, fri(z, k)))) for k in range(n - 1, -1, -1)],
(px(rfi(z) - 0.3 * m, halfi(z, r0i(z))), py(rfi(z) - 0.3 * m, halfi(z, r0i(z)))),
(px(rfi(z) - 0.3 * m, -halfi(z, r0i(z))), py(rfi(z) - 0.3 * m, -halfi(z, r0i(z))))
]
def ring_neg2d(z) = itooth(z) | polar z 0 orient:true | union (circle rfi(z))
# ---------------------------------------------------------------- phases
# Sun tooth on +X. Planet k sits at 120 k deg with a tooth space facing the
# sun, corrected by the sun's tooth offset there. The ring is turned so that
# at planet 0 it presents the conjugate of the planet's outer side.
def dsun(k) = (k * 120) % (360 / zs)
def prot(k) = k * 120 + 180 + 180 / zp + dsun(k) * zs / zp
eps = if zp % 2 == 0 then 180 / zp else 0
ring_rot = eps * zp / zr
ratio = 1 + zr / zs
# ---------------------------------------------------------------- parts (local, bottom on z = 0)
rh = rai(zr) + wall # housing outer radius
ring_h = 0.3 + face + 0.2 # ring gear height above the floor
sun2d = gear2d(zs) - (circle (pin_r + fit))
sun_gear = sun2d | extrude face
sun_boss = cylinder boss_r (carrier_t + 2) | floor | translate 0 0 (face - 0.5)
sun_key = box key key 5 | floor | translate 0 0 (face + carrier_t + 1.5)
sun_bore = cylinder (pin_r + fit) (face + 2) | floor | translate 0 0 -1 # blind bore up into the boss
sun = sun_gear + sun_boss + sun_key - sun_bore
planet2d = gear2d(zp) - (circle (pin_r + fit))
planet = planet2d | extrude face
housing_body = cylinder rh (floor_t + ring_h) | floor
ring_cut = ring_neg2d(zr) | rotate ring_rot | extrude (ring_h + 1) | translate 0 0 floor_t
center_pin = cylinder pin_r (face + 1) | floor | edges >Z | chamfer 0.5 | translate 0 0 floor_t
housing = housing_body - ring_cut + center_pin
carrier_disc = (circle rh) - (circle (boss_r + fit)) | extrude carrier_t
carrier_pins = union [cylinder pin_r face | floor | edges >Z | chamfer 0.5 | translate (rcar * cos(120 * k)) (rcar * sin(120 * k)) carrier_t for k in range(3)]
notch = polyline [(rh + 1, -3), (rh - 4, 0), (rh + 1, 3)] | extrude (carrier_t + 2) | translate 0 0 -1
carrier = carrier_disc + carrier_pins - notch
knob_disc = (circle knob_r) - (rect (key + 0.2) (key + 0.2)) | extrude 5
knob_peg = cylinder 3 8 | floor | edges >Z | chamfer 0.5 | translate (knob_r - 4) 0 5
knob = knob_disc + knob_peg
assert sun.solids == 1 and planet.solids == 1 and housing.solids == 1
assert carrier.solids == 1 and knob.solids == 1
assert sun.bbox.z ~ (face + carrier_t + 6.5)
assert housing.bbox.x ~ (2 * rh)
# ---------------------------------------------------------------- tooth checks
# Tips touch a circle 0.05 inside the nominal tip radius and clear one 0.01 outside
def ring_out(r) = (cylinder (r + 5) (face * 3)) - (cylinder r (face * 4))
assert clear(sun, ring_out(ra(zs) + 0.01)) and clear(ring_out(ra(zs) - 0.05), sun) == false
assert clear(planet, ring_out(ra(zp) + 0.01)) and clear(ring_out(ra(zp) - 0.05), planet) == false
# Ring tips touch a tube 0.01 outside the bore circle rp - m and clear one 0.05 inside it
def ring_in(r) = (cylinder r face | floor) - (cylinder (r - 5) (face * 2)) # above the floor plate
ring3d = housing | translate 0 0 (-floor_t - 0.3)
assert clear(ring3d, ring_in(rfi(zr) - 0.05)) and clear(ring3d, ring_in(rfi(zr) + 0.01)) == false
print "pitch dia sun/planet/ring" (m * zs) (m * zp) (m * zr) "ratio" ratio "planet center radius" rcar
# ---------------------------------------------------------------- mesh checks
# Carrier at angle c: sun turns c (1 + zr/zs), planet k spins c (1 - zr/zp) about
# its own axis while its center moves to 120 k + c. Nothing touches at four
# positions spanning one planet pitch along the ring.
def planet_at(k, c) = planet | rotate 0 0 (prot(k) + c * (1 - zr / zp)) | translate (rcar * cos(120 * k + c)) (rcar * sin(120 * k + c)) 0
def planets_at(c) = union [planet_at(k, c) for k in range(3)]
def sun_at(c) = sun | rotate 0 0 (c * ratio)
cp = 360 / zr # carrier angle for one planet pitch on the ring
p0 = planets_at(0)
p1 = planets_at(cp / 4)
p2 = planets_at(cp / 2)
p3 = planets_at(3 * cp / 4)
assert p0.volume ~ (3 * planet.volume) tol:0.1% # planets never overlap each other
assert clear(sun_at(0), p0) and clear(ring3d, p0)
assert clear(sun_at(cp / 4), p1) and clear(ring3d, p1)
assert clear(sun_at(cp / 2), p2) and clear(ring3d, p2)
assert clear(sun_at(3 * cp / 4), p3) and clear(ring3d, p3)
# Backlash: planet 0 may turn about +-(backlash / 2) / rp before a flank
# touches the sun or the ring. 0.75 of that is clear, 1.15 collides.
dl = deg(backlash / 2 / rp(zp))
def p0_turn(d) = planet | rotate 0 0 (prot(0) + d) | translate rcar 0 0
assert clear(sun, p0_turn(0.75 * dl)) and clear(sun, p0_turn(-0.75 * dl))
assert clear(ring3d, p0_turn(0.75 * dl)) and clear(ring3d, p0_turn(-0.75 * dl))
assert clear(sun, p0_turn(1.15 * dl)) == false and clear(sun, p0_turn(-1.15 * dl)) == false
assert clear(ring3d, p0_turn(1.15 * dl)) == false and clear(ring3d, p0_turn(-1.15 * dl)) == false
# ---------------------------------------------------------------- assembly
zg = floor_t + 0.3 # gear bottoms
sun_asm = sun_at(view_angle) | translate 0 0 zg
planets_asm = planets_at(view_angle) | translate 0 0 zg
carrier_asm = carrier | rotate 180 0 0 | rotate 0 0 view_angle | translate 0 0 (zg + face + 0.5 + carrier_t)
knob_asm = knob | rotate 0 0 (view_angle * ratio) | translate 0 0 (zg + face + carrier_t + 1.5)
assert clear(housing, sun_asm)
assert clear(housing, planets_asm)
assert clear(housing, carrier_asm)
assert clear(carrier_asm, sun_asm) and clear(carrier_asm, planets_asm) and clear(carrier_asm, knob_asm)
assert clear(knob_asm, sun_asm)
asm = housing + sun_asm + planets_asm + carrier_asm + knob_asm
# ---------------------------------------------------------------- print layout
$gx = 2 * rh + 10
carrier_prt = carrier | translate $gx 0 0
sun_prt = sun | translate $gx (-(rh + 20)) 0
knob_prt = knob | translate ($gx + 35) (-(rh + 20)) 0
planets_prt = union [planet | translate (40 * k - 40) (-(rh + 20)) 0 for k in range(3)]
$print = housing + carrier_prt + sun_prt + knob_prt + planets_prt
if part == "assembled" then asm
else if part == "print" then $print
else if part == "housing" then housing
else if part == "sun" then sun
else if part == "planet" then planet
else if part == "carrier" then carrier
else knob