1//////////////////////////////////////////////////////////////////////
2// LibFile: joiners.scad
3// Modules for joining separately printed parts including screw together, snap-together and dovetails.
4// Includes:
5// include <BOSL2/std.scad>
6// include <BOSL2/joiners.scad>
7// FileGroup: Parts
8// FileSummary: Joiner shapes for connecting separately printed objects.
9//////////////////////////////////////////////////////////////////////
10
11
12include <rounding.scad>
13
14
15// Section: Half Joiners
16
17
18// Function&Module: half_joiner_clear()
19// Usage: As Module
20// half_joiner_clear(l, w, [ang=], [clearance=], [overlap=]) [ATTACHMENTS];
21// Usage: As Function
22// vnf = half_joiner_clear(l, w, [ang=], [clearance=], [overlap=]);
23// Topics: Joiners, Parts
24// Description:
25// Creates a mask to clear an area so that a half_joiner can be placed there.
26// Arguments:
27// l = Length of the joiner to clear space for.
28// w = Width of the joiner to clear space for.
29// ang = Overhang angle of the joiner.
30// ---
31// clearance = Extra width to clear.
32// overlap = Extra depth to clear.
33// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
34// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
35// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
36// See Also: half_joiner_clear(), half_joiner(), half_joiner2(), joiner_clear(), joiner()
37// Example:
38// half_joiner_clear();
39function half_joiner_clear(l=20, w=10, ang=30, clearance=0, overlap=0.01, anchor=CENTER, spin=0, orient=UP) =
40 let(
41 guide = [w/3-get_slop()*2, ang_adj_to_opp(ang, l/3)*2, l/3],
42 path = [
43 [ l/2,-overlap], [ guide.z/2, -guide.y/2-overlap],
44 [-guide.z/2, -guide.y/2-overlap], [-l/2,-overlap],
45 [-l/2, overlap], [-guide.z/2, guide.y/2+overlap],
46 [ guide.z/2, guide.y/2+overlap], [ l/2, overlap],
47 ],
48 dpath = deduplicate(path, closed=true),
49 vnf = linear_sweep(dpath, height=w+clearance*2, center=true, spin=90, orient=RIGHT)
50 ) reorient(anchor,spin,orient, vnf=vnf, p=vnf);
51
52module half_joiner_clear(l=20, w=10, ang=30, clearance=0, overlap=0.01, anchor=CENTER, spin=0, orient=UP)
53{
54 vnf = half_joiner_clear(l=l, w=w, ang=ang, clearance=clearance, overlap=overlap);
55 attachable(anchor,spin,orient, vnf=vnf) {
56 vnf_polyhedron(vnf, convexity=2);
57 children();
58 }
59}
60
61
62// Function&Module: half_joiner()
63// Usage: As Module
64// half_joiner(l, w, [base=], [ang=], [screwsize=], [$slop=]) [ATTACHMENTS];
65// Usage: As Function
66// vnf = half_joiner(l, w, [base=], [ang=], [screwsize=], [$slop=]);
67// Topics: Joiners, Parts
68// Description:
69// Creates a half_joiner object that can be attached to a matching half_joiner2 object.
70// Arguments:
71// l = Length of the half_joiner.
72// w = Width of the half_joiner.
73// ---
74// base = Length of the backing to the half_joiner.
75// ang = Overhang angle of the half_joiner.
76// screwsize = If given, diameter of screwhole.
77// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
78// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
79// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
80// $slop = Printer specific slop value to make parts fit more closely.
81// See Also: half_joiner_clear(), half_joiner(), half_joiner2(), joiner_clear(), joiner()
82// Examples(FlatSpin,VPD=75):
83// half_joiner(screwsize=3);
84// half_joiner(l=20,w=10,base=10);
85// Example(3D):
86// diff()
87// cuboid(40)
88// attach([FWD,TOP,RIGHT])
89// xcopies(20) half_joiner();
90function half_joiner(l=20, w=10, base=10, ang=30, screwsize, anchor=CENTER, spin=0, orient=UP) =
91 let(
92 guide = [w/3-get_slop()*2, ang_adj_to_opp(ang, l/3)*2, l/3],
93 snap_h = 1,
94 snap = [guide.x+snap_h, 2*snap_h, l*0.6],
95 slope = guide.z/2/(w/8),
96 snap_top = slope * (snap.x-guide.x)/2,
97
98 verts = [
99 [-w/2,-base,-l/2], [-w/2,-base,l/2], [w/2,-base,l/2], [w/2,-base,-l/2],
100
101 [-w/2, 0,-l/2],
102 [-w/2,-guide.y/2,-guide.z/2],
103 [-w/2,-guide.y/2, guide.z/2],
104 [-w/2, 0,l/2],
105 [ w/2, 0,l/2],
106 [ w/2,-guide.y/2, guide.z/2],
107 [ w/2,-guide.y/2,-guide.z/2],
108 [ w/2, 0,-l/2],
109
110 [-guide.x/2, 0,-l/2],
111 [-guide.x/2,-guide.y/2,-guide.z/2],
112 [-guide.x/2-w/8,-guide.y/2, 0],
113 [-guide.x/2,-guide.y/2, guide.z/2],
114 [-guide.x/2, 0,l/2],
115 [ guide.x/2, 0,l/2],
116 [ guide.x/2,-guide.y/2, guide.z/2],
117 [ guide.x/2+w/8,-guide.y/2, 0],
118 [ guide.x/2,-guide.y/2,-guide.z/2],
119 [ guide.x/2, 0,-l/2],
120
121 [-w/6, -snap.y/2, -snap.z/2],
122 [-w/6, -snap.y/2, -guide.z/2],
123 [-snap.x/2, 0, snap_top-guide.z/2],
124 [-w/6, snap.y/2, -guide.z/2],
125 [-w/6, snap.y/2, -snap.z/2],
126 [-snap.x/2, 0, snap_top-snap.z/2],
127
128 [-w/6, -snap.y/2, snap.z/2],
129 [-w/6, -snap.y/2, guide.z/2],
130 [-snap.x/2, 0, guide.z/2-snap_top],
131 [-w/6, snap.y/2, guide.z/2],
132 [-w/6, snap.y/2, snap.z/2],
133 [-snap.x/2, 0, snap.z/2-snap_top],
134
135 [ w/6, -snap.y/2, snap.z/2],
136 [ w/6, -snap.y/2, guide.z/2],
137 [ snap.x/2, 0, guide.z/2-snap_top],
138 [ w/6, snap.y/2, guide.z/2],
139 [ w/6, snap.y/2, snap.z/2],
140 [ snap.x/2, 0, snap.z/2-snap_top],
141
142 [ w/6, -snap.y/2, -snap.z/2],
143 [ w/6, -snap.y/2, -guide.z/2],
144 [ snap.x/2, 0, snap_top-guide.z/2],
145 [ w/6, snap.y/2, -guide.z/2],
146 [ w/6, snap.y/2, -snap.z/2],
147 [ snap.x/2, 0, snap_top-snap.z/2],
148
149 [-w/6, guide.y/2, -guide.z/2],
150 [-guide.x/2-w/8, guide.y/2, 0],
151 [-w/6, guide.y/2, guide.z/2],
152 [ w/6, guide.y/2, guide.z/2],
153 [ guide.x/2+w/8, guide.y/2, 0],
154 [ w/6, guide.y/2, -guide.z/2],
155
156 if (screwsize != undef) each [
157 for (a = [0:45:359]) [guide.x/2+w/8, 0, 0] + screwsize * 1.1 / 2 * [-abs(sin(a))/slope, cos(a), sin(a)],
158 for (a = [0:45:359]) [-(guide.x/2+w/8), 0, 0] + screwsize * 1.1 / 2 * [abs(sin(a))/slope, cos(a), sin(a)],
159 ]
160 ],
161 faces = [
162 [0,1,2], [2,3,0],
163
164 [0,4,5], [0,5,6], [0,6,1], [1,6,7],
165 [3,10,11], [3,9,10], [2,9,3], [2,8,9],
166
167 [1,7,16], [1,16,17], [1,17,8], [1,8,2],
168 [0,3,11], [0,11,21], [0,21,12], [0,12,4],
169
170 [10,20,11], [20,21,11],
171 [12,13,5], [12,5,4],
172 [9,8,18], [17,18,8],
173 [6,16,7], [6,15,16],
174
175 [19,10,9], [19,9,18], [19,20,10],
176 [6,14,15], [6,5,14], [5,13,14],
177
178 [24,26,25], [26,24,27],
179 [22,27,24], [22,24,23],
180 [22,26,27],
181
182 [30,32,33], [30,31,32],
183 [30,33,28], [30,28,29],
184 [32,28,33],
185
186 [40,41,42], [40,42,45],
187 [45,42,43], [43,44,45],
188 [40,45,44],
189
190 [36,38,37], [36,39,38],
191 [36,35,34], [36,34,39],
192 [39,34,38],
193
194 [12,26,22], [12,22,13], [22,23,13], [12,46,26], [46,25,26],
195 [16,28,32], [16,15,28], [15,29,28], [48,16,32], [32,31,48],
196 [17,38,34], [17,34,18], [18,34,35], [49,38,17], [37,38,49],
197 [21,40,44], [51,21,44], [43,51,44], [20,40,21], [20,41,40],
198
199 [17,16,49], [49,16,48],
200 [21,51,46], [46,12,21],
201
202 [51,50,49], [48,47,46], [46,51,49], [46,49,48],
203
204 if (screwsize == undef) each [
205 [19,36,50], [19,35,36], [19,18,35], [36,37,50], [49,50,37],
206 [19,50,42], [19,42,41], [41,20,19], [50,43,42], [50,51,43],
207 [14,24,47], [14,23,24], [14,13,23], [47,24,25], [46,47,25],
208 [47,30,14], [14,30,29], [14,29,15], [47,31,30], [47,48,31],
209 ] else each [
210 [20,19,56], [20,56,57], [20,57,58], [20,58,42], [20,42,41],
211 [50,51,52], [51,59,52], [51,58,59], [51,42,58], [51,43,42],
212 [49,50,52], [49,52,53], [49,53,54], [49,54,36], [49,36,37],
213 [56,19,18], [18,55,56], [18,54,55], [18,36,54], [18,35,36],
214 [14,64,15], [15,64,63], [15,63,62], [15,62,30], [15,30,29],
215 [48,31,30], [48,30,62], [48,62,61], [48,61,60], [60,47,48],
216 [13,23,24], [13,24,66], [13,66,65], [13,65,64], [64,14,13],
217 [46,47,60], [46,60,67], [46,67,66], [46,66,24], [46,24,25],
218 for (i=[0:7]) let(b=52) [b+i, b+8+i, b+8+(i+1)%8],
219 for (i=[0:7]) let(b=52) [b+i, b+8+(i+1)%8, b+(i+1)%8],
220 ],
221 ],
222 pvnf = [verts, faces],
223 vnf = xrot(90, p=pvnf)
224 ) reorient(anchor,spin,orient, size=[w,l,base*2], p=vnf);
225
226module half_joiner(l=20, w=10, base=10, ang=30, screwsize, anchor=CENTER, spin=0, orient=UP)
227{
228 vnf = half_joiner(l=l, w=w, base=base, ang=ang, screwsize=screwsize);
229 if (is_list($tags_shown) && in_list("remove",$tags_shown)) {
230 attachable(anchor,spin,orient, size=[w,l,base*2], $tag="remove") {
231 half_joiner_clear(l=l, w=w, ang=ang, clearance=1);
232 union();
233 }
234 } else {
235 attachable(anchor,spin,orient, size=[w,base*2,l], $tag="keep") {
236 vnf_polyhedron(vnf, convexity=12);
237 children();
238 }
239 }
240}
241
242
243// Function&Module: half_joiner2()
244// Usage: As Module
245// half_joiner2(l, w, [base=], [ang=], [screwsize=])
246// Usage: As Function
247// vnf = half_joiner2(l, w, [base=], [ang=], [screwsize=])
248// Topics: Joiners, Parts
249// Description:
250// Creates a half_joiner2 object that can be attached to half_joiner object.
251// Arguments:
252// l = Length of the half_joiner.
253// w = Width of the half_joiner.
254// ---
255// base = Length of the backing to the half_joiner.
256// ang = Overhang angle of the half_joiner.
257// screwsize = Diameter of screwhole.
258// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
259// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
260// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
261// See Also: half_joiner_clear(), half_joiner(), half_joiner2(), joiner_clear(), joiner()
262// Examples(FlatSpin,VPD=75):
263// half_joiner2(screwsize=3);
264// half_joiner2(w=10,base=10,l=20);
265// Example(3D):
266// diff()
267// cuboid(40)
268// attach([FWD,TOP,RIGHT])
269// xcopies(20) half_joiner2();
270function half_joiner2(l=20, w=10, base=10, ang=30, screwsize, anchor=CENTER, spin=0, orient=UP) =
271 let(
272 guide = [w/3, ang_adj_to_opp(ang, l/3)*2, l/3],
273 snap_h = 1,
274 snap = [guide.x+snap_h, 2*snap_h, l*0.6],
275 slope = guide.z/2/(w/8),
276 snap_top = slope * (snap.x-guide.x)/2,
277
278 verts = [
279 [-w/2,-base,-l/2], [-w/2,-base,l/2], [w/2,-base,l/2], [w/2,-base,-l/2],
280
281 [-w/2, 0,-l/2],
282 [-w/2, guide.y/2,-guide.z/2],
283 [-w/2, guide.y/2, guide.z/2],
284 [-w/2, 0,l/2],
285 [ w/2, 0,l/2],
286 [ w/2, guide.y/2, guide.z/2],
287 [ w/2, guide.y/2,-guide.z/2],
288 [ w/2, 0,-l/2],
289
290 [-guide.x/2, 0,-l/2],
291 [-guide.x/2,-guide.y/2,-guide.z/2],
292 [-guide.x/2-w/8,-guide.y/2, 0],
293 [-guide.x/2,-guide.y/2, guide.z/2],
294 [-guide.x/2, 0,l/2],
295 [ guide.x/2, 0,l/2],
296 [ guide.x/2,-guide.y/2, guide.z/2],
297 [ guide.x/2+w/8,-guide.y/2, 0],
298 [ guide.x/2,-guide.y/2,-guide.z/2],
299 [ guide.x/2, 0,-l/2],
300
301 [-w/6, -snap.y/2, -snap.z/2],
302 [-w/6, -snap.y/2, -guide.z/2],
303 [-snap.x/2, 0, snap_top-guide.z/2],
304 [-w/6, snap.y/2, -guide.z/2],
305 [-w/6, snap.y/2, -snap.z/2],
306 [-snap.x/2, 0, snap_top-snap.z/2],
307
308 [-w/6, -snap.y/2, snap.z/2],
309 [-w/6, -snap.y/2, guide.z/2],
310 [-snap.x/2, 0, guide.z/2-snap_top],
311 [-w/6, snap.y/2, guide.z/2],
312 [-w/6, snap.y/2, snap.z/2],
313 [-snap.x/2, 0, snap.z/2-snap_top],
314
315 [ w/6, -snap.y/2, snap.z/2],
316 [ w/6, -snap.y/2, guide.z/2],
317 [ snap.x/2, 0, guide.z/2-snap_top],
318 [ w/6, snap.y/2, guide.z/2],
319 [ w/6, snap.y/2, snap.z/2],
320 [ snap.x/2, 0, snap.z/2-snap_top],
321
322 [ w/6, -snap.y/2, -snap.z/2],
323 [ w/6, -snap.y/2, -guide.z/2],
324 [ snap.x/2, 0, snap_top-guide.z/2],
325 [ w/6, snap.y/2, -guide.z/2],
326 [ w/6, snap.y/2, -snap.z/2],
327 [ snap.x/2, 0, snap_top-snap.z/2],
328
329 [-w/6, guide.y/2, -guide.z/2],
330 [-guide.x/2-w/8, guide.y/2, 0],
331 [-w/6, guide.y/2, guide.z/2],
332 [ w/6, guide.y/2, guide.z/2],
333 [ guide.x/2+w/8, guide.y/2, 0],
334 [ w/6, guide.y/2, -guide.z/2],
335
336 if (screwsize != undef) each [
337 for (a = [0:45:359]) [guide.x/2+w/8, 0, 0] + screwsize * 1.1 / 2 * [-abs(sin(a))/slope, cos(a), sin(a)],
338 for (a = [0:45:359]) [-(guide.x/2+w/8), 0, 0] + screwsize * 1.1 / 2 * [abs(sin(a))/slope, cos(a), sin(a)],
339 for (a = [0:45:359]) [w/2, 0, 0] + screwsize * 1.1 / 2 * [0, cos(a), sin(a)],
340 for (a = [0:45:359]) [-w/2, 0, 0] + screwsize * 1.1 / 2 * [0, cos(a), sin(a)],
341 ]
342 ],
343 faces = [
344 [0,1,2], [2,3,0],
345
346 [1,7,16], [1,16,17], [1,17,8], [1,8,2],
347 [0,3,11], [0,11,21], [0,21,12], [0,12,4],
348
349 [10,51,11], [51,21,11],
350 [12,46,5], [12,5,4],
351 [9,8,49], [17,49,8],
352 [6,16,7], [6,48,16],
353
354 [50,10,9], [50,9,49], [50,51,10],
355 [6,47,48], [6,5,47], [5,46,47],
356
357 [24,25,26], [26,27,24],
358 [22,24,27], [22,23,24],
359 [22,27,26],
360
361 [30,33,32], [30,32,31],
362 [30,28,33], [30,29,28],
363 [32,33,28],
364
365 [40,42,41], [40,45,42],
366 [45,43,42], [43,45,44],
367 [40,44,45],
368
369 [36,37,38], [36,38,39],
370 [36,34,35], [36,39,34],
371 [39,38,34],
372
373 [12,22,26], [12,13,22], [22,13,23], [12,26,46], [46,26,25],
374 [16,32,28], [16,28,15], [15,28,29], [48,32,16], [32,48,31],
375 [17,34,38], [17,18,34], [18,35,34], [49,17,38], [37,49,38],
376 [21,44,40], [51,44,21], [43,44,51], [20,21,40], [20,40,41],
377
378 [17,16,18], [18,16,15],
379 [21,20,13], [13,12,21],
380
381 [20,19,18], [15,14,13], [13,20,18], [13,18,15],
382
383 if (screwsize == undef) each [
384 [0,4,5], [0,5,6], [0,6,1], [1,6,7],
385 [3,10,11], [3,9,10], [2,9,3], [2,8,9],
386
387 [19,50,36], [19,36,35], [19,35,18], [36,50,37], [49,37,50],
388 [19,42,50], [19,41,42], [41,19,20], [50,42,43], [50,43,51],
389 [14,47,24], [14,24,23], [14,23,13], [47,25,24], [46,25,47],
390 [47,14,30], [14,29,30], [14,15,29], [47,30,31], [47,31,48],
391 ] else each [
392 [3,2,72], [2,71,72], [2,70,71], [2,8,70],
393 [8,9,70], [9,69,70], [9,68,69], [9,10,68],
394 [10,75,68], [10,74,75], [10,11,74],
395 [3,72,73], [3,73,74], [3,74,11],
396
397 [1,0,80], [0,81,80], [0,82,81], [0,4,82],
398 [4,5,82], [5,83,82], [5,76,83], [5,6,76],
399 [6,77,76], [6,78,77], [6,7,78],
400 [7,1,78], [1,79,78], [1,80,79],
401
402 [20,56,19], [20,57,56], [20,58,57], [20,42,58], [20,41,42],
403 [50,52,51], [51,52,59], [51,59,58], [51,58,42], [51,42,43],
404 [49,52,50], [49,53,52], [49,54,53], [49,36,54], [49,37,36],
405 [56,18,19], [18,56,55], [18,55,54], [18,54,36], [18,36,35],
406 [14,15,64], [15,63,64], [15,62,63], [15,30,62], [15,29,30],
407 [48,30,31], [48,62,30], [48,61,62], [48,60,61], [60,48,47],
408 [13,24,23], [13,66,24], [13,65,66], [13,64,65], [64,13,14],
409 [46,60,47], [46,67,60], [46,66,67], [46,24,66], [46,25,24],
410
411 for (i=[0:7]) let(b=52) each [
412 [b+i, b+16+(i+1)%8, b+16+i],
413 [b+i, b+(i+1)%8, b+16+(i+1)%8],
414 ],
415 for (i=[0:7]) let(b=60) each [
416 [b+i, b+16+i, b+16+(i+1)%8],
417 [b+i, b+16+(i+1)%8, b+(i+1)%8],
418 ],
419 ],
420 ],
421 pvnf = [verts, faces],
422 vnf = xrot(90, p=pvnf)
423 ) reorient(anchor,spin,orient, size=[w,l,base*2], p=vnf);
424
425module half_joiner2(l=20, w=10, base=10, ang=30, screwsize, anchor=CENTER, spin=0, orient=UP)
426{
427 vnf = half_joiner2(l=l, w=w, base=base, ang=ang, screwsize=screwsize);
428 if (is_list($tags_shown) && in_list("remove",$tags_shown)) {
429 attachable(anchor,spin,orient, size=[w,l,base*2], $tag="remove") {
430 half_joiner_clear(l=l, w=w, ang=ang, clearance=1);
431 union();
432 }
433 } else {
434 attachable(anchor,spin,orient, size=[w,base*2,l], $tag="keep") {
435 vnf_polyhedron(vnf, convexity=12);
436 children();
437 }
438 }
439}
440
441
442
443// Section: Full Joiners
444
445
446// Module: joiner_clear()
447// Description:
448// Creates a mask to clear an area so that a joiner can be placed there.
449// Usage:
450// joiner_clear(l, w, [ang=], [clearance=], [overlap=]) [ATTACHMENTS];
451// Topics: Joiners, Parts
452// Arguments:
453// l = Length of the joiner to clear space for.
454// w = Width of the joiner to clear space for.
455// ang = Overhang angle of the joiner.
456// ---
457// clearance = Extra width to clear.
458// overlap = Extra depth to clear.
459// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
460// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
461// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
462// See Also: half_joiner_clear(), half_joiner(), half_joiner2(), joiner_clear(), joiner()
463// Example:
464// joiner_clear();
465function joiner_clear(l=40, w=10, ang=30, clearance=0, overlap=0.01, anchor=CENTER, spin=0, orient=UP) = no_function("joiner_clear");
466module joiner_clear(l=40, w=10, ang=30, clearance=0, overlap=0.01, anchor=CENTER, spin=0, orient=UP)
467{
468 dmnd_height = l*0.5;
469 dmnd_width = dmnd_height*tan(ang);
470 guide_size = w/3;
471 guide_width = 2*(dmnd_height/2-guide_size)*tan(ang);
472
473 attachable(anchor,spin,orient, size=[w, guide_width, l]) {
474 union() {
475 back(l/4) half_joiner_clear(l=l/2+0.01, w=w, ang=ang, overlap=overlap, clearance=clearance);
476 fwd(l/4) half_joiner_clear(l=l/2+0.01, w=w, ang=ang, overlap=overlap, clearance=-0.01);
477 }
478 children();
479 }
480}
481
482
483
484// Module: joiner()
485// Usage:
486// joiner(l, w, base, [ang=], [screwsize=], [$slop=]) [ATTACHMENTS];
487// Topics: Joiners, Parts
488// Description:
489// Creates a joiner object that can be attached to another joiner object.
490// Arguments:
491// l = Length of the joiner.
492// w = Width of the joiner.
493// base = Length of the backing to the joiner.
494// ang = Overhang angle of the joiner.
495// ---
496// screwsize = If given, diameter of screwhole.
497// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
498// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
499// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
500// $slop = Printer specific slop value to make parts fit more closely.
501// See Also: half_joiner_clear(), half_joiner(), half_joiner2(), joiner_clear(), joiner()
502// Examples(FlatSpin,VPD=125):
503// joiner(screwsize=3);
504// joiner(l=40, w=10, base=10);
505// Example(3D):
506// diff()
507// cuboid(50)
508// attach([FWD,TOP,RIGHT])
509// zrot_copies(n=2,r=15)
510// joiner();
511function joiner(l=40, w=10, base=10, ang=30, screwsize, anchor=CENTER, spin=0, orient=UP) = no_function("joiner");
512module joiner(l=40, w=10, base=10, ang=30, screwsize, anchor=CENTER, spin=0, orient=UP)
513{
514 if (is_list($tags_shown) && in_list("remove",$tags_shown)) {
515 attachable(anchor,spin,orient, size=[w,l,base*2], $tag="remove") {
516 joiner_clear(w=w, l=l, ang=ang, clearance=1);
517 union();
518 }
519 } else {
520 attachable(anchor,spin,orient, size=[w,l,base*2], $tag="keep") {
521 union() {
522 back(l/4) half_joiner(l=l/2, w=w, base=base, ang=ang, screwsize=screwsize);
523 fwd(l/4) half_joiner2(l=l/2, w=w, base=base, ang=ang, screwsize=screwsize);
524 }
525 children();
526 }
527 }
528}
529
530
531
532// Section: Dovetails
533
534// Module: dovetail()
535//
536// Usage:
537// dovetail(gender, w=|width, h=|height, slide|thickness=, [slope=|angle=], [taper=|back_width=], [chamfer=], [r=|radius=], [round=], [extra=], [$slop=])
538//
539// Description:
540// Produces a possibly tapered dovetail joint shape to attach to or subtract from two parts you wish to join together.
541// The tapered dovetail is particularly advantageous for long joints because the joint assembles without binding until
542// it is fully closed, and then wedges tightly. You can chamfer or round the corners of the dovetail shape for better
543// printing and assembly, or choose a fully rounded joint that looks more like a puzzle piece. The dovetail appears
544// parallel to the Y axis and projecting upwards, so in its default orientation it will slide together with a translation
545// in the positive Y direction. The gender determines whether the shape is meant to be added to your model or
546// differenced, and it also changes the anchor and orientation. The default anchor for dovetails is BOTTOM;
547// the default orientation depends on the gender, with male dovetails oriented UP and female ones DOWN. The dovetails by default
548// have extra extension of 0.01 for unions and differences. You should ensure that attachment is done with overlap=0 to ensure that
549// the sizing and positioning is correct. To adjust the fit, use the $slop variable, which increases the depth and width of
550// the female part of the joint to allow a clearance gap of $slop on each of the three sides.
551//
552// Arguments:
553// gender = A string, "male" or "female", to specify the gender of the dovetail.
554// w / width = Width (at the wider, top end) of the dovetail before tapering
555// h / height = Height of the dovetail (the amount it projects from its base)
556// slide / thickness = Distance the dovetail slides when you assemble it (length of sliding dovetails, thickness of regular dovetails)
557// ---
558// slope = slope of the dovetail. Standard woodworking slopes are 4, 6, or 8. Default: 6.
559// angle = angle (in degrees) of the dovetail. Specify only one of slope and angle.
560// taper = taper angle (in degrees). Dovetail gets narrower by this angle. Default: no taper
561// back_width = width of right hand end of the dovetail. This alternate method of specifying the taper may be easier to manage. Specify only one of `taper` and `back_width`. Note that `back_width` should be smaller than `width` to taper in the customary direction, with the smaller end at the back.
562// chamfer = amount to chamfer the corners of the joint (Default: no chamfer)
563// r / radius = amount to round over the corners of the joint (Default: no rounding)
564// round = true to round both corners of the dovetail and give it a puzzle piece look. Default: false.
565// $slop = Increase the width of socket by double this amount and depth by this amount to allow adjustment of the fit.
566// extra = amount of extra length and base extension added to dovetails for unions and differences. Default: 0.01
567// Example: Ordinary straight dovetail, male version (sticking up) and female version (below the xy plane)
568// dovetail("male", width=15, height=8, slide=30);
569// right(20) dovetail("female", width=15, height=8, slide=30);
570// Example: Adding a 6 degree taper (Such a big taper is usually not necessary, but easier to see for the example.)
571// dovetail("male", w=15, h=8, slide=30, taper=6);
572// right(20) dovetail("female", 15, 8, 30, taper=6); // Same as above
573// Example: A block that can link to itself
574// diff()
575// cuboid([50,30,10]){
576// attach(BACK) dovetail("male", slide=10, width=15, height=8);
577// tag("remove")attach(FRONT) dovetail("female", slide=10, width=15, height=8);
578// }
579// Example: Setting the dovetail angle. This is too extreme to be useful.
580// diff()
581// cuboid([50,30,10]){
582// attach(BACK) dovetail("male", slide=10, width=15, height=8, angle=30);
583// tag("remove")attach(FRONT) dovetail("female", slide=10, width=15, height=8, angle=30);
584// }
585// Example: Adding a chamfer helps printed parts fit together without problems at the corners
586// diff("remove")
587// cuboid([50,30,10]){
588// attach(BACK) dovetail("male", slide=10, width=15, height=8, chamfer=1);
589// tag("remove")attach(FRONT) dovetail("female", slide=10, width=15, height=8,chamfer=1);
590// }
591// Example: Rounding the outside corners is another option
592// diff("remove")
593// cuboid([50,30,10]) {
594// attach(BACK) dovetail("male", slide=10, width=15, height=8, radius=1, $fn=32);
595// tag("remove") attach(FRONT) dovetail("female", slide=10, width=15, height=8, radius=1, $fn=32);
596// }
597// Example: Or you can make a fully rounded joint
598// $fn=32;
599// diff("remove")
600// cuboid([50,30,10]){
601// attach(BACK) dovetail("male", slide=10, width=15, height=8, radius=1.5, round=true);
602// tag("remove")attach(FRONT) dovetail("female", slide=10, width=15, height=8, radius=1.5, round=true);
603// }
604// Example: With a long joint like this, a taper makes the joint easy to assemble. It will go together easily and wedge tightly if you get the tolerances right. Specifying the taper with `back_width` may be easier than using a taper angle.
605// cuboid([50,30,10])
606// attach(TOP) dovetail("male", slide=50, width=18, height=4, back_width=15, spin=90);
607// fwd(35)
608// diff("remove")
609// cuboid([50,30,10])
610// tag("remove") attach(TOP) dovetail("female", slide=50, width=18, height=4, back_width=15, spin=90);
611// Example: A series of dovetails forming a tail board, with the inside of the joint up. A standard wood joint would have a zero taper.
612// cuboid([50,30,10])
613// attach(BACK) xcopies(10,5) dovetail("male", slide=10, width=7, taper=4, height=4);
614// Example: Mating pin board for a half-blind right angle joint, where the joint only shows on the side but not the front. Note that the anchor method and use of `spin` ensures that the joint works even with a taper.
615// diff("remove")
616// cuboid([50,30,10])
617// tag("remove")position(TOP+BACK) xcopies(10,5) dovetail("female", slide=10, width=7, taper=4, height=4, anchor=BOTTOM+FRONT,spin=180);
618function dovetail(gender, width, height, slide, h, w, angle, slope, thickness, taper, back_width, chamfer, extra=0.01, r, radius, round=false, anchor=BOTTOM, spin=0, orient) = no_function("dovetail");
619module dovetail(gender, width, height, slide, h, w, angle, slope, thickness, taper, back_width, chamfer, extra=0.01, r, radius, round=false, anchor=BOTTOM, spin=0, orient)
620{
621 radius = get_radius(r1=radius,r2=r);
622 slide = one_defined([slide,thickness],"slide,thickness");
623 h = one_defined([h,height],"h,height");
624 w = one_defined([w,width],"w,width");
625 orient = is_def(orient) ? orient
626 : gender == "female" ? DOWN
627 : UP;
628 count = num_defined([angle,slope]);
629 count2 = num_defined([taper,back_width]);
630 count3 = num_defined([chamfer, radius]);
631 dummy =
632 assert(count<=1, "Do not specify both angle and slope")
633 assert(count2<=1, "Do not specify both taper and back_width")
634 assert(count3<=1 || (radius==0 && chamfer==0), "Do not specify both chamfer and radius");
635 slope = is_def(slope) ? slope
636 : is_def(angle) ? 1/tan(angle)
637 : 6;
638 height_slop = gender == "female" ? get_slop() : 0;
639
640 // Need taper angle for computing width adjustment, but not used elsewhere
641 taper_ang = is_def(taper) ? taper
642 : is_def(back_width) ? atan((back_width-width)/2/slide)
643 : 0;
644 // This is the adjustment factor for width to grow in the direction normal to the dovetail face
645 wfactor = sqrt( 1/slope^2 + 1/cos(taper_ang)^2 );
646 // adjust width for increased height adjust for normal to dovetail surface
647 width_slop = 2*height_slop/slope + 2* height_slop * wfactor;
648 width = w + width_slop;
649 height = h + height_slop;
650 back_width = u_add(back_width, width_slop);
651
652 extra_offset = is_def(taper) ? -extra * tan(taper)
653 : is_def(back_width) ? extra * (back_width-width)/slide/2
654 : 0;
655
656 size = is_def(chamfer) && chamfer>0 ? chamfer
657 : is_def(radius) && radius>0 ? radius
658 : 0;
659 fullsize = round ? [size,size]
660 : gender == "male" ? [size,0]
661 : [0,size];
662
663 type = is_def(chamfer) && chamfer>0 ? "chamfer" : "circle";
664
665 smallend_half = round_corners(
666 move(
667 [0,-slide/2-extra,0],
668 p=[
669 [0, 0, height],
670 [width/2 - extra_offset, 0, height],
671 [width/2 - extra_offset - height/slope, 0, 0 ],
672 [width/2 - extra_offset + height, 0, 0 ]
673 ]
674 ),
675 method=type, cut = fullsize, closed=false
676 );
677
678 smallend_points = concat(select(smallend_half, 1, -2), [down(extra,p=select(smallend_half, -2))]);
679 offset = is_def(taper) ? -slide * tan(taper)
680 : is_def(back_width) ? (back_width-width) / 2
681 : 0;
682 bigend_points = move([offset+2*extra_offset,slide+2*extra,0], p=smallend_points);
683
684 bigenough = all_nonnegative(column(smallend_half,0)) && all_nonnegative(column(bigend_points,0));
685
686 assert(bigenough, "Width of dovetail is not large enough for its geometry (angle and taper");
687
688 //adjustment = $overlap * (gender == "male" ? -1 : 1); // Adjustment for default overlap in attach()
689 adjustment = 0; // Default overlap is assumed to be zero
690
691 // This code computes the true normal from which the exact width factor can be obtained
692 // as the x component. Comparing to wfactor above shows that they agree.
693 // pts = [smallend_points[0], smallend_points[1], bigend_points[1],bigend_points[0]];
694 // n = -polygon_normal(pts);
695 // echo(n=n);
696 // echo(invwfactor = 1/wfactor, error = n.x-1/wfactor);
697
698 attachable(anchor,spin,orient, size=[width+2*offset, slide, height]) {
699 down(height/2+adjustment) {
700 //color("red")stroke([pts],width=.1);
701
702 skin(
703 [
704 reverse(concat(smallend_points, xflip(p=reverse(smallend_points)))),
705 reverse(concat(bigend_points, xflip(p=reverse(bigend_points))))
706 ],
707 slices=0, convexity=4
708 );
709 }
710 children();
711 }
712}
713
714
715// Section: Tension Clips
716
717// h is total height above 0 of the nub
718// nub extends below xy plane by distance nub/2
719module _pin_nub(r, nub, h)
720{
721 L = h / 4;
722 rotate_extrude(){
723 polygon(
724 [[ 0,-nub/2],
725 [-r,-nub/2],
726 [-r-nub, nub/2],
727 [-r-nub, nub/2+L],
728 [-r, h],
729 [0, h]]);
730 }
731}
732
733
734module _pin_slot(l, r, t, d, nub, depth, stretch) {
735 yscale(4)
736 intersection() {
737 translate([t, 0, d + t / 4])
738 _pin_nub(r = r + t, nub = nub, h = l - (d + t / 4));
739 translate([-t, 0, d + t / 4])
740 _pin_nub(r = r + t, nub = nub, h = l - (d + t / 4));
741 }
742 cube([2 * r, depth, 2 * l], center = true);
743 up(l)
744 zscale(stretch)
745 ycyl(r = r, h = depth);
746}
747
748
749module _pin_shaft(r, lStraight, nub, nubscale, stretch, d, pointed)
750{
751 extra = 0.02; // This sets the extra extension below the socket bottom
752 // so that difference() works without issues
753 rPoint = r / sqrt(2);
754 down(extra) cylinder(r = r, h = lStraight + extra);
755 up(lStraight) {
756 zscale(stretch) {
757 sphere(r = r);
758 if (pointed) up(rPoint) cylinder(r1 = rPoint, r2 = 0, h = rPoint);
759 }
760 }
761 up(d) yscale(nubscale) _pin_nub(r = r, nub = nub, h = lStraight - d);
762}
763
764function _pin_size(size) =
765 is_undef(size) ? [] :
766 let(sizeok = in_list(size,["tiny", "small","medium", "large", "standard"]))
767 assert(sizeok,"Pin size must be one of \"tiny\", \"small\", or \"standard\"")
768 size=="standard" || size=="large" ?
769 struct_set([], ["length", 10.8,
770 "diameter", 7,
771 "snap", 0.5,
772 "nub_depth", 1.8,
773 "thickness", 1.8,
774 "preload", 0.2]):
775 size=="medium" ?
776 struct_set([], ["length", 8,
777 "diameter", 4.6,
778 "snap", 0.45,
779 "nub_depth", 1.5,
780 "thickness", 1.4,
781 "preload", 0.2]) :
782 size=="small" ?
783 struct_set([], ["length", 6,
784 "diameter", 3.2,
785 "snap", 0.4,
786 "nub_depth", 1.2,
787 "thickness", 1.0,
788 "preload", 0.16]) :
789 size=="tiny" ?
790 struct_set([], ["length", 4,
791 "diameter", 2.5,
792 "snap", 0.25,
793 "nub_depth", 0.9,
794 "thickness", 0.8,
795 "preload", 0.1]):
796 undef;
797
798
799// Module: snap_pin()
800// Usage:
801// snap_pin(size, [pointed=], [anchor=], [spin=], [orient]=) [ATTACHMENTS];
802// snap_pin(r=|radius=|d=|diameter=, l=|length=, nub_depth=, snap=, thickness=, [clearance=], [preload=], [pointed=]) [ATTACHMENTS];
803// Description:
804// Creates a snap pin that can be inserted into an appropriate socket to connect two objects together. You can choose from some standard
805// pin dimensions by giving a size, or you can specify all the pin geometry parameters yourself. If you use a standard size you can
806// override the standard parameters by specifying other ones. The pins have flat sides so they can
807// be printed. When oriented UP the shaft of the pin runs in the Z direction and the flat sides are the front and back. The default
808// orientation (FRONT) and anchor (FRONT) places the pin in a printable configuration, flat side down on the xy plane.
809// The tightness of fit is determined by `preload` and `clearance`. To make pins tighter increase `preload` and/or decrease `clearance`.
810// .
811// The "large" or "standard" size pin has a length of 10.8 and diameter of 7. The "medium" pin has a length of 8 and diameter of 4.6. The "small" pin
812// has a length of 6 and diameter of 3.2. The "tiny" pin has a length of 4 and a diameter of 2.5.
813// .
814// This pin is based on https://www.thingiverse.com/thing:213310 by Emmett Lalishe
815// and a modified version at https://www.thingiverse.com/thing:3218332 by acwest
816// and distributed under the Creative Commons - Attribution - Share Alike License
817// Arguments:
818// size = text string to select from a list of predefined sizes, one of "standard", "small", or "tiny".
819// ---
820// pointed = set to true to get a pointed pin, false to get one with a rounded end. Default: true
821// r/radius = radius of the pin
822// d/diameter = diameter of the pin
823// l/length = length of the pin
824// nub_depth = the distance of the nub from the base of the pin
825// snap = how much snap the pin provides (the nub projection)
826// thickness = thickness of the pin walls
827// pointed = if true the pin is pointed, otherwise it has a rounded tip. Default: true
828// clearance = how far to shrink the pin away from the socket walls. Default: 0.2
829// preload = amount to move the nub towards the pin base, which can create tension from the misalignment with the socket. Default: 0.2
830// Example: Pin in native orientation
831// snap_pin("standard", anchor=CENTER, orient=UP, thickness = 1, $fn=40);
832// Example: Pins oriented for printing
833// xcopies(spacing=10, n=4) snap_pin("standard", $fn=40);
834function snap_pin(size,r,radius,d,diameter, l,length, nub_depth, snap, thickness, clearance=0.2, preload, pointed=true, anchor=FRONT, spin=0, orient=FRONT, center) =no_function("snap_pin");
835module snap_pin(size,r,radius,d,diameter, l,length, nub_depth, snap, thickness, clearance=0.2, preload, pointed=true, anchor=FRONT, spin=0, orient=FRONT, center) {
836 preload_default = 0.2;
837 sizedat = _pin_size(size);
838 radius = get_radius(r1=r,r2=radius,d1=d,d2=diameter,dflt=struct_val(sizedat,"diameter")/2);
839 length = first_defined([l,length,struct_val(sizedat,"length")]);
840 snap = first_defined([snap, struct_val(sizedat,"snap")]);
841 thickness = first_defined([thickness, struct_val(sizedat,"thickness")]);
842 nub_depth = first_defined([nub_depth, struct_val(sizedat,"nub_depth")]);
843 preload = first_defined([first_defined([preload, struct_val(sizedat, "preload")]),preload_default]);
844
845 nubscale = 0.9; // Mysterious arbitrary parameter
846
847 // The basic pin assumes a rounded cap of length sqrt(2)*r, which defines lStraight.
848 // If the point is enabled the cap length is instead 2*r
849 // preload shrinks the length, bringing the nubs closer together
850
851 rInner = radius - clearance;
852 stretch = sqrt(2)*radius/rInner; // extra stretch factor to make cap have proper length even though r is reduced.
853 lStraight = length - sqrt(2) * radius - clearance;
854 lPin = lStraight + (pointed ? 2*radius : sqrt(2)*radius);
855 attachable(anchor=anchor,spin=spin, orient=orient,
856 size=[nubscale*(2*rInner+2*snap + clearance),radius*sqrt(2)-2*clearance,2*lPin]){
857 zflip_copy()
858 difference() {
859 intersection() {
860 cube([3 * (radius + snap), radius * sqrt(2) - 2 * clearance, 2 * length + 3 * radius], center = true);
861 _pin_shaft(rInner, lStraight, snap+clearance/2, nubscale, stretch, nub_depth-preload, pointed);
862 }
863 _pin_slot(l = lStraight, r = rInner - thickness, t = thickness, d = nub_depth - preload, nub = snap, depth = 2 * radius + 0.02, stretch = stretch);
864 }
865 children();
866 }
867}
868
869// Module: snap_pin_socket()
870// Usage:
871// snap_pin_socket(size, [fixed=], [fins=], [pointed=], [anchor=], [spin=], [orient=]) [ATTACHMENTS];
872// snap_pin_socket(r=|radius=|d=|diameter=, l=|length=, nub_depth=, snap=, [fixed=], [pointed=], [fins=]) [ATTACHMENTS];
873// Description:
874// Constructs a socket suitable for a snap_pin with the same parameters. If `fixed` is true then the socket has flat walls and the
875// pin will not rotate in the socket. If `fixed` is false then the socket is round and the pin will rotate, particularly well
876// if you add a lubricant. If `pointed` is true the socket is pointed to receive a pointed pin, otherwise it has a rounded and and
877// will be shorter. If `fins` is set to true then two fins are included inside the socket to act as supports (which may help when printing tip up,
878// especially when `pointed=false`). The default orientation is DOWN with anchor BOTTOM so that you can difference() the socket away from an object.
879// The socket extends 0.02 extra below its bottom anchor point so that differences will work correctly. (You must have $overlap smaller than 0.02 in
880// attach or the socket will be beneath the surface of the parent object.)
881// .
882// The "large" or "standard" size pin has a length of 10.8 and diameter of 7. The "medium" pin has a length of 8 and diameter of 4.6. The "small" pin
883// has a length of 6 and diameter of 3.2. The "tiny" pin has a length of 4 and a diameter of 2.5.
884// Arguments:
885// size = text string to select from a list of predefined sizes, one of "standard", "small", or "tiny".
886// ---
887// pointed = set to true to get a pointed pin, false to get one with a rounded end. Default: true
888// r/radius = radius of the pin
889// d/diameter = diameter of the pin
890// l/length = length of the pin
891// nub_depth = the distance of the nub from the base of the pin
892// snap = how much snap the pin provides (the nub projection)
893// fixed = if true the pin cannot rotate, if false it can. Default: true
894// pointed = if true the socket has a pointed tip. Default: true
895// fins = if true supporting fins are included. Default: false
896// Example: The socket shape itself in native orientation.
897// snap_pin_socket("standard", anchor=CENTER, orient=UP, fins=true, $fn=40);
898// Example: A spinning socket with fins:
899// snap_pin_socket("standard", anchor=CENTER, orient=UP, fins=true, fixed=false, $fn=40);
900// Example: A cube with a socket in the middle and one half-way off the front edge so you can see inside:
901// $fn=40;
902// diff("socket") cuboid([20,20,20])
903// tag("socket"){
904// attach(TOP) snap_pin_socket("standard");
905// position(TOP+FRONT)snap_pin_socket("standard");
906// }
907function snap_pin_socket(size, r, radius, l,length, d,diameter,nub_depth, snap, fixed=true, pointed=true, fins=false, anchor=BOTTOM, spin=0, orient=DOWN) = no_function("snap_pin_socket");
908module snap_pin_socket(size, r, radius, l,length, d,diameter,nub_depth, snap, fixed=true, pointed=true, fins=false, anchor=BOTTOM, spin=0, orient=DOWN) {
909 sizedat = _pin_size(size);
910 radius = get_radius(r1=r,r2=radius,d1=d,d2=diameter,dflt=struct_val(sizedat,"diameter")/2);
911 length = first_defined([l,length,struct_val(sizedat,"length")]);
912 snap = first_defined([snap, struct_val(sizedat,"snap")]);
913 nub_depth = first_defined([nub_depth, struct_val(sizedat,"nub_depth")]);
914
915 tip = pointed ? sqrt(2) * radius : radius;
916 lPin = length + (pointed?(2-sqrt(2))*radius:0);
917 lStraight = lPin - (pointed?sqrt(2)*radius:radius);
918 attachable(anchor=anchor,spin=spin,orient=orient,
919 size=[2*(radius+snap),radius*sqrt(2),lPin])
920 {
921 down(lPin/2)
922 intersection() {
923 cube([3 * (radius + snap), fixed ? radius * sqrt(2) : 3*(radius+snap), 3 * lPin + 3 * radius], center = true);
924 union() {
925 _pin_shaft(radius,lStraight,snap,1,1,nub_depth,pointed);
926 if (fins)
927 up(lStraight){
928 cube([2 * radius, 0.01, 2 * tip], center = true);
929 cube([0.01, 2 * radius, 2 * tip], center = true);
930 }
931 }
932 }
933 children();
934 }
935}
936
937
938
939// Module: rabbit_clip()
940// Usage:
941// rabbit_clip(type, length, width, snap, thickness, depth, [compression=], [clearance=], [lock=], [lock_clearance=], [splineteps=], [anchor=], [orient=], [spin=]) [ATTACHMENTS];
942// Description:
943// Creates a clip with two flexible ears to lock into a mating socket, or create a mask to produce the appropriate
944// mating socket. The clip can be made to insert and release easily, or to hold much better, or it can be
945// created with locking flanges that will make it very hard or impossible to remove. Unlike the snap pin, this clip
946// is rectangular and can be made at any height, so a suitable clip could be very thin. It's also possible to get a
947// solid connection with a short pin.
948// .
949// The type parameters specifies whether to make a clip, a socket mask, or a double clip. The length is the
950// total nominal length of the clip. (The actual length will be very close, but not equal to this.) The width
951// gives the nominal width of the clip, which is the actual width of the clip at its base. The snap parameter
952// gives the depth of the clip sides, which controls how easy the clip is to insert and remove. The clip "ears" are
953// made over-wide by the compression value. A nonzero compression helps make the clip secure in its socket.
954// The socket's width and length are increased by the clearance value which creates some space and can compensate
955// for printing inaccuracy. The socket will be slightly longer than the nominal width. The thickness is the thickness
956// curved line that forms the clip. The clip depth is the amount the basic clip shape is extruded. Be sure that you
957// make the socket with a larger depth than the clip (try 0.4 mm) to allow ease of insertion of the clip. The clearance
958// value does not apply to the depth. The splinesteps parameter increases the sampling of the clip curves.
959// .
960// By default clips appear with orient=UP and sockets with orient=DOWN. The clips and sockets extend 0.02 units below
961// their base so that unions and differences will work without trouble, but be sure that the attach overlap is smaller
962// than 0.02.
963// .
964// The first figure shows the dimensions of the rabbit clip. The second figure shows the clip in red overlayed on
965// its socket in yellow. The left clip has a nonzero clearance, so its socket is bigger than the clip all around.
966// The right hand locking clip has no clearance, but it has a lock clearance, which provides some space behind
967// the lock to allow the clip to fit. (Note that depending on your printer, this can be set to zero.)
968// Figure(2DMed):
969// snap=1.5;
970// comp=0.75;
971// mid = 8.053; // computed in rabbit_clip
972// tip = [-4.58,18.03];
973// translate([9,3]){
974// back_half()
975// rabbit_clip("pin",width=12, length=18, depth=1, thickness = 1, compression=comp, snap=snap, orient=BACK);
976// color("blue"){
977// stroke([[6,0],[6,18]],width=0.1);
978// stroke([[6+comp, 12], [6+comp, 18]], width=.1);
979// }
980// color("red"){
981// stroke([[6-snap,mid], [6,mid]], endcaps="arrow2",width=0.15);
982// translate([6+.4,mid-.15])text("snap",size=1,valign="center");
983// translate([6+comp/2,19.5])text("compression", size=1, halign="center");
984// stroke([[6+comp/2,19.3], [6+comp/2,17.7]], endcap2="arrow2", width=.15);
985// fwd(1.1)text("width",size=1,halign="center");
986// xflip_copy()stroke([[2,-.7], [6,-.7]], endcap2="arrow2", width=.15);
987// move([-6.7,mid])rot(90)text("length", size=1, halign="center");
988// stroke([[-7,10.3], [-7,18]], width=.15, endcap2="arrow2");
989// stroke([[-7,0], [-7,5.8]], width=.15,endcap1="arrow2");
990// stroke([tip, tip-[0,1]], width=.15);
991// move([tip.x+2,19.5])text("thickness", halign="center",size=1);
992// stroke([[tip.x+2, 19.3], tip+[.1,.1]], width=.15, endcap2="arrow2");
993// }
994// }
995//
996// Figure(2DMed):
997// snap=1.5;
998// comp=0;
999// translate([29,3]){
1000// back_half()
1001// rabbit_clip("socket", width=12, length=18, depth=1, thickness = 1, compression=comp, snap=snap, orient=BACK,lock=true);
1002// color("red")back_half()
1003// rabbit_clip("pin",width=12, length=18, depth=1, thickness = 1, compression=comp, snap=snap,
1004// orient=BACK,lock=true,lock_clearance=1);
1005// }
1006// translate([9,3]){
1007// back_half()
1008// rabbit_clip("socket", clearance=.5,width=12, length=18, depth=1, thickness = 1,
1009// compression=comp, snap=snap, orient=BACK,lock=false);
1010// color("red")back_half()
1011// rabbit_clip("pin",width=12, length=18, depth=1, thickness = 1, compression=comp, snap=snap,
1012// orient=BACK,lock=false,lock_clearance=1);
1013// }
1014// Arguments:
1015// type = One of "pin", "socket", "male", "female" or "double" to specify what to make.
1016// length = nominal clip length
1017// width = nominal clip width
1018// snap = depth of hollow on the side of the clip
1019// thickness = thickness of the clip "line"
1020// depth = amount to extrude clip (give extra room for the socket, about 0.4mm)
1021// ---
1022// compression = excess width at the "ears" to lock more tightly. Default: 0.1
1023// clearance = extra space in the socket for easier insertion. Default: 0.1
1024// lock = set to true to make a locking clip that may be irreversible. Default: false
1025// lock_clearance = give clearance for the lock. Default: 0
1026// splinesteps = number of samples in the curves of the clip. Default: 8
1027// anchor = anchor point for clip
1028// orient = clip orientation. Default: UP for pins, DOWN for sockets
1029// spin = spin the clip. Default: 0
1030//
1031// Example: Here are several sizes that work printed in PLA on a Prusa MK3, with default clearance of 0.1 and a depth of 5
1032// module test_pair(length, width, snap, thickness, compression, lock=false)
1033// {
1034// depth = 5;
1035// extra_depth = 10;// Change this to 0.4 for closed sockets
1036// cuboid([max(width+5,12),12, depth], chamfer=.5, edges=[FRONT,"Y"], anchor=BOTTOM)
1037// attach(BACK)
1038// rabbit_clip(type="pin",length=length, width=width,snap=snap,thickness=thickness,depth=depth,
1039// compression=compression,lock=lock);
1040// right(width+13)
1041// diff("remove")
1042// cuboid([width+8,max(12,length+2),depth+3], chamfer=.5, edges=[FRONT,"Y"], anchor=BOTTOM)
1043// tag("remove")
1044// attach(BACK)
1045// rabbit_clip(type="socket",length=length, width=width,snap=snap,thickness=thickness,
1046// depth=depth+extra_depth, lock=lock,compression=0);
1047// }
1048// left(37)ydistribute(spacing=28){
1049// test_pair(length=6, width=7, snap=0.25, thickness=0.8, compression=0.1);
1050// test_pair(length=3.5, width=7, snap=0.1, thickness=0.8, compression=0.1); // snap = 0.2 gives a firmer connection
1051// test_pair(length=3.5, width=5, snap=0.1, thickness=0.8, compression=0.1); // hard to take apart
1052// }
1053// right(17)ydistribute(spacing=28){
1054// test_pair(length=12, width=10, snap=1, thickness=1.2, compression=0.2);
1055// test_pair(length=8, width=7, snap=0.75, thickness=0.8, compression=0.2, lock=true); // With lock, very firm and irreversible
1056// test_pair(length=8, width=7, snap=0.75, thickness=0.8, compression=0.2, lock=true); // With lock, very firm and irreversible
1057// }
1058// Example: Double clip to connect two sockets
1059// rabbit_clip("double",length=8, width=7, snap=0.75, thickness=0.8, compression=0.2,depth=5);
1060// Example: A modified version of the clip that acts like a backpack strap clip, where it locks tightly but you can squeeze to release.
1061// cuboid([25,15,5],anchor=BOTTOM)
1062// attach(BACK)rabbit_clip("pin", length=25, width=25, thickness=1.5, snap=2, compression=0, lock=true, depth=5, lock_clearance=3);
1063// left(32)
1064// diff("remove")
1065// cuboid([30,30,11],orient=BACK,anchor=BACK){
1066// tag("remove")attach(BACK)rabbit_clip("socket", length=25, width=25, thickness=1.5, snap=2, compression=0, lock=true, depth=5.5, lock_clearance=3);
1067// xflip_copy()
1068// position(FRONT+LEFT)
1069// xscale(0.8)
1070// tag("remove")zcyl(l=20,r=13.5, $fn=64);
1071// }
1072
1073function rabbit_clip(type, length, width, snap, thickness, depth, compression=0.1, clearance=.1, lock=false, lock_clearance=0,
1074 splinesteps=8, anchor, orient, spin=0) = no_function("rabbit_clip");
1075
1076module rabbit_clip(type, length, width, snap, thickness, depth, compression=0.1, clearance=.1, lock=false, lock_clearance=0,
1077 splinesteps=8, anchor, orient, spin=0)
1078{
1079 legal_types = ["pin","socket","male","female","double"];
1080 check =
1081 assert(is_num(width) && width>0,"Width must be a positive value")
1082 assert(is_num(length) && length>0, "Length must be a positive value")
1083 assert(is_num(thickness) && thickness>0, "Thickness must be a positive value")
1084 assert(is_num(snap) && snap>=0, "Snap must be a non-negative value")
1085 assert(is_num(depth) && depth>0, "Depth must be a positive value")
1086 assert(is_num(compression) && compression >= 0, "Compression must be a nonnegative value")
1087 assert(is_bool(lock))
1088 assert(is_num(lock_clearance))
1089 assert(in_list(type,legal_types),str("type must be one of ",legal_types));
1090 if (type=="double") {
1091 attachable(size=[width+2*compression, depth, 2*length], anchor=default(anchor,BACK), spin=spin, orient=default(orient,BACK)){
1092 union(){
1093 rabbit_clip("pin", length=length, width=width, snap=snap, thickness=thickness, depth=depth, compression=compression,
1094 lock=lock, anchor=BOTTOM, orient=UP);
1095 rabbit_clip("pin", length=length, width=width, snap=snap, thickness=thickness, depth=depth, compression=compression,
1096 lock=lock, anchor=BOTTOM, orient=DOWN);
1097 cuboid([width-thickness, depth, thickness]);
1098 }
1099 children();
1100 }
1101 } else {
1102 anchor = default(anchor,BOTTOM);
1103 is_pin = in_list(type,["pin","male"]);
1104 //default_overlap = 0.01 * (is_pin?1:-1); // Shift by this much to undo default overlap
1105 default_overlap = 0;
1106 extra = 0.02; // Amount of extension below nominal based position for the socket, must exceed default overlap of 0.01
1107 clearance = is_pin ? 0 : clearance;
1108 compression = is_pin ? compression : 0;
1109 orient = is_def(orient) ? orient
1110 : is_pin ? UP
1111 : DOWN;
1112 earwidth = 2*thickness+snap;
1113 point_length = earwidth/2.15;
1114 // The adjustment is using cos(theta)*earwidth/2 and sin(theta)*point_length, but the computation
1115 // is obscured because theta is atan(length/2/snap)
1116 scaled_len = length - 0.5 * (earwidth * snap + point_length * length) / sqrt(sqr(snap)+sqr(length/2));
1117 bottom_pt = [0,max(scaled_len*0.15+thickness, 2*thickness)];
1118 ctr = [width/2,scaled_len] + line_normal([width/2-snap, scaled_len/2], [width/2, scaled_len]) * earwidth/2;
1119 inside_pt = circle_circle_tangents(0, bottom_pt, earwidth/2, ctr)[0][1];
1120 sidepath =[
1121 [width/2,0],
1122 [width/2-snap,scaled_len/2],
1123 [width/2+(is_pin?compression:0), scaled_len],
1124 ctr - point_length * line_normal([width/2,scaled_len], inside_pt),
1125 inside_pt
1126 ];
1127 fullpath = concat(
1128 sidepath,
1129 [bottom_pt],
1130 reverse(apply(xflip(),sidepath))
1131 );
1132 dummy2 = assert(fullpath[4].y < fullpath[3].y, "Pin is too wide for its length");
1133
1134 snapmargin = -snap + last(sidepath).x;// - compression;
1135 if (is_pin){
1136 if (snapmargin<0) echo("WARNING: The snap is too large for the clip to squeeze to fit its socket")
1137 echo(snapmargin=snapmargin);
1138 }
1139 // Force tangent to be vertical at the outer edge of the clip to avoid overshoot
1140 fulltangent = list_set(path_tangents(fullpath, uniform=false),[2,8], [[0,1],[0,-1]]);
1141
1142 subset = is_pin ? [0:10] : [0,1,2,3, 7,8,9,10]; // Remove internal points from the socket
1143 tangent = select(fulltangent, subset);
1144 path = select(fullpath, subset);
1145
1146 socket_smooth = .04;
1147 pin_smooth = [.075, .075, .15, .12, .06];
1148 smoothing = is_pin
1149 ? concat(pin_smooth, reverse(pin_smooth))
1150 : let(side_smooth=select(pin_smooth, 0, 2))
1151 concat(side_smooth, [socket_smooth], reverse(side_smooth));
1152 bez = path_to_bezpath(path,relsize=smoothing,tangents=tangent);
1153 rounded = bezpath_curve(bez,splinesteps=splinesteps);
1154 bounds = pointlist_bounds(rounded);
1155 extrapt = is_pin ? [] : [rounded[0] - [0,extra]];
1156 finalpath = is_pin ? rounded
1157 : let(withclearance=offset(rounded, r=-clearance))
1158 concat( [[withclearance[0].x,-extra]],
1159 withclearance,
1160 [[-withclearance[0].x,-extra]]);
1161 attachable(size=[bounds[1].x-bounds[0].x, depth, bounds[1].y-bounds[0].y], anchor=anchor, spin=spin, orient=orient){
1162 xrot(90)
1163 translate([0,-(bounds[1].y-bounds[0].y)/2+default_overlap,-depth/2])
1164 linear_extrude(height=depth, convexity=10) {
1165 if (lock)
1166 xflip_copy()
1167 right(clearance)
1168 polygon([sidepath[1]+[-thickness/10,lock_clearance],
1169 sidepath[2]-[thickness*.75,0],
1170 sidepath[2],
1171 [sidepath[2].x,sidepath[1].y+lock_clearance]]);
1172 if (is_pin)
1173 offset_stroke(finalpath, width=[thickness,0]);
1174 else
1175 polygon(finalpath);
1176 }
1177 children();
1178 }
1179 }
1180}
1181
1182
1183
1184// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap