fix(copy): a copy reacts only to what was copied with it

COPY and PASTE kept the source's reactors on each copy, so a copied
dimension still pointed at the source's dimension association (and
boundary or group members at the source hatch or group). Another
application reports such a drawing as damaged and, after recovery,
moves the dimensions. A copy's reactors now follow only targets copied
with it; group copies give their members the new group's reactor; and a
drawing opened with such stray dimension reactors has them dropped.

Fixes #1645
Fixes #1649

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-10-08 08:34:49 +03:00
1 parent e2f558ae4f
commit 9adb6f59c8
6 files changed
+80

No files matched your search

+2
View File
@@ -578,6 +578,8 @@ pub(crate) fn paste_entities_kernel(
// A pasted associative hatch follows the pasted boundary, or drops the
// association when the boundary was not pasted with it. (#1370)
crate::scene::remap_hatch_associations(doc, &handle_map);
// Reactors on the source drawing's objects do not travel with the paste.
crate::scene::remap_copied_reactors(doc, &handle_map);
let leader_links: Vec<(Handle, Handle)> = entities
.iter()
.filter_map(|source| {
+5
View File
@@ -1883,6 +1883,11 @@ impl OpenCADStudio {
// A file saved without the built-in Standard styles (foreign
// or damaged) gets them re-seeded so nothing dangles (#366).
crate::app::style_ops::ensure_standard_styles(&mut self.tabs[i].scene.document);
// Copies made by earlier releases kept their source's dimension
// association; such a file is reported as damaged elsewhere.
crate::scene::dimension_assoc::drop_stray_dimension_reactors(
&mut self.tabs[i].scene.document,
);
// Follow the file's saved current UCS from the moment it opens.
self.tabs[i].adopt_active_ucs_from_header();
// Adopt the drawing's own Ortho ($ORTHOMODE) and running OSNAP
+47
View File
@@ -2434,3 +2434,50 @@ fn persisted_measurement_scale(
viewport_compensation: calculated,
})
}
/// Drop reactors an entity holds on a dimension association it has no part
/// in: neither the association's dimension nor geometry it references. Copies
/// made by earlier releases kept their source's association this way, which
/// the reference application reports as a damaged drawing. Returns how many
/// reactors were dropped.
pub(crate) fn drop_stray_dimension_reactors(document: &mut codec::CadDocument) -> usize {
let associations: rustc_hash::FxHashMap<Handle, (Handle, Vec<Handle>)> = document
.objects
.iter()
.filter_map(|(handle, object)| {
let ObjectType::Associative(object) = object else {
return None;
};
let AssociativeData::DimensionAssociation(association) = &object.data else {
return None;
};
let geometry = association
.references
.iter()
.flatten()
.flat_map(|reference| {
reference.xrefs.iter().chain(&reference.intersection_objects).copied()
})
.collect();
Some((*handle, (association.dimension, geometry)))
})
.collect();
if associations.is_empty() {
return 0;
}
let mut dropped = 0;
let handles: Vec<Handle> = document.entities().map(|entity| entity.common().handle).collect();
for handle in handles {
let Some(entity) = document.get_entity_mut(handle) else {
continue;
};
let reactors = &mut entity.common_mut().reactors;
let before = reactors.len();
reactors.retain(|reactor| match associations.get(reactor) {
Some((dimension, geometry)) => *dimension == handle || geometry.contains(&handle),
None => true,
});
dropped += before - reactors.len();
}
dropped
}
+7
View File
@@ -114,6 +114,13 @@ impl Scene {
}
self.record_undo_object_before(gh, None);
}
for member in group.entities.clone() {
if let Some(entity) = self.document.get_entity_mut(member) {
if !entity.common().reactors.contains(&gh) {
entity.common_mut().reactors.push(gh);
}
}
}
self.document.objects.insert(gh, ObjectType::Group(group));
if let Some(ObjectType::Dictionary(dict)) =
self.document.objects.get_mut(&group_dict_handle)
+1
View File
@@ -44,6 +44,7 @@ mod group_layer;
mod layout;
mod limits;
mod modify;
pub(crate) use modify::remap_copied_reactors;
mod mspace;
pub mod viewport_ref;
pub mod viewport_dimension_pick;
+18
View File
@@ -823,6 +823,7 @@ impl Scene {
let _ = self.sync_displayed_annotation_context(leader_handle);
}
remap_copied_reactors(&mut self.document, &handle_map);
// Complete group copies record their new Group objects and dictionary
// entry as targeted object deltas inside copy_complete_groups.
self.copy_complete_groups(&handle_map);
@@ -1803,3 +1804,20 @@ impl Scene {
}
}
}
/// A copy reacts only to what was copied with it: each reactor of the source
/// that maps to a copy follows it there, and the rest (the source's dimension
/// association, group, associative hatch) are dropped. The association and
/// group copies add their own reactors afterwards.
pub(crate) fn remap_copied_reactors(
document: &mut codec::CadDocument,
handle_map: &rustc_hash::FxHashMap<Handle, Handle>,
) {
for &copy in handle_map.values() {
let Some(entity) = document.get_entity_mut(copy) else {
continue;
};
let reactors = &mut entity.common_mut().reactors;
*reactors = reactors.iter().filter_map(|reactor| handle_map.get(reactor).copied()).collect();
}
}