Skip to main content

brep_kernel/offset/offset_shell/
pipeline.rs

1use super::*;
2
3/// Put every face on one normal convention (surface normal on the CCW
4/// parameter-winding side) and then restore outward normals. The offset shell's
5/// source and offset skins are assembled as independent components whose
6/// per-face `same_sense` can follow opposite conventions; `validate()` only
7/// checks coedge-direction coherence (the `forward` flags), so a joined,
8/// watertight, coedge-coherent manifold can still be normal-incoherent and
9/// report the wrong volume. Re-deriving `same_sense` from the loop winding makes
10/// the coedge-coherent manifold normal-coherent; the signed-volume flip then
11/// orients it outward. Only invoked after a non-coplanar rim weld, so the
12/// coplanar cylinder/prism shells keep their exact assembled orientation.
13fn coheres_face_normals(solid: &mut BrepSolid) -> Result<(), String> {
14    for face in solid.shells.iter_mut().flat_map(|shell| &mut shell.faces) {
15        face.same_sense = parameter_space_area(face)? > 0.0;
16    }
17    // Outward is a PER-SHELL property: a through-hole shelled at both ends
18    // legitimately produces two disjoint closed components (the outer wall
19    // tube and the hole wall tube), and a single global signed-volume flip
20    // cannot orient both.
21    for shell_index in 0..solid.shells.len() {
22        let single = BrepSolid {
23            id: solid.id,
24            vertices: solid.vertices.clone(),
25            edges: solid.edges.clone(),
26            shells: vec![solid.shells[shell_index].clone()],
27            genus: 0,
28        };
29        if crate::solid_signed_volume(&single)? < 0.0 {
30            flip_shell_faces(&mut solid.shells[shell_index])?;
31        }
32    }
33    Ok(())
34}
35
36pub fn offset_shell(
37    source: &BrepSolid,
38    opening_face_ids: &[u64],
39    distance: f64,
40) -> Result<OffsetShellResultRecord, String> {
41    // First try with oblique hole-wall carriers extended past their opening
42    // planes (the lane that closes tilted/conical through-holes). If that
43    // extension fired but the arrangement could not assemble the harder
44    // configuration it produced (e.g. the extended bore genuinely intersects
45    // other offset walls), fall back to the un-extended pipeline so every
46    // previously-supported shape keeps its exact path and every refusal keeps
47    // the canonical honesty-gate message.
48    let mut extension_fired = false;
49    let mut reflex_rebuilds = 0usize;
50    match offset_shell_impl(
51        source,
52        opening_face_ids,
53        distance,
54        true,
55        &mut extension_fired,
56        &mut reflex_rebuilds,
57    ) {
58        Ok(result) => {
59            // Attribution marker: which pipeline produced this result. A
60            // retry-produced Ok can silently mask a broken extension lane —
61            // always say which attempt won. The reflex-rebuild count rides
62            // along because that lane fires on BOTH attempts: "extension
63            // idle" alone would misread as "the plain pipeline did this".
64            os_debug!(
65                "offset_shell result: FIRST attempt (extension {}, reflex-rebuilt {})",
66                if extension_fired { "fired" } else { "idle" },
67                reflex_rebuilds,
68            );
69            Ok(result)
70        }
71        Err(error) if extension_fired => {
72            os_debug!("extended pipeline failed ({error}); retrying without carrier extension");
73            let mut unused = false;
74            let mut retry_reflex = 0usize;
75            let retried = offset_shell_impl(
76                source,
77                opening_face_ids,
78                distance,
79                false,
80                &mut unused,
81                &mut retry_reflex,
82            );
83            os_debug!(
84                "offset_shell result: RETRY without extension ({}, reflex-rebuilt {retry_reflex})",
85                if retried.is_ok() { "ok" } else { "err" },
86            );
87            retried
88        }
89        Err(error) => Err(error),
90    }
91}
92
93fn offset_shell_impl(
94    source: &BrepSolid,
95    opening_face_ids: &[u64],
96    distance: f64,
97    extend_oblique_carriers: bool,
98    extension_fired: &mut bool,
99    reflex_rebuilds: &mut usize,
100) -> Result<OffsetShellResultRecord, String> {
101    if !distance.is_finite() || distance.abs() <= 1e-7 {
102        return Err("offset_shell: distance must be finite and non-zero".into());
103    }
104    if opening_face_ids.is_empty() {
105        return Err("offset_shell: at least one opening face is required".into());
106    }
107    let source_faces = source
108        .shells
109        .iter()
110        .flat_map(|shell| &shell.faces)
111        .collect::<Vec<_>>();
112    let opening_set = opening_face_ids.iter().copied().collect::<HashSet<_>>();
113    if opening_set
114        .iter()
115        .any(|id| !source_faces.iter().any(|face| face.id == *id))
116    {
117        return Err("offset_shell: opening face does not belong to source".into());
118    }
119    let retained = source_faces
120        .iter()
121        .filter(|face| !opening_set.contains(&face.id))
122        .map(|face| face.id)
123        .collect::<Vec<_>>();
124    if retained.is_empty() {
125        return Err("offset_shell: removing every face cannot produce a shell".into());
126    }
127    let mut carriers = Vec::new();
128    for face_id in &retained {
129        let face = source_faces.iter().find(|face| face.id == *face_id).unwrap();
130        if offset_support_collapsed(face, distance)? {
131            os_debug!("omitting collapsed offset support for source face {face_id}");
132            continue;
133        }
134        // A planar carrier whose source face meets a neighbour at a REFLEX
135        // (concave) edge extends its trim by |distance|: at such a junction
136        // the offset skin must GROW past the source footprint to meet the
137        // neighbour's offset (a pocket wall's source [5,15] → skin
138        // [3.5,16.5]); a source-sized trim leaves a gap no miter can bridge.
139        // The surplus at the face's convex ends is clipped by the imprints +
140        // on-skin filter. Convex-only faces keep their exact source-sized
141        // trim — extending them perturbs delicately-selected fragment
142        // landscapes for no benefit.
143        let miter = source_faces
144            .iter()
145            .find(|face| face.id == *face_id)
146            .map(|face| face_reflex_miter_tan(source, face))
147            .transpose()?
148            .flatten();
149        // The offsets of two faces joined at a reflex edge meet d·tan(θn/2)
150        // past the source rim — d only for a perpendicular join. Scale the
151        // extension to the worst sampled miter (×1.5 headroom so the true
152        // intersection lands strictly INSIDE the extended trim, never on its
153        // boundary where the arrangement drops boundary-coincident cuts) and
154        // cap it: a near-tangential join would ask for an unbounded trim.
155        let reflex_extension = miter.map(|worst_tan| {
156            (distance.abs() * worst_tan * 1.5)
157                .max(distance.abs())
158                .min(distance.abs() * 12.0)
159        });
160        if let Some(extension) = reflex_extension {
161            os_debug!("carrier for src face {face_id}: reflex-extended by {extension:.4}");
162        }
163        // OUTWARD: every retained carrier grows by |distance| across its
164        // sharp and opening-facing sides (the offsets of a convex junction
165        // meet past both source rims; a wall plane cuts the surplus), and
166        // by nothing across a smooth junction into a retained neighbour —
167        // see `outward_carrier_extension`. A reflex junction needs no
168        // outward growth at all (those offsets cross inside the trims).
169        let extension = if distance < 0.0 {
170            outward_carrier_extension(source, face, &source_faces, &opening_set, distance.abs())?
171        } else {
172            crate::CarrierExtension::uniform(reflex_extension.unwrap_or(0.0))
173        };
174        if distance < 0.0 {
175            os_debug!(
176                "carrier for src face {face_id}: outward extension u=({:.3},{:.3}) v=({:.3},{:.3})",
177                extension.u_min,
178                extension.u_max,
179                extension.v_min,
180                extension.v_max,
181            );
182        }
183        let mut carrier = carrier_solid_sided(source, *face_id, distance, &extension)?;
184        if (distance < 0.0 || reflex_extension.is_some()) && face.surface.is_affine()? {
185            // PADDED planes: the pad slides the plane's net while the trim's
186            // loops are cloned in uv, so an INTERIOR loop (a bore or boss rim
187            // in the face) is carried outward with the grown outline instead
188            // of following the neighbour's offset.
189            //   OUTWARD: a bore's rim grew (r=4.68 → 5.15) while the shrunk
190            //   offset bore (r=3.68) that must hole this plane fell inside the
191            //   enlarged void, so neither the imprint nor the orphan-rim weld
192            //   could cut it.
193            //   INWARD, reflex-extended: the same slide moves a REFLEX hole
194            //   rim the wrong way — a sphere sitting under a frustum's base
195            //   annulus (the user's 2026-09-07 document) had its r=8.3 rim
196            //   scaled to 9.22 while the shrunk offset sphere (r=7.3) crosses
197            //   the offset plane at r=7.23, inside the enlarged hole: the
198            //   pair imprint found nothing and the rim stayed one-use.
199            // Drop the interior loops (as the wall carriers do): the
200            // neighbour's carrier imprints — or its rim, welded in as a
201            // hole — cut the true opening, and the seeded selection drops
202            // the disk over it. A CONVEX interior rim on the same face (a
203            // through-bore in a pocket floor) is safe to drop too: its
204            // offset bore imprints at r+d and the ring inside is rejected by
205            // class (Out) or by the on-skin filter, no seed involved.
206            drop_wall_interior_loops(&mut carrier)?;
207        }
208        carriers.push(Carrier {
209            solid: carrier,
210            source_face_id: *face_id,
211            kind: OffsetFaceRole::Offset,
212        });
213    }
214    // APEX-CAP carriers: a retained face whose DEGENERATE edge (an apex
215    // point, e.g. a conic crater's tip) offsets into a RING is an EXTERIOR
216    // cone offset — the parallel surface truncates at the ring and the exact
217    // rolling-ball offset closes it with a SPHERE of radius |distance| about
218    // the apex. Emit that sphere as an extra carrier: the ring imprint
219    // (sphere × offset frustum) splits it, the on-skin filter keeps exactly
220    // the cap sector (its points are |d| from the apex and ≥|d| from the
221    // rest of the face), and the ring becomes two-use.
222    let mut apex_cap_sources = HashSet::default();
223    let mut apex_cap_pairs: Vec<(usize, usize)> = Vec::new();
224    {
225        let retained_count = carriers.len();
226        for index in 0..retained_count {
227            let source_face = source_by_id_lookup(&source_faces, carriers[index].source_face_id);
228            let Some(source_face) = source_face else {
229                continue;
230            };
231            let carrier_surface = carriers[index].solid.shells[0].faces[0].surface.clone();
232            for coedge in source_face
233                .loops
234                .iter()
235                .flat_map(|loop_record| &loop_record.coedges)
236            {
237                let Some(edge) = source
238                    .edges
239                    .iter()
240                    .find(|edge| edge.id == coedge.edge_id)
241                else {
242                    continue;
243                };
244                if !edge.degenerate {
245                    continue;
246                }
247                // Image of the degenerate edge on the offset surface.
248                let [p0, p1] = coedge.pcurve.domain()?;
249                let mut ring = Vec::new();
250                for sample in 0..=8 {
251                    let uv = coedge
252                        .pcurve
253                        .evaluate(p0 + (p1 - p0) * sample as f64 / 8.0)?;
254                    ring.push(carrier_surface.evaluate(uv.x, uv.y)?);
255                }
256                let spread = ring
257                    .iter()
258                    .map(|point| point.sub(ring[0]).length())
259                    .fold(0.0f64, f64::max);
260                if spread <= distance.abs() * 1e-2 {
261                    continue;
262                }
263                let apex = source
264                    .vertices
265                    .iter()
266                    .find(|vertex| vertex.id == edge.start_vertex_id)
267                    .map(|vertex| vertex.point)
268                    .ok_or_else(|| "offset_shell: apex vertex missing".to_string())?;
269                let centroid = ring
270                    .iter()
271                    .fold(Vec3::default(), |sum, point| sum.add(*point))
272                    .scale(1.0 / ring.len() as f64);
273                let axis = apex.sub(centroid);
274                let axis = if axis.length() > 1e-9 {
275                    axis.normalized()?
276                } else {
277                    Vec3::new(0.0, 0.0, 1.0)
278                };
279                os_debug!(
280                    "apex-cap carrier for src face {} at ({:.3},{:.3},{:.3}) r={:.3}",
281                    carriers[index].source_face_id,
282                    apex.x,
283                    apex.y,
284                    apex.z,
285                    distance.abs(),
286                );
287                apex_cap_sources.insert(index);
288                apex_cap_sources.insert(carriers.len());
289                apex_cap_pairs.push((index, carriers.len()));
290                carriers.push(Carrier {
291                    solid: crate::make_sphere_brep(apex, distance.abs(), axis)?,
292                    source_face_id: carriers[index].source_face_id,
293                    kind: OffsetFaceRole::Offset,
294                });
295            }
296        }
297    }
298    for face_id in opening_face_ids {
299        let source_face = source_faces
300            .iter()
301            .find(|face| face.id == *face_id)
302            .unwrap();
303        carriers.push(Carrier {
304            solid: if distance < 0.0 && source_face.surface.is_affine()? {
305                let pad = outward_wall_pad(
306                    source,
307                    source_face,
308                    &source_faces,
309                    &opening_set,
310                    distance.abs(),
311                )?;
312                os_debug!("wall for opening face {face_id}: outward pad {pad:.4}");
313                let mut wall = carrier_solid(source, *face_id, 0.0, pad)?;
314                // The pad extends the wall by STRETCHING the plane's 2x2 net
315                // while the trim pcurves are cloned in UV, so every loop
316                // scales about the patch centre by (w+2|d|)/w. For the OUTER
317                // loop of a full-domain rectangle that lands exactly on the
318                // grown outline — the intended wall extent. But an INTERIOR
319                // loop (a carve outline: a bore, gouge or crater rim in the
320                // opening face) is scaled OUTWARD too, and the enlarged hole
321                // then swallows both circles the wall must be cut by (the
322                // source carve rim from the wall x source imprint and the
323                // shrunk rim from the wall x offset-carrier imprint) — the
324                // imprints clip to nothing and the rims dangle one-use. Drop
325                // interior loops instead: the wall covers the carve mouth,
326                // the imprints cut the true rims, and selection (void-wall
327                // guard + on-skin filter) drops the fragments over the void.
328                drop_wall_interior_loops(&mut wall)?;
329                wall
330            } else {
331                standalone_face(source, *face_id)?
332            },
333            source_face_id: *face_id,
334            kind: OffsetFaceRole::Wall,
335        });
336    }
337    if carriers.len() >= SOURCE_OPERAND as usize {
338        return Err("offset_shell: too many carrier faces for current ABI".into());
339    }
340    // The characteristic length every tolerance below is derived from.  This
341    // was `max ‖vertex‖` — the distance from the WORLD ORIGIN — until audit
342    // slice 0: that form made every band widen as the part was moved away from
343    // the origin while ignoring the part's actual size, so the same shell
344    // solved to different tolerances depending only on where it was modelled.
345    // `solid_scale` is the bbox diagonal, floored at 1.0 exactly as the old
346    // fold was, so this changes ONE thing: placement-dependence.
347    let scale = crate::solid_scale(source);
348    crate::report_scale_migration("offset_shell", scale, || {
349        source
350            .vertices
351            .iter()
352            .map(|vertex| vertex.point.length())
353            .fold(1.0, f64::max)
354    });
355    let smooth = synchronize_smooth_offset_boundaries(&mut carriers, &source_faces, scale)?;
356    // Reflex-rim rebuilds run on BOTH attempts: the fallback for a broken
357    // extension is an honest refusal, but the fallback for a skipped reflex
358    // rebuild is a silently WRONG (membrane-capped) shell.
359    let reflex_rebuilt = rebuild_reflex_rim_carriers(
360        &mut carriers,
361        source,
362        &source_faces,
363        &smooth.pairs,
364        scale,
365        distance,
366    )?;
367    *reflex_rebuilds = reflex_rebuilt.len();
368    if extend_oblique_carriers {
369        let oblique_extended = extend_offset_carriers_past_open_hole_rims(
370            &mut carriers,
371            source,
372            &source_faces,
373            &opening_set,
374            &smooth.pairs,
375            scale,
376        )?;
377        let mut already_extended = oblique_extended.clone();
378        already_extended.extend(reflex_rebuilt.iter().copied());
379        let fallshort_extended = extend_fallshort_curved_carriers(
380            &mut carriers,
381            source,
382            &source_faces,
383            &opening_set,
384            &smooth.pairs,
385            &already_extended,
386            scale,
387            distance,
388        )?;
389        *extension_fired = !oblique_extended.is_empty() || fallshort_extended > 0;
390        os_debug!(
391            "extended {} oblique + {fallshort_extended} fall-short carriers past their opening planes",
392            oblique_extended.len(),
393        );
394    }
395    let tolerance = 1e-7f64.max(scale * 1e-9);
396    let pair_tolerance = (scale * 1e-8).max(2e-6);
397    let reach = distance.abs() * 4.0 + pair_tolerance;
398    let carrier_samples = carriers
399        .iter()
400        .map(|carrier| carrier_extent_points(&carrier.solid))
401        .collect::<Result<Vec<_>, _>>()?;
402    let carrier_bounds = carrier_samples
403        .iter()
404        .map(|samples| Bounds::from_points(samples))
405        .collect::<Option<Vec<_>>>()
406        .ok_or_else(|| "offset_shell: carrier has no boundary samples".to_string())?;
407    let source_by_id = source_faces
408        .iter()
409        .map(|face| (face.id, *face))
410        .collect::<HashMap<_, _>>();
411    let source_samples = source_faces
412        .iter()
413        .map(|face| {
414            let standalone = standalone_face(source, face.id)?;
415            Ok((face.id, edge_sample_points(&standalone)?))
416        })
417        .collect::<Result<HashMap<_, _>, String>>()?;
418    if debug_enabled() {
419        for (index, carrier) in carriers.iter().enumerate() {
420            let face = &carrier.solid.shells[0].faces[0];
421            let samples = &carrier_samples[index];
422            let bounds =
423                samples
424                    .iter()
425                    .fold((Vec3::default(), Vec3::default()), |(low, high), point| {
426                        (
427                            Vec3 {
428                                x: low.x.min(point.x),
429                                y: low.y.min(point.y),
430                                z: low.z.min(point.z),
431                            },
432                            Vec3 {
433                                x: high.x.max(point.x),
434                                y: high.y.max(point.y),
435                                z: high.z.max(point.z),
436                            },
437                        )
438                    });
439            let surface = &face.surface;
440            let rational = surface
441                .control_points
442                .iter()
443                .flatten()
444                .any(|point| (point.w - 1.0).abs() > 1e-12);
445            os_debug!(
446                "carrier[{index}] kind={:?} source_face={} loops={} deg=({},{}) net={}x{} rational={rational} bounds=({:.3},{:.3},{:.3})..({:.3},{:.3},{:.3})",
447                carrier.kind, carrier.source_face_id, face.loops.len(),
448                surface.degree_u, surface.degree_v,
449                surface.control_points.len(),
450                surface.control_points.first().map(|row| row.len()).unwrap_or(0),
451                bounds.0.x, bounds.0.y, bounds.0.z, bounds.1.x, bounds.1.y, bounds.1.z,
452            );
453        }
454        for (index, carrier) in carriers.iter().enumerate() {
455            if !matches!(carrier.kind, OffsetFaceRole::Offset) {
456                continue;
457            }
458            let source_face = source_by_id[&carrier.source_face_id];
459            let carrier_surface = &carrier.solid.shells[0].faces[0].surface;
460            let (ku, kv) = (
461                crate::KnotVector::new(
462                    source_face.surface.knots_u.clone(),
463                    source_face.surface.degree_u,
464                ),
465                crate::KnotVector::new(
466                    source_face.surface.knots_v.clone(),
467                    source_face.surface.degree_v,
468                ),
469            );
470            if let (Ok(ku), Ok(kv)) = (ku, kv) {
471                let [u0, u1] = ku.domain();
472                let [v0, v1] = kv.domain();
473                let mut worst = 0.0f64;
474                let mut worst_at = (0.0f64, 0.0f64);
475                for iu in 0..=24 {
476                    for iv in 0..=24 {
477                        let u = u0 + (u1 - u0) * iu as f64 / 24.0;
478                        let v = v0 + (v1 - v0) * iv as f64 / 24.0;
479                        let expected = face_offsets(source_face)
480                            .at(u, v, -distance)
481                            .map(|sample| sample.point);
482                        let actual = carrier_surface.evaluate(u, v);
483                        if let (Ok(expected), Ok(actual)) = (expected, actual) {
484                            let error = expected.sub(actual).length();
485                            if error > worst {
486                                worst = error;
487                                worst_at = (u, v);
488                            }
489                        }
490                    }
491                }
492                let mut normal_swing = 0.0f64;
493                let mut previous: Option<Vec3> = None;
494                for iu in 0..=48 {
495                    let u = u0 + (u1 - u0) * iu as f64 / 48.0;
496                    let v = (v0 + v1) / 2.0;
497                    if let Ok(normal) = face_normal(source_face, u, v) {
498                        if let Some(previous) = previous {
499                            normal_swing = normal_swing.max(previous.sub(normal).length());
500                        }
501                        previous = Some(normal);
502                    }
503                }
504                let weights = source_face
505                    .surface
506                    .control_points
507                    .iter()
508                    .flatten()
509                    .map(|point| point.w)
510                    .fold((f64::MAX, f64::MIN), |(low, high), w| {
511                        (low.min(w), high.max(w))
512                    });
513                os_debug!(
514                    "carrier[{index}] offset fit error max={worst:.6} at=({:.4},{:.4}) domain_u=({u0:.4},{u1:.4}) normal_step_max={normal_swing:.4} src_net={}x{} src_deg=({},{}) src_w=({:.4},{:.4}) knots_u={:?}",
515                    worst_at.0, worst_at.1,
516                    source_face.surface.control_points.len(),
517                    source_face.surface.control_points.first().map(|row| row.len()).unwrap_or(0),
518                    source_face.surface.degree_u, source_face.surface.degree_v,
519                    weights.0, weights.1,
520                    &source_face.surface.knots_u,
521                );
522            }
523        }
524        os_debug!("smooth pairs: {:?}", smooth.pairs);
525    }
526    let mut imprint = empty_imprint();
527    let mut next_piece_id = 1;
528    let mut next_vertex_id = 1;
529    for first in 0..carriers.len() {
530        for second in first + 1..carriers.len() {
531            let first_source = source_by_id[&carriers[first].source_face_id];
532            let second_source = source_by_id[&carriers[second].source_face_id];
533            if matches!(carriers[first].kind, OffsetFaceRole::Wall)
534                && matches!(carriers[second].kind, OffsetFaceRole::Wall)
535            {
536                // INWARD walls are the opening faces themselves: two openings
537                // never overlap and adjacent ones only touch along their
538                // shared edge, so there is nothing to imprint. OUTWARD walls
539                // are the opening planes EXTENDED by |distance|: the walls of
540                // two ADJACENT openings cross along the line of their shared
541                // source edge, and each wall ring must stop there (the sheet
542                // past that line lies in front of the other opening). Without
543                // the cut the wall × source chain below stays open on the
544                // shared-edge side (its neighbour there is an opening, not a
545                // retained face), the wall never fragments into its ring, and
546                // every source rim along both openings dangles one-use.
547                if distance > 0.0 || !source_faces_adjacent(first_source, second_source) {
548                    continue;
549                }
550            }
551            if smooth.pairs.contains(&(first, second)) {
552                os_debug!("pair ({first},{second}) skipped: smooth");
553                continue;
554            }
555            if !carrier_bounds[first].intersects(carrier_bounds[second], pair_tolerance) {
556                os_debug!("pair ({first},{second}) skipped: bounds");
557                continue;
558            }
559            if !source_faces_adjacent(first_source, second_source)
560                && sample_separation(
561                    &source_samples[&first_source.id],
562                    &source_samples[&second_source.id],
563                ) > reach
564            {
565                os_debug!("pair ({first},{second}) skipped: separation");
566                continue;
567            }
568            let pair = match build_imprints(
569                &carriers[first].solid,
570                &carriers[second].solid,
571                &ImprintOptions {
572                    tolerance,
573                    maximum_fit_points: 96,
574                    local_fit: true,
575                    fit_chunk_points: None,
576                    // Offset carriers can meet in short, tightly curved
577                    // branches. The general SSI default (diagonal / 15) is
578                    // too coarse here and lets independently fitted branches
579                    // miss a shared junction when thickness crosses one of
580                    // those branches.
581                    maximum_ssi_step: Some((scale * 0.0005).max(tolerance * 100.0)),
582                },
583            ) {
584                Ok(pair) => pair,
585                // A failed pair imprint (e.g. the marching SSI exhausting its
586                // step budget on a long closed sphere/plane intersection) is
587                // not fatal to the shell: the affected rim simply stays
588                // unsplit and the rim-weld passes below get to close it. The
589                // final watertightness gate still refuses anything the welds
590                // cannot reach, so tolerating the miss only enlarges the
591                // honest success set.
592                Err(error) => {
593                    os_debug!("pair ({first},{second}) imprint failed: {error}");
594                    continue;
595                }
596            };
597            let pieces_before = imprint.pieces.len();
598            merge_pair_imprint(
599                &mut imprint,
600                pair,
601                first as u8,
602                second as u8,
603                &mut next_piece_id,
604                &mut next_vertex_id,
605                tolerance,
606                scale,
607            );
608            os_debug!(
609                "pair ({first},{second}) src=({},{}) pieces+={}",
610                carriers[first].source_face_id,
611                carriers[second].source_face_id,
612                imprint.pieces.len() - pieces_before,
613            );
614        }
615    }
616    if distance < 0.0 {
617        // OUTWARD shells: the opening-wall carrier is the opening face's
618        // plane EXTENDED by |distance|, so its trim carries only the GROWN
619        // outline. Its inner boundary — where the wall ring stops at the
620        // source solid — is the source outline, and the retained SOURCE faces
621        // are not carriers, so no carrier×carrier pair ever imprints it.
622        // Un-cut, the wall stays one whole-plane fragment covering the
623        // opening void, and the source faces' rim edges dangle one-use.
624        // Imprint each wall carrier against the retained source faces that
625        // share an edge with its opening: the wall then fragments into the
626        // ring (kept) and the opening hole (dropped by the void guard).
627        for index in 0..carriers.len() {
628            if !matches!(carriers[index].kind, OffsetFaceRole::Wall) {
629                continue;
630            }
631            let opening = source_by_id[&carriers[index].source_face_id];
632            for retained_id in &retained {
633                let retained_face = source_by_id[retained_id];
634                if !source_faces_adjacent(opening, retained_face) {
635                    continue;
636                }
637                let neighbor = standalone_face(source, *retained_id)?;
638                let pair = match build_imprints(
639                    &carriers[index].solid,
640                    &neighbor,
641                    &ImprintOptions {
642                        tolerance,
643                        maximum_fit_points: 96,
644                        local_fit: true,
645                        fit_chunk_points: None,
646                        maximum_ssi_step: Some((scale * 0.0005).max(tolerance * 100.0)),
647                    },
648                ) {
649                    Ok(pair) => pair,
650                    Err(error) => {
651                        os_debug!(
652                            "wall×source pair ({index},{retained_id}) imprint failed: {error}"
653                        );
654                        continue;
655                    }
656                };
657                let pieces_before = imprint.pieces.len();
658                merge_pair_imprint(
659                    &mut imprint,
660                    pair,
661                    index as u8,
662                    SOURCE_OPERAND,
663                    &mut next_piece_id,
664                    &mut next_vertex_id,
665                    tolerance,
666                    scale,
667                );
668                os_debug!(
669                    "wall×source pair ({index},{retained_id}) pieces+={}",
670                    imprint.pieces.len() - pieces_before,
671                );
672            }
673        }
674    }
675    // APEX-CAP ring splits: the cap × cone section lies ON the cone
676    // carrier's promoted ring edge (an overlap, not a crossing), so
677    // `build_imprints` mints no edge splits for the cone side. Without them
678    // the cone keeps ONE closed ring edge while the cap carries the section
679    // as arcs — mismatched segmentation, both sides one-use. Record the arc
680    // junctions as splits on the cone's coincident edge.
681    for (cone_index, cap_index) in &apex_cap_pairs {
682        let cap_face_id = carriers[*cap_index].solid.shells[0].faces[0].id;
683        let piece_ids = imprint
684            .by_face
685            .iter()
686            .find(|entry| entry.operand == *cap_index as u8 && entry.face_id == cap_face_id)
687            .map(|entry| entry.piece_ids.clone())
688            .unwrap_or_default();
689        let coincidence_band = 2e-5f64.max(scale * 1e-7);
690        for piece_id in piece_ids {
691            let Some((curve, t0, t1)) = imprint
692                .pieces
693                .iter()
694                .find(|piece| piece.id == piece_id)
695                .map(|piece| (piece.curve.clone(), piece.t0, piece.t1))
696            else {
697                continue;
698            };
699            for endpoint in [curve.evaluate(t0)?, curve.evaluate(t1)?] {
700                for edge in &carriers[*cone_index].solid.edges {
701                    if edge.degenerate {
702                        continue;
703                    }
704                    let projection = crate::project_point_to_curve(&edge.curve, endpoint)?;
705                    if projection.distance > coincidence_band
706                        || projection.u < edge.t0 + 1e-9
707                        || projection.u > edge.t1 - 1e-9
708                    {
709                        continue;
710                    }
711                    let parameter = projection.u;
712                    if let Some(existing) = imprint.edge_splits.iter_mut().find(|split| {
713                        split.operand == *cone_index as u8 && split.edge_id == edge.id
714                    }) {
715                        if !existing
716                            .parameters
717                            .iter()
718                            .any(|value| (*value - parameter).abs() <= 1e-8)
719                        {
720                            existing.parameters.push(parameter);
721                        }
722                    } else {
723                        imprint.edge_splits.push(EdgeSplitRecord {
724                            operand: *cone_index as u8,
725                            edge_id: edge.id,
726                            parameters: vec![parameter],
727                        });
728                    }
729                    os_debug!(
730                        "apex-cap ring split: cone carrier {cone_index} edge {} at t={parameter:.6}",
731                        edge.id,
732                    );
733                }
734            }
735        }
736    }
737    if debug_enabled() {
738        for piece in &imprint.pieces {
739            let start = piece.curve.evaluate(piece.t0);
740            let mid = piece.curve.evaluate((piece.t0 + piece.t1) * 0.5);
741            let end = piece.curve.evaluate(piece.t1);
742            if let (Ok(start), Ok(mid), Ok(end)) = (start, mid, end) {
743                os_debug!(
744                    "piece[{}] ({:.4},{:.4},{:.4})..({:.4},{:.4},{:.4})..({:.4},{:.4},{:.4})",
745                    piece.id,
746                    start.x,
747                    start.y,
748                    start.z,
749                    mid.x,
750                    mid.y,
751                    mid.z,
752                    end.x,
753                    end.y,
754                    end.z,
755                );
756            }
757        }
758    }
759    synchronize_smooth_edge_splits(&mut imprint, &smooth.edge_pairs);
760    // This decides whether an imprint is genuinely the carrier's existing
761    // trim, not whether independently fitted curves can later be sewn. The
762    // looser assembly tolerance can erase a short separating branch while it
763    // is still departing a boundary.
764    let boundary_coincidence_tolerance = 2e-5f64.max(scale * 1e-7);
765    let piece_geometry = imprint
766        .pieces
767        .iter()
768        .map(|piece| {
769            (
770                piece.id,
771                FragmentEdgeGeometry {
772                    curve: piece.curve.clone(),
773                    t0: piece.t0,
774                    t1: piece.t1,
775                },
776            )
777        })
778        .collect::<HashMap<_, _>>();
779    for (index, carrier) in carriers.iter().enumerate() {
780        if !matches!(carrier.kind, OffsetFaceRole::Offset) {
781            continue;
782        }
783        let boundaries = carrier
784            .solid
785            .edges
786            .iter()
787            .map(|edge| FragmentEdgeGeometry {
788                curve: edge.curve.clone(),
789                t0: edge.t0,
790                t1: edge.t1,
791            })
792            .collect::<Vec<_>>();
793        if let Some(by_face) = imprint
794            .by_face
795            .iter_mut()
796            .find(|entry| entry.operand == index as u8)
797        {
798            let before = by_face.piece_ids.clone();
799            by_face.piece_ids.retain(|piece_id| {
800                let piece = &piece_geometry[piece_id];
801                !boundaries.iter().any(|boundary| {
802                    fragment_edge_lies_on(piece, boundary, boundary_coincidence_tolerance)
803                        .unwrap_or(false)
804                })
805            });
806            if debug_enabled() && before.len() != by_face.piece_ids.len() {
807                os_debug!(
808                    "carrier[{index}] dropped boundary-coincident pieces: {:?} -> {:?}",
809                    before,
810                    by_face.piece_ids,
811                );
812            }
813        }
814    }
815
816    if debug_enabled() {
817        for entry in &imprint.by_face {
818            os_debug!(
819                "by_face operand={} face={} pieces={:?}",
820                entry.operand,
821                entry.face_id,
822                entry.piece_ids,
823            );
824        }
825    }
826    let mut split_carriers = Vec::new();
827    let mut fragments_by_carrier = Vec::new();
828    for (index, carrier) in carriers.iter().enumerate() {
829        // Offset carriers keep their boundary un-split (their trims never
830        // coincide with imprints — the boundary-coincidence filter drops such
831        // pieces instead). An apex-cap ring breaks that assumption: the
832        // frustum's ring is its TRIM boundary while the cap crosses it as TWO
833        // imprint arcs — without splitting, the two sides segment differently
834        // and both stay one-use. Split exactly the cap-affected carriers.
835        let split = if matches!(carrier.kind, OffsetFaceRole::Offset)
836            && !apex_cap_sources.contains(&index)
837        {
838            carrier.solid.clone()
839        } else {
840            apply_edge_splits(&carrier.solid, index as u8, &imprint)?
841        };
842        let fragments = fragment_solid(&split, index as u8, &imprint)?;
843        os_debug!(
844            "carrier[{index}] kind={:?} src={} fragments={}",
845            carrier.kind,
846            carrier.source_face_id,
847            fragments.len(),
848        );
849        if debug_enabled() {
850            for (fragment_index, fragment) in fragments.iter().enumerate() {
851                let mut boundary_count = 0usize;
852                let mut imprint_pieces = Vec::new();
853                let mut derived_count = 0usize;
854                for coedge in fragment
855                    .loops
856                    .iter()
857                    .flat_map(|loop_record| &loop_record.coedges)
858                {
859                    match &coedge.source {
860                        FragmentEdgeSource::Boundary { .. }
861                        | FragmentEdgeSource::SharedBoundary { .. } => boundary_count += 1,
862                        FragmentEdgeSource::Imprint { piece_id } => imprint_pieces.push(*piece_id),
863                        FragmentEdgeSource::Derived { .. } => derived_count += 1,
864                    }
865                }
866                os_debug!(
867                    "  frag[{index}.{fragment_index}] test=({:.3},{:.3},{:.3}) uv=({:.3},{:.3}) boundary={boundary_count} derived={derived_count} pieces={imprint_pieces:?}",
868                    fragment.test_point.x, fragment.test_point.y, fragment.test_point.z,
869                    fragment.test_uv.x, fragment.test_uv.y,
870                );
871            }
872        }
873        split_carriers.push(split);
874        fragments_by_carrier.push(fragments);
875    }
876    let mut assembly_sources = [(SOURCE_OPERAND, source)]
877        .into_iter()
878        .collect::<HashMap<_, _>>();
879    for (index, carrier) in split_carriers.iter().enumerate() {
880        assembly_sources.insert(index as u8, carrier);
881    }
882
883    let source_edge_by_id = source
884        .edges
885        .iter()
886        .map(|edge| (edge.id, edge))
887        .collect::<HashMap<_, _>>();
888    let retained_faces_with_edges = retained
889        .iter()
890        .map(|face_id| {
891            let face = source_by_id[face_id];
892            let edges = face
893                .loops
894                .iter()
895                .flat_map(|loop_record| &loop_record.coedges)
896                .filter_map(|coedge| source_edge_by_id.get(&coedge.edge_id).copied())
897                .collect::<Vec<_>>();
898            (face, edges)
899        })
900        .collect::<Vec<_>>();
901    let offset_skin_tolerance = 2e-3f64.max(scale * 5e-5).max(distance.abs() * 1e-3);
902    // OUTWARD shells: a wall fragment lying IN FRONT of another opening —
903    // past that opening's plane on its void side, within the extent of its
904    // (extended) wall carrier — is not material: an opening removes
905    // everything in front of it. Such a fragment is the sheet of this wall
906    // between the source outline and the grown outline on the far side of an
907    // ADJACENT opening's shared edge (the x∈[−1,0] strip of an opened −Z
908    // face's wall, in front of the opened −X face). It sits within |distance|
909    // of a retained face's corner edge, so the void-cross-section guard reads
910    // it as wall thickness; only the other opening's plane tells it apart.
911    let mut opening_planes: Vec<(usize, Vec3, Vec3)> = Vec::new();
912    if distance < 0.0 {
913        for (index, carrier) in carriers.iter().enumerate() {
914            if !matches!(carrier.kind, OffsetFaceRole::Wall) {
915                continue;
916            }
917            let opening = source_by_id[&carrier.source_face_id];
918            if !opening.surface.is_affine()? {
919                continue;
920            }
921            let [u0, u1] = opening.surface.domain_u()?;
922            let [v0, v1] = opening.surface.domain_v()?;
923            let (u, v) = ((u0 + u1) * 0.5, (v0 + v1) * 0.5);
924            opening_planes.push((
925                index,
926                opening.surface.evaluate(u, v)?,
927                face_normal(opening, u, v)?,
928            ));
929        }
930    }
931    let in_front_of_other_opening = |wall_index: usize, point: Vec3| -> Result<bool, String> {
932        let band = 2e-3f64.max(scale * 5e-5);
933        for (index, plane_point, outward) in &opening_planes {
934            if *index == wall_index || point.sub(*plane_point).dot(*outward) <= band {
935                continue;
936            }
937            let face = &split_carriers[*index].shells[0].faces[0];
938            let projection = project_point_to_surface(&face.surface, point)?;
939            let uv = Vec2 {
940                x: projection.u,
941                y: projection.v,
942            };
943            if parameter_point_in_face(face, uv, 1e-8)? != PolygonClass::Outside {
944                return Ok(true);
945            }
946        }
947        Ok(false)
948    };
949
950    let mut chosen_offsets = Vec::new();
951    let mut chosen_walls = Vec::new();
952    for (index, carrier) in carriers.iter().enumerate() {
953        let raw_seed = source_seed(source, carrier.source_face_id)?;
954        let seed = if matches!(carrier.kind, OffsetFaceRole::Offset) {
955            offset_seed(
956                source_by_id[&carrier.source_face_id],
957                raw_seed,
958                &split_carriers[index].shells[0].faces[0].surface,
959                distance,
960                2e-3f64.max(scale * 5e-5),
961            )?
962            .unwrap_or(raw_seed)
963        } else {
964            raw_seed
965        };
966        if seed.sub(raw_seed).length() > 1e-9 {
967            os_debug!(
968                "  seed[{index}] moved from ({:.4},{:.4}) to the offset image ({:.4},{:.4})",
969                raw_seed.x,
970                raw_seed.y,
971                seed.x,
972                seed.y
973            );
974        }
975        if matches!(carrier.kind, OffsetFaceRole::Offset) {
976            // Fragments whose test point sits at the full offset distance
977            // from every retained source face are the true offset-skin
978            // regions. Fragments closer to some other face are shadowed
979            // pockets/strips that another carrier owns. Only fall back to the
980            // unfiltered set when nothing qualifies (numerical safety net).
981            let on_skin = fragments_by_carrier[index]
982                .iter()
983                .map(|fragment| {
984                    point_on_offset_skin(
985                        fragment.test_point,
986                        &retained_faces_with_edges,
987                        distance,
988                        offset_skin_tolerance,
989                    )
990                })
991                .collect::<Result<Vec<_>, String>>()?;
992            let skin_filter_active = on_skin.iter().any(|flag| *flag);
993            let mut classified = Vec::new();
994            let mut viable = Vec::new();
995            for (fragment_index, fragment) in fragments_by_carrier[index].iter().enumerate() {
996                let class = classify_point(fragment.test_point, source, tolerance * 10.0)?.class;
997                let excluded_by_class = (distance > 0.0 && class == PointClass::Out)
998                    || (distance < 0.0 && class == PointClass::In);
999                let contact = if excluded_by_class {
1000                    false
1001                } else {
1002                    distance > 0.0
1003                        && fragment_has_sustained_source_contact(
1004                            fragment,
1005                            &source_faces,
1006                            &assembly_sources,
1007                            &imprint,
1008                            2e-3f64.max(scale * 5e-5),
1009                        )?
1010                };
1011                os_debug!(
1012                    "  select[{index}.{fragment_index}] class={class:?} contact={contact} on_skin={} seed_in={:?} uv=({:.3},{:.3})",
1013                    on_skin[fragment_index],
1014                    parameter_point_in_face(&fragment_as_trim(fragment), seed, 1e-8),
1015                    fragment.test_uv.x, fragment.test_uv.y,
1016                );
1017                if excluded_by_class || (skin_filter_active && !on_skin[fragment_index]) {
1018                    continue;
1019                }
1020                classified.push(fragment.clone());
1021                if contact {
1022                    continue;
1023                }
1024                viable.push(fragment.clone());
1025            }
1026            os_debug!("  select[{index}] seed=({:.4},{:.4})", seed.x, seed.y);
1027            let seeded = viable
1028                .iter()
1029                .filter(|fragment| {
1030                    parameter_point_in_face(&fragment_as_trim(fragment), seed, 1e-8)
1031                        .is_ok_and(|class| class != PolygonClass::Outside)
1032                })
1033                .cloned()
1034                .collect::<Vec<_>>();
1035            let seeded_classified = classified
1036                .iter()
1037                .filter(|fragment| {
1038                    parameter_point_in_face(&fragment_as_trim(fragment), seed, 1e-8)
1039                        .is_ok_and(|class| class != PolygonClass::Outside)
1040                })
1041                .cloned()
1042                .collect::<Vec<_>>();
1043            let selected = if viable.is_empty() {
1044                if !skin_filter_active {
1045                    // NOTHING on this carrier reads on-skin: every classified
1046                    // fragment's test point is measurably CLOSER than the
1047                    // offset distance to some retained face — a buried strip
1048                    // another carrier owns. Forcing the largest one in anyway
1049                    // plants a wrong skin patch that collides with the
1050                    // completion phase's reconstruction (3-use edges).
1051                    // Contribute nothing: `complete_opening_boundary_cycles`
1052                    // rebuilds the true skin from the carrier surface, and the
1053                    // watertight gate still refuses if it cannot.
1054                    None
1055                } else {
1056                    let mut by_area = classified
1057                        .into_iter()
1058                        .map(|fragment| {
1059                            let area = parameter_space_area(&fragment_as_trim(&fragment))?.abs();
1060                            Ok((fragment, area))
1061                        })
1062                        .collect::<Result<Vec<_>, String>>()?;
1063                    by_area.sort_by(|a, b| b.1.total_cmp(&a.1));
1064                    by_area.into_iter().next().map(|entry| entry.0)
1065                }
1066            } else if !seeded_classified.is_empty() {
1067                let mut candidates = seeded_classified;
1068                candidates.sort_by(|a, b| {
1069                    a.test_uv
1070                        .sub(seed)
1071                        .length()
1072                        .total_cmp(&b.test_uv.sub(seed).length())
1073                });
1074                candidates.into_iter().next()
1075            } else if viable.len() > 2 {
1076                let mut by_area = viable
1077                    .into_iter()
1078                    .map(|fragment| {
1079                        let area = parameter_space_area(&fragment_as_trim(&fragment))?.abs();
1080                        Ok((fragment, area))
1081                    })
1082                    .collect::<Result<Vec<_>, String>>()?;
1083                by_area.sort_by(|a, b| b.1.total_cmp(&a.1));
1084                by_area.into_iter().next().map(|entry| entry.0)
1085            } else {
1086                let mut candidates = if seeded.is_empty() { viable } else { seeded };
1087                candidates.sort_by(|a, b| {
1088                    a.test_uv
1089                        .sub(seed)
1090                        .length()
1091                        .total_cmp(&b.test_uv.sub(seed).length())
1092                });
1093                candidates.into_iter().next()
1094            };
1095            if let Some(fragment) = selected {
1096                chosen_offsets.push(fragment);
1097            }
1098        } else {
1099            // Keep arrangement order stable. Besides making the result
1100            // reproducible, this matches the reference kernel's Set insertion
1101            // order when the final manifold-wall guard admits fragments.
1102            let opening = source_by_id[&carrier.source_face_id];
1103            let wall_seeds = opening_wall_seeds(
1104                opening,
1105                &split_carriers[index].shells[0].faces[0],
1106                source,
1107                &opening_set,
1108                distance,
1109            )?;
1110            // The wall-seed exemptions below rest on "the coplanar-rim weld
1111            // cuts the void hole later" — which only ever happens on PLANAR
1112            // opening carriers. A curved carrier (e.g. the sphere face used
1113            // directly as the opening) keeps its un-split fragment forever:
1114            // exempting it would seal the opening with the source face itself
1115            // and fabricate a closed hollow solid. Curved carriers therefore
1116            // get the strict void guard.
1117            let carrier_planar =
1118                surface_is_planar(&split_carriers[index].shells[0].faces[0].surface, tolerance)
1119                    .unwrap_or(false);
1120            let mut selected_indices = Vec::new();
1121            for (fragment_index, fragment) in fragments_by_carrier[index].iter().enumerate() {
1122                if parameter_point_in_face(&fragment_as_trim(fragment), seed, 1e-8)?
1123                    == PolygonClass::Outside
1124                {
1125                    if in_front_of_other_opening(index, fragment.test_point)? {
1126                        os_debug!(
1127                            "  wall[{index}.{fragment_index}] skipped: in front of another \
1128                             opening at ({:.3},{:.3},{:.3})",
1129                            fragment.test_point.x,
1130                            fragment.test_point.y,
1131                            fragment.test_point.z,
1132                        );
1133                        continue;
1134                    }
1135                    // Wall = the material cross-section exposed at the
1136                    // opening: every point of a genuine wall fragment lies
1137                    // WITHIN the offset distance of some retained face (it is
1138                    // the thickness between that face and its offset skin). A
1139                    // fragment at ≥ distance from EVERY retained face — and
1140                    // containing no expected wall seed (an un-split cap
1141                    // fragment can span both the void AND the wall annulus;
1142                    // the coplanar-rim weld cuts its hole later) — is the
1143                    // core's own cross-section: a HOLE in the opening (e.g.
1144                    // the corner void where a carve's offset circle passes
1145                    // just inside the opening's corner). Welding it shut
1146                    // would fabricate a wall over the void.
1147                    if point_on_offset_skin(
1148                        fragment.test_point,
1149                        &retained_faces_with_edges,
1150                        distance,
1151                        offset_skin_tolerance,
1152                    )? && !(carrier_planar
1153                        && wall_seeds.iter().any(|wall_seed| {
1154                            parameter_point_in_face(&fragment_as_trim(fragment), *wall_seed, 1e-8)
1155                                .is_ok_and(|class| class != PolygonClass::Outside)
1156                        }))
1157                    {
1158                        os_debug!(
1159                            "  wall[{index}.{fragment_index}] skipped: void cross-section at \
1160                             ({:.3},{:.3},{:.3})",
1161                            fragment.test_point.x,
1162                            fragment.test_point.y,
1163                            fragment.test_point.z,
1164                        );
1165                        continue;
1166                    }
1167                    selected_indices.push(fragment_index);
1168                }
1169            }
1170            for wall_seed in wall_seeds {
1171                let fragments = &fragments_by_carrier[index];
1172                let nearest_containing = fragments
1173                    .iter()
1174                    .enumerate()
1175                    .filter(|(_, fragment)| {
1176                        parameter_point_in_face(&fragment_as_trim(fragment), wall_seed, 1e-8)
1177                            .is_ok_and(|class| class != PolygonClass::Outside)
1178                    })
1179                    .min_by(|(_, first), (_, second)| {
1180                        first
1181                            .test_uv
1182                            .sub(wall_seed)
1183                            .length()
1184                            .total_cmp(&second.test_uv.sub(wall_seed).length())
1185                    })
1186                    .map(|(fragment_index, _)| fragment_index);
1187                // Arrangement fitting can leave the seed just outside every
1188                // fragment by a small amount. Match the reference kernel's
1189                // geometric fallback instead of silently omitting the wall
1190                // fragment and leaving the assembled shell open.
1191                let nearest = if nearest_containing.is_some() {
1192                    nearest_containing
1193                } else {
1194                    let seed_point = split_carriers[index].shells[0].faces[0]
1195                        .surface
1196                        .evaluate(wall_seed.x, wall_seed.y)?;
1197                    fragments
1198                        .iter()
1199                        .enumerate()
1200                        .min_by(|(_, first), (_, second)| {
1201                            first
1202                                .test_point
1203                                .sub(seed_point)
1204                                .length()
1205                                .total_cmp(&second.test_point.sub(seed_point).length())
1206                        })
1207                        .map(|(fragment_index, _)| fragment_index)
1208                };
1209                if let Some(fragment_index) = nearest {
1210                    let candidate = &fragments_by_carrier[index][fragment_index];
1211                    if in_front_of_other_opening(index, candidate.test_point)? {
1212                        continue;
1213                    }
1214                    // Void-cross-section guard. On a PLANAR carrier it applies
1215                    // to the GEOMETRIC FALLBACK only: a fragment that
1216                    // genuinely contains this wall seed is wall content by
1217                    // construction (even when un-split it also spans void —
1218                    // the rim weld cuts the hole later), but a merely-nearest
1219                    // fragment must not resurrect a hole in the opening. On a
1220                    // CURVED carrier no later weld cuts the hole, so even a
1221                    // seed-containing fragment gets the strict guard.
1222                    if (nearest_containing.is_none() || !carrier_planar)
1223                        && point_on_offset_skin(
1224                            candidate.test_point,
1225                            &retained_faces_with_edges,
1226                            distance,
1227                            offset_skin_tolerance,
1228                        )?
1229                    {
1230                        os_debug!(
1231                            "  wall[{index}.{fragment_index}] seed-hit skipped: void \
1232                             cross-section (planar={carrier_planar}) at ({:.3},{:.3},{:.3})",
1233                            candidate.test_point.x,
1234                            candidate.test_point.y,
1235                            candidate.test_point.z,
1236                        );
1237                        continue;
1238                    }
1239                    let existing = chosen_offsets.iter().cloned().chain(
1240                        selected_indices
1241                            .iter()
1242                            .map(|selected| fragments_by_carrier[index][*selected].clone()),
1243                    );
1244                    if !selected_indices.contains(&fragment_index)
1245                        && !would_overuse_existing_boundary(
1246                            candidate,
1247                            existing,
1248                            &assembly_sources,
1249                            &imprint,
1250                            2e-3,
1251                        )?
1252                    {
1253                        selected_indices.push(fragment_index);
1254                    }
1255                }
1256            }
1257            for fragment_index in selected_indices.iter().copied() {
1258                chosen_walls.push(fragments_by_carrier[index][fragment_index].clone());
1259            }
1260        }
1261    }
1262    if debug_enabled() {
1263        for fragment in &chosen_offsets {
1264            os_debug!(
1265                "chosen offset: operand={} src={} test=({:.3},{:.3},{:.3})",
1266                fragment.operand,
1267                carriers[fragment.operand as usize].source_face_id,
1268                fragment.test_point.x,
1269                fragment.test_point.y,
1270                fragment.test_point.z,
1271            );
1272            for (loop_index, loop_record) in fragment.loops.iter().enumerate() {
1273                for coedge in &loop_record.coedges {
1274                    let surface = &fragment.surface;
1275                    let [c0, c1] = coedge.pcurve.domain().unwrap_or([0.0, 0.0]);
1276                    let uv0 = coedge.pcurve.evaluate(c0);
1277                    let uv1 = coedge.pcurve.evaluate(c1);
1278                    if let (Ok(uv0), Ok(uv1)) = (uv0, uv1) {
1279                        let p0 = surface.evaluate(uv0.x, uv0.y);
1280                        let p1 = surface.evaluate(uv1.x, uv1.y);
1281                        if let (Ok(p0), Ok(p1)) = (p0, p1) {
1282                            let kind = match &coedge.source {
1283                                FragmentEdgeSource::Boundary { edge_id, .. } => {
1284                                    format!("bnd:{edge_id}")
1285                                }
1286                                FragmentEdgeSource::SharedBoundary { edge_id, .. } => {
1287                                    format!("sbnd:{edge_id}")
1288                                }
1289                                FragmentEdgeSource::Imprint { piece_id } => {
1290                                    format!("imp:{piece_id}")
1291                                }
1292                                FragmentEdgeSource::Derived { .. } => "derived".to_string(),
1293                            };
1294                            os_debug!(
1295                                "    loop{loop_index} {kind} ({:.4},{:.4},{:.4})..({:.4},{:.4},{:.4})",
1296                                p0.x, p0.y, p0.z, p1.x, p1.y, p1.z,
1297                            );
1298                        }
1299                    }
1300                }
1301            }
1302        }
1303        for fragment in &chosen_walls {
1304            os_debug!(
1305                "chosen wall: operand={} src={} test=({:.3},{:.3},{:.3})",
1306                fragment.operand,
1307                carriers[fragment.operand as usize].source_face_id,
1308                fragment.test_point.x,
1309                fragment.test_point.y,
1310                fragment.test_point.z,
1311            );
1312        }
1313    }
1314    if chosen_offsets.is_empty() {
1315        return Err("offset_shell: carrier intersections produced no offset boundary".into());
1316    }
1317    // Opening-wall fragments can overlap at dense carrier junctions. The
1318    // seed-time guard above handles geometric containment; this final pass
1319    // mirrors the topology-level rule and counts exact fragment edge sources.
1320    let mut boundary_use_count = HashMap::<[i64; 9], usize>::default();
1321    for fragment in &chosen_offsets {
1322        for coedge in fragment
1323            .loops
1324            .iter()
1325            .flat_map(|loop_record| &loop_record.coedges)
1326        {
1327            let key = fragment_edge_segment_key(&coedge.source, &assembly_sources, &imprint)?;
1328            *boundary_use_count.entry(key).or_default() += 1;
1329        }
1330    }
1331    let mut manifold_walls = Vec::new();
1332    for fragment in chosen_walls {
1333        let keys = fragment
1334            .loops
1335            .iter()
1336            .flat_map(|loop_record| &loop_record.coedges)
1337            .map(|coedge| fragment_edge_segment_key(&coedge.source, &assembly_sources, &imprint))
1338            .collect::<Result<Vec<_>, _>>()?;
1339        if !keys
1340            .iter()
1341            .any(|key| boundary_use_count.get(key).copied().unwrap_or(0) >= 2)
1342        {
1343            for key in keys {
1344                *boundary_use_count.entry(key).or_default() += 1;
1345            }
1346            manifold_walls.push(fragment);
1347        }
1348    }
1349    os_debug!(
1350        "manifold walls kept: {} operands={:?}",
1351        manifold_walls.len(),
1352        manifold_walls
1353            .iter()
1354            .map(|fragment| fragment.operand)
1355            .collect::<Vec<_>>(),
1356    );
1357    let chosen_walls = manifold_walls;
1358
1359    let source_fragments = fragment_solid(source, SOURCE_OPERAND, &empty_imprint())?;
1360    let mut selected = source_fragments
1361        .into_iter()
1362        .filter(|fragment| retained.contains(&fragment.source_face_id))
1363        .collect::<Vec<_>>();
1364    let mut face_images = selected
1365        .iter()
1366        .map(|fragment| OffsetShellFaceImageRecord {
1367            role: OffsetFaceRole::Source,
1368            source_face_id: fragment.source_face_id,
1369        })
1370        .collect::<Vec<_>>();
1371    if distance < 0.0 {
1372        for fragment in &mut selected {
1373            flip_fragment(fragment)?;
1374        }
1375    }
1376    if distance > 0.0 {
1377        for fragment in &mut chosen_offsets {
1378            flip_fragment(fragment)?;
1379        }
1380    }
1381    face_images.extend(
1382        chosen_offsets
1383            .iter()
1384            .map(|fragment| OffsetShellFaceImageRecord {
1385                role: OffsetFaceRole::Offset,
1386                source_face_id: carriers[fragment.operand as usize].source_face_id,
1387            }),
1388    );
1389    face_images.extend(
1390        chosen_walls
1391            .iter()
1392            .map(|fragment| OffsetShellFaceImageRecord {
1393                role: OffsetFaceRole::Wall,
1394                source_face_id: carriers[fragment.operand as usize].source_face_id,
1395            }),
1396    );
1397    selected.extend(chosen_offsets);
1398    selected.extend(chosen_walls);
1399    let mut solid = assemble_open_fragments(
1400        selected,
1401        &assembly_sources,
1402        &imprint,
1403        2e-3f64.max(scale * 5e-5),
1404    )?;
1405    orient_open_solid_faces(&mut solid)?;
1406    let connector_images = complete_sharp_offset_connectors(
1407        &mut solid,
1408        source,
1409        &face_images,
1410        distance,
1411        2e-3f64.max(scale * 5e-5),
1412    )?;
1413    os_debug!(
1414        "sharp connector completion added {} faces",
1415        connector_images.len()
1416    );
1417    if !connector_images.is_empty() {
1418        // Wall fragments were arranged before the sharp seam was closed, so
1419        // their prospective inner boundary was an open chain. Rebuild the
1420        // wall from the now-closed source/offset cycles instead of retaining
1421        // that pre-connector fragment.
1422        for shell in &mut solid.shells {
1423            shell.faces.retain(|face| {
1424                !matches!(
1425                    face_images
1426                        .get(face.id.saturating_sub(1) as usize)
1427                        .map(|image| image.role),
1428                    Some(OffsetFaceRole::Wall)
1429                )
1430            });
1431        }
1432        solid.shells.retain(|shell| !shell.faces.is_empty());
1433    }
1434    face_images.extend(connector_images);
1435    orient_open_solid_faces(&mut solid)?;
1436    // Weld curved-face offset rims that the wall fragmentation left orphaned
1437    // into their coplanar opening cap (cylinder / straight-hole shells). This
1438    // is the seam-rim consistency step the offset pipeline previously lacked.
1439    let welded_rims = weld_coplanar_orphan_rims(&mut solid, 2e-3f64.max(scale * 5e-5))?;
1440    os_debug!("welded {welded_rims} coplanar orphan rims into opening caps");
1441    // Weld oblique-wall offset rims (truncated cone) into a ruled frustum
1442    // opening-wall band, rebuilding the flat cap the pipeline mis-builds.
1443    let ruled_rims = weld_ruled_offset_rims(&mut solid, 2e-3f64.max(scale * 5e-5))?;
1444    os_debug!("welded {ruled_rims} non-coplanar offset rims into ruled bands");
1445    // Weld leftover coaxial coplanar rim PAIRS (a through-hole's exposed wall
1446    // thickness at each opening) into new planar annulus faces.
1447    let pair_rims =
1448        weld_coplanar_rim_pair_annuli(&mut solid, &mut face_images, 2e-3f64.max(scale * 5e-5))?;
1449    os_debug!("welded {pair_rims} coplanar rim pairs into annulus walls");
1450    if debug_enabled() {
1451        for (shell_index, shell) in solid.shells.iter().enumerate() {
1452            for face in &shell.faces {
1453                os_debug!(
1454                    "POSTWELD shell {} face {} same_sense={} loops={}",
1455                    shell_index,
1456                    face.id,
1457                    face.same_sense,
1458                    face.loops.len()
1459                );
1460                for (loop_index, loop_record) in face.loops.iter().enumerate() {
1461                    let walk = loop_record
1462                        .coedges
1463                        .iter()
1464                        .map(|coedge| {
1465                            let spin = (|| -> Result<f64, String> {
1466                                let [d0, d1] = coedge.pcurve.domain()?;
1467                                let a = coedge.pcurve.evaluate(d0 + (d1 - d0) * 0.45)?;
1468                                let b = coedge.pcurve.evaluate(d0 + (d1 - d0) * 0.55)?;
1469                                let pa = face.surface.evaluate(a.x, a.y)?;
1470                                let pb = face.surface.evaluate(b.x, b.y)?;
1471                                Ok(pa.cross(pb).z)
1472                            })()
1473                            .unwrap_or(f64::NAN);
1474                            format!(
1475                                "{}{}(spin{:+.0})",
1476                                if coedge.forward { "+" } else { "-" },
1477                                coedge.edge_id,
1478                                spin.signum()
1479                            )
1480                        })
1481                        .collect::<Vec<_>>();
1482                    os_debug!("    loop {} {:?}", loop_index, walk);
1483                }
1484            }
1485        }
1486    }
1487    let completion_carriers = carriers.iter().collect::<Vec<_>>();
1488    let completion_tolerance = 2e-3f64.max(scale * 5e-5);
1489    // Unify geometric duplicate arc records BEFORE completion: a ring that is
1490    // already covered by both its faces (frustum + apex cap) must not read as
1491    // an open boundary, or completion papers over it with duplicate faces.
1492    let duplicate_welds = weld_duplicate_one_use_arcs(&mut solid, completion_tolerance)?;
1493    os_debug!("welded {duplicate_welds} duplicate one-use boundary arcs");
1494    if debug_enabled() {
1495        let mut use_counts = HashMap::<u64, usize>::default();
1496        for coedge in solid
1497            .shells
1498            .iter()
1499            .flat_map(|shell| &shell.faces)
1500            .flat_map(|face| &face.loops)
1501            .flat_map(|loop_record| &loop_record.coedges)
1502        {
1503            *use_counts.entry(coedge.edge_id).or_default() += 1;
1504        }
1505        let open = use_counts.values().filter(|count| **count == 1).count();
1506        os_debug!(
1507            "assembled: faces={} open_edges={open}",
1508            solid
1509                .shells
1510                .iter()
1511                .map(|shell| shell.faces.len())
1512                .sum::<usize>()
1513        );
1514    }
1515    let completed_images = complete_opening_boundary_cycles(
1516        &mut solid,
1517        &completion_carriers,
1518        &face_images,
1519        completion_tolerance,
1520    )?;
1521    os_debug!("completion added {} faces", completed_images.len());
1522    face_images.extend(completed_images);
1523    let solid = merge_same_surface_faces_open(&solid, (1e-7f64).max(scale * 1e-9))?;
1524    let assembly_tolerance = 2e-3f64.max(scale * 5e-5);
1525    // No faceted fallback here: an offset shell must be made of real analytic
1526    // surfaces. Failing loudly beats silently returning a tessellated BREP
1527    // with destroyed face provenance.
1528    let mut solid = finalize_assembled_solid(solid, assembly_tolerance)?;
1529    if ruled_rims + pair_rims > 0 {
1530        // A ruled-band or rim-pair weld joins independently-oriented skins;
1531        // put the whole finalized manifold on one normal convention so the
1532        // shell reports its exact wall volume (coplanar-only shells keep
1533        // their assembled sense).
1534        coheres_face_normals(&mut solid)?;
1535    }
1536    let solid = solid;
1537    face_images = solid
1538        .shells
1539        .iter()
1540        .flat_map(|shell| &shell.faces)
1541        .map(|face| {
1542            face_images
1543                .get(face.id.saturating_sub(1) as usize)
1544                .cloned()
1545                .ok_or_else(|| "offset_shell: merged face lost provenance".to_string())
1546        })
1547        .collect::<Result<Vec<_>, _>>()?;
1548    // A bore's end ring carries the bore wall's provenance whichever
1549    // construction built it (the opening's fragmentation or the rim-pair
1550    // weld), so its name does not depend on the path the rims took.
1551    let bore_rings = claim_bore_end_rings(&solid, &mut face_images)?;
1552    os_debug!("{bore_rings} bore end rings took the bore wall's provenance");
1553    // Honesty gate: a real rim left one-use is a hole in the shell that the
1554    // degenerate marking hides from `validate()` (degenerate edges are allowed
1555    // to be face-local). Refuse such a result instead of shipping a
1556    // non-watertight solid as valid. A rim is a CLOSED edge whose interior
1557    // sweeps measurably away from its seam vertex — a genuine circle, not a
1558    // legitimate pole point placeholder (which stays one-use by design).
1559    let mut rim_use_counts = HashMap::<u64, usize>::default();
1560    for coedge in solid
1561        .shells
1562        .iter()
1563        .flat_map(|shell| &shell.faces)
1564        .flat_map(|face| &face.loops)
1565        .flat_map(|loop_record| &loop_record.coedges)
1566    {
1567        *rim_use_counts.entry(coedge.edge_id).or_default() += 1;
1568    }
1569    if debug_enabled() {
1570        for (shell_index, shell) in solid.shells.iter().enumerate() {
1571            for face in &shell.faces {
1572                let role = face_images
1573                    .get(face.id.saturating_sub(1) as usize)
1574                    .map(|image| image.role);
1575                let c = face.surface.evaluate(0.5, 0.5).unwrap_or_default();
1576                os_debug!(
1577                    "DUMPFACE shell {} face {} affine={} role={:?} loops={} center=({:.3},{:.3},{:.3})",
1578                    shell_index, face.id, face.surface.is_affine().unwrap_or(false), role,
1579                    face.loops.len(), c.x, c.y, c.z
1580                );
1581                for (li, lp) in face.loops.iter().enumerate() {
1582                    let ids = lp.coedges.iter().map(|c| c.edge_id).collect::<Vec<_>>();
1583                    os_debug!("    loop {} edges {:?}", li, ids);
1584                }
1585            }
1586        }
1587        for edge in &solid.edges {
1588            let uses = rim_use_counts.get(&edge.id).copied().unwrap_or(0);
1589            let s = edge.curve.evaluate(edge.t0)?;
1590            let m = edge.curve.evaluate((edge.t0 + edge.t1) * 0.5)?;
1591            let q = edge.curve.evaluate(edge.t0 + (edge.t1 - edge.t0) * 0.25)?;
1592            os_debug!(
1593                "DUMPEDGE edge {} uses={} closed={} deg={} cpts={} cdeg={} knots={:?} start=({:.3},{:.3},{:.3}) q=({:.3},{:.3},{:.3}) mid=({:.3},{:.3},{:.3})",
1594                edge.id, uses, edge.start_vertex_id == edge.end_vertex_id, edge.degenerate,
1595                edge.curve.control_points.len(), edge.curve.degree, edge.curve.knots,
1596                s.x, s.y, s.z, q.x, q.y, q.z, m.x, m.y, m.z
1597            );
1598        }
1599    }
1600    for edge in &solid.edges {
1601        if rim_use_counts.get(&edge.id).copied().unwrap_or(0) != 1
1602            || edge.start_vertex_id != edge.end_vertex_id
1603        {
1604            continue;
1605        }
1606        let anchor = edge.curve.evaluate(edge.t0)?;
1607        let sweep_threshold = 1e-4f64.max(scale * 1e-6);
1608        let sweeps = [0.25, 0.5, 0.75].into_iter().any(|fraction| {
1609            edge.curve
1610                .evaluate(edge.t0 + (edge.t1 - edge.t0) * fraction)
1611                .map(|point| point.sub(anchor).length() > sweep_threshold)
1612                .unwrap_or(false)
1613        });
1614        if sweeps {
1615            if debug_enabled() {
1616                let rim_mid = edge.curve.evaluate((edge.t0 + edge.t1) * 0.5)?;
1617                os_debug!(
1618                    "PROBE orphan rim edge {} anchor=({:.4},{:.4},{:.4}) mid=({:.4},{:.4},{:.4})",
1619                    edge.id,
1620                    anchor.x,
1621                    anchor.y,
1622                    anchor.z,
1623                    rim_mid.x,
1624                    rim_mid.y,
1625                    rim_mid.z
1626                );
1627                for (shell_index, shell) in solid.shells.iter().enumerate() {
1628                    for face in &shell.faces {
1629                        let uses_rim = face
1630                            .loops
1631                            .iter()
1632                            .flat_map(|l| &l.coedges)
1633                            .any(|c| c.edge_id == edge.id);
1634                        let affine = face.surface.is_affine().unwrap_or(false);
1635                        let on = edge_on_surface(edge, &face.surface, 2e-3f64.max(scale * 5e-5))
1636                            .unwrap_or(false);
1637                        let proj = project_point_to_surface(&face.surface, rim_mid)?;
1638                        let contains = if on {
1639                            parameter_point_in_face(
1640                                face,
1641                                Vec2 {
1642                                    x: proj.u,
1643                                    y: proj.v,
1644                                },
1645                                2e-3f64.max(scale * 5e-5),
1646                            )? != PolygonClass::Outside
1647                        } else {
1648                            false
1649                        };
1650                        if on || uses_rim {
1651                            os_debug!(
1652                                "  face {} shell {} affine={} uses_rim={} on_surf={} proj_dist={:.5} contains={}",
1653                                face.id, shell_index, affine, uses_rim, on, proj.distance, contains
1654                            );
1655                        }
1656                    }
1657                }
1658            }
1659            return Err(format!(
1660                "offset_shell: unwelded rim leaves a non-watertight shell \
1661                 (one-use closed edge {} near ({:.3},{:.3},{:.3})); this \
1662                 curved-solid opening is not yet supported",
1663                edge.id, anchor.x, anchor.y, anchor.z
1664            ));
1665        }
1666    }
1667    Ok(OffsetShellResultRecord { solid, face_images })
1668}
1669
1670/// Run offset shell with stable counters and validation diagnostics suitable
1671/// for automated regressions and UI presentation.
1672pub fn offset_shell_with_diagnostics(
1673    source: &BrepSolid,
1674    opening_face_ids: &[u64],
1675    distance: f64,
1676    tolerances: Option<KernelTolerances>,
1677) -> Result<KernelOutcome<OffsetShellResultRecord>, String> {
1678    let policy = tolerances.unwrap_or_else(|| KernelTolerances::for_solid(source, 1e-7));
1679    policy.check()?;
1680    let mut diagnostics = KernelDiagnostics::default();
1681    diagnostics.count_n(
1682        "collect.source_faces",
1683        source
1684            .shells
1685            .iter()
1686            .map(|shell| shell.faces.len() as u64)
1687            .sum(),
1688    );
1689    diagnostics.count_n("collect.opening_faces", opening_face_ids.len() as u64);
1690    diagnostics.measure_max("offset.distance", distance.abs());
1691    diagnostics.measure_max("tolerance.model", policy.model);
1692    let result = offset_shell(source, opening_face_ids, distance)?;
1693    diagnostics.count_n(
1694        "sew.output_faces",
1695        result
1696            .solid
1697            .shells
1698            .iter()
1699            .map(|shell| shell.faces.len() as u64)
1700            .sum(),
1701    );
1702    diagnostics.count_n("sew.face_images", result.face_images.len() as u64);
1703    let validation = result.solid.validate_detailed(&policy);
1704    diagnostics.measure_max("validate.max_pcurve_error", validation.max_pcurve_error);
1705    diagnostics.count_n("validate.issues", validation.issues.len() as u64);
1706    diagnostics.count_n(
1707        "validate.wire_warnings",
1708        validation.wire_warnings.len() as u64,
1709    );
1710    for warning in validation.wire_warnings {
1711        diagnostics.event(
1712            DiagnosticSeverity::Warning,
1713            KernelStage::Validate,
1714            "validate.uv_wire",
1715            warning.message,
1716        );
1717    }
1718    if !validation.issues.is_empty() {
1719        for issue in &validation.issues {
1720            diagnostics.event(
1721                DiagnosticSeverity::Error,
1722                KernelStage::Validate,
1723                "validate.brep",
1724                issue.message.clone(),
1725            );
1726        }
1727        return Err(format!(
1728            "offset_shell: invalid diagnostic result: {:?}",
1729            validation.issues
1730        ));
1731    }
1732    Ok(KernelOutcome {
1733        value: result,
1734        diagnostics,
1735    })
1736}