From 3efe34780996f1b4d7613add26f73ca896532791 Mon Sep 17 00:00:00 2001 From: bherbruck Date: Thu, 8 Oct 2026 09:42:08 +0000 Subject: [PATCH] Appearance API for the dialog: faces as {body, point} too, library entries carry a category, appearance.list gives in_design (each look used, with its bodies, faces and components) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL --- crates/engine/src/cmd/inspect.rs | 41 ++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/crates/engine/src/cmd/inspect.rs b/crates/engine/src/cmd/inspect.rs index fa49ac30..652a0510 100644 --- a/crates/engine/src/cmd/inspect.rs +++ b/crates/engine/src/cmd/inspect.rs @@ -332,7 +332,7 @@ fn appearance(s: &mut Session, p: &Value) -> Result { .or_else(|| v.get("point").and_then(crate::params::vec3)) .ok_or_else(|| bad(cmd, "`faces` must list points [x, y, z]"))?; let on = |b: &solvecraft_doc::ModelBody| solvecraft_doc::appearance::face_index_at(b, pt).is_some(); - let body = match str_(p, "body") { + let body = match v.get("body").and_then(Value::as_str).or_else(|| str_(p, "body")) { Some(n) => st.body(n).filter(|b| on(b)).map(|b| b.name.clone()), None => st.bodies.iter().filter(|b| on(b)).min_by(|a, b| dist_to(a, pt).total_cmp(&dist_to(b, pt))).map(|b| b.name.clone()), }; @@ -407,14 +407,51 @@ fn look_json(l: &solvecraft_doc::appearance::Look) -> Value { fn appearance_library(_s: &mut Session, _p: &Value) -> Result { let list: Vec = solvecraft_doc::appearance::LIBRARY .iter() - .map(|(n, c, o)| json!({"name": n, "color": format!("#{:02x}{:02x}{:02x}", c[0], c[1], c[2]), "opacity": o})) + .map(|(n, c, o)| { + let category = n.split(" - ").next().unwrap_or(n); + json!({"name": n, "category": category, "color": format!("#{:02x}{:02x}{:02x}", c[0], c[1], c[2]), "opacity": o}) + }) .collect(); Ok(json!({"appearances": list})) } fn appearance_list(s: &mut Session, p: &Value) -> Result { let a = &s.doc.appearances; + // "In this design": each look used, with where. + let mut used: Vec<(solvecraft_doc::appearance::Look, Vec, Vec, Vec)> = Vec::new(); + fn slot(used: &mut Vec<(solvecraft_doc::appearance::Look, Vec, Vec, Vec)>, l: &solvecraft_doc::appearance::Look) -> usize { + match used.iter().position(|(u, ..)| u == l) { + Some(i) => i, + None => { + used.push((l.clone(), Vec::new(), Vec::new(), Vec::new())); + used.len() - 1 + } + } + } + for (b, l) in &a.bodies { + let i = slot(&mut used, l); + if let Some(u) = used.get_mut(i) { + u.1.push(json!(b)); + } + } + for f in &a.faces { + let i = slot(&mut used, &f.look); + if let Some(u) = used.get_mut(i) { + u.2.push(json!({"body": f.body, "point": f.point})); + } + } + for (c, l) in &a.components { + let i = slot(&mut used, l); + if let Some(u) = used.get_mut(i) { + u.3.push(json!(c)); + } + } + let in_design: Vec = used + .iter() + .map(|(l, b, f, c)| json!({"name": l.name, "color": l.hex(), "opacity": l.opacity, "bodies": b, "faces": f, "components": c})) + .collect(); let mut out = json!({ + "in_design": in_design, "bodies": a.bodies.iter().map(|(b, l)| (b.clone(), look_json(l))).collect::>(), "faces": a.faces.iter().map(|f| json!({"body": f.body, "point": f.point, "appearance": look_json(&f.look)})).collect::>(), "components": a.components.iter().map(|(c, l)| json!({"component": c, "appearance": look_json(l)})).collect::>(),