forgecad 0.7.0 → 0.8.0

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 (158) hide show
  1. package/README.md +1 -1
  2. package/dist/assets/{AdminPage-DAu1C1ST.js → AdminPage-D4bocK4E.js} +1 -1
  3. package/dist/assets/{DocsPage-Gc_BCdqC.js → DocsPage-D3A_g8V3.js} +85 -45
  4. package/dist/assets/{EditorApp-DG1-oUSV.css → EditorApp-BWYUSpUN.css} +133 -51
  5. package/dist/assets/EditorApp-Cihhqcsq.js +11692 -0
  6. package/dist/assets/{EmbedViewer-CEO8XbV8.js → EmbedViewer-kWjKaC_t.js} +1 -1
  7. package/dist/assets/LandingPageProofDriven-Bg2IUc3l.css +856 -0
  8. package/dist/assets/LandingPageProofDriven-DXkKlyhI.js +601 -0
  9. package/dist/assets/{PricingPage-BSrxu6d7.js → PricingPage-BsU5vzEx.js} +1 -1
  10. package/dist/assets/{SettingsPage-FUCSIRq6.js → SettingsPage-PqvpAKIs.js} +1 -1
  11. package/dist/assets/{evalWorker-KoR0SNKq.js → evalWorker-C-hzNUMy.js} +2218 -286
  12. package/dist/assets/{index-wTEK39at.js → index-Pz321YAt.js} +7416 -1481
  13. package/dist/assets/{index-CyVd1D4D.css → index-ay13WNfa.css} +501 -2
  14. package/dist/assets/{manifold-B1sGWdYk.js → manifold-BcbjWLIo.js} +3 -3
  15. package/dist/assets/{manifold-D7o0N50J.js → manifold-DBckbFgx.js} +1 -1
  16. package/dist/assets/{manifold-G5sBaXzi.js → manifold-O2AAGXyj.js} +1 -1
  17. package/dist/assets/{reportWorker-DYcRHhv9.js → reportWorker-Dxr-5A7w.js} +2003 -259
  18. package/dist/docs/index.html +2 -2
  19. package/dist/docs-raw/CLI.md +488 -0
  20. package/dist/docs-raw/generated/assembly.md +19 -11
  21. package/dist/docs-raw/generated/concepts.md +1023 -360
  22. package/dist/docs-raw/generated/core.md +1165 -264
  23. package/dist/docs-raw/generated/curves.md +168 -1
  24. package/dist/docs-raw/generated/lib.md +10 -5
  25. package/dist/docs-raw/generated/output.md +1 -1
  26. package/dist/docs-raw/generated/sdf.md +208 -0
  27. package/dist/docs-raw/generated/sketch.md +1281 -329
  28. package/dist/docs-raw/generated/viewport.md +29 -2
  29. package/dist/index.html +2 -2
  30. package/dist/landing/proof-ams-adapter.png +0 -0
  31. package/dist/landing/proof-bolt-and-nut.png +0 -0
  32. package/dist/landing/proof-fillet-enclosure.png +0 -0
  33. package/dist/landing/proof-glasses.png +0 -0
  34. package/dist/landing/proof-gyroid.png +0 -0
  35. package/dist/sitemap.xml +6 -6
  36. package/dist-cli/forgecad.js +3148 -555
  37. package/dist-cli/forgecad.js.map +1 -1
  38. package/dist-cli/{solver-FV7TJZGI.js → solver-46FFSK2U.js} +1 -3
  39. package/dist-cli/{solver-FV7TJZGI.js.map → solver-46FFSK2U.js.map} +1 -1
  40. package/dist-skill/CONTEXT.md +3700 -1153
  41. package/dist-skill/SKILL-dev.md +15 -17
  42. package/dist-skill/SKILL.md +14 -9
  43. package/dist-skill/docs/API/core/concepts.md +28 -1
  44. package/dist-skill/docs/CLI.md +488 -0
  45. package/dist-skill/docs/generated/assembly.md +19 -11
  46. package/dist-skill/docs/generated/core.md +1165 -264
  47. package/dist-skill/docs/generated/curves.md +168 -1
  48. package/dist-skill/docs/generated/lib.md +10 -5
  49. package/dist-skill/docs/generated/output.md +1 -1
  50. package/dist-skill/docs/generated/sdf.md +208 -0
  51. package/dist-skill/docs/generated/sketch.md +1281 -329
  52. package/dist-skill/docs/generated/viewport.md +29 -2
  53. package/dist-skill/docs/guides/joint-design.md +78 -0
  54. package/dist-skill/docs-dev/API/core/concepts.md +28 -1
  55. package/dist-skill/docs-dev/CLI.md +488 -0
  56. package/dist-skill/docs-dev/coding.md +1 -1
  57. package/dist-skill/docs-dev/component-model.md +164 -0
  58. package/dist-skill/docs-dev/generated/assembly.md +19 -11
  59. package/dist-skill/docs-dev/generated/core.md +1165 -264
  60. package/dist-skill/docs-dev/generated/curves.md +168 -1
  61. package/dist-skill/docs-dev/generated/lib.md +10 -5
  62. package/dist-skill/docs-dev/generated/output.md +1 -1
  63. package/dist-skill/docs-dev/generated/sdf.md +208 -0
  64. package/dist-skill/docs-dev/generated/sketch.md +1281 -329
  65. package/dist-skill/docs-dev/generated/viewport.md +29 -2
  66. package/dist-skill/docs-dev/guides/joint-design.md +78 -0
  67. package/examples/api/attachTo-basics.forge.js +3 -3
  68. package/examples/api/bill-of-materials.forge.js +9 -9
  69. package/examples/api/bolt-pattern.forge.js +5 -5
  70. package/examples/api/boolean-operations.forge.js +2 -2
  71. package/examples/api/bounding-box-visualizer.forge.js +1 -1
  72. package/examples/api/clone-duplicate.forge.js +1 -1
  73. package/examples/api/connector-assembly.forge.js +4 -2
  74. package/examples/api/connector-basics.forge.js +5 -5
  75. package/examples/api/constrained-sketch-mechanical.forge.js +4 -4
  76. package/examples/api/elbow-test.forge.js +3 -3
  77. package/examples/api/extrude-options.forge.js +4 -4
  78. package/examples/api/fillet-showcase.forge.js +1 -1
  79. package/examples/api/gears-tier1.forge.js +5 -5
  80. package/examples/api/group-test.forge.js +2 -2
  81. package/examples/api/mesh-import-slats.forge.js +3 -3
  82. package/examples/api/patterns.forge.js +3 -3
  83. package/examples/api/pointAlong-orientation.forge.js +2 -2
  84. package/examples/api/profile-2020-b-slot6.forge.js +4 -4
  85. package/examples/api/sketch-rounding-strategies.forge.js +1 -1
  86. package/examples/api/smooth-curve-connections.forge.js +1 -1
  87. package/examples/api/transition-curves.forge.js +3 -3
  88. package/examples/constraints/01-fully-constrained-rect.forge.js +2 -2
  89. package/examples/constraints/02-underconstrained-triangle.forge.js +1 -1
  90. package/examples/constraints/03-redundant-constraints.forge.js +2 -2
  91. package/examples/constraints/05-parallel-with-linedistance.forge.js +2 -2
  92. package/examples/constraints/06-complex-spectrogram.forge.js +1 -1
  93. package/examples/constraints/07-perpendicular-chain.forge.js +4 -4
  94. package/examples/constraints/08-symmetric-bracket.forge.js +4 -4
  95. package/examples/constraints/09-stress-spiral.forge.js +1 -1
  96. package/examples/constraints/10-stress-honeycomb.forge.js +1 -1
  97. package/examples/constraints/11-surface-grid.forge.js +2 -2
  98. package/examples/constraints/12-surface-nested.forge.js +4 -4
  99. package/examples/constraints/13-surface-complex.forge.js +1 -1
  100. package/examples/exact-arc-housing.forge.js +12 -0
  101. package/examples/furniture/adjustable-table.forge.js +13 -13
  102. package/examples/furniture/bathroom.forge.js +15 -15
  103. package/examples/furniture/chair.forge.js +12 -12
  104. package/examples/furniture/picture-frame.forge.js +6 -6
  105. package/examples/furniture/shoe-rack-doors.forge.js +8 -8
  106. package/examples/furniture/shoe-rack.forge.js +7 -7
  107. package/examples/furniture/table-lamp.forge.js +8 -8
  108. package/examples/gcode/lissajous-vase.forge.js +4 -4
  109. package/examples/gcode/math-surface.forge.js +3 -3
  110. package/examples/gcode/parametric-vase.forge.js +4 -4
  111. package/examples/gcode/spiral-tower.forge.js +4 -4
  112. package/examples/generative/crystal-growth.forge.js +7 -7
  113. package/examples/generative/frost-spires.forge.js +6 -6
  114. package/examples/generative/golden-spiral-tower.forge.js +8 -8
  115. package/examples/generative/molten-forge.forge.js +6 -6
  116. package/examples/generative/neon-coral.forge.js +7 -7
  117. package/examples/mechanical/3d-printer.forge.js +9 -9
  118. package/examples/mechanical/5-finger-robot-hand.forge.js +4 -4
  119. package/examples/mechanical/airplane-propeller.forge.js +7 -7
  120. package/examples/mechanical/bolt-and-nut.forge.js +10 -10
  121. package/examples/mechanical/door-with-hinges.forge.js +7 -7
  122. package/examples/mechanical/fillet-enclosure.forge.js +14 -10
  123. package/examples/mechanical/headphone-hanger-v2.forge.js +9 -9
  124. package/examples/mechanical/robot_hand.forge.js +10 -10
  125. package/examples/mechanical/robot_hand_2.forge.js +17 -17
  126. package/examples/nurbs-surface.forge.js +8 -0
  127. package/examples/nurbs-tube.forge.js +7 -0
  128. package/examples/products/bottle.forge.js +7 -7
  129. package/examples/products/chess-set.forge.js +6 -6
  130. package/examples/products/classical-piano.forge.js +9 -9
  131. package/examples/products/clock.forge.js +21 -21
  132. package/examples/products/cup.forge.js +5 -5
  133. package/examples/products/iphone.forge.js +12 -12
  134. package/examples/products/laptop.forge.js +9 -9
  135. package/examples/products/laser-cut-box.forge.js +6 -6
  136. package/examples/products/laser-cut-tray.forge.js +6 -6
  137. package/examples/products/liquid-soap-dispenser.forge.js +5 -5
  138. package/examples/products/origami-fish.forge.js +6 -6
  139. package/examples/products/spiderman-cake.forge.js +2 -2
  140. package/examples/shelf/container.forge.js +5 -5
  141. package/examples/shelf/shelf-unit.forge.js +6 -6
  142. package/examples/toolbox/bolted-joint.forge.js +5 -5
  143. package/package.json +3 -1
  144. package/dist/assets/EditorApp-D9bJvtf7.js +0 -11338
  145. package/dist/assets/LandingPage-CdCuEOdC.js +0 -451
  146. package/dist-cli/chunk-PZ5AY32C.js +0 -10
  147. package/dist-cli/chunk-PZ5AY32C.js.map +0 -1
  148. package/dist-skill/docs/CLI/export.md +0 -91
  149. package/dist-skill/docs/CLI/projects.md +0 -107
  150. package/dist-skill/docs/CLI/studio_publishing.md +0 -52
  151. package/dist-skill/docs/CLI/validation.md +0 -66
  152. package/dist-skill/docs-dev/API/core/sdf-advanced.md +0 -92
  153. package/dist-skill/docs-dev/API/core/sdf-primitives.md +0 -58
  154. package/dist-skill/docs-dev/API/core/sdf-workflow.md +0 -42
  155. package/dist-skill/docs-dev/CLI/export.md +0 -91
  156. package/dist-skill/docs-dev/CLI/projects.md +0 -107
  157. package/dist-skill/docs-dev/CLI/studio_publishing.md +0 -52
  158. package/dist-skill/docs-dev/CLI/validation.md +0 -66
@@ -1,27 +1,25 @@
1
1
  # API by Concept
2
2
 
3
- > **Auto-generated** from `@concept` tags in `src/forge/forge-public-api.ts`. Do not edit by hand — run `npm run gen:docs` to regenerate.
4
-
5
3
  Every public API function belongs to one of 16 fundamental concepts. This document groups them by concept rather than by module, making it easy to see the full set of operations for each idea.
6
4
 
7
5
  ## Concepts
8
6
 
