Product · Tolerant topology

Tolerant topology — declare the gap, name the weld

A tolerant edge, vertex or face carries its own statement of how well it is known: "this curve/corner/surface is only known to within r". ops/stitch.js's sew tolerance is a single number by default (the model's own resabs); a declared per-entity tolerance widens only the entities that declare it, through one shared accessor (CoincidencePolicy.tolFor / widenOf). Every case below is the same two-rim seam — the exact fixture tests/test_cadsf-948.js and tests/test_cadsf-949.js use — that does not pair at the model's own tolerance, paired (or, in the last case, deliberately refused) by one declaration, with the heal report saying which entity bought the radius.

CP.tolFor(topology, edgeOrVertexOrFaceId, fallbackTol)   // → max(fallback, what the id declares)
CP.widenOf(topology, id)                                  // → what the id declares ABOVE the model default

A declaration above TOLERANCE_CAP_MULTIPLIER × resabs (the ADR-2 widening ceiling) is named in the report and not silently used — see the last card. Nothing declared ⇒ every number below is byte-identical to a model with no tolerant topology at all (the inertness rule, docs/TOLERANT_TOPOLOGY.md).

Five declarations, one seam

Each card: the entity/entities that declare, a viewer pane (the seam before the weld, after the weld, and re-parsed out of the shipped STEP — posters until you press View in 3D, then NativeCAD's own glTF viewer), the weld before and after (from ops/stitch.js directly, and from the orchestration layer ops/healPipeline.js), and a STEP NativeCAD.tolerance property-block round trip plus a Parasolid XT write-back, both verified exact on every run this generator makes. The download is the seam. Each case writes the body ops/stitch.js published — a MANIFOLD_SOLID_BREP where the declaration paired the rims, a SHELL_BASED_SURFACE_MODEL where it did not — so the .step and .x_t beside each card hold the body the card is about, and the third pane is that file read back in. The two rims are coincident coplanar half-discs 0.2 µm apart, so every pane draws rim B 1.5 mm below rim A (a node offset in the GLB, stated in index.json) — the weld is a change of topology, not of anything you could see; the declared face is tinted, a refused one tinted red.

One asymmetry, stated rather than hidden: STEP is written from the whole model and XT from the one body, so where the weld merged rim A and rim B into a single edge, the superseded edge's declaration is still in the .step but is not in the .x_t. index.json carries both sets (modelByKind, bodyByKind) and each file is checked against the one that is actually its own.

Loading the tolerance index…

Why a declaration is not a blank cheque

  1. Per entity, not per model. A declared tolerance widens the sew radius only for the edges/vertices/faces that carry it; a sibling entity that declares nothing is judged at the model's own tolerance, unchanged.
  2. Faces speak for their own rim. A tolerant FACE's declaration is the pairing ceiling for its own edges and corners too — a gap inside the face tolerance but outside every edge tolerance still closes (tests/test_cadsf-949.js's "sheet bag" case).
  3. Two declarations take the max, never a sum. Both sides of a seam declaring the same number welds at that number, not double it.
  4. A ceiling, not a suggestion. A face declaration over the ADR-2 cap is refused outright — applied is exactly 0, and the report says so by name (overCap: true), never silently clamped and used.
  5. The declaration survives the trip out and back. The same number, written to STEP as a documented NativeCAD.tolerance property block or to Parasolid XT, reads back exactly — verified on every run, not asserted once and trusted forever.

More demos

All demos →

Build your own part with NativeCAD

Free for education and makers with a registered key · $200 per seat per year · Kernel SDK for products.