Merge armatures button

This commit is contained in:
989onan
2024-09-12 21:56:39 -04:00
parent 1608841a9a
commit 0cb4d6bb3a
7 changed files with 126 additions and 7 deletions
+9 -3
View File
@@ -22,6 +22,12 @@ def register() -> None:
description=t("VisemePanel.selected_mesh.desc") description=t("VisemePanel.selected_mesh.desc")
))) )))
register_property((bpy.types.Scene, "merge_armature_source", bpy.props.EnumProperty(
items=get_armatures,
name=t("MergeArmatures.selected_armature.label"),
description=t("MergeArmatures.selected_armature.label")
)))
register_property((bpy.types.Scene, "avatar_toolkit_language_changed", bpy.props.BoolProperty(default=False))) register_property((bpy.types.Scene, "avatar_toolkit_language_changed", bpy.props.BoolProperty(default=False)))
register_property((bpy.types.Scene, "avatar_toolkit_progress_steps", bpy.props.IntProperty(default=0))) register_property((bpy.types.Scene, "avatar_toolkit_progress_steps", bpy.props.IntProperty(default=0)))
@@ -49,8 +55,8 @@ def register() -> None:
register_property((bpy.types.Scene, "selected_armature", bpy.props.EnumProperty( register_property((bpy.types.Scene, "selected_armature", bpy.props.EnumProperty(
items=get_armatures, items=get_armatures,
name="Selected Armature", name=t("Quick_Access.selected_armature.label"),
description="The currently selected armature for Avatar Toolkit operations" description=t("Quick_Access.selected_armature.desc")
))) )))
#happy with how compressed this get_texture_node_list method is - @989onan #happy with how compressed this get_texture_node_list method is - @989onan
@@ -88,7 +94,7 @@ def register() -> None:
items=get_texture_node_list))) items=get_texture_node_list)))
register_property((Material, "texture_atlas_height", EnumProperty( register_property((Material, "texture_atlas_height", EnumProperty(
name=t("TextureAtlas.height"), name=t("TextureAtlas.height"),
description=t("TextureAtlas.texture_use_atlas.desc").format(name=t("TextureAtlas.height_map").lower()), description=t("TextureAtlas.texture_use_atlas.desc").format(name=t("TextureAtlas.height").lower()),
default=0, default=0,
items=get_texture_node_list))) items=get_texture_node_list)))
register_property((Material, "texture_atlas_roughness", EnumProperty( register_property((Material, "texture_atlas_roughness", EnumProperty(
+73 -1
View File
@@ -338,6 +338,78 @@ class AvatarToolkit_OT_MergeBonesToParents(Operator):
bone_child.parent = armature_data.edit_bones[bone].parent bone_child.parent = armature_data.edit_bones[bone].parent
armature_data.edit_bones.remove(armature_data.edit_bones[bone]) armature_data.edit_bones.remove(armature_data.edit_bones[bone])
bpy.ops.object.mode_set(mode=prev_mode) bpy.ops.object.mode_set(mode=prev_mode)
return {'FINISHED'} return {'FINISHED'}
@register_wrap
class AvatarToolkit_OT_MergeArmatures(Operator):
bl_idname = "avatar_toolkit.merge_armatures"
bl_label = t("MergeArmature.merge_armatures.label")
bl_description = t("MergeArmature.merge_armatures.desc").format(selected_armature_label=t("MergeArmatures.selected_armature.label"))
bl_options = {'REGISTER', 'UNDO'}
"""align_bones: bpy.props.BoolProperty(default=False,name=t("MergeArmature.merge_armatures.align_bones.label"),description=t("MergeArmature.merge_armatures.align_bones.desc"))"""
@classmethod
def poll(cls, context: Context) -> bool:
return (common.get_selected_armature(context) is not None) and (context.scene.merge_armature_source is not None)
def make_active(self, obj: bpy.types.Object, context: Context):
context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
context.view_layer.objects.active = obj
obj.select_set(True)
def execute(cls, context: Context) -> set[str]:
source_armature: bpy.types.Object = bpy.data.objects[context.scene.merge_armature_source]
source_armature_data: Armature = source_armature.data
target_armature: bpy.types.Object = common.get_selected_armature(context)
target_armature_data: Armature = target_armature.data
parent_dictionary: dict[str, list[str]] = {}
cls.make_active(obj=source_armature, context=context)
#TODO: This is woefully screwed. This needs to be fixed - @989onan
"""if cls.align_bones:
bpy.ops.object.mode_set(mode='POSE')
for bone in source_armature.pose.bones:
if bone.name in target_armature_data.bones:
#sorry for this one liner - @989onan
bone.matrix = source_armature.convert_space(matrix=target_armature.convert_space(matrix=target_armature_data.bones[bone.name].matrix_local, pose_bone=None,from_space='LOCAL',to_space='WORLD'), pose_bone=None, from_space='WORLD', to_space='LOCAL')
if not common.apply_pose_as_rest(armature=source_armature,meshes=[i for i in source_armature.children if i.type == 'MESH'], context=context):
cls.report({'ERROR'}, t("Quick_Access.apply_armature_failed"))
return {'FINISHED'}"""
cls.make_active(obj=source_armature, context=context)
bpy.ops.object.mode_set(mode='EDIT')
source_armature_data: Armature = source_armature.data
for bone_name in [i.name for i in source_armature_data.edit_bones]:
if bone_name in target_armature_data.bones:
parent_dictionary[bone_name] = [i.name for i in source_armature_data.edit_bones[bone_name].children]
source_armature_data.edit_bones.remove(source_armature_data.edit_bones[bone_name])
bpy.ops.object.mode_set(mode='OBJECT')
cls.make_active(obj=target_armature, context=context)
source_armature.select_set(True)
bpy.ops.object.join()
target_armature: bpy.types.Object = common.get_selected_armature(context)
cls.make_active(obj=target_armature, context=context)
bpy.ops.object.mode_set(mode='EDIT')
for bone_name, bone_name_list in parent_dictionary.items():
if bone_name in target_armature_data.edit_bones:
for bone_child in bone_name_list:
target_armature_data.edit_bones[bone_child].parent = target_armature_data.edit_bones[bone_name]
bpy.ops.object.mode_set(mode='OBJECT')
return {'FINISHED'}
+11
View File
@@ -62,6 +62,8 @@
"Optimization.selecting_meshes": "Selecting meshes...", "Optimization.selecting_meshes": "Selecting meshes...",
"Optimization.transform_apply_failed": "Transform apply failed", "Optimization.transform_apply_failed": "Transform apply failed",
"Optimization.vertex_excluded": "Shapekey has a moved vertex at index \"{index}\", excluding from double merging!", "Optimization.vertex_excluded": "Shapekey has a moved vertex at index \"{index}\", excluding from double merging!",
"Quick_Access.selected_armature.label": "Selected Armature",
"Quick_Access.selected_armature.desc": "The currently \"targeted\" armature for Avatar Toolkit operations",
"Quick_Access.export": "Export", "Quick_Access.export": "Export",
"Quick_Access.export_fbx.desc": "Export the model as FBX", "Quick_Access.export_fbx.desc": "Export the model as FBX",
"Quick_Access.export_fbx.label": "Export FBX", "Quick_Access.export_fbx.label": "Export FBX",
@@ -162,6 +164,15 @@
"Tools.merge_bones_to_parents.label": "Merge Bones to Individual Parents", "Tools.merge_bones_to_parents.label": "Merge Bones to Individual Parents",
"Tools.remove_zero_weight_bones.threshold.label": "Weight Threshold", "Tools.remove_zero_weight_bones.threshold.label": "Weight Threshold",
"Tools.remove_zero_weight_bones.threshold.desc": "If a bone is not weighted to any part of any mesh under the armature with a threshold greater than this, it is removed", "Tools.remove_zero_weight_bones.threshold.desc": "If a bone is not weighted to any part of any mesh under the armature with a threshold greater than this, it is removed",
"MergeArmatures.select_armature": "Please select an armature",
"MergeArmatures.title.label": "Merge Armatures:",
"MergeArmatures.label": "Merge Armatures",
"MergeArmatures.selected_armature.label": "Armature to Merge From",
"MergeArmatures.selected_armature.desc": "The armature that should be merged into the targeted armature for Avatar Toolkit.",
"MergeArmature.merge_armatures.label": "Merge Armatures Together",
"MergeArmature.merge_armatures.desc": "Merge {selected_armature_label} to the targeted armature for Avatar Toolkit.",
"MergeArmature.merge_armatures.align_bones.label": "Align Bones",
"MergeArmature.merge_armatures.align_bones.desc": "Align bones from source armature to target armature,\nstretching bones to match before merging.",
"VisemePanel.create_visemes": "Create Visemes", "VisemePanel.create_visemes": "Create Visemes",
"VisemePanel.creating_viseme": "Creating viseme: {viseme_name}", "VisemePanel.creating_viseme": "Creating viseme: {viseme_name}",
"VisemePanel.creating_viseme_detail": "Creating viseme: {viseme_name}", "VisemePanel.creating_viseme_detail": "Creating viseme: {viseme_name}",
+1 -1
View File
@@ -66,7 +66,7 @@ class AvatarToolKit_PT_TextureAtlasPanel(Panel):
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 = 4 bl_order = 5
def draw(self, context: Context): def draw(self, context: Context):
layout = self.layout layout = self.layout
+30
View File
@@ -0,0 +1,30 @@
import bpy
from ..core.register import register_wrap
from .panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
from bpy.types import Panel, Context
from ..core.common import get_selected_armature
from ..functions.translations import t
from ..functions.armature_modifying import AvatarToolkit_OT_MergeArmatures
@register_wrap
class AvatarToolkit_PT_MergeArmaturesPanel(Panel):
bl_label = t("MergeArmatures.label")
bl_idname = "OBJECT_PT_avatar_toolkit_merge_armatures"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = CATEGORY_NAME
bl_parent_id = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname
bl_order = 4
def draw(self, context: Context):
layout = self.layout
armature = get_selected_armature(context)
if armature:
layout.label(text=t("MergeArmatures.title.label"), icon='ARMATURE_DATA')
layout.separator(factor=0.5)
layout.prop(context.scene,property="merge_armature_source",icon="ARMATURE_DATA")
layout.operator(operator=AvatarToolkit_OT_MergeArmatures.bl_idname,icon="ARMATURE_DATA")
else:
layout.label(text=t("MergeArmatures.select_armature"), icon='ERROR')
+1 -1
View File
@@ -11,7 +11,7 @@ class AvatarToolkitSettingsPanel(bpy.types.Panel):
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 = 6 bl_order = 7
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
+1 -1
View File
@@ -13,7 +13,7 @@ class AvatarToolkitVisemePanel(bpy.types.Panel):
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 = 5 bl_order = 6
def draw(self, context: bpy.types.Context) -> None: def draw(self, context: bpy.types.Context) -> None:
layout = self.layout layout = self.layout