ui.at picks an origin plane where a click selects it (#575)

ui.at {plane} now returns the middle of the origin plane's square, or else the middle of a quarter that a click actually picks, so scripted clicks on XY no longer start a sketch on XZ from iso-like views.
This commit is contained in:
Kevin Ravensberg authored and GitHub committed 2026-10-13 02:42:45 -04:00
1 parent 736d9277b8
commit b1740aeee5
3 files changed
+37 -1

No files matched your search

+1 -1
View File
@@ -357,7 +357,7 @@ pub fn handle(app: &mut SolveApp, ctx: &egui::Context, req: &ControlRequest) ->
.and_then(|id| st.sketch(id))
.map(|ss| ss.plane.to_world(solidcraft_engine::geom::Vec2::new(num(s, 0), num(s, 1))))
} else if let Some(n) = p.get("plane").and_then(Value::as_str) {
crate::viewport::origin_planes(app).into_iter().find(|(name, _, _)| *name == n).map(|(_, _, q)| (q[0] + q[1] + q[2] + q[3]) / 4.0)
crate::viewport::origin_plane_target(app, &proj, n)
} else if let Some(n) = p.get("axis").and_then(Value::as_str) {
// Along an origin axis, on its solid part.
let size = crate::selection::origin_size(app.cam.half_height());
+14
View File
@@ -19,6 +19,10 @@ use crate::{SolveApp, icons};
#[path = "viewport_appearance_tests.rs"]
mod appearance_tests;
#[cfg(test)]
#[path = "viewport_plane_target_tests.rs"]
mod plane_target_tests;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum NavMode {
Orbit,
@@ -548,6 +552,16 @@ pub fn origin_planes(app: &SolveApp) -> Vec<(&'static str, Vec3, [Vec3; 4])> {
.collect()
}
/// A point on origin plane `name`'s square that a click at its screen point picks: the middle,
/// or else the middle of a quarter the other planes don't cover from this view (in an iso view
/// the XZ square hides the middle of the XY one). The middle when no candidate is picked.
pub fn origin_plane_target(app: &SolveApp, proj: &Proj, name: &str) -> Option<Vec3> {
let (_, _, q) = origin_planes(app).into_iter().find(|(n, _, _)| *n == name)?;
let mid = (q[0] + q[1] + q[2] + q[3]) / 4.0;
let picked = |w: &Vec3| proj.to_screen(*w).is_some_and(|p| matches!(pick(app, proj, p).first(), Some(Hit::Plane { name: n, .. }) if n == name));
Some(std::iter::once(mid).chain(q.iter().map(|c| (mid + *c) / 2.0)).find(picked).unwrap_or(mid))
}
pub fn origin_axes(app: &SolveApp) -> Vec<(&'static str, Vec3)> {
if !app.origin_visible() {
return Vec::new();
@@ -0,0 +1,22 @@
use crate::scenario::Harness;
use serde_json::json;
/// `ui.at {"plane": …}` gives a point that a click picks that plane at, even where another
/// origin plane hides the middle of its square (seen from the front right and a little lower
/// than iso, the XZ square covers the middle of the XY one).
#[test]
fn clicking_an_origin_plane_target_sketches_on_that_plane() {
for (yaw, pitch) in [(-0.785, 0.615), (-1.1, 0.5), (-0.45, 0.5), (2.4, 0.6), (-2.4, 0.4)] {
for (plane, normal) in [("XY", [0.0, 0.0, 1.0]), ("XZ", [0.0, 1.0, 0.0]), ("YZ", [1.0, 0.0, 0.0])] {
let mut h = Harness::new();
h.app.cam.yaw = yaw;
h.app.cam.pitch = pitch;
h.step(&json!({"start":"sketch.create"})).unwrap();
h.step(&json!({"click":{"plane":plane}})).unwrap();
let state = h.app.session.world_state();
let n = state.sketches.first().map(|s| s.plane.normal()).unwrap_or_default();
let along = (n.x * normal[0] + n.y * normal[1] + n.z * normal[2]).abs();
assert!(along > 0.999, "yaw {yaw} pitch {pitch} {plane}: sketched on a plane with normal {n:?}");
}
}
}