forgecad 0.10.5 → 0.11.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 (104) hide show
  1. package/dist/assets/{AdminPage-raksfnNA.js → AdminPage-B1nIvqLS.js} +1 -1
  2. package/dist/assets/{BenchmarkPage-DP3RxhPs.js → BenchmarkPage-YZJbw5nd.js} +1 -1
  3. package/dist/assets/{BlogPage-D7Dos-vl.js → BlogPage-DIWRApKS.js} +1 -1
  4. package/dist/assets/{DocsPage-DO1kvBns.js → DocsPage-ClL6X1hR.js} +2 -22
  5. package/dist/assets/{EditorApp-DQJmcmRT.js → EditorApp-CYBDvSyT.js} +575 -119
  6. package/dist/assets/{EmbedViewer-DFDUhOma.js → EmbedViewer-Dmfu_LIw.js} +2 -2
  7. package/dist/assets/{LandingPageProofDriven-DbE_tp8-.js → LandingPageProofDriven-XYTiYxfM.js} +1 -1
  8. package/dist/assets/{LegalPage-CominSso.js → LegalPage-D5Z3CscF.js} +1 -1
  9. package/dist/assets/{PricingPage-CcVIN9yj.js → PricingPage-BP4lIGio.js} +1 -1
  10. package/dist/assets/{SettingsPage-DLWcP289.js → SettingsPage-D3bcPBsC.js} +1 -1
  11. package/dist/assets/{app-xW3hOdq9.js → app-BKjogwIZ.js} +2192 -231
  12. package/dist/assets/{backendInit-mDHk97u7.js → backendInit-6a9-ilom.js} +76448 -75066
  13. package/dist/assets/cli/{render--SIU27W_.js → render-CMNudGb0.js} +3 -3
  14. package/dist/assets/{constructionHistoryWorker-uEe_Q7Kg.js → constructionHistoryWorker-BuZgc606.js} +6985 -6706
  15. package/dist/assets/{evalWorker-BqyDHDcI.js → evalWorker-DQ82ueGu.js} +40862 -39497
  16. package/dist/assets/{inspectWorker-UXMxlcR8.js → inspectWorker-Cuby2qfT.js} +2078 -478
  17. package/dist/assets/{jointPose-bYMlwU3v.js → jointPose-CFql5I-u.js} +1 -1
  18. package/dist/assets/{manifold-CyOV5B9S.js → manifold-02pmr7O7.js} +2 -2
  19. package/dist/assets/{manifold-BR7UYI4P.js → manifold-C6KU0oII.js} +1 -1
  20. package/dist/assets/{manifold-D4d5NQst.js → manifold-P1yF3GKn.js} +1 -1
  21. package/dist/assets/{reportWorker-DsaICZsn.js → reportWorker-kg065BVL.js} +85183 -78309
  22. package/dist/cli/render.html +1 -1
  23. package/dist/docs/index.html +2 -2
  24. package/dist/docs-raw/AI/usage.md +6 -8
  25. package/dist/docs-raw/CLI.md +10 -10
  26. package/dist/docs-raw/component-model.md +28 -9
  27. package/dist/docs-raw/generated/concepts.md +13 -4
  28. package/dist/docs-raw/generated/core.md +244 -56
  29. package/dist/docs-raw/generated/curves.md +13 -0
  30. package/dist/docs-raw/generated/runtime-names.md +2 -2
  31. package/dist/docs-raw/guides/inspection-bundles.md +1 -1
  32. package/dist/docs-raw/guides/structural-fea.md +11 -0
  33. package/dist/docs-raw/skills/forgecad-build-model.md +70 -147
  34. package/dist/docs-raw/skills/forgecad-image-prompt.md +1 -1
  35. package/dist/docs-raw/skills/forgecad-project-sync.md +3 -3
  36. package/dist/docs-raw/skills/forgecad-reconstruct-cad-file.md +2 -2
  37. package/dist/docs-raw/skills/forgecad-reconstruct-from-images.md +4 -5
  38. package/dist/docs-raw/skills/forgecad.md +3 -1
  39. package/dist/docs-raw/skills/index.md +1 -5
  40. package/dist/docs-raw/welcome.md +3 -4
  41. package/dist/index.html +1 -1
  42. package/dist/llms.txt +1 -2
  43. package/dist/sitemap.xml +15 -15
  44. package/dist-cli/{check-compiler-7YAHVXYM.js → check-compiler-UJWUEIDC.js} +1 -1
  45. package/dist-cli/{check-query-propagation-ZRR6IOJW.js → check-query-propagation-O2EPDJSY.js} +1 -1
  46. package/dist-cli/{chunk-VNM67DIV.js → chunk-MNDROM7T.js} +77145 -75767
  47. package/dist-cli/forgecad.js +1145 -441
  48. package/dist-skill/CONTEXT.md +429 -64
  49. package/dist-skill/SKILL.md +3 -1
  50. package/dist-skill/docs/API/core/concepts.md +31 -4
  51. package/dist-skill/docs/CLI.md +10 -10
  52. package/dist-skill/docs/generated/core.md +240 -57
  53. package/dist-skill/docs/generated/curves.md +13 -0
  54. package/dist-skill/docs/generated/runtime-names.md +2 -2
  55. package/dist-skill/docs/guides/inspection-bundles.md +1 -1
  56. package/dist-skill/docs/guides/manual-parameters.md +130 -0
  57. package/dist-skill/docs/guides/structural-fea.md +11 -0
  58. package/dist-skill/library/README.md +0 -4
  59. package/dist-skill/library/forgecad-build-model/SKILL.md +57 -150
  60. package/dist-skill/library/forgecad-build-model/references/inspection-feedback.md +58 -0
  61. package/dist-skill/library/forgecad-build-model/references/module-contracts.md +53 -0
  62. package/dist-skill/library/forgecad-build-model/references/parameter-controls.md +22 -0
  63. package/dist-skill/library/forgecad-build-model/references/readiness-review.md +43 -0
  64. package/dist-skill/library/forgecad-build-model/references/simulation-feedback.md +49 -0
  65. package/dist-skill/library/forgecad-build-model/references/stage-1-design-intent.md +21 -0
  66. package/dist-skill/library/forgecad-build-model/references/stage-2-architecture-plan.md +23 -0
  67. package/dist-skill/library/forgecad-build-model/references/stage-3-build-slices.md +39 -0
  68. package/dist-skill/library/forgecad-build-model/references/stage-4-feedback-iteration.md +24 -0
  69. package/dist-skill/library/forgecad-build-model/references/stage-5-readiness-package.md +34 -0
  70. package/dist-skill/library/forgecad-image-prompt/SKILL.md +1 -1
  71. package/dist-skill/library/forgecad-project-sync/SKILL.md +3 -3
  72. package/dist-skill/library/forgecad-reconstruct-cad-file/SKILL.md +2 -2
  73. package/dist-skill/library/forgecad-reconstruct-from-images/SKILL.md +4 -5
  74. package/dist-skill/website/skills/forgecad-build-model.md +70 -147
  75. package/dist-skill/website/skills/forgecad-image-prompt.md +1 -1
  76. package/dist-skill/website/skills/forgecad-project-sync.md +3 -3
  77. package/dist-skill/website/skills/forgecad-reconstruct-cad-file.md +2 -2
  78. package/dist-skill/website/skills/forgecad-reconstruct-from-images.md +4 -5
  79. package/dist-skill/website/skills/forgecad.md +3 -1
  80. package/dist-skill/website/skills/index.md +1 -5
  81. package/examples/api/param-path2d.forge.js +65 -0
  82. package/examples/api/param-placement2d.forge.js +80 -0
  83. package/examples/api/param-spline2d-g-continuity.forge.js +57 -0
  84. package/examples/api/spoon-full-tang-handle.forge.js +57 -17
  85. package/examples/api/surface-variable-thickness-panel.forge.js +62 -0
  86. package/examples/mechanical/airplane-propeller.forge.js +81 -28
  87. package/package.json +2 -2
  88. package/dist/docs-raw/skills/forgecad-design-spec.md +0 -145
  89. package/dist/docs-raw/skills/forgecad-grade-model.md +0 -84
  90. package/dist/docs-raw/skills/forgecad-inspect-model.md +0 -80
  91. package/dist/docs-raw/skills/forgecad-verify-mujoco.md +0 -78
  92. package/dist-skill/library/forgecad-design-spec/SKILL.md +0 -132
  93. package/dist-skill/library/forgecad-design-spec/references/default-profiles.md +0 -99
  94. package/dist-skill/library/forgecad-design-spec/references/master-prompt.md +0 -73
  95. package/dist-skill/library/forgecad-grade-model/SKILL.md +0 -72
  96. package/dist-skill/library/forgecad-grade-model/agents/openai.yaml +0 -4
  97. package/dist-skill/library/forgecad-inspect-model/SKILL.md +0 -68
  98. package/dist-skill/library/forgecad-verify-mujoco/SKILL.md +0 -66
  99. package/dist-skill/website/skills/forgecad-design-spec.md +0 -145
  100. package/dist-skill/website/skills/forgecad-grade-model.md +0 -84
  101. package/dist-skill/website/skills/forgecad-inspect-model.md +0 -80
  102. package/dist-skill/website/skills/forgecad-verify-mujoco.md +0 -78
  103. /package/dist-skill/library/{forgecad-verify-mujoco → forgecad-build-model}/scripts/mujoco_verify.py +0 -0
  104. /package/dist-skill/library/{forgecad-inspect-model → forgecad-build-model/scripts}/summarize_manifest.py +0 -0
