Define a kernel-neutral native B-rep format and guarded adapter (#461)
A SolidCraft-owned portable B-rep schema (geometry schema 2 with analytic curves and surfaces, explicit frames and ranges, fitted intersection curves) and a versioned body envelope (SCBREP03) with its own metadata records, a bounded binary codec, a validator with resource budgets, and an adapter that refuses what it can't convert exactly. Pinned by a box-and-fillet golden fixture. Not yet written by application saves. Refs #303.
This commit is contained in:
1 parent
3159fc0dd0
commit
9f88537927
14 files changed
+3925
-1
No files matched your search
@@ -0,0 +1,85 @@
|
||||
//! Bounded export of procedural intersections as ordinary spatial cubic splines.
|
||||
//! The stored tolerance is the fitting target; the result is always provisional.
|
||||
use super::*;
|
||||
|
||||
fn point_at(c: &mt::Curve, t: f64, budget: &mut usize) -> Result<mt::Point3> {
|
||||
*budget = budget.checked_sub(1).ok_or_else(|| invalid("intersection fitting budget"))?;
|
||||
let p = c.subs(t);
|
||||
if !p.x.is_finite() || !p.y.is_finite() || !p.z.is_finite() {
|
||||
return Err(invalid("intersection sample"));
|
||||
}
|
||||
Ok(p)
|
||||
}
|
||||
pub(super) fn fit_intersection(c: &mt::Curve, budget: &mut usize) -> Result<b::Curve> {
|
||||
let tolerance = 1e-4;
|
||||
let (start, end) = c.range_tuple();
|
||||
if !start.is_finite() || !end.is_finite() || start >= end {
|
||||
return Err(invalid("intersection range"));
|
||||
}
|
||||
// Fit each smooth parameter interval using spatial samples, without backend derivative conventions.
|
||||
// Only the resulting spatial spline crosses the persisted format boundary.
|
||||
let mt::Curve::IntersectionCurve(intersection) = c else {
|
||||
return Err(invalid("expected intersection"));
|
||||
};
|
||||
let mut breaks = vec![start, end];
|
||||
match intersection.leader() {
|
||||
mt::Leader::Polyline(points) => {
|
||||
if points.len() > 4096 {
|
||||
return Err(invalid("intersection parameter budget"));
|
||||
}
|
||||
breaks.extend((0..points.len()).map(|i| i as f64).filter(|t| *t > start && *t < end));
|
||||
}
|
||||
mt::Leader::BSpline(curve) => {
|
||||
if curve.knot_vec().len() > 4096 {
|
||||
return Err(invalid("intersection parameter budget"));
|
||||
}
|
||||
breaks.extend(curve.knot_vec().iter().copied().filter(|t| *t > start && *t < end));
|
||||
}
|
||||
}
|
||||
breaks.sort_by(f64::total_cmp);
|
||||
breaks.dedup();
|
||||
let mut stack = Vec::new();
|
||||
for pair in breaks.windows(2).rev() {
|
||||
let (a, b) = (pair[0], pair[1]);
|
||||
stack.push((a, b, point_at(c, a, budget)?, point_at(c, b, budget)?, 0u8));
|
||||
}
|
||||
let mut pieces = Vec::new();
|
||||
while let Some((a, b, pa, pb, depth)) = stack.pop() {
|
||||
let p1 = point_at(c, a + (b - a) / 3., budget)?.to_vec();
|
||||
let p2 = point_at(c, a + (b - a) * 2. / 3., budget)?.to_vec();
|
||||
let x = p1 * 27. - pa.to_vec() * 8. - pb.to_vec();
|
||||
let y = p2 * 27. - pa.to_vec() - pb.to_vec() * 8.;
|
||||
let controls = [pa, mt::Point3::from_vec((x * 2. - y) / 18.), mt::Point3::from_vec((y * 2. - x) / 18.), pb];
|
||||
let mut fits = true;
|
||||
for fraction in [0.125, 0.25, 0.5, 0.75, 0.875] {
|
||||
let actual = point_at(c, a + (b - a) * fraction, budget)?;
|
||||
let u = 1. - fraction;
|
||||
let fitted = controls[0].to_vec() * u * u * u
|
||||
+ controls[1].to_vec() * 3. * u * u * fraction
|
||||
+ controls[2].to_vec() * 3. * u * fraction * fraction
|
||||
+ controls[3].to_vec() * fraction * fraction * fraction;
|
||||
if (actual.to_vec() - fitted).magnitude() > tolerance * 0.1 {
|
||||
fits = false;
|
||||
}
|
||||
}
|
||||
if fits {
|
||||
pieces.push((a, b, controls));
|
||||
} else {
|
||||
if depth >= 12 || pieces.len() + stack.len() >= 1023 {
|
||||
return Err(invalid("intersection fit did not converge"));
|
||||
}
|
||||
let middle = a + (b - a) / 2.;
|
||||
let pm = point_at(c, middle, budget)?;
|
||||
stack.push((middle, b, pm, pb, depth + 1));
|
||||
stack.push((a, middle, pa, pm, depth + 1));
|
||||
}
|
||||
}
|
||||
let first = pieces.first().ok_or_else(|| invalid("empty intersection fit"))?;
|
||||
let mut points = vec![point(first.2[0])];
|
||||
let mut knots = vec![start; 4];
|
||||
for (i, (_, b, controls)) in pieces.iter().enumerate() {
|
||||
points.extend(controls.iter().skip(1).copied().map(point));
|
||||
knots.extend(std::iter::repeat_n(*b, if i + 1 == pieces.len() { 4 } else { 3 }));
|
||||
}
|
||||
Ok(b::Curve::Intersection { knots, points, fitting_tolerance: tolerance, faces: None })
|
||||
}
|
||||
Reference in new issue
Block a user