99 lines
3.1 KiB
Python
99 lines
3.1 KiB
Python
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.")
|
|
|