forked from 1b-agent/opencadstudio-upstream
feat: per-viewport grid/snap, adopted from the active viewport
Make grid-snap a per-drawing view setting like grid visibility: drop it from the persisted OSNAP set (settings.txt) and round-trip it through each viewport's VPort SNAPMODE instead. Grid display and grid-snap are now stored per model tile (like render mode), written to and read from each tile's *Active VPort entry. The live toggles mirror the active tile, and grid/snap are adopted from the active viewport whenever it changes — on file load, tab switch, model-tile activation, and new drawing — so they follow the active viewport rather than a global preference. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
b2feeb716b
commit
d4a665a34c
3 files changed
+80
-56
No files matched your search
+3
-1
@@ -27,7 +27,9 @@ const SNAP_ORDER: &[SnapType] = &[
|
||||
SnapType::Nearest,
|
||||
SnapType::ApparentIntersection,
|
||||
SnapType::Parallel,
|
||||
SnapType::Grid,
|
||||
// Grid snap (SNAPMODE) is a per-drawing view setting stored on the VPort,
|
||||
// not a global OSNAP preference, so it is deliberately excluded from the
|
||||
// persisted set. (#121)
|
||||
];
|
||||
|
||||
fn snap_id(s: SnapType) -> &'static str {
|
||||
|
||||
+29
-14
@@ -340,17 +340,26 @@ impl OpenCADStudio {
|
||||
self.persist_settings_if_changed();
|
||||
}
|
||||
|
||||
/// Mirror the current grid/snap toggles onto tab `i`'s `*Active` VPort
|
||||
/// entries so a save writes the real display state rather than VPort
|
||||
/// defaults (#121). Cheap; safe to call every frame.
|
||||
/// Mirror the live grid display + grid-snap toggles onto tab `i`'s active
|
||||
/// model tile so a save writes them to that viewport's VPort entry (#121).
|
||||
fn sync_vport_display(&mut self, i: usize) {
|
||||
let grid_on = self.show_grid;
|
||||
let snap_on =
|
||||
self.snapper.snap_enabled && self.snapper.is_on(crate::snap::SnapType::Grid);
|
||||
let grid_spacing = self.snapper.grid_spacing as f64;
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.set_active_vport_display(grid_on, snap_on, grid_spacing);
|
||||
let snap_on = self.snapper.is_on(crate::snap::SnapType::Grid);
|
||||
self.tabs[i].scene.set_active_tile_grid_snap(grid_on, snap_on);
|
||||
}
|
||||
|
||||
/// Adopt the active viewport's grid display + grid-snap into the live
|
||||
/// toggles. Called on load and whenever the active tab or viewport changes,
|
||||
/// so grid/snap follow the active viewport rather than a global setting.
|
||||
fn adopt_view_display(&mut self, i: usize) {
|
||||
if let Some((grid_on, snap_on)) = self.tabs[i].scene.active_tile_grid_snap() {
|
||||
self.show_grid = grid_on;
|
||||
if snap_on {
|
||||
self.snapper.enabled.insert(crate::snap::SnapType::Grid);
|
||||
} else {
|
||||
self.snapper.enabled.remove(&crate::snap::SnapType::Grid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_inner(&mut self, msg: Message) -> Task<Message> {
|
||||
@@ -598,11 +607,9 @@ impl OpenCADStudio {
|
||||
// selected — see #21.
|
||||
self.sync_ribbon_from_selection();
|
||||
self.tabs[i].scene.restore_saved_camera();
|
||||
// Grid visibility is a per-drawing view setting — adopt the
|
||||
// opened file's saved state rather than a global preference.
|
||||
if let Some(grid_on) = self.tabs[i].scene.active_vport_grid_on() {
|
||||
self.show_grid = grid_on;
|
||||
}
|
||||
// Grid/snap are per-drawing view settings — adopt the opened
|
||||
// file's active viewport state rather than a global preference.
|
||||
self.adopt_view_display(i);
|
||||
self.sync_render_mode_to_active_tile(i);
|
||||
self.tabs[i].last_synced_camera_gen = self.tabs[i].scene.camera_generation;
|
||||
self.tabs[i].dirty = false;
|
||||
@@ -1263,6 +1270,8 @@ impl OpenCADStudio {
|
||||
// fresh tab's defaults (ByLayer) instead of inheriting the
|
||||
// previous tab's last selection.
|
||||
self.sync_ribbon_from_selection();
|
||||
// A fresh drawing starts with grid/snap off (its tile defaults).
|
||||
self.adopt_view_display(self.active_tab);
|
||||
Task::none()
|
||||
}
|
||||
|
||||
@@ -1276,6 +1285,8 @@ impl OpenCADStudio {
|
||||
// CECOLOR / CELTYPE / CELWEIGHT (or its current selection
|
||||
// if there is one), not the prior tab's choice.
|
||||
self.sync_ribbon_from_selection();
|
||||
// Grid/snap follow the newly active drawing's viewport.
|
||||
self.adopt_view_display(idx);
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
@@ -2471,6 +2482,8 @@ impl OpenCADStudio {
|
||||
{
|
||||
self.tabs[i].scene.camera_generation += 1;
|
||||
self.sync_render_mode_to_active_tile(i);
|
||||
// Grid/snap follow the newly active viewport.
|
||||
self.adopt_view_display(i);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3092,6 +3105,8 @@ impl OpenCADStudio {
|
||||
{
|
||||
self.tabs[i].scene.camera_generation += 1;
|
||||
self.sync_render_mode_to_active_tile(i);
|
||||
// Grid/snap follow the newly active viewport.
|
||||
self.adopt_view_display(i);
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
|
||||
+48
-41
@@ -475,6 +475,11 @@ pub(crate) struct ModelTile {
|
||||
/// Visual style for this tile alone — each pane carries its own so
|
||||
/// changing one tile's render mode never touches the others.
|
||||
pub(crate) render_mode: acadrust::entities::ViewportRenderMode,
|
||||
/// Grid display + grid-snap for this viewport alone, round-tripped through
|
||||
/// its VPort entry. The app mirrors the *active* tile's pair into the live
|
||||
/// grid/snap toggles. (#121)
|
||||
pub(crate) grid_on: bool,
|
||||
pub(crate) snap_on: bool,
|
||||
}
|
||||
|
||||
/// Tolerance for matching two normalized tile coordinates as "the same"
|
||||
@@ -792,6 +797,8 @@ impl Scene {
|
||||
},
|
||||
camera: Camera::default(),
|
||||
render_mode: acadrust::entities::ViewportRenderMode::Wireframe2D,
|
||||
grid_on: false,
|
||||
snap_on: false,
|
||||
}]),
|
||||
active_model_tile: std::cell::Cell::new(0),
|
||||
selection: Rc::new(RefCell::new(SelectionState::default())),
|
||||
@@ -5383,14 +5390,17 @@ impl Scene {
|
||||
/// Set the model-space camera from the VPORT table's *Active entry.
|
||||
/// Returns true if the entry was found and the camera was set.
|
||||
fn apply_active_vport_camera(&mut self) -> bool {
|
||||
// Restore the single tile's visual style from the *Active entry,
|
||||
// independent of where the camera itself comes from below.
|
||||
// Restore the single tile's visual style + grid/snap from the *Active
|
||||
// entry, independent of where the camera itself comes from below.
|
||||
if let Some(vp) = self.document.vports.iter().find(|v| v.name == "*Active") {
|
||||
let mode = vp.render_mode;
|
||||
let (grid_on, snap_on) = (vp.grid_on, vp.snap_on);
|
||||
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) {
|
||||
t.render_mode = mode;
|
||||
t.grid_on = grid_on;
|
||||
t.snap_on = snap_on;
|
||||
}
|
||||
}
|
||||
// Prefer our named View entry — survives DWG save without being overridden.
|
||||
@@ -5542,6 +5552,8 @@ impl Scene {
|
||||
rect: Self::vport_to_tile_rect(vp.lower_left, vp.upper_right),
|
||||
camera: cam,
|
||||
render_mode: vp.render_mode,
|
||||
grid_on: vp.grid_on,
|
||||
snap_on: vp.snap_on,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
@@ -5573,15 +5585,6 @@ impl Scene {
|
||||
}
|
||||
|
||||
let table_handle = self.document.vports.handle();
|
||||
// Carry the grid/snap display state from the existing `*Active` entry
|
||||
// across the rebuild — these flags track the in-app toggles and must
|
||||
// not reset to VPort defaults each camera sync. (#121)
|
||||
let display = self
|
||||
.document
|
||||
.vports
|
||||
.iter()
|
||||
.find(|v| v.name == "*Active")
|
||||
.map(|v| (v.grid_on, v.snap_on, v.grid_spacing, v.snap_spacing, v.snap_base));
|
||||
let preserved_vps: Vec<acadrust::tables::VPort> = self
|
||||
.document
|
||||
.vports
|
||||
@@ -5613,41 +5616,32 @@ impl Scene {
|
||||
let (ll, ur) = Self::tile_rect_to_vport(tile.rect);
|
||||
let mut entry = self.vport_from_camera("*Active", &tile.camera, ll, ur);
|
||||
entry.render_mode = tile.render_mode;
|
||||
if let Some((grid_on, snap_on, grid_spacing, snap_spacing, snap_base)) = display {
|
||||
entry.grid_on = grid_on;
|
||||
entry.snap_on = snap_on;
|
||||
entry.grid_spacing = grid_spacing;
|
||||
entry.snap_spacing = snap_spacing;
|
||||
entry.snap_base = snap_base;
|
||||
}
|
||||
// Each viewport persists its own grid display + grid-snap (#121).
|
||||
entry.grid_on = tile.grid_on;
|
||||
entry.snap_on = tile.snap_on;
|
||||
entry.handle = self.document.allocate_handle();
|
||||
self.document.vports.add_allow_duplicate(entry);
|
||||
}
|
||||
}
|
||||
|
||||
/// Stamp the grid/snap display state onto every `*Active` VPort entry so a
|
||||
/// saved file reflects the in-app grid/snap toggles (otherwise other CAD
|
||||
/// apps read VPort defaults). `grid_spacing` is applied to both axes and
|
||||
/// mirrored to the snap spacing. (#121)
|
||||
pub fn set_active_vport_display(&mut self, grid_on: bool, snap_on: bool, grid_spacing: f64) {
|
||||
let spacing = acadrust::types::Vector2 { x: grid_spacing, y: grid_spacing };
|
||||
for vp in self.document.vports.iter_mut().filter(|v| v.name == "*Active") {
|
||||
vp.grid_on = grid_on;
|
||||
vp.snap_on = snap_on;
|
||||
vp.grid_spacing = spacing;
|
||||
vp.snap_spacing = spacing;
|
||||
/// 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) {
|
||||
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) {
|
||||
t.grid_on = grid_on;
|
||||
t.snap_on = snap_on;
|
||||
}
|
||||
}
|
||||
|
||||
/// Grid visibility stored on the `*Active` VPort entry, applied to the app's
|
||||
/// grid toggle when a file loads (grid is a per-drawing view setting, not a
|
||||
/// global preference). `None` when there is no active entry. (#121)
|
||||
pub fn active_vport_grid_on(&self) -> Option<bool> {
|
||||
self.document
|
||||
.vports
|
||||
.iter()
|
||||
.find(|v| v.name == "*Active")
|
||||
.map(|v| v.grid_on)
|
||||
/// 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)
|
||||
pub fn active_tile_grid_snap(&self) -> Option<(bool, bool)> {
|
||||
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))
|
||||
}
|
||||
|
||||
/// Set the paper-space camera from the sheet viewport's stored view.
|
||||
@@ -6344,10 +6338,13 @@ impl Scene {
|
||||
)
|
||||
};
|
||||
let mode = tiles[active].render_mode;
|
||||
let (grid_on, snap_on) = (tiles[active].grid_on, tiles[active].snap_on);
|
||||
tiles[active] = ModelTile {
|
||||
rect: a,
|
||||
camera: cam_now.clone(),
|
||||
render_mode: mode,
|
||||
grid_on,
|
||||
snap_on,
|
||||
};
|
||||
tiles.insert(
|
||||
active + 1,
|
||||
@@ -6355,6 +6352,8 @@ impl Scene {
|
||||
rect: b,
|
||||
camera: cam_now,
|
||||
render_mode: mode,
|
||||
grid_on,
|
||||
snap_on,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -6402,13 +6401,17 @@ impl Scene {
|
||||
pub fn set_model_tile_layout(&self, rects: Vec<iced::Rectangle>) {
|
||||
let cam_now = self.camera.borrow().clone();
|
||||
// Every new pane inherits the active tile's current visual style.
|
||||
let mode = {
|
||||
let (mode, grid_on, snap_on) = {
|
||||
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.render_mode)
|
||||
.unwrap_or(acadrust::entities::ViewportRenderMode::Wireframe2D)
|
||||
.map(|t| (t.render_mode, t.grid_on, t.snap_on))
|
||||
.unwrap_or((
|
||||
acadrust::entities::ViewportRenderMode::Wireframe2D,
|
||||
false,
|
||||
false,
|
||||
))
|
||||
};
|
||||
let tiles: Vec<ModelTile> = rects
|
||||
.into_iter()
|
||||
@@ -6416,6 +6419,8 @@ impl Scene {
|
||||
rect,
|
||||
camera: cam_now.clone(),
|
||||
render_mode: mode,
|
||||
grid_on,
|
||||
snap_on,
|
||||
})
|
||||
.collect();
|
||||
*self.model_tiles.borrow_mut() = if tiles.is_empty() {
|
||||
@@ -6428,6 +6433,8 @@ impl Scene {
|
||||
},
|
||||
camera: cam_now,
|
||||
render_mode: mode,
|
||||
grid_on,
|
||||
snap_on,
|
||||
}]
|
||||
} else {
|
||||
tiles
|
||||
|
||||
Reference in new issue
Block a user