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
@@ -1,46 +0,0 @@
1
- ---
2
- skill-group: geometry
3
- skill-order: 1
4
- ---
5
-
6
- # Coordinate System Convention
7
-
8
- ForgeCAD uses a **Z-up** right-handed coordinate system.
9
-
10
- ## Axes
11
-
12
- | Axis | Direction | Positive |
13
- |------|-----------------|----------|
14
- | X | Left / Right | Right |
15
- | Y | Forward / Back | Forward |
16
- | Z | Up / Down | Up |
17
-
18
- ## Standard Views
19
-
20
- | View | Camera position direction | Sees plane |
21
- |--------|--------------------------|------------|
22
- | Front | −Y | XZ |
23
- | Back | +Y | XZ |
24
- | Right | +X | YZ |
25
- | Left | −X | YZ |
26
- | Top | +Z | XY |
27
- | Bottom | −Z | XY |
28
-
29
- ## GizmoViewcube Face Mapping
30
-
31
- Three.js BoxGeometry material indices vs ForgeCAD labels (Z-up remapping):
32
-
33
- | Index | Three.js direction | ForgeCAD label |
34
- |-------|--------------------|----------------|
35
- | 0 | +X | Right |
36
- | 1 | −X | Left |
37
- | 2 | +Y | Front |
38
- | 3 | −Y | Back |
39
- | 4 | +Z | Top |
40
- | 5 | −Z | Bottom |
41
-
42
- Default drei labels are Y-up; ForgeCAD passes `faces={['Right','Left','Front','Back','Top','Bottom']}`.
43
-
44
- ## Grid
45
-
46
- The ground plane is XY (Z = 0). Extrusion goes along +Z. Manifold is Y-up internally — if a kernel-facing operation behaves as if axes are swapped, check for Manifold Y-up semantics leaking through.
@@ -1,52 +0,0 @@
1
- ---
2
- skill-group: geometry
3
- skill-order: 2
4
- ---
5
-
6
- # Geometry Conventions
7
-
8
- ForgeCAD wraps Manifold (mesh kernel) and Three.js (Y-up renderer). This doc captures convention mismatches and how ForgeCAD resolves them.
9
-
10
- ## Winding Order
11
-
12
- CCW = positive area, CW = empty in Manifold's `CrossSection`. ForgeCAD auto-fixes at all entry points:
13
- - `polygon(points)` — computes signed area (shoelace), reverses if CW
14
- - `path().close()` — same fix
15
-
16
- **Rule for new code:** Any function accepting user point arrays that creates a `CrossSection` MUST auto-fix winding.
17
-
18
- ## Coordinate System (Z-up vs Y-up)
19
-
20
- Three.js is Y-up; ForgeCAD is Z-up. Fix applied at camera level (`camera.up = (0,0,1)`) — geometry coordinates are native Z-up. Never swap Y/Z in geometry.
21
-
22
- ## Revolution Axis
23
-
24
- `CrossSection.revolve()` revolves around Y. Profile X = radial distance, Profile Y = height (becomes Z after revolution). Profile must be at X > 0.
25
-
26
- ## Boolean Winding (3D)
27
-
28
- Manifold requires consistent outward face normals. ForgeCAD only creates meshes through Manifold's own constructors, which guarantee correct normals.
29
-
30
- ## Transform Order
31
-
32
- Transforms apply left-to-right. `Sketch.rotate()`, `scale()`, and `mirror()` operate around bounding-box center. For 3D `Shape` / `ShapeGroup`, `scale()` and `mirror()` operate around bounding-box center, while `rotate()` remains origin-based unless you pass `options.pivot` or use `rotateAroundAxis(...)`.
33
-
34
- For explicit transform objects: `A.mul(B)` = apply A then B; `composeChain(A, B, C)` = A→B→C.
35
-
36
- ## Assembly Frame Composition
37
-
38
- ```ts
39
- childWorld = composeChain(childBase, jointMotion, jointFrame, parentWorld)
40
- ```
41
-
42
- Prefer `composeChain(...)` over manual `.mul(...).mul(...)` in kinematics code to avoid order mistakes.
43
-
44
- ## Summary
45
-
46
- | Convention | User sees | Kernel needs | Where we fix it |
47
- |---|---|---|---|
48
- | Winding | Any point order | CCW | `polygon()`, `path().close()` |
49
- | Up axis | Z-up | Y-up (Three.js) | `camera.up`, gizmo labels |
50
- | Revolution | "revolve this profile" | Profile in X-Y, X>0 | Documented only |
51
- | Face normals | Doesn't think about it | Outward-pointing | Manifold constructors |
52
- | Transform order | Left-to-right chain | Post-multiply | Native match |
@@ -1,485 +0,0 @@
1
- ---
2
- skill-group: cli
3
- skill-order: 2
4
- ---
5
-
6
- # Inspection Bundles
7
-
8
- `forgecad inspect <family> <mode>` writes a deterministic directory bundle for
9
- agents, tests, and automation. Use it when a single shaded PNG is too ambiguous
10
- and the consumer needs geometry-aware evidence such as depth, normals, Zebra
11
- stripes, surface roughness, part identity, physical connected components,
12
- interference, local thickness, or cross-sections.
13
-
14
- ## When To Use It
15
-
16
- - Use `forgecad inspect <family> <mode>` for local agent repair loops, model
17
- debugging, and targeted visual evidence.
18
- - Use `forgecad render 3d` for a quick human viewport PNG.
19
- - Use `forgecad render section` when you only need one specific cut plane.
20
- - Use `forgecad render hq` for presentation-quality output, docs, and marketing
21
- renders.
22
-
23
- ## Command
24
-
25
- ```bash
26
- forgecad inspect fit interference model.forge.js --camera iso
27
- forgecad inspect visual cutaway model.forge.js --plane yz
28
- forgecad inspect visual objects model.forge.js --camera front --camera right
29
- forgecad inspect manufacture thickness model.forge.js --min 1.2 --warn 2.0
30
- forgecad inspect sections at model.forge.js --plane yz --offset 12.5
31
- forgecad inspect sections stack model.forge.js --plane yz --every 1
32
- forgecad inspect sections sample model.forge.js --count 5
33
- forgecad inspect compare overlay model.forge.js --with reference.3mf
34
- forgecad inspect evidence
35
- ```
36
-
37
- The default output directory is a unique, non-colliding run directory under
38
- `outputs/inspect`, relative to the current working directory:
39
-
40
- ```text
41
- outputs/inspect/2026-05-28T23-19-14.412Z__visual-objects__ball-bearing__43d0ee/
42
- ```
43
-
44
- Pass a positional `output-dir` or `--output`/`--out` for a preferred directory.
45
- If that directory already exists, ForgeCAD writes to a numbered sibling unless
46
- `--force` is passed. `--force` is the explicit replace path.
47
-
48
- Non-section inspection commands emit one `iso` view by default, except
49
- `inspect visual cutaway`, which emits one automatic orthographic cut-side view.
50
- Pass `--camera` repeatedly, `--view`, `--camera-json`, or `--scene` to use the
51
- same view strategy as `render 3d`.
52
-
53
- Inspection visual evidence defaults to the `inspection` render style: a light
54
- technical background, matte neutral lighting, and thin dark edges for shaded
55
- image/cutaway evidence. Pass `--background`, `--render-style`, or `--edges` when
56
- you need a different presentation. Model-authored `scene()` background, lights,
57
- fog, and exposure are ignored for inspection bundle captures so evidence remains
58
- stable; named scene views are still available through `--view`.
59
-
60
- The command tree is intentionally job-shaped:
61
-
62
- ```text
63
- inspect visual image|cutaway|depth|normals|objects
64
- inspect surface zebra|roughness
65
- inspect physical components|floating|gaps
66
- inspect fit interference
67
- inspect manufacture thickness
68
- inspect compare overlay
69
- inspect sections at|stack|sample
70
- ```
71
-
72
- Manifest keys stay evidence-oriented for stable bundle readers. For example,
73
- `forgecad inspect fit interference` writes `manifest.evidence.collisions`, and
74
- `forgecad inspect physical components` writes `manifest.evidence.connectivity`.
75
-
76
- `--focus` and `--hide` use the same object-name filtering semantics as
77
- `forgecad run` and `forgecad render 3d`. A bare `--focus` hides mock objects;
78
- `--focus name1,name2` emits only matching objects; `--hide name1,name2` removes
79
- matching objects from an otherwise visible scene. Matching is case-insensitive
80
- and supports `*` / `?` globs, so grouped child objects are usually best matched
81
- with patterns such as `Bench.*`.
82
-
83
- ## Bundle Layout
84
-
85
- Bundles store image evidence under an `evidence/` directory. An
86
- `inspect visual objects` bundle with `front`, `right`, and `iso` cameras has this
87
- layout:
88
-
89
- ```text
90
- outputs/inspect/2026-05-28T23-19-14.412Z__visual-objects__model__43d0ee/
91
- manifest.json
92
- evidence/
93
- objects/
94
- front.png
95
- right.png
96
- iso.png
97
- ```
98
-
99
- Use targeted evidence commands for expensive analyses:
100
-
101
- ```bash
102
- forgecad inspect visual depth model.forge.js --camera iso
103
- forgecad inspect visual normals model.forge.js --camera iso
104
- forgecad inspect surface zebra model.forge.js --camera iso
105
- forgecad inspect surface roughness model.forge.js --camera iso
106
- forgecad inspect visual objects model.forge.js --camera iso
107
- forgecad inspect fit interference model.forge.js --camera iso
108
- forgecad inspect sections at model.forge.js --plane yz --offset 12.5
109
- forgecad inspect sections stack model.forge.js --plane yz --every 1
110
- forgecad inspect sections sample model.forge.js --count 5
111
- forgecad inspect manufacture thickness model.forge.js --min 1.2 --warn 2.0 --camera iso
112
- forgecad inspect compare overlay model.forge.js --with reference.3mf
113
- ```
114
-
115
- Run `forgecad inspect evidence` for the canonical command list and the matching
116
- manifest evidence keys.
117
-
118
- ## How To Read A Bundle
119
-
120
- Read inspection bundles as feedback about the model, not as standalone images.
121
- Start with `manifest.json`, then use the evidence PNGs to locate and understand
122
- the finding in the rendered geometry.
123
-
124
- 1. Confirm `bundle.evidenceRequested`, `bundle.evidenceEmitted`, and
125
- `bundle.filters` so you know what was inspected and what was hidden.
126
- 2. Check `scene.bbox`, `scene.volume`, and `scene.objects` for missing geometry,
127
- absurd scale, unexpected mocks, or wrong object names.
128
- 3. For identity evidence such as `objects`, `connectivity`, `distance`, and
129
- `collisions`, resolve colors through the evidence manifest. The same visual
130
- color does not carry a universal meaning across bundles.
131
- 4. For metric evidence such as `depth`, `roughness`, `thickness`, `distance`, and `comparison`,
132
- read the thresholds, ranges, object summaries, and warnings before judging a
133
- PNG by eye.
134
- 5. Inspect image and object evidence first when you need visual context, then
135
- the risk evidence and any orthographic view that exposes the issue. Use
136
- section slices only to inspect hidden internals; do not turn the production
137
- model into a permanent cutaway.
138
- 6. Treat unexpected collisions, critical thin regions, unresolved thickness,
139
- missing section detail, wrong component counts, floating bodies, or surprising
140
- distance gaps as model bugs to fix and reinspect.
141
-
142
- Common color reading rules:
143
-
144
- - Black is usually background; in `floating`, black also means ground-reachable
145
- geometry.
146
- - `objects` and `connectivity` colors are labels. Use the manifest to map colors to
147
- objects, groups, components, or body entries.
148
- - `collisions` colors mark solid overlap findings; match them to
149
- `manifest.evidence.collisions.collisions[].color`.
150
- - `thickness` uses red/orange for critical or warning-thin regions, green/blue
151
- for acceptable or thick regions, and gray for unresolved samples.
152
- - `distance` grades rooted component gaps from green near the root through
153
- yellow to red farther away.
154
- - `comparison` uses the same Difference Only overlay as the viewport: faint
155
- model context, amber candidate mismatch evidence, and cyan reference mismatch
156
- evidence.
157
- - `depth` grades visible camera distance from blue near the camera through green
158
- to red farther away.
159
- - `roughness` uses orange and magenta for sharp, harsh, boundary, or
160
- non-manifold edge neighborhoods.
161
- - `zebra` is read by stripe continuity: smooth flowing bands are healthy, while
162
- kinks, breaks, and faceting deserve investigation.
163
- - `normals` is an encoded camera-view normal map. Use it with `image` and `zebra`
164
- to debug orientation and faceting rather than as a fixed semantic palette.
165
-
166
- ## Evidence Semantics
167
-
168
- `image` emits the standard solid viewport render with a thin edge overlay. Views
169
- are canonical `front`, `right`, `top`, and `iso`.
170
-
171
- `depth` emits visible ray-distance heatmaps. Each shaded pixel is colored by the
172
- distance from the camera position to the visible surface point, normalized per
173
- view between `minDistance` and `maxDistance` from the manifest:
174
-
175
- ```text
176
- rayDistance = distance(cameraPosition, surfacePoint)
177
- normalized = (rayDistance - minDistance) / (maxDistance - minDistance)
178
- ```
179
-
180
- The ramp is blue near the camera, green in the middle, and red far from the
181
- camera. Background pixels are black and should be treated as `null`.
182
-
183
- `normals` emits camera-view normals packed into RGB:
184
-
185
- ```text
186
- normal = normalize((rgb / 255) * 2 - 1)
187
- ```
188
-
189
- Background pixels are black and should be treated as `null`.
190
-
191
- `zebra` emits reflective black-and-white stripe renders for visual
192
- surface-continuity inspection. Stripes are generated from the visible
193
- camera-view normal and simulated reflection direction, so smooth surfaces show
194
- smooth flowing bands while normal discontinuities, faceting, and unexpected
195
- creases kink or break the bands.
196
-
197
- Use Zebra with `image` and `normals` when judging lofts, fillets, swept surfaces,
198
- and skin-like forms. It is a human-readable shader diagnostic, not an exact
199
- curvature-continuity proof; mesh tessellation quality and available smooth
200
- normals determine how faithfully it represents the underlying surface.
201
-
202
- `roughness` emits a mesh-dihedral surface-quality heatmap. Smooth and gently
203
- curved triangles render as a faint translucent shadow over black, while
204
- triangles adjacent to sharp, harsh, boundary, or non-manifold mesh edges render
205
- in orange or magenta:
206
-
207
- ```text
208
- shadow = max adjacent angle < sharpAngleDeg
209
- orange = sharpAngleDeg <= angle < harshAngleDeg
210
- magenta = angle >= harshAngleDeg, boundary, or non-manifold
211
- ```
212
-
213
- The default thresholds are `smoothAngleDeg=5`, `sharpAngleDeg=30`, and
214
- `harshAngleDeg=90`. The manifest stores the method, thresholds, palette, object
215
- list, per-object triangle and edge counts, area percentages by smooth,
216
- moderate, sharp, and harsh classes, angle percentiles, maximum angle, quality
217
- score, and warnings. Moderate angles are reported in the manifest but stay in
218
- the shadow layer by default so intentionally curved surfaces do not light up as
219
- defects. Use this evidence to spot spiky tessellation, accidental faceting,
220
- jagged boolean residue, and dense sharp-corner regions without losing the
221
- silhouette of otherwise smooth surfaces.
222
-
223
- The evidence also writes `evidence/roughness/point-cloud.json`. Each point sample
224
- stores object identity, object-local position, normal, dihedral angle, class,
225
- RGB color, and represented surface area. The PNG renders those samples over
226
- muted source geometry so the visual evidence stays point-level instead of
227
- painting a whole object.
228
-
229
- `objects` emits one object-color image per view. Black is background. Non-black
230
- pixels resolve through `manifest.evidence.objects.objects`, which includes object
231
- index, RGB color, object id, name, group, tree path, and mock flag. Edge pixels
232
- may be antialiased blends; use solid interior colors for exact object lookup.
233
-
234
- `connectivity` emits one physical-component-color image per view. Black is
235
- background. Non-black pixels resolve through
236
- `manifest.evidence.connectivity.components`, and every visible object also has a
237
- `componentIndex` in `manifest.evidence.connectivity.objects`.
238
-
239
- Connectivity is computed from visible scene objects:
240
-
241
- ```text
242
- bbox candidate = bbox interiors overlap or bbox contact gap <= 0.05 model units
243
- mesh contact edge = minimum mesh-surface distance <= contactTolerance
244
- overlap edge = exact boolean intersection volume > 0.1 model units^3 for positive-volume overlap
245
- component = transitive closure over mesh contact and exact overlap edges
246
- ```
247
-
248
- The manifest stores the edge list, component list, per-object body counts, and
249
- warnings. Component colors group scene objects and mesh body entries. If one
250
- scene object contains multiple disconnected mesh islands, those islands are
251
- reported and colored separately as entries such as `Part body 1` and
252
- `Part body 2`.
253
-
254
- Connectivity uses bbox only as a broadphase. Bbox contact alone is not enough to
255
- merge separate scene objects by default, but mesh surfaces within contact
256
- tolerance count as physically connected. This keeps concave assemblies such as
257
- cages and captive balls from being falsely colored as one component while still
258
- allowing stacked or nearly touching parts to share a component. Use the
259
- `collisions` evidence when you need positive-volume overlap evidence as a defect
260
- report rather than a component grouping.
261
-
262
- `floating` emits one disconnected-body highlight image per view. Black is
263
- background or ground-reachable geometry. The highlight color marks physical
264
- components that have no contact path to the ground plane.
265
-
266
- Floating body detection splits visible meshes into disconnected body islands,
267
- links bodies only when their minimum mesh-surface distance is within contact
268
- tolerance (or exact positive-volume overlap when only shape evidence is
269
- available), treats any connected component whose lower Z reaches the viewport
270
- ground plane plus bed tolerance as grounded, then highlights every ungrounded
271
- component. The default ground plane is the visible model's minimum Z;
272
- `scene({ ground: { offset } })` moves it below that by the configured offset.
273
-
274
- ```text
275
- grounded = component bbox minZ <= groundZ + bedTolerance
276
- floating body = !grounded
277
- ```
278
-
279
- This means a `union()` result with two disconnected mesh islands is inspected as
280
- two separate bodies instead of being treated as one safe object. Bbox overlap or
281
- bbox face contact alone is not support evidence. Use `connectivity`, `distance`,
282
- or `collisions` when you need the full physical graph, rooted gap distances, or
283
- collision defects.
284
-
285
- `distance` emits one rooted physical-component-distance heatmap per view. Black
286
- is background. Non-black pixels resolve through
287
- `manifest.evidence.distance.components`, and every visible object also has
288
- `componentIndex`, `rootDistance`, `nearestGap`, and parent-tree metadata in
289
- `manifest.evidence.distance.objects`.
290
-
291
- Distance is computed from visible scene objects:
292
-
293
- ```text
294
- component = physical connectivity component
295
- gap edge = Euclidean distance between component bounding boxes
296
- root = largest component by body count, object count, then bbox volume
297
- rootDistance = shortest accumulated gap distance from root component
298
- ```
299
-
300
- For large scenes the manifest does not materialize the complete component gap
301
- graph, because that graph is quadratic in the number of components. The
302
- `gapEdgeCount` field reports the logical complete-graph edge count used by the
303
- analysis. `gapEdges` stores a compact evidence subset containing nearest-gap
304
- and root-parent edges.
305
-
306
- The PNG colors components from green at the root/near distances through yellow to
307
- red at the farthest rooted component. The manifest stores the root component,
308
- maximum rooted distance, compact gap edge evidence, nearest-gap data, and
309
- shortest-path parent fields. The current v1 metric is bbox-based: it measures air
310
- gaps between component bounding boxes, not exact closest mesh-surface distance.
311
-
312
- `comparison` emits one reference-vs-candidate overlay per view. Pass
313
- `--compare-with <reference>` or declare the target in model code with
314
- `compareWith('./reference.3mf')`. The PNG uses the same Difference Only
315
- comparison overlay as the viewport. Amber marks candidate mismatch evidence,
316
- cyan marks reference mismatch evidence, and faint candidate/reference context
317
- keeps the overlay readable while rotating or comparing against the standard RGB
318
- render.
319
-
320
- Colored mismatch evidence comes from sampled nearest-surface distances: cyan
321
- means reference surface missing from the candidate, and amber means extra
322
- candidate surface. Run `forgecad inspect sections at`, `stack`, or `sample` when
323
- you also want exact cross-section evidence next to the comparison context views.
324
-
325
- The manifest stores visual screen-space mismatch counts, the geometric
326
- `compare 3d` score when the CLI can resolve both inputs, and a
327
- `evidence/comparison/mismatch-points.json` point cloud with world-space sample
328
- positions. Use the geometric score and point-cloud summary as the source of
329
- truth; the PNG is the fast visual index for where to look.
330
-
331
- `collisions` emits one ghosted-overlap image per view. It uses the same
332
- `--focus` / `--hide` visibility set as every other inspect evidence: focused
333
- objects are the only inspected objects. Source objects render as translucent
334
- ghosts, while actual boolean intersection volumes render as solid per-finding
335
- palette colors.
336
-
337
- Collision findings are computed from visible scene objects:
338
-
339
- ```text
340
- collision = boolean intersection volume > 0.1mm^3
341
- ```
342
-
343
- The manifest stores the inspected objects, collision pair names/ids, overlap
344
- volume, broadphase counters, warnings, render style, and each collision finding's
345
- `groupIndex`, `color`, and `hex`. Exact interior pixels can be matched against
346
- `manifest.evidence.collisions.collisions[].color`; antialiased edges may blend
347
- with the ghosted source geometry. If `--focus PartA,PartB` is used, everything
348
- except those objects is hidden, `PartA` and `PartB` are ghosted, and their
349
- overlap volume is highlighted if present.
350
-
351
- Collision broadphase prunes exact boolean checks when the bbox intersection
352
- volume is already below the overlap threshold. This does not change findings:
353
- the real intersection volume cannot exceed the bbox intersection volume.
354
-
355
- `thickness` emits one local wall-thickness heatmap per view. The renderer places
356
- deterministic area-weighted point samples across visible mesh surfaces, casts
357
- through the object along each sample normal, and colors each point by the first
358
- opposite-surface distance:
359
-
360
- ```text
361
- red = thickness <= minThickness
362
- orange = thickness <= warnThickness
363
- green = acceptable thickness
364
- blue = thickness >= maxThickness
365
- gray = unresolved sample
366
- ```
367
-
368
- Thickness uses the same physical-contact edges as `connectivity` and `floating`.
369
- When a ray crosses from one object to a direct physical-contact neighbor, hits
370
- within `contactTolerance` are treated as contact seams and the ray continues to
371
- the next surface. This prevents a tiny modeled gap between touching parts from
372
- being reported as a paper-thin wall.
373
-
374
- The default thresholds are `minThickness=1.2`, `warnThickness=2.0`, and
375
- `maxThickness=6.0` model units. Override them with `--min-thickness`,
376
- `--warn-thickness`, and `--max-thickness`. Use `--thickness-samples` to raise or
377
- lower the maximum thickness point samples per object.
378
-
379
- The manifest stores the method, thresholds, palette, object list, per-object
380
- triangle counts, sampled-triangle counts, minimum, p05, median, mean, maximum,
381
- critical-area percentage, warning-area percentage, below-warning percentage, and
382
- unresolved-area percentage. This makes the PNG useful for visual debugging while
383
- the manifest remains the machine-readable source of truth.
384
-
385
- The evidence also writes `evidence/thickness/point-cloud.json`. Each point sample
386
- stores object identity, object-local position, normal, measured thickness,
387
- class, RGB color, and represented surface area. The PNG renders those samples
388
- over muted source geometry, so local evidence survives even when neighboring
389
- triangles have very different values.
390
-
391
- `roughness` uses the same area-weighted point placement. Point colors are local
392
- to nearby physical feature edges: smooth tessellation diagonals do not become
393
- visible roughness lines. Use `--roughness-samples` to raise or lower the maximum
394
- roughness point samples per object.
395
-
396
- `sections` is split into three explicit modes:
397
-
398
- - `sections at`: one precise section at an exact plane offset.
399
- - `sections stack`: periodic physical slices for reconstruction scans.
400
- - `sections sample`: a fixed number of sparse representative slices.
401
-
402
- Use `at` when an agent has localized a feature and needs the exact cross-section
403
- through it:
404
-
405
- ```bash
406
- forgecad inspect sections at model.forge.js --plane yz --offset 12.5
407
- forgecad inspect sections at model.forge.js --angle 45 --offset 20
408
- ```
409
-
410
- For reconstruction-grade evidence, request a physical stack:
411
-
412
- ```bash
413
- forgecad inspect sections stack model.forge.js --plane yz --every 1
414
- ```
415
-
416
- This emits a slice from the selected plane-family minimum to maximum at the
417
- requested spacing, plus a final max-bound slice when the span is not an exact
418
- multiple. Use `--plane xy|xz|yz` repeatedly or comma-separated to choose
419
- principal planes.
420
-
421
- Vertical angled families are available without manual normal math:
422
-
423
- ```bash
424
- forgecad inspect sections stack model.forge.js --angle 45 --every 1
425
- ```
426
-
427
- `--angle 0` is equivalent to a YZ-style vertical stack, `90` is
428
- equivalent to XZ, and other angles rotate the vertical plane family around Z.
429
-
430
- Use `sample` when you want sparse evidence without choosing exact offsets:
431
-
432
- ```bash
433
- forgecad inspect sections sample model.forge.js --count 5
434
- forgecad inspect sections sample model.forge.js --plane yz --count 9
435
- ```
436
-
437
- If no `--plane` or `--angle` is passed, `sample` emits the principal `xy`, `xz`,
438
- and `yz` families. The renderer refuses accidental giant bundles above
439
- `--max-slices` (default `1000`); raise that limit intentionally for large
440
- reconstruction scans.
441
-
442
- Each section slice records its exact offset, fraction when applicable, area,
443
- path count, size, and contributing object count in the manifest. Each plane
444
- family records its kind, normal, range, spacing, and slice count.
445
-
446
- ## Manifest
447
-
448
- `manifest.json` is the authoritative contract for consuming a bundle. It
449
- contains:
450
-
451
- - `schemaVersion` and generator metadata.
452
- - Source entry file and project root paths.
453
- - Requested evidence, emitted evidence, filters, image size, and quality.
454
- - Canonical views.
455
- - Scene metadata: bbox, volume, params, cut planes, animations, verifications,
456
- and objects.
457
- - Evidence metadata and relative file paths.
458
-
459
- A consumer should prefer paths from the manifest over hard-coding bundle layout.
460
- The layout is intentionally simple, but the manifest is where encoding details,
461
- per-view depth ranges, and object identity mappings live.
462
-
463
- ## Current Limits
464
-
465
- - Depth is a visual heatmap, not an EXR or raw float array.
466
- - Normals are camera-view normals, not world-space normals.
467
- - Object evidence colors are stable within a bundle and resolved through the manifest; do
468
- not infer identity from object order alone.
469
- - Connectivity is object-level. It reports disconnected kernel bodies in the
470
- manifest, but the PNG does not split a single scene object into per-body colors.
471
- - Bbox contact is only broadphase evidence and does not merge separate scene
472
- objects by default. Boolean-overlap edges are exact.
473
- - Distance is a physical-component bbox-gap metric in v1, not exact nearest
474
- mesh-surface distance. Concave components and loose bounding boxes can make the
475
- reported gap smaller than the real closest-surface distance.
476
- - Comparison PNG coverage is screen-space evidence. Hidden or internal
477
- mismatches need the sampled point cloud and geometric score in the manifest.
478
- - Collisions are only positive-volume boolean overlaps. Face-touching parts are
479
- not collision findings.
480
- - Thickness is a mesh/raycast approximation, not FEA or a manufacturability
481
- guarantee. Open meshes, concave geometry, very coarse tessellation, or low
482
- `--thickness-samples` values can leave gray/unresolved or approximate regions.
483
- - Section evidence is exact 2D contour slicing. Use `sections at`, `sections
484
- stack`, or `sections sample`; bare `inspect sections` only explains the modes.
485
- - Zebra is a shader-based visual continuity aid, not exact curvature analysis.
@@ -1,78 +0,0 @@
1
- ---
2
- skill-group: recipes
3
- skill-order: 5
4
- ---
5
-
6
- # Joint Design Recipes
7
-
8
- How to build mechanical joints — clevis-tongue hinges, ball-and-socket, dovetails — that actually rotate without binding and stop where they should.
9
-
10
- ## The Cavity Rule
11
-
12
- Every mechanical joint has a **cavity** in one part and a **tenon** in the other. The cavity must be a real empty volume — not a gap implied by the absence of two separate solids.
13
-
14
- If two adjacent parts in an assembly show a collision volume larger than the expected clearance volume in `forgecad run`, one part is missing its cavity. Both parts have solid material at the same joint position. This will look fine at rest pose but will block rotation and produce confusing joint behavior.
15
-
16
- ```ts
17
- // BAD — body has a stadium cap at both ends; the "slot" between two clevis tines
18
- // is just empty space next to a solid body cap. The next phalanx's tongue knuckle
19
- // has nowhere to go (it intersects the previous body's cap).
20
- const body = stadiumBar(L); // cap at X=0 AND X=L
21
- const tine1 = box(...).translate(L, Y_OFF, 0);
22
- const tine2 = box(...).translate(L, -Y_OFF, 0);
23
- let phalanx = union(body, tine1, tine2);
24
-
25
- // GOOD — body ends FLAT before the joint. Tines extend forward to the pivot.
26
- // The X = L-KNUCK_R..L+KNUCK_R volume between the tines is genuinely empty.
27
- const body = box(L - KNUCK_R, TONG_T, H).translate((L - KNUCK_R) / 2, 0, -H / 2);
28
- const tongueKnuckle = knuckleDisc(0, 0, TONG_T); // proximal cap only
29
- let phalanx = union(tongueKnuckle, body, tine1, tine2, ...tineCaps);
30
- ```
31
-
32
- After applying the cavity rule, `forgecad run` collision volume between adjacent parts in a clevis-tongue chain should drop to **zero** (or a few mm³ of clearance overlap). If it doesn't, there's still solid material where there should be a cavity.
33
-
34
- ## Connecting Cantilevers
35
-
36
- A clevis tine arm at Y=±Y_OFF is geometrically separate from a body at Y=±TONG_T/2. With Y_OFF > TONG_T/2 + clearance, there is a **physical gap** between them. The tines float — they would snap off as soon as load is applied.
37
-
38
- Always add a **yoke**: a short slab spanning the full clevis width, sitting between the body's flat distal end and the tines' attachment point. The yoke fills the Y gap so material is continuous from the body through to each tine.
39
-
40
- ```ts
41
- const yokeLen = 3; // a few mm of structural overlap
42
- const yokeStart = L - KNUCK_R - yokeLen;
43
- const totalY = (Y_OFF + TINE_T / 2) * 2; // full clevis width
44
- const yoke = box(yokeLen, totalY, H)
45
- .translate(yokeStart + yokeLen / 2, 0, -H / 2);
46
- phalanx = union(phalanx, yoke);
47
- ```
48
-
49
- ## Hard Stops vs Slider Limits
50
-
51
- `addRevolute({ min: 0, max: 90 })` sets **slider limits** — the viewport won't let the user drag past them, but the geometry permits any rotation. There is no physical stop.
52
-
53
- For a **geometric** hard stop (parts can't backbend past extension, or can't curl past full closure), add a small protrusion on one part that interferes with the other at the limit angle:
54
-
55
- - **Extension stop at 0°** (typical for fingers, knees, elbows): add a small "lip" on the dorsal side of the proximal end of the child phalanx, sized so it just touches the parent's distal dorsal corner at 0°. Negative rotation (backbending) is then blocked by part-on-part contact.
56
- - **Flexion stop at θmax**: add a similar lip on the palmar side, or rely on the body-to-body collision when bodies meet.
57
-
58
- Verify with `forgecad run` at the limit poses — the contact pair should show ~0 mm³ collision (just touching), and rotation past the limit should report a non-zero collision volume.
59
-
60
- ## Knuckle Sizing
61
-
62
- For a clevis-tongue joint with body height H, the tongue knuckle radius and clevis tine knuckle radius must satisfy:
63
-
64
- ```
65
- KNUCK_R >= H / 2
66
- ```
67
-
68
- If the knuckle radius is smaller than the body's half-height, the body's corners protrude beyond the knuckle envelope. When the joint rotates, those corners sweep through space outside the cylindrical envelope and collide with the adjacent part.
69
-
70
- Setting `KNUCK_R = H / 2` exactly makes the body cross-section a stadium that perfectly fits the knuckle envelope.
71
-
72
- ## Verification Workflow
73
-
74
- 1. Build the joint at rest pose. Run `forgecad run`. Check collision volumes.
75
- 2. If adjacent parts in the joint show > clearance-volume of overlap → missing cavity (apply the cavity rule).
76
- 3. Render with `--focus PartName` to inspect each part in isolation. The clevis end should clearly show a gap between the tines (the cavity).
77
- 4. Render at curl angles (set joint debug params) at 30°, 60°, 90°. No new collisions should appear from rotation.
78
- 5. Render at -10° (backbend test). Either no rotation possible (geometric stop in place) or rotation occurs and you need to add a stop.