From 10911681964420fb056ad09a04f39bac48040b38 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Mon, 28 Sep 2026 18:34:52 +0300 Subject: [PATCH] fix(copy): copied associative hatches follow the copied boundary The copy kept the source boundary handles, so editing the source regenerated the copy onto the source outline and it seemed to vanish. Fixes #1370 Co-Authored-By: Claude Opus 5.5 --- src/scene/boundary.rs | 35 +++++++++++++++++++++++++++++++++++ src/scene/modify.rs | 1 + 2 files changed, 36 insertions(+) diff --git a/src/scene/boundary.rs b/src/scene/boundary.rs index 01d398e00..6db167936 100644 --- a/src/scene/boundary.rs +++ b/src/scene/boundary.rs @@ -729,6 +729,41 @@ impl Scene { self.refresh_fill_model(handle); } + /// A copied associative hatch must follow the copied boundary, not the + /// source one — otherwise editing the source regenerates the copy onto the + /// source's outline and the copy seems to vanish. A path whose boundary + /// was not copied along loses its association. (#1370) + pub(crate) fn copy_hatch_associations( + &mut self, + handle_map: &rustc_hash::FxHashMap, + ) { + let mut touched = false; + for © in handle_map.values() { + let Some(EntityType::Hatch(hatch)) = self.document.get_entity_mut(copy) else { + continue; + }; + if !hatch.is_associative { + continue; + } + for path in &mut hatch.paths { + if path.boundary_handles.iter().all(|source| handle_map.contains_key(source)) { + for source in &mut path.boundary_handles { + *source = handle_map[source]; + } + } else { + path.boundary_handles.clear(); + path.flags.set_external(false); + } + } + hatch.is_associative = + hatch.paths.iter().any(|path| !path.boundary_handles.is_empty()); + touched = true; + } + if touched { + self.associative_hatch_source_cache.borrow_mut().take(); + } + } + pub(crate) fn edit_hatch_boundary_handles( &mut self, hatch_handle: Handle, diff --git a/src/scene/modify.rs b/src/scene/modify.rs index 7de123e14..0384d6c3c 100644 --- a/src/scene/modify.rs +++ b/src/scene/modify.rs @@ -810,6 +810,7 @@ impl Scene { // entry as targeted object deltas inside copy_complete_groups. self.copy_complete_groups(&handle_map); self.copy_dimension_associations(&handle_map); + self.copy_hatch_associations(&handle_map); // The copies are new handles (natural memo misses, tessellated fresh) // and reference only already-cached blocks — no block defn changes. // Report them as additions so derived caches patch in exactly the copies.