diff --git a/crates/engine/src/select.rs b/crates/engine/src/select.rs index 550dd163c..421265ae1 100644 --- a/crates/engine/src/select.rs +++ b/crates/engine/src/select.rs @@ -193,8 +193,21 @@ pub(crate) fn in_window(d: &Drawing, e: &Entity, known: Option, 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)); + } +}