Closed twitchyliquid64 closed 3 weeks ago
I think this feature would be very nice!
Out of curiosity, for this application why not just create a shell using builder::try_wire_homotopy between the root and the wing tip?
Out of curiosity, for this application why not just create a shell using builder::try_wire_homotopy between the root and the wing tip?
Wait that works? I thought try_wire_homotopy
was for closing a path into a face in 2d.
No, I think that is truck_modeling::builder::homotopy
, truck_modeling::builder::try_wire_homotopy
is different:
https://docs.rs/truck-modeling/latest/truck_modeling/builder/fn.try_wire_homotopy.html
Since I will need this functionality for a project anyway, I have written some toy code to illustrate how to use it to make a wing. For the 'proper' version I would interpolate the airfoil points into a bezier spline. Forgive all the .unwrap(), this is just a concept:
use truck_modeling::*;
use truck_modeling::cgmath::{Deg, Matrix4, Quaternion, Vector3};
use truck_stepio::out;
use truck_topology::compress::CompressedSolid;
fn main() {
let outline = parse_airfoil(AIRFOIL);
let new_wing = make_wing(outline, 0.5, 4.0, 1.2, 0.3, Deg(0.0), Deg(10.0));
// Compress the wing solid
let compressed: CompressedSolid<Point3, Curve, Surface> = new_wing.compress();
// Prepare the STEP file string
let step_string = out::CompleteStepDisplay::new(
out::StepModel::from(&compressed),
out::StepHeaderDescriptor {
origination_system: "wing_new".to_owned(),
..Default::default()
},
)
.to_string();
// Write the STEP file
let output_step_file = "wing_new.step";
let mut step_file = std::fs::File::create(output_step_file).unwrap();
std::io::Write::write_all(&mut step_file, step_string.as_ref()).unwrap();
println!("wing model exported to {}", output_step_file);
}
// Function to apply twist to the airfoil at a given span position
fn apply_twist(airfoil: &Wire, twist_angle: Deg<f64>) -> Wire {
let twist_quaternion = Quaternion::from_angle_z(Rad::from(twist_angle));
let twist_mat = Matrix4::from(twist_quaternion);
builder::transformed(&airfoil.clone(), twist_mat)
}
// Function to create the 3D wing solid
fn make_wing(airfoil: Wire, sweep: f64, span: f64, root_chord: f64, tip_chord: f64, root_twist: Deg<f64>, tip_twist: Deg<f64>) -> Solid {
// Root section: Apply root twist and scale
let mut root_airfoil = apply_twist(&airfoil, root_twist);
let root_scale_mat = Matrix4::from_nonuniform_scale(root_chord, root_chord, 1.0);
root_airfoil = builder::transformed(&root_airfoil, root_scale_mat);
// Tip section: Apply tip twist, scale, and sweep
let mut tip_airfoil = apply_twist(&airfoil, tip_twist);
let tip_scale_mat = Matrix4::from_nonuniform_scale(tip_chord, tip_chord, 1.0);
tip_airfoil = builder::transformed(&tip_airfoil, tip_scale_mat);
let sweep_matrix = Matrix4::from_translation(Vector3::new(sweep, 0.0, span));
tip_airfoil = builder::transformed(&tip_airfoil, sweep_matrix);
// Create the shell between the root and tip using `try_wire_homotopy`
let mut shell = builder::try_wire_homotopy(&root_airfoil, &tip_airfoil).unwrap();
// Attach closing faces to the root and tip
shell.push(builder::try_attach_plane(&[root_airfoil]).unwrap());
*shell.last_mut().unwrap() = shell.last().unwrap().inverse(); // Ensure correct orientation
shell.push(builder::try_attach_plane(&[tip_airfoil]).unwrap());
// Return the completed solid
Solid::try_new(vec![shell]).unwrap()
}
// Function to parse an airfoil from an array of 2D points
fn parse_airfoil(points: [[f64; 2]; 90]) -> Wire {
let mut vertices = Vec::new();
for point in points {
vertices.push(Vertex::new(Point3::new(point[0], point[1], 0.0)));
}
vertices[90/2..].reverse(); // Reverse the lower surface vertices
let mut wire = Wire::new();
for pair in vertices.windows(2) {
wire.push_back(builder::line(&pair[0], &pair[1]));
}
wire.push_back(builder::line(&vertices.last().unwrap(), &vertices[0]));
wire
}
// 12% JOUKOWSKI AIRFOIL (joukowsk-il) from airfoiltools.com
const AIRFOIL: [[f64; 2]; 90] = [
[0.0000000, 0.0000000],
[0.0012180, 0.0063290],
[0.0048660, 0.0125800],
[0.0109260, 0.0186800],
[0.0193690, 0.0245530],
[0.0301540, 0.0301310],
[0.0432270, 0.0353490],
[0.0585260, 0.0401490],
[0.0759760, 0.0444790],
[0.0954910, 0.0482940],
[0.1169780, 0.0515580],
[0.1403300, 0.0542450],
[0.1654350, 0.0563370],
[0.1921690, 0.0578250],
[0.2204040, 0.0587090],
[0.2500000, 0.0590000],
[0.2808140, 0.0587170],
[0.3126970, 0.0578860],
[0.3454910, 0.0565430],
[0.3790390, 0.0547300],
[0.4131760, 0.0524950],
[0.4477360, 0.0498910],
[0.4825500, 0.0469750],
[0.5174500, 0.0438060],
[0.5522640, 0.0404480],
[0.5868240, 0.0369610],
[0.6209610, 0.0334080],
[0.6545080, 0.0298470],
[0.6873030, 0.0263360],
[0.7191850, 0.0229270],
[0.7500000, 0.0196670],
[0.7795960, 0.0165980],
[0.8078310, 0.0137550],
[0.8345650, 0.0111680],
[0.8596700, 0.0088550],
[0.8830220, 0.0068300],
[0.9045080, 0.0050990],
[0.9240240, 0.0036570],
[0.9414740, 0.0024960],
[0.9567730, 0.0015970],
[0.9698460, 0.0009370],
[0.9806310, 0.0004850],
[0.9890740, 0.0002060],
[0.9951340, 0.0000620],
[0.9987820, 0.0000080], // removed middle 2 points manually for ease
[0.0012180, -0.0063290],
[0.0048660, -0.0125800],
[0.0109260, -0.0186800],
[0.0193690, -0.0245530],
[0.0301540, -0.0301310],
[0.0432270, -0.0353490],
[0.0585260, -0.0401490],
[0.0759760, -0.0444790],
[0.0954920, -0.0482940],
[0.1169780, -0.0515580],
[0.1403300, -0.0542450],
[0.1654350, -0.0563370],
[0.1921690, -0.0578250],
[0.2204040, -0.0587090],
[0.2500000, -0.0590000],
[0.2808140, -0.0587170],
[0.3126970, -0.0578860],
[0.3454910, -0.0565430],
[0.3790390, -0.0547300],
[0.4131760, -0.0524950],
[0.4477360, -0.0498910],
[0.4825500, -0.0469750],
[0.5174500, -0.0438060],
[0.5522640, -0.0404480],
[0.5868240, -0.0369610],
[0.6209610, -0.0334080],
[0.6545090, -0.0298470],
[0.6873030, -0.0263360],
[0.7191850, -0.0229270],
[0.7500000, -0.0196670],
[0.7795960, -0.0165980],
[0.8078310, -0.0137550],
[0.8345650, -0.0111680],
[0.8596700, -0.0088550],
[0.8830220, -0.0068300],
[0.9045090, -0.0050990],
[0.9240240, -0.0036570],
[0.9414740, -0.0024960],
[0.9567730, -0.0015970],
[0.9698460, -0.0009370],
[0.9806310, -0.0004850],
[0.9890740, -0.0002060],
[0.9951340, -0.0000620],
[0.9987820, -0.0000080],
[1.0000000, 0.0000000]
];
I hope that helps illustrate how to use try_wire_homotopy
. Here's the resulting wing:
Fly safe!
Wow thats incredible! try_wire_homotopy
is super cool, and your solution is super cool!
One q: Why do you need to reverse the bottom surface vertex? vertices[90/2..].reverse(); // Reverse the lower surface vertices
Also feel free to send a PR to https://github.com/twitchyliquid64/airfoil-to-stl with your approach, otherwise I'll try and update it myself if I have a moment!
Happy to hear you like it!
The thing with the bottom surface messed me up for a while, see #77. For a shell to be closed all it's normal vectors need to point in a consistent direction. So if you copy and move a face, both faces will point in the same direction. And when you perform the homotopy you get a solid where (for instance) the bottom face normal is inward, while the top face normal is facing outward.
I'm not sure if I have the time to contribute to your repo, but if I do I'll for sure make a PR!
I ended up getting around to it, thanks!
I didnt have to do anything messy with the vertices, simply inverting one of the faces was sufficient:
let mut base: Shell = builder::try_wire_homotopy(&bottom, &top).unwrap();
// Inverted bc opposite faces must have opposite normals
base.push(builder::try_attach_plane(&[bottom]).unwrap().inverse());
base.push(builder::try_attach_plane(&[top]).unwrap());
https://github.com/twitchyliquid64/airfoil-to-stl/blob/main/src/main.rs#L163-L167
Hi! love your work so far!!
Consider an airfoil:
I can use
tsweep
to create a fully-symmetrical airfoil, but no such method exists in thebuilder
module to make a non-symmetrical airfoil like shown above (where one end tapers, like a commerical jet).Can support for such an extrusion be added?
This is what I am using right now, but I don't think its fully correct:
So far: https://github.com/twitchyliquid64/airfoil-to-stl