From b7c2ce2e137182a6fc557e29cac65604495df653 Mon Sep 17 00:00:00 2001 From: gianlucafiore Date: Mon, 7 Sep 2026 18:18:36 -0300 Subject: [PATCH] fix(hatch): refresh associative hatch during geometry edits --- src/scene/boundary.rs | 94 ++++++++++++++++++++++++++++++++++++++----- src/scene/modify.rs | 9 +++++ 2 files changed, 93 insertions(+), 10 deletions(-) diff --git a/src/scene/boundary.rs b/src/scene/boundary.rs index 020ba2e7e..c04691960 100644 --- a/src/scene/boundary.rs +++ b/src/scene/boundary.rs @@ -84,6 +84,38 @@ fn entity_curves_on_plane( } } +fn entity_boundary_source_on_plane( + entity: &EntityType, + plane: WorkingPlane, + tolerance: f64, +) -> Option { + let curves = entity_curves_on_plane(entity, plane, tolerance); + + if curves.is_empty() { + return None; + } + + let segments: Vec = curves + .iter() + .flat_map(|curve| { + curve + .tessellate_angle(cadkernel::tessellation::DEFAULT_ANGLE) + .windows(2) + .filter_map(|pair| { + let start = pair[0]; + let end = pair[1]; + + (start.iter().chain(&end).all(|value| value.is_finite()) + && start != end) + .then_some(Line { start, end }) + }) + .collect::>() + }) + .collect(); + + (!segments.is_empty()).then_some(BoundarySource { segments, curves }) +} + fn hatch_path_seed(path: &acadrust::entities::BoundaryPath) -> Option<[f64; 2]> { let mut ring = Vec::new(); for edge in &path.edges { @@ -731,10 +763,22 @@ impl Scene { .boundary_handles .iter() .filter_map(|source| { - all_sources - .get(source) - .cloned() - .map(|geometry| (*source, geometry)) + // Associative boundaries are explicit document references. + // During a grip drag their display wire can be preview-hidden, + // so prefer the live entity geometry over the resident wire cache. + let geometry = self + .document + .get_entity(*source) + .and_then(|entity| { + entity_boundary_source_on_plane( + entity, + plane, + WELD_TOLERANCE, + ) + }) + .or_else(|| all_sources.get(source).cloned()); + + geometry.map(|geometry| (*source, geometry)) }) .collect(); let segments: Vec<_> = sources @@ -817,17 +861,47 @@ impl Scene { .extend(segments); } for (&handle, source) in &mut sources { - source.curves = self + let curves = self .document .get_entity(handle) .map(|entity| entity_curves_on_plane(entity, plane, tolerance)) .unwrap_or_default(); - source.curves.retain(|curve| { - source.segments.iter().any(|segment| { - distance_to(curve, segment.start).max(distance_to(curve, segment.end)) - <= tolerance * 4.0 + + // Associative hatch regeneration must use the entity's live geometry, + // not a potentially one-frame-old display wire. Grip edits and STRETCH + // mutate the document before the resident wire cache catches up. + let live_segments: Vec = curves + .iter() + .flat_map(|curve| { + curve + .tessellate_angle(cadkernel::tessellation::DEFAULT_ANGLE) + .windows(2) + .filter_map(|pair| { + let start = pair[0]; + let end = pair[1]; + + (start.iter().chain(&end).all(|value| value.is_finite()) + && start != end) + .then_some(Line { start, end }) + }) + .collect::>() }) - }); + .collect(); + + if !live_segments.is_empty() { + source.segments = live_segments; + source.curves = curves; + } else { + // Fallback for entity types that do not expose usable native curves: + // retain the existing display-wire geometry. + source.curves = curves; + source.curves.retain(|curve| { + source.segments.iter().any(|segment| { + distance_to(curve, segment.start).max(distance_to(curve, segment.end)) + <= tolerance * 4.0 + }) + }); + } } sources } diff --git a/src/scene/modify.rs b/src/scene/modify.rs index a37dfc39c..f84605425 100644 --- a/src/scene/modify.rs +++ b/src/scene/modify.rs @@ -1387,5 +1387,14 @@ impl Scene { self.images.insert(handle, model); } } + // A grip edit can move geometry used as the boundary of an associative hatch. + // The source entity has already been mutated above, so rebuild any dependent + // hatch from the live document geometry immediately. + let source_change = [(handle, ChangeKind::Modified)]; + let hatch_changes = self.refresh_associative_hatches(&source_change); + + if !hatch_changes.is_empty() { + self.bump_entities(&hatch_changes); + } } }