Skip to content

Clip fault assemblies against the mesh's own boundary; label the outcrop trace - #582

Open
lmoresi wants to merge 5 commits into
developmentfrom
bugfix/fault-clip-boundary
Open

Clip fault assemblies against the mesh's own boundary; label the outcrop trace#582
lmoresi wants to merge 5 commits into
developmentfrom
bugfix/fault-clip-boundary

Conversation

@lmoresi

@lmoresi lmoresi commented Aug 16, 2026

Copy link
Copy Markdown
Member

Addresses the curved-boundary side of #555 and builds the trace primitive agreed on #553.

What this does

The thin-volume assembly clip (place_thin_volume, 2-D and 3-D) no longer intersects with an analytic axis-aligned box. It intersects with the domain the mesh actually has: the boundary facets (support 1 and unshared) are gathered collectively, stitched by exact coordinate identity, and rebuilt as OCC geometry — collinear runs and coplanar triangles compressed to their corners, so a box constructs the same four-sided / six-faced tool addRectangle/addBox did, and the boolean does not imprint the wall's mesh spacing onto the clipped faces. The cut lands on the mesh's own facets (measured 3.5e-17 from the chords, against the 8.6e-3 sagitta a cut on the smooth circle would leave), so no corefinement library is needed and the clip-then-mesh order is preserved: the ribbon still meshes at gmsh quality.

The outcrop band is now identified as the trace — membership of the domain's own boundary facets, defined on any boundary — and the band facets carry <label>_trace alongside the skin and wall labels, in both dimensions. The trace is the intersection itself; it never partitions the wall into named pieces, so an annulus (closed wall) and a 3-D footprint need no naming convention. info["n_trace_facets"] reports the count.

The 2-D carve/sew is fully general:

  • the ring splice detects the wall span topologically (boundary-edge pairs) and orders coverage and chain orientation by arc projection onto the removed span's own polyline;
  • boundary-vertex deletability: a vertex goes when the band re-provides its geometry, or when it lies on a band-touched straight segment where the splice's cap stays collinear; a compressed corner off the band is the domain's shape itself and is protected. On a box this reproduces the wall-plane rule exactly;
  • a clipped corner landing within 0.1*size of an imprinted domain vertex is merged into it, along the boundary segment — measured: a 0.18-degree sliver on the annulus radial outcrop restored to 29.4 degrees against the 29.6-degree interior twin, with domain shape and area untouched.

What this does not do yet

The 3-D outcrop keeps the box frame: a zone outcropping on a non-axis-aligned 3-D boundary is refused with the reason (marked TODO(DESIGN) at _trace_wall_code). The general 3-D cap must re-triangulate the bowl-minus-band collar per coplanar boundary region, with 1-D overlays where the band outline crosses creases — a separate, spike-validated piece of work. The 3-D clip, trace identification and trace labelling in this PR are already general; only the carve/sew cap is not.

place_sheet's discrete Sutherland–Hodgman clip is also untouched, deliberately — unifying it is follow-on work.

Evidence

  • Box regression oracle unchanged: test_0854, test_0855, ptest_0854, ptest_0855 pass in serial and at np=2/np=4.
  • New test_0859: the tool's OCC mass equals the mesh's own summed cell measure to round-off in both dimensions, and — the negative control — differs from the smooth-geometry measure by far more, proving the tool is the polygon, not the circle. A cube merges to exactly six faces.
  • New annulus outcrop test: interior twin as negative control (no trace), area conserved to 1e-12, every boundary edge relabelled, trace coincides with the band, minimum angle floored at 15 degrees, P2 oracle exact through the zone.
  • Parallel: annulus outcrop passes at np=2, 3, 4; counts identical at np=1/2/4 (trace=1, zone=44, skin=24, placed=60, removed=17) — the trace is a function of the mesh, not the partition.
  • Full level-1 suite: 1499 passed. Style gates clean.

Underworld development team with AI support from Claude Code

… geometry

