pixelkiln 0.89.5 → 0.90.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/dist/cli.js +53 -16
- package/dist/cli.js.map +1 -1
- package/dist/client/gallery.css +1 -0
- package/dist/client/gallery.js +54 -17
- package/dist/index.cjs +53 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +36 -13
- package/dist/index.d.ts +36 -13
- package/dist/index.js +53 -16
- package/dist/index.js.map +1 -1
- package/docs/CHARACTERS.md +43 -3
- package/docs/ENDPOINTS.md +32 -0
- package/docs/MANIFEST.md +2 -2
- package/docs/REVISIONS.md +15 -8
- package/package.json +1 -1
- package/schema/manifest.schema.json +1 -1
- package/skills/pixelkiln/references/pixellab-roadmap.md +21 -2
- package/skills/pixelkiln/references/pixellab.md +8 -0
package/dist/client/gallery.css
CHANGED
|
@@ -167,6 +167,7 @@ form.edit { border:1px solid var(--accent); padding:12px 14px 14px; margin-top:1
|
|
|
167
167
|
form.edit h3 { margin:0; font:650 12.5px/1 var(--mono); color:var(--accent-soft); }
|
|
168
168
|
form.edit .row { display:grid; grid-template-columns:repeat(auto-fit, minmax(110px, 1fr)); gap:10px; }
|
|
169
169
|
.field { display:grid; gap:4px; font-size:12px; color:var(--dim); min-width:0; }
|
|
170
|
+
.field[hidden] { display:none; }
|
|
170
171
|
.field span { font-family:var(--mono); font-size:11px; }
|
|
171
172
|
.field small { font-size:11px; color:var(--dim); }
|
|
172
173
|
.field input, .field textarea, .field select { background:var(--panel-deep); border:1px solid var(--line);
|
package/dist/client/gallery.js
CHANGED
|
@@ -364,12 +364,15 @@
|
|
|
364
364
|
// src/gallery/client/src/form-kit.ts
|
|
365
365
|
var skeletonV3Allowed = (engine) => engine !== "standard";
|
|
366
366
|
var defaultLoopMode = (engine) => skeletonV3Allowed(engine) ? "skeleton-v3" : "template";
|
|
367
|
+
var SKELETON_WORDS = "skeleton-v3-words";
|
|
368
|
+
var SKELETON_WORDS_FRAMES = { min: 3, max: 15, fallback: 8 };
|
|
367
369
|
function syncSkeletonOption(select, engine) {
|
|
368
|
-
const option = [...select.options].find((o) => o.value === "skeleton-v3");
|
|
369
|
-
if (!option) return;
|
|
370
370
|
const allowed = skeletonV3Allowed(engine);
|
|
371
|
-
option.
|
|
372
|
-
|
|
371
|
+
for (const option of select.options) {
|
|
372
|
+
if (option.value !== "skeleton-v3" && option.value !== SKELETON_WORDS) continue;
|
|
373
|
+
option.disabled = !allowed;
|
|
374
|
+
option.title = allowed ? "" : option.value === SKELETON_WORDS ? "PixelLab writes the motion onto a stored 3D skeleton, which a standard base has none of; use v3 or a template." : "PixelLab poses a template only onto a v3 character. A standard base has no 3D skeleton; use template.";
|
|
375
|
+
}
|
|
373
376
|
}
|
|
374
377
|
var nextStep = () => GENERATION ? "Generate it from its record when you are ready." : "Nothing is generated until you run pixelkiln gen.";
|
|
375
378
|
async function addAssetAndOpen(body, where, what = "Added to the manifest.") {
|
|
@@ -460,6 +463,7 @@
|
|
|
460
463
|
if (isCharacter) {
|
|
461
464
|
mode.append(
|
|
462
465
|
new Option("skeleton-v3 (a template, posed by the skeleton model; steadiest)", "skeleton-v3"),
|
|
466
|
+
new Option("skeleton-v3: described in words (3-15 frames; 3 per direction measured)", SKELETON_WORDS),
|
|
463
467
|
new Option("template (a template, redrawn; 1 generation)", "template")
|
|
464
468
|
);
|
|
465
469
|
}
|
|
@@ -474,6 +478,7 @@
|
|
|
474
478
|
"skeleton-v3": "A PixelLab template posed onto the character by the skeleton model, which moves it instead of redrawing it: steadier identity and colours. 2 to 4 generations and 3 to 5 minutes per direction; beta, Tier 1 plans and up.",
|
|
475
479
|
template: "A PixelLab template, redrawn frame by frame; 1 generation regardless of frame count."
|
|
476
480
|
};
|
|
481
|
+
const wordsHint = "Needs a v3 base. The sentence is written into one 3D motion, 3 to 15 frames; the canvas can grow around the sprite. About 3 generations per direction measured; beta, Tier 1 plans and up.";
|
|
477
482
|
const templateField = field("template id", template, templateHint.template);
|
|
478
483
|
const frames = el("input");
|
|
479
484
|
frames.type = "number";
|
|
@@ -481,6 +486,7 @@
|
|
|
481
486
|
frames.max = "16";
|
|
482
487
|
frames.step = "2";
|
|
483
488
|
frames.placeholder = "8";
|
|
489
|
+
const framesField = field("frames", frames);
|
|
484
490
|
const fps = el("input");
|
|
485
491
|
fps.type = "number";
|
|
486
492
|
fps.min = "1";
|
|
@@ -504,16 +510,24 @@
|
|
|
504
510
|
form.append(row1, field("what it does", prompt, "The style still adds its prefix and suffix."));
|
|
505
511
|
if (isCharacter) form.append(templateField);
|
|
506
512
|
const row2 = el("div", "row");
|
|
507
|
-
row2.append(
|
|
513
|
+
row2.append(framesField, field("fps", fps));
|
|
508
514
|
form.append(row2);
|
|
509
|
-
|
|
515
|
+
const subjectField = subject ? field("subject override", subject) : null;
|
|
516
|
+
if (subjectField) form.append(subjectField);
|
|
510
517
|
const syncMode = () => {
|
|
511
518
|
const isTemplate = isCharacter && (mode.value === "template" || mode.value === "skeleton-v3");
|
|
512
519
|
templateField.style.display = isTemplate ? "" : "none";
|
|
513
520
|
if (isTemplate) templateField.querySelector("small").textContent = templateHint[mode.value];
|
|
514
521
|
template.required = isTemplate;
|
|
515
522
|
prompt.required = !isTemplate;
|
|
523
|
+
const words = isCharacter && mode.value === SKELETON_WORDS;
|
|
516
524
|
row2.style.display = isTemplate ? "none" : "";
|
|
525
|
+
frames.min = words ? String(SKELETON_WORDS_FRAMES.min) : "4";
|
|
526
|
+
frames.max = words ? String(SKELETON_WORDS_FRAMES.max) : "16";
|
|
527
|
+
frames.step = words ? "1" : "2";
|
|
528
|
+
if (words) frames.title = wordsHint;
|
|
529
|
+
else frames.removeAttribute("title");
|
|
530
|
+
if (subjectField) subjectField.style.display = words ? "none" : "";
|
|
517
531
|
};
|
|
518
532
|
if (isCharacter) {
|
|
519
533
|
mode.onchange = syncMode;
|
|
@@ -544,11 +558,11 @@
|
|
|
544
558
|
animation.template = template.value.trim();
|
|
545
559
|
if (mode.value === "skeleton-v3") animation.mode = "skeleton-v3";
|
|
546
560
|
} else {
|
|
547
|
-
animation.mode = mode.value;
|
|
561
|
+
animation.mode = mode.value === SKELETON_WORDS ? "skeleton-v3" : mode.value;
|
|
548
562
|
if (frames.value.trim() !== "") animation.frames = Number(frames.value);
|
|
549
563
|
if (fps.value.trim() !== "") animation.fps = Number(fps.value);
|
|
550
564
|
}
|
|
551
|
-
if (subject && subject.value.trim()) animation.subject = subject.value.trim();
|
|
565
|
+
if (subject && subject.value.trim() && mode.value !== SKELETON_WORDS) animation.subject = subject.value.trim();
|
|
552
566
|
const asset = { prompt: prompt.value, styles: [item.styleId], animation };
|
|
553
567
|
try {
|
|
554
568
|
const body = { action: "add-asset", assetId: id.value.trim(), expectedSha256: pr.manifestSha256, asset };
|
|
@@ -1216,10 +1230,11 @@
|
|
|
1216
1230
|
for (const d of ["south", "south-east", "east", "north-east", "north", "north-west", "west", "south-west"]) direction.append(new Option(d, d));
|
|
1217
1231
|
const description = el("input");
|
|
1218
1232
|
description.type = "text";
|
|
1219
|
-
description.placeholder = "
|
|
1233
|
+
description.placeholder = "e.g. a knight in steel armour holding a sword";
|
|
1234
|
+
description.required = true;
|
|
1220
1235
|
const row1 = el("div", "row");
|
|
1221
1236
|
row1.append(field("new asset id", id), field("facing", direction, "Which way the sprite faces."));
|
|
1222
|
-
form.append(row1, field("motion", prompt, "A short label for the movement; sent as PixelLab's action."), field("looks like", description));
|
|
1237
|
+
form.append(row1, field("motion", prompt, "A short label for the movement; sent as PixelLab's action."), field("looks like", description, "What the character looks like: colours, clothing, held items. PixelLab refuses the request without it."));
|
|
1223
1238
|
const source = el("select");
|
|
1224
1239
|
source.append(new Option("estimate from this sprite", "estimate"), new Option("paste poses", "paste"), new Option("use a file in the project", "file"));
|
|
1225
1240
|
const file = el("input");
|
|
@@ -1368,9 +1383,9 @@
|
|
|
1368
1383
|
assetId: id.value.trim(),
|
|
1369
1384
|
prompt: prompt.value.trim(),
|
|
1370
1385
|
direction: direction.value,
|
|
1371
|
-
keypointsFile: file.value.trim()
|
|
1386
|
+
keypointsFile: file.value.trim(),
|
|
1387
|
+
description: description.value.trim()
|
|
1372
1388
|
};
|
|
1373
|
-
if (description.value.trim()) body.description = description.value.trim();
|
|
1374
1389
|
if (source.value !== "file") {
|
|
1375
1390
|
body.set = set;
|
|
1376
1391
|
if (overwrite.checked) body.overwrite = true;
|
|
@@ -5046,6 +5061,7 @@
|
|
|
5046
5061
|
var PORTRAIT_SIZES = [16, 32, 48, 64, 128, 160];
|
|
5047
5062
|
var LOOP_MODES = [
|
|
5048
5063
|
{ id: "skeleton-v3", label: "skeleton-v3: a template, posed by the skeleton model (steadiest; 2-4 per direction)" },
|
|
5064
|
+
{ id: SKELETON_WORDS, label: "skeleton-v3: described in words (3-15 frames; 3 per direction measured)" },
|
|
5049
5065
|
{ id: "template", label: "template: a template, redrawn (1 per direction)" },
|
|
5050
5066
|
{ id: "v3", label: "v3: described in words" },
|
|
5051
5067
|
{ id: "pro", label: "pro: described in words, sequential (10-25 per direction)" }
|
|
@@ -5262,13 +5278,17 @@
|
|
|
5262
5278
|
const engineId = loopEngine();
|
|
5263
5279
|
syncSkeletonOption(row2.mode, engineId);
|
|
5264
5280
|
if (!row2.picked) row2.mode.value = defaultLoopMode(engineId);
|
|
5265
|
-
else if (
|
|
5281
|
+
else if (!skeletonV3Allowed(engineId)) {
|
|
5282
|
+
if (row2.mode.value === "skeleton-v3") row2.mode.value = "template";
|
|
5283
|
+
else if (row2.mode.value === SKELETON_WORDS) row2.mode.value = "v3";
|
|
5284
|
+
}
|
|
5266
5285
|
syncLoopMode(row2);
|
|
5267
5286
|
}
|
|
5268
5287
|
function syncLoopMode(row2) {
|
|
5269
5288
|
const t = templated(row2.mode.value);
|
|
5270
5289
|
row2.what.placeholder = t ? "template, e.g. walk, breathing-idle, running-8-frames" : 'the motion, e.g. "swings a sword overhead"';
|
|
5271
5290
|
row2.what.title = t ? "A PixelLab template id: pick one, or type any id PixelLab has" : "What the loop does";
|
|
5291
|
+
row2.framesField.hidden = row2.mode.value !== SKELETON_WORDS;
|
|
5272
5292
|
if (t) row2.what.setAttribute("list", templateList.id);
|
|
5273
5293
|
else row2.what.removeAttribute("list");
|
|
5274
5294
|
syncPreview(row2);
|
|
@@ -5332,6 +5352,14 @@
|
|
|
5332
5352
|
what.type = "text";
|
|
5333
5353
|
what.required = true;
|
|
5334
5354
|
what.value = defaults.what ?? "";
|
|
5355
|
+
const frames = el("input");
|
|
5356
|
+
frames.type = "number";
|
|
5357
|
+
frames.min = String(SKELETON_WORDS_FRAMES.min);
|
|
5358
|
+
frames.max = String(SKELETON_WORDS_FRAMES.max);
|
|
5359
|
+
frames.step = "1";
|
|
5360
|
+
frames.placeholder = String(SKELETON_WORDS_FRAMES.fallback);
|
|
5361
|
+
frames.value = defaults.frames ?? "";
|
|
5362
|
+
const framesField = field("frames", frames, "3 to 15; each direction is billed separately.");
|
|
5335
5363
|
const dirs = el("div", "studio-dirs");
|
|
5336
5364
|
const boxes = /* @__PURE__ */ new Map();
|
|
5337
5365
|
for (const d of COMPASS2) {
|
|
@@ -5347,7 +5375,7 @@
|
|
|
5347
5375
|
remove.type = "button";
|
|
5348
5376
|
const preview = el("div", "studio-tpl-preview");
|
|
5349
5377
|
preview.hidden = true;
|
|
5350
|
-
const row2 = { id, mode, picked: defaults.mode !== void 0, what, directions: boxes, wrap, preview };
|
|
5378
|
+
const row2 = { id, mode, picked: defaults.mode !== void 0, what, frames, framesField, directions: boxes, wrap, preview };
|
|
5351
5379
|
what.addEventListener("input", () => syncPreview(row2));
|
|
5352
5380
|
remove.onclick = () => {
|
|
5353
5381
|
loops2.splice(loops2.indexOf(row2), 1);
|
|
@@ -5361,7 +5389,11 @@
|
|
|
5361
5389
|
schedulePrice();
|
|
5362
5390
|
};
|
|
5363
5391
|
const line = el("div", "row");
|
|
5364
|
-
line.append(field("loop id", id), field("mode", mode), field("template or motion", what));
|
|
5392
|
+
line.append(field("loop id", id), field("mode", mode), field("template or motion", what), framesField);
|
|
5393
|
+
frames.oninput = () => {
|
|
5394
|
+
schedulePrice();
|
|
5395
|
+
keepDraft();
|
|
5396
|
+
};
|
|
5365
5397
|
wrap.append(line, preview, dirs, remove);
|
|
5366
5398
|
loopList.append(wrap);
|
|
5367
5399
|
loops2.push(row2);
|
|
@@ -5389,6 +5421,11 @@
|
|
|
5389
5421
|
if (templated(row2.mode.value)) {
|
|
5390
5422
|
animation.template = row2.what.value.trim();
|
|
5391
5423
|
if (row2.mode.value === "skeleton-v3") animation.mode = "skeleton-v3";
|
|
5424
|
+
} else if (row2.mode.value === SKELETON_WORDS) {
|
|
5425
|
+
animation.mode = "skeleton-v3";
|
|
5426
|
+
asset.prompt = row2.what.value.trim();
|
|
5427
|
+
const count = row2.frames.value.trim();
|
|
5428
|
+
if (count !== "" && Number(count) !== SKELETON_WORDS_FRAMES.fallback) animation.frames = Number(count);
|
|
5392
5429
|
} else {
|
|
5393
5430
|
animation.mode = row2.mode.value;
|
|
5394
5431
|
asset.prompt = row2.what.value.trim();
|
|
@@ -5609,7 +5646,7 @@
|
|
|
5609
5646
|
baseId: baseId.value,
|
|
5610
5647
|
prompt: prompt.value,
|
|
5611
5648
|
reference,
|
|
5612
|
-
loops: loops2.map((r) => ({ id: r.id.value, mode: r.mode.value, what: r.what.value, directions: [...r.directions].filter(([, b]) => b.checked).map(([d]) => d) })),
|
|
5649
|
+
loops: loops2.map((r) => ({ id: r.id.value, mode: r.mode.value, what: r.what.value, frames: r.frames.value, directions: [...r.directions].filter(([, b]) => b.checked).map(([d]) => d) })),
|
|
5613
5650
|
portrait: portrait.checked,
|
|
5614
5651
|
portraitSize: portraitSize.value
|
|
5615
5652
|
});
|
|
@@ -5656,7 +5693,7 @@
|
|
|
5656
5693
|
portrait.checked = Boolean(draft.portrait);
|
|
5657
5694
|
set(portraitSize, draft.portraitSize);
|
|
5658
5695
|
syncStyle();
|
|
5659
|
-
for (const l of Array.isArray(draft.loops) ? draft.loops : []) addLoopRow({ id: l.id, mode: l.mode, what: l.what, directions: l.directions });
|
|
5696
|
+
for (const l of Array.isArray(draft.loops) ? draft.loops : []) addLoopRow({ id: l.id, mode: l.mode, what: l.what, frames: l.frames, directions: l.directions });
|
|
5660
5697
|
const note = el("div", "notice studio-draft");
|
|
5661
5698
|
note.append(document.createTextNode("Picked up the draft you left " + (fmtWhen(draft.savedAt) || "earlier") + ". "));
|
|
5662
5699
|
const discard = el("button", "linkish", "Start over");
|
package/dist/index.cjs
CHANGED
|
@@ -692,16 +692,21 @@ var CharacterAnimationSchema = import_zod.z.object({
|
|
|
692
692
|
* `src/loop-directions.ts`.
|
|
693
693
|
*/
|
|
694
694
|
directions: import_zod.z.array(CharacterDirectionSchema).min(1).optional(),
|
|
695
|
-
/**
|
|
696
|
-
|
|
695
|
+
/**
|
|
696
|
+
* Frames to generate without a template. A v3 loop takes an even 4 to 16;
|
|
697
|
+
* a sentence loop (`mode: "skeleton-v3"` with no `template`) takes 3 to 15.
|
|
698
|
+
*/
|
|
699
|
+
frames: import_zod.z.number().int().min(3).max(16).optional(),
|
|
697
700
|
/** Playback rate recorded with the frames; PixelLab does not store one. */
|
|
698
701
|
fps: import_zod.z.number().int().min(1).max(60).default(8),
|
|
699
702
|
/** v3 only: keep the resting pose as frame 0, so `frames` generated frames land as `frames + 1` files. */
|
|
700
703
|
keepFirstFrame: import_zod.z.boolean().default(true),
|
|
701
704
|
/**
|
|
702
705
|
* `template` when a template is named, otherwise `v3`; `skeleton-v3`
|
|
703
|
-
* poses a template with PixelLab's skeleton model instead
|
|
704
|
-
*
|
|
706
|
+
* poses a template with PixelLab's skeleton model instead, or, with no
|
|
707
|
+
* template, animates the asset's prompt as a sentence written into one 3D
|
|
708
|
+
* motion (a v3 base; 3-15 `frames`); `pro` for the sequential
|
|
709
|
+
* high-quality engine.
|
|
705
710
|
*/
|
|
706
711
|
mode: CharacterAnimationModeSchema.optional(),
|
|
707
712
|
/**
|
|
@@ -731,15 +736,23 @@ var CharacterAnimationSchema = import_zod.z.object({
|
|
|
731
736
|
path: ["directions"]
|
|
732
737
|
});
|
|
733
738
|
}
|
|
734
|
-
|
|
735
|
-
|
|
739
|
+
const sentence = animation.mode === "skeleton-v3" && !animation.template;
|
|
740
|
+
if (animation.frames !== void 0 && !sentence) {
|
|
741
|
+
if (animation.frames % 2 !== 0) {
|
|
742
|
+
context.addIssue({ code: import_zod.z.ZodIssueCode.custom, message: "frames must be even", path: ["frames"] });
|
|
743
|
+
} else if (animation.frames < 4) {
|
|
744
|
+
context.addIssue({ code: import_zod.z.ZodIssueCode.custom, message: "frames must be 4 to 16", path: ["frames"] });
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
if (animation.frames !== void 0 && sentence && animation.frames > 15) {
|
|
748
|
+
context.addIssue({ code: import_zod.z.ZodIssueCode.custom, message: "a skeleton-v3 loop from a sentence takes 3 to 15 frames", path: ["frames"] });
|
|
736
749
|
}
|
|
737
750
|
const mode2 = animation.mode ?? (animation.template ? "template" : "v3");
|
|
738
751
|
for (const key of ["startFrame", "endFrame", "enhancePrompt"]) {
|
|
739
752
|
if (animation[key] !== void 0 && mode2 !== "v3") {
|
|
740
753
|
context.addIssue({
|
|
741
754
|
code: import_zod.z.ZodIssueCode.custom,
|
|
742
|
-
message: `${key} is for v3 loops; a ${mode2} loop ${mode2 === "template" || mode2 === "skeleton-v3" ?
|
|
755
|
+
message: `${key} is for v3 loops; a ${mode2} loop ${mode2 === "template" || mode2 === "skeleton-v3" ? `starts from the character's rotation and ${animation.template ? "follows its template" : "animates its sentence"}` : "takes neither"}`,
|
|
743
756
|
path: [key]
|
|
744
757
|
});
|
|
745
758
|
}
|
|
@@ -763,8 +776,11 @@ var CharacterAnimationSchema = import_zod.z.object({
|
|
|
763
776
|
path: ["frames"]
|
|
764
777
|
});
|
|
765
778
|
}
|
|
766
|
-
if (!animation.template &&
|
|
767
|
-
context.addIssue({ code: import_zod.z.ZodIssueCode.custom, message:
|
|
779
|
+
if (!animation.template && animation.mode === "template") {
|
|
780
|
+
context.addIssue({ code: import_zod.z.ZodIssueCode.custom, message: "template mode needs a template", path: ["template"] });
|
|
781
|
+
}
|
|
782
|
+
if (sentence && animation.subject !== void 0) {
|
|
783
|
+
context.addIssue({ code: import_zod.z.ZodIssueCode.custom, message: "subject does not apply to a skeleton-v3 loop from a sentence; describe the motion in the prompt", path: ["subject"] });
|
|
768
784
|
}
|
|
769
785
|
});
|
|
770
786
|
var RevisionDitheringSchema = import_zod.z.enum(["none", "2x2", "4x4", "8x8"]);
|
|
@@ -2992,7 +3008,10 @@ var PixelLabClient = class {
|
|
|
2992
3008
|
* Every mode but skeleton-v3 goes to `/animate-character`, the path whose
|
|
2993
3009
|
* shapes were measured live. skeleton-v3 is documented only on
|
|
2994
3010
|
* `/characters/animations`, which takes the same request and answers with
|
|
2995
|
-
* the same job list (plus an `animation_group_id`).
|
|
3011
|
+
* the same job list (plus an `animation_group_id`). With no `template` it
|
|
3012
|
+
* animates `actionDescription` as a sentence, 3 to 15 `frameCount` frames
|
|
3013
|
+
* written into one 3D motion; measured on a v3 character (8 frames, 64px:
|
|
3014
|
+
* 3 generations, frames back at 96x96).
|
|
2996
3015
|
*/
|
|
2997
3016
|
async animateCharacter(args) {
|
|
2998
3017
|
const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
|
|
@@ -3007,7 +3026,8 @@ var PixelLabClient = class {
|
|
|
3007
3026
|
...args.template ? { template_animation_id: args.template } : {},
|
|
3008
3027
|
...args.actionDescription ? { action_description: args.actionDescription } : {},
|
|
3009
3028
|
...args.description ? { description: args.description } : {},
|
|
3010
|
-
|
|
3029
|
+
// A template fixes its own frame count; v3 and a skeleton-v3 sentence take one.
|
|
3030
|
+
...(args.mode === "v3" || args.mode === "skeleton-v3" && !args.template) && args.frameCount ? { frame_count: args.frameCount } : {},
|
|
3011
3031
|
...args.mode === "v3" && args.keepFirstFrame === false ? { keep_first_frame: false } : {},
|
|
3012
3032
|
...args.mode === "v3" && args.startFrame ? { custom_start_frame: encode(args.startFrame) } : {},
|
|
3013
3033
|
...args.mode === "v3" && args.endFrame ? { end_frame: encode(args.endFrame) } : {},
|
|
@@ -5659,6 +5679,16 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5659
5679
|
if (skeletonTemplate && !CHARACTER_TEMPLATES.includes(skeletonTemplate)) {
|
|
5660
5680
|
throw new Error(`PixelLab animate-skeleton template "${skeletonTemplate}" is not one of ${CHARACTER_TEMPLATES.join(", ")}`);
|
|
5661
5681
|
}
|
|
5682
|
+
if (!spec.revision.description?.trim()) {
|
|
5683
|
+
throw new Error(
|
|
5684
|
+
`${spec.styleId}/${spec.assetId}: an animate-skeleton revision needs revision.description, what the character looks like (for example "a knight in steel armour holding a sword"); PixelLab refuses the request without it`
|
|
5685
|
+
);
|
|
5686
|
+
}
|
|
5687
|
+
if (!spec.prompt.trim()) {
|
|
5688
|
+
throw new Error(
|
|
5689
|
+
`${spec.styleId}/${spec.assetId}: an animate-skeleton revision needs a prompt, a short motion label (for example "swinging the sword")`
|
|
5690
|
+
);
|
|
5691
|
+
}
|
|
5662
5692
|
}
|
|
5663
5693
|
if (spec.revision?.mode === "animate" || spec.revision?.mode === "animate-pixminimax") {
|
|
5664
5694
|
const { sourceWidth: width, sourceHeight: height, mode: mode2, frames, lastFrameWidth, lastFrameHeight } = spec.revision;
|
|
@@ -6062,12 +6092,18 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6062
6092
|
`${spec.styleId}/${spec.assetId} animates "${character.animation.direction}", but its character has ${character.directions} directions: ${allowed.join(", ")}`
|
|
6063
6093
|
);
|
|
6064
6094
|
}
|
|
6065
|
-
|
|
6066
|
-
|
|
6095
|
+
const sentence = character.animation.mode === "skeleton-v3" && !character.animation.template;
|
|
6096
|
+
if (character.animation.mode !== "template" && (character.animation.mode !== "skeleton-v3" || sentence) && !spec.prompt.trim()) {
|
|
6097
|
+
throw new Error(
|
|
6098
|
+
`${spec.styleId}/${spec.assetId} needs a prompt describing the motion, or a template` + (sentence ? ' (a skeleton-v3 loop with no template animates its prompt, e.g. "swinging a sword in a wide arc")' : "")
|
|
6099
|
+
);
|
|
6100
|
+
}
|
|
6101
|
+
if (sentence && (character.animation.frames < 3 || character.animation.frames > 15)) {
|
|
6102
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: a skeleton-v3 loop from a sentence takes 3 to 15 frames; ${character.animation.frames} is out of range`);
|
|
6067
6103
|
}
|
|
6068
6104
|
if (character.animation.mode === "skeleton-v3" && character.mode === "standard") {
|
|
6069
6105
|
throw new Error(
|
|
6070
|
-
`${spec.styleId}/${spec.assetId}: skeleton-v3 poses a template onto a 3D skeleton, which a ${character.mode} character has none of; use a v3 base (style mode: "v3"), or mode: "template" for this loop`
|
|
6106
|
+
sentence ? `${spec.styleId}/${spec.assetId}: a skeleton-v3 loop from a sentence writes the motion onto a 3D skeleton, which a ${character.mode} character has none of; use a v3 base (style mode: "v3"), or mode: "template" with a template for this loop` : `${spec.styleId}/${spec.assetId}: skeleton-v3 poses a template onto a 3D skeleton, which a ${character.mode} character has none of; use a v3 base (style mode: "v3"), or mode: "template" for this loop`
|
|
6071
6107
|
);
|
|
6072
6108
|
}
|
|
6073
6109
|
}
|
|
@@ -18448,7 +18484,8 @@ var SkeletonAnimationRequestSchema = import_zod12.z.object({
|
|
|
18448
18484
|
/** The motion, sent as PixelLab's `action`. */
|
|
18449
18485
|
prompt: import_zod12.z.string().min(1),
|
|
18450
18486
|
direction: CharacterDirectionSchema,
|
|
18451
|
-
|
|
18487
|
+
/** What the character looks like; PixelLab refuses the request without it. */
|
|
18488
|
+
description: import_zod12.z.string().min(1),
|
|
18452
18489
|
skeletonTemplate: import_zod12.z.string().min(1).optional(),
|
|
18453
18490
|
/** Manifest-relative JSON path inside the project. */
|
|
18454
18491
|
keypointsFile: import_zod12.z.string().min(1),
|
|
@@ -18498,7 +18535,7 @@ async function createSkeletonAnimation(manifestPath, request) {
|
|
|
18498
18535
|
from: request.from,
|
|
18499
18536
|
keypointsFile: relative.split(import_node_path33.default.sep).join("/"),
|
|
18500
18537
|
direction: request.direction,
|
|
18501
|
-
|
|
18538
|
+
description: request.description,
|
|
18502
18539
|
...request.skeletonTemplate ? { skeletonTemplate: request.skeletonTemplate } : {}
|
|
18503
18540
|
}
|
|
18504
18541
|
}
|