cozyclay 1.9.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (286) hide show
  1. package/CHANGELOG.md +171 -0
  2. package/bin/agent/agent-routes.mjs +74 -70
  3. package/bin/agent/agent-runner.mjs +10 -3
  4. package/bin/agent/motion-runtime.mjs +32 -260
  5. package/bin/agent/providers.mjs +46 -3
  6. package/bin/agent/session-store.mjs +41 -5
  7. package/bin/agent/studio-prompt.mjs +1 -1
  8. package/bin/agent/studio-tools.mjs +48 -11
  9. package/bin/cozyclay.mjs +26 -8
  10. package/bin/live/cli.mjs +22 -3
  11. package/bin/mcp-runtime.mjs +35 -20
  12. package/bin/telemetry-state.mjs +5 -2
  13. package/bin/update-check.mjs +15 -0
  14. package/dist/app/index.html +6 -6
  15. package/dist/assets/analytics-MVZOEW8O.js +1 -0
  16. package/dist/assets/app-C1sdVbg4.css +1 -0
  17. package/dist/assets/app-CA55tcI3.js +4876 -0
  18. package/dist/assets/{demo-B-eg1CT8.js → demo-ByeYp-dY.js} +1 -1
  19. package/dist/assets/{first-shot-handoff-CGgnvNu8.js → first-shot-handoff-DgT28ZiV.js} +1 -1
  20. package/dist/assets/{landing-IbRp3FnQ.js → landing-BaXkpFip.js} +1 -1
  21. package/dist/assets/shot-prompt-CbqWcWYi.css +1 -0
  22. package/dist/assets/shot-prompt-Yrt99wZx.js +17 -0
  23. package/dist/assets/{ticket-BgIzOBOf.js → ticket-BcmsnHn5.js} +1 -1
  24. package/dist/assets/workflow-DRdwoDBT.css +1 -0
  25. package/dist/assets/workflow-dNPGY9xz.js +179 -0
  26. package/dist/cozyclay-package.json +1 -1
  27. package/dist/fonts/IBM-Plex-Sans-OFL.txt +92 -0
  28. package/dist/fonts/JetBrains-Mono-OFL.txt +93 -0
  29. package/dist/fonts/README.md +13 -9
  30. package/dist/fonts/ibm-plex-sans-400-latin.woff2 +0 -0
  31. package/dist/fonts/ibm-plex-sans-500-latin.woff2 +0 -0
  32. package/dist/fonts/ibm-plex-sans-600-latin.woff2 +0 -0
  33. package/dist/fonts/jetbrains-mono-400-latin.woff2 +0 -0
  34. package/dist/fonts/jetbrains-mono-500-latin.woff2 +0 -0
  35. package/dist/index.html +7 -7
  36. package/dist/privacy/index.html +13 -8
  37. package/dist/sitemap.xml +6 -6
  38. package/dist/workflow/index.html +5 -5
  39. package/mcp/LIVE-PROTOCOL.md +1 -1
  40. package/mcp/live-hub.mjs +35 -93
  41. package/mcp/mesh-file.mjs +48 -0
  42. package/mcp/server.mjs +6 -77
  43. package/mcp/tool-handlers.mjs +224 -377
  44. package/package.json +2 -1
  45. package/src/App.jsx +1273 -9075
  46. package/src/analytics.js +393 -9
  47. package/src/app-context.js +211 -0
  48. package/src/app-stage.jsx +25 -24
  49. package/src/ardy/auto-fix-panel.css +108 -0
  50. package/src/ardy/collision-blockers.js +9 -3
  51. package/src/ardy/fix-collisions.js +10 -66
  52. package/src/ardy/ground.js +4 -4
  53. package/src/ardy/ik-drag.js +101 -0
  54. package/src/ardy/ik-key-json.js +43 -0
  55. package/src/ardy/ik.js +122 -76
  56. package/src/ardy/physics-panel.css +2 -0
  57. package/src/ardy/physics-panel.jsx +17 -5
  58. package/src/ardy/platform-fit-panel.css +64 -0
  59. package/src/ardy/platform-fit-panel.jsx +40 -0
  60. package/src/ardy/platform-fit.js +326 -0
  61. package/src/ardy/playback.js +166 -20
  62. package/src/ardy/range-pin.js +299 -0
  63. package/src/ardy/timeline-coordinates.js +26 -0
  64. package/src/ardy/timeline.css +1199 -0
  65. package/src/ardy/timeline.jsx +149 -41
  66. package/src/ardy/waypoints.js +2 -2
  67. package/src/asset-pane.css +462 -0
  68. package/src/asset-pane.jsx +461 -232
  69. package/src/command-bus.js +349 -0
  70. package/src/commands/ai.js +31 -0
  71. package/src/commands/cast.js +140 -0
  72. package/src/commands/elements/character.js +13 -0
  73. package/src/commands/elements/motion.js +14 -0
  74. package/src/commands/elements/object.js +22 -0
  75. package/src/commands/elements/scene.js +16 -0
  76. package/src/commands/elements/shot.js +9 -0
  77. package/src/commands/elements/stage.js +11 -0
  78. package/src/commands/elements.js +155 -0
  79. package/src/commands/export.js +20 -0
  80. package/src/commands/index.js +40 -0
  81. package/src/commands/motion.js +231 -0
  82. package/src/commands/objects.js +174 -0
  83. package/src/commands/project.js +53 -0
  84. package/src/commands/scene.js +81 -0
  85. package/src/commands/shared.js +19 -0
  86. package/src/commands/shot.js +103 -0
  87. package/src/commands/stage.js +17 -0
  88. package/src/commands/view.js +42 -0
  89. package/src/document-store.js +144 -0
  90. package/src/domains/cast.js +1007 -0
  91. package/src/domains/motion.js +3219 -0
  92. package/src/domains/objects.js +932 -0
  93. package/src/domains/scenes.js +817 -0
  94. package/src/domains/shots.js +404 -0
  95. package/src/domains/stage.js +93 -0
  96. package/src/dualview.jsx +113 -57
  97. package/src/facing-marks.js +3 -0
  98. package/src/first-success-guide.jsx +14 -14
  99. package/src/grid-view.js +18 -5
  100. package/src/hierarchy-model.js +24 -16
  101. package/src/hierarchy-panel.css +386 -0
  102. package/src/hierarchy-panel.jsx +150 -21
  103. package/src/{fal-motion-client.js → i2v-motion-client.js} +13 -13
  104. package/src/i2v-motion-studio.jsx +180 -0
  105. package/src/ik-camera.js +3 -0
  106. package/src/main.jsx +6 -0
  107. package/src/motion/generation.js +105 -0
  108. package/src/motion-readiness-ui.jsx +4 -4
  109. package/src/motion-readiness.js +11 -0
  110. package/src/motion-trail.js +366 -9
  111. package/src/object-gizmo.jsx +7 -1
  112. package/src/otio.js +2 -1
  113. package/src/panels/CameraPanel.jsx +60 -0
  114. package/src/panels/CharacterTransformPanel.jsx +48 -0
  115. package/src/panels/EnvironmentPanel.jsx +40 -0
  116. package/src/panels/Foldout.jsx +32 -0
  117. package/src/panels/LightPanel.jsx +24 -0
  118. package/src/panels/ObjectTransformPanel.jsx +554 -0
  119. package/src/panels/PosePanel.jsx +97 -0
  120. package/src/panels/ProjectPanel.jsx +33 -0
  121. package/src/panels/PromptBlocksPanel.jsx +442 -0
  122. package/src/panels/PropsPanel.jsx +70 -0
  123. package/src/panels/ReferenceImageField.jsx +91 -0
  124. package/src/panels/RigControlPanel.jsx +78 -0
  125. package/src/panels/RigPanel.jsx +37 -0
  126. package/src/panels/SubjectBox.jsx +47 -0
  127. package/src/panels/SubjectsPanel.jsx +39 -0
  128. package/src/panels/VideoCapturePanel.jsx +180 -0
  129. package/src/panels/details.css +1171 -0
  130. package/src/panels/motion.css +197 -0
  131. package/src/panels/pose.css +39 -0
  132. package/src/planview.jsx +60 -31
  133. package/src/posestudio.jsx +59 -4
  134. package/src/project-browser.css +1073 -0
  135. package/src/project-browser.jsx +284 -123
  136. package/src/range-pin-object-transform.js +19 -0
  137. package/src/range-pin-panel.css +518 -0
  138. package/src/range-pin-panel.jsx +344 -0
  139. package/src/result-modal.jsx +5 -5
  140. package/src/room.jsx +79 -51
  141. package/src/scene-objects.js +37 -1
  142. package/src/scenes.js +5 -0
  143. package/src/semantic-edit.js +2 -0
  144. package/src/settings-menu.jsx +39 -105
  145. package/src/shell/BottomDock.jsx +450 -0
  146. package/src/shell/DetailsSlot.jsx +513 -0
  147. package/src/shell/LibrarySlot.jsx +86 -0
  148. package/src/shell/MenuBar.jsx +601 -0
  149. package/src/shell/OutlinerSlot.jsx +50 -0
  150. package/src/shell/PreferencesDialog.jsx +538 -0
  151. package/src/shell/PreferencesSlot.jsx +40 -0
  152. package/src/shell/StatusBar.jsx +125 -0
  153. package/src/shell/StudioShell.jsx +92 -0
  154. package/src/shell/TopBar.jsx +129 -0
  155. package/src/shell/ViewportToolbar.jsx +656 -0
  156. package/src/shell/agent-glass.css +296 -0
  157. package/src/shell/dock.css +93 -0
  158. package/src/shell/glass-regions.css +985 -0
  159. package/src/shell/glass.css +687 -0
  160. package/src/shell/log-store.js +52 -0
  161. package/src/shell/mode.css +9 -0
  162. package/src/shell/preferences.css +651 -0
  163. package/src/shell/shell.css +282 -0
  164. package/src/shell/studio-shell-context.js +19 -0
  165. package/src/shell/topbar.css +435 -0
  166. package/src/shell/viewport.css +451 -0
  167. package/src/store/authored-intent.js +22 -0
  168. package/src/store/runtime-adapters.js +92 -0
  169. package/src/store/scene-stage.js +21 -0
  170. package/src/store/use-document-store.js +14 -0
  171. package/src/studio-actions.js +210 -0
  172. package/src/studio-agent-commands.js +41 -271
  173. package/src/studio-agent-context.js +39 -10
  174. package/src/studio-agent-motion.js +122 -373
  175. package/src/studio-agent-protocol.js +67 -31
  176. package/src/studio-app-binding.js +379 -0
  177. package/src/studio-contact-sheet.js +73 -0
  178. package/src/studio-elements.js +58 -51
  179. package/src/styles/themes.css +200 -0
  180. package/src/styles/tokens.css +69 -0
  181. package/src/styles.css +460 -1742
  182. package/src/theme.js +44 -0
  183. package/src/timeline-extent.js +16 -0
  184. package/src/trail-key-conflicts.js +39 -0
  185. package/src/trail-pick.js +59 -0
  186. package/src/ui.jsx +8 -8
  187. package/src/use-case-question.jsx +66 -0
  188. package/src/workflow/AgentPanel.jsx +82 -69
  189. package/src/workflow/agent-client.js +12 -2
  190. package/src/workflow/agent-panel.css +34 -32
  191. package/src/workflow/cozy-scene-node.css +2 -0
  192. package/src/workflow/workflow.css +2 -0
  193. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  194. package/tools/ardy/bridge.mjs +164 -146
  195. package/tools/ardy/visual-qa.mjs +5 -5
  196. package/tools/bench/EXP3.md +61 -0
  197. package/tools/bench/cclay_bench_extract_incam.py +125 -0
  198. package/tools/bench/cclay_bench_extract_obs.py +204 -0
  199. package/tools/bench/cclay_bench_runner.py +42 -0
  200. package/tools/bench/cube-contact.mjs +162 -0
  201. package/tools/bench/exp3.mjs +152 -0
  202. package/tools/bench/extract-bench-lib.mjs +216 -0
  203. package/tools/bench/extract-bench.mjs +302 -0
  204. package/tools/bench/fal-generate.mjs +88 -0
  205. package/tools/bench/fit/README.md +189 -0
  206. package/tools/bench/fit/camera.mjs +59 -0
  207. package/tools/bench/fit/contact.mjs +419 -0
  208. package/tools/bench/fit/footlock.mjs +234 -0
  209. package/tools/bench/fit/motion.mjs +85 -0
  210. package/tools/bench/fit/pin.mjs +27 -0
  211. package/tools/bench/fit/remote.mjs +105 -0
  212. package/tools/bench/fit-bench.mjs +126 -0
  213. package/tools/bench/fit-sanity.mjs +45 -0
  214. package/tools/bench/metrics.mjs +313 -0
  215. package/tools/bench/obs/depth.mjs +258 -0
  216. package/tools/bench/obs/extrinsics.mjs +175 -0
  217. package/tools/bench/obs/fit_mannequin_betas.py +340 -0
  218. package/tools/bench/obs/ground.mjs +294 -0
  219. package/tools/bench/obs/heading.mjs +151 -0
  220. package/tools/bench/obs/ladder.mjs +555 -0
  221. package/tools/bench/obs/mannequin-betas.json +124 -0
  222. package/tools/bench/obs/remote.mjs +83 -0
  223. package/tools/bench/obs/rest_joints.py +52 -0
  224. package/tools/bench/obs/ybot-targets.mjs +49 -0
  225. package/tools/bench/obs-bench.mjs +520 -0
  226. package/tools/bench/score.mjs +343 -0
  227. package/tools/bench/summarize.mjs +86 -0
  228. package/tools/dev/pages/privacy.html +13 -8
  229. package/tools/gt-render/browser.mjs +159 -0
  230. package/tools/gt-render/camera-math.mjs +276 -0
  231. package/tools/gt-render/page.mjs +267 -0
  232. package/tools/gt-render/render.mjs +514 -0
  233. package/tools/gt-render/scene-box.mjs +31 -0
  234. package/tools/gt-render/take-transform.mjs +87 -0
  235. package/tools/morphgs/assets/gen_truth.py +39 -0
  236. package/tools/morphgs/assets/mesh_ori_rig.txt +27 -0
  237. package/tools/morphgs/assets/playback-check.mjs +30 -0
  238. package/tools/morphgs/demo-gate.sh +26 -0
  239. package/tools/morphgs/fbx2morphgs.mjs +68 -0
  240. package/tools/morphgs/morphgs-to-cskel27.mjs +105 -0
  241. package/tools/morphgs/patches/preprocess_src-none-mode.patch +76 -0
  242. package/tools/morphgs/setup-on-cluster.sh +126 -0
  243. package/tools/qa/css-rule-usage.mjs +283 -0
  244. package/tools/qa/studio-control-count.mjs +70 -32
  245. package/tools/run-tests.mjs +134 -12
  246. package/tools/track/DESIGN.md +513 -0
  247. package/tools/track/backfill-provenance.mjs +116 -0
  248. package/tools/track/budget.mjs +8 -0
  249. package/tools/track/check-rig.mjs +209 -0
  250. package/tools/track/diagnostics.schema.json +60 -0
  251. package/tools/track/export-rig.mjs +256 -0
  252. package/tools/track/fallback.mjs +7 -0
  253. package/tools/track/fk-parity-fixture.mjs +161 -0
  254. package/tools/track/gate.mjs +349 -0
  255. package/tools/track/masks.mjs +318 -0
  256. package/tools/track/metrics.mjs +201 -0
  257. package/tools/track/publish-obs.mjs +108 -0
  258. package/tools/track/py/check_env.py +61 -0
  259. package/tools/track/py/eval_lr.py +229 -0
  260. package/tools/track/py/lr_viterbi.py +294 -0
  261. package/tools/track/py/masks.py +405 -0
  262. package/tools/track/py/objective.py +382 -0
  263. package/tools/track/py/rig.py +508 -0
  264. package/tools/track/py/scene.py +326 -0
  265. package/tools/track/py/test_joint_indices.py +123 -0
  266. package/tools/track/py/test_lr_viterbi.py +195 -0
  267. package/tools/track/py/test_masks.py +229 -0
  268. package/tools/track/py/test_rig.py +288 -0
  269. package/tools/track/py/test_scene.py +354 -0
  270. package/tools/track/py/test_track.py +328 -0
  271. package/tools/track/py/track.py +411 -0
  272. package/tools/track/remote.mjs +140 -0
  273. package/tools/track/rig-dump.mjs +227 -0
  274. package/tools/track/run-box-tests.mjs +14 -0
  275. package/tools/track/run-box.mjs +168 -0
  276. package/tools/track/setup-box.sh +59 -0
  277. package/tools/track/study-2d.mjs +728 -0
  278. package/dist/assets/analytics-B1hnH66c.js +0 -1
  279. package/dist/assets/app-BWusbqgO.js +0 -4861
  280. package/dist/assets/app-qKDo4PBX.css +0 -1
  281. package/dist/assets/shot-prompt-CHUtw6af.js +0 -17
  282. package/dist/assets/shot-prompt-C_g2BHVc.css +0 -1
  283. package/dist/assets/workflow-CVwzMhz2.js +0 -179
  284. package/dist/assets/workflow-DsudxKHF.css +0 -1
  285. package/src/fal-motion-studio.jsx +0 -180
  286. package/src/scene-history.js +0 -129
