@melite/formats 0.0.0-stage → 0.1.4-alpha

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 (64) hide show
  1. package/LICENSE +557 -0
  2. package/lib/b64.d.ts +2 -0
  3. package/lib/b64.js +7 -0
  4. package/lib/bodyPresets.d.ts +40 -0
  5. package/lib/bodyPresets.js +77 -0
  6. package/lib/capabilityCards.d.ts +54 -0
  7. package/lib/capabilityCards.js +127 -0
  8. package/lib/captionSplitter.d.ts +49 -0
  9. package/lib/captionSplitter.js +172 -0
  10. package/lib/cardMutations.d.ts +52 -0
  11. package/lib/cardMutations.js +67 -0
  12. package/lib/downloads.d.ts +18 -0
  13. package/lib/downloads.js +64 -0
  14. package/lib/extractVideoFrame.d.ts +23 -0
  15. package/lib/extractVideoFrame.js +114 -0
  16. package/lib/glbLoad.d.ts +54 -0
  17. package/lib/glbLoad.js +96 -0
  18. package/lib/glbSkin.d.ts +14 -0
  19. package/lib/glbSkin.js +124 -0
  20. package/lib/handPose.d.ts +46 -0
  21. package/lib/handPose.js +122 -0
  22. package/lib/index.d.ts +26 -0
  23. package/lib/index.js +26 -0
  24. package/lib/keyframeExtract.d.ts +46 -0
  25. package/lib/keyframeExtract.js +89 -0
  26. package/lib/maskPrep.d.ts +88 -0
  27. package/lib/maskPrep.js +386 -0
  28. package/lib/motionConstraints.d.ts +237 -0
  29. package/lib/motionConstraints.js +369 -0
  30. package/lib/motionEnvelope.d.ts +56 -0
  31. package/lib/motionEnvelope.js +59 -0
  32. package/lib/motionNpz.d.ts +120 -0
  33. package/lib/motionNpz.js +497 -0
  34. package/lib/npz.d.ts +24 -0
  35. package/lib/npz.js +158 -0
  36. package/lib/openPoseCanvas.d.ts +17 -0
  37. package/lib/openPoseCanvas.js +90 -0
  38. package/lib/pngMeta.d.ts +42 -0
  39. package/lib/pngMeta.js +212 -0
  40. package/lib/pngMetaInfo.d.ts +14 -0
  41. package/lib/pngMetaInfo.js +127 -0
  42. package/lib/poseCodec.d.ts +20 -0
  43. package/lib/poseCodec.js +61 -0
  44. package/lib/poseTransforms.d.ts +73 -0
  45. package/lib/poseTransforms.js +237 -0
  46. package/lib/poserSkin.d.ts +75 -0
  47. package/lib/poserSkin.js +126 -0
  48. package/lib/prompt-tags.json +132 -0
  49. package/lib/promptTags.d.ts +42 -0
  50. package/lib/promptTags.js +116 -0
  51. package/lib/psdExport.d.ts +28 -0
  52. package/lib/psdExport.js +272 -0
  53. package/lib/readFile.d.ts +9 -0
  54. package/lib/readFile.js +16 -0
  55. package/lib/renderSnapshot.d.ts +34 -0
  56. package/lib/renderSnapshot.js +55 -0
  57. package/lib/somaSkin.d.ts +82 -0
  58. package/lib/somaSkin.js +267 -0
  59. package/lib/textToPose.d.ts +388 -0
  60. package/lib/textToPose.js +604 -0
  61. package/lib/uploadToEstate.d.ts +25 -0
  62. package/lib/uploadToEstate.js +97 -0
  63. package/package.json +148 -4
  64. package/README.md +0 -3
