Re-do still hate it

This commit is contained in:
Yusarina
2024-12-08 06:52:05 +00:00
parent 3e187bd18a
commit c39f77d6d5
6 changed files with 648 additions and 678 deletions
+59 -1
View File
@@ -1,6 +1,6 @@
import bpy
import numpy as np
from bpy.types import Context, Object, Modifier, EditBone
from bpy.types import Context, Object, Modifier, EditBone, Operator
from typing import Optional, Tuple, List, Set, Dict, Any, Generator, Callable
from ..core.logging_setup import logger
from ..core.translations import t
@@ -485,3 +485,61 @@ def remove_unused_shapekeys(mesh_obj: Object, tolerance: float = 0.001) -> int:
removed_count += 1
return removed_count
def save_armature_state(armature: Object) -> Dict[str, Any]:
"""Save current armature state for recovery"""
state = {
'bones': {},
'pose': {},
'settings': {}
}
# Save bone data
for bone in armature.data.bones:
state['bones'][bone.name] = {
'head': bone.head_local.copy(),
'tail': bone.tail_local.copy(),
'roll': bone.roll,
'parent': bone.parent.name if bone.parent else None
}
# Save pose data if exists
if armature.pose:
for bone in armature.pose.bones:
state['pose'][bone.name] = {
'location': bone.location.copy(),
'rotation': bone.rotation_quaternion.copy(),
'scale': bone.scale.copy()
}
return state
def restore_armature_state(armature: Object, state: Dict[str, Any]) -> None:
"""Restore armature from saved state"""
bpy.ops.object.mode_set(mode='EDIT')
# Restore bones
for name, data in state['bones'].items():
if name in armature.data.edit_bones:
bone = armature.data.edit_bones[name]
bone.head = data['head']
bone.tail = data['tail']
bone.roll = data['roll']
# Restore parenting
for name, data in state['bones'].items():
if data['parent'] and name in armature.data.edit_bones:
bone = armature.data.edit_bones[name]
if data['parent'] in armature.data.edit_bones:
bone.parent = armature.data.edit_bones[data['parent']]
bpy.ops.object.mode_set(mode='POSE')
# Restore pose if exists
if 'pose' in state:
for name, data in state['pose'].items():
if name in armature.pose.bones:
bone = armature.pose.bones[name]
bone.location = data['location']
bone.rotation_quaternion = data['rotation']
bone.scale = data['scale']
+89 -157
View File
@@ -5,89 +5,97 @@
# Note from @989onan: Please make sure to make your names are lowercase in this array. I banged my head metaphorically till I figured that out...
# Taken from Tuxedo/Cats
bone_names = {
"right_shoulder": ["rightshoulder", "shoulderr", "rshoulder", "valvebipedbip01rclavicle"],
"right_arm": ["rightarm", "armr", "rarm", "upperarmr", "rupperarm", "rightupperarm", "uparmr", "ruparm", "valvebipedbip01rupperarm"],
"right_elbow": ["rightelbow", "elbowr", "relbow", "lowerarmr", "rightlowerarm", "lowerarmr","rlowerarm", "lowarmr", "rlowarm", "forearmr","rforearm", "valvebipedbip01rforearm"],
"right_wrist": ["rightwrist", "wristr", "rwrist", "handr", "righthand", "rhand", "valvebipedbip01rhand"],
#hand l fingers
"pinkie_0_r": ["littlefinger0r","pinkie0r","rpinkie0","pinkiemetacarpalr"],
"pinkie_1_r": ["littlefinger1r","pinkie1r","rpinkie1","pinkieproximalr", "valvebipedbip01rfinger4"],
"pinkie_2_r": ["littlefinger2r","pinkie2r","rpinkie2","pinkieintermediater", "valvebipedbip01rfinger41"],
"pinkie_3_r": ["littlefinger3r","pinkie3r","rpinkie3","pinkiedistalr", "valvebipedbip01rfinger42"],
"ring_0_r": ["ringfinger0r","ring0r","rring0","ringmetacarpalr"],
"ring_1_r": ["ringfinger1r","ring1r","rring1","ringproximalr", "valvebipedbip01rfinger3"],
"ring_2_r": ["ringfinger2r","ring2r","rring2","ringintermediater", "valvebipedbip01rfinger31"],
"ring_3_r": ["ringfinger3r","ring3r","rring3","ringdistalr", "valvebipedbip01rfinger32"],
"middle_0_r": ["middlefinger0r","middle0r","rmiddle0","middlemetacarpalr"],
"middle_1_r": ["middlefinger1r","middle1r","rmiddle1","middleproximalr", "valvebipedbip01rfinger2"],
"middle_2_r": ["middlefinger2r","middle2r","rmiddle2","middleintermediater", "valvebipedbip01rfinger21"],
"middle_3_r": ["middlefinger3r","middle3r","rmiddle3","middledistalr", "valvebipedbip01rfinger22"],
"index_0_r": ["indexfinger0r","index0r","rindex0","indexmetacarpalr"],
"index_1_r": ["indexfinger1r","index1r","rindex1","indexproximalr", "valvebipedbip01rfinger1"],
"index_2_r": ["indexfinger2r","index2r","rindex2","indexintermediater", "valvebipedbip01rfinger11"],
"index_3_r": ["indexfinger3r","index3r","rindex3","indexdistalr", "valvebipedbip01rfinger12"],
"thumb_0_r": ["thumb0r","rthumb0","thumbmetacarpalr"],
"thumb_1_r": ['thumb1r',"rthumb1","thumbproximalr", "valvebipedbip01rfinger0"],
"thumb_2_r": ['thumb2r',"rthumb2","thumbintermediater", "valvebipedbip01rfinger01"],
"thumb_3_r": ['thumb3r',"rthumb3","thumbdistalr", "valvebipedbip01rfinger02"],
"right_leg": ["rightleg", "legr", "rleg", "upperlegr", "rupperleg", "thighr", "rightupperleg", "uplegr", "rupleg", "valvebipedbip01rthigh"],
"right_knee": ["rightknee", "kneer", "rknee", "lowerlegr", "calfr", "rlowerleg", "rcalf", "rightlowerleg", "lowlegr", "rlowleg", "valvebipedbip01rcalf"],
"right_ankle": ["rightankle", "ankler", "rankle", "rightfoot", "footr", "rfoot", "rightfoot", "rightfeet", "feetright", "rfeet", "feetr", "valvebipedbip01rfoot"],
"right_toe": ["righttoe", "toeright", "toer", "rtoe", "toesr", "rtoes", "valvebipedbip01rtoe0"],
"left_shoulder": ["leftshoulder", "shoulderl", "lshoulder", "valvebipedbip01lclavicle"],
"left_arm": ["leftarm", "arml", "rarm", "upperarml", "lupperarm", "leftupperarm", "uparml", "luparm", "valvebipedbip01lupperarm"],
"left_elbow": ["leftelbow", "elbowl", "lelbow", "lowerarml", "leftlowerarm", "lowerarml", "llowerarm", "lowarml", "llowarm", "forearml","lforearm", "valvebipedbip01lforearm"],
"left_wrist": ["leftwrist", "wristl", "lwrist", "handl", "lefthand", "lhand", "valvebipedbip01lhand"],
#hand l fingers
"pinkie_0_l": ["pinkiefinger0l","pinkie0l","lpinkie0","pinkiemetacarpall"],
"pinkie_1_l": ["littlefinger1l","pinkie1l","lpinkie1","pinkieproximall", "valvebipedbip01lfinger4"],
"pinkie_2_l": ["littlefinger2l","pinkie2l","lpinkie2","pinkieintermediatel", "valvebipedbip01lfinger41"],
"pinkie_3_l": ["littlefinger3l","pinkie3l","lpinkie3","pinkiedistall", "valvebipedbip01lfinger42"],
"ring_0_l": ["ringfinger0l","ring0l","lring0","ringmetacarpall"],
"ring_1_l": ["ringfinger1l","ring1l","lring1","ringproximall", "valvebipedbip01lfinger3"],
"ring_2_l": ["ringfinger2l","ring2l","lring2","ringintermediatel", "valvebipedbip01lfinger31"],
"ring_3_l": ["ringfinger3l","ring3l","lring3","ringdistall", "valvebipedbip01lfinger32"],
"middle_0_l": ["middlefinger0l","middle_0l","lmiddle0","middlemetacarpall"],
"middle_1_l": ["middlefinger1l","middle_1l","lmiddle1","middleproximall", "valvebipedbip01lfinger2"],
"middle_2_l": ["middlefinger2l","middle_2l","lmiddle2","middleintermediatel", "valvebipedbip01lfinger21"],
"middle_3_l": ["middlefinger3l","middle_3l","lmiddle3","middledistall", "valvebipedbip01lfinger22"],
"index_0_l": ["indexfinger0l","index0l","lindex0","indexmetacarpall"],
"index_1_l": ["indexfinger1l","index1l","lindex1","indexproximall", "valvebipedbip01lfinger1"],
"index_2_l": ["indexfinger2l","index2l","lindex2","indexintermediatel", "valvebipedbip01lfinger11"],
"index_3_l": ["indexfinger3l","index3l","lindex3","indexdistall", "valvebipedbip01lfinger12"],
"thumb_0_l": ["thumb0l","lthumb0","thumbmetacarpall"],
"thumb_1_l": ['thumb1l',"lthumb1","thumbproximall", "valvebipedbip01lfinger0"],
"thumb_2_l": ['thumb2l',"lthumb2","thumbintermediatel", "valvebipedbip01lfinger01"],
"thumb_3_l": ['thumb3l',"lthumb3","thumbdistall", "valvebipedbip01lfinger02"],
"left_leg": ["leftleg", "legl", "lleg", "upperlegl", "lupperleg", "thighl", "leftupperleg", "uplegl", "lupleg", "valvebipedbip01lthigh"],
"left_knee": ["leftknee", "kneel", "lknee", "lowerlegl", "llowerleg", "calfl", "lcalf", "leftlowerleg", 'lowlegl', 'llowleg', "valvebipedbip01lcalf"],
"left_ankle": ["leftankle", "anklel", "rankle", "leftfoot", "footl", "lfoot", "leftfoot", "leftfeet", "feetleft", "lfeet", "feetl", "valvebipedbip01lfoot"],
"left_toe": ["lefttoe", "toeleft", "toel", "ltoe", "toesl", "ltoes", "valvebipedbip01ltoe0"],
"hips": ["pelvis", "hips", "hip", "valvebipedbip01pelvis"],
"spine": ["torso", "spine", "valvebipedbip01spine"],
"chest": ["chest", "valvebipedbip01spine1"],
"upper_chest": ["upperchest", "valvebipedbip01spine4"],
"neck": ["neck", "valvebipedbip01neck1"],
"head": ["head", "valvebipedbip01head1"],
"left_eye": ["eyeleft", "lefteye", "eyel", "leye"],
"right_eye": ["eyeright", "righteye", "eyer", "reye"],
"right_shoulder": ["rightshoulder", "shoulderr", "rshoulder", "valvebipedbip01rclavicle", "右肩"],
"right_arm": ["rightarm", "armr", "rarm", "upperarmr", "rupperarm", "rightupperarm", "uparmr", "ruparm", "valvebipedbip01rupperarm", "右腕"],
"right_elbow": ["rightelbow", "elbowr", "relbow", "lowerarmr", "rightlowerarm", "lowerarmr", "rlowerarm", "lowarmr", "rlowarm", "forearmr", "rforearm", "valvebipedbip01rforearm", "右ひじ"],
"right_wrist": ["rightwrist", "wristr", "rwrist", "handr", "righthand", "rhand", "valvebipedbip01rhand", "右手首"],
"pinkie_0_r": ["littlefinger0r", "pinkie0r", "rpinkie0", "pinkiemetacarpalr", "右小指0"],
"pinkie_1_r": ["littlefinger1r", "pinkie1r", "rpinkie1", "pinkieproximalr", "valvebipedbip01rfinger4", "右小指1"],
"pinkie_2_r": ["littlefinger2r", "pinkie2r", "rpinkie2", "pinkieintermediater", "valvebipedbip01rfinger41", "右小指2"],
"pinkie_3_r": ["littlefinger3r", "pinkie3r", "rpinkie3", "pinkiedistalr", "valvebipedbip01rfinger42", "右小指3"],
"ring_0_r": ["ringfinger0r", "ring0r", "rring0", "ringmetacarpalr", "右薬指0"],
"ring_1_r": ["ringfinger1r", "ring1r", "rring1", "ringproximalr", "valvebipedbip01rfinger3", "右薬指1"],
"ring_2_r": ["ringfinger2r", "ring2r", "rring2", "ringintermediater", "valvebipedbip01rfinger31", "右薬指2"],
"ring_3_r": ["ringfinger3r", "ring3r", "rring3", "ringdistalr", "valvebipedbip01rfinger32", "右薬指3"],
"middle_0_r": ["middlefinger0r", "middle0r", "rmiddle0", "middlemetacarpalr", "右中指0"],
"middle_1_r": ["middlefinger1r", "middle1r", "rmiddle1", "middleproximalr", "valvebipedbip01rfinger2", "右中指1"],
"middle_2_r": ["middlefinger2r", "middle2r", "rmiddle2", "middleintermediater", "valvebipedbip01rfinger21", "右中指2"],
"middle_3_r": ["middlefinger3r", "middle3r", "rmiddle3", "middledistalr", "valvebipedbip01rfinger22", "右中指3"],
"index_0_r": ["indexfinger0r", "index0r", "rindex0", "indexmetacarpalr", "右人差指0"],
"index_1_r": ["indexfinger1r", "index1r", "rindex1", "indexproximalr", "valvebipedbip01rfinger1", "右人差指1"],
"index_2_r": ["indexfinger2r", "index2r", "rindex2", "indexintermediater", "valvebipedbip01rfinger11", "右人差指2"],
"index_3_r": ["indexfinger3r", "index3r", "rindex3", "indexdistalr", "valvebipedbip01rfinger12", "右人差指3"],
"thumb_0_r": ["thumb0r", "rthumb0", "thumbmetacarpalr", "右親指0"],
"thumb_1_r": ["thumb1r", "rthumb1", "thumbproximalr", "valvebipedbip01rfinger0", "右親指1"],
"thumb_2_r": ["thumb2r", "rthumb2", "thumbintermediater", "valvebipedbip01rfinger01", "右親指2"],
"thumb_3_r": ["thumb3r", "rthumb3", "thumbdistalr", "valvebipedbip01rfinger02", "右親指3"],
"right_leg": ["rightleg", "legr", "rleg", "upperlegr", "rupperleg", "thighr", "rightupperleg", "uplegr", "rupleg", "valvebipedbip01rthigh", "右足"],
"right_knee": ["rightknee", "kneer", "rknee", "lowerlegr", "calfr", "rlowerleg", "rcalf", "rightlowerleg", "lowlegr", "rlowleg", "valvebipedbip01rcalf", "右ひざ"],
"right_ankle": ["rightankle", "ankler", "rankle", "rightfoot", "footr", "rfoot", "rightfoot", "rightfeet", "feetright", "rfeet", "feetr", "valvebipedbip01rfoot", "右足首"],
"right_toe": ["righttoe", "toeright", "toer", "rtoe", "toesr", "rtoes", "valvebipedbip01rtoe0", "右つま先"],
"left_shoulder": ["leftshoulder", "shoulderl", "lshoulder", "valvebipedbip01lclavicle", "左肩"],
"left_arm": ["leftarm", "arml", "larm", "upperarml", "lupperarm", "leftupperarm", "uparml", "luparm", "valvebipedbip01lupperarm", "左腕"],
"left_elbow": ["leftelbow", "elbowl", "lelbow", "lowerarml", "leftlowerarm", "lowerarml", "llowerarm", "lowarml", "llowarm", "forearml", "lforearm", "valvebipedbip01lforearm", "左ひじ"],
"left_wrist": ["leftwrist", "wristl", "lwrist", "handl", "lefthand", "lhand", "valvebipedbip01lhand", "左手首"],
"pinkie_0_l": ["pinkiefinger0l", "pinkie0l", "lpinkie0", "pinkiemetacarpall", "左小指0"],
"pinkie_1_l": ["littlefinger1l", "pinkie1l", "lpinkie1", "pinkieproximall", "valvebipedbip01lfinger4", "左小指1"],
"pinkie_2_l": ["littlefinger2l", "pinkie2l", "lpinkie2", "pinkieintermediatel", "valvebipedbip01lfinger41", "左小指2"],
"pinkie_3_l": ["littlefinger3l", "pinkie3l", "lpinkie3", "pinkiedistall", "valvebipedbip01lfinger42", "左小指3"],
"ring_0_l": ["ringfinger0l", "ring0l", "lring0", "ringmetacarpall", "左薬指0"],
"ring_1_l": ["ringfinger1l", "ring1l", "lring1", "ringproximall", "valvebipedbip01lfinger3", "左薬指1"],
"ring_2_l": ["ringfinger2l", "ring2l", "lring2", "ringintermediatel", "valvebipedbip01lfinger31", "左薬指2"],
"ring_3_l": ["ringfinger3l", "ring3l", "lring3", "ringdistall", "valvebipedbip01lfinger32", "左薬指3"],
"middle_0_l": ["middlefinger0l", "middle_0l", "lmiddle0", "middlemetacarpall", "左中指0"],
"middle_1_l": ["middlefinger1l", "middle_1l", "lmiddle1", "middleproximall", "valvebipedbip01lfinger2", "左中指1"],
"middle_2_l": ["middlefinger2l", "middle_2l", "lmiddle2", "middleintermediatel", "valvebipedbip01lfinger21", "左中指2"],
"middle_3_l": ["middlefinger3l", "middle_3l", "lmiddle3", "middledistall", "valvebipedbip01lfinger22", "左中指3"],
"index_0_l": ["indexfinger0l", "index0l", "lindex0", "indexmetacarpall", "左人差指0"],
"index_1_l": ["indexfinger1l", "index1l", "lindex1", "indexproximall", "valvebipedbip01lfinger1", "左人差指1"],
"index_2_l": ["indexfinger2l", "index2l", "lindex2", "indexintermediatel", "valvebipedbip01lfinger11", "左人差指2"],
"index_3_l": ["indexfinger3l", "index3l", "lindex3", "indexdistall", "valvebipedbip01lfinger12", "左人差指3"],
"thumb_0_l": ["thumb0l", "lthumb0", "thumbmetacarpall", "左親指0"],
"thumb_1_l": ["thumb1l", "lthumb1", "thumbproximall", "valvebipedbip01lfinger0", "左親指1"],
"thumb_2_l": ["thumb2l", "lthumb2", "thumbintermediatel", "valvebipedbip01lfinger01", "左親指2"],
"thumb_3_l": ["thumb3l", "lthumb3", "thumbdistall", "valvebipedbip01lfinger02", "左親指3"],
"left_leg": ["leftleg", "legl", "lleg", "upperlegl", "lupperleg", "thighl", "leftupperleg", "uplegl", "lupleg", "valvebipedbip01lthigh", "左足"],
"left_knee": ["leftknee", "kneel", "lknee", "lowerlegl", "llowerleg", "calfl", "lcalf", "leftlowerleg", "lowlegl", "llowleg", "valvebipedbip01lcalf", "左ひざ"],
"left_ankle": ["leftankle", "anklel", "lankle", "leftfoot", "footl", "lfoot", "leftfoot", "leftfeet", "feetleft", "lfeet", "feetl", "valvebipedbip01lfoot", "左足首"],
"left_toe": ["lefttoe", "toeleft", "toel", "ltoe", "toesl", "ltoes", "valvebipedbip01ltoe0", "左つま先"],
"hips": ["pelvis", "hips", "hip", "valvebipedbip01pelvis", ""],
"spine": ["torso", "spine", "valvebipedbip01spine", "脊椎"],
"chest": ["chest", "valvebipedbip01spine1", ""],
"upper_chest": ["upperchest", "valvebipedbip01spine4", "上胸"],
"neck": ["neck", "valvebipedbip01neck1", ""],
"head": ["head", "valvebipedbip01head1", ""],
"left_eye": ["eyeleft", "lefteye", "eyel", "leye", "左目"],
"right_eye": ["eyeright", "righteye", "eyer", "reye", "右目"],
}
# Add VRM bone name variations
bone_names.update({
'hips': bone_names['hips'] + ['j_bip_c_hips', 'j_hips', 'vrm_hips'],
'spine': bone_names['spine'] + ['j_bip_c_spine', 'j_spine', 'vrm_spine'],
'chest': bone_names['chest'] + ['j_bip_c_chest', 'j_chest', 'vrm_chest'],
'upper_chest': bone_names['upper_chest'] + ['j_bip_c_upper_chest', 'j_upper_chest', 'vrm_upperchest'],
'neck': bone_names['neck'] + ['j_bip_c_neck', 'j_neck', 'vrm_neck'],
'head': bone_names['head'] + ['j_bip_c_head', 'j_head', 'vrm_head'],
# VRM specific finger naming
'thumb_0_l': bone_names['thumb_0_l'] + ['thumb_metacarpal_l', 'j_thumb1_l'],
'index_0_l': bone_names['index_0_l'] + ['index_metacarpal_l', 'j_index1_l'],
'middle_0_l': bone_names['middle_0_l'] + ['middle_metacarpal_l', 'j_middle1_l'],
'ring_0_l': bone_names['ring_0_l'] + ['ring_metacarpal_l', 'j_ring1_l'],
'pinkie_0_l': bone_names['pinkie_0_l'] + ['little_metacarpal_l', 'j_little1_l'],
# Mirror for right side
'thumb_0_r': bone_names['thumb_0_r'] + ['thumb_metacarpal_r', 'j_thumb1_r'],
'index_0_r': bone_names['index_0_r'] + ['index_metacarpal_r', 'j_index1_r'],
'middle_0_r': bone_names['middle_0_r'] + ['middle_metacarpal_r', 'j_middle1_r'],
'ring_0_r': bone_names['ring_0_r'] + ['ring_metacarpal_r', 'j_ring1_r'],
'pinkie_0_r': bone_names['pinkie_0_r'] + ['little_metacarpal_r', 'j_little1_r']
})
# array taken from cats
dont_delete_these_main_bones = [
'Hips', 'Spine', 'Chest', 'Upper Chest', 'Neck', 'Head',
@@ -166,79 +174,3 @@ resonite_translations = {
'thumb_2_r': "thumb2.R",
'thumb_3_r': "thumb3.R"
}
mmd_bone_renames = {
# Core body
"センター": "Center",
"グルーブ": "Groove",
"": "Waist",
"上半身": "Upper Body",
"上半身2": "Upper Body 2",
"下半身": "Lower Body",
# Head
"": "Neck",
"": "Head",
"両目": "Eyes",
"左目": "Eye_L",
"右目": "Eye_R",
# Arms
"左肩": "Shoulder_L",
"左腕": "Arm_L",
"左ひじ": "Elbow_L",
"左手首": "Wrist_L",
"右肩": "Shoulder_R",
"右腕": "Arm_R",
"右ひじ": "Elbow_R",
"右手首": "Wrist_R",
# Fingers
"左親指1": "Thumb1_L",
"左親指2": "Thumb2_L",
"左人指1": "Index1_L",
"左人指2": "Index2_L",
"左人指3": "Index3_L",
"左中指1": "Middle1_L",
"左中指2": "Middle2_L",
"左中指3": "Middle3_L",
"左薬指1": "Ring1_L",
"左薬指2": "Ring2_L",
"左薬指3": "Ring3_L",
"左小指1": "Pinky1_L",
"左小指2": "Pinky2_L",
"左小指3": "Pinky3_L",
"右親指1": "Thumb1_R",
"右親指2": "Thumb2_R",
"右人指1": "Index1_R",
"右人指2": "Index2_R",
"右人指3": "Index3_R",
"右中指1": "Middle1_R",
"右中指2": "Middle2_R",
"右中指3": "Middle3_R",
"右薬指1": "Ring1_R",
"右薬指2": "Ring2_R",
"右薬指3": "Ring3_R",
"右小指1": "Pinky1_R",
"右小指2": "Pinky2_R",
"右小指3": "Pinky3_R",
# Legs
"左足": "Leg_L",
"左ひざ": "Knee_L",
"左足首": "Ankle_L",
"右足": "Leg_R",
"右ひざ": "Knee_R",
"右足首": "Ankle_R",
# Toes
"左つま先": "Toe_L",
"右つま先": "Toe_R",
# IK bones
"左足IK": "Leg_IK_L",
"右足IK": "Leg_IK_R",
"左つま先IK": "Toe_IK_L",
"右つま先IK": "Toe_IK_R"
}
+10 -18
View File
@@ -116,30 +116,22 @@ class AvatarToolkitSceneProperties(PropertyGroup):
max=1.0
)
mmd_keep_upper_chest: BoolProperty(
name=t("MMDTools.keep_upper_chest"),
description=t("MMDTools.keep_upper_chest_desc"),
mmd_process_twist_bones: BoolProperty(
name=t("MMD.process_twist_bones"),
description=t("MMD.process_twist_bones_desc"),
default=True
)
mmd_remove_unused_bones: BoolProperty(
name=t("MMDTools.remove_unused"),
description=t("MMDTools.remove_unused_desc"),
mmd_connect_bones: BoolProperty(
name=t("MMD.connect_bones"),
description=t("MMD.connect_bones_desc"),
default=True
)
mmd_merge_distance: FloatProperty(
name=t("MMDTools.merge_distance"),
description=t("MMDTools.merge_distance_desc"),
default=0.001,
min=0.0001,
max=0.1
)
mmd_cleanup_shapekeys: BoolProperty(
name=t("MMDTools.cleanup_shapekeys"),
description=t("MMDTools.cleanup_shapekeys_desc"),
default=True
save_backup_state: BoolProperty(
name="Save Backup State",
description="Save the initial state of the armature before standardizing bones",
default=False
)
def register() -> None:
+431 -427
View File
@@ -1,498 +1,502 @@
import bpy
import numpy as np
from typing import Set, Dict, List, Optional, Tuple
from bpy.types import Operator, Context, Object, EditBone, Mesh
from typing import Tuple, Set, Dict
from bpy.types import Operator, Context, Object
from mathutils import Vector
from ..core.common import (
ProgressTracker,
get_active_armature,
validate_meshes,
simplify_bonename,
duplicate_bone_chain,
save_armature_state,
restore_armature_state,
get_all_meshes,
validate_bone_hierarchy,
transfer_vertex_weights,
get_vertex_weights
)
from ..core.logging_setup import logger
from ..core.translations import t
from ..core.common import (
get_active_armature,
validate_armature,
get_all_meshes,
ProgressTracker,
transfer_vertex_weights,
remove_unused_shapekeys
)
from ..core.dictionaries import bone_names, mmd_bone_renames
from ..core.dictionaries import bone_names
class AvatarToolkit_OT_FixBoneNames(Operator):
"""Standardize and fix bone names"""
bl_idname = "avatar_toolkit.fix_bone_names"
bl_label = t("MMDTools.fix_bone_names")
bl_description = t("MMDTools.fix_bone_names_desc")
class AvatarToolkit_OT_StandardizeMMDBones(Operator):
bl_idname = "avatar_toolkit.mmd_standardize_bones"
bl_label = t("MMD.standardize_bones")
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context: Context) -> bool:
armature = get_active_armature(context)
if not armature:
return False
valid, _ = validate_armature(armature)
return valid
def execute(self, context: Context) -> Set[str]:
armature = get_active_armature(context)
with ProgressTracker(context, 3, "Fixing Bone Names") as progress:
bpy.ops.object.mode_set(mode='EDIT')
# First pass - standardize names
for bone in armature.data.edit_bones:
bone.name = self.standardize_bone_name(bone.name)
progress.step("Standardized names")
# Second pass - apply MMD mappings
for bone in armature.data.edit_bones:
if bone.name in mmd_bone_renames:
bone.name = mmd_bone_renames[bone.name]
progress.step("Applied MMD mappings")
# Third pass - fix common names
for bone in armature.data.edit_bones:
self.fix_common_names(bone)
progress.step("Fixed common names")
self.report({'INFO'}, t("MMDTools.bones_renamed"))
return {'FINISHED'}
def standardize_bone_name(self, name: str) -> str:
"""Standardize bone naming convention"""
prefixes = ['def-', 'def_', 'sk_', 'b_', 'bone_', 'mmd_']
name_lower = name.lower()
# Remove common prefixes
for prefix in prefixes:
if name_lower.startswith(prefix):
name = name[len(prefix):]
break
# Fix side indicators
name = name.replace('_l', '_L').replace('_r', '_R')
name = name.replace('.l', '_L').replace('.r', '_R')
name = name.replace('', '_L').replace('', '_R')
return name
def fix_common_names(self, bone: EditBone) -> None:
"""Fix common bone names to standard names"""
def standardize_bone_names(self, armature: Object) -> None:
"""Standardize bone names using MMD to Unity/VRChat conventions"""
for bone in armature.data.bones:
simplified_name = simplify_bonename(bone.name)
for standard_name, variations in bone_names.items():
if bone.name.lower() in variations:
if simplified_name in variations:
bone.name = standard_name
break
class AvatarToolkit_OT_FixBoneHierarchy(Operator):
"""Fix bone parenting and hierarchy"""
bl_idname = "avatar_toolkit.fix_bone_hierarchy"
bl_label = t("MMDTools.fix_hierarchy")
bl_description = t("MMDTools.fix_hierarchy_desc")
bl_options = {'REGISTER', 'UNDO'}
def process_lr_bones(self, armature: Object) -> None:
"""Process left/right bone pairs for consistency"""
for bone in armature.data.bones:
if bone.name.endswith(('_l', '_r', '.l', '.r', 'Left', 'Right')):
base_name = bone.name.rsplit('_', 1)[0]
side = '_l' if any(s in bone.name.lower() for s in ('left', '_l', '.l')) else '_r'
bone.name = f"{base_name}{side}"
def resolve_name_conflicts(self, armature: Object) -> None:
"""Handle duplicate bone names"""
used_names = set()
for bone in armature.data.bones:
base_name = bone.name
counter = 1
while bone.name in used_names:
bone.name = f"{base_name}_{counter}"
counter += 1
used_names.add(bone.name)
def process_spine_chain(self, armature: Object) -> None:
"""Process spine bones for VRChat compatibility"""
bpy.ops.object.mode_set(mode='EDIT')
edit_bones = armature.data.edit_bones
spine_bones = {
'hips': None,
'spine': None,
'chest': None,
'upper_chest': None,
'neck': None,
'head': None
}
# Map existing spine bones
for bone in edit_bones:
simplified = simplify_bonename(bone.name)
for spine_name in spine_bones.keys():
if simplified in bone_names[spine_name]:
spine_bones[spine_name] = bone
break
# Create missing spine bones
if spine_bones['spine'] and not spine_bones['chest']:
chest = edit_bones.new('chest')
chest.head = spine_bones['spine'].tail
chest.tail = spine_bones['neck'].head if spine_bones['neck'] else spine_bones['head'].head
spine_bones['chest'] = chest
# Set up spine hierarchy
if spine_bones['hips']:
for i, key in enumerate(['spine', 'chest', 'upper_chest', 'neck', 'head']):
if spine_bones[key]:
prev_key = list(spine_bones.keys())[i]
if spine_bones[prev_key]:
spine_bones[key].parent = spine_bones[prev_key]
def correct_bone_orientations(self, armature: Object) -> None:
"""Automatically correct bone orientations to align with Unity's axes"""
bpy.ops.object.mode_set(mode='EDIT')
edit_bones = armature.data.edit_bones
# Define standard orientations
orientations = {
'spine': Vector((0, 0, 1)), # Points up
'chest': Vector((0, 0, 1)),
'neck': Vector((0, 0, 1)),
'head': Vector((0, 0, 1)),
'shoulder': Vector((1, 0, 0)), # Points outward
'arm': Vector((0, -1, 0)), # Points down
'elbow': Vector((0, -1, 0)),
'leg': Vector((0, -1, 0)),
'knee': Vector((0, -1, 0)),
'foot': Vector((1, 0, 0)), # Points forward
}
for bone in edit_bones:
simplified_name = simplify_bonename(bone.name)
for bone_type, direction in orientations.items():
if bone_type in simplified_name:
# Calculate new tail position while maintaining length
length = (bone.tail - bone.head).length
bone.tail = bone.head + direction * length
break
@classmethod
def poll(cls, context: Context) -> bool:
armature = get_active_armature(context)
if not armature:
return False
valid, _ = validate_armature(armature)
return valid
"""Check if there is an active armature in the scene"""
return get_active_armature(context) is not None
def execute(self, context: Context) -> Set[str]:
try:
armature = get_active_armature(context)
with ProgressTracker(context, 3, "Fixing Bone Hierarchy") as progress:
bpy.ops.object.mode_set(mode='EDIT')
# Save initial state if enabled
if context.scene.avatar_toolkit.save_backup_state:
self.initial_state = save_armature_state(armature)
# Fix spine chain
self.fix_spine_chain(armature)
progress.step("Fixed spine chain")
with ProgressTracker(context, 6, "Standardizing Bones") as progress:
# Step 1: Standardize bone names
self.standardize_bone_names(armature)
progress.step("Standardized bone names")
# Fix limb chains
self.fix_limb_chains(armature)
progress.step("Fixed limb chains")
# Step 3: Process left/right bones
self.process_lr_bones(armature)
progress.step("Processed left/right bones")
# Fix bone orientations
self.fix_bone_orientations(armature)
progress.step("Fixed bone orientations")
# Step 4: Handle name conflicts
self.resolve_name_conflicts(armature)
progress.step("Resolved naming conflicts")
self.report({'INFO'}, t("MMDTools.hierarchy_fixed"))
# Step 5: Process spine chain
self.process_spine_chain(armature)
progress.step("Processed spine chain")
# Step 6: Correct bone orientations
self.correct_bone_orientations(armature)
progress.step("Corrected bone orientations")
self.report({'INFO'}, t("MMD.bones_standardized"))
return {'FINISHED'}
def fix_spine_chain(self, armature: Object) -> None:
"""Fix the spine bone chain hierarchy"""
edit_bones = armature.data.edit_bones
spine_chain = ['Hips', 'Spine', 'Chest', 'Neck', 'Head']
previous = None
for bone_name in spine_chain:
if bone_name in edit_bones:
bone = edit_bones[bone_name]
if previous:
bone.parent = edit_bones[previous]
previous = bone_name
def fix_limb_chains(self, armature: Object) -> None:
"""Fix arm and leg bone chains"""
edit_bones = armature.data.edit_bones
limb_chains = {
'Left': {
'arm': ['Left shoulder', 'Left arm', 'Left elbow', 'Left wrist'],
'leg': ['Left leg', 'Left knee', 'Left ankle', 'Left toe']
},
'Right': {
'arm': ['Right shoulder', 'Right arm', 'Right elbow', 'Right wrist'],
'leg': ['Right leg', 'Right knee', 'Right ankle', 'Right toe']
}
}
for side in limb_chains:
for chain in limb_chains[side].values():
previous = None
for bone_name in chain:
if bone_name in edit_bones:
bone = edit_bones[bone_name]
if previous:
bone.parent = edit_bones[previous]
previous = bone_name
def fix_bone_orientations(self, armature: Object) -> None:
"""Fix bone roll and axis orientations"""
edit_bones = armature.data.edit_bones
# Fix spine chain orientations
spine_bones = ['Hips', 'Spine', 'Chest']
for name in spine_bones:
if name in edit_bones:
bone = edit_bones[name]
bone.roll = 0
bone.tail.y = bone.head.y
# Fix arm orientations
arm_bones = ['Left arm', 'Right arm', 'Left elbow', 'Right elbow']
for name in arm_bones:
if name in edit_bones:
bone = edit_bones[name]
bone.roll = 0 if 'Left' in name else np.pi
class AvatarToolkit_OT_FixBoneWeights(Operator):
"""Fix and clean up bone weights"""
bl_idname = "avatar_toolkit.fix_bone_weights"
bl_label = t("MMDTools.fix_weights")
bl_description = t("MMDTools.fix_weights_desc")
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context: Context) -> bool:
armature = get_active_armature(context)
if not armature:
return False
valid, _ = validate_armature(armature)
return valid
def execute(self, context: Context) -> Set[str]:
armature = get_active_armature(context)
meshes = get_all_meshes(context)
if not meshes:
self.report({'WARNING'}, t("MMDTools.no_meshes"))
except Exception as e:
logger.error(f"Bone standardization failed: {str(e)}")
if hasattr(self, 'initial_state'):
restore_armature_state(armature, self.initial_state)
self.report({'ERROR'}, str(e))
return {'CANCELLED'}
with ProgressTracker(context, len(meshes), "Fixing Bone Weights") as progress:
for mesh in meshes:
# Clean weights
self.clean_weights(mesh, context.scene.avatar_toolkit.clean_weights_threshold)
class AvatarToolkit_OT_ProcessMMDWeights(Operator):
bl_idname = "avatar_toolkit.mmd_process_weights"
bl_label = t("MMD.process_weights")
bl_options = {'REGISTER', 'UNDO'}
# Handle twist bones
if context.scene.avatar_toolkit.merge_twist_bones:
self.process_twist_bones(mesh)
def merge_bone_weights(self, context: Context, mesh: Object, source: str, target: str) -> None:
"""Transfer weights from source bone to target bone"""
transfer_vertex_weights(
mesh,
source,
target,
context.scene.avatar_toolkit.merge_weights_threshold
)
# Remove empty groups
self.remove_empty_groups(mesh)
def process_eye_weights(self, context: Context, mesh: Object) -> None:
"""Handle special cases for eye bone weights"""
eye_bones = {
'eye_l': ['eyel', 'lefteye', 'eye.l'],
'eye_r': ['eyer', 'righteye', 'eye.r']
}
# Normalize weights
self.normalize_weights(mesh)
for target, sources in eye_bones.items():
for source in sources:
if source in mesh.vertex_groups:
self.merge_bone_weights(context, mesh, source, target)
progress.step(f"Processed {mesh.name}")
def process_twist_bones(self, context: Context, mesh: Object) -> None:
"""Process and merge twist bone weights"""
if not context.scene.avatar_toolkit.mmd_process_twist_bones:
return
self.report({'INFO'}, t("MMDTools.weights_fixed"))
return {'FINISHED'}
twist_pairs = [
('arm_twist_l', 'left_arm'),
('arm_twist_r', 'right_arm'),
('forearm_twist_l', 'left_elbow'),
('forearm_twist_r', 'right_elbow')
]
def clean_weights(self, mesh: Object, threshold: float) -> None:
"""Remove weights below threshold"""
for vertex_group in mesh.vertex_groups:
for vertex in mesh.data.vertices:
try:
weight = vertex_group.weight(vertex.index)
if weight < threshold:
vertex_group.remove([vertex.index])
except RuntimeError:
for twist, target in twist_pairs:
if twist in mesh.vertex_groups:
self.merge_bone_weights(context, mesh, twist, target)
def cleanup_vertex_groups(self, context: Context, mesh: Object) -> None:
"""Remove empty and unused vertex groups"""
threshold = context.scene.avatar_toolkit.clean_weights_threshold
# Get list of used bones from armature
armature = mesh.find_armature()
if not armature:
return
valid_bones = set(bone.name for bone in armature.data.bones)
# Remove unused groups
for group in mesh.vertex_groups[:]:
if group.name not in valid_bones:
mesh.vertex_groups.remove(group)
continue
def process_twist_bones(self, mesh: Object) -> None:
"""Process and merge twist bone weights"""
twist_groups = [g for g in mesh.vertex_groups if 'twist' in g.name.lower()]
for group in twist_groups:
base_name = group.name.lower().replace('twist', '').strip('_')
for target in mesh.vertex_groups:
if target.name.lower() == base_name:
transfer_vertex_weights(mesh, group.name, target.name)
break
def remove_empty_groups(self, mesh: Object) -> None:
"""Remove vertex groups with no weights"""
empty_groups = []
for group in mesh.vertex_groups:
# Check if group has any weights above threshold
has_weights = False
for vert in mesh.data.vertices:
for g in vert.groups:
if g.group == group.index and g.weight > 0:
for group_element in vert.groups:
if group_element.group == group.index:
if group_element.weight > threshold:
has_weights = True
break
if has_weights:
break
if not has_weights:
empty_groups.append(group)
for group in empty_groups:
if not has_weights:
mesh.vertex_groups.remove(group)
def normalize_weights(self, mesh: Object) -> None:
"""Normalize vertex weights"""
for vertex in mesh.data.vertices:
total_weight = sum(group.weight for group in vertex.groups)
if total_weight > 0:
for group in vertex.groups:
group.weight /= total_weight
def merge_remaining_weights(self, context: Context, mesh: Object) -> None:
"""Process remaining weight merging cases"""
# Common MMD weight merge pairs
merge_pairs = [
# Finger weights
('pinky', 'pinkie'),
('thumb0', 'thumb_0'),
('index0', 'index_0'),
('middle0', 'middle_0'),
('ring0', 'ring_0'),
class AvatarToolkit_OT_FixMMDFeatures(Operator):
"""Fix MMD-specific features and settings"""
bl_idname = "avatar_toolkit.fix_mmd_features"
bl_label = t("MMDTools.fix_mmd_features")
bl_description = t("MMDTools.fix_mmd_features_desc")
bl_options = {'REGISTER', 'UNDO'}
# Additional arm weights
('upperarm', 'arm'),
('lowerarm', 'elbow'),
('wrist', 'hand'),
# Leg weights
('upperleg', 'leg'),
('lowerleg', 'knee'),
('ankle', 'foot'),
# Spine weights
('spine1', 'chest'),
('spine2', 'upper_chest'),
]
for source, target in merge_pairs:
for suffix in ['_l', '_r', '.l', '.r']:
source_name = f"{source}{suffix}"
target_name = f"{target}{suffix}"
if source_name in mesh.vertex_groups:
self.merge_bone_weights(context, mesh, source_name, target_name)
@classmethod
def poll(cls, context: Context) -> bool:
armature = get_active_armature(context)
if not armature:
return False
valid, _ = validate_armature(armature)
return valid
"""Check if there is an active armature in the scene"""
return get_active_armature(context) is not None
def execute(self, context: Context) -> Set[str]:
armature = get_active_armature(context)
try:
meshes = get_all_meshes(context)
with ProgressTracker(context, 4, "Fixing MMD Features") as progress:
# Process shape keys
# Save initial state
if context.scene.avatar_toolkit.save_backup_state:
self.initial_states = {mesh: get_vertex_weights(mesh) for mesh in meshes}
with ProgressTracker(context, len(meshes) * 4, "Processing Weights") as progress:
for mesh in meshes:
self.process_shape_keys(mesh)
progress.step("Processed shape keys")
# Step 1: Process eye weights
self.process_eye_weights(context, mesh)
progress.step(f"Processed eye weights for {mesh.name}")
# Fix MMD shading
self.fix_mmd_shading(meshes)
progress.step("Fixed MMD shading")
# Step 2: Process twist bones
self.process_twist_bones(context, mesh)
progress.step(f"Processed twist bones for {mesh.name}")
# Handle physics cleanup
self.cleanup_physics(armature)
progress.step("Cleaned up physics")
# Step 3: Merge remaining weights
self.merge_remaining_weights(context, mesh)
progress.step(f"Merged weights for {mesh.name}")
# Remove unused data
self.cleanup_unused_data(context)
progress.step("Cleaned up unused data")
# Step 4: Cleanup
self.cleanup_vertex_groups(context, mesh)
progress.step(f"Cleaned up weights for {mesh.name}")
self.report({'INFO'}, t("MMD.weights_processed"))
return {'FINISHED'}
def process_shape_keys(self, mesh: Object) -> None:
"""Process and clean up shape keys"""
if not mesh.data.shape_keys:
return
except Exception as e:
logger.error(f"Weight processing failed: {str(e)}")
if hasattr(self, 'initial_states'):
for mesh, state in self.initial_states.items():
restore_mesh_weights_state(mesh, state)
self.report({'ERROR'}, str(e))
return {'CANCELLED'}
# Clean unused shape keys
remove_unused_shapekeys(mesh)
# Sort and rename shape keys
shape_keys = mesh.data.shape_keys.key_blocks
for key in shape_keys:
# Handle Japanese prefixes
if key.name.startswith(''):
key.name = key.name[1:]
# Handle common MMD prefixes
if key.name.startswith('表情'):
key.name = key.name[2:]
def fix_mmd_shading(self, meshes: List[Object]) -> None:
"""Fix MMD material shading settings"""
for mesh in meshes:
for material in mesh.data.materials:
if material:
material.use_backface_culling = True
material.blend_method = 'HASHED'
if material.node_tree:
for node in material.node_tree.nodes:
if node.type == 'BSDF_PRINCIPLED':
node.inputs['Alpha'].default_value = 1.0
def cleanup_physics(self, armature: Object) -> None:
"""Clean up MMD physics objects"""
physics_objects = [obj for obj in bpy.data.objects
if obj.parent == armature and
(obj.rigid_body or obj.rigid_body_constraint)]
for obj in physics_objects:
bpy.data.objects.remove(obj, do_unlink=True)
def cleanup_unused_data(self, context: Context) -> None:
"""Clean up unused MMD data"""
# Remove unused actions
for action in bpy.data.actions:
if not action.users:
bpy.data.actions.remove(action)
# Remove empty vertex groups
for mesh in get_all_meshes(context):
self.remove_empty_groups(mesh)
def remove_empty_groups(self, mesh: Object) -> None:
"""Remove empty vertex groups"""
empty_groups = []
for group in mesh.vertex_groups:
has_weights = False
for vert in mesh.data.vertices:
for g in vert.groups:
if g.group == group.index and g.weight > 0:
has_weights = True
break
if has_weights:
break
if not has_weights:
empty_groups.append(group)
for group in empty_groups:
mesh.vertex_groups.remove(group)
class AvatarToolkit_OT_AdvancedBoneOps(Operator):
"""Advanced bone operations and fixes"""
bl_idname = "avatar_toolkit.advanced_bone_ops"
bl_label = t("MMDTools.advanced_bone_ops")
bl_description = t("MMDTools.advanced_bone_ops_desc")
class AvatarToolkit_OT_FixMMDHierarchy(Operator):
bl_idname = "avatar_toolkit.mmd_fix_hierarchy"
bl_label = t("MMD.fix_hierarchy")
bl_options = {'REGISTER', 'UNDO'}
def fix_bone_parenting(self, armature: Object) -> None:
"""Fix bone parenting to match standard hierarchy"""
bpy.ops.object.mode_set(mode='EDIT')
edit_bones = armature.data.edit_bones
# Define parent-child relationships
hierarchy_map = {
'hips': ['spine', 'left_leg', 'right_leg'],
'spine': ['chest'],
'chest': ['upper_chest', 'left_shoulder', 'right_shoulder'],
'upper_chest': ['neck'],
'neck': ['head'],
'head': ['left_eye', 'right_eye'],
'left_shoulder': ['left_arm'],
'right_shoulder': ['right_arm'],
'left_arm': ['left_elbow'],
'right_arm': ['right_elbow'],
'left_elbow': ['left_wrist'],
'right_elbow': ['right_wrist'],
'left_leg': ['left_knee'],
'right_leg': ['right_knee'],
'left_knee': ['left_ankle'],
'right_knee': ['right_ankle'],
'left_ankle': ['left_toe'],
'right_ankle': ['right_toe']
}
# Apply parenting
for parent_name, children in hierarchy_map.items():
parent_bone = None
for bone in edit_bones:
if simplify_bonename(bone.name) in bone_names[parent_name]:
parent_bone = bone
break
if parent_bone:
for child_name in children:
for bone in edit_bones:
if simplify_bonename(bone.name) in bone_names[child_name]:
bone.parent = parent_bone
def connect_bones(self, context: Context, armature: Object) -> None:
"""Connect bones to their children where appropriate"""
if not context.scene.avatar_toolkit.mmd_connect_bones:
return
bpy.ops.object.mode_set(mode='EDIT')
edit_bones = armature.data.edit_bones
min_distance = context.scene.avatar_toolkit.connect_bones_min_distance
for bone in edit_bones:
if bone.children:
for child in bone.children:
# Check if bones are close enough to connect
distance = (bone.tail - child.head).length
if distance < min_distance:
bone.tail = child.head
child.use_connect = True
def validate_hierarchy(self, armature: Object) -> bool:
"""Validate final bone hierarchy"""
# Check essential parent-child relationships
essential_pairs = [
('spine', 'hips'),
('chest', 'spine'),
('neck', 'chest'),
('head', 'neck')
]
for child, parent in essential_pairs:
if not validate_bone_hierarchy(armature.data.bones, parent, child):
return False
return True
def execute(self, context: Context) -> Set[str]:
try:
armature = get_active_armature(context)
with ProgressTracker(context, 4, "Advanced Bone Operations") as progress:
# Fix zero length bones
self.fix_zero_length_bones(armature)
progress.step("Fixed zero length bones")
# Save initial state
if context.scene.avatar_toolkit.save_backup_state:
self.initial_state = save_armature_state(armature)
# Connect bones with children
self.connect_bone_chains(armature)
progress.step("Connected bone chains")
with ProgressTracker(context, 3, "Fixing Bone Hierarchy") as progress:
# Step 1: Fix bone parenting
self.fix_bone_parenting(armature)
progress.step("Fixed bone parenting")
# Handle bone roll values
self.fix_bone_rolls(armature)
progress.step("Fixed bone rolls")
# Step 2: Connect bones
self.connect_bones(context, armature)
progress.step("Connected bones")
# Fix bone orientations
# Step 3: Validate hierarchy
if not self.validate_hierarchy(armature):
self.report({'WARNING'}, t("MMD.hierarchy_validation_warning"))
progress.step("Validated hierarchy")
self.report({'INFO'}, t("MMD.hierarchy_fixed"))
return {'FINISHED'}
except Exception as e:
logger.error(f"Hierarchy fix failed: {str(e)}")
if hasattr(self, 'initial_state'):
restore_armature_state(armature, self.initial_state)
self.report({'ERROR'}, str(e))
return {'CANCELLED'}
class AvatarToolkit_OT_CleanupMMDArmature(Operator):
bl_idname = "avatar_toolkit.mmd_cleanup_armature"
bl_label = t("MMD.cleanup_armature")
bl_options = {'REGISTER', 'UNDO'}
def remove_unused_bones(self, context: Context, armature: Object) -> None:
"""Remove bones that aren't in the standard hierarchy or affecting weights"""
bpy.ops.object.mode_set(mode='EDIT')
edit_bones = armature.data.edit_bones
# Get all bones affecting vertex groups
used_bones = set()
for mesh in get_all_meshes(context):
used_bones.update(group.name for group in mesh.vertex_groups)
# Add essential bones from dictionary
essential_bones = set(bone_names.keys())
# Remove non-essential, unused bones
for bone in edit_bones[:]: # Slice to avoid modification during iteration
simplified_name = simplify_bonename(bone.name)
if (not any(simplified_name in variations for variations in bone_names.values()) and
bone.name not in used_bones):
edit_bones.remove(bone)
def fix_bone_orientations(self, armature: Object) -> None:
"""Fix bone orientations for Unity/VRChat compatibility"""
bpy.ops.object.mode_set(mode='EDIT')
edit_bones = armature.data.edit_bones
# Standard bone alignments
alignments = {
'spine': (0, 0, 1), # Points up
'chest': (0, 0, 1),
'neck': (0, 0, 1),
'head': (0, 0, 1),
'shoulder': (1, 0, 0), # Points outward
'arm': (0, -1, 0), # Points down
'elbow': (0, -1, 0),
'leg': (0, -1, 0),
'knee': (0, -1, 0),
'foot': (1, 0, 0), # Points forward
}
for bone in edit_bones:
simplified_name = simplify_bonename(bone.name)
for bone_type, direction in alignments.items():
if bone_type in simplified_name:
# Calculate new tail position while maintaining length
length = (bone.tail - bone.head).length
bone.tail = bone.head + Vector(direction) * length
break
def execute(self, context: Context) -> Set[str]:
try:
armature = get_active_armature(context)
# Save initial state
if context.scene.avatar_toolkit.save_backup_state:
self.initial_state = save_armature_state(armature)
with ProgressTracker(context, 2, "Cleaning Up Armature") as progress:
# Step 1: Remove unused bones
self.remove_unused_bones(context, armature)
progress.step("Removed unused bones")
# Step 2: Fix bone orientations
self.fix_bone_orientations(armature)
progress.step("Fixed bone orientations")
self.report({'INFO'}, t("MMD.cleanup_completed"))
return {'FINISHED'}
def fix_zero_length_bones(self, armature: Object) -> None:
"""Fix bones with zero length by extending them"""
min_length = 0.001
for bone in armature.data.edit_bones:
length = (bone.tail - bone.head).length
if length < min_length:
if bone.parent:
bone.tail = bone.head + bone.parent.vector * 0.1
else:
bone.tail.z = bone.head.z + 0.1
def connect_bone_chains(self, armature: Object) -> None:
"""Connect bones that should form chains"""
min_distance = bpy.context.scene.avatar_toolkit.connect_bones_min_distance
for bone in armature.data.edit_bones:
if len(bone.children) == 1:
child = bone.children[0]
distance = (bone.tail - child.head).length
if distance < min_distance:
child.use_connect = True
child.head = bone.tail
def fix_bone_rolls(self, armature: Object) -> None:
"""Fix bone roll values for proper orientation"""
for bone in armature.data.edit_bones:
if 'spine' in bone.name.lower() or 'chest' in bone.name.lower():
bone.roll = 0
elif 'shoulder' in bone.name.lower():
bone.roll = 0 if 'left' in bone.name.lower() else np.pi
class AvatarToolkit_OT_CleanupOperations(Operator):
"""Cleanup unused data and objects"""
bl_idname = "avatar_toolkit.cleanup_operations"
bl_label = t("MMDTools.cleanup_operations")
bl_description = t("MMDTools.cleanup_operations_desc")
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context: Context) -> Set[str]:
armature = get_active_armature(context)
with ProgressTracker(context, 4, "Cleanup Operations") as progress:
# Remove rigidbodies and joints
self.remove_physics_objects(armature)
progress.step("Removed physics objects")
# Clear unused animation data
self.clear_unused_animations(armature)
progress.step("Cleared unused animations")
# Remove empty objects
self.remove_empty_objects()
progress.step("Removed empty objects")
# Clean up collections
self.cleanup_collections(armature)
progress.step("Cleaned up collections")
return {'FINISHED'}
def remove_physics_objects(self, armature: Object) -> None:
"""Remove all physics objects and constraints"""
physics_objects = [obj for obj in bpy.data.objects
if obj.parent == armature and
(obj.rigid_body or obj.rigid_body_constraint)]
for obj in physics_objects:
bpy.data.objects.remove(obj, do_unlink=True)
def clear_unused_animations(self, armature: Object) -> None:
"""Remove unused animation data"""
if armature.animation_data:
if armature.animation_data.action and armature.animation_data.action.users == 0:
bpy.data.actions.remove(armature.animation_data.action)
# Clear unused NLA tracks
if armature.animation_data.nla_tracks:
for track in armature.animation_data.nla_tracks:
if not track.strips:
armature.animation_data.nla_tracks.remove(track)
def remove_empty_objects(self) -> None:
"""Remove empty objects from the scene"""
empty_objects = [obj for obj in bpy.data.objects
if obj.type == 'EMPTY' and not obj.children]
for obj in empty_objects:
bpy.data.objects.remove(obj, do_unlink=True)
def cleanup_collections(self, armature: Object) -> None:
"""Clean up and organize collections"""
# Remove empty collections
for collection in bpy.data.collections:
if not collection.objects and not collection.children:
bpy.data.collections.remove(collection)
# Ensure armature is in main collection
if armature.users_collection[0] != bpy.context.scene.collection:
bpy.context.scene.collection.objects.link(armature)
except Exception as e:
logger.error(f"Armature cleanup failed: {str(e)}")
if hasattr(self, 'initial_state'):
restore_armature_state(armature, self.initial_state)
self.report({'ERROR'}, str(e))
return {'CANCELLED'}
+18 -28
View File
@@ -188,34 +188,24 @@
"Tools.shapekey_tolerance_desc": "Minimum difference to consider a shape key as used",
"Tools.shapekeys_removed": "Removed {count} unused shape keys",
"MMDTools.label": "MMD Tools",
"MMDTools.basic_tools": "Basic MMD Tools",
"MMDTools.advanced_tools": "Advanced Tools",
"MMDTools.settings": "MMD Settings",
"MMDTools.cleanup": "Cleanup Tools",
"MMDTools.fix_bone_names": "Fix Bone Names",
"MMDTools.fix_bone_names_desc": "Standardize and fix bone names",
"MMDTools.fix_hierarchy": "Fix Bone Hierarchy",
"MMDTools.fix_hierarchy_desc": "Fix bone parenting and hierarchy",
"MMDTools.fix_weights": "Fix Bone Weights",
"MMDTools.fix_weights_desc": "Clean up and normalize bone weights",
"MMDTools.fix_mmd_features": "Fix MMD Features",
"MMDTools.fix_mmd_features_desc": "Fix MMD-specific features and settings",
"MMDTools.advanced_bone_ops": "Advanced Bone Operations",
"MMDTools.advanced_bone_ops_desc": "Perform advanced bone fixes and cleanup",
"MMDTools.keep_upper_chest": "Keep Upper Chest",
"MMDTools.keep_upper_chest_desc": "Keep the upper chest bone during cleanup",
"MMDTools.remove_unused": "Remove Unused Bones",
"MMDTools.remove_unused_desc": "Remove bones with no weights or influence",
"MMDTools.merge_distance": "Merge Distance",
"MMDTools.merge_distance_desc": "Distance threshold for merging vertices",
"MMDTools.cleanup_shapekeys": "Clean Shape Keys",
"MMDTools.cleanup_shapekeys_desc": "Remove unused and duplicate shape keys",
"MMDTools.bones_renamed": "Bone names standardized successfully",
"MMDTools.hierarchy_fixed": "Bone hierarchy fixed successfully",
"MMDTools.weights_fixed": "Bone weights cleaned and normalized",
"MMDTools.no_meshes": "No meshes found to process",
"MMDTools.not_mmd_model": "Selected armature is not an MMD model",
"MMD.label": "MMD Tools",
"MMD.bone_standardization": "Bone Standardization",
"MMD.weight_processing": "Weight Processing",
"MMD.hierarchy": "Bone Hierarchy",
"MMD.cleanup": "Cleanup",
"MMD.no_armature": "No armature selected",
"MMD.no_meshes": "No meshes found",
"MMD.validation.rigify_unsupported": "Rigify armatures are not supported",
"MMD.validation.multi_user_mesh": "Multi-user mesh detected: {mesh}",
"MMD.bones_standardized": "Bones standardized successfully",
"MMD.weights_processed": "Weights processed successfully",
"MMD.hierarchy_fixed": "Bone hierarchy fixed successfully",
"MMD.hierarchy_validation_warning": "Some hierarchy relationships could not be validated",
"MMD.cleanup_completed": "Armature cleanup completed",
"MMD.process_twist_bones": "Process Twist Bones",
"MMD.process_twist_bones_desc": "Transfer weights from twist bones to their parent bones",
"MMD.connect_bones": "Connect Bones",
"MMD.connect_bones_desc": "Connect bones in chain where appropriate",
"Settings.label": "Settings",
"Settings.language": "Language",
+22 -28
View File
@@ -5,48 +5,42 @@ from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
from ..core.translations import t
class AvatarToolKit_PT_MMDPanel(Panel):
"""Panel containing MMD-specific tools and operations"""
bl_label = t("MMDTools.label")
"""Panel containing MMD conversion and optimization tools"""
bl_label = t("MMD.label")
bl_idname = "OBJECT_PT_avatar_toolkit_mmd"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = CATEGORY_NAME
bl_parent_id = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname
bl_order = 3
bl_order = 2
def draw(self, context: Context) -> None:
"""Draw the MMD tools panel interface"""
layout = self.layout
layout: UILayout = self.layout
# Basic MMD Tools Box
basic_box = layout.box()
col = basic_box.column(align=True)
col.label(text=t("MMDTools.basic_tools"), icon='ARMATURE_DATA')
# Bone Standardization Box
bone_box: UILayout = layout.box()
col: UILayout = bone_box.column(align=True)
col.label(text=t("MMD.bone_standardization"), icon='ARMATURE_DATA')
col.separator(factor=0.5)
col.operator("avatar_toolkit.fix_bone_names", icon='SORTALPHA')
col.operator("avatar_toolkit.fix_bone_hierarchy", icon='BONE_DATA')
col.operator("avatar_toolkit.fix_bone_weights", icon='GROUP_BONE')
col.operator("avatar_toolkit.mmd_standardize_bones", icon='BONE_DATA')
# Advanced MMD Tools Box
advanced_box = layout.box()
col = advanced_box.column(align=True)
col.label(text=t("MMDTools.advanced_tools"), icon='MODIFIER')
# Weight Processing Box
weight_box: UILayout = layout.box()
col = weight_box.column(align=True)
col.label(text=t("MMD.weight_processing"), icon='GROUP_VERTEX')
col.separator(factor=0.5)
col.operator("avatar_toolkit.fix_mmd_features", icon='SHAPEKEY_DATA')
col.operator("avatar_toolkit.advanced_bone_ops", icon='CONSTRAINT_BONE')
col.operator("avatar_toolkit.mmd_process_weights", icon='WPAINT_HLT')
# Settings Box
settings_box = layout.box()
col = settings_box.column(align=True)
col.label(text=t("MMDTools.settings"), icon='PREFERENCES')
# Hierarchy Box
hierarchy_box: UILayout = layout.box()
col = hierarchy_box.column(align=True)
col.label(text=t("MMD.hierarchy"), icon='OUTLINER')
col.separator(factor=0.5)
col.prop(context.scene.avatar_toolkit, "mmd_keep_upper_chest")
col.prop(context.scene.avatar_toolkit, "mmd_remove_unused_bones")
col.prop(context.scene.avatar_toolkit, "mmd_cleanup_shapekeys")
col.operator("avatar_toolkit.mmd_fix_hierarchy", icon='CONSTRAINT_BONE')
# Cleanup Box
cleanup_box = layout.box()
cleanup_box: UILayout = layout.box()
col = cleanup_box.column(align=True)
col.label(text=t("MMDTools.cleanup"), icon='TRASH')
col.label(text=t("MMD.cleanup"), icon='BRUSH_DATA')
col.separator(factor=0.5)
col.operator("avatar_toolkit.cleanup_operations", icon='BRUSH_DATA')
col.operator("avatar_toolkit.mmd_cleanup_armature", icon='MODIFIER')