From ec3a21778ee4b421b963797fcee593cdab598730 Mon Sep 17 00:00:00 2001 From: Brett Lawson Date: Sat, 31 Oct 2026 00:35:23 +0100 Subject: [PATCH] smlib: a blend on solidcore has a history, as on the C++ kernel solidcore#1594 carries the body's attributes through its fillets, so a solidcore blend's faces and edges keep their tags. Brep::made_core_blend, which marked a solidcore blend's history untracked ("not supported yet: the history of a blend on solidcore (solidcore#1594)"), goes: solidcore's blends are made_core bodies, as its other operations' are, and Brep::history reads them. tags_cross_a_fillet runs again, and tags_cross_a_chamfer joins it: one edge of a box rounded or chamfered keeps the bottom and back, trims the top, front and sides and makes the blend face, on both kernels, with the same history face by face. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01MNXLXkZLQ6T7TSi4wswcmk --- crates/smlib/src/core/blend_tests.rs | 51 +++++++++++++++++++++++----- crates/smlib/src/history.rs | 3 -- crates/smlib/src/lib.rs | 13 ++----- crates/smlib/src/ops.rs | 8 ++--- 4 files changed, 49 insertions(+), 26 deletions(-) diff --git a/crates/smlib/src/core/blend_tests.rs b/crates/smlib/src/core/blend_tests.rs index d36d10ad29..1fb76a80c2 100644 --- a/crates/smlib/src/core/blend_tests.rs +++ b/crates/smlib/src/core/blend_tests.rs @@ -510,19 +510,54 @@ fn the_first_distance_goes_on_the_face_along_first() { /// the two results' histories must be the same. #[cfg(feature = "native")] mod history { + use solidcraft_geom::brep::history::Change; + + use crate::history::Raw; use crate::{Blend, Brep, ffi}; + /// A 20 x 30 x 10 box, tagged as operand 0, with `kind` of `size` on its top front edge + /// (y = 0, z = 10), on both kernels: solidcore's history, then the C++ kernel's. + fn one_edge(kind: Blend, size: f64) -> (Raw, Raw) { + crate::init().unwrap(); + let a = Brep::made(ffi::make_box(&[0.0; 3], &[20.0, 30.0, 10.0]).unwrap()).tagged(0).unwrap(); + let edge = [[10.0, 0.0, 10.0]]; + let code = match kind { + Blend::Round => ffi::ops::BLEND_ROUND, + Blend::Curvature => ffi::ops::BLEND_CURVATURE, + Blend::Chord => ffi::ops::BLEND_CHORD, + Blend::Chamfer => ffi::ops::BLEND_CHAMFER, + }; + let cpp = ffi::ops::blend(code, a.cpp().unwrap(), &edge, &[size], super::TOL).unwrap(); + let cpp = Brep::made(cpp).history().unwrap(); + let core = super::blend(a.core().unwrap(), kind, &edge, &[size], super::TOL, &[], &[], super::Corners::Filled).unwrap(); + (Brep::made_core(core).history().unwrap(), cpp) + } + + /// The changes of `h`'s faces, sorted: one blend of one box edge leaves the bottom and back + /// as they were, trims the top, front and both sides, and makes the blend face. + fn changes(h: &Raw) -> Vec { + let mut c: Vec = h.faces.iter().map(|f| f.change).collect(); + c.sort_by_key(|c| *c as u8); + c + } + + fn check(kind: Blend, size: f64) { + let (core, cpp) = one_edge(kind, size); + let want = [Change::Kept, Change::Kept, Change::Modified, Change::Modified, Change::Modified, Change::Modified, Change::Generated]; + assert_eq!(changes(&cpp), want, "C++ {kind:?}: {cpp:?}"); + assert_eq!(core.canonical(), cpp.canonical(), "{kind:?}"); + } + /// A 2 mm round on the top front edge of a box: the top and front are trimmed, the round is /// new. #[test] - #[ignore = "solidcore#1594: the fillet does not carry the body's attributes onto the faces it rebuilds (the top, front and sides come out Generated)"] fn tags_cross_a_fillet() { - crate::init().unwrap(); - let a = Brep::made(ffi::make_box(&[0.0; 3], &[20.0, 30.0, 10.0]).unwrap()).tagged(0).unwrap(); - let edge = [[10.0, 0.0, 10.0]]; - let cpp = ffi::ops::blend(ffi::ops::BLEND_ROUND, a.cpp().unwrap(), &edge, &[2.0], super::TOL).unwrap(); - let cpp = Brep::made(cpp).history().unwrap(); - let core = super::blend(a.core().unwrap(), Blend::Round, &edge, &[2.0], super::TOL, &[], &[], super::Corners::Filled).unwrap(); - assert_eq!(Brep::made_core(core).history().unwrap().canonical(), cpp.canonical()); + check(Blend::Round, 2.0); + } + + /// A 2 mm chamfer on the same edge: the same faces kept, trimmed and made as the round. + #[test] + fn tags_cross_a_chamfer() { + check(Blend::Chamfer, 2.0); } } diff --git a/crates/smlib/src/history.rs b/crates/smlib/src/history.rs index dafd1cf17a..b5b682e63a 100644 --- a/crates/smlib/src/history.rs +++ b/crates/smlib/src/history.rs @@ -87,9 +87,6 @@ impl Brep { /// that made the body: the tags never cross between them. pub fn history(&self) -> Result { if self.maker == route::Backend::Solidcore { - if let Some(what) = self.untracked { - return Err(Error::Solidcore(format!("smlib_history_read: not supported yet: {what}"))); - } return core::history::read(self.core()?); } let v = ffi::history::read(self.cpp()?).map_err(error)?; diff --git a/crates/smlib/src/lib.rs b/crates/smlib/src/lib.rs index 982915f524..46409751e8 100644 --- a/crates/smlib/src/lib.rs +++ b/crates/smlib/src/lib.rs @@ -236,9 +236,6 @@ pub struct Brep { /// `Some(k)` for [`Brep::tagged`]`(k)`: its faces and edges stand for their own positions in /// operand `k`, so the body converted for the other kernel is tagged the same way there. operand: Option, - /// Why [`Brep::history`] cannot be read, for a body made by a solidcore operation that does - /// not carry the attributes yet. - untracked: Option<&'static str>, } impl std::fmt::Debug for Brep { @@ -250,18 +247,12 @@ impl std::fmt::Debug for Brep { impl Brep { /// A body the C++ kernel made. fn made(h: ffi::Handle) -> Brep { - Brep { cpp: OnceLock::from(Ok(h)), core: OnceLock::new(), maker: route::Backend::Smlib, operand: None, untracked: None } + Brep { cpp: OnceLock::from(Ok(h)), core: OnceLock::new(), maker: route::Backend::Smlib, operand: None } } /// A body solidcore made. fn made_core(b: core::Body) -> Brep { - Brep { cpp: OnceLock::new(), core: OnceLock::from(Ok(b)), maker: route::Backend::Solidcore, operand: None, untracked: None } - } - - /// A body a solidcore blend made: solidcore's fillets do not carry the body's attributes onto - /// the faces they rebuild yet (solidcore#1594), so its history is not read. - fn made_core_blend(b: core::Body) -> Brep { - Brep { untracked: Some("the history of a blend on solidcore (solidcore#1594)"), ..Brep::made_core(b) } + Brep { cpp: OnceLock::new(), core: OnceLock::from(Ok(b)), maker: route::Backend::Solidcore, operand: None } } /// The C++ kernel's body: converted from solidcore's the first time it is asked for (and diff --git a/crates/smlib/src/ops.rs b/crates/smlib/src/ops.rs index c025280ecb..ef0cc37b9b 100644 --- a/crates/smlib/src/ops.rs +++ b/crates/smlib/src/ops.rs @@ -280,7 +280,7 @@ impl Brep { } if route::Op::Blend.backend() == route::Backend::Solidcore { return core::blend::blend_holes(self.core()?, kind, edges, sizes, tolerance, corners, setbacks, core::blend::Corners::Open) - .map(|(b, holes)| (Brep::made_core_blend(b), holes)); + .map(|(b, holes)| (Brep::made_core(b), holes)); } init()?; let kind = match kind { @@ -301,7 +301,7 @@ impl Brep { /// (the edge curve's parameter scaled to 0..1). pub fn fillet_variable(&self, edge: [f64; 3], tolerance: f64, start: [f64; 3], degree: usize, knots: &[f64], radii: &[f64]) -> Result { if route::Op::Blend.backend() == route::Backend::Solidcore { - return core::blend::fillet_variable(self.core()?, edge, tolerance, start, degree, knots, radii).map(Brep::made_core_blend); + return core::blend::fillet_variable(self.core()?, edge, tolerance, start, degree, knots, radii).map(Brep::made_core); } init()?; let degree = i32::try_from(degree).map_err(|_| Error::Argument)?; @@ -321,7 +321,7 @@ impl Brep { return Err(Error::Argument); } if route::Op::Blend.backend() == route::Backend::Solidcore { - return core::blend::chamfer_sides(self.core()?, edges, tolerance, distance, second, first).map(Brep::made_core_blend); + return core::blend::chamfer_sides(self.core()?, edges, tolerance, distance, second, first).map(Brep::made_core); } init()?; let (value, angle) = match second { @@ -417,7 +417,7 @@ impl Brep { if edges.len() > MAX_POINTS || edges.is_empty() { return Err(Error::Argument); } - core::blend::blend(self.core()?, kind, edges, sizes, tolerance, corners, setbacks, open).map(Brep::made_core_blend) + core::blend::blend(self.core()?, kind, edges, sizes, tolerance, corners, setbacks, open).map(Brep::made_core) } fn pick_op(&self, kind: ffi::ops::Pick, points: &[[f64; 3]], tolerance: f64, size: f64, allow_none: bool) -> Result {