cozyclay 1.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.
- package/LICENSE +674 -0
- package/README.md +109 -0
- package/THIRD_PARTY_NOTICES.md +29 -0
- package/bin/cozyclay.mjs +194 -0
- package/dist/ardy/cskel27-rest.json +557 -0
- package/dist/assets/basis_transcoder-VXdx5NbI.wasm +0 -0
- package/dist/assets/basis_transcoder-o4Hde_L7.js +19 -0
- package/dist/assets/draco_decoder-C32yEggz.wasm +0 -0
- package/dist/assets/draco_decoder-Z1_iN-Ht.wasm +0 -0
- package/dist/assets/draco_decoder-fzg4nYZr.js +34 -0
- package/dist/assets/draco_wasm_wrapper-DxJM36Ib.js +117 -0
- package/dist/assets/draco_wasm_wrapper-fZCQGLGb.js +116 -0
- package/dist/assets/index-BW_S1YRy.js +4395 -0
- package/dist/assets/index-nRmmJgap.css +1 -0
- package/dist/demo/walk-then-stop.npz +0 -0
- package/dist/fonts/instrument-serif-italic-latin.woff2 +0 -0
- package/dist/fonts/instrument-serif-latin.woff2 +0 -0
- package/dist/fonts/inter-latin.woff2 +0 -0
- package/dist/index.html +13 -0
- package/dist/models/x-bot-tpose.fbx +0 -0
- package/dist/models/y-bot-tpose.fbx +0 -0
- package/package.json +80 -0
- package/src/App.jsx +3840 -0
- package/src/ardy/client.js +140 -0
- package/src/ardy/convert.js +313 -0
- package/src/ardy/cskel27-neutral.js +39 -0
- package/src/ardy/cskel27.js +68 -0
- package/src/ardy/export.js +157 -0
- package/src/ardy/ik.js +610 -0
- package/src/ardy/npz.js +520 -0
- package/src/ardy/playback.js +414 -0
- package/src/ardy/prompt-clips.js +16 -0
- package/src/ardy/timeline-coordinates.js +17 -0
- package/src/ardy/timeline-resize.js +11 -0
- package/src/ardy/timeline.jsx +723 -0
- package/src/ardy/to-cskel27.js +205 -0
- package/src/ardy/waypoints.js +364 -0
- package/src/camera-follow.js +366 -0
- package/src/camera-move.js +286 -0
- package/src/controls.jsx +245 -0
- package/src/dualview.jsx +305 -0
- package/src/hierarchy-model.js +77 -0
- package/src/hierarchy-panel.jsx +365 -0
- package/src/history.js +90 -0
- package/src/main.jsx +10 -0
- package/src/object-catalog.jsx +90 -0
- package/src/object-gizmo.jsx +755 -0
- package/src/planview.jsx +653 -0
- package/src/poses.js +424 -0
- package/src/posestudio.jsx +811 -0
- package/src/props.jsx +302 -0
- package/src/room.jsx +64 -0
- package/src/scene-history.js +125 -0
- package/src/scene-objects.js +420 -0
- package/src/shot-authoring.js +113 -0
- package/src/shot.js +239 -0
- package/src/styles.css +5639 -0
- package/src/ui.jsx +391 -0
- package/src/use-render-activity.js +96 -0
- package/tools/ardy/BRIDGE.md +255 -0
- package/tools/ardy/README.md +136 -0
- package/tools/ardy/__pycache__/cclay_sequence_generate.cpython-313.pyc +0 -0
- package/tools/ardy/bridge.mjs +1427 -0
- package/tools/ardy/cclay_motion_edit.py +445 -0
- package/tools/ardy/cclay_sequence_generate.py +595 -0
- package/tools/ardy/dump-npz.py +205 -0
- package/tools/ardy/extract-rest.mjs +299 -0
- package/tools/ardy/npz.mjs +335 -0
- package/tools/ardy/out/gen-1786443326924-c6019e-generated.npz +0 -0
- package/tools/ardy/out/gen-1786443609325-0053db-generated.npz +0 -0
- package/tools/ardy/out/gen-1786443835628-749ed1-generated.npz +0 -0
- package/tools/ardy/out/gen-1786462605247-03cb19-generated.npz +0 -0
- package/tools/ardy/pose-to-npz.mjs +106 -0
- package/tools/ardy/run-edit-on-box.sh +73 -0
- package/tools/ardy/run-on-box.sh +568 -0
- package/tools/ardy/run-sequence-on-box.sh +162 -0
- package/tools/ardy/visual-qa.mjs +188 -0
- package/tools/ardy/vq-car.mjs +52 -0
- package/tools/dev-full.mjs +29 -0
- package/tools/process-supervisor.mjs +63 -0
- package/tools/qa-browser.mjs +84 -0
- package/tools/qa-crop3.mjs +22 -0
- package/tools/qa-playview.mjs +49 -0
- package/tools/qa-screenshot.mjs +24 -0
- package/tools/qa-visual.mjs +51 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# ARDY dev bridge (`tools/ardy/bridge.mjs`)
|
|
2
|
+
|
|
3
|
+
A dependency-free Node (`node:http` only) sidecar that makes the full ARDY
|
|
4
|
+
loop drivable from the CozyClay UI without turning CozyClay into a backend
|
|
5
|
+
app.
|
|
6
|
+
|
|
7
|
+
CozyClay stays a static SPA: `vite build` emits a `dist/` that serves from
|
|
8
|
+
anywhere with no server. The box work — pose → npz conversion and the remote
|
|
9
|
+
constrained generation — cannot run in a browser, so the bridge exposes it as
|
|
10
|
+
four HTTP endpoints on **127.0.0.1:5181** and the Vite dev server proxies
|
|
11
|
+
`/ardy` to it. The bridge is **optional and dev-only**: when it is not
|
|
12
|
+
running, the app works exactly as before with the generate affordance visibly
|
|
13
|
+
unavailable, and the production build never depends on it.
|
|
14
|
+
|
|
15
|
+
## Start / stop
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
node tools/ardy/bridge.mjs # listens on 127.0.0.1:5181
|
|
19
|
+
node tools/ardy/bridge.mjs --port 5182 # or COZYCLAY_BRIDGE_PORT=5182
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
On startup it logs the bound address. Ctrl-C kills any in-flight generation
|
|
23
|
+
process group (the ssh session is closed, so nothing is orphaned on the box).
|
|
24
|
+
|
|
25
|
+
The bridge reads the same env vars `run-on-box.sh` reads. The remote SSH host
|
|
26
|
+
has no public default and must be configured by the operator:
|
|
27
|
+
|
|
28
|
+
| env | default | meaning |
|
|
29
|
+
| --- | --- | --- |
|
|
30
|
+
| `COZYCLAY_BRIDGE_PORT` | `5181` | listen port (loopback only) |
|
|
31
|
+
| `CCLAY_ARDY_HOST` | required | ssh destination for the ARDY host |
|
|
32
|
+
| `CCLAY_ARDY_REPO` | `$HOME/ardy` | ARDY checkout on the box |
|
|
33
|
+
| `CCLAY_ARDY_VENV` | `~/ardy/.venv-cuda/bin/python` | generator venv python on the box |
|
|
34
|
+
| `CCLAY_ARDY_ENCODER_URL` | `http://127.0.0.1:9550/` | text-encoder service |
|
|
35
|
+
|
|
36
|
+
The box carries two venvs and they are **not interchangeable**: the CPU
|
|
37
|
+
`.venv` runs the text-encoder service and is used for the numpy-only read
|
|
38
|
+
steps (frame counts, reference dumps); the `.venv-cuda` venv runs the motion
|
|
39
|
+
generator and is what the device probe and generation use.
|
|
40
|
+
|
|
41
|
+
## Endpoints
|
|
42
|
+
|
|
43
|
+
### `GET /ardy/health`
|
|
44
|
+
|
|
45
|
+
Probes the box in one ssh round trip: the host answering, the encoder URL's
|
|
46
|
+
HTTP status, and the device the generator venv would pick (the exact probe
|
|
47
|
+
line `run-on-box.sh` uses, under the same env).
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
200 {"ok":true,"host":"user@ardy-host","encoder":200,"device":"cuda:0"}
|
|
51
|
+
503 {"ok":false,"reason":"<human readable>"}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Successes and failures are cached for 5 s so the UI's health polling does not
|
|
55
|
+
hammer ssh.
|
|
56
|
+
|
|
57
|
+
### `GET /ardy/bases`
|
|
58
|
+
|
|
59
|
+
Lists `~/ardy/outputs/*.npz` and `~/ardy/outputs/omb/*.npz` on the box with
|
|
60
|
+
real frame counts, read in a single ssh call (one `np.load(...)['posed_joints'].shape[0]`
|
|
61
|
+
per file, via a python heredoc on the box — never one ssh call per file).
|
|
62
|
+
Files that fail to load are skipped with a note on the bridge's stderr.
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
200 {"bases":[{"id":"...","path":"outputs/omb/....npz","frames":80}, ...]}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Cached for 120 s; the list changes rarely. If the same name exists under both
|
|
69
|
+
`outputs/` and `outputs/omb/`, both entries are listed and generation picks
|
|
70
|
+
the first — the same lookup order `run-on-box.sh` uses.
|
|
71
|
+
|
|
72
|
+
### `POST /ardy/generate`
|
|
73
|
+
|
|
74
|
+
Multi-block autoregressive request:
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"prompt": "a person walking",
|
|
79
|
+
"duration": 4,
|
|
80
|
+
"posePin": false,
|
|
81
|
+
"segments": [
|
|
82
|
+
{"startFrame": 0, "endFrame": 40, "prompt": "a person walking"},
|
|
83
|
+
{"startFrame": 40, "endFrame": 80, "prompt": "the person stops"}
|
|
84
|
+
],
|
|
85
|
+
"seed": 2
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
For a single-prompt refinement request, `poses` is the source of truth. Each entry carries an authored
|
|
90
|
+
full-body pose plus ARDY-space root XYZ in metres. Frames must be strictly
|
|
91
|
+
ascending and are converted independently with the floor-aligned canonical
|
|
92
|
+
CoreSkeleton27 reference. No user-selected motion supplies root height.
|
|
93
|
+
`run-on-box.sh` passes every entry as a repeated
|
|
94
|
+
`--pose-from <npz> 0 <frame>` constraint. The remote
|
|
95
|
+
`FullBodyConstraintSet` pins joint positions, root X/Y/Z, and heading at that
|
|
96
|
+
frame.
|
|
97
|
+
|
|
98
|
+
`segments` is optional. When present it must contain 2..64 contiguous,
|
|
99
|
+
non-overlapping ranges covering frame 0 through `duration * 20`; each range
|
|
100
|
+
has its own non-empty prompt. The bridge passes the complete schedule to
|
|
101
|
+
`cclay_sequence_generate.py` in one remote process. ARDY stays loaded and
|
|
102
|
+
each block receives the previous block's normalized motion tail through
|
|
103
|
+
`init_history_sequence`; no per-block process restart, NPZ concatenation, or
|
|
104
|
+
crossfade occurs. Segment mode requires `posePin:false`.
|
|
105
|
+
|
|
106
|
+
`segments` + `waypoints` together run the sequence generator with a root
|
|
107
|
+
path: the Root2D constraint set is built once over the whole rollout
|
|
108
|
+
(rollout-global frames, origin facing +Z) and each chained call receives its
|
|
109
|
+
own slice with history frames zeroed — the interactive demo's streaming
|
|
110
|
+
pattern. The 10 s trained window then binds each prompt block (the bridge
|
|
111
|
+
rejects any block longer than that), not the total clip, so a root path can
|
|
112
|
+
ride a rollout longer than 10 s. Without `segments`, `waypoints` still run
|
|
113
|
+
the one-shot constrained generator and the whole clip must fit 10 s.
|
|
114
|
+
|
|
115
|
+
A single-prompt rough generation sends `posePin:false` and no `segments`; it
|
|
116
|
+
remains a plain text-to-motion run. Once that motion is loaded, CozyClay can
|
|
117
|
+
send the composite motion+IK poses and roots as `poses` for constrained
|
|
118
|
+
single-prompt regeneration.
|
|
119
|
+
The legacy single `pose` + `dstFrame` form remains accepted and is normalized
|
|
120
|
+
to one `poses` entry.
|
|
121
|
+
|
|
122
|
+
For a loaded multi-block motion with authored IK keys, CozyClay sends
|
|
123
|
+
`motionEdit` instead of replaying the whole prompt schedule. The edit owns a
|
|
124
|
+
source motion URL, an editable interval, history/future context lengths, and
|
|
125
|
+
sparse authored keys. Each key names only the IK tracks actually changed.
|
|
126
|
+
`cclay_motion_edit.py` supplies the preceding source frames through ARDY's
|
|
127
|
+
`init_history_sequence`, supplies following source frames as observed future
|
|
128
|
+
context, and creates position/rotation masks only for the named joints.
|
|
129
|
+
Root position/heading use a separate track: body-only edits preserve the
|
|
130
|
+
source root exactly, while hips edits author a smooth root offset. Contact
|
|
131
|
+
arrays stay source-owned unless a foot/leg track was edited.
|
|
132
|
+
|
|
133
|
+
Pending browser IK keys remain a non-destructive layer until this call
|
|
134
|
+
finishes and the returned motion loads. On success they are recorded as
|
|
135
|
+
committed edit metadata and removed from the pending IK map, preventing the
|
|
136
|
+
same correction from being applied again over the generated motion.
|
|
137
|
+
|
|
138
|
+
`regenerateSegments` remains accepted as a legacy bridge payload; the
|
|
139
|
+
CozyClay UI no longer uses it. This task intentionally does not remove that
|
|
140
|
+
old route.
|
|
141
|
+
|
|
142
|
+
`base` is optional legacy/debug input only. The CozyClay UI does not expose
|
|
143
|
+
it. Pose conversion uses the canonical neutral skeleton, while the shell uses
|
|
144
|
+
a hidden upright NPZ only to satisfy the current remote script's parser and
|
|
145
|
+
baseline report. That NPZ does not supply the authored pose root.
|
|
146
|
+
|
|
147
|
+
On success the response is NDJSON:
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
{"event":"status","message":"..."}
|
|
151
|
+
{"event":"report","report":{}}
|
|
152
|
+
{"event":"done","output":"<absolute path>","bytes":123,"motionUrl":"/ardy/motions/<run-id>"}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Validation rejects malformed roots, duplicate/out-of-range pose frames,
|
|
156
|
+
non-contiguous segments, empty prompts, invalid durations/seeds, and mixed
|
|
157
|
+
segment+waypoint requests with a 400 naming the offending field. Child process
|
|
158
|
+
groups are killed on disconnect. A `done` event is emitted only after the
|
|
159
|
+
requested output exists and its byte count matches the shell marker.
|
|
160
|
+
|
|
161
|
+
### `GET /ardy/motions/<run-id>`
|
|
162
|
+
|
|
163
|
+
Serves the exact npz produced by a successful generation. `<run-id>` is the
|
|
164
|
+
id embedded in the `done` event's `motionUrl` — it is a lookup key, never a
|
|
165
|
+
path. On success the response is `200` with
|
|
166
|
+
`Content-Type: application/octet-stream` and
|
|
167
|
+
`Content-Disposition: attachment; filename="gen-<ts>-<rand>-constrained.npz"`
|
|
168
|
+
(pose runs) or `"gen-<ts>-<rand>-generated.npz"` (`posePin: false` runs).
|
|
169
|
+
|
|
170
|
+
The bridge keeps an in-memory run-id → absolute path allowlist that is
|
|
171
|
+
populated **only after this process generated the file and verified it on
|
|
172
|
+
disk** (see the `done` verification above). Unknown, expired, or
|
|
173
|
+
never-this-process ids return `404` JSON; a path is never accepted from the
|
|
174
|
+
URL, query, or body. Served files are always under `tools/ardy/out/` — the
|
|
175
|
+
allowlist only ever holds paths the bridge itself joined there, and the
|
|
176
|
+
check is re-applied at serve time.
|
|
177
|
+
|
|
178
|
+
## Root coordinate convention (`--root-2d`)
|
|
179
|
+
|
|
180
|
+
ARDY is **Y-up**: the character stands on the X/Z ground plane, and root
|
|
181
|
+
positions are horizontal X and Z in **meters** relative to the ARDY origin
|
|
182
|
+
(Y is not constrained by root guidance). A path request carries **2..32
|
|
183
|
+
sparse keys** beginning at frame **0**, with strictly ascending frame
|
|
184
|
+
indices. CozyClay converts authored scene positions into clip-local X/Z and
|
|
185
|
+
uses `heading:null`, leaving facing free. Each key is forwarded to
|
|
186
|
+
`run-on-box.sh` as `--root-2d FRAME X Z none`; ARDY generates every
|
|
187
|
+
intermediate frame. The script `%q`-quotes every value into the remote
|
|
188
|
+
generator invocation.
|
|
189
|
+
|
|
190
|
+
## Failure semantics
|
|
191
|
+
|
|
192
|
+
- Before the stream starts (bad fields, unreachable box): plain JSON
|
|
193
|
+
`400`/`503` responses.
|
|
194
|
+
- After the stream starts: failures arrive as an `error` event, then the
|
|
195
|
+
stream ends.
|
|
196
|
+
- A client disconnect kills the detached child process group (SIGTERM, then
|
|
197
|
+
SIGKILL after 3 s) — the bash script and the ssh session die together, so
|
|
198
|
+
no remote generation is orphaned.
|
|
199
|
+
- `OPTIONS` preflight is answered with `204` and **no CORS headers**, so a
|
|
200
|
+
cross-origin browser preflight always fails; unknown paths return `404`
|
|
201
|
+
JSON; wrong methods on known paths return `405`.
|
|
202
|
+
|
|
203
|
+
## Security posture
|
|
204
|
+
|
|
205
|
+
- **Loopback only.** The server binds `127.0.0.1`; the host is not
|
|
206
|
+
configurable. This process shells out to a machine that can run GPU work,
|
|
207
|
+
so it must never be reachable from the network.
|
|
208
|
+
- **Browser access is same-origin proxy-only; there is no CORS.** The bridge
|
|
209
|
+
never sends `Access-Control-Allow-*` headers on any response (JSON, NDJSON,
|
|
210
|
+
binary, or `OPTIONS`), so the browser's same-origin policy is the
|
|
211
|
+
enforcement boundary: a page from any other origin — including an arbitrary
|
|
212
|
+
website the operator happens to have open — cannot read a response, and the
|
|
213
|
+
`POST /ardy/generate` preflight (JSON bodies are never "simple" requests)
|
|
214
|
+
is refused before it reaches the bridge. Loopback alone is insufficient:
|
|
215
|
+
binding `127.0.0.1` only keeps other *hosts* out, while a request from the
|
|
216
|
+
operator's own browser arrives from the operator's machine, on the loopback
|
|
217
|
+
interface, indistinguishable from a legitimate local call. The CozyClay UI
|
|
218
|
+
always talks to the bridge through the Vite dev server's same-origin
|
|
219
|
+
`/ardy` proxy (server-side forwarding, no browser CORS involved); direct
|
|
220
|
+
browser access from another origin is unsupported by design. `curl` and
|
|
221
|
+
other non-browser clients are unaffected — they do not enforce
|
|
222
|
+
same-origin policy.
|
|
223
|
+
- **argv arrays, never shell strings.** Request data (prompt, base, frames,
|
|
224
|
+
waypoints) is passed to `spawn` as separate argv entries; no request value
|
|
225
|
+
is ever interpolated into a shell string. The only remote shell strings are
|
|
226
|
+
built from the box's own listing (regex-whitelisted to
|
|
227
|
+
`outputs/(omb/)?[A-Za-z0-9._-]+\.npz`) or from operator env vars.
|
|
228
|
+
- **Everything is validated**: prompt length, duration/dstFrame ranges, base
|
|
229
|
+
whitelist, frame-zero root start bounds, body size cap, `seed`/`cpu` types.
|
|
230
|
+
- **ssh hardening**: `BatchMode` (never a password prompt), `ConnectTimeout`,
|
|
231
|
+
`ServerAlive*` — the same options `run-on-box.sh` uses.
|
|
232
|
+
- **No writes to `~/ardy`**: the dump script is copied to `/tmp` on the box
|
|
233
|
+
and removed afterwards; all artifacts land under the gitignored
|
|
234
|
+
`tools/ardy/out/`.
|
|
235
|
+
- **Served npz files are allowlisted, not addressed.** `GET /ardy/motions/<run-id>`
|
|
236
|
+
resolves the id against an in-memory map populated only after this process
|
|
237
|
+
generated and verified the file; a path never comes from the URL, query, or
|
|
238
|
+
body, and nothing outside `tools/ardy/out/` is ever served.
|
|
239
|
+
|
|
240
|
+
## Caching
|
|
241
|
+
|
|
242
|
+
| data | cache | why |
|
|
243
|
+
| --- | --- | --- |
|
|
244
|
+
| health | 5 s (success and failure) | the UI polls it; a dead box must not become an ssh stampede |
|
|
245
|
+
| bases | 120 s | the list changes rarely |
|
|
246
|
+
| reference dumps | on disk under `tools/ardy/out/refs/` | same base, reused across requests |
|
|
247
|
+
|
|
248
|
+
## Relationship to the rest of the repo
|
|
249
|
+
|
|
250
|
+
- `tools/ardy/pose-to-npz.mjs`, `tools/ardy/run-on-box.sh`,
|
|
251
|
+
`tools/ardy/dump-npz.py`, `src/ardy/*` and `test/ardy/*` are the verified,
|
|
252
|
+
reusable pieces; the bridge calls them, never reimplements them.
|
|
253
|
+
- The Vite dev server (`vite.config.js`) proxies `/ardy` to
|
|
254
|
+
`127.0.0.1:5181`; the production `dist/` build contains no proxy, no
|
|
255
|
+
server code, and no dependency on the bridge.
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# CozyClay -> ARDY pose bridge
|
|
2
|
+
|
|
3
|
+
`run-on-box.sh` pushes a synthetic pose `.npz` produced from a CozyClay pose
|
|
4
|
+
into the ARDY text-to-motion pipeline on the remote box, runs a constrained
|
|
5
|
+
generation pass that honours that pose, and pulls the resulting motion back.
|
|
6
|
+
This is the "long tail" of the bridge: the pose is authored in CozyClay,
|
|
7
|
+
converted to an ARDY motion npz locally, and this script is what actually
|
|
8
|
+
gets the box to generate with it.
|
|
9
|
+
|
|
10
|
+
## Remote ARDY host
|
|
11
|
+
|
|
12
|
+
The bridge expects an SSH-accessible machine where ARDY is already installed.
|
|
13
|
+
CozyClay deliberately does not prescribe a VPN, hostname scheme, cloud
|
|
14
|
+
provider, or network topology.
|
|
15
|
+
|
|
16
|
+
Configure the host explicitly before starting the bridge:
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
export CCLAY_ARDY_HOST="<ssh-user>@<ssh-host>"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Optional overrides:
|
|
23
|
+
|
|
24
|
+
- `CCLAY_ARDY_REPO` — ARDY checkout on the remote host (default `$HOME/ardy`)
|
|
25
|
+
- `CCLAY_ARDY_VENV` — generator Python (default `~/ardy/.venv-cuda/bin/python`)
|
|
26
|
+
- `CCLAY_ARDY_ENCODER_URL` — encoder URL as seen from the remote host
|
|
27
|
+
|
|
28
|
+
SSH must work non-interactively with `BatchMode=yes`. Hardware and device
|
|
29
|
+
selection are operator concerns; pass `--cpu` when CPU generation is desired.
|
|
30
|
+
|
|
31
|
+
## Where the motions live on the box
|
|
32
|
+
|
|
33
|
+
- Base (first-pass, unconstrained) motions: `~/ardy/outputs/*.npz` and
|
|
34
|
+
`~/ardy/outputs/omb/*.npz`. `run-on-box.sh` resolves a bare `--base` id
|
|
35
|
+
against `outputs/<id>.npz` first, then `outputs/omb/<id>.npz`; a bare
|
|
36
|
+
`<name>.npz` is treated the same with the suffix stripped; anything with a
|
|
37
|
+
`/` is used as a repo-relative or absolute path.
|
|
38
|
+
- The base npz must be at least as long as the requested clip — the
|
|
39
|
+
generator rejects a shorter base (`--base npz has N frames but the
|
|
40
|
+
requested clip is M`), so pick a base that covers `--duration`.
|
|
41
|
+
- Nothing is ever written into the checkout: the pose npz and the generated
|
|
42
|
+
output live under a fresh `mktemp -d` dir on the box (usually `/tmp`),
|
|
43
|
+
which an `EXIT` trap removes even on failure.
|
|
44
|
+
|
|
45
|
+
## The pose npz contract
|
|
46
|
+
|
|
47
|
+
`<pose.npz>` is an ARDY motion npz — the same format as a base motion — that
|
|
48
|
+
must carry `local_rot_mats` and `posed_joints`. One frame is enough: the
|
|
49
|
+
CozyClay pose is baked into `local_rot_mats[src_frame]` (the cskel27
|
|
50
|
+
per-joint local rotations, built from the CozyClay basis quaternions via
|
|
51
|
+
`basis = Rb^T @ L @ Rb` / `L = Rb @ basis @ Rb^T`, where `Rb` is the bone's
|
|
52
|
+
armature-space rest rotation — see CozyClay `motion_retarget.py` /
|
|
53
|
+
`motion_constraints.py`). The src-frame is range-checked against the npz by
|
|
54
|
+
the generator remotely, so an out-of-range `--src-frame` dies on the box
|
|
55
|
+
with a clear message rather than silently.
|
|
56
|
+
|
|
57
|
+
## The generation grammar
|
|
58
|
+
|
|
59
|
+
The raw generator flag for a full-body pose constraint is:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
--pose-from <src-npz> <src-frame> <dst-frame>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
It copies **every joint's** pose from `<src-npz>` at `<src-frame>` and pins
|
|
66
|
+
it at `<dst-frame>` of the new clip. It is repeatable, requires `--base`,
|
|
67
|
+
and works for poses no end-effector constraint can express (sitting, lying,
|
|
68
|
+
reaching). The clip is `int(duration * 20)` frames at ARDY's 20 fps, and
|
|
69
|
+
`dst-frame` must satisfy `0 <= dst-frame < duration * 20`; the generator
|
|
70
|
+
also rejects clips under 3 frames.
|
|
71
|
+
|
|
72
|
+
`run-on-box.sh` maps its arguments onto the generator one-to-one:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
run-on-box.sh <pose.npz> --base <motion-id|npz-path> --prompt "<prompt>" \
|
|
76
|
+
--duration <seconds> --dst-frame <N> [--src-frame <N>] [--seed <S>] \
|
|
77
|
+
[--output <local.npz>] [--dry-run]
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
which becomes, on the box (modulo the temp dir):
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
cd $HOME/ardy && ~/ardy/.venv-cuda/bin/python \
|
|
84
|
+
scripts/cclay_constrained_generate.py \
|
|
85
|
+
--prompt "<prompt>" --duration <seconds> \
|
|
86
|
+
--base <resolved-base> --output <tmp>/out \
|
|
87
|
+
--pose-from <tmp>/pose.npz <src-frame> <dst-frame> [--seed <S>]
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The CozyClay wrapper `cclay-ardy-generate` exposes the same feature through
|
|
91
|
+
its own grammar — `--constrain-pose <src-motion-id> <src-frame> <dst-frame>`
|
|
92
|
+
— where `<src-motion-id>` is a motion already staged in the CozyClay
|
|
93
|
+
project (`.cclay/motions/<id>.npz`) rather than a raw npz path. `run-on-box.sh`
|
|
94
|
+
exists for the CozyClay flow, where the pose source is a synthetic npz
|
|
95
|
+
produced by the conversion module and the base motion is a pre-existing
|
|
96
|
+
`~/ardy/outputs/*.npz`; both paths drive the same `--pose-from` mechanism.
|
|
97
|
+
|
|
98
|
+
## Preflight, safety, idempotence
|
|
99
|
+
|
|
100
|
+
Before anything is pushed, `run-on-box.sh` fails fast, cheapest check
|
|
101
|
+
first:
|
|
102
|
+
|
|
103
|
+
1. SSH reachability (`BatchMode` + `ConnectTimeout`, so a dead host or a
|
|
104
|
+
missing key errors in seconds, not minutes).
|
|
105
|
+
2. `~/ardy/.venv/bin/python` and `scripts/cclay_constrained_generate.py`
|
|
106
|
+
exist on the box (points at `sync-to-box --apply` when missing).
|
|
107
|
+
3. The base motion resolves to a file that exists on the box.
|
|
108
|
+
4. The device probe: the one-line torch check mirroring the generator's
|
|
109
|
+
device expression under the same environment, printed before launching.
|
|
110
|
+
|
|
111
|
+
`--dry-run` runs all of the above (they are reads only), prints the exact
|
|
112
|
+
push / remote-command / pull / cleanup steps, and exits without connecting
|
|
113
|
+
for the generation step.
|
|
114
|
+
|
|
115
|
+
Every expansion is quoted; `set -euo pipefail` is on; prompts and paths are
|
|
116
|
+
shell-quoted with `printf %q` so they survive the remote shell. The remote
|
|
117
|
+
temp dir is created with `mktemp -d`, removed by an `EXIT` trap (cleanup
|
|
118
|
+
failure never masks the real result), and guarded to be absolute before it
|
|
119
|
+
can reach `rm -rf`. Re-running the same command with the same `--output`
|
|
120
|
+
overwrites it — no state accumulates anywhere.
|
|
121
|
+
|
|
122
|
+
## Example
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
tools/ardy/run-on-box.sh /tmp/pose-shooting.npz \
|
|
126
|
+
--base a-person-runs-forward-0722151659 \
|
|
127
|
+
--prompt "a person sprints forward and raises both arms" \
|
|
128
|
+
--duration 5 --dst-frame 60 --seed 7 \
|
|
129
|
+
--output tools/ardy/out/sprint-pose.npz
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The generator's stdout passes through (device line, loaded model, per-frame
|
|
133
|
+
constraint lines, final JSON result), and the pulled npz lands at
|
|
134
|
+
`--output` (default `tools/ardy/out/<pose>-constrained.npz`, gitignored).
|
|
135
|
+
The result is a full ARDY motion npz and can be used like any other
|
|
136
|
+
generated motion.
|
|
Binary file
|