forked from 1b-agent/opencadstudio-upstream
fix(grips): drag model grips in the drawing plane, not onto themselves
In a tilted 3D view a grip followed the cursor onto the screen plane through the orbit target, so a circle or arc jumped to another radius and a polyline vertex left its plane. A model-space grip now moves in the UCS XY plane at its own elevation (the screen plane only when seen edge-on). A solid stays drawn while its grip moves, and its own edges were still snap candidates: a cone's base radius landed on its own side. The edited entities are left out of the grip's snap set. Fixes #1033 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
df94117fe5
commit
9cc890c911
2 files changed
+72
-7
No files matched your search
@@ -1525,14 +1525,36 @@ impl OpenCADStudio {
|
||||
// In a viewport, pick the cursor's model point with the
|
||||
// viewport camera directly; otherwise project on the paper
|
||||
// sheet and map to model. Either way `raw` is model space.
|
||||
//
|
||||
// A model-space grip moves in the drawing plane through where it
|
||||
// started (the UCS XY plane at the grip's own elevation). The
|
||||
// plane facing the screen through the orbit target put the point
|
||||
// somewhere else in depth in any tilted view, so a circle jumped
|
||||
// to another radius and a vertex left its plane (#1033). Seen
|
||||
// edge-on that plane cannot be hit; the screen plane takes over.
|
||||
let grip_plane = self.tabs[i].editing_model_space().then(|| {
|
||||
let normal = self.tabs[i]
|
||||
.active_ucs
|
||||
.as_ref()
|
||||
.map_or(glam::DVec3::Z, ucs_z_axis);
|
||||
(normal, grip.origin_world)
|
||||
});
|
||||
let pick = |cam: &crate::scene::view::camera::Camera| match grip_plane {
|
||||
Some((normal, origin))
|
||||
if (cam.rotation * glam::Vec3::NEG_Z)
|
||||
.as_dvec3()
|
||||
.dot(normal)
|
||||
.abs()
|
||||
> 0.05 =>
|
||||
{
|
||||
cam.pick_on_plane(p, bounds, normal.as_vec3(), origin)
|
||||
}
|
||||
_ => cam.pick_on_target_plane(p, bounds),
|
||||
};
|
||||
let raw = match &edit_cam {
|
||||
Some(cam) => cam.pick_on_target_plane(p, bounds),
|
||||
Some(cam) => pick(cam),
|
||||
None => {
|
||||
let paper = self.tabs[i]
|
||||
.scene
|
||||
.camera
|
||||
.borrow()
|
||||
.pick_on_target_plane(p, bounds);
|
||||
let paper = pick(&self.tabs[i].scene.camera.borrow());
|
||||
self.tabs[i].scene.paper_to_model(paper)
|
||||
}
|
||||
};
|
||||
@@ -1647,7 +1669,7 @@ impl OpenCADStudio {
|
||||
} else {
|
||||
self.tabs[i].scene.hit_test_wires()
|
||||
};
|
||||
let snap_candidates = self.tabs[i].scene.interaction_candidates_near(
|
||||
let mut snap_candidates = self.tabs[i].scene.interaction_candidates_near(
|
||||
all_wires,
|
||||
raw,
|
||||
view_rot,
|
||||
@@ -1655,6 +1677,16 @@ impl OpenCADStudio {
|
||||
bounds,
|
||||
self.snapper.osnap_radius_px,
|
||||
);
|
||||
// A solid stays drawn while its grip moves, so its own edges are
|
||||
// still in the set: the grip would snap back onto the shape it is
|
||||
// reshaping (a cone's base radius landed on its own side, #1033).
|
||||
let edited = &self.grip_preview_handles;
|
||||
if !edited.is_empty() {
|
||||
snap_candidates.exclude_wires(|wire| {
|
||||
crate::scene::Scene::handle_from_wire_name(&wire.name)
|
||||
.is_some_and(|handle| edited.contains(&handle))
|
||||
});
|
||||
}
|
||||
// The engaged grip is the rubber-band origin. Perpendicular
|
||||
// snapping must drop its foot from this point, including when a
|
||||
// hot-grip set is moved by the same drag vector.
|
||||
|
||||
@@ -1646,6 +1646,39 @@ impl InteractionCandidates {
|
||||
.map(|(rect, (view, eye, bounds))| (rect, view, eye, bounds))
|
||||
}
|
||||
|
||||
/// Leave out every wire `drop` matches, with the segments, points and
|
||||
/// triangles that belong to it.
|
||||
pub(crate) fn exclude_wires(&mut self, drop: impl Fn(&WireModel) -> bool) {
|
||||
let wires = Arc::clone(&self.wires);
|
||||
let dropped = |wire: u32| wires.get(wire as usize).is_some_and(&drop);
|
||||
match self.wire_indices.as_mut() {
|
||||
Some(indices) => indices.retain(|&wire| !dropped(wire)),
|
||||
None => {
|
||||
self.wire_indices = Some(
|
||||
(0..wires.len() as u32)
|
||||
.filter(|&wire| !dropped(wire))
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
}
|
||||
fn keep<T>(
|
||||
refs: &mut Option<Vec<T>>,
|
||||
wire: impl Fn(&T) -> u32,
|
||||
dropped: impl Fn(u32) -> bool,
|
||||
) {
|
||||
if let Some(refs) = refs.as_mut() {
|
||||
refs.retain(|item| !dropped(wire(item)));
|
||||
}
|
||||
}
|
||||
keep(&mut self.segments, |r| r.wire, dropped);
|
||||
keep(&mut self.snap_points, |r| r.wire, dropped);
|
||||
keep(&mut self.key_vertices, |r| r.wire, dropped);
|
||||
keep(&mut self.key_segments, |r| r.wire, dropped);
|
||||
keep(&mut self.fill_triangles, |r| r.wire, dropped);
|
||||
keep(&mut self.pick_triangles, |r| r.wire, dropped);
|
||||
keep(&mut self.glyphs, |r| r.wire, dropped);
|
||||
}
|
||||
|
||||
pub(crate) fn extend_indexed_area(&mut self, other: Self) {
|
||||
debug_assert!(Arc::ptr_eq(&self.wires, &other.wires));
|
||||
|
||||
|
||||
Reference in new issue
Block a user