mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-05 11:35:55 +00:00
brep: cones narrowing along z, and a linear duplicate-shell check
SMLib makes narrowing cones with a negative half angle; schema 4 now takes them as they are (r(v) = r0 + v tan a already covers them), with the natural v range below the apex. The check that no region lists a shell twice was quadratic per region (about 1e9 comparisons at the caps); it now remembers the last region that listed each shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QWhMwdqJYQKQq8RCLSL1LT
This commit is contained in:
@@ -198,7 +198,8 @@ pub enum Surface {
|
||||
/// `CYLINDRICAL_SURFACE`).
|
||||
Cylinder { frame: Frame, radius: f64 },
|
||||
/// `origin + v z + r(v) (cos u x + sin u y)`, `r(v) = radius_at_origin + v tan(half_angle)`,
|
||||
/// v ≥ the apex's height (STEP `CONICAL_SURFACE`).
|
||||
/// on the side of the apex where r(v) ≥ 0: above it for a positive half angle, below it for
|
||||
/// a negative one (a cone narrowing along z, as SMLib makes them) (STEP `CONICAL_SURFACE`).
|
||||
Cone { frame: Frame, radius_at_origin: f64, half_angle: f64 },
|
||||
/// `origin + radius (cos v (cos u x + sin u y) + sin v z)`: u the longitude, v the latitude
|
||||
/// in [-π/2, π/2] (STEP `SPHERICAL_SURFACE`).
|
||||
@@ -319,7 +320,9 @@ fn surface_natural(s: &Surface) -> [Natural; 2] {
|
||||
Surface::Plane { .. } | Surface::PlaneThrough { .. } => [UNBOUNDED; 2],
|
||||
Surface::Cylinder { .. } => [Natural::Periodic, UNBOUNDED],
|
||||
Surface::Cone { radius_at_origin, half_angle, .. } => {
|
||||
[Natural::Periodic, Natural::Range(-radius_at_origin / half_angle.tan(), f64::INFINITY)]
|
||||
let apex = -radius_at_origin / half_angle.tan();
|
||||
let v = if *half_angle > 0. { Natural::Range(apex, f64::INFINITY) } else { Natural::Range(f64::NEG_INFINITY, apex) };
|
||||
[Natural::Periodic, v]
|
||||
}
|
||||
Surface::Sphere { .. } => [Natural::Periodic, Natural::Range(-FRAC_PI_2, FRAC_PI_2)],
|
||||
Surface::Torus { .. } => [Natural::Periodic; 2],
|
||||
@@ -432,7 +435,7 @@ fn surface(s: &Surface, depth: usize, budget: &mut Budget) -> Result<()> {
|
||||
Surface::Cone { frame: f, radius_at_origin, half_angle } => {
|
||||
frame(f, budget)?;
|
||||
numbers([*radius_at_origin, *half_angle])?;
|
||||
require(*radius_at_origin >= 0. && *half_angle >= 1e-12 && *half_angle < std::f64::consts::FRAC_PI_2, "cone dimensions")
|
||||
require(*radius_at_origin >= 0. && half_angle.abs() >= 1e-12 && half_angle.abs() < std::f64::consts::FRAC_PI_2, "cone dimensions")
|
||||
}
|
||||
Surface::Torus { frame: f, major_radius, minor_radius } => {
|
||||
frame(f, budget)?;
|
||||
|
||||
@@ -322,6 +322,13 @@ fn one_defect_fixtures_are_refused() {
|
||||
b.vertices.push(Vertex { point: [9.; 3], tolerance: 1e-7 });
|
||||
b
|
||||
}),
|
||||
("shell twice in a region", "shell listed twice in a region", {
|
||||
// The void lists its one shell twice: still within the shell count.
|
||||
let mut b = hollow();
|
||||
let void = b.regions.last_mut().unwrap();
|
||||
void.shells.push(void.shells[0]);
|
||||
b
|
||||
}),
|
||||
("void infinite region", "the infinite region is a void", {
|
||||
let mut b = sheet();
|
||||
b.regions[0].void = true;
|
||||
@@ -472,9 +479,17 @@ fn analytic_records_are_unbounded_with_independent_frames() {
|
||||
let surfaces = [
|
||||
Surface::Cylinder { frame: f.clone(), radius: 2. },
|
||||
Surface::Cone { frame: f.clone(), radius_at_origin: 2., half_angle: 0.2 },
|
||||
Surface::Cone { frame: f.clone(), radius_at_origin: 2., half_angle: -0.2 },
|
||||
Surface::Sphere { frame: f.clone(), radius: 2. },
|
||||
Surface::Torus { frame: f.clone(), major_radius: 4., minor_radius: 1. },
|
||||
];
|
||||
// A cone narrowing along z (a negative half angle, as SMLib makes them) lies below its apex.
|
||||
let apex = 2. / 0.2f64.tan();
|
||||
let narrowing = Surface::Cone { frame: f.clone(), radius_at_origin: 2., half_angle: -0.2 };
|
||||
assert_eq!(surface_natural(&narrowing)[1], Natural::Range(f64::NEG_INFINITY, apex));
|
||||
for flat in [0., 1e-13, -1e-13, std::f64::consts::FRAC_PI_2, -std::f64::consts::FRAC_PI_2] {
|
||||
assert!(surface(&Surface::Cone { frame: f.clone(), radius_at_origin: 2., half_angle: flat }, 0, &mut Budget(0)).is_err());
|
||||
}
|
||||
for s in surfaces {
|
||||
surface(&s, 0, &mut Budget(0)).unwrap();
|
||||
let encoded = serde_json::to_string(&s).unwrap();
|
||||
|
||||
@@ -45,10 +45,15 @@ pub(super) fn check(b: &Brep) -> Result<()> {
|
||||
let mut outer_of = vec![0u32; b.shells.len()];
|
||||
let infinite = b.regions.first().ok_or(Invalid("region count"))?;
|
||||
require(!infinite.void, "the infinite region is a void")?;
|
||||
// The last region (numbered from 1) that listed each shell: a repeat within one region shows
|
||||
// in one lookup, so the check stays linear however many shells and regions there are.
|
||||
let mut last_region = vec![0usize; b.shells.len()];
|
||||
for (r, region) in b.regions.iter().enumerate() {
|
||||
require(region.shells.len() <= b.shells.len(), "region shells")?;
|
||||
for (k, &s) in region.shells.iter().enumerate() {
|
||||
require(!region.shells.get(..k).is_some_and(|before| before.contains(&s)), "shell listed twice in a region")?;
|
||||
let last = last_region.get_mut(s as usize).ok_or(Invalid("region shell reference"))?;
|
||||
require(*last != r + 1, "shell listed twice in a region")?;
|
||||
*last = r + 1;
|
||||
let n = listed.get_mut(s as usize).ok_or(Invalid("region shell reference"))?;
|
||||
*n += 1;
|
||||
if r > 0 && k == 0 {
|
||||
|
||||
+2
-2
@@ -126,7 +126,7 @@ Natural normals are ∂S/∂u × ∂S/∂v.
|
||||
| `plane` {origin, u_axis, v_axis} | origin + u u_axis + v v_axis | unbounded | unbounded |
|
||||
| `plane_through` {origin, u_point, v_point} | origin + u (u_point − origin) + v (v_point − origin) | unbounded | unbounded |
|
||||
| `cylinder` {frame, radius} | origin + v z + r (cos u x + sin u y) | angle, periodic | height, unbounded |
|
||||
| `cone` {frame, radius_at_origin, half_angle} | origin + v z + r(v) (cos u x + sin u y), r(v) = radius_at_origin + v tan(half_angle) | angle, periodic | height, from the apex up |
|
||||
| `cone` {frame, radius_at_origin, half_angle} | origin + v z + r(v) (cos u x + sin u y), r(v) = radius_at_origin + v tan(half_angle) | angle, periodic | height, on the side of the apex where r(v) ≥ 0 (a negative half angle narrows along z) |
|
||||
| `sphere` {frame, radius} | origin + r (cos v (cos u x + sin u y) + sin v z) | longitude, periodic | latitude, [−π/2, π/2] |
|
||||
| `torus` {frame, major_radius, minor_radius} | origin + (R + r cos v)(cos u x + sin u y) + r sin v z | longitude, periodic | meridian angle, periodic |
|
||||
| `spline` {u_knots, v_knots, points} | tensor-product B-spline, `points[i][j]` with i along u | u knot domain | v knot domain |
|
||||
@@ -145,7 +145,7 @@ natural normal.
|
||||
An edge interval and each side of a face domain are finite with min < max. In a
|
||||
periodic direction they may start anywhere and run up to one turn (2π), across
|
||||
the seam. Otherwise they lie inside the natural range: a spline's knot domain, a
|
||||
polyline's [0, n − 1], a sphere's latitudes, a cone above its apex.
|
||||
polyline's [0, n − 1], a sphere's latitudes, a cone on its open side of the apex.
|
||||
|
||||
### Pcurves
|
||||
|
||||
|
||||
Reference in New Issue
Block a user