From 218150c76a176ec4286e140c52fa82de5f972b15 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Fri, 16 Oct 2026 06:37:19 +0300 Subject: [PATCH] fix(clipboard): copied XDATA keeps its application in another drawing EED names its application by APPID handle. Copying between drawings (paste, COPYTO, block palette insert, REFEDIT) kept the source drawing's handles, so the saved DWG named APPIDs it did not have and the reference program refused to open it (xdata.cpp eInvalidInput). The copy now carries the APPID records its XDATA names, registers the missing ones, and re-points the EED at them. COPYTO also skipped every layer the target lacked (the check tested the source) and filled the target with the whole linetype catalog; it now copies the missing layers and loads only the linetypes used. Co-Authored-By: Claude Opus 5.5 --- src/app/command_driver/clipboard.rs | 61 +++++++++++++++++++++++++++-- src/app/commands/refedit.rs | 6 ++- src/app/control/sheets.rs | 19 +++++++-- src/app/mod.rs | 32 +++++++++++++++ src/app/update/blocks_palette.rs | 11 +++++- 5 files changed, 120 insertions(+), 9 deletions(-) diff --git a/src/app/command_driver/clipboard.rs b/src/app/command_driver/clipboard.rs index 1b841209f..64827cad8 100644 --- a/src/app/command_driver/clipboard.rs +++ b/src/app/command_driver/clipboard.rs @@ -301,10 +301,13 @@ impl OpenCADStudio { return; } let blocks = self.clipboard_deps.blocks.clone(); + let document = &mut self.tabs[i].scene.document; + merge_app_ids(document, &self.clipboard_deps); + let apps = eed_app_map(document, &self.clipboard_deps); // Single shared copy — see `merge_block_defs`. One batched geometry // rebuild instead of one per definition; same end state as the // per-definition bumps `Scene::define_block_raw` used to run. - if merge_block_defs(&mut self.tabs[i].scene.document, &blocks) > 0 { + if merge_block_defs(document, &blocks, &apps) > 0 { self.tabs[i].scene.bump_geometry(); } } @@ -515,6 +518,51 @@ pub(crate) fn merge_table_records(doc: &mut codec::CadDocument, deps: &crate::ap let _ = doc.dim_styles.add(r); } } + merge_app_ids(doc, deps); +} + +/// Register the copied XDATA's applications this drawing lacks. +pub(crate) fn merge_app_ids(doc: &mut codec::CadDocument, deps: &crate::app::ClipboardDeps) { + for rec in &deps.app_ids { + if !doc.app_ids.contains(&rec.name) { + let mut r = rec.clone(); + r.handle = doc.allocate_handle(); + let _ = doc.app_ids.add(r); + } + } +} + +/// Source APPID handle → this drawing's APPID of the same name, for the +/// copied XDATA. Run after [`merge_app_ids`]. +pub(crate) fn eed_app_map( + doc: &codec::CadDocument, + deps: &crate::app::ClipboardDeps, +) -> rustc_hash::FxHashMap { + deps.app_ids + .iter() + .filter_map(|a| Some((a.handle.value(), doc.app_ids.get(&a.name)?.handle.value()))) + .collect() +} + +/// Re-point a copied entity's verbatim EED at this drawing's APPIDs. A block +/// whose application the map does not know names no APPID here, and a DWG +/// carrying one is rejected outright by the reference program, so it goes. +pub(crate) fn remap_eed_apps(entity: &mut codec::EntityType, apps: &rustc_hash::FxHashMap) { + let remap = |xdata: &mut codec::xdata::ExtendedData| { + xdata.raw_dwg_eed.retain_mut(|(app, _)| match apps.get(app) { + Some(&target) => { + *app = target; + true + } + None => false, + }); + }; + remap(&mut entity.common_mut().extended_data); + if let codec::EntityType::Insert(insert) = entity { + for attribute in &mut insert.attributes { + remap(&mut attribute.common.extended_data); + } + } } /// Recreate block definitions the pasted INSERTs reference but the target @@ -524,11 +572,16 @@ pub(crate) fn merge_table_records(doc: &mut codec::CadDocument, deps: &crate::ap pub(crate) fn merge_block_defs( doc: &mut codec::CadDocument, blocks: &[crate::app::BlockDef], + apps: &rustc_hash::FxHashMap, ) -> usize { let mut created = Vec::new(); for def in blocks { let before = doc.block_records.len(); - define_block_raw_doc(doc, &def.name, def.base_point, &def.entities); + let mut entities = def.entities.clone(); + for entity in &mut entities { + remap_eed_apps(entity, apps); + } + define_block_raw_doc(doc, &def.name, def.base_point, &entities); if doc.block_records.len() > before { created.push(def); } @@ -605,12 +658,14 @@ pub(crate) fn paste_entities_kernel( // missing records with fresh handles from the target document. merge_table_records(doc, deps); // Shared block-definition merge (also used by `merge_clipboard_blocks`). - merge_block_defs(doc, &deps.blocks); + let apps = eed_app_map(doc, deps); + merge_block_defs(doc, &deps.blocks, &apps); let by_index: Vec = entities .iter() .cloned() .map(|mut entity| { + remap_eed_apps(&mut entity, &apps); let picture_placement = crate::scene::render_graph::dim_picture_placement(&entity); if let Some(t) = translate { crate::scene::view::dispatch::apply_transform(&mut entity, t); diff --git a/src/app/commands/refedit.rs b/src/app/commands/refedit.rs index 9011a16b7..72e94addb 100644 --- a/src/app/commands/refedit.rs +++ b/src/app/commands/refedit.rs @@ -920,9 +920,13 @@ impl OpenCADStudio { }) }) .collect(); - crate::app::command_driver::clipboard::merge_block_defs(document, &blocks); + use crate::app::command_driver::clipboard::{eed_app_map, merge_app_ids, remap_eed_apps}; + merge_app_ids(document, &deps); + let apps = eed_app_map(document, &deps); + crate::app::command_driver::clipboard::merge_block_defs(document, &blocks, &apps); for entity in &mut copies { rename_symbols(entity, &rename); + remap_eed_apps(entity, &apps); } (copies, names) } diff --git a/src/app/control/sheets.rs b/src/app/control/sheets.rs index 358b60d1e..3045149ac 100644 --- a/src/app/control/sheets.rs +++ b/src/app/control/sheets.rs @@ -296,13 +296,21 @@ impl OpenCADStudio { // Copy the referenced layer definitions out of the source before the // target borrow starts (the two tabs never borrow together). let mut missing_layers: Vec = Vec::new(); + // The APPIDs their XDATA names, so it is re-pointed at the target's. + let deps = crate::app::ClipboardDeps { + app_ids: crate::app::ClipboardDeps::capture( + &self.tabs[source_index].scene.document, + &cloned, + ) + .app_ids, + ..Default::default() + }; { let source_document = &self.tabs[source_index].scene.document; for entity in &cloned { let layer = entity.common().layer.clone(); if layer.is_empty() || layer == "0" - || source_document.layers.contains(&layer) || missing_layers.iter().any(|l| l.name == layer) { continue; @@ -317,7 +325,6 @@ impl OpenCADStudio { let mut created = Vec::new(); { let scene = &mut self.tabs[target_index].scene; - crate::io::linetypes::populate_document(&mut scene.document); for layer_record in &missing_layers { if !scene.document.layers.contains(&layer_record.name) { let mut layer_record = layer_record.clone(); @@ -325,7 +332,13 @@ impl OpenCADStudio { let _ = scene.document.layers.add(layer_record); } } - for entity in cloned { + use crate::app::command_driver::clipboard::{eed_app_map, merge_app_ids, remap_eed_apps}; + merge_app_ids(&mut scene.document, &deps); + let apps = eed_app_map(&scene.document, &deps); + for mut entity in cloned { + remap_eed_apps(&mut entity, &apps); + let linetype = entity.common().linetype.clone(); + crate::io::linetypes::ensure_catalog_linetype(&mut scene.document, &linetype); created.push(format!("{:X}", scene.add_entity_clone(entity).value())); } } diff --git a/src/app/mod.rs b/src/app/mod.rs index 4a6b1550b..c58598e43 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1908,6 +1908,10 @@ pub struct ClipboardDeps { /// the in-drawing COPY path preserves it. Partly-selected groups are not /// captured (copying one member should not spawn a fragment). See #440. pub groups: Vec, + /// The APPID records the copied XDATA names. EED refers to its + /// application by APPID handle, so a paste into another drawing + /// re-points it at that drawing's own record of the same name. + pub app_ids: Vec, } /// A captured block definition: its base point and the entities it owns @@ -2065,6 +2069,33 @@ impl ClipboardDeps { }) .collect(); + // APPIDs named by the copied XDATA, verbatim EED (by handle) and + // structured records (by name) alike, block members and attributes + // included. + let mut app_handles = rustc_hash::FxHashSet::default(); + let mut app_names = rustc_hash::FxHashSet::default(); + let mut note_apps = |xdata: &codec::xdata::ExtendedData| { + app_handles.extend(xdata.raw_dwg_eed.iter().map(|(app, _)| *app)); + app_names.extend(xdata.records().iter().map(|r| r.application_name.clone())); + }; + for e in entities + .iter() + .chain(blocks.iter().flat_map(|d| d.entities.iter())) + { + note_apps(&e.common().extended_data); + if let EntityType::Insert(insert) = e { + for attribute in &insert.attributes { + note_apps(&attribute.common.extended_data); + } + } + } + let app_ids = doc + .app_ids + .iter() + .filter(|a| app_handles.contains(&a.handle.value()) || app_names.contains(&a.name)) + .cloned() + .collect(); + ClipboardDeps { layers: layers .iter() @@ -2086,6 +2117,7 @@ impl ClipboardDeps { dim_blocks, ext_objects, groups, + app_ids, } } diff --git a/src/app/update/blocks_palette.rs b/src/app/update/blocks_palette.rs index 9d8be52f6..0ad1312a8 100644 --- a/src/app/update/blocks_palette.rs +++ b/src/app/update/blocks_palette.rs @@ -424,17 +424,24 @@ impl OpenCADStudio { let i = self.active_tab; self.push_undo_snapshot(i, "INSERT"); self.merge_dependencies(i, &deps); + let apps = crate::app::command_driver::clipboard::eed_app_map( + &self.tabs[i].scene.document, + &deps, + ); let mut created = Vec::new(); for def in &deps.blocks { let scene = &mut self.tabs[i].scene; + let mut entities = def.entities.clone(); + for entity in &mut entities { + crate::app::command_driver::clipboard::remap_eed_apps(entity, &apps); + } if redefine && def.name.eq_ignore_ascii_case(name) { - let entities = def.entities.clone(); if let Err(err) = scene.redefine_block_raw(&def.name, def.base_point, entities) { self.command_line.push_error(&err); return Err(err); } } else if scene.document.block_records.get(&def.name).is_none() { - scene.define_block_raw(&def.name, def.base_point, def.entities.clone()); + scene.define_block_raw(&def.name, def.base_point, entities); created.push(def); } }