Sketch: symmetry about the origin point or an origin axis (#1318)
Fixes #1075. sketch.constraint.symmetry takes a centre point (the origin or any sketch point) as well as a line; the Symmetry tool's third pick may be the origin point or the sketch's own X/Y origin axis. Checked on main + this branch: cargo check --workspace, the engine cmd::sketch tests (257) and the UI symmetry pick tests.
This commit is contained in:
1 parent
6f115ad740
commit
3e13eb6392
4 files changed
+245
-1
No files matched your search
@@ -0,0 +1,46 @@
|
||||
//! The Symmetry tool's third pick may be the origin point or an origin axis (#1075).
|
||||
|
||||
use serde_json::json;
|
||||
use solidcraft_engine::geom::Vec2;
|
||||
|
||||
use crate::scenario::Harness;
|
||||
|
||||
/// An XY sketch with points p (10, 5) and q (-12, -3), the Symmetry tool started and both picked.
|
||||
fn two_points_picked() -> Harness {
|
||||
let mut h = Harness::new();
|
||||
h.app.ui.show_origin = true;
|
||||
h.app.run("sketch.create", json!({"plane":"XY"})).unwrap();
|
||||
h.app.run("sketch.point", json!({"point":[10,5],"id":"p"})).unwrap();
|
||||
h.app.run("sketch.point", json!({"point":[-12,-3],"id":"q"})).unwrap();
|
||||
h.app.cam.distance = 120.0;
|
||||
h.settle();
|
||||
h.app.start("sketch.constraint.symmetry");
|
||||
for xy in [[10, 5], [-12, -3]] {
|
||||
let at = h.call("ui.at", json!({ "sketch": xy }))["result"].clone();
|
||||
h.step(&json!({ "click": at })).unwrap();
|
||||
}
|
||||
h
|
||||
}
|
||||
|
||||
fn point(h: &Harness, id: &str) -> Vec2 {
|
||||
let sk = h.app.session.doc.sketch(h.app.session.active_sketch.unwrap()).unwrap();
|
||||
sk.point(sk.resolve_point(id).unwrap()).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symmetry_about_the_origin_point() {
|
||||
let mut h = two_points_picked();
|
||||
let at = h.call("ui.at", json!({"sketch":[0,0]}))["result"].clone();
|
||||
h.step(&json!({ "click": at })).unwrap();
|
||||
assert!((point(&h, "p") + point(&h, "q")).len() < 1e-6, "{:?} {:?}", point(&h, "p"), point(&h, "q"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symmetry_about_an_origin_axis() {
|
||||
let mut h = two_points_picked();
|
||||
// On the Y axis, away from the origin point and from p and q.
|
||||
let at = h.call("ui.at", json!({"sketch":[0,6]}))["result"].clone();
|
||||
h.step(&json!({ "click": at })).unwrap();
|
||||
let (p, q) = (point(&h, "p"), point(&h, "q"));
|
||||
assert!((p.x + q.x).abs() < 1e-6 && (p.y - q.y).abs() < 1e-6, "mirrored in the Y axis: {p:?} {q:?}");
|
||||
}
|
||||
@@ -16,6 +16,10 @@ mod display_preview_tests;
|
||||
#[path = "sketch_neutral_click_tests.rs"]
|
||||
mod neutral_click_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "symmetry_pick_tests.rs"]
|
||||
mod symmetry_pick_tests;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Kind {
|
||||
/// Click points; `n` points complete the shape (0 = chain until finished).
|
||||
@@ -189,8 +193,12 @@ pub fn on_click(app: &mut SolveApp, proj: &Proj, pos: Pos2) {
|
||||
}
|
||||
Kind::Pick(n) => {
|
||||
let hits = pick(app, proj, pos);
|
||||
// Symmetry's third pick may be an origin axis lying along the sketch's own x or y
|
||||
// (#1075); the command names those `x` / `y`.
|
||||
let axis_ok = tool.cmd == "sketch.constraint.symmetry" && tool.picks.len() == 2;
|
||||
if let Some(id) = hits.iter().find_map(|h| match h {
|
||||
Hit::SketchPoint { id, .. } | Hit::SketchCurve { id, .. } => Some(id.clone()),
|
||||
Hit::Axis { name } if axis_ok => sketch_axis_of(app, name).map(str::to_string),
|
||||
_ => None,
|
||||
}) {
|
||||
tool.picks.push(id);
|
||||
@@ -455,6 +463,31 @@ pub(crate) fn projection_preselection(selection: &[solidcraft_engine::Sel]) -> O
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// `x` or `y` when the origin axis `name` is the active sketch's own x or y axis: along it, and
|
||||
/// through the sketch's origin.
|
||||
fn sketch_axis_of(app: &SolveApp, name: &str) -> Option<&'static str> {
|
||||
use solidcraft_engine::geom::Vec3;
|
||||
let dir = match name {
|
||||
"X" => Vec3::X,
|
||||
"Y" => Vec3::Y,
|
||||
"Z" => Vec3::Z,
|
||||
_ => return None,
|
||||
};
|
||||
let st = app.session.world_state();
|
||||
let plane = st.sketch(app.session.active_sketch?)?.plane;
|
||||
let o = plane.origin;
|
||||
if (o - dir * o.dot(dir)).len() > 1e-6 {
|
||||
return None;
|
||||
}
|
||||
if plane.x.dot(dir).abs() > 1.0 - 1e-9 {
|
||||
Some("x")
|
||||
} else if plane.y.dot(dir).abs() > 1.0 - 1e-9 {
|
||||
Some("y")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn run_pick(app: &mut SolveApp, t: &Tool) {
|
||||
let k = &t.picks;
|
||||
let params = match t.cmd {
|
||||
|
||||
Reference in new issue
Block a user