diff --git a/source/blender/blenkernel/BKE_blendfile.h b/source/blender/blenkernel/BKE_blendfile.h index 357aeb4427f..1ce7d440219 100644 --- a/source/blender/blenkernel/BKE_blendfile.h +++ b/source/blender/blenkernel/BKE_blendfile.h @@ -14,6 +14,7 @@ extern "C" { struct BlendFileData; struct BlendFileReadParams; struct BlendFileReadReport; +struct BlendFileReadWMSetupData; struct ID; struct Main; struct MemFile; @@ -50,33 +51,38 @@ bool BKE_blendfile_library_path_explode(const char *path, /** * Shared setup function that makes the data from `bfd` into the current blend file, * replacing the contents of #G.main. - * This uses the bfd #BKE_blendfile_read and similarly named functions. + * This uses the bfd returned by #BKE_blendfile_read and similarly named functions. * * This is done in a separate step so the caller may perform actions after it is known the file * loaded correctly but before the file replaces the existing blend file contents. */ -void BKE_blendfile_read_setup_ex(struct bContext *C, - struct BlendFileData *bfd, - const struct BlendFileReadParams *params, - struct BlendFileReadReport *reports, - /* Extra args. */ - bool startup_update_defaults, - const char *startup_app_template); - -void BKE_blendfile_read_setup(struct bContext *C, - struct BlendFileData *bfd, - const struct BlendFileReadParams *params, - struct BlendFileReadReport *reports); +void BKE_blendfile_read_setup_readfile(struct bContext *C, + struct BlendFileData *bfd, + const struct BlendFileReadParams *params, + struct BlendFileReadWMSetupData *wm_setup_data, + struct BlendFileReadReport *reports, + bool startup_update_defaults, + const char *startup_app_template); /** - * \return Blend file data, this must be passed to #BKE_blendfile_read_setup when non-NULL. + * Simpler version of #BKE_blendfile_read_setup_readfile used when reading undoe steps from + * memfile. */ +void BKE_blendfile_read_setup_undo(struct bContext *C, + struct BlendFileData *bfd, + const struct BlendFileReadParams *params, + struct BlendFileReadReport *reports); + +/** + * \return Blend file data, this must be passed to + * #BKE_blendfile_read_setup_readfile/#BKE_blendfile_read_setup_undo when non-NULL. */ struct BlendFileData *BKE_blendfile_read(const char *filepath, const struct BlendFileReadParams *params, struct BlendFileReadReport *reports); /** - * \return Blend file data, this must be passed to #BKE_blendfile_read_setup when non-NULL. + * \return Blend file data, this must be passed to + * #BKE_blendfile_read_setup_readfile/#BKE_blendfile_read_setup_undo when non-NULL. */ struct BlendFileData *BKE_blendfile_read_from_memory(const void *filebuf, int filelength, @@ -84,7 +90,9 @@ struct BlendFileData *BKE_blendfile_read_from_memory(const void *filebuf, struct ReportList *reports); /** - * \return Blend file data, this must be passed to #BKE_blendfile_read_setup when non-NULL. + * \return Blend file data, this must be passed to + * #BKE_blendfile_read_setup_readfile/#BKE_blendfile_read_setup_undo when non-NULL. + * * \note `memfile` is the undo buffer. */ struct BlendFileData *BKE_blendfile_read_from_memfile(struct Main *bmain, diff --git a/source/blender/blenkernel/BKE_idtype.h b/source/blender/blenkernel/BKE_idtype.h index 1ee3317bd52..e7f0c137678 100644 --- a/source/blender/blenkernel/BKE_idtype.h +++ b/source/blender/blenkernel/BKE_idtype.h @@ -220,6 +220,10 @@ typedef struct IDTypeInfo { * Allow an ID type to preserve some of its data across (memfile) undo steps. * * \note Called from #setup_app_data when undoing or redoing a memfile step. + * + * \note In case the whole ID should be fully preserved across undo steps, it is better to flag + * its type with `IDTYPE_FLAGS_NO_MEMFILE_UNDO`, since that flag allows more aggressive + * optimizations in readfile code for memfile undo. */ IDTypeBlendReadUndoPreserve blend_read_undo_preserve; diff --git a/source/blender/blenkernel/intern/blender_undo.cc b/source/blender/blenkernel/intern/blender_undo.cc index af353b6d5de..6e6de65bd15 100644 --- a/source/blender/blenkernel/intern/blender_undo.cc +++ b/source/blender/blenkernel/intern/blender_undo.cc @@ -69,7 +69,7 @@ bool BKE_memfile_undo_decode(MemFileUndoData *mfu, BlendFileReadReport bf_reports{}; BlendFileData *bfd = BKE_blendfile_read(mfu->filepath, ¶ms, &bf_reports); if (bfd != nullptr) { - BKE_blendfile_read_setup(C, bfd, ¶ms, &bf_reports); + BKE_blendfile_read_setup_undo(C, bfd, ¶ms, &bf_reports); success = true; } } @@ -82,7 +82,7 @@ bool BKE_memfile_undo_decode(MemFileUndoData *mfu, BlendFileReadReport blend_file_read_report{}; BlendFileData *bfd = BKE_blendfile_read_from_memfile(bmain, &mfu->memfile, ¶ms, nullptr); if (bfd != nullptr) { - BKE_blendfile_read_setup(C, bfd, ¶ms, &blend_file_read_report); + BKE_blendfile_read_setup_undo(C, bfd, ¶ms, &blend_file_read_report); success = true; } } diff --git a/source/blender/blenkernel/intern/blendfile.cc b/source/blender/blenkernel/intern/blendfile.cc index a11a9b72ba3..1fdb1afa2ba 100644 --- a/source/blender/blenkernel/intern/blendfile.cc +++ b/source/blender/blenkernel/intern/blendfile.cc @@ -39,12 +39,16 @@ #include "BKE_colorband.h" #include "BKE_context.h" #include "BKE_global.h" +#include "BKE_idtype.h" #include "BKE_ipo.h" #include "BKE_keyconfig.h" #include "BKE_layer.h" #include "BKE_lib_id.h" #include "BKE_lib_override.h" +#include "BKE_lib_query.h" +#include "BKE_lib_remap.h" #include "BKE_main.h" +#include "BKE_main_idmap.h" #include "BKE_main_namemap.h" #include "BKE_preferences.h" #include "BKE_report.h" @@ -216,6 +220,428 @@ static void setup_app_userdef(BlendFileData *bfd) } } +/** Helper struct to manage IDs that are re-used across blendfile loading (i.e. moved from the old + * Main the the new one). + * + * NOTE: this is only used when actually loading a real .blend file, loading of memfile undo steps + * does not need it. */ +typedef struct ReuseOldBMainData { + Main *new_bmain; + Main *old_bmain; + + /** Data generated and used by calling WM code to handle keeping WM and UI IDs as best as + * possible across file reading. + * + * \note: May be null in undo (memfile) case.. */ + BlendFileReadWMSetupData *wm_setup_data; + + /** Storage for all remapping rules (old_id -> new_id) required by the preservation of old IDs + * into the new Main. */ + IDRemapper *remapper; + bool is_libraries_remapped; + + /** Used to find matching IDs by name/lib in new main, to remap ID usages of data ported over + * from old main. */ + IDNameLib_Map *id_map; +} ReuseOldBMainData; + +/** Search for all libraries in `old_bmain` that are also in `new_bmain` (i.e. different Library + * IDs having the same absolute filepath), and create a remapping rule for these. + * + * NOTE: The case where the `old_bmain` would be a library in the newly read one is not handled + * here, as it does not create explicit issues. The local data from `old_bmain` is either + * discarded, or added to the `new_bmain` as local data as well. Worst case, there will be a + * doublon of a linked data as a local one, without any known relationships between them. In + * practice, this latter case is not expected to commonly happen. + */ +static IDRemapper *reuse_bmain_data_remapper_ensure(ReuseOldBMainData *reuse_data) +{ + if (reuse_data->is_libraries_remapped) { + return reuse_data->remapper; + } + + if (reuse_data->remapper == nullptr) { + reuse_data->remapper = BKE_id_remapper_create(); + } + + Main *new_bmain = reuse_data->new_bmain; + Main *old_bmain = reuse_data->old_bmain; + IDRemapper *remapper = reuse_data->remapper; + + LISTBASE_FOREACH (Library *, old_lib_iter, &old_bmain->libraries) { + /* In case newly opened `new_bmain` is a library of the `old_bmain`, remap it to NULL, since a + * file should never ever have linked data from itself. */ + if (STREQ(old_lib_iter->filepath_abs, new_bmain->filepath)) { + BKE_id_remapper_add(remapper, &old_lib_iter->id, nullptr); + continue; + } + + /* NOTE: Although this is quadratic complexity, it is not expected to be an issue in practice: + * - Files using more than a few tens of libraries are extremely rare. + * - This code is only executed once for every file reading (not on undos). + */ + LISTBASE_FOREACH (Library *, new_lib_iter, &new_bmain->libraries) { + if (!STREQ(old_lib_iter->filepath_abs, new_lib_iter->filepath_abs)) { + continue; + } + + BKE_id_remapper_add(remapper, &old_lib_iter->id, &new_lib_iter->id); + break; + } + } + + reuse_data->is_libraries_remapped = true; + return reuse_data->remapper; +} + +static bool reuse_bmain_data_remapper_is_id_remapped(IDRemapper *remapper, ID *id) +{ + IDRemapperApplyResult result = BKE_id_remapper_get_mapping_result( + remapper, id, ID_REMAP_APPLY_DEFAULT, nullptr); + if (ELEM(result, ID_REMAP_RESULT_SOURCE_REMAPPED, ID_REMAP_RESULT_SOURCE_UNASSIGNED)) { + /* ID is already remapped to its matching ID in the new main, or explicitly remapped to NULL, + * nothing else to do here. */ + return true; + } + BLI_assert_msg(result != ID_REMAP_RESULT_SOURCE_NOT_MAPPABLE, + "There should never be a non-mappable (i.e. NULL) input here."); + BLI_assert(result == ID_REMAP_RESULT_SOURCE_UNAVAILABLE); + return false; +} + +/** Does a complete replacement of data in `new_bmain` by data from `old_bmain. Original new data + * are moved to the `old_bmain`, and will be freed together with it. + * + * WARNING: Currently only expects to work on local data, won't work properly if some of the IDs of + * given type are linked. + * + * NOTE: There is no support at all for potential dependencies of the IDs moved around. This is not + * expected to be necessary for the current use cases (UI-related IDs). */ +static void swap_old_bmain_data_for_blendfile(ReuseOldBMainData *reuse_data, const short id_code) +{ + Main *new_bmain = reuse_data->new_bmain; + Main *old_bmain = reuse_data->old_bmain; + + ListBase *new_lb = which_libbase(new_bmain, id_code); + ListBase *old_lb = which_libbase(old_bmain, id_code); + + IDRemapper *remapper = reuse_bmain_data_remapper_ensure(reuse_data); + + /* NOTE: Full swapping is only supported for ID types that are assumed to be only local + * data-blocks (like UI-like ones). Otherwise, the swapping could fail in many funny ways. */ + BLI_assert(BLI_listbase_is_empty(old_lb) || !ID_IS_LINKED(old_lb->last)); + BLI_assert(BLI_listbase_is_empty(new_lb) || !ID_IS_LINKED(new_lb->last)); + + SWAP(ListBase, *new_lb, *old_lb); + + /* Since all IDs here are supposed to be local, no need to call #BKE_main_namemap_clear. */ + /* TODO: Could add per-IDType control over namemaps clearing, if this becomes a performances + * concern. */ + if (old_bmain->name_map != nullptr) { + BKE_main_namemap_destroy(&old_bmain->name_map); + } + if (new_bmain->name_map != nullptr) { + BKE_main_namemap_destroy(&new_bmain->name_map); + } + + /* Original 'new' IDs have been moved into the old listbase and will be discarded (deleted). + * Original 'old' IDs have been moved into the new listbase and are being reused (kept). + * The discarded ones need to be remapped to a matching reused one, based on their names, if + * possible. + * + * Since both lists are ordered, and they are all local, we can do a smart parallel processing of + * both lists here instead of doing complete full list searches. */ + ID *discarded_id_iter = static_cast(old_lb->first); + ID *reused_id_iter = static_cast(new_lb->first); + while (!ELEM(nullptr, discarded_id_iter, reused_id_iter)) { + const int strcmp_result = strcmp(discarded_id_iter->name + 2, reused_id_iter->name + 2); + if (strcmp_result == 0) { + /* Matching IDs, we can remap the discarded 'new' one to the re-used 'old' one. */ + BKE_id_remapper_add(remapper, discarded_id_iter, reused_id_iter); + + discarded_id_iter = static_cast(discarded_id_iter->next); + reused_id_iter = static_cast(reused_id_iter->next); + } + else if (strcmp_result < 0) { + /* No matching reused 'old' ID for this discarded 'new' one. */ + BKE_id_remapper_add(remapper, discarded_id_iter, nullptr); + + discarded_id_iter = static_cast(discarded_id_iter->next); + } + else { + reused_id_iter = static_cast(reused_id_iter->next); + } + } + /* Also remap all remaining non-compared discarded 'new' IDs to null. */ + for (; discarded_id_iter != nullptr; + discarded_id_iter = static_cast(discarded_id_iter->next)) + { + BKE_id_remapper_add(remapper, discarded_id_iter, nullptr); + } + + FOREACH_MAIN_LISTBASE_ID_BEGIN (new_lb, reused_id_iter) { + /* Necessary as all `session_uuid` are renewed on blendfile loading. */ + BKE_lib_libblock_session_uuid_renew(reused_id_iter); + + /* Ensure that the reused ID is remapped to itself, since it is known to be in the `new_bmain`. + */ + BKE_id_remapper_add_overwrite(remapper, reused_id_iter, reused_id_iter); + } + FOREACH_MAIN_LISTBASE_ID_END; +} + +/** Similar to #swap_old_bmain_data_for_blendfile, but with special handling for WM ID. Tightly + * related to further WM post-processing from calling WM code (see #WM_file_read and + * #wm_homefile_read_ex). */ +static void swap_wm_data_for_blendfile(ReuseOldBMainData *reuse_data, const bool load_ui) +{ + Main *old_bmain = reuse_data->old_bmain; + Main *new_bmain = reuse_data->new_bmain; + ListBase *old_wm_list = &old_bmain->wm; + ListBase *new_wm_list = &new_bmain->wm; + + /* Currently there should never be more than one WM in a main. */ + BLI_assert(BLI_listbase_count_at_most(new_wm_list, 2) <= 1); + BLI_assert(BLI_listbase_count_at_most(old_wm_list, 2) <= 1); + + wmWindowManager *old_wm = static_cast(old_wm_list->first); + wmWindowManager *new_wm = static_cast(new_wm_list->first); + + if (old_wm == nullptr) { + /* No current (old) WM. Either (new) WM from file is used, or if none, WM code is responsible + * to add a new default WM. Nothing to do here. */ + return; + } + + /* Current (old) WM, and (new) WM in file, and loading UI: use WM from file, keep old WM around + * for further processing in WM code. */ + if (load_ui && new_wm != nullptr) { + /* Support window-manager ID references being held between file load operations by keeping + * #Main.wm.first memory address in-place, while swapping all of it's contents. + * + * This is needed so items such as key-maps can be held by an add-on, + * without it pointing to invalid memory, see: #86431. */ + BLI_remlink(old_wm_list, old_wm); + BLI_remlink(new_wm_list, new_wm); + BKE_lib_id_swap_full(nullptr, + &old_wm->id, + &new_wm->id, + true, + (ID_REMAP_SKIP_NEVER_NULL_USAGE | ID_REMAP_SKIP_UPDATE_TAGGING | + ID_REMAP_SKIP_USER_REFCOUNT | ID_REMAP_FORCE_UI_POINTERS)); + /* Not strictly necessary, but helps for readability. */ + std::swap(old_wm, new_wm); + BLI_addhead(new_wm_list, new_wm); + /* Do not add old WM back to `old_bmain`, so that it does not get freed when `old_bmain` is + * freed. Calling WM code will need this old WM to restore some windows etc. data into the + * new WM, and is responsible to free it properly. */ + reuse_data->wm_setup_data->old_wm = old_wm; + + IDRemapper *remapper = reuse_bmain_data_remapper_ensure(reuse_data); + BKE_id_remapper_add(remapper, &old_wm->id, &new_wm->id); + } + /* Current (old) WM, but no (new) one in file (should only happen when reading pre 2.5 files, no + * WM back then), or not loading UI: Keep current WM. */ + else { + swap_old_bmain_data_for_blendfile(reuse_data, ID_WM); + old_wm->init_flag &= ~WM_INIT_FLAG_WINDOW; + } +} + +static int swap_old_bmain_data_for_blendfile_dependencies_process_cb( + LibraryIDLinkCallbackData *cb_data) +{ + ID *id = *cb_data->id_pointer; + + if (id == nullptr) { + return IDWALK_RET_NOP; + } + + ReuseOldBMainData *reuse_data = static_cast(cb_data->user_data); + + /* First check if it has already been remapped. */ + IDRemapper *remapper = reuse_bmain_data_remapper_ensure(reuse_data); + if (reuse_bmain_data_remapper_is_id_remapped(remapper, id)) { + return IDWALK_RET_NOP; + } + + IDNameLib_Map *id_map = reuse_data->id_map; + BLI_assert(id_map != nullptr); + + ID *id_new = BKE_main_idmap_lookup_id(id_map, id); + BKE_id_remapper_add(remapper, id, id_new); + + return IDWALK_RET_NOP; +} + +static void swap_old_bmain_data_dependencies_process(ReuseOldBMainData *reuse_data, + const short id_code) +{ + Main *new_bmain = reuse_data->new_bmain; + ListBase *new_lb = which_libbase(new_bmain, id_code); + + BLI_assert(reuse_data->id_map != nullptr); + + ID *new_id_iter; + FOREACH_MAIN_LISTBASE_ID_BEGIN (new_lb, new_id_iter) { + /* Check all ID usages and find a matching new ID to remap them to in `new_bmain` if possible + * (matching by names and libraries). + * + * Note that this call does not do any effective remapping, it only adds required remapping + * operations to the remapper. */ + BKE_library_foreach_ID_link(new_bmain, + new_id_iter, + swap_old_bmain_data_for_blendfile_dependencies_process_cb, + reuse_data, + IDWALK_READONLY | IDWALK_INCLUDE_UI | IDWALK_DO_LIBRARY_POINTER); + } + FOREACH_MAIN_LISTBASE_ID_END; +} + +static int reuse_bmain_data_invalid_local_usages_fix_cb(LibraryIDLinkCallbackData *cb_data) +{ + ID *id = *cb_data->id_pointer; + + if (id == nullptr) { + return IDWALK_RET_NOP; + } + + /* Embedded data cannot (yet) be fully trusted to have the same lib pointer as their owner ID, so + * for now ignore them. This code should never have anything to fix for them anyway, otherwise + * there is something extremely wrong going on. */ + if ((cb_data->cb_flag & (IDWALK_CB_EMBEDDED | IDWALK_CB_EMBEDDED_NOT_OWNING)) != 0) { + return IDWALK_RET_NOP; + } + + if (!ID_IS_LINKED(id)) { + ID *owner_id = cb_data->owner_id; + + /* Do not allow linked data to use local data. */ + if (ID_IS_LINKED(owner_id)) { + if (cb_data->cb_flag & IDWALK_CB_USER) { + id_us_min(id); + } + *cb_data->id_pointer = nullptr; + } + /* Do not allow local liboverride data to use local data as reference. */ + else if (ID_IS_OVERRIDE_LIBRARY_REAL(owner_id) && + &owner_id->override_library->reference == cb_data->id_pointer) + { + if (cb_data->cb_flag & IDWALK_CB_USER) { + id_us_min(id); + } + *cb_data->id_pointer = nullptr; + } + } + + return IDWALK_RET_NOP; +} + +/** Detect and fix invalid usages of locale IDs by linked ones (or as reference of liboverrides). + */ +static void reuse_bmain_data_invalid_local_usages_fix(ReuseOldBMainData *reuse_data) +{ + Main *new_bmain = reuse_data->new_bmain; + ID *id_iter; + FOREACH_MAIN_ID_BEGIN (new_bmain, id_iter) { + if (!ID_IS_LINKED(id_iter) && !ID_IS_OVERRIDE_LIBRARY_REAL(id_iter)) { + continue; + } + + ID *liboverride_reference = ID_IS_OVERRIDE_LIBRARY_REAL(id_iter) ? + id_iter->override_library->reference : + nullptr; + + BKE_library_foreach_ID_link( + new_bmain, id_iter, reuse_bmain_data_invalid_local_usages_fix_cb, reuse_data, 0); + + /* Liboverrides who lost their reference should not be liboverrides anymore, but regular IDs. + */ + if (ID_IS_OVERRIDE_LIBRARY_REAL(id_iter) && + id_iter->override_library->reference != liboverride_reference) + { + BKE_lib_override_library_free(&id_iter->override_library, true); + } + } + FOREACH_MAIN_ID_END; +} + +/* Post-remapping helpers to ensure validity of the UI data. */ + +static void view3d_data_consistency_ensure(wmWindow *win, Scene *scene, ViewLayer *view_layer) +{ + bScreen *screen = BKE_workspace_active_screen_get(win->workspace_hook); + + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) { + if (sl->spacetype != SPACE_VIEW3D) { + continue; + } + + View3D *v3d = reinterpret_cast(sl); + if (v3d->camera == nullptr || v3d->scenelock) { + v3d->camera = scene->camera; + } + if (v3d->localvd == nullptr) { + continue; + } + + if (v3d->localvd->camera == nullptr || v3d->scenelock) { + v3d->localvd->camera = v3d->camera; + } + /* Local-view can become invalid during undo/redo steps, exit it when no valid object could + * be found. */ + Base *base; + for (base = static_cast(view_layer->object_bases.first); base; base = base->next) { + if (base->local_view_bits & v3d->local_view_uuid) { + break; + } + } + if (base != nullptr) { + /* The local view3D still has a valid object, nothing else to do. */ + continue; + } + + /* No valid object found for the local view3D, it has to be cleared off. */ + MEM_freeN(v3d->localvd); + v3d->localvd = nullptr; + v3d->local_view_uuid = 0; + + /* Region-base storage is different depending on whether the space is active or not. */ + ListBase *regionbase = (sl == area->spacedata.first) ? &area->regionbase : &sl->regionbase; + LISTBASE_FOREACH (ARegion *, region, regionbase) { + if (region->regiontype != RGN_TYPE_WINDOW) { + continue; + } + + RegionView3D *rv3d = static_cast(region->regiondata); + MEM_SAFE_FREE(rv3d->localvd); + } + } + } +} + +static void wm_data_consistency_ensure(wmWindowManager *curwm, + Scene *cur_scene, + ViewLayer *cur_view_layer) +{ + /* There may not be any available WM (e.g. when reading `userpref.blend`). */ + if (curwm == nullptr) { + return; + } + + LISTBASE_FOREACH (wmWindow *, win, &curwm->windows) { + if (win->scene == nullptr) { + win->scene = cur_scene; + } + if (BKE_view_layer_find(win->scene, win->view_layer_name) == nullptr) { + STRNCPY(win->view_layer_name, cur_view_layer->name); + } + + view3d_data_consistency_ensure(win, win->scene, cur_view_layer); + } +} + /** * Context matching, handle no-UI case. * @@ -227,10 +653,10 @@ static void setup_app_userdef(BlendFileData *bfd) static void setup_app_data(bContext *C, BlendFileData *bfd, const BlendFileReadParams *params, + BlendFileReadWMSetupData *wm_setup_data, BlendFileReadReport *reports) { Main *bmain = G_MAIN; - Scene *curscene = nullptr; const bool recover = (G.fileflags & G_FILE_RECOVER_READ) != 0; const bool is_startup = params->is_startup; enum { @@ -266,103 +692,134 @@ static void setup_app_data(bContext *C, clean_paths(bfd->main); } - /* The following code blocks performs complex window-manager matching. */ + BLI_assert(BKE_main_namemap_validate(bfd->main)); - /* no load screens? */ + /* Temp data to handle swapping around IDs between old and new mains, and accumulate the + * required remapping accordingly. */ + ReuseOldBMainData reuse_data = {nullptr}; + reuse_data.new_bmain = bfd->main; + reuse_data.old_bmain = bmain; + reuse_data.wm_setup_data = wm_setup_data; + + if (mode != LOAD_UNDO) { + const short ui_id_codes[]{ID_WS, ID_SCR}; + + /* WM needs special complex handling, regardless of whether UI is kept or loaded from file. */ + swap_wm_data_for_blendfile(&reuse_data, mode == LOAD_UI); + if (mode != LOAD_UI) { + /* Re-use UI data from `old_bmain` if keeping existing UI. */ + for (auto id_code : ui_id_codes) { + swap_old_bmain_data_for_blendfile(&reuse_data, id_code); + } + } + + /* Needs to happen after all data from `old_bmain` has been moved into new one. */ + BLI_assert(reuse_data.id_map == nullptr); + reuse_data.id_map = BKE_main_idmap_create( + reuse_data.new_bmain, true, reuse_data.old_bmain, MAIN_IDMAP_TYPE_NAME); + + swap_old_bmain_data_dependencies_process(&reuse_data, ID_WM); + if (mode != LOAD_UI) { + for (auto id_code : ui_id_codes) { + swap_old_bmain_data_dependencies_process(&reuse_data, id_code); + } + } + + BKE_main_idmap_destroy(reuse_data.id_map); + } + + /* Logic for 'track_undo_scene' is to keep using the scene which the active screen has, as long + * as the scene associated with the undo operation is visible in one of the open windows. + * + * - 'curscreen->scene' - scene the user is currently looking at. + * - 'bfd->curscene' - scene undo-step was created in. + * + * This means that users can have 2 or more windows open and undo in both without screens + * switching. But if they close one of the screens, undo will ensure that the scene being + * operated on will be activated (otherwise we'd be undoing on an off-screen scene which isn't + * acceptable). See: #43424. */ + bool track_undo_scene = false; + + /* Always use the Scene and ViewLayer pointers from new file, if possible. */ + ViewLayer *cur_view_layer = bfd->cur_view_layer; + Scene *curscene = bfd->curscene; + + wmWindow *win = nullptr; + bScreen *curscreen = nullptr; + + /* Ensure that there is a valid scene and viewlayer. */ + if (curscene == nullptr) { + curscene = static_cast(bfd->main->scenes.first); + } + /* Empty file, add a scene to make Blender work. */ + if (curscene == nullptr) { + curscene = BKE_scene_add(bfd->main, "Empty"); + } + if (cur_view_layer == nullptr) { + /* Fallback to the active scene view layer. */ + cur_view_layer = BKE_view_layer_default_view(curscene); + } + + /* If UI is not loaded when opening actual .blend file, and always in case of undo memfile + * reading. */ if (mode != LOAD_UI) { - /* Logic for 'track_undo_scene' is to keep using the scene which the active screen has, - * as long as the scene associated with the undo operation is visible - * in one of the open windows. - * - * - 'curscreen->scene' - scene the user is currently looking at. - * - 'bfd->curscene' - scene undo-step was created in. - * - * This means users can have 2+ windows open and undo in both without screens switching. - * But if they close one of the screens, - * undo will ensure that the scene being operated on will be activated - * (otherwise we'd be undoing on an off-screen scene which isn't acceptable). - * see: #43424 - */ - wmWindow *win; - bScreen *curscreen = nullptr; - ViewLayer *cur_view_layer; - bool track_undo_scene; - - /* comes from readfile.c */ - SWAP(ListBase, bmain->wm, bfd->main->wm); - SWAP(ListBase, bmain->workspaces, bfd->main->workspaces); - SWAP(ListBase, bmain->screens, bfd->main->screens); - /* NOTE: UI IDs are assumed to be only local data-blocks, so no need to call - * #BKE_main_namemap_clear here (otherwise, the swapping would fail in many funny ways). */ - if (bmain->name_map != nullptr) { - BKE_main_namemap_destroy(&bmain->name_map); - } - if (bfd->main->name_map != nullptr) { - BKE_main_namemap_destroy(&bfd->main->name_map); - } - - /* In case of actual new file reading without loading UI, we need to regenerate the session - * uuid of the UI-related datablocks we are keeping from previous session, otherwise their uuid - * will collide with some generated for newly read data. */ - if (mode != LOAD_UNDO) { - ID *id; - FOREACH_MAIN_LISTBASE_ID_BEGIN (&bfd->main->wm, id) { - BKE_lib_libblock_session_uuid_renew(id); - } - FOREACH_MAIN_LISTBASE_ID_END; - - FOREACH_MAIN_LISTBASE_ID_BEGIN (&bfd->main->workspaces, id) { - BKE_lib_libblock_session_uuid_renew(id); - } - FOREACH_MAIN_LISTBASE_ID_END; - - FOREACH_MAIN_LISTBASE_ID_BEGIN (&bfd->main->screens, id) { - BKE_lib_libblock_session_uuid_renew(id); - } - FOREACH_MAIN_LISTBASE_ID_END; - } - - /* we re-use current window and screen */ + /* Re-use current window and screen. */ win = CTX_wm_window(C); curscreen = CTX_wm_screen(C); - /* but use Scene pointer from new file */ - curscene = bfd->curscene; - cur_view_layer = bfd->cur_view_layer; track_undo_scene = (mode == LOAD_UNDO && curscreen && curscene && bfd->main->wm.first); - if (curscene == nullptr) { - curscene = static_cast(bfd->main->scenes.first); - } - /* empty file, we add a scene to make Blender work */ - if (curscene == nullptr) { - curscene = BKE_scene_add(bfd->main, "Empty"); - } - if (cur_view_layer == nullptr) { - /* fallback to scene layer */ - cur_view_layer = BKE_view_layer_default_view(curscene); - } - if (track_undo_scene) { - /* keep the old (free'd) scene, let 'blo_lib_link_screen_restore' - * replace it with 'curscene' if its needed */ + /* Keep the old (to-be-freed) scene, remapping below will ensure it's remapped to the + * matching new scene if available, or NULL otherwise, in which case + * #wm_data_consistency_ensure will define `curscene` as the active one. */ } - /* and we enforce curscene to be in current screen */ + /* Enforce curscene to be in current screen. */ else if (win) { /* The window may be nullptr in background-mode. */ win->scene = curscene; } + } - /* BKE_blender_globals_clear will free G_MAIN, here we can still restore pointers */ - blo_lib_link_restore(bmain, bfd->main, CTX_wm_manager(C), curscene, cur_view_layer); + BLI_assert(BKE_main_namemap_validate(bfd->main)); + + /* Apply remapping of ID pointers caused by re-using part of the data from the 'old' main into + * the new one. */ + if (reuse_data.remapper != nullptr) { + /* In undo case all 'keeping old data' and remapping logic is now handled in readfile code + * itself, so there should never be any remapping to do here. */ + BLI_assert(mode != LOAD_UNDO); + + /* Handle all pending remapping from swapping old and new IDs around. */ + BKE_libblock_remap_multiple_raw(bfd->main, + reuse_data.remapper, + (ID_REMAP_FORCE_UI_POINTERS | ID_REMAP_SKIP_USER_REFCOUNT | + ID_REMAP_SKIP_UPDATE_TAGGING | ID_REMAP_SKIP_USER_CLEAR)); + + /* Fix potential invalid usages of now-locale-data created by remapping above. Should never + * be needed in undo case, this is to address cases like 'opening a blendfile that was a + * library of the previous opened blendfile'. */ + reuse_bmain_data_invalid_local_usages_fix(&reuse_data); + + BKE_id_remapper_free(reuse_data.remapper); + reuse_data.remapper = nullptr; + + wm_data_consistency_ensure(CTX_wm_manager(C), curscene, cur_view_layer); + } + + BLI_assert(BKE_main_namemap_validate(bfd->main)); + + if (mode != LOAD_UI) { if (win) { curscene = win->scene; } if (track_undo_scene) { wmWindowManager *wm = static_cast(bfd->main->wm.first); - if (wm_scene_is_visible(wm, bfd->curscene) == false) { + if (!wm_scene_is_visible(wm, bfd->curscene)) { curscene = bfd->curscene; - win->scene = curscene; + if (win) { + win->scene = curscene; + } BKE_screen_view3d_scene_sync(curscreen, curscene); } } @@ -374,48 +831,35 @@ static void setup_app_data(bContext *C, BKE_screen_gizmo_tag_refresh(curscreen); } } + CTX_data_scene_set(C, curscene); + BLI_assert(BKE_main_namemap_validate(bfd->main)); + + /* This frees the `old_bmain`. */ BKE_blender_globals_main_replace(bfd->main); bmain = G_MAIN; bfd->main = nullptr; - CTX_data_main_set(C, bmain); - /* case G_FILE_NO_UI or no screens in file */ - if (mode != LOAD_UI) { - /* leave entire context further unaltered? */ - CTX_data_scene_set(C, curscene); - } - else { + BLI_assert(BKE_main_namemap_validate(bmain)); + + /* These context data should remain valid if old UI is being re-used. */ + if (mode == LOAD_UI) { + /* Setting WindowManager in context clears all other Context UI data (window, area, etc.). So + * only do it when effectively loading a new WM, otherwise just assert that the WM from context + * is still the same as in `new_bmain`. */ CTX_wm_manager_set(C, static_cast(bmain->wm.first)); CTX_wm_screen_set(C, bfd->curscreen); - CTX_data_scene_set(C, bfd->curscene); CTX_wm_area_set(C, nullptr); CTX_wm_region_set(C, nullptr); CTX_wm_menu_set(C, nullptr); - curscene = bfd->curscene; } + BLI_assert(CTX_wm_manager(C) == static_cast(bmain->wm.first)); /* Keep state from preferences. */ const int fileflags_keep = G_FILE_FLAG_ALL_RUNTIME; G.fileflags = (G.fileflags & fileflags_keep) | (bfd->fileflags & ~fileflags_keep); - /* this can happen when active scene was lib-linked, and doesn't exist anymore */ - if (CTX_data_scene(C) == nullptr) { - wmWindow *win = CTX_wm_window(C); - - /* in case we don't even have a local scene, add one */ - if (!bmain->scenes.first) { - BKE_scene_add(bmain, "Empty"); - } - - CTX_data_scene_set(C, static_cast(bmain->scenes.first)); - win->scene = CTX_data_scene(C); - curscene = CTX_data_scene(C); - } - - BLI_assert(curscene == CTX_data_scene(C)); - /* special cases, override loaded flags: */ if (G.f != bfd->globalf) { const int flags_keep = G_FLAG_ALL_RUNTIME; @@ -440,7 +884,7 @@ static void setup_app_data(bContext *C, /* NOTE: readfile's `do_versions` does not allow to create new IDs, and only operates on a single * library at a time. This code needs to operate on the whole Main at once. */ - /* NOTE: Check bmain version (i.e. current blend file version), AND the versions of all the + /* NOTE: Check Main version (i.e. current blend file version), AND the versions of all the * linked libraries. */ if (mode != LOAD_UNDO && !blendfile_or_libraries_versions_atleast(bmain, 302, 1)) { BKE_lib_override_library_main_proxy_convert(bmain, reports); @@ -494,17 +938,11 @@ static void setup_app_data(bContext *C, RE_FreeAllPersistentData(); } - if (mode == LOAD_UNDO) { - /* In undo/redo case, we do a whole lot of magic tricks to avoid having to re-read linked - * data-blocks from libraries (since those are not supposed to change). Unfortunately, that - * means that we do not reset their user count, however we do increase that one when doing - * lib_link on local IDs using linked ones. - * There is no real way to predict amount of changes here, so we have to fully redo - * reference-counting. - * Now that we re-use (and do not liblink in readfile.c) most local data-blocks as well, - * we have to recompute reference-counts for all local IDs too. */ - BKE_main_id_refcount_recompute(bmain, false); - } + /* Both undo and regular file loading can perform some fairly complex ID manipulation, simpler + * and safer to fully redo reference-counting. This is a relatively cheap process anyway. */ + BKE_main_id_refcount_recompute(bmain, false); + + BLI_assert(BKE_main_namemap_validate(bmain)); if (mode != LOAD_UNDO && !USER_EXPERIMENTAL_TEST(&U, no_override_auto_resync)) { reports->duration.lib_overrides_resync = PIL_check_seconds_timer(); @@ -526,13 +964,14 @@ static void setup_app_data(bContext *C, static void setup_app_blend_file_data(bContext *C, BlendFileData *bfd, const BlendFileReadParams *params, + BlendFileReadWMSetupData *wm_setup_data, BlendFileReadReport *reports) { if ((params->skip_flags & BLO_READ_SKIP_USERDEF) == 0) { setup_app_userdef(bfd); } if ((params->skip_flags & BLO_READ_SKIP_DATA) == 0) { - setup_app_data(C, bfd, params, reports); + setup_app_data(C, bfd, params, wm_setup_data, reports); } } @@ -550,13 +989,14 @@ static void handle_subversion_warning(Main *main, BlendFileReadReport *reports) } } -void BKE_blendfile_read_setup_ex(bContext *C, - BlendFileData *bfd, - const BlendFileReadParams *params, - BlendFileReadReport *reports, - /* Extra args. */ - const bool startup_update_defaults, - const char *startup_app_template) +void BKE_blendfile_read_setup_readfile(bContext *C, + BlendFileData *bfd, + const BlendFileReadParams *params, + BlendFileReadWMSetupData *wm_setup_data, + BlendFileReadReport *reports, + /* Extra args. */ + const bool startup_update_defaults, + const char *startup_app_template) { if (bfd->main->is_read_invalid) { BKE_reports_prepend(reports->reports, @@ -570,16 +1010,16 @@ void BKE_blendfile_read_setup_ex(bContext *C, BLO_update_defaults_startup_blend(bfd->main, startup_app_template); } } - setup_app_blend_file_data(C, bfd, params, reports); + setup_app_blend_file_data(C, bfd, params, wm_setup_data, reports); BLO_blendfiledata_free(bfd); } -void BKE_blendfile_read_setup(bContext *C, - BlendFileData *bfd, - const BlendFileReadParams *params, - BlendFileReadReport *reports) +void BKE_blendfile_read_setup_undo(bContext *C, + BlendFileData *bfd, + const BlendFileReadParams *params, + BlendFileReadReport *reports) { - BKE_blendfile_read_setup_ex(C, bfd, params, reports, false, nullptr); + BKE_blendfile_read_setup_readfile(C, bfd, params, nullptr, reports, false, nullptr); } BlendFileData *BKE_blendfile_read(const char *filepath, @@ -636,16 +1076,7 @@ BlendFileData *BKE_blendfile_read_from_memfile(Main *bmain, BLO_blendfiledata_free(bfd); bfd = nullptr; } - if (bfd) { - /* Removing the unused workspaces, screens and wm is useless here, setup_app_data will switch - * those lists with the ones from old bmain, which freeing is much more efficient than - * individual calls to `BKE_id_free()`. - * Further more, those are expected to be empty anyway with new memfile reading code. */ - BLI_assert(BLI_listbase_is_empty(&bfd->main->wm)); - BLI_assert(BLI_listbase_is_empty(&bfd->main->workspaces)); - BLI_assert(BLI_listbase_is_empty(&bfd->main->screens)); - } - else { + if (bfd == nullptr) { BKE_reports_prepend(reports, "Loading failed: "); } return bfd; diff --git a/source/blender/blenloader/BLO_readfile.h b/source/blender/blenloader/BLO_readfile.h index 7e4bfc4d4cf..3fbd960f77f 100644 --- a/source/blender/blenloader/BLO_readfile.h +++ b/source/blender/blenloader/BLO_readfile.h @@ -66,6 +66,12 @@ typedef struct BlendFileData { eBlenFileType type; } BlendFileData; +/** Data used by WM readfile code and BKE's setup_app_data to handle the complex preservation logic + * of WindowManager and other UI data-blocks across blendfile reading prcess. */ +typedef struct BlendFileReadWMSetupData { + struct wmWindowManager *old_wm; /** The existing WM when filereading process is started. */ +} BlendFileReadWMSetupData; + struct BlendFileReadParams { uint skip_flags : 3; /* #eBLOReadSkip */ uint is_startup : 1; @@ -446,17 +452,6 @@ void BLO_library_temp_free(TempLibraryContext *temp_lib_ctx); void *BLO_library_read_struct(struct FileData *fd, struct BHead *bh, const char *blockname); -/* internal function but we need to expose it */ -/** - * Used to link a file (without UI) to the current UI. - * Note that it assumes the old pointers in UI are still valid, so old Main is not freed. - */ -void blo_lib_link_restore(struct Main *oldmain, - struct Main *newmain, - struct wmWindowManager *curwm, - struct Scene *curscene, - struct ViewLayer *cur_view_layer); - typedef void (*BLOExpandDoitCallback)(void *fdhandle, struct Main *mainvar, void *idv); /** diff --git a/source/blender/blenloader/intern/readblenentry.cc b/source/blender/blenloader/intern/readblenentry.cc index 92a23e494a5..96b36d5192e 100644 --- a/source/blender/blenloader/intern/readblenentry.cc +++ b/source/blender/blenloader/intern/readblenentry.cc @@ -455,21 +455,20 @@ BlendFileData *BLO_read_from_memfile(Main *oldmain, fd->skip_flags = eBLOReadSkip(params->skip_flags); STRNCPY(fd->relabase, filepath); - /* separate libraries from old main */ + /* Build old ID map for all old IDs. */ + blo_make_old_idmap_from_main(fd, oldmain); + + /* Separate linked data from old main. */ blo_split_main(&old_mainlist, oldmain); - /* add the library pointers in oldmap lookup */ - blo_add_library_pointer_map(&old_mainlist, fd); + fd->old_mainlist = &old_mainlist; - if ((params->skip_flags & BLO_READ_SKIP_UNDO_OLD_MAIN) == 0) { - /* Build idmap of old main (we only care about local data here, so we can do that after - * split_main() call. */ - blo_make_old_idmap_from_main(fd, static_cast
(old_mainlist.first)); - } - - /* removed packed data from this trick - it's internal data that needs saves */ + /* Removed packed data from this trick - it's internal data that needs saves. */ /* Store all existing ID caches pointers into a mapping, to allow restoring them into newly - * read IDs whenever possible. */ + * read IDs whenever possible. + * + * Note that this is only required for local data, since linked data are always re-used + * 'as-is'. */ blo_cache_storage_init(fd, oldmain); bfd = blo_read_file_internal(fd, filepath); diff --git a/source/blender/blenloader/intern/readfile.cc b/source/blender/blenloader/intern/readfile.cc index 8750eca2b68..bc60cd7a4e5 100644 --- a/source/blender/blenloader/intern/readfile.cc +++ b/source/blender/blenloader/intern/readfile.cc @@ -1302,6 +1302,9 @@ void blo_filedata_free(FileData *fd) if (fd->old_idmap_uuid != nullptr) { BKE_main_idmap_destroy(fd->old_idmap_uuid); } + if (fd->new_idmap_uuid != nullptr) { + BKE_main_idmap_destroy(fd->new_idmap_uuid); + } blo_cache_storage_end(fd); if (fd->bheadmap) { MEM_freeN(fd->bheadmap); @@ -1523,22 +1526,6 @@ void blo_end_packed_pointer_map(FileData *fd, Main *oldmain) } } -void blo_add_library_pointer_map(ListBase *old_mainlist, FileData *fd) -{ - ListBase *lbarray[INDEX_ID_MAX]; - - LISTBASE_FOREACH (Main *, ptr, old_mainlist) { - int i = set_listbasepointers(ptr, lbarray); - while (i--) { - LISTBASE_FOREACH (ID *, id, lbarray[i]) { - oldnewmap_lib_insert(fd, id, id, GS(id->name)); - } - } - } - - fd->old_mainlist = old_mainlist; -} - void blo_make_old_idmap_from_main(FileData *fd, Main *bmain) { if (fd->old_idmap_uuid != nullptr) { @@ -2176,498 +2163,7 @@ static void lib_link_scenes_check_set(Main *bmain) /** \} */ /* -------------------------------------------------------------------- */ -/** \name Read ID: Screen - * \{ */ -/* how to handle user count on pointer restore */ -enum ePointerUserMode { - USER_IGNORE = 0, /* ignore user count */ - USER_REAL = 1, /* ensure at least one real user (fake user ignored) */ -}; - -static void restore_pointer_user(ID *id, ID *newid, ePointerUserMode user) -{ - BLI_assert(STREQ(newid->name + 2, id->name + 2)); - BLI_assert(newid->lib == id->lib); - UNUSED_VARS_NDEBUG(id); - - if (user == USER_REAL) { - id_us_ensure_real(newid); - } -} - -#ifndef USE_GHASH_RESTORE_POINTER -/** - * A version of #restore_pointer_by_name that performs a full search (slow!). - * Use only for limited lookups, when the overhead of - * creating a #IDNameLib_Map for a single lookup isn't worthwhile. - */ -static void *restore_pointer_by_name_main(Main *mainp, ID *id, ePointerUserMode user) -{ - if (id) { - ListBase *lb = which_libbase(mainp, GS(id->name)); - if (lb) { /* there's still risk of checking corrupt mem (freed Ids in oops) */ - ID *idn = lb->first; - for (; idn; idn = idn->next) { - if (STREQ(idn->name + 2, id->name + 2)) { - if (idn->lib == id->lib) { - restore_pointer_user(id, idn, user); - break; - } - } - } - return idn; - } - } - return nullptr; -} -#endif - -/** - * Only for undo files, or to restore a screen after reading without UI... - * - * \param user: - * - USER_IGNORE: no user-count change. - * - USER_REAL: ensure a real user (even if a fake one is set). - * \param id_map: lookup table, use when performing many lookups. - * this could be made an optional argument (falling back to a full lookup), - * however at the moment it's always available. - */ -static void *restore_pointer_by_name(IDNameLib_Map *id_map, ID *id, ePointerUserMode user) -{ -#ifdef USE_GHASH_RESTORE_POINTER - if (id) { - /* use fast lookup when available */ - ID *idn = BKE_main_idmap_lookup_id(id_map, id); - if (idn) { - restore_pointer_user(id, idn, user); - } - return idn; - } - return nullptr; -#else - Main *mainp = BKE_main_idmap_main_get(id_map); - return restore_pointer_by_name_main(mainp, id, user); -#endif -} - -static int lib_link_main_data_restore_cb(LibraryIDLinkCallbackData *cb_data) -{ - const int cb_flag = cb_data->cb_flag; - ID **id_pointer = cb_data->id_pointer; - if (cb_flag & (IDWALK_CB_EMBEDDED | IDWALK_CB_EMBEDDED_NOT_OWNING) || *id_pointer == nullptr) { - return IDWALK_RET_NOP; - } - - IDNameLib_Map *id_map = static_cast(cb_data->user_data); - - /* NOTE: Handling of user-count here is really bad, defining its own system... - * Will have to be refactored at some point, but that is not top priority task for now. - * And all user-counts are properly recomputed at the end of the undo management code anyway. */ - *id_pointer = static_cast(restore_pointer_by_name( - id_map, *id_pointer, (cb_flag & IDWALK_CB_USER_ONE) ? USER_REAL : USER_IGNORE)); - - return IDWALK_RET_NOP; -} - -static void lib_link_main_data_restore(IDNameLib_Map *id_map, Main *newmain) -{ - ID *id; - FOREACH_MAIN_ID_BEGIN (newmain, id) { - BKE_library_foreach_ID_link(newmain, id, lib_link_main_data_restore_cb, id_map, IDWALK_NOP); - } - FOREACH_MAIN_ID_END; -} - -static void lib_link_wm_xr_data_restore(IDNameLib_Map *id_map, wmXrData *xr_data) -{ - xr_data->session_settings.base_pose_object = static_cast(restore_pointer_by_name( - id_map, reinterpret_cast(xr_data->session_settings.base_pose_object), USER_REAL)); -} - -static void lib_link_window_scene_data_restore(wmWindow *win, Scene *scene, ViewLayer *view_layer) -{ - bScreen *screen = BKE_workspace_active_screen_get(win->workspace_hook); - - LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { - LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) { - if (sl->spacetype == SPACE_VIEW3D) { - View3D *v3d = reinterpret_cast(sl); - - if (v3d->camera == nullptr || v3d->scenelock) { - v3d->camera = scene->camera; - } - - if (v3d->localvd) { - Base *base = nullptr; - - v3d->localvd->camera = scene->camera; - - /* Local-view can become invalid during undo/redo steps, - * so we exit it when no could be found. */ - for (base = static_cast(view_layer->object_bases.first); base; base = base->next) - { - if (base->local_view_bits & v3d->local_view_uuid) { - break; - } - } - if (base == nullptr) { - MEM_freeN(v3d->localvd); - v3d->localvd = nullptr; - v3d->local_view_uuid = 0; - - /* Region-base storage is different depending if the space is active. */ - ListBase *regionbase = (sl == area->spacedata.first) ? &area->regionbase : - &sl->regionbase; - LISTBASE_FOREACH (ARegion *, region, regionbase) { - if (region->regiontype == RGN_TYPE_WINDOW) { - RegionView3D *rv3d = static_cast(region->regiondata); - if (rv3d->localvd) { - MEM_freeN(rv3d->localvd); - rv3d->localvd = nullptr; - } - } - } - } - } - } - } - } -} - -static void lib_link_restore_viewer_path(IDNameLib_Map *id_map, ViewerPath *viewer_path) -{ - LISTBASE_FOREACH (ViewerPathElem *, elem, &viewer_path->path) { - if (elem->type == VIEWER_PATH_ELEM_TYPE_ID) { - IDViewerPathElem *typed_elem = reinterpret_cast(elem); - typed_elem->id = static_cast( - restore_pointer_by_name(id_map, typed_elem->id, USER_IGNORE)); - } - } -} - -static void lib_link_workspace_layout_restore(IDNameLib_Map *id_map, - Main *newmain, - WorkSpaceLayout *layout) -{ - bScreen *screen = BKE_workspace_layout_screen_get(layout); - - /* avoid conflicts with 2.8x branch */ - { - LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { - LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) { - switch (static_cast(sl->spacetype)) { - case SPACE_VIEW3D: { - View3D *v3d = reinterpret_cast(sl); - - v3d->camera = static_cast( - restore_pointer_by_name(id_map, reinterpret_cast(v3d->camera), USER_REAL)); - v3d->ob_center = static_cast(restore_pointer_by_name( - id_map, reinterpret_cast(v3d->ob_center), USER_REAL)); - - lib_link_restore_viewer_path(id_map, &v3d->viewer_path); - break; - } - case SPACE_GRAPH: { - SpaceGraph *sipo = reinterpret_cast(sl); - bDopeSheet *ads = sipo->ads; - - if (ads) { - ads->source = static_cast( - restore_pointer_by_name(id_map, reinterpret_cast(ads->source), USER_REAL)); - - if (ads->filter_grp) { - ads->filter_grp = static_cast(restore_pointer_by_name( - id_map, reinterpret_cast(ads->filter_grp), USER_IGNORE)); - } - } - - /* force recalc of list of channels (i.e. includes calculating F-Curve colors) - * thus preventing the "black curves" problem post-undo - */ - sipo->runtime.flag |= SIPO_RUNTIME_FLAG_NEED_CHAN_SYNC_COLOR; - break; - } - case SPACE_PROPERTIES: { - SpaceProperties *sbuts = reinterpret_cast(sl); - sbuts->pinid = static_cast( - restore_pointer_by_name(id_map, sbuts->pinid, USER_IGNORE)); - if (sbuts->pinid == nullptr) { - sbuts->flag &= ~SB_PIN_CONTEXT; - } - - /* TODO: restore path pointers: #40046 - * (complicated because this contains data pointers too, not just ID). */ - MEM_SAFE_FREE(sbuts->path); - break; - } - case SPACE_FILE: { - SpaceFile *sfile = reinterpret_cast(sl); - sfile->op = nullptr; - sfile->tags = FILE_TAG_REBUILD_MAIN_FILES; - break; - } - case SPACE_ACTION: { - SpaceAction *saction = reinterpret_cast(sl); - - saction->action = static_cast(restore_pointer_by_name( - id_map, reinterpret_cast(saction->action), USER_REAL)); - saction->ads.source = static_cast(restore_pointer_by_name( - id_map, reinterpret_cast(saction->ads.source), USER_REAL)); - - if (saction->ads.filter_grp) { - saction->ads.filter_grp = static_cast(restore_pointer_by_name( - id_map, reinterpret_cast(saction->ads.filter_grp), USER_IGNORE)); - } - - /* force recalc of list of channels, potentially updating the active action - * while we're at it (as it can only be updated that way) #28962. - */ - saction->runtime.flag |= SACTION_RUNTIME_FLAG_NEED_CHAN_SYNC; - break; - } - case SPACE_IMAGE: { - SpaceImage *sima = reinterpret_cast(sl); - - sima->image = static_cast( - restore_pointer_by_name(id_map, reinterpret_cast(sima->image), USER_REAL)); - - /* this will be freed, not worth attempting to find same scene, - * since it gets initialized later */ - sima->iuser.scene = nullptr; - -#if 0 - /* Those are allocated and freed by space code, no need to handle them here. */ - MEM_SAFE_FREE(sima->scopes.waveform_1); - MEM_SAFE_FREE(sima->scopes.waveform_2); - MEM_SAFE_FREE(sima->scopes.waveform_3); - MEM_SAFE_FREE(sima->scopes.vecscope); -#endif - sima->scopes.ok = 0; - - /* NOTE: pre-2.5, this was local data not lib data, but now we need this as lib data - * so assume that here we're doing for undo only... - */ - sima->gpd = static_cast( - restore_pointer_by_name(id_map, reinterpret_cast(sima->gpd), USER_REAL)); - sima->mask_info.mask = static_cast(restore_pointer_by_name( - id_map, reinterpret_cast(sima->mask_info.mask), USER_REAL)); - break; - } - case SPACE_SEQ: { - SpaceSeq *sseq = reinterpret_cast(sl); - - /* NOTE: pre-2.5, this was local data not lib data, but now we need this as lib data - * so assume that here we're doing for undo only... - */ - sseq->gpd = static_cast( - restore_pointer_by_name(id_map, reinterpret_cast(sseq->gpd), USER_REAL)); - break; - } - case SPACE_NLA: { - SpaceNla *snla = reinterpret_cast(sl); - bDopeSheet *ads = snla->ads; - - if (ads) { - ads->source = static_cast( - restore_pointer_by_name(id_map, reinterpret_cast(ads->source), USER_REAL)); - - if (ads->filter_grp) { - ads->filter_grp = static_cast(restore_pointer_by_name( - id_map, reinterpret_cast(ads->filter_grp), USER_IGNORE)); - } - } - break; - } - case SPACE_TEXT: { - SpaceText *st = reinterpret_cast(sl); - - st->text = static_cast( - restore_pointer_by_name(id_map, reinterpret_cast(st->text), USER_IGNORE)); - if (st->text == nullptr) { - st->text = static_cast(newmain->texts.first); - } - } break; - case SPACE_SCRIPT: { - SpaceScript *scpt = reinterpret_cast(sl); - - scpt->script = static_cast