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
@@ -23,7 +23,8 @@ Author or modify ForgeCAD models, sketches, assemblies, and CLI workflows. Prefe
23
23
 
24
24
  ### Import and Composition
25
25
 
26
- - 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.
26
+ - 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.
27
+ - Reusable `.forge.js` part files should return builder functions such as `return { buildPart }`; direct-run preview params belong inside `if (require.main === module)`.
27
28
  - 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")`.
28
29
  - For static multi-part models, connectors + `matchTo()` are the default way to assemble touching parts.
29
30
  - 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.
@@ -41,6 +42,7 @@ Execution model, colors, coordinate system, primitives, booleans, patterns, impo
41
42
 
42
43
  - `{{SKILL_DIR}}/docs/API/core/concepts.md`
43
44
  - `{{SKILL_DIR}}/docs/generated/runtime-names.md`
45
+ - `{{SKILL_DIR}}/docs/guides/manual-parameters.md`
44
46
  - `{{SKILL_DIR}}/docs/generated/core.md`
45
47
 
46
48
  ### 2. Static Assembly and Positioning (for any multi-part model)
@@ -11,10 +11,10 @@ A `.forge.js` script is plain JavaScript that returns geometry. The entire forge
11
11
 
12
12
  All geometry operations are **immutable** — shapes, sketches, groups, assemblies, and boards return new values, never mutate in place.
13
13
 
14
- A script must return one of three shapes:
14
+ A script should return one of three shapes:
15
15
 
16
16
  1. **A single renderable** — `Shape`, `Sketch`, `ShapeGroup`, `Assembly`, `SolvedAssembly`, or `SdfShape`.
17
- 2. **An array** of renderables or named descriptors `{ name, tags?, shape | sketch | group, color? }`:
17
+ 2. **An array** of renderables or named descriptors `{ name, tags?, shape | sketch | group, color? }`, usually for direct-run previews and multi-object display:
18
18
 
19
19
  ```javascript
20
20
  return [
@@ -23,11 +23,38 @@ A script must return one of three shapes:
23
23
  ];
24
24
  ```
25
25
 
26
- 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).
26
+ 3. **A module interface object** — usually builder functions, optionally a built shape plus interface data:
27
+
28
+ ```javascript
29
+ return { buildBracket };
30
+ // or, when the file's useful output is already built:
31
+ return { shape, connectors, boltPattern };
32
+ ```
33
+
34
+ For reusable part files, prefer a builder export and keep direct-run preview controls inside the entry guard:
35
+
36
+ ```javascript
37
+ function buildThing(props) {
38
+ return box(props.width, props.depth, props.height);
39
+ }
40
+
41
+ if (require.main === module) {
42
+ const previewProps = {
43
+ width: param("Width", 80),
44
+ depth: param("Depth", 40),
45
+ height: param("Height", 12),
46
+ };
47
+ return buildThing(previewProps);
48
+ }
49
+
50
+ return { buildThing };
51
+ ```
52
+
53
+ When a plain object is returned directly, renderable values are shown in the viewport and non-renderable values are available to importers through `require()`.
27
54
 
28
55
  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.
29
56
 
30
- For multi-file projects import path rules, the metadata pattern, and Forge-aware builder modules see the [`require()` docs](../../generated/core.md).
57
+ For multi-file projects, import path rules, and reusable builder modules, see the [`require()` docs](../../generated/core.md).
31
58
 
32
59
  ## Identity
33
60
 
@@ -13,8 +13,7 @@ Create projects, open local studios, run, inspect, export, publish, and sync `.f
13
13
  # 1. Install
14
14
  npm install -g forgecad
15
15
 
16
- # 2. Sign in and create a dedicated project folder
17
- forgecad login
16
+ # 2. Create a dedicated local project folder
18
17
  mkdir spool-adapter
19
18
  cd spool-adapter
20
19
  forgecad project init "Spool Adapter" --visibility private
@@ -25,14 +24,15 @@ forgecad new adapter --template part
25
24
  # 4. Open the local editor
26
25
  forgecad studio .
27
26
 
28
- # 5. Validate, export, and push to the browser
27
+ # 5. Validate, export, sign in, and push to the browser
29
28
  forgecad run adapter.forge.js
30
29
  forgecad export stl adapter.forge.js
30
+ forgecad login
31
31
  forgecad project push
32
32
  forgecad project open
33
33
  ```
34
34
 
