Pose Shape Keys: Major Update #321
@ -13,14 +13,17 @@ bl_info = {
|
|||||||
import importlib
|
import importlib
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
from . import ui
|
from . import (
|
||||||
from . import pose_key
|
props,
|
||||||
from . import ui_list
|
ui,
|
||||||
from . import symmetrize_shape_key
|
ops,
|
||||||
from . import prefs
|
ui_list,
|
||||||
|
symmetrize_shape_key,
|
||||||
|
prefs,
|
||||||
|
)
|
||||||
|
|
||||||
# Each module can have register() and unregister() functions and a list of classes to register called "registry".
|
# Each module can have register() and unregister() functions and a list of classes to register called "registry".
|
||||||
modules = [prefs, ui, pose_key, ui_list, symmetrize_shape_key]
|
modules = [props, prefs, ui, ops, ui_list, symmetrize_shape_key]
|
||||||
|
|
||||||
|
|
||||||
def register_unregister_modules(modules, register: bool):
|
def register_unregister_modules(modules, register: bool):
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import bpy
|
import bpy
|
||||||
from bpy.types import PropertyGroup, Object, Operator, Action, ShapeKey, VertexGroup, MeshVertex
|
from bpy.types import Object, Operator
|
||||||
from bpy.props import PointerProperty, IntProperty, CollectionProperty, StringProperty, BoolProperty
|
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
|
||||||
@ -29,99 +29,6 @@ DEFORM_MODIFIERS = [
|
|||||||
GOOD_MODIFIERS = ['ARMATURE']
|
GOOD_MODIFIERS = ['ARMATURE']
|
||||||
|
|
||||||
|
|
||||||
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 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
|
|
||||||
try:
|
|
||||||
sk_name = self.target_shapes[self.active_target_shape_index].shape_key_name
|
|
||||||
except IndexError:
|
|
||||||
obj.active_shape_key_index = len(obj.data.shape_keys.key_blocks) - 1
|
|
||||||
return
|
|
||||||
key_block_idx = obj.data.shape_keys.key_blocks.find(sk_name)
|
|
||||||
if key_block_idx > -1:
|
|
||||||
obj.active_shape_key_index = key_block_idx
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
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,
|
|
||||||
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",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_deforming_armature(mesh_ob: Object) -> Object:
|
def get_deforming_armature(mesh_ob: Object) -> Object:
|
||||||
for mod in mesh_ob.modifiers:
|
for mod in mesh_ob.modifiers:
|
||||||
@ -956,8 +863,6 @@ class OBJECT_OT_pose_key_copy_data(Operator):
|
|||||||
|
|
||||||
|
|
||||||
registry = [
|
registry = [
|
||||||
PoseShapeKeyTarget,
|
|
||||||
PoseShapeKey,
|
|
||||||
OBJECT_OT_pose_key_add,
|
OBJECT_OT_pose_key_add,
|
||||||
OBJECT_OT_pose_key_save,
|
OBJECT_OT_pose_key_save,
|
||||||
OBJECT_OT_pose_key_set_pose,
|
OBJECT_OT_pose_key_set_pose,
|
||||||
@ -969,23 +874,3 @@ registry = [
|
|||||||
OBJECT_OT_pose_key_jump_to_shape,
|
OBJECT_OT_pose_key_jump_to_shape,
|
||||||
OBJECT_OT_pose_key_copy_data,
|
OBJECT_OT_pose_key_copy_data,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
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 = mesh.pose_keys[mesh.active_pose_key_index]
|
|
||||||
# We just want to fire the update func.
|
|
||||||
pk.active_target_shape_index = pk.active_target_shape_index
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
123
scripts-blender/addons/pose_shape_keys/props.py
Normal file
123
scripts-blender/addons/pose_shape_keys/props.py
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
import bpy
|
||||||
|
from bpy.types import PropertyGroup, Object, Action, ShapeKey
|
||||||
|
from bpy.props import PointerProperty, IntProperty, CollectionProperty, StringProperty, BoolProperty
|
||||||
|
|
||||||
|
|
||||||
|
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 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
|
||||||
|
try:
|
||||||
|
sk_name = self.target_shapes[self.active_target_shape_index].shape_key_name
|
||||||
|
except IndexError:
|
||||||
|
obj.active_shape_key_index = len(obj.data.shape_keys.key_blocks) - 1
|
||||||
|
return
|
||||||
|
key_block_idx = obj.data.shape_keys.key_blocks.find(sk_name)
|
||||||
|
if key_block_idx > -1:
|
||||||
|
obj.active_shape_key_index = key_block_idx
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
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,
|
||||||
|
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 = mesh.pose_keys[mesh.active_pose_key_index]
|
||||||
|
# We just want to fire the update func.
|
||||||
|
pk.active_target_shape_index = pk.active_target_shape_index
|
||||||
|
|
||||||
|
|
||||||
|
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
|
Loading…
Reference in New Issue
Block a user