Pose Shape Keys: Major Update #321
@ -1,8 +1,9 @@
|
||||
import bpy
|
||||
from bpy.types import Object, Operator
|
||||
from bpy.props import StringProperty, BoolProperty
|
||||
from mathutils import Vector
|
||||
from mathutils import Vector, Euler
|
||||
from math import sqrt
|
||||
from collections import OrderedDict
|
||||
|
||||
from .symmetrize_shape_key import mirror_mesh
|
||||
from .prefs import get_addon_prefs
|
||||
@ -48,7 +49,7 @@ class OBJECT_OT_pose_key_add(UILIST_OT_Entry_Add, Operator):
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout.column()
|
||||
layout.use_property_split=True
|
||||
layout.use_property_split = True
|
||||
|
||||
layout.prop(self, 'pose_key_name')
|
||||
if not self.pose_key_name:
|
||||
@ -720,17 +721,18 @@ class OBJECT_OT_pose_key_shape_add(UILIST_OT_Entry_Add, Operator):
|
||||
def update_create_vg(self, context):
|
||||
if self.create_vg:
|
||||
self.vg_name = self.sk_name
|
||||
|
||||
create_vg: BoolProperty(
|
||||
name="Create New Vertex Group",
|
||||
description="Create a new blank Vertex Group as a mask for this shape key. This means the shape key won't work until this mask is authored",
|
||||
default=False,
|
||||
update=update_create_vg
|
||||
update=update_create_vg,
|
||||
)
|
||||
|
||||
create_slot: BoolProperty(
|
||||
name="Create New Slot",
|
||||
description="Internal. Whether to assign the chosen (or created) shape key to the current slot, or to create a new one",
|
||||
default=True
|
||||
default=True,
|
||||
)
|
||||
|
||||
def invoke(self, context, event):
|
||||
@ -750,7 +752,7 @@ class OBJECT_OT_pose_key_shape_add(UILIST_OT_Entry_Add, Operator):
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout.column()
|
||||
layout.use_property_split=True
|
||||
layout.use_property_split = True
|
||||
|
||||
obj = context.object
|
||||
|
||||
@ -766,8 +768,10 @@ class OBJECT_OT_pose_key_shape_add(UILIST_OT_Entry_Add, Operator):
|
||||
row = layout.row(align=True)
|
||||
if self.create_vg:
|
||||
if obj.vertex_groups.get(self.vg_name):
|
||||
row.alert=True
|
||||
layout.label(text="Cannot create that vertex group because it already exists!", icon='ERROR')
|
||||
row.alert = True
|
||||
layout.label(
|
||||
text="Cannot create that vertex group because it already exists!", icon='ERROR'
|
||||
)
|
||||
row.prop(self, 'vg_name', icon='GROUP_VERTEX')
|
||||
else:
|
||||
row.prop_search(self, 'vg_name', obj, "vertex_groups")
|
||||
@ -867,6 +871,119 @@ class OBJECT_OT_pose_key_shape_remove(UILIST_OT_Entry_Remove, OperatorWithWarnin
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class OBJECT_OT_pose_key_magic_driver(Operator):
|
||||
"""Automatically drive this shape key based on current pose"""
|
||||
|
||||
bl_idname = "object.posekey_magic_driver"
|
||||
bl_label = "Auto-initialize Driver"
|
||||
bl_options = {'UNDO', 'REGISTER', 'INTERNAL'}
|
||||
|
||||
key_name: StringProperty()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return poll_correct_pose_key_pose(cls, context)
|
||||
|
||||
@staticmethod
|
||||
def get_posed_channels(context) -> OrderedDict[str, tuple[str, float]]:
|
||||
obj = context.object
|
||||
arm_ob = get_deforming_armature(obj)
|
||||
|
||||
channels = OrderedDict()
|
||||
|
||||
for pb in arm_ob.pose.bones:
|
||||
bone_channels = OrderedDict({'loc' : [], 'rot': [], 'scale': []})
|
||||
|
||||
for axis in "xyz":
|
||||
value = getattr(pb.location, axis)
|
||||
if value != 0.0:
|
||||
bone_channels['loc'].append((axis.upper(), value))
|
||||
channels[pb.name] = bone_channels
|
||||
|
||||
if len(pb.rotation_mode) == 3:
|
||||
# Euler rotation: Check each axis.
|
||||
value = getattr(pb.rotation_euler, axis)
|
||||
if value != 0.0:
|
||||
bone_channels['rot'].append((axis.upper(), value))
|
||||
channels[pb.name] = bone_channels
|
||||
else:
|
||||
# Quat/etc: Add variables for all 3 axes.
|
||||
euler_rot = pb.matrix_channel.to_euler()
|
||||
if euler_rot != Euler((0, 0, 0)):
|
||||
value = getattr(euler_rot, axis)
|
||||
bone_channels['rot'].append((axis.upper(), value))
|
||||
channels[pb.name] = bone_channels
|
||||
|
||||
value = getattr(pb.scale, axis)
|
||||
if value != 1.0:
|
||||
bone_channels['scale'].append((axis.upper(), value))
|
||||
channels[pb.name] = bone_channels
|
||||
|
||||
return channels
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.posed_channels = self.get_posed_channels(context)
|
||||
return context.window_manager.invoke_props_dialog(self, width=300)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text="Driver will be created based on these transforms:")
|
||||
|
||||
obj = context.object
|
||||
arm_ob = get_deforming_armature(obj)
|
||||
|
||||
col = layout.column(align=True)
|
||||
for bone_name, transforms in self.posed_channels.items():
|
||||
pb = arm_ob.pose.bones.get(bone_name)
|
||||
bone_box = col.box()
|
||||
bone_box.prop(pb, 'name', icon='BONE_DATA', text="", emboss=False)
|
||||
for transform, trans_inf in transforms.items():
|
||||
axes = [inf[0] for inf, val in trans_inf]
|
||||
if not axes:
|
||||
continue
|
||||
|
||||
if transform == 'rot':
|
||||
icon = 'CON_ROTLIKE'
|
||||
elif transform == 'scale':
|
||||
icon = 'CON_SIZELIKE'
|
||||
else:
|
||||
icon = 'CON_LOCLIKE'
|
||||
bone_box.row().label(text=", ".join(axes), icon=icon)
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.object
|
||||
arm_ob = get_deforming_armature(obj)
|
||||
key_block = obj.data.shape_keys.key_blocks.get(self.key_name)
|
||||
|
||||
key_block.driver_remove('value')
|
||||
fc = key_block.driver_add('value')
|
||||
drv = fc.driver
|
||||
|
||||
expressions = []
|
||||
|
||||
for bone_name, transforms in self.posed_channels.items():
|
||||
for transform, trans_inf in transforms.items():
|
||||
for axis, value in trans_inf:
|
||||
transf_type = transform.upper()+"_"+axis
|
||||
var = drv.variables.new()
|
||||
var.name = bone_name.replace(" ", "_") + "_" + transf_type.lower()
|
||||
var.type = 'TRANSFORMS'
|
||||
var.targets[0].id = arm_ob
|
||||
var.targets[0].bone_target = bone_name
|
||||
var.targets[0].transform_type = transf_type
|
||||
var.targets[0].rotation_mode = 'SWING_TWIST_Y'
|
||||
var.targets[0].transform_space = 'LOCAL_SPACE'
|
||||
if transf_type.startswith("SCALE"):
|
||||
expressions.append(f"((1-{var.name})/{value})")
|
||||
else:
|
||||
expressions.append(f"({var.name}/{value})")
|
||||
|
||||
drv.expression = " * ".join(expressions)
|
||||
|
||||
self.report({'INFO'}, "Created automatic driver.")
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def get_deforming_armature(mesh_ob: Object) -> Object | None:
|
||||
for mod in mesh_ob.modifiers:
|
||||
if mod.type == 'ARMATURE':
|
||||
@ -999,4 +1116,5 @@ registry = [
|
||||
OBJECT_OT_pose_key_copy_data,
|
||||
OBJECT_OT_pose_key_shape_add,
|
||||
OBJECT_OT_pose_key_shape_remove,
|
||||
OBJECT_OT_pose_key_magic_driver,
|
||||
]
|
||||
|
@ -133,7 +133,7 @@ class MESH_PT_shape_key_subpanel(Panel):
|
||||
if not (obj and obj.data and obj.data.shape_key_ui_type=='POSE_KEYS'):
|
||||
return False
|
||||
try:
|
||||
return poll_correct_pose_key_pose(cls, context)
|
||||
return poll_correct_pose_key_pose(cls, context, demand_pose=False)
|
||||
except AttributeError:
|
||||
# Happens any time that function tries to set a poll message,
|
||||
# since panels don't have poll messages, lol.
|
||||
@ -218,6 +218,7 @@ class POSEKEYS_UL_target_shape_keys(UIList):
|
||||
):
|
||||
name_row.active = value_row.active = False
|
||||
|
||||
value_row.operator('object.posekey_magic_driver', text="", icon='DECORATE_DRIVER').key_name = key_block.name
|
||||
value_row.prop(key_block, "value", text="")
|
||||
|
||||
mute_row = split.row()
|
||||
|
Loading…
Reference in New Issue
Block a user