35
- Local modeling, inspection, export, and skill-install commands do not require a ForgeCAD account. Hosted project sync, publishing, token management, and license activation require sign-in: run `forgecad login` and choose email/password or API token (for GitHub/Google accounts, create a token in Settings > API Tokens first). Use `FORGECAD_TOKEN=fc_pat_... forgecad <hosted-command>` only for CI/CD and one-off automation. `forgecad studio` always requires an explicit project path; use `.` for the current project.
35
+ Local modeling, project initialization, inspection, export, and skill-install commands do not require a ForgeCAD account. Hosted project sync, publishing, token management, and license activation require sign-in: run `forgecad login` and choose email/password or API token (for GitHub/Google accounts, create a token in Settings > API Tokens first). Use `FORGECAD_TOKEN=fc_pat_... forgecad <hosted-command>` only for CI/CD and one-off automation. `forgecad studio` always requires an explicit project path; use `.` for the current project.
36
36
 
37
37
  You can also start from the hosted starter project with `forgecad project clone start-here`, then `cd start-here` and `forgecad studio .`.
38
38
 
@@ -74,7 +74,7 @@ The fast validation command: runs the script with the real geometry kernel (no b
74
74
 
75
75
  A bare `forgecad run` skips expensive diagnostics. Opt in with `--details` (volumes/bounds), `--history` (construction tree), `--features` (feature tallies), `--solver-profile` (constraint solver timing), or `--connectivity` (physical connected components — bbox contact is evidence, exact geometry is checked by default). `--quality live|default|high` selects the same geometry quality profile as the editor and export tools; `live` is fastest for large models.
76
76
 
77
- Direct `.stl`/`.obj`/`.3mf`/`.step`/`.stp` inputs are imported automatically; STEP/STP auto-selects OCCT unless you pass `--backend`. For deeper confidence gates, prefer `inspect mechanical-integrity`, `check print`, or `inspect fit interference` instead of turning `run` into a catch-all audit command.
77
+ Direct `.stl`/`.obj`/`.3mf`/`.step`/`.stp` inputs are imported automatically; STEP/STP auto-selects OCCT unless you pass `--backend`. Use `--project-root <dir>` when running a file inside a larger uninitialized folder and you intentionally want relative imports to resolve against that root. For deeper confidence gates, prefer `inspect mechanical-integrity`, `check print`, or `inspect fit interference` instead of turning `run` into a catch-all audit command.
78
78
 
79
79
  ```bash
80
80
  forgecad run model.forge.js
@@ -358,16 +358,16 @@ Script-backed exports accept repeatable `--param Key=Value` overrides before the
358
358
 
359
359
  ForgeCAD has a hosted platform at [forgecad.io](https://forgecad.io). The CLI connects a dedicated local project folder to it.
360
360
 
361
- A project is a local folder linked to the hosted app by `forgecad.json`. Use `forgecad project clone <slug>` to download an existing hosted project into a local folder, or run `forgecad project init` inside a folder that should become a new ForgeCAD project. Open local projects with `forgecad studio <project-path>`.
361
+ A project is a local folder marked by `forgecad.json`; after the first push, that same file also links it to the hosted app. Use `forgecad project clone <slug>` to download an existing hosted project into a local folder, or run `forgecad project init` inside a folder that should become a new ForgeCAD project. Open local projects with `forgecad studio <project-path>`.
362
362
 
363
363
  Keep the project root small and intentional. Do not run the editor from `~`, downloads, desktop, or a huge source tree. ForgeCAD scans project files such as `.forge.js`, `.js`, `.svg`, and `.dxf`; broad roots make local workflows and AI-agent context slow and confusing.
364
364
 
365
- First-time setup (login, init, studio) is the [Quick Start](#quick-start) sequence above. `forgecad project init` creates the remote project, writes `forgecad.json`, pushes any existing local source files, and records server file IDs. `forgecad project push` syncs an already initialized project; it does not create a remote project from an arbitrary folder.
365
+ First-time setup (init, studio, login, push) is the [Quick Start](#quick-start) sequence above. `forgecad project init` writes a local `forgecad.json` with the project name, slug, server, and visibility. It does not contact the server. The first `forgecad project push` creates the hosted project if `forgecad.json` has no project ID yet, uploads local source files, and records server file IDs. Later pushes sync the already linked hosted project. `forgecad project push` still requires `forgecad.json`; it does not initialize a random folder.
366
366
 
367
367
  ### Sync
368
368
 
369
369
  ```bash
370
- forgecad project push # Upload local changes
370
+ forgecad project push # Create hosted project if needed; upload changes
371
371
  forgecad project pull # Download remote changes
372
372
  forgecad project status # See what's different
373
373
  ```
@@ -395,10 +395,10 @@ Shares are live references — always the current version, not a snapshot.
395
395
 
396
396
  | Command | Description |
397
397
  |---------|-------------|
398
- | `project init` | Initialize the current directory as a ForgeCAD project and create it on the server. |
398
+ | `project init` | Initialize the current directory as a local ForgeCAD project. |
399
399
  | `project clone` | Download a remote project into a new local directory. |
400
400
  | `project pull` | Download remote changes into the current project. |
401
- | `project push` | Upload local changes to the remote project. |
401
+ | `project push` | Create the hosted project if needed, then upload local changes. |
402
402
  | `project status` | Show differences between local and remote project files. |
403
403
  | `project list` | List your remote projects. |
404
404
  | `project open` | Open the current project in the browser. |
@@ -24,6 +24,9 @@ skill-order: 100
24
24
  - [SurfacePattern](#surfacepattern)
25
25
  - [Pattern2D](#pattern2d)
26
26
  - [Pattern2DBuilder](#pattern2dbuilder)
27
+ - [Path2DParamValue](#path2dparamvalue)
28
+ - [Spline2DParamValue](#spline2dparamvalue)
29
+ - [Placement2DParamValue](#placement2dparamvalue)
27
30
  - [CurveNetBuilder](#curvenetbuilder)
28
31
  - [MatchEdgeBuilder](#matchedgebuilder)
29
32
  - [BridgeBuilder](#bridgebuilder)
@@ -352,11 +355,11 @@ for (const edge of coalesceEdges(topEdges)) {
352
355
 
353
356
  ### Imports & Composition
354
357
 
355
- #### `require(path: string, paramOverrides?: Record<string, number | string>): any` — Import a module with optional ForgeCAD parameter overrides. Returns the module's exports.
358
+ #### `require(path: string): any` — Import a ForgeCAD or helper module. Returns the file's returned value.
356
359
 
357
- 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.
360
+ 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).
358
361
 
359
- **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.
362
+ **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.
360
363
 
361
364
  **Assembly return contract**
362
365
 
@@ -369,52 +372,56 @@ When importing a `.forge.js` file, most return values are passed through exactly
369
372
 
370
373
  **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`.
371
374
 
372
- **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.
373
-
374
- **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.
375
-
376
- **Multi-file assembly pattern** — pass cross-cutting design values from the assembly to parts:
375
+ **Multi-file assembly pattern** the assembly owns params and passes ordinary props to child builders:
377
376
 
378
377
  ```js
379
378
  // assembly.forge.js — owns cross-cutting params, passes to parts
380
379
  const wall = param("Wall", 3);
381
380
  const baseH = param("Base Height", 20);
382
381
 
383
- const mount = require('./motor-mount.forge.js', { Wall: wall });
384
- const base = require('./base-body.forge.js', { Wall: wall, Height: baseH });
382
+ const mountModule = require('./motor-mount.forge.js');
383
+ const baseModule = require('./base-body.forge.js');
384
+
385
+ const mount = mountModule.buildMount({ wall });
386
+ const base = baseModule.buildBase({ wall, height: baseH });
385
387
  ```
386
388
 
387
- **Metadata pattern** — parts publish interface data alongside geometry:
389
+ **Builder result pattern** — parts publish interface data alongside geometry:
388
390
 
389
391
  ```js
390
392
  // motor-mount.forge.js
391
- return { shape: mount, bolts: { dia: 5.3, pos: holePositions } };
393
+ function buildMount({ wall }) {
394
+ const shape = box(80, 40, wall);
395
+ return { shape, boltPattern: { dia: 5.3, positions: [[-25, 0], [25, 0]] } };
396
+ }
392
397
 
393
398
  // base-body.forge.js
394
399
  const mount = require('./motor-mount.forge.js');
395
- mount.bolts.pos // access the metadata
396
- mount.shape // access the geometry
400
+ const built = mount.buildMount({ wall: 3 });
401
+ built.boltPattern // access interface data
402
+ built.shape // access geometry
397
403
  ```
398
404
 
399
405
  **Forge-aware builder module pattern** — use `.forge.js` modules for reusable sketch, profile, shape, or assembly builders that need ForgeCAD runtime APIs:
400
406
 
401
407
  ```js
402
- // profiles.forge.js — inspectable on its own, reusable through require()
408
+ // profiles.forge.js
403
409
  function wheelProfile() {
404
410
  return circle2d(40).subtract(circle2d(18));
405
411
  }
406
412
 
407
- return {
408
- preview: [{ name: 'Wheel profile', sketch: wheelProfile() }],
409
- make: { wheelProfile },
410
- };
413
+ if (require.main === module) {
414
+ return [{ name: 'Wheel profile', sketch: wheelProfile() }];
415
+ }
416
+
417
+ return { wheelProfile };
411
418
 
412
419
  // main.forge.js
413
420
  const profiles = require('./profiles.forge.js');
414
- const wheel = profiles.make.wheelProfile().extrude(8);
421
+ const wheel = profiles.wheelProfile().extrude(8);
415
422
  ```
416
423
 
417
- 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.
424
+ 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.
418
425
 
419
426
  **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:
420
427
 
@@ -422,16 +429,30 @@ Keep exported builders pure over top-level constants, top-level `param()` values
422
429
  // part.forge.js
423
430
  function bracket() { ... }
424
431
  if (require.main === module) {
425
- return { preview: [{ name: 'Bracket', shape: bracket() }] }; // direct run: render it
432
+ return bracket({ width: param('Width', 80), height: param('Height', 40) });
426
433
  }
427
- return { make: { bracket } }; // imported: builders only
434
+ return { bracket };
428
435
  ```
429
436
 
430
437
  ### Parameters
431
438
 
432
- #### `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.
439
+ #### `Param.anchor: { ... }` — Viewport anchor builders for spatial parameter editing.
440
+
441
+ 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.
433
442
 
434
- 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.
443
+ A sheet anchor's origin is a world/model-space point. Its plane selects how the 2D editor coordinates map into the viewport:
444
+
445
+ - `sheetOnXY([x, y, z])`: editor x/y map to world X/Y at fixed Z.
446
+ - `sheetOnXZ([x, y, z])`: editor x/y map to world X/Z at fixed Y.
447
+ - `sheetOnYZ([x, y, z])`: editor x/y map to world Y/Z at fixed X.
448
+
449
+ Omitting `anchor` still registers the parameter in the parameter panel; it just will not create a viewport pin/sheet.
450
+
451
+ `ParamAnchorOptions`: `{ label?: string, color?: string }`
452
+
453
+ #### `Param.number(name: string, defaultValue: number, opts?: NumberParamOptions): number` — Declare a numeric parameter that renders as a slider in the UI.
454
+
455
+ 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.
435
456
 
436
457
  Default range rules when options are omitted:
437
458
 
@@ -447,38 +468,34 @@ const angle = Param.number("Angle", 45, { min: 0, max: 180, unit: "°" });
447
468
  const sides = Param.number("Sides", 6, { min: 3, max: 12, integer: true });
448
469
  ```
449
470
 
450
- **Parameter overrides** key must match `name` exactly:
451
-
452
- ```ts
453
- // Via require()
454
- const bracket = require("./bracket.forge.js", { Width: 80 });
471
+ CLI overrides use the parameter name:
455
472
 
456
- // Via CLI
457
- // forgecad run model.forge.js --param "Wall Thickness=3"
473
+ ```bash
474
+ forgecad run model.forge.js --param "Wall Thickness=3"
458
475
  ```
459
476
 
460
477
  Also available as the shorthand alias `param()`.
461
478
 
462
- #### `Param.string(name: string, defaultValue: string, opts?: { maxLength?: number; }): string` — Declare a string parameter that renders as a text input in the UI.
479
+ `ParamAnchorableOptions`: `{ anchor?: ParamAnchorDef }`
463
480
 
464
- String parameters let users type free-form text — labels, names, inscriptions, file paths, etc. The `name` string is the override key.
481
+ `NumberParamOptions`: `{ min?: number, max?: number, step?: number, unit?: string, integer?: boolean, reverse?: boolean }`
465
482
 
466
- ```ts
467
- const label = Param.string("Label", "Hello World");
468
- const name = Param.string("Name", "Part-001", { maxLength: 20 });
469
- ```
483
+ #### `Param.string(name: string, defaultValue: string, opts?: StringParamOptions): string` — Declare a string parameter that renders as a text input in the UI.
470
484
 
471
- Override via import:
485
+ String parameters let users type free-form text — labels, names, inscriptions, file paths, etc.
472
486
 
473
487
  ```ts
474
- const tag = require("./tag.forge.js", { Label: "Custom Text" });
488
+ const label = Param.string("Label", "Hello World");
489
+ const name = Param.string("Name", "Part-001", { maxLength: 20 });
475
490
  ```
476
491
 
477
492
  Only available as `Param.string()` — no standalone alias.
478
493
 
479
- #### `Param.bool(name: string, defaultValue: boolean): boolean` — Declare a boolean parameter that renders as a checkbox in the UI.
494
+ `StringParamOptions`: `{ maxLength?: number }`
495
+
496
+ #### `Param.bool(name: string, defaultValue: boolean, opts?: ParamAnchorableOptions): boolean` — Declare a boolean parameter that renders as a checkbox in the UI.
480
497
 
481
- 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.
498
+ Internally stored as `0`/`1` for CLI overrides. Pass `1` for true and `0` for false.
482
499
 
483
500
  ```ts
484
501
  const showHoles = Param.bool("Show Holes", true);
@@ -486,29 +503,17 @@ if (showHoles) return difference(plate, cylinder(10, 5).translate(50, 30, 0));
486
503
  return plate;
487
504
  ```
488
505
 
489
- Override via import:
490
-
491
- ```ts
492
- const pan = require("./pan.forge.js", { "Show Lid": 0 });
493
- ```
494
-
495
- #### `Param.choice(name: string, defaultValue: string, choices: string[]): string` — Declare a choice parameter that renders as a dropdown in the UI.
506
+ #### `Param.choice(name: string, defaultValue: string, choices: string[], opts?: ParamAnchorableOptions): string` — Declare a choice parameter that renders as a dropdown in the UI.
496
507
 
497
508
  `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.
498
509
 
499
- Overrides may be passed as the choice label string (preferred) or as a numeric index. The `name` string is the override key.
510
+ CLI overrides may be passed as the choice label string (preferred) or as a numeric index.
500
511
 
501
512
  ```ts
502
513
  const panStyle = Param.choice("Pan Style", "frying-pan", ["frying-pan", "saute-pan", "wok"]);
503
514
  if (panStyle === "wok") return buildWok();
504
515
  ```
505
516
 
506
- Override via import:
507
-
508
- ```ts
509
- const pan = require("./pan.forge.js", { "Pan Style": "wok" });
510
- ```
511
-
512
517
  Override via CLI:
513
518
 
514
519
  ```bash
@@ -527,6 +532,90 @@ Field types:
527
532
 
528
533
  `ListParamFieldDef`: `{ min?: number, max?: number, step?: number, unit?: string, integer?: boolean, boolean?: boolean, choices?: string[] }`
529
534
 
535
+ #### `Param.path2d(name: string, defaultPoints: Path2DPointInput[], opts?: Path2DParamOptions): Path2DParamValue` — Declare an editable 2D path parameter.
536
+
537
+ 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.
538
+
539
+ Override keys use the same explicit row-field form as list params: `Path Name[0].x`, `Path Name[0].y`, and `Path Name.__count__`.
540
+
541
+ ```ts
542
+ const outline = Param.path2d("Bracket Outline", [
543
+ [-40, -20],
544
+ [40, -20],
545
+ [36, 24],
546
+ [-30, 28],
547
+ ], {
548
+ closed: true,
549
+ anchor: Param.anchor.sheetOnXY([0, 0, 8], { label: "Bracket outline" }),
550
+ });
551
+
552
+ return outline.toSketch().filletCorners(4).extrude(5);
553
+ ```
554
+
555
+ **`Path2DParamOptions`** extends ParamAnchorableOptions: `closed?: boolean`, `minPoints?: number`, `maxPoints?: number`, `x?: Partial<Path2DParamAxisDef>`, `y?: Partial<Path2DParamAxisDef>`, `unit?: string`
556
+
557
+ **`Path2DParamAxisDef`**
558
+ - `min: number` — Initial editor-frame minimum. Points may move outside this range on the infinite canvas.
559
+ - `max: number` — Initial editor-frame maximum. Points may move outside this range on the infinite canvas.
560
+ - Also: `step: number`.
561
+
562
+ #### `Param.spline2d(name: string, defaultPoints: Spline2DPointInput[], opts?: Spline2DParamOptions): Spline2DParamValue` — Declare an editable 2D spline parameter.
563
+
564
+ 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:
565
+
566
+ - `"G0"` starts/ends a hard curve segment at that point
567
+ - `"G1"` keeps the point in a tangent-smooth run
568
+ - `"G2"` keeps the point in a curvature-smooth cubic run
569
+
570
+ Override keys use explicit row-field form: `Curve Name[0].x`, `Curve Name[0].y`, `Curve Name[0].g`, and `Curve Name.__count__`.
571
+
572
+ ```ts
573
+ const spine = Param.spline2d("Handle Spine", [
574
+ { x: 0, y: 0, g: "G2" },
575
+ { x: 35, y: 8, g: "G2" },
576
+ { x: 80, y: 2, g: "G1" },
577
+ ], {
578
+ anchor: Param.anchor.sheetOnXZ([0, -18, 0], { label: "Side-view spine" }),
579
+ });
580
+
581
+ return sweep(circle2d(2), spine.toCurveOnXZ());
582
+ ```
583
+
584
+ **`Spline2DParamOptions`** extends ParamAnchorableOptions: `closed?: boolean`, `degree?: number`, `defaultContinuity?: Spline2DContinuity`, `minPoints?: number`, `maxPoints?: number`, `x?: Partial<Path2DParamAxisDef>`, `y?: Partial<Path2DParamAxisDef>`, `unit?: string`
585
+
586
+ #### `Param.placement2d(name: string, spec: Placement2DParamOptions): Placement2DParamValue` — Declare an editable 2D placement sheet parameter.
587
+
588
+ 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.
589
+
590
+ 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.
591
+
592
+ Override keys are item-ID based: `Layout.battery.x`, `Layout.battery.y`, `Layout.battery.angle`, and `Layout.battery.zone`.
593
+
594
+ ```ts
595
+ const layout = Param.placement2d("Internal Layout", {
596
+ frame: { size: [120, 80] },
597
+ items: [
598
+ { id: "battery", footprint: { type: "rect", size: [42, 24] }, at: [-25, 0] },
599
+ { id: "speaker", footprint: { type: "circle", radius: 12 }, at: [32, 8] },
600
+ ],
601
+ rules: { bounds: "prevent", collisions: "warn", snap: 1 },
602
+ anchor: Param.anchor.sheetOnXY([0, 0, 12], { label: "Internal layout" }),
603
+ });
604
+
605
+ const battery = layout.item("battery");
606
+ const batteryPocket = box(42, 24, 6).translate(battery.x, battery.y, 3);
607
+ ```
608
+
609
+ **`Placement2DParamOptions`** extends ParamAnchorableOptions: `frame?: Placement2DFrameInput`, `zones?: Placement2DZoneInput[]`, `items: Placement2DItemInput[]`, `rules?: Partial<Placement2DRulesDef>`, `unit?: string`
610
+
611
+ `Placement2DFrameInput`: `{ width?: number, height?: number, size?: Vec2, center?: Placement2DPointInput, at?: Placement2DPointInput }`
612
+
613
+ `Placement2DZoneInput`: `{ id: string, label?: string, frame?: Placement2DFrameInput }`
614
+
615
+ **`Placement2DItemInput`**: `id: string`, `label?: string`, `footprint: Placement2DFootprintInput`, `at?: Placement2DPointInput`, `center?: Placement2DPointInput`, `angle?: number`, `zone?: string`, `locked?: boolean`
616
+
617
+ `Placement2DRulesDef`: `{ bounds: Placement2DRuleMode, collisions: Placement2DRuleMode, snap: number }`
618
+
530
619
  ### Grouping & Local Coordinates
531
620
 
532
621
  #### `group(...items: GroupInput[]): ShapeGroup` — Group multiple shapes/sketches for joint transforms without merging into a single mesh.
@@ -1204,7 +1293,7 @@ cylinder(60, 20).wrapTexture(label, Wrap.aroundCylinder({ axis: 'z' })); // wra
1204
1293
 
1205
1294
  #### `ref(path: string): ShapeRef` — Resolve a semantic reference path like `lid`, `lid/back`, or a midpoint selector on `lid/back`.
1206
1295
 
1207
- #### `thicken(thickness: number): Shape` — Offset-thicken an exact open surface or shell into a solid.
1296
+ #### `thicken(thickness: ThicknessInput): Shape` — Offset-thicken an exact open surface or shell into a solid.
1208
1297
 
1209
1298
  #### `getMesh(): ShapeRuntimeMesh` — Extract triangle mesh for Three.js rendering
1210
1299
 
@@ -1444,6 +1533,99 @@ const bracket = group(
1444
1533
  | `depth?` | `number` | Thread groove depth in millimeters. Default: 0.8. |
1445
1534
  | `underScale?` | `number` | Relative height of the under-crossing thread. Default: 0.15. |
1446
1535
 
1536
+ ### `Path2DParamValue`
1537
+
1538
+ Runtime value returned by `Param.path2d()`.
1539
+
1540
+ Use closed paths as editable profile outlines via `toSketch()`. Use open paths as editable centerlines via `toStroke(width)`.
1541
+
1542
+ **Properties:**
1543
+
1544
+ | Property | Type | Description |
1545
+ |----------|------|-------------|
1546
+ | `closed` | `boolean` | True when this path is intended to close back to its first point. |
1547
+
1548
+ **Methods:**
1549
+
1550
+ #### `points(): Vec2[]` — Return the current points as `[x, y]` pairs in the editor coordinate system.
1551
+
1552
+ #### `toSketch(): Sketch` — Convert a closed editable path into a sketch profile.
1553
+
1554
+ 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.
1555
+
1556
+ #### `toStroke(width: number, join?: "Round" | "Square"): Sketch` — Convert an editable path into a stroked sketch with physical width.
1557
+
1558
+ 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.
1559
+
1560
+ ### `Spline2DParamValue`
1561
+
1562
+ Runtime value returned by `Param.spline2d()`.
1563
+
1564
+ 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.
1565
+
1566
+ **Properties:**
1567
+
1568
+ | Property | Type | Description |
1569
+ |----------|------|-------------|
1570
+ | `closed` | `boolean` | True when this spline is intended to close back to its first point. |
1571
+ | `degree` | `number` | Requested fitting degree before any automatic reduction for short spans. |
1572
+
1573
+ **Methods:**
1574
+
1575
+ #### `points(): Vec2[]` — Return the current control nodes as `[x, y]` pairs without continuity metadata.
1576
+
1577
+ #### `nodes(): Spline2DPointDef[]` — Return the current editable nodes, including per-point `g` continuity values.
1578
+
1579
+ #### `continuities(): Spline2DContinuity[]` — Return only the per-node continuity intents in point order.
1580
+
1581
+ #### `toPolyline(samples?: number): Vec2[]` — Sample the spline into 2D `[x, y]` points.
1582
+
1583
+ Use this when downstream code needs a polyline instead of a curve object.
1584
+
1585
+ #### `toCurveOnXY(z?: number, options?: Spline2DCurveOptions): NurbsCurve3D` — Fit the editable spline as a 3D curve on the XY plane at constant `z`.
1586
+
1587
+ The point's `x` maps to world X and `y` maps to world Y.
1588
+
1589
+ `Spline2DCurveOptions`: `{ degree?: number, tolerance?: number, samples?: number }`
1590
+
1591
+ #### `toCurveOnXZ(y?: number, options?: Spline2DCurveOptions): NurbsCurve3D` — Fit the editable spline as a 3D curve on the XZ plane at constant `y`.
1592
+
1593
+ 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.
1594
+
1595
+ #### `toCurveOnYZ(x?: number, options?: Spline2DCurveOptions): NurbsCurve3D` — Fit the editable spline as a 3D curve on the YZ plane at constant `x`.
1596
+
1597
+ The point's `x` maps to world Y and `y` maps to world Z.
1598
+
1599
+ #### `toCurveSegmentsOnXY(z?: number, options?: Spline2DCurveOptions): NurbsCurve3D[]` — Fit the spline on XY and return separate curve segments split at `G0` nodes.
1600
+
1601
+ Use segment output when a hard break should remain visible to downstream code instead of being joined into one continuous curve.
1602
+
1603
+ #### `toCurveSegmentsOnXZ(y?: number, options?: Spline2DCurveOptions): NurbsCurve3D[]` — Fit the spline on XZ and return separate curve segments split at `G0` nodes.
1604
+
1605
+ #### `toCurveSegmentsOnYZ(x?: number, options?: Spline2DCurveOptions): NurbsCurve3D[]` — Fit the spline on YZ and return separate curve segments split at `G0` nodes.
1606
+
1607
+ #### `toPathOnXY(z?: number, options?: Spline2DCurveOptions): Vec3[]` — Sample the spline as 3D points on the XY plane at constant `z`.
1608
+
1609
+ This is useful for sweeps and surface helpers that accept point paths.
1610
+
1611
+ #### `toPathOnXZ(y?: number, options?: Spline2DCurveOptions): Vec3[]` — Sample the spline as 3D points on the XZ plane at constant `y`.
1612
+
1613
+ #### `toPathOnYZ(x?: number, options?: Spline2DCurveOptions): Vec3[]` — Sample the spline as 3D points on the YZ plane at constant `x`.
1614
+
1615
+ ### `Placement2DParamValue`
1616
+
1617
+ Runtime value returned by `Param.placement2d()`.
1618
+
1619
+ 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.
1620
+
1621
+ #### `items(): Placement2DItemPlacement[]` — Return all current item placements as immutable copies.
1622
+
1623
+ #### `positions(): Record<string, Placement2DItemPlacement>` — Return current placements keyed by item id for table-style lookup.
1624
+
1625
+ #### `item(id: string): Placement2DItemPlacement` — Return one named item placement.
1626
+
1627
+ Throws if `id` was not declared in the sheet, which keeps model code tied to stable semantic item IDs rather than fragile list indices.
1628
+
1447
1629
  ### `CurveNetBuilder`
1448
1630
 
1449
1631
  #### `alongRails(railA: CurveInput, railB: CurveInput): this` — Use two lengthwise boundary curves as guide rails.
@@ -1515,6 +1697,7 @@ Pass `{ edge }` to match an adjacent sheet's tangent (G1) or curvature (G2), or
1515
1697
  - `pathAlongU(v: number, options?: SheetPathAlongOptions): Vec3[]`
1516
1698
  - `pathAlongV(u: number, options?: SheetPathAlongOptions): Vec3[]`
1517
1699
  - `thicken(wall: number, options?: { resolution?: number; }): Shape`
1700
+ - `thickenInsideBy(thickness: ThicknessInput, options?: { resolution?: number; }): Shape`
1518
1701
  - `matchEdge(edge: SheetEdge): MatchEdgeBuilder`
1519
1702
 
1520
1703
  **`SheetFrameOptions`**
@@ -39,6 +39,7 @@ Smooth curves, lofted surfaces, swept solids, splines, and high-level product sk
39
39
  - [Surface](#surface)
40
40
  - [Blend](#blend)
41
41
  - [Analysis](#analysis)
42
+ - [Thickness](#thickness)
42
43
  - [Product](#product)
43
44
  - [Carrier](#carrier)
44
45
  - [SurfaceMembers](#surfacemembers)
@@ -648,6 +649,10 @@ Use this when the guide path runs in the surface V direction and the sketch prof
648
649
 
649
650
  #### `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).
650
651
 
652
+ #### `thickenInsideBy(thickness: ThicknessInput, options?: { resolution?: number; }): Shape` — Thicken this sheet inward by a scalar UV thickness field.
653
+
654
+ Numeric input delegates to `Sheet.thicken()`. `Thickness.*` fields produce a sampled solid because a variable normal offset is not generally an exact NURBS surface.
655
+
651
656
  #### `matchEdge(edge: SheetEdge): MatchEdgeBuilder` — Per-edge continuity match against a neighbor (returns a NEW Sheet).
652
657
 
653
658
  - `get rearEdge(): SheetEdge`
@@ -1523,6 +1528,14 @@ Members (full entries under [Curves & Surfacing](#curves-surfacing)): `Curve.Ble
1523
1528
  - `SurfaceHealth(shape: Shape, options?: { tinyEdgeThreshold?: number; sliverThreshold?: number; }): SurfaceHealthReport`
1524
1529
  - `BRepValidity(shape: Shape, options?: BRepValidityOptions): BRepValidityReport` — Validate B-rep/shell/solid structure and return closedness, manifoldness, orientation, and issue diagnostics.
1525
1530
 
1531
+ ### `Thickness`
1532
+
1533
+ - `constant: (thickness: number) => ThicknessField` — Use the same wall thickness everywhere on the sheet.
1534
+ - `alongU: (profile: ThicknessStation[] | ThicknessStationProfile) => ThicknessField` — Vary wall thickness across the sheet U direction.
1535
+ - `alongV: (profile: ThicknessStation[] | ThicknessStationProfile) => ThicknessField` — Vary wall thickness across the sheet V direction.
1536
+ - `grid: (values: number[][], options?: ThicknessGridOptions) => ThicknessField` — Bilinearly interpolate wall thickness from a rectangular UV grid.
1537
+ - `nurbs: (values: number[][], options?: ThicknessNurbsOptions) => ThicknessField` — Interpolate wall thickness from a scalar tensor-product B-spline over sheet UV.
1538
+
1526
1539
  ### `Product`
1527
1540
 
1528
1541
  - `skin(name: string): ProductSkinBuilder` — Start a named product skin builder.
@@ -28,8 +28,8 @@ rect, Rectangle2D, roundedRect, Route3D, scene, Sculpt, sdf, SdfShape
28
28
  selectEdge, selectEdges, self, setActiveBackend, setImmediate, setInterval, setTimeout, Shape
29
29
  ShapeGroup, sheetMetal, SheetMetalPart, sheetStock, Sim, Sketch, sketchToDxf, sketchToSvg
30
30
  slot, SolvedAssembly, spec, sphere, spline2d, stroke, Surface, SurfaceBody
31
- SurfaceMembers, sweep, text2d, textWidth, torus, toShape, Transform, union
32
- union2d, variableSweep, verify, Viewport, window, Wood, Wrap
31
+ SurfaceMembers, sweep, text2d, textWidth, Thickness, torus, toShape, Transform
32
+ union, union2d, variableSweep, verify, Viewport, window, Wood, Wrap
33
33
  ```
34
34
 
35
35
  `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.
@@ -5,7 +5,7 @@ skill-order: 2
5
5
 
6
6
  # Inspection Bundles — Evidence Contract
7
7
 
8
- `forgecad inspect <family> <mode>` writes a deterministic bundle: evidence PNGs under `evidence/<type>/` plus a root `manifest.json`. **The manifest is the authoritative contract** — take file paths, encodings, per-view ranges, thresholds, tolerances, and identity maps from it; never hard-code bundle layout or infer object identity from object order. The PNGs are a visual index for locating findings, not standalone artifacts. Command tree, flags, and `--focus`/`--hide` filtering live in `docs/skill/CLI.md` and `forgecad inspect evidence`; the inspection workflow lives in the `forgecad-inspect-model` skill. Model-authored `scene()` background, lights, fog, and exposure are ignored for inspection captures so evidence stays stable; named scene views remain available via `--view`.
8
+ `forgecad inspect <family> <mode>` writes a deterministic bundle: evidence PNGs under `evidence/<type>/` plus a root `manifest.json`. **The manifest is the authoritative contract** — take file paths, encodings, per-view ranges, thresholds, tolerances, and identity maps from it; never hard-code bundle layout or infer object identity from object order. The PNGs are a visual index for locating findings, not standalone artifacts. Command tree, flags, and `--focus`/`--hide` filtering live in `docs/skill/CLI.md` and `forgecad inspect evidence`; the model-building inspection workflow lives in `forgecad-build-model/references/inspection-feedback.md`. Model-authored `scene()` background, lights, fog, and exposure are ignored for inspection captures so evidence stays stable; named scene views remain available via `--view`.
9
9
 
10
10
  Manifest evidence keys are evidence-oriented and stable for bundle readers: e.g. `fit interference` writes `manifest.evidence.collisions`, `physical components` writes `manifest.evidence.connectivity`.
11
11