diff --git a/crates/geom/src/brep/form.rs b/crates/geom/src/brep/form.rs index d1ca7a96a4..f25809952a 100644 --- a/crates/geom/src/brep/form.rs +++ b/crates/geom/src/brep/form.rs @@ -61,6 +61,13 @@ fn quarters([a0, a1]: [f64; 2]) -> Option<(f64, usize)> { (n <= 5).then_some((q0, n)) } +/// The form parameters of an angle interval on the quarters `quarters` chose for it (its ends kept +/// on them: an end within rounding past a quarter's end stays in that quarter). +fn params([a0, a1]: [f64; 2], (q0, n): (f64, usize)) -> [f64; 2] { + let end = q0 + n as f64; + [param(a0).clamp(q0, end), param(a1).clamp(q0, end)] +} + /// Quarter arcs from quarter `q0`, `n` of them: their knot vector (in form parameter) and their /// control points (as `(cos, sin)` factors on the unit circle's plane) with weights. fn arcs(q0: f64, n: usize) -> (Vec, Vec<([f64; 2], f64)>) { @@ -111,19 +118,19 @@ pub fn curve(c: &Curve, [t0, t1]: [f64; 2]) -> Option<(Curve, [f64; 2])> { fn conic(frame: &Frame, [rx, ry]: [f64; 2], t: [f64; 2]) -> Option<(Curve, [f64; 2])> { let (o, x, y, _) = axes(frame)?; - let (q0, n) = quarters(t)?; - let (knots, pts) = arcs(q0, n); + let grid = quarters(t)?; + let (knots, pts) = arcs(grid.0, grid.1); let homogeneous = pts.iter().map(|([c, s], w)| hom(o + x * (rx * c) + y * (ry * s), *w)).collect(); - Some((Curve::RationalSpline { knots, homogeneous }, [param(t[0]), param(t[1])])) + Some((Curve::RationalSpline { knots, homogeneous }, params(t, grid))) } /// The NURBS form of a cylinder, cone, sphere or torus face over `domain` (in the label's /// parameters: angles, and the height along the axis of a cylinder or cone) and the domain in the /// form's parameters; `None` for another kind, or a domain of more than one turn. pub fn surface(s: &Surface, [u, v]: [[f64; 2]; 2]) -> Option<(Surface, [[f64; 2]; 2])> { - let (q0, n) = quarters(u)?; - let (u_knots, ring) = arcs(q0, n); - let fu = [param(u[0]), param(u[1])]; + let grid = quarters(u)?; + let (u_knots, ring) = arcs(grid.0, grid.1); + let fu = params(u, grid); // The ring of arcs times a profile `(control, weight)` mapped to (radial, axial) about the // frame: a rational tensor product, its weights the products. let net = |f: &Frame, profile: &[([f64; 2], f64)], at: &dyn Fn([f64; 2]) -> (f64, f64)| -> Option>> { @@ -155,16 +162,16 @@ pub fn surface(s: &Surface, [u, v]: [[f64; 2]; 2]) -> Option<(Surface, [[f64; 2] Some((Surface::RationalSpline { u_knots, v_knots: vec![v[0], v[0], v[1], v[1]], homogeneous }, [fu, v])) } Surface::Sphere { frame, radius } => { - let (p0, m) = quarters(v)?; - let (v_knots, meridian) = arcs(p0, m); + let vgrid = quarters(v)?; + let (v_knots, meridian) = arcs(vgrid.0, vgrid.1); let homogeneous = net(frame, &meridian, &|[c, s]| (radius * c, radius * s))?; - Some((Surface::RationalSpline { u_knots, v_knots, homogeneous }, [fu, [param(v[0]), param(v[1])]])) + Some((Surface::RationalSpline { u_knots, v_knots, homogeneous }, [fu, params(v, vgrid)])) } Surface::Torus { frame, major_radius, minor_radius } => { - let (p0, m) = quarters(v)?; - let (v_knots, tube) = arcs(p0, m); + let vgrid = quarters(v)?; + let (v_knots, tube) = arcs(vgrid.0, vgrid.1); let homogeneous = net(frame, &tube, &|[c, s]| (major_radius + minor_radius * c, minor_radius * s))?; - Some((Surface::RationalSpline { u_knots, v_knots, homogeneous }, [fu, [param(v[0]), param(v[1])]])) + Some((Surface::RationalSpline { u_knots, v_knots, homogeneous }, [fu, params(v, vgrid)])) } _ => None, } diff --git a/crates/geom/src/brep/pcurve.rs b/crates/geom/src/brep/pcurve.rs index 4ecd8b8444..ba7cead192 100644 --- a/crates/geom/src/brep/pcurve.rs +++ b/crates/geom/src/brep/pcurve.rs @@ -199,6 +199,17 @@ impl On { Ok(On { surface, starts: uv::Starts::default(), dom, face: None }) } + fn surface_kind(&self) -> &'static str { + match &self.surface { + SurfaceEval::Eval(crate::tess::EvalSurface::Plane { .. }, _) => "plane", + SurfaceEval::Cylinder { .. } => "cylinder", + SurfaceEval::Cone { .. } => "cone", + SurfaceEval::Sphere { .. } => "sphere", + SurfaceEval::Torus { .. } => "torus", + _ => "other", + } + } + fn within(self, face: Option<[[f64; 2]; 2]>) -> Self { let face = face.filter(|f| f.iter().flatten().all(|x| x.is_finite())); On { face, ..self } @@ -210,7 +221,8 @@ impl On { { return Ok(pc); } - self.fit(record, curve, range, tol) + let _ = tol; + Err(Error::Failed(format!("no exact pcurve: {:?} on {}", std::mem::discriminant(record), self.surface_kind()))) } /// The closed-form pcurve where there is one (unchecked). diff --git a/crates/kernel/src/sew.rs b/crates/kernel/src/sew.rs index 80521221c8..9ec71250fe 100644 --- a/crates/kernel/src/sew.rs +++ b/crates/kernel/src/sew.rs @@ -1141,21 +1141,47 @@ pub(crate) fn assemble(parts: Parts, solid: bool) -> Result { .collect() }) .collect(); + // Every circle and ellipse edge on its NURBS form (an edge the caller gave a pcurve for keeps + // the parameter that pcurve is on). + let given_on: Vec = { + let mut g = vec![false; edges.len()]; + for u in faces.iter().flat_map(|f| f.loops.iter().flatten()).filter(|u| u.given.is_some()) { + if let Some(slot) = g.get_mut(u.edge as usize) { + *slot = true; + } + } + g + }; + let forms: Vec> = edges + .iter() + .zip(&given_on) + .map(|(e, given)| match e.curve { + b::Curve::Circle { .. } | b::Curve::Ellipse { .. } if !given => { + b::form::curve(&e.curve, e.interval).ok_or_else(|| fail("an arc of more than one turn".into())).map(Some) + } + _ => Ok(None), + }) + .collect::>()?; + // Each face on its NURBS form: a cylinder, cone, sphere or torus over the extent its label's + // pcurves give, then every pcurve again on the forms, exactly. let mut pcs: Vec>> = Vec::with_capacity(nf); let mut domains = Vec::with_capacity(nf); + let mut face_forms: Vec> = Vec::with_capacity(nf); for (fi, (f, ls)) in faces.iter().zip(&loops).enumerate() { - let mut uses = Vec::with_capacity(ls.len()); - for l in ls { - let mut u = Vec::with_capacity(l.len()); - for &(e, forward, given) in l { - let edge = edges.get(e as usize).ok_or_else(|| fail("edge reference".into()))?; - u.push(pcurve::EdgeUse { edge: e, curve: &edge.curve, interval: edge.interval, forward, tolerance: tol, given }); + let failed = |e: pcurve::Error| KernelError::Failed(format!("sew: face {fi}: {e}")); + let form = match f.surface { + b::Surface::Cylinder { .. } | b::Surface::Cone { .. } | b::Surface::Sphere { .. } | b::Surface::Torus { .. } => { + let label = pcurve::pcurves(&f.surface, &uses_of(&edges, &forms, ls, false, tol)?, tol).map_err(failed)?; + let extent = domain_of(&f.surface, &label).ok_or_else(|| fail(format!("face {fi} has no area in its parameters")))?; + Some(b::form::surface(&f.surface, extent).ok_or_else(|| fail(format!("face {fi}: more than one turn")))?.0) } - uses.push(u); - } - let pc = pcurve::pcurves(&f.surface, &uses, tol).map_err(|e| KernelError::Failed(format!("sew: face {fi}: {e}")))?; - domains.push(domain_of(&f.surface, &pc).ok_or_else(|| fail(format!("face {fi} has no area in its parameters")))?); + _ => None, + }; + let on = form.as_ref().unwrap_or(&f.surface); + let pc = pcurve::pcurves(on, &uses_of(&edges, &forms, ls, true, tol)?, tol).map_err(failed)?; + domains.push(domain_of(on, &pc).ok_or_else(|| fail(format!("face {fi} has no area in its parameters")))?); pcs.push(pc); + face_forms.push(form); } let users = edge_users(edges.len(), &faces)?; let (groups, _) = components(nf, &users); @@ -1178,7 +1204,8 @@ pub(crate) fn assemble(parts: Parts, solid: bool) -> Result { let mut sum = 0.0; for &f in g { let (Some(face), Some(pc), Some(ls), Some(dom)) = (faces.get(f), pcs.get(f), loops.get(f), domains.get(f)) else { continue }; - let s = SurfaceEval::new(&face.surface).map_err(|e| KernelError::Failed(format!("sew: face {f}: {e}")))?; + let on = face_forms.get(f).and_then(Option::as_ref).unwrap_or(&face.surface); + let s = SurfaceEval::new(on).map_err(|e| KernelError::Failed(format!("sew: face {f}: {e}")))?; let uses: Vec> = pc.iter().zip(ls).map(|(c, l)| c.iter().zip(l).map(|(c, u)| (c, u.1)).collect()).collect(); let flux = face_flux(&s, dom[1][0], &uses, field).ok_or_else(|| KernelError::Failed(format!("sew: face {f} cannot be evaluated")))?; sum += if face.forward { flux } else { -flux }; @@ -1268,11 +1295,19 @@ pub(crate) fn assemble(parts: Parts, solid: bool) -> Result { if record_loops.is_empty() { return Err(fail(format!("face {f} has no loop"))); } - record_faces.push(b::Face { nurbs: None, surface: face.surface.clone(), domain: *domain, loops: record_loops }); + let nurbs = face_forms.get(f).cloned().flatten(); + record_faces.push(b::Face { nurbs, surface: face.surface.clone(), domain: *domain, loops: record_loops }); } let record_edges = edges .iter() - .map(|e| b::Edge { nurbs: None, curve: e.curve.clone(), interval: e.interval, vertices: e.vertices, tolerance: tol, uses: Vec::new() }) + .zip(&forms) + .map(|(e, form)| { + let (nurbs, interval) = match form { + Some((c, i)) => (Some(c.clone()), *i), + None => (None, e.interval), + }; + b::Edge { nurbs, curve: e.curve.clone(), 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 }; @@ -1295,6 +1330,31 @@ pub(crate) fn assemble(parts: Parts, solid: bool) -> Result { /// A face's parameter box: around its pcurves' control points (a B-spline lies in their hull), /// held inside the surface's natural domain (one turn where periodic). None when it has no /// width in either direction. +/// A face's edge uses for `pcurve::pcurves`: on each edge's NURBS form where it has one and +/// `formed`, else on its curve as given. +fn uses_of<'a>( + edges: &'a [PartEdge], + forms: &'a [Option<(b::Curve, [f64; 2])>], + ls: &[Vec<(u32, bool, Option<&'a Curve2>)>], + formed: bool, + tol: f64, +) -> Result>>> { + ls.iter() + .map(|l| { + l.iter() + .map(|&(e, forward, given)| { + let edge = edges.get(e as usize).ok_or_else(|| KernelError::Invalid("sew: edge reference".into()))?; + let (curve, interval) = match forms.get(e as usize).and_then(Option::as_ref) { + Some((c, i)) if formed => (c, *i), + _ => (&edge.curve, edge.interval), + }; + Ok(pcurve::EdgeUse { edge: e, curve, interval, forward, tolerance: tol, given }) + }) + .collect() + }) + .collect() +} + fn domain_of(surface: &b::Surface, pcs: &[Vec]) -> Option<[[f64; 2]; 2]> { let (mut lo, mut hi) = ([f64::INFINITY; 2], [f64::NEG_INFINITY; 2]); for c in pcs.iter().flatten() {