pixelkiln 0.53.4 → 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 +704 -31
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +706 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +599 -115
- package/dist/index.d.ts +599 -115
- package/dist/index.js +705 -31
- package/dist/index.js.map +1 -1
- package/docs/CHARACTERS.md +6 -0
- package/docs/ENDPOINTS.md +47 -2
- package/docs/GENERATORS.md +89 -0
- package/docs/MANIFEST.md +1 -1
- package/package.json +1 -1
- package/schema/manifest.schema.json +50 -2
- package/schema/recipe.schema.json +25 -1
- package/skills/pixelkiln/references/pixellab-roadmap.md +40 -11
- package/skills/pixelkiln/references/pixellab.md +57 -0
package/dist/index.cjs
CHANGED
|
@@ -169,6 +169,7 @@ __export(index_exports, {
|
|
|
169
169
|
isFrameSetEntry: () => isFrameSetEntry,
|
|
170
170
|
isMemberSetEntry: () => isMemberSetEntry,
|
|
171
171
|
isSensitiveSourceUrl: () => isSensitiveSourceUrl,
|
|
172
|
+
isometricTileCost: () => isometricTileCost,
|
|
172
173
|
listBundledRecipes: () => listBundledRecipes,
|
|
173
174
|
loadClaims: () => loadClaims,
|
|
174
175
|
loadLock: () => loadLock,
|
|
@@ -303,7 +304,7 @@ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
|
303
304
|
// src/types.ts
|
|
304
305
|
var import_zod = require("zod");
|
|
305
306
|
var MediaTypeSchema = import_zod.z.enum(["image/png", "image/gif"]);
|
|
306
|
-
var GeneratorSchema = import_zod.z.enum(["1dir", "map", "pixflux", "tiles", "animation", "frames", "character", "terrain", "imagePro"]);
|
|
307
|
+
var GeneratorSchema = import_zod.z.enum(["1dir", "map", "pixflux", "tiles", "animation", "frames", "character", "terrain", "imagePro", "isometricTile", "objectPro"]);
|
|
307
308
|
var GridConfidenceSchema = import_zod.z.enum(["low", "medium", "high"]);
|
|
308
309
|
var RevisionModeSchema = import_zod.z.enum(["image-to-image", "inpaint", "outpaint"]);
|
|
309
310
|
function tileVariationCount(descriptions) {
|
|
@@ -337,6 +338,9 @@ function tilesCost(tileSize, variations) {
|
|
|
337
338
|
if (px <= 2048) return 25;
|
|
338
339
|
return 40;
|
|
339
340
|
}
|
|
341
|
+
function isometricTileCost() {
|
|
342
|
+
return 1;
|
|
343
|
+
}
|
|
340
344
|
function terrainTileCount(transitionSize) {
|
|
341
345
|
return transitionSize === 1 ? 25 : 16;
|
|
342
346
|
}
|
|
@@ -666,6 +670,28 @@ var StyleObjectSchema = import_zod.z.object({
|
|
|
666
670
|
/** `terrain` generator only. Camera angle; the API default is `high
|
|
667
671
|
* top-down`. */
|
|
668
672
|
terrainView: import_zod.z.enum(["low top-down", "high top-down"]).optional(),
|
|
673
|
+
/**
|
|
674
|
+
* `isometricTile` generator only. `/create-isometric-tile`'s own tile
|
|
675
|
+
* grid size, distinct from `size` (the generation canvas, 16-64px — the
|
|
676
|
+
* API's own guidance is that sizes above 24px "often produce better
|
|
677
|
+
* quality results"). The API default is 16.
|
|
678
|
+
*/
|
|
679
|
+
isometricTileSize: import_zod.z.union([import_zod.z.literal(16), import_zod.z.literal(32)]).optional(),
|
|
680
|
+
/**
|
|
681
|
+
* `isometricTile` generator only. Vertical thickness, i.e. how much
|
|
682
|
+
* height/elevation variation the tile can represent on a map: `"thin
|
|
683
|
+
* tile"` (~15% of canvas height), `"thick tile"` (~25%), or `"block"`
|
|
684
|
+
* (~50%, the API default).
|
|
685
|
+
*/
|
|
686
|
+
isometricTileShape: import_zod.z.enum(["thin tile", "thick tile", "block"]).optional(),
|
|
687
|
+
/**
|
|
688
|
+
* `objectPro` generator only. Rotations captured for a base: 1 (the API
|
|
689
|
+
* still draws and stores a single facing) or the full 8. The API default
|
|
690
|
+
* is 8. Unlike `character`, `objectPro` has no skeleton/template modes
|
|
691
|
+
* and so no `mode`/`template` fields of its own — `/create-object-pro-flash`
|
|
692
|
+
* is the only base-creation path.
|
|
693
|
+
*/
|
|
694
|
+
objectDirections: import_zod.z.union([import_zod.z.literal(1), import_zod.z.literal(8)]).optional(),
|
|
669
695
|
/**
|
|
670
696
|
* `pixflux` only. Whether to strip the generated background.
|
|
671
697
|
*
|
|
@@ -870,25 +896,41 @@ var AssetSchema = import_zod.z.object({
|
|
|
870
896
|
revision: RevisionSchema.optional(),
|
|
871
897
|
/**
|
|
872
898
|
* The provider's own id for art that already exists on the account, so
|
|
873
|
-
* `adopt` can map it without matching bytes: an object id
|
|
874
|
-
*
|
|
875
|
-
*
|
|
899
|
+
* `adopt` can map it without matching bytes: an object id (an
|
|
900
|
+
* `objectPro` base and state included, since they share PixelLab's
|
|
901
|
+
* `/objects` identity space), a character id, or `<id>#<animation group
|
|
902
|
+
* id>` for a character or `objectPro` animation. Not part of the spec's
|
|
903
|
+
* identity.
|
|
876
904
|
*/
|
|
877
905
|
remoteId: import_zod.z.string().min(1).optional(),
|
|
878
|
-
/**
|
|
906
|
+
/**
|
|
907
|
+
* `character` or `objectPro` styles: this asset is a pose or edit of
|
|
908
|
+
* another asset in the same family. `objectPro` ignores
|
|
909
|
+
* `paletteFromReference`/`canvas` — the API has no equivalent, and
|
|
910
|
+
* always inherits the parent's exact canvas.
|
|
911
|
+
*/
|
|
879
912
|
state: CharacterStateSchema.optional(),
|
|
880
|
-
/**
|
|
913
|
+
/**
|
|
914
|
+
* `character` or `objectPro` styles: this asset is a loop of another
|
|
915
|
+
* asset in the same family, in one direction. `objectPro` has no
|
|
916
|
+
* skeleton/template concept, so `template`/`subject`/`outline`/
|
|
917
|
+
* `shading`/`detail` are rejected on an `objectPro` animation rather
|
|
918
|
+
* than silently ignored — describe the motion in the asset's own
|
|
919
|
+
* `prompt` instead.
|
|
920
|
+
*/
|
|
881
921
|
animation: CharacterAnimationSchema.optional(),
|
|
882
922
|
/** `character` styles, `standard` humanoid bases: this character's proportions, over the style's. */
|
|
883
923
|
proportions: CharacterProportionsSchema.optional(),
|
|
884
924
|
/**
|
|
885
|
-
* `character` styles, bases only: the
|
|
886
|
-
* PixelLab rotates into the other directions instead of
|
|
887
|
-
* the prompt. A manifest-relative PNG or JPEG of the
|
|
888
|
-
* sprite, or an object keyed by direction (`{ "south":
|
|
889
|
-
* ... }`); quadrupeds in `standard` mode need
|
|
890
|
-
* `standard` takes more than south. `
|
|
891
|
-
*
|
|
925
|
+
* `character` or `objectPro` styles, bases only: the subject's own
|
|
926
|
+
* sprite, which PixelLab rotates into the other directions instead of
|
|
927
|
+
* drawing from the prompt. A manifest-relative PNG or JPEG of the
|
|
928
|
+
* south-facing sprite, or an object keyed by direction (`{ "south":
|
|
929
|
+
* ..., "east": ... }`); quadrupeds in `standard` character mode need
|
|
930
|
+
* south and east, and only `standard` takes more than south. `objectPro`
|
|
931
|
+
* only ever reads the south image, since `/create-object-pro-flash`
|
|
932
|
+
* rotates from one frame. `standard` wants each image at the style's
|
|
933
|
+
* size; v3 accepts up to 256px, pro up to 168px. The prompt
|
|
892
934
|
* still guides the result.
|
|
893
935
|
*/
|
|
894
936
|
reference: import_zod.z.union([import_zod.z.string().min(1), import_zod.z.record(CharacterDirectionSchema, import_zod.z.string().min(1))]).optional(),
|
|
@@ -1370,13 +1412,36 @@ var TilesetGetSchema = import_zod2.z.object({
|
|
|
1370
1412
|
}).passthrough(),
|
|
1371
1413
|
usage: UsageSchema
|
|
1372
1414
|
}).passthrough();
|
|
1415
|
+
var IsometricTileSubmitSchema = import_zod2.z.object({
|
|
1416
|
+
tile_id: import_zod2.z.string().min(1),
|
|
1417
|
+
background_job_id: import_zod2.z.string().min(1),
|
|
1418
|
+
status: import_zod2.z.literal("processing").default("processing")
|
|
1419
|
+
}).passthrough();
|
|
1420
|
+
var IsometricTileGetSchema = import_zod2.z.object({
|
|
1421
|
+
image: import_zod2.z.object({ base64: import_zod2.z.string().min(1), format: import_zod2.z.string().default("png") }).passthrough(),
|
|
1422
|
+
usage: UsageSchema
|
|
1423
|
+
}).passthrough();
|
|
1373
1424
|
var RevisionJobSubmitSchema = import_zod2.z.object({
|
|
1374
1425
|
background_job_id: import_zod2.z.string().min(1),
|
|
1375
1426
|
status: import_zod2.z.string().default("processing")
|
|
1376
1427
|
}).passthrough();
|
|
1428
|
+
var ObjectAnimationGroupSchema = import_zod2.z.object({
|
|
1429
|
+
animation_group_id: import_zod2.z.string().min(1),
|
|
1430
|
+
display_name: import_zod2.z.string().nullable().default(null),
|
|
1431
|
+
description: import_zod2.z.string().default(""),
|
|
1432
|
+
frame_count: import_zod2.z.number().int().default(0),
|
|
1433
|
+
directions: import_zod2.z.array(
|
|
1434
|
+
import_zod2.z.object({
|
|
1435
|
+
direction: import_zod2.z.string(),
|
|
1436
|
+
storage_urls: import_zod2.z.record(import_zod2.z.unknown()).default({}),
|
|
1437
|
+
created_at: import_zod2.z.string()
|
|
1438
|
+
}).passthrough()
|
|
1439
|
+
).default([])
|
|
1440
|
+
}).passthrough();
|
|
1377
1441
|
var PixelLabObjectSchema = import_zod2.z.object({
|
|
1378
1442
|
id: import_zod2.z.string().min(1),
|
|
1379
1443
|
name: import_zod2.z.string().nullable().default(null),
|
|
1444
|
+
state_name: import_zod2.z.string().nullable().optional(),
|
|
1380
1445
|
prompt: import_zod2.z.string().default(""),
|
|
1381
1446
|
size: import_zod2.z.object({ width: import_zod2.z.number().int().positive(), height: import_zod2.z.number().int().positive() }),
|
|
1382
1447
|
directions: import_zod2.z.number().default(0),
|
|
@@ -1388,7 +1453,9 @@ var PixelLabObjectSchema = import_zod2.z.object({
|
|
|
1388
1453
|
tags: import_zod2.z.array(import_zod2.z.string()).default([]),
|
|
1389
1454
|
status: import_zod2.z.string().nullable().default(null),
|
|
1390
1455
|
progress_percent: import_zod2.z.number().nullable().optional(),
|
|
1391
|
-
eta_seconds: import_zod2.z.number().nullable().optional()
|
|
1456
|
+
eta_seconds: import_zod2.z.number().nullable().optional(),
|
|
1457
|
+
group_id: import_zod2.z.string().nullable().optional(),
|
|
1458
|
+
animations: import_zod2.z.array(ObjectAnimationGroupSchema).default([])
|
|
1392
1459
|
}).passthrough();
|
|
1393
1460
|
var MapObjectSchema = import_zod2.z.object({
|
|
1394
1461
|
object_id: import_zod2.z.string().min(1),
|
|
@@ -1413,6 +1480,28 @@ var AnimateSubmitSchema = import_zod2.z.object({
|
|
|
1413
1480
|
status: import_zod2.z.string().default("processing"),
|
|
1414
1481
|
usage: UsageSchema
|
|
1415
1482
|
}).passthrough();
|
|
1483
|
+
var ObjectProSubmitSchema = import_zod2.z.object({
|
|
1484
|
+
object_id: import_zod2.z.string().min(1),
|
|
1485
|
+
background_job_id: import_zod2.z.string().min(1),
|
|
1486
|
+
status: import_zod2.z.string().default("processing"),
|
|
1487
|
+
usage: UsageSchema
|
|
1488
|
+
}).passthrough();
|
|
1489
|
+
var DirectionSubmissionSchema = import_zod2.z.object({
|
|
1490
|
+
direction: import_zod2.z.string(),
|
|
1491
|
+
status: import_zod2.z.string(),
|
|
1492
|
+
background_job_id: import_zod2.z.string().nullable().default(null),
|
|
1493
|
+
animation_id: import_zod2.z.string().nullable().default(null)
|
|
1494
|
+
}).passthrough();
|
|
1495
|
+
var AnimateObjectSubmitSchema = import_zod2.z.object({
|
|
1496
|
+
animation_group_id: import_zod2.z.string().min(1),
|
|
1497
|
+
object_id: import_zod2.z.string().min(1),
|
|
1498
|
+
mode: import_zod2.z.string(),
|
|
1499
|
+
frame_count: import_zod2.z.number().int(),
|
|
1500
|
+
display_name: import_zod2.z.string().nullable().default(null),
|
|
1501
|
+
description: import_zod2.z.string().default(""),
|
|
1502
|
+
submissions: import_zod2.z.array(DirectionSubmissionSchema),
|
|
1503
|
+
usage: UsageSchema
|
|
1504
|
+
}).passthrough();
|
|
1416
1505
|
var CharacterAnimationDirectionSchema = import_zod2.z.object({
|
|
1417
1506
|
direction: import_zod2.z.string(),
|
|
1418
1507
|
frame_count: import_zod2.z.number().int().min(0),
|
|
@@ -1655,6 +1744,33 @@ var PixelLabClient = class {
|
|
|
1655
1744
|
);
|
|
1656
1745
|
return { tileset: res.tileset, usage: res.usage ?? null };
|
|
1657
1746
|
}
|
|
1747
|
+
/** A single standalone isometric tile — no connectable set, no candidates. */
|
|
1748
|
+
async createIsometricTile(args) {
|
|
1749
|
+
const body = { description: args.description };
|
|
1750
|
+
if (args.imageWidth != null && args.imageHeight != null) {
|
|
1751
|
+
body.image_size = { width: args.imageWidth, height: args.imageHeight };
|
|
1752
|
+
}
|
|
1753
|
+
if (args.tileSize != null) body.isometric_tile_size = args.tileSize;
|
|
1754
|
+
if (args.tileShape) body.isometric_tile_shape = args.tileShape;
|
|
1755
|
+
if (args.outline) body.outline = args.outline;
|
|
1756
|
+
if (args.shading) body.shading = args.shading;
|
|
1757
|
+
if (args.detail) body.detail = args.detail;
|
|
1758
|
+
if (args.seed != null) body.seed = args.seed;
|
|
1759
|
+
return validateResponse(
|
|
1760
|
+
IsometricTileSubmitSchema,
|
|
1761
|
+
await this.request("/create-isometric-tile", { method: "POST", body: JSON.stringify(body) }),
|
|
1762
|
+
"create isometric tile"
|
|
1763
|
+
);
|
|
1764
|
+
}
|
|
1765
|
+
/** Throws PixelLabError(423) while the tile is still processing. */
|
|
1766
|
+
async getIsometricTile(tileId) {
|
|
1767
|
+
const res = await validateResponse(
|
|
1768
|
+
IsometricTileGetSchema,
|
|
1769
|
+
await this.request(`/isometric-tiles/${tileId}`),
|
|
1770
|
+
"get isometric tile"
|
|
1771
|
+
);
|
|
1772
|
+
return { image: res.image, usage: res.usage ?? null };
|
|
1773
|
+
}
|
|
1658
1774
|
/**
|
|
1659
1775
|
* Synchronous single-image generation. Returns the PNG inline rather than a
|
|
1660
1776
|
* job id, and is the only endpoint that honours a forced palette;
|
|
@@ -1688,6 +1804,81 @@ var PixelLabClient = class {
|
|
|
1688
1804
|
"get object"
|
|
1689
1805
|
);
|
|
1690
1806
|
}
|
|
1807
|
+
/**
|
|
1808
|
+
* A standalone object with no skeleton, one call draws the south sprite
|
|
1809
|
+
* (or takes the author's) and rotates it with v3 — the character
|
|
1810
|
+
* pro-flash pattern, minus `template_id` (objects have no skeleton) and
|
|
1811
|
+
* with a real `n_directions` choice (1 or 8, character pro-flash is
|
|
1812
|
+
* always 8). Async only: `/create-object-pro-flash` has no sync response.
|
|
1813
|
+
*/
|
|
1814
|
+
async createObjectProFlash(args) {
|
|
1815
|
+
const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
|
|
1816
|
+
const traits = args.styleTraits;
|
|
1817
|
+
const body = {
|
|
1818
|
+
description: args.description,
|
|
1819
|
+
n_directions: args.directions,
|
|
1820
|
+
...args.reference ? { first_frame: encode(args.reference) } : { image_size: { width: args.size, height: args.size } },
|
|
1821
|
+
...args.view ? { view: args.view } : {},
|
|
1822
|
+
...args.seed != null ? { seed: args.seed } : {},
|
|
1823
|
+
...args.styleReference && args.styleReferenceSize ? {
|
|
1824
|
+
style_image: { image: encode(args.styleReference), size: args.styleReferenceSize },
|
|
1825
|
+
...traits ? {
|
|
1826
|
+
style_options: {
|
|
1827
|
+
...traits.palette !== void 0 ? { color_palette: traits.palette } : {},
|
|
1828
|
+
...traits.outline !== void 0 ? { outline: traits.outline } : {},
|
|
1829
|
+
...traits.detail !== void 0 ? { detail: traits.detail } : {},
|
|
1830
|
+
...traits.shading !== void 0 ? { shading: traits.shading } : {}
|
|
1831
|
+
}
|
|
1832
|
+
} : {}
|
|
1833
|
+
} : {}
|
|
1834
|
+
};
|
|
1835
|
+
return validateResponse(
|
|
1836
|
+
ObjectProSubmitSchema,
|
|
1837
|
+
await this.request("/create-object-pro-flash", { method: "POST", body: JSON.stringify(body) }),
|
|
1838
|
+
"create-object-pro-flash"
|
|
1839
|
+
);
|
|
1840
|
+
}
|
|
1841
|
+
/** 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. */
|
|
1842
|
+
async createObjectProState(args) {
|
|
1843
|
+
const raw = await this.request(`/objects/${encodeURIComponent(args.objectId)}/states`, {
|
|
1844
|
+
method: "POST",
|
|
1845
|
+
body: JSON.stringify({
|
|
1846
|
+
edit_description: args.editDescription,
|
|
1847
|
+
...args.stateName ? { state_name: args.stateName } : {},
|
|
1848
|
+
...args.seed != null ? { seed: args.seed } : {}
|
|
1849
|
+
})
|
|
1850
|
+
});
|
|
1851
|
+
return validateResponse(ObjectProSubmitSchema, raw, "create object state");
|
|
1852
|
+
}
|
|
1853
|
+
/**
|
|
1854
|
+
* One direction of one object's animation. Unlike character animation,
|
|
1855
|
+
* this endpoint's own `replace_existing` flag regenerates an
|
|
1856
|
+
* already-animated direction directly (character has no such flag and
|
|
1857
|
+
* pixelkiln instead deletes the prior take itself before resubmitting;
|
|
1858
|
+
* see `submitCharacter`). A 1-direction object must not receive
|
|
1859
|
+
* `directions` at all -- the API 400s if it does.
|
|
1860
|
+
*/
|
|
1861
|
+
async animateObject(args) {
|
|
1862
|
+
const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
|
|
1863
|
+
const body = {
|
|
1864
|
+
mode: args.mode,
|
|
1865
|
+
...args.directions === 8 ? { directions: [args.direction] } : {},
|
|
1866
|
+
...args.animationDescription ? { animation_description: args.animationDescription } : {},
|
|
1867
|
+
...args.animationGroupId ? { animation_group_id: args.animationGroupId } : {},
|
|
1868
|
+
...args.displayName ? { display_name: args.displayName } : {},
|
|
1869
|
+
...args.frameCount != null ? { frame_count: args.frameCount } : {},
|
|
1870
|
+
...args.keepFirstFrame === false ? { keep_first_frame: false } : {},
|
|
1871
|
+
...args.startFrame ? { custom_start_frame: encode(args.startFrame) } : {},
|
|
1872
|
+
...args.endFrame ? { end_frame: encode(args.endFrame) } : {},
|
|
1873
|
+
...args.enhancePrompt ? { enhance_prompt: true } : {},
|
|
1874
|
+
...args.replaceExisting ? { replace_existing: true } : {}
|
|
1875
|
+
};
|
|
1876
|
+
const raw = await this.request(`/objects/${encodeURIComponent(args.objectId)}/animations`, {
|
|
1877
|
+
method: "POST",
|
|
1878
|
+
body: JSON.stringify(body)
|
|
1879
|
+
});
|
|
1880
|
+
return validateResponse(AnimateObjectSubmitSchema, raw, "animate object");
|
|
1881
|
+
}
|
|
1691
1882
|
async getMapObject(objectId) {
|
|
1692
1883
|
return validateResponse(
|
|
1693
1884
|
MapObjectSchema,
|
|
@@ -2633,6 +2824,8 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
2633
2824
|
tileView: spec.tileView,
|
|
2634
2825
|
tileFeature: spec.tileFeature,
|
|
2635
2826
|
outlineMode: spec.outlineMode,
|
|
2827
|
+
isometricTileSize: spec.isometricTileSize,
|
|
2828
|
+
isometricTileShape: spec.isometricTileShape,
|
|
2636
2829
|
styleImages: styleImageHashes,
|
|
2637
2830
|
// A character family: the engine and rotations of a base, the parent's
|
|
2638
2831
|
// generated bytes for a state or animation (so a regenerated parent
|
|
@@ -2661,6 +2854,23 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
2661
2854
|
concept: spec.character.concept?.sha256,
|
|
2662
2855
|
styleAnchor: spec.character.styleAnchor ? { of: spec.character.styleAnchor.assetId, sha256: spec.character.styleAnchor.sha256 } : void 0
|
|
2663
2856
|
} : void 0,
|
|
2857
|
+
// An objectPro family: same shape as character above, minus the
|
|
2858
|
+
// fields it has none of (mode/template/proportions/isometric/concept/
|
|
2859
|
+
// styleAnchor) — see ResolvedObjectPro.
|
|
2860
|
+
objectPro: spec.objectPro ? {
|
|
2861
|
+
kind: spec.objectPro.kind,
|
|
2862
|
+
directions: spec.objectPro.directions,
|
|
2863
|
+
parent: spec.objectPro.parentAssetId,
|
|
2864
|
+
parentSha256: spec.objectPro.parentSha256 ?? null,
|
|
2865
|
+
state: spec.objectPro.state,
|
|
2866
|
+
animation: spec.objectPro.animation ? {
|
|
2867
|
+
...spec.objectPro.animation,
|
|
2868
|
+
startFrame: spec.objectPro.animation.startFrame?.sha256,
|
|
2869
|
+
endFrame: spec.objectPro.animation.endFrame?.sha256
|
|
2870
|
+
} : void 0,
|
|
2871
|
+
styleTraits: spec.objectPro.styleTraits,
|
|
2872
|
+
reference: spec.objectPro.reference?.sha256
|
|
2873
|
+
} : void 0,
|
|
2664
2874
|
// A mirror's bytes come from its source's recorded outputs; the plan
|
|
2665
2875
|
// compares those directly, so only the choice of source is identity.
|
|
2666
2876
|
mirror: spec.mirror ? { of: spec.mirror.sourceAssetId } : void 0,
|
|
@@ -2728,6 +2938,29 @@ function proFlashCharacterCost(width, height, fromReference) {
|
|
|
2728
2938
|
function characterAnimationName(spec) {
|
|
2729
2939
|
return `pixelkiln:${spec.styleId}/${spec.assetId}`;
|
|
2730
2940
|
}
|
|
2941
|
+
function objectAnimationName(spec) {
|
|
2942
|
+
return `pixelkiln:${spec.styleId}/${spec.assetId}`;
|
|
2943
|
+
}
|
|
2944
|
+
function proFlashObjectCost(width, height, directions, fromReference) {
|
|
2945
|
+
const side = Math.max(32, Math.ceil(Math.max(width, height) / 4) * 4);
|
|
2946
|
+
const rotations = Math.max(1, Math.ceil(side * side * directions / 65536));
|
|
2947
|
+
if (fromReference) return rotations;
|
|
2948
|
+
const image = side <= 96 ? 5 : side <= 208 ? 6 : 9;
|
|
2949
|
+
return image + rotations;
|
|
2950
|
+
}
|
|
2951
|
+
function objectProCost(spec) {
|
|
2952
|
+
const object = spec.objectPro;
|
|
2953
|
+
if (!object) return generationCost(spec.width, spec.height, "1dir");
|
|
2954
|
+
const px = spec.width * spec.height;
|
|
2955
|
+
if (object.kind === "animation") {
|
|
2956
|
+
const animation = object.animation;
|
|
2957
|
+
if (animation.mode === "pro") return generationCost(spec.width, spec.height, "1dir");
|
|
2958
|
+
return Math.max(1, Math.ceil(px * animation.frames / 65536));
|
|
2959
|
+
}
|
|
2960
|
+
if (object.kind === "state") return generationCost(spec.width, spec.height, "1dir");
|
|
2961
|
+
const south = object.reference;
|
|
2962
|
+
return south ? proFlashObjectCost(south.width, south.height, object.directions, true) : proFlashObjectCost(spec.width, spec.height, object.directions, false);
|
|
2963
|
+
}
|
|
2731
2964
|
var CHARACTER_TEMPLATES = ["mannequin", "bear", "cat", "dog", "horse", "lion"];
|
|
2732
2965
|
var PRO_FLASH_TEMPLATES = [...CHARACTER_TEMPLATES, "custom"];
|
|
2733
2966
|
function encodeAnimationJob(job) {
|
|
@@ -2744,6 +2977,20 @@ function decodeAnimationJob(jobId) {
|
|
|
2744
2977
|
}
|
|
2745
2978
|
return null;
|
|
2746
2979
|
}
|
|
2980
|
+
function encodeObjectAnimationJob(job) {
|
|
2981
|
+
return `object-animation:${Buffer.from(JSON.stringify(job)).toString("base64url")}`;
|
|
2982
|
+
}
|
|
2983
|
+
function decodeObjectAnimationJob(jobId) {
|
|
2984
|
+
if (!jobId.startsWith("object-animation:")) return null;
|
|
2985
|
+
try {
|
|
2986
|
+
const value = JSON.parse(Buffer.from(jobId.slice("object-animation:".length), "base64url").toString("utf8"));
|
|
2987
|
+
if (typeof value?.objectId === "string" && typeof value.name === "string" && typeof value.direction === "string") {
|
|
2988
|
+
return { objectId: value.objectId, name: value.name, direction: value.direction, jobId: typeof value.jobId === "string" ? value.jobId : null };
|
|
2989
|
+
}
|
|
2990
|
+
} catch {
|
|
2991
|
+
}
|
|
2992
|
+
return null;
|
|
2993
|
+
}
|
|
2747
2994
|
var PixelLabProvider = class _PixelLabProvider {
|
|
2748
2995
|
constructor(client2) {
|
|
2749
2996
|
this.client = client2;
|
|
@@ -2762,7 +3009,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2762
3009
|
return new _PixelLabProvider(new PixelLabClient("download-only"));
|
|
2763
3010
|
}
|
|
2764
3011
|
supports(generator) {
|
|
2765
|
-
return generator === "1dir" || generator === "map" || generator === "pixflux" || generator === "tiles" || generator === "terrain" || generator === "imagePro" || generator === "character";
|
|
3012
|
+
return generator === "1dir" || generator === "map" || generator === "pixflux" || generator === "tiles" || generator === "terrain" || generator === "imagePro" || generator === "character" || generator === "isometricTile" || generator === "objectPro";
|
|
2766
3013
|
}
|
|
2767
3014
|
/**
|
|
2768
3015
|
* `inpaint` (`/inpaint-v3`, a mask) and `image-to-image` (`/edit-images-v2`,
|
|
@@ -2798,12 +3045,15 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2798
3045
|
const height = spec.revision.sourceHeight ?? spec.height;
|
|
2799
3046
|
return { unit: "generations", amount: generationCost(width, height, "1dir"), candidates: 1 };
|
|
2800
3047
|
}
|
|
2801
|
-
if (spec.generator === "tiles" || spec.generator === "terrain") {
|
|
3048
|
+
if (spec.generator === "tiles" || spec.generator === "terrain" || spec.generator === "isometricTile") {
|
|
2802
3049
|
return { unit: "generations", amount: spec.cost, candidates: spec.candidates };
|
|
2803
3050
|
}
|
|
2804
3051
|
if (spec.generator === "character") {
|
|
2805
3052
|
return { unit: "generations", amount: characterCost(spec), candidates: 1 };
|
|
2806
3053
|
}
|
|
3054
|
+
if (spec.generator === "objectPro") {
|
|
3055
|
+
return { unit: "generations", amount: objectProCost(spec), candidates: 1 };
|
|
3056
|
+
}
|
|
2807
3057
|
return {
|
|
2808
3058
|
unit: "generations",
|
|
2809
3059
|
amount: generationCost(spec.width, spec.height, spec.generator),
|
|
@@ -2829,19 +3079,19 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2829
3079
|
);
|
|
2830
3080
|
}
|
|
2831
3081
|
if (spec.generator === "map") {
|
|
2832
|
-
requirePixelLabOption("view", spec.view, ["low top-down", "high top-down", "side"]);
|
|
2833
|
-
requirePixelLabOption("outline", spec.outline, [
|
|
3082
|
+
requirePixelLabOption("map", "view", spec.view, ["low top-down", "high top-down", "side"]);
|
|
3083
|
+
requirePixelLabOption("map", "outline", spec.outline, [
|
|
2834
3084
|
"single color outline",
|
|
2835
3085
|
"selective outline",
|
|
2836
3086
|
"lineless"
|
|
2837
3087
|
]);
|
|
2838
|
-
requirePixelLabOption("shading", spec.shading, [
|
|
3088
|
+
requirePixelLabOption("map", "shading", spec.shading, [
|
|
2839
3089
|
"flat shading",
|
|
2840
3090
|
"basic shading",
|
|
2841
3091
|
"medium shading",
|
|
2842
3092
|
"detailed shading"
|
|
2843
3093
|
]);
|
|
2844
|
-
requirePixelLabOption("detail", spec.detail, [
|
|
3094
|
+
requirePixelLabOption("map", "detail", spec.detail, [
|
|
2845
3095
|
"low detail",
|
|
2846
3096
|
"medium detail",
|
|
2847
3097
|
"high detail"
|
|
@@ -2849,7 +3099,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2849
3099
|
}
|
|
2850
3100
|
if (spec.generator === "terrain") {
|
|
2851
3101
|
if (spec.outline) {
|
|
2852
|
-
requirePixelLabOption("outline", spec.outline, [
|
|
3102
|
+
requirePixelLabOption("terrain", "outline", spec.outline, [
|
|
2853
3103
|
"single color black outline",
|
|
2854
3104
|
"single color outline",
|
|
2855
3105
|
"selective outline",
|
|
@@ -2857,7 +3107,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2857
3107
|
]);
|
|
2858
3108
|
}
|
|
2859
3109
|
if (spec.shading) {
|
|
2860
|
-
requirePixelLabOption("shading", spec.shading, [
|
|
3110
|
+
requirePixelLabOption("terrain", "shading", spec.shading, [
|
|
2861
3111
|
"flat shading",
|
|
2862
3112
|
"basic shading",
|
|
2863
3113
|
"medium shading",
|
|
@@ -2866,7 +3116,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2866
3116
|
]);
|
|
2867
3117
|
}
|
|
2868
3118
|
if (spec.detail) {
|
|
2869
|
-
requirePixelLabOption("detail", spec.detail, ["low detail", "medium detail", "highly detailed"]);
|
|
3119
|
+
requirePixelLabOption("terrain", "detail", spec.detail, ["low detail", "medium detail", "highly detailed"]);
|
|
2870
3120
|
}
|
|
2871
3121
|
if (spec.terrainShapeStyle && spec.terrainMode === "pro") {
|
|
2872
3122
|
throw new Error(
|
|
@@ -2887,7 +3137,32 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2887
3137
|
);
|
|
2888
3138
|
}
|
|
2889
3139
|
}
|
|
3140
|
+
if (spec.generator === "isometricTile") {
|
|
3141
|
+
if (spec.outline) {
|
|
3142
|
+
requirePixelLabOption("isometricTile", "outline", spec.outline, [
|
|
3143
|
+
"single color outline",
|
|
3144
|
+
"selective outline",
|
|
3145
|
+
"lineless"
|
|
3146
|
+
]);
|
|
3147
|
+
}
|
|
3148
|
+
if (spec.shading) {
|
|
3149
|
+
requirePixelLabOption("isometricTile", "shading", spec.shading, [
|
|
3150
|
+
"flat shading",
|
|
3151
|
+
"basic shading",
|
|
3152
|
+
"medium shading",
|
|
3153
|
+
"detailed shading",
|
|
3154
|
+
"highly detailed shading"
|
|
3155
|
+
]);
|
|
3156
|
+
}
|
|
3157
|
+
if (spec.detail) {
|
|
3158
|
+
requirePixelLabOption("isometricTile", "detail", spec.detail, ["low detail", "medium detail", "highly detailed"]);
|
|
3159
|
+
}
|
|
3160
|
+
if (spec.width < 16 || spec.width > 64) {
|
|
3161
|
+
throw new Error(`PixelLab isometricTile: size must be 16 to 64 pixels, got ${spec.width}`);
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
2890
3164
|
if (spec.generator === "character") this.validateCharacter(spec, styleImages);
|
|
3165
|
+
if (spec.generator === "objectPro") this.validateObjectPro(spec, styleImages);
|
|
2891
3166
|
if ((spec.generator === "map" || spec.generator === "pixflux") && styleImages.length) {
|
|
2892
3167
|
throw new Error(`PixelLab ${spec.generator} does not support style images`);
|
|
2893
3168
|
}
|
|
@@ -2901,6 +3176,11 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2901
3176
|
"PixelLab imagePro does not support style images yet; /generate-image-v2's own reference_images and style_image are not modeled here"
|
|
2902
3177
|
);
|
|
2903
3178
|
}
|
|
3179
|
+
if (spec.generator === "isometricTile" && styleImages.length) {
|
|
3180
|
+
throw new Error(
|
|
3181
|
+
"PixelLab isometricTile does not support style images; /create-isometric-tile has no such field (its own init_image/color_image are not modeled here yet)"
|
|
3182
|
+
);
|
|
3183
|
+
}
|
|
2904
3184
|
for (const image of styleImages) {
|
|
2905
3185
|
if (image.width > 256 || image.height > 256) {
|
|
2906
3186
|
throw new Error(
|
|
@@ -3043,6 +3323,63 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3043
3323
|
}
|
|
3044
3324
|
}
|
|
3045
3325
|
}
|
|
3326
|
+
validateObjectPro(spec, styleImages = []) {
|
|
3327
|
+
const object = spec.objectPro;
|
|
3328
|
+
if (!object) throw new Error(`${spec.styleId}/${spec.assetId} has no objectPro shape`);
|
|
3329
|
+
const label = `${spec.styleId}/${spec.assetId}`;
|
|
3330
|
+
if (object.kind === "base" && object.reference && styleImages.length) {
|
|
3331
|
+
throw new Error(`${label}: a base rotating its own reference sprite takes no style image`);
|
|
3332
|
+
}
|
|
3333
|
+
if (object.reference && (object.reference.width > 256 || object.reference.height > 256)) {
|
|
3334
|
+
throw new Error(`${label}: reference is ${object.reference.width}x${object.reference.height}; PixelLab objectPro takes up to 256px`);
|
|
3335
|
+
}
|
|
3336
|
+
if (styleImages.length) {
|
|
3337
|
+
if (styleImages.length > 1) throw new Error(`PixelLab objectPro takes one style image`);
|
|
3338
|
+
const image = styleImages[0];
|
|
3339
|
+
if (image.width > 256 || image.height > 256) {
|
|
3340
|
+
throw new Error(`PixelLab objectPro style image is ${image.width}x${image.height}; the limit is 256px`);
|
|
3341
|
+
}
|
|
3342
|
+
if (image.width > spec.width || image.height > spec.height) {
|
|
3343
|
+
throw new Error(
|
|
3344
|
+
`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`
|
|
3345
|
+
);
|
|
3346
|
+
}
|
|
3347
|
+
}
|
|
3348
|
+
if (object.kind === "base" && (spec.width < 16 || spec.height < 16 || spec.width > 256 || spec.height > 256)) {
|
|
3349
|
+
throw new Error(`PixelLab objectPro bases must be between 16 and 256 pixels`);
|
|
3350
|
+
}
|
|
3351
|
+
if (object.kind === "base" && (spec.width % 4 || spec.height % 4)) {
|
|
3352
|
+
throw new Error(`PixelLab objectPro bases are sized in multiples of 4 pixels; ${spec.width}x${spec.height} is not`);
|
|
3353
|
+
}
|
|
3354
|
+
if (object.kind !== "base" && !object.parentAssetId) {
|
|
3355
|
+
throw new Error(`${spec.styleId}/${spec.assetId} is a ${object.kind} with no parent`);
|
|
3356
|
+
}
|
|
3357
|
+
if (object.animation) {
|
|
3358
|
+
const allowed = object.directions === 1 ? ["south"] : CHARACTER_DIRECTIONS_8;
|
|
3359
|
+
if (!allowed.includes(object.animation.direction)) {
|
|
3360
|
+
throw new Error(
|
|
3361
|
+
`${label} animates "${object.animation.direction}", but its object has ${object.directions} direction${object.directions === 1 ? "" : "s"}: ${allowed.join(", ")}`
|
|
3362
|
+
);
|
|
3363
|
+
}
|
|
3364
|
+
if (!spec.prompt.trim()) {
|
|
3365
|
+
throw new Error(`${label} needs a prompt describing the motion`);
|
|
3366
|
+
}
|
|
3367
|
+
const { startFrame, endFrame } = object.animation;
|
|
3368
|
+
for (const [name, image] of [["start frame", startFrame], ["end frame", endFrame]]) {
|
|
3369
|
+
if (image && (image.width > 256 || image.height > 256)) {
|
|
3370
|
+
throw new Error(`${label}: ${name} is ${image.width}x${image.height}; PixelLab v3 takes up to 256px`);
|
|
3371
|
+
}
|
|
3372
|
+
}
|
|
3373
|
+
if (endFrame) {
|
|
3374
|
+
const start = startFrame ?? (object.parentFile && (0, import_node_fs2.existsSync)(object.parentFile) ? imageMetadata((0, import_node_fs2.readFileSync)(object.parentFile)) : null);
|
|
3375
|
+
if (start && (start.width !== endFrame.width || start.height !== endFrame.height)) {
|
|
3376
|
+
throw new Error(
|
|
3377
|
+
`${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`
|
|
3378
|
+
);
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3046
3383
|
/**
|
|
3047
3384
|
* A base is one request that answers with the character id; a state is
|
|
3048
3385
|
* the same with the parent's id; an animation names itself after the
|
|
@@ -3136,6 +3473,78 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3136
3473
|
metadata: { character: { kind: "animation", characterId: parentId, animationName: name, direction: animation.direction, mode: animation.mode, backgroundJobIds: res.background_job_ids } }
|
|
3137
3474
|
};
|
|
3138
3475
|
}
|
|
3476
|
+
/**
|
|
3477
|
+
* Same shape as `submitCharacter`, minus the mode branching (objectPro has
|
|
3478
|
+
* exactly one base-creation path) and minus the client-side "find and
|
|
3479
|
+
* delete the prior take" dance for animation: `/objects/{id}/animations`
|
|
3480
|
+
* has its own `replace_existing` flag, so this always passes it rather
|
|
3481
|
+
* than fetching the object first to check.
|
|
3482
|
+
*/
|
|
3483
|
+
async submitObjectPro(spec, styleImages, context) {
|
|
3484
|
+
const object = spec.objectPro;
|
|
3485
|
+
if (object.kind === "base") {
|
|
3486
|
+
const styleImage = styleImages[0];
|
|
3487
|
+
const res2 = await this.client.createObjectProFlash({
|
|
3488
|
+
description: spec.prompt,
|
|
3489
|
+
directions: object.directions,
|
|
3490
|
+
size: spec.width,
|
|
3491
|
+
view: spec.view,
|
|
3492
|
+
seed: spec.seed,
|
|
3493
|
+
reference: object.reference ? readPose(spec, "reference sprite", object.reference) : void 0,
|
|
3494
|
+
styleReference: styleImage ? { base64: styleImage.base64, format: styleImage.format } : void 0,
|
|
3495
|
+
styleReferenceSize: styleImage ? { width: styleImage.width, height: styleImage.height } : void 0,
|
|
3496
|
+
styleTraits: object.styleTraits
|
|
3497
|
+
});
|
|
3498
|
+
return { jobId: res2.object_id, metadata: { objectPro: { kind: "base", objectId: res2.object_id, directions: object.directions, backgroundJobId: res2.background_job_id } } };
|
|
3499
|
+
}
|
|
3500
|
+
const parentId = context?.parentObjectId;
|
|
3501
|
+
if (!parentId) {
|
|
3502
|
+
throw new Error(
|
|
3503
|
+
`${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`
|
|
3504
|
+
);
|
|
3505
|
+
}
|
|
3506
|
+
if (object.kind === "state") {
|
|
3507
|
+
const res2 = await this.client.createObjectProState({
|
|
3508
|
+
objectId: parentId,
|
|
3509
|
+
editDescription: spec.prompt,
|
|
3510
|
+
stateName: spec.assetId.slice(0, 50),
|
|
3511
|
+
seed: spec.seed
|
|
3512
|
+
});
|
|
3513
|
+
return { jobId: res2.object_id, metadata: { objectPro: { kind: "state", objectId: res2.object_id, parentObjectId: parentId, directions: object.directions, backgroundJobId: res2.background_job_id } } };
|
|
3514
|
+
}
|
|
3515
|
+
const animation = object.animation;
|
|
3516
|
+
const name = objectAnimationName(spec);
|
|
3517
|
+
const res = await this.client.animateObject({
|
|
3518
|
+
objectId: parentId,
|
|
3519
|
+
directions: object.directions,
|
|
3520
|
+
direction: animation.direction,
|
|
3521
|
+
animationDescription: spec.prompt.trim() || void 0,
|
|
3522
|
+
displayName: name,
|
|
3523
|
+
mode: animation.mode === "pro" ? "pro" : "v3",
|
|
3524
|
+
frameCount: animation.frames,
|
|
3525
|
+
keepFirstFrame: animation.keepFirstFrame,
|
|
3526
|
+
startFrame: animation.startFrame ? readPose(spec, "start frame", animation.startFrame) : void 0,
|
|
3527
|
+
endFrame: animation.endFrame ? readPose(spec, "end frame", animation.endFrame) : void 0,
|
|
3528
|
+
enhancePrompt: animation.enhancePrompt,
|
|
3529
|
+
replaceExisting: true
|
|
3530
|
+
});
|
|
3531
|
+
const submission = res.submissions.find((s) => s.direction === animation.direction) ?? res.submissions[0];
|
|
3532
|
+
const job = { objectId: parentId, name, direction: animation.direction, jobId: submission?.background_job_id ?? null };
|
|
3533
|
+
return {
|
|
3534
|
+
jobId: encodeObjectAnimationJob(job),
|
|
3535
|
+
metadata: {
|
|
3536
|
+
objectPro: {
|
|
3537
|
+
kind: "animation",
|
|
3538
|
+
objectId: parentId,
|
|
3539
|
+
animationName: name,
|
|
3540
|
+
direction: animation.direction,
|
|
3541
|
+
mode: animation.mode,
|
|
3542
|
+
animationGroupId: res.animation_group_id,
|
|
3543
|
+
animationId: submission?.animation_id ?? null
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
3546
|
+
};
|
|
3547
|
+
}
|
|
3139
3548
|
async pollCharacter(jobId, context) {
|
|
3140
3549
|
const animation = decodeAnimationJob(jobId);
|
|
3141
3550
|
if (animation) return this.pollCharacterAnimation(animation, context);
|
|
@@ -3219,10 +3628,93 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3219
3628
|
if (found) return review(found.frames, found.animationId, found.groupId, null);
|
|
3220
3629
|
return cleanedUp ? { status: "failed", error: `animation job is gone upstream and no "${job.name}" ${job.direction} animation exists on the character` } : { status: "processing" };
|
|
3221
3630
|
}
|
|
3631
|
+
async pollObjectPro(jobId, context) {
|
|
3632
|
+
const animation = decodeObjectAnimationJob(jobId);
|
|
3633
|
+
if (animation) return this.pollObjectProAnimation(animation, context);
|
|
3634
|
+
const object = await this.client.getObject(jobId);
|
|
3635
|
+
if (object.status === "failed") return { status: "failed", error: "object generation failed upstream" };
|
|
3636
|
+
if (object.status !== "completed" || !object.rotation_urls) return { status: "processing" };
|
|
3637
|
+
const sources = objectRotationSources(object);
|
|
3638
|
+
if (!sources.length) return { status: "failed", error: "object completed with no rotation images" };
|
|
3639
|
+
const backgroundJobId = context?.metadata?.objectPro?.backgroundJobId;
|
|
3640
|
+
return {
|
|
3641
|
+
status: "ready",
|
|
3642
|
+
objectId: object.id,
|
|
3643
|
+
sourceUrl: sources[0].url,
|
|
3644
|
+
sources,
|
|
3645
|
+
metadata: {
|
|
3646
|
+
objectPro: {
|
|
3647
|
+
objectId: object.id,
|
|
3648
|
+
groupId: object.group_id ?? null,
|
|
3649
|
+
name: object.name,
|
|
3650
|
+
stateName: object.state_name ?? null,
|
|
3651
|
+
directions: object.directions,
|
|
3652
|
+
size: object.size
|
|
3653
|
+
}
|
|
3654
|
+
},
|
|
3655
|
+
billed: await this.billedForJob(backgroundJobId)
|
|
3656
|
+
};
|
|
3657
|
+
}
|
|
3658
|
+
/**
|
|
3659
|
+
* Same resilience shape as `pollCharacterAnimation`: the background job is
|
|
3660
|
+
* the authoritative record while it lasts, the object's own `animations`
|
|
3661
|
+
* list (matched by the `pixelkiln:` display name this asset submitted, or
|
|
3662
|
+
* a recorded animation id) is the fallback once PixelLab cleans the job
|
|
3663
|
+
* up. Never declares failure from absence alone -- a `rate_limited`
|
|
3664
|
+
* `DirectionSubmission` has no background job id at all, so this keeps
|
|
3665
|
+
* reporting "processing" rather than guessing a job that never existed
|
|
3666
|
+
* has failed.
|
|
3667
|
+
*/
|
|
3668
|
+
async pollObjectProAnimation(job, context) {
|
|
3669
|
+
const fps = context?.spec?.objectPro?.animation?.fps ?? 8;
|
|
3670
|
+
const review = (frames, animationId, groupId, billed) => ({
|
|
3671
|
+
status: "review-set",
|
|
3672
|
+
objectId: `${job.objectId}#${groupId ?? animationId ?? job.name}`,
|
|
3673
|
+
frameUrls: frames,
|
|
3674
|
+
sources: frames.map((url, index) => ({ url, role: `frame-${String(index).padStart(2, "0")}` })),
|
|
3675
|
+
fps,
|
|
3676
|
+
metadata: {
|
|
3677
|
+
frameSet: { fps, count: frames.length },
|
|
3678
|
+
objectPro: {
|
|
3679
|
+
kind: "animation",
|
|
3680
|
+
objectId: job.objectId,
|
|
3681
|
+
animationId,
|
|
3682
|
+
animationGroupId: groupId,
|
|
3683
|
+
animationName: job.name,
|
|
3684
|
+
direction: job.direction
|
|
3685
|
+
}
|
|
3686
|
+
},
|
|
3687
|
+
billed
|
|
3688
|
+
});
|
|
3689
|
+
if (job.jobId) {
|
|
3690
|
+
const status = await this.client.getBackgroundJob(job.jobId).catch((err) => {
|
|
3691
|
+
if (err instanceof PixelLabError && err.status === 404) return null;
|
|
3692
|
+
throw err;
|
|
3693
|
+
});
|
|
3694
|
+
if (status) {
|
|
3695
|
+
if (status.status === "failed") return { status: "failed", error: "animation generation failed upstream" };
|
|
3696
|
+
if (status.status !== "completed") return { status: "processing" };
|
|
3697
|
+
const done = status.last_response ?? {};
|
|
3698
|
+
const frames = done.storage_urls?.frames;
|
|
3699
|
+
const animationId = typeof done.animation_id === "string" ? done.animation_id : null;
|
|
3700
|
+
if (Array.isArray(frames) && frames.length && frames.every((f) => typeof f === "string")) {
|
|
3701
|
+
const object2 = await this.client.getObject(job.objectId);
|
|
3702
|
+
const group = findObjectAnimation(object2, job.name, job.direction, animationId);
|
|
3703
|
+
return review(frames, animationId, group?.groupId ?? null, billedFromUsage(status.usage));
|
|
3704
|
+
}
|
|
3705
|
+
}
|
|
3706
|
+
}
|
|
3707
|
+
const recorded = context?.metadata?.objectPro?.animationId ?? null;
|
|
3708
|
+
const object = await this.client.getObject(job.objectId);
|
|
3709
|
+
const found = findObjectAnimation(object, job.name, job.direction, recorded);
|
|
3710
|
+
if (found) return review(found.frames, found.animationId, found.groupId, null);
|
|
3711
|
+
return { status: "processing" };
|
|
3712
|
+
}
|
|
3222
3713
|
async submit(spec, styleImages, context) {
|
|
3223
3714
|
this.validate(spec, styleImages);
|
|
3224
3715
|
if (spec.revision) return this.submitRevision(spec);
|
|
3225
3716
|
if (spec.generator === "character") return this.submitCharacter(spec, styleImages, context);
|
|
3717
|
+
if (spec.generator === "objectPro") return this.submitObjectPro(spec, styleImages, context);
|
|
3226
3718
|
if (spec.generator === "pixflux") {
|
|
3227
3719
|
const swatch = spec.palette.length ? paletteSwatch(spec.palette).toString("base64") : void 0;
|
|
3228
3720
|
const { png } = await this.client.createImagePixflux({
|
|
@@ -3283,6 +3775,20 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3283
3775
|
});
|
|
3284
3776
|
return { jobId: res2.tileset_id, metadata: { backgroundJobId: res2.background_job_id } };
|
|
3285
3777
|
}
|
|
3778
|
+
if (spec.generator === "isometricTile") {
|
|
3779
|
+
const res2 = await this.client.createIsometricTile({
|
|
3780
|
+
description: spec.prompt,
|
|
3781
|
+
imageWidth: spec.width,
|
|
3782
|
+
imageHeight: spec.height,
|
|
3783
|
+
tileSize: spec.isometricTileSize,
|
|
3784
|
+
tileShape: spec.isometricTileShape,
|
|
3785
|
+
outline: spec.outline,
|
|
3786
|
+
shading: spec.shading,
|
|
3787
|
+
detail: spec.detail,
|
|
3788
|
+
seed: spec.seed
|
|
3789
|
+
});
|
|
3790
|
+
return { jobId: res2.tile_id, metadata: { backgroundJobId: res2.background_job_id } };
|
|
3791
|
+
}
|
|
3286
3792
|
if (spec.generator === "imagePro") {
|
|
3287
3793
|
const res2 = await this.client.createImagePro({
|
|
3288
3794
|
description: spec.prompt,
|
|
@@ -3399,8 +3905,10 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3399
3905
|
if (generator === "map") return this.pollMap(jobId, context);
|
|
3400
3906
|
if (generator === "tiles") return this.pollTiles(jobId, Boolean(context?.tileFeature), context);
|
|
3401
3907
|
if (generator === "terrain") return this.pollTerrain(jobId, context);
|
|
3908
|
+
if (generator === "isometricTile") return this.pollIsometricTile(jobId, context);
|
|
3402
3909
|
if (generator === "imagePro") return this.pollImagePro(jobId);
|
|
3403
3910
|
if (generator === "character") return this.pollCharacter(jobId, context);
|
|
3911
|
+
if (generator === "objectPro") return this.pollObjectPro(jobId, context);
|
|
3404
3912
|
const backgroundJobId = context?.metadata?.backgroundJobId;
|
|
3405
3913
|
const obj = await this.client.getObject(jobId);
|
|
3406
3914
|
if (obj.status === "review") {
|
|
@@ -3609,6 +4117,31 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3609
4117
|
throw err;
|
|
3610
4118
|
}
|
|
3611
4119
|
}
|
|
4120
|
+
/**
|
|
4121
|
+
* `/create-isometric-tile` reports progress the same way `/create-tileset`
|
|
4122
|
+
* does (423 while drawing, 200 once finished, no `status` field to poll),
|
|
4123
|
+
* and its image also comes back embedded as base64 rather than a
|
|
4124
|
+
* `storage_urls` link. One call is one tile with nothing to review, so
|
|
4125
|
+
* this goes straight to "ready" like a connectable `tiles`/`terrain` set.
|
|
4126
|
+
*/
|
|
4127
|
+
async pollIsometricTile(tileId, context) {
|
|
4128
|
+
try {
|
|
4129
|
+
const { image, usage } = await this.client.getIsometricTile(tileId);
|
|
4130
|
+
const backgroundJobId = context?.metadata?.backgroundJobId;
|
|
4131
|
+
const billed = billedFromUsage(usage) ?? await this.billedForJob(backgroundJobId);
|
|
4132
|
+
const file = import_node_path.default.join(_PixelLabProvider.cacheDir(), `${tileId}.png`);
|
|
4133
|
+
(0, import_node_fs2.writeFileSync)(file, Buffer.from(image.base64, "base64"));
|
|
4134
|
+
return {
|
|
4135
|
+
status: "ready",
|
|
4136
|
+
objectId: tileId,
|
|
4137
|
+
sourceUrl: `file://${file}`,
|
|
4138
|
+
billed
|
|
4139
|
+
};
|
|
4140
|
+
} catch (err) {
|
|
4141
|
+
if (err instanceof PixelLabError && err.status === 423) return { status: "processing" };
|
|
4142
|
+
throw err;
|
|
4143
|
+
}
|
|
4144
|
+
}
|
|
3612
4145
|
async selectCandidate(jobId, index, commonTag, generator) {
|
|
3613
4146
|
if (generator === "tiles") {
|
|
3614
4147
|
const set = await this.client.getTilesPro(jobId);
|
|
@@ -3725,9 +4258,9 @@ function remoteCharacter(character) {
|
|
|
3725
4258
|
animationCount: character.animation_count
|
|
3726
4259
|
};
|
|
3727
4260
|
}
|
|
3728
|
-
function requirePixelLabOption(name, value, allowed) {
|
|
4261
|
+
function requirePixelLabOption(generator, name, value, allowed) {
|
|
3729
4262
|
if (value != null && !allowed.includes(value)) {
|
|
3730
|
-
throw new Error(`PixelLab
|
|
4263
|
+
throw new Error(`PixelLab ${generator} ${name} must be one of: ${allowed.join(", ")}`);
|
|
3731
4264
|
}
|
|
3732
4265
|
}
|
|
3733
4266
|
function tilesInIndexOrder(urls) {
|
|
@@ -3793,6 +4326,28 @@ function findAnimation(character, name, direction, animationId) {
|
|
|
3793
4326
|
}
|
|
3794
4327
|
return null;
|
|
3795
4328
|
}
|
|
4329
|
+
function objectRotationSources(object) {
|
|
4330
|
+
const order = object.directions === 1 ? ["south"] : CHARACTER_DIRECTIONS_8;
|
|
4331
|
+
const sources = [];
|
|
4332
|
+
for (const direction of order) {
|
|
4333
|
+
const url = object.rotation_urls?.[direction];
|
|
4334
|
+
if (url) sources.push({ url, role: direction });
|
|
4335
|
+
}
|
|
4336
|
+
return sources;
|
|
4337
|
+
}
|
|
4338
|
+
function findObjectAnimation(object, name, direction, animationId) {
|
|
4339
|
+
for (const group of object.animations) {
|
|
4340
|
+
const match = group.directions.find((d) => d.direction === direction);
|
|
4341
|
+
const frames = match ? match.storage_urls.frames : void 0;
|
|
4342
|
+
if (!match || !Array.isArray(frames) || !frames.length || !frames.every((f) => typeof f === "string")) continue;
|
|
4343
|
+
const byName = group.display_name === name;
|
|
4344
|
+
const byId = animationId !== null && frames.some((url) => url.includes(`/animations/${animationId}/`));
|
|
4345
|
+
if (!byName && !byId) continue;
|
|
4346
|
+
const fromUrl = /\/animations\/([^/]+)\//.exec(frames[0])?.[1] ?? null;
|
|
4347
|
+
return { frames, groupId: group.animation_group_id, animationId: animationId ?? fromUrl };
|
|
4348
|
+
}
|
|
4349
|
+
return null;
|
|
4350
|
+
}
|
|
3796
4351
|
|
|
3797
4352
|
// src/providers/retrodiffusion.ts
|
|
3798
4353
|
var DEFAULT_BASE_URL = "https://api.retrodiffusion.ai/v1";
|
|
@@ -6346,14 +6901,22 @@ async function resolveSpecs(loaded, filter) {
|
|
|
6346
6901
|
size = style.terrainTileSize ?? 16;
|
|
6347
6902
|
width = size;
|
|
6348
6903
|
height = size;
|
|
6904
|
+
} else if (generator === "isometricTile") {
|
|
6905
|
+
size = asset.size ?? style.size ?? 32;
|
|
6906
|
+
width = size;
|
|
6907
|
+
height = size;
|
|
6908
|
+
} else if (generator === "objectPro") {
|
|
6909
|
+
size = asset.size ?? style.size ?? 64;
|
|
6910
|
+
width = size;
|
|
6911
|
+
height = size;
|
|
6349
6912
|
} else {
|
|
6350
6913
|
width = asset.width ?? style.size ?? 64;
|
|
6351
6914
|
height = asset.height ?? style.size ?? 64;
|
|
6352
6915
|
size = Math.max(width, height);
|
|
6353
6916
|
}
|
|
6354
|
-
if ((asset.state || asset.animation) && generator !== "character") {
|
|
6917
|
+
if ((asset.state || asset.animation) && generator !== "character" && generator !== "objectPro") {
|
|
6355
6918
|
throw new Error(
|
|
6356
|
-
`assets.${assetId}: ${asset.state ? "state" : "animation"} needs a character style; "${styleId}" generates ${generator}`
|
|
6919
|
+
`assets.${assetId}: ${asset.state ? "state" : "animation"} needs a character or objectPro style; "${styleId}" generates ${generator}`
|
|
6357
6920
|
);
|
|
6358
6921
|
}
|
|
6359
6922
|
const characterKind = asset.animation ? "animation" : asset.state ? "state" : "base";
|
|
@@ -6374,7 +6937,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
6374
6937
|
terrainUpperDescription = wrap(parsed.upper);
|
|
6375
6938
|
terrainTransitionDescription = parsed.transition ? wrap(parsed.transition) : void 0;
|
|
6376
6939
|
prompt = subject.trim();
|
|
6377
|
-
} else if (generator === "character" && characterKind !== "base") {
|
|
6940
|
+
} else if ((generator === "character" || generator === "objectPro") && characterKind !== "base") {
|
|
6378
6941
|
prompt = subject.trim();
|
|
6379
6942
|
} else {
|
|
6380
6943
|
prompt = [style.promptPrefix, subject, style.promptSuffix].map((p) => p.trim()).filter(Boolean).join(", ");
|
|
@@ -6439,8 +7002,11 @@ async function resolveSpecs(loaded, filter) {
|
|
|
6439
7002
|
terrainRaggedness: generator === "terrain" ? style.terrainRaggedness : void 0,
|
|
6440
7003
|
terrainTransitionSize: generator === "terrain" ? style.terrainTransitionSize : void 0,
|
|
6441
7004
|
terrainView: generator === "terrain" ? style.terrainView : void 0,
|
|
7005
|
+
isometricTileSize: generator === "isometricTile" ? style.isometricTileSize : void 0,
|
|
7006
|
+
isometricTileShape: generator === "isometricTile" ? style.isometricTileShape : void 0,
|
|
6442
7007
|
...generator === "character" ? { character: await resolveCharacterShape(asset, style, characterKind, { root, load: loadStyleImage }) } : {},
|
|
6443
|
-
|
|
7008
|
+
...generator === "objectPro" ? { objectPro: await resolveObjectProShape(assetId, asset, style, characterKind, { root, load: loadStyleImage }) } : {},
|
|
7009
|
+
cost: generator === "tiles" ? tilesCost(tileSize, tileVariations) : generator === "terrain" ? tilesCost(size, terrainTiles) : generator === "isometricTile" ? isometricTileCost() : generationCost(width, height, generator),
|
|
6444
7010
|
costUnit: "generations",
|
|
6445
7011
|
candidates: generator === "tiles" ? tileVariations : generator === "terrain" ? terrainTiles : generator === "1dir" || generator === "imagePro" ? candidateCount(size) : 1
|
|
6446
7012
|
};
|
|
@@ -6505,6 +7071,18 @@ async function resolveSpecs(loaded, filter) {
|
|
|
6505
7071
|
} else if (sourceSpec.character) {
|
|
6506
7072
|
resolved.character = { ...sourceSpec.character };
|
|
6507
7073
|
}
|
|
7074
|
+
if (sourceSpec.objectPro?.kind === "animation") {
|
|
7075
|
+
const facing = sourceSpec.objectPro.animation.direction;
|
|
7076
|
+
const mirrored = MIRRORED_DIRECTION[facing];
|
|
7077
|
+
if (mirrored === facing) {
|
|
7078
|
+
throw new Error(
|
|
7079
|
+
`assets.${assetId}: mirroring ${asset.mirror} gives another ${facing}-facing loop; mirror a loop facing east, west, or a diagonal`
|
|
7080
|
+
);
|
|
7081
|
+
}
|
|
7082
|
+
resolved.objectPro = { ...sourceSpec.objectPro, animation: { ...sourceSpec.objectPro.animation, direction: mirrored } };
|
|
7083
|
+
} else if (sourceSpec.objectPro) {
|
|
7084
|
+
resolved.objectPro = { ...sourceSpec.objectPro };
|
|
7085
|
+
}
|
|
6508
7086
|
resolved.mirror = { sourceAssetId: asset.mirror, sourceSpec };
|
|
6509
7087
|
resolved.generator = sourceSpec.generator;
|
|
6510
7088
|
resolved.width = sourceSpec.width;
|
|
@@ -6546,6 +7124,23 @@ async function resolveSpecs(loaded, filter) {
|
|
|
6546
7124
|
directions: resolved.character.kind === "base" ? resolved.character.directions : parentSpec.character?.directions ?? resolved.character.directions
|
|
6547
7125
|
};
|
|
6548
7126
|
}
|
|
7127
|
+
if (resolved.objectPro && characterParent) {
|
|
7128
|
+
const parentSpec = await finalize(characterParent);
|
|
7129
|
+
if (!parentSpec.objectPro) {
|
|
7130
|
+
throw new Error(`assets.${assetId}: ${characterParent} is not an objectPro asset`);
|
|
7131
|
+
}
|
|
7132
|
+
const parentDirections = parentSpec.objectPro.directions;
|
|
7133
|
+
const parentFile = memberPath(parentSpec.outFile, "south", 0, parentDirections, MediaType.PNG);
|
|
7134
|
+
resolved.objectPro = {
|
|
7135
|
+
...resolved.objectPro,
|
|
7136
|
+
parentAssetId: characterParent,
|
|
7137
|
+
parentSpec,
|
|
7138
|
+
parentFile,
|
|
7139
|
+
parentSha256: (0, import_node_fs5.existsSync)(parentFile) ? await sha256File(parentFile) : null,
|
|
7140
|
+
// A state and its animations keep the base's own rotation count.
|
|
7141
|
+
directions: resolved.objectPro.kind === "base" ? resolved.objectPro.directions : parentSpec.objectPro.directions
|
|
7142
|
+
};
|
|
7143
|
+
}
|
|
6549
7144
|
if (asset.revision) {
|
|
6550
7145
|
if (!activeProvider.supportsRevision?.(asset.revision.mode)) {
|
|
6551
7146
|
throw new Error(
|
|
@@ -6731,6 +7326,60 @@ async function resolveCharacterShape(asset, style, kind, files) {
|
|
|
6731
7326
|
}
|
|
6732
7327
|
return shape;
|
|
6733
7328
|
}
|
|
7329
|
+
async function resolveObjectProShape(assetId, asset, style, kind, files) {
|
|
7330
|
+
const directions = style.objectDirections ?? 8;
|
|
7331
|
+
const shape = {
|
|
7332
|
+
kind,
|
|
7333
|
+
directions,
|
|
7334
|
+
...style.styleTraits ? { styleTraits: style.styleTraits } : {}
|
|
7335
|
+
};
|
|
7336
|
+
if (asset.reference) {
|
|
7337
|
+
const south = typeof asset.reference === "string" ? asset.reference : asset.reference.south;
|
|
7338
|
+
if (south) {
|
|
7339
|
+
const image = await files.load(south, "Reference sprite");
|
|
7340
|
+
shape.reference = { path: import_node_path5.default.resolve(files.root, south), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
7341
|
+
}
|
|
7342
|
+
}
|
|
7343
|
+
if (asset.state) {
|
|
7344
|
+
shape.state = {
|
|
7345
|
+
paletteFromReference: asset.state.paletteFromReference,
|
|
7346
|
+
...asset.state.canvas ? { canvas: asset.state.canvas } : {}
|
|
7347
|
+
};
|
|
7348
|
+
}
|
|
7349
|
+
if (asset.animation) {
|
|
7350
|
+
for (const [field, value] of Object.entries({
|
|
7351
|
+
template: asset.animation.template,
|
|
7352
|
+
subject: asset.animation.subject,
|
|
7353
|
+
outline: asset.animation.outline,
|
|
7354
|
+
shading: asset.animation.shading,
|
|
7355
|
+
detail: asset.animation.detail
|
|
7356
|
+
})) {
|
|
7357
|
+
if (value !== void 0) {
|
|
7358
|
+
throw new Error(
|
|
7359
|
+
`assets.${assetId}: objectPro has no skeleton/template concept, so animation.${field} does not apply; describe the motion in the asset's own prompt instead`
|
|
7360
|
+
);
|
|
7361
|
+
}
|
|
7362
|
+
}
|
|
7363
|
+
if (asset.animation.mode === "template") {
|
|
7364
|
+
throw new Error(`assets.${assetId}: objectPro animations have no "template" mode; use "v3" or "pro"`);
|
|
7365
|
+
}
|
|
7366
|
+
const pose = async (rel, what) => {
|
|
7367
|
+
const image = await files.load(rel, what);
|
|
7368
|
+
return { path: import_node_path5.default.resolve(files.root, rel), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
7369
|
+
};
|
|
7370
|
+
shape.animation = {
|
|
7371
|
+
mode: asset.animation.mode ?? "v3",
|
|
7372
|
+
direction: asset.animation.direction,
|
|
7373
|
+
frames: asset.animation.frames ?? 8,
|
|
7374
|
+
fps: asset.animation.fps,
|
|
7375
|
+
keepFirstFrame: asset.animation.keepFirstFrame,
|
|
7376
|
+
...asset.animation.startFrame ? { startFrame: await pose(asset.animation.startFrame, "Animation start frame") } : {},
|
|
7377
|
+
...asset.animation.endFrame ? { endFrame: await pose(asset.animation.endFrame, "Animation end frame") } : {},
|
|
7378
|
+
...asset.animation.enhancePrompt !== void 0 ? { enhancePrompt: asset.animation.enhancePrompt } : {}
|
|
7379
|
+
};
|
|
7380
|
+
}
|
|
7381
|
+
return shape;
|
|
7382
|
+
}
|
|
6734
7383
|
function characterDirections(directions) {
|
|
6735
7384
|
return directions === 4 ? CHARACTER_DIRECTIONS_4 : CHARACTER_DIRECTIONS_8;
|
|
6736
7385
|
}
|
|
@@ -8207,6 +8856,7 @@ async function inspectRevisionReadiness(spec, lock) {
|
|
|
8207
8856
|
if (spec.mirror) return inspectMirror(spec, lock, /* @__PURE__ */ new Set());
|
|
8208
8857
|
if (spec.character?.parentSpec) return inspectCharacterParent(spec, lock, /* @__PURE__ */ new Set());
|
|
8209
8858
|
if (spec.character?.styleAnchor) return inspectStyleAnchor(spec, lock, /* @__PURE__ */ new Set());
|
|
8859
|
+
if (spec.objectPro?.parentSpec) return inspectObjectProParent(spec, lock, /* @__PURE__ */ new Set());
|
|
8210
8860
|
if (!spec.revision) return null;
|
|
8211
8861
|
return inspectRevision(spec, lock, /* @__PURE__ */ new Set());
|
|
8212
8862
|
}
|
|
@@ -8248,6 +8898,24 @@ async function inspectCharacterParent(spec, lock, seen) {
|
|
|
8248
8898
|
}
|
|
8249
8899
|
return { ready: true, reason: `${label} is current` };
|
|
8250
8900
|
}
|
|
8901
|
+
async function inspectObjectProParent(spec, lock, seen) {
|
|
8902
|
+
const object = spec.objectPro;
|
|
8903
|
+
const parentSpec = object.parentSpec;
|
|
8904
|
+
const label = `${object.kind} parent ${spec.styleId}/${object.parentAssetId}`;
|
|
8905
|
+
const dependency = await inspectParent(parentSpec, lock, seen);
|
|
8906
|
+
if (!dependency.ready) return { ready: false, reason: `${label} is not ready: ${dependency.reason}` };
|
|
8907
|
+
if (!object.parentSha256 || !object.parentFile || !(0, import_node_fs8.existsSync)(object.parentFile)) {
|
|
8908
|
+
return { ready: false, reason: `${label} has no generated south-facing file yet` };
|
|
8909
|
+
}
|
|
8910
|
+
if (await sha256File(object.parentFile) !== object.parentSha256) {
|
|
8911
|
+
return { ready: false, reason: `${label} changed after the manifest was resolved` };
|
|
8912
|
+
}
|
|
8913
|
+
const parentEntry = lock.entries[lockKey(parentSpec.styleId, parentSpec.assetId)];
|
|
8914
|
+
if (!parentEntry?.objectId) {
|
|
8915
|
+
return { ready: false, reason: `${label} has no PixelLab object id recorded` };
|
|
8916
|
+
}
|
|
8917
|
+
return { ready: true, reason: `${label} is current` };
|
|
8918
|
+
}
|
|
8251
8919
|
async function inspectRevision(spec, lock, seen) {
|
|
8252
8920
|
const revision = spec.revision;
|
|
8253
8921
|
if (!revision) return { ready: true, reason: "asset has no revision inputs" };
|
|
@@ -8299,6 +8967,10 @@ async function inspectParent(spec, lock, seen) {
|
|
|
8299
8967
|
const inputs = await inspectStyleAnchor(spec, lock, nextSeen);
|
|
8300
8968
|
if (!inputs.ready) return inputs;
|
|
8301
8969
|
}
|
|
8970
|
+
if (spec.objectPro?.parentSpec) {
|
|
8971
|
+
const inputs = await inspectObjectProParent(spec, lock, nextSeen);
|
|
8972
|
+
if (!inputs.ready) return inputs;
|
|
8973
|
+
}
|
|
8302
8974
|
if (spec.quality) {
|
|
8303
8975
|
const quality = await inspectQualityProfile(spec, lock);
|
|
8304
8976
|
return quality?.state === "approved" ? { ready: true, reason: "approved quality output is current" } : {
|
|
@@ -8343,7 +9015,8 @@ function dependencies(spec) {
|
|
|
8343
9015
|
spec.mirror?.sourceSpec,
|
|
8344
9016
|
spec.revision?.sourceSpec,
|
|
8345
9017
|
spec.character?.parentSpec,
|
|
8346
|
-
spec.character?.styleAnchor?.spec
|
|
9018
|
+
spec.character?.styleAnchor?.spec,
|
|
9019
|
+
spec.objectPro?.parentSpec
|
|
8347
9020
|
].filter((parent) => Boolean(parent));
|
|
8348
9021
|
}
|
|
8349
9022
|
function resumeCommandForStatus(status) {
|
|
@@ -11004,7 +11677,8 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
11004
11677
|
lastSubmitAt = Date.now();
|
|
11005
11678
|
try {
|
|
11006
11679
|
const refs = styleImages.get(spec.styleId) ?? [];
|
|
11007
|
-
const
|
|
11680
|
+
const parentSpec = spec.character?.parentSpec ?? spec.objectPro?.parentSpec;
|
|
11681
|
+
const parentEntry = parentSpec ? lock.entries[lockKey(parentSpec.styleId, parentSpec.assetId)] : void 0;
|
|
11008
11682
|
const anchor = spec.character?.styleAnchor;
|
|
11009
11683
|
const anchorEntry = anchor ? lock.entries[lockKey(anchor.spec.styleId, anchor.spec.assetId)] : void 0;
|
|
11010
11684
|
const replacedMetadata = previousEntry?.providerMetadata?.[provider.id];
|
|
@@ -15239,6 +15913,7 @@ async function workspaceStatus(ws, dir) {
|
|
|
15239
15913
|
isFrameSetEntry,
|
|
15240
15914
|
isMemberSetEntry,
|
|
15241
15915
|
isSensitiveSourceUrl,
|
|
15916
|
+
isometricTileCost,
|
|
15242
15917
|
listBundledRecipes,
|
|
15243
15918
|
loadClaims,
|
|
15244
15919
|
loadLock,
|