Files
AIGame/Tools/greathorn_studio_trial.py
T

65 lines
4.1 KiB
Python

"""Isolated GreatHorn trial: real Hunyuan Studio lowpoly/UV, bake, conditional UV-preserving reduction."""
import sys
sys.dont_write_bytecode = True
import hashlib
import json
import shutil
from pathlib import Path
ROOT=Path(__file__).resolve().parents[1]
STUDIO=Path('D:/UD/AI/XModel/LLM3DGraphics')
OUT=ROOT/'Tools/output/enemy_studio_trial/GreatHorn'
SOURCE=ROOT/'Tools/output/enemy_3d/GreatHorn/greathorn.glb'
sys.path.insert(0,str(ROOT/'Tools'))
from hunyuan_3d import auth
sys.path.insert(0,str(STUDIO))
from backend import hunyuan_studio as studio
from backend.retopology import _run_blender_step,compress_bake_maps,limit_studio_faces
def sha(path):return hashlib.sha256(path.read_bytes()).hexdigest()
def update(**values):print(json.dumps(values,ensure_ascii=True),flush=True)
def main():
OUT.mkdir(parents=True,exist_ok=True)
receipt=OUT/'trial.json'
current=json.loads(receipt.read_text(encoding='utf-8')) if receipt.exists() else {}
digest=sha(SOURCE)
if current and current.get('sourceSha256')!=digest:raise ValueError('Trial source changed')
original=ROOT/'AIClient/Assets/Bundles/Characters/Enemy/GreatHorn'
originals=[original/'GreatHorn_Enemy.prefab',original/'GreatHorn_Animator.controller',*[original/'Clips'/f'{n}.anim' for n in ('Idle','Walk','Attack')], ROOT/'Tools/output/enemy_dgx/GreatHorn/high_rig/rigged.glb']
snapshot={str(p.relative_to(ROOT)):sha(p) for p in originals}
if current.get('originals') and current['originals']!=snapshot:raise ValueError('Original skeleton/animations changed during trial')
current.update(actor='GreatHorn',source=str(SOURCE.relative_to(ROOT)),sourceSha256=digest,targetTriangles=2000,originals=snapshot,status='studio')
receipt.write_text(json.dumps(current,indent=2),encoding='utf-8')
shutil.copy2(SOURCE,OUT/'model.glb')
# Reuse this project's existing authorised Hunyuan session through the installed
# Studio integration. Credentials stay local and are never logged or copied.
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.exists():studio.validate_uv_glb(uv)
else:
meta=json.loads((OUT/'hunyuan_studio.json').read_text(encoding='utf-8')) if (OUT/'hunyuan_studio.json').exists() else {}
studio.process_existing(SOURCE,OUT,update,target_faces=2000,resume_low_id=meta.get('lowpoly_works_id'),resume_uv_id=meta.get('uv_works_id'))
current.update(status='baking',studio=json.loads((OUT/'hunyuan_studio.json').read_text(encoding='utf-8')))
receipt.write_text(json.dumps(current,indent=2),encoding='utf-8')
blender='D:/Tools/Blender/blender-5.0.0-windows-x64/blender.exe'
aligned=OUT/'model_lowpoly_uv_aligned.glb'
if not aligned.exists():
import subprocess
with (OUT/'alignment.log').open('w',encoding='utf-8') as log:
subprocess.run([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=120)
# A completed source alignment is mandatory before the ray projection.
if not current.get('bakeAligned'):
if (OUT/'model_baked.glb').exists():shutil.copy2(OUT/'model_baked.glb',OUT/'diagnostic_unaligned_baked.glb')
_run_blender_step(OUT,blender,'blender_lowpoly_bake.py',(OUT/'model.glb',OUT/'model_baked.glb',1,1024,aligned),'bake.log',marker='Lowpoly bake saved to')
compress_bake_maps(OUT)
current['bakeAligned']=True
shutil.copy2(OUT/'model_baked.glb',OUT/'model_ex.glb')
mesh,decimation=limit_studio_faces(OUT,2000,blender,update)
current.update(status='mesh_ready',triangles=len(mesh.faces),vertices=len(mesh.vertices),decimation=decimation,bakeAlignment=json.loads((OUT/'bake_alignment.json').read_text(encoding='utf-8')),finalSha256=sha(OUT/'model_ex.glb'))
receipt.write_text(json.dumps(current,indent=2),encoding='utf-8')
update(status='mesh_ready',triangles=len(mesh.faces),decimation=decimation)
if __name__=='__main__':main()