Check for keyframes before applying pose

This commit is contained in:
2025-01-18 21:41:24 -06:00
parent cb3999de33
commit 6fbfc22956
5 changed files with 936 additions and 923 deletions

View File

@ -1,48 +1,48 @@
# Damn Simple Pose Library
In Blender 3.x, a new Asset-based Pose system was introduced. That's fine and dandy, but it was not (and still is not as of Jan 2025) ready to replace the old Action-based Pose Library system.
The former Pose Library was deprecated and gutted rapidly during Blender 3.x, leaving legacy users high and dry. This addon brings the feature back as best as it can in a modern panel. Most, if not all existing Pose Libraries should work without any modifications, little new is introduced outside of string suffixes and mitigations (read further).
## Features
- Supports Blender 3.3 - 4.3 (4.1 recommended).
- New panel interface for interacting with pose libraries, inspired by [gret's Actions Panel feature](https://github.com/greisane/gret?tab=readme-ov-file#animation-actions-panel).
- The older Pose Library list layout is provided as option.
- Operators and data property [that were removed in 3.5](https://projects.blender.org/blender/blender/issues/93406) are ported from C to Python:
- `dspl.apply_pose`
- `dspl.browse_poses`
- `dspl.create_pose_library`
- `dspl.convert_pose_library`
- `dspl.add_pose`
- `dspl.move_pose`
- `dspl.remove_pose`
- `dspl.rename_pose`
- `dspl.unlink_pose_library`
- `Object.pose_library`
## Installation
1. [Download the repository as a zip](https://git.bkspl.me/breakingspell/DamnSimplePoseLibrary/archive/develop.zip), or otherwise clone the repository.
2. Install as an Add-on in Blender via Install -> Zip, and enable.
3. Optionally configure the suffix strings to fit your workflow, and whether the orhpan checker should run at startup.
## Usage
1. Open Sidebar (`N`), and choose the `Pose` tab.
2. Select existing Pose Library from drop-down, or create a new library.
### Hotkeys:
- `Shift + L` - Add/Replace Pose
- `Alt + L` - Browse Poses with Arrow Keys
### Menu Controls
- `Single click` - Apply Pose
- `Shift + Click` - Rename Pose
- `Alt + Click` - Remove Pose
- `Ctrl + Click ` - Select Pose
- Choose `Edit` for fast Move/Rename/Removal
## Considerations
- Opening older scenes will cause existing Pose Libraries to unlink from their former targets and fall into orphan state. They can be re-linked and will retain their link when saved afterwards, but would otherwise disappear if saved without linking or protecting the datablock.
- This is due to the core DNA type `poselib` having been removed, so objects will drop the data. [There is no way to prevent this](https://developer.blender.org/docs/features/core/rna/#internals) as Python ID properties cannot be present at program runtime, only once the add-on is initialized.
- To mitgate, an operator is provided to protect orphaned pose libraries: `dspl.protect_orphan_pose_library`.
- This add-on was made much easier by the `pose_markers` property being retained for converting old pose libraries to the new asset system. If they decide to remove that property as well, there will be a need to improvise an index-based lookup.
# Damn Simple Pose Library
In Blender 3.x, a new Asset-based Pose system was introduced. That's fine and dandy, but it was not (and still is not as of Jan 2025) ready to replace the old Action-based Pose Library system.
The former Pose Library was deprecated and gutted rapidly during Blender 3.x, leaving legacy users high and dry. This addon brings the feature back as best as it can in a modern panel. Most, if not all existing Pose Libraries should work without any modifications, little new is introduced outside of string suffixes and mitigations (read further).
## Features
- Supports Blender 3.3 - 4.3 (4.1 recommended).
- New panel interface for interacting with pose libraries, inspired by [gret's Actions Panel feature](https://github.com/greisane/gret?tab=readme-ov-file#animation-actions-panel).
- The older Pose Library list layout is provided as option.
- Operators and data property [that were removed in 3.5](https://projects.blender.org/blender/blender/issues/93406) are ported from C to Python:
- `dspl.apply_pose`
- `dspl.browse_poses`
- `dspl.create_pose_library`
- `dspl.convert_pose_library`
- `dspl.add_pose`
- `dspl.move_pose`
- `dspl.remove_pose`
- `dspl.rename_pose`
- `dspl.unlink_pose_library`
- `Object.pose_library`
## Installation
1. [Download the repository as a zip](https://git.bkspl.me/breakingspell/DamnSimplePoseLibrary/archive/develop.zip), or otherwise clone the repository.
2. Install as an Add-on in Blender via Install -> Zip, and enable.
3. Optionally configure the suffix strings to fit your workflow, and whether the orhpan checker should run at startup.
## Usage
1. Open Sidebar (`N`), and choose the `Pose` tab.
2. Select existing Pose Library from drop-down, or create a new library.
### Hotkeys:
- `Shift + L` - Add/Replace Pose
- `Alt + L` - Browse Poses with Arrow Keys
### Menu Controls
- `Single click` - Apply Pose
- `Shift + Click` - Rename Pose
- `Alt + Click` - Remove Pose
- `Ctrl + Click ` - Select Pose
- Choose `Edit` for fast Move/Rename/Removal
## Considerations
- Opening older scenes will cause existing Pose Libraries to unlink from their former targets and fall into orphan state. They can be re-linked and will retain their link when saved afterwards, but would otherwise disappear if saved without linking or protecting the datablock.
- This is due to the core DNA type `poselib` having been removed, so objects will drop the data. [There is no way to prevent this](https://developer.blender.org/docs/features/core/rna/#internals) as Python ID properties cannot be present at program runtime, only once the add-on is initialized.
- To mitgate, an operator is provided to protect orphaned pose libraries: `dspl.protect_orphan_pose_library`.
- This add-on was made much easier by the `pose_markers` property being retained for converting old pose libraries to the new asset system. If they decide to remove that property as well, there will be a need to improvise an index-based lookup.

409
common.py
View File

@ -1,199 +1,212 @@
import bpy
import mathutils
def getArmatureData(context):
try:
arm_object = context.active_object
if arm_object and arm_object.type == "ARMATURE":
return arm_object, getattr(arm_object, "pose_library", None)
elif arm_object and arm_object.parent and arm_object.parent.type == "ARMATURE":
return arm_object.parent, getattr(arm_object.parent, "pose_library", None)
else:
return None, None
except:
return None, None
def getArmatureAction(context):
try:
arm_object, pose_library = getArmatureData(context)
if getattr(arm_object.animation_data.action, "pose_markers", None):
return getattr(arm_object.animation_data, "action", None)
except:
pass
def searchPoseMarker(context, posename, type):
try:
arm_object, pose_library = getArmatureData(context)
if type == "marker":
return pose_library.pose_markers.get(posename, None)
if type == "frame":
return pose_library.pose_markers.get(posename, None).frame
if type == "index":
return pose_library.pose_markers.find(posename)
except:
pass
def findFcurve(context, bone_name, transform, index_int):
arm_object, pose_library = getArmatureData(context)
pose_markers = pose_library.pose_markers
active_frame = pose_markers.active.frame
fcurve_object = pose_library.fcurves.find(
'pose.bones["'+bone_name+'"].'+transform+'', index=index_int)
if hasattr(fcurve_object, 'evaluate'):
return fcurve_object.evaluate(active_frame)
else:
return None
def createFcurve(context, bone_name, transform, index_int):
arm_object, pose_library = getArmatureData(context)
pose_markers = pose_library.pose_markers
try:
pose_library.fcurves.new(
'pose.bones["'+bone_name+'"].'+transform+'', index=index_int, action_group=bone_name)
return
except:
pass
def createKeyframe(context, bone_name, transform, index_int, new_marker, loc):
arm_object, pose_library = getArmatureData(context)
pose_markers = pose_library.pose_markers
try:
pose_library.fcurves.find(
'pose.bones["'+bone_name+'"].'+transform+'', index=index_int).keyframe_points.insert(new_marker, loc)
return
except:
pass
def setKeyframesFromBones(context, arm_object, new_marker):
none_selected = True
for bone in arm_object.pose.bones:
if bone.bone.select:
none_selected = False
for bone in arm_object.pose.bones:
if bone.bone.select or none_selected == True:
bone_name = bone.name
if bone.rotation_mode == "XYZ":
rot_mode = "rotation_euler"
elif bone.rotation_mode == "YZX":
rot_mode = "rotation_euler"
elif bone.rotation_mode == "ZXY":
rot_mode = "rotation_euler"
elif bone.rotation_mode == "QUATERNION":
rot_mode = "rotation_quaternion"
else:
self.report({'WARNING'}, "DSPL: Unsupported bone: " + bone.name + ": " + bone.rotation_mode)
rot_mode = None
loc_x = bone.location[0]
loc_y = bone.location[1]
loc_z = bone.location[2]
createFcurve(context, bone_name, "location", 0)
createFcurve(context, bone_name, "location", 1)
createFcurve(context, bone_name, "location", 2)
createKeyframe(context, bone_name, "location", 0, new_marker, loc_x)
createKeyframe(context, bone_name, "location", 1, new_marker, loc_y)
createKeyframe(context, bone_name, "location", 2, new_marker, loc_z)
if rot_mode == "rotation_quaternion":
rot_w = bone.rotation_quaternion[0]
rot_x = bone.rotation_quaternion[1]
rot_y = bone.rotation_quaternion[2]
rot_z = bone.rotation_quaternion[3]
createFcurve(context, bone_name, rot_mode, 0)
createFcurve(context, bone_name, rot_mode, 1)
createFcurve(context, bone_name, rot_mode, 2)
createFcurve(context, bone_name, rot_mode, 3)
createKeyframe(context, bone_name, rot_mode, 0, new_marker, rot_w)
createKeyframe(context, bone_name, rot_mode, 1, new_marker, rot_x)
createKeyframe(context, bone_name, rot_mode, 2, new_marker, rot_y)
createKeyframe(context, bone_name, rot_mode, 3, new_marker, rot_z)
elif rot_mode == "rotation_euler":
rot_x = bone.rotation_euler[0]
rot_y = bone.rotation_euler[1]
rot_z = bone.rotation_euler[2]
createFcurve(context, bone_name, rot_mode, 0)
createFcurve(context, bone_name, rot_mode, 1)
createFcurve(context, bone_name, rot_mode, 2)
createKeyframe(context, bone_name, rot_mode, 0, new_marker, rot_x)
createKeyframe(context, bone_name, rot_mode, 1, new_marker, rot_y)
createKeyframe(context, bone_name, rot_mode, 2, new_marker, rot_z)
scl_x = bone.scale[0]
scl_y = bone.scale[1]
scl_z = bone.scale[2]
createFcurve(context, bone_name, "scale", 0)
createFcurve(context, bone_name, "scale", 1)
createFcurve(context, bone_name, "scale", 2)
createKeyframe(context, bone_name, "scale", 0, new_marker, scl_x)
createKeyframe(context, bone_name, "scale", 1, new_marker, scl_y)
createKeyframe(context, bone_name, "scale", 2, new_marker, scl_z)
def setBonesfromKeyframes(context, arm_object, active_marker):
none_selected = True
for bone in arm_object.pose.bones:
if bone.bone.select:
none_selected = False
for bone in arm_object.pose.bones:
if bone.bone.select or none_selected == True:
bone_name = bone.name
if bone.rotation_mode == "XYZ":
rot_mode = "rotation_euler"
elif bone.rotation_mode == "YZX":
rot_mode = "rotation_euler"
elif bone.rotation_mode == "ZXY":
rot_mode = "rotation_euler"
elif bone.rotation_mode == "QUATERNION":
rot_mode = "rotation_quaternion"
else:
self.report({'WARNING'}, "DSPL: Unsupported bone: " + bone.name + ": " + bone.rotation_mode)
rot_mode = None
loc_x = findFcurve(context, bone_name, "location", 0) or 0.0
loc_y = findFcurve(context, bone_name, "location", 1) or 0.0
loc_z = findFcurve(context, bone_name, "location", 2) or 0.0
if rot_mode == "rotation_quaternion":
rot_w = findFcurve(context, bone_name, rot_mode, 0) or 1.0
rot_x = findFcurve(context, bone_name, rot_mode, 1) or 0.0
rot_y = findFcurve(context, bone_name, rot_mode, 2) or 0.0
rot_z = findFcurve(context, bone_name, rot_mode, 3) or 0.0
elif rot_mode == "rotation_euler":
rot_x = findFcurve(context, bone_name, rot_mode, 0) or 0.0
rot_y = findFcurve(context, bone_name, rot_mode, 1) or 0.0
rot_z = findFcurve(context, bone_name, rot_mode, 2) or 0.0
scl_x = findFcurve(context, bone_name, "scale", 0) or 1.0
scl_y = findFcurve(context, bone_name, "scale", 1) or 1.0
scl_z = findFcurve(context, bone_name, "scale", 2) or 1.0
bone.location = mathutils.Vector((loc_x, loc_y, loc_z))
if bone.rotation_mode == "XYZ":
bone.rotation_euler = mathutils.Euler(
(rot_x, rot_y, rot_z))
elif bone.rotation_mode == "YZX":
bone.rotation_euler = mathutils.Euler(
(rot_x, rot_y, rot_z))
elif bone.rotation_mode == "ZXY":
bone.rotation_euler = mathutils.Euler(
(rot_z, rot_x, rot_y))
elif bone.rotation_mode == "YXZ":
bone.rotation_euler = mathutils.Euler(
(rot_y, rot_x, rot_z))
elif bone.rotation_mode == "XZY":
bone.rotation_euler = mathutils.Euler(
(rot_x, rot_z, rot_y))
elif rot_mode == "rotation_quaternion":
bone.rotation_quaternion = mathutils.Quaternion(
(rot_w, rot_x, rot_y, rot_z))
import bpy
import mathutils
def getArmatureData(context):
try:
arm_object = context.active_object
if arm_object and arm_object.type == "ARMATURE":
return arm_object, getattr(arm_object, "pose_library", None)
elif arm_object and arm_object.parent and arm_object.parent.type == "ARMATURE":
return arm_object.parent, getattr(arm_object.parent, "pose_library", None)
else:
return None, None
except:
return None, None
def getArmatureAction(context):
try:
arm_object, pose_library = getArmatureData(context)
if getattr(arm_object.animation_data.action, "pose_markers", None):
return getattr(arm_object.animation_data, "action", None)
except:
pass
def searchPoseMarker(context, posename, type):
try:
arm_object, pose_library = getArmatureData(context)
if type == "marker":
return pose_library.pose_markers.get(posename, None)
if type == "frame":
return pose_library.pose_markers.get(posename, None).frame
if type == "index":
return pose_library.pose_markers.find(posename)
except:
pass
def findFcurve(context, bone_name, transform, index_int):
arm_object, pose_library = getArmatureData(context)
pose_markers = pose_library.pose_markers
active_frame = pose_markers.active.frame
fcurve_object = pose_library.fcurves.find(
'pose.bones["'+bone_name+'"].'+transform+'', index=index_int)
if hasattr(fcurve_object, 'evaluate'):
return fcurve_object.evaluate(active_frame)
else:
return None
def createFcurve(context, bone_name, transform, index_int):
arm_object, pose_library = getArmatureData(context)
pose_markers = pose_library.pose_markers
try:
pose_library.fcurves.new(
'pose.bones["'+bone_name+'"].'+transform+'', index=index_int, action_group=bone_name)
return
except:
pass
def createKeyframe(context, bone_name, transform, index_int, new_marker, loc):
arm_object, pose_library = getArmatureData(context)
pose_markers = pose_library.pose_markers
try:
pose_library.fcurves.find(
'pose.bones["'+bone_name+'"].'+transform+'', index=index_int).keyframe_points.insert(new_marker, loc)
return
except:
pass
def findKeyframe(context, bone, active_frame):
arm_object, pose_library = getArmatureData(context)
for fcu in pose_library.fcurves:
if fcu.data_path.startswith('pose.bones["'+bone.name+'"]'):
for kp in fcu.keyframe_points:
if kp.co.x == active_frame:
return fcu.data_path
def setKeyframesFromBones(context, arm_object, new_marker):
none_selected = True
for bone in arm_object.pose.bones:
if bone.bone.select:
none_selected = False
for bone in arm_object.pose.bones:
if bone.bone.select or none_selected == True:
bone_name = bone.name
if bone.rotation_mode == "XYZ":
rot_mode = "rotation_euler"
elif bone.rotation_mode == "YZX":
rot_mode = "rotation_euler"
elif bone.rotation_mode == "ZXY":
rot_mode = "rotation_euler"
elif bone.rotation_mode == "QUATERNION":
rot_mode = "rotation_quaternion"
else:
self.report({'WARNING'}, "DSPL: Unsupported bone: " + bone.name + ": " + bone.rotation_mode)
rot_mode = None
loc_x = bone.location[0]
loc_y = bone.location[1]
loc_z = bone.location[2]
createFcurve(context, bone_name, "location", 0)
createFcurve(context, bone_name, "location", 1)
createFcurve(context, bone_name, "location", 2)
createKeyframe(context, bone_name, "location", 0, new_marker, loc_x)
createKeyframe(context, bone_name, "location", 1, new_marker, loc_y)
createKeyframe(context, bone_name, "location", 2, new_marker, loc_z)
if rot_mode == "rotation_quaternion":
rot_w = bone.rotation_quaternion[0]
rot_x = bone.rotation_quaternion[1]
rot_y = bone.rotation_quaternion[2]
rot_z = bone.rotation_quaternion[3]
createFcurve(context, bone_name, rot_mode, 0)
createFcurve(context, bone_name, rot_mode, 1)
createFcurve(context, bone_name, rot_mode, 2)
createFcurve(context, bone_name, rot_mode, 3)
createKeyframe(context, bone_name, rot_mode, 0, new_marker, rot_w)
createKeyframe(context, bone_name, rot_mode, 1, new_marker, rot_x)
createKeyframe(context, bone_name, rot_mode, 2, new_marker, rot_y)
createKeyframe(context, bone_name, rot_mode, 3, new_marker, rot_z)
elif rot_mode == "rotation_euler":
rot_x = bone.rotation_euler[0]
rot_y = bone.rotation_euler[1]
rot_z = bone.rotation_euler[2]
createFcurve(context, bone_name, rot_mode, 0)
createFcurve(context, bone_name, rot_mode, 1)
createFcurve(context, bone_name, rot_mode, 2)
createKeyframe(context, bone_name, rot_mode, 0, new_marker, rot_x)
createKeyframe(context, bone_name, rot_mode, 1, new_marker, rot_y)
createKeyframe(context, bone_name, rot_mode, 2, new_marker, rot_z)
scl_x = bone.scale[0]
scl_y = bone.scale[1]
scl_z = bone.scale[2]
createFcurve(context, bone_name, "scale", 0)
createFcurve(context, bone_name, "scale", 1)
createFcurve(context, bone_name, "scale", 2)
createKeyframe(context, bone_name, "scale", 0, new_marker, scl_x)
createKeyframe(context, bone_name, "scale", 1, new_marker, scl_y)
createKeyframe(context, bone_name, "scale", 2, new_marker, scl_z)
def setBonesfromKeyframes(context, arm_object, active_marker):
none_selected = True
for bone in arm_object.pose.bones:
if bone.bone.select:
none_selected = False
for bone in arm_object.pose.bones:
if bone.bone.select or none_selected == True:
bone_name = bone.name
if findKeyframe(context, bone, active_marker.frame) is None:
continue
if bone.rotation_mode == "XYZ":
rot_mode = "rotation_euler"
elif bone.rotation_mode == "YZX":
rot_mode = "rotation_euler"
elif bone.rotation_mode == "ZXY":
rot_mode = "rotation_euler"
elif bone.rotation_mode == "QUATERNION":
rot_mode = "rotation_quaternion"
else:
self.report({'WARNING'}, "DSPL: Unsupported bone: " + bone.name + ": " + bone.rotation_mode)
rot_mode = None
loc_x = findFcurve(context, bone_name, "location", 0) or 0.0
loc_y = findFcurve(context, bone_name, "location", 1) or 0.0
loc_z = findFcurve(context, bone_name, "location", 2) or 0.0
if rot_mode == "rotation_quaternion":
rot_w = findFcurve(context, bone_name, rot_mode, 0) or 1.0
rot_x = findFcurve(context, bone_name, rot_mode, 1) or 0.0
rot_y = findFcurve(context, bone_name, rot_mode, 2) or 0.0
rot_z = findFcurve(context, bone_name, rot_mode, 3) or 0.0
elif rot_mode == "rotation_euler":
rot_x = findFcurve(context, bone_name, rot_mode, 0) or 0.0
rot_y = findFcurve(context, bone_name, rot_mode, 1) or 0.0
rot_z = findFcurve(context, bone_name, rot_mode, 2) or 0.0
scl_x = findFcurve(context, bone_name, "scale", 0) or 1.0
scl_y = findFcurve(context, bone_name, "scale", 1) or 1.0
scl_z = findFcurve(context, bone_name, "scale", 2) or 1.0
bone.location = mathutils.Vector((loc_x, loc_y, loc_z))
if bone.rotation_mode == "XYZ":
bone.rotation_euler = mathutils.Euler(
(rot_x, rot_y, rot_z))
elif bone.rotation_mode == "YZX":
bone.rotation_euler = mathutils.Euler(
(rot_x, rot_y, rot_z))
elif bone.rotation_mode == "ZXY":
bone.rotation_euler = mathutils.Euler(
(rot_z, rot_x, rot_y))
elif bone.rotation_mode == "YXZ":
bone.rotation_euler = mathutils.Euler(
(rot_y, rot_x, rot_z))
elif bone.rotation_mode == "XZY":
bone.rotation_euler = mathutils.Euler(
(rot_x, rot_z, rot_y))
elif rot_mode == "rotation_quaternion":
bone.rotation_quaternion = mathutils.Quaternion(
(rot_w, rot_x, rot_y, rot_z))
bone.scale = mathutils.Vector((scl_x, scl_y, scl_z))

380
gui.py
View File

@ -1,190 +1,190 @@
import bpy
from .common import *
class DATA_PT_DSPLPanel(bpy.types.Panel):
bl_label = "Damn Simple Pose Library"
bl_id = "DATA_PT_DSPLPanel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Pose'
@classmethod
def poll(cls, context):
return len(bpy.context.selected_objects)
def draw(self, context):
dspl_panel_layout = self.layout
dsplsettings = bpy.context.scene.dsplSettings
# Detect Armature object and parent
armature_layout = dspl_panel_layout.column(align=True)
active_obj = context.active_object
arm_object, pose_library = getArmatureData(context)
pose_library_action = getArmatureAction(context)
if arm_object:
if arm_object == active_obj:
armature_layout.label(text=arm_object.name + " (Active)")
elif arm_object == active_obj.parent:
armature_layout.label(text=arm_object.name + " (Parent)")
# Attach or create pose library
if pose_library or pose_library_action:
if pose_library and not pose_library_action:
armature_layout.template_ID(
arm_object, "pose_library", new="dspl.create_pose_library", unlink="dspl.unlink_pose_library")
elif pose_library_action and not pose_library:
armature_layout.template_ID(
arm_object.animation_data, "action", new="dspl.create_pose_library")
armature_layout.label(
text="Pose Library detected as Action")
armature_layout.label(
text="You should convert to avoid problems")
armature_layout.operator(
"dspl.convert_pose_library", icon='PLUGIN', text="Convert to Pose Library")
elif pose_library and pose_library_action:
armature_layout.template_ID(
arm_object, "pose_library", new="dspl.create_pose_library")
armature_layout.label(text="Pose Library is opened as Action")
armature_layout.label(text="Keyframes will affect pose")
armature_layout.operator(
"dspl.convert_pose_library", icon='PLUGIN', text="Unlink from Action")
# List poses in pose library
if pose_library:
pose_box_layout = dspl_panel_layout.column()
# Menu switcher
pose_box_menu_switcher_layout = pose_box_layout.row()
pose_box_menu_switcher_layout.prop(
dsplsettings, "new_menu", icon='PMARKER_ACT', text="New Menu", toggle=True)
# Quick controls
quick_pose_controls_layout = pose_box_layout.column()
quick_pose_controls_layout.menu(
OBJECT_MT_AddPoseMenu.bl_idname, icon='ADD', text="New Pose")
if pose_library.pose_markers.active:
quick_apply_layout = quick_pose_controls_layout.split(
align=True)
quick_apply_layout.operator(
"dspl.browse_poses", icon='CON_ARMATURE', text="Browse")
if dsplsettings.new_menu == False:
quick_apply_layout.operator(
"dspl.apply_pose", icon='ARMATURE_DATA', text="Apply Pose").posename = pose_library.pose_markers.active.name
else:
quick_apply_layout.prop(dsplsettings,
"edit_mode", icon='GREASEPENCIL', text="Edit", toggle=True)
# New menu
if dsplsettings.new_menu == True:
pose_button_layout = pose_box_layout.row()
pose_button_entries_layout = pose_button_layout.column(align=True)
for pm in pose_library.pose_markers:
row = pose_button_entries_layout.row(align=True)
# Selected indicator
selected = pm.frame == pose_library.pose_markers.active.frame
if dsplsettings.edit_mode == False:
row.label(text="", icon='PMARKER_ACT' if selected else 'PMARKER_SEL')
# Pose operator buttons
if dsplsettings.edit_mode == True:
row.operator('dspl.rename_pose', text=pm.name).posename = pm.name
else:
row.operator('dspl.apply_pose', text=pm.name).posename = pm.name
if dsplsettings.edit_mode == True:
movebuttondown = row.operator("dspl.move_pose", icon='TRIA_DOWN', text="")
movebuttondown.direction = "DOWN"
movebuttondown.posename = pm.name
movebuttonup = row.operator("dspl.move_pose", icon='TRIA_UP', text="")
movebuttonup.direction = "UP"
movebuttonup.posename = pm.name
row.operator("dspl.remove_pose", icon='REMOVE', text="").posename = pm.name
# Old menu
elif dsplsettings.new_menu == False:
pose_list_layout = pose_box_layout.column()
# Pose list
pose_list_entries_layout = pose_list_layout.row()
pose_list_entries_layout.template_list("UI_UL_list","pose_markers",
pose_library, "pose_markers",
pose_library.pose_markers, "active_index", rows=4)
# Pose operators
pose_ops_layout = pose_list_entries_layout.column(align=True)
pose_ops_layout.operator(
"wm.call_menu", icon='ADD', text="").name = "OBJECT_MT_AddPoseMenu"
if pose_library.pose_markers.active:
pose_ops_layout.operator(
"dspl.remove_pose", icon='REMOVE', text="")
pose_ops_layout.operator(
"dspl.apply_pose", icon='ARMATURE_DATA', text=""
).posename = pose_library.pose_markers.active.name
pose_ops_layout.operator(
"dspl.move_pose", icon='TRIA_UP', text="").direction = "UP"
pose_ops_layout.operator(
"dspl.move_pose", icon='TRIA_DOWN', text="").direction = "DOWN"
else:
armature_layout.label(
text="No Action or Pose Library detected")
armature_layout.template_ID(
arm_object, "pose_library", new="dspl.create_pose_library")
else:
armature_layout.label(text="No armature or parent selected")
class OBJECT_MT_AddPoseMenu(bpy.types.Menu):
bl_idname = "OBJECT_MT_AddPoseMenu"
bl_label = "Add Pose"
def draw(self, context):
arm_object, pose_library = getArmatureData(context)
dspl_add_menu_layout = self.layout
dspl_add_menu_layout.operator(
"dspl.add_pose", icon='ADD', text="Add New Pose")
if len(pose_library.pose_markers):
dspl_add_menu_layout.menu(
"OBJECT_MT_ReplacePoseMenu", text="Replace Existing Pose", icon="DECORATE_OVERRIDE")
class OBJECT_MT_ReplacePoseMenu(bpy.types.Menu):
bl_idname = "OBJECT_MT_ReplacePoseMenu"
bl_label = "Add Pose"
def draw(self, context):
arm_object, pose_library = getArmatureData(context)
dspl_replace_menu_layout = self.layout
for pm in pose_library.pose_markers:
op = dspl_replace_menu_layout.operator("dspl.add_pose", text=pm.name, icon="PMARKER")
op.replace = True
op.posename = pm.name
classes = (
DATA_PT_DSPLPanel,
OBJECT_MT_AddPoseMenu,
OBJECT_MT_ReplacePoseMenu,
)
def register():
from bpy.utils import register_class
for cls in classes:
register_class(cls)
def unregister():
from bpy.utils import unregister_class
for cls in classes:
unregister_class(cls)
import bpy
from .common import *
class DATA_PT_DSPLPanel(bpy.types.Panel):
bl_label = "Damn Simple Pose Library"
bl_id = "DATA_PT_DSPLPanel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Pose'
@classmethod
def poll(cls, context):
return len(bpy.context.selected_objects)
def draw(self, context):
dspl_panel_layout = self.layout
dsplsettings = bpy.context.scene.dsplSettings
# Detect Armature object and parent
armature_layout = dspl_panel_layout.column(align=True)
active_obj = context.active_object
arm_object, pose_library = getArmatureData(context)
pose_library_action = getArmatureAction(context)
if arm_object:
if arm_object == active_obj:
armature_layout.label(text=arm_object.name + " (Active)")
elif arm_object == active_obj.parent:
armature_layout.label(text=arm_object.name + " (Parent)")
# Attach or create pose library
if pose_library or pose_library_action:
if pose_library and not pose_library_action:
armature_layout.template_ID(
arm_object, "pose_library", new="dspl.create_pose_library", unlink="dspl.unlink_pose_library")
elif pose_library_action and not pose_library:
armature_layout.template_ID(
arm_object.animation_data, "action", new="dspl.create_pose_library")
armature_layout.label(
text="Pose Library detected as Action")
armature_layout.label(
text="You should convert to avoid problems")
armature_layout.operator(
"dspl.convert_pose_library", icon='PLUGIN', text="Convert to Pose Library")
elif pose_library and pose_library_action:
armature_layout.template_ID(
arm_object, "pose_library", new="dspl.create_pose_library")
armature_layout.label(text="Pose Library is opened as Action")
armature_layout.label(text="Keyframes will affect pose")
armature_layout.operator(
"dspl.convert_pose_library", icon='PLUGIN', text="Unlink from Action")
# List poses in pose library
if pose_library:
pose_box_layout = dspl_panel_layout.column()
# Menu switcher
pose_box_menu_switcher_layout = pose_box_layout.row()
pose_box_menu_switcher_layout.prop(
dsplsettings, "new_menu", icon='PMARKER_ACT', text="New Menu", toggle=True)
# Quick controls
quick_pose_controls_layout = pose_box_layout.column()
quick_pose_controls_layout.menu(
OBJECT_MT_AddPoseMenu.bl_idname, icon='ADD', text="New Pose")
if pose_library.pose_markers.active:
quick_apply_layout = quick_pose_controls_layout.split(
align=True)
quick_apply_layout.operator(
"dspl.browse_poses", icon='CON_ARMATURE', text="Browse")
if dsplsettings.new_menu == False:
quick_apply_layout.operator(
"dspl.apply_pose", icon='ARMATURE_DATA', text="Apply Pose").posename = pose_library.pose_markers.active.name
else:
quick_apply_layout.prop(dsplsettings,
"edit_mode", icon='GREASEPENCIL', text="Edit", toggle=True)
# New menu
if dsplsettings.new_menu == True:
pose_button_layout = pose_box_layout.row()
pose_button_entries_layout = pose_button_layout.column(align=True)
for pm in pose_library.pose_markers:
row = pose_button_entries_layout.row(align=True)
# Selected indicator
selected = pm.frame == pose_library.pose_markers.active.frame
if dsplsettings.edit_mode == False:
row.label(text="", icon='PMARKER_ACT' if selected else 'PMARKER_SEL')
# Pose operator buttons
if dsplsettings.edit_mode == True:
row.operator('dspl.rename_pose', text=pm.name).posename = pm.name
else:
row.operator('dspl.apply_pose', text=pm.name).posename = pm.name
if dsplsettings.edit_mode == True:
movebuttondown = row.operator("dspl.move_pose", icon='TRIA_DOWN', text="")
movebuttondown.direction = "DOWN"
movebuttondown.posename = pm.name
movebuttonup = row.operator("dspl.move_pose", icon='TRIA_UP', text="")
movebuttonup.direction = "UP"
movebuttonup.posename = pm.name
row.operator("dspl.remove_pose", icon='REMOVE', text="").posename = pm.name
# Old menu
elif dsplsettings.new_menu == False:
pose_list_layout = pose_box_layout.column()
# Pose list
pose_list_entries_layout = pose_list_layout.row()
pose_list_entries_layout.template_list("UI_UL_list","pose_markers",
pose_library, "pose_markers",
pose_library.pose_markers, "active_index", rows=4)
# Pose operators
pose_ops_layout = pose_list_entries_layout.column(align=True)
pose_ops_layout.operator(
"wm.call_menu", icon='ADD', text="").name = "OBJECT_MT_AddPoseMenu"
if pose_library.pose_markers.active:
pose_ops_layout.operator(
"dspl.remove_pose", icon='REMOVE', text="")
pose_ops_layout.operator(
"dspl.apply_pose", icon='ARMATURE_DATA', text=""
).posename = pose_library.pose_markers.active.name
pose_ops_layout.operator(
"dspl.move_pose", icon='TRIA_UP', text="").direction = "UP"
pose_ops_layout.operator(
"dspl.move_pose", icon='TRIA_DOWN', text="").direction = "DOWN"
else:
armature_layout.label(
text="No Action or Pose Library detected")
armature_layout.template_ID(
arm_object, "pose_library", new="dspl.create_pose_library")
else:
armature_layout.label(text="No armature or parent selected")
class OBJECT_MT_AddPoseMenu(bpy.types.Menu):
bl_idname = "OBJECT_MT_AddPoseMenu"
bl_label = "Add Pose"
def draw(self, context):
arm_object, pose_library = getArmatureData(context)
dspl_add_menu_layout = self.layout
dspl_add_menu_layout.operator(
"dspl.add_pose", icon='ADD', text="Add New Pose")
if len(pose_library.pose_markers):
dspl_add_menu_layout.menu(
"OBJECT_MT_ReplacePoseMenu", text="Replace Existing Pose", icon="DECORATE_OVERRIDE")
class OBJECT_MT_ReplacePoseMenu(bpy.types.Menu):
bl_idname = "OBJECT_MT_ReplacePoseMenu"
bl_label = "Add Pose"
def draw(self, context):
arm_object, pose_library = getArmatureData(context)
dspl_replace_menu_layout = self.layout
for pm in pose_library.pose_markers:
op = dspl_replace_menu_layout.operator("dspl.add_pose", text=pm.name, icon="PMARKER")
op.replace = True
op.posename = pm.name
classes = (
DATA_PT_DSPLPanel,
OBJECT_MT_AddPoseMenu,
OBJECT_MT_ReplacePoseMenu,
)
def register():
from bpy.utils import register_class
for cls in classes:
register_class(cls)
def unregister():
from bpy.utils import unregister_class
for cls in classes:
unregister_class(cls)

View File

@ -1,30 +1,30 @@
import bpy
addon_keymaps = []
def register_keymaps():
wm = bpy.context.window_manager
# Add Pose
km = wm.keyconfigs.addon.keymaps.new(name='Pose', space_type='EMPTY')
kmi = km.keymap_items.new('wm.call_menu', 'L', 'PRESS', shift=True)
kmi.properties.name = "OBJECT_MT_AddPoseMenu"
addon_keymaps.append((km, kmi))
# Browse Poses
km = wm.keyconfigs.addon.keymaps.new(name='Pose', space_type='EMPTY')
kmi = km.keymap_items.new('dspl.browse_poses', 'L', 'PRESS', alt=True)
addon_keymaps.append((km, kmi))
def unregister_keymaps():
for km, kmi in addon_keymaps:
km.keymap_items.remove(kmi)
addon_keymaps.clear()
def register():
register_keymaps()
def unregister():
import bpy
addon_keymaps = []
def register_keymaps():
wm = bpy.context.window_manager
# Add Pose
km = wm.keyconfigs.addon.keymaps.new(name='Pose', space_type='EMPTY')
kmi = km.keymap_items.new('wm.call_menu', 'L', 'PRESS', shift=True)
kmi.properties.name = "OBJECT_MT_AddPoseMenu"
addon_keymaps.append((km, kmi))
# Browse Poses
km = wm.keyconfigs.addon.keymaps.new(name='Pose', space_type='EMPTY')
kmi = km.keymap_items.new('dspl.browse_poses', 'L', 'PRESS', alt=True)
addon_keymaps.append((km, kmi))
def unregister_keymaps():
for km, kmi in addon_keymaps:
km.keymap_items.remove(kmi)
addon_keymaps.clear()
def register():
register_keymaps()
def unregister():
unregister_keymaps()

View File

@ -1,458 +1,458 @@
import bpy
import blf
from .common import *
# Operator to create a new pose library
class DSPL_OT_CreatePoseLibrary(bpy.types.Operator):
bl_idname = "dspl.create_pose_library"
bl_label = "Create Pose Library"
bl_description = "Create Pose Library"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
arm_object, pose_library = getArmatureData(context)
arm_object.pose_library = bpy.data.actions.new(
name=arm_object.name + "_PoseLib")
arm_object.pose_library.use_fake_user = True
return {'FINISHED'}
# Operator to convert an action to pose library
class DSPL_OT_ConvertPoseLibrary(bpy.types.Operator):
bl_idname = "dspl.convert_pose_library"
bl_label = "Convert Pose Library"
bl_description = "Convert Pose Library"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
arm_object, pose_library = getArmatureData(context)
if pose_library is None:
arm_object.pose_library = arm_object.animation_data.action
arm_object.animation_data.action = None
return {'FINISHED'}
# Operator to add keyframes and marker to pose library
class DSPL_OT_AddPose(bpy.types.Operator):
bl_idname = "dspl.add_pose"
bl_label = "Add Pose"
bl_description = "Add Pose"
bl_options = {'REGISTER', 'UNDO'}
posename: bpy.props.StringProperty(default="Pose")
replace: bpy.props.BoolProperty(name="Replace", description="Replace existing pose", default=False, options={'SKIP_SAVE'})
def execute(self, context):
arm_object, pose_library = getArmatureData(context)
if self.replace == False:
pose_markers = pose_library.pose_markers
new_name = self.posename
counter = 1
# Find first unused marker frame
for f in range(0, len(pose_markers) + 1):
f += 1
for pm in pose_markers:
name_check = pm.name
frame_check = pm.frame
if frame_check == f:
break
else:
new_marker = f
break
# Check for duplicate names
while pose_markers.find(new_name) > -1:
new_name = self.posename + ".{:03d}".format(counter)
counter += 1
else:
pose_name = new_name
pose_markers.new(name=pose_name)
pose_markers[pose_name].frame = new_marker
setKeyframesFromBones(context, arm_object, new_marker)
pose_library.pose_markers.active = pose_markers[pose_name]
bpy.context.area.tag_redraw()
self.report({'INFO'}, "DSPL: Added " + pose_markers[new_name].name + " to frame " + str(pose_markers[new_name].frame))
else:
pose_markers = pose_library.pose_markers
active_marker = pose_markers.active
new_name = self.posename
counter = 1
for pm in pose_markers:
name_check = pm.name
frame_check = pm.frame
if name_check == new_name:
target_name = name_check
target_frame = frame_check
break
else:
return
new_marker = target_frame
setKeyframesFromBones(context, arm_object, new_marker)
self.report({'INFO'}, "DSPL: Replaced " + pose_markers[new_name].name + " on frame " + str(pose_markers[new_name].frame))
return {'FINISHED'}
# Operator to remove keyframes and marker
class DSPL_OT_RemovePose(bpy.types.Operator):
bl_idname = "dspl.remove_pose"
bl_label = "Remove Pose"
bl_description = "Remove Pose"
bl_options = {'REGISTER', 'UNDO'}
posename: bpy.props.StringProperty()
def execute(self, context):
arm_object, pose_library = getArmatureData(context)
pose_markers = pose_library.pose_markers
if self.posename:
pose_library.pose_markers.active_index = searchPoseMarker(context, posename=self.posename, type="index")
active_index = pose_library.pose_markers.active_index
else:
active_index = pose_markers.active_index
active_marker = pose_markers.active
active_frame = active_marker.frame
fcurves = pose_library.fcurves
for fcu in fcurves:
for kf in fcu.keyframe_points.values():
if kf.co.x == active_frame:
fcu.keyframe_points.remove(kf)
if active_index <= 0:
next_index = active_index
next_marker = pose_markers[next_index]
elif (active_index + 1) >= len(pose_markers):
next_index = active_index - 1
next_marker = pose_markers[next_index]
else:
next_index = active_index
next_marker = pose_markers[next_index]
pose_markers.remove(marker=active_marker)
pose_library.pose_markers.active = next_marker
pose_library.pose_markers.active_index = next_index
self.report({'INFO'}, "DSPL: Removed " + self.posename)
return {'FINISHED'}
# Operator to rename the current pose
class DSPL_OT_RenamePose(bpy.types.Operator):
bl_idname = "dspl.rename_pose"
bl_label = "Rename Pose"
bl_description = "Rename Pose"
bl_options = {'REGISTER', 'UNDO'}
posename: bpy.props.StringProperty()
pose_new_name: bpy.props.StringProperty()
def execute(self, context):
arm_object, pose_library = getArmatureData(context)
pose_markers = pose_library.pose_markers
active_marker = pose_markers.active
if self.posename:
target_marker = searchPoseMarker(context, posename=self.posename, type="marker")
else:
target_marker = active_marker
if self.pose_new_name:
target_marker.name = self.pose_new_name
context.area.tag_redraw()
self.report({'INFO'}, "DSPL: Renamed " + self.posename + " to " + self.pose_new_name + " on frame " + str(active_marker.frame))
return {'FINISHED'}
else:
return {'FINISHED'}
def invoke(self, context, event):
self.pose_new_name = self.posename
return context.window_manager.invoke_props_dialog(self)
# Operator to reorder pose markers
class DSPL_OT_MovePose(bpy.types.Operator):
bl_idname = "dspl.move_pose"
bl_label = "Move Pose"
bl_description = "Move pose"
bl_options = {'REGISTER', 'UNDO'}
direction: bpy.props.StringProperty(name="Direction", default="DOWN")
posename: bpy.props.StringProperty(name="Pose Name", default="", options={'SKIP_SAVE'})
def execute(self, context):
arm_object, pose_library = getArmatureData(context)
pose_markers = pose_library.pose_markers
if self.posename:
active_index = searchPoseMarker(context, posename=self.posename, type="index")
active_marker = searchPoseMarker(context, posename=self.posename, type="marker")
active_frame = active_marker.frame
active_posename = active_marker.name
else:
active_marker = pose_markers.active
active_index = pose_markers.active_index
if self.direction == "UP":
move_dir = -1
elif self.direction == "DOWN":
move_dir = 1
swap_index = active_index + move_dir
if swap_index < 0:
return {'FINISHED'}
elif swap_index >= len(pose_library.pose_markers):
return {'FINISHED'}
swap_marker = pose_markers[swap_index]
tmp_marker_name = swap_marker.name
tmp_marker_frame = swap_marker.frame
pose_library.pose_markers[swap_index].name = active_marker.name
pose_library.pose_markers[swap_index].frame = active_marker.frame
pose_library.pose_markers[active_index].name = tmp_marker_name
pose_library.pose_markers[active_index].frame = tmp_marker_frame
pose_library.pose_markers.active_index = swap_index
return {'FINISHED'}
# Operator to apply a pose from active marker
class DSPL_OT_ApplyPose(bpy.types.Operator):
bl_idname = "dspl.apply_pose"
bl_label = "Apply Pose"
bl_description = "Apply Pose (Ctrl+Click to select, Shift+Click to rename, Alt+Click to remove)"
bl_options = {'REGISTER', 'UNDO'}
posename: bpy.props.StringProperty()
def execute(self, context):
arm_object, pose_library = getArmatureData(context)
pose_markers = pose_library.pose_markers
if self.posename:
active_marker = searchPoseMarker(context, posename=self.posename, type="marker")
active_frame = active_marker.frame
active_posename = active_marker.name
pose_library.pose_markers.active_index = searchPoseMarker(context, posename=self.posename, type="index")
else:
active_index = pose_markers.active_index
active_marker = pose_markers.active
active_frame = active_marker.frame
active_posename = active_marker.name
setBonesfromKeyframes(context, arm_object, active_marker)
self.report({'INFO'}, "DSPL: Applied " + active_posename)
return {'FINISHED'}
def invoke(self, context, event):
if event.ctrl:
# Select
arm_object, pose_library = getArmatureData(context)
pose_library.pose_markers.active_index = searchPoseMarker(context, posename=self.posename, type="index")
return {'FINISHED'}
elif event.alt:
# Remove
bpy.ops.dspl.remove_pose(posename = self.posename)
return {'FINISHED'}
elif event.shift:
# Rename
bpy.ops.dspl.rename_pose('INVOKE_DEFAULT', posename = self.posename)
return {'FINISHED'}
else:
return self.execute(context)
# Operator to preview up and down pose list
class DSPL_OT_BrowsePoses(bpy.types.Operator):
bl_idname = "dspl.browse_poses"
bl_label = "Browse Poses"
bl_description = "Browse Poses"
bl_options = {'REGISTER', 'UNDO'}
def draw_callback_px(self, context, test):
font_id = 1
font_size = 24
font_dpi = 72
blf.position(font_id, 15, 30, 0)
mod_var = self.pose_library.pose_markers.active.name
blf.color(font_id, 1.0, 1.0, 1.0, 1.0)
blf.size(font_id, font_size)
blf.draw(font_id, "Previewing pose: " + mod_var)
def modal(self, context, event):
context.area.tag_redraw()
if event.value == 'PRESS':
if event.type in {'LEFT_ARROW', 'UP_ARROW'}:
if self.pose_library.pose_markers.active_index <= 0:
self.pose_library.pose_markers.active_index = len(self.pose_library.pose_markers) - 1
else:
self.pose_library.pose_markers.active_index = self.pose_library.pose_markers.active_index - 1
bpy.ops.dspl.apply_pose()
elif event.type in {'RIGHT_ARROW', 'DOWN_ARROW'}:
if self.pose_library.pose_markers.active_index + 1 >= len(self.pose_library.pose_markers):
self.pose_library.pose_markers.active_index = 0
else:
self.pose_library.pose_markers.active_index = self.pose_library.pose_markers.active_index + 1
bpy.ops.dspl.apply_pose()
if event.type in {'LEFTMOUSE', 'RET', 'NUMPAD_ENTER'}:
bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
return {'FINISHED'}
elif event.type in {'RIGHTMOUSE', 'ESC'}:
self.arm_object.pose.backup_restore()
self.pose_library.pose_markers.active_index = self.backup_index
bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
return {'CANCELLED'}
return {'RUNNING_MODAL'}
def invoke(self, context, event):
bpy.context.area.tag_redraw()
self.arm_object, self.pose_library = getArmatureData(context)
if self.pose_library is None:
self.report({'WARNING'}, "DSPL: Pose Library not active")
return {'CANCELLED'}
self.arm_object.pose.backup_create(self.pose_library)
self.backup_index = self.pose_library.pose_markers.active_index
bpy.ops.dspl.apply_pose()
if context.area.type == 'VIEW_3D':
self.report({'INFO'}, "DSPL: Browsing Poses")
args = (self, context)
self._handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_callback_px, args, 'WINDOW', 'POST_PIXEL')
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
return {'CANCELLED'}
# Operator to unlink a pose library and mark for removal
class DSPL_OT_UnlinkPoseLibrary(bpy.types.Operator):
bl_idname = "dspl.unlink_pose_library"
bl_label = "Unlink Pose Library"
bl_description = "Unlink Pose Library"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
arm_object, pose_library = getArmatureData(context)
try:
arm_object.pose_library = None
# arm_object.pose_library.use_fake_user = False
# if not arm_object.pose_library.name.startswith("del_"):
# arm_object.pose_library.name = "del_{}".format(arm_object.pose_library.name)
except:
pass
return {'FINISHED'}
# Operator to protect orphaned legacy pose libraries
class DSPL_OT_ProtectOrphanPoseLibrary(bpy.types.Operator):
bl_idname = "dspl.protect_orphan_pose_library"
bl_label = "Protect Orphaned Pose Libraries"
bl_description = "Protect Orphaned Pose Libraries"
bl_options = {'REGISTER', 'UNDO'}
only_poselibs: bpy.props.BoolProperty(name="Only Poselibs", description="Only named poselibs", default=False, options={'SKIP_SAVE'})
protect: bpy.props.BoolProperty(name="Protect", description="Or not", default=True, options={'SKIP_SAVE'})
def check(self, context):
return True
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
layout = self.layout
orphaned_act = [act for act in bpy.data.actions if act.users == 0]
if orphaned_act:
# bpy.types.Scene.my_prop = bpy.props.BoolVectorProperty(name="boo", size = len(orphaned_act), default=True)
col = layout.column()
col.label(text="Orphaned actions", icon="ORPHAN_DATA")
for act in orphaned_act:
entryrow = col.row()
protectbutton = entryrow.prop(self, "protect", text=act.name)
col.split()
else:
layout.label(text="No orphans here")
def execute(self, context):
orphaned_act = [act for act in bpy.data.actions if act.users == 0]
if orphaned_act:
for act in orphaned_act:
if "_loc" in act.name or "PoseLib" in act.name:
self.report({'INFO'}, "DSPL: Protecting orphaned action: " + act.name)
act.use_fake_user = True
return {'FINISHED'}
classes = (
DSPL_OT_CreatePoseLibrary,
DSPL_OT_ConvertPoseLibrary,
DSPL_OT_AddPose,
DSPL_OT_RemovePose,
DSPL_OT_RenamePose,
DSPL_OT_MovePose,
DSPL_OT_ApplyPose,
DSPL_OT_BrowsePoses,
DSPL_OT_UnlinkPoseLibrary,
DSPL_OT_ProtectOrphanPoseLibrary
)
register, unregister = bpy.utils.register_classes_factory(classes)
import bpy
import blf
from .common import *
# Operator to create a new pose library
class DSPL_OT_CreatePoseLibrary(bpy.types.Operator):
bl_idname = "dspl.create_pose_library"
bl_label = "Create Pose Library"
bl_description = "Create Pose Library"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
arm_object, pose_library = getArmatureData(context)
arm_object.pose_library = bpy.data.actions.new(
name=arm_object.name + "_PoseLib")
arm_object.pose_library.use_fake_user = True
return {'FINISHED'}
# Operator to convert an action to pose library
class DSPL_OT_ConvertPoseLibrary(bpy.types.Operator):
bl_idname = "dspl.convert_pose_library"
bl_label = "Convert Pose Library"
bl_description = "Convert Pose Library"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
arm_object, pose_library = getArmatureData(context)
if pose_library is None:
arm_object.pose_library = arm_object.animation_data.action
arm_object.animation_data.action = None
return {'FINISHED'}
# Operator to add keyframes and marker to pose library
class DSPL_OT_AddPose(bpy.types.Operator):
bl_idname = "dspl.add_pose"
bl_label = "Add Pose"
bl_description = "Add Pose"
bl_options = {'REGISTER', 'UNDO'}
posename: bpy.props.StringProperty(default="Pose")
replace: bpy.props.BoolProperty(name="Replace", description="Replace existing pose", default=False, options={'SKIP_SAVE'})
def execute(self, context):
arm_object, pose_library = getArmatureData(context)
if self.replace == False:
pose_markers = pose_library.pose_markers
new_name = self.posename
counter = 1
# Find first unused marker frame
for f in range(0, len(pose_markers) + 1):
f += 1
for pm in pose_markers:
name_check = pm.name
frame_check = pm.frame
if frame_check == f:
break
else:
new_marker = f
break
# Check for duplicate names
while pose_markers.find(new_name) > -1:
new_name = self.posename + ".{:03d}".format(counter)
counter += 1
else:
pose_name = new_name
pose_markers.new(name=pose_name)
pose_markers[pose_name].frame = new_marker
setKeyframesFromBones(context, arm_object, new_marker)
pose_library.pose_markers.active = pose_markers[pose_name]
bpy.context.area.tag_redraw()
self.report({'INFO'}, "DSPL: Added " + pose_markers[new_name].name + " to frame " + str(pose_markers[new_name].frame))
else:
pose_markers = pose_library.pose_markers
active_marker = pose_markers.active
new_name = self.posename
counter = 1
for pm in pose_markers:
name_check = pm.name
frame_check = pm.frame
if name_check == new_name:
target_name = name_check
target_frame = frame_check
break
else:
return
new_marker = target_frame
setKeyframesFromBones(context, arm_object, new_marker)
self.report({'INFO'}, "DSPL: Replaced " + pose_markers[new_name].name + " on frame " + str(pose_markers[new_name].frame))
return {'FINISHED'}
# Operator to remove keyframes and marker
class DSPL_OT_RemovePose(bpy.types.Operator):
bl_idname = "dspl.remove_pose"
bl_label = "Remove Pose"
bl_description = "Remove Pose"
bl_options = {'REGISTER', 'UNDO'}
posename: bpy.props.StringProperty()
def execute(self, context):
arm_object, pose_library = getArmatureData(context)
pose_markers = pose_library.pose_markers
if self.posename:
pose_library.pose_markers.active_index = searchPoseMarker(context, posename=self.posename, type="index")
active_index = pose_library.pose_markers.active_index
else:
active_index = pose_markers.active_index
active_marker = pose_markers.active
active_frame = active_marker.frame
fcurves = pose_library.fcurves
for fcu in fcurves:
for kf in fcu.keyframe_points.values():
if kf.co.x == active_frame:
fcu.keyframe_points.remove(kf)
if active_index <= 0:
next_index = active_index
next_marker = pose_markers[next_index]
elif (active_index + 1) >= len(pose_markers):
next_index = active_index - 1
next_marker = pose_markers[next_index]
else:
next_index = active_index
next_marker = pose_markers[next_index]
pose_markers.remove(marker=active_marker)
pose_library.pose_markers.active = next_marker
pose_library.pose_markers.active_index = next_index
self.report({'INFO'}, "DSPL: Removed " + self.posename)
return {'FINISHED'}
# Operator to rename the current pose
class DSPL_OT_RenamePose(bpy.types.Operator):
bl_idname = "dspl.rename_pose"
bl_label = "Rename Pose"
bl_description = "Rename Pose"
bl_options = {'REGISTER', 'UNDO'}
posename: bpy.props.StringProperty()
pose_new_name: bpy.props.StringProperty()
def execute(self, context):
arm_object, pose_library = getArmatureData(context)
pose_markers = pose_library.pose_markers
active_marker = pose_markers.active
if self.posename:
target_marker = searchPoseMarker(context, posename=self.posename, type="marker")
else:
target_marker = active_marker
if self.pose_new_name:
target_marker.name = self.pose_new_name
context.area.tag_redraw()
self.report({'INFO'}, "DSPL: Renamed " + self.posename + " to " + self.pose_new_name + " on frame " + str(active_marker.frame))
return {'FINISHED'}
else:
return {'FINISHED'}
def invoke(self, context, event):
self.pose_new_name = self.posename
return context.window_manager.invoke_props_dialog(self)
# Operator to reorder pose markers
class DSPL_OT_MovePose(bpy.types.Operator):
bl_idname = "dspl.move_pose"
bl_label = "Move Pose"
bl_description = "Move pose"
bl_options = {'REGISTER', 'UNDO'}
direction: bpy.props.StringProperty(name="Direction", default="DOWN")
posename: bpy.props.StringProperty(name="Pose Name", default="", options={'SKIP_SAVE'})
def execute(self, context):
arm_object, pose_library = getArmatureData(context)
pose_markers = pose_library.pose_markers
if self.posename:
active_index = searchPoseMarker(context, posename=self.posename, type="index")
active_marker = searchPoseMarker(context, posename=self.posename, type="marker")
active_frame = active_marker.frame
active_posename = active_marker.name
else:
active_marker = pose_markers.active
active_index = pose_markers.active_index
if self.direction == "UP":
move_dir = -1
elif self.direction == "DOWN":
move_dir = 1
swap_index = active_index + move_dir
if swap_index < 0:
return {'FINISHED'}
elif swap_index >= len(pose_library.pose_markers):
return {'FINISHED'}
swap_marker = pose_markers[swap_index]
tmp_marker_name = swap_marker.name
tmp_marker_frame = swap_marker.frame
pose_library.pose_markers[swap_index].name = active_marker.name
pose_library.pose_markers[swap_index].frame = active_marker.frame
pose_library.pose_markers[active_index].name = tmp_marker_name
pose_library.pose_markers[active_index].frame = tmp_marker_frame
pose_library.pose_markers.active_index = swap_index
return {'FINISHED'}
# Operator to apply a pose from active marker
class DSPL_OT_ApplyPose(bpy.types.Operator):
bl_idname = "dspl.apply_pose"
bl_label = "Apply Pose"
bl_description = "Apply Pose (Ctrl+Click to select, Shift+Click to rename, Alt+Click to remove)"
bl_options = {'REGISTER', 'UNDO'}
posename: bpy.props.StringProperty()
def execute(self, context):
arm_object, pose_library = getArmatureData(context)
pose_markers = pose_library.pose_markers
if self.posename:
active_marker = searchPoseMarker(context, posename=self.posename, type="marker")
active_frame = active_marker.frame
active_posename = active_marker.name
pose_library.pose_markers.active_index = searchPoseMarker(context, posename=self.posename, type="index")
else:
active_index = pose_markers.active_index
active_marker = pose_markers.active
active_frame = active_marker.frame
active_posename = active_marker.name
setBonesfromKeyframes(context, arm_object, active_marker)
self.report({'INFO'}, "DSPL: Applied " + active_posename)
return {'FINISHED'}
def invoke(self, context, event):
if event.ctrl:
# Select
arm_object, pose_library = getArmatureData(context)
pose_library.pose_markers.active_index = searchPoseMarker(context, posename=self.posename, type="index")
return {'FINISHED'}
elif event.alt:
# Remove
bpy.ops.dspl.remove_pose(posename = self.posename)
return {'FINISHED'}
elif event.shift:
# Rename
bpy.ops.dspl.rename_pose('INVOKE_DEFAULT', posename = self.posename)
return {'FINISHED'}
else:
return self.execute(context)
# Operator to preview up and down pose list
class DSPL_OT_BrowsePoses(bpy.types.Operator):
bl_idname = "dspl.browse_poses"
bl_label = "Browse Poses"
bl_description = "Browse Poses"
bl_options = {'REGISTER', 'UNDO'}
def draw_callback_px(self, context, test):
font_id = 1
font_size = 24
font_dpi = 72
blf.position(font_id, 15, 30, 0)
mod_var = self.pose_library.pose_markers.active.name
blf.color(font_id, 1.0, 1.0, 1.0, 1.0)
blf.size(font_id, font_size)
blf.draw(font_id, "Previewing pose: " + mod_var)
def modal(self, context, event):
context.area.tag_redraw()
if event.value == 'PRESS':
if event.type in {'LEFT_ARROW', 'UP_ARROW'}:
if self.pose_library.pose_markers.active_index <= 0:
self.pose_library.pose_markers.active_index = len(self.pose_library.pose_markers) - 1
else:
self.pose_library.pose_markers.active_index = self.pose_library.pose_markers.active_index - 1
bpy.ops.dspl.apply_pose()
elif event.type in {'RIGHT_ARROW', 'DOWN_ARROW'}:
if self.pose_library.pose_markers.active_index + 1 >= len(self.pose_library.pose_markers):
self.pose_library.pose_markers.active_index = 0
else:
self.pose_library.pose_markers.active_index = self.pose_library.pose_markers.active_index + 1
bpy.ops.dspl.apply_pose()
if event.type in {'LEFTMOUSE', 'RET', 'NUMPAD_ENTER'}:
bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
return {'FINISHED'}
elif event.type in {'RIGHTMOUSE', 'ESC'}:
self.arm_object.pose.backup_restore()
self.pose_library.pose_markers.active_index = self.backup_index
bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
return {'CANCELLED'}
return {'RUNNING_MODAL'}
def invoke(self, context, event):
bpy.context.area.tag_redraw()
self.arm_object, self.pose_library = getArmatureData(context)
if self.pose_library is None:
self.report({'WARNING'}, "DSPL: Pose Library not active")
return {'CANCELLED'}
self.arm_object.pose.backup_create(self.pose_library)
self.backup_index = self.pose_library.pose_markers.active_index
bpy.ops.dspl.apply_pose()
if context.area.type == 'VIEW_3D':
self.report({'INFO'}, "DSPL: Browsing Poses")
args = (self, context)
self._handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_callback_px, args, 'WINDOW', 'POST_PIXEL')
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
return {'CANCELLED'}
# Operator to unlink a pose library and mark for removal
class DSPL_OT_UnlinkPoseLibrary(bpy.types.Operator):
bl_idname = "dspl.unlink_pose_library"
bl_label = "Unlink Pose Library"
bl_description = "Unlink Pose Library"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
arm_object, pose_library = getArmatureData(context)
try:
arm_object.pose_library = None
# arm_object.pose_library.use_fake_user = False
# if not arm_object.pose_library.name.startswith("del_"):
# arm_object.pose_library.name = "del_{}".format(arm_object.pose_library.name)
except:
pass
return {'FINISHED'}
# Operator to protect orphaned legacy pose libraries
class DSPL_OT_ProtectOrphanPoseLibrary(bpy.types.Operator):
bl_idname = "dspl.protect_orphan_pose_library"
bl_label = "Protect Orphaned Pose Libraries"
bl_description = "Protect Orphaned Pose Libraries"
bl_options = {'REGISTER', 'UNDO'}
only_poselibs: bpy.props.BoolProperty(name="Only Poselibs", description="Only named poselibs", default=False, options={'SKIP_SAVE'})
protect: bpy.props.BoolProperty(name="Protect", description="Or not", default=True, options={'SKIP_SAVE'})
def check(self, context):
return True
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
layout = self.layout
orphaned_act = [act for act in bpy.data.actions if act.users == 0]
if orphaned_act:
# bpy.types.Scene.my_prop = bpy.props.BoolVectorProperty(name="boo", size = len(orphaned_act), default=True)
col = layout.column()
col.label(text="Orphaned actions", icon="ORPHAN_DATA")
for act in orphaned_act:
entryrow = col.row()
protectbutton = entryrow.prop(self, "protect", text=act.name)
col.split()
else:
layout.label(text="No orphans here")
def execute(self, context):
orphaned_act = [act for act in bpy.data.actions if act.users == 0]
if orphaned_act:
for act in orphaned_act:
if "_loc" in act.name or "PoseLib" in act.name:
self.report({'INFO'}, "DSPL: Protecting orphaned action: " + act.name)
act.use_fake_user = True
return {'FINISHED'}
classes = (
DSPL_OT_CreatePoseLibrary,
DSPL_OT_ConvertPoseLibrary,
DSPL_OT_AddPose,
DSPL_OT_RemovePose,
DSPL_OT_RenamePose,
DSPL_OT_MovePose,
DSPL_OT_ApplyPose,
DSPL_OT_BrowsePoses,
DSPL_OT_UnlinkPoseLibrary,
DSPL_OT_ProtectOrphanPoseLibrary
)
register, unregister = bpy.utils.register_classes_factory(classes)