feat(plot): PSETUPIN and page-setup import, page setup for new layouts

- PSETUPIN / -PSETUPIN: with no arguments a file picker offers the chosen
  drawing's named page setups in the Plot dialog; `PSETUPIN <file>` opens
  the same chooser on that file; `PSETUPIN <file> *` or
  `PSETUPIN <file> name,name` import straight away. Same-named setups are
  replaced, the import is one undo step, and the report counts both.
- The Plot dialog's page-setup list gains "Import…": the chooser shows the
  source drawing's setups with a tick each, All, Import and Cancel, and
  selects the first imported setup afterwards. The source is read off the
  UI thread with the same reader the editor uses for its own setups
  (`scene::document_page_setups`), and handles that only meant something
  in the source drawing (plot view, visual style) are dropped on import.
- Options → General → Plotting: "Show the page setup for new layouts"
  opens the Plot / Page Setup dialog whenever a layout is created
  (persisted with the plot preferences).

Tests: the document reader, the command (all / named / unknown / missing
file / undo) against a drawing saved by the app, the chooser flow, and the
new-layout option. Nine new strings in all 21 locales.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
PanosandClaude Opus 5 committed 2026-09-18 14:53:09 +03:00
1 parent 7efb04a666
commit 74936063f9
35 files changed
+786 -48

No files matched your search

+1 -1
View File
@@ -33,7 +33,7 @@ pub(crate) mod centermark;
pub(crate) mod dimension_assoc;
pub(crate) mod dimension_assoc_chain;
pub use dimension_assoc::{ReferenceStatus, ResolvedReference};
pub use page_setup::{apply_default_page_setup, rotated_margins};
pub use page_setup::{apply_default_page_setup, document_page_setups, rotated_margins};
mod dwg_native_constraints;
mod entity;
#[cfg(test)]
+60 -24
View File
@@ -6,7 +6,7 @@
use super::Scene;
use acadrust::objects::{Dictionary, Layout, ObjectType, PlotSettings};
use acadrust::Handle;
use acadrust::{CadDocument, Handle};
/// Give a freshly created paper layout the page setup a new drawing starts
/// with — ISO A4 landscape on no plotter, plotted 1:1 as a layout — stored the
@@ -59,30 +59,59 @@ pub fn rotated_margins(
}
}
impl Scene {
/// Handle of the `ACAD_PLOTSETTINGS` dictionary, located robustly.
///
/// The canonical path is the header's `acad_plotsettings_dict_handle`, but
/// DWGs written by other programs don't always leave that pointer resolvable
/// (the header handle points at no loaded dictionary — see
/// [`crate::scene::annotative::root_named_dict_handle`]). In that case fall
/// back to the dictionary that owns the drawing's `PlotSettings` objects,
/// mirroring [`Scene::scalelist_dict_handle`]. Returns `None` when the
/// drawing genuinely has no named page setups.
fn plotsettings_dict_handle(&self) -> Option<Handle> {
let dh = self.document.header.acad_plotsettings_dict_handle;
if matches!(self.document.objects.get(&dh), Some(ObjectType::Dictionary(_))) {
return Some(dh);
}
let owner = self.document.objects.values().find_map(|o| match o {
ObjectType::PlotSettings(ps) => Some(ps.owner),
/// Handle of a document's `ACAD_PLOTSETTINGS` dictionary, located robustly.
///
/// The canonical path is the header's `acad_plotsettings_dict_handle`, but
/// DWGs written by other programs don't always leave that pointer resolvable
/// (the header handle points at no loaded dictionary — see
/// [`crate::scene::annotative::root_named_dict_handle`]). In that case fall
/// back to the dictionary that owns the drawing's `PlotSettings` objects,
/// mirroring [`Scene::scalelist_dict_handle`]. Returns `None` when the
/// drawing genuinely has no named page setups.
fn plotsettings_dict_handle_in(document: &CadDocument) -> Option<Handle> {
let dh = document.header.acad_plotsettings_dict_handle;
if matches!(document.objects.get(&dh), Some(ObjectType::Dictionary(_))) {
return Some(dh);
}
let owner = document.objects.values().find_map(|o| match o {
ObjectType::PlotSettings(ps) => Some(ps.owner),
_ => None,
})?;
matches!(document.objects.get(&owner), Some(ObjectType::Dictionary(_))).then_some(owner)
}
/// The named page setups of any document — `(name, settings)` in dictionary
/// order. Reads a drawing that is not open in the editor (the source of a
/// `PSETUPIN` import) the same way the editor reads its own.
pub fn document_page_setups(document: &CadDocument) -> Vec<(String, PlotSettings)> {
let Some(dh) = plotsettings_dict_handle_in(document) else {
return Vec::new();
};
let Some(ObjectType::Dictionary(dict)) = document.objects.get(&dh) else {
return Vec::new();
};
dict.entries
.iter()
.filter_map(|(name, handle)| match document.objects.get(handle) {
Some(ObjectType::PlotSettings(ps)) => Some((name.clone(), ps.clone())),
_ => None,
})?;
matches!(
self.document.objects.get(&owner),
Some(ObjectType::Dictionary(_))
)
.then_some(owner)
})
.collect()
}
/// A page setup copied out of another drawing, made safe for this one: the
/// handles it pointed at (a plot view, a visual style) meant something only
/// in its source, so they are dropped while the names, which travel, stay.
pub fn detach_page_setup(ps: &mut PlotSettings) {
ps.plot_view_handle = Handle::NULL;
ps.visual_style_handle = Handle::NULL;
ps.reactors.clear();
ps.xdictionary_handle = None;
}
impl Scene {
fn plotsettings_dict_handle(&self) -> Option<Handle> {
plotsettings_dict_handle_in(&self.document)
}
/// Names of the document's named page setups, in dictionary order.
@@ -93,6 +122,13 @@ impl Scene {
}
}
/// Bring a page setup from another drawing in under `name`, replacing
/// one of that name the way `PSETUPIN` redefines it.
pub fn import_page_setup(&mut self, name: &str, mut ps: PlotSettings) {
detach_page_setup(&mut ps);
self.page_setup_save(name, ps);
}
/// Clone the named page setup's `PlotSettings`, or `None` if absent.
pub fn page_setup_get(&self, name: &str) -> Option<PlotSettings> {
let dh = self.plotsettings_dict_handle()?;