@@ -28,7 +28,8 @@ Author or modify ForgeCAD models, sketches, assemblies, and CLI workflows. Prefe
28
28
 
29
29
  ### Import and composition
30
30
 
31
- - Always include the extension in relative imports: `require("./file.forge.js", { Param: value })` for model files, `require("./helpers.js")` for plain helper modules. Extensionless imports such as `require("./file")` do not resolve; ForgeCAD resolves project imports by exact path.
31
+ - Always include the extension in relative imports: `require("./file.forge.js")` for model files, `require("./helpers.js")` for plain helper modules. Extensionless imports such as `require("./file")` do not resolve; ForgeCAD resolves project imports by exact path.
32
+ - Reusable `.forge.js` part files should return builder functions such as `return { buildPart }`; direct-run preview params belong inside `if (require.main === module)`.
32
33
  - ForgeCAD APIs are injected globals in `.forge.js` files. Use `bom()`, `box()`, `scene()`, `Shape`, etc. directly; never destructure those names from helpers (`const { bom } = require("./bom.js")`). Import helper files under a project-specific name such as `const bomHelpers = require("./bom.js")`.
33
34
  - For static multi-part models, connectors + `matchTo()` are the default way to assemble touching parts.
34
35
  - Top-level scripts can return `Assembly` or `SolvedAssembly` directly. Do not call `.toGroup()` just to render an assembly; use it only when you need `ShapeGroup` composition, transforms, or named-child lookup.
@@ -77,10 +78,10 @@ A `.forge.js` script is plain JavaScript that returns geometry. The entire forge
77
78
 
78
79
  All geometry operations are **immutable** — shapes, sketches, groups, assemblies, and boards return new values, never mutate in place.
79
80
 
80
- A script must return one of three shapes:
81
+ A script should return one of three shapes:
81
82
 
82
83
  1. **A single renderable** — `Shape`, `Sketch`, `ShapeGroup`, `Assembly`, `SolvedAssembly`, or `SdfShape`.
83
- 2. **An array** of renderables or named descriptors `{ name, tags?, shape | sketch | group, color? }`:
84
+ 2. **An array** of renderables or named descriptors `{ name, tags?, shape | sketch | group, color? }`, usually for direct-run previews and multi-object display:
84
85
 
85
86
  ```javascript
86
87
  return [
@@ -89,11 +90,38 @@ A script must return one of three shapes:
89
90
  ];
90
91
  ```
91
92
 
92
- 3. **A metadata object** — a plain object whose renderable values are rendered and whose non-renderable values (numbers, hole tables, builder functions) are silently skipped at render but flow to importers via `require()`. Each key becomes a named group, so don't pile independent parts into one array key (`{ parts: [a, b, c] }`) — the integrity gate reads that as a single fragmented part. Give each part its own key (`{ collar12, collar16, plug }`) or use named descriptors (form 2).
93
+ 3. **A module interface object** — usually builder functions, optionally a built shape plus interface data:
94
+
95
+ ```javascript
96
+ return { buildBracket };
97
+ // or, when the file's useful output is already built:
98
+ return { shape, connectors, boltPattern };
99
+ ```
100
+
101
+ For reusable part files, prefer a builder export and keep direct-run preview controls inside the entry guard:
102
+
103
+ ```javascript
104
+ function buildThing(props) {
105
+ return box(props.width, props.depth, props.height);
106
+ }
107
+
108
+ if (require.main === module) {
109
+ const previewProps = {
110
+ width: param("Width", 80),
111
+ depth: param("Depth", 40),
112
+ height: param("Height", 12),
113
+ };
114
+ return buildThing(previewProps);
115
+ }
116
+
117
+ return { buildThing };
118
+ ```
119
+
120
+ When a plain object is returned directly, renderable values are shown in the viewport and non-renderable values are available to importers through `require()`.
93
121
 
