LAM2 / generateARKITGLBWithBlender.py
ych144's picture
Upload 4 files
ef5c6d3 verified
"""
Copyright (c) 2024-2025, The Alibaba 3DAIGC Team Authors.
FLAME Model FBX/GLB Converter
A pipeline for processing FLAME 3D models including:
1. Shape parameter injection into FBX templates
2. FBX format conversion (ASCII <-> Binary)
3. GLB export via Blender
"""
import os.path
import numpy as np
import logging
import subprocess
from pathlib import Path
import trimesh
import shlex
try:
import fbx
except ImportError:
raise RuntimeError(
"FBX SDK required: https://www.autodesk.com/developer-network/platform-technologies/fbx-sdk-2020-2")
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
logger = logging.getLogger(__name__)
def update_flame_shape(
input_mesh: Path,
output_ascii_fbx: Path,
template_fbx: Path
) -> None:
"""
Injects FLAME shape parameters into FBX template
Args:
input_mesh: Path to FLAME mesh (OBJ format)
output_ascii_fbx: Output path for modified ASCII FBX
template_fbx: Template FBX with FLAME structure
Raises:
FileNotFoundError: If input files are missing
ValueError: If template format mismatch
"""
logger.info(f"Updating FLAME shape in {template_fbx}")
# Validate inputs
if not all([input_mesh.exists(), template_fbx.exists()]):
raise FileNotFoundError("Missing input file(s)")
# Load and process FLAME mesh
mesh = trimesh.load(input_mesh)
bs_verts = np.array(mesh.vertices).flatten()
verts_csv = ",".join([f"{v:.6f}" for v in bs_verts]) + ","
# Read template FBX
with template_fbx.open('r',encoding='utf-8') as f:
template_lines = f.readlines()
f.close()
# Replace vertex data section
output_lines = []
vertex_section = False
VERTEX_HEADER = "Vertices: *60054 {" # FLAME-specific vertex count
for line in template_lines:
if VERTEX_HEADER in line:
vertex_section = True
output_lines.append(line)
# Inject new vertex data
output_lines.extend([f" {v}\n" for v in verts_csv.split(",") if v])
continue
if vertex_section:
if '}' in line:
vertex_section = False
output_lines.append(line)
continue
output_lines.append(line)
# Write modified FBX
with output_ascii_fbx.open('w',encoding='utf-8') as f:
f.writelines(output_lines)
f.close()
logger.info(f"Generated updated ASCII FBX: {output_ascii_fbx}")
def convert_ascii_to_binary(
input_ascii: Path,
output_binary: Path
) -> None:
"""
Converts FBX between ASCII and Binary formats
Args:
input_ascii: Path to ASCII FBX
output_binary: Output path for binary FBX
Raises:
RuntimeError: If conversion fails
"""
logger.info(f"Converting {input_ascii} to binary FBX")
manager = fbx.FbxManager.Create()
ios = fbx.FbxIOSettings.Create(manager, fbx.IOSROOT)
manager.SetIOSettings(ios)
try:
# Initialize scene
scene = fbx.FbxScene.Create(manager, "ConversionScene")
# Import ASCII
importer = fbx.FbxImporter.Create(manager, "")
if not importer.Initialize(str(input_ascii), -1, manager.GetIOSettings()):
raise RuntimeError(f"FBX import failed: {importer.GetStatus().GetErrorString()}")
importer.Import(scene)
# Export Binary
exporter = fbx.FbxExporter.Create(manager, "")
if not exporter.Initialize(str(output_binary), 0, manager.GetIOSettings()):
raise RuntimeError(f"FBX export failed: {exporter.GetStatus().GetErrorString()}")
exporter.Export(scene)
finally:
# Cleanup FBX SDK resources
scene.Destroy()
importer.Destroy()
exporter.Destroy()
manager.Destroy()
logger.info(f"Binary FBX saved to {output_binary}")
def convert_with_blender(
input_fbx: Path,
output_glb: Path,
blender_exec: Path = Path("blender"),
input_mesh: Path = Path("input_mesh.obj"),
) -> None:
"""
Converts FBX to GLB using Blender
Args:
input_fbx: Path to input FBX
output_glb: Output GLB path
blender_exec: Path to Blender executable
Raises:
CalledProcessError: If Blender conversion fails
"""
logger.info(f"Starting Blender conversion to GLB")
print("blender_exec exist: {}".format(os.path.exists(blender_exec)))
cmd = [
str(blender_exec),
"--background",
"--python", "convertFBX2GLB.py", # Path to conversion script
"--", str(input_fbx), str(output_glb)
]
cmd_str = ' '.join(shlex.quote(arg) for arg in cmd)
print("Run {}".format(cmd_str))
# 执行命令
os.system(cmd_str)
# try:
# subprocess.run(cmd, check=True, capture_output=True, text=True, encoding='utf-8')
#
# except subprocess.CalledProcessError as e:
# logger.error(f"Blender conversion failed: {e.stderr}")
# raise
logger.info(f"GLB output saved to {output_glb}")
def gen_vertex_order_with_blender(
input_mesh: Path,
output_json: Path,
blender_exec: Path = Path("blender"),
) -> None:
"""
Args:
input_mesh: Path to input mesh
output_json: Output json path
blender_exec: Path to Blender executable
Raises:
CalledProcessError: If Blender conversion fails
"""
logger.info(f"Starting Generation Vertex Order")
cmd = [
str(blender_exec),
"--background",
"--python", "generateVertexIndices.py", # Path to conversion script
"--", str(input_mesh), str(output_json)
]
# try:
# subprocess.run(cmd, check=True, capture_output=True, text=True, encoding='utf-8')
# except subprocess.CalledProcessError as e:
# logger.error(f"Blender conversion failed: {e.stderr}")
# raise
cmd_str = ' '.join(shlex.quote(arg) for arg in cmd)
print("Run {}".format(cmd_str))
# 执行命令
os.system(cmd_str)
logger.info(f"Vertex Order output saved to {output_json}")
def generate_glb(
input_mesh: Path,
template_fbx: Path,
output_glb: Path,
blender_exec: Path = Path("blender"),
cleanup: bool = True
) -> None:
"""
Complete pipeline for FLAME GLB generation
Args:
input_mesh: Input FLAME mesh (OBJ)
template_fbx: Template FBX file
output_glb: Final GLB output
blender_exec: Blender executable path
cleanup: Remove temporary files
"""
temp_files = {
"ascii": Path("./temp_ascii.fbx"),
"binary": Path("./temp_bin.fbx")
}
try:
# Step 1: Shape parameter injection
update_flame_shape(input_mesh, temp_files["ascii"], template_fbx)
# Step 2: FBX format conversion
convert_ascii_to_binary(temp_files["ascii"], temp_files["binary"])
# Step 3: Blender conversion
convert_with_blender(temp_files["binary"], output_glb, blender_exec)
# Step 4: Vertex Order Generation
gen_vertex_order_with_blender(input_mesh,
Path(os.path.join(os.path.dirname(output_glb),'vertex_order.json')),
blender_exec)
finally:
# Cleanup temporary files
if cleanup:
for f in temp_files.values():
if f.exists():
f.unlink()
logger.info("Cleaned up temporary files")
if __name__ == "__main__":
# Example usage
generate_glb(
input_mesh=Path("./asserts/sample_oac/nature.obj"),
template_fbx=Path("./asserts/sample_oac/template_file.fbx"),
output_glb=Path("./asserts/sample_oac/skin.glb"),
blender_exec=Path("./blender-4.0.0-linux-x64/blender")
)