fix: reject degenerate boolean intersection polylines
This commit is contained in:
1 parent
970da30a99
commit
ed102b5393
8 files changed
+84
-1
No files matched your search
+1
-1
@@ -55,7 +55,7 @@ Generated-in-code assets are original and have no file to list:
|
||||
|---|---|---|---|
|
||||
| vendor/truck-meshalgo | truck-meshalgo 0.4.0, https://github.com/ricosjp/truck (RICOS Co. Ltd.) | Apache-2.0 | meshing: parameter searches kept inside the surface domain (vendor/README.md) |
|
||||
| vendor/truck-stepio | truck-stepio 0.3.0, https://github.com/ricosjp/truck (RICOS Co. Ltd.) | Apache-2.0 | STEP writer: intersection-curve surfaces written at unique ids (vendor/README.md) |
|
||||
| vendor/truck-shapeops | truck-shapeops 0.4.0, https://github.com/ricosjp/truck (RICOS Co. Ltd.) | Apache-2.0 | robust meshing and nearest-parameter fallback in booleans; trace macro (vendor/README.md) |
|
||||
| vendor/truck-shapeops | truck-shapeops 0.4.0, https://github.com/ricosjp/truck (RICOS Co. Ltd.) | Apache-2.0 | robust meshing and nearest-parameter fallback in booleans; degenerate intersection-curve rejection; trace macro (vendor/README.md) |
|
||||
| vendor/matext4cgmath | matext4cgmath 0.1.0, https://github.com/ricosjp/matext4cgmath (RICOS Co. Ltd.) | Apache-2.0 | root refinement stops after 100 Newton steps (vendor/README.md) |
|
||||
|
||||
## Vendored renderer compatibility fix
|
||||
|
||||
@@ -304,6 +304,9 @@ headline counts in-scope tabs only and lists the deferred ones separately.
|
||||
|
||||
## Kernel
|
||||
|
||||
- Boolean intersection-curve construction refuses point-only, collapsed and non-finite
|
||||
polylines without panicking. Direct constructor regressions retain valid open endpoints
|
||||
and closed spherical intersections; the original interactive trigger remains unidentified.
|
||||
- Shell of a stepped, joined planar body no longer creates horizontal subdivisions across
|
||||
continuous walls. Fresh axis-aligned Cut walls are stitched using their exact construction
|
||||
support and shared topology, preserving real steps and intentional boundaries. The local
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
//! Regressions against the actual patched truck constructor, run by workspace CI.
|
||||
use truck_meshalgo::prelude::PolylineCurve;
|
||||
use truck_modeling::{BoundedCurve, ParametricCurve, Plane, Point3, Sphere};
|
||||
use truck_shapeops::IntersectionCurveWithParameters;
|
||||
|
||||
fn planes() -> (Plane, Plane) {
|
||||
let o = Point3::new(0.0, 0.0, 0.0);
|
||||
(Plane::new(o, Point3::new(1.0, 0.0, 0.0), Point3::new(0.0, 1.0, 0.0)), Plane::new(o, Point3::new(1.0, 0.0, 0.0), Point3::new(0.0, 0.0, 1.0)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn degenerate_intersection_polylines_return_none_without_panicking() {
|
||||
let origin = Point3::new(0.0, 0.0, 0.0);
|
||||
for points in [
|
||||
vec![],
|
||||
vec![origin],
|
||||
vec![origin, origin],
|
||||
vec![origin; 3],
|
||||
vec![origin, Point3::new(f64::NAN, 0.0, 0.0)],
|
||||
vec![origin, Point3::new(f64::INFINITY, 0.0, 0.0)],
|
||||
] {
|
||||
let (a, b) = planes();
|
||||
let result = std::panic::catch_unwind(|| IntersectionCurveWithParameters::try_new(a, b, PolylineCurve(points.clone()), 0.01));
|
||||
assert!(result.is_ok(), "constructor panicked for {points:?}");
|
||||
assert!(result.unwrap().is_none(), "collapsed input must not create a curve: {points:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closed_intersection_retains_the_closure_and_spherical_supports() {
|
||||
let a = Sphere::new(Point3::new(0.0, 0.0, 1.0), 2.0_f64.sqrt());
|
||||
let b = Sphere::new(Point3::new(0.0, 0.0, -1.0), 2.0_f64.sqrt());
|
||||
let poly = PolylineCurve(vec![
|
||||
Point3::new(1.0, 0.0, 0.0),
|
||||
Point3::new(0.0, 1.0, 0.0),
|
||||
Point3::new(-1.0, 0.0, 0.0),
|
||||
Point3::new(0.0, -1.0, 0.0),
|
||||
Point3::new(1.0, 0.0, 0.0),
|
||||
]);
|
||||
let curve = IntersectionCurveWithParameters::try_new(a, b, poly, 0.01).unwrap();
|
||||
let (lo, hi) = curve.range_tuple();
|
||||
assert_eq!(curve.subs(lo), curve.subs(hi));
|
||||
for i in 0..=40 {
|
||||
let p = curve.subs(lo + (hi - lo) * i as f64 / 40.0);
|
||||
assert!(p.z.abs() < 1e-8, "{p:?}");
|
||||
assert!(((p.x * p.x + p.y * p.y).sqrt() - 1.0).abs() <= 0.01, "{p:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn open_intersection_keeps_endpoints_on_both_planes() {
|
||||
let (a, b) = planes();
|
||||
let start = Point3::new(1.0, 0.0, 0.0);
|
||||
let end = Point3::new(3.0, 0.0, 0.0);
|
||||
let curve = IntersectionCurveWithParameters::try_new(a, b, PolylineCurve(vec![start, end]), 0.01).unwrap();
|
||||
let (lo, hi) = curve.range_tuple();
|
||||
assert_eq!(curve.subs(lo), start);
|
||||
assert_eq!(curve.subs(hi), end);
|
||||
assert_eq!(curve.subs((lo + hi) / 2.0), Point3::new(2.0, 0.0, 0.0));
|
||||
}
|
||||
@@ -30,6 +30,8 @@ mod heal;
|
||||
mod helix;
|
||||
mod iges_in;
|
||||
mod iges_out;
|
||||
#[cfg(test)]
|
||||
mod intersection_curve_tests;
|
||||
mod lip;
|
||||
mod loopblend;
|
||||
mod measure;
|
||||
|
||||
Vendored
+3
@@ -7,6 +7,9 @@ are marked `SolveCraft:` in the source.
|
||||
## truck-shapeops 0.4.0 (Apache-2.0, © RICOS Co. Ltd., https://github.com/ricosjp/truck)
|
||||
|
||||
Changes:
|
||||
- `transversal/intersection_curve/mod.rs`: reject empty/single-point polylines and
|
||||
collapsed or non-finite segments before endpoint indexing and normalization. A hidden
|
||||
constructor re-export lets the kernel workspace tests exercise the actual vendored code.
|
||||
- `transversal/integrate/mod.rs`: mesh both shells with `robust_triangulation` (faces made by
|
||||
earlier booleans have boundaries that lie on their surface only within tolerance).
|
||||
- `transversal/divide_face/mod.rs`: when projecting a loop onto a face's surface, fall back to
|
||||
|
||||
Vendored
+3
@@ -34,4 +34,7 @@ mod healing;
|
||||
pub use healing::{RobustSplitClosedEdgesAndFaces, SplitClosedEdgesAndFaces};
|
||||
mod transversal;
|
||||
pub use transversal::{and, or, ShapeOpsCurve, ShapeOpsSurface};
|
||||
// SolveCraft: exercised by kernel regressions in the workspace CI.
|
||||
#[doc(hidden)]
|
||||
pub use transversal::IntersectionCurveWithParameters;
|
||||
mod alternative;
|
||||
@@ -19,12 +19,21 @@ impl<S> From<IntersectionCurveWithParameters<S>> for IntersectionCurve<PolylineC
|
||||
impl<S> IntersectionCurveWithParameters<S>
|
||||
where S: ParametricSurface3D + SearchNearestParameter<D2, Point = Point3>
|
||||
{
|
||||
/// SolveCraft: construct a projected curve, returning `None` for degenerate input.
|
||||
pub fn try_new(
|
||||
surface0: S,
|
||||
surface1: S,
|
||||
poly: PolylineCurve<Point3>,
|
||||
tol: f64,
|
||||
) -> Option<Self> {
|
||||
// SolveCraft: a point contact is not a curve. Reject it before indexing the
|
||||
// endpoints, and reject collapsed/non-finite segments before normalizing.
|
||||
if poly.len() < 2 || poly.windows(2).any(|p| {
|
||||
let length2 = (p[1] - p[0]).magnitude2();
|
||||
!length2.is_finite() || length2 <= 0.0
|
||||
}) {
|
||||
return None;
|
||||
}
|
||||
let mut polyline = PolylineCurve(Vec::new());
|
||||
let mut params0 = PolylineCurve(Vec::new());
|
||||
let mut params1 = PolylineCurve(Vec::new());
|
||||
|
||||
@@ -2,6 +2,9 @@ mod divide_face;
|
||||
mod faces_classification;
|
||||
mod integrate;
|
||||
mod intersection_curve;
|
||||
// SolveCraft: expose the constructor for workspace regression coverage of the vendored fix.
|
||||
#[doc(hidden)]
|
||||
pub use intersection_curve::IntersectionCurveWithParameters;
|
||||
mod loops_store;
|
||||
mod polyline_construction;
|
||||
pub use integrate::{and, or, ShapeOpsCurve, ShapeOpsSurface};
|
||||
Reference in new issue
Block a user