452 lines
39 KiB
Python
452 lines
39 KiB
Python
|
|
# -*- coding: utf-8 -*-
|
|||
|
|
# 2026-10-02 PD 지시 — "내가 원하는 아트풍은 첨부한 아트풍(Game Icon Research Institute LoRA-ANFG_V3 예시)인데, 우리 프로젝트의 원본 에셋이 너무 실사풍이라…"
|
|||
|
|
# → 원본 에셋을 따라 칠하는 방식(comfy_moba_repaint*.py)은 실사풍을 그대로 끌고 오므로 방향이 틀렸다.
|
|||
|
|
# 여기서는 원본 에셋을 쓰지 않고, 양식화된 게임 아이콘 기반 모델(mobaGameEquipment_v10) 위에 ANFG v3 LoRA 를 얹어 글자만으로 뽑는다.
|
|||
|
|
# (10-01 시도는 기반이 실사풍 difconsistency 였다 — 얇고 사실적으로 나와 PD 가 반려. 이 조합은 처음이다.)
|
|||
|
|
# python comfy_moba_anfg.py <출력폴더> <round 이름> [LoRA 강도들 "0.7,1.0"] [시드 수=2] [크기=512] [다듬기 0|1]
|
|||
|
|
import io, json, os, sys, time, uuid, urllib.parse
|
|||
|
|
from PIL import Image, ImageDraw
|
|||
|
|
import comfy_moba_repaint as base # 통신 · 바둑판 바탕은 앞선 스크립트의 것을 쓴다
|
|||
|
|
|
|||
|
|
CKPT = "mobaGameEquipment_v10.safetensors"
|
|||
|
|
LORA = "giI_anfg_v3_000018.safetensors"
|
|||
|
|
TRIGGER = "game icon institute, game icon, ahfg" # PD 가 알려 준 트리거
|
|||
|
|
NEG = ("(worst quality, low quality:1.3), lowres, text, watermark, signature, blurry, photo, realistic, photorealistic, 3d render, thin, "
|
|||
|
|
"1girl, 1boy, person, human, face, body, multiple objects, duplicate, frame, border, circle background")
|
|||
|
|
UPSCALER = "4x-UltraSharp.pth"
|
|||
|
|
# 2026-10-02 PD 지적 — a2 선별본(final_sheet)이 "너무 캐주얼한 느낌". 예시는 어두운 금속 · 정교한 장식 · 깊은 명암의 MMORPG 풍인데
|
|||
|
|
# a2 는 묘사가 단순하고(금 반지 + 붉은 보석) 다듬기(0.35)가 면을 매끈하게 밀어 만화풍이 됐다.
|
|||
|
|
# 분위기 사전 설정(환경변수 ANFG_TONE): 프롬프트 뒤에 붙는 낱말 · 부정어에 더하는 낱말. 다듬기 세기는 ANFG_REFINE(기본 0.35).
|
|||
|
|
TONES = {
|
|||
|
|
"": ("", ""),
|
|||
|
|
"epic": (", epic fantasy mmorpg equipment, ornate, intricate engraved details, dark metal, painterly shading, dramatic lighting, deep shadows, glowing accents",
|
|||
|
|
", cartoon, casual, cute, chibi, toy, flat color, simple design, cel shading, thick outline"),
|
|||
|
|
# a4(epic) 검토: 명암 · 질감은 목표에 가까워졌으나 형태가 녹고(반지 → 가면, 벨트 → 칼날) 빛 번짐 · 잘림이 늘었다. LoRA 1.0 은 형태가 더 녹는다.
|
|||
|
|
# refined: PD 가 고른 것의 공통점(실제 장비 비례 · 얇은 외곽선 · 면 안의 명암 단계 · 금 + 한 색)을 낱말로 옮기고, 발광 · 원색 · 굵은 외곽선을 부정어로 누른다.
|
|||
|
|
"refined": (", realistic proportions, clean sharp edges, crisp silhouette, polished metal, subtle painterly shading, thin outline, soft studio light",
|
|||
|
|
", cartoon, casual, cute, chibi, toy, flat color, thick black outline, neon colors, oversized gem, glow, bloom, smoke, flame, motion blur, cropped, cut off"),
|
|||
|
|
# a9 검토: 형태는 길잡이로 풀렸고 남은 걸림돌은 "단순함"(도형에 색만 입힌 수준 · 명암 단계가 적어 플라스틱 느낌 · 정면 평면 구도).
|
|||
|
|
# rich: 장식 밀도와 명암을 올린다. 길잡이 ControlNet 은 일찍 풀어(끝 0.35) 모델이 장식을 덧붙일 여지를 준다.
|
|||
|
|
# a10 검토: 단검 · 방패는 장식 · 명암이 PD 합격본 수준에 근접. 다만 ControlNet 끝 0.35 는 24장 중 약 10장이 길잡이를 벗어났고(활 → 덩어리, 반지 → 메달),
|
|||
|
|
# "embedded gemstones" 는 망치 머리 · 반지 안을 통보석으로 만들었고, "three-quarter view" 는 효과가 약했다 → 두 낱말을 빼고 끝을 0.5 로(가는 종류는 0.6). ANFG 는 0.7 고정(0.9 는 형태가 녹는다).
|
|||
|
|
"rich": (", ornate fantasy design, engraved filigree, layered metal plates, crisp silhouette, clean sharp edges, "
|
|||
|
|
"detailed painterly shading, painterly brush strokes, strong highlights and deep shadows, dark ambient occlusion",
|
|||
|
|
", cartoon, casual, cute, chibi, toy, plastic, glossy plastic, plain, simple design, flat color, flat front view, thick black outline, neon colors, glow, bloom, smoke, flame, motion blur, cropped, cut off, "
|
|||
|
|
"round medallion, coin, disc, close-up"),
|
|||
|
|
# a14 검토: 배색 · 외곽선 · 밝기는 PD 합격본과 한 세트. 남은 차이 = 손그림 붓결과 깊은 그림자(깨끗한 그라데이션 + 흰 하이라이트 띠가 지배적).
|
|||
|
|
# paint: 붓결 · 무광 · 어두운 바탕색 쪽 낱말로 바꾸고, 매끈함 · 광택을 부정어로 누른다(다듬기 방식 비교와 함께 a16 에서 시험).
|
|||
|
|
"paint": (", ornate fantasy design, engraved filigree, layered metal plates, crisp silhouette, hand painted texture, visible painterly brush strokes, "
|
|||
|
|
"matte dark metal, deep shadows, dark ambient occlusion, rich muted colors, strong rim light, specular highlights, high contrast",
|
|||
|
|
", cartoon, casual, cute, chibi, toy, plastic, glossy plastic, plain, simple design, flat color, flat front view, thick black outline, neon colors, glow, bloom, smoke, flame, motion blur, cropped, cut off, "
|
|||
|
|
"round medallion, coin, disc, close-up, smooth gradient, airbrushed, shiny, white highlight band, 3d render, purple, magenta, pink"),
|
|||
|
|
}
|
|||
|
|
# 구도 낱말(맨 앞) — 분위기 설정이 있을 때만 붙인다(a1 · a2 재현을 깨지 않게)
|
|||
|
|
COMPOSE = "single item, centered, full object in frame, wide margin, "
|
|||
|
|
TONE = os.environ.get("ANFG_TONE", "")
|
|||
|
|
REFINE = float(os.environ.get("ANFG_REFINE", "0.35"))
|
|||
|
|
# 자체 학습 LoRA(comfy_anfg_train.py 가 만든 FR_anfg_<버전>)를 ANFG LoRA 위에 함께 건다 — 환경변수 ANFG_LORA2 = "파일이름:강도". 걸면 트리거 fricon 을 앞에 붙인다.
|
|||
|
|
LORA2 = os.environ.get("ANFG_LORA2", "")
|
|||
|
|
# 1024 로 키우는 방식: model = 4x-UltraSharp(선명하지만 면을 매끈하게 민다) · lanczos = 단순 확대 뒤 다듬기에서 모델이 다시 칠하게 둔다(붓결을 살리려는 시험)
|
|||
|
|
UPSCALE = os.environ.get("ANFG_UPSCALE", "model")
|
|||
|
|
|
|||
|
|
# PD 가 보여 준 예시와 같은 종류(반지 · 투구 · 장갑 · 갑옷 · 목걸이) + FR 부위(검 · 부츠 · 벨트)
|
|||
|
|
SUBJECTS = [
|
|||
|
|
("ring", "a blue dragon head ring, silver scales, glowing cyan"),
|
|||
|
|
("helmet", "a red and gold horned helmet"),
|
|||
|
|
("gloves", "a single armored gauntlet, dark steel with gold trim and purple gem"),
|
|||
|
|
("armor", "a red and gold chest armor with shoulder plates"),
|
|||
|
|
("necklace", "a red gem pendant necklace with gold wings"),
|
|||
|
|
("sword", "a broad sword, blue glowing blade, gold hilt"),
|
|||
|
|
("boots", "a pair of armored boots, blue and gold"),
|
|||
|
|
("belt", "a leather belt with large gold buckle and red gem"),
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
|
|||
|
|
# FR 장비 전 부위(무기 6 · 방어구 4 · 장신구 2 · 보조 장비 8) — a1 에서 형태가 약했던 벨트 · 검 · 장갑은 낱말을 구체적으로 바꿨다
|
|||
|
|
SUBJECTS_FR = [
|
|||
|
|
("sword", "a single longsword, steel blade, gold crossguard, blue gem, full weapon in frame"),
|
|||
|
|
("blunt", "a single war hammer, heavy steel head with gold trim, full weapon in frame"),
|
|||
|
|
("bow", "a single recurve bow, wooden with gold tips and green gem, full weapon in frame"),
|
|||
|
|
("dagger", "a single dagger, curved steel blade, red gem pommel, full weapon in frame"),
|
|||
|
|
("staff", "a single magic staff, gold top holding a purple crystal, full weapon in frame"),
|
|||
|
|
("magic_dagger", "a single magic dagger, glowing green crystal blade, gold hilt, full weapon in frame"),
|
|||
|
|
("armor", "a red and gold chest armor with shoulder plates"),
|
|||
|
|
("belt", "a waist belt, wide brown leather strap loop, large round gold buckle with red gem"),
|
|||
|
|
("gloves", "a pair of armored gloves, steel fingers, gold trim, purple gem"),
|
|||
|
|
("boots", "a pair of armored boots, blue and gold"),
|
|||
|
|
("ring", "a gold ring with a large red gemstone"),
|
|||
|
|
("necklace", "a blue gem pendant necklace with gold chain"),
|
|||
|
|
("shield", "a kite shield, blue steel with gold rim and lion emblem"),
|
|||
|
|
("quiver", "a leather quiver full of arrows, gold trim"),
|
|||
|
|
("spellbook", "a closed thick spellbook, purple cover, gold corners, glowing gem"),
|
|||
|
|
("throwing", "a four pointed steel shuriken with gold center"),
|
|||
|
|
("orb", "a glowing cyan crystal orb held in a gold claw stand"),
|
|||
|
|
("offhand_sword", "a single short sword, wide blade, gold hilt, full weapon in frame"),
|
|||
|
|
("totem", "a carved wooden totem with glowing green runes and feathers"),
|
|||
|
|
("horn", "a gold trimmed war horn"),
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
|
|||
|
|
# a2 검토(직접 봄): 풍은 일정하나 긴 무기(둔기 · 활 · 지팡이 · 화살통)가 전부 검 모양으로 쏠리고, 장식 테두리 · 화면 밖 잘림이 섞였다.
|
|||
|
|
# a3: FR 원본 아이콘에서 "윤곽선만" 빌려 형태를 잡는다(색 · 질감은 가져오지 않으므로 실사풍이 따라오지 않는다). (이름, 묘사, 윤곽을 빌릴 원본 번호)
|
|||
|
|
SUBJECTS_SHAPE = [
|
|||
|
|
("blunt", "a war hammer, heavy steel head with gold trim and blue gem, brown handle", 1015),
|
|||
|
|
("mace", "a flanged mace, gold and steel head with red gem, dark handle", 1057),
|
|||
|
|
("bow", "a recurve bow, green and gold limbs, thin bowstring", 1029),
|
|||
|
|
("dagger", "a dagger, steel blade, gold guard, red gem", 1043),
|
|||
|
|
("sword", "a short sword, blue steel blade, gold crossguard", 1435),
|
|||
|
|
("belt1", "a leather belt, brown strap, gold buckle with red gem", 1337),
|
|||
|
|
("belt2", "a leather belt, dark strap, gold skull buckle, cyan gems", 1351),
|
|||
|
|
("belt3", "a leather belt with gold plates and hanging chains, cyan gems", 1309),
|
|||
|
|
("gloves1", "a pair of armored gloves, purple leather, gold trim, glowing runes", 1169),
|
|||
|
|
("gloves2", "a pair of steel gauntlets, gold trim, red gems", 1211),
|
|||
|
|
("boots1", "a pair of armored boots, blue and gold", 1393),
|
|||
|
|
("boots2", "a pair of high plate boots, red and gold", 1421),
|
|||
|
|
("armor1", "a chest armor, red and gold plates, shoulder pauldrons", 1253),
|
|||
|
|
("armor2", "a chest armor, blue steel and gold, fur collar, cyan gem", 1239),
|
|||
|
|
("necklace1", "a round amulet, gold rim, glowing cyan gem", 1099),
|
|||
|
|
("necklace2", "a shield shaped pendant, silver and gold, blue gem", 1113),
|
|||
|
|
]
|
|||
|
|
# a5 검토(에이전트가 직접 봄): 글자만으로는 검 · 단검 · 갑옷 · 장화만 안정. 둔기 · 활 · 지팡이 · 화살통 · 뿔피리 등은 칼날 모양으로 쏠리고, 벨트 · 투척 · 방패는 원형 바탕이 붙는다.
|
|||
|
|
# SUBJECTS_ALL: (이름, 묘사, 윤곽 원본 번호 또는 None, 종류별 부정어). 묘사 · 부정어는 a5 검토의 수정안을 반영했다.
|
|||
|
|
SUBJECTS_ALL = [
|
|||
|
|
("sword", "a single longsword, steel blade, gold crossguard, blue gem, full weapon in frame", None, "two blades"),
|
|||
|
|
("blunt", "a one-handed flanged mace, heavy spiked steel head with gold trim on a short handle", 1057, "sword, blade"),
|
|||
|
|
("hammer", "a war hammer, heavy block steel head with gold trim and blue gem, brown wooden handle", 1015, "sword, blade"),
|
|||
|
|
("bow", "a recurve bow, curved wooden limbs with gold tips, thin taut bowstring, side view", 1029, "sword, blade, shield, arrow, harp, panel, banner, sail"),
|
|||
|
|
("dagger", "a single dagger, curved steel blade, red gem pommel, full weapon in frame", None, "two blades"),
|
|||
|
|
("staff", "a long wooden wizard staff, full length visible, purple crystal on top, diagonal", None, "sword, blade, scepter"),
|
|||
|
|
("magic_dagger", "a single magic dagger, green crystal blade, gold hilt, full weapon in frame", None, "two blades, floating orbs"),
|
|||
|
|
("armor", "a red and gold chest armor with shoulder plates", None, ""),
|
|||
|
|
("belt", "a leather waist belt laid flat as a horizontal strap, large gold buckle in the center, red leather", 1337, "ring, circle, pants, torso, round frame"),
|
|||
|
|
("gloves", "a single armored gauntlet, five fingers, steel plate glove with gold trim, wrist cuff", None, "boot, bare hand"),
|
|||
|
|
("boots", "a pair of armored boots, blue and gold", None, ""),
|
|||
|
|
("ring", "a gold ring with a large red gemstone, closed circular finger ring band", None, "ribbon, broken ring"),
|
|||
|
|
("necklace", "a pendant necklace, short closed chain loop fully visible, blue gem pendant with gold setting", None, "bottle, wings, cropped chain"),
|
|||
|
|
("shield", "a kite shield, plain blue field with gold rim and central boss, three-quarter view, small in frame", None, "lion, animal, face, crest, round frame"),
|
|||
|
|
("quiver", "a leather quiver full of arrows, feathered arrow shafts sticking out of the top, shoulder strap", None, "sword, blade"),
|
|||
|
|
("spellbook", "a closed thick spellbook, purple cover, gold corner clasps, gem on the front cover, three-quarter view", None, "open book, scroll"),
|
|||
|
|
("throwing", "a single steel throwing knife, kunai, small leaf shaped blade with ring pommel", None, "shuriken, star, disc, wheel, round frame"),
|
|||
|
|
("orb", "a crystal ball, deep blue glass sphere on a small gold stand", None, "cyan, teal, horns"),
|
|||
|
|
("offhand_sword", "a short parrying dagger, main-gauche, wide curved gold handguard, whole weapon in frame", None, "two blades"),
|
|||
|
|
("totem", "a small carved wooden idol statue, tribal face, gold bands, red gem eyes", None, "green glow, neon, pillar, column, round frame"),
|
|||
|
|
("horn", "a curved ivory war horn, blowing horn, gold rim bands, leather strap", None, "axe, blade, trumpet, all gold"),
|
|||
|
|
]
|
|||
|
|
# 초안 방식(draft): 물건을 정확히 아는 실사풍 모델로 먼저 "형태 초안"을 그리고, 그 초안의 윤곽선만 ControlNet 으로 넘겨 ANFG 풍으로 새로 그린다.
|
|||
|
|
# 색 · 질감은 넘어가지 않으므로 실사풍이 따라오지 않고, 글자만으로는 못 잡던 형태(활 · 화살통 · 뿔피리 등)를 잡는다 — 어떤 장비든 글자로 뽑기 위한 방법.
|
|||
|
|
DRAFT_CKPT = "difconsistencyCheckpoint_v10.safetensors"
|
|||
|
|
DRAFT_NEG = "person, human, hand, character, multiple objects, text, watermark, frame, border, cropped, cut off, blurry, lowres"
|
|||
|
|
|
|||
|
|
|
|||
|
|
def add_draft(wf, desc, seed, cn_strength, cn_end):
|
|||
|
|
"""형태 초안(d*) → 굵은 선화 → ControlNet 을 글자 조건(4 · 5)에 걸어 샘플러(7)에 물린다."""
|
|||
|
|
wf["d1"] = {"class_type": "CheckpointLoaderSimple", "inputs": {"ckpt_name": DRAFT_CKPT}}
|
|||
|
|
wf["d4"] = {"class_type": "CLIPTextEncode", "inputs": {"clip": ["d1", 1], "text": "%s, single object, centered, whole object visible, wide margin, game item concept art, plain dark background" % desc}}
|
|||
|
|
wf["d5"] = {"class_type": "CLIPTextEncode", "inputs": {"clip": ["d1", 1], "text": DRAFT_NEG}}
|
|||
|
|
wf["d6"] = {"class_type": "EmptyLatentImage", "inputs": {"width": 512, "height": 512, "batch_size": 1}}
|
|||
|
|
wf["d7"] = {"class_type": "KSampler", "inputs": {"model": ["d1", 0], "positive": ["d4", 0], "negative": ["d5", 0], "latent_image": ["d6", 0], "seed": seed + 7,
|
|||
|
|
"steps": 24, "cfg": 7.0, "sampler_name": "dpmpp_2m", "scheduler": "karras", "denoise": 1.0}}
|
|||
|
|
wf["d8"] = {"class_type": "VAEDecode", "inputs": {"samples": ["d7", 0], "vae": ["d1", 2]}}
|
|||
|
|
wf["d9"] = {"class_type": "SaveImage", "inputs": {"images": ["d8", 0], "filename_prefix": "moba_anfg_draft"}}
|
|||
|
|
wf["30"] = {"class_type": "ControlNetLoader", "inputs": {"control_net_name": CN_LINEART}}
|
|||
|
|
wf["31"] = {"class_type": "LineArtPreprocessor", "inputs": {"image": ["d8", 0], "coarse": "enable", "resolution": 512}}
|
|||
|
|
wf["32"] = {"class_type": "ControlNetApplyAdvanced", "inputs": {"positive": ["4", 0], "negative": ["5", 0], "control_net": ["30", 0], "image": ["31", 0],
|
|||
|
|
"strength": cn_strength, "start_percent": 0.0, "end_percent": cn_end, "vae": ["1", 2]}}
|
|||
|
|
wf["7"]["inputs"]["positive"] = ["32", 0]; wf["7"]["inputs"]["negative"] = ["32", 1]
|
|||
|
|
return wf
|
|||
|
|
|
|||
|
|
|
|||
|
|
# 길잡이 모음 방식(lib): icon_shapes.py 가 만든 moba_anfg/shapes/<종류>__<출처>.png 의 윤곽선으로 형태를 잡는다(a6 검토에서 가장 정확했던 방식).
|
|||
|
|
# 종류별 묘사의 {pal} 자리에 색 조합을 돌려 넣어, 같은 형태라도 다른 디자인이 나오게 한다(학습 데이터가 한 색으로 쏠리지 않게).
|
|||
|
|
TYPE_DESC = {
|
|||
|
|
"sword": "a longsword, ornate engraved steel blade with a fuller, dark steel shading, ornate crossguard, {pal}",
|
|||
|
|
"dagger": "a dagger, engraved steel blade with a fuller, dark steel shading, ornate hilt and crossguard, {pal}",
|
|||
|
|
"blunt": "a flanged mace, heavy spiked metal head on a short handle, {pal}",
|
|||
|
|
"hammer": "a war hammer, heavy block metal head with ornate banded ends and an engraved emblem, wrapped handle, {pal}",
|
|||
|
|
"axe": "a single-bladed battle axe, solid broad axe head attached to the shaft, engraved steel blade, dark steel shading, wooden shaft with metal fittings, {pal}",
|
|||
|
|
"bow": "an ornate recurve bow, carved limbs with gold filigree fittings, gem at the wrapped grip, thin bowstring, {pal}",
|
|||
|
|
"staff": "a wizard staff, long shaft, glowing crystal head ornament, {pal}",
|
|||
|
|
"armor": "a chest armor, layered plates, shoulder pauldrons, {pal}",
|
|||
|
|
"helmet": "a knight helmet, visor, crest, {pal}",
|
|||
|
|
"belt": "a waist belt, leather strap, large ornate buckle, {pal}",
|
|||
|
|
"gloves": "a pair of armored gauntlets, plated fingers, wrist cuffs, {pal}",
|
|||
|
|
"boots": "a pair of armored boots, plated shins, {pal}",
|
|||
|
|
"ring": "a finger ring, large faceted gemstone, engraved band, {pal}",
|
|||
|
|
"necklace": "a pendant amulet, faceted gem in an ornate setting, {pal}",
|
|||
|
|
"shield": "a shield, metal rim, central emblem boss, {pal}",
|
|||
|
|
"quiver": "a leather quiver full of arrows, feathered arrows sticking out, shoulder strap, {pal}",
|
|||
|
|
"spellbook": "a closed spellbook, thick tome, metal corner clasps, gem on the cover, {pal}",
|
|||
|
|
"throwing": "a throwing blade, kunai, sharp steel, {pal}",
|
|||
|
|
"orb": "a crystal orb, glass sphere on an ornate stand, {pal}",
|
|||
|
|
"totem": "a carved wooden totem, stacked tribal faces, small wings, {pal}",
|
|||
|
|
"horn": "a curved war horn, ivory body, metal rim bands, {pal}",
|
|||
|
|
}
|
|||
|
|
# PD 가 고른 것 = 금 + 적 또는 금 + 청 + 은빛 금속. 그 둘을 앞에 두고 나머지로 폭을 넓힌다.
|
|||
|
|
# a7 검토: 여섯 색을 돌리니 청록 · 보라 · 초록 단색 칠이 40% 쯤 나와 불합격이 늘었다 → 금을 늘 넣고 한 색만 더한다. 은 단색은 세트에서 차갑게 떠서 뺐다.
|
|||
|
|
PALETTES = ["gold and crimson red with silver steel", "gold and royal blue with silver steel", "gold trim on dark steel with red gems", "gold trim on silver steel with blue gems"]
|
|||
|
|
# a21 뒤 최상급 점검(검토): 세트로는 묶이지만 목표 풍과 다른 점 = 원색 파랑이 전면(30장 중 약 20장) · 목표는 어둡고 채도 낮은 바탕에 포인트만 빛난다.
|
|||
|
|
# ANFG_PAL=dark 이면 어두운 바탕색 + 작은 강조색 조합을 쓴다(PD 에게 방향 비교로 보여 줄 시험용 — PD 가 고른 합격본은 금 + 적/청의 밝은 쪽이다).
|
|||
|
|
if os.environ.get("ANFG_PAL", "") == "dark":
|
|||
|
|
PALETTES = ["dark steel with antique gold trim and a small glowing blue gem accent", "dark iron and antique gold with deep crimson accents",
|
|||
|
|
"blackened steel with bronze trim and a small glowing red gem accent", "dark silver and antique gold with deep blue enamel accents"]
|
|||
|
|
# 2026-10-03 PD 지적 — "붉은색 · 푸른색 같은 원색 계열만 쓰고 있어서 색감이 풍부하지 않고 저급해 보인다" + 레퍼런스(핀터레스트 → ArtStation "RPG Weapon Icons 02", Olekzandr Zahorulko).
|
|||
|
|
# 레퍼런스에서 본 것: 색이 "칠"이 아니라 "재질"에서 나온다(강철 회색 · 나무 갈색 · 가죽 · 뼈 · 청동 · 녹슨 쇠). 원색은 물건 전체가 아니라 작은 강조(감은 끈 · 보석 · 마법 빛)에만,
|
|||
|
|
# 그 강조색이 물건마다 다르다(청록 · 보라 · 초록 · 주황 · 분홍 · 하늘). 어두운 바탕 위에서 명암 폭이 넓다.
|
|||
|
|
# 우리 문제: 색 조합이 "금 + 적 / 금 + 청" 넷뿐이고 물건 전체를 그 색 에나멜로 칠한다 · 자체 LoRA v4 가 그 배색으로 학습돼 더 끌어당긴다.
|
|||
|
|
# ANFG_PAL=material: 재질 조합(몸체) × 강조색(작은 부분)을 따로 돌린다 → 조합 수가 많아 물건마다 색이 달라진다.
|
|||
|
|
MATERIALS = ["weathered steel and dark walnut wood", "dark iron and worn brown leather", "aged bronze and bone ivory", "blackened steel and antique brass",
|
|||
|
|
"pale silver and deep burgundy leather", "tarnished copper and dark oak wood", "gunmetal grey steel and tan leather wraps", "antique gold and dark slate grey steel"]
|
|||
|
|
ACCENTS = ["a small emerald green gem accent", "a small amethyst purple gem accent", "a small teal cyan glowing accent", "a small amber orange gem accent",
|
|||
|
|
"a small ruby red gem accent", "a small sapphire blue gem accent", "a small jade green inlay accent", "a small rose pink crystal accent",
|
|||
|
|
"a small turquoise inlay accent", "a small pale moonstone white gem accent"]
|
|||
|
|
if os.environ.get("ANFG_PAL", "") == "material":
|
|||
|
|
# a25(PM 이 비교 시트를 직접 봄): 반지 · 벨트 · 방패 · 목걸이 · 망치 · 검은 재질색 + 작은 강조로 잘 나왔으나, 갑옷 · 장화 · 투구는 강조색이 몸체 전체를 덮었다(보라 갑옷 · 초록 투구).
|
|||
|
|
# → 재질에 가중치를 주고 "몸체는 재질색, 강조는 작은 보석 하나"임을 낱말로 못박는다.
|
|||
|
|
PALETTES = ["(body made of %s:1.25), natural material colors, %s only" % (m, ACCENTS[(k * 3 + j) % len(ACCENTS)]) for j in range(5) for k, m in enumerate(MATERIALS)]
|
|||
|
|
# a26 검토(에이전트 + PM 이 최상급 24장 직접 확인): 원색 문제는 약 70% 에서 풀리고 세트가 확실히 풍부해졌다. 남은 문제 —
|
|||
|
|
# ① 재질 낱말이 엉뚱한 부위에 붙는다(나무 칼날 · 나무 투구 · 뼈 벨트) ② 장식 밀도가 줄어 민무늬 면이 늘었다 ③ 갈색 쪽으로 쏠려 차가운 재질이 적다 ④ 일부는 여전히 한 색이 몸체를 덮는다.
|
|||
|
|
# ANFG_PAL=parts: 재질을 "부위"에 묶는다. TYPE_PARTS 의 묘사에 {metal}(주 금속) · {metal2}(장식 금속) · {soft}(가죽/나무/천) · {accent}(작은 강조색) 자리를 두고 따로 돌린다.
|
|||
|
|
METALS = ["weathered steel", "blued steel", "dark iron", "aged bronze", "verdigris bronze", "pale silver", "blackened steel", "antique gold", "tarnished copper", "gunmetal grey steel"]
|
|||
|
|
METALS2 = ["antique brass trim", "silver trim", "antique gold trim", "dark iron rivets and trim", "bronze trim"]
|
|||
|
|
SOFTS = ["dark walnut wood", "worn brown leather", "deep burgundy leather", "tan leather wraps", "dark oak wood", "teal leather wrap", "black leather", "bone ivory"]
|
|||
|
|
ACCENTS2 = ["a small emerald green gem", "a small amethyst purple gem", "a small turquoise inlay", "a small amber orange gem", "a small ruby red gem",
|
|||
|
|
"a small sapphire blue gem", "a small jade green inlay", "a small moonstone white gem", "a small topaz yellow gem", "a small garnet dark red gem"]
|
|||
|
|
ORNATE = "engraved filigree, etched runes, rivets, worn edges, fine scratches"
|
|||
|
|
TYPE_PARTS = {
|
|||
|
|
"sword": "a longsword, {metal} blade with a fuller, {metal2} crossguard, {soft} wrapped grip, {accent} in the pommel",
|
|||
|
|
"dagger": "a dagger, {metal} blade with a fuller, {metal2} crossguard, {soft} wrapped grip, {accent} in the pommel",
|
|||
|
|
"axe": "a single-bladed battle axe, solid broad {metal} axe head attached to the shaft, {soft} shaft with {metal2}, {accent} on the axe head",
|
|||
|
|
"blunt": "a flanged mace, heavy spiked {metal} head, {soft} wrapped handle with {metal2}, {accent} on the head",
|
|||
|
|
"hammer": "a war hammer, heavy block {metal} head with banded ends, {soft} wrapped handle with {metal2}, {accent} on the head",
|
|||
|
|
"bow": "an ornate recurve bow, carved {soft} limbs with {metal} fittings, wrapped grip, thin bowstring, {accent} at the grip",
|
|||
|
|
"staff": "a wizard staff, long {soft} shaft with {metal} fittings, crystal head ornament, {accent} glowing softly",
|
|||
|
|
"armor": "a chest armor, layered {metal} plates with {metal2}, {soft} straps, shoulder pauldrons, {accent} on the chest",
|
|||
|
|
"helmet": "a knight helmet, {metal} plates with {metal2}, visor, crest, {accent} on the brow",
|
|||
|
|
"belt": "a waist belt, {soft} strap, large ornate {metal} buckle with {metal2}, {accent} in the buckle",
|
|||
|
|
"gloves": "a pair of armored gauntlets, {metal} plated fingers with {metal2}, {soft} cuffs, {accent} on the back of the hand",
|
|||
|
|
"boots": "a pair of armored boots, {metal} plated shins with {metal2}, {soft} boot, {accent} on the shin",
|
|||
|
|
"ring": "a finger ring, engraved {metal} band with {metal2}, {accent} in an ornate setting",
|
|||
|
|
"necklace": "a pendant amulet, {metal} ornate setting with {metal2}, {accent} in the center",
|
|||
|
|
"shield": "a shield, {metal} rim with {metal2}, {soft} covered face, central emblem boss, {accent} on the boss",
|
|||
|
|
"quiver": "a quiver full of arrows, {soft} body with {metal} fittings, feathered arrows sticking out, shoulder strap, {accent} on the fitting",
|
|||
|
|
"spellbook": "a closed spellbook, thick tome, {soft} cover, {metal} corner clasps, {accent} on the cover",
|
|||
|
|
"throwing": "a throwing blade, kunai, sharp {metal} blade, {soft} wrapped grip, {accent} on the ring pommel",
|
|||
|
|
"orb": "a crystal orb, glass sphere on an ornate {metal} stand with {metal2}, {accent} glow inside the sphere",
|
|||
|
|
"totem": "a carved totem, {soft} body with stacked tribal faces, {metal} bands, small wings, {accent} eyes",
|
|||
|
|
"horn": "a curved war horn, bone ivory body, {metal} rim bands with {metal2}, {soft} strap, {accent} on the band",
|
|||
|
|
}
|
|||
|
|
PARTS = os.environ.get("ANFG_PAL", "") == "parts"
|
|||
|
|
|
|||
|
|
|
|||
|
|
def parts_desc(kind, n):
|
|||
|
|
"""종류 kind 의 n 번째 색 조합 묘사 — 금속 · 장식 금속 · 부드러운 재질 · 강조색을 서로 다른 걸음으로 돌려 겹치지 않게 한다"""
|
|||
|
|
return TYPE_PARTS[kind].format(metal=METALS[n % len(METALS)], metal2=METALS2[(n // 2) % len(METALS2)],
|
|||
|
|
soft=SOFTS[(n * 3 + 1) % len(SOFTS)], accent=ACCENTS2[(n * 7 + 2) % len(ACCENTS2)]) + ", " + ORNATE
|
|||
|
|
|
|||
|
|
|
|||
|
|
# 색을 누르는 부정어(재질 조합일 때만 붙인다)
|
|||
|
|
MATERIAL_NEG = ", saturated primary colors, blue enamel, red enamel, fully blue, fully red, all gold, monochrome gold, two-tone color scheme, candy colors, fully purple, fully green, fully teal, colored metal plates, painted armor, colored enamel body, red armor, magenta, wooden blade, circular background, glow outline, halo"
|
|||
|
|
SHAPES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "moba_anfg", "shapes")
|
|||
|
|
# icon_make.py 가 적는 종류별 묘사(새 종류 · 묘사 고침)를 표에 합친다
|
|||
|
|
_DESC_JSON = os.path.join(os.path.dirname(os.path.abspath(__file__)), "moba_anfg", "type_desc.json")
|
|||
|
|
if os.path.exists(_DESC_JSON):
|
|||
|
|
TYPE_DESC.update(json.load(io.open(_DESC_JSON, encoding="utf-8")))
|
|||
|
|
|
|||
|
|
|
|||
|
|
def lib_subjects():
|
|||
|
|
"""길잡이 모음의 그림마다 대상 한 줄: (이름 = 종류-출처, 묘사, 길잡이 경로, 부정어, 종류)"""
|
|||
|
|
import glob
|
|||
|
|
neg = dict((x[0], x[3]) for x in SUBJECTS_ALL)
|
|||
|
|
subs = []
|
|||
|
|
act = os.path.join(SHAPES_DIR, "active.txt") # icon_shapes.py 가 적는 "지금 쓰는 길잡이" 목록
|
|||
|
|
names = io.open(act, encoding="utf-8").read().split() if os.path.exists(act) else [os.path.basename(f) for f in sorted(glob.glob(os.path.join(SHAPES_DIR, "*__*.png")))]
|
|||
|
|
for k, path in enumerate(os.path.join(SHAPES_DIR, n) for n in names):
|
|||
|
|
kind, label = os.path.basename(path)[:-4].split("__", 1)
|
|||
|
|
if kind in TYPE_DESC:
|
|||
|
|
subs.append((kind + "-" + label.replace("_", ""), TYPE_DESC[kind], path, neg.get(kind, ""), kind))
|
|||
|
|
return subs
|
|||
|
|
|
|||
|
|
|
|||
|
|
SRC = base.SRC
|
|||
|
|
CN_LINEART = "control_v11p_sd15_lineart_fp16.safetensors"
|
|||
|
|
FRAME_NEG = ", ornate frame, picture frame, card, corner ornaments, vignette, ring shaped background, out of frame"
|
|||
|
|
|
|||
|
|
|
|||
|
|
def shape_image(gid, fit=0.72):
|
|||
|
|
"""원본 아이콘을 1024 캔버스 가운데에 여백을 넉넉히 두고 놓는다(윤곽 추출용 · 여백이 곧 완성본의 여백)"""
|
|||
|
|
im = Image.open(os.path.join(SRC, "%d.png" % gid)).convert("RGBA")
|
|||
|
|
box = im.getchannel("A").getbbox()
|
|||
|
|
if box:
|
|||
|
|
im = im.crop(box)
|
|||
|
|
scale = 1024 * fit / float(max(im.size))
|
|||
|
|
im = im.resize((max(1, int(im.width * scale)), max(1, int(im.height * scale))), Image.LANCZOS)
|
|||
|
|
rgb = Image.new("RGB", (1024, 1024), base.BG)
|
|||
|
|
rgb.paste(im, ((1024 - im.width) // 2, (1024 - im.height) // 2), im)
|
|||
|
|
return rgb
|
|||
|
|
|
|||
|
|
|
|||
|
|
def add_shape(wf, image_name, cn_strength, cn_end):
|
|||
|
|
"""글자 조건(4 · 5)에 윤곽 ControlNet 을 걸어 샘플러(7 · 44)에 물린다. 굵은 선(coarse)만 써서 세부는 모델이 새로 그리게 둔다."""
|
|||
|
|
wf["22"] = {"class_type": "LoadImage", "inputs": {"image": image_name}}
|
|||
|
|
wf["30"] = {"class_type": "ControlNetLoader", "inputs": {"control_net_name": CN_LINEART}}
|
|||
|
|
wf["31"] = {"class_type": "LineArtPreprocessor", "inputs": {"image": ["22", 0], "coarse": "enable", "resolution": 768}}
|
|||
|
|
wf["32"] = {"class_type": "ControlNetApplyAdvanced", "inputs": {"positive": ["4", 0], "negative": ["5", 0], "control_net": ["30", 0], "image": ["31", 0],
|
|||
|
|
"strength": cn_strength, "start_percent": 0.0, "end_percent": cn_end, "vae": ["1", 2]}}
|
|||
|
|
wf["7"]["inputs"]["positive"] = ["32", 0]; wf["7"]["inputs"]["negative"] = ["32", 1]
|
|||
|
|
return wf
|
|||
|
|
|
|||
|
|
|
|||
|
|
def workflow(prompt, seed, strength, size, refine, extra_neg=""):
|
|||
|
|
wf = {
|
|||
|
|
"1": {"class_type": "CheckpointLoaderSimple", "inputs": {"ckpt_name": CKPT}},
|
|||
|
|
"2": {"class_type": "LoraLoader", "inputs": {"model": ["1", 0], "clip": ["1", 1], "lora_name": LORA, "strength_model": strength, "strength_clip": strength}},
|
|||
|
|
"3": {"class_type": "CLIPSetLastLayer", "inputs": {"clip": ["2", 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 + FRAME_NEG + (TONES[TONE][1].replace(", purple, magenta, pink", "") if os.environ.get("ANFG_PAL", "") in ("material", "parts") else TONES[TONE][1]) + (MATERIAL_NEG if os.environ.get("ANFG_PAL", "") in ("material", "parts") else "") + ((", " + extra_neg) if extra_neg else "")}},
|
|||
|
|
"6": {"class_type": "EmptyLatentImage", "inputs": {"width": size, "height": size, "batch_size": 1}},
|
|||
|
|
"7": {"class_type": "KSampler", "inputs": {"model": ["2", 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]}},
|
|||
|
|
}
|
|||
|
|
model = ["2", 0]
|
|||
|
|
if LORA2:
|
|||
|
|
name2, strength2 = LORA2.rsplit(":", 1)
|
|||
|
|
wf["2b"] = {"class_type": "LoraLoaderModelOnly", "inputs": {"model": ["2", 0], "lora_name": name2, "strength_model": float(strength2)}}
|
|||
|
|
model = ["2b", 0]
|
|||
|
|
wf["7"]["inputs"]["model"] = model
|
|||
|
|
last = "8"
|
|||
|
|
if refine:
|
|||
|
|
# 4배 확대 → 1024 → 약하게 다듬기(세부 선명도)
|
|||
|
|
wf["40"] = {"class_type": "UpscaleModelLoader", "inputs": {"model_name": UPSCALER}}
|
|||
|
|
wf["41"] = {"class_type": "ImageUpscaleWithModel", "inputs": {"upscale_model": ["40", 0], "image": ["8", 0]}}
|
|||
|
|
wf["42"] = {"class_type": "ImageScale", "inputs": {"image": ["41", 0] if UPSCALE == "model" else ["8", 0], "upscale_method": "lanczos", "width": 1024, "height": 1024, "crop": "disabled"}}
|
|||
|
|
if UPSCALE != "model":
|
|||
|
|
del wf["40"]; del wf["41"]
|
|||
|
|
wf["43"] = {"class_type": "VAEEncode", "inputs": {"pixels": ["42", 0], "vae": ["1", 2]}}
|
|||
|
|
wf["44"] = {"class_type": "KSampler", "inputs": {"model": model, "positive": ["4", 0], "negative": ["5", 0], "latent_image": ["43", 0], "seed": seed + 1,
|
|||
|
|
"steps": 24, "cfg": 7.0, "sampler_name": "dpmpp_2m", "scheduler": "karras", "denoise": REFINE}}
|
|||
|
|
wf["45"] = {"class_type": "VAEDecode", "inputs": {"samples": ["44", 0], "vae": ["1", 2]}}
|
|||
|
|
last = "45"
|
|||
|
|
wf["12"] = {"class_type": "InspyrenetRembg", "inputs": {"image": [last, 0], "torchscript_jit": "default"}}
|
|||
|
|
wf["9"] = {"class_type": "SaveImage", "inputs": {"images": ["12", 0], "filename_prefix": "moba_anfg"}}
|
|||
|
|
return wf
|
|||
|
|
|
|||
|
|
|
|||
|
|
def run(wf, want="9"):
|
|||
|
|
pid = base.post("/prompt", {"prompt": wf, "client_id": str(uuid.uuid4())})["prompt_id"]
|
|||
|
|
t0 = time.time()
|
|||
|
|
while time.time() - t0 < 900:
|
|||
|
|
hist = json.loads(base.get("/history/" + pid))
|
|||
|
|
if pid in hist:
|
|||
|
|
st = hist[pid].get("status", {})
|
|||
|
|
if st.get("status_str") == "error":
|
|||
|
|
raise RuntimeError(json.dumps(st)[:1500])
|
|||
|
|
for nid, node in hist[pid].get("outputs", {}).items():
|
|||
|
|
if nid != want:
|
|||
|
|
continue
|
|||
|
|
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(base.get("/view?" + q))).convert("RGBA")
|
|||
|
|
time.sleep(1.0)
|
|||
|
|
raise TimeoutError("comfy timeout")
|
|||
|
|
|
|||
|
|
|
|||
|
|
# a12 검토: 가는 무기(검 · 단검 · 도끼 · 활 · 지팡이 …)는 512 에서 날이 흰 납작면으로 뭉개진다 → 768 로 그리고(모델 권장 크기), 길잡이를 끝까지 더 오래 잡는다(끝 0.7).
|
|||
|
|
THIN = ("sword", "dagger", "axe", "bow", "staff", "throwing", "blunt", "hammer", "magic_dagger", "offhand_sword")
|
|||
|
|
THIN_SIZE = int(os.environ.get("ANFG_THIN_SIZE", "768"))
|
|||
|
|
SEED_BASE = int(os.environ.get("ANFG_SEED", "0")) # 회차마다 다른 시드를 쓰려면 바꾼다
|
|||
|
|
|
|||
|
|
|
|||
|
|
def hash_name(name):
|
|||
|
|
"""종류 이름으로 시드를 흩는다(표의 순서가 바뀌어도 같은 종류는 같은 시드)"""
|
|||
|
|
return sum(ord(c) * (k + 3) for k, c in enumerate(name)) % 9973
|
|||
|
|
|
|||
|
|
|
|||
|
|
def main():
|
|||
|
|
out, tag = sys.argv[1], sys.argv[2]
|
|||
|
|
strengths = [float(v) for v in (sys.argv[3] if len(sys.argv) > 3 else "0.7,1.0").split(",")]
|
|||
|
|
seeds = int(sys.argv[4]) if len(sys.argv) > 4 else 2
|
|||
|
|
size = int(sys.argv[5]) if len(sys.argv) > 5 else 512
|
|||
|
|
refine = int(sys.argv[6]) if len(sys.argv) > 6 else 0
|
|||
|
|
global SUBJECTS
|
|||
|
|
mode = sys.argv[7] if len(sys.argv) > 7 else ""
|
|||
|
|
cn_strength = float(sys.argv[8]) if len(sys.argv) > 8 else 0.6
|
|||
|
|
cn_end = float(sys.argv[9]) if len(sys.argv) > 9 else 0.6
|
|||
|
|
if mode == "fr":
|
|||
|
|
SUBJECTS = SUBJECTS_FR
|
|||
|
|
elif mode == "shape":
|
|||
|
|
SUBJECTS = SUBJECTS_SHAPE
|
|||
|
|
elif mode in ("all", "draft", "allshape"):
|
|||
|
|
SUBJECTS = SUBJECTS_ALL
|
|||
|
|
elif mode == "lib":
|
|||
|
|
SUBJECTS = lib_subjects()
|
|||
|
|
only = os.environ.get("ANFG_ONLY", "") # 쉼표로 적은 종류만 돌린다
|
|||
|
|
if only:
|
|||
|
|
SUBJECTS = [x for x in SUBJECTS if x[0].split("-")[0] in only.split(",")]
|
|||
|
|
skip = os.environ.get("ANFG_SKIP", "") # 행 이름에 이 글자가 들어 있으면 뺀다(예: genpd — PD 합격본 윤곽 길잡이는 복제를 만든다)
|
|||
|
|
if skip:
|
|||
|
|
SUBJECTS = [x for x in SUBJECTS if not any(k in x[0] for k in skip.split(","))]
|
|||
|
|
rows = os.environ.get("ANFG_ROWS", "") # 쉼표로 적은 행 이름(종류-길잡이)만 돌린다
|
|||
|
|
if rows:
|
|||
|
|
SUBJECTS = [x for x in SUBJECTS if x[0] in rows.split(",")]
|
|||
|
|
os.makedirs(out, exist_ok=True)
|
|||
|
|
cols = len(strengths) * seeds
|
|||
|
|
cell, pad, left = 220, 4, 80
|
|||
|
|
sheet = Image.new("RGB", (left + cols * (cell + pad) + pad, len(SUBJECTS) * (cell + pad) + pad), (24, 24, 28))
|
|||
|
|
draw = ImageDraw.Draw(sheet)
|
|||
|
|
for i, sub in enumerate(SUBJECTS):
|
|||
|
|
name, desc = sub[0], sub[1]
|
|||
|
|
extra_neg = sub[3] if len(sub) > 3 else ""
|
|||
|
|
use_shape = len(sub) > 2 and sub[2] and mode in ("shape", "allshape")
|
|||
|
|
shape = base.upload(shape_image(sub[2]), "bt_anfg_shape_%d.png" % sub[2]) if use_shape else None
|
|||
|
|
if mode == "lib":
|
|||
|
|
guide = Image.open(sub[2]).convert("RGB")
|
|||
|
|
if "-genpd" in name:
|
|||
|
|
# a11 검토: PD 합격본의 윤곽을 그대로 넘기면 색만 바뀐 복제가 나온다 → 좌우를 뒤집어 넘긴다
|
|||
|
|
guide = guide.transpose(Image.FLIP_LEFT_RIGHT)
|
|||
|
|
shape = base.upload(guide, "bt_anfg_lib_%s.png" % name)
|
|||
|
|
y = pad + i * (cell + pad)
|
|||
|
|
draw.text((pad, y + 4), name, fill=(240, 220, 160))
|
|||
|
|
col = 0
|
|||
|
|
for s in strengths:
|
|||
|
|
for j in range(seeds):
|
|||
|
|
fn = "%s_%s_s%02d_%d.png" % (tag, name, int(s * 10), j)
|
|||
|
|
# 길잡이 모음 방식은 그림마다 색 조합을 돌린다(종류 안 순번 + 시드 순번 + 회차 시드)
|
|||
|
|
d1 = desc.format(pal=PALETTES[(i * 7 + j * 3 + SEED_BASE) % len(PALETTES)]) if "{pal}" in desc else desc
|
|||
|
|
if PARTS and name.split("-")[0] in TYPE_PARTS:
|
|||
|
|
d1 = parts_desc(name.split("-")[0], i * 7 + j * 3 + SEED_BASE)
|
|||
|
|
prompt = "%s, %s%s, simple background, black background, no humans" % (("fricon, " if LORA2 else "") + TRIGGER, (COMPOSE if TONE else "") + d1, TONES[TONE][0])
|
|||
|
|
# 이미 만든 그림은 다시 만들지 않는다(끊겨도 이어서 돌린다)
|
|||
|
|
if os.path.exists(os.path.join(out, fn)):
|
|||
|
|
im = Image.open(os.path.join(out, fn)).convert("RGBA")
|
|||
|
|
else:
|
|||
|
|
# 종류 표가 정해진 예전 회차(a1 · a2)는 순번 시드를 그대로 쓴다(재현). 새 표는 이름으로 흩는다.
|
|||
|
|
seed = (50000 + i * 37 + j * 733) if mode in ("", "fr", "shape") else (50000 + SEED_BASE + hash_name(name) + j * 733)
|
|||
|
|
kind = name.split("-")[0]
|
|||
|
|
wf = workflow(prompt, seed, s, THIN_SIZE if kind in THIN else size, refine, extra_neg)
|
|||
|
|
if shape:
|
|||
|
|
# Layer Lab 길잡이는 캐주얼한 세부가 새므로 약하게 걸어 큰 윤곽만 잡는다
|
|||
|
|
weak = mode == "lib" and "-ll" in name
|
|||
|
|
# 가는 형태(반지 · 활 · 지팡이 · 투척)는 ControlNet 을 일찍 풀면 다른 물건이 된다 — 끝을 0.6 아래로 내리지 않는다
|
|||
|
|
end = max(cn_end, 0.7) if kind in THIN else (max(cn_end, 0.6) if kind == "ring" else cn_end)
|
|||
|
|
add_shape(wf, shape, 0.4 if weak else cn_strength, 0.4 if weak else end)
|
|||
|
|
elif mode == "draft":
|
|||
|
|
add_draft(wf, d1, seed, cn_strength, cn_end)
|
|||
|
|
im = run(wf)
|
|||
|
|
im.save(os.path.join(out, fn))
|
|||
|
|
# 학습 캡션으로 쓸 묘사를 곁파일로 남긴다(icon_dataset.py 가 읽는다)
|
|||
|
|
with io.open(os.path.join(out, fn[:-4] + ".txt"), "w", encoding="utf-8") as f:
|
|||
|
|
f.write(d1)
|
|||
|
|
# 예시와 견주기 쉽게 어두운 바탕에 올린다
|
|||
|
|
bg = Image.new("RGB", (cell, cell), (12, 12, 14)); small = im.resize((cell, cell), Image.LANCZOS); bg.paste(small, (0, 0), small)
|
|||
|
|
sheet.paste(bg, (left + col * (cell + pad), y))
|
|||
|
|
draw.text((left + col * (cell + pad) + 3, y + 2), "LoRA %.1f #%d" % (s, j), fill=(200, 200, 200))
|
|||
|
|
col += 1
|
|||
|
|
print("%s %s" % (tag, name)); sys.stdout.flush()
|
|||
|
|
sheet.save(os.path.join(out, "%s_sheet.png" % tag))
|
|||
|
|
print("sheet", os.path.join(out, "%s_sheet.png" % tag))
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
main()
|