1 # SPDX-License-Identifier: GPL-2.0-or-later
4 Pose Library - GUI definition.
8 from bpy
.types
import (
19 class PoseLibraryPanel
:
21 def pose_library_panel_poll(cls
, context
: Context
) -> bool:
22 return bool(context
.object and context
.object.mode
== 'POSE')
25 def poll(cls
, context
: Context
) -> bool:
26 return cls
.pose_library_panel_poll(context
)
29 class VIEW3D_PT_pose_library(PoseLibraryPanel
, Panel
):
30 bl_space_type
= "VIEW_3D"
32 bl_category
= "Animation"
33 bl_label
= "Pose Library"
35 def draw(self
, context
: Context
) -> None:
38 if hasattr(layout
, "template_asset_view"):
39 workspace
= context
.workspace
40 wm
= context
.window_manager
41 activate_op_props
, drag_op_props
= layout
.template_asset_view(
48 "active_pose_asset_index",
49 filter_id_types
={"filter_action"},
50 activate_operator
="poselib.apply_pose_asset",
51 drag_operator
="poselib.blend_pose_asset",
54 # Make sure operators properties match those used in
55 # `pose_library_list_item_context_menu` so shortcuts show in menus (see T103267).
56 activate_op_props
.flipped
= False
59 def pose_library_list_item_context_menu(self
: UIList
, context
: Context
) -> None:
60 def is_pose_asset_view() -> bool:
61 # Important: Must check context first, or the menu is added for every kind of list.
62 list = getattr(context
, "ui_list", None)
63 if not list or list.bl_idname
!= "UI_UL_asset_view" or list.list_id
!= "pose_assets":
65 if not context
.active_file
:
69 def is_pose_library_asset_browser() -> bool:
70 asset_library_ref
= getattr(context
, "asset_library_ref", None)
71 if not asset_library_ref
:
73 asset
= getattr(context
, "asset_file_handle", None)
76 return bool(asset
.id_type
== 'ACTION')
78 if not is_pose_asset_view() and not is_pose_library_asset_browser():
85 # Make sure these operator properties match those used in `VIEW3D_PT_pose_library`.
86 layout
.operator("poselib.apply_pose_asset", text
="Apply Pose").flipped
= False
87 layout
.operator("poselib.apply_pose_asset", text
="Apply Pose Flipped").flipped
= True
89 old_op_ctx
= layout
.operator_context
90 layout
.operator_context
= 'INVOKE_DEFAULT'
91 props
= layout
.operator("poselib.blend_pose_asset", text
="Blend Pose")
92 layout
.operator_context
= old_op_ctx
95 props
= layout
.operator("poselib.pose_asset_select_bones", text
="Select Pose Bones")
97 props
= layout
.operator("poselib.pose_asset_select_bones", text
="Deselect Pose Bones")
100 if not is_pose_asset_view():
102 layout
.operator("asset.assign_action")
105 if is_pose_asset_view():
106 layout
.operator("asset.open_containing_blend_file")
111 class DOPESHEET_PT_asset_panel(PoseLibraryPanel
, Panel
):
112 bl_space_type
= "DOPESHEET_EDITOR"
113 bl_region_type
= "UI"
114 bl_label
= "Create Pose Asset"
115 bl_category
= "Action"
117 def draw(self
, context
: Context
) -> None:
119 col
= layout
.column(align
=True)
120 row
= col
.row(align
=True)
121 row
.operator("poselib.create_pose_asset").activate_new_action
= True
122 if bpy
.types
.POSELIB_OT_restore_previous_action
.poll(context
):
123 row
.operator("poselib.restore_previous_action", text
="", icon
='LOOP_BACK')
124 col
.operator("poselib.copy_as_asset", icon
="COPYDOWN")
126 layout
.operator("poselib.convert_old_poselib")
129 def pose_library_list_item_asset_menu(self
: UIList
, context
: Context
) -> None:
131 layout
.menu("ASSETBROWSER_MT_asset")
134 class ASSETBROWSER_MT_asset(Menu
):
138 def poll(cls
, context
):
139 from bpy_extras
.asset_utils
import SpaceAssetInfo
141 return SpaceAssetInfo
.is_asset_browser_poll(context
)
143 def draw(self
, context
: Context
) -> None:
146 layout
.operator("poselib.paste_asset", icon
='PASTEDOWN')
148 layout
.operator("poselib.create_pose_asset").activate_new_action
= False
151 ### Messagebus subscription to monitor asset library changes.
152 _msgbus_owner
= object()
155 def _on_asset_library_changed() -> None:
156 """Update areas when a different asset library is selected."""
157 refresh_area_types
= {'DOPESHEET_EDITOR', 'VIEW_3D'}
158 for win
in bpy
.context
.window_manager
.windows
:
159 for area
in win
.screen
.areas
:
160 if area
.type not in refresh_area_types
:
166 def register_message_bus() -> None:
167 bpy
.msgbus
.subscribe_rna(
168 key
=(bpy
.types
.FileAssetSelectParams
, "asset_library_ref"),
171 notify
=_on_asset_library_changed
,
172 options
={'PERSISTENT'},
176 def unregister_message_bus() -> None:
177 bpy
.msgbus
.clear_by_owner(_msgbus_owner
)
180 @bpy.app
.handlers
.persistent
181 def _on_blendfile_load_pre(none
, other_none
) -> None:
182 # The parameters are required, but both are None.
183 unregister_message_bus()
186 @bpy.app
.handlers
.persistent
187 def _on_blendfile_load_post(none
, other_none
) -> None:
188 # The parameters are required, but both are None.
189 register_message_bus()
193 DOPESHEET_PT_asset_panel
,
194 VIEW3D_PT_pose_library
,
195 ASSETBROWSER_MT_asset
,
198 _register
, _unregister
= bpy
.utils
.register_classes_factory(classes
)
201 def register() -> None:
204 WorkSpace
.active_pose_asset_index
= bpy
.props
.IntProperty(
205 name
="Active Pose Asset",
206 # TODO explain which list the index belongs to, or how it can be used to get the pose.
207 description
="Per workspace index of the active pose asset",
209 # Register for window-manager. This is a global property that shouldn't be
211 WindowManager
.pose_assets
= bpy
.props
.CollectionProperty(type=AssetHandle
)
213 bpy
.types
.UI_MT_list_item_context_menu
.prepend(pose_library_list_item_context_menu
)
214 bpy
.types
.ASSETBROWSER_MT_context_menu
.prepend(pose_library_list_item_context_menu
)
215 bpy
.types
.ASSETBROWSER_MT_editor_menus
.append(pose_library_list_item_asset_menu
)
217 register_message_bus()
218 bpy
.app
.handlers
.load_pre
.append(_on_blendfile_load_pre
)
219 bpy
.app
.handlers
.load_post
.append(_on_blendfile_load_post
)
222 def unregister() -> None:
225 unregister_message_bus()
227 del WorkSpace
.active_pose_asset_index
228 del WindowManager
.pose_assets
230 bpy
.types
.UI_MT_list_item_context_menu
.remove(pose_library_list_item_context_menu
)
231 bpy
.types
.ASSETBROWSER_MT_context_menu
.remove(pose_library_list_item_context_menu
)