use std::sync::Mutex; use constants::*; use nalgebra::Vector3; use scad::*; use selig::Point; use selig::SeligFile; use selig::Span; use std::ops::Range; mod constants; mod selig; fn main() { let mut scad_file = ScadFile::new(); //let mut parts: Vec = Vec::new(); let mut parts = Vec::new(); scad_file.set_detail(50); // cambered airfoil, used in the wing let wing_airfoil: SeligFile = SeligFile::parse(include_str!("../ag24.dat")); println!( "name {}, perim {}", wing_airfoil.get_name(), wing_airfoil.get_points().length() ); println!( "desc {}", wing_airfoil .get_description() .clone() .unwrap_or("no desc".to_string()) ); // symetric airfoil, used in the control surfaces let control_airfoil: SeligFile = SeligFile::parse(include_str!("../edgevertical.dat")); let wing_transform = mirrored_wing(&wing_airfoil, &WING); // spars // "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))); let (mut struts,mut spar) = wing( &control_airfoil, &RUDDER, SparType::Center, ); rudder.add_child(struts.visualization); rudder.add_child(spar.visualization); parts.append(struts.parts.as_mut()); parts.append(spar.parts.as_mut()); rudder = scad!(Translate(vec3(LENGTH-RUDDER_CHORD, 0.0, 0.0)); rudder); scad_file.add_object(rudder); // elevator let mut elevator = mirrored_wing(&wing_airfoil, &ELEVATOR); scad_file.add_object(scad!(Translate(vec3(LENGTH - ELEVATOR_CHORD, 0.0, 0.0)); elevator.visualization)); parts.append(&mut elevator.parts); let cardboard = vec3(0.38, 0.26, 0.26); scad_file.add_object(scad!(Color(cardboard); wing_transform.visualization)); scad_file.write_to_file(String::from("build/assembly.scad")); for (idx, part) in parts.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.into_iter().enumerate() { allparts.add_object(scad!(Translate2d(vec2(0.0, INF * idx as f32)); part)); } allparts.write_to_file("build/allparts.scad".to_string()); let mut test = ScadFile::new(); let test_airfoil = SeligFile::parse(include_str!("../m1-il.dat")); let wing = wing(&test_airfoil, &RUDDER, SparType::Top); test.add_object(scad!(NamedColor("clear-blue".to_string()); wing.0.visualization)); test.add_object(scad!(NamedColor("clear-red".to_string()); wing.1.visualization)); test.write_to_file(String::from("build/testwing.scad")); } /// A physical object struct Construct { /// The object in 3d space pub visualization: ScadObject, /// the 2d parts that make up the object pub parts: Vec, } impl Construct { fn new(visualization: ScadObject, parts: Vec) -> Self { Construct { visualization, parts, } } /// A construct with no parts fn cosmetic(visualization: ScadObject) -> Self { Construct { visualization, parts: Vec::new() } } /// Convert to tuple /// (visualization, parts) fn tup(self) -> (ScadObject, Vec) { (self.visualization, self.parts) } } fn mirrored_wing(wing_airfoil: &SeligFile, wing_config: &WingConfig) -> Construct { let mut parts = Vec::new(); let mut wing_transform = scad!(Translate(vec3(0.0, 0.0, 0.0))); // struts let mut symetric_spar = scad!(Union); for port in [true, false] { let (mut strut, spar) = wing(wing_airfoil, wing_config, SparType::Top); let mut wing = strut.visualization; let mut top_spar_neg = spar.visualization; parts.append(strut.parts.as_mut()); let transform = Translate(vec3(0.0, FUSELAGE_GAP,0.0)); wing = scad!(transform.clone(); wing); top_spar_neg = scad!(transform; top_spar_neg); if port { let mirror = Mirror(vec3(0.0, 1.0, 0.0)); wing = scad!(mirror.clone(); wing); top_spar_neg = scad!(mirror; top_spar_neg); } symetric_spar.add_child(top_spar_neg); wing_transform.add_child(wing); } // fuselage affixment point symetric_spar.add_child(centered_cube( vec3(CHORD, FUSELAGE_GAP * 2.0, 1.0), (false, true, false), )); parts.push(scad!(Projection(false); symetric_spar.clone())); wing_transform.add_child(symetric_spar); Construct::new( wing_transform.clone() , parts) } /// Returns (struts, spar) fn wing(wing_airfoil: &SeligFile, wing_config: &WingConfig, spar: SparType) -> (Construct, Construct) { let (wing, wing_parts) = wing_struts( wing_airfoil, &wing_config, &spar, ).tup(); // TODO: other spar types let spar = match spar { SparType::None => { scad!(Union) }, SparType::Top => { let top_spar = topwing_spar( wing_airfoil, wing_config, ); let mut spar = scad!(Difference; top_spar); spar.add_child(wing.clone()); spar }, SparType::Center => { // TODO: taper let mut mid_spar = scad!(Hull); let bottom = centered_cube(vec3(wing_config.chord * (MIDSPAR_RANGE.end - MIDSPAR_RANGE.start) , CARDBOARD_WIDTH, CARDBOARD_WIDTH), (false, false, true)); let mut top = scad!(Translate(vec3(wing_config.taper * MIDSPAR_RANGE.start * wing_config.chord - MIDSPAR_RANGE.start * wing_config.chord, wing_config.length, 0.0))); top.add_child(scad!(Scale(vec3(wing_config.taper, 1.0, 1.0)); bottom.clone())); mid_spar.add_child(top); mid_spar.add_child(bottom); mid_spar = scad!(Translate(vec3(MIDSPAR_RANGE.start * wing_config.chord, 0.0, 0.0)); mid_spar); let mut spar = scad!(Difference; mid_spar); spar.add_child(wing.clone()); spar }, }; let spar_flat = scad!(Projection(false); spar.clone()); let strut = Construct::new(wing, wing_parts); let spar = Construct::new(spar, vec![spar_flat]); (strut, spar) } enum SparType { /// No spar, just struts None, /// Spar along the top of the wing Top, /// Spar that passes through each strut Center, } /// returns a extruded airfoil with the given dimensions fn strut(airfoil: &SeligFile, chord: f32, width: f32, spar: &SparType) -> Construct { let aerofoil = scad::PolygonParameters::new(airfoil.get_points().to_vec()); let shape = scad!(Polygon(aerofoil)); let mut parts = Vec::new(); let strut_hole = { match spar { SparType::Top => topspar_negative(airfoil, chord, TOPSPAR_RANGE), SparType::Center => { scad!(Translate2d(vec2(MIDSPAR_RANGE.start, 0.0)); centered_square(vec2(MIDSPAR_RANGE.end - MIDSPAR_RANGE.start, CARDBOARD_WIDTH/ chord), (false,true)) ) } SparType::None => { scad!(Union) } } }; let shape = shape; let chord = chord; let mut strut_shape = scad!(Difference); strut_shape.add_child(shape); strut_shape.add_child(strut_hole); let shape = strut_shape; let extrude = LinExtrudeParams { height: width, ..Default::default() }; let unit: Vector3 = Vector3::new(1.0, 1.0, 1.0); let scaled = scad!(Scale(unit * chord); shape); parts.push(scaled.clone()); let mut strut = scad!(LinearExtrude(extrude); scaled); strut = scad!(Translate(vec3(0.0,0.0,-width)); strut); strut = scad!(Rotate(90.0, vec3(1.0, 0.0, 0.0)); strut); Construct { visualization: strut, parts, } } fn extrude_strut( shape: ScadObject, strut_hole: ScadObject, width: f32, chord: f32, ) -> ScadObject { let mut strut_shape = scad!(Difference); strut_shape.add_child(shape); strut_shape.add_child(strut_hole); let shape = strut_shape; 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); let strut = scad!(LinearExtrude(extrude); scaled); let strut = scad!(Translate(vec3(0.0, 0.0, -width/2.0)); strut); let rotated = scad!(Rotate(90.0, vec3(1.0, 0.0, 0.0)); strut); rotated } fn topspar_negative(airfoil: &SeligFile, chord: f32, range: Range) -> ScadObject { let points = airfoil.get_points(); let points = &points[0..points.len() / 2]; let perimeter = points.to_vec().length(); let span = points .to_vec() .points_in_range(perimeter * (1.0 - range.end)..perimeter * (1.0 - range.start)); let mut mask = scad!(Union); let mut last: Option = None; for point in span { let mut current = scad!(Square(vec2(0.001 / chord, CARDBOARD_WIDTH / chord))); current = scad!(Translate2d(point - vec2(0.0, CARDBOARD_WIDTH/chord)); current); if let Some(last_mask) = last { let mut hull = scad!(Hull); hull.add_child(current.clone()); hull.add_child(last_mask); mask.add_child(hull); } last = Some(current); } mask } // TODO: shift to trangle design 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 lerp(a: f32, b: f32, x: f32) -> f32 { a * (1.0 - x) + b * x } fn wing_struts( aerofoil: &SeligFile, config: &WingConfig, spar: &SparType, ) -> Construct { let mut wing = scad!(Translate(vec3(0.0, 0.0, 0.0))); let mut parts = Vec::new(); // struts for strut_idx in 0..config.struts + 1 { let gap = config.length / config.struts as f32; let chord = lerp(config.chord, config.chord * config.taper, strut_idx as f32 / config.struts as f32); let spacing = strut_idx as f32 * gap; let mut transform = scad!(Translate(vec3(0.0, spacing, 0.0))); let mut strut = strut(aerofoil, chord, CARDBOARD_WIDTH, &spar); transform.add_child(strut.visualization); parts.append(strut.parts.as_mut()); wing.add_child(transform); } Construct { visualization: wing, parts } } fn topwing_spar( aerofoil: &SeligFile, config: &WingConfig ) -> ScadObject { let mut wing = scad!(Hull); let mut last_segment: Option = None; let mut pre_vis = scad!(Union); // struts for strut_idx in 0..config.struts + 1 { let gap = config.length / config.struts as f32; let chord = lerp(config.chord, config.chord * config.taper, strut_idx as f32 / config.struts as f32); let spacing = strut_idx as f32 * gap; let shape = topspar_negative(aerofoil, chord, TOPSPAR_RANGE); let extruded = extrude_strut(shape.clone(), scad!(Union), CARDBOARD_WIDTH, chord); let mut transform = scad!(Translate(vec3(0.0, spacing, 0.0))); transform.add_child(extruded); // in betweens if let Some(last) = last_segment { let mut hull = scad!(Hull); let extrude = LinExtrudeParams { height: 0.01, center: true, ..Default::default() }; let mut last_t = scad!(Translate(vec3(0.0, -gap, 0.0))); last_t.add_child(scad!(LinearExtrude(extrude.clone()); last)); //hull.add_child(last_t); //hull.add_child(scad!(LinearExtrude(extrude); shape.clone())); transform.add_child(hull); } pre_vis.add_child(transform); last_segment = Some(shape); } wing.add_child(pre_vis); wing } /// Parameters for what would be half of a symmetrical (port/starboard) wing /// All distance units are millimeters pub struct WingConfig { pub length: f32, /// half of the wingspan pub chord: f32, pub taper: f32, /// chord at wingtip in relation to the chord at the root pub struts: usize, }