forked from 1b-agent/opencadstudio-upstream
fix(text): clip SDF text to xclip boundary; stop debug-assert crash on block fills
Two fixes for opening drawings with XCLIP'd blocks (crashed / mis-rendered since SDF text became the default). - block_cache emit_wire: the fill_tris/fill_tris_low length debug_assert panicked in debug builds on a legitimate case — a Leader/dimension arrowhead fill nested in a block carries fill_tris with an empty fill_tris_low (the WireModel contract's "all-zero low" form, which the code already handles via get().unwrap_or). Relax the assert to permit the empty case; a *partial* low half is still caught. - xclip clip_wires: also clip the SDF glyph quads. Block-internal text was bleeding past the clip boundary (mixed batch) or vanishing (the tail retain dropped empty-points wires). Now each glyph quad is kept/dropped by its centroid vs the boundary (whole-glyph granularity), text_verts-carrying wires survive the retain, and the pick-box AABB is recomputed from the surviving glyphs. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
5c15091e74
commit
fa322991c6
2 files changed
+53
-3
No files matched your search
Vendored
+11
-1
@@ -1187,7 +1187,17 @@ fn emit_wire(
|
||||
.tangent_geoms
|
||||
.push(transform_tangent(tg, accum_xform));
|
||||
}
|
||||
debug_assert_eq!(lw.fill_tris.len(), lw.fill_tris_low.len());
|
||||
// Per the WireModel contract an empty `fill_tris_low` means "all-zero low
|
||||
// half" (e.g. a Leader / dimension arrowhead fill, which the tessellator
|
||||
// emits without a low half). Only a *partially* populated low half is a
|
||||
// real bug — keep the tripwire for that, but permit the empty case so debug
|
||||
// builds don't panic on legitimate geometry.
|
||||
debug_assert!(
|
||||
lw.fill_tris_low.is_empty() || lw.fill_tris.len() == lw.fill_tris_low.len(),
|
||||
"fill_tris_low must be empty or the same length as fill_tris (got {} vs {})",
|
||||
lw.fill_tris.len(),
|
||||
lw.fill_tris_low.len(),
|
||||
);
|
||||
for (idx, p) in lw.fill_tris.iter().enumerate() {
|
||||
// Empty/short fill_tris_low means "no low half" (all-zero), per the
|
||||
// WireModel contract — same panic-safe access the other fill consumers
|
||||
|
||||
+42
-2
@@ -183,13 +183,53 @@ pub fn clip_wires(wires: &mut Vec<WireModel>, poly: &[[f64; 2]]) {
|
||||
w.fill_tris = hi;
|
||||
w.fill_tris_low = lo;
|
||||
}
|
||||
// Clip SDF text: keep each glyph quad (6 verts = 2 triangles) whose
|
||||
// centroid is inside the boundary. Whole-glyph granularity so block-
|
||||
// instance text is bounded by the xclip region instead of bleeding past
|
||||
// it — the stroke path clips per-segment, but per-glyph reads cleanly.
|
||||
if !w.text_verts.is_empty() {
|
||||
let mut kept = Vec::with_capacity(w.text_verts.len());
|
||||
for quad in w.text_verts.chunks(6) {
|
||||
let n = quad.len() as f64;
|
||||
let (mut cx, mut cy) = (0.0f64, 0.0f64);
|
||||
for v in quad {
|
||||
cx += v.pos[0] as f64 + v.pos_low[0] as f64;
|
||||
cy += v.pos[1] as f64 + v.pos_low[1] as f64;
|
||||
}
|
||||
if point_in_poly((cx / n - rx) as f32, (cy / n - ry) as f32, &lpoly) {
|
||||
kept.extend_from_slice(quad);
|
||||
}
|
||||
}
|
||||
w.text_verts = kept;
|
||||
}
|
||||
w.key_vertices
|
||||
.retain(|v| point_in_poly((v[0] - rx) as f32, (v[1] - ry) as f32, &lpoly));
|
||||
w.snap_pts
|
||||
.retain(|(p, _)| point_in_poly((p.x - rx) as f32, (p.y - ry) as f32, &lpoly));
|
||||
w.aabb = recompute_aabb(&w.points, &w.fill_tris);
|
||||
// AABB from surviving points + fills, then fold in the surviving glyph
|
||||
// quads so a text-only wire keeps a tight (non-degenerate) pick box.
|
||||
let mut bb = recompute_aabb(&w.points, &w.fill_tris);
|
||||
if !w.text_verts.is_empty() {
|
||||
let (mut nx, mut ny, mut xx, mut xy) = if bb == [0.0; 4] {
|
||||
(f32::MAX, f32::MAX, f32::MIN, f32::MIN)
|
||||
} else {
|
||||
(bb[0], bb[1], bb[2], bb[3])
|
||||
};
|
||||
for v in &w.text_verts {
|
||||
nx = nx.min(v.pos[0]);
|
||||
ny = ny.min(v.pos[1]);
|
||||
xx = xx.max(v.pos[0]);
|
||||
xy = xy.max(v.pos[1]);
|
||||
}
|
||||
if nx <= xx {
|
||||
bb = [nx, ny, xx, xy];
|
||||
}
|
||||
}
|
||||
w.aabb = bb;
|
||||
}
|
||||
wires.retain(|w| !w.points.is_empty() || !w.fill_tris.is_empty());
|
||||
wires.retain(|w| {
|
||||
!w.points.is_empty() || !w.fill_tris.is_empty() || !w.text_verts.is_empty()
|
||||
});
|
||||
}
|
||||
|
||||
/// Double-single split of an f64 into (high, low) f32 — mirrors
|
||||
|
||||
Reference in new issue
Block a user