fix(viewport): stabilize grid and grip clipping

Keep the grid on UCS XY, derive spacing from zoom, and clip perspective lines by screen density without dropping the near side.
This commit is contained in:
Hakan Seven 2026-07-27 11:36:07 +03:00
commit 7f1a1ff57b
4 changed files with 573 additions and 233 deletions

View file

@ -1,4 +1,3 @@
use crate::ui::overlay::GridPlane;
use acadrust::tables::Ucs;
// ── Coordinate parsing ─────────────────────────────────────────────────────
@ -160,22 +159,6 @@ pub(super) fn ucs_rotated_z(origin: glam::DVec3, angle_z: f32) -> Ucs {
ucs
}
// ── Grid plane detection ───────────────────────────────────────────────────
/// Choose the grid plane whose normal is most aligned with the camera view direction.
pub(super) fn grid_plane_from_camera(pitch: f32, yaw: f32) -> GridPlane {
let fz = pitch.sin().abs();
let fy = (pitch.cos() * yaw.cos()).abs();
let fx = (pitch.cos() * yaw.sin()).abs();
if fz >= fy && fz >= fx {
GridPlane::Xy
} else if fy >= fx {
GridPlane::Xz
} else {
GridPlane::Yz
}
}
// ── Drawing constraint helpers ─────────────────────────────────────────────
/// Constrain `pt` to the nearest 90° direction from `base`, in the active UCS

View file

@ -962,15 +962,23 @@ impl OpenCADStudio {
// content), so snap / hit-test / preview run exactly like the
// main model view — no paper projection, tracks pan/zoom/twist.
let cursor_world = self.cursor_model_point(i, &edit_cam, p, bounds);
let (view_rot, eye) = match &edit_cam {
Some(cam) => (cam.view_proj_rte(bounds), cam.eye()),
let (view_rot, eye, grid_spacing) = match &edit_cam {
Some(cam) => (
cam.view_proj_rte(bounds),
cam.eye(),
crate::ui::overlay::compute_grid_step(cam.distance, cam.fov_y, bounds),
),
None => {
let cam = self.tabs[i].scene.camera.borrow();
(cam.view_proj_rte(bounds), cam.eye())
(
cam.view_proj_rte(bounds),
cam.eye(),
crate::ui::overlay::compute_grid_step(cam.distance, cam.fov_y, bounds),
)
}
};
// Sync grid-snap spacing to the adaptive spacing of the visible grid.
self.snapper.grid_spacing = crate::ui::overlay::compute_grid_step(view_rot, bounds);
self.snapper.grid_spacing = grid_spacing;
// Cursor and wires are model-space; the snap result is model.
let snap_cursor = cursor_world;
@ -1590,11 +1598,19 @@ impl OpenCADStudio {
// Object/grid snap, same path as an entity grip or command drag: the
// dragged UCS point sticks to endpoints/midpoints/grid under the cursor,
// and the snap marker is published via `snap_result`.
let (view_rot, eye) = match &edit_cam {
Some(cam) => (cam.view_proj_rte(bounds), cam.eye()),
let (view_rot, eye, grid_spacing) = match &edit_cam {
Some(cam) => (
cam.view_proj_rte(bounds),
cam.eye(),
crate::ui::overlay::compute_grid_step(cam.distance, cam.fov_y, bounds),
),
None => {
let cam = self.tabs[i].scene.camera.borrow();
(cam.view_proj_rte(bounds), cam.eye())
(
cam.view_proj_rte(bounds),
cam.eye(),
crate::ui::overlay::compute_grid_step(cam.distance, cam.fov_y, bounds),
)
}
};
let all_wires = if let (Some(_), Some(h)) = (&edit_cam, self.tabs[i].scene.active_viewport)
@ -1613,7 +1629,7 @@ impl OpenCADStudio {
bounds,
self.snapper.osnap_radius_px,
);
self.snapper.grid_spacing = crate::ui::overlay::compute_grid_step(view_rot, bounds);
self.snapper.grid_spacing = grid_spacing;
// No rubber-band origin (perp/extension feet don't apply to a free drag).
self.snapper.from_point = None;
let (go, gr) = self.tabs[i].ucs_grid_basis();

View file

@ -1,6 +1,5 @@
use super::document::DocumentTab;
use super::document::DynComponent;
use super::helpers::grid_plane_from_camera;
use super::history::history_dropdown_labels;
use super::{Message, OpenCADStudio};
use crate::scene::pick::grip::{grips_to_screen, grips_to_screen_paper, grips_to_screen_rte};
@ -228,12 +227,15 @@ impl OpenCADStudio {
} else {
model_basis
};
let plane = grid_plane_from_camera(cam.pitch, cam.yaw);
crate::ui::overlay::GridParams {
view_rot: cam.view_proj_rte(bounds),
eye: cam.eye(),
bounds,
plane,
step: crate::ui::overlay::compute_grid_step(
cam.distance,
cam.fov_y,
bounds,
),
origin,
axes,
}
@ -326,6 +328,17 @@ impl OpenCADStudio {
} else {
vec![]
};
let grip_clip = if grips.is_empty() {
None
} else {
let (vw, vh) = tab.scene.selection.borrow().vp_size;
Some(
tab.scene
.viewport_edit_frame((vw, vh))
.map(|(_, bounds)| bounds)
.unwrap_or_else(|| tab.scene.active_model_tile_bounds(vw, vh)),
)
};
let (vw, vh) = tab.scene.selection.borrow().vp_size;
// Active tile rectangle (canvas-offset included) so grid / UCS
@ -508,6 +521,7 @@ impl OpenCADStudio {
snap_ext_base,
snap_ext_base2,
grips,
grip_clip,
ucs_icons,
ost_points,
otrack_line,