Blender V5.0
bpy_library_load.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
15
16#include <Python.h>
17#include <cstddef>
18
19#include "BLI_linklist.h"
21#include "BLI_path_utils.hh"
22#include "BLI_string.h"
23#include "BLI_utildefines.h"
24
25#include "BKE_blender_version.h"
27#include "BKE_context.hh"
28#include "BKE_idtype.hh"
29#include "BKE_lib_id.hh"
30#include "BKE_main.hh"
31#include "BKE_report.hh"
32
33#include "DNA_space_types.h" /* FILE_LINK, FILE_RELPATH */
34
35#include "BLO_readfile.hh"
36
37#include "bpy_capi_utils.hh"
38#include "bpy_library.hh"
39
41#include "../generic/python_compat.hh" /* IWYU pragma: keep. */
43
44/* nifty feature. swap out strings for RNA data */
45#define USE_RNA_DATABLOCKS
46
47#ifdef USE_RNA_DATABLOCKS
48# include "RNA_access.hh"
49# include "bpy_rna.hh"
50#endif
51
52/* -------------------------------------------------------------------- */
55
57 bool value;
58 uint32_t flag;
59};
60static uint32_t bool_flag_pair_as_flag(const BoolFlagPair *bool_flags, int bool_flags_num)
61{
62 uint32_t flag = 0;
63 for (int i = 0; i < bool_flags_num; i++) {
64 BLI_assert(bool_flags[i].flag);
65 if (bool_flags[i].value) {
66 flag |= bool_flags[i].flag;
67 }
68 }
69 return flag;
70}
71
73
77static constexpr Py_ssize_t bpy_library_dict_num = INDEX_ID_MAX + 1;
78
113
114static PyObject *bpy_lib_load(BPy_PropertyRNA *self, PyObject *args, PyObject *kw);
115static PyObject *bpy_lib_enter(BPy_Library *self);
116static PyObject *bpy_lib_exit(BPy_Library *self, PyObject *args);
117static PyObject *bpy_lib_dir(BPy_Library *self);
118
119#ifdef __GNUC__
120# ifdef __clang__
121# pragma clang diagnostic push
122# pragma clang diagnostic ignored "-Wcast-function-type"
123# else
124# pragma GCC diagnostic push
125# pragma GCC diagnostic ignored "-Wcast-function-type"
126# endif
127#endif
128
129static PyMethodDef bpy_lib_methods[] = {
130 {"__enter__", (PyCFunction)bpy_lib_enter, METH_NOARGS},
131 {"__exit__", (PyCFunction)bpy_lib_exit, METH_VARARGS},
132 {"__dir__", (PyCFunction)bpy_lib_dir, METH_NOARGS},
133 {nullptr} /* sentinel */
134};
135
136#ifdef __GNUC__
137# ifdef __clang__
138# pragma clang diagnostic pop
139# else
140# pragma GCC diagnostic pop
141# endif
142#endif
143
145{
146 Py_XDECREF(self->dict);
147 Py_TYPE(self)->tp_free(self);
148}
149
150static PyTypeObject bpy_lib_Type = {
151 /*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
152 /*tp_name*/ "bpy_lib",
153 /*tp_basicsize*/ sizeof(BPy_Library),
154 /*tp_itemsize*/ 0,
155 /*tp_dealloc*/ (destructor)bpy_lib_dealloc,
156 /*tp_vectorcall_offset*/ 0,
157 /*tp_getattr*/ nullptr,
158 /*tp_setattr*/ nullptr,
159 /*tp_as_async*/ nullptr,
160 /*tp_repr*/ nullptr,
161 /*tp_as_number*/ nullptr,
162 /*tp_as_sequence*/ nullptr,
163 /*tp_as_mapping*/ nullptr,
164 /*tp_hash*/ nullptr,
165 /*tp_call*/ nullptr,
166 /*tp_str*/ nullptr,
167 /*tp_getattro*/ PyObject_GenericGetAttr,
168 /*tp_setattro*/ nullptr,
169 /*tp_as_buffer*/ nullptr,
170 /*tp_flags*/ Py_TPFLAGS_DEFAULT,
171 /*tp_doc*/ nullptr,
172 /*tp_traverse*/ nullptr,
173 /*tp_clear*/ nullptr,
174 /*tp_richcompare*/ nullptr,
175 /*tp_weaklistoffset*/ 0,
176 /*tp_iter*/ nullptr,
177 /*tp_iternext*/ nullptr,
178 /*tp_methods*/ bpy_lib_methods,
179 /*tp_members*/ nullptr,
180 /*tp_getset*/ nullptr,
181 /*tp_base*/ nullptr,
182 /*tp_dict*/ nullptr,
183 /*tp_descr_get*/ nullptr,
184 /*tp_descr_set*/ nullptr,
185 /*tp_dictoffset*/ offsetof(BPy_Library, dict),
186 /*tp_init*/ nullptr,
187 /*tp_alloc*/ nullptr,
188 /*tp_new*/ nullptr,
189 /*tp_free*/ nullptr,
190 /*tp_is_gc*/ nullptr,
191 /*tp_bases*/ nullptr,
192 /*tp_mro*/ nullptr,
193 /*tp_cache*/ nullptr,
194 /*tp_subclasses*/ nullptr,
195 /*tp_weaklist*/ nullptr,
196 /*tp_del*/ nullptr,
197 /*tp_version_tag*/ 0,
198 /*tp_finalize*/ nullptr,
199 /*tp_vectorcall*/ nullptr,
200};
201
203 /* Wrap. */
204 bpy_lib_load_doc,
205 ".. method:: load("
206 "filepath, "
207 "*, "
208 "link=False, "
209 "relative=False, "
210 "set_fake=False, "
211 "recursive=False, "
212 "reuse_local_id=False, "
213 "assets_only=False, "
214 "clear_asset_data=False, "
215 "create_liboverrides=False, "
216 "reuse_liboverrides=False, "
217 "create_liboverrides_runtime=False)\n"
218 "\n"
219 " Returns a context manager which exposes 2 library objects on entering.\n"
220 " Each object has attributes matching bpy.data which are lists of strings to be linked.\n"
221 "\n"
222 " :arg filepath: The path to a blend file.\n"
223 " :type filepath: str | bytes\n"
224 " :arg link: When False reference to the original file is lost.\n"
225 " :type link: bool\n"
226 " :arg pack: If True, and ``link`` is also True, pack linked data-blocks into the current "
227 "blend-file.\n"
228 " :type pack: bool\n"
229 " :arg relative: When True the path is stored relative to the open blend file.\n"
230 " :type relative: bool\n"
231 " :arg set_fake: If True, set fake user on appended IDs.\n"
232 " :type set_fake: bool\n"
233 " :arg recursive: If True, also make indirect dependencies of appended libraries local.\n"
234 " :type recursive: bool\n"
235 " :arg reuse_local_id: If True,"
236 "try to re-use previously appended matching ID on new append.\n"
237 " :type reuse_local_id: bool\n"
238 " :arg assets_only: If True, only list data-blocks marked as assets.\n"
239 " :type assets_only: bool\n"
240 " :arg clear_asset_data: If True, "
241 "clear the asset data on append (it is always kept for linked data).\n"
242 " :type clear_asset_data: bool\n"
243 " :arg create_liboverrides: If True and ``link`` is True, liboverrides will\n"
244 " be created for linked data.\n"
245 " :type create_liboverrides: bool\n"
246 " :arg reuse_liboverrides: If True and ``create_liboverride`` is True,\n"
247 " search for existing liboverride first.\n"
248 " :type reuse_liboverrides: bool\n"
249 " :arg create_liboverrides_runtime: If True and ``create_liboverride`` is True,\n"
250 " create (or search for existing) runtime liboverride.\n"
251 " :type create_liboverrides_runtime: bool\n");
252static PyObject *bpy_lib_load(BPy_PropertyRNA *self, PyObject *args, PyObject *kw)
253{
254 Main *bmain_base = CTX_data_main(BPY_context_get());
255 Main *bmain = static_cast<Main *>(self->ptr->data); /* Typically #G_MAIN */
257 PyC_UnicodeAsBytesAndSize_Data filepath_data = {nullptr};
258
259 /* #BPy_Library::flag
260 *
261 * - #BLO_LIBLINK_OBDATA_INSTANCE: The caller must manage instancing.
262 * - #BLO_LIBLINK_COLLECTION_INSTANCE: The caller must manage instancing.
263 */
264 struct {
265 BoolFlagPair is_link = {false, FILE_LINK};
266 BoolFlagPair is_pack = {false, BLO_LIBLINK_PACK};
267 BoolFlagPair is_relative = {false, FILE_RELPATH};
269 BoolFlagPair recursive = {false, BLO_LIBLINK_APPEND_RECURSIVE};
270 BoolFlagPair reuse_local_id = {false, BLO_LIBLINK_APPEND_LOCAL_ID_REUSE};
271 BoolFlagPair assets_only = {false, FILE_ASSETS_ONLY};
272 BoolFlagPair clear_asset_data = {false, BLO_LIBLINK_APPEND_ASSET_DATA_CLEAR};
273 } flag_vars;
274
275 bool create_liboverrides = false;
276
277 /* #BPy_Library::liboverride_flags */
278 struct {
280 BoolFlagPair create_liboverrides_runtime = {false, BKE_LIBLINK_OVERRIDE_CREATE_RUNTIME};
281 } liboverride_flag_vars;
282
283 static const char *_keywords[] = {
284 "filepath",
285 "link",
286 "pack",
287 "relative",
288 "set_fake",
289 "recursive",
290 "reuse_local_id",
291 "assets_only",
292 "clear_asset_data",
293 "create_liboverrides",
294 "reuse_liboverrides",
295 "create_liboverrides_runtime",
296 nullptr,
297 };
298 static _PyArg_Parser _parser = {
300 "O&" /* `filepath` */
301 /* Optional keyword only arguments. */
302 "|$"
303 "O&" /* `link` */
304 "O&" /* `pack` */
305 "O&" /* `relative` */
306 "O&" /* `recursive` */
307 "O&" /* `set_fake` */
308 "O&" /* `reuse_local_id` */
309 "O&" /* `assets_only` */
310 "O&" /* `clear_asset_data` */
311 "O&" /* `create_liboverrides` */
312 "O&" /* `reuse_liboverrides` */
313 "O&" /* `create_liboverrides_runtime` */
314 ":load",
315 _keywords,
316 nullptr,
317 };
318 if (!_PyArg_ParseTupleAndKeywordsFast(args,
319 kw,
320 &_parser,
322 &filepath_data,
324 &flag_vars.is_link,
326 &flag_vars.is_pack,
328 &flag_vars.is_relative,
330 &flag_vars.recursive,
332 &flag_vars.set_fake,
334 &flag_vars.reuse_local_id,
336 &flag_vars.assets_only,
338 &flag_vars.clear_asset_data,
340 &create_liboverrides,
342 &liboverride_flag_vars.reuse_liboverrides,
344 &liboverride_flag_vars.create_liboverrides_runtime))
345 {
346 return nullptr;
347 }
348
349 const char *blendfile_path = BKE_main_blendfile_path(bmain);
350 char filepath_rel[FILE_MAX];
351 char filepath_abs[FILE_MAX];
352
353 STRNCPY(filepath_rel, filepath_data.value);
354 STRNCPY(filepath_abs, filepath_rel);
355 BLI_path_abs(filepath_abs, blendfile_path);
356 Py_XDECREF(filepath_data.value_coerce);
357
358 if (blendfile_path[0]) {
359 /* NOTE: intentionally leave `filepath_abs` and only use normalizing for comparison.
360 * It's important that this comparison matches read-files logic for matching paths.
361 * See the logic inside #BKE_blendfile_link.
362 *
363 * This means it's not necessary to check if the paths are *actually* the same.
364 * It's possible to load from this file if a user makes a symbolic-link - for example.
365 * See #140929. */
366 char filepath_abs_normalized[FILE_MAX];
367 STRNCPY(filepath_abs_normalized, filepath_abs);
368 BLI_path_normalize(filepath_abs_normalized);
369 if (BLI_path_cmp(filepath_abs_normalized, blendfile_path) == 0) {
370 PyErr_SetString(PyExc_ValueError, "Cannot load from the current blend file.");
371 return nullptr;
372 }
373 }
374
375 if (flag_vars.is_link.value) {
376 /* Link. */
377 if (flag_vars.set_fake.value) {
378 PyErr_SetString(PyExc_ValueError, "`link` must be False if `set_fake` is True");
379 return nullptr;
380 }
381 if (flag_vars.recursive.value) {
382 PyErr_SetString(PyExc_ValueError, "`link` must be False if `recursive` is True");
383 return nullptr;
384 }
385 if (flag_vars.reuse_local_id.value) {
386 PyErr_SetString(PyExc_ValueError, "`link` must be False if `reuse_local_id` is True");
387 return nullptr;
388 }
389 if (flag_vars.clear_asset_data.value) {
390 PyErr_SetString(PyExc_ValueError, "`link` must be False if `clear_asset_data` is True");
391 return nullptr;
392 }
393 }
394 else {
395 /* Append. */
396 if (create_liboverrides) {
397 PyErr_SetString(PyExc_ValueError, "`link` is False but `create_liboverrides` is True");
398 return nullptr;
399 }
400 if (flag_vars.is_pack.value) {
401 PyErr_SetString(PyExc_ValueError, "`pack` must be False if `link` is False");
402 return nullptr;
403 }
404 }
405
406 if (create_liboverrides) {
407 /* Library overrides. */
408 if (flag_vars.is_pack.value) {
409 PyErr_SetString(PyExc_ValueError, "`create_liboverrides` must be False if `pack` is True");
410 return nullptr;
411 }
412 }
413 else {
414 /* Library overrides (disabled). */
415 if (liboverride_flag_vars.reuse_liboverrides.value) {
416 PyErr_SetString(PyExc_ValueError,
417 "`create_liboverrides` is False but `reuse_liboverrides` is True");
418 return nullptr;
419 }
420 if (liboverride_flag_vars.create_liboverrides_runtime.value) {
421 PyErr_SetString(PyExc_ValueError,
422 "`create_liboverrides` is False but `create_liboverrides_runtime` is True");
423 return nullptr;
424 }
425 }
426
427 ret = PyObject_New(BPy_Library, &bpy_lib_Type);
428
429 STRNCPY(ret->relpath, filepath_rel);
430 STRNCPY(ret->abspath, filepath_abs);
431
432 ret->bmain = bmain;
433 ret->bmain_is_temp = (bmain != bmain_base);
434
435 ret->blo_handle = nullptr;
436
437 ret->flag = bool_flag_pair_as_flag(reinterpret_cast<const BoolFlagPair *>(&flag_vars),
438 sizeof(flag_vars) / sizeof(BoolFlagPair));
439
440 ret->create_liboverrides = create_liboverrides;
441 ret->liboverride_flags = create_liboverrides ?
443 reinterpret_cast<const BoolFlagPair *>(&liboverride_flag_vars),
444 sizeof(liboverride_flag_vars) / sizeof(BoolFlagPair))) :
446
447 ret->dict = _PyDict_NewPresized(bpy_library_dict_num);
448
449 return (PyObject *)ret;
450}
451
452static PyObject *_bpy_names(BPy_Library *self, int blocktype)
453{
454 int names_num;
456 self->blo_handle, blocktype, (self->flag & FILE_ASSETS_ONLY) != 0, &names_num);
457 PyObject *list = PyList_New(names_num);
458
459 if (names) {
460 int i = 0;
461 for (LinkNode *l = names; l; l = l->next, i++) {
462 PyList_SET_ITEM(list, i, PyUnicode_FromString((char *)l->link));
463 }
464 BLI_linklist_freeN(names); /* free linklist *and* each node's data */
465 }
466
467 return list;
468}
469
471{
472 ReportList *reports = &self->reports;
473 BlendFileReadReport *bf_reports = &self->bf_reports;
474
476 memset(bf_reports, 0, sizeof(*bf_reports));
477 bf_reports->reports = reports;
478
479 self->blo_handle = BLO_blendhandle_from_file(self->abspath, bf_reports);
480
481 if (self->blo_handle == nullptr) {
482 if (BPy_reports_to_error(reports, PyExc_IOError, true) != -1) {
483 PyErr_Format(PyExc_IOError, "load: %s failed to open blend file", self->abspath);
484 }
485 return nullptr;
486 }
487
488 PyObject *dict_src = _PyDict_NewPresized(bpy_library_dict_num);
489 PyObject *dict_dst = self->dict; /* Only for convenience (always `self->dict`). */
490 int dict_num_offset = 0;
491
492 int i = 0, code;
493 while ((code = BKE_idtype_idcode_iter_step(&i))) {
495 dict_num_offset += 1;
496 continue;
497 }
498 const char *name_plural = BKE_idtype_idcode_to_name_plural(code);
499 PyObject *str = PyUnicode_FromString(name_plural);
500 PyObject *item;
501
502 PyDict_SetItem(dict_dst, str, item = PyList_New(0));
503 Py_DECREF(item);
504 PyDict_SetItem(dict_src, str, item = _bpy_names(self, code));
505 Py_DECREF(item);
506
507 Py_DECREF(str);
508 }
509
510 /* Create a dummy. */
511 BPy_Library *self_src = PyObject_New(BPy_Library, &bpy_lib_Type);
512 STRNCPY(self_src->relpath, self->relpath);
513 STRNCPY(self_src->abspath, self->abspath);
514
515 /* Library blend-file version. */
516 {
517 PyObject *version;
518 PyObject *identifier = PyUnicode_FromString("version");
519 blender::int3 blendfile_version;
520
521 /* Source. */
522 blendfile_version = BLO_blendhandle_get_version(self->blo_handle);
523 version = PyC_Tuple_PackArray_I32(&blendfile_version[0], 3);
524 PyDict_SetItem(dict_src, identifier, version);
525 Py_DECREF(version);
526
527 /* Destination. */
528 blendfile_version = blender::int3(
530 version = PyC_Tuple_PackArray_I32(&blendfile_version[0], 3);
531 PyDict_SetItem(dict_dst, identifier, version);
532 Py_DECREF(version);
533
534 Py_DECREF(identifier);
535 }
536
537 self_src->blo_handle = nullptr;
538 self_src->flag = 0;
539 self_src->create_liboverrides = false;
541 self_src->dict = dict_src; /* owns the dict */
542
543 /* While it's not a bug if the sizes differ, the size is expected to match.
544 * Ensure `bpy_library_dict_num` gets updated when members are added. */
545 BLI_assert(PyDict_GET_SIZE(self_src->dict) + dict_num_offset == bpy_library_dict_num);
546 BLI_assert(PyDict_GET_SIZE(self->dict) + dict_num_offset == bpy_library_dict_num);
547 UNUSED_VARS_NDEBUG(dict_num_offset);
548
549 BKE_reports_clear(reports);
550
551 /* Return a pair. */
552 PyObject *ret = PyTuple_New(2);
553 PyTuple_SET_ITEMS(ret, (PyObject *)self_src, Py_NewRef((PyObject *)self));
554 return ret;
555}
556
558 const char *name_plural,
559 const char *idname)
560{
561 PyObject *exc, *val, *tb;
562 PyErr_Fetch(&exc, &val, &tb);
563 if (PyErr_WarnFormat(PyExc_UserWarning,
564 1,
565 "load: '%s' does not contain %s[\"%s\"]",
566 self->abspath,
567 name_plural,
568 idname))
569 {
570 /* Spurious errors can appear at shutdown */
571 if (PyErr_ExceptionMatches(PyExc_Warning)) {
572 PyErr_WriteUnraisable((PyObject *)self);
573 }
574 }
575 PyErr_Restore(exc, val, tb);
576}
577
578static void bpy_lib_exit_warn_type(BPy_Library *self, PyObject *item)
579{
580 PyObject *exc, *val, *tb;
581 PyErr_Fetch(&exc, &val, &tb);
582 if (PyErr_WarnFormat(PyExc_UserWarning,
583 1,
584 "load: '%s' expected a string type, not a %.200s",
585 self->abspath,
586 Py_TYPE(item)->tp_name))
587 {
588 /* Spurious errors can appear at shutdown */
589 if (PyErr_ExceptionMatches(PyExc_Warning)) {
590 PyErr_WriteUnraisable((PyObject *)self);
591 }
592 }
593 PyErr_Restore(exc, val, tb);
594}
595
602
606{
607 /* Since `bpy_lib_exit` loops over all ID types, all items in `lapp_context` end up being looped
608 * over for each ID type, so when it does not match the item can simply be skipped: it either has
609 * already been processed, or will be processed in a later loop. */
610 if (BKE_blendfile_link_append_context_item_idcode_get(lapp_context, item) != data.idcode) {
611 return true;
612 }
613
614 const int py_list_index = POINTER_AS_INT(
616 ID *new_id = BKE_blendfile_link_append_context_item_newid_get(lapp_context, item);
617 ID *liboverride_id = data.py_library->create_liboverrides ?
619 item) :
620 nullptr;
621
622 BLI_assert(py_list_index < data.py_list_size);
623
624 /* Fully invalid items (which got set to `Py_None` already in first loop of `bpy_lib_exit`)
625 * should never be accessed here, since their index should never be set to any item in
626 * `lapp_context`. */
627 PyObject *item_src = PyList_GET_ITEM(data.py_list, py_list_index);
628 BLI_assert(item_src != Py_None);
629
630 PyObject *py_item;
631 if (liboverride_id != nullptr) {
632 PointerRNA newid_ptr = RNA_id_pointer_create(liboverride_id);
633 py_item = pyrna_struct_CreatePyObject(&newid_ptr);
634 }
635 else if (new_id != nullptr) {
636 PointerRNA newid_ptr = RNA_id_pointer_create(new_id);
637 py_item = pyrna_struct_CreatePyObject(&newid_ptr);
638 }
639 else {
640 const char *item_idname = PyUnicode_AsUTF8(item_src);
641 const char *idcode_name_plural = BKE_idtype_idcode_to_name_plural(data.idcode);
642
643 bpy_lib_exit_warn_idname(data.py_library, idcode_name_plural, item_idname);
644
645 py_item = Py_NewRef(Py_None);
646 }
647
648 PyList_SET_ITEM(data.py_list, py_list_index, py_item);
649
650 Py_DECREF(item_src);
651
652 return true;
653}
654
655static PyObject *bpy_lib_exit(BPy_Library *self, PyObject * /*args*/)
656{
657 Main *bmain = self->bmain;
658 const bool do_pack = ((self->flag & BLO_LIBLINK_PACK) != 0);
659 const bool do_append = ((self->flag & FILE_LINK) == 0);
660 const bool create_liboverrides = self->create_liboverrides;
661 /* Code in #bpy_lib_load should have raised exception in case of incompatible parameter values.
662 */
663 BLI_assert(!do_append || !create_liboverrides);
664
666
667 /* here appending/linking starts */
668 const int id_tag_extra = self->bmain_is_temp ? int(ID_TAG_TEMP_MAIN) : 0;
669 LibraryLink_Params liblink_params;
670 BLO_library_link_params_init(&liblink_params, bmain, self->flag, id_tag_extra);
671
673 &liblink_params);
674 /* NOTE: Transfers the ownership of the `blo_handle` to the `lapp_context`. */
675 BKE_blendfile_link_append_context_library_add(lapp_context, self->abspath, self->blo_handle);
676 self->blo_handle = nullptr;
677
678 int idcode_step = 0;
679 short idcode;
680 while ((idcode = BKE_idtype_idcode_iter_step(&idcode_step))) {
681 if (!BKE_idtype_idcode_is_linkable(idcode) || (idcode == ID_WS && !do_append)) {
682 continue;
683 }
684
685 const char *name_plural = BKE_idtype_idcode_to_name_plural(idcode);
686 PyObject *ls = PyDict_GetItemString(self->dict, name_plural);
687 // printf("lib: %s\n", name_plural);
688 if (ls == nullptr || !PyList_Check(ls)) {
689 continue;
690 }
691
692 const Py_ssize_t size = PyList_GET_SIZE(ls);
693 if (size == 0) {
694 continue;
695 }
696
697 /* loop */
698 for (Py_ssize_t i = 0; i < size; i++) {
699 PyObject *item_src = PyList_GET_ITEM(ls, i);
700 const char *item_idname = PyUnicode_AsUTF8(item_src);
701
702 // printf(" %s\n", item_idname);
703
704 /* NOTE: index of item in py list is stored in userdata pointer, so that it can be found
705 * later on to replace the ID name by the actual ID pointer. */
706 if (item_idname != nullptr) {
708 lapp_context, item_idname, idcode, POINTER_FROM_INT(i));
710 }
711 else {
712 /* XXX, could complain about this */
713 bpy_lib_exit_warn_type(self, item_src);
714 PyErr_Clear();
715
716#ifdef USE_RNA_DATABLOCKS
717 /* We can replace the item immediately with `None`. */
718 PyObject *py_item = Py_NewRef(Py_None);
719 PyList_SET_ITEM(ls, i, py_item);
720 Py_DECREF(item_src);
721#endif
722 }
723 }
724 }
725
727
728 BKE_blendfile_link(lapp_context, nullptr);
729 if (do_pack) {
730 BKE_blendfile_link_pack(lapp_context, nullptr);
731 }
732 else if (do_append) {
733 BKE_blendfile_append(lapp_context, nullptr);
734 }
735 else if (create_liboverrides) {
736 BKE_blendfile_override(lapp_context, self->liboverride_flags, nullptr);
737 }
738
740
741/* If enabled, replace named items in given lists by the final matching new ID pointer. */
742#ifdef USE_RNA_DATABLOCKS
743 idcode_step = 0;
744 while ((idcode = BKE_idtype_idcode_iter_step(&idcode_step))) {
745 if (!BKE_idtype_idcode_is_linkable(idcode) || (idcode == ID_WS && !do_append)) {
746 continue;
747 }
748 const char *name_plural = BKE_idtype_idcode_to_name_plural(idcode);
749 PyObject *ls = PyDict_GetItemString(self->dict, name_plural);
750 // printf("lib: %s\n", name_plural);
751 if (ls == nullptr || !PyList_Check(ls)) {
752 continue;
753 }
754
755 const Py_ssize_t size = PyList_GET_SIZE(ls);
756 if (size == 0) {
757 continue;
758 }
759
760 /* Loop over linked items in `lapp_context` to find matching python one in the list, and
761 * replace them with proper ID pointer. */
763 iter_data.idcode = idcode;
764 iter_data.py_library = self;
765 iter_data.py_list = ls;
766 iter_data.py_list_size = size;
768 lapp_context,
769 [&iter_data](BlendfileLinkAppendContext *lapp_context,
770 BlendfileLinkAppendContextItem *item) -> bool {
771 return bpy_lib_exit_lapp_context_items_cb(lapp_context, item, iter_data);
772 },
774 }
775#endif // USE_RNA_DATABLOCKS
776
779
780 BKE_reports_free(&self->reports);
781
782 Py_RETURN_NONE;
783}
784
785static PyObject *bpy_lib_dir(BPy_Library *self)
786{
787 return PyDict_Keys(self->dict);
788}
789
790#ifdef __GNUC__
791# ifdef __clang__
792# pragma clang diagnostic push
793# pragma clang diagnostic ignored "-Wcast-function-type"
794# else
795# pragma GCC diagnostic push
796# pragma GCC diagnostic ignored "-Wcast-function-type"
797# endif
798#endif
799
801 "load",
802 (PyCFunction)bpy_lib_load,
803 METH_VARARGS | METH_KEYWORDS,
804 bpy_lib_load_doc,
805};
806
807#ifdef __GNUC__
808# ifdef __clang__
809# pragma clang diagnostic pop
810# else
811# pragma GCC diagnostic pop
812# endif
813#endif
814
816{
817 if (PyType_Ready(&bpy_lib_Type) < 0) {
818 return -1;
819 }
820
821 return 0;
822}
#define BLENDER_FILE_SUBVERSION
#define BLENDER_FILE_VERSION
Main * CTX_data_main(const bContext *C)
bool BKE_idtype_idcode_is_linkable(short idcode)
Definition idtype.cc:197
short BKE_idtype_idcode_iter_step(int *idtype_index)
Definition idtype.cc:373
const char * BKE_idtype_idcode_to_name_plural(short idcode)
Definition idtype.cc:171
void BKE_main_id_tag_all(Main *mainvar, int tag, bool value)
Definition lib_id.cc:1224
const char * BKE_main_blendfile_path(const Main *bmain) ATTR_NONNULL()
Definition main.cc:887
@ RPT_PRINT_HANDLED_BY_OWNER
Definition BKE_report.hh:60
@ RPT_STORE
Definition BKE_report.hh:56
void BKE_reports_free(ReportList *reports)
Definition report.cc:97
void BKE_reports_clear(ReportList *reports)
Definition report.cc:109
void BKE_reports_init(ReportList *reports, int flag)
Definition report.cc:82
#define BLI_assert(a)
Definition BLI_assert.h:46
bool BLI_path_abs(char path[FILE_MAX], const char *basepath) ATTR_NONNULL(1
#define FILE_MAX
int BLI_path_normalize(char *path) ATTR_NONNULL(1)
#define BLI_path_cmp
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:693
#define POINTER_FROM_INT(i)
#define UNUSED_VARS_NDEBUG(...)
#define POINTER_AS_INT(i)
external readfile function prototypes.
@ BLO_LIBLINK_APPEND_RECURSIVE
@ BLO_LIBLINK_APPEND_ASSET_DATA_CLEAR
@ BLO_LIBLINK_APPEND_SET_FAKEUSER
@ BLO_LIBLINK_PACK
@ BLO_LIBLINK_APPEND_LOCAL_ID_REUSE
void BLO_library_link_params_init(LibraryLink_Params *params, Main *bmain, int flag, int id_tag_extra)
Definition readfile.cc:5105
BlendHandle * BLO_blendhandle_from_file(const char *filepath, BlendFileReadReport *reports)
blender::int3 BLO_blendhandle_get_version(const BlendHandle *bh)
LinkNode * BLO_blendhandle_get_datablock_names(BlendHandle *bh, int ofblocktype, bool use_assets_only, int *r_tot_names)
@ ID_TAG_TEMP_MAIN
Definition DNA_ID.h:971
@ ID_TAG_PRE_EXISTING
Definition DNA_ID.h:926
#define INDEX_ID_MAX
Definition DNA_ID.h:1360
@ ID_WS
@ FILE_RELPATH
@ FILE_LINK
@ FILE_ASSETS_ONLY
BMesh const char void * data
ATTR_WARN_UNUSED_RESULT const BMLoop * l
short BPy_reports_to_error(ReportList *reports, PyObject *exception, const bool clear)
struct bContext * BPY_context_get()
PyObject * self
PyMethodDef BPY_library_load_method_def
static void bpy_lib_dealloc(BPy_Library *self)
static PyObject * bpy_lib_exit(BPy_Library *self, PyObject *args)
static PyMethodDef bpy_lib_methods[]
static uint32_t bool_flag_pair_as_flag(const BoolFlagPair *bool_flags, int bool_flags_num)
static PyObject * bpy_lib_enter(BPy_Library *self)
static PyObject * _bpy_names(BPy_Library *self, int blocktype)
static PyObject * bpy_lib_dir(BPy_Library *self)
static bool bpy_lib_exit_lapp_context_items_cb(BlendfileLinkAppendContext *lapp_context, BlendfileLinkAppendContextItem *item, LibExitLappContextItemsIterData &data)
static PyObject * bpy_lib_load(BPy_PropertyRNA *self, PyObject *args, PyObject *kw)
static constexpr Py_ssize_t bpy_library_dict_num
int BPY_library_load_type_ready()
static void bpy_lib_exit_warn_type(BPy_Library *self, PyObject *item)
static PyTypeObject bpy_lib_Type
static void bpy_lib_exit_warn_idname(BPy_Library *self, const char *name_plural, const char *idname)
PyDoc_STRVAR(bpy_lib_load_doc, ".. method:: load(" "filepath, " "*, " "link=False, " "relative=False, " "set_fake=False, " "recursive=False, " "reuse_local_id=False, " "assets_only=False, " "clear_asset_data=False, " "create_liboverrides=False, " "reuse_liboverrides=False, " "create_liboverrides_runtime=False)\n" "\n" " Returns a context manager which exposes 2 library objects on entering.\n" " Each object has attributes matching bpy.data which are lists of strings to be linked.\n" "\n" " :arg filepath: The path to a blend file.\n" " :type filepath: str | bytes\n" " :arg link: When False reference to the original file is lost.\n" " :type link: bool\n" " :arg pack: If True, and ``link`` is also True, pack linked data-blocks into the current " "blend-file.\n" " :type pack: bool\n" " :arg relative: When True the path is stored relative to the open blend file.\n" " :type relative: bool\n" " :arg set_fake: If True, set fake user on appended IDs.\n" " :type set_fake: bool\n" " :arg recursive: If True, also make indirect dependencies of appended libraries local.\n" " :type recursive: bool\n" " :arg reuse_local_id: If True," "try to re-use previously appended matching ID on new append.\n" " :type reuse_local_id: bool\n" " :arg assets_only: If True, only list data-blocks marked as assets.\n" " :type assets_only: bool\n" " :arg clear_asset_data: If True, " "clear the asset data on append (it is always kept for linked data).\n" " :type clear_asset_data: bool\n" " :arg create_liboverrides: If True and ``link`` is True, liboverrides will\n" " be created for linked data.\n" " :type create_liboverrides: bool\n" " :arg reuse_liboverrides: If True and ``create_liboverride`` is True,\n" " search for existing liboverride first.\n" " :type reuse_liboverrides: bool\n" " :arg create_liboverrides_runtime: If True and ``create_liboverride`` is True,\n" " create (or search for existing) runtime liboverride.\n" " :type create_liboverrides_runtime: bool\n")
PyObject * pyrna_struct_CreatePyObject(PointerRNA *ptr)
Definition bpy_rna.cc:8496
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
#define offsetof(t, d)
#define str(s)
VecBase< int32_t, 3 > int3
int PyC_ParseUnicodeAsBytesAndSize(PyObject *o, void *p)
PyObject * PyC_Tuple_PackArray_I32(const int *array, uint len)
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
PointerRNA RNA_id_pointer_create(ID *id)
eBKELibLinkOverride liboverride_flags
BlendHandle * blo_handle
char abspath[FILE_MAX]
BlendFileReadReport bf_reports
ReportList reports
PyObject_HEAD char relpath[FILE_MAX]
Definition DNA_ID.h:414
i
Definition text_draw.cc:230
uint8_t flag
Definition wm_window.cc:145