1//////////////////////////////////////////////////////////////////////
2// LibFile: bottlecaps.scad
3// Bottle caps and necks for PCO18XX standard plastic beverage bottles, and SPI standard bottle necks.
4// Includes:
5// include <BOSL2/std.scad>
6// include <BOSL2/bottlecaps.scad>
7// FileGroup: Threaded Parts
8// FileSummary: Standard bottle caps and necks.
9//////////////////////////////////////////////////////////////////////
10
11
12include <threading.scad>
13include <structs.scad>
14include <rounding.scad>
15
16// Section: PCO-1810 Bottle Threading
17
18
19// Module: pco1810_neck()
20// Synopsis: Creates a neck for a PCO1810 standard bottle.
21// SynTags: Geom
22// Topics: Bottles, Threading
23// See Also: pco1810_cap()
24// Usage:
25// pco1810_neck([wall]) [ATTACHMENTS];
26// Description:
27// Creates an approximation of a standard PCO-1810 threaded beverage bottle neck.
28// Arguments:
29// wall = Wall thickness in mm.
30// ---
31// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
32// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
33// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
34// Extra Anchors:
35// "tamper-ring" = Centered at the top of the anti-tamper ring channel.
36// "support-ring" = Centered at the bottom of the support ring.
37// Example:
38// pco1810_neck();
39// Example: Standard Anchors
40// pco1810_neck() show_anchors(custom=false);
41// Example: Custom Named Anchors
42// expose_anchors(0.3)
43// pco1810_neck()
44// show_anchors(std=false);
45module pco1810_neck(wall=2, anchor="support-ring", spin=0, orient=UP)
46{
47 inner_d = 21.74;
48 neck_d = 26.19;
49 neck_h = 5.00;
50 support_d = 33.00;
51 support_width = 1.45;
52 support_rad = 0.40;
53 support_h = 21.00;
54 support_ang = 16;
55 tamper_ring_d = 27.97;
56 tamper_ring_width = 0.50;
57 tamper_ring_r = 1.60;
58 tamper_base_d = 25.71;
59 tamper_base_h = 14.10;
60 threadbase_d = 24.51;
61 thread_pitch = 3.18;
62 flank_angle = 20;
63 thread_od = 27.43;
64 lip_d = 25.07;
65 lip_h = 1.70;
66 lip_leadin_r = 0.20;
67 lip_recess_d = 24.94;
68 lip_recess_h = 1.00;
69 lip_roundover_r = 0.58;
70
71 $fn = segs(support_d/2);
72 h = support_h+neck_h;
73 thread_h = (thread_od-threadbase_d)/2;
74 anchors = [
75 named_anchor("support-ring", [0,0,neck_h-h/2]),
76 named_anchor("tamper-ring", [0,0,h/2-tamper_base_h])
77 ];
78 attachable(anchor,spin,orient, d1=neck_d, d2=lip_recess_d+2*lip_leadin_r, l=h, anchors=anchors) {
79 down(h/2) {
80 rotate_extrude(convexity=10) {
81 polygon(turtle(
82 state=[inner_d/2,0], [
83 "untilx", neck_d/2,
84 "left", 90,
85 "move", neck_h - 1,
86 "arcright", 1, 90,
87 "untilx", support_d/2-support_rad,
88 "arcleft", support_rad, 90,
89 "move", support_width,
90 "arcleft", support_rad, 90-support_ang,
91 "untilx", tamper_base_d/2,
92 "right", 90-support_ang,
93 "untily", h-tamper_base_h, // Tamper ring holder base.
94 "right", 90,
95 "untilx", tamper_ring_d/2,
96 "left", 90,
97 "move", tamper_ring_width,
98 "arcleft", tamper_ring_r, 90,
99 "untilx", threadbase_d/2,
100 "right", 90,
101 "untily", h-lip_h-lip_leadin_r, // Lip base.
102 "arcright", lip_leadin_r, 90,
103 "untilx", lip_d/2,
104 "left", 90,
105 "untily", h-lip_recess_h,
106 "left", 90,
107 "untilx", lip_recess_d/2,
108 "right", 90,
109 "untily", h-lip_roundover_r,
110 "arcleft", lip_roundover_r, 90,
111 "untilx", inner_d/2
112 ]
113 ));
114 }
115 up(h-lip_h) {
116 bottom_half() {
117 difference() {
118 thread_helix(
119 d=threadbase_d-0.1,
120 pitch=thread_pitch,
121 thread_depth=thread_h+0.1,
122 flank_angle=flank_angle,
123 turns=810/360,
124 lead_in=-thread_h*2,
125 anchor=TOP
126 );
127 zrot_copies(rots=[90,270]) {
128 zrot_copies(rots=[-28,28], r=threadbase_d/2) {
129 prismoid([20,1.82], [20,1.82+2*sin(29)*thread_h], h=thread_h+0.1, anchor=BOT, orient=RIGHT);
130 }
131 }
132 }
133 }
134 }
135 }
136 children();
137 }
138}
139
140function pco1810_neck(wall=2, anchor="support-ring", spin=0, orient=UP) =
141 no_function("pco1810_neck");
142
143
144// Module: pco1810_cap()
145// Synopsis: Creates a cap for a PCO1810 standard bottle.
146// SynTags: Geom
147// Topics: Bottles, Threading
148// See Also: pco1810_neck()
149// Usage:
150// pco1810_cap([h], [r|d], [wall], [texture]) [ATTACHMENTS];
151// Description:
152// Creates a basic cap for a PCO1810 threaded beverage bottle.
153// Arguments:
154// h = The height of the cap.
155// r = Outer radius of the cap.
156// d = Outer diameter of the cap.
157// wall = Wall thickness in mm.
158// texture = The surface texture of the cap. Valid values are "none", "knurled", or "ribbed". Default: "none"
159// ---
160// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
161// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
162// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
163// Extra Anchors:
164// "inside-top" = Centered on the inside top of the cap.
165// Examples:
166// pco1810_cap();
167// pco1810_cap(texture="knurled");
168// pco1810_cap(texture="ribbed");
169// Example: Standard Anchors
170// pco1810_cap(texture="ribbed") show_anchors(custom=false);
171// Example: Custom Named Anchors
172// expose_anchors(0.3)
173// pco1810_cap(texture="ribbed")
174// show_anchors(std=false);
175module pco1810_cap(h, r, d, wall, texture="none", anchor=BOTTOM, spin=0, orient=UP)
176{
177 cap_id = 28.58;
178 tamper_ring_h = 14.10;
179 thread_pitch = 3.18;
180 flank_angle = 20;
181 thread_od = cap_id;
182 thread_depth = 1.6;
183
184 rr = get_radius(r=r, d=d, dflt=undef);
185 wwall = default(u_sub(rr,cap_id/2), default(wall, 2));
186 hh = default(h, tamper_ring_h + wwall);
187 checks =
188 assert(wwall >= 0, "wall can't be negative.")
189 assert(hh >= tamper_ring_h, str("height can't be less than ", tamper_ring_h, "."));
190
191 $fn = segs(33/2);
192 w = cap_id + 2*wwall;
193 anchors = [
194 named_anchor("inside-top", [0,0,-(hh/2-wwall)])
195 ];
196 attachable(anchor,spin,orient, d=w, l=hh, anchors=anchors) {
197 down(hh/2) zrot(45) {
198 difference() {
199 union() {
200 if (texture == "knurled") {
201 cyl(d=w, h=hh, texture="diamonds", tex_size=[3,3], tex_style="concave", anchor=BOT);
202 } else if (texture == "ribbed") {
203 cyl(d=w, h=hh, texture="ribs", tex_size=[3,3], tex_style="min_edge", anchor=BOT);
204 } else {
205 cyl(d=w, l=hh, anchor=BOTTOM);
206 }
207 }
208 up(hh-tamper_ring_h) cyl(d=cap_id, h=tamper_ring_h+wwall, anchor=BOTTOM);
209 }
210 up(hh-tamper_ring_h+2) thread_helix(d=thread_od-thread_depth*2, pitch=thread_pitch, thread_depth=thread_depth, flank_angle=flank_angle, turns=810/360, lead_in=-thread_depth, internal=true, anchor=BOTTOM);
211 }
212 children();
213 }
214}
215
216function pco1810_cap(h, r, d, wall, texture="none", anchor=BOTTOM, spin=0, orient=UP) =
217 no_function("pco1810_cap");
218
219
220
221// Section: PCO-1881 Bottle Threading
222
223
224// Module: pco1881_neck()
225// Synopsis: Creates a neck for a PCO1881 standard bottle.
226// SynTags: Geom
227// Topics: Bottles, Threading
228// See Also: pco1881_cap()
229// Usage:
230// pco1881_neck([wall]) [ATTACHMENTS];
231// Description:
232// Creates an approximation of a standard PCO-1881 threaded beverage bottle neck.
233// Arguments:
234// wall = Wall thickness in mm.
235// ---
236// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
237// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
238// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
239// Extra Anchors:
240// "tamper-ring" = Centered at the top of the anti-tamper ring channel.
241// "support-ring" = Centered at the bottom of the support ring.
242// Example:
243// pco1881_neck();
244// Example:
245// pco1881_neck() show_anchors(custom=false);
246// Example:
247// expose_anchors(0.3)
248// pco1881_neck()
249// show_anchors(std=false);
250module pco1881_neck(wall=2, anchor="support-ring", spin=0, orient=UP)
251{
252 inner_d = 21.74;
253 neck_d = 26.19;
254 neck_h = 5.00;
255 support_d = 33.00;
256 support_width = 0.58;
257 support_rad = 0.30;
258 support_h = 17.00;
259 support_ang = 15;
260 tamper_ring_d = 28.00;
261 tamper_ring_width = 0.30;
262 tamper_ring_ang = 45;
263 tamper_base_d = 25.71;
264 tamper_base_h = 11.20;
265 tamper_divot_r = 1.08;
266 threadbase_d = 24.20;
267 thread_pitch = 2.70;
268 flank_angle = 15;
269 thread_od = 27.4;
270 lip_d = 25.07;
271 lip_h = 1.70;
272 lip_leadin_r = 0.30;
273 lip_recess_d = 24.94;
274 lip_recess_h = 1.00;
275 lip_roundover_r = 0.58;
276
277 $fn = segs(support_d/2);
278 h = support_h+neck_h;
279 thread_h = (thread_od-threadbase_d)/2;
280 anchors = [
281 named_anchor("support-ring", [0,0,neck_h-h/2]),
282 named_anchor("tamper-ring", [0,0,h/2-tamper_base_h])
283 ];
284 attachable(anchor,spin,orient, d1=neck_d, d2=lip_recess_d+2*lip_leadin_r, l=h, anchors=anchors) {
285 down(h/2) {
286 rotate_extrude(convexity=10) {
287 polygon(turtle(
288 state=[inner_d/2,0], [
289 "untilx", neck_d/2,
290 "left", 90,
291 "move", neck_h - 1,
292 "arcright", 1, 90,
293 "untilx", support_d/2-support_rad,
294 "arcleft", support_rad, 90,
295 "move", support_width,
296 "arcleft", support_rad, 90-support_ang,
297 "untilx", tamper_base_d/2,
298 "arcright", tamper_divot_r, 180-support_ang*2,
299 "left", 90-support_ang,
300 "untily", h-tamper_base_h, // Tamper ring holder base.
301 "right", 90,
302 "untilx", tamper_ring_d/2,
303 "left", 90,
304 "move", tamper_ring_width,
305 "left", tamper_ring_ang,
306 "untilx", threadbase_d/2,
307 "right", tamper_ring_ang,
308 "untily", h-lip_h-lip_leadin_r, // Lip base.
309 "arcright", lip_leadin_r, 90,
310 "untilx", lip_d/2,
311 "left", 90,
312 "untily", h-lip_recess_h,
313 "left", 90,
314 "untilx", lip_recess_d/2,
315 "right", 90,
316 "untily", h-lip_roundover_r,
317 "arcleft", lip_roundover_r, 90,
318 "untilx", inner_d/2
319 ]
320 ));
321 }
322 up(h-lip_h) {
323 difference() {
324 thread_helix(
325 d=threadbase_d-0.1,
326 pitch=thread_pitch,
327 thread_depth=thread_h+0.1,
328 flank_angle=flank_angle,
329 turns=650/360,
330 lead_in=-thread_h*2,
331 anchor=TOP
332 );
333 zrot_copies(rots=[90,270]) {
334 zrot_copies(rots=[-28,28], r=threadbase_d/2) {
335 prismoid([20,1.82], [20,1.82+2*sin(29)*thread_h], h=thread_h+0.1, anchor=BOT, orient=RIGHT);
336 }
337 }
338 }
339 }
340 }
341 children();
342 }
343}
344
345function pco1881_neck(wall=2, anchor="support-ring", spin=0, orient=UP) =
346 no_function("pco1881_neck");
347
348
349// Module: pco1881_cap()
350// Synopsis: Creates a cap for a PCO1881 standard bottle.
351// SynTags: Geom
352// Topics: Bottles, Threading
353// See Also: pco1881_neck()
354// Usage:
355// pco1881_cap(wall, [texture]) [ATTACHMENTS];
356// Description:
357// Creates a basic cap for a PCO1881 threaded beverage bottle.
358// Arguments:
359// wall = Wall thickness in mm.
360// texture = The surface texture of the cap. Valid values are "none", "knurled", or "ribbed". Default: "none"
361// ---
362// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
363// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
364// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
365// Extra Anchors:
366// "inside-top" = Centered on the inside top of the cap.
367// Examples:
368// pco1881_cap();
369// pco1881_cap(texture="knurled");
370// pco1881_cap(texture="ribbed");
371// Example: Standard Anchors
372// pco1881_cap(texture="ribbed") show_anchors(custom=false);
373// Example: Custom Named Anchors
374// expose_anchors(0.5)
375// pco1881_cap(texture="ribbed")
376// show_anchors(std=false);
377module pco1881_cap(wall=2, texture="none", anchor=BOTTOM, spin=0, orient=UP)
378{
379 $fn = segs(33/2);
380 w = 28.58 + 2*wall;
381 h = 11.2 + wall;
382 anchors = [
383 named_anchor("inside-top", [0,0,-(h/2-wall)])
384 ];
385 attachable(anchor,spin,orient, d=w, l=h, anchors=anchors) {
386 down(h/2) zrot(45) {
387 difference() {
388 union() {
389 if (texture == "knurled") {
390 cyl(d=w, h=11.2+wall, texture="diamonds", tex_size=[3,3], tex_style="concave", anchor=BOT);
391 } else if (texture == "ribbed") {
392 cyl(d=w, h=11.2+wall, texture="ribs", tex_size=[3,3], tex_style="min_edge", anchor=BOT);
393 } else {
394 cyl(d=w, l=11.2+wall, anchor=BOTTOM);
395 }
396 }
397 up(wall) cyl(d=28.58, h=11.2+wall, anchor=BOTTOM);
398 }
399 up(wall+2) thread_helix(d=25.5, pitch=2.7, thread_depth=1.6, flank_angle=15, turns=650/360, lead_in=-1.6, internal=true, anchor=BOTTOM);
400 }
401 children();
402 }
403}
404
405function pco1881_cap(wall=2, texture="none", anchor=BOTTOM, spin=0, orient=UP) =
406 no_function("pco1881_cap");
407
408
409
410// Section: Generic Bottle Connectors
411
412// Module: generic_bottle_neck()
413// Synopsis: Creates a generic neck for a bottle.
414// SynTags: Geom
415// Topics: Bottles, Threading
416// See Also: generic_bottle_cap()
417// Usage:
418// generic_bottle_neck([wall], ...) [ATTACHMENTS];
419// Description:
420// Creates a bottle neck given specifications.
421// Arguments:
422// wall = distance between ID and any wall that may be below the support
423// ---
424// neck_d = Outer diameter of neck without threads
425// id = Inner diameter of neck
426// thread_od = Outer diameter of thread
427// height = Height of neck above support
428// support_d = Outer diameter of support ring. Set to 0 for no support.
429// pitch = Thread pitch
430// round_supp = True to round the lower edge of the support ring
431// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
432// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
433// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
434// Extra Anchors:
435// "support-ring" = Centered at the bottom of the support ring.
436// Example:
437// generic_bottle_neck();
438module generic_bottle_neck(
439 wall,
440 neck_d = 25,
441 id = 21.4,
442 thread_od = 27.2,
443 height = 17,
444 support_d = 33.0,
445 pitch = 3.2,
446 round_supp = false,
447 anchor = "support-ring",
448 spin = 0,
449 orient = UP
450) {
451 inner_d = id;
452 neck_d = neck_d;
453 supp_d = max(neck_d, support_d);
454 thread_pitch = pitch;
455 flank_angle = 15;
456
457 diamMagMult = neck_d / 26.19;
458 heightMagMult = height / 17.00;
459
460 assert(all_nonnegative([support_d]),"support_d must be a nonnegative number");
461 sup_r = 0.30 * (heightMagMult > 1 ? heightMagMult : 1);
462 support_r = floor(((supp_d == neck_d) ? sup_r : min(sup_r, (supp_d - neck_d) / 2)) * 5000) / 10000;
463 support_rad = (wall == undef || !round_supp) ? support_r :
464 min(support_r, floor((supp_d - (inner_d + 2 * wall)) * 5000) / 10000);
465 //Too small of a radius will cause errors with the arc, this limits granularity to .0001mm
466 support_width = max(heightMagMult,1) * sign(support_d);
467 roundover = 0.58 * diamMagMult;
468 lip_roundover_r = (roundover > (neck_d - inner_d) / 2) ? 0 : roundover;
469 h = height + support_width;
470 echo(h=h);
471 threadbase_d = neck_d - 0.8 * diamMagMult;
472
473 $fn = segs(33 / 2);
474 thread_h = (thread_od - threadbase_d) / 2;
475 anchors = [
476 named_anchor("support-ring", [0, 0, 0 - h / 2])
477 ];
478 attachable(anchor, spin, orient, d = neck_d, l = h, anchors = anchors) {
479 down(h / 2) {
480 rotate_extrude(convexity = 10) {
481 polygon(turtle(
482 state = [inner_d / 2, 0], (supp_d != neck_d) ? [
483 "untilx", supp_d / 2 - ((round_supp) ? support_rad : 0),
484 "arcleft", ((round_supp) ? support_rad : 0), 90,
485 "untily", support_width - support_rad,
486 "arcleft", support_rad, 90,
487 "untilx", neck_d / 2,
488 "right", 90,
489 "untily", h - lip_roundover_r,
490 "arcleft", lip_roundover_r, 90,
491 "untilx", inner_d / 2
492 ] : [
493 "untilx", supp_d / 2 - ((round_supp) ? support_rad : 0),
494 "arcleft", ((round_supp) ? support_rad : 0), 90,
495 "untily", h - lip_roundover_r,
496 "arcleft", lip_roundover_r, 90,
497 "untilx", inner_d / 2
498 ]
499 ));
500 }
501 up(h - pitch / 2 - lip_roundover_r) {
502 difference() {
503 thread_helix(
504 d = threadbase_d - 0.1 * diamMagMult,
505 pitch = thread_pitch,
506 thread_depth = thread_h + 0.1 * diamMagMult,
507 flank_angle = flank_angle,
508 turns = (height - pitch - lip_roundover_r) * .6167 / pitch,
509 lead_in = -thread_h * 2,
510 anchor = TOP
511 );
512 zrot_copies(rots = [90, 270]) {
513 zrot_copies(rots = [-28, 28], r = threadbase_d / 2) {
514 prismoid(
515 [20 * heightMagMult, 1.82 * diamMagMult],
516 [20 * heightMagMult, 1.82 * diamMagMult * .6 + 2 * sin(29) * thread_h],
517 h = thread_h + 0.1 * diamMagMult,
518 anchor = BOT,
519 orient = RIGHT
520 );
521 }
522 }
523 }
524 }
525 }
526 children();
527 }
528}
529
530function generic_bottle_neck(
531 neck_d,
532 id,
533 thread_od,
534 height,
535 support_d,
536 pitch,
537 round_supp,
538 wall,
539 anchor, spin, orient
540) = no_function("generic_bottle_neck");
541
542
543// Module: generic_bottle_cap()
544// Synopsis: Creates a generic cap for a bottle.
545// SynTags: Geom
546// Topics: Bottles, Threading
547// See Also: generic_bottle_neck()
548// Usage:
549// generic_bottle_cap(wall, [texture], ...) [ATTACHMENTS];
550// Description:
551// Creates a basic threaded cap given specifications.
552// Arguments:
553// wall = Wall thickness in mm.
554// texture = The surface texture of the cap. Valid values are "none", "knurled", or "ribbed". Default: "none"
555// ---
556// height = Interior height of the cap in mm.
557// thread_od = Outer diameter of the threads in mm.
558// tolerance = Extra space to add to the outer diameter of threads and neck in mm. Applied to radius.
559// neck_od = Outer diameter of neck in mm.
560// flank_angle = Angle of taper on threads.
561// pitch = Thread pitch in mm.
562// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
563// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
564// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
565// Extra Anchors:
566// "inside-top" = Centered on the inside top of the cap.
567// Examples:
568// generic_bottle_cap();
569// generic_bottle_cap(texture="knurled");
570// generic_bottle_cap(texture="ribbed");
571module generic_bottle_cap(
572 wall = 2,
573 texture = "none",
574 height = 11.2,
575 thread_depth = 2.34,
576 tolerance = .2,
577 neck_od = 25.5,
578 flank_angle = 15,
579 pitch = 4,
580 anchor = BOTTOM,
581 spin = 0,
582 orient = UP
583) {
584 $fn = segs(33 / 2);
585 threadOuterDTol = neck_od + 2*(thread_depth - 0.8) + 2 * tolerance; // WTF; Engineered for consistency with old code, but
586 w = threadOuterDTol + 2 * wall; // no clue why this was chosen
587 h = height + wall;
588 neckOuterDTol = neck_od + 2 * tolerance;
589
590 diamMagMult = (w > 32.58) ? w / 32.58 : 1;
591 heightMagMult = (height > 11.2) ? height / 11.2 : 1;
592
593 anchors = [
594 named_anchor("inside-top", [0, 0, -(h / 2 - wall)])
595 ];
596 attachable(anchor, spin, orient, d = w, l = h, anchors = anchors) {
597 down(h / 2) {
598 difference() {
599 union() {
600 // For the knurled and ribbed caps the PCO caps in BOSL2 cut into the wall
601 // thickness so the wall+texture are the specified wall thickness. That
602 // seems wrong so this does specified thickness+texture
603 if (texture == "knurled") {
604 cyl(d=w + 1.5*diamMagMult, l=h, texture="diamonds", tex_size=[3,3], tex_style="concave", anchor=BOT);
605 } else if (texture == "ribbed") {
606 cyl(d=w + 1.5*diamMagMult, l=h, texture="ribs", tex_size=[3,3], tex_style="min_edge", anchor=BOT);
607 } else {
608 cyl(d = w, l = h, anchor = BOTTOM);
609 }
610 }
611 up(wall) cyl(d = threadOuterDTol, h = h, anchor = BOTTOM);
612 }
613 difference(){
614 up(wall + pitch / 2) {
615 thread_helix(d = neckOuterDTol, pitch = pitch, thread_depth = thread_depth, flank_angle = flank_angle,
616 turns = ((height - pitch) / pitch), lead_in = -thread_depth, internal = true, anchor = BOTTOM);
617 }
618 }
619 }
620 children();
621 }
622}
623
624function generic_bottle_cap(
625 wall, texture, height,
626 thread_od, tolerance,
627 neck_od, flank_angle, pitch,
628 anchor, spin, orient
629) = no_function("generic_bottle_cap");
630
631
632// Module: bottle_adapter_neck_to_cap()
633// Synopsis: Creates a generic adaptor between a neck and a cap.
634// SynTags: Geom
635// Topics: Bottles, Threading
636// See Also: bottle_adapter_neck_to_neck()
637// Usage:
638// bottle_adapter_neck_to_cap(wall, [texture], ...) [ATTACHMENTS];
639// Description:
640// Creates a threaded neck to cap adapter
641// Arguments:
642// wall = Thickness of wall between neck and cap when d=0. Leave undefined to have the outside of the tube go from the OD of the neck support ring to the OD of the cap. Default: undef
643// texture = The surface texture of the cap. Valid values are "none", "knurled", or "ribbed". Default: "none"
644// cap_wall = Wall thickness of the cap in mm.
645// cap_h = Interior height of the cap in mm.
646// cap_thread_depth = Cap thread depth. Default: 2.34
647// tolerance = Extra space to add to the outer diameter of threads and neck in mm. Applied to radius.
648// cap_neck_od = Inner diameter of the cap threads.
649// cap_neck_id = Inner diameter of the hole through the cap.
650// cap_thread_taper = Angle of taper on threads.
651// cap_thread_pitch = Thread pitch in mm
652// neck_d = Outer diameter of neck w/o threads
653// neck_id = Inner diameter of neck
654// neck_thread_od = 27.2
655// neck_h = Height of neck down to support ring
656// neck_thread_pitch = Thread pitch in mm.
657// neck_support_od = Outer diameter of neck support ring. Leave undefined to set equal to OD of cap. Set to 0 for no ring. Default: undef
658// d = Distance between bottom of neck and top of cap
659// taper_lead_in = Length to leave straight before tapering on tube between neck and cap if exists.
660// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
661// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
662// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
663// Examples:
664// bottle_adapter_neck_to_cap();
665module bottle_adapter_neck_to_cap(
666 wall,
667 texture = "none",
668 cap_wall = 2,
669 cap_h = 11.2,
670 cap_thread_depth = 2.34,
671 tolerance = .2,
672 cap_neck_od = 25.5,
673 cap_neck_id,
674 cap_thread_taper = 15,
675 cap_thread_pitch = 4,
676 neck_d = 25,
677 neck_id = 21.4,
678 neck_thread_od = 27.2,
679 neck_h = 17,
680 neck_thread_pitch = 3.2,
681 neck_support_od,
682 d = 0,
683 taper_lead_in = 0, anchor, spin,orient
684) {
685 cap_od = cap_neck_od + 2*(cap_thread_depth - 0.8) + 2 * tolerance;
686 neck_support_od = (neck_support_od == undef || (d == 0 && neck_support_od < cap_od)) ? cap_od+2*cap_wall
687 : neck_support_od;
688 cap_neck_id = default(cap_neck_id,neck_id);
689 wall = default(wall, neck_support_od + neck_d + cap_od + neck_id - 2*tolerance);
690 echo(wall=wall);
691
692 $fn = segs(33 / 2);
693 wallt1 = min(wall, (max(neck_support_od, neck_d) - neck_id) / 2);
694 wallt2 = min(wall, (cap_od + 2 * cap_wall - cap_neck_id) / 2);
695
696 top_h = neck_h + max(1,neck_h/17)*sign(neck_support_od);
697 echo(top_h=top_h);
698 bot_h = cap_h + cap_wall;
699 attachable(anchor=anchor,orient=orient,spin=spin, r=max([neck_id/2+wallt1, cap_neck_id/2+wallt2, neck_support_od/2]), h=top_h+bot_h+d) {
700 zmove((bot_h-top_h)/2)
701 difference(){
702 union(){
703 up(d / 2) {
704 generic_bottle_neck(neck_d = neck_d,
705 id = neck_id,
706 thread_od = neck_thread_od,
707 height = neck_h,
708 support_d = neck_support_od,
709 pitch = neck_thread_pitch,
710 round_supp = ((wallt1 < (neck_support_od - neck_id) / 2) && (d > 0 || neck_support_od > (cap_thread_od + 2 * (cap_wall + tolerance)))),
711 wall = (d > 0) ? wallt1 : min(wallt1, ((cap_od + 2 * (cap_wall) - neck_id) / 2))
712 );
713 }
714 if (d != 0) {
715 rotate_extrude(){
716 polygon(points = [
717 [0, d / 2],
718 [neck_id / 2 + wallt1, d / 2],
719 [neck_id / 2 + wallt1, d / 2 - taper_lead_in],
720 [cap_neck_id / 2 + wallt2, taper_lead_in - d / 2],
721 [cap_neck_id / 2 + wallt2, -d / 2],
722 [0, -d / 2]
723 ]);
724 }
725 }
726 down(d / 2){
727 generic_bottle_cap(wall = cap_wall,
728 texture = texture,
729 height = cap_h,
730 thread_depth = cap_thread_depth,
731 tolerance = tolerance,
732 neck_od = cap_neck_od,
733 flank_angle = cap_thread_taper,
734 orient = DOWN,
735 pitch = cap_thread_pitch
736 );
737 }
738 }
739 rotate_extrude() {
740 polygon(points = [
741 [0, d / 2 + 0.1],
742 [neck_id / 2, d / 2],
743 [neck_id / 2, d / 2 - taper_lead_in],
744 [cap_neck_id / 2, taper_lead_in - d / 2],
745 [cap_neck_id / 2, -d / 2 - cap_wall],
746 [0, -d / 2 - cap_wall - 0.1]
747 ]);
748 }
749 }
750 children();
751 }
752}
753
754function bottle_adapter_neck_to_cap(
755 wall, texture, cap_wall, cap_h, cap_thread_depth1,
756 tolerance, cap_neck_od, cap_neck_id, cap_thread_taper,
757 cap_thread_pitch, neck_d, neck_id, neck_thread_od,
758 neck_h, neck_thread_pitch, neck_support_od, d, taper_lead_in
759) = no_fuction("bottle_adapter_neck_to_cap");
760
761
762// Module: bottle_adapter_cap_to_cap()
763// Synopsis: Creates a generic adaptor between a cap and a cap.
764// SynTags: Geom
765// Topics: Bottles, Threading
766// See Also: bottle_adapter_neck_to_cap(), bottle_adapter_neck_to_neck()
767// Usage:
768// bottle_adapter_cap_to_cap(wall, [texture]) [ATTACHMENTS];
769// Description:
770// Creates a threaded cap to cap adapter.
771// Arguments:
772// wall = Wall thickness in mm.
773// texture = The surface texture of the cap. Valid values are "none", "knurled", or "ribbed". Default: "none"
774// cap_h1 = Interior height of top cap.
775// cap_thread_depth1 = Thread depth on top cap. Default: 2.34
776// tolerance = Extra space to add to the outer diameter of threads and neck in mm. Applied to radius.
777// cap_neck_od1 = Inner diameter of threads on top cap.
778// cap_thread_pitch1 = Thread pitch of top cap in mm.
779// cap_h2 = Interior height of bottom cap. Leave undefined to duplicate cap_h1.
780// cap_thread_depth2 = Thread depth on bottom cap. Default: same as cap_thread_depth1
781// cap_neck_od2 = Inner diameter of threads on top cap. Leave undefined to duplicate cap_neck_od1.
782// cap_thread_pitch2 = Thread pitch of bottom cap in mm. Leave undefinced to duplicate cap_thread_pitch1.
783// d = Distance between caps.
784// neck_id1 = Inner diameter of cutout in top cap.
785// neck_id2 = Inner diameter of cutout in bottom cap.
786// taper_lead_in = Length to leave straight before tapering on tube between caps if exists.
787// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
788// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
789// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
790// Examples:
791// bottle_adapter_cap_to_cap();
792module bottle_adapter_cap_to_cap(
793 wall = 2,
794 texture = "none",
795 cap_h1 = 11.2,
796 cap_thread_depth1 = 2.34,
797 tolerance = .2,
798 cap_neck_od1 = 25.5,
799 cap_thread_pitch1 = 4,
800 cap_h2,
801 cap_thread_depth2,
802 cap_neck_od2,
803 cap_thread_pitch2,
804 d = 0,
805 neck_id,
806 taper_lead_in = 0, anchor, spin,orient
807) {
808 cap_h2 = default(cap_h2,cap_h1);
809 cap_thread_depth2 = default(cap_thread_depth2,cap_thread_depth1);
810 cap_neck_od2 = default(cap_neck_od2,cap_neck_od1);
811 cap_thread_pitch2 = default(cap_thread_pitch2,cap_thread_pitch1);
812 taper_lead_in = (d >= taper_lead_in * 2) ? taper_lead_in : d / 2;
813
814 neck_id = min(cap_neck_od1 - cap_thread_depth1, cap_neck_od2-cap_thread_depth2);
815
816 top_h = cap_h1+wall;
817 bot_h = cap_h2+wall;
818
819
820 cap_od1 = cap_neck_od1 + 2*(cap_thread_depth1 - 0.8) + 2 * tolerance; // WTF; Engineered for consistency with old code, but
821 cap_od2 = cap_neck_od2 + 2*(cap_thread_depth2 - 0.8) + 2 * tolerance; // WTF; Engineered for consistency with old code, but
822
823 $fn = segs(33 / 2);
824 attachable(anchor=anchor,spin=spin,orient=orient, h=top_h+bot_h+d, d=max(cap_od1,cap_od2)+2*wall){
825 zmove((bot_h-top_h)/2)
826 difference(){
827 union(){
828 up(d / 2){
829 generic_bottle_cap(
830 orient = UP,
831 wall = wall,
832 texture = texture,
833 height = cap_h1,
834 thread_depth = cap_thread_depth1,
835 tolerance = tolerance,
836 neck_od = cap_neck_od1,
837 pitch = cap_thread_pitch1
838 );
839 }
840 if (d != 0) {
841 rotate_extrude() {
842 polygon(points = [
843 [0, d / 2],
844 [cap_od1 / 2 + wall, d / 2],
845 [cap_od1 / 2 + wall, d / 2 - taper_lead_in],
846 [cap_od2 / 2 + wall, taper_lead_in - d / 2],
847 [cap_od2 / 2 + wall, -d / 2],
848 [0, -d / 2]
849 ]);
850 }
851 }
852 down(d / 2){
853 generic_bottle_cap(
854 orient = DOWN,
855 wall = wall,
856 texture = texture,
857 height = cap_h2,
858 thread_depth = cap_thread_depth2,
859 tolerance = tolerance,
860 neck_od = cap_neck_od2,
861 pitch = cap_thread_pitch2
862 );
863 }
864 }
865 rotate_extrude() {
866 polygon(points = [
867 [0, wall + d / 2 + 0.1],
868 [neck_id / 2, wall + d / 2],
869 [neck_id / 2, wall + d / 2 - taper_lead_in],
870 [neck_id / 2, taper_lead_in - d / 2 - wall],
871 [neck_id / 2, -d / 2 - wall],
872 [0, -d / 2 - wall - 0.1]
873 ]);
874 }
875 }
876 children();
877 }
878}
879
880function bottle_adapter_cap_to_cap(
881 wall, texture, cap_h1, cap_thread_od1, tolerance,
882 cap_neck_od1, cap_thread_pitch1, cap_h2, cap_thread_od2,
883 cap_neck_od2, cap_thread_pitch2, d, neck_id1, neck_id2, taper_lead_in
884) = no_function("bottle_adapter_cap_to_cap");
885
886
887// Module: bottle_adapter_neck_to_neck()
888// Synopsis: Creates a generic adaptor between a neck and a neck.
889// SynTags: Geom
890// Topics: Bottles, Threading
891// See Also: bottle_adapter_neck_to_cap(), bottle_adapter_cap_to_cap()
892// Usage:
893// bottle_adapter_neck_to_neck(...) [ATTACHMENTS];
894// Description:
895// Creates a threaded neck to neck adapter.
896// Arguments:
897// ---
898// d = Distance between bottoms of necks
899// neck_od1 = Outer diameter of top neck w/o threads
900// neck_id1 = Inner diameter of top neck
901// thread_od1 = Outer diameter of threads on top neck
902// height1 = Height of top neck above support ring.
903// support_od1 = Outer diameter of the support ring on the top neck. Set to 0 for no ring.
904// thread_pitch1 = Thread pitch of top neck.
905// neck_od2 = Outer diameter of bottom neck w/o threads. Leave undefined to duplicate neck_od1
906// neck_id2 = Inner diameter of bottom neck. Leave undefined to duplicate neck_id1
907// thread_od2 = Outer diameter of threads on bottom neck. Leave undefined to duplicate thread_od1
908// height2 = Height of bottom neck above support ring. Leave undefined to duplicate height1
909// support_od2 = Outer diameter of the support ring on bottom neck. Set to 0 for no ring. Leave undefined to duplicate support_od1
910// pitch2 = Thread pitch of bottom neck. Leave undefined to duplicate thread_pitch1
911// taper_lead_in = Length to leave straight before tapering on tube between necks if exists.
912// wall = Thickness of tube wall between necks. Leave undefined to match outer diameters with the neckODs/supportODs.
913// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
914// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
915// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
916// Examples:
917// bottle_adapter_neck_to_neck();
918module bottle_adapter_neck_to_neck(
919 d = 0,
920 neck_od1 = 25,
921 neck_id1 = 21.4,
922 thread_od1 = 27.2,
923 height1 = 17,
924 support_od1 = 33.0,
925 thread_pitch1 = 3.2,
926 neck_od2, neck_id2,
927 thread_od2, height2,
928 support_od2, pitch2,
929 taper_lead_in = 0, wall, anchor, spin, orient
930) {
931 neck_od2 = (neck_od2 == undef) ? neck_od1 : neck_od2;
932 neck_id2 = (neck_id2 == undef) ? neck_id1 : neck_id2;
933 thread_od2 = (thread_od2 == undef) ? thread_od1 : thread_od2;
934 height2 = (height2 == undef) ? height1 : height2;
935 support_od2 = (support_od2 == undef) ? support_od1 : support_od2;
936 pitch2 = (pitch2 == undef) ? thread_pitch1 : pitch2;
937 wall = (wall == undef) ? support_od1 + support_od2 + neck_id1 + neck_id2 : wall;
938
939 supprtOD2 = (d == 0 && support_od2 != 0) ? max(neck_od1, support_od2) : support_od2;
940 supprtOD1 = (d == 0 && support_od1 != 0) ? max(neck_od2, support_od1) : support_od1;
941
942 $fn = segs(33 / 2);
943 wallt1 = min(wall, (max(supprtOD1, neck_od1) - neck_id1) / 2);
944 wallt2 = min(wall, (max(supprtOD2, neck_od2) - neck_id2) / 2);
945
946 taper_lead_in = (d >= taper_lead_in * 2) ? taper_lead_in : d / 2;
947
948 top_h = height1 + max(1,height1/17)*sign(support_od1);
949 bot_h = height2 + max(1,height2/17)*sign(support_od2);
950
951 attachable(anchor=anchor,orient=orient,spin=spin, h=top_h+bot_h+d, d=max(neck_od1,neck_od2)){
952 zmove((bot_h-top_h)/2)
953 difference(){
954 union(){
955 up(d / 2){
956 generic_bottle_neck(orient = UP,
957 neck_d = neck_od1,
958 id = neck_id1,
959 thread_od = thread_od1,
960 height = height1,
961 support_d = supprtOD1,
962 pitch = thread_pitch1,
963 round_supp = ((wallt1 < (supprtOD1 - neck_id1) / 2) || (support_od1 > max(neck_od2, support_od2) && d == 0)),
964 wall = (d > 0) ? wallt1 : min(wallt1, ((max(neck_od2, support_od2)) - neck_id1) / 2)
965 );
966 }
967 if (d != 0) {
968 rotate_extrude() {
969 polygon(points = [
970 [0, d / 2],
971 [neck_id1 / 2 + wallt1, d / 2],
972 [neck_id1 / 2 + wallt1, d / 2 - taper_lead_in],
973 [neck_id2 / 2 + wallt2, taper_lead_in - d / 2],
974 [neck_id2 / 2 + wallt2, -d / 2],
975 [0, -d / 2]
976 ]);
977 }
978 }
979 down(d / 2){
980 generic_bottle_neck(orient = DOWN,
981 neck_d = neck_od2,
982 id = neck_id2,
983 thread_od = thread_od2,
984 height = height2,
985 support_d = supprtOD2,
986 pitch = pitch2,
987 round_supp = ((wallt2 < (supprtOD2 - neck_id2) / 2) || (support_od2 > max(neck_od1, support_od1) && d == 0)),
988 wall = (d > 0) ? wallt2 : min(wallt2, ((max(neck_od1, support_od1)) - neck_id2) / 2)
989 );
990 }
991 }
992 if (neck_id1 != undef || neck_id2 != undef) {
993 neck_id1 = (neck_id1 == undef) ? neck_id2 : neck_id1;
994 neck_id2 = (neck_id2 == undef) ? neck_id1 : neck_id2;
995
996 rotate_extrude() {
997 polygon(points = [
998 [0, d / 2],
999 [neck_id1 / 2, d / 2],
1000 [neck_id1 / 2, d / 2 - taper_lead_in],
1001 [neck_id2 / 2, taper_lead_in - d / 2],
1002 [neck_id2 / 2, -d / 2],
1003 [0, -d / 2]
1004 ]);
1005 }
1006 }
1007 }
1008 children();
1009 }
1010}
1011
1012function bottle_adapter_neck_to_neck(
1013 d, neck_od1, neck_id1, thread_od1, height1,
1014 support_od1, thread_pitch1, neck_od2, neck_id2,
1015 thread_od2, height2, support_od2,
1016 pitch2, taper_lead_in, wall
1017) = no_fuction("bottle_adapter_neck_to_neck");
1018
1019
1020
1021// Section: SPI Bottle Threading
1022
1023
1024// Module: sp_neck()
1025// Synopsis: Creates an SPI threaded bottle neck.
1026// SynTags: Geom
1027// Topics: Bottles, Threading
1028// See Also: sp_cap()
1029// Usage:
1030// sp_neck(diam, type, wall|id=, [style=], [bead=]) [ATTACHMENTS];
1031// Description:
1032// Make a SPI (Society of Plastics Industry) threaded bottle neck. You must
1033// supply the nominal outer diameter of the threads and the thread type, one of
1034// 400, 410 and 415. The 400 type neck has 360 degrees of thread, the 410
1035// neck has 540 degrees of thread, and the 415 neck has 720 degrees of thread.
1036// You can also choose between the L style thread, which is symmetric and
1037// the M style thread, which is an asymmetric buttress thread. The M style
1038// may be good for 3d printing if printed with the flat face up.
1039// You can specify the wall thickness (measured from the base of the threads) or
1040// the inner diameter, and you can specify an optional bead at the base of the threads.
1041// Arguments:
1042// diam = nominal outer diameter of threads
1043// type = thread type, one of 400, 410 and 415
1044// wall = wall thickness
1045// ---
1046// id = inner diameter
1047// style = Either "L" or "M" to specify the thread style. Default: "L"
1048// bead = if true apply a bad to the neck. Default: false
1049// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
1050// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
1051// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
1052// Examples:
1053// sp_neck(48,400,2);
1054// sp_neck(48,400,2,bead=true);
1055// sp_neck(22,410,2);
1056// sp_neck(22,410,2,bead=true);
1057// sp_neck(28,415,id=20,style="M");
1058// sp_neck(13,415,wall=1,style="M",bead=true);
1059
1060
1061// Thread specs from https://www.isbt.com/threadspecs-downloads.asp
1062
1063// T = peak to peak diameter (outer diameter)
1064// I = Inner diameter
1065// S = space above top thread
1066// H = total height of neck
1067
1068_sp_specs = [
1069 [400, //diam T I H S tpi
1070 [[ 18, [ 17.68, 8.26, 9.42, 0.94, 8]],
1071 [ 20, [ 19.69, 10.26, 9.42, 0.94, 8]],
1072 [ 22, [ 21.69, 12.27, 9.42, 0.94, 8]],
1073 [ 24, [ 23.67, 13.11, 10.16, 1.17, 8]],
1074 [ 28, [ 27.38, 15.60, 10.16, 1.17, 6]],
1075 [ 30, [ 28.37, 16.59, 10.24, 1.17, 6]],
1076 [ 33, [ 31.83, 20.09, 10.24, 1.17, 6]],
1077 [ 35, [ 34.34, 22.23, 10.24, 1.17, 6]],
1078 [ 38, [ 37.19, 25.07, 10.24, 1.17, 6]],
1079 [ 40, [ 39.75, 27.71, 10.24, 1.17, 6]],
1080 [ 43, [ 41.63, 29.59, 10.24, 1.17, 6]],
1081 [ 45, [ 43.82, 31.78, 10.24, 1.17, 6]],
1082 [ 48, [ 47.12, 35.08, 10.24, 1.17, 6]],
1083 [ 51, [ 49.56, 37.57, 10.36, 1.17, 6]],
1084 [ 53, [ 52.07, 40.08, 10.36, 1.17, 6]],
1085 [ 58, [ 56.06, 44.07, 10.36, 1.17, 6]],
1086 [ 60, [ 59.06, 47.07, 10.36, 1.17, 6]],
1087 [ 63, [ 62.08, 50.09, 10.36, 1.17, 6]],
1088 [ 66, [ 65.07, 53.09, 10.36, 1.17, 6]],
1089 [ 70, [ 69.06, 57.07, 10.36, 1.17, 6]],
1090 [ 75, [ 73.56, 61.57, 10.36, 1.17, 6]],
1091 [ 77, [ 76.66, 64.67, 12.37, 1.52, 6]],
1092 [ 83, [ 82.58, 69.93, 12.37, 1.52, 5]],
1093 [ 89, [ 88.75, 74.12, 13.59, 1.52, 5]],
1094 [100, [ 99.57, 84.94, 15.16, 1.52, 5]],
1095 [110, [109.58, 94.92, 15.16, 1.52, 5]],
1096 [120, [119.56,104.93, 17.40, 1.52, 5]],
1097 ]],
1098 [410, //diam T I H S tpi L W
1099 [[ 18, [ 17.68, 8.26, 13.28, 0.94, 8, 9.17, 2.13]],
1100 [ 20, [ 19.59, 10.26, 14.07, 0.94, 8, 9.17, 2.13]],
1101 [ 22, [ 21.69, 12.27, 14.86, 0.94, 8, 9.55, 2.13]],
1102 [ 24, [ 23.67, 13.11, 16.41, 1.17, 8, 11.10, 2.13]],
1103 [ 28, [ 27.38, 15.60, 17.98, 1.17, 6, 11.76, 2.39]],
1104 ]],
1105 [415, //diam T I H S tpi L W
1106 [[ 13, [ 12.90, 5.54, 11.48, 0.94,12, 7.77, 1.14]],
1107 [ 15, [ 14.61, 6.55, 14.15, 0.94,12, 8.84, 1.14]],
1108 [ 18, [ 17.68, 8.26, 15.67, 0.94, 8, 10.90, 2.13]],
1109 [ 20, [ 19.69, 10.26, 18.85, 0.94, 8, 11.58, 2.13]],
1110 [ 22, [ 21.69, 12.27, 21.26, 0.94, 8, 13.87, 2.13]],
1111 [ 24, [ 23.67, 13.11, 24.31, 1.17, 8, 14.25, 2.13]],
1112 [ 28, [ 27.38, 15.60, 27.48, 1.17, 6, 16.64, 2.39]],
1113 [ 33, [ 31.83, 20.09, 32.36, 1.17, 6, 19.61, 2.39]],
1114 ]]
1115];
1116
1117_sp_twist = [ [400, 360],
1118 [410, 540],
1119 [415, 720]
1120 ];
1121
1122
1123// profile data: tpi, total width, depth,
1124_sp_thread_width= [
1125 [5, 3.05],
1126 [6, 2.39],
1127 [8, 2.13],
1128 [12, 1.14], // But note style M is different
1129 ];
1130
1131
1132function _sp_thread_profile(tpi, a, S, style, flip=false) =
1133 let(
1134 pitch = 1/tpi*INCH,
1135 cL = a*(1-1/sqrt(3)),
1136 cM = (1-tan(10))*a/2,
1137 // SP specified roundings for the thread profile have special case for tpi=12
1138 roundings = style=="L" && tpi < 12 ? 0.5
1139 : style=="M" && tpi < 12 ? [0.25, 0.25, 0.75, 0.75]
1140 : style=="L" ? [0.38, 0.13, 0.13, 0.38]
1141 : /* style=="M" */ [0.25, 0.25, 0.2, 0.5],
1142 path1 = style=="L"
1143 ? round_corners([[-1/2*pitch,-a/2],
1144 [-a/2,-a/2],
1145 [-cL/2,0],
1146 [cL/2,0],
1147 [a/2,-a/2],
1148 [1/2*pitch,-a/2]], radius=roundings, closed=false,$fn=24)
1149 : round_corners(
1150 [[-1/2*pitch,-a/2],
1151 [-a/2, -a/2],
1152 [-cM, 0],
1153 [0,0],
1154 [a/2,-a/2],
1155 [1/2*pitch,-a/2]], radius=roundings, closed=false, $fn=24),
1156 path2 = flip ? reverse(xflip(path1)) : path1
1157 )
1158 // Shift so that the profile is S mm from the right end to create proper length S top gap
1159 select(right(-a/2+1/2-S,p=path2),1,-2)/pitch;
1160
1161
1162function sp_neck(diam,type,wall,id,style="L",bead=false, anchor, spin, orient) = no_function("sp_neck");
1163module sp_neck(diam,type,wall,id,style="L",bead=false, anchor, spin, orient)
1164{
1165 assert(num_defined([wall,id])==1, "Must define exactly one of wall and id");
1166
1167 table = struct_val(_sp_specs,type);
1168 dum1=assert(is_def(table),"Unknown SP closure type. Type must be one of 400, 410, or 415");
1169 entry = struct_val(table, diam);
1170 dum2=assert(is_def(entry), str("Unknown closure nominal diameter. Allowed diameters for SP",type,": ",struct_keys(table)))
1171 assert(style=="L" || style=="M", "style must be \"L\" or \"M\"");
1172
1173 T = entry[0];
1174 I = entry[1];
1175 H = entry[2];
1176 S = entry[3];
1177 tpi = entry[4];
1178
1179 // a is the width of the thread
1180 a = (style=="M" && tpi==12) ? 1.3 : struct_val(_sp_thread_width,tpi);
1181
1182 twist = struct_val(_sp_twist, type);
1183
1184 profile = _sp_thread_profile(tpi,a,S,style);
1185
1186 depth = a/2;
1187 taperlen = 2*a;
1188
1189 beadmax = type==400 ? (T/2-depth)+depth*1.25
1190 : diam <=15 ? (T-.15)/2 : (T-.05)/2;
1191
1192 W = type==400 ? a*1.5 // arbitrary decision for type 400
1193 : entry[6]; // specified width for 410 and 415
1194
1195 beadpts = [
1196 [0,-W/2],
1197 each arc(16, points = [[T/2-depth, -W/2],
1198 [beadmax, 0],
1199 [T/2-depth, W/2]]),
1200 [0,W/2]
1201 ];
1202
1203 isect400 = [for(seg=pair(beadpts)) let(segisect = line_intersection([[T/2,0],[T/2,1]] , seg, LINE, SEGMENT)) if (is_def(segisect)) segisect.y];
1204
1205 extra_bot = type==400 && bead ? -min(column(beadpts,1))+max(isect400) : 0;
1206 bead_shift = type==400 ? H+max(isect400) : entry[5]+W/2; // entry[5] is L
1207
1208 attachable(anchor,spin,orient,r=bead ? beadmax : T/2, l=H+extra_bot){
1209 up((H+extra_bot)/2){
1210 difference(){
1211 union(){
1212 thread_helix(d=T-.01, profile=profile, pitch = INCH/tpi, turns=twist/360, lead_in=taperlen, anchor=TOP);
1213 cylinder(d=T-depth*2,h=H,anchor=TOP);
1214 if (bead)
1215 down(bead_shift)
1216 rotate_extrude()
1217 polygon(beadpts);
1218 }
1219 up(.5)cyl(d=is_def(id) ? id : T-a-2*wall, l=H-extra_bot+1, anchor=TOP);
1220 }
1221 }
1222 children();
1223 }
1224}
1225
1226
1227
1228// Module: sp_cap()
1229// Synopsis: Creates an SPI threaded bottle cap.
1230// SynTags: Geom
1231// Topics: Bottles, Threading
1232// See Also: sp_neck()
1233// Usage:
1234// sp_cap(diam, type, wall, [style=], [top_adj=], [bot_adj=], [texture=], [$slop]) [ATTACHMENTS];
1235// Description:
1236// Make a SPI (Society of Plastics Industry) threaded bottle neck. You must
1237// supply the nominal outer diameter of the threads and the thread type, one of
1238// 400, 410 and 415. The 400 type neck has 360 degrees of thread, the 410
1239// neck has 540 degrees of thread, and the 415 neck has 720 degrees of thread.
1240// You can also choose between the L style thread, which is symmetric and
1241// the M style thread, which is an asymmetric buttress thread. Note that it
1242// is OK to mix styles, so you can put an L-style cap onto an M-style neck.
1243// .
1244// The 410 and 415 caps have very long unthreaded sections at the bottom.
1245// The bot_adj parameter specifies an amount to reduce that bottom extension, which might be
1246// necessary if the cap bottoms out on the bead. Be careful that you don't shrink past the threads,
1247// especially if making adjustments to 400 caps which have a very small bottom extension.
1248// These caps often contain a cardboard or foam sealer disk, which can be as much as 1mm thick, and
1249// would cause the cap to stop in a higher position.
1250// .
1251// You can also adjust the space between the top of the cap and the threads using top_adj. This
1252// will change how the threads engage when the cap is fully seated.
1253// .
1254// The inner diameter of the cap is set to allow 10% of the thread depth in clearance. The diameter
1255// is further increased by `2 * $slop` so you can increase clearance if necessary.
1256// .
1257// Note: there is a published SPI standard for necks, but absolutely nothing for caps. This
1258// cap module was designed based on the neck standard to mate reasonably well, but if you
1259// find ways that it does the wrong thing, file a report.
1260// Arguments:
1261// diam = nominal outer diameter of threads
1262// type = thread type, one of 400, 410 and 415
1263// wall = wall thickness
1264// ---
1265// style = Either "L" or "M" to specify the thread style. Default: "L"
1266// top_adj = Amount to reduce top space in the cap, which means it doesn't screw down as far. Default: 0
1267// bot_adj = Amount to reduce extension of cap at the bottom, which also means it doesn't screw down as far. Default: 0
1268// texture = texture for outside of cap, one of "knurled", "ribbed" or "none. Default: "none"
1269// $slop = Increase inner diameter by `2 * $slop`.
1270// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
1271// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
1272// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
1273// Examples:
1274// sp_cap(48,400,2);
1275// sp_cap(22,400,2);
1276// sp_cap(22,410,2);
1277// sp_cap(28,415,1.5,style="M");
1278module sp_cap(diam,type,wall,style="L",top_adj=0, bot_adj=0, texture="none", anchor, spin, orient)
1279{
1280 table = struct_val(_sp_specs,type);
1281 dum1=assert(is_def(table),"Unknown SP closure type. Type must be one of 400, 410, or 415");
1282 entry = struct_val(table, diam);
1283 dum2=assert(is_def(entry), str("Unknown closure nominal diameter. Allowed diameters for SP",type,": ",struct_keys(table)))
1284 assert(style=="L" || style=="M", "style must be \"L\" or \"M\"");
1285
1286 T = entry[0];
1287 I = entry[1];
1288 H = entry[2]-0.5;
1289 S = entry[3];
1290 tpi = entry[4];
1291 a = (style=="M" && tpi==12) ? 1.3 : struct_val(_sp_thread_width,tpi);
1292
1293 twist = struct_val(_sp_twist, type);
1294
1295 echo(top_adj=top_adj,bot_adj=bot_adj);
1296 dum3=assert(top_adj<S+0.75*a, str("The top_adj value is too large so the thread won't fit. It must be smaller than ",S+0.75*a));
1297 oprofile = _sp_thread_profile(tpi,a,S+0.75*a-top_adj,style,flip=true);
1298 bounds=pointlist_bounds(oprofile);
1299 profile = fwd(-bounds[0].y,yflip(oprofile));
1300
1301 depth = a/2;
1302 taperlen = 2*a;
1303 assert(in_list(texture, ["none","knurled","ribbed"]));
1304 space=2*depth/10+2*get_slop();
1305 attachable(anchor,spin,orient,r= (T+space)/2+wall, l=H-bot_adj+wall){
1306 xrot(180)
1307 up((H-bot_adj)/2-wall/2){
1308 difference(){
1309 up(wall){
1310 if (texture=="knurled")
1311 cyl(d=T+space+2*wall,l=H+wall-bot_adj,anchor=TOP,texture="trunc_pyramids", tex_size=[3,3], tex_style="convex");
1312 else if (texture == "ribbed")
1313 cyl(d=T+space+2*wall,l=H+wall-bot_adj,anchor=TOP,chamfer2=.8,tex_taper=0,texture="trunc_ribs", tex_size=[3,3], tex_style="min_edge");
1314 else
1315 cyl(d=T+space+2*wall,l=H+wall-bot_adj,anchor=TOP,chamfer2=.8);
1316 }
1317 cyl(d=T+space, l=H-bot_adj+1, anchor=TOP);
1318 }
1319 thread_helix(d=T+space-.01, profile=profile, pitch = INCH/tpi, turns=twist/360, lead_in=taperlen, anchor=TOP, internal=true);
1320 }
1321 children();
1322 }
1323}
1324
1325
1326
1327// Function: sp_diameter()
1328// Synopsis: Returns the base diameter of an SPI bottle neck from the nominal diameter and type number.
1329// Topics: Bottles, Threading
1330// See Also: sp_neck(), sp_cap()
1331// Usage:
1332// true_diam = sp_diameter(diam,type)
1333// Description:
1334// Returns the actual base diameter (root of the threads) for a SPI plastic bottle neck given the nominal diameter and type number (400, 410, 415).
1335// Arguments:
1336// diam = nominal diameter
1337// type = closure type number (400, 410 or 415)
1338function sp_diameter(diam,type) =
1339 let(
1340 table = struct_val(_sp_specs,type)
1341 )
1342 assert(is_def(table),"Unknown SP closure type. Type must be one of 400, 410, or 415")
1343 let(
1344 entry = struct_val(table, diam)
1345 )
1346 assert(is_def(entry), str("Unknown closure nominal diameter. Allowed diameters for SP",type,": ",struct_keys(table)))
1347 entry[0];
1348
1349
1350
1351// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap