mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-04 02:55:54 +00:00
Booleans and Shell keep the lines faces were split along on purpose
Split Face records its lines on the body (split_lines): heal and the certified-planar regularizer keep the faces apart along them, and they make the body regularization-protected. On SMLib the boolean, the n-way union and Shell carried the protection over but dropped the lines, so a body with an authored split lost its record after a Union with partial contact (issue #674/#782's case), and a shelled one after Shell. Each result now keeps its operands' lines, each line once (Body::inherit_split_lines), as the old kernel's coplanar join did. regularization_protection_tests: SMLib's shell makes no section strips, so the painted cup no longer has more faces than the ordinary one. The ordinary cup is the box's five kept faces, their five linings and the rim: 11. A paint over a whole face authors no boundary inside it, so the painted cup has the same 11 faces; the split side's line stays an edge on the wall and its lining: 13. The test now asserts those counts exactly (it asserted more than 11 for both, the old strip route's witness) and that the split lines survive. Fixes kernel issue_674_tests::partial_contact_retains_an_authored_split_away_from_the_contact and body::regularization_protection_tests::stamped_shell_keeps_authored_painted_and_split_boundaries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -574,6 +574,22 @@ impl Body {
|
||||
self
|
||||
}
|
||||
|
||||
/// This body with the lines `inputs`' faces were split along on purpose added to its own:
|
||||
/// an operation combining bodies keeps their authored splits (each line once), as it keeps
|
||||
/// their regularization protection.
|
||||
pub(crate) fn inherit_split_lines(self, inputs: &[&Body]) -> Self {
|
||||
let mut lines: Vec<Vec<Vec3>> = self.split_keep().to_vec();
|
||||
for line in inputs.iter().flat_map(|input| input.split_keep()) {
|
||||
if !lines.contains(line) {
|
||||
lines.push(line.clone());
|
||||
}
|
||||
}
|
||||
if lines.len() == self.split_keep().len() {
|
||||
return self;
|
||||
}
|
||||
self.with_split_lines(Some(std::sync::Arc::new(lines)))
|
||||
}
|
||||
|
||||
/// Exact history of the operation that made this body, relative to that operation's
|
||||
/// operands (see [`crate::history`]); `None` when the operation does not report one.
|
||||
pub fn history(&self) -> Option<&crate::history::History> {
|
||||
|
||||
@@ -49,7 +49,9 @@ pub fn union_all(bodies: &[&Body]) -> Result<Body> {
|
||||
if bodies.is_empty() {
|
||||
return Err(KernelError::Invalid("a union of no bodies".into()));
|
||||
}
|
||||
guard("SMLib union", || crate::smlib_boolean::smlib_union_all(bodies))?.with_preserved_union_planar_sources(bodies)
|
||||
guard("SMLib union", || crate::smlib_boolean::smlib_union_all(bodies))?
|
||||
.inherit_split_lines(bodies)
|
||||
.with_preserved_union_planar_sources(bodies)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -64,9 +66,10 @@ fn apart(a: &Body, b: &Body) -> Result<bool> {
|
||||
}
|
||||
|
||||
/// A boolean's result with what its operands' faces stood for carried over: regularization
|
||||
/// protection and the planar construction and shell roles on supports it kept.
|
||||
/// protection, the lines they were split along on purpose, and the planar construction and shell roles on supports it kept.
|
||||
fn with_operand_roles(a: &Body, b: &Body, op: BoolOp, r: Result<Option<Body>>) -> Result<Option<Body>> {
|
||||
let r = r.map(|result| result.map(|body| body.inherit_regularization_protection(a).inherit_regularization_protection(b)));
|
||||
let r = r
|
||||
.map(|result| result.map(|body| body.inherit_regularization_protection(a).inherit_regularization_protection(b).inherit_split_lines(&[a, b])));
|
||||
match op {
|
||||
BoolOp::Cut => r.and_then(|result| result.map(|body| body.with_preserved_boolean_planar_sources(a, b, true)).transpose()),
|
||||
BoolOp::Union => r.and_then(|result| result.map(|body| body.with_preserved_union_planar_sources(&[a, b])).transpose()),
|
||||
|
||||
@@ -187,10 +187,11 @@ fn polyhedron_record(hs: &[HalfSpace], verts: &[Vec3], faces: &[(usize, Vec<usiz
|
||||
/// Hollow a body: walls of `thickness` inside, open at the faces under `open` (SMLib's shell).
|
||||
/// The result's history is over `[b]`: the inner walls are offsets of the faces they line. Each
|
||||
/// kept planar wall and its inner lining carry the outer and inner shell roles of the face they
|
||||
/// came from (face certificates: sketches and press-pulls attach to them).
|
||||
/// came from (face certificates: sketches and press-pulls attach to them), and the body keeps
|
||||
/// the lines its faces were split along on purpose.
|
||||
pub fn shell(b: &Body, open: &[Vec3], thickness: f64) -> Result<Body> {
|
||||
let _span = solidcraft_profile::span("kernel", "shell");
|
||||
crate::offset_backend::shell(b, open, thickness)?.with_shell_planar_sources(b, thickness, open)
|
||||
crate::offset_backend::shell(b, open, thickness)?.inherit_split_lines(&[b]).with_shell_planar_sources(b, thickness, open)
|
||||
}
|
||||
|
||||
/// Tilt the faces near `faces` by `angle` about their line on the neutral plane, so that they
|
||||
|
||||
@@ -18,10 +18,15 @@ fn stamped_shell_keeps_authored_painted_and_split_boundaries() {
|
||||
let side = (0..stock.face_count()).find(|i| stock.planar_face(*i).is_some_and(|p| p.normal().dot(Vec3::X) > 0.999 && p.origin.x == 20.)).unwrap();
|
||||
let split = crate::split_faces(&stock, &[side], &crate::SplitTool::Plane(Plane { origin: Vec3::new(0., 0., 8.), ..Plane::XY })).unwrap();
|
||||
assert!(split.split_lines().is_some());
|
||||
for protected in [painted, split] {
|
||||
// SMLib's shell makes no section strips: the ordinary cup is the stock's five kept faces,
|
||||
// their five linings and the rim (11). A paint over a whole face authors no boundary inside
|
||||
// it, so the painted cup has those same faces; the split side's line stays an edge on both
|
||||
// walls (13).
|
||||
for (protected, faces) in [(painted, ordinary.face_count()), (split, ordinary.face_count() + 2)] {
|
||||
let result = cup(&protected);
|
||||
assert!(result.regularization_protected);
|
||||
assert!(result.face_count() > ordinary.face_count(), "authored boundaries must survive both Shell regularizers");
|
||||
assert_eq!(result.face_count(), faces, "authored boundaries must survive Shell");
|
||||
assert_eq!(result.split_lines(), protected.split_lines());
|
||||
assert!(result.validity().is_empty());
|
||||
let expected = 40. * 30. * 24. - 38. * 28. * 23.;
|
||||
let mesh = result.mesh(&crate::MeshTolerance::chord(1e-5)).unwrap();
|
||||
|
||||
Reference in New Issue
Block a user