kernel: an SMLib blend's corner patch comes from the edges meeting there

SMLib leaves a corner patch where blends meet untagged, and blends() matches no single edge to
it, so it had no origin, and the edges between two blend faces (two chamfers' mitre) none
either. The history checks refused both (modify_history_tests' corner, whole-box and mitre
cases on SMLib). Now a patch without origin is made in place of all the edges of the blend
faces beside it, as truck records a corner, and every edge of a face new to the blend is where
its two faces meet.

modify_history_tests with SOLIDCRAFT_KERNEL=smlib: the three blend cases pass (28 of 30; the
painted shell and the painted box cut's pieces are the shell's and boolean's). Truck: 30 of 30.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bherbruck
2026-10-21 11:40:45 -04:00
committed by jyviko
parent ae1d746ae1
commit a043e4ae2d
+39 -2
View File
@@ -87,7 +87,7 @@ mod imp {
use super::Kind;
use crate::blend_variable::RadiusSpline;
use crate::body::Body;
use crate::history::{BodyHistory, EdgeOrigin, History};
use crate::history::{BodyHistory, Change, EdgeOrigin, FaceOrigin, History};
use crate::native::{body_from_smlib_ordered, smlib_body_ordered};
use crate::{KernelError, Result, guard, record};
@@ -221,7 +221,7 @@ mod imp {
let operand = History::identity(body).edges;
let replaced = blended(body, edges, &h);
solidcraft_smlib::history::blends(&mut h, &operand, &replaced);
Some(h)
Some(with_corners(h))
}
Err(e) => {
log::debug!(target: "smlib", "{what} history: {e}");
@@ -234,6 +234,43 @@ mod imp {
})
}
/// `h` with each blend face still without origin (a corner patch where blends meet) made in
/// place of all the edges of the blend faces next to it, as truck records a corner, and every
/// edge of a face new to the blend recorded as where its faces meet (two chamfers' mitre is
/// where the two chamfers meet).
fn with_corners(mut h: History) -> History {
let mut corners: Vec<(usize, BTreeSet<FaceOrigin>)> = Vec::new();
for (i, f) in h.faces.iter().enumerate() {
if f.change != Change::Generated || !f.origins.is_empty() {
continue;
}
let around: BTreeSet<FaceOrigin> = h
.edges
.iter()
.filter(|e| e.faces.contains(&i))
.flat_map(|e| e.faces.iter().filter(|g| **g != i))
.filter_map(|g| h.faces.get(*g))
.flat_map(|g| g.origins.iter().copied())
.filter(|o| matches!(o, FaceOrigin::Edge { .. }))
.collect();
if !around.is_empty() {
corners.push((i, around));
}
}
for (i, origins) in corners {
if let Some(f) = h.faces.get_mut(i) {
*f = record::generated(origins.into_iter().collect());
}
}
let faces = h.faces.clone();
for e in &mut h.edges {
if e.faces.iter().any(|f| faces.get(*f).is_some_and(|f| f.change == Change::Generated)) {
e.origins = record::meeting(&e.faces, &faces);
}
}
h
}
/// The operand's edges the blend replaced: each picked edge, and every edge no result edge
/// comes from (the rest of the tangent chains), in the operand's edge order.
fn blended(body: &Body, picked: &[usize], h: &History) -> Vec<usize> {