Files
test2/source/blender/blenkernel/intern/lib_id.cc

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

2250 lines
70 KiB
C++
Raw Normal View History

/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*
* Contains management of ID's and libraries
* allocate and free of all library data
2011-02-27 20:40:57 +00:00
*/
#include <cctype>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>
2002-10-12 11:37:38 +00:00
#include "CLG_log.h"
2002-10-12 11:37:38 +00:00
#include "MEM_guardedalloc.h"
/* all types are needed here, in order to do memory operations */
2020-04-21 14:56:02 +02:00
#include "DNA_ID.h"
#include "DNA_anim_types.h"
#include "DNA_collection_types.h"
#include "DNA_gpencil_legacy_types.h"
2002-10-12 11:37:38 +00:00
#include "DNA_key_types.h"
#include "DNA_node_types.h"
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
#include "DNA_workspace_types.h"
#include "BLI_utildefines.h"
#include "BLI_alloca.h"
#include "BLI_array.hh"
#include "BLI_blenlib.h"
#include "BLI_ghash.h"
#include "BLI_linklist.h"
#include "BLI_memarena.h"
#include "BLI_string_utils.hh"
#include "BLT_translation.h"
2002-10-12 11:37:38 +00:00
#include "BKE_anim_data.h"
#include "BKE_armature.hh"
#include "BKE_asset.hh"
#include "BKE_bpath.h"
#include "BKE_context.hh"
#include "BKE_global.h"
#include "BKE_gpencil_legacy.h"
#include "BKE_idprop.h"
#include "BKE_idtype.h"
2002-10-12 11:37:38 +00:00
#include "BKE_key.h"
#include "BKE_lib_id.h"
#include "BKE_lib_override.hh"
#include "BKE_lib_query.h"
#include "BKE_lib_remap.hh"
#include "BKE_main.hh"
#include "BKE_main_namemap.hh"
Orange: more noodle updates! **** NEW: Group Nodes Node trees usually become messy and confusing quickly, so we need not only a way to collapse Nodes into single 'groups', but also a way to re-use that data to create libraries of effects. This has been done by making a new Library data type, the NodeTree. Everything that has been grouped is stored here, and available for re-use, appending or linking. These NodeTrees are fully generic, i.e. can store shader trees, composit trees, and so on. The 'type' value as stored in the NodeTree will keep track of internal type definitions and execute/drawing callbacks. Needless to say, re-using shader trees in a composit tree is a bit useless, and will be prevented in the browsing code. :) So; any NodeTree can become a "Goup Node" inside in a NodeTree. This Group Node then works just like any Node. To prevent the current code to become too complex, I've disabled the possibility to insert Groups inside of Groups. That might be enabled later, but is a real nasty piece of code to get OK. Since Group Nodes are a dynamic Node type, a lot of work has been done to ensure Node definitions can be dynamic too, but still allow to be stored in files, and allow to be verified for type-definition changes on reloading. This system needs a little bit maturing still, so the Python gurus should better wait a little bit! (Also for me to write the definite API docs for it). What works now: - Press CTRL+G to create a new Group. The grouping code checks for impossible selections (like an unselected node between selected nodes). Everthing that's selected then gets removed from the current tree, and inserted in a new NodeTree library data block. A Group Node then is added which links to this new NodeTree. - Press ALT+G to ungroup. This will not delete the NodeTree library data, but just duplicate the Group into the current tree. - Press TAB, or click on the NodeTree icon to edit Groups. Note that NodeTrees are instances, so editing one Group will also change the other users. This also means that when removing nodes in a Group (or hiding sockets or changing internal links) this is immediately corrected for all users of this Group, also in other Materials. - While editing Groups, only the internal Nodes can be edited. A single click outside of the Group boundary will close this 'edit mode'. What needs to be done: - SHIFT+A menu in toolbox style, also including a list of Groups - Enable the single-user button in the Group Node - Displaying all (visible) internal group UI elements in the Node Panel - Enable Library linking and prevent editing of Groups then. **** NEW: Socket Visibility control Node types will be generated with a lot of possible inputs or outputs, and drawing all sockets all the time isn't very useful then. A new option in the Node header ('plus' icon) allows to either hide all unused sockets (first keypress) or to reveil them (when there are hidden sockets, the icon displays black, otherwise it's blended). Hidden sockets in Nodes also are not exported to a Group, so this way you can control what options (in/outputs) exactly are available. To be done: - a way to hide individual sockets, like with a RMB click on it. **** NEW: Nodes now render! This is still quite primitive, more on a level to replace the (now obsolete and disabled) Material Layers. What needs to be done: - make the "Geometry" node work properly, also for AA textures - make the Texture Node work (does very little at the moment) - give Material Nodes all inputs as needed (like Map-to Panel) - find a way to export more data from a Material Node, like the shadow value, or light intensity only, etc Very important also to separate from the Material Buttons the "global" options, like "Ztransp" or "Wire" or "Halo". These can not be set for each Material-Node individually. Also note that the Preview Render (Buttons window) now renders a bit differently. This was a horrid piece of antique code, using a totally incompatible way of rendering. Target is to fully re-use internal render code for previews. OK... that's it mostly. Now test!
2006-01-02 13:06:05 +00:00
#include "BKE_node.h"
#include "BKE_rigidbody.h"
Orange: more noodle updates! **** NEW: Group Nodes Node trees usually become messy and confusing quickly, so we need not only a way to collapse Nodes into single 'groups', but also a way to re-use that data to create libraries of effects. This has been done by making a new Library data type, the NodeTree. Everything that has been grouped is stored here, and available for re-use, appending or linking. These NodeTrees are fully generic, i.e. can store shader trees, composit trees, and so on. The 'type' value as stored in the NodeTree will keep track of internal type definitions and execute/drawing callbacks. Needless to say, re-using shader trees in a composit tree is a bit useless, and will be prevented in the browsing code. :) So; any NodeTree can become a "Goup Node" inside in a NodeTree. This Group Node then works just like any Node. To prevent the current code to become too complex, I've disabled the possibility to insert Groups inside of Groups. That might be enabled later, but is a real nasty piece of code to get OK. Since Group Nodes are a dynamic Node type, a lot of work has been done to ensure Node definitions can be dynamic too, but still allow to be stored in files, and allow to be verified for type-definition changes on reloading. This system needs a little bit maturing still, so the Python gurus should better wait a little bit! (Also for me to write the definite API docs for it). What works now: - Press CTRL+G to create a new Group. The grouping code checks for impossible selections (like an unselected node between selected nodes). Everthing that's selected then gets removed from the current tree, and inserted in a new NodeTree library data block. A Group Node then is added which links to this new NodeTree. - Press ALT+G to ungroup. This will not delete the NodeTree library data, but just duplicate the Group into the current tree. - Press TAB, or click on the NodeTree icon to edit Groups. Note that NodeTrees are instances, so editing one Group will also change the other users. This also means that when removing nodes in a Group (or hiding sockets or changing internal links) this is immediately corrected for all users of this Group, also in other Materials. - While editing Groups, only the internal Nodes can be edited. A single click outside of the Group boundary will close this 'edit mode'. What needs to be done: - SHIFT+A menu in toolbox style, also including a list of Groups - Enable the single-user button in the Group Node - Displaying all (visible) internal group UI elements in the Node Panel - Enable Library linking and prevent editing of Groups then. **** NEW: Socket Visibility control Node types will be generated with a lot of possible inputs or outputs, and drawing all sockets all the time isn't very useful then. A new option in the Node header ('plus' icon) allows to either hide all unused sockets (first keypress) or to reveil them (when there are hidden sockets, the icon displays black, otherwise it's blended). Hidden sockets in Nodes also are not exported to a Group, so this way you can control what options (in/outputs) exactly are available. To be done: - a way to hide individual sockets, like with a RMB click on it. **** NEW: Nodes now render! This is still quite primitive, more on a level to replace the (now obsolete and disabled) Material Layers. What needs to be done: - make the "Geometry" node work properly, also for AA textures - make the Texture Node work (does very little at the moment) - give Material Nodes all inputs as needed (like Map-to Panel) - find a way to export more data from a Material Node, like the shadow value, or light intensity only, etc Very important also to separate from the Material Buttons the "global" options, like "Ztransp" or "Wire" or "Halo". These can not be set for each Material-Node individually. Also note that the Preview Render (Buttons window) now renders a bit differently. This was a horrid piece of antique code, using a totally incompatible way of rendering. Target is to fully re-use internal render code for previews. OK... that's it mostly. Now test!
2006-01-02 13:06:05 +00:00
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_build.hh"
#include "DEG_depsgraph_query.hh"
Depsgraph: New dependency graph integration commit This commit integrates the work done so far on the new dependency graph system, where goal was to replace legacy depsgraph with the new one, supporting loads of neat features like: - More granular dependency relation nature, which solves issues with fake cycles in the dependencies. - Move towards all-animatable, by better integration of drivers into the system. - Lay down some basis for upcoming copy-on-write, overrides and so on. The new system is living side-by-side with the previous one and disabled by default, so nothing will become suddenly broken. The way to enable new depsgraph is to pass `--new-depsgraph` command line argument. It's a bit early to consider the system production-ready, there are some TODOs and issues were discovered during the merge period, they'll be addressed ASAP. But it's important to merge, because it's the only way to attract artists to really start testing this system. There are number of assorted documents related on the design of the new system: * http://wiki.blender.org/index.php/User:Aligorith/GSoC2013_Depsgraph#Design_Documents * http://wiki.blender.org/index.php/User:Nazg-gul/DependencyGraph There are also some user-related information online: * http://code.blender.org/2015/02/blender-dependency-graph-branch-for-users/ * http://code.blender.org/2015/03/more-dependency-graph-tricks/ Kudos to everyone who was involved into the project: - Joshua "Aligorith" Leung -- design specification, initial code - Lukas "lukas_t" Toenne -- integrating code into blender, with further fixes - Sergey "Sergey" "Sharybin" -- some mocking around, trying to wrap up the project and so - Bassam "slikdigit" Kurdali -- stressing the new system, reporting all the issues and recording/writing documentation. - Everyone else who i forgot to mention here :)
2015-05-12 15:05:57 +05:00
#include "RNA_access.hh"
2011-06-29 05:07:12 +00:00
#include "BLO_read_write.hh"
#include "atomic_ops.h"
#include "lib_intern.hh"
//#define DEBUG_TIME
#ifdef DEBUG_TIME
# include "PIL_time_utildefines.h"
#endif
static CLG_LogRef LOG = {"bke.lib_id"};
IDTypeInfo IDType_ID_LINK_PLACEHOLDER = {
/*id_code*/ ID_LINK_PLACEHOLDER,
/*id_filter*/ 0,
/*main_listbase_index*/ INDEX_ID_NULL,
/*struct_size*/ sizeof(ID),
/*name*/ "LinkPlaceholder",
/*name_plural*/ N_("link_placeholders"),
/*translation_context*/ BLT_I18NCONTEXT_ID_ID,
/*flags*/ IDTYPE_FLAGS_NO_COPY | IDTYPE_FLAGS_NO_LIBLINKING,
/*asset_type_info*/ nullptr,
/*init_data*/ nullptr,
/*copy_data*/ nullptr,
/*free_data*/ nullptr,
/*make_local*/ nullptr,
/*foreach_id*/ nullptr,
/*foreach_cache*/ nullptr,
/*foreach_path*/ nullptr,
/*owner_pointer_get*/ nullptr,
/*blend_write*/ nullptr,
/*blend_read_data*/ nullptr,
/*blend_read_after_liblink*/ nullptr,
/*blend_read_undo_preserve*/ nullptr,
/*lib_override_apply_post*/ nullptr,
};
2018-06-17 17:05:51 +02:00
/* GS reads the memory pointed at in a specific ordering.
* only use this definition, makes little and big endian systems
* work fine, in conjunction with MAKE_ID */
/* ************* general ************************ */
2002-10-12 11:37:38 +00:00
/**
* Rewrites a relative path to be relative to the main file - unless the path is
* absolute, in which case it is not altered.
*/
static bool lib_id_library_local_paths_callback(BPathForeachPathData *bpath_data,
char *path_dst,
size_t path_dst_maxncpy,
const char *path_src)
{
const char **data = static_cast<const char **>(bpath_data->user_data);
/* be sure there is low chance of the path being too short */
char filepath[(FILE_MAXDIR * 2) + FILE_MAXFILE];
const char *base_new = data[0];
const char *base_old = data[1];
if (BLI_path_is_rel(base_old)) {
CLOG_ERROR(&LOG, "old base path '%s' is not absolute.", base_old);
return false;
}
/* Make referenced file absolute. This would be a side-effect of
* BLI_path_normalize, but we do it explicitly so we know if it changed. */
BLI_strncpy(filepath, path_src, FILE_MAX);
if (BLI_path_abs(filepath, base_old)) {
/* Path was relative and is now absolute. Remap.
* Important BLI_path_normalize runs before the path is made relative
* because it won't work for paths that start with "//../" */
BLI_path_normalize(filepath);
BLI_path_rel(filepath, base_new);
BLI_strncpy(path_dst, filepath, path_dst_maxncpy);
return true;
}
/* Path was not relative to begin with. */
return false;
}
/**
* This has to be called from each make_local_* func, we could call from BKE_lib_id_make_local()
* but then the make local functions would not be self contained.
*
* NOTE(@ideasman42): that the id _must_ have a library.
* TODO: This can probably be replaced by an ID-level version of #BKE_bpath_relative_rebase.
*/
static void lib_id_library_local_paths(Main *bmain, Library *lib, ID *id)
{
const char *bpath_user_data[2] = {BKE_main_blendfile_path(bmain), lib->filepath_abs};
BPathForeachPathData path_data{};
path_data.bmain = bmain;
path_data.callback_function = lib_id_library_local_paths_callback;
path_data.flag = BKE_BPATH_FOREACH_PATH_SKIP_MULTIFILE;
path_data.user_data = (void *)bpath_user_data;
BKE_bpath_foreach_path_id(&path_data, id);
}
static int lib_id_clear_library_data_users_update_cb(LibraryIDLinkCallbackData *cb_data)
{
ID *id = static_cast<ID *>(cb_data->user_data);
if (*cb_data->id_pointer == id) {
/* Even though the ID itself remain the same after being made local, from depsgraph point of
* view this is a different ID. Hence we need to tag all of its users for COW update. */
DEG_id_tag_update_ex(
cb_data->bmain, cb_data->owner_id, ID_RECALC_TAG_FOR_UNDO | ID_RECALC_COPY_ON_WRITE);
return IDWALK_RET_STOP_ITER;
}
return IDWALK_RET_NOP;
}
void BKE_lib_id_clear_library_data(Main *bmain, ID *id, const int flags)
{
const bool id_in_mainlist = (id->tag & LIB_TAG_NO_MAIN) == 0 &&
(id->flag & LIB_EMBEDDED_DATA) == 0;
if (id_in_mainlist) {
BKE_main_namemap_remove_name(bmain, id, id->name + 2);
}
lib_id_library_local_paths(bmain, id->lib, id);
id_fake_user_clear(id);
id->lib = nullptr;
id->tag &= ~(LIB_TAG_INDIRECT | LIB_TAG_EXTERN);
id->flag &= ~LIB_INDIRECT_WEAK_LINK;
if (id_in_mainlist) {
if (BKE_id_new_name_validate(bmain, which_libbase(bmain, GS(id->name)), id, nullptr, false)) {
bmain->is_memfile_undo_written = false;
}
}
/* Conceptually, an ID made local is not the same as the linked one anymore. Reflect that by
* regenerating its session UUID. */
if ((id->tag & LIB_TAG_TEMP_MAIN) == 0) {
BKE_lib_libblock_session_uuid_renew(id);
}
if (ID_IS_ASSET(id)) {
if ((flags & LIB_ID_MAKELOCAL_ASSET_DATA_CLEAR) != 0) {
BKE_asset_metadata_free(&id->asset_data);
}
else {
/* Assets should always have a fake user. Ensure this is the case after "Make Local". */
id_fake_user_set(id);
}
}
/* We need to tag this IDs and all of its users, conceptually new local ID and original linked
2020-06-25 23:13:02 +10:00
* ones are two completely different data-blocks that were virtually remapped, even though in
* reality they remain the same data. For undo this info is critical now. */
DEG_id_tag_update_ex(bmain, id, ID_RECALC_COPY_ON_WRITE);
ID *id_iter;
FOREACH_MAIN_ID_BEGIN (bmain, id_iter) {
BKE_library_foreach_ID_link(
bmain, id_iter, lib_id_clear_library_data_users_update_cb, id, IDWALK_READONLY);
}
FOREACH_MAIN_ID_END;
/* Internal shape key blocks inside data-blocks also stores id->lib,
* make sure this stays in sync (note that we do not need any explicit handling for real EMBEDDED
* IDs here, this is down automatically in `lib_id_expand_local_cb()`. */
Key *key = BKE_key_from_id(id);
if (key != nullptr) {
BKE_lib_id_clear_library_data(bmain, &key->id, flags);
}
/* Even though the ID itself remain the same after being made local, from depsgraph point of view
* this is a different ID. Hence we rebuild depsgraph relationships. */
DEG_relations_tag_update(bmain);
}
2002-10-12 11:37:38 +00:00
void id_lib_extern(ID *id)
{
if (id && ID_IS_LINKED(id)) {
BLI_assert(BKE_idtype_idcode_is_linkable(GS(id->name)));
if (id->tag & LIB_TAG_INDIRECT) {
2019-01-30 11:38:13 +01:00
id->tag &= ~LIB_TAG_INDIRECT;
id->flag &= ~LIB_INDIRECT_WEAK_LINK;
id->tag |= LIB_TAG_EXTERN;
id->lib->parent = nullptr;
2002-10-12 11:37:38 +00:00
}
}
}
void id_lib_indirect_weak_link(ID *id)
{
if (id && ID_IS_LINKED(id)) {
BLI_assert(BKE_idtype_idcode_is_linkable(GS(id->name)));
if (id->tag & LIB_TAG_INDIRECT) {
id->flag |= LIB_INDIRECT_WEAK_LINK;
}
}
}
void id_us_ensure_real(ID *id)
{
if (id) {
const int limit = ID_FAKE_USERS(id);
id->tag |= LIB_TAG_EXTRAUSER;
if (id->us <= limit) {
if (id->us < limit || ((id->us == limit) && (id->tag & LIB_TAG_EXTRAUSER_SET))) {
CLOG_ERROR(&LOG,
"ID user count error: %s (from '%s')",
id->name,
id->lib ? id->lib->filepath_abs : "[Main]");
}
id->us = limit + 1;
id->tag |= LIB_TAG_EXTRAUSER_SET;
}
}
}
void id_us_clear_real(ID *id)
{
if (id && (id->tag & LIB_TAG_EXTRAUSER)) {
if (id->tag & LIB_TAG_EXTRAUSER_SET) {
id->us--;
BLI_assert(id->us >= ID_FAKE_USERS(id));
}
id->tag &= ~(LIB_TAG_EXTRAUSER | LIB_TAG_EXTRAUSER_SET);
}
}
void id_us_plus_no_lib(ID *id)
{
if (id) {
if ((id->tag & LIB_TAG_EXTRAUSER) && (id->tag & LIB_TAG_EXTRAUSER_SET)) {
BLI_assert(id->us >= 1);
/* No need to increase count, just tag extra user as no more set.
* Avoids annoying & inconsistent +1 in user count. */
id->tag &= ~LIB_TAG_EXTRAUSER_SET;
}
else {
BLI_assert(id->us >= 0);
id->us++;
}
}
}
2002-10-12 11:37:38 +00:00
void id_us_plus(ID *id)
{
if (id) {
id_us_plus_no_lib(id);
id_lib_extern(id);
2002-10-12 11:37:38 +00:00
}
}
void id_us_min(ID *id)
{
if (id) {
const int limit = ID_FAKE_USERS(id);
if (id->us <= limit) {
if (!ID_TYPE_IS_DEPRECATED(GS(id->name))) {
2023-01-03 10:19:27 +11:00
/* Do not assert on deprecated ID types, we cannot really ensure that their ID
* reference-counting is valid. */
CLOG_ERROR(&LOG,
"ID user decrement error: %s (from '%s'): %d <= %d",
id->name,
id->lib ? id->lib->filepath_abs : "[Main]",
id->us,
limit);
}
id->us = limit;
}
else {
id->us--;
}
if ((id->us == limit) && (id->tag & LIB_TAG_EXTRAUSER)) {
/* We need an extra user here, but never actually incremented user count for it so far,
* do it now. */
id_us_ensure_real(id);
}
}
}
void id_fake_user_set(ID *id)
{
if (id && !(id->flag & LIB_FAKEUSER)) {
id->flag |= LIB_FAKEUSER;
id_us_plus(id);
}
}
void id_fake_user_clear(ID *id)
{
if (id && (id->flag & LIB_FAKEUSER)) {
id->flag &= ~LIB_FAKEUSER;
id_us_min(id);
}
}
void BKE_id_newptr_and_tag_clear(ID *id)
{
/* We assume that if this ID has no new ID, its embedded data has not either. */
if (id->newid == nullptr) {
return;
}
id->newid->tag &= ~LIB_TAG_NEW;
id->newid = nullptr;
/* Deal with embedded data too. */
/* NOTE: even though ShapeKeys are not technically embedded data currently, they behave as such
* in most cases, so for sake of consistency treat them as such here. Also mirrors the behavior
* in `BKE_lib_id_make_local`. */
Key *key = BKE_key_from_id(id);
if (key != nullptr) {
BKE_id_newptr_and_tag_clear(&key->id);
}
bNodeTree *ntree = ntreeFromID(id);
if (ntree != nullptr) {
BKE_id_newptr_and_tag_clear(&ntree->id);
}
if (GS(id->name) == ID_SCE) {
Collection *master_collection = ((Scene *)id)->master_collection;
if (master_collection != nullptr) {
BKE_id_newptr_and_tag_clear(&master_collection->id);
}
}
}
static int lib_id_expand_local_cb(LibraryIDLinkCallbackData *cb_data)
{
Main *bmain = cb_data->bmain;
ID *self_id = cb_data->self_id;
ID **id_pointer = cb_data->id_pointer;
int const cb_flag = cb_data->cb_flag;
const int flags = POINTER_AS_INT(cb_data->user_data);
if (cb_flag & IDWALK_CB_LOOPBACK) {
2020-08-29 13:41:02 +10:00
/* We should never have anything to do with loop-back pointers here. */
return IDWALK_RET_NOP;
}
if (cb_flag & (IDWALK_CB_EMBEDDED | IDWALK_CB_EMBEDDED_NOT_OWNING)) {
/* Embedded data-blocks need to be made fully local as well.
* Note however that in some cases (when owner ID had to be duplicated instead of being made
* local directly), its embedded IDs should also have already been duplicated, and hence be
* fully local here already. */
if (*id_pointer != nullptr && ID_IS_LINKED(*id_pointer)) {
BLI_assert(*id_pointer != self_id);
BKE_lib_id_clear_library_data(bmain, *id_pointer, flags);
}
return IDWALK_RET_NOP;
}
/* Can happen that we get un-linkable ID here, e.g. with shape-key referring to itself
* (through drivers)...
* Just skip it, shape key can only be either indirectly linked, or fully local, period.
2022-09-16 18:13:19 +10:00
* And let's curse one more time that stupid useless shape-key ID type! */
if (*id_pointer && *id_pointer != self_id &&
BKE_idtype_idcode_is_linkable(GS((*id_pointer)->name)))
{
id_lib_extern(*id_pointer);
}
return IDWALK_RET_NOP;
}
void BKE_lib_id_expand_local(Main *bmain, ID *id, const int flags)
{
BKE_library_foreach_ID_link(
bmain, id, lib_id_expand_local_cb, POINTER_FROM_INT(flags), IDWALK_READONLY);
}
/**
* Ensure new (copied) ID is fully made local.
*/
void lib_id_copy_ensure_local(Main *bmain, const ID *old_id, ID *new_id, const int flags)
{
if (ID_IS_LINKED(old_id)) {
BKE_lib_id_expand_local(bmain, new_id, flags);
lib_id_library_local_paths(bmain, old_id->lib, new_id);
}
}
void BKE_lib_id_make_local_generic_action_define(
Main *bmain, ID *id, int flags, bool *r_force_local, bool *r_force_copy)
{
bool force_local = (flags & LIB_ID_MAKELOCAL_FORCE_LOCAL) != 0;
bool force_copy = (flags & LIB_ID_MAKELOCAL_FORCE_COPY) != 0;
BLI_assert(force_copy == false || force_copy != force_local);
if (force_local || force_copy) {
2022-01-26 16:06:22 +11:00
/* Already set by caller code, nothing to do here. */
*r_force_local = force_local;
*r_force_copy = force_copy;
return;
}
const bool lib_local = (flags & LIB_ID_MAKELOCAL_FULL_LIBRARY) != 0;
bool is_local = false, is_lib = false;
/* - no user (neither lib nor local): make local (happens e.g. with UI-used only data).
* - only lib users: do nothing (unless force_local is set)
* - only local users: make local
* - mixed: make copy
* In case we make a whole lib's content local,
* we always want to localize, and we skip remapping (done later).
*/
BKE_library_ID_test_usages(bmain, id, &is_local, &is_lib);
if (!lib_local && !is_local && !is_lib) {
force_local = true;
}
else if (lib_local || is_local) {
if (!is_lib) {
force_local = true;
}
else {
force_copy = true;
}
}
*r_force_local = force_local;
*r_force_copy = force_copy;
}
void BKE_lib_id_make_local_generic(Main *bmain, ID *id, const int flags)
{
if (!ID_IS_LINKED(id)) {
return;
}
bool force_local, force_copy;
BKE_lib_id_make_local_generic_action_define(bmain, id, flags, &force_local, &force_copy);
if (force_local) {
BKE_lib_id_clear_library_data(bmain, id, flags);
if ((flags & LIB_ID_MAKELOCAL_LIBOVERRIDE_CLEAR) != 0) {
BKE_lib_override_library_make_local(bmain, id);
}
BKE_lib_id_expand_local(bmain, id, flags);
}
else if (force_copy) {
const int copy_flags =
(LIB_ID_COPY_DEFAULT |
((flags & LIB_ID_MAKELOCAL_LIBOVERRIDE_CLEAR) != 0 ? LIB_ID_COPY_NO_LIB_OVERRIDE : 0));
ID *id_new = BKE_id_copy_ex(bmain, id, nullptr, copy_flags);
/* Should not fail in expected use cases,
* but a few ID types cannot be copied (LIB, WM, SCR...). */
if (id_new != nullptr) {
id_new->us = 0;
/* setting newid is mandatory for complex make_lib_local logic... */
ID_NEW_SET(id, id_new);
Key *key = BKE_key_from_id(id), *key_new = BKE_key_from_id(id);
if (key && key_new) {
ID_NEW_SET(key, key_new);
}
bNodeTree *ntree = ntreeFromID(id), *ntree_new = ntreeFromID(id_new);
if (ntree && ntree_new) {
ID_NEW_SET(ntree, ntree_new);
}
if (GS(id->name) == ID_SCE) {
Collection *master_collection = ((Scene *)id)->master_collection,
*master_collection_new = ((Scene *)id_new)->master_collection;
if (master_collection && master_collection_new) {
ID_NEW_SET(master_collection, master_collection_new);
}
}
const bool lib_local = (flags & LIB_ID_MAKELOCAL_FULL_LIBRARY) != 0;
if (!lib_local) {
BKE_libblock_remap(bmain, id, id_new, ID_REMAP_SKIP_INDIRECT_USAGE);
}
}
}
}
bool BKE_lib_id_make_local(Main *bmain, ID *id, const int flags)
{
const bool lib_local = (flags & LIB_ID_MAKELOCAL_FULL_LIBRARY) != 0;
/* We don't care whether ID is directly or indirectly linked
* in case we are making a whole lib local... */
if (!lib_local && (id->tag & LIB_TAG_INDIRECT)) {
return false;
}
const IDTypeInfo *idtype_info = BKE_idtype_get_info_from_id(id);
if (idtype_info == nullptr) {
BLI_assert_msg(0, "IDType Missing IDTypeInfo");
return false;
}
BLI_assert((idtype_info->flags & IDTYPE_FLAGS_NO_LIBLINKING) == 0);
if (idtype_info->make_local != nullptr) {
idtype_info->make_local(bmain, id, flags);
}
else {
BKE_lib_id_make_local_generic(bmain, id, flags);
}
return true;
}
struct IDCopyLibManagementData {
const ID *id_src;
ID *id_dst;
int flag;
};
2022-09-16 18:13:19 +10:00
/** Increases user-count as required, and remap self ID pointers. */
static int id_copy_libmanagement_cb(LibraryIDLinkCallbackData *cb_data)
{
ID **id_pointer = cb_data->id_pointer;
ID *id = *id_pointer;
const int cb_flag = cb_data->cb_flag;
IDCopyLibManagementData *data = static_cast<IDCopyLibManagementData *>(cb_data->user_data);
/* Remap self-references to new copied ID. */
if (id == data->id_src) {
2023-07-09 21:22:31 +10:00
/* We cannot use self_id here, it is not *always* id_dst (thanks to confounded node-trees!). */
id = *id_pointer = data->id_dst;
}
/* Increase used IDs refcount if needed and required. */
if ((data->flag & LIB_ID_CREATE_NO_USER_REFCOUNT) == 0 && (cb_flag & IDWALK_CB_USER)) {
if ((data->flag & LIB_ID_CREATE_NO_MAIN) != 0) {
BLI_assert(cb_data->self_id->tag & LIB_TAG_NO_MAIN);
id_us_plus_no_lib(id);
}
else {
id_us_plus(id);
}
}
return IDWALK_RET_NOP;
}
bool BKE_id_copy_is_allowed(const ID *id)
{
#define LIB_ID_TYPES_NOCOPY ID_LI, ID_SCR, ID_WM, ID_WS /* Not supported */
return !ID_TYPE_IS_DEPRECATED(GS(id->name)) && !ELEM(GS(id->name), LIB_ID_TYPES_NOCOPY);
#undef LIB_ID_TYPES_NOCOPY
}
ID *BKE_id_copy_ex(Main *bmain, const ID *id, ID **r_newid, const int flag)
{
ID *newid = (r_newid != nullptr) ? *r_newid : nullptr;
/* Make sure destination pointer is all good. */
if ((flag & LIB_ID_CREATE_NO_ALLOCATE) == 0) {
newid = nullptr;
}
else {
if (newid != nullptr) {
/* Allow some garbage non-initialized memory to go in, and clean it up here. */
const size_t size = BKE_libblock_get_alloc_info(GS(id->name), nullptr);
memset(newid, 0, size);
}
}
/* Early output if source is nullptr. */
if (id == nullptr) {
return nullptr;
}
const IDTypeInfo *idtype_info = BKE_idtype_get_info_from_id(id);
if (idtype_info != nullptr) {
if ((idtype_info->flags & IDTYPE_FLAGS_NO_COPY) != 0) {
return nullptr;
}
BKE_libblock_copy_ex(bmain, id, &newid, flag);
if (idtype_info->copy_data != nullptr) {
idtype_info->copy_data(bmain, newid, id, flag);
}
}
else {
BLI_assert_msg(0, "IDType Missing IDTypeInfo");
}
/* Update ID refcount, remap pointers to self in new ID. */
IDCopyLibManagementData data{};
data.id_src = id;
data.id_dst = newid;
data.flag = flag;
BKE_library_foreach_ID_link(bmain, newid, id_copy_libmanagement_cb, &data, IDWALK_NOP);
/* Do not make new copy local in case we are copying outside of main...
* XXX TODO: is this behavior OK, or should we need own flag to control that? */
if ((flag & LIB_ID_CREATE_NO_MAIN) == 0) {
BLI_assert((flag & LIB_ID_COPY_KEEP_LIB) == 0);
lib_id_copy_ensure_local(bmain, id, newid, 0);
}
else {
newid->lib = id->lib;
}
if (r_newid != nullptr) {
*r_newid = newid;
}
return newid;
}
ID *BKE_id_copy(Main *bmain, const ID *id)
{
return BKE_id_copy_ex(bmain, id, nullptr, LIB_ID_COPY_DEFAULT);
}
ID *BKE_id_copy_for_duplicate(Main *bmain,
ID *id,
const eDupli_ID_Flags duplicate_flags,
const int copy_flags)
{
if (id == nullptr) {
return id;
}
if (id->newid == nullptr) {
const bool do_linked_id = (duplicate_flags & USER_DUP_LINKED_ID) != 0;
if (!(do_linked_id || !ID_IS_LINKED(id))) {
return id;
}
ID *id_new = BKE_id_copy_ex(bmain, id, nullptr, copy_flags);
/* Copying add one user by default, need to get rid of that one. */
id_us_min(id_new);
ID_NEW_SET(id, id_new);
/* Shape keys are always copied with their owner ID, by default. */
ID *key_new = (ID *)BKE_key_from_id(id_new);
ID *key = (ID *)BKE_key_from_id(id);
if (key != nullptr) {
ID_NEW_SET(key, key_new);
}
2023-07-09 21:22:31 +10:00
/* NOTE: embedded data (root node-trees and master collections) should never be referenced by
* anything else, so we do not need to set their newid pointer and flag. */
BKE_animdata_duplicate_id_action(bmain, id_new, duplicate_flags);
if (key_new != nullptr) {
BKE_animdata_duplicate_id_action(bmain, key_new, duplicate_flags);
}
2023-07-09 21:22:31 +10:00
/* Note that actions of embedded data (root node-trees and master collections) are handled
* by #BKE_animdata_duplicate_id_action as well. */
}
return id->newid;
}
static int foreach_assign_id_to_orig_callback(LibraryIDLinkCallbackData *cb_data)
{
ID **id_p = cb_data->id_pointer;
if (*id_p) {
ID *id = *id_p;
*id_p = DEG_get_original_id(id);
/* If the ID changes increase the user count.
*
* This means that the reference to evaluated ID has been changed with a reference to the
* original ID which implies that the user count of the original ID is increased.
*
* The evaluated IDs do not maintain their user counter, so do not change it to avoid issues
* with the user counter going negative. */
if (*id_p != id) {
if ((cb_data->cb_flag & IDWALK_CB_USER) != 0) {
id_us_plus(*id_p);
}
}
}
return IDWALK_RET_NOP;
}
ID *BKE_id_copy_for_use_in_bmain(Main *bmain, const ID *id)
{
ID *newid = BKE_id_copy(bmain, id);
if (newid == nullptr) {
return newid;
}
/* Assign ID references directly used by the given ID to their original complementary parts.
*
* For example, when is called on an evaluated object will assign object->data to its original
* pointer, the evaluated object->data will be kept unchanged. */
BKE_library_foreach_ID_link(
nullptr, newid, foreach_assign_id_to_orig_callback, nullptr, IDWALK_NOP);
/* Shape keys reference on evaluated ID is preserved to keep driver paths available, but the key
* data is likely to be invalid now due to modifiers, so clear the shape key reference avoiding
* any possible shape corruption. */
if (DEG_is_evaluated_id(id)) {
Key **key_p = BKE_key_from_id_p(newid);
if (key_p) {
*key_p = nullptr;
}
}
return newid;
}
static void id_embedded_swap(ID **embedded_id_a,
ID **embedded_id_b,
const bool do_full_id,
IDRemapper *remapper_id_a,
IDRemapper *remapper_id_b);
/**
* Does a mere memory swap over the whole IDs data (including type-specific memory).
* \note Most internal ID data itself is not swapped (only IDProperties are).
*/
static void id_swap(Main *bmain,
ID *id_a,
ID *id_b,
const bool do_full_id,
const bool do_self_remap,
IDRemapper *input_remapper_id_a,
IDRemapper *input_remapper_id_b,
const int self_remap_flags)
{
BLI_assert(GS(id_a->name) == GS(id_b->name));
IDRemapper *remapper_id_a = input_remapper_id_a;
IDRemapper *remapper_id_b = input_remapper_id_b;
if (do_self_remap) {
if (remapper_id_a == nullptr) {
remapper_id_a = BKE_id_remapper_create();
}
if (remapper_id_b == nullptr) {
remapper_id_b = BKE_id_remapper_create();
}
}
const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(id_a);
BLI_assert(id_type != nullptr);
const size_t id_struct_size = id_type->struct_size;
const ID id_a_back = *id_a;
const ID id_b_back = *id_b;
char *id_swap_buff = static_cast<char *>(alloca(id_struct_size));
memcpy(id_swap_buff, id_a, id_struct_size);
memcpy(id_a, id_b, id_struct_size);
memcpy(id_b, id_swap_buff, id_struct_size);
if (!do_full_id) {
/* Restore original ID's internal data. */
*id_a = id_a_back;
*id_b = id_b_back;
/* Exception: IDProperties. */
id_a->properties = id_b_back.properties;
id_b->properties = id_a_back.properties;
/* Exception: recalc flags. */
id_a->recalc = id_b_back.recalc;
id_b->recalc = id_a_back.recalc;
}
id_embedded_swap((ID **)BKE_ntree_ptr_from_id(id_a),
(ID **)BKE_ntree_ptr_from_id(id_b),
do_full_id,
remapper_id_a,
remapper_id_b);
if (GS(id_a->name) == ID_SCE) {
Scene *scene_a = (Scene *)id_a;
Scene *scene_b = (Scene *)id_b;
id_embedded_swap((ID **)&scene_a->master_collection,
(ID **)&scene_b->master_collection,
do_full_id,
remapper_id_a,
remapper_id_b);
}
if (remapper_id_a != nullptr) {
BKE_id_remapper_add(remapper_id_a, id_b, id_a);
}
if (remapper_id_b != nullptr) {
BKE_id_remapper_add(remapper_id_b, id_a, id_b);
}
2023-04-19 08:02:41 +10:00
/* Finalize remapping of internal references to self broken by swapping, if requested. */
if (do_self_remap) {
BKE_libblock_relink_multiple(
bmain, {id_a}, ID_REMAP_TYPE_REMAP, remapper_id_a, self_remap_flags);
BKE_libblock_relink_multiple(
bmain, {id_b}, ID_REMAP_TYPE_REMAP, remapper_id_b, self_remap_flags);
}
if (input_remapper_id_a == nullptr && remapper_id_a != nullptr) {
BKE_id_remapper_free(remapper_id_a);
}
if (input_remapper_id_b == nullptr && remapper_id_b != nullptr) {
BKE_id_remapper_free(remapper_id_b);
}
}
/* Conceptually, embedded IDs are part of their owner's data. However, some parts of the code
2023-04-19 08:02:41 +10:00
* (like e.g. the depsgraph) may treat them as independent IDs, so swapping them here and
* switching their pointers in the owner IDs allows to help not break cached relationships and
* such (by preserving the pointer values). */
static void id_embedded_swap(ID **embedded_id_a,
ID **embedded_id_b,
const bool do_full_id,
IDRemapper *remapper_id_a,
IDRemapper *remapper_id_b)
{
if (embedded_id_a != nullptr && *embedded_id_a != nullptr) {
BLI_assert(embedded_id_b != nullptr);
if (*embedded_id_b == nullptr) {
/* Cannot swap anything if one of the embedded IDs is nullptr. */
return;
}
/* Do not remap internal references to itself here, since embedded IDs pointers also need to be
* potentially remapped in owner ID's data, which will also handle embedded IDs data. */
id_swap(nullptr,
*embedded_id_a,
*embedded_id_b,
do_full_id,
false,
remapper_id_a,
remapper_id_b,
0);
/* Manual 'remap' of owning embedded pointer in owner ID. */
SWAP(ID *, *embedded_id_a, *embedded_id_b);
/* Restore internal pointers to the swapped embedded IDs in their owners' data. This also
* includes the potential self-references inside the embedded IDs themselves. */
if (remapper_id_a != nullptr) {
BKE_id_remapper_add(remapper_id_a, *embedded_id_b, *embedded_id_a);
}
if (remapper_id_b != nullptr) {
BKE_id_remapper_add(remapper_id_b, *embedded_id_a, *embedded_id_b);
}
}
}
void BKE_lib_id_swap(
Main *bmain, ID *id_a, ID *id_b, const bool do_self_remap, const int self_remap_flags)
{
id_swap(bmain, id_a, id_b, false, do_self_remap, nullptr, nullptr, self_remap_flags);
}
void BKE_lib_id_swap_full(
Main *bmain, ID *id_a, ID *id_b, const bool do_self_remap, const int self_remap_flags)
{
id_swap(bmain, id_a, id_b, true, do_self_remap, nullptr, nullptr, self_remap_flags);
}
bool id_single_user(bContext *C, ID *id, PointerRNA *ptr, PropertyRNA *prop)
{
ID *newid = nullptr;
2018-06-17 17:05:51 +02:00
if (id && (ID_REAL_USERS(id) > 1)) {
/* If property isn't editable,
* we're going to have an extra block hanging around until we save. */
if (RNA_property_editable(ptr, prop)) {
Main *bmain = CTX_data_main(C);
/* copy animation actions too */
newid = BKE_id_copy_ex(bmain, id, nullptr, LIB_ID_COPY_DEFAULT | LIB_ID_COPY_ACTIONS);
if (newid != nullptr) {
/* us is 1 by convention with new IDs, but RNA_property_pointer_set
* will also increment it, decrement it here. */
id_us_min(newid);
/* assign copy */
PointerRNA idptr = RNA_id_pointer_create(newid);
RNA_property_pointer_set(ptr, prop, idptr, nullptr);
RNA_property_update(C, ptr, prop);
2018-06-17 17:05:51 +02:00
2019-06-12 09:04:10 +10:00
/* tag grease pencil data-block and disable onion */
if (GS(id->name) == ID_GD_LEGACY) {
DEG_id_tag_update(id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
DEG_id_tag_update(newid, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
bGPdata *gpd = (bGPdata *)newid;
gpd->flag &= ~GP_DATA_SHOW_ONIONSKINS;
}
return true;
}
}
}
2018-06-17 17:05:51 +02:00
return false;
}
static int libblock_management_us_plus(LibraryIDLinkCallbackData *cb_data)
{
ID **id_pointer = cb_data->id_pointer;
const int cb_flag = cb_data->cb_flag;
if (cb_flag & IDWALK_CB_USER) {
id_us_plus(*id_pointer);
}
if (cb_flag & IDWALK_CB_USER_ONE) {
id_us_ensure_real(*id_pointer);
}
return IDWALK_RET_NOP;
}
static int libblock_management_us_min(LibraryIDLinkCallbackData *cb_data)
{
ID **id_pointer = cb_data->id_pointer;
const int cb_flag = cb_data->cb_flag;
if (cb_flag & IDWALK_CB_USER) {
id_us_min(*id_pointer);
}
/* We can do nothing in IDWALK_CB_USER_ONE case! */
return IDWALK_RET_NOP;
}
void BKE_libblock_management_main_add(Main *bmain, void *idv)
{
ID *id = static_cast<ID *>(idv);
BLI_assert(bmain != nullptr);
if ((id->tag & LIB_TAG_NO_MAIN) == 0) {
return;
}
if ((id->tag & LIB_TAG_NOT_ALLOCATED) != 0) {
/* We cannot add non-allocated ID to Main! */
return;
}
/* We cannot allow non-userrefcounting IDs in Main database! */
if ((id->tag & LIB_TAG_NO_USER_REFCOUNT) != 0) {
BKE_library_foreach_ID_link(bmain, id, libblock_management_us_plus, nullptr, IDWALK_NOP);
}
ListBase *lb = which_libbase(bmain, GS(id->name));
BKE_main_lock(bmain);
BLI_addtail(lb, id);
/* We need to allow adding extra datablocks into libraries too, e.g. to support generating new
* overrides for recursive resync. */
BKE_id_new_name_validate(bmain, lb, id, nullptr, true);
/* alphabetic insertion: is in new_id */
id->tag &= ~(LIB_TAG_NO_MAIN | LIB_TAG_NO_USER_REFCOUNT);
bmain->is_memfile_undo_written = false;
BKE_main_unlock(bmain);
BKE_lib_libblock_session_uuid_ensure(id);
}
void BKE_libblock_management_main_remove(Main *bmain, void *idv)
{
ID *id = static_cast<ID *>(idv);
BLI_assert(bmain != nullptr);
if ((id->tag & LIB_TAG_NO_MAIN) != 0) {
return;
}
/* For now, allow userrefcounting IDs to get out of Main - can be handy in some cases... */
ListBase *lb = which_libbase(bmain, GS(id->name));
BKE_main_lock(bmain);
BLI_remlink(lb, id);
BKE_main_namemap_remove_name(bmain, id, id->name + 2);
id->tag |= LIB_TAG_NO_MAIN;
bmain->is_memfile_undo_written = false;
BKE_main_unlock(bmain);
}
void BKE_libblock_management_usercounts_set(Main *bmain, void *idv)
{
ID *id = static_cast<ID *>(idv);
if ((id->tag & LIB_TAG_NO_USER_REFCOUNT) == 0) {
return;
}
BKE_library_foreach_ID_link(bmain, id, libblock_management_us_plus, nullptr, IDWALK_NOP);
id->tag &= ~LIB_TAG_NO_USER_REFCOUNT;
}
void BKE_libblock_management_usercounts_clear(Main *bmain, void *idv)
{
ID *id = static_cast<ID *>(idv);
/* We do not allow IDs in Main database to not be userrefcounting. */
if ((id->tag & LIB_TAG_NO_USER_REFCOUNT) != 0 || (id->tag & LIB_TAG_NO_MAIN) != 0) {
return;
}
BKE_library_foreach_ID_link(bmain, id, libblock_management_us_min, nullptr, IDWALK_NOP);
id->tag |= LIB_TAG_NO_USER_REFCOUNT;
}
void BKE_main_id_tag_listbase(ListBase *lb, const int tag, const bool value)
{
ID *id;
if (value) {
for (id = static_cast<ID *>(lb->first); id; id = static_cast<ID *>(id->next)) {
id->tag |= tag;
}
}
else {
const int ntag = ~tag;
for (id = static_cast<ID *>(lb->first); id; id = static_cast<ID *>(id->next)) {
id->tag &= ntag;
}
}
}
void BKE_main_id_tag_idcode(Main *mainvar, const short type, const int tag, const bool value)
{
ListBase *lb = which_libbase(mainvar, type);
BKE_main_id_tag_listbase(lb, tag, value);
}
void BKE_main_id_tag_all(Main *mainvar, const int tag, const bool value)
{
ListBase *lbarray[INDEX_ID_MAX];
int a;
a = set_listbasepointers(mainvar, lbarray);
while (a--) {
BKE_main_id_tag_listbase(lbarray[a], tag, value);
}
}
void BKE_main_id_flag_listbase(ListBase *lb, const int flag, const bool value)
{
ID *id;
if (value) {
for (id = static_cast<ID *>(lb->first); id; id = static_cast<ID *>(id->next)) {
id->tag |= flag;
}
}
else {
const int nflag = ~flag;
for (id = static_cast<ID *>(lb->first); id; id = static_cast<ID *>(id->next)) {
id->tag &= nflag;
}
}
}
void BKE_main_id_flag_all(Main *bmain, const int flag, const bool value)
{
ListBase *lbarray[INDEX_ID_MAX];
int a;
a = set_listbasepointers(bmain, lbarray);
while (a--) {
BKE_main_id_flag_listbase(lbarray[a], flag, value);
}
}
void BKE_main_id_repair_duplicate_names_listbase(Main *bmain, ListBase *lb)
{
int lb_len = 0;
LISTBASE_FOREACH (ID *, id, lb) {
if (!ID_IS_LINKED(id)) {
lb_len += 1;
}
}
if (lb_len <= 1) {
return;
}
/* Fill an array because renaming sorts. */
ID **id_array = static_cast<ID **>(MEM_mallocN(sizeof(*id_array) * lb_len, __func__));
GSet *gset = BLI_gset_str_new_ex(__func__, lb_len);
int i = 0;
LISTBASE_FOREACH (ID *, id, lb) {
if (!ID_IS_LINKED(id)) {
id_array[i] = id;
i++;
}
}
for (i = 0; i < lb_len; i++) {
if (!BLI_gset_add(gset, id_array[i]->name + 2)) {
BKE_id_new_name_validate(bmain, lb, id_array[i], nullptr, false);
}
}
BLI_gset_free(gset, nullptr);
MEM_freeN(id_array);
}
void BKE_main_lib_objects_recalc_all(Main *bmain)
{
Object *ob;
/* flag for full recalc */
for (ob = static_cast<Object *>(bmain->objects.first); ob;
ob = static_cast<Object *>(ob->id.next)) {
if (ID_IS_LINKED(ob)) {
DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION);
}
}
Fix T38054: High CPU usage with many objects This is a regression since threaded dependency graph landed to master. Root of the issue goes to the loads of graph preparation being done even if there's nothing to be updated. The idea of this change is to use ID type recalc bits to determine whether there're objects to be updated. Generally speaking, we now check object and object data datablocks with DAG_id_type_tagged() and if there's no such IDs tagged we skip the whole task pool creation and so, The only difficult aspect was that in some circumstances it was possible that there are tagged objects but nothing in ID recalc bit fields. There were several different circumstances when it was possible: * When one assigns object->recalc flag directly DAG flush didn't set corresponding bits to ID recalc bits. Partially it is fixed by making it so flush will set bitfield, but also for object types there's no reason to assign recalc flag directly. Using generic DAG_id_type_tag works almost the same fast as direct assignment, ensures all the bitflags are set properly and for the long run it seems it's what we would actually want to. * DAG_on_visible_update() didn't set recalc bits at all. * Some areas were checking for object->recalc != 0, however it is was possible that object recalc flag contains PSYS_RECALC_CHILD which was never cleaned from there. No idea why would we need to assign such a flag when enabling scene simplification, this is to be investigated separately. * It is possible that scene_update_post and frame_update_post handlers will modify objects. The issue is that DAG_ids_clear_recalc is called just after callbacks, which leaves objects with recalc flags but no corresponding bit in ID recalc bitfield. This leads to some kind of regression when using ID type tag fields to check whether there objects to be updated internally comparing threaded DAG with legacy one. For now let's have a workaround which will preserve tag for ID_OB if there're objects with OB_RECALC_ALL bits. This keeps behavior unchanged comparing with 2.69 release.
2014-01-09 19:13:26 +06:00
DEG_id_type_tag(bmain, ID_OB);
}
/* *********** ALLOC AND FREE *****************
*
* BKE_libblock_free(ListBase *lb, ID *id )
2019-06-12 09:04:10 +10:00
* provide a list-basis and data-block, but only ID is read
*
* void *BKE_libblock_alloc(ListBase *lb, type, name)
* inserts in list and returns a new ID
*
* **************************** */
2002-10-12 11:37:38 +00:00
size_t BKE_libblock_get_alloc_info(short type, const char **name)
{
const IDTypeInfo *id_type = BKE_idtype_get_info_from_idcode(type);
if (id_type == nullptr) {
if (name != nullptr) {
*name = nullptr;
}
return 0;
}
if (name != nullptr) {
*name = id_type->name;
}
return id_type->struct_size;
}
void *BKE_libblock_alloc_notest(short type)
2002-10-12 11:37:38 +00:00
{
const char *name;
size_t size = BKE_libblock_get_alloc_info(type, &name);
if (size != 0) {
return MEM_callocN(size, name);
2002-10-12 11:37:38 +00:00
}
BLI_assert_msg(0, "Request to allocate unknown data type");
return nullptr;
2002-10-12 11:37:38 +00:00
}
void *BKE_libblock_alloc(Main *bmain, short type, const char *name, const int flag)
2002-10-12 11:37:38 +00:00
{
BLI_assert((flag & LIB_ID_CREATE_NO_ALLOCATE) == 0);
BLI_assert((flag & LIB_ID_CREATE_NO_MAIN) != 0 || bmain != nullptr);
BLI_assert((flag & LIB_ID_CREATE_NO_MAIN) != 0 || (flag & LIB_ID_CREATE_LOCAL) == 0);
ID *id = static_cast<ID *>(BKE_libblock_alloc_notest(type));
if (id) {
if ((flag & LIB_ID_CREATE_NO_MAIN) != 0) {
id->tag |= LIB_TAG_NO_MAIN;
}
if ((flag & LIB_ID_CREATE_NO_USER_REFCOUNT) != 0) {
id->tag |= LIB_TAG_NO_USER_REFCOUNT;
}
if (flag & LIB_ID_CREATE_LOCAL) {
id->tag |= LIB_TAG_LOCALIZED;
}
id->icon_id = 0;
*((short *)id->name) = type;
2017-08-09 14:38:07 +02:00
if ((flag & LIB_ID_CREATE_NO_USER_REFCOUNT) == 0) {
id->us = 1;
}
if ((flag & LIB_ID_CREATE_NO_MAIN) == 0) {
/* Note that 2.8x versioning has tested not to cause conflicts. Node trees are
* skipped in this check to allow adding a geometry node tree for versioning. */
BLI_assert(bmain->is_locked_for_linking == false || ELEM(type, ID_WS, ID_GR, ID_NT));
ListBase *lb = which_libbase(bmain, type);
BKE_main_lock(bmain);
BLI_addtail(lb, id);
BKE_id_new_name_validate(bmain, lb, id, name, false);
bmain->is_memfile_undo_written = false;
/* alphabetic insertion: is in new_id */
BKE_main_unlock(bmain);
/* This assert avoids having to keep name_map consistency when changing the library of an ID,
* if this check is not true anymore it will have to be done here too. */
BLI_assert(bmain->curlib == nullptr || bmain->curlib->runtime.name_map == nullptr);
/* This is important in 'readfile doversion after liblink' context mainly, but is a good
* consistency change in general: ID created for a Main should get that main's current
* library pointer. */
id->lib = bmain->curlib;
/* TODO: to be removed from here! */
if ((flag & LIB_ID_CREATE_NO_DEG_TAG) == 0) {
DEG_id_type_tag(bmain, type);
}
}
else {
BLI_strncpy(id->name + 2, name, sizeof(id->name) - 2);
}
/* We also need to ensure a valid `session_uuid` for some non-main data (like embedded IDs).
* IDs not allocated however should not need those (this would e.g. avoid generating session
* uuids for depsgraph CoW IDs, if it was using this function). */
if ((flag & LIB_ID_CREATE_NO_ALLOCATE) == 0) {
BKE_lib_libblock_session_uuid_ensure(id);
}
2002-10-12 11:37:38 +00:00
}
2002-10-12 11:37:38 +00:00
return id;
}
void BKE_libblock_init_empty(ID *id)
{
const IDTypeInfo *idtype_info = BKE_idtype_get_info_from_id(id);
if (idtype_info != nullptr) {
if (idtype_info->init_data != nullptr) {
idtype_info->init_data(id);
}
return;
}
BLI_assert_msg(0, "IDType Missing IDTypeInfo");
}
void BKE_libblock_runtime_reset_remapping_status(ID *id)
{
id->runtime.remap.status = 0;
id->runtime.remap.skipped_refcounted = 0;
id->runtime.remap.skipped_direct = 0;
id->runtime.remap.skipped_indirect = 0;
}
/* ********** ID session-wise UUID management. ********** */
static uint global_session_uuid = 0;
void BKE_lib_libblock_session_uuid_ensure(ID *id)
{
if (id->session_uuid == MAIN_ID_SESSION_UUID_UNSET) {
BLI_assert((id->tag & LIB_TAG_TEMP_MAIN) == 0); /* Caller must ensure this. */
id->session_uuid = atomic_add_and_fetch_uint32(&global_session_uuid, 1);
/* In case overflow happens, still assign a valid ID. This way opening files many times works
* correctly. */
if (UNLIKELY(id->session_uuid == MAIN_ID_SESSION_UUID_UNSET)) {
id->session_uuid = atomic_add_and_fetch_uint32(&global_session_uuid, 1);
}
}
}
void BKE_lib_libblock_session_uuid_renew(ID *id)
{
id->session_uuid = MAIN_ID_SESSION_UUID_UNSET;
BKE_lib_libblock_session_uuid_ensure(id);
}
void *BKE_id_new(Main *bmain, const short type, const char *name)
{
BLI_assert(bmain != nullptr);
if (name == nullptr) {
name = DATA_(BKE_idtype_idcode_to_name(type));
}
ID *id = static_cast<ID *>(BKE_libblock_alloc(bmain, type, name, 0));
BKE_libblock_init_empty(id);
return id;
}
void *BKE_id_new_nomain(const short type, const char *name)
{
if (name == nullptr) {
name = DATA_(BKE_idtype_idcode_to_name(type));
}
ID *id = static_cast<ID *>(BKE_libblock_alloc(
nullptr,
type,
name,
LIB_ID_CREATE_NO_MAIN | LIB_ID_CREATE_NO_USER_REFCOUNT | LIB_ID_CREATE_NO_DEG_TAG));
BKE_libblock_init_empty(id);
return id;
}
void BKE_libblock_copy_ex(Main *bmain, const ID *id, ID **r_newid, const int orig_flag)
{
ID *new_id = *r_newid;
int flag = orig_flag;
const bool is_embedded_id = (id->flag & LIB_EMBEDDED_DATA) != 0;
BLI_assert((flag & LIB_ID_CREATE_NO_MAIN) != 0 || bmain != nullptr);
BLI_assert((flag & LIB_ID_CREATE_NO_MAIN) != 0 || (flag & LIB_ID_CREATE_NO_ALLOCATE) == 0);
BLI_assert((flag & LIB_ID_CREATE_NO_MAIN) != 0 || (flag & LIB_ID_CREATE_LOCAL) == 0);
/* Embedded ID handling.
*
* NOTE: This makes copying code of embedded IDs non-reentrant (i.e. copying an embedded ID as
* part of another embedded ID would not work properly). This is not an issue currently, but may
* need to be addressed in the future. */
if ((bmain != nullptr) && is_embedded_id) {
flag |= LIB_ID_CREATE_NO_MAIN;
}
/* The id->flag bits to copy over. */
const int copy_idflag_mask = LIB_EMBEDDED_DATA;
if ((flag & LIB_ID_CREATE_NO_ALLOCATE) != 0) {
/* r_newid already contains pointer to allocated memory. */
/* TODO: do we want to memset(0) whole mem before filling it? */
2023-05-09 12:50:37 +10:00
STRNCPY(new_id->name, id->name);
new_id->us = 0;
new_id->tag |= LIB_TAG_NOT_ALLOCATED | LIB_TAG_NO_MAIN | LIB_TAG_NO_USER_REFCOUNT;
/* TODO: Do we want/need to copy more from ID struct itself? */
}
else {
new_id = static_cast<ID *>(BKE_libblock_alloc(bmain, GS(id->name), id->name + 2, flag));
}
BLI_assert(new_id != nullptr);
if ((flag & LIB_ID_COPY_SET_COPIED_ON_WRITE) != 0) {
new_id->tag |= LIB_TAG_COPIED_ON_WRITE;
}
else {
new_id->tag &= ~LIB_TAG_COPIED_ON_WRITE;
}
const size_t id_len = BKE_libblock_get_alloc_info(GS(new_id->name), nullptr);
const size_t id_offset = sizeof(ID);
if (int(id_len) - int(id_offset) > 0) { /* signed to allow neg result */ /* XXX ????? */
const char *cp = (const char *)id;
char *cpn = (char *)new_id;
memcpy(cpn + id_offset, cp + id_offset, id_len - id_offset);
}
new_id->flag = (new_id->flag & ~copy_idflag_mask) | (id->flag & copy_idflag_mask);
/* 'Private ID' data handling. */
if (is_embedded_id && (orig_flag & LIB_ID_CREATE_NO_MAIN) == 0) {
new_id->tag &= ~LIB_TAG_NO_MAIN;
}
2022-09-16 18:13:19 +10:00
/* We do not want any handling of user-count in code duplicating the data here, we do that all
* at once in id_copy_libmanagement_cb() at the end. */
const int copy_data_flag = orig_flag | LIB_ID_CREATE_NO_USER_REFCOUNT;
if (id->properties) {
new_id->properties = IDP_CopyProperty_ex(id->properties, copy_data_flag);
}
/* This is never duplicated, only one existing ID should have a given weak ref to library/ID. */
new_id->library_weak_reference = nullptr;
if ((orig_flag & LIB_ID_COPY_NO_LIB_OVERRIDE) == 0) {
if (ID_IS_OVERRIDE_LIBRARY_REAL(id)) {
/* We do not want to copy existing override rules here, as they would break the proper
* remapping between IDs. Proper overrides rules will be re-generated anyway. */
BKE_lib_override_library_copy(new_id, id, false);
}
else if (ID_IS_OVERRIDE_LIBRARY_VIRTUAL(id)) {
/* Just ensure virtual overrides do get properly tagged, there is not actual override data to
* copy here. */
new_id->flag |= LIB_EMBEDDED_DATA_LIB_OVERRIDE;
}
}
if (id_can_have_animdata(new_id)) {
IdAdtTemplate *iat = (IdAdtTemplate *)new_id;
/* the duplicate should get a copy of the animdata */
if ((flag & LIB_ID_COPY_NO_ANIMDATA) == 0) {
2023-07-09 21:22:31 +10:00
/* Note that even though horrors like root node-trees are not in bmain, the actions they use
2019-09-19 13:18:52 +10:00
* in their anim data *are* in bmain... super-mega-hooray. */
BLI_assert((copy_data_flag & LIB_ID_COPY_ACTIONS) == 0 ||
(copy_data_flag & LIB_ID_CREATE_NO_MAIN) == 0);
iat->adt = BKE_animdata_copy(bmain, iat->adt, copy_data_flag);
}
else {
iat->adt = nullptr;
}
}
if ((flag & LIB_ID_CREATE_NO_DEG_TAG) == 0 && (flag & LIB_ID_CREATE_NO_MAIN) == 0) {
2017-08-07 20:48:22 +02:00
DEG_id_type_tag(bmain, GS(new_id->name));
}
*r_newid = new_id;
}
void *BKE_libblock_copy(Main *bmain, const ID *id)
2002-10-12 11:37:38 +00:00
{
ID *idn;
BKE_libblock_copy_ex(bmain, id, &idn, 0);
2002-10-12 11:37:38 +00:00
return idn;
}
/* ***************** ID ************************ */
ID *BKE_libblock_find_name(Main *bmain, const short type, const char *name)
2002-10-12 11:37:38 +00:00
{
ListBase *lb = which_libbase(bmain, type);
BLI_assert(lb != nullptr);
return static_cast<ID *>(BLI_findstring(lb, name, offsetof(ID, name) + 2));
2002-10-12 11:37:38 +00:00
}
ID *BKE_libblock_find_session_uuid(Main *bmain, const short type, const uint32_t session_uuid)
{
ListBase *lb = which_libbase(bmain, type);
BLI_assert(lb != nullptr);
LISTBASE_FOREACH (ID *, id, lb) {
if (id->session_uuid == session_uuid) {
return id;
}
}
return nullptr;
}
void id_sort_by_name(ListBase *lb, ID *id, ID *id_sorting_hint)
2002-10-12 11:37:38 +00:00
{
#define ID_SORT_STEP_SIZE 512
2002-10-12 11:37:38 +00:00
ID *idtest;
2018-06-17 17:05:51 +02:00
2012-05-06 17:22:54 +00:00
/* insert alphabetically */
if (lb->first == lb->last) {
return;
}
BLI_remlink(lb, id);
/* Check if we can actually insert id before or after id_sorting_hint, if given. */
if (!ELEM(id_sorting_hint, nullptr, id) && id_sorting_hint->lib == id->lib) {
BLI_assert(BLI_findindex(lb, id_sorting_hint) >= 0);
ID *id_sorting_hint_next = static_cast<ID *>(id_sorting_hint->next);
if (BLI_strcasecmp(id_sorting_hint->name, id->name) < 0 &&
(id_sorting_hint_next == nullptr || id_sorting_hint_next->lib != id->lib ||
BLI_strcasecmp(id_sorting_hint_next->name, id->name) > 0))
{
BLI_insertlinkafter(lb, id_sorting_hint, id);
return;
}
ID *id_sorting_hint_prev = static_cast<ID *>(id_sorting_hint->prev);
if (BLI_strcasecmp(id_sorting_hint->name, id->name) > 0 &&
(id_sorting_hint_prev == nullptr || id_sorting_hint_prev->lib != id->lib ||
BLI_strcasecmp(id_sorting_hint_prev->name, id->name) < 0))
{
BLI_insertlinkbefore(lb, id_sorting_hint, id);
return;
ID Management: Improve speed of code used when creating/renaming and ID. This alone can make e.g. adding 40k IDs with default name (i.e. 'fully constant' case in table below) about 15-20% faster: | Number and type of names of IDs | old code | new code | speed improvement | | -------------------------------- | -------- | -------- | ----------------- | | 40K, mixed (14k rand, 26k const) | 75s | 62s | 21% | | 40K, fully random | 90s | 76s | 18% | | 40K, fully constant | 94s | 77s | 22% | Idea is to use a first pass, where we just check one item every nth ones, until we have found the chunk in which we want to insert the new item, then do a basic linear comparison with all items in that chunk as before. Also, list is now walked backward, as in most common 'massive insertion' cases new ID's names have higher number, hence ends up towards the end of the list. This new sorting function can be between a few percents and 50% quicker than original code, depending on various factors (like length of common parts of ID names, whether new IDs should be added towards the end or not, how high in numbering we are, etc.). Note: another, full bisecting approach was also tried, which gives a massive reduction of comparisons (from n items to log2(n)), but it only gave minor improvements of speed from original fully linear code, while adding a fair amount of complexity to the logic. Only case it was shining was the unlikely 'very long, almost similar ID names' case (since `strcasecmp()` is rather costly then).
2019-12-13 19:18:01 +01:00
}
}
ID Management: Improve speed of code used when creating/renaming and ID. This alone can make e.g. adding 40k IDs with default name (i.e. 'fully constant' case in table below) about 15-20% faster: | Number and type of names of IDs | old code | new code | speed improvement | | -------------------------------- | -------- | -------- | ----------------- | | 40K, mixed (14k rand, 26k const) | 75s | 62s | 21% | | 40K, fully random | 90s | 76s | 18% | | 40K, fully constant | 94s | 77s | 22% | Idea is to use a first pass, where we just check one item every nth ones, until we have found the chunk in which we want to insert the new item, then do a basic linear comparison with all items in that chunk as before. Also, list is now walked backward, as in most common 'massive insertion' cases new ID's names have higher number, hence ends up towards the end of the list. This new sorting function can be between a few percents and 50% quicker than original code, depending on various factors (like length of common parts of ID names, whether new IDs should be added towards the end or not, how high in numbering we are, etc.). Note: another, full bisecting approach was also tried, which gives a massive reduction of comparisons (from n items to log2(n)), but it only gave minor improvements of speed from original fully linear code, while adding a fair amount of complexity to the logic. Only case it was shining was the unlikely 'very long, almost similar ID names' case (since `strcasecmp()` is rather costly then).
2019-12-13 19:18:01 +01:00
void *item_array[ID_SORT_STEP_SIZE];
int item_array_index;
/* Step one: We go backward over a whole chunk of items at once, until we find a limit item
* that is lower than, or equal (should never happen!) to the one we want to insert. */
/* NOTE: We start from the end, because in typical 'heavy' case (insertion of lots of IDs at
* once using the same base name), newly inserted items will generally be towards the end
* (higher extension numbers). */
bool is_in_library = false;
item_array_index = ID_SORT_STEP_SIZE - 1;
for (idtest = static_cast<ID *>(lb->last); idtest != nullptr;
idtest = static_cast<ID *>(idtest->prev))
{
if (is_in_library) {
if (idtest->lib != id->lib) {
/* We got out of expected library 'range' in the list, so we are done here and can move on
* to the next step. */
break;
}
}
else if (idtest->lib == id->lib) {
/* We are entering the expected library 'range' of IDs in the list. */
is_in_library = true;
}
if (!is_in_library) {
continue;
}
item_array[item_array_index] = idtest;
if (item_array_index == 0) {
if (BLI_strcasecmp(idtest->name, id->name) <= 0) {
break;
}
item_array_index = ID_SORT_STEP_SIZE;
}
item_array_index--;
}
/* Step two: we go forward in the selected chunk of items and check all of them, as we know
* that our target is in there. */
/* If we reached start of the list, current item_array_index is off-by-one.
* Otherwise, we already know that it points to an item lower-or-equal-than the one we want to
* insert, no need to redo the check for that one.
* So we can increment that index in any case. */
for (item_array_index++; item_array_index < ID_SORT_STEP_SIZE; item_array_index++) {
idtest = static_cast<ID *>(item_array[item_array_index]);
if (BLI_strcasecmp(idtest->name, id->name) > 0) {
BLI_insertlinkbefore(lb, idtest, id);
break;
}
}
if (item_array_index == ID_SORT_STEP_SIZE) {
if (idtest == nullptr) {
/* If idtest is nullptr here, it means that in the first loop, the last comparison was
* performed exactly on the first item of the list, and that it also failed. And that the
* second loop was not walked at all.
*
* In other words, if `id` is local, all the items in the list are greater than the inserted
* one, so we can put it at the start of the list. Or, if `id` is linked, it is the first one
* of its library, and we can put it at the very end of the list. */
if (ID_IS_LINKED(id)) {
BLI_addtail(lb, id);
}
else {
BLI_addhead(lb, id);
}
}
else {
BLI_insertlinkafter(lb, idtest, id);
}
}
ID Management: Improve speed of code used when creating/renaming and ID. This alone can make e.g. adding 40k IDs with default name (i.e. 'fully constant' case in table below) about 15-20% faster: | Number and type of names of IDs | old code | new code | speed improvement | | -------------------------------- | -------- | -------- | ----------------- | | 40K, mixed (14k rand, 26k const) | 75s | 62s | 21% | | 40K, fully random | 90s | 76s | 18% | | 40K, fully constant | 94s | 77s | 22% | Idea is to use a first pass, where we just check one item every nth ones, until we have found the chunk in which we want to insert the new item, then do a basic linear comparison with all items in that chunk as before. Also, list is now walked backward, as in most common 'massive insertion' cases new ID's names have higher number, hence ends up towards the end of the list. This new sorting function can be between a few percents and 50% quicker than original code, depending on various factors (like length of common parts of ID names, whether new IDs should be added towards the end or not, how high in numbering we are, etc.). Note: another, full bisecting approach was also tried, which gives a massive reduction of comparisons (from n items to log2(n)), but it only gave minor improvements of speed from original fully linear code, while adding a fair amount of complexity to the logic. Only case it was shining was the unlikely 'very long, almost similar ID names' case (since `strcasecmp()` is rather costly then).
2019-12-13 19:18:01 +01:00
#undef ID_SORT_STEP_SIZE
}
2002-10-12 11:37:38 +00:00
bool BKE_id_new_name_validate(
Main *bmain, ListBase *lb, ID *id, const char *tname, const bool do_linked_data)
{
bool result = false;
2012-05-06 17:22:54 +00:00
char name[MAX_ID_NAME - 2];
2021-06-02 17:04:21 +10:00
/* If library, don't rename (unless explicitly required), but do ensure proper sorting. */
if (!do_linked_data && ID_IS_LINKED(id)) {
id_sort_by_name(lb, id, nullptr);
return result;
}
2023-04-14 14:23:31 +02:00
/* If no name given, use name of current ID. */
if (tname == nullptr) {
2012-05-06 17:22:54 +00:00
tname = id->name + 2;
}
2023-04-14 14:23:31 +02:00
/* Make a copy of given name (tname args can be const). */
2023-05-09 12:50:37 +10:00
STRNCPY(name, tname);
if (name[0] == '\0') {
/* Disallow empty names. */
STRNCPY_UTF8(name, DATA_(BKE_idtype_idcode_to_name(GS(id->name))));
}
else {
/* disallow non utf8 chars,
2012-03-18 07:38:51 +00:00
* the interface checks for this but new ID's based on file names don't */
BLI_str_utf8_invalid_strip(name, strlen(name));
}
result = BKE_main_namemap_get_name(bmain, id, name, false);
2018-06-17 17:05:51 +02:00
2023-06-19 20:06:55 +10:00
BLI_strncpy(id->name + 2, name, sizeof(id->name) - 2);
id_sort_by_name(lb, id, nullptr);
return result;
}
void BKE_main_id_newptr_and_tag_clear(Main *bmain)
2002-10-12 11:37:38 +00:00
{
ID *id;
FOREACH_MAIN_ID_BEGIN (bmain, id) {
BKE_id_newptr_and_tag_clear(id);
2002-10-12 11:37:38 +00:00
}
FOREACH_MAIN_ID_END;
2002-10-12 11:37:38 +00:00
}
static int id_refcount_recompute_callback(LibraryIDLinkCallbackData *cb_data)
{
ID **id_pointer = cb_data->id_pointer;
const int cb_flag = cb_data->cb_flag;
const bool do_linked_only = bool(POINTER_AS_INT(cb_data->user_data));
if (*id_pointer == nullptr) {
return IDWALK_RET_NOP;
}
if (do_linked_only && !ID_IS_LINKED(*id_pointer)) {
return IDWALK_RET_NOP;
}
if (cb_flag & IDWALK_CB_USER) {
/* Do not touch to direct/indirect linked status here... */
id_us_plus_no_lib(*id_pointer);
}
if (cb_flag & IDWALK_CB_USER_ONE) {
id_us_ensure_real(*id_pointer);
}
return IDWALK_RET_NOP;
}
void BKE_main_id_refcount_recompute(Main *bmain, const bool do_linked_only)
{
ID *id;
FOREACH_MAIN_ID_BEGIN (bmain, id) {
if (!ID_IS_LINKED(id) && do_linked_only) {
continue;
}
id->us = ID_FAKE_USERS(id);
/* Note that we keep EXTRAUSER tag here, since some UI users may define it too... */
if (id->tag & LIB_TAG_EXTRAUSER) {
id->tag &= ~(LIB_TAG_EXTRAUSER | LIB_TAG_EXTRAUSER_SET);
id_us_ensure_real(id);
}
if (ELEM(GS(id->name), ID_SCE, ID_WM, ID_WS)) {
/* These IDs should always have a 'virtual' user. */
id_us_ensure_real(id);
}
}
FOREACH_MAIN_ID_END;
2022-09-16 18:13:19 +10:00
/* Go over whole Main database to re-generate proper user-counts. */
FOREACH_MAIN_ID_BEGIN (bmain, id) {
BKE_library_foreach_ID_link(bmain,
id,
id_refcount_recompute_callback,
POINTER_FROM_INT(int(do_linked_only)),
IDWALK_READONLY | IDWALK_INCLUDE_UI);
}
FOREACH_MAIN_ID_END;
}
static void library_make_local_copying_check(ID *id,
GSet *loop_tags,
MainIDRelations *id_relations,
GSet *done_ids)
{
if (BLI_gset_haskey(done_ids, id)) {
return; /* Already checked, nothing else to do. */
}
MainIDRelationsEntry *entry = static_cast<MainIDRelationsEntry *>(
BLI_ghash_lookup(id_relations->relations_from_pointers, id));
BLI_gset_insert(loop_tags, id);
for (MainIDRelationsEntryItem *from_id_entry = entry->from_ids; from_id_entry != nullptr;
from_id_entry = from_id_entry->next)
{
/* Our oh-so-beloved 'from' pointers... Those should always be ignored here, since the actual
* relation we want to check is in the other way around. */
if (from_id_entry->usage_flag & IDWALK_CB_LOOPBACK) {
continue;
}
ID *from_id = from_id_entry->id_pointer.from;
2020-06-13 12:50:07 +10:00
/* Shape-keys are considered 'private' to their owner ID here, and never tagged
* (since they cannot be linked), so we have to switch effective parent to their owner.
*/
if (GS(from_id->name) == ID_KE) {
from_id = ((Key *)from_id)->from;
}
if (!ID_IS_LINKED(from_id)) {
/* Local user, early out to avoid some gset querying... */
continue;
}
if (!BLI_gset_haskey(done_ids, from_id)) {
if (BLI_gset_haskey(loop_tags, from_id)) {
/* We are in a 'dependency loop' of IDs, this does not say us anything, skip it.
2022-11-02 10:14:37 +11:00
* Note that this is the situation that can lead to archipelagos of linked data-blocks
* (since all of them have non-local users, they would all be duplicated,
* leading to a loop of unused linked data-blocks that cannot be freed since they all use
* each other...). */
continue;
}
/* Else, recursively check that user ID. */
library_make_local_copying_check(from_id, loop_tags, id_relations, done_ids);
}
if (from_id->tag & LIB_TAG_DOIT) {
/* This user will be fully local in future, so far so good,
* nothing to do here but check next user. */
}
else {
/* This user won't be fully local in future, so current ID won't be either.
* And we are done checking it. */
id->tag &= ~LIB_TAG_DOIT;
break;
}
}
BLI_gset_add(done_ids, id);
BLI_gset_remove(loop_tags, id, nullptr);
}
void BKE_library_make_local(Main *bmain,
const Library *lib,
GHash *old_to_new_ids,
const bool untagged_only,
const bool set_fake)
2002-10-12 11:37:38 +00:00
{
/* NOTE: Old (2.77) version was simply making (tagging) data-blocks as local,
* without actually making any check whether they were also indirectly used or not...
*
* Current version uses regular id_make_local callback, with advanced pre-processing step to
* detect all cases of IDs currently indirectly used, but which will be used by local data only
* once this function is finished. This allows to avoid any unneeded duplication of IDs, and
* hence all time lost afterwards to remove orphaned linked data-blocks. */
ListBase *lbarray[INDEX_ID_MAX];
2002-10-12 11:37:38 +00:00
LinkNode *todo_ids = nullptr;
LinkNode *copied_ids = nullptr;
MemArena *linklist_mem = BLI_memarena_new(512 * sizeof(*todo_ids), __func__);
GSet *done_ids = BLI_gset_ptr_new(__func__);
#ifdef DEBUG_TIME
TIMEIT_START(make_local);
#endif
BKE_main_relations_create(bmain, 0);
#ifdef DEBUG_TIME
printf("Pre-compute current ID relations: Done.\n");
TIMEIT_VALUE_PRINT(make_local);
#endif
2019-06-12 09:04:10 +10:00
/* Step 1: Detect data-blocks to make local. */
for (int a = set_listbasepointers(bmain, lbarray); a--;) {
ID *id = static_cast<ID *>(lbarray[a]->first);
/* Do not explicitly make local non-linkable IDs (shape-keys, in fact),
2019-06-12 09:04:10 +10:00
* they are assumed to be handled by real data-blocks responsible of them. */
const bool do_skip = (id && !BKE_idtype_idcode_is_linkable(GS(id->name)));
for (; id; id = static_cast<ID *>(id->next)) {
ID *ntree = (ID *)ntreeFromID(id);
id->tag &= ~LIB_TAG_DOIT;
if (ntree != nullptr) {
ntree->tag &= ~LIB_TAG_DOIT;
}
if (!ID_IS_LINKED(id)) {
id->tag &= ~(LIB_TAG_EXTERN | LIB_TAG_INDIRECT | LIB_TAG_NEW);
id->flag &= ~LIB_INDIRECT_WEAK_LINK;
if (ID_IS_OVERRIDE_LIBRARY_REAL(id) &&
ELEM(lib, nullptr, id->override_library->reference->lib) &&
((untagged_only == false) || !(id->tag & LIB_TAG_PRE_EXISTING)))
{
/* Validating liboverride hierarchy root pointers will happen later in this function,
* rather than doing it for each and every localized ID. */
BKE_lib_override_library_make_local(nullptr, id);
}
}
/* The check on the fourth line (LIB_TAG_PRE_EXISTING) is done so it's possible to tag data
* you don't want to be made local, used for appending data,
* so any libdata already linked won't become local (very nasty
* to discover all your links are lost after appending).
* Also, never ever make proxified objects local, would not make any sense. */
/* Some more notes:
* - Shape-keys are never tagged here (since they are not linkable).
* - Node-trees used in materials etc. have to be tagged manually,
* since they do not exist in Main (!).
* This is ok-ish on 'make local' side of things
* (since those are handled by their 'owner' IDs),
* but complicates slightly the pre-processing of relations between IDs at step 2... */
else if (!do_skip && id->tag & (LIB_TAG_EXTERN | LIB_TAG_INDIRECT | LIB_TAG_NEW) &&
ELEM(lib, nullptr, id->lib) &&
((untagged_only == false) || !(id->tag & LIB_TAG_PRE_EXISTING)))
{
BLI_linklist_prepend_arena(&todo_ids, id, linklist_mem);
id->tag |= LIB_TAG_DOIT;
2023-07-09 21:22:31 +10:00
/* Tag those nasty non-ID node-trees,
* but do not add them to todo list, making them local is handled by 'owner' ID.
* This is needed for library_make_local_copying_check() to work OK at step 2. */
if (ntree != nullptr) {
ntree->tag |= LIB_TAG_DOIT;
}
}
else {
/* Linked ID that we won't be making local (needed info for step 2, see below). */
BLI_gset_add(done_ids, id);
}
}
}
#ifdef DEBUG_TIME
2019-06-12 09:04:10 +10:00
printf("Step 1: Detect data-blocks to make local: Done.\n");
TIMEIT_VALUE_PRINT(make_local);
#endif
2019-06-12 09:04:10 +10:00
/* Step 2: Check which data-blocks we can directly make local
* (because they are only used by already, or future, local data),
* others will need to be duplicated. */
GSet *loop_tags = BLI_gset_ptr_new(__func__);
for (LinkNode *it = todo_ids; it; it = it->next) {
library_make_local_copying_check(
static_cast<ID *>(it->link), loop_tags, bmain->relations, done_ids);
BLI_assert(BLI_gset_len(loop_tags) == 0);
}
BLI_gset_free(loop_tags, nullptr);
BLI_gset_free(done_ids, nullptr);
/* Next step will most likely add new IDs, better to get rid of this mapping now. */
BKE_main_relations_free(bmain);
#ifdef DEBUG_TIME
2019-06-12 09:04:10 +10:00
printf("Step 2: Check which data-blocks we can directly make local: Done.\n");
TIMEIT_VALUE_PRINT(make_local);
#endif
/* Step 3: Make IDs local, either directly (quick and simple), or using generic process,
* which involves more complex checks and might instead
* create a local copy of original linked ID. */
for (LinkNode *it = todo_ids, *it_next; it; it = it_next) {
it_next = it->next;
ID *id = static_cast<ID *>(it->link);
if (id->tag & LIB_TAG_DOIT) {
/* We know all users of this object are local or will be made fully local, even if
* currently there are some indirect usages. So instead of making a copy that we'll likely
* get rid of later, directly make that data block local.
* Saves a tremendous amount of time with complex scenes... */
BKE_lib_id_clear_library_data(bmain, id, 0);
BKE_lib_id_expand_local(bmain, id, 0);
id->tag &= ~LIB_TAG_DOIT;
if (GS(id->name) == ID_OB) {
BKE_rigidbody_ensure_local_object(bmain, (Object *)id);
}
}
else {
/* In this specific case, we do want to make ID local even if it has no local usage yet... */
BKE_lib_id_make_local(bmain, id, LIB_ID_MAKELOCAL_FULL_LIBRARY);
if (id->newid) {
if (GS(id->newid->name) == ID_OB) {
BKE_rigidbody_ensure_local_object(bmain, (Object *)id->newid);
}
/* Reuse already allocated LinkNode (transferring it from todo_ids to copied_ids). */
BLI_linklist_prepend_nlink(&copied_ids, id, it);
}
}
if (set_fake) {
if (!ELEM(GS(id->name), ID_OB, ID_GR)) {
/* do not set fake user on objects, groups (instancing) */
id_fake_user_set(id);
2002-10-12 11:37:38 +00:00
}
}
}
#ifdef DEBUG_TIME
printf("Step 3: Make IDs local: Done.\n");
TIMEIT_VALUE_PRINT(make_local);
#endif
/* At this point, we are done with directly made local IDs.
* Now we have to handle duplicated ones, since their
* remaining linked original counterpart may not be needed anymore... */
todo_ids = nullptr;
/* Step 4: We have to remap local usages of old (linked) ID to new (local)
* ID in a separated loop,
* as lbarray ordering is not enough to ensure us we did catch all dependencies
* (e.g. if making local a parent object before its child...). See #48907. */
/* TODO: This is now the biggest step by far (in term of processing time).
* We may be able to gain here by using again main->relations mapping, but...
* this implies BKE_libblock_remap & co to be able to update main->relations on the fly.
* Have to think about it a bit more, and see whether new code is OK first, anyway. */
for (LinkNode *it = copied_ids; it; it = it->next) {
ID *id = static_cast<ID *>(it->link);
BLI_assert(id->newid != nullptr);
BLI_assert(ID_IS_LINKED(id));
BKE_libblock_remap(bmain, id, id->newid, ID_REMAP_SKIP_INDIRECT_USAGE);
if (old_to_new_ids) {
BLI_ghash_insert(old_to_new_ids, id, id->newid);
}
/* Special hack for groups... Thing is, since we can't instantiate them here, we need to
* ensure they remain 'alive' (only instantiation is a real group 'user'... *sigh* See
* #49722. */
if (GS(id->name) == ID_GR && (id->tag & LIB_TAG_INDIRECT) != 0) {
id_us_ensure_real(id->newid);
}
2002-10-12 11:37:38 +00:00
}
/* Making some liboverride local may have had some impact on validity of liboverrides hierarchy
* roots, these need to be re-validated/re-generated. */
BKE_lib_override_library_main_hierarchy_root_ensure(bmain);
#ifdef DEBUG_TIME
printf("Step 4: Remap local usages of old (linked) ID to new (local) ID: Done.\n");
TIMEIT_VALUE_PRINT(make_local);
#endif
/* This is probably more of a hack than something we should do here, but...
* Issue is, the whole copying + remapping done in complex cases above may leave pose-channels
* of armatures in complete invalid state (more precisely, the bone pointers of the
2023-05-24 11:21:18 +10:00
* pose-channels - very crappy cross-data-blocks relationship), so we tag it to be fully
* recomputed, but this does not seems to be enough in some cases, and evaluation code ends up
* trying to evaluate a not-yet-updated armature object's deformations.
* Try "make all local" in 04_01_H.lighting.blend from Agent327 without this, e.g. */
for (Object *ob = static_cast<Object *>(bmain->objects.first); ob;
ob = static_cast<Object *>(ob->id.next))
{
if (ob->data != nullptr && ob->type == OB_ARMATURE && ob->pose != nullptr &&
ob->pose->flag & POSE_RECALC)
{
BKE_pose_rebuild(bmain, ob, static_cast<bArmature *>(ob->data), true);
}
}
#ifdef DEBUG_TIME
printf("Hack: Forcefully rebuild armature object poses: Done.\n");
TIMEIT_VALUE_PRINT(make_local);
#endif
BKE_main_id_newptr_and_tag_clear(bmain);
BLI_memarena_free(linklist_mem);
#ifdef DEBUG_TIME
printf("Cleanup and finish: Done.\n");
TIMEIT_END(make_local);
#endif
2002-10-12 11:37:38 +00:00
}
void BLI_libblock_ensure_unique_name(Main *bmain, const char *name)
2002-10-12 11:37:38 +00:00
{
ListBase *lb;
ID *idtest;
lb = which_libbase(bmain, GS(name));
if (lb == nullptr) {
2012-05-06 17:22:54 +00:00
return;
}
2018-06-17 17:05:51 +02:00
/* search for id */
idtest = static_cast<ID *>(BLI_findstring(lb, name + 2, offsetof(ID, name) + 2));
if (idtest != nullptr && !ID_IS_LINKED(idtest)) {
/* BKE_id_new_name_validate also takes care of sorting. */
BKE_id_new_name_validate(bmain, lb, idtest, nullptr, false);
bmain->is_memfile_undo_written = false;
2012-11-18 02:41:55 +00:00
}
2002-10-12 11:37:38 +00:00
}
void BKE_libblock_rename(Main *bmain, ID *id, const char *name)
2002-10-12 11:37:38 +00:00
{
BLI_assert(!ID_IS_LINKED(id));
BKE_main_namemap_remove_name(bmain, id, id->name + 2);
ListBase *lb = which_libbase(bmain, GS(id->name));
if (BKE_id_new_name_validate(bmain, lb, id, name, false)) {
bmain->is_memfile_undo_written = false;
}
2002-10-12 11:37:38 +00:00
}
void BKE_id_full_name_get(char name[MAX_ID_FULL_NAME], const ID *id, char separator_char)
{
2023-06-19 20:06:55 +10:00
BLI_strncpy(name, id->name + 2, MAX_ID_FULL_NAME);
if (ID_IS_LINKED(id)) {
const size_t idname_len = strlen(id->name + 2);
const size_t libname_len = strlen(id->lib->id.name + 2);
name[idname_len] = separator_char ? separator_char : ' ';
name[idname_len + 1] = '[';
2023-06-19 20:06:55 +10:00
BLI_strncpy(name + idname_len + 2, id->lib->id.name + 2, MAX_ID_FULL_NAME - (idname_len + 2));
name[idname_len + 2 + libname_len] = ']';
name[idname_len + 2 + libname_len + 1] = '\0';
}
}
void BKE_id_full_name_ui_prefix_get(char name[MAX_ID_FULL_NAME_UI],
const ID *id,
const bool add_lib_hint,
char separator_char,
int *r_prefix_len)
{
int i = 0;
if (add_lib_hint) {
name[i++] = id->lib ? (ID_MISSING(id) ? 'M' : 'L') : ID_IS_OVERRIDE_LIBRARY(id) ? 'O' : ' ';
}
name[i++] = (id->flag & LIB_FAKEUSER) ? 'F' : ((id->us == 0) ? '0' : ' ');
name[i++] = ' ';
BKE_id_full_name_get(name + i, id, separator_char);
if (r_prefix_len) {
*r_prefix_len = i;
}
}
char *BKE_id_to_unique_string_key(const ID *id)
{
if (!ID_IS_LINKED(id)) {
return BLI_strdup(id->name);
}
/* Prefix with an ascii character in the range of 32..96 (visible)
* this ensures we can't have a library ID pair that collide.
* Where 'LIfooOBbarOBbaz' could be ('LIfoo, OBbarOBbaz') or ('LIfooOBbar', 'OBbaz'). */
const char ascii_len = strlen(id->lib->id.name + 2) + 32;
return BLI_sprintfN("%c%s%s", ascii_len, id->lib->id.name, id->name);
}
void BKE_id_tag_set_atomic(ID *id, int tag)
{
atomic_fetch_and_or_int32(&id->tag, tag);
}
void BKE_id_tag_clear_atomic(ID *id, int tag)
{
atomic_fetch_and_and_int32(&id->tag, ~tag);
}
bool BKE_id_is_in_global_main(ID *id)
2018-06-25 12:02:57 +02:00
{
/* We do not want to fail when id is nullptr here, even though this is a bit strange behavior...
*/
return (id == nullptr || BLI_findindex(which_libbase(G_MAIN, GS(id->name)), id) != -1);
}
bool BKE_id_can_be_asset(const ID *id)
{
return !ID_IS_LINKED(id) && !ID_IS_OVERRIDE_LIBRARY(id) &&
BKE_idtype_idcode_is_linkable(GS(id->name));
}
ID *BKE_id_owner_get(ID *id)
{
const IDTypeInfo *idtype = BKE_idtype_get_info_from_id(id);
if (idtype->owner_pointer_get != nullptr) {
ID **owner_id_pointer = idtype->owner_pointer_get(id);
if (owner_id_pointer != nullptr) {
return *owner_id_pointer;
}
}
return nullptr;
}
bool BKE_id_is_editable(const Main *bmain, const ID *id)
{
return !(ID_IS_LINKED(id) || BKE_lib_override_library_is_system_defined(bmain, id));
}
/************************* Datablock order in UI **************************/
static int *id_order_get(ID *id)
{
/* Only for workspace tabs currently. */
switch (GS(id->name)) {
case ID_WS:
return &((WorkSpace *)id)->order;
default:
return nullptr;
}
}
static int id_order_compare(const void *a, const void *b)
{
ID *id_a = static_cast<ID *>(((LinkData *)a)->data);
ID *id_b = static_cast<ID *>(((LinkData *)b)->data);
int *order_a = id_order_get(id_a);
int *order_b = id_order_get(id_b);
if (order_a && order_b) {
if (*order_a < *order_b) {
return -1;
}
if (*order_a > *order_b) {
return 1;
}
}
return strcmp(id_a->name, id_b->name);
}
void BKE_id_ordered_list(ListBase *ordered_lb, const ListBase *lb)
{
BLI_listbase_clear(ordered_lb);
LISTBASE_FOREACH (ID *, id, lb) {
BLI_addtail(ordered_lb, BLI_genericNodeN(id));
}
BLI_listbase_sort(ordered_lb, id_order_compare);
int num = 0;
LISTBASE_FOREACH (LinkData *, link, ordered_lb) {
int *order = id_order_get(static_cast<ID *>(link->data));
if (order) {
*order = num++;
}
}
}
void BKE_id_reorder(const ListBase *lb, ID *id, ID *relative, bool after)
{
int *id_order = id_order_get(id);
int relative_order;
if (relative) {
relative_order = *id_order_get(relative);
}
else {
relative_order = (after) ? BLI_listbase_count(lb) : 0;
}
if (after) {
/* Insert after. */
LISTBASE_FOREACH (ID *, other, lb) {
int *order = id_order_get(other);
if (*order > relative_order) {
(*order)++;
}
}
*id_order = relative_order + 1;
}
else {
/* Insert before. */
LISTBASE_FOREACH (ID *, other, lb) {
int *order = id_order_get(other);
if (*order < relative_order) {
(*order)--;
}
}
*id_order = relative_order - 1;
}
}
void BKE_id_blend_write(BlendWriter *writer, ID *id)
{
if (id->asset_data) {
BKE_asset_metadata_write(writer, id->asset_data);
}
if (id->library_weak_reference != nullptr) {
BLO_write_struct(writer, LibraryWeakReference, id->library_weak_reference);
}
/* ID_WM's id->properties are considered runtime only, and never written in .blend file. */
if (id->properties && !ELEM(GS(id->name), ID_WM)) {
IDP_BlendWrite(writer, id->properties);
}
BKE_animdata_blend_write(writer, id);
if (id->override_library) {
BLO_write_struct(writer, IDOverrideLibrary, id->override_library);
BLO_write_struct_list(writer, IDOverrideLibraryProperty, &id->override_library->properties);
LISTBASE_FOREACH (IDOverrideLibraryProperty *, op, &id->override_library->properties) {
BLO_write_string(writer, op->rna_path);
BLO_write_struct_list(writer, IDOverrideLibraryPropertyOperation, &op->operations);
LISTBASE_FOREACH (IDOverrideLibraryPropertyOperation *, opop, &op->operations) {
if (opop->subitem_reference_name) {
BLO_write_string(writer, opop->subitem_reference_name);
}
if (opop->subitem_local_name) {
BLO_write_string(writer, opop->subitem_local_name);
}
}
}
}
}