forgecad 0.6.3 → 0.7.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 (193) hide show
  1. package/README.md +2 -11
  2. package/dist/assets/{AdminPage-CeqCUUgu.js → AdminPage-DAu1C1ST.js} +250 -151
  3. package/dist/assets/{BlogPage-P_AJP0v9.js → BlogPage-CJEXL_zJ.js} +94 -70
  4. package/dist/assets/{DocsPage-CKRV2iq2.js → DocsPage-Gc_BCdqC.js} +269 -143
  5. package/dist/assets/EditorApp-D9bJvtf7.js +11338 -0
  6. package/dist/assets/{EditorApp-CnC2k4cW.css → EditorApp-DG1-oUSV.css} +459 -87
  7. package/dist/assets/{EmbedViewer-DBlzmQ5i.js → EmbedViewer-CEO8XbV8.js} +2 -4
  8. package/dist/assets/LandingPage-CdCuEOdC.js +451 -0
  9. package/dist/assets/PricingPage-BSrxu6d7.js +232 -0
  10. package/dist/assets/{SettingsPage-BqCh9JcC.js → SettingsPage-FUCSIRq6.js} +129 -5
  11. package/dist/assets/{evalWorker-Ql-aKwLA.js → evalWorker-KoR0SNKq.js} +6770 -2914
  12. package/dist/assets/{index-2hfs_ub0.css → index-CyVd1D4D.css} +227 -53
  13. package/dist/assets/{Viewport-CoB46f5R.js → index-wTEK39at.js} +31385 -6439
  14. package/dist/assets/{javascript-DCxGoE5Y.js → javascript-DAl8Gmyo.js} +1 -1
  15. package/dist/assets/{manifold-CqNMHHKO.js → manifold-B1sGWdYk.js} +4 -3
  16. package/dist/assets/{manifold-Cce9wRFz.js → manifold-D7o0N50J.js} +1 -1
  17. package/dist/assets/{manifold-D6BeHIOo.js → manifold-G5sBaXzi.js} +1 -1
  18. package/dist/assets/{reportWorker-sFEFonXf.js → reportWorker-DYcRHhv9.js} +6798 -3341
  19. package/dist/assets/{vendor-react-Dt7-aaJH.js → vendor-react-CG3i_wp0.js} +65 -8
  20. package/dist/docs-raw/generated/assembly.md +691 -112
  21. package/dist/docs-raw/generated/concepts.md +1225 -1400
  22. package/dist/docs-raw/generated/core.md +464 -1412
  23. package/dist/docs-raw/generated/curves.md +593 -117
  24. package/dist/docs-raw/generated/lib.md +38 -748
  25. package/dist/docs-raw/generated/output.md +139 -245
  26. package/dist/docs-raw/generated/sheet-metal.md +473 -21
  27. package/dist/docs-raw/generated/sketch.md +553 -349
  28. package/dist/docs-raw/generated/viewport.md +345 -303
  29. package/dist/docs-raw/generated/wood.md +104 -0
  30. package/dist/index.html +2 -2
  31. package/dist/sitemap.xml +6 -6
  32. package/dist-cli/chunk-PZ5AY32C.js +10 -0
  33. package/dist-cli/chunk-PZ5AY32C.js.map +1 -0
  34. package/dist-cli/forgecad.js +9435 -5407
  35. package/dist-cli/forgecad.js.map +1 -0
  36. package/dist-cli/solver-FV7TJZGI.js +365 -0
  37. package/dist-cli/solver-FV7TJZGI.js.map +1 -0
  38. package/dist-skill/CONTEXT.md +3186 -7145
  39. package/dist-skill/SKILL-dev.md +21 -63
  40. package/dist-skill/SKILL.md +12 -56
  41. package/dist-skill/docs/API/core/concepts.md +16 -98
  42. package/dist-skill/docs/CLI/export.md +91 -0
  43. package/dist-skill/docs/CLI/projects.md +107 -0
  44. package/dist-skill/docs/CLI/studio_publishing.md +52 -0
  45. package/dist-skill/docs/CLI/validation.md +66 -0
  46. package/dist-skill/docs/generated/assembly.md +691 -112
  47. package/dist-skill/docs/generated/core.md +464 -1412
  48. package/dist-skill/docs/generated/curves.md +593 -117
  49. package/dist-skill/docs/generated/lib.md +38 -748
  50. package/dist-skill/docs/generated/output.md +139 -245
  51. package/dist-skill/docs/generated/sheet-metal.md +473 -21
  52. package/dist-skill/docs/generated/sketch.md +553 -349
  53. package/dist-skill/docs/generated/viewport.md +345 -303
  54. package/dist-skill/docs/generated/wood.md +104 -0
  55. package/dist-skill/docs/guides/coordinate-system.md +11 -17
  56. package/dist-skill/docs/guides/geometry-conventions.md +13 -70
  57. package/dist-skill/docs/guides/modeling-recipes.md +22 -195
  58. package/dist-skill/docs/guides/positioning.md +88 -147
  59. package/dist-skill/docs-dev/API/core/concepts.md +51 -0
  60. package/dist-skill/docs-dev/API/core/sdf-advanced.md +92 -0
  61. package/dist-skill/docs-dev/API/core/sdf-primitives.md +58 -0
  62. package/dist-skill/docs-dev/API/core/sdf-workflow.md +42 -0
  63. package/dist-skill/docs-dev/CLI/export.md +91 -0
  64. package/dist-skill/docs-dev/CLI/projects.md +107 -0
  65. package/dist-skill/docs-dev/CLI/studio_publishing.md +52 -0
  66. package/dist-skill/docs-dev/CLI/validation.md +66 -0
  67. package/dist-skill/{docs → docs-dev}/blueprint-first.md +5 -0
  68. package/dist-skill/{docs → docs-dev}/coding-best-practices.md +6 -8
  69. package/dist-skill/{docs → docs-dev}/coding.md +1 -3
  70. package/dist-skill/docs-dev/generated/assembly.md +771 -0
  71. package/dist-skill/docs-dev/generated/core.md +775 -0
  72. package/dist-skill/docs-dev/generated/curves.md +688 -0
  73. package/dist-skill/docs-dev/generated/lib.md +50 -0
  74. package/dist-skill/docs-dev/generated/output.md +234 -0
  75. package/dist-skill/docs-dev/generated/sheet-metal.md +506 -0
  76. package/dist-skill/docs-dev/generated/sketch.md +801 -0
  77. package/dist-skill/docs-dev/generated/viewport.md +486 -0
  78. package/dist-skill/docs-dev/generated/wood.md +104 -0
  79. package/dist-skill/docs-dev/guides/coordinate-system.md +46 -0
  80. package/dist-skill/docs-dev/guides/geometry-conventions.md +52 -0
  81. package/dist-skill/docs-dev/guides/modeling-recipes.md +77 -0
  82. package/dist-skill/docs-dev/guides/positioning.md +151 -0
  83. package/dist-skill/{docs → docs-dev}/guides/skill-maintenance.md +21 -10
  84. package/dist-skill/{docs → docs-dev}/internals/compiler.md +5 -6
  85. package/dist-skill/{docs → docs-dev}/internals/constraint-solver-quality.md +0 -1
  86. package/dist-skill/{docs → docs-dev}/internals/constraint-solver.md +0 -1
  87. package/dist-skill/{docs → docs-dev}/internals/sketch-2d-pipeline.md +2 -3
  88. package/examples/api/attachTo-basics.forge.js +5 -5
  89. package/examples/api/boolean-operations.forge.js +3 -3
  90. package/examples/api/bounding-box-visualizer.forge.js +2 -2
  91. package/examples/api/clone-duplicate.forge.js +1 -1
  92. package/examples/api/colors-union-vs-array.forge.js +6 -6
  93. package/examples/api/connector-assembly.forge.js +4 -4
  94. package/examples/api/connector-basics.forge.js +2 -2
  95. package/examples/api/extrude-options.forge.js +4 -10
  96. package/examples/api/feature-created-faces.forge.js +6 -10
  97. package/examples/api/fillet-showcase.forge.js +1 -1
  98. package/examples/api/folded-service-panel-cover.forge.js +2 -2
  99. package/examples/api/group-test.forge.js +1 -1
  100. package/examples/api/group-vs-union.forge.js +1 -1
  101. package/examples/api/highlight-debug.forge.js +4 -0
  102. package/examples/api/js-module-pillars.js +1 -1
  103. package/examples/api/js-module-scene.js +2 -2
  104. package/examples/api/mesh-import-slats.forge.js +1 -1
  105. package/examples/api/pointAlong-orientation.forge.js +1 -1
  106. package/examples/api/profile-2020-b-slot6.forge.js +0 -1
  107. package/examples/api/route-perimeter-flange.forge.js +1 -1
  108. package/examples/api/sdf-rover-demo.forge.js +10 -10
  109. package/examples/api/sketch-on-face-demo.forge.js +2 -2
  110. package/examples/api/sketch-regions.forge.js +4 -4
  111. package/examples/api/transition-curves.forge.js +1 -1
  112. package/examples/api/variable-sweep-pure-sdf-test.forge.js +162 -0
  113. package/examples/api/variable-sweep-test.forge.js +2 -2
  114. package/examples/api/wood-joinery.forge.js +60 -0
  115. package/examples/compiler-corpus/enclosure-shell-cuts.forge.js +3 -3
  116. package/examples/compiler-corpus/fastener-plate-variants.forge.js +2 -2
  117. package/examples/experiments/drone-arm.forge.js +53 -0
  118. package/examples/furniture/adjustable-table.forge.js +2 -2
  119. package/examples/furniture/bathroom.forge.js +11 -11
  120. package/examples/furniture/chair.forge.js +1 -1
  121. package/examples/generative/crystal-growth.forge.js +2 -2
  122. package/examples/generative/frost-spires.forge.js +3 -3
  123. package/examples/generative/golden-spiral-tower.forge.js +3 -3
  124. package/examples/mechanical/3d-printer.forge.js +28 -28
  125. package/examples/mechanical/5-finger-robot-hand.forge.js +15 -15
  126. package/examples/mechanical/airplane-propeller.forge.js +2 -2
  127. package/examples/mechanical/fillet-enclosure.forge.js +1 -1
  128. package/examples/mechanical/headphone-hanger-v2.forge.js +2 -2
  129. package/examples/mechanical/robot_hand.forge.js +15 -15
  130. package/examples/mechanical/robot_hand_2.forge.js +9 -9
  131. package/examples/products/bottle.forge.js +1 -1
  132. package/examples/products/chess-set.forge.js +19 -19
  133. package/examples/products/classical-piano.forge.js +11 -11
  134. package/examples/products/clock.forge.js +12 -12
  135. package/examples/products/iphone.forge.js +8 -8
  136. package/examples/products/laptop.forge.js +15 -15
  137. package/examples/products/liquid-soap-dispenser.forge.js +18 -18
  138. package/examples/products/origami-fish.forge.js +8 -6
  139. package/examples/products/spiderman-cake.forge.js +4 -4
  140. package/examples/toolbox/bolted-joint.forge.js +2 -2
  141. package/package.json +7 -4
  142. package/dist/assets/EditorApp-B-vQvgam.js +0 -9888
  143. package/dist/assets/LandingPage-C5n9hDXI.js +0 -322
  144. package/dist/assets/PublishedModelPage-Dt7PCVBj.js +0 -146
  145. package/dist/assets/__vite-browser-external-CURh0WXD.js +0 -8
  146. package/dist/assets/deserializeRunResult-BLAFoiE0.js +0 -19365
  147. package/dist/assets/index-1CYp3zUp.js +0 -1455
  148. package/dist/docs-raw/CLI.md +0 -865
  149. package/dist-skill/docs/API/API.md +0 -1666
  150. package/dist-skill/docs/API/README.md +0 -37
  151. package/dist-skill/docs/API/assembly/assembly.md +0 -617
  152. package/dist-skill/docs/API/core/edge-queries.md +0 -130
  153. package/dist-skill/docs/API/core/parameters.md +0 -122
  154. package/dist-skill/docs/API/core/reserved-terms.md +0 -137
  155. package/dist-skill/docs/API/core/sdf.md +0 -326
  156. package/dist-skill/docs/API/core/skill-cli.md +0 -194
  157. package/dist-skill/docs/API/core/skill-guide.md +0 -205
  158. package/dist-skill/docs/API/core/specs.md +0 -186
  159. package/dist-skill/docs/API/core/topology.md +0 -372
  160. package/dist-skill/docs/API/entities.md +0 -268
  161. package/dist-skill/docs/API/output/bom.md +0 -58
  162. package/dist-skill/docs/API/output/brep-export.md +0 -87
  163. package/dist-skill/docs/API/output/dimensions.md +0 -67
  164. package/dist-skill/docs/API/output/export.md +0 -110
  165. package/dist-skill/docs/API/output/gcode.md +0 -195
  166. package/dist-skill/docs/API/runtime/viewport.md +0 -420
  167. package/dist-skill/docs/API/sheet-metal/sheet-metal.md +0 -185
  168. package/dist-skill/docs/API/sketch/anchor.md +0 -37
  169. package/dist-skill/docs/API/sketch/booleans.md +0 -91
  170. package/dist-skill/docs/API/sketch/core.md +0 -73
  171. package/dist-skill/docs/API/sketch/extrude.md +0 -62
  172. package/dist-skill/docs/API/sketch/on-face.md +0 -104
  173. package/dist-skill/docs/API/sketch/operations.md +0 -78
  174. package/dist-skill/docs/API/sketch/path.md +0 -75
  175. package/dist-skill/docs/API/sketch/primitives.md +0 -146
  176. package/dist-skill/docs/API/sketch/regions.md +0 -80
  177. package/dist-skill/docs/API/sketch/text.md +0 -108
  178. package/dist-skill/docs/API/sketch/transforms.md +0 -65
  179. package/dist-skill/docs/API/toolbox/fasteners.md +0 -129
  180. package/dist-skill/docs/CLI.md +0 -865
  181. package/dist-skill/docs/INDEX.md +0 -94
  182. package/dist-skill/docs/RELEASING.md +0 -55
  183. package/dist-skill/docs/cli-monetization.md +0 -111
  184. package/dist-skill/docs/deployment.md +0 -281
  185. package/dist-skill/docs/generated/concepts.md +0 -2112
  186. package/dist-skill/docs/internals/shape-from-slices.md +0 -152
  187. package/dist-skill/docs/platform/admin.md +0 -45
  188. package/dist-skill/docs/platform/architecture.md +0 -79
  189. package/dist-skill/docs/platform/auth.md +0 -110
  190. package/dist-skill/docs/platform/email.md +0 -67
  191. package/dist-skill/docs/platform/projects.md +0 -111
  192. package/dist-skill/docs/platform/sharing.md +0 -90
  193. package/dist-skill/docs/runbook.md +0 -345
