diff --git a/crates/smlib/SMLIB_REV b/crates/smlib/SMLIB_REV index 9b5b25f792..5b5aefbd64 100644 --- a/crates/smlib/SMLIB_REV +++ b/crates/smlib/SMLIB_REV @@ -1 +1 @@ -ffaf4e87bab2508b816339e49f0edafccf5c53a8 +0294982cb55e3727dbe00de8585d9cb802437028 diff --git a/crates/smlib/src/ffi/sweeps.rs b/crates/smlib/src/ffi/sweeps.rs index 0a40f24947..56fec9485a 100644 --- a/crates/smlib/src/ffi/sweeps.rs +++ b/crates/smlib/src/ffi/sweeps.rs @@ -1,5 +1,6 @@ //! The sweeps block of `capi/include/smlib.h` (`smlib_extrude`, `smlib_revolve`, `smlib_sweep`, -//! `smlib_loft`, `smlib_screw_sweep`, `smlib_sweep_stations`; the fork's `capi/src/smlib_sweeps.cpp`), declared by hand. +//! `smlib_loft`, `smlib_loft_to_point`, `smlib_screw_sweep`, `smlib_sweep_stations`; the fork's +//! `capi/src/smlib_sweeps.cpp`), declared by hand. //! Part of the FFI boundary, like its parent: called only through [`crate::sweeps`], which builds //! the curve lists. @@ -99,6 +100,7 @@ unsafe extern "C" { cap: i32, out: *mut *mut RawBrep, ) -> i32; + fn smlib_loft_to_point(kinds: *const i64, n_kinds: i64, reals: *const f64, n_reals: i64, apex: *const f64, out: *mut *mut RawBrep) -> i32; fn smlib_screw_sweep( kinds: *const i64, n_kinds: i64, @@ -157,6 +159,12 @@ pub fn loft(sections: &List, sizes: &[i64], degree: i32, cap: bool) -> Result Result { + let (k, nk, r, nr) = raw(section)?; + new_brep(|out| unsafe { smlib_loft_to_point(k, nk, r, nr, apex.as_ptr(), out) }) +} + #[cfg(feature = "native")] pub fn screw_sweep(profile: &List, origin: &[f64; 3], direction: &[f64; 3], pitch: f64, turns: f64, tolerance: f64) -> Result { let (k, nk, r, nr) = raw(profile)?; @@ -194,6 +202,11 @@ pub fn loft(_: &List, _: &[i64], _: i32, _: bool) -> Result { Err(NOT_BUILT) } +#[cfg(not(feature = "native"))] +pub fn loft_to_point(_: &List, _: &[f64; 3]) -> Result { + Err(NOT_BUILT) +} + #[cfg(not(feature = "native"))] pub fn screw_sweep(_: &List, _: &[f64; 3], _: &[f64; 3], _: f64, _: f64, _: f64) -> Result { Err(NOT_BUILT) diff --git a/crates/smlib/src/sweeps.rs b/crates/smlib/src/sweeps.rs index 4f1214b174..18e17adaa0 100644 --- a/crates/smlib/src/sweeps.rs +++ b/crates/smlib/src/sweeps.rs @@ -1,6 +1,7 @@ //! SMLib's sweeps: extrusion (straight or drafted), revolution, sweep along a path, loft through -//! sections, the screw sweep of a thread or coil, and the sweep of a profile placed at stations -//! (one that twists, scales or follows guide rails along its path). Profiles, paths and sections are lists of +//! sections or to a point, the screw sweep of a thread or coil, and the sweep of a profile placed +//! at stations (one that twists, scales or follows guide rails along its path). Profiles, paths and +//! sections are lists of //! [`Curve`]s; each sweep returns a new body. //! //! With `cap`, the profile's loops close the ends and the result must be a closed manifold solid @@ -191,6 +192,14 @@ impl Brep { ffi::sweeps::sweep_stations(&l.raw(), &maps, ends).map(Brep).map_err(error) } + /// A loft from the planar `section` (one closed loop of lines, in order) to the point `apex` + /// off its plane: a pyramid, its base the section and a triangle on each line. + pub fn loft_to_point(section: &[Curve], apex: [f64; 3]) -> Result { + init()?; + let l = List::of(section)?; + ffi::sweeps::loft_to_point(&l.raw(), &apex).map(Brep).map_err(error) + } + /// `profile` carried by a screw motion about the axis through `origin` along `direction`: /// `turns` right-handed turns about it, rising `pitch` (signed) per turn, capped at both ends, /// its walls within about `tolerance` of the true screw surfaces. diff --git a/crates/smlib/src/sweeps_tests.rs b/crates/smlib/src/sweeps_tests.rs index b08840f74e..2760a55afe 100644 --- a/crates/smlib/src/sweeps_tests.rs +++ b/crates/smlib/src/sweeps_tests.rs @@ -60,6 +60,19 @@ fn sweeps_lofts_and_screws() { let sections = vec![vec![circle([0.0; 3], 5.0)], vec![circle([0.0, 0.0, 6.0], 3.0)]]; let b = Brep::loft(§ions, 1, true).unwrap(); assert_close(solid_volume(&b, "frustum"), PI * 2.0 * (25.0 + 9.0 + 15.0), 1e-5, "lofted frustum"); + // A ring lofted to a ring: the outline and the hole are one section, lofted loop to loop. + let ring = |z: f64| { + let hole = Curve::Arc { center: [0.0, 0.0, z], axis: [0.0, 0.0, -1.0], reference: [1.0, 0.0, 0.0], radius: 12.0, start: 0.0, end: TAU }; + vec![circle([0.0, 0.0, z], 20.0), hole] + }; + let b = Brep::loft(&[ring(0.0), ring(20.0)], 1, true).unwrap(); + assert_close(solid_volume(&b, "tube"), PI * (400.0 - 144.0) * 20.0, 1e-5, "lofted tube"); + // A square to a point 15 above it: a pyramid, A h / 3. + let base = polygon(&[[-10.0, -10.0, 0.0], [10.0, -10.0, 0.0], [10.0, 10.0, 0.0], [-10.0, 10.0, 0.0]]); + let b = Brep::loft_to_point(&base, [0.0, 0.0, 15.0]).unwrap(); + assert_close(solid_volume(&b, "pyramid"), 2000.0, 1e-9, "pyramid"); + assert_eq!(Brep::loft_to_point(&base, [3.0, 4.0, 0.0]).err(), Some(Error::Argument), "apex in the base's plane"); + assert_eq!(Brep::loft_to_point(&[circle([0.0; 3], 5.0)], [0.0, 0.0, 5.0]).err(), Some(Error::Argument), "curved base"); let tooth = polygon(&[[4.0, 0.0, 0.0], [4.4, 0.0, 0.0], [4.4, 0.0, 0.4], [4.0, 0.0, 0.4]]); let b = Brep::screw_sweep(&tooth, [0.0; 3], [0.0, 0.0, 1.0], 1.0, 3.0, 1e-4).unwrap(); assert_close(solid_volume(&b, "screw"), 0.16 * TAU * 4.2 * 3.0, 1e-4, "screw sweep");