146 lines
4.8 KiB
Python
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
|