From f4dc74d091558ce1cb9cf15b2d1e161d4e457256 Mon Sep 17 00:00:00 2001 From: Yusarina Date: Sat, 14 Dec 2024 01:10:24 +0000 Subject: [PATCH] Fixes --- core/dictionaries.py | 310 +++++++++++++---- functions/mmd_tools.py | 603 +++++++++++++++++++++++++--------- functions/tools/bone_tools.py | 2 +- ui/mmd_panel.py | 31 +- 4 files changed, 720 insertions(+), 226 deletions(-) diff --git a/core/dictionaries.py b/core/dictionaries.py index 4ec0b07..9a54b05 100644 --- a/core/dictionaries.py +++ b/core/dictionaries.py @@ -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", "左薬指2"], - "ring_3_l": ["ringfinger3l", "ring3l", "lring3", "ringdistall", "valvebipedbip01lfinger32", "左薬指3"], - "middle_0_l": ["middlefinger0l", "middle_0l", "lmiddle0", "middlemetacarpall", "左中指0"], - "middle_1_l": ["middlefinger1l", "middle_1l", "lmiddle1", "middleproximall", "valvebipedbip01lfinger2", "左中指1"], - "middle_2_l": ["middlefinger2l", "middle_2l", "lmiddle2", "middleintermediatel", "valvebipedbip01lfinger21", "左中指2"], - "middle_3_l": ["middlefinger3l", "middle_3l", "lmiddle3", "middledistall", "valvebipedbip01lfinger22", "左中指3"], - "index_0_l": ["indexfinger0l", "index0l", "lindex0", "indexmetacarpall", "左人差指0"], - "index_1_l": ["indexfinger1l", "index1l", "lindex1", "indexproximall", "valvebipedbip01lfinger1", "左人差指1"], - "index_2_l": ["indexfinger2l", "index2l", "lindex2", "indexintermediatel", "valvebipedbip01lfinger11", "左人差指2"], - "index_3_l": ["indexfinger3l", "index3l", "lindex3", "indexdistall", "valvebipedbip01lfinger12", "左人差指3"], - "thumb_0_l": ["thumb0l", "lthumb0", "thumbmetacarpall", "左親指0"], - "thumb_1_l": ["thumb1l", "lthumb1", "thumbproximall", "valvebipedbip01lfinger0", "左親指1"], - "thumb_2_l": ["thumb2l", "lthumb2", "thumbintermediatel", "valvebipedbip01lfinger01", "左親指2"], - "thumb_3_l": ["thumb3l", "lthumb3", "thumbdistall", "valvebipedbip01lfinger02", "左親指3"], - "left_leg": ["leftleg", "legl", "lleg", "upperlegl", "lupperleg", "thighl", "leftupperleg", "uplegl", "lupleg", "valvebipedbip01lthigh", "左足"], - "left_knee": ["leftknee", "kneel", "lknee", "lowerlegl", "llowerleg", "calfl", "lcalf", "leftlowerleg", "lowlegl", "llowleg", "valvebipedbip01lcalf", "左ひざ"], - "left_ankle": ["leftankle", "anklel", "lankle", "leftfoot", "footl", "lfoot", "leftfoot", "leftfeet", "feetleft", "lfeet", "feetl", "valvebipedbip01lfoot", "左足首"], - "left_toe": ["lefttoe", "toeleft", "toel", "ltoe", "toesl", "ltoes", "valvebipedbip01ltoe0", "左つま先"], - "hips": ["pelvis", "hips", "hip", "valvebipedbip01pelvis", "腰"], - "spine": ["torso", "spine", "valvebipedbip01spine", "脊椎"], - "chest": ["chest", "valvebipedbip01spine1", "胸"], - "upper_chest": ["upperchest", "valvebipedbip01spine4", "上胸"], - "neck": ["neck", "valvebipedbip01neck1", "首"], - "head": ["head", "valvebipedbip01head1", "頭"], - "left_eye": ["eyeleft", "lefteye", "eyel", "leye", "左目"], - "right_eye": ["eyeright", "righteye", "eyer", "reye", "右目"], -} + # 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", "右薬指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", + "右足", "足.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({ diff --git a/functions/mmd_tools.py b/functions/mmd_tools.py index 0ce96f3..884509a 100644 --- a/functions/mmd_tools.py +++ b/functions/mmd_tools.py @@ -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""" @@ -59,99 +60,62 @@ class AVATAR_TOOLKIT_OT_StandardizeMmd(Operator): logger.error(f"MMD Standardization failed: {str(e)}") 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') @@ -249,6 +231,12 @@ class AVATAR_TOOLKIT_OT_StandardizeMmd(Operator): if parent_bone and child_bone: 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""" @@ -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" @@ -498,4 +411,382 @@ class ReparentMeshesOperator(bpy.types.Operator): # Set parent to armature mesh.parent = armature if not mesh.parent_type == 'ARMATURE': - mesh.parent_type = 'ARMATURE' \ No newline at end of file + 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")) diff --git a/functions/tools/bone_tools.py b/functions/tools/bone_tools.py index 5cd7b4d..2e60524 100644 --- a/functions/tools/bone_tools.py +++ b/functions/tools/bone_tools.py @@ -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 diff --git a/ui/mmd_panel.py b/ui/mmd_panel.py index 4232bfb..96210b5 100644 --- a/ui/mmd_panel.py +++ b/ui/mmd_panel.py @@ -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')