case.rkt 7.01 KB
Newer Older
Phil Hagelberg's avatar
Phil Hagelberg committed
1
#lang racket
Phil Hagelberg's avatar
Phil Hagelberg committed
2
;; Atreus 2 case design
Phil Hagelberg's avatar
Phil Hagelberg committed
3
;; Copyright © 2019-2020 Phil Hagelberg and contributors
Phil Hagelberg's avatar
Phil Hagelberg committed
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
;; released under the GPLv3 or later

(require xml)

;; glowforge uses 96 dpi, 25.4 mm in an inch
(define scale (/ 96 25.4))
(define width 260)
(define height 132)


(define cols 6) ; per hand
(define rows 4)
(define angle (degrees->radians 10))
(define corner-radius 6.0)

Phil Hagelberg's avatar
Phil Hagelberg committed
19
20
(define alps-switch-width 15.34)
(define alps-switch-height 12.49)
Phil Hagelberg's avatar
Phil Hagelberg committed
21
22
(define cherry-switch-width 13.62)
(define cherry-switch-height 13.72)
Phil Hagelberg's avatar
Phil Hagelberg committed
23
(define cherry? false)
Phil Hagelberg's avatar
Phil Hagelberg committed
24
25
26
27
28
(define switch-height (if cherry? cherry-switch-height alps-switch-height))
(define switch-width (if cherry? cherry-switch-width alps-switch-width))

(define switch-spacing 19.0)

Phil Hagelberg's avatar
Phil Hagelberg committed
29
(define screw-radius 1.4) ; for M3 screws + kerf
Phil Hagelberg's avatar
Phil Hagelberg committed
30

Phil Hagelberg's avatar
Phil Hagelberg committed
31
32
(define side-screw-distance (* switch-spacing rows))
(define bottom-screw-distance (* switch-spacing cols))
Phil Hagelberg's avatar
Phil Hagelberg committed
33
34
35
36
37
38
(define left corner-radius)
(define bottom 95) ; outer bottom
(define left-top (+ left (* side-screw-distance (sin angle))))
(define top (- bottom (* side-screw-distance (cos angle))))
(define right (- width corner-radius))
(define right-top (- right (* side-screw-distance (sin angle))))
Phil Hagelberg's avatar
Phil Hagelberg committed
39
(define mid-bottom (+ bottom (* bottom-screw-distance (sin angle)) -3))
Phil Hagelberg's avatar
Phil Hagelberg committed
40
(define mid-offset 25)
Phil Hagelberg's avatar
Phil Hagelberg committed
41
42
43
(define mid-x (/ width 2))
(define mid-left (- mid-x mid-offset))
(define mid-right (+ mid-x mid-offset))
Phil Hagelberg's avatar
Phil Hagelberg committed
44

Phil Hagelberg's avatar
Phil Hagelberg committed
45
(define hull-coords (list (list right-top top)
Phil Hagelberg's avatar
Phil Hagelberg committed
46
47
48
49
                          (list right bottom)
                          (list mid-right mid-bottom)
                          (list mid-left mid-bottom)
                          (list left bottom)
Phil Hagelberg's avatar
Phil Hagelberg committed
50
                          (list left-top top)))
Phil Hagelberg's avatar
Phil Hagelberg committed
51
52
53

;;; screws
(define screws
Phil Hagelberg's avatar
Phil Hagelberg committed
54
  `(g () ,@(for/list ([s (append (take hull-coords 2)
Phil Hagelberg's avatar
Phil Hagelberg committed
55
56
57
                                 ;; the bottom middle has only one screw but
                                 ;; two hull positions
                                 (list (list (/ width 2) mid-bottom))
Phil Hagelberg's avatar
Phil Hagelberg committed
58
                                 (drop hull-coords 4))])
Phil Hagelberg's avatar
Phil Hagelberg committed
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
             `(circle ((r ,(number->string screw-radius))
                       (cx ,(number->string (first s)))
                       (cy ,(number->string (second s))))))))

;;; outline
(define outline-coords (append hull-coords (take hull-coords 2)))

(define (to-next-screw? theta current-screw)
  (let* ([current (list-ref outline-coords current-screw)]
         [cx (first current)] [cy (second current)]
         [next (list-ref outline-coords (add1 current-screw))]
         [nx (first next)] [ny (second next)]
         [dx (- nx cx)] [dy (- ny cy)]
         [next-theta (- (radians->degrees (atan dy dx)))])
    (= (floor (modulo (floor next-theta) 180))
       (floor (modulo (- theta 90) 180)))))

;; trace the outline by going from screw to screw until you've gone full-circle
(define (outline-points coords theta current-screw)
  (if (< -360 (- theta 90) 360)
      (let* ([current (list-ref outline-coords current-screw)]
             [sx (first current)] [sy (second current)]
             [x (+ sx (* (cos (degrees->radians theta)) corner-radius))]
             [y (- sy (* (sin (degrees->radians theta)) corner-radius))]
             [coords (cons (format "~s,~s" x y) coords)])
        (if (to-next-screw? theta current-screw)
            (outline-points coords theta (add1 current-screw))
            (outline-points coords (sub1 theta) current-screw)))
      coords))

