1 # SPDX-FileCopyrightText: 2021-2023 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
6 Pose Library - GUI definition.
10 from bpy
.types
import (
21 from bl_ui_utils
.layout
import operator_context
24 class PoseLibraryPanel
:
26 def pose_library_panel_poll(cls
, context
: Context
) -> bool:
27 return context
.mode
== 'POSE'
30 def poll(cls
, context
: Context
) -> bool:
31 return cls
.pose_library_panel_poll(context
)
34 class VIEW3D_AST_pose_library(bpy
.types
.AssetShelf
):
35 bl_space_type
= "VIEW_3D"
36 # We have own keymap items to add custom drag behavior (pose blending), disable the default
38 bl_options
= {'NO_ASSET_DRAG'}
41 def poll(cls
, context
: Context
) -> bool:
42 return PoseLibraryPanel
.poll(context
)
45 def asset_poll(cls
, asset
: AssetRepresentation
) -> bool:
46 return asset
.id_type
== 'ACTION'
49 def draw_context_menu(cls
, _context
: Context
, _asset
: AssetRepresentation
, layout
: UILayout
):
50 # Make sure these operator properties match those used in `VIEW3D_PT_pose_library_legacy`.
51 layout
.operator("poselib.apply_pose_asset", text
="Apply Pose").flipped
= False
52 layout
.operator("poselib.apply_pose_asset", text
="Apply Pose Flipped").flipped
= True
54 with
operator_context(layout
, 'INVOKE_DEFAULT'):
55 layout
.operator("poselib.blend_pose_asset", text
="Blend Pose")
58 props
= layout
.operator("poselib.pose_asset_select_bones", text
="Select Pose Bones")
60 props
= layout
.operator("poselib.pose_asset_select_bones", text
="Deselect Pose Bones")
64 layout
.operator("asset.open_containing_blend_file")
67 class VIEW3D_PT_pose_library_legacy(PoseLibraryPanel
, Panel
):
68 bl_space_type
= "VIEW_3D"
70 bl_category
= "Animation"
71 bl_label
= "Pose Library"
73 def draw(self
, _context
: Context
) -> None:
75 layout
.label(text
="The pose library moved.", icon
='INFO')
76 sub
= layout
.column(align
=True)
77 sub
.label(text
="Pose assets are now available")
78 sub
.label(text
="in the asset shelf.")
79 layout
.operator("screen.region_toggle", text
="Toggle Asset Shelf").region_type
= 'ASSET_SHELF'
82 def pose_library_list_item_context_menu(self
: UIList
, context
: Context
) -> None:
83 def is_pose_asset_view() -> bool:
84 # Important: Must check context first, or the menu is added for every kind of list.
85 list = getattr(context
, "ui_list", None)
86 if not list or list.bl_idname
!= "UI_UL_asset_view" or list.list_id
!= "pose_assets":
88 if not context
.active_file
:
92 def is_pose_library_asset_browser() -> bool:
93 asset_library_ref
= getattr(context
, "asset_library_reference", None)
94 if not asset_library_ref
:
96 asset
= getattr(context
, "asset", None)
99 return bool(asset
.id_type
== 'ACTION')
101 if not is_pose_asset_view() and not is_pose_library_asset_browser():
108 # Make sure these operator properties match those used in `VIEW3D_PT_pose_library_legacy`.
109 layout
.operator("poselib.apply_pose_asset", text
="Apply Pose").flipped
= False
110 layout
.operator("poselib.apply_pose_asset", text
="Apply Pose Flipped").flipped
= True
112 with
operator_context(layout
, 'INVOKE_DEFAULT'):
113 layout
.operator("poselib.blend_pose_asset", text
="Blend Pose")
116 props
= layout
.operator("poselib.pose_asset_select_bones", text
="Select Pose Bones")
118 props
= layout
.operator("poselib.pose_asset_select_bones", text
="Deselect Pose Bones")
121 if not is_pose_asset_view():
123 layout
.operator("asset.assign_action")
126 if is_pose_asset_view():
127 layout
.operator("asset.open_containing_blend_file")
132 class DOPESHEET_PT_asset_panel(PoseLibraryPanel
, Panel
):
133 bl_space_type
= "DOPESHEET_EDITOR"
134 bl_region_type
= "UI"
135 bl_label
= "Create Pose Asset"
136 bl_category
= "Action"
138 def draw(self
, context
: Context
) -> None:
140 col
= layout
.column(align
=True)
141 row
= col
.row(align
=True)
142 row
.operator("poselib.create_pose_asset").activate_new_action
= True
143 if bpy
.types
.POSELIB_OT_restore_previous_action
.poll(context
):
144 row
.operator("poselib.restore_previous_action", text
="", icon
='LOOP_BACK')
145 col
.operator("poselib.copy_as_asset", icon
="COPYDOWN")
147 layout
.operator("poselib.convert_old_poselib")
150 def pose_library_list_item_asset_menu(self
: UIList
, context
: Context
) -> None:
152 layout
.menu("ASSETBROWSER_MT_asset")
155 class ASSETBROWSER_MT_asset(Menu
):
159 def poll(cls
, context
):
160 from bpy_extras
.asset_utils
import SpaceAssetInfo
162 return SpaceAssetInfo
.is_asset_browser_poll(context
)
164 def draw(self
, context
: Context
) -> None:
167 layout
.operator("poselib.paste_asset", icon
='PASTEDOWN')
169 layout
.operator("poselib.create_pose_asset").activate_new_action
= False
172 ### Messagebus subscription to monitor asset library changes.
173 _msgbus_owner
= object()
176 def _on_asset_library_changed() -> None:
177 """Update areas when a different asset library is selected."""
178 refresh_area_types
= {'DOPESHEET_EDITOR', 'VIEW_3D'}
179 for win
in bpy
.context
.window_manager
.windows
:
180 for area
in win
.screen
.areas
:
181 if area
.type not in refresh_area_types
:
187 def register_message_bus() -> None:
188 bpy
.msgbus
.subscribe_rna(
189 key
=(bpy
.types
.FileAssetSelectParams
, "asset_library_reference"),
192 notify
=_on_asset_library_changed
,
193 options
={'PERSISTENT'},
197 def unregister_message_bus() -> None:
198 bpy
.msgbus
.clear_by_owner(_msgbus_owner
)
201 @bpy.app
.handlers
.persistent
202 def _on_blendfile_load_pre(none
, other_none
) -> None:
203 # The parameters are required, but both are None.
204 unregister_message_bus()
207 @bpy.app
.handlers
.persistent
208 def _on_blendfile_load_post(none
, other_none
) -> None:
209 # The parameters are required, but both are None.
210 register_message_bus()
214 DOPESHEET_PT_asset_panel
,
215 VIEW3D_PT_pose_library_legacy
,
216 ASSETBROWSER_MT_asset
,
217 VIEW3D_AST_pose_library
,
220 _register
, _unregister
= bpy
.utils
.register_classes_factory(classes
)
223 def register() -> None:
226 WorkSpace
.active_pose_asset_index
= bpy
.props
.IntProperty(
227 name
="Active Pose Asset",
228 # TODO explain which list the index belongs to, or how it can be used to get the pose.
229 description
="Per workspace index of the active pose asset",
231 # Register for window-manager. This is a global property that shouldn't be
233 WindowManager
.pose_assets
= bpy
.props
.CollectionProperty(type=AssetHandle
)
235 bpy
.types
.UI_MT_list_item_context_menu
.prepend(pose_library_list_item_context_menu
)
236 bpy
.types
.ASSETBROWSER_MT_context_menu
.prepend(pose_library_list_item_context_menu
)
237 bpy
.types
.ASSETBROWSER_MT_editor_menus
.append(pose_library_list_item_asset_menu
)
239 register_message_bus()
240 bpy
.app
.handlers
.load_pre
.append(_on_blendfile_load_pre
)
241 bpy
.app
.handlers
.load_post
.append(_on_blendfile_load_post
)
244 def unregister() -> None:
247 unregister_message_bus()
249 del WorkSpace
.active_pose_asset_index
250 del WindowManager
.pose_assets
252 bpy
.types
.UI_MT_list_item_context_menu
.remove(pose_library_list_item_context_menu
)
253 bpy
.types
.ASSETBROWSER_MT_context_menu
.remove(pose_library_list_item_context_menu
)
254 bpy
.types
.ASSETBROWSER_MT_editor_menus
.remove(pose_library_list_item_asset_menu
)