forgecad 0.9.4 → 0.9.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/dist/assets/{AdminPage-jwoEgwE_.js → AdminPage-Da6hhpJx.js} +1 -1
  2. package/dist/assets/{BlogPage-Ck7g3ue2.js → BlogPage-Bl_sKeWb.js} +1 -1
  3. package/dist/assets/{DocsPage-9WaRC14b.js → DocsPage-Blz3Tp4j.js} +1 -6
  4. package/dist/assets/EditorApp-CuiPbtn5.js +12754 -0
  5. package/dist/assets/{EmbedViewer-37_PfMwv.js → EmbedViewer-BFG6-Ufm.js} +2 -2
  6. package/dist/assets/{LandingPageProofDriven-CO8WL0CY.js → LandingPageProofDriven-DB9fQd5P.js} +1 -1
  7. package/dist/assets/{PricingPage-DADKGuOa.js → PricingPage-BMxYT_F0.js} +1 -1
  8. package/dist/assets/{SettingsPage-DKKI4W49.js → SettingsPage-VVQNrCAg.js} +1 -1
  9. package/dist/assets/{app-CwI02pTA.js → app-Dl9ymBWC.js} +355 -36
  10. package/dist/assets/cli/{render-Kw5hLEcL.js → render-CFtwKCCY.js} +203 -862
  11. package/dist/assets/{sectionPlaneMath-C8N0w8o3.js → distance-BEC2RjJi.js} +4150 -801
  12. package/dist/assets/{evalWorker-D6ub3kfS.js → evalWorker-CRvbzTXm.js} +2611 -528
  13. package/dist/assets/{manifold-CwDdMKyc.js → manifold-B9QSr-qP.js} +1 -1
  14. package/dist/assets/{manifold-DTvmxSDf.js → manifold-DpBXFS2K.js} +1 -1
  15. package/dist/assets/{manifold-lru0jwVw.js → manifold-DzZ4VRPs.js} +2 -2
  16. package/dist/assets/{renderSceneState-tvtNKNRi.js → renderSceneState-BuAXF2jh.js} +1 -1
  17. package/dist/assets/{reportWorker-DeqktDGt.js → reportWorker-BNWEnRg1.js} +2606 -525
  18. package/dist/cli/render.html +1 -1
  19. package/dist/docs/index.html +2 -2
  20. package/dist/docs-raw/AI/usage.md +0 -1
  21. package/dist/docs-raw/API/core/concepts.md +11 -1
  22. package/dist/docs-raw/CLI.md +64 -13
  23. package/dist/docs-raw/beta-operations.md +4 -0
  24. package/dist/docs-raw/deployment.md +38 -23
  25. package/dist/docs-raw/generated/assembly.md +8 -3
  26. package/dist/docs-raw/generated/concepts.md +126 -46
  27. package/dist/docs-raw/generated/core.md +97 -47
  28. package/dist/docs-raw/generated/curves.md +113 -595
  29. package/dist/docs-raw/generated/lib.md +40 -3
  30. package/dist/docs-raw/generated/output.md +6 -1
  31. package/dist/docs-raw/generated/sdf.md +50 -4
  32. package/dist/docs-raw/generated/sketch.md +9 -1
  33. package/dist/docs-raw/generated/viewport.md +1 -9
  34. package/dist/docs-raw/guides/inspection-bundles.md +40 -9
  35. package/dist/docs-raw/runbook.md +3 -3
  36. package/dist/docs-raw/skills/forgecad-blockout-model.md +1 -0
  37. package/dist/docs-raw/skills/forgecad-image-replicator.md +3 -1
  38. package/dist/docs-raw/skills/forgecad-make-a-model.md +48 -4
  39. package/dist/docs-raw/skills/forgecad-render-inspect.md +3 -1
  40. package/dist/docs-raw/skills/forgecad-visual-spec.md +2 -0
  41. package/dist/docs-raw/skills/forgecad.md +2 -1
  42. package/dist/docs-raw/skills/index.md +0 -1
  43. package/dist/index.html +1 -1
  44. package/dist/sitemap.xml +6 -6
  45. package/dist-cli/blender/render.py +43 -8
  46. package/dist-cli/forgecad.js +5729 -2015
  47. package/dist-cli/forgecad.js.map +1 -1
  48. package/dist-skill/CONTEXT.md +372 -667
  49. package/dist-skill/SKILL-dev.md +2 -1
  50. package/dist-skill/SKILL.md +2 -1
  51. package/dist-skill/docs/API/core/concepts.md +11 -1
  52. package/dist-skill/docs/CLI.md +64 -13
  53. package/dist-skill/docs/generated/assembly.md +8 -3
  54. package/dist-skill/docs/generated/core.md +97 -47
  55. package/dist-skill/docs/generated/curves.md +113 -595
  56. package/dist-skill/docs/generated/lib.md +40 -3
  57. package/dist-skill/docs/generated/output.md +6 -1
  58. package/dist-skill/docs/generated/sdf.md +50 -4
  59. package/dist-skill/docs/generated/sketch.md +9 -1
  60. package/dist-skill/docs/generated/viewport.md +1 -9
  61. package/dist-skill/docs/guides/inspection-bundles.md +40 -9
  62. package/dist-skill/docs-dev/API/core/concepts.md +11 -1
  63. package/dist-skill/docs-dev/CLI.md +64 -13
  64. package/dist-skill/docs-dev/generated/assembly.md +8 -3
  65. package/dist-skill/docs-dev/generated/core.md +97 -47
  66. package/dist-skill/docs-dev/generated/curves.md +113 -595
  67. package/dist-skill/docs-dev/generated/lib.md +40 -3
  68. package/dist-skill/docs-dev/generated/output.md +6 -1
  69. package/dist-skill/docs-dev/generated/sdf.md +50 -4
  70. package/dist-skill/docs-dev/generated/sketch.md +9 -1
  71. package/dist-skill/docs-dev/generated/viewport.md +1 -9
  72. package/dist-skill/docs-dev/guides/inspection-bundles.md +40 -9
  73. package/dist-skill/library/README.md +0 -1
  74. package/dist-skill/library/forgecad-blockout-model/SKILL.md +1 -0
  75. package/dist-skill/library/forgecad-image-replicator/SKILL.md +3 -1
  76. package/dist-skill/library/forgecad-make-a-model/SKILL.md +48 -4
  77. package/dist-skill/library/forgecad-render-inspect/SKILL.md +3 -1
  78. package/dist-skill/library/forgecad-visual-spec/SKILL.md +2 -0
  79. package/examples/api/drive-wheel-regions.forge.js +43 -0
  80. package/examples/api/guided-loft-olive-oil-bottle.forge.js +135 -0
  81. package/examples/api/sdf-circular-array-knurling.forge.js +19 -0
  82. package/examples/api/sdf-pattern2d-ceramic-ripple-set.forge.js +83 -0
  83. package/examples/api/sdf-pattern2d-grip-tread.forge.js +72 -0
  84. package/examples/api/sdf-pattern2d-orbital-jewelry.forge.js +62 -0
  85. package/examples/api/sdf-surface-basket-weave.forge.js +67 -0
  86. package/examples/api/sector-gear-body.forge.js +34 -0
  87. package/package.json +20 -2
  88. package/dist/assets/EditorApp-Dja2jMmW.js +0 -12509
  89. package/dist/docs-raw/skills/forgecad-api-dogfood.md +0 -130
  90. package/dist-skill/library/forgecad-api-dogfood/SKILL.md +0 -125
@@ -25,6 +25,7 @@ Prefer documented primitives, import rules, and placement strategies over invent
25
25
 
26
26
  - `.forge.js` — parametric part or assembly script; return a `Shape`, `Sketch`, `ShapeGroup`, `Assembly`, `SolvedAssembly`, array of renderables, or metadata object. Assemblies render directly; do not add `.toGroup()` unless you need `ShapeGroup` behavior.
27
27
  - Model the physical artifact, not an educational diagram. Do not add explanatory labels, arrows, legends, or text plaques unless the user explicitly asks for a presentation or teaching view. Product markings are allowed only when they would exist on the real object.
28
+ - Build the real closed CAD first. Do not bake cutaways, sectioned shells, permanently exploded layouts, or hidden-parts views into the default model just to show internals. Use viewer-only cut planes, `explodeView`, object hiding, transparency, or `render inspect` section channels after the artifact exists.
28
29
 
29
30
  ### Import and composition
30
31
 
@@ -58,10 +59,12 @@ Use the CLI to validate, inspect, and export the model the AI is editing. Keep c
58
59
  forgecad run path/to/model.forge.js
59
60
  forgecad run path/to/model.forge.js --debug-imports
60
61
  forgecad run path/to/model.forge.js --backend occt
62
+ forgecad check print path/to/model.forge.js --json
61
63
  forgecad check params path/to/model.forge.js --samples 12
62
64
  ```
63
65
 
64
66
  - `forgecad run` prints geometry diagnostics, object summaries, collisions, verification results, and solver info.
67
+ - `forgecad check print` reports collisions, mesh health, sampled walls, overhangs, and bed contact.
65
68
  - `forgecad check params` sweeps declared parameter ranges and reports crashes, degenerates, and new collisions.
66
69
 
67
70
  ## Visual Checks
@@ -126,7 +129,7 @@ Top-level declarations such as `const bom = ...`, `let scene = ...`, or `class S
126
129
 
127
130
  - Scripts re-execute on every parameter change (400ms debounce)
128
131
  - Geometry operations are **immutable** — shapes, sketches, groups, imported assemblies, and wood boards return new values instead of modifying in place
129
- - Must return one of: `Shape`, `Sketch`, `ShapeGroup`, `Assembly`, `SolvedAssembly`, `SdfShape`, `Array` of renderables, `Array` of `{ name, shape?, sketch?, group?, color? }`, or a **metadata object** (see below)
132
+ - Must return one of: `Shape`, `Sketch`, `ShapeGroup`, `Assembly`, `SolvedAssembly`, `SdfShape`, `Array` of renderables, `Array` of `{ name, tags?, shape?, sketch?, group?, color? }`, or a **metadata object** (see below)
130
133
 
131
134
  Top-level assembly scripts can return an unsolved `Assembly` directly; ForgeCAD solves it at default joint values for display. Return `assembly.solve(state)` when you want a specific pose. Do not call `.toGroup()` just to make an assembly render — use `.toGroup()` only when you specifically need `ShapeGroup` composition, group-style transforms, or named-child lookup.
132
135
 
@@ -159,6 +162,16 @@ return {
159
162
  };
160
163
  ```
161
164
 
165
+ Named return objects and named `group(...)` children can include `tags`. Tags are viewport metadata: they do not affect geometry, exports, face labels, or BOM rows, but the command palette can hide, show only, or focus every object with a selected tag.
166
+
167
+ ```javascript
168
+ return [
169
+ { name: 'Base Plate', tags: ['printed', 'structural'], shape: base },
170
+ { name: 'M4 Bolt A', tags: 'fastener', shape: boltA },
171
+ { name: 'M4 Bolt B', tags: 'fastener', shape: boltB },
172
+ ];
173
+ ```
174
+
162
175
  ## Coordinate System
163
176
 
164
177
  Z-up right-handed: X = left/right, Y = forward/back, Z = up/down.
@@ -218,15 +231,15 @@ For organic shapes, smooth blending, TPMS lattices, and surface deformations. Re
218
231
  - [Grouping & Local Coordinates](#grouping-local-coordinates) — `group`
219
232
  - [Section & Projection](#section-projection) — `intersectWithPlane`, `faceProfile`, `projectToPlane`
220
233
  - [Transforms](#transforms) — `composeChain`
221
- - [Backend Runtime](#backend-runtime) — `initKernel`, `setActiveBackend`, `activateBackend`, `getActiveBackend`
222
234
  - [Verification](#verification) — `spec`
223
235
  - [Shape](#shape) — Appearance, Face Topology, Edge Topology, Transforms, Booleans & Cutting, Features, Placement, Connectors, References, Measurement
224
236
  - [Transform](#transform)
225
237
  - [ShapeGroup](#shapegroup) — Children, Transforms, Placement, Connectors, References
226
238
  - [SurfacePattern](#surfacepattern)
239
+ - [Pattern2D](#pattern2d)
240
+ - [Pattern2DBuilder](#pattern2dbuilder)
227
241
  - [ShapeRef](#shaperef)
228
242
  - [ANCHOR3D_NAMES](#anchor3d-names)
229
- - [DEFAULT_ACTIVE_BACKEND](#default-active-backend)
230
243
  - [verify](#verify)
231
244
  - [Constraint](#constraint)
232
245
  - [Points](#points)
@@ -321,9 +334,11 @@ intersection(...inputs: ShapeOperandInput[]): Shape
321
334
 
322
335
  ### Edge Features
323
336
 
324
- #### `fillet()` — Apply fillets (rounded edges) to one or more edges of a shape.
337
+ #### `fillet()` — Apply experimental fillets (rounded edges) to one or more edges of a shape.
325
338
 
326
- Works on edge selections from any active backend. Truck and OCCT route edge finishes through native kernel operations; unsupported selections fail as explicit kernel gaps instead of using TypeScript geometry fallbacks.
339
+ **Experimental**: fillets are still backend-sensitive. The Manifold backend is known to produce incorrect results for some edge-finish cases, and the OCCT backend can be very slow, especially with broad edge selections. Prefer targeted edge selectors and inspect the result before treating it as production-ready geometry.
340
+
341
+ Edge selections compile into backend operations; unsupported selections fail as explicit kernel gaps instead of using TypeScript geometry fallbacks.
327
342
 
328
343
  The `edges` parameter is flexible:
329
344
 
@@ -349,9 +364,11 @@ fillet(myShape, 3, edges)
349
364
  fillet(shape: Shape, radius: number, edges?: EdgeSelector, segments?: number): Shape
350
365
  ```
351
366
 
352
- #### `chamfer()` — Apply chamfers (beveled edges) to one or more edges of a shape.
367
+ #### `chamfer()` — Apply experimental chamfers (beveled edges) to one or more edges of a shape.
368
+
369
+ **Experimental**: chamfers are still backend-sensitive. The Manifold backend is known to produce incorrect results for some edge-finish cases, and the OCCT backend can be very slow, especially with broad edge selections. Prefer targeted edge selectors and inspect the result before treating it as production-ready geometry.
353
370
 
