From 3754426361e828cb4c0300cb5b36138d623aebfc Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Mon, 28 Sep 2026 20:33:54 +0300 Subject: [PATCH] fix(paste): pasted associative hatches follow the pasted boundary Paste (and WBLOCK, which shares the kernel) kept the source boundary handles, so the pasted hatch stayed tied to the original outline or to unrelated handles in another drawing. The COPY remap now lives at the document level and runs for both. Refs #1370 Co-Authored-By: Claude Opus 5.5 --- src/app/command_driver/clipboard.rs | 3 ++ src/scene/boundary.rs | 62 ++++++++++++++++------------- src/scene/mod.rs | 2 +- 3 files changed, 39 insertions(+), 28 deletions(-) diff --git a/src/app/command_driver/clipboard.rs b/src/app/command_driver/clipboard.rs index c708a60a5..d566fa7fa 100644 --- a/src/app/command_driver/clipboard.rs +++ b/src/app/command_driver/clipboard.rs @@ -543,6 +543,9 @@ pub(crate) fn paste_entities_kernel( handle_map.insert(source.common().handle, copied); } } + // 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); let leader_links: Vec<(Handle, Handle)> = entities .iter() .filter_map(|source| { diff --git a/src/scene/boundary.rs b/src/scene/boundary.rs index 6db167936..da573722e 100644 --- a/src/scene/boundary.rs +++ b/src/scene/boundary.rs @@ -720,6 +720,39 @@ pub(crate) fn boundary_entities_from_sources( .collect() } +/// A duplicated associative hatch must follow the duplicated boundary, not the +/// source one — otherwise editing the source regenerates the duplicate onto +/// the source's outline and it seems to vanish. A path whose boundary was not +/// duplicated along loses its association. `handle_map` maps source handles +/// to their duplicates (COPY and paste both build one). (#1370) +pub(crate) fn remap_hatch_associations( + doc: &mut codec::CadDocument, + handle_map: &rustc_hash::FxHashMap, +) -> bool { + let mut touched = false; + for © in handle_map.values() { + let Some(EntityType::Hatch(hatch)) = doc.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; + } + touched +} + impl Scene { pub(crate) fn replace_hatch_association(&mut self,handle:Handle,paths:Vec) { let Some(EntityType::Hatch(hatch))=self.document.get_entity_mut(handle) else{return;}; @@ -729,37 +762,12 @@ 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) + /// COPY's half of [`remap_hatch_associations`], plus the cache it feeds. 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 { + if remap_hatch_associations(&mut self.document, handle_map) { self.associative_hatch_source_cache.borrow_mut().take(); } } diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 6fe667be0..98f658bcf 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -60,7 +60,7 @@ mod parametric_solve; pub(crate) use boundary::{ boundary_entities, boundary_entities_from_sources, boundary_faces, boundary_polyline_entities, exact_hatch_paths, hatch_boundary_rings, hatch_path_directions, hatch_path_ring, - ring_source_handles, separated_hatch_path_groups, BoundarySource, + remap_hatch_associations, ring_source_handles, separated_hatch_path_groups, BoundarySource, }; // Parallel tessellation free functions live in `convert::tess` (alongside the