diff --git a/crates/kernel/src/fill.rs b/crates/kernel/src/fill.rs
index ac084be61c..bfc5a342bd 100644
--- a/crates/kernel/src/fill.rs
+++ b/crates/kernel/src/fill.rs
@@ -383,7 +383,7 @@ pub(crate) fn patch_loop(body: &Body, at: &[Vec3], crown: f64) -> Result
{
Err(e) => return Err(spline_error(e)),
};
let projection = Projection { origin: fill.origin, axes: fill.axes };
- (surface_record(&fill.surface)?, fill.counter_clockwise, Some(projection))
+ (crate::surfaces::spline_surface(&fill.surface), fill.counter_clockwise, Some(projection))
}
};
let pcurves = match &pcurve {
@@ -604,21 +604,6 @@ impl Projection {
}
}
-/// A geom NURBS surface as a record: a plain spline where every weight is one.
-fn surface_record(s: &solidcraft_geom::bspline::NurbsSurface) -> Result {
- let [ku, kv] = s.knots().clone();
- let rows = s.net().into_u_rows();
- if rows.iter().flatten().all(|h| h.w == 1.0) {
- Ok(b::Surface::Spline { u_knots: ku, v_knots: kv, points: rows.iter().map(|r| r.iter().map(|h| [h.x, h.y, h.z]).collect()).collect() })
- } else {
- Ok(b::Surface::RationalSpline {
- u_knots: ku,
- v_knots: kv,
- homogeneous: rows.iter().map(|r| r.iter().map(|h| [h.x, h.y, h.z, h.w]).collect()).collect(),
- })
- }
-}
-
/// The one-face sheet on `surface` bounded by the loop `chain` of `record`'s own edges, run
/// counter-clockwise about the surface's natural normal (`ccw`: the chain already does), each
/// edge's pcurve the exact projection where the surface is a height field. Its history is over
@@ -770,7 +755,7 @@ fn quilt_body(body: &Body, record: &b::Brep, chain: &[(usize, bool)], fill: &Iso
}
}
}
- faces.push(PartFace { surface: surface_record(&patch.surface)?, forward: true, loops: vec![uses] });
+ faces.push(PartFace { surface: crate::surfaces::spline_surface(&patch.surface), forward: true, loops: vec![uses] });
}
let made = assemble(Parts { vertices, edges, faces, tolerance: tol }, false)?;
let smlib = solidcraft_smlib::Brep::from_record(&made.brep, true).map_err(|e| KernelError::Failed(format!("patch: SMLib: {e}")))?;
diff --git a/crates/kernel/src/sew.rs b/crates/kernel/src/sew.rs
index a5caccbe7d..c193d278d8 100644
--- a/crates/kernel/src/sew.rs
+++ b/crates/kernel/src/sew.rs
@@ -1231,115 +1231,65 @@ pub(crate) fn assemble(parts: Parts, solid: bool) -> Result {
let o = around.ok_or_else(|| fail("a cavity lies in no solid".into()))?;
cavities.get_mut(o).ok_or_else(|| fail("cavity".into()))?.push(k);
}
- // Regions, and the shells numbered by first appearance in them.
- let mut shell_group: Vec = Vec::new();
- let mut shell_of: HashMap = HashMap::new();
- let mut number = |g: usize, shell_group: &mut Vec| -> u32 {
- *shell_of.entry(g).or_insert_with(|| {
- shell_group.push(g);
- (shell_group.len() - 1) as u32
- })
- };
+ // The record as its parts stand (shell k is group k, face i part face i, edges and vertices
+ // as given), then put in canonical order.
+ let index = |i: usize| u32::try_from(i).map_err(|_| fail("index".into()));
let open: Vec = (0..groups.len()).filter(|k| !closed.get(*k).copied().unwrap_or(false)).collect();
- let infinite: Vec = outers.iter().chain(&open).map(|g| number(*g, &mut shell_group)).collect();
- let mut regions = vec![b::Region { shells: infinite, void: false }];
+ let mut regions = vec![b::Region { shells: outers.iter().chain(&open).map(|g| index(*g)).collect::>()?, void: false }];
for &o in &outers {
- let mut shells = vec![number(o, &mut shell_group)];
+ let mut shells = vec![index(o)?];
for &c in cavities.get(o).into_iter().flatten() {
- shells.push(number(c, &mut shell_group));
+ shells.push(index(c)?);
}
regions.push(b::Region { shells, void: false });
}
for &o in &outers {
for &c in cavities.get(o).into_iter().flatten() {
- regions.push(b::Region { shells: vec![number(c, &mut shell_group)], void: true });
+ regions.push(b::Region { shells: vec![index(c)?], void: true });
}
}
- // Faces in first appearance walking shells; edges walking faces → loops → uses (backwards on
- // a face whose side in its shell is reversed), with their uses; vertices walking edges.
- let index = |i: usize| u32::try_from(i).map_err(|_| fail("index".into()));
- let mut face_new = vec![usize::MAX; nf];
- let mut face_order = Vec::with_capacity(nf);
- for g in &shell_group {
- for &f in groups.get(*g).into_iter().flatten() {
- if face_new.get(f) == Some(&usize::MAX) {
- if let Some(slot) = face_new.get_mut(f) {
- *slot = face_order.len();
- }
- face_order.push(f);
- }
- }
- }
- let mut edge_new: Vec> = vec![None; edges.len()];
- let mut edge_order = Vec::with_capacity(edges.len());
- let mut uses: Vec> = vec![Vec::new(); edges.len()];
- for (fnew, &f) in face_order.iter().enumerate() {
- let forward = faces.get(f).is_some_and(|x| x.forward);
- for (li, l) in loops.get(f).into_iter().flatten().enumerate() {
- let n = l.len();
- for k in 0..n {
- let p = if forward { k } else { n - 1 - k };
- let &(e, fwd, _) = l.get(p).ok_or_else(|| fail("edge reference".into()))?;
- let slot = edge_new.get_mut(e as usize).ok_or_else(|| fail("edge reference".into()))?;
- if slot.is_none() {
- *slot = Some(edge_order.len());
- edge_order.push(e as usize);
- }
- let at = b::EdgeUseRef { face: index(fnew)?, r#loop: index(li)?, position: index(p)?, upper: !fwd };
- uses.get_mut(e as usize).ok_or_else(|| fail("edge reference".into()))?.push(at);
- }
- }
- }
- let mut vertex_new: Vec> = vec![None; vertices.len()];
- let mut out_vertices = Vec::new();
- let mut out_edges = Vec::with_capacity(edge_order.len());
- for &e in &edge_order {
- let edge = edges.get(e).ok_or_else(|| fail("edge reference".into()))?;
- let mut ends = [0u32; 2];
- for (end, v) in ends.iter_mut().zip(edge.vertices) {
- let slot = vertex_new.get_mut(v as usize).ok_or_else(|| fail("vertex reference".into()))?;
- *end = match slot {
- Some(n) => *n,
- None => {
- let n = index(out_vertices.len())?;
- let point = *vertices.get(v as usize).ok_or_else(|| fail("vertex reference".into()))?;
- out_vertices.push(b::Vertex { point, tolerance: tol });
- *slot = Some(n);
- n
- }
- };
- }
- let edge_uses = uses.get(e).cloned().unwrap_or_default();
- out_edges.push(b::Edge { curve: edge.curve.clone(), interval: edge.interval, vertices: ends, tolerance: tol, uses: edge_uses });
- }
- let emap = |e: u32| edge_new.get(e as usize).copied().flatten().ok_or_else(|| fail("edge reference".into())).and_then(index);
- let mut out_faces = Vec::with_capacity(nf);
- for &f in &face_order {
- let (Some(face), Some(ls), Some(pc), Some(domain)) = (faces.get(f), loops.get(f), pcs.get(f), domains.get(f)) else {
- return Err(fail("face reference".into()));
- };
+ let shells = groups
+ .iter()
+ .map(|g| {
+ let faces = g
+ .iter()
+ .map(|&f| Ok(b::OrientedFace { face: index(f)?, forward: faces.get(f).is_some_and(|x| x.forward) }))
+ .collect::>()?;
+ Ok(b::Shell::Faces { faces, wires: Vec::new() })
+ })
+ .collect::>()?;
+ let mut record_faces = Vec::with_capacity(nf);
+ for (f, ((face, ls), (pc, domain))) in faces.iter().zip(&loops).zip(pcs.iter().zip(&domains)).enumerate() {
let mut record_loops = Vec::with_capacity(ls.len());
for (l, c) in ls.iter().zip(pc) {
- let mut oriented = Vec::with_capacity(l.len());
- for (&(e, forward, _), pcurve) in l.iter().zip(c) {
- oriented.push(b::OrientedEdge { edge: emap(e)?, forward, pcurve: pcurve.clone() });
- }
+ let oriented = l.iter().zip(c).map(|(&(edge, forward, _), pcurve)| b::OrientedEdge { edge, forward, pcurve: pcurve.clone() }).collect();
record_loops.push(b::Loop::Edges { edges: oriented });
}
- out_faces.push(b::Face { surface: face.surface.clone(), domain: *domain, loops: record_loops });
- }
- let mut shells = Vec::with_capacity(shell_group.len());
- for g in &shell_group {
- let mut oriented = Vec::new();
- for &f in groups.get(*g).into_iter().flatten() {
- let face = face_new.get(f).copied().ok_or_else(|| fail("face reference".into())).and_then(index)?;
- oriented.push(b::OrientedFace { face, forward: faces.get(f).is_some_and(|x| x.forward) });
+ if record_loops.is_empty() {
+ return Err(fail(format!("face {f} has no loop")));
}
- shells.push(b::Shell::Faces { faces: oriented, wires: Vec::new() });
+ record_faces.push(b::Face { surface: face.surface.clone(), domain: *domain, loops: record_loops });
}
- let brep = b::Brep { version: b::VERSION, tolerance: tol, regions, shells, faces: out_faces, edges: out_edges, vertices: out_vertices };
- brep.validate().map_err(|e| KernelError::Failed(format!("sew: the record is invalid: {e}")))?;
- Ok(Assembled { brep, faces: face_new, edges: edge_new })
+ let record_edges = edges
+ .iter()
+ .map(|e| b::Edge { curve: e.curve.clone(), interval: e.interval, vertices: e.vertices, tolerance: tol, uses: Vec::new() })
+ .collect();
+ let record_vertices = vertices.iter().map(|point| b::Vertex { point: *point, tolerance: tol }).collect();
+ let loose = b::Brep { version: b::VERSION, tolerance: tol, regions, shells, faces: record_faces, edges: record_edges, vertices: record_vertices };
+ let part = loose.canonical().map_err(|e| KernelError::Failed(format!("sew: the record is invalid: {e}")))?;
+ let mut face_new = vec![usize::MAX; nf];
+ for (c, &f) in part.faces.iter().enumerate() {
+ if let Some(slot) = face_new.get_mut(f as usize) {
+ *slot = c;
+ }
+ }
+ let mut edge_new = vec![None; edges.len()];
+ for (c, &e) in part.edges.iter().enumerate() {
+ if let Some(slot) = edge_new.get_mut(e as usize) {
+ *slot = Some(c);
+ }
+ }
+ Ok(Assembled { brep: part.brep, faces: face_new, edges: edge_new })
}
/// A face's parameter box: around its pcurves' control points (a B-spline lies in their hull),
diff --git a/crates/kernel/src/spline_conv.rs b/crates/kernel/src/spline_conv.rs
index e034d9f269..75bf524559 100644
--- a/crates/kernel/src/spline_conv.rs
+++ b/crates/kernel/src/spline_conv.rs
@@ -1,18 +1,10 @@
//! geom's B-spline curves and surfaces as schema 4 records, weights kept (homogeneous control
-//! points carried over as they are), and a whole tensor surface as a one-face sheet.
+//! points carried over as they are). A whole tensor surface as a one-face sheet is
+//! `surfaces::tensor_sheet`.
-use solidcraft_geom::Vec3;
-use solidcraft_geom::brep::{Curve, Curve2, Surface};
+use solidcraft_geom::brep::{Curve, Surface};
use solidcraft_geom::bspline::{HPoint, NurbsSurface};
-use crate::assemble::{AFace, AUse, Assembly};
-use crate::body::Body;
-use crate::{KernelError, Result};
-
-fn fail(m: impl std::fmt::Display) -> KernelError {
- KernelError::Failed(format!("spline geometry: {m}"))
-}
-
/// A homogeneous control point as the record's `[x*w, y*w, z*w, w]`.
pub(crate) fn h4(h: HPoint) -> [f64; 4] {
[h.x, h.y, h.z, h.w]
@@ -34,48 +26,3 @@ pub(crate) fn surface(s: &NurbsSurface) -> Surface {
let [ku, kv] = s.knots();
nurbs_surface(ku, kv, (0..nu).map(|i| (0..nv).map(|j| h4(s.point(i, j))).collect()).collect())
}
-
-/// The active interval `[knots[degree], knots[count]]` of a direction with `count` points.
-fn active(knots: &[f64], count: usize) -> Result<[f64; 2]> {
- let degree = knots.len().checked_sub(count + 1).ok_or_else(|| fail("knots and points disagree"))?;
- match (knots.get(degree), knots.get(count)) {
- (Some(a), Some(b)) if a < b => Ok([*a, *b]),
- _ => Err(fail("an empty knot interval")),
- }
-}
-
-/// One face of a whole tensor surface as a sheet: its four boundary curves (the net's first and
-/// last rows and columns) as edges sharing the corners, each with its exact pcurve (an edge's
-/// parameter is the surface's along it). This is what a patch built as a single control net
-/// (`bridge`, `boundary`, the T-spline sheet) hands to the B-rep.
-pub(crate) fn tensor_face(s: &NurbsSurface) -> Result {
- let [nu, nv] = s.count();
- if nu < 2 || nv < 2 {
- return Err(fail("a face needs at least 2 x 2 control points"));
- }
- let [ku, kv] = s.knots();
- let ([u0, u1], [v0, v1]) = (active(ku, nu)?, active(kv, nv)?);
- let corners = [s.point(0, 0), s.point(nu - 1, 0), s.point(nu - 1, nv - 1), s.point(0, nv - 1)];
- let corners: Vec = corners.iter().map(|c| c.project().ok_or_else(|| fail("a corner has no position"))).collect::>()?;
- let diag = corners.iter().flat_map(|a| corners.iter().map(move |b| a.dist(*b))).fold(0.0, f64::max);
- let mut asm = Assembly::sheet((diag * 1e-7).max(1e-9));
- let v: Vec = corners.iter().map(|p| asm.vertex(*p)).collect::>()?;
- let (Some(&a), Some(&b), Some(&c), Some(&d)) = (v.first(), v.get(1), v.get(2), v.get(3)) else { return Err(fail("corners")) };
- let row = |j: usize| -> Vec { (0..nu).map(|i| s.point(i, j)).collect() };
- let column = |i: usize| -> Vec { (0..nv).map(|j| s.point(i, j)).collect() };
- let line = |t: [f64; 2], p: [f64; 2], q: [f64; 2]| Curve2::Spline { knots: vec![t[0], t[0], t[1], t[1]], points: vec![p, q] };
- // Bottom (v0) and top (v1) run along u, right (u1) and left (u0) along v, each from its
- // lower corner; the loop runs counter-clockwise in (u, v).
- let bottom = asm.edge(nurbs_curve(ku, &row(0)), [u0, u1], a, b)?;
- let right = asm.edge(nurbs_curve(kv, &column(nu - 1)), [v0, v1], b, c)?;
- let top = asm.edge(nurbs_curve(ku, &row(nv - 1)), [u0, u1], d, c)?;
- let left = asm.edge(nurbs_curve(kv, &column(0)), [v0, v1], a, d)?;
- let loop_ = vec![
- AUse { edge: bottom, forward: true, given: Some(line([u0, u1], [u0, v0], [u1, v0])) },
- AUse { edge: right, forward: true, given: Some(line([v0, v1], [u1, v0], [u1, v1])) },
- AUse { edge: top, forward: false, given: Some(line([u0, u1], [u0, v1], [u1, v1])) },
- AUse { edge: left, forward: false, given: Some(line([v0, v1], [u0, v0], [u0, v1])) },
- ];
- asm.face(AFace { surface: surface(s), natural_out: true, loops: vec![loop_], shell: 0, domain: Some([[u0, u1], [v0, v1]]) });
- Body::from_record(asm.finish()?.record)
-}
diff --git a/crates/kernel/src/surfaces.rs b/crates/kernel/src/surfaces.rs
index 315a6b04db..54daeb8c1a 100644
--- a/crates/kernel/src/surfaces.rs
+++ b/crates/kernel/src/surfaces.rs
@@ -194,88 +194,18 @@ impl Parts {
return Err(invalid("no faces"));
}
let shells = self.shells()?;
- let order: Vec = shells.iter().flatten().copied().collect();
- let face = |fi: usize| self.faces.get(fi).ok_or_else(|| invalid("a face reference"));
- let mut edge_new: Vec> = vec![None; self.edges.len()];
- let mut edges_old: Vec = Vec::new();
- let mut uses: Vec> = Vec::new();
- for (nf, &fi) in order.iter().enumerate() {
- let (f, forward) = face(fi)?;
- for (li, l) in f.loops.iter().enumerate() {
- let b::Loop::Edges { edges } = l else { continue };
- let k = edges.len();
- for step in 0..k {
- let p = if *forward { step } else { k - 1 - step };
- let Some(u) = edges.get(p) else { continue };
- let slot = edge_new.get_mut(u.edge as usize).ok_or_else(|| invalid("an edge reference"))?;
- let ne = match *slot {
- Some(ne) => ne,
- None => {
- let ne = index(edges_old.len())?;
- *slot = Some(ne);
- edges_old.push(u.edge as usize);
- uses.push(Vec::new());
- ne
- }
- };
- let at = b::EdgeUseRef { face: index(nf)?, r#loop: index(li)?, position: index(p)?, upper: !u.forward };
- if let Some(list) = uses.get_mut(ne as usize) {
- list.push(at);
- }
- }
- }
- }
- let mut vertex_new: Vec> = vec![None; self.vertices.len()];
- let mut vertices: Vec = Vec::new();
- let mut visit = |v: u32, vertices: &mut Vec| -> Result {
- let slot = vertex_new.get_mut(v as usize).ok_or_else(|| invalid("a vertex reference"))?;
- if let Some(nv) = *slot {
- return Ok(nv);
- }
- let nv = index(vertices.len())?;
- *slot = Some(nv);
- vertices.push(self.vertices.get(v as usize).cloned().ok_or_else(|| invalid("a vertex reference"))?);
- Ok(nv)
- };
- let mut edges = Vec::with_capacity(edges_old.len());
- for (old, uses) in edges_old.iter().zip(uses) {
- let e = self.edges.get(*old).ok_or_else(|| invalid("an edge reference"))?;
- let ends = [visit(e.vertices[0], &mut vertices)?, visit(e.vertices[1], &mut vertices)?];
- edges.push(b::Edge { vertices: ends, uses, ..e.clone() });
- }
- let mut faces = Vec::with_capacity(order.len());
- for &fi in &order {
- let (f, _) = face(fi)?;
- let mut loops = Vec::with_capacity(f.loops.len());
- for l in &f.loops {
- loops.push(match l {
- b::Loop::Edges { edges } => b::Loop::Edges {
- edges: edges
- .iter()
- .map(|u| {
- let edge = edge_new.get(u.edge as usize).copied().flatten().ok_or_else(|| invalid("an edge reference"))?;
- Ok(b::OrientedEdge { edge, ..u.clone() })
- })
- .collect::>()?,
- },
- b::Loop::Vertex { vertex } => b::Loop::Vertex { vertex: visit(*vertex, &mut vertices)? },
- });
- }
- faces.push(b::Face { loops, ..f.clone() });
- }
- let mut numbered = 0usize;
- let mut shell_records = Vec::with_capacity(shells.len());
+ // The record as the parts stand (shell k as `shells` groups it), then in canonical order.
let mut regions = vec![b::Region { shells: Vec::new(), void: false }];
+ let mut shell_records = Vec::with_capacity(shells.len());
for (s, members) in shells.iter().enumerate() {
let mut count: std::collections::HashMap = std::collections::HashMap::new();
let mut list = Vec::with_capacity(members.len());
for &fi in members {
- let (f, forward) = face(fi)?;
+ let (f, forward) = self.faces.get(fi).ok_or_else(|| invalid("a face reference"))?;
for e in Parts::uses_of(f) {
*count.entry(e).or_default() += 1;
}
- list.push(b::OrientedFace { face: index(numbered)?, forward: *forward });
- numbered += 1;
+ list.push(b::OrientedFace { face: index(fi)?, forward: *forward });
}
shell_records.push(b::Shell::Faces { faces: list, wires: Vec::new() });
let s = index(s)?;
@@ -286,9 +216,21 @@ impl Parts {
regions.push(b::Region { shells: vec![s], void: false });
}
}
- let brep = Brep { version: b::VERSION, tolerance: self.tolerance, regions, shells: shell_records, faces, edges, vertices };
- brep.validate().map_err(|e| failed(format!("faces into a record: {e}")))?;
- Ok(Canonical { brep, faces: order, edges: edges_old })
+ let loose = Brep {
+ version: b::VERSION,
+ tolerance: self.tolerance,
+ regions,
+ shells: shell_records,
+ faces: self.faces.iter().map(|(f, _)| f.clone()).collect(),
+ edges: self.edges.iter().map(|e| b::Edge { uses: Vec::new(), ..e.clone() }).collect(),
+ vertices: self.vertices.clone(),
+ };
+ let part = loose.canonical().map_err(|e| failed(format!("faces into a record: {e}")))?;
+ Ok(Canonical {
+ brep: part.brep,
+ faces: part.faces.iter().map(|f| *f as usize).collect(),
+ edges: part.edges.iter().map(|e| *e as usize).collect(),
+ })
}
}