BurningTimesAi/공유/기획/FirstRising/아이템/comfy_moba.py

127 lines
6.2 KiB
Python

# -*- coding: utf-8 -*-
# 2026-10-01 PD 지시 — 체크포인트 mobaGameEquipment_v10 으로 civitai 88908 "Moba game equipment icon" 풍의 아이콘을 만든다. 배경은 투명.
# 페이지 예시 그림의 설정(직접 확인): 프롬프트 "(masterpiece:1.2), (best quality:1.2), moba game icon, …" · 부정어 "lowres, bad anatomy, bad hands, text, error, …" ·
# DPM++ 2M Karras · 30 steps · CFG 7 · clip skip 2 · 권장 해상도 512 / 768. 트리거 = moba game icon.
# python comfy_moba.py <출력폴더> <round 이름> [크기=768] [시드 수=2] [시작] [끝] [rembg=1]
import io, json, os, sys, time, uuid, urllib.request, urllib.parse
from PIL import Image, ImageDraw
HOST = "http://127.0.0.1:8000"
CKPT = "mobaGameEquipment_v10.safetensors"
PREFIX = "(masterpiece:1.2), (best quality:1.2), moba game icon"
# 2차: 1차(768)에서 지팡이 · 목걸이 · 부츠 · 벨트가 인물로 나와 인물 부정어를 더했다
NEG = "lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry, 1girl, 1boy, person, human, character, face, hair, breasts, body, wearing"
# FR 장비 전 부위(무기 6 · 방어구 4 · 장신구 2 · 보조 장비 6)
SUBJECTS = [
("sword", "sword"),
("blunt", "war hammer, mace"),
("bow", "bow"),
("dagger", "dagger"),
("staff", "magic staff"),
("magic_dagger", "magic dagger, glowing blade"),
("armor", "armor, breastplate"),
("belt", "belt"),
("gloves", "gauntlets, gloves"),
("boots", "boots"),
("ring", "ring"),
("necklace", "necklace, pendant"),
("shield", "shield"),
("quiver", "quiver with arrows"),
("spellbook", "spellbook, magic book"),
("throwing", "shuriken, throwing blade"),
("orb", "magic orb, crystal ball"),
("offhand_sword", "short sword"),
]
def post(path, data):
req = urllib.request.Request(HOST + path, data=json.dumps(data).encode("utf-8"), headers={"Content-Type": "application/json"})
return json.loads(urllib.request.urlopen(req, timeout=60).read())
def get(path):
return urllib.request.urlopen(HOST + path, timeout=300).read()
def workflow(prompt, seed, size, rembg):
wf = {
"1": {"class_type": "CheckpointLoaderSimple", "inputs": {"ckpt_name": CKPT}},
"3": {"class_type": "CLIPSetLastLayer", "inputs": {"clip": ["1", 1], "stop_at_clip_layer": -2}},
"4": {"class_type": "CLIPTextEncode", "inputs": {"clip": ["3", 0], "text": prompt}},
"5": {"class_type": "CLIPTextEncode", "inputs": {"clip": ["3", 0], "text": NEG}},
"6": {"class_type": "EmptyLatentImage", "inputs": {"width": size, "height": size, "batch_size": 1}},
"7": {"class_type": "KSampler", "inputs": {"model": ["1", 0], "positive": ["4", 0], "negative": ["5", 0], "latent_image": ["6", 0], "seed": seed, "steps": 30, "cfg": 7.0, "sampler_name": "dpmpp_2m", "scheduler": "karras", "denoise": 1.0}},
"8": {"class_type": "VAEDecode", "inputs": {"samples": ["7", 0], "vae": ["1", 2]}},
}
if rembg:
wf["12"] = {"class_type": "InspyrenetRembg", "inputs": {"image": ["8", 0], "torchscript_jit": "default"}}
wf["9"] = {"class_type": "SaveImage", "inputs": {"images": ["12", 0], "filename_prefix": "moba"}}
else:
wf["9"] = {"class_type": "SaveImage", "inputs": {"images": ["8", 0], "filename_prefix": "moba"}}
return wf
def run(prompt, seed, size, rembg):
pid = post("/prompt", {"prompt": workflow(prompt, seed, size, rembg), "client_id": str(uuid.uuid4())})["prompt_id"]
t0 = time.time()
while time.time() - t0 < 900:
hist = json.loads(get("/history/" + pid))
if pid in hist:
st = hist[pid].get("status", {})
if st.get("status_str") == "error":
raise RuntimeError(json.dumps(st)[:900])
for node in hist[pid].get("outputs", {}).values():
for im in node.get("images", []):
q = urllib.parse.urlencode({"filename": im["filename"], "subfolder": im.get("subfolder", ""), "type": im.get("type", "output")})
return Image.open(io.BytesIO(get("/view?" + q))).convert("RGBA")
time.sleep(1.2)
raise TimeoutError("comfy timeout")
def checker(size, n=16):
im = Image.new("RGB", (size, size), (58, 58, 58))
d = ImageDraw.Draw(im)
for y in range(0, size, n):
for x in range(0, size, n):
if (x // n + y // n) % 2:
d.rectangle([x, y, x + n - 1, y + n - 1], fill=(74, 74, 74))
return im
def main():
out, tag = sys.argv[1], sys.argv[2]
size = int(sys.argv[3]) if len(sys.argv) > 3 else 768
seeds = int(sys.argv[4]) if len(sys.argv) > 4 else 2
lo = int(sys.argv[5]) if len(sys.argv) > 5 else 0
hi = int(sys.argv[6]) if len(sys.argv) > 6 else len(SUBJECTS)
rembg = int(sys.argv[7]) if len(sys.argv) > 7 else 1
os.makedirs(out, exist_ok=True)
subs = SUBJECTS[lo:hi]
cell, pad, label, cols = 200, 4, 16, 9
rows = (len(subs) + cols - 1) // cols
sheet = Image.new("RGB", (cols * (cell + pad) + pad, rows * (seeds * (cell + pad) + label) + pad), (40, 40, 40))
draw = ImageDraw.Draw(sheet)
for i, (name, body) in enumerate(subs):
# 2차: 대상이 장식에 묻혀 가중치를 주고, 물건 하나만 가운데 오게 적는다
prompt = "%s, (%s:1.35), no humans, single item, centered, still life" % (PREFIX, body)
cx = pad + (i % cols) * (cell + pad)
cy = pad + (i // cols) * (seeds * (cell + pad) + label)
draw.text((cx + 3, cy + 2), name, fill=(240, 220, 160))
for j in range(seeds):
t0 = time.time()
im = run(prompt, 9000 + (lo + i) * 23 + j * 401, size, rembg)
print("%s %s #%d %.1f초" % (tag, name, j, time.time() - t0)); sys.stdout.flush()
im.save(os.path.join(out, "%s_%s_%d.png" % (tag, name, j)))
bg = checker(cell)
small = im.resize((cell, cell), Image.LANCZOS)
bg.paste(small, (0, 0), small)
sheet.paste(bg, (cx, cy + label + j * (cell + pad)))
path = os.path.join(out, "%s_sheet.png" % tag)
sheet.save(path)
print("sheet", path)
if __name__ == "__main__":
main()