forgecad 0.9.15 → 0.9.16

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 (70) hide show
  1. package/dist/assets/{AdminPage-CDyGUinA.js → AdminPage-CXvls4-J.js} +1 -1
  2. package/dist/assets/{BenchmarkPage-DfPMY_-d.js → BenchmarkPage-B27zk8xL.js} +1 -1
  3. package/dist/assets/{BlogPage-kF0fkdJT.js → BlogPage-CMAVvgQL.js} +1 -1
  4. package/dist/assets/{DocsPage-B954L3YN.js → DocsPage-knf4I4h7.js} +1 -1
  5. package/dist/assets/EditorApp-BHMQlJ-D.js +14686 -0
  6. package/dist/assets/{EditorApp-CuDLxKqL.css → EditorApp-BpjZgzk0.css} +148 -0
  7. package/dist/assets/{EmbedViewer-C77B-TrF.js → EmbedViewer-D7ZGlFjx.js} +2 -2
  8. package/dist/assets/{LandingPageProofDriven-Cr6fXMDj.js → LandingPageProofDriven-CnevhTE8.js} +2 -2
  9. package/dist/assets/{LegalPage-Dzklqmmg.js → LegalPage-BPTUmqeg.js} +1 -1
  10. package/dist/assets/{PricingPage-zWXkvlwl.js → PricingPage-B0D4goG_.js} +1 -1
  11. package/dist/assets/{SettingsPage-Bz0of4KQ.js → SettingsPage-CFF-UgjI.js} +1 -1
  12. package/dist/assets/{app-D3kDkggg.js → app-T0pDcSX4.js} +1184 -218
  13. package/dist/assets/cli/{render-DSY3mMQa.js → render-C5pcIISc.js} +144 -26
  14. package/dist/assets/{constructionHistoryWorker-gpDo-uH2.js → constructionHistoryWorker-Ba2Hm58b.js} +1 -0
  15. package/dist/assets/{evalWorker-CU0Ke6DP.js → evalWorker-vkx310U2.js} +1380 -2173
  16. package/dist/assets/{inspectWorker-COyp8XXA.js → inspectWorker-BuTJDVX6.js} +252 -30
  17. package/dist/assets/{targets-B9sGB5nB.js → jointPose-B_Cgedn9.js} +71 -3
  18. package/dist/assets/{manifold-DNkrUWpA.js → manifold-BWgsjmAM.js} +1 -1
  19. package/dist/assets/{manifold-C-3h2M7p.js → manifold-D6IFSkhH.js} +2 -2
  20. package/dist/assets/{manifold-BRI5prcH.js → manifold-rZexZI0G.js} +1 -1
  21. package/dist/assets/{reportWorker-CdBz5bNg.js → reportWorker-0AGij1Ru.js} +1373 -2166
  22. package/dist/assets/{scalar-sampling-budget-wJF98aY9.js → scalar-sampling-budget-J5cuzxT1.js} +1494 -2251
  23. package/dist/assets/{scanProxyWorker-B-9VbLIs.js → scanProxyWorker-Vl4Wxa1y.js} +18 -5
  24. package/dist/cli/render.html +1 -1
  25. package/dist/docs/index.html +1 -1
  26. package/dist/docs-raw/AI/usage.md +2 -0
  27. package/dist/docs-raw/CLI.md +4 -0
  28. package/dist/docs-raw/generated/assembly.md +104 -6
  29. package/dist/docs-raw/generated/concepts.md +14 -4
  30. package/dist/docs-raw/generated/lib.md +2 -18
  31. package/dist/docs-raw/generated/output.md +14 -4
  32. package/dist/docs-raw/generated/runtime-names.md +27 -19
  33. package/dist/docs-raw/skills/forgecad-make-a-model.md +39 -38
  34. package/dist/docs-raw/skills/forgecad-project.md +2 -0
  35. package/dist/docs-raw/welcome.md +2 -0
  36. package/dist/index.html +1 -1
  37. package/dist/sitemap.xml +13 -13
  38. package/dist-cli/{check-compiler-SDX5QIXI.js → check-compiler-SYQ2PWOB.js} +1 -1
  39. package/dist-cli/{check-query-propagation-EAYEFT77.js → check-query-propagation-HIAGV62W.js} +1 -1
  40. package/dist-cli/{chunk-N4O47JLF.js → chunk-SPZE3DUY.js} +1591 -2356
  41. package/dist-cli/forgecad.js +1698 -487
  42. package/dist-skill/CONTEXT.md +117 -46
  43. package/dist-skill/docs/CLI.md +4 -0
  44. package/dist-skill/docs/generated/assembly.md +83 -5
  45. package/dist-skill/docs/generated/lib.md +2 -18
  46. package/dist-skill/docs/generated/output.md +14 -4
  47. package/dist-skill/docs/generated/runtime-names.md +18 -19
  48. package/dist-skill/library/forgecad-make-a-model/SKILL.md +39 -38
  49. package/dist-skill/library/forgecad-project/SKILL.md +2 -0
  50. package/examples/api/helix-basics.forge.js +2 -2
  51. package/examples/api/route3d-elbow.forge.js +3 -0
  52. package/examples/api/variable-sweep-test.forge.js +3 -1
  53. package/package.json +4 -1
  54. package/dist/assets/EditorApp-Beb-IZ0y.js +0 -14014
  55. package/examples/api/bolted-service-cover.forge.js +0 -17
  56. package/examples/api/cable-gland-anchor.forge.js +0 -14
  57. package/examples/api/captured-cartridge-guide.forge.js +0 -14
  58. package/examples/api/captured-linear-slide.forge.js +0 -13
  59. package/examples/api/clevis-pin-joint.forge.js +0 -13
  60. package/examples/api/datum-enclosure.forge.js +0 -16
  61. package/examples/api/hose-barb-port.forge.js +0 -14
  62. package/examples/api/knuckled-hinge-assembly.forge.js +0 -15
  63. package/examples/api/living-hinge-cover.forge.js +0 -14
  64. package/examples/api/pcb-terminal-block.forge.js +0 -22
  65. package/examples/api/pinned-lever-pivot-stack.forge.js +0 -14
  66. package/examples/api/retained-shaft-knob-stack.forge.js +0 -15
  67. package/examples/api/routed-tube-clip.forge.js +0 -15
  68. package/examples/api/seated-bearing-stack.forge.js +0 -30
  69. package/examples/api/snap-latch-cover.forge.js +0 -14
  70. package/examples/api/thumb-screw-clamp.forge.js +0 -15
@@ -64,6 +64,8 @@ forgecad studio .
64
64
 
65
65
  `forgecad studio .` opens the installed local editor around the current project. It requires an explicit project path; `.` means the current folder. `forgecad dev <project-path>` is for ForgeCAD source development and internal live-reload work, not the normal user onboarding path.
66
66
 
67
+ Keep one long-running `forgecad studio <project-path> [project-path ...]` process open with every active project folder listed in its arguments; the user opens the single printed localhost port once, and AI agents should only create or edit files under those folders so the browser updates live without starting more servers.
68
+
67
69
  ## Install Skills For Local Coding Agents
68
70
 
69
71
  Install the ForgeCAD public skill library:
@@ -48,6 +48,8 @@ ForgeCAD includes a local editor. Open it around a dedicated project folder, edi
48
48
 
49
49
  `forgecad studio <project-path>` is the normal installed-CLI command for users. `forgecad dev <project-path>` starts the Vite dev server and is mainly for ForgeCAD source development.
50
50
 
51
+ Keep one long-running `forgecad studio <project-path> [project-path ...]` process open with every active project folder listed in its arguments; the user opens the single printed localhost port once, and AI agents should only create or edit files under those folders so the browser updates live without starting more servers.
52
+
51
53
  <details>
52
54
  <summary>Common flags for studio / dev</summary>
53
55
 
@@ -366,6 +368,7 @@ forgecad render section examples/furniture/01-table.forge.js out/bold.svg --edge
366
368
  |--------|-------------|
367
369
  | `--param <Key=Value>` | Override a parameter value (Key=Value). Repeatable. |
368
370
  | `-p <Key=Value>` | Shorthand for --param |
371
+ | `--joint <JointName=Value>` | Override a Motion tab joint value (JointName=Value). Repeatable. |
369
372
  | `--focus <names>` | Focus: no arg hides mocks; comma-separated names/globs show only those |
370
373
  | `--hide <names>` | Hide comma-separated object names/globs |
371
374
  | `--camera <front\|back\|side\|right\|top\|iso\|az:el\|az:el:dist\|spec>` | Camera preset, spherical (az:el), or full spec such as `proj=perspective;pos=x,y,z;target=x,y,z;up=x,y,z;fov=45`. Repeatable. |
@@ -468,6 +471,7 @@ forgecad export sdf rover.forge.js --output out/forge_scout
468
471
 
469
472
  | Option | Description |
470
473
  |--------|-------------|
474
+ | `--joint <JointName=Value>` | Override a Motion tab joint value (JointName=Value). Repeatable. |
471
475
  | `--output <path>` | Output STEP path |
472
476
  | `--backend <occt\|truck>` | Exact BREP exporter: occt (default) or truck (native analytic kernel) |
473
477
  | `--quality <default\|live\|high>` | Forge quality preset |
