solidcraft-smlib: Brep::loft_to_point, and the fork pinned at 0294982

The fork's new smlib_loft_to_point (a pyramid from a planar section of lines
to an apex) as Brep::loft_to_point. The pin brings the sweeps' other
changes: a loft's section may hold holes (loops lofted loop to loop), a
sweep along a straight path is an extrusion and one turning with arcs about
one axis a revolution (exact walls), and a sweep round a closed path closes
its walls on each other, without caps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017itGh5fpMEousnZ91cYcVt
This commit is contained in:
jyviko
2026-10-21 22:55:56 -04:00
parent cac7d6effd
commit faae48d6ee
4 changed files with 39 additions and 4 deletions
+1 -1
View File
@@ -1 +1 @@
ffaf4e87bab2508b816339e49f0edafccf5c53a8
0294982cb55e3727dbe00de8585d9cb802437028
+14 -1
View File
@@ -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<Ha
new_brep(|out| unsafe { smlib_loft(k, nk, r, nr, sizes.as_ptr(), ns, degree, i32::from(cap), out) })
}
#[cfg(feature = "native")]
pub fn loft_to_point(section: &List, apex: &[f64; 3]) -> Result<Handle, i32> {
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<Handle, i32> {
let (k, nk, r, nr) = raw(profile)?;
@@ -194,6 +202,11 @@ pub fn loft(_: &List, _: &[i64], _: i32, _: bool) -> Result<Handle, i32> {
Err(NOT_BUILT)
}
#[cfg(not(feature = "native"))]
pub fn loft_to_point(_: &List, _: &[f64; 3]) -> Result<Handle, i32> {
Err(NOT_BUILT)
}
#[cfg(not(feature = "native"))]
pub fn screw_sweep(_: &List, _: &[f64; 3], _: &[f64; 3], _: f64, _: f64, _: f64) -> Result<Handle, i32> {
Err(NOT_BUILT)
+11 -2
View File
@@ -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<Brep> {
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.
+13
View File
@@ -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(&sections, 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");