use cadrum::{BSplineEnd, Color, DVec3, ProfileOrient, Solid, Wire};
use std::fs::File;
use std::io::{BufWriter, Write};
use std::path::{Path, PathBuf};
use crate::Result;
use crate::coils;
pub fn run(
input: &Path,
output: &Path,
width: f64,
thickness: f64,
toroidal_extent: f64,
) -> Result<()> {
println!("Parsing coils: {}", input.display());
let filaments = coils::parse(input)?;
println!(
" Parsed {} filaments (nfp={})",
filaments.coils.len(),
filaments.nfp
);
if toroidal_extent < 360.0 {
println!(
" [note] --toroidal-extent {} specified but coil filtering not implemented in this version",
toroidal_extent
);
}
println!(
"Building {} coil solids (width = {} m, thickness = {} m)...",
filaments.coils.len(),
width,
thickness
);
let mut solids: Vec<Solid> = Vec::with_capacity(filaments.coils.len());
let mut coil_points: Vec<Vec<DVec3>> = Vec::with_capacity(filaments.coils.len());
for (idx, raw_pts) in filaments.coils.iter().enumerate() {
match build_one(raw_pts, width, thickness) {
Ok((s, pts)) => {
solids.push(s);
coil_points.push(pts);
}
Err(e) => {
eprintln!(" [warn] coil #{} sweep failed: {}", idx, e);
}
}
}
println!(" {} / {} coils succeeded", solids.len(), filaments.coils.len());
if solids.is_empty() {
return Err("no coil solids produced".into());
}
if let Some(parent) = output.parent() {
if !parent.as_os_str().is_empty() {
std::fs::create_dir_all(parent)
.map_err(|e| format!("create_dir_all {}: {}", parent.display(), e))?;
}
}
println!("Writing STEP: {}", output.display());
let colored: Vec<Solid> = solids.into_iter().map(|s| s.color("orange")).collect();
let mut f = File::create(output)
.map_err(|e| format!("create {}: {}", output.display(), e))?;
cadrum::write_step(colored.iter(), &mut f)
.map_err(|e| format!("write_step failed: {:?}", e))?;
let csv_path = output.with_extension("csv");
println!("Writing CSV: {}", csv_path.display());
let csv_file = File::create(&csv_path)
.map_err(|e| format!("create {}: {}", csv_path.display(), e))?;
let mut csv = BufWriter::new(csv_file);
for pts in &coil_points {
for p in pts {
writeln!(csv, "{},{},{}", p.x, p.y, p.z)
.map_err(|e| format!("write csv: {}", e))?;
}
}
csv.flush().map_err(|e| format!("flush csv: {}", e))?;
println!("Done.");
Ok(())
}
pub fn build_sector(
input: &Path,
width: f64,
thickness: f64,
remove_half_span_tau: f64,
) -> Result<Vec<Solid>> {
let filaments = coils::parse(input)?;
let remove_rad = std::f64::consts::TAU * remove_half_span_tau;
let remove_all = remove_half_span_tau >= 0.5;
let n_total = filaments.coils.len();
let color = |i: usize| Color::from_hsv(i as f32 / n_total as f32, 1.0, 1.0);
let mut solids: Vec<Solid> = Vec::new();
let mut kept = 0usize;
for (idx, raw_pts) in filaments.coils.iter().enumerate() {
if remove_all {
continue;
}
let com: DVec3 = raw_pts.iter().copied().sum::<DVec3>() / (raw_pts.len() as f64);
if com.y.atan2(com.x).abs() <= remove_rad {
continue; }
match build_one(raw_pts, width, thickness) {
Ok((s, _pts)) => {
solids.push(s.color(color(idx)));
kept += 1;
}
Err(e) => eprintln!(" [warn] coil #{} sweep failed: {}", idx, e),
}
}
println!(
" magnet::build_sector: {} / {} coils outside |phi| <= {}*tau (rainbow by coil idx)",
kept, n_total, remove_half_span_tau,
);
Ok(solids)
}
fn build_one(raw_pts: &[DVec3], width: f64, thickness: f64) -> Result<(Solid, Vec<DVec3>)> {
use cadrum::Edge;
if raw_pts.len() < 4 {
return Err(format!("too few points ({})", raw_pts.len()).into());
}
let spine_pts: Vec<DVec3> = raw_pts.to_vec();
let spine = Edge::bspline(&spine_pts, BSplineEnd::NotAKnot)
.map_err(|e| format!("bspline failed: {:?}", e))?;
const AUX_SCALE: f64 = 1.1;
let com: DVec3 = spine_pts.iter().copied().sum::<DVec3>() / (spine_pts.len() as f64);
let aux_pts: Vec<DVec3> = spine_pts
.iter()
.map(|p| com + (*p - com) * AUX_SCALE)
.collect();
let aux_spine = Edge::bspline(&aux_pts, BSplineEnd::NotAKnot)
.map_err(|e| format!("aux bspline failed: {:?}", e))?;
let w = width;
let t = thickness;
let profile = Edge::polygon(&[
DVec3::new(w / 2.0, t / 2.0, 0.0),
DVec3::new(w / 2.0, -t / 2.0, 0.0),
DVec3::new(-w / 2.0, -t / 2.0, 0.0),
DVec3::new(-w / 2.0, t / 2.0, 0.0),
])
.map_err(|e| format!("polygon failed: {:?}", e))?;
let tangent = spine.start_tangent();
let origin = spine.start_point();
let outward = origin - com;
let profile = profile.align_z(tangent, outward).translate(origin);
let mut dump_pts: Vec<DVec3> = Vec::with_capacity(profile.len() + spine_pts.len());
for e in profile.iter() {
dump_pts.push(e.start_point());
}
dump_pts.extend_from_slice(&spine_pts);
let coil = Solid::sweep(
profile.iter(),
std::iter::once(&spine),
ProfileOrient::Torsion
)
.map_err(|e| format!("sweep failed: {:?}", e))?;
Ok((coil, dump_pts))
}