Mesh edges that do not evaluate without a panic; refuse such boolean results (#1252) (#1364)

The mesher samples edges through a fallible evaluation, so an intersection curve whose projection fails to converge no longer panics; a boolean result with an edge that does not evaluate is refused with an error. Fixes #1252.
This commit is contained in:
Lasse Lian authored and GitHub committed 2026-10-15 11:10:55 -04:00
1 parent 27529d9742
commit 3fa8fbd162
8 files changed
+144 -7

No files matched your search

+25 -1
View File
@@ -116,9 +116,30 @@ pub(crate) fn shapeops(h: &truck_shapeops::History, tool: &Solid, tool_edges: Op
Some((faces, edges))
}
/// Samples along each edge when checking that its curve evaluates.
const EVALUATION_SAMPLES: usize = 32;
/// Whether every edge of `solid` evaluates along its whole range. An intersection curve projects
/// its leader's point onto both surfaces, which fails to converge where a boolean made the edge
/// from an approximation that strayed (#1252). Such a result is no solid to keep: the mesher,
/// healing and measuring all sample the edge and stumble over the gap.
pub(crate) fn edges_evaluate(solid: &Solid) -> bool {
use truck_meshalgo::prelude::TrySubs;
use truck_modeling::BoundedCurve;
solid.boundaries().iter().flat_map(|shell| shell.edge_iter()).all(|edge| {
let curve = edge.curve();
let (t0, t1) = curve.range_tuple();
(0..=EVALUATION_SAMPLES).all(|k| {
let t = t0 + (t1 - t0) * k as f64 / EVALUATION_SAMPLES as f64;
curve.try_subs(t).is_some_and(|p| p.x.is_finite() && p.y.is_finite() && p.z.is_finite())
})
})
}
/// The body made of `solid` healed (keeping faces apart along `keep`), with the history whose
/// faces and edges (in `solid`'s orders) are given before healing, carried through it.
/// Operands with `counts` faces and edges.
/// Operands with `counts` faces and edges. A result with an edge that does not evaluate is
/// refused (see [`edges_evaluate`]).
pub(crate) fn heal_build(
solid: Solid,
size: f64,
@@ -128,6 +149,9 @@ pub(crate) fn heal_build(
counts: &[(usize, usize)],
) -> Result<Body> {
let (healed, map) = crate::heal::heal_traced(solid, size, keep);
if !edges_evaluate(&healed) {
return Err(crate::KernelError::Failed("an edge of the result does not evaluate".into()));
}
let carried = faces.zip(edges).zip(map).and_then(|((f, e), m)| m.carry(&f, &e));
let (faces, edges) = carried.unzip();
record::build(healed, counts, faces, edges.as_ref())