diff --git a/crates/smlib/src/ffi.rs b/crates/smlib/src/ffi.rs index 17c1f66e84..1254d7c04c 100644 --- a/crates/smlib/src/ffi.rs +++ b/crates/smlib/src/ffi.rs @@ -191,6 +191,7 @@ pub mod flat; pub mod history; pub mod measure; pub mod ops; +pub mod split; pub mod ssi; pub mod sweeps; pub mod trim; diff --git a/crates/smlib/src/ffi/split.rs b/crates/smlib/src/ffi/split.rs new file mode 100644 index 0000000000..6e07ae3aed --- /dev/null +++ b/crates/smlib/src/ffi/split.rs @@ -0,0 +1,76 @@ +//! The split block of `capi/include/smlib.h` (`smlib_split_faces`, `smlib_split_body`; the +//! fork's `capi/src/smlib_split.cpp`), declared by hand. Part of the FFI boundary, like its +//! parent: called only through [`crate::split`]. + +#![allow(unsafe_code)] + +use super::Handle; +#[cfg(not(feature = "native"))] +use super::NOT_BUILT; +use super::ssi::SurfaceHandle; +#[cfg(feature = "native")] +use super::{ERR_ARG, ERR_EMPTY, OK, RawBrep, new_brep, ssi::RawSurface}; + +/// `SMLIB_SPLIT_MAX_FACES`, `SMLIB_SPLIT_MAX_TOOLS`. +pub const MAX_FACES: usize = 100_000; +pub const MAX_TOOLS: usize = 4096; + +#[cfg(feature = "native")] +unsafe extern "C" { + fn smlib_split_faces( + body: *const RawBrep, + faces: *const i64, + n_faces: i64, + tools: *const *const RawSurface, + n_tools: i64, + out: *mut *mut RawBrep, + made: *mut i64, + ) -> i32; + fn smlib_split_body(body: *const RawBrep, tool: *const RawSurface, back: *mut *mut RawBrep, front: *mut *mut RawBrep) -> i32; +} + +/// `body` with `faces` cut by `tools` in turn, and per face how many faces it became besides +/// itself. +#[cfg(feature = "native")] +pub fn split_faces(body: &Handle, faces: &[i64], tools: &[&SurfaceHandle]) -> Result<(Handle, Vec), i32> { + if faces.is_empty() || faces.len() > MAX_FACES || tools.is_empty() || tools.len() > MAX_TOOLS { + return Err(ERR_ARG); + } + let raw: Vec<*const RawSurface> = tools.iter().map(|t| t.raw()).collect(); + let mut made = vec![0i64; faces.len()]; + let n = i64::try_from(faces.len()).map_err(|_| ERR_ARG)?; + let m = i64::try_from(raw.len()).map_err(|_| ERR_ARG)?; + // SAFETY: the arrays outlive the call and hold `n` and `m` entries; the handles are live. + let h = new_brep(|out| unsafe { smlib_split_faces(body.raw(), faces.as_ptr(), n, raw.as_ptr(), m, out, made.as_mut_ptr()) })?; + Ok((h, made)) +} + +/// The parts of `body` behind and in front of `tool`. +#[cfg(feature = "native")] +pub fn split_body(body: &Handle, tool: &SurfaceHandle) -> Result<(Handle, Handle), i32> { + let mut back: *mut RawBrep = std::ptr::null_mut(); + let mut front: *mut RawBrep = std::ptr::null_mut(); + // SAFETY: the handles are live; the out-pointers are written only on success. + let s = unsafe { smlib_split_body(body.raw(), tool.raw(), &mut back, &mut front) }; + let (b, f) = (std::ptr::NonNull::new(back), std::ptr::NonNull::new(front)); + match (s, b, f) { + (OK, Some(b), Some(f)) => Ok((Handle(b), Handle(f))), + (OK, b, f) => { + // Free whichever came back alone. + drop(b.map(Handle)); + drop(f.map(Handle)); + Err(ERR_EMPTY) + } + (s, _, _) => Err(s), + } +} + +#[cfg(not(feature = "native"))] +pub fn split_faces(_: &Handle, _: &[i64], _: &[&SurfaceHandle]) -> Result<(Handle, Vec), i32> { + Err(NOT_BUILT) +} + +#[cfg(not(feature = "native"))] +pub fn split_body(_: &Handle, _: &SurfaceHandle) -> Result<(Handle, Handle), i32> { + Err(NOT_BUILT) +} diff --git a/crates/smlib/src/ffi/ssi.rs b/crates/smlib/src/ffi/ssi.rs index f02533bcea..fb9b576297 100644 --- a/crates/smlib/src/ffi/ssi.rs +++ b/crates/smlib/src/ffi/ssi.rs @@ -110,6 +110,13 @@ unsafe impl Sync for SurfaceHandle {} unsafe impl Send for ResultHandle {} unsafe impl Sync for ResultHandle {} +impl SurfaceHandle { + #[cfg_attr(not(feature = "native"), allow(dead_code))] + pub(crate) fn raw(&self) -> *const RawSurface { + self.0.as_ptr() + } +} + impl Drop for SurfaceHandle { fn drop(&mut self) { #[cfg(feature = "native")] diff --git a/crates/smlib/src/lib.rs b/crates/smlib/src/lib.rs index b2903e2ca1..91f2b98c13 100644 --- a/crates/smlib/src/lib.rs +++ b/crates/smlib/src/lib.rs @@ -23,6 +23,7 @@ pub mod history; pub mod import; mod measure; mod ops; +mod split; pub mod ssi; pub mod sweeps; pub mod trim; diff --git a/crates/smlib/src/split.rs b/crates/smlib/src/split.rs new file mode 100644 index 0000000000..9a818f87d8 --- /dev/null +++ b/crates/smlib/src/split.rs @@ -0,0 +1,40 @@ +//! Split Face and Split Body (`capi/include/smlib.h`, "Split"): faces cut where tool surfaces +//! meet them, and a solid cut in two by a surface. Tools are [`crate::ssi::Surface`]s, each +//! taken over its whole domain. + +use crate::ssi::Surface; +use crate::{Brep, Error, Result, error, ffi, init}; + +impl Brep { + /// `self` with faces `faces` (indices in SMLib's face order, distinct) cut where each of + /// `tools` meets them, the tools in turn, each cutting the pieces the earlier ones left; and + /// for each face how many faces it became besides itself (0: no tool split it). `None` when + /// no face is split. Tagged operands ([`Brep::tagged`]) give the pieces' history. + pub fn split_faces(&self, faces: &[usize], tools: &[&Surface]) -> Result)>> { + init()?; + if faces.is_empty() || faces.len() > ffi::split::MAX_FACES || tools.is_empty() || tools.len() > ffi::split::MAX_TOOLS { + return Err(Error::Argument); + } + let faces: Vec = faces.iter().map(|f| i64::try_from(*f).map_err(|_| Error::Argument)).collect::>()?; + let handles: Vec<&ffi::ssi::SurfaceHandle> = tools.iter().map(|t| t.handle()).collect(); + match ffi::split::split_faces(&self.0, &faces, &handles) { + Ok((h, made)) => Ok(Some((Brep(h), made.into_iter().map(|m| usize::try_from(m).unwrap_or(0)).collect()))), + Err(ffi::ops::ERR_NO_CUT) => Ok(None), + Err(s) => Err(error(s)), + } + } + + /// The manifold solid `self` cut in two by `tool` (whose domain must reach past it): the + /// part behind the surface (its normal points away) and the part in front, each closed by + /// the tool's piece inside the body and each possibly several solids. A tool that misses + /// leaves both parts the whole body, with no face from the tool (tag `self` to tell); `None` + /// when a side is left empty. + pub fn split_body(&self, tool: &Surface) -> Result> { + init()?; + match ffi::split::split_body(&self.0, tool.handle()) { + Ok((b, f)) => Ok(Some((Brep(b), Brep(f)))), + Err(ffi::ops::ERR_NO_CUT) => Ok(None), + Err(s) => Err(error(s)), + } + } +} diff --git a/crates/smlib/src/ssi.rs b/crates/smlib/src/ssi.rs index e05078b874..628b6dece7 100644 --- a/crates/smlib/src/ssi.rs +++ b/crates/smlib/src/ssi.rs @@ -25,6 +25,10 @@ fn int(n: usize) -> Result { } impl Surface { + pub(crate) fn handle(&self) -> &ffi::ssi::SurfaceHandle { + &self.0 + } + /// `origin + u*x + v*y` (x, y the axes made unit), for u in `u`, v in `v`. pub fn plane(origin: [f64; 3], xaxis: [f64; 3], yaxis: [f64; 3], u: [f64; 2], v: [f64; 2]) -> Result { init()?;