mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 02:25:55 +00:00
brep: a face on a cylinder, cone, sphere or torus carries the NURBS form SMLib holds it as
Every SMLib analytic surface is a B-spline surface, and its trims are in that NURBS's parameters. Face::nurbs keeps that NURBS: the face's domain and pcurves are its (u, v), and `surface` says what the face is. Face::parameters is the surface every (u, v) evaluation uses. The SMLib conversion reads and writes it (fork FACE_NURBS_FORM, SMLIB_REV c4483f6); the STEP writer writes the analytic surface and the face's edges without pcurves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017itGh5fpMEousnZ91cYcVt
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
//! A face on a cylinder, cone, sphere or torus may save `nurbs`: the NURBS form it is
|
||||
//! parameterised on, as SMLib holds it. Its domain and pcurves are then that NURBS's (u, v), and
|
||||
//! `surface` only says what the face is. An older design has none and keeps the analytic (u, v).
|
||||
//! The last release refuses a design with this change (the newer format number).
|
||||
+26
-1
@@ -84,7 +84,16 @@ pub struct OrientedFace {
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Face {
|
||||
/// What the face is: its surface as the producer names it (a cylinder, a NURBS…).
|
||||
pub surface: Surface,
|
||||
/// The NURBS form the face is parameterised on, when it differs from `surface`'s own
|
||||
/// parameters: a cylinder, cone, sphere or torus as SMLib holds it (every SMLib analytic
|
||||
/// surface is a B-spline surface, its trims in that NURBS's parameters). When given, `domain`
|
||||
/// and every pcurve are in this surface's (u, v), and `surface` only says what it is; it is a
|
||||
/// `Spline` or `RationalSpline` and `surface` is a cylinder, cone, sphere or torus
|
||||
/// (docs/native-brep.md, "Surfaces").
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub nurbs: Option<Surface>,
|
||||
/// The modelled rectangle in surface parameters, `[[u0, u1], [v0, v1]]`, inside the
|
||||
/// surface's natural domain and around every loop.
|
||||
pub domain: [[f64; 2]; 2],
|
||||
@@ -469,6 +478,14 @@ fn surface(s: &Surface, depth: usize, budget: &mut Budget) -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Face {
|
||||
/// The surface the face's (u, v) are on: its NURBS form when it has one, else its surface.
|
||||
/// Everything that evaluates the face's domain or pcurves goes through this.
|
||||
pub fn parameters(&self) -> &Surface {
|
||||
self.nurbs.as_ref().unwrap_or(&self.surface)
|
||||
}
|
||||
}
|
||||
|
||||
impl Brep {
|
||||
/// Validate portable structure without calling a kernel or evaluating geometry: resource
|
||||
/// budgets, geometry records, parameter ranges, references, loops, regions, every edge's
|
||||
@@ -495,7 +512,15 @@ impl Brep {
|
||||
}
|
||||
for f in &self.faces {
|
||||
surface(&f.surface, 0, &mut budget)?;
|
||||
let [u, v] = surface_natural(&f.surface);
|
||||
if let Some(n) = &f.nurbs {
|
||||
surface(n, 0, &mut budget)?;
|
||||
require(matches!(n, Surface::Spline { .. } | Surface::RationalSpline { .. }), "NURBS form is not a NURBS")?;
|
||||
require(
|
||||
matches!(f.surface, Surface::Cylinder { .. } | Surface::Cone { .. } | Surface::Sphere { .. } | Surface::Torus { .. }),
|
||||
"NURBS form on a surface that is not a cylinder, cone, sphere or torus",
|
||||
)?;
|
||||
}
|
||||
let [u, v] = surface_natural(f.parameters());
|
||||
interval(&f.domain[0], u)?;
|
||||
interval(&f.domain[1], v)?;
|
||||
budget.add(f.loops.len())?;
|
||||
|
||||
@@ -122,7 +122,7 @@ impl Brep {
|
||||
}
|
||||
for (fi, f) in self.faces.iter().enumerate() {
|
||||
let face = index(fi);
|
||||
let surface = SurfaceEval::new(&f.surface).map_err(|err| unevaluable(Record::Face(face), err))?;
|
||||
let surface = SurfaceEval::new(f.parameters()).map_err(|err| unevaluable(Record::Face(face), err))?;
|
||||
let period = surface.domain().period;
|
||||
// Each edge loop's signed (u, v) area.
|
||||
let mut areas: Vec<(u32, f64)> = Vec::with_capacity(f.loops.len());
|
||||
|
||||
@@ -139,6 +139,7 @@ impl Brep {
|
||||
let mut out = self.clone();
|
||||
for f in &mut out.faces {
|
||||
f.surface = motion.surface(&f.surface);
|
||||
f.nurbs = f.nurbs.as_ref().map(|n| motion.surface(n));
|
||||
}
|
||||
for e in &mut out.edges {
|
||||
e.curve = motion.curve(&e.curve);
|
||||
|
||||
@@ -101,7 +101,7 @@ fn faceted(points: &[[f64; 3]], shells: &[&[&[usize]]], regions: Vec<Region>) ->
|
||||
let [o, p, q] = [0, 1, 2].map(|k| points[cycle[k]]);
|
||||
let axis = |a: [f64; 3]| [a[0] - o[0], a[1] - o[1], a[2] - o[2]];
|
||||
let surface = Surface::Plane { origin: o, u_axis: axis(p), v_axis: axis(q) };
|
||||
b.faces.push(Face { surface, domain: [[-10., 10.], [-10., 10.]], loops: vec![Loop::Edges { edges: loop_ }] });
|
||||
b.faces.push(Face { nurbs: None, surface, domain: [[-10., 10.], [-10., 10.]], loops: vec![Loop::Edges { edges: loop_ }] });
|
||||
}
|
||||
b.shells.push(Shell::Faces { faces: uses, wires: vec![] });
|
||||
}
|
||||
@@ -145,6 +145,7 @@ fn tube() -> Brep {
|
||||
regions: vec![infinite(vec![0])],
|
||||
shells: vec![Shell::Faces { faces: vec![OrientedFace { face: 0, forward: true }], wires: vec![] }],
|
||||
faces: vec![Face {
|
||||
nurbs: None,
|
||||
surface: Surface::Cylinder { frame: f.clone(), radius: 2. },
|
||||
domain: [[0., TAU], [0., 5.]],
|
||||
loops: vec![Loop::Edges {
|
||||
@@ -176,6 +177,7 @@ fn one_face(surface: Surface, domain: [[f64; 2]; 2], edges: Vec<(Curve, [f64; 2]
|
||||
regions: vec![infinite(vec![0])],
|
||||
shells: vec![Shell::Faces { faces: vec![OrientedFace { face: 0, forward: true }], wires: vec![] }],
|
||||
faces: vec![Face {
|
||||
nurbs: None,
|
||||
surface,
|
||||
domain,
|
||||
loops: vec![Loop::Edges { edges: uses.iter().map(|&(edge, forward)| OrientedEdge { edge, forward, pcurve: segment(0., 1.) }).collect() }],
|
||||
|
||||
@@ -278,7 +278,7 @@ impl Assembly {
|
||||
edges: l.iter().zip(pc).map(|(u, pcurve)| OrientedEdge { edge: u.edge, forward: u.forward, pcurve: pcurve.clone() }).collect(),
|
||||
})
|
||||
.collect();
|
||||
Ok(Face { surface: f.surface.clone(), domain, loops: stored })
|
||||
Ok(Face { nurbs: None, surface: f.surface.clone(), domain, loops: stored })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -337,7 +337,7 @@ impl EdgeFaces {
|
||||
let in_loop = in_loop.ok_or_else(|| KernelError::Failed("boundary surface: an edge's face".into()))?;
|
||||
let pts = (0..=16).map(|k| along(curve, k as f64 / 16.0)).collect::<Option<Vec<_>>>();
|
||||
let pts = pts.ok_or_else(|| KernelError::Invalid(format!("{label}: an edge can't be evaluated")))?;
|
||||
let surface = SurfaceEval::new(&face.surface).map_err(|err| KernelError::Failed(format!("boundary surface: {err}")))?;
|
||||
let surface = SurfaceEval::new(face.parameters()).map_err(|err| KernelError::Failed(format!("boundary surface: {err}")))?;
|
||||
sides.push(EdgeSide { pts, surface, along: in_loop == forward, hint: None });
|
||||
}
|
||||
Ok(EdgeFaces { sides })
|
||||
@@ -459,6 +459,7 @@ fn face_of(s: &NurbsSurface, corners: &[Vec3]) -> Result<Body> {
|
||||
uses.extend(pieces);
|
||||
}
|
||||
let face = solidcraft_geom::brep::Face {
|
||||
nurbs: None,
|
||||
surface: spline_surface(s),
|
||||
domain: s.domain(),
|
||||
loops: vec![solidcraft_geom::brep::Loop::Edges { edges: uses }],
|
||||
|
||||
@@ -438,7 +438,7 @@ impl Walk {
|
||||
impl Support {
|
||||
fn of(r: &Brep, face: usize, length: f64) -> Result<Support> {
|
||||
let f = r.faces.get(face).ok_or_else(|| KernelError::Invalid("no such face".into()))?;
|
||||
let surf = SurfaceEval::new(&f.surface).map_err(|e| KernelError::Failed(format!("bridge: {e}")))?;
|
||||
let surf = SurfaceEval::new(f.parameters()).map_err(|e| KernelError::Failed(format!("bridge: {e}")))?;
|
||||
let dom = surf.domain();
|
||||
let range = [0, 1].map(|d| match (dom.period[d], dom.range[d]) {
|
||||
(None, Some(r)) => r,
|
||||
@@ -734,7 +734,7 @@ pub fn bridge(a: &[Frame], b: &[Frame], orders: [Continuity; 2], crown: [f64; 2]
|
||||
pub(crate) fn the_face(body: &Body) -> Result<(SurfaceEval, [[f64; 2]; 2])> {
|
||||
let r = body.record();
|
||||
let face = r.faces.first().ok_or_else(|| KernelError::Failed("bridge: no face".into()))?;
|
||||
let surf = SurfaceEval::new(&face.surface).map_err(|e| KernelError::Failed(format!("bridge: {e}")))?;
|
||||
let surf = SurfaceEval::new(face.parameters()).map_err(|e| KernelError::Failed(format!("bridge: {e}")))?;
|
||||
Ok((surf, face.domain))
|
||||
}
|
||||
|
||||
@@ -783,7 +783,7 @@ fn support_faces(r: &Brep, chain: &Chain, leave: Vec3, size: f64) -> Result<Vec<
|
||||
let mut best: Option<(f64, usize)> = None;
|
||||
for u in &edge.uses {
|
||||
let face = u.face as usize;
|
||||
let Some(surf) = r.faces.get(face).and_then(|f| SurfaceEval::new(&f.surface).ok()) else { continue };
|
||||
let Some(surf) = r.faces.get(face).and_then(|f| SurfaceEval::new(f.parameters()).ok()) else { continue };
|
||||
let Some(uv) = surf.project(mid, None, size * 1e-6 + 1e-9) else { continue };
|
||||
let Some(n) = surf.derivs(uv[0], uv[1]).and_then(|(_, su, sv)| su.cross(sv).normalized()) else { continue };
|
||||
let score = (leave - n * leave.dot(n)).len();
|
||||
|
||||
@@ -885,7 +885,7 @@ pub fn planar_face_record(plane: &Plane, r: &Region2) -> Result<solidcraft_geom:
|
||||
tolerance,
|
||||
regions: vec![rec::Region { shells: vec![0], void: false }],
|
||||
shells: vec![rec::Shell::Faces { faces: vec![rec::OrientedFace { face: 0, forward: true }], wires: vec![] }],
|
||||
faces: vec![rec::Face { surface, domain: [[lo[0] - pad, hi[0] + pad], [lo[1] - pad, hi[1] + pad]], loops: face_loops }],
|
||||
faces: vec![rec::Face { nurbs: None, surface, domain: [[lo[0] - pad, hi[0] + pad], [lo[1] - pad, hi[1] + pad]], loops: face_loops }],
|
||||
edges,
|
||||
vertices,
|
||||
};
|
||||
|
||||
@@ -251,7 +251,7 @@ fn measure(body: &Body, edges: &[usize], samples: usize, tolerances: ContinuityT
|
||||
}
|
||||
let face = |i: usize| -> Option<FaceSurface> {
|
||||
let f = record.faces.get(i)?;
|
||||
FaceSurface::new(&f.surface, outward.get(i).copied().unwrap_or(true))
|
||||
FaceSurface::new(f.parameters(), outward.get(i).copied().unwrap_or(true))
|
||||
};
|
||||
let mut seams = Vec::new();
|
||||
let mut unmeasured = 0usize;
|
||||
|
||||
@@ -59,7 +59,7 @@ impl<'a> DistanceShape<'a> {
|
||||
/// `mesh` is `body`'s mesh, within `chord` (mm) of its faces.
|
||||
pub fn new(body: &'a Body, mesh: &'a Mesh, chord: f64) -> DistanceShape<'a> {
|
||||
let record = body.record();
|
||||
let faces: Vec<Option<SurfaceEval>> = record.faces.iter().map(|f| SurfaceEval::new(&f.surface).ok()).collect();
|
||||
let faces: Vec<Option<SurfaceEval>> = record.faces.iter().map(|f| SurfaceEval::new(f.parameters()).ok()).collect();
|
||||
let edges = record.edges.iter().map(|e| CurveEval::new(&e.curve).ok().map(|c| (c, e.interval))).collect();
|
||||
let surfaces = faces.iter().map(|_| OnceLock::new()).collect();
|
||||
let chord = if chord.is_finite() && chord > 0.0 { chord } else { 0.0 };
|
||||
|
||||
@@ -266,8 +266,8 @@ impl Beside {
|
||||
return Err(KernelError::Invalid("an edge of the loop lies between two faces: g1 and g2 need the one face beside it".into()));
|
||||
};
|
||||
let face = record.faces.get(u.face as usize).ok_or_else(|| KernelError::Failed("patch: face reference".into()))?;
|
||||
let surface =
|
||||
SurfaceEval::new(&face.surface).map_err(|err| KernelError::Invalid(format!("the face beside edge {e} cannot be evaluated: {err}")))?;
|
||||
let surface = SurfaceEval::new(face.parameters())
|
||||
.map_err(|err| KernelError::Invalid(format!("the face beside edge {e} cannot be evaluated: {err}")))?;
|
||||
let outward = record
|
||||
.shells
|
||||
.iter()
|
||||
@@ -947,7 +947,7 @@ mod tests {
|
||||
fn height_over(body: &Body, x: f64, y: f64, start: &mut Option<(f64, f64)>) -> Option<f64> {
|
||||
let r = record(body);
|
||||
let face = &r.faces[0];
|
||||
let s = SurfaceEval::new(&face.surface).ok()?;
|
||||
let s = SurfaceEval::new(face.parameters()).ok()?;
|
||||
let [[u0, u1], [v0, v1]] = face.domain;
|
||||
let (mut u, mut v) = start.unwrap_or(((u0 + u1) / 2.0, (v0 + v1) / 2.0));
|
||||
for _ in 0..30 {
|
||||
|
||||
@@ -297,7 +297,7 @@ fn shell(w: &mut W, b: &Brep, si: u32, paint: &HashMap<usize, [f32; 3]>) -> Resu
|
||||
let mut face_data = Vec::with_capacity(faces.len());
|
||||
for of in faces {
|
||||
let face = b.faces.get(of.face as usize).ok_or_else(|| failed(format!("face {}: no such face", of.face)))?;
|
||||
let (s, map) = topology::surface(&face.surface).map_err(|e| failed(format!("face {}: {e}", of.face)))?;
|
||||
let (s, map) = topology::surface(face.parameters()).map_err(|e| failed(format!("face {}: {e}", of.face)))?;
|
||||
let keeps = map.keeps_normal();
|
||||
let outer = topology::outer_loop(b, face);
|
||||
let order: Vec<usize> = outer.into_iter().chain((0..face.loops.len()).filter(|l| Some(*l) != outer)).collect();
|
||||
|
||||
@@ -31,7 +31,7 @@ fn triangle_with_intersection_edge(support_gap: f64) -> b::Brep {
|
||||
pcurve: Curve2::Spline { knots: vec![0.0, 0.0, 1.0, 1.0], points: vec![uv[k], uv[(k + 1) % 3]] },
|
||||
})
|
||||
.collect();
|
||||
let face = b::Face { surface: plane(0.5), domain: [[0.0, 1.0], [0.0, 1.0]], loops: vec![b::Loop::Edges { edges: uses }] };
|
||||
let face = b::Face { nurbs: None, surface: plane(0.5), domain: [[0.0, 1.0], [0.0, 1.0]], loops: vec![b::Loop::Edges { edges: uses }] };
|
||||
let loose = b::Brep {
|
||||
version: b::VERSION,
|
||||
tolerance: 1e-6,
|
||||
|
||||
@@ -109,7 +109,7 @@ pub fn section_frames(body: &Body, edges: &[Vec3], leave: Vec3, stations: usize)
|
||||
if !chain.iter().all(|(e, _)| bounds(f, *e)) {
|
||||
continue;
|
||||
}
|
||||
let Ok(surf) = SurfaceEval::new(&face.surface) else { continue };
|
||||
let Ok(surf) = SurfaceEval::new(face.parameters()) 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();
|
||||
|
||||
@@ -263,7 +263,7 @@ struct Patch {
|
||||
impl Patch {
|
||||
fn of(r: &b::Brep, face: usize) -> Result<Patch> {
|
||||
let f = r.faces.get(face).ok_or_else(|| KernelError::Invalid(format!("no face {face}")))?;
|
||||
let surface = SurfaceEval::new(&f.surface).map_err(|e| KernelError::Failed(format!("match: face {face}: {e}")))?;
|
||||
let surface = SurfaceEval::new(f.parameters()).map_err(|e| KernelError::Failed(format!("match: face {face}: {e}")))?;
|
||||
let [[u0, u1], [v0, v1]] = f.domain;
|
||||
Ok(Patch { surface, ranges: [(u0, u1), (v0, v1)] })
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ pub fn project_onto_face(
|
||||
pub fn face_section_along(body: &Body, face: usize, at: Vec3, along: Vec3) -> Result<Vec<(brep::Curve, [f64; 2])>> {
|
||||
use solidcraft_geom::brep::eval::SurfaceEval;
|
||||
let f = body.record().faces.get(face).ok_or_else(|| KernelError::Invalid(format!("the body has no face {face}")))?;
|
||||
let eval = SurfaceEval::new(&f.surface).map_err(|e| KernelError::Failed(format!("face section: {e}")))?;
|
||||
let eval = SurfaceEval::new(f.parameters()).map_err(|e| KernelError::Failed(format!("face section: {e}")))?;
|
||||
let [[u0, u1], [v0, v1]] = f.domain;
|
||||
let size = body.size().max(1e-9);
|
||||
let uv = eval
|
||||
@@ -206,7 +206,7 @@ pub fn iso_curves(body: &Body, face: usize, at: Vec3, along_u: bool) -> Result<V
|
||||
use solidcraft_geom::brep::eval::SurfaceEval;
|
||||
let record = body.record();
|
||||
let f = record.faces.get(face).ok_or_else(|| KernelError::Invalid(format!("the body has no face {face}")))?;
|
||||
let eval = SurfaceEval::new(&f.surface).map_err(|e| KernelError::Failed(format!("iso curve: {e}")))?;
|
||||
let eval = SurfaceEval::new(f.parameters()).map_err(|e| KernelError::Failed(format!("iso curve: {e}")))?;
|
||||
let [[u0, u1], [v0, v1]] = f.domain;
|
||||
let size = body.size().max(1e-9);
|
||||
let mid = [0.5 * (u0 + u1), 0.5 * (v0 + v1)];
|
||||
@@ -275,11 +275,11 @@ pub fn iso_curves(body: &Body, face: usize, at: Vec3, along_u: bool) -> Result<V
|
||||
if inside(at_t(0.5 * (a + b))) {
|
||||
run = Some(run.map_or((a, b), |(s, _)| (s, b)));
|
||||
} else if let Some(r) = run.take() {
|
||||
out.push(iso_record(&f.surface, along_u, fixed, r)?);
|
||||
out.push(iso_record(f.parameters(), along_u, fixed, r)?);
|
||||
}
|
||||
}
|
||||
if let Some(r) = run {
|
||||
out.push(iso_record(&f.surface, along_u, fixed, r)?);
|
||||
out.push(iso_record(f.parameters(), along_u, fixed, r)?);
|
||||
}
|
||||
if out.is_empty() {
|
||||
return Err(KernelError::Invalid("the iso curve runs outside the face".into()));
|
||||
|
||||
@@ -137,6 +137,7 @@ fn polyhedron_record(hs: &[HalfSpace], verts: &[Vec3], faces: &[(usize, Vec<usiz
|
||||
}
|
||||
let w = |x: Vec3| [x.x, x.y, x.z];
|
||||
out.push(rec::Face {
|
||||
nurbs: None,
|
||||
surface: rec::Surface::Plane { origin: w(o), u_axis: w(u), v_axis: w(v) },
|
||||
domain: [[lo[0], hi_uv[0]], [lo[1], hi_uv[1]]],
|
||||
loops: vec![Loop::Edges { edges }],
|
||||
|
||||
@@ -1268,7 +1268,7 @@ 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 { surface: face.surface.clone(), domain: *domain, loops: record_loops });
|
||||
record_faces.push(b::Face { nurbs: None, surface: face.surface.clone(), domain: *domain, loops: record_loops });
|
||||
}
|
||||
let record_edges = edges
|
||||
.iter()
|
||||
|
||||
@@ -155,7 +155,7 @@ fn face_patch(body: &Body, face: usize) -> Result<Patch> {
|
||||
let f = record.faces.get(face).ok_or_else(|| invalid(format!("the tool body has no face {face}")))?;
|
||||
let forward = body.face_forward(face);
|
||||
// The surface's own parameter box, unbounded where it is.
|
||||
let dom = SurfaceEval::new(&f.surface).map_err(|e| invalid(format!("the split tool's surface: {e}")))?.domain();
|
||||
let dom = SurfaceEval::new(f.parameters()).map_err(|e| invalid(format!("the split tool's surface: {e}")))?.domain();
|
||||
let range = |k: usize| dom.range[k].map_or(ALL, |(a, b)| [a, b]);
|
||||
Ok(Patch { surface: f.surface.clone(), domain: [range(0), range(1)], flip: !forward })
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ fn planned(body: &Body, at: &[Vec3], r: &Ruled, handles: &[Option<Vec<Vec3>>]) -
|
||||
*count.entry(*i).or_default() += 1;
|
||||
}
|
||||
}
|
||||
let surfaces: Vec<Option<SurfaceEval>> = rec.faces.iter().map(|f| SurfaceEval::new(&f.surface).ok()).collect();
|
||||
let surfaces: Vec<Option<SurfaceEval>> = rec.faces.iter().map(|f| SurfaceEval::new(f.parameters()).ok()).collect();
|
||||
let normal = |face: usize, p: Vec3| normal_at(surfaces.get(face)?.as_ref()?, body.face_forward(face), p);
|
||||
// The face each edge takes its directions from: the picked face where it borders the edge,
|
||||
// carried on along the chain to the faces meeting it smoothly (a cylinder's halves); with no
|
||||
@@ -1001,7 +1001,7 @@ fn face_of(mut parts: Parts, surface: b::Surface, uses: Vec<(b::Curve, [f64; 2],
|
||||
}
|
||||
_ => uv_bounds(&loops).ok_or_else(|| KernelError::Invalid("a ruled face encloses no area".into()))?,
|
||||
};
|
||||
parts.faces.push((b::Face { surface, domain, loops }, true));
|
||||
parts.faces.push((b::Face { nurbs: None, surface, domain, loops }, true));
|
||||
Ok(parts.canonical()?.brep)
|
||||
}
|
||||
|
||||
|
||||
@@ -379,7 +379,7 @@ pub(crate) fn tensor_sheet(n: &NurbsSurface) -> Result<Body> {
|
||||
parts.edges.push(b::Edge { curve, interval, vertices: [index(a)?, index(z)?], tolerance: parts.tolerance, uses: Vec::new() });
|
||||
uses.push(b::OrientedEdge { edge: index(i)?, forward, pcurve: uv_line(pa, pz, interval) });
|
||||
}
|
||||
parts.faces.push((b::Face { surface: spline_surface(n), domain: n.domain(), loops: vec![b::Loop::Edges { edges: uses }] }, true));
|
||||
parts.faces.push((b::Face { nurbs: None, surface: spline_surface(n), domain: n.domain(), loops: vec![b::Loop::Edges { edges: uses }] }, true));
|
||||
surface_body(&parts.canonical()?.brep)
|
||||
}
|
||||
|
||||
@@ -396,7 +396,7 @@ pub fn patch_region(plane: &Plane, region: &Region2) -> Result<Body> {
|
||||
let holes = region.holes.iter().map(|h| h.ccw().reversed());
|
||||
let loops = std::iter::once(region.outer.ccw()).chain(holes).map(|lp| sketch_loop(&mut parts, plane, &lp.segs)).collect::<Result<Vec<_>>>()?;
|
||||
let domain = uv_box(&loops).ok_or_else(|| invalid("the region has no area"))?;
|
||||
parts.faces.push((b::Face { surface: crate::freeform::plane_surface(plane), domain, loops }, true));
|
||||
parts.faces.push((b::Face { nurbs: None, surface: crate::freeform::plane_surface(plane), domain, loops }, true));
|
||||
let c = parts.canonical()?;
|
||||
let h = record_history(&c.brep, 0, vec![record::generated(Vec::new())], |_, _, _| Vec::new());
|
||||
let (body, same) = record_body(&c.brep)?;
|
||||
@@ -842,7 +842,7 @@ pub fn extend(body: &Body, at: &[Vec3], distance: f64) -> Result<Body> {
|
||||
let loops = vec![b::Loop::Edges { edges }];
|
||||
let domain = uv_box(&loops).ok_or_else(|| invalid("the extended face has no area"))?;
|
||||
let forward = face_sides(r).first().copied().unwrap_or(true);
|
||||
parts.faces.push((b::Face { surface, domain, loops }, forward));
|
||||
parts.faces.push((b::Face { nurbs: None, surface, domain, loops }, forward));
|
||||
let c = parts.canonical()?;
|
||||
let h = record_history(&c.brep, 1, vec![record::from_face(0, 0, Change::Modified)], |e, _, _| {
|
||||
c.edges.get(e).and_then(|p| rim.get(*p)).map(|u| EdgeOrigin::Edge { operand: 0, edge: u.edge as usize }).into_iter().collect()
|
||||
@@ -1325,7 +1325,7 @@ mod imp {
|
||||
let pl = Plane { origin: at, x: u, y: n.cross(u) };
|
||||
let lp = sketch_loop(&mut parts, &pl, &segs)?;
|
||||
let domain = uv_box(std::slice::from_ref(&lp)).ok_or_else(|| invalid("mitre plane"))?;
|
||||
parts.faces.push((b::Face { surface: crate::freeform::plane_surface(&pl), domain, loops: vec![lp] }, true));
|
||||
parts.faces.push((b::Face { nurbs: None, surface: crate::freeform::plane_surface(&pl), domain, loops: vec![lp] }, true));
|
||||
}
|
||||
SmBrep::from_record(&parts.canonical()?.brep, true).map_err(sm("sweep mitre"))
|
||||
}
|
||||
|
||||
@@ -217,7 +217,7 @@ mod tests {
|
||||
let face = &body.faces[u.face as usize];
|
||||
let brep::Loop::Edges { edges: loop_edges } = &face.loops[u.r#loop as usize] else { panic!("an edge loop") };
|
||||
let oe = &loop_edges[u.position as usize];
|
||||
let Ok((s, map)) = crate::tess::surface::eval_surface(&face.surface, face.domain) else { panic!("evaluates") };
|
||||
let Ok((s, map)) = crate::tess::surface::eval_surface(face.parameters(), face.domain) else { panic!("evaluates") };
|
||||
for (t, p) in n.params.iter().zip(&n.points) {
|
||||
let Some(q) = oe.pcurve.eval(*t) else { panic!("pcurve at {t}") };
|
||||
let q = map.to_eval(q);
|
||||
|
||||
@@ -135,7 +135,7 @@ pub(crate) fn plan(b: &brep::Brep, tol: f64, turn: Option<size::Turn>) -> Result
|
||||
let evals: Vec<(EvalSurface, surface::Params)> = faces
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(fi, f)| surface::eval_surface(&f.surface, f.domain).map_err(|e| unsupported(format!("face {fi} ({}): {e}", kind(&f.surface)))))
|
||||
.map(|(fi, f)| surface::eval_surface(f.parameters(), f.domain).map_err(|e| unsupported(format!("face {fi} ({}): {e}", kind(&f.surface)))))
|
||||
.collect::<Result<_>>()?;
|
||||
let (evals, params): (Vec<EvalSurface>, Vec<surface::Params>) = evals.into_iter().unzip();
|
||||
let curved = evals.iter().filter(|e| !matches!(e, EvalSurface::Plane { .. })).count();
|
||||
@@ -160,7 +160,7 @@ pub(crate) fn plan(b: &brep::Brep, tol: f64, turn: Option<size::Turn>) -> Result
|
||||
};
|
||||
let loops = boundary(b, fi, face, eval, map, &nodes, &rules)?;
|
||||
let mut stats = PlanStats::default();
|
||||
let fp = plan_face(fi, kind(&face.surface), eval, &loops, &rules, *size, &mut stats)?;
|
||||
let fp = plan_face(fi, kind(face.parameters()), eval, &loops, &rules, *size, &mut stats)?;
|
||||
let fwd = forward.get(fi).copied().unwrap_or(true);
|
||||
let fp =
|
||||
FacePlan { flip: !fwd, triangles: if fwd { fp.triangles } else { fp.triangles.into_iter().map(|[a, b, c]| [a, c, b]).collect() }, ..fp };
|
||||
|
||||
@@ -180,11 +180,11 @@ mod tests {
|
||||
brep::Surface::Cylinder { .. } | brep::Surface::Cone { .. } | brep::Surface::Sphere { .. } | brep::Surface::Torus { .. } => 1,
|
||||
_ => 2,
|
||||
};
|
||||
let (eval, map) = match eval_surface(&face.surface, face.domain) {
|
||||
let (eval, map) = match eval_surface(face.parameters(), face.domain) {
|
||||
Ok(e) => e,
|
||||
Err(e) => panic!("not evaluable: {e} ({:?})", face.surface),
|
||||
};
|
||||
let Ok(reference) = SurfaceEval::new(&face.surface) else { panic!("the record's evaluator: {:?}", face.surface) };
|
||||
let Ok(reference) = SurfaceEval::new(face.parameters()) else { panic!("the record's evaluator: {:?}", face.surface) };
|
||||
let [[u0, u1], [v0, v1]] = face.domain;
|
||||
for i in 0..=6 {
|
||||
for j in 0..=6 {
|
||||
|
||||
@@ -138,7 +138,7 @@ fn patch_faces(patches: &[BezierPatch]) -> Result<Parts> {
|
||||
let ctrl: Vec<HPoint> = (0..=pt).flat_map(|j| (0..=ps).map(move |i| (i, j))).map(|(i, j)| p.point(i, j)).collect();
|
||||
let surface = NurbsSurface::from_homogeneous([ps, pt], [knots(ps), knots(pt)], ctrl).map_err(spline_err)?;
|
||||
parts.faces.push((
|
||||
b::Face { surface: spline_surface(&surface), domain: [[0.0, 1.0], [0.0, 1.0]], loops: vec![b::Loop::Edges { edges: uses }] },
|
||||
b::Face { nurbs: None, surface: spline_surface(&surface), domain: [[0.0, 1.0], [0.0, 1.0]], loops: vec![b::Loop::Edges { edges: uses }] },
|
||||
true,
|
||||
));
|
||||
}
|
||||
|
||||
+59
-40
@@ -305,22 +305,59 @@ fn frame(origin: [f64; 3], axis: [f64; 3], x: [f64; 3]) -> b::Frame {
|
||||
|
||||
// ---- Geometry ---------------------------------------------------------------------------
|
||||
|
||||
/// The schema record of each face's surface, in face order.
|
||||
fn surfaces(flat: View, n_faces: usize) -> Res<Vec<b::Surface>> {
|
||||
/// The schema record of each face's surface, in face order, and its NURBS form when the arrays give
|
||||
/// one (`FACE_NURBS_FORM`: an analytic face with the NURBS SMLib holds it as).
|
||||
fn surfaces(flat: View, n_faces: usize) -> Res<Vec<(b::Surface, Option<b::Surface>)>> {
|
||||
let kinds = flat.i(FACE_SURFACE_TYPE);
|
||||
if kinds.len() != n_faces {
|
||||
return Err(invalid("face surface kinds"));
|
||||
}
|
||||
// The NURBS blocks, in face order: each NURBS face's surface and each face's NURBS form.
|
||||
struct At {
|
||||
j: usize,
|
||||
cv: usize,
|
||||
uk: usize,
|
||||
vk: usize,
|
||||
w: usize,
|
||||
}
|
||||
let next_nurbs = |at_: &mut At| -> Res<b::Surface> {
|
||||
let j = at_.j;
|
||||
let nu = count(at(flat.i(FACE_NURBS_U_VERTEX_COUNT), j, "NURBS face")?, "NURBS face")?;
|
||||
let nv = count(at(flat.i(FACE_NURBS_V_VERTEX_COUNT), j, "NURBS face")?, "NURBS face")?;
|
||||
let ou = count(at(flat.i(FACE_NURBS_U_ORDER), j, "NURBS face")?, "NURBS face")?;
|
||||
let ov = count(at(flat.i(FACE_NURBS_V_ORDER), j, "NURBS face")?, "NURBS face")?;
|
||||
let n = nu.checked_mul(nv).filter(|n| *n <= b::MAX_ITEMS).ok_or_else(|| invalid("NURBS face size"))?;
|
||||
let slice = |v: &[f64], from: usize, len: usize| v.get(from..from + len).map(<[f64]>::to_vec).ok_or_else(|| invalid("NURBS face arrays"));
|
||||
let pts = slice(flat.f(FACE_NURBS_CVS), 3 * at_.cv, 3 * n)?;
|
||||
let u_knots = slice(flat.f(FACE_NURBS_U_KNOTS), at_.uk, nu + ou)?;
|
||||
let v_knots = slice(flat.f(FACE_NURBS_V_KNOTS), at_.vk, nv + ov)?;
|
||||
let weights = if flat.f(FACE_NURBS_WEIGHTS).is_empty() { vec![1.0; n] } else { slice(flat.f(FACE_NURBS_WEIGHTS), at_.w, n)? };
|
||||
*at_ = At { j: j + 1, cv: at_.cv + n, uk: at_.uk + nu + ou, vk: at_.vk + nv + ov, w: at_.w + n };
|
||||
let point = |p: usize| -> [f64; 3] { [pts[3 * p], pts[3 * p + 1], pts[3 * p + 2]] };
|
||||
Ok(if weights.iter().all(|x| *x == 1.0) {
|
||||
b::Surface::Spline { u_knots, v_knots, points: (0..nu).map(|r| (0..nv).map(|c| point(r * nv + c)).collect()).collect() }
|
||||
} else {
|
||||
let h = |p: usize| {
|
||||
let (q, wt) = (point(p), weights[p]);
|
||||
[q[0] * wt, q[1] * wt, q[2] * wt, wt]
|
||||
};
|
||||
b::Surface::RationalSpline { u_knots, v_knots, homogeneous: (0..nu).map(|r| (0..nv).map(|c| h(r * nv + c)).collect()).collect() }
|
||||
})
|
||||
};
|
||||
let mut nurbs_at = At { j: 0, cv: 0, uk: 0, vk: 0, w: 0 };
|
||||
let forms = flat.i(FACE_NURBS_FORM);
|
||||
if !forms.is_empty() && forms.len() != n_faces {
|
||||
return Err(invalid("face NURBS forms"));
|
||||
}
|
||||
let mut k = [0usize; 6];
|
||||
let (mut cv, mut uk, mut vk, mut w) = (0usize, 0usize, 0usize, 0usize);
|
||||
let mut out = Vec::with_capacity(n_faces);
|
||||
for &kind in kinds {
|
||||
for (f, &kind) in kinds.iter().enumerate() {
|
||||
let slot = usize::try_from(kind).ok().filter(|s| *s < 6).ok_or_else(|| unsupported(format!("SMLib surface kind {kind}")))?;
|
||||
let i = at(&k, slot, "kind")?;
|
||||
if let Some(c) = k.get_mut(slot) {
|
||||
*c += 1;
|
||||
}
|
||||
out.push(match kind {
|
||||
let surface = match kind {
|
||||
SURFACE_PLANE => {
|
||||
let f = frame(
|
||||
v3(flat.f(FACE_PLANE_ORIGIN), i, "plane")?,
|
||||
@@ -368,35 +405,12 @@ fn surfaces(flat: View, n_faces: usize) -> Res<Vec<b::Surface>> {
|
||||
major_radius: at(flat.f(FACE_TORUS_MAJOR_RADIUS), i, "torus")?,
|
||||
minor_radius: at(flat.f(FACE_TORUS_MINOR_RADIUS), i, "torus")?,
|
||||
},
|
||||
SURFACE_NURBS => {
|
||||
let nu = count(at(flat.i(FACE_NURBS_U_VERTEX_COUNT), i, "NURBS face")?, "NURBS face")?;
|
||||
let nv = count(at(flat.i(FACE_NURBS_V_VERTEX_COUNT), i, "NURBS face")?, "NURBS face")?;
|
||||
let ou = count(at(flat.i(FACE_NURBS_U_ORDER), i, "NURBS face")?, "NURBS face")?;
|
||||
let ov = count(at(flat.i(FACE_NURBS_V_ORDER), i, "NURBS face")?, "NURBS face")?;
|
||||
let n = nu.checked_mul(nv).filter(|n| *n <= b::MAX_ITEMS).ok_or_else(|| invalid("NURBS face size"))?;
|
||||
let slice =
|
||||
|a: &[f64], from: usize, len: usize| a.get(from..from + len).map(<[f64]>::to_vec).ok_or_else(|| invalid("NURBS face arrays"));
|
||||
let pts = slice(flat.f(FACE_NURBS_CVS), 3 * cv, 3 * n)?;
|
||||
let u_knots = slice(flat.f(FACE_NURBS_U_KNOTS), uk, nu + ou)?;
|
||||
let v_knots = slice(flat.f(FACE_NURBS_V_KNOTS), vk, nv + ov)?;
|
||||
let weights = if flat.f(FACE_NURBS_WEIGHTS).is_empty() { vec![1.0; n] } else { slice(flat.f(FACE_NURBS_WEIGHTS), w, n)? };
|
||||
cv += n;
|
||||
uk += nu + ou;
|
||||
vk += nv + ov;
|
||||
w += n;
|
||||
let point = |p: usize| -> [f64; 3] { [pts[3 * p], pts[3 * p + 1], pts[3 * p + 2]] };
|
||||
if weights.iter().all(|x| *x == 1.0) {
|
||||
b::Surface::Spline { u_knots, v_knots, points: (0..nu).map(|a| (0..nv).map(|c| point(a * nv + c)).collect()).collect() }
|
||||
} else {
|
||||
let h = |p: usize| {
|
||||
let (q, wt) = (point(p), weights[p]);
|
||||
[q[0] * wt, q[1] * wt, q[2] * wt, wt]
|
||||
};
|
||||
b::Surface::RationalSpline { u_knots, v_knots, homogeneous: (0..nu).map(|a| (0..nv).map(|c| h(a * nv + c)).collect()).collect() }
|
||||
}
|
||||
}
|
||||
SURFACE_NURBS => next_nurbs(&mut nurbs_at)?,
|
||||
other => return Err(unsupported(format!("SMLib surface kind {other}"))),
|
||||
});
|
||||
};
|
||||
// A NURBS face's own block came first; a form follows the analytic face's canonical entry.
|
||||
let form = if kind != SURFACE_NURBS && forms.get(f) == Some(&1) { Some(next_nurbs(&mut nurbs_at)?) } else { None };
|
||||
out.push((surface, form));
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
@@ -675,6 +689,8 @@ fn onto(r: [f64; 2], natural: b::Natural) -> [f64; 2] {
|
||||
/// One face before renumbering: loops relative to the record's normal, as (edge, forward).
|
||||
#[derive(Clone)]
|
||||
struct Face {
|
||||
/// The NURBS form the face's (u, v) are on (`FACE_NURBS_FORM`), when it differs from `surface`.
|
||||
nurbs: Option<b::Surface>,
|
||||
surface: b::Surface,
|
||||
domain: [[f64; 2]; 2],
|
||||
/// SMLib's natural normal is the record's.
|
||||
@@ -809,11 +825,13 @@ fn native_ordered(arrays: &Arrays) -> Res<(b::Brep, Report, Order)> {
|
||||
return Err(invalid("face ranges"));
|
||||
}
|
||||
let mut faces = Vec::with_capacity(n_faces);
|
||||
for (f, (surface, loops)) in surfaces.into_iter().zip(&face_loops).enumerate() {
|
||||
for (f, ((surface, nurbs), loops)) in surfaces.into_iter().zip(&face_loops).enumerate() {
|
||||
let r = range.get(4 * f..4 * f + 4).ok_or_else(|| invalid("face range"))?;
|
||||
let natural = surface.natural();
|
||||
// A face with a NURBS form is parameterised on it: its range and trims are that NURBS's.
|
||||
let params = nurbs.as_ref().unwrap_or(&surface);
|
||||
let natural = params.natural();
|
||||
let domain = [onto([r[0], r[2]], natural[0]), onto([r[1], r[3]], natural[1])];
|
||||
let spline = matches!(surface, b::Surface::Spline { .. } | b::Surface::RationalSpline { .. });
|
||||
let spline = matches!(params, b::Surface::Spline { .. } | b::Surface::RationalSpline { .. });
|
||||
let mut out = Vec::with_capacity(loops.len());
|
||||
let mut records = Vec::with_capacity(loops.len());
|
||||
let mut given = Vec::with_capacity(loops.len());
|
||||
@@ -843,7 +861,7 @@ fn native_ordered(arrays: &Arrays) -> Res<(b::Brep, Report, Order)> {
|
||||
out.push(Loop::Edges(l));
|
||||
given.push(g);
|
||||
}
|
||||
faces.push(Face { surface, domain, agrees: true, loops: out, records, given });
|
||||
faces.push(Face { nurbs, surface, domain, agrees: true, loops: out, records, given });
|
||||
}
|
||||
for face in &mut faces {
|
||||
if outer_area(face, &edges, largest)? < 0.0 {
|
||||
@@ -1154,7 +1172,8 @@ fn canonical(parts: Parts, n_vertices: usize, flat: View, tolerances: &Tolerance
|
||||
Loop::Vertex(v) => Ok(Loop::Vertex(*v)),
|
||||
})
|
||||
.collect::<Res<_>>()?;
|
||||
let mut curves = pcurves(&face.surface, face.domain, &renumbered, &face.given, &edge_records, tolerance)?.into_iter();
|
||||
let mut curves =
|
||||
pcurves(face.nurbs.as_ref().unwrap_or(&face.surface), face.domain, &renumbered, &face.given, &edge_records, tolerance)?.into_iter();
|
||||
let mut loops = Vec::with_capacity(renumbered.len());
|
||||
for (li, l) in renumbered.iter().enumerate() {
|
||||
loops.push(match l {
|
||||
@@ -1170,7 +1189,7 @@ fn canonical(parts: Parts, n_vertices: usize, flat: View, tolerances: &Tolerance
|
||||
}
|
||||
});
|
||||
}
|
||||
face_records.push(b::Face { surface: face.surface.clone(), domain: face.domain, loops });
|
||||
face_records.push(b::Face { nurbs: face.nurbs.clone(), surface: face.surface.clone(), domain: face.domain, loops });
|
||||
}
|
||||
let positions = flat.f(VERTEX_POSITION);
|
||||
let vertices = vertex_order
|
||||
@@ -1461,7 +1480,7 @@ fn outer_area(face: &Face, edges: &[Edge], tolerance: f64) -> Res<f64> {
|
||||
let records: Vec<b::Edge> =
|
||||
edges.iter().map(|e| b::Edge { curve: e.curve.clone(), interval: e.interval, vertices: e.vertices, tolerance, uses: Vec::new() }).collect();
|
||||
let given = face.given.first().map(std::slice::from_ref).unwrap_or(&[]);
|
||||
let curves = pcurves(&face.surface, face.domain, &[Loop::Edges(outer.clone())], given, &records, tolerance)?;
|
||||
let curves = pcurves(face.nurbs.as_ref().unwrap_or(&face.surface), face.domain, &[Loop::Edges(outer.clone())], given, &records, tolerance)?;
|
||||
let mut pts: Vec<[f64; 2]> = Vec::new();
|
||||
for (pc, &(e, forward)) in curves.first().map(Vec::as_slice).unwrap_or(&[]).iter().zip(outer) {
|
||||
let edge = edges.get(e as usize).ok_or_else(|| invalid("edge"))?;
|
||||
|
||||
@@ -37,6 +37,7 @@ pub const FACE_NURBS_V_VERTEX_COUNT: i32 = 29;
|
||||
pub const FACE_NURBS_U_ORDER: i32 = 30;
|
||||
pub const FACE_NURBS_V_ORDER: i32 = 31;
|
||||
pub const SOURCE: i32 = 32;
|
||||
pub const FACE_NURBS_FORM: i32 = 33;
|
||||
pub const TOLERANCE: i32 = 101;
|
||||
pub const EXTENT: i32 = 102;
|
||||
pub const FACE_RANGE: i32 = 103;
|
||||
|
||||
@@ -169,11 +169,25 @@ fn laid_out(brep: &b::Brep, trusted: bool, analytic: bool) -> R<Arrays> {
|
||||
let intervals: Vec<([f64; 2], f64)> = brep.edges.iter().map(|e| turned(&e.curve, e.interval)).collect();
|
||||
|
||||
// Faces, loops and edgeuses, with the surfaces and, on B-spline faces, the trims.
|
||||
let any_spline_face = brep.faces.iter().any(|f| matches!(f.surface, b::Surface::Spline { .. } | b::Surface::RationalSpline { .. }));
|
||||
let any_spline_face =
|
||||
brep.faces.iter().any(|f| f.nurbs.is_some() || matches!(f.surface, b::Surface::Spline { .. } | b::Surface::RationalSpline { .. }));
|
||||
let any_form = brep.faces.iter().any(|f| f.nurbs.is_some());
|
||||
for f in &brep.faces {
|
||||
a.i(flat::FACE_LOOP_COUNT).push(int(f.loops.len())?);
|
||||
a.i(flat::FACE_TRIM_TYPE).push(0);
|
||||
let spline = surface(&mut a, &f.surface, f.domain, analytic)?;
|
||||
// A face with a NURBS form: what it is (its analytic entry), then the NURBS it is
|
||||
// parameterised on, whose range and trims these are (FACE_NURBS_FORM).
|
||||
let spline = match &f.nurbs {
|
||||
Some(n) => {
|
||||
surface(&mut a, &f.surface, f.domain, analytic)?;
|
||||
nurbs_block(&mut a, n)?;
|
||||
true
|
||||
}
|
||||
None => surface(&mut a, &f.surface, f.domain, analytic)?,
|
||||
};
|
||||
if any_form {
|
||||
a.i(flat::FACE_NURBS_FORM).push(i64::from(f.nurbs.is_some()));
|
||||
}
|
||||
for l in &f.loops {
|
||||
match l {
|
||||
b::Loop::Vertex { vertex } => {
|
||||
@@ -243,8 +257,13 @@ fn net(a: &mut Arrays, rows: impl Iterator<Item = ([f64; 3], f64)>) {
|
||||
}
|
||||
|
||||
fn nurbs_face(a: &mut Arrays, u_knots: &[f64], v_knots: &[f64], nu: usize, nv: usize) -> R<()> {
|
||||
let order = |k: &[f64], n: usize| k.len().checked_sub(n).filter(|o| *o >= 2).ok_or(Unsupported::Invalid("spline order"));
|
||||
a.i(flat::FACE_SURFACE_TYPE).push(i64::from(flat::SURFACE_NURBS));
|
||||
nurbs_sizes(a, u_knots, v_knots, nu, nv)
|
||||
}
|
||||
|
||||
/// A NURBS block's sizes, orders and knots.
|
||||
fn nurbs_sizes(a: &mut Arrays, u_knots: &[f64], v_knots: &[f64], nu: usize, nv: usize) -> R<()> {
|
||||
let order = |k: &[f64], n: usize| k.len().checked_sub(n).filter(|o| *o >= 2).ok_or(Unsupported::Invalid("spline order"));
|
||||
a.i(flat::FACE_NURBS_U_VERTEX_COUNT).push(int(nu)?);
|
||||
a.i(flat::FACE_NURBS_V_VERTEX_COUNT).push(int(nv)?);
|
||||
a.i(flat::FACE_NURBS_U_ORDER).push(int(order(u_knots, nu)?)?);
|
||||
@@ -254,6 +273,22 @@ fn nurbs_face(a: &mut Arrays, u_knots: &[f64], v_knots: &[f64], nu: usize, nv: u
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The NURBS block of a face's NURBS form, with no surface kind or range of its own.
|
||||
fn nurbs_block(a: &mut Arrays, s: &b::Surface) -> R<()> {
|
||||
match s {
|
||||
b::Surface::Spline { u_knots, v_knots, points } => {
|
||||
nurbs_sizes(a, u_knots, v_knots, points.len(), points.first().map_or(0, Vec::len))?;
|
||||
net(a, points.iter().flatten().map(|p| (*p, 1.0)));
|
||||
}
|
||||
b::Surface::RationalSpline { u_knots, v_knots, homogeneous } => {
|
||||
nurbs_sizes(a, u_knots, v_knots, homogeneous.len(), homogeneous.first().map_or(0, Vec::len))?;
|
||||
net(a, homogeneous.iter().flatten().map(|h| ([h[0] / h[3], h[1] / h[3], h[2] / h[3]], h[3])));
|
||||
}
|
||||
_ => return Err(Unsupported::Invalid("NURBS form is not a NURBS")),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The face's surface and range; true for a B-spline surface (which carries trims).
|
||||
fn surface(a: &mut Arrays, s: &b::Surface, domain: [[f64; 2]; 2], analytic_planes: bool) -> R<bool> {
|
||||
let [[u0, u1], [v0, v1]] = domain;
|
||||
|
||||
@@ -49,6 +49,7 @@ fn polyhedron(corners: &[[f64; 3]], cycles: &[&[usize]]) -> Record {
|
||||
});
|
||||
}
|
||||
faces.push(Face {
|
||||
nurbs: None,
|
||||
surface: Surface::Plane { origin: o, u_axis: x, v_axis: y },
|
||||
domain: [[lo[0], hi[0]], [lo[1], hi[1]]],
|
||||
loops: vec![Loop::Edges { edges }],
|
||||
@@ -132,7 +133,7 @@ fn a_plane_on_other_axes_goes_as_smlibs_plane() {
|
||||
// non-unit and check it still goes as SMLib's plane, the volume unchanged.
|
||||
let c = [[0., 0., 0.], [6., 0., 0.], [0., 6., 0.], [0., 0., 6.]];
|
||||
let mut r = polyhedron(&c, &[&[0, 2, 1], &[0, 1, 3], &[1, 2, 3], &[2, 0, 3]]);
|
||||
if let Some(Face { surface: Surface::Plane { u_axis, v_axis, .. }, domain, loops }) = r.faces.get_mut(0) {
|
||||
if let Some(Face { nurbs: None, surface: Surface::Plane { u_axis, v_axis, .. }, domain, loops }) = r.faces.get_mut(0) {
|
||||
*u_axis = scale(*u_axis, 2.0);
|
||||
*v_axis = scale(*v_axis, 2.0);
|
||||
*domain = domain.map(|d| d.map(|x| x / 2.0));
|
||||
|
||||
@@ -883,7 +883,7 @@ impl<'b, 'a> Builder<'b, 'a> {
|
||||
}
|
||||
}
|
||||
let domain = *domains.get(f as usize).ok_or("face reference")?;
|
||||
faces.push(Face { surface: face.surface.clone(), domain, loops });
|
||||
faces.push(Face { nurbs: None, surface: face.surface.clone(), domain, loops });
|
||||
}
|
||||
let mut out_shells = Vec::with_capacity(shells.len());
|
||||
for s in &shells {
|
||||
|
||||
@@ -454,7 +454,14 @@ impl BrepWriter<'_, '_, '_> {
|
||||
if let Some(Some(s)) = self.surfaces.get(f as usize) {
|
||||
return Ok(s.clone());
|
||||
}
|
||||
let (s, map) = surface(&face_of(self.b, f)?.surface).map_err(|m| format!("face {f}: {m}"))?;
|
||||
let face = face_of(self.b, f)?;
|
||||
let (s, map) = surface(&face.surface).map_err(|m| format!("face {f}: {m}"))?;
|
||||
// A face on its NURBS form is written as the analytic surface it is, with no pcurves
|
||||
// (STEP's are in the angle); only which way its normal points carries over.
|
||||
let map = match &face.nurbs {
|
||||
Some(n) => form_map(&face.surface, n, face.domain, map).ok_or_else(|| format!("face {f}: its NURBS form has no normal"))?,
|
||||
None => map,
|
||||
};
|
||||
let s = (Arc::new(s), map);
|
||||
if let Some(slot) = self.surfaces.get_mut(f as usize) {
|
||||
*slot = Some(s.clone());
|
||||
@@ -499,14 +506,21 @@ impl BrepWriter<'_, '_, '_> {
|
||||
let b = self.b;
|
||||
let mut associated = Vec::with_capacity(edge.uses.len());
|
||||
for u in &edge.uses {
|
||||
let Some(Loop::Edges { edges }) = face_of(b, u.face)?.loops.get(u.r#loop as usize) else {
|
||||
let face = face_of(b, u.face)?;
|
||||
if face.nurbs.is_some() {
|
||||
continue;
|
||||
}
|
||||
let Some(Loop::Edges { edges }) = face.loops.get(u.r#loop as usize) else {
|
||||
return Err(format!("face {}: loop {} is no edge loop", u.face, u.r#loop));
|
||||
};
|
||||
let used = edges.get(u.position as usize).ok_or_else(|| format!("face {}: no use at {}", u.face, u.position))?;
|
||||
let (surface, map) = self.surface(u.face)?;
|
||||
associated.push(Associated::Pcurve(Pcurve { surface, curve: Arc::new(pcurve(&used.pcurve, map)?) }));
|
||||
}
|
||||
let seam = matches!(edge.uses.as_slice(), [a, b] if a.face == b.face);
|
||||
if associated.is_empty() {
|
||||
return Ok(self.g.curve(&curve_3d));
|
||||
}
|
||||
let seam = associated.len() == 2 && matches!(edge.uses.as_slice(), [a, b] if a.face == b.face);
|
||||
let kind = if seam { SurfaceCurveKind::Seam } else { SurfaceCurveKind::Surface };
|
||||
Ok(self.g.curve(&Curve::SurfaceCurve { kind, curve_3d: Arc::new(curve_3d), associated, master: PreferredRep::Curve3d }))
|
||||
}
|
||||
@@ -575,3 +589,39 @@ fn signed_area(ring: &[[f64; 2]]) -> f64 {
|
||||
let n = ring.len();
|
||||
0.5 * (0..n).filter_map(|i| Some((ring.get(i)?, ring.get((i + 1) % n)?))).map(|(p, q)| p[0] * q[1] - q[0] * p[1]).sum::<f64>()
|
||||
}
|
||||
|
||||
/// The map of a face on its NURBS form `n`: no pcurve goes through it, so it only says whether
|
||||
/// the face's natural normal (the NURBS's, which the loops follow) is the STEP surface's. The
|
||||
/// record's cylinder, cone, sphere and torus have their natural normal pointing away from the
|
||||
/// axis (the sphere's centre, the torus's centre circle); `None` when the NURBS has no normal at
|
||||
/// its domain's centre.
|
||||
fn form_map(analytic: &BSurface, n: &BSurface, domain: [[f64; 2]; 2], map: Map) -> Option<Map> {
|
||||
let eval = solidcraft_geom::brep::eval::SurfaceEval::new(n).ok()?;
|
||||
let (p, su, sv) = eval.derivs(0.5 * (domain[0][0] + domain[0][1]), 0.5 * (domain[1][0] + domain[1][1]))?;
|
||||
let normal = su.cross(sv);
|
||||
let away = |f: &Frame| {
|
||||
let o = solidcraft_geom::Vec3::new(f.origin[0], f.origin[1], f.origin[2]);
|
||||
let z = solidcraft_geom::Vec3::new(f.x_axis[0], f.x_axis[1], f.x_axis[2]).cross(solidcraft_geom::Vec3::new(
|
||||
f.y_axis[0],
|
||||
f.y_axis[1],
|
||||
f.y_axis[2],
|
||||
));
|
||||
let d = p - o;
|
||||
(o, z, d - z * d.dot(z))
|
||||
};
|
||||
let outward = match analytic {
|
||||
BSurface::Cylinder { frame, .. } | BSurface::Cone { frame, .. } => away(frame).2,
|
||||
BSurface::Sphere { frame, .. } => p - away(frame).0,
|
||||
BSurface::Torus { frame, major_radius, .. } => {
|
||||
let (o, _, radial) = away(frame);
|
||||
p - (o + radial.normalized()? * *major_radius)
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
let along = normal.dot(outward);
|
||||
if !(along.abs() > 0.0) {
|
||||
return None;
|
||||
}
|
||||
let keeps = (along > 0.0) == map.keeps_normal();
|
||||
Some(Map { m: [[1.0, 0.0], [0.0, if keeps { 1.0 } else { -1.0 }]] })
|
||||
}
|
||||
|
||||
@@ -143,6 +143,15 @@ its producer holds it (truck's `RevolutedCurve` under a `Processor`): nothing is
|
||||
baked into the profile, and `forward: false` swaps u and v, which reverses the
|
||||
natural normal.
|
||||
|
||||
A face on a `cylinder`, `cone`, `sphere` or `torus` may also carry `nurbs`, a
|
||||
`spline` or `rational_spline`: the NURBS form it is parameterised on, as SMLib
|
||||
holds it (every SMLib analytic surface is a B-spline surface, and its trims are
|
||||
in that NURBS's parameters, not the angle). The face's domain and pcurves are
|
||||
then in the NURBS's (u, v), its natural normal is the NURBS's, and `surface`
|
||||
only says what the face is (axis, radius). Pcurves on such a face are SMLib's
|
||||
own trims, with nothing fitted. A STEP writer writes the analytic surface and
|
||||
the face's edges without pcurves.
|
||||
|
||||
### Parameter ranges
|
||||
|
||||
An edge interval and each side of a face domain are finite with min < max. In a
|
||||
|
||||
Reference in New Issue
Block a user