From eb6abecc3d4492feb5710e3e00338ecd9ad0718e Mon Sep 17 00:00:00 2001 From: MateuszSychXpl <151755914+MateuszSychXpl@users.noreply.github.com> Date: Fri, 16 Oct 2026 23:02:33 +0200 Subject: [PATCH] Sketch symmetry: the free symmetry line moves when both mirrored lines are fixed (#1796) A constraint is applied with its first-picked entities held and the second moving; when the second can't move, a first one moves instead. That fallback was skipped as soon as ANY first entity was locked, so a free construction line between two fixed lines was never tried, and Symmetry was refused with "neither entity can move". Each first entity that can move is now tried on its own, after the second. Nothing changes where the second can move. --- crates/engine/src/cmd/sketch.rs | 8 ++++---- .../src/cmd/sketch_constraints_tests.rs | 20 +++++++++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/crates/engine/src/cmd/sketch.rs b/crates/engine/src/cmd/sketch.rs index 4cf5e4b0..95cf367e 100644 --- a/crates/engine/src/cmd/sketch.rs +++ b/crates/engine/src/cmd/sketch.rs @@ -1463,10 +1463,10 @@ pub(super) fn apply_in_order(sk: &mut Sketch, rep: &SolveReport, first: &[Ent], if !second_locked { ordered(first, second); } - if !first_locked { - for f in first { - ordered(&[second], *f); - } + // Each first entity that can move may (a free symmetry line between two fixed lines, #1796), + // even when another of them can't. + for f in first.iter().filter(|f| !ent_locked(sk, rep, **f)) { + ordered(&[second], *f); } const TEMP: &str = "__keep_size"; for (h, extra) in tries { diff --git a/crates/engine/src/cmd/sketch_constraints_tests.rs b/crates/engine/src/cmd/sketch_constraints_tests.rs index 4e3efc03..c68343ff 100644 --- a/crates/engine/src/cmd/sketch_constraints_tests.rs +++ b/crates/engine/src/cmd/sketch_constraints_tests.rs @@ -283,3 +283,23 @@ fn an_angle_between_lines_takes_the_reading_nearest_its_value_and_reports_degree let d = run(&mut s, "sketch.dimension", json!({"entities": ["l2"], "driven": true})); assert_eq!(d["unit"], "mm", "{d}"); } + +#[test] +fn symmetry_moves_a_free_line_between_two_fixed_lines() { + // #1796: with both mirrored lines fixed, the symmetry line between them (a free construction + // line) is the one to move; it was refused as "neither entity can move". + let mut s = new_sketch(); + run(&mut s, "sketch.line", json!({"points": [[0, 0], [0, 20]], "ids": ["left"]})); + run(&mut s, "sketch.line", json!({"points": [[10, 0], [10, 20]], "ids": ["right"]})); + run(&mut s, "sketch.line", json!({"points": [[3, 0], [3, 20]], "construction": true, "ids": ["mid"]})); + run(&mut s, "sketch.constraint.fix", json!({"entities": ["left", "right"]})); + run(&mut s, "sketch.constraint.symmetry", json!({"a": "left", "b": "right", "line": "mid"})); + let sk = sketch(&s); + let at = |r: &str| sk.point(sk.resolve_point(r).unwrap()).unwrap(); + for end in ["mid.start", "mid.end"] { + assert!((at(end).x - 5.0).abs() < 1e-7, "{end} at {:?}", at(end)); + } + for (end, x) in [("left.start", 0.0), ("left.end", 0.0), ("right.start", 10.0), ("right.end", 10.0)] { + assert!((at(end).x - x).abs() < 1e-9, "{end} moved to {:?}", at(end)); + } +}