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]]: 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'] 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]: def get_selected_armature(context: Context) -> Optional[Object]:
if context.scene.selected_armature: if context.scene.selected_armature:
armature = bpy.data.objects.get(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 True
return False 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]: def get_all_meshes(context: Context) -> List[Object]:
armature = get_selected_armature(context) armature = get_selected_armature(context)
if armature and is_valid_armature(armature): 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.types import Scene, Object, Material, Context
from bpy.props import BoolProperty, EnumProperty, IntProperty, CollectionProperty, StringProperty, FloatVectorProperty, PointerProperty from bpy.props import BoolProperty, EnumProperty, IntProperty, CollectionProperty, StringProperty, FloatVectorProperty, PointerProperty
from ..core.addon_preferences import get_preference 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: def register() -> None:
default_language = get_preference("language", 0) default_language = get_preference("language", 0)
@@ -23,11 +23,22 @@ def register() -> None:
))) )))
register_property((bpy.types.Scene, "merge_armature_source", bpy.props.EnumProperty( 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"), name=t("MergeArmatures.selected_armature.label"),
description=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))) 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)))
+44 -100
View File
@@ -4,6 +4,8 @@ from bpy.types import Context, Mesh, Panel, Operator, Armature, EditBone
from ..functions.translations import t from ..functions.translations import t
from ..core.common import get_selected_armature, get_all_meshes from ..core.common import get_selected_armature, get_all_meshes
from ..core import common from ..core import common
from ..core.dictionaries import bone_names
from mathutils import Matrix
@register_wrap @register_wrap
class AvatarToolkit_OT_StartPoseMode(Operator): class AvatarToolkit_OT_StartPoseMode(Operator):
@@ -95,98 +97,10 @@ class AvatarToolkit_OT_ApplyPoseAsRest(Operator):
return get_selected_armature(context) != None and context.mode == "POSE" return get_selected_armature(context) != None and context.mode == "POSE"
def execute(self, context: Context): def execute(self, context: Context):
for obj in get_all_meshes(context):
mesh_data: Mesh = obj.data
if common.apply_pose_as_rest(armature_obj=get_selected_armature(context),meshes=get_all_meshes(context), context=context):
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")) self.report({'ERROR'}, t("Quick_Access.apply_armature_failed"))
#delete shapekey objects to not leave ourselves in a bad exit state - @989onan return {'FINISHED'}
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)
return {'FINISHED'} return {'FINISHED'}
@register_wrap @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_description = t("MergeArmature.merge_armatures.desc").format(selected_armature_label=t("MergeArmatures.selected_armature.label"))
bl_options = {'REGISTER', 'UNDO'} 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 @classmethod
def poll(cls, context: Context) -> bool: def poll(cls, context: Context) -> bool:
return (common.get_selected_armature(context) is not None) and (context.scene.merge_armature_source is not None) return (common.get_selected_armature(context) is not None) and (context.scene.merge_armature_source is not None)
@@ -372,18 +284,50 @@ class AvatarToolkit_OT_MergeArmatures(Operator):
cls.make_active(obj=source_armature, context=context) cls.make_active(obj=source_armature, context=context)
#TODO: This is woefully screwed. This needs to be fixed - @989onan
"""if cls.align_bones: 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') bpy.ops.object.mode_set(mode='POSE')
for bone in source_armature.pose.bones: source_armature_bone_names = [j.name for j in children_bone_recursive(
if bone.name in target_armature_data.bones: 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.
#sorry for this one liner - @989onan bpy.ops.object.mode_set(mode='OBJECT')
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') 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 not common.apply_pose_as_rest(armature=source_armature,meshes=[i for i in source_armature.children if i.type == 'MESH'], context=context): 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")) cls.report({'ERROR'}, t("Quick_Access.apply_armature_failed"))
return {'FINISHED'}""" return {'FINISHED'}
+4
View File
@@ -169,10 +169,14 @@
"MergeArmatures.label": "Merge Armatures", "MergeArmatures.label": "Merge Armatures",
"MergeArmatures.selected_armature.label": "Armature to Merge From", "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.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.label": "Merge Armatures Together",
"MergeArmature.merge_armatures.desc": "Merge {selected_armature_label} to the targeted armature for Avatar Toolkit.", "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.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.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.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}",
+12 -2
View File
@@ -23,8 +23,18 @@ class AvatarToolkit_PT_MergeArmaturesPanel(Panel):
if armature: if armature:
layout.label(text=t("MergeArmatures.title.label"), icon='ARMATURE_DATA') layout.label(text=t("MergeArmatures.title.label"), icon='ARMATURE_DATA')
layout.separator(factor=0.5) layout.separator(factor=0.5)
layout.prop(context.scene,property="merge_armature_source",icon="ARMATURE_DATA") row = layout.row(align=True)
layout.operator(operator=AvatarToolkit_OT_MergeArmatures.bl_idname,icon="ARMATURE_DATA") 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: else:
layout.label(text=t("MergeArmatures.select_armature"), icon='ERROR') layout.label(text=t("MergeArmatures.select_armature"), icon='ERROR')