mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 10:35:58 +00:00
solidcraft-smlib: SMLib's own validity check, Brep::check
ValidatePointers, then AssertValid over the whole topology graph (the fork's smlib_brep_check), at CheckLevel::Basic, Geometry or All. A body that fails is still Ok, with the failed tests counted and one line each in the CheckReport; Err means the check could not run. Tests run it on every SMLib result and import. SMLIB_REV moves to the fork's check commit (39f398e), which also fixes a seam-curve leak that AssertValid hit on every periodic face. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1 +1 @@
|
||||
6fe0446f4956a4faec9ea022eeab408ffdca5b8c
|
||||
39f398e99b8e1ee8de158cab75d57bd66d8cbff5
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
//! SMLib's own validity check: `SmBrep::ValidatePointers`, then `AssertValid` over the whole
|
||||
//! topology graph. Tests run it on every SMLib result and every import.
|
||||
|
||||
use crate::{Brep, Error, Result, error, ffi};
|
||||
|
||||
/// How many of SMLib's tests to run.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum CheckLevel {
|
||||
/// The fewest: topology and pointers, no geometry.
|
||||
Basic = 0,
|
||||
/// Adds the geometry checks (gaps between vertices, edges and faces against tolerances).
|
||||
Geometry = 1,
|
||||
/// Every test, the slow ones too.
|
||||
All = 2,
|
||||
}
|
||||
|
||||
/// What SMLib's check found.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct CheckReport {
|
||||
/// Failed tests; 0 means SMLib finds the body valid.
|
||||
pub failures: usize,
|
||||
/// One line per failed test, such as
|
||||
/// `SmBrep::Test #82 Tolerance: Vertex tolerance relative to brep tolerance (offset=2, tolerance=0.01) [owner SmVertex]`.
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
impl CheckReport {
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.failures == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for CheckReport {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if self.is_valid() { f.write_str("valid") } else { write!(f, "{} failed tests:\n{}", self.failures, self.text) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Brep {
|
||||
/// Runs SMLib's validity check at `level`. A body that fails it is still `Ok`, with the
|
||||
/// failures in the report; `Err` means the check could not run.
|
||||
pub fn check(&self, level: CheckLevel) -> Result<CheckReport> {
|
||||
let mut buf = vec![0u8; 1 << 16];
|
||||
let (len, failures) = match ffi::check::check(&self.0, level as i32, &mut buf) {
|
||||
Ok(r) => r,
|
||||
// Too long for the first buffer: once more with the length it asked for.
|
||||
Err((ffi::ERR_BUFFER, needed)) => {
|
||||
buf = vec![0u8; needed.saturating_add(1)];
|
||||
ffi::check::check(&self.0, level as i32, &mut buf).map_err(|(s, _)| error(s))?
|
||||
}
|
||||
Err((s, _)) => return Err(error(s)),
|
||||
};
|
||||
let text = String::from_utf8_lossy(buf.get(..len).ok_or(Error::Argument)?).into_owned();
|
||||
Ok(CheckReport { failures: usize::try_from(failures).unwrap_or(0), text })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "native"))]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn a_box_is_valid_at_every_level() {
|
||||
let b = Brep::make_box([1.0, 2.0, 3.0], [10.0, 20.0, 30.0]).unwrap();
|
||||
for level in [CheckLevel::Basic, CheckLevel::Geometry, CheckLevel::All] {
|
||||
let r = b.check(level).unwrap();
|
||||
assert!(r.is_valid(), "{level:?}: {r}");
|
||||
assert_eq!(r.text, "");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_copy_and_a_native_file_round_trip_stay_valid() {
|
||||
let b = Brep::make_box([0.0; 3], [2.0, 3.0, 4.0]).unwrap();
|
||||
assert!(b.try_clone().unwrap().check(CheckLevel::All).unwrap().is_valid());
|
||||
let dir = std::env::temp_dir().join(format!("solidcraft-smlib-check-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let path = dir.join("box.smb");
|
||||
b.write(&path, false).unwrap();
|
||||
let back = Brep::read(&path, false).unwrap();
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
let r = back.check(CheckLevel::All).unwrap();
|
||||
assert!(r.is_valid(), "{r}");
|
||||
}
|
||||
}
|
||||
@@ -179,6 +179,7 @@ mod calls {
|
||||
|
||||
pub use calls::*;
|
||||
|
||||
pub mod check;
|
||||
pub mod history;
|
||||
|
||||
/// Where [`set_log`] sends SMLib's log lines.
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
//! `smlib_brep_check` (the fork's `capi/src/smlib_check.cpp`), declared by hand.
|
||||
//! Part of the FFI boundary, like its parent: called only through [`crate::check`].
|
||||
|
||||
#![allow(unsafe_code)]
|
||||
|
||||
use super::Handle;
|
||||
#[cfg(not(feature = "native"))]
|
||||
use super::NOT_BUILT;
|
||||
#[cfg(feature = "native")]
|
||||
use super::{OK, RawBrep};
|
||||
#[cfg(feature = "native")]
|
||||
use std::ffi::c_char;
|
||||
|
||||
#[cfg(feature = "native")]
|
||||
unsafe extern "C" {
|
||||
pub fn smlib_brep_check(brep: *const RawBrep, level: i32, buf: *mut c_char, cap: i64, out_len: *mut i64, out_failures: *mut i64) -> i32;
|
||||
}
|
||||
|
||||
/// Runs the check into `buf`: the report's length and the failure count, or a status
|
||||
/// (`ERR_BUFFER` with the needed length when `buf` is too small).
|
||||
#[cfg(feature = "native")]
|
||||
pub fn check(b: &Handle, level: i32, buf: &mut [u8]) -> Result<(usize, i64), (i32, usize)> {
|
||||
let cap = i64::try_from(buf.len()).unwrap_or(i64::MAX);
|
||||
let (mut len, mut failures) = (0i64, 0i64);
|
||||
let s = unsafe { smlib_brep_check(b.raw(), level, buf.as_mut_ptr().cast(), cap, &mut len, &mut failures) };
|
||||
let len = usize::try_from(len).unwrap_or(0);
|
||||
match s {
|
||||
OK => Ok((len.min(buf.len()), failures)),
|
||||
s => Err((s, len)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "native"))]
|
||||
pub fn check(_: &Handle, _: i32, _: &mut [u8]) -> Result<(usize, i64), (i32, usize)> {
|
||||
Err((NOT_BUILT, 0))
|
||||
}
|
||||
@@ -15,9 +15,12 @@ use std::path::Path;
|
||||
use std::sync::Once;
|
||||
use std::sync::atomic::{AtomicI32, Ordering};
|
||||
|
||||
mod check;
|
||||
mod ffi;
|
||||
pub mod history;
|
||||
|
||||
pub use check::{CheckLevel, CheckReport};
|
||||
|
||||
/// Why an SMLib call failed.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Error {
|
||||
|
||||
Reference in New Issue
Block a user