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:
1 parent
736d9277b8
commit
b1740aeee5
3 files changed
+37
-1
No files matched your search
@@ -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());
|
||||
|
||||
@@ -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:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user