commit 42711a6044b36bdf1da0402196a1ab6891978984 Author: Blazer Date: Sat Nov 23 12:28:01 2024 -0600 Initial commit diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..d78a9e0 --- /dev/null +++ b/__init__.py @@ -0,0 +1,108 @@ +import bpy +from . import gui, operators, common +from typing import List, Tuple +bl_info = \ + { + "name": "Damn Simple Pose Library", + "author": "jackie", + "version": (4, 1, 0), + "blender": (3, 5, 0), + "description": "woah", + "category": "Object Data", + } + + +_need_reload = "operators" in locals() + +if _need_reload: + import importlib + + gui = importlib.reload(gui) + common = importlib.reload(common) + # keymaps = importlib.reload(keymaps) + operators = importlib.reload(operators) + +# addon_keymaps: List[Tuple[bpy.types.KeyMap, bpy.types.KeyMapItem]] = [] +# addon_keymaps = [] + + +class dsplObj(bpy.types.PropertyGroup): + pose_library: bpy.props.PointerProperty( + name="Active Pose Library", description="", + override={'LIBRARY_OVERRIDABLE'}, type=bpy.types.Action) + # update = common.poselib_update) + # , update = anim_layers.layer_name_update + + +class dsplVars(bpy.types.PropertyGroup): + pose_index: bpy.props.IntProperty( + name="Pose Index", description="", override={'LIBRARY_OVERRIDABLE'}) + pose_new_name: bpy.props.StringProperty( + name="Pose Name", description="New name for pose", + default="Pose", override={'LIBRARY_OVERRIDABLE'}) + only_selected: bpy.props.BoolProperty( + name="Only selected Bones", description="Process only selected bones", + default=False, options={'HIDDEN'}, override={'LIBRARY_OVERRIDABLE'}) + numero: bpy.props.IntProperty( + name='Numero', default=666, override={'LIBRARY_OVERRIDABLE'}) + + +class dsplSettings(bpy.types.PropertyGroup): + new_menu: bpy.props.BoolProperty( + name="New Menu", description="Toggle New Menu", default=False) + edit_mode: bpy.props.BoolProperty( + name="Edit Mode", description="Toggle Edit Mode", default=False) + +classes = (dsplObj, dsplVars, dsplSettings) + + +def register(): + from bpy.utils import register_class + for cls in classes: + register_class(cls) + + # bpy.types.Armature.pose_library = bpy.props.PointerProperty( + # type=dsplObj, override={'LIBRARY_OVERRIDABLE'}) + + # bpy.types.Object.pose_library = bpy.props.PointerProperty( + # type=bpy.types.Action, options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'}) + + bpy.types.Object.dspl = bpy.props.PointerProperty( + type=dsplObj, override={'LIBRARY_OVERRIDABLE'}) + bpy.types.Object.dsplvars = bpy.props.PointerProperty( + type=dsplVars, override={'LIBRARY_OVERRIDABLE'}) + bpy.types.Scene.dsplSettings = bpy.props.PointerProperty( + type=dsplSettings, override={'LIBRARY_OVERRIDABLE'}) + + gui.register() + operators.register() + + # kc = bpy.context.window_manager.keyconfigs.addon + # km = kc.keymaps.new(name='3D View', space_type='VIEW_3D') + # kmi = [ + # km.keymap_items.new("dspl.add_pose", type='L', value='PRESS', shift=True), + # km.keymap_items.new("dspl.browse_poses", type='L', value='PRESS', alt=True), + # ] + # addon_keymaps.append((km, kmi)) + + +def unregister() -> None: + from bpy.utils import unregister_class + for cls in classes: + unregister_class(cls) + + del bpy.types.Object.dspl + del bpy.types.Object.dsplvars + del bpy.types.Scene.dsplSettings + + gui.unregister() + operators.unregister() + + # for km, kmi in addon_keymaps: + # km.keymap_items.remove(kmi) + # addon_keymaps.clear() + + +if __name__ == "__main__": + register() + diff --git a/common.py b/common.py new file mode 100644 index 0000000..6a30e29 --- /dev/null +++ b/common.py @@ -0,0 +1,159 @@ +import bpy + + +def getArmatureObject(context): + obj = context.active_object + if obj: + if obj.type == "ARMATURE": + return obj + elif obj.parent is not None and obj.parent.type == "ARMATURE": + return obj.parent + else: + return None + + +def getLegacyPoseLibrary(context): + arm = getArmatureObject(context) + return getattr(arm, "pose_library", None) + + +def getArmatureAction(context): + arm = getArmatureObject(context) + return getattr(arm.animation_data, "action", None) + + +def getDsplAction(context): + arm = getArmatureObject(context) + return getattr(arm.dspl, "pose_library", None) + + +def getPoseLib(context): + arm = getArmatureObject(context) + pose_library_dspl = getDsplAction(context) + pose_library_action = getArmatureAction(context) + pose_library_legacy = getLegacyPoseLibrary(context) + + if pose_library_dspl: + return pose_library_dspl + elif pose_library_action and not pose_library_dspl: + return pose_library_action + elif pose_library_legacy and not pose_library_dspl: + return pose_library_legacy + else: + return None + + +def searchPoseMarker(context, posename, type): + try: + pose_library = getPoseLib(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 = getArmatureObject(context) + action_object = getPoseLib(context) + pose_markers = action_object.pose_markers + active_frame = pose_markers.active.frame + + fcurve_object = action_object.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 = getArmatureObject(context) + action_object = getPoseLib(context) + pose_markers = action_object.pose_markers + + try: + action_object.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 = getArmatureObject(context) + action_object = getPoseLib(context) + pose_markers = action_object.pose_markers + + try: + action_object.fcurves.find( + 'pose.bones["'+bone_name+'"].'+transform+'', index=index_int).keyframe_points.insert(new_marker, loc) + return + except: + pass + + +def setKeyframesFromBones(context, new_marker): + arm_object = getArmatureObject(context) + for bone in arm_object.pose.bones: + if bone.bone.select or arm_object.dsplvars.only_selected == False: + 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: + print("Unsupported bone!") + 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) + + diff --git a/dspltodo.txt b/dspltodo.txt new file mode 100644 index 0000000..d68eae9 --- /dev/null +++ b/dspltodo.txt @@ -0,0 +1,3 @@ +-Use Posename for all marker lookups + +-Ctrl+Shift+Alt invoke for calling operators \ No newline at end of file diff --git a/gui.py b/gui.py new file mode 100644 index 0000000..d05d4bf --- /dev/null +++ b/gui.py @@ -0,0 +1,183 @@ +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 = getArmatureObject(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 + pose_library_dspl = getDsplAction(context) + pose_library_action = getArmatureAction(context) + + if pose_library_dspl or pose_library_action: + if pose_library_dspl and not pose_library_action: + active_pose_library = pose_library_dspl + armature_layout.template_ID( + arm_object.dspl, "pose_library", new="dspl.create_pose_library", unlink="dspl.unlink_pose_library") + + elif pose_library_action and not pose_library_dspl: + active_pose_library = pose_library_action + armature_layout.template_ID( + arm_object.animation_data, "action", new="dspl.create_pose_library") + armature_layout.label( + text="Legacy Pose Library detected.") + armature_layout.label( + text="You may convert to avoid problems") + armature_layout.operator( + "dspl.convert_pose_library", icon='PLUGIN', text="Convert to DSPL") + + elif pose_library_action and pose_library_dspl: + armature_layout.template_ID( + arm_object.dspl, "pose_library", new="dspl.create_pose_library") + armature_layout.label(text="Double pose configuration!!") + armature_layout.label(text="You should not proceed") + armature_layout.operator( + "dspl.convert_pose_library", icon='PLUGIN', text="Convert to DSPL") + active_pose_library = pose_library_dspl + + + # List poses in pose library + if active_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 active_pose_library.pose_markers.active: + quick_apply_layout = quick_pose_controls_layout.split( + align=True) + quick_apply_layout.prop(arm_object.dsplvars, + "only_selected", icon='GROUP_BONE', text="Selected", toggle=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 = active_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 active_pose_library.pose_markers: + row = pose_button_entries_layout.row(align=True) + + # Selected indicator + selected = pm.frame == active_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", + active_pose_library, "pose_markers", + active_pose_library.pose_markers, "active_index", rows=4) + + # Pose operators + pose_ops_layout = pose_list_entries_layout.column(align=True) + pose_ops_layout.operator( + "dspl.draw_new_pose_menu", icon='ADD', text="") + if active_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 = active_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.dspl, "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 = getArmatureObject(context) + action_object = getPoseLib(context) + + dspl_add_menu_layout = self.layout + dspl_add_menu_layout.operator( + "dspl.draw_new_pose_menu", icon='DECORATE_DRIVER', text="Add New Pose") + if action_object.pose_markers.active: + dspl_add_menu_layout.operator( + "dspl.add_pose", icon='DECORATE_OVERRIDE', text="Replace Existing Pose").replace = True + + +classes = ( + DATA_PT_DSPLPanel, + OBJECT_MT_AddPoseMenu, +) + + +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 new file mode 100644 index 0000000..597dfbe --- /dev/null +++ b/keymaps.py @@ -0,0 +1,28 @@ +# SPDX-FileCopyrightText: 2010-2023 Blender Foundation +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import bpy +from .operators import * +from typing import List, Tuple + + +addon_keymaps = [] + + +def register() -> None: + wm = bpy.context.window_manager + kc = wm.keyconfigs.addon + if kc is None: + return + + km = kc.keymaps.new(name="File Browser Main") + kmi = km.keymap_items.new("dspl.browse_poses", type="L", value="PRESS", alt=True) + kmi.active = True + addon_keymaps.append((km, kmi)) + + +def unregister() -> None: + for km, kmi in addon_keymaps: + km.keymap_items.remove(kmi) + addon_keymaps.clear() diff --git a/operators.py b/operators.py new file mode 100644 index 0000000..e51f762 --- /dev/null +++ b/operators.py @@ -0,0 +1,601 @@ +import bpy +import mathutils +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 = getArmatureObject(context) + arm_object.dspl.pose_library = bpy.data.actions.new( + name=arm_object.name + "_PoseLib") + arm_object.dspl.pose_library.use_fake_user = True + + return {'FINISHED'} + + +# Operator to convert a pose library to dspl property + + +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 = getArmatureObject(context) + arm_object.dspl.pose_library = arm_object.animation_data.action + arm_object.animation_data.action = None + + return {'FINISHED'} + + +# Operator to to draw a menu for new poses + + +class DSPL_OT_DrawNewPoseMenu(bpy.types.Operator): + bl_idname = "dspl.draw_new_pose_menu" + bl_label = "New Pose Menu" + bl_description = "New Pose Menu" + bl_options = {'INTERNAL'} + + def execute(self, context): + return {'FINISHED'} + + def invoke(self, context, event): + return context.window_manager.invoke_popup(self, width=200) + + def draw(self, context): + dspl_create_popup_layout = self.layout + dspl_new_pose_menu = dspl_create_popup_layout.box() + + arm_object = getArmatureObject(context) + pose_library = getPoseLib(context) + dspl_new_pose_menu.prop( + arm_object.dsplvars, "pose_new_name", text="Name") + dspl_new_pose_menu.prop( + arm_object.dsplvars, + "only_selected", icon='GROUP_BONE', text="Selected", toggle=True) + dspl_new_pose_menu.operator( + "dspl.add_pose", icon='ADD', text="Add New Pose").posename = arm_object.dsplvars.pose_new_name + + +# Operator to call up popup menus by ID + + +class DSPL_OT_CallPopupMenu(bpy.types.Operator): + bl_idname = "dspl.call_popup_menu" + bl_label = "Popup menu" + bl_description = "Popup menu" + bl_options = {'REGISTER', 'UNDO'} + + menuID: bpy.props.StringProperty() + + def execute(self, context): + bpy.ops.wm.call_menu(name=self.menuID) + + return {'FINISHED'} + + +# Operator to manage the big menu buttons + + +class DSPL_OT_MenuButtonHandler(bpy.types.Operator): + bl_idname = "dspl.menu_button_handler" + bl_label = "Button Handler" + bl_description = "Button Handler" + bl_options = {'REGISTER', 'UNDO'} + + posename: bpy.props.StringProperty() + + def execute(self, context): + bpy.ops.wm.call_menu(name=self.menuID) + + return {'FINISHED'} + + def invoke(self, context, event): + if event.ctrl: + # Select + action_object = getPoseLib(context) + action_object.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(posename = self.posename) + return {'FINISHED'} + else: + return self.execute(context) + +# 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() + replace: bpy.props.BoolProperty(name="Replace", description="Replace existing pose", default=False, options={'SKIP_SAVE'}) + + def execute(self, context): + + if self.replace == False: + arm_object = getArmatureObject(context) + action_object = getPoseLib(context) + pose_markers = action_object.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: + print("Duplicate posename detected") + 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, new_marker) + + action_object.pose_markers.active = pose_markers[pose_name] + bpy.context.area.tag_redraw() + + print("Added pose - " + pose_markers[new_name].name + " to frame " + str(pose_markers[new_name].frame)) + + else: + arm_object = getArmatureObject(context) + action_object = getPoseLib(context) + pose_markers = action_object.pose_markers + active_marker = pose_markers.active + new_name = active_marker.name + 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, new_marker) + + + + 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 = getArmatureObject(context) + action_object = getPoseLib(context) + pose_markers = action_object.pose_markers + if self.posename: + action_object.pose_markers.active_index = searchPoseMarker(context, posename=self.posename, type="index") + active_index = action_object.pose_markers.active_index + else: + active_index = pose_markers.active_index + active_marker = pose_markers.active + active_frame = active_marker.frame + + fcurves = action_object.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) + + print(next_index) + action_object.pose_markers.active = next_marker + action_object.pose_markers.active_index = next_index + + 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 = getArmatureObject(context) + action_object = getPoseLib(context) + pose_markers = action_object.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() + 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 = getArmatureObject(context) + action_object = getPoseLib(context) + pose_lib = getPoseLib(context) + pose_markers = action_object.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: + arm_object = getArmatureObject(context) + 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: + # swap_index = len(pose_lib.pose_markers) - 1 + return {'FINISHED'} + elif swap_index >= len(pose_lib.pose_markers): + # swap_index = 0 + return {'FINISHED'} + + swap_marker = pose_markers[swap_index] + + tmp_marker_name = swap_marker.name + tmp_marker_frame = swap_marker.frame + action_object.pose_markers[swap_index].name = active_marker.name + action_object.pose_markers[swap_index].frame = active_marker.frame + action_object.pose_markers[active_index].name = tmp_marker_name + action_object.pose_markers[active_index].frame = tmp_marker_frame + action_object.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, Alt+Click to remove)" + bl_options = {'REGISTER', 'UNDO'} + + posename: bpy.props.StringProperty() + + + def execute(self, context): + arm_object = getArmatureObject(context) + action_object = getPoseLib(context) + pose_markers = action_object.pose_markers + + if self.posename: + active_marker = searchPoseMarker(context, posename=self.posename, type="marker") + active_frame = active_marker.frame + active_posename = active_marker.name + action_object.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 + + for bone in arm_object.pose.bones: + if bone.bone.select or arm_object.dsplvars.only_selected == False: + 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: + print("Unsupported bone!") + 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)) + + + print("Applied pose - " + active_posename) + + return {'FINISHED'} + + def invoke(self, context, event): + if event.ctrl: + # Select + action_object = getPoseLib(context) + action_object.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(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_lib.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_lib.pose_markers.active_index <= 0: + self.pose_lib.pose_markers.active_index = len(self.pose_lib.pose_markers) - 1 + else: + self.pose_lib.pose_markers.active_index = self.pose_lib.pose_markers.active_index - 1 + bpy.ops.dspl.apply_pose() + elif event.type in {'RIGHT_ARROW', 'DOWN_ARROW'}: + if self.pose_lib.pose_markers.active_index + 1 >= len(self.pose_lib.pose_markers): + self.pose_lib.pose_markers.active_index = 0 + else: + self.pose_lib.pose_markers.active_index = self.pose_lib.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_lib.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 = getArmatureObject(context) + self.pose_lib = getPoseLib(context) + + if self.pose_lib is None: + self.report({'WARNING'}, "Pose Library not active") + return {'CANCELLED'} + + self.arm_object.pose.backup_create(self.pose_lib) + self.backup_index = self.pose_lib.pose_markers.active_index + bpy.ops.dspl.apply_pose() + + if context.area.type == 'VIEW_3D': + print("Starting modal") + 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'} + print("not in 3d") + 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 = getArmatureObject(context) + pose_library = getPoseLib(context) + + arm_object.dspl.pose_library.name = "del_" + arm_object.dspl.pose_library.name + arm_object.dspl.pose_library = None + + 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: + print("Protecting orphaned action: " + act.name) + act.use_fake_user = True + + return {'FINISHED'} + + +classes = ( + DSPL_OT_CreatePoseLibrary, + DSPL_OT_ConvertPoseLibrary, + DSPL_OT_CallPopupMenu, + DSPL_OT_DrawNewPoseMenu, + DSPL_OT_MenuButtonHandler, + 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)