diff --git a/src/app/commands/styleprops.rs b/src/app/commands/styleprops.rs index b109907ca..d1147aa5f 100644 --- a/src/app/commands/styleprops.rs +++ b/src/app/commands/styleprops.rs @@ -205,6 +205,11 @@ impl OpenCADStudio { } else if name.eq_ignore_ascii_case("BYBLOCK") { Some(("ByBlock".to_string(), codec::types::Handle::NULL)) } else { + // A catalog linetype loads on first use. + crate::io::linetypes::ensure_catalog_linetype( + &mut self.tabs[i].scene.document, + name, + ); self.tabs[i] .scene .document diff --git a/src/app/layers.rs b/src/app/layers.rs index 76a1b6dd3..3984701d5 100644 --- a/src/app/layers.rs +++ b/src/app/layers.rs @@ -157,6 +157,7 @@ impl OpenCADStudio { let art = crate::io::linetypes::extract_pattern(<.description); ui::properties::LinetypeItem { name, art } }) + .chain(catalog_linetype_items(&self.tabs[i].scene.document)) .collect(); self.tabs[i].layers.sync_linetypes(lt_items.clone()); self.ribbon.set_available_linetypes(lt_items); @@ -247,3 +248,16 @@ impl OpenCADStudio { ); } } + +/// The catalog linetypes the drawing lacks, offered after its own; a pick +/// loads the one chosen (`ensure_catalog_linetype`). +pub(super) fn catalog_linetype_items( + document: &codec::CadDocument, +) -> impl Iterator { + crate::io::linetypes::catalog_missing_from(document) + .into_iter() + .map(|(name, description)| ui::properties::LinetypeItem { + name, + art: crate::io::linetypes::extract_pattern(&description), + }) +} diff --git a/src/app/properties.rs b/src/app/properties.rs index cdb514190..e376a84e9 100644 --- a/src/app/properties.rs +++ b/src/app/properties.rs @@ -1824,6 +1824,9 @@ impl OpenCADStudio { let art = linetypes::extract_pattern(<.description); ui::properties::LinetypeItem { name, art } }) + .chain(super::layers::catalog_linetype_items( + &self.tabs[i].scene.document, + )) .collect(); let text_style_names: Vec = self.tabs[i] .scene diff --git a/src/app/update/file.rs b/src/app/update/file.rs index fecfbb10f..16bd379cb 100644 --- a/src/app/update/file.rs +++ b/src/app/update/file.rs @@ -2315,7 +2315,7 @@ impl OpenCADStudio { // not inherit the previous tab's filter, and persisted entity // visibility remains independent (not an isolation session). self.tabs[i].scene.reset_transient_visibility(); - crate::io::linetypes::populate_document(&mut self.tabs[i].scene.document); + crate::io::linetypes::repair_document(&mut self.tabs[i].scene.document); self.tabs[i].properties = PropertiesPanel::empty(); // Seed the current table / multileader style from the file's // header so the ✓ marks the right one (text/dim/mline come from diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index 58d5ef0c7..164e8ad17 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -389,6 +389,20 @@ impl OpenCADStudio { // creating or closing a tab), the contextual ribbon tab follows that // document's selection. let before = self.tabs.get(self.active_tab).map(|tab| tab.id); + // Pickers offer the catalog linetypes the drawing lacks; the one + // picked is loaded before it is assigned. + if let Message::LayerLinetypeSet(name) + | Message::LayerStateEditorLayerLinetype(_, name) + | Message::RibbonLinetypeChanged(name) + | Message::PropLinetypeChanged(name) + | Message::AttrEditorLinetype(name) = &msg + { + if let Some(tab) = self.tabs.get_mut(self.active_tab) { + if crate::io::linetypes::ensure_catalog_linetype(&mut tab.scene.document, name) { + tab.scene.bump_geometry(); + } + } + } let task = self.update_message(msg); self.restore_dynamic_ucs_after_command(); self.sync_block_visibility_display(); diff --git a/src/io/linetypes.rs b/src/io/linetypes.rs index 88d5f0049..f029496e5 100644 --- a/src/io/linetypes.rs +++ b/src/io/linetypes.rs @@ -271,6 +271,40 @@ pub fn populate_document(doc: &mut CadDocument) { populate_document_from_source(doc, lin_source()); } +/// Repair the catalog linetypes `doc` already has (see +/// [`populate_document_from_source`]) without adding any: an opened drawing +/// keeps exactly its own linetypes, so a purged one stays purged. +pub fn repair_document(doc: &mut CadDocument) { + populate_matching(doc, lin_source(), |_| false); +} + +/// The catalog linetypes `doc` does not have, as (name, description): what a +/// picker offers beside the drawing's own. Picking one loads it with +/// [`ensure_catalog_linetype`]. +pub fn catalog_missing_from(doc: &CadDocument) -> Vec<(String, String)> { + static CATALOG: OnceLock> = OnceLock::new(); + CATALOG + .get_or_init(|| { + parse(lin_source()) + .into_iter() + .map(|lt| (lt.name, lt.description)) + .collect() + }) + .iter() + .filter(|(name, _)| !doc.line_types.contains(name)) + .cloned() + .collect() +} + +/// Load catalog linetype `name` into `doc` when the drawing lacks it. +/// Returns whether it was added. +pub fn ensure_catalog_linetype(doc: &mut CadDocument, name: &str) -> bool { + !doc.line_types.contains(name) + && populate_matching(doc, lin_source(), |candidate| { + candidate.eq_ignore_ascii_case(name) + }) > 0 +} + /// Add the linetypes of a caller-supplied `.lin` source, embedded shapes and /// text included, so the drawing draws them and a saved file carries them. /// Returns the number of definitions newly registered in the drawing. @@ -280,6 +314,12 @@ pub fn populate_document(doc: &mut CadDocument) { /// and so saved) gets its shapes and text back when its dashes match the /// source's exactly. pub fn populate_document_from_source(doc: &mut CadDocument, source: &str) -> usize { + populate_matching(doc, source, |_| true) +} + +/// [`populate_document_from_source`], adding only the missing linetypes +/// `add` accepts by name; existing ones are repaired either way. +fn populate_matching(doc: &mut CadDocument, source: &str, add: impl Fn(&str) -> bool) -> usize { let complex = parse_complex(source); let mut added = 0; for mut lt in parse(source) { @@ -303,6 +343,9 @@ pub fn populate_document_from_source(doc: &mut CadDocument, source: &str) -> usi } continue; } + if !add(<.name) { + continue; + } if let Some(elements) = segments.and_then(|s| complex_elements(doc, s, <.elements)) { lt.elements = elements; } diff --git a/src/io/mod.rs b/src/io/mod.rs index 4d3deee2f..11392d93f 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -450,10 +450,11 @@ async fn open_path_with_phase_attempt( recoverable: true, }); } - // Before the geometry is prepared below: completing the - // drawing's catalog linetypes (and repairing stripped ones) - // after it would leave the first view drawn without them. - crate::io::linetypes::populate_document(&mut doc); + // Before the geometry is prepared below: repairing the + // drawing's stripped catalog linetypes after it would leave + // the first view drawn without their shapes. None is added: + // the drawing keeps exactly its own linetypes (#1826). + crate::io::linetypes::repair_document(&mut doc); progress2.set(crate::app::OPEN_PHASE_XREF, 6000, 0, 1); let t_xref = Instant::now(); let (xref_infos, xref_dropped) = if let Some(base_dir) = path2.parent() {