solidcraft-smlib: a horn torus to rounding is no ring torus

A revolved circle whose centre is as far from the axis as its radius (a horn torus, its
tube touching the axis) measured its major radius a few ulps above its minor from the
profile's control points, became SMLib's ring torus, and SMLib refused it
(SM_ERR_INVALID_INPUT: no ring torus of that NURBS). A ring torus now needs its tube
clear of the axis by more than rounding (1e-9 of the radius, the slack the same test
already takes for the circle's plane); a horn torus goes as its rational B-spline.

Test: a revolved unit circle 1.5 from the axis is a ring torus; at 1, 1 + 4 ulp and 0.5
it stays a revolution (develop calls the first two ring tori).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKMoxpM7Vuv8Rb6E3K2
This commit is contained in:
jyviko
2026-10-27 16:45:52 -04:00
committed by Irakli Kourtis
parent 9da3174bb7
commit 31b9410f56
2 changed files with 33 additions and 1 deletions
+3 -1
View File
@@ -331,8 +331,10 @@ impl Revolution {
let v = [v[0].max(-FRAC_PI_2), v[1].min(FRAC_PI_2)];
return Ok((b::Surface::Sphere { frame: frame(foot), radius }, [angle, v]));
}
// A ring torus only (SMLib has no other): a tube that reaches the axis to rounding (a
// horn torus, major = minor) is none.
let major = off.dot(x);
if !(major > radius) {
if !(major > radius + slack) {
return Err(NOT_ANALYTIC);
}
let torus = b::Surface::Torus { frame: frame(foot), major_radius: major, minor_radius: radius };
+30
View File
@@ -237,3 +237,33 @@ fn arcs_past_a_turn_come_a_turn_back() {
let line = Curve::Line { start: [0.0; 3], end: [1.0, 0.0, 0.0] };
assert_eq!(turned(&line, [7.0, 9.0]), ([7.0, 9.0], 0.0));
}
/// A revolved circle is SMLib's ring torus only where its tube stays off the axis: a tube that
/// reaches it to rounding (a horn torus, its centre as far from the axis as its radius, give or
/// take an ulp) and one across it (a spindle) go as their rational B-splines.
#[test]
fn only_a_tube_clear_of_its_axis_is_a_ring_torus() {
let ring = |major: f64| {
let frame = solidcraft_geom::brep::Frame { origin: [major, 0.0, 0.0], x_axis: [1.0, 0.0, 0.0], y_axis: [0.0, 0.0, 1.0] };
let (profile, _) = solidcraft_geom::brep::form::curve(&Curve::Circle { frame, radius: 1.0 }, [-1.0, 1.0]).unwrap();
let identity = [[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 1.0]];
let surface =
Surface::PlacedRevolution { profile: Box::new(profile), origin: [0.0; 3], axis: [0.0, 0.0, 1.0], matrix: identity, forward: false };
let face = Face { surface, nurbs: None, domain: [[0.0, 1.0], [-0.5, 0.5]], loops: Vec::new() };
let record = Record {
version: b::VERSION,
tolerance: 1e-6,
regions: Vec::new(),
shells: Vec::new(),
faces: vec![face],
edges: Vec::new(),
vertices: Vec::new(),
};
let made = analytic::analytic(&record).unwrap();
made.faces[0].surface.clone()
};
assert!(matches!(ring(1.5), Surface::Torus { .. }));
for major in [1.0, 1.0 + 4.0 * f64::EPSILON, 0.5] {
assert!(matches!(ring(major), Surface::PlacedRevolution { .. }), "major {major}");
}
}