@@ -5,85 +5,78 @@ skill-order: 100
5
5
 
6
6
  # Assembly API
7
7
 
8
- > **Auto-generated** from `src/forge/forge-public-api.ts`. Do not edit by hand — run `npm run gen:docs` to regenerate.
9
-
10
8
  Kinematic assemblies, joints, couplings, and robot export.
11
9
 
10
+ ## Contents
11
+
12
+ - [Assembly & Joints](#assembly-joints) — `bomToCsv`, `assembly`, `joint`
13
+ - [Assembly](#assembly) — Structure, Joints, Solving
14
+ - [ImportedAssembly](#importedassembly)
15
+ - [SolvedAssembly](#solvedassembly)
16
+ - [MateBuilder](#matebuilder)
17
+
12
18
  ## Functions
13
19
 
14
20
  ### Assembly & Joints
15
21
 
16
- Build kinematic assemblies with joints and couplings.
22
+ #### `bomToCsv()` Convert an array of BOM rows into a CSV string.
17
23
 
18
- #### `bomToCsv()`
24
+ Produces a CSV with columns: `part`, `qty`, `material`, `process`, `tolerance`, `notes`. String values are quoted and internal double-quotes are escaped. Prefer calling `solvedAssembly.bomCsv()` directly — this function is exposed for custom BOM processing.
19
25
 
20
26
  ```ts
21
27
  bomToCsv(rows: BomRow[]): string
22
28
  ```
23
29
 
24
- Convert BOM rows from a solved assembly into a CSV string.
30
+ **`BomRow`**: `part: string`, `qty: number`, `material?: string`, `process?: string`, `tolerance?: string`, `notes?: string`, `metadata?: PartMetadata`
25
31
 
26
- <details><summary><code>BomRow</code></summary>
32
+ **`PartMetadata`**: `material?: string`, `process?: string`, `tolerance?: string`, `qty?: number`, `notes?: string`, `densityKgM3?: number`, `massKg?: number`
27
33
 
28
- ```ts
29
- interface BomRow {
30
- part: string;
31
- qty: number;
32
- material?: string;
33
- process?: string;
34
- tolerance?: string;
35
- notes?: string;
36
- metadata?: PartMetadata;
37
- }
38
- ```
34
+ #### `assembly()` — Create an assembly container with named parts and joints for kinematic mechanisms.
39
35
 
40
- </details>
36
+ An assembly models a mechanism as a directed graph of parts connected by joints. Parts are the nodes; joints are directed edges from parent to child. The graph must be a forest (no cycles). Root parts (those with no incoming joint) are anchored to world space.
41
37
 
42
- <details><summary><code>PartMetadata</code></summary>
38
+ Three joint types are supported: `'revolute'` (hinge), `'prismatic'` (slider), and `'fixed'` (rigid attachment). Use `addPart()` to add geometry, `addJoint()` (or the shorthands `addRevolute()`, `addPrismatic()`, `addFixed()`) to connect parts, and `solve()` to compute world-space positions at a given joint state.
43
39
 
44
- ```ts
45
- interface PartMetadata {
46
- material?: string;
47
- process?: string;
48
- tolerance?: string;
49
- qty?: number;
50
- notes?: string;
51
- densityKgM3?: number;
52
- massKg?: number;
53
- }
54
- ```
40
+ The higher-level `connect()` API uses declared **connectors** to compute joint frames automatically. The `match()` API uses typed connectors (with gender and type metadata) for automatic compatibility validation and joint creation.
55
41
 
56
- </details>
42
+ For multi-file assemblies, a file that returns an `Assembly` is importable via [`require()`](/docs/core#require) and yields an `ImportedAssembly`. Use `mergeInto()` to flatten a sub-assembly into a parent assembly.
57
43
 
58
- #### `assembly()`
44
+ ```ts
45
+ const mech = assembly("Arm")
46
+ .addPart("base", box(80, 80, 20, true), {
47
+ metadata: { material: "PETG", process: "FDM", qty: 1 },
48
+ })
49
+ .addPart("link", box(140, 24, 24).translate(0, -12, -12))
50
+ .addRevolute("shoulder", "base", "link", {
51
+ axis: [0, 1, 0],
52
+ min: -30, max: 120, default: 25,
53
+ frame: Transform.identity().translate(0, 0, 20),
54
+ });
55
+
56
+ return mech; // auto-solved at defaults, renders all parts
57
+ ```
59
58
 
60
59
  ```ts
61
60
  assembly(name?: string): Assembly
62
61
  ```
63
62
 
64
- Create an assembly container with named parts and joints for kinematic mechanisms. Build with addPart(), addJoint(), addJointCoupling(), addGearCoupling(), then solve() to get positioned parts. Supports revolute, prismatic, and fixed joint types.
63
+ #### `joint()` Create a revolute joint that auto-generates a parameter slider and rotates the shape.
65
64
 
66
- #### `joint()`
65
+ This is a convenience wrapper for single-shape, single-joint use cases. It calls [`param()`](/docs/core#param) to create a named angle slider, then applies `rotateAroundAxis()` to the shape. Use the full `Assembly` API for mechanisms with multiple parts and joints.
67
66
 
68
67
  ```ts
69
- joint(name: string, shape: Shape, pivot: [ number, number, number ], opts?: RevoluteJointOpts): Shape
68
+ const arm = joint("Shoulder", armShape, [0, 0, 20], {
69
+ axis: [0, 1, 0],
70
+ min: -30, max: 120, default: 25,
71
+ });
72
+ return arm;
70
73
  ```
71
74
 
72
- Create a revolute (hinge) joint. Auto-creates a param slider and rotates the shape.
73
-
74
- <details><summary><code>RevoluteJointOpts</code></summary>
75
-
76
75
  ```ts
77
- interface RevoluteJointOpts {
78
- min?: number;
79
- max?: number;
80
- default?: number;
81
- unit?: string;
82
- reverse?: boolean;
83
- }
76
+ joint(name: string, shape: Shape, pivot: [ number, number, number ], opts?: RevoluteJointOpts): Shape
84
77
  ```
85
78
 
86
- </details>
79
+ `RevoluteJointOpts`: `{ min?: number, max?: number, default?: number, unit?: string, reverse?: boolean }`
87
80
 
88
81
  ---
89
82
 
@@ -91,61 +84,507 @@ interface RevoluteJointOpts {
91
84
 
92
85
  ### `Assembly`
93
86
 
87
+ Container for a kinematic mechanism made up of named parts and joints.
88
+
89
+ An assembly is a directed graph where **parts** are nodes and **joints** are directed edges from parent to child. The graph must be a forest (one or more trees with no cycles). Root parts (no incoming joint) are fixed to world space.
90
+
91
+ Each joint carries a `frame` transform (from the parent part frame to the joint's zero-state frame) and a motion formula:
92
+
93
+ ```
94
+ childWorld = parentWorld × frame × motion(value) × childBase
95
+ ```
96
+
97
+ Three joint types are supported:
98
+
99
+ - **revolute** — rotates the child around an axis by `value` degrees
100
+ - **prismatic** — translates the child along an axis by `value` mm
101
+ - **fixed** — no motion; rigidly attaches the child at `frame`
102
+
103
+ **Quick start**
104
+
105
+ ```ts
106
+ const mech = assembly("Arm")
107
+ .addPart("base", box(80, 80, 20, true))
108
+ .addPart("link", box(140, 24, 24).translate(0, -12, -12))
109
+ .addJoint("shoulder", "revolute", "base", "link", {
110
+ axis: [0, 1, 0],
111
+ min: -30, max: 120, default: 25,
112
+ frame: Transform.identity().translate(0, 0, 20),
113
+ });
114
+
115
+ return mech; // auto-solved at defaults
116
+ ```
117
+
118
+ Returning an unsolved `Assembly` auto-solves at default joint values. Return a `SolvedAssembly` directly for a specific pose:
119
+
120
+ ```ts
121
+ return mech.solve({ shoulder: 60 });
122
+ ```
123
+
124
+ **Return types**
125
+
126
+ | Return value | Standalone | `require()` result type | |---|---|---| | `Assembly` (unsolved) | yes | `ImportedAssembly` | | `SolvedAssembly` | yes | `SolvedAssembly` |
127
+
94
128
  **Properties:**
95
129
 
96
130
  | Property | Type | Description |
97
131
  |----------|------|-------------|
98
132
  | `name` | `string` | — |
99
133
 
100
- **Methods:**
134
+ **Solving**
135
+
136
+ #### `solve()` — Solve the assembly at the given joint state and return positioned parts.
137
+
138
+ Performs a depth-first traversal of the joint graph. Each joint's value is taken from `state`, falling back to `defaultValue`. Coupled joints compute their value from source joints. Values outside `[min, max]` are clamped (a warning is added to `SolvedAssembly.warnings()`).
139
+
140
+ If mate constraints were registered via `mate()`, the solver runs a pre-pass to derive base transforms, then the kinematic DFS applies joints on top of those positions.
141
+
142
+ **Pitfall — [`jointsView`](/docs/viewport#jointsview) double-rotation:** When calling `toJointsView()`, always solve at the rest pose (all joint values = 0 or default). Solving at a non-zero angle and then animating will double-rotate parts. Use the `defaults` option on `toJointsView()` to set the initial display angle instead.
143
+
144
+ ```ts
145
+ const solved = mech.solve({ shoulder: 45, elbow: -20 });
146
+ return solved.toGroup();
147
+ ```
148
+
149
+ ```ts
150
+ solve(state?: JointState): SolvedAssembly
151
+ ```
152
+
153
+ **Other**
101
154
 
102
- - `get usedPortRefs()` — Port refs (e.g. "PartName.portName") consumed by connect/match calls.
103
- - `mate()` — Register mate constraints between parts. Constraints are solved during `solve()` to derive part positions and explode hints. Part references use "partName:featureName" format.
104
- - `withReferences()` — Attach named placement reference points to this assembly. These are surfaced automatically on the ImportedAssembly when this file is imported via require(), so consumers can use placeReference() without re-declaring them. Returns `this` for chaining.
105
- - `getReferences()` — getReferences(): PlacementReferences
106
- - `withPorts()` — Attach named ports to a specific part or the assembly root. Ports declared this way are in the part's local coordinate system.
107
- - `getPorts()` — Get ports declared on a part (in part-local space).
108
- - `getAllPorts()` — getAllPorts(): Map<string, PortMap>
109
- - `getPort()` — Parse a "PartName.portName" reference and return the resolved port. Throws descriptive errors if the part or port doesn't exist.
110
- - `addFrame()` — Add a virtual reference frame (no geometry) to the assembly graph.
111
- - `addPart()` — addPart(name: string, part: AssemblyPart, options?: PartOptions): Assembly
112
- - `addJoint()` — addJoint(name: string, type: JointType, parent: string, child: string, options?:
113
- - `addRevolute()` — addRevolute(name: string, parent: string, child: string, options?: JointOptions)
114
- - `addPrismatic()` — addPrismatic(name: string, parent: string, child: string, options?: JointOptions
115
- - `addFixed()` — addFixed(name: string, parent: string, child: string, options?: JointOptions): A
116
- - `connect()` — Connect two parts by aligning their declared ports. `parentPortRef` and `childPortRef` use "PartName.portName" format. The system computes the joint frame and axis automatically from port alignment. ```javascript const mech = assembly("Arm") .addPart("Base", base) .addPart("Link", link) .connect("Base.top", "Link.bottom", { as: "J1", type: "revolute" }); ```
117
- - `match()` — Match connectors between two parts, auto-positioning and auto-creating joints. The connector metadata (type, gender, kind) determines the joint type automatically. If a connector is revolute, a revolute joint is created. If fixed or unspecified, fixed. ```javascript const mech = assembly("Door") .addPart("Frame", frame) .addPart("Door", door) .match("Door", "Frame", { hinge_top: "hinge_top", hinge_bottom: "hinge_bottom" }); ```
118
- - `addJointCoupling()` — addJointCoupling(jointName: string, options: JointCouplingOptions): Assembly
119
- - `addGearCoupling()` — addGearCoupling(drivenJointName: string, driverJointName: string, options?: Gear
120
- - `solve()` — solve(state?: JointState): SolvedAssembly
121
- - `sweepJoint()` — sweepJoint(jointName: string, from: number, to: number, steps: number, baseState
122
- - `toJointsView()` Derive `jointsView()` configuration from this assembly's joint graph and call it. Computes world-space pivots and axes from the solved rest pose, so you don't have to manually restate joint kinematics for the viewport runtime.
123
- - `toDisassemblyView()` — Generate a cinematic disassembly animation from the assembly's joint graph. Creates a `jointsView()` configuration with a "Disassemble" animation that sequences joint motions in reverse topological order (leaves first): - Revolute joints swing open to their max angle - Prismatic joints extend to their max distance - Fastener-named parts get extra rotation (unscrewing effect) Translation/separation is handled by the explode system (auto-configured by `solve()` with joint-derived directions). Use the explode slider in combination with this animation for the full disassembly effect.
124
- - `describe()`describe(): AssemblyDefinition
155
+ #### `usedPortRefs()` — Connector refs (e.g. "PartName.connectorName") consumed by connect/match calls.
156
+
157
+ ```ts
158
+ get usedPortRefs(): ReadonlySet<string>
159
+ ```
160
+
161
+ #### `mate()` — Register mate constraints between parts. Constraints are solved during `solve()` to derive part positions and explode hints. Part references use "partName:featureName" format.
162
+
163
+ ```ts
164
+ mate(fn: (m: MateBuilder) => void): Assembly
165
+ ```
166
+
167
+ #### `withReferences()` — Attach named placement reference points to this assembly. These are surfaced automatically on the ImportedAssembly when this file is imported via require(), so consumers can use placeReference() without re-declaring them. Returns `this` for chaining.
168
+
169
+ ```ts
170
+ withReferences(refs: Pick<PlacementReferenceInput, "points">): Assembly
171
+ ```
172
+
173
+ #### `withConnectors()` — Attach named connectors to a specific part or the assembly as a whole.
174
+
175
+ Connectors declared this way are in the part's local coordinate system. They are captured automatically if the incoming [`Shape`](/docs/core#shape) already has connectors via `shape.withConnectors(...)`, but you can also add or override connectors after the fact with this method.
176
+
177
+ Use the single-argument overload to attach assembly-level connectors these are exposed when this assembly is imported as a sub-assembly.
178
+
179
+ ```ts
180
+ withConnectors(partName: string, connectors: Record<string, ConnectorInput>): Assembly
181
+ ```
182
+
183
+ #### `withPorts()` — Backward-compatible alias for `withConnectors()`.
184
+
185
+ ```ts
186
+ withPorts(partNameOrPorts: string | Record<string, PortInput>, maybePorts?: Record<string, PortInput>): Assembly
187
+ ```
188
+
189
+ #### `getConnectors()` — Get connectors declared on a part in part-local space.
190
+
191
+ ```ts
192
+ getConnectors(partName: string): PortMap
193
+ ```
194
+
195
+ #### `getPorts()` — Backward-compatible alias for `getConnectors()`.
196
+
197
+ ```ts
198
+ getPorts(partName: string): PortMap
199
+ ```
200
+
201
+ #### `getPort()` — Parse a "PartName.connectorName" reference and return the resolved connector. Throws descriptive errors if the part or connector doesn't exist.
202
+
203
+ ```ts
204
+ getPort(ref: string): { partName: string; portName: string; port: PortDef; }
205
+ ```
206
+
207
+ #### `addFrame()` — Add a virtual reference frame (no geometry) to the assembly graph.
208
+
209
+ 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.
210
+
211
+ ```ts
212
+ addFrame(name: string, options?: PartOptions): Assembly
213
+ ```
214
+
215
+ #### `addPart()` — Add a named part to the assembly.
216
+
217
+ Connectors declared on the part (via `withConnectors()`) are captured automatically. Parts are positioned at world origin by default unless a `transform` is provided in `options`. For root parts (no incoming joint), `transform` is their final world position.
218
+
219
+ When a part is a [`ShapeGroup`](/docs/core#shapegroup), name the group children explicitly to get readable viewport labels (e.g. `"Base Assembly.Body"` instead of `"Base Assembly.1"`):
220
+
221
+ ```ts
222
+ const housing = group(
223
+ { name: "Body", shape: body },
224
+ { name: "Lid", shape: lid },
225
+ );
226
+ assembly.addPart("Base Assembly", housing);
227
+ ```
228
+
229
+ ```ts
230
+ addPart(name: string, part: AssemblyPart, options?: PartOptions): Assembly
231
+ ```
232
+
233
+ #### `addJoint()` — Add a kinematic joint between a parent and child part.
234
+
235
+ `frame` is a transform from the **parent part frame** to the **joint frame at zero state**. The child's world position is computed as:
236
+
237
+ ```
238
+ childWorld = parentWorld × frame × motion(value) × childBase
239
+ ```
240
+
241
+ For revolute joints `value` is in degrees; for prismatic joints `value` is in mm. Coupled joints (see `addJointCoupling`) ignore the `state` value passed to `solve()` and compute their value from source joints.
242
+
243
+ ```ts
244
+ addJoint(name: string, type: JointType, parent: string, child: string, options?: JointOptions): Assembly
245
+ ```
246
+
247
+ #### `addRevolute()` — Shorthand for `addJoint(name, 'revolute', parent, child, options)`.
248
+
249
+ ```ts
250
+ addRevolute(name: string, parent: string, child: string, options?: JointOptions): Assembly
251
+ ```
252
+
253
+ #### `addPrismatic()` — Shorthand for `addJoint(name, 'prismatic', parent, child, options)`.
254
+
255
+ ```ts
256
+ addPrismatic(name: string, parent: string, child: string, options?: JointOptions): Assembly
257
+ ```
258
+
259
+ #### `addFixed()` — Shorthand for `addJoint(name, 'fixed', parent, child, options)`.
260
+
261
+ Fixed joints rigidly attach a child part to its parent at `frame` with no motion. Before calling `mergeInto()`, use `addFixed()` to collapse multiple root parts into a single root.
262
+
263
+ ```ts
264
+ addFixed(name: string, parent: string, child: string, options?: JointOptions): Assembly
265
+ ```
266
+
267
+ #### `connect()` — Connect two parts by aligning their declared connectors, automatically computing frame and axis.
268
+
269
+ Connector references use `"PartName.connectorName"` format. The system aligns connector origins (child connector lands exactly on parent connector) and derives the joint frame and axis from the connector geometry — no manual `frame` or `axis` math needed.
270
+
271
+ The joint type is inferred from the connector's `kind` field if not specified in `options`. Use `flip: true` for mirrored parts whose connector axis is reflected.
272
+
273
+ When connectors are defined with `start`/`end`, you can control which point on each connector meets via `align` / `parentAlign` / `childAlign` (`'start'`, `'middle'`, `'end'`).
274
+
275
+ Use `connect()` when connector origins must physically coincide (flange-to-flange, bolt-into-bore). For mechanisms where parts share an axis but are deliberately spaced apart, use `addRevolute()` with pre-positioned parts instead.
276
+
277
+ ```ts
278
+ const mech = assembly("Arm")
279
+ .addPart("Base", base)
280
+ .addPart("Link", link)
281
+ .connect("Base.top", "Link.shoulder", {
282
+ as: "J1",
283
+ min: -90, max: 90, default: 0,
284
+ });
285
+
286
+ return mech.solve({ J1: 45 }).toGroup();
287
+ ```
288
+
289
+ ```ts
290
+ connect(parentPortRef: string, childPortRef: string, options?: ConnectOptions): Assembly
291
+ ```
292
+
293
+ #### `match()` — Auto-create a joint by matching typed connectors between two parts.
294
+
295
+ Connectors are ports with a `connectorType` string and a `gender` (`'male'`, `'female'`, or `'neutral'`). `match()` validates type and gender compatibility (use `{ force: true }` to skip validation) and creates the joint automatically from the connector's `kind` metadata.
296
+
297
+ The `pairs` map is `{ childConnector: parentConnector }`. The first pair drives joint creation; additional pairs are validated but do not create additional joints (they constrain the same rigid connection).
298
+
299
+ Define connectors on shapes with `shape.withConnectors(...)`:
300
+
301
+ ```ts
302
+ const door = doorShape.withConnectors({
303
+ hinge_top: connector.male("hinge", { origin: [0, 0, 90], axis: [0, 0, 1] }),
304
+ hinge_bottom: connector.male("hinge", { origin: [0, 0, 10], axis: [0, 0, 1] }),
305
+ });
306
+ ```
307
+
308
+ Then match in the assembly:
309
+
310
+ ```ts
311
+ const mech = assembly("Door")
312
+ .addPart("Frame", frame)
313
+ .addPart("Door", door)
314
+ .match("Door", "Frame", { hinge_top: "hinge_top", hinge_bottom: "hinge_bottom" });
315
+ // Revolute connectors → auto-creates revolute joint. No manual addRevolute needed.
316
+ ```
317
+
318
+ ```ts
319
+ match(childPartName: string, parentPartName: string, pairs: Record<string, string>, options?: MatchToOptions & { as?: string; }): Assembly
320
+ ```
321
+
322
+ #### `addJointCoupling()` — Link a joint's value to a linear combination of other joint values.
323
+
324
+ The driven joint's value is computed as:
325
+
326
+ ```
327
+ driven = offset + Σ(ratio_i × source_i)
328
+ ```
329
+
330
+ Coupled joints ignore any value passed in `solve(state)` — a warning is emitted if you try to override one. Coupling cycles are rejected. You cannot sweep a coupled joint directly; sweep one of its source joints instead.
331
+
332
+ ```ts
333
+ assembly
334
+ .addRevolute("Steering", "Base", "Turret", { axis: [0, 0, 1] })
335
+ .addRevolute("WheelDrive", "Turret", "Wheel", { axis: [1, 0, 0] })
336
+ .addRevolute("TopGear", "Base", "TopInput", { axis: [0, 0, 1] })
337
+ .addJointCoupling("TopGear", {
338
+ terms: [
339
+ { joint: "Steering", ratio: 1 },
340
+ { joint: "WheelDrive", ratio: 20 / 14 },
341
+ ],
342
+ });
343
+ ```
344
+
345
+ ```ts
346
+ addJointCoupling(jointName: string, options: JointCouplingOptions): Assembly
347
+ ```
348
+
349
+ #### `addGearCoupling()` — Link two revolute joints via a gear ratio.
350
+
351
+ Choose exactly one ratio source:
352
+
353
+ - `ratio` — explicit numeric ratio (driven/driver, negative for external mesh)
354
+ - `pair` — a `GearRatioLike` from `lib.gearPair`, `lib.bevelGearPair`, etc. (uses `pair.jointRatio`)
355
+ - `driverTeeth` + `drivenTeeth` — auto-computes ratio; use `mesh` to control sign (`'external'` = negative/opposite rotation, `'internal'` = positive, `'bevel'`/`'face'` = negative)
356
+
357
+ When `pair` carries a `phaseDeg`, it is auto-applied as the coupling `offset` to align teeth correctly. Override with `offset: 0` if gear shapes already have the phase baked in.
358
+
359
+ ```ts
360
+ const pair = lib.gearPair({ pinion: { module: 1.25, teeth: 14 }, gear: { module: 1.25, teeth: 42 } });
361
+ assembly
362
+ .addRevolute("Pinion", "Base", "PinionPart", { axis: [0, 0, 1] })
363
+ .addRevolute("Driven", "Base", "GearPart", { axis: [0, 0, 1] })
364
+ .addGearCoupling("Driven", "Pinion", { pair });
365
+ ```
366
+
367
+ ```ts
368
+ addGearCoupling(drivenJointName: string, driverJointName: string, options?: GearCouplingOptions): Assembly
369
+ ```
370
+
371
+ #### `sweepJoint()` — Sample a joint through its motion range, collecting collision data at each step.
372
+
373
+ Divides `[from, to]` into `steps` intervals (producing `steps + 1` frames). At each sample, the assembly is solved with the sweeping joint at that value and `baseState` for all others. Returns one `JointSweepFrame` per sample with the joint value, collision findings, and any solve warnings.
374
+
375
+ You cannot sweep a coupled joint — sweep one of its source joints instead.
376
+
377
+ ```ts
378
+ const sweep = mech.sweepJoint("elbow", -10, 135, 12, { shoulder: 35 });
379
+ const hits = sweep.filter(frame => frame.collisions.length > 0);
380
+ console.log(`Collisions at ${hits.length} of ${sweep.length} poses`);
381
+ ```
382
+
383
+ ```ts
384
+ sweepJoint(jointName: string, from: number, to: number, steps: number, baseState?: JointState, collisionOptions?: CollisionOptions): JointSweepFrame[]
385
+ ```
386
+
387
+ #### `toJointsView()` — Derive viewport joint controls from the assembly graph and register them.
388
+
389
+ Solves the assembly at rest (all joints = default), then converts each joint into a `JointViewInput` with world-space pivot and axis. Fixed joints become hidden zero-range revolute entries so attached parts follow their parent during animation. Joint couplings are forwarded to the viewport automatically.
390
+
391
+ **Critical pitfall:** Always call `toJointsView()` before solving for display. Then solve at the **rest pose** (no state overrides) and return that result. Do not solve at a non-zero angle when using `toJointsView()` — the viewport will apply the same rotation again, double-rotating the part.
392
+
393
+ ```ts
394
+ mech.toJointsView({
395
+ defaults: { J1: 30 },
396
+ animations: [{
397
+ name: "Swing", duration: 2, loop: true,
398
+ keyframes: [{ values: { J1: -45 } }, { values: { J1: 45 } }, { values: { J1: -45 } }],
399
+ }],
400
+ });
401
+
402
+ // Solve at REST — viewport handles posing
403
+ return mech.solve().toGroup();
404
+ ```
405
+
406
+ ```ts
407
+ toJointsView(options?: ToJointsViewOptions): void
408
+ ```
409
+
410
+ #### `describe()` — Return the serializable assembly definition used by solve/inspect pipelines.
411
+
412
+ ```ts
413
+ describe(): AssemblyDefinition
414
+ ```
125
415
 
126
416
  ### `ImportedAssembly`
127
417
 
128
- Wraps an imported Assembly, giving access to named parts and group conversion without losing the kinematic structure. Supports placement references (`.withReferences()` / `.placeReference()`) so sub-assemblies can be positioned the same way as imported parts and groups.
418
+ A wrapper around an imported `Assembly` that provides kinematic access and convenient transform helpers.
129
419
 
130
- **Methods:**
420
+ When a `.forge.js` file returns an unsolved `Assembly`, [`require()`](/docs/core#require) wraps it in an `ImportedAssembly`. This preserves the kinematic structure — you can call `solve()`, `sweepJoint()`, and `mergeInto()` — while also allowing convenience transforms that auto-solve at default values.
421
+
422
+ **Kinematic access**
423
+
424
+ ```ts
425
+ const arm = require("./arm.forge.js");
426
+
427
+ const solved = arm.solve({ shoulder: 45 }); // full kinematic solve
428
+ const link = arm.part("Link", { shoulder: 60 }); // single part at state
429
+ const group = arm.toGroup({ shoulder: 45 }); // ShapeGroup with named children
430
+ ```
431
+
432
+ **Convenience transforms** (auto-solve at defaults, return [`ShapeGroup`](/docs/core#shapegroup)):
433
+
434
+ ```ts
435
+ const positioned = arm.rotateZ(-90).translate(0, -20, 50);
436
+ ```
437
+
438
+ **Merging into a parent**
439
+
440
+ ```ts
441
+ require("./arm.forge.js").mergeInto(robot, {
442
+ prefix: "Left Arm",
443
+ mountParent: "Chassis",
444
+ mountJoint: "leftMount",
445
+ mountOptions: { frame: Transform.identity().translate(-70, 0, 10) },
446
+ });
447
+ ```
448
+
449
+ #### `assembly()` — The underlying Assembly — use for sweepJoint, addPart into parent, etc.
450
+
451
+ ```ts
452
+ get assembly(): Assembly
453
+ ```
454
+
455
+ #### `solve()` — Solve the assembly at the given joint state (defaults to each joint's default value).
131
456
 
132
- - `get assembly()` — The underlying Assembly — use for sweepJoint, addPart into parent, etc.
133
- - `solve()` — Solve the assembly at the given joint state (defaults to each joint's default value).
134
- - `part()` — Return a specific named part positioned at the given joint state, with any stored placement offset applied.
135
- - `toGroup()` — Convert all assembly parts to a ShapeGroup with named children. Child names match the part names used in the assembly. Any stored placement offset and placement references are forwarded to the group.
136
- - `withReferences()` — Attach named placement reference points to this assembly. Points are simple 3D coordinates (relative to the assembly's own origin). Returns a new ImportedAssembly does not mutate.
137
- - `referenceNames()` — List all attached placement reference names.
138
- - `placeReference()` — Translate the assembly so the named reference point lands on `target`. Returns a new ImportedAssembly — does not mutate. All point refs are translated by the same delta.
139
- - `translate()` Solve at defaults and return a translated ShapeGroup.
140
- - `rotate()` — Solve at defaults and return a rotated ShapeGroup (Euler XYZ degrees).
141
- - `scale()` — Solve at defaults and return a scaled ShapeGroup.
142
- - `mirror()` — Solve at defaults and return a mirrored ShapeGroup.
143
- - `color()` — Solve at defaults and return a colored ShapeGroup.
144
- - `child()` — Solve at defaults, get a named child part from the resulting group.
145
- - `mergeInto()` — Flatten this sub-assembly's parts and joints into `parent`, then wire a mount joint connecting `mountParent` (a part already in `parent`) to the sub-assembly root. All part names and joint names from the sub-assembly are prefixed with `"${options.prefix}."` to avoid collisions. After the merge you can drive sub-assembly joints from the parent: `parent.solve({ "Left Arm.shoulder": 45 })`. Throws if the sub-assembly has multiple root parts (connect them with addFixed first). Returns `parent` for chaining.
457
+ ```ts
458
+ solve(state?: JointState): SolvedAssembly
459
+ ```
460
+
461
+ #### `part()` — Return a specific named part positioned at the given joint state, with any stored placement offset applied.
462
+
463
+ ```ts
464
+ part(name: string, state?: JointState): AssemblyPart
465
+ ```
466
+
467
+ #### `toGroup()` — Convert all assembly parts to a ShapeGroup with named children. Child names match the part names used in the assembly. Any stored placement offset and placement references are forwarded to the group.
468
+
469
+ ```ts
470
+ toGroup(state?: JointState): ShapeGroup
471
+ ```
472
+
473
+ #### `withReferences()` — Attach named placement reference points to this assembly. Points are simple 3D coordinates (relative to the assembly's own origin). Returns a new ImportedAssembly — does not mutate.
474
+
475
+ ```ts
476
+ withReferences(refs: Pick<PlacementReferenceInput, "points">): ImportedAssembly
477
+ ```
478
+
479
+ #### `referenceNames()` — List all attached placement reference names.
480
+
481
+ ```ts
482
+ referenceNames(kind?: PlacementReferenceKind): string[]
483
+ ```
484
+
485
+ #### `placeReference()` — Translate the assembly so the named reference point lands on `target`. Returns a new ImportedAssembly — does not mutate. All point refs are translated by the same delta.
486
+
487
+ ```ts
488
+ placeReference(ref: string, target: [ number, number, number ], offset?: [ number, number, number ]): ImportedAssembly
489
+ ```
490
+
491
+ #### `translate()` — Solve at defaults and return a translated ShapeGroup.
492
+
493
+ ```ts
494
+ translate(x: number, y: number, z: number): ShapeGroup
495
+ ```
496
+
497
+ #### `rotate()` — Solve at defaults and return a rotated ShapeGroup.
498
+
499
+ ```ts
500
+ rotate(axis: [ number, number, number ], angleDeg: number, options?: { pivot?: [ number, number, number ]; }): ShapeGroup
501
+ ```
502
+
503
+ #### `rotateX()` — Solve at defaults and return a ShapeGroup rotated around X.
504
+
505
+ ```ts
506
+ rotateX(angleDeg: number, options?: { pivot?: [ number, number, number ]; }): ShapeGroup
507
+ ```
508
+
509
+ #### `rotateY()` — Solve at defaults and return a ShapeGroup rotated around Y.
510
+
511
+ ```ts
512
+ rotateY(angleDeg: number, options?: { pivot?: [ number, number, number ]; }): ShapeGroup
513
+ ```
514
+
515
+ #### `rotateZ()` — Solve at defaults and return a ShapeGroup rotated around Z.
516
+
517
+ ```ts
518
+ rotateZ(angleDeg: number, options?: { pivot?: [ number, number, number ]; }): ShapeGroup
519
+ ```
520
+
521
+ #### `scale()` — Solve at defaults and return a scaled ShapeGroup.
522
+
523
+ ```ts
524
+ scale(v: number | [ number, number, number ]): ShapeGroup
525
+ ```
526
+
527
+ #### `mirror()` — Solve at defaults and return a mirrored ShapeGroup.
528
+
529
+ ```ts
530
+ mirror(normal: [ number, number, number ]): ShapeGroup
531
+ ```
532
+
533
+ #### `color()` — Solve at defaults and return a colored ShapeGroup.
534
+
535
+ ```ts
536
+ color(hex: string): ShapeGroup
537
+ ```
538
+
539
+ #### `child()` — Solve at defaults, get a named child part from the resulting group.
540
+
541
+ ```ts
542
+ child(name: string): Shape | Sketch | ShapeGroup
543
+ ```
544
+
545
+ #### `mergeInto()` — Flatten this sub-assembly's parts and joints into `parent` and wire a mount joint.
546
+
547
+ All part and joint names from the sub-assembly are prefixed with `"${options.prefix}."` to avoid collisions. After the merge, sub-assembly joints are driven from the parent using the prefixed names:
548
+
549
+ ```ts
550
+ parent.solve({ "Left Arm.shoulder": 45, "Right Arm.shoulder": -20 })
551
+ ```
552
+
553
+ Joint couplings inside the sub-assembly are preserved and rewritten with the prefix. Ports from sub-assembly parts are forwarded with the prefix.
554
+
555
+ The sub-assembly must have exactly one root part. If it has multiple roots, use `addFixed()` first to consolidate them before merging.
556
+
557
+ ```ts
558
+ const robot = assembly("Robot").addPart("Chassis", chassis);
559
+
560
+ require("./arm.forge.js").mergeInto(robot, {
561
+ prefix: "Left Arm",
562
+ mountParent: "Chassis",
563
+ mountJoint: "leftMount",
564
+ mountOptions: { frame: Transform.identity().translate(-70, 0, 10) },
565
+ });
566
+ ```
567
+
568
+ ```ts
569
+ mergeInto(parent: Assembly, options: MergeIntoOptions): Assembly
570
+ ```
146
571
 
147
572
  ### `SolvedAssembly`
148
573
 
574
+ The result of solving an assembly at a specific joint state.
575
+
576
+ `SolvedAssembly` holds world-space transforms for every part at a given pose. Use `toGroup()` to convert all parts into a renderable [`ShapeGroup`](/docs/core#shapegroup), or use `getPart()` / `getTransform()` to inspect individual parts programmatically.
577
+
578
+ **Validation**
579
+
580
+ Call `collisionReport()` to detect overlapping parts, or `sweepJoint()` on the parent `Assembly` to check for interference across the joint's motion range.
581
+
582
+ ```ts
583
+ const solved = mech.solve({ shoulder: 45, elbow: -20 });
584
+ console.log("Collisions", solved.collisionReport());
585
+ return solved.toGroup();
586
+ ```
587
+
149
588
  **Properties:**
150
589
 
151
590
  | Property | Type | Description |
@@ -154,20 +593,106 @@ Wraps an imported Assembly, giving access to named parts and group conversion wi
154
593
 
155
594
  **Methods:**
156
595
 
157
- - `warnings()` — warnings(): string[]
158
- - `getJointState()` — getJointState(): JointState
159
- - `get mateExplodeHints()` — Explode direction hints derived from mate constraints, or null if no mates.
160
- - `get mateDof()` — Remaining degrees of freedom after mate constraints, or null if no mates.
161
- - `get mateConverged()` — Whether the mate constraint solver converged, or null if no mates.
162
- - `getTransform()` — getTransform(partName: string): Transform
163
- - `getPart()` — getPart(partName: string): AssemblyPart
164
- - `toGroup()` — Convert all solved parts to a ShapeGroup with named children. Each part becomes a child, positioned at its solved transform. This is the primary way to get a group for rendering, `show()`, or embedding.
165
- - `toSceneObjects()` — Return an array of named scene objects for the viewport renderer. Each part becomes `{ name, shape }` or `{ name, group: [...] }` if the part is a ShapeGroup. Prefer `toGroup()` for most uses; this method exists for advanced scene-graph control.
166
- - `toScene()` — toScene(): Array<{ name: string; shape?: Shape; group?: Array<{ name: string; sh
167
- - `bom()` — bom(): BomRow[]
168
- - `bomCsv()` — bomCsv(): string
169
- - `collisionReport()` — collisionReport(options?: CollisionOptions): CollisionFinding[]
170
- - `minClearance()` — minClearance(partA: string, partB: string, searchLength?: number): number
596
+ #### `warnings()` — Return any warnings generated during solve (clamped joints, unconverged mates, etc.).
597
+
598
+ ```ts
599
+ warnings(): string[]
600
+ ```
601
+
602
+ #### `getJointState()` — Return a snapshot of resolved joint values (after clamping and coupling).
603
+
604
+ ```ts
605
+ getJointState(): JointState
606
+ ```
607
+
608
+ #### `mateExplodeHints()` — Explode direction hints derived from mate constraints, or null if no mates.
609
+
610
+ ```ts
611
+ get mateExplodeHints(): Record<string, { direction: Vec3; }> | null
612
+ ```
613
+
614
+ #### `mateDof()` — Remaining degrees of freedom after mate constraints, or null if no mates.
615
+
616
+ ```ts
617
+ get mateDof(): number | null
618
+ ```
619
+
620
+ #### `mateConverged()` — Whether the mate constraint solver converged, or null if no mates.
621
+
622
+ ```ts
623
+ get mateConverged(): boolean | null
624
+ ```
625
+
626
+ #### `getTransform()` — Return the world-space [`Transform`](/docs/core#transform) for the named part at the solved pose.
627
+
628
+ ```ts
629
+ getTransform(partName: string): Transform
630
+ ```
631
+
632
+ #### `getPart()` — Return the named part already positioned at its solved world transform.
633
+
634
+ ```ts
635
+ getPart(partName: string): AssemblyPart
636
+ ```
637
+
638
+ #### `toGroup()` — Convert all solved parts into a [`ShapeGroup`](/docs/core#shapegroup) with named children.
639
+
640
+ Each part becomes a named child in the group, already positioned at its solved world transform. This is the primary method for rendering, passing to `show()`, or embedding the assembly into a parent model.
641
+
642
+ ```ts
643
+ return mech.solve({ shoulder: 60 }).toGroup();
644
+ ```
645
+
646
+ ```ts
647
+ toGroup(): ShapeGroup
648
+ ```
649
+
650
+ #### `toSceneObjects()` — Return an array of named scene objects for the viewport renderer.
651
+
652
+ Each part becomes `{ name, shape }` or `{ name, group: [...] }` if the part is a [`ShapeGroup`](/docs/core#shapegroup). Prefer `toGroup()` for most use cases. This method exists for advanced scene-graph control where you need access to the flat per-part array with metadata.
653
+
654
+ ```ts
655
+ toSceneObjects(): Array<{ name: string; shape?: Shape; group?: Array<{ name: string; shape: Shape; }>; metadata?: PartMetadata; }>
656
+ ```
657
+
658
+ #### `toScene()` — Backward-compatible alias for `toSceneObjects()`.
659
+
660
+ ```ts
661
+ toScene(): Array<{ name: string; shape?: Shape; group?: Array<{ name: string; shape: Shape; }>; metadata?: PartMetadata; }>
662
+ ```
663
+
664
+ #### [`bom()`](/docs/output#bom) — Generate a bill of materials for all parts in the solved assembly.
665
+
666
+ ```ts
667
+ bom(): BomRow[]
668
+ ```
669
+
670
+ #### `bomCsv()` — Generate a bill of materials as a CSV string.
671
+
672
+ ```ts
673
+ bomCsv(): string
674
+ ```
675
+
676
+ #### `collisionReport()` — Detect overlapping (colliding) part pairs in this solved pose.
677
+
678
+ Computes boolean intersections between all part pairs and returns findings where the overlap volume exceeds `minOverlapVolume` (default 0.1 mm³).
679
+
680
+ ```ts
681
+ const solved = mech.solve({ shoulder: 35, elbow: 60 });
682
+ console.log("Collisions", solved.collisionReport());
683
+ ```
684
+
685
+ ```ts
686
+ collisionReport(options?: CollisionOptions): CollisionFinding[]
687
+ ```
688
+
689
+ #### `minClearance()` — Compute the minimum gap (clearance) between two parts in this solved pose.
690
+
691
+ Returns `0` if the parts are touching or overlapping. Requires the Manifold backend. `searchLength` bounds the search radius in mm — increase it for widely separated parts.
692
+
693
+ ```ts
694
+ minClearance(partA: string, partB: string, searchLength?: number): number
695
+ ```
171
696
 
172
697
  ### `MateBuilder`
173
698
 
@@ -179,14 +704,68 @@ Wraps an imported Assembly, giving access to named parts and group conversion wi
179
704
 
180
705
  **Methods:**
181
706
 
182
- - `flush()` — flush(faceA: string, faceB: string): string
183
- - `align()` — align(faceA: string, faceB: string): string
184
- - `parallel()` — parallel(faceA: string, faceB: string): string
185
- - `faceDistance()` — faceDistance(faceA: string, faceB: string, distance: number): string
186
- - `concentric()` — concentric(axisA: string, axisB: string): string
187
- - `axisParallel()` — axisParallel(axisA: string, axisB: string): string
188
- - `pointCoincident()` — pointCoincident(pointA: string, pointB: string): string
189
- - `pointOnFace()` — pointOnFace(point: string, face: string): string
190
- - `pointOnAxis()` — pointOnAxis(point: string, axis: string): string
191
- - `angle()` — angle(faceA: string, faceB: string, degrees: number): string
192
- - `get totalEquations()` — Total constraint equations.
707
+ #### `flush()` — Constrain two faces so they stay flush.
708
+
709
+ ```ts
710
+ flush(faceA: string, faceB: string): string
711
+ ```
712
+
713
+ #### `align()` — Constrain two faces so their normals align.
714
+
715
+ ```ts
716
+ align(faceA: string, faceB: string): string
717
+ ```
718
+
719
+ #### `parallel()` — Constrain two faces so they remain parallel.
720
+
721
+ ```ts
722
+ parallel(faceA: string, faceB: string): string
723
+ ```
724
+
725
+ #### `faceDistance()` — Constrain the distance between two faces.
726
+
727
+ ```ts
728
+ faceDistance(faceA: string, faceB: string, distance: number): string
729
+ ```
730
+
731
+ #### `concentric()` — Constrain two axes to share the same center line.
732
+
733
+ ```ts
734
+ concentric(axisA: string, axisB: string): string
735
+ ```
736
+
737
+ #### `axisParallel()` — Constrain two axes to remain parallel.
738
+
739
+ ```ts
740
+ axisParallel(axisA: string, axisB: string): string
741
+ ```
742
+
743
+ #### `pointCoincident()` — Constrain two points to coincide.
744
+
745
+ ```ts
746
+ pointCoincident(pointA: string, pointB: string): string
747
+ ```
748
+
749
+ #### `pointOnFace()` — Constrain a point to lie on a face.
750
+
751
+ ```ts
752
+ pointOnFace(point: string, face: string): string
753
+ ```
754
+
755
+ #### `pointOnAxis()` — Constrain a point to lie on an axis.
756
+
757
+ ```ts
758
+ pointOnAxis(point: string, axis: string): string
759
+ ```
760
+
761
+ #### `angle()` — Constrain the angle between two faces.
762
+
763
+ ```ts
764
+ angle(faceA: string, faceB: string, degrees: number): string
765
+ ```
766
+
767
+ #### `totalEquations()` — Total constraint equations.
768
+
769
+ ```ts
770
+ get totalEquations(): number
771
+ ```