mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-04 02:55:54 +00:00
kernel: revolutions of a line square to the axis go into SMLib as planes
A revolve's flat ends (a line square to the axis turned about it) were refused, so any revolved profile off the axis could not reach SMLib (oracle case 29). Such a face now goes as SMLib's plane square to the axis, its range the box round the sector the line sweeps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QWhMwdqJYQKQq8RCLSL1LT
This commit is contained in:
@@ -93,3 +93,20 @@ fn a_cavity_comes_back_from_smlib() {
|
||||
let got = super::smlib_body(&back).unwrap().volume(1e-7).unwrap();
|
||||
assert!((got - (64_000.0 - 125.0)).abs() < 1e-6, "{got}");
|
||||
}
|
||||
|
||||
/// A square revolved off the axis through three quarters of a turn: its flat ends are planes
|
||||
/// square to the axis (annular sectors), its walls cylinders.
|
||||
#[test]
|
||||
fn a_revolved_annulus_goes_with_its_flat_faces() {
|
||||
use solidcraft_geom::{Loop2, Plane, Region2, Vec2};
|
||||
let square =
|
||||
Region2 { outer: Loop2::polygon(&[Vec2::new(4., 12.), Vec2::new(12., 12.), Vec2::new(12., 20.), Vec2::new(4., 20.)]), holes: vec![] };
|
||||
let plane = Plane { y: v(0., 0., 1.), ..Plane::XZ };
|
||||
let body = crate::revolve(&plane, &[square], Vec2::ZERO, Vec2::Y, 1.5 * PI).unwrap().pop().unwrap();
|
||||
let s = super::smlib_body(&body).unwrap();
|
||||
let check = s.check(solidcraft_smlib::CheckLevel::All).unwrap();
|
||||
assert!(check.is_valid(), "{check}");
|
||||
let exact = 0.75 * PI * (144.0 - 16.0) * 8.0;
|
||||
let got = s.volume(1e-7).unwrap();
|
||||
assert!((got - exact).abs() <= 1e-6 * exact, "SMLib volume {got}, exact {exact}");
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//! Revolutions and extruded circles as SMLib's analytic surfaces.
|
||||
//!
|
||||
//! A `placed_revolution` record holds a surface the way truck does (a profile turned about an
|
||||
//! axis, then placed); SMLib has no such surface, but every one truck makes for a cylinder, cone,
|
||||
//! sphere or torus is one of those exactly: a line along the axis, a line in a plane through it, a
|
||||
//! axis, then placed); SMLib has no such surface, but every one truck makes for a plane, cylinder, cone,
|
||||
//! sphere or torus is one of those exactly: a line square to the axis, a line along it, a line in a plane through it, a
|
||||
//! circle centred on it, a circle in a plane through it. A rational spline that is a circular arc
|
||||
//! swept straight along its axis (truck's extruded circles) is a cylinder. Each such face becomes
|
||||
//! that analytic surface with the face's range on it, checked point by point over the face's
|
||||
@@ -21,7 +21,7 @@ use solidcraft_geom::brep::eval::{CurveEval, SurfaceEval};
|
||||
|
||||
use super::{R, Unsupported};
|
||||
|
||||
const NOT_ANALYTIC: Unsupported = Unsupported::NotYet("revolved surfaces other than cylinders, cones, spheres and tori");
|
||||
const NOT_ANALYTIC: Unsupported = Unsupported::NotYet("revolved surfaces other than planes, cylinders, cones, spheres and tori");
|
||||
|
||||
/// `brep` with every revolution and extruded circle replaced by its analytic surface (borrowed
|
||||
/// when it has none).
|
||||
@@ -44,8 +44,10 @@ pub(super) fn analytic(brep: &b::Brep) -> R<Cow<'_, b::Brep>> {
|
||||
// The angle about the axis starts at 0: the frame turns to the face's first angle (SMLib
|
||||
// refuses an analytic range past 360°). A torus's tube angle can't turn so: it starts
|
||||
// within the first turn, where SMLib's degree domains are at home.
|
||||
turn_frame(&mut surface, range[0][0]);
|
||||
range[0] = [0.0, range[0][1] - range[0][0]];
|
||||
if !matches!(surface, b::Surface::Plane { .. }) {
|
||||
turn_frame(&mut surface, range[0][0]);
|
||||
range[0] = [0.0, range[0][1] - range[0][0]];
|
||||
}
|
||||
if matches!(surface, b::Surface::Torus { .. }) {
|
||||
range[1] = first_turn(range[1]);
|
||||
}
|
||||
@@ -255,7 +257,21 @@ impl Revolution {
|
||||
let (h0, h1) = (height(at(t[0])?), height(at(t[1])?));
|
||||
let (dh, dr) = (d.dot(self.z), d.dot(x));
|
||||
if dh.abs() <= 1e-12 * len {
|
||||
return Err(NOT_ANALYTIC); // a disc: a plane, which a revolution face never is in the kernel
|
||||
// A disc or an annulus: the plane square to the axis, its range the box round the
|
||||
// sector the line sweeps (each end's radius at both end angles and every quarter
|
||||
// turn between: in the plane a point is linear in the radius).
|
||||
let (ra, rb) = (radial(at(t[0])?), radial(at(t[1])?));
|
||||
let mut angles = vec![angle[0], angle[1]];
|
||||
let mut q = (angle[0] / FRAC_PI_2).ceil() * FRAC_PI_2;
|
||||
while q < angle[1] && angles.len() < 16 {
|
||||
angles.push(q);
|
||||
q += FRAC_PI_2;
|
||||
}
|
||||
let corners: Vec<[f64; 2]> = angles.iter().flat_map(|a| [ra, rb].map(|r| [r * a.cos(), r * a.sin()])).collect();
|
||||
let lo = |k: usize| corners.iter().map(|c| c[k]).fold(f64::INFINITY, f64::min);
|
||||
let hi = |k: usize| corners.iter().map(|c| c[k]).fold(f64::NEG_INFINITY, f64::max);
|
||||
let plane = b::Surface::Plane { origin: arr(foot), u_axis: arr(x), v_axis: arr(y) };
|
||||
return Ok((plane, [[lo(0), hi(0)], [lo(1), hi(1)]]));
|
||||
}
|
||||
let r0 = radial(s) - dr / dh * height(s);
|
||||
let v = sorted(h0, h1);
|
||||
@@ -329,6 +345,10 @@ fn analytic_uv(s: &b::Surface, p: Vec3, near: [f64; 2]) -> Option<[f64; 2]> {
|
||||
}
|
||||
a
|
||||
};
|
||||
if let b::Surface::Plane { origin, u_axis, v_axis } = s {
|
||||
let q = p - v3(*origin);
|
||||
return Some([q.dot(v3(*u_axis)), q.dot(v3(*v_axis))]);
|
||||
}
|
||||
let (frame, round) = match s {
|
||||
b::Surface::Cylinder { frame, .. } | b::Surface::Cone { frame, .. } | b::Surface::Sphere { frame, .. } => (frame, 0.0),
|
||||
b::Surface::Torus { frame, major_radius, .. } => (frame, *major_radius),
|
||||
|
||||
Reference in New Issue
Block a user