diff --git a/crates/kernel/examples/step_board_fixture.rs b/crates/kernel/examples/step_board_fixture.rs new file mode 100644 index 0000000000..69691c535e --- /dev/null +++ b/crates/kernel/examples/step_board_fixture.rs @@ -0,0 +1,87 @@ +//! A board-like assembly for issue #883, the worst case for a large STEP import: every +//! component is its own product with a package body and its own pins (nothing shared). +//! +//! ```sh +//! scripts/fetch-smlib.sh +//! SOLIDCRAFT_STEP_PCB_COMPONENTS=600 SOLIDCRAFT_STEP_PCB_PINS=15 \ +//! cargo run --release -p solidcraft-kernel --features smlib --example step_board_fixture -- board.step +//! ``` +//! +//! 600 components of 15 pins give 52 MB. The defaults are 500 and 16; the path defaults to +//! `board.step`. + +#[cfg(not(feature = "smlib"))] +fn main() { + eprintln!("step_board_fixture needs SMLib: run scripts/fetch-smlib.sh, then add --features smlib"); + std::process::exit(2); +} + +#[cfg(feature = "smlib")] +fn main() { + if let Err(e) = run::main() { + eprintln!("step_board_fixture: {e}"); + std::process::exit(1); + } +} + +#[cfg(feature = "smlib")] +mod run { + use solidcraft_geom::Vec3; + use solidcraft_kernel::{Body, ExportBody, ExportProduct, StepHeader, box_solid, cylinder, fillet, step_export_products}; + + /// A count from `SOLIDCRAFT_`, or `default` when it is unset. + fn count(name: &str, default: usize) -> Result { + solidcraft_geom::env::var(name).map_or(Ok(default), |s| s.parse().map_err(|e| format!("SOLIDCRAFT_{name}: {e}"))) + } + + /// A package: a box with its vertical edges and then its top edges rounded (left sharp where + /// a round does not build). + fn package(w: f64, d: f64, h: f64) -> Result { + let b = box_solid(Vec3::ZERO, Vec3::new(w, d, h)).map_err(|e| e.to_string())?; + let vertical = [Vec3::new(0.0, 0.0, h / 2.0), Vec3::new(w, 0.0, h / 2.0), Vec3::new(w, d, h / 2.0), Vec3::new(0.0, d, h / 2.0)]; + let top = [Vec3::new(w / 2.0, 0.0, h), Vec3::new(w, d / 2.0, h), Vec3::new(w / 2.0, d, h), Vec3::new(0.0, d / 2.0, h)]; + let b = fillet(&b, &vertical, 0.3).unwrap_or(b); + Ok(fillet(&b, &top, 0.1).unwrap_or(b)) + } + + pub fn main() -> Result<(), String> { + let components = count("STEP_PCB_COMPONENTS", 500)?; + let pins = count("STEP_PCB_PINS", 16)?; + let path = std::env::args().nth(1).unwrap_or_else(|| "board.step".into()); + let mut owned: Vec> = Vec::new(); + for c in 0..components { + let w = 4.0 + (c % 7) as f64 * 0.37; + let d = 3.0 + (c % 5) as f64 * 0.29; + let mut bodies = vec![package(w, d, 1.2 + (c % 3) as f64 * 0.1)?]; + for p in 0..pins { + let side = if p % 2 == 0 { -0.6 } else { d + 0.6 }; + let x = 0.3 + (p / 2) as f64 * (w - 0.6) / (pins / 2).max(1) as f64 + c as f64 * 1e-4; + bodies.push(cylinder(Vec3::new(x, side, -1.5), Vec3::Z, 0.25, 2.0 + (p % 3) as f64 * 0.01).map_err(|e| e.to_string())?); + } + owned.push(bodies); + } + let mut products: Vec = owned + .iter() + .enumerate() + .map(|(c, bodies)| ExportProduct { + name: format!("U{c}"), + bodies: bodies.iter().enumerate().map(|(k, b)| ExportBody { name: format!("U{c}_{k}"), body: b, color: Some([0.2, 0.2, 0.2]) }).collect(), + children: Vec::new(), + }) + .collect(); + let side = (components as f64).sqrt().ceil() as usize; + let children = (0..components) + .map(|c| { + let (x, y) = ((c % side) as f64 * 12.0, (c / side) as f64 * 10.0); + (c, [[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [x, y, 1.6, 1.0]], format!("U{c}")) + }) + .collect(); + products.push(ExportProduct { name: "Board".into(), bodies: Vec::new(), children }); + let root = products.len() - 1; + let header = StepHeader { file_name: "board.step".into(), ..Default::default() }; + let text = step_export_products(&products, root, &header).map_err(|e| e.to_string())?; + std::fs::write(&path, &text).map_err(|e| format!("{path}: {e}"))?; + println!("wrote {path}: {} bytes, {components} components", text.len()); + Ok(()) + } +} diff --git a/crates/kernel/src/step_in/tests.rs b/crates/kernel/src/step_in/tests.rs index 99049a2c40..9583c575b4 100644 --- a/crates/kernel/src/step_in/tests.rs +++ b/crates/kernel/src/step_in/tests.rs @@ -1545,60 +1545,6 @@ fn faces_with_too_many_bounds_are_refused() { assert!(start.elapsed() < std::time::Duration::from_secs(10), "took {:?}", start.elapsed()); } -/// A board-like assembly for issue #883: `SOLIDCRAFT_STEP_PCB_FIXTURE=path` writes it (with -/// `SOLIDCRAFT_STEP_PCB_COMPONENTS` and `SOLIDCRAFT_STEP_PCB_PINS`; 600 and 15 give 52 MB). Every component is -/// its own product with a package body and its own pins (nothing shared), the worst case for -/// a large import. -#[test] -#[ignore = "manual fixture generator for large STEP imports"] -fn generate_board_assembly_fixture() { - use crate::step_out::{ExportBody, ExportProduct, StepHeader, step_export_products}; - let count: usize = solidcraft_geom::env::var("STEP_PCB_COMPONENTS").map(|s| s.parse().unwrap()).unwrap_or(500); - let pins: usize = solidcraft_geom::env::var("STEP_PCB_PINS").map(|s| s.parse().unwrap()).unwrap_or(16); - let mut owned: Vec> = Vec::new(); - for c in 0..count { - let w = 4.0 + (c % 7) as f64 * 0.37; - let d = 3.0 + (c % 5) as f64 * 0.29; - let mut bodies = vec![fillet_box(w, d, 1.2 + (c % 3) as f64 * 0.1)]; - for p in 0..pins { - let side = if p % 2 == 0 { -0.6 } else { d + 0.6 }; - let x = 0.3 + (p / 2) as f64 * (w - 0.6) / (pins / 2).max(1) as f64 + c as f64 * 1e-4; - bodies.push(cylinder(Vec3::new(x, side, -1.5), Vec3::Z, 0.25, 2.0 + (p % 3) as f64 * 0.01).unwrap()); - } - owned.push(bodies); - } - let mut products: Vec = owned - .iter() - .enumerate() - .map(|(c, bodies)| ExportProduct { - name: format!("U{c}"), - bodies: bodies.iter().enumerate().map(|(k, b)| ExportBody { name: format!("U{c}_{k}"), body: b, color: Some([0.2, 0.2, 0.2]) }).collect(), - children: Vec::new(), - }) - .collect(); - let side = (count as f64).sqrt().ceil() as usize; - let children = (0..count) - .map(|c| { - let (x, y) = ((c % side) as f64 * 12.0, (c / side) as f64 * 10.0); - (c, [[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [x, y, 1.6, 1.0]], format!("U{c}")) - }) - .collect(); - products.push(ExportProduct { name: "Board".into(), bodies: Vec::new(), children }); - let root = products.len() - 1; - let text = step_export_products(&products, root, &StepHeader { file_name: "board.step".into(), ..Default::default() }).unwrap(); - let path = solidcraft_geom::env::var("STEP_PCB_FIXTURE").unwrap_or_else(|| "board.step".into()); - std::fs::write(&path, &text).unwrap(); - println!("wrote {path}: {} bytes, {} components", text.len(), count); -} - -fn fillet_box(w: f64, d: f64, h: f64) -> Body { - let b = box_solid(Vec3::ZERO, Vec3::new(w, d, h)).unwrap(); - let vertical = [Vec3::new(0.0, 0.0, h / 2.0), Vec3::new(w, 0.0, h / 2.0), Vec3::new(w, d, h / 2.0), Vec3::new(0.0, d, h / 2.0)]; - let top = [Vec3::new(w / 2.0, 0.0, h), Vec3::new(w, d / 2.0, h), Vec3::new(w / 2.0, d, h), Vec3::new(0.0, d / 2.0, h)]; - let b = fillet(&b, &vertical, 0.3).unwrap_or(b); - fillet(&b, &top, 0.1).unwrap_or(b) -} - /// Independent boxes as one STEP file (about 3 KB a box). fn boxes_step(count: usize, name: &str) -> String { let bodies: Vec<_> = (0..count)