From fd12325d9078e2070620bb17f8752fb5c77a744d Mon Sep 17 00:00:00 2001 From: HiroKenEz <59478073+HiroKenEz@users.noreply.github.com> Date: Wed, 14 Oct 2026 20:43:15 +0200 Subject: [PATCH] body.rename: follow the body in later features' participants (a primitive's targets) (#1108) (#1159) A primitive's `targets` live in the feature's participants beside its kind; renaming a body now renames them too, so a box or cylinder cutting the renamed body no longer fails with unknown body. --- crates/engine/src/cmd/browser.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/crates/engine/src/cmd/browser.rs b/crates/engine/src/cmd/browser.rs index 3e5faf19..35876873 100644 --- a/crates/engine/src/cmd/browser.rs +++ b/crates/engine/src/cmd/browser.rs @@ -511,6 +511,11 @@ fn rename_body(s: &mut Session, p: &Value) -> Result { // Later features refer to bodies by name. let mut failed = None; for f in d.features.iter_mut().filter(|f| f.id != fid) { + // The bodies a join/cut/intersect may change (a primitive's `targets`) are kept beside + // the feature's kind, not in it (#1108). + for b in f.participants.iter_mut().filter(|b| **b == old) { + *b = name.to_string(); + } if let FeatureKind::Sketch { plane, .. } = &mut f.kind { solidcraft_doc::sheet::rename_plane_sheet_body(plane, &old, name); } @@ -711,6 +716,29 @@ mod tests { assert_eq!(names(&s)[1], b); } + /// #1108: a primitive's `targets` are its participants, kept beside its kind; a rename + /// follows them, as it follows the names inside the kind (a combine's target and tools). + #[test] + fn renamed_body_keeps_the_primitives_that_target_it() { + let mut s = Session::default(); + let total = |s: &mut Session| run(s, "inspect.measure", json!({}))["total"]["volume_mm3"].as_f64().unwrap(); + run(&mut s, "solid.box", json!({"length": 20, "width": 20, "height": 10, "name": "Block", "body_name": "A"})); + run(&mut s, "solid.cylinder", json!({"diameter": 5, "height": 12, "base": [5, 5, -1], "operation": "cut", "targets": ["A"], "name": "Hole"})); + run(&mut s, "solid.box", json!({"length": 5, "width": 5, "height": 5, "corner": [20, 0, 0], "operation": "new", "body_name": "Tab"})); + run(&mut s, "solid.combine", json!({"target": "A", "tools": ["Tab"], "operation": "join", "name": "Joined"})); + let want = total(&mut s); + run(&mut s, "body.rename", json!({"body": "A", "name": "B"})); + assert!( + s.model.results.iter().all(|r| r.error.is_none()), + "{:?}", + s.model.results.iter().filter_map(|r| r.error.clone()).collect::>() + ); + assert_eq!(s.doc.find_feature("Hole").unwrap().participants, vec!["B".to_string()]); + assert!((total(&mut s) - want).abs() < 1e-6); + run(&mut s, "edit.undo", json!({})); + assert_eq!(s.doc.find_feature("Hole").unwrap().participants, vec!["A".to_string()]); + } + #[test] fn renaming_a_body_leaves_other_text_with_its_name_alone() { let mut s = Session::default();