mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 10:35:58 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user