Pose Shape Keys: Major Update #321
@ -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,
|
||||
]
|
||||
|
@ -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",
|
||||
),
|
||||
],
|
||||
|
Loading…
Reference in New Issue
Block a user