pixelkiln 0.43.0 → 0.45.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 +257 -53
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +256 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +665 -126
- package/dist/index.d.ts +665 -126
- package/dist/index.js +256 -52
- package/dist/index.js.map +1 -1
- package/docs/MANIFEST.md +52 -4
- package/package.json +1 -1
- package/schema/manifest.schema.json +49 -0
- package/schema/recipe.schema.json +7 -0
- package/skills/pixelkiln/references/pixellab.md +9 -1
package/dist/index.cjs
CHANGED
|
@@ -427,11 +427,45 @@ var CharacterAnimationSchema = import_zod.z.object({
|
|
|
427
427
|
/** v3 only: keep the resting pose as frame 0, so `frames` generated frames land as `frames + 1` files. */
|
|
428
428
|
keepFirstFrame: import_zod.z.boolean().default(true),
|
|
429
429
|
/** `template` when a template is named, otherwise `v3`; `pro` for the sequential high-quality engine. */
|
|
430
|
-
mode: CharacterAnimationModeSchema.optional()
|
|
430
|
+
mode: CharacterAnimationModeSchema.optional(),
|
|
431
|
+
/**
|
|
432
|
+
* v3 only: a manifest-relative image of the pose to start from, instead
|
|
433
|
+
* of the character's rotation for this direction. Up to 256px.
|
|
434
|
+
*/
|
|
435
|
+
startFrame: import_zod.z.string().min(1).optional(),
|
|
436
|
+
/**
|
|
437
|
+
* v3 only: a pose to animate toward. The loop then interpolates from the
|
|
438
|
+
* start frame (the rotation, or `startFrame`) to this image, which must
|
|
439
|
+
* be the same size as the start frame.
|
|
440
|
+
*/
|
|
441
|
+
endFrame: import_zod.z.string().min(1).optional(),
|
|
442
|
+
/** What is being animated, when the character's own description would mislead the model. */
|
|
443
|
+
subject: import_zod.z.string().min(1).optional(),
|
|
444
|
+
/** Template mode only: style hints for this loop, over the character's own. */
|
|
445
|
+
outline: import_zod.z.string().min(1).optional(),
|
|
446
|
+
shading: import_zod.z.string().min(1).optional(),
|
|
447
|
+
detail: import_zod.z.string().min(1).optional(),
|
|
448
|
+
/** v3 text loops only: let PixelLab expand the action into a fuller motion description first. */
|
|
449
|
+
enhancePrompt: import_zod.z.boolean().optional()
|
|
431
450
|
}).strict().superRefine((animation, context) => {
|
|
432
451
|
if (animation.frames !== void 0 && animation.frames % 2 !== 0) {
|
|
433
452
|
context.addIssue({ code: import_zod.z.ZodIssueCode.custom, message: "frames must be even", path: ["frames"] });
|
|
434
453
|
}
|
|
454
|
+
const mode2 = animation.mode ?? (animation.template ? "template" : "v3");
|
|
455
|
+
for (const key of ["startFrame", "endFrame", "enhancePrompt"]) {
|
|
456
|
+
if (animation[key] !== void 0 && mode2 !== "v3") {
|
|
457
|
+
context.addIssue({
|
|
458
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
459
|
+
message: `${key} is for v3 loops; a ${mode2} loop ${mode2 === "template" ? "starts from the character's rotation and follows its template" : "takes neither"}`,
|
|
460
|
+
path: [key]
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
for (const key of ["outline", "shading", "detail"]) {
|
|
465
|
+
if (animation[key] !== void 0 && mode2 !== "template") {
|
|
466
|
+
context.addIssue({ code: import_zod.z.ZodIssueCode.custom, message: `${key} overrides apply to template loops only`, path: [key] });
|
|
467
|
+
}
|
|
468
|
+
}
|
|
435
469
|
if (animation.template && animation.mode && animation.mode !== "template") {
|
|
436
470
|
context.addIssue({
|
|
437
471
|
code: import_zod.z.ZodIssueCode.custom,
|
|
@@ -569,6 +603,14 @@ var StyleObjectSchema = import_zod.z.object({
|
|
|
569
603
|
textGuidanceScale: import_zod.z.number().min(1).max(20).optional(),
|
|
570
604
|
/** `character` only. Draw `standard` bases and every loop in isometric view. */
|
|
571
605
|
isometric: import_zod.z.boolean().optional(),
|
|
606
|
+
/** `character` only, `v3` bases: let PixelLab expand the prompt into a fuller one before drawing. */
|
|
607
|
+
enhancePrompt: import_zod.z.boolean().optional(),
|
|
608
|
+
/**
|
|
609
|
+
* `character` only, `pro` bases: the asset id of a generated 8-direction
|
|
610
|
+
* character in this style whose look every base drawn from text or a
|
|
611
|
+
* concept follows. An asset may override it; the anchor itself ignores it.
|
|
612
|
+
*/
|
|
613
|
+
styleCharacter: import_zod.z.string().min(1).optional(),
|
|
572
614
|
/** Fixed seed for reproducibility where the endpoint supports it. */
|
|
573
615
|
seed: import_zod.z.number().int().optional(),
|
|
574
616
|
/**
|
|
@@ -741,6 +783,15 @@ var AssetSchema = import_zod.z.object({
|
|
|
741
783
|
* still guides the result.
|
|
742
784
|
*/
|
|
743
785
|
reference: import_zod.z.union([import_zod.z.string().min(1), import_zod.z.record(CharacterDirectionSchema, import_zod.z.string().min(1))]).optional(),
|
|
786
|
+
/**
|
|
787
|
+
* `character` styles, `pro` bases only: a manifest-relative concept
|
|
788
|
+
* image (a painting, a photo, a sketch; up to 1024px) that seeds the
|
|
789
|
+
* design instead of the prompt alone. A style image or `styleCharacter`
|
|
790
|
+
* may still anchor the look.
|
|
791
|
+
*/
|
|
792
|
+
concept: import_zod.z.string().min(1).optional(),
|
|
793
|
+
/** `character` styles, `pro` bases: this base's style anchor, over the style's. */
|
|
794
|
+
styleCharacter: import_zod.z.string().min(1).optional(),
|
|
744
795
|
/**
|
|
745
796
|
* Another asset of the same style flipped left to right, made locally
|
|
746
797
|
* from that asset's downloaded files at no generation cost.
|
|
@@ -822,6 +873,20 @@ var AssetSchema = import_zod.z.object({
|
|
|
822
873
|
path: ["reference"]
|
|
823
874
|
});
|
|
824
875
|
}
|
|
876
|
+
if (asset.concept && (asset.state || asset.animation || asset.mirror)) {
|
|
877
|
+
context.addIssue({
|
|
878
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
879
|
+
message: "a concept image belongs on a base; a state, animation, or mirror takes its look from its parent",
|
|
880
|
+
path: ["concept"]
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
if (asset.concept && asset.reference) {
|
|
884
|
+
context.addIssue({
|
|
885
|
+
code: import_zod.z.ZodIssueCode.custom,
|
|
886
|
+
message: "concept and reference are mutually exclusive: a concept seeds a new design, a reference is the sprite to rotate",
|
|
887
|
+
path: ["concept"]
|
|
888
|
+
});
|
|
889
|
+
}
|
|
825
890
|
if (typeof asset.reference === "object" && !Object.keys(asset.reference).length) {
|
|
826
891
|
context.addIssue({ code: import_zod.z.ZodIssueCode.custom, message: "a reference needs at least a south image", path: ["reference"] });
|
|
827
892
|
}
|
|
@@ -1493,7 +1558,8 @@ var PixelLabClient = class {
|
|
|
1493
1558
|
...args.outline ? { outline: args.outline } : {},
|
|
1494
1559
|
...args.detail ? { detail: args.detail } : {},
|
|
1495
1560
|
...args.seed != null ? { seed: args.seed } : {},
|
|
1496
|
-
...south ? { reference_image: encode(south) } : {}
|
|
1561
|
+
...south ? { reference_image: encode(south) } : {},
|
|
1562
|
+
...args.enhancePrompt ? { enhance_prompt: true } : {}
|
|
1497
1563
|
};
|
|
1498
1564
|
} else if (args.mode === "pro") {
|
|
1499
1565
|
path33 = "/create-character-pro";
|
|
@@ -1502,10 +1568,12 @@ var PixelLabClient = class {
|
|
|
1502
1568
|
image_size: imageSize,
|
|
1503
1569
|
template_id: args.template,
|
|
1504
1570
|
no_background: args.noBackground ?? true,
|
|
1505
|
-
method: south ? "rotate_character" : "create_with_style",
|
|
1571
|
+
method: south ? "rotate_character" : args.concept ? "create_from_concept" : "create_with_style",
|
|
1506
1572
|
...args.view ? { view: args.view } : {},
|
|
1507
1573
|
...args.seed != null ? { seed: args.seed } : {},
|
|
1508
|
-
...south ? { reference_image: encode(south) } : args.styleReference ? { reference_image: encode(args.styleReference) } : {}
|
|
1574
|
+
...south ? { reference_image: encode(south) } : args.styleReference ? { reference_image: encode(args.styleReference) } : {},
|
|
1575
|
+
...!south && args.concept ? { concept_image: encode(args.concept) } : {},
|
|
1576
|
+
...!south && args.styleCharacterId ? { style_character_id: args.styleCharacterId } : {}
|
|
1509
1577
|
};
|
|
1510
1578
|
} else {
|
|
1511
1579
|
path33 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
|
|
@@ -1550,6 +1618,7 @@ var PixelLabClient = class {
|
|
|
1550
1618
|
* the action text; pro is the sequential engine. One job per direction.
|
|
1551
1619
|
*/
|
|
1552
1620
|
async animateCharacter(args) {
|
|
1621
|
+
const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
|
|
1553
1622
|
const raw = await this.request("/animate-character", {
|
|
1554
1623
|
method: "POST",
|
|
1555
1624
|
body: JSON.stringify({
|
|
@@ -1559,10 +1628,18 @@ var PixelLabClient = class {
|
|
|
1559
1628
|
directions: args.directions,
|
|
1560
1629
|
...args.template ? { template_animation_id: args.template } : {},
|
|
1561
1630
|
...args.actionDescription ? { action_description: args.actionDescription } : {},
|
|
1631
|
+
...args.description ? { description: args.description } : {},
|
|
1562
1632
|
...args.mode === "v3" && args.frameCount ? { frame_count: args.frameCount } : {},
|
|
1563
1633
|
...args.mode === "v3" && args.keepFirstFrame === false ? { keep_first_frame: false } : {},
|
|
1634
|
+
...args.mode === "v3" && args.startFrame ? { custom_start_frame: encode(args.startFrame) } : {},
|
|
1635
|
+
...args.mode === "v3" && args.endFrame ? { end_frame: encode(args.endFrame) } : {},
|
|
1636
|
+
...args.mode === "v3" && args.enhancePrompt ? { enhance_prompt: true } : {},
|
|
1564
1637
|
...args.mode === "template" && args.textGuidanceScale !== void 0 ? { text_guidance_scale: args.textGuidanceScale } : {},
|
|
1638
|
+
...args.mode === "template" && args.outline ? { outline: args.outline } : {},
|
|
1639
|
+
...args.mode === "template" && args.shading ? { shading: args.shading } : {},
|
|
1640
|
+
...args.mode === "template" && args.detail ? { detail: args.detail } : {},
|
|
1565
1641
|
...args.isometric !== void 0 ? { isometric: args.isometric } : {},
|
|
1642
|
+
...args.paletteSwatchBase64 ? { color_image: { type: "base64", base64: args.paletteSwatchBase64, format: "png" }, force_colors: true } : {},
|
|
1566
1643
|
...args.seed != null ? { seed: args.seed } : {}
|
|
1567
1644
|
})
|
|
1568
1645
|
});
|
|
@@ -2159,6 +2236,34 @@ function skipSubBlocks(bytes, start) {
|
|
|
2159
2236
|
function cacheFileName(hash, mediaType = MediaType.PNG) {
|
|
2160
2237
|
return `${hash}${mediaExtension(mediaType)}`;
|
|
2161
2238
|
}
|
|
2239
|
+
function imageMetadata(buf) {
|
|
2240
|
+
if (buf.length >= 24 && buf.readUInt32BE(0) === 2303741511 && buf.readUInt32BE(4) === 218765834 && buf.toString("ascii", 12, 16) === "IHDR") {
|
|
2241
|
+
return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20), format: "png" };
|
|
2242
|
+
}
|
|
2243
|
+
if (buf.length < 4 || buf[0] !== 255 || buf[1] !== 216) return null;
|
|
2244
|
+
let offset = 2;
|
|
2245
|
+
while (offset + 3 < buf.length) {
|
|
2246
|
+
if (buf[offset] !== 255) return null;
|
|
2247
|
+
while (buf[offset] === 255) offset++;
|
|
2248
|
+
const marker = buf[offset++];
|
|
2249
|
+
if (marker == null || marker === 217 || marker === 218) break;
|
|
2250
|
+
if (marker === 1 || marker >= 208 && marker <= 215) continue;
|
|
2251
|
+
if (offset + 2 > buf.length) return null;
|
|
2252
|
+
const length = buf.readUInt16BE(offset);
|
|
2253
|
+
if (length < 2 || offset + length > buf.length) return null;
|
|
2254
|
+
const isStartOfFrame = marker >= 192 && marker <= 207 && ![196, 200, 204].includes(marker);
|
|
2255
|
+
if (isStartOfFrame) {
|
|
2256
|
+
if (length < 7) return null;
|
|
2257
|
+
return {
|
|
2258
|
+
width: buf.readUInt16BE(offset + 5),
|
|
2259
|
+
height: buf.readUInt16BE(offset + 3),
|
|
2260
|
+
format: "jpeg"
|
|
2261
|
+
};
|
|
2262
|
+
}
|
|
2263
|
+
offset += length;
|
|
2264
|
+
}
|
|
2265
|
+
return null;
|
|
2266
|
+
}
|
|
2162
2267
|
|
|
2163
2268
|
// src/providers/pixellab.ts
|
|
2164
2269
|
var import_node_fs2 = require("fs");
|
|
@@ -2219,12 +2324,20 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
2219
2324
|
parent: spec.character.parentAssetId,
|
|
2220
2325
|
parentSha256: spec.character.parentSha256 ?? null,
|
|
2221
2326
|
state: spec.character.state,
|
|
2222
|
-
|
|
2327
|
+
// Pose images enter as their hashes, never their paths.
|
|
2328
|
+
animation: spec.character.animation ? {
|
|
2329
|
+
...spec.character.animation,
|
|
2330
|
+
startFrame: spec.character.animation.startFrame?.sha256,
|
|
2331
|
+
endFrame: spec.character.animation.endFrame?.sha256
|
|
2332
|
+
} : void 0,
|
|
2223
2333
|
// Absent keys keep the hashes of manifests that never set these.
|
|
2224
2334
|
proportions: spec.character.proportions,
|
|
2225
2335
|
textGuidanceScale: spec.character.textGuidanceScale,
|
|
2226
2336
|
isometric: spec.character.isometric,
|
|
2227
|
-
|
|
2337
|
+
enhancePrompt: spec.character.enhancePrompt,
|
|
2338
|
+
reference: spec.character.reference ? Object.fromEntries(Object.entries(spec.character.reference).map(([direction, image]) => [direction, image.sha256])) : void 0,
|
|
2339
|
+
concept: spec.character.concept?.sha256,
|
|
2340
|
+
styleAnchor: spec.character.styleAnchor ? { of: spec.character.styleAnchor.assetId, sha256: spec.character.styleAnchor.sha256 } : void 0
|
|
2228
2341
|
} : void 0,
|
|
2229
2342
|
// A mirror's bytes come from its source's recorded outputs; the plan
|
|
2230
2343
|
// compares those directly, so only the choice of source is identity.
|
|
@@ -2392,6 +2505,38 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2392
2505
|
throw new Error(`${label}: proportions apply to the mannequin template; ${character.template} is a quadruped`);
|
|
2393
2506
|
}
|
|
2394
2507
|
}
|
|
2508
|
+
if (character.kind === "base" && character.enhancePrompt !== void 0 && character.mode !== "v3") {
|
|
2509
|
+
throw new Error(`${label}: enhancePrompt applies to v3 bases; the ${character.mode} engine does not take it`);
|
|
2510
|
+
}
|
|
2511
|
+
if (character.animation) {
|
|
2512
|
+
const { startFrame, endFrame } = character.animation;
|
|
2513
|
+
for (const [name, image] of [["start frame", startFrame], ["end frame", endFrame]]) {
|
|
2514
|
+
if (image && (image.width > 256 || image.height > 256)) {
|
|
2515
|
+
throw new Error(`${label}: ${name} is ${image.width}x${image.height}; PixelLab v3 takes up to 256px`);
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
if (endFrame) {
|
|
2519
|
+
const start = startFrame ?? (character.parentFile && (0, import_node_fs2.existsSync)(character.parentFile) ? imageMetadata((0, import_node_fs2.readFileSync)(character.parentFile)) : null);
|
|
2520
|
+
if (start && (start.width !== endFrame.width || start.height !== endFrame.height)) {
|
|
2521
|
+
throw new Error(
|
|
2522
|
+
`${label}: end frame is ${endFrame.width}x${endFrame.height} but the ${startFrame ? "start frame" : "character's rotation"} is ${start.width}x${start.height}; PixelLab interpolates between frames of one size`
|
|
2523
|
+
);
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
if (character.concept) {
|
|
2528
|
+
if (character.mode !== "pro") throw new Error(`${label}: a concept image is a pro input; the ${character.mode} engine draws from text or a reference`);
|
|
2529
|
+
if (character.concept.width > 1024 || character.concept.height > 1024) {
|
|
2530
|
+
throw new Error(`${label}: concept image is ${character.concept.width}x${character.concept.height}; PixelLab pro takes up to 1024px`);
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
if (character.styleAnchor) {
|
|
2534
|
+
if (character.mode !== "pro") throw new Error(`${label}: styleCharacter is a pro input; the ${character.mode} engine has no style anchor`);
|
|
2535
|
+
if (character.reference) throw new Error(`${label}: a pro base rotating its own reference takes no styleCharacter`);
|
|
2536
|
+
if (character.styleAnchor.spec.character?.directions !== 8) {
|
|
2537
|
+
throw new Error(`${label}: styleCharacter ${character.styleAnchor.assetId} has ${character.styleAnchor.spec.character?.directions} directions; PixelLab wants an 8-direction anchor`);
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2395
2540
|
if (character.reference) {
|
|
2396
2541
|
const given = Object.keys(character.reference);
|
|
2397
2542
|
if (!character.reference.south) throw new Error(`${label}: a reference needs a south-facing sprite`);
|
|
@@ -2492,7 +2637,10 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2492
2637
|
textGuidanceScale: character.textGuidanceScale,
|
|
2493
2638
|
isometric: character.isometric,
|
|
2494
2639
|
reference: character.reference ? readReference(spec, character.reference) : void 0,
|
|
2495
|
-
styleReference: styleImage ? { base64: styleImage.base64, format: styleImage.format } : void 0
|
|
2640
|
+
styleReference: styleImage ? { base64: styleImage.base64, format: styleImage.format } : void 0,
|
|
2641
|
+
enhancePrompt: character.enhancePrompt,
|
|
2642
|
+
concept: character.concept ? readPose(spec, "concept image", character.concept) : void 0,
|
|
2643
|
+
styleCharacterId: character.styleAnchor ? requireStyleObjectId(spec, context) : void 0
|
|
2496
2644
|
});
|
|
2497
2645
|
return { jobId: res2.character_id, metadata: { character: { kind: "base", characterId: res2.character_id, mode: character.mode, directions: character.directions, backgroundJobId: res2.background_job_id } } };
|
|
2498
2646
|
}
|
|
@@ -2536,7 +2684,15 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2536
2684
|
directions: [animation.direction],
|
|
2537
2685
|
seed: spec.seed,
|
|
2538
2686
|
textGuidanceScale: character.textGuidanceScale,
|
|
2539
|
-
isometric: character.isometric
|
|
2687
|
+
isometric: character.isometric,
|
|
2688
|
+
startFrame: animation.startFrame ? readPose(spec, "start frame", animation.startFrame) : void 0,
|
|
2689
|
+
endFrame: animation.endFrame ? readPose(spec, "end frame", animation.endFrame) : void 0,
|
|
2690
|
+
description: animation.subject,
|
|
2691
|
+
outline: animation.outline,
|
|
2692
|
+
shading: animation.shading,
|
|
2693
|
+
detail: animation.detail,
|
|
2694
|
+
enhancePrompt: animation.enhancePrompt,
|
|
2695
|
+
paletteSwatchBase64: spec.palette.length ? paletteSwatch(spec.palette).toString("base64") : void 0
|
|
2540
2696
|
});
|
|
2541
2697
|
const job = { characterId: parentId, name, direction: animation.direction, jobIds: res.background_job_ids };
|
|
2542
2698
|
return {
|
|
@@ -2904,6 +3060,21 @@ function readReference(spec, reference) {
|
|
|
2904
3060
|
}
|
|
2905
3061
|
return images;
|
|
2906
3062
|
}
|
|
3063
|
+
function requireStyleObjectId(spec, context) {
|
|
3064
|
+
if (!context?.styleObjectId) {
|
|
3065
|
+
throw new Error(
|
|
3066
|
+
`${spec.styleId}/${spec.assetId} anchors its style on ${spec.character.styleAnchor.assetId}, which has no PixelLab character id in the lockfile yet; generate the anchor first`
|
|
3067
|
+
);
|
|
3068
|
+
}
|
|
3069
|
+
return context.styleObjectId;
|
|
3070
|
+
}
|
|
3071
|
+
function readPose(spec, what, image) {
|
|
3072
|
+
const bytes = (0, import_node_fs2.readFileSync)(image.path);
|
|
3073
|
+
if (sha256(bytes) !== image.sha256) {
|
|
3074
|
+
throw new Error(`${spec.styleId}/${spec.assetId}: ${what} changed after the manifest was resolved: ${image.path}`);
|
|
3075
|
+
}
|
|
3076
|
+
return { base64: bytes.toString("base64"), format: image.format };
|
|
3077
|
+
}
|
|
2907
3078
|
function rotationSources(character) {
|
|
2908
3079
|
const order = character.directions === 4 ? CHARACTER_DIRECTIONS_4 : CHARACTER_DIRECTIONS_8;
|
|
2909
3080
|
const sources = [];
|
|
@@ -5596,6 +5767,18 @@ async function resolveSpecs(loaded, filter) {
|
|
|
5596
5767
|
finalized.add(assetId);
|
|
5597
5768
|
return resolved;
|
|
5598
5769
|
}
|
|
5770
|
+
const anchorId = asset.styleCharacter ?? style.styleCharacter;
|
|
5771
|
+
if (resolved.character?.kind === "base" && anchorId && anchorId !== assetId) {
|
|
5772
|
+
const anchorSpec = await finalize(anchorId);
|
|
5773
|
+
if (!anchorSpec.character || anchorSpec.character.kind === "animation") {
|
|
5774
|
+
throw new Error(`assets.${assetId}: styleCharacter ${anchorId} is not a character base or state`);
|
|
5775
|
+
}
|
|
5776
|
+
const file = memberPath(anchorSpec.outFile, "south", 0, anchorSpec.character.directions, MediaType.PNG);
|
|
5777
|
+
resolved.character = {
|
|
5778
|
+
...resolved.character,
|
|
5779
|
+
styleAnchor: { assetId: anchorId, spec: anchorSpec, file, sha256: (0, import_node_fs5.existsSync)(file) ? await sha256File(file) : null }
|
|
5780
|
+
};
|
|
5781
|
+
}
|
|
5599
5782
|
const characterParent = asset.state?.of ?? asset.animation?.of;
|
|
5600
5783
|
if (resolved.character && characterParent) {
|
|
5601
5784
|
const parentSpec = await finalize(characterParent);
|
|
@@ -5734,34 +5917,6 @@ async function resolveStyleImages(loaded, styleId) {
|
|
|
5734
5917
|
async function styleImagesBase64(loaded, styleId) {
|
|
5735
5918
|
return (await resolveStyleImages(loaded, styleId)).map((img) => img.base64);
|
|
5736
5919
|
}
|
|
5737
|
-
function imageMetadata(buf) {
|
|
5738
|
-
if (buf.length >= 24 && buf.readUInt32BE(0) === 2303741511 && buf.readUInt32BE(4) === 218765834 && buf.toString("ascii", 12, 16) === "IHDR") {
|
|
5739
|
-
return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20), format: "png" };
|
|
5740
|
-
}
|
|
5741
|
-
if (buf.length < 4 || buf[0] !== 255 || buf[1] !== 216) return null;
|
|
5742
|
-
let offset = 2;
|
|
5743
|
-
while (offset + 3 < buf.length) {
|
|
5744
|
-
if (buf[offset] !== 255) return null;
|
|
5745
|
-
while (buf[offset] === 255) offset++;
|
|
5746
|
-
const marker = buf[offset++];
|
|
5747
|
-
if (marker == null || marker === 217 || marker === 218) break;
|
|
5748
|
-
if (marker === 1 || marker >= 208 && marker <= 215) continue;
|
|
5749
|
-
if (offset + 2 > buf.length) return null;
|
|
5750
|
-
const length = buf.readUInt16BE(offset);
|
|
5751
|
-
if (length < 2 || offset + length > buf.length) return null;
|
|
5752
|
-
const isStartOfFrame = marker >= 192 && marker <= 207 && ![196, 200, 204].includes(marker);
|
|
5753
|
-
if (isStartOfFrame) {
|
|
5754
|
-
if (length < 7) return null;
|
|
5755
|
-
return {
|
|
5756
|
-
width: buf.readUInt16BE(offset + 5),
|
|
5757
|
-
height: buf.readUInt16BE(offset + 3),
|
|
5758
|
-
format: "jpeg"
|
|
5759
|
-
};
|
|
5760
|
-
}
|
|
5761
|
-
offset += length;
|
|
5762
|
-
}
|
|
5763
|
-
return null;
|
|
5764
|
-
}
|
|
5765
5920
|
async function resolveCharacterShape(asset, style, kind, files) {
|
|
5766
5921
|
const mode2 = style.mode ?? "standard";
|
|
5767
5922
|
const directions = mode2 === "standard" ? style.directions ?? 8 : 8;
|
|
@@ -5773,8 +5928,13 @@ async function resolveCharacterShape(asset, style, kind, files) {
|
|
|
5773
5928
|
template: style.template ?? "mannequin",
|
|
5774
5929
|
...proportions !== void 0 ? { proportions } : {},
|
|
5775
5930
|
...style.textGuidanceScale !== void 0 ? { textGuidanceScale: style.textGuidanceScale } : {},
|
|
5776
|
-
...style.isometric !== void 0 ? { isometric: style.isometric } : {}
|
|
5931
|
+
...style.isometric !== void 0 ? { isometric: style.isometric } : {},
|
|
5932
|
+
...style.enhancePrompt !== void 0 ? { enhancePrompt: style.enhancePrompt } : {}
|
|
5777
5933
|
};
|
|
5934
|
+
if (asset.concept) {
|
|
5935
|
+
const image = await files.load(asset.concept, "Concept image");
|
|
5936
|
+
shape.concept = { path: import_node_path5.default.resolve(files.root, asset.concept), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
5937
|
+
}
|
|
5778
5938
|
if (asset.reference) {
|
|
5779
5939
|
const byDirection = typeof asset.reference === "string" ? { south: asset.reference } : asset.reference;
|
|
5780
5940
|
shape.reference = {};
|
|
@@ -5797,13 +5957,24 @@ async function resolveCharacterShape(asset, style, kind, files) {
|
|
|
5797
5957
|
}
|
|
5798
5958
|
if (asset.animation) {
|
|
5799
5959
|
const animationMode = asset.animation.mode ?? (asset.animation.template ? "template" : "v3");
|
|
5960
|
+
const pose = async (rel, what) => {
|
|
5961
|
+
const image = await files.load(rel, what);
|
|
5962
|
+
return { path: import_node_path5.default.resolve(files.root, rel), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
5963
|
+
};
|
|
5800
5964
|
shape.animation = {
|
|
5801
5965
|
mode: animationMode,
|
|
5802
5966
|
...asset.animation.template ? { template: asset.animation.template } : {},
|
|
5803
5967
|
direction: asset.animation.direction,
|
|
5804
5968
|
frames: asset.animation.frames ?? 8,
|
|
5805
5969
|
fps: asset.animation.fps,
|
|
5806
|
-
keepFirstFrame: asset.animation.keepFirstFrame
|
|
5970
|
+
keepFirstFrame: asset.animation.keepFirstFrame,
|
|
5971
|
+
...asset.animation.startFrame ? { startFrame: await pose(asset.animation.startFrame, "Animation start frame") } : {},
|
|
5972
|
+
...asset.animation.endFrame ? { endFrame: await pose(asset.animation.endFrame, "Animation end frame") } : {},
|
|
5973
|
+
...asset.animation.subject ? { subject: asset.animation.subject } : {},
|
|
5974
|
+
...asset.animation.outline ? { outline: asset.animation.outline } : {},
|
|
5975
|
+
...asset.animation.shading ? { shading: asset.animation.shading } : {},
|
|
5976
|
+
...asset.animation.detail ? { detail: asset.animation.detail } : {},
|
|
5977
|
+
...asset.animation.enhancePrompt !== void 0 ? { enhancePrompt: asset.animation.enhancePrompt } : {}
|
|
5807
5978
|
};
|
|
5808
5979
|
}
|
|
5809
5980
|
return shape;
|
|
@@ -7283,9 +7454,24 @@ var import_node_path11 = __toESM(require("path"), 1);
|
|
|
7283
7454
|
async function inspectRevisionReadiness(spec, lock) {
|
|
7284
7455
|
if (spec.mirror) return inspectMirror(spec, lock, /* @__PURE__ */ new Set());
|
|
7285
7456
|
if (spec.character?.parentSpec) return inspectCharacterParent(spec, lock, /* @__PURE__ */ new Set());
|
|
7457
|
+
if (spec.character?.styleAnchor) return inspectStyleAnchor(spec, lock, /* @__PURE__ */ new Set());
|
|
7286
7458
|
if (!spec.revision) return null;
|
|
7287
7459
|
return inspectRevision(spec, lock, /* @__PURE__ */ new Set());
|
|
7288
7460
|
}
|
|
7461
|
+
async function inspectStyleAnchor(spec, lock, seen) {
|
|
7462
|
+
const anchor = spec.character.styleAnchor;
|
|
7463
|
+
const label = `style anchor ${spec.styleId}/${anchor.assetId}`;
|
|
7464
|
+
const dependency = await inspectParent(anchor.spec, lock, seen);
|
|
7465
|
+
if (!dependency.ready) return { ready: false, reason: `${label} is not ready: ${dependency.reason}` };
|
|
7466
|
+
if (!anchor.sha256 || !(0, import_node_fs8.existsSync)(anchor.file)) return { ready: false, reason: `${label} has no generated south-facing file yet` };
|
|
7467
|
+
if (await sha256File(anchor.file) !== anchor.sha256) {
|
|
7468
|
+
return { ready: false, reason: `${label} changed after the manifest was resolved` };
|
|
7469
|
+
}
|
|
7470
|
+
if (!lock.entries[lockKey(anchor.spec.styleId, anchor.spec.assetId)]?.objectId) {
|
|
7471
|
+
return { ready: false, reason: `${label} has no PixelLab character id recorded` };
|
|
7472
|
+
}
|
|
7473
|
+
return { ready: true, reason: `${label} is current` };
|
|
7474
|
+
}
|
|
7289
7475
|
async function inspectMirror(spec, lock, seen) {
|
|
7290
7476
|
const source = spec.mirror.sourceSpec;
|
|
7291
7477
|
const label = `mirror source ${spec.styleId}/${spec.mirror.sourceAssetId}`;
|
|
@@ -7357,6 +7543,10 @@ async function inspectParent(spec, lock, seen) {
|
|
|
7357
7543
|
const inputs = await inspectCharacterParent(spec, lock, nextSeen);
|
|
7358
7544
|
if (!inputs.ready) return inputs;
|
|
7359
7545
|
}
|
|
7546
|
+
if (spec.character?.styleAnchor) {
|
|
7547
|
+
const inputs = await inspectStyleAnchor(spec, lock, nextSeen);
|
|
7548
|
+
if (!inputs.ready) return inputs;
|
|
7549
|
+
}
|
|
7360
7550
|
if (spec.quality) {
|
|
7361
7551
|
const quality = await inspectQualityProfile(spec, lock);
|
|
7362
7552
|
return quality?.state === "approved" ? { ready: true, reason: "approved quality output is current" } : {
|
|
@@ -7396,6 +7586,14 @@ async function anyOutputModified(entry, spec) {
|
|
|
7396
7586
|
}
|
|
7397
7587
|
return false;
|
|
7398
7588
|
}
|
|
7589
|
+
function dependencies(spec) {
|
|
7590
|
+
return [
|
|
7591
|
+
spec.mirror?.sourceSpec,
|
|
7592
|
+
spec.revision?.sourceSpec,
|
|
7593
|
+
spec.character?.parentSpec,
|
|
7594
|
+
spec.character?.styleAnchor?.spec
|
|
7595
|
+
].filter((parent) => Boolean(parent));
|
|
7596
|
+
}
|
|
7399
7597
|
function resumeCommandForStatus(status) {
|
|
7400
7598
|
if (status === "pending" || status === "processing") return "poll";
|
|
7401
7599
|
if (status === "review") return "pick";
|
|
@@ -7504,16 +7702,19 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
7504
7702
|
const stateOf = new Map(items.map((i) => [i.key, i.state]));
|
|
7505
7703
|
for (const item of items) {
|
|
7506
7704
|
if (!isActionable(item)) continue;
|
|
7507
|
-
const parent
|
|
7508
|
-
|
|
7509
|
-
|
|
7510
|
-
|
|
7511
|
-
|
|
7512
|
-
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
item.
|
|
7516
|
-
|
|
7705
|
+
for (const parent of dependencies(item.spec)) {
|
|
7706
|
+
const parentKey = lockKey(parent.styleId, parent.assetId);
|
|
7707
|
+
const parentState = stateOf.get(parentKey);
|
|
7708
|
+
if (parentState && isActionable({ state: parentState })) {
|
|
7709
|
+
item.state = "blocked";
|
|
7710
|
+
item.reason = `parent ${parentKey} is being generated in this run; this follows in the next wave`;
|
|
7711
|
+
break;
|
|
7712
|
+
}
|
|
7713
|
+
if (item.spec.mirror && (parentState === "recoverable" || parentState === "in-flight")) {
|
|
7714
|
+
item.state = "blocked";
|
|
7715
|
+
item.reason = `mirror source ${parentKey} is ${parentState}; this follows in the next wave`;
|
|
7716
|
+
break;
|
|
7717
|
+
}
|
|
7517
7718
|
}
|
|
7518
7719
|
}
|
|
7519
7720
|
const actionable = items.filter(isActionable);
|
|
@@ -9992,10 +10193,13 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
9992
10193
|
try {
|
|
9993
10194
|
const refs = styleImages.get(spec.styleId) ?? [];
|
|
9994
10195
|
const parentEntry = spec.character?.parentSpec ? lock.entries[lockKey(spec.character.parentSpec.styleId, spec.character.parentSpec.assetId)] : void 0;
|
|
10196
|
+
const anchor = spec.character?.styleAnchor;
|
|
10197
|
+
const anchorEntry = anchor ? lock.entries[lockKey(anchor.spec.styleId, anchor.spec.assetId)] : void 0;
|
|
9995
10198
|
const replacedMetadata = previousEntry?.providerMetadata?.[provider.id];
|
|
9996
10199
|
const { jobId, metadata } = await provider.submit(spec, refs, {
|
|
9997
10200
|
...previousJobId ? { previousJobId } : {},
|
|
9998
10201
|
...parentEntry?.objectId ? { parentObjectId: parentEntry.objectId } : {},
|
|
10202
|
+
...anchorEntry?.objectId ? { styleObjectId: anchorEntry.objectId } : {},
|
|
9999
10203
|
...replacedMetadata ? { replacedMetadata } : {},
|
|
10000
10204
|
...previousMetadata ? { previousMetadata } : {},
|
|
10001
10205
|
checkpoint: async (checkpoint) => {
|
|
@@ -12604,7 +12808,7 @@ var pin_default = {
|
|
|
12604
12808
|
godot: "4.7.2",
|
|
12605
12809
|
extensionsApi: 9,
|
|
12606
12810
|
protocol: 4,
|
|
12607
|
-
release: "editor-pixelorama-v1.2.2-pk.
|
|
12811
|
+
release: "editor-pixelorama-v1.2.2-pk.26",
|
|
12608
12812
|
files: {
|
|
12609
12813
|
"index.apple-touch-icon.png": {
|
|
12610
12814
|
sha256: "ee5434873e0fed91e9a888c3ea5a8b79beac25999308be7a6eb41bf4ce640d27",
|
|
@@ -12619,7 +12823,7 @@ var pin_default = {
|
|
|
12619
12823
|
bytes: 7298
|
|
12620
12824
|
},
|
|
12621
12825
|
"index.html": {
|
|
12622
|
-
sha256: "
|
|
12826
|
+
sha256: "326101ab02bb4a29a7964214c9307c7dff26e9d66b8ebaaf6e7a3987e84a05b2",
|
|
12623
12827
|
bytes: 5442
|
|
12624
12828
|
},
|
|
12625
12829
|
"index.icon.png": {
|
|
@@ -12631,8 +12835,8 @@ var pin_default = {
|
|
|
12631
12835
|
bytes: 279815
|
|
12632
12836
|
},
|
|
12633
12837
|
"index.pck": {
|
|
12634
|
-
sha256: "
|
|
12635
|
-
bytes:
|
|
12838
|
+
sha256: "e26292650bb996965acd1ba70e74d2b4020b063d14f1276701312c1d34cb0a83",
|
|
12839
|
+
bytes: 6431932
|
|
12636
12840
|
},
|
|
12637
12841
|
"index.png": {
|
|
12638
12842
|
sha256: "6e3ca3eb4a99e4c07bdd89ebc873b440ec0ffd66ce1955160da9e7ed80d538d7",
|