fix(hatch): one grip per corner, and live preview while editing
Two follow-ups to dropping the hatch boundary outline from the wire set: - `closed_outlines` copied segment-list wire points (LwPolyline emits each segment as a NaN-separated pair, so every shared corner repeats) straight into the hatch boundary, giving two grips at each corner. Collapse the points back into a clean ring — skip NaN separators and any vertex coincident with the previous one, and drop a trailing vertex equal to the first. One grip per corner; point-in-polygon and the BOUNDARY / GRADIENT commands get the clean ring too. - Edit previews (move / copy / array / grip-drag) tessellate the edited entity into preview wires, but a hatch now produces none, so a dragged hatch didn't update until release. Build a live outline from the current HatchModel for the preview only (`apply_grip` keeps it in step), so the shape tracks the cursor again. The normal render still shows just the fill. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
1831a63236
commit
0db513e07a
1 changed files with 73 additions and 6 deletions
|
|
@ -1968,7 +1968,39 @@ impl Scene {
|
||||||
if (dx * dx + dy * dy).sqrt() > 1e-2 {
|
if (dx * dx + dy * dy).sqrt() > 1e-2 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
Some(pts.iter().map(|p| [p[0], p[1]]).collect())
|
// Segment-list wires (e.g. LwPolyline) store each segment as an
|
||||||
|
// independent NaN-separated pair, so every shared corner repeats
|
||||||
|
// (`A B | B C | C D | D A`). Collapse that back into a clean ring:
|
||||||
|
// skip the NaN separators and any vertex coincident with the
|
||||||
|
// previous one, so consumers (point-in-polygon, the hatch /
|
||||||
|
// boundary commands) see one vertex per corner — not the doubled
|
||||||
|
// ring that otherwise shows two grips at every corner.
|
||||||
|
let mut ring: Vec<[f32; 2]> = Vec::with_capacity(pts.len());
|
||||||
|
for p in &pts {
|
||||||
|
if !p[0].is_finite() || !p[1].is_finite() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let q = [p[0], p[1]];
|
||||||
|
if let Some(&last) = ring.last() {
|
||||||
|
if (last[0] - q[0]).abs() < 1e-4 && (last[1] - q[1]).abs() < 1e-4 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ring.push(q);
|
||||||
|
}
|
||||||
|
// Drop a trailing vertex equal to the first — the ring is closed
|
||||||
|
// implicitly, so keeping it would be a duplicate corner.
|
||||||
|
if ring.len() > 1 {
|
||||||
|
let first = ring[0];
|
||||||
|
let last = *ring.last().unwrap();
|
||||||
|
if (first[0] - last[0]).abs() < 1e-4 && (first[1] - last[1]).abs() < 1e-4 {
|
||||||
|
ring.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ring.len() < 3 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(ring)
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
@ -5486,15 +5518,50 @@ impl Scene {
|
||||||
handles
|
handles
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|h| {
|
.flat_map(|h| {
|
||||||
self.document
|
match self.document.entities().find(|e| e.common().handle == *h) {
|
||||||
.entities()
|
// Hatches carry no outline in the normal wire set, but an
|
||||||
.find(|e| e.common().handle == *h)
|
// edit preview (move / copy / array / grip-drag) needs to
|
||||||
.map(|e| self.tessellate_one(e))
|
// show the shape following the cursor. Build a live boundary
|
||||||
.unwrap_or_default()
|
// from the current HatchModel — `apply_grip` keeps it in
|
||||||
|
// step, so the preview tracks a dragged grip in real time.
|
||||||
|
Some(EntityType::Hatch(_)) => {
|
||||||
|
self.hatch_outline_wire(*h).into_iter().collect()
|
||||||
|
}
|
||||||
|
Some(e) => self.tessellate_one(e),
|
||||||
|
None => Vec::new(),
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Boundary outline wire for a hatch, reconstructed from its cached
|
||||||
|
/// `HatchModel` (offsets from `world_origin`). Used only for edit previews —
|
||||||
|
/// the normal render shows the fill, not this outline.
|
||||||
|
fn hatch_outline_wire(&self, handle: Handle) -> Option<WireModel> {
|
||||||
|
let m = self.hatches.get(&handle)?;
|
||||||
|
let (wx, wy) = (m.world_origin[0], m.world_origin[1]);
|
||||||
|
let pts: Vec<[f64; 3]> = m
|
||||||
|
.boundary
|
||||||
|
.iter()
|
||||||
|
.map(|&[x, y]| {
|
||||||
|
if x.is_finite() && y.is_finite() {
|
||||||
|
[wx + x as f64, wy + y as f64, 0.0]
|
||||||
|
} else {
|
||||||
|
[f64::NAN; 3]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
if pts.len() < 2 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(WireModel::solid_f64(
|
||||||
|
handle.value().to_string(),
|
||||||
|
pts,
|
||||||
|
m.color,
|
||||||
|
false,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
/// Build wire models for an arbitrary slice of entities (e.g. clipboard contents).
|
/// Build wire models for an arbitrary slice of entities (e.g. clipboard contents).
|
||||||
/// Entities need not be in the document — they are tessellated directly.
|
/// Entities need not be in the document — they are tessellated directly.
|
||||||
pub fn wires_for_entities(&self, entities: &[acadrust::EntityType]) -> Vec<WireModel> {
|
pub fn wires_for_entities(&self, entities: &[acadrust::EntityType]) -> Vec<WireModel> {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue