"""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()