solidcraft-smlib: Brep::imprint

Imprint cuts each face of a body where a tool body meets it (the fork's
smlib_imprint, SMLib's imprinting boolean) and gives None where the tool
crosses no face. Trim builds on it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brennen Herbruck
2026-10-21 10:36:57 -04:00
committed by jyviko
parent 96bdbbf3ba
commit 82fe0e59e5
3 changed files with 39 additions and 1 deletions
+13 -1
View File
@@ -1,6 +1,6 @@
//! The operations block of `capi/include/smlib.h` (`smlib_boolean`, `smlib_fillet`,
//! `smlib_chamfer`, `smlib_blend`, `smlib_fillet_variable`, `smlib_shell`, `smlib_offset_faces`,
//! `smlib_thicken`, `smlib_brep_solid_count`; the fork's `capi/src/smlib_ops.cpp`), declared by
//! `smlib_thicken`, `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
//! [`crate::ops`].
@@ -16,6 +16,7 @@ pub const ERR_NOT_SOLID: i32 = -1100;
pub const ERR_PICK_NONE: i32 = -1101;
pub const ERR_PICK_AMBIGUOUS: i32 = -1102;
pub const ERR_NO_ROOM: i32 = -1103;
pub const ERR_NO_CUT: i32 = -1104;
pub const BOOLEAN_UNION: i32 = 0;
pub const BOOLEAN_SUBTRACT: i32 = 1;
@@ -46,6 +47,7 @@ type PickFn = unsafe extern "C" fn(*const RawBrep, *const f64, i64, f64, f64, *m
unsafe extern "C" {
fn smlib_boolean(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;
fn smlib_fillet(
brep: *const RawBrep,
points: *const f64,
@@ -112,6 +114,11 @@ pub fn boolean(a: &Handle, b: &Handle, op: i32) -> Result<Handle, i32> {
new_brep(|out| unsafe { smlib_boolean(a.raw(), b.raw(), op, out) })
}
#[cfg(feature = "native")]
pub fn imprint(body: &Handle, tool: &Handle) -> Result<Handle, i32> {
new_brep(|out| unsafe { smlib_imprint(body.raw(), tool.raw(), out) })
}
#[cfg(feature = "native")]
pub fn solid_count(b: &Handle) -> Result<i64, i32> {
let mut n = 0i64;
@@ -197,6 +204,11 @@ pub fn boolean(_: &Handle, _: &Handle, _: i32) -> Result<Handle, i32> {
Err(NOT_BUILT)
}
#[cfg(not(feature = "native"))]
pub fn imprint(_: &Handle, _: &Handle) -> Result<Handle, i32> {
Err(NOT_BUILT)
}
#[cfg(not(feature = "native"))]
pub fn solid_count(_: &Handle) -> Result<i64, i32> {
Err(NOT_BUILT)
+12
View File
@@ -62,6 +62,18 @@ impl Brep {
}
}
/// `self` with each face `tool` meets cut along the intersection, the pieces kept as faces of
/// one body: `None` when the tool meets no face. Tagged operands give the pieces' history
/// (each piece is a split of the face it was cut from).
pub fn imprint(&self, tool: &Brep) -> Result<Option<Brep>> {
init()?;
match ffi::ops::imprint(&self.0, &tool.0) {
Ok(h) => Ok(Some(Brep(h))),
Err(ffi::ops::ERR_NO_CUT) => Ok(None),
Err(s) => Err(error(s)),
}
}
/// The number of separate solid regions (voids not counted).
pub fn solid_count(&self) -> Result<usize> {
ffi::ops::solid_count(&self.0).map(|n| usize::try_from(n).unwrap_or(0)).map_err(error)
+14
View File
@@ -275,3 +275,17 @@ fn a_variable_round_follows_its_law() {
assert!(c.fillet_variable([10.0, 20.0, 20.0], 0.1, [0.0, 20.0, 20.0], 1, &[0.0, 0.5, 1.0, 1.0], &[3.0, 1.0]).is_err());
assert!(matches!(c.fillet_variable([90.0, 90.0, 90.0], 0.1, [0.0; 3], 1, &knots, &[3.0, 1.0]), Err(Error::NoMatch { .. })));
}
#[test]
fn imprint_cuts_faces_where_a_tool_meets_them() {
// A box half sunk into the cube cuts the four side faces it crosses and the top it lies in.
let cube = cube(20.0);
let tool = Brep::make_box([5.0, -1.0, 10.0], [10.0, 22.0, 15.0]).unwrap();
let cut = cube.imprint(&tool).unwrap().unwrap();
assert!(cut.counts().unwrap()[0] > 6);
assert!((cut.volume(1e-7).unwrap() - 8000.0).abs() < 1e-6);
assert_eq!(cube.counts().unwrap()[0], 6);
// A tool that misses cuts nothing.
let far = Brep::make_box([50.0, 0.0, 0.0], [10.0, 10.0, 10.0]).unwrap();
assert!(cube.imprint(&far).unwrap().is_none());
}