Ship the WeChat hot-update catalogs and the camp, TideWhale T2, UI atlas, and animation-lab work.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
"""LmGirl T-pose: Hunyuan 50k, then Studio low-poly, Studio UV, bake, and 5000-face reduction.
|
||||
|
||||
No skeleton. Step 05 runs only when the baked mesh is more than 30% over 5000 triangles.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
STUDIO = Path(r"D:\UD\AI\XModel\LLM3DGraphics")
|
||||
BLENDER = Path(r"D:\Tools\Blender\blender-5.0.0-windows-x64\blender.exe")
|
||||
PY = Path(r"D:\UD\AI\XModel\LLM3DGraphics\.venv\Scripts\python.exe")
|
||||
IMAGE = ROOT / "Doc/Design/Character/LmGirl/lm_girl_tpose.jpg"
|
||||
HIGH = ROOT / "Tools/output/lmgirl_tpose_3d/lmgirl.glb"
|
||||
OUT = ROOT / "Tools/output/lmgirl_tpose_studio"
|
||||
NAME = "LmGirlTpose"
|
||||
TARGET = 5000
|
||||
SIZE = 1024
|
||||
|
||||
sys.path.insert(0, str(ROOT / "Tools"))
|
||||
from hunyuan_3d import auth # noqa: E402
|
||||
from hunyuan_3d.glb_check import assert_glb_textures_ok # noqa: E402
|
||||
from model_tools.bl_decimate import FBX_YUP_EXPORT_VERSION # noqa: E402
|
||||
|
||||
sys.path.insert(0, str(STUDIO))
|
||||
from backend import hunyuan_studio as studio # noqa: E402
|
||||
from backend.retopology import _run_blender_step, compress_bake_maps, limit_studio_faces # noqa: E402
|
||||
|
||||
|
||||
def update(**values):
|
||||
print(json.dumps(values, ensure_ascii=False), flush=True)
|
||||
|
||||
|
||||
def generate_high() -> None:
|
||||
if HIGH.is_file() and HIGH.stat().st_size > 1_000_000:
|
||||
update(stage="reuse high mesh", path=str(HIGH))
|
||||
return
|
||||
HIGH.parent.mkdir(parents=True, exist_ok=True)
|
||||
update(stage="hunyuan 50k", image=str(IMAGE))
|
||||
subprocess.run(
|
||||
[str(PY), str(ROOT / "Tools/hunyuan_3d_generate.py"),
|
||||
"--image", str(IMAGE), "--output", str(HIGH), "--faces", "50k",
|
||||
"--no-interactive-login", "--result-only"],
|
||||
check=True,
|
||||
timeout=1500,
|
||||
)
|
||||
assert_glb_textures_ok(HIGH)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if not IMAGE.is_file():
|
||||
raise SystemExit(f"missing design image: {IMAGE}")
|
||||
generate_high()
|
||||
assert_glb_textures_ok(HIGH)
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(HIGH, OUT / "model.glb")
|
||||
studio.is_logged_in = lambda client_id=None: auth.is_logged_in()
|
||||
studio.load_storage_state = lambda client_id=None: (
|
||||
json.loads(auth.storage_state_path().read_text(encoding="utf-8")),
|
||||
None,
|
||||
)
|
||||
uv = OUT / "model_lowpoly_uv.glb"
|
||||
if uv.is_file():
|
||||
studio.validate_uv_glb(uv)
|
||||
update(stage="reuse studio uv", path=str(uv))
|
||||
else:
|
||||
meta_path = OUT / "hunyuan_studio.json"
|
||||
meta = json.loads(meta_path.read_text(encoding="utf-8")) if meta_path.is_file() else {}
|
||||
studio.process_existing(
|
||||
HIGH,
|
||||
OUT,
|
||||
update,
|
||||
target_faces=TARGET,
|
||||
resume_low_id=meta.get("lowpoly_works_id"),
|
||||
resume_uv_id=meta.get("uv_works_id"),
|
||||
)
|
||||
aligned = OUT / "model_lowpoly_uv_aligned.glb"
|
||||
if not aligned.is_file():
|
||||
with (OUT / "alignment.log").open("w", encoding="utf-8") as log:
|
||||
subprocess.run(
|
||||
[str(BLENDER), "-b", "--factory-startup", "--python",
|
||||
str(ROOT / "Tools/model_tools/bl_align_studio_uv.py"), "--", str(OUT)],
|
||||
stdout=log, stderr=subprocess.STDOUT, check=True, timeout=180,
|
||||
)
|
||||
baked = OUT / "model_baked.glb"
|
||||
if not baked.is_file():
|
||||
_run_blender_step(
|
||||
OUT, str(BLENDER), "blender_lowpoly_bake.py",
|
||||
(OUT / "model.glb", baked, 1, SIZE, aligned),
|
||||
"bake.log", marker="Lowpoly bake saved to",
|
||||
)
|
||||
compress_bake_maps(OUT)
|
||||
shutil.copy2(baked, OUT / "model_ex.glb")
|
||||
mesh, decimation = limit_studio_faces(OUT, TARGET, str(BLENDER), update)
|
||||
static = OUT / "static" / f"{NAME}.fbx"
|
||||
export_receipt = OUT / "static_export.json"
|
||||
export_info = json.loads(export_receipt.read_text(encoding="utf-8")) if export_receipt.is_file() else {}
|
||||
if not static.is_file() or export_info.get("fbxAxes", {}).get("exportVersion") != FBX_YUP_EXPORT_VERSION:
|
||||
with (OUT / "static_export.log").open("w", encoding="utf-8") as log:
|
||||
subprocess.run(
|
||||
[str(BLENDER), "-b", "-t", "1", "--factory-startup", "--python",
|
||||
str(ROOT / "Tools/model_tools/bl_prepare_studio_trial.py"), "--", str(OUT), NAME],
|
||||
stdout=log, stderr=subprocess.STDOUT, check=True, timeout=180,
|
||||
)
|
||||
receipt = {
|
||||
"ok": True,
|
||||
"character": NAME,
|
||||
"image": "Doc/Design/Character/LmGirl/lm_girl_tpose.jpg",
|
||||
"source": "Tools/output/lmgirl_tpose_3d/lmgirl.glb",
|
||||
"targetTriangles": TARGET,
|
||||
"textureSize": SIZE,
|
||||
"triangles": len(mesh.faces),
|
||||
"vertices": len(mesh.vertices),
|
||||
"decimation": decimation,
|
||||
"final": "Tools/output/lmgirl_tpose_studio/model_ex.glb",
|
||||
"fbx": f"Tools/output/lmgirl_tpose_studio/static/{NAME}.fbx",
|
||||
"fbxAxes": json.loads(export_receipt.read_text(encoding="utf-8")).get("fbxAxes"),
|
||||
"rig": False,
|
||||
}
|
||||
(OUT / "mesh_receipt.json").write_text(json.dumps(receipt, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
print(json.dumps(receipt, ensure_ascii=False, indent=2), flush=True)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user