diff --git a/src/app/update/viewport.rs b/src/app/update/viewport.rs index bb84021ba..df6f3df7a 100644 --- a/src/app/update/viewport.rs +++ b/src/app/update/viewport.rs @@ -6137,9 +6137,9 @@ mod selection_preview_tests { .normalize() }; assert!(direction(current[0]).dot(direction(current[1])).abs() < 1e-8); - assert_ne!( + assert_eq!( current[1], before[1], - "constrained neighbor must follow each grip frame" + "geometry outside the grip edit must stay fixed" ); assert_eq!( current[0][0], before[0][0], diff --git a/src/scene/parametric_solve.rs b/src/scene/parametric_solve.rs index 1a99c817c..8297860df 100644 --- a/src/scene/parametric_solve.rs +++ b/src/scene/parametric_solve.rs @@ -2636,11 +2636,24 @@ impl Scene { if !is_touched { continue; } + let mut anchors = driven_refs.to_vec(); + for reference in set + .constraints + .iter() + .filter(|constraint| constraint.enabled) + .flat_map(|constraint| &constraint.refs) + { + if !touched.contains(&reference.entity) + && !anchors.iter().any(|anchor| anchor.entity == reference.entity) + { + anchors.push(ParametricRef::whole(reference.entity)); + } + } let Some((solved, _dof, _conflicts)) = solve_scope( &self.document, &self.named_parameters, set, - driven_refs, + &anchors, retain_size, &retained_before, ) @@ -2912,11 +2925,11 @@ mod tests { ); } - /// `solve_parametric_constraints_preview` reports the constrained - /// neighbor's solved position without writing anything into the - /// document itself — the grip-drag caller owns applying it. + /// A grip preview treats geometry outside the edit as a fixed reference, + /// so a parallel constraint projects the dragged endpoint without moving + /// the other line or changing their separation. #[test] - fn preview_solve_reports_the_neighbors_new_state_without_mutating_the_document() { + fn preview_solve_keeps_untouched_parallel_geometry_fixed() { let mut scene = Scene::new(); let a = scene.add_entity(EntityType::Line(acadrust::entities::Line::from_points( Vector3::new(0.0, 0.0, 0.0), @@ -2941,40 +2954,39 @@ mod tests { } let b_before = scene.document.get_entity(b).cloned(); - let solved = scene.solve_parametric_constraints_preview(&[a], &[], false, &[]); + let solved = scene.solve_parametric_constraints_preview( + &[a], + &[ParametricRef::point(a, 0)], + false, + &[], + ); assert_eq!( scene.document.get_entity(b), b_before.as_ref(), "preview must not mutate the document" ); - // The whole scope solves together (every registered entity's params - // are free, not just `b`'s — see `register_entity`'s doc comment), - // so `a` itself may also have shifted slightly to reach the nearest - // mutually-parallel configuration; read whichever position `a` ends - // up at from `solved` too, rather than assuming it stayed exactly - // where the simulated drag put it. - let line_dir = |entity: &EntityType| { - let EntityType::Line(l) = entity else { - panic!("expected a Line") - }; - (l.end.x - l.start.x, l.end.y - l.start.y) - }; - let dir_a = solved + let moved_a = solved .iter() .find(|(h, _)| *h == a) - .map(|(_, e)| line_dir(e)) - .unwrap_or((10.0, 6.0)); - let (_, moved_entity) = solved - .iter() - .find(|(h, _)| *h == b) - .expect("b should be reported as moved"); - let dir_b = line_dir(moved_entity); - let cross = dir_a.0 * dir_b.1 - dir_a.1 * dir_b.0; + .map(|(_, entity)| entity) + .expect("the dragged line should be projected"); + assert!(solved.iter().all(|(handle, _)| *handle != b)); + let EntityType::Line(moved_a_line) = moved_a else { + panic!("expected a Line") + }; + assert_eq!(moved_a_line.start, Vector3::new(0.0, 0.0, 0.0)); + let EntityType::Line(fixed_b) = b_before.unwrap() else { + panic!("expected a Line") + }; + let dir_a = moved_a_line.end - moved_a_line.start; + let dir_b = fixed_b.end - fixed_b.start; + let cross = dir_a.x * dir_b.y - dir_a.y * dir_b.x; assert!( cross.abs() < 1e-6, "the previewed positions should be mutually parallel: dir_a={dir_a:?} dir_b={dir_b:?}" ); + assert_eq!(fixed_b.start.y - moved_a_line.start.y, 5.0); } #[test]