mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 18:45:55 +00:00
sew: faces and edges on their NURBS forms, their pcurves exact
Every circle and ellipse edge sew builds now carries its NURBS form, its interval in the form's parameter; every cylinder, cone, sphere and torus face its form over the extent its label's pcurves give; and every pcurve is computed on the forms, so a bend's rim circle is a straight line on the bend and an affine image on the flange, exactly. The volume test evaluates the faces on their forms. A form interval's end within rounding past its last quarter stays on it. With the pcurve fit turned off, 29 of the 44 sew cases that needed it pass (from none); with it on, all 44 pass as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BnQawySUFx57iaJZq7z1Fm
This commit is contained in:
@@ -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<f64>, 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<Vec<Vec<[f64; 4]>>> {
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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).
|
||||
|
||||
+73
-13
@@ -1141,21 +1141,47 @@ pub(crate) fn assemble(parts: Parts, solid: bool) -> Result<Assembled> {
|
||||
.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<bool> = {
|
||||
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<Option<(b::Curve, [f64; 2])>> = 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::<Result<_>>()?;
|
||||
// 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<Vec<Curve2>>> = Vec::with_capacity(nf);
|
||||
let mut domains = Vec::with_capacity(nf);
|
||||
let mut face_forms: Vec<Option<b::Surface>> = 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<Assembled> {
|
||||
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<Vec<(&Curve2, bool)>> = 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<Assembled> {
|
||||
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<Assembled> {
|
||||
/// 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<Vec<Vec<pcurve::EdgeUse<'a>>>> {
|
||||
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<Curve2>]) -> Option<[[f64; 2]; 2]> {
|
||||
let (mut lo, mut hi) = ([f64::INFINITY; 2], [f64::NEG_INFINITY; 2]);
|
||||
for c in pcs.iter().flatten() {
|
||||
|
||||
Reference in New Issue
Block a user