Merge branch 'blender-v5.2-release'

This commit is contained in:
Guillermo Venegas 2026-07-01 08:35:59 -06:00
commit ccec1025e0
16 changed files with 345 additions and 22 deletions

View file

@ -994,14 +994,24 @@ Layout *pie_menu_layout(PieMenu *pie);
using BlockCreateFunc = Block *(*)(bContext * C, ARegion *region, void *arg1);
using BlockCancelFunc = void (*)(bContext *C, void *arg1);
void popup_block_invoke(bContext *C, BlockCreateFunc func, void *arg, FreeArgFunc arg_free);
void popup_block_invoke(bContext *C,
BlockCreateFunc func,
void *arg,
FreeArgFunc arg_free,
StructRNA *srna_owner = nullptr);
/**
* \param can_refresh: When true, the popup may be refreshed (updated after creation).
* \note It can be useful to disable refresh (even though it will work)
* as this exits text fields which can be disruptive if refresh isn't needed.
* \param srna_owner: The StructRNA type that owns this popup, this popup should be removed if this
* type gets unregistered.
*/
void popup_block_invoke_ex(
bContext *C, BlockCreateFunc func, void *arg, FreeArgFunc arg_free, bool can_refresh);
void popup_block_invoke_ex(bContext *C,
BlockCreateFunc func,
void *arg,
FreeArgFunc arg_free,
bool can_refresh,
StructRNA *srna_owner = nullptr);
void popup_block_ex(bContext *C,
BlockCreateFunc func,
BlockHandleFunc popup_func,
@ -2308,6 +2318,13 @@ void popup_handlers_add(bContext *C,
void popup_handlers_remove(ListBaseT<wmEventHandler> *handlers, PopupBlockHandle *popup);
void popup_handlers_remove_all(bContext *C, ListBaseT<wmEventHandler> *handlers);
/**
* Tags for refresh popup/menu handlers referencing a #StructRNA that is being unregistered,
* popups/menus that can't be refreshed or are created using the \a srna_to_unreg reference will
* be removed.
*/
void refresh_for_srna_unregister(Main *bmain, StructRNA *srna_to_unreg);
/* Module
*
* init and exit should be called before using this module. init_userdef must

View file

@ -42,6 +42,8 @@
#include "BKE_colortools.hh"
#include "BKE_context.hh"
#include "BKE_curveprofile.h"
#include "BKE_global.hh"
#include "BKE_main.hh"
#include "BKE_movieclip.hh"
#include "BKE_paint.hh"
#include "BKE_paint_types.hh"
@ -9730,6 +9732,9 @@ static void button_activate_init(bContext *C,
}
}
static int popup_handler(bContext *C, const wmEvent *event, void *userdata);
static void popup_handler_remove(bContext *C, void *userdata);
static void button_activate_exit(
bContext *C, Button *but, HandleButtonData *data, const bool mousemove, const bool onfree)
{
@ -13207,6 +13212,197 @@ void popup_handlers_remove_all(bContext *C, ListBaseT<wmEventHandler> *handlers)
WM_event_free_ui_handler_all(C, handlers, popup_handler, popup_handler_remove);
}
/**
* Returns true if the button is referencing \a srna.
* \note This would fail to properly determine if the Button is referencing \a srna if the
* reference is stored through an opaque type (like #Button::apply_func).
*/
static bool button_references_srna(Button &button, const StructRNA *srna)
{
if (RNA_struct_is_a(button.rnapoin.type, srna)) {
return true;
}
if (button.opptr && RNA_struct_is_a(button.opptr->type, srna)) {
return true;
}
if (MenuType *mt = button_menutype_get(&button); mt && RNA_struct_is_a(mt->rna_ext.srna, srna)) {
return true;
}
if (PanelType *pt = button_paneltype_get(&button); pt && RNA_struct_is_a(pt->rna_ext.srna, srna))
{
return true;
}
if (wmOperatorType *ot = button_operatortype_get_from_enum_menu(&button, nullptr);
ot && RNA_struct_is_a(ot->rna_ext.srna, srna))
{
return true;
}
if (button.type == ButtonType::SearchMenu) {
const ButtonSearch &search_button = static_cast<const ButtonSearch &>(button);
if (RNA_struct_is_a(search_button.rnasearchpoin.type, srna)) {
return true;
}
}
if (button.type == ButtonType::Decorator) {
const ButtonDecorator &decorator_button = static_cast<const ButtonDecorator &>(button);
if (RNA_struct_is_a(decorator_button.decorated_rnapoin.type, srna)) {
return true;
}
}
for (ButtonExtraOpIcon &extra_op_icon : button.extra_op_icons) {
if (RNA_struct_is_a(extra_op_icon.optype_params->optype->rna_ext.srna, srna)) {
return true;
}
}
if (button.context) {
for (const bContextStoreEntry &entry : button.context->entries) {
if (const PointerRNA *ptr = std::get_if<PointerRNA>(&entry.value)) {
if (RNA_struct_is_a(ptr->type, srna)) {
return true;
}
}
}
}
return false;
}
/**
* Return true if the #popup_block_handle have any reference the #srna_to_unreg but can't be
* refreshed, when this happens its parent must be refreshed or the popup should be closed.
*/
static bool popup_needs_update_for_unreg_srna_recursive(bContext *C,
PopupBlockHandle *popup_block_handle,
StructRNA *srna_to_unreg)
{
bool have_reference = false;
bool valid_for_refresh = popup_block_handle->can_refresh;
if (popup_block_handle->srna_owner &&
RNA_struct_is_a(popup_block_handle->srna_owner, srna_to_unreg))
{
have_reference = true;
valid_for_refresh = false;
}
[&]() {
for (Block &block : popup_block_handle->region->runtime->uiblocks) {
for (Button &button : block.buttons()) {
have_reference = have_reference || button_references_srna(button, srna_to_unreg);
if (button.type == ButtonType::SearchMenu) {
const ButtonSearch &search_button = static_cast<const ButtonSearch &>(button);
if (search_button.active) {
/* Search buttons may reference menus/operators but we can't lookup in its items, close
* any popup with a search button active. */
have_reference = true;
valid_for_refresh = false;
return;
}
}
}
}
}();
Button *active_button = region_find_active_but(popup_block_handle->region);
HandleButtonData *data = active_button ? active_button->active : nullptr;
PopupBlockHandle *sub_handle = data ? data->menu : nullptr;
if (have_reference) {
if (valid_for_refresh) {
/* This popup needs to be refresh, close any sub-menu first. */
if (sub_handle) {
CTX_wm_region_popup_set(C, popup_block_handle->region);
button_active_free(C, active_button);
CTX_wm_region_popup_set(C, nullptr);
}
ED_region_tag_refresh_ui(popup_block_handle->region);
ED_region_tag_redraw(popup_block_handle->region);
return false;
}
/* This popup can't be refreshed, try to refesh parent popup. */
return true;
}
if (!(sub_handle && popup_needs_update_for_unreg_srna_recursive(C, sub_handle, srna_to_unreg))) {
/* There is no child popup referencing the rna type or it can be refresh, no need to refresh
* this popup. */
return false;
}
if (valid_for_refresh) {
CTX_wm_region_popup_set(C, popup_block_handle->region);
button_active_free(C, active_button);
CTX_wm_region_popup_set(C, nullptr);
ED_region_tag_refresh_ui(popup_block_handle->region);
ED_region_tag_redraw(popup_block_handle->region);
return false;
}
/* This popup can't be refreshed, try to refesh parent popup. */
return true;
}
void refresh_for_srna_unregister(Main *bmain, StructRNA *srna_to_unreg)
{
if (G.background) {
return;
}
if (!srna_to_unreg) {
return;
}
bContext *C = CTX_create();
CTX_data_main_set(C, bmain);
auto refresh_or_remove_handler = [&](wmEventHandler &handler_base,
ListBaseT<wmEventHandler> &handlers) {
if (handler_base.type == WM_HANDLER_TYPE_UI && !bool(handler_base.flag & WM_HANDLER_DO_FREE)) {
wmEventHandler_UI *handler = reinterpret_cast<wmEventHandler_UI *>(&handler_base);
if (handler->handle_fn != popup_handler) {
return;
}
PopupBlockHandle *popup_block_handle = static_cast<PopupBlockHandle *>(handler->user_data);
if (!popup_needs_update_for_unreg_srna_recursive(C, popup_block_handle, srna_to_unreg)) {
return;
}
if (handler->remove_fn) {
handler->remove_fn(C, handler->user_data);
}
BLI_remlink(&handlers, handler);
wm_event_free_handler(&handler->head);
}
};
for (wmWindowManager &wm : bmain->wm) {
CTX_wm_manager_set(C, &wm);
for (wmWindow &win : wm.windows) {
CTX_wm_window_set(C, &win);
/* Close any active popup referencing the StructRNA. */
for (wmEventHandler &handler_base : win.runtime->modalhandlers.items_mutable()) {
refresh_or_remove_handler(handler_base, win.runtime->modalhandlers);
}
for (wmEventHandler &handler_base : win.runtime->handlers.items_mutable()) {
refresh_or_remove_handler(handler_base, win.runtime->handlers);
}
/* Close any active menu referencing the StructRNA. */
bScreen *screen = WM_window_get_active_screen(&win);
if (screen && screen->active_region) {
CTX_wm_region_set(C, screen->active_region);
Button *active_button = region_find_active_but(screen->active_region);
HandleButtonData *data = (active_button) ? active_button->active : nullptr;
PopupBlockHandle *sub_handle = (data) ? data->menu : nullptr;
if (sub_handle &&
popup_needs_update_for_unreg_srna_recursive(C, sub_handle, srna_to_unreg))
{
button_active_free(C, active_button);
ED_region_tag_refresh_ui(screen->active_region);
ED_region_tag_redraw(screen->active_region);
}
CTX_wm_region_set(C, nullptr);
}
}
}
CTX_free(C);
}
bool textbutton_activate_rna(const bContext *C,
ARegion *region,
const void *rna_poin_data,

View file

@ -1029,6 +1029,12 @@ struct PopupBlockHandle {
void (*cancel_func)(bContext *C, void *arg) = nullptr;
void *popup_arg = nullptr;
/**
* The StructRNA type that owns this popup, this popup should be removed if this type gets
* unregistered.
*/
StructRNA *srna_owner = nullptr;
/** Store data for refreshing popups. */
PopupBlockCreate popup_create_vars;
/**

View file

@ -621,7 +621,7 @@ static void popup_menu_create_from_menutype(bContext *C,
item_menutype_func(C, layout, mt);
},
true);
handle->srna_owner = mt->rna_ext.srna;
STRNCPY_UTF8(handle->menu_idname, mt->idname);
WorkspaceStatus status(C);
@ -671,14 +671,19 @@ wmOperatorStatus popup_menu_invoke(bContext *C, const char *idname, ReportList *
/** \name Popup Block API
* \{ */
void popup_block_invoke_ex(
bContext *C, BlockCreateFunc func, void *arg, FreeArgFunc arg_free, const bool can_refresh)
void popup_block_invoke_ex(bContext *C,
BlockCreateFunc func,
void *arg,
FreeArgFunc arg_free,
const bool can_refresh,
StructRNA *srna_owner)
{
wmWindow *window = CTX_wm_window(C);
PopupBlockHandle *handle = popup_block_create(
C, nullptr, nullptr, func, nullptr, arg, arg_free, can_refresh);
handle->popup = true;
handle->srna_owner = srna_owner;
/* Clear the status bar. */
WorkspaceStatus status(C);
@ -690,9 +695,10 @@ void popup_block_invoke_ex(
WM_event_add_mousemove(window);
}
void popup_block_invoke(bContext *C, BlockCreateFunc func, void *arg, FreeArgFunc arg_free)
void popup_block_invoke(
bContext *C, BlockCreateFunc func, void *arg, FreeArgFunc arg_free, StructRNA *srna_owner)
{
popup_block_invoke_ex(C, func, arg, arg_free, true);
popup_block_invoke_ex(C, func, arg, arg_free, true, srna_owner);
}
void popup_block_ex(bContext *C,
@ -710,6 +716,9 @@ void popup_block_ex(bContext *C,
handle->retvalue = 1;
handle->popup_op = op;
if (op) {
handle->srna_owner = op->type->srna;
}
handle->popup_arg = arg;
handle->popup_func = popup_func;
handle->cancel_func = cancel_func;

View file

@ -302,7 +302,7 @@ PopupBlockHandle *popover_panel_create(bContext *C,
WM_event_add_mousemove(window);
handle->popup = true;
}
handle->srna_owner = panel_type->rna_ext.srna;
return handle;
}

View file

@ -19,6 +19,8 @@
#include "RNA_define.hh"
#include "RNA_enum_types.hh"
#include "UI_interface_c.hh"
#include "WM_types.hh"
#include "rna_internal.hh"
@ -699,8 +701,9 @@ IDProperty **rna_PropertyGroup_idprops(PointerRNA *ptr)
return reinterpret_cast<IDProperty **>(&ptr->data);
}
bool rna_PropertyGroup_unregister(Main * /*bmain*/, StructRNA *type)
bool rna_PropertyGroup_unregister(Main *bmain, StructRNA *type)
{
ui::refresh_for_srna_unregister(bmain, type);
# ifdef WITH_PYTHON
/* Ensure that a potential py object representing this RNA type is properly dereferenced. */
BPY_free_srna_pytype(type);

View file

@ -16,6 +16,8 @@
#include "rna_internal.hh"
#include "UI_interface_c.hh"
#include "WM_api.hh"
#include "CLG_log.h"
@ -159,7 +161,9 @@ static bool rna_NodeSocket_unregister(Main *bmain, StructRNA *type)
if (!st) {
return false;
}
ui::refresh_for_srna_unregister(bmain, type);
ui::refresh_for_srna_unregister(bmain, st->ext_interface.srna);
ui::refresh_for_srna_unregister(bmain, st->ext_socket.srna);
RNA_struct_free_extension(type, &st->ext_socket);
RNA_struct_free(&RNA_blender_rna_get(), type);

View file

@ -14,6 +14,8 @@
#include "rna_internal.hh"
#include "UI_interface_c.hh"
#include "WM_types.hh"
namespace blender {
@ -216,13 +218,16 @@ static int rna_NodeTreeInterfaceItem_index_get(PointerRNA *ptr)
return ntree->tree_interface.find_item_index(*item);
}
static bool rna_NodeTreeInterfaceSocket_unregister(Main * /*bmain*/, StructRNA *type)
static bool rna_NodeTreeInterfaceSocket_unregister(Main *bmain, StructRNA *type)
{
bke::bNodeSocketType *st = static_cast<bke::bNodeSocketType *>(
RNA_struct_blender_type_get(type));
if (!st) {
return false;
}
ui::refresh_for_srna_unregister(bmain, type);
ui::refresh_for_srna_unregister(bmain, st->ext_interface.srna);
ui::refresh_for_srna_unregister(bmain, st->ext_socket.srna);
RNA_struct_free_extension(type, &st->ext_interface);
@ -340,7 +345,7 @@ static void rna_NodeTreeInterfaceSocket_from_socket_custom(
RNA_parameter_list_free(&list);
}
static StructRNA *rna_NodeTreeInterfaceSocket_register(Main * /*bmain*/,
static StructRNA *rna_NodeTreeInterfaceSocket_register(Main *bmain,
ReportList * /*reports*/,
void *data,
const char *identifier,
@ -378,6 +383,7 @@ static StructRNA *rna_NodeTreeInterfaceSocket_register(Main * /*bmain*/,
/* if RNA type is already registered, unregister first */
if (st->ext_interface.srna) {
ui::refresh_for_srna_unregister(bmain, st->ext_interface.srna);
StructRNA *srna = st->ext_interface.srna;
RNA_struct_free_extension(srna, &st->ext_interface);
RNA_struct_free(&RNA_blender_rna_get(), srna);

View file

@ -38,6 +38,8 @@
#include "IMB_colormanagement.hh"
#include "UI_interface_c.hh"
#include "WM_types.hh"
namespace blender {
@ -1076,6 +1078,7 @@ static bool rna_NodeTree_unregister(Main *bmain, StructRNA *type)
if (!nt) {
return false;
}
ui::refresh_for_srna_unregister(bmain, type);
RNA_struct_free_extension(type, &nt->rna_ext);
RNA_struct_free(&RNA_blender_rna_get(), type);
@ -2000,6 +2003,7 @@ static bool rna_Node_unregister(Main *bmain, StructRNA *type)
if (!nt || rna_Node_is_builtin(nt)) {
return false;
}
ui::refresh_for_srna_unregister(bmain, type);
RNA_struct_free_extension(type, &nt->rna_ext);
RNA_struct_free(&RNA_blender_rna_get(), type);

View file

@ -22,6 +22,8 @@
#include "rna_internal.hh"
#include "UI_interface_c.hh"
#include "RE_engine.h"
namespace blender {
@ -286,6 +288,7 @@ static bool rna_RenderEngine_unregister(Main *bmain, StructRNA *type)
if (!et) {
return false;
}
ui::refresh_for_srna_unregister(bmain, type);
/* Stop all renders in case we were using this one. */
ED_render_engine_changed(bmain, false);

View file

@ -21,6 +21,7 @@
#include "rna_internal.hh"
#include "UI_interface.hh"
#include "UI_interface_c.hh"
#include "UI_interface_layout.hh"
#include "WM_toolsystem.hh"
@ -211,6 +212,7 @@ static bool rna_Panel_unregister(Main *bmain, StructRNA *type)
return false;
}
ui::refresh_for_srna_unregister(bmain, type);
RNA_struct_free_extension(type, &pt->rna_ext);
RNA_struct_free(&RNA_blender_rna_get(), type);
@ -697,7 +699,7 @@ static bool rna_UIList_unregister(Main *bmain, StructRNA *type)
if (!ult) {
return false;
}
ui::refresh_for_srna_unregister(bmain, type);
RNA_struct_free_extension(type, &ult->rna_ext);
RNA_struct_free(&RNA_blender_rna_get(), type);
@ -817,7 +819,7 @@ static void header_draw(const bContext *C, Header *hdr)
RNA_parameter_list_free(&list);
}
static bool rna_Header_unregister(Main * /*bmain*/, StructRNA *type)
static bool rna_Header_unregister(Main *bmain, StructRNA *type)
{
ARegionType *art;
HeaderType *ht = static_cast<HeaderType *>(RNA_struct_blender_type_get(type));
@ -825,10 +827,12 @@ static bool rna_Header_unregister(Main * /*bmain*/, StructRNA *type)
if (!ht) {
return false;
}
if (!(art = region_type_find(nullptr, ht->space_type, ht->region_type))) {
return false;
}
ui::refresh_for_srna_unregister(bmain, type);
RNA_struct_free_extension(type, &ht->rna_ext);
RNA_struct_free(&RNA_blender_rna_get(), type);
@ -978,7 +982,7 @@ static void menu_draw(const bContext *C, Menu *menu)
RNA_parameter_list_free(&list);
}
static bool rna_Menu_unregister(Main * /*bmain*/, StructRNA *type)
static bool rna_Menu_unregister(Main *bmain, StructRNA *type)
{
MenuType *mt = static_cast<MenuType *>(RNA_struct_blender_type_get(type));
@ -986,6 +990,7 @@ static bool rna_Menu_unregister(Main * /*bmain*/, StructRNA *type)
return false;
}
ui::refresh_for_srna_unregister(bmain, type);
RNA_struct_free_extension(type, &mt->rna_ext);
RNA_struct_free(&RNA_blender_rna_get(), type);
@ -1222,6 +1227,7 @@ static bool rna_AssetShelf_unregister(Main *bmain, StructRNA *type)
ed::asset::shelf::type_unlink(*bmain, *shelf_type);
ui::refresh_for_srna_unregister(bmain, type);
RNA_struct_free_extension(type, &shelf_type->rna_ext);
RNA_struct_free(&RNA_blender_rna_get(), type);

View file

@ -33,6 +33,8 @@
#include "rna_internal.hh"
#include "UI_interface_c.hh"
#include "WM_api.hh"
#include "WM_keymap.hh"
#include "WM_types.hh"
@ -1297,14 +1299,14 @@ static PointerRNA rna_Addon_preferences_get(PointerRNA *ptr)
}
}
static bool rna_AddonPref_unregister(Main * /*bmain*/, StructRNA *type)
static bool rna_AddonPref_unregister(Main *bmain, StructRNA *type)
{
bAddonPrefType *apt = static_cast<bAddonPrefType *>(RNA_struct_blender_type_get(type));
if (!apt) {
return false;
}
ui::refresh_for_srna_unregister(bmain, type);
RNA_struct_free_extension(type, &apt->rna_ext);
RNA_struct_free(&RNA_blender_rna_get(), type);

View file

@ -23,6 +23,7 @@
#include "rna_internal.hh"
#include "UI_interface_c.hh"
#include "UI_interface_layout.hh"
#include "WM_api.hh"
@ -1363,7 +1364,7 @@ static IDProperty **rna_wmKeyConfigPref_idprops(PointerRNA *ptr)
return reinterpret_cast<IDProperty **>(&ptr->data);
}
static bool rna_wmKeyConfigPref_unregister(Main * /*bmain*/, StructRNA *type)
static bool rna_wmKeyConfigPref_unregister(Main *bmain, StructRNA *type)
{
wmKeyConfigPrefType_Runtime *kpt_rt = static_cast<wmKeyConfigPrefType_Runtime *>(
RNA_struct_blender_type_get(type));
@ -1371,7 +1372,7 @@ static bool rna_wmKeyConfigPref_unregister(Main * /*bmain*/, StructRNA *type)
if (!kpt_rt) {
return false;
}
ui::refresh_for_srna_unregister(bmain, type);
RNA_struct_free_extension(type, &kpt_rt->rna_ext);
RNA_struct_free(&RNA_blender_rna_get(), type);
@ -1944,7 +1945,8 @@ static bool rna_Operator_unregister(Main *bmain, StructRNA *type)
if (!ot) {
return false;
}
ui::refresh_for_srna_unregister(bmain, ot->srna);
ui::refresh_for_srna_unregister(bmain, type);
/* update while blender is running */
wm = static_cast<wmWindowManager *>(bmain->wm.first);
if (wm) {

View file

@ -1912,7 +1912,7 @@ wmOperatorStatus WM_operator_redo_popup(bContext *C, wmOperator *op)
/* Operator is stored and kept alive in the window manager. So passing a pointer to the UI is
* fine, it will remain valid. */
ui::popup_block_invoke(C, wm_block_create_redo, op, nullptr);
ui::popup_block_invoke(C, wm_block_create_redo, op, nullptr, op->type->srna);
return OPERATOR_CANCELLED;
}

View file

@ -1733,6 +1733,7 @@ if(WITH_UI_TESTS)
test_render.interactive_rendering_cycles
test_render.interactive_rendering_eevee
test_render.animation_rendering_and_player
test_bpy_types.unregister_referenced_type
)
foreach(ui_test ${_ui_tests})
add_blender_test_ui(

View file

@ -0,0 +1,64 @@
# SPDX-FileCopyrightText: 2026 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
"""
This file does not run anything, its methods are accessed for tests by ``run_blender_setup.py``.
"""
import modules.ui_test_utils as ui
def _test_panel():
from bpy.types import Panel
class TEST_PT_panel(Panel):
bl_label = "Test Panel"
bl_idname = "TEST_PT_panel"
bl_category = 'Test Panel'
bl_space_type = 'TEXT_EDITOR'
bl_region_type = 'UI'
def draw(self, context):
self.layout.operator("test.operator")
return TEST_PT_panel
def _test_operator():
from bpy.types import Operator
class TEST_PT_operator(Operator):
bl_label = "Test Operator"
bl_idname = "test.operator"
bl_category = 'Test Operator'
def execute(self, context):
return {'FINISHED'}
return TEST_PT_operator
def unregister_referenced_type():
e, _t, _window = ui.test_window()
test_panel = _test_panel()
test_operator = _test_operator()
import bpy
bpy.utils.register_class(test_panel)
bpy.utils.register_class(test_operator)
yield
# Show the popup with a 'test.operator' reference
bpy.ops.wm.call_panel(name=test_panel.bl_idname, keep_open=True)
yield
bpy.utils.unregister_class(test_operator)
# Let popup be refreshed
yield
# If the reference is not removed activating the button should crash
yield e.ret()