pcurves: a flat patch's are a plane's; SMLib's sheet loops read either way round

A degree-1 patch of four control points on a parallelogram (a plane as an IGES
or STEP B-spline writes it) is that plane at every parameter, so its pcurves
take the plane's closed forms instead of going to SMLib (which drew the rim
circle of 3806's base as a 385-point spline).

SMLib may run a one-face sheet's loop against ours. A use's pcurve is the same
either way, so the loop's way round is read from an edge the face uses once and
every use is matched with it (3806 failed with 'a use SMLib did not keep').

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BnQawySUFx57iaJZq7z1Fm
This commit is contained in:
jyviko
2026-10-23 22:22:32 -04:00
parent 1bf2062590
commit 8799587210
2 changed files with 45 additions and 7 deletions
+26 -1
View File
@@ -241,6 +241,30 @@ fn cut(c: Curve2, [t0, t1]: [f64; 2]) -> Result<Curve2> {
})
}
/// A degree-1 patch of four control points on a parallelogram (a plane as a file's B-spline
/// writes it) as that plane: the patch is affine in (u, v), so it is the plane at every
/// parameter, and its pcurves have the plane's closed forms.
fn flat_patch(s: &Surface) -> Option<Surface> {
let Surface::Spline { u_knots, v_knots, points } = s else { return None };
let (&[a, a1, b, b1], &[c, c1, d, d1]) = (u_knots.as_slice(), v_knots.as_slice()) else { return None };
let [row0, row1] = points.as_slice() else { return None };
let (&[p00, p01], &[p10, p11]) = (row0.as_slice(), row1.as_slice()) else { return None };
let [p00, p01, p10, p11] = [p00, p01, p10, p11].map(Vec3::from);
if a != a1 || b != b1 || c != c1 || d != d1 || !(a < b && c < d) {
return None;
}
let size = [p00, p01, p10, p11].iter().flat_map(|p| [p.x.abs(), p.y.abs(), p.z.abs()]).fold(1.0, f64::max);
if (p00 + p11 - p10 - p01).len() > EXACT * size {
return None;
}
let (du, dv) = ((p10 - p00) * (1.0 / (b - a)), (p01 - p00) * (1.0 / (d - c)));
if !(du.cross(dv).len() > EXACT * size * size) {
return None;
}
let origin = p00 - du * a - dv * c;
Some(Surface::Plane { origin: [origin.x, origin.y, origin.z], u_axis: [du.x, du.y, du.z], v_axis: [dv.x, dv.y, dv.z] })
}
/// What a curve record is, for a message.
fn curve_kind(c: &Curve) -> &'static str {
match c {
@@ -266,7 +290,8 @@ struct On {
impl On {
fn new(s: &Surface) -> Result<Self> {
let surface = SurfaceEval::new(s)?;
let flat = flat_patch(s);
let surface = SurfaceEval::new(flat.as_ref().unwrap_or(s))?;
let dom = surface.domain();
Ok(On { surface, starts: uv::Starts::default(), dom, face: None })
}
+19 -6
View File
@@ -25,22 +25,35 @@ fn trims(face: &TrimFace<'_>) -> Result<Vec<Vec<Curve2>>, String> {
let body = crate::flat::Flat::from_arrays(&arrays).and_then(|f| f.import()).map_err(|e| e.to_string())?;
let (out, order) = body.to_native_ordered().map_err(|e| e.to_string())?;
let [made] = out.faces.as_slice() else { return Err(format!("SMLib made {} faces of one", out.faces.len())) };
let made_uses: Vec<&OrientedEdge> = made
.loops
.iter()
.flat_map(|l| match l {
Loop::Edges { edges } => edges.as_slice(),
Loop::Vertex { .. } => &[],
})
.collect();
// SMLib keeps the edges in the order they came; the record renumbers them.
let edge_of = |u: u32| order.edges.get(u as usize).copied().ok_or("an edge SMLib lost");
// A sheet's loops come back the way SMLib runs them, which may be against ours: a use is the
// same pcurve either way (on its edge's interval in its curve's direction), and the loop's
// way round is read from an edge the face uses once.
let ours = face.loops.iter().flatten();
let once = ours.clone().find(|u| ours.clone().filter(|v| v.edge == u.edge).count() == 1).ok_or("a face of seam edges only")?;
let at = edge_of(once.edge)?;
let reversed = made_uses.iter().find(|m| m.edge as usize == at).ok_or("a use SMLib did not keep")?.forward != once.forward;
let mut result = Vec::with_capacity(face.loops.len());
for l in &face.loops {
let mut pcs = Vec::with_capacity(l.len());
for u in l {
// SMLib keeps the edges in the order they came; the record renumbers them.
let e = order.edges.get(u.edge as usize).copied().ok_or("an edge SMLib lost")?;
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.loops.iter().find_map(|ml| match ml {
Loop::Edges { edges } => edges.iter().find(|m| m.edge as usize == e && m.forward == u.forward),
Loop::Vertex { .. } => None,
});
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());
}
result.push(pcs);