forgecad 0.10.4 → 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 (121) hide show
  1. package/dist/assets/{AdminPage-B3L3W1Uo.js → AdminPage-B1nIvqLS.js} +1 -1
  2. package/dist/assets/{BenchmarkPage-DXKVXMrJ.js → BenchmarkPage-YZJbw5nd.js} +2 -2
  3. package/dist/assets/{BlogPage-B7BWxOCg.js → BlogPage-DIWRApKS.js} +1 -1
  4. package/dist/assets/{DocsPage-BPGGwht1.js → DocsPage-ClL6X1hR.js} +8 -22
  5. package/dist/assets/EditorApp-CYBDvSyT.js +17067 -0
  6. package/dist/assets/{EmbedViewer-DygByZS2.js → EmbedViewer-Dmfu_LIw.js} +2 -2
  7. package/dist/assets/{LandingPageProofDriven-BoVE7JGY.js → LandingPageProofDriven-XYTiYxfM.js} +2 -2
  8. package/dist/assets/{LegalPage-Din8wv8d.js → LegalPage-D5Z3CscF.js} +2 -2
  9. package/dist/assets/{PricingPage-C2PMzmDc.js → PricingPage-BP4lIGio.js} +2 -2
  10. package/dist/assets/{SettingsPage-BlJDCRe8.js → SettingsPage-D3bcPBsC.js} +1 -1
  11. package/dist/assets/{app-BsRYSfxY.js → app-BKjogwIZ.js} +3288 -512
  12. package/dist/assets/{backendInit-6C0DLgH0.js → backendInit-6a9-ilom.js} +80498 -74979
  13. package/dist/assets/cli/{render-XXol_ET7.js → render-CMNudGb0.js} +1264 -113
  14. package/dist/assets/{constructionHistoryWorker-cTHWRJEi.js → constructionHistoryWorker-BuZgc606.js} +8369 -6839
  15. package/dist/assets/{evalWorker-BssDYW9u.js → evalWorker-DQ82ueGu.js} +45438 -39996
  16. package/dist/assets/{forgecad_geometry-CZ_IfuvA.js → forgecad_geometry-D8rWX7nQ.js} +1 -1
  17. package/dist/assets/{forgecad_geometry_bg-C3rQHfwg.wasm → forgecad_geometry_bg-ObqfqjJT.wasm} +0 -0
  18. package/dist/assets/{inspectWorker-ymhBV4Ll.js → inspectWorker-Cuby2qfT.js} +4899 -1303
  19. package/dist/assets/{jointPose-B0blBj9A.js → jointPose-CFql5I-u.js} +1 -1
  20. package/dist/assets/{landing-proof-driven-Cpf-MIbI.css → landing-proof-driven-_u4v_xQb.css} +2 -2
  21. package/dist/assets/{manifold-CYlIm-M6.js → manifold-02pmr7O7.js} +2 -2
  22. package/dist/assets/{manifold-B_7QXpGB.js → manifold-C6KU0oII.js} +1 -1
  23. package/dist/assets/{manifold-CNShmpEJ.js → manifold-P1yF3GKn.js} +1 -1
  24. package/dist/assets/{reportWorker-Cb5eyM7D.js → reportWorker-kg065BVL.js} +76583 -65731
  25. package/dist/cli/render.html +1 -1
  26. package/dist/docs/index.html +2 -2
  27. package/dist/docs-raw/AI/usage.md +6 -8
  28. package/dist/docs-raw/CLI.md +14 -12
  29. package/dist/docs-raw/component-model.md +28 -9
  30. package/dist/docs-raw/generated/assembly.md +76 -3
  31. package/dist/docs-raw/generated/concepts.md +43 -7
  32. package/dist/docs-raw/generated/core.md +399 -73
  33. package/dist/docs-raw/generated/curves.md +357 -6
  34. package/dist/docs-raw/generated/runtime-names.md +12 -12
  35. package/dist/docs-raw/generated/sketch.md +16 -3
  36. package/dist/docs-raw/guides/inspection-bundles.md +5 -3
  37. package/dist/docs-raw/guides/structural-fea.md +235 -0
  38. package/dist/docs-raw/skills/forgecad-build-model.md +70 -147
  39. package/dist/docs-raw/skills/forgecad-image-prompt.md +1 -1
  40. package/dist/docs-raw/skills/forgecad-project-sync.md +3 -3
  41. package/dist/docs-raw/skills/forgecad-reconstruct-cad-file.md +2 -2
  42. package/dist/docs-raw/skills/forgecad-reconstruct-from-images.md +4 -5
  43. package/dist/docs-raw/skills/forgecad.md +4 -1
  44. package/dist/docs-raw/skills/index.md +1 -5
  45. package/dist/docs-raw/welcome.md +3 -4
  46. package/dist/index.html +1 -1
  47. package/dist/llms.txt +1 -2
  48. package/dist/sitemap.xml +15 -15
  49. package/dist-cli/{check-compiler-4RPB6SB5.js → check-compiler-UJWUEIDC.js} +1 -1
  50. package/dist-cli/{check-query-propagation-KN3DFQTX.js → check-query-propagation-O2EPDJSY.js} +1 -1
  51. package/dist-cli/{chunk-UHBRMYA6.js → chunk-MNDROM7T.js} +78926 -73392
  52. package/dist-cli/forgecad.js +6306 -1061
  53. package/dist-cli/forgecad_geometry_bg.wasm +0 -0
  54. package/dist-skill/CONTEXT.md +1257 -110
  55. package/dist-skill/SKILL.md +4 -1
  56. package/dist-skill/docs/API/core/concepts.md +31 -4
  57. package/dist-skill/docs/CLI.md +14 -12
  58. package/dist-skill/docs/generated/assembly.md +73 -3
  59. package/dist-skill/docs/generated/core.md +395 -74
  60. package/dist-skill/docs/generated/curves.md +356 -6
  61. package/dist-skill/docs/generated/runtime-names.md +12 -12
  62. package/dist-skill/docs/generated/sketch.md +16 -3
  63. package/dist-skill/docs/guides/inspection-bundles.md +5 -3
  64. package/dist-skill/docs/guides/manual-parameters.md +130 -0
  65. package/dist-skill/docs/guides/structural-fea.md +235 -0
  66. package/dist-skill/library/README.md +0 -4
  67. package/dist-skill/library/forgecad-build-model/SKILL.md +57 -150
  68. package/dist-skill/library/forgecad-build-model/references/inspection-feedback.md +58 -0
  69. package/dist-skill/library/forgecad-build-model/references/module-contracts.md +53 -0
  70. package/dist-skill/library/forgecad-build-model/references/parameter-controls.md +22 -0
  71. package/dist-skill/library/forgecad-build-model/references/readiness-review.md +43 -0
  72. package/dist-skill/library/forgecad-build-model/references/simulation-feedback.md +49 -0
  73. package/dist-skill/library/forgecad-build-model/references/stage-1-design-intent.md +21 -0
  74. package/dist-skill/library/forgecad-build-model/references/stage-2-architecture-plan.md +23 -0
  75. package/dist-skill/library/forgecad-build-model/references/stage-3-build-slices.md +39 -0
  76. package/dist-skill/library/forgecad-build-model/references/stage-4-feedback-iteration.md +24 -0
  77. package/dist-skill/library/forgecad-build-model/references/stage-5-readiness-package.md +34 -0
  78. package/dist-skill/library/forgecad-image-prompt/SKILL.md +1 -1
  79. package/dist-skill/library/forgecad-project-sync/SKILL.md +3 -3
  80. package/dist-skill/library/forgecad-reconstruct-cad-file/SKILL.md +2 -2
  81. package/dist-skill/library/forgecad-reconstruct-from-images/SKILL.md +4 -5
  82. package/dist-skill/website/skills/forgecad-build-model.md +70 -147
  83. package/dist-skill/website/skills/forgecad-image-prompt.md +1 -1
  84. package/dist-skill/website/skills/forgecad-project-sync.md +3 -3
  85. package/dist-skill/website/skills/forgecad-reconstruct-cad-file.md +2 -2
  86. package/dist-skill/website/skills/forgecad-reconstruct-from-images.md +4 -5
  87. package/dist-skill/website/skills/forgecad.md +4 -1
  88. package/dist-skill/website/skills/index.md +1 -5
  89. package/examples/analysis/structural-stress-fea.forge.js +19 -0
  90. package/examples/api/blend-full-round.forge.js +37 -0
  91. package/examples/api/blend-variable-radius.forge.js +51 -0
  92. package/examples/api/curve-project-and-intersect.forge.js +59 -0
  93. package/examples/api/extrude-up-to-face.forge.js +47 -0
  94. package/examples/api/param-path2d.forge.js +65 -0
  95. package/examples/api/param-placement2d.forge.js +80 -0
  96. package/examples/api/param-spline2d-g-continuity.forge.js +57 -0
  97. package/examples/api/spoon-full-tang-handle.forge.js +188 -0
  98. package/examples/api/surface-boundarynet-dished-bowl.forge.js +63 -0
  99. package/examples/api/surface-fill-interior-constraints.forge.js +59 -0
  100. package/examples/api/surface-variable-thickness-panel.forge.js +62 -0
  101. package/examples/mechanical/airplane-propeller.forge.js +81 -28
  102. package/package.json +5 -2
  103. package/dist/assets/EditorApp-BWUGCdD5.js +0 -16610
  104. package/dist/docs-raw/skills/forgecad-design-spec.md +0 -145
  105. package/dist/docs-raw/skills/forgecad-grade-model.md +0 -84
  106. package/dist/docs-raw/skills/forgecad-inspect-model.md +0 -80
  107. package/dist/docs-raw/skills/forgecad-verify-mujoco.md +0 -78
  108. package/dist-skill/library/forgecad-design-spec/SKILL.md +0 -132
  109. package/dist-skill/library/forgecad-design-spec/references/default-profiles.md +0 -99
  110. package/dist-skill/library/forgecad-design-spec/references/master-prompt.md +0 -73
  111. package/dist-skill/library/forgecad-grade-model/SKILL.md +0 -72
  112. package/dist-skill/library/forgecad-grade-model/agents/openai.yaml +0 -4
  113. package/dist-skill/library/forgecad-inspect-model/SKILL.md +0 -68
  114. package/dist-skill/library/forgecad-verify-mujoco/SKILL.md +0 -66
  115. package/dist-skill/website/skills/forgecad-design-spec.md +0 -145
  116. package/dist-skill/website/skills/forgecad-grade-model.md +0 -84
  117. package/dist-skill/website/skills/forgecad-inspect-model.md +0 -80
  118. package/dist-skill/website/skills/forgecad-verify-mujoco.md +0 -78
  119. /package/dist/assets/{landing-proof-driven-BxZZh5r5.js → landing-proof-driven-DNPRKL_p.js} +0 -0
  120. /package/dist-skill/library/{forgecad-verify-mujoco → forgecad-build-model}/scripts/mujoco_verify.py +0 -0
  121. /package/dist-skill/library/{forgecad-inspect-model → forgecad-build-model/scripts}/summarize_manifest.py +0 -0
