89 lines
3.9 KiB
Rust
89 lines
3.9 KiB
Rust
//! 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());
|
||
}
|
||
}
|