Phil Hagelberg's avatar
Phil Hagelberg committed
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
(define port-depth 8)

(define port-curve (list (format "~s,~s" mid-right (- top corner-radius))
                         (format "~s,~s" (+ mid-x corner-radius)
                                 (+ top port-depth
                                    (- corner-radius)))
                         (format "~s,~s" (- mid-x corner-radius)
                                 (+ top port-depth
                                    (- corner-radius)))
                         (format "~s,~s" mid-left (- top corner-radius))))

(define (outline with-port?)
  `(polygon ((points ,(string-join (let ((noport (outline-points '() 90 0)))
                                     (if with-port?
                                         (append noport port-curve)
                                         noport)))))))
Phil Hagelberg's avatar
Phil Hagelberg committed
105
106
107

;;; switches

Phil Hagelberg's avatar
Phil Hagelberg committed
108
(define column-offsets `(8 5 0 6 11 ,(+ 8 switch-spacing switch-spacing)))
Phil Hagelberg's avatar
Phil Hagelberg committed
109
110
111
112
113
114
115
116
117
118
119

(define (switch row col)
  (let* ([x (* (+ 1 col) switch-spacing)]
         [y (+ (list-ref column-offsets col) (* switch-spacing row))])
    `(rect ((height ,(number->string switch-height))
            (width ,(number->string switch-width))
            (x ,(number->string x))
            (y ,(number->string y))))))

(define hand-height (+ (* switch-spacing rows) (- switch-spacing switch-height)
                       (list-ref column-offsets 0)))
Phil Hagelberg's avatar
Phil Hagelberg committed
120
121
(define switch-x-offset -6.5)
(define switch-y-offset (- bottom hand-height -3.5))
Phil Hagelberg's avatar
Phil Hagelberg committed
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136

(define switches
  `(g ((transform ,(format "translate(~s, ~s) rotate(~s, ~s, ~s)"
                           switch-x-offset switch-y-offset
                           (radians->degrees angle)
                           0 hand-height)))
      ,@(for/list ([col (in-range cols)]
                   #:when true
                   [row (if (= 5 col) '(0 1) (in-range rows))])
          (switch row col))))

(define switches-right
  `(g ((transform ,(format "translate(~s,~s) scale(-1, 1)" width 0)))
      ,switches))

Phil Hagelberg's avatar
Phil Hagelberg committed
137
138
139
140
141
(define logo-doc (call-with-input-file "logo-fragment.svg" read-xml))

(define pcb-doc (call-with-input-file "pcb-fragment.svg" read-xml))

(define (layer plate)
Phil Hagelberg's avatar
Phil Hagelberg committed
142
143
144
145
146
  (document (prolog '() false '())
            (xexpr->xml
             `(svg ((xmlns:svg "http://www.w3.org/2000/svg")
                    (height ,(number->string (* height scale)))
                    (width ,(number->string (* width scale))))
Phil Hagelberg's avatar
Phil Hagelberg committed
147
148
149
150
151
152
153
154
                   ,@(if (eq? plate 'switch)
                         `((g ((transform "translate(436, 115)")
                               (stroke "red"))
                              ,(xml->xexpr (document-element logo-doc))))
                         '())
                   ,@(if (eq? plate 'spacer)
                         (list (xml->xexpr (document-element pcb-doc)))
                         (list))
Phil Hagelberg's avatar
Phil Hagelberg committed
155
156
157
158
159
                   (g ((transform ,(format "scale(~s, ~s)" scale scale))
                       (stroke-width "1")
                       (stroke "black")
                       (fill-opacity "0"))
                      ,screws
Phil Hagelberg's avatar
Phil Hagelberg committed
160
161
162
163
                      ,(outline (not (eq? plate 'switch)))
                      ,@(if (eq? plate 'switch)
                            (list switches switches-right)
                            (list)))))
Phil Hagelberg's avatar
Phil Hagelberg committed
164
165
            '()))

Phil Hagelberg's avatar
Phil Hagelberg committed
166
167
168
169
170
171
172
173
174
175
176
;; to laser cut these, you have to open them in inkscape, then save,
;; and then upload; for some reason the glowforge crashes if you try to cut
;; them directly.

(define (write-out-layer layer-name)
  (call-with-output-file (format "case2-~a.svg" (symbol->string layer-name))
    (lambda (out)
      (display "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>" out)
      (display-xml (layer layer-name) out))
    #:exists 'replace))

177
178
179
;; live-reload with:
;; qiv --watch case2-switch.svg

Phil Hagelberg's avatar
Phil Hagelberg committed
180
181
182
(write-out-layer 'switch)
(write-out-layer 'bottom)
(write-out-layer 'spacer)