94
122
  Return an unsolved `Assembly` directly — ForgeCAD solves it at default joint values for display. Use `assembly.solve(state)` for a specific pose. Never call `.toGroup()` just to make an assembly render; use it only when you need `ShapeGroup` composition or named-child lookup.
95
123
 
96
- For multi-file projects import path rules, the metadata pattern, and Forge-aware builder modules see the [`require()` docs](../../generated/core.md).
124
+ For multi-file projects, import path rules, and reusable builder modules, see the [`require()` docs](../../generated/core.md).
97
125
 
98
126
  ## Identity
99
127
 
@@ -145,14 +173,144 @@ rect, Rectangle2D, roundedRect, Route3D, scene, Sculpt, sdf, SdfShape
145
173
  selectEdge, selectEdges, self, setActiveBackend, setImmediate, setInterval, setTimeout, Shape
146
174
  ShapeGroup, sheetMetal, SheetMetalPart, sheetStock, Sim, Sketch, sketchToDxf, sketchToSvg
147
175
  slot, SolvedAssembly, spec, sphere, spline2d, stroke, Surface, SurfaceBody
148
- SurfaceMembers, sweep, text2d, textWidth, torus, toShape, Transform, union
149
- union2d, variableSweep, verify, Viewport, window, Wood, Wrap
176
+ SurfaceMembers, sweep, text2d, textWidth, Thickness, torus, toShape, Transform
177
+ union, union2d, variableSweep, verify, Viewport, window, Wood, Wrap
150
178
  ```
151
179
 
152
180
  `showLabels` is also a runtime global, but it is not part of the top-level collision check. Avoid reusing it unless you intentionally want a local value with that name.
153
181
 
154
182
  ---
155
183
 
184
+ <!-- guides/manual-parameters.md -->
185
+
186
+ # Manual Parameter Sheets
187
+
188
+ Manual parameters are constrained design data: the script owns the structure and
189
+ the user edits only the declared values. They are for cases where a numeric
190
+ slider is the wrong shape of input.
191
+
192
+ ## Contents
193
+
194
+ - Decision Ladder
195
+ - Path2D
196
+ - Spline2D
197
+ - Placement2D
198
+ - Spatial Anchors
199
+ - Saving
200
+
201
+ ## Decision Ladder
202
+
203
+ Use the smallest parameter type that matches the design intent:
204
+
205
+ | Intent | Use |
206
+ | --- | --- |
207
+ | One scalar dimension, count, angle, or toggle | `Param.number()`, `Param.bool()`, `Param.choice()` |
208
+ | A repeated table of named scalar fields | `Param.list()` |
209
+ | A hand-shaped polygon, section outline, or open centerline | `Param.path2d()` |
210
+ | A smooth hand-shaped curve with tangent/curvature intent | `Param.spline2d()` |
211
+ | Named semantic blocks arranged in zones | `Param.placement2d()` |
212
+
213
+ Do not use `path2d` or `spline2d` as a generic table. Use them when dragging
214
+ points is meaningfully better than editing numbers.
215
+
216
+ ## Path2D
217
+
218
+ `Param.path2d(name, points, opts)` returns a `Path2DParamValue`.
219
+
220
+ - Closed paths are filled profile intent: call `.toSketch()`.
221
+ - Open paths are centerline intent: call `.toStroke(width)`.
222
+ - `x` and `y` ranges define the initial editor frame, not hard movement limits.
223
+ - Override keys are `Name[0].x`, `Name[0].y`, and `Name.__count__`.
224
+
225
+ ```javascript
226
+ const outline = Param.path2d('Outline', [[-30, -15], [30, -15], [24, 18], [-28, 16]], {
227
+ closed: true,
228
+ minPoints: 3,
229
+ maxPoints: 12,
230
+ unit: 'mm',
231
+ anchor: Param.anchor.sheetOnXY([0, 0, 8], { label: 'Top outline' }),
232
+ });
233
+
234
+ return outline.toSketch().extrude(4);
235
+ ```
236
+
237
+ ## Spline2D
238
+
239
+ `Param.spline2d(name, points, opts)` returns a `Spline2DParamValue`.
240
+
241
+ - Each point has `g`: `G0` for a hard break, `G1` for tangent smooth, `G2` for
242
+ curvature smooth.
243
+ - Use `.toCurveOnXY()`, `.toCurveOnXZ()`, or `.toCurveOnYZ()` for sweeps and
244
+ curve consumers.
245
+ - Use `.toPathOnXY()`, `.toPathOnXZ()`, or `.toPathOnYZ()` when an API expects
246
+ sampled points.
247
+ - Override keys are `Name[0].x`, `Name[0].y`, `Name[0].g`, and `Name.__count__`.
248
+
249
+ ```javascript
250
+ const sideProfile = Param.spline2d('Side Profile', [
251
+ { x: 0, y: 0, g: 'G2' },
252
+ { x: 35, y: 8, g: 'G2' },
253
+ { x: 70, y: 3, g: 'G1' },
254
+ ], {
255
+ unit: 'mm',
256
+ anchor: Param.anchor.sheetOnXZ([0, -20, 0], { label: 'Side profile' }),
257
+ });
258
+
259
+ const rail = sideProfile.toCurveOnXZ();
260
+ ```
261
+
262
+ ## Placement2D
263
+
264
+ `Param.placement2d(name, spec)` returns a `Placement2DParamValue`.
265
+
266
+ - The script declares stable item IDs, footprints, optional zones, and rules.
267
+ - The user moves named items; the model decides what each item creates.
268
+ - Use `.item(id)` for one placement or `.positions()` for keyed lookup.
269
+ - Override keys are item based: `Layout.battery.x`, `Layout.battery.y`,
270
+ `Layout.battery.angle`, and `Layout.battery.zone`.
271
+
272
+ ```javascript
273
+ const layout = Param.placement2d('Internal Layout', {
274
+ frame: { size: [120, 80] },
275
+ zones: [{ id: 'electronics', size: [70, 70], center: [-20, 0] }],
276
+ items: [
277
+ { id: 'battery', footprint: { type: 'rect', size: [42, 24] }, zone: 'electronics', at: [-25, 0] },
278
+ { id: 'speaker', footprint: { type: 'circle', radius: 12 }, at: [32, 8] },
279
+ ],
280
+ rules: { bounds: 'prevent', collisions: 'warn', snap: 1 },
281
+ anchor: Param.anchor.sheetOnXY([0, 0, 12], { label: 'Internal layout' }),
282
+ });
283
+
284
+ const battery = layout.item('battery');
285
+ const batteryBlock = box(42, 24, 6).translate(battery.x, battery.y, 3);
286
+ ```
287
+
288
+ ## Spatial Anchors
289
+
290
+ Every parameter type can carry optional viewport metadata through `anchor`.
291
+ Anchors do not change geometry.
292
+
293
+ - `Param.anchor.point([x, y, z])` creates a clickable pin for scalar, string,
294
+ list, boolean, or choice parameters.
295
+ - `Param.anchor.sheetOnXY([x, y, z])` places a 2D sheet in the XY plane at
296
+ fixed Z.
297
+ - `Param.anchor.sheetOnXZ([x, y, z])` places a 2D sheet in the XZ plane at
298
+ fixed Y.
299
+ - `Param.anchor.sheetOnYZ([x, y, z])` places a 2D sheet in the YZ plane at
300
+ fixed X.
301
+
302
+ The parameter still appears in the parameter panel when `anchor` is omitted; it
303
+ just has no 3D pin or spatial sheet.
304
+
305
+ ## Saving
306
+
307
+ Dragging a manual sheet writes parameter overrides first. The source model keeps
308
+ the declared defaults until those overrides are intentionally folded back into
309
+ code. Use snapshots for named parameter states, and use the parameter panel's
310
+ AI handoff for manual canvas edits when the desired result should become source.
311
+
312
+ ---
313
+
156
314
  <!-- generated/core.md -->
157
315
 
158
316
  # Core API
@@ -176,6 +334,9 @@ union2d, variableSweep, verify, Viewport, window, Wood, Wrap
176
334
  - [SurfacePattern](#surfacepattern)
177
335
  - [Pattern2D](#pattern2d)
178
336
  - [Pattern2DBuilder](#pattern2dbuilder)
337
+ - [Path2DParamValue](#path2dparamvalue)
338
+ - [Spline2DParamValue](#spline2dparamvalue)
339
+ - [Placement2DParamValue](#placement2dparamvalue)
179
340
  - [CurveNetBuilder](#curvenetbuilder)
180
341
  - [MatchEdgeBuilder](#matchedgebuilder)
181
342
  - [BridgeBuilder](#bridgebuilder)
@@ -504,11 +665,11 @@ for (const edge of coalesceEdges(topEdges)) {
504
665
 
505
666
  ### Imports & Composition
506
667
 
507
- #### `require(path: string, paramOverrides?: Record<string, number | string>): any` — Import a module with optional ForgeCAD parameter overrides. Returns the module's exports.
668
+ #### `require(path: string): any` — Import a ForgeCAD or helper module. Returns the file's returned value.
508
669
 
509
- When importing a `.forge.js` file, most return values are passed through exactly as the script returns them. Assembly returns have one extra composition rule: an unsolved [`Assembly`](/docs/assembly#assembly) is wrapped as an [`ImportedAssembly`](/docs/assembly#importedassembly), preserving `solve(state)` and `mergeInto()` across file boundaries, while a returned [`SolvedAssembly`](/docs/assembly#solvedassembly) stays a [`SolvedAssembly`](/docs/assembly#solvedassembly). If the script returns a metadata object (e.g. `{ shape: myShape, bolts: {...} }`), the caller receives the full object — renderable values and metadata together.
670
+ When importing a `.forge.js` file, return values are passed through exactly as the script returns them, except for unsolved assemblies: a returned [`Assembly`](/docs/assembly#assembly) is wrapped as an [`ImportedAssembly`](/docs/assembly#importedassembly), preserving `solve(state)` and `mergeInto()` across file boundaries. A returned [`SolvedAssembly`](/docs/assembly#solvedassembly) stays a [`SolvedAssembly`](/docs/assembly#solvedassembly).
510
671
 
511
- **Script return contract:** a `.forge.js` script returns one of three shapes: a single renderable (Shape, ShapeGroup, Sketch, SdfShape, Assembly), an array of renderables or named descriptors (`{ name, shape|sketch|group }`), or a metadata object mixing renderable values with plain data. When a script runs directly, renderable entries of a metadata object are rendered under their key names and non-renderable entries are silently skipped — both halves of the metadata contract: one return value serves the viewport and `require()` callers.
672
+ **Script return contract:** a `.forge.js` script should return one of three shapes: a single renderable (Shape, ShapeGroup, Sketch, SdfShape, Assembly), an array of renderables or named descriptors (`{ name, shape|sketch|group }`) for previews and multi-object display, or a module interface object. Reusable part files should return builders such as `return { buildBracket }`; files that already build useful geometry may return `{ shape, connectors, boltPattern }`. When a script runs directly, renderable entries of a plain object are rendered under their key names and non-renderable entries are skipped.
512
673
 
513
674
  **Assembly return contract**
514
675
 
@@ -521,52 +682,56 @@ When importing a `.forge.js` file, most return values are passed through exactly
521
682
 
522
683
  **Path rule:** Always include the file extension in relative imports: use `require("./part.forge.js")` for model files and `require("./helpers.js")` for plain helper modules. ForgeCAD does not apply Node-style extension inference, so `require("./part")` will not find `part.forge.js` or `part.js`.
523
684
 
524
- **Parameter scoping:** Parameters declared in required files are automatically namespaced with a `"filename#N / "` prefix (e.g. `"bracket.forge.js#1 / Width"`). This prevents collisions when multiple files declare same-named params. Each file's params appear as separate sliders.
525
-
526
- **Parameter overrides:** When passing overrides, use the bare param name (not the scoped name). Overrides are type-checked — unrecognized keys throw an error with typo suggestions.
527
-
528
- **Multi-file assembly pattern** — pass cross-cutting design values from the assembly to parts:
685
+ **Multi-file assembly pattern** the assembly owns params and passes ordinary props to child builders:
529
686
 
530
687
  ```js
