From 1e8fc25976dc9bf8def61ce2eba33f5dd044d2a0 Mon Sep 17 00:00:00 2001 From: bherbruck Date: Thu, 8 Oct 2026 15:31:10 +0000 Subject: [PATCH] X and the toolbar's Construction no longer start a pick tool (Fusion has none): with a selection they convert it, with nothing selected they switch construction mode, outside a sketch they say so. Center Rectangle draws both diagonals as construction, the centre at their crossing, with only the first carrying the midpoint constraint (the second is implied), so a fresh one stays at 4 DOF. Scenarios s30 (pattern a hole) and s32 (cut in the active component) enabled now that b36b332 is on main Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL --- crates/engine/src/cmd/sketch.rs | 12 +++++++++--- crates/engine/src/cmd/sketch_rect_tests.rs | 16 ++++++++++++++++ crates/ui-egui/src/lib.rs | 4 ++-- crates/ui-egui/src/sketch_palette.rs | 4 +++- crates/ui-egui/src/tools.rs | 2 -- crates/ui-egui/tests/scenarios.rs | 6 +++--- 6 files changed, 33 insertions(+), 11 deletions(-) diff --git a/crates/engine/src/cmd/sketch.rs b/crates/engine/src/cmd/sketch.rs index d3edf833..960a7374 100644 --- a/crates/engine/src/cmd/sketch.rs +++ b/crates/engine/src/cmd/sketch.rs @@ -552,10 +552,16 @@ fn rect_center(s: &mut Session, p: &Value) -> Result { let (out, info) = edit(s, p, cmd, false, |sk, _| { let (l, pts) = polygon_lines(sk, &[c - h, Vec2::new(c.x + h.x, c.y - h.y), c + h, Vec2::new(c.x - h.x, c.y + h.y)])?; let mut cons = rect_constraints(sk, &l)?; - if let (Some(p0), Some(p2)) = (pts.first(), pts.get(2)) { + // Both diagonals (construction), the centre at their crossing. Only the first carries + // the midpoint constraint: diagonals of a parallelogram bisect each other, so a second + // one would be redundant (over-constrained). + if let (Some(p0), Some(p1), Some(p2), Some(p3)) = (pts.first(), pts.get(1), pts.get(2), pts.get(3)) { let diag = sk.add_line_pts(*p0, *p2, None)?; - if let Some(cu) = sk.curves.get_mut(diag) { - cu.construction = true; + let diag2 = sk.add_line_pts(*p1, *p3, None)?; + for d in [diag, diag2] { + if let Some(cu) = sk.curves.get_mut(d) { + cu.construction = true; + } } let cp = sk.add_point(c, None)?; cons.push(add_c(sk, ConstraintKind::Midpoint { p: cp, l: diag })?); diff --git a/crates/engine/src/cmd/sketch_rect_tests.rs b/crates/engine/src/cmd/sketch_rect_tests.rs index d8d85324..4f0f0f43 100644 --- a/crates/engine/src/cmd/sketch_rect_tests.rs +++ b/crates/engine/src/cmd/sketch_rect_tests.rs @@ -101,3 +101,19 @@ fn dragging_corners_never_collapses_a_side() { } } } + +#[test] +fn center_rectangle_draws_both_diagonals_like_fusion() { + let (mut s, _) = rect("center"); + let si = run(&mut s, "sketch.inspect", json!({})); + let curves = si["curves"].as_array().cloned().unwrap_or_default(); + // Four sides plus two construction diagonals; the other rectangles have no diagonals. + assert_eq!(curves.len(), 6, "{si}"); + assert_eq!(curves.iter().filter(|c| c["construction"] == true).count(), 2, "{si}"); + assert_eq!(healthy(&mut s, "center diagonals"), 4); + for k in ["two", "three"] { + let (mut s, _) = rect(k); + let si = run(&mut s, "sketch.inspect", json!({})); + assert_eq!(si["curves"].as_array().map(Vec::len), Some(4), "{k}: {si}"); + } +} diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index bd978758..2da8d252 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -420,8 +420,8 @@ impl SolveApp { } } // Construction toggle: selected sketch curves switch at once, otherwise pick them. - // Construction (X): convert the selection, or switch construction mode. - if id == "sketch.construction" && self.session.active_sketch.is_some() { + // Construction (X): convert the selection, or switch construction mode; no pick tool. + if id == "sketch.construction" { sketch_palette::toggle_construction(self); return; } diff --git a/crates/ui-egui/src/sketch_palette.rs b/crates/ui-egui/src/sketch_palette.rs index 18bde7c9..1f7a2fb7 100644 --- a/crates/ui-egui/src/sketch_palette.rs +++ b/crates/ui-egui/src/sketch_palette.rs @@ -75,7 +75,9 @@ pub fn set_mode(app: &mut SolveApp, m: u8) { /// switch construction mode on or off. pub fn toggle_construction(app: &mut SolveApp) { let curves = selected_curves(app); - if curves.is_empty() { + if curves.is_empty() && app.session.active_sketch.is_none() { + app.set_status("Construction mode is for drawing in a sketch: edit a sketch first", false); + } else if curves.is_empty() { let m = if mode(app) == 1 { 0 } else { 1 }; set_mode(app, m); } else { diff --git a/crates/ui-egui/src/tools.rs b/crates/ui-egui/src/tools.rs index 4123486a..37db9d3b 100644 --- a/crates/ui-egui/src/tools.rs +++ b/crates/ui-egui/src/tools.rs @@ -65,7 +65,6 @@ impl Tool { "ConstraintCollinear" => ("ConstraintCollinear", Kind::Pick(2)), "ConstraintMidPoint" => ("ConstraintMidPoint", Kind::Pick(2)), "ConstraintSymmetry" => ("ConstraintSymmetry", Kind::Pick(3)), - "sketch.construction" => ("sketch.construction", Kind::Pick(1)), _ => return crate::sketch_tools::tool_for(id), }; Some(Tool { cmd, kind, pts: Vec::new(), hover: None, picks: Vec::new(), dims: Vec::new(), dims_stage: usize::MAX, dims_focus: false }) @@ -280,7 +279,6 @@ fn run_pick(app: &mut SolveApp, t: &Tool) { let params = match t.cmd { "ConstraintHorizontalVertical" => json!({"line": k[0]}), "ConstraintFix" => json!({"entity": k[0]}), - "sketch.construction" => json!({"curves": [k[0]]}), "ConstraintCoincident" => json!({"a": k[0], "b": k[1]}), "ConstraintMidPoint" => json!({"point": k[0], "line": k[1]}), "ConstraintSymmetry" => json!({"a": k[0], "b": k[1], "line": k[2]}), diff --git a/crates/ui-egui/tests/scenarios.rs b/crates/ui-egui/tests/scenarios.rs index 2d445c44..87ea05f6 100644 --- a/crates/ui-egui/tests/scenarios.rs +++ b/crates/ui-egui/tests/scenarios.rs @@ -53,7 +53,9 @@ scenarios!( s33_move_x_arrow, s34_component_move_carries_sketches, s35_construction_mode, - s36_sketch_palette_visible + s36_sketch_palette_visible, + s32_cut_in_active_component, + s30_pattern_a_hole ); /// Scenarios written ahead of their fixes or features (the user's open bugs): run with @@ -70,6 +72,4 @@ macro_rules! pending { pending!( s31_sketch_on_face_projects: "user bug: a sketch on a face projects its loops as reference (solvecraft-sketch)", - s32_cut_in_active_component: "user bug: a cut in the active component leaves others alone (solvecraft-params)", - s30_pattern_a_hole: "the dialog sends the hole as a feature; the engine's pattern of a blind hole cuts too little (one hole short)", );