pixelkiln 0.54.0 → 0.55.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 +563 -20
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +563 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +394 -115
- package/dist/index.d.ts +394 -115
- package/dist/index.js +563 -20
- package/dist/index.js.map +1 -1
- package/docs/CHARACTERS.md +6 -0
- package/docs/GENERATORS.md +49 -0
- package/docs/MANIFEST.md +1 -1
- package/package.json +1 -1
- package/schema/manifest.schema.json +18 -2
- package/schema/recipe.schema.json +9 -1
- package/skills/pixelkiln/references/pixellab-roadmap.md +22 -11
- package/skills/pixelkiln/references/pixellab.md +27 -0
package/dist/cli.js
CHANGED
|
@@ -683,6 +683,23 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
683
683
|
concept: spec.character.concept?.sha256,
|
|
684
684
|
styleAnchor: spec.character.styleAnchor ? { of: spec.character.styleAnchor.assetId, sha256: spec.character.styleAnchor.sha256 } : void 0
|
|
685
685
|
} : void 0,
|
|
686
|
+
// An objectPro family: same shape as character above, minus the
|
|
687
|
+
// fields it has none of (mode/template/proportions/isometric/concept/
|
|
688
|
+
// styleAnchor) — see ResolvedObjectPro.
|
|
689
|
+
objectPro: spec.objectPro ? {
|
|
690
|
+
kind: spec.objectPro.kind,
|
|
691
|
+
directions: spec.objectPro.directions,
|
|
692
|
+
parent: spec.objectPro.parentAssetId,
|
|
693
|
+
parentSha256: spec.objectPro.parentSha256 ?? null,
|
|
694
|
+
state: spec.objectPro.state,
|
|
695
|
+
animation: spec.objectPro.animation ? {
|
|
696
|
+
...spec.objectPro.animation,
|
|
697
|
+
startFrame: spec.objectPro.animation.startFrame?.sha256,
|
|
698
|
+
endFrame: spec.objectPro.animation.endFrame?.sha256
|
|
699
|
+
} : void 0,
|
|
700
|
+
styleTraits: spec.objectPro.styleTraits,
|
|
701
|
+
reference: spec.objectPro.reference?.sha256
|
|
702
|
+
} : void 0,
|
|
686
703
|
// A mirror's bytes come from its source's recorded outputs; the plan
|
|
687
704
|
// compares those directly, so only the choice of source is identity.
|
|
688
705
|
mirror: spec.mirror ? { of: spec.mirror.sourceAssetId } : void 0,
|
|
@@ -709,7 +726,7 @@ import path2 from "path";
|
|
|
709
726
|
// src/types.ts
|
|
710
727
|
import { z } from "zod";
|
|
711
728
|
var MediaTypeSchema = z.enum(["image/png", "image/gif"]);
|
|
712
|
-
var GeneratorSchema = z.enum(["1dir", "map", "pixflux", "tiles", "animation", "frames", "character", "terrain", "imagePro", "isometricTile"]);
|
|
729
|
+
var GeneratorSchema = z.enum(["1dir", "map", "pixflux", "tiles", "animation", "frames", "character", "terrain", "imagePro", "isometricTile", "objectPro"]);
|
|
713
730
|
var GridConfidenceSchema = z.enum(["low", "medium", "high"]);
|
|
714
731
|
var RevisionModeSchema = z.enum(["image-to-image", "inpaint", "outpaint"]);
|
|
715
732
|
function tileVariationCount(descriptions) {
|
|
@@ -1089,6 +1106,14 @@ var StyleObjectSchema = z.object({
|
|
|
1089
1106
|
* (~50%, the API default).
|
|
1090
1107
|
*/
|
|
1091
1108
|
isometricTileShape: z.enum(["thin tile", "thick tile", "block"]).optional(),
|
|
1109
|
+
/**
|
|
1110
|
+
* `objectPro` generator only. Rotations captured for a base: 1 (the API
|
|
1111
|
+
* still draws and stores a single facing) or the full 8. The API default
|
|
1112
|
+
* is 8. Unlike `character`, `objectPro` has no skeleton/template modes
|
|
1113
|
+
* and so no `mode`/`template` fields of its own — `/create-object-pro-flash`
|
|
1114
|
+
* is the only base-creation path.
|
|
1115
|
+
*/
|
|
1116
|
+
objectDirections: z.union([z.literal(1), z.literal(8)]).optional(),
|
|
1092
1117
|
/**
|
|
1093
1118
|
* `pixflux` only. Whether to strip the generated background.
|
|
1094
1119
|
*
|
|
@@ -1293,25 +1318,41 @@ var AssetSchema = z.object({
|
|
|
1293
1318
|
revision: RevisionSchema.optional(),
|
|
1294
1319
|
/**
|
|
1295
1320
|
* The provider's own id for art that already exists on the account, so
|
|
1296
|
-
* `adopt` can map it without matching bytes: an object id
|
|
1297
|
-
*
|
|
1298
|
-
*
|
|
1321
|
+
* `adopt` can map it without matching bytes: an object id (an
|
|
1322
|
+
* `objectPro` base and state included, since they share PixelLab's
|
|
1323
|
+
* `/objects` identity space), a character id, or `<id>#<animation group
|
|
1324
|
+
* id>` for a character or `objectPro` animation. Not part of the spec's
|
|
1325
|
+
* identity.
|
|
1299
1326
|
*/
|
|
1300
1327
|
remoteId: z.string().min(1).optional(),
|
|
1301
|
-
/**
|
|
1328
|
+
/**
|
|
1329
|
+
* `character` or `objectPro` styles: this asset is a pose or edit of
|
|
1330
|
+
* another asset in the same family. `objectPro` ignores
|
|
1331
|
+
* `paletteFromReference`/`canvas` — the API has no equivalent, and
|
|
1332
|
+
* always inherits the parent's exact canvas.
|
|
1333
|
+
*/
|
|
1302
1334
|
state: CharacterStateSchema.optional(),
|
|
1303
|
-
/**
|
|
1335
|
+
/**
|
|
1336
|
+
* `character` or `objectPro` styles: this asset is a loop of another
|
|
1337
|
+
* asset in the same family, in one direction. `objectPro` has no
|
|
1338
|
+
* skeleton/template concept, so `template`/`subject`/`outline`/
|
|
1339
|
+
* `shading`/`detail` are rejected on an `objectPro` animation rather
|
|
1340
|
+
* than silently ignored — describe the motion in the asset's own
|
|
1341
|
+
* `prompt` instead.
|
|
1342
|
+
*/
|
|
1304
1343
|
animation: CharacterAnimationSchema.optional(),
|
|
1305
1344
|
/** `character` styles, `standard` humanoid bases: this character's proportions, over the style's. */
|
|
1306
1345
|
proportions: CharacterProportionsSchema.optional(),
|
|
1307
1346
|
/**
|
|
1308
|
-
* `character` styles, bases only: the
|
|
1309
|
-
* PixelLab rotates into the other directions instead of
|
|
1310
|
-
* the prompt. A manifest-relative PNG or JPEG of the
|
|
1311
|
-
* sprite, or an object keyed by direction (`{ "south":
|
|
1312
|
-
* ... }`); quadrupeds in `standard` mode need
|
|
1313
|
-
* `standard` takes more than south. `
|
|
1314
|
-
*
|
|
1347
|
+
* `character` or `objectPro` styles, bases only: the subject's own
|
|
1348
|
+
* sprite, which PixelLab rotates into the other directions instead of
|
|
1349
|
+
* drawing from the prompt. A manifest-relative PNG or JPEG of the
|
|
1350
|
+
* south-facing sprite, or an object keyed by direction (`{ "south":
|
|
1351
|
+
* ..., "east": ... }`); quadrupeds in `standard` character mode need
|
|
1352
|
+
* south and east, and only `standard` takes more than south. `objectPro`
|
|
1353
|
+
* only ever reads the south image, since `/create-object-pro-flash`
|
|
1354
|
+
* rotates from one frame. `standard` wants each image at the style's
|
|
1355
|
+
* size; v3 accepts up to 256px, pro up to 168px. The prompt
|
|
1315
1356
|
* still guides the result.
|
|
1316
1357
|
*/
|
|
1317
1358
|
reference: z.union([z.string().min(1), z.record(CharacterDirectionSchema, z.string().min(1))]).optional(),
|
|
@@ -3797,6 +3838,7 @@ async function inspectRevisionReadiness(spec, lock) {
|
|
|
3797
3838
|
if (spec.mirror) return inspectMirror(spec, lock, /* @__PURE__ */ new Set());
|
|
3798
3839
|
if (spec.character?.parentSpec) return inspectCharacterParent(spec, lock, /* @__PURE__ */ new Set());
|
|
3799
3840
|
if (spec.character?.styleAnchor) return inspectStyleAnchor(spec, lock, /* @__PURE__ */ new Set());
|
|
3841
|
+
if (spec.objectPro?.parentSpec) return inspectObjectProParent(spec, lock, /* @__PURE__ */ new Set());
|
|
3800
3842
|
if (!spec.revision) return null;
|
|
3801
3843
|
return inspectRevision(spec, lock, /* @__PURE__ */ new Set());
|
|
3802
3844
|
}
|
|
@@ -3838,6 +3880,24 @@ async function inspectCharacterParent(spec, lock, seen) {
|
|
|
3838
3880
|
}
|
|
3839
3881
|
return { ready: true, reason: `${label} is current` };
|
|
3840
3882
|
}
|
|
3883
|
+
async function inspectObjectProParent(spec, lock, seen) {
|
|
3884
|
+
const object = spec.objectPro;
|
|
3885
|
+
const parentSpec = object.parentSpec;
|
|
3886
|
+
const label = `${object.kind} parent ${spec.styleId}/${object.parentAssetId}`;
|
|
3887
|
+
const dependency = await inspectParent(parentSpec, lock, seen);
|
|
3888
|
+
if (!dependency.ready) return { ready: false, reason: `${label} is not ready: ${dependency.reason}` };
|
|
3889
|
+
if (!object.parentSha256 || !object.parentFile || !existsSync4(object.parentFile)) {
|
|
3890
|
+
return { ready: false, reason: `${label} has no generated south-facing file yet` };
|
|
3891
|
+
}
|
|
3892
|
+
if (await sha256File(object.parentFile) !== object.parentSha256) {
|
|
3893
|
+
return { ready: false, reason: `${label} changed after the manifest was resolved` };
|
|
3894
|
+
}
|
|
3895
|
+
const parentEntry = lock.entries[lockKey(parentSpec.styleId, parentSpec.assetId)];
|
|
3896
|
+
if (!parentEntry?.objectId) {
|
|
3897
|
+
return { ready: false, reason: `${label} has no PixelLab object id recorded` };
|
|
3898
|
+
}
|
|
3899
|
+
return { ready: true, reason: `${label} is current` };
|
|
3900
|
+
}
|
|
3841
3901
|
async function inspectRevision(spec, lock, seen) {
|
|
3842
3902
|
const revision = spec.revision;
|
|
3843
3903
|
if (!revision) return { ready: true, reason: "asset has no revision inputs" };
|
|
@@ -3889,6 +3949,10 @@ async function inspectParent(spec, lock, seen) {
|
|
|
3889
3949
|
const inputs = await inspectStyleAnchor(spec, lock, nextSeen);
|
|
3890
3950
|
if (!inputs.ready) return inputs;
|
|
3891
3951
|
}
|
|
3952
|
+
if (spec.objectPro?.parentSpec) {
|
|
3953
|
+
const inputs = await inspectObjectProParent(spec, lock, nextSeen);
|
|
3954
|
+
if (!inputs.ready) return inputs;
|
|
3955
|
+
}
|
|
3892
3956
|
if (spec.quality) {
|
|
3893
3957
|
const quality = await inspectQualityProfile(spec, lock);
|
|
3894
3958
|
return quality?.state === "approved" ? { ready: true, reason: "approved quality output is current" } : {
|
|
@@ -3933,7 +3997,8 @@ function dependencies(spec) {
|
|
|
3933
3997
|
spec.mirror?.sourceSpec,
|
|
3934
3998
|
spec.revision?.sourceSpec,
|
|
3935
3999
|
spec.character?.parentSpec,
|
|
3936
|
-
spec.character?.styleAnchor?.spec
|
|
4000
|
+
spec.character?.styleAnchor?.spec,
|
|
4001
|
+
spec.objectPro?.parentSpec
|
|
3937
4002
|
].filter((parent) => Boolean(parent));
|
|
3938
4003
|
}
|
|
3939
4004
|
function resumeCommandForStatus(status) {
|
|
@@ -5281,9 +5346,23 @@ var RevisionJobSubmitSchema = z2.object({
|
|
|
5281
5346
|
background_job_id: z2.string().min(1),
|
|
5282
5347
|
status: z2.string().default("processing")
|
|
5283
5348
|
}).passthrough();
|
|
5349
|
+
var ObjectAnimationGroupSchema = z2.object({
|
|
5350
|
+
animation_group_id: z2.string().min(1),
|
|
5351
|
+
display_name: z2.string().nullable().default(null),
|
|
5352
|
+
description: z2.string().default(""),
|
|
5353
|
+
frame_count: z2.number().int().default(0),
|
|
5354
|
+
directions: z2.array(
|
|
5355
|
+
z2.object({
|
|
5356
|
+
direction: z2.string(),
|
|
5357
|
+
storage_urls: z2.record(z2.unknown()).default({}),
|
|
5358
|
+
created_at: z2.string()
|
|
5359
|
+
}).passthrough()
|
|
5360
|
+
).default([])
|
|
5361
|
+
}).passthrough();
|
|
5284
5362
|
var PixelLabObjectSchema = z2.object({
|
|
5285
5363
|
id: z2.string().min(1),
|
|
5286
5364
|
name: z2.string().nullable().default(null),
|
|
5365
|
+
state_name: z2.string().nullable().optional(),
|
|
5287
5366
|
prompt: z2.string().default(""),
|
|
5288
5367
|
size: z2.object({ width: z2.number().int().positive(), height: z2.number().int().positive() }),
|
|
5289
5368
|
directions: z2.number().default(0),
|
|
@@ -5295,7 +5374,9 @@ var PixelLabObjectSchema = z2.object({
|
|
|
5295
5374
|
tags: z2.array(z2.string()).default([]),
|
|
5296
5375
|
status: z2.string().nullable().default(null),
|
|
5297
5376
|
progress_percent: z2.number().nullable().optional(),
|
|
5298
|
-
eta_seconds: z2.number().nullable().optional()
|
|
5377
|
+
eta_seconds: z2.number().nullable().optional(),
|
|
5378
|
+
group_id: z2.string().nullable().optional(),
|
|
5379
|
+
animations: z2.array(ObjectAnimationGroupSchema).default([])
|
|
5299
5380
|
}).passthrough();
|
|
5300
5381
|
var MapObjectSchema = z2.object({
|
|
5301
5382
|
object_id: z2.string().min(1),
|
|
@@ -5320,6 +5401,28 @@ var AnimateSubmitSchema = z2.object({
|
|
|
5320
5401
|
status: z2.string().default("processing"),
|
|
5321
5402
|
usage: UsageSchema
|
|
5322
5403
|
}).passthrough();
|
|
5404
|
+
var ObjectProSubmitSchema = z2.object({
|
|
5405
|
+
object_id: z2.string().min(1),
|
|
5406
|
+
background_job_id: z2.string().min(1),
|
|
5407
|
+
status: z2.string().default("processing"),
|
|
5408
|
+
usage: UsageSchema
|
|
5409
|
+
}).passthrough();
|
|
5410
|
+
var DirectionSubmissionSchema = z2.object({
|
|
5411
|
+
direction: z2.string(),
|
|
5412
|
+
status: z2.string(),
|
|
5413
|
+
background_job_id: z2.string().nullable().default(null),
|
|
5414
|
+
animation_id: z2.string().nullable().default(null)
|
|
5415
|
+
}).passthrough();
|
|
5416
|
+
var AnimateObjectSubmitSchema = z2.object({
|
|
5417
|
+
animation_group_id: z2.string().min(1),
|
|
5418
|
+
object_id: z2.string().min(1),
|
|
5419
|
+
mode: z2.string(),
|
|
5420
|
+
frame_count: z2.number().int(),
|
|
5421
|
+
display_name: z2.string().nullable().default(null),
|
|
5422
|
+
description: z2.string().default(""),
|
|
5423
|
+
submissions: z2.array(DirectionSubmissionSchema),
|
|
5424
|
+
usage: UsageSchema
|
|
5425
|
+
}).passthrough();
|
|
5323
5426
|
var CharacterAnimationDirectionSchema = z2.object({
|
|
5324
5427
|
direction: z2.string(),
|
|
5325
5428
|
frame_count: z2.number().int().min(0),
|
|
@@ -5622,6 +5725,81 @@ var PixelLabClient = class {
|
|
|
5622
5725
|
"get object"
|
|
5623
5726
|
);
|
|
5624
5727
|
}
|
|
5728
|
+
/**
|
|
5729
|
+
* A standalone object with no skeleton, one call draws the south sprite
|
|
5730
|
+
* (or takes the author's) and rotates it with v3 — the character
|
|
5731
|
+
* pro-flash pattern, minus `template_id` (objects have no skeleton) and
|
|
5732
|
+
* with a real `n_directions` choice (1 or 8, character pro-flash is
|
|
5733
|
+
* always 8). Async only: `/create-object-pro-flash` has no sync response.
|
|
5734
|
+
*/
|
|
5735
|
+
async createObjectProFlash(args) {
|
|
5736
|
+
const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
|
|
5737
|
+
const traits = args.styleTraits;
|
|
5738
|
+
const body = {
|
|
5739
|
+
description: args.description,
|
|
5740
|
+
n_directions: args.directions,
|
|
5741
|
+
...args.reference ? { first_frame: encode(args.reference) } : { image_size: { width: args.size, height: args.size } },
|
|
5742
|
+
...args.view ? { view: args.view } : {},
|
|
5743
|
+
...args.seed != null ? { seed: args.seed } : {},
|
|
5744
|
+
...args.styleReference && args.styleReferenceSize ? {
|
|
5745
|
+
style_image: { image: encode(args.styleReference), size: args.styleReferenceSize },
|
|
5746
|
+
...traits ? {
|
|
5747
|
+
style_options: {
|
|
5748
|
+
...traits.palette !== void 0 ? { color_palette: traits.palette } : {},
|
|
5749
|
+
...traits.outline !== void 0 ? { outline: traits.outline } : {},
|
|
5750
|
+
...traits.detail !== void 0 ? { detail: traits.detail } : {},
|
|
5751
|
+
...traits.shading !== void 0 ? { shading: traits.shading } : {}
|
|
5752
|
+
}
|
|
5753
|
+
} : {}
|
|
5754
|
+
} : {}
|
|
5755
|
+
};
|
|
5756
|
+
return validateResponse(
|
|
5757
|
+
ObjectProSubmitSchema,
|
|
5758
|
+
await this.request("/create-object-pro-flash", { method: "POST", body: JSON.stringify(body) }),
|
|
5759
|
+
"create-object-pro-flash"
|
|
5760
|
+
);
|
|
5761
|
+
}
|
|
5762
|
+
/** A text edit of an existing object, applied to its whole rotation set and stored as a sibling. Always inherits the parent's exact canvas — no size override exists on this endpoint. */
|
|
5763
|
+
async createObjectProState(args) {
|
|
5764
|
+
const raw = await this.request(`/objects/${encodeURIComponent(args.objectId)}/states`, {
|
|
5765
|
+
method: "POST",
|
|
5766
|
+
body: JSON.stringify({
|
|
5767
|
+
edit_description: args.editDescription,
|
|
5768
|
+
...args.stateName ? { state_name: args.stateName } : {},
|
|
5769
|
+
...args.seed != null ? { seed: args.seed } : {}
|
|
5770
|
+
})
|
|
5771
|
+
});
|
|
5772
|
+
return validateResponse(ObjectProSubmitSchema, raw, "create object state");
|
|
5773
|
+
}
|
|
5774
|
+
/**
|
|
5775
|
+
* One direction of one object's animation. Unlike character animation,
|
|
5776
|
+
* this endpoint's own `replace_existing` flag regenerates an
|
|
5777
|
+
* already-animated direction directly (character has no such flag and
|
|
5778
|
+
* pixelkiln instead deletes the prior take itself before resubmitting;
|
|
5779
|
+
* see `submitCharacter`). A 1-direction object must not receive
|
|
5780
|
+
* `directions` at all -- the API 400s if it does.
|
|
5781
|
+
*/
|
|
5782
|
+
async animateObject(args) {
|
|
5783
|
+
const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
|
|
5784
|
+
const body = {
|
|
5785
|
+
mode: args.mode,
|
|
5786
|
+
...args.directions === 8 ? { directions: [args.direction] } : {},
|
|
5787
|
+
...args.animationDescription ? { animation_description: args.animationDescription } : {},
|
|
5788
|
+
...args.animationGroupId ? { animation_group_id: args.animationGroupId } : {},
|
|
5789
|
+
...args.displayName ? { display_name: args.displayName } : {},
|
|
5790
|
+
...args.frameCount != null ? { frame_count: args.frameCount } : {},
|
|
5791
|
+
...args.keepFirstFrame === false ? { keep_first_frame: false } : {},
|
|
5792
|
+
...args.startFrame ? { custom_start_frame: encode(args.startFrame) } : {},
|
|
5793
|
+
...args.endFrame ? { end_frame: encode(args.endFrame) } : {},
|
|
5794
|
+
...args.enhancePrompt ? { enhance_prompt: true } : {},
|
|
5795
|
+
...args.replaceExisting ? { replace_existing: true } : {}
|
|
5796
|
+
};
|
|
5797
|
+
const raw = await this.request(`/objects/${encodeURIComponent(args.objectId)}/animations`, {
|
|
5798
|
+
method: "POST",
|
|
5799
|
+
body: JSON.stringify(body)
|
|
5800
|
+
});
|
|
5801
|
+
return validateResponse(AnimateObjectSubmitSchema, raw, "animate object");
|
|
5802
|
+
}
|
|
5625
5803
|
async getMapObject(objectId) {
|
|
5626
5804
|
return validateResponse(
|
|
5627
5805
|
MapObjectSchema,
|
|
@@ -6021,6 +6199,29 @@ function proFlashCharacterCost(width, height, fromReference) {
|
|
|
6021
6199
|
function characterAnimationName(spec) {
|
|
6022
6200
|
return `pixelkiln:${spec.styleId}/${spec.assetId}`;
|
|
6023
6201
|
}
|
|
6202
|
+
function objectAnimationName(spec) {
|
|
6203
|
+
return `pixelkiln:${spec.styleId}/${spec.assetId}`;
|
|
6204
|
+
}
|
|
6205
|
+
function proFlashObjectCost(width, height, directions, fromReference) {
|
|
6206
|
+
const side = Math.max(32, Math.ceil(Math.max(width, height) / 4) * 4);
|
|
6207
|
+
const rotations = Math.max(1, Math.ceil(side * side * directions / 65536));
|
|
6208
|
+
if (fromReference) return rotations;
|
|
6209
|
+
const image = side <= 96 ? 5 : side <= 208 ? 6 : 9;
|
|
6210
|
+
return image + rotations;
|
|
6211
|
+
}
|
|
6212
|
+
function objectProCost(spec) {
|
|
6213
|
+
const object = spec.objectPro;
|
|
6214
|
+
if (!object) return generationCost(spec.width, spec.height, "1dir");
|
|
6215
|
+
const px = spec.width * spec.height;
|
|
6216
|
+
if (object.kind === "animation") {
|
|
6217
|
+
const animation = object.animation;
|
|
6218
|
+
if (animation.mode === "pro") return generationCost(spec.width, spec.height, "1dir");
|
|
6219
|
+
return Math.max(1, Math.ceil(px * animation.frames / 65536));
|
|
6220
|
+
}
|
|
6221
|
+
if (object.kind === "state") return generationCost(spec.width, spec.height, "1dir");
|
|
6222
|
+
const south = object.reference;
|
|
6223
|
+
return south ? proFlashObjectCost(south.width, south.height, object.directions, true) : proFlashObjectCost(spec.width, spec.height, object.directions, false);
|
|
6224
|
+
}
|
|
6024
6225
|
var CHARACTER_TEMPLATES = ["mannequin", "bear", "cat", "dog", "horse", "lion"];
|
|
6025
6226
|
var PRO_FLASH_TEMPLATES = [...CHARACTER_TEMPLATES, "custom"];
|
|
6026
6227
|
function encodeAnimationJob(job) {
|
|
@@ -6037,6 +6238,20 @@ function decodeAnimationJob(jobId) {
|
|
|
6037
6238
|
}
|
|
6038
6239
|
return null;
|
|
6039
6240
|
}
|
|
6241
|
+
function encodeObjectAnimationJob(job) {
|
|
6242
|
+
return `object-animation:${Buffer.from(JSON.stringify(job)).toString("base64url")}`;
|
|
6243
|
+
}
|
|
6244
|
+
function decodeObjectAnimationJob(jobId) {
|
|
6245
|
+
if (!jobId.startsWith("object-animation:")) return null;
|
|
6246
|
+
try {
|
|
6247
|
+
const value = JSON.parse(Buffer.from(jobId.slice("object-animation:".length), "base64url").toString("utf8"));
|
|
6248
|
+
if (typeof value?.objectId === "string" && typeof value.name === "string" && typeof value.direction === "string") {
|
|
6249
|
+
return { objectId: value.objectId, name: value.name, direction: value.direction, jobId: typeof value.jobId === "string" ? value.jobId : null };
|
|
6250
|
+
}
|
|
6251
|
+
} catch {
|
|
6252
|
+
}
|
|
6253
|
+
return null;
|
|
6254
|
+
}
|
|
6040
6255
|
var PixelLabProvider = class _PixelLabProvider {
|
|
6041
6256
|
constructor(client2) {
|
|
6042
6257
|
this.client = client2;
|
|
@@ -6055,7 +6270,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6055
6270
|
return new _PixelLabProvider(new PixelLabClient("download-only"));
|
|
6056
6271
|
}
|
|
6057
6272
|
supports(generator) {
|
|
6058
|
-
return generator === "1dir" || generator === "map" || generator === "pixflux" || generator === "tiles" || generator === "terrain" || generator === "imagePro" || generator === "character" || generator === "isometricTile";
|
|
6273
|
+
return generator === "1dir" || generator === "map" || generator === "pixflux" || generator === "tiles" || generator === "terrain" || generator === "imagePro" || generator === "character" || generator === "isometricTile" || generator === "objectPro";
|
|
6059
6274
|
}
|
|
6060
6275
|
/**
|
|
6061
6276
|
* `inpaint` (`/inpaint-v3`, a mask) and `image-to-image` (`/edit-images-v2`,
|
|
@@ -6097,6 +6312,9 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6097
6312
|
if (spec.generator === "character") {
|
|
6098
6313
|
return { unit: "generations", amount: characterCost(spec), candidates: 1 };
|
|
6099
6314
|
}
|
|
6315
|
+
if (spec.generator === "objectPro") {
|
|
6316
|
+
return { unit: "generations", amount: objectProCost(spec), candidates: 1 };
|
|
6317
|
+
}
|
|
6100
6318
|
return {
|
|
6101
6319
|
unit: "generations",
|
|
6102
6320
|
amount: generationCost(spec.width, spec.height, spec.generator),
|
|
@@ -6205,6 +6423,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6205
6423
|
}
|
|
6206
6424
|
}
|
|
6207
6425
|
if (spec.generator === "character") this.validateCharacter(spec, styleImages);
|
|
6426
|
+
if (spec.generator === "objectPro") this.validateObjectPro(spec, styleImages);
|
|
6208
6427
|
if ((spec.generator === "map" || spec.generator === "pixflux") && styleImages.length) {
|
|
6209
6428
|
throw new Error(`PixelLab ${spec.generator} does not support style images`);
|
|
6210
6429
|
}
|
|
@@ -6365,6 +6584,63 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6365
6584
|
}
|
|
6366
6585
|
}
|
|
6367
6586
|
}
|
|
6587
|
+
validateObjectPro(spec, styleImages = []) {
|
|
6588
|
+
const object = spec.objectPro;
|
|
6589
|
+
if (!object) throw new Error(`${spec.styleId}/${spec.assetId} has no objectPro shape`);
|
|
6590
|
+
const label = `${spec.styleId}/${spec.assetId}`;
|
|
6591
|
+
if (object.kind === "base" && object.reference && styleImages.length) {
|
|
6592
|
+
throw new Error(`${label}: a base rotating its own reference sprite takes no style image`);
|
|
6593
|
+
}
|
|
6594
|
+
if (object.reference && (object.reference.width > 256 || object.reference.height > 256)) {
|
|
6595
|
+
throw new Error(`${label}: reference is ${object.reference.width}x${object.reference.height}; PixelLab objectPro takes up to 256px`);
|
|
6596
|
+
}
|
|
6597
|
+
if (styleImages.length) {
|
|
6598
|
+
if (styleImages.length > 1) throw new Error(`PixelLab objectPro takes one style image`);
|
|
6599
|
+
const image = styleImages[0];
|
|
6600
|
+
if (image.width > 256 || image.height > 256) {
|
|
6601
|
+
throw new Error(`PixelLab objectPro style image is ${image.width}x${image.height}; the limit is 256px`);
|
|
6602
|
+
}
|
|
6603
|
+
if (image.width > spec.width || image.height > spec.height) {
|
|
6604
|
+
throw new Error(
|
|
6605
|
+
`PixelLab objectPro style image is ${image.width}x${image.height} but the object is ${spec.width}x${spec.height}; crop the image to its subject or raise the style's size`
|
|
6606
|
+
);
|
|
6607
|
+
}
|
|
6608
|
+
}
|
|
6609
|
+
if (object.kind === "base" && (spec.width < 16 || spec.height < 16 || spec.width > 256 || spec.height > 256)) {
|
|
6610
|
+
throw new Error(`PixelLab objectPro bases must be between 16 and 256 pixels`);
|
|
6611
|
+
}
|
|
6612
|
+
if (object.kind === "base" && (spec.width % 4 || spec.height % 4)) {
|
|
6613
|
+
throw new Error(`PixelLab objectPro bases are sized in multiples of 4 pixels; ${spec.width}x${spec.height} is not`);
|
|
6614
|
+
}
|
|
6615
|
+
if (object.kind !== "base" && !object.parentAssetId) {
|
|
6616
|
+
throw new Error(`${spec.styleId}/${spec.assetId} is a ${object.kind} with no parent`);
|
|
6617
|
+
}
|
|
6618
|
+
if (object.animation) {
|
|
6619
|
+
const allowed = object.directions === 1 ? ["south"] : CHARACTER_DIRECTIONS_8;
|
|
6620
|
+
if (!allowed.includes(object.animation.direction)) {
|
|
6621
|
+
throw new Error(
|
|
6622
|
+
`${label} animates "${object.animation.direction}", but its object has ${object.directions} direction${object.directions === 1 ? "" : "s"}: ${allowed.join(", ")}`
|
|
6623
|
+
);
|
|
6624
|
+
}
|
|
6625
|
+
if (!spec.prompt.trim()) {
|
|
6626
|
+
throw new Error(`${label} needs a prompt describing the motion`);
|
|
6627
|
+
}
|
|
6628
|
+
const { startFrame, endFrame } = object.animation;
|
|
6629
|
+
for (const [name, image] of [["start frame", startFrame], ["end frame", endFrame]]) {
|
|
6630
|
+
if (image && (image.width > 256 || image.height > 256)) {
|
|
6631
|
+
throw new Error(`${label}: ${name} is ${image.width}x${image.height}; PixelLab v3 takes up to 256px`);
|
|
6632
|
+
}
|
|
6633
|
+
}
|
|
6634
|
+
if (endFrame) {
|
|
6635
|
+
const start = startFrame ?? (object.parentFile && existsSync7(object.parentFile) ? imageMetadata(readFileSync2(object.parentFile)) : null);
|
|
6636
|
+
if (start && (start.width !== endFrame.width || start.height !== endFrame.height)) {
|
|
6637
|
+
throw new Error(
|
|
6638
|
+
`${label}: end frame is ${endFrame.width}x${endFrame.height} but the ${startFrame ? "start frame" : "object's rotation"} is ${start.width}x${start.height}; PixelLab interpolates between frames of one size`
|
|
6639
|
+
);
|
|
6640
|
+
}
|
|
6641
|
+
}
|
|
6642
|
+
}
|
|
6643
|
+
}
|
|
6368
6644
|
/**
|
|
6369
6645
|
* A base is one request that answers with the character id; a state is
|
|
6370
6646
|
* the same with the parent's id; an animation names itself after the
|
|
@@ -6458,6 +6734,78 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6458
6734
|
metadata: { character: { kind: "animation", characterId: parentId, animationName: name, direction: animation.direction, mode: animation.mode, backgroundJobIds: res.background_job_ids } }
|
|
6459
6735
|
};
|
|
6460
6736
|
}
|
|
6737
|
+
/**
|
|
6738
|
+
* Same shape as `submitCharacter`, minus the mode branching (objectPro has
|
|
6739
|
+
* exactly one base-creation path) and minus the client-side "find and
|
|
6740
|
+
* delete the prior take" dance for animation: `/objects/{id}/animations`
|
|
6741
|
+
* has its own `replace_existing` flag, so this always passes it rather
|
|
6742
|
+
* than fetching the object first to check.
|
|
6743
|
+
*/
|
|
6744
|
+
async submitObjectPro(spec, styleImages, context) {
|
|
6745
|
+
const object = spec.objectPro;
|
|
6746
|
+
if (object.kind === "base") {
|
|
6747
|
+
const styleImage = styleImages[0];
|
|
6748
|
+
const res2 = await this.client.createObjectProFlash({
|
|
6749
|
+
description: spec.prompt,
|
|
6750
|
+
directions: object.directions,
|
|
6751
|
+
size: spec.width,
|
|
6752
|
+
view: spec.view,
|
|
6753
|
+
seed: spec.seed,
|
|
6754
|
+
reference: object.reference ? readPose(spec, "reference sprite", object.reference) : void 0,
|
|
6755
|
+
styleReference: styleImage ? { base64: styleImage.base64, format: styleImage.format } : void 0,
|
|
6756
|
+
styleReferenceSize: styleImage ? { width: styleImage.width, height: styleImage.height } : void 0,
|
|
6757
|
+
styleTraits: object.styleTraits
|
|
6758
|
+
});
|
|
6759
|
+
return { jobId: res2.object_id, metadata: { objectPro: { kind: "base", objectId: res2.object_id, directions: object.directions, backgroundJobId: res2.background_job_id } } };
|
|
6760
|
+
}
|
|
6761
|
+
const parentId = context?.parentObjectId;
|
|
6762
|
+
if (!parentId) {
|
|
6763
|
+
throw new Error(
|
|
6764
|
+
`${spec.styleId}/${spec.assetId} is a ${object.kind} of ${object.parentAssetId}, which has no PixelLab object id in the lockfile yet; generate the parent first`
|
|
6765
|
+
);
|
|
6766
|
+
}
|
|
6767
|
+
if (object.kind === "state") {
|
|
6768
|
+
const res2 = await this.client.createObjectProState({
|
|
6769
|
+
objectId: parentId,
|
|
6770
|
+
editDescription: spec.prompt,
|
|
6771
|
+
stateName: spec.assetId.slice(0, 50),
|
|
6772
|
+
seed: spec.seed
|
|
6773
|
+
});
|
|
6774
|
+
return { jobId: res2.object_id, metadata: { objectPro: { kind: "state", objectId: res2.object_id, parentObjectId: parentId, directions: object.directions, backgroundJobId: res2.background_job_id } } };
|
|
6775
|
+
}
|
|
6776
|
+
const animation = object.animation;
|
|
6777
|
+
const name = objectAnimationName(spec);
|
|
6778
|
+
const res = await this.client.animateObject({
|
|
6779
|
+
objectId: parentId,
|
|
6780
|
+
directions: object.directions,
|
|
6781
|
+
direction: animation.direction,
|
|
6782
|
+
animationDescription: spec.prompt.trim() || void 0,
|
|
6783
|
+
displayName: name,
|
|
6784
|
+
mode: animation.mode === "pro" ? "pro" : "v3",
|
|
6785
|
+
frameCount: animation.frames,
|
|
6786
|
+
keepFirstFrame: animation.keepFirstFrame,
|
|
6787
|
+
startFrame: animation.startFrame ? readPose(spec, "start frame", animation.startFrame) : void 0,
|
|
6788
|
+
endFrame: animation.endFrame ? readPose(spec, "end frame", animation.endFrame) : void 0,
|
|
6789
|
+
enhancePrompt: animation.enhancePrompt,
|
|
6790
|
+
replaceExisting: true
|
|
6791
|
+
});
|
|
6792
|
+
const submission = res.submissions.find((s) => s.direction === animation.direction) ?? res.submissions[0];
|
|
6793
|
+
const job = { objectId: parentId, name, direction: animation.direction, jobId: submission?.background_job_id ?? null };
|
|
6794
|
+
return {
|
|
6795
|
+
jobId: encodeObjectAnimationJob(job),
|
|
6796
|
+
metadata: {
|
|
6797
|
+
objectPro: {
|
|
6798
|
+
kind: "animation",
|
|
6799
|
+
objectId: parentId,
|
|
6800
|
+
animationName: name,
|
|
6801
|
+
direction: animation.direction,
|
|
6802
|
+
mode: animation.mode,
|
|
6803
|
+
animationGroupId: res.animation_group_id,
|
|
6804
|
+
animationId: submission?.animation_id ?? null
|
|
6805
|
+
}
|
|
6806
|
+
}
|
|
6807
|
+
};
|
|
6808
|
+
}
|
|
6461
6809
|
async pollCharacter(jobId, context) {
|
|
6462
6810
|
const animation = decodeAnimationJob(jobId);
|
|
6463
6811
|
if (animation) return this.pollCharacterAnimation(animation, context);
|
|
@@ -6541,10 +6889,93 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6541
6889
|
if (found) return review(found.frames, found.animationId, found.groupId, null);
|
|
6542
6890
|
return cleanedUp ? { status: "failed", error: `animation job is gone upstream and no "${job.name}" ${job.direction} animation exists on the character` } : { status: "processing" };
|
|
6543
6891
|
}
|
|
6892
|
+
async pollObjectPro(jobId, context) {
|
|
6893
|
+
const animation = decodeObjectAnimationJob(jobId);
|
|
6894
|
+
if (animation) return this.pollObjectProAnimation(animation, context);
|
|
6895
|
+
const object = await this.client.getObject(jobId);
|
|
6896
|
+
if (object.status === "failed") return { status: "failed", error: "object generation failed upstream" };
|
|
6897
|
+
if (object.status !== "completed" || !object.rotation_urls) return { status: "processing" };
|
|
6898
|
+
const sources = objectRotationSources(object);
|
|
6899
|
+
if (!sources.length) return { status: "failed", error: "object completed with no rotation images" };
|
|
6900
|
+
const backgroundJobId = context?.metadata?.objectPro?.backgroundJobId;
|
|
6901
|
+
return {
|
|
6902
|
+
status: "ready",
|
|
6903
|
+
objectId: object.id,
|
|
6904
|
+
sourceUrl: sources[0].url,
|
|
6905
|
+
sources,
|
|
6906
|
+
metadata: {
|
|
6907
|
+
objectPro: {
|
|
6908
|
+
objectId: object.id,
|
|
6909
|
+
groupId: object.group_id ?? null,
|
|
6910
|
+
name: object.name,
|
|
6911
|
+
stateName: object.state_name ?? null,
|
|
6912
|
+
directions: object.directions,
|
|
6913
|
+
size: object.size
|
|
6914
|
+
}
|
|
6915
|
+
},
|
|
6916
|
+
billed: await this.billedForJob(backgroundJobId)
|
|
6917
|
+
};
|
|
6918
|
+
}
|
|
6919
|
+
/**
|
|
6920
|
+
* Same resilience shape as `pollCharacterAnimation`: the background job is
|
|
6921
|
+
* the authoritative record while it lasts, the object's own `animations`
|
|
6922
|
+
* list (matched by the `pixelkiln:` display name this asset submitted, or
|
|
6923
|
+
* a recorded animation id) is the fallback once PixelLab cleans the job
|
|
6924
|
+
* up. Never declares failure from absence alone -- a `rate_limited`
|
|
6925
|
+
* `DirectionSubmission` has no background job id at all, so this keeps
|
|
6926
|
+
* reporting "processing" rather than guessing a job that never existed
|
|
6927
|
+
* has failed.
|
|
6928
|
+
*/
|
|
6929
|
+
async pollObjectProAnimation(job, context) {
|
|
6930
|
+
const fps = context?.spec?.objectPro?.animation?.fps ?? 8;
|
|
6931
|
+
const review = (frames, animationId, groupId, billed) => ({
|
|
6932
|
+
status: "review-set",
|
|
6933
|
+
objectId: `${job.objectId}#${groupId ?? animationId ?? job.name}`,
|
|
6934
|
+
frameUrls: frames,
|
|
6935
|
+
sources: frames.map((url, index) => ({ url, role: `frame-${String(index).padStart(2, "0")}` })),
|
|
6936
|
+
fps,
|
|
6937
|
+
metadata: {
|
|
6938
|
+
frameSet: { fps, count: frames.length },
|
|
6939
|
+
objectPro: {
|
|
6940
|
+
kind: "animation",
|
|
6941
|
+
objectId: job.objectId,
|
|
6942
|
+
animationId,
|
|
6943
|
+
animationGroupId: groupId,
|
|
6944
|
+
animationName: job.name,
|
|
6945
|
+
direction: job.direction
|
|
6946
|
+
}
|
|
6947
|
+
},
|
|
6948
|
+
billed
|
|
6949
|
+
});
|
|
6950
|
+
if (job.jobId) {
|
|
6951
|
+
const status = await this.client.getBackgroundJob(job.jobId).catch((err) => {
|
|
6952
|
+
if (err instanceof PixelLabError && err.status === 404) return null;
|
|
6953
|
+
throw err;
|
|
6954
|
+
});
|
|
6955
|
+
if (status) {
|
|
6956
|
+
if (status.status === "failed") return { status: "failed", error: "animation generation failed upstream" };
|
|
6957
|
+
if (status.status !== "completed") return { status: "processing" };
|
|
6958
|
+
const done = status.last_response ?? {};
|
|
6959
|
+
const frames = done.storage_urls?.frames;
|
|
6960
|
+
const animationId = typeof done.animation_id === "string" ? done.animation_id : null;
|
|
6961
|
+
if (Array.isArray(frames) && frames.length && frames.every((f) => typeof f === "string")) {
|
|
6962
|
+
const object2 = await this.client.getObject(job.objectId);
|
|
6963
|
+
const group = findObjectAnimation(object2, job.name, job.direction, animationId);
|
|
6964
|
+
return review(frames, animationId, group?.groupId ?? null, billedFromUsage(status.usage));
|
|
6965
|
+
}
|
|
6966
|
+
}
|
|
6967
|
+
}
|
|
6968
|
+
const recorded = context?.metadata?.objectPro?.animationId ?? null;
|
|
6969
|
+
const object = await this.client.getObject(job.objectId);
|
|
6970
|
+
const found = findObjectAnimation(object, job.name, job.direction, recorded);
|
|
6971
|
+
if (found) return review(found.frames, found.animationId, found.groupId, null);
|
|
6972
|
+
return { status: "processing" };
|
|
6973
|
+
}
|
|
6544
6974
|
async submit(spec, styleImages, context) {
|
|
6545
6975
|
this.validate(spec, styleImages);
|
|
6546
6976
|
if (spec.revision) return this.submitRevision(spec);
|
|
6547
6977
|
if (spec.generator === "character") return this.submitCharacter(spec, styleImages, context);
|
|
6978
|
+
if (spec.generator === "objectPro") return this.submitObjectPro(spec, styleImages, context);
|
|
6548
6979
|
if (spec.generator === "pixflux") {
|
|
6549
6980
|
const swatch = spec.palette.length ? paletteSwatch(spec.palette).toString("base64") : void 0;
|
|
6550
6981
|
const { png } = await this.client.createImagePixflux({
|
|
@@ -6738,6 +7169,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6738
7169
|
if (generator === "isometricTile") return this.pollIsometricTile(jobId, context);
|
|
6739
7170
|
if (generator === "imagePro") return this.pollImagePro(jobId);
|
|
6740
7171
|
if (generator === "character") return this.pollCharacter(jobId, context);
|
|
7172
|
+
if (generator === "objectPro") return this.pollObjectPro(jobId, context);
|
|
6741
7173
|
const backgroundJobId = context?.metadata?.backgroundJobId;
|
|
6742
7174
|
const obj = await this.client.getObject(jobId);
|
|
6743
7175
|
if (obj.status === "review") {
|
|
@@ -7155,6 +7587,28 @@ function findAnimation(character, name, direction, animationId) {
|
|
|
7155
7587
|
}
|
|
7156
7588
|
return null;
|
|
7157
7589
|
}
|
|
7590
|
+
function objectRotationSources(object) {
|
|
7591
|
+
const order = object.directions === 1 ? ["south"] : CHARACTER_DIRECTIONS_8;
|
|
7592
|
+
const sources = [];
|
|
7593
|
+
for (const direction of order) {
|
|
7594
|
+
const url = object.rotation_urls?.[direction];
|
|
7595
|
+
if (url) sources.push({ url, role: direction });
|
|
7596
|
+
}
|
|
7597
|
+
return sources;
|
|
7598
|
+
}
|
|
7599
|
+
function findObjectAnimation(object, name, direction, animationId) {
|
|
7600
|
+
for (const group of object.animations) {
|
|
7601
|
+
const match = group.directions.find((d) => d.direction === direction);
|
|
7602
|
+
const frames = match ? match.storage_urls.frames : void 0;
|
|
7603
|
+
if (!match || !Array.isArray(frames) || !frames.length || !frames.every((f) => typeof f === "string")) continue;
|
|
7604
|
+
const byName = group.display_name === name;
|
|
7605
|
+
const byId = animationId !== null && frames.some((url) => url.includes(`/animations/${animationId}/`));
|
|
7606
|
+
if (!byName && !byId) continue;
|
|
7607
|
+
const fromUrl = /\/animations\/([^/]+)\//.exec(frames[0])?.[1] ?? null;
|
|
7608
|
+
return { frames, groupId: group.animation_group_id, animationId: animationId ?? fromUrl };
|
|
7609
|
+
}
|
|
7610
|
+
return null;
|
|
7611
|
+
}
|
|
7158
7612
|
|
|
7159
7613
|
// src/providers/retrodiffusion.ts
|
|
7160
7614
|
var DEFAULT_BASE_URL2 = "https://api.retrodiffusion.ai/v1";
|
|
@@ -8334,14 +8788,18 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8334
8788
|
size = asset.size ?? style.size ?? 32;
|
|
8335
8789
|
width = size;
|
|
8336
8790
|
height = size;
|
|
8791
|
+
} else if (generator === "objectPro") {
|
|
8792
|
+
size = asset.size ?? style.size ?? 64;
|
|
8793
|
+
width = size;
|
|
8794
|
+
height = size;
|
|
8337
8795
|
} else {
|
|
8338
8796
|
width = asset.width ?? style.size ?? 64;
|
|
8339
8797
|
height = asset.height ?? style.size ?? 64;
|
|
8340
8798
|
size = Math.max(width, height);
|
|
8341
8799
|
}
|
|
8342
|
-
if ((asset.state || asset.animation) && generator !== "character") {
|
|
8800
|
+
if ((asset.state || asset.animation) && generator !== "character" && generator !== "objectPro") {
|
|
8343
8801
|
throw new Error(
|
|
8344
|
-
`assets.${assetId}: ${asset.state ? "state" : "animation"} needs a character style; "${styleId}" generates ${generator}`
|
|
8802
|
+
`assets.${assetId}: ${asset.state ? "state" : "animation"} needs a character or objectPro style; "${styleId}" generates ${generator}`
|
|
8345
8803
|
);
|
|
8346
8804
|
}
|
|
8347
8805
|
const characterKind = asset.animation ? "animation" : asset.state ? "state" : "base";
|
|
@@ -8362,7 +8820,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8362
8820
|
terrainUpperDescription = wrap(parsed.upper);
|
|
8363
8821
|
terrainTransitionDescription = parsed.transition ? wrap(parsed.transition) : void 0;
|
|
8364
8822
|
prompt = subject.trim();
|
|
8365
|
-
} else if (generator === "character" && characterKind !== "base") {
|
|
8823
|
+
} else if ((generator === "character" || generator === "objectPro") && characterKind !== "base") {
|
|
8366
8824
|
prompt = subject.trim();
|
|
8367
8825
|
} else {
|
|
8368
8826
|
prompt = [style.promptPrefix, subject, style.promptSuffix].map((p) => p.trim()).filter(Boolean).join(", ");
|
|
@@ -8430,6 +8888,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8430
8888
|
isometricTileSize: generator === "isometricTile" ? style.isometricTileSize : void 0,
|
|
8431
8889
|
isometricTileShape: generator === "isometricTile" ? style.isometricTileShape : void 0,
|
|
8432
8890
|
...generator === "character" ? { character: await resolveCharacterShape(asset, style, characterKind, { root, load: loadStyleImage }) } : {},
|
|
8891
|
+
...generator === "objectPro" ? { objectPro: await resolveObjectProShape(assetId, asset, style, characterKind, { root, load: loadStyleImage }) } : {},
|
|
8433
8892
|
cost: generator === "tiles" ? tilesCost(tileSize, tileVariations) : generator === "terrain" ? tilesCost(size, terrainTiles) : generator === "isometricTile" ? isometricTileCost() : generationCost(width, height, generator),
|
|
8434
8893
|
costUnit: "generations",
|
|
8435
8894
|
candidates: generator === "tiles" ? tileVariations : generator === "terrain" ? terrainTiles : generator === "1dir" || generator === "imagePro" ? candidateCount(size) : 1
|
|
@@ -8495,6 +8954,18 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8495
8954
|
} else if (sourceSpec.character) {
|
|
8496
8955
|
resolved.character = { ...sourceSpec.character };
|
|
8497
8956
|
}
|
|
8957
|
+
if (sourceSpec.objectPro?.kind === "animation") {
|
|
8958
|
+
const facing = sourceSpec.objectPro.animation.direction;
|
|
8959
|
+
const mirrored = MIRRORED_DIRECTION[facing];
|
|
8960
|
+
if (mirrored === facing) {
|
|
8961
|
+
throw new Error(
|
|
8962
|
+
`assets.${assetId}: mirroring ${asset.mirror} gives another ${facing}-facing loop; mirror a loop facing east, west, or a diagonal`
|
|
8963
|
+
);
|
|
8964
|
+
}
|
|
8965
|
+
resolved.objectPro = { ...sourceSpec.objectPro, animation: { ...sourceSpec.objectPro.animation, direction: mirrored } };
|
|
8966
|
+
} else if (sourceSpec.objectPro) {
|
|
8967
|
+
resolved.objectPro = { ...sourceSpec.objectPro };
|
|
8968
|
+
}
|
|
8498
8969
|
resolved.mirror = { sourceAssetId: asset.mirror, sourceSpec };
|
|
8499
8970
|
resolved.generator = sourceSpec.generator;
|
|
8500
8971
|
resolved.width = sourceSpec.width;
|
|
@@ -8536,6 +9007,23 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8536
9007
|
directions: resolved.character.kind === "base" ? resolved.character.directions : parentSpec.character?.directions ?? resolved.character.directions
|
|
8537
9008
|
};
|
|
8538
9009
|
}
|
|
9010
|
+
if (resolved.objectPro && characterParent) {
|
|
9011
|
+
const parentSpec = await finalize(characterParent);
|
|
9012
|
+
if (!parentSpec.objectPro) {
|
|
9013
|
+
throw new Error(`assets.${assetId}: ${characterParent} is not an objectPro asset`);
|
|
9014
|
+
}
|
|
9015
|
+
const parentDirections = parentSpec.objectPro.directions;
|
|
9016
|
+
const parentFile = memberPath(parentSpec.outFile, "south", 0, parentDirections, MediaType.PNG);
|
|
9017
|
+
resolved.objectPro = {
|
|
9018
|
+
...resolved.objectPro,
|
|
9019
|
+
parentAssetId: characterParent,
|
|
9020
|
+
parentSpec,
|
|
9021
|
+
parentFile,
|
|
9022
|
+
parentSha256: existsSync8(parentFile) ? await sha256File(parentFile) : null,
|
|
9023
|
+
// A state and its animations keep the base's own rotation count.
|
|
9024
|
+
directions: resolved.objectPro.kind === "base" ? resolved.objectPro.directions : parentSpec.objectPro.directions
|
|
9025
|
+
};
|
|
9026
|
+
}
|
|
8539
9027
|
if (asset.revision) {
|
|
8540
9028
|
if (!activeProvider.supportsRevision?.(asset.revision.mode)) {
|
|
8541
9029
|
throw new Error(
|
|
@@ -8718,6 +9206,60 @@ async function resolveCharacterShape(asset, style, kind, files) {
|
|
|
8718
9206
|
}
|
|
8719
9207
|
return shape;
|
|
8720
9208
|
}
|
|
9209
|
+
async function resolveObjectProShape(assetId, asset, style, kind, files) {
|
|
9210
|
+
const directions = style.objectDirections ?? 8;
|
|
9211
|
+
const shape = {
|
|
9212
|
+
kind,
|
|
9213
|
+
directions,
|
|
9214
|
+
...style.styleTraits ? { styleTraits: style.styleTraits } : {}
|
|
9215
|
+
};
|
|
9216
|
+
if (asset.reference) {
|
|
9217
|
+
const south = typeof asset.reference === "string" ? asset.reference : asset.reference.south;
|
|
9218
|
+
if (south) {
|
|
9219
|
+
const image = await files.load(south, "Reference sprite");
|
|
9220
|
+
shape.reference = { path: path13.resolve(files.root, south), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
9221
|
+
}
|
|
9222
|
+
}
|
|
9223
|
+
if (asset.state) {
|
|
9224
|
+
shape.state = {
|
|
9225
|
+
paletteFromReference: asset.state.paletteFromReference,
|
|
9226
|
+
...asset.state.canvas ? { canvas: asset.state.canvas } : {}
|
|
9227
|
+
};
|
|
9228
|
+
}
|
|
9229
|
+
if (asset.animation) {
|
|
9230
|
+
for (const [field, value] of Object.entries({
|
|
9231
|
+
template: asset.animation.template,
|
|
9232
|
+
subject: asset.animation.subject,
|
|
9233
|
+
outline: asset.animation.outline,
|
|
9234
|
+
shading: asset.animation.shading,
|
|
9235
|
+
detail: asset.animation.detail
|
|
9236
|
+
})) {
|
|
9237
|
+
if (value !== void 0) {
|
|
9238
|
+
throw new Error(
|
|
9239
|
+
`assets.${assetId}: objectPro has no skeleton/template concept, so animation.${field} does not apply; describe the motion in the asset's own prompt instead`
|
|
9240
|
+
);
|
|
9241
|
+
}
|
|
9242
|
+
}
|
|
9243
|
+
if (asset.animation.mode === "template") {
|
|
9244
|
+
throw new Error(`assets.${assetId}: objectPro animations have no "template" mode; use "v3" or "pro"`);
|
|
9245
|
+
}
|
|
9246
|
+
const pose = async (rel, what) => {
|
|
9247
|
+
const image = await files.load(rel, what);
|
|
9248
|
+
return { path: path13.resolve(files.root, rel), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
9249
|
+
};
|
|
9250
|
+
shape.animation = {
|
|
9251
|
+
mode: asset.animation.mode ?? "v3",
|
|
9252
|
+
direction: asset.animation.direction,
|
|
9253
|
+
frames: asset.animation.frames ?? 8,
|
|
9254
|
+
fps: asset.animation.fps,
|
|
9255
|
+
keepFirstFrame: asset.animation.keepFirstFrame,
|
|
9256
|
+
...asset.animation.startFrame ? { startFrame: await pose(asset.animation.startFrame, "Animation start frame") } : {},
|
|
9257
|
+
...asset.animation.endFrame ? { endFrame: await pose(asset.animation.endFrame, "Animation end frame") } : {},
|
|
9258
|
+
...asset.animation.enhancePrompt !== void 0 ? { enhancePrompt: asset.animation.enhancePrompt } : {}
|
|
9259
|
+
};
|
|
9260
|
+
}
|
|
9261
|
+
return shape;
|
|
9262
|
+
}
|
|
8721
9263
|
|
|
8722
9264
|
// src/pipeline/fetch.ts
|
|
8723
9265
|
import { existsSync as existsSync9, readFileSync as readFileSync3 } from "fs";
|
|
@@ -12840,7 +13382,8 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
12840
13382
|
lastSubmitAt = Date.now();
|
|
12841
13383
|
try {
|
|
12842
13384
|
const refs = styleImages.get(spec.styleId) ?? [];
|
|
12843
|
-
const
|
|
13385
|
+
const parentSpec = spec.character?.parentSpec ?? spec.objectPro?.parentSpec;
|
|
13386
|
+
const parentEntry = parentSpec ? lock.entries[lockKey(parentSpec.styleId, parentSpec.assetId)] : void 0;
|
|
12844
13387
|
const anchor = spec.character?.styleAnchor;
|
|
12845
13388
|
const anchorEntry = anchor ? lock.entries[lockKey(anchor.spec.styleId, anchor.spec.assetId)] : void 0;
|
|
12846
13389
|
const replacedMetadata = previousEntry?.providerMetadata?.[provider.id];
|