From d8d54ded0d5d46bf227a5335244e8780db2892ad Mon Sep 17 00:00:00 2001 From: JIJINBEI <87472238+jijinbei@users.noreply.github.com> Date: Wed, 14 Oct 2026 07:47:10 +0900 Subject: [PATCH] Renaming a body rewrites only the references to it (#875) Renaming a body used to replace every string in later features equal to its old name, including sketch text, plane and feature names and expressions. Now only values under keys that name bodies (body, bodies, target, targets, tools) at any depth are rewritten, with a test that a sketch text spelling the body's name stays while a move follows the rename. --- crates/engine/src/cmd/browser.rs | 40 +++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/crates/engine/src/cmd/browser.rs b/crates/engine/src/cmd/browser.rs index b27c8718..a022baf9 100644 --- a/crates/engine/src/cmd/browser.rs +++ b/crates/engine/src/cmd/browser.rs @@ -453,18 +453,24 @@ fn rename_key(d: &mut solidcraft_doc::Document, folder: &str, old: &str, new: &s } } -/// Replace every string equal to `old` with `new`; true when something changed. -fn replace_str(v: &mut Value, old: &str, new: &str, depth: usize) -> bool { +/// The keys whose strings name bodies, alone or in a list (`body`, `targets`…), at any depth of a +/// feature: its own fields and the face, edge and link references inside it. +const BODY_KEYS: [&str; 5] = ["body", "bodies", "target", "targets", "tools"]; + +/// Replace the body name `old` with `new` where a string names a body (the value of one of +/// [`BODY_KEYS`]); other strings equal to it (sketch text, plane and feature names, expressions) +/// stay. True when something changed. +fn replace_body_name(v: &mut Value, old: &str, new: &str, names_body: bool, depth: usize) -> bool { if depth > 64 { return false; } match v { - Value::String(s) if s == old => { + Value::String(s) if names_body && s == old => { *s = new.to_string(); true } - Value::Array(a) => a.iter_mut().fold(false, |acc, x| replace_str(x, old, new, depth + 1) | acc), - Value::Object(o) => o.values_mut().fold(false, |acc, x| replace_str(x, old, new, depth + 1) | acc), + Value::Array(a) => a.iter_mut().fold(false, |acc, x| replace_body_name(x, old, new, names_body, depth + 1) | acc), + Value::Object(o) => o.iter_mut().fold(false, |acc, (k, x)| replace_body_name(x, old, new, BODY_KEYS.contains(&k.as_str()), depth + 1) | acc), _ => false, } } @@ -516,7 +522,7 @@ fn rename_body(s: &mut Session, p: &Value) -> Result { } } let Ok(mut v) = serde_json::to_value(&f.kind) else { continue }; - if replace_str(&mut v, &old, name, 0) { + if replace_body_name(&mut v, &old, name, false, 0) { match serde_json::from_value(v) { Ok(k) => f.kind = k, Err(e) => failed = Some(e.to_string()), @@ -699,6 +705,28 @@ mod tests { assert_eq!(names(&s)[1], b); } + #[test] + fn renaming_a_body_leaves_other_text_with_its_name_alone() { + let mut s = Session::default(); + run(&mut s, "solid.box", json!({"length": 10, "width": 10, "height": 10})); + let body = names(&s)[0].clone(); + run(&mut s, "solid.move", json!({"bodies": [body.clone()], "translate": [0, 0, 5]})); + // Sketch text that happens to spell the body's name is not a reference to it. + run(&mut s, "sketch.create", json!({"plane": "XY", "name": "Label"})); + run(&mut s, "sketch.text", json!({"text": body.clone(), "at": [30, 30], "height": 5})); + run(&mut s, "sketch.finish", json!({})); + run(&mut s, "body.rename", json!({"body": body.clone(), "name": "Lid"})); + let kind = |name: &str| serde_json::to_value(&s.doc.find_feature(name).unwrap().kind).unwrap(); + let label = kind("Label").to_string(); + assert!(label.contains(&format!("\"{body}\"")) && !label.contains("\"Lid\""), "{label}"); + let moved = s.doc.features.iter().find_map(|f| match &f.kind { + solidcraft_doc::FeatureKind::Move { bodies, .. } => Some(bodies.clone()), + _ => None, + }); + assert_eq!(moved, Some(vec!["Lid".to_string()]), "the move follows the rename"); + assert!(s.model.results.iter().all(|r| r.error.is_none())); + } + #[test] fn redefined_sketch_moves_its_extrude() { let mut s = Session::default();