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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MNXLXkZLQ6T7TSi4wswcmk
This commit is contained in:
Brett Lawson
2026-10-31 00:35:23 +01:00
parent c9fcf82a54
commit ec3a21778e
4 changed files with 49 additions and 26 deletions
+43 -8
View File
@@ -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<Change> {
let mut c: Vec<Change> = 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);
}
}
-3
View File
@@ -87,9 +87,6 @@ impl Brep {
/// that made the body: the tags never cross between them.
pub fn history(&self) -> Result<Raw> {
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)?;
+2 -11
View File
@@ -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<usize>,
/// 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
+4 -4
View File
@@ -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<Brep> {
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<Brep> {