use std::sync::Mutex; use nalgebra::Vector3; use scad::*; use constants::*; use selig::Airfoil; use selig::Point; use std::ops::Range; mod constants; mod selig; static PARTS: Mutex> = Mutex::new(Vec::new()); fn main() { let mut scad_file = ScadFile::new(); //let mut parts: Vec = Vec::new(); scad_file.set_detail(50); // cambered airfoil, used in the wing let wing_airfoil: Airfoil = Airfoil::parse(include_str!("../ag24.dat")); println!("name {}, perim {}", wing_airfoil.get_name(), wing_airfoil.perimiter()); println!("desc {}", wing_airfoil.get_description().clone().unwrap_or("no desc".to_string())); //println!("span {:?}", points_in_range(wing_airfoil.get_points()[0..80], 0.0..1.0)); let points = &wing_airfoil.get_points()[0..80]; let perimeter = span_length(points); let span = points_in_range(&Vec::from(points), perimeter * 0.4.. perimeter); let points = scad::PolygonParameters::new(span); register_part(scad!(Polygon(points))); // symetric airfoil, used in the control surfaces let control_airfoil: Airfoil = Airfoil::parse(include_str!("../edgevertical.dat")); let mut wing_transform = scad!(Translate(vec3(0.0,0.0,0.0))); // struts for port in [true,false] { let mut wing = wing(&wing_airfoil, STRUTS/2, WINGSPAN/2.0, CHORD, CHORD * WING_TAPER); wing = scad!(Translate(vec3(0.0, FUSELAGE_GAP,0.0)); wing); if port { wing = scad!(Mirror(vec3(0.0, 1.0, 0.0)); wing); } wing_transform.add_child(wing); } // spars // main wing scad_file.add_object(spar(WINGSPAN+FUSELAGE_GAP*2.0, true)); // "fuselage" scad_file.add_object(scad!(Rotate(-90.0, vec3(0.0, 0.0, 1.0)); spar(LENGTH, false)) ); // rudder let mut rudder = scad!(Rotate(90.0, vec3(1.0, 0.0, 0.0))); rudder.add_child(wing(&control_airfoil, RUDDER_STRUTS, RUDDER_HEIGHT, RUDDER_CHORD, RUDDER_CHORD * RUDDER_TAPER)); rudder.add_child(spar(RUDDER_HEIGHT, false)); rudder = scad!(Translate(vec3(LENGTH-RUDDER_CHORD, 0.0, 0.0)); rudder); scad_file.add_object(rudder); // elevator let mut elevator = scad!(Translate(vec3(LENGTH-ELEVATOR_CHORD, 0.0,0.0))); for port in [true,false] { let mut wing = wing(&control_airfoil, ELEVATOR_STRUTS, ELEVATOR_HEIGHT, ELEVATOR_CHORD, ELEVATOR_CHORD * ELEVATOR_TAPER); wing = scad!(Translate(vec3(0.0, FUSELAGE_GAP,0.0)); wing); if port { wing = scad!(Mirror(vec3(0.0, 1.0, 0.0)); wing); } elevator.add_child(wing); } elevator.add_child(spar(ELEVATOR_HEIGHT*2.0+FUSELAGE_GAP*2.0, true)); scad_file.add_object(elevator); let cardboard = vec3(0.38, 0.26, 0.26); scad_file.add_object(scad!(Color(cardboard); wing_transform)); scad_file.write_to_file(String::from("build/assembly.scad")); for (idx, part) in PARTS.lock().unwrap().clone().into_iter().enumerate() { let mut file = ScadFile::new(); file.set_detail(50); file.add_object(part); file.write_to_file(format!("build/part{idx}.scad")); } let mut allparts = ScadFile::new(); allparts.set_detail(50); for (idx, part) in PARTS.lock().unwrap().to_owned().into_iter().enumerate() { allparts.add_object(scad!(Translate2d(vec2(0.0, INF * idx as f32)); part)); } allparts.write_to_file(format!("build/allparts.scad")); } /// returns a extruded airfoil with the given dimensions fn strut(airfoil: &Airfoil, chord: f32, width: f32) -> ScadObject { let aerofoil = scad::PolygonParameters::new(airfoil.get_points().clone()); let shape = scad!(Polygon(aerofoil)); let extrude = LinExtrudeParams { height: width, center: true, ..Default::default() }; let unit: Vector3 = Vector3::new(1.0, 1.0, 1.0); let scaled = scad!(Scale(unit * chord); shape); register_part(scaled.clone()); let strut = scad!(LinearExtrude(extrude); scaled); let rotated = scad!(Rotate(90.0, vec3(1.0, 0.0, 0.0)); strut); rotated } fn spar(length: f32, center: bool) -> ScadObject { let mut spar = scad!(Union); register_part(scad!(Square(vec2(length, SPAR_SIDE_WIDTH * 3.0)))); for i in 0..3 { let mut panel = scad!(Cube(vec3(SPAR_SIDE_WIDTH, length, CARDBOARD_WIDTH))); if center { panel = scad!(Translate(vec3(0.0, -length/2.0, 0.0)); panel) } let rot = 120.0 * i as f32; spar.add_child(scad!( Rotate(rot, vec3(0.0, 1.0, 0.0)); panel )); } spar } fn register_part(part: ScadObject) { PARTS.lock().unwrap().push(part); } fn lerp(a: f32, b:f32, x:f32) -> f32 { a*(1.0-x) + b*x } fn wing(aerofoil: &Airfoil, struts: usize, length: f32, chord: f32, taper: f32) -> ScadObject { let mut wing = scad!(Translate(vec3(0.0,0.0,0.0))); // struts for strut_idx in 0..struts + 1 { let gap = length/struts as f32; let chord = lerp(chord, taper, strut_idx as f32 / struts as f32); let spacing = strut_idx as f32 * gap; let mut transform = scad!(Translate(vec3(0.0, spacing ,0.0))); let strut = strut(&aerofoil, chord, CARDBOARD_WIDTH); transform.add_child(strut); wing.add_child(transform); } wing } pub fn points_in_range(input: &Vec, range: Range) -> Vec { let mut last_point: Option = None; let mut distance: f32 = 0.0; let mut points: Vec = Vec::new(); for point in input { if let Some(last) = last_point { let span: f32 = (last - point).magnitude(); match (range.contains(&distance), range.contains(&(distance+span))) { (true, true) => { // fully within span points.push(point.clone()); println!("in {span}"); }, (false, true) => { // entering span let undershoot = distance - range.start; let part_out = (span - undershoot) / span; points.push(last.lerp(&point, part_out)); println!("enter"); }, (true, false) => { // exiting span let overshoot = range.end - distance; let part_in = (span - overshoot) / span; points.push(last.lerp(&point, part_in)); }, _ => {}, } distance += span; } last_point = Some(point.clone()); } points } fn span_length(points: &[Point]) -> f32 { let mut last_point: Option = None; let mut distance: f32 = 0.0; for point in points { if let Some(last) = last_point { distance += (last - point).magnitude(); } last_point = Some(point.clone()); } distance }