Files
2026-03-17 14:58:51 -06:00

146 lines
4.8 KiB
Python

# SPDX-FileCopyrightText: 2025 Blender Studio Tools Authors
#
# SPDX-License-Identifier: GPL-3.0-or-later
import bpy
from bpy.types import PropertyGroup, Object, Action, ShapeKey
from bpy.props import PointerProperty, IntProperty, CollectionProperty, StringProperty, BoolProperty
from .ops import get_active_pose_key
class PoseShapeKeyTarget(PropertyGroup):
def update_name(self, context):
if self.block_name_update:
return
obj = context.object
if not obj.data.shape_keys:
return
sk = obj.data.shape_keys.key_blocks.get(self.shape_key_name)
if sk:
sk.name = self.name
self.shape_key_name = self.name
def update_shape_key_name(self, context):
self.block_name_update = True
self.name = self.shape_key_name
self.block_name_update = False
name: StringProperty(
name="Shape Key Target",
description="Name of this shape key target. Should stay in sync with the displayed name and the shape key name, unless the shape key is renamed outside of our UI",
update=update_name,
)
mirror_x: BoolProperty(
name="Mirror X",
description="Mirror the shape key on the X axis when applying the stored shape to this shape key",
default=False,
)
block_name_update: BoolProperty(
description="Flag to help keep shape key names in sync", default=False
)
shape_key_name: StringProperty(
name="Shape Key",
description="Name of the shape key to push data to",
update=update_shape_key_name,
)
@property
def has_error(self):
return self.key_block == None
@property
def key_block(self) -> list[ShapeKey]:
mesh = self.id_data
if not mesh.shape_keys:
return
return mesh.shape_keys.key_blocks.get(self.name)
class PoseShapeKey(PropertyGroup):
target_shapes: CollectionProperty(type=PoseShapeKeyTarget)
def update_active_sk_index(self, context):
obj = context.object
if not obj.data.shape_keys:
return
active_target = self.active_target
if active_target:
sk_name = self.active_target.shape_key_name
key_block_idx = obj.data.shape_keys.key_blocks.find(sk_name)
obj.active_shape_key_index = key_block_idx
else:
obj.active_shape_key_index = -1
return
# If in weight paint mode and there is a mask vertex group,
# also set that vertex group as active.
if context.mode == 'PAINT_WEIGHT':
key_block = obj.data.shape_keys.key_blocks[key_block_idx]
vg_idx = obj.vertex_groups.find(key_block.vertex_group)
if vg_idx > -1:
obj.vertex_groups.active_index = vg_idx
active_target_shape_index: IntProperty(update=update_active_sk_index)
@property
def active_target(self):
if len(self.target_shapes) == 0:
return
return self.target_shapes[self.active_target_shape_index]
@property
def has_error(self):
return self.name.strip() == "" or any([target.has_error for target in self.target_shapes])
def update_name(self, context):
if self.name.strip() == "":
self.name = "Pose Key"
name: StringProperty(name="Name", update=update_name)
action: PointerProperty(
name="Action",
type=Action,
description="Action that contains the frame that should be used when applying the stored shape as a shape key",
)
frame: IntProperty(
name="Frame",
description="Frame that should be used within the selected action when applying the stored shape as a shape key",
default=0,
)
storage_object: PointerProperty(
type=Object,
name="Storage Object",
description="Specify an object that stores the vertex position data",
)
registry = [
PoseShapeKeyTarget,
PoseShapeKey,
]
def update_posekey_index(self, context):
# Want to piggyback on update_active_sk_index() to also update the active
# shape key index when switching pose keys.
mesh = context.object.data
if mesh.pose_keys:
pk = get_active_pose_key(context.object)
if pk:
# We just want to fire the update func.
pk.active_target_shape_index = pk.active_target_shape_index
else:
# If nothing is active in the UI, avoid any shape key being active,
# so it doesn't get unintentionally modified.
context.object.data.active_shape_key_index = -1
def register():
bpy.types.Mesh.pose_keys = CollectionProperty(type=PoseShapeKey)
bpy.types.Mesh.active_pose_key_index = IntProperty(update=update_posekey_index)
def unregister():
del bpy.types.Mesh.pose_keys
del bpy.types.Mesh.active_pose_key_index