diff --git a/README.md b/README.md index d1b87e3..b82809c 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/common.py b/common.py index 090f453..e002cb6 100644 --- a/common.py +++ b/common.py @@ -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)) \ No newline at end of file diff --git a/gui.py b/gui.py index 52467e3..66ad8d9 100644 --- a/gui.py +++ b/gui.py @@ -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) diff --git a/keymaps.py b/keymaps.py index cfd3be6..381f983 100644 --- a/keymaps.py +++ b/keymaps.py @@ -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() \ No newline at end of file diff --git a/operators.py b/operators.py index e161cc4..ace25fc 100644 --- a/operators.py +++ b/operators.py @@ -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)