From ab574952b120cf36966109c6691b3113a398fd29 Mon Sep 17 00:00:00 2001 From: bherbruck Date: Fri, 9 Oct 2026 22:05:01 +0000 Subject: [PATCH] Hidden origin planes and axes are not pickable: Create Sketch only brings the Origin back when no body is shown; origin and construction planes give way to a face or profile at the same depth (a shelled box's floor where the XZ plane crosses it). Scenarios hidden_origin_planes_not_pickable and face_wins_over_origin_plane Fixes #31 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT --- crates/ui-egui/src/lib.rs | 6 +- crates/ui-egui/src/viewport.rs | 9 +- .../face_wins_over_origin_plane.json | 81 +++++++++++++++++ .../hidden_origin_planes_not_pickable.json | 91 +++++++++++++++++++ 4 files changed, 183 insertions(+), 4 deletions(-) create mode 100644 crates/ui-egui/tests/scenarios/face_wins_over_origin_plane.json create mode 100644 crates/ui-egui/tests/scenarios/hidden_origin_planes_not_pickable.json diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 161f3122..df20645c 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -547,8 +547,12 @@ impl SolveApp { } /// Is the origin widget shown (always while picking a sketch plane)? + /// Origin planes and axes are drawn and pickable. Hidden ones only come back while Create + /// Sketch has nothing else to offer (no body shown). pub fn origin_visible(&self) -> bool { - self.ui.show_origin || self.dialog.as_ref().is_some_and(|d| matches!(d.kind, dialogs::Kind::Sketch)) + self.ui.show_origin + || (self.dialog.as_ref().is_some_and(|d| matches!(d.kind, dialogs::Kind::Sketch)) + && !self.session.world_state().bodies.iter().any(|b| !self.ui.hidden_bodies.contains(&b.name))) } pub fn set_status(&mut self, s: impl Into, error: bool) { diff --git a/crates/ui-egui/src/viewport.rs b/crates/ui-egui/src/viewport.rs index b120dbb2..86d274d1 100644 --- a/crates/ui-egui/src/viewport.rs +++ b/crates/ui-egui/src/viewport.rs @@ -406,7 +406,9 @@ pub fn origin_planes(app: &SolveApp) -> Vec<(&'static str, Vec3, [Vec3; 4])> { if !app.origin_visible() { return Vec::new(); } - let creating = app.dialog.as_ref().is_some_and(|d| matches!(d.kind, crate::dialogs::Kind::Sketch)); + // While the Origin is hidden, Create Sketch may bring the planes back (empty design): then + // every plane; otherwise only those not hidden one by one. + let creating = !app.ui.show_origin && app.dialog.as_ref().is_some_and(|d| matches!(d.kind, crate::dialogs::Kind::Sketch)); origin_quads(origin_size(app.cam.half_height())) .into_iter() .filter(|(n, _, _)| creating || !app.ui.hidden_origin.iter().any(|h| h == n)) @@ -740,14 +742,15 @@ pub fn pick(app: &SolveApp, proj: &Proj, pos: Pos2) -> Vec { if let Some(f) = bestf { surf.push(f); } + // Planes give way to a face or profile at the same depth (a floor on the XZ plane). for (name, _, q) in origin_planes(app) { if let Some(t) = ray_quad(o, d, &q) { - surf.push((t, Hit::Plane { name: name.into(), point: o + d * t })); + surf.push((t + slack, Hit::Plane { name: name.into(), point: o + d * t })); } } for (name, _, q) in construction_quads(app) { if let Some(t) = ray_quad(o, d, &q) { - surf.push((t, Hit::Plane { name, point: o + d * t })); + surf.push((t + slack, Hit::Plane { name, point: o + d * t })); } } surf.sort_by(|a, b| a.0.total_cmp(&b.0)); diff --git a/crates/ui-egui/tests/scenarios/face_wins_over_origin_plane.json b/crates/ui-egui/tests/scenarios/face_wins_over_origin_plane.json new file mode 100644 index 00000000..18f2f4fd --- /dev/null +++ b/crates/ui-egui/tests/scenarios/face_wins_over_origin_plane.json @@ -0,0 +1,81 @@ +[ + { + "note": "#31: a face wins a tie with an origin plane through it (a shelled box's floor where the XZ plane crosses it)." + }, + { + "call": "engine.execute", + "params": { + "command": "solid.box", + "params": { + "corner": [ + -10, + -10, + 0 + ], + "length": 20, + "width": 20, + "height": 6 + } + } + }, + { + "call": "engine.execute", + "params": { + "command": "solid.shell", + "params": { + "faces": [ + [ + 0, + 0, + 6 + ] + ], + "thickness": 2 + } + } + }, + { + "expect": { + "volume": [ + 1376, + 0.001 + ] + } + }, + { + "call": "ui.view", + "params": { + "view": "iso" + } + }, + { + "start": "sketch.create" + }, + { + "click": { + "world": [ + 0, + 0, + 2 + ] + } + }, + { + "expect": { + "sketching": true + } + }, + { + "expect": { + "query": { + "command": "sketch.inspect", + "path": [ + "plane", + "origin", + 2 + ], + "value": 2 + } + } + } +] \ No newline at end of file diff --git a/crates/ui-egui/tests/scenarios/hidden_origin_planes_not_pickable.json b/crates/ui-egui/tests/scenarios/hidden_origin_planes_not_pickable.json new file mode 100644 index 00000000..9a6de35d --- /dev/null +++ b/crates/ui-egui/tests/scenarios/hidden_origin_planes_not_pickable.json @@ -0,0 +1,91 @@ +[ + { + "note": "#31: with the Origin hidden, its planes are not pickable in Create Sketch (a body is shown, so they don't come back)." + }, + { + "call": "engine.execute", + "params": { + "command": "solid.box", + "params": { + "corner": [ + 6, + -15, + 0 + ], + "length": 4, + "width": 4, + "height": 4 + } + } + }, + { + "call": "engine.execute", + "params": { + "command": "solid.box", + "params": { + "corner": [ + -15, + 10, + -5 + ], + "length": 3, + "width": 3, + "height": 3 + } + } + }, + { + "call": "ui.view", + "params": { + "view": "fit" + } + }, + { + "call": "ui.view", + "params": { + "view": "iso" + } + }, + { + "start": "sketch.create" + }, + { + "click": { + "plane": "YZ" + } + }, + { + "expect": { + "sketching": true + } + }, + { + "start": "sketch.finish" + }, + { + "call": "ui.set", + "params": { + "showOrigin": false + } + }, + { + "start": "sketch.create" + }, + { + "click": { + "world": [ + 0, + 3, + 3 + ] + } + }, + { + "expect": { + "sketching": false + } + }, + { + "key": "Escape" + } +] \ No newline at end of file