pixelkiln 0.89.6 → 0.91.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 +194 -29
- 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 +196 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +282 -14
- package/dist/index.d.ts +282 -14
- package/dist/index.js +195 -29
- package/dist/index.js.map +1 -1
- package/docs/CHARACTERS.md +73 -3
- package/docs/ENDPOINTS.md +12 -1
- package/docs/MANIFEST.md +2 -2
- package/docs/PIXELLAB.md +46 -0
- package/docs/REVISIONS.md +8 -5
- package/package.json +1 -1
- package/schema/manifest.schema.json +5 -1
- package/skills/pixelkiln/references/pixellab-roadmap.md +16 -15
- package/skills/pixelkiln/references/pixellab.md +11 -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");
|