354
- Produces a 45° bevel at the specified `size` (distance from edge). Works on edge selections from any active backend. Truck and OCCT route chamfers through native kernel operations; unsupported selections fail as explicit kernel gaps instead of using TypeScript geometry fallbacks.
371
+ Produces a 45° bevel at the specified `size` (distance from edge). Edge selections compile into backend operations; unsupported selections fail as explicit kernel gaps instead of using TypeScript geometry fallbacks.
355
372
 
356
373
  The `edges` parameter accepts the same options as `fillet()`: inline `EdgeQuery`, pre-selected `EdgeSegment`/`EdgeSegment[]`, or `undefined` (all sharp edges).
357
374
 
@@ -576,11 +593,8 @@ selectEdges(shape: Shape, query?: EdgeQuery): EdgeSegment[]
576
593
  | `normalA` | `Vec3` | Normal of first adjacent face. |
577
594
  | `normalB` | `Vec3` | Normal of second adjacent face (same as normalA for boundary edges). |
578
595
  | `boundary` | `boolean` | true if this is a boundary (unmatched) edge — unusual for closed solids. |
579
- | `nativeTopology?` | `EdgeNativeTopologyRef` | Native kernel topology identity when the active backend can provide one. |
580
596
  | `start`, `end`, `midpoint`, `length` | | — |
581
597
 
582
- `EdgeNativeTopologyRef`: `{ backend: "truck", edge: number }`
583
-
584
598
  #### `selectEdge()` — Select the single best-matching edge from a shape.
585
599
 
586
600
  When `near` is specified, returns the edge whose midpoint is closest to that point. Otherwise returns the first matching edge in mesh order. Throws if no edges match the query — useful as a guard when you expect exactly one result.
@@ -681,7 +695,7 @@ importSvgSketch(fileName: string, options?: SvgImportOptions): Sketch
681
695
  importMesh(fileName: string, options?: { scale?: number; center?: boolean; }): Shape
682
696
  ```
683
697
 
684
- #### `importStep()` — Import a STEP file (.step, .stp) as an exact OCCT-backed Shape. Preserves NURBS curves, B-spline surfaces, and exact topology. Requires `setActiveBackend('occt')`.
698
+ #### `importStep()` — Import a STEP file (.step, .stp) as an exact OCCT-backed Shape. Preserves NURBS curves, B-spline surfaces, and exact topology. Requires running with the OCCT backend.
685
699
 
686
700
  ```ts
687
701
  importStep(fileName: string): Shape
