Tolerances and Dimensions - SLM + ROSES study (Frahan StonePack)¶
Date: 2026-06-06. Framework: Research V4, T1 SLM (algorithm math) + T2 ROSES (interdisciplinary synthesis). Scope: every tolerance + dimension parameter across the plugin. Anchoring decisions (user confirmed): per-application UNITS (meters for site/quarry/masonry; millimeters for shop pieces), STANDARD industry dimension figures, a PER-APPLICATION tolerance budget tied to characteristic dimension and the real process. Style: short sentences, no em dashes. Produced by an 8-agent workflow (6 SLM families + ROSES synthesis + adversarial verify); all load-bearing code facts spot-checked against source.
1. The core finding¶
Tolerance in this plugin is a multi-decade-of-length problem. The characteristic dimension L spans ~1e-3 m
(fracture rim / gold seam) to ~1e1 m (quarry bench). A single absolute tolerance cannot serve it. The
shipped Rhino document tolerance is 0.01 (1 cm in a meters doc); it is correct at exactly ONE rung
(dry-stone fit ~1 m) and is silently 100x too loose for sawn slabs, ~200x too loose for mosaic, and
~100x too loose for a Kintsugi vessel. The GH 2D nesters never read RhinoDoc.ModelAbsoluteTolerance
(verified: zero reads in source); they hardcode the literal 0.01. The fix is one master tolerance read off
the document, overridden per application, with everything else derived from it.
2. Scale ladder (5 rungs)¶
- R1 mm - FRACTURE RIM / SEAM. L ~ 1-10 mm. Crack apertures (1.5-2.0 mm observed), Kintsugi gold seams, mosaic grout. Resolution ~0.05-0.1 mm. (Kintsugi seams, Trencadis grout sit here.)
- R2 cm - MOSAIC SHARD / DRESSED FACE. L ~ 40-150 mm. Trencadis + monument shards, Kintsugi fragments. Resolution ~0.5-1 mm.
- R3 dm - DIMENSION-STONE PRODUCT / SLAB PART. L ~ 0.2-1.2 m. Slab cut parts, quarry sub-elements. Resolution ~0.5-1 mm. (Example 10 is the canonical R3.)
- R4 m - QUARRY BLOCK / MONUMENT / SHEET. L ~ 1-3.5 m. Slab sheet, quarry block, monument body, dry-stone. Resolution ~1-10 mm. The ONE rung where the shipped 0.01 m is correct (dry-stone joint).
- R5 10 m - BENCH / OUTCROP / SITE. L ~ 5-50 m. Benches, UTM coords. RECENTER mandatory (float64 loses 7-9 mantissa digits at 1e5-1e6 m before any algorithm runs).
KEY: each example spans TWO adjacent rungs (a body at one rung, joints one rung finer). This is why scale-relative epsilon and the per-application unit choice matter: the tolerance must resolve the finer rung while the geometry is sized at the coarser rung.
3. Unified tolerance budget (function of characteristic dimension L and process P)¶
Three independent quantities, each with a different L-dependence. Conflating them is the root bug (three unreconciled systems run in the nesters today).
- GEOMETRIC EPSILON
eps_geo(L) = max(eps_floor, k_geo * L), k_geo ~ 1e-3 (0.1% of L). Numeric-hygiene quantity (degeneracy, near-coincidence, chord error, Clipper snap). Scales LINEARLY with L. Floor =max(RhinoMath.ZeroTolerance, scan_noise)(~0.1 mm site, ~0.001 mm shop). HARD CONSTRAINTeps_geo < kerf/3so it never swallows a real saw cut. Implement viaGeometryNumerics.ScaleRelativeEpsilon(modelAbsTol, bboxDiag) = modelAbsTol * max(|diag|,1), thenToleranceBudget.Fromderives Model=eps_geo, Join=10x, Intersection=0.1x, Snap=0.01x. - KERF = PROCESS CONSTANT, independent of L (machine-set). Its YIELD impact scales INVERSELY:
relative_loss = kerf/L(5 mm kerf is 0.4% of a 1.2 m part but 12.5% of a 40 mm shard). Keep kerf a SEPARATE physical allowance, never inside the numeric budget (folding it in rejects sub-kerf fractures). - CLEARANCE/GAP = f(L, P), the designed joint. sawn/CNC contact: clearance = kerf. masonry:
clamp(0.005*L, 1e-3, 1e-2) m. dry-stone:clamp(0.02*L, 1e-2, 5e-2) m. mosaic grout: designed 5-20 mm (aesthetic, not from L). ceramic/Kintsugi: firing-shrinkage allowance ~1-2% L + seam width.
COMPOSITION RULE: choose UNITS so bboxDiag > 1 doc-unit; set modelAbsTol = max(k_geo*L, scan_floor)
capped below kerf/3; derive eps_geo + Join/Intersection/Snap; set spacing = kerf (cut-whole) or
clearance(L,P) (fitted); keep kerf separate; set Clipper integer Scale = 1/Snap via SafeIntegerScale on
recentered coords.
4. Process constants (real fabrication tolerances)¶
- Diamond WIRE saw kerf 3-10 mm (bead 5-8 mm). Primary quarry KerfDefaultMetres = 0.05 m (50 mm) is the published CHANNEL/material-lost allowance, NOT a single bead pass; keep 50 mm for block-pack yield, 5-8 mm for slab-saw spacing.
- BRIDGE / BLADE saw kerf 1-3 mm. WATERJET ~0.8-1.2 mm. CNC router ~0.1 mm (0.05-0.15). Engraving ~0.1 mm.
- DRY-STONE joint fit ~cm (target face < 10 mm) - the ONLY app where shipped 0.01 m is correct.
- MASONRY mortar joint 3-15 mm (bed ~10 mm). MOSAIC grout 5-20 mm (Trencadis), 3-6 mm (monument), AESTHETIC.
- CERAMIC firing shrinkage ~1-2% linear. 3D SCAN noise 0.1-2 mm (sets eps floor). PHOTOGRAMMETRY GSD 2 cm/px bench UAV, 5 mm/px close-range. OBSERVED fracture aperture 1.5-2.0 mm. Quarry half-kerf 25 mm/axis.
5. Cross-cutting numeric-hygiene rules (apply once in the shared GeometryNumerics layer)¶
- A. SEED ONE MASTER TOLERANCE FROM THE DOC. Read
RhinoDoc.ActiveDoc.ModelAbsoluteTolerance, override per application, derive everything viaToleranceBudget.From. Today the nesters never read it (hardcode 0.01). - B. RECENTER BEFORE COMPUTE. Recenter the WHOLE problem (sheet+parts+holes, or block+elements) to combined bbox centre; store offset; add back on emit. The 2D path recenters parts but keeps SHEETS in world coords (partial bug). Mandatory for R4/R5; free for R1/R2.
- C. SCALE-RELATIVE EPSILON, NOT ABSOLUTE. The
max(scale,1)clamp means a sub-unit object (a 0.22 m vessel in a meters doc) collapses to a bare floor. THEREFORE keep R1/R2 shop pieces in MILLIMETERS so bboxDiag > 1 and the relative term engages. This is the technical justification for the per-application unit decision. - D. CLIPPER2 SCALING. The fixed
const Scale = 1000is not adaptive; the double PathsD API snaps to int64 at ~2-decimal precision, so emitted geometry inflates ~3-4% (report yield against TRUE input-part area). Replace withSafeIntegerScale(maxAbsCoordAfterRecenter) ~ 1/SnapAFTER recentering: ~1e5 for meter slab/quarry, 1e4-1e5 for mm shop. Correct scale is INVERSELY proportional to L. - E. THREE-SYSTEM RECONCILIATION. The nesters run three unreconciled tolerances (ZeroTolerance ~1e-12, user _tol 0.01, implicit 1/Scale Clipper snap). Wire ONE ToleranceBudget to all gates. Keep BlockCutOpt kerf (0.05 m) separate.
- F. UNIT FLOOR DISCIPLINE.
eps_geofloored atmax(ZeroTolerance, scan_noise)and capped atkerf/3.
6. Per-application set (as applied to examples 10-14, 2026-06-06)¶
| Example | Unit | Overall dims | Element dims | Geom tol | Spacing / grout / kerf | Position |
|---|---|---|---|---|---|---|
| 10_pack2d slab | m | sheet 3.2 x 2.0 m | parts 0.3-1.2 m | 1 mm | spacing = saw kerf 5 mm; Trim 0 | flat XY z=0, lower-left at origin |
| 11_pack3d block | m | block 3.0 x 1.5 x 1.5 m | elements 0.2-1.0 m | 1 mm | kerf 8 mm in-block (or 50 mm primary) | base on z=0, centred XY |
| 12_trencadis | mm | panel ~1100 mm | shards 40-120 mm | 0.05 mm | grout 5 mm (<< shard) | flat XY z=0, lower-left at origin |
| 13_surface monument | mm | 1200 x 1200 x 3500 mm | shards 40-200 mm | 0.05 mm | grout 4 mm | base on z=0, centred XY |
| 14_kintsugi vessel | mm | ~100-220 x 280 mm | fragments 50-150 mm | 0.1 mm | seam 1-3 mm; no kerf | base on z=0, centred XY |
7. Master parameter table (recommended values per application)¶
| Parameter | Component | Recommended | Basis |
|---|---|---|---|
| ModelAbsoluteTolerance (master) | should seed every solver; today hardcoded 0.01, never read from doc | slab 5e-4 m; quarry 1e-3 m; dry-stone 1e-2 m; mosaic 0.05 mm; monument 0.05 mm; vessel 0.1 mm | process anchors; T4 flag |
| Clipper integer Scale | IrregularSheetFillNfpBlf const 1000 | SafeIntegerScale ~1/Snap after recenter; ~1e5 (m), 1e4-1e5 (mm) | T6; emission inflation ~3-4% |
| Scale-relative eps | GeometryNumerics.ScaleRelativeEpsilon (exists, unused by nesters) | baseEps 1e-4 (m), 1e-5 (mm), 2e-4 Kintsugi; keep shop pieces in mm | max(scale,1) clamp |
| Recenter | GeometryNumerics.Recenter | recenter whole problem; mandatory R4/R5 | 7-9 mantissa digit loss at UTM |
| ToleranceBudget (Model/Join/Intersection/Snap) | GeometryNumerics.ToleranceBudget.From (unused) | wire Model->gates, Join 10x weld, Intersection 0.1x accept, Snap 0.01x->Scale | T5 three-system flag |
| Tolerance (2D nester) | default 0.01 | slab 1 mm; mosaic 0.05 mm; CNC 0.01-0.05 mm; rule max(L/1000, scan_floor) and < kerf/3 | area gate tol^2 |
| Spacing (2D nester) | default 0.1 | slab 5 mm (wire) / 1-2 mm (waterjet); mosaic = grout 5-20 mm; 0 only for FreeNestX virtual nest | KB-6 spacing floor |
| Trim Tolerance (V506) | default 0.1 | 0 for cut-whole production; 3-10 mm masonry shared-contact; 0 mosaic. V506 overlap = this default, by design | hover even says try 0.1-1.0 |
| Discretization Tolerance | default -1 (inherit) | slab 1-2 mm; mosaic 0.05-0.1 mm; CNC 0.02-0.05 mm; keep verts < 150 (cap 200) | NFP O(nA*nB) |
| Min Boundary Affinity | default 0.5 | 0.3-0.45 dense hug; 0.7-0.8 strict; DIMENSIONLESS [0,1] (the one scale-independent param) | normalized |
| Cell Size (LEGACY heightmap only) | default 10.0 | min-element/6..10 (0.02-0.05 m). NOT in 11_pack3d (BlockPackTree has no grid) | 10 m cell collapses grid |
| Forests (BlockPackTree, the real 11_pack3d) | default 256 | 256 (plateau f~50-1000); cap via Memory Budget | Kim 2025 |
| Kerf Width (BlockPackTree) | default 0.0 | 3-10 mm in-block wire; 50 mm primary-quarry yield; keep separate from numeric budget | over-estimates yield at 0 |
| Rotations (2D) / Rotation Mode (3D) | {0,90,180,270} / 0 | slab {0,90,180,270} or {0,180} to respect vein; quarry Rotation Mode 1 (vein-aligned); mosaic free | grain matters |
| CGAL dihedral Angle (segmentation) | input degrees | 5-15 strict planarity; 30-60 smooth bands (monument used 35); 90+ orthogonal creases | detect_sharp_edges |
| Kintsugi Joint Width / Sample Spacing | normalized | scale to fragment size; vessel eps 0.1 mm = scan-noise floor | scale-invariance |
8. Verification corrections (adversarial pass)¶
- The Cell-Size-10.0 critique applies to the LEGACY heightmap packers (Pack3DMeshHeightmap / Pack3DIrregular / Pack3DIrregularContainer, all default 10.0), NOT the flagship 11_pack3d, which uses Block Pack (Tree) / DLBF (no cell size; levers are Forests + Kerf + Memory Budget).
- V506 part-to-part overlap is BY DESIGN: the default Trim Tolerance 0.1 permits overlap-then-trim; the clean geometry is the Trimmed Curves output (or set Trim Tolerance = 0). FreeNestX is strict 0-overlap by construction and is what example 10 now uses (verified 0.0 overlap, 16/16 placed, in the rebuilt .gha).
- Source line numbers in older SLM cards drifted after the 2026-06-06 evolution edits; GeometryNumerics, Clipper2Adapter, component, and BlockCutOptTolerances line numbers are current.
- Two parallel source trees exist (this repository's
src/and the older Template-General mirror), identical constants, different namespaces.
9. Open questions for Libish (source-code policy, beyond the example-file fixes)¶
- Change the shipped Rhino doc tolerance default 0.01 -> 0.001 in the meters template, or override per-component from the budget? (Per-component is safer; affects existing files either way.)
- Reconcile the kerf model: 50 mm primary-quarry channel vs 5-8 mm slab-saw bead. Which ships per example?
- Monument (13): confirmed mm for the shard/grout work (applied). Body modelling in mm too (applied) vs a split m-body/mm-shard workflow?
- Flip the V506 Trim Tolerance default 0.1 -> 0 so cut-whole production is out-of-box? (Changes behaviour.)
- Pack3D legacy Cell Size: keep absolute default (unit-fragile) or auto-derive from min-element/8?
- Kintsugi firing shrinkage: fragments are scanned POST-firing (shrinkage already happened), so the only fit allowance is scan noise + seam width. Model shrinkage at all, or drop it?
10. Source UX fixes APPLIED (2026-06-06, user-approved, rebuilt + redeployed + tested live)¶
All six decided; users now get correct behaviour out of the box (no manual per-scale tuning): - Q1 + Q6 (auto tolerance): the 2D nesters (Frahan Sheet Pack Unified + Freeform Sheet Nest Exact NFP) default Tolerance = 0 = AUTO. When 0, the component computes a SCALE-RELATIVE epsilon = 1e-4 x sheet-bbox-diagonal, tightened to the doc tolerance when the user set a finer doc. (The raw doc tolerance alone, 0.01 m in a metre model, is too loose and overlapped exact-NFP parts; the first auto attempt used it and FAILED the 0-overlap test, so it was updated to scale-relative.) VERIFIED LIVE: FreeNestX with Tolerance=0 packs 14/14 at overlap 0.0. - Q3 (V506 Trim default): Trim Tolerance default flipped 0.1 -> 0.0 (strict no-overlap out of the box). VERIFIED: default reads 0. - Q4 (Pack3D Cell Size): legacy heightmap packers (Pack3D Mesh Heightmap / Irregular / Irregular Container) default Cell Size = 0 = AUTO, deriving the grid from the smallest element (min bbox edge / 8). The shipped 10.0 packed nothing in a metre model. VERIFIED: auto packs 8/8 with a 3200-cell grid. - Q2 (kerf): kept as a SEPARATE physical allowance, set per example (50 mm primary-quarry yield for 11_pack3d; 5-8 mm slab-saw spacing for 10_pack2d). No risky unit-ambiguous source default. - Q5 (firing shrinkage): dropped. No shrinkage parameter exists in the Kintsugi source; fragments are scanned post-firing, so the only fit allowance is scan noise + seam width (documented, not a knob). Rebuilt Frahan.StonePack.gha (0 errors), redeployed to the Grasshopper Libraries folder, refreshed install/plugin/. Open: whether to also lower the shipped Rhino doc-template tolerance (left at 0.01; per-component auto override is the safer path already taken).