2 ;; Atreus 2 case design
3 ;; Copyright © 2019-2020 Phil Hagelberg and contributors
4 ;; released under the GPLv3 or later
8 ;; glowforge uses 96 dpi, 25.4 mm in an inch
9 (define scale (/ 96 25.4))
14 (define cols 6) ; per hand
16 (define angle (degrees->radians 10))
17 (define corner-radius 6.0)
19 (define alps-switch-width 15.34)
20 (define alps-switch-height 12.49)
21 (define cherry-switch-width 13.62)
22 (define cherry-switch-height 13.72)
23 (define cherry? false)
24 (define switch-height (if cherry? cherry-switch-height alps-switch-height))
25 (define switch-width (if cherry? cherry-switch-width alps-switch-width))
27 (define switch-spacing 19.0)
29 (define screw-radius 1.4) ; for M3 screws + kerf
31 (define side-screw-distance (* switch-spacing rows))
32 (define bottom-screw-distance (* switch-spacing cols))
33 (define left corner-radius)
34 (define bottom 95) ; outer bottom
35 (define left-top (+ left (* side-screw-distance (sin angle))))
36 (define top (- bottom (* side-screw-distance (cos angle))))
37 (define right (- width corner-radius))
38 (define right-top (- right (* side-screw-distance (sin angle))))
39 (define mid-bottom (+ bottom (* bottom-screw-distance (sin angle)) -3))
40 (define mid-offset 25)
41 (define mid-x (/ width 2))
42 (define mid-left (- mid-x mid-offset))
43 (define mid-right (+ mid-x mid-offset))
45 (define hull-coords (list (list right-top top)
47 (list mid-right mid-bottom)
48 (list mid-left mid-bottom)
54 `(g () ,@(for/list ([s (append (take hull-coords 2)
55 ;; the bottom middle has only one screw but
57 (list (list (/ width 2) mid-bottom))
58 (drop hull-coords 4))])
59 `(circle ((r ,(number->string screw-radius))
60 (cx ,(number->string (first s)))
61 (cy ,(number->string (second s))))))))
64 (define outline-coords (append hull-coords (take hull-coords 2)))
66 (define (to-next-screw? theta current-screw)
67 (let* ([current (list-ref outline-coords current-screw)]
68 [cx (first current)] [cy (second current)]
69 [next (list-ref outline-coords (add1 current-screw))]
70 [nx (first next)] [ny (second next)]
71 [dx (- nx cx)] [dy (- ny cy)]
72 [next-theta (- (radians->degrees (atan dy dx)))])
73 (= (floor (modulo (floor next-theta) 180))
74 (floor (modulo (- theta 90) 180)))))
76 ;; trace the outline by going from screw to screw until you've gone full-circle
77 (define (outline-points coords theta current-screw)
78 (if (< -360 (- theta 90) 360)
79 (let* ([current (list-ref outline-coords current-screw)]
80 [sx (first current)] [sy (second current)]
81 [x (+ sx (* (cos (degrees->radians theta)) corner-radius))]
82 [y (- sy (* (sin (degrees->radians theta)) corner-radius))]
83 [coords (cons (format "~s,~s" x y) coords)])
84 (if (to-next-screw? theta current-screw)
85 (outline-points coords theta (add1 current-screw))
86 (outline-points coords (sub1 theta) current-screw)))
91 (define port-curve (list (format "~s,~s" mid-right (- top corner-radius))
92 (format "~s,~s" (+ mid-x corner-radius)
95 (format "~s,~s" (- mid-x corner-radius)
98 (format "~s,~s" mid-left (- top corner-radius))))
100 (define (outline with-port?)
101 `(polygon ((points ,(string-join (let ((noport (outline-points '() 90 0)))
103 (append noport port-curve)
108 (define column-offsets `(8 5 0 6 11 ,(+ 8 switch-spacing switch-spacing)))
110 (define (switch row col)
111 (let* ([x (* (+ 1 col) switch-spacing)]
112 [y (+ (list-ref column-offsets col) (* switch-spacing row))])
113 `(rect ((height ,(number->string switch-height))
114 (width ,(number->string switch-width))
115 (x ,(number->string x))
116 (y ,(number->string y))))))
118 (define hand-height (+ (* switch-spacing rows) (- switch-spacing switch-height)
119 (list-ref column-offsets 0)))
120 (define switch-x-offset -6.5)
121 (define switch-y-offset (- bottom hand-height -3.5))
124 `(g ((transform ,(format "translate(~s, ~s) rotate(~s, ~s, ~s)"
125 switch-x-offset switch-y-offset
126 (radians->degrees angle)
128 ,@(for/list ([col (in-range cols)]
130 [row (if (= 5 col) '(0 1) (in-range rows))])
133 (define switches-right
134 `(g ((transform ,(format "translate(~s,~s) scale(-1, 1)" width 0)))
137 (define logo-doc (call-with-input-file "logo-fragment.svg" read-xml))
139 (define pcb-doc (call-with-input-file "pcb-fragment.svg" read-xml))
141 (define (layer plate)
142 (document (prolog '() false '())
144 `(svg ((xmlns:svg "http://www.w3.org/2000/svg")
145 (height ,(number->string (* height scale)))
146 (width ,(number->string (* width scale))))
147 ,@(if (eq? plate 'switch)
148 `((g ((transform "translate(436, 115)")
150 ,(xml->xexpr (document-element logo-doc))))
152 ,@(if (eq? plate 'spacer)
153 (list (xml->xexpr (document-element pcb-doc)))
155 (g ((transform ,(format "scale(~s, ~s)" scale scale))
160 ,(outline (not (eq? plate 'switch)))
161 ,@(if (eq? plate 'switch)
162 (list switches switches-right)
166 ;; to laser cut these, you have to open them in inkscape, then save,
167 ;; and then upload; for some reason the glowforge crashes if you try to cut
170 (define (write-out-layer layer-name)
171 (call-with-output-file (format "case2-~a.svg" (symbol->string layer-name))
173 (display "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>" out)
174 (display-xml (layer layer-name) out))
178 ;; qiv --watch case2-switch.svg
180 (write-out-layer 'switch)
181 (write-out-layer 'bottom)
182 (write-out-layer 'spacer)