Pose Shape Keys: Major Update #321
@ -3,6 +3,7 @@ from bpy.types import Object, Operator
|
|||||||
from bpy.props import StringProperty
|
from bpy.props import StringProperty
|
||||||
from mathutils import Vector
|
from mathutils import Vector
|
||||||
from math import sqrt
|
from math import sqrt
|
||||||
|
|
||||||
from .symmetrize_shape_key import mirror_mesh
|
from .symmetrize_shape_key import mirror_mesh
|
||||||
from .prefs import get_addon_prefs
|
from .prefs import get_addon_prefs
|
||||||
from .ui_list import UILIST_OT_Entry_Add
|
from .ui_list import UILIST_OT_Entry_Add
|
||||||
@ -29,87 +30,99 @@ DEFORM_MODIFIERS = [
|
|||||||
GOOD_MODIFIERS = ['ARMATURE']
|
GOOD_MODIFIERS = ['ARMATURE']
|
||||||
|
|
||||||
|
|
||||||
def get_deforming_armature(mesh_ob: Object) -> Object:
|
class OBJECT_OT_pose_key_add(UILIST_OT_Entry_Add, Operator):
|
||||||
for mod in mesh_ob.modifiers:
|
"""Add Pose Shape Key"""
|
||||||
if mod.type == 'ARMATURE':
|
|
||||||
return mod.object
|
|
||||||
|
|
||||||
|
bl_idname = "object.posekey_add"
|
||||||
class OBJECT_OT_create_shape_key_for_pose(Operator):
|
bl_label = "Add Pose Shape Key"
|
||||||
"""Create and assign a Shape Key"""
|
|
||||||
|
|
||||||
bl_idname = "object.create_shape_key_for_pose"
|
|
||||||
bl_label = "Create Shape Key"
|
|
||||||
bl_options = {'UNDO', 'REGISTER', 'INTERNAL'}
|
bl_options = {'UNDO', 'REGISTER', 'INTERNAL'}
|
||||||
bl_property = "sk_name"
|
bl_property = "pose_key_name" # Focus the text input box
|
||||||
|
|
||||||
def update_sk_name(self, context):
|
list_context_path: StringProperty()
|
||||||
def set_vg(vg_name):
|
active_idx_context_path: StringProperty()
|
||||||
obj = context.object
|
|
||||||
vg = obj.vertex_groups.get(vg_name)
|
|
||||||
if vg:
|
|
||||||
self.vg_name = vg.name
|
|
||||||
return vg
|
|
||||||
|
|
||||||
obj = context.object
|
pose_key_name: StringProperty(name="Name", default="Pose Key")
|
||||||
vg = set_vg(self.sk_name)
|
|
||||||
if not vg and self.sk_name.endswith(".L"):
|
|
||||||
vg = set_vg("Side.L")
|
|
||||||
if not vg and self.sk_name.endswith(".R"):
|
|
||||||
vg = set_vg("Side.R")
|
|
||||||
|
|
||||||
sk_name: StringProperty(
|
|
||||||
name="Name",
|
|
||||||
description="Name to set for the new shape key",
|
|
||||||
default="Key",
|
|
||||||
update=update_sk_name,
|
|
||||||
)
|
|
||||||
vg_name: StringProperty(
|
|
||||||
name="Vertex Group",
|
|
||||||
description="Vertex Group to assign as the masking group of this shape key",
|
|
||||||
default="",
|
|
||||||
)
|
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
obj = context.object
|
|
||||||
if obj.data.shape_keys:
|
|
||||||
self.sk_name = f"Key {len(obj.data.shape_keys.key_blocks)}"
|
|
||||||
else:
|
|
||||||
self.sk_name = "Key"
|
|
||||||
|
|
||||||
pose_key = get_active_pose_key(obj)
|
|
||||||
if pose_key.name:
|
|
||||||
self.sk_name = pose_key.name
|
|
||||||
|
|
||||||
return context.window_manager.invoke_props_dialog(self)
|
return context.window_manager.invoke_props_dialog(self)
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
self.layout.prop(self, 'pose_key_name')
|
||||||
layout.prop(self, 'sk_name')
|
if not self.pose_key_name:
|
||||||
obj = context.object
|
self.layout.alert = True
|
||||||
layout.prop_search(self, 'vg_name', obj, "vertex_groups")
|
self.layout.label(text="Name cannot be empty.", icon='ERROR')
|
||||||
|
|
||||||
def execute(self, context):
|
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_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
|
obj = context.object
|
||||||
|
|
||||||
# Ensure Basis shape key
|
|
||||||
if not obj.data.shape_keys:
|
|
||||||
basis = obj.shape_key_add()
|
|
||||||
basis.name = "Basis"
|
|
||||||
obj.data.update()
|
|
||||||
|
|
||||||
# Add new shape key
|
|
||||||
new_sk = obj.shape_key_add()
|
|
||||||
new_sk.name = self.sk_name
|
|
||||||
new_sk.value = 1
|
|
||||||
if self.vg_name:
|
|
||||||
new_sk.vertex_group = self.vg_name
|
|
||||||
|
|
||||||
pose_key = get_active_pose_key(obj)
|
pose_key = get_active_pose_key(obj)
|
||||||
target = pose_key.target_shapes[pose_key.active_target_shape_index]
|
if (
|
||||||
target.name = new_sk.name
|
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
|
||||||
|
|
||||||
self.report({'INFO'}, f"Added shape key {new_sk.name}.")
|
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_set_pose(Operator):
|
||||||
|
"""Reset the rig, then set the above Action and frame number"""
|
||||||
|
|
||||||
|
bl_idname = "object.posekey_set_pose"
|
||||||
|
bl_label = "Set Pose"
|
||||||
|
bl_options = {'UNDO', 'REGISTER', 'INTERNAL'}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return poll_correct_pose_key_pose(cls, context, demand_pose=False)
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
set_pose_of_active_pose_key(context)
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
@ -209,212 +222,6 @@ class OperatorWithWarning:
|
|||||||
raise NotImplemented
|
raise NotImplemented
|
||||||
|
|
||||||
|
|
||||||
def reset_rig(rig, *, reset_transforms=True, reset_props=True, pbones=[]):
|
|
||||||
if not pbones:
|
|
||||||
pbones = rig.pose.bones
|
|
||||||
for pb in pbones:
|
|
||||||
if reset_transforms:
|
|
||||||
pb.location = (0, 0, 0)
|
|
||||||
pb.rotation_euler = (0, 0, 0)
|
|
||||||
pb.rotation_quaternion = (1, 0, 0, 0)
|
|
||||||
pb.scale = (1, 1, 1)
|
|
||||||
|
|
||||||
if not reset_props or len(pb.keys()) == 0:
|
|
||||||
continue
|
|
||||||
|
|
||||||
rna_properties = [prop.identifier for prop in pb.bl_rna.properties if prop.is_runtime]
|
|
||||||
|
|
||||||
# Reset custom property values to their default value
|
|
||||||
for key in pb.keys():
|
|
||||||
if key.startswith("$"):
|
|
||||||
continue
|
|
||||||
if key in rna_properties:
|
|
||||||
continue # Addon defined property.
|
|
||||||
|
|
||||||
property_settings = None
|
|
||||||
try:
|
|
||||||
property_settings = pb.id_properties_ui(key)
|
|
||||||
if not property_settings:
|
|
||||||
continue
|
|
||||||
property_settings = property_settings.as_dict()
|
|
||||||
if not 'default' in property_settings:
|
|
||||||
continue
|
|
||||||
except TypeError:
|
|
||||||
# Some properties don't support UI data, and so don't have a default value. (like addon PropertyGroups)
|
|
||||||
pass
|
|
||||||
|
|
||||||
if not property_settings:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if type(pb[key]) not in (float, int, bool):
|
|
||||||
continue
|
|
||||||
pb[key] = property_settings['default']
|
|
||||||
|
|
||||||
|
|
||||||
def set_pose_of_active_pose_key(context):
|
|
||||||
rigged_ob = context.object
|
|
||||||
pose_key = rigged_ob.data.pose_keys[rigged_ob.data.active_pose_key_index]
|
|
||||||
|
|
||||||
arm_ob = get_deforming_armature(rigged_ob)
|
|
||||||
reset_rig(arm_ob)
|
|
||||||
if pose_key.action:
|
|
||||||
# Set Action and Frame to get the right pose
|
|
||||||
arm_ob.animation_data.action = pose_key.action
|
|
||||||
context.scene.frame_current = pose_key.frame
|
|
||||||
|
|
||||||
|
|
||||||
class OBJECT_OT_pose_key_set_pose(Operator):
|
|
||||||
"""Reset the rig, then set the above Action and frame number"""
|
|
||||||
|
|
||||||
bl_idname = "object.posekey_set_pose"
|
|
||||||
bl_label = "Set Pose"
|
|
||||||
bl_options = {'UNDO', 'REGISTER', 'INTERNAL'}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context):
|
|
||||||
return poll_correct_pose_key_pose(cls, context, demand_pose=False)
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
set_pose_of_active_pose_key(context)
|
|
||||||
return {'FINISHED'}
|
|
||||||
|
|
||||||
|
|
||||||
def get_active_pose_key(obj):
|
|
||||||
if obj.type != 'MESH':
|
|
||||||
return
|
|
||||||
if len(obj.data.pose_keys) == 0:
|
|
||||||
return
|
|
||||||
|
|
||||||
return obj.data.pose_keys[obj.data.active_pose_key_index]
|
|
||||||
|
|
||||||
|
|
||||||
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:
|
|
||||||
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
|
|
||||||
|
|
||||||
if not pose_key.action:
|
|
||||||
operator.poll_message_set("An Action must be associated with the Pose Shape Key.")
|
|
||||||
return False
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
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'}
|
|
||||||
bl_property = "pose_key_name" # Focus the text input box
|
|
||||||
|
|
||||||
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_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 deformed mesh vertex positions of the current pose into the Storage Object"""
|
"""Save the deformed mesh vertex positions of the current pose into the Storage Object"""
|
||||||
|
|
||||||
@ -675,6 +482,84 @@ class OBJECT_OT_pose_key_push_all(Operator, OperatorWithWarning, SaveAndRestoreS
|
|||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
class OBJECT_OT_create_shape_key_for_pose(Operator):
|
||||||
|
"""Create and assign a Shape Key"""
|
||||||
|
|
||||||
|
bl_idname = "object.create_shape_key_for_pose"
|
||||||
|
bl_label = "Create Shape Key"
|
||||||
|
bl_options = {'UNDO', 'REGISTER', 'INTERNAL'}
|
||||||
|
bl_property = "sk_name"
|
||||||
|
|
||||||
|
def update_sk_name(self, context):
|
||||||
|
def set_vg(vg_name):
|
||||||
|
obj = context.object
|
||||||
|
vg = obj.vertex_groups.get(vg_name)
|
||||||
|
if vg:
|
||||||
|
self.vg_name = vg.name
|
||||||
|
return vg
|
||||||
|
|
||||||
|
obj = context.object
|
||||||
|
vg = set_vg(self.sk_name)
|
||||||
|
if not vg and self.sk_name.endswith(".L"):
|
||||||
|
vg = set_vg("Side.L")
|
||||||
|
if not vg and self.sk_name.endswith(".R"):
|
||||||
|
vg = set_vg("Side.R")
|
||||||
|
|
||||||
|
sk_name: StringProperty(
|
||||||
|
name="Name",
|
||||||
|
description="Name to set for the new shape key",
|
||||||
|
default="Key",
|
||||||
|
update=update_sk_name,
|
||||||
|
)
|
||||||
|
vg_name: StringProperty(
|
||||||
|
name="Vertex Group",
|
||||||
|
description="Vertex Group to assign as the masking group of this shape key",
|
||||||
|
default="",
|
||||||
|
)
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
obj = context.object
|
||||||
|
if obj.data.shape_keys:
|
||||||
|
self.sk_name = f"Key {len(obj.data.shape_keys.key_blocks)}"
|
||||||
|
else:
|
||||||
|
self.sk_name = "Key"
|
||||||
|
|
||||||
|
pose_key = get_active_pose_key(obj)
|
||||||
|
if pose_key.name:
|
||||||
|
self.sk_name = pose_key.name
|
||||||
|
|
||||||
|
return context.window_manager.invoke_props_dialog(self)
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.prop(self, 'sk_name')
|
||||||
|
obj = context.object
|
||||||
|
layout.prop_search(self, 'vg_name', obj, "vertex_groups")
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
obj = context.object
|
||||||
|
|
||||||
|
# Ensure Basis shape key
|
||||||
|
if not obj.data.shape_keys:
|
||||||
|
basis = obj.shape_key_add()
|
||||||
|
basis.name = "Basis"
|
||||||
|
obj.data.update()
|
||||||
|
|
||||||
|
# Add new shape key
|
||||||
|
new_sk = obj.shape_key_add()
|
||||||
|
new_sk.name = self.sk_name
|
||||||
|
new_sk.value = 1
|
||||||
|
if self.vg_name:
|
||||||
|
new_sk.vertex_group = self.vg_name
|
||||||
|
|
||||||
|
pose_key = get_active_pose_key(obj)
|
||||||
|
target = pose_key.target_shapes[pose_key.active_target_shape_index]
|
||||||
|
target.name = new_sk.name
|
||||||
|
|
||||||
|
self.report({'INFO'}, f"Added shape key {new_sk.name}.")
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
class OBJECT_OT_pose_key_clamp_influence(Operator):
|
class OBJECT_OT_pose_key_clamp_influence(Operator):
|
||||||
"""Clamp the influence of this pose key's shape keys to 1.0 for each vertex, by normalizing the vertex weight mask values of vertices where the total influence is greater than 1"""
|
"""Clamp the influence of this pose key's shape keys to 1.0 for each vertex, by normalizing the vertex weight mask values of vertices where the total influence is greater than 1"""
|
||||||
|
|
||||||
@ -865,6 +750,125 @@ class OBJECT_OT_pose_key_copy_data(Operator):
|
|||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
def get_deforming_armature(mesh_ob: Object) -> Object | None:
|
||||||
|
for mod in mesh_ob.modifiers:
|
||||||
|
if mod.type == 'ARMATURE':
|
||||||
|
return mod.object
|
||||||
|
|
||||||
|
|
||||||
|
def reset_rig(rig, *, reset_transforms=True, reset_props=True, pbones=[]):
|
||||||
|
if not pbones:
|
||||||
|
pbones = rig.pose.bones
|
||||||
|
for pb in pbones:
|
||||||
|
if reset_transforms:
|
||||||
|
pb.location = (0, 0, 0)
|
||||||
|
pb.rotation_euler = (0, 0, 0)
|
||||||
|
pb.rotation_quaternion = (1, 0, 0, 0)
|
||||||
|
pb.scale = (1, 1, 1)
|
||||||
|
|
||||||
|
if not reset_props or len(pb.keys()) == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
rna_properties = [prop.identifier for prop in pb.bl_rna.properties if prop.is_runtime]
|
||||||
|
|
||||||
|
# Reset custom property values to their default value
|
||||||
|
for key in pb.keys():
|
||||||
|
if key.startswith("$"):
|
||||||
|
continue
|
||||||
|
if key in rna_properties:
|
||||||
|
continue # Addon defined property.
|
||||||
|
|
||||||
|
property_settings = None
|
||||||
|
try:
|
||||||
|
property_settings = pb.id_properties_ui(key)
|
||||||
|
if not property_settings:
|
||||||
|
continue
|
||||||
|
property_settings = property_settings.as_dict()
|
||||||
|
if not 'default' in property_settings:
|
||||||
|
continue
|
||||||
|
except TypeError:
|
||||||
|
# Some properties don't support UI data, and so don't have a default value. (like addon PropertyGroups)
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not property_settings:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if type(pb[key]) not in (float, int, bool):
|
||||||
|
continue
|
||||||
|
pb[key] = property_settings['default']
|
||||||
|
|
||||||
|
|
||||||
|
def set_pose_of_active_pose_key(context):
|
||||||
|
rigged_ob = context.object
|
||||||
|
pose_key = rigged_ob.data.pose_keys[rigged_ob.data.active_pose_key_index]
|
||||||
|
|
||||||
|
arm_ob = get_deforming_armature(rigged_ob)
|
||||||
|
reset_rig(arm_ob)
|
||||||
|
if pose_key.action:
|
||||||
|
# Set Action and Frame to get the right pose
|
||||||
|
arm_ob.animation_data.action = pose_key.action
|
||||||
|
context.scene.frame_current = pose_key.frame
|
||||||
|
|
||||||
|
|
||||||
|
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:
|
||||||
|
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
|
||||||
|
|
||||||
|
if not pose_key.action:
|
||||||
|
operator.poll_message_set("An Action must be associated with the Pose Shape Key.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
def get_active_pose_key(obj):
|
||||||
|
if obj.type != 'MESH':
|
||||||
|
return
|
||||||
|
if len(obj.data.pose_keys) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
return obj.data.pose_keys[obj.data.active_pose_key_index]
|
||||||
|
|
||||||
|
|
||||||
registry = [
|
registry = [
|
||||||
OBJECT_OT_pose_key_auto_init,
|
OBJECT_OT_pose_key_auto_init,
|
||||||
OBJECT_OT_pose_key_add,
|
OBJECT_OT_pose_key_add,
|
||||||
|
@ -1,73 +1,13 @@
|
|||||||
import bpy
|
import bpy
|
||||||
from bpy.types import Object, Panel, UIList, Menu
|
from bpy.types import 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 bpy.props import EnumProperty
|
from bpy.props import EnumProperty
|
||||||
|
|
||||||
from .ui_list import draw_ui_list
|
from .ui_list import draw_ui_list
|
||||||
from .ops import get_deforming_armature
|
from .ops import get_deforming_armature, poll_correct_pose_key_pose
|
||||||
from .prefs import get_addon_prefs
|
from .prefs import get_addon_prefs
|
||||||
|
|
||||||
|
|
||||||
class CK_UL_pose_keys(UIList):
|
|
||||||
def draw_item(self, context, layout, data, item, _icon, _active_data, _active_propname):
|
|
||||||
pose_key = item
|
|
||||||
|
|
||||||
if self.layout_type != 'DEFAULT':
|
|
||||||
# Other layout types not supported by this UIList.
|
|
||||||
return
|
|
||||||
|
|
||||||
split = layout.row().split(factor=0.7, align=True)
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
class CK_UL_target_keys(UIList):
|
|
||||||
def draw_item(self, context, layout, data, item, _icon, _active_data, _active_propname):
|
|
||||||
obj = context.object
|
|
||||||
pose_key_target = item
|
|
||||||
key_block = pose_key_target.key_block
|
|
||||||
|
|
||||||
if self.layout_type != 'DEFAULT':
|
|
||||||
# Other layout types not supported by this UIList.
|
|
||||||
return
|
|
||||||
|
|
||||||
split = layout.row().split(factor=0.7, align=True)
|
|
||||||
|
|
||||||
name_row = split.row()
|
|
||||||
name_row.prop(pose_key_target, 'name', text="", emboss=False, icon='SHAPEKEY_DATA')
|
|
||||||
|
|
||||||
value_row = split.row(align=True)
|
|
||||||
value_row.emboss = 'NONE_OR_STATUS'
|
|
||||||
if not key_block:
|
|
||||||
return
|
|
||||||
if (
|
|
||||||
key_block.mute
|
|
||||||
or (obj.mode == 'EDIT' and not (obj.use_shape_key_edit_mode and obj.type == 'MESH'))
|
|
||||||
or (obj.show_only_shape_key and key_block != obj.active_shape_key)
|
|
||||||
):
|
|
||||||
name_row.active = value_row.active = False
|
|
||||||
|
|
||||||
value_row.prop(key_block, "value", text="")
|
|
||||||
|
|
||||||
mute_row = split.row()
|
|
||||||
mute_row.alignment = 'RIGHT'
|
|
||||||
mute_row.prop(key_block, 'mute', emboss=False, text="")
|
|
||||||
|
|
||||||
|
|
||||||
def obj_has_armature_mod(obj: Object) -> bool:
|
|
||||||
for mod in obj.modifiers:
|
|
||||||
if mod.type == 'ARMATURE':
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class MESH_PT_pose_keys(Panel):
|
class MESH_PT_pose_keys(Panel):
|
||||||
bl_space_type = 'PROPERTIES'
|
bl_space_type = 'PROPERTIES'
|
||||||
bl_region_type = 'WINDOW'
|
bl_region_type = 'WINDOW'
|
||||||
@ -106,7 +46,7 @@ class MESH_PT_pose_keys(Panel):
|
|||||||
draw_ui_list(
|
draw_ui_list(
|
||||||
groups_col,
|
groups_col,
|
||||||
context,
|
context,
|
||||||
class_name='CK_UL_pose_keys',
|
class_name='POSEKEYS_UL_pose_keys',
|
||||||
list_context_path='object.data.pose_keys',
|
list_context_path='object.data.pose_keys',
|
||||||
active_idx_context_path='object.data.active_pose_key_index',
|
active_idx_context_path='object.data.active_pose_key_index',
|
||||||
menu_class_name='MESH_MT_pose_key_utils',
|
menu_class_name='MESH_MT_pose_key_utils',
|
||||||
@ -148,6 +88,37 @@ class MESH_PT_pose_keys(Panel):
|
|||||||
row.operator('object.posekey_jump_to_storage', text="", icon='RESTRICT_SELECT_OFF')
|
row.operator('object.posekey_jump_to_storage', text="", icon='RESTRICT_SELECT_OFF')
|
||||||
|
|
||||||
|
|
||||||
|
class POSEKEYS_UL_pose_keys(UIList):
|
||||||
|
def draw_item(self, context, layout, data, item, _icon, _active_data, _active_propname):
|
||||||
|
pose_key = item
|
||||||
|
|
||||||
|
if self.layout_type != 'DEFAULT':
|
||||||
|
# Other layout types not supported by this UIList.
|
||||||
|
return
|
||||||
|
|
||||||
|
split = layout.row().split(factor=0.7, align=True)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
class MESH_MT_pose_key_utils(Menu):
|
||||||
|
bl_label = "Pose Key Utilities"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.operator('object.posekey_object_grid', icon='LIGHTPROBE_VOLUME')
|
||||||
|
layout.operator('object.posekey_push_all', icon='WORLD')
|
||||||
|
layout.operator('object.posekey_clamp_influence', icon='NORMALIZE_FCURVES')
|
||||||
|
layout.operator('object.posekey_copy_data', icon='PASTEDOWN')
|
||||||
|
|
||||||
|
|
||||||
class MESH_PT_shape_key_subpanel(Panel):
|
class MESH_PT_shape_key_subpanel(Panel):
|
||||||
bl_space_type = 'PROPERTIES'
|
bl_space_type = 'PROPERTIES'
|
||||||
bl_region_type = 'WINDOW'
|
bl_region_type = 'WINDOW'
|
||||||
@ -158,13 +129,12 @@ class MESH_PT_shape_key_subpanel(Panel):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
obj = context.object
|
try:
|
||||||
return (
|
return poll_correct_pose_key_pose(cls, context)
|
||||||
obj.data.shape_key_ui_type == 'POSE_KEYS'
|
except AttributeError:
|
||||||
and len(obj.data.pose_keys) > 0
|
# Happens any time that function tries to set a poll message,
|
||||||
and obj.data.pose_keys[obj.data.active_pose_key_index].storage_object
|
# since panels don't have poll messages, lol.
|
||||||
and obj_has_armature_mod(obj)
|
return False
|
||||||
)
|
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
obj = context.object
|
obj = context.object
|
||||||
@ -182,7 +152,7 @@ class MESH_PT_shape_key_subpanel(Panel):
|
|||||||
draw_ui_list(
|
draw_ui_list(
|
||||||
layout,
|
layout,
|
||||||
context,
|
context,
|
||||||
class_name='CK_UL_target_keys',
|
class_name='POSEKEYS_UL_target_shape_keys',
|
||||||
list_context_path=f'object.data.pose_keys[{idx}].target_shapes',
|
list_context_path=f'object.data.pose_keys[{idx}].target_shapes',
|
||||||
active_idx_context_path=f'object.data.pose_keys[{idx}].active_target_shape_index',
|
active_idx_context_path=f'object.data.pose_keys[{idx}].active_target_shape_index',
|
||||||
)
|
)
|
||||||
@ -217,15 +187,37 @@ class MESH_PT_shape_key_subpanel(Panel):
|
|||||||
col.row().prop(sk, 'relative_key')
|
col.row().prop(sk, 'relative_key')
|
||||||
|
|
||||||
|
|
||||||
class MESH_MT_pose_key_utils(Menu):
|
class POSEKEYS_UL_target_shape_keys(UIList):
|
||||||
bl_label = "Pose Key Utilities"
|
def draw_item(self, context, layout, data, item, _icon, _active_data, _active_propname):
|
||||||
|
obj = context.object
|
||||||
|
pose_key_target = item
|
||||||
|
key_block = pose_key_target.key_block
|
||||||
|
|
||||||
def draw(self, context):
|
if self.layout_type != 'DEFAULT':
|
||||||
layout = self.layout
|
# Other layout types not supported by this UIList.
|
||||||
layout.operator('object.posekey_object_grid', icon='LIGHTPROBE_VOLUME')
|
return
|
||||||
layout.operator('object.posekey_push_all', icon='WORLD')
|
|
||||||
layout.operator('object.posekey_clamp_influence', icon='NORMALIZE_FCURVES')
|
split = layout.row().split(factor=0.7, align=True)
|
||||||
layout.operator('object.posekey_copy_data', icon='PASTEDOWN')
|
|
||||||
|
name_row = split.row()
|
||||||
|
name_row.prop(pose_key_target, 'name', text="", emboss=False, icon='SHAPEKEY_DATA')
|
||||||
|
|
||||||
|
value_row = split.row(align=True)
|
||||||
|
value_row.emboss = 'NONE_OR_STATUS'
|
||||||
|
if not key_block:
|
||||||
|
return
|
||||||
|
if (
|
||||||
|
key_block.mute
|
||||||
|
or (obj.mode == 'EDIT' and not (obj.use_shape_key_edit_mode and obj.type == 'MESH'))
|
||||||
|
or (obj.show_only_shape_key and key_block != obj.active_shape_key)
|
||||||
|
):
|
||||||
|
name_row.active = value_row.active = False
|
||||||
|
|
||||||
|
value_row.prop(key_block, "value", text="")
|
||||||
|
|
||||||
|
mute_row = split.row()
|
||||||
|
mute_row.alignment = 'RIGHT'
|
||||||
|
mute_row.prop(key_block, 'mute', emboss=False, text="")
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -236,8 +228,8 @@ def shape_key_panel_new_poll(cls, context):
|
|||||||
|
|
||||||
|
|
||||||
registry = [
|
registry = [
|
||||||
CK_UL_pose_keys,
|
POSEKEYS_UL_pose_keys,
|
||||||
CK_UL_target_keys,
|
POSEKEYS_UL_target_shape_keys,
|
||||||
MESH_PT_pose_keys,
|
MESH_PT_pose_keys,
|
||||||
MESH_PT_shape_key_subpanel,
|
MESH_PT_shape_key_subpanel,
|
||||||
MESH_MT_pose_key_utils,
|
MESH_MT_pose_key_utils,
|
||||||
|
Loading…
Reference in New Issue
Block a user