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(), + }) } }