forked from 1b-agent/fieldcraft-upstream
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
70 lines
2.9 KiB
Rust
70 lines
2.9 KiB
Rust
//! A solid with a void shell (a box shelled, then capped) opens with every shell and everything
|
||
//! joined to them (#364); a feature that fails to rebuild is reported naming the body it changes.
|
||
|
||
use fieldcraft_geometry::{Design, open_design};
|
||
|
||
const PI: f64 = std::f64::consts::PI;
|
||
/// The outer box, 40 × 30 × 20 mm, and its cavity with 2 mm walls and floor.
|
||
const BOX: f64 = 40.0 * 30.0 * 20.0;
|
||
const CAVITY: f64 = 36.0 * 26.0 * 18.0;
|
||
const LID: f64 = 40.0 * 30.0 * 2.0;
|
||
/// A post ⌀6 × 5 mm standing on the floor.
|
||
const POST: f64 = PI * 9.0 * 5.0;
|
||
|
||
fn open(bytes: &[u8]) -> Design {
|
||
let design = open_design(bytes).unwrap();
|
||
assert!(design.errors.is_empty(), "{:?}", design.errors);
|
||
assert_eq!(design.bodies.len(), 1);
|
||
design
|
||
}
|
||
|
||
fn check(design: &Design, volume: f64, faces: usize) {
|
||
let body = design.body("Case").unwrap();
|
||
let m = body.measure().unwrap();
|
||
assert!((m.volume - volume).abs() < 1e-5 * volume, "{} vs {volume}", m.volume);
|
||
assert_eq!(m.faces, faces);
|
||
let mut names: Vec<&str> = body.faces().iter().map(|f| f.name.as_str()).collect();
|
||
assert_eq!(names.len(), faces);
|
||
names.sort_unstable();
|
||
names.dedup();
|
||
assert_eq!(names.len(), faces, "every face has its own name");
|
||
// The mesh sees the same solid: every face tessellated, the same enclosed volume.
|
||
let mesh = body.tessellate(0.01).unwrap();
|
||
let mut tags = mesh.tri_face.clone();
|
||
tags.sort_unstable();
|
||
tags.dedup();
|
||
assert_eq!(tags.len(), faces);
|
||
assert!((mesh.enclosed_volume() - volume).abs() < 1e-3 * volume, "{} vs {volume}", mesh.enclosed_volume());
|
||
}
|
||
|
||
#[test]
|
||
fn a_closed_hollow_box_keeps_its_void_shell() {
|
||
// Six outside faces and six cavity faces.
|
||
check(&open(include_bytes!("designs/hollow-box.solidcraft")), BOX - CAVITY + LID, 12);
|
||
}
|
||
|
||
#[test]
|
||
fn a_post_joined_to_the_void_floor_comes_through() {
|
||
// The hollow box's twelve faces, the post's top and its side (two halves).
|
||
check(&open(include_bytes!("designs/hollow-box-post.solidcraft")), BOX - CAVITY + LID + POST, 15);
|
||
}
|
||
|
||
#[test]
|
||
fn a_post_in_an_open_tray_comes_through() {
|
||
// Five outside faces, the rim, five cavity faces and the post's three.
|
||
check(&open(include_bytes!("designs/open-tray-post.solidcraft")), BOX - CAVITY + POST, 14);
|
||
}
|
||
|
||
#[test]
|
||
fn a_feature_that_fails_names_the_body_it_changes() {
|
||
let text = std::str::from_utf8(include_bytes!("designs/hollow-box-post.solidcraft")).unwrap();
|
||
assert!(text.contains(r#""height": "5""#));
|
||
let broken = text.replace(r#""height": "5""#, r#""height": "0""#);
|
||
let design = open_design(broken.as_bytes()).unwrap();
|
||
let [error] = design.errors.as_slice() else { panic!("{:?}", design.errors) };
|
||
assert_eq!(error.feature, "Cylinder1");
|
||
assert_eq!(error.bodies, ["Case"]);
|
||
let warning = error.warning();
|
||
assert!(warning.starts_with(r#"Cylinder1 failed to build, so body "Case" may differ from the design: "#), "{warning}");
|
||
}
|