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.
Files changed (85) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +109 -0
  3. package/THIRD_PARTY_NOTICES.md +29 -0
  4. package/bin/cozyclay.mjs +194 -0
  5. package/dist/ardy/cskel27-rest.json +557 -0
  6. package/dist/assets/basis_transcoder-VXdx5NbI.wasm +0 -0
  7. package/dist/assets/basis_transcoder-o4Hde_L7.js +19 -0
  8. package/dist/assets/draco_decoder-C32yEggz.wasm +0 -0
  9. package/dist/assets/draco_decoder-Z1_iN-Ht.wasm +0 -0
  10. package/dist/assets/draco_decoder-fzg4nYZr.js +34 -0
  11. package/dist/assets/draco_wasm_wrapper-DxJM36Ib.js +117 -0
  12. package/dist/assets/draco_wasm_wrapper-fZCQGLGb.js +116 -0
  13. package/dist/assets/index-BW_S1YRy.js +4395 -0
  14. package/dist/assets/index-nRmmJgap.css +1 -0
  15. package/dist/demo/walk-then-stop.npz +0 -0
  16. package/dist/fonts/instrument-serif-italic-latin.woff2 +0 -0
  17. package/dist/fonts/instrument-serif-latin.woff2 +0 -0
  18. package/dist/fonts/inter-latin.woff2 +0 -0
  19. package/dist/index.html +13 -0
  20. package/dist/models/x-bot-tpose.fbx +0 -0
  21. package/dist/models/y-bot-tpose.fbx +0 -0
  22. package/package.json +80 -0
  23. package/src/App.jsx +3840 -0
  24. package/src/ardy/client.js +140 -0
  25. package/src/ardy/convert.js +313 -0
  26. package/src/ardy/cskel27-neutral.js +39 -0
  27. package/src/ardy/cskel27.js +68 -0
  28. package/src/ardy/export.js +157 -0
  29. package/src/ardy/ik.js +610 -0
  30. package/src/ardy/npz.js +520 -0
  31. package/src/ardy/playback.js +414 -0
  32. package/src/ardy/prompt-clips.js +16 -0
  33. package/src/ardy/timeline-coordinates.js +17 -0
  34. package/src/ardy/timeline-resize.js +11 -0
  35. package/src/ardy/timeline.jsx +723 -0
  36. package/src/ardy/to-cskel27.js +205 -0
  37. package/src/ardy/waypoints.js +364 -0
  38. package/src/camera-follow.js +366 -0
  39. package/src/camera-move.js +286 -0
  40. package/src/controls.jsx +245 -0
  41. package/src/dualview.jsx +305 -0
  42. package/src/hierarchy-model.js +77 -0
  43. package/src/hierarchy-panel.jsx +365 -0
  44. package/src/history.js +90 -0
  45. package/src/main.jsx +10 -0
  46. package/src/object-catalog.jsx +90 -0
  47. package/src/object-gizmo.jsx +755 -0
  48. package/src/planview.jsx +653 -0
  49. package/src/poses.js +424 -0
  50. package/src/posestudio.jsx +811 -0
  51. package/src/props.jsx +302 -0
  52. package/src/room.jsx +64 -0
  53. package/src/scene-history.js +125 -0
  54. package/src/scene-objects.js +420 -0
  55. package/src/shot-authoring.js +113 -0
  56. package/src/shot.js +239 -0
  57. package/src/styles.css +5639 -0
  58. package/src/ui.jsx +391 -0
  59. package/src/use-render-activity.js +96 -0
  60. package/tools/ardy/BRIDGE.md +255 -0
  61. package/tools/ardy/README.md +136 -0
  62. package/tools/ardy/__pycache__/cclay_sequence_generate.cpython-313.pyc +0 -0
  63. package/tools/ardy/bridge.mjs +1427 -0
  64. package/tools/ardy/cclay_motion_edit.py +445 -0
  65. package/tools/ardy/cclay_sequence_generate.py +595 -0
  66. package/tools/ardy/dump-npz.py +205 -0
  67. package/tools/ardy/extract-rest.mjs +299 -0
  68. package/tools/ardy/npz.mjs +335 -0
  69. package/tools/ardy/out/gen-1786443326924-c6019e-generated.npz +0 -0
  70. package/tools/ardy/out/gen-1786443609325-0053db-generated.npz +0 -0
  71. package/tools/ardy/out/gen-1786443835628-749ed1-generated.npz +0 -0
  72. package/tools/ardy/out/gen-1786462605247-03cb19-generated.npz +0 -0
  73. package/tools/ardy/pose-to-npz.mjs +106 -0
  74. package/tools/ardy/run-edit-on-box.sh +73 -0
  75. package/tools/ardy/run-on-box.sh +568 -0
  76. package/tools/ardy/run-sequence-on-box.sh +162 -0
  77. package/tools/ardy/visual-qa.mjs +188 -0
  78. package/tools/ardy/vq-car.mjs +52 -0
  79. package/tools/dev-full.mjs +29 -0
  80. package/tools/process-supervisor.mjs +63 -0
  81. package/tools/qa-browser.mjs +84 -0
  82. package/tools/qa-crop3.mjs +22 -0
  83. package/tools/qa-playview.mjs +49 -0
  84. package/tools/qa-screenshot.mjs +24 -0
  85. package/tools/qa-visual.mjs +51 -0