@@ -817,18 +831,21 @@ Param.list<T extends Record<string, number | boolean | string>>(name: string, de
817
831
 
818
832
  Unlike union(), child colors and individual identities are preserved. Children can be plain shapes, named descriptors ({ name, shape/sketch/group }), or nested groups. The returned ShapeGroup supports all Shape transforms (translate, rotate, etc.).
819
833
 
834
+ Named descriptors can include `tags` for viewport organization. Tags do not affect geometry; they let the command palette hide, show only, or focus all objects with the same tag.
835
+
820
836
  **Local coordinate pattern:** Build child parts at the origin (local coordinates), then group and translate once to place the whole assembly. This eliminates the error-prone pattern of manually adding parent offsets to every sub-part.
821
837
 
822
838
  ```js
823
- // BAD every sub-part repeats the parent's global offset
824
- const unitX = 0, unitY = -18, unitZ = 70;
825
- const body = roundedBox(100, 20, 32, 4).translate(unitX, unitY, unitZ);
826
- const panel = box(98, 2, 18).translate(unitX, unitY - 12, unitZ + 4);
827
- const louver = box(88, 2, 6).translate(unitX, unitY - 14, unitZ - 11);
839
+ const body = roundedBox(100, 20, 32, 4);
840
+ const panel = box(98, 2, 18).translate(0, -12, 4);
841
+ const louver = box(88, 2, 6).translate(0, -14, -11);
842
+ const indoorUnit = group(
843
+ { name: 'Body', shape: body },
844
+ { name: 'Panel', tags: 'cover', shape: panel },
845
+ { name: 'Louver', tags: ['cover', 'moving'], shape: louver },
846
+ ).translate(0, -18, 70);
828
847
  ```
829
848
 
830
- // GOOD — build at origin, group, translate once const body = roundedBox(100, 20, 32, 4); const panel = box(98, 2, 18).translate(0, -12, 4); const louver = box(88, 2, 6).translate(0, -14, -11); const indoorUnit = group( { name: 'Body', shape: body }, { name: 'Panel', shape: panel }, { name: 'Louver', shape: louver }, ).translate(0, -18, 70);
831
-
832
849
  ```ts
833
850
  group(...items: GroupInput[]): ShapeGroup
834
851
  ```
@@ -863,32 +880,6 @@ projectToPlane(shape: Shape, plane: PlaneSpec): Sketch
863
880
  composeChain(...steps: TransformInput[]): Transform
864
881
  ```
865
882
 
866
- ### Backend Runtime
867
-
868
- #### `initKernel()`
869
-
870
- ```ts
871
- initKernel(): Promise<unknown>
872
- ```
873
-
874
- #### `setActiveBackend()`
875
-
876
- ```ts
877
- setActiveBackend(backend: ActiveBackend): void
878
- ```
879
-
880
- #### `activateBackend()` — Set the active backend and ensure its WASM module is initialized. Call this instead of `setActiveBackend` when you're about to execute code — it guarantees the backend is ready, not just selected.
881
-
882
- ```ts
883
- activateBackend(backend: ActiveBackend): Promise<void>
884
- ```
885
-
886
- #### `getActiveBackend()`
887
-
888
- ```ts
889
- getActiveBackend(): ActiveBackend
890
- ```
891
-
892
883
  ### Verification
893
884
 
894
885
  #### `spec()` — Create a named, reusable bundle of verification checks.
@@ -1874,6 +1865,82 @@ color(hex: string): ShapeGroup
1874
1865
  | `body` | `string` | Function body: receives (u, v) in surface mm, returns height displacement. |
1875
1866
  | `constants` | `Record<string, number>` | Named constants injected into the function. |
1876
1867
 
1868
+ ### `Pattern2D`
1869
+
1870
+ #### `add()` — Add this pattern to one or more patterns or constant height offsets.
1871
+
1872
+ ```ts
1873
+ add(...patterns: Pattern2DInput[]): Pattern2D
1874
+ ```
1875
+
1876
+ #### `subtract()` — Subtract another pattern or constant height offset from this pattern.
1877
+
1878
+ ```ts
1879
+ subtract(pattern: Pattern2DInput): Pattern2D
1880
+ ```
1881
+
1882
+ #### `multiply()` — Multiply this pattern by one or more patterns or numeric scale factors.
1883
+
1884
+ ```ts
1885
+ multiply(...patterns: Pattern2DInput[]): Pattern2D
1886
+ ```
1887
+
1888
+ #### `min()` — Keep the lower height between this pattern and one or more other patterns.
1889
+
1890
+ ```ts
1891
+ min(...patterns: Pattern2DInput[]): Pattern2D
1892
+ ```
1893
+
1894
+ #### `max()` — Keep the higher height between this pattern and one or more other patterns.
1895
+
1896
+ ```ts
1897
+ max(...patterns: Pattern2DInput[]): Pattern2D
1898
+ ```
1899
+
1900
+ #### `clamp()` — Limit pattern height to the inclusive `[min, max]` range in millimeters.
1901
+
1902
+ ```ts
1903
+ clamp(min: number, max: number): Pattern2D
1904
+ ```
1905
+
1906
+ #### `abs()` — Convert negative heights to positive heights.
1907
+
1908
+ ```ts
1909
+ abs(): Pattern2D
1910
+ ```
1911
+
1912
+ #### `negate()` — Flip the pattern height sign.
1913
+
1914
+ ```ts
1915
+ negate(): Pattern2D
1916
+ ```
1917
+
1918
+ ### `Pattern2DBuilder`
1919
+
1920
+ #### `constant()` — Create a constant-height pattern in millimeters.
1921
+
1922
+ ```ts
1923
+ constant(value?: number): Pattern2D
1924
+ ```
1925
+
1926
+ #### `sineWave()` — Create a sinusoidal wave pattern in UV space.
1927
+
1928
+ ```ts
1929
+ sineWave(options: Pattern2DSineWaveOptions): Pattern2D
1930
+ ```
1931
+
1932
+ #### `stripes()` — Create recessed stripe bands in UV space.
1933
+
1934
+ ```ts
1935
+ stripes(options: Pattern2DStripesOptions): Pattern2D
1936
+ ```
1937
+
1938
+ #### `overUnderWeave()` — Create an over-under woven relief pattern in UV space.
1939
+
1940
+ ```ts
1941
+ overUnderWeave(options: Pattern2DOverUnderWeaveOptions): Pattern2D
1942
+ ```
1943
+
1877
1944
  ### `ShapeRef`
1878
1945
 
1879
1946
  A first-class reference path over a shape's semantic faces and face relationships.
@@ -1996,10 +2063,6 @@ toString(): string
1996
2063
 
1997
2064
  ### `ANCHOR3D_NAMES`
1998
2065
 
1999
- ### `DEFAULT_ACTIVE_BACKEND`
2000
-
2001
- Default geometry backend when no explicit backend is selected.
2002
-
2003
2066
  ### `verify`
2004
2067
 
2005
2068
  - `that(label: string, check: () => boolean, message?: string): void` — Custom predicate check.
@@ -3769,7 +3832,15 @@ detectArrangement(): Sketch[]
3769
3832
  #### `detectArrangementRegion()` — Select the single arrangement region that contains the given seed point. Throws if no region contains the seed.
3770
3833
 
3771
3834
  ```ts
3772
- detectArrangementRegion(seed: [ number, number ]): Sketch
3835
+ detectArrangementRegion(_seed: [ number, number ]): Sketch
3836
+ ```
3837
+
3838
+ #### `toPolyline()` — Return the solved constrained path as a sampled 2D polyline.
3839
+
3840
+ Use this when a construction rail was authored with `constrainedSketch()` and should feed another operation such as `Loft.pathOnXz(...)`. The sketch must contain exactly one profile path.
3841
+
3842
+ ```ts
3843
+ toPolyline(samples?: number): [ number, number ][]
3773
3844
  ```
3774
3845
 
3775
3846
  #### `withUpdatedConstraint()` — Re-solve the sketch after changing the value of one existing constraint.
@@ -4124,8 +4195,8 @@ Smooth curves, lofted surfaces, swept solids, splines, and high-level product sk
4124
4195
 
4125
4196
  ## Contents
4126
4197
 
4127
- - [Curves & Surfacing](#curves-surfacing) — `hermiteTransitionG2`, `nurbs3d`, `spline2d`, `spline3d`, `loft`, `loftAlongSpine`, `sweep`, `variableSweep`, `nurbsSurface`, `surfacePatch`, `transitionCurve`, `transitionSurface`, `connectEdges`
4128
- - [Surface Members](#surface-members) — `surfaceBand`, `compileSurfaceMember`, `SurfaceBody`
4198
+ - [Curves & Surfacing](#curves-surfacing) — `Loft.station`, `Loft.leftRail`, `Loft.rightRail`, `Loft.frontRail`, `Loft.backRail`, `Loft.centerRail`, `Loft.pathOnXz`, `Loft.pathOnYz`, `Loft.pathOnXy`, `Loft.withGuideRails`, `hermiteTransitionG2`, `nurbs3d`, `spline2d`, `spline3d`, `loft`, `loftAlongSpine`, `sweep`, `variableSweep`, `nurbsSurface`, `surfacePatch`, `transitionCurve`, `transitionSurface`, `connectEdges`
4199
+ - [Surface Members](#surface-members) — `surfaceBand`, `SurfaceBody`
4129
4200
  - [Curve3D](#curve3d)
4130
4201
  - [NurbsCurve3D](#nurbscurve3d)
4131
4202
  - [NurbsSurface](#nurbssurface)
@@ -4142,7 +4213,6 @@ Smooth curves, lofted surfaces, swept solids, splines, and high-level product sk
4142
4213
  - [ProductSpoutBuilder](#productspoutbuilder)
4143
4214
  - [ProductHandleBuilder](#producthandlebuilder)
4144
4215
  - [ProductHandleFeature](#producthandlefeature)
4145
- - [ProductRibbonBuilder](#productribbonbuilder)
4146
4216
  - [CylinderCarrier](#cylindercarrier)
4147
4217
  - [PlaneCarrier](#planecarrier)
4148
4218
  - [ProductSkinCarrier](#productskincarrier)
@@ -4168,6 +4238,87 @@ Smooth curves, lofted surfaces, swept solids, splines, and high-level product sk
4168
4238
 
4169
4239
  ### Curves & Surfacing
4170
4240
 
4241
+ #### `Loft.station()` — Create a loft station from a 2D profile and an axis position.
4242
+
4243
+ ```ts
4244
+ Loft.station(profile: Sketch, position: number): LoftStation
4245
+ ```
4246
+
4247
+ `LoftStation`: `{ profile: Sketch, position: number }`
4248
+
4249
+ #### `Loft.leftRail()` — Create a guide rail that constrains the section-local negative-X side.
4250
+
4251
+ ```ts
4252
+ Loft.leftRail(path: LoftGuideRailPath): LoftGuideRail
4253
+ ```
4254
+
4255
+ `LoftGuideRail`: `{ side: LoftGuideRailSide, path: LoftGuideRailPath }`
4256
+
4257
+ #### `Loft.rightRail()` — Create a guide rail that constrains the section-local positive-X side.
4258
+
4259
+ ```ts
4260
+ Loft.rightRail(path: LoftGuideRailPath): LoftGuideRail
4261
+ ```
4262
+
4263
+ #### `Loft.frontRail()` — Create a guide rail that constrains the section-local positive-Y side.
4264
+
4265
+ ```ts
4266
+ Loft.frontRail(path: LoftGuideRailPath): LoftGuideRail
4267
+ ```
4268
+
4269
+ #### `Loft.backRail()` — Create a guide rail that constrains the section-local negative-Y side.
4270
+
4271
+ ```ts
4272
+ Loft.backRail(path: LoftGuideRailPath): LoftGuideRail
4273
+ ```
4274
+
4275
+ #### `Loft.centerRail()` — Create a guide rail that moves section centers along the loft.
4276
+
4277
+ ```ts
4278
+ Loft.centerRail(path: LoftGuideRailPath): LoftGuideRail
4279
+ ```
4280
+
4281
+ #### `Loft.pathOnXz()` — Place a 2D guide path onto the XZ plane.
4282
+
4283
+ The path's first coordinate becomes X and its second coordinate becomes Z. Use this for left/right silhouette rails authored with [`path()`](/docs/sketch#path) or [`constrainedSketch()`](/docs/sketch#constrainedsketch).
4284
+
4285
+ ```ts
4286
+ Loft.pathOnXz(path: LoftPath2D, y?: number): Vec3[]
4287
+ ```
4288
+
4289
+ #### `Loft.pathOnYz()` — Place a 2D guide path onto the YZ plane.
4290
+
4291
+ The path's first coordinate becomes Y and its second coordinate becomes Z. Use this for front/back crown rails authored with [`path()`](/docs/sketch#path) or [`constrainedSketch()`](/docs/sketch#constrainedsketch).
4292
+
4293
+ ```ts
4294
+ Loft.pathOnYz(path: LoftPath2D, x?: number): Vec3[]
4295
+ ```
4296
+
4297
+ #### `Loft.pathOnXy()` — Place a 2D guide path onto the XY plane.
4298
+
4299
+ The path's first coordinate becomes X and its second coordinate becomes Y. Use this when lofting along X or Y and a rail lives in a horizontal sketch plane.
4300
+
4301
+ ```ts
4302
+ Loft.pathOnXy(path: LoftPath2D, z?: number): Vec3[]
4303
+ ```
4304
+
4305
+ #### `Loft.withGuideRails()` — Loft through profile stations while forcing generated sections to follow guide rails.
4306
+
4307
+ Stations define the cross-section family. Guide rails define the side or center paths the loft must pass through. With opposite side rails, the section is scaled to touch both rails. With one side rail, the section keeps its interpolated size unless a center rail is also present.
4308
+
4309
+ ```ts
4310
+ Loft.withGuideRails(stations: LoftStation[], rails: LoftGuideRail[], options?: LoftWithGuideRailsOptions): Shape
4311
+ ```
4312
+
4313
+ **`LoftOptions`**
4314
+ - `edgeLength?: number` — Marching-grid edge length for level-set meshing. Smaller = finer.
4315
+ - `boundsPadding?: number` — Optional extra bounds padding.
4316
+
4317
+ **`LoftWithGuideRailsOptions`** extends LoftOptions
4318
+ - `axis?: LoftAxis` — Primary station axis. Default Z.
4319
+ - `samples?: number` — Number of generated loft stations including ends. Default scales with station count.
4320
+ - `railSamples?: number` — Number of points sampled from curve-backed rails before axis interpolation. Default 64.
4321
+
4171
4322
  #### `hermiteTransitionG2()` — Create a quintic Hermite transition curve between two edge endpoints (G2 continuity).
4172
4323
 
4173
4324
  The curve starts at `a.point` tangent to `a.tangent` with curvature `a.curvature`, and ends at `b.point` tangent to `b.tangent` with curvature `b.curvature`, with smooth G2-continuous interpolation matching position, tangent, and curvature.
@@ -4256,10 +4407,6 @@ Performance note: loft is significantly heavier than primitive/extrude/revolve.
4256
4407
  loft(profiles: Sketch[], heights: number[], options?: LoftOptions): Shape
4257
4408
  ```
4258
4409
 
4259
- **`LoftOptions`**
4260
- - `edgeLength?: number` — Marching-grid edge length for level-set meshing. Smaller = finer.
4261
- - `boundsPadding?: number` — Optional extra bounds padding.
4262
-
4263
4410
  #### `loftAlongSpine()` — Loft between multiple profiles positioned along an arbitrary 3D spine curve.
4264
4411
 
4265
4412
  Unlike loft() which only supports Z heights, loftAlongSpine() places each profile at a position along a 3D spine, oriented perpendicular to the spine tangent. This enables lofting along curved paths — e.g., a wing root-to-tip transition that follows a swept-back leading edge.
@@ -4490,11 +4637,8 @@ connectEdges(edgeA: EdgeSegment, edgeB: EdgeSegment, options?: ConnectEdgesOptio
4490
4637
  | `normalA` | `Vec3` | Normal of first adjacent face. |
4491
4638
  | `normalB` | `Vec3` | Normal of second adjacent face (same as normalA for boundary edges). |
4492
4639
  | `boundary` | `boolean` | true if this is a boundary (unmatched) edge — unusual for closed solids. |
4493
- | `nativeTopology?` | `EdgeNativeTopologyRef` | Native kernel topology identity when the active backend can provide one. |
4494
4640
  | `start`, `end`, `midpoint`, `length` | | — |
4495
4641
 
4496
- `EdgeNativeTopologyRef`: `{ backend: "truck", edge: number }`
4497
-
4498
4642
 
4499
4643
  **`ConnectEdgesOptions`** extends TransitionSurfaceOptions
4500
4644
 
@@ -4517,63 +4661,6 @@ connectEdges(edgeA: EdgeSegment, edgeB: EdgeSegment, options?: ConnectEdgesOptio
4517
4661
  surfaceBand<C extends SurfaceCoordinate>(path: SurfacePath<C> | SurfacePathBuilder<C>, width: WidthProfile, cap?: SurfaceBandCap): SurfaceBand<C>
4518
4662
  ```
4519
4663
 
4520
- #### `compileSurfaceMember()`
4521
-
4522
- ```ts
4523
- compileSurfaceMember<C extends SurfaceCoordinate>(input: Omit<SurfaceMemberSpec<C>, "section"> & { section: MemberSection | MemberSectionInput; }): CompiledSurfaceMember
4524
- ```
4525
-
4526
- **`SurfaceMemberSpec`**: `name: string`, `kind: SurfaceMemberKind`, `path?: SurfacePath<C> | SurfacePathBuilder<C>`, `anchor?: SurfaceAnchor<C>`, `size?: { width: number; height: number; }`, `section: MemberSection`, `features: MemberFeature[]`, `capStyle?: SurfaceBandCap`, `explicitAnchors?: SurfaceMemberExplicitAnchor<C>[]`
4527
-
4528
- `SurfaceAnchor`: `{ carrier: CarrierSurface<C>, coordinate: C }`
4529
-
4530
- `CarrierSurface`: `{ name: string, kind: SurfaceCarrierKind }`
4531
-
4532
- **`MemberSection`**: `width?: number`, `thickness: number`, `edgeRadius: number`, `direction: MemberOutwardDirection`, `material?: ProductMaterial`, `stations: MemberSectionStation[]`
4533
-
4534
- `ProductMaterial`: `{ color?: string, material?: ShapeMaterialProps }`
4535
-
4536
- **`ShapeMaterialProps`**
4537
-
4538
- | Option | Type | Description |
4539
- |--------|------|-------------|
4540
- | `metalness?` | `number` | Metalness factor (0 = dielectric, 1 = metal). Default: 0.05 |
4541
- | `roughness?` | `number` | Roughness factor (0 = mirror, 1 = fully diffuse). Default: 0.35 |
4542
- | `emissive?` | `string` | Emissive glow color (hex string, e.g. "#ff6b35"). |
4543
- | `emissiveIntensity?` | `number` | Emissive intensity multiplier. Default: 1 |
4544
- | `opacity?` | `number` | Opacity (0 = fully transparent, 1 = fully opaque). Default: 1 |
4545
- | `wireframe?` | `boolean` | Render as wireframe. Default: false |
4546
- | `clearcoat?` | `number` | Clearcoat intensity (0–1). Default: 0.1 |
4547
- | `clearcoatRoughness?` | `number` | Clearcoat roughness (0–1). Default: 0.4 |
4548
- | `transmission?` | `number` | Glass/translucency transmission factor (0–1). Renderer support depends on target. |
4549
- | `ior?` | `number` | Index of refraction for transmissive materials. Typical glass is ~1.45. |
4550
- | `thickness?` | `number` | Approximate transmissive volume thickness in model units. |
4551
- | `specularIntensity?` | `number` | Specular highlight intensity (0–1). |
4552
- | `specularColor?` | `string` | Specular highlight tint. |
4553
- | `reflectivity?` | `number` | Reflection strength for supported renderers (0–1). |
4554
-
4555
- `MemberSectionStation`: `{ t: number, width?: number, thickness?: number }`
4556
-
4557
- **`MemberFeature`**: `type: MemberFeatureType`, `name?: string`, `length?: number`, `width?: number`, `diameter?: number`, `counterboreDiameter?: number`, `clearanceDiameter?: number`, `height?: number`, `depth?: number`, `count?: number`, `along?: number`, `across?: number`, `verticalTravel?: number`
4558
-
4559
- `SurfaceMemberExplicitAnchor`: `{ name: string, coordinate: C, carrierName?: string }`
4560
-
4561
- **`MemberSectionInput`**: `width?: number`, `thickness: number`, `edgeRadius?: number`, `direction?: MemberOutwardDirection`, `material?: ProductMaterial`, `stations?: MemberSectionStation[]`
4562
-
4563
- **`CompiledSurfaceMember`**: `name: string`, `shape: Shape`, `mesh: MemberMesh`, `diagnostics: SurfaceDiagnostic[]`, `anchors: { start?: CompiledSurfaceMemberAnchor; end?: CompiledSurfaceMemberAnchor; center: CompiledSurfaceMemberAnchor; landings?: CompiledSurfaceMemberAnchor[]; boundaries?: CompiledSurfaceMemberAnchor[]; features?: CompiledSurfaceMemberAnchor[]; explicit?: CompiledSurfaceMemberAnchor[]; }`
4564
-
4565
- `MemberMesh`: `{ vertices: Vec3[], triangles: Array<[ number, number, number ]> }`
4566
-
4567
- **`SurfaceDiagnostic`**
4568
- - `code?: SurfaceDiagnosticCode` — Stable machine-readable repair hook. Messages may change; codes should not.
4569
- - Also: `category: "carrier" | "path" | "region" | "member" | "feature" | "join" | "compiler", level: "info" | "warning" | "error", message: string, subject?: string`
4570
-
4571
- **`SurfaceMemberAnchorIR`**: `role: SurfaceMemberAnchorRole`, `kind: "member-end" | "member-center" | "member-landing" | "member-boundary" | "plate-edge" | "plate-center" | "plate-landing" | "feature-center" | "surface-coordinate"`, `name?: string`, `point: Vec3`, `normal?: Vec3`
4572
-
4573
- `CompiledSurfaceMemberAnchor`: `{ frame?: SurfaceFrame }`
4574
-
4575
- **`SurfaceFrame`**: `point: Vec3`, `normal: Vec3`, `tangentAlong: Vec3`, `tangentAcross: Vec3`, `matrix: Mat4`, `carrier: string`, `representation: SurfaceCarrierKind | string`, `coordinate: SurfaceCoordinate`
4576
-
4577
4664
  #### `SurfaceBody()` — Start a surface-member body builder for straps, inlays, guards, braces, cuffs, and similar physical members that live on a carrier surface.
4578
4665
 
4579
4666
  ```js
@@ -4997,6 +5084,21 @@ path().moveTo(0,0).lineTo(10,0).lineTo(10,5).mirror('x').close()
4997
5084
  mirror(axis: "x" | "y" | [ number, number ]): this
4998
5085
  ```
4999
5086
 
5087
+ #### `toPolyline()` — Return the open path as a sampled 2D polyline.
5088
+
5089
+ This is for construction geometry such as guide rails, measured centerlines, and curve-driven helpers where the authored path should stay open instead of becoming a filled sketch or stroked profile.
5090
+
5091
+ ```ts
5092
+ const rail = path()
5093
+ .moveTo(24, 0)
5094
+ .bezierTo(32, 44, 28, 92, 18, 120)
5095
+ .toPolyline();
5096
+ ```
5097
+
5098
+ ```ts
5099
+ toPolyline(): [ number, number ][]
5100
+ ```
5101
+
5000
5102
  #### `closeOffset()` — Close the path and return an offset version of the filled Sketch. Positive delta expands outward, negative shrinks inward.
5001
5103
 
5002
5104
  ```ts
@@ -5149,18 +5251,6 @@ with(...children: GroupInput[]): ShapeGroup
5149
5251
  integrate(...details: Shape[]): Shape
5150
5252
  ```
5151
5253
 
5152
- #### `diagnostics()` — Return lowering representation, station names, rail names, and warnings.
5153
-
5154
- ```ts
5155
- diagnostics(): ProductSkinDiagnostics
5156
- ```
5157
-
5158
- **`ProductSkinDiagnostics`**: `representation: ProductSkinRepresentation`, `lowering: string[]`, `warnings: string[]`, `stationNames: string[]`, `railNames: string[]`
5159
-
5160
- **`ProductSkinRepresentation`** — Reported lowering mode for ProductSkin and conformal feature diagnostics.
5161
-
5162
- `"exact" | "sampled" | "mixed" | "fallback"`
5163
-
5164
5254
  #### `uv()` — Create a side/u/v surface-ref query on this skin.
5165
5255
 
5166
5256
  ```ts
@@ -5216,7 +5306,7 @@ stationAt(vOrAxis: number): { ... }
5216
5306
  frame(query: ProductSkinRefQuery): ProductSurfaceFrame
5217
5307
  ```
5218
5308
 
5219
- **`ProductSurfaceFrame`**: `point: Vec3`, `normal: Vec3`, `tangentU: Vec3`, `tangentV: Vec3`, `matrix: Mat4`, `skin: string`, `representation: ProductSkinRepresentation`
5309
+ `ProductSurfaceFrame`: `{ point: Vec3, normal: Vec3, tangentU: Vec3, tangentV: Vec3, matrix: Mat4, skin: string }`
5220
5310
 
5221
5311
  ### `ProductSurfaceRef`
5222
5312
 
@@ -5234,25 +5324,6 @@ frame(query: ProductSkinRefQuery): ProductSurfaceFrame
5234
5324
  frame(overrides?: Partial<ProductSkinRefQuery>): ProductSurfaceFrame
5235
5325
  ```
5236
5326
 
5237
- **`ProductSkinRefQuery`**
5238
-
5239
- | Option | Type | Description |
5240
- |--------|------|-------------|
5241
- | `side` | `ProductSkinSide` | Side of the product skin. `front` is the minimum axis cap, `rear`/`back` is the maximum axis cap. |
5242
- | `u?` | `number` | Across-side parameter for side refs. Defaults to 0.5. |
5243
- | `v?` | `number` | Along-axis parameter, 0 at the first cap and 1 at the rear/back cap. Defaults to 0.5. |
5244
- | `offset?` | `number` | Positive distance away from the surface along the resolved normal. |
5245
-
5246
- **`ProductSkinSide`** — Semantic side of a ProductSkin. `back` is accepted as an alias for `rear`.
5247
-
5248
- `"left" | "right" | "top" | "bottom" | "front" | "rear" | "back"`
5249
-
5250
- **`ProductSurfaceFrame`**: `point: Vec3`, `normal: Vec3`, `tangentU: Vec3`, `tangentV: Vec3`, `matrix: Mat4`, `skin: string`, `representation: ProductSkinRepresentation`
5251
-
5252
- **`ProductSkinRepresentation`** — Reported lowering mode for ProductSkin and conformal feature diagnostics.
5253
-
5254
- `"exact" | "sampled" | "mixed" | "fallback"`
5255
-
5256
5327
  #### `with()` — Return a copy of this ref with side/u/v/offset overrides.
5257
5328
 
5258
5329
  ```ts
@@ -5297,31 +5368,12 @@ ref(u?: number, v?: number, offset?: number): ProductSurfaceRef
5297
5368
  uv(u?: number, v?: number, offset?: number): ProductSkinRefQuery
5298
5369
  ```
5299
5370
 
5300
- **`ProductSkinRefQuery`**
5301
-
5302
- | Option | Type | Description |
5303
- |--------|------|-------------|
5304
- | `side` | `ProductSkinSide` | Side of the product skin. `front` is the minimum axis cap, `rear`/`back` is the maximum axis cap. |
5305
- | `u?` | `number` | Across-side parameter for side refs. Defaults to 0.5. |
5306
- | `v?` | `number` | Along-axis parameter, 0 at the first cap and 1 at the rear/back cap. Defaults to 0.5. |
5307
- | `offset?` | `number` | Positive distance away from the surface along the resolved normal. |
5308
-
5309
- **`ProductSkinSide`** — Semantic side of a ProductSkin. `back` is accepted as an alias for `rear`.
5310
-
5311
- `"left" | "right" | "top" | "bottom" | "front" | "rear" | "back"`
5312
-
5313
5371
  #### `frame()` — Resolve a point/frame on this surface using the builder's side.
5314
5372
 
5315
5373
  ```ts
5316
5374
  frame(query?: Partial<ProductSkinRefQuery>): ProductSurfaceFrame
5317
5375
  ```
5318
5376
 
5319
- **`ProductSurfaceFrame`**: `point: Vec3`, `normal: Vec3`, `tangentU: Vec3`, `tangentV: Vec3`, `matrix: Mat4`, `skin: string`, `representation: ProductSkinRepresentation`
5320
-
5321
- **`ProductSkinRepresentation`** — Reported lowering mode for ProductSkin and conformal feature diagnostics.
5322
-
5323
- `"exact" | "sampled" | "mixed" | "fallback"`
5324
-
5325
5377
  #### `ribbon()` — Start a conformal ribbon on this skin side.
5326
5378
 
5327
5379
  Path points use side-local `u`/`v` coordinates; this builder supplies the side. The returned ProductRibbonBuilder is already bound to the source skin and can be further configured before build(). Use `widthSamples` >= 3 when the ribbon must visibly wrap over curved product sections instead of behaving like a flat strip.
@@ -5399,11 +5451,7 @@ stations(stations: Array<ProductStationBuilder | ProductStationSpec>): this
5399
5451
 
5400
5452
  `ProductStationProfile`: `{ sketch: Sketch, width: number, depth: number, kind: ProductProfileKind, radius?: number, exponent?: number }`
5401
5453
 
5402
- **`ProductProfileKind`**
5403
-
5404
- `"oval" | "roundedRect" | "circle" | "superEllipse" | "custom"`
5405
-
5406
- #### `rails()` — Attach guide rails as ProductSkin IR metadata and diagnostics.
5454
+ #### `rails()` — Attach named guide rails for product-skin construction and downstream surface references.
5407
5455
 
5408
5456
  ```ts
5409
5457
  rails(rails: Record<string, ProductRailSpec>): this
@@ -5421,19 +5469,6 @@ rails(rails: Record<string, ProductRailSpec>): this
5421
5469
  ref(name: string, query: ProductSkinRefQuery): this
5422
5470
  ```
5423
5471
 
5424
- **`ProductSkinRefQuery`**
5425
-
5426
- | Option | Type | Description |
5427
- |--------|------|-------------|
5428
- | `side` | `ProductSkinSide` | Side of the product skin. `front` is the minimum axis cap, `rear`/`back` is the maximum axis cap. |
5429
- | `u?` | `number` | Across-side parameter for side refs. Defaults to 0.5. |
5430
- | `v?` | `number` | Along-axis parameter, 0 at the first cap and 1 at the rear/back cap. Defaults to 0.5. |
5431
- | `offset?` | `number` | Positive distance away from the surface along the resolved normal. |
5432
-
5433
- **`ProductSkinSide`** — Semantic side of a ProductSkin. `back` is accepted as an alias for `rear`.
5434
-
5435
- `"left" | "right" | "top" | "bottom" | "front" | "rear" | "back"`
5436
-
5437
5472
  #### `refs()` — Publish multiple named semantic surface refs on the skin.
5438
5473
 
5439
5474
  ```ts
@@ -5452,32 +5487,11 @@ uv(side: ProductSkinSide, u?: number, v?: number): ProductSkinRefQuery
5452
5487
  material(material: ProductMaterial): this
5453
5488
  ```
5454
5489
 
5455
- `ProductMaterial`: `{ color?: string, material?: ShapeMaterialProps }`
5490
+ #### `color()` — Apply a simple color override to the lowered skin.
5456
5491
 
5457
- **`ShapeMaterialProps`**
5458
-
5459
- | Option | Type | Description |
5460
- |--------|------|-------------|
5461
- | `metalness?` | `number` | Metalness factor (0 = dielectric, 1 = metal). Default: 0.05 |
5462
- | `roughness?` | `number` | Roughness factor (0 = mirror, 1 = fully diffuse). Default: 0.35 |
5463
- | `emissive?` | `string` | Emissive glow color (hex string, e.g. "#ff6b35"). |
5464
- | `emissiveIntensity?` | `number` | Emissive intensity multiplier. Default: 1 |
5465
- | `opacity?` | `number` | Opacity (0 = fully transparent, 1 = fully opaque). Default: 1 |
5466
- | `wireframe?` | `boolean` | Render as wireframe. Default: false |
5467
- | `clearcoat?` | `number` | Clearcoat intensity (0–1). Default: 0.1 |
5468
- | `clearcoatRoughness?` | `number` | Clearcoat roughness (0–1). Default: 0.4 |
5469
- | `transmission?` | `number` | Glass/translucency transmission factor (0–1). Renderer support depends on target. |
5470
- | `ior?` | `number` | Index of refraction for transmissive materials. Typical glass is ~1.45. |
5471
- | `thickness?` | `number` | Approximate transmissive volume thickness in model units. |
5472
- | `specularIntensity?` | `number` | Specular highlight intensity (0–1). |
5473
- | `specularColor?` | `string` | Specular highlight tint. |
5474
- | `reflectivity?` | `number` | Reflection strength for supported renderers (0–1). |
5475
-
5476
- #### `color()` — Apply a simple color override to the lowered skin.
5477
-
5478
- ```ts
5479
- color(color: string): this
5480
- ```
5492
+ ```ts
5493
+ color(color: string): this
5494
+ ```
5481
5495
 
5482
5496
  #### `edgeLength()` — Set the sampled loft target edge length.
5483
5497
 
@@ -5485,7 +5499,7 @@ color(color: string): this
5485
5499
  edgeLength(value: number): this
5486
5500
  ```
5487
5501
 
5488
- #### `wall()` — Records a target wall thickness; v1 keeps exterior skin lowering sampled and reports wall as a diagnostic.
5502
+ #### `wall()` — Record intended wall thickness for product design metadata. Use explicit shelling when the model needs real inner-wall geometry.
5489
5503
 
5490
5504
  ```ts
5491
5505
  wall(thickness: number): this
@@ -5563,7 +5577,7 @@ circle(diameter: number, options?: { segments?: number; }): this
5563
5577
  custom(sketch: Sketch, width: number, depth: number): this
5564
5578
  ```
5565
5579
 
5566
- #### `crown()` — Stores a semantic crown amount for diagnostics and future rail solving.
5580
+ #### `crown()` — Set the station crown amount for soft product-section intent.
5567
5581
 
5568
5582
  ```ts
5569
5583
  crown(amount: number): this
@@ -5575,14 +5589,6 @@ crown(amount: number): this
5575
5589
  toSpec(): ProductStationSpec
5576
5590
  ```
5577
5591
 
5578
- `ProductStationSpec`: `{ name: string, center: Vec3, profile: ProductStationProfile, crown?: number }`
5579
-
5580
- `ProductStationProfile`: `{ sketch: Sketch, width: number, depth: number, kind: ProductProfileKind, radius?: number, exponent?: number }`
5581
-
5582
- **`ProductProfileKind`**
5583
-
5584
- `"oval" | "roundedRect" | "circle" | "superEllipse" | "custom"`
5585
-
5586
5592
  ### `ProductPanelBuilder`
5587
5593
 
5588
5594
  **Properties:**
@@ -5623,27 +5629,6 @@ thickness(thickness: number): this
5623
5629
  material(material: ProductMaterial): this
5624
5630
  ```
5625
5631
 
5626
- `ProductMaterial`: `{ color?: string, material?: ShapeMaterialProps }`
5627
-
5628
- **`ShapeMaterialProps`**
5629
-
5630
- | Option | Type | Description |
5631
- |--------|------|-------------|
5632
- | `metalness?` | `number` | Metalness factor (0 = dielectric, 1 = metal). Default: 0.05 |
5633
- | `roughness?` | `number` | Roughness factor (0 = mirror, 1 = fully diffuse). Default: 0.35 |
5634
- | `emissive?` | `string` | Emissive glow color (hex string, e.g. "#ff6b35"). |
5635
- | `emissiveIntensity?` | `number` | Emissive intensity multiplier. Default: 1 |
5636
- | `opacity?` | `number` | Opacity (0 = fully transparent, 1 = fully opaque). Default: 1 |
5637
- | `wireframe?` | `boolean` | Render as wireframe. Default: false |
5638
- | `clearcoat?` | `number` | Clearcoat intensity (0–1). Default: 0.1 |
5639
- | `clearcoatRoughness?` | `number` | Clearcoat roughness (0–1). Default: 0.4 |
5640
- | `transmission?` | `number` | Glass/translucency transmission factor (0–1). Renderer support depends on target. |
5641
- | `ior?` | `number` | Index of refraction for transmissive materials. Typical glass is ~1.45. |
5642
- | `thickness?` | `number` | Approximate transmissive volume thickness in model units. |
5643
- | `specularIntensity?` | `number` | Specular highlight intensity (0–1). |
5644
- | `specularColor?` | `string` | Specular highlight tint. |
5645
- | `reflectivity?` | `number` | Reflection strength for supported renderers (0–1). |
5646
-
5647
5632
  #### `color()` — Apply a simple color override to the panel.
5648
5633
 
5649
5634
  ```ts
@@ -5666,23 +5651,9 @@ attachTo(ref: ProductRefInput, options?: ProductPanelAttachOptions): Shape
5666
5651
 
5667
5652
  `ProductSurfaceRef`
5668
5653
 
5669
- `ProductAttachOptions`: `{ offset?: number, inset?: number }`
5670
5654
 
5671
5655
  `ProductPanelAttachOptions`: `{ at?: Partial<ProductSkinRefQuery>, thickness?: number, material?: ProductMaterial, color?: string }`
5672
5656
 
5673
- **`ProductSkinRefQuery`**
5674
-
5675
- | Option | Type | Description |
5676
- |--------|------|-------------|
5677
- | `side` | `ProductSkinSide` | Side of the product skin. `front` is the minimum axis cap, `rear`/`back` is the maximum axis cap. |
5678
- | `u?` | `number` | Across-side parameter for side refs. Defaults to 0.5. |
5679
- | `v?` | `number` | Along-axis parameter, 0 at the first cap and 1 at the rear/back cap. Defaults to 0.5. |
5680
- | `offset?` | `number` | Positive distance away from the surface along the resolved normal. |
5681
-
5682
- **`ProductSkinSide`** — Semantic side of a ProductSkin. `back` is accepted as an alias for `rear`.
5683
-
5684
- `"left" | "right" | "top" | "bottom" | "front" | "rear" | "back"`
5685
-
5686
5657
  ### `ProductRibbonBuilder`
5687
5658
 
5688
5659
  Builder for thin trim, label, grip, and split-line features that bend with a ProductSkin surface.
@@ -5707,53 +5678,6 @@ on(skin: ProductSkin, points: ProductRibbonPathPoint[], options?: ProductRibbonB
5707
5678
 
5708
5679
  `ProductSkinRefQuery | ProductSurfaceRef`
5709
5680
 
5710
- **`ProductSkinRefQuery`**
5711
-
5712
- | Option | Type | Description |
5713
- |--------|------|-------------|
5714
- | `side` | `ProductSkinSide` | Side of the product skin. `front` is the minimum axis cap, `rear`/`back` is the maximum axis cap. |
5715
- | `u?` | `number` | Across-side parameter for side refs. Defaults to 0.5. |
5716
- | `v?` | `number` | Along-axis parameter, 0 at the first cap and 1 at the rear/back cap. Defaults to 0.5. |
5717
- | `offset?` | `number` | Positive distance away from the surface along the resolved normal. |
5718
-
5719
- **`ProductSkinSide`** — Semantic side of a ProductSkin. `back` is accepted as an alias for `rear`.
5720
-
5721
- `"left" | "right" | "top" | "bottom" | "front" | "rear" | "back"`
5722
-
5723
- **`ProductRibbonBuildOptions`** — Options shared by Product.ribbon() builders and Product.surface(...).ribbon(...).
5724
-
5725
- | Option | Type | Description |
5726
- |--------|------|-------------|
5727
- | `width?` | `number` | Width across the surface in millimeters. |
5728
- | `thickness?` | `number` | Solid thickness outward from the source surface in millimeters. |
5729
- | `offset?` | `number` | Positive clearance between the source surface and the ribbon's inner face. |
5730
- | `samples?` | `number` | Samples along the ribbon path. Higher values bend more smoothly. |
5731
- | `widthSamples?` | `number` | Samples across the ribbon width. Use 3+ to visibly wrap over curved cross-sections. |
5732
- | `resolution?` | `number` | Tessellation resolution passed to the lowered NURBS surface. |
5733
- | `material?` | `ProductMaterial` | Apply a product material preset to the ribbon. |
5734
- | `color?` | `string` | Apply a simple color override. |
5735
-
5736
- `ProductMaterial`: `{ color?: string, material?: ShapeMaterialProps }`
5737
-
5738
- **`ShapeMaterialProps`**
5739
-
5740
- | Option | Type | Description |
5741
- |--------|------|-------------|
5742
- | `metalness?` | `number` | Metalness factor (0 = dielectric, 1 = metal). Default: 0.05 |
5743
- | `roughness?` | `number` | Roughness factor (0 = mirror, 1 = fully diffuse). Default: 0.35 |
5744
- | `emissive?` | `string` | Emissive glow color (hex string, e.g. "#ff6b35"). |
5745
- | `emissiveIntensity?` | `number` | Emissive intensity multiplier. Default: 1 |
5746
- | `opacity?` | `number` | Opacity (0 = fully transparent, 1 = fully opaque). Default: 1 |
5747
- | `wireframe?` | `boolean` | Render as wireframe. Default: false |
5748
- | `clearcoat?` | `number` | Clearcoat intensity (0–1). Default: 0.1 |
5749
- | `clearcoatRoughness?` | `number` | Clearcoat roughness (0–1). Default: 0.4 |
5750
- | `transmission?` | `number` | Glass/translucency transmission factor (0–1). Renderer support depends on target. |
5751
- | `ior?` | `number` | Index of refraction for transmissive materials. Typical glass is ~1.45. |
5752
- | `thickness?` | `number` | Approximate transmissive volume thickness in model units. |
5753
- | `specularIntensity?` | `number` | Specular highlight intensity (0–1). |
5754
- | `specularColor?` | `string` | Specular highlight tint. |
5755
- | `reflectivity?` | `number` | Reflection strength for supported renderers (0–1). |
5756
-
5757
5681
  #### `fromRefs()` — Follow explicit surface refs.
5758
5682
 
5759
5683
  Useful for named refs or paths assembled elsewhere. The builder resolves each ref frame and interpolates between those frames; use on(skin, points) when you need full skin-side sampling between sparse control points.
@@ -5816,48 +5740,6 @@ color(color: string): this
5816
5740
  build(options?: ProductRibbonBuildOptions): Shape
5817
5741
  ```
5818
5742
 
5819
- #### `buildWithDiagnostics()` — Build a conformal ribbon and return surface-feature diagnostics.
5820
-
5821
- Use this while validating API usage or model fidelity; diagnostics report sampling counts, side-span clamping, lowering mode, and warnings that should be visible in reviews.
5822
-
5823
- ```ts
5824
- buildWithDiagnostics(options?: ProductRibbonBuildOptions): ProductRibbonResult
5825
- ```
5826
-
5827
- **`ProductRibbonResult`** — Shape plus diagnostics returned by ProductRibbonBuilder.buildWithDiagnostics().
5828
- - `shape: Shape` — Lowered conformal ribbon shape.
5829
- - `diagnostics: ProductRibbonDiagnostics` — Sampling and lowering diagnostics for the returned shape.
5830
-
5831
- **`ProductRibbonDiagnostics`** — Diagnostics describing how a conformal ribbon was sampled and lowered.
5832
-
5833
- | Option | Type | Description |
5834
- |--------|------|-------------|
5835
- | `name` | `string` | Ribbon shape name. |
5836
- | `skin?` | `string` | Source skin name when the ribbon follows a ProductSkin directly. |
5837
- | `side?` | `ProductSkinSide` | Source skin side when all path points are on one semantic side. |
5838
- | `pathPointCount` | `number` | Number of control path points supplied before interpolation. |
5839
- | `width` | `number` | Final ribbon width in millimeters. |
5840
- | `thickness` | `number` | Final ribbon solid thickness in millimeters. |
5841
- | `offset` | `number` | Final normal offset from the source surface in millimeters. |
5842
- | `samples` | `number` | Final sample count along the ribbon path. |
5843
- | `widthSamples` | `number` | Final sample count across the ribbon width. |
5844
- | `resolution` | `number` | NURBS tessellation resolution used for the lowered surface. |
5845
- | `lowering` | `"nurbsSurface"` | Lowering primitive used for the ribbon shape. |
5846
- | `expectedFidelity` | `ProductSkinRepresentation` | Expected fidelity inherited from the source skin/ref sampling mode. |
5847
- | `clampedUCount` | `number` | Number of generated width samples clamped to the valid side span. |
5848
- | `maxUClampDistance` | `number` | Largest absolute u-distance lost to side-span clamping. |
5849
- | `warnings` | `string[]` | Non-fatal sampling and lowering warnings. |
5850
-
5851
- **`ProductSkinRepresentation`** — Reported lowering mode for ProductSkin and conformal feature diagnostics.
5852
-
5853
- `"exact" | "sampled" | "mixed" | "fallback"`
5854
-
5855
- #### `diagnostics()` — Return diagnostics from the most recent build, if this builder has been built.
5856
-
5857
- ```ts
5858
- diagnostics(): ProductRibbonDiagnostics | undefined
5859
- ```
5860
-
5861
5743
  ### `ProductSpoutBuilder`
5862
5744
 
5863
5745
  **Properties:**
@@ -5880,14 +5762,6 @@ from(ref: ProductSurfaceRef): this
5880
5762
  sections(sections: Array<Sketch | ProductStationBuilder | ProductStationSpec>): this
5881
5763
  ```
5882
5764
 
5883
- `ProductStationSpec`: `{ name: string, center: Vec3, profile: ProductStationProfile, crown?: number }`
5884
-
5885
- `ProductStationProfile`: `{ sketch: Sketch, width: number, depth: number, kind: ProductProfileKind, radius?: number, exponent?: number }`
5886
-
5887
- **`ProductProfileKind`**
5888
-
5889
- `"oval" | "roundedRect" | "circle" | "superEllipse" | "custom"`
5890
-
5891
5765
  #### `projection()` — Set the projection length along the source ref normal.
5892
5766
 
5893
5767
  ```ts
@@ -5906,27 +5780,6 @@ edgeLength(value: number): this
5906
5780
  material(material: ProductMaterial): this
5907
5781
  ```
5908
5782
 
5909
- `ProductMaterial`: `{ color?: string, material?: ShapeMaterialProps }`
5910
-
5911
- **`ShapeMaterialProps`**
5912
-
5913
- | Option | Type | Description |
5914
- |--------|------|-------------|
5915
- | `metalness?` | `number` | Metalness factor (0 = dielectric, 1 = metal). Default: 0.05 |
5916
- | `roughness?` | `number` | Roughness factor (0 = mirror, 1 = fully diffuse). Default: 0.35 |
5917
- | `emissive?` | `string` | Emissive glow color (hex string, e.g. "#ff6b35"). |
5918
- | `emissiveIntensity?` | `number` | Emissive intensity multiplier. Default: 1 |
5919
- | `opacity?` | `number` | Opacity (0 = fully transparent, 1 = fully opaque). Default: 1 |
5920
- | `wireframe?` | `boolean` | Render as wireframe. Default: false |
5921
- | `clearcoat?` | `number` | Clearcoat intensity (0–1). Default: 0.1 |
5922
- | `clearcoatRoughness?` | `number` | Clearcoat roughness (0–1). Default: 0.4 |
5923
- | `transmission?` | `number` | Glass/translucency transmission factor (0–1). Renderer support depends on target. |
5924
- | `ior?` | `number` | Index of refraction for transmissive materials. Typical glass is ~1.45. |
5925
- | `thickness?` | `number` | Approximate transmissive volume thickness in model units. |
5926
- | `specularIntensity?` | `number` | Specular highlight intensity (0–1). |
5927
- | `specularColor?` | `string` | Specular highlight tint. |
5928
- | `reflectivity?` | `number` | Reflection strength for supported renderers (0–1). |
5929
-
5930
5783
  #### `color()` — Apply a simple color override to the spout.
5931
5784
 
5932
5785
  ```ts
@@ -5945,8 +5798,6 @@ build(): Shape
5945
5798
  attach(options?: ProductAttachOptions): Shape
5946
5799
  ```
5947
5800
 
5948
- `ProductAttachOptions`: `{ offset?: number, inset?: number }`
5949
-
5950
5801
  ### `ProductHandleBuilder`
5951
5802
 
5952
5803
  **Properties:**
@@ -5969,12 +5820,6 @@ between(upper: ProductSurfaceRef, lower: Vec3): this
5969
5820
  spine(points: Vec3[] | ProductRailSpec): this
5970
5821
  ```
5971
5822
 
5972
- `ProductRailSpec`: `{ kind: ProductRailKind, points: Vec3[], degree?: number, name?: string }`
5973
-
5974
- **`ProductRailKind`**
5975
-
5976
- `"bezier" | "nurbs" | "polyline"`
5977
-
5978
5823
  #### `grip()` — Set the grip cross-section profile.
5979
5824
 
5980
5825
  ```ts
@@ -5987,27 +5832,6 @@ grip(profile: Sketch): this
5987
5832
  material(material: ProductMaterial): this
5988
5833
  ```
5989
5834
 
5990
- `ProductMaterial`: `{ color?: string, material?: ShapeMaterialProps }`
5991
-
5992
- **`ShapeMaterialProps`**
5993
-
5994
- | Option | Type | Description |
5995
- |--------|------|-------------|
5996
- | `metalness?` | `number` | Metalness factor (0 = dielectric, 1 = metal). Default: 0.05 |
5997
- | `roughness?` | `number` | Roughness factor (0 = mirror, 1 = fully diffuse). Default: 0.35 |
5998
- | `emissive?` | `string` | Emissive glow color (hex string, e.g. "#ff6b35"). |
5999
- | `emissiveIntensity?` | `number` | Emissive intensity multiplier. Default: 1 |
6000
- | `opacity?` | `number` | Opacity (0 = fully transparent, 1 = fully opaque). Default: 1 |
6001
- | `wireframe?` | `boolean` | Render as wireframe. Default: false |
6002
- | `clearcoat?` | `number` | Clearcoat intensity (0–1). Default: 0.1 |
6003
- | `clearcoatRoughness?` | `number` | Clearcoat roughness (0–1). Default: 0.4 |
6004
- | `transmission?` | `number` | Glass/translucency transmission factor (0–1). Renderer support depends on target. |
6005
- | `ior?` | `number` | Index of refraction for transmissive materials. Typical glass is ~1.45. |
6006
- | `thickness?` | `number` | Approximate transmissive volume thickness in model units. |
6007
- | `specularIntensity?` | `number` | Specular highlight intensity (0–1). |
6008
- | `specularColor?` | `string` | Specular highlight tint. |
6009
- | `reflectivity?` | `number` | Reflection strength for supported renderers (0–1). |
6010
-
6011
5835
  #### `padMaterial()` — Apply a product material preset to handle landing pads.
6012
5836
 
6013
5837
  ```ts
@@ -6056,181 +5880,6 @@ toShape(): Shape
6056
5880
  toGroup(): ShapeGroup
6057
5881
  ```
6058
5882
 
6059
- ### `ProductRibbonBuilder`
6060
-
6061
- Builder for thin trim, label, grip, and split-line features that bend with a ProductSkin surface.
6062
-
6063
- **Properties:**
6064
-
6065
- | Property | Type | Description |
6066
- |----------|------|-------------|
6067
- | `name` | `string` | — |
6068
-
6069
- **Methods:**
6070
-
6071
- #### `on()` — Follow a ProductSkin with side/u/v path queries or refs.
6072
-
6073
- This is the highest-fidelity mode because every interpolated sample is resolved through ProductSkin.frame(), so the ribbon bends along the selected side as station width/depth changes. All query path points must stay on one side; split side transitions into separate ribbons.
6074
-
6075
- ```ts
6076
- on(skin: ProductSkin, points: ProductRibbonPathPoint[], options?: ProductRibbonBuildOptions): this
6077
- ```
6078
-
6079
- **`ProductRibbonPathPoint`** — Path point for Product.ribbon().on(...): either a side/u/v query or a resolved surface ref.
6080
-
6081
- `ProductSkinRefQuery | ProductSurfaceRef`
6082
-
6083
- **`ProductSkinRefQuery`**
6084
-
6085
- | Option | Type | Description |
6086
- |--------|------|-------------|
6087
- | `side` | `ProductSkinSide` | Side of the product skin. `front` is the minimum axis cap, `rear`/`back` is the maximum axis cap. |
6088
- | `u?` | `number` | Across-side parameter for side refs. Defaults to 0.5. |
6089
- | `v?` | `number` | Along-axis parameter, 0 at the first cap and 1 at the rear/back cap. Defaults to 0.5. |
6090
- | `offset?` | `number` | Positive distance away from the surface along the resolved normal. |
6091
-
6092
- **`ProductSkinSide`** — Semantic side of a ProductSkin. `back` is accepted as an alias for `rear`.
6093
-
6094
- `"left" | "right" | "top" | "bottom" | "front" | "rear" | "back"`
6095
-
6096
- **`ProductRibbonBuildOptions`** — Options shared by Product.ribbon() builders and Product.surface(...).ribbon(...).
6097
-
6098
- | Option | Type | Description |
6099
- |--------|------|-------------|
6100
- | `width?` | `number` | Width across the surface in millimeters. |
6101
- | `thickness?` | `number` | Solid thickness outward from the source surface in millimeters. |
6102
- | `offset?` | `number` | Positive clearance between the source surface and the ribbon's inner face. |
6103
- | `samples?` | `number` | Samples along the ribbon path. Higher values bend more smoothly. |
6104
- | `widthSamples?` | `number` | Samples across the ribbon width. Use 3+ to visibly wrap over curved cross-sections. |
6105
- | `resolution?` | `number` | Tessellation resolution passed to the lowered NURBS surface. |
6106
- | `material?` | `ProductMaterial` | Apply a product material preset to the ribbon. |
6107
- | `color?` | `string` | Apply a simple color override. |
6108
-
6109
- `ProductMaterial`: `{ color?: string, material?: ShapeMaterialProps }`
6110
-
6111
- **`ShapeMaterialProps`**
6112
-
6113
- | Option | Type | Description |
6114
- |--------|------|-------------|
6115
- | `metalness?` | `number` | Metalness factor (0 = dielectric, 1 = metal). Default: 0.05 |
6116
- | `roughness?` | `number` | Roughness factor (0 = mirror, 1 = fully diffuse). Default: 0.35 |
6117
- | `emissive?` | `string` | Emissive glow color (hex string, e.g. "#ff6b35"). |
6118
- | `emissiveIntensity?` | `number` | Emissive intensity multiplier. Default: 1 |
6119
- | `opacity?` | `number` | Opacity (0 = fully transparent, 1 = fully opaque). Default: 1 |
6120
- | `wireframe?` | `boolean` | Render as wireframe. Default: false |
6121
- | `clearcoat?` | `number` | Clearcoat intensity (0–1). Default: 0.1 |
6122
- | `clearcoatRoughness?` | `number` | Clearcoat roughness (0–1). Default: 0.4 |
6123
- | `transmission?` | `number` | Glass/translucency transmission factor (0–1). Renderer support depends on target. |
6124
- | `ior?` | `number` | Index of refraction for transmissive materials. Typical glass is ~1.45. |
6125
- | `thickness?` | `number` | Approximate transmissive volume thickness in model units. |
6126
- | `specularIntensity?` | `number` | Specular highlight intensity (0–1). |
6127
- | `specularColor?` | `string` | Specular highlight tint. |
6128
- | `reflectivity?` | `number` | Reflection strength for supported renderers (0–1). |
6129
-
6130
- #### `fromRefs()` — Follow explicit surface refs.
6131
-
6132
- Useful for named refs or paths assembled elsewhere. The builder resolves each ref frame and interpolates between those frames; use on(skin, points) when you need full skin-side sampling between sparse control points.
6133
-
6134
- ```ts
6135
- fromRefs(points: ProductSurfaceRef[], options?: ProductRibbonBuildOptions): this
6136
- ```
6137
-
6138
- #### `width()` — Set ribbon width in millimeters.
6139
-
6140
- ```ts
6141
- width(width: number): this
6142
- ```
6143
-
6144
- #### `thickness()` — Set solid thickness outward from the source surface in millimeters.
6145
-
6146
- ```ts
6147
- thickness(thickness: number): this
6148
- ```
6149
-
6150
- #### `offset()` — Set positive clearance between the source surface and the ribbon's inner face.
6151
-
6152
- ```ts
6153
- offset(offset: number): this
6154
- ```
6155
-
6156
- #### `samples()` — Set samples along the path.
6157
-
6158
- ```ts
6159
- samples(samples: number): this
6160
- ```
6161
-
6162
- #### `widthSamples()` — Set samples across the width. Use 3+ to bend over curved cross-sections.
6163
-
6164
- ```ts
6165
- widthSamples(samples: number): this
6166
- ```
6167
-
6168
- #### `resolution()` — Set NURBS tessellation resolution.
6169
-
6170
- ```ts
6171
- resolution(resolution: number): this
6172
- ```
6173
-
6174
- #### `material()` — Apply a product material preset.
6175
-
6176
- ```ts
6177
- material(material: ProductMaterial): this
6178
- ```
6179
-
6180
- #### `color()` — Apply a simple color override.
6181
-
6182
- ```ts
6183
- color(color: string): this
6184
- ```
6185
-
6186
- #### `build()` — Build a conformal ribbon as a thin NURBS surface solid.
6187
-
6188
- ```ts
6189
- build(options?: ProductRibbonBuildOptions): Shape
6190
- ```
6191
-
6192
- #### `buildWithDiagnostics()` — Build a conformal ribbon and return surface-feature diagnostics.
6193
-
6194
- Use this while validating API usage or model fidelity; diagnostics report sampling counts, side-span clamping, lowering mode, and warnings that should be visible in reviews.
6195
-
6196
- ```ts
6197
- buildWithDiagnostics(options?: ProductRibbonBuildOptions): ProductRibbonResult
6198
- ```
6199
-
6200
- **`ProductRibbonResult`** — Shape plus diagnostics returned by ProductRibbonBuilder.buildWithDiagnostics().
6201
- - `shape: Shape` — Lowered conformal ribbon shape.
6202
- - `diagnostics: ProductRibbonDiagnostics` — Sampling and lowering diagnostics for the returned shape.
6203
-
6204
- **`ProductRibbonDiagnostics`** — Diagnostics describing how a conformal ribbon was sampled and lowered.
6205
-
6206
- | Option | Type | Description |
6207
- |--------|------|-------------|
6208
- | `name` | `string` | Ribbon shape name. |
6209
- | `skin?` | `string` | Source skin name when the ribbon follows a ProductSkin directly. |
6210
- | `side?` | `ProductSkinSide` | Source skin side when all path points are on one semantic side. |
6211
- | `pathPointCount` | `number` | Number of control path points supplied before interpolation. |
6212
- | `width` | `number` | Final ribbon width in millimeters. |
6213
- | `thickness` | `number` | Final ribbon solid thickness in millimeters. |
6214
- | `offset` | `number` | Final normal offset from the source surface in millimeters. |
6215
- | `samples` | `number` | Final sample count along the ribbon path. |
6216
- | `widthSamples` | `number` | Final sample count across the ribbon width. |
6217
- | `resolution` | `number` | NURBS tessellation resolution used for the lowered surface. |
6218
- | `lowering` | `"nurbsSurface"` | Lowering primitive used for the ribbon shape. |
6219
- | `expectedFidelity` | `ProductSkinRepresentation` | Expected fidelity inherited from the source skin/ref sampling mode. |
6220
- | `clampedUCount` | `number` | Number of generated width samples clamped to the valid side span. |
6221
- | `maxUClampDistance` | `number` | Largest absolute u-distance lost to side-span clamping. |
6222
- | `warnings` | `string[]` | Non-fatal sampling and lowering warnings. |
6223
-
6224
- **`ProductSkinRepresentation`** — Reported lowering mode for ProductSkin and conformal feature diagnostics.
6225
-
6226
- `"exact" | "sampled" | "mixed" | "fallback"`
6227
-
6228
- #### `diagnostics()` — Return diagnostics from the most recent build, if this builder has been built.
6229
-
6230
- ```ts
6231
- diagnostics(): ProductRibbonDiagnostics | undefined
6232
- ```
6233
-
6234
5883
  ### `CylinderCarrier`
6235
5884
 
6236
5885
  **Properties:**
@@ -6362,12 +6011,6 @@ bounds(): SurfaceBounds
6362
6011
  offset(distance: number): CylinderCarrier
6363
6012
  ```
6364
6013
 
6365
- #### `diagnostics()`
6366
-
6367
- ```ts
6368
- diagnostics(): SurfaceDiagnostic[]
6369
- ```
6370
-
6371
6014
  #### `mirrorCoordinate()`
6372
6015
 
6373
6016
  ```ts
@@ -6487,12 +6130,6 @@ bounds(): SurfaceBounds
6487
6130
  offset(distance: number): PlaneCarrier
6488
6131
  ```
6489
6132
 
6490
- #### `diagnostics()`
6491
-
6492
- ```ts
6493
- diagnostics(): SurfaceDiagnostic[]
6494
- ```
6495
-
6496
6133
  #### `mirrorCoordinate()`
6497
6134
 
6498
6135
  ```ts
@@ -6517,10 +6154,6 @@ mirrorCoordinate(coordinate: PlaneSurfaceCoordinate): PlaneSurfaceCoordinate
6517
6154
  surface(side: ProductSkinSide): ProductSkinCarrier
6518
6155
  ```
6519
6156
 
6520
- **`ProductSkinSide`** — Semantic side of a ProductSkin. `back` is accepted as an alias for `rear`.
6521
-
6522
- `"left" | "right" | "top" | "bottom" | "front" | "rear" | "back"`
6523
-
6524
6157
  #### [`path()`](/docs/sketch#path)
6525
6158
 
6526
6159
  ```ts
@@ -6609,16 +6242,6 @@ bounds(): SurfaceBounds
6609
6242
  offset(distance: number): ProductSkinCarrier
6610
6243
  ```
6611
6244
 
6612
- #### `diagnostics()`
6613
-
6614
- ```ts
6615
- diagnostics(): SurfaceDiagnostic[]
6616
- ```
6617
-
6618
- **`SurfaceDiagnostic`**
6619
- - `code?: SurfaceDiagnosticCode` — Stable machine-readable repair hook. Messages may change; codes should not.
6620
- - Also: `category: "carrier" | "path" | "region" | "member" | "feature" | "join" | "compiler", level: "info" | "warning" | "error", message: string, subject?: string`
6621
-
6622
6245
  #### `mirrorCoordinate()`
6623
6246
 
6624
6247
  ```ts
@@ -6761,12 +6384,6 @@ withHole(name: string, input: SurfaceBandHoleInput): SurfaceBand<C>
6761
6384
  holes(): SurfaceBandHoleRegion[]
6762
6385
  ```
6763
6386
 
6764
- #### `diagnostics()`
6765
-
6766
- ```ts
6767
- diagnostics(samples?: number): SurfaceDiagnostic[]
6768
- ```
6769
-
6770
6387
  ### `SurfaceBodyBuilder`
6771
6388
 
6772
6389
  **Properties:**
@@ -6807,18 +6424,6 @@ autoJoinAtSharedAnchors(): this
6807
6424
  build(): Shape | ShapeGroup
6808
6425
  ```
6809
6426
 
6810
- #### `buildWithDiagnostics()`
6811
-
6812
- ```ts
6813
- buildWithDiagnostics(): SurfaceBodyBuildResult
6814
- ```
6815
-
6816
- #### `buildDebug()`
6817
-
6818
- ```ts
6819
- buildDebug(): SurfaceBodyBuildResult
6820
- ```
6821
-
6822
6427
  ### `SurfaceMemberBuilder`
6823
6428
 
6824
6429
  #### `plate()`
@@ -6881,7 +6486,7 @@ cutout(name: string, feature: MemberFeature | RoundedSlotBuilder): this
6881
6486
  counterbore(name: string, feature: MemberFeature | CounterboreBuilder): this
6882
6487
  ```
6883
6488
 
6884
- #### `anchorAt()` — Add a named anchor at a carrier surface coordinate for diagnostics, debug views, and future named-anchor joins.
6489
+ #### `anchorAt()` — Add a named anchor at a carrier surface coordinate for explicit member joins.
6885
6490
 
6886
6491
  ```ts
6887
6492
  anchorAt(name: string, coordinate: C | SurfaceAnchor<C>): this
@@ -6929,18 +6534,6 @@ autoJoinAtSharedAnchors(): SurfaceBodyBuilder
6929
6534
  build(): Shape | ShapeGroup
6930
6535
  ```
6931
6536
 
6932
- #### `buildWithDiagnostics()`
6933
-
6934
- ```ts
6935
- buildWithDiagnostics(): SurfaceBodyBuildResult
6936
- ```
6937
-
6938
- #### `buildDebug()`
6939
-
6940
- ```ts
6941
- buildDebug(): SurfaceBodyBuildResult
6942
- ```
6943
-
6944
6537
  ### `SurfaceJoinBuilder`
6945
6538
 
6946
6539
  #### `betweenAnchors()` — Select named anchors on the source and target members before lowering this join.
@@ -7023,7 +6616,6 @@ toFeature(name?: string): MemberFeature
7023
6616
 
7024
6617
  - `Edge(options: BlendEdgeOptions): Shape`
7025
6618
  - `Surface(options: BlendSurfaceOptions): Shape`
7026
- - `CornerY(options: BlendCornerYOptions): Shape` — Current implementation uses continuity-controlled edge fillets on solid edges. It does not yet provide a dedicated open-sheet or multi-patch Y-corner solver. Follow progress: https://github.com/KoStard/forgecad-private/issues/162
7027
6619
 
7028
6620
  ### `Analysis`
7029
6621
 
@@ -7062,15 +6654,12 @@ toFeature(name?: string): MemberFeature
7062
6654
  - `cylinder(name: string): CylinderCarrier` — Create an analytic cylinder carrier for bottles, limbs, tubes, guards, and cuffs.
7063
6655
  - `plane(name: string): PlaneCarrier` — Create an analytic plane carrier for plates and local flat construction surfaces.
7064
6656
  - `productSkin(skin: ProductSkin): ProductSkinCarrier` — Adapt an existing ProductSkin into the general surface-member carrier protocol.
7065
- - `mesh(name: string): never` — Reserved stub for future parameterized mesh carriers; arbitrary mesh parameterization is not implemented yet.
7066
- - `scan(name: string): never` — Reserved stub for future scan/body-fit carriers; arbitrary scan parameterization is not implemented yet.
7067
6657
 
7068
6658
  ### `SurfaceMembers`
7069
6659
 
7070
6660
  - `Body(name: string): SurfaceBodyBuilder` — Start a surface-member body builder for straps, inlays, guards, braces, cuffs, and similar physical members that live on a carrier surface.
7071
6661
  - `Band: typeof SurfaceBand`
7072
6662
  - `band<C extends SurfaceCoordinate>(path: SurfacePath<C> | SurfacePathBuilder<C>, width: WidthProfile, cap?: SurfaceBandCap): SurfaceBand<C>`
7073
- - `compileMember<C extends SurfaceCoordinate>(input: Omit<SurfaceMemberSpec<C>, "section"> & { section: MemberSection | MemberSectionInput; }): CompiledSurfaceMember`
7074
6663
 
7075
6664
  ### `Slot`
7076
6665
 
@@ -7114,7 +6703,12 @@ bomToCsv(rows: BomRow[]): string
7114
6703
 
7115
6704
  **`BomRow`**: `part: string`, `qty: number`, `material?: string`, `process?: string`, `tolerance?: string`, `notes?: string`, `metadata?: PartMetadata`
7116
6705
 
7117
- **`PartMetadata`**: `material?: string`, `process?: string`, `tolerance?: string`, `qty?: number`, `notes?: string`, `densityKgM3?: number`, `massKg?: number`
6706
+ **`PartMetadata`**
6707
+
6708
+ | Option | Type | Description |
6709
+ |--------|------|-------------|
6710
+ | `tags?` | `string \| readonly string[]` | Viewport organization tags applied to scene objects produced from this part. |
6711
+ | `material?`, `process?`, `tolerance?`, `qty?`, `notes?`, `densityKgM3?`, `massKg?` | | — |
7118
6712
 
7119
6713
  #### `assembly()` — Create an assembly container with named parts and joints for kinematic mechanisms.
7120
6714
 
@@ -7755,13 +7349,13 @@ toGroup(): ShapeGroup
7755
7349
  Each part becomes `{ name, shape }` or `{ name, group: [...] }` if the part is a [`ShapeGroup`](/docs/core#shapegroup). Top-level scripts should normally return the `SolvedAssembly` directly. Use `toGroup()` when you need [`ShapeGroup`](/docs/core#shapegroup) behavior; use this method only for advanced scene-graph control where you need access to the flat per-part array with metadata.
7756
7350
 
7757
7351
  ```ts
7758
- toSceneObjects(): Array<{ name: string; shape?: Shape; group?: Array<{ name: string; shape: Shape; }>; metadata?: PartMetadata; }>
7352
+ toSceneObjects(): Array<{ name: string; shape?: Shape; group?: Array<{ name: string; shape: Shape; tags?: string[]; }>; metadata?: PartMetadata; }>
7759
7353
  ```
7760
7354
 
7761
7355
  #### `toScene()` — Backward-compatible alias for `toSceneObjects()`.
7762
7356
 
7763
7357
  ```ts
7764
- toScene(): Array<{ name: string; shape?: Shape; group?: Array<{ name: string; shape: Shape; }>; metadata?: PartMetadata; }>
7358
+ toScene(): Array<{ name: string; shape?: Shape; group?: Array<{ name: string; shape: Shape; tags?: string[]; }>; metadata?: PartMetadata; }>
7765
7359
  ```
7766
7360
 
7767
7361
  #### [`bom()`](/docs/output#bom) — Generate a bill of materials for all parts in the solved assembly.
@@ -8522,7 +8116,12 @@ robotExport(options: RobotExportOptions): CollectedRobotExport
8522
8116
 
8523
8117
  `AssemblyPartDef`: `{ name: string, part: AssemblyPart, base: Transform, metadata?: PartMetadata }`
8524
8118
 
8525
- **`PartMetadata`**: `material?: string`, `process?: string`, `tolerance?: string`, `qty?: number`, `notes?: string`, `densityKgM3?: number`, `massKg?: number`
8119
+ **`PartMetadata`**
8120
+
8121
+ | Option | Type | Description |
8122
+ |--------|------|-------------|
8123
+ | `tags?` | `string \| readonly string[]` | Viewport organization tags applied to scene objects produced from this part. |
8124
+ | `material?`, `process?`, `tolerance?`, `qty?`, `notes?`, `densityKgM3?`, `massKg?` | | — |
8526
8125
 
8527
8126
  **`AssemblyJointDef`**: `name: string`, `type: JointType`, `parent: string`, `child: string`, `frame: Transform`, `axis: Vec3`, `min?: number`, `max?: number`, `defaultValue: number`, `unit?: string`, `effort?: number`, `velocity?: number`, `damping?: number`, `friction?: number`, `connectorRefs?: JointConnectorRefs`
8528
8127
 
@@ -8630,6 +8229,7 @@ Pre-built fasteners, gears, pipes, structural profiles, and utility shapes. Acce
8630
8229
  ## Contents
8631
8230
 
8632
8231
  - [TangentLoop2D](#tangentloop2d)
8232
+ - [DriveWheelBuilder](#drivewheelbuilder)
8633
8233
  - [lib](#lib)
8634
8234
 
8635
8235
  ---
@@ -8670,6 +8270,32 @@ toProfile(): Sketch
8670
8270
  offsetBand(thickness: number): Sketch
8671
8271
  ```
8672
8272
 
8273
+ ### `DriveWheelBuilder`
8274
+
8275
+ #### `addSpurTeethBetween()` — Add an involute spur-tooth window on part of the pitch circle.
8276
+
8277
+ ```ts
8278
+ addSpurTeethBetween(options: DriveWheelSpurTeethRegionOptions): this
8279
+ ```
8280
+
8281
+ #### `addSolidArcBetween()` — Add a constant-radius solid arc region such as a dwell, stop, or pusher.
8282
+
8283
+ ```ts
8284
+ addSolidArcBetween(options: DriveWheelSolidArcRegionOptions): this
8285
+ ```
8286
+
8287
+ #### `addShapeRegion()` — Add a fully custom region shape while preserving region metadata.
8288
+
8289
+ ```ts
8290
+ addShapeRegion(name: string, shape: Shape, options?: DriveWheelShapeRegionOptions): this
8291
+ ```
8292
+
8293
+ #### `build()` — Build the final wheel shape with a bore connector and region metadata.
8294
+
8295
+ ```ts
8296
+ build(): Shape
8297
+ ```
8298
+
8673
8299
  ---
8674
8300
 
8675
8301
  ## Constants
@@ -8678,7 +8304,7 @@ offsetBand(thickness: number): Sketch
8678
8304
 
8679
8305
  Pre-built parametric parts available in user scripts as `lib.*`.
8680
8306
 
8681
- Every key in this object becomes a method on the `lib` namespace exposed to `.forge.js` scripts. The catalog includes:
8307
+ Every key in this object becomes a method or namespace on the `lib` object exposed to `.forge.js` scripts. The catalog includes:
8682
8308
 
8683
8309
  **Fasteners:** `bolt`, `nut`, `washer`, `fastenerSet`, `fastenerHole`, `boltHole`, `counterbore`, `hexNut`, `holePattern`
8684
8310
 
@@ -8688,7 +8314,9 @@ Every key in this object becomes a method on the `lib` namespace exposed to `.fo
8688
8314
 
8689
8315
  **Threads:** `thread`
8690
8316
 
8691
- **Gears:** `spurGear`, `bevelGear`, `faceGear`, `sideGear`, `ringGear`, `rackGear`, `gearPair`, `bevelGearPair`, `faceGearPair`, `sideGearPair`
8317
+ **Gears:** `spurGear`, `sectorGear`, `driveWheel`, `bevelGear`, `faceGear`, `sideGear`, `ringGear`, `rackGear`, `gearPair`, `bevelGearPair`, `faceGearPair`, `sideGearPair`
8318
+
8319
+ **Gear bodies:** `gearBodies.disk`, `gearBodies.diskWithHub`, `gearBodies.spoked`, `gearBodies.fromProfile` plus direct aliases `gearBodyDisk`, `gearBodyDiskWithHub`, `gearBodySpoked`, `gearBodyFromProfile`
8692
8320
 
8693
8321
  **Gear ratios (pure math helpers):** `gearRatio`, `rackRatio`, `planetaryRatio`
8694
8322
 
@@ -8696,7 +8324,7 @@ Every key in this object becomes a method on the `lib` namespace exposed to `.fo
8696
8324
 
8697
8325
  **Utilities:** `explode`
8698
8326
 
8699
- Extend this by adding new entries here and registering the corresponding runner binding in `runner.ts`. Sizes outside the supported ranges will throw at runtime with a descriptive error.
8327
+ Sizes outside the supported ranges will throw at runtime with a descriptive error.
8700
8328
 
8701
8329
  - `boltHole(diameter: number, depth: number): Shape` — Simple cylindrical through-hole cutter centered on Z=0. Subtract the result from a part to produce a plain cylindrical clearance hole. For ISO metric sizes with fit classes and counterbore/countersink, use {
8702
8330
  - `fastenerHole(opts: FastenerHoleOptions): Shape` — ISO metric fastener hole cutter with optional counterbore or countersink. **Details** Returns a cutter shape (subtract from a solid to produce the hole). Sizes outside M2–M10 will throw. Extend `METRIC_HOLE_TABLE` in this file to add new sizes. **Example** ```ts const plate = box(60, 40, 8) .subtract(lib.fastenerHole({ size: 'M5', fit: 'normal', depth: 8 }) .translate(15, 10, 4)); ```
@@ -8734,6 +8362,14 @@ Extend this by adding new entries here and registering the corresponding runner
8734
8362
  - `rackRatio(module: number, pinionTeeth: number): number` — Coupling ratio between a pinion and a rack. When the pinion rotates by `θ` degrees, the rack slides by `θ × (π × module × teeth / 360)` mm. Equivalently, 1mm of rack travel = `180 / (π × pitchRadius)` degrees of pinion rotation.
8735
8363
  - `planetaryRatio(sunTeeth: number, ringTeeth: number): number` — Planetary gear reduction ratio when the ring is held fixed. Input: sun. Output: carrier. Ratio: `1 + ringTeeth / sunTeeth`. One turn of the sun produces `1 / ratio` turns of the carrier.
8736
8364
  - `boltPattern(options: BoltPatternOptions): BoltPattern` — Define a bolt pattern once and cut it from multiple parts. const base = bolts.cut(box(60, 50, 10), 12, { from: -1 }); const cover = bolts.cut(box(60, 50, 3), 5, { from: -1 }); // Same positions in both parts — guaranteed aligned. ```
8365
+ - `driveWheel(options?: DriveWheelOptions): DriveWheelBuilder` — Start a composable exceptional gear or drive wheel.
8366
+ - `readDriveWheelMeta(shape: Shape): DriveWheelMeta | null` — Read the functional-region metadata attached by `driveWheel().build()`.
8367
+ - `sectorGear(options: SectorGearOptions): Shape` — Involute sector gear with teeth on only part of the pitch circle. Specify the full-circle pitch as `teethOnFullCircle`, then choose the active tooth window with `firstTooth` and `toothCount`. The body is separate from the tooth region: pass a `gearBody...` shape for spokes, hubs, and product styling, or omit it for a simple root-radius disk. **Example** ```ts const body = lib.gearBodies.spoked({ outerRadius: 22, rimWidth: 3, hubDiameter: 10, spokeCount: 5, spokeWidth: 2.5, faceWidth: 8, boreDiameter: 5, }); const sector = lib.sectorGear({ module: 1.25, teethOnFullCircle: 36, toothCount: 10, faceWidth: 8, body, }); ```
8368
+ - `gearBodies: { ... }` — Gear body preset namespace: disk, diskWithHub, spoked, and fromProfile.
8369
+ - `gearBodyDisk(options: GearBodyDiskOptions): Shape` — Solid disk/ring gear body, independent from any tooth geometry.
8370
+ - `gearBodyDiskWithHub(options: GearBodyDiskWithHubOptions): Shape` — Disk gear body with a raised center hub.
8371
+ - `gearBodySpoked(options: GearBodySpokedOptions): Shape` — Spoked gear body with an outer rim, center hub, and radial spokes.
8372
+ - `gearBodyFromProfile(profile: Sketch, options: GearBodyFromProfileOptions): Shape` — Extrude a custom 2D profile into a gear body.
8737
8373
 
8738
8374
  ---
8739
8375
 
@@ -8903,7 +8539,7 @@ When `lights` is specified, **all** default lights are removed. You must include
8903
8539
 
8904
8540
  Setting `camera.position` overrides auto-framing — the viewport will no longer auto-fit the geometry on script reload.
8905
8541
 
8906
- Named render views let scripts check in repeatable cameras next to the model code. The canonical shape is `{ camera: { position, target } }`, and a direct camera shorthand `{ position, target }` is also accepted. Use the canonical shape when you may add view metadata later. Use it from the CLI with `forgecad render 3d model.forge.js --view hero`.
8542
+ Named render views let scripts check in repeatable cameras next to the model code. The canonical shape is `{ camera: { position, target } }`, and a direct camera shorthand `{ position, target }` is also accepted. Use the canonical shape when you may add view metadata later. Use it from the CLI with `--view hero` on `forgecad render 3d`, `forgecad render hq`, or `forgecad capture`.
8907
8543
 
8908
8544
  Model journeys let scripts check in a compact guided path through named objects. Each journey has ordered `steps`; each step can name a `focus` target by object name/tree path, provide a caption, and optionally provide an explicit camera. In the viewer, journeys are opt-in: they appear as a small Explore control and do not move the camera until the user starts them. Use `forgecad run model.forge.js --journeys` or `--journeys-json` to inspect resolved targets.
8909
8545
 
@@ -8972,7 +8608,6 @@ scene(options: SceneOptions): void
8972
8608
  | `behavior?` | `"opt-in" \| "auto"` | Whether the viewer should offer or auto-open the journey. First slice supports opt-in. |
8973
8609
  | `steps` | `SceneJourneyStepConfig[]` | Ordered journey spine. Branches can be added later without changing this core contract. |
8974
8610
  | `valid?` | `boolean` | True unless any journey or step diagnostic has level "error". |
8975
- | `diagnostics?` | `SceneJourneyDiagnostic[]` | Whole-journey diagnostics, including unresolved startsAt and step target diagnostics. |
8976
8611
 
8977
8612
  **`SceneJourneyStepConfig`**
8978
8613
 
@@ -8985,9 +8620,6 @@ scene(options: SceneOptions): void
8985
8620
  | `camera?` | `SceneViewCameraConfig` | Optional explicit camera for this step. When omitted, the viewer fits `focus`. |
8986
8621
  | `resolvedFocusId?` | `string \| null` | Resolved object id after script execution, when `focus` matched exactly one object. |
8987
8622
  | `resolvedFocusPath?` | `string \| null` | Resolved object tree path or name after script execution. |
8988
- | `diagnostics?` | `SceneJourneyDiagnostic[]` | Resolution diagnostics for this step. |
8989
-
8990
- `SceneJourneyDiagnostic`: `{ level: SceneJourneyDiagnosticLevel, message: string, stepId?: string, suggestions?: string[] }`
8991
8623
 
8992
8624
  **`SceneLightConfig`**
8993
8625
 
@@ -9063,10 +8695,6 @@ viewConfig({
9063
8695
  viewConfig(options?: ViewConfigOptions): void
9064
8696
  ```
9065
8697
 
9066
- `ViewConfigOptions`: `{ jointOverlay?: JointOverlayViewConfigOptions }`
9067
-
9068
- **`JointOverlayViewConfigOptions`**: `enabled?: boolean`, `axisColor?: string`, `axisCoreColor?: string`, `arcColor?: string`, `zeroColor?: string`, `arcVisualLimitDeg?: number`, `axisLengthScale?: number`, `axisLengthMin?: number`, `axisLineRadiusScale?: number`, `axisLineRadiusMin?: number`, `axisLineRadiusMax?: number`, `spokeLineRadiusScale?: number`, `spokeLineRadiusMin?: number`, `spokeLineRadiusMax?: number`, `arcLineRadiusScale?: number`, `arcLineRadiusMin?: number`, `arcLineRadiusMax?: number`, `axisDotRadiusScale?: number`, `axisDotRadiusMin?: number`, `axisArrowRadiusScale?: number`, `axisArrowRadiusMin?: number`, `axisArrowLengthScale?: number`, `axisArrowLengthMin?: number`, `axisArrowOffsetFactor?: number`, `arcRadiusScale?: number`, `arcRadiusMin?: number`, `arcDotRadiusScale?: number`, `arcDotRadiusMin?: number`, `arcArrowRadiusScale?: number`, `arcArrowRadiusMin?: number`, `arcArrowLengthScale?: number`, `arcArrowLengthMin?: number`, `arcArrowOffsetFactor?: number`, `arcStepDeg?: number`, `arcMinSteps?: number`, `arcTubeSegmentsMin?: number`, `arcTubeSegmentsFactor?: number`, `arcTubeRadialSegments?: number`
9069
-
9070
8698
  #### `explodeView()` — Configure how the viewport explode slider offsets returned objects.
9071
8699
 
9072
8700
  Offsets are resolved from the returned object tree, not a flat list. In `radial` mode each node follows its parent branch direction, then fans locally from the immediate parent center — nested assemblies peel apart level by level. In fixed-axis or fixed-vector modes, the branch follows that axis/vector but nested descendants fan out perpendicular by default.
@@ -9576,8 +9204,9 @@ Setting `KNUCK_R = H / 2` exactly makes the body cross-section a stadium that pe
9576
9204
 
9577
9205
  `forgecad render inspect` writes a deterministic directory bundle for agents,
9578
9206
  tests, and automation. Use it when a single shaded PNG is too ambiguous and the
9579
- consumer needs geometry-aware signals such as depth, normals, part identity,
9580
- physical connected components, collisions, local thickness, or cross-sections.
9207
+ consumer needs geometry-aware signals such as depth, normals, surface roughness,
9208
+ part identity, physical connected components, collisions, local thickness, or
9209
+ cross-sections.
9581
9210
 
9582
9211
  ## When To Use It
9583
9212
 
@@ -9597,14 +9226,16 @@ forgecad render inspect model.forge.js --channels rgb,mask,section
9597
9226
  forgecad render inspect model.forge.js --channels collisions --focus Bench
9598
9227
  forgecad render inspect model.forge.js --channels rgb,mask --hide "Bench.Slat0,Bench.Slat1"
9599
9228
  forgecad render inspect model.forge.js --channels thickness --min-thickness 1.2 --warn-thickness 2.0
9229
+ forgecad render inspect channels
9600
9230
  ```
9601
9231
 
9602
9232
  The default output directory is `<script-name>-inspect/` next to the input file.
9603
9233
  Pass `--force` to replace an existing bundle directory.
9604
9234
 
9605
9235
  There are no default channels. Pass `--channels` every time as a
9606
- comma-separated subset. Keep bundles targeted to the current question so heavy
9607
- analyses do not run unnecessarily.
9236
+ comma-separated subset. Run `forgecad render inspect channels` to list the
9237
+ supported channels in the installed CLI. Keep bundles targeted to the current
9238
+ question so heavy analyses do not run unnecessarily.
9608
9239
 
9609
9240
  `--focus` and `--hide` use the same object-name filtering semantics as
9610
9241
  `forgecad run` and `forgecad render 3d`. A bare `--focus` hides mock objects;
@@ -9660,12 +9291,13 @@ implemented channel in one bundle:
9660
9291
 
9661
9292
  ```bash
9662
9293
  forgecad render inspect model.forge.js --channels depth,normals
9294
+ forgecad render inspect model.forge.js --channels rgb,roughness
9663
9295
  forgecad render inspect model.forge.js --channels rgb,mask,collisions
9664
9296
  forgecad render inspect model.forge.js --channels rgb,section,thickness
9665
9297
  ```
9666
9298
 
9667
- Supported channels are `rgb`, `depth`, `normals`, `mask`, `connectivity`,
9668
- `distance`, `collisions`, `thickness`, and `section`.
9299
+ Supported channels are `rgb`, `depth`, `normals`, `roughness`, `mask`,
9300
+ `connectivity`, `distance`, `collisions`, `thickness`, and `section`.
9669
9301
 
9670
9302
  ## Channel Semantics
9671
9303
 
@@ -9692,6 +9324,27 @@ normal = normalize((rgb / 255) * 2 - 1)
9692
9324
 
9693
9325
  Background pixels are black and should be treated as `null`.
9694
9326
 
9327
+ `roughness` emits a mesh-dihedral surface-quality heatmap. Smooth and gently
9328
+ curved triangles render as a faint translucent shadow over black, while
9329
+ triangles adjacent to sharp, harsh, boundary, or non-manifold mesh edges render
9330
+ in orange or magenta:
9331
+
9332
+ ```text
9333
+ shadow = max adjacent angle < sharpAngleDeg
9334
+ orange = sharpAngleDeg <= angle < harshAngleDeg
9335
+ magenta = angle >= harshAngleDeg, boundary, or non-manifold
9336
+ ```
9337
+
9338
+ The default thresholds are `smoothAngleDeg=5`, `sharpAngleDeg=30`, and
9339
+ `harshAngleDeg=90`. The manifest stores the method, thresholds, palette, object
9340
+ list, per-object triangle and edge counts, area percentages by smooth,
9341
+ moderate, sharp, and harsh classes, angle percentiles, maximum angle, quality
9342
+ score, and warnings. Moderate angles are reported in the manifest but stay in
9343
+ the shadow layer by default so intentionally curved surfaces do not light up as
9344
+ defects. Use this channel to spot spiky tessellation, accidental faceting,
9345
+ jagged boolean residue, and dense sharp-corner regions without losing the
9346
+ silhouette of otherwise smooth surfaces.
9347
+
9695
9348
  `mask` emits one object-color image per view. Black is background. Non-black
9696
9349
  pixels resolve through `manifest.channels.mask.objects`, which includes object
9697
9350
  index, RGB color, object id, name, group, tree path, and mock flag. Edge pixels
@@ -9734,9 +9387,15 @@ root = largest component by body count, object count, then bbox volume
9734
9387
  rootDistance = shortest accumulated gap distance from root component
9735
9388
  ```
9736
9389
 
9390
+ For large scenes the manifest does not materialize the complete component gap
9391
+ graph, because that graph is quadratic in the number of components. The
9392
+ `gapEdgeCount` field reports the logical complete-graph edge count used by the
9393
+ analysis. `gapEdges` stores a compact evidence subset containing nearest-gap
9394
+ and root-parent edges.
9395
+
9737
9396
  The PNG colors components from green at the root/near distances through yellow to
9738
9397
  red at the farthest rooted component. The manifest stores the root component,
9739
- maximum rooted distance, complete component gap edge list, nearest-gap data, and
9398
+ maximum rooted distance, compact gap edge evidence, nearest-gap data, and
9740
9399
  shortest-path parent fields. The current v1 metric is bbox-based: it measures air
9741
9400
  gaps between component bounding boxes, not exact closest mesh-surface distance.
9742
9401
 
@@ -9753,8 +9412,8 @@ collision = boolean intersection volume > 0.1mm^3
9753
9412
  ```
9754
9413
 
9755
9414
  The manifest stores the inspected objects, collision pair names/ids, overlap
9756
- volume, warnings, render style, and each collision finding's `groupIndex`,
9757
- `color`, and `hex`. Exact interior pixels can be matched against
9415
+ volume, broadphase counters, warnings, render style, and each collision finding's
9416
+ `groupIndex`, `color`, and `hex`. Exact interior pixels can be matched against
9758
9417
  `manifest.channels.collisions.collisions[].color`; antialiased edges may blend
9759
9418
  with the ghosted source geometry. If `--focus PartA,PartB` is used, everything
9760
9419
  except those objects is hidden, `PartA` and `PartB` are ghosted, and their
@@ -10026,6 +9685,36 @@ intersect(...others: SdfShape[]): SdfShape
10026
9685
  clipBox(x: number, y: number, z: number): SdfShape
10027
9686
  ```
10028
9687
 
9688
+ #### `fillWith()` — Keep only the material where this shape overlaps another SDF pattern.
9689
+
9690
+ ```ts
9691
+ fillWith(pattern: SdfShape): SdfShape
9692
+ ```
9693
+
9694
+ #### `fillWithGyroid()` — Keep only the gyroid lattice inside this shape.
9695
+
9696
+ ```ts
9697
+ fillWithGyroid(options: TpmsOptions): SdfShape
9698
+ ```
9699
+
9700
+ #### `fillWithSchwarzP()` — Keep only the Schwarz-P lattice inside this shape.
9701
+
9702
+ ```ts
9703
+ fillWithSchwarzP(options: TpmsOptions): SdfShape
9704
+ ```
9705
+
9706
+ #### `fillWithDiamond()` — Keep only the diamond TPMS lattice inside this shape.
9707
+
9708
+ ```ts
9709
+ fillWithDiamond(options: TpmsOptions): SdfShape
9710
+ ```
9711
+
9712
+ #### `fillWithLidinoid()` — Keep only the lidinoid TPMS lattice inside this shape.
9713
+
9714
+ ```ts
9715
+ fillWithLidinoid(options: TpmsOptions): SdfShape
9716
+ ```
9717
+
10029
9718
  #### `smoothUnion()` — Smooth union — blends shapes together with a smooth radius.
10030
9719
 
10031
9720
  ```ts
@@ -10104,6 +9793,14 @@ bend(radius: number): SdfShape
10104
9793
  repeat(spacing: Vec3, count?: Vec3): SdfShape
10105
9794
  ```
10106
9795
 
9796
+ #### `circularArray()` — Arrange this SDF in a circular array around the Z axis.
9797
+
9798
+ The source shape is translated by `offset` in +X before arraying. This uses angular domain folding, so evaluation stays O(1): the source SDF is sampled twice no matter how many copies are requested.
9799
+
9800
+ ```ts
9801
+ circularArray(count: number, offset?: number): SdfShape
9802
+ ```
9803
+
10107
9804
  #### `shell()` — Hollow out, keeping only a shell of given thickness.
10108
9805
 
10109
9806
  ```ts
@@ -10116,8 +9813,8 @@ shell(thickness: number): SdfShape
10116
9813
  // Function displacement
10117
9814
  shape.displace((x, y, z) => Math.sin(x) * 0.5)
10118
9815
 
10119
- // Pattern displacement (e.g. basketWeave)
10120
- shape.displace(sdf.basketWeave({ threads: 16, spacing: 3 }))
9816
+ // Pattern displacement from a 3D SDF field
9817
+ shape.displace(sdf.knurl({ pitch: 2, depth: 0.3 }))
10121
9818
  ```
10122
9819
 
10123
9820
  ```ts
@@ -10130,10 +9827,16 @@ Automatically detects the shape's UV parametrization (sphere, cylinder, torus) f
10130
9827
 
10131
9828
  UV coordinates are in **surface millimeters** — patterns defined with `spacing: 3` always produce 3mm spacing, regardless of shape size.
10132
9829
 
9830
+ Prefer `sdf.pattern2d()` or built-in surface patterns when the relief should stay on the native shader and meshing path. Callback functions are supported for experimentation, but they are opaque to the typed pattern optimizer.
9831
+
10133
9832
  ```js
10134
- // Surface-following basket weave — auto-detects sphere UV
9833
+ // Native typed pattern — auto-detects sphere UV
9834
+ const p = sdf.pattern2d()
9835
+ const ribs = p.stripes({ spacing: 3, width: 0.8, depth: 0.35 })
9836
+ .add(p.sineWave({ direction: [0, 1], wavelength: 14, amplitude: 0.08 }))
9837
+
10135
9838
  sdf.sphere(27).shell(3)
10136
- .surfaceDisplace(sdf.basketWeave({ spacing: 3, depth: 0.8 }))
9839
+ .surfaceDisplace(ribs)
10137
9840
  .toShape()
10138
9841
 
10139
9842
  // Custom 2D pattern via function
@@ -10205,9 +9908,11 @@ return {
10205
9908
  - `brick(options?: BrickOptions): SdfShape` — Brick/stone wall pattern — running bond with mortar grooves.
10206
9909
  - `weave(options?: WeaveOptions): SdfShape` — Grid lattice pattern — two families of infinite slabs crossing at 90°.
10207
9910
  - `basketWeave(options?: BasketWeaveOptions): SurfacePattern` — Basket weave surface pattern — threads with over-under crossings in UV space. Returns a SurfacePattern for use with `.surfaceDisplace()`.
9911
+ - `pattern2d(): Pattern2DBuilder` — Create typed, composable 2D surface patterns for `.surfaceDisplace()`.
10208
9912
  - `twist(shape: SdfShape, degreesPerUnit: number): SdfShape` — Twist an SDF shape around the Z axis.
10209
9913
  - `bend(shape: SdfShape, radius: number): SdfShape` — Bend an SDF shape around the Z axis.
10210
9914
  - `repeat(shape: SdfShape, spacing: Vec3, count?: Vec3): SdfShape` — Repeat an SDF shape in space.
9915
+ - `circularArray(shape: SdfShape, count: number, offset?: number): SdfShape` — Arrange an SDF shape in a circular array around the Z axis with O(1) folded-domain evaluation.
10211
9916
  - `SurfacePattern: typeof SurfacePattern` — A 2D surface pattern — a heightmap function for use with `.surfaceDisplace()`.
10212
9917
  - `fromFunction(fn: SdfFunctionSource, options: SdfFunctionOptions): SdfShape` — Create a custom SDF from one expression; shader-safe expressions raymarch directly.
10213
9918
  - `Sculpt: { sphere: (radius: number) => SdfShape; box: (x: number, y: number, z: number, options?: SculptBoxOptions) => SdfShape; cylinder: (height: number, radius: number) => SdfShape; disk: (radius: number, thickness?: number) => SdfShape; circle: (radius: number, thickness?: number) => SdfShape; capsule: (height: number, radius: number) => SdfShape; torus: (majorRadius: number, minorRadius: number) => SdfShape; cone: (height: number, radius: number) => SdfShape; tube: (points: SculptPointList, options?: SculptTubeOptions) => SdfShape; curve: (points: SculptPointList, options?: SculptTubeOptions) => SdfShape; path: (points: SculptPointList, options?: SculptTubeOptions) => SdfShape; blend: (first?: SculptBlendInput | SculptBlendOptions, optionsOrShape?: SculptBlendInput | SculptBlendOptions, ...rest: (SculptBlendInput | SculptBlendOptions)[]) => SdfShape; union: (first?: SculptBlendInput, ...rest: SculptBlendInput[]) => SdfShape; carve: (base: SdfShape, cutters: SculptBlendInput, options?: SculptBlendOptions) => SdfShape; keep: (first?: SculptBlendInput | SculptBlendOptions, optionsOrShape?: SculptBlendInput | SculptBlendOptions, ...rest: (SculptBlendInput | SculptBlendOptions)[]) => SdfShape; polish: (shape: SdfShape, input?: SculptPolishInput) => SdfShape; material: (input?: SculptPolishInput) => ShapeMaterialProps & { color?: string; }; look: (preset?: SculptLookPreset) => SceneOptions; knownMaterials: typeof knownSculptMaterialPresets; }` — Sculpt-like facade: friendly liquid-modeling verbs backed by the same SDF kernel.