The assembly clip (place_thin_volume, 2-D and 3-D) now intersects with the
domain the mesh actually has: unshared support-1 facets are gathered
collectively, stitched by exact coordinate identity, and rebuilt in OCC —
collinear runs (coplanar triangles, one dimension up) compressed to their
corners, so a box constructs the same four-sided / six-faced tool the
analytic addRectangle/addBox clip did, and the boolean does not imprint the
wall's mesh spacing onto the clipped faces. The post-mesh snap becomes a
pure normal-component move onto the boundary's own facets, which on an
axis-aligned wall reproduces the wall-plane snap exactly.

The cut lands on the mesh's own facets (measured 3.5e-17 from the chords,
against the 8.6e-3 sagitta a cut on the smooth circle would leave), so no
corefinement library is needed and clip-then-mesh order is preserved.

Box regression oracle unchanged: test_0854, test_0855 serial and at
np=2/np=4. New tool unit tests in test_0859 assert exactness both ways —
OCC mass equals the mesh's summed cell measure to round-off, and differs
from the smooth-geometry measure by far more (the negative control).

Outcrop-band identification still runs against the axis-aligned box frame;
generalising the band to the boundary trace is the next stage.

Underworld development team with AI support from Claude Code
The band split no longer tests coordinate equality against wall-plane
values: _split_skin_trace takes the domain's own boundary complex and
keeps a skin facet when every vertex and its centroid lie on it — exact
after the post-clip snap, and defined on any boundary. The single-wall
frame the carve/sew still runs in is derived afterwards by
_trace_wall_code, which keeps the established refusals (more than one
wall; and, until the general carve/sew exists, a non-axis-aligned
outcrop).

The band facets — the intersection itself, never a partition of the wall
into named pieces — now carry 'label_trace' alongside the skin and wall
labels, full closures included, in 2-D and 3-D, and the facet count is
reported as info['n_trace_facets']. The outcrop tests assert the trace
coincides with the band, with the interior twin as the negative control.

Underworld development team with AI support from Claude Code
…lus outcrop lands

The 2-D outcrop machinery no longer assumes a straight axis-aligned wall:

- The ring splice detects the wall span topologically (the mesh's own
  boundary-edge pairs), and coverage and chain orientation are arc
  projections onto the removed span's polyline, so a curved boundary
  orders exactly as a straight wall did.
- Which boundary vertices the carve may delete is decided by
  _outcrop_frame_2d: a vertex goes when the band re-provides its geometry
  (it lies ON the band) or when it lies on a band-touched straight
  segment, where the splice's cap stays collinear; a compressed corner
  off the band is the domain's shape itself and is protected. On a box
  this reproduces the wall-plane rule exactly.
- One contiguous band only (_refuse_multiple_bands); two ribbons out two
  walls and an arch out the same wall twice keep their refusals.
- The boolean imprints tool corners into the clipped face; a clipped
  corner within 0.1*size of an imprinted domain vertex is merged into it
  (_collapse_boundary_imprints) — measured 0.18-degree sliver restored to
  29.4 degrees against the 29.6 interior twin, with the domain's shape
  and area untouched since the merge moves the band end along the
  boundary segment.

New test: a radial ribbon out of the annulus's outer boundary embeds,
with the interior twin as the negative control; area conserved to 1e-12,
every boundary edge relabelled, trace coincides with the band, minimum
angle floored at 15 degrees, and the P2 oracle exact through the zone.
Box tests unchanged, serial and np=4. The 3-D cap keeps the box frame;
generalising it is the next stage.

Underworld development team with AI support from Claude Code
…rtition-independent

The annulus outcrop runs through the same gather-first mechanism at
np=2, 3 and 4, and the info dict — trace count included — is identical
at np=1, 2 and 4 (measured: trace=1, zone=44, skin=24, placed=60,
removed=17 at every rank count), because the boundary complex is
gathered from unshared support-1 facets and sorted into a canonical
order before the clip.

The new ptest's boundary census skips SHARED facets, not just SF
leaves: a partition-seam facet also has local support 1 on its owner
side, and a leaves-only probe reports phantom bare boundary edges.

