diff --git a/crates/engine/src/select.rs b/crates/engine/src/select.rs index 7bc2f2653..288ae7a87 100644 --- a/crates/engine/src/select.rs +++ b/crates/engine/src/select.rs @@ -89,11 +89,18 @@ pub fn entity_distance(d: &Drawing, e: &Entity, p: Vec2, tol: f64) -> f64 { let polys = hit_polylines(d, e, tol); let mut best = polys.iter().map(|pl| dist_to_polyline(pl, p)).fold(f64::INFINITY, f64::min); let filled = matches!(e.kind, EntityKind::Text(_) | EntityKind::MText(_) | EntityKind::AttDef(_) | EntityKind::Table(_)) - || matches!(e.kind, EntityKind::Tolerance(_)) - || matches!(&e.kind, EntityKind::Hatch(h) if h.solid); + || matches!(e.kind, EntityKind::Tolerance(_)); if (filled && entity_bounds(d, e, 0).contains(p)) || in_text(d, e, p) { best = 0.0; } + // A solid hatch fills its loops with odd parity under its island style, as it is drawn, not + // its bounding rectangle. + if let EntityKind::Hatch(h) = &e.kind + && h.solid + && cadcraft_render::hatch_fill_loops(h, tol).iter().filter(|l| point_in_polygon(l, p)).count() % 2 == 1 + { + best = 0.0; + } // A SOLID fills its polygon, not its bounding rectangle (half of which is empty for a triangle). if matches!(e.kind, EntityKind::Solid(_)) && e.kind.prims().iter().any(|prim| matches!(prim, Prim::Fill(poly) if point_in_polygon(poly, p))) { best = 0.0; @@ -553,4 +560,51 @@ mod hatch_pattern_pick_tests { assert!(pick(&d, &Space::Model, Vec2::new(1.0, 1.0), 0.05).is_some()); assert!(pick(&d, &Space::Model, Vec2::new(5.0, 5.0), 0.05).is_none()); } + + fn solid_hatch(loops: Vec, style: u8) -> Drawing { + let mut d = Drawing::new_metric(); + let hatch = Hatch { + pattern: "SOLID".into(), + solid: true, + loops, + scale: 1.0, + angle: 0.0, + associative: false, + style, + elevation: 0.0, + gradient: None, + origin: Vec2::ZERO, + background: None, + pattern_lines: Vec::new(), + }; + d.add(&Space::Model, Common::default(), EntityKind::Hatch(hatch)).unwrap(); + d + } + + #[test] + fn solid_hatch_is_picked_inside_its_fill_not_its_bounding_rectangle() { + let tri = HatchLoop { + vertices: [(0.0, 0.0), (10.0, 0.0), (0.0, 10.0)].iter().map(|&(x, y)| PolyVertex::new(Vec2::new(x, y))).collect(), + outer: true, + sources: Vec::new(), + }; + let d = solid_hatch(vec![tri], 0); + // The empty corner of the bounding rectangle, 5.66 from the hypotenuse. + assert!(pick(&d, &Space::Model, Vec2::new(9.0, 9.0), 0.25).is_none()); + assert!(pick(&d, &Space::Model, Vec2::new(2.0, 2.0), 0.25).is_some()); + // Outside the fill but within the aperture of the hypotenuse. + assert!(pick(&d, &Space::Model, Vec2::new(5.1, 5.1), 0.25).is_some()); + } + + #[test] + fn solid_hatch_island_follows_the_island_style() { + let loops = || vec![square(0.0, 10.0, true), square(4.0, 6.0, false)]; + // Normal style leaves the island empty, as it is drawn. + let d = solid_hatch(loops(), 0); + assert!(pick(&d, &Space::Model, Vec2::new(5.0, 5.0), 0.05).is_none()); + assert!(pick(&d, &Space::Model, Vec2::new(2.0, 2.0), 0.05).is_some()); + // Ignore style fills over the island. + let d = solid_hatch(loops(), 2); + assert!(pick(&d, &Space::Model, Vec2::new(5.0, 5.0), 0.05).is_some()); + } } diff --git a/crates/render/src/hatch.rs b/crates/render/src/hatch.rs index 97e7fba26..ed397d0a6 100644 --- a/crates/render/src/hatch.rs +++ b/crates/render/src/hatch.rs @@ -90,7 +90,7 @@ fn dedup_polygons(loops: &mut Vec>) { /// detection style (DXF group 75), filled with odd parity: 0 normal keeps every loop (islands /// within islands alternate), 1 outer keeps the outermost loops and their first islands, 2 /// ignore keeps only the outermost loops. Nesting depth = how many other loops enclose a loop. -pub(crate) fn fill_loops(h: &Hatch, tol: f64) -> Vec> { +pub fn fill_loops(h: &Hatch, tol: f64) -> Vec> { let mut loops: Vec> = h.loops.iter().map(|l| Polyline { vertices: l.vertices.clone(), closed: true }.tessellate(tol)).collect(); dedup_polygons(&mut loops); let max_depth = match h.style { diff --git a/crates/render/src/lib.rs b/crates/render/src/lib.rs index ea37591ad..d040f9618 100644 --- a/crates/render/src/lib.rs +++ b/crates/render/src/lib.rs @@ -31,7 +31,7 @@ use pen::Pen; pub use dim::{ArrowGeom, Arrowhead, DimGeometry, DimText, LineRole, arrowhead, dimension_geometry, dimension_geometry_with, format_linear_value}; pub use fill::triangulate_evenodd; -pub use hatch::pattern_swatch; +pub use hatch::{fill_loops as hatch_fill_loops, pattern_swatch}; pub use paper::{PAPER_SIZES, PaperSize, Sheet, paper_size, sheet}; pub use tolerance::{gdt_points, tolerance_in};