whistles

3d printable Irish whistles
git clone https://git.woozle.org/neale/whistles.git

commit
d943160
parent
98059a2
author
Neale Pickett
date
2025-05-03 18:09:22 -0600 MDT
Add low C and fix ruby head
4 files changed,  +72, -81
M Makefile
+10, -2
 1@@ -1,13 +1,21 @@
 2-all: lowd lowe
 3+all: lowc lowd lowe
 4 
 5 clean:
 6 	rm -f *.3mf
 7 
 8 %.3mf: whistle.scad whistle.json tonehole.scad ruby.svg
 9-	openscad -o $@ -p whistle.json -P $(VARIANT) -D Section=\"$*\" $<
10+	openscad -o $@ -p whistle.json -P $(VARIANT) -D Section=$(SECTION) $<
11+
12+lowc: lowc-bot.3mf lowc-mid.3mf lowc-top.3mf
13+lowc-%.3mf: VARIANT="low c"
14 
15 lowd: lowd-bot.3mf lowd-mid.3mf lowd-top.3mf
16 lowd-%.3mf: VARIANT="low d - Dixon"
17 
18 lowe: lowe-bot.3mf lowe-mid.3mf lowe-top.3mf
19 lowe-%.3mf: VARIANT="low e"
20+
21+
22+%-top.3mf: SECTION=0
23+%-mid.3mf: SECTION=1
24+%-bot.3mf: SECTION=2
M ruby.svg
+8, -8
 1@@ -1,9 +1,9 @@
 2 <?xml version="1.0" encoding="utf-8"?>
 3-<svg
 4-    xmlns="http://www.w3.org/2000/svg"
 5-    viewBox="18.4343 -8.2244 462.6288 499.8541"
 6-    width="0.924cm"
 7-    height="1cm"
 8-    preserveAspectRatio="meet">
 9-  <path style="stroke: rgb(0, 0, 0); fill: rgb(107, 84, 84); transform-origin: 249.749px 241.703px;" d="M 187.594 147.071 C 222.949 153.12 270.972 155.731 309.277 143.299 C 317.317 140.69 320.56 118.06 320.56 118.06 C 320.56 118.06 327.678 106.64 330.738 100.657 C 333.381 95.489 333.159 88.246 337.777 84.73 C 353.635 72.659 370.372 52.125 370.372 52.125 C 370.372 52.125 426.27 2.035 440.95 1.462 C 455.63 0.889 461.076 -0.732 469.218 12.046 C 477.36 24.824 453.472 138.031 453.472 138.031 C 453.472 138.031 453.953 147.266 444.263 160.449 C 434.573 173.632 447.673 167.61 440.944 190.185 C 434.215 212.76 438.017 200.224 427.718 215.259 C 417.419 230.294 397.67 242.025 388.524 251.061 C 379.378 260.097 386.798 291.019 386.798 291.019 C 386.798 291.019 387.847 326.31 381.141 347.368 C 374.435 368.426 346.18 391.255 346.18 391.255 C 346.18 391.255 325.323 472.928 268.206 481.757 C 211.089 490.586 166.483 394.938 166.483 394.938 C 166.483 394.938 144.005 389.647 131.704 371.16 C 119.403 352.673 108.748 336.052 108.361 311.035 C 107.974 286.018 101.518 264.509 101.518 264.509 C 101.518 264.509 89.405 262.542 85.275 256.613 C 81.145 250.684 78.859 245.845 70.152 238.568 C 59.127 229.354 42.841 226.273 39.22 199.414 C 35.599 172.555 36.125 179.11 36.125 179.11 C 36.125 179.11 23.121 168.902 31.274 144.728 C 39.427 120.554 89.353 134.561 107.159 120.958 C 124.965 107.355 114.883 115.961 133.013 98.781 C 151.143 81.601 175.442 122.255 175.442 122.255 C 175.442 122.255 178.724 145.553 187.594 147.071 Z" id="object-1" transform="matrix(0.99907297, 0.043046, -0.043046, 0.99907297, -2.7e-7, -2.3e-7)"/>
