Join keeps exact face attachments on faces it leaves unchanged (#420) (#453)

A Union dropped every construction certificate, so after any join no face
could take a durable sketch: sketch.create failed with "this face has no
exact planar source" even on a face the join never touched (#420, and the
"CREATE SKETCH: operation failed" reports #344, #195).

- kernel: map planar sources through a Union from both operands, with the
  same exact-support rule Cut uses. Coplanar supports of the two operands
  carry distinct identities, so a face merged from both stays without one.
- doc: Join extrude and Join box tools are certified like new bodies; a
  cap moved off its designed height (into the target, or past a curved To
  face) is not certified. Join producers are accepted when certifying.

Tests: both operands' faces attach and follow an upstream edit and a
reopen; a merged coplanar face is refused and fixed:true works; a face a
join covers whole is lost without moving to the new roof. Tests that used
a joined face as their "uncertified" example now use a merged face; the
trimmed-face case in a_sketch_on_a_face_stays_on_that_face now stays
attached to the surviving piece.


Claude-Session: https://claude.ai/code/session_01YDDPmNdRoBgkHcLeUDKVfb

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Irakli KourtisandClaude Opus 5.5 authored and GitHub committed 2026-10-12 20:31:32 -04:00
1 parent 9e60080b10
commit 722bd007c8
8 files changed
+199 -41

No files matched your search

+4 -2
View File
@@ -248,8 +248,10 @@ pub fn boolean(a: &Body, b: &Body, op: BoolOp) -> Result<Option<Body>> {
};
}
r = r.map(|result| result.map(|body| body.inherit_regularization_protection(a).inherit_regularization_protection(b)));
if matches!(op, BoolOp::Cut | BoolOp::Union) {
r = r.and_then(|result| result.map(|body| body.with_preserved_boolean_planar_sources(a, b, op == BoolOp::Cut)).transpose());
match op {
BoolOp::Cut => r = r.and_then(|result| result.map(|body| body.with_preserved_boolean_planar_sources(a, b, true)).transpose()),
BoolOp::Union => r = r.and_then(|result| result.map(|body| body.with_preserved_union_planar_sources(a, b)).transpose()),
BoolOp::Intersect => {}
}
if r.is_ok() {
merge(diagnostics);