1) 解决增加A节点的问题
https://github.com/A-Ribeiro/CustomBlenderFBXExporter
2)找出blendshape 不一致,生成blendshape key name映射map 文件compare.txt
C:\Users\49938\Documents\DazToUnreal\zhang01\UpdatedFBX\zhang01_fix7.fbx
C:\Users\49938\Documents\DazToUnreal\zhang01\UpdatedFBX\zhang01.fbx
C:\Users\49938\Documents\DazToUnreal\zhang01\UpdatedFBX\compare.txt
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
BlendShape差异可视化报告生成器
生成HTML格式的BlendShape比较报告
"""
from fbx import *
import sys
import os
import json
from datetime import datetime
def InitializeSdk():
"""初始化SDK"""
manager = FbxManager.Create()
ios = FbxIOSettings.Create(manager, IOSROOT)
manager.SetIOSettings(ios)
return manager
def LoadFBX(manager, filename):
"""加载FBX文件"""
scene = FbxScene.Create(manager, "")
importer = FbxImporter.Create(manager, "")
if not importer.Initialize(filename, -1, manager.GetIOSettings()):
return None
if not importer.Import(scene):
importer.Destroy()
return None
importer.Destroy()
return scene
def SafeCastToBlendShape(deformer):
"""安全地将变形器转换为 BlendShape 对象"""
try:
if hasattr(FbxBlendShape, 'Cast'):
return FbxBlendShape.Cast(deformer)
else:
return deformer
except Exception as e:
print(f"类型转换错误: {e}")
return deformer
def ExtractBlendShapeKeys(scene):
"""提取场景中所有的 BlendShape Key Names"""
blendshape_data = {
'keys': [],
'meshes': {}, # 记录每个mesh的blendshape信息
'total_channels': 0
}
def TraverseNode(node):
try:
mesh = node.GetMesh()
if mesh:
mesh_name = node.GetName()
mesh_blendshapes = []
deformer_count = mesh.GetDeformerCount()
for i in range(deformer_count):
deformer = mesh.GetDeformer(i)
deformer_type = deformer.GetDeformerType()
if deformer_type == FbxDeformer.eBlendShape:
blend_shape = SafeCastToBlendShape(deformer)
try:
channel_count = blend_shape.GetBlendShapeChannelCount()
blendshape_data['total_channels'] += channel_count
for j in range(channel_count):
channel = blend_shape.GetBlendShapeChannel(j)
if channel:
channel_name = channel.GetName()
target_shape_count = channel.GetTargetShapeCount()
if target_shape_count > 0:
key_info = {
'name': channel_name,
'mesh': mesh_name,
'target_count': target_shape_count
}
blendshape_data['keys'].append(key_info)
mesh_blendshapes.append(channel_name)
except Exception as e:
print(f"处理 BlendShape 通道时出错: {e}")
if mesh_blendshapes:
blendshape_data['meshes'][mesh_name] = mesh_blendshapes
# 递归遍历子节点
for i in range(node.GetChildCount()):
TraverseNode(node.GetChild(i))
except Exception as e:
print(f"处理节点 '{node.GetName()}' 时出错: {e}")
root_node = scene.GetRootNode()
if root_node:
TraverseNode(root_node)
return blendshape_data
def process_blendshape_diff(file_a, file_b, output_file):
"""生成HTML比较报告"""
# 初始化SDK
manager = InitializeSdk()
# 加载文件
scene_a = LoadFBX(manager, file_a)
scene_b = LoadFBX(manager, file_b)
if not scene_a or not scene_b:
print("错误: 无法加载文件")
manager.Destroy()
return False
# 获取BlendShape信息
print("正在分析文件A的BlendShape...")
blendshapes_a = ExtractBlendShapeKeys(scene_a)
print("正在分析文件B的BlendShape...")
blendshapes_b = ExtractBlendShapeKeys(scene_b)
blendshapes_a['keys']
# 创建BlendShape名称列表
names_a = [key['name'] for key in blendshapes_a['keys']]
names_b = [key['name'] for key in blendshapes_b['keys']]
if len(names_a) != len(names_b):
print("❌ 错误: 文件A和文件B的BlendShape数量不一致")
manager.Destroy()
return False
else:
with open(output_file, 'w', encoding='utf-8') as f:
print("文件A和文件B的BlendShape数量一致\n")
# in range
for i in range(len(names_a)):
if names_a[i] != names_b[i]:
f.write(f"{names_a[i]}|{names_b[i]}\n")
# 清理
manager.Destroy()
return True
def main():
"""主函数"""
if len(sys.argv) < 3:
print("🎭 BlendShape差异可视化报告生成器")
print("\n📖 使用方法:")
print(" python fbx_blendshape_diff_visualizer.py <fbxA> <fbxB> [blendshape_comparison.txt]")
print("\n📋 参数:")
print(" fbxA: 第一个FBX文件")
print(" fbxB: 第二个FBX文件")
print(" output.html: 可选 ")
sys.exit(1)
file_a = sys.argv[1]
file_b = sys.argv[2]
output_file = sys.argv[3] if len(sys.argv) > 3 else "blendshape_comparison.txt"
# 检查文件
if not os.path.exists(file_a):
print(f"❌ 错误: 文件不存在 - {file_a}")
sys.exit(1)
if not os.path.exists(file_b):
print(f"❌ 错误: 文件不存在 - {file_b}")
sys.exit(1)
print("🚀 开始分析BlendShape差异...")
print(f"📁 文件A: {file_a}")
print(f"📁 文件B: {file_b}")
process_blendshape_diff(file_a, file_b, output_file)
if __name__ == "__main__":
main()
3)在blender里执行脚本修改blendshape key name
import bpy
import sys
import os
def rename_shape_keys_by_map(object_name, map_dict):
"""
批量重命名指定物体的形状键,移除指定前缀
"""
# 获取指定的物体
obj = bpy.data.objects.get(object_name)
if not obj:
print(f"Error: Object '{object_name}' not found.")
return -1
if not obj.data or not hasattr(obj.data, 'shape_keys') or not obj.data.shape_keys:
print(f"Error: Object '{object_name}' has no shape keys.")
return -1
shape_keys = obj.data.shape_keys.key_blocks
renamed_count = 0
# 从第二个key开始遍历 (跳过'Basis'基础键)
for key in shape_keys[1:]:
if key.name in map_dict:
# 保存原名称用于打印
old_name = key.name
# 获取新名称
new_name = map_dict[key.name]
# 重命名
key.name = new_name
print(f"Renamed '{old_name}' to '{new_name}'")
renamed_count += 1
print(f"\nBatch renaming complete! Renamed {renamed_count} shape keys.")
return renamed_count
def batch_rename_all_objects():
"""
遍历场景中的所有物体,对每个物体执行形状键重命名操作
对每个物体名称取'.'前面的部分作为基础名称,
然后移除'_facs_ctrl'和'_facs_bs'前缀
"""
map_path = r"C:\Users\49938\Documents\DazToUnreal\zhang01\UpdatedFBX\compare.txt"
if not os.path.exists(map_path):
print(f"Error: Map file '{map_path}' does not exist.")
return -1
# 读取文件的所有行
with open(map_path, 'r', encoding='utf-8') as file:
lines = file.readlines()
# 创建一个字典来存储旧名称和新名称的映射
map_dict = {}
for line in lines:
# 去除行首尾的空白字符
line = line.strip()
if not line:
continue # 跳过空行
# 分割行,假设格式为 "旧名称|新名称"
parts = line.split('|')
if len(parts) == 2:
old_name, new_name = parts
map_dict[old_name] = new_name
else:
print(f"Warning: Line '{line}' is not in the expected format 'old_name new_name'. Skipping.")
# 获取场景中的所有物体
all_objects = bpy.data.objects
print("Starting batch rename for all objects in scene...")
print("=" * 50)
for obj in all_objects:
# 获取物体名称,取'.'前面的部分
object_name = obj.name
base_name = object_name.split('.')[0]
print(f"\nProcessing object: '{object_name}' (base: '{base_name}')")
# 对每个物体调用两次重命名函数
rename_shape_keys_by_map(object_name, map_dict)
print("\n" + "=" * 50)
print("Batch processing complete!")
print("=" * 50)
batch_rename_all_objects()