kernel: bridges and surfaces read the body's own record

bridge::{chain, frames, frames_across, chain_closes} and surfaces take
body.record() instead of encoding one; a surface body is Body::from_record of the
canonical record, a stitched one Body::from_smlib_record of SMLib's export, its
history matched to the input record when SMLib's import numbers it alike.
record::attach takes the new form.

(not built: base in progress)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017itGh5fpMEousnZ91cYcVt
This commit is contained in:
jyviko
2026-10-22 07:15:40 -04:00
parent 5c1b335e1b
commit b5a7d52b73
3 changed files with 51 additions and 43 deletions
+7 -8
View File
@@ -96,7 +96,7 @@ pub fn chain(body: &Body, at: &[Vec3]) -> Result<Chain> {
if at.is_empty() || at.len() > 10_000 || at.iter().any(|p| !p.is_finite()) {
return Err(KernelError::Invalid("pick 1 to 10000 points on the edges of a chain".into()));
}
let r = crate::native::brep_record(body)?;
let r = body.record();
let mut pool: Vec<usize> = Vec::new();
for p in at {
let e = crate::surfaces::edge_near(body, *p).ok_or_else(|| KernelError::Invalid("no edge near a picked point".to_string()))?;
@@ -132,8 +132,7 @@ pub fn chain(body: &Body, at: &[Vec3]) -> Result<Chain> {
/// Whether a chain of `body`'s edges closes back on itself.
pub fn chain_closes(body: &Body, chain: &Chain) -> bool {
let Ok(r) = crate::native::brep_record(body) else { return false };
closes(&r, chain)
closes(body.record(), chain)
}
fn closes(r: &Brep, chain: &Chain) -> bool {
@@ -166,7 +165,7 @@ impl ChainLine {
fn of(body: &Body, chain: &Chain) -> Result<ChainLine> {
/// Samples per edge used for the length table.
const PER_EDGE: usize = 64;
let r = crate::native::brep_record(body)?;
let r = body.record();
let mut samples: Vec<Sample> = Vec::with_capacity(chain.len() * (PER_EDGE + 1) + 1);
let mut curves = Vec::with_capacity(chain.len());
let push = |samples: &mut Vec<Sample>, p: Vec3, edge: usize, t: f64| {
@@ -322,7 +321,7 @@ pub fn frames(body: &Body, face: usize, chain: &Chain, leave: Vec3, count: usize
}
let line = ChainLine::of(body, chain)?;
let stations = line.stations(count, 0.0, false)?;
let r = crate::native::brep_record(body)?;
let r = body.record();
frames_on(&r, &vec![face; chain.len()], &stations, &line, leave)
}
@@ -392,7 +391,7 @@ pub fn frames_across(body: &Body, face: usize, stations: &[(Vec3, Vec3)], length
if stations.len() > 1 << 16 || !(length.is_finite() && length > 0.0) {
return Err(KernelError::Invalid("frames across a boundary need at most 65536 stations on a boundary of finite size".into()));
}
let r = crate::native::brep_record(body)?;
let r = body.record();
let support = Support::of(&r, face, length)?;
let outward = if support.forward { 1.0 } else { -1.0 };
let mut walk = Walk::new(length);
@@ -733,7 +732,7 @@ pub fn bridge(a: &[Frame], b: &[Frame], orders: [Continuity; 2], crown: [f64; 2]
/// The one face of a bridge body: its surface and its parameter box.
pub(crate) fn the_face(body: &Body) -> Result<(SurfaceEval, [[f64; 2]; 2])> {
let r = crate::native::brep_record(body)?;
let r = body.record();
let face = r.faces.first().ok_or_else(|| KernelError::Failed("bridge: no face".into()))?;
let surf = SurfaceEval::new(&face.surface).map_err(|e| KernelError::Failed(format!("bridge: {e}")))?;
Ok((surf, face.domain))
@@ -903,7 +902,7 @@ pub fn blend_between(
let chain_a = chain(a, picks_a)?;
let chain_b = chain(b, picks_b)?;
let (line_a, line_b) = (ChainLine::of(a, &chain_a)?, ChainLine::of(b, &chain_b)?);
let (record_a, record_b) = (crate::native::brep_record(a)?, crate::native::brep_record(b)?);
let (record_a, record_b) = (a.record(), b.record());
if line_a.closed != line_b.closed {
return Err(KernelError::Invalid("a closed chain blends only to a closed chain, and an open one only to an open one".into()));
}
+2 -2
View File
@@ -79,14 +79,14 @@ fn valley_between_cylinders(count: usize, r: f64) -> (Vec<Frame>, Vec<Frame>) {
/// The edges of `body`'s face `face` chained into one loop (its outer loop, as it runs).
fn loop_of(body: &Body, face: usize) -> Chain {
let r = crate::native::brep_record(body).unwrap();
let r = body.record();
let solidcraft_geom::brep::Loop::Edges { edges } = &r.faces[face].loops[0] else { panic!("a vertex loop") };
edges.iter().map(|u| (u.edge as usize, u.forward)).collect()
}
/// The face of `body` (its index) whose surface contains `at`.
fn face_at(body: &Body, at: Vec3) -> usize {
let r = crate::native::brep_record(body).unwrap();
let r = body.record();
r.faces.iter().position(|f| SurfaceEval::new(&f.surface).unwrap().project(at, None, 1e-6).is_some()).unwrap()
}
+42 -33
View File
@@ -29,7 +29,7 @@ use crate::{KernelError, Result, record};
impl Body {
/// A surface body: some shell does not close (an edge bounds a face on one side only).
pub fn is_surface(&self) -> bool {
!self.is_mesh() && crate::native::brep_record(self).is_ok_and(|r| r.edges.iter().any(|e| e.uses.len() == 1))
!self.is_mesh() && self.record().edges.iter().any(|e| e.uses.len() == 1)
}
}
@@ -339,9 +339,9 @@ fn uv_box(loops: &[b::Loop]) -> Option<[[f64; 2]; 2]> {
}
/// A body of a record this module built, and whether it numbers its faces and edges as the
/// record does (so a history in the record's order is the body's).
/// record does (so a history in the record's order is the body's): the record is the body's own.
fn record_body(r: &Brep) -> Result<(Body, bool)> {
imp::record_body(r)
Ok((Body::from_record(r.clone())?, true))
}
/// `body` with the history `h` over operands of `counts` faces and edges when its order is the
@@ -358,7 +358,7 @@ pub(crate) fn surface_body(r: &Brep) -> Result<Body> {
/// `body` (made by an operation on `operands`) with the history that every face and edge is new to
/// it.
pub(crate) fn all_new(body: Body, operands: &[&Body]) -> Result<Body> {
let r = crate::native::brep_record(&body)?;
let r = body.record();
let faces = vec![record::generated(Vec::new()); r.faces.len()];
let h = record_history(&r, operands.len(), faces, |_, _, _| Vec::new());
body.with_history_counts(h, &record::counts(operands))
@@ -495,7 +495,7 @@ fn sketch_loop(parts: &mut Parts, plane: &Plane, segs: &[Seg2]) -> Result<b::Loo
/// The edge of a body nearest a point (by samples along each edge), within 5% of the body's size:
/// its index in the body's edge order.
pub(crate) fn edge_near(body: &Body, p: Vec3) -> Option<usize> {
let r = crate::native::brep_record(body).ok()?;
let r = body.record();
let mut best: Option<(f64, usize)> = None;
for (i, e) in r.edges.iter().enumerate() {
let Ok(c) = CurveEval::new(&e.curve) else { continue };
@@ -525,7 +525,7 @@ pub fn patch_edges(body: &Body, at: &[Vec3]) -> Result<Body> {
if at.is_empty() || at.len() > 10_000 {
return Err(invalid("pick the edges of a closed loop"));
}
let r = crate::native::brep_record(body)?;
let r = body.record();
let chain = picked_loop(body, at)?;
let Some(plane) = chain_plane(&r, &chain, crate::fill::planarity_tolerance(body))? else {
return crate::fill::fill_loop(body, &chain);
@@ -625,7 +625,7 @@ pub(crate) fn picked_loop(body: &Body, at: &[Vec3]) -> Result<Chain> {
/// chain runs from the free end met first in pick order. Edges that branch, or form more than one
/// chain, are refused.
pub(crate) fn picked_chain(body: &Body, at: &[Vec3]) -> Result<(Chain, bool)> {
let r = crate::native::brep_record(body)?;
let r = body.record();
let mut edges: Vec<usize> = Vec::new();
for p in at {
let e = edge_near(body, *p).ok_or_else(|| invalid(format!("no edge at {p:?}")))?;
@@ -715,7 +715,7 @@ pub fn copy_faces(body: &Body, at: &[Vec3]) -> Result<Body> {
return Err(invalid("pick the faces to copy"));
}
let chosen = picked_faces(body, at)?;
let r = crate::native::brep_record(body)?;
let r = body.record();
taken(body, &r, &chosen.into_iter().map(|f| (f, false)).collect::<Vec<_>>())
}
@@ -724,7 +724,7 @@ pub fn copy_faces(body: &Body, at: &[Vec3]) -> Result<Body> {
/// surfaces. Histories are over `[body]`: each face is the body's.
pub fn unstitch(body: &Body, at: &[Vec3]) -> Result<(Vec<Body>, Option<Body>)> {
body.require_brep("unstitch")?;
let r = crate::native::brep_record(body)?;
let r = body.record();
if r.faces.len() < 2 {
return Err(invalid("the body has one face: nothing to unstitch"));
}
@@ -744,7 +744,7 @@ pub fn reverse_faces(body: &Body, at: &[Vec3]) -> Result<Body> {
return Err(invalid("a solid's faces face out: unstitch it into surfaces first"));
}
let chosen = picked_faces(body, at)?;
let r = crate::native::brep_record(body)?;
let r = body.record();
taken(body, &r, &(0..r.faces.len()).map(|f| (f, chosen.contains(&f))).collect::<Vec<_>>())
}
@@ -756,7 +756,7 @@ pub fn remove_faces(body: &Body, faces: &[usize]) -> Result<Body> {
if faces.is_empty() {
return Err(invalid("no faces given"));
}
let r = crate::native::brep_record(body)?;
let r = body.record();
let left: Vec<(usize, bool)> = (0..r.faces.len()).filter(|f| !faces.contains(f)).map(|f| (f, false)).collect();
if left.is_empty() {
return Err(invalid("that would delete every face of the body"));
@@ -813,8 +813,8 @@ pub fn stitch(bodies: &[&Body], tol: f64) -> Result<Body> {
}
let size = bodies.iter().map(|b| b.size()).fold(0.0, f64::max);
let tol = if tol.is_finite() && tol > 0.0 { tol } else { size * 1e-6 + 1e-6 };
let records = bodies.iter().map(|b| crate::native::brep_record(b)).collect::<Result<Vec<_>>>()?;
let (parts, offsets) = Parts::joined(&records.iter().collect::<Vec<_>>())?;
let records: Vec<&Brep> = bodies.iter().map(|b| b.record()).collect();
let (parts, offsets) = Parts::joined(&records)?;
let c = parts.canonical()?;
let (body, h) = imp::stitched(&c.brep, tol)?;
let h = h.and_then(|h| carried(&h, &c, &offsets, bodies.len(), |operand, face| FaceOrigin::Face { operand, face }));
@@ -835,7 +835,7 @@ pub fn thicken(body: &Body, thickness: f64, symmetric: bool) -> Result<Body> {
/// apart): see [`crate::offset_surface`]. A distance of zero copies the faces. Each face is the
/// offset of the one it came from.
pub(crate) fn offset_faces(body: &Body, picked: &[usize], distance: f64, tolerance: f64) -> Result<Body> {
let r = crate::native::brep_record(body)?;
let r = body.record();
if distance == 0.0 {
return taken(body, &r, &picked.iter().map(|f| (*f, false)).collect::<Vec<_>>());
}
@@ -847,7 +847,7 @@ pub(crate) fn offset_faces(body: &Body, picked: &[usize], distance: f64, toleran
let faces = h
.and_then(|h| carried(&h, &c, &offsets, 1, offset))
.map(|h| h.faces.into_iter().map(|f| FaceProv { change: Change::Generated, ..f }).collect::<Vec<_>>());
let rec = crate::native::brep_record(&result)?;
let rec = result.record();
let h = faces.map(|faces| record_history(&rec, 1, faces, |_, between, faces| record::meeting(between, faces)));
Ok(result.with_checked_history(h, &record::counts(&[body])))
}
@@ -860,7 +860,7 @@ pub fn extend(body: &Body, at: &[Vec3], distance: f64) -> Result<Body> {
if !(distance.is_finite() && distance > 0.0) {
return Err(invalid("the extension distance must be positive"));
}
let r = crate::native::brep_record(body)?;
let r = body.record();
let [face] = r.faces.as_slice() else { return Err(invalid("not supported yet: extending a surface of more than one face")) };
let (origin, du, dv) = match face.surface {
b::Surface::Plane { origin, u_axis, v_axis } => (v3(origin), v3(u_axis), v3(v_axis)),
@@ -1012,7 +1012,7 @@ fn sheet_of(pieces: Vec<(Brep, Vec<FaceProv>)>, stitch: bool) -> Result<Body> {
let faces: Option<Vec<FaceProv>> = c.faces.iter().map(|p| prov(*p)).collect();
if !stitch {
let (body, same) = record_body(&c.brep)?;
return record::attach(body, &[], faces.filter(|_| same), Default::default());
return Ok(record::attach(body, &[], faces.filter(|_| same), &[]));
}
let (body, h) = imp::stitched(&c.brep, c.brep.tolerance * 10.0)?;
// A stitched face keeps the tag of the joined record's face it was.
@@ -1025,7 +1025,7 @@ fn sheet_of(pieces: Vec<(Brep, Vec<FaceProv>)>, stitch: bool) -> Result<Body> {
})
.collect::<Option<Vec<_>>>()
});
record::attach(body, &[], sewn, Default::default())
Ok(record::attach(body, &[], sewn, &[]))
}
/// A surface extruded from chains of sketch segments on `plane`: each chain (open, or closed such
@@ -1272,25 +1272,37 @@ mod imp {
r.to_native().map_err(|e| failed(format!("SMLib {what}: {e}")))
}
pub(super) fn record_body(r: &Brep) -> Result<(Body, bool)> {
let (brep, order) = crate::native::smlib_record_ordered(r)?;
let (body, kept) = crate::native::body_from_smlib_ordered(&brep)?;
Ok((body, order.is_some() && kept.is_some()))
/// Whether two records of one body number its faces, edges and vertices alike: the same
/// loops of the same edges, run the same ways, and each edge between the same vertices.
fn same_numbering(a: &Brep, b: &Brep) -> bool {
let loops = |f: &b::Face| -> Vec<Vec<(u32, bool)>> {
f.loops
.iter()
.map(|l| match l {
b::Loop::Edges { edges } => edges.iter().map(|u| (u.edge, u.forward)).collect(),
b::Loop::Vertex { vertex } => vec![(*vertex, true)],
})
.collect()
};
a.faces.len() == b.faces.len()
&& a.edges.len() == b.edges.len()
&& a.vertices.len() == b.vertices.len()
&& a.faces.iter().zip(&b.faces).all(|(x, y)| loops(x) == loops(y))
&& a.edges.iter().zip(&b.edges).all(|(x, y)| x.vertices == y.vertices)
}
/// The record `r` sewn by SMLib at `tol` (`smlib_stitch`), with its history over `[r]` (faces
/// and edges by their index in `r`); none where the orders cannot be matched.
/// and edges by their index in `r`); none where SMLib's import of `r` does not number it as
/// `r` does.
pub(super) fn stitched(r: &Brep, tol: f64) -> Result<(Body, Option<History>)> {
let (brep, order) = crate::native::smlib_record_ordered(r)?;
let brep = SmBrep::from_record(r, true).map_err(sm("stitch"))?;
let (back, order) = brep.to_native_ordered().map_err(|e| failed(format!("SMLib stitch: {e}")))?;
let tagged = brep.tagged(0).map_err(sm("stitch"))?;
let (sewn, _closed) = tagged.stitch(tol).map_err(sm("stitch"))?;
checked(&sewn, "stitch")?;
let (body, kept) = crate::native::body_from_smlib_ordered(&sewn)?;
let h = match (order, kept) {
(Some(o), Some(k)) => sewn.history().ok().and_then(|raw| raw.history(&[o], &k).ok()),
_ => None,
};
Ok((body, h))
let (record, kept) = sewn.to_native_ordered().map_err(|e| failed(format!("SMLib stitch: {e}")))?;
let h = if same_numbering(r, &back) { sewn.history().ok().and_then(|raw| raw.history(&[order], &kept).ok()) } else { None };
Ok((Body::from_smlib_record(sewn, record)?, h))
}
pub(super) fn thicken(body: &Body, thickness: f64, symmetric: bool) -> Result<Body> {
@@ -1492,9 +1504,6 @@ mod imp {
pub(super) fn refuse(what: &str) -> KernelError {
crate::smlib_sweeps::refuse(what)
}
pub(super) fn record_body(_: &Brep) -> Result<(Body, bool)> {
Err(not_built())
}
pub(super) fn stitched(_: &Brep, _: f64) -> Result<(Body, Option<History>)> {
Err(not_built())
}