from PIL import Image from pathlib import Path import subprocess ext_dir = Path("Tools/output/explorer_3d/mixamo_extracted") target_dir = Path("AIClient/Assets/Bundles/Characters/Human/Explorer") target_dir.mkdir(parents=True, exist_ok=True) # 1. Synthesize _base.png and _mix.png print("Synthesizing PBR textures...") diffuse = Image.open(ext_dir / "diffuse.png").convert("RGBA") diffuse.save(target_dir / "Explorer_base.png") metallic = Image.open(ext_dir / "metallic.png").convert("L") roughness = Image.open(ext_dir / "roughness.png").convert("L") ao = Image.open(ext_dir / "ao.png").convert("L") mix = Image.merge("RGBA", ( metallic, roughness, ao, Image.new("L", diffuse.size, 255) )) mix.save(target_dir / "Explorer_mix.png") print("Saved Explorer_base.png and Explorer_mix.png") # 2. Blender script to inspect and ensure bottom centered, Y-up, +Z front blender_script = """ import bpy import mathutils bpy.ops.wm.read_factory_settings(use_empty=True) fbx_in = r"Tools/output/explorer_3d/mixamo_extracted/model.fbx" fbx_out = r"AIClient/Assets/Bundles/Characters/Human/Explorer/Explorer.fbx" bpy.ops.import_scene.fbx(filepath=fbx_in) # Find armature and mesh armature = None mesh_objs = [] for obj in bpy.context.scene.objects: if obj.type == 'ARMATURE': armature = obj elif obj.type == 'MESH': mesh_objs.append(obj) print("Imported armature:", armature.name if armature else "None") print("Imported meshes:", [m.name for m in mesh_objs]) # Calculate bounds of meshes min_y = float('inf') for obj in mesh_objs: for v in obj.data.vertices: world_co = obj.matrix_world @ v.co if world_co.z < min_y: # In Blender Z is up min_y = world_co.z print("Mesh lowest Z in Blender:", min_y) # Export FBX with Y-up, -Z forward in blender convention (which converts to +Z in Unity) bpy.ops.export_scene.fbx( filepath=fbx_out, use_selection=False, global_scale=1.0, apply_unit_scale=True, apply_scale_options='FBX_SCALE_NONE', bake_space_transform=False, object_types={'ARMATURE', 'MESH'}, use_mesh_modifiers=True, mesh_smooth_type='FACE', add_leaf_bones=False, primary_bone_axis='Y', secondary_bone_axis='X', axis_forward='-Z', axis_up='Y' ) print("Exported normalized Explorer.fbx") """ blender_script_path = Path("Tools/normalize_explorer_fbx.py") blender_script_path.write_text(blender_script, encoding="utf-8") blender_exe = r"C:\Program Files\Blender Foundation\Blender 5.2\blender.exe" print("Running Blender FBX normalization...") res = subprocess.run([blender_exe, "--background", "--python", str(blender_script_path)], capture_output=True, text=True) print(res.stdout[-600:]) if res.returncode != 0: print(res.stderr) raise RuntimeError("Blender export failed") # 3. Copy motion files import shutil mixamo_source = Path(r"D:\UD\AI\XModel\LLM3DGraphics\mixamo\Male") shutil.copy(mixamo_source / "Idle.fbx", target_dir / "Explorer@Idle.fbx") shutil.copy(mixamo_source / "Walking.fbx", target_dir / "Explorer@Walk.fbx") print("Copied Explorer@Idle.fbx and Explorer@Walk.fbx to target directory.")