fix: move coincident grips together

This commit is contained in:
gianlucafiore 2026-08-15 14:22:22 -03:00
commit bc9e206c19

View file

@ -489,19 +489,19 @@ impl OpenCADStudio {
.zip(self.tabs[i].selected_grips.iter()) .zip(self.tabs[i].selected_grips.iter())
.find(|(owner, grip)| *owner == handle && grip.id == grip_id) .find(|(owner, grip)| *owner == handle && grip.id == grip_id)
.and_then(|(_, grip)| grip.axis); .and_then(|(_, grip)| grip.axis);
if !self.tabs[i].hot_grips.contains(&(handle, grip_id)) {
self.tabs[i].hot_grips.clear();
let mut edit = GripEdit::single(handle, grip_id, is_translate, world);
edit.axis = axis;
return edit;
}
let mut targets: Vec<GripTarget> = self.tabs[i] let clicked_is_hot = self.tabs[i].hot_grips.contains(&(handle, grip_id));
let mut targets: Vec<GripTarget> = if clicked_is_hot {
// Explicit hot grips keep their existing multi-grip behaviour.
self.tabs[i]
.selected_grip_handles .selected_grip_handles
.iter() .iter()
.copied() .copied()
.zip(self.tabs[i].selected_grips.iter()) .zip(self.tabs[i].selected_grips.iter())
.filter(|(owner, grip)| self.tabs[i].hot_grips.contains(&(*owner, grip.id))) .filter(|(owner, grip)| {
self.tabs[i].hot_grips.contains(&(*owner, grip.id))
})
.filter(|(_, grip)| grip.id != crate::app::visibility::VIS_GRIP_ID) .filter(|(_, grip)| grip.id != crate::app::visibility::VIS_GRIP_ID)
.map(|(owner, grip)| GripTarget { .map(|(owner, grip)| GripTarget {
handle: owner, handle: owner,
@ -509,17 +509,45 @@ impl OpenCADStudio {
is_translate: grip.is_midpoint, is_translate: grip.is_midpoint,
last_world: grip.world, last_world: grip.world,
}) })
.collect(); .collect()
} else {
// A normal click on coincident grips stretches all selected grips that
// share the same geometric point. Use a tiny world-space tolerance:
// visually-near grips caused by zoom must remain independent.
self.tabs[i].hot_grips.clear();
const COINCIDENT_GRIP_EPSILON: f64 = 1.0e-9;
let epsilon_sq = COINCIDENT_GRIP_EPSILON * COINCIDENT_GRIP_EPSILON;
self.tabs[i]
.selected_grip_handles
.iter()
.copied()
.zip(self.tabs[i].selected_grips.iter())
.filter(|(_, grip)| grip.id != crate::app::visibility::VIS_GRIP_ID)
.filter(|(_, grip)| {
(grip.world - world).length_squared() <= epsilon_sq
})
.map(|(owner, grip)| GripTarget {
handle: owner,
grip_id: grip.id,
is_translate: grip.is_midpoint,
last_world: grip.world,
})
.collect()
};
// A midpoint/centre grip translates its whole entity. If that entity also // A midpoint/centre grip translates its whole entity. If that entity also
// has hot point grips, applying both would move it twice; one translate // has coincident point grips, applying both would move it twice; one
// target owns the entity in that case. // translate target owns that entity in that case.
let translate_handles: rustc_hash::FxHashSet<_> = targets let translate_handles: rustc_hash::FxHashSet<_> = targets
.iter() .iter()
.filter(|target| target.is_translate) .filter(|target| target.is_translate)
.map(|target| target.handle) .map(|target| target.handle)
.collect(); .collect();
let mut used_translates = rustc_hash::FxHashSet::default(); let mut used_translates = rustc_hash::FxHashSet::default();
targets.retain(|target| { targets.retain(|target| {
if translate_handles.contains(&target.handle) { if translate_handles.contains(&target.handle) {
target.is_translate && used_translates.insert(target.handle) target.is_translate && used_translates.insert(target.handle)
@ -527,12 +555,17 @@ impl OpenCADStudio {
true true
} }
}); });
if targets.is_empty() { if targets.is_empty() {
let mut edit = GripEdit::single(handle, grip_id, is_translate, world); let mut edit = GripEdit::single(handle, grip_id, is_translate, world);
edit.axis = axis; edit.axis = axis;
return edit; return edit;
} }
// An axis constraint belongs to a single grip. When several coincident
// grips participate, let the common cursor delta drive all of them.
let axis = (targets.len() == 1).then_some(axis).flatten(); let axis = (targets.len() == 1).then_some(axis).flatten();
GripEdit { GripEdit {
handle, handle,
grip_id, grip_id,