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 <[email protected]>
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;
}
// 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;
+31 -9
View File
@@ -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<String> {
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<EntityType> = 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)
}
}
+13
View File
@@ -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])
}