feat(importing): Add better FBX bone orientations operator and accessory bone deletion

- Add new `apply_better_fbx_orientations` operator as standalone UI action for applying bone orientations
- Add "Delete Accessory Bones" property and UI toggle in EXTRAS panel for optional bone cleanup
- Move bone orientation logic from `modify_armature` execute method to new dedicated operator
- Refactor accessory bone deletion into separate method callable from new operator
- Update panel UI to show better FBX bones operator with delete accessory bones toggle option
- Register new operator in `__init__.py` and export from modifyarmature module
- Update interface dictionaries (English, Japanese, Chinese) with new UI strings
- Enable better FBX orientations and accessory deletion only for Koikatsu armature (C) selection
- Add detailed logging for bone operation counts and status tracking
This commit is contained in:
2025-12-07 12:47:55 +08:00
parent d8015c348f
commit 63e9a6fb00
11 changed files with 127 additions and 37 deletions

View File

@@ -346,9 +346,6 @@ class modify_armature(bpy.types.Operator):
self.categorize_bones()
self.rename_bones_for_clarity()
self.rename_mmd_bones()
# 最后再次应用 better_fbx 骨骼方向,确保不被其他步骤覆盖
self.apply_better_fbx_bone_orientations()
self.apply_bone_widgets()
self.hide_widgets()
@@ -488,10 +485,6 @@ class modify_armature(bpy.types.Operator):
bpy.ops.armature.select_all(action='INVERT')
bpy.ops.armature.delete()
# 只在选择 Koikatsu armature (C) 时删除配饰骨骼
if bpy.context.scene.kkbp.armature_dropdown == 'C':
self.delete_accessory_bones()
c.print_timer('delete_non_height_bones')
def delete_accessory_bones(self):
@@ -1208,35 +1201,7 @@ class modify_armature(bpy.types.Operator):
armature.pose.bones[pmx_rename_dict[bone]].mmd_bone.name_j = bone
c.print_timer('rename_mmd_bones')
def apply_better_fbx_bone_orientations(self):
'''统一应用 better_fbx 骨骼方向,确保不被其他步骤覆盖 (仅在选择 Koikatsu armature 时)'''
# 只在选择 Koikatsu armature (C) 时应用 better_fbx 骨骼方向
if bpy.context.scene.kkbp.armature_dropdown != 'C':
c.print_timer('apply_better_fbx_bone_orientations (skipped)')
return
armature = c.get_armature()
c.switch(armature, 'edit')
applied_count = 0
skipped_count = 0
print("[KKBP] 应用 better_fbx 骨骼方向...")
# 遍历字典中的所有骨骼并应用方向
for bone_name, offset in better_fbx_bone_tails.items():
if bone_name in armature.data.edit_bones:
bone = armature.data.edit_bones[bone_name]
bone.tail = bone.head + Vector(offset)
applied_count += 1
else:
skipped_count += 1
print(f"[KKBP] 骨骼方向应用完成: {applied_count} 个已应用, {skipped_count} 个跳过")
print(f"[KKBP] 骨架总骨骼数: {len(armature.data.edit_bones)}")
c.switch(armature, 'object')
c.print_timer('apply_better_fbx_bone_orientations')
def visually_connect_bones(self):
'''make sure certain bones are visually connected'''
@@ -2365,3 +2330,91 @@ class modify_armature(bpy.types.Operator):
bone = c.get_armature().data.edit_bones.new(new_bone_name)
return bone
class apply_better_fbx_orientations(bpy.types.Operator):
bl_idname = "kkbp.applybetterfbxorientations"
bl_label = "应用 Better FBX 骨骼方向"
bl_description = "将骨骼方向修改为更自然的方向(基于 better_fbx 数据)。这会让骨骼沿着肢体方向延伸,而不是都朝上"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
try:
armature = c.get_armature()
if not armature:
self.report({'ERROR'}, "未找到骨架对象")
return {'CANCELLED'}
c.switch(armature, 'edit')
# 记录删除前的骨骼数量
bones_before = len(armature.data.edit_bones)
print(f"[KKBP] 操作前骨骼数量: {bones_before}")
# 如果勾选了删除配饰骨骼选项,先删除配饰骨骼
delete_option = context.scene.kkbp.delete_accessory_bones
print(f"[KKBP] 删除配饰骨骼选项: {delete_option}")
if delete_option:
print("[KKBP] 开始删除配饰骨骼...")
self.delete_accessory_bones(armature)
bones_after_delete = len(armature.data.edit_bones)
print(f"[KKBP] 删除后骨骼数量: {bones_after_delete}")
else:
print("[KKBP] 跳过删除配饰骨骼")
applied_count = 0
skipped_count = 0
print("[KKBP] 应用 better_fbx 骨骼方向...")
# 遍历字典中的所有骨骼并应用方向
for bone_name, offset in better_fbx_bone_tails.items():
if bone_name in armature.data.edit_bones:
bone = armature.data.edit_bones[bone_name]
bone.tail = bone.head + Vector(offset)
applied_count += 1
else:
skipped_count += 1
c.switch(armature, 'object')
self.report({'INFO'}, f"骨骼方向应用完成: {applied_count} 个已应用, {skipped_count} 个跳过")
print(f"[KKBP] 骨骼方向应用完成: {applied_count} 个已应用, {skipped_count} 个跳过")
print(f"[KKBP] 骨架总骨骼数: {len(armature.data.edit_bones)}")
return {'FINISHED'}
except Exception as error:
c.handle_error(self, error)
return {"CANCELLED"}
def delete_accessory_bones(self, armature):
'''删除不在 better_fbx_bone_tails 字典中的骨骼,只保留核心人体骨骼(仅用于 C 模式)'''
# 定义必须保留的骨骼(即使不在 better_fbx_bone_tails 中)
must_keep_bones = [
'cf_hit_head', # 头部碰撞
'p_cf_body_bone', # 身体骨骼
'p_cf_head_bone', # 头部骨骼
'a_n_back', # 背部锚点
'a_n_headside', # 头部侧面锚点
'cf_J_hitomi_tx_R', # 右眼瞳控制
'cf_J_hitomi_tx_L', # 左眼瞳控制
]
# 收集所有需要删除的骨骼(不在 better_fbx_bone_tails 中且不在必须保留列表中的)
bones_to_delete = []
for bone in armature.data.edit_bones:
bone_name = bone.name
# 如果骨骼不在 better_fbx_bone_tails 字典中,且不在必须保留列表中,标记为删除
if bone_name not in better_fbx_bone_tails and bone_name not in must_keep_bones:
bones_to_delete.append(bone_name)
# 删除标记的骨骼
deleted_count = 0
for bone_name in bones_to_delete:
if bone_name in armature.data.edit_bones:
armature.data.edit_bones.remove(armature.data.edit_bones[bone_name])
deleted_count += 1
print(f"[KKBP] 已删除 {deleted_count} 个配饰骨骼(包括 IK 控制骨骼)")
print(f"[KKBP] 保留了 {len(better_fbx_bone_tails)} 个核心骨骼 + {len(must_keep_bones)} 个必需骨骼")