diff --git a/src/app/command_driver/clipboard.rs b/src/app/command_driver/clipboard.rs index d566fa7fa..74738f0f7 100644 --- a/src/app/command_driver/clipboard.rs +++ b/src/app/command_driver/clipboard.rs @@ -170,11 +170,20 @@ impl OpenCADStudio { } let label = self.history_label_from_active_cmd(i, "TRIM"); self.push_undo_snapshot(i, label); - self.tabs[i].scene.erase_entities(&[handle]); + // A replacement reusing the original handle must land after the erase; + // fresh pieces take over its hatch associations before it goes. + let reuses_handle = new_entities.iter().any(|e| e.common().handle == handle); + if reuses_handle { + self.tabs[i].scene.erase_entities(&[handle]); + } let new_handles: Vec = new_entities .into_iter() .map(|e| self.tabs[i].scene.add_entity(e)) .collect(); + if !reuses_handle { + self.tabs[i].scene.split_hatch_source(handle, &new_handles); + self.tabs[i].scene.erase_entities(&[handle]); + } // Rebuild replaced dimensions from edited data. for &nh in &new_handles { if matches!( diff --git a/src/app/command_driver/mod.rs b/src/app/command_driver/mod.rs index 21b19199e..33edae656 100644 --- a/src/app/command_driver/mod.rs +++ b/src/app/command_driver/mod.rs @@ -381,8 +381,7 @@ impl OpenCADStudio { .document .get_entity(handle) .map(|entity| entity.common().owner_handle); - self.tabs[tab].scene.erase_entities(&[handle]); - entities + let pieces: Vec = entities .into_iter() .map(|mut entity| { entity.common_mut().handle = Handle::NULL; @@ -391,7 +390,11 @@ impl OpenCADStudio { } self.tabs[tab].scene.add_entity(entity) }) - .collect() + .collect(); + // Hand the hatch associations over before the original goes. + self.tabs[tab].scene.split_hatch_source(handle, &pieces); + self.tabs[tab].scene.erase_entities(&[handle]); + pieces }; for &updated in &handles { if matches!( diff --git a/src/scene/boundary.rs b/src/scene/boundary.rs index da573722e..1c7aae65e 100644 --- a/src/scene/boundary.rs +++ b/src/scene/boundary.rs @@ -816,6 +816,48 @@ impl Scene { true } + /// A boundary source split into pieces (TRIM, BREAK): every associative + /// hatch bounded by `old` is bounded by all of `pieces` instead, so the + /// region survives `old` being erased. + pub(crate) fn split_hatch_source(&mut self, old: Handle, pieces: &[Handle]) { + if pieces.is_empty() { + return; + } + let dependents = self.associative_hatch_dependents(&std::iter::once(old).collect()); + if dependents.is_empty() { + return; + } + for &hatch_handle in &dependents { + if self.is_recording_undo() { + let before = self.document.get_entity_arc(hatch_handle); + self.record_undo_before(hatch_handle, before); + } + let Some(EntityType::Hatch(hatch)) = self.document.get_entity_mut(hatch_handle) else { + continue; + }; + for path in &mut hatch.paths { + if !path.boundary_handles.contains(&old) { + continue; + } + path.boundary_handles.retain(|source| *source != old); + for &piece in pieces { + if !path.boundary_handles.contains(&piece) { + path.boundary_handles.push(piece); + } + } + } + } + self.associative_hatch_source_cache.borrow_mut().take(); + let changes: Vec<_> = pieces + .iter() + .map(|&piece| (piece, ChangeKind::Modified)) + .collect(); + let refreshed = self.refresh_associative_hatches(&changes); + if !refreshed.is_empty() { + self.bump_entities(&refreshed); + } + } + fn associative_hatch_dependents( &self, changed: &rustc_hash::FxHashSet,