This commit is contained in:
Yusarina
2024-12-14 01:10:24 +00:00
parent 1e0fe403aa
commit f4dc74d091
4 changed files with 720 additions and 226 deletions
+245 -65
View File
@@ -5,72 +5,252 @@
# 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", "右手首"],
"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", "薬指"],
"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", "右目"],
}
# Right side bones
"right_shoulder": [
"rightshoulder", "shoulderr", "rshoulder", "valvebipedbip01rclavicle",
"右肩", "肩.r", "肩+.r", "右肩+", "右肩", "右肩+", "肩+r", "肩+右", "ik_肩.r"
],
"right_arm": [
"rightarm", "armr", "rarm", "upperarmr", "rupperarm", "rightupperarm",
"uparmr", "ruparm", "valvebipedbip01rupperarm", "右腕", "腕.r", "右腕", "ik_腕.r"
],
"right_elbow": [
"rightelbow", "elbowr", "relbow", "lowerarmr", "rightlowerarm",
"rlowerarm", "lowarmr", "rlowarm", "forearmr", "rforearm",
"valvebipedbip01rforearm", "右ひじ", "ひじ.r", "ik_ひじ.r"
],
"right_wrist": [
"rightwrist", "wristr", "rwrist", "handr", "righthand", "rhand",
"valvebipedbip01rhand", "右手首", "手首.r", "ik_手首.r"
],
"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", "薬指"
],
"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",
"右足", "足.r", "ik_足.r"
],
"right_knee": [
"rightknee", "kneer", "rknee", "lowerlegr", "rightlowerleg",
"rlowerleg", "lowlegr", "rlowleg", "calfr", "rcalf",
"valvebipedbip01rcalf", "右ひざ", "ひざ.r", "すね.r", "ik_ひざ.r"
],
"right_ankle": [
"rightankle", "ankler", "rankle", "rightfoot", "footr", "rfoot",
"rightfeet", "feetright", "rfeet", "feetr", "valvebipedbip01rfoot",
"右足首", "足首.r", "ik_足首.r"
],
"right_toe": [
"righttoe", "toeright", "toer", "rtoe", "toesr", "rtoes",
"valvebipedbip01rtoe0", "右つま先", "つま先.r", "ik_つま先.r"
],
# Left side bones
"left_shoulder": [
"leftshoulder", "shoulderl", "lshoulder", "valvebipedbip01lclavicle",
"左肩", "肩.l", "肩+.l", "左肩+", "左肩", "左肩+", "肩+l", "肩+左", "ik_肩.l"
],
"left_arm": [
"leftarm", "arml", "larm", "upperarml", "lupperarm", "leftupperarm",
"uparml", "luparm", "valvebipedbip01lupperarm", "左腕", "腕.l", "左腕", "ik_腕.l"
],
"left_elbow": [
"leftelbow", "elbowl", "lelbow", "lowerarml", "leftlowerarm",
"llowerarm", "lowarml", "llowarm", "forearml", "lforearm",
"valvebipedbip01lforearm", "左ひじ", "ひじ.l", "すね.l", "ik_ひじ.l"
],
"left_wrist": [
"leftwrist", "wristl", "lwrist", "handl", "lefthand", "lhand",
"valvebipedbip01lhand", "左手首", "手首.l", "ik_手首.l"
],
"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",
"左足", "足.l", "ik_足.l"
],
"left_knee": [
"leftknee", "kneel", "lknee", "lowerlegl", "leftlowerleg",
"llowerleg", "lowlegl", "llowleg", "calfl", "lcalf",
"valvebipedbip01lcalf", "左ひざ", "ひざ.l", "すね.l", "ik_ひざ.l"
],
"left_ankle": [
"leftankle", "anklel", "lankle", "leftfoot", "footl", "lfoot",
"leftfeet", "feetleft", "lfeet", "feetl", "valvebipedbip01lfoot",
"左足首", "足首.l", "ik_足首.l"
],
"left_toe": [
"lefttoe", "toeleft", "toel", "ltoe", "toesl", "ltoes",
"valvebipedbip01ltoe0", "左つま先", "つま先.l", "ik_つま先.l"
],
# Central bones
"hips": [
"pelvis", "hips", "hip", "valvebipedbip01pelvis", "", "ik_腰"
],
"spine": [
"torso", "spine", "valvebipedbip01spine", "脊椎", "ik_脊椎"
],
"chest": [
"chest", "valvebipedbip01spine1", "", "ik_胸"
],
"upper_chest": [
"upperchest", "valvebipedbip01spine4", "上胸", "ik_上胸"
],
"neck": [
"neck", "valvebipedbip01neck1", "", "ik_首"
],
"head": [
"head", "valvebipedbip01head1", "", "ik_頭"
],
"left_eye": [
"eyeleft", "lefteye", "eyel", "leye", "左目", "ik_左目"
],
"right_eye": [
"eyeright", "righteye", "eyer", "reye", "右目", "ik_右目"
],
}
# Add VRM bone name variations
bone_names.update({
+444 -153
View File
@@ -8,10 +8,11 @@ from ..core.common import (
get_active_armature,
validate_armature,
get_vertex_weights,
transfer_vertex_weights
transfer_vertex_weights,
get_all_meshes
)
from ..core.translations import t
from ..core.dictionaries import bone_names
from ..core.dictionaries import bone_names, dont_delete_these_main_bones
class AVATAR_TOOLKIT_OT_StandardizeMmd(Operator):
"""MMD Bone standardization system"""
@@ -60,98 +61,61 @@ class AVATAR_TOOLKIT_OT_StandardizeMmd(Operator):
self.report({'ERROR'}, str(e))
return {'CANCELLED'}
def standardize_armature(self) -> Tuple[bool, str]:
"""Main standardization process"""
if not self.armature:
return False, t("MMD.no_armature")
try:
with ProgressTracker(self.context, 5, "MMD Standardization") as progress:
# Step 1: Process bone names
self.process_bone_names()
progress.step("Processed bone names")
# Step 2: Fix bone structure
self.fix_bone_structure()
progress.step("Fixed bone structure")
# Step 3: Process weights
self.process_weights()
progress.step("Processed weights")
# Step 4: Clean up
self.cleanup_armature()
progress.step("Cleaned up armature")
# Step 5: Final validation
self.validate_results()
progress.step("Validated results")
return True, t("MMD.standardization_complete")
except Exception as e:
logger.error(f"MMD Standardization failed: {str(e)}")
return False, str(e)
def process_bone_names(self, context: Context) -> None:
"""Process and standardize bone names"""
bpy.ops.object.mode_set(mode='EDIT')
edit_bones = self.armature.data.edit_bones
# First pass - handle IK bones
ik_bones = [bone for bone in edit_bones if 'IK' in bone.name or 'IK' in bone.name]
for bone in ik_bones:
new_name = f"ik_{self.standardize_bone_name(bone.name.replace('IK', '').replace('IK', ''))}"
self.bone_mapping[bone.name] = new_name
bone.name = new_name
# Second pass - standard bones
for bone in edit_bones:
new_name = self.standardize_bone_name(bone.name)
if new_name != bone.name:
self.bone_mapping[bone.name] = new_name
bone.name = new_name
if bone not in ik_bones:
new_name = self.standardize_bone_name(bone.name)
if new_name != bone.name:
self.bone_mapping[bone.name] = new_name
bone.name = new_name
def translate_japanese_bone_name(self, name: str) -> str:
"""Translate Japanese bone names to English standardized names"""
from ..core.dictionaries import bone_names
# Convert to lowercase for matching
name_lower = name.lower()
# Check each bone category for Japanese character matches
for bone_category, variations in bone_names.items():
for variation in variations:
if variation in name_lower:
# If Japanese characters are found, return the standardized name
return bone_category
# If no match found, return original name
return name
def standardize_bone_name(self, name: str) -> str:
"""Standardize individual bone names"""
# First translate Japanese names
result = self.translate_japanese_bone_name(name)
# Remove common prefixes
prefixes = ['ValveBiped_', 'Bip01_', 'MMD_', 'Armature|']
for prefix in prefixes:
if result.lower().startswith(prefix.lower()):
result = result[len(prefix):]
# Handle left/right conventions
if result.endswith('_L') or result.endswith('.L'):
result = f"{result[:-2]}.L"
elif result.endswith('_R') or result.endswith('.R'):
result = f"{result[:-2]}.R"
return result
return result
def fix_bone_structure(self, context: Context) -> None:
"""Fix bone hierarchy and orientations"""
bpy.ops.object.mode_set(mode='EDIT')
edit_bones = self.armature.data.edit_bones
# Process spine hierarchy
self.process_spine_chain(context)
# Fix bone orientations
self.fix_bone_orientations(context)
# Connect appropriate bones
self.connect_bones(context)
def process_weights(self, context: Context) -> None:
@@ -167,13 +131,8 @@ class AVATAR_TOOLKIT_OT_StandardizeMmd(Operator):
def cleanup_armature(self, context: Context) -> None:
"""Perform final cleanup operations"""
# Remove unused bones
self.remove_unused_bones(context)
# Clean up constraints
self.cleanup_constraints(context)
# Fix zero-length bones
self.fix_zero_length_bones(context)
def get_associated_meshes(self, context: Context) -> List[Object]:
@@ -184,6 +143,7 @@ class AVATAR_TOOLKIT_OT_StandardizeMmd(Operator):
def process_spine_chain(self, context: Context) -> None:
"""Process and fix spine bone chain hierarchy"""
bpy.ops.object.mode_set(mode='EDIT')
edit_bones = self.armature.data.edit_bones
spine_bones = {
'hips': None,
@@ -220,7 +180,29 @@ class AVATAR_TOOLKIT_OT_StandardizeMmd(Operator):
"""Fix bone orientations for standard pose compatibility"""
edit_bones = self.armature.data.edit_bones
# Process arm bones
# Define standardized roll values for key bones
roll_values = {
'upper_arm.L': -0.1,
'upper_arm.R': 0.1,
'forearm.L': -0.1,
'forearm.R': 0.1,
'thigh.L': 0.0,
'thigh.R': 0.0,
'shin.L': 0.0,
'shin.R': 0.0,
'foot.L': 0.0,
'foot.R': 0.0,
'spine': 0.0,
'chest': 0.0,
'neck': 0.0
}
# Apply roll corrections
for bone in edit_bones:
if bone.name.lower() in roll_values:
bone.roll = roll_values[bone.name.lower()]
# Process arm chains
arm_pairs = [
('upper_arm', 'forearm'),
('forearm', 'hand')
@@ -235,7 +217,7 @@ class AVATAR_TOOLKIT_OT_StandardizeMmd(Operator):
child_bone.use_connect = True
child_bone.use_inherit_rotation = True
# Process leg bones
# Process leg chains
leg_pairs = [
('thigh', 'shin'),
('shin', 'foot')
@@ -250,6 +232,12 @@ class AVATAR_TOOLKIT_OT_StandardizeMmd(Operator):
child_bone.use_connect = True
child_bone.use_inherit_rotation = True
# Align twist bones if present
twist_bones = [b for b in edit_bones if 'twist' in b.name.lower()]
for twist_bone in twist_bones:
if twist_bone.parent:
twist_bone.roll = twist_bone.parent.roll
def remove_unused_bones(self, context: Context) -> None:
"""Remove unused and unnecessary bones from the armature"""
bpy.ops.object.mode_set(mode='EDIT')
@@ -261,27 +249,29 @@ class AVATAR_TOOLKIT_OT_StandardizeMmd(Operator):
for group in mesh.vertex_groups:
used_bones.add(group.name)
# Get list of bones to keep based on settings
toolkit = context.scene.avatar_toolkit
keep_upper_chest = toolkit.keep_upper_chest
keep_twist = toolkit.keep_twist_bones
# Get list of essential bones to always keep
essential_bones = {
'hips', 'spine', 'chest', 'upper_chest', 'neck', 'head',
'left_leg', 'right_leg', 'left_knee', 'right_knee',
'left_ankle', 'right_ankle', 'left_toe', 'right_toe'
}
# Add any additional bones you want to preserve
essential_bones.update(dont_delete_these_main_bones)
# Remove unused bones
for bone in edit_bones:
# Skip if bone is essential
if bone.name.lower() in essential_bones:
continue
# Skip if bone has weights
if bone.name in used_bones:
continue
# Skip if bone is upper chest and we want to keep it
if 'upper_chest' in bone.name.lower() and keep_upper_chest:
continue
# Skip if bone is twist bone and we want to keep them
if 'twist' in bone.name.lower() and keep_twist:
continue
# Remove the bone
edit_bones.remove(bone)
edit_bones.remove(bone)
def connect_bones(self, context: Context) -> None:
"""Connect bones that should be connected in the hierarchy"""
@@ -308,21 +298,16 @@ class AVATAR_TOOLKIT_OT_StandardizeMmd(Operator):
"""Clean up vertex groups by removing zero weights and merging similar groups"""
threshold = context.scene.avatar_toolkit.merge_weights_threshold
# Get list of vertex groups
vertex_groups = mesh_obj.vertex_groups
# Track groups to remove
groups_to_remove = set()
# Check each vertex group
for group in vertex_groups:
weights = get_vertex_weights(mesh_obj, group.name)
# If no weights above threshold, mark for removal
if not any(weight > threshold for weight in weights.values()):
groups_to_remove.add(group.name)
# Remove empty groups
for group_name in groups_to_remove:
group = vertex_groups.get(group_name)
if group:
@@ -335,41 +320,11 @@ class AVATAR_TOOLKIT_OT_StandardizeMmd(Operator):
raise ValueError("\n".join(messages))
def cleanup_constraints(self, context: Context) -> None:
"""Clean up and fix bone constraints"""
"""Remove all constraints from the armature."""
bpy.ops.object.mode_set(mode='POSE')
# Process each pose bone
for pose_bone in self.armature.pose.bones:
constraints_to_remove = []
for constraint in pose_bone.constraints:
should_remove = False
# Handle IK constraints
if constraint.type == 'IK':
if not constraint.target or constraint.target != self.armature:
should_remove = True
elif not constraint.subtarget or constraint.subtarget not in self.armature.data.bones:
should_remove = True
# Handle MMD additional rotation constraints
elif constraint.name == 'mmd_additional_rotation':
if not constraint.target or constraint.target != self.armature:
should_remove = True
elif not constraint.subtarget or constraint.subtarget not in self.armature.data.bones:
should_remove = True
# Handle transformation constraints
elif constraint.type in {'COPY_ROTATION', 'COPY_LOCATION', 'COPY_TRANSFORMS'}:
if not constraint.target or constraint.target != self.armature:
should_remove = True
elif not constraint.subtarget or constraint.subtarget not in self.armature.data.bones:
should_remove = True
if should_remove:
constraints_to_remove.append(constraint)
# Remove invalid constraints
constraints_to_remove = [constraint for constraint in pose_bone.constraints]
for constraint in constraints_to_remove:
pose_bone.constraints.remove(constraint)
@@ -381,59 +336,17 @@ class AVATAR_TOOLKIT_OT_StandardizeMmd(Operator):
min_length = 0.01 # Minimum bone length in Blender units
for bone in edit_bones:
# Calculate bone length
bone_length = (bone.tail - bone.head).length
if bone_length < min_length:
# Set minimal length while preserving direction
if bone.parent:
# Use parent's orientation as reference
direction = bone.parent.tail - bone.parent.head
direction.normalize()
else:
# Default to Z-axis if no parent
direction = mathutils.Vector((0, 0, 1))
direction = Vector((0, 0, 1))
bone.tail = bone.head + (direction * min_length)
class FixUnmovableBonesOperator(bpy.types.Operator):
bl_idname = "avatar_toolkit.fix_unmovable_bones"
bl_label = t("MMD.fix_unmovable_bones")
bl_description = t("MMD.fix_unmovable_bones_desc")
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
armature = get_active_armature(context)
return armature is not None and armature.type == 'ARMATURE'
def execute(self, context):
armature = get_active_armature(context)
if not armature:
self.report({'ERROR'}, t("MMD.no_armature"))
return {'CANCELLED'}
try:
with ProgressTracker(context, 2, "Unlocking Transforms") as progress:
# Unlock armature transforms
progress.step("Unlocking armature transforms")
for attr in ('location', 'rotation', 'scale'):
for i in range(3):
setattr(armature, f"lock_{attr}", [False] * 3)
# Unlock bone transforms
progress.step("Unlocking bone transforms")
for bone in armature.pose.bones:
for attr in ('location', 'rotation', 'scale'):
setattr(bone, f"lock_{attr}", [False] * 3)
self.report({'INFO'}, t("MMD.transforms_unlocked"))
return {'FINISHED'}
except Exception as e:
logger.error(f"Error unlocking transforms: {str(e)}")
self.report({'ERROR'}, str(e))
return {'CANCELLED'}
class ReparentMeshesOperator(bpy.types.Operator):
bl_idname = "avatar_toolkit.reparent_meshes"
@@ -499,3 +412,381 @@ class ReparentMeshesOperator(bpy.types.Operator):
mesh.parent = armature
if not mesh.parent_type == 'ARMATURE':
mesh.parent_type = 'ARMATURE'
class AVATAR_TOOLKIT_OT_ConvertMmdMorphs(Operator):
"""Convert MMD morph data to shape keys"""
bl_idname = "avatar_toolkit.convert_mmd_morphs"
bl_label = t("MMD.convert_morphs")
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
armature = get_active_armature(context)
return armature is not None and get_all_meshes(context)
def execute(self, context):
armature = get_active_armature(context)
if not armature:
self.report({'ERROR'}, t("MMD.no_armature"))
return {'CANCELLED'}
try:
with ProgressTracker(context, 3, "Converting MMD Morphs") as progress:
# Convert bone morphs to shape keys
if hasattr(armature, 'mmd_root') and armature.mmd_root.bone_morphs:
self.process_bone_morphs(context, armature, progress)
progress.step("Processed bone morphs")
# Clean up unused data
self.cleanup_unused_data(context)
progress.step("Cleaned up data")
# Validate results
self.validate_results(context)
progress.step("Validated results")
self.report({'INFO'}, t("MMD.conversion_complete"))
return {'FINISHED'}
except Exception as e:
logger.error(f"Error converting MMD morphs: {str(e)}")
self.report({'ERROR'}, str(e))
return {'CANCELLED'}
def process_bone_morphs(self, context, armature, progress):
"""Process bone morphs into shape keys"""
for morph in armature.mmd_root.bone_morphs:
for mesh in get_all_meshes(context):
# Create armature modifier
mod = mesh.modifiers.new(morph.name, 'ARMATURE')
mod.object = armature
# Apply as shape key
with context.temp_override(object=mesh):
bpy.ops.object.modifier_apply(modifier=mod.name)
class AVATAR_TOOLKIT_OT_CleanupMmdModel(Operator):
"""Clean up MMD model by removing unused data and fixing display settings"""
bl_idname = "avatar_toolkit.cleanup_mmd"
bl_label = t("MMD.cleanup")
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
armature = get_active_armature(context)
if not armature:
self.report({'ERROR'}, t("MMD.no_armature"))
return {'CANCELLED'}
try:
with ProgressTracker(context, 4, "Cleaning MMD Model") as progress:
# Remove rigid bodies and joints
self.remove_physics_objects(armature)
progress.step("Removed physics objects")
# Clean up collections and hierarchy
self.cleanup_hierarchy(context, armature)
progress.step("Cleaned hierarchy")
# Fix viewport settings
self.fix_viewport_settings(context)
progress.step("Fixed viewport")
# Final cleanup
clear_unused_data_blocks()
progress.step("Cleared unused data")
self.report({'INFO'}, t("MMD.cleanup_complete"))
return {'FINISHED'}
except Exception as e:
logger.error(f"Error cleaning MMD model: {str(e)}")
self.report({'ERROR'}, str(e))
return {'CANCELLED'}
def remove_physics_objects(self, armature):
"""Remove physics-related objects"""
to_delete = []
for child in armature.children:
if any(x in child.name.lower() for x in ['rigidbodies', 'joints', 'physics']):
to_delete.append(child)
for obj in to_delete:
bpy.data.objects.remove(obj, do_unlink=True)
def cleanup_hierarchy(self, context, armature):
"""Clean up object hierarchy and collections"""
meshes = get_all_meshes(context)
for mesh in meshes:
# Ensure proper parenting
mesh.parent = armature
mesh.parent_type = 'ARMATURE'
# Clean up collections
for col in mesh.users_collection:
if col != context.scene.collection:
col.objects.unlink(mesh)
if mesh.name not in context.scene.collection.objects:
context.scene.collection.objects.link(mesh)
def fix_viewport_settings(self, context):
"""Fix viewport display settings"""
# Set armature display
armature = get_active_armature(context)
armature.data.display_type = 'OCTAHEDRAL'
armature.show_in_front = True
# Set viewport shading
for area in context.screen.areas:
if area.type == 'VIEW_3D':
space = area.spaces[0]
space.shading.type = 'MATERIAL'
space.clip_start = 0.01
space.clip_end = 300
class AVATAR_TOOLKIT_OT_FixMeshes(Operator):
"""Clean up and optimize mesh materials, shading, and shape keys"""
bl_idname = "avatar_toolkit.fix_meshes"
bl_label = t("Optimization.fix_meshes")
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
armature = get_active_armature(context)
return armature is not None and get_all_meshes(context)
def execute(self, context):
try:
meshes = get_all_meshes(context)
if not meshes:
self.report({'ERROR'}, t("Optimization.no_meshes"))
return {'CANCELLED'}
with ProgressTracker(context, len(meshes), "Fixing Meshes") as progress:
for mesh in meshes:
self.process_mesh(context, mesh)
progress.step(f"Processed {mesh.name}")
self.report({'INFO'}, t("Optimization.meshes_fixed"))
return {'FINISHED'}
except Exception as e:
logger.error(f"Error fixing meshes: {str(e)}")
self.report({'ERROR'}, str(e))
return {'CANCELLED'}
def process_mesh(self, context: Context, mesh: Object) -> None:
"""Process and fix individual mesh"""
# Unlock transforms
for i in range(3):
mesh.lock_location[i] = False
mesh.lock_rotation[i] = False
mesh.lock_scale[i] = False
# Process shape keys
if mesh.data.shape_keys:
self.fix_shape_keys(mesh)
# Process materials
self.fix_materials(context, mesh)
def fix_shape_keys(self, mesh: Object) -> None:
"""Fix and clean up shape keys"""
if not mesh.data.shape_keys:
return
shape_keys = mesh.data.shape_keys.key_blocks
# Rename basis
if shape_keys[0].name != "Basis":
shape_keys[0].name = "Basis"
# Clean up names
for key in shape_keys:
# Remove common prefixes/suffixes
clean_name = key.name
for prefix in ['Face.M F00 000 Fcl ', 'Face.M F00 000 00 Fcl ']:
clean_name = clean_name.replace(prefix, '')
# Replace underscores with spaces
clean_name = clean_name.replace('_', ' ')
key.name = clean_name
# Sort shape keys by category
categories = ['MTH', 'EYE', 'BRW', 'ALL']
# Create sorted list of shape key names
ordered_names = []
# Add categorized keys first
for category in categories:
category_keys = [key.name for key in shape_keys if key.name.startswith(category)]
ordered_names.extend(sorted(category_keys))
# Add remaining keys
remaining = [key.name for key in shape_keys if not any(key.name.startswith(c) for c in categories)]
ordered_names.extend(sorted(remaining))
# Reorder using context override
with bpy.context.temp_override(active_object=mesh, selected_objects=[mesh]):
for idx, name in enumerate(ordered_names):
mesh.active_shape_key_index = shape_keys.find(name)
while mesh.active_shape_key_index > idx:
bpy.ops.object.shape_key_move(type='UP')
def fix_materials(self, context: Context, mesh: Object) -> None:
"""Fix and optimize materials"""
for slot in mesh.material_slots:
if not slot.material:
continue
material = slot.material
# Set up basic material properties
material.use_backface_culling = True
material.blend_method = 'HASHED'
material.shadow_method = 'HASHED'
# Clean up material name
material.name = self.clean_material_name(material.name)
# Consolidate similar materials
for other_slot in mesh.material_slots:
if other_slot.material and other_slot.material != material:
if materials_match(material, other_slot.material):
other_slot.material = material
def clean_material_name(self, name: str) -> str:
"""Clean up material name"""
# Remove common prefixes/suffixes
prefixes = ['material', 'mat', 'mtl', 'material.']
for prefix in prefixes:
if name.lower().startswith(prefix):
name = name[len(prefix):]
# Remove numbers at end
while name and name[-1].isdigit():
name = name[:-1]
return name.strip()
class AVATAR_TOOLKIT_OT_ValidateMeshes(Operator):
"""Validate meshes and UV maps for common issues"""
bl_idname = "avatar_toolkit.validate_meshes"
bl_label = t("Validation.check_meshes")
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
armature = get_active_armature(context)
if not armature:
self.report({'ERROR'}, t("Validation.no_armature"))
return {'CANCELLED'}
try:
with ProgressTracker(context, 3, "Validating Meshes") as progress:
# Check bone hierarchy
hierarchy_issues = self.validate_bone_hierarchy(armature)
progress.step("Checked bone hierarchy")
# Check UV coordinates
uv_issues = self.validate_uv_maps(context)
progress.step("Checked UV maps")
# Generate report
self.generate_validation_report(context, hierarchy_issues, uv_issues)
progress.step("Generated report")
return {'FINISHED'}
except Exception as e:
logger.error(f"Error validating meshes: {str(e)}")
self.report({'ERROR'}, str(e))
return {'CANCELLED'}
def validate_bone_hierarchy(self, armature: Object) -> List[str]:
"""Validate bone hierarchy against standard structure"""
issues = []
# Define expected hierarchy
hierarchy = [
['hips', 'spine', 'chest', 'neck', 'head'],
['hips', 'left_leg', 'left_knee', 'left_ankle'],
['hips', 'right_leg', 'right_knee', 'right_ankle'],
['chest', 'left_shoulder', 'left_arm', 'left_elbow', 'left_wrist'],
['chest', 'right_shoulder', 'right_arm', 'right_elbow', 'right_wrist']
]
for chain in hierarchy:
previous = None
for bone_name in chain:
# Check if bone exists
bone = None
for alt_name in bone_names[bone_name]:
if alt_name in armature.data.bones:
bone = armature.data.bones[alt_name]
break
if not bone:
issues.append(t("Validation.missing_bone", bone=bone_name))
continue
# Check parent relationship
if previous:
if not bone.parent:
issues.append(t("Validation.no_parent", bone=bone.name))
elif bone.parent.name != previous.name:
issues.append(t("Validation.wrong_parent",
bone=bone.name,
expected=previous.name,
actual=bone.parent.name))
previous = bone
return issues
def validate_uv_maps(self, context: Context) -> Dict[str, int]:
"""Check UV maps for issues"""
issues = {'nan_coords': 0, 'missing_uvs': 0}
for mesh in get_all_meshes(context):
if not mesh.data.uv_layers:
issues['missing_uvs'] += 1
continue
for uv_layer in mesh.data.uv_layers:
for uv in uv_layer.data:
if math.isnan(uv.uv.x):
uv.uv.x = 0
issues['nan_coords'] += 1
if math.isnan(uv.uv.y):
uv.uv.y = 0
issues['nan_coords'] += 1
return issues
def generate_validation_report(self, context: Context,
hierarchy_issues: List[str],
uv_issues: Dict[str, int]) -> None:
"""Generate and display validation report"""
report_lines = []
# Add hierarchy issues
if hierarchy_issues:
report_lines.append(t("Validation.hierarchy_issues"))
report_lines.extend(hierarchy_issues)
# Add UV issues
if uv_issues['nan_coords'] > 0:
report_lines.append(t("Validation.uv_nan_coords",
count=uv_issues['nan_coords']))
if uv_issues['missing_uvs'] > 0:
report_lines.append(t("Validation.missing_uvs",
count=uv_issues['missing_uvs']))
# Show report
if report_lines:
self.report({'WARNING'}, "\n".join(report_lines))
else:
self.report({'INFO'}, t("Validation.no_issues"))
+1 -1
View File
@@ -186,7 +186,7 @@ class AvatarToolKit_OT_RemoveZeroWeightBones(Operator):
mesh_data: Mesh = mesh.data
for vertex in mesh_data.vertices:
for group in vertex.groups:
if group.weight > context.scene.avatar_toolkit.clean_weights_threshold:
if group.weight > context.scene.avatar_toolkit.merge_weights_threshold:
weighted_bones.append(mesh.vertex_groups[group.group].name)
# Process bone removal
+27 -4
View File
@@ -5,7 +5,7 @@ from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
from ..core.translations import t
class AvatarToolKit_PT_MMDPanel(Panel):
"""Panel containing MMD bone standardization tools"""
"""Panel containing MMD bone standardization and cleanup tools"""
bl_label = t("MMD.label")
bl_idname = "OBJECT_PT_avatar_toolkit_mmd"
bl_space_type = 'VIEW_3D'
@@ -18,6 +18,29 @@ class AvatarToolKit_PT_MMDPanel(Panel):
layout: UILayout = self.layout
toolkit = context.scene.avatar_toolkit
# Add merge twist bones option
layout.prop(toolkit, "keep_twist_bones")
layout.operator("avatar_toolkit.standardize_mmd", icon='BONE_DATA')
# Bone Settings Box
bone_box: UILayout = layout.box()
col: UILayout = bone_box.column(align=True)
col.label(text=t("MMD.bone_settings"), icon='BONE_DATA')
col.separator(factor=0.5)
col.prop(toolkit, "keep_twist_bones")
col.prop(toolkit, "keep_upper_chest")
col.operator("avatar_toolkit.standardize_mmd", icon='BONE_DATA')
# Mesh Tools Box
mesh_box: UILayout = layout.box()
col = mesh_box.column(align=True)
col.label(text=t("MMD.mesh_tools"), icon='MESH_DATA')
col.separator(factor=0.5)
row: UILayout = col.row(align=True)
row.operator("avatar_toolkit.fix_meshes", icon='MODIFIER')
row.operator("avatar_toolkit.validate_meshes", icon='CHECKMARK')
# Cleanup Box
cleanup_box: UILayout = layout.box()
col = cleanup_box.column(align=True)
col.label(text=t("MMD.cleanup"), icon='BRUSH_DATA')
col.separator(factor=0.5)
col.operator("avatar_toolkit.cleanup_mmd", icon='SHADERFX')
col.operator("avatar_toolkit.convert_mmd_morphs", icon='SHAPEKEY_DATA')
col.operator("avatar_toolkit.reparent_meshes", icon='OUTLINER_OB_ARMATURE')