CHAPTER 05 / 06
Chapter 5: A worm that really bends
The Worm Farm needs its farmer. A curved solid still stays rigid after printing. Our worm has separate links: a ball on one link sits inside a socket on the next. The links can move while staying captured.
This is the hardest mechanism in the book. Start with two links. Once that joint works, making a longer worm is repetition.
Find the nine moving pieces

The complete worm has a head, seven body links, and a tail. They are printed together in a straight row. They must remain nine separate solids, even though the balls are already inside their sockets. The straight printing position does not prevent later bending.
Try the two-link joint sample first. It is the same ball and socket, without the face or long row. After printing, let it cool and gently check movement. Do not force a fused sample; adjust the gap and print another pair.

A ball inside a cage
Look at one connection from above. The ball belongs to the link on the left. The cage belongs to the link on the right. The gap between them is empty space, not a second material.
| Name in the recipe | Job |
|---|---|
ball-radius | Size of the moving ball: 4 mm |
cavity-radius | Ball radius plus joint-clearance: 4.6 mm by default |
socket-wall | Material around that cavity: 2 mm |
socket-throat-radius | Top opening: smaller than the ball, so it stays captive |
link-pitch | Distance between neighbouring link centres: 23 mm |
difference scoops the cavity out of the socket housing. It removes a round opening above and a smaller opening below. The ball cannot pass through either opening. The ball and socket remain separate; a union between neighbouring links would lock the joint.
Reference: sphere, difference.
Give the neck somewhere to move
A short neck joins the ball to its own body. The neck needs a side window in the next socket. Without that window the neck hits the wall as soon as you try to turn it.
The recipe sweeps a cutter through several sideways angles to clear a fan-shaped window. It leaves 1.6 mm of unbroken material below and above the window. Those lips keep the ball captive. Cutting the window all the way through the top would weaken that cage.
A second cut relieves the nearby rounded body so it does not block the socket during up-and-down motion. The flat underside and a small ball pedestal put material on the print bed. The bottom opening also clears that pedestal as the socket tilts. Its radius is derived from the pedestal and motion angle; it stays smaller than the ball.
joint-yaw-limit and joint-pitch-limit size the clearance envelope. They do not animate the worm. They are design targets for side and vertical movement, not proof of a printed angle or permission to force the joint.
Reuse one joint recipe
worm-link builds one rounded body with an optional socket and ball. The head uses :has-socket false; the tail uses :has-ball false. Ordinary body links have both.
The row is the short part of the code:
(translate link-pitch 0 0
(linear-array body-count link-pitch 0 0
(worm-link :joint-clearance joint-clearance)))This fragment needs the component and bindings from the complete file. It says: start one pitch after the head, then place seven body links at that same spacing. linear-array groups copies without joining neighbours.
The head adds eyes and horns. The tail rounds three shrinking shapes into a tip. Those decorations do not replace the joint.
Open the complete worm
Use the complete example as a recipe to inspect and modify. The detailed joint is longer than the rake because it has to clear moving parts and keep them captured. Find the names from the table before reading every cutter.
; Captive ball joints with central neck windows and unbroken lower/upper lips.
(define-component worm-link
((number joint-clearance 0.6mm)
(number joint-yaw-limit 35deg)
(number joint-pitch-limit 20deg)
(toggle has-socket true)
(toggle has-ball true))
(let* ((ball-radius 4mm)
(joint-height 5mm)
(socket-wall 2mm)
(cavity-radius (+ ball-radius joint-clearance))
(socket-outer-radius (+ cavity-radius socket-wall))
(socket-height 12mm)
(socket-throat-radius 3mm)
(socket-top-lever (- socket-height joint-height))
(body-relief-radius (+ joint-clearance
(* socket-outer-radius (cos (deg->rad joint-pitch-limit)))
(* socket-top-lever (sin (deg->rad joint-pitch-limit)))))
(ball-offset 11mm)
(socket-offset -12mm)
(neck-start 1.2mm)
(neck-length (- ball-offset neck-start))
(neck-width 4.8mm)
(neck-height 2.8mm)
(window-bottom 1.6mm)
(window-top (- socket-height window-bottom))
(window-height (- window-top window-bottom))
(window-center (/ (+ window-top window-bottom) 2))
(window-corner-radius 1mm)
(neck-motion-clearance 1.2mm)
(passage-half-width (+ (/ neck-width 2) neck-motion-clearance))
(ball-bed-pedestal-radius 1.4mm)
(ball-bed-pedestal-height 1.3mm)
(ball-bed-flat-height 0.6mm)
(pedestal-motion-clearance 0.3mm)
(bottom-throat-radius (+ pedestal-motion-clearance
(* ball-bed-pedestal-radius (cos (deg->rad joint-pitch-limit)))
(* joint-height (sin (deg->rad joint-pitch-limit)))))
(cutter-length 16mm))
(build
(shape rounded-body
(clip-plane
(translate 0 0 4.8mm (scale 1.15 1.12 1 (sphere 7.2mm)))
:origin '(0 0 0) :normal '(0 0 1) :keep "positive"))
(shape body-with-joint-relief
(if has-ball
(difference rounded-body
(translate ball-offset 0 -1mm (cylinder body-relief-radius 16mm)))
rounded-body))
(shape socket-housing
(if has-socket
(union body-with-joint-relief
(translate socket-offset 0 0 (cylinder socket-outer-radius socket-height)))
body-with-joint-relief))
(shape socket-cavity
(union
(translate socket-offset 0 joint-height (sphere cavity-radius))
(translate socket-offset 0 joint-height
(cylinder socket-throat-radius socket-height))
(translate socket-offset 0 -1mm
(cylinder bottom-throat-radius (+ window-bottom 1mm)))))
(shape neck-passage-base
(translate (- cutter-length) 0 window-center
(rotate 90deg 0 90deg
(extrude
(rounded-rect (* passage-half-width 2) window-height window-corner-radius)
cutter-length))))
(shape neck-motion-envelope
(translate socket-offset 0 0
(apply union
(map (lambda (yaw) (rotate 0 0 yaw neck-passage-base))
(linspace (- joint-yaw-limit) joint-yaw-limit 7)))))
(shape captive-socket
(if has-socket
(difference socket-housing socket-cavity neck-motion-envelope)
socket-housing))
(shape grounded-ball
(union
(clip-plane
(translate ball-offset 0 joint-height (sphere ball-radius))
:origin (list 0 0 ball-bed-flat-height) :normal '(0 0 1) :keep "positive")
(translate ball-offset 0 0
(cylinder ball-bed-pedestal-radius ball-bed-pedestal-height))))
(shape central-neck-bridge
(translate neck-start 0 window-center
(rotate 90deg 0 90deg
(extrude (slot-overall neck-width neck-height) neck-length))))
(result
(if has-ball
(union captive-socket central-neck-bridge grounded-ball)
captive-socket)))))
(model
(meta :title "Articulated puzzle worm")
(params
(number body-count 7 :label "Body links" :min 2 :max 10 :step 1 :frozen true)
(number joint-yaw-limit 35deg :label "Side motion envelope" :min 25deg :max 40deg :step 5deg)
(number joint-pitch-limit 20deg :label "Vertical motion envelope" :min 15deg :max 25deg :step 5deg)
(number joint-clearance 0.6mm :label "Joint clearance" :min 0.4mm :max 0.8mm :step 0.05mm))
(verify
(tag closed-link-surfaces)
(metric bad-edges (stl non-manifold-edge-count))
(expect bad-edges (= 0)))
(verify
(tag separate-moving-links)
(intent "Head, body links and tail must remain separate solids")
(metric link-count (stl connected-component-count))
(expect link-count (= 9)))
(verify
(tag grounded-head)
(metric contact (stl bed-contact-area-ratio head))
(expect contact (>= 0.12)))
(verify
(tag grounded-body-links)
(metric contact (stl bed-contact-area-ratio body-links))
(expect contact (>= 0.12)))
(verify
(tag grounded-tail)
(metric contact (stl bed-contact-area-ratio tail))
(expect contact (>= 0.12)))
(verify
(tag preview-artifact)
(metric present (manifest has-model-stl))
(expect present (= true)))
(let* ((link-pitch 23mm))
(part head
(build
(shape head-body
(worm-link :joint-yaw-limit joint-yaw-limit :joint-pitch-limit joint-pitch-limit :joint-clearance joint-clearance :has-socket false :has-ball true))
(shape face-eyes
(apply union (map (lambda (side)
(translate -4.8mm (* side 3mm) 10.6mm (sphere 1.9mm))) '(-1 1))))
(shape face-with-nose
(union head-body face-eyes
(translate -6.5mm 0 7mm (sphere 2.2mm))))
(shape pupil-recesses
(apply union (map (lambda (side)
(translate -5.3mm (* side 3mm) 11.7mm (cylinder 0.8mm 3mm))) '(-1 1))))
(shape rounded-horns
(apply union
(map (lambda (side)
(translate -2.8mm (* side 4.8mm) 9mm
(rotate (* side -10deg) 0 -8deg
(union
(cone 1.7mm 0.65mm 5.5mm)
(translate 0 0 5.3mm (sphere 0.8mm))))))
'(-1 1))))
(result (difference (union face-with-nose rounded-horns) pupil-recesses))))
(part body-links
(translate link-pitch 0 0
(linear-array body-count link-pitch 0 0
(worm-link :joint-yaw-limit joint-yaw-limit :joint-pitch-limit joint-pitch-limit :joint-clearance joint-clearance))))
(part tail
(translate (* (+ body-count 1) link-pitch) 0 0
(build
(shape tail-root
(worm-link :joint-yaw-limit joint-yaw-limit :joint-pitch-limit joint-pitch-limit :joint-clearance joint-clearance :has-ball false))
(shape tapered-tail-tip
(clip-plane
(hull
(translate 5mm 0 2.4mm (sphere 3.4mm))
(translate 10mm 0.8mm 1.8mm (sphere 2.7mm))
(translate 14mm 1.5mm 1.4mm (sphere 2mm)))
:origin '(0 0 0) :normal '(0 0 1) :keep "positive"))
(result (union tail-root tapered-tail-tip))))))
(tag-face head-bed :faces "bottom" head))Try one joint before nine
- Render the two-link sample. Inspect the ball through the top opening and neck through the side window.
- Find
joint-clearance = 0.6mm. Predict: increasing it enlarges the cavity; it does not shrink the ball. Change it to 0.7 and inspect again. - Compare a printed sample before and after that change. Keep the setting that moves freely while staying captive. Tiny gaps depend on printer settings and material.
- Render the complete worm and run verification. Expect nine closed pieces.
body-countis frozen at seven because that count check expects seven body links plus two ends. When deliberately changing the count in source, update the expectation too.
Closed meshes and separate links establish only part of the mechanism. Check the window and body relief, then test a printed pair before the full worm.
You can now: explain a captive ball joint, distinguish clearance from a motion control, preserve retaining lips, and repeat a working link. Time to bring the farm together.