cozyclay 1.2.0 → 1.3.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 (72) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +31 -0
  3. package/THIRD_PARTY_NOTICES.md +37 -1
  4. package/bin/cozyclay.mjs +51 -2
  5. package/dist/ai-camera-control/index.html +406 -0
  6. package/dist/app/index.html +5 -5
  7. package/dist/assets/app-B3U5aut1.js +4811 -0
  8. package/dist/assets/app-BrRF0wso.css +1 -0
  9. package/dist/assets/vision_bundle-jFkh-fIS.js +41 -0
  10. package/dist/fonts/InstrumentSerif-OFL.txt +93 -0
  11. package/dist/fonts/Inter-OFL.txt +92 -0
  12. package/dist/fonts/README.md +15 -0
  13. package/dist/index.html +60 -16
  14. package/dist/sitemap.xml +7 -1
  15. package/mcp/LIVE-PROTOCOL.md +63 -0
  16. package/mcp/README.md +142 -0
  17. package/mcp/ardy-prompts.mjs +170 -0
  18. package/mcp/live-hub.mjs +105 -0
  19. package/mcp/package.json +24 -0
  20. package/mcp/server.mjs +1394 -0
  21. package/package.json +122 -90
  22. package/src/App.jsx +2957 -514
  23. package/src/ardy/cskel27.js +7 -2
  24. package/src/ardy/ik.js +25 -15
  25. package/src/ardy/npz.js +64 -3
  26. package/src/ardy/playback.js +31 -1
  27. package/src/ardy/prompt-clips.js +7 -2
  28. package/src/ardy/retime.js +211 -0
  29. package/src/ardy/timeline-coordinates.js +13 -0
  30. package/src/ardy/timeline.jsx +124 -5
  31. package/src/ardy/to-cskel27.js +34 -12
  32. package/src/ardy/trim.js +33 -0
  33. package/src/asset-pane.jsx +36 -0
  34. package/src/dualview.jsx +14 -8
  35. package/src/hierarchy-model.js +95 -13
  36. package/src/hierarchy-panel.jsx +139 -6
  37. package/src/live-control.js +122 -0
  38. package/src/matte-editor.js +543 -0
  39. package/src/matte.js +503 -0
  40. package/src/multimodel-ingest.js +344 -0
  41. package/src/object-gizmo.jsx +43 -15
  42. package/src/planview.jsx +43 -32
  43. package/src/pose-extract/detector.js +75 -0
  44. package/src/pose-extract/index.js +3 -0
  45. package/src/pose-extract/take.js +87 -0
  46. package/src/pose-extract/video-frames.js +91 -0
  47. package/src/pose-thumbs.js +152 -0
  48. package/src/posestudio.jsx +361 -11
  49. package/src/project-browser.jsx +135 -0
  50. package/src/project.js +289 -0
  51. package/src/props.jsx +69 -3
  52. package/src/room.jsx +14 -35
  53. package/src/scene-asset-cache.js +125 -0
  54. package/src/scene-assets.js +288 -0
  55. package/src/scene-objects.js +245 -7
  56. package/src/scenes.js +207 -26
  57. package/src/shot-authoring.js +55 -13
  58. package/src/styles.css +1296 -129
  59. package/tools/ardy/BRIDGE.md +3 -2
  60. package/tools/ardy/README.md +9 -5
  61. package/tools/ardy/bridge.mjs +57 -1
  62. package/tools/ardy/bvh-cskel27.mjs +1209 -0
  63. package/tools/ardy/cclay_constrained_generate.py +123 -11
  64. package/tools/ardy/cclay_sequence_generate.py +49 -0
  65. package/tools/ardy/extract.mjs +367 -0
  66. package/tools/ardy/footage.mjs +462 -0
  67. package/tools/ardy/npz.mjs +74 -9
  68. package/tools/ardy/run-on-box.sh +25 -0
  69. package/tools/ardy/run-sequence-on-box.sh +15 -0
  70. package/tools/ardy/runners/remote.mjs +8 -2
  71. package/dist/assets/app-Cgpk2hwX.js +0 -4803
  72. package/dist/assets/app-DgZvaAE1.css +0 -1
@@ -158,8 +158,9 @@ ascending and are converted independently with the floor-aligned canonical
158
158
  CoreSkeleton27 reference. No user-selected motion supplies root height.
