diff --git a/src/app/command_driver/clipboard.rs b/src/app/command_driver/clipboard.rs index 73baecd4d..383c69413 100644 --- a/src/app/command_driver/clipboard.rs +++ b/src/app/command_driver/clipboard.rs @@ -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| { diff --git a/src/app/update/file.rs b/src/app/update/file.rs index f671242bd..d5618c9dd 100644 --- a/src/app/update/file.rs +++ b/src/app/update/file.rs @@ -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 diff --git a/src/scene/dimension_assoc.rs b/src/scene/dimension_assoc.rs index cf469b6b2..0498fb713 100644 --- a/src/scene/dimension_assoc.rs +++ b/src/scene/dimension_assoc.rs @@ -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)> = 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 = 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 +} diff --git a/src/scene/group_layer.rs b/src/scene/group_layer.rs index 03c19ad92..71c857eee 100644 --- a/src/scene/group_layer.rs +++ b/src/scene/group_layer.rs @@ -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) diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 56f5be5c3..a2b7c264d 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -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; diff --git a/src/scene/modify.rs b/src/scene/modify.rs index 2ffb9bea6..ac069dcaf 100644 --- a/src/scene/modify.rs +++ b/src/scene/modify.rs @@ -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, +) { + for © 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(); + } +}