fix merge armatures

now the different options work. and it has an apply transforms
This commit is contained in:
989onan
2024-09-13 12:38:08 -04:00
parent 0cb4d6bb3a
commit b015736ead
5 changed files with 180 additions and 108 deletions
+103
View File
@@ -115,6 +115,9 @@ def get_armature(context: Context, armature_name: Optional[str] = None) -> Optio
def get_armatures(self, context: Context) -> List[Tuple[str, str, str]]:
return [(obj.name, obj.name, "") for obj in bpy.data.objects if obj.type == 'ARMATURE']
def get_armatures_that_are_not_selected(self, context: Context) -> List[Tuple[str, str, str]]:
return [(obj.name, obj.name, "") for obj in bpy.data.objects if ((obj.type == 'ARMATURE') and (obj.name != context.scene.selected_armature))]
def get_selected_armature(context: Context) -> Optional[Object]:
if context.scene.selected_armature:
armature = bpy.data.objects.get(context.scene.selected_armature)
@@ -141,6 +144,106 @@ def select_current_armature(context: Context) -> bool:
return True
return False
def apply_pose_as_rest(context: Context, armature_obj: bpy.types.Object, meshes: list[bpy.types.Object]) -> bool:
for obj in meshes:
mesh_data: Mesh = obj.data
if mesh_data.shape_keys:
shape_key_obj_list: list[bpy.types.Object] = []
modifier_armature_name: str = ""
for modifier in obj.modifiers:
if modifier.type == "ARMATURE":
arm_modifier: bpy.types.ArmatureModifier = modifier
if not (arm_modifier.object == armature_obj):
continue
modifier_armature_name = arm_modifier.object.name
if modifier_armature_name == "":
continue
for idx,shape in enumerate(mesh_data.shape_keys.key_blocks):
if idx == 0:
continue
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)
#create duplicate of object
bpy.ops.object.duplicate()
shape_obj = context.view_layer.objects.active
#make current shapekey a separate object
shape_obj.active_shape_key_index = idx
shape_obj.name = shape.name
bpy.ops.object.shape_key_move(type="TOP")
bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.shape_key_remove(all=True)
bpy.ops.object.modifier_apply(modifier=modifier_armature_name)
#for modifier_name in [i.name for i in shape_obj.modifiers]:
# bpy.ops.object.modifier_remove(modifier=modifier_name)
shape_key_obj_list.append(shape_obj) #add to a list of shape key objects
context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode="OBJECT")
context.view_layer.objects.active.select_set(True)
bpy.ops.object.shape_key_remove(all=True)
bpy.ops.object.modifier_apply(modifier=modifier_armature_name)
bpy.ops.object.select_all(action="DESELECT")
for shapekey_obj in shape_key_obj_list:
shapekey_obj.select_set(True)
context.view_layer.objects.active = obj
context.view_layer.objects.active.select_set(True)
try:
bpy.ops.object.join_shapes()
except:
#delete shapekey objects to not leave ourselves in a bad exit state - @989onan
context.view_layer.objects.active = shape_key_obj_list[0]
obj.select_set(False)
bpy.ops.object.delete(confirm=False)
return False
context.view_layer.objects.active = shape_key_obj_list[0]
obj.select_set(False)
bpy.ops.object.delete(confirm=False)
else:
modifier_armature_name: str = ""
for modifier in obj.modifiers:
if modifier.type == "ARMATURE":
arm_modifier: bpy.types.ArmatureModifier = modifier
if not (arm_modifier.object == armature_obj):
continue
modifier_armature_name = arm_modifier.object.name
if modifier_armature_name == "":
continue
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.select_set(True)
bpy.ops.object.modifier_apply(modifier=modifier_armature_name)
context.view_layer.objects.active = armature_obj
armature_obj.select_set(True)
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.mode_set(mode="POSE")
bpy.ops.pose.armature_apply(selected=False)
return True
def get_all_meshes(context: Context) -> List[Object]:
armature = get_selected_armature(context)
if armature and is_valid_armature(armature):
+13 -2
View File
@@ -4,7 +4,7 @@ from ..core.register import register_property
from bpy.types import Scene, Object, Material, Context
from bpy.props import BoolProperty, EnumProperty, IntProperty, CollectionProperty, StringProperty, FloatVectorProperty, PointerProperty
from ..core.addon_preferences import get_preference
from ..core.common import SceneMatClass, MaterialListBool, get_armatures, get_mesh_items
from ..core.common import SceneMatClass, MaterialListBool, get_armatures, get_mesh_items, get_armatures_that_are_not_selected
def register() -> None:
default_language = get_preference("language", 0)
@@ -23,10 +23,21 @@ def register() -> None:
)))
register_property((bpy.types.Scene, "merge_armature_source", bpy.props.EnumProperty(
items=get_armatures,
items=get_armatures_that_are_not_selected,
name=t("MergeArmatures.selected_armature.label"),
description=t("MergeArmatures.selected_armature.label")
)))
register_property((bpy.types.Scene, "merge_armature_apply_transforms", bpy.props.BoolProperty(
default=False,
name=t("MergeArmature.merge_armatures.apply_transforms.label"),
description=t("MergeArmature.merge_armatures.apply_transforms.desc")
)))
register_property((bpy.types.Scene, "merge_armature_align_bones", bpy.props.BoolProperty(
default=False,
name=t("MergeArmature.merge_armatures.align_bones.label"),
description=t("MergeArmature.merge_armatures.align_bones.desc")
)))
register_property((bpy.types.Scene, "avatar_toolkit_language_changed", bpy.props.BoolProperty(default=False)))
+48 -104
View File
@@ -4,6 +4,8 @@ from bpy.types import Context, Mesh, Panel, Operator, Armature, EditBone
from ..functions.translations import t
from ..core.common import get_selected_armature, get_all_meshes
from ..core import common
from ..core.dictionaries import bone_names
from mathutils import Matrix
@register_wrap
class AvatarToolkit_OT_StartPoseMode(Operator):
@@ -95,98 +97,10 @@ class AvatarToolkit_OT_ApplyPoseAsRest(Operator):
return get_selected_armature(context) != None and context.mode == "POSE"
def execute(self, context: Context):
for obj in get_all_meshes(context):
mesh_data: Mesh = obj.data
if mesh_data.shape_keys:
shape_key_obj_list: list[bpy.types.Object] = []
modifier_armature_name: str = ""
for modifier in obj.modifiers:
if modifier.type == "ARMATURE":
arm_modifier: bpy.types.ArmatureModifier = modifier
modifier_armature_name = arm_modifier.object.name
for idx,shape in enumerate(mesh_data.shape_keys.key_blocks):
if idx == 0:
continue
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)
#create duplicate of object
bpy.ops.object.duplicate()
shape_obj = context.view_layer.objects.active
#make current shapekey a separate object
shape_obj.active_shape_key_index = idx
shape_obj.name = shape.name
bpy.ops.object.shape_key_move(type="TOP")
bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.shape_key_remove(all=True)
bpy.ops.object.modifier_apply(modifier=modifier_armature_name)
#for modifier_name in [i.name for i in shape_obj.modifiers]:
# bpy.ops.object.modifier_remove(modifier=modifier_name)
shape_key_obj_list.append(shape_obj) #add to a list of shape key objects
context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode="OBJECT")
context.view_layer.objects.active.select_set(True)
bpy.ops.object.shape_key_remove(all=True)
bpy.ops.object.modifier_apply(modifier=modifier_armature_name)
bpy.ops.object.select_all(action="DESELECT")
for shapekey_obj in shape_key_obj_list:
shapekey_obj.select_set(True)
context.view_layer.objects.active = obj
context.view_layer.objects.active.select_set(True)
try:
bpy.ops.object.join_shapes()
except:
self.report({'ERROR'}, t("Quick_Access.apply_armature_failed"))
#delete shapekey objects to not leave ourselves in a bad exit state - @989onan
context.view_layer.objects.active = shape_key_obj_list[0]
obj.select_set(False)
bpy.ops.object.delete(confirm=False)
return {'CANCELLED'}
context.view_layer.objects.active = shape_key_obj_list[0]
obj.select_set(False)
bpy.ops.object.delete(confirm=False)
else:
modifier_armature_name: str = ""
for modifier in obj.modifiers:
if modifier.type == "ARMATURE":
arm_modifier: bpy.types.ArmatureModifier = modifier
modifier_armature_name = arm_modifier.object.name
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.select_set(True)
bpy.ops.object.modifier_apply(modifier=modifier_armature_name)
armature_obj: bpy.types.Object = get_selected_armature(context)
context.view_layer.objects.active = armature_obj
armature_obj.select_set(True)
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.mode_set(mode="POSE")
bpy.ops.pose.armature_apply(selected=False)
if common.apply_pose_as_rest(armature_obj=get_selected_armature(context),meshes=get_all_meshes(context), context=context):
self.report({'ERROR'}, t("Quick_Access.apply_armature_failed"))
return {'FINISHED'}
return {'FINISHED'}
@register_wrap
@@ -349,8 +263,6 @@ class AvatarToolkit_OT_MergeArmatures(Operator):
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)
@@ -372,22 +284,54 @@ class AvatarToolkit_OT_MergeArmatures(Operator):
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):
if context.scene.merge_armature_apply_transforms:
target_armature.select_set(True)
for obj in target_armature.children:
obj.select_set(True)
for obj in source_armature.children:
obj.select_set(True)
bpy.ops.object.transform_apply()
if context.scene.merge_armature_align_bones:
if not context.scene.merge_armature_apply_transforms:
source_armature.matrix_world = target_armature.matrix_world
def children_bone_recursive(parent_bone) -> list[bpy.types.PoseBone]:
child_bones = []
child_bones.append(parent_bone)
for child in parent_bone.children:
child_bones.extend(children_bone_recursive(child))
return child_bones
bpy.ops.object.mode_set(mode='POSE')
source_armature_bone_names = [j.name for j in children_bone_recursive(
source_armature.pose.bones[
next(bone.name for bone in source_armature.pose.bones if common.simplify_bonename(bone.name) in bone_names['hips']) #Find bone that matches dictionary for hips before continuing.
]
)] #bones are default in order of parent child.
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
context.view_layer.objects.active = target_armature
bpy.ops.object.mode_set(mode='EDIT')
for source_bone_name in source_armature_bone_names:
if source_bone_name in target_armature_data.edit_bones:
obj = source_armature
editbone = target_armature_data.edit_bones[source_bone_name]
bone = obj.pose.bones[source_bone_name]
bone.matrix = editbone.matrix
else:
continue
if not common.apply_pose_as_rest(armature_obj=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'}"""
return {'FINISHED'}
cls.make_active(obj=source_armature, context=context)
bpy.ops.object.mode_set(mode='EDIT')
source_armature_data: Armature = source_armature.data
+4
View File
@@ -169,10 +169,14 @@
"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.",
"MergeArmatures.target_armature.label": "Armature to Merge To",
"MergeArmatures.target_armature.desc": "The armature that should be the target for merging armatures.",
"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.",
"MergeArmature.merge_armatures.apply_transforms.label": "Apply Transforms",
"MergeArmature.merge_armatures.apply_transforms.desc": "Apply transforms on armature and it's meshes before merging.",
"VisemePanel.create_visemes": "Create Visemes",
"VisemePanel.creating_viseme": "Creating viseme: {viseme_name}",
"VisemePanel.creating_viseme_detail": "Creating viseme: {viseme_name}",
+12 -2
View File
@@ -23,8 +23,18 @@ class AvatarToolkit_PT_MergeArmaturesPanel(Panel):
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")
row = layout.row(align=True)
row.prop(context.scene, property="selected_armature",text=t("MergeArmatures.target_armature.label"),icon="STYLUS_PRESSURE")
row = layout.row(align=True)
row.prop(context.scene, property="merge_armature_source",icon="SORT_DESC")
row = layout.row(align=True)
row.prop(context.scene, property="merge_armature_align_bones")
row = layout.row(align=True)
row.prop(context.scene, property="merge_armature_apply_transforms")
row = layout.row(align=True)
row.operator(operator=AvatarToolkit_OT_MergeArmatures.bl_idname,icon="ARMATURE_DATA")
else:
layout.label(text=t("MergeArmatures.select_armature"), icon='ERROR')