From d191ae571e84088dae8b0899b29dd4342ea1bfb4 Mon Sep 17 00:00:00 2001 From: bherbruck Date: Thu, 8 Oct 2026 18:43:55 +0000 Subject: [PATCH] Pending scenario: a root-active sketch on a face of a moved component projects the face where it is (today at the body's own coordinates); harness check linked_world_range Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL --- crates/ui-egui/src/scenario.rs | 18 +++ .../sketch_on_moved_component_face.json | 112 ++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 crates/ui-egui/tests/scenarios/sketch_on_moved_component_face.json diff --git a/crates/ui-egui/src/scenario.rs b/crates/ui-egui/src/scenario.rs index f79ec2cb..1474f8b1 100644 --- a/crates/ui-egui/src/scenario.rs +++ b/crates/ui-egui/src/scenario.rs @@ -686,6 +686,24 @@ pub fn check(h: &mut Harness, e: &Value) -> Result<(), String> { let got = m["bodies"][0]["volume_mm3"].as_f64().unwrap_or(f64::NAN); approx(got, &v["volume"], &format!("{name} volume"))?; } + "linked_world_range" => { + // {"x"|"y"|"z": [min, max]}: where the active sketch's projected (linked) points lie + // in the world (±1e-3 mm). + let si = h.call("engine.execute", json!({"command": "sketch.inspect", "params": {}}))["result"].clone(); + let pts: Vec = si["points"].as_array().into_iter().flatten().filter(|p| !p["link"].is_null()).cloned().collect(); + if pts.is_empty() { + return Err("the sketch has no projected points".into()); + } + for (i, axis) in ["x", "y", "z"].iter().enumerate() { + let Some(want) = v.get(*axis) else { continue }; + let xs: Vec = pts.iter().filter_map(|p| p["world"][i].as_f64()).collect(); + let (lo, hi) = xs.iter().fold((f64::INFINITY, f64::NEG_INFINITY), |(a, b), &x| (a.min(x), b.max(x))); + let (wl, wh) = (want[0].as_f64().unwrap_or(f64::NAN), want[1].as_f64().unwrap_or(f64::NAN)); + if (lo - wl).abs() > 1e-3 || (hi - wh).abs() > 1e-3 { + return Err(format!("projected points span {axis} {lo:.3}..{hi:.3}, want {wl}..{wh}")); + } + } + } "sketch_world" => { // {sketch, point, at: [x,y,z]}: where a sketch point is in the world (±1e-3 mm). let si = h.call("engine.execute", json!({"command": "sketch.inspect", "params": {"sketch": v["sketch"]}}))["result"].clone(); diff --git a/crates/ui-egui/tests/scenarios/sketch_on_moved_component_face.json b/crates/ui-egui/tests/scenarios/sketch_on_moved_component_face.json new file mode 100644 index 00000000..999f0c3e --- /dev/null +++ b/crates/ui-egui/tests/scenarios/sketch_on_moved_component_face.json @@ -0,0 +1,112 @@ +[ + { + "pending": "a root-active sketch on a face of a moved component lands in body-local coordinates (solvecraft-dialogs)" + }, + { + "note": "A box in a new component, the component moved +100 in x, the root active: a sketch on the box's top face projects the face's edges where the face is (x 100-140), not at the body's own coordinates (x 0-40)." + }, + { + "call": "engine.execute", + "params": { + "command": "FusionCreateNewComponentCommand", + "params": {} + } + }, + { + "call": "engine.execute", + "params": { + "command": "PrimitiveBox", + "params": { + "length": 40, + "width": 30, + "height": 10, + "corner": [ + 0, + 0, + 0 + ] + } + } + }, + { + "call": "engine.execute", + "params": { + "command": "component.activate", + "params": { + "component": 0 + } + } + }, + { + "call": "engine.execute", + "params": { + "command": "occurrence.move", + "params": { + "occurrence": 2, + "translate": [ + 100, + 0, + 0 + ] + } + } + }, + { + "call": "ui.view", + "params": { + "view": "fit" + } + }, + { + "expect": { + "bbox": { + "body": "Body1", + "min": [ + 100, + 0, + 0 + ], + "max": [ + 140, + 30, + 10 + ] + } + } + }, + { + "start": "SketchCreate" + }, + { + "click": { + "world": [ + 120, + 15, + 10 + ] + } + }, + { + "expect": { + "sketching": true + } + }, + { + "expect": { + "linked_world_range": { + "x": [ + 100, + 140 + ], + "y": [ + 0, + 30 + ], + "z": [ + 10, + 10 + ] + } + } + } +]