Files
OneCAD/crates/engine/tests/shell_regressions.rs
T
2026-10-16 01:19:42 -04:00

89 lines
3.9 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Shell regressions found by the robustness fuzzer (tests/robustness/goldens.json).
//!
//! The box-on-box designs below have a saddle corner: where the top box's left face meets the
//! lower box's back face, four planar faces meet with convex and concave edges in turn (the
//! top box overhangs the lower box's back face). A shell splits that corner into two corners
//! joined by a new edge.
//!
//! Lower box `[0, 25] × [0, 17.5] × [0, 35]`, top box `[7.5, 22.5] × [0, 17.5 + o] × [35, 42.5]`.
//! With the bottom face open, a shell of thickness `t` leaves this cavity (for `o < 17.5 − 2t`):
//!
//! - in the lower box: `[t, 25 − t] × [t, 17.5 − t] × [0, 35 − t]`
//! - in the top box, over the lower box: `[7.5 + t, 22.5 − t] × [t, 17.5 − t] × [35 − t, 42.5 − t]`
//! - in the overhang: `[7.5 + t, 22.5 − t] × [17.5 − t, 17.5 + o − t] × [35 + t, 42.5 − t]`
//!
//! The shell's volume is the solid's volume less those three boxes.
use serde_json::json;
use solidcraft_engine::Session;
fn run(s: &mut Session, id: &str, p: serde_json::Value) {
if let Err(e) = s.execute(id, &p) {
panic!("{id} {p}: {e}");
}
}
/// The box on a box, overhanging the lower box's back face by `o`.
fn box_on_box(o: f64) -> Session {
let mut s = Session::default();
run(&mut s, "solid.box", json!({"height": 35.0, "length": 25.0, "width": 17.5}));
run(&mut s, "solid.box", json!({"corner": [7.5, 0.0, 35.0], "height": 7.5, "length": 15.0, "operation": "join", "width": 17.5 + o}));
s
}
/// The shell's volume with the bottom face open (see the module comment).
fn open_bottom_shell_volume(o: f64, t: f64) -> f64 {
let solid = 25.0 * 17.5 * 35.0 + 15.0 * (17.5 + o) * 7.5;
let lower = (25.0 - 2.0 * t) * (17.5 - 2.0 * t) * (35.0 - t);
let above = (15.0 - 2.0 * t) * (17.5 - 2.0 * t) * 7.5;
let overhang = (15.0 - 2.0 * t) * o * (7.5 - 2.0 * t);
solid - lower - above - overhang
}
/// Seed 83 (minimised): the overhang is 2.5 mm, and the shell opens the top box's left face.
/// The shell refused the saddle corner (`a corner whose faces don't meet the same way`).
#[test]
fn shell_box_on_box_overhanging_saddle_corner() {
let mut s = box_on_box(2.5);
run(&mut s, "solid.shell", json!({"faces": [[7.5, 17.5, 38.75]], "thickness": 1.5}));
sound(&s);
}
/// The same saddle corner with the bottom open: every face round the corner moves, and the
/// volume is exact.
#[test]
fn shell_saddle_corner_volume_is_exact() {
for t in [1.0, 1.5] {
let mut s = box_on_box(2.5);
run(&mut s, "solid.shell", json!({"faces": [[12.5, 8.75, 0.0]], "thickness": t}));
sound(&s);
let (got, want) = (volume(&s), open_bottom_shell_volume(2.5, t));
assert!((got - want).abs() < 1e-6 * want, "thickness {t}: shell volume {got}, want {want}");
}
}
/// An overhang (1 mm) thinner than the wall (1.5 mm): the overhang's cavity is only 1 mm deep.
/// A corner landing outside the solid, or a wall left thin, would change the volume.
#[test]
fn shell_saddle_corner_with_an_overhang_thinner_than_the_wall() {
let mut s = box_on_box(1.0);
run(&mut s, "solid.shell", json!({"faces": [[12.5, 8.75, 0.0]], "thickness": 1.5}));
sound(&s);
let (got, want) = (volume(&s), open_bottom_shell_volume(1.0, 1.5));
assert!((got - want).abs() < 1e-6 * want, "shell volume {got}, want {want}");
}
fn volume(s: &Session) -> f64 {
s.model.state().bodies.iter().map(|b| solidcraft_kernel::measure(&b.body).map(|m| m.volume).unwrap_or(f64::NAN)).sum()
}
fn sound(s: &Session) {
let errors: Vec<_> = s.model.results.iter().filter_map(|r| r.error.clone()).collect();
assert!(errors.is_empty(), "{errors:?}");
let st = s.model.state();
assert_eq!(st.bodies.len(), 1, "one body");
for b in &st.bodies {
assert_eq!(b.body.shell_count(), 1, "one shell");
assert!(b.body.validity().is_empty(), "{:?}", b.body.validity());
}
}