@@ -0,0 +1,267 @@
1
+ /**
2
+ * somaSkin — neutral SOMA-77 mesh skinning, a direct port of the kimodo demo's
3
+ * vendor/kimodo/kimodo/viz/soma_skin.py SOMASkin.skin()+lbs().
4
+ *
5
+ * The demo (and only the demo) renders Kimodo motions on a NEUTRAL body via
6
+ * LBS weights stored in skin_standard.npz (bind_vertices + faces +
7
+ * bind_rig_transform + lbs_indices + lbs_weights). That is the "base SOMAX
8
+ * mesh" that actually deforms — distinct from the poser backend's `soma`
9
+ * engine (a static mesh with no LBS weights) and from `anny`/`mhr` (parametric
10
+ * bodies with their own proportions). This module reproduces the demo's math
11
+ * client-side so the asset preview shows a Kimodo motion on the same neutral
12
+ * mesh the demo uses, with no parametric body shape imposed.
13
+ *
14
+ * Flow per frame:
15
+ * fk[j] = [[global_rot[j], global_pos[j]], [0,0,0,1]] (4×4, per joint)
16
+ * affine[j] = (fk[j] · inv(bind_rig[j]))[:3, :] (3×4, per joint)
17
+ * v' = Σ_w lbs_weights[v,w] · (affine[lbs_indices[v,w]] · [bind_v, 1])
18
+ *
19
+ * Units: meters throughout (Kimodo's native unit). No cm↔m conversion — that
20
+ * was a poser-route artifact that doesn't apply here.
21
+ */
22
+ import { parseNpz } from "./npz.js";
23
+ import { apiFetch } from "@melite/catalog";
24
+ // The skin is a DECLARED provision on the kimodo family (models/
25
+ // motion/kimodo/model.py _SOMA_SKIN) — the provision door streams it
26
+ // from the weights mount and ACQUIRES it on demand (fetched direct
27
+ // from the DECL's pinned distribution, DECL-sha-gated before it
28
+ // lands) when missing. NEVER a vite-public
29
+ // blob: the old /editor/soma-skin-standard.npz path 404'd whenever
30
+
31
+ // Exported: the editor test wall pins the door shape.
32
+ export const SKIN_URL = "/v1/models/kimodo/provisions/skin_standard.npz";
33
+ const J = 77; // SOMA-77 joints
34
+ const W = 8; // max influencing joints per vertex in skin_standard.npz
35
+ let _skinPromise = null;
36
+ /** Invert a 4×4 matrix (column-major-agnostic: stored row-major as 16 floats). */
37
+ export function invert4(m) {
38
+ // Gauss-Jordan with partial pivoting. m is row-major (J*16 + col).
39
+ const a = Array.from(m);
40
+ const inv = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
41
+ for (let col = 0; col < 4; col++) {
42
+ // pivot
43
+ let piv = col, max = Math.abs(a[col * 4 + col]);
44
+ for (let r = col + 1; r < 4; r++) {
45
+ const v = Math.abs(a[r * 4 + col]);
46
+ if (v > max) {
47
+ max = v;
48
+ piv = r;
49
+ }
50
+ }
51
+ if (max < 1e-12)
52
+ continue; // singular column — leave as-is
53
+ if (piv !== col) {
54
+ for (let c = 0; c < 4; c++) {
55
+ ;
56
+ [a[col * 4 + c], a[piv * 4 + c]] = [a[piv * 4 + c], a[col * 4 + c]];
57
+ [inv[col * 4 + c], inv[piv * 4 + c]] = [inv[piv * 4 + c], inv[col * 4 + c]];
58
+ }
59
+ }
60
+ const diag = a[col * 4 + col];
61
+ for (let c = 0; c < 4; c++) {
62
+ a[col * 4 + c] /= diag;
63
+ inv[col * 4 + c] /= diag;
64
+ }
65
+ for (let r = 0; r < 4; r++) {
66
+ if (r === col)
67
+ continue;
68
+ const factor = a[r * 4 + col];
69
+ if (factor === 0)
70
+ continue;
71
+ for (let c = 0; c < 4; c++) {
72
+ a[r * 4 + c] -= factor * a[col * 4 + c];
73
+ inv[r * 4 + c] -= factor * inv[col * 4 + c];
74
+ }
75
+ }
76
+ }
77
+ return Float32Array.from(inv);
78
+ }
79
+ function asFloat32(arr) {
80
+ return arr instanceof Float32Array ? arr : new Float32Array(arr);
81
+ }
82
+ /** Extract the rest pose from row-major FORWARD bind transforms (J×16):
83
+ * translation column (tx, ty, tz) = indices 3, 7, 11 of each 4×4, and the
84
+ * 3×3 rotation block = 0,1,2 / 4,5,6 / 8,9,10. This is the TS equivalent
85
+ * of Kimodo's neutral_joints — the motion card's pre-inference pose (see
86
+ * SomaSkin.restJoints doc). Shared by both loaders: the npz carries the
87
+ * forward binds directly; the GLB adapter inverts glTF boneInverses into
88
+ * the same row-major forward shape first. */
89
+ export function restPoseFromBind(bindRig, jointCount) {
90
+ const restJoints = new Float32Array(jointCount * 3);
91
+ const restRots = new Float32Array(jointCount * 9);
92
+ for (let j = 0; j < jointCount; j++) {
93
+ const b = j * 16;
94
+ restJoints[j * 3] = bindRig[b + 3];
95
+ restJoints[j * 3 + 1] = bindRig[b + 7];
96
+ restJoints[j * 3 + 2] = bindRig[b + 11];
97
+ restRots[j * 9] = bindRig[b + 0];
98
+ restRots[j * 9 + 1] = bindRig[b + 1];
99
+ restRots[j * 9 + 2] = bindRig[b + 2];
100
+ restRots[j * 9 + 3] = bindRig[b + 4];
101
+ restRots[j * 9 + 4] = bindRig[b + 5];
102
+ restRots[j * 9 + 5] = bindRig[b + 6];
103
+ restRots[j * 9 + 6] = bindRig[b + 8];
104
+ restRots[j * 9 + 7] = bindRig[b + 9];
105
+ restRots[j * 9 + 8] = bindRig[b + 10];
106
+ }
107
+ return { restJoints, restRots };
108
+ }
109
+ /** Load + cache the neutral SOMA skin (one fetch per session). */
110
+ export async function loadSomaSkin() {
111
+ if (_skinPromise)
112
+ return _skinPromise;
113
+ _skinPromise = (async () => {
114
+ const res = await apiFetch(SKIN_URL);
115
+ if (!res.ok)
116
+ throw new Error(`Failed to fetch SOMA skin: ${res.status}`);
117
+ const bytes = new Uint8Array(await res.arrayBuffer());
118
+ const arrays = await parseNpz(bytes);
119
+ const bv = arrays.get("bind_vertices");
120
+ const fc = arrays.get("faces");
121
+ const brt = arrays.get("bind_rig_transform");
122
+ const li = arrays.get("lbs_indices");
123
+ const lw = arrays.get("lbs_weights");
124
+ if (!bv || !fc || !brt || !li || !lw) {
125
+ throw new Error(`SOMA skin missing keys (have: ${[...arrays.keys()].join(",")})`);
126
+ }
127
+ const bindVertices = asFloat32(bv.data);
128
+ const bindRig = asFloat32(brt.data); // (77, 4, 4) row-major
129
+ // Precompute per-joint inverse bind transforms.
130
+ const bindRigInv = new Float32Array(J * 16);
131
+ for (let j = 0; j < J; j++) {
132
+ const m = bindRig.subarray(j * 16, j * 16 + 16);
133
+ bindRigInv.set(invert4(m), j * 16);
134
+ }
135
+ // Neutral / rest pose — extracted from the row-major forward binds
136
+ // (shared helper; see restPoseFromBind).
137
+ const { restJoints, restRots } = restPoseFromBind(bindRig, J);
138
+ // faces (int64) and lbs_indices (int32) arrive as BigInt64Array / Uint32Array
139
+ const faceSrc = fc.data instanceof BigInt64Array
140
+ ? Array.from(fc.data, (x) => Number(x))
141
+ : Array.from(fc.data);
142
+ const faces = Uint32Array.from(faceSrc);
143
+ const lbsIndices = li.data instanceof Uint32Array
144
+ ? li.data
145
+ : li.data instanceof BigInt64Array
146
+ ? Uint32Array.from(li.data, (x) => Number(x))
147
+ : Uint32Array.from(li.data);
148
+ const lbsWeights = asFloat32(lw.data);
149
+ return {
150
+ bindVertices,
151
+ faces,
152
+ lbsIndices,
153
+ lbsWeights,
154
+ bindRigInv,
155
+ restJoints,
156
+ restRots,
157
+ vertexCount: bindVertices.length / 3,
158
+ faceCount: faces.length / 3,
159
+ jointCount: J,
160
+ influences: W,
161
+ };
162
+ })();
163
+ try {
164
+ return await _skinPromise;
165
+ }
166
+ catch (e) {
167
+ // A failed load must not poison the session cache: the next call
168
+ // re-fetches (a transient gateway blip during the first preview must
169
+ // not brick every later motion preview). Successes stay cached.
170
+ _skinPromise = null;
171
+ throw e;
172
+ }
173
+ }
174
+ /**
175
+ * Build per-joint (3,4) affine matrices for one frame:
176
+ * affine[j] = (fk[j] · bindRigInv[j])[:3, :]
177
+ * fk[j] is the global posed transform (global_rot → rotation, global_pos →
178
+ * translation). Output is `affines` length J*12 (row-major 3×4 per joint).
179
+ */
180
+ function buildAffines(globalRot, // (J, 9) row-major 3×3 per joint
181
+ globalPos, // (J, 3)
182
+ bindRigInv, // (J, 16)
183
+ out, // (J, 12)
184
+ jointCount) {
185
+ for (let j = 0; j < jointCount; j++) {
186
+ const r = j * 9;
187
+ const p = j * 3;
188
+ const bi = j * 16;
189
+ // fk[j] row-major 4×4 (rotation block + translation; last row 0,0,0,1)
190
+ // fk @ bindRigInv: compute the 3×4 top rows only (we discard row 3).
191
+ const a0 = bindRigInv[bi + 0], a1 = bindRigInv[bi + 1], a2 = bindRigInv[bi + 2], a3 = bindRigInv[bi + 3];
192
+ const a4 = bindRigInv[bi + 4], a5 = bindRigInv[bi + 5], a6 = bindRigInv[bi + 6], a7 = bindRigInv[bi + 7];
193
+ const a8 = bindRigInv[bi + 8], a9 = bindRigInv[bi + 9], a10 = bindRigInv[bi + 10], a11 = bindRigInv[bi + 11];
194
+ // (Not needed for top 3 rows:) a12,a13,a14,a15 — last row of inv, multiplied
195
+ // by fk's last row [0,0,0,1], contributes fk doesn't add from row 4.
196
+ // fk rotation block:
197
+ const fr0 = globalRot[r], fr1 = globalRot[r + 1], fr2 = globalRot[r + 2];
198
+ const fr3 = globalRot[r + 3], fr4 = globalRot[r + 4], fr5 = globalRot[r + 5];
199
+ const fr6 = globalRot[r + 6], fr7 = globalRot[r + 7], fr8 = globalRot[r + 8];
200
+ // fk @ inv : out_row_i = fk_row_i · inv_col
201
+ // Row 0 of result (3 cols × 4):
202
+ const o0 = fr0 * a0 + fr1 * a4 + fr2 * a8;
203
+ const o1 = fr0 * a1 + fr1 * a5 + fr2 * a9;
204
+ const o2 = fr0 * a2 + fr1 * a6 + fr2 * a10;
205
+ const o3 = fr0 * a3 + fr1 * a7 + fr2 * a11 + globalPos[p];
206
+ const o4 = fr3 * a0 + fr4 * a4 + fr5 * a8;
207
+ const o5 = fr3 * a1 + fr4 * a5 + fr5 * a9;
208
+ const o6 = fr3 * a2 + fr4 * a6 + fr5 * a10;
209
+ const o7 = fr3 * a3 + fr4 * a7 + fr5 * a11 + globalPos[p + 1];
210
+ const o8 = fr6 * a0 + fr7 * a4 + fr8 * a8;
211
+ const o9 = fr6 * a1 + fr7 * a5 + fr8 * a9;
212
+ const o10 = fr6 * a2 + fr7 * a6 + fr8 * a10;
213
+ const o11 = fr6 * a3 + fr7 * a7 + fr8 * a11 + globalPos[p + 2];
214
+ const o = j * 12;
215
+ out[o] = o0;
216
+ out[o + 1] = o1;
217
+ out[o + 2] = o2;
218
+ out[o + 3] = o3;
219
+ out[o + 4] = o4;
220
+ out[o + 5] = o5;
221
+ out[o + 6] = o6;
222
+ out[o + 7] = o7;
223
+ out[o + 8] = o8;
224
+ out[o + 9] = o9;
225
+ out[o + 10] = o10;
226
+ out[o + 11] = o11;
227
+ }
228
+ }
229
+
230
+
231
+
232
+
233
+
234
+
235
+
236
+
237
+
238
+
239
+
240
+
241
+ export function deformFrame(globalRot, globalPos, skin, out) {
242
+ const J_ = skin.jointCount;
243
+ const W_ = skin.influences;
244
+ const affines = new Float32Array(J_ * 12);
245
+ buildAffines(globalRot, globalPos, skin.bindRigInv, affines, J_);
246
+ const { bindVertices, lbsIndices, lbsWeights } = skin;
247
+ const V = skin.vertexCount;
248
+ for (let v = 0; v < V; v++) {
249
+ const vi = v * 3;
250
+ const bx = bindVertices[vi], by = bindVertices[vi + 1], bz = bindVertices[vi + 2];
251
+ let ox = 0, oy = 0, oz = 0;
252
+ const wi = v * W_;
253
+ for (let widx = 0; widx < W_; widx++) {
254
+ const weight = lbsWeights[wi + widx];
255
+ if (weight === 0)
256
+ continue;
257
+ const ai = lbsIndices[wi + widx] * 12;
258
+ ox += weight * (affines[ai] * bx + affines[ai + 1] * by + affines[ai + 2] * bz + affines[ai + 3]);
259
+ oy += weight * (affines[ai + 4] * bx + affines[ai + 5] * by + affines[ai + 6] * bz + affines[ai + 7]);
260
+ oz += weight * (affines[ai + 8] * bx + affines[ai + 9] * by + affines[ai + 10] * bz + affines[ai + 11]);
261
+ }
262
+ out[vi] = ox;
263
+ out[vi + 1] = oy;
264
+ out[vi + 2] = oz;
265
+ }
266
+ }
267
+ //# sourceMappingURL=somaSkin.js.map
@@ -0,0 +1,388 @@
1
+ /**
2
+ * Text-to-pose client — submit text prompts, poll for results, manage cache.
3
+ *
4
+ * Backend: custom_nodes/melite-poser-nodes/server.py (proxied via melite-head)
5
+ * Endpoints:
6
+ * POST /poser/text-to-pose Submit generation request
7
+ * GET /poser/text-to-pose/jobs/{job_id} Poll job status
8
+ * DELETE /poser/text-to-pose/jobs/{job_id} Cancel running job
9
+ * GET /poser/text-to-pose/cache List cached entries
10
+ * DELETE /poser/text-to-pose/cache/{hash} Delete cache entry
11
+ * POST /poser/text-to-pose/cache/clear Clear all cache entries
12
+ */
13
+ /** Slice a single frame's vertices out of a multi-frame base64 vertex buffer.
14
+ *
15
+ * Async because decoding the (potentially huge) multi-frame buffer is offloaded
16
+ * to the browser's native base64 decoder. Returns a base64 string containing
17
+ * just the one frame's float32 positions.
18
+ *
19
+ * @param multiB64 base64-encoded buffer of shape (T, V, 3) float32
20
+ * @param frameIdx which frame to slice (clamped to [0, T-1])
21
+ * @param vertexCount V — defaults to the SOMA-77 canonical count
22
+ * @returns base64-encoded single-frame buffer, or null on decode failure */
23
+ export declare function sliceFrameAsB64(multiB64: string, frameIdx: number, vertexCount?: number): Promise<string | null>;
24
+ export type CameraView = "front" | "side" | "three-quarter" | "demo";
25
+ export type CfgType = "nocfg" | "regular" | "separated";
26
+ export type FrameStrategy = "auto_peak" | "auto_mid" | "manual" | "all";
27
+ export type JobStatus = "pending" | "running" | "completed" | "failed" | "cancelled";
28
+ /** A prompt segment for the multi-stack timeline. */
29
+ export interface PromptSegmentInput {
30
+ text: string;
31
+ duration_sec: number;
32
+ }
33
+ /** A prompt segment with resolved frame ranges — returned by the backend. */
34
+ export interface PromptSegmentOutput {
35
+ text: string;
36
+ start_frame: number;
37
+ end_frame: number;
38
+ frame_count: number;
39
+ }
40
+ export interface TextToPoseRequest {
41
+ prompt: string;
42
+ seed: number | null;
43
+ duration_sec: number;
44
+ diffusion_steps?: number;
45
+ camera_view?: CameraView;
46
+ frame_strategy?: FrameStrategy;
47
+ cfg_type?: CfgType;
48
+ cfg_weight?: number[] | null;
49
+ num_samples?: number;
50
+ num_transition_frames?: number;
51
+ use_cache?: boolean;
52
+ confidence?: number;
53
+ ear_confidence?: number;
54
+ width?: number;
55
+ height?: number;
56
+ frame_indices?: number[] | null;
57
+ /** Per-segment prompts with explicit durations — "timeline command stacking".
58
+ * When present, the backend gives each segment its own frame budget instead
59
+ * of splitting a single prompt evenly. */
60
+ prompt_segments?: PromptSegmentInput[];
61
+
62
+ post_processing?: boolean;
63
+ /** Root margin for post-processing (meters). Lower = tighter root pinning. */
64
+ root_margin?: number;
65
+ }
66
+ /** One frame of COCO-18 keypoints: [x0, y0, c0, x1, y1, c1, ...] (54 floats). */
67
+ export type FlatKeypoints = number[];
68
+ /** One frame of COCO-18 3D keypoints: [[x, y, z], ...] (18 joints). */
69
+ export type Coco18_3D = [number, number, number][];
70
+ export interface PoseMotion {
71
+ fps: number;
72
+ frame_count: number;
73
+ duration_sec: number;
74
+ skeleton: string;
75
+ model: string;
76
+ camera_view: CameraView;
77
+ width: number;
78
+ height: number;
79
+ peak_frame_idx: number;
80
+ /** Prompt segments with resolved frame ranges — for accurate timeline display. */
81
+ prompt_segments?: PromptSegmentOutput[];
82
+ /** Flat 2D keypoints per frame — ready for /poser/render. */
83
+ frames_flat: FlatKeypoints[];
84
+ /** 3D keypoints per frame — for 3D preview / rotation. */
85
+ frames_coco18_3d: Coco18_3D[];
86
+ /** Full SOMA-77 3D keypoints per frame (T, 77, 3) — lossless, for /poser/skin/deform_soma. */
87
+ frames_soma77_3d?: [number, number, number][][];
88
+ /** Full SOMA-77 global rotation matrices per frame (T, 77, 3, 3) — for correct LBS skinning. */
89
+ frames_soma77_rotmat?: [number, number, number][][][];
90
+ /** Pre-computed deformed mesh vertices per frame — base64-encoded float32 binary (T, V, 3).
91
+ * Decode via: atob() → ArrayBuffer → Float32Array, then reshape [T * V * 3] → [T][V][3].
92
+ * NOT a JSON array — see deformed_vertices_b64 instead. */
93
+ deformed_vertices_b64?: string;
94
+ /** Base64-encoded .npz of SOMA-77 joints — for round-tripping to other tools. */
95
+ soma_npz_b64?: string;
96
+ /** Foot-contact probabilities per frame (T, 4): [L_heel, R_heel, L_toe, R_toe] in [0,1]. */
97
+ foot_contacts?: number[][];
98
+ /** Number of samples actually generated (may be > 1 when num_samples > 1). */
99
+ num_samples?: number;
100
+ /** Alternate samples when num_samples > 1. Each entry is a full SOMA-77 motion
101
+ * with its own deformed verts, so the UI can render clickable alternates. */
102
+ alt_samples?: AltSample[];
103
+ /** Identity model the deformed_vertices were skinned to. Echoed by the
104
+ * backend so the frontend can detect a mismatch if the user switched
105
+ * identity after generating (verts/topo would be wrong). Missing = unknown
106
+ * = do NOT drop (the historical default before this field existed). */
107
+ identity_model?: string;
108
+ /** Identity coefficients used for the skinning pass (echoed so capture-
109
+ * reference can build a matching ReferenceMeshSpec without round-tripping
110
+ * through the bind/mesh endpoint). Missing = defaults were used. */
111
+ identity_coeffs?: number[] | null;
112
+ /** Vertex count of the deformed mesh (V, not V*3). Informational. */
113
+ vertex_count?: number;
114
+ }
115
+ export interface AltSample {
116
+ sample_idx: number;
117
+ frames_soma77_3d: [number, number, number][][];
118
+ frames_soma77_rotmat: [number, number, number][][][];
119
+ deformed_vertices_b64: string;
120
+ }
121
+ export interface TextToPoseResult {
122
+ status: "success";
123
+ cache_hit: boolean;
124
+ inference_time_sec: number;
125
+ motion: PoseMotion;
126
+ }
127
+ export interface SubmitResponse {
128
+ job_id: string;
129
+ status: "pending";
130
+ poll_url: string;
131
+ }
132
+ export interface JobStatusResponse {
133
+ job_id: string;
134
+ status: JobStatus;
135
+ result?: TextToPoseResult;
136
+ error?: string;
137
+ error_class?: string;
138
+ }
139
+ export interface CacheEntry {
140
+ hash: string;
141
+ prompt: string;
142
+ seed: number | null;
143
+ duration_sec: number;
144
+ model_name: string;
145
+ created_at: number;
146
+ accessed_at: number;
147
+ size_bytes: number;
148
+ frame_count: number;
149
+ peak_frame_idx: number;
150
+ }
151
+ export interface CacheListResponse {
152
+ entries: CacheEntry[];
153
+ total_size_bytes: number;
154
+ entry_count: number;
155
+ }
156
+ /**
157
+ * Submit a text-to-pose generation request.
158
+ * Returns immediately with a job_id — poll via pollJob() or waitForJob().
159
+ */
160
+ export declare function submitTextToPose(req: TextToPoseRequest): Promise<SubmitResponse>;
161
+ /**
162
+ * Poll job status once. Returns the current status + result if completed.
163
+ */
164
+ export declare function pollJob(jobId: string): Promise<JobStatusResponse>;
165
+ /**
166
+ * Wait for a job to reach a terminal state (completed/failed/cancelled).
167
+ * Polls every `intervalMs` until `timeoutMs`.
168
+ *
169
+ * Calls `onUpdate` on each poll if provided — useful for progress UI.
170
+ */
171
+ export declare function waitForJob(jobId: string, opts?: {
172
+ intervalMs?: number;
173
+ timeoutMs?: number;
174
+ onUpdate?: (status: JobStatusResponse) => void;
175
+ }): Promise<TextToPoseResult>;
176
+ export declare function cancelJob(jobId: string): Promise<void>;
177
+ /**
178
+ * Submit a text-to-pose request and wait for the result.
179
+ * This is the simplest call for UI components that just want the final motion.
180
+ */
181
+ export declare function generatePose(req: TextToPoseRequest, opts?: {
182
+ intervalMs?: number;
183
+ timeoutMs?: number;
184
+ onUpdate?: (status: JobStatusResponse) => void;
185
+ }): Promise<TextToPoseResult>;
186
+ export declare function listPoseCache(): Promise<CacheListResponse>;
187
+ export declare function deletePoseCacheEntry(hash: string): Promise<void>;
188
+ export declare function clearPoseCache(): Promise<number>;
189
+ export interface ModelInfo {
190
+ id: string;
191
+ label: string;
192
+ /** True if the model checkpoint is available locally. */
193
+ local: boolean;
194
+ is_default: boolean;
195
+ }
196
+ export declare function listModels(): Promise<{
197
+ models: ModelInfo[];
198
+ default: string;
199
+ }>;
200
+ export interface RegistryVersion {
201
+ display: string;
202
+ short_key: string;
203
+ local: boolean;
204
+ is_g1: boolean;
205
+ }
206
+ export interface RegistrySkeleton {
207
+ label: string;
208
+ key: string;
209
+ versions: RegistryVersion[];
210
+ }
211
+ export interface RegistryTree {
212
+ datasets: string[];
213
+ tree: Record<string, {
214
+ skeletons: RegistrySkeleton[];
215
+ }>;
216
+ default_dataset: string | null;
217
+ default_skeleton: string | null;
218
+ default_version: string;
219
+ }
220
+ export declare function listRegistry(): Promise<RegistryTree>;
221
+ export interface KimodoExampleMeta {
222
+ text?: string;
223
+ texts?: string[];
224
+ duration?: number;
225
+ num_samples?: number;
226
+ seed?: number;
227
+ diffusion_steps?: number;
228
+ cfg?: {
229
+ enabled?: boolean;
230
+ text_weight?: number;
231
+ constraint_weight?: number;
232
+ };
233
+ }
234
+ export interface KimodoExample {
235
+ id: string;
236
+ name: string;
237
+ model: string;
238
+ has_motion: boolean;
239
+ has_constraints: boolean;
240
+ meta: KimodoExampleMeta;
241
+ }
242
+ export declare function listExamples(model?: string): Promise<{
243
+ examples: KimodoExample[];
244
+ }>;
245
+ export declare function loadExample(model: string, exampleId: string, loadGt?: boolean): Promise<{
246
+ id: string;
247
+ model: string;
248
+ meta: KimodoExampleMeta;
249
+ motion_b64?: string;
250
+ motion_size?: number;
251
+ constraints?: unknown[];
252
+ }>;
253
+ export declare function exportBVH(framesSoma77: number[][][], framesRotmat: number[][][][], fps: number): Promise<{
254
+ bvh: string;
255
+ frames: number;
256
+ joints: number;
257
+ }>;
258
+ /** Download a BVH file from SOMA-77 motion data. */
259
+ export declare function downloadBVH(motion: PoseMotion, standardTpose?: boolean): Promise<void>;
260
+ /** Export motion as AMASS NPZ (SMPL-X compatible). */
261
+ export declare function downloadAMASSNpz(motion: PoseMotion): Promise<void>;
262
+
263
+ export declare function downloadG1Csv(motion: PoseMotion): Promise<void>;
264
+ /**
265
+ * Export motion as a rigged FBX file (Mixamo-compatible joint names).
266
+ *
267
+ * The backend renders an FBX 7.4.0 ASCII skeleton with the full 77-joint
268
+ * hierarchy and baked animation — importable by Blender, Maya, Unity, and
269
+ * Unreal. Mixamo naming enables automatic retargeting onto Mixamo rigs.
270
+ */
271
+ export declare function downloadFBX(motion: PoseMotion, mixamo?: boolean): Promise<void>;
272
+ /**
273
+ * Render a 2D pose video server-side via matplotlib + imageio.
274
+ * Higher-quality and browser-independent than client-side MediaRecorder.
275
+ */
276
+ export declare function renderServerVideo(motion: PoseMotion, opts?: {
277
+ width?: number;
278
+ height?: number;
279
+ background?: string;
280
+ }): Promise<Blob>;
281
+ /** Render and download a server-side 2D pose video. */
282
+ export declare function downloadServerVideo(motion: PoseMotion, opts?: {
283
+ width?: number;
284
+ height?: number;
285
+ background?: string;
286
+ }): Promise<void>;
287
+ export interface ConstraintEntry {
288
+ name: string;
289
+ filename: string;
290
+ saved_at: number;
291
+ }
292
+ export declare function listConstraints(): Promise<ConstraintEntry[]>;
293
+ export declare function saveConstraints(name: string, constraints: unknown): Promise<void>;
294
+ export declare function loadConstraints(filename: string): Promise<unknown>;
295
+ export declare function deleteConstraints(filename: string): Promise<void>;
296
+ export interface SceneEntry {
297
+ name: string;
298
+ filename: string;
299
+ saved_at: number;
300
+ }
301
+ export declare function listScenes(): Promise<SceneEntry[]>;
302
+ export declare function saveScene(name: string, scene: unknown): Promise<void>;
303
+ export declare function loadScene(filename: string): Promise<unknown>;
304
+ export declare function deleteScene(filename: string): Promise<void>;
305
+ /**
306
+ * Pick a specific frame from a PoseMotion's flat keypoints.
307
+ * Clamps to valid range.
308
+ */
309
+ export declare function pickFrame(motion: PoseMotion, idx: number): FlatKeypoints;
310
+ /**
311
+ * Fetch the 77-joint SOMA-71 rest-pose (bind-pose) positions for the
312
+ * requested identity model. Used by Pose Studio's "Load Model" button to
313
+ * enter Edit Mode WITHOUT generating a motion first — the user can grab
314
+ * joints and pose the character manually.
315
+ *
316
+ * The endpoint accepts both GET (returns the neutral-default joints) and
317
+ * POST (applies the supplied identity morphs first, returns joints that
318
+ * match the shaped body). The POST path is the fully-dynamic one: Anny's
319
+ * height/proportions sliders reshape BOTH the mesh AND the skeleton, so
320
+ * the edit-mode wires always match the body the user sees. Without it the
321
+ * skeleton would stay one fixed size (the static SOMA-77 reference) while
322
+ * the mesh grew/shrank underneath it.
323
+ *
324
+ * Calls GET /poser/skin/bind_joints?identity_model=… when no identity spec
325
+ * is supplied, POST /poser/skin/bind_joints with a JSON body otherwise.
326
+ */
327
+ export declare function fetchBindJoints(identityModel?: string,
328
+ /**
329
+ * Optional identity spec — when ANY of these is supplied, the call
330
+ * switches to POST and the backend applies them via
331
+ * `SomaXSkin.set_identity(coeffs, local_changes=, custom_targets=)`
332
+ * before reading joints, so the returned positions match the shaped
333
+ * body. Mirrors the deformSomaJoints / shaped-mesh contract.
334
+ */
335
+ identityCoeffs?: number[] | null, localChanges?: Record<string, number> | null, customTargets?: Record<string, number> | null): Promise<{
336
+ joints: [number, number, number][];
337
+ identity_model: string;
338
+ }>;
339
+ /**
340
+ * Preload (warm up) a model on the GPU so the first Generate is fast.
341
+ *
342
+ * NOTE: The old forge /admin/load endpoint was removed in the 3-container
343
+ * architecture cleanup. The KimodoPoseService singleton inside ComfyUI
344
+ * now loads lazily on first inference and stays resident, so explicit
345
+ * preloading is unnecessary. This is kept as a no-op so the frontend's
346
+ * "Load Model" button doesn't error.
347
+ */
348
+ export declare function preloadForgeService(_service: string, _model?: string): Promise<{
349
+ status: string;
350
+ service: string;
351
+ }>;
352
+ /**
353
+ * Re-skin the mesh for edited SOMA-77 joint positions.
354
+ * Calls /poser/skin/deform_soma with binary response for speed.
355
+ * Used during interactive pose editing — debounced by the caller.
356
+ *
357
+ * @param joints Float32Array of 77 x 3 joint positions
358
+ * @param identityModel Which mesh to skin ("soma" | "mhr" | "anny"). Must
359
+ * match the mesh the viewer is rendering, otherwise the deformed vertices
360
+ * won't match the bind-mesh topology and the pose will look wrong.
361
+ * @param jointsRot Optional Float32Array of 77 x 9 global rotation matrices
362
+ * (row-major 3x3 per joint). When provided, the backend skins directly with
363
+ * these exact rotations (most accurate). When omitted, the backend derives
364
+ * approximate rotations from bone directions.
365
+ * @returns base64-encoded float32 deformed vertices (V x 3) for one frame
366
+ */
367
+ export declare function deformSomaJoints(joints: Float32Array, identityModel?: string, jointsRot?: Float32Array | null,
368
+ /**
369
+ * Identity spec — when supplied, the backend applies them via
370
+ * `SomaXSkin.set_identity(coeffs, local_changes=, custom_targets=)` before
371
+ * skinning so the re-skinned verts match the loaded character (not the
372
+ * default soma bind pose). Without these, edit-mode re-skin would snap the
373
+ * mesh back to the un-morphed body on the first joint drag. All three are
374
+ * optional and model-specific (Anny uses all three; MHR ignores
375
+ * local_changes/custom_targets; SOMA ignores all three).
376
+ */
377
+ identityCoeffs?: number[] | null, localChanges?: Record<string, number> | null, customTargets?: Record<string, number> | null,
378
+ /**
379
+ * Pre-edit global rotation matrices (77×9, row-major 3×3 per joint) captured
380
+ * BEFORE the user dragged any joint. When supplied (and `jointsRot` is null),
381
+ * the backend recomputes the swing (bone direction) from `joints` while
382
+ * preserving the twist (roll around the bone axis) from these — so dragging
383
+ * a joint updates the bone direction without erasing forearm pronation,
384
+ * neck yaw, etc. This is the edit-mode drag path; `jointsRot` (explicit
385
+ * rotations) takes precedence when both are set.
386
+ */
387
+ baseRotmats?: Float32Array | null): Promise<string>;
388
+ //# sourceMappingURL=textToPose.d.ts.map