ThreeKingdomsSlingshot/Tools/apply_delivery.py

286 lines
15 KiB
Python
Raw Permalink Normal View History

"""납품 인박스 → 게임 반영 (파일 작업만 · Unity 쪽은 끝에 출력하는 명령으로).
PD 2026-11-05: 「매번 수신하지 않아도, 요청한 작업이 완전히 완료된 경우 해당 리소스만 전달받는 체계」.
제작 세션은 묶음이 **완전히** 끝났을 때만 아래 json 한 장을 인박스에 쓴다(중간 보고 없음 · 채팅 메시지 없음).
인박스 = C:\\ArtRnD\\samguk_game\\deliveries\\<YYYYMMDD_HHMM>_<batch>.json
{
"batch": "bg" | "hgj" | "heroes_back" | "dz" | "cc" | "wei" | …,
UI 리뉴얼 A-2~A-4 화면 적용(PD #1100) — 팩 부품 + UI 테마(id 치환) · 화면 10종 · 인박스 ui kind - 테마: SOT Assets/Data/UI/ui_theme.csv(색 57 · 스킨 32 · 아이콘 63 · 제목 폰트) → 메뉴 TKS/Import UI Theme → Resources/UI/UITheme.asset (Runtime/UI/UITheme · UISkin · Editor/UIThemeImporter · ProjectSetup 에서도 임포트) · 화면 코드는 id 로만 묻는다(스프라이트 경로 0) - 신규 제작(B) 치환: Assets/UI/Samguk/**/<id>.png → 임포트 시 같은 id 스킨·아이콘 자동 치환(팩 보존 · 새 id = 아이콘 추가) · Tools/apply_delivery.py ui kind(md5 → Samguk/<group> · gim_* → Resources/Gimmicks/Art · ui_delivered.csv) - 공통: UiKit(카드 = BorderFrame_Round20 틴트 · 버튼 = Button01_s + 광택 · 원 · 진행 바 = Slider_Basic01 · 초상 틀 · 등급 칸 ItemFrame02 · 별 줄 · 병종 배지) · 재화 바(ResourceBar + cur_* 아이콘) · 하단 탭(선택 판 + PictoIcon) · 제목 띠 · 팝업(Popup Topber + 닫기 X) · 토스트 - 화면: 로비(키아트 04 배경 위쪽 ×1.22 · 출정·편성·연무장 아이콘) · 영웅(등급 칸 + 장비 아이콘 · 체크/알림 점 · 별 아이콘) · 편성 · 거래(배너 · 상자 아이콘) · 보물(상자 아이콘 · 포인트 배지) · 내정(보상 줄 · 도면·골드 아이콘) · 연무장 · 결과(리본 · 별) · 월드맵 스테이지 팝업만 팩(거점 kit 유지) - 전투: HUD 칩 · 턴 띠 · 원형 스킬 버튼 + 쿨 라디얼 · 유닛 체력 바 · 기믹 내구 핍 = 팩 슬라이더(월드 9-slice 사본 · 가운데 2 px) · 조준선 색 유지 - 3:4(1536×2048): 본문 비율 축소(UiFitHeight · 1080×1920 = 그대로) - 검증: 플레이모드 116/116(기존 115 + UIThemeTests 1) · 컴파일 0 · error 0 · 캡처 = DataImporter.CaptureUI(UICaptureDriver · 15장) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 14:20:57 +00:00
"kind": "bg" | "poses" | "worldmap" | "ui",
"qa": "PASS",
"session": "<만든 세션 이름>",
# kind == "bg"
"file": "C:/ArtRnD/samguk_game/bg/battlefield_01.png", "md5": "<파일 md5>",
# kind == "poses"
"root": "C:/ArtRnD/samguk_game/poses", # <root>/<art>/<clip>.png
"arts": ["dz_infantry", "dz_spear", …],
"clips": ["front_idle", "front_ready", "front_action"], # back_* 가 있으면 함께
"md5_list": "C:/ArtRnD/samguk_game/poses/md5.txt" # 선택 · <root> 기준 상대 경로 · md5sum 형식
# kind == "worldmap" (챕터 월드맵 기획 확정판 · 챕터맵 보강 세션 · 2026-11-05)
"root": "C:/ArtRnD/samguk_chapmap/plan", # 선택(없으면 이 기본값) · maps[].map·anchors 의 기준
"maps": [{"chapter": 3, "id": "ch03", "ko": "군웅할거", "nodes": 15,
"map": "maps/ch03.png", "map_md5": "<md5>", "anchors": "anchors/ch03.json", "anchors_md5": "<md5>"}]
}
UI 리뉴얼 A-2~A-4 화면 적용(PD #1100) — 팩 부품 + UI 테마(id 치환) · 화면 10종 · 인박스 ui kind - 테마: SOT Assets/Data/UI/ui_theme.csv(색 57 · 스킨 32 · 아이콘 63 · 제목 폰트) → 메뉴 TKS/Import UI Theme → Resources/UI/UITheme.asset (Runtime/UI/UITheme · UISkin · Editor/UIThemeImporter · ProjectSetup 에서도 임포트) · 화면 코드는 id 로만 묻는다(스프라이트 경로 0) - 신규 제작(B) 치환: Assets/UI/Samguk/**/<id>.png → 임포트 시 같은 id 스킨·아이콘 자동 치환(팩 보존 · 새 id = 아이콘 추가) · Tools/apply_delivery.py ui kind(md5 → Samguk/<group> · gim_* → Resources/Gimmicks/Art · ui_delivered.csv) - 공통: UiKit(카드 = BorderFrame_Round20 틴트 · 버튼 = Button01_s + 광택 · 원 · 진행 바 = Slider_Basic01 · 초상 틀 · 등급 칸 ItemFrame02 · 별 줄 · 병종 배지) · 재화 바(ResourceBar + cur_* 아이콘) · 하단 탭(선택 판 + PictoIcon) · 제목 띠 · 팝업(Popup Topber + 닫기 X) · 토스트 - 화면: 로비(키아트 04 배경 위쪽 ×1.22 · 출정·편성·연무장 아이콘) · 영웅(등급 칸 + 장비 아이콘 · 체크/알림 점 · 별 아이콘) · 편성 · 거래(배너 · 상자 아이콘) · 보물(상자 아이콘 · 포인트 배지) · 내정(보상 줄 · 도면·골드 아이콘) · 연무장 · 결과(리본 · 별) · 월드맵 스테이지 팝업만 팩(거점 kit 유지) - 전투: HUD 칩 · 턴 띠 · 원형 스킬 버튼 + 쿨 라디얼 · 유닛 체력 바 · 기믹 내구 핍 = 팩 슬라이더(월드 9-slice 사본 · 가운데 2 px) · 조준선 색 유지 - 3:4(1536×2048): 본문 비율 축소(UiFitHeight · 1080×1920 = 그대로) - 검증: 플레이모드 116/116(기존 115 + UIThemeTests 1) · 컴파일 0 · error 0 · 캡처 = DataImporter.CaptureUI(UICaptureDriver · 15장) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 14:20:57 +00:00
# kind == "ui" (UI 리뉴얼 B 신규 제작 · 세션 tks-ui-art · PD #1100 · 2026-11-06)
"root": "C:/ArtRnD/samguk_ui/<group>", # files 의 기준
"files": ["cur_gold.png", …] 또는 [{"file": "cur_gold.png", "md5": "<md5>"}, …], # 파일 이름(확장자 앞) = 테마 id
"md5_list": "C:/ArtRnD/samguk_ui/<group>/md5.txt" # 선택 · <root> 기준 상대 경로 · md5sum 형식
}
ui = <root>/<file> → Assets/UI/Samguk/<group>/<id>.png (group = batch 의 「ui_」 뒤 · 없으면 root 폴더 이름) ·
기믹 그림(id 가 gim_ 로 시작) → Assets/Resources/Gimmicks/Art/<id>.png(GimmickView 가 읽는 자리) ·
치환 기록 = Assets/UI/Samguk/ui_delivered.csv(id · group · 경로 · md5 · batch) · 테마 치환 테이블(Resources/UI/UITheme.asset)은
Unity 가 Samguk PNG 임포트 때 자동 갱신(Editor/UIThemeImporter · 수동 = 메뉴 TKS/Import UI Theme) — 화면 코드 변경 없음.
worldmap = 맵 PNG → Assets/Resources/ChapterMap/Maps/<id>.png · 좌표 json → Data/<id>.json(게임 노드 = stages[].at) ·
Data/world.json 의 같은 id 항목(길 roadX·roadY · 장소)을 좌표 json 으로 교체. 게임 챕터 → 맵 연결 = Assets/Data/Stages/chapters.csv map 열.
같은 맵 id 를 더 늦게 다시 납품한 json(_r2 등)이 미반영으로 있으면 앞의 것은 반영하지 않고 .superseded.json 으로 바꾼다.
실행: <ComfyUI venv python> -I -B -X utf8 Tools/apply_delivery.py list
<ComfyUI venv python> -I -B -X utf8 Tools/apply_delivery.py apply <json 경로> (반영 뒤 .applied.json 으로 이름 바꿈)
<ComfyUI venv python> -I -B -X utf8 Tools/apply_delivery.py apply --all (미반영 전부)
<ComfyUI venv python> -I -B -X utf8 Tools/apply_delivery.py apply --all --kind worldmap (그 kind 만)
반영 뒤 Unity: 메뉴 재임포트 → 비동기 테스트 → 커밋 (끝에 출력).
"""
import argparse
import hashlib
import json
import os
import shutil
import subprocess
import sys
PROJECT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
INBOX = r"C:\ArtRnD\samguk_game\deliveries"
EXPORTER = r"C:\Ai\NerdNavisAi\.claude\skills\skeletal-anim-pipeline\files\scripts\rig_frames_export.py"
UNITS = os.path.join(PROJECT, "Assets", "Resources", "Units")
BOARD = os.path.join(PROJECT, "Assets", "Resources", "Board", "battlefield_01.png")
CHAPMAP = os.path.join(PROJECT, "Assets", "Resources", "ChapterMap")
WORLDMAP_ROOT = r"C:\ArtRnD\samguk_chapmap\plan"
UI 리뉴얼 A-2~A-4 화면 적용(PD #1100) — 팩 부품 + UI 테마(id 치환) · 화면 10종 · 인박스 ui kind - 테마: SOT Assets/Data/UI/ui_theme.csv(색 57 · 스킨 32 · 아이콘 63 · 제목 폰트) → 메뉴 TKS/Import UI Theme → Resources/UI/UITheme.asset (Runtime/UI/UITheme · UISkin · Editor/UIThemeImporter · ProjectSetup 에서도 임포트) · 화면 코드는 id 로만 묻는다(스프라이트 경로 0) - 신규 제작(B) 치환: Assets/UI/Samguk/**/<id>.png → 임포트 시 같은 id 스킨·아이콘 자동 치환(팩 보존 · 새 id = 아이콘 추가) · Tools/apply_delivery.py ui kind(md5 → Samguk/<group> · gim_* → Resources/Gimmicks/Art · ui_delivered.csv) - 공통: UiKit(카드 = BorderFrame_Round20 틴트 · 버튼 = Button01_s + 광택 · 원 · 진행 바 = Slider_Basic01 · 초상 틀 · 등급 칸 ItemFrame02 · 별 줄 · 병종 배지) · 재화 바(ResourceBar + cur_* 아이콘) · 하단 탭(선택 판 + PictoIcon) · 제목 띠 · 팝업(Popup Topber + 닫기 X) · 토스트 - 화면: 로비(키아트 04 배경 위쪽 ×1.22 · 출정·편성·연무장 아이콘) · 영웅(등급 칸 + 장비 아이콘 · 체크/알림 점 · 별 아이콘) · 편성 · 거래(배너 · 상자 아이콘) · 보물(상자 아이콘 · 포인트 배지) · 내정(보상 줄 · 도면·골드 아이콘) · 연무장 · 결과(리본 · 별) · 월드맵 스테이지 팝업만 팩(거점 kit 유지) - 전투: HUD 칩 · 턴 띠 · 원형 스킬 버튼 + 쿨 라디얼 · 유닛 체력 바 · 기믹 내구 핍 = 팩 슬라이더(월드 9-slice 사본 · 가운데 2 px) · 조준선 색 유지 - 3:4(1536×2048): 본문 비율 축소(UiFitHeight · 1080×1920 = 그대로) - 검증: 플레이모드 116/116(기존 115 + UIThemeTests 1) · 컴파일 0 · error 0 · 캡처 = DataImporter.CaptureUI(UICaptureDriver · 15장) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 14:20:57 +00:00
SAMGUK_UI = os.path.join(PROJECT, "Assets", "UI", "Samguk")
GIMMICK_ART = os.path.join(PROJECT, "Assets", "Resources", "Gimmicks", "Art")
THEME_CSV = os.path.join(PROJECT, "Assets", "Data", "UI", "ui_theme.csv")
def md5_of(path):
h = hashlib.md5()
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(1 << 20), b""):
h.update(chunk)
return h.hexdigest()
def pending():
os.makedirs(INBOX, exist_ok=True)
return sorted(f for f in os.listdir(INBOX)
if f.endswith(".json") and not f.endswith(".applied.json") and not f.endswith(".superseded.json"))
def superseded(files):
"""worldmap 납품 중 같은 맵 id 를 모두 더 늦은 미반영 납품이 덮는 것(파일명 = 시각순)."""
maps = {}
for f in files:
d = json.load(open(os.path.join(INBOX, f), encoding="utf-8"))
if d.get("kind") == "worldmap":
maps[f] = {m["id"] for m in d.get("maps", [])}
out = set()
for f, ids in maps.items():
later = set().union(*[v for g, v in maps.items() if g > f] or [set()])
if ids and ids <= later:
out.add(f)
return out
def check_md5_list(root, md5_list):
"""md5sum 형식(<md5> <상대경로>) 목록 검증 → (ok, bad)."""
ok, bad = 0, []
for line in open(md5_list, encoding="utf-8"):
line = line.strip()
if not line:
continue
digest, rel = line.split(None, 1)
rel = rel.lstrip("*").replace("/", os.sep)
p = os.path.join(root, rel)
if not os.path.exists(p) or md5_of(p) != digest.lower():
bad.append(rel)
else:
ok += 1
return ok, bad
def apply_bg(d):
src = d["file"]
if d.get("md5") and md5_of(src) != d["md5"].lower():
sys.exit("배경 md5 불일치: " + src)
os.makedirs(os.path.dirname(BOARD), exist_ok=True)
shutil.copyfile(src, BOARD)
print("bg ->", BOARD, "md5", md5_of(BOARD))
def apply_poses(d):
root = d["root"]
if d.get("md5_list"):
ok, bad = check_md5_list(root, d["md5_list"])
if bad:
sys.exit("md5 불일치 %d: %s" % (len(bad), ", ".join(bad[:5])))
print("md5 OK", ok)
clips = d.get("clips") or ["front_idle", "front_ready", "front_action"]
for art in d["arts"]:
src = os.path.join(root, art)
have = [c for c in clips if os.path.exists(os.path.join(src, c + ".png"))]
if not have:
sys.exit("포즈 없음: " + src)
out = os.path.join(UNITS, art)
if os.path.isdir(out):
shutil.rmtree(out) # 구판(리그 프레임·정지 그림) 제거 — git 에 있음
r = subprocess.run([sys.executable, "-I", "-B", "-X", "utf8", EXPORTER, "poseset", src, "--out", out, "--clips", ",".join(have)],
capture_output=True, text=True, encoding="utf-8")
if r.returncode != 0:
sys.exit("poseset 실패 " + art + "\n" + r.stdout + r.stderr)
print(r.stdout.strip().splitlines()[-1])
def apply_worldmap(d):
root = d.get("root") or WORLDMAP_ROOT
world_path = os.path.join(CHAPMAP, "Data", "world.json")
world = json.load(open(world_path, encoding="utf-8"))
spec = os.path.join(root, "spec", "chapters.json")
lm_scale = json.load(open(spec, encoding="utf-8")).get("lm_scale", 1.0) if os.path.exists(spec) else 1.0
for m in d["maps"]:
mid = m["id"]
pairs = [(m["map"], m.get("map_md5"), os.path.join(CHAPMAP, "Maps", mid + ".png")),
(m["anchors"], m.get("anchors_md5"), os.path.join(CHAPMAP, "Data", mid + ".json"))]
for rel, digest, dst in pairs:
src = os.path.join(root, rel.replace("/", os.sep))
if digest and md5_of(src) != digest.lower():
sys.exit("worldmap md5 불일치: " + src)
for rel, digest, dst in pairs:
shutil.copyfile(os.path.join(root, rel.replace("/", os.sep)), dst)
if digest and md5_of(dst) != digest.lower():
sys.exit("복사 뒤 md5 불일치: " + dst)
an = json.load(open(pairs[1][2], encoding="utf-8"))
# 장소 이름판 자리 = 장소 아래(chap_install.py 와 같은 식) · showLabels 가 꺼져 있으면 쓰지 않음
lms = [{"ko": L["ko"], "x": L["at"][0], "y": round(L["at"][1] + 120 * L.get("s", 1.0) * lm_scale, 1), "role": L.get("role", "")}
for L in an.get("landmarks", []) if L.get("ko")]
entry = {"id": mid, "no": m.get("chapter", an.get("no", 0)), "ko": m.get("ko", an.get("ko", "")), "years": "", "map": mid,
"roadX": [p[0] for p in an["road"]], "roadY": [p[1] for p in an["road"]], "landmarks": lms}
chapters = [c for c in world["chapters"] if c["id"] != mid] + [entry]
world["chapters"] = sorted(chapters, key=lambda c: (c["no"], c["id"]))
print("worldmap", mid, "->", pairs[0][2], "· 노드", len(an.get("stages", [])), "· md5 OK")
world["_doc"] = ("게임이 읽는 챕터 맵 데이터 · 맵별 길·장소 = Data/<id>.json(좌표 json) 에서 옮김 · 기획 확정판 맵은 "
"Tools/apply_delivery.py worldmap 이 갱신(원화 세션 가안 chap_install.py 를 다시 돌리면 가안으로 덮이니 쓰지 않는다) · "
"좌표 = 맵 px(왼쪽 위 원점 · y 아래로) · nodes·stageLink 는 게임이 읽지 않음(노드 = 스테이지 표 + Data/<id>.json stages[].at)")
open(world_path, "w", encoding="utf-8").write(json.dumps(world, ensure_ascii=False, indent=1))
UI 리뉴얼 A-2~A-4 화면 적용(PD #1100) — 팩 부품 + UI 테마(id 치환) · 화면 10종 · 인박스 ui kind - 테마: SOT Assets/Data/UI/ui_theme.csv(색 57 · 스킨 32 · 아이콘 63 · 제목 폰트) → 메뉴 TKS/Import UI Theme → Resources/UI/UITheme.asset (Runtime/UI/UITheme · UISkin · Editor/UIThemeImporter · ProjectSetup 에서도 임포트) · 화면 코드는 id 로만 묻는다(스프라이트 경로 0) - 신규 제작(B) 치환: Assets/UI/Samguk/**/<id>.png → 임포트 시 같은 id 스킨·아이콘 자동 치환(팩 보존 · 새 id = 아이콘 추가) · Tools/apply_delivery.py ui kind(md5 → Samguk/<group> · gim_* → Resources/Gimmicks/Art · ui_delivered.csv) - 공통: UiKit(카드 = BorderFrame_Round20 틴트 · 버튼 = Button01_s + 광택 · 원 · 진행 바 = Slider_Basic01 · 초상 틀 · 등급 칸 ItemFrame02 · 별 줄 · 병종 배지) · 재화 바(ResourceBar + cur_* 아이콘) · 하단 탭(선택 판 + PictoIcon) · 제목 띠 · 팝업(Popup Topber + 닫기 X) · 토스트 - 화면: 로비(키아트 04 배경 위쪽 ×1.22 · 출정·편성·연무장 아이콘) · 영웅(등급 칸 + 장비 아이콘 · 체크/알림 점 · 별 아이콘) · 편성 · 거래(배너 · 상자 아이콘) · 보물(상자 아이콘 · 포인트 배지) · 내정(보상 줄 · 도면·골드 아이콘) · 연무장 · 결과(리본 · 별) · 월드맵 스테이지 팝업만 팩(거점 kit 유지) - 전투: HUD 칩 · 턴 띠 · 원형 스킬 버튼 + 쿨 라디얼 · 유닛 체력 바 · 기믹 내구 핍 = 팩 슬라이더(월드 9-slice 사본 · 가운데 2 px) · 조준선 색 유지 - 3:4(1536×2048): 본문 비율 축소(UiFitHeight · 1080×1920 = 그대로) - 검증: 플레이모드 116/116(기존 115 + UIThemeTests 1) · 컴파일 0 · error 0 · 캡처 = DataImporter.CaptureUI(UICaptureDriver · 15장) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 14:20:57 +00:00
def apply_ui(d):
root = d["root"]
if d.get("md5_list"):
ok, bad = check_md5_list(root, d["md5_list"])
if bad:
sys.exit("md5 불일치 %d: %s" % (len(bad), ", ".join(bad[:5])))
print("md5 OK", ok)
batch = str(d.get("batch", ""))
group = batch[3:] if batch.startswith("ui_") else os.path.basename(os.path.normpath(root))
known = set()
if os.path.exists(THEME_CSV):
for line in open(THEME_CSV, encoding="utf-8-sig"):
f = line.split(",")
if len(f) > 1 and f[0] in ("skin", "icon"):
known.add(f[1])
log_path = os.path.join(SAMGUK_UI, "ui_delivered.csv")
rows = {}
if os.path.exists(log_path):
for line in open(log_path, encoding="utf-8-sig").read().splitlines()[1:]:
if line.strip():
rows[line.split(",")[0]] = line
replaced, added = [], []
for item in d["files"]:
rel, digest = (item, None) if isinstance(item, str) else (item["file"], item.get("md5"))
src = os.path.join(root, rel.replace("/", os.sep))
if not rel.lower().endswith(".png"):
sys.exit("PNG 가 아님: " + rel)
if digest and md5_of(src) != digest.lower():
sys.exit("ui md5 불일치: " + src)
uid = os.path.splitext(os.path.basename(rel))[0]
dst = os.path.join(GIMMICK_ART, uid + ".png") if uid.startswith("gim_") else os.path.join(SAMGUK_UI, group, uid + ".png")
os.makedirs(os.path.dirname(dst), exist_ok=True)
shutil.copyfile(src, dst)
m = md5_of(dst)
if digest and m != digest.lower():
sys.exit("복사 뒤 md5 불일치: " + dst)
(replaced if uid in known else added).append(uid)
try:
shown = os.path.relpath(dst, PROJECT)
except ValueError: # 다른 드라이브(시험)
shown = dst
rows[uid] = ",".join([uid, group, shown.replace(os.sep, "/"), m, batch])
os.makedirs(SAMGUK_UI, exist_ok=True)
with open(log_path, "w", encoding="utf-8-sig", newline="\n") as f:
f.write("id,group,path,md5,batch\n")
for k in sorted(rows):
f.write(rows[k] + "\n")
print("ui", group, "· 팩 치환", len(replaced), "· 새 id", len(added), (added[:8] if added else ""), "→", log_path)
def cmd_list(a):
files = pending()
old = superseded(files)
print("미반영 납품:", len(files))
for f in files:
d = json.load(open(os.path.join(INBOX, f), encoding="utf-8"))
print(" ", f, "|", d.get("kind"), d.get("batch"), "|", d.get("qa"), "|", d.get("session", ""), "| (뒤 납품으로 대체됨)" if f in old else "")
def cmd_apply(a):
files = pending()
targets = files if a.all else [os.path.basename(a.json)]
if a.kind:
targets = [f for f in targets if json.load(open(os.path.join(INBOX, f), encoding="utf-8")).get("kind") == a.kind]
if not targets:
print("반영할 납품 없음")
return
old = superseded(files)
for f in targets:
p = os.path.join(INBOX, f)
d = json.load(open(p, encoding="utf-8"))
if f in old:
print("대체됨(같은 맵의 뒤 납품이 있음) → .superseded.json:", f)
os.replace(p, p[:-5] + ".superseded.json")
continue
# QA 문구 뒤에 설명이 붙은 경우(「PASS(…)」) 도 통과로 본다
if not str(d.get("qa", "PASS")).startswith("PASS"):
print("건너뜀(QA 미통과):", f)
continue
print("==", f, d.get("kind"), d.get("batch"))
if d["kind"] == "bg":
apply_bg(d)
elif d["kind"] == "poses":
apply_poses(d)
elif d["kind"] == "worldmap":
apply_worldmap(d)
UI 리뉴얼 A-2~A-4 화면 적용(PD #1100) — 팩 부품 + UI 테마(id 치환) · 화면 10종 · 인박스 ui kind - 테마: SOT Assets/Data/UI/ui_theme.csv(색 57 · 스킨 32 · 아이콘 63 · 제목 폰트) → 메뉴 TKS/Import UI Theme → Resources/UI/UITheme.asset (Runtime/UI/UITheme · UISkin · Editor/UIThemeImporter · ProjectSetup 에서도 임포트) · 화면 코드는 id 로만 묻는다(스프라이트 경로 0) - 신규 제작(B) 치환: Assets/UI/Samguk/**/<id>.png → 임포트 시 같은 id 스킨·아이콘 자동 치환(팩 보존 · 새 id = 아이콘 추가) · Tools/apply_delivery.py ui kind(md5 → Samguk/<group> · gim_* → Resources/Gimmicks/Art · ui_delivered.csv) - 공통: UiKit(카드 = BorderFrame_Round20 틴트 · 버튼 = Button01_s + 광택 · 원 · 진행 바 = Slider_Basic01 · 초상 틀 · 등급 칸 ItemFrame02 · 별 줄 · 병종 배지) · 재화 바(ResourceBar + cur_* 아이콘) · 하단 탭(선택 판 + PictoIcon) · 제목 띠 · 팝업(Popup Topber + 닫기 X) · 토스트 - 화면: 로비(키아트 04 배경 위쪽 ×1.22 · 출정·편성·연무장 아이콘) · 영웅(등급 칸 + 장비 아이콘 · 체크/알림 점 · 별 아이콘) · 편성 · 거래(배너 · 상자 아이콘) · 보물(상자 아이콘 · 포인트 배지) · 내정(보상 줄 · 도면·골드 아이콘) · 연무장 · 결과(리본 · 별) · 월드맵 스테이지 팝업만 팩(거점 kit 유지) - 전투: HUD 칩 · 턴 띠 · 원형 스킬 버튼 + 쿨 라디얼 · 유닛 체력 바 · 기믹 내구 핍 = 팩 슬라이더(월드 9-slice 사본 · 가운데 2 px) · 조준선 색 유지 - 3:4(1536×2048): 본문 비율 축소(UiFitHeight · 1080×1920 = 그대로) - 검증: 플레이모드 116/116(기존 115 + UIThemeTests 1) · 컴파일 0 · error 0 · 캡처 = DataImporter.CaptureUI(UICaptureDriver · 15장) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 14:20:57 +00:00
elif d["kind"] == "ui":
apply_ui(d)
else:
sys.exit("모르는 kind: " + str(d.get("kind")))
os.replace(p, p[:-5] + ".applied.json")
print("\n다음(Unity · PD 에디터 Play 중이면 먼저 editor.playmode stop):")
print(" unity command --project-path . editor invoke_menu --path 'TKS/Reimport Unit Art (유닛 아트 재임포트)' --result-only (Units·Board·ChapterMap)")
UI 리뉴얼 A-2~A-4 화면 적용(PD #1100) — 팩 부품 + UI 테마(id 치환) · 화면 10종 · 인박스 ui kind - 테마: SOT Assets/Data/UI/ui_theme.csv(색 57 · 스킨 32 · 아이콘 63 · 제목 폰트) → 메뉴 TKS/Import UI Theme → Resources/UI/UITheme.asset (Runtime/UI/UITheme · UISkin · Editor/UIThemeImporter · ProjectSetup 에서도 임포트) · 화면 코드는 id 로만 묻는다(스프라이트 경로 0) - 신규 제작(B) 치환: Assets/UI/Samguk/**/<id>.png → 임포트 시 같은 id 스킨·아이콘 자동 치환(팩 보존 · 새 id = 아이콘 추가) · Tools/apply_delivery.py ui kind(md5 → Samguk/<group> · gim_* → Resources/Gimmicks/Art · ui_delivered.csv) - 공통: UiKit(카드 = BorderFrame_Round20 틴트 · 버튼 = Button01_s + 광택 · 원 · 진행 바 = Slider_Basic01 · 초상 틀 · 등급 칸 ItemFrame02 · 별 줄 · 병종 배지) · 재화 바(ResourceBar + cur_* 아이콘) · 하단 탭(선택 판 + PictoIcon) · 제목 띠 · 팝업(Popup Topber + 닫기 X) · 토스트 - 화면: 로비(키아트 04 배경 위쪽 ×1.22 · 출정·편성·연무장 아이콘) · 영웅(등급 칸 + 장비 아이콘 · 체크/알림 점 · 별 아이콘) · 편성 · 거래(배너 · 상자 아이콘) · 보물(상자 아이콘 · 포인트 배지) · 내정(보상 줄 · 도면·골드 아이콘) · 연무장 · 결과(리본 · 별) · 월드맵 스테이지 팝업만 팩(거점 kit 유지) - 전투: HUD 칩 · 턴 띠 · 원형 스킬 버튼 + 쿨 라디얼 · 유닛 체력 바 · 기믹 내구 핍 = 팩 슬라이더(월드 9-slice 사본 · 가운데 2 px) · 조준선 색 유지 - 3:4(1536×2048): 본문 비율 축소(UiFitHeight · 1080×1920 = 그대로) - 검증: 플레이모드 116/116(기존 115 + UIThemeTests 1) · 컴파일 0 · error 0 · 캡처 = DataImporter.CaptureUI(UICaptureDriver · 15장) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 14:20:57 +00:00
print(" unity command --project-path . editor invoke_menu --path 'TKS/Import UI Theme (UI 테마 임포트)' --result-only (ui · Samguk PNG 임포트 때 자동 · 확인용)")
print(" unity command --project-path . test run --mode playmode --async-tests --timeout 600 --result-only ; unity command --project-path . test status --result-only")
UI 리뉴얼 A-2~A-4 화면 적용(PD #1100) — 팩 부품 + UI 테마(id 치환) · 화면 10종 · 인박스 ui kind - 테마: SOT Assets/Data/UI/ui_theme.csv(색 57 · 스킨 32 · 아이콘 63 · 제목 폰트) → 메뉴 TKS/Import UI Theme → Resources/UI/UITheme.asset (Runtime/UI/UITheme · UISkin · Editor/UIThemeImporter · ProjectSetup 에서도 임포트) · 화면 코드는 id 로만 묻는다(스프라이트 경로 0) - 신규 제작(B) 치환: Assets/UI/Samguk/**/<id>.png → 임포트 시 같은 id 스킨·아이콘 자동 치환(팩 보존 · 새 id = 아이콘 추가) · Tools/apply_delivery.py ui kind(md5 → Samguk/<group> · gim_* → Resources/Gimmicks/Art · ui_delivered.csv) - 공통: UiKit(카드 = BorderFrame_Round20 틴트 · 버튼 = Button01_s + 광택 · 원 · 진행 바 = Slider_Basic01 · 초상 틀 · 등급 칸 ItemFrame02 · 별 줄 · 병종 배지) · 재화 바(ResourceBar + cur_* 아이콘) · 하단 탭(선택 판 + PictoIcon) · 제목 띠 · 팝업(Popup Topber + 닫기 X) · 토스트 - 화면: 로비(키아트 04 배경 위쪽 ×1.22 · 출정·편성·연무장 아이콘) · 영웅(등급 칸 + 장비 아이콘 · 체크/알림 점 · 별 아이콘) · 편성 · 거래(배너 · 상자 아이콘) · 보물(상자 아이콘 · 포인트 배지) · 내정(보상 줄 · 도면·골드 아이콘) · 연무장 · 결과(리본 · 별) · 월드맵 스테이지 팝업만 팩(거점 kit 유지) - 전투: HUD 칩 · 턴 띠 · 원형 스킬 버튼 + 쿨 라디얼 · 유닛 체력 바 · 기믹 내구 핍 = 팩 슬라이더(월드 9-slice 사본 · 가운데 2 px) · 조준선 색 유지 - 3:4(1536×2048): 본문 비율 축소(UiFitHeight · 1080×1920 = 그대로) - 검증: 플레이모드 116/116(기존 115 + UIThemeTests 1) · 컴파일 0 · error 0 · 캡처 = DataImporter.CaptureUI(UICaptureDriver · 15장) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 14:20:57 +00:00
print(" git add -A && git commit (끝줄 Co-Authored-By: <실제 모델> <noreply@anthropic.com>)")
if __name__ == "__main__":
p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
sp = p.add_subparsers(dest="cmd", required=True)
l = sp.add_parser("list")
l.set_defaults(fn=cmd_list)
ap = sp.add_parser("apply")
ap.add_argument("json", nargs="?", default="")
ap.add_argument("--all", action="store_true")
UI 리뉴얼 A-2~A-4 화면 적용(PD #1100) — 팩 부품 + UI 테마(id 치환) · 화면 10종 · 인박스 ui kind - 테마: SOT Assets/Data/UI/ui_theme.csv(색 57 · 스킨 32 · 아이콘 63 · 제목 폰트) → 메뉴 TKS/Import UI Theme → Resources/UI/UITheme.asset (Runtime/UI/UITheme · UISkin · Editor/UIThemeImporter · ProjectSetup 에서도 임포트) · 화면 코드는 id 로만 묻는다(스프라이트 경로 0) - 신규 제작(B) 치환: Assets/UI/Samguk/**/<id>.png → 임포트 시 같은 id 스킨·아이콘 자동 치환(팩 보존 · 새 id = 아이콘 추가) · Tools/apply_delivery.py ui kind(md5 → Samguk/<group> · gim_* → Resources/Gimmicks/Art · ui_delivered.csv) - 공통: UiKit(카드 = BorderFrame_Round20 틴트 · 버튼 = Button01_s + 광택 · 원 · 진행 바 = Slider_Basic01 · 초상 틀 · 등급 칸 ItemFrame02 · 별 줄 · 병종 배지) · 재화 바(ResourceBar + cur_* 아이콘) · 하단 탭(선택 판 + PictoIcon) · 제목 띠 · 팝업(Popup Topber + 닫기 X) · 토스트 - 화면: 로비(키아트 04 배경 위쪽 ×1.22 · 출정·편성·연무장 아이콘) · 영웅(등급 칸 + 장비 아이콘 · 체크/알림 점 · 별 아이콘) · 편성 · 거래(배너 · 상자 아이콘) · 보물(상자 아이콘 · 포인트 배지) · 내정(보상 줄 · 도면·골드 아이콘) · 연무장 · 결과(리본 · 별) · 월드맵 스테이지 팝업만 팩(거점 kit 유지) - 전투: HUD 칩 · 턴 띠 · 원형 스킬 버튼 + 쿨 라디얼 · 유닛 체력 바 · 기믹 내구 핍 = 팩 슬라이더(월드 9-slice 사본 · 가운데 2 px) · 조준선 색 유지 - 3:4(1536×2048): 본문 비율 축소(UiFitHeight · 1080×1920 = 그대로) - 검증: 플레이모드 116/116(기존 115 + UIThemeTests 1) · 컴파일 0 · error 0 · 캡처 = DataImporter.CaptureUI(UICaptureDriver · 15장) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 14:20:57 +00:00
ap.add_argument("--kind", default="", help="이 kind 만(bg · poses · worldmap · ui)")
ap.set_defaults(fn=cmd_apply)
a = p.parse_args()
if a.cmd == "apply" and not a.all and not a.json:
sys.exit("json 경로 또는 --all")
a.fn(a)