kernel: an assembled face keeps its NURBS form

The assembly kept a face's domain and pcurves, which are in its NURBS form's parameters, but
dropped the form and computed new pcurves and the domain on the analytic surface. The form now
travels with the face, and its pcurves and domain are computed on it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
jyviko
2026-10-23 15:12:50 -04:00
parent f070eee2b6
commit 58f60adbfb
3 changed files with 41 additions and 9 deletions
+21 -4
View File
@@ -52,6 +52,9 @@ pub(crate) struct AUse {
#[derive(Clone, Debug)]
pub(crate) struct AFace {
pub surface: Surface,
/// The NURBS form the face's parameters are on, as the record's `Face::nurbs`: its domain,
/// pcurves and `natural_out` are about this surface when it is given.
pub nurbs: Option<Surface>,
pub natural_out: bool,
pub loops: Vec<Vec<AUse>>,
pub shell: usize,
@@ -59,6 +62,13 @@ pub(crate) struct AFace {
pub domain: Option<[[f64; 2]; 2]>,
}
impl AFace {
/// The surface the face's (u, v) are on, as `Face::parameters`.
pub fn parameters(&self) -> &Surface {
self.nurbs.as_ref().unwrap_or(&self.surface)
}
}
/// A body taken apart: vertices, edges and faces that [`Assembly::finish`] builds into a record.
#[derive(Clone, Debug)]
pub(crate) struct Assembly {
@@ -108,7 +118,14 @@ impl Assembly {
if slot.is_some() {
return Err(invalid("a face used by two shells"));
}
*slot = Some(AFace { surface: f.surface.clone(), natural_out: of.forward, loops, shell: s, domain: Some(f.domain) });
*slot = Some(AFace {
surface: f.surface.clone(),
nurbs: f.nurbs.clone(),
natural_out: of.forward,
loops,
shell: s,
domain: Some(f.domain),
});
}
}
if faces.iter().any(Option::is_none) {
@@ -273,8 +290,8 @@ impl Assembly {
.collect::<Result<Vec<_>>>()
})
.collect::<Result<_>>()?;
let pcurves = pcurves_within(&f.surface, &uses, self.tolerance, f.domain).map_err(|e| invalid(format!("face {i}: {e}")))?;
let domain = domain_of(&f.surface, &pcurves, f.domain).ok_or_else(|| invalid(format!("face {i}: no parameter box")))?;
let pcurves = pcurves_within(f.parameters(), &uses, self.tolerance, f.domain).map_err(|e| invalid(format!("face {i}: {e}")))?;
let domain = domain_of(f.parameters(), &pcurves, f.domain).ok_or_else(|| invalid(format!("face {i}: no parameter box")))?;
let stored = loops
.iter()
.zip(&pcurves)
@@ -282,7 +299,7 @@ impl Assembly {
edges: l.iter().zip(pc).map(|(u, pcurve)| OrientedEdge { edge: u.edge, forward: u.forward, pcurve: pcurve.clone() }).collect(),
})
.collect();
Ok(Face { nurbs: None, surface: f.surface.clone(), domain, loops: stored })
Ok(Face { nurbs: f.nurbs.clone(), surface: f.surface.clone(), domain, loops: stored })
}
}
+10 -3
View File
@@ -357,7 +357,7 @@ fn emboss_impl(body: &Body, anchor: Option<Vec3>, plane: &Plane, regions: &[Regi
cap.push(top);
added.extend(walls);
}
added.push(AFace { surface: wall.cylinder(rr), natural_out: true, loops: cap, shell: selected.shell, domain: None });
added.push(AFace { surface: wall.cylinder(rr), nurbs: None, natural_out: true, loops: cap, shell: selected.shell, domain: None });
added_history.push(crate::record::cap(true));
}
// An island inside a profile hole cuts that hole's original-radius patch,
@@ -382,7 +382,14 @@ fn emboss_impl(body: &Body, anchor: Option<Vec3>, plane: &Plane, regions: &[Regi
for (k, loops) in base_patches.into_iter().enumerate() {
// The face's own parameter box only where it keeps its outer loop.
let domain = if k == 0 { selected.domain } else { None };
asm.face(AFace { surface: selected.surface.clone(), natural_out: selected.natural_out, loops, shell: selected.shell, domain });
asm.face(AFace {
surface: selected.surface.clone(),
nurbs: selected.nurbs.clone(),
natural_out: selected.natural_out,
loops,
shell: selected.shell,
domain,
});
}
for f in added {
asm.face(f);
@@ -470,7 +477,7 @@ impl Wrap {
AUse { edge: et.edge, forward: !et.forward, given: None },
AUse { edge: ra, forward: false, given: None },
];
walls.push(AFace { surface, natural_out: true, loops: vec![lp], shell, domain: None });
walls.push(AFace { surface, nurbs: None, natural_out: true, loops: vec![lp], shell, domain: None });
be.push(eb);
te.push(et);
}
+10 -2
View File
@@ -270,7 +270,7 @@ fn wall(p: &Piece, n: Vec3, bottom: &AUse, top: &AUse, up_a: u32, up_b: u32, rig
u.forward = !u.forward;
}
}
Ok(AFace { surface, natural_out, loops: vec![lp], shell, domain: None })
Ok(AFace { surface, nurbs: None, natural_out, loops: vec![lp], shell, domain: None })
}
/// Raise the band of the planar face at `at` that runs `width` wide along its outer loop
@@ -321,7 +321,14 @@ pub fn raise_band(body: &Body, at: Vec3, width: f64, height: f64, outside: bool)
// (The band's edge runs the face's way round: a hole when the band follows the inner
// loop, the outer loop when it follows the outer.)
let rest_loops = if outside { vec![cedges.clone(), other] } else { vec![other, cedges.clone()] };
let rest = AFace { surface: face.surface.clone(), natural_out: face.natural_out, loops: rest_loops, shell: face.shell, domain: face.domain };
let rest = AFace {
surface: face.surface.clone(),
nurbs: face.nurbs.clone(),
natural_out: face.natural_out,
loops: rest_loops,
shell: face.shell,
domain: face.domain,
};
// Walls: along the face's loop (the band's outside continues the walls below, on the
// loop's right) and along the band's edge (its outside on that edge's left).
let mut walls = Vec::with_capacity(2 * k);
@@ -343,6 +350,7 @@ pub fn raise_band(body: &Body, at: Vec3, width: f64, height: f64, outside: bool)
let u = n.any_perp();
let top = AFace {
surface: Surface::Plane { origin: arr(origin), u_axis: arr(u), v_axis: arr(n.cross(u)) },
nurbs: None,
natural_out: true,
loops: top_loops,
shell: face.shell,