package/README.md ADDED
@@ -0,0 +1,109 @@
1
+ # CozyClay
2
+
3
+ CozyClay is a browser-based 3D staging studio built with Three.js and React Three Fiber. Block a scene, pose characters, sequence motion prompts on a timeline, and preview generated motion — in one local workspace that handles like the Unity Editor.
4
+
5
+ **[Open the live demo](https://had0yun.github.io/CozyClay/)** — the studio itself, running in your browser. Nothing to install. It comes seeded with a pre-generated motion clip so you can scrub the timeline, drive the cameras and draw a dolly rail straight away; generating new motion needs a local ARDY machine, so that part is off.
6
+
7
+ ![CozyClay browser-based 3D staging studio](docs/images/cozyclay-studio.png)
8
+
9
+ ## Demo
10
+
11
+ ![CozyClay motion, staging, and camera workflow](docs/images/cozyclay-demo.gif)
12
+
13
+ CozyClay can connect to [NVIDIA ARDY](https://github.com/nv-tlabs/ardy) for motion generation. ARDY is a separate third-party project owned and maintained by NVIDIA; it is not included in this repository, and CozyClay is not affiliated with or endorsed by NVIDIA.
14
+
15
+ ## Requirements
16
+
17
+ - Node.js 22 or newer
18
+ - npm, or bun
19
+ - A Chromium-based browser
20
+ - Optional: an SSH-accessible NVIDIA machine running ARDY, for motion generation
21
+
22
+ ## Quick start
23
+
24
+ ```bash
25
+ npx cozyclay
26
+ # or
27
+ bunx cozyclay
28
+ ```
29
+
30
+ That downloads the built studio and opens it at `http://127.0.0.1:5180`. There is
31
+ nothing to compile and no dependency tree to install. Useful flags:
32
+ `--port 5200`, `--no-open`, `--no-ardy`.
33
+
34
+ Motion generation is off until you point it at a machine that can run it:
35
+
36
+ ```bash
37
+ CCLAY_ARDY_HOST=user@your-gpu-box npx cozyclay
38
+ ```
39
+
40
+ Everything else, staging through camera work and playback, runs without it.
41
+
42
+ ### From a clone
43
+
44
+ ```bash
45
+ git clone https://github.com/HaD0Yun/CozyClay.git
46
+ cd CozyClay
47
+ npm install
48
+ npm run dev
49
+ ```
50
+
51
+ Open `http://127.0.0.1:5180`. `npm run dev` starts the studio together with its local ARDY bridge; `npm run dev:ui` starts the browser UI alone, without Block Generation.
52
+
53
+ The bridge listens on loopback only. The environment variables that point it at a remote ARDY machine are documented in `tools/ardy/BRIDGE.md`.
54
+
55
+ ## What you can do
56
+
57
+ **Stage a scene.** Create primitives and set pieces, then move, rotate, and scale them with a W/E/R gizmo. Grid snapping is a preference, not a law — hold `Ctrl` during a drag to invert it. A bird's-eye plan view drives 2D root waypoints for character paths.
58
+
59
+ **Edit like Unity.** Right-drag flies the camera (WASD walks, Q/E cranes), middle-drag pans, Alt+drag orbits the selection, left click selects, `F` frames. The full rule set, its source in Unity's manual, and every deliberate divergence are recorded in `docs/unity-reference.md`.
60
+
61
+ **Undo anything.** Every scene mutation goes through a single history store: one interaction — a drag, a scrub, an inspector edit — is exactly one undo entry. `Esc` cancels an in-flight drag and restores the pre-drag transform.
62
+
63
+ **Generate motion.** Pose characters and export poses, sequence multi-phase motion as Prompt Blocks on a resizable timeline, send them to ARDY, then play the result back with sparse IK correction where the generated motion needs fixing.
64
+
65
+ ## Controls
66
+
67
+ | Input | Action |
68
+ | --- | --- |
69
+ | Right-drag | Look around (fly) |
70
+ | RMB + WASD | Walk while flying |
71
+ | RMB + Q/E | Crane down / up |
72
+ | Middle-drag | Pan |
73
+ | Alt + drag | Orbit the selection |
74
+ | Scroll | Dolly |
75
+ | Click | Select; empty space clears |
76
+ | W / E / R | Move / rotate / scale tool |
77
+ | Ctrl (during drag) | Invert grid snapping |
78
+ | Ctrl/Cmd+Z, Ctrl/Cmd+Shift+Z | Undo / redo |
79
+ | Esc | Cancel the in-flight drag |
80
+ | End | Drop the selection to the surface |
81
+ | Ctrl/Cmd+D | Duplicate the selection |
82
+ | F | Frame the selection |
83
+
84
+ ## Validate
85
+
86
+ | Command | Covers |
87
+ | --- | --- |
88
+ | `npm run test:history` | Undo/redo store and transaction coordinator |
89
+ | `npm run test:scene-objects` | Scene-object model |
90
+ | `npm run test:hierarchy` | Hierarchy panel model |
91
+ | `npm run test:objects` | Gizmo interaction in a real browser — needs `npm run dev:ui` in another shell |
92
+ | `npm run test:theme` / `test:appearance` / `test:layout` | UI theme, appearance, layout |
93
+ | `npm run test:lifecycle` | Dev-server process lifecycle |
94
+ | `npm run test:ardy` | ARDY conversion, playback, and IK pipeline |
95
+ | `npm run build` | Production build |
96
+
97
+ Ad-hoc browser QA, while a dev server is available:
98
+
99
+ ```bash
100
+ npm run qa:browser -- <qa-script>
101
+ ```
102
+
103
+ ## Repository hygiene
104
+
105
+ Generated motion archives, QA output, build output, logs, and local runtime artifacts are not source files and must not be committed. In particular, keep `tools/ardy/out/`, `artifacts/`, `dist/`, `.gjc/`, and `.npz` files local.
106
+
107
+ ## License
108
+
109
+ GNU General Public License v3.0 or later — see `LICENSE`. Third-party projects retain their own licenses and copyright; see `THIRD_PARTY_NOTICES.md`.
@@ -0,0 +1,29 @@
1
+ # Third-Party Notices
2
+
3
+ CozyClay is an independent project. The names and licenses below apply only to their respective third-party projects and do not imply sponsorship, affiliation, or endorsement.
4
+
5
+ ## Three.js
6
+
7
+ CozyClay's browser-based 3D studio uses [Three.js](https://threejs.org/) through `three`, `@react-three/fiber`, and `@react-three/drei`.
8
+
9
+ - Copyright (c) 2010-2026 three.js authors
10
+ - License: MIT
11
+ - Source: https://github.com/mrdoob/three.js
12
+ - License text: https://github.com/mrdoob/three.js/blob/dev/LICENSE
13
+
14
+ ## NVIDIA ARDY
15
+
16
+ CozyClay provides an optional bridge and data-conversion workflow for externally installed [ARDY](https://github.com/nv-tlabs/ardy), an interactive human-motion generation project from NVIDIA Research.
17
+
18
+ ARDY is not bundled with CozyClay. Users must obtain, install, and operate ARDY separately under NVIDIA's terms.
19
+
20
+ - Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES
21
+ - ARDY source license: Apache License 2.0
22
+ - Source: https://github.com/nv-tlabs/ardy
23
+ - Source license: https://github.com/nv-tlabs/ardy/blob/main/LICENSE
24
+
25
+ ARDY model checkpoints and other model assets may be governed by separate terms, including the NVIDIA Open Model License identified by the ARDY project. Users are responsible for reviewing and complying with those terms before downloading or using the models.
26
+
27
+ ## CozyClay license scope
28
+
29
+ The CozyClay source code in this repository is licensed under GPL-3.0-or-later. That license does not replace or relicense Three.js, ARDY, ARDY model checkpoints, or any other third-party component.
@@ -0,0 +1,194 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * cozyclay - run the studio from a published package.
4
+ *
5
+ * `npx cozyclay` / `bunx cozyclay` should behave like `npm run dev` does in a
6
+ * clone: the studio in a browser, with the optional ARDY sidecar wired up.
7
+ * The difference is that nothing is built here. The package ships the built
8
+ * `dist/`, so this launcher only has to
9
+ *
10
+ * - serve those files over loopback,
11
+ * - forward /ardy to the sidecar on 5181 (the job Vite's dev proxy does),
12
+ * - keep the sidecar's lifetime tied to this process.
13
+ *
14
+ * It has no dependencies on purpose. A launcher that needs an install step
15
+ * before it can serve a prebuilt app is a launcher that will break.
16
+ */
17
+ import { spawn } from "node:child_process";
18
+ import { createReadStream, existsSync, statSync } from "node:fs";
19
+ import { createServer, request as httpRequest } from "node:http";
20
+ import { extname, join, normalize, resolve } from "node:path";
21
+ import { fileURLToPath } from "node:url";
22
+
23
+ const PKG_ROOT = resolve(fileURLToPath(new URL("..", import.meta.url)));
24
+ const DIST = join(PKG_ROOT, "dist");
25
+ const BRIDGE = join(PKG_ROOT, "tools", "ardy", "bridge.mjs");
26
+ const BRIDGE_PORT = Number(process.env.COZYCLAY_BRIDGE_PORT ?? 5181);
27
+
28
+ const TYPES = {
29
+ ".html": "text/html; charset=utf-8",
30
+ ".js": "text/javascript; charset=utf-8",
31
+ ".mjs": "text/javascript; charset=utf-8",
32
+ ".css": "text/css; charset=utf-8",
33
+ ".json": "application/json; charset=utf-8",
34
+ ".wasm": "application/wasm",
35
+ ".woff2": "font/woff2",
36
+ ".fbx": "application/octet-stream",
37
+ ".npz": "application/octet-stream",
38
+ ".png": "image/png",
39
+ ".jpg": "image/jpeg",
40
+ ".gif": "image/gif",
41
+ ".svg": "image/svg+xml",
42
+ ".ico": "image/x-icon",
43
+ };
44
+
45
+ function parseArgs(argv) {
46
+ const opts = { port: 5180, host: "127.0.0.1", ardy: true, open: true };
47
+ for (let i = 0; i < argv.length; i += 1) {
48
+ const arg = argv[i];
49
+ if (arg === "--port" || arg === "-p") opts.port = Number(argv[++i]);
50
+ else if (arg.startsWith("--port=")) opts.port = Number(arg.slice(7));
51
+ else if (arg === "--host") opts.host = String(argv[++i]);
52
+ else if (arg.startsWith("--host=")) opts.host = arg.slice(7);
53
+ else if (arg === "--no-ardy") opts.ardy = false;
54
+ else if (arg === "--no-open") opts.open = false;
55
+ else if (arg === "--help" || arg === "-h") opts.help = true;
56
+ else if (arg === "--version" || arg === "-v") opts.version = true;
57
+ else {
58
+ console.error(`cozyclay: unknown option ${arg}`);
59
+ opts.help = true;
60
+ }
61
+ }
62
+ if (!Number.isInteger(opts.port) || opts.port < 1 || opts.port > 65535) {
63
+ console.error("cozyclay: --port must be a port number");
64
+ opts.help = true;
65
+ }
66
+ return opts;
67
+ }
68
+
69
+ const HELP = `cozyclay - browser-based 3D staging studio
70
+
71
+ npx cozyclay start the studio and open it
72
+ npx cozyclay --port 5200 serve on another port
73
+ npx cozyclay --no-ardy skip the optional motion-generation sidecar
74
+ npx cozyclay --no-open do not open a browser
75
+
76
+ Motion generation needs an SSH-reachable NVIDIA machine running ARDY; point
77
+ the sidecar at it with CCLAY_ARDY_HOST. Everything else - staging, posing,
78
+ paths, cameras, timeline, playback - runs locally with no extra setup.`;
79
+
80
+ function serveFile(res, path) {
81
+ const type = TYPES[extname(path).toLowerCase()] ?? "application/octet-stream";
82
+ res.writeHead(200, {
83
+ "content-type": type,
84
+ "content-length": statSync(path).size,
85
+ // The studio is served from a local process a user just started; a
86
+ // stale cache across versions is more confusing than a re-read.
87
+ "cache-control": "no-cache",
88
+ });
89
+ createReadStream(path).pipe(res);
90
+ }
91
+
92
+ // Forward /ardy to the sidecar. Same contract as the Vite dev proxy, so the
93
+ // browser code needs no build-time knowledge of how it was launched.
94
+ function proxyToBridge(req, res) {
95
+ const upstream = httpRequest(
96
+ { host: "127.0.0.1", port: BRIDGE_PORT, path: req.url, method: req.method, headers: req.headers },
97
+ (upstreamRes) => {
98
+ res.writeHead(upstreamRes.statusCode ?? 502, upstreamRes.headers);
99
+ upstreamRes.pipe(res);
100
+ },
101
+ );
102
+ upstream.on("error", () => {
103
+ // An absent sidecar is an expected state, not a crash: the app treats a
104
+ // failed probe as "generation unavailable" and carries on.
105
+ res.writeHead(503, { "content-type": "application/json" });
106
+ res.end(JSON.stringify({ error: "ardy sidecar is not running" }));
107
+ });
108
+ req.pipe(upstream);
109
+ }
110
+
111
+ function openBrowser(url) {
112
+ const cmd =
113
+ process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
114
+ const child = spawn(cmd, [url], { stdio: "ignore", detached: true, shell: process.platform === "win32" });
115
+ child.on("error", () => {
116
+ /* headless box, no browser: the URL is printed anyway */
117
+ });
118
+ child.unref();
119
+ }
120
+
121
+ const opts = parseArgs(process.argv.slice(2));
122
+ if (opts.help) {
123
+ console.log(HELP);
124
+ process.exit(0);
125
+ }
126
+ if (opts.version) {
127
+ const pkg = JSON.parse(await import("node:fs").then((fs) => fs.promises.readFile(join(PKG_ROOT, "package.json"), "utf8")));
128
+ console.log(pkg.version);
129
+ process.exit(0);
130
+ }
131
+ if (!existsSync(join(DIST, "index.html"))) {
132
+ console.error("cozyclay: this package is missing its build (dist/index.html).");
133
+ console.error("cozyclay: from a clone, run `npm install && npm run build` first.");
134
+ process.exit(1);
135
+ }
136
+
137
+ // The sidecar exits immediately without a box to talk to, so starting it
138
+ // unconditionally would greet a first-time `npx cozyclay` with an error it
139
+ // cannot act on. An unset CCLAY_ARDY_HOST is the normal case, not a fault.
140
+ const ardyHost = process.env.CCLAY_ARDY_HOST?.trim();
141
+ let bridge = null;
142
+ if (opts.ardy && ardyHost && existsSync(BRIDGE)) {
143
+ bridge = spawn(process.execPath, [BRIDGE], { cwd: PKG_ROOT, stdio: "inherit" });
144
+ bridge.on("error", () => {
145
+ bridge = null;
146
+ });
147
+ }
148
+
149
+ const server = createServer((req, res) => {
150
+ const url = new URL(req.url ?? "/", "http://localhost");
151
+ if (url.pathname.startsWith("/ardy/")) {
152
+ proxyToBridge(req, res);
153
+ return;
154
+ }
155
+ const rel = decodeURIComponent(url.pathname === "/" ? "/index.html" : url.pathname);
156
+ // normalize + prefix check: a request must not escape dist/.
157
+ const target = join(DIST, normalize(rel));
158
+ if (!target.startsWith(DIST) || !existsSync(target) || statSync(target).isDirectory()) {
159
+ res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
160
+ res.end("not found");
161
+ return;
162
+ }
163
+ serveFile(res, target);
164
+ });
165
+
166
+ function shutdown() {
167
+ if (bridge) bridge.kill("SIGTERM");
168
+ server.close(() => process.exit(0));
169
+ setTimeout(() => process.exit(0), 2000).unref();
170
+ }
171
+ process.on("SIGINT", shutdown);
172
+ process.on("SIGTERM", shutdown);
173
+
174
+ server.on("error", (err) => {
175
+ if (err && err.code === "EADDRINUSE") {
176
+ console.error(`cozyclay: port ${opts.port} is taken. Try --port ${opts.port + 1}.`);
177
+ if (bridge) bridge.kill("SIGTERM");
178
+ process.exit(1);
179
+ }
180
+ throw err;
181
+ });
182
+
183
+ server.listen(opts.port, opts.host, () => {
184
+ const url = `http://${opts.host}:${opts.port}/`;
185
+ console.log(`CozyClay is running at ${url}`);
186
+ if (!opts.ardy) console.log("Motion generation: off (--no-ardy).");
187
+ else if (bridge) console.log(`Motion generation: sidecar running against ${ardyHost}.`);
188
+ else
189
+ console.log(
190
+ "Motion generation: off. It runs on an SSH-reachable NVIDIA machine with ARDY;\n" +
191
+ "set CCLAY_ARDY_HOST=user@host to turn it on. Everything else works without it.",
192
+ );
193
+ if (opts.open) openBrowser(url);
194
+ });