531
688
  // assembly.forge.js — owns cross-cutting params, passes to parts
532
689
  const wall = param("Wall", 3);
533
690
  const baseH = param("Base Height", 20);
534
691
 
535
- const mount = require('./motor-mount.forge.js', { Wall: wall });
536
- const base = require('./base-body.forge.js', { Wall: wall, Height: baseH });
692
+ const mountModule = require('./motor-mount.forge.js');
693
+ const baseModule = require('./base-body.forge.js');
694
+
695
+ const mount = mountModule.buildMount({ wall });
696
+ const base = baseModule.buildBase({ wall, height: baseH });
537
697
  ```
538
698
 
539
- **Metadata pattern** — parts publish interface data alongside geometry:
699
+ **Builder result pattern** — parts publish interface data alongside geometry:
540
700
 
541
701
  ```js
542
702
  // motor-mount.forge.js
543
- return { shape: mount, bolts: { dia: 5.3, pos: holePositions } };
703
+ function buildMount({ wall }) {
704
+ const shape = box(80, 40, wall);
705
+ return { shape, boltPattern: { dia: 5.3, positions: [[-25, 0], [25, 0]] } };
706
+ }
544
707
 
545
708
  // base-body.forge.js
546
709
  const mount = require('./motor-mount.forge.js');
