mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 02:25:55 +00:00
brep, kernel: code that reads an edge's angles takes them from the circle, code that evaluates takes the NURBS form
Edge::curve_interval (and brep::curve_interval for assemble's edges) gives the interval on the edge's curve itself: a form circle's angles of its ends, its frame turned by the SMLib conversion so the angle grows along the NURBS. lip and the ruled surfaces read arcs by it; the hull, the blend tangents, the patch pcurves, point paths, IGES output and the prism runs evaluate on Edge::parameters. assemble carries an edge's form through. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -361,7 +361,7 @@ fn edge_curves(body: Option<&crate::ModelBody>, e: usize) -> Vec<WireCurve> {
|
||||
.edges
|
||||
.get(e)
|
||||
.filter(|edge| !matches!(edge.curve, solidcraft_geom::brep::Curve::SurfaceIntersection { .. }))
|
||||
.map(|edge| vec![WireCurve { curve: edge.curve.clone(), interval: edge.interval }])
|
||||
.map(|edge| vec![WireCurve { curve: edge.parameters().clone(), interval: edge.interval }])
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
|
||||
+30
-1
@@ -488,10 +488,39 @@ fn surface(s: &Surface, depth: usize, budget: &mut Budget) -> Result<()> {
|
||||
|
||||
impl Edge {
|
||||
/// The curve the edge's parameter is on: its NURBS form when it has one, else its curve.
|
||||
/// Everything that evaluates the edge at a parameter goes through this.
|
||||
/// Everything that evaluates the edge at a parameter goes through this, with `interval`.
|
||||
pub fn parameters(&self) -> &Curve {
|
||||
self.nurbs.as_ref().unwrap_or(&self.curve)
|
||||
}
|
||||
|
||||
/// The edge's interval on `curve` itself, for code that reads what the edge is (a circle's
|
||||
/// angles): `interval` when the edge has no NURBS form; otherwise the angles of its ends on the
|
||||
/// NURBS, unwrapped forward along the edge (a whole turn for a closed edge). A form's
|
||||
/// circle or ellipse has its frame turned so its angle grows along the NURBS (the SMLib
|
||||
/// conversion makes it so). `None` where the NURBS can't be evaluated.
|
||||
pub fn curve_interval(&self) -> Option<[f64; 2]> {
|
||||
curve_interval(&self.curve, self.nurbs.as_ref(), self.interval)
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Edge::curve_interval`] of an edge given by its parts: its `curve`, its NURBS form and its
|
||||
/// interval on that form.
|
||||
pub fn curve_interval(curve: &Curve, nurbs: Option<&Curve>, interval: [f64; 2]) -> Option<[f64; 2]> {
|
||||
use std::f64::consts::TAU;
|
||||
let Some(n) = nurbs else { return Some(interval) };
|
||||
let (frame, radii) = match curve {
|
||||
Curve::Circle { frame, radius } => (frame, [*radius, *radius]),
|
||||
Curve::Ellipse { frame, radii } => (frame, *radii),
|
||||
_ => return Some(interval),
|
||||
};
|
||||
let eval = eval::CurveEval::new(n).ok()?;
|
||||
let [t0, t1] = interval;
|
||||
let o = crate::Vec3::new(frame.origin[0], frame.origin[1], frame.origin[2]);
|
||||
let (x, y) = (crate::Vec3::from(frame.x_axis), crate::Vec3::from(frame.y_axis));
|
||||
let angle = |t: f64| eval.point(t).map(|p| ((p - o).dot(y) / radii[1]).atan2((p - o).dot(x) / radii[0]).rem_euclid(TAU));
|
||||
let (a0, a1) = (angle(t0)?, angle(t1)?);
|
||||
let sweep = (a1 - a0).rem_euclid(TAU);
|
||||
Some([a0, a0 + if sweep <= 1e-12 { TAU } else { sweep }])
|
||||
}
|
||||
|
||||
impl Face {
|
||||
|
||||
@@ -26,6 +26,8 @@ fn invalid(m: impl std::fmt::Display) -> KernelError {
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct AEdge {
|
||||
pub curve: Curve,
|
||||
/// The NURBS form the edge's parameter is on, as the record's `Edge::nurbs`.
|
||||
pub nurbs: Option<Curve>,
|
||||
pub interval: [f64; 2],
|
||||
pub vertices: [u32; 2],
|
||||
pub tolerance: f64,
|
||||
@@ -118,6 +120,7 @@ impl Assembly {
|
||||
.enumerate()
|
||||
.map(|(i, e)| AEdge {
|
||||
curve: e.curve.clone(),
|
||||
nurbs: e.nurbs.clone(),
|
||||
interval: e.interval,
|
||||
vertices: e.vertices,
|
||||
tolerance: e.tolerance,
|
||||
@@ -144,7 +147,7 @@ impl Assembly {
|
||||
|
||||
/// A new edge on `curve` over `interval`, from `a` to `b`.
|
||||
pub(crate) fn edge(&mut self, curve: Curve, interval: [f64; 2], a: u32, b: u32) -> Result<u32> {
|
||||
self.edges.push(AEdge { curve, interval, vertices: [a, b], tolerance: self.tolerance, source: None, of: Vec::new() });
|
||||
self.edges.push(AEdge { curve, nurbs: None, interval, vertices: [a, b], tolerance: self.tolerance, source: None, of: Vec::new() });
|
||||
index(self.edges.len() - 1)
|
||||
}
|
||||
|
||||
@@ -218,7 +221,7 @@ impl Assembly {
|
||||
}
|
||||
}
|
||||
let edges = self.edges.iter().map(|a| Edge {
|
||||
nurbs: None,
|
||||
nurbs: a.nurbs.clone(),
|
||||
curve: a.curve.clone(),
|
||||
interval: a.interval,
|
||||
vertices: a.vertices,
|
||||
@@ -260,7 +263,7 @@ impl Assembly {
|
||||
let e = self.edges.get(u.edge as usize).ok_or_else(|| invalid("edge reference"))?;
|
||||
Ok(EdgeUse {
|
||||
edge: u.edge,
|
||||
curve: &e.curve,
|
||||
curve: e.nurbs.as_ref().unwrap_or(&e.curve),
|
||||
interval: e.interval,
|
||||
forward: u.forward,
|
||||
tolerance: e.tolerance,
|
||||
|
||||
@@ -119,7 +119,7 @@ impl Faces {
|
||||
/// The edge's unit tangent along its interval (start to end vertex) at its start or end.
|
||||
pub(crate) fn tangent(&self, e: usize, at_end: bool) -> Option<Vec3> {
|
||||
let edge = self.rec.edges.get(e)?;
|
||||
curve_tangent(&edge.curve, if at_end { edge.interval[1] } else { edge.interval[0] })
|
||||
curve_tangent(edge.parameters(), if at_end { edge.interval[1] } else { edge.interval[0] })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -870,7 +870,7 @@ impl Body {
|
||||
b.add(v3(v.point));
|
||||
}
|
||||
for e in &self.record.edges {
|
||||
curve_hull(&e.curve, e.interval, &mut b);
|
||||
curve_hull(e.parameters(), e.interval, &mut b);
|
||||
}
|
||||
b
|
||||
}
|
||||
|
||||
@@ -625,14 +625,14 @@ fn patch_body(body: &Body, record: &b::Brep, chain: &[(usize, bool)], surface: b
|
||||
}
|
||||
}
|
||||
let given = match pcurves {
|
||||
Pcurves::Projected(p) => Some(p.pcurve(&edge.curve, edge.interval).ok_or_else(|| {
|
||||
Pcurves::Projected(p) => Some(p.pcurve(edge.parameters(), edge.interval).ok_or_else(|| {
|
||||
KernelError::Invalid(
|
||||
"not supported yet: a fitted patch on a circular, elliptic or traced edge (it has no exact pcurve on the fit)".into(),
|
||||
)
|
||||
})?),
|
||||
Pcurves::Computed => None,
|
||||
};
|
||||
edges.push(PartEdge { curve: edge.curve.clone(), interval: edge.interval, vertices: ends });
|
||||
edges.push(PartEdge { curve: edge.parameters().clone(), interval: edge.interval, vertices: ends });
|
||||
uses.push(PartUse { edge: k as u32, forward, given });
|
||||
}
|
||||
if !ccw {
|
||||
|
||||
@@ -345,7 +345,7 @@ fn shell(w: &mut W, b: &Brep, si: u32, paint: &HashMap<usize, [f32; 3]>) -> Resu
|
||||
let mut ep = vec![edges.len().to_string()];
|
||||
for &e in &edges {
|
||||
let edge = b.edges.get(e as usize).ok_or_else(|| failed(format!("edge {e}: no such edge")))?;
|
||||
let c = topology::curve(&edge.curve).map_err(|m| failed(format!("edge {e}: {m}")))?;
|
||||
let c = topology::curve(edge.parameters()).map_err(|m| failed(format!("edge {e}: {m}")))?;
|
||||
let [sv, tv] = edge.vertices.map(|v| vidx.get(&v).copied().unwrap_or(0));
|
||||
let (pa, pb) = (verts.get(sv).copied().unwrap_or([0.0; 3]), verts.get(tv).copied().unwrap_or([0.0; 3]));
|
||||
let cd = curve(w, &c, edge.interval, pa, pb, sv == tv)?;
|
||||
|
||||
@@ -56,7 +56,7 @@ pub fn meeting_edges(a: &Body, a_face: Option<usize>, b: &Body, b_face: Option<u
|
||||
let (Some(e), Some(points)) = (at.and_then(|k| record.edges.get(k)), at.and_then(|k| mesh.edges.get(k))) else {
|
||||
return Err(failed("history", format!("edge {i} is not in the imprinted body")));
|
||||
};
|
||||
out.push(MeetingEdge { curve: e.curve.clone(), interval: e.interval, points: points.clone() });
|
||||
out.push(MeetingEdge { curve: e.parameters().clone(), interval: e.interval, points: points.clone() });
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
@@ -149,7 +149,8 @@ fn pieces(asm: &Assembly, lp: &[AUse], n: Vec3) -> Result<Vec<Piece>> {
|
||||
let e = asm.edges.get(u.edge as usize).ok_or_else(|| fail("edge"))?;
|
||||
let (s, t) = asm.ends(u).ok_or_else(|| fail("edge"))?;
|
||||
let (a, b) = (asm.point(s).ok_or_else(|| fail("vertex"))?, asm.point(t).ok_or_else(|| fail("vertex"))?);
|
||||
let [t0, t1] = e.interval;
|
||||
// The arc's angles, on the circle itself (its NURBS form, if any, is parameterised otherwise).
|
||||
let [t0, t1] = solidcraft_geom::brep::curve_interval(&e.curve, e.nurbs.as_ref(), e.interval).ok_or_else(|| fail("edge"))?;
|
||||
match &e.curve {
|
||||
Curve::Line { .. } => Ok(Piece { a, b, arc: None }),
|
||||
Curve::Circle { frame, .. } => {
|
||||
|
||||
@@ -152,7 +152,7 @@ const MAX_EDGE_SEGS: usize = 1000;
|
||||
pub fn edge_path(body: &Body, edge: usize) -> Result<Vec<PathSeg>> {
|
||||
let record = crate::native::brep_record(body)?;
|
||||
let e = record.edges.get(edge).ok_or_else(|| KernelError::Invalid(format!("the body has no edge {edge}")))?;
|
||||
curve_path(&e.curve, e.interval)
|
||||
curve_path(e.parameters(), e.interval)
|
||||
}
|
||||
|
||||
/// The sketch segments `segs` on `plane` projected along `direction` (pointing from them toward
|
||||
|
||||
@@ -31,7 +31,7 @@ impl<'a> Run<'a> {
|
||||
let e = record.edges.get(edge)?;
|
||||
let at = |v: u32| record.vertices.get(v as usize).map(|v| Vec3::new(v.point[0], v.point[1], v.point[2]));
|
||||
let (start, end) = (at(e.vertices[0])?, at(e.vertices[1])?);
|
||||
Some(Run { curve: &e.curve, interval: e.interval, forward, ends: if forward { [start, end] } else { [end, start] } })
|
||||
Some(Run { curve: e.parameters(), interval: e.interval, forward, ends: if forward { [start, end] } else { [end, start] } })
|
||||
}
|
||||
|
||||
/// The parameters it runs from and to.
|
||||
|
||||
@@ -351,7 +351,8 @@ fn normal_at(surface: &SurfaceEval, forward: bool, p: Vec3) -> Option<Vec3> {
|
||||
/// one span per quarter turn or less).
|
||||
fn base_curve(r: &Brep, e: usize, forward: bool) -> Result<CurveN<4>> {
|
||||
let edge = r.edges.get(e).ok_or_else(|| KernelError::Invalid("an edge reference".into()))?;
|
||||
let [t0, t1] = edge.interval;
|
||||
// On the curve itself: a conic's angles (its NURBS form, if any, is parameterised otherwise).
|
||||
let [t0, t1] = edge.curve_interval().ok_or_else(|| KernelError::Invalid("an edge's NURBS form cannot be evaluated".into()))?;
|
||||
let c = match &edge.curve {
|
||||
b::Curve::Line { start, end } => {
|
||||
let at = |t: f64| [start[0] + (end[0] - start[0]) * t, start[1] + (end[1] - start[1]) * t, start[2] + (end[2] - start[2]) * t, 1.0];
|
||||
|
||||
@@ -1 +1 @@
|
||||
deec9cb312b68a92d1b018cece65e1d86643fee6
|
||||
26a4b7b6255a0e4db29fdd81cb4985bb9a9a1a8f
|
||||
|
||||
@@ -417,6 +417,19 @@ fn surfaces(flat: View, n_faces: usize) -> Res<Vec<(b::Surface, Option<b::Surfac
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// A circle's frame turned (y reversed) where needed so that its angle grows along `form`, the
|
||||
/// NURBS the edge runs on over `range`: what [`b::Edge::curve_interval`] reads.
|
||||
fn along(f: b::Frame, form: &b::Curve, range: [f64; 2]) -> Res<b::Frame> {
|
||||
let eval = b::eval::CurveEval::new(form).map_err(|e| invalid(format!("circle NURBS form: {e}")))?;
|
||||
let [t0, t1] = range;
|
||||
let (p, q) = (eval.point(t0), eval.point(t0 + 1e-3 * (t1 - t0)));
|
||||
let (Some(p), Some(q)) = (p, q) else { return Err(invalid("circle NURBS form")) };
|
||||
let (o, x, y) = (Vec3::from(f.origin), Vec3::from(f.x_axis), Vec3::from(f.y_axis));
|
||||
let angle = |s: Vec3| (s - o).dot(y).atan2((s - o).dot(x));
|
||||
let step = (angle(q) - angle(p)).rem_euclid(TAU);
|
||||
Ok(if step > std::f64::consts::PI { b::Frame { y_axis: [-f.y_axis[0], -f.y_axis[1], -f.y_axis[2]], ..f } } else { f })
|
||||
}
|
||||
|
||||
/// A NURBS curve: full knots, cartesian control points and weights.
|
||||
struct Nurbs {
|
||||
knots: Vec<f64>,
|
||||
@@ -559,7 +572,8 @@ fn curves(flat: View, n_edges: usize) -> Res<(Vec<(b::Curve, Option<b::Curve>, [
|
||||
// A circle with its NURBS form is parameterised on it: its range and the trims
|
||||
// on it are that NURBS's, so the trims carry over as they are.
|
||||
if forms.get(e) == Some(&1) {
|
||||
(b::Curve::Circle { frame: f, radius }, Some(next_nurbs()?.record()), r, Some([1.0, 0.0]))
|
||||
let form = next_nurbs()?.record();
|
||||
(b::Curve::Circle { frame: along(f, &form, r)?, radius }, Some(form), r, Some([1.0, 0.0]))
|
||||
} else {
|
||||
(b::Curve::Circle { frame: f, radius }, None, r, None)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user