Blender V5.0
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
10
11#include <Python.h>
12#include <cstddef>
13
14#include "MEM_guardedalloc.h"
15
16#include "BLI_bitmap.h"
17#include "BLI_string.h"
18
19#include "BKE_bpath.hh"
20#include "BKE_global.hh"
21#include "BKE_lib_id.hh"
22#include "BKE_lib_query.hh"
23#include "BKE_main.hh"
24
25#include "DNA_ID.h"
26/* Those following are only to support hack of not listing some internal
27 * 'backward' pointers in generated user_map. */
28
29#include "WM_api.hh"
30#include "WM_types.hh"
31
33
36#include "../generic/python_compat.hh" /* IWYU pragma: keep. */
38
39#include "RNA_enum_types.hh"
40#include "RNA_prototypes.hh"
41
42#include "bpy_rna.hh"
43
44static Main *pyrna_bmain_FromPyObject(PyObject *obj)
45{
46 if (!BPy_StructRNA_Check(obj)) {
47 PyErr_Format(PyExc_TypeError,
48 "Expected a StructRNA of type BlendData, not %.200s",
49 Py_TYPE(obj)->tp_name);
50 return nullptr;
51 }
52 BPy_StructRNA *pyrna = reinterpret_cast<BPy_StructRNA *>(obj);
54 if (!(pyrna->ptr && pyrna->ptr->type == &RNA_BlendData && pyrna->ptr->data)) {
55 PyErr_Format(PyExc_TypeError,
56 "Expected a StructRNA of type BlendData, not %.200s",
57 Py_TYPE(pyrna)->tp_name);
58 return nullptr;
59 }
60 return static_cast<Main *>(pyrna->ptr->data);
61}
62
65 PyObject *py_id_curr;
67
70
72 PyObject *user_map;
75};
76
77static int id_code_as_index(const short idcode)
78{
79 return int(*((ushort *)&idcode));
80}
81
82static bool id_check_type(const ID *id, const BLI_bitmap *types_bitmap)
83{
84 return BLI_BITMAP_TEST_BOOL(types_bitmap, id_code_as_index(GS(id->name)));
85}
86
88{
89 ID **id_p = cb_data->id_pointer;
90
91 if (*id_p) {
92 IDUserMapData *data = static_cast<IDUserMapData *>(cb_data->user_data);
93 const LibraryForeachIDCallbackFlag cb_flag = cb_data->cb_flag;
94
95 if (data->types_bitmap) {
96 if (!id_check_type(*id_p, data->types_bitmap)) {
97 return IDWALK_RET_NOP;
98 }
99 }
100
101 if (cb_flag & IDWALK_CB_LOOPBACK) {
102 /* We skip loop-back pointers like Key.from here,
103 * since it's some internal pointer which is not relevant info for py/API level. */
104 return IDWALK_RET_NOP;
105 }
106
108 /* We skip private pointers themselves, like root node trees, we'll 'link' their own ID
109 * pointers to their 'ID owner' instead. */
110 return IDWALK_RET_NOP;
111 }
112
113 PyObject *key = pyrna_id_CreatePyObject(*id_p);
114
115 PyObject *set;
116 if ((set = PyDict_GetItem(data->user_map, key)) == nullptr) {
117 /* limit to key's added already */
118 if (data->is_subset) {
119 return IDWALK_RET_NOP;
120 }
121
122 set = PySet_New(nullptr);
123 PyDict_SetItem(data->user_map, key, set);
124 Py_DECREF(set);
125 }
126 Py_DECREF(key);
127
128 if (data->py_id_curr == nullptr) {
129 data->py_id_curr = pyrna_id_CreatePyObject(data->id_curr);
130 }
131
132 PySet_Add(set, data->py_id_curr);
133 }
134
135 return IDWALK_RET_NOP;
136}
137
139 /* Wrap. */
140 bpy_user_map_doc,
141 /* NOTE: These documented default values (None) are here just to signal that these parameters
142 * are optional. Explicitly passing None is not valid, and will raise a TypeError. */
143 ".. method:: user_map(*, subset=None, key_types=None, value_types=None)\n"
144 "\n"
145 " Returns a mapping of all ID data-blocks in current ``bpy.data`` to a set of all "
146 "data-blocks using them.\n"
147 "\n"
148 " For list of valid set members for key_types & value_types, see: "
149 ":class:`bpy.types.KeyingSetPath.id_type`.\n"
150 "\n"
151 " :arg subset: When passed, only these data-blocks and their users will be "
152 "included as keys/values in the map.\n"
153 " :type subset: Sequence[:class:`bpy.types.ID`]\n"
154 " :arg key_types: Filter the keys mapped by ID types.\n"
155 " :type key_types: set[str]\n"
156 " :arg value_types: Filter the values in the set by ID types.\n"
157 " :type value_types: set[str]\n"
158 " :return: dictionary that maps data-blocks ID's to their users.\n"
159 " :rtype: dict[:class:`bpy.types.ID`, set[:class:`bpy.types.ID`]]\n");
160static PyObject *bpy_user_map(PyObject *self, PyObject *args, PyObject *kwds)
161{
163 if (!bmain) {
164 return nullptr;
165 }
166
167 ListBase *lb;
168 ID *id;
169
170 PyObject *subset = nullptr;
171
172 PyObject *key_types = nullptr;
173 PyObject *val_types = nullptr;
174 BLI_bitmap *key_types_bitmap = nullptr;
175 BLI_bitmap *val_types_bitmap = nullptr;
176
177 PyObject *ret = nullptr;
178
179 IDUserMapData data_cb = {nullptr};
180
181 static const char *_keywords[] = {"subset", "key_types", "value_types", nullptr};
182 static _PyArg_Parser _parser = {
184 "|$" /* Optional keyword only arguments. */
185 "O" /* `subset` */
186 "O!" /* `key_types` */
187 "O!" /* `value_types` */
188 ":user_map",
189 _keywords,
190 nullptr,
191 };
192 if (!_PyArg_ParseTupleAndKeywordsFast(
193 args, kwds, &_parser, &subset, &PySet_Type, &key_types, &PySet_Type, &val_types))
194 {
195 return nullptr;
196 }
197
198 if (key_types) {
199 key_types_bitmap = pyrna_enum_bitmap_from_set(
200 rna_enum_id_type_items, key_types, sizeof(short), true, USHRT_MAX, "key types");
201 if (key_types_bitmap == nullptr) {
202 goto error;
203 }
204 }
205
206 if (val_types) {
207 val_types_bitmap = pyrna_enum_bitmap_from_set(
208 rna_enum_id_type_items, val_types, sizeof(short), true, USHRT_MAX, "value types");
209 if (val_types_bitmap == nullptr) {
210 goto error;
211 }
212 }
213
214 if (subset) {
215 PyObject *subset_fast = PySequence_Fast(subset, "user_map");
216 if (subset_fast == nullptr) {
217 goto error;
218 }
219
220 PyObject **subset_array = PySequence_Fast_ITEMS(subset_fast);
221 Py_ssize_t subset_len = PySequence_Fast_GET_SIZE(subset_fast);
222
223 data_cb.user_map = _PyDict_NewPresized(subset_len);
224 data_cb.is_subset = true;
225 for (; subset_len; subset_array++, subset_len--) {
226 ID *id;
227 if (!pyrna_id_FromPyObject(*subset_array, &id)) {
228 PyErr_Format(PyExc_TypeError,
229 "Expected an ID type in `subset` iterable, not %.200s",
230 Py_TYPE(*subset_array)->tp_name);
231 Py_DECREF(subset_fast);
232 Py_DECREF(data_cb.user_map);
233 goto error;
234 }
235
236 if (!PyDict_Contains(data_cb.user_map, *subset_array)) {
237 PyObject *set = PySet_New(nullptr);
238 PyDict_SetItem(data_cb.user_map, *subset_array, set);
239 Py_DECREF(set);
240 }
241 }
242 Py_DECREF(subset_fast);
243 }
244 else {
245 data_cb.user_map = PyDict_New();
246 }
247
248 data_cb.types_bitmap = key_types_bitmap;
249
250 FOREACH_MAIN_LISTBASE_BEGIN (bmain, lb) {
252 /* We cannot skip here in case we have some filter on key types... */
253 if (key_types_bitmap == nullptr && val_types_bitmap != nullptr) {
254 if (!id_check_type(id, val_types_bitmap)) {
255 break;
256 }
257 }
258
259 if (!data_cb.is_subset &&
260 /* We do not want to pre-add keys of filtered out types. */
261 (key_types_bitmap == nullptr || id_check_type(id, key_types_bitmap)) &&
262 /* We do not want to pre-add keys when we have filter on value types,
263 * but not on key types. */
264 (val_types_bitmap == nullptr || key_types_bitmap != nullptr))
265 {
266 PyObject *key = pyrna_id_CreatePyObject(id);
267 PyObject *set;
268
269 /* We have to insert the key now,
270 * otherwise ID unused would be missing from final dict... */
271 if ((set = PyDict_GetItem(data_cb.user_map, key)) == nullptr) {
272 set = PySet_New(nullptr);
273 PyDict_SetItem(data_cb.user_map, key, set);
274 Py_DECREF(set);
275 }
276 Py_DECREF(key);
277 }
278
279 if (val_types_bitmap != nullptr && !id_check_type(id, val_types_bitmap)) {
280 continue;
281 }
282
283 data_cb.id_curr = id;
285 nullptr, id, foreach_libblock_id_user_map_callback, &data_cb, IDWALK_NOP);
286
287 if (data_cb.py_id_curr) {
288 Py_DECREF(data_cb.py_id_curr);
289 data_cb.py_id_curr = nullptr;
290 }
291 }
293 }
295
296 ret = data_cb.user_map;
297
298error:
299 if (key_types_bitmap != nullptr) {
300 MEM_freeN(key_types_bitmap);
301 }
302 if (val_types_bitmap != nullptr) {
303 MEM_freeN(val_types_bitmap);
304 }
305
306 return ret;
307}
308
310 /* Data unchanged for the whole process. */
311
313 PyObject *file_path_map;
314
317
318 /* Data modified for each processed ID. */
319
324};
325
327 char * /*path_dst*/,
328 size_t /*path_dst_maxncpy*/,
329 const char *path_src)
330{
331 IDFilePathMapData &data = *static_cast<IDFilePathMapData *>(bpath_data->user_data);
332 PyObject *id_file_path_set = data.id_file_path_set;
333
334 BLI_assert(data.id == bpath_data->owner_id);
335
336 if (path_src && *path_src) {
337 PyObject *path = PyC_UnicodeFromBytes(path_src);
338 PySet_Add(id_file_path_set, path);
339 Py_DECREF(path);
340 }
341 return false;
342}
343
345{
346 IDFilePathMapData &data = *static_cast<IDFilePathMapData *>(bpath_data.user_data);
347 ID *id = data.id;
348 PyObject *id_file_path_set = data.id_file_path_set;
349
350 if (data.include_libraries && ID_IS_LINKED(id)) {
351 PyObject *path = PyC_UnicodeFromBytes(id->lib->filepath);
352 PySet_Add(id_file_path_set, path);
353 Py_DECREF(path);
354 }
355
356 BKE_bpath_foreach_path_id(&bpath_data, id);
357}
358
360 /* Wrap. */
361 bpy_file_path_map_doc,
362 ".. method:: file_path_map(*, subset=None, key_types=None, include_libraries=False)\n"
363 "\n"
364 " Returns a mapping of all ID data-blocks in current ``bpy.data`` to a set of all "
365 "file paths used by them.\n"
366 "\n"
367 " For list of valid set members for key_types, see: "
368 ":class:`bpy.types.KeyingSetPath.id_type`.\n"
369 "\n"
370 " :arg subset: When given, only these data-blocks and their used file paths "
371 "will be included as keys/values in the map.\n"
372 " :type subset: sequence\n"
373 " :arg key_types: When given, filter the keys mapped by ID types. Ignored if ``subset`` is "
374 "also given.\n"
375 " :type key_types: set[str]\n"
376 " :arg include_libraries: Include library file paths of linked data. False by default.\n"
377 " :type include_libraries: bool\n"
378 " :return: dictionary of :class:`bpy.types.ID` instances, with sets of file path "
379 "strings as their values.\n"
380 " :rtype: dict\n");
381static PyObject *bpy_file_path_map(PyObject *self, PyObject *args, PyObject *kwds)
382{
384 if (!bmain) {
385 return nullptr;
386 }
387
388 PyObject *subset = nullptr;
389
390 PyObject *key_types = nullptr;
391 PyObject *include_libraries = nullptr;
392 BLI_bitmap *key_types_bitmap = nullptr;
393
394 PyObject *ret = nullptr;
395
396 IDFilePathMapData filepathmap_data{};
397 BPathForeachPathData bpath_data{};
398
399 static const char *_keywords[] = {"subset", "key_types", "include_libraries", nullptr};
400 static _PyArg_Parser _parser = {
402 "|$" /* Optional keyword only arguments. */
403 "O" /* `subset` */
404 "O!" /* `key_types` */
405 "O!" /* `include_libraries` */
406 ":file_path_map",
407 _keywords,
408 nullptr,
409 };
410 if (!_PyArg_ParseTupleAndKeywordsFast(args,
411 kwds,
412 &_parser,
413 &subset,
414 &PySet_Type,
415 &key_types,
416 &PyBool_Type,
417 &include_libraries))
418 {
419 return nullptr;
420 }
421
422 if (key_types) {
423 key_types_bitmap = pyrna_enum_bitmap_from_set(
424 rna_enum_id_type_items, key_types, sizeof(short), true, USHRT_MAX, "key types");
425 if (key_types_bitmap == nullptr) {
426 goto error;
427 }
428 }
429
430 bpath_data.bmain = bmain;
432 /* TODO: needs to be controllable from caller (add more options to the API). */
434 bpath_data.user_data = &filepathmap_data;
435
436 filepathmap_data.include_libraries = (include_libraries == Py_True);
437
438 if (subset) {
439 PyObject *subset_fast = PySequence_Fast(subset, "subset");
440 if (subset_fast == nullptr) {
441 goto error;
442 }
443
444 PyObject **subset_array = PySequence_Fast_ITEMS(subset_fast);
445 Py_ssize_t subset_len = PySequence_Fast_GET_SIZE(subset_fast);
446
447 filepathmap_data.file_path_map = _PyDict_NewPresized(subset_len);
448 for (; subset_len; subset_array++, subset_len--) {
449 if (PyDict_Contains(filepathmap_data.file_path_map, *subset_array)) {
450 continue;
451 }
452
453 ID *id;
454 if (!pyrna_id_FromPyObject(*subset_array, &id)) {
455 PyErr_Format(PyExc_TypeError,
456 "Expected an ID type in `subset` iterable, not %.200s",
457 Py_TYPE(*subset_array)->tp_name);
458 Py_DECREF(subset_fast);
459 Py_DECREF(filepathmap_data.file_path_map);
460 goto error;
461 }
462
463 filepathmap_data.id_file_path_set = PySet_New(nullptr);
464 PyDict_SetItem(
465 filepathmap_data.file_path_map, *subset_array, filepathmap_data.id_file_path_set);
466 Py_DECREF(filepathmap_data.id_file_path_set);
467
468 filepathmap_data.id = id;
469 foreach_id_file_path_map(bpath_data);
470 }
471 Py_DECREF(subset_fast);
472 }
473 else {
474 ListBase *lb;
475 ID *id;
476 filepathmap_data.file_path_map = PyDict_New();
477
478 FOREACH_MAIN_LISTBASE_BEGIN (bmain, lb) {
480 /* We can skip here in case we have some filter on key types. */
481 if (key_types_bitmap && !id_check_type(id, key_types_bitmap)) {
482 break;
483 }
484
485 PyObject *key = pyrna_id_CreatePyObject(id);
486 filepathmap_data.id_file_path_set = PySet_New(nullptr);
487 PyDict_SetItem(filepathmap_data.file_path_map, key, filepathmap_data.id_file_path_set);
488 Py_DECREF(filepathmap_data.id_file_path_set);
489 Py_DECREF(key);
490
491 filepathmap_data.id = id;
492 foreach_id_file_path_map(bpath_data);
493 }
495 }
497 }
498
499 ret = filepathmap_data.file_path_map;
500
501error:
502 if (key_types_bitmap != nullptr) {
503 MEM_freeN(key_types_bitmap);
504 }
505
506 return ret;
507}
508
518 PyObject *visit_path_fn;
519
526};
527
535 /* BKE_BPATH_FOREACH_PATH_ABSOLUTE is not included here, as its only use is to initialize a
536 * field in BPathForeachPathData that is not used by the callback. */
538 "SKIP_LINKED",
539 0,
540 "Skip Linked",
541 "Skip paths of linked IDs"},
543 "SKIP_PACKED",
544 0,
545 "Skip Packed",
546 "Skip paths when their matching data is packed"},
548 "RESOLVE_TOKEN",
549 0,
550 "Resolve Token",
551 "Resolve tokens within a virtual filepath to a single, concrete, filepath. Currently only "
552 "used for UDIM tiles"},
554 "SKIP_WEAK_REFERENCES",
555 0,
556 "Skip Weak References",
557 "Skip weak reference paths. Those paths are typically 'nice to have' extra information, but "
558 "are not used as actual source of data by the current .blend file"},
560 "SKIP_MULTIFILE",
561 0,
562 "Skip Multi-file",
563 "Skip paths where a single dir is used with an array of files, eg. sequence strip images or "
564 "point-caches. In this case only the first file path is processed. This is needed for "
565 "directory manipulation callbacks which might otherwise modify the same directory multiple "
566 "times"},
568 "RELOAD_EDITED",
569 0,
570 "Reload Edited",
571 "Reload data when the path is edited"},
572 {0, nullptr, 0, nullptr, nullptr},
573};
574
576 char *path_dst,
577 const size_t path_dst_maxncpy,
578 const char *path_src)
579{
580 IDFilePathForeachData &data = *static_cast<IDFilePathForeachData *>(bpath_data->user_data);
581
582 if (data.seen_error) {
583 /* The Python interpreter is already set up for reporting an exception, so don't touch it. */
584 return false;
585 }
586
587 if (!path_src || !path_src[0]) {
588 return false;
589 }
590 BLI_assert(path_dst);
591
592 /* Construct the callback function parameters. */
593 PointerRNA id_ptr = RNA_id_pointer_create(bpath_data->owner_id);
594 PyObject *args = PyTuple_New(3);
595 /* args[0]: */
596 PyObject *py_owner_id = pyrna_struct_CreatePyObject(&id_ptr);
597 /* args[1]: */
598 PyObject *py_path_src = PyUnicode_FromString(path_src);
599 /* args[2]: currently-unused parameter for passing metadata of the path to the Python function.
600 * This is intended pass info like:
601 * - Is the path intended to reference a directory or a file.
602 * - Does the path support templates.
603 * - Is the path referring to input or output (the render output, or file output nodes).
604 * Even though this is not implemented currently, the parameter is already added so that the
605 * eventual implementation is not an API-breaking change. */
606 PyObject *py_path_meta = Py_NewRef(Py_None);
607 PyTuple_SET_ITEMS(args, py_owner_id, py_path_src, py_path_meta);
608
609 /* Call the Python callback function. */
610 PyObject *result = PyObject_CallObject(data.visit_path_fn, args);
611
612 /* Done with the function arguments. */
613 Py_DECREF(args);
614 args = nullptr;
615
616 if (result == nullptr) {
617 data.seen_error = true;
618 return false;
619 }
620
621 if (result == Py_None) {
622 /* Nothing to do. */
624 return false;
625 }
626
627 if (!PyUnicode_Check(result)) {
628 PyErr_Format(PyExc_TypeError,
629 "visit_path_fn() should return a string or None, but returned %s for "
630 "owner_id=\"%s\" and file_path=\"%s\"",
631 Py_TYPE(result)->tp_name,
632 bpath_data->owner_id->name,
633 path_src);
634 data.seen_error = true;
636 return false;
637 }
638
639 /* Copy the returned string back into the path. */
640 Py_ssize_t replacement_path_length = 0;
641 PyObject *value_coerce = nullptr;
642 const char *replacement_path = PyC_UnicodeAsBytesAndSize(
643 result, &replacement_path_length, &value_coerce);
644
645 /* BLI_strncpy wants buffer size, but PyC_UnicodeAsBytesAndSize reports string
646 * length, hence the +1. */
648 path_dst, replacement_path, std::min(path_dst_maxncpy, size_t(replacement_path_length + 1)));
649
650 Py_XDECREF(value_coerce);
652 return true;
653}
654
656 /* Wrap. */
657 bpy_file_path_foreach_doc,
658 ".. method:: file_path_foreach(visit_path_fn, *, subset=None, visit_types=None, "
659 "flags={'SKIP_PACKED', 'SKIP_WEAK_REFERENCES'})\n"
660 "\n"
661 " Call ``visit_path_fn`` for the file paths used by all ID data-blocks in current "
662 "``bpy.data``.\n"
663 "\n"
664 " For list of valid set members for visit_types, see: "
665 ":class:`bpy.types.KeyingSetPath.id_type`.\n"
666 "\n"
667 " :arg visit_path_fn: function that takes three parameters: the data-block, a file path, "
668 "and a placeholder for future use. The function should return either ``None`` or a ``str``. "
669 "In the latter case, the visited file path will be replaced with the returned string.\n"
670 " :type visit_path_fn: Callable[[:class:`bpy.types.ID`, str, Any], str|None]\n"
671 " :arg subset: When given, only these data-blocks and their used file paths "
672 "will be visited.\n"
673 " :type subset: set[str]\n"
674 " :arg visit_types: When given, only visit data-blocks of these types. Ignored if "
675 "``subset`` is also given.\n"
676 " :type visit_types: set[str]\n"
677 " :type flags: set[str]\n"
678 " :arg flags: Set of flags that influence which data-blocks are visited. See "
679 ":ref:`rna_enum_file_path_foreach_flag_items`.\n");
680static PyObject *bpy_file_path_foreach(PyObject *self, PyObject *args, PyObject *kwds)
681{
683 if (!bmain) {
684 return nullptr;
685 }
686
687 PyObject *visit_path_fn = nullptr;
688 PyObject *subset = nullptr;
689 PyObject *visit_types = nullptr;
690 std::unique_ptr<BLI_bitmap, MEM_freeN_smart_ptr_deleter> visit_types_bitmap;
691 PyObject *py_flags = nullptr;
692
693 IDFilePathForeachData filepathforeach_data{};
694 BPathForeachPathData bpath_data{};
695
696 static const char *_keywords[] = {"visit_path_fn", "subset", "visit_types", "flags", nullptr};
697 static _PyArg_Parser _parser = {
699 "O!" /* `visit_path_fn` */
700 "|$" /* Optional keyword only arguments. */
701 "O" /* `subset` */
702 "O!" /* `visit_types` */
703 "O!" /* `flags` */
704 ":file_path_foreach",
705 _keywords,
706 nullptr,
707 };
708 if (!_PyArg_ParseTupleAndKeywordsFast(args,
709 kwds,
710 &_parser,
711 &PyFunction_Type,
712 &visit_path_fn,
713 &subset,
714 &PySet_Type,
715 &visit_types,
716 &PySet_Type,
717 &py_flags))
718 {
719 return nullptr;
720 }
721
722 if (visit_types) {
723 BLI_bitmap *visit_types_bitmap_rawptr = pyrna_enum_bitmap_from_set(
724 rna_enum_id_type_items, visit_types, sizeof(short), true, USHRT_MAX, "visit_types");
725 if (visit_types_bitmap_rawptr == nullptr) {
726 return nullptr;
727 }
728 visit_types_bitmap.reset(visit_types_bitmap_rawptr);
729 }
730
731 /* Parse the flags, start with sensible defaults. */
733 if (py_flags) {
735 py_flags,
736 reinterpret_cast<int *>(&bpath_data.flag),
737 "flags") == -1)
738 {
739 return nullptr;
740 }
741 }
742
743 bpath_data.bmain = bmain;
745 bpath_data.user_data = &filepathforeach_data;
746
747 filepathforeach_data.visit_path_fn = visit_path_fn;
748 filepathforeach_data.seen_error = false;
749
750 if (subset) {
751 /* Visit the given subset of IDs. */
752 PyObject *subset_fast = PySequence_Fast(subset, "subset");
753 if (!subset_fast) {
754 return nullptr;
755 }
756
757 PyObject **subset_array = PySequence_Fast_ITEMS(subset_fast);
758 const Py_ssize_t subset_len = PySequence_Fast_GET_SIZE(subset_fast);
759 for (Py_ssize_t index = 0; index < subset_len; index++) {
760 PyObject *subset_item = subset_array[index];
761
762 ID *id;
763 if (!pyrna_id_FromPyObject(subset_item, &id)) {
764 PyErr_Format(PyExc_TypeError,
765 "Expected an ID type in `subset` iterable, not %.200s",
766 Py_TYPE(subset_item)->tp_name);
767 Py_DECREF(subset_fast);
768 return nullptr;
769 }
770
771 BKE_bpath_foreach_path_id(&bpath_data, id);
772 if (filepathforeach_data.seen_error) {
773 /* Whatever triggered this error should have already set up the Python
774 * interpreter for producing an exception. */
775 Py_DECREF(subset_fast);
776 return nullptr;
777 }
778 }
779 Py_DECREF(subset_fast);
780 }
781 else {
782 /* Visit all IDs, filtered by type if necessary. */
783 ListBase *lb;
784 FOREACH_MAIN_LISTBASE_BEGIN (bmain, lb) {
785 ID *id;
787 if (visit_types_bitmap && !id_check_type(id, visit_types_bitmap.get())) {
788 break;
789 }
790
791 BKE_bpath_foreach_path_id(&bpath_data, id);
792 if (filepathforeach_data.seen_error) {
793 /* Whatever triggered this error should have already set up the Python
794 * interpreter for producing an exception. */
795 return nullptr;
796 }
797 }
799 }
801 }
802
803 Py_RETURN_NONE;
804}
805
807 /* Wrap. */
808 bpy_batch_remove_doc,
809 ".. method:: batch_remove(ids)\n"
810 "\n"
811 " Remove (delete) several IDs at once.\n"
812 "\n"
813 " Note that this function is quicker than individual calls to :func:`remove()` "
814 "(from :class:`bpy.types.BlendData`\n"
815 " ID collections), but less safe/versatile (it can break Blender, e.g. by removing "
816 "all scenes...).\n"
817 "\n"
818 " :arg ids: Sequence of IDs (types can be mixed).\n"
819 " :type ids: Sequence[:class:`bpy.types.ID`]\n");
820static PyObject *bpy_batch_remove(PyObject *self, PyObject *args, PyObject *kwds)
821{
823 if (!bmain) {
824 return nullptr;
825 }
826
827 PyObject *ids = nullptr;
828
829 static const char *_keywords[] = {"ids", nullptr};
830 static _PyArg_Parser _parser = {
832 "O" /* `ids` */
833 ":batch_remove",
834 _keywords,
835 nullptr,
836 };
837 if (!_PyArg_ParseTupleAndKeywordsFast(args, kwds, &_parser, &ids)) {
838 return nullptr;
839 }
840
841 if (!ids) {
842 return nullptr;
843 }
844
845 PyObject *ids_fast = PySequence_Fast(ids, "batch_remove");
846 if (ids_fast == nullptr) {
847 return nullptr;
848 }
849
850 PyObject **ids_array = PySequence_Fast_ITEMS(ids_fast);
851 Py_ssize_t ids_len = PySequence_Fast_GET_SIZE(ids_fast);
852 blender::Set<ID *> ids_to_delete;
853 for (; ids_len; ids_array++, ids_len--) {
854 ID *id;
855 if (!pyrna_id_FromPyObject(*ids_array, &id)) {
856 PyErr_Format(
857 PyExc_TypeError, "Expected an ID type, not %.200s", Py_TYPE(*ids_array)->tp_name);
858 Py_DECREF(ids_fast);
859 return nullptr;
860 }
861
862 ids_to_delete.add(id);
863 }
864 Py_DECREF(ids_fast);
865
866 BKE_id_multi_delete(bmain, ids_to_delete);
867 /* Force full redraw, mandatory to avoid crashes when running this from UI... */
869
870 Py_RETURN_NONE;
871}
872
874 /* Wrap. */
875 bpy_orphans_purge_doc,
876 ".. method:: orphans_purge()\n"
877 "\n"
878 " Remove (delete) all IDs with no user.\n"
879 "\n"
880 " :arg do_local_ids: Include unused local IDs in the deletion, defaults to True\n"
881 " :type do_local_ids: bool, optional\n"
882 " :arg do_linked_ids: Include unused linked IDs in the deletion, defaults to True\n"
883 " :type do_linked_ids: bool, optional\n"
884 " :arg do_recursive: Recursively check for unused IDs, ensuring no orphaned one "
885 "remain after a single run of that function, defaults to False\n"
886 " :type do_recursive: bool, optional\n"
887 " :return: The number of deleted IDs.\n");
888static PyObject *bpy_orphans_purge(PyObject *self, PyObject *args, PyObject *kwds)
889{
891 if (!bmain) {
892 return nullptr;
893 }
894
895 LibQueryUnusedIDsData unused_ids_data;
896 unused_ids_data.do_local_ids = true;
897 unused_ids_data.do_linked_ids = true;
898 unused_ids_data.do_recursive = false;
899
900 static const char *_keywords[] = {"do_local_ids", "do_linked_ids", "do_recursive", nullptr};
901 static _PyArg_Parser _parser = {
903 "|" /* Optional arguments. */
904 "O&" /* `do_local_ids` */
905 "O&" /* `do_linked_ids` */
906 "O&" /* `do_recursive` */
907 ":orphans_purge",
908 _keywords,
909 nullptr,
910 };
911 if (!_PyArg_ParseTupleAndKeywordsFast(args,
912 kwds,
913 &_parser,
915 &unused_ids_data.do_local_ids,
917 &unused_ids_data.do_linked_ids,
919 &unused_ids_data.do_recursive))
920 {
921 return nullptr;
922 }
923
924 /* Tag all IDs to delete. */
925 BKE_lib_query_unused_ids_tag(bmain, ID_TAG_DOIT, unused_ids_data);
926
927 if (unused_ids_data.num_total[INDEX_ID_NULL] == 0) {
928 return PyLong_FromSize_t(0);
929 }
930
931 const size_t num_datablocks_deleted = BKE_id_multi_tagged_delete(bmain);
932 /* Force full redraw, mandatory to avoid crashes when running this from UI... */
934
935 return PyLong_FromSize_t(num_datablocks_deleted);
936}
937
938#ifdef __GNUC__
939# ifdef __clang__
940# pragma clang diagnostic push
941# pragma clang diagnostic ignored "-Wcast-function-type"
942# else
943# pragma GCC diagnostic push
944# pragma GCC diagnostic ignored "-Wcast-function-type"
945# endif
946#endif
947
949 "user_map",
950 (PyCFunction)bpy_user_map,
951 METH_VARARGS | METH_KEYWORDS,
952 bpy_user_map_doc,
953};
955 "file_path_map",
956 (PyCFunction)bpy_file_path_map,
957 METH_VARARGS | METH_KEYWORDS,
958 bpy_file_path_map_doc,
959};
961 "file_path_foreach",
962 (PyCFunction)bpy_file_path_foreach,
963 METH_VARARGS | METH_KEYWORDS,
964 bpy_file_path_foreach_doc,
965};
967 "batch_remove",
968 (PyCFunction)bpy_batch_remove,
969 METH_VARARGS | METH_KEYWORDS,
970 bpy_batch_remove_doc,
971};
973 "orphans_purge",
974 (PyCFunction)bpy_orphans_purge,
975 METH_VARARGS | METH_KEYWORDS,
976 bpy_orphans_purge_doc,
977};
978
979#ifdef __GNUC__
980# ifdef __clang__
981# pragma clang diagnostic pop
982# else
983# pragma GCC diagnostic pop
984# endif
985#endif
void BKE_bpath_foreach_path_id(BPathForeachPathData *bpath_data, ID *id)
Definition bpath.cc:75
@ BKE_BPATH_TRAVERSE_SKIP_WEAK_REFERENCES
Definition BKE_bpath.hh:55
@ BKE_BPATH_FOREACH_PATH_SKIP_LINKED
Definition BKE_bpath.hh:40
@ BKE_BPATH_FOREACH_PATH_SKIP_PACKED
Definition BKE_bpath.hh:42
@ BKE_BPATH_FOREACH_PATH_SKIP_MULTIFILE
Definition BKE_bpath.hh:69
@ BKE_BPATH_FOREACH_PATH_RESOLVE_TOKEN
Definition BKE_bpath.hh:47
@ BKE_BPATH_FOREACH_PATH_RELOAD_EDITED
Definition BKE_bpath.hh:74
void size_t BKE_id_multi_tagged_delete(Main *bmain) ATTR_NONNULL()
size_t BKE_id_multi_delete(Main *bmain, blender::Set< ID * > &ids_to_delete)
@ IDWALK_RET_NOP
void BKE_lib_query_unused_ids_tag(Main *bmain, int tag, LibQueryUnusedIDsData &parameters)
LibraryForeachIDCallbackFlag
@ IDWALK_CB_LOOPBACK
@ IDWALK_CB_EMBEDDED_NOT_OWNING
@ IDWALK_CB_EMBEDDED
void BKE_library_foreach_ID_link(Main *bmain, ID *id, blender::FunctionRef< LibraryIDLinkCallback > callback, void *user_data, LibraryForeachIDFlag flag)
Definition lib_query.cc:431
@ IDWALK_NOP
#define FOREACH_MAIN_LISTBASE_ID_END
Definition BKE_main.hh:552
#define FOREACH_MAIN_LISTBASE_ID_BEGIN(_lb, _id)
Definition BKE_main.hh:546
#define FOREACH_MAIN_LISTBASE_END
Definition BKE_main.hh:564
#define FOREACH_MAIN_LISTBASE_BEGIN(_bmain, _lb)
Definition BKE_main.hh:557
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_BITMAP_TEST_BOOL(_bitmap, _index)
Definition BLI_bitmap.h:71
unsigned int BLI_bitmap
Definition BLI_bitmap.h:13
char * BLI_strncpy(char *__restrict dst, const char *__restrict src, size_t dst_maxncpy) ATTR_NONNULL(1
unsigned short ushort
ID and Library types, which are fundamental for SDNA.
@ ID_TAG_DOIT
Definition DNA_ID.h:1036
#define ID_IS_LINKED(_id)
Definition DNA_ID.h:694
@ INDEX_ID_NULL
Definition DNA_ID.h:1357
Read Guarded memory(de)allocation.
#define NC_WINDOW
Definition WM_types.hh:375
BMesh const char void * data
PyObject * self
PyObject * pyrna_struct_CreatePyObject(PointerRNA *ptr)
Definition bpy_rna.cc:8496
#define PYRNA_STRUCT_CHECK_OBJ(obj)
Definition bpy_rna.hh:78
#define BPy_StructRNA_Check(v)
Definition bpy_rna.hh:73
static bool id_check_type(const ID *id, const BLI_bitmap *types_bitmap)
static PyObject * bpy_orphans_purge(PyObject *self, PyObject *args, PyObject *kwds)
static int id_code_as_index(const short idcode)
static void foreach_id_file_path_map(BPathForeachPathData &bpath_data)
const EnumPropertyItem rna_enum_file_path_foreach_flag_items[]
static bool foreach_id_file_path_foreach_callback(BPathForeachPathData *bpath_data, char *path_dst, const size_t path_dst_maxncpy, const char *path_src)
PyDoc_STRVAR(bpy_user_map_doc, ".. method:: user_map(*, subset=None, key_types=None, value_types=None)\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")
PyMethodDef BPY_rna_id_collection_file_path_foreach_method_def
static bool foreach_id_file_path_map_callback(BPathForeachPathData *bpath_data, char *, size_t, const char *path_src)
static PyObject * bpy_file_path_map(PyObject *self, PyObject *args, PyObject *kwds)
PyMethodDef BPY_rna_id_collection_batch_remove_method_def
static int foreach_libblock_id_user_map_callback(LibraryIDLinkCallbackData *cb_data)
static Main * pyrna_bmain_FromPyObject(PyObject *obj)
static PyObject * bpy_file_path_foreach(PyObject *self, PyObject *args, PyObject *kwds)
static PyObject * bpy_user_map(PyObject *self, PyObject *args, PyObject *kwds)
PyMethodDef BPY_rna_id_collection_orphans_purge_method_def
PyMethodDef BPY_rna_id_collection_file_path_map_method_def
static PyObject * bpy_batch_remove(PyObject *self, PyObject *args, PyObject *kwds)
PyMethodDef BPY_rna_id_collection_user_map_method_def
bool add(const Key &key)
Definition BLI_set.hh:248
#define GS(x)
PyObject * pyrna_id_CreatePyObject(ID *id)
Definition bpy_rna.cc:8601
bool pyrna_id_FromPyObject(PyObject *obj, ID **id)
Definition bpy_rna.cc:8611
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
static void error(const char *str)
int pyrna_enum_bitfield_from_set(const EnumPropertyItem *items, PyObject *value, int *r_value, const char *error_prefix)
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)
const char * PyC_UnicodeAsBytesAndSize(PyObject *py_str, Py_ssize_t *r_size, PyObject **r_coerce)
PyObject * PyC_UnicodeFromBytes(const char *str)
int PyC_ParseBool(PyObject *o, void *p)
header-only compatibility defines.
Py_DECREF(oname)
#define PY_ARG_PARSER_HEAD_COMPAT()
header-only utilities
#define PyTuple_SET_ITEMS(op_arg,...)
return ret
const EnumPropertyItem rna_enum_id_type_items[]
Definition rna_ID.cc:29
PointerRNA RNA_id_pointer_create(ID *id)
eBPathForeachFlag flag
Definition BKE_bpath.hh:102
BPathForeachPathFunctionCallback callback_function
Definition BKE_bpath.hh:101
PyObject_HEAD std::optional< PointerRNA > ptr
Definition bpy_rna.hh:130
BLI_bitmap * types_bitmap
Definition DNA_ID.h:414
struct Library * lib
Definition DNA_ID.h:420
char name[258]
Definition DNA_ID.h:432
std::array< int, INDEX_ID_MAX > num_total
LibraryForeachIDCallbackFlag cb_flag
char filepath[1024]
Definition DNA_ID.h:552
void WM_main_add_notifier(uint type, void *reference)