2023-08-16 00:20:26 +10:00
|
|
|
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
2023-05-31 16:19:06 +02:00
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
library loading api.
this is not well suited to RNA so this is a native python api.
This uses:
bpy.data.libraries.load(filepath, link=False, relative=False)
however the return value needs to use pythons context manager, this means the library loading is confined to a block of code and python cant leave a half loaded library state.
eg, load a single scene we know the name of:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = ["Scene"]
eg, load all scenes:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = data_from.scenes
eg, load all objects starting with 'A'
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.objects = [name for name in data_from.objects if name.startswith("A")]
As you can see gives 2 objects like 'bpy.data', but containing lists of strings which can be moved from one into another.
2011-03-12 16:06:37 +00:00
|
|
|
|
2019-02-18 08:08:12 +11:00
|
|
|
/** \file
|
|
|
|
|
* \ingroup pythonintern
|
2011-11-05 08:21:12 +00:00
|
|
|
*
|
|
|
|
|
* This file exposed blend file library appending/linking to python, typically
|
2025-05-17 09:18:03 +10:00
|
|
|
* this would be done via RNA API but in this case a hand written Python API
|
2019-06-21 09:50:23 +10:00
|
|
|
* allows us to use Python's context manager (`__enter__` and `__exit__`).
|
2011-11-05 08:21:12 +00:00
|
|
|
*
|
2019-06-21 09:50:23 +10:00
|
|
|
* Everything here is exposed via `bpy.data.libraries.load(...)` which returns
|
2011-11-05 08:40:07 +00:00
|
|
|
* a context manager.
|
library loading api.
this is not well suited to RNA so this is a native python api.
This uses:
bpy.data.libraries.load(filepath, link=False, relative=False)
however the return value needs to use pythons context manager, this means the library loading is confined to a block of code and python cant leave a half loaded library state.
eg, load a single scene we know the name of:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = ["Scene"]
eg, load all scenes:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = data_from.scenes
eg, load all objects starting with 'A'
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.objects = [name for name in data_from.objects if name.startswith("A")]
As you can see gives 2 objects like 'bpy.data', but containing lists of strings which can be moved from one into another.
2011-03-12 16:06:37 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <Python.h>
|
2023-07-21 16:05:33 +10:00
|
|
|
#include <cstddef>
|
library loading api.
this is not well suited to RNA so this is a native python api.
This uses:
bpy.data.libraries.load(filepath, link=False, relative=False)
however the return value needs to use pythons context manager, this means the library loading is confined to a block of code and python cant leave a half loaded library state.
eg, load a single scene we know the name of:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = ["Scene"]
eg, load all scenes:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = data_from.scenes
eg, load all objects starting with 'A'
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.objects = [name for name in data_from.objects if name.startswith("A")]
As you can see gives 2 objects like 'bpy.data', but containing lists of strings which can be moved from one into another.
2011-03-12 16:06:37 +00:00
|
|
|
|
2012-07-08 06:00:27 +00:00
|
|
|
#include "BLI_linklist.h"
|
2025-07-14 14:43:08 +02:00
|
|
|
#include "BLI_math_vector_types.hh"
|
2024-09-26 21:13:39 +10:00
|
|
|
#include "BLI_path_utils.hh"
|
2020-03-19 09:33:03 +01:00
|
|
|
#include "BLI_string.h"
|
|
|
|
|
#include "BLI_utildefines.h"
|
2012-07-08 06:00:27 +00:00
|
|
|
|
2025-07-14 14:43:08 +02:00
|
|
|
#include "BKE_blender_version.h"
|
2023-12-13 12:36:45 +01:00
|
|
|
#include "BKE_blendfile_link_append.hh"
|
2023-11-16 11:41:55 +01:00
|
|
|
#include "BKE_context.hh"
|
2024-01-20 19:17:36 +01:00
|
|
|
#include "BKE_idtype.hh"
|
2024-01-15 12:44:04 -05:00
|
|
|
#include "BKE_lib_id.hh"
|
2023-12-01 19:43:16 +01:00
|
|
|
#include "BKE_main.hh"
|
2024-02-10 18:34:29 +01:00
|
|
|
#include "BKE_report.hh"
|
library loading api.
this is not well suited to RNA so this is a native python api.
This uses:
bpy.data.libraries.load(filepath, link=False, relative=False)
however the return value needs to use pythons context manager, this means the library loading is confined to a block of code and python cant leave a half loaded library state.
eg, load a single scene we know the name of:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = ["Scene"]
eg, load all scenes:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = data_from.scenes
eg, load all objects starting with 'A'
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.objects = [name for name in data_from.objects if name.startswith("A")]
As you can see gives 2 objects like 'bpy.data', but containing lists of strings which can be moved from one into another.
2011-03-12 16:06:37 +00:00
|
|
|
|
|
|
|
|
#include "DNA_space_types.h" /* FILE_LINK, FILE_RELPATH */
|
|
|
|
|
|
2024-02-09 13:41:30 +01:00
|
|
|
#include "BLO_readfile.hh"
|
2017-11-14 17:00:10 +11:00
|
|
|
|
2024-09-24 15:25:36 +02:00
|
|
|
#include "bpy_capi_utils.hh"
|
2024-09-24 12:23:25 +02:00
|
|
|
#include "bpy_library.hh"
|
library loading api.
this is not well suited to RNA so this is a native python api.
This uses:
bpy.data.libraries.load(filepath, link=False, relative=False)
however the return value needs to use pythons context manager, this means the library loading is confined to a block of code and python cant leave a half loaded library state.
eg, load a single scene we know the name of:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = ["Scene"]
eg, load all scenes:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = data_from.scenes
eg, load all objects starting with 'A'
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.objects = [name for name in data_from.objects if name.startswith("A")]
As you can see gives 2 objects like 'bpy.data', but containing lists of strings which can be moved from one into another.
2011-03-12 16:06:37 +00:00
|
|
|
|
2024-09-24 15:25:36 +02:00
|
|
|
#include "../generic/py_capi_utils.hh"
|
2025-06-20 04:19:35 +00:00
|
|
|
#include "../generic/python_compat.hh" /* IWYU pragma: keep. */
|
2024-09-24 15:25:36 +02:00
|
|
|
#include "../generic/python_utildefines.hh"
|
2015-01-06 16:42:22 +11:00
|
|
|
|
2013-04-05 17:56:54 +00:00
|
|
|
/* nifty feature. swap out strings for RNA data */
|
|
|
|
|
#define USE_RNA_DATABLOCKS
|
|
|
|
|
|
2011-05-24 15:21:14 +00:00
|
|
|
#ifdef USE_RNA_DATABLOCKS
|
2023-08-10 22:40:27 +02:00
|
|
|
# include "RNA_access.hh"
|
2024-09-24 11:30:38 +02:00
|
|
|
# include "bpy_rna.hh"
|
2011-05-24 15:21:14 +00:00
|
|
|
#endif
|
|
|
|
|
|
2025-06-30 15:29:04 +10:00
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
|
/** \name Internal Utilities
|
|
|
|
|
* \{ */
|
|
|
|
|
|
|
|
|
|
struct BoolFlagPair {
|
|
|
|
|
bool value;
|
|
|
|
|
uint32_t flag;
|
|
|
|
|
};
|
|
|
|
|
static uint32_t bool_flag_pair_as_flag(const BoolFlagPair *bool_flags, int bool_flags_num)
|
|
|
|
|
{
|
|
|
|
|
uint32_t flag = 0;
|
|
|
|
|
for (int i = 0; i < bool_flags_num; i++) {
|
|
|
|
|
BLI_assert(bool_flags[i].flag);
|
|
|
|
|
if (bool_flags[i].value) {
|
|
|
|
|
flag |= bool_flags[i].flag;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return flag;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** \} */
|
|
|
|
|
|
2025-08-02 11:09:07 +10:00
|
|
|
/**
|
|
|
|
|
* The size to pre-allocate #BPy_Library::dict Add +1 for the "version".
|
|
|
|
|
*/
|
|
|
|
|
static constexpr Py_ssize_t bpy_library_dict_num = INDEX_ID_MAX + 1;
|
|
|
|
|
|
2023-07-21 02:18:59 +02:00
|
|
|
struct BPy_Library {
|
2025-08-02 11:09:07 +10:00
|
|
|
/** Required Python macro. */
|
|
|
|
|
PyObject_HEAD
|
|
|
|
|
|
|
|
|
|
/** The path supplied by the caller (may be relative). */
|
2021-06-24 17:10:22 +10:00
|
|
|
char relpath[FILE_MAX];
|
2025-08-02 11:09:07 +10:00
|
|
|
/** The absolute path. */
|
|
|
|
|
char abspath[FILE_MAX];
|
library loading api.
this is not well suited to RNA so this is a native python api.
This uses:
bpy.data.libraries.load(filepath, link=False, relative=False)
however the return value needs to use pythons context manager, this means the library loading is confined to a block of code and python cant leave a half loaded library state.
eg, load a single scene we know the name of:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = ["Scene"]
eg, load all scenes:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = data_from.scenes
eg, load all objects starting with 'A'
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.objects = [name for name in data_from.objects if name.startswith("A")]
As you can see gives 2 objects like 'bpy.data', but containing lists of strings which can be moved from one into another.
2011-03-12 16:06:37 +00:00
|
|
|
BlendHandle *blo_handle;
|
2025-08-02 11:09:07 +10:00
|
|
|
/** Referenced by `blo_handle`, so stored here to keep alive for long enough. */
|
2021-11-11 14:29:14 +01:00
|
|
|
ReportList reports;
|
2021-07-15 15:44:33 +02:00
|
|
|
BlendFileReadReport bf_reports;
|
2021-11-11 14:29:14 +01:00
|
|
|
|
library loading api.
this is not well suited to RNA so this is a native python api.
This uses:
bpy.data.libraries.load(filepath, link=False, relative=False)
however the return value needs to use pythons context manager, this means the library loading is confined to a block of code and python cant leave a half loaded library state.
eg, load a single scene we know the name of:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = ["Scene"]
eg, load all scenes:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = data_from.scenes
eg, load all objects starting with 'A'
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.objects = [name for name in data_from.objects if name.startswith("A")]
As you can see gives 2 objects like 'bpy.data', but containing lists of strings which can be moved from one into another.
2011-03-12 16:06:37 +00:00
|
|
|
int flag;
|
2023-02-14 15:17:53 +01:00
|
|
|
|
|
|
|
|
bool create_liboverrides;
|
|
|
|
|
eBKELibLinkOverride liboverride_flags;
|
|
|
|
|
|
2025-08-02 11:09:07 +10:00
|
|
|
/**
|
|
|
|
|
* A dictionary, accessed via attributes (so keys are strings).
|
|
|
|
|
* - Stores the ID types ("meshes", "objects", etc...).
|
|
|
|
|
* - Also has a "version" attribute to support accessing the blender version.
|
|
|
|
|
*
|
|
|
|
|
* Assigned a pre-sized dictionary using #BPY_LIBRARY_DICT_NUM_INIT item.
|
|
|
|
|
* this will always have these the ID names and some additional slots filled.
|
|
|
|
|
*/
|
library loading api.
this is not well suited to RNA so this is a native python api.
This uses:
bpy.data.libraries.load(filepath, link=False, relative=False)
however the return value needs to use pythons context manager, this means the library loading is confined to a block of code and python cant leave a half loaded library state.
eg, load a single scene we know the name of:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = ["Scene"]
eg, load all scenes:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = data_from.scenes
eg, load all objects starting with 'A'
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.objects = [name for name in data_from.objects if name.startswith("A")]
As you can see gives 2 objects like 'bpy.data', but containing lists of strings which can be moved from one into another.
2011-03-12 16:06:37 +00:00
|
|
|
PyObject *dict;
|
2025-08-02 11:09:07 +10:00
|
|
|
/**
|
|
|
|
|
* Borrowed reference to the `bmain`, taken from the RNA instance of #RNA_BlendDataLibraries.
|
|
|
|
|
* Defaults to #G.main, Otherwise use a temporary #Main when `bmain_is_temp` is true.
|
|
|
|
|
*/
|
2021-03-04 23:13:07 +11:00
|
|
|
Main *bmain;
|
2021-03-09 01:01:31 +11:00
|
|
|
bool bmain_is_temp;
|
2023-07-21 02:18:59 +02:00
|
|
|
};
|
library loading api.
this is not well suited to RNA so this is a native python api.
This uses:
bpy.data.libraries.load(filepath, link=False, relative=False)
however the return value needs to use pythons context manager, this means the library loading is confined to a block of code and python cant leave a half loaded library state.
eg, load a single scene we know the name of:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = ["Scene"]
eg, load all scenes:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = data_from.scenes
eg, load all objects starting with 'A'
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.objects = [name for name in data_from.objects if name.startswith("A")]
As you can see gives 2 objects like 'bpy.data', but containing lists of strings which can be moved from one into another.
2011-03-12 16:06:37 +00:00
|
|
|
|
2024-07-27 13:20:43 +10:00
|
|
|
static PyObject *bpy_lib_load(BPy_PropertyRNA *self, PyObject *args, PyObject *kw);
|
2017-08-20 19:04:16 +10:00
|
|
|
static PyObject *bpy_lib_enter(BPy_Library *self);
|
library loading api.
this is not well suited to RNA so this is a native python api.
This uses:
bpy.data.libraries.load(filepath, link=False, relative=False)
however the return value needs to use pythons context manager, this means the library loading is confined to a block of code and python cant leave a half loaded library state.
eg, load a single scene we know the name of:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = ["Scene"]
eg, load all scenes:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = data_from.scenes
eg, load all objects starting with 'A'
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.objects = [name for name in data_from.objects if name.startswith("A")]
As you can see gives 2 objects like 'bpy.data', but containing lists of strings which can be moved from one into another.
2011-03-12 16:06:37 +00:00
|
|
|
static PyObject *bpy_lib_exit(BPy_Library *self, PyObject *args);
|
2011-03-14 01:00:41 +00:00
|
|
|
static PyObject *bpy_lib_dir(BPy_Library *self);
|
library loading api.
this is not well suited to RNA so this is a native python api.
This uses:
bpy.data.libraries.load(filepath, link=False, relative=False)
however the return value needs to use pythons context manager, this means the library loading is confined to a block of code and python cant leave a half loaded library state.
eg, load a single scene we know the name of:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = ["Scene"]
eg, load all scenes:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = data_from.scenes
eg, load all objects starting with 'A'
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.objects = [name for name in data_from.objects if name.startswith("A")]
As you can see gives 2 objects like 'bpy.data', but containing lists of strings which can be moved from one into another.
2011-03-12 16:06:37 +00:00
|
|
|
|
2025-04-01 12:06:03 +11:00
|
|
|
#ifdef __GNUC__
|
|
|
|
|
# ifdef __clang__
|
|
|
|
|
# pragma clang diagnostic push
|
|
|
|
|
# pragma clang diagnostic ignored "-Wcast-function-type"
|
|
|
|
|
# else
|
|
|
|
|
# pragma GCC diagnostic push
|
|
|
|
|
# pragma GCC diagnostic ignored "-Wcast-function-type"
|
|
|
|
|
# endif
|
2023-07-21 13:42:35 +10:00
|
|
|
#endif
|
|
|
|
|
|
2011-12-26 12:26:11 +00:00
|
|
|
static PyMethodDef bpy_lib_methods[] = {
|
library loading api.
this is not well suited to RNA so this is a native python api.
This uses:
bpy.data.libraries.load(filepath, link=False, relative=False)
however the return value needs to use pythons context manager, this means the library loading is confined to a block of code and python cant leave a half loaded library state.
eg, load a single scene we know the name of:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = ["Scene"]
eg, load all scenes:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = data_from.scenes
eg, load all objects starting with 'A'
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.objects = [name for name in data_from.objects if name.startswith("A")]
As you can see gives 2 objects like 'bpy.data', but containing lists of strings which can be moved from one into another.
2011-03-12 16:06:37 +00:00
|
|
|
{"__enter__", (PyCFunction)bpy_lib_enter, METH_NOARGS},
|
2011-11-05 08:40:07 +00:00
|
|
|
{"__exit__", (PyCFunction)bpy_lib_exit, METH_VARARGS},
|
|
|
|
|
{"__dir__", (PyCFunction)bpy_lib_dir, METH_NOARGS},
|
2023-07-21 02:18:59 +02:00
|
|
|
{nullptr} /* sentinel */
|
library loading api.
this is not well suited to RNA so this is a native python api.
This uses:
bpy.data.libraries.load(filepath, link=False, relative=False)
however the return value needs to use pythons context manager, this means the library loading is confined to a block of code and python cant leave a half loaded library state.
eg, load a single scene we know the name of:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = ["Scene"]
eg, load all scenes:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = data_from.scenes
eg, load all objects starting with 'A'
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.objects = [name for name in data_from.objects if name.startswith("A")]
As you can see gives 2 objects like 'bpy.data', but containing lists of strings which can be moved from one into another.
2011-03-12 16:06:37 +00:00
|
|
|
};
|
|
|
|
|
|
2025-04-01 12:06:03 +11:00
|
|
|
#ifdef __GNUC__
|
|
|
|
|
# ifdef __clang__
|
|
|
|
|
# pragma clang diagnostic pop
|
|
|
|
|
# else
|
|
|
|
|
# pragma GCC diagnostic pop
|
|
|
|
|
# endif
|
2023-07-21 13:42:35 +10:00
|
|
|
#endif
|
|
|
|
|
|
library loading api.
this is not well suited to RNA so this is a native python api.
This uses:
bpy.data.libraries.load(filepath, link=False, relative=False)
however the return value needs to use pythons context manager, this means the library loading is confined to a block of code and python cant leave a half loaded library state.
eg, load a single scene we know the name of:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = ["Scene"]
eg, load all scenes:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = data_from.scenes
eg, load all objects starting with 'A'
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.objects = [name for name in data_from.objects if name.startswith("A")]
As you can see gives 2 objects like 'bpy.data', but containing lists of strings which can be moved from one into another.
2011-03-12 16:06:37 +00:00
|
|
|
static void bpy_lib_dealloc(BPy_Library *self)
|
|
|
|
|
{
|
|
|
|
|
Py_XDECREF(self->dict);
|
|
|
|
|
Py_TYPE(self)->tp_free(self);
|
|
|
|
|
}
|
|
|
|
|
|
2011-12-26 12:26:11 +00:00
|
|
|
static PyTypeObject bpy_lib_Type = {
|
2023-07-21 02:18:59 +02:00
|
|
|
/*ob_base*/ PyVarObject_HEAD_INIT(nullptr, 0)
|
2022-11-07 22:34:35 +11:00
|
|
|
/*tp_name*/ "bpy_lib",
|
|
|
|
|
/*tp_basicsize*/ sizeof(BPy_Library),
|
|
|
|
|
/*tp_itemsize*/ 0,
|
|
|
|
|
/*tp_dealloc*/ (destructor)bpy_lib_dealloc,
|
|
|
|
|
/*tp_vectorcall_offset*/ 0,
|
2023-07-21 02:18:59 +02:00
|
|
|
/*tp_getattr*/ nullptr,
|
|
|
|
|
/*tp_setattr*/ nullptr,
|
|
|
|
|
/*tp_as_async*/ nullptr,
|
|
|
|
|
/*tp_repr*/ nullptr,
|
|
|
|
|
/*tp_as_number*/ nullptr,
|
|
|
|
|
/*tp_as_sequence*/ nullptr,
|
|
|
|
|
/*tp_as_mapping*/ nullptr,
|
|
|
|
|
/*tp_hash*/ nullptr,
|
|
|
|
|
/*tp_call*/ nullptr,
|
|
|
|
|
/*tp_str*/ nullptr,
|
2022-11-07 22:34:35 +11:00
|
|
|
/*tp_getattro*/ PyObject_GenericGetAttr,
|
2023-07-21 02:18:59 +02:00
|
|
|
/*tp_setattro*/ nullptr,
|
|
|
|
|
/*tp_as_buffer*/ nullptr,
|
2022-11-07 22:34:35 +11:00
|
|
|
/*tp_flags*/ Py_TPFLAGS_DEFAULT,
|
2023-07-21 02:18:59 +02:00
|
|
|
/*tp_doc*/ nullptr,
|
|
|
|
|
/*tp_traverse*/ nullptr,
|
|
|
|
|
/*tp_clear*/ nullptr,
|
|
|
|
|
/*tp_richcompare*/ nullptr,
|
2022-11-07 22:34:35 +11:00
|
|
|
/*tp_weaklistoffset*/ 0,
|
2023-07-21 02:18:59 +02:00
|
|
|
/*tp_iter*/ nullptr,
|
|
|
|
|
/*tp_iternext*/ nullptr,
|
2022-11-07 22:34:35 +11:00
|
|
|
/*tp_methods*/ bpy_lib_methods,
|
2023-07-21 02:18:59 +02:00
|
|
|
/*tp_members*/ nullptr,
|
|
|
|
|
/*tp_getset*/ nullptr,
|
|
|
|
|
/*tp_base*/ nullptr,
|
|
|
|
|
/*tp_dict*/ nullptr,
|
|
|
|
|
/*tp_descr_get*/ nullptr,
|
|
|
|
|
/*tp_descr_set*/ nullptr,
|
2022-11-07 22:34:35 +11:00
|
|
|
/*tp_dictoffset*/ offsetof(BPy_Library, dict),
|
2023-07-21 02:18:59 +02:00
|
|
|
/*tp_init*/ nullptr,
|
|
|
|
|
/*tp_alloc*/ nullptr,
|
|
|
|
|
/*tp_new*/ nullptr,
|
|
|
|
|
/*tp_free*/ nullptr,
|
|
|
|
|
/*tp_is_gc*/ nullptr,
|
|
|
|
|
/*tp_bases*/ nullptr,
|
|
|
|
|
/*tp_mro*/ nullptr,
|
|
|
|
|
/*tp_cache*/ nullptr,
|
|
|
|
|
/*tp_subclasses*/ nullptr,
|
|
|
|
|
/*tp_weaklist*/ nullptr,
|
|
|
|
|
/*tp_del*/ nullptr,
|
2022-11-07 22:34:35 +11:00
|
|
|
/*tp_version_tag*/ 0,
|
2023-07-21 02:18:59 +02:00
|
|
|
/*tp_finalize*/ nullptr,
|
|
|
|
|
/*tp_vectorcall*/ nullptr,
|
library loading api.
this is not well suited to RNA so this is a native python api.
This uses:
bpy.data.libraries.load(filepath, link=False, relative=False)
however the return value needs to use pythons context manager, this means the library loading is confined to a block of code and python cant leave a half loaded library state.
eg, load a single scene we know the name of:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = ["Scene"]
eg, load all scenes:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = data_from.scenes
eg, load all objects starting with 'A'
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.objects = [name for name in data_from.objects if name.startswith("A")]
As you can see gives 2 objects like 'bpy.data', but containing lists of strings which can be moved from one into another.
2011-03-12 16:06:37 +00:00
|
|
|
};
|
|
|
|
|
|
2011-05-24 16:05:51 +00:00
|
|
|
PyDoc_STRVAR(
|
2024-01-25 10:22:16 +11:00
|
|
|
/* Wrap. */
|
2011-05-24 16:05:51 +00:00
|
|
|
bpy_lib_load_doc,
|
2023-03-03 16:24:14 +11:00
|
|
|
".. method:: load("
|
|
|
|
|
"filepath, "
|
|
|
|
|
"link=False, "
|
|
|
|
|
"relative=False, "
|
2025-06-30 15:29:04 +10:00
|
|
|
"set_fake=False, "
|
|
|
|
|
"recursive=False, "
|
|
|
|
|
"reuse_local_id=False, "
|
2023-03-03 16:24:14 +11:00
|
|
|
"assets_only=False, "
|
2025-06-30 15:29:04 +10:00
|
|
|
"clear_asset_data=False, "
|
2023-03-03 16:24:14 +11:00
|
|
|
"create_liboverrides=False, "
|
|
|
|
|
"reuse_liboverrides=False, "
|
|
|
|
|
"create_liboverrides_runtime=False)\n"
|
2012-06-04 20:11:09 +00:00
|
|
|
"\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"
|
2024-11-03 15:42:19 +11:00
|
|
|
" :type filepath: str | bytes\n"
|
2012-06-04 20:11:09 +00:00
|
|
|
" :arg link: When False reference to the original file is lost.\n"
|
|
|
|
|
" :type link: bool\n"
|
|
|
|
|
" :arg relative: When True the path is stored relative to the open blend file.\n"
|
2021-02-04 13:04:54 +11:00
|
|
|
" :type relative: bool\n"
|
2025-06-30 15:29:04 +10:00
|
|
|
" :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"
|
2021-02-04 13:04:54 +11:00
|
|
|
" :arg assets_only: If True, only list data-blocks marked as assets.\n"
|
2023-02-14 15:17:53 +01:00
|
|
|
" :type assets_only: bool\n"
|
2025-06-30 15:29:04 +10:00
|
|
|
" :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"
|
2023-02-14 15:17:53 +01:00
|
|
|
" :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");
|
2021-03-04 23:13:07 +11:00
|
|
|
static PyObject *bpy_lib_load(BPy_PropertyRNA *self, PyObject *args, PyObject *kw)
|
library loading api.
this is not well suited to RNA so this is a native python api.
This uses:
bpy.data.libraries.load(filepath, link=False, relative=False)
however the return value needs to use pythons context manager, this means the library loading is confined to a block of code and python cant leave a half loaded library state.
eg, load a single scene we know the name of:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = ["Scene"]
eg, load all scenes:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = data_from.scenes
eg, load all objects starting with 'A'
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.objects = [name for name in data_from.objects if name.startswith("A")]
As you can see gives 2 objects like 'bpy.data', but containing lists of strings which can be moved from one into another.
2011-03-12 16:06:37 +00:00
|
|
|
{
|
2021-03-09 01:01:31 +11:00
|
|
|
Main *bmain_base = CTX_data_main(BPY_context_get());
|
RNA: Make the `PointerRNA` struct non-trivial.
For now, PointerRNA is made non-trivial by giving explicit default
values to its members.
Besides of BPY python binding code, the change is relatively trivial.
The main change (besides the creation/deletion part) is the replacement
of `memset` by zero-initialized assignment (using `{}`).
makesrna required changes are quite small too.
The big piece of this PR is the refactor of the BPY RNA code.
It essentially brings back allocation and deletion of the BPy_StructRNA,
BPy_Pointer etc. python objects into 'cannonical process', using `__new__`,
and `__init__` callbacks (and there matching CAPI functions).
Existing code was doing very low-level manipulations to create these
data, which is not really easy to understand, and AFAICT incompatible
with handling C++ data that needs to be constructed and destructed.
Unfortunately, similar change in destruction code (using `__del__` and
matching `tp_finalize` CAPI callback) is not possible, because of technical
low-level implementation details in CPython (see [1] for details).
`std::optional` pointer management is used to encapsulate PointerRNA
data. This allows to keep control on _when_ actual RNA creation is done,
and to have a safe destruction in `tp_dealloc` callbacks.
Note that a critical change in Blender's Python API will be that classes
inherinting from `bpy_struct` etc. will now have to properly call the
base class `__new__` and/or `__init__`if they define them.
Implements #122431.
[1] https://discuss.python.org/t/cpython-usage-of-tp-finalize-in-c-defined-static-types-with-no-custom-tp-dealloc/64100
2024-10-30 15:08:37 +01:00
|
|
|
Main *bmain = static_cast<Main *>(self->ptr->data); /* Typically #G_MAIN */
|
library loading api.
this is not well suited to RNA so this is a native python api.
This uses:
bpy.data.libraries.load(filepath, link=False, relative=False)
however the return value needs to use pythons context manager, this means the library loading is confined to a block of code and python cant leave a half loaded library state.
eg, load a single scene we know the name of:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = ["Scene"]
eg, load all scenes:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = data_from.scenes
eg, load all objects starting with 'A'
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.objects = [name for name in data_from.objects if name.startswith("A")]
As you can see gives 2 objects like 'bpy.data', but containing lists of strings which can be moved from one into another.
2011-03-12 16:06:37 +00:00
|
|
|
BPy_Library *ret;
|
2023-08-11 14:59:55 +10:00
|
|
|
PyC_UnicodeAsBytesAndSize_Data filepath_data = {nullptr};
|
2025-06-30 15:29:04 +10:00
|
|
|
|
|
|
|
|
/* #BPy_Library::flag
|
|
|
|
|
*
|
|
|
|
|
* - #BLO_LIBLINK_OBDATA_INSTANCE: The caller must manage instancing.
|
|
|
|
|
* - #BLO_LIBLINK_COLLECTION_INSTANCE: The caller must manage instancing.
|
|
|
|
|
*/
|
|
|
|
|
struct {
|
|
|
|
|
BoolFlagPair is_link = {false, FILE_LINK};
|
|
|
|
|
BoolFlagPair is_relative = {false, FILE_RELPATH};
|
|
|
|
|
BoolFlagPair set_fake = {false, BLO_LIBLINK_APPEND_SET_FAKEUSER};
|
|
|
|
|
BoolFlagPair recursive = {false, BLO_LIBLINK_APPEND_RECURSIVE};
|
|
|
|
|
BoolFlagPair reuse_local_id = {false, BLO_LIBLINK_APPEND_LOCAL_ID_REUSE};
|
|
|
|
|
BoolFlagPair assets_only = {false, FILE_ASSETS_ONLY};
|
|
|
|
|
BoolFlagPair clear_asset_data = {false, BLO_LIBLINK_APPEND_ASSET_DATA_CLEAR};
|
|
|
|
|
} flag_vars;
|
|
|
|
|
|
|
|
|
|
bool create_liboverrides = false;
|
|
|
|
|
|
|
|
|
|
/* #BPy_Library::liboverride_flags */
|
|
|
|
|
struct {
|
|
|
|
|
BoolFlagPair reuse_liboverrides = {false, BKE_LIBLINK_OVERRIDE_USE_EXISTING_LIBOVERRIDES};
|
|
|
|
|
BoolFlagPair create_liboverrides_runtime = {false, BKE_LIBLINK_OVERRIDE_CREATE_RUNTIME};
|
|
|
|
|
} liboverride_flag_vars;
|
2023-02-14 15:17:53 +01:00
|
|
|
|
|
|
|
|
static const char *_keywords[] = {
|
|
|
|
|
"filepath",
|
|
|
|
|
"link",
|
|
|
|
|
"relative",
|
2025-06-30 15:29:04 +10:00
|
|
|
"set_fake",
|
|
|
|
|
"recursive",
|
|
|
|
|
"reuse_local_id",
|
2023-02-14 15:17:53 +01:00
|
|
|
"assets_only",
|
2025-06-30 15:29:04 +10:00
|
|
|
"clear_asset_data",
|
2023-02-14 15:17:53 +01:00
|
|
|
"create_liboverrides",
|
|
|
|
|
"reuse_liboverrides",
|
|
|
|
|
"create_liboverrides_runtime",
|
2023-07-21 02:18:59 +02:00
|
|
|
nullptr,
|
2023-02-14 15:17:53 +01:00
|
|
|
};
|
2022-04-08 09:41:28 +10:00
|
|
|
static _PyArg_Parser _parser = {
|
2023-08-30 14:05:32 +10:00
|
|
|
PY_ARG_PARSER_HEAD_COMPAT()
|
2023-08-11 14:59:55 +10:00
|
|
|
"O&" /* `filepath` */
|
2022-04-08 09:41:28 +10:00
|
|
|
/* Optional keyword only arguments. */
|
|
|
|
|
"|$"
|
|
|
|
|
"O&" /* `link` */
|
|
|
|
|
"O&" /* `relative` */
|
2025-06-30 15:29:04 +10:00
|
|
|
"O&" /* `recursive` */
|
|
|
|
|
"O&" /* `set_fake` */
|
|
|
|
|
"O&" /* `reuse_local_id` */
|
2022-04-08 09:41:28 +10:00
|
|
|
"O&" /* `assets_only` */
|
2025-06-30 15:29:04 +10:00
|
|
|
"O&" /* `clear_asset_data` */
|
2023-02-14 15:17:53 +01:00
|
|
|
"O&" /* `create_liboverrides` */
|
|
|
|
|
"O&" /* `reuse_liboverrides` */
|
|
|
|
|
"O&" /* `create_liboverrides_runtime` */
|
2022-04-08 09:41:28 +10:00
|
|
|
":load",
|
|
|
|
|
_keywords,
|
2023-08-03 19:14:53 +10:00
|
|
|
nullptr,
|
2022-04-08 09:41:28 +10:00
|
|
|
};
|
2021-01-28 18:04:10 +01:00
|
|
|
if (!_PyArg_ParseTupleAndKeywordsFast(args,
|
|
|
|
|
kw,
|
|
|
|
|
&_parser,
|
2023-08-11 14:59:55 +10:00
|
|
|
PyC_ParseUnicodeAsBytesAndSize,
|
|
|
|
|
&filepath_data,
|
2021-01-28 18:04:10 +01:00
|
|
|
PyC_ParseBool,
|
2025-06-30 15:29:04 +10:00
|
|
|
&flag_vars.is_link,
|
2021-01-28 18:04:10 +01:00
|
|
|
PyC_ParseBool,
|
2025-06-30 15:29:04 +10:00
|
|
|
&flag_vars.is_relative,
|
2021-01-28 18:04:10 +01:00
|
|
|
PyC_ParseBool,
|
2025-06-30 15:29:04 +10:00
|
|
|
&flag_vars.recursive,
|
|
|
|
|
PyC_ParseBool,
|
|
|
|
|
&flag_vars.set_fake,
|
|
|
|
|
PyC_ParseBool,
|
|
|
|
|
&flag_vars.reuse_local_id,
|
|
|
|
|
PyC_ParseBool,
|
|
|
|
|
&flag_vars.assets_only,
|
|
|
|
|
PyC_ParseBool,
|
|
|
|
|
&flag_vars.clear_asset_data,
|
2023-02-14 15:17:53 +01:00
|
|
|
PyC_ParseBool,
|
|
|
|
|
&create_liboverrides,
|
|
|
|
|
PyC_ParseBool,
|
2025-06-30 15:29:04 +10:00
|
|
|
&liboverride_flag_vars.reuse_liboverrides,
|
2023-02-14 15:17:53 +01:00
|
|
|
PyC_ParseBool,
|
2025-06-30 15:29:04 +10:00
|
|
|
&liboverride_flag_vars.create_liboverrides_runtime))
|
2023-02-14 15:17:53 +01:00
|
|
|
{
|
2023-07-21 02:18:59 +02:00
|
|
|
return nullptr;
|
2023-02-14 15:17:53 +01:00
|
|
|
}
|
|
|
|
|
|
2025-06-25 13:37:52 +00:00
|
|
|
const char *blendfile_path = BKE_main_blendfile_path(bmain);
|
|
|
|
|
char filepath_rel[FILE_MAX];
|
|
|
|
|
char filepath_abs[FILE_MAX];
|
|
|
|
|
|
|
|
|
|
STRNCPY(filepath_rel, filepath_data.value);
|
|
|
|
|
STRNCPY(filepath_abs, filepath_rel);
|
|
|
|
|
BLI_path_abs(filepath_abs, blendfile_path);
|
|
|
|
|
Py_XDECREF(filepath_data.value_coerce);
|
|
|
|
|
|
|
|
|
|
if (blendfile_path[0]) {
|
|
|
|
|
/* NOTE: intentionally leave `filepath_abs` and only use normalizing for comparison.
|
|
|
|
|
* It's important that this comparison matches read-files logic for matching paths.
|
|
|
|
|
* See the logic inside #BKE_blendfile_link.
|
|
|
|
|
*
|
|
|
|
|
* This means it's not necessary to check if the paths are *actually* the same.
|
|
|
|
|
* It's possible to load from this file if a user makes a symbolic-link - for example.
|
|
|
|
|
* See #140929. */
|
|
|
|
|
char filepath_abs_normalized[FILE_MAX];
|
|
|
|
|
STRNCPY(filepath_abs_normalized, filepath_abs);
|
|
|
|
|
BLI_path_normalize(filepath_abs_normalized);
|
|
|
|
|
if (BLI_path_cmp(filepath_abs_normalized, blendfile_path) == 0) {
|
|
|
|
|
PyErr_SetString(PyExc_ValueError, "Cannot load from the current blend file.");
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-30 15:29:04 +10:00
|
|
|
if (flag_vars.is_link.value) {
|
|
|
|
|
/* Link. */
|
|
|
|
|
if (flag_vars.set_fake.value) {
|
|
|
|
|
PyErr_SetString(PyExc_ValueError, "`link` must be False if `set_fake` is True");
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
if (flag_vars.recursive.value) {
|
|
|
|
|
PyErr_SetString(PyExc_ValueError, "`link` must be False if `recursive` is True");
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
if (flag_vars.reuse_local_id.value) {
|
|
|
|
|
PyErr_SetString(PyExc_ValueError, "`link` must be False if `reuse_local_id` is True");
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
if (flag_vars.clear_asset_data.value) {
|
|
|
|
|
PyErr_SetString(PyExc_ValueError, "`link` must be False if `clear_asset_data` is True");
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
2023-02-14 15:17:53 +01:00
|
|
|
}
|
2025-06-30 15:29:04 +10:00
|
|
|
else {
|
|
|
|
|
/* Append. */
|
|
|
|
|
if (create_liboverrides) {
|
|
|
|
|
PyErr_SetString(PyExc_ValueError, "`link` is False but `create_liboverrides` is True");
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
2023-02-14 15:17:53 +01:00
|
|
|
}
|
2025-06-30 15:29:04 +10:00
|
|
|
|
|
|
|
|
if (create_liboverrides) {
|
|
|
|
|
/* Library overrides. */
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* Library overrides (disabled). */
|
|
|
|
|
if (liboverride_flag_vars.reuse_liboverrides.value) {
|
|
|
|
|
PyErr_SetString(PyExc_ValueError,
|
|
|
|
|
"`create_liboverrides` is False but `reuse_liboverrides` is True");
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
if (liboverride_flag_vars.create_liboverrides_runtime.value) {
|
|
|
|
|
PyErr_SetString(PyExc_ValueError,
|
|
|
|
|
"`create_liboverrides` is False but `create_liboverrides_runtime` is True");
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
2015-08-04 18:34:20 +10:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2011-12-26 12:26:11 +00:00
|
|
|
ret = PyObject_New(BPy_Library, &bpy_lib_Type);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2025-06-25 13:37:52 +00:00
|
|
|
STRNCPY(ret->relpath, filepath_rel);
|
|
|
|
|
STRNCPY(ret->abspath, filepath_abs);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2021-03-04 23:13:07 +11:00
|
|
|
ret->bmain = bmain;
|
2021-03-09 01:01:31 +11:00
|
|
|
ret->bmain_is_temp = (bmain != bmain_base);
|
2021-03-04 23:13:07 +11:00
|
|
|
|
2023-07-21 02:18:59 +02:00
|
|
|
ret->blo_handle = nullptr;
|
2025-06-30 15:29:04 +10:00
|
|
|
|
|
|
|
|
ret->flag = bool_flag_pair_as_flag(reinterpret_cast<const BoolFlagPair *>(&flag_vars),
|
|
|
|
|
sizeof(flag_vars) / sizeof(BoolFlagPair));
|
|
|
|
|
|
2023-02-14 15:17:53 +01:00
|
|
|
ret->create_liboverrides = create_liboverrides;
|
2025-06-30 15:29:04 +10:00
|
|
|
ret->liboverride_flags = create_liboverrides ?
|
|
|
|
|
eBKELibLinkOverride(bool_flag_pair_as_flag(
|
|
|
|
|
reinterpret_cast<const BoolFlagPair *>(&liboverride_flag_vars),
|
|
|
|
|
sizeof(liboverride_flag_vars) / sizeof(BoolFlagPair))) :
|
|
|
|
|
eBKELibLinkOverride(0);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2025-08-02 11:09:07 +10:00
|
|
|
ret->dict = _PyDict_NewPresized(bpy_library_dict_num);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
library loading api.
this is not well suited to RNA so this is a native python api.
This uses:
bpy.data.libraries.load(filepath, link=False, relative=False)
however the return value needs to use pythons context manager, this means the library loading is confined to a block of code and python cant leave a half loaded library state.
eg, load a single scene we know the name of:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = ["Scene"]
eg, load all scenes:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = data_from.scenes
eg, load all objects starting with 'A'
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.objects = [name for name in data_from.objects if name.startswith("A")]
As you can see gives 2 objects like 'bpy.data', but containing lists of strings which can be moved from one into another.
2011-03-12 16:06:37 +00:00
|
|
|
return (PyObject *)ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static PyObject *_bpy_names(BPy_Library *self, int blocktype)
|
|
|
|
|
{
|
2025-08-02 02:10:59 +00:00
|
|
|
int names_num;
|
|
|
|
|
LinkNode *names = BLO_blendhandle_get_datablock_names(
|
|
|
|
|
self->blo_handle, blocktype, (self->flag & FILE_ASSETS_ONLY) != 0, &names_num);
|
|
|
|
|
PyObject *list = PyList_New(names_num);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2011-10-13 01:29:08 +00:00
|
|
|
if (names) {
|
2025-08-02 02:10:59 +00:00
|
|
|
int i = 0;
|
|
|
|
|
for (LinkNode *l = names; l; l = l->next, i++) {
|
|
|
|
|
PyList_SET_ITEM(list, i, PyUnicode_FromString((char *)l->link));
|
library loading api.
this is not well suited to RNA so this is a native python api.
This uses:
bpy.data.libraries.load(filepath, link=False, relative=False)
however the return value needs to use pythons context manager, this means the library loading is confined to a block of code and python cant leave a half loaded library state.
eg, load a single scene we know the name of:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = ["Scene"]
eg, load all scenes:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = data_from.scenes
eg, load all objects starting with 'A'
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.objects = [name for name in data_from.objects if name.startswith("A")]
As you can see gives 2 objects like 'bpy.data', but containing lists of strings which can be moved from one into another.
2011-03-12 16:06:37 +00:00
|
|
|
}
|
2020-12-08 15:00:31 +01:00
|
|
|
BLI_linklist_freeN(names); /* free linklist *and* each node's data */
|
library loading api.
this is not well suited to RNA so this is a native python api.
This uses:
bpy.data.libraries.load(filepath, link=False, relative=False)
however the return value needs to use pythons context manager, this means the library loading is confined to a block of code and python cant leave a half loaded library state.
eg, load a single scene we know the name of:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = ["Scene"]
eg, load all scenes:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = data_from.scenes
eg, load all objects starting with 'A'
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.objects = [name for name in data_from.objects if name.startswith("A")]
As you can see gives 2 objects like 'bpy.data', but containing lists of strings which can be moved from one into another.
2011-03-12 16:06:37 +00:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
library loading api.
this is not well suited to RNA so this is a native python api.
This uses:
bpy.data.libraries.load(filepath, link=False, relative=False)
however the return value needs to use pythons context manager, this means the library loading is confined to a block of code and python cant leave a half loaded library state.
eg, load a single scene we know the name of:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = ["Scene"]
eg, load all scenes:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = data_from.scenes
eg, load all objects starting with 'A'
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.objects = [name for name in data_from.objects if name.startswith("A")]
As you can see gives 2 objects like 'bpy.data', but containing lists of strings which can be moved from one into another.
2011-03-12 16:06:37 +00:00
|
|
|
return list;
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-20 19:04:16 +10:00
|
|
|
static PyObject *bpy_lib_enter(BPy_Library *self)
|
library loading api.
this is not well suited to RNA so this is a native python api.
This uses:
bpy.data.libraries.load(filepath, link=False, relative=False)
however the return value needs to use pythons context manager, this means the library loading is confined to a block of code and python cant leave a half loaded library state.
eg, load a single scene we know the name of:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = ["Scene"]
eg, load all scenes:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = data_from.scenes
eg, load all objects starting with 'A'
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.objects = [name for name in data_from.objects if name.startswith("A")]
As you can see gives 2 objects like 'bpy.data', but containing lists of strings which can be moved from one into another.
2011-03-12 16:06:37 +00:00
|
|
|
{
|
2021-11-11 14:29:14 +01:00
|
|
|
ReportList *reports = &self->reports;
|
|
|
|
|
BlendFileReadReport *bf_reports = &self->bf_reports;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2021-11-11 14:29:14 +01:00
|
|
|
BKE_reports_init(reports, RPT_STORE);
|
|
|
|
|
memset(bf_reports, 0, sizeof(*bf_reports));
|
|
|
|
|
bf_reports->reports = reports;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2021-11-11 14:29:14 +01:00
|
|
|
self->blo_handle = BLO_blendhandle_from_file(self->abspath, bf_reports);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2023-07-21 02:18:59 +02:00
|
|
|
if (self->blo_handle == nullptr) {
|
2021-11-11 14:29:14 +01:00
|
|
|
if (BPy_reports_to_error(reports, PyExc_IOError, true) != -1) {
|
2011-04-30 13:58:31 +00:00
|
|
|
PyErr_Format(PyExc_IOError, "load: %s failed to open blend file", self->abspath);
|
library loading api.
this is not well suited to RNA so this is a native python api.
This uses:
bpy.data.libraries.load(filepath, link=False, relative=False)
however the return value needs to use pythons context manager, this means the library loading is confined to a block of code and python cant leave a half loaded library state.
eg, load a single scene we know the name of:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = ["Scene"]
eg, load all scenes:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = data_from.scenes
eg, load all objects starting with 'A'
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.objects = [name for name in data_from.objects if name.startswith("A")]
As you can see gives 2 objects like 'bpy.data', but containing lists of strings which can be moved from one into another.
2011-03-12 16:06:37 +00:00
|
|
|
}
|
2023-07-21 02:18:59 +02:00
|
|
|
return nullptr;
|
library loading api.
this is not well suited to RNA so this is a native python api.
This uses:
bpy.data.libraries.load(filepath, link=False, relative=False)
however the return value needs to use pythons context manager, this means the library loading is confined to a block of code and python cant leave a half loaded library state.
eg, load a single scene we know the name of:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = ["Scene"]
eg, load all scenes:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = data_from.scenes
eg, load all objects starting with 'A'
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.objects = [name for name in data_from.objects if name.startswith("A")]
As you can see gives 2 objects like 'bpy.data', but containing lists of strings which can be moved from one into another.
2011-03-12 16:06:37 +00:00
|
|
|
}
|
2020-08-07 12:41:06 +02:00
|
|
|
|
2025-08-02 02:10:59 +00:00
|
|
|
PyObject *dict_src = _PyDict_NewPresized(bpy_library_dict_num);
|
|
|
|
|
PyObject *dict_dst = self->dict; /* Only for convenience (always `self->dict`). */
|
2025-08-02 11:09:07 +10:00
|
|
|
int dict_num_offset = 0;
|
2025-08-01 13:40:56 +10:00
|
|
|
|
2020-08-07 12:41:06 +02:00
|
|
|
int i = 0, code;
|
|
|
|
|
while ((code = BKE_idtype_idcode_iter_step(&i))) {
|
2025-08-02 11:09:07 +10:00
|
|
|
if (!BKE_idtype_idcode_is_linkable(code)) {
|
|
|
|
|
dict_num_offset += 1;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
const char *name_plural = BKE_idtype_idcode_to_name_plural(code);
|
|
|
|
|
PyObject *str = PyUnicode_FromString(name_plural);
|
|
|
|
|
PyObject *item;
|
2020-08-07 12:41:06 +02:00
|
|
|
|
2025-08-02 02:10:59 +00:00
|
|
|
PyDict_SetItem(dict_dst, str, item = PyList_New(0));
|
2025-08-02 11:09:07 +10:00
|
|
|
Py_DECREF(item);
|
2025-08-02 02:10:59 +00:00
|
|
|
PyDict_SetItem(dict_src, str, item = _bpy_names(self, code));
|
2025-08-02 11:09:07 +10:00
|
|
|
Py_DECREF(item);
|
2020-08-07 12:41:06 +02:00
|
|
|
|
2025-08-02 11:09:07 +10:00
|
|
|
Py_DECREF(str);
|
library loading api.
this is not well suited to RNA so this is a native python api.
This uses:
bpy.data.libraries.load(filepath, link=False, relative=False)
however the return value needs to use pythons context manager, this means the library loading is confined to a block of code and python cant leave a half loaded library state.
eg, load a single scene we know the name of:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = ["Scene"]
eg, load all scenes:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = data_from.scenes
eg, load all objects starting with 'A'
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.objects = [name for name in data_from.objects if name.startswith("A")]
As you can see gives 2 objects like 'bpy.data', but containing lists of strings which can be moved from one into another.
2011-03-12 16:06:37 +00:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2025-08-02 02:10:59 +00:00
|
|
|
/* Create a dummy. */
|
|
|
|
|
BPy_Library *self_src = PyObject_New(BPy_Library, &bpy_lib_Type);
|
|
|
|
|
STRNCPY(self_src->relpath, self->relpath);
|
|
|
|
|
STRNCPY(self_src->abspath, self->abspath);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2025-08-02 02:10:59 +00:00
|
|
|
/* Library blend-file version. */
|
2025-07-14 14:43:08 +02:00
|
|
|
{
|
|
|
|
|
PyObject *version;
|
|
|
|
|
PyObject *identifier = PyUnicode_FromString("version");
|
|
|
|
|
blender::int3 blendfile_version;
|
|
|
|
|
|
2025-08-02 02:10:59 +00:00
|
|
|
/* Source. */
|
2025-07-14 14:43:08 +02:00
|
|
|
blendfile_version = BLO_blendhandle_get_version(self->blo_handle);
|
|
|
|
|
version = PyC_Tuple_PackArray_I32(&blendfile_version[0], 3);
|
2025-08-02 02:10:59 +00:00
|
|
|
PyDict_SetItem(dict_src, identifier, version);
|
2025-07-14 14:43:08 +02:00
|
|
|
Py_DECREF(version);
|
|
|
|
|
|
2025-08-02 02:10:59 +00:00
|
|
|
/* Destination. */
|
2025-07-14 14:43:08 +02:00
|
|
|
blendfile_version = blender::int3(
|
|
|
|
|
BLENDER_FILE_VERSION / 100, BLENDER_FILE_VERSION % 100, BLENDER_FILE_SUBVERSION);
|
|
|
|
|
version = PyC_Tuple_PackArray_I32(&blendfile_version[0], 3);
|
2025-08-02 02:10:59 +00:00
|
|
|
PyDict_SetItem(dict_dst, identifier, version);
|
2025-07-14 14:43:08 +02:00
|
|
|
Py_DECREF(version);
|
|
|
|
|
|
|
|
|
|
Py_DECREF(identifier);
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-02 02:10:59 +00:00
|
|
|
self_src->blo_handle = nullptr;
|
|
|
|
|
self_src->flag = 0;
|
|
|
|
|
self_src->create_liboverrides = false;
|
|
|
|
|
self_src->liboverride_flags = BKE_LIBLINK_OVERRIDE_INIT;
|
|
|
|
|
self_src->dict = dict_src; /* owns the dict */
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2025-08-02 11:09:07 +10:00
|
|
|
/* While it's not a bug if the sizes differ, the size is expected to match.
|
|
|
|
|
* Ensure `bpy_library_dict_num` gets updated when members are added. */
|
2025-08-02 02:10:59 +00:00
|
|
|
BLI_assert(PyDict_GET_SIZE(self_src->dict) + dict_num_offset == bpy_library_dict_num);
|
2025-08-02 11:09:07 +10:00
|
|
|
BLI_assert(PyDict_GET_SIZE(self->dict) + dict_num_offset == bpy_library_dict_num);
|
|
|
|
|
UNUSED_VARS_NDEBUG(dict_num_offset);
|
|
|
|
|
|
2021-11-11 14:29:14 +01:00
|
|
|
BKE_reports_clear(reports);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2025-08-02 02:10:59 +00:00
|
|
|
/* Return a pair. */
|
|
|
|
|
PyObject *ret = PyTuple_New(2);
|
|
|
|
|
PyTuple_SET_ITEMS(ret, (PyObject *)self_src, Py_NewRef((PyObject *)self));
|
library loading api.
this is not well suited to RNA so this is a native python api.
This uses:
bpy.data.libraries.load(filepath, link=False, relative=False)
however the return value needs to use pythons context manager, this means the library loading is confined to a block of code and python cant leave a half loaded library state.
eg, load a single scene we know the name of:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = ["Scene"]
eg, load all scenes:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = data_from.scenes
eg, load all objects starting with 'A'
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.objects = [name for name in data_from.objects if name.startswith("A")]
As you can see gives 2 objects like 'bpy.data', but containing lists of strings which can be moved from one into another.
2011-03-12 16:06:37 +00:00
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
2011-05-24 15:21:14 +00:00
|
|
|
static void bpy_lib_exit_warn_idname(BPy_Library *self,
|
|
|
|
|
const char *name_plural,
|
|
|
|
|
const char *idname)
|
|
|
|
|
{
|
|
|
|
|
PyObject *exc, *val, *tb;
|
|
|
|
|
PyErr_Fetch(&exc, &val, &tb);
|
|
|
|
|
if (PyErr_WarnFormat(PyExc_UserWarning,
|
|
|
|
|
1,
|
2011-11-05 08:40:07 +00:00
|
|
|
"load: '%s' does not contain %s[\"%s\"]",
|
2012-05-20 19:49:27 +00:00
|
|
|
self->abspath,
|
|
|
|
|
name_plural,
|
|
|
|
|
idname))
|
|
|
|
|
{
|
2011-05-24 15:21:14 +00:00
|
|
|
/* Spurious errors can appear at shutdown */
|
|
|
|
|
if (PyErr_ExceptionMatches(PyExc_Warning)) {
|
|
|
|
|
PyErr_WriteUnraisable((PyObject *)self);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
PyErr_Restore(exc, val, tb);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void bpy_lib_exit_warn_type(BPy_Library *self, PyObject *item)
|
|
|
|
|
{
|
|
|
|
|
PyObject *exc, *val, *tb;
|
|
|
|
|
PyErr_Fetch(&exc, &val, &tb);
|
|
|
|
|
if (PyErr_WarnFormat(PyExc_UserWarning,
|
|
|
|
|
1,
|
2011-11-05 08:40:07 +00:00
|
|
|
"load: '%s' expected a string type, not a %.200s",
|
2012-05-20 19:49:27 +00:00
|
|
|
self->abspath,
|
|
|
|
|
Py_TYPE(item)->tp_name))
|
|
|
|
|
{
|
2011-05-24 15:21:14 +00:00
|
|
|
/* Spurious errors can appear at shutdown */
|
|
|
|
|
if (PyErr_ExceptionMatches(PyExc_Warning)) {
|
|
|
|
|
PyErr_WriteUnraisable((PyObject *)self);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
PyErr_Restore(exc, val, tb);
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-23 12:12:28 +01:00
|
|
|
struct LibExitLappContextItemsIterData {
|
|
|
|
|
short idcode;
|
|
|
|
|
BPy_Library *py_library;
|
|
|
|
|
PyObject *py_list;
|
|
|
|
|
Py_ssize_t py_list_size;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static bool bpy_lib_exit_lapp_context_items_cb(BlendfileLinkAppendContext *lapp_context,
|
|
|
|
|
BlendfileLinkAppendContextItem *item,
|
2024-08-20 11:03:36 +02:00
|
|
|
LibExitLappContextItemsIterData &data)
|
2021-11-23 12:12:28 +01:00
|
|
|
{
|
|
|
|
|
/* Since `bpy_lib_exit` loops over all ID types, all items in `lapp_context` end up being looped
|
|
|
|
|
* over for each ID type, so when it does not match the item can simply be skipped: it either has
|
|
|
|
|
* already been processed, or will be processed in a later loop. */
|
2024-08-20 11:03:36 +02:00
|
|
|
if (BKE_blendfile_link_append_context_item_idcode_get(lapp_context, item) != data.idcode) {
|
2021-11-23 12:12:28 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const int py_list_index = POINTER_AS_INT(
|
|
|
|
|
BKE_blendfile_link_append_context_item_userdata_get(lapp_context, item));
|
|
|
|
|
ID *new_id = BKE_blendfile_link_append_context_item_newid_get(lapp_context, item);
|
2024-08-20 11:03:36 +02:00
|
|
|
ID *liboverride_id = data.py_library->create_liboverrides ?
|
2023-02-14 15:17:53 +01:00
|
|
|
BKE_blendfile_link_append_context_item_liboverrideid_get(lapp_context,
|
|
|
|
|
item) :
|
2023-07-21 02:18:59 +02:00
|
|
|
nullptr;
|
2021-11-23 12:12:28 +01:00
|
|
|
|
2024-08-20 11:03:36 +02:00
|
|
|
BLI_assert(py_list_index < data.py_list_size);
|
2021-11-23 12:12:28 +01:00
|
|
|
|
|
|
|
|
/* Fully invalid items (which got set to `Py_None` already in first loop of `bpy_lib_exit`)
|
|
|
|
|
* should never be accessed here, since their index should never be set to any item in
|
|
|
|
|
* `lapp_context`. */
|
2024-08-20 11:03:36 +02:00
|
|
|
PyObject *item_src = PyList_GET_ITEM(data.py_list, py_list_index);
|
2021-11-23 12:12:28 +01:00
|
|
|
BLI_assert(item_src != Py_None);
|
|
|
|
|
|
|
|
|
|
PyObject *py_item;
|
2023-07-21 02:18:59 +02:00
|
|
|
if (liboverride_id != nullptr) {
|
2023-09-06 00:48:50 +02:00
|
|
|
PointerRNA newid_ptr = RNA_id_pointer_create(liboverride_id);
|
2023-02-14 15:17:53 +01:00
|
|
|
py_item = pyrna_struct_CreatePyObject(&newid_ptr);
|
|
|
|
|
}
|
2023-07-21 02:18:59 +02:00
|
|
|
else if (new_id != nullptr) {
|
2023-09-06 00:48:50 +02:00
|
|
|
PointerRNA newid_ptr = RNA_id_pointer_create(new_id);
|
2021-11-23 12:12:28 +01:00
|
|
|
py_item = pyrna_struct_CreatePyObject(&newid_ptr);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
const char *item_idname = PyUnicode_AsUTF8(item_src);
|
2024-08-20 11:03:36 +02:00
|
|
|
const char *idcode_name_plural = BKE_idtype_idcode_to_name_plural(data.idcode);
|
2021-11-23 12:12:28 +01:00
|
|
|
|
2024-08-20 11:03:36 +02:00
|
|
|
bpy_lib_exit_warn_idname(data.py_library, idcode_name_plural, item_idname);
|
2021-11-23 12:12:28 +01:00
|
|
|
|
2024-10-12 00:20:55 +11:00
|
|
|
py_item = Py_NewRef(Py_None);
|
2021-11-23 12:12:28 +01:00
|
|
|
}
|
|
|
|
|
|
2024-08-20 11:03:36 +02:00
|
|
|
PyList_SET_ITEM(data.py_list, py_list_index, py_item);
|
2021-11-23 12:12:28 +01:00
|
|
|
|
|
|
|
|
Py_DECREF(item_src);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-21 02:18:59 +02:00
|
|
|
static PyObject *bpy_lib_exit(BPy_Library *self, PyObject * /*args*/)
|
library loading api.
this is not well suited to RNA so this is a native python api.
This uses:
bpy.data.libraries.load(filepath, link=False, relative=False)
however the return value needs to use pythons context manager, this means the library loading is confined to a block of code and python cant leave a half loaded library state.
eg, load a single scene we know the name of:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = ["Scene"]
eg, load all scenes:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = data_from.scenes
eg, load all objects starting with 'A'
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.objects = [name for name in data_from.objects if name.startswith("A")]
As you can see gives 2 objects like 'bpy.data', but containing lists of strings which can be moved from one into another.
2011-03-12 16:06:37 +00:00
|
|
|
{
|
2021-03-04 23:13:07 +11:00
|
|
|
Main *bmain = self->bmain;
|
Main Workspace Integration
This commit does the main integration of workspaces, which is a design we agreed on during the 2.8 UI workshop (see https://wiki.blender.org/index.php/Dev:2.8/UI/Workshop_Writeup)
Workspaces should generally be stable, I'm not aware of any remaining bugs (or I've forgotten them :) ). If you find any, let me know!
(Exception: mode switching button might get out of sync with actual mode in some cases, would consider that a limitation/ToDo. Needs to be resolved at some point.)
== Main Changes/Features
* Introduces the new Workspaces as data-blocks.
* Allow storing a number of custom workspaces as part of the user configuration. Needs further work to allow adding and deleting individual workspaces.
* Bundle a default workspace configuration with Blender (current screen-layouts converted to workspaces).
* Pressing button to add a workspace spawns a menu to select between "Duplicate Current" and the workspaces from the user configuration. If no workspaces are stored in the user configuration, the default workspaces are listed instead.
* Store screen-layouts (`bScreen`) per workspace.
* Store an active screen-layout per workspace. Changing the workspace will enable this layout.
* Store active mode in workspace. Changing the workspace will also enter the mode of the new workspace. (Note that we still store the active mode in the object, moving this completely to workspaces is a separate project.)
* Store an active render layer per workspace.
* Moved mode switch from 3D View header to Info Editor header.
* Store active scene in window (not directly workspace related, but overlaps quite a bit).
* Removed 'Use Global Scene' User Preference option.
* Compatibility with old files - a new workspace is created for every screen-layout of old files. Old Blender versions should be able to read files saved with workspace support as well.
* Default .blend only contains one workspace ("General").
* Support appending workspaces.
Opening files without UI and commandline rendering should work fine.
Note that the UI is temporary! We plan to introduce a new global topbar
that contains the workspace options and tabs for switching workspaces.
== Technical Notes
* Workspaces are data-blocks.
* Adding and removing `bScreen`s should be done through `ED_workspace_layout` API now.
* A workspace can be active in multiple windows at the same time.
* The mode menu (which is now in the Info Editor header) doesn't display "Grease Pencil Edit" mode anymore since its availability depends on the active editor. Will be fixed by making Grease Pencil an own object type (as planned).
* The button to change the active workspace object mode may get out of sync with the mode of the active object. Will either be resolved by moving mode out of object data, or we'll disable workspace modes again (there's a `#define USE_WORKSPACE_MODE` for that).
* Screen-layouts (`bScreen`) are IDs and thus stored in a main list-base. Had to add a wrapper `WorkSpaceLayout` so we can store them in a list-base within workspaces, too. On the long run we could completely replace `bScreen` by workspace structs.
* `WorkSpace` types use some special compiler trickery to allow marking structs and struct members as private. BKE_workspace API should be used for accessing those.
* Added scene operators `SCENE_OT_`. Was previously done through screen operators.
== BPY API Changes
* Removed `Screen.scene`, added `Window.scene`
* Removed `UserPreferencesView.use_global_scene`
* Added `Context.workspace`, `Window.workspace` and `BlendData.workspaces`
* Added `bpy.types.WorkSpace` containing `screens`, `object_mode` and `render_layer`
* Added Screen.layout_name for the layout name that'll be displayed in the UI (may differ from internal name)
== What's left?
* There are a few open design questions (T50521). We should find the needed answers and implement them.
* Allow adding and removing individual workspaces from workspace configuration (needs UI design).
* Get the override system ready and support overrides per workspace.
* Support custom UI setups as part of workspaces (hidden panels, hidden buttons, customizable toolbars, etc).
* Allow enabling add-ons per workspace.
* Support custom workspace keymaps.
* Remove special exception for workspaces in linking code (so they're always appended, never linked). Depends on a few things, so best to solve later.
* Get the topbar done.
* Workspaces need a proper icon, current one is just a placeholder :)
Reviewed By: campbellbarton, mont29
Tags: #user_interface, #bf_blender_2.8
Maniphest Tasks: T50521
Differential Revision: https://developer.blender.org/D2451
2017-06-01 19:56:58 +02:00
|
|
|
const bool do_append = ((self->flag & FILE_LINK) == 0);
|
2023-02-14 15:17:53 +01:00
|
|
|
const bool create_liboverrides = self->create_liboverrides;
|
|
|
|
|
/* Code in #bpy_lib_load should have raised exception in case of incompatible parameter values.
|
|
|
|
|
*/
|
|
|
|
|
BLI_assert(!do_append || !create_liboverrides);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2024-08-07 12:12:17 +02:00
|
|
|
BKE_main_id_tag_all(bmain, ID_TAG_PRE_EXISTING, true);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
library loading api.
this is not well suited to RNA so this is a native python api.
This uses:
bpy.data.libraries.load(filepath, link=False, relative=False)
however the return value needs to use pythons context manager, this means the library loading is confined to a block of code and python cant leave a half loaded library state.
eg, load a single scene we know the name of:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = ["Scene"]
eg, load all scenes:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = data_from.scenes
eg, load all objects starting with 'A'
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.objects = [name for name in data_from.objects if name.startswith("A")]
As you can see gives 2 objects like 'bpy.data', but containing lists of strings which can be moved from one into another.
2011-03-12 16:06:37 +00:00
|
|
|
/* here appending/linking starts */
|
2024-08-07 12:12:17 +02:00
|
|
|
const int id_tag_extra = self->bmain_is_temp ? int(ID_TAG_TEMP_MAIN) : 0;
|
2023-06-03 08:36:28 +10:00
|
|
|
LibraryLink_Params liblink_params;
|
2021-03-09 01:01:31 +11:00
|
|
|
BLO_library_link_params_init(&liblink_params, bmain, self->flag, id_tag_extra);
|
2020-09-08 15:32:43 +10:00
|
|
|
|
2021-11-23 12:12:28 +01:00
|
|
|
BlendfileLinkAppendContext *lapp_context = BKE_blendfile_link_append_context_new(
|
|
|
|
|
&liblink_params);
|
2024-06-06 09:43:48 +10:00
|
|
|
/* NOTE: Transfers the ownership of the `blo_handle` to the `lapp_context`. */
|
2021-11-23 12:12:28 +01:00
|
|
|
BKE_blendfile_link_append_context_library_add(lapp_context, self->abspath, self->blo_handle);
|
2024-05-08 18:39:19 +02:00
|
|
|
self->blo_handle = nullptr;
|
2021-03-09 01:01:31 +11:00
|
|
|
|
2021-11-23 12:12:28 +01:00
|
|
|
int idcode_step = 0;
|
|
|
|
|
short idcode;
|
|
|
|
|
while ((idcode = BKE_idtype_idcode_iter_step(&idcode_step))) {
|
|
|
|
|
if (!BKE_idtype_idcode_is_linkable(idcode) || (idcode == ID_WS && !do_append)) {
|
|
|
|
|
continue;
|
library loading api.
this is not well suited to RNA so this is a native python api.
This uses:
bpy.data.libraries.load(filepath, link=False, relative=False)
however the return value needs to use pythons context manager, this means the library loading is confined to a block of code and python cant leave a half loaded library state.
eg, load a single scene we know the name of:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = ["Scene"]
eg, load all scenes:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = data_from.scenes
eg, load all objects starting with 'A'
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.objects = [name for name in data_from.objects if name.startswith("A")]
As you can see gives 2 objects like 'bpy.data', but containing lists of strings which can be moved from one into another.
2011-03-12 16:06:37 +00:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2021-11-23 12:12:28 +01:00
|
|
|
const char *name_plural = BKE_idtype_idcode_to_name_plural(idcode);
|
|
|
|
|
PyObject *ls = PyDict_GetItemString(self->dict, name_plural);
|
|
|
|
|
// printf("lib: %s\n", name_plural);
|
2023-07-21 02:18:59 +02:00
|
|
|
if (ls == nullptr || !PyList_Check(ls)) {
|
2021-11-23 12:12:28 +01:00
|
|
|
continue;
|
|
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2021-11-23 12:12:28 +01:00
|
|
|
const Py_ssize_t size = PyList_GET_SIZE(ls);
|
|
|
|
|
if (size == 0) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2021-11-23 12:12:28 +01:00
|
|
|
/* loop */
|
|
|
|
|
for (Py_ssize_t i = 0; i < size; i++) {
|
|
|
|
|
PyObject *item_src = PyList_GET_ITEM(ls, i);
|
|
|
|
|
const char *item_idname = PyUnicode_AsUTF8(item_src);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2021-11-23 12:12:28 +01:00
|
|
|
// printf(" %s\n", item_idname);
|
2020-08-07 12:41:06 +02:00
|
|
|
|
2021-11-23 12:12:28 +01:00
|
|
|
/* NOTE: index of item in py list is stored in userdata pointer, so that it can be found
|
|
|
|
|
* later on to replace the ID name by the actual ID pointer. */
|
2023-07-21 02:18:59 +02:00
|
|
|
if (item_idname != nullptr) {
|
2021-11-23 12:12:28 +01:00
|
|
|
BlendfileLinkAppendContextItem *item = BKE_blendfile_link_append_context_item_add(
|
|
|
|
|
lapp_context, item_idname, idcode, POINTER_FROM_INT(i));
|
|
|
|
|
BKE_blendfile_link_append_context_item_library_index_enable(lapp_context, item, 0);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* XXX, could complain about this */
|
|
|
|
|
bpy_lib_exit_warn_type(self, item_src);
|
|
|
|
|
PyErr_Clear();
|
|
|
|
|
|
|
|
|
|
#ifdef USE_RNA_DATABLOCKS
|
|
|
|
|
/* We can replace the item immediately with `None`. */
|
2024-10-12 00:20:55 +11:00
|
|
|
PyObject *py_item = Py_NewRef(Py_None);
|
2021-11-23 12:12:28 +01:00
|
|
|
PyList_SET_ITEM(ls, i, py_item);
|
|
|
|
|
Py_DECREF(item_src);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
2011-03-13 01:15:14 +00:00
|
|
|
}
|
2020-08-07 12:41:06 +02:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
Python API: Add link/append pre/post handlers.
The `pre` handler is called after blender internal code is done populating
the link/append context with data to be processed, and before this data
starts being linked from library files.
The `post` handler is called after blender is done linking, and
potentailly appending and/or instantiating, the requested data and all
of their dependencies.
Both handlers are called with a single argument, the link/append
context.
An new RNA sets of wrappers have been added to expose relevant info from
these internal C++ structures.
NOTE: !113658 is very similar (but tied to asset drag & drop), whereas
this PR is more general (these could probably live hand-in-hand / side-
by-side).
Implements #122357
Pull Request: https://projects.blender.org/blender/blender/pulls/128279
-----------------
Some quick py example code:
```python
import bpy
def my_handler_pre(lapp_context):
print("About to {}:\n\t".format("link" if "LINK" in lapp_context.options else "append"),
"\n\t".join("{} '{}', from libs ['{}']".format(item.id_type, item.name,
"', '".join([l.filepath for l in item.source_libraries]))
for item in lapp_context.import_items))
def my_handler_post(lapp_context):
print("{}:\n\t".format("Linked" if "LINK" in lapp_context.options else "Appended"),
"\n\t".join("{} '{}', from lib '{}'".format(item.id.id_type, item.id.name, item.source_library.filepath)
for item in lapp_context.import_items))
bpy.app.handlers.link_append_pre.append(my_handler_pre)
bpy.app.handlers.link_append_post.append(my_handler_post)
```
2024-10-02 16:44:38 +02:00
|
|
|
BKE_blendfile_link_append_context_init_done(lapp_context);
|
|
|
|
|
|
2023-07-21 02:18:59 +02:00
|
|
|
BKE_blendfile_link(lapp_context, nullptr);
|
2021-11-23 12:12:28 +01:00
|
|
|
if (do_append) {
|
2023-07-21 02:18:59 +02:00
|
|
|
BKE_blendfile_append(lapp_context, nullptr);
|
2021-11-23 12:12:28 +01:00
|
|
|
}
|
2023-02-14 15:17:53 +01:00
|
|
|
else if (create_liboverrides) {
|
2023-07-21 02:18:59 +02:00
|
|
|
BKE_blendfile_override(lapp_context, self->liboverride_flags, nullptr);
|
2023-02-14 15:17:53 +01:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
Python API: Add link/append pre/post handlers.
The `pre` handler is called after blender internal code is done populating
the link/append context with data to be processed, and before this data
starts being linked from library files.
The `post` handler is called after blender is done linking, and
potentailly appending and/or instantiating, the requested data and all
of their dependencies.
Both handlers are called with a single argument, the link/append
context.
An new RNA sets of wrappers have been added to expose relevant info from
these internal C++ structures.
NOTE: !113658 is very similar (but tied to asset drag & drop), whereas
this PR is more general (these could probably live hand-in-hand / side-
by-side).
Implements #122357
Pull Request: https://projects.blender.org/blender/blender/pulls/128279
-----------------
Some quick py example code:
```python
import bpy
def my_handler_pre(lapp_context):
print("About to {}:\n\t".format("link" if "LINK" in lapp_context.options else "append"),
"\n\t".join("{} '{}', from libs ['{}']".format(item.id_type, item.name,
"', '".join([l.filepath for l in item.source_libraries]))
for item in lapp_context.import_items))
def my_handler_post(lapp_context):
print("{}:\n\t".format("Linked" if "LINK" in lapp_context.options else "Appended"),
"\n\t".join("{} '{}', from lib '{}'".format(item.id.id_type, item.id.name, item.source_library.filepath)
for item in lapp_context.import_items))
bpy.app.handlers.link_append_pre.append(my_handler_pre)
bpy.app.handlers.link_append_post.append(my_handler_post)
```
2024-10-02 16:44:38 +02:00
|
|
|
BKE_blendfile_link_append_context_finalize(lapp_context);
|
|
|
|
|
|
2023-07-21 02:18:59 +02:00
|
|
|
/* If enabled, replace named items in given lists by the final matching new ID pointer. */
|
2016-07-14 21:00:05 +10:00
|
|
|
#ifdef USE_RNA_DATABLOCKS
|
2021-11-23 12:12:28 +01:00
|
|
|
idcode_step = 0;
|
|
|
|
|
while ((idcode = BKE_idtype_idcode_iter_step(&idcode_step))) {
|
|
|
|
|
if (!BKE_idtype_idcode_is_linkable(idcode) || (idcode == ID_WS && !do_append)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
const char *name_plural = BKE_idtype_idcode_to_name_plural(idcode);
|
|
|
|
|
PyObject *ls = PyDict_GetItemString(self->dict, name_plural);
|
|
|
|
|
// printf("lib: %s\n", name_plural);
|
2023-07-21 02:18:59 +02:00
|
|
|
if (ls == nullptr || !PyList_Check(ls)) {
|
2021-11-23 12:12:28 +01:00
|
|
|
continue;
|
2014-01-31 00:53:02 +11:00
|
|
|
}
|
2021-11-23 12:12:28 +01:00
|
|
|
|
|
|
|
|
const Py_ssize_t size = PyList_GET_SIZE(ls);
|
|
|
|
|
if (size == 0) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Loop over linked items in `lapp_context` to find matching python one in the list, and
|
|
|
|
|
* replace them with proper ID pointer. */
|
2023-07-21 02:18:59 +02:00
|
|
|
LibExitLappContextItemsIterData iter_data{};
|
|
|
|
|
iter_data.idcode = idcode;
|
|
|
|
|
iter_data.py_library = self;
|
|
|
|
|
iter_data.py_list = ls;
|
|
|
|
|
iter_data.py_list_size = size;
|
2021-11-23 12:12:28 +01:00
|
|
|
BKE_blendfile_link_append_context_item_foreach(
|
|
|
|
|
lapp_context,
|
2024-08-20 11:03:36 +02:00
|
|
|
[&iter_data](BlendfileLinkAppendContext *lapp_context,
|
|
|
|
|
BlendfileLinkAppendContextItem *item) -> bool {
|
|
|
|
|
return bpy_lib_exit_lapp_context_items_cb(lapp_context, item, iter_data);
|
|
|
|
|
},
|
|
|
|
|
BKE_BLENDFILE_LINK_APPEND_FOREACH_ITEM_FLAG_DO_DIRECT);
|
2020-08-07 12:41:06 +02:00
|
|
|
}
|
2021-11-23 12:12:28 +01:00
|
|
|
#endif // USE_RNA_DATABLOCKS
|
|
|
|
|
|
|
|
|
|
BKE_blendfile_link_append_context_free(lapp_context);
|
2024-08-07 12:12:17 +02:00
|
|
|
BKE_main_id_tag_all(bmain, ID_TAG_PRE_EXISTING, false);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2023-10-13 11:29:59 +02:00
|
|
|
BKE_reports_free(&self->reports);
|
|
|
|
|
|
2020-08-07 12:41:06 +02:00
|
|
|
Py_RETURN_NONE;
|
library loading api.
this is not well suited to RNA so this is a native python api.
This uses:
bpy.data.libraries.load(filepath, link=False, relative=False)
however the return value needs to use pythons context manager, this means the library loading is confined to a block of code and python cant leave a half loaded library state.
eg, load a single scene we know the name of:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = ["Scene"]
eg, load all scenes:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = data_from.scenes
eg, load all objects starting with 'A'
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.objects = [name for name in data_from.objects if name.startswith("A")]
As you can see gives 2 objects like 'bpy.data', but containing lists of strings which can be moved from one into another.
2011-03-12 16:06:37 +00:00
|
|
|
}
|
|
|
|
|
|
2011-03-14 01:00:41 +00:00
|
|
|
static PyObject *bpy_lib_dir(BPy_Library *self)
|
|
|
|
|
{
|
|
|
|
|
return PyDict_Keys(self->dict);
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-01 12:06:03 +11:00
|
|
|
#ifdef __GNUC__
|
|
|
|
|
# ifdef __clang__
|
|
|
|
|
# pragma clang diagnostic push
|
|
|
|
|
# pragma clang diagnostic ignored "-Wcast-function-type"
|
|
|
|
|
# else
|
|
|
|
|
# pragma GCC diagnostic push
|
|
|
|
|
# pragma GCC diagnostic ignored "-Wcast-function-type"
|
|
|
|
|
# endif
|
2023-07-21 13:42:35 +10:00
|
|
|
#endif
|
|
|
|
|
|
2020-05-29 14:50:29 +10:00
|
|
|
PyMethodDef BPY_library_load_method_def = {
|
|
|
|
|
"load",
|
|
|
|
|
(PyCFunction)bpy_lib_load,
|
2021-03-04 23:13:07 +11:00
|
|
|
METH_VARARGS | METH_KEYWORDS,
|
2020-05-29 14:50:29 +10:00
|
|
|
bpy_lib_load_doc,
|
|
|
|
|
};
|
|
|
|
|
|
2025-04-01 12:06:03 +11:00
|
|
|
#ifdef __GNUC__
|
|
|
|
|
# ifdef __clang__
|
|
|
|
|
# pragma clang diagnostic pop
|
|
|
|
|
# else
|
|
|
|
|
# pragma GCC diagnostic pop
|
|
|
|
|
# endif
|
2023-07-21 13:42:35 +10:00
|
|
|
#endif
|
|
|
|
|
|
2023-07-21 10:59:54 +10:00
|
|
|
int BPY_library_load_type_ready()
|
library loading api.
this is not well suited to RNA so this is a native python api.
This uses:
bpy.data.libraries.load(filepath, link=False, relative=False)
however the return value needs to use pythons context manager, this means the library loading is confined to a block of code and python cant leave a half loaded library state.
eg, load a single scene we know the name of:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = ["Scene"]
eg, load all scenes:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = data_from.scenes
eg, load all objects starting with 'A'
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.objects = [name for name in data_from.objects if name.startswith("A")]
As you can see gives 2 objects like 'bpy.data', but containing lists of strings which can be moved from one into another.
2011-03-12 16:06:37 +00:00
|
|
|
{
|
2019-03-30 06:12:48 +11:00
|
|
|
if (PyType_Ready(&bpy_lib_Type) < 0) {
|
library loading api.
this is not well suited to RNA so this is a native python api.
This uses:
bpy.data.libraries.load(filepath, link=False, relative=False)
however the return value needs to use pythons context manager, this means the library loading is confined to a block of code and python cant leave a half loaded library state.
eg, load a single scene we know the name of:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = ["Scene"]
eg, load all scenes:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = data_from.scenes
eg, load all objects starting with 'A'
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.objects = [name for name in data_from.objects if name.startswith("A")]
As you can see gives 2 objects like 'bpy.data', but containing lists of strings which can be moved from one into another.
2011-03-12 16:06:37 +00:00
|
|
|
return -1;
|
2019-03-30 06:12:48 +11:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
library loading api.
this is not well suited to RNA so this is a native python api.
This uses:
bpy.data.libraries.load(filepath, link=False, relative=False)
however the return value needs to use pythons context manager, this means the library loading is confined to a block of code and python cant leave a half loaded library state.
eg, load a single scene we know the name of:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = ["Scene"]
eg, load all scenes:
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.scenes = data_from.scenes
eg, load all objects starting with 'A'
with bpy.data.libraries.load(filepath) as (data_from, data_to):
data_to.objects = [name for name in data_from.objects if name.startswith("A")]
As you can see gives 2 objects like 'bpy.data', but containing lists of strings which can be moved from one into another.
2011-03-12 16:06:37 +00:00
|
|
|
return 0;
|
|
|
|
|
}
|