547
- mount.bolts.pos // access the metadata
548
- mount.shape // access the geometry
710
+ const built = mount.buildMount({ wall: 3 });
711
+ built.boltPattern // access interface data
712
+ built.shape // access geometry
549
713
  ```
550
714
 
551
715
  **Forge-aware builder module pattern** — use `.forge.js` modules for reusable sketch, profile, shape, or assembly builders that need ForgeCAD runtime APIs:
552
716
 
553
717
  ```js
554
- // profiles.forge.js — inspectable on its own, reusable through require()
718
+ // profiles.forge.js
555
719
  function wheelProfile() {
556
720
  return circle2d(40).subtract(circle2d(18));
557
721
  }
558
722
 
559
- return {
560
- preview: [{ name: 'Wheel profile', sketch: wheelProfile() }],
561
- make: { wheelProfile },
562
- };
723
+ if (require.main === module) {
724
+ return [{ name: 'Wheel profile', sketch: wheelProfile() }];
725
+ }
726
+
727
+ return { wheelProfile };
563
728
 
564
729
  // main.forge.js
565
730
  const profiles = require('./profiles.forge.js');
566
- const wheel = profiles.make.wheelProfile().extrude(8);
731
+ const wheel = profiles.wheelProfile().extrude(8);
567
732
  ```
568
733
 
569
- Keep exported builders pure over top-level constants, top-level `param()` values, or explicit function arguments. Do not declare new `param()` values inside an exported builder if callers need `require('./profiles.forge.js', { Width: 80 })` overrides: import overrides are validated while the module loads, before any exported builder is called. Use plain `.js` modules only for pure constants, tables, math helpers, and formatting code that does not construct ForgeCAD geometry.
734
+ Keep returned builders pure over top-level constants or explicit function arguments. Put preview-only `param()` values inside `if (require.main === module)` so parent assemblies stay in charge of design parameters. Use plain `.js` modules only for pure constants, tables, math helpers, and formatting code that does not construct ForgeCAD geometry.
570
735
 
571
736
  **Entry detection (Node semantics):** `require.main` is the entry script's module object, so `require.main === module` is true only in the file being run directly. Part files use it to build standalone preview geometry only when opened directly — importers then skip that work entirely:
572
737
 
@@ -574,16 +739,30 @@ Keep exported builders pure over top-level constants, top-level `param()` values
574
739
  // part.forge.js
575
740
  function bracket() { ... }
576
741
  if (require.main === module) {
577
- return { preview: [{ name: 'Bracket', shape: bracket() }] }; // direct run: render it
742
+ return bracket({ width: param('Width', 80), height: param('Height', 40) });
578
743
  }
579
- return { make: { bracket } }; // imported: builders only
744
+ return { bracket };
580
745
  ```
581
746
 
582
747
  ### Parameters
583
748
 
