← cad viewer

CAD viewer · #9

Section caps: closing the cut

Post #4 ended with an admission: the section plane cuts the model open but leaves it hollow — you look through the walls into empty space. That gap is closed. Check section in /viewer and the cut surface now renders as a solid face, colored to match each part.

The algorithm, honestly stated

No B-rep Booleans — that's kernel territory. This is the mesh-level version, four steps:

triangles ∩ plane → segments → weld endpoints → closed loops → earcut

For every triangle, find where the plane crosses it (two points = one segment). Weld segment endpoints so adjacent pieces share nodes. Chain segments into closed loops. Flatten each loop onto the plane's 2D basis and triangulate with earcut — which natively supports hole indices, so a boss inside a cut region fills correctly around it.

The bug that took the longest

First version produced zero caps on the rounded-cube sample. Cause: two straddling triangles had a vertex exactly on the plane, so the strict "two sign-changes" test found only one intersection point and emitted no segment — an open chain, silently dropped. The fix treats on-plane vertices as section points, and emits coplanar edges whose third vertex sits on the clipped side:

if (Math.abs(da) < EPS) pts.push(vpos(a));          // vertex on the plane
if (Math.abs(da) < EPS && Math.abs(db) < EPS && dc < -EPS)
  pts.push(vpos(a), vpos(b));                        // coplanar boundary edge

Degenerate plane-alignment cases are where mesh cap algorithms live or die.

Honest limits

  • Caps are sampled at tessellation density — a coarse model gives a coarse cap polygon.
  • Open chains (a cut that ends in genuinely open geometry) get dropped, not closed with a guessed chord.
  • Holes one level deep are handled; exotic nested containment is not.
  • Rebuild runs debounced off the slider (~150 ms) — it recomputes after you stop dragging, not per pixel of drag.

The full pipeline — intersect, weld, chain, project, nest, triangulate — is about 200 lines in viewer/section-cap.js, renderer-agnostic, works identically on WebGPU and the WebGL2 fallback.