10-</svg>
11+<svg xmlns="http://www.w3.org/2000/svg" height="1cm" width="0.924cm" viewBox="0 0 500 500" xmlns:bx="https://boxy-svg.com">
12+  <defs>
13+    <bx:export>
14+      <bx:file format="png" path="ruby.png"/>
15+    </bx:export>
16+  </defs>
17+  <path style="stroke: rgb(0, 0, 0); fill: rgb(107, 84, 84); transform-box: fill-box; transform-origin: 50% 50%;" d="M 187.594 147.071 C 222.949 153.12 270.972 155.731 309.277 143.299 C 317.317 140.69 324.255 101.717 337.777 84.73 C 365.301 50.153 418.636 3.565 440.95 1.462 C 463.264 -0.641 461.076 -0.732 469.218 12.046 C 477.36 24.824 453.472 138.031 453.472 138.031 C 453.472 138.031 453.953 147.266 444.263 160.449 C 434.573 173.632 447.673 167.61 440.944 190.185 C 434.215 212.76 438.017 200.224 427.718 215.259 C 417.419 230.294 397.67 242.025 388.524 251.061 C 379.378 260.097 386.798 291.019 386.798 291.019 C 386.798 291.019 387.847 326.31 381.141 347.368 C 374.435 368.426 346.18 391.255 346.18 391.255 C 346.18 391.255 325.323 472.928 268.206 481.757 C 211.089 490.586 166.483 394.938 166.483 394.938 C 166.483 394.938 144.005 389.647 131.704 371.16 C 119.403 352.673 108.748 336.052 108.361 311.035 C 107.974 286.018 101.518 264.509 101.518 264.509 C 101.518 264.509 89.405 262.542 85.275 256.613 C 81.145 250.684 78.859 245.845 70.152 238.568 C 59.127 229.354 42.841 226.273 39.22 199.414 C 35.599 172.555 36.125 179.11 36.125 179.11 C 36.125 179.11 23.121 168.902 31.274 144.728 C 39.427 120.554 89.353 134.561 107.159 120.958 C 124.965 107.355 114.883 115.961 133.013 98.781 C 151.143 81.601 175.442 122.255 175.442 122.255 C 175.442 122.255 178.724 145.553 187.594 147.071 Z" id="object-1" transform="matrix(0.999073, 0.043046, -0.043046, 0.999073, 0.014376, 0.000286)"/>
18+</svg>
M whistle.json
+14, -41
 1@@ -1,10 +1,10 @@
 2 {
 3     "parameterSets": {
 4         "low d - Dixon": {
 5+            "Text": "d",
 6             "Height": "469",
 7             "InnerDiameter": "20.9",
 8-            "Cut1": "190",
 9-            "Cut2": "240",
10+            "Cuts": "[190, 240]",
11             "Hole1": "[90, 7.45]",
12             "Hole2": "[130, 11.0]",
13             "Hole3": "[161, 9.45]",
14@@ -13,10 +13,10 @@
15             "Hole6": "[293, 7.45]"
16         },
17         "low e": {
18+            "Text": "e",
19             "Height": "406",
20             "InnerDiameter": "20.9",
21-            "Cut1": "160",
22-            "Cut2": "300",
23+            "Cuts": "[160, 300]",
24             "Hole1": "[261.3, 8.0]",
25             "Hole2": "[226.3, 9.5]",
26             "Hole3": "[191.9, 9.0]",
27@@ -24,44 +24,17 @@
28             "Hole5": "[116.4, 12.0]",
29             "Hole6": "[81.5, 7.5]"
30         },
31-        "low e - pretty good, high E a bit flat": {
32-            "Height": "406",
33-            "InnerDiameter": "20.9",
34-            "Cut1": "165",
35-            "Cut2": "300",
36-            "Hole1": "[261.3, 8.0]",
37-            "Hole2": "[226.3, 9.5]",
38-            "Hole3": "[191.8, 9.0]",
39-            "Hole4": "[151.5, 7.5]",
40-            "Hole5": "[116.0, 12.0]",
41-            "Hole6": "[83.7, 7.0]"
42-        },
43-        "low e - awesome, except for extreme B3 reach": {
44-            "Height": "409.4",
45+        "low c": {
46+            "Text": "c",
47+            "Height": "545.2",
48             "InnerDiameter": "20.9",
49-            "Cut1": "165",
50-            "Cut2": "300",
51-            "Hole1": "[261.2, 8.0]",
52-            "Hole2": "[226.2, 9.5]",
53-            "Hole3": "[191.0, 9.0]",
54-            "Hole4": "[150.5, 7.5]",
55-            "Hole5": "[118.1, 12.0]",
56-            "Hole6": "[70.5, 11.5]"
57-        },
58-        "low FAIL": {
59-            "Height": "430",
60-            "OuterDiameter": "25",
61-            "InnerDiameter": "20",
62-            "ORingDiameter": "1.5",
63-            "ORingProtrusion": "0.98",
64-            "Cut1": "165",
65-            "Cut2": "300",
66-            "Hole1": "[90, 6.5]",
67-            "Hole2": "[122, 11]",
68-            "Hole3": "[150, 8.5]",
69-            "Hole4": "[202, 7.5]",
70-            "Hole5": "[231, 8.5]",
71-            "Hole6": "[267, 8.5]"
72+            "Cuts": "[195, 380]",
73+            "Hole1": "[326.3, 8.0]",
74+            "Hole2": "[283.2, 9.5]",
75+            "Hole3": "[238.1, 9.0]",
76+            "Hole4": "[187.1, 7.5]",
77+            "Hole5": "[145.3, 12.0]",
78+            "Hole6": "[101.8, 7.0]"
79         }
80     },
81     "fileFormatVersion": "1"
M whistle.scad
+40, -30
  1@@ -2,7 +2,7 @@ include <common.scad>
  2 include <tonehole.scad>
  3 
  4 // Which section to render for printing
  5-Section = "e"; // [e:Everything, top:Top, mid:Middle, bot:Bottom]
  6+Section = -1; // [-1:Everything, 0:Bottom, 1:Middle, 2:Top]
  7 
  8 // Height of the instrument
  9 Height = 469;
 10@@ -16,14 +16,15 @@ ORingDiameter = 1.50; // 0.01
 11 // How far beyond the outer diameter the O ring should protrude
 12 ORingProtrusion = 0.80; // 0.01
 13 
 14-// Height of first cut for a sectioned print
 15-Cut1 = 190;
 16-// Height of second cut for a sectioned print
 17-Cut2 = 340;
 18+// Cuts to make for a sectioned print
 19+Cuts = [190, 340];
 20 
 21 // Use Axianov tone holes?
 22 AxianovHoles = true;
 23 
 24+// Text to display on the back
 25+Text = "d";
 26+
 27 Hole1 = [90, 7.45];
 28 Hole2 = [130, 11.0];
 29 Hole3 = [161, 9.45];
 30@@ -31,6 +32,13 @@ Hole4 = [221, 7.45];
 31 Hole5 = [254, 9.50];
 32 Hole6 = [293, 7.45];
 33 
 34+/* [ Esoterics ] */
 35+// Angle for tone holes. Anything other than 45 will create challenging overhangs!
 36+AxianovHoleAngle = 45;
 37+
 38+/* [ Hidden ] */
 39+sortedCuts = [min(Cuts[0], Cuts[1]), max(Cuts[0], Cuts[1])];
 40+
 41 module whistle() {
 42   difference() {
 43     tube(h=Height, id=InnerDiameter, od=OuterDiameter);
 44@@ -41,7 +49,7 @@ module whistle() {
 45     }
 46 
 47     // Decoration
 48-    for (i = [0, Cut1, Cut2]) {
 49+    for (i = [0, sortedCuts[0], sortedCuts[1]]) {
 50       translate([0, 0, i + 2]) {
 51         translate([0, 0, 0]) tube(h=1, id=OuterDiameter-0.5, od=OuterDiameter+0.5);
 52         translate([0, 0, 4]) tube(h=1, id=OuterDiameter-0.5, od=OuterDiameter+0.5);
 53@@ -51,9 +59,9 @@ module whistle() {
 54     for (hole = [Hole1, Hole2, Hole3, Hole4, Hole5, Hole6]) {
 55       if (AxianovHoles) {
 56         z = hole[0];
 57-        top = (z>Cut1) || (z>Cut2);
 58+        top = (z>sortedCuts[0]) || (z>sortedCuts[1]);
 59         m = [0, top?0:1, 0];
 60-        r = [90, top?45:135, 0];
 61+        r = [90, top?90-AxianovHoleAngle:90+AxianovHoleAngle, 0];
 62         translate([0, 0, hole[0]]) rotate(r) mirror(m) tonehole(d=hole[1]);
 63       } else {
 64         translate([0, 0, hole[0]]) rotate([90, 0, 0]) cylinder(d=hole[1], h=50);
 65@@ -61,42 +69,44 @@ module whistle() {
 66     }
 67 
 68     // Ridges to catch seams
 69-    rotate([0, 0,  110]) translate([0, OuterDiameter/2, 0]) cylinder(h=Cut1, d=1);
 70-    rotate([0, 0, -110]) translate([0, OuterDiameter/2, Cut1]) cylinder(h=Cut2-Cut1, d=1);
 71-    rotate([0, 0,  110]) translate([0, OuterDiameter/2, Cut2]) cylinder(h=Height, d=1);
 72+    rotate([0, 0,  110]) translate([0, OuterDiameter/2, 0]) cylinder(h=sortedCuts[0], d=1);
 73+    rotate([0, 0, -110]) translate([0, OuterDiameter/2, sortedCuts[0]]) cylinder(h=sortedCuts[1]-sortedCuts[0], d=1);
 74+    rotate([0, 0,  110]) translate([0, OuterDiameter/2, sortedCuts[1]]) cylinder(h=Height, d=1);
 75+
 76+    // Inner channel, seems to cause problems with the tenon join being too thin
 77     //rotate([0, 0, 140]) translate([0, InnerDiameter/2, 0]) cylinder(h=Height, d=1);
 78 
 79-    // Ruby :)
 80+    // Ruby head, and text on the back
 81     // This makes previews slow, so we only do it in final renders.
 82     if (!$preview) {
 83       intersection() {
 84-        rotate([90, 0, 0]) {
 85-          translate([0, 5, OuterDiameter/2]) {
 86-            linear_extrude(height=OuterDiameter, center=true) {
 87-              import("ruby.svg", center=true);
 88+        tube(h=50, id=OuterDiameter-0.5, od=OuterDiameter+10);
 89+        union() {
 90+          rotate([90, 0, 0]) {
 91+            linear_extrude(height=OuterDiameter) {
 92+              translate([-5, 0, 0]) import("ruby.svg");
 93+            }
 94+          }
 95+          translate([0, 0, 8]) {
 96+            rotate([90, 0, 180]) {
 97+              linear_extrude(height=OuterDiameter) {
 98+                text(Text, halign="center");
 99+              }
100             }
101           }
102         }
103-        tube(h=50, id=OuterDiameter-0.5, od=OuterDiameter+1);
104       }
105     }
106   }
107 }
108 
109-if (Section == "top") {
110-  tenon(h=Cut2, od=OuterDiameter, top=true) {
111-    whistle();
112-  }
113-} else if (Section == "mid") {
114-  tenon(h=Cut1, od=OuterDiameter, top=true) {
115-    tenon(h=Cut2, od=OuterDiameter, top=false) {
116+sectionedCuts = concat([-1000], sortedCuts, [1000]);
117+if (Section == -1) {
118+  whistle();
119+} else {
120+  tenon(h=sectionedCuts[Section], od=OuterDiameter, top=true) {
121+    tenon(h=sectionedCuts[Section+1], od=OuterDiameter, top=false) {
122       whistle();
123     }
124   }
125-} else if (Section == "bot") {
126-  tenon(h=Cut1, od=OuterDiameter, top=false) {
127-    whistle();
128-  }
129-} else {
130-  whistle();
131 }