Combine: a cut keeps a body joined from separate pieces in one body (#2247)

Thanks @H1D.
This commit is contained in:
Artem Sushchev authored and GitHub committed 2026-10-18 06:02:44 -04:00
1 parent 5eb7640b54
commit a4c4b411e5
3 files changed
+53 -8

No files matched your search

@@ -808,3 +808,43 @@ fn multi_target_join_keeps_nearby_bodies_and_original_absorption_order() {
}
}
}
/// Two posts joined into one body while apart, trimmed by a cut that splits neither, then joined
/// to a case they stand on. The cut split the joined posts into "Posts" and "Posts (1)" without
/// saying so (`created_bodies` was empty), and the join then took only "Posts", leaving the other
/// post behind as a body of its own. A cut that does cut a body in two still makes two bodies,
/// and its reply names the new one.
#[test]
fn cut_keeps_posts_joined_while_apart_in_one_body() {
let mut s = Session::default();
let post = |name: &str, x: f64| json!({"length": 10, "width": 10, "height": 30, "center": [x, 0, 25], "operation": "new", "body_name": name});
s.execute("solid.box", &json!({"length": 60, "width": 40, "height": 10, "center": [0, 0, 5], "operation": "new", "body_name": "Case"})).unwrap();
s.execute("solid.box", &post("Posts", -20.0)).unwrap();
s.execute("solid.box", &post("Post2", 20.0)).unwrap();
s.execute("solid.combine", &json!({"target": "Posts", "tools": ["Post2"], "operation": "join"})).unwrap();
s.execute("solid.box", &json!({"length": 80, "width": 20, "height": 10, "center": [0, 0, 40], "operation": "new", "body_name": "Trim"})).unwrap();
let cut = s.execute("solid.combine", &json!({"target": "Posts", "tools": ["Trim"], "operation": "cut"})).unwrap();
// Each post loses its top 5 mm: 10 x 10 x 25 left of each.
let posts = 2.0 * 10.0 * 10.0 * 25.0;
assert_eq!(cut["bodies"], json!(["Case", "Posts"]), "{cut}");
assert_eq!(cut["created_bodies"], json!([]), "{cut}");
assert_eq!(s.model.state().body("Posts").map(|b| b.body.shell_count()), Some(2));
assert!((volume(&s, "Posts") - posts).abs() < 1e-6 * posts, "{}", volume(&s, "Posts"));
let join = s.execute("solid.combine", &json!({"target": "Case", "tools": ["Posts"], "operation": "join"})).unwrap();
assert_eq!(join["bodies"], json!(["Case"]), "{join}");
assert_eq!(join["removed_bodies"], json!(["Posts"]), "{join}");
let want = 60.0 * 40.0 * 10.0 + posts;
let got = volume(&s, "Case");
println!("posts joined while apart, cut, joined: Case {got:.6} mm³, expected {want:.6}");
assert!((got - want).abs() < 1e-6 * want, "{got} vs {want}");
// A cut through the middle of the case still leaves two bodies, and says it made the second.
s.execute("solid.box", &json!({"length": 2, "width": 80, "height": 80, "center": [0, 0, 20], "operation": "new", "body_name": "Saw"})).unwrap();
let saw = s.execute("solid.combine", &json!({"target": "Case", "tools": ["Saw"], "operation": "cut"})).unwrap();
assert_eq!(saw["bodies"], json!(["Case", "Case (1)"]), "{saw}");
assert_eq!(saw["created_bodies"], json!(["Case (1)"]), "{saw}");
let half = (60.0 - 2.0) / 2.0 * 40.0 * 10.0 + posts / 2.0;
for name in ["Case", "Case (1)"] {
assert!((volume(&s, name) - half).abs() < 1e-6 * half, "{name}: {} vs {half}", volume(&s, name));
}
}