Merge pull request #25 from 989onan/ResoniteTools

Added Resonite Tools
This commit is contained in:
Onan Chew
2024-07-04 16:51:38 -04:00
committed by GitHub
7 changed files with 315 additions and 12 deletions
View File
+18
View File
@@ -1,6 +1,8 @@
import bpy import bpy
import numpy as np import numpy as np
from .dictionaries import bone_names
from typing import List, Optional
from bpy.types import Object, ShapeKey, Mesh, Context from bpy.types import Object, ShapeKey, Mesh, Context
from functools import lru_cache from functools import lru_cache
@@ -39,3 +41,19 @@ def has_shapekeys(mesh_obj: Object) -> bool:
@lru_cache(maxsize=None) @lru_cache(maxsize=None)
def _get_shape_key_co(shape_key: ShapeKey) -> np.ndarray: def _get_shape_key_co(shape_key: ShapeKey) -> np.ndarray:
return np.array([v.co for v in shape_key.data]) return np.array([v.co for v in shape_key.data])
def simplify_bonename(n):
return n.lower().translate(dict.fromkeys(map(ord, u" _.")))
def get_armature(context, armature_name=None) -> Optional[Object]:
if armature_name:
obj = bpy.data.objects[armature_name]
if obj.type == "ARMATURE":
return obj
else:
return None
if context.view_layer.objects.active:
obj = context.view_layer.objects.active
if obj.type == "ARMATURE":
return obj
return next((obj for obj in context.view_layer.objects if obj.type == 'ARMATURE'), None)
+112
View File
@@ -0,0 +1,112 @@
# GPL Licence
# Bone names from https://github.com/triazo/immersive_scaler/
# 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"],
"right_arm": ["rightarm", "armr", "rarm", "upperarmr", "rupperarm", "rightupperarm", "upperarmright", "uparmr", "ruparm"],
"right_elbow": ["rightelbow", "elbowr", "relbow", "lowerarmr", "rightlowerarm", "lowerarmr","rlowerarm", "lowerarmright", "lowarmr", "rlowarm", "forearmr","rforearm"],
"right_wrist": ["rightwrist", "wristr", "rwrist", "handr", "righthand", "rhand"],
#hand l fingers
"pinkie_0_r": ["littlefinger0r","pinkie0r","rpinkie0","pinkiemetacarpalr"],
"pinkie_1_r": ["littlefinger1r","pinkie1r","rpinkie1","pinkieproximalr"],
"pinkie_2_r": ["littlefinger2r","pinkie2r","rpinkie2","pinkieintermediater"],
"pinkie_3_r": ["littlefinger3r","pinkie3r","rpinkie3","pinkiedistalr"],
"ring_0_r": ["ringfinger0r","ring0r","rring0","ringmetacarpalr"],
"ring_1_r": ["ringfinger1r","ring1r","rring1","ringproximalr"],
"ring_2_r": ["ringfinger2r","ring2r","rring2","ringintermediater"],
"ring_3_r": ["ringfinger3r","ring3r","rring3","ringdistalr"],
"middle_0_r": ["middlefinger0r","middle0r","rmiddle0","middlemetacarpalr"],
"middle_1_r": ["middlefinger1r","middle1r","rmiddle1","middleproximalr"],
"middle_2_r": ["middlefinger2r","middle2r","rmiddle2","middleintermediater"],
"middle_3_r": ["middlefinger3r","middle3r","rmiddle3","middledistalr"],
"index_0_r": ["indexfinger0r","index0r","rindex0","indexmetacarpalr"],
"index_1_r": ["indexfinger1r","index1r","rindex1","indexproximalr"],
"index_2_r": ["indexfinger2r","index2r","rindex2","indexintermediater"],
"index_3_r": ["indexfinger3r","index3r","rindex3","indexdistalr"],
"thumb_0_r": ["thumb0r","rthumb0","thumbmetacarpalr"],
"thumb_1_r": ['thumb1r',"rthumb1","thumbproximalr"],
"thumb_2_r": ['thumb2r',"rthumb2","thumbintermediater"],
"thumb_3_r": ['thumb3r',"rthumb3","thumbdistalr"],
"right_leg": ["rightleg", "legr", "rleg", "upperlegr", "rupperleg", "thighr", "rightupperleg", "upperlegright", "uplegr", "rupleg"],
"right_knee": ["rightknee", "kneer", "rknee", "lowerlegr", "calfr", "rlowerleg", "rcalf", "rightlowerleg", "lowerlegright", "lowlegr", "rlowleg"],
"right_ankle": ["rightankle", "ankler", "rankle", "footright", "footr", "rfoot", "rightfoot", "rightfeet", "feetright", "rfeet", "feetr"],
"right_toe": ["righttoe", "toeright", "toer", "rtoe", "toesr", "rtoes"],
"left_shoulder": ["leftshoulder", "shoulderl", "lshoulder"],
"left_arm": ["leftarm", "arml", "rarm", "upperarml", "lupperarm", "leftupperarm", "upperarmleft", "uparml", "luparm"],
"left_elbow": ["leftelbow", "elbowl", "lelbow", "lowerarml", "leftlowerarm", "lowerarmleft", "lowerarml", "llowerarm", "lowarml", "llowarm", "forearml","lforearm"],
"left_wrist": ["leftwrist", "wristl", "lwrist", "handl", "lefthand", "lhand"],
#hand l fingers
"pinkie_0_l": ["pinkiefinger0l","pinkie0l","lpinkie0","pinkiemetacarpall"],
"pinkie_1_l": ["littlefinger1l","pinkie1l","lpinkie1","pinkieproximall"],
"pinkie_2_l": ["littlefinger2l","pinkie2l","lpinkie2","pinkieintermediatel"],
"pinkie_3_l": ["littlefinger3l","pinkie3l","lpinkie3","pinkiedistall"],
"ring_0_l": ["ringfinger0l","ring0l","lring0","ringmetacarpall"],
"ring_1_l": ["ringfinger1l","ring1l","lring1","ringproximall"],
"ring_2_l": ["ringfinger2l","ring2l","lring2","ringintermediatel"],
"ring_3_l": ["ringfinger3l","ring3l","lring3","ringdistall"],
"middle_0_l": ["middlefinger0l","middle_0l","lmiddle0","middlemetacarpall"],
"middle_1_l": ["middlefinger1l","middle_1l","lmiddle1","middleproximall"],
"middle_2_l": ["middlefinger2l","middle_2l","lmiddle2","middleintermediatel"],
"middle_3_l": ["middlefinger3l","middle_3l","lmiddle3","middledistall"],
"index_0_l": ["indexfinger0l","index0l","lindex0","indexmetacarpall"],
"index_1_l": ["indexfinger1l","index1l","lindex1","indexproximall"],
"index_2_l": ["indexfinger2l","index2l","lindex2","indexintermediatel"],
"index_3_l": ["indexfinger3l","index3l","lindex3","indexdistall"],
"thumb_0_l": ["thumb0l","lthumb0","thumbmetacarpall"],
"thumb_1_l": ['thumb1l',"lthumb1","thumbproximall"],
"thumb_2_l": ['thumb2l',"lthumb2","thumbintermediatel"],
"thumb_3_l": ['thumb3l',"lthumb3","thumbdistall"],
"left_leg": ["leftleg", "legl", "lleg", "upperlegl", "lupperleg", "thighl", "leftupperleg", "upperlegleft", "uplegl", "lupleg"],
"left_knee": ["leftknee", "kneel", "lknee", "lowerlegl", "llowerleg", "calfl", "lcalf", "leftlowerleg", "lowerlegleft", 'lowlegl', 'llowleg'],
"left_ankle": ["leftankle", "anklel", "rankle", "footleft", "footl", "lfoot", "leftfoot", "leftfeet", "feetleft", "lfeet", "feetl"],
"left_toe": ["lefttoe", "toeleft", "toel", "ltoe", "toesl", "ltoes"],
"hips": ["pelvis", "hips", "hip"],
"spine": ["torso", "spine"],
"chest": ["chest"],
"upper_chest": ["upperchest", "chestupper"],
"neck": ["neck"],
"head": ["head", "cabeza"],
"left_eye": ["eyeleft", "lefteye", "eyel", "leye"],
"right_eye": ["eyeright", "righteye", "eyer", "reye"],
}
# array taken from cats
dont_delete_these_main_bones = [
'Hips', 'Spine', 'Chest', 'Upper Chest', 'Neck', 'Head',
'Left leg', 'Left knee', 'Left ankle', 'Left toe',
'Right leg', 'Right knee', 'Right ankle', 'Right toe',
'Left shoulder', 'Left arm', 'Left elbow', 'Left wrist',
'Right shoulder', 'Right arm', 'Right elbow', 'Right wrist',
'LeftEye', 'RightEye', 'Eye_L', 'Eye_R',
'Left leg 2', 'Right leg 2',
'Thumb0_L', 'Thumb1_L', 'Thumb2_L',
'IndexFinger1_L', 'IndexFinger2_L', 'IndexFinger3_L',
'MiddleFinger1_L', 'MiddleFinger2_L', 'MiddleFinger3_L',
'RingFinger1_L', 'RingFinger2_L', 'RingFinger3_L',
'LittleFinger1_L', 'LittleFinger2_L', 'LittleFinger3_L',
'Thumb0_R', 'Thumb1_R', 'Thumb2_R',
'IndexFinger1_R', 'IndexFinger2_R', 'IndexFinger3_R',
'MiddleFinger1_R', 'MiddleFinger2_R', 'MiddleFinger3_R',
'RingFinger1_R', 'RingFinger2_R', 'RingFinger3_R',
'LittleFinger1_R', 'LittleFinger2_R', 'LittleFinger3_R',
]
+35
View File
@@ -0,0 +1,35 @@
import bpy
from typing import List, Optional
from .common import get_armature
from bpy.types import Object, ShapeKey, Mesh, Context, Operator
from functools import lru_cache
from ..core.register import register_wrap
@register_wrap
class ExportResonite(Operator):
bl_idname = 'avatar_toolkit.export_resonite'
bl_label = "Export to Resonite"
bl_description = "Export a GLB with all animations and materials. For animation data see: "
bl_options = {'REGISTER', 'UNDO'}
filepath: bpy.props.StringProperty()
@classmethod
def poll(cls, context: Context):
if get_armature(context) is None:
return False
return True
def execute(self, context: Context):
#settings stolen from cats.
bpy.ops.export_scene.gltf('INVOKE_AREA',
export_image_format = 'WEBP',
export_image_quality = 75,
export_materials = 'EXPORT',
export_animations = True,
export_animation_mode = 'ACTIONS',
export_nla_strips_merged_animation_name = 'Animation',
export_nla_strips = True)
return {'FINISHED'}
+114
View File
@@ -0,0 +1,114 @@
import bpy
from ..core.register import register_wrap
from typing import List, Optional
import re
from bpy.types import Operator, Context, Object
from ..core.dictionaries import bone_names
from ..core.common import get_armature, simplify_bonename
@register_wrap
class ConvertToResonite(Operator):
bl_idname = 'avatar_toolkit.convert_to_resonite'
bl_label = "Convert to Resonite" #t('Tools.convert_to_resonite.label')
bl_description = "Converts bone names on a model to names compatable with Resonite" #t('Tools.convert_to_resonite.desc')
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context: Context) -> bool:
if not get_armature(context):
return False
return True
def execute(self, context: Context) -> set:
armature = get_armature(context)
translate_bone_fails = 0
untranslated_bones = set()
reverse_bone_lookup = dict()
for (preferred_name, name_list) in bone_names.items():
for name in name_list:
reverse_bone_lookup[name] = preferred_name
resonite_translations = {
'hips': "Hips",
'spine': "Spine",
'chest': "Chest",
'neck': "Neck",
'head': "Head",
'left_eye': "Eye.L",
'right_eye': "Eye.R",
'right_leg': "UpperLeg.R",
'right_knee': "Calf.R",
'right_ankle': "Foot.R",
'right_toe': 'Toes.R',
'right_shoulder': "Shoulder.R",
'right_arm': "UpperArm.R",
'right_elbow': "ForeArm.R",
'right_wrist': "Hand.R",
'left_leg': "UpperLeg.L",
'left_knee': "Calf.L",
'left_ankle': "Foot.L",
'left_toe': "Toes.L",
'left_shoulder': "Shoulder.L",
'left_arm': "UpperArm.L",
'left_elbow': "ForeArm.L",
'left_wrist': "Hand.R",
'pinkie_1_l': "pinkie1.L",
'pinkie_2_l': "pinkie2.L",
'pinkie_3_l': "pinkie3.L",
'ring_1_l': "ring1.L",
'ring_2_l': "ring2.L",
'ring_3_l': "ring3.L",
'middle_1_l': "middle1.L",
'middle_2_l': "middle2.L",
'middle_3_l': "middle3.L",
'index_1_l': "index1.L",
'index_2_l': "index2.L",
'index_3_l': "index3.L",
'thumb_1_l': "thumb1.L",
'thumb_2_l': "thumb2.L",
'thumb_3_l': "thumb3.L",
'pinkie_1_r': "pinkie1.R",
'pinkie_2_r': "pinkie2.R",
'pinkie_3_r': "pinkie3.R",
'ring_1_r': "ring1.R",
'ring_2_r': "ring2.R",
'ring_3_r': "ring3.R",
'middle_1_r': "middle1.R",
'middle_2_r': "middle2.R",
'middle_3_r': "middle3.R",
'index_1_r': "index1.R",
'index_2_r': "index2.R",
'index_3_r': "index3.R",
'thumb_1_r': "thumb1.R",
'thumb_2_r': "thumb2.R",
'thumb_3_r': "thumb3.R"
}
context.view_layer.objects.active = armature
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.object.mode_set(mode='OBJECT')
for bone in armature.data.bones:
if simplify_bonename(bone.name) in reverse_bone_lookup and reverse_bone_lookup[simplify_bonename(bone.name)] in resonite_translations:
bone.name = resonite_translations[reverse_bone_lookup[simplify_bonename(bone.name)]]
else:
untranslated_bones.add(bone.name)
bone.name = re.compile(re.escape("<noik>"), re.IGNORECASE).sub("",bone.name)+"<noik>"
translate_bone_fails += 1
bpy.ops.object.mode_set(mode='OBJECT')
if translate_bone_fails > 0:
self.report({'INFO'}, "Failed to translate {translate_bone_fails} bones to humanoid names. Adding \"<noik>\" to their names.".format(translate_bone_fails=translate_bone_fails))
else:
self.report({'INFO'}, "Successfully translated all bones to humanoid names")
return {'FINISHED'}
+14 -12
View File
@@ -1,6 +1,7 @@
import bpy import bpy
from ..core.register import register_wrap from ..core.register import register_wrap
from .panel import AvatarToolkitPanel from .panel import AvatarToolkitPanel
from bpy.types import Context
from ..core.import_pmx import import_pmx from ..core.import_pmx import import_pmx
from ..core.import_pmd import import_pmd from ..core.import_pmd import import_pmd
@@ -14,7 +15,7 @@ class AvatarToolkitQuickAccessPanel(bpy.types.Panel):
bl_category = "Avatar Toolkit" bl_category = "Avatar Toolkit"
bl_parent_id = "OBJECT_PT_avatar_toolkit" bl_parent_id = "OBJECT_PT_avatar_toolkit"
def draw(self, context): def draw(self, context: Context):
layout = self.layout layout = self.layout
layout.label(text="Quick Access Options") layout.label(text="Quick Access Options")
@@ -33,14 +34,14 @@ class AVATAR_TOOLKIT_OT_import_menu(bpy.types.Operator):
bl_idname = "avatar_toolkit.import_menu" bl_idname = "avatar_toolkit.import_menu"
bl_label = "Import Menu" bl_label = "Import Menu"
def execute(self, context): def execute(self, context: Context):
return {'FINISHED'} return {'FINISHED'}
def invoke(self, context, event): def invoke(self, context: Context, event):
wm = context.window_manager wm = context.window_manager
return wm.invoke_popup(self, width=200) return wm.invoke_popup(self, width=200)
def draw(self, context): def draw(self, context: Context):
layout = self.layout layout = self.layout
layout.label(text="Select Import Method") layout.label(text="Select Import Method")
layout.operator("avatar_toolkit.import_pmx", text="Import PMX") layout.operator("avatar_toolkit.import_pmx", text="Import PMX")
@@ -51,16 +52,17 @@ class AVATAR_TOOLKIT_OT_export_menu(bpy.types.Operator):
bl_idname = "avatar_toolkit.export_menu" bl_idname = "avatar_toolkit.export_menu"
bl_label = "Export Menu" bl_label = "Export Menu"
def execute(self, context): def execute(self, context: Context):
return {'FINISHED'} return {'FINISHED'}
def invoke(self, context, event): def invoke(self, context: Context, event):
wm = context.window_manager wm = context.window_manager
return wm.invoke_popup(self, width=200) return wm.invoke_popup(self, width=200)
def draw(self, context): def draw(self, context: Context):
layout = self.layout layout = self.layout
layout.label(text="Export options will go here") layout.label(text="Select Export Method")
layout.operator("avatar_toolkit.export_resonite", text="Export Resonite")
@register_wrap @register_wrap
class AVATAR_TOOLKIT_OT_import_pmx(bpy.types.Operator): class AVATAR_TOOLKIT_OT_import_pmx(bpy.types.Operator):
@@ -69,11 +71,11 @@ class AVATAR_TOOLKIT_OT_import_pmx(bpy.types.Operator):
filepath: bpy.props.StringProperty(subtype="FILE_PATH") filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context): def execute(self, context: Context):
import_pmx(self.filepath) import_pmx(self.filepath)
return {'FINISHED'} return {'FINISHED'}
def invoke(self, context, event): def invoke(self, context: Context, event):
context.window_manager.fileselect_add(self) context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'} return {'RUNNING_MODAL'}
@@ -84,10 +86,10 @@ class AVATAR_TOOLKIT_OT_import_pmd(bpy.types.Operator):
filepath: bpy.props.StringProperty(subtype="FILE_PATH") filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context): def execute(self, context: Context):
import_pmd(self.filepath) import_pmd(self.filepath)
return {'FINISHED'} return {'FINISHED'}
def invoke(self, context, event): def invoke(self, context: Context, event):
context.window_manager.fileselect_add(self) context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'} return {'RUNNING_MODAL'}
+22
View File
@@ -0,0 +1,22 @@
import bpy
from ..core.register import register_wrap
from .panel import AvatarToolkitPanel
from bpy.types import Context
@register_wrap
class AvatarToolkitToolsPanel(bpy.types.Panel):
bl_label = "Tools"
bl_idname = "OBJECT_PT_avatar_toolkit_tools"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "Avatar Toolkit"
bl_parent_id = "OBJECT_PT_avatar_toolkit"
def draw(self, context: Context):
layout = self.layout
layout.label(text="Tools")
layout.separator(factor=0.5)
row = layout.row(align=True)
row.scale_y = 1.5
row.operator("avatar_toolkit.convert_to_resonite", text="Translate to Resonite")