159
159
  `run-on-box.sh` passes every entry as a repeated
160
160
  `--pose-from <npz> 0 <frame>` constraint. The remote
161
- `FullBodyConstraintSet` pins joint positions, root X/Y/Z, and heading at that
162
- frame.
161
+ the CozyClay full-body constraint pins joint positions, authored joint
162
+ rotations, root X/Y/Z, and heading at that frame. The pose npz carries the
163
+ authored rotation indices so identity-filled Core joints are not overconstrained.
163
164
 
164
165
  `segments` is optional. When present it must contain 2..64 contiguous,
165
166
  non-overlapping ranges covering frame 0 through `duration * 20`; each range
@@ -107,7 +107,10 @@ per-joint local rotations, built from the CozyClay basis quaternions via
107
107
  armature-space rest rotation — see CozyClay `motion_retarget.py` /
108
108
  `motion_constraints.py`). The src-frame is range-checked against the npz by
109
109
  the generator remotely, so an out-of-range `--src-frame` dies on the box
110
- with a clear message rather than silently.
110
+ with a clear message rather than silently. CozyClay-authored pose files also
111
+ carry `rotation_constraint_indices`: only those rotations are observed by the
112
+ model, while the identity-filled Core joints remain free. An ordinary motion
113
+ npz without that member constrains every joint rotation.
111
114
 
112
115
  ## The generation grammar
113
116
 
@@ -117,10 +120,11 @@ The raw generator flag for a full-body pose constraint is:
117
120
  --pose-from <src-npz> <src-frame> <dst-frame>
