]> git.armaanb.net Git - atreides.git/blob - case/deck.rkt
f2876cb462643818b3f380a9b5ccfadd167849a4
[atreides.git] / case / deck.rkt
1 #lang racket
2 ;; Atreus 2 deck design
3 ;; Copyright © 2019 Phil Hagelberg and contributors
4 ;; released under the GPLv3 or later
5
6 (require xml)
7
8 ;; glowforge uses 96 dpi, 25.4 mm in an inch
9 (define scale (/ 96 25.4))
10 (define width 260)
11 (define height 232)
12
13 (define cols 6) ; per hand
14 (define rows 4)
15 (define angle (degrees->radians 10))
16 (define corner-radius 6.0)
17
18 (define alps-switch-width 15.887)
19 (define alps-switch-height 13.087)
20 (define cherry-switch-width 13.62)
21 (define cherry-switch-height 13.72)
22 (define cherry? false)
23 (define switch-height (if cherry? cherry-switch-height alps-switch-height))
24 (define switch-width (if cherry? cherry-switch-width alps-switch-width))
25
26 (define switch-spacing 19.0)
27
28 (define screw-radius 1.4) ; for M3 screws + kerf
29
30 (define side-screw-distance (* switch-spacing rows))
31 (define bottom-screw-distance (* switch-spacing cols))
32 (define left corner-radius)
33 (define bottom 95) ; outer bottom
34 (define left-top (+ left (* side-screw-distance (sin angle))))
35 (define top (- bottom (* side-screw-distance (cos angle))))
36 (define right (- width corner-radius))
37 (define right-top (- right (* side-screw-distance (sin angle))))
38 (define mid-bottom (+ bottom (* bottom-screw-distance (sin angle)) -3))
39 (define mid-offset 25)
40 (define mid-x (/ width 2))
41 (define mid-left (- mid-x mid-offset))
42 (define mid-right (+ mid-x mid-offset))
43
44 (define upper-height 80)
45 (define left-upper-top (+ left-top (* upper-height (sin angle))))
46 (define right-upper-top (- right-top (* upper-height (sin angle))))
47 (define upper-top (- top (* upper-height (cos angle))))
48
49 (define hull-coords (list (list right-upper-top upper-top)
50                           (list right-top top)
51                           (list right bottom)
52                           (list mid-right mid-bottom)
53                           (list mid-left mid-bottom)
54                           (list left bottom)
55                           (list left-top top)
56                           (list left-upper-top upper-top)))
57
58 ;;; screws
59 (define screws
60   `(g () ,@(for/list ([s (append (take hull-coords 3)
61                                  ;; the bottom middle has only one screw but
62                                  ;; two hull positions
63                                  (list (list (/ width 2) mid-bottom))
64                                  (drop hull-coords 5))])
65              `(circle ((r ,(number->string screw-radius))
66                        (cx ,(number->string (first s)))
67                        (cy ,(number->string (second s))))))))
68
69 ;;; outline
70 (define outline-coords (append hull-coords (take hull-coords 2)))
71
72 (define (to-next-screw? theta current-screw)
73   (let* ([current (list-ref outline-coords current-screw)]
74          [cx (first current)] [cy (second current)]
75          [next (list-ref outline-coords (add1 current-screw))]
76          [nx (first next)] [ny (second next)]
77          [dx (- nx cx)] [dy (- ny cy)]
78          [next-theta (- (radians->degrees (atan dy dx)))])
79     (= (floor (modulo (floor next-theta) 180))
80        (floor (modulo (- theta 90) 180)))))
81
82 ;; trace the outline by going from screw to screw until you've gone full-circle
83 (define (outline-points coords theta current-screw)
84   (if (< -360 (- theta 90) 360)
85       (let* ([current (list-ref outline-coords current-screw)]
86              [sx (first current)] [sy (second current)]
87              [x (+ sx (* (cos (degrees->radians theta)) corner-radius))]
88              [y (- sy (* (sin (degrees->radians theta)) corner-radius))]
89              [coords (cons (format "~s,~s" x y) coords)])
90         (if (to-next-screw? theta current-screw)
91             (outline-points coords theta (add1 current-screw))
92             (outline-points coords (sub1 theta) current-screw)))
93       coords))
94
95 (define (outline)
96   `(polygon ((points ,(string-join (outline-points '() 90 0))))))
97
98 ;;; board
99
100 (define board-doc (call-with-input-file "pine64-fragment.svg" read-xml))
101
102 ;; screen viewable: 116x68
103
104 (define screen-slots
105   (let ((x 170)
106         (y "-65"))
107     (list `(rect ((x ,(format "~s" x))
108                   (y ,y)
109                   (height "20")
110                   (width "2.55")))
111           `(rect ((x ,(format "~s" (+ x 35.6)))
112                   (y ,y)
113                   (height "20")
114                   (width "2.55"))))))
115
116 ;;; switches
117
118 (define column-offsets `(8 5 0 6 11 ,(+ 8 switch-spacing switch-spacing)))
119
120 (define (switch row col)
121   (let* ([x (* (+ 1 col) switch-spacing)]
122          [y (+ (list-ref column-offsets col) (* switch-spacing row))])
123     `(rect ((height ,(number->string switch-height))
124             (width ,(number->string switch-width))
125             (x ,(number->string x))
126             (y ,(number->string y))))))
127
128 (define hand-height (+ (* switch-spacing rows) (- switch-spacing switch-height)
129                        (list-ref column-offsets 0)))
130 (define switch-x-offset -6.5)
131 (define switch-y-offset (- bottom hand-height -3.5))
132
133 (define switches
134   `(g ((transform ,(format "translate(~s, ~s) rotate(~s, ~s, ~s)"
135                            switch-x-offset switch-y-offset
136                            (radians->degrees angle)
137                            0 hand-height)))
138       ,@(for/list ([col (in-range cols)]
139                    #:when true
140                    [row (if (= 5 col) '(0 1) (in-range rows))])
141           (switch row col))))
142
143 (define switches-right
144   `(g ((transform ,(format "translate(~s,~s) scale(-1, 1)" width 0)))
145       ,switches))
146
147 (define logo-doc (call-with-input-file "logo-fragment.svg" read-xml))
148
149 (define (layer plate)
150   (document (prolog '() false '())
151             (xexpr->xml
152              `(svg ((xmlns:svg "http://www.w3.org/2000/svg")
153                     (height ,(number->string (* height scale)))
154                     (width ,(number->string (* width scale))))
155                    ,@(if (eq? plate 'switch)
156                          `((g ((transform ,(format "translate(436, ~s)"
157                                                    (+ (* scale upper-height) 115)))
158                                (stroke "red"))
159                               ,(xml->xexpr (document-element logo-doc))))
160                          '())
161                    (g ((transform ,(format "translate(100.14, -765) scale(~s, ~s)"
162                                            scale scale)))
163                       ,(xml->xexpr (document-element board-doc)))
164                    (g ((transform ,(format "scale(~s, ~s) translate(0, ~s)"
165                                            scale scale (- upper-height 1.4)))
166                        (stroke-width "1")
167                        (stroke "black")
168                        (fill-opacity "0"))
169                       ,screws
170                       ,@screen-slots
171                       ,(outline)
172                       ,@(if (eq? plate 'switch)
173                             (list switches switches-right)
174                             (list)))))
175             '()))
176
177 ;; to laser cut these, you have to open them in inkscape, then save,
178 ;; and then upload; for some reason the glowforge crashes if you try to cut
179 ;; them directly.
180
181 (define (write-out-layer layer-name)
182   (call-with-output-file (format "deck-~a.svg" (symbol->string layer-name))
183     (lambda (out)
184       (display "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>" out)
185       (display-xml (layer layer-name) out))
186     #:exists 'replace))
187
188 ;; live-reload with:
189 ;; qiv --watch deck-switch.svg
190
191 (write-out-layer 'switch)
192 (write-out-layer 'bottom)