diff --git a/crates/kernel/src/loft_blend.rs b/crates/kernel/src/loft_blend.rs index 2364b200aa..7b0f980796 100644 --- a/crates/kernel/src/loft_blend.rs +++ b/crates/kernel/src/loft_blend.rs @@ -41,14 +41,12 @@ //! each end span and leaves every joint's conditions alone. use solidcraft_geom::Vec3; +use solidcraft_geom::brep::eval::SurfaceEval; use solidcraft_geom::bspline::{CurveN, HPoint, MAX_SPLINE_DEGREE, NurbsSurface}; -use truck_modeling as mt; -use truck_modeling::{ParametricSurface3D, SearchNearestParameter}; -use crate::body::{Body, from_p3, p3}; +use crate::body::Body; use crate::bridge::{Continuity, Frame}; -use crate::surfaces::surface_body; -use crate::{KernelError, Result, guard}; +use crate::{KernelError, Result}; /// Most cross-sections one blend may run through. pub const MAX_BLEND_SECTIONS: usize = 50; @@ -81,7 +79,7 @@ const ROWS_PER_SOLVE: usize = 16; pub fn loft_blend(sections: &[Vec], orders: [Continuity; 2], crown: [f64; 2], samples: usize) -> Result { let blend = Blend::new(sections, orders, crown, samples)?; let net = blend.net()?; - guard("loft blend surface", || crate::spline_conv::tensor_face(&net).map(|f| surface_body(vec![f]))) + crate::surfaces::tensor_sheet(&net) } /// The frames of one cross-section picked as an edge chain of a body, sampled at `stations` @@ -94,34 +92,36 @@ pub fn loft_blend(sections: &[Vec], orders: [Continuity; 2], crown: [f64; /// tangent plane (a direction normal to it has no tangent plane to leave in). The last section's /// `leave` runs back into the blend, towards the section before it. pub fn section_frames(body: &Body, edges: &[Vec3], leave: Vec3, stations: usize) -> Result> { - guard("loft blend section", || { - let chain = crate::bridge::chain(body, edges)?; - let leave = - leave.normalized().filter(|l| l.is_finite()).ok_or_else(|| KernelError::Invalid("the leave direction must be a finite vector".into()))?; - let first = chain.first().ok_or_else(|| KernelError::Failed("loft blend: no chain".into()))?; - let mid = (from_p3(first.front().point()) + from_p3(first.back().point())) * 0.5; - // The face the chain lies on that the blend leaves through: the one whose tangent plane - // holds as much of `leave` as possible. - let mut best: Option<(f64, mt::Face)> = None; - for face in body.solid.face_iter() { - if !chain.iter().all(|e| face.edge_iter().any(|f| f.id() == e.id())) { - continue; - } - let surf = face.surface(); - let Some((u, v)) = surf.search_nearest_parameter(p3(mid), None, 100) else { continue }; - let normal = surf.normal(u, v); - let n = Vec3::new(normal.x, normal.y, normal.z).normalized().unwrap_or(Vec3::ZERO); - let score = (leave - n * leave.dot(n)).len(); - if best.as_ref().is_none_or(|(b, _)| score > *b) { - best = Some((score, face.clone())); - } + let chain = crate::bridge::chain(body, edges)?; + let leave = + leave.normalized().filter(|l| l.is_finite()).ok_or_else(|| KernelError::Invalid("the leave direction must be a finite vector".into()))?; + let r = body.record(); + let &(first, _) = chain.first().ok_or_else(|| KernelError::Failed("loft blend: no chain".into()))?; + let ends = r.edges.get(first).map(|e| e.vertices).ok_or_else(|| KernelError::Failed("loft blend: an edge reference".into()))?; + let point = |v: u32| r.vertices.get(v as usize).map(|x| Vec3::new(x.point[0], x.point[1], x.point[2])); + let (Some(a), Some(b)) = (point(ends[0]), point(ends[1])) else { return Err(KernelError::Failed("loft blend: a vertex reference".into())) }; + let mid = (a + b) * 0.5; + // The face the chain lies on that the blend leaves through: the one whose tangent plane holds + // as much of `leave` as possible. + let bounds = |f: usize, e: usize| r.edges.get(e).is_some_and(|x| x.uses.iter().any(|u| u.face as usize == f)); + let mut best: Option<(f64, usize)> = None; + for (f, face) in r.faces.iter().enumerate() { + if !chain.iter().all(|(e, _)| bounds(f, *e)) { + continue; } - let (score, face) = best.ok_or_else(|| KernelError::Invalid("no face of the body holds the picked edges".into()))?; - if score < 0.2 { - return Err(KernelError::Invalid("the direction towards the next section lies in no face at the picked edges".into())); + let Ok(surf) = SurfaceEval::new(&face.surface) else { continue }; + let Some((_, su, sv)) = surf.project(mid, None, f64::INFINITY).and_then(|uv| surf.derivs(uv[0], uv[1])) else { continue }; + let n = su.cross(sv).normalized().unwrap_or(Vec3::ZERO); + let score = (leave - n * leave.dot(n)).len(); + if best.is_none_or(|(b, _)| score > b) { + best = Some((score, f)); } - crate::bridge::frames(&face, &chain, leave, stations) - }) + } + let (score, face) = best.ok_or_else(|| KernelError::Invalid("no face of the body holds the picked edges".into()))?; + if score < 0.2 { + return Err(KernelError::Invalid("the direction towards the next section lies in no face at the picked edges".into())); + } + crate::bridge::frames(body, face, &chain, leave, stations) } /// What the face has to do at one section: where it is, and the run direction's first, second and