Blender V4.3
bpy_rna_id_collection.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
11#include <Python.h>
12#include <cstddef>
13
14#include "MEM_guardedalloc.h"
15
16#include "BLI_bitmap.h"
17#include "BLI_utildefines.h"
18
19#include "BKE_global.hh"
20#include "BKE_lib_id.hh"
21#include "BKE_lib_query.hh"
22#include "BKE_main.hh"
23
24#include "DNA_ID.h"
25/* Those following are only to support hack of not listing some internal
26 * 'backward' pointers in generated user_map. */
27
28#include "WM_api.hh"
29#include "WM_types.hh"
30
32
36
37#include "RNA_enum_types.hh"
38
39#include "bpy_rna.hh"
40
43 PyObject *py_id_curr;
45
48
50 PyObject *user_map;
53};
54
55static int id_code_as_index(const short idcode)
56{
57 return int(*((ushort *)&idcode));
58}
59
60static bool id_check_type(const ID *id, const BLI_bitmap *types_bitmap)
61{
62 return BLI_BITMAP_TEST_BOOL(types_bitmap, id_code_as_index(GS(id->name)));
63}
64
66{
67 ID **id_p = cb_data->id_pointer;
68
69 if (*id_p) {
70 IDUserMapData *data = static_cast<IDUserMapData *>(cb_data->user_data);
71 const int cb_flag = cb_data->cb_flag;
72
73 if (data->types_bitmap) {
74 if (!id_check_type(*id_p, data->types_bitmap)) {
75 return IDWALK_RET_NOP;
76 }
77 }
78
79 if (cb_flag & IDWALK_CB_LOOPBACK) {
80 /* We skip loop-back pointers like Key.from here,
81 * since it's some internal pointer which is not relevant info for py/API level. */
82 return IDWALK_RET_NOP;
83 }
84
86 /* We skip private pointers themselves, like root node trees, we'll 'link' their own ID
87 * pointers to their 'ID owner' instead. */
88 return IDWALK_RET_NOP;
89 }
90
91 PyObject *key = pyrna_id_CreatePyObject(*id_p);
92
93 PyObject *set;
94 if ((set = PyDict_GetItem(data->user_map, key)) == nullptr) {
95 /* limit to key's added already */
96 if (data->is_subset) {
97 return IDWALK_RET_NOP;
98 }
99
100 set = PySet_New(nullptr);
101 PyDict_SetItem(data->user_map, key, set);
102 Py_DECREF(set);
103 }
104 Py_DECREF(key);
105
106 if (data->py_id_curr == nullptr) {
107 data->py_id_curr = pyrna_id_CreatePyObject(data->id_curr);
108 }
109
110 PySet_Add(set, data->py_id_curr);
111 }
112
113 return IDWALK_RET_NOP;
114}
115
117 /* Wrap. */
118 bpy_user_map_doc,
119 ".. method:: user_map(subset, key_types, value_types)\n"
120 "\n"
121 " Returns a mapping of all ID data-blocks in current ``bpy.data`` to a set of all "
122 "data-blocks using them.\n"
123 "\n"
124 " For list of valid set members for key_types & value_types, see: "
125 ":class:`bpy.types.KeyingSetPath.id_type`.\n"
126 "\n"
127 " :arg subset: When passed, only these data-blocks and their users will be "
128 "included as keys/values in the map.\n"
129 " :type subset: Sequence[:class:`bpy.types.ID`]\n"
130 " :arg key_types: Filter the keys mapped by ID types.\n"
131 " :type key_types: set[str]\n"
132 " :arg value_types: Filter the values in the set by ID types.\n"
133 " :type value_types: set[str]\n"
134 " :return: dictionary that maps data-blocks ID's to their users.\n"
135 " :rtype: dict[:class:`bpy.types.ID`, set[:class:`bpy.types.ID`]]\n");
136static PyObject *bpy_user_map(PyObject * /*self*/, PyObject *args, PyObject *kwds)
137{
138#if 0 /* If someone knows how to get a proper 'self' in that case... */
139 BPy_StructRNA *pyrna = (BPy_StructRNA *)self;
140 Main *bmain = pyrna->ptr.data;
141#else
142 Main *bmain = G_MAIN; /* XXX Ugly, but should work! */
143#endif
144 ListBase *lb;
145 ID *id;
146
147 PyObject *subset = nullptr;
148
149 PyObject *key_types = nullptr;
150 PyObject *val_types = nullptr;
151 BLI_bitmap *key_types_bitmap = nullptr;
152 BLI_bitmap *val_types_bitmap = nullptr;
153
154 PyObject *ret = nullptr;
155
156 IDUserMapData data_cb = {nullptr};
157
158 static const char *_keywords[] = {"subset", "key_types", "value_types", nullptr};
159 static _PyArg_Parser _parser = {
161 "|$" /* Optional keyword only arguments. */
162 "O" /* `subset` */
163 "O!" /* `key_types` */
164 "O!" /* `value_types` */
165 ":user_map",
166 _keywords,
167 nullptr,
168 };
169 if (!_PyArg_ParseTupleAndKeywordsFast(
170 args, kwds, &_parser, &subset, &PySet_Type, &key_types, &PySet_Type, &val_types))
171 {
172 return nullptr;
173 }
174
175 if (key_types) {
176 key_types_bitmap = pyrna_enum_bitmap_from_set(
177 rna_enum_id_type_items, key_types, sizeof(short), true, USHRT_MAX, "key types");
178 if (key_types_bitmap == nullptr) {
179 goto error;
180 }
181 }
182
183 if (val_types) {
184 val_types_bitmap = pyrna_enum_bitmap_from_set(
185 rna_enum_id_type_items, val_types, sizeof(short), true, USHRT_MAX, "value types");
186 if (val_types_bitmap == nullptr) {
187 goto error;
188 }
189 }
190
191 if (subset) {
192 PyObject *subset_fast = PySequence_Fast(subset, "user_map");
193 if (subset_fast == nullptr) {
194 goto error;
195 }
196
197 PyObject **subset_array = PySequence_Fast_ITEMS(subset_fast);
198 Py_ssize_t subset_len = PySequence_Fast_GET_SIZE(subset_fast);
199
200 data_cb.user_map = _PyDict_NewPresized(subset_len);
201 data_cb.is_subset = true;
202 for (; subset_len; subset_array++, subset_len--) {
203 PyObject *set = PySet_New(nullptr);
204 PyDict_SetItem(data_cb.user_map, *subset_array, set);
205 Py_DECREF(set);
206 }
207 Py_DECREF(subset_fast);
208 }
209 else {
210 data_cb.user_map = PyDict_New();
211 }
212
213 data_cb.types_bitmap = key_types_bitmap;
214
215 FOREACH_MAIN_LISTBASE_BEGIN (bmain, lb) {
217 /* We cannot skip here in case we have some filter on key types... */
218 if (key_types_bitmap == nullptr && val_types_bitmap != nullptr) {
219 if (!id_check_type(id, val_types_bitmap)) {
220 break;
221 }
222 }
223
224 if (!data_cb.is_subset &&
225 /* We do not want to pre-add keys of filtered out types. */
226 (key_types_bitmap == nullptr || id_check_type(id, key_types_bitmap)) &&
227 /* We do not want to pre-add keys when we have filter on value types,
228 * but not on key types. */
229 (val_types_bitmap == nullptr || key_types_bitmap != nullptr))
230 {
231 PyObject *key = pyrna_id_CreatePyObject(id);
232 PyObject *set;
233
234 /* We have to insert the key now,
235 * otherwise ID unused would be missing from final dict... */
236 if ((set = PyDict_GetItem(data_cb.user_map, key)) == nullptr) {
237 set = PySet_New(nullptr);
238 PyDict_SetItem(data_cb.user_map, key, set);
239 Py_DECREF(set);
240 }
241 Py_DECREF(key);
242 }
243
244 if (val_types_bitmap != nullptr && !id_check_type(id, val_types_bitmap)) {
245 continue;
246 }
247
248 data_cb.id_curr = id;
251
252 if (data_cb.py_id_curr) {
253 Py_DECREF(data_cb.py_id_curr);
254 data_cb.py_id_curr = nullptr;
255 }
256 }
258 }
260
261 ret = data_cb.user_map;
262
263error:
264 if (key_types_bitmap != nullptr) {
265 MEM_freeN(key_types_bitmap);
266 }
267
268 if (val_types_bitmap != nullptr) {
269 MEM_freeN(val_types_bitmap);
270 }
271
272 return ret;
273}
274
276 /* Wrap. */
277 bpy_batch_remove_doc,
278 ".. method:: batch_remove(ids)\n"
279 "\n"
280 " Remove (delete) several IDs at once.\n"
281 "\n"
282 " WARNING: Considered experimental feature currently.\n"
283 "\n"
284 " Note that this function is quicker than individual calls to :func:`remove()` "
285 "(from :class:`bpy.types.BlendData`\n"
286 " ID collections), but less safe/versatile (it can break Blender, e.g. by removing "
287 "all scenes...).\n"
288 "\n"
289 " :arg ids: Sequence of IDs (types can be mixed).\n"
290 " :type ids: Sequence[:class:`bpy.types.ID`]\n");
291static PyObject *bpy_batch_remove(PyObject * /*self*/, PyObject *args, PyObject *kwds)
292{
293#if 0 /* If someone knows how to get a proper 'self' in that case... */
294 BPy_StructRNA *pyrna = (BPy_StructRNA *)self;
295 Main *bmain = pyrna->ptr.data;
296#else
297 Main *bmain = G_MAIN; /* XXX Ugly, but should work! */
298#endif
299
300 PyObject *ids = nullptr;
301
302 PyObject *ret = nullptr;
303
304 static const char *_keywords[] = {"ids", nullptr};
305 static _PyArg_Parser _parser = {
307 "O" /* `ids` */
308 ":batch_remove",
309 _keywords,
310 nullptr,
311 };
312 if (!_PyArg_ParseTupleAndKeywordsFast(args, kwds, &_parser, &ids)) {
313 return ret;
314 }
315
316 if (ids) {
317 BKE_main_id_tag_all(bmain, ID_TAG_DOIT, false);
318
319 PyObject *ids_fast = PySequence_Fast(ids, "batch_remove");
320 if (ids_fast == nullptr) {
321 goto error;
322 }
323
324 PyObject **ids_array = PySequence_Fast_ITEMS(ids_fast);
325 Py_ssize_t ids_len = PySequence_Fast_GET_SIZE(ids_fast);
326
327 for (; ids_len; ids_array++, ids_len--) {
328 ID *id;
329 if (!pyrna_id_FromPyObject(*ids_array, &id)) {
330 PyErr_Format(
331 PyExc_TypeError, "Expected an ID type, not %.200s", Py_TYPE(*ids_array)->tp_name);
332 Py_DECREF(ids_fast);
333 goto error;
334 }
335
336 id->tag |= ID_TAG_DOIT;
337 }
338 Py_DECREF(ids_fast);
339
341 /* Force full redraw, mandatory to avoid crashes when running this from UI... */
343 }
344 else {
345 goto error;
346 }
347
348 Py_INCREF(Py_None);
349 ret = Py_None;
350
351error:
352 return ret;
353}
354
356 /* Wrap. */
357 bpy_orphans_purge_doc,
358 ".. method:: orphans_purge()\n"
359 "\n"
360 " Remove (delete) all IDs with no user.\n"
361 "\n"
362 " :arg do_local_ids: Include unused local IDs in the deletion, defaults to True\n"
363 " :type do_local_ids: bool, optional\n"
364 " :arg do_linked_ids: Include unused linked IDs in the deletion, defaults to True\n"
365 " :type do_linked_ids: bool, optional\n"
366 " :arg do_recursive: Recursively check for unused IDs, ensuring no orphaned one "
367 "remain after a single run of that function, defaults to False\n"
368 " :type do_recursive: bool, optional\n"
369 " :return: The number of deleted IDs.\n");
370static PyObject *bpy_orphans_purge(PyObject * /*self*/, PyObject *args, PyObject *kwds)
371{
372#if 0 /* If someone knows how to get a proper 'self' in that case... */
373 BPy_StructRNA *pyrna = (BPy_StructRNA *)self;
374 Main *bmain = pyrna->ptr.data;
375#else
376 Main *bmain = G_MAIN; /* XXX Ugly, but should work! */
377#endif
378
379 LibQueryUnusedIDsData unused_ids_data;
380 unused_ids_data.do_local_ids = true;
381 unused_ids_data.do_linked_ids = true;
382 unused_ids_data.do_recursive = false;
383
384 static const char *_keywords[] = {"do_local_ids", "do_linked_ids", "do_recursive", nullptr};
385 static _PyArg_Parser _parser = {
387 "|" /* Optional arguments. */
388 "O&" /* `do_local_ids` */
389 "O&" /* `do_linked_ids` */
390 "O&" /* `do_recursive` */
391 ":orphans_purge",
392 _keywords,
393 nullptr,
394 };
395 if (!_PyArg_ParseTupleAndKeywordsFast(args,
396 kwds,
397 &_parser,
399 &unused_ids_data.do_local_ids,
401 &unused_ids_data.do_linked_ids,
403 &unused_ids_data.do_recursive))
404 {
405 return nullptr;
406 }
407
408 /* Tag all IDs to delete. */
409 BKE_lib_query_unused_ids_tag(bmain, ID_TAG_DOIT, unused_ids_data);
410
411 if (unused_ids_data.num_total[INDEX_ID_NULL] == 0) {
412 return PyLong_FromSize_t(0);
413 }
414
415 const size_t num_datablocks_deleted = BKE_id_multi_tagged_delete(bmain);
416 /* Force full redraw, mandatory to avoid crashes when running this from UI... */
418
419 return PyLong_FromSize_t(num_datablocks_deleted);
420}
421
422#if (defined(__GNUC__) && !defined(__clang__))
423# pragma GCC diagnostic push
424# pragma GCC diagnostic ignored "-Wcast-function-type"
425#endif
426
428 "user_map",
429 (PyCFunction)bpy_user_map,
430 METH_STATIC | METH_VARARGS | METH_KEYWORDS,
431 bpy_user_map_doc,
432};
434 "batch_remove",
435 (PyCFunction)bpy_batch_remove,
436 METH_STATIC | METH_VARARGS | METH_KEYWORDS,
437 bpy_batch_remove_doc,
438};
440 "orphans_purge",
441 (PyCFunction)bpy_orphans_purge,
442 METH_STATIC | METH_VARARGS | METH_KEYWORDS,
443 bpy_orphans_purge_doc,
444};
445
446#if (defined(__GNUC__) && !defined(__clang__))
447# pragma GCC diagnostic pop
448#endif
#define G_MAIN
void size_t BKE_id_multi_tagged_delete(Main *bmain) ATTR_NONNULL()
void BKE_main_id_tag_all(Main *mainvar, int tag, bool value)
Definition lib_id.cc:1198
@ IDWALK_RET_NOP
void BKE_lib_query_unused_ids_tag(Main *bmain, int tag, LibQueryUnusedIDsData &parameters)
Definition lib_query.cc:962
@ IDWALK_CB_LOOPBACK
@ IDWALK_CB_EMBEDDED_NOT_OWNING
@ IDWALK_CB_EMBEDDED
@ IDWALK_CB_NOP
void BKE_library_foreach_ID_link(Main *bmain, ID *id, blender::FunctionRef< LibraryIDLinkCallback > callback, void *user_data, int flag)
Definition lib_query.cc:416
#define FOREACH_MAIN_LISTBASE_ID_END
Definition BKE_main.hh:469
#define FOREACH_MAIN_LISTBASE_ID_BEGIN(_lb, _id)
Definition BKE_main.hh:463
#define FOREACH_MAIN_LISTBASE_BEGIN(_bmain, _lb)
Definition BKE_main.hh:474
#define BLI_BITMAP_TEST_BOOL(_bitmap, _index)
Definition BLI_bitmap.h:75
unsigned int BLI_bitmap
Definition BLI_bitmap.h:17
unsigned short ushort
ID and Library types, which are fundamental for SDNA.
@ ID_TAG_DOIT
Definition DNA_ID.h:1003
@ INDEX_ID_NULL
Definition DNA_ID.h:1325
Read Guarded memory(de)allocation.
#define NC_WINDOW
Definition WM_types.hh:342
PyObject * self
PyDoc_STRVAR(bpy_user_map_doc, ".. method:: user_map(subset, key_types, value_types)\n" "\n" " Returns a mapping of all ID data-blocks in current ``bpy.data`` to a set of all " "data-blocks using them.\n" "\n" " For list of valid set members for key_types & value_types, see: " ":class:`bpy.types.KeyingSetPath.id_type`.\n" "\n" " :arg subset: When passed, only these data-blocks and their users will be " "included as keys/values in the map.\n" " :type subset: Sequence[:class:`bpy.types.ID`]\n" " :arg key_types: Filter the keys mapped by ID types.\n" " :type key_types: set[str]\n" " :arg value_types: Filter the values in the set by ID types.\n" " :type value_types: set[str]\n" " :return: dictionary that maps data-blocks ID's to their users.\n" " :rtype: dict[:class:`bpy.types.ID`, set[:class:`bpy.types.ID`]]\n")
static bool id_check_type(const ID *id, const BLI_bitmap *types_bitmap)
static PyObject * bpy_orphans_purge(PyObject *, PyObject *args, PyObject *kwds)
static int id_code_as_index(const short idcode)
PyMethodDef BPY_rna_id_collection_batch_remove_method_def
static int foreach_libblock_id_user_map_callback(LibraryIDLinkCallbackData *cb_data)
PyMethodDef BPY_rna_id_collection_orphans_purge_method_def
static PyObject * bpy_batch_remove(PyObject *, PyObject *args, PyObject *kwds)
PyMethodDef BPY_rna_id_collection_user_map_method_def
static PyObject * bpy_user_map(PyObject *, PyObject *args, PyObject *kwds)
node_ attributes set("label", ss.str())
draw_view push_constant(Type::INT, "radiance_src") .push_constant(Type capture_info_buf storage_buf(1, Qualifier::READ, "ObjectBounds", "bounds_buf[]") .push_constant(Type draw_view int
PyObject * pyrna_id_CreatePyObject(ID *id)
Definition bpy_rna.cc:7802
bool pyrna_id_FromPyObject(PyObject *obj, ID **id)
Definition bpy_rna.cc:7812
#define GS(x)
Definition iris.cc:202
void MEM_freeN(void *vmemh)
Definition mallocn.cc:105
static void error(const char *str)
BLI_bitmap * pyrna_enum_bitmap_from_set(const EnumPropertyItem *items, PyObject *value, int type_size, bool type_convert_sign, int bitmap_size, const char *error_prefix)
int PyC_ParseBool(PyObject *o, void *p)
header-only compatibility defines.
#define PY_ARG_PARSER_HEAD_COMPAT()
return ret
const EnumPropertyItem rna_enum_id_type_items[]
Definition rna_ID.cc:35
PyObject_HEAD PointerRNA ptr
Definition bpy_rna.hh:124
BLI_bitmap * types_bitmap
Definition DNA_ID.h:413
std::array< int, INDEX_ID_MAX > num_total
void * data
Definition RNA_types.hh:42
void WM_main_add_notifier(uint type, void *reference)