Bodies: an operation's result is its record, not the SMLib body it was exported from

A body made by an SMLib operation kept that SMLib body beside its record (SMLib's
export). The export is not exact to the bit, so the kept body measured and operated a
little differently from the record's import, which is what a body read back from a
file has: a box measured 24000.000000000004 mm³ live and 24000.0 reopened, a drilled
box 21989.38056 against 21989.38013. A cut ring's volume therefore changed when it
was moved, the moved ring being measured on the SMLib body the move made
(measure_centre_tests::combine_cut_volume_does_not_depend_on_placement). Every body
now imports its record when an operation first needs it, so evaluated and reopened
bodies are the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
jyviko
2026-10-27 21:52:46 -04:00
committed by Irakli Kourtis
parent bc6d31e603
commit f11c0c5712
3 changed files with 14 additions and 16 deletions
+11 -12
View File
@@ -441,15 +441,11 @@ impl SurfaceKind {
impl Body {
/// A body with `record` and nothing else set.
fn bare(record: Brep, smlib: Option<solidcraft_smlib::Brep>, mesh: Option<std::sync::Arc<crate::meshbody::TriMesh>>) -> Body {
fn bare(record: Brep, mesh: Option<std::sync::Arc<crate::meshbody::TriMesh>>) -> Body {
let size = span_of(&record);
let cell = std::sync::OnceLock::new();
if let Some(handle) = smlib {
let _ = cell.set(Ok(handle));
}
Body {
record: std::sync::Arc::new(record),
smlib: std::sync::Arc::new(cell),
smlib: std::sync::Arc::new(std::sync::OnceLock::new()),
size,
volume: Some(std::sync::Arc::new(std::sync::Mutex::new(None))),
mesh,
@@ -468,18 +464,21 @@ impl Body {
/// structure (budgets, references, loops, regions, canonical order). Its faces face as the
/// record says.
pub(crate) fn from_record(record: Brep) -> Result<Body> {
Ok(Body::bare(solid_record(record)?, None, None))
Ok(Body::bare(solid_record(record)?, None))
}
/// The body of an SMLib B-rep and its record (SMLib's export, in canonical order), keeping
/// the SMLib body so later operations need not build it again.
pub(crate) fn from_smlib_record(handle: solidcraft_smlib::Brep, record: Brep) -> Result<Body> {
Ok(Body::bare(solid_record(record)?, Some(handle), None))
/// The body of SMLib's export of a B-rep (its record, in canonical order). The SMLib body
/// the export came from is not kept: the export is not exact to the bit (its parameters,
/// radians, pcurves), so that body measures and operates a little differently from the
/// record's import, and a body evaluated afresh would then differ from the same body read
/// back from a file. Every operation starts from the record's import ([`Body::smlib`]).
pub(crate) fn from_smlib_record(record: Brep) -> Result<Body> {
Ok(Body::bare(solid_record(record)?, None))
}
/// A mesh body of `mesh`.
pub(crate) fn of_mesh(mesh: crate::meshbody::TriMesh) -> Body {
Body::bare(empty_record(), None, Some(std::sync::Arc::new(mesh)))
Body::bare(empty_record(), Some(std::sync::Arc::new(mesh)))
}
/// The body's record: faces, edges and vertices in the body's own order. Empty for a mesh body.
+2 -3
View File
@@ -126,7 +126,7 @@ pub fn decode_native_body(bytes: &[u8]) -> Result<Body> {
}
/// An SMLib body as a kernel body: SMLib's export mapped by `solidcraft_smlib` (canonical order,
/// radians, pcurves) is the body's record, and the SMLib body is kept with it.
/// radians, pcurves) is the body's record ([`Body::from_smlib_record`]).
pub fn body_from_smlib(brep: &solidcraft_smlib::Brep) -> Result<Body> {
let _span = solidcraft_profile::span("kernel", "body_from_smlib");
body_from_smlib_ordered(brep).map(|(body, _)| body)
@@ -137,8 +137,7 @@ pub fn body_from_smlib(brep: &solidcraft_smlib::Brep) -> Result<Body> {
pub(crate) fn body_from_smlib_ordered(brep: &solidcraft_smlib::Brep) -> Result<(Body, Option<solidcraft_smlib::history::Order>)> {
let _span = solidcraft_profile::span("kernel", "body_from_smlib_ordered");
let (record, order) = brep.to_native_ordered().map_err(|e| KernelError::Failed(e.to_string()))?;
let handle = brep.try_clone().map_err(|e| KernelError::Failed(format!("SMLib: {e}")))?;
Ok((Body::from_smlib_record(handle, record)?, Some(order)))
Ok((Body::from_smlib_record(record)?, Some(order)))
}
/// `body` in SMLib, with where each of SMLib's faces and edges is in the body's face and edge
+1 -1
View File
@@ -1255,7 +1255,7 @@ mod imp {
checked(&sewn, "stitch")?;
let (record, kept) = sewn.to_native_ordered().map_err(|e| failed(format!("SMLib stitch: {e}")))?;
let h = if same_numbering(r, &back) { sewn.history().ok().and_then(|raw| raw.history(&[order], &kept).ok()) } else { None };
Ok((Body::from_smlib_record(sewn, record)?, h))
Ok((Body::from_smlib_record(record)?, h))
}
pub(super) fn thicken(body: &Body, thickness: f64, symmetric: bool) -> Result<Body> {