forked from 1b-agent/opencadstudio-upstream
fix(grid): independent, clipped grid per viewport
Grid (and grid-snap) state was shared through the global show_grid flag, so toggling it in model space also turned it on in paper space, and a floating viewport's grid painted across the whole paper — and beyond the 3-D view when zoomed in. - Store grid/snap per view: a model tile in model space, the active floating viewport or the layout's sheet viewport in paper space, round-tripped via the viewport status flags. - adopt_view_display on layout switch and viewport enter/exit so the live toggle follows the view you are in. - Render one grid per view (paper_viewport_grid_views): the sheet over the paper, each floating viewport clipped to its screen rectangle. - Clamp the grid clip to the widget so an overflowing viewport rectangle never paints grid outside the 3-D view. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
ba834fb166
commit
26479b6faa
4 files changed
+122
-35
No files matched your search
+9
-1
@@ -5589,6 +5589,10 @@ impl OpenCADStudio {
|
||||
self.tabs[i].scene.deselect_all();
|
||||
self.tabs[i].scene.restore_saved_camera();
|
||||
self.tabs[i].last_synced_camera_gen = self.tabs[i].scene.camera_generation;
|
||||
// Grid/snap are per-view: load the layout we just entered (its
|
||||
// sheet viewport in paper space, the model tile in model space)
|
||||
// so model and each layout keep independent grid state.
|
||||
self.adopt_view_display(i);
|
||||
// Paper-space tools live in the right-edge side toolbar now, so
|
||||
// entering/leaving a layout no longer hijacks the ribbon tab.
|
||||
let _ = going_to_paper;
|
||||
@@ -6101,6 +6105,8 @@ impl OpenCADStudio {
|
||||
// Clear paper-space selection before entering model space.
|
||||
self.tabs[i].scene.deselect_all();
|
||||
self.tabs[i].scene.active_viewport = Some(handle);
|
||||
// Grid/snap follow the entered viewport.
|
||||
self.adopt_view_display(i);
|
||||
self.refresh_properties();
|
||||
self.command_line.push_output("MSPACE");
|
||||
Task::none()
|
||||
@@ -6110,8 +6116,10 @@ impl OpenCADStudio {
|
||||
let i = self.active_tab;
|
||||
// Clear model-space selection before returning to paper space.
|
||||
self.tabs[i].scene.deselect_all();
|
||||
self.refresh_properties();
|
||||
self.tabs[i].scene.active_viewport = None;
|
||||
// Grid/snap return to the paper sheet's own state.
|
||||
self.adopt_view_display(i);
|
||||
self.refresh_properties();
|
||||
self.command_line.push_output("PSPACE");
|
||||
Task::none()
|
||||
}
|
||||
|
||||
+17
-25
@@ -133,33 +133,25 @@ impl OpenCADStudio {
|
||||
// the correct place and scale.
|
||||
let vp_bounds = tab.scene.active_model_tile_bounds(vw, vh);
|
||||
|
||||
// Model layout: one grid per tile that has grid display on, drawn
|
||||
// in its own bounds + camera so panes are independent and never
|
||||
// flicker on hover. Paper layout: a single grid gated by show_grid.
|
||||
let grid: Vec<overlay::GridParams> = if !is_paper {
|
||||
tab.scene
|
||||
.model_tile_grid_views(vw, vh)
|
||||
.into_iter()
|
||||
.map(|(bounds, cam)| {
|
||||
let plane = grid_plane_from_camera(cam.pitch, cam.yaw);
|
||||
overlay::GridParams {
|
||||
view_proj: cam.view_proj(bounds),
|
||||
bounds,
|
||||
plane,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
} else if self.show_grid {
|
||||
let cam = tab.scene.camera.borrow();
|
||||
let plane = grid_plane_from_camera(cam.pitch, cam.yaw);
|
||||
vec![overlay::GridParams {
|
||||
view_proj: cam.view_proj(vp_bounds),
|
||||
bounds: vp_bounds,
|
||||
plane,
|
||||
}]
|
||||
// Each view draws its own grid, clipped to its own bounds, so they
|
||||
// stay independent: model tiles in model space; the sheet plus each
|
||||
// floating viewport (clipped to its rectangle) in paper space.
|
||||
let grid_views = if !is_paper {
|
||||
tab.scene.model_tile_grid_views(vw, vh)
|
||||
} else {
|
||||
Vec::new()
|
||||
tab.scene.paper_viewport_grid_views(vw, vh)
|
||||
};
|
||||
let grid: Vec<overlay::GridParams> = grid_views
|
||||
.into_iter()
|
||||
.map(|(bounds, cam)| {
|
||||
let plane = grid_plane_from_camera(cam.pitch, cam.yaw);
|
||||
overlay::GridParams {
|
||||
view_proj: cam.view_proj(bounds),
|
||||
bounds,
|
||||
plane,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let ucs_icon = if self.show_ucs_icon && !is_paper {
|
||||
let cam = tab.scene.camera.borrow();
|
||||
|
||||
+76
-6
@@ -6276,9 +6276,26 @@ impl Scene {
|
||||
}
|
||||
}
|
||||
|
||||
/// Mirror the live grid/snap toggles onto the *active* model tile so the
|
||||
/// save writes them to that viewport's VPort entry. (#121)
|
||||
pub fn set_active_tile_grid_snap(&self, grid_on: bool, snap_on: bool) {
|
||||
/// Mirror the live grid/snap toggles onto the active view's own store so the
|
||||
/// state stays independent per viewport: a model tile in model space, the
|
||||
/// layout's sheet viewport in paper space. (#121)
|
||||
pub fn set_active_tile_grid_snap(&mut self, grid_on: bool, snap_on: bool) {
|
||||
if self.current_layout != "Model" {
|
||||
// Paper space: target the active floating viewport if the user is
|
||||
// working inside one, otherwise the layout's sheet viewport. Each
|
||||
// viewport keeps its own grid/snap (round-tripped via status flags).
|
||||
let h = self
|
||||
.active_viewport
|
||||
.filter(|h| h.is_valid())
|
||||
.unwrap_or_else(|| self.current_layout_sheet_viewport_handle());
|
||||
if h.is_valid() {
|
||||
if let Some(EntityType::Viewport(vp)) = self.document.get_entity_mut(h) {
|
||||
vp.status.grid_on = grid_on;
|
||||
vp.status.snap_on = snap_on;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
let mut tiles = self.model_tiles.borrow_mut();
|
||||
let active = self.active_model_tile.get().min(tiles.len().saturating_sub(1));
|
||||
if let Some(t) = tiles.get_mut(active) {
|
||||
@@ -6287,10 +6304,22 @@ impl Scene {
|
||||
}
|
||||
}
|
||||
|
||||
/// The active model tile's grid display + grid-snap, adopted into the live
|
||||
/// toggles on load and whenever the active viewport / tab changes. `None`
|
||||
/// when there are no tiles. (#121)
|
||||
/// The active view's grid display + grid-snap, adopted into the live toggles
|
||||
/// on load and whenever the active viewport / tab / layout changes. Reads the
|
||||
/// model tile in model space, the sheet viewport in paper space. (#121)
|
||||
pub fn active_tile_grid_snap(&self) -> Option<(bool, bool)> {
|
||||
if self.current_layout != "Model" {
|
||||
let h = self
|
||||
.active_viewport
|
||||
.filter(|h| h.is_valid())
|
||||
.unwrap_or_else(|| self.current_layout_sheet_viewport_handle());
|
||||
if h.is_valid() {
|
||||
if let Some(EntityType::Viewport(vp)) = self.document.get_entity(h) {
|
||||
return Some((vp.status.grid_on, vp.status.snap_on));
|
||||
}
|
||||
}
|
||||
return Some((false, false));
|
||||
}
|
||||
let tiles = self.model_tiles.borrow();
|
||||
let active = self.active_model_tile.get().min(tiles.len().saturating_sub(1));
|
||||
tiles.get(active).map(|t| (t.grid_on, t.snap_on))
|
||||
@@ -7133,6 +7162,47 @@ impl Scene {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Grid views for a paper-space layout: the sheet (paper) grid covers the
|
||||
/// whole canvas under the paper camera; each floating viewport whose grid is
|
||||
/// on draws the model grid through its own camera, clipped to its screen
|
||||
/// rectangle so it never spills onto the paper. Mirrors model tiles. (#121)
|
||||
pub fn paper_viewport_grid_views(&self, vw: f32, vh: f32) -> Vec<(iced::Rectangle, Camera)> {
|
||||
if self.current_layout == "Model" {
|
||||
return Vec::new();
|
||||
}
|
||||
let layout_block = self.current_layout_block_handle();
|
||||
let sheet = self.current_layout_sheet_viewport_handle();
|
||||
// Snapshot (handle, grid_on, is_sheet) so the later camera_for_viewport
|
||||
// calls don't overlap this immutable borrow of the document.
|
||||
let vps: Vec<(Handle, bool, bool)> = self
|
||||
.document
|
||||
.entities()
|
||||
.filter_map(|e| {
|
||||
let EntityType::Viewport(vp) = e else { return None };
|
||||
if vp.common.owner_handle != layout_block {
|
||||
return None;
|
||||
}
|
||||
Some((vp.common.handle, vp.status.grid_on, vp.common.handle == sheet))
|
||||
})
|
||||
.collect();
|
||||
let full = iced::Rectangle { x: 0.0, y: 0.0, width: vw, height: vh };
|
||||
let mut out = Vec::new();
|
||||
for (h, grid_on, is_sheet) in vps {
|
||||
if !grid_on {
|
||||
continue;
|
||||
}
|
||||
if is_sheet {
|
||||
out.push((full, self.camera.borrow().clone()));
|
||||
} else if let (Some(rect), Some(cam)) = (
|
||||
self.viewport_screen_rect(h, (vw, vh)),
|
||||
self.camera_for_viewport(h),
|
||||
) {
|
||||
out.push((rect, cam));
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
pub fn active_model_tile_bounds(&self, vw: f32, vh: f32) -> iced::Rectangle {
|
||||
if self.current_layout != "Model" {
|
||||
return iced::Rectangle { x: 0.0, y: 0.0, width: vw, height: vh };
|
||||
|
||||
+20
-3
@@ -284,11 +284,28 @@ impl canvas::Program<Message> for SelectionCanvas {
|
||||
}
|
||||
|
||||
// ── Grid display ──────────────────────────────────────────────────
|
||||
// One entry per pane with its grid on. Each is clipped to its own tile
|
||||
// rectangle so lines never spill into neighbouring panes.
|
||||
// One entry per pane with its grid on. Each is clipped to its own
|
||||
// rectangle so lines never spill into neighbouring panes — and the clip
|
||||
// is intersected with the widget so a zoomed-in viewport whose rectangle
|
||||
// overflows the canvas never paints grid outside the 3-D view.
|
||||
for g in &self.grid {
|
||||
let gb = g.bounds;
|
||||
frame.with_clip(gb, |f| draw_grid(f, g.view_proj, g.plane, gb));
|
||||
let cx0 = gb.x.max(0.0);
|
||||
let cy0 = gb.y.max(0.0);
|
||||
let cx1 = (gb.x + gb.width).min(bounds.width);
|
||||
let cy1 = (gb.y + gb.height).min(bounds.height);
|
||||
if cx1 <= cx0 || cy1 <= cy0 {
|
||||
continue;
|
||||
}
|
||||
let clip = iced::Rectangle {
|
||||
x: cx0,
|
||||
y: cy0,
|
||||
width: cx1 - cx0,
|
||||
height: cy1 - cy0,
|
||||
};
|
||||
// Project with the full viewport rect `gb` so the grid stays aligned;
|
||||
// only the clip is clamped.
|
||||
frame.with_clip(clip, |f| draw_grid(f, g.view_proj, g.plane, gb));
|
||||
}
|
||||
|
||||
if let (Some(a), Some(b)) = (self.selection.box_anchor, self.selection.box_current) {
|
||||
|
||||
Reference in new issue
Block a user