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
@@ -1,37 +0,0 @@
1
- # ForgeCAD API Documentation
2
-
3
- This directory contains **hand-written API guides** for model authors. For auto-generated API docs extracted from source, see the [`generated/`](../generated/) directory (one file per module).
4
-
5
- ## Directory Layout
6
-
7
- | Directory | Audience | What's in it |
8
- |-----------|----------|-------------|
9
- | `core/` | Model authors | Core reference: primitives, transforms, booleans, imports, parameters, topology, specs |
10
- | `sketch/` | Model authors (sketch-heavy tasks) | 2D sketch construction, transforms, booleans, on-face, extrude |
11
- | `assembly/` | Model authors (mechanisms) | Assembly graph, joints, couplings, kinematics |
12
- | `sheet-metal/` | Model authors (folded parts) | Sheet metal, flanges, flat patterns |
13
- | `runtime/` | Model authors (viewport) | Cut planes, exploded views, joint controls |
14
- | `output/` | Model authors (export/reports) | BOM, dimensions, G-code toolpaths, mesh export, STEP/BREP export |
15
- | `toolbox/` | Model authors (library) | Fasteners, hardware helpers |
16
-
17
- ## Read Plan
18
-
19
- > Load only what the current task needs. Start from the top, add docs as needed.
20
-
21
- 1. **Always start with** `core/reference.md` — the core script contract
22
- 2. **For geometry orientation**: `../guides/coordinate-system.md`, `../guides/geometry-conventions.md`, `../guides/positioning.md`
23
- 3. **For sketch-heavy work**: `sketch/*.md` (9 files covering 2D APIs)
24
- 4. **For topology/constraints**: `core/topology.md`
25
- 5. **For assemblies**: `assembly/assembly.md`
26
- 6. **For sheet metal**: `sheet-metal/sheet-metal.md`
27
- 7. **For viewport controls**: `runtime/viewport.md`
28
- 8. **For recipes/debugging**: `../guides/modeling-recipes.md`
29
- 9. **For CLI/export**: `../cli.md`, `output/*.md`
30
-
31
- ## Adjacent Documentation
32
-
33
- - **[`../generated/api-reference.md`](../generated/api-reference.md)** — Auto-generated complete API index (run `npm run gen:docs` to refresh)
34
- - **[`../guides/`](../guides/)** — Conceptual orientation: coordinate system, conventions, recipes
35
- - **[`../internals/`](../internals/)** — Engine internals for ForgeCAD contributors
36
- - **[`../project/`](../project/)** — Development workflow, coding standards, deployment
37
- - **[`../cli.md`](../cli.md)** — CLI reference
@@ -1,617 +0,0 @@
1
- ---
2
- skill-group: assembly
3
- skill-order: 1
4
- ---
5
-
6
- # Assembly + Mechanism API
7
-
8
- Use this API when your model is a mechanism, not a single booleaned solid.
9
-
10
- ## Mental model
11
- - `Part` = manufacturable object (shape + metadata)
12
- - `Joint` = relationship between parent and child part
13
- - `State` = current joint values
14
- - `Solve` = compute world transforms for all parts
15
- - `Validate` = collisions / clearances / sweep checks
16
-
17
- ## Quick start
18
-
19
- ```javascript
20
- const mech = assembly("Arm")
21
- .addPart("base", box(80, 80, 20, true), {
22
- metadata: { material: "PETG", process: "FDM", qty: 1 },
23
- })
24
- .addPart("link", box(140, 24, 24).translate(0, -12, -12))
25
- .addJoint("shoulder", "revolute", "base", "link", {
26
- axis: [0, 1, 0],
27
- min: -30,
28
- max: 120,
29
- default: 25,
30
- frame: Transform.identity().translate(0, 0, 20),
31
- });
32
-
33
- return mech; // auto-solved at defaults, renders all parts
34
- ```
35
-
36
- Returning `mech` (unsolved Assembly) auto-solves at default joint values and renders all parts. You can also return a `SolvedAssembly` for a specific pose:
37
-
38
- ```javascript
39
- return mech.solve({ shoulder: 60 });
40
- ```
41
-
42
- ## Return types and imports — how they fit together
43
-
44
- | Return value | Standalone | `require()` result type |
45
- |---|---|---|
46
- | `Shape` | yes | `Shape` |
47
- | `Sketch` | yes | `Sketch` |
48
- | `ShapeGroup` | yes | `ShapeGroup` |
49
- | `Assembly` (unsolved) | **yes** | `ImportedAssembly` |
50
- | `SolvedAssembly` | **yes** | `SolvedAssembly` |
51
-
52
- Use `require("./file.forge.js")` to import any `.forge.js` file. Pass param overrides as the second argument: `require("./file.forge.js", { Speed: 2 })`.
53
-
54
- **`Assembly` is the dual-use type**: a file that returns an unsolved `Assembly` works both as a standalone renderable script *and* as an import target for `require()` (which returns an `ImportedAssembly`).
55
-
56
- Pattern for dual-use assembly files:
57
-
58
- ```javascript
59
- // handle.forge.js — works standalone AND importable via require()
60
- const mech = assembly("Handle")
61
- .addPart("Base", baseBracket)
62
- .addPart("Arm", arm)
63
- .addRevolute("Fold", "Base", "Arm", { axis: [0, 1, 0], min: 0, max: 90 });
64
-
65
- // Animation setup — runs when standalone, ignored on import
66
- mech.toJointsView({
67
- animations: [{ name: "Fold", duration: 2, loop: true,
68
- keyframes: [{ values: { Fold: 0 } }, { values: { Fold: 90 } }, { values: { Fold: 0 } }],
69
- }],
70
- });
71
-
72
- return mech; // works standalone (auto-solved + animated) AND with require()
73
- ```
74
-
75
- ```javascript
76
- // case.forge.js — imports the handle as a positioned assembly
77
- const handle = require("./handle.forge.js");
78
-
79
- // Convenience transforms: solve at defaults, return ShapeGroup
80
- const handleGroup = handle.rotate(0, 0, -90).translate(0, -20, 50);
81
-
82
- return [
83
- { name: "Case", shape: caseBody },
84
- { name: "Handle", shape: handleGroup },
85
- ];
86
- ```
87
-
88
- ## Connector-based assembly
89
-
90
- **Connectors** are typed, gendered ports. They carry semantic metadata (type like `"dovetail"`, gender `male`/`female`/`neutral`, and optional measurements) that enables automatic positioning, type/gender validation, and connector-aware explode views.
91
-
92
- ### Defining connectors
93
-
94
- ```javascript
95
- const shelf = box(200, 120, 10, true).withConnectors({
96
- left_tab: connector.male("dovetail", { origin: [-100, 0, 0], axis: [-1, 0, 0] }),
97
- right_tab: connector.male("dovetail", { origin: [100, 0, 0], axis: [1, 0, 0] }),
98
- });
99
- ```
100
-
101
- Connector factories: `connector.male(type, portInput)`, `connector.female(type, portInput)`, `connector.neutral(type, portInput)`.
102
-
103
- Connectors are stored as ports with extra metadata — they work everywhere ports work (`connect()`, `assembly.match()`, transforms, imports).
104
-
105
- ### `matchTo()` — Static connector matching (no assembly needed)
106
-
107
- Position a part by snapping its connector to a target's connector. Full 6-DOF alignment: origins coincide, axes anti-parallel.
108
-
109
- ```javascript
110
- // Single pair
111
- const placed = shelf.matchTo(panel, "left_tab", "shelf_slot_0");
112
-
113
- // Dictionary (multiple pairs, same target)
114
- const placed = shelf.matchTo(panel, { left_tab: "shelf_slot_0", right_tab: "shelf_slot_1" });
115
- ```
116
-
117
- Works on `Shape` and `ShapeGroup`. Matched parts auto-get explode hints — the viewport explode slider separates them along connector axes.
118
-
119
- ### `assembly.match()` — Auto-create joints from connectors
120
-
121
- ```javascript
122
- const mech = assembly("Door")
123
- .addPart("Frame", frame)
124
- .addPart("Door", door)
125
- .match("Door", "Frame", { hinge_top: "hinge_top" });
126
- // Revolute connectors → auto-creates revolute joint. No manual addRevolute needed.
127
- ```
128
-
129
- ### Connector queries
130
-
131
- ```javascript
132
- shape.connectorNames() // all connector names
133
- shape.connectorsByType("dovetail") // filter by type
134
- shape.connectorDistance("slot_1", "slot_2") // 3D distance between origins
135
- shape.connectorMeasurements("mount") // { bolt_circle: 31, ... }
136
- ```
137
-
138
- ### Auto-bubbling through groups
139
-
140
- Named group children automatically expose their connectors via dotted paths:
141
-
142
- ```javascript
143
- const cabinet = group(
144
- { name: "Left", shape: leftPanel },
145
- { name: "Right", shape: rightPanel },
146
- );
147
- cabinet.connectorNames() // → ["Left.shelf_0", "Left.shelf_1", "Right.shelf_0", ...]
148
- shelf.matchTo(cabinet, "left_tab", "Left.shelf_0");
149
- ```
150
-
151
- ## Port-based assembly
152
-
153
- Declare **ports** on parts and let `connect()` align them automatically — no manual `frame` or `axis` math.
154
-
155
- ### When to use `match()` vs `connect()` vs `addRevolute()`
156
-
157
- | Pattern | Use | Example |
158
- |---------|-----|---------|
159
- | **Typed connectors with validation** | `match()` | Connector type + gender checking, auto joint creation |
160
- | **Ports physically meet** | `connect()` | Flange-to-flange, bolt-to-hole, motor shaft into gear bore |
161
- | **Shared axis, offset position** | `addRevolute()` | Hinge with arm between two ear brackets, scissor linkage |
162
-
163
- `connect()` aligns port origins — child port lands exactly on parent port. Use it when the attachment points should coincide. For mechanisms where parts share a rotation axis but are deliberately spaced apart along it (e.g. a folding hinge arm that sits between bracket ears), use `addRevolute()` with pre-positioned parts and let the transform cancellation handle placement.
164
-
165
- ### Declaring ports
166
-
167
- A port is an oriented attachment frame: origin + axis + up vector, in part-local coordinates.
168
-
169
- ```javascript
170
- const base = box(80, 80, 20, true).withPorts({
171
- top: port.revolute({
172
- origin: [0, 0, 10],
173
- axis: [0, 0, 1], // joint rotation axis
174
- up: [0, 1, 0], // zero-angle reference (optional, auto-computed if omitted)
175
- }),
176
- });
177
-
178
- const arm = box(120, 20, 20).translate(60, 0, 0).withPorts({
179
- shoulder: port.revolute({ origin: [0, 0, 0], axis: [0, 0, 1] }),
180
- elbow: port.revolute({ origin: [120, 0, 0], axis: [0, 0, 1] }),
181
- });
182
- ```
183
-
184
- Port factories: `port.revolute(...)`, `port.prismatic(...)`, `port.fixed(...)`, or generic `port(...)`.
185
-
186
- **Alternative: define by physical extent** — use `start`/`end` instead of `origin`/`axis`:
187
-
188
- ```javascript
189
- // Origin and axis auto-computed from start→end
190
- const arm = arm3D.withPorts({
191
- bore: port.revolute({
192
- start: [0, -armWidth/2, 0], // bore entrance
193
- end: [0, armWidth/2, 0], // bore exit
194
- }),
195
- });
196
- ```
197
-
198
- This also stores the extent length for alignment (see below).
199
-
200
- Ports survive transforms — if you `translate()` or `rotate()` a shape, its ports move with it.
201
-
202
- ### Connecting parts
203
-
204
- ```javascript
205
- const mech = assembly("Arm")
206
- .addPart("Base", base)
207
- .addPart("Link", arm)
208
- .connect("Base.top", "Link.shoulder", {
209
- as: "J1", // joint name (auto-generated if omitted)
210
- min: -90, max: 90, // optional limits
211
- default: 0, // optional default angle
212
- });
213
-
214
- const solved = mech.solve({ J1: 45 });
215
- return solved.toScene();
216
- ```
217
-
218
- `connect()` computes the joint `frame` and `axis` from port alignment, then delegates to `addJoint()` internally. The kinematic solver is unchanged.
219
-
220
- **ConnectOptions:** `as`, `type` (override port kind), `min`, `max`, `default`, `unit`, `flip` (oppose axes for mirrored parts), `align` / `parentAlign` / `childAlign` (start/middle/end), `effort`, `velocity`, `damping`, `friction`.
221
-
222
- **Alignment along port extent:** When ports are defined with `start`/`end`, you can control which points align:
223
-
224
- ```javascript
225
- .connect("Pin.shaft", "Arm.bore", { align: "middle" }) // default — midpoints meet
226
- .connect("Pin.shaft", "Arm.bore", { align: "start" }) // start points meet
227
- .connect("Pin.shaft", "Arm.bore", { align: "end" }) // end points meet
228
- .connect("Pin.shaft", "Arm.bore", {
229
- parentAlign: "start", childAlign: "middle", // independent control
230
- })
231
- ```
232
-
233
- **Mirrored parts:** When a part is created with `.mirror()`, its port axis flips direction. Use `flip: true` on the connect call so the axes oppose rather than align:
234
-
235
- ```javascript
236
- .connect("Right Base.hinge", "Right Arm.pivot", {
237
- as: "Fold Right", flip: true,
238
- })
239
- ```
240
-
241
- ### Derived jointsView
242
-
243
- Instead of manually restating the kinematic chain for `jointsView()`, derive it from the assembly graph:
244
-
245
- ```javascript
246
- mech.toJointsView({
247
- defaults: { J1: 30 },
248
- animations: [
249
- {
250
- name: "Swing",
251
- duration: 2,
252
- loop: true,
253
- keyframes: [
254
- { values: { J1: -45 } },
255
- { values: { J1: 45 } },
256
- { values: { J1: -45 } },
257
- ],
258
- },
259
- ],
260
- });
261
-
262
- // IMPORTANT: solve at REST when using toJointsView — the viewport handles posing.
263
- return mech.solve().toScene();
264
- ```
265
-
266
- `toJointsView()` solves the assembly at rest, computes world-space pivots and axes, and calls the existing `jointsView()` function. No manual pivot coordinates needed.
267
-
268
- **Fixed attachments are handled automatically.** Parts connected with `addFixed()` are emitted as zero-range revolute joints in the viewport chain, so they follow their parent during animation (e.g. a stick bolted to an arm will rotate with it).
269
-
270
- **Do not solve at a non-zero angle when using `toJointsView()`.** The viewport applies `toJointsView` transforms on top of the scene — solving at the same angle would double-rotate parts. Use `defaults` to set the initial pose instead:
271
-
272
- ```javascript
273
- // BAD — double rotation
274
- mech.toJointsView({ defaults: { J1: 45 } });
275
- return mech.solve({ J1: 45 }).toScene();
276
-
277
- // GOOD — solve at rest, viewport poses via defaults
278
- mech.toJointsView({ defaults: { J1: 45 } });
279
- return mech.solve().toScene();
280
- ```
281
-
282
- ### Port API on shapes
283
-
284
- - `shape.withPorts({ name: port.revolute({...}) })` — attach ports (returns new shape)
285
- - `shape.portNames()` — list port names
286
- - Works on `Shape`, `TrackedShape`, and `ShapeGroup`
287
-
288
- ### Port API on assemblies
289
-
290
- - `assembly.withPorts("PartName", { name: port.revolute({...}) })` — add ports to an existing part
291
- - `assembly.getPorts("PartName")` — get ports on a part
292
- - `assembly.getPort("Part.port")` — resolve a "Part.port" reference
293
- - Ports from imported parts are captured automatically in `addPart()`
294
- - Ports are forwarded through `mergeInto()` with the prefix
295
-
296
- ## Ergonomic helpers
297
- - `addFrame(name, { transform? })` adds a virtual reference frame (no geometry)
298
- - `addRevolute(name, parent, child, opts)` shorthand for `addJoint(..., "revolute", ...)`
299
- - `addPrismatic(name, parent, child, opts)` shorthand for `addJoint(..., "prismatic", ...)`
300
- - `addFixed(name, parent, child, opts)` shorthand for `addJoint(..., "fixed", ...)`
301
- - `addJointCoupling(jointName, { terms, offset? })` links joints with linear relationships
302
- - `addGearCoupling(drivenJoint, driverJoint, opts)` links revolute joints using gear ratios
303
-
304
- ## Joint couplings
305
-
306
- Use couplings when one joint should be derived from other joints.
307
-
308
- Formula:
309
- - `driven = offset + Σ(ratio_i * source_i)`
310
-
311
- Example:
312
-
313
- ```javascript
314
- const mech = assembly("Differential")
315
- .addFrame("Base")
316
- .addFrame("Turret")
317
- .addFrame("Wheel")
318
- .addFrame("TopInput")
319
- .addRevolute("Steering", "Base", "Turret", { axis: [0, 0, 1] })
320
- .addRevolute("WheelDrive", "Turret", "Wheel", { axis: [1, 0, 0] })
321
- .addRevolute("TopGear", "Base", "TopInput", { axis: [0, 0, 1] })
322
- .addJointCoupling("TopGear", {
323
- terms: [
324
- { joint: "Steering", ratio: 1 },
325
- { joint: "WheelDrive", ratio: 20 / 14 },
326
- ],
327
- });
328
- ```
329
-
330
- Notes:
331
- - Coupled joints ignore direct values in `solve(state)` and emit a warning.
332
- - Coupling cycles are rejected.
333
- - `sweepJoint(...)` cannot sweep a coupled target; sweep one of its source joints instead.
334
-
335
- ## Gear couplings
336
-
337
- Use this helper to connect two **revolute** joints as a gear mesh without manually writing `addJointCoupling(...)`.
338
-
339
- ```javascript
340
- const pair = lib.gearPair({
341
- pinion: { module: 1.25, teeth: 14, faceWidth: 8 },
342
- gear: { module: 1.25, teeth: 42, faceWidth: 8 },
343
- });
344
-
345
- const mech = assembly("Spur Stage")
346
- .addFrame("Base")
347
- .addFrame("PinionPart")
348
- .addFrame("GearPart")
349
- .addRevolute("Pinion", "Base", "PinionPart", { axis: [0, 0, 1] })
350
- .addRevolute("Driven", "Base", "GearPart", { axis: [0, 0, 1] })
351
- .addGearCoupling("Driven", "Pinion", { pair }); // uses pair.jointRatio
352
- ```
353
-
354
- `addGearCoupling(...)` ratio sources (choose exactly one):
355
- - `ratio` (explicit multiplier)
356
- - `pair` (`lib.gearPair(...)`, `lib.bevelGearPair(...)`, or `lib.faceGearPair(...)` result using `pair.jointRatio`)
357
- - `driverTeeth` + `drivenTeeth` (auto ratio; `internal` mesh is positive, `external`/`bevel`/`face` are negative)
358
-
359
- For bevel stages, pairing helpers also return placement aids:
360
- - `pinionAxis`, `gearAxis`
361
- - `pinionCenter`, `gearCenter`
362
-
363
- For face stages, use `centerDistance` and `meshPlaneZ` from `lib.faceGearPair(...)`; with `place: true`, the face gear stays on the Z axis and the vertical spur is placed at `[centerDistance, 0, meshPlaneZ]`.
364
-
365
- ## Joint frames
366
-
367
- `frame` is a transform from the **parent part frame** to the **joint frame at zero state**.
368
-
369
- For a child part:
370
-
371
- Matrix form:
372
- - `childWorld = parentWorld * frame * motion(value) * childBase`
373
-
374
- Forge chain form:
375
- - `childWorld = composeChain(childBase, motion(value), frame, parentWorld)`
376
-
377
- This keeps kinematic chains declarative and avoids repeated manual pivot math.
378
-
379
- ## SolvedAssembly
380
-
381
- `mech.solve(state?)` returns a `SolvedAssembly` with these methods:
382
-
383
- | Method | Returns | Use for |
384
- |--------|---------|---------|
385
- | `toGroup()` | `ShapeGroup` | Primary way to get positioned parts as a group — for `show()`, embedding, transforms |
386
- | `getPart(name)` | `AssemblyPart` | Extract a single part at its solved position |
387
- | `getTransform(name)` | `Transform` | Raw world transform for a part |
388
- | `bom()` / `bomCsv()` | `BomRow[]` / `string` | Bill of materials |
389
- | `collisionReport()` | `CollisionFinding[]` | Interference detection |
390
- | `minClearance(a, b)` | `number` | Minimum gap between two parts |
391
- | `toSceneObjects()` | `Array<{name, shape?, group?}>` | Advanced: raw scene-graph array for custom rendering |
392
-
393
- **`toGroup()`** is the preferred way to convert a solved assembly to a positionable group:
394
-
395
- ```javascript
396
- const solved = mech.solve({ shoulder: 45 });
397
- show(solved.toGroup()); // in notebooks
398
- ```
399
-
400
- ## Validation helpers
401
- - `solved.collisionReport()` returns overlapping part pairs and volume
402
- - `solved.minClearance("PartA", "PartB", 10)` computes minimum gap
403
- - `assembly.sweepJoint("elbow", -20, 140, 24)` samples motion and reports collisions
404
-
405
- Notebook-friendly pattern:
406
-
407
- ```javascript
408
- const solved = mech.solve({ shoulder: 35, elbow: 60 });
409
- console.log("Collisions", solved.collisionReport());
410
-
411
- const sweep = mech.sweepJoint("elbow", -10, 135, 12, { shoulder: 35 });
412
- console.log("Sweep collisions", sweep.filter((step) => step.collisions.length > 0).length);
413
-
414
- show(solved);
415
- ```
416
-
417
- That keeps mechanism setup in earlier cells and collision/sweep investigation in the current preview cell.
418
-
419
- ## ImportedAssembly
420
-
421
- `require("./assembly.forge.js")` returns an `ImportedAssembly` when the file returns an `Assembly`. It has these capabilities:
422
-
423
- ### Kinematic access
424
-
425
- ```javascript
426
- const arm = require("./arm.forge.js");
427
-
428
- // Full kinematic access
429
- const solved = arm.solve({ shoulder: 45 });
430
- console.log(solved.bom());
431
- arm.assembly.sweepJoint("shoulder", -30, 120, 24);
432
- ```
433
-
434
- ### Extracting parts
435
-
436
- ```javascript
437
- const base = arm.part("Base"); // at default state
438
- const link = arm.part("Link", { shoulder: 60 }); // at specific state
439
- ```
440
-
441
- ### Converting to group
442
-
443
- ```javascript
444
- const g = arm.toGroup({ shoulder: 45 }); // ShapeGroup with named children
445
- const baseChild = g.child("Base");
446
- ```
447
-
448
- ### Convenience transforms
449
-
450
- `ImportedAssembly` has `.rotate()`, `.translate()`, `.scale()`, `.mirror()`, `.color()`, and `.child()` that auto-solve at defaults and return a `ShapeGroup`:
451
-
452
- ```javascript
453
- const handle = require("./handle.forge.js");
454
- const positioned = handle.rotate(0, 0, -90).translate(0, -20, 50);
455
- // positioned is a ShapeGroup — use directly in named arrays or group()
456
- ```
457
-
458
- ### Placement references
459
-
460
- ```javascript
461
- const arm = require("./arm.forge.js");
462
- const placed = arm.placeReference("mountHole", [100, 0, 50]);
463
- ```
464
-
465
- ## Merging sub-assemblies into a parent
466
-
467
- `importedAssembly.mergeInto(parent, options)` flattens a sub-assembly's parts and joints into a parent `Assembly`, then wires a mount joint connecting a parent part to the sub-assembly root. After the merge, the parent graph can drive sub-assembly joints directly.
468
-
469
- ```javascript
470
- // scene.forge.js
471
- const chassis = box(200, 80, 20, true);
472
-
473
- const robot = assembly("Robot")
474
- .addPart("Chassis", chassis);
475
-
476
- // Merge left arm — all parts/joints prefixed "Left Arm."
477
- require("./arm.forge.js")
478
- .mergeInto(robot, {
479
- prefix: "Left Arm",
480
- mountParent: "Chassis",
481
- mountJoint: "leftMount",
482
- mountOptions: { frame: Transform.identity().translate(-70, 0, 10) },
483
- });
484
-
485
- // Merge right arm — same source file, different prefix and position
486
- require("./arm.forge.js")
487
- .mergeInto(robot, {
488
- prefix: "Right Arm",
489
- mountParent: "Chassis",
490
- mountJoint: "rightMount",
491
- mountOptions: { frame: Transform.identity().translate(70, 0, 10) },
492
- });
493
-
494
- // Drive sub-assembly joints from the parent using prefixed names
495
- return robot.solve({
496
- "Left Arm.shoulder": 45,
497
- "Right Arm.shoulder": -20,
498
- });
499
- ```
500
-
501
- **`mergeInto(parent, options)` options:**
502
-
503
- | Option | Type | Required | Description |
504
- |---|---|---|---|
505
- | `prefix` | `string` | recommended | Prefix for all part and joint names. `"Left Arm"` turns `"Base"` into `"Left Arm.Base"`. |
506
- | `mountParent` | `string` | yes | Part name in `parent` to attach the sub-assembly root to. |
507
- | `mountJoint` | `string` | yes | Name for the new mount joint in the parent graph. |
508
- | `mountType` | `JointType` | no | Joint type for the mount (default: `'fixed'`). |
509
- | `mountOptions` | `JointOptions` | no | Frame, axis, limits, etc. for the mount joint. |
510
-
511
- **Notes:**
512
- - The sub-assembly must have exactly one root part. If it has multiple roots, connect them with `addFixed()` first.
513
- - Joint couplings inside the sub-assembly are preserved and rewritten with the prefix.
514
- - After merging, use `parent.sweepJoint("Left Arm.shoulder", ...)` for collision sweeps across the full hierarchy.
515
- - Returns `parent` for chaining.
516
-
517
- ## Common pitfalls
518
- - **Animating assemblies with `jointsView`**: If you use [`jointsView()`](../runtime/viewport.md) to animate an assembly, solve the assembly at rest pose (all animated joints = 0) and let `jointsView` control posing via `default` values and animation keyframes. Solving at non-zero angles and then animating will double-rotate parts. See the [viewport docs](../runtime/viewport.md#using-jointsview-with-assemblies) for the full pattern.
519
- - If parts vanish in the viewport, check whether a cut plane is active before debugging kinematics. The viewer-side APIs live in [../runtime/viewport.md](../runtime/viewport.md).
520
- - If a returned object is empty, Forge logs a warning in script output.
521
-
522
- ## Metadata
523
- - `addPart(..., { metadata })` attaches per-part metadata to an assembly part.
524
- - BOM/report helpers such as `solved.bom()` and `solved.bomCsv()` live in [../output/bom.md](../output/bom.md).
525
-
526
- ## Naming grouped assembly children
527
-
528
- When an assembly part is a `ShapeGroup`, Forge flattens the group into separate viewport objects. To avoid opaque labels like `Base Assembly.1`, name the group children explicitly:
529
-
530
- ```javascript
531
- const housing = group(
532
- { name: "Body", shape: body },
533
- { name: "Lid", shape: lid },
534
- );
535
-
536
- const mech = assembly("Case")
537
- .addPart("Base Assembly", housing);
538
- ```
539
-
540
- That produces labels such as `Base Assembly.Body` and `Base Assembly.Lid`.
541
-
542
- ## Robot export
543
-
544
- Use `robotExport({...})` when an assembly should become a simulator package instead of only a viewport scene.
545
-
546
- ```javascript
547
- const rover = assembly("Scout")
548
- .addPart("Chassis", box(300, 220, 50, true))
549
- .addPart("Left Wheel", cylinder(30, 60, undefined, 48, true).pointAlong([0, 1, 0]))
550
- .addPart("Right Wheel", cylinder(30, 60, undefined, 48, true).pointAlong([0, 1, 0]))
551
- .addRevolute("leftWheel", "Chassis", "Left Wheel", {
552
- axis: [0, 1, 0],
553
- frame: Transform.identity().translate(90, 140, 60),
554
- effort: 20,
555
- velocity: 1080,
556
- })
557
- .addRevolute("rightWheel", "Chassis", "Right Wheel", {
558
- axis: [0, 1, 0],
559
- frame: Transform.identity().translate(90, -140, 60),
560
- effort: 20,
561
- velocity: 1080,
562
- });
563
-
564
- robotExport({
565
- assembly: rover,
566
- modelName: "Scout",
567
- links: {
568
- Chassis: { massKg: 10 },
569
- "Left Wheel": { massKg: 0.8 },
570
- "Right Wheel": { massKg: 0.8 },
571
- },
572
- plugins: {
573
- diffDrive: {
574
- leftJoints: ["leftWheel"],
575
- rightJoints: ["rightWheel"],
576
- wheelSeparationMm: 280,
577
- wheelRadiusMm: 60,
578
- },
579
- },
580
- world: {
581
- generateDemoWorld: true,
582
- },
583
- });
584
- ```
585
-
586
- ### Export formats
587
-
588
- ```bash
589
- # SDF package (Gazebo/Ignition) — generates model.sdf + world + STL meshes
590
- forgecad export sdf model.forge.js
591
-
592
- # URDF package (ROS/PyBullet/MuJoCo) — generates .urdf + STL meshes
593
- forgecad export urdf model.forge.js
594
- ```
595
-
596
- Both exporters produce:
597
- - **Mesh-based inertia tensors** — computed from actual triangle geometry via divergence theorem (not bounding-box approximation). Includes full 6-component tensor (Ixx, Iyy, Izz, Ixy, Ixz, Iyz) and center of mass.
598
- - **Separate collision meshes** — controlled per-link via `collision` option.
599
- - **Joint mimic elements** — joint couplings (`addJointCoupling`, `addGearCoupling`) are exported as `<mimic>` elements in both SDF and URDF.
600
-
601
- ### Collision mesh modes
602
-
603
- Set per-link in `robotExport({ links: { "PartName": { collision: mode } } })`:
604
-
605
- | Mode | Description | Default |
606
- |------|-------------|---------|
607
- | `'convex'` | Convex hull of visual geometry (separate `_collision.stl`). Typically 50-80% smaller. | **Yes** |
608
- | `'box'` | Axis-aligned bounding box primitive. Fastest physics but least accurate. | |
609
- | `'visual'` | Same mesh as visual. Exact but slow for simulation. | |
610
- | `'none'` | No collision geometry. Link passes through other objects. | |
611
-
612
- ### Notes
613
-
614
- - Revolute joint `velocity` values are expressed in degrees/second in Forge; the exporters convert them to radians/second.
615
- - Prismatic distances are authored in millimeters and exported in meters.
616
- - `massKg` is preferred for demo robots; `densityKgM3` is a decent fallback when mass is unknown.
617
- - Joint couplings with multiple terms use the primary term (largest ratio) for `<mimic>` since SDF/URDF only support single-leader mimic. A warning is emitted for dropped terms.