From a043e4ae2d95f69fe7121c6d7d2bbb3dc2af8925 Mon Sep 17 00:00:00 2001 From: bherbruck Date: Wed, 21 Oct 2026 11:40:45 -0400 Subject: [PATCH] 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 --- crates/kernel/src/blend_smlib.rs | 41 ++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/crates/kernel/src/blend_smlib.rs b/crates/kernel/src/blend_smlib.rs index aec3ac35b1..f7bf6739bc 100644 --- a/crates/kernel/src/blend_smlib.rs +++ b/crates/kernel/src/blend_smlib.rs @@ -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)> = Vec::new(); + for (i, f) in h.faces.iter().enumerate() { + if f.change != Change::Generated || !f.origins.is_empty() { + continue; + } + let around: BTreeSet = 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 {