diff --git a/src/app/command_driver/clipboard.rs b/src/app/command_driver/clipboard.rs index e7280b189..1b841209f 100644 --- a/src/app/command_driver/clipboard.rs +++ b/src/app/command_driver/clipboard.rs @@ -469,8 +469,9 @@ impl OpenCADStudio { } let captures = self.clipboard_deps.ext_objects.clone(); let doc = &mut self.tabs[i].scene.document; + let records = block_record_map(doc, &self.clipboard_deps.blocks); for cap in &captures { - if let Some(new_root) = recreate_ext_subtree(doc, cap, None) { + if let Some(new_root) = recreate_ext_subtree(doc, cap, None, &records) { out.insert(cap.entity_index, new_root); } } @@ -524,13 +525,56 @@ pub(crate) fn merge_block_defs( doc: &mut codec::CadDocument, blocks: &[crate::app::BlockDef], ) -> usize { - let mut created = 0; + 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); - created += usize::from(doc.block_records.len() > before); + if doc.block_records.len() > before { + created.push(def); + } } - created + // Graphs name other records (an evaluated copy names its definition), so + // they go in once every record exists. + let records = block_record_map(doc, blocks); + for def in &created { + import_block_def_graph(doc, def, &records); + } + created.len() +} + +/// Source record → this drawing's record of the same name, per definition. +pub(crate) fn block_record_map( + doc: &codec::CadDocument, + blocks: &[crate::app::BlockDef], +) -> std::collections::HashMap { + blocks + .iter() + .filter_map(|def| Some((def.source, doc.block_records.get(&def.name)?.handle))) + .collect() +} + +/// Carry `def`'s object graph (a dynamic block's parameters and states) onto +/// the record just defined from it here. `records` maps the source records +/// the graph may name. +pub(crate) fn import_block_def_graph( + doc: &mut codec::CadDocument, + def: &crate::app::BlockDef, + records: &std::collections::HashMap, +) { + let Some(graph) = &def.graph else { + return; + }; + let Some(record) = doc.block_records.get(&def.name).map(|record| record.handle) else { + return; + }; + let members = crate::scene::block_authoring::members(doc, record); + if members.len() != def.entities.len() { + return; + } + let mut map = records.clone(); + map.extend(def.entities.iter().map(|e| e.common().handle).zip(members)); + doc.import_block_graph(graph, record, &map); + crate::scene::block_authoring::retag_members(doc, record); } /// (`NULL` where an add failed) plus the source-handle → pasted-handle map. @@ -612,6 +656,7 @@ pub(crate) fn paste_entities_kernel( Some(crate::command::EntityTransform::Translate(delta)) => *delta, _ => glam::DVec3::ZERO, }; + let records = block_record_map(doc, &deps.blocks); for cap in &deps.ext_objects { let Some(&new_entity) = by_index.get(cap.entity_index) else { continue; @@ -619,7 +664,7 @@ pub(crate) fn paste_entities_kernel( if new_entity.is_null() { continue; } - if let Some(new_root) = recreate_ext_subtree(doc, cap, Some(new_entity)) { + if let Some(new_root) = recreate_ext_subtree(doc, cap, Some(new_entity), &records) { if let Some(e) = doc.get_entity_mut(new_entity) { e.common_mut().xdictionary_handle = Some(new_root); } @@ -837,9 +882,10 @@ fn recreate_ext_subtree( doc: &mut codec::CadDocument, cap: &crate::app::ClipExtObjects, entity_handle: Option, + records: &std::collections::HashMap, ) -> Option { use std::collections::HashMap; - let mut remap: HashMap = HashMap::new(); + let mut remap: HashMap = records.clone(); if let Some(eh) = entity_handle { remap.insert(cap.src_entity_handle, eh); } @@ -854,6 +900,14 @@ fn recreate_ext_subtree( let mut obj = obj.clone(); let new_h = remap[old]; remap_object(&mut obj, new_h, &remap); + // A dynamic reference's link to its definition, indexed. + if let codec::objects::ObjectType::DynamicBlock(object) = &obj { + if let codec::objects::DynamicBlockData::Representation(link) = &object.data { + if object.dxf_name == "ACDB_BLOCKREPRESENTATION_DATA" { + doc.block_representations.insert(new_h, link.block); + } + } + } doc.objects.insert(new_h, obj); } remap.get(&cap.root).copied() @@ -1013,6 +1067,10 @@ pub(crate) fn remap_object( _ => {} } } + ObjectType::DynamicBlock(object) => { + object.handle = new_handle; + object.visit_handles_mut(&mut |h| *h = map(*h)); + } // Other leaf object kinds don't appear in an entity xdictionary; if one // does, it's inserted with the fresh handle below via the caller's key, // but its internal owner is left as-is (best effort). @@ -1082,7 +1140,7 @@ mod clipboard_xdict_clone_tests { let before: Vec = dst.objects.keys().copied().collect(); let dst_entity = dst.allocate_handle(); - let new_root = recreate_ext_subtree(&mut dst, &cap, Some(dst_entity)) + let new_root = recreate_ext_subtree(&mut dst, &cap, Some(dst_entity), &Default::default()) .expect("recreated xdictionary root"); assert!(!new_root.is_null()); assert!(!before.contains(&new_root), "clone must use a fresh handle"); @@ -1119,7 +1177,8 @@ mod clipboard_xdict_clone_tests { let mut dst = codec::CadDocument::new(); let before_len = dst.objects.len(); - let root = recreate_ext_subtree(&mut dst, &cap, None).expect("recreated xdictionary root"); + let root = recreate_ext_subtree(&mut dst, &cap, None, &Default::default()) + .expect("recreated xdictionary root"); let leaf = leaf_of(&dst, root); match dst.objects.get(&leaf) { Some(ObjectType::XRecord(x)) => assert!( diff --git a/src/app/commands/refedit.rs b/src/app/commands/refedit.rs index a06969b6a..9011a16b7 100644 --- a/src/app/commands/refedit.rs +++ b/src/app/commands/refedit.rs @@ -915,8 +915,8 @@ impl OpenCADStudio { } Some(crate::app::BlockDef { name: used, - base_point: def.base_point, entities, + ..def.clone() }) }) .collect(); diff --git a/src/app/mod.rs b/src/app/mod.rs index 782bc15e0..bf5592fc8 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1916,6 +1916,10 @@ pub struct BlockDef { pub name: String, pub base_point: codec::types::Vector3, pub entities: Vec, + /// The source drawing's record, which other captured graphs name. + pub source: codec::Handle, + /// The record's object graph: a dynamic block's parameters and states. + pub graph: Option, } /// The extension-dictionary object graph captured for one copied entity. @@ -2131,8 +2135,16 @@ impl ClipboardDeps { let mut queue: Vec = Vec::new(); for e in entities { if let EntityType::Insert(ins) = e { - if seen.insert(ins.block_name.clone()) { - queue.push(ins.block_name.clone()); + // A reference showing an evaluated copy needs its dynamic + // definition too. + let definition = doc + .dynamic_definition_for_insert(ins.common.handle) + .and_then(|h| doc.block_records.iter().find(|r| r.handle == h)) + .map(|record| record.name.clone()); + for name in std::iter::once(ins.block_name.clone()).chain(definition) { + if seen.insert(name.clone()) { + queue.push(name); + } } } } @@ -2182,6 +2194,8 @@ impl ClipboardDeps { name: name.to_string(), base_point, entities: owned, + source: br.handle, + graph: doc.export_block_graph(br.handle), }) } diff --git a/src/app/update/blocks_palette.rs b/src/app/update/blocks_palette.rs index 88189d53a..9d8be52f6 100644 --- a/src/app/update/blocks_palette.rs +++ b/src/app/update/blocks_palette.rs @@ -424,18 +424,27 @@ impl OpenCADStudio { let i = self.active_tab; self.push_undo_snapshot(i, "INSERT"); self.merge_dependencies(i, &deps); - for def in deps.blocks { + let mut created = Vec::new(); + for def in &deps.blocks { let scene = &mut self.tabs[i].scene; if redefine && def.name.eq_ignore_ascii_case(name) { - if let Err(err) = scene.redefine_block_raw(&def.name, def.base_point, def.entities) - { + 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); + scene.define_block_raw(&def.name, def.base_point, def.entities.clone()); + created.push(def); } } + // A dynamic definition keeps its parameters and states. + use crate::app::command_driver::clipboard::{block_record_map, import_block_def_graph}; + let document = &mut self.tabs[i].scene.document; + let records = block_record_map(document, &deps.blocks); + for def in created { + import_block_def_graph(document, def, &records); + } self.tabs[i].scene.populate_meshes_from_document(); self.tabs[i].dirty = true; Ok(()) diff --git a/src/scene/block_authoring.rs b/src/scene/block_authoring.rs index bb1ce7a1d..f67140671 100644 --- a/src/scene/block_authoring.rs +++ b/src/scene/block_authoring.rs @@ -348,7 +348,7 @@ pub(super) fn dynamic( object } -pub(super) fn members(document: &CadDocument, definition: Handle) -> Vec { +pub(crate) fn members(document: &CadDocument, definition: Handle) -> Vec { document .block_records .iter() @@ -409,6 +409,16 @@ pub(super) fn set_entity_tag( } } +/// Point each tagged member's element tag back at the member itself, after +/// the definition was copied onto new handles. +pub(crate) fn retag_members(document: &mut CadDocument, definition: Handle) { + for handle in members(document, definition) { + if let Some(id) = document.get_entity(handle).and_then(element_id) { + set_entity_tag(document, handle, element_tag(id, handle)); + } + } +} + /// Give every object of the definition its element tag, numbering the new /// ones after the highest id in use, and record the next id on the block. pub(super) fn tag_members(document: &mut CadDocument, definition: Handle) { diff --git a/src/scene/block_visibility.rs b/src/scene/block_visibility.rs index 6934f209c..6c88a883c 100644 --- a/src/scene/block_visibility.rs +++ b/src/scene/block_visibility.rs @@ -521,11 +521,7 @@ impl Scene { record.flags.anonymous = true; let copy = record.handle; // The copies keep their element ids, now naming themselves. - for handle in members(&self.document, copy) { - if let Some(id) = self.document.get_entity(handle).and_then(element_id) { - set_entity_tag(&mut self.document, handle, element_tag(id, handle)); - } - } + super::block_authoring::retag_members(&mut self.document, copy); let mut tag = ExtendedDataRecord::new(REPRESENTATION_TAG); tag.add_value(XDataValue::Integer16(1)); tag.add_value(XDataValue::Handle(definition));