SMLib pcurves: an end SMLib sets within the edge's tolerance is mapped onto our interval

SMLib may end an edge at a parameter of its own: a knot its vertex lies at (-1.0 where the
vertex's angle gives -0.99999998, a 2e-8 step on the edge's quarter arc, far under 1e-6 mm).
We refused any change beyond 1e-9 of the parameter, a bound with no geometric meaning:
"SMLib moved an edge's interval" on 8 of Poppy Humanoid's faces. The ends SMLib chose are now
measured on the edge's curve: within the edge's tolerance, its pcurve is mapped onto our
interval by the affine change of parameter (exact for a B-spline: its knots move), and the
record's check measures it as any pcurve; farther is SMLib trimming the edge, refused with
how far.

Poppy: 227 → 232 bodies, 17 → 15 solids not imported, no interval refusals left.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKMoxpM7Vuv8Rb6E3K2
This commit is contained in:
jyviko
2026-10-27 19:01:37 -04:00
committed by Irakli Kourtis
parent 293d19f65b
commit 19c4a1502b
2 changed files with 57 additions and 5 deletions
+38 -5
View File
@@ -49,18 +49,51 @@ fn trims(face: &TrimFace<'_>) -> Result<Vec<Vec<Curve2>>, String> {
let e = edge_of(u.edge)?;
let edge = out.edges.get(e).ok_or("an edge SMLib lost")?;
let ours = face.edges.get(u.edge as usize).ok_or("edge reference")?;
let slack = 1e-9 * ours.interval.iter().fold(1.0f64, |m, t| m.max(t.abs()));
if (0..2).any(|k| (edge.interval[k] - ours.interval[k]).abs() > slack) {
return Err(format!("SMLib moved an edge's interval from {:?} to {:?}", ours.interval, edge.interval));
}
let found = made_uses.iter().find(|m| m.edge as usize == e && m.forward == (u.forward != reversed));
pcs.push(found.ok_or("a use SMLib did not keep")?.pcurve.clone());
let pc = found.ok_or("a use SMLib did not keep")?.pcurve.clone();
pcs.push(on_our_interval(pc, ours, edge.interval)?);
}
result.push(pcs);
}
Ok(result)
}
/// A pcurve SMLib made on its edge's interval `theirs`, on ours. SMLib may set an edge's end to
/// a parameter of its own (a knot its vertex lies at, the end of the part on the face): where
/// each end it chose is within the edge's tolerance of ours on the edge's curve, the pcurve is
/// mapped onto our interval by the affine change of parameter (exact for a B-spline: its knots
/// move), and the record's check measures it against the edge as any pcurve. Farther is SMLib
/// trimming the edge, refused with how far.
pub(crate) fn on_our_interval(pc: Curve2, ours: &solidcraft_geom::brep::pcurve::TrimEdge<'_>, theirs: [f64; 2]) -> Result<Curve2, String> {
let [a, b] = ours.interval;
let [c, d] = theirs;
if a == c && b == d {
return Ok(pc);
}
let curve = solidcraft_geom::brep::eval::CurveEval::new(ours.nurbs.unwrap_or(ours.curve)).map_err(|e| e.to_string())?;
let at = |t: f64| curve.point(t).ok_or_else(|| format!("the edge's curve has no point at {t}"));
let shift = at(a)?.dist(at(c)?).max(at(b)?.dist(at(d)?));
if !(shift <= ours.tolerance) || !(d > c) {
return Err(format!(
"SMLib moved an edge's interval from {:?} to {:?}, its ends {shift} mm along its curve (tolerance {} mm)",
ours.interval, theirs, ours.tolerance
));
}
let map = |k: f64| {
if k == c {
a
} else if k == d {
b
} else {
a + (k - c) * ((b - a) / (d - c))
}
};
Ok(match pc {
Curve2::Spline { knots, points } => Curve2::Spline { knots: knots.into_iter().map(map).collect(), points },
Curve2::RationalSpline { knots, homogeneous } => Curve2::RationalSpline { knots: knots.into_iter().map(map).collect(), homogeneous },
})
}
/// The face as a one-face open sheet, and its uses that go without trims.
fn sheet(face: &TrimFace<'_>) -> Result<(b::Brep, HashSet<(u32, u32, u32)>), String> {
let mut edges: Vec<b::Edge> = face
+19
View File
@@ -90,3 +90,22 @@ fn every_call_is_a_profiler_span_timed_by_the_c_api() {
let names: Vec<&str> = r.rows.iter().filter(|r| r.category == "smlib").map(|r| r.name).collect();
assert!(names.contains(&"smlib_make_box") && names.contains(&"smlib_brep_volume"), "{r:?}");
}
/// SMLib may end an edge at a parameter of its own (a knot its vertex lies at): within the
/// edge's tolerance on its curve the pcurve is mapped onto our interval exactly; farther is SMLib
/// trimming the edge, refused with how far.
#[test]
fn a_pcurve_smlib_ends_within_the_edges_tolerance_is_mapped_onto_our_interval() {
use solidcraft_geom::brep::pcurve::TrimEdge;
use solidcraft_geom::brep::{Curve, Curve2};
let line = Curve::Line { start: [0.0, 0.0, 0.0], end: [2.0, 0.0, 0.0] };
let edge = TrimEdge { curve: &line, nurbs: None, interval: [0.0, 1.0], vertices: [0, 1], tolerance: 1e-5 };
let theirs = [0.0, 1.0 - 2e-8];
let pc = Curve2::Spline { knots: vec![0.0, 0.0, theirs[1], theirs[1]], points: vec![[0.0, 0.0], [1.0, 0.0]] };
let mapped = crate::pcurves::on_our_interval(pc.clone(), &edge, theirs).unwrap();
assert_eq!(mapped.interval(), Some([0.0, 1.0]));
assert_eq!(mapped.eval(1.0), Some([1.0, 0.0]));
// 0.02 mm along the edge is past its 1e-5 mm.
let err = crate::pcurves::on_our_interval(pc, &edge, [0.0, 0.99]).unwrap_err();
assert!(err.contains("SMLib moved an edge's interval") && err.contains("tolerance"), "{err}");
}