584
- #### `Param.number(name: string, defaultValue: number, opts?: { min?: number; max?: number; step?: number; unit?: string; integer?: boolean; reverse?: boolean; }): number` — Declare a numeric parameter that renders as a slider in the UI.
749
+ #### `Param.anchor: { ... }` — Viewport anchor builders for spatial parameter editing.
750
+
751
+ Anchors are metadata only: they do not change geometry. The editor uses them to show clickable parameter pins and manual-editing sheets in the 3D viewport. Use point anchors for scalar/text/list parameters and sheet anchors for `path2d`, [`spline2d`](/docs/curves#spline2d), and `placement2d` editors.
752
+
753
+ A sheet anchor's origin is a world/model-space point. Its plane selects how the 2D editor coordinates map into the viewport:
754
+
755
+ - `sheetOnXY([x, y, z])`: editor x/y map to world X/Y at fixed Z.
756
+ - `sheetOnXZ([x, y, z])`: editor x/y map to world X/Z at fixed Y.
757
+ - `sheetOnYZ([x, y, z])`: editor x/y map to world Y/Z at fixed X.
758
+
759
+ Omitting `anchor` still registers the parameter in the parameter panel; it just will not create a viewport pin/sheet.
760
+
761
+ `ParamAnchorOptions`: `{ label?: string, color?: string }`
762
+
763
+ #### `Param.number(name: string, defaultValue: number, opts?: NumberParamOptions): number` — Declare a numeric parameter that renders as a slider in the UI.
585
764
 
586
- 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.
765
+ Each call registers a slider control. When the user moves the slider the entire script re-executes with the new value. The `name` string is the UI label and the CLI `--param` key.
587
766
 
588
767
  Default range rules when options are omitted:
589
768
 
@@ -599,38 +778,34 @@ const angle = Param.number("Angle", 45, { min: 0, max: 180, unit: "°" });
599
778
  const sides = Param.number("Sides", 6, { min: 3, max: 12, integer: true });
600
779
  ```
601
780
 
602
- **Parameter overrides** key must match `name` exactly:
781
+ CLI overrides use the parameter name:
603
782
 
604
- ```ts
605
- // Via require()
606
- const bracket = require("./bracket.forge.js", { Width: 80 });
607
-
608
- // Via CLI
609
- // forgecad run model.forge.js --param "Wall Thickness=3"
783
+ ```bash
784
+ forgecad run model.forge.js --param "Wall Thickness=3"
610
785
  ```
611
786
 
612
787
  Also available as the shorthand alias `param()`.
613
788
 
614
- #### `Param.string(name: string, defaultValue: string, opts?: { maxLength?: number; }): string` — Declare a string parameter that renders as a text input in the UI.
789
+ `ParamAnchorableOptions`: `{ anchor?: ParamAnchorDef }`
615
790
 
616
- String parameters let users type free-form text — labels, names, inscriptions, file paths, etc. The `name` string is the override key.
791
+ `NumberParamOptions`: `{ min?: number, max?: number, step?: number, unit?: string, integer?: boolean, reverse?: boolean }`
617
792
 
618
- ```ts
619
- const label = Param.string("Label", "Hello World");
620
- const name = Param.string("Name", "Part-001", { maxLength: 20 });
621
- ```
793
+ #### `Param.string(name: string, defaultValue: string, opts?: StringParamOptions): string` — Declare a string parameter that renders as a text input in the UI.
622
794
 
623
- Override via import:
795
+ String parameters let users type free-form text — labels, names, inscriptions, file paths, etc.
624
796
 
625
797
  ```ts
626
- const tag = require("./tag.forge.js", { Label: "Custom Text" });
798
+ const label = Param.string("Label", "Hello World");
799
+ const name = Param.string("Name", "Part-001", { maxLength: 20 });
627
800
  ```
628
801
 
629
802
  Only available as `Param.string()` — no standalone alias.
630
803
 
631
- #### `Param.bool(name: string, defaultValue: boolean): boolean` — Declare a boolean parameter that renders as a checkbox in the UI.
804
+ `StringParamOptions`: `{ maxLength?: number }`
632
805
 
633
- 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.
806
+ #### `Param.bool(name: string, defaultValue: boolean, opts?: ParamAnchorableOptions): boolean` Declare a boolean parameter that renders as a checkbox in the UI.
807
+
808
+ Internally stored as `0`/`1` for CLI overrides. Pass `1` for true and `0` for false.
634
809
 
635
810
  ```ts
636
811
  const showHoles = Param.bool("Show Holes", true);
@@ -638,29 +813,17 @@ if (showHoles) return difference(plate, cylinder(10, 5).translate(50, 30, 0));
638
813
  return plate;
639
814
  ```
640
815
 
641
- Override via import:
642
-
643
- ```ts
644
- const pan = require("./pan.forge.js", { "Show Lid": 0 });
645
- ```
646
-
647
- #### `Param.choice(name: string, defaultValue: string, choices: string[]): string` — Declare a choice parameter that renders as a dropdown in the UI.
816
+ #### `Param.choice(name: string, defaultValue: string, choices: string[], opts?: ParamAnchorableOptions): string` — Declare a choice parameter that renders as a dropdown in the UI.
648
817
 
649
818
  `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.
650
819
 
651
- Overrides may be passed as the choice label string (preferred) or as a numeric index. The `name` string is the override key.
820
+ CLI overrides may be passed as the choice label string (preferred) or as a numeric index.
652
821
 
653
822
  ```ts
654
823
  const panStyle = Param.choice("Pan Style", "frying-pan", ["frying-pan", "saute-pan", "wok"]);
655
824
  if (panStyle === "wok") return buildWok();
656
825
  ```
657
826
 
658
- Override via import:
659
-
660
- ```ts
661
- const pan = require("./pan.forge.js", { "Pan Style": "wok" });
662
- ```
663
-
664
827
  Override via CLI:
665
828
 
666
829
  ```bash
@@ -679,6 +842,90 @@ Field types:
679
842
 
680
843
  `ListParamFieldDef`: `{ min?: number, max?: number, step?: number, unit?: string, integer?: boolean, boolean?: boolean, choices?: string[] }`
681
844
 
845
+ #### `Param.path2d(name: string, defaultPoints: Path2DPointInput[], opts?: Path2DParamOptions): Path2DParamValue` — Declare an editable 2D path parameter.
846
+
847
+ Use this for hand-shaped 2D profile data: plate outlines, slice profiles, stroke centerlines, and sweep rails. The returned value keeps the model code deterministic while the editor can render a drag-handle path UI.
848
+
849
+ Override keys use the same explicit row-field form as list params: `Path Name[0].x`, `Path Name[0].y`, and `Path Name.__count__`.
850
+
851
+ ```ts
852
+ const outline = Param.path2d("Bracket Outline", [
853
+ [-40, -20],
854
+ [40, -20],
855
+ [36, 24],
856
+ [-30, 28],
857
+ ], {
858
+ closed: true,
859
+ anchor: Param.anchor.sheetOnXY([0, 0, 8], { label: "Bracket outline" }),
860
+ });
861
+
862
+ return outline.toSketch().filletCorners(4).extrude(5);
863
+ ```
864
+
865
+ **`Path2DParamOptions`** extends ParamAnchorableOptions: `closed?: boolean`, `minPoints?: number`, `maxPoints?: number`, `x?: Partial<Path2DParamAxisDef>`, `y?: Partial<Path2DParamAxisDef>`, `unit?: string`
866
+
867
+ **`Path2DParamAxisDef`**
868
+ - `min: number` — Initial editor-frame minimum. Points may move outside this range on the infinite canvas.
869
+ - `max: number` — Initial editor-frame maximum. Points may move outside this range on the infinite canvas.
870
+ - Also: `step: number`.
871
+
872
+ #### `Param.spline2d(name: string, defaultPoints: Spline2DPointInput[], opts?: Spline2DParamOptions): Spline2DParamValue` — Declare an editable 2D spline parameter.
873
+
874
+ Use this when the model wants hand-shaped smooth curve data instead of a numeric table: handle spines, bowl station curves, sweep rails, and class-A guide profiles. Each node stores `g`, a continuity intent:
875
+
876
+ - `"G0"` starts/ends a hard curve segment at that point
877
+ - `"G1"` keeps the point in a tangent-smooth run
878
+ - `"G2"` keeps the point in a curvature-smooth cubic run
879
+
880
+ Override keys use explicit row-field form: `Curve Name[0].x`, `Curve Name[0].y`, `Curve Name[0].g`, and `Curve Name.__count__`.
881
+
882
+ ```ts
883
+ const spine = Param.spline2d("Handle Spine", [
884
+ { x: 0, y: 0, g: "G2" },
885
+ { x: 35, y: 8, g: "G2" },
886
+ { x: 80, y: 2, g: "G1" },
887
+ ], {
888
+ anchor: Param.anchor.sheetOnXZ([0, -18, 0], { label: "Side-view spine" }),
889
+ });
890
+
891
+ return sweep(circle2d(2), spine.toCurveOnXZ());
892
+ ```
893
+
894
+ **`Spline2DParamOptions`** extends ParamAnchorableOptions: `closed?: boolean`, `degree?: number`, `defaultContinuity?: Spline2DContinuity`, `minPoints?: number`, `maxPoints?: number`, `x?: Partial<Path2DParamAxisDef>`, `y?: Partial<Path2DParamAxisDef>`, `unit?: string`
895
+
896
+ #### `Param.placement2d(name: string, spec: Placement2DParamOptions): Placement2DParamValue` — Declare an editable 2D placement sheet parameter.
897
+
898
+ Use this when the user should arrange named model roles rather than draw geometry: batteries inside an enclosure, rooms across floors, controls on a panel, robot modules on a chassis, or other semantic blockouts.
899
+
900
+ The script declares stable item IDs, footprints, optional rectangular zones, and interaction rules. The returned value exposes named placements as data; the model code decides what those placements mean geometrically.
901
+
902
+ Override keys are item-ID based: `Layout.battery.x`, `Layout.battery.y`, `Layout.battery.angle`, and `Layout.battery.zone`.
903
+
904
+ ```ts
905
+ const layout = Param.placement2d("Internal Layout", {
906
+ frame: { size: [120, 80] },
907
+ items: [
908
+ { id: "battery", footprint: { type: "rect", size: [42, 24] }, at: [-25, 0] },
909
+ { id: "speaker", footprint: { type: "circle", radius: 12 }, at: [32, 8] },
910
+ ],
911
+ rules: { bounds: "prevent", collisions: "warn", snap: 1 },
912
+ anchor: Param.anchor.sheetOnXY([0, 0, 12], { label: "Internal layout" }),
913
+ });
914
+
915
+ const battery = layout.item("battery");
916
+ const batteryPocket = box(42, 24, 6).translate(battery.x, battery.y, 3);
917
+ ```
918
+
919
+ **`Placement2DParamOptions`** extends ParamAnchorableOptions: `frame?: Placement2DFrameInput`, `zones?: Placement2DZoneInput[]`, `items: Placement2DItemInput[]`, `rules?: Partial<Placement2DRulesDef>`, `unit?: string`
920
+
921
+ `Placement2DFrameInput`: `{ width?: number, height?: number, size?: Vec2, center?: Placement2DPointInput, at?: Placement2DPointInput }`
922
+
923
+ `Placement2DZoneInput`: `{ id: string, label?: string, frame?: Placement2DFrameInput }`
924
+
925
+ **`Placement2DItemInput`**: `id: string`, `label?: string`, `footprint: Placement2DFootprintInput`, `at?: Placement2DPointInput`, `center?: Placement2DPointInput`, `angle?: number`, `zone?: string`, `locked?: boolean`
926
+
927
+ `Placement2DRulesDef`: `{ bounds: Placement2DRuleMode, collisions: Placement2DRuleMode, snap: number }`
928
+
682
929
  ### Grouping & Local Coordinates
683
930
 
684
931
  #### `group(...items: GroupInput[]): ShapeGroup` — Group multiple shapes/sketches for joint transforms without merging into a single mesh.
@@ -1356,7 +1603,7 @@ cylinder(60, 20).wrapTexture(label, Wrap.aroundCylinder({ axis: 'z' })); // wra
1356
1603
 
1357
1604
  #### `ref(path: string): ShapeRef` — Resolve a semantic reference path like `lid`, `lid/back`, or a midpoint selector on `lid/back`.
1358
1605
 
1359
- #### `thicken(thickness: number): Shape` — Offset-thicken an exact open surface or shell into a solid.
1606
+ #### `thicken(thickness: ThicknessInput): Shape` — Offset-thicken an exact open surface or shell into a solid.
1360
1607
 
1361
1608
  #### `getMesh(): ShapeRuntimeMesh` — Extract triangle mesh for Three.js rendering
1362
1609
 
@@ -1596,6 +1843,99 @@ const bracket = group(
1596
1843
  | `depth?` | `number` | Thread groove depth in millimeters. Default: 0.8. |
1597
1844
  | `underScale?` | `number` | Relative height of the under-crossing thread. Default: 0.15. |
1598
1845
 
1846
+ ### `Path2DParamValue`
1847
+
1848
+ Runtime value returned by `Param.path2d()`.
1849
+
1850
+ Use closed paths as editable profile outlines via `toSketch()`. Use open paths as editable centerlines via `toStroke(width)`.
1851
+
1852
+ **Properties:**
1853
+
1854
+ | Property | Type | Description |
1855
+ |----------|------|-------------|
1856
+ | `closed` | `boolean` | True when this path is intended to close back to its first point. |
1857
+
1858
+ **Methods:**
1859
+
1860
+ #### `points(): Vec2[]` — Return the current points as `[x, y]` pairs in the editor coordinate system.
1861
+
1862
+ #### `toSketch(): Sketch` — Convert a closed editable path into a sketch profile.
1863
+
1864
+ This is the common path for hand-edited plates, outlines, and 2D section profiles that will be extruded, subtracted, or used in sketch booleans. Throws for open paths; use `toStroke(width)` for editable centerlines.
1865
+
1866
+ #### `toStroke(width: number, join?: "Round" | "Square"): Sketch` — Convert an editable path into a stroked sketch with physical width.
1867
+
1868
+ Use this for rails, ribs, cable routes, gasket paths, and other open centerlines. Closed paths can also be stroked when the intent is a looped band rather than a filled profile.
1869
+
1870
+ ### `Spline2DParamValue`
1871
+
1872
+ Runtime value returned by `Param.spline2d()`.
1873
+
1874
+ Spline params preserve both editable point coordinates and each point's continuity intent (`G0`, `G1`, or `G2`). Convert them to curves for sweeps and loft rails, or to sampled paths when an API expects plain points.
1875
+
1876
+ **Properties:**
1877
+
1878
+ | Property | Type | Description |
1879
+ |----------|------|-------------|
1880
+ | `closed` | `boolean` | True when this spline is intended to close back to its first point. |
1881
+ | `degree` | `number` | Requested fitting degree before any automatic reduction for short spans. |
1882
+
1883
+ **Methods:**
1884
+
1885
+ #### `points(): Vec2[]` — Return the current control nodes as `[x, y]` pairs without continuity metadata.
1886
+
1887
+ #### `nodes(): Spline2DPointDef[]` — Return the current editable nodes, including per-point `g` continuity values.
1888
+
1889
+ #### `continuities(): Spline2DContinuity[]` — Return only the per-node continuity intents in point order.
1890
+
1891
+ #### `toPolyline(samples?: number): Vec2[]` — Sample the spline into 2D `[x, y]` points.
1892
+
1893
+ Use this when downstream code needs a polyline instead of a curve object.
1894
+
1895
+ #### `toCurveOnXY(z?: number, options?: Spline2DCurveOptions): NurbsCurve3D` — Fit the editable spline as a 3D curve on the XY plane at constant `z`.
1896
+
1897
+ The point's `x` maps to world X and `y` maps to world Y.
1898
+
1899
+ `Spline2DCurveOptions`: `{ degree?: number, tolerance?: number, samples?: number }`
1900
+
1901
+ #### `toCurveOnXZ(y?: number, options?: Spline2DCurveOptions): NurbsCurve3D` — Fit the editable spline as a 3D curve on the XZ plane at constant `y`.
1902
+
1903
+ The point's `x` maps to world X and `y` maps to world Z. This is useful for side-view height/depth profiles such as spoon bowls, handles, and rails.
1904
+
1905
+ #### `toCurveOnYZ(x?: number, options?: Spline2DCurveOptions): NurbsCurve3D` — Fit the editable spline as a 3D curve on the YZ plane at constant `x`.
1906
+
1907
+ The point's `x` maps to world Y and `y` maps to world Z.
1908
+
1909
+ #### `toCurveSegmentsOnXY(z?: number, options?: Spline2DCurveOptions): NurbsCurve3D[]` — Fit the spline on XY and return separate curve segments split at `G0` nodes.
1910
+
1911
+ Use segment output when a hard break should remain visible to downstream code instead of being joined into one continuous curve.
1912
+
1913
+ #### `toCurveSegmentsOnXZ(y?: number, options?: Spline2DCurveOptions): NurbsCurve3D[]` — Fit the spline on XZ and return separate curve segments split at `G0` nodes.
1914
+
1915
+ #### `toCurveSegmentsOnYZ(x?: number, options?: Spline2DCurveOptions): NurbsCurve3D[]` — Fit the spline on YZ and return separate curve segments split at `G0` nodes.
1916
+
1917
+ #### `toPathOnXY(z?: number, options?: Spline2DCurveOptions): Vec3[]` — Sample the spline as 3D points on the XY plane at constant `z`.
1918
+
1919
+ This is useful for sweeps and surface helpers that accept point paths.
1920
+
1921
+ #### `toPathOnXZ(y?: number, options?: Spline2DCurveOptions): Vec3[]` — Sample the spline as 3D points on the XZ plane at constant `y`.
1922
+
1923
+ #### `toPathOnYZ(x?: number, options?: Spline2DCurveOptions): Vec3[]` — Sample the spline as 3D points on the YZ plane at constant `x`.
1924
+
1925
+ ### `Placement2DParamValue`
1926
+
1927
+ Runtime value returned by `Param.placement2d()`.
1928
+
1929
+ Placement sheets return semantic item positions. The model decides what each item means geometrically, so users can drag named blocks without editing the construction code.
1930
+
1931
+ #### `items(): Placement2DItemPlacement[]` — Return all current item placements as immutable copies.
1932
+
1933
+ #### `positions(): Record<string, Placement2DItemPlacement>` — Return current placements keyed by item id for table-style lookup.
1934
+
1935
+ #### `item(id: string): Placement2DItemPlacement` — Return one named item placement.
1936
+
1937
+ Throws if `id` was not declared in the sheet, which keeps model code tied to stable semantic item IDs rather than fragile list indices.
1938
+
1599
1939
  ### `CurveNetBuilder`
1600
1940
 
1601
1941
  #### `alongRails(railA: CurveInput, railB: CurveInput): this` — Use two lengthwise boundary curves as guide rails.
@@ -1667,6 +2007,7 @@ Pass `{ edge }` to match an adjacent sheet's tangent (G1) or curvature (G2), or
1667
2007
  - `pathAlongU(v: number, options?: SheetPathAlongOptions): Vec3[]`
1668
2008
  - `pathAlongV(u: number, options?: SheetPathAlongOptions): Vec3[]`
1669
2009
  - `thicken(wall: number, options?: { resolution?: number; }): Shape`
2010
+ - `thickenInsideBy(thickness: ThicknessInput, options?: { resolution?: number; }): Shape`
1670
2011
  - `matchEdge(edge: SheetEdge): MatchEdgeBuilder`
1671
2012
 
1672
2013
  **`SheetFrameOptions`**
@@ -2884,6 +3225,7 @@ Smooth curves, lofted surfaces, swept solids, splines, and high-level product sk
2884
3225
  - [Surface](#surface)
2885
3226
  - [Blend](#blend)
2886
3227
  - [Analysis](#analysis)
3228
+ - [Thickness](#thickness)
2887
3229
  - [Product](#product)
2888
3230
  - [Carrier](#carrier)
2889
3231
  - [SurfaceMembers](#surfacemembers)
@@ -3493,6 +3835,10 @@ Use this when the guide path runs in the surface V direction and the sketch prof
3493
3835
 
3494
3836
  #### `thicken(wall: number, options?: { resolution?: number; }): Shape` — Offset the sheet along its analytic normals into a watertight solid shell of the given wall thickness. Throws if the wall would self-intersect on a concave region (no silent degenerate solid).
3495
3837
 
3838
+ #### `thickenInsideBy(thickness: ThicknessInput, options?: { resolution?: number; }): Shape` — Thicken this sheet inward by a scalar UV thickness field.
3839
+
3840
+ Numeric input delegates to `Sheet.thicken()`. `Thickness.*` fields produce a sampled solid because a variable normal offset is not generally an exact NURBS surface.
3841
+
3496
3842
  #### `matchEdge(edge: SheetEdge): MatchEdgeBuilder` — Per-edge continuity match against a neighbor (returns a NEW Sheet).
3497
3843
 
3498
3844
  - `get rearEdge(): SheetEdge`
@@ -4368,6 +4714,14 @@ Members (full entries under [Curves & Surfacing](#curves-surfacing)): `Curve.Ble
4368
4714
  - `SurfaceHealth(shape: Shape, options?: { tinyEdgeThreshold?: number; sliverThreshold?: number; }): SurfaceHealthReport`
4369
4715
  - `BRepValidity(shape: Shape, options?: BRepValidityOptions): BRepValidityReport` — Validate B-rep/shell/solid structure and return closedness, manifoldness, orientation, and issue diagnostics.
4370
4716
 
4717
+ ### `Thickness`
4718
+
4719
+ - `constant: (thickness: number) => ThicknessField` — Use the same wall thickness everywhere on the sheet.
4720
+ - `alongU: (profile: ThicknessStation[] | ThicknessStationProfile) => ThicknessField` — Vary wall thickness across the sheet U direction.
4721
+ - `alongV: (profile: ThicknessStation[] | ThicknessStationProfile) => ThicknessField` — Vary wall thickness across the sheet V direction.
4722
+ - `grid: (values: number[][], options?: ThicknessGridOptions) => ThicknessField` — Bilinearly interpolate wall thickness from a rectangular UV grid.
4723
+ - `nurbs: (values: number[][], options?: ThicknessNurbsOptions) => ThicknessField` — Interpolate wall thickness from a scalar tensor-product B-spline over sheet UV.
4724
+
4371
4725
  ### `Product`
4372
4726
 
4373
4727
  - `skin(name: string): ProductSkinBuilder` — Start a named product skin builder.
@@ -5388,6 +5742,17 @@ Use structural FEA when you want a ForgeCAD model to answer a load-case question
5388
5742
 
5389
5743
  ForgeCAD owns the authoring contract, solver orchestration, result feedback, and inspection report. The numerical solve is done out of process with Gmsh and CalculiX. Users author a study in the model, run `forgecad fea run`, and inspect a result bundle.
5390
5744
 
5745
+ ## Contents
5746
+
5747
+ - What You Get
5748
+ - What You Need Installed
5749
+ - Author The Study
5750
+ - Choose Stable Regions
5751
+ - Run The Flow
5752
+ - Read The Results
5753
+ - Current Scope
5754
+ - Troubleshooting
5755
+
5391
5756
  ## What You Get
5392
5757
 
5393
5758
  A solved FEA result bundle can produce: