-
Notifications
You must be signed in to change notification settings - Fork 1
/
iso.clj
286 lines (263 loc) · 12 KB
/
iso.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
;;; ISO 262/4017 fasteners and ISO 7089 washers.
(ns scad-klupe.iso
(:require [clojure.spec.alpha :as spec]
[scad-clj.model :as model]
[scad-tarmi.core :refer [sin cos τ long-hex-diagonal]]
[scad-tarmi.maybe :as maybe]
[scad-tarmi.dfm :as dfm]
[scad-klupe.base :as base]
[scad-klupe.schema.base :as base-schema]
[scad-klupe.schema.iso :as iso-schema]))
;;;;;;;;;;;;;;;
;; CONSTANTS ;;
;;;;;;;;;;;;;;;
(def standard-threading-angle
"The default angle of threading in this module is ISO 262’s 60 degrees,
approximated by 1.0472 radians. For easier printing, consider a lower,
standards-noncompliant value."
1.0472)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; INTERFACE FUNCTIONS — MINOR ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn datum
"Retrieve or calculate a fact based on the ISO standards."
[nominal-diameter key]
{:pre [(spec/valid? ::iso-schema/m-diameter nominal-diameter)
(spec/valid? ::iso-schema/iso-property key)]}
(let [data (get iso-schema/iso-data nominal-diameter)]
(case key
:hex-head-short-diagonal ; Flat-to-flat width of a hex head.
;; For most sizes, this value is equal to socket diameter.
(get data key (datum nominal-diameter :socket-diameter))
:hex-head-long-diagonal ; Corner-to-corner diameter of a hex head.
(long-hex-diagonal
(datum nominal-diameter :hex-head-short-diagonal))
:head-hex-drive-long-diagonal
(long-hex-diagonal
(datum nominal-diameter :head-hex-drive-short-diagonal))
:socket-height
nominal-diameter
:button-diameter
(* 1.75 nominal-diameter)
:button-height
(* 0.55 nominal-diameter)
:countersunk-diameter
(* 2 nominal-diameter)
:countersunk-height
;; Nominal chamfer is 89.9°. Treated here as 90°.
(/ nominal-diameter 2)
(if-let [value (get data key)]
value
(throw
(ex-info "Unknown datum"
{:nominal-diameter nominal-diameter
:requested-property key}))))))
(defn head-length
"Get the axial length of an ISO bolt head.
This is more commonly thought of as a height, especially given the
vertical orientation of the model output by the bolt function. The word
“length” is intended to provide conceptual continuity with the more intuitive
bolt-length function, below, as well as bolt parameter names.
This is exposed for predicting the results of the bolt function in this
module, specifically where the transition from head to body will occur."
[m-diameter head-type]
{:pre [(spec/valid? ::iso-schema/m-diameter m-diameter)
(spec/valid? ::iso-schema/head-type head-type)]}
(datum m-diameter
(case head-type
:hex :iso4017-hex-head-length-nominal
:socket :socket-height
:button :button-height
:countersunk :countersunk-height)))
(defn bolt-length
"Get the projected length of an ISO bolt, including the head. This is
exposed for predicting the results of the bolt function in this module."
[{:keys [m-diameter head-type] :as options}]
{:pre [(spec/valid? ::iso-schema/bolt-parameters options)
(spec/valid? ::base-schema/bolt-length-specifiers options)]}
(base/bolt-length
(assoc options :head-length (head-length m-diameter head-type))))
;;;;;;;;;;;;;;;;;;;;;;;;
;; INTERNAL FUNCTIONS ;;
;;;;;;;;;;;;;;;;;;;;;;;;
(defn- positive-body
"Build the main body of the positive version of a fastener.
Apply a compensator to the shape as a whole but discard that compensator
so it isn’t reused by the shape function itself. Similarly, describe the
shape as a negative, so that it will not recurse to include those features
that will be subtracted from the positive body by the caller."
[shape-fn {:keys [m-diameter compensator] :or {compensator dfm/none}
:as shape-options}]
(compensator m-diameter {:negative false}
(-> shape-options (dissoc :compensator) (assoc :negative true) shape-fn)))
(defn- hex-item
([m-diameter height]
(hex-item m-diameter height :hex-head-long-diagonal))
([m-diameter height diagonal-datum]
(base/hex (/ (datum m-diameter diagonal-datum) 2) height)))
(let [head->key {:hex :hex-head-long-diagonal
:socket :socket-diameter
:button :button-diameter
:countersunk :countersunk-diameter}]
(defn- channel-above-bolt-head
"A round channel for a screwdriver to reach the head of a bolt.
This is not part of any ISO standard but merely a convenience for CAD work."
[{:keys [m-diameter head-type channel-diameter channel-length]}]
{:pre [(spec/valid? ::iso-schema/m-diameter m-diameter)
(spec/valid? ::iso-schema/head-type head-type)]}
(let [base-diameter (datum m-diameter (head-type head->key))
r (mapv #(/ % 2) [base-diameter (or channel-diameter base-diameter)])]
(model/translate [0 0 (/ channel-length 2)]
(model/cylinder r channel-length)))))
(defn- bolt-head
"A model of the head of a bolt, without a drive.
This function takes an auxiliary ‘countersink-edge-fn’ which computes the
thickness of a countersunk head at its edge. The computed thickness will,
effectively, lengthen the head, potentially producing a negative that is too
shallow for the threaded portion of a real screw.
The default ‘countersink-edge-fn’ is a slight exaggeration intended
to make sure the head will not protrude with normal printing defects."
[{:keys [m-diameter head-type countersink-edge-fn compensator]
:or {countersink-edge-fn (fn [m-diameter] (/ (Math/log m-diameter) 8))}}]
{:pre [(spec/valid? ::iso-schema/m-diameter m-diameter)
(spec/valid? ::iso-schema/head-type head-type)]}
(let [height (head-length m-diameter head-type)]
(case head-type
:hex
(compensator (datum m-diameter :hex-head-long-diagonal) {}
(hex-item m-diameter height))
:socket
(let [diameter (datum m-diameter :socket-diameter)]
(model/cylinder (/ (compensator diameter) 2) height))
:button
(let [diameter (datum m-diameter :button-diameter)]
(model/cylinder (/ (compensator diameter) 2) height))
:countersunk
(let [diameter (datum m-diameter :countersunk-diameter)
edge (countersink-edge-fn m-diameter)]
(model/hull
(model/translate [0 0 (+ (/ edge -2) (/ height 2))]
(model/cylinder (/ (compensator diameter) 2) edge))
(model/translate [0 0 (+ (/ edge -2) (/ height -2))]
(model/cylinder (/ (compensator m-diameter) 2) edge)))))))
(defn- bolt-drive
"A model of the thing you stick your bit in."
[{:keys [m-diameter head-type drive-type drive-recess-depth]}]
{:pre [(spec/valid? ::iso-schema/m-diameter m-diameter)
(spec/valid? ::iso-schema/drive-type drive-type)]}
(let [depth (or drive-recess-depth
(/ (head-length m-diameter head-type) 2))]
(model/translate [0 0 (/ depth -2)]
(case drive-type
:hex (hex-item m-diameter depth :head-hex-drive-long-diagonal)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; INTERFACE FUNCTIONS — MAJOR ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn rod
"A rod centred at [0 0 0].
By default, this is a threaded rod. However, if the “include-threading”
keyword parameter is false, the rod will instead be a plain cylinder. If
marked as negative, such an unthreaded rod will be shrunk for tapping the
hole it makes, after printing. Otherwise, it will be left at regular
DFM-nominal size for threading with a die."
[{:keys [m-diameter length taper-fn include-threading compensator negative]
:or {taper-fn base/rounding-taper, include-threading true,
negative false, compensator dfm/none}
:as options}]
{:pre [(spec/valid? ::iso-schema/m-diameter m-diameter)]}
(let [options (merge {:outer-diameter m-diameter
:pitch (datum m-diameter :thread-pitch-coarse)
:angle standard-threading-angle
:taper-fn taper-fn}
options)]
(compensator m-diameter {:negative negative}
(if include-threading
(base/threading options)
(model/cylinder
(if negative (base/bolt-inner-radius options) (/ m-diameter 2))
length)))))
(defn bolt
"A model of an ISO metric bolt.
The very top of the head sits at [0 0 0] with the bolt pointing down.
The total length of the bolt is the sum of head height (computed from
nominal ISO size), unthreaded and threaded length parameters, plus an
optional point.
Though a drive-type parameter is accepted, only a socket-cap-style hex
drive is supported, and even that will be ignored on a negative.
Likewise, though a point-type parameter is accepted, the only implemented
option beyond the default flat point is a cone."
[{:keys [m-diameter angle head-type drive-type point-type channel-length
include-threading negative compensator]
:or {angle standard-threading-angle, include-threading true,
negative false, compensator dfm/none}
:as options}]
{:pre [(spec/valid? ::iso-schema/bolt-parameters options)]}
(let [hh (head-length m-diameter head-type)
lengths (base/shank-section-lengths (assoc options :head-length hh))
[unthreaded-length threaded-length] lengths
merged (merge
{:outer-diameter m-diameter
:pitch (datum m-diameter :thread-pitch-coarse)
:angle angle
:compensator compensator}
options
{:unthreaded-length unthreaded-length
:threaded-length threaded-length})
r (/ m-diameter 2)]
(if negative
(model/union
(model/translate [0 0 (/ hh -2)]
(bolt-head merged))
(compensator m-diameter {}
(when channel-length
(channel-above-bolt-head merged))
(when (pos? unthreaded-length)
(model/translate [0 0 (- (- hh) (/ unthreaded-length 2))]
(model/cylinder r unthreaded-length)))
(when (pos? threaded-length)
(model/translate
[0 0 (- (- (+ hh unthreaded-length)) (/ threaded-length 2))]
(rod {:m-diameter m-diameter
:length threaded-length
:angle angle
:taper-fn base/bolt-taper
:include-threading include-threading
:negative negative})))
(when (= point-type :cone)
(model/translate
[0 0 (- (- (+ hh unthreaded-length threaded-length))
(/ (base/bolt-inner-radius merged) 2))]
(base/cone merged)))))
;; Else a positive. Consider subtracting a drive from the head.
(maybe/difference
(positive-body bolt merged)
(when drive-type
(compensator m-diameter {}
(bolt-drive merged)))))))
(defn nut
"A single hex nut centred at [0 0 0]."
[{:keys [m-diameter height compensator negative]
:or {compensator dfm/none}
:as options}]
{:pre [(spec/valid? ::iso-schema/m-diameter m-diameter)]}
(let [height (or height (datum m-diameter :hex-nut-height))]
(if negative
;; A convex model of a nut.
(compensator (datum m-diameter :hex-head-long-diagonal) {}
(hex-item m-diameter height))
;; A more complete model.
(model/difference
;; Recurse to make the positive model.
(positive-body nut options)
;; Cut out the threading.
(rod (merge options
{:length height :taper-fn base/flare :negative true}))))))
(defn washer
"A flat, round washer centred at [0 0 0]."
[{:keys [m-diameter inner-diameter outer-diameter height]}]
(let [id (or inner-diameter (datum m-diameter :iso7089-inner-diameter))
od (or outer-diameter (datum m-diameter :iso7089-outer-diameter))
thickness (or height (datum m-diameter :iso7089-thickness))]
(model/difference
(model/cylinder (/ od 2) thickness)
(model/cylinder (/ id 2) (+ thickness 1)))))