Pose Shape Keys: Major Update #321
@ -5,6 +5,7 @@ from mathutils import Vector
|
||||
from math import sqrt
|
||||
from .symmetrize_shape_key import mirror_mesh
|
||||
from .prefs import get_addon_prefs
|
||||
from .ui_list import UILIST_OT_Entry_Add
|
||||
|
||||
# When saving or pushing shapes, disable any modifier NOT in this list.
|
||||
DEFORM_MODIFIERS = [
|
||||
@ -99,6 +100,12 @@ class PoseShapeKey(PropertyGroup):
|
||||
|
||||
active_target_shape_index: IntProperty(update=update_active_sk_index)
|
||||
|
||||
def update_name(self, context):
|
||||
if self.name == "":
|
||||
self.name = "Pose Key"
|
||||
|
||||
name: StringProperty(name="Name", update=update_name)
|
||||
|
||||
action: PointerProperty(
|
||||
name="Action",
|
||||
type=Action,
|
||||
@ -359,26 +366,7 @@ class OBJECT_OT_pose_key_set_pose(Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = context.object
|
||||
if not obj or obj.type != 'MESH':
|
||||
cls.poll_message_set("Active object must be a mesh.")
|
||||
return False
|
||||
arm_ob = get_deforming_armature(obj)
|
||||
if not arm_ob:
|
||||
cls.poll_message_set("Mesh must be deformed by an Armature modifier.")
|
||||
return False
|
||||
if not obj.data.shape_keys:
|
||||
cls.poll_message_set("Mesh must have shape keys.")
|
||||
return False
|
||||
if len(obj.data.pose_keys) == 0:
|
||||
cls.poll_message_set("Mesh must have pose keys.")
|
||||
return True
|
||||
pose_key = obj.data.pose_keys[obj.data.active_pose_key_index]
|
||||
if not pose_key.action:
|
||||
cls.poll_message_set("Pose Key must have an Action assigned.")
|
||||
return False
|
||||
|
||||
return True
|
||||
return poll_has_psk_and_deformed(cls, context)
|
||||
|
||||
def execute(self, context):
|
||||
set_pose_of_active_pose_key(context)
|
||||
@ -394,25 +382,41 @@ def get_active_pose_key(obj):
|
||||
return obj.data.pose_keys[obj.data.active_pose_key_index]
|
||||
|
||||
|
||||
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.
|
||||
"""
|
||||
def poll_has_psk_and_deformed(operator, context):
|
||||
obj = context.object
|
||||
|
||||
if not obj:
|
||||
operator.poll_message_set("There must be an active mesh object.")
|
||||
return False
|
||||
|
||||
pose_key = get_active_pose_key(obj)
|
||||
if not pose_key:
|
||||
operator.poll_message_set("A Pose Shape Key must be selected.")
|
||||
return False
|
||||
if not pose_key.name:
|
||||
operator.poll_message_set("The Pose Shape Key must be named.")
|
||||
|
||||
arm_ob = get_deforming_armature(obj)
|
||||
|
||||
if not arm_ob:
|
||||
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:
|
||||
return False
|
||||
|
||||
obj, pose_key, arm_ob = obj_psk_arm
|
||||
|
||||
# Action must exist and match.
|
||||
if not (
|
||||
@ -435,6 +439,47 @@ def poll_correct_pose_key_pose(operator, context):
|
||||
return True
|
||||
|
||||
|
||||
class OBJECT_OT_pose_key_add(UILIST_OT_Entry_Add, Operator):
|
||||
"""Add Pose Shape Key"""
|
||||
|
||||
bl_idname = "object.posekey_add"
|
||||
bl_label = "Add Pose Shape Key"
|
||||
bl_options = {'UNDO', 'REGISTER', 'INTERNAL'}
|
||||
|
||||
list_context_path: StringProperty()
|
||||
active_idx_context_path: StringProperty()
|
||||
|
||||
pose_key_name: StringProperty(name="Name", default="Pose Key")
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
def draw(self, context):
|
||||
self.layout.prop(self, 'pose_key_name')
|
||||
if not self.pose_key_name:
|
||||
self.layout.alert = True
|
||||
self.layout.label(text="Name cannot be empty.", icon='ERROR')
|
||||
|
||||
def execute(self, context):
|
||||
if not self.pose_key_name:
|
||||
self.report({'ERROR'}, "Must specify a name.")
|
||||
return {'CANCELLED'}
|
||||
|
||||
my_list = self.get_list(context)
|
||||
active_index = self.get_active_index(context)
|
||||
|
||||
to_index = active_index + 1
|
||||
if len(my_list) == 0:
|
||||
to_index = 0
|
||||
|
||||
psk = my_list.add()
|
||||
psk.name = self.pose_key_name
|
||||
my_list.move(len(my_list) - 1, to_index)
|
||||
self.set_active_index(context, to_index)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class OBJECT_OT_pose_key_save(Operator, OperatorWithWarning, SaveAndRestoreState):
|
||||
"""Save the current evaluated mesh vertex positions into the Storage Object"""
|
||||
|
||||
@ -444,6 +489,21 @@ 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
|
||||
|
||||
@ -729,7 +789,9 @@ class OBJECT_OT_pose_key_clamp_influence(Operator):
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not cls.get_affected_vertex_group_names(context.object):
|
||||
cls.poll_message_set("No shape keys of this pose shape key use vertex masks. There is nothing to clamp.")
|
||||
cls.poll_message_set(
|
||||
"No shape keys of this pose shape key use vertex masks. There is nothing to clamp."
|
||||
)
|
||||
return False
|
||||
return True
|
||||
|
||||
@ -776,7 +838,9 @@ class OBJECT_OT_pose_key_place_objects_in_grid(Operator):
|
||||
def poll(cls, context):
|
||||
"""Only available if there are any storage objects in any of the pose keys."""
|
||||
if not cls.get_storage_objects(context):
|
||||
cls.poll_message_set("This pose key has no storage objects, so there is nothing to sort into a grid.")
|
||||
cls.poll_message_set(
|
||||
"This pose key has no storage objects, so there is nothing to sort into a grid."
|
||||
)
|
||||
return False
|
||||
return True
|
||||
|
||||
@ -894,6 +958,7 @@ class OBJECT_OT_pose_key_copy_data(Operator):
|
||||
registry = [
|
||||
PoseShapeKeyTarget,
|
||||
PoseShapeKey,
|
||||
OBJECT_OT_pose_key_add,
|
||||
OBJECT_OT_pose_key_save,
|
||||
OBJECT_OT_pose_key_set_pose,
|
||||
OBJECT_OT_pose_key_push,
|
||||
|
@ -19,6 +19,11 @@ class CK_UL_pose_keys(UIList):
|
||||
|
||||
icon = 'SURFACE_NCIRCLE' if pose_key.storage_object else 'CURVE_NCIRCLE'
|
||||
name_row = split.row()
|
||||
if not pose_key.name:
|
||||
name_row.alert = True
|
||||
split = name_row.split()
|
||||
name_row = split.row()
|
||||
split.label(text="Unnamed!", icon='ERROR')
|
||||
name_row.prop(pose_key, 'name', text="", emboss=False, icon=icon)
|
||||
|
||||
|
||||
@ -103,6 +108,7 @@ class MESH_PT_pose_keys(Panel):
|
||||
list_context_path='object.data.pose_keys',
|
||||
active_idx_context_path='object.data.active_pose_key_index',
|
||||
menu_class_name='MESH_MT_pose_key_utils',
|
||||
add_op_name='object.posekey_add',
|
||||
)
|
||||
|
||||
layout.use_property_split = True
|
||||
@ -126,7 +132,10 @@ class MESH_PT_pose_keys(Panel):
|
||||
else:
|
||||
layout.operator('object.posekey_set_pose', text="Set Pose", icon="ARMATURE_DATA")
|
||||
row = layout.row()
|
||||
row.operator('object.posekey_save', text="Store Evaluated Mesh", icon="FILE_TICK")
|
||||
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
|
||||
|
||||
|
@ -118,6 +118,10 @@ def draw_ui_list(
|
||||
list_context_path='object.data.vertex_groups',
|
||||
active_idx_context_path='object.data.vertex_groups.active_index',
|
||||
insertion_operators=True,
|
||||
add_op_name=None,
|
||||
add_kwargs={},
|
||||
remove_op_name=None,
|
||||
remove_kwargs={},
|
||||
move_operators=True,
|
||||
menu_class_name='',
|
||||
**kwargs,
|
||||
@ -148,13 +152,19 @@ def draw_ui_list(
|
||||
|
||||
col = row.column()
|
||||
if insertion_operators:
|
||||
add_op = col.operator('ui.list_entry_add', text="", icon='ADD')
|
||||
op_name = add_op_name or 'ui.list_entry_add'
|
||||
add_op = col.operator(op_name, text="", icon='ADD')
|
||||
for key, value in add_kwargs.items():
|
||||
setattr(add_op, key, value)
|
||||
add_op.list_context_path = list_context_path
|
||||
add_op.active_idx_context_path = active_idx_context_path
|
||||
|
||||
row = col.row()
|
||||
row.enabled = len(my_list) > 0
|
||||
remove_op = row.operator('ui.list_entry_remove', text="", icon='REMOVE')
|
||||
op_name = remove_op_name or 'ui.list_entry_remove'
|
||||
remove_op = row.operator(op_name, text="", icon='REMOVE')
|
||||
for key, value in remove_kwargs.items():
|
||||
setattr(remove_op, key, value)
|
||||
remove_op.list_context_path = list_context_path
|
||||
remove_op.active_idx_context_path = active_idx_context_path
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user