15 Commits

Author SHA1 Message Date
522fbc3383 Merge pull request 'Function to select bones in pose' (#24) from select-button into develop
Reviewed-on: #24
2025-01-19 12:37:22 -06:00
2abd74bdc8 Function to select bones in pose 2025-01-19 12:34:29 -06:00
be06439bc0 Merge pull request 'More bone rotation cases' (#17) from bone-rotation into develop
Reviewed-on: #17
2025-01-19 12:10:16 -06:00
486b23b378 Better map out rotation modes 2025-01-19 01:17:12 -06:00
0c4ecffc18 More bone rotation cases 2025-01-18 23:09:40 -06:00
5ffe61e81d Merge pull request 'Check for keyframes before applying pose' (#22) from keyframe-check into develop
Reviewed-on: #22
2025-01-18 23:08:01 -06:00
95955d2dfd Check for keyframes before applying pose 2025-01-18 21:44:24 -06:00
cb3999de33 Correct tooltip text 2025-01-02 23:21:46 -06:00
5f0a4c93c2 Merge pull request 'Use Object.pose_library instead of Object.dspl.pose_library' (#14) from pose-library-prop into develop
Reviewed-on: #14
2025-01-02 21:38:24 -06:00
efd8acd9a9 Filter for Pose Libraries only 2025-01-02 21:37:09 -06:00
e9d33b7f87 Unify calls to armature and pose library 2025-01-02 21:37:09 -06:00
d008e4750c Use Object.pose_library instead of Object.dspl.pose_library 2025-01-02 21:37:09 -06:00
b2fc121a88 Enable "new" menu by default 2025-01-02 21:35:55 -06:00
a4abb41c73 Merge pull request 'Credits and readme update' (#16) from credits into develop
Reviewed-on: #16
2025-01-02 21:31:37 -06:00
8f936ec085 Credits and readme update 2025-01-02 21:30:58 -06:00
4 changed files with 93 additions and 36 deletions

View File

@ -1,6 +1,48 @@
# Damn Simple Pose Library # Damn Simple Pose Library
In Blender 3.x, a new Asset-based Pose system was introduced. 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.
That's fine and dandy, however because of that, the old Pose Library was deprecated and gutted out real quick-like. Too quick.
This brings it back as best as it can. 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.

View File

@ -3,10 +3,10 @@ from . import gui, operators, common, keymaps
bl_info = \ bl_info = \
{ {
"name": "Damn Simple Pose Library", "name": "Damn Simple Pose Library",
"author": "jackie", "author": "breakingspell",
"version": (4, 1, 0), "version": (0, 1, 0),
"blender": (3, 5, 0), "blender": (3, 6, 0),
"description": "woah", "description": "Re-implement Pose Library",
"category": "Object Data", "category": "Object Data",
} }
@ -24,12 +24,15 @@ if _need_reload:
class dsplSettings(bpy.types.PropertyGroup): class dsplSettings(bpy.types.PropertyGroup):
new_menu: bpy.props.BoolProperty( new_menu: bpy.props.BoolProperty(
name="New Menu", description="Toggle New Menu", default=False) name="New Menu", description="Toggle New Menu", default=True)
edit_mode: bpy.props.BoolProperty( edit_mode: bpy.props.BoolProperty(
name="Edit Mode", description="Toggle Edit Mode", default=False) name="Edit Mode", description="Toggle Edit Mode", default=False)
classes = dsplSettings classes = dsplSettings
def pose_libraries_poll(self, action):
if getattr(action, "pose_markers", None):
return True
def register(): def register():
from bpy.utils import register_class from bpy.utils import register_class
@ -37,7 +40,8 @@ def register():
bpy.types.Object.pose_library = bpy.props.PointerProperty( bpy.types.Object.pose_library = bpy.props.PointerProperty(
name="Active Pose Library", description="", name="Active Pose Library", description="",
type=bpy.types.Action, override={'LIBRARY_OVERRIDABLE'}) type=bpy.types.Action, override={'LIBRARY_OVERRIDABLE'},
poll=pose_libraries_poll)
bpy.types.Scene.dsplSettings = bpy.props.PointerProperty( bpy.types.Scene.dsplSettings = bpy.props.PointerProperty(
type=dsplSettings, override={'LIBRARY_OVERRIDABLE'}) type=dsplSettings, override={'LIBRARY_OVERRIDABLE'})
@ -51,6 +55,7 @@ def unregister():
unregister_class(dsplSettings) unregister_class(dsplSettings)
del bpy.types.Scene.dsplSettings del bpy.types.Scene.dsplSettings
del bpy.types.Object.pose_library
keymaps.unregister() keymaps.unregister()
operators.unregister() operators.unregister()

View File

@ -37,6 +37,17 @@ def searchPoseMarker(context, posename, type):
pass pass
def selectBonesinPose(context, posename, active_marker):
try:
arm_object, pose_library = getArmatureData(context)
for bone in arm_object.pose.bones:
bone.bone.select = False
if findKeyframe(context, bone, active_marker.frame):
bone.bone.select = True
except:
pass
def findFcurve(context, bone_name, transform, index_int): def findFcurve(context, bone_name, transform, index_int):
arm_object, pose_library = getArmatureData(context) arm_object, pose_library = getArmatureData(context)
pose_markers = pose_library.pose_markers pose_markers = pose_library.pose_markers
@ -62,6 +73,16 @@ def createFcurve(context, bone_name, transform, index_int):
pass 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 createKeyframe(context, bone_name, transform, index_int, new_marker, loc): def createKeyframe(context, bone_name, transform, index_int, new_marker, loc):
arm_object, pose_library = getArmatureData(context) arm_object, pose_library = getArmatureData(context)
pose_markers = pose_library.pose_markers pose_markers = pose_library.pose_markers
@ -84,11 +105,7 @@ def setKeyframesFromBones(context, arm_object, new_marker):
if bone.bone.select or none_selected == True: if bone.bone.select or none_selected == True:
bone_name = bone.name bone_name = bone.name
if bone.rotation_mode == "XYZ": if bone.rotation_mode in ("XYZ", "XZY", "YXZ", "YZX", "ZXY", "ZYX"):
rot_mode = "rotation_euler"
elif bone.rotation_mode == "YZX":
rot_mode = "rotation_euler"
elif bone.rotation_mode == "ZXY":
rot_mode = "rotation_euler" rot_mode = "rotation_euler"
elif bone.rotation_mode == "QUATERNION": elif bone.rotation_mode == "QUATERNION":
rot_mode = "rotation_quaternion" rot_mode = "rotation_quaternion"
@ -149,11 +166,10 @@ def setBonesfromKeyframes(context, arm_object, active_marker):
if bone.bone.select or none_selected == True: if bone.bone.select or none_selected == True:
bone_name = bone.name bone_name = bone.name
if bone.rotation_mode == "XYZ": if findKeyframe(context, bone, active_marker.frame) is None:
rot_mode = "rotation_euler" continue
elif bone.rotation_mode == "YZX":
rot_mode = "rotation_euler" if bone.rotation_mode in ("XYZ", "XZY", "YXZ", "YZX", "ZXY", "ZYX"):
elif bone.rotation_mode == "ZXY":
rot_mode = "rotation_euler" rot_mode = "rotation_euler"
elif bone.rotation_mode == "QUATERNION": elif bone.rotation_mode == "QUATERNION":
rot_mode = "rotation_quaternion" rot_mode = "rotation_quaternion"
@ -178,21 +194,9 @@ def setBonesfromKeyframes(context, arm_object, active_marker):
scl_z = findFcurve(context, bone_name, "scale", 2) or 1.0 scl_z = findFcurve(context, bone_name, "scale", 2) or 1.0
bone.location = mathutils.Vector((loc_x, loc_y, loc_z)) bone.location = mathutils.Vector((loc_x, loc_y, loc_z))
if bone.rotation_mode == "XYZ": if rot_mode == "rotation_euler":
bone.rotation_euler = mathutils.Euler( bone.rotation_euler = mathutils.Euler(
(rot_x, rot_y, rot_z)) (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": elif rot_mode == "rotation_quaternion":
bone.rotation_quaternion = mathutils.Quaternion( bone.rotation_quaternion = mathutils.Quaternion(
(rot_w, rot_x, rot_y, rot_z)) (rot_w, rot_x, rot_y, rot_z))

View File

@ -254,7 +254,7 @@ class DSPL_OT_MovePose(bpy.types.Operator):
class DSPL_OT_ApplyPose(bpy.types.Operator): class DSPL_OT_ApplyPose(bpy.types.Operator):
bl_idname = "dspl.apply_pose" bl_idname = "dspl.apply_pose"
bl_label = "Apply Pose" bl_label = "Apply Pose"
bl_description = "Apply Pose (Ctrl+Click to select, Alt+Click to remove)" bl_description = "Apply Pose (Ctrl+Click to select bones, Shift+Click to rename, Alt+Click to remove)"
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
posename: bpy.props.StringProperty() posename: bpy.props.StringProperty()
@ -262,7 +262,6 @@ class DSPL_OT_ApplyPose(bpy.types.Operator):
def execute(self, context): def execute(self, context):
arm_object, pose_library = getArmatureData(context) arm_object, pose_library = getArmatureData(context)
pose_markers = pose_library.pose_markers pose_markers = pose_library.pose_markers
if self.posename: if self.posename:
@ -284,9 +283,16 @@ class DSPL_OT_ApplyPose(bpy.types.Operator):
def invoke(self, context, event): def invoke(self, context, event):
if event.ctrl: if event.ctrl:
# Select # Select bones
arm_object, pose_library = getArmatureData(context) arm_object, pose_library = getArmatureData(context)
pose_library.pose_markers.active_index = searchPoseMarker(context, posename=self.posename, type="index") active_marker = searchPoseMarker(context, posename=self.posename, type="marker")
arm_object.select = True
bpy.context.view_layer.objects.active = arm_object
bpy.ops.object.mode_set(mode='POSE')
selectBonesinPose(context, self.posename, active_marker)
self.execute(context)
return {'FINISHED'} return {'FINISHED'}
elif event.alt: elif event.alt:
# Remove # Remove