forked from 1b-agent/fieldcraft-upstream
geometry: tests open a closed hollow box, a post on its void floor and an open tray (#364)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
1 parent
358ed2f54f
commit
8ba59701b4
8 files changed
+488
No files matched your search
@@ -0,0 +1,3 @@
|
|||||||
|
Designs saved by SolidCraft, opened by `tests/void_shells.rs` as a person's file would be. Each
|
||||||
|
`<name>.solidcraft` is `solidcraft-cli run <name>.json --out <name>.solidcraft`; the volumes the
|
||||||
|
tests expect are analytic.
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"command": "solid.box",
|
||||||
|
"params": {
|
||||||
|
"length": 40,
|
||||||
|
"width": 30,
|
||||||
|
"height": 20,
|
||||||
|
"corner": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"body_name": "Case"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "solid.shell",
|
||||||
|
"params": {
|
||||||
|
"faces": [
|
||||||
|
[
|
||||||
|
20,
|
||||||
|
15,
|
||||||
|
20
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"thickness": 2,
|
||||||
|
"direction": "inside",
|
||||||
|
"body": "Case"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "solid.box",
|
||||||
|
"params": {
|
||||||
|
"length": 40,
|
||||||
|
"width": 30,
|
||||||
|
"height": 2,
|
||||||
|
"corner": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
20
|
||||||
|
],
|
||||||
|
"operation": "join",
|
||||||
|
"targets": [
|
||||||
|
"Case"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "solid.cylinder",
|
||||||
|
"params": {
|
||||||
|
"diameter": 6,
|
||||||
|
"height": 5,
|
||||||
|
"base": [
|
||||||
|
8,
|
||||||
|
8,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
"operation": "join",
|
||||||
|
"targets": [
|
||||||
|
"Case"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
{
|
||||||
|
"format": "solidcraft/21",
|
||||||
|
"name": "Untitled",
|
||||||
|
"units": "mm",
|
||||||
|
"params": [],
|
||||||
|
"features": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "Box1",
|
||||||
|
"suppressed": false,
|
||||||
|
"body_names": [
|
||||||
|
"Case"
|
||||||
|
],
|
||||||
|
"param_names": [
|
||||||
|
"d1",
|
||||||
|
"d2",
|
||||||
|
"d3"
|
||||||
|
],
|
||||||
|
"type": "box",
|
||||||
|
"corner": [
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"length": "40",
|
||||||
|
"width": "30",
|
||||||
|
"height": "20",
|
||||||
|
"operation": "new_body"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "Shell1",
|
||||||
|
"suppressed": false,
|
||||||
|
"param_names": [
|
||||||
|
"d4"
|
||||||
|
],
|
||||||
|
"face_names": [
|
||||||
|
"F1:+z"
|
||||||
|
],
|
||||||
|
"type": "shell",
|
||||||
|
"faces": [
|
||||||
|
[
|
||||||
|
20.0,
|
||||||
|
15.0,
|
||||||
|
20.0
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"thickness": "2",
|
||||||
|
"body": "Case"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "Box2",
|
||||||
|
"suppressed": false,
|
||||||
|
"param_names": [
|
||||||
|
"d5",
|
||||||
|
"d6",
|
||||||
|
"d7"
|
||||||
|
],
|
||||||
|
"participants": [
|
||||||
|
"Case"
|
||||||
|
],
|
||||||
|
"type": "box",
|
||||||
|
"corner": [
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
20.0
|
||||||
|
],
|
||||||
|
"length": "40",
|
||||||
|
"width": "30",
|
||||||
|
"height": "2",
|
||||||
|
"operation": "join"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"name": "Cylinder1",
|
||||||
|
"suppressed": false,
|
||||||
|
"param_names": [
|
||||||
|
"d8",
|
||||||
|
"d9"
|
||||||
|
],
|
||||||
|
"participants": [
|
||||||
|
"Case"
|
||||||
|
],
|
||||||
|
"type": "cylinder",
|
||||||
|
"base": [
|
||||||
|
8.0,
|
||||||
|
8.0,
|
||||||
|
2.0
|
||||||
|
],
|
||||||
|
"axis": [
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"radius": "(6 mm) / 2",
|
||||||
|
"height": "5",
|
||||||
|
"operation": "join"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"next_id": 5
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"command": "solid.box",
|
||||||
|
"params": {
|
||||||
|
"length": 40,
|
||||||
|
"width": 30,
|
||||||
|
"height": 20,
|
||||||
|
"corner": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"body_name": "Case"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "solid.shell",
|
||||||
|
"params": {
|
||||||
|
"faces": [
|
||||||
|
[
|
||||||
|
20,
|
||||||
|
15,
|
||||||
|
20
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"thickness": 2,
|
||||||
|
"direction": "inside",
|
||||||
|
"body": "Case"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "solid.box",
|
||||||
|
"params": {
|
||||||
|
"length": 40,
|
||||||
|
"width": 30,
|
||||||
|
"height": 2,
|
||||||
|
"corner": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
20
|
||||||
|
],
|
||||||
|
"operation": "join",
|
||||||
|
"targets": [
|
||||||
|
"Case"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
{
|
||||||
|
"format": "solidcraft/21",
|
||||||
|
"name": "Untitled",
|
||||||
|
"units": "mm",
|
||||||
|
"params": [],
|
||||||
|
"features": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "Box1",
|
||||||
|
"suppressed": false,
|
||||||
|
"body_names": [
|
||||||
|
"Case"
|
||||||
|
],
|
||||||
|
"param_names": [
|
||||||
|
"d1",
|
||||||
|
"d2",
|
||||||
|
"d3"
|
||||||
|
],
|
||||||
|
"type": "box",
|
||||||
|
"corner": [
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"length": "40",
|
||||||
|
"width": "30",
|
||||||
|
"height": "20",
|
||||||
|
"operation": "new_body"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "Shell1",
|
||||||
|
"suppressed": false,
|
||||||
|
"param_names": [
|
||||||
|
"d4"
|
||||||
|
],
|
||||||
|
"face_names": [
|
||||||
|
"F1:+z"
|
||||||
|
],
|
||||||
|
"type": "shell",
|
||||||
|
"faces": [
|
||||||
|
[
|
||||||
|
20.0,
|
||||||
|
15.0,
|
||||||
|
20.0
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"thickness": "2",
|
||||||
|
"body": "Case"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "Box2",
|
||||||
|
"suppressed": false,
|
||||||
|
"param_names": [
|
||||||
|
"d5",
|
||||||
|
"d6",
|
||||||
|
"d7"
|
||||||
|
],
|
||||||
|
"participants": [
|
||||||
|
"Case"
|
||||||
|
],
|
||||||
|
"type": "box",
|
||||||
|
"corner": [
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
20.0
|
||||||
|
],
|
||||||
|
"length": "40",
|
||||||
|
"width": "30",
|
||||||
|
"height": "2",
|
||||||
|
"operation": "join"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"next_id": 4
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"command": "solid.box",
|
||||||
|
"params": {
|
||||||
|
"length": 40,
|
||||||
|
"width": 30,
|
||||||
|
"height": 20,
|
||||||
|
"corner": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"body_name": "Case"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "solid.shell",
|
||||||
|
"params": {
|
||||||
|
"faces": [
|
||||||
|
[
|
||||||
|
20,
|
||||||
|
15,
|
||||||
|
20
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"thickness": 2,
|
||||||
|
"direction": "inside",
|
||||||
|
"body": "Case"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "solid.cylinder",
|
||||||
|
"params": {
|
||||||
|
"diameter": 6,
|
||||||
|
"height": 5,
|
||||||
|
"base": [
|
||||||
|
8,
|
||||||
|
8,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
"operation": "join",
|
||||||
|
"targets": [
|
||||||
|
"Case"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
{
|
||||||
|
"format": "solidcraft/21",
|
||||||
|
"name": "Untitled",
|
||||||
|
"units": "mm",
|
||||||
|
"params": [],
|
||||||
|
"features": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "Box1",
|
||||||
|
"suppressed": false,
|
||||||
|
"body_names": [
|
||||||
|
"Case"
|
||||||
|
],
|
||||||
|
"param_names": [
|
||||||
|
"d1",
|
||||||
|
"d2",
|
||||||
|
"d3"
|
||||||
|
],
|
||||||
|
"type": "box",
|
||||||
|
"corner": [
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"length": "40",
|
||||||
|
"width": "30",
|
||||||
|
"height": "20",
|
||||||
|
"operation": "new_body"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "Shell1",
|
||||||
|
"suppressed": false,
|
||||||
|
"param_names": [
|
||||||
|
"d4"
|
||||||
|
],
|
||||||
|
"face_names": [
|
||||||
|
"F1:+z"
|
||||||
|
],
|
||||||
|
"type": "shell",
|
||||||
|
"faces": [
|
||||||
|
[
|
||||||
|
20.0,
|
||||||
|
15.0,
|
||||||
|
20.0
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"thickness": "2",
|
||||||
|
"body": "Case"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "Cylinder1",
|
||||||
|
"suppressed": false,
|
||||||
|
"param_names": [
|
||||||
|
"d5",
|
||||||
|
"d6"
|
||||||
|
],
|
||||||
|
"participants": [
|
||||||
|
"Case"
|
||||||
|
],
|
||||||
|
"type": "cylinder",
|
||||||
|
"base": [
|
||||||
|
8.0,
|
||||||
|
8.0,
|
||||||
|
2.0
|
||||||
|
],
|
||||||
|
"axis": [
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"radius": "(6 mm) / 2",
|
||||||
|
"height": "5",
|
||||||
|
"operation": "join"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"next_id": 4
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
//! 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}");
|
||||||
|
}
|
||||||
Reference in new issue
Block a user