Neale Pickett
·
2025-05-04
whistle.scad
1include <common.scad>
2include <tonehole.scad>
3
4// Which section to render for printing
5Section = -1; // [-1:Everything, 0:Bottom, 1:Middle, 2:Top]
6
7// Height of the instrument
8Height = 469;
9// Inner bore diameter
10InnerDiameter = 20; // 0.1
11// Outer diameter
12OuterDiameter = 25.0; // 0.1
13
14// Diameter of the rubber O ring
15ORingDiameter = 1.50; // 0.01
16// How far beyond the outer diameter the O ring should protrude
17ORingProtrusion = 0.80; // 0.01
18
19// Cuts to make for a sectioned print
20Cuts = [190, 340];
21
22// Use Axianov tone holes?
23AxianovHoles = true;
24
25// Text to display on the back
26Text = "d";
27
28Hole1 = [90, 7.45];
29Hole2 = [130, 11.0];
30Hole3 = [161, 9.45];
31Hole4 = [221, 7.45];
32Hole5 = [254, 9.50];
33Hole6 = [293, 7.45];
34
35/* [ Esoterics ] */
36// Angle for tone holes. Anything other than 45 will create challenging overhangs!
37AxianovHoleAngle = 45;
38
39// Depth of the tenon joint. Shallower will be less stable.
40TenonDepth = 20;
41
42/* [ Hidden ] */
43sortedCuts = [min(Cuts[0], Cuts[1]), max(Cuts[0], Cuts[1])];
44
45module whistle() {
46 difference() {
47 tube(h=Height, id=InnerDiameter, od=OuterDiameter);
48
49 // O ring grooves
50 for (z = [Height-7, Height-20]) {
51 translate([0, 0, z]) tube(h=ORingDiameter*1.3, id=OuterDiameter - ORingDiameter*2 + ORingProtrusion, od=OuterDiameter+1);
52 }
53
54 // Decoration
55 for (i = [0, sortedCuts[0], sortedCuts[1]]) {
56 translate([0, 0, i + 2]) {
57 translate([0, 0, 0]) tube(h=1, id=OuterDiameter-0.5, od=OuterDiameter+0.5);
58 translate([0, 0, 4]) tube(h=1, id=OuterDiameter-0.5, od=OuterDiameter+0.5);
59 }
60 }
61
62 for (hole = [Hole1, Hole2, Hole3, Hole4, Hole5, Hole6]) {
63 if (AxianovHoles) {
64 z = hole[0];
65 top = (z>sortedCuts[0]) || (z>sortedCuts[1]);
66 m = [0, top?0:1, 0];
67 r = [90, top?90-AxianovHoleAngle:90+AxianovHoleAngle, 0];
68 translate([0, 0, hole[0]]) rotate(r) mirror(m) tonehole(d=hole[1]);
69 } else {
70 translate([0, 0, hole[0]]) rotate([90, 0, 0]) cylinder(d=hole[1], h=50);
71 }
72 }
73
74 // Ridges to catch seams
75 rotate([0, 0, 110]) translate([0, OuterDiameter/2, 0]) cylinder(h=sortedCuts[0], d=1);
76 rotate([0, 0, -110]) translate([0, OuterDiameter/2, sortedCuts[0]]) cylinder(h=sortedCuts[1]-sortedCuts[0], d=1);
77 rotate([0, 0, 110]) translate([0, OuterDiameter/2, sortedCuts[1]]) cylinder(h=Height, d=1);
78
79 // Inner channel, seems to cause problems with the tenon join being too thin
80 //rotate([0, 0, 140]) translate([0, InnerDiameter/2, 0]) cylinder(h=Height, d=1);
81
82 // Ruby head, and text on the back
83 // This makes previews slow, so we only do it in final renders.
84 if (!$preview) {
85 intersection() {
86 tube(h=50, id=OuterDiameter-0.5, od=OuterDiameter+10);
87 union() {
88 rotate([90, 0, 0]) {
89 linear_extrude(height=OuterDiameter) {
90 translate([-5, 0, 0]) import("ruby.svg");
91 }
92 }
93 translate([0, 0, 8]) {
94 rotate([90, 0, 180]) {
95 linear_extrude(height=OuterDiameter) {
96 text(Text, halign="center");
97 }
98 }
99 }
100 }
101 }
102 }
103 }
104}
105
106sectionedCuts = concat([-1000], sortedCuts, [1000]);
107if (Section == -1) {
108 whistle();
109} else {
110 tenon(h=sectionedCuts[Section], od=OuterDiameter, depth=TenonDepth, top=true) {
111 tenon(h=sectionedCuts[Section+1], od=OuterDiameter, depth=TenonDepth, top=false) {
112 whistle();
113 }
114 }
115}