421 lines
12 KiB
Rust
421 lines
12 KiB
Rust
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;
|
|
|
|
static PARTS: Mutex<Vec<ScadObject>> = Mutex::new(Vec::new());
|
|
|
|
fn main() {
|
|
let mut scad_file = ScadFile::new();
|
|
//let mut parts: Vec<ScadObject> = 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.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().iter().cloned().enumerate() {
|
|
allparts.add_object(scad!(Translate2d(vec2(0.0, INF * idx as f32)); part));
|
|
}
|
|
allparts.write_to_file("build/allparts.scad".to_string());
|
|
}
|
|
|
|
/// A physical object
|
|
struct Construct {
|
|
/// The object in 3d space
|
|
pub visualization: ScadObject,
|
|
/// the 2d parts that make up the object
|
|
pub parts: Vec<ScadObject>,
|
|
}
|
|
|
|
impl Construct {
|
|
fn new(visualization: ScadObject, parts: Vec<ScadObject>) -> 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<ScadObject>) {
|
|
(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(0.0, wing_config.length - CARDBOARD_WIDTH, 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, 0.1..0.6),
|
|
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,
|
|
center: true,
|
|
..Default::default()
|
|
};
|
|
|
|
let unit: Vector3<f32> = Vector3::new(1.0, 1.0, 1.0);
|
|
let scaled = scad!(Scale(unit * chord); shape);
|
|
parts.push(scaled.clone());
|
|
let strut = scad!(LinearExtrude(extrude); scaled);
|
|
let rotated = scad!(Rotate(90.0, vec3(1.0, 0.0, 0.0)); strut);
|
|
Construct {
|
|
visualization: rotated,
|
|
parts,
|
|
}
|
|
}
|
|
|
|
fn extrude_strut(
|
|
shape: ScadObject,
|
|
strut_hole: ScadObject,
|
|
width: f32,
|
|
chord: f32,
|
|
register: bool,
|
|
) -> 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<f32> = Vector3::new(1.0, 1.0, 1.0);
|
|
let scaled = scad!(Scale(unit * chord); shape);
|
|
if register {
|
|
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 topspar_negative(airfoil: &SeligFile, chord: f32, range: Range<f32>) -> 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<ScadObject> = 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);
|
|
}
|
|
register_part(mask.clone());
|
|
mask
|
|
}
|
|
|
|
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_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<ScadObject> = 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, 0.1..0.6);
|
|
let extruded = extrude_strut(shape.clone(), scad!(Union), CARDBOARD_WIDTH, chord, false);
|
|
|
|
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,
|
|
}
|