@@ -207,6 +207,16 @@ addAngleBetweenLinks(a: string, b: string, c: string, options?: AssemblyAngleBet
207
207
 
208
208
  `AssemblyKinematicLimitOptions`: `{ min?: number, max?: number }`
209
209
 
210
+ #### `addAngleBetweenLinkSegmentAndWorldDirection()` — Add an absolute angle relationship from a world direction to a link segment.
211
+
212
+ The first link is the vertex/pivot and the second link is the moving point. A value of `0` places `fromLink -> toLink` along `direction` in the mechanism plane; positive angles rotate counter-clockwise in that plane.
213
+
214
+ Use `Points.polar(1, angleDeg)` when the reference direction is planar and angle-based instead of axis-aligned.
215
+
216
+ ```ts
217
+ addAngleBetweenLinkSegmentAndWorldDirection(fromLink: string, toLink: string, direction: Vec3, options?: AssemblyAngleBetweenLinksOptions): Assembly
218
+ ```
219
+
210
220
  #### `describeKinematics()` — Return the assembly-native kinematic graph definition.
211
221
 
212
222
  ```ts
@@ -235,19 +245,49 @@ assembly.addPart("Base Assembly", housing);
235
245
  addPart(name: string, part: AssemblyPart, options?: PartOptions): Assembly
236
246
  ```
237
247
 
238
- `PartOptions`: `{ transform?: TransformInput, metadata?: PartMetadata, mate?: AssemblyPartMateInput | AssemblyPartMateInput[] }`
248
+ **`PartOptions`**: `transform?: TransformInput`, `metadata?: PartMetadata`, `mate?: AssemblyPartMateInput | AssemblyPartMateInput[]`, `bindToFrame?: string`
239
249
 
240
250
  **`AssemblyPartMateInput`**
241
251
  - `connector: string` — Name of a connector declared on the part (via `withConnectors()`).
242
252
  - `toLink: string` — Name of the link this connector's origin is pinned to.
243
253
  - `aimLink?: string` — Optional second link to orient toward. When set, the part is rotated so the connector's **axis** aims from `toLink` toward `aimLink`, posing an oriented bone instead of only translating it. For full pose without relying on a connector axis, declare a second mate (two connectors → two links).
244
254
 
245
- #### `addFrame()` — Add a virtual reference frame (no geometry) to the assembly graph.
255
+ #### `frame()` — Add a named rig frame to the assembly.
256
+
257
+ A frame is a solved pose: `origin` plus orientation. `axis` is the frame's primary direction and `up` fixes roll around that axis. Use frames for robot links, joint axes, and parts that must carry orientation. Use `link()` for solved points in distance/angle graphs.
258
+
259
+ ```ts
260
+ frame(name: string, options: AssemblyFrameOptions): Assembly
261
+ ```
262
+
263
+ **`AssemblyFrameOptions`**: `origin: [ number, number, number ]`, `axis: [ number, number, number ]`, `up: [ number, number, number ]`, `fixed?: boolean`, `metadata?: Record<string, unknown>`
264
+
265
+ #### `fixedJoint()` — Rigidly attach a child rig frame to a parent rig frame.
266
+
267
+ Fixed joints carry frame hierarchy but do not expose a Motion control.
268
+
269
+ ```ts
270
+ fixedJoint(name: string, options: AssemblyFixedFrameJointOptions): Assembly
271
+ ```
272
+
273
+ `AssemblyFixedFrameJointOptions`: `{ parent: string, child: string, metadata?: Record<string, unknown> }`
246
274
 
247
- Useful when you need a named pivot point or coordinate frame that has no visual geometry. Acts like a zero-volume part and can be connected to other parts via joints.
275
+ #### `revoluteJoint()` Add a revolute rig-frame joint.
276
+
277
+ The child frame rotates around the parent frame's `axis` direction. Moving frame joints appear in Motion by default; pass `control: false` to keep the joint solved at its default value without showing a Motion control.
248
278
 
249
279
  ```ts
250
- addFrame(name: string, options?: PartOptions): Assembly
280
+ revoluteJoint(name: string, options: AssemblyMovingFrameJointOptions): Assembly
281
+ ```
282
+
283
+ **`AssemblyMovingFrameJointOptions`**: `parent: string`, `child: string`, `min?: number`, `max?: number`, `default?: number`, `unit?: string`, `control?: boolean`, `metadata?: Record<string, unknown>`
284
+
285
+ #### `prismaticJoint()` — Add a prismatic rig-frame joint.
286
+
287
+ The child frame translates along the parent frame's `axis` direction. Moving frame joints appear in Motion by default; pass `control: false` to keep the joint solved at its default value without showing a Motion control.
288
+
289
+ ```ts
290
+ prismaticJoint(name: string, options: AssemblyMovingFrameJointOptions): Assembly
251
291
  ```
252
292
 
253
293
  **Connectors**
@@ -405,10 +445,30 @@ return mech.solve({ theta: 45 });
405
445
  solve(state?: JointState): SolvedAssembly
406
446
  ```
407
447
 
408
- <!-- forgecad-skill:exclude-start symbol="Assembly.Compatibility methods" reason="Compatibility-only viewport FK adapter. Prefer returning `Assembly` directly so controls move through the solver-backed link/edge kinematics model." -->
448
+ <!-- forgecad-skill:exclude-start symbol="Assembly.Compatibility methods" reason="Compatibility-only renamed API. Use `addAngleBetweenLinkSegmentAndWorldDirection()` instead." -->
409
449
  **Compatibility**
410
450
  <!-- forgecad-skill:exclude-end -->
411
451
 
452
+ <!-- forgecad-skill:exclude-start symbol="Assembly.addAngleOfLinkSegmentFromXAxis" reason="Compatibility-only renamed API. Use `addAngleBetweenLinkSegmentAndWorldDirection()` instead." -->
453
+ #### `addAngleOfLinkSegmentFromXAxis()`
454
+
455
+ > **Not included in ForgeCAD AI skill context yet.** This API remains visible in human docs, but is intentionally omitted from shipped agent skills until it is ready for agent-first use. Compatibility-only renamed API. Use `addAngleBetweenLinkSegmentAndWorldDirection()` instead.
456
+
457
+ ```ts
458
+ addAngleOfLinkSegmentFromXAxis(fromLink: string, toLink: string, options?: AssemblyAngleBetweenLinksOptions): Assembly
459
+ ```
460
+ <!-- forgecad-skill:exclude-end -->
461
+
462
+ <!-- forgecad-skill:exclude-start symbol="Assembly.addAngleOfLinkSegmentFromYAxis" reason="Compatibility-only renamed API. Use `addAngleBetweenLinkSegmentAndWorldDirection()` instead." -->
463
+ #### `addAngleOfLinkSegmentFromYAxis()`
464
+
465
+ > **Not included in ForgeCAD AI skill context yet.** This API remains visible in human docs, but is intentionally omitted from shipped agent skills until it is ready for agent-first use. Compatibility-only renamed API. Use `addAngleBetweenLinkSegmentAndWorldDirection()` instead.
466
+
467
+ ```ts
468
+ addAngleOfLinkSegmentFromYAxis(fromLink: string, toLink: string, options?: AssemblyAngleBetweenLinksOptions): Assembly
469
+ ```
470
+ <!-- forgecad-skill:exclude-end -->
471
+
412
472
  <!-- forgecad-skill:exclude-start symbol="Assembly.toJointsView" reason="Compatibility-only viewport FK adapter. Prefer returning `Assembly` directly so controls move through the solver-backed link/edge kinematics model." -->
413
473
  #### `toJointsView()` — Deprecated adapter that derives viewport-only FK controls from the assembly graph.
414
474
 
@@ -454,6 +514,32 @@ toJointsView(options?: ToJointsViewOptions): void
454
514
  mate(fn: (m: MateBuilder) => void): Assembly
455
515
  ```
456
516
 
517
+ #### `edgeBetweenFrames()` — Add a visual skeleton edge between two rig frame origins.
518
+
519
+ Frame edges follow the solved frame poses produced by `fixedJoint()`, `revoluteJoint()`, and `prismaticJoint()`. They do not add constraints, degrees of freedom, parts, or geometry; use them to make a frame-only rig readable in the Motion/rig inspection overlay.
520
+
521
+ ```ts
522
+ edgeBetweenFrames(a: string, b: string, options?: AssemblyFrameEdgeOptions): Assembly
523
+ ```
524
+
525
+ `AssemblyFrameEdgeOptions`: `{ name?: string, metadata?: Record<string, unknown> }`
526
+
527
+ #### `linkToward()` — Create a derived link at a fixed distance from `fromLink` toward `towardLink`.
528
+
529
+ Derived links are trace/reference points. They are recomputed after the primary link solve and cannot participate in structural edges or angle constraints.
530
+
531
+ ```ts
532
+ linkToward(name: string, fromLink: string, towardLink: string, distance: number): Assembly
533
+ ```
534
+
535
+ #### `linkAwayFrom()` — Create a derived link at a fixed distance from `fromLink` away from `awayFromLink`.
536
+
537
+ Use this for coupler trace/extension points such as the Chebyshev lambda linkage's point beyond the rocker joint.
538
+
539
+ ```ts
540
+ linkAwayFrom(name: string, fromLink: string, awayFromLink: string, distance: number): Assembly
541
+ ```
542
+
457
543
  #### `describe()` — Return the serializable assembly definition used by solve/inspect pipelines.
458
544
 
459
545
  ```ts
@@ -722,7 +808,7 @@ get mateDof(): number | null
722
808
  get mateConverged(): boolean | null
723
809
  ```
724
810
 
725
- #### `kinematics()` — Solved assembly-native kinematic graph, or null when no links were declared.
811
+ #### `kinematics()` — Solved assembly-native kinematic or frame-edge overlay data, or null when no rig overlay data was declared.
726
812
 
727
813
  ```ts
728
814
  get kinematics(): SolvedAssemblyKinematics | null
@@ -734,6 +820,18 @@ get kinematics(): SolvedAssemblyKinematics | null
734
820
  getLinkPosition(linkName: string): Vec3
735
821
  ```
736
822
 
823
+ #### `getFrame()` — Return the solved world transform for a named rig frame.
824
+
825
+ ```ts
826
+ getFrame(frameName: string): Transform
827
+ ```
828
+
829
+ #### `frames()` — Return solved rig frames, including origin, axis, up, and transform.
830
+
831
+ ```ts
832
+ get frames(): SolvedAssemblyFrameDef[]
833
+ ```
834
+
737
835
  #### `getTransform()` — Return the world-space [`Transform`](/docs/core#transform) for the named part at the solved pose.
738
836
 
739
837
  ```ts
@@ -2791,9 +2791,9 @@ robotExport(options: RobotExportOptions): CollectedRobotExport
2791
2791
 
2792
2792
  **`CollectedRobotExport`**: `modelName: string`, `assembly: AssemblyDefinition`, `state: JointState`, `static: boolean`, `selfCollide: boolean`, `allowAutoDisable: boolean`, `links: Record<string, RobotLinkExportOptions>`, `joints: Record<string, RobotJointExportOptions>`, `plugins: { diffDrive?: RobotDiffDrivePluginOptions; jointStatePublisher?: RobotJointStatePublisherOptions; }`, `world: RobotWorldOptions | null`
2793
2793
 
2794
- **`AssemblyDefinition`**: `name: string`, `parts: AssemblyPartDef[]`, `joints: AssemblyJointDef[]`, `jointCouplings: AssemblyJointCouplingDef[]`, `kinematics: AssemblyKinematicGraphDef`
2794
+ **`AssemblyDefinition`**: `name: string`, `parts: AssemblyPartDef[]`, `joints: AssemblyJointDef[]`, `jointCouplings: AssemblyJointCouplingDef[]`, `kinematics: AssemblyKinematicGraphDef`, `frames: AssemblyFrameDef[]`, `frameJoints: AssemblyFrameJointDef[]`, `frameEdges: AssemblyFrameEdgeDef[]`
2795
2795
 
2796
- `AssemblyPartDef`: `{ name: string, part: AssemblyPart, base: Transform, metadata?: PartMetadata, mates: AssemblyPartMateInput[] }`
2796
+ **`AssemblyPartDef`**: `name: string`, `part: AssemblyPart`, `base: Transform`, `metadata?: PartMetadata`, `mates: AssemblyPartMateInput[]`, `bindToFrame?: string`
2797
2797
 
2798
2798
  **`PartMetadata`**
2799
2799
 
@@ -2815,7 +2815,7 @@ robotExport(options: RobotExportOptions): CollectedRobotExport
2815
2815
 
2816
2816
  `JointCouplingTermRecord`: `{ joint: string, ratio: number }`
2817
2817
 
2818
- `AssemblyKinematicGraphDef`: `{ links: AssemblyLinkDef[], edges: AssemblyEdgeBetweenLinksDef[], angles: AssemblyAngleBetweenLinksDef[] }`
2818
+ **`AssemblyKinematicGraphDef`**: `links: AssemblyLinkDef[]`, `edges: AssemblyEdgeBetweenLinksDef[]`, `angles: AssemblyAngleBetweenLinksDef[]`, `derivedLinks: AssemblyDerivedLinkDef[]`
2819
2819
 
2820
2820
  `AssemblyLinkDef`: `{ name: string, at: Vec3, fixed: boolean, metadata?: Record<string, unknown> }`
2821
2821
 
@@ -2823,7 +2823,17 @@ robotExport(options: RobotExportOptions): CollectedRobotExport
2823
2823
 
2824
2824
  `AssemblyKinematicControlOptions`: `{ min?: number, max?: number, default?: number, unit?: string }`
2825
2825
 
2826
- **`AssemblyAngleBetweenLinksDef`**: `name: string`, `a: string`, `b: string`, `c: string`, `target?: number`, `min?: number`, `max?: number`, `control?: AssemblyKinematicControlOptions`, `metadata?: Record<string, unknown>`
2826
+ **`AssemblyAngleBetweenLinksDef`**: `name: string`, `a?: string`, `b: string`, `c: string`, `reference?: AssemblyAngleReferenceDef`, `target?: number`, `min?: number`, `max?: number`, `control?: AssemblyKinematicControlOptions`, `metadata?: Record<string, unknown>`
2827
+
2828
+ `AssemblyAngleReferenceDef`: `{ kind: "worldDirection", direction: Vec3 }`
2829
+
2830
+ `AssemblyDerivedLinkDef`: `{ name: string, fromLink: string, referenceLink: string, distance: number, direction: AssemblyDerivedLinkDirection }`
2831
+
2832
+ `AssemblyFrameDef`: `{ name: string, origin: Vec3, axis: Vec3, up: Vec3, fixed: boolean, metadata?: Record<string, unknown> }`
2833
+
2834
+ **`AssemblyFrameJointDef`**: `name: string`, `type: AssemblyFrameJointType`, `parent: string`, `child: string`, `rest: Transform`, `min?: number`, `max?: number`, `defaultValue: number`, `unit?: string`, `control: boolean`, `metadata?: Record<string, unknown>`
2835
+
2836
+ `AssemblyFrameEdgeDef`: `{ name: string, a: string, b: string, metadata?: Record<string, unknown> }`
2827
2837
 
2828
2838
  #### `sheetMetal()` — Create a parametric sheet metal part with flanges, bend allowances, and flat-pattern unfolding.
2829
2839
 
@@ -93,7 +93,7 @@ Pre-built parametric parts available in user scripts as `lib.*`.
93
93
 
94
94
  Every key in this object becomes a method or namespace on the `lib` object exposed to `.forge.js` scripts. The catalog includes:
95
95
 
96
- **Fasteners and hardware patterns:** `bolt`, `nut`, `washer`, `fastenerSet`, `boltedServiceCover`, `datumEnclosureAssembly`, `snapLatchCoverAssembly`, `pinnedLeverAssembly`, `retainedShaftAssembly`, `capturedLinearSlide`, `capturedCartridgeGuideAssembly`, `livingHingeCoverAssembly`, `knuckledHingeAssembly`, `clevisPinJointAssembly`, `seatedBearingAssembly`, `cableGlandAnchorAssembly`, `hoseBarbPortAssembly`, `routedTubeClipAssembly`, `pcbTerminalBlockAssembly`, `thumbScrewClampAssembly`, `fastenerHole`, `boltHole`, `counterbore`, `hexNut`, `holePattern`
96
+ **Fasteners and hardware patterns:** `bolt`, `nut`, `washer`, `fastenerSet`, `fastenerHole`, `boltHole`, `counterbore`, `hexNut`, `holePattern`
97
97
 
98
98
  **Structure:** `tube`, `pipe`, `bracket`, `pipeRoute`, `elbow`, `tSlotProfile`, `tSlotExtrusion`, `profile2020BSlot6Profile`, `profile2020BSlot6`
99
99
 
@@ -127,24 +127,8 @@ Sizes outside the supported ranges will throw at runtime with a descriptive erro
127
127
  - `nut(diameter: number, options?: { pitch?: number; height?: number; acrossFlats?: number; segments?: number; }): Shape` — ISO-style hex nut with a threaded bore. **Details** Constructed from the intersection of three rotated slabs with a cylindrical bore subtracted. The nut is centered at the origin, height along Z. Default proportions follow ISO 4032 loosely: height ≈ 0.8×diameter, across-flats ≈ 1.6×diameter. The bore is a clearance bore (not modelled with helical threads) for rendering efficiency. For standard M-size nuts pre-configured for a complete joint, use { **Example** ```ts const n = lib.nut(5); // M5 nut ```
128
128
  - `washer(size: MetricSize, options?: { standard?: WasherStandard; segments?: number; }): Shape` — ISO metric flat washer (DIN 125-A). **Details** Returns a flat ring centered at the origin, thickness along Z. Dimensions are taken from { **Example** ```ts const w = lib.washer('M5'); // DIN 125-A M5 washer ```
129
129
  - `fastenerSet(size: MetricSize, boltLength: number, options?: FastenerSetOptions): FastenerSetResult` — Complete ISO metric fastener set — bolt, nut, optional washers, and matching hole cutters. **Details** Returns all geometry for one bolted joint: the bolt, nut, up to two washers, a clearance-hole cutter, and a tap-drill cutter. All shapes are returned **un-positioned** (each on the Z-axis). Place them with `.translate()`. Sizes outside M4–M10 are supported for the washer (M2–M10); unsupported combinations will throw. **Example** ```ts const hw = lib.fastenerSet('M5', 20); const topPlate = box(60, 40, 8).translate(0, 0, 12) .subtract(hw.clearanceHole.translate(15, 10, 12)); const botPlate = box(60, 40, 8) .subtract(hw.clearanceHole.translate(15, 10, 0)); return [ { name: 'Top Plate', shape: topPlate }, { name: 'Bot Plate', shape: botPlate }, { name: 'Bolt', shape: hw.bolt.translate(15, 10, 20) }, { name: 'Nut', shape: hw.nut.translate(15, 10, -4) }, ]; ```
130
- - `boltedServiceCover(options: BoltedServiceCoverOptions): BoltedServiceCoverResult` — Bolted service-cover interface with real seats, aligned holes, gasket, fused pull tabs, and installed screws. **Details** This is a higher-level mechanical pattern for the common "removable service cover" failure mode. It creates the parent ledge, cover, gasket, and screws from one shared bolt pattern so agents do not place decorative screw heads or floating pull tabs by eye. Coordinate convention: the parent frame sits from `z=0` to `parentThickness`, the gasket sits on the ledge, the cover sits above the gasket, and screw shafts run downward through the cover into the parent. All parts are centered on the XY origin. **Example** ```ts const cover = lib.boltedServiceCover({ width: 90, depth: 56, screwSize: 'M4', ledgeWidth: 10, boltInset: [6, 6], }); verify.equal('four retained cover screws', cover.screws.length, 4); return cover.parts; ```
131
- - `datumEnclosureAssembly(options: DatumEnclosureAssemblyOptions): DatumEnclosureAssemblyResult` — Datum-driven enclosure tray with shared wall, ledge, standoff, cover, gasket, port, and screw geometry. **Details** This pattern is for electronics boxes, thermostat backplates, service-stack housings, camera housings, and small fixtures where generated models often place panels, ribs, bosses, ports, and covers by eye. The tray, internal ledges, standoffs, ribs, service port, gasket, cover holes, and installed screws all come from one datum system. This keeps screw axes, boss locations, wall thickness, and service openings aligned instead of relying on independent magic numbers. Coordinate convention: X/Y are the enclosure footprint, Z is up. The base tray starts at `z=0` and rises to `height`; the gasket and cover sit above the top ledge with small explicit face clearances. **Example** ```ts const enclosure = lib.datumEnclosureAssembly({ width: 96, depth: 64, height: 18, }); verify.notColliding('cover clears enclosure gasket', enclosure.cover, enclosure.gasket); verify.inRange('cover stack has small seating clearance', enclosure.dims.faceClearance, 0.01, 0.08); return enclosure.parts; ```
132
- - `snapLatchCoverAssembly(options: SnapLatchCoverAssemblyOptions): SnapLatchCoverAssemblyResult` — Snap-retained cover with a receiver frame, latch windows, underside catch lands, and fused snap hooks. **Details** This pattern is for covers, cartridges, clasps, and small housings where agents often add decorative tabs without a catch. The receiver has a real service opening plus two clearance latch windows. The cover is one fused part with two flexible-looking snap fingers that pass through the windows and barb under the receiver underside. Nothing intersects in the final assembly; the hook geometry sits close enough to the catch lands to prove retention intent. Coordinate convention: the receiver frame sits from `z=0` to `parentThickness`; the cover is seated just above the receiver on +Z. Two snap hooks sit on the +/-Y ledges and tuck under the receiver. **Example** ```ts const snapCover = lib.snapLatchCoverAssembly({ width: 72, depth: 44, }); verify.notColliding('snap hooks clear receiver windows', snapCover.cover, snapCover.parent); verify.inRange('snap cover has small seating clearance', snapCover.dims.faceClearance, 0.01, 0.08); return snapCover.parts; ```
133
- - `pinnedLeverAssembly(options: PinnedLeverAssemblyOptions): PinnedLeverAssemblyResult` — Retained pinned lever stack with a fused hub/arm/grip, low stop land, pivot pin, bore cutters, and thrust washers. **Details** This pattern is for the common handle/lever failure mode where a visual arm, hub, washer, and pin are placed near each other but never form a credible mechanism. The lever body is one fused part, the pin runs through aligned bores, washers sit on both sides of the lever, and the support includes a bearing land plus an optional low stop land beside the lever path. Coordinate convention: pivot axis is +Z at the XY origin. The support starts at `z=0`, the lower washer sits on top of the support, the lever sits on the lower washer, the upper washer sits on the lever, and the retained pin spans the full stack. **Example** ```ts const lever = lib.pinnedLeverAssembly({ armLength: 54, armWidth: 10, pinDiameter: 5, }); verify.equal('lever stack has five retained parts', lever.parts.length, 5); return lever.parts; ```
134
- - `retainedShaftAssembly(options: RetainedShaftAssemblyOptions): RetainedShaftAssemblyResult` — Retained shaft, washer, knob, and support-cheek stack for trunnions, pivots, and adjustable clamps. **Details** This pattern replaces the common "pin, washers, and knob are near each other" visual shortcut with a mechanically accountable shaft stack. The two support cheeks get matching clearance bores, the through shaft spans the whole stack, washers and knobs share the same axis, and retaining heads keep the knobs from reading as loose floating cylinders. Coordinate convention: the shaft axis is +X through the world origin. Support cheeks are centered at `x = +/- supportSpacing / 2`. The supports are bored for clearance, so collision inspection should report no support/shaft overlap while the connectivity audit still sees one retained stack. **Example** ```ts const trunnion = lib.retainedShaftAssembly({ supportSpacing: 96, shaftDiameter: 8, supportHeight: 42, }); verify.equal('retained shaft stack has seven parts', trunnion.parts.length, 7); return trunnion.parts; ```
135
- - `capturedLinearSlide(options: CapturedLinearSlideOptions): CapturedLinearSlideResult` — Captured linear slide with a U-channel rail, return lips, end stops, and a carriage posed inside the guide. **Details** This pattern is for drawer-slide, quick-release plate, and guided-carriage models where agents often place rail details and a moving block near each other without a capture relationship. The rail is one fused part with side walls, inward lips, and end stops; the carriage is wider than the lip throat but narrower than the inner rail width, so it is mechanically captured while retaining explicit clearance. Coordinate convention: rail length is along X, width is along Y, and Z is up. The rail base starts at `z=0`; the carriage sits above the base and below the return lips. `travel=0` places the carriage at the negative-X end of travel, and `travel=maxTravel` places it at the positive-X end. **Example** ```ts const slide = lib.capturedLinearSlide({ length: 160, carriageLength: 52, travel: 42, }); verify.greaterThan('carriage is captured by return lips', slide.dims.carriageWidth, slide.dims.throatWidth); return slide.parts; ```
136
- - `capturedCartridgeGuideAssembly(options: CapturedCartridgeGuideAssemblyOptions): CapturedCartridgeGuideAssemblyResult` — Captured removable cartridge guide with return lips, rear stop, wide cartridge flange, and pull tab. **Details** This pattern is for pump cartridges, filter cassettes, skeg cassettes, battery cartridges, and slide-in service modules where generated models often place a tray and a loose block near each other. The guide is one fused part with side walls, inward return lips, and a rear stop. The cartridge has a wide lower flange captured under the lips and a narrower body that passes through the throat, so the model has a real retention contract without manual coordinate tuning. Coordinate convention: insertion travel is along +X. The open entry is at −X, the rear stop is at +X, the guide base starts at `z=0`, and `insertion=0` places the cartridge at the front travel limit. **Example** ```ts const cassette = lib.capturedCartridgeGuideAssembly({ length: 150, cartridgeLength: 72, }); verify.notColliding('cartridge clears guide rails', cassette.cartridge, cassette.guide); verify.greaterThan('cartridge flange is captured by lips', cassette.dims.cartridgeWidth, cassette.dims.throatWidth); return cassette.parts; ```
137
- - `livingHingeCoverAssembly(options: LivingHingeCoverAssemblyOptions): LivingHingeCoverAssemblyResult` — One-piece molded living-hinge cover strip with a fixed leaf, thin flexible web, cover leaf, pull lip, snap barb, and catch land. **Details** This pattern is for small polypropylene-style lids, battery doors, sample covers, blister latches, and molded service flaps where generated models often draw a decorative hinge strip between two disconnected plates. It returns one fused molded part in its as-molded flat state: fixed mounting leaf, thin hinge web, moving cover leaf, pull lip, raised snap barb, and catch land. The flexible web is intentionally much thinner than the rigid leaves and shares material with both leaves. Coordinate convention: X is hinge length/part width, Y runs from fixed leaf through hinge web to cover leaf, and Z is thickness. The hinge web is centered on `y=0`; the fixed leaf lies at −Y and the cover leaf at +Y. **Example** ```ts const livingCover = lib.livingHingeCoverAssembly({ width: 64, coverDepth: 42, }); verify.greaterThan('living hinge is much thinner than rigid leaves', livingCover.dims.flexRatio, 3); return livingCover.parts; ```
138
- - `knuckledHingeAssembly(options: KnuckledHingeAssemblyOptions): KnuckledHingeAssemblyResult` — Alternating knuckle hinge with two fused leaves and a retained pin. **Details** This pattern replaces hand-placed hinge barrels and pin ghosts with a mechanically accountable hinge. The fixed leaf owns every other knuckle, the moving leaf owns the alternating knuckles, all knuckles share one bore size, and the retained pin spans the full stack with heads outside the barrels. Coordinate convention: the hinge pin axis is +X through the world origin. The fixed leaf extends toward +Y. The moving leaf extends toward -Y and rotates about +X by `openAngleDeg`. **Example** ```ts const hinge = lib.knuckledHingeAssembly({ length: 70, leafLength: 28, openAngleDeg: 45, }); verify.equal('hinge has two leaves and one retained pin', hinge.parts.length, 3); return hinge.parts; ```
139
- - `clevisPinJointAssembly(options?: ClevisPinJointAssemblyOptions): ClevisPinJointAssemblyResult` — Clevis-style pin joint with bored yoke ears, a center link eye, and a retained pin. **Details** This pattern is for crank links, damper rod ends, pump crossheads, capo/cam pivots, and small mechanism joints where agents often place an eyelet and a pin near a bracket without modeling the captured load path. The clevis is one fused part with two bored ears and a rear bridge, the center link has a real eye and arm, and the retained pin spans the full stack with heads outside the ears. Coordinate convention: the pin axis is +Y through the world origin. The center link arm extends toward +X. The clevis bridge sits behind the eye on -X, leaving the link eye clear inside the yoke. **Example** ```ts const clevis = lib.clevisPinJointAssembly({ pinDiameter: 4, linkArmLength: 38, }); verify.equal('clevis joint has three retained parts', clevis.parts.length, 3); return clevis.parts; ```
140
- - `seatedBearingAssembly(options: SeatedBearingAssemblyOptions): SeatedBearingAssemblyResult` — Seated radial-bearing support with a real counterbore, shoulder, through shaft, and retaining collars. **Details** This pattern is for purchased bearings, rollers, burr-cartridge shafts, and small spindle supports where agents often place a ring and a shaft near a block without modelling the pocket that locates the bearing. The housing includes a through-bore and a larger counterbore that leaves a shoulder for the bearing outer race. The shaft is smaller than the bearing bore and carries collars outside the housing, so collision checks can distinguish intended clearance from impossible overlap. Coordinate convention: the shaft axis is +Z through the world origin. The housing block starts at `z=0`, the raised boss is on top of the block, the bearing is seated from the top counterbore, and the shaft extends above and below the housing. **Example** ```ts const bearingStack = lib.seatedBearingAssembly({ bearingOuterDiameter: 22, bearingInnerDiameter: 8, bearingWidth: 7, }); verify.greaterThan('housing has wall around bearing pocket', bearingStack.dims.bossOuterDiameter - bearingStack.dims.pocketDiameter, 4); return bearingStack.parts; ```
141
- - `cableGlandAnchorAssembly(options: CableGlandAnchorAssemblyOptions): CableGlandAnchorAssemblyResult` — Cable, wire, or tube gland anchor with a real panel hole, hollow gland body, compression nut, and routed cable. **Details** This pattern is for pumps, filters, electronics boxes, vents, monitors, and fixtures where generated models often leave hoses or cables terminating in space. It creates the receiving panel hole, a hollow gland body with a panel-side flange seated in a shallow pocket, a hollow compression nut, and a cable/tube that runs through the gland bore with explicit clearance. Coordinate convention: the cable axis is +X through the world origin. The panel is centered around `x=0` with thickness along X; the flange sits on the +X side of the panel and the compression nut sits on the −X side. The cable spans the full anchor. **Example** ```ts const anchor = lib.cableGlandAnchorAssembly({ cableDiameter: 6, panelThickness: 3, }); verify.notColliding('cable clears gland bore', anchor.cable, anchor.gland); verify.clearanceBetween('gland flange is seated at panel pocket', anchor.gland, anchor.panel, 0.01, 0.2); return anchor.parts; ```
142
- - `hoseBarbPortAssembly(options: HoseBarbPortAssemblyOptions): HoseBarbPortAssemblyResult` — Hose-barb pump/filter port with a bored receiver, shoulder, barb ridges, installed hose, and clamp band. **Details** This pattern is for pump heads, filters, vents, lab cartridges, and fluid fittings where generated models often leave tubes ending near a block. The receiver has a real through-port and raised boss, the fitting is hollow with a shoulder and multiple barb ridges, and the hose is modeled as an installed tube over the barb envelope with a clamp band. The hose bore is sized for the deformed installed hose, so collision checks distinguish the retained interface from impossible solid overlap. Coordinate convention: the fluid axis is +X through the world origin. The receiver block is centered around `x=0`; the raised boss and hose are on the +X side. **Example** ```ts const hosePort = lib.hoseBarbPortAssembly({ hoseInnerDiameter: 6, hoseOuterDiameter: 10, }); verify.notColliding('hose clears barb peaks', hosePort.hose, hosePort.fitting); verify.inRange('fitting shoulder seats near boss face', hosePort.dims.faceClearance, 0.01, 0.08); return hosePort.parts; ```
143
- - `routedTubeClipAssembly(options: RoutedTubeClipAssemblyOptions): RoutedTubeClipAssemblyResult` — Routed tube or cable retained by saddle clips with real bores, screw holes, and installed screws. **Details** This pattern is for hoses, wires, pump tubes, sensor leads, and appliance cable runs where generated models often draw a cylinder near a wall without clips or strain relief. The base panel has receiving screw envelopes, each saddle clip has a real through-bore around the tube and vertical screw clearances, and the installed screws share those positions. Coordinate convention: the routed tube runs along +X through the world origin. The base panel starts at `z=0`; clips sit on top of the panel, and the tube passes through their bores. **Example** ```ts const route = lib.routedTubeClipAssembly({ tubeDiameter: 6, clipCount: 3, }); verify.notColliding('tube clears clip bores', route.tube, union(...route.clips)); verify.notColliding('clip screws clear retained stack', union(...route.screws), union(route.panel, ...route.clips)); return route.parts; ```
144
- - `pcbTerminalBlockAssembly(options?: PcbTerminalBlockAssemblyOptions): PcbTerminalBlockAssemblyResult` — PCB terminal-block stack with a backplate, standoffs, mounting screws, pin holes, and a seated terminal block. **Details** This pattern is for thermostat backplates, appliance control panels, sensor boards, and small electronics where generated models often place a terminal block, screw heads, and holes as independent decorations. The PCB mounting holes, fused standoffs, installed screws, terminal pins, and PCB pin clearances all come from one shared datum system so the purchased block is mechanically seated and the board is actually mounted. Coordinate convention: X/Y are the board footprint, Z is up. The backplate starts at `z=0`, standoffs rise from the plate, the PCB rests on the standoffs, and the terminal block sits on top of the PCB near the front edge. **Example** ```ts const terminalStack = lib.pcbTerminalBlockAssembly({ terminalCount: 5, screwSize: 'M3', }); verify.notColliding('terminal pins clear PCB holes', terminalStack.terminalBlock, terminalStack.pcb); verify.notColliding('mounting screws clear PCB and standoff holes', union(...terminalStack.screws), union(terminalStack.pcb, terminalStack.backplate)); return terminalStack.parts; ```
145
- - `thumbScrewClampAssembly(options?: ThumbScrewClampAssemblyOptions): ThumbScrewClampAssemblyResult` — Thumb-screw clamp with a C-frame, threaded boss, captive pressure pad, knob, and clamped workpiece. **Details** This pattern is for bench clamps, monitor-arm desk clamps, small vise screws, capo pressure screws, fixture hold-downs, and service brackets where generated models often place a loose screw, knob, or pressure pad near a bracket. The helper creates a one-piece clamp frame with a fixed anvil pad, a bored threaded support and boss, an installed screw with a captive pressure pad and hand knob, and a representative clamped workpiece seated between the pads. Coordinate convention: the clamp screw runs along +X. The fixed anvil is on the -X side, the threaded support and knob are on the +X side, and Z is up from the base bridge. **Example** ```ts const clamp = lib.thumbScrewClampAssembly({ screwSize: 'M6', workpieceThickness: 20, }); verify.notColliding('thumb screw clears threaded boss', clamp.clampScrew, clamp.frame); verify.clearanceBetween('pressure pad is seated on workpiece', clamp.clampScrew, clamp.workpiece, -0.01, 0.05); return clamp.parts; ```
146
130
  - `pipeRoute(points: [ number, number, number ][], radius: number, options?: { bendRadius?: number; wall?: number; segments?: number; }): Shape` — Route a pipe (solid or hollow) through 3D waypoints with smooth bends. This is a convenience recipe over `Curve.Route.fromPolyline()` and [`sweep()`](/docs/curves#sweep). Use `Curve.Route` directly when you need route metadata, named ports, or custom swept profiles.
147
- - `elbow(pipeRadius: number, bendRadius: number, angle?: number | { ... }, options?: { ... }): Shape` — Pipe elbow — a curved pipe section (torus arc) for connecting two pipe directions. By default creates a bend in the XZ plane: incoming along +Z, outgoing rotated by `angle`. The bend starts at the origin, curving away from it.
131
+ - `elbow(pipeRadius: number, bendRadius: number, angle?: number | { ... }, options?: { ... }): Shape` — Pipe elbow — a curved pipe section for connecting two pipe directions. This is a convenience recipe over `Curve.Arc()` and [`sweep()`](/docs/curves#sweep). Use `Curve.Route.fromPolyline()` directly when you need named route ports, segment metadata, or multi-bend pipe runs.
148
132
  - `beltDrive(options: BeltDriveOptions): BeltDriveResult` — Create a flat open-belt body around two pulley pitch circles. The belt is generated as a tangent loop in the XY plane and extruded along +Z by `beltWidth`. The result includes the solid belt, the 2D belt profile, a thin pitch-path sketch for visualization, total belt length, tangent spans, and wrap metadata for each pulley. For more than two pulleys, the API intentionally asks for route intent before geometry is created. Use `route: "outer"` for the future outside-envelope mode, or an ordered route for future serpentine/idler layouts. ```ts const drive = lib.beltDrive({ pulleys: [ { name: "motor", center: [0, 0], pitchRadius: 12 }, { name: "output", center: [80, 0], pitchRadius: 28 }, ], beltWidth: 8, beltThickness: 2, }); return drive.belt; ```
149
133
  - `tangentLoop2d(circles: TangentCircle2D[], options?: TangentLoop2DOptions): TangentLoop2D` — Build a closed 2D route made from common tangent spans and pulley wrap arcs. Use this when you need reusable belt/chain route geometry before creating a solid body. The first implementation supports two circles. `mode: "open"` uses external tangents; `mode: "crossed"` uses internal tangents. ```ts const route = lib.tangentLoop2d([ { center: [0, 0], radius: 12 }, { center: [80, 0], radius: 28 }, ]); const belt = route.offsetBand(2).extrude(8); ```
150
134
  - `tSlotProfile(options?: TSlotProfileOptions): Sketch` — Build a 2D T-slot cross-section sketch. Default parameters describe a 20x20 B-type profile with slot 6. Use this when you want a drawing-ready profile sketch before extrusion.
@@ -140,9 +140,9 @@ robotExport(options: RobotExportOptions): CollectedRobotExport
140
140
 
141
141
  **`CollectedRobotExport`**: `modelName: string`, `assembly: AssemblyDefinition`, `state: JointState`, `static: boolean`, `selfCollide: boolean`, `allowAutoDisable: boolean`, `links: Record<string, RobotLinkExportOptions>`, `joints: Record<string, RobotJointExportOptions>`, `plugins: { diffDrive?: RobotDiffDrivePluginOptions; jointStatePublisher?: RobotJointStatePublisherOptions; }`, `world: RobotWorldOptions | null`
142
142
 
143
- **`AssemblyDefinition`**: `name: string`, `parts: AssemblyPartDef[]`, `joints: AssemblyJointDef[]`, `jointCouplings: AssemblyJointCouplingDef[]`, `kinematics: AssemblyKinematicGraphDef`
143
+ **`AssemblyDefinition`**: `name: string`, `parts: AssemblyPartDef[]`, `joints: AssemblyJointDef[]`, `jointCouplings: AssemblyJointCouplingDef[]`, `kinematics: AssemblyKinematicGraphDef`, `frames: AssemblyFrameDef[]`, `frameJoints: AssemblyFrameJointDef[]`, `frameEdges: AssemblyFrameEdgeDef[]`
144
144
 
145
- `AssemblyPartDef`: `{ name: string, part: AssemblyPart, base: Transform, metadata?: PartMetadata, mates: AssemblyPartMateInput[] }`
145
+ **`AssemblyPartDef`**: `name: string`, `part: AssemblyPart`, `base: Transform`, `metadata?: PartMetadata`, `mates: AssemblyPartMateInput[]`, `bindToFrame?: string`
146
146
 
147
147
  **`PartMetadata`**
148
148
 
@@ -164,7 +164,7 @@ robotExport(options: RobotExportOptions): CollectedRobotExport
164
164
 
165
165
  `JointCouplingTermRecord`: `{ joint: string, ratio: number }`
166
166
 
167
- `AssemblyKinematicGraphDef`: `{ links: AssemblyLinkDef[], edges: AssemblyEdgeBetweenLinksDef[], angles: AssemblyAngleBetweenLinksDef[] }`
167
+ **`AssemblyKinematicGraphDef`**: `links: AssemblyLinkDef[]`, `edges: AssemblyEdgeBetweenLinksDef[]`, `angles: AssemblyAngleBetweenLinksDef[]`, `derivedLinks: AssemblyDerivedLinkDef[]`
168
168
 
169
169
  `AssemblyLinkDef`: `{ name: string, at: Vec3, fixed: boolean, metadata?: Record<string, unknown> }`
170
170
 
@@ -172,7 +172,17 @@ robotExport(options: RobotExportOptions): CollectedRobotExport
172
172
 
173
173
  `AssemblyKinematicControlOptions`: `{ min?: number, max?: number, default?: number, unit?: string }`
174
174
 
175
- **`AssemblyAngleBetweenLinksDef`**: `name: string`, `a: string`, `b: string`, `c: string`, `target?: number`, `min?: number`, `max?: number`, `control?: AssemblyKinematicControlOptions`, `metadata?: Record<string, unknown>`
175
+ **`AssemblyAngleBetweenLinksDef`**: `name: string`, `a?: string`, `b: string`, `c: string`, `reference?: AssemblyAngleReferenceDef`, `target?: number`, `min?: number`, `max?: number`, `control?: AssemblyKinematicControlOptions`, `metadata?: Record<string, unknown>`
176
+
177
+ `AssemblyAngleReferenceDef`: `{ kind: "worldDirection", direction: Vec3 }`
178
+
179
+ `AssemblyDerivedLinkDef`: `{ name: string, fromLink: string, referenceLink: string, distance: number, direction: AssemblyDerivedLinkDirection }`
180
+
181
+ `AssemblyFrameDef`: `{ name: string, origin: Vec3, axis: Vec3, up: Vec3, fixed: boolean, metadata?: Record<string, unknown> }`
182
+
183
+ **`AssemblyFrameJointDef`**: `name: string`, `type: AssemblyFrameJointType`, `parent: string`, `child: string`, `rest: Transform`, `min?: number`, `max?: number`, `defaultValue: number`, `unit?: string`, `control: boolean`, `metadata?: Record<string, unknown>`
184
+
185
+ `AssemblyFrameEdgeDef`: `{ name: string, a: string, b: string, metadata?: Record<string, unknown> }`
176
186
 
177
187
  #### `dim()` — Add a dimension annotation between two points.
178
188
 
@@ -17,25 +17,33 @@ These collision-reserved names are case-sensitive:
17
17
  activateBackend, Analysis, arcSlot, assembly, Assembly, assemblyInstructions, assemblyPreview, Blend
18
18
  bom, bomToCsv, boolParam, box, cameraTrajectory, Carrier, chamfer, chamferTrackedEdge
19
19
  choiceParam, circle, circle2d, Circle2D, circularLayout, circularPattern, circularPattern2d, coalesceEdges
20
- combine, COMMON_KERFS, compareWith, composeChain, connectEdges, connector, console, constrainedSketch
21
- Constraint, Counterbore, Curve, Curve3D, cutPlane, cylinder, degrees, difference
22
- difference2d, dim, dimLine, draft, ellipse, explodeView, faceProfile, fillet
23
- filletCorners, filletTrackedEdge, fingerJoint, flatPanel, flatPart, formatInstructions, Function, gcode
24
- GCodeBuilder, getActiveBackend, global, globalThis, group, Helix, HelixCurve, hermiteTransitionG2
25
- highlight, Import, ImportedAssembly, importMesh, importStep, importSvgSketch, initKernel, intersection
26
- intersection2d, intersectWithPlane, joint, jointsView, laserKit, lib, line, Line2D
27
- linearPattern, linearPattern2d, listParam, loadFont, loft, Loft, loftAlongSpine, lookupKerf
28
- mirrorCopy, mock, ngon, nurbs3d, NurbsCurve3D, nurbsSurface, NurbsSurface, offsetSolid
29
- param, Param, path, point, Point2D, Points, polygon, polygonVertices
30
- port, Product, ProductHandleBuilder, ProductHandleFeature, ProductPanelBuilder, ProductRibbonBuilder, ProductSkin, ProductSkinBuilder
31
- ProductSpoutBuilder, ProductStationBuilder, ProductSurfaceBuilder, ProductSurfaceRef, projectToPlane, queueMicrotask, radians, rect
32
- Rectangle2D, Ribs, robotExport, roundedRect, Route3D, scene, Sculpt, sdf
33
- SdfShape, selectEdge, selectEdges, self, setActiveBackend, setImmediate, setInterval, setTimeout
34
- Shape, ShapeGroup, sheetMetal, SheetMetalPart, sheetStock, Sketch, sketchToDxf, sketchToSvg
35
- slot, Slot, SolvedAssembly, spec, sphere, spline2d, spline3d, star
36
- stroke, Surface, SurfaceBody, SurfaceMembers, surfacePatch, sweep, tabSlot, text2d
37
- textWidth, torus, toShape, Transform, transitionCurve, transitionSurface, union, union2d
38
- variableSweep, verify, viewConfig, Viewport, window, Wood
20
+ combine, COMMON_KERFS, compareWith, composeChain, connector, console, constrainedSketch, Constraint
21
+ Counterbore, Curve, Curve3D, cutPlane, cylinder, degrees, difference, difference2d
22
+ dim, dimLine, draft, ellipse, explodeView, faceProfile, fillet, filletCorners
23
+ filletTrackedEdge, fingerJoint, flatPanel, flatPart, formatInstructions, Function, gcode, GCodeBuilder
24
+ getActiveBackend, global, globalThis, group, highlight, Import, ImportedAssembly, importMesh
25
+ importStep, importSvgSketch, initKernel, intersection, intersection2d, intersectWithPlane, joint, jointsView
26
+ laserKit, lib, line, Line2D, linearPattern, linearPattern2d, listParam, loadFont
27
+ loft, Loft, lookupKerf, mirrorCopy, mock, ngon, NurbsCurve3D, nurbsSurface
28
+ NurbsSurface, offsetSolid, param, Param, path, point, Point2D, Points
29
+ polygon, polygonVertices, port, Product, ProductHandleBuilder, ProductHandleFeature, ProductPanelBuilder, ProductRibbonBuilder
30
+ ProductSkin, ProductSkinBuilder, ProductSpoutBuilder, ProductStationBuilder, ProductSurfaceBuilder, ProductSurfaceRef, projectToPlane, queueMicrotask
31
+ radians, rect, Rectangle2D, Ribs, robotExport, roundedRect, Route3D, scene
32
+ Sculpt, sdf, SdfShape, selectEdge, selectEdges, self, setActiveBackend, setImmediate
33
+ setInterval, setTimeout, Shape, ShapeGroup, sheetMetal, SheetMetalPart, sheetStock, Sketch
34
+ sketchToDxf, sketchToSvg, slot, Slot, SolvedAssembly, spec, sphere, spline2d
35
+ star, stroke, Surface, SurfaceBody, SurfaceMembers, surfacePatch, sweep, tabSlot
36
+ text2d, textWidth, torus, toShape, Transform, union, union2d, variableSweep
37
+ verify, viewConfig, Viewport, window, Wood
39
38
  ```
40
39
 
40
+ <!-- forgecad-skill:exclude-start symbol="compatibility runtime names" reason="Compatibility-only renamed runtime globals." -->
41
+ Compatibility-only renamed runtime globals are also reserved in human-authored scripts, but are omitted from shipped agent skills so agents do not learn retired authoring APIs:
42
+
43
+ ```text
44
+ connectEdges, Helix, HelixCurve, hermiteTransitionG2, loftAlongSpine, nurbs3d, spline3d, transitionCurve
45
+ transitionSurface
46
+ ```
47
+ <!-- forgecad-skill:exclude-end -->
48
+
41
49
  `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.
@@ -49,9 +49,14 @@ Use today's date for the directory. Use the user's current ForgeCAD project when
49
49
 
50
50
  ### Workflow
51
51
 
52
- 1. Load the ForgeCAD skill — always invoke the `forgecad` skill first to get API docs and authoring guidance. Read at minimum the Core API reference. If any two parts are intended to touch or mate in the final model, read the positioning guide immediately and default to connectors + `matchTo()`.
52
+ 1. Load the ForgeCAD skill — always invoke the `forgecad` skill first to get API docs and authoring guidance. Read at minimum the Core API reference. If the model has moving parts, load the assembly group and `docs/permanent/guides/joint-design.md` before writing geometry. If any two parts are intended to touch or mate in the final model, read the positioning guide immediately and default to connectors + `matchTo()`.
53
53
  2. Create the directory — `mkdir -p YYYY/MM/DD/[folder]` as needed.
54
- 3. Write the modelcreate the `.forge.js` file(s) following ForgeCAD conventions:
54
+ 3. Prove moving-part kinematics before detailed geometry for any mechanism, robot, linkage, hinge, slider, suspension, gripper, wheel train, drawer, door, linkage toy, or articulated product:
55
+ - Author the rig first: links, frames, fixed ground references, parent/child relationships, joints, couplings, limits, defaults, mirrored-side sign mapping, and named controls.
56
+ - Choose the right rig primitive before modeling the skin: use link graphs (`link()`, `edgeBetweenLinks()`, `addAngleBetweenLinks()`) for point skeletons and closed loops; use frame/connector joints (`frame()`, `revoluteJoint()`, `prismaticJoint()`, `connect()`, `match()`) when part orientation matters.
57
+ - Attach only simple markers, bars, or proxy parts at first. Return the `Assembly` directly and run it at rest, mid-travel, and limit joint values until the solver, controls, connector/frame alignment, and `verify.*` checks pass.
58
+ - Only after the rig works should you replace proxies with manufacture-real geometry attached to the solved links, frames, and connectors. Do not bake a posed `SolvedAssembly` as the default return just to make one pose look right.
59
+ 4. Write the model — create the `.forge.js` file(s) following ForgeCAD conventions:
55
60
  - Treat the default build profile as `manufacture-realistic prototype`; choose and encode the artifact's manufacturing/process cues before adding styling detail
56
61
  - Declare `param()` / `boolParam()` for all tunable dimensions
57
62
  - If the model is split across files, use `main.forge.js` as the primary entry point, import renderable parts from neighboring `.forge.js` files, and keep only pure helpers/constants in plain `.js` modules
@@ -62,11 +67,12 @@ Use today's date for the directory. Use the user's current ForgeCAD project when
62
67
  - Make final mating geometry physically plausible: parts may touch, clear each other, or be boolean-joined, but should not unintentionally pass through each other
63
68
  - Model the physical artifact, not an educational diagram: no explanatory arrows, floating labels, section labels, legends, or text plaques unless the user explicitly requested a presentation/teaching view
64
69
  - Do not make the default returned model a cutaway, sectioned shell, permanently exploded assembly, or hidden-parts teaching view. ForgeCAD gives the user viewer and inspection tools for slicing, exploding, hiding, and looking inside after the real CAD exists.
65
- - Return the final geometry (single shape, array, or named objects array)
70
+ - Return the final artifact (single shape, array, named objects array, or `Assembly` for moving mechanisms)
71
+ - For moving assemblies, return the `Assembly` itself whenever possible so Motion controls re-run the real kinematic solve; avoid legacy viewport-only animation APIs for new work unless the user explicitly asks for them
66
72
  - Treat `fillet(shape, r)` and `chamfer(shape, r)` as experimental edge treatments: Manifold can produce incorrect results and OCCT can be very slow. Prefer simpler primitive profiles, lower segment counts, targeted edge selectors, and inspection before relying on the result.
67
- 4. Validate — run `forgecad run <file>` to check for errors. For multi-file projects, always validate `main.forge.js`.
68
- 5. Verify geometry — render a multi-angle visual evidence set before final delivery: whole-model context plus agent-chosen orthographic, oblique, underside, or hidden-object views that expose the relevant components and interfaces. Choose camera directions from the model's shape and likely failure modes, not from a fixed recipe. Use those views to look for internals that are accidentally visible, parts that visibly do not fit, floating details, blocked access, missing seats, and unexpected interference. Run `forgecad inspect physical components` when the model has multiple returned objects or visible attachments, run `forgecad debug assembly --fail-on warning` when the script uses `assembly()`, run `forgecad inspect mechanical-integrity <project-or-file> --collisions` before sharing generated mechanical work, and run the targeted `forgecad inspect <family> <mode>` commands that match the task (see Final Acceptance Gate and Render-Verify Loop below). For multi-file projects, render and inspect `main.forge.js`. Collision findings are model work, not FYI: remove unexpected overlaps before delivery.
69
- 6. Iterate from visual and inspection feedback — treat every render and inspection bundle as model evidence, not a checkbox. Read the normal PNGs, manifest, and evidence PNGs; convert each unexpected collision, thin region, missing section detail, wrong component count, floating body, distance gap, confusing object-color result, accidentally exposed internal structure, bad fit, or visually unsupported interface into a concrete model edit; then rerun the same targeted evidence pass until the result matches the intended physical component graph.
73
+ 5. Validate — run `forgecad run <file>` to check for errors. For multi-file projects, always validate `main.forge.js`. For moving assemblies, also run representative joint states with `--joint`, including rest/default, mid-travel, both limits, and any coupled or mirrored poses.
74
+ 6. Verify geometry — render a multi-angle visual evidence set before final delivery: whole-model context plus agent-chosen orthographic, oblique, underside, or hidden-object views that expose the relevant components and interfaces. Choose camera directions from the model's shape and likely failure modes, not from a fixed recipe. Use those views to look for internals that are accidentally visible, parts that visibly do not fit, floating details, blocked access, missing seats, and unexpected interference. For moving assemblies, render at several joint values with `--joint` so clearance, range, stops, and connected followers are proven in motion, not only at rest. Run `forgecad inspect physical components` when the model has multiple returned objects or visible attachments, run `forgecad debug assembly --fail-on warning` when the script uses `assembly()`, run `forgecad inspect mechanical-integrity <project-or-file> --collisions` before sharing generated mechanical work, and run the targeted `forgecad inspect <family> <mode>` commands that match the task (see Final Acceptance Gate and Render-Verify Loop below). For multi-file projects, render and inspect `main.forge.js`. Collision findings are model work, not FYI: remove unexpected overlaps before delivery.
75
+ 7. Iterate from visual and inspection feedback — treat every render and inspection bundle as model evidence, not a checkbox. Read the normal PNGs, manifest, and evidence PNGs; convert each unexpected collision, thin region, missing section detail, wrong component count, floating body, distance gap, confusing object-color result, accidentally exposed internal structure, bad fit, bad joint sweep, unconverged pose, follower drift, or visually unsupported interface into a concrete model edit; then rerun the same targeted evidence pass until the result matches the intended physical component graph.
70
76
 
71
77
  ### Manufacturing Process Is Not Assumed
72
78
 
@@ -123,6 +129,28 @@ Do not reveal hidden structure by permanently cutting away the production geomet
123
129
 
124
130
  When internals are hidden by the final exterior, verify them with exploration tools instead of changing the artifact: render underside or alternate camera views, use `forgecad inspect sections at|stack|sample`, use viewer-only cut planes or explode controls, temporarily make a shell transparent, or add named ghost objects for fit checks. Those views are diagnostic/presentation modes; they must not replace the real model unless the user explicitly asked for a cutaway teaching model.
125
131
 
132
+ ### Kinematics-First for Moving Parts
133
+
134
+ For moving models, the mechanism rig is the source of truth. Do not start with finished housings, arms, shells, covers, or decorative surfaces and then try to retrofit motion. Build the motion structure first, prove it, and attach geometry to it.
135
+
136
+ - Decide the rig shape before geometry: point links for closed-loop skeletons and distance/angle constraints; frames and connector joints for serial rigid parts whose orientation matters.
137
+ - Name every moving degree of freedom and encode its limits/defaults at the rig level. For mirrored revolute axes, make the sign mapping explicit instead of assuming equal values create mirrored poses.
138
+ - Add simple proxy geometry only to expose the rig: pivot markers, bars between links, slider blocks, wheel discs, or frame axes. Use those proxies to verify the mechanism before adding real material.
139
+ - Attach final geometry to solved links, frames, and connectors. A visible arm, cover, wheel, drawer, or follower should move because it is mated to the rig, not because a final `rotate()` or `translate()` happens to make one pose look assembled.
140
+ - Keep the default return as the unsolved `Assembly` when possible. This lets Motion controls and CLI `--joint` overrides re-run the real solve.
141
+ - Put representative pose checks in the script with `verify.*`: convergence, connector origins coinciding, link lengths holding, end effectors reaching their expected points, followers remaining attached, and required running clearances staying positive.
142
+
143
+ At minimum, run the mechanism at rest, mid-travel, both limits, and any mirrored/coupled poses:
144
+
145
+ ```bash
146
+ node dist-cli/forgecad.js run model.forge.js --joint "theta=0"
147
+ node dist-cli/forgecad.js run model.forge.js --joint "theta=45"
148
+ node dist-cli/forgecad.js run model.forge.js --joint "theta=90"
149
+ node dist-cli/forgecad.js render 3d model.forge.js /tmp/model-theta-45.png --joint "theta=45" --camera iso --size 700
150
+ ```
151
+
152
+ Use the real joint/control names from the assembly. For multiple controls, repeat `--joint`: `--joint "hip=20" --joint "knee=-35"`. If any pose fails to solve, clamps unexpectedly, breaks a connector check, or creates a new collision, the rig is not ready for final geometry.
153
+
126
154
  ### Mechanical Assembly Contract
127
155
 
128
156
  For mechanical models, a ForgeCAD script is not done when it merely looks assembled. Every visible piece must have a believable physical reason to be where it is: fused material, contact faces, a screw stack, a pin in a bore, a tab in a slot, a gasket on a land, a bearing in a seat, a cable in a channel, or a named intentional ghost.
@@ -137,37 +165,7 @@ For mechanical models, a ForgeCAD script is not done when it merely looks assemb
137
165
  - Purchased loose parts may remain separate bodies, but they should be named as purchased hardware or consumables and should sit in believable sockets, bores, races, guides, or fastener stacks.
138
166
  - Encode interface intent with `verify.*`, not only comments. Use `verify.clearanceBetween("cover is seated on gasket", cover, gasket, -0.01, 0.05)` for contact/seated fits and clearance bands, `verify.minClearance(...)` or `verify.notColliding(...)` for keep-out/running gaps, and `verify.connectorDistance(...)` for connector-authored mates. Part counts and generic dimensions are useful supporting checks, but they do not prove an interface by themselves.
139
167
 
140
- For ordinary removable covers, prefer `lib.boltedServiceCover(...)` before hand-placing plates, tabs, screw heads, gaskets, and holes. It creates the parent ledge, gasket, cover plate with fused pull tabs, shared bolt pattern, and installed screws as one mechanically accountable interface.
141
-
142
- For electronics boxes, backplates, service-stack housings, and camera/monitor enclosures, prefer `lib.datumEnclosureAssembly(...)` before independently placing panels, ribs, bosses, ports, covers, and screws. It creates the tray, ledges, standoffs, ribs, service port, gasket, cover, and screws from one shared datum system.
143
-
144
- For PCB-mounted terminal blocks, thermostat backplates, control boards, and wire-entry electronics panels, prefer `lib.pcbTerminalBlockAssembly(...)` before placing a loose green block near a board or cover. It creates the backplate, fused standoffs, PCB mounting screws, PCB pin holes, terminal pins, and seated purchased terminal block from one shared datum system.
145
-
146
- For snap-retained covers, cartridges, small clasps, and housings, prefer `lib.snapLatchCoverAssembly(...)` before drawing decorative snap tabs. It creates latch windows, underside catch lands, fused snap hooks, barbs, and clearance checks so the cover is retained by real geometry.
147
-
148
- For ordinary pinned handles, cam levers, release levers, and latch arms, prefer `lib.pinnedLeverAssembly(...)` before hand-placing a hub, arm, washers, and pin. It creates a fused lever body, aligned pivot bore, retained pin, thrust washers, support land, and low stop land as one mechanically accountable pivot stack.
149
-
150
- For trunnions, side knobs, adjustable pivots, and clamp shafts, prefer `lib.retainedShaftAssembly(...)` before hand-placing rods, washers, and knobs. It creates bored support cheeks, a through shaft, thrust washers, knobs, retaining heads, and shared bore dimensions as one mechanically accountable stack.
151
-
152
- For thumb screws, desk clamps, vise screws, capo pressure screws, and small fixture hold-downs, prefer `lib.thumbScrewClampAssembly(...)` before hand-placing a knob, screw cylinder, pressure pad, and bracket jaw. It creates the C-frame, threaded boss/bore, captive pressure pad, hand knob, and seated workpiece contact from one shared datum system.
153
-
154
- For drawer slides, quick-release plates, and guided linear carriages, prefer `lib.capturedLinearSlide(...)` before hand-placing rails and a block. It creates a U-channel rail with return lips, end stops, a captured carriage, and explicit travel/clearance dimensions.
155
-
156
- For pump cartridges, filter cassettes, battery cartridges, skeg cassettes, and removable slide-in modules, prefer `lib.capturedCartridgeGuideAssembly(...)` before placing a loose tray and block. It creates return lips, a rear stop, a captured cartridge flange, pull tab, insertion travel, and explicit clearance dimensions.
157
-
158
- For molded flexible battery doors, sample covers, blister latches, and polypropylene-style service flaps, prefer `lib.livingHingeCoverAssembly(...)` before drawing two plates with a decorative hinge strip. It creates one fused molded strip with fixed leaf, thin flexible web, moving cover leaf, pull lip, snap barb, catch land, and web-thickness checks.
159
-
160
- For doors, barn-door leaves, lids, locket leaves, and small hinged access panels, prefer `lib.knuckledHingeAssembly(...)` before hand-placing barrels and a pin. It creates alternating fused knuckles, two leaves, a shared bore, and a retained pin as one mechanically accountable hinge.
161
-
162
- For crank links, damper rod ends, crossheads, and clevis-yoke pivots, prefer `lib.clevisPinJointAssembly(...)` before hand-placing an eyelet and pin. It creates bored clevis ears, a captured center link eye, a rear bridge, and a retained pin as one mechanically accountable load path.
163
-
164
- For bearings, rollers, burr cartridges, spindle supports, and purchased radial bearings, prefer `lib.seatedBearingAssembly(...)` before hand-placing a ring and shaft near a block. It creates a bored housing, counterbore pocket, bearing shoulder, seated bearing, shaft, collars, and shared clearance dimensions as one mechanically accountable support.
165
-
166
- For cables, wires, hoses, pump tubes, and panel pass-throughs, prefer `lib.cableGlandAnchorAssembly(...)` before hand-placing a loose cylinder near a wall. It creates the panel clearance hole, hollow gland body, compression nut, and routed cable/tube as one mechanically accountable pass-through.
167
-
168
- For routed cables, wires, hoses, pump tubes, and sensor leads that run along a surface, prefer `lib.routedTubeClipAssembly(...)` before drawing a tube that floats between endpoints. It creates a base panel, saddle clip bores, clip screw holes, installed screws, and the retained tube route from one shared datum system.
169
-
170
- For fluid hoses, pump inlets/outlets, filter ports, and lab tubing, prefer `lib.hoseBarbPortAssembly(...)` before drawing a tube that stops at a block. It creates the bored receiver, raised boss, hollow barbed fitting, installed hose, and clamp band as one accountable hose-port interface.
168
+ Do not introduce canned finished-object helpers for project-specific assemblies. When a repeated physical pattern appears, build the project-specific interface explicitly: use `lib.fastenerSet()` or `lib.boltPattern()` for screw stacks, real bores/counterbores/pockets for purchased parts, connectors plus `matchTo()` for mating parts, and `verify.*` checks for seated contact and running clearances. Reusable library vocabulary should be primitives, cutters, patterns, and mechanical contracts, not finished thermostat backplates, clamp brackets, hinge leaves, or other canned objects.
171
169
 
172
170
  ### Final Geometry Should Be Physically Plausible
173
171
 
@@ -211,6 +209,8 @@ Before telling the user the model is done, prove both technical validity and vis
211
209
 
212
210
  The model should include at least one verification that proves a mechanical interface, not just object count. Prefer checks such as `verify.clearanceBetween("bearing is seated in pocket", bearing, housing, -0.01, 0.1)`, `verify.minClearance("carriage clears rail", carriage, rail, 0.15)`, `verify.notColliding("cover screw clears parent hole", screw, parent)`, or `verify.connectorDistance("leg connector is seated", bench, "Rail.leg_0", "Leg0.head", 0, 0.01)`.
213
211
 
212
+ For moving assemblies, this gate is incomplete until the rig has been run through representative control states. Use `--joint` overrides and/or in-script `solve(state)` verification to cover rest/default, mid-travel, physical limits, coupled states, and mirrored-side poses. Each tested pose should keep the solver converged, attached geometry following the rig, and clearances/collisions within the intended mechanical contract.
213
+
214
214
  2. Run collision evidence and read both the manifest and images:
215
215
 
216
216
  ```bash
@@ -246,7 +246,7 @@ Before telling the user the model is done, prove both technical validity and vis
246
246
 
247
247
  5. Treat ProductSkin and surface-member limitations honestly. If `inspect fit interference` reports boolean-test warnings because a sampled `Product.skin` loft has boundary edges, distinguish that from real collision findings. You may still deliver if `collisionCount` is clean, the intended connectivity is correct, and the visual attachment audit passes. Mention the residual warning briefly in the final response.
248
248
 
249
- 6. Final response must name the evidence: commands run, render views checked, any focus/hide filters used, component count, collision count, and any residual warnings or intentional exceptions. Do not just say "validated."
249
+ 6. Final response must name the evidence: commands run, render views checked, joint values tested for moving assemblies, any focus/hide filters used, component count, collision count, and any residual warnings or intentional exceptions. Do not just say "validated."
250
250
 
251
251
  ### Render-Verify Loop
252
252
 
@@ -443,6 +443,7 @@ Primitive placement convention:
443
443
  Key composition tools:
444
444
 
445
445
  - Connectors + `matchTo()` for parts that should touch in the final model
446
+ - `assembly()` link graphs, frames, and connector joints for moving mechanisms
446
447
  - `group()` for local-coordinate subassemblies
447
448
  - `attachTo()` for quick bounding-box placement
448
449
  - `.translate()` / `.rotate()` for free offsets or bridging computed locations, not as the default assembly contract
@@ -15,6 +15,8 @@ ForgeCAD project CLI workflow — creating, managing, syncing projects and files
15
15
 
16
16
  **forgecad.io** is the primary platform for ForgeCAD projects. The CLI is the main way AI agents interact with it — creating projects, managing files, publishing models, and collaborating. `forgecad studio <project-path> [project-path ...]` opens the installed local editor for users; `forgecad dev <project-path> [project-path ...]` is mainly for ForgeCAD source development.
17
17
 
18
+ Keep one long-running `forgecad studio <project-path> [project-path ...]` process open with every active project folder listed in its arguments; the user opens the single printed localhost port once, and AI agents should only create or edit files under those folders so the browser updates live without starting more servers.
19
+
18
20
  ### Authentication
19
21
 
20
22
  ```sh
@@ -31,6 +31,8 @@ forgecad studio .
31
31
 
32
32
  `forgecad studio .` opens the local editor around the current project folder. The `.` is not optional; `forgecad studio` requires a project path.
33
33
 
34
+ Keep one long-running `forgecad studio <project-path> [project-path ...]` process open with every active project folder listed in its arguments; the user opens the single printed localhost port once, and AI agents should only create or edit files under those folders so the browser updates live without starting more servers.
35
+
34
36
  ### Create A New Local Project
35
37
 
36
38
  Use this when you want a fresh model instead of the starter project:
package/dist/index.html CHANGED
@@ -83,7 +83,7 @@
83
83
  * { margin: 0; padding: 0; box-sizing: border-box; }
84
84
  html, body, #root { width: 100%; min-height: 100%; background: var(--fc-bg); color: var(--fc-text); font-family: system-ui, -apple-system, sans-serif; }
85
85
  </style>
86
- <script type="module" crossorigin src="/assets/app-D3kDkggg.js"></script>
86
+ <script type="module" crossorigin src="/assets/app-T0pDcSX4.js"></script>
87
87
  <link rel="stylesheet" crossorigin href="/assets/app-CE3sYcV7.css">
88
88
  </head>
89
89
  <body>