smlib: Brep::classify_point says exactly where a point lies against a body

Binds the fork's smlib_brep_classify_point (SmBrep::Point3DClassify at the body's own zone
tolerance): inside, outside, or on the boundary within that tolerance of a face, an edge or a
vertex. A box and a cylinder just off its curved wall in measure's tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NvoxKQ2rEZvcXNQnhE3Sgs
This commit is contained in:
jyviko
2026-10-30 13:39:25 -04:00
committed by jyviko
parent 20c0c2ffcc
commit 56d39a5764
3 changed files with 71 additions and 4 deletions
+22 -2
View File
@@ -1,5 +1,5 @@
//! `smlib_brep_mass_properties`, `smlib_brep_area`, `smlib_brep_bounds` and
//! `smlib_brep_edge_face_distance` (the fork's `capi/src/smlib_measure.cpp`), declared by
//! `smlib_brep_mass_properties`, `smlib_brep_area`, `smlib_brep_bounds`,
//! `smlib_brep_edge_face_distance` and `smlib_brep_classify_point` (the fork's `capi/src/smlib_measure.cpp`), declared by
//! hand. Part of the FFI boundary, like its parent: called only through [`crate::measure`].
#![allow(unsafe_code)]
@@ -16,8 +16,14 @@ unsafe extern "C" {
pub fn smlib_brep_area(brep: *const RawBrep, relative_accuracy: f64, out: *mut f64) -> i32;
pub fn smlib_brep_bounds(brep: *const RawBrep, lo: *mut f64, hi: *mut f64) -> i32;
pub fn smlib_brep_edge_face_distance(brep: *const RawBrep, edge: i64, face: i64, out: *mut f64) -> i32;
pub fn smlib_brep_classify_point(brep: *const RawBrep, x: f64, y: f64, z: f64, out: *mut i32) -> i32;
}
/// `SMLIB_POINT_OUTSIDE`, `SMLIB_POINT_INSIDE` and `SMLIB_POINT_ON_BOUNDARY`.
pub const POINT_OUTSIDE: i32 = 0;
pub const POINT_INSIDE: i32 = 1;
pub const POINT_ON_BOUNDARY: i32 = 2;
#[cfg(feature = "native")]
pub fn mass_properties(b: &Handle, relative_accuracy: f64) -> Result<[f64; 11], i32> {
let mut m = [0.0; 11];
@@ -54,6 +60,20 @@ pub fn edge_face_distance(b: &Handle, edge: i64, face: i64) -> Result<f64, i32>
}
}
#[cfg(feature = "native")]
pub fn classify_point(b: &Handle, p: [f64; 3]) -> Result<i32, i32> {
let mut c = -1;
match unsafe { smlib_brep_classify_point(b.raw(), p[0], p[1], p[2], &mut c) } {
OK => Ok(c),
s => Err(s),
}
}
#[cfg(not(feature = "native"))]
pub fn classify_point(_: &Handle, _: [f64; 3]) -> Result<i32, i32> {
Err(NOT_BUILT)
}
#[cfg(not(feature = "native"))]
pub fn edge_face_distance(_: &Handle, _: i64, _: i64) -> Result<f64, i32> {
Err(NOT_BUILT)
+1 -1
View File
@@ -31,7 +31,7 @@ pub mod sweeps;
pub mod trim;
pub use check::{CheckLevel, CheckReport};
pub use measure::{MEASURE_ACCURACY, MassProperties};
pub use measure::{MEASURE_ACCURACY, MassProperties, PointClass};
pub use ops::{Blend, BooleanOp, ChamferSecond, Hole, HoleEdge, MAX_POINTS, MAX_UNION_BREPS};
/// Why an SMLib call failed.
+48 -1
View File
@@ -1,5 +1,6 @@
//! Exact mass properties from SMLib: area, volume, centroid and inertia, integrated on the exact
//! surfaces rather than a tessellation; the exact box; how far an edge lies from a face's surface.
//! surfaces rather than a tessellation; the exact box; how far an edge lies from a face's surface;
//! where a point lies against a body.
use crate::{Brep, Result, error, ffi};
@@ -31,7 +32,33 @@ impl MassProperties {
}
}
/// Where a point lies against a body ([`Brep::classify_point`]).
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum PointClass {
/// In a void region: outside the material, or in a cavity.
Outside,
/// In a material region.
Inside,
/// Within the body's tolerance of a face, an edge or a vertex.
OnBoundary,
}
impl Brep {
/// Where `p` (mm) lies against this body, decided exactly on its curves and surfaces (no
/// tessellation) by SMLib's `SmBrep::Point3DClassify` at the body's own zone tolerance: on
/// the boundary when within that tolerance of a face, an edge or a vertex (sheet faces too),
/// else inside a material region or outside. A body with no material (a sheet, an open shell)
/// has no inside. A non-finite coordinate is [`Error::Argument`](crate::Error::Argument).
pub fn classify_point(&self, p: [f64; 3]) -> Result<PointClass> {
match ffi::measure::classify_point(&self.0, p).map_err(error)? {
ffi::measure::POINT_OUTSIDE => Ok(PointClass::Outside),
ffi::measure::POINT_INSIDE => Ok(PointClass::Inside),
ffi::measure::POINT_ON_BOUNDARY => Ok(PointClass::OnBoundary),
// The C API writes only the three above; any other value is passed on as it came.
other => Err(crate::Error::Status(other)),
}
}
/// Mass properties at `relative_accuracy` (clamped to [1e-8, 1e-1]; [`MEASURE_ACCURACY`] is
/// the finest): the sum over the body's material regions, so several lumps, or lumps
/// touching along edges or at points, measure too. A body with no material (a sheet, an open
@@ -243,6 +270,26 @@ mod tests {
}
}
/// A 10 × 20 × 30 box: inside, outside, on a face, an edge and a vertex; a cylinder of radius
/// 5 just off its curved wall, within the tolerance (on it) and beyond (inside or outside).
#[test]
fn points_classify_exactly() {
let b = Brep::make_box([0.0; 3], [10.0, 20.0, 30.0]).unwrap();
assert_eq!(b.classify_point([5.0, 10.0, 15.0]).unwrap(), PointClass::Inside);
assert_eq!(b.classify_point([5.0, 10.0, 30.0 + 1e-4]).unwrap(), PointClass::Outside);
assert_eq!(b.classify_point([5.0, 10.0, 30.0 - 1e-4]).unwrap(), PointClass::Inside);
assert_eq!(b.classify_point([5.0, 10.0, 30.0]).unwrap(), PointClass::OnBoundary);
assert_eq!(b.classify_point([10.0, 10.0, 30.0]).unwrap(), PointClass::OnBoundary);
assert_eq!(b.classify_point([10.0, 20.0, 30.0]).unwrap(), PointClass::OnBoundary);
assert_eq!(b.classify_point([5.0, 10.0, f64::NAN]).unwrap_err(), Error::Argument);
let c = Brep::make_cylinder([0.0; 3], 5.0, 10.0).unwrap();
let at = |r: f64| [r * std::f64::consts::FRAC_1_SQRT_2, r * std::f64::consts::FRAC_1_SQRT_2, 5.0];
assert_eq!(c.classify_point(at(5.0 + 1e-8)).unwrap(), PointClass::OnBoundary);
assert_eq!(c.classify_point(at(5.0 - 1e-8)).unwrap(), PointClass::OnBoundary);
assert_eq!(c.classify_point(at(5.0 + 1e-4)).unwrap(), PointClass::Outside);
assert_eq!(c.classify_point(at(5.0 - 1e-4)).unwrap(), PointClass::Inside);
}
#[test]
fn bad_accuracies_are_errors() {
let b = Brep::make_box([0.0; 3], [1.0; 3]).unwrap();