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
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Live-rig -> CozyClayPoseV1 export for the ARDY pose bridge.
3
+ *
4
+ * The exported bones are DELTAS, not absolute rotations: per bone
5
+ * `basis = rest^-1 * current`, the same "basis" concept Blender stores in
6
+ * pose_bone.matrix_basis (current = rest * basis). x-bot and y-bot bake
7
+ * different non-zero bind rotations, so absolute values would not transfer
8
+ * between rigs; a delta is rig-independent.
9
+ *
10
+ * Quaternions are emitted w-first [w, x, y, z]: three.js stores x, y, z, w
11
+ * and CozyClay's motion_constraints (authoritative for the ARDY side) is
12
+ * w-first, so values are reordered at this boundary. The camera block is
13
+ * ARDY Y-up, which is also three.js Y-up, so no axis conversion is needed.
14
+ *
15
+ * Every quaternion is normalized; non-finite or missing data throws instead
16
+ * of producing a corrupt JSON blob. The rest quaternions come from the rig's
17
+ * `userData.poseBind` snapshot (primed by PoseHandles, see posestudio.jsx),
18
+ * falling back to a lazy capture — the same resolution chain poses.js uses.
19
+ */
20
+
21
+ import { POSE_BONES, normalizeBoneName } from "../poses.js";
22
+ import { forwardFrom } from "../controls.jsx";
23
+
24
+ /** Wire key for a POSE_BONES entry: the Mixamo name without the `mixamorig`
25
+ * prefix, which is exactly the cskel27 joint name ("mixamorigLeftArm" ->
26
+ * "LeftArm"). */
27
+ function wireBoneName(entry) {
28
+ return entry.bone.replace(/^mixamorig/i, "");
29
+ }
30
+
31
+ /** Same normalised-name match rule as poses.js/posestudio.jsx: equal, or one
32
+ * is a suffix of the other, so `mixamorig:LeftArm`, `mixamorigLeftArm` and
33
+ * `LeftArm` all hit. */
34
+ function boneMatches(boneName, entry) {
35
+ const norm = normalizeBoneName(boneName);
36
+ const target = normalizeBoneName(entry.bone);
37
+ return norm === target || norm.endsWith(target) || target.endsWith(norm);
38
+ }
39
+
40
+ /** Per-root fallback rest snapshot, used only when PoseHandles never primed
41
+ * `root.userData.poseBind`; mirrors the bindOf() fallback in poses.js. */
42
+ const restFallback = new WeakMap();
43
+
44
+ function restOf(root) {
45
+ const primed = root.userData && root.userData.poseBind;
46
+ if (primed) return primed;
47
+ let map = restFallback.get(root);
48
+ if (!map) {
49
+ map = new Map();
50
+ root.traverse((object) => {
51
+ if (object.isBone) {
52
+ const q = object.quaternion;
53
+ map.set(object, { x: q.x, y: q.y, z: q.z, w: q.w });
54
+ }
55
+ });
56
+ restFallback.set(root, map);
57
+ }
58
+ return map;
59
+ }
60
+
61
+ /** Normalize and reject non-finite or zero-length quaternions loudly. */
62
+ function normalizeChecked(q, label) {
63
+ const parts = [q.x, q.y, q.z, q.w];
64
+ if (!parts.every(Number.isFinite)) {
65
+ throw new Error(`buildArdyPose: non-finite quaternion for ${label}`);
66
+ }
67
+ const len = Math.hypot(parts[0], parts[1], parts[2], parts[3]);
68
+ if (len < 1e-12) {
69
+ throw new Error(`buildArdyPose: zero-length quaternion for ${label}`);
70
+ }
71
+ return { x: parts[0] / len, y: parts[1] / len, z: parts[2] / len, w: parts[3] / len };
72
+ }
73
+
74
+ /** Conjugate; valid as the inverse because inputs are normalized unit quats. */
75
+ function qInverse(q) {
76
+ return { x: -q.x, y: -q.y, z: -q.z, w: q.w };
77
+ }
78
+
79
+ /** Hamilton product a * b, same convention as poses.js qMultiply. */
80
+ function qMultiply(a, b) {
81
+ return {
82
+ x: a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y,
83
+ y: a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x,
84
+ z: a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w,
85
+ w: a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z,
86
+ };
87
+ }
88
+
89
+ /**
90
+ * Build a CozyClayPoseV1 JSON object for the posed character in `rig`.
91
+ * `rig` is the live three.js Object3D tree (the rigA/rigB model handed to
92
+ * App via onRig), `camRef` the shot camera ref, `look` the `{ yaw, pitch }`
93
+ * ref, `fovDeg` the vertical FOV in degrees, `slate` the slate string, and
94
+ * `rigName` the wire `source.rig` value ("x-bot-tpose").
95
+ */
96
+ export function buildArdyPose({ rig, camRef, look, fovDeg, slate, rigName, root }) {
97
+ if (!rig) throw new Error("buildArdyPose: rig is required");
98
+ const cam = camRef && camRef.current;
99
+ if (!cam) throw new Error("buildArdyPose: camRef.current is required");
100
+ if (!look || !look.current) throw new Error("buildArdyPose: look is required");
101
+ if (typeof slate !== "string" || !slate) throw new Error("buildArdyPose: slate must be a non-empty string");
102
+ if (!Number.isFinite(fovDeg)) throw new Error("buildArdyPose: fovDeg must be a finite number");
103
+ if (root !== undefined && (!Array.isArray(root) || root.length !== 3 || !root.every(Number.isFinite))) {
104
+ throw new Error("buildArdyPose: root must be [x, y, z] finite metres when present");
105
+ }
106
+
107
+ const rest = restOf(rig);
108
+ const bones = {};
109
+ const found = new Set();
110
+
111
+ rig.traverse((object) => {
112
+ if (!object.isBone) return;
113
+ for (const entry of POSE_BONES) {
114
+ if (found.has(entry.id) || !boneMatches(object.name, entry)) continue;
115
+ // First depth-first match is the control bone; Mixamo FBX exports
116
+ // nest an identity "skinned" copy of each bone beneath it (see
117
+ // poses.js), which would read back as an identity delta if it won.
118
+ found.add(entry.id);
119
+ const bind = rest.get(object);
120
+ if (!bind) {
121
+ throw new Error(`buildArdyPose: no rest quaternion for ${entry.id}`);
122
+ }
123
+ const current = normalizeChecked(object.quaternion, `${entry.id} (current)`);
124
+ const restQ = normalizeChecked(bind, `${entry.id} (rest)`);
125
+ // Blender basis: current = rest * basis => basis = rest^-1 * current.
126
+ const basis = normalizeChecked(qMultiply(qInverse(restQ), current), `${entry.id} (basis)`);
127
+ bones[wireBoneName(entry)] = [basis.w, basis.x, basis.y, basis.z];
128
+ }
129
+ });
130
+
131
+ if (found.size !== POSE_BONES.length) {
132
+ const missing = POSE_BONES.filter((entry) => !found.has(entry.id)).map((entry) => entry.id);
133
+ throw new Error(`buildArdyPose: rig is missing joints: ${missing.join(", ")}`);
134
+ }
135
+
136
+ const position = cam.position;
137
+ const forward = forwardFrom(look.current.yaw, look.current.pitch);
138
+ const camera = {
139
+ position: [position.x, position.y, position.z],
140
+ look_at: [position.x + forward.x, position.y + forward.y, position.z + forward.z],
141
+ up: [0, 1, 0],
142
+ vertical_fov_radians: (fovDeg * Math.PI) / 180,
143
+ };
144
+ for (const value of [...camera.position, ...camera.look_at, camera.vertical_fov_radians]) {
145
+ if (!Number.isFinite(value)) throw new Error("buildArdyPose: non-finite camera value");
146
+ }
147
+
148
+ return {
149
+ schema: "cozyclay.pose.v1",
150
+ created_ms: Date.now(),
151
+ source: { app: "cozyclay", rig: typeof rigName === "string" && rigName ? rigName : "unknown" },
152
+ ...(root ? { root: root.slice() } : {}),
153
+ bones,
154
+ camera,
155
+ slate,
156
+ };
157
+ }