9
- - **[C1: Primitive Construction](#c1-primitive-construction)** — Create geometry from parameters — no input geometry required. *(20 functions)*
7
+ - **[C1: Primitive Construction](#c1-primitive-construction)** — Create geometry from parameters — no input geometry required. *(50 functions)*
10
8
  - **[C2: Boolean Combination](#c2-boolean-combination)** — Combine same-dimension geometry using CSG set operations. *(6 functions)*
11
9
  - **[C3: Rigid Transform](#c3-rigid-transform)** — Reposition or reorient geometry without changing its shape. *(3 functions)*
12
- - **[C4: Dimensional Promotion](#c4-dimensional-promotion)** — Convert a 2D profile into a 3D solid (extrude, revolve, loft, sweep). *(10 functions)*
10
+ - **[C4: Dimensional Promotion](#c4-dimensional-promotion)** — Convert a 2D profile into a 3D solid (extrude, revolve, loft, sweep). *(12 functions)*
13
11
  - **[C5: Topology Query](#c5-topology-query)** — Select or inspect named faces and edges on a shape. *(3 functions)*
14
12
  - **[C6: Edge Feature](#c6-edge-feature)** — Modify edges of a solid — fillets, chamfers, draft, offset. *(7 functions)*
15
13
  - **[C7: Pattern Replication](#c7-pattern-replication)** — Duplicate geometry in regular arrangements (linear, circular, mirror). *(6 functions)*
16
- - **[C8: Constraint Solving](#c8-constraint-solving)** — Define geometry by relationships and let a solver find positions. *(7 functions)*
17
- - **[C9: Spatial Placement](#c9-spatial-placement)** — Position geometry relative to other geometry using semantic anchors. *(0 functions)*
14
+ - **[C8: Constraint Solving](#c8-constraint-solving)** — Define geometry by relationships and let a solver find positions. *(17 functions)*
15
+ - **[C9: Spatial Placement](#c9-spatial-placement)** — Position geometry relative to other geometry using semantic anchors. *(6 functions)*
18
16
  - **[C10: Assembly & Kinematics](#c10-assembly-kinematics)** — Compose parts with joints for kinematic simulation. *(4 functions)*
19
- - **[C11: Parameterization & UI](#c11-parameterization-ui)** — Declare user-facing controls that drive model geometry. *(6 functions)*
17
+ - **[C11: Parameterization & UI](#c11-parameterization-ui)** — Declare user-facing controls that drive model geometry. *(7 functions)*
20
18
  - **[C12: Dimensional Demotion](#c12-dimensional-demotion)** — Extract 2D geometry from a 3D solid (section, projection). *(3 functions)*
21
19
  - **[C13: Export & Output](#c13-export-output)** — Convert geometry to external formats (STL, 3MF, SVG, DXF, G-code, PDF). *(5 functions)*
22
- - **[C14: Visual & Debugging](#c14-visual-debugging)** — Control viewport appearance and debugging aids. *(6 functions)*
20
+ - **[C14: Visual & Debugging](#c14-visual-debugging)** — Control viewport appearance and debugging aids. *(26 functions)*
23
21
  - **[C15: Import & Composition](#c15-import-composition)** — Bring external geometry or other ForgeCAD modules into the current script. *(1 functions)*
24
- - **[C16: Part Library](#c16-part-library)** — Pre-built parametric parts accessible via `lib.*`. *(0 functions)*
22
+ - **[C16: Part Library](#c16-part-library)** — Pre-built parametric parts accessible via `lib.*`. *(4 functions)*
25
23
 
26
24
  ---
27
25
 
@@ -29,41 +27,280 @@ Every public API function belongs to one of 16 fundamental concepts. This docume
29
27
 
30
28
  Create geometry from parameters — no input geometry required.
31
29
 
32
- #### `circle2d()` — Create a 2D circle centered at the origin.
30
+ #### `sdf.sphere()` — Create an SDF sphere centered at the origin.
33
31
 
34
- **Details**
32
+ ```ts
33
+ sdf.sphere(radius: number): SdfShape
34
+ ```
35
35
 
36
- Omit `segments` for a smooth (auto-tessellated) circle. Pass an integer to get a regular polygon approximation e.g. `6` for a hexagon, `8` for an octagon.
36
+ #### `sdf.box()` Create an SDF box centered at the origin with given full dimensions (not half-extents).
37
+
38
+ ```ts
39
+ sdf.box(x: number, y: number, z: number): SdfShape
40
+ ```
41
+
42
+ #### `sdf.cylinder()` — Create an SDF cylinder centered at the origin, axis along Z.
43
+
44
+ ```ts
45
+ sdf.cylinder(height: number, radius: number): SdfShape
46
+ ```
47
+
48
+ #### `sdf.torus()` — Create an SDF torus centered at the origin, lying in the XY plane.
49
+
50
+ ```ts
51
+ sdf.torus(majorRadius: number, minorRadius: number): SdfShape
52
+ ```
53
+
54
+ #### `sdf.capsule()` — Create an SDF capsule centered at the origin, axis along Z.
55
+
56
+ ```ts
57
+ sdf.capsule(height: number, radius: number): SdfShape
58
+ ```
59
+
60
+ #### `sdf.cone()` — Create an SDF cone with base at z=0 and tip at z=height.
61
+
62
+ ```ts
63
+ sdf.cone(height: number, radius: number): SdfShape
64
+ ```
65
+
66
+ #### `sdf.smoothUnion()` — Smooth union — blends shapes together with a smooth transition radius.
67
+
68
+ ```ts
69
+ sdf.smoothUnion(a: SdfShape, b: SdfShape, options: { radius: number; }): SdfShape
70
+ ```
71
+
72
+ #### `sdf.smoothDifference()` — Smooth difference — smoothly subtracts b from a.
73
+
74
+ ```ts
75
+ sdf.smoothDifference(a: SdfShape, b: SdfShape, options: { radius: number; }): SdfShape
76
+ ```
77
+
78
+ #### `sdf.smoothIntersection()` — Smooth intersection — smoothly intersects a and b.
79
+
80
+ ```ts
81
+ sdf.smoothIntersection(a: SdfShape, b: SdfShape, options: { radius: number; }): SdfShape
82
+ ```
83
+
84
+ #### `sdf.morph()` — Morph between two SDF shapes. t=0 → a, t=1 → b.
85
+
86
+ ```ts
87
+ sdf.morph(a: SdfShape, b: SdfShape, t: number): SdfShape
88
+ ```
89
+
90
+ #### `sdf.blend()` — Spatially blend between two SDF patterns. The blend function receives (x, y, z) and returns 0..1: 0 = fully pattern `a`, 1 = fully pattern `b`.
91
+
92
+ ```ts
93
+ sdf.blend(a: SdfShape, b: SdfShape, fn: (x: number, y: number, z: number) => number, options?: BlendOptions): SdfShape
94
+ ```
95
+
96
+ **`BlendOptions`**
97
+ - `constants?: Record<string, number>` — Optional named constants accessible in the blend function.
98
+
99
+ #### `sdf.gyroid()` — Gyroid TPMS lattice — the most common lattice for additive manufacturing.
100
+
101
+ ```ts
102
+ sdf.gyroid(options: TpmsOptions): SdfShape
103
+ ```
104
+
105
+ `TpmsOptions`: `{ cellSize: number, thickness: number }`
106
+
107
+ #### `sdf.schwarzP()` — Schwarz-P TPMS lattice — isotropic pore structure.
108
+
109
+ ```ts
110
+ sdf.schwarzP(options: TpmsOptions): SdfShape
111
+ ```
112
+
113
+ #### `sdf.diamond()` — Diamond TPMS lattice — stiffest TPMS structure.
114
+
115
+ ```ts
116
+ sdf.diamond(options: TpmsOptions): SdfShape
117
+ ```
118
+
119
+ #### `sdf.lidinoid()` — Lidinoid TPMS lattice — visually distinct from gyroid, popular in research and art.
120
+
121
+ ```ts
122
+ sdf.lidinoid(options: TpmsOptions): SdfShape
123
+ ```
124
+
125
+ #### `sdf.noise()` — 3D Simplex noise field — produces organic, natural-looking displacements.
126
+
127
+ ```ts
128
+ sdf.noise(options?: NoiseOptions): SdfShape
129
+ ```
130
+
131
+ **`NoiseOptions`**
132
+
133
+ | Option | Type | Description |
134
+ |--------|------|-------------|
135
+ | `scale?` | `number` | Spatial frequency — smaller = larger features. Default: 0.1 |
136
+ | `amplitude?` | `number` | Peak displacement amplitude. Default: 1 |
137
+ | `octaves?` | `number` | fBm octaves (1 = plain simplex, higher = more detail). Default: 1 |
138
+ | `seed?` | `number` | Seed for deterministic variation. Default: 0 |
139
+
140
+ #### `sdf.voronoi()` — 3D Voronoi pattern — organic cellular structures like bone, coral, or soap bubbles.
141
+
142
+ ```ts
143
+ sdf.voronoi(options?: VoronoiOptions): SdfShape
144
+ ```
145
+
146
+ **`VoronoiOptions`**
147
+
148
+ | Option | Type | Description |
149
+ |--------|------|-------------|
150
+ | `cellSize?` | `number` | Size of each Voronoi cell in world units. Default: 10 |
151
+ | `wallThickness?` | `number` | Wall thickness between cells. Default: 1 |
152
+ | `seed?` | `number` | Seed for deterministic variation. Default: 0 |
153
+ | `suppressionThreshold?` | `number` | Projection weight for membrane suppression (0..1). Controls how much of the surface-normal distance component is removed from Voronoi cell distances. 0 = no projection (classic 3D voronoi with membranes). 1 = full tangent-plane projection (pure 2D pattern on surface). Default: 0.85. Only active when voronoi is intersected with another shape. |
154
+
155
+ #### `sdf.honeycomb()` — Honeycomb (hexagonal) lattice pattern. Intersect with your shape to apply.
156
+
157
+ ```ts
158
+ sdf.honeycomb(options?: HoneycombOptions): SdfShape
159
+ ```
160
+
161
+ **`HoneycombOptions`**
162
+ - `cellSize?: number` — Size of each hex cell. Default: 8
163
+ - `wallThickness?: number` — Wall thickness. Default: 1
164
+
165
+ #### `sdf.waves()` — Sinusoidal wave ridges — parallel ridges along an axis.
166
+
167
+ ```ts
168
+ sdf.waves(options?: WavesOptions): SdfShape
169
+ ```
170
+
171
+ **`WavesOptions`**
172
+ - `wavelength?: number` — Distance between wave peaks. Default: 10
173
+ - `amplitude?: number` — Height of waves. Default: 1
174
+ - `axis?: "x" | "y" | "z"` — Axis along which waves propagate: 'x', 'y', or 'z'. Default: 'x'
175
+
176
+ #### `sdf.knurl()` — Knurl pattern — crossed helical grooves for grips and handles.
177
+
178
+ ```ts
179
+ sdf.knurl(options?: KnurlOptions): SdfShape
180
+ ```
181
+
182
+ **`KnurlOptions`**
183
+ - `pitch?: number` — Distance between knurl ridges. Default: 3
184
+ - `depth?: number` — Depth of knurl grooves. Default: 0.5
185
+ - `angle?: number` — Helix angle in degrees. Default: 30
186
+
187
+ #### `sdf.perforated()` — Perforated plate pattern — regular array of cylindrical holes.
188
+
189
+ ```ts
190
+ sdf.perforated(options?: PerforatedOptions): SdfShape
191
+ ```
192
+
193
+ **`PerforatedOptions`**
194
+ - `radius?: number` — Hole radius. Default: 3
195
+ - `spacing?: number` — Center-to-center spacing. Default: 8
196
+
197
+ #### `sdf.scales()` — Fish/dragon scale pattern — overlapping circular scales in hex-packed rows.
198
+
199
+ ```ts
200
+ sdf.scales(options?: ScalesOptions): SdfShape
201
+ ```
202
+
203
+ **`ScalesOptions`**
204
+ - `size?: number` — Scale diameter. Default: 5
205
+ - `depth?: number` — How much scales protrude. Default: 0.8
206
+
207
+ #### `sdf.brick()` — Brick/stone wall pattern — running bond with mortar grooves.
208
+
209
+ ```ts
210
+ sdf.brick(options?: BrickOptions): SdfShape
211
+ ```
212
+
213
+ **`BrickOptions`**
214
+
215
+ | Option | Type | Description |
216
+ |--------|------|-------------|
217
+ | `width?` | `number` | Brick width. Default: 10 |
218
+ | `height?` | `number` | Brick height. Default: 5 |
219
+ | `depth?` | `number` | Mortar groove depth. Default: 0.5 |
220
+ | `mortar?` | `number` | Mortar gap width. Default: 1 |
221
+
222
+ #### `sdf.weave()` — Grid lattice pattern — two families of infinite slabs crossing at 90°.
223
+
224
+ ```ts
225
+ sdf.weave(options?: WeaveOptions): SdfShape
226
+ ```
227
+
228
+ **`WeaveOptions`**
229
+ - `spacing?: number` — Thread center-to-center spacing (for intersection patterns). Default: 5
230
+ - `threadRadius?: number` — Thread half-width. Default: 1
231
+
232
+ #### `sdf.basketWeave()` — Basket weave surface pattern — threads with over-under crossings in UV space. Returns a SurfacePattern for use with `.surfaceDisplace()`.
233
+
234
+ ```ts
235
+ sdf.basketWeave(options?: BasketWeaveOptions): SurfacePattern
236
+ ```
237
+
238
+ **`BasketWeaveOptions`**
239
+ - `spacing?: number` — Spacing between threads in mm (both directions). Default: 3
240
+ - `threadWidth?: number` — Thread width in mm. Default: 1.5
241
+ - `depth?: number` — Thread protrusion depth in mm. Default: 0.8
242
+
243
+ #### `sdf.twist()` — Twist an SDF shape around the Z axis.
244
+
245
+ ```ts
246
+ sdf.twist(shape: SdfShape, degreesPerUnit: number): SdfShape
247
+ ```
248
+
249
+ #### `sdf.bend()` — Bend an SDF shape around the Z axis.
250
+
251
+ ```ts
252
+ sdf.bend(shape: SdfShape, radius: number): SdfShape
253
+ ```
254
+
255
+ #### `sdf.repeat()` — Repeat an SDF shape in space.
256
+
257
+ ```ts
258
+ sdf.repeat(shape: SdfShape, spacing: Vec3, count?: Vec3): SdfShape
259
+ ```
260
+
261
+ #### `sdf.SurfacePattern()` — A 2D surface pattern — a heightmap function for use with `.surfaceDisplace()`.
262
+
263
+ ```ts
264
+ sdf.SurfacePattern: typeof SurfacePattern
265
+ ```
37
266
 
38
- **Example**
267
+ #### `sdf.fromFunction()` — Create an SDF shape from an arbitrary distance function. You must provide bounds since the function is opaque.
268
+
269
+ ```ts
270
+ sdf.fromFunction(fn: (x: number, y: number, z: number) => number, bounds: { min: Vec3; max: Vec3; }, constants?: Record<string, number>): SdfShape
271
+ ```
272
+
273
+ #### `circle2d()` — Create a 2D circle centered at the origin.
274
+
275
+ Omit `segments` for a smooth (auto-tessellated) circle. Pass an integer to get a regular polygon approximation — e.g. `6` for a hexagon, `8` for an octagon.
39
276
 
40
277
  ```ts
41
278
  circle2d(25).extrude(10); // smooth cylinder
42
279
  circle2d(25, 6).extrude(10); // hexagonal prism
43
280
  ```
44
281
 
45
- `circle2d(radius: number, segments?: number): Sketch`
282
+ ```ts
283
+ circle2d(radius: number, segments?: number): Sketch
284
+ ```
46
285
 
47
286
  #### `ellipse()` — Create a 2D ellipse centered at the origin.
48
287
 
49
- **Example**
50
-
51
288
  ```ts
52
289
  ellipse(30, 15).extrude(5);
53
290
  ellipse(30, 15, 32).extrude(5); // lower-resolution approximation
54
291
  ```
55
292
 
56
- `ellipse(rx: number, ry: number, segments?: number): Sketch`
293
+ ```ts
294
+ ellipse(rx: number, ry: number, segments?: number): Sketch
295
+ ```
57
296
 
58
297
  #### `loadFont()` — Pre-load and cache a font for use with `text2d()`.
59
298
 
60
- **Details**
61
-
62
299
  Fonts are cached by their source string (or `cacheKey` for `ArrayBuffer` sources), so repeated calls with the same path are free. Pre-loading is useful when you call `text2d()` many times with the same font — it avoids repeated disk reads.
63
300
 
64
- Built-in font names that work everywhere (browser + CLI): - `'sans-serif'` or `'inter'` — bundled Inter Regular
301
+ Built-in font names that work everywhere (browser + CLI):
65
302
 
66
- **Example**
303
+ - `'sans-serif'` or `'inter'` — bundled Inter Regular
67
304
 
68
305
  ```ts
69
306
  const font = loadFont('/path/to/Arial Bold.ttf');
@@ -71,32 +308,28 @@ text2d('Title', { size: 12, font }).extrude(1.5);
71
308
  text2d('Subtitle', { size: 8, font }).extrude(1);
72
309
  ```
73
310
 
74
- `loadFont(source: string | ArrayBuffer, cacheKey?: string): opentype$1.Font`
311
+ ```ts
312
+ loadFont(source: string | ArrayBuffer, cacheKey?: string): opentype.Font
313
+ ```
75
314
 
76
315
  #### `ngon()` — Create a regular polygon inscribed in a circle of the given radius.
77
316
 
78
- **Details**
79
-
80
317
  `radius` is the center-to-vertex (circumradius) distance. Use `sides` of `3` for a triangle, `6` for a hexagon, etc. The first vertex is at the top (−90° from +X).
81
318
 
82
- **Example**
83
-
84
319
  ```ts
85
320
  ngon(6, 20).extrude(10); // hexagonal prism, circumradius 20
86
321
  ```
87
322
 
88
- `ngon(sides: number, radius: number): Sketch`
323
+ ```ts
324
+ ngon(sides: number, radius: number): Sketch
325
+ ```
89
326
 
90
327
  #### `path()` — Create a new `PathBuilder` for tracing a 2D outline point by point.
91
328
 
92
- **Details**
93
-
94
329
  `PathBuilder` is a fluent API for constructing 2D profiles using a mix of line segments, arcs, bezier curves, and splines. Always start with `.moveTo(x, y)` to set the starting point. Call `.close()` to get a filled `Sketch`, or `.stroke(width)` to thicken an open polyline into a solid profile.
95
330
 
96
331
  Edge labels can be assigned with `.label('name')` after any segment — they propagate through extrusion, revolve, loft, and sweep into named faces on the resulting `Shape`.
97
332
 
98
- **Example**
99
-
100
333
  ```ts
101
334
  // Closed triangle
102
335
  const triangle = path().moveTo(0, 0).lineH(50).lineV(30).close();
@@ -113,21 +346,21 @@ const slot = path()
113
346
  .close();
114
347
  ```
115
348
 
116
- `path(): PathBuilder`
349
+ ```ts
350
+ path(): PathBuilder
351
+ ```
117
352
 
118
353
  #### `polygon()` — Create a 2D polygon from an array of `[x, y]` points or `Point2D` objects.
119
354
 
120
- **Details**
121
-
122
355
  Winding order is normalized automatically — clockwise (CW) input is silently reversed to CCW before being passed to the geometry kernel.
123
356
 
124
- **Example**
125
-
126
357
  ```ts
127
358
  polygon([[0, 0], [50, 0], [25, 40]]).extrude(5); // triangle
128
359
  ```
129
360
 
130
- `polygon(points: ([ number, number ] | Point2D)[]): Sketch`
361
+ ```ts
362
+ polygon(points: ([ number, number ] | Point2D)[]): Sketch
363
+ ```
131
364
 
132
365
  #### `polygonVertices()` — Compute the vertex positions of a regular polygon.
133
366
 
@@ -145,7 +378,9 @@ const v3 = [center.x + r, center.y];
145
378
  const [v1, v2, v3] = polygonVertices(3, r);
146
379
  ```
147
380
 
148
- `polygonVertices(sides: number, radius: number, options?: PolygonVerticesOptions): LayoutPoint[]`
381
+ ```ts
382
+ polygonVertices(sides: number, radius: number, options?: PolygonVerticesOptions): LayoutPoint[]
383
+ ```
149
384
 
150
385
  **`PolygonVerticesOptions`**
151
386
  - `startDeg?: number` — Angle of the first vertex in degrees (default: 90 = top).
@@ -156,57 +391,55 @@ const [v1, v2, v3] = polygonVertices(3, r);
156
391
 
157
392
  #### `rect()` — Create a 2D rectangle centered at the origin.
158
393
 
159
- **Example**
160
-
161
394
  ```ts
162
395
  rect(40, 20).extrude(5);
163
396
  ```
164
397
 
165
- `rect(width: number, height: number): Sketch`
398
+ ```ts
399
+ rect(width: number, height: number): Sketch
400
+ ```
166
401
 
167
402
  #### `arcSlot()` — Create an arc-shaped slot (banana / annular sector) centered at the origin.
168
403
 
169
- **Details**
170
-
171
404
  The slot is symmetric about the +X axis. The two ends are closed with semicircular caps. `pitchRadius` is the distance from the origin to the centerline of the slot, and `thickness` is the radial width of the slot.
172
405
 
173
- **Example**
174
-
175
406
  ```ts
176
407
  arcSlot(135, 74, 40).extrude(5); // pitch R135, 74° sweep, 40mm wide
177
408
  ```
178
409
 
179
- `arcSlot(pitchRadius: number, sweepDeg: number, thickness: number): Sketch`
410
+ ```ts
411
+ arcSlot(pitchRadius: number, sweepDeg: number, thickness: number): Sketch
412
+ ```
180
413
 
181
414
  #### `roundedRect()` — Create a 2D rectangle with rounded corners, centered at the origin.
182
415
 
183
- **Details**
184
-
185
416
  The corner radius is automatically clamped to `min(width/2, height/2)` so it can never exceed the shape dimensions.
186
417
 
187
- **Example**
188
-
189
418
  ```ts
190
419
  roundedRect(60, 30, 5).extrude(3);
191
420
  ```
192
421
 
193
- `roundedRect(width: number, height: number, radius: number): Sketch`
422
+ ```ts
423
+ roundedRect(width: number, height: number, radius: number): Sketch
424
+ ```
194
425
 
195
426
  #### `slot()` — Create a slot (oblong / stadium shape) — a rectangle with semicircular ends, centered at the origin.
196
427
 
197
- **Example**
198
-
199
428
  ```ts
200
429
  slot(40, 10).extrude(3); // 40mm long, 10mm wide slot
201
430
  ```
202
431
 
203
- `slot(length: number, width: number): Sketch`
432
+ ```ts
433
+ slot(length: number, width: number): Sketch
434
+ ```
204
435
 
205
436
  #### `spline2d()` — Build a smooth Catmull-Rom spline sketch from 2D control points.
206
437
 
207
438
  A closed spline (default) returns a filled profile. An open spline requires a strokeWidth option to produce a solid sketch. Use tension (0..1, default 0.5) to control curve tightness.
208
439
 
209
- `spline2d(points: Vec2[], options?: Spline2DOptions): Sketch`
440
+ ```ts
441
+ spline2d(points: Vec2[], options?: Spline2DOptions): Sketch
442
+ ```
210
443
 
211
444
  **`Spline2DOptions`**
212
445
 
@@ -220,30 +453,28 @@ A closed spline (default) returns a filled profile. An open spline requires a st
220
453
 
221
454
  #### `star()` — Create a star shape with alternating outer and inner radii.
222
455
 
223
- **Example**
224
-
225
456
  ```ts
226
457
  star(5, 30, 12).extrude(4); // five-pointed star
227
458
  ```
228
459
 
229
- `star(points: number, outerR: number, innerR: number): Sketch`
460
+ ```ts
461
+ star(points: number, outerR: number, innerR: number): Sketch
462
+ ```
230
463
 
231
464
  #### `stroke()` — Create a stroked polyline sketch from an array of 2D points.
232
465
 
233
- `stroke(points: [ number, number ][], width: number, join?: "Round" | "Square"): Sketch`
466
+ ```ts
467
+ stroke(points: [ number, number ][], width: number, join?: "Round" | "Square"): Sketch
468
+ ```
234
469
 
235
470
  #### `text2d()` — Build a filled 2D Sketch from a text string.
236
471
 
237
- **Details**
238
-
239
472
  The Sketch origin is at the left end of the text baseline by default. Use `align` and `baseline` options to adjust placement. Text is rendered using the bundled Inter font by default, or any TTF/OTF/WOFF font you provide.
240
473
 
241
474
  Alignment reference table:
242
475
 
243
476
  | `align` | `baseline` | Origin | |------------|--------------|-------------------------------------| | `'left'` | `'baseline'` | Bottom-left of first char (default) | | `'center'` | `'center'` | Dead center of text block | | `'right'` | `'top'` | Top-right corner |
244
477
 
245
- **Example**
246
-
247
478
  ```ts
248
479
  // Extruded nameplate
249
480
  text2d('FORGE CAD', { size: 8 }).extrude(1.2);
@@ -263,7 +494,9 @@ const font = loadFont('/path/to/Arial Bold.ttf');
263
494
  text2d('Title', { size: 12, font }).extrude(1.5);
264
495
  ```
265
496
 
266
- `text2d(content: string, options?: TextOptions): Sketch`
497
+ ```ts
498
+ text2d(content: string, options?: TextOptions): Sketch
499
+ ```
267
500
 
268
501
  **`TextOptions`**
269
502
 
@@ -273,43 +506,49 @@ text2d('Title', { size: 12, font }).extrude(1.5);
273
506
  | `letterSpacing?` | `number` | Extra space between characters in model units. Negative values tighten the tracking. |
274
507
  | `align?` | `"left" | "center" | "right"` | Horizontal alignment relative to x = 0. - `'left'` — left edge at x = 0 (default) - `'center'` — centred on x = 0 - `'right'` — right edge at x = 0 |
275
508
  | `baseline?` | `"baseline" | "center" | "top"` | Vertical alignment relative to y = 0. - `'baseline'` — y = 0 is the text baseline (bottom of capital letters) - `'center'` — y = 0 is the vertical midpoint of the cap height - `'top'` — y = 0 is the top of capital letters |
276
- | `font?` | `string | opentype$1.Font` | Font to use for text rendering. - `'sans-serif'` or `'inter'` — bundled Inter font (works everywhere, including browser) - **file path** — path to a TTF, OTF, or WOFF font file (CLI/Node only) - **Font object** — a previously loaded opentype.js Font (from `loadFont()`) - **omitted** — uses the bundled Inter font (same as `'sans-serif'`) text2d('Hello World', { size: 10 }) // default Inter text2d('Custom Font', { size: 10, font: '/path/to/font.ttf' }) |
509
+ | `font?` | `string | opentype.Font` | Font to use for text rendering. - `'sans-serif'` or `'inter'` — bundled Inter font (works everywhere, including browser) - **file path** — path to a TTF, OTF, or WOFF font file (CLI/Node only) - **Font object** — a previously loaded opentype.js Font (from `loadFont()`) - **omitted** — uses the bundled Inter font (same as `'sans-serif'`) text2d('Hello World', { size: 10 }) // default Inter text2d('Custom Font', { size: 10, font: '/path/to/font.ttf' }) |
277
510
  | `flattenTolerance?` | `number` | Bezier flattening tolerance in model units. Smaller = more polygon segments = smoother curves. |
278
511
 
279
512
  #### `textWidth()` — Measure the rendered advance width of a string without creating any geometry.
280
513
 
281
- **Details**
282
-
283
514
  Uses the same font metrics as `text2d()`. Useful for computing layout dimensions before building the actual sketch — e.g. sizing a plate to fit a label.
284
515
 
285
- **Example**
286
-
287
516
  ```ts
288
517
  const w = textWidth('SERIAL: 001', { size: 6 });
289
518
  const plate = box(w + 10, 12, 2);
290
519
  ```
291
520
 
292
- `textWidth(content: string, options?: Pick<TextOptions, "size" | "letterSpacing" | "font">): number`
521
+ ```ts
522
+ textWidth(content: string, options?: Pick<TextOptions, "size" | "letterSpacing" | "font">): number
523
+ ```
293
524
 
294
525
  #### `box()` — Create a rectangular box. Centered on XY, base at Z=0.
295
526
 
296
527
  For named faces, build from a labeled sketch: `rect(x, y).labelEdges('s', 'e', 'n', 'w').extrude(z, { labels: { start: 'bottom', end: 'top' } })`.
297
528
 
298
- `box$1(x: number, y: number, z: number): Shape`
529
+ ```ts
530
+ box(x: number, y: number, z: number): Shape
531
+ ```
299
532
 
300
533
  #### `cylinder()` — Create a cylinder or cone with named faces and edges. Centered on XY, base at Z=0.
301
534
 
302
535
  When radiusTop differs from radius, creates a tapered cone. Use the segments parameter to create regular prisms (e.g. 6 for a hexagonal prism). Returns a Shape with faces: top, bottom, side; and edges: top-rim, bottom-rim.
303
536
 
304
- `cylinder$1(height: number, radius: number, radiusTop?: number, segments?: number): Shape`
537
+ ```ts
538
+ cylinder(height: number, radius: number, radiusTop?: number, segments?: number): Shape
539
+ ```
305
540
 
306
541
  #### `sphere()` — Create a sphere centered at the origin. Use segments for lower-poly approximations.
307
542
 
308
- `sphere$1(radius: number, segments?: number): Shape`
543
+ ```ts
544
+ sphere(radius: number, segments?: number): Shape
545
+ ```
309
546
 
310
547
  #### `torus()` — Create a torus (donut shape) lying in the XY plane. Centered on all axes (origin is the ring center).
311
548
 
312
- `torus$1(majorRadius: number, minorRadius: number, segments?: number): Shape`
549
+ ```ts
550
+ torus(majorRadius: number, minorRadius: number, segments?: number): Shape
551
+ ```
313
552
 
314
553
  ---
315
554
 
@@ -319,63 +558,63 @@ Combine same-dimension geometry using CSG set operations.
319
558
 
320
559
  #### `difference2d()` — Subtract one or more 2D sketches from a base sketch.
321
560
 
322
- **Details**
323
-
324
561
  The first sketch is the base; all subsequent sketches are subtracted from it. Accepts individual sketches or arrays: `difference2d(base, c1, c2)` or `difference2d([base, c1, c2])`. Uses Manifold's batch operation — faster than chaining `.subtract()` one by one.
325
562
 
326
- **Example**
327
-
328
563
  ```ts
329
564
  const donut = difference2d(circle2d(50), circle2d(30));
330
565
  ```
331
566
 
332
- `difference2d(...inputs: SketchOperandInput[]): Sketch`
567
+ ```ts
568
+ difference2d(...inputs: SketchOperandInput[]): Sketch
569
+ ```
333
570
 
334
571
  #### `intersection2d()` — Keep only the area where all input sketches overlap (intersection boolean).
335
572
 
336
- **Details**
337
-
338
573
  Accepts individual sketches or arrays: `intersection2d(a, b)` or `intersection2d([a, b, c])`. Uses Manifold's batch operation — faster than chaining `.intersect()` one by one.
339
574
 
340
- **Example**
341
-
342
575
  ```ts
343
576
  const lens = intersection2d(circle2d(30).translate(-10, 0), circle2d(30).translate(10, 0));
344
577
  ```
345
578
 
346
- `intersection2d(...inputs: SketchOperandInput[]): Sketch`
579
+ ```ts
580
+ intersection2d(...inputs: SketchOperandInput[]): Sketch
581
+ ```
347
582
 
348
583
  #### `union2d()` — Combine 2D sketches into a single profile using an additive boolean union.
349
584
 
350
- **Details**
351
-
352
585
  Accepts individual sketches or arrays: `union2d(a, b, c)` or `union2d([a, b, c])`. Uses Manifold's batch operation — faster than chaining `.add()` one by one when combining many sketches.
353
586
 
354
- **Example**
355
-
356
587
  ```ts
357
588
  const cross = union2d(rect(60, 10), rect(10, 60));
358
589
  ```
359
590
 
360
- `union2d(...inputs: SketchOperandInput[]): Sketch`
591
+ ```ts
592
+ union2d(...inputs: SketchOperandInput[]): Sketch
593
+ ```
361
594
 
362
595
  #### `union()` — Combine shapes into a single solid (additive boolean).
363
596
 
364
597
  Accepts individual shapes, or an array of shapes. The first operand's color is preserved in the result.
365
598
 
366
- `union(...inputs: ShapeOperandInput[]): Shape`
599
+ ```ts
600
+ union(...inputs: ShapeOperandInput[]): Shape
601
+ ```
367
602
 
368
603
  #### `difference()` — Subtract shapes from a base shape (subtractive boolean).
369
604
 
370
605
  The first shape is the base; all subsequent shapes are subtracted from it. Accepts individual shapes, or an array of shapes.
371
606
 
372
- `difference(...inputs: ShapeOperandInput[]): Shape`
607
+ ```ts
608
+ difference(...inputs: ShapeOperandInput[]): Shape
609
+ ```
373
610
 
374
611
  #### `intersection()` — Keep only the overlapping volume of the input shapes (intersection boolean).
375
612
 
376
613
  Requires at least two shapes. Accepts individual shapes, or an array.
377
614
 
378
- `intersection(...inputs: ShapeOperandInput[]): Shape`
615
+ ```ts
616
+ intersection(...inputs: ShapeOperandInput[]): Shape
617
+ ```
379
618
 
380
619
  ---
381
620
 
@@ -387,17 +626,23 @@ Reposition or reorient geometry without changing its shape.
387
626
 
388
627
  Use for clarity when the unit of an angle value would otherwise be ambiguous — e.g. `param("Angle", degrees(45))`.
389
628
 
390
- `degrees(deg: number): number`
629
+ ```ts
630
+ degrees(deg: number): number
631
+ ```
391
632
 
392
633
  #### `radians()` — Convert radians to degrees.
393
634
 
394
635
  ForgeCAD's public API uses degrees throughout. Use this when you have a radian value (e.g. from `Math.atan2`) that you want to express in degrees.
395
636
 
396
- `radians(rad: number): number`
637
+ ```ts
638
+ radians(rad: number): number
639
+ ```
397
640
 
398
641
  #### `composeChain()` — Compose transforms in chain order. Equivalent to Transform.identity().mul(a).mul(b).mul(c)...
399
642
 
400
- `composeChain(...steps: TransformInput[]): Transform`
643
+ ```ts
644
+ composeChain(...steps: TransformInput[]): Transform
645
+ ```
401
646
 
402
647
  ---
403
648
 
@@ -405,11 +650,77 @@ ForgeCAD's public API uses degrees throughout. Use this when you have a radian v
405
650
 
406
651
  Convert a 2D profile into a 3D solid (extrude, revolve, loft, sweep).
407
652
 
653
+ #### `nurbs3d()` — Create a NURBS curve from control points.
654
+
655
+ With default options, creates a cubic non-rational B-spline with uniform clamped knots. Set `weights` for rational curves (exact circles, conics). Set `degree` for linear (1), quadratic (2), cubic (3), or higher-order curves.
656
+
657
+ ```js
658
+ // Simple cubic B-spline through control points
659
+ const curve = nurbs3d([[0,0,0], [10,5,0], [20,-5,10], [30,0,5]]);
660
+ const tube = sweep(circle(2), curve);
661
+ ```
662
+
663
+ ```js
664
+ // Rational quadratic — exact circular arc
665
+ const arc = nurbs3d(
666
+ [[10,0,0], [10,10,0], [0,10,0]],
667
+ { degree: 2, weights: [1, Math.SQRT1_2, 1] }
668
+ );
669
+ ```
670
+
671
+ ```ts
672
+ nurbs3d(points: Vec3[], options?: NurbsCurve3DOptions): NurbsCurve3D
673
+ ```
674
+
675
+ **`NurbsCurve3DOptions`**
676
+
677
+ | Option | Type | Description |
678
+ |--------|------|-------------|
679
+ | `degree?` | `number` | Polynomial degree (default 3 = cubic). Must be ≥ 1. |
680
+ | `weights?` | `number[]` | Rational weights, one per control point (default: all 1.0 = non-rational). |
681
+ | `knots?` | `number[]` | Knot vector (default: uniform clamped). Must have length = controlPoints.length + degree + 1. |
682
+ | `closed?` | `boolean` | Whether the curve is closed/periodic (default false). |
683
+
684
+ #### `nurbsSurface()` — Create a NURBS surface from a grid of control points.
685
+
686
+ The control grid is indexed as `controlGrid[u][v]` — each row is a curve in the V direction, and columns trace curves in the U direction.
687
+
688
+ With default options, creates a bicubic non-rational B-spline surface with uniform clamped knots.
689
+
690
+ ```js
691
+ // Simple 4×4 control grid — a gently curved surface
692
+ const grid = [
693
+ [[0,0,0], [10,0,2], [20,0,2], [30,0,0]],
694
+ [[0,10,1], [10,10,5], [20,10,5], [30,10,1]],
695
+ [[0,20,1], [10,20,5], [20,20,5], [30,20,1]],
696
+ [[0,30,0], [10,30,2], [20,30,2], [30,30,0]],
697
+ ];
698
+ const surface = nurbsSurface(grid, { thickness: 2 });
699
+ ```
700
+
701
+ ```ts
702
+ nurbsSurface(controlGrid: Vec3[][], options?: NurbsSurfaceOptions): Shape
703
+ ```
704
+
705
+ **`NurbsSurfaceOptions`**
706
+
707
+ | Option | Type | Description |
708
+ |--------|------|-------------|
709
+ | `degreeU?` | `number` | Degree in U direction (default 3). |
710
+ | `degreeV?` | `number` | Degree in V direction (default 3). |
711
+ | `weights?` | `number[][]` | Weights grid — same dimensions as controlGrid (default: all 1.0). |
712
+ | `knotsU?` | `number[]` | Knot vector in U direction (default: uniform clamped). |
713
+ | `knotsV?` | `number[]` | Knot vector in V direction (default: uniform clamped). |
714
+ | `thickness?` | `number` | Sheet thickness — if > 0, thickens the surface into a solid (default 0 = surface only). |
715
+ | `resolution?` | `number` | Tessellation resolution — points per direction (default 32). |
716
+
408
717
  #### `connectEdges()` — Create a transition surface or solid bridge between two edge segments.
409
718
 
410
719
  Tangents can be inferred from neighboring geometry or supplied explicitly through `options`. This is useful for loft-like blends where you want a direct connection between two edge spans.
411
720
 
412
- `connectEdges(edgeA: EdgeSegment, edgeB: EdgeSegment, options?: ConnectEdgesOptions): Shape`
721
+ ```ts
722
+ connectEdges(edgeA: EdgeSegment, edgeB: EdgeSegment, options?: ConnectEdgesOptions): Shape
723
+ ```
413
724
 
414
725
  **`EdgeSegment`**
415
726
 
@@ -435,7 +746,7 @@ Tangents can be inferred from neighboring geometry or supplied explicitly throug
435
746
  |--------|------|-------------|
436
747
  | `profile?` | `Sketch` | Cross-section profile to sweep along the transition curve. If omitted, a circular profile with `radius` is used. |
437
748
  | `radius?` | `number` | Radius of circular cross-section (used when `profile` is omitted). Default: 5% of chord length. |
438
- | `up?` | `Vec3$6` | Preferred up vector for the sweep frame. Default: auto-detected. |
749
+ | `up?` | `Vec3` | Preferred up vector for the sweep frame. Default: auto-detected. |
439
750
  | `edgeLength?` | `number` | Edge length for level-set meshing. Smaller = finer. |
440
751
  | `boundsPadding?` | `number` | Extra bounds padding for level-set meshing. |
441
752
  | `width`, `height` | | — |
@@ -448,8 +759,8 @@ Tangents can be inferred from neighboring geometry or supplied explicitly throug
448
759
  | `endB?` | `EdgeEnd` | Which end of edge B to connect. Default: 'start'. |
449
760
  | `tangentModeA?` | `TangentMode` | Tangent mode for edge A. Default: 'along'. |
450
761
  | `tangentModeB?` | `TangentMode` | Tangent mode for edge B. Default: 'along'. |
451
- | `tangentA?` | `Vec3$6` | Explicit tangent for edge A. |
452
- | `tangentB?` | `Vec3$6` | Explicit tangent for edge B. |
762
+ | `tangentA?` | `Vec3` | Explicit tangent for edge A. |
763
+ | `tangentB?` | `Vec3` | Explicit tangent for edge B. |
453
764
  | `flipA?` | `boolean` | Flip tangent A. |
454
765
  | `flipB?` | `boolean` | Flip tangent B. |
455
766
 
@@ -457,15 +768,17 @@ Tangents can be inferred from neighboring geometry or supplied explicitly throug
457
768
 
458
769
  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.
459
770
 
460
- `hermiteTransitionG2(a: QuinticHermiteCurveEndpoint, b: QuinticHermiteCurveEndpoint): QuinticHermiteCurve3D`
771
+ ```ts
772
+ hermiteTransitionG2(a: QuinticHermiteCurveEndpoint, b: QuinticHermiteCurveEndpoint): QuinticHermiteCurve3D
773
+ ```
461
774
 
462
775
  **`QuinticHermiteCurveEndpoint`**
463
776
 
464
777
  | Option | Type | Description |
465
778
  |--------|------|-------------|
466
- | `point` | `Vec3$4` | Position |
467
- | `tangent` | `Vec3$4` | Tangent direction (will be normalized internally) |
468
- | `curvature?` | `Vec3$4` | Second derivative / curvature vector. Default [0, 0, 0]. |
779
+ | `point` | `Vec3` | Position |
780
+ | `tangent` | `Vec3` | Tangent direction (will be normalized internally) |
781
+ | `curvature?` | `Vec3` | Second derivative / curvature vector. Default [0, 0, 0]. |
469
782
  | `weight?` | `number` | Weight: scales tangent magnitude relative to chord length. Default 1.0. |
470
783
 
471
784
  #### `loft()` — Loft between multiple sketches along Z stations.
@@ -474,7 +787,9 @@ Profiles can differ in topology and vertex count: interpolation is done on signe
474
787
 
475
788
  Performance note: loft is significantly heavier than primitive/extrude/revolve. If the part is axis-symmetric (bottles, vases, knobs), prefer revolve().
476
789
 
477
- `loft(profiles: Sketch[], heights: number[], options?: LoftOptions): Shape`
790
+ ```ts
791
+ loft(profiles: Sketch[], heights: number[], options?: LoftOptions): Shape
792
+ ```
478
793
 
479
794
  **`LoftOptions`**
480
795
  - `edgeLength?: number` — Marching-grid edge length for level-set meshing. Smaller = finer.
@@ -490,7 +805,9 @@ Internally uses variableSweep infrastructure with SDF interpolation.
490
805
 
491
806
  Performance note: uses level-set meshing, heavier than simple loft().
492
807
 
493
- `loftAlongSpine(profiles: Sketch[], spine: Curve3D | Vec3$3[], tValues: number[], options?: LoftAlongSpineOptions): Shape`
808
+ ```ts
809
+ loftAlongSpine(profiles: Sketch[], spine: Curve3D | Vec3[], tValues: number[], options?: LoftAlongSpineOptions): Shape
810
+ ```
494
811
 
495
812
  **`LoftAlongSpineOptions`**
496
813
 
@@ -499,13 +816,15 @@ Performance note: uses level-set meshing, heavier than simple loft().
499
816
  | `samples?` | `number` | Number of samples when spine is a Curve3D. Default 48. |
500
817
  | `edgeLength?` | `number` | Marching-grid edge length for level-set meshing. Smaller = finer. |
501
818
  | `boundsPadding?` | `number` | Optional extra bounds padding. |
502
- | `up?` | `Vec3$3` | Preferred "up" vector for local profile frame. Auto fallback is used near parallel segments. |
819
+ | `up?` | `Vec3` | Preferred "up" vector for local profile frame. Auto fallback is used near parallel segments. |
503
820
 
504
821
  #### `spline3d()` — Create a reusable 3D spline curve object (Catmull-Rom).
505
822
 
506
823
  The returned Curve3D provides sample(), pointAt(t), tangentAt(t), and length() for downstream use in sweep() or manual path operations.
507
824
 
508
- `spline3d(points: Vec3$3[], options?: Spline3DOptions): Curve3D`
825
+ ```ts
826
+ spline3d(points: Vec3[], options?: Spline3DOptions): Curve3D
827
+ ```
509
828
 
510
829
  **`Spline3DOptions`**
511
830
  - `closed?: boolean` — Closed loop (default false).
@@ -513,7 +832,12 @@ The returned Curve3D provides sample(), pointAt(t), tangentAt(t), and length() f
513
832
 
514
833
  #### `surfacePatch()` — Create a smooth surface patch from 4 boundary curves (Coons patch).
515
834
 
516
- The four curves form the boundary of a quadrilateral patch: - bottom: u=0..1 at v=0 (from corner00 to corner10) - top: u=0..1 at v=1 (from corner01 to corner11) - left: v=0..1 at u=0 (from corner00 to corner01) - right: v=0..1 at u=1 (from corner10 to corner11)
835
+ The four curves form the boundary of a quadrilateral patch:
836
+
837
+ - bottom: u=0..1 at v=0 (from corner00 to corner10)
838
+ - top: u=0..1 at v=1 (from corner01 to corner11)
839
+ - left: v=0..1 at u=0 (from corner00 to corner01)
840
+ - right: v=0..1 at u=1 (from corner10 to corner11)
517
841
 
518
842
  The interior is filled using bilinear Coons patch interpolation: P(u,v) = Lc(u,v) + Ld(u,v) - B(u,v)
519
843
 
@@ -521,19 +845,19 @@ The result is a thin solid created by offsetting the surface mesh along its norm
521
845
 
522
846
  Note: curves should meet at corners. Small gaps are tolerated.
523
847
 
524
- `surfacePatch(curves: { ... }, options?: SurfacePatchOptions): Shape`
848
+ ```ts
849
+ surfacePatch(curves: { ... }, options?: SurfacePatchOptions): Shape
850
+ ```
525
851
 
526
852
  **`SurfacePatchOptions`**
527
853
  - `resolution?: number` — Number of samples along each direction. Default 24.
528
854
  - `thickness?: number` — Thickness of the generated solid. Default 0.5.
529
855
 
530
- #### `sweep()` — Sweep a 2D profile along a 3D path to create a solid.
531
-
532
- Path can be a Curve3D from spline3d() or an array of [x,y,z] points (polyline). The profile is interpreted in the local frame normal plane. Compatible sweeps can export through the OCCT exact route using the canonical path representation.
856
+ #### `sweep()`
533
857
 
534
- Performance note: sweep uses level-set meshing internally. Prefer direct primitives/extrude/revolve when they can express the same shape.
535
-
536
- `sweep(profile: Sketch, path: Curve3D | Vec3$3[], options?: SweepOptions): Shape`
858
+ ```ts
859
+ sweep(profile: Sketch, path: SweepPathInput, options?: SweepOptions): Shape
860
+ ```
537
861
 
538
862
  **`SweepOptions`**
539
863
 
@@ -542,7 +866,7 @@ Performance note: sweep uses level-set meshing internally. Prefer direct primiti
542
866
  | `samples?` | `number` | Number of samples when path is a Curve3D. Default 48. |
543
867
  | `edgeLength?` | `number` | Marching-grid edge length for level-set meshing. Smaller = finer. |
544
868
  | `boundsPadding?` | `number` | Optional extra bounds padding. |
545
- | `up?` | `Vec3$3` | Preferred "up" vector for local profile frame. Auto fallback is used near parallel segments. |
869
+ | `up?` | `Vec3` | Preferred "up" vector for local profile frame. Auto fallback is used near parallel segments. |
546
870
 
547
871
  #### `variableSweep()` — Sweep a variable cross-section along a 3D spine curve.
548
872
 
@@ -552,7 +876,9 @@ Each section specifies a t parameter (0 = start, 1 = end of spine) and a 2D prof
552
876
 
553
877
  Performance note: like sweep(), this uses level-set meshing internally.
554
878
 
555
- `variableSweep(spine: Curve3D | Vec3$3[], sections: VariableSweepSection[], options?: VariableSweepOptions): Shape`
879
+ ```ts
880
+ variableSweep(spine: SweepPathInput, sections: VariableSweepSection[], options?: VariableSweepOptions): Shape
881
+ ```
556
882
 
557
883
  **`VariableSweepSection`**
558
884
  - `t: number` — Parameter along the spine (0 = start, 1 = end).
@@ -565,7 +891,7 @@ Performance note: like sweep(), this uses level-set meshing internally.
565
891
  | `samples?` | `number` | Number of samples when spine is a Curve3D. Default 48. |
566
892
  | `edgeLength?` | `number` | Marching-grid edge length for level-set meshing. Smaller = finer. |
567
893
  | `boundsPadding?` | `number` | Optional extra bounds padding. |
568
- | `up?` | `Vec3$3` | Preferred "up" vector for local profile frame. Auto fallback is used near parallel segments. |
894
+ | `up?` | `Vec3` | Preferred "up" vector for local profile frame. Auto fallback is used near parallel segments. |
569
895
 
570
896
  #### `transitionCurve()` — Create a smooth transition curve between two edges.
571
897
 
@@ -574,11 +900,11 @@ Returns a `HermiteCurve3D` that starts at `edgeA.point` tangent to `edgeA.tangen
574
900
  The curve maintains G1 continuity (matching tangent direction) at both endpoints. Weight parameters control the shape of the transition.
575
901
 
576
902
  ```js
577
- // Connect two edges with a balanced transition
578
- const curve = transitionCurve(
579
- { point: [0, 0, 0], tangent: [1, 0, 0] },
580
- { point: [10, 5, 0], tangent: [1, 0, 0] },
581
- );
903
+ ```js
904
+
905
+ // Connect two edges with a balanced transition const curve = transitionCurve( { point: [0, 0, 0], tangent: [1, 0, 0] }, { point: [10, 5, 0], tangent: [1, 0, 0] }, );
906
+
907
+ ```
582
908
 
583
909
  // Weighted: curve hugs edge A longer
584
910
  const weighted = transitionCurve(
@@ -588,24 +914,25 @@ const weighted = transitionCurve(
588
914
  );
589
915
  ```
590
916
 
591
- `transitionCurve(edgeA: TransitionEdge, edgeB: TransitionEdge, options?: TransitionCurveOptions): HermiteCurve3D`
917
+ ```ts
918
+ transitionCurve(edgeA: TransitionEdge, edgeB: TransitionEdge, options?: TransitionCurveOptions): HermiteCurve3D
919
+ ```
592
920
 
593
921
  **`TransitionEdge`**
594
- - `point: Vec3$6` — Connection point on the edge. Can be any point along the edge where the transition should connect.
595
- - `tangent: Vec3$6` — Tangent direction at the connection point. This is the direction the curve should initially follow when leaving this edge. For a straight edge, this is typically the edge direction pointing "outward" (away from the body of the edge, toward the other edge).
596
- - `normal?: Vec3$6` — Surface normal at the connection point (optional). Used as a hint for the sweep frame's up vector.
922
+ - `point: Vec3` — Connection point on the edge. Can be any point along the edge where the transition should connect.
923
+ - `tangent: Vec3` — Tangent direction at the connection point. This is the direction the curve should initially follow when leaving this edge. For a straight edge, this is typically the edge direction pointing "outward" (away from the body of the edge, toward the other edge).
924
+ - `normal?: Vec3` — Surface normal at the connection point (optional). Used as a hint for the sweep frame's up vector.
597
925
 
598
926
  #### `transitionSurface()` — Create a solid transition surface between two edges by sweeping a profile along a Hermite transition curve.
599
927
 
600
928
  This produces a watertight solid that smoothly connects the two edges. Works with both Manifold and OCCT backends.
601
929
 
602
930
  ```js
603
- // Circular tube connecting two edges
604
- const tube = transitionSurface(
605
- { point: [0, 0, 0], tangent: [1, 0, 0] },
606
- { point: [10, 5, 3], tangent: [0, 1, 0] },
607
- { radius: 0.5 },
608
- );
931
+ ```js
932
+
933
+ // Circular tube connecting two edges const tube = transitionSurface( { point: [0, 0, 0], tangent: [1, 0, 0] }, { point: [10, 5, 3], tangent: [0, 1, 0] }, { radius: 0.5 }, );
934
+
935
+ ```
609
936
 
610
937
  // Custom profile with weights
611
938
  const custom = transitionSurface(
@@ -615,7 +942,9 @@ const custom = transitionSurface(
615
942
  );
616
943
  ```
617
944
 
618
- `transitionSurface(edgeA: TransitionEdge, edgeB: TransitionEdge, options?: TransitionSurfaceOptions): Shape`
945
+ ```ts
946
+ transitionSurface(edgeA: TransitionEdge, edgeB: TransitionEdge, options?: TransitionSurfaceOptions): Shape
947
+ ```
619
948
 
620
949
  ---
621
950
 
@@ -625,14 +954,10 @@ Select or inspect named faces and edges on a shape.
625
954
 
626
955
  #### `coalesceEdges()` — Merge collinear edge segments into longer logical edges.
627
956
 
628
- **Details**
629
-
630
957
  Tessellation often splits one geometric edge into multiple short segments. `coalesceEdges` groups adjacent collinear segments and merges each group into a single `EdgeSegment` spanning the full extent. This is usually needed before passing edges to `fillet()` or `chamfer()` on non-primitive shapes.
631
958
 
632
959
  The `tolerance` controls the maximum perpendicular distance from collinearity before two segments are considered non-collinear. Default: `0.01`.
633
960
 
634
- **Example**
635
-
636
961
  ```ts
637
962
  const topEdges = selectEdges(part, { atZ: 20 });
638
963
  for (const edge of coalesceEdges(topEdges)) {
@@ -640,7 +965,9 @@ for (const edge of coalesceEdges(topEdges)) {
640
965
  }
641
966
  ```
642
967
 
643
- `coalesceEdges(segments: EdgeSegment[], tolerance?: number): EdgeSegment[]`
968
+ ```ts
969
+ coalesceEdges(segments: EdgeSegment[], tolerance?: number): EdgeSegment[]
970
+ ```
644
971
 
645
972
  **`EdgeSegment`**
646
973
 
@@ -657,19 +984,17 @@ for (const edge of coalesceEdges(topEdges)) {
657
984
 
658
985
  #### `selectEdge()` — Select the single best-matching edge from a shape.
659
986
 
660
- **Details**
661
-
662
987
  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.
663
988
 
664
- **Example**
665
-
666
989
  ```ts
667
990
  // Chamfer one specific edge near a known point
668
991
  const bottomEdge = selectEdge(part, { near: [25, 0, 0], atZ: 0 });
669
992
  result = chamfer(result, 1.5, bottomEdge);
670
993
  ```
671
994
 
672
- `selectEdge(shape: Shape, query?: EdgeQuery): EdgeSegment`
995
+ ```ts
996
+ selectEdge(shape: Shape, query?: EdgeQuery): EdgeSegment
997
+ ```
673
998
 
674
999
  **`EdgeQuery`**
675
1000
 
@@ -693,14 +1018,10 @@ result = chamfer(result, 1.5, bottomEdge);
693
1018
 
694
1019
  #### `selectEdges()` — Select all edges from a shape that match the given query.
695
1020
 
696
- **Details**
697
-
698
1021
  Extracts sharp edges from the mesh (dihedral angle > 1°), applies all filters in the query, and returns the matching `EdgeSegment[]`. When `near` is specified the results are sorted closest-first.
699
1022
 
700
1023
  Works on any shape — primitives, booleans, shells, and imported meshes. Use this when tracked topology is unavailable (e.g. after a difference or on imported geometry). For simpler cases, pass an `EdgeQuery` directly to `fillet()` or `chamfer()` instead of calling `selectEdges` separately.
701
1024
 
702
- **Example**
703
-
704
1025
  ```ts
705
1026
  // Fillet all top edges of a box
706
1027
  const topEdges = selectEdges(part, { atZ: 20, perpendicular: [0, 0, 1] });
@@ -710,7 +1031,9 @@ for (const edge of coalesceEdges(topEdges)) {
710
1031
  }
711
1032
  ```
712
1033
 
713
- `selectEdges(shape: Shape, query?: EdgeQuery): EdgeSegment[]`
1034
+ ```ts
1035
+ selectEdges(shape: Shape, query?: EdgeQuery): EdgeSegment[]
1036
+ ```
714
1037
 
715
1038
  ---
716
1039
 
@@ -720,14 +1043,10 @@ Modify edges of a solid — fillets, chamfers, draft, offset.
720
1043
 
721
1044
  #### `chamfer()` — Apply chamfers (beveled edges) to one or more edges of a shape.
722
1045
 
723
- **Details**
724
-
725
1046
  Produces a 45° bevel at the specified `size` (distance from edge). Works on both straight and curved edges. Supports OCCT and Manifold backends.
726
1047
 
727
1048
  The `edges` parameter accepts the same options as `fillet()`: inline `EdgeQuery`, pre-selected `EdgeSegment`/`EdgeSegment[]`, or `undefined` (all sharp edges).
728
1049
 
729
- **Example**
730
-
731
1050
  ```ts
732
1051
  // Chamfer all edges
733
1052
  chamfer(myShape, 1)
@@ -736,18 +1055,16 @@ chamfer(myShape, 1)
736
1055
  chamfer(myShape, 2, { parallel: [0, 0, 1] })
737
1056
  ```
738
1057
 
739
- `chamfer(shape: Shape, size: number, edges?: EdgeSelector): Shape`
1058
+ ```ts
1059
+ chamfer(shape: Shape, size: number, edges?: EdgeSelector): Shape
1060
+ ```
740
1061
 
741
1062
  #### `draft()` — Apply a draft angle (taper) to vertical faces for mold extraction.
742
1063
 
743
- **Details**
744
-
745
1064
  Adds a taper angle to the vertical faces of a solid so that it can be extracted from a mold. The neutral plane is the Z position where the draft angle is zero — faces above and below are tapered symmetrically. Typical values for injection molding are 1–5°.
746
1065
 
747
1066
  Requires the OCCT backend. Throws on Manifold.
748
1067
 
749
- **Example**
750
-
751
1068
  ```ts
752
1069
  // Add 3° draft to a box for injection molding
753
1070
  draft(myBox, 3)
@@ -756,19 +1073,21 @@ draft(myBox, 3)
756
1073
  draft(myShape, 2, [0, 0, 1], 10)
757
1074
  ```
758
1075
 
759
- `draft(shape: Shape, angleDeg: number, pullDirection?: [ number, number, number ], neutralPlaneOffset?: number): Shape`
1076
+ ```ts
1077
+ draft(shape: Shape, angleDeg: number, pullDirection?: [ number, number, number ], neutralPlaneOffset?: number): Shape
1078
+ ```
760
1079
 
761
1080
  #### `fillet()` — Apply fillets (rounded edges) to one or more edges of a shape.
762
1081
 
763
- **Details**
764
-
765
1082
  Works on both straight and curved edges. Supports OCCT and Manifold backends. When using OCCT, all edges are filleted in a single kernel operation for best quality. When using Manifold, edges are filleted sequentially.
766
1083
 
767
- The `edges` parameter is flexible: - Omit to fillet **all** sharp edges - Pass an `EdgeQuery` for an inline filter (most common) - Pass an `EdgeSegment` or `EdgeSegment[]` from `selectEdges()` for pre-selected edges
1084
+ The `edges` parameter is flexible:
768
1085
 
769
- Throws if no edges match the selection, or if `radius` is not a positive finite number.
1086
+ - Omit to fillet **all** sharp edges
1087
+ - Pass an `EdgeQuery` for an inline filter (most common)
1088
+ - Pass an `EdgeSegment` or `EdgeSegment[]` from `selectEdges()` for pre-selected edges
770
1089
 
771
- **Example**
1090
+ Throws if no edges match the selection, or if `radius` is not a positive finite number.
772
1091
 
773
1092
  ```ts
774
1093
  // Fillet all edges
@@ -782,18 +1101,16 @@ const edges = selectEdges(myShape, { parallel: [0, 0, 1] })
782
1101
  fillet(myShape, 3, edges)
783
1102
  ```
784
1103
 
785
- `fillet(shape: Shape, radius: number, edges?: EdgeSelector, segments?: number): Shape`
1104
+ ```ts
1105
+ fillet(shape: Shape, radius: number, edges?: EdgeSelector, segments?: number): Shape
1106
+ ```
786
1107
 
787
1108
  #### `offsetSolid()` — Uniformly offset all surfaces of a solid inward or outward.
788
1109
 
789
- **Details**
790
-
791
1110
  Unlike `shell()`, which hollows a solid by removing one face, `offsetSolid()` produces a new solid whose every surface is shifted by `thickness`. Positive values grow the shape outward; negative values shrink it inward.
792
1111
 
793
1112
  Requires the OCCT backend. Throws on Manifold.
794
1113
 
795
- **Example**
796
-
797
1114
  ```ts
798
1115
  // Grow a box outward by 1mm on all sides
799
1116
  offsetSolid(myBox, 1)
@@ -802,22 +1119,22 @@ offsetSolid(myBox, 1)
802
1119
  offsetSolid(myShape, -0.5)
803
1120
  ```
804
1121
 
805
- `offsetSolid(shape: Shape, thickness: number): Shape`
1122
+ ```ts
1123
+ offsetSolid(shape: Shape, thickness: number): Shape
1124
+ ```
806
1125
 
807
1126
  #### `chamfer2d()` — Bevel a named vertical edge of a shape with a 45° chamfer.
808
1127
 
809
- **Details**
810
-
811
1128
  Compiler-owned chamfer for tracked vertical edges. Requires a compile-plan-covered target. Supported subset and quadrant semantics are the same as `fillet2d()` — see that function for details.
812
1129
 
813
- **Example**
814
-
815
1130
  ```ts
816
1131
  const b = rectangle(0, 0, 50, 50).extrude(20);
817
1132
  const chamfered = chamfer2d(b.toShape(), b.edge('vert-br'), 3, [-1, -1]);
818
1133
  ```
819
1134
 
820
- `chamfer2d(shape: Shape, edge: EdgeRef, size: number, quadrant?: [ number, number ]): Shape`
1135
+ ```ts
1136
+ chamfer2d(shape: Shape, edge: EdgeRef, size: number, quadrant?: [ number, number ]): Shape
1137
+ ```
821
1138
 
822
1139
  **`EdgeRef`**
823
1140
  - `query?: EdgeQueryRef` — Compiler-owned edge query when available.
@@ -825,36 +1142,38 @@ const chamfered = chamfer2d(b.toShape(), b.edge('vert-br'), 3, [-1, -1]);
825
1142
 
826
1143
  #### `fillet2d()` — Round a named vertical edge of a shape with a circular fillet.
827
1144
 
828
- **Details**
829
-
830
1145
  Compiler-owned fillet for tracked vertical edges. Requires a compile-plan-covered target (shapes from `box()`, `rectangle().extrude()`, or rigid transforms of those).
831
1146
 
832
- **Supported edges:** - Tracked vertical edges from `box()` or `rectangle().extrude()` - Rigid transforms between tracked source and target - Untouched sibling tracked vertical edges after earlier `fillet2d`/`chamfer2d`
1147
+ **Supported edges:**
1148
+
1149
+ - Tracked vertical edges from `box()` or `rectangle().extrude()`
1150
+ - Rigid transforms between tracked source and target
1151
+ - Untouched sibling tracked vertical edges after earlier `fillet2d`/`chamfer2d`
833
1152
 
834
1153
  **Not supported:** edges after shell, hole, cut, trim, difference, intersection, generic sketch extrudes, or tapered extrudes. Use `fillet()` with an `EdgeQuery` for those cases.
835
1154
 
836
1155
  Canonical quadrants: `vert-bl → [1,-1]`, `vert-br → [-1,-1]`, `vert-tr → [-1,1]`, `vert-tl → [1,1]`
837
1156
 
838
- **Example**
839
-
840
1157
  ```ts
841
1158
  const b = rectangle(0, 0, 50, 50).extrude(20);
842
1159
  const filleted = fillet2d(b.toShape(), b.edge('vert-br'), 5, [-1, -1]);
843
1160
  ```
844
1161
 
845
- `fillet2d(shape: Shape, edge: EdgeRef, radius: number, quadrant?: [ number, number ], segments?: number): Shape`
1162
+ ```ts
1163
+ fillet2d(shape: Shape, edge: EdgeRef, radius: number, quadrant?: [ number, number ], segments?: number): Shape
1164
+ ```
846
1165
 
847
1166
  #### `filletCorners()` — Create a polygon from points with specific corners rounded to arc fillets.
848
1167
 
849
- **Details**
850
-
851
1168
  Each corner spec identifies a vertex by its index in the `points` array and the desired fillet `radius`. Both convex and concave corners are supported.
852
1169
 
853
- Constraints: - Collinear corners cannot be filleted (throws an error) - Two neighboring fillets whose tangent lengths overlap the same edge will throw - Radius must be positive and small enough to fit within the adjacent edge lengths
1170
+ Constraints:
854
1171
 
855
- Use `offset(-r).offset(+r)` instead if you want to round **all** convex corners uniformly. Use `filletCorners` when you need selective or mixed sharp/rounded profiles.
1172
+ - Collinear corners cannot be filleted (throws an error)
1173
+ - Two neighboring fillets whose tangent lengths overlap the same edge will throw
1174
+ - Radius must be positive and small enough to fit within the adjacent edge lengths
856
1175
 
857
- **Example**
1176
+ Use `offset(-r).offset(+r)` instead if you want to round **all** convex corners uniformly. Use `filletCorners` when you need selective or mixed sharp/rounded profiles.
858
1177
 
859
1178
  ```ts
860
1179
  const roof = filletCorners(roofPoints, [
@@ -864,7 +1183,9 @@ const roof = filletCorners(roofPoints, [
864
1183
  ]);
865
1184
  ```
866
1185
 
867
- `filletCorners(points: PointInput[], corners: FilletCornerSpec[]): Sketch`
1186
+ ```ts
1187
+ filletCorners(points: PointInput[], corners: FilletCornerSpec[]): Sketch
1188
+ ```
868
1189
 
869
1190
  `FilletCornerSpec`: `{ index: number, radius: number, segments?: number }`
870
1191
 
@@ -891,7 +1212,9 @@ for (const {x, y} of circularLayout(12, r)) {
891
1212
  }
892
1213
  ```
893
1214
 
894
- `circularLayout(count: number, radius: number, options?: CircularLayoutOptions): LayoutPoint[]`
1215
+ ```ts
1216
+ circularLayout(count: number, radius: number, options?: CircularLayoutOptions): LayoutPoint[]
1217
+ ```
895
1218
 
896
1219
  **`CircularLayoutOptions`**
897
1220
  - `startDeg?: number` — Angle of the first element in degrees (default: 0 = +X axis).
@@ -902,13 +1225,12 @@ for (const {x, y} of circularLayout(12, r)) {
902
1225
 
903
1226
  #### `circularPattern()` — Repeat a shape in a circular pattern around an axis and union the copies.
904
1227
 
905
- **Details**
906
-
907
1228
  Distributes `count` copies evenly around the rotation axis (360° / count per step). All copies are unioned into a single `Shape`. Distinct compiler ownership is assigned to each copy — post-merge face identity via owner-scoped canonical queries still works for pattern descendants.
908
1229
 
909
- Two calling conventions: - **Simple** (Z axis): `circularPattern(shape, 6)` or `circularPattern(shape, 6, centerX, centerY)` - **Advanced** (arbitrary axis): `circularPattern(shape, 6, { axis, origin })`
1230
+ Two calling conventions:
910
1231
 
911
- **Example**
1232
+ - **Simple** (Z axis): `circularPattern(shape, 6)` or `circularPattern(shape, 6, centerX, centerY)`
1233
+ - **Advanced** (arbitrary axis): `circularPattern(shape, 6, { axis, origin })`
912
1234
 
913
1235
  ```ts
914
1236
  // 8 holes evenly spaced around origin
@@ -918,7 +1240,9 @@ circularPattern(cylinder(12, 4).translate(30, 0, -1), 8)
918
1240
  circularPattern(myFeature, 4, { axis: [1, 0, 0], origin: [0, 0, 50] })
919
1241
  ```
920
1242
 
921
- `circularPattern(shape: Shape, count: number, centerXOrOpts?: number | CircularPatternOptions, centerY?: number): Shape`
1243
+ ```ts
1244
+ circularPattern(shape: Shape, count: number, centerXOrOpts?: number | CircularPatternOptions, centerY?: number): Shape
1245
+ ```
922
1246
 
923
1247
  **`CircularPatternOptions`**
924
1248
  - `centerX?: number` — Center X of the rotation (default: 0). Used when axis is Z (legacy mode).
@@ -926,41 +1250,41 @@ circularPattern(myFeature, 4, { axis: [1, 0, 0], origin: [0, 0, 50] })
926
1250
 
927
1251
  #### `circularPattern2d()` — Repeat a 2D sketch in a circular pattern around a center point and union the copies.
928
1252
 
929
- `circularPattern2d(sketch: Sketch, count: number, centerXOrOpts?: number | { centerX?: number; centerY?: number; startDeg?: number; }, centerY?: number): Sketch`
1253
+ ```ts
1254
+ circularPattern2d(sketch: Sketch, count: number, centerXOrOpts?: number | { centerX?: number; centerY?: number; startDeg?: number; }, centerY?: number): Sketch
1255
+ ```
930
1256
 
931
1257
  #### `linearPattern()` — Repeat a shape in a linear pattern along a direction vector and union the copies.
932
1258
 
933
- **Details**
934
-
935
1259
  Creates `count` copies of `shape`, each offset by `(dx*i, dy*i, dz*i)` from the original. All copies are unioned into a single `Shape`. Distinct compiler ownership is assigned to each copy so face identity via owner-scoped canonical queries still works post-merge.
936
1260
 
937
- **Example**
938
-
939
1261
  ```ts
940
1262
  // 5 cylinders, 20mm apart along X
941
1263
  linearPattern(cylinder(10, 3), 5, 20, 0)
942
1264
  ```
943
1265
 
944
- `linearPattern(shape: Shape, count: number, dx: number, dy: number, dz?: number): Shape`
1266
+ ```ts
1267
+ linearPattern(shape: Shape, count: number, dx: number, dy: number, dz?: number): Shape
1268
+ ```
945
1269
 
946
1270
  #### `linearPattern2d()` — Repeat a 2D sketch in a linear pattern and union the copies.
947
1271
 
948
- `linearPattern2d(sketch: Sketch, count: number, dx: number, dy?: number): Sketch`
1272
+ ```ts
1273
+ linearPattern2d(sketch: Sketch, count: number, dx: number, dy?: number): Sketch
1274
+ ```
949
1275
 
950
1276
  #### `mirrorCopy()` — Mirror a shape across a plane and union the mirror with the original.
951
1277
 
952
- **Details**
953
-
954
1278
  The mirror plane passes through the origin and is defined by its normal vector. The mirrored copy is unioned with the original to produce a single symmetric Shape.
955
1279
 
956
- **Example**
957
-
958
1280
  ```ts
959
1281
  // Mirror across the YZ plane (X=0)
960
1282
  mirrorCopy(box(50, 30, 10), [1, 0, 0])
961
1283
  ```
962
1284
 
963
- `mirrorCopy(shape: Shape, normal: [ number, number, number ]): Shape`
1285
+ ```ts
1286
+ mirrorCopy(shape: Shape, normal: [ number, number, number ]): Shape
1287
+ ```
964
1288
 
965
1289
  ---
966
1290
 
@@ -968,14 +1292,72 @@ mirrorCopy(box(50, 30, 10), [1, 0, 0])
968
1292
 
969
1293
  Define geometry by relationships and let a solver find positions.
970
1294
 
1295
+ #### `Constraint.makeParallel()` — Constrain two lines to be parallel.
1296
+
1297
+ ```ts
1298
+ Constraint.makeParallel(builder: ConstrainedSketchBuilder, a: LineArg, b: LineArg): ConstrainedSketchBuilder
1299
+ ```
1300
+
1301
+ #### `Constraint.enforceAngle()` — Constrain the signed angle from line `a` to line `b`.
1302
+
1303
+ ```ts
1304
+ Constraint.enforceAngle(builder: ConstrainedSketchBuilder, a: LineArg, b: LineArg, angleDeg: number): ConstrainedSketchBuilder
1305
+ ```
1306
+
1307
+ #### `Constraint.horizontal()` — Constrain a line to be horizontal.
1308
+
1309
+ ```ts
1310
+ Constraint.horizontal(builder: ConstrainedSketchBuilder, line: LineArg): ConstrainedSketchBuilder
1311
+ ```
1312
+
1313
+ #### `Constraint.vertical()` — Constrain a line to be vertical.
1314
+
1315
+ ```ts
1316
+ Constraint.vertical(builder: ConstrainedSketchBuilder, line: LineArg): ConstrainedSketchBuilder
1317
+ ```
1318
+
1319
+ #### `Constraint.equalLength()` — Constrain two lines to have equal length.
1320
+
1321
+ ```ts
1322
+ Constraint.equalLength(builder: ConstrainedSketchBuilder, a: LineArg, b: LineArg): ConstrainedSketchBuilder
1323
+ ```
1324
+
1325
+ #### `Constraint.distance()` — Constrain the distance between two points.
1326
+
1327
+ ```ts
1328
+ Constraint.distance(builder: ConstrainedSketchBuilder, a: PointArg, b: PointArg, value: number): ConstrainedSketchBuilder
1329
+ ```
1330
+
1331
+ #### `Constraint.fix()` — Fix a point at a specific coordinate.
1332
+
1333
+ ```ts
1334
+ Constraint.fix(builder: ConstrainedSketchBuilder, pt: PointArg, x: number, y: number): ConstrainedSketchBuilder
1335
+ ```
1336
+
1337
+ #### `Constraint.coincident()` — Constrain two points to occupy the same location.
1338
+
1339
+ ```ts
1340
+ Constraint.coincident(builder: ConstrainedSketchBuilder, a: PointArg, b: PointArg): ConstrainedSketchBuilder
1341
+ ```
1342
+
1343
+ #### `Constraint.perpendicular()` — Constrain two lines to be perpendicular.
1344
+
1345
+ ```ts
1346
+ Constraint.perpendicular(builder: ConstrainedSketchBuilder, a: LineArg, b: LineArg): ConstrainedSketchBuilder
1347
+ ```
1348
+
1349
+ #### `Constraint.length()` — Constrain the length of a line.
1350
+
1351
+ ```ts
1352
+ Constraint.length(builder: ConstrainedSketchBuilder, line: LineArg, value: number): ConstrainedSketchBuilder
1353
+ ```
1354
+
971
1355
  #### `addPolygon()` — Add a general polygon concept to the builder.
972
1356
 
973
1357
  Creates n vertices and n sides (CCW: `sides[i]` from `vertices[i]` → `vertices[(i+1) % n]`). Applies a `ccw` constraint to enforce winding. All dimensional constraints (lengths, angles, position) are left to the caller.
974
1358
 
975
1359
  Use `sk.addPolygon()` as the shorthand builder method.
976
1360
 
977
- **Example**
978
-
979
1361
  ```ts
980
1362
  const sk = constrainedSketch();
981
1363
  const tri = sk.addPolygon({ points: [[0,0],[100,0],[50,80]] });
@@ -984,7 +1366,9 @@ sk.length(tri.side(0), 100);
984
1366
  return sk.solve().extrude(5);
985
1367
  ```
986
1368
 
987
- `addPolygon(sk: ConstrainedSketchBuilder, options: PolygonOptions): ConstrainedPolygon`
1369
+ ```ts
1370
+ addPolygon(sk: ConstrainedSketchBuilder, options: PolygonOptions): ConstrainedPolygon
1371
+ ```
988
1372
 
989
1373
  **`PolygonOptions`**
990
1374
  - `addLoop?: boolean` — Whether to register a closed loop for sketch generation. Default: true.
@@ -1001,8 +1385,6 @@ Creates 4 vertices (CCW: bl→br→tr→tl), 4 sides, 4 structural constraints (
1001
1385
 
1002
1386
  Use `sk.rect()` as the shorthand builder method.
1003
1387
 
1004
- **Example**
1005
-
1006
1388
  ```ts
1007
1389
  const sk = constrainedSketch();
1008
1390
  const r = sk.rect({ x: 0, y: 0, width: 100, height: 50 });
@@ -1011,7 +1393,9 @@ sk.length(r.bottom, 120); // override initial width
1011
1393
  return sk.solve().extrude(10);
1012
1394
  ```
1013
1395
 
1014
- `addRect(sk: ConstrainedSketchBuilder, options?: RectOptions): ConstrainedRect`
1396
+ ```ts
1397
+ addRect(sk: ConstrainedSketchBuilder, options?: RectOptions): ConstrainedRect
1398
+ ```
1015
1399
 
1016
1400
  **`RectOptions`**
1017
1401
 
@@ -1041,8 +1425,6 @@ Vertices are placed at `(cx + r·cos(startAngle + i·2π/n), cy + r·sin(...))`.
1041
1425
 
1042
1426
  Use `sk.regularPolygon()` as the shorthand builder method.
1043
1427
 
1044
- **Example**
1045
-
1046
1428
  ```ts
1047
1429
  const sk = constrainedSketch();
1048
1430
  const hex = sk.regularPolygon({ sides: 6, radius: 25 });
@@ -1051,7 +1433,9 @@ sk.length(hex.side(0), 30); // all sides change (equal constraint)
1051
1433
  return sk.solve().extrude(5);
1052
1434
  ```
1053
1435
 
1054
- `addRegularPolygon(sk: ConstrainedSketchBuilder, options: RegularPolygonOptions): ConstrainedRegularPolygon`
1436
+ ```ts
1437
+ addRegularPolygon(sk: ConstrainedSketchBuilder, options: RegularPolygonOptions): ConstrainedRegularPolygon
1438
+ ```
1055
1439
 
1056
1440
  **`RegularPolygonOptions`**
1057
1441
 
@@ -1070,8 +1454,6 @@ return sk.solve().extrude(5);
1070
1454
 
1071
1455
  #### `circle()` — Create an analytic 2D circle for measurement, construction, and extrusion.
1072
1456
 
1073
- **Example**
1074
-
1075
1457
  ```ts
1076
1458
  const c = circle(0, 0, 25);
1077
1459
  c.diameter; c.circumference; c.area;
@@ -1085,15 +1467,18 @@ cyl.face('side'); // FaceRef (curved)
1085
1467
  Circle2D.fromDiameter(point(0, 0), 50);
1086
1468
  ```
1087
1469
 
1088
- `circle(cx: number, cy: number, radius: number): Circle2D`
1470
+ ```ts
1471
+ circle(cx: number, cy: number, radius: number): Circle2D
1472
+ ```
1089
1473
 
1090
1474
  #### `constrainedSketch()` — Create a parametric 2D sketch driven by geometric constraints and a nonlinear solver.
1091
1475
 
1092
1476
  **Workflow**
1093
1477
 
1094
- 1. Create a builder with `constrainedSketch()`. 2. Add geometry — points, lines, circles, arcs — using the builder methods. 3. Add constraints (`horizontal`, `length`, `fix`, etc.) to drive the geometry. 4. Call `.solve()` to run the solver and get a `ConstraintSketch` (which extends `Sketch`).
1095
-
1096
- **Example**
1478
+ 1. Create a builder with `constrainedSketch()`.
1479
+ 2. Add geometry — points, lines, circles, arcs — using the builder methods.
1480
+ 3. Add constraints (`horizontal`, `length`, `fix`, etc.) to drive the geometry.
1481
+ 4. Call `.solve()` to run the solver and get a `ConstraintSketch` (which extends `Sketch`).
1097
1482
 
1098
1483
  ```ts
1099
1484
  const sk = constrainedSketch();
@@ -1117,15 +1502,15 @@ result.inspect(); // human-readable summary
1117
1502
  result.withUpdatedConstraint('cst-5', 120); // update a dimension without rebuilding
1118
1503
  ```
1119
1504
 
1120
- `constrainedSketch(options?: ConstrainedSketchOptions): ConstrainedSketchBuilder`
1505
+ ```ts
1506
+ constrainedSketch(options?: ConstrainedSketchOptions): ConstrainedSketchBuilder
1507
+ ```
1121
1508
 
1122
1509
  **`ConstrainedSketchOptions`**
1123
1510
  - `strict?: boolean` — When true, adding a constraint that cannot be satisfied throws instead of silently discarding it.
1124
1511
 
1125
1512
  #### `line()` — Create an analytic 2D line segment between two points.
1126
1513
 
1127
- **Example**
1128
-
1129
1514
  ```ts
1130
1515
  const l = line(0, 0, 50, 0);
1131
1516
  l.length; l.midpoint; l.angle; l.direction;
@@ -1137,12 +1522,12 @@ Line2D.fromPointAndAngle(point(0, 0), 45, 100);
1137
1522
  Line2D.fromPointAndDirection(point(0, 0), [1, 1], 50);
1138
1523
  ```
1139
1524
 
1140
- `line(x1: number, y1: number, x2: number, y2: number): Line2D`
1525
+ ```ts
1526
+ line(x1: number, y1: number, x2: number, y2: number): Line2D
1527
+ ```
1141
1528
 
1142
1529
  #### `point()` — Create an analytic 2D point for measurement and construction geometry.
1143
1530
 
1144
- **Example**
1145
-
1146
1531
  ```ts
1147
1532
  const p = point(10, 20);
1148
1533
  p.distanceTo(point(30, 40)); // Euclidean distance
@@ -1151,15 +1536,52 @@ p.translate(5, 5); // new shifted point
1151
1536
  p.toTuple(); // [10, 20]
1152
1537
  ```
1153
1538
 
1154
- `point(x: number, y: number): Point2D`
1539
+ ```ts
1540
+ point(x: number, y: number): Point2D
1541
+ ```
1155
1542
 
1156
1543
  ---
1157
1544
 
1158
- ## C9: Spatial Placement
1545
+ ## C9: Spatial Placement
1546
+
1547
+ Position geometry relative to other geometry using semantic anchors.
1548
+
1549
+
1550
+ #### `Points.distance()` — Euclidean distance between two 3D points.
1551
+
1552
+ ```ts
1553
+ Points.readonly distance: typeof distance
1554
+ ```
1555
+
1556
+ #### `Points.midpoint()` — Center point between two 3D points.
1557
+
1558
+ ```ts
1559
+ Points.readonly midpoint: typeof midpoint
1560
+ ```
1561
+
1562
+ #### `Points.lerp()` — Linearly interpolate between two 3D points. t=0 returns a, t=1 returns b.
1563
+
1564
+ ```ts
1565
+ Points.readonly lerp: typeof lerp
1566
+ ```
1567
+
1568
+ #### `Points.direction()` — Unit direction vector from a to b. Throws if a and b are the same point.
1569
+
1570
+ ```ts
1571
+ Points.readonly direction: typeof direction
1572
+ ```
1573
+
1574
+ #### `Points.offset()` — Move a point along a direction vector by a given amount.
1575
+
1576
+ ```ts
1577
+ Points.readonly offset: typeof offset
1578
+ ```
1159
1579
 
1160
- Position geometry relative to other geometry using semantic anchors.
1580
+ #### `Points.polar()` Compute a 2D point at distance and angle (degrees) from an optional origin.
1161
1581
 
1162
- *No free functions — see class methods (Shape, Sketch, ConstrainedSketchBuilder).*
1582
+ ```ts
1583
+ Points.readonly polar: typeof polar
1584
+ ```
1163
1585
 
1164
1586
  ---
1165
1587
 
@@ -1169,7 +1591,7 @@ Compose parts with joints for kinematic simulation.
1169
1591
 
1170
1592
  #### `assembly()` — Create an assembly container with named parts and joints for kinematic mechanisms.
1171
1593
 
1172
- **Details**
1594
+ **Use this from iteration 1 for any model with moving parts.** Hinges, sliders, gears, articulated fingers, doors — all start with `assembly()`, not with manual rotation math. Don't build a static "extended pose" first and refactor to an assembly later: joint sliders, animations, sweeps, collision detection, and robot export all flow from the kinematic graph.
1173
1595
 
1174
1596
  An assembly models a mechanism as a directed graph of parts connected by joints. Parts are the nodes; joints are directed edges from parent to child. The graph must be a forest (no cycles). Root parts (those with no incoming joint) are anchored to world space.
1175
1597
 
@@ -1179,8 +1601,6 @@ The higher-level `connect()` API uses declared **connectors** to compute joint f
1179
1601
 
1180
1602
  For multi-file assemblies, a file that returns an `Assembly` is importable via `require()` and yields an `ImportedAssembly`. Use `mergeInto()` to flatten a sub-assembly into a parent assembly.
1181
1603
 
1182
- **Example**
1183
-
1184
1604
  ```ts
1185
1605
  const mech = assembly("Arm")
1186
1606
  .addPart("base", box(80, 80, 20, true), {
@@ -1196,15 +1616,17 @@ const mech = assembly("Arm")
1196
1616
  return mech; // auto-solved at defaults, renders all parts
1197
1617
  ```
1198
1618
 
1199
- `assembly(name?: string): Assembly`
1619
+ ```ts
1620
+ assembly(name?: string): Assembly
1621
+ ```
1200
1622
 
1201
1623
  #### `bomToCsv()` — Convert an array of BOM rows into a CSV string.
1202
1624
 
1203
- **Details**
1204
-
1205
1625
  Produces a CSV with columns: `part`, `qty`, `material`, `process`, `tolerance`, `notes`. String values are quoted and internal double-quotes are escaped. Prefer calling `solvedAssembly.bomCsv()` directly — this function is exposed for custom BOM processing.
1206
1626
 
1207
- `bomToCsv(rows: BomRow[]): string`
1627
+ ```ts
1628
+ bomToCsv(rows: BomRow[]): string
1629
+ ```
1208
1630
 
1209
1631
  **`BomRow`**: `part: string`, `qty: number`, `material?: string`, `process?: string`, `tolerance?: string`, `notes?: string`, `metadata?: PartMetadata`
1210
1632
 
@@ -1212,12 +1634,8 @@ Produces a CSV with columns: `part`, `qty`, `material`, `process`, `tolerance`,
1212
1634
 
1213
1635
  #### `joint()` — Create a revolute joint that auto-generates a parameter slider and rotates the shape.
1214
1636
 
1215
- **Details**
1216
-
1217
1637
  This is a convenience wrapper for single-shape, single-joint use cases. It calls `param()` to create a named angle slider, then applies `rotateAroundAxis()` to the shape. Use the full `Assembly` API for mechanisms with multiple parts and joints.
1218
1638
 
1219
- **Example**
1220
-
1221
1639
  ```ts
1222
1640
  const arm = joint("Shoulder", armShape, [0, 0, 20], {
1223
1641
  axis: [0, 1, 0],
@@ -1226,14 +1644,14 @@ const arm = joint("Shoulder", armShape, [0, 0, 20], {
1226
1644
  return arm;
1227
1645
  ```
1228
1646
 
1229
- `joint(name: string, shape: Shape, pivot: [ number, number, number ], opts?: RevoluteJointOpts): Shape`
1647
+ ```ts
1648
+ joint(name: string, shape: Shape, pivot: [ number, number, number ], opts?: RevoluteJointOpts): Shape
1649
+ ```
1230
1650
 
1231
1651
  `RevoluteJointOpts`: `{ min?: number, max?: number, default?: number, unit?: string, reverse?: boolean }`
1232
1652
 
1233
1653
  #### `jointsView()` — Register viewport-only mechanism controls that animate returned objects without re-running the script.
1234
1654
 
1235
- **Details**
1236
-
1237
1655
  Defines joints (revolute or prismatic), optional gear/rack couplings, and named animations. The viewport resolves transforms through the joint chain at display time — the script geometry is computed only once at rest pose.
1238
1656
 
1239
1657
  **Critical:** Solve the assembly at **rest pose** (all animated joints = 0). The viewport applies `jointsView` transforms on top of the returned scene. If geometry is already solved at non-zero angles, animation will double-rotate everything.
@@ -1280,8 +1698,6 @@ keyframes: [
1280
1698
 
1281
1699
  Mixing explicit `at` and omitted `at` in the same animation is not allowed.
1282
1700
 
1283
- **Example**
1284
-
1285
1701
  ```js
1286
1702
  jointsView({
1287
1703
  joints: [{
@@ -1300,7 +1716,9 @@ jointsView({
1300
1716
  });
1301
1717
  ```
1302
1718
 
1303
- `jointsView(options?: JointsViewOptions): void`
1719
+ ```ts
1720
+ jointsView(options?: JointsViewOptions): void
1721
+ ```
1304
1722
 
1305
1723
  **`JointsViewOptions`**: `enabled?: boolean`, `joints?: JointViewInput[]`, `couplings?: JointViewCouplingInput[]`, `animations?: JointViewAnimationInput[]`, `defaultAnimation?: string`
1306
1724
 
@@ -1323,112 +1741,140 @@ jointsView({
1323
1741
 
1324
1742
  Declare user-facing controls that drive model geometry.
1325
1743
 
1326
- #### `boolParam()` — Declare a boolean parameter that renders as a checkbox in the UI.
1744
+ #### `Param.number()` — Declare a numeric parameter that renders as a slider in the UI.
1327
1745
 
1328
- **Details**
1746
+ Each call registers a slider control. When the user moves the slider the entire script re-executes with the new value. Parameter values are also overridable from `require()` imports or the CLI `--param` flag — the `name` string is the key used in both cases.
1329
1747
 
1330
- Internally stored as `0`/`1`. When overriding from CLI or `require()`, pass `1` for true and `0` for false. The `name` string is the override key.
1748
+ Default range rules when options are omitted:
1749
+
1750
+ - `min` defaults to `0`
1751
+ - `max` defaults to `defaultValue * 4`
1752
+ - `step` is auto-calculated: `1` for integer params, `0.1` for ranges ≤ 100, `1` for larger ranges
1331
1753
 
1332
- **Example**
1754
+ The `unit` option is cosmetic only — no conversion is performed. Use `integer: true` for counts, sides, quantities (rounds to whole numbers; step defaults to `1`).
1333
1755
 
1334
1756
  ```ts
1335
- const showHoles = boolParam("Show Holes", true);
1336
- if (showHoles) return difference(plate, cylinder(10, 5).translate(50, 30, 0));
1337
- return plate;
1757
+ const width = Param.number("Width", 50);
1758
+ const angle = Param.number("Angle", 45, { min: 0, max: 180, unit: "°" });
1759
+ const sides = Param.number("Sides", 6, { min: 3, max: 12, integer: true });
1338
1760
  ```
1339
1761
 
1340
- Override via import:
1762
+ **Parameter overrides** — key must match `name` exactly:
1341
1763
 
1342
1764
  ```ts
1343
- const pan = require("./pan.forge.js", { "Show Lid": 0 });
1765
+ // Via require()
1766
+ const bracket = require("./bracket.forge.js", { Width: 80 });
1767
+
1768
+ // Via CLI
1769
+ // forgecad run model.forge.js --param "Wall Thickness=3"
1344
1770
  ```
1345
1771
 
1346
- `boolParam(name: string, defaultValue: boolean): boolean`
1772
+ Also available as the shorthand alias `param()`.
1347
1773
 
1348
- #### `choiceParam()` — Declare a choice parameter that renders as a dropdown in the UI.
1774
+ ```ts
1775
+ Param.number(name: string, defaultValue: number, opts?: { min?: number; max?: number; step?: number; unit?: string; integer?: boolean; reverse?: boolean; }): number
1776
+ ```
1349
1777
 
1350
- **Details**
1778
+ #### `Param.string()` — Declare a string parameter that renders as a text input in the UI.
1351
1779
 
1352
- `defaultValue` must exactly match one entry in `choices`. Returns the selected string label. Prefer `choiceParam` over `param` when a slider would hide intent — named choices like `"wok"` are self-describing.
1780
+ String parameters let users type free-form text labels, names, inscriptions, file paths, etc. The `name` string is the override key.
1353
1781
 
1354
- Overrides may be passed as the choice label string (preferred) or as a numeric index. The `name` string is the override key.
1782
+ ```ts
1783
+ const label = Param.string("Label", "Hello World");
1784
+ const name = Param.string("Name", "Part-001", { maxLength: 20 });
1785
+ ```
1355
1786
 
1356
- **Example**
1787
+ Override via import:
1357
1788
 
1358
1789
  ```ts
1359
- const panStyle = choiceParam("Pan Style", "frying-pan", ["frying-pan", "saute-pan", "wok"]);
1360
- if (panStyle === "wok") return buildWok();
1790
+ const tag = require("./tag.forge.js", { Label: "Custom Text" });
1361
1791
  ```
1362
1792
 
1363
- Override via import:
1793
+ Only available as `Param.string()` — no standalone alias.
1364
1794
 
1365
1795
  ```ts
1366
- const pan = require("./pan.forge.js", { "Pan Style": "wok" });
1796
+ Param.string(name: string, defaultValue: string, opts?: { maxLength?: number; }): string
1367
1797
  ```
1368
1798
 
1369
- Override via CLI:
1799
+ #### `Param.bool()` — Declare a boolean parameter that renders as a checkbox in the UI.
1370
1800
 
1371
- ```bash
1372
- forgecad run model.forge.js --param "Pan Style=wok"
1801
+ Internally stored as `0`/`1`. When overriding from CLI or `require()`, pass `1` for true and `0` for false. The `name` string is the override key.
1802
+
1803
+ ```ts
1804
+ const showHoles = Param.bool("Show Holes", true);
1805
+ if (showHoles) return difference(plate, cylinder(10, 5).translate(50, 30, 0));
1806
+ return plate;
1373
1807
  ```
1374
1808
 
1375
- `choiceParam(name: string, defaultValue: string, choices: string[]): string`
1809
+ Override via import:
1376
1810
 
1377
- #### `listParam()` — Declare a list parameter — an array of struct items with per-field UI controls.
1811
+ ```ts
1812
+ const pan = require("./pan.forge.js", { "Show Lid": 0 });
1813
+ ```
1378
1814
 
1379
- **Details**
1815
+ Also available as the shorthand alias `boolParam()`.
1380
1816
 
1381
- Each item in the list is a struct whose fields each render as their own control (slider, checkbox, or dropdown). The user can add/remove rows up to `minItems`/`maxItems` bounds.
1817
+ ```ts
1818
+ Param.bool(name: string, defaultValue: boolean): boolean
1819
+ ```
1382
1820
 
1383
- Field types: - Boolean fields (`boolean: true` in field defs) return as `boolean` - Choice fields (`choices: [...]` in field defs) return as `string` - All other fields return as `number`
1821
+ #### `Param.choice()` Declare a choice parameter that renders as a dropdown in the UI.
1384
1822
 
1385
- `listParam<T extends Record<string, number | boolean | string>>(name: string, defaultItems: T[], opts: { ... }): T[]`
1823
+ `defaultValue` must exactly match one entry in `choices`. Returns the selected string label. Prefer `Param.choice` over `Param.number` when a slider would hide intent named choices like `"wok"` are self-describing.
1386
1824
 
1387
- `ListParamFieldDef`: `{ min?: number, max?: number, step?: number, unit?: string, integer?: boolean, boolean?: boolean, choices?: string[] }`
1825
+ Overrides may be passed as the choice label string (preferred) or as a numeric index. The `name` string is the override key.
1388
1826
 
1389
- #### `param()` — Declare a numeric parameter that renders as a slider in the UI.
1827
+ ```ts
1828
+ const panStyle = Param.choice("Pan Style", "frying-pan", ["frying-pan", "saute-pan", "wok"]);
1829
+ if (panStyle === "wok") return buildWok();
1830
+ ```
1390
1831
 
1391
- **Details**
1832
+ Override via import:
1392
1833
 
1393
- Each `param()` call registers a slider control. When the user moves the slider the entire script re-executes with the new value. Parameter values are also overridable from `require()` imports or the CLI `--param` flag — the `name` string is the key used in both cases.
1834
+ ```ts
1835
+ const pan = require("./pan.forge.js", { "Pan Style": "wok" });
1836
+ ```
1394
1837
 
1395
- Default range rules when options are omitted: - `min` defaults to `0` - `max` defaults to `defaultValue * 4` - `step` is auto-calculated: `1` for integer params, `0.1` for ranges ≤ 100, `1` for larger ranges
1838
+ Override via CLI:
1396
1839
 
1397
- The `unit` option is cosmetic only — no conversion is performed. Use `integer: true` for counts, sides, quantities (rounds to whole numbers; step defaults to `1`).
1840
+ ```bash
1841
+ forgecad run model.forge.js --param "Pan Style=wok"
1842
+ ```
1398
1843
 
1399
- **Example**
1844
+ Also available as the shorthand alias `choiceParam()`.
1400
1845
 
1401
1846
  ```ts
1402
- const width = param("Width", 50);
1403
- const angle = param("Angle", 45, { min: 0, max: 180, unit: "°" });
1404
- const sides = param("Sides", 6, { min: 3, max: 12, integer: true });
1847
+ Param.choice(name: string, defaultValue: string, choices: string[]): string
1405
1848
  ```
1406
1849
 
1407
- **Parameter overrides**key must match `name` exactly:
1850
+ #### `Param.list()`Declare a list parameter — an array of struct items with per-field UI controls.
1408
1851
 
1409
- ```ts
1410
- // Via require()
1411
- const bracket = require("./bracket.forge.js", { Width: 80 });
1852
+ Each item in the list is a struct whose fields each render as their own control (slider, checkbox, or dropdown). The user can add/remove rows up to `minItems`/`maxItems` bounds.
1412
1853
 
1413
- // Via CLI
1414
- // forgecad run model.forge.js --param "Wall Thickness=3"
1854
+ Field types:
1855
+
1856
+ - Boolean fields (`boolean: true` in field defs) return as `boolean`
1857
+ - Choice fields (`choices: [...]` in field defs) return as `string`
1858
+ - All other fields return as `number`
1859
+
1860
+ ```ts
1861
+ Param.list<T extends Record<string, number | boolean | string>>(name: string, defaultItems: T[], opts: { ... }): T[]
1415
1862
  ```
1416
1863
 
1417
- `param(name: string, defaultValue: number, opts?: { min?: number; max?: number; step?: number; unit?: string; integer?: boolean; reverse?: boolean; }): number`
1864
+ `ListParamFieldDef`: `{ min?: number, max?: number, step?: number, unit?: string, integer?: boolean, boolean?: boolean, choices?: string[] }`
1418
1865
 
1419
1866
  #### `dim()` — Add a dimension annotation between two points.
1420
1867
 
1421
- **Details**
1422
-
1423
1868
  Dimension annotations are purely visual callouts rendered in the viewport and report export. They do not affect geometry or constrain the model.
1424
1869
 
1425
1870
  Point arguments accept 2D tuples `[x, y]`, 3D tuples `[x, y, z]`, or `Point2D` objects (Z is treated as 0 for 2D inputs).
1426
1871
 
1427
1872
  **Ownership Rules (Report Pages)**
1428
1873
 
1429
- - `currentComponent: true` — deterministic ownership by the calling import instance. Use when authoring reusable imported parts. - `component: "Part Name"` — route dimension to another named returned object. - Multiple owners: dimension is shared and appears on the assembly overview page. - No ownership set: report export infers ownership via endpoint-in-bbox.
1430
-
1431
- **Example**
1874
+ - `currentComponent: true` — deterministic ownership by the calling import instance. Use when authoring reusable imported parts.
1875
+ - `component: "Part Name"` — route dimension to another named returned object.
1876
+ - Multiple owners: dimension is shared and appears on the assembly overview page.
1877
+ - No ownership set: report export infers ownership via endpoint-in-bbox.
1432
1878
 
1433
1879
  ```ts
1434
1880
  dim([-w / 2, 0, 0], [w / 2, 0, 0], { label: "Width" });
@@ -1438,25 +1884,25 @@ dim([0, 0, 0], [100, 0, 0], { component: "Base", color: "#00AAFF" });
1438
1884
 
1439
1885
  `component` (string or string[] — report ownership), `currentComponent` (boolean)
1440
1886
 
1441
- `dim(from: PointArg$1, to: PointArg$1, opts?: DimOpts): void`
1887
+ ```ts
1888
+ dim(from: PointArg, to: PointArg, opts?: DimOpts): void
1889
+ ```
1442
1890
 
1443
1891
  `DimOpts`: `{ offset?: number, label?: string, color?: string, component?: string | string[], currentComponent?: boolean }`
1444
1892
 
1445
1893
  #### `dimLine()` — Add a dimension annotation along a `Line2D`.
1446
1894
 
1447
- **Details**
1448
-
1449
1895
  Convenience wrapper around { points from a constrained-sketch `Line2D` entity. All `opts` are forwarded unchanged.
1450
1896
 
1451
- **Example**
1452
-
1453
1897
  ```ts
1454
1898
  const a = point(0, 0);
1455
1899
  const b = point(100, 0);
1456
1900
  dimLine(line(a, b), { label: "Span", offset: -8 });
1457
1901
  ```
1458
1902
 
1459
- `dimLine(l: Line2D, opts?: DimOpts): void`
1903
+ ```ts
1904
+ dimLine(l: Line2D, opts?: DimOpts): void
1905
+ ```
1460
1906
 
1461
1907
  ---
1462
1908
 
@@ -1468,15 +1914,21 @@ Extract 2D geometry from a 3D solid (section, projection).
1468
1914
 
1469
1915
  The result is returned in the face's local 2D coordinate system, making it convenient for offsets, pocket profiles, or follow-up sketch operations driven by an existing face.
1470
1916
 
1471
- `faceProfile(shape: Shape, face: FaceSelector): Sketch`
1917
+ ```ts
1918
+ faceProfile(shape: Shape, face: FaceSelector): Sketch
1919
+ ```
1472
1920
 
1473
1921
  #### `intersectWithPlane()` — Cross-section: slice a 3D shape with a plane and return the intersection as a 2D Sketch.
1474
1922
 
1475
- `intersectWithPlane(shape: Shape, plane: PlaneSpec): Sketch`
1923
+ ```ts
1924
+ intersectWithPlane(shape: Shape, plane: PlaneSpec): Sketch
1925
+ ```
1476
1926
 
1477
1927
  #### `projectToPlane()` — Orthographically project a 3D shape onto a plane and return the silhouette as a 2D Sketch.
1478
1928
 
1479
- `projectToPlane(shape: Shape, plane: PlaneSpec): Sketch`
1929
+ ```ts
1930
+ projectToPlane(shape: Shape, plane: PlaneSpec): Sketch
1931
+ ```
1480
1932
 
1481
1933
  ---
1482
1934
 
@@ -1486,13 +1938,11 @@ Convert geometry to external formats (STL, 3MF, SVG, DXF, G-code, PDF).
1486
1938
 
1487
1939
  #### `bom()` — Register a Bill of Materials entry for report export.
1488
1940
 
1489
- **Details**
1490
-
1491
1941
  BOM entries are accumulated during script execution and exported alongside the model in report views. Rows are grouped by normalized `description + unit`. Pass an explicit `key` to force multiple descriptions to collapse into a single line item.
1492
1942
 
1493
- - `quantity` must be a finite number `>= 0`. A quantity of `0` is silently ignored (useful for conditional scripting with `param()`-driven counts). - `unit` defaults to `"pieces"` when omitted or empty. - The assembly `solved.bom()` / `solved.bomCsv()` API is separate and covers per-part assembly metadata; this function is for free-form purchased-item annotation.
1494
-
1495
- **Example**
1943
+ - `quantity` must be a finite number `>= 0`. A quantity of `0` is silently ignored (useful for conditional scripting with `param()`-driven counts).
1944
+ - `unit` defaults to `"pieces"` when omitted or empty.
1945
+ - The assembly `solved.bom()` / `solved.bomCsv()` API is separate and covers per-part assembly metadata; this function is for free-form purchased-item annotation.
1496
1946
 
1497
1947
  ```ts
1498
1948
  const tubeLen = param("Tube Length", 1200, { min: 300, max: 4000, unit: "mm" });
@@ -1501,27 +1951,54 @@ const boltCount = param("Bolt Count", 16, { min: 0, max: 200, integer: true });
1501
1951
  bom(tubeLen, "iron tube 30 x 20", { unit: "mm" });
1502
1952
  bom(boltCount, "M4 bolt, 16 mm length");
1503
1953
  bom(4, "rubber foot", { key: "foot-rubber" }); // explicit aggregation key
1954
+
1955
+ // Structured metadata for richer reports:
1956
+ bom(tubeLen, "rectangular steel tube", {
1957
+ unit: "mm",
1958
+ material: "steel",
1959
+ section: [30, 20],
1960
+ wall: 3,
1961
+ });
1504
1962
  ```
1505
1963
 
1506
- `bom(quantity: number, description: string, opts?: BomOpts): void`
1964
+ ```ts
1965
+ bom(quantity: number, description: string, opts?: BomOpts): void
1966
+ ```
1507
1967
 
1508
1968
  **`BomOpts`**
1509
- - `unit?: string` — Quantity unit label, e.g. "mm", "pieces", "kg". Default: "pieces"
1510
- - `key?: string` Optional explicit grouping key used during report aggregation.
1969
+
1970
+ | Option | Type | Description |
1971
+ |--------|------|-------------|
1972
+ | `unit?` | `string` | Quantity unit label, e.g. "mm", "pieces", "kg". Default: "pieces" |
1973
+ | `key?` | `string` | Optional explicit grouping key used during report aggregation. |
1974
+ | `material?` | `string` | Material name, e.g. "steel", "birch plywood", "nylon" |
1975
+ | `dimensions?` | `number[]` | Overall dimensions `[width, height]` or `[width, height, thickness]` in the entry's unit |
1976
+ | `section?` | `number[]` | Cross-section dimensions `[w, h]` for tubes and profiles |
1977
+ | `wall?` | `number` | Wall thickness for hollow sections (mm) |
1978
+ | `diameter?` | `number` | Diameter for round stock, bolts, dowels (mm) |
1979
+ | `length?` | `number` | Length for fasteners (mm) |
1980
+ | `process?` | `string` | Manufacturing process, e.g. "laser cut", "CNC", "welded" |
1981
+ | `notes?` | `string` | Free-form notes |
1982
+ | `grain?` | `string` | Wood grain direction, e.g. "long", "cross" |
1511
1983
 
1512
1984
  #### `robotExport()` — Declare that this script should export the assembly as a SDF/URDF robot package.
1513
1985
 
1514
- **Details**
1986
+ Call `robotExport()` alongside your assembly definition. The CLI commands `forgecad export sdf` and `forgecad export urdf` pick up the declaration and produce a robot package with:
1515
1987
 
1516
- Call `robotExport()` alongside your assembly definition. The CLI commands `forgecad export sdf` and `forgecad export urdf` pick up the declaration and produce a robot package with: - Mesh-based inertia tensors (full 6-component, not bounding-box approximations) - Separate collision meshes (convex hull by default — ~50–80% smaller) - Joint mimic elements derived from `addJointCoupling` / `addGearCoupling`
1988
+ - Mesh-based inertia tensors (full 6-component, not bounding-box approximations)
1989
+ - Separate collision meshes (convex hull by default — ~50–80% smaller)
1990
+ - Joint mimic elements derived from `addJointCoupling` / `addGearCoupling`
1517
1991
 
1518
1992
  **Collision mesh modes** (set per-link via `links["PartName"].collision`):
1519
1993
 
1520
1994
  | Mode | Description | Default | |------|-------------|---------| | `'convex'` | Convex hull (separate `_collision.stl`) | Yes | | `'box'` | AABB primitive — fastest physics | | | `'visual'` | Same mesh as visual — exact but slow | | | `'none'` | No collision geometry | |
1521
1995
 
1522
- **Unit conventions:** - Revolute `velocity` is in degrees/second in Forge; exporters convert to rad/s. - Prismatic distances are in mm in Forge; exported in meters. - `massKg` is preferred; `densityKgM3` is used when mass is unknown. - Couplings with multiple terms: only the primary term (largest ratio) maps to `<mimic>` — SDF/URDF support single-leader mimic only. Dropped terms emit a warning.
1996
+ **Unit conventions:**
1523
1997
 
1524
- **Example**
1998
+ - Revolute `velocity` is in degrees/second in Forge; exporters convert to rad/s.
1999
+ - Prismatic distances are in mm in Forge; exported in meters.
2000
+ - `massKg` is preferred; `densityKgM3` is used when mass is unknown.
2001
+ - Couplings with multiple terms: only the primary term (largest ratio) maps to `<mimic>` — SDF/URDF support single-leader mimic only. Dropped terms emit a warning.
1525
2002
 
1526
2003
  ```ts
1527
2004
  const rover = assembly("Scout")
@@ -1557,7 +2034,9 @@ forgecad export sdf model.forge.js # SDF package (Gazebo/Ignition)
1557
2034
  forgecad export urdf model.forge.js # URDF package (ROS/PyBullet/MuJoCo)
1558
2035
  ```
1559
2036
 
1560
- `robotExport(options: RobotExportOptions): CollectedRobotExport`
2037
+ ```ts
2038
+ robotExport(options: RobotExportOptions): CollectedRobotExport
2039
+ ```
1561
2040
 
1562
2041
  **`RobotExportOptions`**: `assembly: Assembly`, `modelName?: string`, `state?: JointState`, `static?: boolean`, `selfCollide?: boolean`, `allowAutoDisable?: boolean`, `links?: Record<string, RobotLinkExportOptions>`, `joints?: Record<string, RobotJointExportOptions>`, `diffDrive?: RobotDiffDrivePluginOptions`, `jointStatePublisher?: RobotJointStatePublisherOptions`, `world?: RobotWorldOptions`
1563
2042
 
@@ -1589,15 +2068,16 @@ forgecad export urdf model.forge.js # URDF package (ROS/PyBullet/MuJoCo)
1589
2068
 
1590
2069
  #### `sheetMetal()` — Create a parametric sheet metal part with flanges, bend allowances, and flat-pattern unfolding.
1591
2070
 
1592
- **Details**
1593
-
1594
2071
  `sheetMetal()` keeps one semantic model and derives both a folded 3D solid and an accurate flat pattern from it. The K-factor bend allowance is applied during unfolding. This is a strict v1 subset — it does not infer sheet metal from arbitrary solids.
1595
2072
 
1596
- **Recommended authoring order:** 1. Define the base panel + thickness + bend parameters. 2. Chain `.flange()` calls for each edge. Validate with `.folded()` and `.flatPattern()` before adding cutouts. 3. Add panel cutouts, then flange cutouts one region at a time. 4. Validate after each new cutout region.
2073
+ **Recommended authoring order:**
1597
2074
 
1598
- **v1 limitations:** one base panel, up to four 90° edge flanges, constant thickness, explicit K-factor, rectangular corner reliefs, planar cutouts only. No hems, jogs, lofted bends, non-90° flanges, or bend-region cutouts.
2075
+ 1. Define the base panel + thickness + bend parameters.
2076
+ 2. Chain `.flange()` calls for each edge. Validate with `.folded()` and `.flatPattern()` before adding cutouts.
2077
+ 3. Add panel cutouts, then flange cutouts one region at a time.
2078
+ 4. Validate after each new cutout region.
1599
2079
 
1600
- **Example**
2080
+ **v1 limitations:** one base panel, up to four 90° edge flanges, constant thickness, explicit K-factor, rectangular corner reliefs, planar cutouts only. No hems, jogs, lofted bends, non-90° flanges, or bend-region cutouts.
1601
2081
 
1602
2082
  ```ts
1603
2083
  const cover = sheetMetal({
@@ -1618,7 +2098,9 @@ const folded = cover.folded();
1618
2098
  const flat = cover.flatPattern();
1619
2099
  ```
1620
2100
 
1621
- `sheetMetal(options: SheetMetalOptions): SheetMetalPart`
2101
+ ```ts
2102
+ sheetMetal(options: SheetMetalOptions): SheetMetalPart
2103
+ ```
1622
2104
 
1623
2105
  **`SheetMetalOptions`**
1624
2106
 
@@ -1634,20 +2116,18 @@ const flat = cover.flatPattern();
1634
2116
 
1635
2117
  #### `sketchToDxf()` — Export a 2D sketch as a DXF string (R12/AC1009 — maximally compatible).
1636
2118
 
1637
- **Details**
1638
-
1639
2119
  For regular sketches, each polygon loop becomes a closed `LWPOLYLINE`. For constrained sketches, exports raw `LINE`, `CIRCLE`, and `ARC` entities from the constraint edge geometry, which preserves internal/shared edges that `toPolygons()` would merge away.
1640
2120
 
1641
2121
  The R12 format is chosen for maximum compatibility with CAM tools, laser-cutter software, and older CAD readers.
1642
2122
 
1643
- **Example**
1644
-
1645
2123
  ```ts
1646
2124
  const s = rect(100, 60);
1647
2125
  const dxf = sketchToDxf(s, { layer: 'cut' });
1648
2126
  ```
1649
2127
 
1650
- `sketchToDxf(sketch: Sketch, options?: SketchDxfOptions): string`
2128
+ ```ts
2129
+ sketchToDxf(sketch: Sketch, options?: SketchDxfOptions): string
2130
+ ```
1651
2131
 
1652
2132
  **`SketchDxfOptions`**
1653
2133
  - `layer?: string` — DXF layer name. Default: "0"
@@ -1655,20 +2135,18 @@ const dxf = sketchToDxf(s, { layer: 'cut' });
1655
2135
 
1656
2136
  #### `sketchToSvg()` — Export a 2D sketch as an SVG string.
1657
2137
 
1658
- **Details**
1659
-
1660
2138
  For regular sketches, exports filled polygon regions. For constrained sketches, exports raw edge geometry (LINE, ARC, CIRCLE) which preserves internal/shared edges that `toPolygons()` would merge away.
1661
2139
 
1662
2140
  The SVG uses the sketch's native coordinate system (Y-up) with a CSS transform that flips Y so the output renders correctly in SVG's Y-down space. Coordinates are in sketch units (typically mm).
1663
2141
 
1664
- **Example**
1665
-
1666
2142
  ```ts
1667
2143
  const s = rect(100, 60);
1668
2144
  const svg = sketchToSvg(s, { stroke: '#333', strokeWidth: 0.8 });
1669
2145
  ```
1670
2146
 
1671
- `sketchToSvg(sketch: Sketch, options?: SketchSvgOptions): string`
2147
+ ```ts
2148
+ sketchToSvg(sketch: Sketch, options?: SketchSvgOptions): string
2149
+ ```
1672
2150
 
1673
2151
  **`SketchSvgOptions`**
1674
2152
 
@@ -1686,9 +2164,121 @@ const svg = sketchToSvg(s, { stroke: '#333', strokeWidth: 0.8 });
1686
2164
 
1687
2165
  Control viewport appearance and debugging aids.
1688
2166
 
1689
- #### `explodeView()` — Configure how the viewport explode slider offsets returned objects.
2167
+ #### `verify.that()` — Custom predicate check.
2168
+
2169
+ ```ts
2170
+ verify.that(label: string, check: () => boolean, message?: string): void
2171
+ ```
2172
+
2173
+ #### `verify.equal()` — Check that two numbers are approximately equal (within tolerance).
2174
+
2175
+ ```ts
2176
+ verify.equal(label: string, actual: number, expected: number, tolerance?: number, message?: string): void
2177
+ ```
1690
2178
 
1691
- **Details**
2179
+ #### `verify.notEqual()` — Check that two numbers are NOT equal (differ by more than tolerance).
2180
+
2181
+ ```ts
2182
+ verify.notEqual(label: string, actual: number, unexpected: number, tolerance?: number, message?: string): void
2183
+ ```
2184
+
2185
+ #### `verify.greaterThan()` — Check that actual > min.
2186
+
2187
+ ```ts
2188
+ verify.greaterThan(label: string, actual: number, min: number, message?: string): void
2189
+ ```
2190
+
2191
+ #### `verify.lessThan()` — Check that actual < max.
2192
+
2193
+ ```ts
2194
+ verify.lessThan(label: string, actual: number, max: number, message?: string): void
2195
+ ```
2196
+
2197
+ #### `verify.inRange()` — Check that min <= actual <= max.
2198
+
2199
+ ```ts
2200
+ verify.inRange(label: string, actual: number, min: number, max: number, message?: string): void
2201
+ ```
2202
+
2203
+ #### `verify.centersCoincide()` — Check that the bounding-box centers of two shapes coincide within tolerance (mm).
2204
+
2205
+ ```ts
2206
+ verify.centersCoincide(label: string, a: ShapeLike, b: ShapeLike, tolerance?: number): void
2207
+ ```
2208
+
2209
+ #### `verify.notColliding()` — Check that two shapes do not collide (minGap > 0).
2210
+
2211
+ ```ts
2212
+ verify.notColliding(label: string, a: ShapeLike, b: ShapeLike, searchLength?: number): void
2213
+ ```
2214
+
2215
+ #### `verify.minClearance()` — Check that a minimum clearance gap exists between two shapes.
2216
+
2217
+ ```ts
2218
+ verify.minClearance(label: string, a: ShapeLike, b: ShapeLike, minGap: number, searchLength?: number): void
2219
+ ```
2220
+
2221
+ #### `verify.parallel()` — Check that two face normals are parallel (within toleranceDeg degrees).
2222
+
2223
+ ```ts
2224
+ verify.parallel(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number): void
2225
+ ```
2226
+
2227
+ #### `verify.perpendicular()` — Check that two face normals are perpendicular (within toleranceDeg degrees).
2228
+
2229
+ ```ts
2230
+ verify.perpendicular(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number): void
2231
+ ```
2232
+
2233
+ #### `verify.coplanar()` — Check that a face is coplanar with (same plane as) another face, meaning they are parallel AND their centers lie on the same plane.
2234
+
2235
+ ```ts
2236
+ verify.coplanar(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number, toleranceMm?: number): void
2237
+ ```
2238
+
2239
+ #### `verify.faceAt()` — Check that a face center lies at a specific position (within toleranceMm).
2240
+
2241
+ ```ts
2242
+ verify.faceAt(label: string, face: FaceRefLike, expectedPos: [ number
2243
+ ```
2244
+
2245
+ #### `verify.sameDirection()` — Check that two face normals point in the same direction (not antiparallel). Stricter than parallel — both |angle| AND sign must match.
2246
+
2247
+ ```ts
2248
+ verify.sameDirection(label: string, faceA: FaceRefLike, faceB: FaceRefLike, toleranceDeg?: number): void
2249
+ ```
2250
+
2251
+ #### `verify.isEmpty()` — Check that a shape is empty.
2252
+
2253
+ ```ts
2254
+ verify.isEmpty(label: string, shape: ShapeLike, message?: string): void
2255
+ ```
2256
+
2257
+ #### `verify.notEmpty()` — Check that a shape is NOT empty.
2258
+
2259
+ ```ts
2260
+ verify.notEmpty(label: string, shape: ShapeLike, message?: string): void
2261
+ ```
2262
+
2263
+ #### `verify.volumeApprox()` — Check that a shape's volume is approximately equal to expected (mm³).
2264
+
2265
+ ```ts
2266
+ verify.volumeApprox(label: string, shape: ShapeLike, expected: number, tolerance?: number): void
2267
+ ```
2268
+
2269
+ #### `verify.areaApprox()` — Check that a shape's surface area is approximately equal to expected (mm²).
2270
+
2271
+ ```ts
2272
+ verify.areaApprox(label: string, shape: ShapeLike, expected: number, tolerance?: number): void
2273
+ ```
2274
+
2275
+ #### `verify.boundingBoxSize()` — Check that a shape's bounding box has approximately the given size.
2276
+
2277
+ ```ts
2278
+ verify.boundingBoxSize(label: string, shape: ShapeLike, expectedSize: [ number
2279
+ ```
2280
+
2281
+ #### `explodeView()` — Configure how the viewport explode slider offsets returned objects.
1692
2282
 
1693
2283
  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.
1694
2284
 
@@ -1696,8 +2286,6 @@ Multiple calls merge — later values override earlier ones on a per-key basis.
1696
2286
 
1697
2287
  For programmatic explode applied before returning (without the slider), use `lib.explode()` instead.
1698
2288
 
1699
- **Example**
1700
-
1701
2289
  ```js
1702
2290
  explodeView({
1703
2291
  amountScale: 1.2,
@@ -1707,7 +2295,9 @@ explodeView({
1707
2295
  });
1708
2296
  ```
1709
2297
 
1710
- `explodeView(options?: ExplodeViewOptions): void`
2298
+ ```ts
2299
+ explodeView(options?: ExplodeViewOptions): void
2300
+ ```
1711
2301
 
1712
2302
  **`ExplodeViewOptions`**
1713
2303
 
@@ -1728,15 +2318,42 @@ explodeView({
1728
2318
 
1729
2319
  #### `cutPlane()`
1730
2320
 
1731
- `cutPlane(name: string, normal: [ number, number, number ], options?: CutPlaneOptions): void`
2321
+ ```ts
2322
+ cutPlane(name: string, normal: [ number, number, number ], options?: CutPlaneOptions): void
2323
+ ```
1732
2324
 
1733
2325
  **`CutPlaneOptions`**
1734
2326
  - `offset?: number` — Optional offset along the plane normal (primarily for object-form overload).
1735
2327
  - `exclude?: CutPlaneExcludeInput` — Object names to keep uncut for this plane.
1736
2328
 
1737
- #### `scene()` — Configure the scene environment for the current script execution.
2329
+ #### `mock()` — Register a mock (context) object for visualization and collision checking.
2330
+
2331
+ Mock objects appear in the viewport and spatial analysis when you run a file directly, but are excluded when the file is imported via `require()`. This lets you model the surrounding context — walls, bolts, mating parts — without polluting the module's exports.
2332
+
2333
+ The shape is returned unchanged, so you can reference it for alignment, dimensioning, and `verify` checks.
2334
+
2335
+ Mock objects participate in `forgecad run` collision detection and spatial analysis. Their names appear with a `(mock)` suffix in reports.
2336
+
2337
+ In the viewport, mock objects render at reduced opacity so they are visually distinct from real geometry.
2338
+
2339
+ ```ts
2340
+ // bracket.forge.js
2341
+ const wall = mock(box(100, 200, 10).translate(0, 0, -5), "wall");
2342
+ const bolt = mock(cylinder(3, 15).translate(10, 15, 0), "bolt");
2343
+
2344
+ const bracket = box(20, 30, 5);
2345
+ verify.notColliding("bracket vs wall", bracket, wall);
2346
+
2347
+ return bracket;
2348
+ // When imported: only bracket is exported
2349
+ // When run directly: bracket + wall + bolt all visible
2350
+ ```
1738
2351
 
1739
- **Details**
2352
+ ```ts
2353
+ mock<T extends Shape>(shape: T, name?: string): T
2354
+ ```
2355
+
2356
+ #### `scene()` — Configure the scene environment for the current script execution.
1740
2357
 
1741
2358
  Controls camera position, lighting rig, background color or gradient, atmospheric fog, environment maps, post-processing effects, and capture parameters for the `forgecad capture` command. Multiple calls merge — later values override earlier ones on a per-key basis, so you can split configuration across multiple `scene()` calls.
1742
2359
 
@@ -1748,8 +2365,6 @@ Post-processing effects (`bloom`, `vignette`, `grain`) work in the browser viewp
1748
2365
 
1749
2366
  All numeric values accept `param()` expressions.
1750
2367
 
1751
- **Example**
1752
-
1753
2368
  ```js
1754
2369
  scene({
1755
2370
  background: { top: '#000814', bottom: '#001d3d' },
@@ -1771,7 +2386,9 @@ scene({
1771
2386
  });
1772
2387
  ```
1773
2388
 
1774
- `scene(options: SceneOptions): void`
2389
+ ```ts
2390
+ scene(options: SceneOptions): void
2391
+ ```
1775
2392
 
1776
2393
  **`SceneOptions`**
1777
2394
 
@@ -1840,18 +2457,16 @@ scene({
1840
2457
 
1841
2458
  Shows each user-authored label name in the viewport for visual debugging. Returns the shape unchanged for chaining: `return showLabels(myShape)`.
1842
2459
 
1843
- `showLabels(shape: Shape): Shape`
2460
+ ```ts
2461
+ showLabels(shape: Shape): Shape
2462
+ ```
1844
2463
 
1845
2464
  #### `viewConfig()` — Configure viewport helper visuals for the current script execution.
1846
2465
 
1847
- **Details**
1848
-
1849
2466
  Controls renderer-only overlays that appear in the viewport but are not part of the geometry. Currently supports the joint overlay that renders axis arrows and arc indicators when `jointsView` is active. Multiple calls merge — later values override earlier ones per key.
1850
2467
 
1851
2468
  This does **not** trigger a geometry recompute; it only affects the visual helpers drawn on top of the 3D scene.
1852
2469
 
1853
- **Example**
1854
-
1855
2470
  ```js
1856
2471
  viewConfig({
1857
2472
  jointOverlay: {
@@ -1863,7 +2478,9 @@ viewConfig({
1863
2478
  });
1864
2479
  ```
1865
2480
 
1866
- `viewConfig(options?: ViewConfigOptions): void`
2481
+ ```ts
2482
+ viewConfig(options?: ViewConfigOptions): void
2483
+ ```
1867
2484
 
1868
2485
  `ViewConfigOptions`: `{ jointOverlay?: JointOverlayViewConfigOptions }`
1869
2486
 
@@ -1871,16 +2488,12 @@ viewConfig({
1871
2488
 
1872
2489
  #### `spec()` — Create a named, reusable bundle of verification checks.
1873
2490
 
1874
- **Details**
1875
-
1876
2491
  A spec groups related `verify.*` calls under a collapsible header in the Checks panel. This makes large check suites scannable. Specs can be applied to multiple shapes and can check relationships between parts.
1877
2492
 
1878
2493
  Specs can be defined in separate `.forge.js` files and imported via `require()` to share them across models.
1879
2494
 
1880
2495
  `spec.check()` returns a `SpecResult` — you can inspect it programmatically or ignore the return value and let the Checks panel show results.
1881
2496
 
1882
- **Example**
1883
-
1884
2497
  ```ts
1885
2498
  const printable = spec("Fits printer bed", (shape) => {
1886
2499
  verify.notEmpty("Has geometry", shape);
@@ -1903,7 +2516,9 @@ fitSpec.check(bracket, standoff);
1903
2516
 
1904
2517
  **Spec-first workflow:** Write specs before building geometry. Checks go from red to green as you build — effectively TDD for CAD.
1905
2518
 
1906
- `spec(name: string, checkFn: (...args: any[]) => void): Spec`
2519
+ ```ts
2520
+ spec(name: string, checkFn: (...args: any[]) => void): Spec
2521
+ ```
1907
2522
 
1908
2523
  **`Spec`**
1909
2524
  - `name: string` — The display name of this spec
@@ -1920,11 +2535,19 @@ Unlike union(), colors and individual identities are preserved. Children can be
1920
2535
 
1921
2536
  **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.
1922
2537
 
1923
- // BAD — every sub-part repeats the parent's global offset const unitX = 0, unitY = -18, unitZ = 70; const body = roundedBox(100, 20, 32, 4).translate(unitX, unitY, unitZ); const panel = box(98, 2, 18).translate(unitX, unitY - 12, unitZ + 4); const louver = box(88, 2, 6).translate(unitX, unitY - 14, unitZ - 11);
2538
+ ```js
2539
+ // BAD — every sub-part repeats the parent's global offset
2540
+ const unitX = 0, unitY = -18, unitZ = 70;
2541
+ const body = roundedBox(100, 20, 32, 4).translate(unitX, unitY, unitZ);
2542
+ const panel = box(98, 2, 18).translate(unitX, unitY - 12, unitZ + 4);
2543
+ const louver = box(88, 2, 6).translate(unitX, unitY - 14, unitZ - 11);
2544
+ ```
1924
2545
 
1925
2546
  // 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);
1926
2547
 
1927
- `group(...items: GroupInput[]): ShapeGroup`
2548
+ ```ts
2549
+ group(...items: GroupInput[]): ShapeGroup
2550
+ ```
1928
2551
 
1929
2552
  ---
1930
2553
 
@@ -1932,6 +2555,46 @@ Unlike union(), colors and individual identities are preserved. Children can be
1932
2555
 
1933
2556
  Pre-built parametric parts accessible via `lib.*`.
1934
2557
 
1935
- *No free functions see class methods (Shape, Sketch, ConstrainedSketchBuilder).*
2558
+ #### `Wood.board()`Create a wood board with metadata for manufacturing.
2559
+
2560
+ The board is a box(width, height, thickness) centered on XY, base at Z=0. Width along X, height along Y, thickness along Z (0 to thickness).
2561
+
2562
+ ```ts
2563
+ Wood.readonly board: (width: number, height: number, thickness: number, opts?: WoodBoardOptions) => WoodBoard
2564
+ ```
2565
+
2566
+ **`WoodBoardOptions`**
2567
+
2568
+ | Option | Type | Description |
2569
+ |--------|------|-------------|
2570
+ | `species?` | `string` | Wood species, e.g. "birch", "oak", "plywood". Default: "wood" |
2571
+ | `material?` | `string` | Material description for BOM, e.g. "birch plywood". Default: species value |
2572
+ | `grain?` | `string` | Grain direction: "long" (along width) or "cross" (along height). Default: "long" |
2573
+ | `color?` | `string` | Color hex string for visualization. Default: "#d2b48c" (tan/wood) |
2574
+ | `autoBom?` | `boolean` | If false, skip automatic BOM registration. Default: true |
2575
+
2576
+ #### `Wood.dado()` — Cut a dado (channel) across the face of a host board for a guest board to sit in.
2577
+
2578
+ Mutates `host.shape` by subtracting a rectangular channel.
2579
+
2580
+ ```ts
2581
+ Wood.readonly dado: typeof dado
2582
+ ```
2583
+
2584
+ #### `Wood.rabbet()` — Cut a rabbet (L-shaped step) along an edge of a board.
2585
+
2586
+ Mutates `board.shape` by subtracting a step from the specified edge.
2587
+
2588
+ ```ts
2589
+ Wood.readonly rabbet: typeof rabbet
2590
+ ```
2591
+
2592
+ #### `Wood.mortiseAndTenon()` — Cut a mortise in one board and shape a tenon on another.
2593
+
2594
+ Mutates both boards — subtracts the mortise pocket and removes shoulder material to form the tenon.
2595
+
2596
+ ```ts
2597
+ Wood.readonly mortiseAndTenon: typeof mortiseAndTenon
2598
+ ```
1936
2599
 
1937
2600
  ---