smlib: a horn or spindle torus face comes out as its NURBS form

The record's torus label is a ring torus (major > minor; the validator
refuses any other). SMLib keeps horn and spindle tori: a half disc revolved
about a line through its rim's end is a horn torus, its major radius a
rounding below its minor, and the export wrote it as a torus label the
record then refused (rig sweeps/4007, "ring torus radii"). Such a face is
now its NURBS form, unlabelled, which is its geometry in any case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
jyviko
2026-10-26 04:49:27 -04:00
parent 1df330c79b
commit 5aeff43f85
2 changed files with 25 additions and 0 deletions
+7
View File
@@ -404,6 +404,13 @@ fn surfaces(flat: View, n_faces: usize) -> Res<Vec<(b::Surface, Option<b::Surfac
};
// 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 };
// The record's torus is a ring torus. SMLib keeps horn and spindle tori too (a half disc
// revolved about a line through its rim's end, its tube touching the axis, the major
// radius a rounding below the minor): such a face is its NURBS form, unlabelled.
let (surface, form) = match (surface, form) {
(b::Surface::Torus { major_radius, minor_radius, .. }, Some(n)) if !(major_radius > minor_radius) => (n, None),
other => other,
};
out.push((surface, form));
}
Ok(out)
+18
View File
@@ -49,6 +49,24 @@ fn revolutions_whole_and_partial() {
assert_close(solid_volume(&b, "quarter ring"), PI / 2.0 * 7.0 * 4.0, 1e-6, "quarter ring");
}
/// A half disc of radius 14 centred 14 off the axis, its rim outward, three quarters of a turn
/// (rig sweeps/4007): a horn torus, which SMLib gives a major radius a rounding below its minor.
/// The record's torus is a ring torus, so the face comes out as its NURBS form, unlabelled, and
/// the record validates. Pappus: 3/4 turn · 2π (14 + 4·14 / 3π) · π 14² / 2.
#[test]
fn a_horn_torus_comes_out_as_its_nurbs() {
let half = vec![
Curve::Arc { center: [14.0, 0.0, 0.0], axis: [0.0, -1.0, 0.0], reference: [1.0, 0.0, 0.0], radius: 14.0, start: -PI / 2.0, end: PI / 2.0 },
Curve::Line { start: [14.0, 0.0, 14.0], end: [14.0, 0.0, -14.0] },
];
let b = Brep::revolve(&half, [0.0; 3], [0.0, 0.0, 1.0], 1.5 * PI, true).unwrap();
let want = 1.5 * PI * (14.0 + 56.0 / (3.0 * PI)) * PI * 98.0;
assert_close(solid_volume(&b, "horn torus"), want, 1e-6, "horn torus");
let record = b.to_native().unwrap();
record.validate().unwrap();
assert!(record.faces.iter().all(|f| !matches!(f.surface, solidcraft_geom::brep::Surface::Torus { .. })), "a torus label on a horn torus");
}
#[test]
fn sweeps_lofts_and_screws() {
let disc = [circle([5.0, 0.0, 0.0], 2.0)];