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 datetimedef InitializeSdk():"""初始化SDK"""manager = FbxManager.Create()ios = FbxIOSettings.Create(manager, IOSROOT)manager.SetIOSettings(ios)return managerdef LoadFBX(manager, filename):"""加載FBX文件"""scene = FbxScene.Create(manager, "")importer = FbxImporter.Create(manager, "")if not importer.Initialize(filename, -1, manager.GetIOSettings()):return Noneif not importer.Import(scene):importer.Destroy()return Noneimporter.Destroy()return scenedef SafeCastToBlendShape(deformer):"""安全地將變形器轉換為 BlendShape 對象"""try:if hasattr(FbxBlendShape, 'Cast'):return FbxBlendShape.Cast(deformer)else:return deformerexcept Exception as e:print(f"類型轉換錯誤: {e}")return deformerdef 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_countfor 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_datadef process_blendshape_diff(file_a, file_b, output_file):"""生成HTML比較報告"""# 初始化SDKmanager = 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 Falseelse:with open(output_file, 'w', encoding='utf-8') as f:print("文件A和文件B的BlendShape數量一致\n")# in rangefor 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 Truedef 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 osdef 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 -1if 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 -1shape_keys = obj.data.shape_keys.key_blocksrenamed_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_nameprint(f"Renamed '{old_name}' to '{new_name}'")renamed_count += 1print(f"\nBatch renaming complete! Renamed {renamed_count} shape keys.")return renamed_countdef 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 = partsmap_dict[old_name] = new_nameelse:print(f"Warning: Line '{line}' is not in the expected format 'old_name new_name'. Skipping.")# 獲取場景中的所有物體all_objects = bpy.data.objectsprint("Starting batch rename for all objects in scene...")print("=" * 50)for obj in all_objects:# 獲取物體名稱,取'.'前面的部分object_name = obj.namebase_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()