feat(layout): reopen a drawing in the space it was saved in
Files saved while a paper layout was active reopened in Model space because the loader hardcoded the current layout to "Model" and never consulted the space recorded in the file. The active space is a standard part of the format and acadrust already round-trips it: $TILEMODE (header.show_model_space) for model-vs-paper, and the CTAB current-tab variable for the exact layout name. - Read: on open, pick the current layout from CTAB (when it names a layout that exists), else $TILEMODE (Model, or the first paper layout). - Write: Scene::sync_active_space_to_document() mirrors the active layout back into the document (show_model_space + CTAB). It runs from set_current_layout, so every active-layout change — the status-bar tab, the Layout Manager's Set Current, create / rename / delete, and undo/redo — keeps the document in sync for the next save. $TILEMODE is the guaranteed mechanism (read and written for both DXF and DWG); CTAB refines the exact tab when the variable is present. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
5215a9773b
commit
dd3ce6767c
6 changed files with 53 additions and 8 deletions
|
|
@ -489,7 +489,28 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
||||||
}
|
}
|
||||||
self.tabs[i].scene.selected = rustc_hash::FxHashSet::default();
|
self.tabs[i].scene.selected = rustc_hash::FxHashSet::default();
|
||||||
self.tabs[i].scene.preview_wires = vec![];
|
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
|
// Rebuild the Isolate/Hide set from the entities the file itself
|
||||||
// marks invisible (DXF code 60), so hidden objects stay hidden on
|
// marks invisible (DXF code 60), so hidden objects stay hidden on
|
||||||
// reopen and End Isolation can bring them back.
|
// reopen and End Isolation can bring them back.
|
||||||
|
|
|
||||||
|
|
@ -2569,7 +2569,7 @@ impl OpenCADStudio {
|
||||||
self.push_undo_snapshot(i, "LAYOUT RENAME");
|
self.push_undo_snapshot(i, "LAYOUT RENAME");
|
||||||
self.tabs[i].scene.rename_layout(&old_name, &new_name);
|
self.tabs[i].scene.rename_layout(&old_name, &new_name);
|
||||||
if self.tabs[i].scene.current_layout == old_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.layout_manager_selected = new_name.clone();
|
||||||
self.tabs[i].dirty = true;
|
self.tabs[i].dirty = true;
|
||||||
|
|
@ -2610,8 +2610,7 @@ impl OpenCADStudio {
|
||||||
self.tabs[i].dirty = true;
|
self.tabs[i].dirty = true;
|
||||||
// Switch to Model if active layout was deleted.
|
// Switch to Model if active layout was deleted.
|
||||||
if self.tabs[i].scene.current_layout == name {
|
if self.tabs[i].scene.current_layout == name {
|
||||||
self.tabs[i].scene.current_layout = "Model".to_string();
|
self.tabs[i].scene.set_current_layout("Model".to_string());
|
||||||
self.tabs[i].scene.bump_geometry();
|
|
||||||
}
|
}
|
||||||
self.layout_manager_selected = "Model".to_string();
|
self.layout_manager_selected = "Model".to_string();
|
||||||
self.layout_manager_rename_buf = String::new();
|
self.layout_manager_rename_buf = String::new();
|
||||||
|
|
@ -2658,8 +2657,7 @@ impl OpenCADStudio {
|
||||||
Message::LayoutManagerSetCurrent => {
|
Message::LayoutManagerSetCurrent => {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let name = self.layout_manager_selected.clone();
|
let name = self.layout_manager_selected.clone();
|
||||||
self.tabs[i].scene.current_layout = name.clone();
|
self.tabs[i].scene.set_current_layout(name.clone());
|
||||||
self.tabs[i].scene.bump_geometry();
|
|
||||||
self.command_line
|
self.command_line
|
||||||
.push_output(&format!("Switched to layout '{name}'."));
|
.push_output(&format!("Switched to layout '{name}'."));
|
||||||
Task::none()
|
Task::none()
|
||||||
|
|
|
||||||
|
|
@ -3050,7 +3050,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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
|
// Safety net — `add_layout` already creates the overall
|
||||||
// sheet viewport; this covers any path that doesn't.
|
// sheet viewport; this covers any path that doesn't.
|
||||||
self.tabs[i].scene.ensure_sheet_viewport(&new_name);
|
self.tabs[i].scene.ensure_sheet_viewport(&new_name);
|
||||||
|
|
@ -3085,7 +3085,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||||
self.push_undo_snapshot(i, "LAYOUT RENAME");
|
self.push_undo_snapshot(i, "LAYOUT RENAME");
|
||||||
self.tabs[i].scene.rename_layout(&orig, &new_name);
|
self.tabs[i].scene.rename_layout(&orig, &new_name);
|
||||||
if self.tabs[i].scene.current_layout == orig {
|
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.tabs[i].dirty = true;
|
||||||
self.command_line
|
self.command_line
|
||||||
|
|
|
||||||
|
|
@ -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<String> {
|
||||||
|
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
|
/// Materialise the current-style choices into their format-specific storage
|
||||||
/// before saving, treating the current-style *names* as the single source of
|
/// before saving, treating the current-style *names* as the single source of
|
||||||
/// truth:
|
/// truth:
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,7 @@ impl Scene {
|
||||||
// If the deleted layout was active, fall back to Model space.
|
// If the deleted layout was active, fall back to Model space.
|
||||||
if self.current_layout == name {
|
if self.current_layout == name {
|
||||||
self.current_layout = "Model".to_string();
|
self.current_layout = "Model".to_string();
|
||||||
|
self.sync_active_space_to_document();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.bump_geometry();
|
self.bump_geometry();
|
||||||
|
|
|
||||||
|
|
@ -1333,11 +1333,22 @@ impl Scene {
|
||||||
pub fn set_current_layout(&mut self, name: String) {
|
pub fn set_current_layout(&mut self, name: String) {
|
||||||
if self.current_layout != name {
|
if self.current_layout != name {
|
||||||
self.current_layout = name;
|
self.current_layout = name;
|
||||||
|
self.sync_active_space_to_document();
|
||||||
self.recolor_meshes();
|
self.recolor_meshes();
|
||||||
self.bump_geometry();
|
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
|
/// Returns true if this viewport should display model-space content
|
||||||
/// (i.e. it is a user viewport, not the sheet/overall viewport).
|
/// (i.e. it is a user viewport, not the sheet/overall viewport).
|
||||||
///
|
///
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue