fix(hatch): refresh associative hatch during geometry edits

This commit is contained in:
gianlucafiore committed 2026-09-07 18:18:36 -03:00
1 parent 67163de15d
commit b7c2ce2e13
2 files changed
+93 -10

No files matched your search

+84 -10
View File
@@ -84,6 +84,38 @@ fn entity_curves_on_plane(
} }
} }
fn entity_boundary_source_on_plane(
entity: &EntityType,
plane: WorkingPlane,
tolerance: f64,
) -> Option<BoundarySource> {
let curves = entity_curves_on_plane(entity, plane, tolerance);
if curves.is_empty() {
return None;
}
let segments: Vec<Line> = 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::<Vec<_>>()
})
.collect();
(!segments.is_empty()).then_some(BoundarySource { segments, curves })
}
fn hatch_path_seed(path: &acadrust::entities::BoundaryPath) -> Option<[f64; 2]> { fn hatch_path_seed(path: &acadrust::entities::BoundaryPath) -> Option<[f64; 2]> {
let mut ring = Vec::new(); let mut ring = Vec::new();
for edge in &path.edges { for edge in &path.edges {
@@ -731,10 +763,22 @@ impl Scene {
.boundary_handles .boundary_handles
.iter() .iter()
.filter_map(|source| { .filter_map(|source| {
all_sources // Associative boundaries are explicit document references.
.get(source) // During a grip drag their display wire can be preview-hidden,
.cloned() // so prefer the live entity geometry over the resident wire cache.
.map(|geometry| (*source, geometry)) 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(); .collect();
let segments: Vec<_> = sources let segments: Vec<_> = sources
@@ -817,17 +861,47 @@ impl Scene {
.extend(segments); .extend(segments);
} }
for (&handle, source) in &mut sources { for (&handle, source) in &mut sources {
source.curves = self let curves = self
.document .document
.get_entity(handle) .get_entity(handle)
.map(|entity| entity_curves_on_plane(entity, plane, tolerance)) .map(|entity| entity_curves_on_plane(entity, plane, tolerance))
.unwrap_or_default(); .unwrap_or_default();
source.curves.retain(|curve| {
source.segments.iter().any(|segment| { // Associative hatch regeneration must use the entity's live geometry,
distance_to(curve, segment.start).max(distance_to(curve, segment.end)) // not a potentially one-frame-old display wire. Grip edits and STRETCH
<= tolerance * 4.0 // mutate the document before the resident wire cache catches up.
let live_segments: Vec<Line> = 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::<Vec<_>>()
}) })
}); .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 sources
} }
+9
View File
@@ -1387,5 +1387,14 @@ impl Scene {
self.images.insert(handle, model); 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);
}
} }
} }