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