; Generated by scripts/worm_farm_sources.ts from the chapter models. (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))))) (define-component tractor-chassis ((number axle-radius 3.2mm) (number body-clearance 0.3mm)) (let* ((axle-height 5mm) (body (box 30mm 50mm 10mm)) (engine (translate 0 -11mm 10mm (box 22mm 23mm 10mm))) (chimney (translate 0 -15mm 20mm (cylinder 2.5mm 12mm))) (cabin (translate 0 14mm 10mm (difference (box 24mm 18mm 23mm) (translate 0 0 3mm (box 26mm 12mm 16mm))))) (axle-bores (repeat-compound i 2 (translate 0 (+ -15mm (* i 30mm)) axle-height (rotate 0 90deg 0 (cylinder (+ axle-radius body-clearance) 34mm :align '(center center center))))))) (difference (union body engine chimney cabin) axle-bores))) (define-component tractor-axle ((number axle-radius 3.2mm) (number axle-length 47.2mm) (number mouth-radius 1.8mm) (number cavity-radius 2.35mm) (number lip-depth 2.2mm) (number socket-depth 5.5mm)) (difference (cylinder axle-radius axle-length) (translate 0 0 -1mm (cylinder mouth-radius (+ axle-length 2mm))) (translate 0 0 lip-depth (cylinder cavity-radius (- socket-depth lip-depth))) (translate 0 0 (- axle-length socket-depth) (cylinder cavity-radius (- socket-depth lip-depth))))) (define-component tractor-wheel ((number radius 10mm) (number width 6mm) (number axle-radius 3.2mm) (number running-clearance 0.4mm)) (difference (cylinder radius width) (translate 0 0 -1mm (cylinder (+ axle-radius running-clearance) (+ width 2mm))))) (define-component tractor-rivet ((number mouth-radius 1.8mm) (number snap-interference 0.25mm) (number head-radius 4.8mm) (number head-thickness 1.6mm)) (let* ((stem-radius (- mouth-radius 0.2mm)) (barb-radius (+ mouth-radius snap-interference)) (barb-base 4.3mm) (slot-bottom (+ head-thickness 0.8mm))) (difference (union (cylinder head-radius head-thickness) (translate 0 0 (- head-thickness 0.2mm) (cylinder stem-radius 3.1mm)) (translate 0 0 barb-base (cone barb-radius 1mm 1.9mm))) (translate 0 0 slot-bottom (box 1.1mm 8mm 5mm))))) (define-component fence_post ((number x 0mm) (number height 22mm)) (translate x 0 0 (union (cylinder 2mm height) (translate 0 0 height (cone 3mm 0mm 5mm))))) (model (meta :title "Worm Farm") (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) (number wall 2mm :label "House wall" :min 1.5 :max 3 :step 0.5) (number roof_clearance 0.3mm :label "Roof clearance" :min 0.2 :max 0.6 :step 0.1) (toggle house_print_layout false :label "House print layout (changes export)") (number tooth_count 5 :label "Rake teeth" :min 3 :max 7 :step 1) (number wheel_radius 10mm :label "Wheel radius" :min 8 :max 12 :step 1) (number wheel_clearance 0.4mm :label "Wheel running clearance" :min 0.2 :max 0.6 :step 0.05) (number snap_interference 0.25mm :label "Rivet snap interference" :min 0.1 :max 0.35 :step 0.05) (toggle print_layout false :label "Print layout (changes export)") (number posts 6 :label "Fence posts" :min 3 :max 9 :step 1)) (let* ((link-pitch 23mm)) (part worm-head (translate -100mm -35mm 0mm (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 worm-body-links (translate -100mm -35mm 0mm (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 worm-tail (translate -100mm -35mm 0mm (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))))))) (part mushroom-house (translate -65mm 40mm 0mm (let* ((outside (cylinder 18mm 28mm)) (room (translate 0 0 2mm (cylinder (- 18mm wall) 27mm))) (doorway (translate 0 -16mm 9mm (rotate 90deg 0 0 (cylinder 8.5mm 18mm :align '(center center center)))))) (difference outside room doorway)))) (part mushroom-roof (translate -65mm 40mm 0mm (let* ((rim-radius (+ 18mm roof_clearance)) (skirt-height 3mm) (cap (cone 26mm 2mm 26mm)) (rim-opening (translate 0 0 -1mm (cylinder rim-radius (+ skirt-height 1mm)))) (inside (translate 0 0 skirt-height (cone rim-radius 0mm rim-radius)))) (translate (if house_print_layout 52mm 0mm) 0 (if house_print_layout 0mm 25mm) (difference cap rim-opening inside))))) (part tool-rake (translate -20mm 75mm 0mm (let* ((tooth-length 12mm) (tooth-radius 1.3mm) (head-height tooth-length) (head-radius 2mm) (tooth-spacing 4mm) (head-length (+ (* (- tooth_count 1) tooth-spacing) (* head-radius 2))) (handle (translate 0 0 head-height (rotate -35deg 0 0 (cylinder 2mm 44mm)))) (crossbar (translate 0 0 head-height (rotate 0 90deg 0 (cylinder head-radius head-length :align '(center center center))))) (teeth (repeat-union i tooth_count (translate (* (- i (/ (- tooth_count 1) 2)) tooth-spacing) 0 0 (cylinder tooth-radius tooth-length))))) (union handle crossbar teeth)))) (let* ((axle-radius 3.2mm) (body-half-width 15mm) (wheel-width 6mm) (wheel-side-gap 2mm) (wheel-end-play 0.6mm) (wheel-center (+ body-half-width wheel-side-gap (/ wheel-width 2))) (axle-half-length (+ body-half-width wheel-side-gap wheel-width wheel-end-play)) (axle-length (* 2 axle-half-length)) (rivet-head-thickness 1.6mm) (body-lift (- wheel_radius 5mm))) (part tractor-chassis (translate 40mm 35mm 0mm (translate 0 0 (if print_layout 0mm body-lift) (tractor-chassis :axle-radius axle-radius)))) (part tractor-axles (translate 40mm 35mm 0mm (repeat-compound i 2 (let* ((axle (tractor-axle :axle-radius axle-radius :axle-length axle-length))) (if print_layout (translate (+ -28mm (* i 56mm)) 46mm 0 axle) (translate (- axle-half-length) (+ -15mm (* i 30mm)) wheel_radius (rotate 0 90deg 0 axle))))))) (part tractor-wheels (translate 40mm 35mm 0mm (repeat-compound side 2 (repeat-compound i 2 (let* ((sign (- (* side 2) 1)) (wheel (tractor-wheel :radius wheel_radius :width wheel-width :axle-radius axle-radius :running-clearance wheel_clearance))) (if print_layout (translate (* sign 32mm) (+ -15mm (* i 30mm)) 0 wheel) (translate (* sign (- wheel-center (/ wheel-width 2))) (+ -15mm (* i 30mm)) wheel_radius (rotate 0 (* sign 90deg) 0 wheel)))))))) (part tractor-rivets (translate 40mm 35mm 0mm (repeat-compound side 2 (repeat-compound i 2 (let* ((sign (- (* side 2) 1)) (rivet (tractor-rivet :snap-interference snap_interference :head-thickness rivet-head-thickness))) (if print_layout (translate (+ -18mm (* side 24mm) (* i 12mm)) 46mm 0 rivet) (translate (* sign (+ axle-half-length rivet-head-thickness)) (+ -15mm (* i 30mm)) wheel_radius (rotate 0 (* sign -90deg) 0 rivet))))))))) (part garden-fence (translate -15mm 100mm 0mm (let* ((post_row (repeat-union i posts (fence_post :x (* i 9mm)))) (rail_length (+ (* (- posts 1) 9mm) 4mm)) (rail_center (/ (* (- posts 1) 9mm) 2)) (bottom_rail (translate rail_center 0 8mm (box rail_length 2mm 3mm))) (top_rail (translate rail_center 0 16mm (box rail_length 2mm 3mm)))) (union post_row bottom_rail top_rail)))) (part garden-carrot (translate -15mm 100mm 0mm (translate 20mm -12mm 0 (loft 18mm (circle 1mm) (regular-polygon 6 5mm))))) (view peek_inside (offset-part mushroom-roof 0 0 24mm)) (verify (tag closed-farm) (metric bad-edges (stl non-manifold-edge-count)) (expect bad-edges (= 0))) (verify (tag separate-mechanisms) (metric pieces (stl connected-component-count)) (expect pieces (= 25))))