forgecad 0.9.13 → 0.9.15

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 (216) hide show
  1. package/LICENSE +6 -4
  2. package/README.md +8 -4
  3. package/dist/assets/{AdminPage-DramHHDf.js → AdminPage-CDyGUinA.js} +2 -2
  4. package/dist/assets/{BenchmarkPage-Bjgkh5m9.js → BenchmarkPage-DfPMY_-d.js} +4 -15
  5. package/dist/assets/{BlogPage-n_HGP3Qm.js → BlogPage-kF0fkdJT.js} +2 -2
  6. package/dist/assets/{DocsPage-WCIkPmzC.js → DocsPage-B954L3YN.js} +9 -3
  7. package/dist/assets/EditorApp-Beb-IZ0y.js +14014 -0
  8. package/dist/assets/{EditorApp-BAnckbsk.css → EditorApp-CuDLxKqL.css} +698 -0
  9. package/dist/assets/{EmbedViewer-DEZKqdfW.js → EmbedViewer-C77B-TrF.js} +3 -3
  10. package/dist/assets/{LandingPageProofDriven-CeRIctuj.js → LandingPageProofDriven-Cr6fXMDj.js} +35 -37
  11. package/dist/assets/LegalPage-BRlScr9A.css +91 -0
  12. package/dist/assets/LegalPage-Dzklqmmg.js +39 -0
  13. package/dist/assets/{PricingPage-BMedqFef.css → PricingPage-BPF6HKyO.css} +25 -0
  14. package/dist/assets/{PricingPage-rIRa8p4Y.js → PricingPage-zWXkvlwl.js} +19 -19
  15. package/dist/assets/{SettingsPage-BqCUvEXM.js → SettingsPage-Bz0of4KQ.js} +2 -2
  16. package/dist/assets/app-CE3sYcV7.css +3890 -0
  17. package/dist/assets/{app-BUZqJvSO.js → app-D3kDkggg.js} +2305 -960
  18. package/dist/assets/cli/{render-lhGxj50Y.js → render-DSY3mMQa.js} +423 -30
  19. package/dist/assets/{constructionHistoryWorker-ipD1jcIv.js → constructionHistoryWorker-gpDo-uH2.js} +927 -243
  20. package/dist/assets/{evalWorker-CHXSe_-u.js → evalWorker-CU0Ke6DP.js} +7799 -4163
  21. package/dist/assets/{forgecad_geometry-BVnIeXMG.js → forgecad_geometry-Dgceylq9.js} +43 -1
  22. package/dist/assets/{forgecad_geometry_bg-DufhhCBV.wasm → forgecad_geometry_bg-dD4RNQF1.wasm} +0 -0
  23. package/dist/assets/{inspectWorker-DeRnMVv1.js → inspectWorker-COyp8XXA.js} +927 -243
  24. package/dist/assets/{javascript-70-4uGcz.js → javascript-1kQXfVaz.js} +1 -1
  25. package/dist/assets/landing-proof-driven-DiGqdtWa.js +18 -0
  26. package/dist/assets/{landing-proof-driven-oFYW6mjz.css → landing-proof-driven-ORyigZ6p.css} +13 -7
  27. package/dist/assets/legalContent-ZfFGMmi4.js +251 -0
  28. package/dist/assets/{manifold-D1LZIHqn.js → manifold-BRI5prcH.js} +1 -1
  29. package/dist/assets/{manifold-C2fwoTgd.js → manifold-C-3h2M7p.js} +2 -2
  30. package/dist/assets/{manifold-BTkzxi9V.js → manifold-DNkrUWpA.js} +1 -1
  31. package/dist/assets/{reportWorker-Cq1qGmg0.js → reportWorker-CdBz5bNg.js} +7537 -10856
  32. package/dist/assets/{scalar-sampling-budget-D9Qv_UlJ.js → scalar-sampling-budget-wJF98aY9.js} +6943 -4345
  33. package/dist/assets/{scanProxyWorker-Bs2TDgLw.js → scanProxyWorker-B-9VbLIs.js} +32 -1
  34. package/dist/assets/{renderSceneState-Dr0xPq1A.js → targets-B9sGB5nB.js} +27 -1
  35. package/dist/assets/{vendor-react-Da3A2QmU.js → vendor-react-6j1Kke-Y.js} +6 -5
  36. package/dist/cli/render.html +1 -1
  37. package/dist/docs/index.html +2 -2
  38. package/dist/docs-raw/AI/ai-native-cad.md +50 -0
  39. package/dist/docs-raw/AI/usage.md +9 -17
  40. package/dist/docs-raw/CLI.md +71 -21
  41. package/dist/docs-raw/component-model.md +27 -11
  42. package/dist/docs-raw/generated/assembly.md +301 -212
  43. package/dist/docs-raw/generated/concepts.md +238 -240
  44. package/dist/docs-raw/generated/core.md +283 -6
  45. package/dist/docs-raw/generated/curves.md +274 -361
  46. package/dist/docs-raw/generated/lib.md +7 -1
  47. package/dist/docs-raw/generated/output.md +19 -4
  48. package/dist/docs-raw/generated/runtime-names.md +41 -0
  49. package/dist/docs-raw/generated/sdf.md +31 -0
  50. package/dist/docs-raw/generated/sheet-metal.md +9 -0
  51. package/dist/docs-raw/generated/sketch.md +44 -1
  52. package/dist/docs-raw/generated/viewport.md +14 -6
  53. package/dist/docs-raw/guides/coordinate-system.md +20 -16
  54. package/dist/docs-raw/guides/geometry-conventions.md +2 -2
  55. package/dist/docs-raw/guides/inspection-bundles.md +2 -1
  56. package/dist/docs-raw/guides/joint-design.md +24 -0
  57. package/dist/docs-raw/guides/positioning.md +13 -3
  58. package/dist/docs-raw/legal/privacy.md +63 -0
  59. package/dist/docs-raw/legal/software-license.md +55 -0
  60. package/dist/docs-raw/legal/terms.md +87 -0
  61. package/dist/docs-raw/skills/forgecad-3d-reconstruction.md +3 -3
  62. package/dist/docs-raw/skills/forgecad-blockout-model.md +1 -1
  63. package/dist/docs-raw/skills/forgecad-component-model.md +11 -2
  64. package/dist/docs-raw/skills/forgecad-high-level-spec.md +1 -1
  65. package/dist/docs-raw/skills/forgecad-image-replicator.md +8 -8
  66. package/dist/docs-raw/skills/forgecad-lld.md +1 -1
  67. package/dist/docs-raw/skills/forgecad-make-a-model.md +4 -4
  68. package/dist/docs-raw/skills/forgecad-model-grader.md +2 -2
  69. package/dist/docs-raw/skills/forgecad-prepare-prompt.md +2 -2
  70. package/dist/docs-raw/skills/forgecad-project.md +1 -1
  71. package/dist/docs-raw/skills/forgecad-reconstruction-benchmark.md +4 -4
  72. package/dist/docs-raw/skills/forgecad-render-inspect.md +4 -2
  73. package/dist/docs-raw/skills/forgecad-visual-spec.md +1 -1
  74. package/dist/docs-raw/skills/forgecad.md +4 -3
  75. package/dist/index.html +40 -12
  76. package/dist/llms.txt +8 -0
  77. package/dist/site.webmanifest +1 -1
  78. package/dist/sitemap.xml +49 -13
  79. package/dist-cli/{check-compiler-LOXCPEOI.js → check-compiler-SDX5QIXI.js} +1 -2
  80. package/dist-cli/{check-query-propagation-BAKNVWXR.js → check-query-propagation-EAYEFT77.js} +1 -2
  81. package/dist-cli/{chunk-RY43WF46.js → chunk-N4O47JLF.js} +13772 -9938
  82. package/dist-cli/forgecad.js +2387 -899
  83. package/dist-cli/{forgecad_geometry-GYVNKPIE.js → forgecad_geometry-QOQIIP53.js} +42 -1
  84. package/dist-cli/forgecad_geometry_bg.wasm +0 -0
  85. package/dist-cli/{solver-46FFSK2U.js → solver-OK4HECRH.js} +0 -1
  86. package/dist-skill/CONTEXT.md +1120 -724
  87. package/dist-skill/SKILL.md +3 -2
  88. package/dist-skill/docs/API/core/concepts.md +64 -1
  89. package/dist-skill/docs/CLI.md +71 -21
  90. package/dist-skill/docs/generated/assembly.md +277 -229
  91. package/dist-skill/docs/generated/core.md +283 -6
  92. package/dist-skill/docs/generated/curves.md +272 -362
  93. package/dist-skill/docs/generated/lib.md +7 -1
  94. package/dist-skill/docs/generated/output.md +19 -4
  95. package/dist-skill/docs/generated/runtime-names.md +41 -0
  96. package/dist-skill/docs/generated/sdf.md +31 -0
  97. package/dist-skill/docs/generated/sheet-metal.md +9 -0
  98. package/dist-skill/docs/generated/sketch.md +44 -2
  99. package/dist-skill/docs/generated/viewport.md +5 -90
  100. package/dist-skill/docs/guides/coordinate-system.md +20 -16
  101. package/dist-skill/docs/guides/geometry-conventions.md +2 -2
  102. package/dist-skill/docs/guides/inspection-bundles.md +2 -1
  103. package/dist-skill/docs/guides/joint-design.md +24 -0
  104. package/dist-skill/docs/guides/positioning.md +13 -3
  105. package/dist-skill/library/forgecad-3d-reconstruction/SKILL.md +2 -2
  106. package/dist-skill/library/forgecad-component-model/SKILL.md +10 -1
  107. package/dist-skill/library/forgecad-image-replicator/SKILL.md +6 -6
  108. package/dist-skill/library/forgecad-image-replicator/scripts/compare_images.py +166 -0
  109. package/dist-skill/library/forgecad-make-a-model/SKILL.md +3 -3
  110. package/dist-skill/library/forgecad-model-grader/SKILL.md +1 -1
  111. package/dist-skill/library/forgecad-prepare-prompt/SKILL.md +1 -1
  112. package/dist-skill/library/forgecad-reconstruction-benchmark/SKILL.md +3 -3
  113. package/dist-skill/library/forgecad-render-inspect/SKILL.md +3 -1
  114. package/examples/api/assembly-kinematics-foundation.forge.js +65 -0
  115. package/examples/api/assembly-kinematics-four-bar.forge.js +115 -0
  116. package/examples/api/assembly-kinematics-limb.forge.js +116 -0
  117. package/examples/api/connector-frame-rig-chain.forge.js +102 -0
  118. package/examples/api/exact-sheet-shell-assembly.forge.js +0 -2
  119. package/examples/api/exact-surface-studio.forge.js +6 -8
  120. package/examples/api/helix-basics.forge.js +6 -6
  121. package/examples/api/lean-foundations/README.md +12 -0
  122. package/examples/api/lean-foundations/curve-blend-exact.forge.js +22 -0
  123. package/examples/api/lean-foundations/curve-fit-interpolation.forge.js +18 -0
  124. package/examples/api/lean-foundations/curve-helix-canonicalization.forge.js +27 -0
  125. package/examples/api/lean-foundations/curve-route-canonicalization.forge.js +16 -0
  126. package/examples/api/lean-foundations/curve-trim-reverse.forge.js +24 -0
  127. package/examples/api/lean-foundations/exact-curve-arc.forge.js +36 -0
  128. package/examples/api/mixed-edge-finishes-proof.forge.js +8 -11
  129. package/examples/api/route3d-elbow.forge.js +68 -0
  130. package/examples/api/transition-curves.forge.js +44 -15
  131. package/examples/api/y-blend-corner-showcase.forge.js +0 -2
  132. package/examples/generative/coral-vase.forge.js +1 -1
  133. package/examples/nurbs-tube.forge.js +1 -1
  134. package/package.json +14 -18
  135. package/dist/assets/EditorApp-CP9Za6tm.js +0 -13630
  136. package/dist/assets/app-CsHnaBWt.css +0 -1789
  137. package/dist/docs-raw/API/README.md +0 -16
  138. package/dist/docs-raw/API/core/concepts.md +0 -118
  139. package/dist/docs-raw/INDEX.md +0 -138
  140. package/dist/docs-raw/RELEASING.md +0 -87
  141. package/dist/docs-raw/agent-native-api.md +0 -27
  142. package/dist/docs-raw/beta-deployment.md +0 -304
  143. package/dist/docs-raw/beta-operations.md +0 -325
  144. package/dist/docs-raw/blueprint-first.md +0 -145
  145. package/dist/docs-raw/cli-monetization.md +0 -112
  146. package/dist/docs-raw/coding-best-practices.md +0 -120
  147. package/dist/docs-raw/coding.md +0 -340
  148. package/dist/docs-raw/deployment.md +0 -374
  149. package/dist/docs-raw/guides/skill-maintenance.md +0 -161
  150. package/dist/docs-raw/guides/surface-members.md +0 -82
  151. package/dist/docs-raw/internals/backend-vocabulary.md +0 -35
  152. package/dist/docs-raw/internals/compiler.md +0 -307
  153. package/dist/docs-raw/internals/constraint-solver-quality.md +0 -161
  154. package/dist/docs-raw/internals/constraint-solver.md +0 -176
  155. package/dist/docs-raw/internals/shape-from-slices.md +0 -152
  156. package/dist/docs-raw/internals/sketch-2d-pipeline.md +0 -108
  157. package/dist/docs-raw/platform/admin.md +0 -45
  158. package/dist/docs-raw/platform/architecture.md +0 -82
  159. package/dist/docs-raw/platform/auth.md +0 -139
  160. package/dist/docs-raw/platform/email.md +0 -67
  161. package/dist/docs-raw/platform/google-oauth-setup.md +0 -88
  162. package/dist/docs-raw/platform/observability.md +0 -197
  163. package/dist/docs-raw/platform/projects.md +0 -111
  164. package/dist/docs-raw/platform/sharing.md +0 -90
  165. package/dist/docs-raw/product/README.md +0 -39
  166. package/dist/docs-raw/product/api-as-product-language.md +0 -13
  167. package/dist/docs-raw/product/business-model.md +0 -15
  168. package/dist/docs-raw/product/competitive-positioning.md +0 -17
  169. package/dist/docs-raw/product/creative-manufacturing.md +0 -15
  170. package/dist/docs-raw/product/founder-story.md +0 -11
  171. package/dist/docs-raw/product/manufacturing-workflows.md +0 -15
  172. package/dist/docs-raw/product/onboarding-first-experience.md +0 -256
  173. package/dist/docs-raw/product/product-loop.md +0 -17
  174. package/dist/docs-raw/product/strategic-decisions.md +0 -22
  175. package/dist/docs-raw/product/user-outreach-email-templates.md +0 -161
  176. package/dist/docs-raw/product/user-segments.md +0 -15
  177. package/dist/docs-raw/product/vision.md +0 -26
  178. package/dist/docs-raw/rl-environments.md +0 -508
  179. package/dist/docs-raw/runbook.md +0 -611
  180. package/dist-cli/check-compiler-LOXCPEOI.js.map +0 -1
  181. package/dist-cli/check-query-propagation-BAKNVWXR.js.map +0 -1
  182. package/dist-cli/chunk-RY43WF46.js.map +0 -1
  183. package/dist-cli/forgecad.js.map +0 -1
  184. package/dist-cli/forgecad_geometry-GYVNKPIE.js.map +0 -1
  185. package/dist-cli/solver-46FFSK2U.js.map +0 -1
  186. package/dist-skill/SKILL-dev.md +0 -145
  187. package/dist-skill/docs-dev/API/core/concepts.md +0 -118
  188. package/dist-skill/docs-dev/CLI.md +0 -647
  189. package/dist-skill/docs-dev/agent-native-api.md +0 -27
  190. package/dist-skill/docs-dev/blueprint-first.md +0 -145
  191. package/dist-skill/docs-dev/coding-best-practices.md +0 -120
  192. package/dist-skill/docs-dev/coding.md +0 -340
  193. package/dist-skill/docs-dev/component-model.md +0 -164
  194. package/dist-skill/docs-dev/generated/assembly.md +0 -794
  195. package/dist-skill/docs-dev/generated/core.md +0 -2117
  196. package/dist-skill/docs-dev/generated/curves.md +0 -2583
  197. package/dist-skill/docs-dev/generated/lib.md +0 -169
  198. package/dist-skill/docs-dev/generated/output.md +0 -247
  199. package/dist-skill/docs-dev/generated/sdf.md +0 -446
  200. package/dist-skill/docs-dev/generated/sheet-metal.md +0 -504
  201. package/dist-skill/docs-dev/generated/sketch.md +0 -1811
  202. package/dist-skill/docs-dev/generated/viewport.md +0 -585
  203. package/dist-skill/docs-dev/generated/wood.md +0 -108
  204. package/dist-skill/docs-dev/guides/coordinate-system.md +0 -46
  205. package/dist-skill/docs-dev/guides/geometry-conventions.md +0 -52
  206. package/dist-skill/docs-dev/guides/inspection-bundles.md +0 -485
  207. package/dist-skill/docs-dev/guides/joint-design.md +0 -78
  208. package/dist-skill/docs-dev/guides/modeling-recipes.md +0 -78
  209. package/dist-skill/docs-dev/guides/positioning.md +0 -161
  210. package/dist-skill/docs-dev/guides/skill-maintenance.md +0 -161
  211. package/dist-skill/docs-dev/internals/backend-vocabulary.md +0 -35
  212. package/dist-skill/docs-dev/internals/compiler.md +0 -307
  213. package/dist-skill/docs-dev/internals/constraint-solver-quality.md +0 -161
  214. package/dist-skill/docs-dev/internals/constraint-solver.md +0 -176
  215. package/dist-skill/docs-dev/internals/sketch-2d-pipeline.md +0 -108
  216. package/dist-skill/library/forgecad-image-replicator/scripts/compare_images.mjs +0 -289
