Gimbal Gyroscope Fidget
A desk fidget of four nested rings and a center disc, each hung in the next larger ring on a pair of radial pins like a gimbal. The pin axes alternate by 90 degrees from one ring to the next, so every part spins on an axis at right angles to its neighbour's. The whole thing prints in one go, lying flat: every pin sits in its hole with 0.4 mm of clearance and the rings keep a 0.7 mm radial gap, so the parts come off the bed already assembled and turn freely after a first twist. The default is 60 x 60 x 6 mm with 4 x 6 mm ring sections and 3 mm pins; no supports are needed, since the pin holes are short horizontal bridges. If the rings are stuck, raise the clearance parameter; if they rattle, lower it. Number of rings (3 to 5), outer diameter, ring width and height, pin diameter and clearance are parameters.
# Gimbal Gyroscope Fidget - nested rings on alternating hinges, printed in place
#
# Four flat rings and a center disc sit one inside the other. Each part hangs
# in the next larger ring on a pair of radial pins (a gimbal): the pins of
# part k point along 0/180 deg, those of part k+1 along 90/270 deg, and so on,
# so every part can spin on an axis at right angles to its neighbour's. All
# parts are printed at once, lying flat, with a small gap at every pin and
# between every pair of rings, so they come off the bed already assembled and
# spin freely after a first twist.
#
# Hinge: the pin (a short horizontal cylinder with a 45 deg chamfered tip) is
# on the inner part and reaches across the radial gap into a blind round hole
# in the wall of the outer ring. The hole is horizontal, so its roof is a
# bridge of less than 4 mm - short enough to print without support; the pin's
# underside is a 3 mm round overhang, which prints fine as well.
#
# Layout: rings lie flat (axis Z), bottom on z = 0. Print as laid out, no
# supports. Part 0 is the outermost ring, part `count` is the center disc.
@param 3..5 desc:"Number of rings (plus the center disc)" group:"Size"
count = 4
@param 50..90 step:5 desc:"Outer diameter of the largest ring (mm)" group:"Size"
diameter = 60
@param 3..5 step:0.5 desc:"Ring width, radial (mm)" group:"Size"
ring_w = 4
@param 6..8 step:0.5 desc:"Ring height (mm)" group:"Size"
ring_h = 6
@param 2.5..3.5 step:0.5 desc:"Pin diameter (mm)" group:"Hinge"
pin_d = 3
@param 0.25..0.6 step:0.05 desc:"Clearance around each pin (mm) - tune for your printer" group:"Print"
clearance = 0.4
gap = clearance + 0.3 # radial gap between a ring and the next one inside
pin_r = pin_d / 2
hole_r = pin_r + clearance
hole_d = ring_w - 1.5 # blind hole depth: leaves a 1.5 mm outer wall
tip_c = 0.6 # 45 deg chamfer on the pin tip
zc = ring_h / 2 # hinge axis height
# Radii of ring k (0 = outermost) and the pin direction of part k
def rout(k) = diameter / 2 - k * (ring_w + gap)
def rin(k) = rout(k) - ring_w
def ang(k) = (k % 2) * 90
disc_r = rin(count - 1) - gap
# One pin along +X from x0 to x1 (tip chamfered), radius grown by e for the fit checks
def pin(x0, x1, e) = polyline [(x0, 0), (x0, pin_r + e), (x1 - tip_c, pin_r + e), (x1, pin_r - tip_c + e), (x1, 0)] | revolve X
def pin_a(k, i) = ang(k) + 180 * i
def pins(k, x0, x1, e) = union [pin(x0, x1, e) | translate 0 0 zc | rotate 0 0 pin_a(k, i) for i in range(2)]
# The two blind holes in ring k (for the pins of part k+1)
def hole_x(k) = rin(k) + (hole_d - 1) / 2
def hole_a(k, i) = ang(k + 1) + 180 * i
def holes(k) = union [cylinder hole_r (hole_d + 1) | rotate 0 90 0 | translate hole_x(k) 0 zc | rotate 0 0 hole_a(k, i) for i in range(2)]
# Body of part k without pins: ring (outer radius grown by e) or the center disc
def annulus(k, e) = (circle (rout(k) + e)) - (circle (rin(k)))
def ring_body(k, e) = annulus(k, e) | extrude ring_h | diff (holes(k))
def disc(e) = cylinder (disc_r + e) ring_h | floor | chamfer 0.8
def body(k, e) = if k == count then disc(e) else ring_body(k, e)
# Part k complete: body plus the pins that hang it in ring k-1 (part 0 has none)
def part(k, e) = if k == 0 then body(0, e)
else (body(k, e) + pins(k, (if k == count then disc_r else rout(k)) - 1, rin(k - 1) + hole_d - clearance + e, e))
gyro = union [part(k, 0) for k in range(count + 1)]
# ---- Checks
assert gyro.solids == count + 1 # every part is a separate solid: nothing fused
assert gyro.bbox.x ~ diameter and gyro.bbox.y ~ diameter
assert gyro.bbox.z ~ ring_h
assert gyro.bbox.min[2] >= -0.001 # lies on the bed
assert zc - hole_r >= 1.0 # at least 1 mm of ring above and below each hole
assert disc_r >= 5 # the center disc is big enough to carry its pins
# The outermost ring stands on a flat annular face: its lowest 0.2 mm is the full annulus
foot = part(0, 0) * (box (diameter + 2) (diameter + 2) 0.2 | floor)
assert foot.volume ~ (0.2 * pi * (rout(0) ** 2 - rin(0) ** 2))
# Pins: clear all round at the design clearance ...
assert clear(part(0, 0), part(1, 0))
assert clear(part(1, 0), part(2, 0))
assert clear(part(count - 1, 0), part(count, 0))
# ... and a copy whose pins are grown by clearance + 0.05 collides: the gap is not larger than designed
assert clear(part(0, 0), part(1, clearance + 0.05)) == false
assert clear(part(1, 0), part(2, clearance + 0.05)) == false
assert clear(part(count - 1, 0), part(count, clearance + 0.05)) == false
# Rings nest with the radial gap, and no more
assert clear(body(0, 0), body(1, gap - 0.01))
assert clear(body(0, 0), body(1, gap + 0.05)) == false
# A ring tilted 45 deg on its hinge still clears the ring around it
tilt1 = part(1, 0) | rotate 45 0 0 origin:(0, 0, zc)
tilt2 = part(2, 0) | rotate 0 45 0 origin:(0, 0, zc)
assert clear(part(0, 0), tilt1)
assert clear(part(1, 0), tilt2)
print "ring outer radii" [rout(k) for k in range(count)] "disc radius" disc_r
print "pin / hole diameter" pin_d (2 * hole_r) "radial gap" gap
print "each adjacent pair clear" [clear(part(k, 0), part(k + 1, 0)) for k in range(count)]
gyro