mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 10:35:58 +00:00
solidcraft-smlib: offset faces, thicken, and NoRoom for walls with no room
Brep::offset_faces and Brep::thicken reach the fork's new smlib_offset_faces and smlib_thicken; SMLIB_REV moves to the fork commit that adds them. The shim now refuses an inward wall that leaves no room inside the body with its own status, Error::NoRoom, instead of an argument error, so callers can say what is wrong; the test that a wall thicker than the box fails now expects exactly that. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QWhMwdqJYQKQq8RCLSL1LT
This commit is contained in:
@@ -1 +1 @@
|
||||
14996818bedacc057cf35f296b29876df022faa7
|
||||
1f4d4a173e9fae1f149d5457b2e1cb0a8d62d005
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! The operations block of `capi/include/smlib.h` (`smlib_boolean`, `smlib_fillet`,
|
||||
//! `smlib_chamfer`, `smlib_shell`, `smlib_brep_solid_count`; the fork's `capi/src/smlib_ops.cpp`),
|
||||
//! `smlib_chamfer`, `smlib_shell`, `smlib_offset_faces`, `smlib_thicken`,
|
||||
//! `smlib_brep_solid_count`; the fork's `capi/src/smlib_ops.cpp`),
|
||||
//! declared by hand. Part of the FFI boundary, like its parent: called only through
|
||||
//! [`crate::ops`].
|
||||
|
||||
@@ -14,6 +15,7 @@ use super::{ERR_ARG, OK, RawBrep, new_brep};
|
||||
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 BOOLEAN_UNION: i32 = 0;
|
||||
pub const BOOLEAN_SUBTRACT: i32 = 1;
|
||||
@@ -25,6 +27,7 @@ pub enum Pick {
|
||||
Fillet,
|
||||
Chamfer,
|
||||
Shell,
|
||||
OffsetFaces,
|
||||
}
|
||||
|
||||
/// A failed selection operation: the status, and the index of the point that selected nothing
|
||||
@@ -65,6 +68,16 @@ unsafe extern "C" {
|
||||
out: *mut *mut RawBrep,
|
||||
out_failed_point: *mut i64,
|
||||
) -> i32;
|
||||
fn smlib_offset_faces(
|
||||
brep: *const RawBrep,
|
||||
points: *const f64,
|
||||
n_points: i64,
|
||||
tolerance: f64,
|
||||
distance: f64,
|
||||
out: *mut *mut RawBrep,
|
||||
out_failed_point: *mut i64,
|
||||
) -> i32;
|
||||
fn smlib_thicken(brep: *const RawBrep, thickness: f64, symmetric: i32, out: *mut *mut RawBrep) -> i32;
|
||||
}
|
||||
|
||||
#[cfg(feature = "native")]
|
||||
@@ -89,6 +102,7 @@ pub fn pick_op(kind: Pick, b: &Handle, points: &[[f64; 3]], tolerance: f64, size
|
||||
Pick::Fillet => smlib_fillet,
|
||||
Pick::Chamfer => smlib_chamfer,
|
||||
Pick::Shell => smlib_shell,
|
||||
Pick::OffsetFaces => smlib_offset_faces,
|
||||
};
|
||||
let flat = points.as_ptr().cast::<f64>();
|
||||
let mut failed = -1i64;
|
||||
@@ -98,6 +112,16 @@ pub fn pick_op(kind: Pick, b: &Handle, points: &[[f64; 3]], tolerance: f64, size
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "native")]
|
||||
pub fn thicken(b: &Handle, thickness: f64, symmetric: bool) -> Result<Handle, i32> {
|
||||
new_brep(|out| unsafe { smlib_thicken(b.raw(), thickness, i32::from(symmetric), out) })
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "native"))]
|
||||
pub fn thicken(_: &Handle, _: f64, _: bool) -> Result<Handle, i32> {
|
||||
Err(NOT_BUILT)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "native"))]
|
||||
pub fn boolean(_: &Handle, _: &Handle, _: i32) -> Result<Handle, i32> {
|
||||
Err(NOT_BUILT)
|
||||
|
||||
@@ -44,6 +44,8 @@ pub enum Error {
|
||||
NoMatch { point: usize },
|
||||
/// Selection point `point` is equally near to two edges (faces).
|
||||
Ambiguous { point: usize },
|
||||
/// An inward offset (a shell's wall, faces moved in) leaves no room inside the body.
|
||||
NoRoom,
|
||||
/// A C++ exception (most likely out of memory) was caught at the boundary.
|
||||
Exception,
|
||||
/// The C API is a different version from the one this crate was written against.
|
||||
@@ -64,6 +66,7 @@ impl std::fmt::Display for Error {
|
||||
Error::NotSolid => write!(f, "SMLib's result is not a closed solid"),
|
||||
Error::NoMatch { point } => write!(f, "selection point {} is not near any edge or face", point + 1),
|
||||
Error::Ambiguous { point } => write!(f, "selection point {} is equally near to two edges or faces", point + 1),
|
||||
Error::NoRoom => write!(f, "the offset leaves no room inside the body"),
|
||||
Error::Exception => write!(f, "SMLib raised an internal exception"),
|
||||
Error::Version(v) => write!(f, "SMLib C API version {v}, expected {}", ffi::ABI_VERSION),
|
||||
Error::NotBuilt => write!(f, "not supported yet: this build has no SMLib"),
|
||||
@@ -81,6 +84,7 @@ fn error(s: i32) -> Error {
|
||||
ffi::ERR_ARG | ffi::ERR_BUFFER => Error::Argument,
|
||||
ffi::ERR_EMPTY => Error::Empty,
|
||||
ffi::ops::ERR_NOT_SOLID => Error::NotSolid,
|
||||
ffi::ops::ERR_NO_ROOM => Error::NoRoom,
|
||||
ffi::ERR_EXCEPTION => Error::Exception,
|
||||
ffi::NOT_BUILT => Error::NotBuilt,
|
||||
s => Error::Status(s),
|
||||
|
||||
+19
-2
@@ -1,4 +1,5 @@
|
||||
//! SMLib's modelling operations: booleans, fillet, chamfer and shell. Each returns a new body
|
||||
//! SMLib's modelling operations: booleans, fillet, chamfer, shell, offset faces and thicken.
|
||||
//! Each returns a new body
|
||||
//! and leaves its inputs unchanged.
|
||||
//!
|
||||
//! Edges and faces are chosen by points, never by SMLib's internal order: each point selects the
|
||||
@@ -66,11 +67,27 @@ impl Brep {
|
||||
|
||||
/// Hollowed to a wall of `thickness` (negative: inside the original surface, positive:
|
||||
/// outside), open at the faces nearest to `open_faces` (each within `tolerance`); none gives
|
||||
/// a closed hollow body.
|
||||
/// a closed hollow body. An inward wall that leaves no room inside is [`Error::NoRoom`].
|
||||
pub fn shell(&self, open_faces: &[[f64; 3]], tolerance: f64, thickness: f64) -> Result<Brep> {
|
||||
self.pick_op(ffi::ops::Pick::Shell, open_faces, tolerance, thickness, true)
|
||||
}
|
||||
|
||||
/// The solid with the faces nearest to `faces` (each within `tolerance`) moved `distance`
|
||||
/// along their outward normals (negative: into the body), onto their surfaces' offsets, the
|
||||
/// faces around them extended or trimmed to meet them. Faces moved in past all of the body
|
||||
/// behind them are [`Error::NoRoom`].
|
||||
pub fn offset_faces(&self, faces: &[[f64; 3]], tolerance: f64, distance: f64) -> Result<Brep> {
|
||||
self.pick_op(ffi::ops::Pick::OffsetFaces, faces, tolerance, distance, false)
|
||||
}
|
||||
|
||||
/// A sheet thickened into a solid by `thickness` along its surfaces' normals (negative:
|
||||
/// against them), or by half each way when `symmetric`, with ruled walls along its open
|
||||
/// edges. A closed solid is [`Error::Argument`].
|
||||
pub fn thicken(&self, thickness: f64, symmetric: bool) -> Result<Brep> {
|
||||
init()?;
|
||||
ffi::ops::thicken(&self.0, thickness, symmetric).map(Brep).map_err(error)
|
||||
}
|
||||
|
||||
fn pick_op(&self, kind: ffi::ops::Pick, points: &[[f64; 3]], tolerance: f64, size: f64, allow_none: bool) -> Result<Brep> {
|
||||
init()?;
|
||||
if points.len() > MAX_POINTS || (points.is_empty() && !allow_none) {
|
||||
|
||||
@@ -206,16 +206,16 @@ fn hostile_inputs_are_errors() {
|
||||
assert_eq!(c.shell(&[[10.0, 10.0, 20.0]], 0.1, t).unwrap_err(), Error::Argument, "thickness {t}");
|
||||
}
|
||||
// Sizes SMLib cannot build fail as errors, whatever SMLib says.
|
||||
for (what, r) in [
|
||||
("radius larger than the cube", c.fillet(&mid, 0.1, 50.0)),
|
||||
("chamfer past the faces", c.chamfer(&mid, 0.1, 30.0)),
|
||||
("wall thicker than the box", c.shell(&[[10.0, 10.0, 20.0]], 0.1, -15.0)),
|
||||
] {
|
||||
for (what, r) in [("radius larger than the cube", c.fillet(&mid, 0.1, 50.0)), ("chamfer past the faces", c.chamfer(&mid, 0.1, 30.0))] {
|
||||
match r {
|
||||
Ok(b) => panic!("{what}: SMLib built a body of volume {:?}, counts {:?}", b.volume(ACC), b.counts()),
|
||||
Err(e) => eprintln!("{what}: {e:?}"),
|
||||
}
|
||||
}
|
||||
// A wall with no room inside it never reaches SMLib's offset.
|
||||
assert_eq!(c.shell(&[[10.0, 10.0, 20.0]], 0.1, -15.0).unwrap_err(), Error::NoRoom);
|
||||
assert_eq!(c.offset_faces(&[[10.0, 10.0, 20.0]], 0.1, -20.0).unwrap_err(), Error::NoRoom);
|
||||
assert_eq!(c.thicken(1.0, false).unwrap_err(), Error::Argument, "a solid is no sheet");
|
||||
assert_untouched(&c, 8000.0, [6, 12, 8]);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user