Pose Shape Keys: Major Update #321

Merged
Demeter Dzadik merged 10 commits from pose-shape-keys-extension into main 2024-07-03 14:50:58 +02:00
2 changed files with 104 additions and 99 deletions
Showing only changes of commit a0f32b4d0d - Show all commits

View File

@ -29,7 +29,6 @@ DEFORM_MODIFIERS = [
GOOD_MODIFIERS = ['ARMATURE']
def get_deforming_armature(mesh_ob: Object) -> Object:
for mod in mesh_ob.modifiers:
if mod.type == 'ARMATURE':
@ -78,7 +77,7 @@ class OBJECT_OT_create_shape_key_for_pose(Operator):
else:
self.sk_name = "Key"
pose_key = obj.data.pose_keys[obj.data.active_pose_key_index]
pose_key = get_active_pose_key(obj)
if pose_key.name:
self.sk_name = pose_key.name
@ -106,7 +105,7 @@ class OBJECT_OT_create_shape_key_for_pose(Operator):
if self.vg_name:
new_sk.vertex_group = self.vg_name
pose_key = obj.data.pose_keys[obj.data.active_pose_key_index]
pose_key = get_active_pose_key(obj)
target = pose_key.target_shapes[pose_key.active_target_shape_index]
target.name = new_sk.name
@ -265,7 +264,7 @@ def set_pose_of_active_pose_key(context):
class OBJECT_OT_pose_key_set_pose(Operator):
"""Set the rig pose to the specified action and frame (Reset any other posing)"""
"""Reset the rig, then set the above Action and frame number"""
bl_idname = "object.posekey_set_pose"
bl_label = "Set Pose"
@ -273,7 +272,7 @@ class OBJECT_OT_pose_key_set_pose(Operator):
@classmethod
def poll(cls, context):
return poll_has_psk_and_deformed(cls, context)
return poll_correct_pose_key_pose(cls, context, demand_pose=False)
def execute(self, context):
set_pose_of_active_pose_key(context)
@ -289,7 +288,12 @@ def get_active_pose_key(obj):
return obj.data.pose_keys[obj.data.active_pose_key_index]
def poll_has_psk_and_deformed(operator, context):
def poll_correct_pose_key_pose(operator, context, demand_pose=True):
"""To make these operators foolproof, there are a lot of checks to make sure
that the user gets to see the effect of the operator. The "Set Pose" operator
can be used first to set the correct state and pass all the checks here.
"""
obj = context.object
if not obj:
@ -309,39 +313,27 @@ def poll_has_psk_and_deformed(operator, context):
operator.poll_message_set("This mesh object is not deformed by any Armature modifier.")
return False
return obj, pose_key, arm_ob
def poll_correct_pose_key_pose(operator, context):
"""To make these operators foolproof, there are a lot of checks to make sure
that the user gets to see the effect of the operator. The "Set Pose" operator
can be used first to set the correct state and pass all the checks here.
"""
obj_psk_arm = poll_has_psk_and_deformed(operator, context)
if not obj_psk_arm:
if not pose_key.action:
operator.poll_message_set("An Action must be associated with the Pose Shape Key.")
return False
obj, pose_key, arm_ob = obj_psk_arm
if demand_pose:
# Action must exist and match.
if not (
arm_ob.animation_data
and arm_ob.animation_data.action
and arm_ob.animation_data.action == pose_key.action
):
operator.poll_message_set(
"The armature must have the Pose Shape Key's action assigned. Use the Set Pose button."
)
return False
# Action must exist and match.
if not (
pose_key.action
and arm_ob.animation_data
and arm_ob.animation_data.action
and arm_ob.animation_data.action == pose_key.action
):
operator.poll_message_set(
"The armature must have the Pose Shape Key's action assigned. Use the Set Pose button."
)
return False
if pose_key.frame != context.scene.frame_current:
operator.poll_message_set(
"The Pose Shape Key's frame must be the same as the current scene frame. Use the Set Pose button."
)
return False
if pose_key.frame != context.scene.frame_current:
operator.poll_message_set(
"The Pose Shape Key's frame must be the same as the current scene frame. Use the Set Pose button."
)
return False
return True
@ -352,6 +344,7 @@ class OBJECT_OT_pose_key_add(UILIST_OT_Entry_Add, Operator):
bl_idname = "object.posekey_add"
bl_label = "Add Pose Shape Key"
bl_options = {'UNDO', 'REGISTER', 'INTERNAL'}
bl_property = "pose_key_name" # Focus the text input box
list_context_path: StringProperty()
active_idx_context_path: StringProperty()
@ -387,8 +380,43 @@ class OBJECT_OT_pose_key_add(UILIST_OT_Entry_Add, Operator):
return {'FINISHED'}
class OBJECT_OT_pose_key_auto_init(Operator):
"""Assign the current Action and scene frame number to this pose key"""
bl_idname = "object.posekey_auto_init"
bl_label = "Initialize From Context"
bl_options = {'UNDO', 'REGISTER', 'INTERNAL'}
@classmethod
def poll(cls, context):
obj = context.object
arm_ob = get_deforming_armature(obj)
if not arm_ob:
cls.poll_message_set("No deforming armature.")
return False
if not (arm_ob.animation_data and arm_ob.animation_data.action):
cls.poll_message_set("Armature has no Action assigned.")
return False
obj = context.object
pose_key = get_active_pose_key(obj)
if pose_key.action == arm_ob.animation_data.action and pose_key.frame == context.scene.frame_current:
cls.poll_message_set("Action and frame number are already set.")
return False
return True
def execute(self, context):
# Set action and frame number to the current ones.
obj = context.object
pose_key = get_active_pose_key(obj)
arm_ob = get_deforming_armature(obj)
pose_key.action = arm_ob.animation_data.action
pose_key.frame = context.scene.frame_current
self.report({'INFO'}, "Initialized Pose Key data.")
return {'FINISHED'}
class OBJECT_OT_pose_key_save(Operator, OperatorWithWarning, SaveAndRestoreState):
"""Save the current evaluated mesh vertex positions into the Storage Object"""
"""Save the deformed mesh vertex positions of the current pose into the Storage Object"""
bl_idname = "object.posekey_save"
bl_label = "Overwrite Storage Object"
@ -396,36 +424,18 @@ class OBJECT_OT_pose_key_save(Operator, OperatorWithWarning, SaveAndRestoreState
@classmethod
def poll(cls, context):
obj_psk_arm = poll_has_psk_and_deformed(cls, context)
if not obj_psk_arm:
return False
obj, pose_key, arm_ob = obj_psk_arm
if (
not pose_key.storage_object
and not pose_key.action
and arm_ob.animation_data
and arm_ob.animation_data.action
):
# If we can guess the action, allow the operator to run.
# Let's call this "Initialize mode", since we're allowing the
# user to 1-click initialize some variables.
return True
if not poll_correct_pose_key_pose(cls, context):
return False
return True
return poll_correct_pose_key_pose(cls, context)
def invoke(self, context, event):
obj = context.object
pose_key = obj.data.pose_keys[obj.data.active_pose_key_index]
pose_key = get_active_pose_key(obj)
if pose_key.storage_object:
return super().invoke(context, event)
return self.execute(context)
def get_warning_text(self, context):
obj = context.object
pose_key = obj.data.pose_keys[obj.data.active_pose_key_index]
pose_key = get_active_pose_key(obj)
return f'Overwrite storage object "{pose_key.storage_object.name}"?'
def execute(self, context):
@ -450,13 +460,6 @@ class OBJECT_OT_pose_key_save(Operator, OperatorWithWarning, SaveAndRestoreState
pose_key.storage_object = storage_ob
storage_ob.location = rigged_ob.location
storage_ob.location.x -= rigged_ob.dimensions.x * 1.1
# Set action and frame number to the current ones, in case the user
# is already in the desired pose for this pose key.
arm_ob = get_deforming_armature(rigged_ob)
if arm_ob and arm_ob.animation_data and arm_ob.animation_data.action:
pose_key.action = arm_ob.animation_data.action
pose_key.frame = context.scene.frame_current
else:
old_mesh = storage_ob.data
storage_ob.data = storage_ob_mesh
@ -535,7 +538,7 @@ class OBJECT_OT_pose_key_push(Operator, OperatorWithWarning, SaveAndRestoreState
def get_warning_text(self, context):
obj = context.object
pose_key = obj.data.pose_keys[obj.data.active_pose_key_index]
pose_key = get_active_pose_key(obj)
target_shape_names = [target.name for target in pose_key.target_shapes if target]
return (
"This will overwrite the following Shape Keys: \n "
@ -775,7 +778,7 @@ class OBJECT_OT_pose_key_place_objects_in_grid(Operator):
return {'FINISHED'}
class OBJECT_OT_pose_key_jump_to_shape(Operator):
class OBJECT_OT_pose_key_jump_to_storage(Operator):
"""Place the storage object next to this object and select it"""
bl_idname = "object.posekey_jump_to_storage"
@ -785,7 +788,7 @@ class OBJECT_OT_pose_key_jump_to_shape(Operator):
@staticmethod
def get_storage_object(context):
obj = context.object
pose_key = obj.data.pose_keys[obj.data.active_pose_key_index]
pose_key = get_active_pose_key(obj)
return pose_key.storage_object
@classmethod
@ -863,6 +866,7 @@ class OBJECT_OT_pose_key_copy_data(Operator):
registry = [
OBJECT_OT_pose_key_auto_init,
OBJECT_OT_pose_key_add,
OBJECT_OT_pose_key_save,
OBJECT_OT_pose_key_set_pose,
@ -871,6 +875,6 @@ registry = [
OBJECT_OT_create_shape_key_for_pose,
OBJECT_OT_pose_key_clamp_influence,
OBJECT_OT_pose_key_place_objects_in_grid,
OBJECT_OT_pose_key_jump_to_shape,
OBJECT_OT_pose_key_jump_to_storage,
OBJECT_OT_pose_key_copy_data,
]

View File

@ -1,9 +1,10 @@
import bpy
from bpy.types import Object, Panel, UIList, Menu
from bl_ui.properties_data_mesh import DATA_PT_shape_keys
from .ui_list import draw_ui_list
from bpy.props import EnumProperty
from .ui_list import draw_ui_list
from .ops import get_deforming_armature
from .prefs import get_addon_prefs
@ -72,7 +73,7 @@ class MESH_PT_pose_keys(Panel):
bl_region_type = 'WINDOW'
bl_context = 'data'
bl_options = {'DEFAULT_CLOSED'}
bl_label = "Shape/Pose Keys"
bl_label = "Pose Shape Keys"
@classmethod
def poll(cls, context):
@ -81,16 +82,17 @@ class MESH_PT_pose_keys(Panel):
def draw(self, context):
obj = context.object
mesh = obj.data
layout = self.layout
layout = self.layout.column()
layout.prop(mesh, 'shape_key_ui_type', text="List Type: ", expand=True)
layout.row().prop(mesh, 'shape_key_ui_type', expand=True)
if mesh.shape_key_ui_type == 'DEFAULT':
return DATA_PT_shape_keys.draw(self, context)
if not obj_has_armature_mod(obj):
arm_ob = get_deforming_armature(obj)
if not arm_ob:
layout.alert = True
layout.label(text="Object must have an Armature modifier to use Pose Keys.")
layout.label(text="Object must be deformed by an Armature to use Pose Keys.")
return
if mesh.shape_keys and not mesh.shape_keys.use_relative:
@ -120,33 +122,30 @@ class MESH_PT_pose_keys(Panel):
idx = context.object.data.active_pose_key_index
active_posekey = context.object.data.pose_keys[idx]
col = layout.column(align=True)
col.prop(active_posekey, 'action')
action_split = layout.row().split(factor=0.4, align=True)
action_split.alignment='RIGHT'
action_split.label(text="Action")
row = action_split.row(align=True)
icon = 'FORWARD'
if active_posekey.action:
col.prop(active_posekey, 'frame')
if active_posekey.storage_object:
row = layout.row()
row.prop(active_posekey, 'storage_object')
row.operator('object.posekey_jump_to_storage', text="", icon='RESTRICT_SELECT_OFF')
else:
layout.operator('object.posekey_set_pose', text="Set Pose", icon="ARMATURE_DATA")
row = layout.row()
text = "Store Posed Mesh"
if not active_posekey.storage_object and not active_posekey.action:
text = "Store Mesh & Init Pose Key"
row.operator('object.posekey_save', text=text, icon="FILE_TICK")
row.prop(active_posekey, 'storage_object', text="")
return
icon = 'FILE_REFRESH'
row.operator('object.posekey_auto_init', text="", icon=icon)
row.prop(active_posekey, 'action', text="")
layout.prop(active_posekey, 'frame')
layout.separator()
col = layout.column(align=True)
col.operator('object.posekey_set_pose', text="Set Pose", icon="ARMATURE_DATA")
col.separator()
row = col.row()
row.operator('object.posekey_save', text="Overwrite Storage Object", icon="FILE_TICK")
row.operator('object.posekey_push', text="Overwrite Shape Keys", icon="IMPORT")
layout.operator('object.posekey_set_pose', text="Set Pose", icon="ARMATURE_DATA")
layout.separator()
row = layout.row(align=True)
text = "Save Posed Mesh"
if active_posekey.storage_object:
text="Overwrite Posed Mesh"
row.operator('object.posekey_save', text=text, icon="FILE_TICK")
row.prop(active_posekey, 'storage_object', text="")
row.operator('object.posekey_jump_to_storage', text="", icon='RESTRICT_SELECT_OFF')
class MESH_PT_shape_key_subpanel(Panel):
@ -178,6 +177,8 @@ class MESH_PT_shape_key_subpanel(Panel):
idx = context.object.data.active_pose_key_index
active_posekey = context.object.data.pose_keys[idx]
layout.operator('object.posekey_push', text="Overwrite Shape Keys", icon="IMPORT")
draw_ui_list(
layout,
context,
@ -250,7 +251,7 @@ def register():
('DEFAULT', 'Shape Keys', "Show a flat list of shape keys"),
(
'POSE_KEYS',
'Pose Keys',
'Pose Shape Keys',
"Organize shape keys into a higher-level concept called Pose Keys. These can store vertex positions and push one shape to multiple shape keys at once, relative to existing deformation",
),
],