Select: pick a solid hatch inside its fill, islands left out, not its bounding rectangle (follow-up to #747) (#763)

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Marco Arnulfo 2026-10-13 11:20:39 +02:00 committed by GitHub
parent 966356545d
commit 76b0ef8897
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 58 additions and 4 deletions

View File

@ -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<HatchLoop>, 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());
}
}

View File

@ -90,7 +90,7 @@ fn dedup_polygons(loops: &mut Vec<Vec<Vec2>>) {
/// 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<Vec<Vec2>> {
pub fn fill_loops(h: &Hatch, tol: f64) -> Vec<Vec<Vec2>> {
let mut loops: Vec<Vec<Vec2>> = 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 {

View File

@ -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};