mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 02:25:55 +00:00
STEP import: a loop jumps at a pole to within the tolerance, as its pcurves closed it
The pcurves close a face's loop through a pole to within the face's tolerance (a point where crossing half a period moves less than that): there the periodic parameter is free and the loop jumps across. The reader's check that no loop crosses a seam without a seam edge took a pole only exactly (1e-9 relative), so it refused the loops the pcurves had closed near one. Poppy Humanoid's nearly round pin ends (a spindle torus of major radius 0.005, minor 0.345) have their apex vertex at the tube's top, 3.6e-5 mm above the true pole: their meridians end 1.7e-6 rad short of it, and 13 faces were refused. Both now use one test, SurfaceEval::pole_within. Test: a point 2e-7 mm from a sphere's axis is a pole within 1e-6 mm, not within 1e-7. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LUXeKMoxpM7Vuv8Rb6E3K2
This commit is contained in:
@@ -241,6 +241,26 @@ impl SurfaceEval {
|
||||
let (a, b) = (su.len(), sv.len());
|
||||
[a <= 1e-9 * b.max(1e-12), b <= 1e-9 * a.max(1e-12)]
|
||||
}
|
||||
|
||||
/// [`Self::pole`], or within `reach` mm of one: moving along a direction across half its
|
||||
/// period (or its whole range) moves the point no more than `reach` (a boundary point that
|
||||
/// lies at a pole to within a tolerance, where its periodic parameter is free).
|
||||
pub fn pole_within(&self, uv: [f64; 2], reach: f64) -> [bool; 2] {
|
||||
let mut out = self.pole(uv);
|
||||
let Some((_, su, sv)) = self.derivs(uv[0], uv[1]) else { return out };
|
||||
let dom = self.domain();
|
||||
for (d, speed) in [su.len(), sv.len()].into_iter().enumerate() {
|
||||
let span = match (dom.period.get(d).copied().flatten(), dom.range.get(d).copied().flatten()) {
|
||||
(Some(period), _) => 0.5 * period,
|
||||
(None, Some((a, b))) => (b - a).abs(),
|
||||
(None, None) => continue,
|
||||
};
|
||||
if let Some(flag) = out.get_mut(d) {
|
||||
*flag |= speed * span <= reach;
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
/// A tensor spline's rows (`rows[i][j]`, i along u) as the evaluator's u-fastest array.
|
||||
|
||||
@@ -529,19 +529,7 @@ impl On {
|
||||
/// parameter by half its period (or across its range) moves the point by no more than `reach`
|
||||
/// at its speed there. There the parameter is as free as at the pole itself.
|
||||
fn pole(&self, uv: [f64; 2], reach: f64) -> [bool; 2] {
|
||||
let mut out = self.surface.pole(uv);
|
||||
let Some((_, su, sv)) = self.surface.derivs(uv[0], uv[1]) else { return out };
|
||||
for (d, speed) in [su.len(), sv.len()].into_iter().enumerate() {
|
||||
let span = match (self.dom.period.get(d).copied().flatten(), self.dom.range.get(d).copied().flatten()) {
|
||||
(Some(period), _) => 0.5 * period,
|
||||
(None, Some((a, b))) => (b - a).abs(),
|
||||
(None, None) => continue,
|
||||
};
|
||||
if let Some(flag) = out.get_mut(d) {
|
||||
*flag |= speed * span <= reach;
|
||||
}
|
||||
}
|
||||
out
|
||||
self.surface.pole_within(uv, reach)
|
||||
}
|
||||
|
||||
/// The loop's uses moved by whole periods so each starts where the one before it ends (in
|
||||
|
||||
@@ -844,3 +844,16 @@ fn a_face_box_reaches_its_boundary_extremes() {
|
||||
let [_, v] = form::extent(&cylinder, &[vec![(&cut, [0.1, 0.1 + TAU], true)]]).expect("an extent");
|
||||
assert!((v[0] - 10.0).abs() < 1e-9 && (v[1] - 50.0).abs() < 1e-9, "{v:?}");
|
||||
}
|
||||
|
||||
/// A point next to a sphere's pole is a pole to within the reach it is given: crossing half a
|
||||
/// turn of longitude there moves it about π times its distance from the axis, and no further.
|
||||
#[test]
|
||||
fn a_point_beside_a_pole_is_one_within_its_reach() {
|
||||
let sphere = eval::SurfaceEval::new(&Surface::Sphere { frame: a_frame(), radius: 2. }).unwrap();
|
||||
let near = [0.3, FRAC_PI_2 - 1e-7];
|
||||
// 2e-7 mm from the axis: half a turn moves it 2π·1e-7.
|
||||
assert_eq!(sphere.pole(near), [false, false]);
|
||||
assert_eq!(sphere.pole_within(near, 1e-6), [true, false]);
|
||||
assert_eq!(sphere.pole_within(near, 1e-7), [false, false]);
|
||||
assert_eq!(sphere.pole_within([0.3, 0.2], 1e-3), [false, false]);
|
||||
}
|
||||
|
||||
@@ -1104,10 +1104,11 @@ impl<'b, 'a> Builder<'b, 'a> {
|
||||
// does not wrap: a loop around a closed spline surface without a seam edge jumps across
|
||||
// its domain.
|
||||
let natural = surface.natural();
|
||||
// At a pole (where the surface collapses to a point across a direction) a loop may jump
|
||||
// in that direction: the seam's two uses meet there a period apart.
|
||||
// At a pole (where the surface collapses to a point across a direction, to within the
|
||||
// tolerance, as the pcurves closed the loop there) a loop may jump in that direction: the
|
||||
// seam's two uses meet there a period apart.
|
||||
let poles = SurfaceEval::new(&surface).ok();
|
||||
let at_pole = |uv: [f64; 2]| poles.as_ref().is_some_and(|e| e.pole(uv).iter().any(|p| *p));
|
||||
let at_pole = |uv: [f64; 2]| poles.as_ref().is_some_and(|e| e.pole_within(uv, tol).iter().any(|p| *p));
|
||||
for (l, pc) in edge_loops.iter().zip(&pcs) {
|
||||
let mut ends = Vec::with_capacity(l.len());
|
||||
for (&(_, forward), c) in l.iter().zip(pc) {
|
||||
|
||||
Reference in New Issue
Block a user