mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-05 19:45:54 +00:00
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:
@@ -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 })
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user