1 /* SPDX-FileCopyrightText: 2023 Blender Authors
3 * SPDX-License-Identifier: GPL-2.0-or-later */
6 * \ingroup pythonintern
8 * This file adds some helper methods to the context, that cannot fit well in RNA itself.
13 #include "BLI_listbase.h"
14 #include "BLI_utildefines.h"
16 #include "BKE_context.hh"
17 #include "BKE_main.hh"
18 #include "BKE_screen.hh"
19 #include "BKE_workspace.hh"
22 #include "WM_types.hh"
24 #include "bpy_rna_context.hh"
26 #include "../generic/python_compat.hh"
28 #include "RNA_access.hh"
29 #include "RNA_prototypes.hh"
33 /* -------------------------------------------------------------------- */
34 /** \name Private Utility Functions
37 static void bpy_rna_context_temp_set_screen_for_window(bContext
*C
, wmWindow
*win
, bScreen
*screen
)
39 if (screen
== nullptr) {
42 if (screen
== WM_window_get_active_screen(win
)) {
47 BKE_workspace_layout_find_global(CTX_data_main(C
), screen
, &workspace
);
48 /* Changing workspace instead of just screen as they are tied. */
49 WM_window_set_active_workspace(C
, win
, workspace
);
50 WM_window_set_active_screen(win
, workspace
, screen
);
54 * Switching to or away from this screen is not supported.
56 static bool wm_check_screen_switch_supported(const bScreen
*screen
)
58 if (screen
->temp
!= 0) {
61 if (BKE_screen_is_fullscreen_area(screen
)) {
67 static bool wm_check_window_exists(const Main
*bmain
, const wmWindow
*win
)
69 LISTBASE_FOREACH (wmWindowManager
*, wm
, &bmain
->wm
) {
70 if (BLI_findindex(&wm
->windows
, win
) != -1) {
77 static bool wm_check_screen_exists(const Main
*bmain
, const bScreen
*screen
)
79 if (BLI_findindex(&bmain
->screens
, screen
) != -1) {
85 static bool wm_check_area_exists(const wmWindow
*win
, const bScreen
*screen
, const ScrArea
*area
)
87 if (win
&& (BLI_findindex(&win
->global_areas
.areabase
, area
) != -1)) {
90 if (screen
&& (BLI_findindex(&screen
->areabase
, area
) != -1)) {
96 static bool wm_check_region_exists(const bScreen
*screen
,
98 const ARegion
*region
)
100 if (screen
&& (BLI_findindex(&screen
->regionbase
, region
) != -1)) {
103 if (area
&& (BLI_findindex(&area
->regionbase
, region
) != -1)) {
111 /* -------------------------------------------------------------------- */
112 /** \name Temporary Context Override (Python Context Manager)
115 struct ContextStore
{
126 struct BPyContextTempOverride
{
127 PyObject_HEAD
/* Required Python macro. */
130 ContextStore ctx_init
;
131 ContextStore ctx_temp
;
135 * The original screen of `ctx_temp.win`, needed when restoring this windows screen as it
136 * won't be `ctx_init.screen` (when switching the window as well as the screen), see #115937.
141 /** Bypass Python overrides set when calling an operator from Python. */
142 bContext_PyState py_state
;
144 * This dictionary is used to store members that don't have special handling,
145 * see: #bpy_context_temp_override_extract_known_args,
146 * these will then be accessed via #BPY_context_member_get.
148 * This also supports nested *stacking*, so a nested temp-context-overrides
149 * will overlay the new members on the old members (instead of ignoring them).
151 PyObject
*py_state_context_dict
;
154 static void bpy_rna_context_temp_override_dealloc(BPyContextTempOverride
*self
)
156 PyObject_GC_UnTrack(self
);
157 Py_CLEAR(self
->py_state_context_dict
);
158 PyObject_GC_Del(self
);
161 static int bpy_rna_context_temp_override_traverse(BPyContextTempOverride
*self
,
165 Py_VISIT(self
->py_state_context_dict
);
169 static int bpy_rna_context_temp_override_clear(BPyContextTempOverride
*self
)
171 Py_CLEAR(self
->py_state_context_dict
);
175 static bool bpy_rna_context_temp_override_enter_ok_or_error(const BPyContextTempOverride
*self
,
178 const bScreen
*screen
,
180 const ARegion
*region
)
183 /* NOTE(@ideasman42): Regarding sanity checks.
184 * There are 3 different situations to be accounted for here regarding overriding windowing data.
186 * - 1) Nothing is overridden.
187 * Simple, no sanity checks needed.
189 * - 2) Some members are overridden.
190 * Check the state is consistent (that the region is part the area or screen for e.g.).
192 * - 3) Some members are overridden *but* the context members are unchanged.
193 * This is a less obvious case which often happens when a Python script copies the context
194 * typically via `context.copy()`, manipulates it and passes it in as keyword arguments.
196 * A naive approach could be to behave as if these arguments weren't passed in
197 * which would work in many situations however there is a difference
198 * since these members are used to restore the context afterwards.
199 * It's possible a script might use this context-manager to *pin* the context,
200 * running actions that change the context, relying on the context to be restored.
202 * When error-checking unchanged context members some error checks must be skipped
203 * such as the check to disallow temporary screens since that could break using
204 * `temp_override(..)` running with the current context from a render-window for e.g.
206 * In fact all sanity checks could be disabled when the members involved remain unchanged
207 * however it's possible Python scripts corrupt Blender's internal windowing state so keeping
208 * the checks is harmless and alerts developers early on that something is wrong.
211 if (self
->ctx_temp
.region_is_set
&& (region
!= nullptr)) {
212 if (screen
== nullptr && area
== nullptr) {
213 PyErr_SetString(PyExc_TypeError
, "Region set with screen & area set to None");
216 if (!wm_check_region_exists(screen
, area
, region
)) {
217 PyErr_SetString(PyExc_TypeError
, "Region not found in area or screen");
222 if (self
->ctx_temp
.area_is_set
&& (area
!= nullptr)) {
223 if (win
== nullptr && screen
== nullptr) {
224 PyErr_SetString(PyExc_TypeError
, "Area set with window & screen set to None");
227 if (!wm_check_area_exists(win
, screen
, area
)) {
228 PyErr_SetString(PyExc_TypeError
, "Area not found in screen");
233 if (self
->ctx_temp
.screen_is_set
&& (screen
!= nullptr)) {
234 if (win
== nullptr) {
235 PyErr_SetString(PyExc_TypeError
, "Screen set with null window");
238 if (!wm_check_screen_exists(bmain
, screen
)) {
239 PyErr_SetString(PyExc_TypeError
, "Screen not found");
243 /* Skip some checks when the screen is unchanged. */
244 if (self
->ctx_init
.screen_is_set
) {
245 /* Switching away from a temporary screen isn't supported. */
246 if ((self
->ctx_init
.screen
!= nullptr) &&
247 !wm_check_screen_switch_supported(self
->ctx_init
.screen
))
249 PyErr_SetString(PyExc_TypeError
,
250 "Overriding context with an active temporary screen isn't supported");
253 if (!wm_check_screen_switch_supported(screen
)) {
254 PyErr_SetString(PyExc_TypeError
,
255 "Overriding context with temporary screen isn't supported");
258 if (BKE_workspace_layout_find_global(bmain
, screen
, nullptr) == nullptr) {
259 PyErr_SetString(PyExc_TypeError
, "Screen has no workspace");
263 LISTBASE_FOREACH (wmWindowManager
*, wm
, &bmain
->wm
) {
264 LISTBASE_FOREACH (wmWindow
*, win_iter
, &wm
->windows
) {
265 if (win_iter
== win
) {
268 if (screen
== WM_window_get_active_screen(win_iter
)) {
269 PyErr_SetString(PyExc_TypeError
, "Screen is used by another window");
277 if (self
->ctx_temp
.win_is_set
&& (win
!= nullptr)) {
278 if (!wm_check_window_exists(bmain
, win
)) {
279 PyErr_SetString(PyExc_TypeError
, "Window not found");
287 static PyObject
*bpy_rna_context_temp_override_enter(BPyContextTempOverride
*self
)
289 bContext
*C
= self
->context
;
290 Main
*bmain
= CTX_data_main(C
);
292 /* It's crucial to call #CTX_py_state_pop if this function fails with an error. */
293 CTX_py_state_push(C
, &self
->py_state
, self
->py_state_context_dict
);
295 self
->ctx_init
.win
= CTX_wm_window(C
);
296 self
->ctx_init
.screen
= self
->ctx_init
.win
? WM_window_get_active_screen(self
->ctx_init
.win
) :
298 self
->ctx_init
.area
= CTX_wm_area(C
);
299 self
->ctx_init
.region
= CTX_wm_region(C
);
301 wmWindow
*win
= self
->ctx_temp
.win_is_set
? self
->ctx_temp
.win
: self
->ctx_init
.win
;
302 bScreen
*screen
= self
->ctx_temp
.screen_is_set
? self
->ctx_temp
.screen
: self
->ctx_init
.screen
;
303 ScrArea
*area
= self
->ctx_temp
.area_is_set
? self
->ctx_temp
.area
: self
->ctx_init
.area
;
304 ARegion
*region
= self
->ctx_temp
.region_is_set
? self
->ctx_temp
.region
: self
->ctx_init
.region
;
306 self
->ctx_init
.win_is_set
= (self
->ctx_init
.win
!= win
);
307 self
->ctx_init
.screen_is_set
= (self
->ctx_init
.screen
!= screen
);
308 self
->ctx_init
.area_is_set
= (self
->ctx_init
.area
!= area
);
309 self
->ctx_init
.region_is_set
= (self
->ctx_init
.region
!= region
);
311 /* When the screen isn't passed but a window is, match the screen to the window,
312 * it's important to do this after setting `self->ctx_init.screen_is_set` because the screen is
313 * *not* set, only the window, restoring the window will also restore its screen, see #116297. */
314 if ((self
->ctx_temp
.win_is_set
== true) && (self
->ctx_temp
.screen_is_set
== false)) {
315 screen
= win
? WM_window_get_active_screen(win
) : nullptr;
318 if (!bpy_rna_context_temp_override_enter_ok_or_error(self
, bmain
, win
, screen
, area
, region
)) {
319 CTX_py_state_pop(C
, &self
->py_state
);
323 /* Manipulate the context (setup). */
324 if (self
->ctx_temp
.screen_is_set
) {
325 self
->ctx_temp_orig
.screen
= WM_window_get_active_screen(win
);
326 bpy_rna_context_temp_set_screen_for_window(C
, win
, self
->ctx_temp
.screen
);
329 /* NOTE: always set these members, even when they are equal to the current values because
330 * setting the window (for e.g.) clears the area & region, setting the area clears the region.
331 * While it would be useful in some cases to leave the context as-is when setting members
332 * to their current values.
334 * Favor predictable behavior, where setting a member *always* clears the nested
335 * values it contains - no matter the state of the current context.
336 * If this difference is important, the caller can always detect this case and avoid
337 * passing in the context override altogether. */
339 if (self
->ctx_temp
.win_is_set
) {
340 CTX_wm_window_set(C
, self
->ctx_temp
.win
);
342 if (self
->ctx_temp
.screen_is_set
) {
343 CTX_wm_screen_set(C
, self
->ctx_temp
.screen
);
345 if (self
->ctx_temp
.area_is_set
) {
346 CTX_wm_area_set(C
, self
->ctx_temp
.area
);
348 if (self
->ctx_temp
.region_is_set
) {
349 CTX_wm_region_set(C
, self
->ctx_temp
.region
);
355 static PyObject
*bpy_rna_context_temp_override_exit(BPyContextTempOverride
*self
,
358 bContext
*C
= self
->context
;
360 Main
*bmain
= CTX_data_main(C
);
362 /* Manipulate the context (restore). */
363 if (self
->ctx_temp
.screen_is_set
) {
364 if (self
->ctx_temp_orig
.screen
&& wm_check_screen_exists(bmain
, self
->ctx_temp_orig
.screen
)) {
365 wmWindow
*win
= self
->ctx_temp
.win_is_set
? self
->ctx_temp
.win
: self
->ctx_init
.win
;
366 if (win
&& wm_check_window_exists(bmain
, win
)) {
367 /* Disallow switching away from temporary-screens & full-screen areas, while it could be
368 * useful to support this closing a these screens uses different and more involved logic
369 * compared with switching between user managed screens, see: #117188. */
370 if (wm_check_screen_switch_supported(WM_window_get_active_screen(win
))) {
371 bpy_rna_context_temp_set_screen_for_window(C
, win
, self
->ctx_temp_orig
.screen
);
377 /* Account for the window to be freed on file-read,
378 * in this case the window should not be restored, see: #92818.
379 * Also account for other windowing members to be removed on exit,
380 * in this case the context is cleared. */
381 bool do_restore
= true;
383 /* Restore context members as needed.
385 * The checks here behaves as follows:
386 * - When `self->ctx_init.win_is_set` is true, the window was changed by the override.
387 * in this case restore the initial window.
388 * - When `self->ctx_temp.win_is_set` is true, the window was set to the current value.
389 * Setting the window (even to the current value) must be accounted for
390 * because setting the window clears the area and the region members,
391 * which must now be restored.
393 * `is_container_set` is used to detect if nested context members need to be restored.
394 * The comments above refer to the window, it also applies to the screen containing an area
395 * and area which contains a region. */
396 bool is_container_set
= false;
400 if (self
->ctx_init
.win
&& !wm_check_window_exists(bmain
, self
->ctx_init
.win
)) {
401 CTX_wm_window_set(C
, nullptr);
406 if (self
->ctx_init
.win_is_set
) {
407 CTX_wm_window_set(C
, self
->ctx_init
.win
);
408 is_container_set
= true;
410 else if (self
->ctx_temp
.win_is_set
) {
411 if (self
->ctx_init
.win
== CTX_wm_window(C
)) {
412 is_container_set
= true;
415 /* If the context changed, it's incorrect to attempt to restored nested members,
416 * in this case leave the context as-is, see: #119202. */
425 if (self
->ctx_init
.screen
&& !wm_check_screen_exists(bmain
, self
->ctx_init
.screen
)) {
426 CTX_wm_screen_set(C
, nullptr);
431 if (self
->ctx_init
.screen_is_set
|| is_container_set
) {
432 CTX_wm_screen_set(C
, self
->ctx_init
.screen
);
433 is_container_set
= true;
435 else if (self
->ctx_temp
.screen_is_set
) {
436 if (self
->ctx_init
.screen
== CTX_wm_screen(C
)) {
437 is_container_set
= true;
448 if (self
->ctx_init
.area
&&
449 !wm_check_area_exists(self
->ctx_init
.win
, self
->ctx_init
.screen
, self
->ctx_init
.area
))
451 CTX_wm_area_set(C
, nullptr);
456 if (self
->ctx_init
.area_is_set
|| is_container_set
) {
457 CTX_wm_area_set(C
, self
->ctx_init
.area
);
458 is_container_set
= true;
460 else if (self
->ctx_temp
.area_is_set
) {
461 if (self
->ctx_init
.area
== CTX_wm_area(C
)) {
462 is_container_set
= true;
473 if (self
->ctx_init
.region
&&
474 !wm_check_region_exists(self
->ctx_init
.screen
, self
->ctx_init
.area
, self
->ctx_init
.region
))
476 CTX_wm_region_set(C
, nullptr);
481 if (self
->ctx_init
.region_is_set
|| is_container_set
) {
482 CTX_wm_region_set(C
, self
->ctx_init
.region
);
483 is_container_set
= true;
485 /* Enable is there is ever data nested within the region. */
486 else if (false && self
->ctx_temp
.region_is_set
) {
487 if (self
->ctx_init
.region
== CTX_wm_region(C
)) {
488 is_container_set
= true;
496 UNUSED_VARS(is_container_set
, do_restore
);
498 /* Finished restoring the context. */
500 /* A copy may have been made when writing context members, see #BPY_context_dict_clear_members */
501 PyObject
*context_dict_test
= static_cast<PyObject
*>(CTX_py_dict_get(C
));
502 if (context_dict_test
&& (context_dict_test
!= self
->py_state_context_dict
)) {
503 Py_DECREF(context_dict_test
);
505 CTX_py_state_pop(C
, &self
->py_state
);
510 #if (defined(__GNUC__) && !defined(__clang__))
511 # pragma GCC diagnostic push
512 # pragma GCC diagnostic ignored "-Wcast-function-type"
515 static PyMethodDef bpy_rna_context_temp_override_methods
[] = {
516 {"__enter__", (PyCFunction
)bpy_rna_context_temp_override_enter
, METH_NOARGS
},
517 {"__exit__", (PyCFunction
)bpy_rna_context_temp_override_exit
, METH_VARARGS
},
521 #if (defined(__GNUC__) && !defined(__clang__))
522 # pragma GCC diagnostic pop
525 static PyTypeObject BPyContextTempOverride_Type
= {
526 /*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
527 /*tp_name*/ "ContextTempOverride",
528 /*tp_basicsize*/ sizeof(BPyContextTempOverride
),
530 /*tp_dealloc*/ (destructor
)bpy_rna_context_temp_override_dealloc
,
531 /*tp_vectorcall_offset*/ 0,
532 /*tp_getattr*/ nullptr,
533 /*tp_setattr*/ nullptr,
534 /*tp_as_async*/ nullptr,
536 /*tp_as_number*/ nullptr,
537 /*tp_as_sequence*/ nullptr,
538 /*tp_as_mapping*/ nullptr,
542 /*tp_getattro*/ nullptr,
543 /*tp_setattro*/ nullptr,
544 /*tp_as_buffer*/ nullptr,
545 /*tp_flags*/ Py_TPFLAGS_DEFAULT
| Py_TPFLAGS_HAVE_GC
,
547 /*tp_traverse*/ (traverseproc
)bpy_rna_context_temp_override_traverse
,
548 /*tp_clear*/ (inquiry
)bpy_rna_context_temp_override_clear
,
549 /*tp_richcompare*/ nullptr,
550 /*tp_weaklistoffset*/ 0,
552 /*tp_iternext*/ nullptr,
553 /*tp_methods*/ bpy_rna_context_temp_override_methods
,
554 /*tp_members*/ nullptr,
555 /*tp_getset*/ nullptr,
558 /*tp_descr_get*/ nullptr,
559 /*tp_descr_set*/ nullptr,
562 /*tp_alloc*/ nullptr,
565 /*tp_is_gc*/ nullptr,
566 /*tp_bases*/ nullptr,
568 /*tp_cache*/ nullptr,
569 /*tp_subclasses*/ nullptr,
570 /*tp_weaklist*/ nullptr,
572 /*tp_version_tag*/ 0,
573 /*tp_finalize*/ nullptr,
574 /*tp_vectorcall*/ nullptr,
579 /* -------------------------------------------------------------------- */
580 /** \name Context Temporary Override Method
583 static PyObject
*bpy_context_temp_override_extract_known_args(const char *const *kwds_static
,
586 PyObject
*sentinel
= Py_Ellipsis
;
587 PyObject
*kwds_parse
= PyDict_New();
588 for (int i
= 0; kwds_static
[i
]; i
++) {
589 PyObject
*key
= PyUnicode_FromString(kwds_static
[i
]);
590 PyObject
*val
= _PyDict_Pop(kwds
, key
, sentinel
);
591 if (val
!= sentinel
) {
592 if (PyDict_SetItem(kwds_parse
, key
, val
) == -1) {
593 BLI_assert_unreachable();
602 /* NOTE(@ideasman42): `ContextTempOverride` isn't accessible from (without creating an instance),
603 * it should be exposed although it doesn't seem especially important either. */
606 bpy_context_temp_override_doc
,
607 ".. method:: temp_override(*, window=None, area=None, region=None, **keywords)\n"
609 " Context manager to temporarily override members in the context.\n"
611 " :arg window: Window override or None.\n"
612 " :type window: :class:`bpy.types.Window`\n"
613 " :arg screen: Screen override or None.\n"
615 " .. note:: Switching to or away from full-screen areas & temporary screens "
616 "isn't supported. Passing in these screens will raise an exception, "
617 "actions that leave the context such screens won't restore the prior screen.\n"
619 " .. note:: Changing the screen has wider implications "
620 "than other arguments as it will also change the works-space "
621 "and potentially the scene (when pinned).\n"
623 " :type screen: :class:`bpy.types.Screen`\n"
624 " :arg area: Area override or None.\n"
625 " :type area: :class:`bpy.types.Area`\n"
626 " :arg region: Region override or None.\n"
627 " :type region: :class:`bpy.types.Region`\n"
628 " :arg keywords: Additional keywords override context members.\n"
629 " :return: The context manager .\n"
630 " :rtype: ContextTempOverride\n");
631 static PyObject
*bpy_context_temp_override(PyObject
*self
, PyObject
*args
, PyObject
*kwds
)
633 const PointerRNA
*context_ptr
= pyrna_struct_as_ptr(self
, &RNA_Context
);
634 if (context_ptr
== nullptr) {
638 if (kwds
== nullptr) {
639 /* While this is effectively NOP, support having no keywords as it's more involved
640 * to return an alternative (dummy) context manager. */
643 /* Needed because the keywords copied into `kwds_parse` could contain anything.
644 * As the types of keys aren't checked. */
645 if (!PyArg_ValidateKeywordArguments(kwds
)) {
651 BPy_StructRNA_Parse window
;
652 BPy_StructRNA_Parse screen
;
653 BPy_StructRNA_Parse area
;
654 BPy_StructRNA_Parse region
;
656 params
.window
.type
= &RNA_Window
;
657 params
.screen
.type
= &RNA_Screen
;
658 params
.area
.type
= &RNA_Area
;
659 params
.region
.type
= &RNA_Region
;
661 static const char *const _keywords
[] = {
668 static _PyArg_Parser _parser
= {
669 PY_ARG_PARSER_HEAD_COMPAT()
670 "|$" /* Optional, keyword only arguments. */
679 /* Parse known keywords, the remaining keywords are set using #CTX_py_state_push. */
680 kwds
= kwds
? PyDict_Copy(kwds
) : PyDict_New();
682 PyObject
*kwds_parse
= bpy_context_temp_override_extract_known_args(_keywords
, kwds
);
683 const int parse_result
= _PyArg_ParseTupleAndKeywordsFast(args
,
686 pyrna_struct_as_ptr_or_null_parse
,
688 pyrna_struct_as_ptr_or_null_parse
,
690 pyrna_struct_as_ptr_or_null_parse
,
692 pyrna_struct_as_ptr_or_null_parse
,
694 Py_DECREF(kwds_parse
);
701 bContext
*C
= static_cast<bContext
*>(context_ptr
->data
);
703 /* Merge existing keys that don't exist in the keywords passed in.
704 * This makes it possible to nest context overrides. */
705 PyObject
*context_dict_current
= static_cast<PyObject
*>(CTX_py_dict_get(C
));
706 if (context_dict_current
!= nullptr) {
707 PyDict_Merge(kwds
, context_dict_current
, 0);
711 ContextStore ctx_temp
= {nullptr};
712 if (params
.window
.ptr
!= nullptr) {
713 ctx_temp
.win
= static_cast<wmWindow
*>(params
.window
.ptr
->data
);
714 ctx_temp
.win_is_set
= true;
717 if (params
.screen
.ptr
!= nullptr) {
718 ctx_temp
.screen
= static_cast<bScreen
*>(params
.screen
.ptr
->data
);
719 ctx_temp
.screen_is_set
= true;
722 if (params
.area
.ptr
!= nullptr) {
723 ctx_temp
.area
= static_cast<ScrArea
*>(params
.area
.ptr
->data
);
724 ctx_temp
.area_is_set
= true;
727 if (params
.region
.ptr
!= nullptr) {
728 ctx_temp
.region
= static_cast<ARegion
*>(params
.region
.ptr
->data
);
729 ctx_temp
.region_is_set
= true;
732 BPyContextTempOverride
*ret
= PyObject_GC_New(BPyContextTempOverride
,
733 &BPyContextTempOverride_Type
);
735 ret
->ctx_temp
= ctx_temp
;
736 memset(&ret
->ctx_init
, 0, sizeof(ret
->ctx_init
));
738 ret
->ctx_temp_orig
.screen
= nullptr;
740 ret
->py_state_context_dict
= kwds
;
742 PyObject_GC_Track(ret
);
744 return (PyObject
*)ret
;
749 /* -------------------------------------------------------------------- */
750 /** \name Public Type Definition
753 #if (defined(__GNUC__) && !defined(__clang__))
754 # pragma GCC diagnostic push
755 # pragma GCC diagnostic ignored "-Wcast-function-type"
758 PyMethodDef BPY_rna_context_temp_override_method_def
= {
760 (PyCFunction
)bpy_context_temp_override
,
761 METH_VARARGS
| METH_KEYWORDS
,
762 bpy_context_temp_override_doc
,
765 #if (defined(__GNUC__) && !defined(__clang__))
766 # pragma GCC diagnostic pop
769 void bpy_rna_context_types_init()
771 if (PyType_Ready(&BPyContextTempOverride_Type
) < 0) {
772 BLI_assert_unreachable();