forked from 1b-agent/opencadstudio-upstream
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:
1 parent
e2f558ae4f
commit
9adb6f59c8
6 files changed
+80
No files matched your search
@@ -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| {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 © 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();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user