Bladeren bron

Generate separate boss preview images

bang 2 weken geleden
bovenliggende
commit
7d279918cd
2 gewijzigde bestanden met toevoegingen van 51 en 6 verwijderingen
  1. 21 1
      pipeline.py
  2. 30 5
      spine_builder.py

+ 21 - 1
pipeline.py

@@ -174,6 +174,20 @@ def run(manifest, out_root, creds=None, log=print, merge_existing=False):
             if c.get("type") == "spine_parts":
                 parts = c.get("parts") or []
                 part_images = {}
+                preview_img = None
+                try:
+                    preview_prompt = ", ".join(x for x in [
+                        c.get("prompt", ""),
+                        style,
+                        transparent_prompt(
+                            "single complete assembled boss character preview, full body, centered, readable silhouette, no separated parts, no sprite sheet, no atlas"
+                        )
+                    ] if x)
+                    log(f"🖼  [{cid}] 生成完整 Boss 预览图…")
+                    preview_img = generate_checked(f"{cid}/preview", preview_prompt,
+                                                   c.get("size", creds.get("size", "1024x1024")), True)
+                except Exception as e:
+                    log(f"⚠️ [{cid}] 完整预览图生成失败,稍后使用拆件拼装预览兜底:{e}")
                 sheet_cfg = c.get("spriteSheet") or {}
                 use_sheet = c.get("partGeneration") == "sprite_sheet" or sheet_cfg.get("enabled")
                 if use_sheet:
@@ -206,7 +220,13 @@ def run(manifest, out_root, creds=None, log=print, merge_existing=False):
                                                 part.get("size", c.get("size", creds.get("size", "1024x1024"))),
                                                 True)
                         part_images[part_id] = pimg
-                spine_builder.build_parts_character(cid, part_images, chars_out, anims, parts)
+                spine_builder.build_parts_character(cid, part_images, chars_out, anims, parts,
+                                                    write_preview=preview_img is None)
+                if preview_img is not None:
+                    os.makedirs(chars_out, exist_ok=True)
+                    spine_builder.trim_to_content(preview_img, pad=16).save(
+                        os.path.join(chars_out, f"{cid}_preview.png")
+                    )
                 w, h = 1000, 1000
                 files = [f"characters/{cid}.json", f"characters/{cid}.atlas", f"characters/{cid}.png",
                          f"characters/{cid}_preview.png"]

+ 30 - 5
spine_builder.py

@@ -348,7 +348,7 @@ def build_parts_skeleton_json(char_id, parts, atlas_w, atlas_h, animations):
     }
 
 
-def build_parts_character(char_id, part_images, out_dir, animations, layout_parts):
+def build_parts_character(char_id, part_images, out_dir, animations, layout_parts, write_preview=True):
     """多部件 Boss:part_images {part_id: PIL RGBA} -> 单 atlas + 多骨骼 JSON。"""
     os.makedirs(out_dir, exist_ok=True)
     packed = []
@@ -376,7 +376,8 @@ def build_parts_character(char_id, part_images, out_dir, animations, layout_part
     skel = build_parts_skeleton_json(char_id, packed, atlas_w, atlas_h, animations)
     with open(os.path.join(out_dir, f"{char_id}.json"), "w", encoding="utf-8") as f:
         json.dump(skel, f, ensure_ascii=False, indent=2)
-    write_parts_preview(char_id, packed, out_dir)
+    if write_preview:
+        write_parts_preview(char_id, packed, out_dir)
     return png_path
 
 
@@ -384,6 +385,23 @@ def write_parts_preview(char_id, packed, out_dir):
     """Write a human-readable assembled preview for multi-part characters."""
     canvas = Image.new("RGBA", (1000, 1000), (0, 0, 0, 0))
     by_id = {p["id"]: p for p in packed}
+    world_cache = {}
+
+    def world_pos(pid):
+        if pid in world_cache:
+            return world_cache[pid]
+        p = by_id.get(pid)
+        if not p:
+            return 0, 0
+        px, py = p.get("x", 0), p.get("y", 0)
+        parent = p.get("parent")
+        if parent and parent != "root":
+            ox, oy = world_pos(parent)
+            px += ox
+            py += oy
+        world_cache[pid] = (px, py)
+        return px, py
+
     order = [
         "cape", "defeated_hero_shadow", "left_leg", "right_leg", "torso",
         "left_arm", "right_arm", "greatsword", "coin_bag", "head",
@@ -394,8 +412,9 @@ def write_parts_preview(char_id, packed, out_dir):
         if not p:
             continue
         img = p["img"]
-        x = int(500 + p.get("x", 0) - img.width / 2)
-        y = int(700 - p.get("y", 0) - img.height / 2)
+        wx, wy = world_pos(pid)
+        x = int(500 + wx - img.width / 2)
+        y = int(760 - wy - img.height / 2)
         canvas.paste(img, (x, y), img)
     preview = trim_to_content(canvas, pad=16)
     preview.save(os.path.join(out_dir, f"{char_id}_preview.png"))
@@ -444,7 +463,13 @@ def write_parts_preview_from_files(char_id, out_dir):
         if not r:
             continue
         img = atlas.crop((r["x"], r["y"], r["x"] + r["w"], r["y"] + r["h"]))
-        packed.append({"id": name, "img": img, "x": b.get("x", 0), "y": b.get("y", 0)})
+        packed.append({
+            "id": name,
+            "img": img,
+            "x": b.get("x", 0),
+            "y": b.get("y", 0),
+            "parent": b.get("parent", "root"),
+        })
     if not packed:
         return ""
     write_parts_preview(char_id, packed, out_dir)