use cadrum::{DVec3, Solid};
use std::f64::consts::TAU;
use std::fs::File;
use std::path::{Path, PathBuf};
use crate::Result;
use crate::vmec::VmecData;
macro_rules! emit {
($vmec:expr, $index_s:expr, $scale:expr, $output_dir:expr, [$(($m:literal, $n:literal)),* $(,)?]) => {{
$(
for &(periodic, tag) in &[(true, "periodic"), (false, "nonperiodic")] {
let path = $output_dir.join(format!("plasma_M{}_N{}_{}.step", $m, $n, tag));
println!(
"Building plasma at M={}, N={}, periodic={} → {}",
$m, $n, periodic, path.display()
);
let solid = build::<$m, $n>($vmec, $index_s, $scale, periodic)?;
write_step(&solid, &path)?;
}
)*
}};
}
pub fn run(input: &Path, output_dir: &Path, scale: f64) -> Result<()> {
println!("Loading VMEC: {}", input.display());
let vmec = VmecData::load(input)?;
let index_s = vmec.s_grid.len() - 1;
let s_lcfs = vmec.s_grid[index_s];
println!(
" ns = {}, mnmax = {}, using index_s = {} (s = {})",
vmec.s_grid.len(),
vmec.mode_poloidal.len(),
index_s,
s_lcfs
);
let (min_n, max_n) = vmec
.mode_toroidal
.iter()
.fold((f64::INFINITY, f64::NEG_INFINITY), |(lo, hi), &x| {
(lo.min(x), hi.max(x))
});
let (min_m, max_m) = vmec
.mode_poloidal
.iter()
.fold((f64::INFINITY, f64::NEG_INFINITY), |(lo, hi), &x| {
(lo.min(x), hi.max(x))
});
println!(
" xn range [{min_n}, {max_n}] → phi Nyquist requires M ≥ {}",
2.0 * max_n.abs().max(min_n.abs())
);
println!(
" xm range [{min_m}, {max_m}] → theta Nyquist requires N ≥ {}",
2.0 * max_m.abs().max(min_m.abs())
);
std::fs::create_dir_all(output_dir)
.map_err(|e| format!("create_dir_all {}: {}", output_dir.display(), e))?;
emit!(
&vmec,
index_s,
scale,
output_dir,
[
(32, 16),
(48, 24),
(64, 32),
(128, 48),
(240, 60),
(480, 60),
]
);
println!("Done.");
Ok(())
}
fn build<const M: usize, const N: usize>(
vmec: &VmecData,
index_s: usize,
scale: f64,
periodic: bool,
) -> Result<Solid> {
let grid: [[DVec3; N]; M] = std::array::from_fn(|i| {
let phi = TAU * (i as f64) / (M as f64);
let (sp, cp) = phi.sin_cos();
std::array::from_fn(|j| {
let theta = TAU * (j as f64) / (N as f64);
let rz = vmec.index_rz(index_s, theta, phi);
DVec3::new(rz.r * cp * scale, rz.r * sp * scale, rz.z * scale)
})
});
Solid::bspline(grid, periodic)
.map_err(|e| format!("bspline M={M}, N={N}, periodic={periodic}: {:?}", e).into())
}
fn write_step(solid: &Solid, output: &Path) -> Result<()> {
let colored = solid.clone().color("cyan");
let mut f = File::create(output)
.map_err(|e| format!("create {}: {}", output.display(), e))?;
cadrum::write_step(std::iter::once(&colored), &mut f)
.map_err(|e| format!("write_step {}: {:?}", output.display(), e))?;
let stl_path: PathBuf = output.with_extension("stl");
let mut fstl = File::create(&stl_path)
.map_err(|e| format!("create {}: {}", stl_path.display(), e))?;
let objects = [colored];
cadrum::mesh(&objects, 0.1)
.and_then(|m| m.write_stl(&mut fstl))
.map_err(|e| format!("write stl {}: {:?}", stl_path.display(), e))?;
Ok(())
}