118
121
  ```
119
122
 
120
- It copies **every joint's** pose from `<src-npz>` at `<src-frame>` and pins
121
- it at `<dst-frame>` of the new clip. It is repeatable, requires `--base`,
122
- and works for poses no end-effector constraint can express (sitting, lying,
123
- reaching). The clip is `int(duration * 20)` frames at ARDY's 20 fps, and
123
+ It pins every joint position plus the selected joint rotations from
124
+ `<src-npz>` at `<src-frame>` onto `<dst-frame>` of the new clip. It is
125
+ repeatable and works for poses no end-effector constraint can express
126
+ (sitting, lying, reaching). The clip is `int(duration * 20)` frames at
127
+ ARDY's 20 fps, and
124
128
  `dst-frame` must satisfy `0 <= dst-frame < duration * 20`; the generator
125
129
  also rejects clips under 3 frames.
126
130
 
@@ -43,6 +43,8 @@ import { decodeMotionNpz } from "../../src/ardy/npz.js";
43
43
  import { motionArraysToNpzMembers, replaceMotionSegment, writeNpz } from "./npz.mjs";
44
44
  import { globalChildren, killGroup, runStreaming, track } from "./runners/proc.mjs";
45
45
  import { createRunner } from "./runners/index.mjs";
46
+ import { footagePath, handleFootage, serveFootage } from "./footage.mjs";
47
+ import { handleExtract } from "./extract.mjs";
46
48
  const HERE = dirname(fileURLToPath(import.meta.url));
47
49
  const REPO = resolve(HERE, "../..");
48
50
  const OUT_DIR = join(HERE, "out");
@@ -187,6 +189,13 @@ function validateGenerate(body) {
187
189
  if (body.prompt.length > PROMPT_MAX_CHARS) return `field 'prompt' is ${body.prompt.length} chars; the cap is ${PROMPT_MAX_CHARS}`;
188
190
  if (typeof body.duration !== "number" || !Number.isFinite(body.duration)) return `field 'duration' must be a finite number`;
189
191
  if (body.duration < DURATION_MIN || body.duration > DURATION_MAX) return `field 'duration' must be in ${DURATION_MIN}..${DURATION_MAX} seconds`;
192
+ // Optional post-processing knobs, forwarded to the box generators
193
+ // (ARDY defaults: root margin 0.04 m, contact threshold 0.5).
194
+ if (body.rootMargin !== undefined && (typeof body.rootMargin !== "number" || !Number.isFinite(body.rootMargin) || body.rootMargin < 0 || body.rootMargin > 1)) return `field 'rootMargin' must be a number in 0..1 (meters)`;
195
+ if (body.contactThreshold !== undefined && (typeof body.contactThreshold !== "number" || !Number.isFinite(body.contactThreshold) || body.contactThreshold < 0 || body.contactThreshold > 1)) return `field 'contactThreshold' must be a number in 0..1`;
196
+ // Optional history crop (per autoregressive step), forwarded to the box
197
+ // generators; the box enforces its own token-size multiple.
198
+ if (body.historyFrames !== undefined && (!Number.isInteger(body.historyFrames) || body.historyFrames <= 0 || body.historyFrames > 400)) return `field 'historyFrames' must be an integer in 1..400`;
190
199
  const clipFrames = Math.floor(body.duration * FPS);
191
200
  if (clipFrames < 3) return `field 'duration' yields fewer than 3 frames`;
192
201
 
@@ -772,6 +781,9 @@ async function handleGenerate(req, res) {
772
781
  waypoints: body.waypoints,
773
782
  seed: body.seed,
774
783
  cpu: body.cpu,
784
+ rootMargin: body.rootMargin,
785
+ contactThreshold: body.contactThreshold,
786
+ historyFrames: body.historyFrames,
775
787
  output: outNpzPath,
776
788
  });
777
789
  sendStatus(
@@ -838,6 +850,9 @@ async function handleGenerate(req, res) {
838
850
  seed: body.seed,
839
851
  cpu: body.cpu,
840
852
  waypoints: body.waypoints,
853
+ rootMargin: body.rootMargin,
854
+ contactThreshold: body.contactThreshold,
855
+ historyFrames: body.historyFrames,
841
856
  output: outputPath,
842
857
  });
843
858
  sendStatus(`[bridge] generating frames ${segment.startFrame}..${segment.endFrame - 1}`);
@@ -1084,11 +1099,52 @@ const server = createServer((req, res) => {
1084
1099
  log(serveMotion(req, res, pathname));
1085
1100
  return;
1086
1101
  }
1102
+ if (pathname === "/ardy/footage" && req.method === "POST") {
1103
+ handleFootage(req, res, (request) => readBody(request, MAX_BODY_BYTES)).catch((err) => {
1104
+ if (!res.headersSent) sendJson(res, 500, { ok: false, reason: `internal error: ${err.message}` });
1105
+ else {
1106
+ try {
1107
+ res.end();
1108
+ } catch {
1109
+ /* socket gone */
1110
+ }
1111
+ }
1112
+ console.error(`[bridge] ${req.method} ${pathname} threw: ${err.stack || err}`);
1113
+ log(500);
1114
+ });
1115
+ return;
1116
+ }
1117
+ if (/^\/ardy\/footage\//.test(pathname) && req.method === "GET") {
1118
+ log(serveFootage(req, res, pathname));
1119
+ return;
1120
+ }
1121
+ if (pathname === "/ardy/extract" && req.method === "POST") {
1122
+ handleExtract(req, res, {
1123
+ readBody: (request) => readBody(request, MAX_BODY_BYTES),
1124
+ footagePath,
1125
+ registerMotion,
1126
+ }).catch((err) => {
1127
+ if (!res.headersSent) sendJson(res, 500, { ok: false, reason: `internal error: ${err.message}` });
1128
+ else {
1129
+ try {
1130
+ res.end();
1131
+ } catch {
1132
+ /* socket gone */
1133
+ }
1134
+ }
1135
+ console.error(`[bridge] ${req.method} ${pathname} threw: ${err.stack || err}`);
1136
+ log(500);
1137
+ });
1138
+ return;
1139
+ }
1087
1140
  if (
1088
1141
  pathname === "/ardy/health" ||
1089
1142
  pathname === "/ardy/bases" ||
1090
1143
  pathname === "/ardy/generate" ||
1091
- /^\/ardy\/motions\//.test(pathname)
1144
+ pathname === "/ardy/footage" ||
1145
+ pathname === "/ardy/extract" ||
1146
+ /^\/ardy\/motions\//.test(pathname) ||
1147
+ /^\/ardy\/footage\//.test(pathname)
1092
1148
  ) {
1093
1149
  sendJson(res, 405, { ok: false, reason: `method ${req.method} not allowed on ${pathname}` });
1094
1150
  log(405);