Skip to content

0092 — Mesh sections report the solid's cut as closed regions ​

  • Status: Accepted
  • Date: 2026-10-29
  • Deciders: Friedrich Schrödter
  • Supersedes: —
  • Amends: 0091
  • Amended by: 0093

Context ​

Issue #372 (consumer: rheonda/cad). A wall whose sill lies exactly in the cutting plane has piers that the plane cuts across. ADR 0091 reports those as open SectionChains that end on the sill's InPlaneRegion, and the sill itself as a region with a side. A consumer that wants to hatch "the wall cut just below the plane" has to close each chain along the right region edges by itself: it must know which region edges the cut lies across, and which vertices are shared. That is topology the kernel already holds, and the consumer cannot rebuild it from rounded coordinates.

The kernel also must not choose "just below" or "just above" for the consumer, which ADR 0091 decided. The question here is what the kernel can report without choosing.

Decision ​

An opt-in request returns the cut of a solid as closed regions. SectionRequest::with_cut_regions(CutRegionLimits) makes PlaneSection::cut_regions a Some(Vec<CutRegion>); without it the field is None. All types are additive (#[non_exhaustive]), so no breaking change and no semver-exceptions entry.

  • Semantics. The cut is the closure of the plane points that have the solid's material on both sides: where the solid's interior meets the plane. In-plane faces are not part of it. They stay InPlaneRegions with their side (ADR 0091). A cut just below the plane is therefore the cut regions together with the Below regions, and a cut just above is the cut regions together with the Above regions. The rings run through bit-identical vertices, so a consumer joins them without topology of its own and still makes the choice.
  • Which region edges bound the cut. Take an edge on the boundary of an in-plane region and the off-plane triangle next to it. If that triangle leaves the plane on the side opposite the region's material (a Below face with the neighbour rising, an Above face with the neighbour falling), the cut lies across the edge. If it leaves on the material side, the edge borders the outside, as at the top of a box or a pedestal. The test is an exact sign comparison on the source vertices; the edge is directed with the cut on its left.
  • Rings. The directed cut segments and the bounding region edges form a graph in which each node has as many outgoing as incoming edges. Rings are traced on source topology, so which edge links to which never depends on a coordinate. A node is dropped when both of its edges are cut segments and they are exactly collinear (the rule of ADR 0091), or when both are region edges and the region ring itself dropped that vertex. A junction of a cut segment and a region edge stays, which keeps chain ends bit-identical to region vertices. Outer rings run counter-clockwise and holes clockwise, so the material lies on the left, and every closed chain of a solid is a cut ring bit for bit. An open chain with both ends on a region is a contiguous run of exactly one ring, forward or reversed.
  • Exactness, amended (amended again below). ADR 0091 decides every step on source data. Two decisions here cannot: whether a ring is an outer ring or a hole, and how rings nest. An edge-crossing node has no exact binary64 position, so these are decided exactly on the returned coordinates: the sign of the polygon area in expansion arithmetic, and orient2d for crossing and containment tests. This is the precedent of the closed chain of a sheet (ADR 0091). The coordinates are the ones the consumer receives, so the decision is auditable on the output.
  • Nesting. A ring is inside another when every vertex of it that is not on the other is strictly inside. The innermost container decides: an outer ring has no container or lies in a hole; a hole lies in an outer ring, which owns it. Candidate edge pairs of a bounding-box sweep and ring pairs of the containment test share one budget (CutRegionLimits::max_pairs). Where two rings touch, the directions in which one leaves the contact point are classified, with exact orient2d, against the material side of the other; directions on both sides mean the rings cross there and are refused, while neighbours that share an edge with opposite directions pass. Two non-adjacent edges of one ring that touch (a repeated vertex) and a spike are refused.
  • Refusals by name. REFUSED_PINCHED_CUT_REGION where the cut boundary touches itself at a vertex, so the pairing at that vertex is not unique. REFUSED_CROSSING_CUT_RINGS where rings cross, overlap along an edge with the material on the same side, or nest inconsistently; this is how overlapping shells that the mesh audit cannot see are refused (a self-intersecting mesh is still not certified, ADR 0033). A provider that reports a Both region refuses a cut-region request with REFUSED_IN_PLANE_FACES_ON_BOTH_SIDES until the meaning of Both is decided. A sheet has no inside: a cut request with SectionRole::Sheet is InvalidInput. A ring that collapses under projection or has zero area is Degenerate. Budgets get their own names: cut regions, cut region vertices, cut pairs.
  • Registry. A provider must answer cut regions exactly when asked: an answer without them, or with unrequested ones, is a contract violation. The rings get the checks of in-plane regions (cardinality, finiteness, adjacent duplicates, canonical start and order), the budgets and the evidence counts (cut_regions, cut_region_vertices, cut_pairs). A provider from before this feature ignores the request and answers with cut_regions == None; the registry treats that as Unsupported and tries the next provider, so the request still fails closed when none traces the cut. A sheet request is refused before dispatch. Orientation is not checked there (#274).
  • Conformance. The region suite runs every fixture again with cut regions requested. A provider whose first such request returns Unsupported does not claim them (cut_regions_claimed == false) and passes. New fixtures with hand-derived cuts: an L-step, a wall at a sill, a wall at a head, a stepped slab, a pocket floor and nested tubes. The suite checks that the answer equals the plain one, the cut regions and their evidence, and the three relations between chains, regions and rings above.

Alternatives considered ​

OptionWhy not
Close the open chains in placeChanges the chain output of ADR 0091 and mixes non-crossings into chains. ADR 0091 already rejected a closed loop around an in-plane face.
Return the regularised plane ∩ solid, in-plane faces includedNo real cut equals it: it merges Below and Above and hides the side, which is the consumer's decision.
Merge each side's regions with the cut into one region per sideA planar union along shared arcs needs a boolean on the output. It can be added later as an option if wanted.
Always return cut regionsThe new refusals (pinch, crossing rings) would reach callers that never asked. Opt-in keeps them away.
Decide outer or hole from segment directionsDirections say which side the material is on, not whether a ring encloses its neighbours. Nesting needs the geometry anyway.

Consequences ​

Positive

  • A floor plan at a sill or head level gets the piers as closed regions, and the consumer composes "just below" or "just above" without any topology.
  • Every output ring is traceable to chains and region rings by identity.

Negative / costs

  • (Amended below.) Outer or hole and nesting use the returned coordinates, not source data; overlapping shells that the audit cannot see are refused when detected and otherwise stay uncertified.
  • The crossing, contact and containment tests are argued complete for polygon overlap, not proven. Contacts are decided locally and exactly: rings that cross through a shared vertex or a vertex on an edge are refused, as are a repeated vertex and a spike in one ring. What remains uncertified is an overlap of shells that no ring edge reveals (ADR 0033).
  • The nesting is quadratic in the worst case; the reference is an oracle, not a performance tier.

Follow-ups / risks to watch

  • Pair the boundary edges at a pinch vertex by angle instead of refusing.
  • Both regions (ADR 0091, landing 3) must say what the cut means there. Decided in 0093: a Both region is a cut region.

Relation to existing code ​

  • crates/contracts/operations/mesh-section/src/cut.rs: the types and refusal names; request.rs: the request, result and evidence fields; fixtures.rs and region_conformance.rs: the suite.
  • crates/execution/dispatch/src/section_request.rs: the answer checks.
  • crates/algorithms/reference/src/section/: request.rs (which region edges bound the cut), cut_regions.rs (ring tracing), plane_points.rs (the exact points of the cut nodes), ring_nesting.rs (nesting).
  • scripts/probe_section_cut_regions_mutants.py: the mutation probe.

Amendment 2026-10-29: decide on the exact source points (#377) ​

Deciding on the returned coordinates was the risk this ADR accepted, and it refused shells that touch in the source. Measured on two boxes that touch along a line, with the plane turned by pi k / 201 for k = 1..=200:

  • With the frame turned and the mesh as it is, 150 of 200 frames were refused as crossing. The source touches exactly; the rounding of the projection to a turned frame (project_local) and of the oblique edge crossings (node_point) made the returned rings penetrate.
  • With the mesh rotated in binary64 and the frame as it is (a consumer that places the mesh in world coordinates), 147 of 200 were refused, and they are exactly the angles at which a rounded corner of one box lies strictly inside the other, checked in rational arithmetic. That overlap is in the input, so refusing it is the exact verdict (ADR 0033). No exact predicate can call it touching; reading it as contact would need a tolerance, which is a separate decision (#377).

Decision. Crossing, contact, containment and the choice between outer ring and hole are decided on exact points of the plane, never on the returned coordinates (this supersedes the Exactness, amended paragraph and the first cost above):

  • A cut node is a source vertex in the plane or the exact crossing of a source edge with it. The plane is the one the classification already uses, through origin, origin + x and origin + y (not the rounded normal). With D(Q) = det[X - O, Y - O, Q - O] (degree 3), a crossing of A-B is (D(A) B - D(B) A) / (D(A) - D(B)), kept as homogeneous dyadic coordinates (x, y, w), w > 0, in the projection that drops one axis.
  • orient is the 3 x 3 determinant (degree 11 in the source coordinates when all three points are crossings) and compare is c_a w_b - c_b w_a (degree 7). Each is evaluated in Intervals of the stored enclosures, and only when that straddles zero in Dyadics. Dyadic has no range limit, so the tiny coordinates of a rotated mesh never cause a refusal. This is the filter pattern of ADR 0068, as hidden lines use it (ADR 0090). axiolid-reference therefore gains axiolid-exact and, through it, num-bigint for the first time; every closure profile that lists the reference crate already lists axiolid-exact, so no profile changes. (ADR 0080's amendment for #236 is not a precedent: there axiolid-exact was already in the closure through axiolid-nurbs.)
  • This reverses the placement argument of ADR 0090, which kept hidden lines out of axiolid-reference so that the umbrella crate would not pull in axiolid-exact and its big integers. The reasons here are that the section provider lives in axiolid-reference and is the exact oracle, and that deciding on rounded output was shown unsound in #377. Moving the section provider into its own crate would not help consumers of axiolid-reference, which would still re-export it.
  • Expansion arithmetic was rejected: degree-11 products underflow for values such as 6e-17 * x, and a range guard would refuse real input. A rounding band or snapping was rejected too: it would accept small real overlaps and needs a proven error bound for the interpolation and the projection (ADR 0091: no tolerance and no rounded coordinate takes part in a decision).
  • A ring is an outer ring or a hole by its turn at its lexicographically smallest vertex, taken after the crossing sweep, which has proven the ring simple, so that vertex is never straight. A ring whose vertices are all collinear stays Degenerate.
  • A sound box of every point (from the interval enclosures) prunes the sweep and the containment pairs; it never decides.

The check for adjacent duplicates among the returned points stays: it is a fail-closed check of the output that the registry requires, not a geometric decision.

Returned coordinates are unchanged bit for bit: those exact points computed in binary64. Rings that touch may therefore cross or overlap by rounding error in the output, and a rounded ring may in rare cases repeat a vertex. The registry checks neither (cardinality, finiteness, adjacent duplicates, canonical start and order, budgets). The contract documents it.

Still refused. An overlap that is in the input, including one that rounding put there, is refused as before. What a consumer does about shells that are unfused by an ulp after its own placement transform is open (#377).

Cost. Building a point is about two degree-3 dyadic determinants. A cable ladder (two rails, ten rungs) in plan took about 0.25 ms per request in release against 0.18 ms before this change. Points whose coordinates are binary64 (source vertices, and crossings that happen to be exact, such as those of an edge that does not move in a coordinate) take a fast path with orient2d and plain comparisons; without it the request took 0.67 ms. Only exactly degenerate cases (contacts, shared collinear edges) reach Dyadic. The nesting stays quadratic in the worst case.

Amendment 2026-11-07: pair the boundary at a pinch vertex (#373) ​

A cut whose boundary meets itself at a vertex (two piers whose footprints meet at a corner, a void whose corner touches its wall) was refused as REFUSED_PINCHED_CUT_REGION. The cut tracer now pairs the edges there, with no tolerance:

  1. Pair by exact angle. The edges at the vertex are ordered counter-clockwise in the frame by exact signs: first the half-plane on the projected axes, then orient within a half (the construction of axiolid_exact_arrangement::Arrangement::fan, correct under a negative frame sign). Outgoing and incoming edges must alternate; each incoming edge continues with the next outgoing edge clockwise, the one that opens its material wedge.
  2. Split each closed walk where it returns to a vertex. No ring repeats a vertex, as the cut contract already promises. Each region is one connected piece of the cut's inside, written as simple rings that may touch. Two corners that touch give two regions; a hole touching its outer ring is a hole that touches it; a hole touching its outer ring twice leaves two regions. The result does not depend on where a walk starts.
  3. Containment of a ring that touches another at every corner. With the split, an island inside a hole can touch it at all its corners, so no vertex is strictly inside or outside. The nesting then decides on the exact midpoint of an inner edge that is off the outer boundary (homogeneous (x1 w2 + x2 w1, y1 w2 + y2 w1, 2 w1 w2)). Rings that coincide stay refused.
  4. Still refused by name. REFUSED_PINCHED_CUT_REGION for an edge in and an edge out along one ray and for a chain through the pinch that the rings would part (chains are not split at a pinch, ADR 0091); REFUSED_CROSSING_CUT_RINGS for edges that do not alternate and for two edges of one direction along one ray (the material overlaps); a zero-length edge is Degenerate. A Both ring that repeats a vertex stays refused, because it must equal a cut region bit for bit.

The conformance fixtures are RegionsOrRefused: a provider may still refuse a pinch by name.

Rejected.

  • Pairing without the split. A hole touching its outer ring becomes a ring that repeats a vertex, which the contract forbids and ring_nesting refuses.
  • Pairing counter-clockwise, then splitting. The same for two touching corners, but a hole touching its outer ring twice becomes one region whose inside is disconnected (#467).
  • Weakly simple rings, as ADR 0093 uses for in-plane regions. This breaks the cut contract and the outer/hole and contact logic of ring_nesting.

The split is how overlay writes regions (ADR 0104: rings touch at points). One convention for in-plane and visible-face regions is #478; splitting chains at a pinch is #479.

Released under the Mozilla Public License 2.0.