@@ -7,13 +7,13 @@ Every public API function belongs to one of 16 fundamental concepts. This docume
7
7
  - **[C1: Primitive Construction](#c1-primitive-construction)** — Create geometry from parameters — no input geometry required. *(76 functions)*
8
8
  - **[C2: Boolean Combination](#c2-boolean-combination)** — Combine same-dimension geometry using CSG set operations. *(6 functions)*
9
9
  - **[C3: Rigid Transform](#c3-rigid-transform)** — Reposition or reorient geometry without changing its shape. *(3 functions)*
10
- - **[C4: Dimensional Promotion](#c4-dimensional-promotion)** — Convert a 2D profile into a 3D solid (extrude, revolve, loft, sweep). *(50 functions)*
10
+ - **[C4: Dimensional Promotion](#c4-dimensional-promotion)** — Convert a 2D profile into a 3D solid (extrude, revolve, loft, sweep). *(52 functions)*
11
11
  - **[C5: Topology Query](#c5-topology-query)** — Select or inspect named faces and edges on a shape. *(3 functions)*
12
12
  - **[C6: Edge Feature](#c6-edge-feature)** — Modify edges of a solid — fillets, chamfers, draft, offset. *(7 functions)*
13
13
  - **[C7: Pattern Replication](#c7-pattern-replication)** — Duplicate geometry in regular arrangements (linear, circular, mirror). *(6 functions)*
14
14
  - **[C8: Constraint Solving](#c8-constraint-solving)** — Define geometry by relationships and let a solver find positions. *(17 functions)*
15
15
  - **[C9: Spatial Placement](#c9-spatial-placement)** — Position geometry relative to other geometry using semantic anchors. *(6 functions)*
16
- - **[C10: Assembly & Kinematics](#c10-assembly-kinematics)** — Compose parts with joints for kinematic simulation. *(4 functions)*
16
+ - **[C10: Assembly & Kinematics](#c10-assembly-kinematics)** — Compose parts with joints for kinematic simulation. *(3 functions)*
17
17
  - **[C11: Parameterization & UI](#c11-parameterization-ui)** — Declare user-facing controls that drive model geometry. *(7 functions)*
18
18
  - **[C12: Dimensional Demotion](#c12-dimensional-demotion)** — Extract 2D geometry from a 3D solid (section, projection). *(3 functions)*
19
19
  - **[C13: Export & Output](#c13-export-output)** — Convert geometry to external formats (STL, 3MF, SVG, DXF, G-code, PDF). *(5 functions)*
@@ -1002,10 +1002,182 @@ composeChain(...steps: TransformInput[]): Transform
1002
1002
 
1003
1003
  Convert a 2D profile into a 3D solid (extrude, revolve, loft, sweep).
1004
1004
 
1005
- #### `Helix.path()` — Create a metadata-bearing helical centerline around the Z axis.
1005
+ #### `Curve.Blend()` — Create an exact G1 blend curve between two directed endpoints.
1006
+
1007
+ The returned curve is a cubic non-rational `NurbsCurve3D`: ForgeCAD converts the endpoint positions and tangents into Bezier control points, so the curve can feed `sweep` and exact surface boundaries through the existing `nurbs` IR rather than a sampled polyline.
1008
+
1009
+ ```js
1010
+ const rail = Curve.Blend(
1011
+ { point: [0, 0, 0], tangent: [1, 0, 0], weight: 0.8 },
1012
+ { point: [40, 20, 8], tangent: [0, 1, 0], weight: 0.8 },
1013
+ );
1014
+ const tube = sweep(circle2d(2), rail);
1015
+ ```
1016
+
1017
+ ```ts
1018
+ Curve.Blend(start: CurveBlendEndpoint, end: CurveBlendEndpoint): NurbsCurve3D
1019
+ ```
1020
+
1021
+ **`CurveBlendEndpoint`**
1022
+ - `point: Vec3` — Endpoint position.
1023
+ - `tangent: Vec3` — Tangent direction at this endpoint. Magnitude is ignored.
1024
+ - `weight?: number` — Tangent reach relative to the endpoint chord length. Default 1.
1025
+
1026
+ #### `Curve.BlendG2()` — Create an exact G2 blend curve between two directed endpoints.
1027
+
1028
+ This is the curvature-aware companion to `Curve.Blend()`. It returns a degree-5 non-rational `NurbsCurve3D` that matches endpoint position, tangent direction, and optional curvature/second-derivative vectors.
1029
+
1030
+ ```js
1031
+ const rail = Curve.BlendG2(
1032
+ { point: [0, 0, 0], tangent: [1, 0, 0], curvature: [0, 0.02, 0] },
1033
+ { point: [50, 20, 0], tangent: [0, 1, 0], curvature: [-0.02, 0, 0] },
1034
+ );
1035
+ ```
1036
+
1037
+ ```ts
1038
+ Curve.BlendG2(start: CurveBlendG2Endpoint, end: CurveBlendG2Endpoint): NurbsCurve3D
1039
+ ```
1040
+
1041
+
1042
+ **`CurveBlendG2Endpoint`** extends CurveBlendEndpoint
1043
+ - `curvature?: Vec3` — Optional endpoint curvature/second-derivative vector. Default is zero.
1044
+
1045
+ #### `Curve.Arc()` — Create an exact circular 3D arc from start, end, and start tangent.
1046
+
1047
+ The returned curve is a rational quadratic `NurbsCurve3D`, split into stable spans when needed, so it can feed `sweep` without sampling the authoring intent away.
1048
+
1049
+ ```js
1050
+ const rail = Curve.Arc({
1051
+ start: [40, 0, 0],
1052
+ end: [0, 40, 0],
1053
+ tangent: [0, 1, 0],
1054
+ });
1055
+ const tube = sweep(circle2d(2), rail);
1056
+ ```
1057
+
1058
+ ```ts
1059
+ Curve.Arc(options: CurveArcOptions): NurbsCurve3D
1060
+ ```
1061
+
1062
+ **`CurveArcOptions`**
1063
+ - `start: Vec3` — Arc start point.
1064
+ - `end: Vec3` — Arc end point.
1065
+ - `tangent: Vec3` — Tangent direction at the start point. Magnitude is ignored.
1066
+
1067
+ #### `Curve.Line()` — Create an exact straight 3D NURBS line segment.
1068
+
1069
+ ```js
1070
+ const rail = Curve.Line([0, 0, 0], [80, 0, 15]);
1071
+ const rib = sweep(circle2d(2), rail);
1072
+ ```
1073
+
1074
+ ```ts
1075
+ Curve.Line(start: Vec3, end: Vec3): NurbsCurve3D
1076
+ ```
1077
+
1078
+ #### `Curve.Polyline()` — Create a polyline path as cloned 3D points.
1079
+
1080
+ Polylines are exact as route/path input to `sweep`. Use `Curve.Route` when the centerline needs bend and endpoint-frame metadata.
1081
+
1082
+ ```ts
1083
+ Curve.Polyline(points: Vec3[]): Vec3[]
1084
+ ```
1085
+
1086
+ #### `Curve.Spline()` — Create a smooth Catmull-Rom spline path.
1087
+
1088
+ This is a smooth sampled curve object. Use `Curve.Nurbs` when the path must preserve exact control-point and knot data.
1089
+
1090
+ ```ts
1091
+ Curve.Spline(points: Vec3[], options?: Spline3DOptions): Curve3D
1092
+ ```
1093
+
1094
+ **`Spline3DOptions`**
1095
+ - `closed?: boolean` — Closed loop (default false).
1096
+ - `tension?: number` — Catmull-Rom tension in [0, 1]. 0 = very round, 1 = linear-ish. Default 0.5.
1097
+
1098
+ #### `Curve.Nurbs()` — Create an exact NURBS 3D curve from control points, weights, knots, and degree.
1099
+
1100
+ ```js
1101
+ const rail = Curve.Nurbs([[0, 0, 0], [30, 4, 12], [60, -4, 12], [90, 0, 0]]);
1102
+ const tube = sweep(circle2d(2), rail);
1103
+ ```
1006
1104
 
1007
1105
  ```ts
1008
- Helix.path(options: HelixOptions): HelixCurve
1106
+ Curve.Nurbs(points: Vec3[], options?: NurbsCurve3DOptions): NurbsCurve3D
1107
+ ```
1108
+
1109
+ **`NurbsCurve3DOptions`**
1110
+
1111
+ | Option | Type | Description |
1112
+ |--------|------|-------------|
1113
+ | `degree?` | `number` | Polynomial degree (default 3 = cubic). Must be ≥ 1. |
1114
+ | `weights?` | `number[]` | Rational weights, one per control point (default: all 1.0 = non-rational). |
1115
+ | `knots?` | `number[]` | Knot vector (default: uniform clamped). Must have length = controlPoints.length + degree + 1. |
1116
+ | `closed?` | `boolean` | Whether the curve is closed/periodic (default false). |
1117
+
1118
+ #### `Curve.Fit()` — Fit a non-rational NURBS curve that interpolates every input point.
1119
+
1120
+ This is global B-spline interpolation, not approximate curve reduction: ForgeCAD computes chord-length parameters, averaged clamped knots, solves the control points, then verifies the interpolation residual against `tolerance`.
1121
+
1122
+ ```js
1123
+ const rail = Curve.Fit(
1124
+ [[0, 0, 0], [20, 8, 12], [50, -4, 18], [80, 0, 0]],
1125
+ { degree: 3, tolerance: 0.001 },
1126
+ );
1127
+ const tube = sweep(circle2d(2), rail);
1128
+ ```
1129
+
1130
+ ```ts
1131
+ Curve.Fit(points: Vec3[], options?: CurveFitOptions): NurbsCurve3D
1132
+ ```
1133
+
1134
+ **`CurveFitOptions`**
1135
+ - `degree?: number` — Polynomial degree. Default is cubic, reduced automatically for short point lists.
1136
+ - `tolerance?: number` — Maximum allowed interpolation residual in model units. Default 1e-7.
1137
+
1138
+ #### `Curve.Trim()` — Extract an exact curve segment from normalized parameter `start` to `end`.
1139
+
1140
+ `NurbsCurve3D` inputs are trimmed with exact knot insertion/subdomain extraction. Polyline point arrays are trimmed by arclength over their exact line segments. Sampled `Curve3D` splines are rejected until ForgeCAD has a tolerance-controlled rebuild path.
1141
+
1142
+ ```ts
1143
+ Curve.Trim<T extends CurveTrimInput>(curve: T, start: number, end: number): CurveTrimOutput<T>
1144
+ ```
1145
+
1146
+ #### `Curve.Reverse()` — Reverse an exact curve without changing its geometry.
1147
+
1148
+ `NurbsCurve3D` inputs reverse control points, weights, and knots. Polyline point arrays are cloned and reversed. Sampled `Curve3D` splines are rejected until ForgeCAD has a tolerance-controlled rebuild path.
1149
+
1150
+ ```ts
1151
+ Curve.Reverse<T extends CurveTrimInput>(curve: T): CurveTrimOutput<T>
1152
+ ```
1153
+
1154
+ #### `Curve.Route()` — Build analytic 3D line/arc routes for sweeps.
1155
+
1156
+ `Curve.Route.fromPolyline()` is the canonical route API. It returns a `Route3D` value object, preserving exact route segments, named port frames, and the lowerable `route3d` sweep compile plan.
1157
+
1158
+ ```js
1159
+ const route = Curve.Route.fromPolyline(
1160
+ [[0, 0, 0], [0, 0, 50], [40, 0, 50]],
1161
+ { cornerRadius: 12, startPort: 'inlet', endPort: 'outlet' },
1162
+ );
1163
+ const tube = sweep(circle2d(4), route);
1164
+ ```
1165
+
1166
+ ```ts
1167
+ Curve.Route: typeof Route3D
1168
+ ```
1169
+
1170
+ #### `Curve.Helix()` — Build helical paths and swept coils.
1171
+
1172
+ `Curve.Helix` is the canonical namespace for helical paths and coils. It uses the same sweep-based lowering as other curve paths.
1173
+
1174
+ ```js
1175
+ const guide = Curve.Helix.path({ radius: 20, pitch: 6, turns: 4 });
1176
+ const spring = Curve.Helix.coil({ radius: 20, pitch: 6, turns: 4, wireRadius: 1 });
1177
+ ```
1178
+
1179
+ ```ts
1180
+ Curve.Helix: { path(options: HelixOptions): CurveHelixPath; coil: CurveHelixCoil; }
1009
1181
  ```
1010
1182
 
1011
1183
  **`HelixOptions`**
@@ -1020,18 +1192,8 @@ Helix.path(options: HelixOptions): HelixCurve
1020
1192
  | `clockwise?` | `boolean` | Reverse winding direction when viewed from +Z. |
1021
1193
  | `samplesPerTurn?` | `number` | Point samples per turn for the metadata path. Default 32. |
1022
1194
 
1023
- #### `Helix.coil()` — Create a solid helical coil by sweeping a profile through helix-local frames.
1024
-
1025
- Overloads:
1026
-
1027
- - `Helix.coil(options: HelixCoilOptions): Shape`
1028
- - `Helix.coil(profile: Sketch, options: HelixCoilOptions): Shape`
1029
-
1030
1195
 
1031
- **`HelixCoilOptions`** extends HelixOptions
1032
- - `wireRadius?: number` — Radius of the circular wire profile. Required unless a custom profile is passed.
1033
- - `profileSegments?: number` — Segment count for the default circular wire profile. Default 24.
1034
- - `divisionsPerTurn?: number` — Sweep path samples per turn. Default 32.
1196
+ `CurveHelixPath`: `{ radius: number, pitch: number, turns: number, height: number, startAngle: number, clockwise: boolean }`
1035
1197
 
1036
1198
  #### `Loft.station()` — Create a loft station from a 2D profile and an axis position.
1037
1199
 
@@ -1397,37 +1559,6 @@ Surface.Match(shape: Shape, options: { edge: "u0" | "u1" | "v0" | "v1"; target:
1397
1559
  Surface.MatchEdge(shape: Shape, options: { edge: "u0" | "u1" | "v0" | "v1"; target: EdgeRef; continuity?: SurfaceContinuity; }): Shape
1398
1560
  ```
1399
1561
 
1400
- #### `nurbs3d()` — Create a NURBS curve from control points.
1401
-
1402
- With default options, creates a cubic non-rational B-spline with uniform clamped knots. Set `weights` for rational curves (exact circles, conics). Set `degree` for linear (1), quadratic (2), cubic (3), or higher-order curves.
1403
-
1404
- ```js
1405
- // Simple cubic B-spline through control points
1406
- const curve = nurbs3d([[0,0,0], [10,5,0], [20,-5,10], [30,0,5]]);
1407
- const tube = sweep(circle(2), curve);
1408
- ```
1409
-
1410
- ```js
1411
- // Rational quadratic — exact circular arc
1412
- const arc = nurbs3d(
1413
- [[10,0,0], [10,10,0], [0,10,0]],
1414
- { degree: 2, weights: [1, Math.SQRT1_2, 1] }
1415
- );
1416
- ```
1417
-
1418
- ```ts
1419
- nurbs3d(points: Vec3[], options?: NurbsCurve3DOptions): NurbsCurve3D
1420
- ```
1421
-
1422
- **`NurbsCurve3DOptions`**
1423
-
1424
- | Option | Type | Description |
1425
- |--------|------|-------------|
1426
- | `degree?` | `number` | Polynomial degree (default 3 = cubic). Must be ≥ 1. |
1427
- | `weights?` | `number[]` | Rational weights, one per control point (default: all 1.0 = non-rational). |
1428
- | `knots?` | `number[]` | Knot vector (default: uniform clamped). Must have length = controlPoints.length + degree + 1. |
1429
- | `closed?` | `boolean` | Whether the curve is closed/periodic (default false). |
1430
-
1431
1562
  #### `nurbsSurface()` — Create a NURBS surface from a grid of control points.
1432
1563
 
1433
1564
  The control grid is indexed as `controlGrid[u][v]` — each row is a curve in the V direction, and columns trace curves in the U direction.
@@ -1449,73 +1580,6 @@ const surface = nurbsSurface(grid, { thickness: 2 });
1449
1580
  nurbsSurface(controlGrid: Vec3[][], options?: NurbsSurfaceOptions): Shape
1450
1581
  ```
1451
1582
 
1452
- #### `connectEdges()` — Create a transition surface or solid bridge between two edge segments.
1453
-
1454
- Tangents can be inferred from neighboring geometry or supplied explicitly through `options`. This is useful for loft-like blends where you want a direct connection between two edge spans.
1455
-
1456
- ```ts
1457
- connectEdges(edgeA: EdgeSegment, edgeB: EdgeSegment, options?: ConnectEdgesOptions): Shape
1458
- ```
1459
-
1460
- **`EdgeSegment`**
1461
-
1462
- | Option | Type | Description |
1463
- |--------|------|-------------|
1464
- | `index` | `number` | Stable index within the extraction (deterministic for a given mesh). |
1465
- | `direction` | `Vec3` | Normalized direction from start → end. |
1466
- | `dihedralAngle` | `number` | Dihedral angle in degrees (0 = coplanar, 180 = knife edge). |
1467
- | `convex` | `boolean` | true = outside corner (convex), false = inside corner (concave). |
1468
- | `normalA` | `Vec3` | Normal of first adjacent face. |
1469
- | `normalB` | `Vec3` | Normal of second adjacent face (same as normalA for boundary edges). |
1470
- | `boundary` | `boolean` | true if this is a boundary (unmatched) edge — unusual for closed solids. |
1471
- | `start`, `end`, `midpoint`, `length` | | — |
1472
-
1473
- **`TransitionCurveOptions`**
1474
- - `weightA?: number` — Weight for the start edge. Controls tangent magnitude at the start. - 1.0 (default): balanced transition - > 1.0: curve follows start edge longer before turning - < 1.0: curve turns sooner at the start
1475
- - `weightB?: number` — Weight for the end edge. Controls tangent magnitude at the end. - 1.0 (default): balanced transition - > 1.0: curve follows end edge longer before turning - < 1.0: curve turns sooner at the end
1476
- - `samples?: number` — Number of sample points for the output polyline. Default 64. Higher values give smoother curves at the cost of more geometry.
1477
-
1478
- **`TransitionSurfaceOptions`** extends TransitionCurveOptions
1479
-
1480
- | Option | Type | Description |
1481
- |--------|------|-------------|
1482
- | `profile?` | `Sketch` | Cross-section profile to sweep along the transition curve. If omitted, a circular profile with `radius` is used. |
1483
- | `radius?` | `number` | Radius of circular cross-section (used when `profile` is omitted). Default: 5% of chord length. |
1484
- | `rectangleSection?` | `{ width: number; height: number; }` | Width and height for rectangular cross-section. Alternative to `radius` when `profile` is omitted. |
1485
- | `up?` | `Vec3` | Preferred up vector for the sweep frame. Default: auto-detected. |
1486
- | `edgeLength?` | `number` | Edge length for level-set meshing. Smaller = finer. |
1487
- | `boundsPadding?` | `number` | Extra bounds padding for level-set meshing. |
1488
-
1489
- **`ConnectEdgesOptions`** extends TransitionSurfaceOptions
1490
-
1491
- | Option | Type | Description |
1492
- |--------|------|-------------|
1493
- | `endA?` | `EdgeEnd` | Which end of edge A to connect. Default: 'start'. |
1494
- | `endB?` | `EdgeEnd` | Which end of edge B to connect. Default: 'start'. |
1495
- | `tangentModeA?` | `TangentMode` | Tangent mode for edge A. Default: 'along'. |
1496
- | `tangentModeB?` | `TangentMode` | Tangent mode for edge B. Default: 'along'. |
1497
- | `tangentA?` | `Vec3` | Explicit tangent for edge A. |
1498
- | `tangentB?` | `Vec3` | Explicit tangent for edge B. |
1499
- | `flipA?` | `boolean` | Flip tangent A. |
1500
- | `flipB?` | `boolean` | Flip tangent B. |
1501
-
1502
- #### `hermiteTransitionG2()` — Create a quintic Hermite transition curve between two edge endpoints (G2 continuity).
1503
-
1504
- The curve starts at `a.point` tangent to `a.tangent` with curvature `a.curvature`, and ends at `b.point` tangent to `b.tangent` with curvature `b.curvature`, with smooth G2-continuous interpolation matching position, tangent, and curvature.
1505
-
1506
- ```ts
1507
- hermiteTransitionG2(a: QuinticHermiteCurveEndpoint, b: QuinticHermiteCurveEndpoint): QuinticHermiteCurve3D
1508
- ```
1509
-
1510
- **`QuinticHermiteCurveEndpoint`**
1511
-
1512
- | Option | Type | Description |
1513
- |--------|------|-------------|
1514
- | `point` | `Vec3` | Position |
1515
- | `tangent` | `Vec3` | Tangent direction (will be normalized internally) |
1516
- | `curvature?` | `Vec3` | Second derivative / curvature vector. Default [0, 0, 0]. |
1517
- | `weight?` | `number` | Weight: scales tangent magnitude relative to chord length. Default 1.0. |
1518
-
1519
1583
  #### `loft()` — Loft between multiple sketches along Z stations.
1520
1584
 
1521
1585
  Profiles can differ in topology and vertex count: interpolation is done on signed-distance fields and meshed with level-set extraction. Heights must be strictly increasing. Compatible loft stacks can also stay on the maintained export-backend path.
@@ -1526,41 +1590,6 @@ Performance note: loft is significantly heavier than primitive/extrude/revolve.
1526
1590
  loft(profiles: Sketch[], heights: number[], options?: LoftOptions): Shape
1527
1591
  ```
1528
1592
 
1529
- #### `loftAlongSpine()` — Loft between multiple profiles positioned along an arbitrary 3D spine curve.
1530
-
1531
- Unlike loft() which only supports Z heights, loftAlongSpine() places each profile at a position along a 3D spine, oriented perpendicular to the spine tangent. This enables lofting along curved paths — e.g., a wing root-to-tip transition that follows a swept-back leading edge.
1532
-
1533
- The tValues array specifies where each profile sits along the spine (0 = start, 1 = end). Must have the same length as profiles and be in [0, 1].
1534
-
1535
- Internally uses variableSweep infrastructure with SDF interpolation.
1536
-
1537
- Performance note: uses level-set meshing, heavier than simple loft().
1538
-
1539
- ```ts
1540
- loftAlongSpine(profiles: Sketch[], spine: Curve3D | Vec3[], tValues: number[], options?: LoftAlongSpineOptions): Shape
1541
- ```
1542
-
1543
- **`LoftAlongSpineOptions`**
1544
-
1545
- | Option | Type | Description |
1546
- |--------|------|-------------|
1547
- | `samples?` | `number` | Number of samples when spine is a Curve3D. Default 48. |
1548
- | `edgeLength?` | `number` | Marching-grid edge length for level-set meshing. Smaller = finer. |
1549
- | `boundsPadding?` | `number` | Optional extra bounds padding. |
1550
- | `up?` | `Vec3` | Preferred "up" vector for local profile frame. Auto fallback is used near parallel segments. |
1551
-
1552
- #### `spline3d()` — Create a reusable 3D spline curve object (Catmull-Rom).
1553
-
1554
- The returned Curve3D provides sample(), pointAt(t), tangentAt(t), and length() for downstream use in sweep() or manual path operations.
1555
-
1556
- ```ts
1557
- spline3d(points: Vec3[], options?: Spline3DOptions): Curve3D
1558
- ```
1559
-
1560
- **`Spline3DOptions`**
1561
- - `closed?: boolean` — Closed loop (default false).
1562
- - `tension?: number` — Catmull-Rom tension in [0, 1]. 0 = very round, 1 = linear-ish. Default 0.5.
1563
-
1564
1593
  #### `surfacePatch()` — Create a smooth surface patch from 4 boundary curves (Coons patch).
1565
1594
 
1566
1595
  The four curves form the boundary of a quadrilateral patch:
@@ -1620,54 +1649,6 @@ variableSweep(spine: SweepPathInput, sections: VariableSweepSection[], options?:
1620
1649
  | `boundsPadding?` | `number` | Optional extra bounds padding. |
1621
1650
  | `up?` | `Vec3` | Preferred "up" vector for local profile frame. Auto fallback is used near parallel segments. |
1622
1651
 
1623
- #### `transitionCurve()` — Create a smooth transition curve between two edges.
1624
-
1625
- Returns a `HermiteCurve3D` that starts at `edgeA.point` tangent to `edgeA.tangent` and ends at `edgeB.point` tangent to `edgeB.tangent`.
1626
-
1627
- The curve maintains G1 continuity (matching tangent direction) at both endpoints. Weight parameters control the shape of the transition.
1628
-
1629
- ```js
1630
- // Connect two edges with a balanced transition
1631
- const curve = transitionCurve(
1632
- { point: [0, 0, 0], tangent: [1, 0, 0] },
1633
- { point: [10, 5, 0], tangent: [1, 0, 0] },
1634
- );
1635
- ```
1636
-
1637
- // Weighted: curve hugs edge A longer const weighted = transitionCurve( { point: [0, 0, 0], tangent: [1, 0, 0] }, { point: [10, 5, 0], tangent: [1, 0, 0] }, { weightA: 2.0, weightB: 0.5 }, );
1638
-
1639
- ```
1640
-
1641
- ```ts
1642
- transitionCurve(edgeA: TransitionEdge, edgeB: TransitionEdge, options?: TransitionCurveOptions): HermiteCurve3D
1643
- ```
1644
-
1645
- **`TransitionEdge`**
1646
- - `point: Vec3` — Connection point on the edge. Can be any point along the edge where the transition should connect.
1647
- - `tangent: Vec3` — Tangent direction at the connection point. This is the direction the curve should initially follow when leaving this edge. For a straight edge, this is typically the edge direction pointing "outward" (away from the body of the edge, toward the other edge).
1648
- - `normal?: Vec3` — Surface normal at the connection point (optional). Used as a hint for the sweep frame's up vector.
1649
-
1650
- #### `transitionSurface()` — Create a solid transition surface between two edges by sweeping a profile along a Hermite transition curve.
1651
-
1652
- This produces a watertight solid that smoothly connects the two edges. Works with both Manifold and OCCT backends.
1653
-
1654
- ```js
1655
- // Circular tube connecting two edges
1656
- const tube = transitionSurface(
1657
- { point: [0, 0, 0], tangent: [1, 0, 0] },
1658
- { point: [10, 5, 3], tangent: [0, 1, 0] },
1659
- { radius: 0.5 },
1660
- );
1661
- ```
1662
-
1663
- // Custom profile with weights const custom = transitionSurface( { point: [0, 0, 0], tangent: [1, 0, 0] }, { point: [10, 5, 3], tangent: [0, 1, 0] }, { profile: mySketch, weightA: 1.5, weightB: 0.8 }, );
1664
-
1665
- ```
1666
-
1667
- ```ts
1668
- transitionSurface(edgeA: TransitionEdge, edgeB: TransitionEdge, options?: TransitionSurfaceOptions): Shape
1669
- ```
1670
-
1671
1652
  ---
1672
1653
 
1673
1654
  ## C5: Topology Query
@@ -1732,9 +1713,9 @@ selectEdge(shape: Shape, query?: EdgeQuery): EdgeSegment
1732
1713
  | `minLength?` | `number` | Filter: minimum edge length. |
1733
1714
  | `maxLength?` | `number` | Filter: maximum edge length. |
1734
1715
  | `within?` | `BoundingRegion` | Filter: edge midpoint must be within this bounding region. |
1735
- | `atZ?` | `number` | Shorthand: edge midpoint Z this value (within `tolerance`). Equivalent to `within: { zMin: atZ - tol, zMax: atZ + tol }`. |
1736
- | `tolerance?` | `number` | Position tolerance for approximate matches (default: `1.0`). Used by `atZ` and `near`. |
1737
- | `angleTolerance?` | `number` | Angular tolerance in degrees for `parallel`/`perpendicular` filters (default: `10`). |
1716
+ | `atZ?` | `number` | Shorthand: edge midpoint Z is approximately this value within `tolerance`. |
1717
+ | `tolerance?` | `number` | Position tolerance for approximate matches. Used by `atZ` and `near`. Default: `1.0`. |
1718
+ | `angleTolerance?` | `number` | Angular tolerance in degrees for `parallel`/`perpendicular` filters. Default: `10`. |
1738
1719
 
1739
1720
  `BoundingRegion`: `{ xMin?: number, xMax?: number, yMin?: number, yMax?: number, zMin?: number, zMax?: number }`
1740
1721
 
@@ -2332,31 +2313,43 @@ Points.polar(length: number, angleDeg: number, from?: [ number, number ]): [ num
2332
2313
 
2333
2314
  Compose parts with joints for kinematic simulation.
2334
2315
 
2335
- #### `assembly()` — Create an assembly container with named parts and joints for kinematic mechanisms.
2316
+ #### `assembly()` — Create an assembly container with named parts, connectors, and kinematic links.
2336
2317
 
2337
- **Use this from iteration 1 for any model with moving parts.** Hinges, sliders, gears, articulated fingers, doors — all start with `assembly()`, not with manual rotation math. Don't build a static "extended pose" first and refactor to an assembly later: joint sliders, animations, sweeps, collision detection, and robot export all flow from the kinematic graph.
2318
+ **Use this from iteration 1 for any model with moving parts.** Do not build one static pose and retrofit motion later.
2338
2319
 
2339
- 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.
2320
+ Links are named kinematic markers in the assembly. `edgeBetweenLinks()` records structural distances or visual relationships. `addAngleBetweenLinks()` records measured, limited, or controlled angles. These APIs solve point positions, not rigid-body frames.
2340
2321
 
2341
- 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.
2322
+ `addPart(..., { mate })` attaches a part connector origin to a solved link point by translation only. It is right for markers and point-following geometry. Use `connect()` / `match()` for physical articulated parts that need full connector frame alignment and deterministic rest orientation.
2342
2323
 
2343
- 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.
2324
+ Return an `Assembly` directly to expose its connector joints and driven link controls in the editor. Moving those controls re-runs the assembly solve with the new state, so closed-loop link/edge mechanisms move through the real kinematic solver instead of a viewport-only forward-kinematics approximation.
2325
+
2326
+ If no link in a connected kinematic component is fixed, ForgeCAD chooses a deterministic gauge link for solving and reports a floating-component warning.
2327
+
2328
+ The legacy joint-chain APIs still exist for compatibility and exporter plumbing. New work should choose between point-link kinematics and connector-frame joints based on whether the part needs orientation.
2344
2329
 
2345
2330
  For multi-file assemblies, a file that returns an `Assembly` is importable via `require()` and yields an `ImportedAssembly`. Use `mergeInto()` to flatten a sub-assembly into a parent assembly.
2346
2331
 
2332
+ **Point-link example**
2333
+
2334
+ This snippet mates a marker to the solved `tip` point. It does not orient a bar along `ground -> tip`.
2335
+
2347
2336
  ```ts
2348
- const mech = assembly("Arm")
2349
- .addPart("base", box(80, 80, 20).translate(0, 0, -10), {
2350
- metadata: { material: "PETG", process: "FDM", qty: 1 },
2337
+ const marker = box(8, 8, 4).withConnectors({
2338
+ center: connector({ origin: [0, 0, 0], axis: [0, 0, 1] }),
2339
+ });
2340
+
2341
+ const mech = assembly("Linkage")
2342
+ .link("ground", { at: [0, 0, 0], fixed: true })
2343
+ .link("worldX", { at: [10, 0, 0], fixed: true })
2344
+ .link("tip", { at: [40, 0, 0] })
2345
+ .edgeBetweenLinks("ground", "tip", { name: "bar" })
2346
+ .addAngleBetweenLinks("worldX", "ground", "tip", {
2347
+ name: "theta",
2348
+ control: { min: 0, max: 120, default: 30 },
2351
2349
  })
2352
- .addPart("link", box(140, 24, 24).translate(0, -12, -12))
2353
- .addRevolute("shoulder", "base", "link", {
2354
- axis: [0, 1, 0],
2355
- min: -30, max: 120, default: 25,
2356
- frame: Transform.identity().translate(0, 0, 20),
2357
- });
2350
+ .addPart("Tip marker", marker, { mate: { connector: "center", toLink: "tip" } });
2358
2351
 
2359
- return mech; // auto-solved at defaults, renders all parts
2352
+ return mech;
2360
2353
  ```
2361
2354
 
2362
2355
  ```ts
@@ -2380,28 +2373,15 @@ bomToCsv(rows: BomRow[]): string
2380
2373
  | `tags?` | `string \| readonly string[]` | Viewport organization tags applied to scene objects produced from this part. |
2381
2374
  | `material?`, `process?`, `tolerance?`, `qty?`, `notes?`, `densityKgM3?`, `massKg?` | | — |
2382
2375
 
2383
- #### `joint()` Create a revolute joint that auto-generates a parameter slider and rotates the shape.
2376
+ <!-- forgecad-skill:exclude-start symbol="jointsView" reason="Compatibility-only viewport FK API. Prefer returning `Assembly` directly so controls move through the solver-backed link/edge kinematics model." -->
2377
+ #### `jointsView()` — Register legacy viewport-only mechanism controls that animate returned objects without re-running the script.
2384
2378
 
2385
- This is a convenience wrapper for single-shape, single-joint use cases. It calls `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.
2386
-
2387
- ```ts
2388
- const arm = joint("Shoulder", armShape, [0, 0, 20], {
2389
- axis: [0, 1, 0],
2390
- min: -30, max: 120, default: 25,
2391
- });
2392
- return arm;
2393
- ```
2394
-
2395
- ```ts
2396
- joint(name: string, shape: Shape, pivot: [ number, number, number ], opts?: RevoluteJointOpts): Shape
2397
- ```
2398
-
2399
- `RevoluteJointOpts`: `{ axis?: [ number, number, number ], min?: number, max?: number, default?: number, unit?: string, reverse?: boolean }`
2400
-
2401
- #### `jointsView()` — Register viewport-only mechanism controls that animate returned objects without re-running the script.
2379
+ > **Not included in ForgeCAD AI skill context yet.** This API remains visible in human docs, but is intentionally omitted from shipped agent skills until it is ready for agent-first use. Compatibility-only viewport FK API. Prefer returning `Assembly` directly so controls move through the solver-backed link/edge kinematics model.
2402
2380
 
2403
2381
  Defines joints (revolute or prismatic), optional gear/rack couplings, and named animations. The viewport resolves transforms through the joint chain at display time — the script geometry is computed only once at rest pose.
2404
2382
 
2383
+ For `Assembly` mechanisms, prefer returning the `Assembly` directly. Returned assemblies expose solver-backed controls automatically, so link/edge closed loops move through the real assembly solver instead of this viewport-only FK layer.
2384
+
2405
2385
  **Critical:** Solve the assembly at **rest pose** (all animated joints = 0). The viewport applies `jointsView` transforms on top of the returned scene. If geometry is already solved at non-zero angles, animation will double-rotate everything.
2406
2386
 
2407
2387
  ```js
@@ -2433,6 +2413,8 @@ return solved;
2433
2413
 
2434
2414
  Animation values are interpolated linearly between keyframes. ForgeCAD does **not** auto-wrap revolute values across `-180/180`. Keep keyframe values continuous — a `-180 -> 171` jump spins the part the long way around. Use `-180 -> -189` instead. Author high-speed multi-turn joints as accumulating angles (`0, 360, 720, ...`) with `continuous: true`.
2435
2415
 
2416
+ **Mirrored revolute axes:** `default` values and animation keyframes are physical joint values, signed by the joint axis. In a bilateral mechanism, mirrored hinge axes such as `[1, 0, 0]` and `[-1, 0, 0]` need opposite physical values for the same mirrored pose. Negate the mirrored revolute track and mirror physical limits as `[min, max] -> [-max, -min]`. Prismatic tracks do not have this handedness flip.
2417
+
2436
2418
  **Tick-based keyframes:** Omit `at` from all keyframes to auto-distribute by tick weight:
2437
2419
 
2438
2420
  ```js
@@ -2482,6 +2464,7 @@ jointsView(options?: JointsViewOptions): void
2482
2464
  - `at?: number` — Timeline position [0, 1]. If omitted from ALL keyframes, positions are auto-computed from tick weights.
2483
2465
  - `ticks?: number` — Relative weight of the segment from this keyframe to the next (default 1). Only used in tick-based mode (when `at` is omitted). Last keyframe's ticks value is ignored.
2484
2466
  - Also: `values: Record<string, number>`
2467
+ <!-- forgecad-skill:exclude-end -->
2485
2468
 
2486
2469
  ---
2487
2470
 
@@ -2736,7 +2719,7 @@ Call `robotExport()` alongside your assembly definition. The CLI commands `forge
2736
2719
 
2737
2720
  - Mesh-based inertia tensors (full 6-component, not bounding-box approximations)
2738
2721
  - Separate collision meshes (convex hull by default — ~50–80% smaller)
2739
- - Joint mimic elements derived from `addJointCoupling` / `addGearCoupling`
2722
+ - Joint limits, effort/velocity/damping/friction metadata from assembly joints
2740
2723
 
2741
2724
  **Collision mesh modes** (set per-link via `links["PartName"].collision`):
2742
2725
 
@@ -2752,7 +2735,7 @@ Call `robotExport()` alongside your assembly definition. The CLI commands `forge
2752
2735
  - Revolute `velocity` is in degrees/second in Forge; exporters convert to rad/s.
2753
2736
  - Prismatic distances are in mm in Forge; exported in meters.
2754
2737
  - `massKg` is preferred; `densityKgM3` is used when mass is unknown.
2755
- - Couplings with multiple terms: only the primary term (largest ratio) maps to `<mimic>` — SDF/URDF support single-leader mimic only. Dropped terms emit a warning.
2738
+ - Compatibility coupling metadata, when present, maps only the primary term (largest ratio) to `<mimic>` — SDF/URDF support single-leader mimic only. Dropped terms emit a warning.
2756
2739
 
2757
2740
  ```ts
2758
2741
  const rover = assembly("Scout")
@@ -2808,9 +2791,9 @@ robotExport(options: RobotExportOptions): CollectedRobotExport
2808
2791
 
2809
2792
  **`CollectedRobotExport`**: `modelName: string`, `assembly: AssemblyDefinition`, `state: JointState`, `static: boolean`, `selfCollide: boolean`, `allowAutoDisable: boolean`, `links: Record<string, RobotLinkExportOptions>`, `joints: Record<string, RobotJointExportOptions>`, `plugins: { diffDrive?: RobotDiffDrivePluginOptions; jointStatePublisher?: RobotJointStatePublisherOptions; }`, `world: RobotWorldOptions | null`
2810
2793
 
2811
- `AssemblyDefinition`: `{ name: string, parts: AssemblyPartDef[], joints: AssemblyJointDef[], jointCouplings: AssemblyJointCouplingDef[] }`
2794
+ **`AssemblyDefinition`**: `name: string`, `parts: AssemblyPartDef[]`, `joints: AssemblyJointDef[]`, `jointCouplings: AssemblyJointCouplingDef[]`, `kinematics: AssemblyKinematicGraphDef`
2812
2795
 
2813
- `AssemblyPartDef`: `{ name: string, part: AssemblyPart, base: Transform, metadata?: PartMetadata }`
2796
+ `AssemblyPartDef`: `{ name: string, part: AssemblyPart, base: Transform, metadata?: PartMetadata, mates: AssemblyPartMateInput[] }`
2814
2797
 
2815
2798
  **`PartMetadata`**
2816
2799
 
@@ -2819,6 +2802,11 @@ robotExport(options: RobotExportOptions): CollectedRobotExport
2819
2802
  | `tags?` | `string \| readonly string[]` | Viewport organization tags applied to scene objects produced from this part. |
2820
2803
  | `material?`, `process?`, `tolerance?`, `qty?`, `notes?`, `densityKgM3?`, `massKg?` | | — |
2821
2804
 
2805
+ **`AssemblyPartMateInput`**
2806
+ - `connector: string` — Name of a connector declared on the part (via `withConnectors()`).
2807
+ - `toLink: string` — Name of the link this connector's origin is pinned to.
2808
+ - `aimLink?: string` — Optional second link to orient toward. When set, the part is rotated so the connector's **axis** aims from `toLink` toward `aimLink`, posing an oriented bone instead of only translating it. For full pose without relying on a connector axis, declare a second mate (two connectors → two links).
2809
+
2822
2810
  **`AssemblyJointDef`**: `name: string`, `type: JointType`, `parent: string`, `child: string`, `frame: Transform`, `axis: Vec3`, `min?: number`, `max?: number`, `defaultValue: number`, `unit?: string`, `effort?: number`, `velocity?: number`, `damping?: number`, `friction?: number`, `connectorRefs?: JointConnectorRefs`
2823
2811
 
2824
2812
  `JointConnectorRefs`: `{ parent: string, child: string, parentAlign?: PortAlign, childAlign?: PortAlign }`
@@ -2827,6 +2815,16 @@ robotExport(options: RobotExportOptions): CollectedRobotExport
2827
2815
 
2828
2816
  `JointCouplingTermRecord`: `{ joint: string, ratio: number }`
2829
2817
 
2818
+ `AssemblyKinematicGraphDef`: `{ links: AssemblyLinkDef[], edges: AssemblyEdgeBetweenLinksDef[], angles: AssemblyAngleBetweenLinksDef[] }`
2819
+
2820
+ `AssemblyLinkDef`: `{ name: string, at: Vec3, fixed: boolean, metadata?: Record<string, unknown> }`
2821
+
2822
+ **`AssemblyEdgeBetweenLinksDef`**: `name: string`, `a: string`, `b: string`, `length: number | null`, `min?: number`, `max?: number`, `visualOnly: boolean`, `control?: AssemblyKinematicControlOptions`, `metadata?: Record<string, unknown>`
2823
+
2824
+ `AssemblyKinematicControlOptions`: `{ min?: number, max?: number, default?: number, unit?: string }`
2825
+
2826
+ **`AssemblyAngleBetweenLinksDef`**: `name: string`, `a: string`, `b: string`, `c: string`, `target?: number`, `min?: number`, `max?: number`, `control?: AssemblyKinematicControlOptions`, `metadata?: Record<string, unknown>`
2827
+
2830
2828
  #### `sheetMetal()` — Create a parametric sheet metal part with flanges, bend allowances, and flat-pattern unfolding.
2831
2829
 
2832
2830
  `sheetMetal()` keeps one semantic model and derives both a folded 3D solid and an accurate flat pattern from it. The K-factor bend allowance is applied during unfolding. This is a strict v1 subset — it does not infer sheet metal from arbitrary solids.
@@ -3244,13 +3242,13 @@ compareWith(path: string, options?: CompareWithOptions): void
3244
3242
  | `samples?` | `number` | Surface samples per direction for numeric scoring. Defaults to the comparison scorer's standard sample count. |
3245
3243
  | `label?` | `string` | Human label for the reference model in inspection manifests. |
3246
3244
 
3247
- #### `mock()` — Register a mock (context) object for visualization and collision checking.
3245
+ #### `mock()` — Register a mock (context) object for visualization and inspection.
3248
3246
 
3249
- Mock objects appear in the viewport and spatial analysis when you run a file directly, but are excluded when the file is imported via `require()`. This lets you model the surrounding context — walls, bolts, mating parts — without polluting the module's exports.
3247
+ Mock objects appear in the viewport and inspection analysis when you run a file directly, but are excluded when the file is imported via `require()`. This lets you model the surrounding context — walls, bolts, mating parts — without polluting the module's exports.
3250
3248
 
3251
3249
  The shape is returned unchanged, so you can reference it for alignment, dimensioning, and `verify` checks.
3252
3250
 
3253
- Mock objects participate in `forgecad run --spatial bounded|exact` collision detection and spatial analysis. Their names appear with a `(mock)` suffix in reports.
3251
+ Mock objects participate in focused inspection commands such as `forgecad inspect fit interference` and `forgecad inspect physical gaps`. Their names appear with a `(mock)` suffix in reports.
3254
3252
 
3255
3253
  In the viewport, mock objects render at reduced opacity so they are visually distinct from real geometry.
3256
3254
 
@@ -3424,7 +3422,7 @@ showLabels(shape: Shape): Shape
3424
3422
 
3425
3423
  #### `viewConfig()` — Configure viewport helper visuals for the current script execution.
3426
3424
 
3427
- Controls renderer-only overlays that appear in the viewport but are not part of the geometry. Currently supports the joint overlay that renders axis arrows and arc indicators when `jointsView` is active. Multiple calls merge — later values override earlier ones per key.
3425
+ Controls renderer-only overlays that appear in the viewport but are not part of the geometry. Currently supports the joint overlay that renders axis arrows and arc indicators when joint controls are active. Multiple calls merge — later values override earlier ones per key.
3428
3426
 
3429
3427
  This does **not** trigger a geometry recompute; it only affects the visual helpers drawn on top of the 3D scene.
3430
3428