fix: apply horizontal and vertical constraints to preselection

This commit is contained in:
lillith committed 2026-10-11 18:51:34 +02:00
1 parent d9d4dd262d
commit 09bf9cafdf
3 files changed
+109

No files matched your search

+15
View File
@@ -545,6 +545,21 @@ impl SolveApp {
self.last_command = Some((spec.id.to_string(), spec.label.to_string())); self.last_command = Some((spec.id.to_string(), spec.label.to_string()));
} }
if let Some(mut t) = tools::Tool::for_command(id) { if let Some(mut t) = tools::Tool::for_command(id) {
if id == "sketch.constraint.horizontal_vertical" {
use solvecraft_engine::Sel;
let params = match self.session.selection.as_slice() {
[Sel::SketchCurve { id }] => Some(json!({ "line": id })),
[Sel::SketchPoint { id: a }, Sel::SketchPoint { id: b }] if a != b => Some(json!({ "points": [a, b] })),
_ => None,
};
if let Some(params) = params {
if self.run(id, params).is_ok() {
let _ = self.session.execute("select.clear", &json!({}));
self.set_status("Constraint applied", false);
}
return;
}
}
// Curve-pair constraints can consume an exact preselection. Leave ambiguous // Curve-pair constraints can consume an exact preselection. Leave ambiguous
// selections to the ordinary pick tool; never silently constrain a subset. // selections to the ordinary pick tool; never silently constrain a subset.
if matches!( if matches!(
+91
View File
@@ -4,6 +4,97 @@ use serde_json::json;
use solvecraft_engine::sketch::ConstraintKind; use solvecraft_engine::sketch::ConstraintKind;
use solvecraft_ui_egui::scenario::Harness; use solvecraft_ui_egui::scenario::Harness;
#[test]
fn horizontal_vertical_uses_preselected_line_and_preserves_rejection() {
for fixed in [false, true] {
let mut h = Harness::new();
h.run(&json!([
{"call":"engine.execute","params":{"command":"sketch.create","params":{"plane":"XY"}}},
{"call":"engine.execute","params":{"command":"sketch.line","params":{"points":[[10,10],[40,13]],"infer":false}}},
{"call":"ui.view","params":{"view":"top"}}
]))
.unwrap();
if fixed {
h.app.session.execute("sketch.constraint.fix", &json!({"entity":"l1"})).unwrap();
}
h.run(&json!([{"click":{"sketch":[25,11.5]}}])).unwrap();
assert_eq!(h.app.session.selection.len(), 1);
let selection = h.app.session.selection.clone();
let before = serde_json::to_value(&h.app.session.doc).unwrap();
let undo = h.app.session.undo.len();
h.run(&json!([{"start":"sketch.constraint.horizontal_vertical"}])).unwrap();
assert_eq!(h.app.status.as_ref().is_some_and(|(_, _, error)| *error), fixed);
if fixed {
assert_eq!(serde_json::to_value(&h.app.session.doc).unwrap(), before);
assert_eq!(h.app.session.selection, selection);
assert_eq!(h.app.session.undo.len(), undo);
} else {
let sk = &h.app.session.model.state().sketches[0].sketch;
let a = sk.point(sk.resolve_point("l1.start").unwrap()).unwrap();
let b = sk.point(sk.resolve_point("l1.end").unwrap()).unwrap();
assert!((a.y - b.y).abs() < 1e-6);
assert_eq!(h.app.session.undo.len(), undo + 1);
assert!(h.app.session.selection.is_empty());
}
assert!(h.app.tool.is_none());
}
}
#[test]
fn horizontal_vertical_leaves_ambiguous_preselection_to_pick_tool() {
use solvecraft_engine::Sel;
for selection in [
vec![Sel::SketchPoint { id: "origin".into() }],
vec![Sel::SketchPoint { id: "origin".into() }, Sel::SketchCurve { id: "c1".into() }],
vec![Sel::SketchPoint { id: "origin".into() }, Sel::SketchPoint { id: "c1.center".into() }, Sel::SketchCurve { id: "c1".into() }],
] {
let mut h = Harness::new();
h.app.session.execute("sketch.create", &json!({"plane":"XY"})).unwrap();
h.app.session.execute("sketch.circle.center", &json!({"center":[20,3],"radius":5})).unwrap();
h.app.session.selection = selection.clone();
let undo = h.app.session.undo.len();
h.run(&json!([{"start":"sketch.constraint.horizontal_vertical"}])).unwrap();
assert!(h.app.tool.as_ref().unwrap().picks.is_empty());
assert_eq!(h.app.session.undo.len(), undo);
assert_eq!(h.app.session.selection, selection);
assert!(!h.app.status.as_ref().is_some_and(|(_, _, error)| *error));
}
}
#[test]
fn horizontal_vertical_uses_preselected_points() {
for center in [[20.0, 3.0], [3.0, 20.0]] {
for reverse in [false, true] {
let mut h = Harness::new();
let (first, second) = if reverse { (center, [0.0, 0.0]) } else { ([0.0, 0.0], center) };
h.run(&json!([
{"call":"engine.execute","params":{"command":"sketch.create","params":{"plane":"XY"}}},
{"call":"engine.execute","params":{"command":"sketch.circle.center","params":{"center":center,"radius":5}}},
{"call":"ui.view","params":{"view":"top"}},
{"click":{"sketch":first}},
{"click":{"sketch":second},"shift":true}
]))
.unwrap();
assert_eq!(h.app.session.selection.len(), 2);
let before = serde_json::to_value(&h.app.session.doc).unwrap();
let undo = h.app.session.undo.len();
h.run(&json!([{"start":"sketch.constraint.horizontal_vertical"}])).unwrap();
assert!(!h.app.status.as_ref().is_some_and(|(_, _, error)| *error));
let sk = &h.app.session.model.state().sketches[0].sketch;
let p = sk.point(sk.resolve_point("c1.center").unwrap()).unwrap();
assert!((if center[0] > center[1] { p.y } else { p.x }).abs() < 1e-6);
assert!(h.app.tool.is_none());
assert!(h.app.session.selection.is_empty());
assert_eq!(h.app.session.undo.len(), undo + 1);
let after = serde_json::to_value(&h.app.session.doc).unwrap();
h.app.session.execute("edit.undo", &json!({})).unwrap();
assert_eq!(serde_json::to_value(&h.app.session.doc).unwrap(), before);
h.app.session.execute("edit.redo", &json!({})).unwrap();
assert_eq!(serde_json::to_value(&h.app.session.doc).unwrap(), after);
}
}
}
#[test] #[test]
fn horizontal_vertical_aligns_origin_and_circle_center() { fn horizontal_vertical_aligns_origin_and_circle_center() {
for (center, horizontal) in [([20.0, 3.0], true), ([3.0, 20.0], false)] { for (center, horizontal) in [([20.0, 3.0], true), ([3.0, 20.0], false)] {
+3
View File
@@ -91,6 +91,9 @@ midpoint, symmetry, fix, curvature (G2) and polygon. **Sketch Dimension** (D) ad
aligned, horizontal, vertical, radius, diameter and angle dimensions. Each is a parameter aligned, horizontal, vertical, radius, diameter and angle dimensions. Each is a parameter
(`d1`, `d2`, …), so it can hold an expression. (`d1`, `d2`, …), so it can hold an expression.
Horizontal/Vertical also applies immediately to one selected line or two distinct selected
points. Shift-click to add the second point before choosing the constraint.
For Parallel, Perpendicular, Equal, Tangent, Concentric and Collinear, select exactly two For Parallel, Perpendicular, Equal, Tangent, Concentric and Collinear, select exactly two
sketch curves (Shift-click to add the second) and choose the constraint to apply it immediately. sketch curves (Shift-click to add the second) and choose the constraint to apply it immediately.
You can also start the constraint first and then pick its curves. An incomplete or mixed You can also start the constraint first and then pick its curves. An incomplete or mixed