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 bpy
import numpy as np 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 typing import Optional, Tuple, List, Set, Dict, Any, Generator, Callable
from ..core.logging_setup import logger from ..core.logging_setup import logger
from ..core.translations import t from ..core.translations import t
@@ -485,3 +485,61 @@ def remove_unused_shapekeys(mesh_obj: Object, tolerance: float = 0.001) -> int:
removed_count += 1 removed_count += 1
return removed_count 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... # 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 # Taken from Tuxedo/Cats
bone_names = { bone_names = {
"right_shoulder": ["rightshoulder", "shoulderr", "rshoulder", "valvebipedbip01rclavicle"], "right_shoulder": ["rightshoulder", "shoulderr", "rshoulder", "valvebipedbip01rclavicle", "右肩"],
"right_arm": ["rightarm", "armr", "rarm", "upperarmr", "rupperarm", "rightupperarm", "uparmr", "ruparm", "valvebipedbip01rupperarm"], "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_elbow": ["rightelbow", "elbowr", "relbow", "lowerarmr", "rightlowerarm", "lowerarmr", "rlowerarm", "lowarmr", "rlowarm", "forearmr", "rforearm", "valvebipedbip01rforearm", "右ひじ"],
"right_wrist": ["rightwrist", "wristr", "rwrist", "handr", "righthand", "rhand", "valvebipedbip01rhand"], "right_wrist": ["rightwrist", "wristr", "rwrist", "handr", "righthand", "rhand", "valvebipedbip01rhand", "右手首"],
"pinkie_0_r": ["littlefinger0r", "pinkie0r", "rpinkie0", "pinkiemetacarpalr", "右小指0"],
#hand l fingers "pinkie_1_r": ["littlefinger1r", "pinkie1r", "rpinkie1", "pinkieproximalr", "valvebipedbip01rfinger4", "右小指1"],
"pinkie_0_r": ["littlefinger0r","pinkie0r","rpinkie0","pinkiemetacarpalr"], "pinkie_2_r": ["littlefinger2r", "pinkie2r", "rpinkie2", "pinkieintermediater", "valvebipedbip01rfinger41", "右小指2"],
"pinkie_1_r": ["littlefinger1r","pinkie1r","rpinkie1","pinkieproximalr", "valvebipedbip01rfinger4"], "pinkie_3_r": ["littlefinger3r", "pinkie3r", "rpinkie3", "pinkiedistalr", "valvebipedbip01rfinger42", "右小指3"],
"pinkie_2_r": ["littlefinger2r","pinkie2r","rpinkie2","pinkieintermediater", "valvebipedbip01rfinger41"], "ring_0_r": ["ringfinger0r", "ring0r", "rring0", "ringmetacarpalr", "右薬指0"],
"pinkie_3_r": ["littlefinger3r","pinkie3r","rpinkie3","pinkiedistalr", "valvebipedbip01rfinger42"], "ring_1_r": ["ringfinger1r", "ring1r", "rring1", "ringproximalr", "valvebipedbip01rfinger3", "右薬指1"],
"ring_2_r": ["ringfinger2r", "ring2r", "rring2", "ringintermediater", "valvebipedbip01rfinger31", "右薬指2"],
"ring_0_r": ["ringfinger0r","ring0r","rring0","ringmetacarpalr"], "ring_3_r": ["ringfinger3r", "ring3r", "rring3", "ringdistalr", "valvebipedbip01rfinger32", "右薬指3"],
"ring_1_r": ["ringfinger1r","ring1r","rring1","ringproximalr", "valvebipedbip01rfinger3"], "middle_0_r": ["middlefinger0r", "middle0r", "rmiddle0", "middlemetacarpalr", "右中指0"],
"ring_2_r": ["ringfinger2r","ring2r","rring2","ringintermediater", "valvebipedbip01rfinger31"], "middle_1_r": ["middlefinger1r", "middle1r", "rmiddle1", "middleproximalr", "valvebipedbip01rfinger2", "右中指1"],
"ring_3_r": ["ringfinger3r","ring3r","rring3","ringdistalr", "valvebipedbip01rfinger32"], "middle_2_r": ["middlefinger2r", "middle2r", "rmiddle2", "middleintermediater", "valvebipedbip01rfinger21", "右中指2"],
"middle_3_r": ["middlefinger3r", "middle3r", "rmiddle3", "middledistalr", "valvebipedbip01rfinger22", "右中指3"],
"middle_0_r": ["middlefinger0r","middle0r","rmiddle0","middlemetacarpalr"], "index_0_r": ["indexfinger0r", "index0r", "rindex0", "indexmetacarpalr", "右人差指0"],
"middle_1_r": ["middlefinger1r","middle1r","rmiddle1","middleproximalr", "valvebipedbip01rfinger2"], "index_1_r": ["indexfinger1r", "index1r", "rindex1", "indexproximalr", "valvebipedbip01rfinger1", "右人差指1"],
"middle_2_r": ["middlefinger2r","middle2r","rmiddle2","middleintermediater", "valvebipedbip01rfinger21"], "index_2_r": ["indexfinger2r", "index2r", "rindex2", "indexintermediater", "valvebipedbip01rfinger11", "右人差指2"],
"middle_3_r": ["middlefinger3r","middle3r","rmiddle3","middledistalr", "valvebipedbip01rfinger22"], "index_3_r": ["indexfinger3r", "index3r", "rindex3", "indexdistalr", "valvebipedbip01rfinger12", "右人差指3"],
"thumb_0_r": ["thumb0r", "rthumb0", "thumbmetacarpalr", "右親指0"],
"index_0_r": ["indexfinger0r","index0r","rindex0","indexmetacarpalr"], "thumb_1_r": ["thumb1r", "rthumb1", "thumbproximalr", "valvebipedbip01rfinger0", "右親指1"],
"index_1_r": ["indexfinger1r","index1r","rindex1","indexproximalr", "valvebipedbip01rfinger1"], "thumb_2_r": ["thumb2r", "rthumb2", "thumbintermediater", "valvebipedbip01rfinger01", "右親指2"],
"index_2_r": ["indexfinger2r","index2r","rindex2","indexintermediater", "valvebipedbip01rfinger11"], "thumb_3_r": ["thumb3r", "rthumb3", "thumbdistalr", "valvebipedbip01rfinger02", "右親指3"],
"index_3_r": ["indexfinger3r","index3r","rindex3","indexdistalr", "valvebipedbip01rfinger12"], "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", "右ひざ"],
"thumb_0_r": ["thumb0r","rthumb0","thumbmetacarpalr"], "right_ankle": ["rightankle", "ankler", "rankle", "rightfoot", "footr", "rfoot", "rightfoot", "rightfeet", "feetright", "rfeet", "feetr", "valvebipedbip01rfoot", "右足首"],
"thumb_1_r": ['thumb1r',"rthumb1","thumbproximalr", "valvebipedbip01rfinger0"], "right_toe": ["righttoe", "toeright", "toer", "rtoe", "toesr", "rtoes", "valvebipedbip01rtoe0", "右つま先"],
"thumb_2_r": ['thumb2r',"rthumb2","thumbintermediater", "valvebipedbip01rfinger01"], "left_shoulder": ["leftshoulder", "shoulderl", "lshoulder", "valvebipedbip01lclavicle", "左肩"],
"thumb_3_r": ['thumb3r',"rthumb3","thumbdistalr", "valvebipedbip01rfinger02"], "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", "左ひじ"],
"right_leg": ["rightleg", "legr", "rleg", "upperlegr", "rupperleg", "thighr", "rightupperleg", "uplegr", "rupleg", "valvebipedbip01rthigh"], "left_wrist": ["leftwrist", "wristl", "lwrist", "handl", "lefthand", "lhand", "valvebipedbip01lhand", "左手首"],
"right_knee": ["rightknee", "kneer", "rknee", "lowerlegr", "calfr", "rlowerleg", "rcalf", "rightlowerleg", "lowlegr", "rlowleg", "valvebipedbip01rcalf"], "pinkie_0_l": ["pinkiefinger0l", "pinkie0l", "lpinkie0", "pinkiemetacarpall", "左小指0"],
"right_ankle": ["rightankle", "ankler", "rankle", "rightfoot", "footr", "rfoot", "rightfoot", "rightfeet", "feetright", "rfeet", "feetr", "valvebipedbip01rfoot"], "pinkie_1_l": ["littlefinger1l", "pinkie1l", "lpinkie1", "pinkieproximall", "valvebipedbip01lfinger4", "左小指1"],
"right_toe": ["righttoe", "toeright", "toer", "rtoe", "toesr", "rtoes", "valvebipedbip01rtoe0"], "pinkie_2_l": ["littlefinger2l", "pinkie2l", "lpinkie2", "pinkieintermediatel", "valvebipedbip01lfinger41", "左小指2"],
"pinkie_3_l": ["littlefinger3l", "pinkie3l", "lpinkie3", "pinkiedistall", "valvebipedbip01lfinger42", "左小指3"],
"left_shoulder": ["leftshoulder", "shoulderl", "lshoulder", "valvebipedbip01lclavicle"], "ring_0_l": ["ringfinger0l", "ring0l", "lring0", "ringmetacarpall", "左薬指0"],
"left_arm": ["leftarm", "arml", "rarm", "upperarml", "lupperarm", "leftupperarm", "uparml", "luparm", "valvebipedbip01lupperarm"], "ring_1_l": ["ringfinger1l", "ring1l", "lring1", "ringproximall", "valvebipedbip01lfinger3", "左薬指1"],
"left_elbow": ["leftelbow", "elbowl", "lelbow", "lowerarml", "leftlowerarm", "lowerarml", "llowerarm", "lowarml", "llowarm", "forearml","lforearm", "valvebipedbip01lforearm"], "ring_2_l": ["ringfinger2l", "ring2l", "lring2", "ringintermediatel", "valvebipedbip01lfinger31", "左薬指2"],
"left_wrist": ["leftwrist", "wristl", "lwrist", "handl", "lefthand", "lhand", "valvebipedbip01lhand"], "ring_3_l": ["ringfinger3l", "ring3l", "lring3", "ringdistall", "valvebipedbip01lfinger32", "左薬指3"],
"middle_0_l": ["middlefinger0l", "middle_0l", "lmiddle0", "middlemetacarpall", "左中指0"],
#hand l fingers "middle_1_l": ["middlefinger1l", "middle_1l", "lmiddle1", "middleproximall", "valvebipedbip01lfinger2", "左中指1"],
"middle_2_l": ["middlefinger2l", "middle_2l", "lmiddle2", "middleintermediatel", "valvebipedbip01lfinger21", "左中指2"],
"pinkie_0_l": ["pinkiefinger0l","pinkie0l","lpinkie0","pinkiemetacarpall"], "middle_3_l": ["middlefinger3l", "middle_3l", "lmiddle3", "middledistall", "valvebipedbip01lfinger22", "左中指3"],
"pinkie_1_l": ["littlefinger1l","pinkie1l","lpinkie1","pinkieproximall", "valvebipedbip01lfinger4"], "index_0_l": ["indexfinger0l", "index0l", "lindex0", "indexmetacarpall", "左人差指0"],
"pinkie_2_l": ["littlefinger2l","pinkie2l","lpinkie2","pinkieintermediatel", "valvebipedbip01lfinger41"], "index_1_l": ["indexfinger1l", "index1l", "lindex1", "indexproximall", "valvebipedbip01lfinger1", "左人差指1"],
"pinkie_3_l": ["littlefinger3l","pinkie3l","lpinkie3","pinkiedistall", "valvebipedbip01lfinger42"], "index_2_l": ["indexfinger2l", "index2l", "lindex2", "indexintermediatel", "valvebipedbip01lfinger11", "左人差指2"],
"index_3_l": ["indexfinger3l", "index3l", "lindex3", "indexdistall", "valvebipedbip01lfinger12", "左人差指3"],
"ring_0_l": ["ringfinger0l","ring0l","lring0","ringmetacarpall"], "thumb_0_l": ["thumb0l", "lthumb0", "thumbmetacarpall", "左親指0"],
"ring_1_l": ["ringfinger1l","ring1l","lring1","ringproximall", "valvebipedbip01lfinger3"], "thumb_1_l": ["thumb1l", "lthumb1", "thumbproximall", "valvebipedbip01lfinger0", "左親指1"],
"ring_2_l": ["ringfinger2l","ring2l","lring2","ringintermediatel", "valvebipedbip01lfinger31"], "thumb_2_l": ["thumb2l", "lthumb2", "thumbintermediatel", "valvebipedbip01lfinger01", "左親指2"],
"ring_3_l": ["ringfinger3l","ring3l","lring3","ringdistall", "valvebipedbip01lfinger32"], "thumb_3_l": ["thumb3l", "lthumb3", "thumbdistall", "valvebipedbip01lfinger02", "左親指3"],
"left_leg": ["leftleg", "legl", "lleg", "upperlegl", "lupperleg", "thighl", "leftupperleg", "uplegl", "lupleg", "valvebipedbip01lthigh", "左足"],
"middle_0_l": ["middlefinger0l","middle_0l","lmiddle0","middlemetacarpall"], "left_knee": ["leftknee", "kneel", "lknee", "lowerlegl", "llowerleg", "calfl", "lcalf", "leftlowerleg", "lowlegl", "llowleg", "valvebipedbip01lcalf", "左ひざ"],
"middle_1_l": ["middlefinger1l","middle_1l","lmiddle1","middleproximall", "valvebipedbip01lfinger2"], "left_ankle": ["leftankle", "anklel", "lankle", "leftfoot", "footl", "lfoot", "leftfoot", "leftfeet", "feetleft", "lfeet", "feetl", "valvebipedbip01lfoot", "左足首"],
"middle_2_l": ["middlefinger2l","middle_2l","lmiddle2","middleintermediatel", "valvebipedbip01lfinger21"], "left_toe": ["lefttoe", "toeleft", "toel", "ltoe", "toesl", "ltoes", "valvebipedbip01ltoe0", "左つま先"],
"middle_3_l": ["middlefinger3l","middle_3l","lmiddle3","middledistall", "valvebipedbip01lfinger22"], "hips": ["pelvis", "hips", "hip", "valvebipedbip01pelvis", ""],
"spine": ["torso", "spine", "valvebipedbip01spine", "脊椎"],
"index_0_l": ["indexfinger0l","index0l","lindex0","indexmetacarpall"], "chest": ["chest", "valvebipedbip01spine1", ""],
"index_1_l": ["indexfinger1l","index1l","lindex1","indexproximall", "valvebipedbip01lfinger1"], "upper_chest": ["upperchest", "valvebipedbip01spine4", "上胸"],
"index_2_l": ["indexfinger2l","index2l","lindex2","indexintermediatel", "valvebipedbip01lfinger11"], "neck": ["neck", "valvebipedbip01neck1", ""],
"index_3_l": ["indexfinger3l","index3l","lindex3","indexdistall", "valvebipedbip01lfinger12"], "head": ["head", "valvebipedbip01head1", ""],
"left_eye": ["eyeleft", "lefteye", "eyel", "leye", "左目"],
"thumb_0_l": ["thumb0l","lthumb0","thumbmetacarpall"], "right_eye": ["eyeright", "righteye", "eyer", "reye", "右目"],
"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"],
} }
# 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 # array taken from cats
dont_delete_these_main_bones = [ dont_delete_these_main_bones = [
'Hips', 'Spine', 'Chest', 'Upper Chest', 'Neck', 'Head', 'Hips', 'Spine', 'Chest', 'Upper Chest', 'Neck', 'Head',
@@ -166,79 +174,3 @@ resonite_translations = {
'thumb_2_r': "thumb2.R", 'thumb_2_r': "thumb2.R",
'thumb_3_r': "thumb3.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 max=1.0
) )
mmd_keep_upper_chest: BoolProperty( mmd_process_twist_bones: BoolProperty(
name=t("MMDTools.keep_upper_chest"), name=t("MMD.process_twist_bones"),
description=t("MMDTools.keep_upper_chest_desc"), description=t("MMD.process_twist_bones_desc"),
default=True default=True
) )
mmd_remove_unused_bones: BoolProperty( mmd_connect_bones: BoolProperty(
name=t("MMDTools.remove_unused"), name=t("MMD.connect_bones"),
description=t("MMDTools.remove_unused_desc"), description=t("MMD.connect_bones_desc"),
default=True default=True
) )
mmd_merge_distance: FloatProperty( save_backup_state: BoolProperty(
name=t("MMDTools.merge_distance"), name="Save Backup State",
description=t("MMDTools.merge_distance_desc"), description="Save the initial state of the armature before standardizing bones",
default=0.001, default=False
min=0.0001,
max=0.1
)
mmd_cleanup_shapekeys: BoolProperty(
name=t("MMDTools.cleanup_shapekeys"),
description=t("MMDTools.cleanup_shapekeys_desc"),
default=True
) )
def register() -> None: def register() -> None:
+431 -427
View File
@@ -1,498 +1,502 @@
import bpy import bpy
import numpy as np from typing import Tuple, Set, Dict
from typing import Set, Dict, List, Optional, Tuple from bpy.types import Operator, Context, Object
from bpy.types import Operator, Context, Object, EditBone, Mesh 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.logging_setup import logger
from ..core.translations import t from ..core.translations import t
from ..core.common import ( from ..core.dictionaries import bone_names
get_active_armature,
validate_armature,
get_all_meshes,
ProgressTracker,
transfer_vertex_weights,
remove_unused_shapekeys
)
from ..core.dictionaries import bone_names, mmd_bone_renames
class AvatarToolkit_OT_FixBoneNames(Operator): class AvatarToolkit_OT_StandardizeMMDBones(Operator):
"""Standardize and fix bone names""" bl_idname = "avatar_toolkit.mmd_standardize_bones"
bl_idname = "avatar_toolkit.fix_bone_names" bl_label = t("MMD.standardize_bones")
bl_label = t("MMDTools.fix_bone_names")
bl_description = t("MMDTools.fix_bone_names_desc")
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
@classmethod def standardize_bone_names(self, armature: Object) -> None:
def poll(cls, context: Context) -> bool: """Standardize bone names using MMD to Unity/VRChat conventions"""
armature = get_active_armature(context) for bone in armature.data.bones:
if not armature: simplified_name = simplify_bonename(bone.name)
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"""
for standard_name, variations in bone_names.items(): for standard_name, variations in bone_names.items():
if bone.name.lower() in variations: if simplified_name in variations:
bone.name = standard_name bone.name = standard_name
break break
class AvatarToolkit_OT_FixBoneHierarchy(Operator): def process_lr_bones(self, armature: Object) -> None:
"""Fix bone parenting and hierarchy""" """Process left/right bone pairs for consistency"""
bl_idname = "avatar_toolkit.fix_bone_hierarchy" for bone in armature.data.bones:
bl_label = t("MMDTools.fix_hierarchy") if bone.name.endswith(('_l', '_r', '.l', '.r', 'Left', 'Right')):
bl_description = t("MMDTools.fix_hierarchy_desc") base_name = bone.name.rsplit('_', 1)[0]
bl_options = {'REGISTER', 'UNDO'} 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 @classmethod
def poll(cls, context: Context) -> bool: def poll(cls, context: Context) -> bool:
armature = get_active_armature(context) """Check if there is an active armature in the scene"""
if not armature: return get_active_armature(context) is not None
return False
valid, _ = validate_armature(armature)
return valid
def execute(self, context: Context) -> Set[str]: def execute(self, context: Context) -> Set[str]:
try:
armature = get_active_armature(context) armature = get_active_armature(context)
with ProgressTracker(context, 3, "Fixing Bone Hierarchy") as progress: # Save initial state if enabled
bpy.ops.object.mode_set(mode='EDIT') if context.scene.avatar_toolkit.save_backup_state:
self.initial_state = save_armature_state(armature)
# Fix spine chain with ProgressTracker(context, 6, "Standardizing Bones") as progress:
self.fix_spine_chain(armature) # Step 1: Standardize bone names
progress.step("Fixed spine chain") self.standardize_bone_names(armature)
progress.step("Standardized bone names")
# Fix limb chains # Step 3: Process left/right bones
self.fix_limb_chains(armature) self.process_lr_bones(armature)
progress.step("Fixed limb chains") progress.step("Processed left/right bones")
# Fix bone orientations # Step 4: Handle name conflicts
self.fix_bone_orientations(armature) self.resolve_name_conflicts(armature)
progress.step("Fixed bone orientations") 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'} return {'FINISHED'}
def fix_spine_chain(self, armature: Object) -> None: except Exception as e:
"""Fix the spine bone chain hierarchy""" logger.error(f"Bone standardization failed: {str(e)}")
edit_bones = armature.data.edit_bones if hasattr(self, 'initial_state'):
spine_chain = ['Hips', 'Spine', 'Chest', 'Neck', 'Head'] restore_armature_state(armature, self.initial_state)
previous = None self.report({'ERROR'}, str(e))
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"))
return {'CANCELLED'} return {'CANCELLED'}
with ProgressTracker(context, len(meshes), "Fixing Bone Weights") as progress: class AvatarToolkit_OT_ProcessMMDWeights(Operator):
for mesh in meshes: bl_idname = "avatar_toolkit.mmd_process_weights"
# Clean weights bl_label = t("MMD.process_weights")
self.clean_weights(mesh, context.scene.avatar_toolkit.clean_weights_threshold) bl_options = {'REGISTER', 'UNDO'}
# Handle twist bones def merge_bone_weights(self, context: Context, mesh: Object, source: str, target: str) -> None:
if context.scene.avatar_toolkit.merge_twist_bones: """Transfer weights from source bone to target bone"""
self.process_twist_bones(mesh) transfer_vertex_weights(
mesh,
source,
target,
context.scene.avatar_toolkit.merge_weights_threshold
)
# Remove empty groups def process_eye_weights(self, context: Context, mesh: Object) -> None:
self.remove_empty_groups(mesh) """Handle special cases for eye bone weights"""
eye_bones = {
'eye_l': ['eyel', 'lefteye', 'eye.l'],
'eye_r': ['eyer', 'righteye', 'eye.r']
}
# Normalize weights for target, sources in eye_bones.items():
self.normalize_weights(mesh) 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")) twist_pairs = [
return {'FINISHED'} ('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: for twist, target in twist_pairs:
"""Remove weights below threshold""" if twist in mesh.vertex_groups:
for vertex_group in mesh.vertex_groups: self.merge_bone_weights(context, mesh, twist, target)
for vertex in mesh.data.vertices:
try: def cleanup_vertex_groups(self, context: Context, mesh: Object) -> None:
weight = vertex_group.weight(vertex.index) """Remove empty and unused vertex groups"""
if weight < threshold: threshold = context.scene.avatar_toolkit.clean_weights_threshold
vertex_group.remove([vertex.index])
except RuntimeError: # 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 continue
def process_twist_bones(self, mesh: Object) -> None: # Check if group has any weights above threshold
"""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:
has_weights = False has_weights = False
for vert in mesh.data.vertices: for vert in mesh.data.vertices:
for g in vert.groups: for group_element in vert.groups:
if g.group == group.index and g.weight > 0: if group_element.group == group.index:
if group_element.weight > threshold:
has_weights = True has_weights = True
break break
if has_weights: if has_weights:
break break
if not has_weights:
empty_groups.append(group)
for group in empty_groups: if not has_weights:
mesh.vertex_groups.remove(group) mesh.vertex_groups.remove(group)
def normalize_weights(self, mesh: Object) -> None: def merge_remaining_weights(self, context: Context, mesh: Object) -> None:
"""Normalize vertex weights""" """Process remaining weight merging cases"""
for vertex in mesh.data.vertices: # Common MMD weight merge pairs
total_weight = sum(group.weight for group in vertex.groups) merge_pairs = [
if total_weight > 0: # Finger weights
for group in vertex.groups: ('pinky', 'pinkie'),
group.weight /= total_weight ('thumb0', 'thumb_0'),
('index0', 'index_0'),
('middle0', 'middle_0'),
('ring0', 'ring_0'),
class AvatarToolkit_OT_FixMMDFeatures(Operator): # Additional arm weights
"""Fix MMD-specific features and settings""" ('upperarm', 'arm'),
bl_idname = "avatar_toolkit.fix_mmd_features" ('lowerarm', 'elbow'),
bl_label = t("MMDTools.fix_mmd_features") ('wrist', 'hand'),
bl_description = t("MMDTools.fix_mmd_features_desc")
bl_options = {'REGISTER', 'UNDO'} # 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 @classmethod
def poll(cls, context: Context) -> bool: def poll(cls, context: Context) -> bool:
armature = get_active_armature(context) """Check if there is an active armature in the scene"""
if not armature: return get_active_armature(context) is not None
return False
valid, _ = validate_armature(armature)
return valid
def execute(self, context: Context) -> Set[str]: def execute(self, context: Context) -> Set[str]:
armature = get_active_armature(context) try:
meshes = get_all_meshes(context) meshes = get_all_meshes(context)
with ProgressTracker(context, 4, "Fixing MMD Features") as progress: # Save initial state
# Process shape keys 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: for mesh in meshes:
self.process_shape_keys(mesh) # Step 1: Process eye weights
progress.step("Processed shape keys") self.process_eye_weights(context, mesh)
progress.step(f"Processed eye weights for {mesh.name}")
# Fix MMD shading # Step 2: Process twist bones
self.fix_mmd_shading(meshes) self.process_twist_bones(context, mesh)
progress.step("Fixed MMD shading") progress.step(f"Processed twist bones for {mesh.name}")
# Handle physics cleanup # Step 3: Merge remaining weights
self.cleanup_physics(armature) self.merge_remaining_weights(context, mesh)
progress.step("Cleaned up physics") progress.step(f"Merged weights for {mesh.name}")
# Remove unused data # Step 4: Cleanup
self.cleanup_unused_data(context) self.cleanup_vertex_groups(context, mesh)
progress.step("Cleaned up unused data") progress.step(f"Cleaned up weights for {mesh.name}")
self.report({'INFO'}, t("MMD.weights_processed"))
return {'FINISHED'} return {'FINISHED'}
def process_shape_keys(self, mesh: Object) -> None: except Exception as e:
"""Process and clean up shape keys""" logger.error(f"Weight processing failed: {str(e)}")
if not mesh.data.shape_keys: if hasattr(self, 'initial_states'):
return 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 class AvatarToolkit_OT_FixMMDHierarchy(Operator):
remove_unused_shapekeys(mesh) bl_idname = "avatar_toolkit.mmd_fix_hierarchy"
bl_label = t("MMD.fix_hierarchy")
# 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")
bl_options = {'REGISTER', 'UNDO'} 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]: def execute(self, context: Context) -> Set[str]:
try:
armature = get_active_armature(context) armature = get_active_armature(context)
with ProgressTracker(context, 4, "Advanced Bone Operations") as progress: # Save initial state
# Fix zero length bones if context.scene.avatar_toolkit.save_backup_state:
self.fix_zero_length_bones(armature) self.initial_state = save_armature_state(armature)
progress.step("Fixed zero length bones")
# Connect bones with children with ProgressTracker(context, 3, "Fixing Bone Hierarchy") as progress:
self.connect_bone_chains(armature) # Step 1: Fix bone parenting
progress.step("Connected bone chains") self.fix_bone_parenting(armature)
progress.step("Fixed bone parenting")
# Handle bone roll values # Step 2: Connect bones
self.fix_bone_rolls(armature) self.connect_bones(context, armature)
progress.step("Fixed bone rolls") 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) self.fix_bone_orientations(armature)
progress.step("Fixed bone orientations") progress.step("Fixed bone orientations")
self.report({'INFO'}, t("MMD.cleanup_completed"))
return {'FINISHED'} return {'FINISHED'}
def fix_zero_length_bones(self, armature: Object) -> None: except Exception as e:
"""Fix bones with zero length by extending them""" logger.error(f"Armature cleanup failed: {str(e)}")
min_length = 0.001 if hasattr(self, 'initial_state'):
for bone in armature.data.edit_bones: restore_armature_state(armature, self.initial_state)
length = (bone.tail - bone.head).length self.report({'ERROR'}, str(e))
if length < min_length: return {'CANCELLED'}
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)
+18 -28
View File
@@ -188,34 +188,24 @@
"Tools.shapekey_tolerance_desc": "Minimum difference to consider a shape key as used", "Tools.shapekey_tolerance_desc": "Minimum difference to consider a shape key as used",
"Tools.shapekeys_removed": "Removed {count} unused shape keys", "Tools.shapekeys_removed": "Removed {count} unused shape keys",
"MMDTools.label": "MMD Tools", "MMD.label": "MMD Tools",
"MMDTools.basic_tools": "Basic MMD Tools", "MMD.bone_standardization": "Bone Standardization",
"MMDTools.advanced_tools": "Advanced Tools", "MMD.weight_processing": "Weight Processing",
"MMDTools.settings": "MMD Settings", "MMD.hierarchy": "Bone Hierarchy",
"MMDTools.cleanup": "Cleanup Tools", "MMD.cleanup": "Cleanup",
"MMDTools.fix_bone_names": "Fix Bone Names", "MMD.no_armature": "No armature selected",
"MMDTools.fix_bone_names_desc": "Standardize and fix bone names", "MMD.no_meshes": "No meshes found",
"MMDTools.fix_hierarchy": "Fix Bone Hierarchy", "MMD.validation.rigify_unsupported": "Rigify armatures are not supported",
"MMDTools.fix_hierarchy_desc": "Fix bone parenting and hierarchy", "MMD.validation.multi_user_mesh": "Multi-user mesh detected: {mesh}",
"MMDTools.fix_weights": "Fix Bone Weights", "MMD.bones_standardized": "Bones standardized successfully",
"MMDTools.fix_weights_desc": "Clean up and normalize bone weights", "MMD.weights_processed": "Weights processed successfully",
"MMDTools.fix_mmd_features": "Fix MMD Features", "MMD.hierarchy_fixed": "Bone hierarchy fixed successfully",
"MMDTools.fix_mmd_features_desc": "Fix MMD-specific features and settings", "MMD.hierarchy_validation_warning": "Some hierarchy relationships could not be validated",
"MMDTools.advanced_bone_ops": "Advanced Bone Operations", "MMD.cleanup_completed": "Armature cleanup completed",
"MMDTools.advanced_bone_ops_desc": "Perform advanced bone fixes and cleanup", "MMD.process_twist_bones": "Process Twist Bones",
"MMDTools.keep_upper_chest": "Keep Upper Chest", "MMD.process_twist_bones_desc": "Transfer weights from twist bones to their parent bones",
"MMDTools.keep_upper_chest_desc": "Keep the upper chest bone during cleanup", "MMD.connect_bones": "Connect Bones",
"MMDTools.remove_unused": "Remove Unused Bones", "MMD.connect_bones_desc": "Connect bones in chain where appropriate",
"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",
"Settings.label": "Settings", "Settings.label": "Settings",
"Settings.language": "Language", "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 from ..core.translations import t
class AvatarToolKit_PT_MMDPanel(Panel): class AvatarToolKit_PT_MMDPanel(Panel):
"""Panel containing MMD-specific tools and operations""" """Panel containing MMD conversion and optimization tools"""
bl_label = t("MMDTools.label") bl_label = t("MMD.label")
bl_idname = "OBJECT_PT_avatar_toolkit_mmd" bl_idname = "OBJECT_PT_avatar_toolkit_mmd"
bl_space_type = 'VIEW_3D' bl_space_type = 'VIEW_3D'
bl_region_type = 'UI' bl_region_type = 'UI'
bl_category = CATEGORY_NAME bl_category = CATEGORY_NAME
bl_parent_id = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname bl_parent_id = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname
bl_order = 3 bl_order = 2
def draw(self, context: Context) -> None: def draw(self, context: Context) -> None:
"""Draw the MMD tools panel interface""" layout: UILayout = self.layout
layout = self.layout
# Basic MMD Tools Box # Bone Standardization Box
basic_box = layout.box() bone_box: UILayout = layout.box()
col = basic_box.column(align=True) col: UILayout = bone_box.column(align=True)
col.label(text=t("MMDTools.basic_tools"), icon='ARMATURE_DATA') col.label(text=t("MMD.bone_standardization"), icon='ARMATURE_DATA')
col.separator(factor=0.5) col.separator(factor=0.5)
col.operator("avatar_toolkit.fix_bone_names", icon='SORTALPHA') col.operator("avatar_toolkit.mmd_standardize_bones", icon='BONE_DATA')
col.operator("avatar_toolkit.fix_bone_hierarchy", icon='BONE_DATA')
col.operator("avatar_toolkit.fix_bone_weights", icon='GROUP_BONE')
# Advanced MMD Tools Box # Weight Processing Box
advanced_box = layout.box() weight_box: UILayout = layout.box()
col = advanced_box.column(align=True) col = weight_box.column(align=True)
col.label(text=t("MMDTools.advanced_tools"), icon='MODIFIER') col.label(text=t("MMD.weight_processing"), icon='GROUP_VERTEX')
col.separator(factor=0.5) col.separator(factor=0.5)
col.operator("avatar_toolkit.fix_mmd_features", icon='SHAPEKEY_DATA') col.operator("avatar_toolkit.mmd_process_weights", icon='WPAINT_HLT')
col.operator("avatar_toolkit.advanced_bone_ops", icon='CONSTRAINT_BONE')
# Settings Box # Hierarchy Box
settings_box = layout.box() hierarchy_box: UILayout = layout.box()
col = settings_box.column(align=True) col = hierarchy_box.column(align=True)
col.label(text=t("MMDTools.settings"), icon='PREFERENCES') col.label(text=t("MMD.hierarchy"), icon='OUTLINER')
col.separator(factor=0.5) col.separator(factor=0.5)
col.prop(context.scene.avatar_toolkit, "mmd_keep_upper_chest") col.operator("avatar_toolkit.mmd_fix_hierarchy", icon='CONSTRAINT_BONE')
col.prop(context.scene.avatar_toolkit, "mmd_remove_unused_bones")
col.prop(context.scene.avatar_toolkit, "mmd_cleanup_shapekeys")
# Cleanup Box # Cleanup Box
cleanup_box = layout.box() cleanup_box: UILayout = layout.box()
col = cleanup_box.column(align=True) 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.separator(factor=0.5)
col.operator("avatar_toolkit.cleanup_operations", icon='BRUSH_DATA') col.operator("avatar_toolkit.mmd_cleanup_armature", icon='MODIFIER')