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 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-10-16 00:11:56 +03:00
1 parent b947f99a87
commit 98cce048c7
3 files changed
+49 -26

No files matched your search

+5 -17
View File
@@ -186,19 +186,11 @@ impl OpenCADStudio {
return; return;
} }
// A reference to the definition itself gets an evaluated copy first, // A reference to the definition itself, or to a copy other
// so the state picked for it leaves the definition alone. // references share, gets its own evaluated copy first, so the state
let direct = { // picked for it leaves the definition and the others alone.
let doc = &self.tabs[i].scene.document; self.push_undo_snapshot(i, "Visibility State");
matches!(doc.get_entity(insert_handle), Some(EntityType::Insert(ins)) self.tabs[i].scene.visibility_representation(insert_handle);
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);
}
// Resolve everything against an immutable borrow first. // Resolve everything against an immutable borrow first.
let mapping = { let mapping = {
@@ -216,10 +208,6 @@ impl OpenCADStudio {
return; return;
}; };
if !direct {
self.push_undo_snapshot(i, "Visibility State");
}
// Apply invisibility flags to the anonymous block's members. // Apply invisibility flags to the anonymous block's members.
{ {
let doc = &mut self.tabs[i].scene.document; let doc = &mut self.tabs[i].scene.document;
+31 -9
View File
@@ -464,18 +464,31 @@ pub fn finish_editing(document: &mut CadDocument, definition: Handle) {
impl Scene { impl Scene {
/// Give a reference to a dynamic definition its own evaluated copy, so a /// Give a reference to a dynamic definition its own evaluated copy, so a
/// state picked for it leaves the definition and the other references /// state picked for it leaves the definition and the other references
/// alone. Returns the copy's name; a reference already showing a copy, /// alone. A copy shared with other references (after COPY) is copied
/// or not dynamic, keeps its block and gets `None`. /// 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<String> { pub fn visibility_representation(&mut self, insert: Handle) -> Option<String> {
let Some(EntityType::Insert(reference)) = self.document.get_entity(insert) else { let Some(EntityType::Insert(reference)) = self.document.get_entity(insert) else {
return None; return None;
}; };
let definition = self let source = self
.document .document
.block_records .block_records
.get(&reference.block_name)? .get(&reference.block_name)?
.handle; .handle;
let definition = self.document.dynamic_definition_for_insert(insert)?;
parameter(&self.document, definition)?; 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 name = {
let next = self let next = self
.document .document
@@ -490,7 +503,7 @@ impl Scene {
.document .document
.block_records .block_records
.iter() .iter()
.find(|record| record.handle == definition) .find(|record| record.handle == source)
.and_then( .and_then(
|record| match self.document.get_entity(record.block_entity_handle) { |record| match self.document.get_entity(record.block_entity_handle) {
Some(EntityType::Block(block)) => Some(block.base_point), Some(EntityType::Block(block)) => Some(block.base_point),
@@ -498,7 +511,7 @@ impl Scene {
}, },
) )
.unwrap_or(Vector3::ZERO); .unwrap_or(Vector3::ZERO);
let originals = members(&self.document, definition); let originals = members(&self.document, source);
let copies: Vec<EntityType> = originals let copies: Vec<EntityType> = originals
.iter() .iter()
.filter_map(|handle| self.document.get_entity(*handle).cloned()) .filter_map(|handle| self.document.get_entity(*handle).cloned())
@@ -518,6 +531,11 @@ impl Scene {
tag.add_value(XDataValue::Handle(definition)); tag.add_value(XDataValue::Handle(definition));
self.document.set_object_xdata_record(copy, tag); self.document.set_object_xdata_record(copy, tag);
self.document.ensure_extension_dictionary(copy); 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 // The reference: AcDbBlockRepresentation / AcDbRepData names the
// definition it was evaluated from. // definition it was evaluated from.
@@ -550,18 +568,22 @@ impl Scene {
.objects .objects
.insert(data, ObjectType::DynamicBlock(link)); .insert(data, ObjectType::DynamicBlock(link));
self.document.block_representations.insert(data, definition); 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) { 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() { for record in self.document.block_records.iter_mut() {
if record.handle == definition { if record.handle == from {
record.insert_handles.retain(|handle| *handle != insert); record.insert_handles.retain(|handle| *handle != insert);
} else if record.handle == copy { } else if record.handle == to {
record.insert_handles.push(insert); record.insert_handles.push(insert);
} }
} }
self.bump_geometry(); self.bump_geometry();
Some(name)
} }
} }
+13
View File
@@ -688,6 +688,7 @@ impl Scene {
} }
}; };
let mut clones = Vec::with_capacity(order.len()); let mut clones = Vec::with_capacity(order.len());
let mut representations = Vec::new();
for &h in &order { for &h in &order {
let new_h = remap[&h]; let new_h = remap[&h];
let obj = doc.objects.get(&h)?; let obj = doc.objects.get(&h)?;
@@ -726,6 +727,17 @@ impl Scene {
x.raw_dwg_version = None; x.raw_dwg_version = None;
x.preserve_object_reference_stream = false; 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)); clones.push((new_h, obj));
@@ -734,6 +746,7 @@ impl Scene {
doc.objects.insert(new_h, obj); doc.objects.insert(new_h, obj);
created.push(new_h); created.push(new_h);
} }
doc.block_representations.extend(representations);
Some(remap[&src_root]) Some(remap[&src_root])
} }