@@ -0,0 +1,124 @@
1
+ {
2
+ "schema": "obs.mannequin-betas.v1",
3
+ "method": {
4
+ "model": "smpl-x supermotion neutral",
5
+ "optimizer": "projected Adam warm start + bounded SLSQP minimax",
6
+ "adam_steps": 5000,
7
+ "adam_lr": 0.005,
8
+ "minimax_steps": 1500,
9
+ "minimax_starts": 3,
10
+ "minimax_success": true,
11
+ "minimax_status": 0,
12
+ "beta_limit": 3.0,
13
+ "smooth_max_weight": 0.0,
14
+ "smooth_max_temperature": 0.002,
15
+ "l2_prior": 1e-05,
16
+ "device": "cpu",
17
+ "source": "rendered rig rest pose (public/models/y-bot-tpose.fbx), segments: thigh_left, thigh_right, shin_left, shin_right, foot_left, foot_right, pelvis_width, spine_length, upper_arm_left, upper_arm_right, forearm_left, forearm_right, shoulder_width"
18
+ },
19
+ "betas": [
20
+ 0.9879521131515503,
21
+ -0.43811583518981934,
22
+ 0.23403924703598022,
23
+ 1.1289187669754028,
24
+ 0.4586992859840393,
25
+ -0.2744547724723816,
26
+ -2.9971723556518555,
27
+ -3.0,
28
+ 2.999577045440674,
29
+ 2.7470200061798096
30
+ ],
31
+ "target_geometry": {
32
+ "ybot_leg_height_m": 0.9650828829792554,
33
+ "canonical_cskel27_leg_height_m": 0.9544128,
34
+ "playback_scale": 1.0111797358326036,
35
+ "rest_contract": "public/ardy/cskel27-rest.json"
36
+ },
37
+ "residuals": {
38
+ "thigh_left": {
39
+ "target_m": 0.4059943638074366,
40
+ "fitted_m": 0.39750587940216064,
41
+ "residual_m": -0.008488484405275942,
42
+ "abs_residual_m": 0.008488484405275942
43
+ },
44
+ "thigh_right": {
45
+ "target_m": 0.4059943859177966,
46
+ "fitted_m": 0.4050813913345337,
47
+ "residual_m": -0.0009129945832628827,
48
+ "abs_residual_m": 0.0009129945832628827
49
+ },
50
+ "shin_left": {
51
+ "target_m": 0.42099025912196575,
52
+ "fitted_m": 0.4308183193206787,
53
+ "residual_m": 0.009828060198712962,
54
+ "abs_residual_m": 0.009828060198712962
55
+ },
56
+ "shin_right": {
57
+ "target_m": 0.42099023874729363,
58
+ "fitted_m": 0.43444767594337463,
59
+ "residual_m": 0.013457437196081001,
60
+ "abs_residual_m": 0.013457437196081001
61
+ },
62
+ "foot_left": {
63
+ "target_m": 0.15721559146548023,
64
+ "fitted_m": 0.14614197611808777,
65
+ "residual_m": -0.011073615347392457,
66
+ "abs_residual_m": 0.011073615347392457
67
+ },
68
+ "foot_right": {
69
+ "target_m": 0.15721558809599626,
70
+ "fitted_m": 0.14439848065376282,
71
+ "residual_m": -0.012817107442233439,
72
+ "abs_residual_m": 0.012817107442233439
73
+ },
74
+ "pelvis_width": {
75
+ "target_m": 0.18248906135565482,
76
+ "fitted_m": 0.15615954995155334,
77
+ "residual_m": -0.026329511404101474,
78
+ "abs_residual_m": 0.026329511404101474
79
+ },
80
+ "spine_length": {
81
+ "target_m": 0.3518987587650434,
82
+ "fitted_m": 0.3289218842983246,
83
+ "residual_m": -0.02297687446671881,
84
+ "abs_residual_m": 0.02297687446671881
85
+ },
86
+ "upper_arm_left": {
87
+ "target_m": 0.20827565969956363,
88
+ "fitted_m": 0.23210719227790833,
89
+ "residual_m": 0.023831532578344694,
90
+ "abs_residual_m": 0.023831532578344694
91
+ },
92
+ "upper_arm_right": {
93
+ "target_m": 0.2082745012754011,
94
+ "fitted_m": 0.2391563355922699,
95
+ "residual_m": 0.030881834316868806,
96
+ "abs_residual_m": 0.030881834316868806
97
+ },
98
+ "forearm_left": {
99
+ "target_m": 0.27614463806176104,
100
+ "fitted_m": 0.2709249258041382,
101
+ "residual_m": -0.005219712257622855,
102
+ "abs_residual_m": 0.005219712257622855
103
+ },
104
+ "forearm_right": {
105
+ "target_m": 0.2761446380616249,
106
+ "fitted_m": 0.26336047053337097,
107
+ "residual_m": -0.012784167528253954,
108
+ "abs_residual_m": 0.012784167528253954
109
+ },
110
+ "shoulder_width": {
111
+ "target_m": 0.3752159881593781,
112
+ "fitted_m": 0.34457817673683167,
113
+ "residual_m": -0.030637811422546457,
114
+ "abs_residual_m": 0.030637811422546457
115
+ }
116
+ },
117
+ "summary": {
118
+ "segment_count": 13,
119
+ "max_abs_beta": 3.0,
120
+ "max_abs_residual_m": 0.030881834316868806,
121
+ "rms_residual_m": 0.018625148220087758,
122
+ "max_bone_length_residual_m": 0.030881834316868806
123
+ }
124
+ }
@@ -0,0 +1,83 @@
1
+ import { spawn } from "node:child_process";
2
+ import { randomBytes } from "node:crypto";
3
+ import { appendFileSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
4
+ import { fileURLToPath } from "node:url";
5
+ import { assertGpuIdle } from "../fit/remote.mjs";
6
+
7
+ const sshOptions = ["-o", "BatchMode=yes", "-o", "ConnectTimeout=15"];
8
+ const port = process.env.CCLAY_EXTRACT_SSH_PORT;
9
+ const sshFlags = port ? [...sshOptions, "-p", port] : sshOptions;
10
+ const scpFlags = port ? [...sshOptions, "-P", port] : sshOptions;
11
+ const here = fileURLToPath(new URL("../", import.meta.url));
12
+ let injectedExec = null;
13
+
14
+ const quote = value => `'${String(value).replaceAll("'", "'\\''")}'`;
15
+
16
+ function realExec(program, args, log) {
17
+ return new Promise((resolve, reject) => {
18
+ const child = spawn(program, args, { stdio: ["ignore", "pipe", "pipe"] });
19
+ let output = "";
20
+ const onData = chunk => { output += chunk; if (log) appendFileSync(log, chunk); };
21
+ child.stdout.on("data", onData); child.stderr.on("data", onData);
22
+ child.once("error", reject);
23
+ child.once("close", code => code === 0 ? resolve(output) : reject(new Error(`${program} exited ${code}: ${output.slice(-3000)}`)));
24
+ });
25
+ }
26
+
27
+ /** Test hook: replace ssh/scp execution with (program, args, log) => Promise. */
28
+ export function setObsExec(exec) { injectedExec = exec; }
29
+
30
+ function cameraK(camera) {
31
+ const value = typeof camera === "string" ? JSON.parse(readFileSync(camera, "utf8")) : camera;
32
+ const K = value?.K ?? value?.intrinsics?.K ?? value?.intrinsics;
33
+ if (!Array.isArray(K) || K.length !== 3 || K.some(row => !Array.isArray(row) || row.length !== 3)) {
34
+ throw new Error("camera must contain a 3x3 K array");
35
+ }
36
+ return K;
37
+ }
38
+
39
+ async function run(program, args, log) {
40
+ return (injectedExec ?? realExec)(program, args, log);
41
+ }
42
+
43
+ export async function extractObs({ host, video, camera, output, detector = "palette", keypoints = "vitpose", betas, log, force = false }) {
44
+ if (!host) throw new Error("missing host");
45
+ if (!video || !camera || !output) throw new Error("video, camera, and output are required");
46
+ if (!["palette", "yolo"].includes(detector)) throw new Error(`invalid detector: ${detector}`);
47
+ if (!["vitpose", "hybrid"].includes(keypoints)) throw new Error(`invalid keypoints: ${keypoints}`);
48
+ const exec = (program, args) => run(program, args, log);
49
+ if (!injectedExec) await assertGpuIdle(host);
50
+ else await exec("ssh", [...sshFlags, host, "python3 -c 'import sys; print(\"GPU idle for GVHMR\")'"]);
51
+
52
+ const remote = `/tmp/cclay-obs-${Date.now()}-${randomBytes(5).toString("hex")}`;
53
+ const localK = `${output}.K-${process.pid}.json`;
54
+ const K = cameraK(camera);
55
+ writeFileSync(localK, JSON.stringify(K));
56
+ let failure;
57
+ try {
58
+ await exec("ssh", [...sshFlags, host, `umask 077 && mkdir ${quote(remote)}`]);
59
+ await exec("scp", [...scpFlags, `${here}/cclay_bench_extract_obs.py`, `${host}:${remote}/cclay_bench_extract_obs.py`]);
60
+ await exec("scp", [...scpFlags, video, `${host}:${remote}/video.mp4`]);
61
+ await exec("scp", [...scpFlags, localK, `${host}:${remote}/K.json`]);
62
+ const args = [
63
+ `${remote}/cclay_bench_extract_obs.py`,
64
+ "cclay_gvhmr_extract.py",
65
+ `${remote}/video.mp4`, `${remote}/obs.npz`, "--K-json", `${remote}/K.json`,
66
+ "--detector", detector, "--keypoints", keypoints, "--out-root", `${remote}/cache`, "--check-pp",
67
+ ];
68
+ if (betas) {
69
+ await exec("scp", [...scpFlags, betas, `${host}:${remote}/betas.json`]);
70
+ args.push("--betas-json", `${remote}/betas.json`);
71
+ }
72
+ await exec("ssh", [...sshFlags, host, `set -e; cd ~/cclay-ingest/GVHMR; .venv/bin/python ${args.map(quote).join(" ")}`]);
73
+ await exec("scp", [...scpFlags, `${host}:${remote}/obs.npz`, output]);
74
+ } catch (error) {
75
+ failure = error;
76
+ } finally {
77
+ try { unlinkSync(localK); } catch {}
78
+ try { await exec("ssh", [...sshFlags, host, `rm -rf ${quote(remote)}`]); }
79
+ catch (error) { failure = failure ? new AggregateError([failure, error], `remote cleanup failed: ${remote}`) : error; }
80
+ }
81
+ if (failure) throw failure;
82
+ return { output, remote };
83
+ }
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env python3
2
+ """SMPL rest joints for one betas vector (box helper for tools/bench/obs-bench.mjs).
3
+
4
+ Run from ~/cclay-ingest/GVHMR with its venv:
5
+ .venv/bin/python rest_joints.py '<json list of 10 betas>'
6
+
7
+ Reproduces cclay_gvhmr_extract.py main()'s ``smpl_rest_joints`` exactly:
8
+ SMPL-X ``make_smplx("supermotion")`` zero pose for ``betas`` -> smplx2smpl
9
+ sparse vertices -> neutral SMPL J-regressor, pelvis moved to the origin.
10
+ Also reports the rest mesh's lowest vertex height (pelvis-relative), which
11
+ gives the body's sole clearance under each foot joint. CPU only: the GPU is
12
+ reserved for serial GVHMR extraction, so CUDA is hidden before torch loads.
13
+ """
14
+ import json
15
+ import os
16
+ import sys
17
+
18
+ os.environ["CUDA_VISIBLE_DEVICES"] = ""
19
+
20
+ import numpy as np
21
+ import torch
22
+
23
+ from hmr4d.utils.smplx_utils import make_smplx
24
+
25
+
26
+ def main():
27
+ assert not torch.cuda.is_available(), "rest_joints.py must not see the GPU"
28
+ betas = np.asarray(json.loads(sys.argv[1]), dtype=np.float32)
29
+ if betas.shape != (10,) or not np.isfinite(betas).all():
30
+ raise ValueError("expected 10 finite betas")
31
+ smplx = make_smplx("supermotion")
32
+ sparse = torch.load("hmr4d/utils/body_model/smplx2smpl_sparse.pt", map_location="cpu")
33
+ regressor = torch.load("hmr4d/utils/body_model/smpl_neutral_J_regressor.pt", map_location="cpu")
34
+ with torch.no_grad():
35
+ rest = smplx(betas=torch.from_numpy(betas)[None], body_pose=torch.zeros(1, 63),
36
+ global_orient=torch.zeros(1, 3), transl=torch.zeros(1, 3))
37
+ verts = torch.matmul(sparse, rest.vertices[0])
38
+ joints = torch.einsum("jv,vi->ji", regressor, verts)
39
+ pelvis = joints[0].clone()
40
+ joints = joints - pelvis
41
+ min_y = float((verts[:, 1] - pelvis[1]).min())
42
+ print(json.dumps({
43
+ "schema": "obs.rest-joints.v1",
44
+ "betas": [float(v) for v in betas],
45
+ "restJoints": joints.numpy().astype(np.float32).tolist(),
46
+ "restMinVertexY": min_y,
47
+ "model": "make_smplx('supermotion') zero pose -> smplx2smpl_sparse -> smpl_neutral_J_regressor; pelvis at origin",
48
+ }))
49
+
50
+
51
+ if __name__ == "__main__":
52
+ main()
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+ // Body-size targets for fit_mannequin_betas.py, measured on the character that
3
+ // is actually rendered: the y-bot FBX rig in its rest (T) pose, at the Studio's
4
+ // 0.01 armature scale. Only the character model is read (no motion, no video).
5
+ // Usage: node tools/bench/obs/ybot-targets.mjs [model] > targets.json
6
+ import { existsSync, readFileSync } from "node:fs";
7
+ import { dirname, join } from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+ import * as THREE from "three";
10
+ import { FBXLoader } from "three/examples/jsm/loaders/FBXLoader.js";
11
+
12
+ const ROOT = join(dirname(fileURLToPath(import.meta.url)), "../../..");
13
+
14
+ // Segment name -> Mixamo bone pair, matching fit_mannequin_betas.py SEGMENTS
15
+ // (SMPL joint pairs there). Upper arm: the SMPL spine3 -> shoulder span maps to
16
+ // Spine2 -> Arm on the rig, the same visible shoulder reach.
17
+ export const RIG_SEGMENTS = {
18
+ thigh_left: ["LeftUpLeg", "LeftLeg"], thigh_right: ["RightUpLeg", "RightLeg"],
19
+ shin_left: ["LeftLeg", "LeftFoot"], shin_right: ["RightLeg", "RightFoot"],
20
+ foot_left: ["LeftFoot", "LeftToeBase"], foot_right: ["RightFoot", "RightToeBase"],
21
+ pelvis_width: ["LeftUpLeg", "RightUpLeg"], spine_length: ["Hips", "Spine2"],
22
+ upper_arm_left: ["Spine2", "LeftArm"], upper_arm_right: ["Spine2", "RightArm"],
23
+ forearm_left: ["LeftForeArm", "LeftHand"], forearm_right: ["RightForeArm", "RightHand"],
24
+ shoulder_width: ["LeftArm", "RightArm"],
25
+ };
26
+
27
+ export function ybotTargets(model = "y-bot-tpose") {
28
+ const path = ["public", "dist"].map((d) => join(ROOT, d, "models", `${model}.fbx`)).find(existsSync);
29
+ if (!path) throw new Error(`character model ${model}.fbx not found under public/ or dist/models`);
30
+ const bytes = readFileSync(path);
31
+ const rig = new FBXLoader().parse(bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength), "");
32
+ rig.scale.setScalar(0.01);
33
+ rig.updateMatrixWorld(true);
34
+ const bones = new Map();
35
+ rig.traverse((o) => { if (o.isBone) bones.set(o.name.replace(/^mixamorig:?/, ""), o); });
36
+ const at = (name) => {
37
+ const bone = bones.get(name);
38
+ if (!bone) throw new Error(`${model}: no bone ${name}`);
39
+ return new THREE.Vector3().setFromMatrixPosition(bone.matrixWorld);
40
+ };
41
+ const targets = Object.fromEntries(Object.entries(RIG_SEGMENTS).map(([name, [a, b]]) => [name, at(a).distanceTo(at(b))]));
42
+ // Standing height: the skinned mesh's rest bounding box (sole to crown).
43
+ const box = new THREE.Box3();
44
+ rig.traverse((o) => { if (o.isMesh) { o.geometry.computeBoundingBox(); box.union(o.geometry.boundingBox.clone().applyMatrix4(o.matrixWorld)); } });
45
+ targets.total_height = box.max.y - box.min.y;
46
+ return { schema: "obs.ybot-targets.v1", model: path.slice(ROOT.length + 1), scale: 0.01, pose: "FBX rest (T) pose", targets };
47
+ }
48
+
49
+ if (import.meta.url === `file://${process.argv[1]}`) console.log(JSON.stringify(ybotTargets(process.argv[2]), null, 1));