diff --git a/src/app/update/file.rs b/src/app/update/file.rs index faea7921f..0bba88e4a 100644 --- a/src/app/update/file.rs +++ b/src/app/update/file.rs @@ -489,7 +489,28 @@ pub(super) fn on_open_file(&mut self) -> Task { } self.tabs[i].scene.selected = rustc_hash::FxHashSet::default(); self.tabs[i].scene.preview_wires = vec![]; - self.tabs[i].scene.current_layout = "Model".to_string(); + // Reopen in whichever space the file was saved in — the CTAB + // tab name when recorded, else the $TILEMODE model/paper flag — + // instead of always landing in Model. + { + let names = self.tabs[i].scene.layout_names(); + let saved = crate::io::saved_active_layout(&self.tabs[i].scene.document) + .and_then(|n| { + names.iter().find(|x| x.eq_ignore_ascii_case(&n)).cloned() + }); + self.tabs[i].scene.current_layout = match saved { + Some(n) => n, + None if self.tabs[i].scene.document.header.show_model_space => { + "Model".to_string() + } + // Paper was active but no CTAB — fall back to the first + // paper layout (names[0] is always "Model"). + None => names + .into_iter() + .nth(1) + .unwrap_or_else(|| "Model".to_string()), + }; + } // Rebuild the Isolate/Hide set from the entities the file itself // marks invisible (DXF code 60), so hidden objects stay hidden on // reopen and End Isolation can bring them back. diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index f6b08ac4a..1fda1a44c 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -2569,7 +2569,7 @@ impl OpenCADStudio { self.push_undo_snapshot(i, "LAYOUT RENAME"); self.tabs[i].scene.rename_layout(&old_name, &new_name); if self.tabs[i].scene.current_layout == old_name { - self.tabs[i].scene.current_layout = new_name.clone(); + self.tabs[i].scene.set_current_layout(new_name.clone()); } self.layout_manager_selected = new_name.clone(); self.tabs[i].dirty = true; @@ -2610,8 +2610,7 @@ impl OpenCADStudio { self.tabs[i].dirty = true; // Switch to Model if active layout was deleted. if self.tabs[i].scene.current_layout == name { - self.tabs[i].scene.current_layout = "Model".to_string(); - self.tabs[i].scene.bump_geometry(); + self.tabs[i].scene.set_current_layout("Model".to_string()); } self.layout_manager_selected = "Model".to_string(); self.layout_manager_rename_buf = String::new(); @@ -2658,8 +2657,7 @@ impl OpenCADStudio { Message::LayoutManagerSetCurrent => { let i = self.active_tab; let name = self.layout_manager_selected.clone(); - self.tabs[i].scene.current_layout = name.clone(); - self.tabs[i].scene.bump_geometry(); + self.tabs[i].scene.set_current_layout(name.clone()); self.command_line .push_output(&format!("Switched to layout '{name}'.")); Task::none() diff --git a/src/app/update/viewport.rs b/src/app/update/viewport.rs index 70cb82f32..975ac0a2b 100644 --- a/src/app/update/viewport.rs +++ b/src/app/update/viewport.rs @@ -3050,7 +3050,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task { } } } - self.tabs[i].scene.current_layout = new_name.clone(); + self.tabs[i].scene.set_current_layout(new_name.clone()); // Safety net — `add_layout` already creates the overall // sheet viewport; this covers any path that doesn't. self.tabs[i].scene.ensure_sheet_viewport(&new_name); @@ -3085,7 +3085,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task { self.push_undo_snapshot(i, "LAYOUT RENAME"); self.tabs[i].scene.rename_layout(&orig, &new_name); if self.tabs[i].scene.current_layout == orig { - self.tabs[i].scene.current_layout = new_name.clone(); + self.tabs[i].scene.set_current_layout(new_name.clone()); } self.tabs[i].dirty = true; self.command_line diff --git a/src/io/mod.rs b/src/io/mod.rs index ab1ab5587..b735e1fac 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -530,6 +530,20 @@ fn set_vardict_value(doc: &mut CadDocument, name: &str, value: &str) { } } +/// The layout tab that was active when the drawing was saved — the `CTAB` +/// system variable (stored in the variable dictionary as a `DICTIONARYVAR`). +/// `None` when the file recorded no current tab. +pub fn saved_active_layout(doc: &CadDocument) -> Option { + vardict_value(doc, "CTAB").filter(|s| !s.is_empty()) +} + +/// Record `name` as the active layout tab (`CTAB`) so the next save round-trips +/// which space was open. No-op when the document has no `CTAB` entry yet; the +/// `$TILEMODE` header (model vs paper) remains the guaranteed fallback. +pub fn set_saved_active_layout(doc: &mut CadDocument, name: &str) { + set_vardict_value(doc, "CTAB", name); +} + /// Materialise the current-style choices into their format-specific storage /// before saving, treating the current-style *names* as the single source of /// truth: diff --git a/src/scene/layout.rs b/src/scene/layout.rs index 36068be47..d0ebe8d05 100644 --- a/src/scene/layout.rs +++ b/src/scene/layout.rs @@ -89,6 +89,7 @@ impl Scene { // If the deleted layout was active, fall back to Model space. if self.current_layout == name { self.current_layout = "Model".to_string(); + self.sync_active_space_to_document(); } self.bump_geometry(); diff --git a/src/scene/mod.rs b/src/scene/mod.rs index b69c745ad..7816871dc 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -1333,11 +1333,22 @@ impl Scene { pub fn set_current_layout(&mut self, name: String) { if self.current_layout != name { self.current_layout = name; + self.sync_active_space_to_document(); self.recolor_meshes(); self.bump_geometry(); } } + /// Mirror the active space (Model tile-mode vs a paper layout) into the + /// document's persisted settings so it round-trips on save: the `$TILEMODE` + /// header (`show_model_space`) and the `CTAB` current-tab variable. The + /// reader restores it via [`current_layout`] on open. Called whenever the + /// active layout changes; the file otherwise always reopened in Model. + pub fn sync_active_space_to_document(&mut self) { + self.document.header.show_model_space = self.current_layout == "Model"; + crate::io::set_saved_active_layout(&mut self.document, &self.current_layout); + } + /// Returns true if this viewport should display model-space content /// (i.e. it is a user viewport, not the sheet/overall viewport). ///