94 lines
3.4 KiB
Python
94 lines
3.4 KiB
Python
"""CampGuide steps 02-05 from the existing Hunyuan 50k high mesh.
|
|
|
|
02 Studio low-poly, 03 Studio UV, 04 Blender bake onto those UVs,
|
|
05 UV-preserving decimation only when the baked mesh exceeds the target by 30%.
|
|
"""
|
|
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")
|
|
SOURCE = ROOT / "Tools/output/campguide_3d/campguide.glb"
|
|
OUT = ROOT / "Tools/output/campguide_studio"
|
|
TARGET = 3000
|
|
SIZE = 1024
|
|
|
|
sys.path.insert(0, str(ROOT / "Tools"))
|
|
from hunyuan_3d import auth # 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 main() -> int:
|
|
if not SOURCE.is_file():
|
|
raise SystemExit(f"missing high mesh: {SOURCE}")
|
|
OUT.mkdir(parents=True, exist_ok=True)
|
|
shutil.copy2(SOURCE, 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(
|
|
SOURCE,
|
|
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)
|
|
receipt = {
|
|
"ok": True,
|
|
"source": str(SOURCE.relative_to(ROOT)).replace("\\", "/"),
|
|
"targetTriangles": TARGET,
|
|
"textureSize": SIZE,
|
|
"triangles": len(mesh.faces),
|
|
"vertices": len(mesh.vertices),
|
|
"decimation": decimation,
|
|
"final": str((OUT / "model_ex.glb").relative_to(ROOT)).replace("\\", "/"),
|
|
}
|
|
(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())
|