mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-06 03:51:08 +00:00
geom: a face's loops lie in one turn of its periodic surface
form::extent unwrapped each loop of a face from its own first point, so a hole beside the seam (a cross bore through a rod at the rod's seam angle) came out a period away from the outer loop. The box over both was more than a turn, cut to one turn from the hole: the face's NURBS form ran over quarters 3..7 while its seam uses lay at 4 and 8, and SMLib found the re-imported rod's edge ends outside its face domain (SmBrep #70-#72). Each loop after the first now goes the whole periods along that keep the face's box smallest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0113gDVmStT5tCGKW9Fxgc5K
This commit is contained in:
@@ -319,12 +319,18 @@ const EXTENT_POINTS: usize = 32;
|
||||
|
||||
/// The parameter box of a face on `label` over its boundary: each loop's points (`loops`, in
|
||||
/// order along each loop) projected onto the label, a periodic parameter unwrapped from one point
|
||||
/// to the next. What the face's NURBS form ([`surface`]) is built over; no pcurve is involved.
|
||||
/// to the next, and each loop after the first a whole number of periods along where that keeps
|
||||
/// the box smallest. What the face's NURBS form ([`surface`]) is built over; no pcurve is
|
||||
/// involved.
|
||||
pub fn extent(label: &Surface, loops: &[Vec<(&Curve, [f64; 2], bool)>]) -> Option<[[f64; 2]; 2]> {
|
||||
let eval = super::eval::SurfaceEval::new(label).ok()?;
|
||||
let period = eval.domain().period;
|
||||
let (mut lo, mut hi) = ([f64::INFINITY; 2], [f64::NEG_INFINITY; 2]);
|
||||
for l in loops {
|
||||
// A loop is unwrapped from its own first point, so it can come out whole periods away
|
||||
// from the loops before it (a hole beside the seam projects to the seam's other side),
|
||||
// and a box over both is more than a turn wide.
|
||||
let (mut l_lo, mut l_hi) = ([f64::INFINITY; 2], [f64::NEG_INFINITY; 2]);
|
||||
let mut prev: Option<[f64; 2]> = None;
|
||||
for (c, [t0, t1], forward) in l {
|
||||
let curve = super::eval::CurveEval::new(c).ok()?;
|
||||
@@ -343,8 +349,8 @@ pub fn extent(label: &Surface, loops: &[Vec<(&Curve, [f64; 2], bool)>]) -> Optio
|
||||
let pole = eval.pole(q);
|
||||
for d in 0..2 {
|
||||
if !pole.get(d).copied().unwrap_or(false) {
|
||||
lo[d] = lo[d].min(q[d]);
|
||||
hi[d] = hi[d].max(q[d]);
|
||||
l_lo[d] = l_lo[d].min(q[d]);
|
||||
l_hi[d] = l_hi[d].max(q[d]);
|
||||
}
|
||||
}
|
||||
if !pole.iter().any(|p| *p) {
|
||||
@@ -352,6 +358,23 @@ pub fn extent(label: &Surface, loops: &[Vec<(&Curve, [f64; 2], bool)>]) -> Optio
|
||||
}
|
||||
}
|
||||
}
|
||||
for d in 0..2 {
|
||||
if !(l_lo[d] <= l_hi[d]) {
|
||||
continue;
|
||||
}
|
||||
let shift = match period.get(d) {
|
||||
Some(Some(per)) if lo[d] <= hi[d] => {
|
||||
// The whole periods around the ones that bring the loop's middle to the box's,
|
||||
// the one giving the smallest box.
|
||||
let k = ((lo[d] + hi[d] - l_lo[d] - l_hi[d]) / (2.0 * per)).round();
|
||||
let span = |k: f64| hi[d].max(l_hi[d] + k * per) - lo[d].min(l_lo[d] + k * per);
|
||||
[k, k - 1.0, k + 1.0].into_iter().min_by(|a, b| span(*a).total_cmp(&span(*b))).unwrap_or(k) * per
|
||||
}
|
||||
_ => 0.0,
|
||||
};
|
||||
lo[d] = lo[d].min(l_lo[d] + shift);
|
||||
hi[d] = hi[d].max(l_hi[d] + shift);
|
||||
}
|
||||
}
|
||||
// At most one turn round a periodic direction; a boundary that never goes round it (a face
|
||||
// bounded by its seam alone) covers the whole turn from there.
|
||||
|
||||
@@ -802,3 +802,30 @@ fn analytic_records_are_unbounded_with_independent_frames() {
|
||||
assert!(radius(f64::NAN).is_err());
|
||||
assert!(surface(&Surface::Torus { frame: a_frame(), major_radius: 1., minor_radius: 2. }, 0, &mut Budget(0)).is_err());
|
||||
}
|
||||
|
||||
/// A cylinder face round its axis with a hole beside its seam: the seam at 350 degrees (so its
|
||||
/// loop projects from 350 degrees on), the hole at 15..25 degrees (which on its own projects a
|
||||
/// turn below). The face's box is the outer loop's turn with the hole in it; the hole's own
|
||||
/// placement made a box from 15 degrees that cut the turn short and left the seam outside it
|
||||
/// (#4598: a bored rod read back with its bore's pcurve a period off its face).
|
||||
#[test]
|
||||
fn a_hole_beside_the_seam_lies_in_its_face_turn() {
|
||||
let (r, h) = (20.0f64, 60.0);
|
||||
let cylinder = Surface::Cylinder { frame: Frame { origin: [0.; 3], x_axis: [1., 0., 0.], y_axis: [0., 1., 0.] }, radius: r };
|
||||
let s = -10f64.to_radians();
|
||||
let at = |a: f64, z: f64| [r * a.cos(), r * a.sin(), z];
|
||||
let ring = |z: f64| Curve::Circle { frame: Frame { origin: [0., 0., z], x_axis: [s.cos(), s.sin(), 0.], y_axis: [-s.sin(), s.cos(), 0.] }, radius: r };
|
||||
let (bottom, top) = (ring(0.), ring(h));
|
||||
let seam = Curve::Line { start: at(s, 0.), end: at(s, h) };
|
||||
let (a0, a1) = (15f64.to_radians(), 25f64.to_radians());
|
||||
let hole =
|
||||
Curve::Spline { knots: vec![0., 0., 1., 2., 3., 4., 4.], points: vec![at(a0, 25.), at(a1, 25.), at(a1, 35.), at(a0, 35.), at(a0, 25.)] };
|
||||
let outer = vec![(&bottom, [0., TAU], true), (&seam, [0., 1.], true), (&top, [0., TAU], false), (&seam, [0., 1.], false)];
|
||||
let inner = vec![(&hole, [0., 4.], true)];
|
||||
let [u, v] = form::extent(&cylinder, &[outer, inner]).expect("an extent");
|
||||
assert!((u[1] - u[0] - TAU).abs() < 1e-9, "one turn: {u:?}");
|
||||
assert!((u[0].rem_euclid(TAU) - s.rem_euclid(TAU)).abs() < 1e-9, "from the seam: {u:?}");
|
||||
let hole_u = a0 + ((u[0] - a0) / TAU).ceil() * TAU;
|
||||
assert!(hole_u > u[0] && hole_u + (a1 - a0) < u[1], "the hole in the turn: {u:?}");
|
||||
assert!(v[0].abs() < 1e-9 && (v[1] - h).abs() < 1e-9, "{v:?}");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user