AREA of a normal hatch no longer counts unfilled islands (#91) (#155)

This commit is contained in:
MarekSpodBiedry 2026-10-10 18:13:47 +02:00 committed by GitHub
parent 9882b85a41
commit 0af1528f4c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 36 additions and 4 deletions

View File

@ -189,10 +189,21 @@ fn run_area(s: &mut Session, p: &Value) -> Result<Value> {
let b = a * el.ratio;
(cadcraft_geom::PI * a * b, cadcraft_geom::PI * (3.0 * (a + b) - ((3.0 * a + b) * (a + 3.0 * b)).sqrt()))
}
EntityKind::Hatch(hh) => hh.loops.iter().fold((0.0, 0.0), |acc, l| {
let g = Polyline { vertices: l.vertices.clone(), closed: true };
(acc.0 + g.area().abs(), acc.1 + g.len())
}),
EntityKind::Hatch(hh) => {
// Odd parity, as drawn: a loop nested inside an odd number of other loops is an unfilled island.
let geoms: Vec<Polyline> = hh.loops.iter().map(|l| Polyline { vertices: l.vertices.clone(), closed: true }).collect();
let polys: Vec<Vec<Vec2>> = geoms.iter().map(|g| g.tessellate(1e-3)).collect();
let mut total = (0.0, 0.0);
for (i, g) in geoms.iter().enumerate() {
let probe = polys.get(i).and_then(|p| p.first().copied());
let depth = probe.map_or(0, |pt| {
polys.iter().enumerate().filter(|(j, q)| *j != i && cadcraft_geom::point_in_polygon(q, pt)).count()
});
total.0 += if depth % 2 == 0 { g.area().abs() } else { -g.area().abs() };
total.1 += g.len();
}
total
}
_ => return Err(bad("area", "object has no area")),
}
};

View File

@ -350,6 +350,27 @@ fn dim_auto_vertical_and_radius() {
assert!(matches!(kinds[1], cadcraft_doc::DimKind::Radius));
}
#[test]
fn area_of_hatch_excludes_island() {
let mut s = Session::new();
s.execute("pline", &json!({"vertices": [[0, 0], [10, 0], [10, 10], [0, 10]], "closed": true})).unwrap();
s.execute("pline", &json!({"vertices": [[4, 4], [6, 4], [6, 6], [4, 6]], "closed": true})).unwrap();
s.execute("hatch", &json!({"points": [[1, 1]], "pattern": "SOLID"})).unwrap();
let h = s.doc().unwrap().model.iter().find_map(|e| if let EntityKind::Hatch(_) = &e.kind { Some(e.handle) } else { None }).unwrap();
let r = s.execute("area", &json!({"handle": h.hex()})).unwrap();
assert!((r["area"].as_f64().unwrap() - 96.0).abs() < 1e-6);
assert!((r["perimeter"].as_f64().unwrap() - 48.0).abs() < 1e-6);
// A loop nested inside the island is filled again: 100 - 36 + 4.
let mut s = Session::new();
s.execute("pline", &json!({"vertices": [[0, 0], [10, 0], [10, 10], [0, 10]], "closed": true})).unwrap();
s.execute("pline", &json!({"vertices": [[2, 2], [8, 2], [8, 8], [2, 8]], "closed": true})).unwrap();
s.execute("pline", &json!({"vertices": [[4, 4], [6, 4], [6, 6], [4, 6]], "closed": true})).unwrap();
s.execute("hatch", &json!({"points": [[1, 1]], "pattern": "SOLID"})).unwrap();
let h = s.doc().unwrap().model.iter().find_map(|e| if let EntityKind::Hatch(_) = &e.kind { Some(e.handle) } else { None }).unwrap();
let r = s.execute("area", &json!({"handle": h.hex()})).unwrap();
assert!((r["area"].as_f64().unwrap() - 68.0).abs() < 1e-6);
}
#[test]
fn hatch_by_pick_point_with_island() {
let mut s = Session::new();