fix(select): include solid interiors in crossing-window selection (#401)

* fix(select): include solid interiors in crossing-window selection

* test(select): keep solid crossing regressions in an isolated module
This commit is contained in:
Nigel Ihza Firdausy
2026-10-10 18:43:06 -04:00
committed by GitHub
parent 964eb0fb40
commit bc2c80289c
+53 -2
View File
@@ -193,8 +193,21 @@ pub(crate) fn in_window(d: &Drawing, e: &Entity, known: Option<Bounds2>, bx: &Bo
pl.windows(2).any(|w| w.first().zip(w.get(1)).is_some_and(|(a, b)| seg_hits_box(*a, *b, bx)))
}
});
// A crossing box entirely inside a filled/text object also selects it.
hit || (eb.contains_box(bx) && matches!(e.kind, EntityKind::Text(_) | EntityKind::MText(_) | EntityKind::Insert(_)))
// A crossing box entirely inside a text/block bounding rectangle also selects it.
if hit || (eb.contains_box(bx) && matches!(e.kind, EntityKind::Text(_) | EntityKind::MText(_) | EntityKind::Insert(_))) {
return true;
}
// SOLID/TRACE are filled polygons. A crossing window wholly within the
// fill must select them even when it does not touch their outline. Check
// the polygon, not just its bounding rectangle (which can cover empty
// space around triangular solids).
if matches!(e.kind, EntityKind::Solid(_) | EntityKind::Trace(_)) && eb.contains_box(bx) {
return e.kind.prims().iter().any(|prim| match prim {
Prim::Fill(poly) => bx.corners().iter().all(|p| cadcraft_geom::point_in_polygon(poly, *p)),
_ => false,
});
}
false
}
/// Fence selection: entities crossed by the fence polyline.
@@ -255,3 +268,41 @@ mod tests {
assert_eq!(fence_hits(EntityKind::Ray(line), [(5.0, 1.0), (5.0, 5.0)]), 0);
}
}
#[cfg(test)]
mod solid_interior_tests {
use super::*;
fn face(kind: bool, triangular: bool) -> Entity {
let a = Vec2::new(0.0, 0.0).to3(0.0);
let b = Vec2::new(10.0, 0.0).to3(0.0);
let c = Vec2::new(0.0, 10.0).to3(0.0);
let d = if triangular { c } else { Vec2::new(10.0, 10.0).to3(0.0) };
let solid = cadcraft_doc::Solid { corners: [a, b, c, d] };
Entity::new(Handle(256), if kind { EntityKind::Solid(solid) } else { EntityKind::Trace(solid) })
}
#[test]
fn crossing_window_inside_solid_and_trace_selects_fill() {
let inside = Bounds2::new(Vec2::new(4.0, 4.0), Vec2::new(6.0, 6.0));
for solid in [true, false] {
let mut d = Drawing::default();
let e = face(solid, false);
assert!(in_window(&d, &e, None, &inside, true, 0.01));
assert!(!in_window(&d, &e, None, &inside, false, 0.01));
d.model.push(e);
assert_eq!(select_window(&d, &Space::Model, inside, true), vec![Handle(256)]);
assert!(select_window(&d, &Space::Model, inside, false).is_empty());
}
}
#[test]
fn crossing_window_in_triangular_bbox_but_outside_fill_does_not_select() {
let d = Drawing::default();
let e = face(true, true);
let empty = Bounds2::new(Vec2::new(7.0, 7.0), Vec2::new(8.0, 8.0));
assert!(!in_window(&d, &e, None, &empty, true, 0.01));
let inside = Bounds2::new(Vec2::new(3.0, 3.0), Vec2::new(4.0, 4.0));
assert!(in_window(&d, &e, None, &inside, true, 0.01));
}
}