diff --git a/crates/smlib/src/ffi/ops.rs b/crates/smlib/src/ffi/ops.rs index 248086e4b5..47c055c6ba 100644 --- a/crates/smlib/src/ffi/ops.rs +++ b/crates/smlib/src/ffi/ops.rs @@ -1,4 +1,4 @@ -//! The operations block of `capi/include/smlib.h` (`smlib_boolean`, `smlib_boolean2d`, `smlib_fillet`, +//! The operations block of `capi/include/smlib.h` (`smlib_boolean`, `smlib_boolean_union_all`, `smlib_boolean2d`, `smlib_fillet`, //! `smlib_chamfer`, `smlib_blend`, `smlib_fillet_variable`, `smlib_chamfer_sides`, `smlib_shell`, `smlib_offset_faces`, //! `smlib_thicken`, `smlib_draft`, `smlib_merge_faces`, `smlib_brep_solid_count`, `smlib_imprint`; the fork's `capi/src/smlib_ops.cpp`), declared by //! hand. Part of the FFI boundary, like its parent: called only through @@ -48,6 +48,7 @@ type PickFn = unsafe extern "C" fn(*const RawBrep, *const f64, i64, f64, f64, *m #[cfg(feature = "native")] unsafe extern "C" { fn smlib_boolean(a: *const RawBrep, b: *const RawBrep, op: i32, out: *mut *mut RawBrep) -> i32; + fn smlib_boolean_union_all(breps: *const *const RawBrep, n: i64, out: *mut *mut RawBrep) -> i32; fn smlib_boolean2d(a: *const RawBrep, b: *const RawBrep, op: i32, out: *mut *mut RawBrep) -> i32; fn smlib_brep_solid_count(brep: *const RawBrep, out: *mut i64) -> i32; fn smlib_imprint(body: *const RawBrep, tool: *const RawBrep, out: *mut *mut RawBrep) -> i32; @@ -149,6 +150,13 @@ pub fn boolean(a: &Handle, b: &Handle, op: i32) -> Result { new_brep(|out| unsafe { smlib_boolean(a.raw(), b.raw(), op, out) }) } +#[cfg(feature = "native")] +pub fn boolean_union_all(breps: &[&Handle]) -> Result { + let raw: Vec<*const RawBrep> = breps.iter().map(|h| h.raw()).collect(); + let n = i64::try_from(raw.len()).map_err(|_| ERR_ARG)?; + new_brep(|out| unsafe { smlib_boolean_union_all(raw.as_ptr(), n, out) }) +} + #[cfg(feature = "native")] pub fn boolean2d(a: &Handle, b: &Handle, op: i32) -> Result { new_brep(|out| unsafe { smlib_boolean2d(a.raw(), b.raw(), op, out) }) @@ -315,6 +323,11 @@ pub fn boolean(_: &Handle, _: &Handle, _: i32) -> Result { Err(NOT_BUILT) } +#[cfg(not(feature = "native"))] +pub fn boolean_union_all(_: &[&Handle]) -> Result { + Err(NOT_BUILT) +} + #[cfg(not(feature = "native"))] pub fn boolean2d(_: &Handle, _: &Handle, _: i32) -> Result { Err(NOT_BUILT) diff --git a/crates/smlib/src/lib.rs b/crates/smlib/src/lib.rs index e832683ae6..8b683d17f4 100644 --- a/crates/smlib/src/lib.rs +++ b/crates/smlib/src/lib.rs @@ -31,7 +31,7 @@ pub mod trim; pub use check::{CheckLevel, CheckReport}; pub use measure::{MEASURE_ACCURACY, MassProperties}; -pub use ops::{Blend, BooleanOp, ChamferSecond, MAX_POINTS}; +pub use ops::{Blend, BooleanOp, ChamferSecond, MAX_POINTS, MAX_UNION_BREPS}; /// Why an SMLib call failed. #[derive(Clone, Debug, PartialEq)] diff --git a/crates/smlib/src/ops.rs b/crates/smlib/src/ops.rs index 866d65b891..0e18c4d719 100644 --- a/crates/smlib/src/ops.rs +++ b/crates/smlib/src/ops.rs @@ -41,6 +41,9 @@ pub enum ChamferSecond { Angle(f64), } +/// Most bodies [`Brep::union_all`] takes (the C API's limit). +pub const MAX_UNION_BREPS: usize = 100_000; + /// Most points one selection may hold (the C API's limit). pub const MAX_POINTS: usize = 4096; @@ -71,6 +74,19 @@ impl Brep { } } + /// The union of `breps` in one call: they are unioned pairwise, level by level, so none takes + /// part in more than about log2(n) unions. The result is as [`Brep::boolean`]'s; operands + /// tagged each with its own number ([`Brep::tagged`]) give a result whose [`Brep::history`] + /// names them. At least one body and at most [`MAX_UNION_BREPS`]. + pub fn union_all(breps: &[&Brep]) -> Result { + init()?; + if breps.is_empty() || breps.len() > MAX_UNION_BREPS { + return Err(error(ffi::ERR_ARG)); + } + let handles: Vec<&ffi::Handle> = breps.iter().map(|b| &b.0).collect(); + ffi::ops::boolean_union_all(&handles).map(Brep).map_err(error) + } + /// `self` and `other`, sheets of planar faces on one plane ([`Brep::planar_faces`]), combined /// as regions of that plane by `op`: `None` when the result is empty. The result's edges are /// the inputs' own curves, split where they cross.