From 98cce048c76a36f69e40a261f1fd6e87fa3e0f07 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Fri, 16 Oct 2026 00:11:55 +0300 Subject: [PATCH] fix(blocks): a copied dynamic reference changes state on its own (#1818) COPY cloned the reference's AcDbRepData without its handle, owner or representation index, and both references kept sharing one evaluated *U block, so a state picked on either changed both. The clone now names itself and is indexed; a state change gives a reference whose *U other references share its own copy first. Co-Authored-By: Claude Opus 5.5 --- src/app/visibility.rs | 22 +++++-------------- src/scene/block_visibility.rs | 40 +++++++++++++++++++++++++++-------- src/scene/modify.rs | 13 ++++++++++++ 3 files changed, 49 insertions(+), 26 deletions(-) diff --git a/src/app/visibility.rs b/src/app/visibility.rs index 856085235..60d38bd15 100644 --- a/src/app/visibility.rs +++ b/src/app/visibility.rs @@ -186,19 +186,11 @@ impl OpenCADStudio { return; } - // A reference to the definition itself gets an evaluated copy first, - // so the state picked for it leaves the definition alone. - let direct = { - let doc = &self.tabs[i].scene.document; - matches!(doc.get_entity(insert_handle), Some(EntityType::Insert(ins)) - if doc.block_records.get(&ins.block_name).is_some_and(|record| { - crate::scene::block_visibility::parameter(doc, record.handle).is_some() - })) - }; - if direct { - self.push_undo_snapshot(i, "Visibility State"); - self.tabs[i].scene.visibility_representation(insert_handle); - } + // A reference to the definition itself, or to a copy other + // references share, gets its own evaluated copy first, so the state + // picked for it leaves the definition and the others alone. + self.push_undo_snapshot(i, "Visibility State"); + self.tabs[i].scene.visibility_representation(insert_handle); // Resolve everything against an immutable borrow first. let mapping = { @@ -216,10 +208,6 @@ impl OpenCADStudio { return; }; - if !direct { - self.push_undo_snapshot(i, "Visibility State"); - } - // Apply invisibility flags to the anonymous block's members. { let doc = &mut self.tabs[i].scene.document; diff --git a/src/scene/block_visibility.rs b/src/scene/block_visibility.rs index abcbdd27e..6934f209c 100644 --- a/src/scene/block_visibility.rs +++ b/src/scene/block_visibility.rs @@ -464,18 +464,31 @@ pub fn finish_editing(document: &mut CadDocument, definition: Handle) { impl Scene { /// Give a reference to a dynamic definition its own evaluated copy, so a /// state picked for it leaves the definition and the other references - /// alone. Returns the copy's name; a reference already showing a copy, - /// or not dynamic, keeps its block and gets `None`. + /// alone. A copy shared with other references (after COPY) is copied + /// again with its current state. Returns the copy's name; a reference + /// already showing its own copy, or not dynamic, gets `None`. pub fn visibility_representation(&mut self, insert: Handle) -> Option { let Some(EntityType::Insert(reference)) = self.document.get_entity(insert) else { return None; }; - let definition = self + let source = self .document .block_records .get(&reference.block_name)? .handle; + let definition = self.document.dynamic_definition_for_insert(insert)?; parameter(&self.document, definition)?; + let direct = source == definition; + let shared = || { + self.document.entities().any(|entity| { + matches!(entity, EntityType::Insert(other) + if other.common.handle != insert + && other.block_name == reference.block_name) + }) + }; + if !direct && !shared() { + return None; + } let name = { let next = self .document @@ -490,7 +503,7 @@ impl Scene { .document .block_records .iter() - .find(|record| record.handle == definition) + .find(|record| record.handle == source) .and_then( |record| match self.document.get_entity(record.block_entity_handle) { Some(EntityType::Block(block)) => Some(block.base_point), @@ -498,7 +511,7 @@ impl Scene { }, ) .unwrap_or(Vector3::ZERO); - let originals = members(&self.document, definition); + let originals = members(&self.document, source); let copies: Vec = originals .iter() .filter_map(|handle| self.document.get_entity(*handle).cloned()) @@ -518,6 +531,11 @@ impl Scene { tag.add_value(XDataValue::Handle(definition)); self.document.set_object_xdata_record(copy, tag); self.document.ensure_extension_dictionary(copy); + if !direct { + // Already linked to its definition; only its block changes. + self.repoint_representation(insert, source, copy, &name); + return Some(name); + } // The reference: AcDbBlockRepresentation / AcDbRepData names the // definition it was evaluated from. @@ -550,18 +568,22 @@ impl Scene { .objects .insert(data, ObjectType::DynamicBlock(link)); self.document.block_representations.insert(data, definition); + self.repoint_representation(insert, source, copy, &name); + Some(name) + } + + fn repoint_representation(&mut self, insert: Handle, from: Handle, to: Handle, name: &str) { if let Some(EntityType::Insert(reference)) = self.document.get_entity_mut(insert) { - reference.block_name = name.clone(); + reference.block_name = name.to_string(); } for record in self.document.block_records.iter_mut() { - if record.handle == definition { + if record.handle == from { record.insert_handles.retain(|handle| *handle != insert); - } else if record.handle == copy { + } else if record.handle == to { record.insert_handles.push(insert); } } self.bump_geometry(); - Some(name) } } diff --git a/src/scene/modify.rs b/src/scene/modify.rs index 7fd0d5ec0..e2ff5f3fc 100644 --- a/src/scene/modify.rs +++ b/src/scene/modify.rs @@ -688,6 +688,7 @@ impl Scene { } }; let mut clones = Vec::with_capacity(order.len()); + let mut representations = Vec::new(); for &h in &order { let new_h = remap[&h]; let obj = doc.objects.get(&h)?; @@ -726,6 +727,17 @@ impl Scene { x.raw_dwg_version = None; x.preserve_object_reference_stream = false; } + // A dynamic reference's AcDbRepData naming its definition. + codec::objects::ObjectType::DynamicBlock(b) => { + b.handle = new_h; + b.owner = rewrite(b.owner); + for r in &mut b.reactors { + *r = rewrite(*r); + } + if let Some(&definition) = doc.block_representations.get(&h) { + representations.push((new_h, definition)); + } + } _ => {} } clones.push((new_h, obj)); @@ -734,6 +746,7 @@ impl Scene { doc.objects.insert(new_h, obj); created.push(new_h); } + doc.block_representations.extend(representations); Some(remap[&src_root]) }