Compare commits

..

2 Commits

Author SHA1 Message Date
63e9a6fb00 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
2025-12-07 12:47:55 +08:00
d8015c348f feat(importing): Add pupil control bone tail positions and refine bone cleanup logic
- Add tail position definitions for left and right pupil control bones (cf_J_hitomi_tx_L and cf_J_hitomi_tx_R) with Z-axis offset of 0.01
- Remove IK control bones (cf_pv_*) from must_keep_bones list for C model armature cleanup, as these are not needed in subsequent KKBP steps
- Update must_keep_bones comments to clarify that IK control bones are intentionally excluded during C model processing
- Improve logging message to explicitly indicate that deleted accessory bones include IK control bones
- Add inline comments to pupil control bones in must_keep_bones list for better code documentation
2025-12-06 21:07:54 +08:00
11 changed files with 133 additions and 46 deletions

View File

@@ -170,6 +170,11 @@ class PlaceholderProperties(PropertyGroup):
description=t('animation_type_tt'),
default = False)
delete_accessory_bones : BoolProperty(
name="Delete Accessory Bones",
description=t('delete_accessory_bones_tt'),
default = False)
image_dropdown : EnumProperty(
items=(
("A", t('dark_C'), "Use Day LUT to saturate image"),
@@ -378,6 +383,20 @@ class EXTRAS_PT_panel(bpy.types.Panel):
split.label(text=t('rigify_convert'))
split.operator('kkbp.rigifyconvert', text = '', icon='MOD_ARMATURE')
col = box.column(align=True)
row = col.row(align=True)
split = row.split(align=True, factor=splitfac)
split.label(text=t('better_fbx_bones'))
split.operator('kkbp.applybetterfbxorientations', text = '', icon='BONE_DATA')
row.enabled = scene.plugin_state in ['imported'] and scene.armature_dropdown == 'C'
row = col.row(align=True)
split = row.split(align=True, factor=splitfac)
split.label(text="")
split.prop(context.scene.kkbp, "delete_accessory_bones", toggle=True, text = t('delete_accessory_bones'))
row.enabled = scene.plugin_state in ['imported'] and scene.armature_dropdown == 'C'
col = box.column(align=True)
row = col.row(align=True)
split = row.split(align=True, factor=splitfac)

View File

@@ -25,7 +25,7 @@ def reg_unreg(register_bool):
from .importing.importbuttons import kkbp_import
from .importing.modifymesh import modify_mesh
from .importing.modifyarmature import modify_armature
from .importing.modifyarmature import modify_armature, apply_better_fbx_orientations
from .importing.modifymaterial import modify_material
from .importing.postoperations import post_operations
@@ -53,6 +53,7 @@ def reg_unreg(register_bool):
from .extras.resetmaterials import reset_materials
from .extras.linkhair import link_hair
from . KKPanel import PlaceholderProperties
from . KKPanel import (
IMPORTINGHEADER_PT_panel,
@@ -88,9 +89,11 @@ def reg_unreg(register_bool):
reset_materials,
link_hair,
kkbp_import,
modify_mesh,
modify_armature,
apply_better_fbx_orientations,
modify_material,
post_operations,

Binary file not shown.

View File

@@ -258,6 +258,7 @@ better_fbx_bone_tails = {
'cf_J_Eye06_s_L': (0.0179830194, -0.0083362609, -0.0178645924),
'cf_J_Eye07_s_L': (0.0006974302, -0.0135568678, -0.0231180415),
'cf_J_Eye08_s_L': (-0.0155729428, -0.0184436664, -0.0195927024),
'cf_J_hitomi_tx_L': (0.0, 0.0, 0.01), # 左眼瞳控制骨骼
'cf_J_Eye_txdam_R': (-0.0005300008, -0.0006456077, 0.0108191594),
'cf_J_Eye_tx_R': (-0.0005300008, -0.0006456077, 0.0108191594),
'cf_J_Eye_rz_R': (-0.0018717796, -0.0143385679, -0.007591337),
@@ -270,6 +271,7 @@ better_fbx_bone_tails = {
'cf_J_Eye06_s_R': (-0.0179587901, -0.0083584338, -0.0178358555),
'cf_J_Eye07_s_R': (-0.0006720722, -0.0135397911, -0.0230915993),
'cf_J_Eye08_s_R': (0.0155643187, -0.018403694, -0.0196200199),
'cf_J_hitomi_tx_R': (0.0, 0.0, 0.01), # 右眼瞳控制骨骼
'cf_J_Mayu_ty': (1.36853e-05, 0.0106744692, 0.0),
'cf_J_Mayumoto_L': (0.0005722754, -0.0010105595, 0.0111785308),
'cf_J_Mayu_L': (0.0005722754, -0.0010105595, 0.0111785308),
@@ -344,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()
@@ -486,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):
@@ -498,20 +493,15 @@ class modify_armature(bpy.types.Operator):
c.switch(armature, 'edit')
# 定义必须保留的骨骼(即使不在 better_fbx_bone_tails 中)
# 这些是 KKBP 后续步骤需要的特殊骨骼
# 注意:在 C 模式下,不保留 IK 控制骨骼cf_pv_*
must_keep_bones = [
'cf_pv_root', # IK 根骨骼
'cf_pv_hand_L', 'cf_pv_hand_R', # 手部 IK
'cf_pv_elbo_L', 'cf_pv_elbo_R', # 肘部 IK
'cf_pv_foot_L', 'cf_pv_foot_R', # 脚部 IK
'cf_pv_knee_L', 'cf_pv_knee_R', # 膝盖 IK
'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',
'cf_J_hitomi_tx_R', # 右眼瞳控制
'cf_J_hitomi_tx_L', # 左眼瞳控制
]
# 收集所有需要删除的骨骼(不在 better_fbx_bone_tails 中且不在必须保留列表中的)
@@ -529,7 +519,7 @@ class modify_armature(bpy.types.Operator):
armature.data.edit_bones.remove(armature.data.edit_bones[bone_name])
deleted_count += 1
print(f"[KKBP] 已删除 {deleted_count} 个配饰骨骼")
print(f"[KKBP] 已删除 {deleted_count} 个配饰骨骼(包括 IK 控制骨骼)")
print(f"[KKBP] 保留了 {len(better_fbx_bone_tails)} 个核心骨骼 + {len(must_keep_bones)} 个必需骨骼")
c.switch(armature, 'object')
@@ -1211,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'''
@@ -2368,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)} 个必需骨骼")

View File

@@ -112,6 +112,11 @@ translation_dictionary = {
'rigify_convert' : "Convert for Rigify",
'rigify_convert_tt' : "Runs several scripts to convert a KKBP armature to be Rigify compatible",
'better_fbx_bones' : "Apply Better FBX Bone Orientations",
'better_fbx_bones_tt' : "Modifies bone orientations to be more natural. This makes bones extend along limbs instead of pointing upward",
'delete_accessory_bones' : "Delete Accessory Bones",
'delete_accessory_bones_tt' : "When enabled, applying bone orientations will also delete hair, clothing and other accessory bones, keeping only core body bones",
'sep_eye' : "Separate Eyes and Eyebrows",
'sep_eye_tt' : "Separates the Eyes and Eyebrows from the Body object and links the shapekeys to the Body object. Useful for when you want to make eyes or eyebrows appear through the hair using the Cryptomatte features in the compositor",
'bone_visibility' : "Show bones for current outfit",

View File

@@ -108,6 +108,11 @@ translation_dictionary = {
'rigify_convert' : "Rigifyアーマチュアに変えて",
'rigify_convert_tt' : "このボタンをクリックしたら、KKBPアーマチュアをRigifyアーマチュアに変えて",
'better_fbx_bones' : "Better FBXボーン方向を適用",
'better_fbx_bones_tt' : "ボーンの方向をより自然な方向に変更します。ボーンが上向きではなく、肢体に沿って伸びるようになります",
'delete_accessory_bones' : "アクセサリボーンを削除",
'delete_accessory_bones_tt' : "有効にすると、ボーン方向を適用する際に髪や服などのアクセサリボーンを削除し、コアボディボーンのみを保持します",
'sep_eye' : "EyesやEyebrowsやBodyのオブジェクトから別々になって",
'sep_eye_tt' : "BodyのオブジェクトからEyesやEyebrowsマテリアルを別のオブジェクトを別々にして、シェイプ キーをBodyオブジェクトにリンクされる",
'bone_visibility' : "現在の服のボーンを表示して",

View File

@@ -112,6 +112,11 @@ translation_dictionary = {
'rigify_convert' : "转换为Rigify",
'rigify_convert_tt' : "运行几个脚本将KKBP骨架转换为兼容Rigify的骨架",
'better_fbx_bones' : "应用Better FBX骨骼方向",
'better_fbx_bones_tt' : "将骨骼方向修改为更自然的方向。这会让骨骼沿着肢体方向延伸,而不是都朝上",
'delete_accessory_bones' : "删除配饰骨骼",
'delete_accessory_bones_tt' : "勾选后,应用骨骼方向时会删除头发、衣服等配饰骨骼,只保留核心人体骨骼",
'sep_eye' : "分离眼睛和眉毛",
'sep_eye_tt' : "将眼睛和眉毛从身体对象中分离出来,并将形态键链接到身体对象。当您想让眼睛或眉毛通过头发透视时,该功能非常有用",
'bone_visibility' : "更新骨骼可见性",