Underworld development team with AI support from Claude Code
Copilot AI lite review requested due to automatic review settings August 16, 2026 08:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@lmoresi

lmoresi commented Aug 16, 2026

Copy link
Copy Markdown
Member Author

Adversarial review (self, pre-merge):

Found and fixed during the work — reporting the negative results:

  1. The first annulus outcrop meshed a 0.18-degree sliver. Probe: the two offending nodes were an imprinted domain vertex (bit-exact match with the boundary loop) and the ribbon's clipped corner 7.3e-5 away on the same chord — the boolean forces both into the layer mesh. Fixed by _collapse_boundary_imprints (merge along the boundary segment, after the meshed-vs-CAD gate); the outcrop now meshes at 29.4 degrees against the interior twin's 29.6.
  2. Our first parallel census reported 21 "bare" boundary edges at np=2. That was the probe, not the code: a partition-seam facet also has local support 1 on its owner side, and a leaves-only filter counts it as boundary. We made exactly the mistake _true_wall_vertex_mask's docstring warns about; the probe now skips shared facets.

Checked and held:

  • The snap is a pure normal-component move, so on an axis-aligned wall the off-axis coordinate is set exactly and the tangential coordinate is bit-untouched — box parity is by construction, and the box oracle did not move at any stage.
  • _split_skin_trace requires every vertex AND the centroid on the boundary, so a skin facet spanning between two boundary contacts through the interior cannot classify as trace.
  • Counts are identical at np=1/2/4 because _domain_boundary_facets sorts the gathered complex into canonical order before the rank-0 clip.

Residual risks we have argued but not measured:

  • _collapse_boundary_imprints only merges into a node that exactly matches a domain vertex; a crossing near a compressed-away vertex leaves no second node and therefore no sliver — argued from the tool construction, not measured on a box corner case.
  • _occ_domain_3d is measured only at test scale (a few hundred boundary triangles). A fine spherical shell builds one OCC face per triangle; cost on thousands-of-face tools is unmeasured. The reach restriction from the design note remains available if it bites.
  • The trace membership tolerance (1e-9) is metric. The separation to the nearest non-trace facet is the layer mesh size, five orders larger, but it is a tolerance, not a topological identity.

Deferred, explicitly: the general 3-D cap (bowl-minus-band collar per coplanar region) — refused with the reason and marked TODO(DESIGN); place_sheet's discrete clip — untouched by design to keep the diff reviewable.

…moved segment

The general carve/sew subsumes the box-corner outcrop the wall-code
frame used to refuse: the corner vertex lies ON the band, so it is
deletable and the band re-provides its exact position. What the corner
exposed was the label harvest, not the geometry — labels common to ALL
removed wall edges are empty across a corner (Top one side, Right the
other), leaving the new wall edges bare. The harvest is now per removed
edge, and each splice/band edge takes the labels of the removed segment
it lies on, so the corner node itself recovers both walls' labels
through the two closures. Uniform spans (one wall, the annulus loop)
are unchanged.

Pinned by a new test: the diagonal corner ribbon embeds with area
conserved to 1e-12, the trace present on both walls, no bare wall edge,
and minimum angle above 15 degrees. Two separate bands stay refused.

Underworld development team with AI support from Claude Code
@lmoresi

lmoresi commented Aug 16, 2026

Copy link
Copy Markdown
Member Author

Review addendum (04cca8e): probing the question "is every 2-D outcrop case covered?" found one that was neither tested nor refused. The general carve/sew had silently subsumed the box-corner outcrop the old wall-code frame refused — a single ribbon out through the (1,1) corner embeds, area conserved to 1e-12 — but the wall-label harvest was wrong there: labels common to ALL removed span edges are empty across a corner (Top one side, Right the other), leaving four bare wall edges. The harvest is now per removed edge and each new wall edge takes the labels of the segment it lies on; the corner node recovers both walls' labels through the two closures. Pinned by a new test; uniform spans (one wall, the annulus loop) unchanged; two separate bands stay refused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants