fix(hatch): keep the association when TRIM splits a boundary

Splitting a boundary object erased it and added the pieces, so the hatch
lost that source and rebuilt from what was left, often the whole circle.
The pieces now take over the old object's association before it is
erased.

Fixes #1572

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-10-04 20:44:08 +03:00
1 parent fd01a36ad3
commit 6b5c2758e1
3 files changed
+58 -4

No files matched your search

+10 -1
View File
@@ -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<codec::Handle> = 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!(
+6 -3
View File
@@ -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<Handle> = 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!(
+42
View File
@@ -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<Handle>,