Re-do still hate it
This commit is contained in:
+59
-1
@@ -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
@@ -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
@@ -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
@@ -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)
|
|
||||||
|
|||||||
@@ -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
@@ -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')
|
||||||
|
|||||||
Reference in New Issue
Block a user