/*whistle stack vawt
Holland Hopson
2021
Generates a stack of whistles with a specific fundamental pitch and each segment tuned an interval above the segment below
Includes Vertical Axis Wind Turbine (VAWT) foils which cause the whistle to rotate in a more regular and predictable way.
Code for VAWT by Patrick Gaston, 2/2019
Note: requires hopson_attachment_bar.scad
Adjust Hz to tune whistle and interval to determine the relative tuning of each segment in the stack
*/
include ;
$fn = 100; //make high resolution
//customize these variables
Hz = 1350; //fundamental frquency of whistle
interval = 0.666666; //specifies reduction of size of each segment and thus the sounding interval of each whistle segment.
//sample values:
//0.5 = octaves
//0.666666 = perfect 5th 3/2
//0.75 = perfect 4th 4/3
//0.8 = pythagorean major 3rd 5/4
//0.88888 = pythagorean second 9/8
//etc.
minSegmentHeight = 15; //set the minimum height of the smallest segment.
//this effectively controls the number of whistle segments in the stack
//<15mm is typically too small to sound, but this value depends on the resolution of your printer
foilHeightCoeff = 2.0; //(1.0 - ???) adjust height of foil to match height of stack. Stack height is calculated recursively, so we can't know how tall to make the foils until after we've rendered the stack! 1 = height of base whistle module
diameter = 30; //width of object. 30mm works well here. Could be larger, but a stack of whistles requires significant material to print.
z=45; //amount of rotation in degrees for each whistle segment. Adjust this value to ensure whistle openings aren't blocked by the VAWT airfoils
//constants and calculations
speed_of_sound = 343.; //
height = ((speed_of_sound / (Hz * 8.))*1000.); //multiply by 1000 to get mm
sectionHeight = height; //initialize first section. defined as special variable so it can be updated
//echo(height, " mm");
//echo(height * 0.1, " cm"); //print height in cm
offset = 0.001; //constant to use when ofsetting an object so it joins another object rather than resting against it
//variables for attachment bar. These overwrite the local variables in attachment_bar.scad
bar_radius = 2.5; //was 1.5mm, but was prone to breakage
cavity_diameter = 20; //At least 10mm, but less than 40mm (or diameter of whistle)
cavity_depth = 2*(bar_radius + 5); //2mm to provide clearance for smaller swivel. 5mm for larger swivel. May need to be closer to 10mm deep. Multiply by 2 because we're using a sphere, so this number is the diameter
/*
Code for VAWT by Patrick Gaston, 2/2019. I make no warranty as to the correctness of the
calculations or algorithm. Use at your own risk.
Intended for use in VAWT (vertical axis wind turbine) design.
Based on NACA4 calculations detailed on http://airfoiltools.com and http://www.dept.aoe.vt.edu/~mason/Mason_f/CAtxtAppA.pdf
*/
//Foils
NACA1 = 4; //maximum camber as percentage of chord. Example: 4 = 0.04 or 4% of chord. Typical range 0-9.
NACA2 = 0; //position of maximum camber as percentage of chord, x 10. Typical range 0-9. Example: 3 = 30% of the distance from front to back of chord.
NACA34 = 10; //thickness of foil as % of chord. Typical range 1-40.
chord = 20; //mm length of foil chord. width of foil
height_foil = height*foilHeightCoeff; //mm vertical extrusion.
count_foil = 2; //1 for single foil, 3-4 is typical for VAWT
AOA = 0; //Angle of attack
radius_turbine = diameter * 0.65; //0 for centered on origin. height_foil/2 for typical VAWT.
twist_helix = 180/count_foil; //0 = straight foil, 360/count_foil for helix is typical for VAWT
point_count = 61; //Number of points of foil profile to plot. More means larger output file, higher chance of program crash.
vertical_slices = 10; //Vertical resolution of blade. More means larger output file, higher chance of program crash.
resolution = 64; //For circular parts, (= $fn) (inner hub)
//epsilon = 0.01; //For showing negative spaces in preview mode
M = NACA1/100;
P = NACA2/10;
T = NACA34/100;
bottom_point_count = floor(point_count /2);
top_point_count = point_count - bottom_point_count;
//Arms, hub
width_arms = 0.25 * chord;
thickness_arms = 2;
////attachment bar top - skip this because we can't know the height until we're done with the recursion
// //translate([0,0,(cavity_depth/2)]){ //move to top of cylinder
// translate([0,0,sectionHeight+(cavity_depth/2)]){ //move to top of cylinder
// #attachmentCavity();
// attachmentBar();
// }
//attachment bar bottom
translate([0,0,(-1*cavity_depth/2)+offset]){ //move to bottom of cylinder
rotate([180,0,0]){
attachmentCavity();
attachmentBar();
}
}
module whistle_stack(sectionHeight) //s = section height
{
//constants from module parameters. Hard-coded here
//diameter = 40; //this was hard-coded at 40mm
slit_width=sectionHeight*0.125; //this was hard-coded at 5.5mm
wall_thickness=2;
top_bottom_thickness=1;
angle=50; //angle of slit
//constants and calculations
//echo(sectionHeight);
slit_length = sectionHeight/3; //one third of height of cylinder
translate([0,0,sectionHeight/2]) //position bottom of object on origin
{
difference()
{
union() { //objects to be added to the design
//outer shell
cylinder(h = sectionHeight, r = diameter/2, center=true);
}
union() { //objects to be subtracted from the design
//inner space
cylinder(h = sectionHeight-(2*top_bottom_thickness), r = (diameter/2)-wall_thickness, center=true);
//slit
translate([0, -diameter*0.5, 0]) { //move slit so it pokes out the front side
rotate([0, 0, angle]) { //rotating too far eats into the inside wall
cube([slit_width,diameter*0.65,slit_length], center=true);
}
}
//channel to direct air to edge
translate([0, -diameter*0.5-(slit_length/2)+(wall_thickness*0.25), 0]) {
rotate([90, 90, angle*2]) { //rotate cylinder so rounded edge is approx. perpendicular to slit
scale([2,1,1]) //squash cylinder so it contacts the surface more broadly
cylinder(r = slit_length/2, h = slit_length);
}
}
}
}
}
if(sectionHeight>minSegmentHeight) //conditional to call recursion as long as height is large enough
///this was 10, but the top whistle was too small to sound
translate([0,0,sectionHeight-offset]){ //move base of next branch of tree
rotate([0,0,z]) whistle_stack(sectionHeight*interval); //recursion
echo("section height ", sectionHeight);
}
}
module Hub_and_Arms()
{
difference()
{
union()
{
for (current_arm = [1:1:count_foil])
{
rotate([0,0, current_arm * 360/count_foil])
rotate([90,0,90])
linear_extrude(height = radius_turbine, scale = 1)
{
translate([-width_arms/2,0,0])
difference()
{
square([width_arms, thickness_arms]);
translate([width_arms - thickness_arms,0,0])
difference()
{
square(thickness_arms);
circle(thickness_arms, $fn = resolution);
}
translate([thickness_arms,0,0])
mirror([1,0,0])
difference()
{
square(thickness_arms);
circle(thickness_arms, $fn = resolution);
}
}
}
}
}
union() //items to subtract from the design here
{
//carve out space for the whistle stack, minus a small amount so the arms intersect the body of the whistle
cylinder(h = height-(2*2), r = (diameter/2)-2, center=true);
}
}
}
module Complete_Foil()
{
for (i = [1:1:count_foil])
{
echo("A");
start_height = 0;
subdivide_rotation = 0;
local_height_foil = height_foil;
local_twist_helix = twist_helix;
color("PaleTurquoise")
Foilmaker(i, start_height, subdivide_rotation, local_height_foil, local_twist_helix, vertical_slices);
}
//translate([0,0,cavity_depth*-0.5])
Hub_and_Arms(); //draw bottom arms
translate([0,0,height_foil]) //lower the top of the foil to account for attachment bar module
rotate([180,0,twist_helix])
{
Hub_and_Arms(); //draw top arms
}
}
module Foilmaker(i, start_height, subdivide_rotation, local_height_foil, local_twist_helix, vertical_slices)
{
translate([0,0, start_height])
rotate([0,0,(i-1)*360/(count_foil) + subdivide_rotation])
linear_extrude(height = local_height_foil, center = false, convexity = 10, twist = -local_twist_helix, slices = vertical_slices, scale = 1)
translate([radius_turbine,chord/2,0])
rotate([0,0,-90])
rotate([0,0,AOA])
mirror([0,1,0])
{
top_Xs = chord * [for (current_point = [0:1:top_point_count]) X_Upper((1 - cos(current_point * 180/top_point_count))/2, (1 - cos(current_point * 180/top_point_count))/2 < P)];
top_Ys = chord * [for (current_point = [0:1:top_point_count]) Y_Upper((1 - cos(current_point * 180/top_point_count))/2, (1 - cos(current_point * 180/top_point_count))/2 < P)];
bottom_Xs = chord * [for (current_point = [bottom_point_count:-1:1]) X_Lower((1 - cos(current_point * 180/bottom_point_count))/2, (1 - cos(current_point * 180/bottom_point_count))/2 < P)];
bottom_Ys = chord * [for (current_point = [bottom_point_count:-1:1]) Y_Lower((1 - cos(current_point * 180/bottom_point_count))/2, (1 - cos(current_point * 180/bottom_point_count))/2 < P)] ;
all_Xs = concat(top_Xs, bottom_Xs);
all_Ys = concat(top_Ys, bottom_Ys);
polygon([for (current_point = [0:1:point_count]) [all_Xs[current_point], all_Ys[current_point]]]);
}
}
function CamberY(x, test) = test ?
(M/pow(P,2) * (2*P*x - pow(x,2))) : (M/pow(1-P,2) * (1 - 2*P + 2*P*x - pow(x,2)));
function Gradient(x, test) = test ?
(2*M/pow(P,2) * (P - x)) : (2*M/pow(1-P,2) * (P - x));
function ThicknessY(x) = T * (1.4845*pow(x,0.5) - 0.63 * x - 1.758*pow(x,2) + 1.4215*pow(x,3) - 0.5075*pow(x,4));
function Theta(x, test) = atan(Gradient(x, test));
function X_Upper(x, test) = x - ThicknessY(x)/2 * sin(Theta(x,test));
function X_Lower(x, test) = x + ThicknessY(x)/2 * sin(Theta(x,test));
function Y_Upper(x, test) = CamberY(x, test) + ThicknessY(x) * cos(Theta(x,test));
function Y_Lower(x, test) = CamberY(x, test) - ThicknessY(x) * cos(Theta(x,test));
//build the whistle and VAWT foils
whistle_stack(height);
Complete_Foil(); //VAWT foil