Files
Dansori_Characters/tools/apply_leesori_v2_integration.py
2026-07-04 10:34:46 +09:00

126 lines
4.1 KiB
Python

from __future__ import annotations
import json
from datetime import date
from pathlib import Path
from PIL import Image
ROOT = Path(r"D:\Work_AI\Dansori")
APP = ROOT / "DansoriEQ" / "src" / "DansoriEQ.App"
HOST = APP / "Assets" / "Live2DHost"
PUPPET = APP / "Assets" / "Characters" / "Puppets" / "LeeSoriV2"
PLAN = ROOT / "DansoriEQ" / "docs" / "LIVE2D_CHARACTER_INTEGRATION_PLAN.md"
def update_characters() -> None:
path = HOST / "characters.json"
data = json.loads(path.read_text(encoding="utf-8-sig"))
for character in data["characters"]:
if character.get("id") == "leesori":
character["puppet"] = {
"rig": "../Characters/Puppets/LeeSoriV2/rig.json",
"imageBase": "../Characters/Puppets/LeeSoriV2/Images/",
}
break
path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
def update_css() -> None:
path = HOST / "style.css"
css = path.read_text(encoding="utf-8")
old = """#puppet {
display: none;
right: 10%;
bottom: 1%;
width: 80%;
height: auto;
aspect-ratio: 1086 / 1448;
transform-origin: 50% 100%;
filter: drop-shadow(0 20px 24px rgba(0, 0, 0, .46));
animation: puppetRootIdle 3.8s ease-in-out infinite;
}"""
new = """#puppet {
display: none;
right: 17.5%;
bottom: -20%;
width: 65%;
height: auto;
aspect-ratio: 365 / 1058;
transform-origin: 50% 100%;
filter: drop-shadow(0 20px 24px rgba(0, 0, 0, .46));
animation: puppetRootIdle 3.8s ease-in-out infinite;
}"""
if old not in css:
raise RuntimeError("Expected #puppet CSS block was not found.")
path.write_text(css.replace(old, new), encoding="utf-8")
def make_css_qa() -> None:
src = Image.open(PUPPET / "leesori_v2_source.png").convert("RGBA")
width, height = 390, 600
stage = Image.new("RGBA", (width, height), (16, 16, 18, 255))
target_w = int(width * 0.65)
target_h = int(target_w * src.height / src.width)
resized = src.resize((target_w, target_h), Image.Resampling.LANCZOS)
right = int(width * 0.175)
bottom = int(height * -0.20)
x = width - target_w - right
y = height - target_h - bottom
stage.alpha_composite(resized, (x, y))
stage.convert("RGB").save(PUPPET / "qa_view_390x600_css_selected.png")
def update_plan() -> None:
entry = f"""
### {date.today().isoformat()} - New Sheet LeeSoriV2 Conversion
User correction:
- The previous LeeSoriExtended puppet solved clipping but used the old LeeSori design.
- The authoritative new design sheet is:
- `Characters_Build_Docs/LeeSori_Profile/03_Assets/Reference/sori_sheet.png`
Changed:
- Created `LeeSoriV2` from the new sheet's front full-body pose without AI identity drift.
- Extracted alpha-clean source and overlap puppet parts:
- base, legs, chest, left/right arms, left/right hands, head.
- Replaced the app preview image with the new sheet-based LeeSori.
- Switched `characters.json` to:
- `../Characters/Puppets/LeeSoriV2/rig.json`
- `../Characters/Puppets/LeeSoriV2/Images/`
- Updated WebView framing for the new tall sheet ratio:
- `right: 17.5%`
- `bottom: -20%`
- `width: 65%`
- Framing goal: upper-body biased view for idle/work states while keeping head and both hands visible. Full body remains available in the source/rig for later state-specific framing.
QA artifacts:
- `src/DansoriEQ.App/Assets/Characters/Puppets/LeeSoriV2/qa_source_black.png`
- `src/DansoriEQ.App/Assets/Characters/Puppets/LeeSoriV2/qa_view_390x600_upper_bias.png`
- `src/DansoriEQ.App/Assets/Characters/Puppets/LeeSoriV2/qa_view_390x600_css_selected.png`
Next:
- Build and run in WPF to verify real WebView framing.
- If this sheet-based puppet is accepted, add state-specific animation tuning and then generate Haruka/Isabel/Noeul with the same source-first process.
"""
text = PLAN.read_text(encoding="utf-8")
if "New Sheet LeeSoriV2 Conversion" not in text:
PLAN.write_text(text.rstrip() + entry + "\n", encoding="utf-8")
def main() -> None:
update_characters()
update_css()
make_css_qa()
update_plan()
if __name__ == "__main__":
main()