Pose Shape Keys: Major Update #321
@ -29,7 +29,6 @@ DEFORM_MODIFIERS = [
|
|||||||
GOOD_MODIFIERS = ['ARMATURE']
|
GOOD_MODIFIERS = ['ARMATURE']
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_deforming_armature(mesh_ob: Object) -> Object:
|
def get_deforming_armature(mesh_ob: Object) -> Object:
|
||||||
for mod in mesh_ob.modifiers:
|
for mod in mesh_ob.modifiers:
|
||||||
if mod.type == 'ARMATURE':
|
if mod.type == 'ARMATURE':
|
||||||
@ -78,7 +77,7 @@ class OBJECT_OT_create_shape_key_for_pose(Operator):
|
|||||||
else:
|
else:
|
||||||
self.sk_name = "Key"
|
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:
|
if pose_key.name:
|
||||||
self.sk_name = 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:
|
if self.vg_name:
|
||||||
new_sk.vertex_group = 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 = pose_key.target_shapes[pose_key.active_target_shape_index]
|
||||||
target.name = new_sk.name
|
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):
|
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_idname = "object.posekey_set_pose"
|
||||||
bl_label = "Set Pose"
|
bl_label = "Set Pose"
|
||||||
@ -273,7 +272,7 @@ class OBJECT_OT_pose_key_set_pose(Operator):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
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):
|
def execute(self, context):
|
||||||
set_pose_of_active_pose_key(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]
|
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
|
obj = context.object
|
||||||
|
|
||||||
if not obj:
|
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.")
|
operator.poll_message_set("This mesh object is not deformed by any Armature modifier.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return obj, pose_key, arm_ob
|
if not pose_key.action:
|
||||||
|
operator.poll_message_set("An Action must be associated with the Pose Shape Key.")
|
||||||
|
|
||||||
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:
|
|
||||||
return False
|
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 pose_key.frame != context.scene.frame_current:
|
||||||
if not (
|
operator.poll_message_set(
|
||||||
pose_key.action
|
"The Pose Shape Key's frame must be the same as the current scene frame. Use the Set Pose button."
|
||||||
and arm_ob.animation_data
|
)
|
||||||
and arm_ob.animation_data.action
|
return False
|
||||||
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
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -352,6 +344,7 @@ class OBJECT_OT_pose_key_add(UILIST_OT_Entry_Add, Operator):
|
|||||||
bl_idname = "object.posekey_add"
|
bl_idname = "object.posekey_add"
|
||||||
bl_label = "Add Pose Shape Key"
|
bl_label = "Add Pose Shape Key"
|
||||||
bl_options = {'UNDO', 'REGISTER', 'INTERNAL'}
|
bl_options = {'UNDO', 'REGISTER', 'INTERNAL'}
|
||||||
|
bl_property = "pose_key_name" # Focus the text input box
|
||||||
|
|
||||||
list_context_path: StringProperty()
|
list_context_path: StringProperty()
|
||||||
active_idx_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'}
|
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):
|
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_idname = "object.posekey_save"
|
||||||
bl_label = "Overwrite Storage Object"
|
bl_label = "Overwrite Storage Object"
|
||||||
@ -396,36 +424,18 @@ class OBJECT_OT_pose_key_save(Operator, OperatorWithWarning, SaveAndRestoreState
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
obj_psk_arm = poll_has_psk_and_deformed(cls, context)
|
return poll_correct_pose_key_pose(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
|
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
obj = context.object
|
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:
|
if pose_key.storage_object:
|
||||||
return super().invoke(context, event)
|
return super().invoke(context, event)
|
||||||
return self.execute(context)
|
return self.execute(context)
|
||||||
|
|
||||||
def get_warning_text(self, context):
|
def get_warning_text(self, context):
|
||||||
obj = context.object
|
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}"?'
|
return f'Overwrite storage object "{pose_key.storage_object.name}"?'
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
@ -450,13 +460,6 @@ class OBJECT_OT_pose_key_save(Operator, OperatorWithWarning, SaveAndRestoreState
|
|||||||
pose_key.storage_object = storage_ob
|
pose_key.storage_object = storage_ob
|
||||||
storage_ob.location = rigged_ob.location
|
storage_ob.location = rigged_ob.location
|
||||||
storage_ob.location.x -= rigged_ob.dimensions.x * 1.1
|
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:
|
else:
|
||||||
old_mesh = storage_ob.data
|
old_mesh = storage_ob.data
|
||||||
storage_ob.data = storage_ob_mesh
|
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):
|
def get_warning_text(self, context):
|
||||||
obj = context.object
|
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]
|
target_shape_names = [target.name for target in pose_key.target_shapes if target]
|
||||||
return (
|
return (
|
||||||
"This will overwrite the following Shape Keys: \n "
|
"This will overwrite the following Shape Keys: \n "
|
||||||
@ -775,7 +778,7 @@ class OBJECT_OT_pose_key_place_objects_in_grid(Operator):
|
|||||||
return {'FINISHED'}
|
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"""
|
"""Place the storage object next to this object and select it"""
|
||||||
|
|
||||||
bl_idname = "object.posekey_jump_to_storage"
|
bl_idname = "object.posekey_jump_to_storage"
|
||||||
@ -785,7 +788,7 @@ class OBJECT_OT_pose_key_jump_to_shape(Operator):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def get_storage_object(context):
|
def get_storage_object(context):
|
||||||
obj = context.object
|
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
|
return pose_key.storage_object
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -863,6 +866,7 @@ class OBJECT_OT_pose_key_copy_data(Operator):
|
|||||||
|
|
||||||
|
|
||||||
registry = [
|
registry = [
|
||||||
|
OBJECT_OT_pose_key_auto_init,
|
||||||
OBJECT_OT_pose_key_add,
|
OBJECT_OT_pose_key_add,
|
||||||
OBJECT_OT_pose_key_save,
|
OBJECT_OT_pose_key_save,
|
||||||
OBJECT_OT_pose_key_set_pose,
|
OBJECT_OT_pose_key_set_pose,
|
||||||
@ -871,6 +875,6 @@ registry = [
|
|||||||
OBJECT_OT_create_shape_key_for_pose,
|
OBJECT_OT_create_shape_key_for_pose,
|
||||||
OBJECT_OT_pose_key_clamp_influence,
|
OBJECT_OT_pose_key_clamp_influence,
|
||||||
OBJECT_OT_pose_key_place_objects_in_grid,
|
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,
|
OBJECT_OT_pose_key_copy_data,
|
||||||
]
|
]
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import bpy
|
import bpy
|
||||||
from bpy.types import Object, Panel, UIList, Menu
|
from bpy.types import Object, Panel, UIList, Menu
|
||||||
from bl_ui.properties_data_mesh import DATA_PT_shape_keys
|
from bl_ui.properties_data_mesh import DATA_PT_shape_keys
|
||||||
from .ui_list import draw_ui_list
|
|
||||||
from bpy.props import EnumProperty
|
from bpy.props import EnumProperty
|
||||||
|
|
||||||
|
from .ui_list import draw_ui_list
|
||||||
|
from .ops import get_deforming_armature
|
||||||
from .prefs import get_addon_prefs
|
from .prefs import get_addon_prefs
|
||||||
|
|
||||||
|
|
||||||
@ -72,7 +73,7 @@ class MESH_PT_pose_keys(Panel):
|
|||||||
bl_region_type = 'WINDOW'
|
bl_region_type = 'WINDOW'
|
||||||
bl_context = 'data'
|
bl_context = 'data'
|
||||||
bl_options = {'DEFAULT_CLOSED'}
|
bl_options = {'DEFAULT_CLOSED'}
|
||||||
bl_label = "Shape/Pose Keys"
|
bl_label = "Pose Shape Keys"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
@ -81,16 +82,17 @@ class MESH_PT_pose_keys(Panel):
|
|||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
obj = context.object
|
obj = context.object
|
||||||
mesh = obj.data
|
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':
|
if mesh.shape_key_ui_type == 'DEFAULT':
|
||||||
return DATA_PT_shape_keys.draw(self, context)
|
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.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
|
return
|
||||||
|
|
||||||
if mesh.shape_keys and not mesh.shape_keys.use_relative:
|
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
|
idx = context.object.data.active_pose_key_index
|
||||||
active_posekey = context.object.data.pose_keys[idx]
|
active_posekey = context.object.data.pose_keys[idx]
|
||||||
|
|
||||||
col = layout.column(align=True)
|
action_split = layout.row().split(factor=0.4, align=True)
|
||||||
col.prop(active_posekey, 'action')
|
action_split.alignment='RIGHT'
|
||||||
|
action_split.label(text="Action")
|
||||||
|
row = action_split.row(align=True)
|
||||||
|
icon = 'FORWARD'
|
||||||
if active_posekey.action:
|
if active_posekey.action:
|
||||||
col.prop(active_posekey, 'frame')
|
icon = 'FILE_REFRESH'
|
||||||
|
row.operator('object.posekey_auto_init', text="", icon=icon)
|
||||||
if active_posekey.storage_object:
|
row.prop(active_posekey, 'action', text="")
|
||||||
row = layout.row()
|
layout.prop(active_posekey, 'frame')
|
||||||
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
|
|
||||||
|
|
||||||
layout.separator()
|
layout.separator()
|
||||||
col = layout.column(align=True)
|
|
||||||
col.operator('object.posekey_set_pose', text="Set Pose", icon="ARMATURE_DATA")
|
|
||||||
col.separator()
|
|
||||||
|
|
||||||
row = col.row()
|
layout.operator('object.posekey_set_pose', text="Set Pose", icon="ARMATURE_DATA")
|
||||||
row.operator('object.posekey_save', text="Overwrite Storage Object", icon="FILE_TICK")
|
|
||||||
row.operator('object.posekey_push', text="Overwrite Shape Keys", icon="IMPORT")
|
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):
|
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
|
idx = context.object.data.active_pose_key_index
|
||||||
active_posekey = context.object.data.pose_keys[idx]
|
active_posekey = context.object.data.pose_keys[idx]
|
||||||
|
|
||||||
|
layout.operator('object.posekey_push', text="Overwrite Shape Keys", icon="IMPORT")
|
||||||
|
|
||||||
draw_ui_list(
|
draw_ui_list(
|
||||||
layout,
|
layout,
|
||||||
context,
|
context,
|
||||||
@ -250,7 +251,7 @@ def register():
|
|||||||
('DEFAULT', 'Shape Keys', "Show a flat list of shape keys"),
|
('DEFAULT', 'Shape Keys', "Show a flat list of shape keys"),
|
||||||
(
|
(
|
||||||
'POSE_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",
|
"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