@@ -18,13 +18,15 @@ skill-order: 100
18
18
  - [Grouping & Local Coordinates](#grouping-local-coordinates)
19
19
  - [Section & Projection](#section-projection)
20
20
  - [Verification](#verification)
21
- - [Shape](#shape) — Appearance, Face Topology, Edge Topology, Transforms, Booleans & Cutting, Features, Placement, Connectors, References, Measurement
21
+ - [Shape](#shape) — Freeform Construction, Appearance, Face Topology, Edge Topology, Transforms, Booleans & Cutting, Features, Placement, Connectors, References, Measurement
22
22
  - [Transform](#transform)
23
23
  - [ShapeGroup](#shapegroup) — Children, Transforms, Placement, Connectors, References
24
24
  - [SurfacePattern](#surfacepattern)
25
25
  - [Pattern2D](#pattern2d)
26
26
  - [Pattern2DBuilder](#pattern2dbuilder)
27
- - [Sheet](#sheet)
27
+ - [Path2DParamValue](#path2dparamvalue)
28
+ - [Spline2DParamValue](#spline2dparamvalue)
29
+ - [Placement2DParamValue](#placement2dparamvalue)
28
30
  - [CurveNetBuilder](#curvenetbuilder)
29
31
  - [MatchEdgeBuilder](#matchedgebuilder)
30
32
  - [BridgeBuilder](#bridgebuilder)
@@ -353,11 +355,11 @@ for (const edge of coalesceEdges(topEdges)) {
353
355
 
354
356
  ### Imports & Composition
355
357
 
356
- #### `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.
357
359
 
358
- 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).
359
361
 
360
- **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.
361
363
 
362
364
  **Assembly return contract**
363
365
 
@@ -370,52 +372,56 @@ When importing a `.forge.js` file, most return values are passed through exactly
370
372
 
371
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`.
372
374
 
373
- **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.
374
-
375
- **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.
376
-
377
- **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:
378
376
 
379
377
  ```js
380
378
  // assembly.forge.js — owns cross-cutting params, passes to parts
381
379
  const wall = param("Wall", 3);
382
380
  const baseH = param("Base Height", 20);
383
381
 
384
- const mount = require('./motor-mount.forge.js', { Wall: wall });
385
- 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 });
386
387
  ```
387
388
 
388
- **Metadata pattern** — parts publish interface data alongside geometry:
389
+ **Builder result pattern** — parts publish interface data alongside geometry:
389
390
 
390
391
  ```js
391
392
  // motor-mount.forge.js
392
- 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
+ }
393
397
 
394
398
  // base-body.forge.js
395
399
  const mount = require('./motor-mount.forge.js');
396
- mount.bolts.pos // access the metadata
397
- mount.shape // access the geometry
400
+ const built = mount.buildMount({ wall: 3 });
401
+ built.boltPattern // access interface data
402
+ built.shape // access geometry
398
403
  ```
399
404
 
400
405
  **Forge-aware builder module pattern** — use `.forge.js` modules for reusable sketch, profile, shape, or assembly builders that need ForgeCAD runtime APIs:
401
406
 
402
407
  ```js
403
- // profiles.forge.js — inspectable on its own, reusable through require()
408
+ // profiles.forge.js
404
409
  function wheelProfile() {
405
410
  return circle2d(40).subtract(circle2d(18));
406
411
  }
407
412
 
408
- return {
409
- preview: [{ name: 'Wheel profile', sketch: wheelProfile() }],
410
- make: { wheelProfile },
411
- };
413
+ if (require.main === module) {
414
+ return [{ name: 'Wheel profile', sketch: wheelProfile() }];
415
+ }
416
+
417
+ return { wheelProfile };
412
418
 
413
419
  // main.forge.js
414
420
  const profiles = require('./profiles.forge.js');
415
- const wheel = profiles.make.wheelProfile().extrude(8);
421
+ const wheel = profiles.wheelProfile().extrude(8);
416
422
  ```
417
423
 
418
- 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.
419
425
 
420
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:
421
427
 
@@ -423,16 +429,30 @@ Keep exported builders pure over top-level constants, top-level `param()` values
423
429
  // part.forge.js
424
430
  function bracket() { ... }
425
431
  if (require.main === module) {
426
- return { preview: [{ name: 'Bracket', shape: bracket() }] }; // direct run: render it
432
+ return bracket({ width: param('Width', 80), height: param('Height', 40) });
427
433
  }
428
- return { make: { bracket } }; // imported: builders only
434
+ return { bracket };
429
435
  ```
430
436
 
431
437
  ### Parameters
432
438
 
433
- #### `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.
442
+
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.
434
450
 
435
- 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.
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.
436
456
 
437
457
  Default range rules when options are omitted:
438
458
 
@@ -448,38 +468,34 @@ const angle = Param.number("Angle", 45, { min: 0, max: 180, unit: "°" });
448
468
  const sides = Param.number("Sides", 6, { min: 3, max: 12, integer: true });
449
469
  ```
450
470
 
451
- **Parameter overrides** key must match `name` exactly:
452
-
453
- ```ts
454
- // Via require()
455
- const bracket = require("./bracket.forge.js", { Width: 80 });
471
+ CLI overrides use the parameter name:
456
472
 
457
- // Via CLI
458
- // forgecad run model.forge.js --param "Wall Thickness=3"
473
+ ```bash
474
+ forgecad run model.forge.js --param "Wall Thickness=3"
459
475
  ```
460
476
 
461
477
  Also available as the shorthand alias `param()`.
462
478
 
463
- #### `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 }`
464
480
 
465
- 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 }`
466
482
 
467
- ```ts
468
- const label = Param.string("Label", "Hello World");
469
- const name = Param.string("Name", "Part-001", { maxLength: 20 });
470
- ```
483
+ #### `Param.string(name: string, defaultValue: string, opts?: StringParamOptions): string` — Declare a string parameter that renders as a text input in the UI.
471
484
 
472
- Override via import:
485
+ String parameters let users type free-form text — labels, names, inscriptions, file paths, etc.
473
486
 
474
487
  ```ts
475
- 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 });
476
490
  ```
477
491
 
478
492
  Only available as `Param.string()` — no standalone alias.
479
493
 
480
- #### `Param.bool(name: string, defaultValue: boolean): boolean` — Declare a boolean parameter that renders as a checkbox in the UI.
494
+ `StringParamOptions`: `{ maxLength?: number }`
481
495
 
482
- 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.
496
+ #### `Param.bool(name: string, defaultValue: boolean, opts?: ParamAnchorableOptions): boolean` Declare a boolean parameter that renders as a checkbox in the UI.
497
+
498
+ Internally stored as `0`/`1` for CLI overrides. Pass `1` for true and `0` for false.
483
499
 
484
500
  ```ts
485
501
  const showHoles = Param.bool("Show Holes", true);
@@ -487,29 +503,17 @@ if (showHoles) return difference(plate, cylinder(10, 5).translate(50, 30, 0));
487
503
  return plate;
488
504
  ```
489
505
 
490
- Override via import:
491
-
492
- ```ts
493
- const pan = require("./pan.forge.js", { "Show Lid": 0 });
494
- ```
495
-
496
- #### `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.
497
507
 
498
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.
499
509
 
500
- 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.
501
511
 
502
512
  ```ts
503
513
  const panStyle = Param.choice("Pan Style", "frying-pan", ["frying-pan", "saute-pan", "wok"]);
504
514
  if (panStyle === "wok") return buildWok();
505
515
  ```
506
516
 
507
- Override via import:
508
-
509
- ```ts
510
- const pan = require("./pan.forge.js", { "Pan Style": "wok" });
511
- ```
512
-
513
517
  Override via CLI:
514
518
 
515
519
  ```bash
@@ -528,6 +532,90 @@ Field types:
528
532
 
529
533
  `ListParamFieldDef`: `{ min?: number, max?: number, step?: number, unit?: string, integer?: boolean, boolean?: boolean, choices?: string[] }`
530
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
+
531
619
  ### Grouping & Local Coordinates
532
620
 
533
621
  #### `group(...items: GroupInput[]): ShapeGroup` — Group multiple shapes/sketches for joint transforms without merging into a single mesh.
@@ -705,6 +793,96 @@ Supports transforms (translate, rotate, scale, mirror, transform, rotateAround,
705
793
  |----------|------|-------------|
706
794
  | `materialProps` | `ShapeMaterialProps \| undefined` | — |
707
795
 
796
+ **Freeform Construction**
797
+
798
+ #### `slicePerpendicularToX(x: number, profile: Sketch, options?: FromSlicesAxisSliceOptions): FromSlicesSlice` — Create a slice descriptor perpendicular to the X axis.
799
+
800
+ The profile is drawn in the YZ plane. `options.center` is `[y, z]`, so authors can place changing section centers without manually translating sketches in ForgeCAD's internal plane axes.
801
+
802
+ ```js
803
+ Shape.fromSlices([
804
+ Shape.slicePerpendicularToX(-20, ellipse(10, 2), { center: [0, 3] }),
805
+ Shape.slicePerpendicularToX(20, ellipse(8, 1.5), { center: [0, 6] }),
806
+ ]);
807
+ ```
808
+
809
+ **`FromSlicesAxisSliceOptions`**
810
+ - `center?: FromSlicesVec2` — Plane-local profile center. XY uses [x, y], XZ uses [x, z], YZ uses [y, z].
811
+
812
+ #### `slicePerpendicularToY(y: number, profile: Sketch, options?: FromSlicesAxisSliceOptions): FromSlicesSlice` — Create a slice descriptor perpendicular to the Y axis.
813
+
814
+ The profile is drawn in the XZ plane. `options.center` is `[x, z]`.
815
+
816
+ #### `slicePerpendicularToZ(z: number, profile: Sketch, options?: FromSlicesAxisSliceOptions): FromSlicesSlice` — Create a slice descriptor perpendicular to the Z axis.
817
+
818
+ The profile is drawn in the XY plane. `options.center` is `[x, y]`.
819
+
820
+ #### `sliceThrough(center: FromSlicesVec3, normal: FromSlicesVec3, profile: Sketch): FromSlicesSlice` — Create a slice descriptor through a world point with an arbitrary plane normal.
821
+
822
+ The profile origin lands at `center`. Use this when the section plane is not one of the world XY/XZ/YZ planes.
823
+
824
+ #### `sliceOnFrame(frame: FromSlicesFrameInput, profile: Sketch): FromSlicesSlice` — Create a slice descriptor on a full 3D work frame.
825
+
826
+ Sheet frame helpers return the right shape for `frame`. Use `Sheet.frameAt()` for tangent construction planes, or `Sheet.framePerpendicularToU()` / `Sheet.framePerpendicularToV()` for cross-sections normal to a surface path. On the Manifold backend, framed slices are lofted in input order when every slice comes from a frame.
827
+
828
+ **`FromSlicesFrameInput`**
829
+
830
+ | Option | Type | Description |
831
+ |--------|------|-------------|
832
+ | `point?` | `FromSlicesVec3` | World-space frame origin. Sheet frame helpers return this as `point`. |
833
+ | `origin?` | `FromSlicesVec3` | Alias for `point` when using generic CAD frame terminology. |
834
+ | `normal` | `FromSlicesVec3` | World-space frame normal. |
835
+ | `tangentU?` | `FromSlicesVec3` | World-space direction for the profile's local X axis. Sheet frame helpers return this as `tangentU`. |
836
+ | `tangentV?` | `FromSlicesVec3` | Optional world-space direction for the profile's local Y axis. Sheet frame helpers return this as `tangentV`. |
837
+ | `xAxis?` | `FromSlicesVec3` | Alias for `tangentU`. |
838
+ | `yAxis?` | `FromSlicesVec3` | Alias for `tangentV`. |
839
+
840
+ #### `fromSlices(slices: FromSlicesSlice[], options?: FromSlicesOptions): Shape` — Construct a 3D shape from cross-section slices on one or more planes.
841
+
842
+ On the Manifold backend, slices created with `Shape.sliceOnFrame()` are lofted in their input order while preserving each full 3D frame. Other slices with the same normal direction are lofted together. Slices with different normals are combined via smooth radial blending — each silhouette constrains the shape's extent, producing smooth ellipsoidal cross-sections.
843
+
844
+ ```js
845
+ // Egg from two orthogonal silhouettes
846
+ const eggProfile = ellipse(15, 25);
847
+ return Shape.fromSlices([
848
+ { on: 'xz', at: 0, profile: eggProfile },
849
+ { on: 'yz', at: 0, profile: eggProfile },
850
+ ]);
851
+ ```
852
+
853
+ ```js
854
+ // Vase with cross-section transitions
855
+ return Shape.fromSlices([
856
+ Shape.slicePerpendicularToZ(0, circle2d(20)),
857
+ Shape.slicePerpendicularToZ(40, rect(25, 25)),
858
+ Shape.slicePerpendicularToZ(80, circle2d(8)),
859
+ Shape.slicePerpendicularToY(0, vaseOutline),
860
+ ]);
861
+ ```
862
+
863
+ **`FromSlicesSlice`**
864
+
865
+ | Option | Type | Description |
866
+ |--------|------|-------------|
867
+ | `on` | `SlicePlane` | Plane normal: axis name or arbitrary unit vector. |
868
+ | `at?` | `number` | Signed offset along the normal from the origin. Omit when `center` defines the plane. |
869
+ | `center?` | `FromSlicesVec3` | World-space point where the 2D profile origin should land on the slice plane. |
870
+ | `profile` | `Sketch` | 2D cross-section profile on that plane. |
871
+ | `frame?` | `FromSlicesFramePlacement` | Full 3D section frame, preserved for ordered lofts through rotating planes. |
872
+
873
+ **`FromSlicesFramePlacement`**
874
+
875
+ | Option | Type | Description |
876
+ |--------|------|-------------|
877
+ | `point` | `FromSlicesVec3` | World-space frame origin. |
878
+ | `normal` | `FromSlicesVec3` | World-space section normal. |
879
+ | `tangentU` | `FromSlicesVec3` | World-space direction for the profile's local X axis. |
880
+ | `tangentV` | `FromSlicesVec3` | World-space direction for the profile's local Y axis. |
881
+
882
+ **`FromSlicesOptions`**
883
+ - `edgeLength?: number` — Marching-grid edge length for level-set meshing (Manifold only).
884
+ - `boundsPadding?: number` — Extra bounding-box padding (Manifold only).
885
+
708
886
  **Appearance**
709
887
 
710
888
  #### `color(value: string | undefined): Shape` — Set the color of this shape (hex string, e.g. "#ff0000"). Returns a new Shape with the color applied.
@@ -1115,7 +1293,7 @@ cylinder(60, 20).wrapTexture(label, Wrap.aroundCylinder({ axis: 'z' })); // wra
1115
1293
 
1116
1294
  #### `ref(path: string): ShapeRef` — Resolve a semantic reference path like `lid`, `lid/back`, or a midpoint selector on `lid/back`.
1117
1295
 
1118
- #### `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.
1119
1297
 
1120
1298
  #### `getMesh(): ShapeRuntimeMesh` — Extract triangle mesh for Three.js rendering
1121
1299
 
@@ -1355,57 +1533,200 @@ const bracket = group(
1355
1533
  | `depth?` | `number` | Thread groove depth in millimeters. Default: 0.8. |
1356
1534
  | `underScale?` | `number` | Relative height of the under-crossing thread. Default: 0.15. |
1357
1535
 
1358
- ### `Sheet`
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.
1359
1559
 
1360
- A parametric open surface value (control grid + knots + analytic differential geometry).
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.
1361
1565
 
1362
1566
  **Properties:**
1363
1567
 
1364
1568
  | Property | Type | Description |
1365
1569
  |----------|------|-------------|
1366
- | `surface` | `BSplineSurface` | |
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. |
1367
1572
 
1368
1573
  **Methods:**
1369
1574
 
1370
- #### `get frontEdge(): SheetEdge` — Edge naming follows parameter direction (documented): front=v0, rear=v1, left=u0, right=u1.
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.
1371
1604
 
1372
- #### `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).
1605
+ #### `toCurveSegmentsOnYZ(x?: number, options?: Spline2DCurveOptions): NurbsCurve3D[]` — Fit the spline on YZ and return separate curve segments split at `G0` nodes.
1373
1606
 
1374
- #### `matchEdge(edge: SheetEdge): MatchEdgeBuilder` — Per-edge continuity match against a neighbor (returns a NEW Sheet).
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
+
1629
+ ### `CurveNetBuilder`
1630
+
1631
+ #### `alongRails(railA: CurveInput, railB: CurveInput): this` — Use two lengthwise boundary curves as guide rails.
1632
+
1633
+ Chain `.sections(...)` to create a bi-rail surface: the rails define the sheet edges while each section curve shapes the cross-span at its station.
1634
+
1635
+ #### `sections(...curves: CurveInput[]): this` — Add crosswise section curves.
1636
+
1637
+ By itself this skins the sections into a surface. After `.alongRails(...)`, the sections are fitted between the two rails so the surface follows both the boundary guide curves and the section profiles.
1638
+
1639
+ #### `resolution(samples: number): this` — Set the sampling resolution used to build curve-family surface grids.
1640
+
1641
+ This affects `.lengthwise(...)`, `.crosswise(...)`, and `.alongRails(...).sections(...)` surfaces. It does not resample explicit `.cage(grid)` input because the cage already is the authored control net.
1642
+
1643
+ #### `matchStartU(condition: BoundaryCondition): this` — Enforce a continuity condition on the `u = 0` (left) boundary.
1644
+
1645
+ Pass `{ edge }` to match an adjacent sheet's tangent (G1) or curvature (G2), or `{ tangent }` to impose an explicit cross-boundary direction. See `BoundaryCondition`.
1646
+
1647
+ **`BoundaryCondition`**
1648
+
1649
+ | Option | Type | Description |
1650
+ |--------|------|-------------|
1651
+ | `edge?` | `SheetEdge` | Match the tangent (G1) and curvature (G2) of an existing sheet edge across this boundary. |
1652
+ | `tangent?` | `Vec3` | Or impose an explicit cross-boundary tangent direction in world space (auto-normalized). |
1653
+ | `tangentScale?` | `number` | Scalar magnitude for the imposed `tangent` ramp, in model units. Ignored when `edge` is given. Default: the local cross-boundary control-span length (chord-scaled), so the imposed tangent has the same strength as the surface already carries — no magic number. |
1654
+ | `continuity?` | `0 \| 1 \| 2` | Continuity order to enforce on this side. Default inferred: 1 if a tangent or edge is given, else 0. G2 (curvature) requires an `edge` to copy the neighbor's second difference. |
1375
1655
 
1376
1656
  **`SheetEdge`**
1377
1657
  - `fixed: "u" | "v"` — Which parameter is held fixed along this edge.
1378
1658
  - `value: 0 | 1` — The fixed value (0 or 1).
1379
1659
  - Also: `sheet: Sheet`.
1380
1660
 
1381
- - `get rearEdge(): SheetEdge`
1382
- - `get leftEdge(): SheetEdge`
1383
- - `get rightEdge(): SheetEdge`
1384
- - `pointAt(u: number, v: number): Vec3`
1385
- - `normalAt(u: number, v: number): Vec3`
1386
- - `curvatureAt(u: number, v: number): SurfaceCurvature`
1661
+ #### `matchEndU(condition: BoundaryCondition): this` — Enforce a continuity condition on the `u = 1` (right) boundary. See `matchStartU`.
1387
1662
 
1388
- ### `CurveNetBuilder`
1663
+ #### `matchStartV(condition: BoundaryCondition): this` — Enforce a continuity condition on the `v = 0` (front) boundary. See `matchStartU`.
1664
+
1665
+ #### `matchEndV(condition: BoundaryCondition): this` — Enforce a continuity condition on the `v = 1` (rear) boundary. See `matchStartU`.
1666
+
1667
+ #### `closedU(): this` — Weld the two ends of the U direction into a tangent-continuous periodic loop, so the `u = 0` and `u = 1` boundaries coincide with NO G0 kink (a closed tube/ring in U — e.g. a bowl's around-rim seam). The cage's first and last U rows must already be coincident (the loop must close in position).
1668
+
1669
+ #### `closedV(): this` — Weld the two ends of the V direction into a tangent-continuous periodic loop. See `closedU`.
1389
1670
 
1390
1671
  #### `toSheet(): Sheet` — Build (once) and return the Sheet.
1391
1672
 
1392
1673
  - `lengthwise(...curves: CurveInput[]): this`
1393
1674
  - `crosswise(...curves: CurveInput[]): this`
1394
- - `alongRails(railA: CurveInput, railB: CurveInput): this`
1395
- - `sections(...curves: CurveInput[]): this`
1396
1675
  - `cage(grid: Vec3[][]): this`
1397
1676
  - `degree(u: number, v: number): this`
1398
1677
  - `get frontEdge(): SheetEdge`
1399
1678
  - `get rearEdge(): SheetEdge`
1400
1679
  - `get leftEdge(): SheetEdge`
1401
1680
  - `get rightEdge(): SheetEdge`
1681
+ - `get frontCurve(): NurbsCurve3D`
1682
+ - `get rearCurve(): NurbsCurve3D`
1683
+ - `get leftCurve(): NurbsCurve3D`
1684
+ - `get rightCurve(): NurbsCurve3D`
1402
1685
  - `get surface(): BSplineSurface`
1403
1686
  - `pointAt(u: number, v: number): Vec3`
1404
1687
  - `normalAt(u: number, v: number): Vec3`
1688
+ - `frameAt(u: number, v: number, options?: SheetFrameOptions): SheetFrame`
1689
+ - `framePerpendicularToU(u: number, v: number, options?: SheetFrameOptions): SheetFrame`
1690
+ - `framePerpendicularToV(u: number, v: number, options?: SheetFrameOptions): SheetFrame`
1405
1691
  - `curvatureAt(u: number, v: number): SurfaceCurvature`
1692
+ - `curveAlong(edge: SheetEdge): NurbsCurve3D`
1693
+ - `curveAlongU(v: number): NurbsCurve3D`
1694
+ - `curveAlongV(u: number): NurbsCurve3D`
1695
+ - `pathAlong(edge: SheetEdge, options?: SheetPathAlongOptions): Vec3[]`
1696
+ - `pathAlongBoundary(spans: SheetBoundaryPathSpan[], options?: SheetBoundaryPathOptions): Vec3[]`
1697
+ - `pathAlongU(v: number, options?: SheetPathAlongOptions): Vec3[]`
1698
+ - `pathAlongV(u: number, options?: SheetPathAlongOptions): Vec3[]`
1406
1699
  - `thicken(wall: number, options?: { resolution?: number; }): Shape`
1700
+ - `thickenInsideBy(thickness: ThicknessInput, options?: { resolution?: number; }): Shape`
1407
1701
  - `matchEdge(edge: SheetEdge): MatchEdgeBuilder`
1408
1702
 
1703
+ **`SheetFrameOptions`**
1704
+ - `normalOffset?: number` — Offset the frame origin along the analytic surface normal. Default 0.
1705
+
1706
+ **`SheetPathAlongOptions`**
1707
+
1708
+ | Option | Type | Description |
1709
+ |--------|------|-------------|
1710
+ | `samples?` | `number` | Samples along the path span. Default 32. |
1711
+ | `start?` | `number` | Normalized start parameter along the path. Default 0. |
1712
+ | `end?` | `number` | Normalized end parameter along the path. Default 1. |
1713
+ | `reverse?` | `boolean` | Return points from end to start after sampling the span. Default false. |
1714
+ | `normalOffset?` | `number` | Offset each path point along the analytic surface normal. Default 0. |
1715
+
1716
+ **`SheetBoundaryPathSpan`**
1717
+
1718
+ | Option | Type | Description |
1719
+ |--------|------|-------------|
1720
+ | `edge` | `SheetEdge` | Boundary edge to sample for this span. |
1721
+ | `start?` | `SheetPathParameter` | Normalized edge parameter or world point projected to the closest edge parameter. Default 0. |
1722
+ | `end?` | `SheetPathParameter` | Normalized edge parameter or world point projected to the closest edge parameter. Default 1. |
1723
+ | `samples?` | `number` | Samples along this edge span. Defaults to options.samplesPerEdge or 32. |
1724
+
1725
+ **`SheetBoundaryPathOptions`**
1726
+ - `samplesPerEdge?: number` — Samples for spans that do not specify their own count. Default 32.
1727
+ - `normalOffset?: number` — Offset each path point along the analytic surface normal. Default 0.
1728
+ - `tolerance?: number` — Maximum allowed gap between adjacent sampled spans. Default 1e-6.
1729
+
1409
1730
  ### `MatchEdgeBuilder`
1410
1731
 
1411
1732
  - `toG0(neighbor: SheetEdge): Sheet`