diff --git a/crates/geom/src/brep/pcurve.rs b/crates/geom/src/brep/pcurve.rs index 00a4dbbf57..8460e5dff3 100644 --- a/crates/geom/src/brep/pcurve.rs +++ b/crates/geom/src/brep/pcurve.rs @@ -241,6 +241,30 @@ fn cut(c: Curve2, [t0, t1]: [f64; 2]) -> Result { }) } +/// 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 { + 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 { - 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 }) } diff --git a/crates/smlib/src/pcurves.rs b/crates/smlib/src/pcurves.rs index cdebead6ca..daf8b80cad 100644 --- a/crates/smlib/src/pcurves.rs +++ b/crates/smlib/src/pcurves.rs @@ -25,22 +25,35 @@ fn trims(face: &TrimFace<'_>) -> Result>, 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);