Readfile: Refactor several parts of the process #108016
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
mont29 marked this conversation as resolved
Outdated
|
||||
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)) {
|
||||
Jacques Lucke
commented
Would be good to avoid this kind of quadratic algorithms, even though the number of libraries is probably relatively low. To remove the quadratic run-time, just create a Would be good to avoid this kind of quadratic algorithms, even though the number of libraries is probably relatively low. To remove the quadratic run-time, just create a `filepath -> Library *` map for either the new or old `Main` first.
Bastien Montagne
commented
I don't think that this added complexity will ever justify itself. At the very least, I would wait to get a real-life example where this change would give any measurable (and significant) benefit. I don't think that this added complexity will ever justify itself. At the very least, I would wait to get a real-life example where this change would give any measurable (and significant) benefit.
|
||||
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) {
|
||||
mont29 marked this conversation as resolved
Outdated
Bastien Montagne
commented
there should be a break here too, btw. there should be a break here too, btw.
|
||||
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<ID *>(old_lb->first);
|
||||
ID *reused_id_iter = static_cast<ID *>(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<ID *>(discarded_id_iter->next);
|
||||
reused_id_iter = static_cast<ID *>(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<ID *>(discarded_id_iter->next);
|
||||
}
|
||||
else {
|
||||
reused_id_iter = static_cast<ID *>(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<ID *>(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. */
|
||||
mont29 marked this conversation as resolved
Outdated
Jacques Lucke
commented
Could return early when Could return early when `old_wm == nullptr` to avoid indentation.
|
||||
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<wmWindowManager *>(old_wm_list->first);
|
||||
wmWindowManager *new_wm = static_cast<wmWindowManager *>(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<wmWindowManager *>(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<ReuseOldBMainData *>(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<View3D *>(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<Base *>(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<RegionView3D *>(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<Scene *>(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<Scene *>(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<wmWindowManager *>(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<wmWindowManager *>(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<wmWindowManager *>(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<Scene *>(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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
/**
|
||||
|
|
|
@ -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<Main *>(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);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -119,6 +119,16 @@ typedef struct FileData {
|
|||
* IDMap using UUID's as keys of all the old IDs in the old bmain. Used during undo to find a
|
||||
* matching old data when reading a new ID. */
|
||||
struct IDNameLib_Map *old_idmap_uuid;
|
||||
/**
|
||||
* IDMap using uuids as keys of the IDs read (or moved) in the new main(s).
|
||||
*
|
||||
* Used during undo to ensure that the ID pointers from the 'no undo' IDs remain valid (these
|
||||
* IDs are re-used from old main even if their content is not the same as in the memfile undo
|
||||
* step, so they could point e.g. to an ID that does not exist in the newly read undo step).
|
||||
*
|
||||
* Also used to find current valid pointers (or none) of these 'no undo' IDs existing in
|
||||
* read memfile. */
|
||||
struct IDNameLib_Map *new_idmap_uuid;
|
||||
|
||||
struct BlendFileReadReport *reports;
|
||||
} FileData;
|
||||
|
@ -151,10 +161,6 @@ void blo_make_packed_pointer_map(FileData *fd, struct Main *oldmain);
|
|||
* this works because freeing old main only happens after this call.
|
||||
*/
|
||||
void blo_end_packed_pointer_map(FileData *fd, struct Main *oldmain);
|
||||
/**
|
||||
* Undo file support: add all library pointers in lookup.
|
||||
*/
|
||||
void blo_add_library_pointer_map(ListBase *old_mainlist, FileData *fd);
|
||||
/**
|
||||
* Build a #GSet of old main (we only care about local data here,
|
||||
* so we can do that after #blo_split_main() call.
|
||||
|
|
|
@ -280,7 +280,8 @@ static void memfile_undosys_step_decode(
|
|||
|
||||
FOREACH_MAIN_ID_BEGIN (bmain, id) {
|
||||
/* Clear temporary tag. */
|
||||
id->tag &= ~(LIB_TAG_UNDO_OLD_ID_REUSED_UNCHANGED | LIB_TAG_UNDO_OLD_ID_REREAD_IN_PLACE);
|
||||
id->tag &= ~(LIB_TAG_UNDO_OLD_ID_REUSED_UNCHANGED | LIB_TAG_UNDO_OLD_ID_REUSED_NOUNDO |
|
||||
LIB_TAG_UNDO_OLD_ID_REREAD_IN_PLACE);
|
||||
|
||||
/* We only start accumulating from this point, any tags set up to here
|
||||
* are already part of the current undo state. This is done in a second
|
||||
|
|
|
@ -874,12 +874,23 @@ enum {
|
|||
*/
|
||||
LIB_TAG_UNDO_OLD_ID_REUSED_UNCHANGED = 1 << 17,
|
||||
/**
|
||||
* ID has be re-read in-place, the ID address is the same as in the old BMain, but the content is
|
||||
* ID is being re-used from the old Main (instead of read from memfile), during memfile undo
|
||||
* processing, because it is a 'NO_UNDO' type of ID.
|
||||
*
|
||||
* \note: Also means that such ID does not need to be lib-linked during undo readfile process. It
|
||||
* does need to be relinked in a different way however, doing a `session_uuid`-based lookup into
|
||||
* the newly read main database.
|
||||
*
|
||||
* RESET_AFTER_USE
|
||||
*/
|
||||
LIB_TAG_UNDO_OLD_ID_REUSED_NOUNDO = 1 << 18,
|
||||
/**
|
||||
* ID has be re-read in-place, the ID address is the same as in the old main, but the content is
|
||||
* different.
|
||||
*
|
||||
* RESET_AFTER_USE
|
||||
*/
|
||||
LIB_TAG_UNDO_OLD_ID_REREAD_IN_PLACE = 1 << 18,
|
||||
LIB_TAG_UNDO_OLD_ID_REREAD_IN_PLACE = 1 << 19,
|
||||
|
||||
/* ------------------------------------------------------------------------------------------- */
|
||||
/**
|
||||
|
|
|
@ -617,21 +617,6 @@ void wm_close_and_free(bContext *C, wmWindowManager *wm)
|
|||
}
|
||||
}
|
||||
|
||||
void wm_close_and_free_all(bContext *C, ListBase *wmlist)
|
||||
{
|
||||
wmWindowManager *wm;
|
||||
while ((wm = wmlist->first)) {
|
||||
wm_close_and_free(C, wm);
|
||||
BLI_remlink(wmlist, wm);
|
||||
/* Don't handle user counts as this is only ever called once #G_MAIN has already been freed via
|
||||
* #BKE_main_free so any ID's referenced by the window-manager (from ID properties) will crash.
|
||||
* See: #100703. */
|
||||
BKE_libblock_free_data(&wm->id, false);
|
||||
BKE_libblock_free_data_py(&wm->id);
|
||||
MEM_freeN(wm);
|
||||
}
|
||||
}
|
||||
|
||||
void WM_main(bContext *C)
|
||||
{
|
||||
/* Single refresh before handling events.
|
||||
|
|
|
@ -177,45 +177,46 @@ bool wm_file_or_session_data_has_unsaved_changes(const Main *bmain, const wmWind
|
|||
* - restoring the screens from non-active windows
|
||||
* Best case is all screens match, in that case they get assigned to proper window.
|
||||
*/
|
||||
static void wm_window_match_init(bContext *C, ListBase *wmlist)
|
||||
|
||||
/**
|
||||
* Clear several WM/UI runtime data that would make later complex WM handling impossible.
|
||||
*
|
||||
* Return data should be cleared by #wm_file_read_setup_wm_finalize. */
|
||||
static BlendFileReadWMSetupData *wm_file_read_setup_wm_init(bContext *C, Main *bmain)
|
||||
{
|
||||
*wmlist = G_MAIN->wm;
|
||||
BLI_assert(BLI_listbase_count_at_most(&bmain->wm, 2) <= 1);
|
||||
wmWindowManager *wm = static_cast<wmWindowManager *>(bmain->wm.first);
|
||||
BlendFileReadWMSetupData *wm_setup_data = MEM_cnew<BlendFileReadWMSetupData>(__func__);
|
||||
|
||||
if (wm == nullptr) {
|
||||
return wm_setup_data;
|
||||
}
|
||||
|
||||
/* First wrap up running stuff.
|
||||
*
|
||||
* Code copied from wm_init_exit.cc */
|
||||
WM_jobs_kill_all(wm);
|
||||
|
||||
wmWindow *active_win = CTX_wm_window(C);
|
||||
|
||||
/* first wrap up running stuff */
|
||||
/* code copied from wm_init_exit.cc */
|
||||
LISTBASE_FOREACH (wmWindowManager *, wm, wmlist) {
|
||||
WM_jobs_kill_all(wm);
|
||||
|
||||
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
|
||||
CTX_wm_window_set(C, win); /* needed by operator close callbacks */
|
||||
WM_event_remove_handlers(C, &win->handlers);
|
||||
WM_event_remove_handlers(C, &win->modalhandlers);
|
||||
ED_screen_exit(C, win, WM_window_get_active_screen(win));
|
||||
}
|
||||
|
||||
/* NOTE(@ideasman42): Clear the message bus so it's always cleared on file load.
|
||||
* Otherwise it's cleared when "Load UI" is set (see #USER_FILENOUI & #wm_close_and_free).
|
||||
* However it's _not_ cleared when the UI is kept. This complicates use from add-ons
|
||||
* which can re-register subscribers on file-load. To support this use case,
|
||||
* it's best to have predictable behavior - always clear. */
|
||||
if (wm->message_bus != nullptr) {
|
||||
WM_msgbus_destroy(wm->message_bus);
|
||||
wm->message_bus = nullptr;
|
||||
}
|
||||
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
|
||||
CTX_wm_window_set(C, win); /* Needed by operator close callbacks. */
|
||||
WM_event_remove_handlers(C, &win->handlers);
|
||||
WM_event_remove_handlers(C, &win->modalhandlers);
|
||||
ED_screen_exit(C, win, WM_window_get_active_screen(win));
|
||||
}
|
||||
|
||||
BLI_listbase_clear(&G_MAIN->wm);
|
||||
if (G_MAIN->name_map != nullptr) {
|
||||
/* NOTE: UI IDs are assumed to be only local data-blocks, so no need to call
|
||||
* #BKE_main_namemap_clear here. */
|
||||
BKE_main_namemap_destroy(&G_MAIN->name_map);
|
||||
}
|
||||
|
||||
/* reset active window */
|
||||
/* Reset active window. */
|
||||
CTX_wm_window_set(C, active_win);
|
||||
|
||||
/* NOTE(@ideasman42): Clear the message bus so it's always cleared on file load.
|
||||
* Otherwise it's cleared when "Load UI" is set (see #USER_FILENOUI and #wm_close_and_free).
|
||||
* However it's _not_ cleared when the UI is kept. This complicates use from add-ons
|
||||
* which can re-register subscribers on file-load. To support this use case,
|
||||
* it's best to have predictable behavior - always clear. */
|
||||
if (wm->message_bus != nullptr) {
|
||||
WM_msgbus_destroy(wm->message_bus);
|
||||
wm->message_bus = nullptr;
|
||||
}
|
||||
|
||||
/* XXX Hack! We have to clear context menu here, because removing all modalhandlers
|
||||
* above frees the active menu (at least, in the 'startup splash' case),
|
||||
* causing use-after-free error in later handling of the button callbacks in UI code
|
||||
|
@ -225,18 +226,23 @@ static void wm_window_match_init(bContext *C, ListBase *wmlist)
|
|||
* so for now just handling this specific case here. */
|
||||
CTX_wm_menu_set(C, nullptr);
|
||||
|
||||
ED_editors_exit(G_MAIN, true);
|
||||
ED_editors_exit(bmain, true);
|
||||
|
||||
/* Asset loading is done by the UI/editors and they keep pointers into it. So make sure to clear
|
||||
* it after UI/editors. */
|
||||
ED_assetlist_storage_exit();
|
||||
AS_asset_libraries_exit();
|
||||
|
||||
/* NOTE: `wm_setup_data->old_wm` cannot be set here, as this pointer may be swapped with the
|
||||
* newly read one in `setup_app_data` process (See #swap_wm_data_for_blendfile). */
|
||||
|
||||
return wm_setup_data;
|
||||
}
|
||||
|
||||
static void wm_window_substitute_old(wmWindowManager *oldwm,
|
||||
wmWindowManager *wm,
|
||||
wmWindow *oldwin,
|
||||
wmWindow *win)
|
||||
static void wm_file_read_setup_wm_substitute_old_window(wmWindowManager *oldwm,
|
||||
wmWindowManager *wm,
|
||||
wmWindow *oldwin,
|
||||
wmWindow *win)
|
||||
{
|
||||
win->ghostwin = oldwin->ghostwin;
|
||||
win->gpuctx = oldwin->gpuctx;
|
||||
|
@ -279,20 +285,24 @@ static void wm_window_substitute_old(wmWindowManager *oldwm,
|
|||
* An alternative solution could also be to close all windows except the first however this is
|
||||
* enough of a corner case that it's the current behavior is acceptable.
|
||||
*/
|
||||
static void wm_window_match_keep_current_wm(const bContext *C,
|
||||
ListBase *current_wm_list,
|
||||
const bool load_ui,
|
||||
ListBase *r_new_wm_list)
|
||||
static void wm_file_read_setup_wm_keep_old(const bContext *C,
|
||||
Main *bmain,
|
||||
BlendFileReadWMSetupData * /*wm_setup_data*/,
|
||||
wmWindowManager *wm,
|
||||
const bool load_ui)
|
||||
{
|
||||
Main *bmain = CTX_data_main(C);
|
||||
wmWindowManager *wm = static_cast<wmWindowManager *>(current_wm_list->first);
|
||||
bScreen *screen = nullptr;
|
||||
if (!load_ui) {
|
||||
/* When loading without UI (i.e. keeping existing UI), no matching needed.
|
||||
*
|
||||
* The other UI data (workspaces, layouts, screens) has also been re-used from old Main, and
|
||||
* newly read one from file has already been discarded in #setup_app_data. */
|
||||
return;
|
||||
}
|
||||
|
||||
/* match oldwm to new dbase, only old files */
|
||||
wm->init_flag &= ~WM_INIT_FLAG_WINDOW;
|
||||
|
||||
/* when loading without UI, no matching needed */
|
||||
if (load_ui && (screen = CTX_wm_screen(C))) {
|
||||
/* Old WM is being reused, but other UI data (workspaces, layouts, screens) comes from the new
|
||||
* file, so the WM needs to be updated to use these. */
|
||||
bScreen *screen = CTX_wm_screen(C);
|
||||
if (screen != nullptr) {
|
||||
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
|
||||
WorkSpace *workspace;
|
||||
|
||||
|
@ -320,140 +330,102 @@ static void wm_window_match_keep_current_wm(const bContext *C,
|
|||
win_screen->winid = win->winid;
|
||||
}
|
||||
}
|
||||
|
||||
/* we'll be using the current wm list directly; make sure
|
||||
* the names are validated and in the name map. */
|
||||
LISTBASE_FOREACH (wmWindowManager *, wm_item, current_wm_list) {
|
||||
BKE_main_namemap_get_name(bmain, &wm_item->id, wm_item->id.name + 2);
|
||||
}
|
||||
|
||||
*r_new_wm_list = *current_wm_list;
|
||||
}
|
||||
|
||||
static void wm_window_match_replace_by_file_wm(bContext *C,
|
||||
ListBase *current_wm_list,
|
||||
ListBase *readfile_wm_list,
|
||||
ListBase *r_new_wm_list)
|
||||
static void wm_file_read_setup_wm_use_new(bContext *C,
|
||||
Main * /* bmain */,
|
||||
BlendFileReadWMSetupData *wm_setup_data,
|
||||
wmWindowManager *wm)
|
||||
{
|
||||
wmWindowManager *oldwm = static_cast<wmWindowManager *>(current_wm_list->first);
|
||||
/* will become our new WM */
|
||||
wmWindowManager *wm = static_cast<wmWindowManager *>(readfile_wm_list->first);
|
||||
wmWindowManager *old_wm = wm_setup_data->old_wm;
|
||||
|
||||
/* 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 */
|
||||
{
|
||||
/* Referencing the window-manager pointer from elsewhere in the file is highly unlikely
|
||||
* however it's possible with ID-properties & animation-drivers.
|
||||
* At some point we could check on disallowing this since it doesn't seem practical. */
|
||||
Main *bmain = G_MAIN;
|
||||
BLI_assert(bmain->relations == nullptr);
|
||||
BKE_libblock_remap(bmain, wm, oldwm, ID_REMAP_SKIP_INDIRECT_USAGE | ID_REMAP_SKIP_USER_CLEAR);
|
||||
wm->op_undo_depth = old_wm->op_undo_depth;
|
||||
|
||||
/* Maintain the undo-depth between file loads. Useful so Python can perform
|
||||
* nested operator calls that exit with the proper undo-depth. */
|
||||
wm->op_undo_depth = oldwm->op_undo_depth;
|
||||
/* Move existing key configurations into the new WM. */
|
||||
wm->keyconfigs = old_wm->keyconfigs;
|
||||
wm->addonconf = old_wm->addonconf;
|
||||
wm->defaultconf = old_wm->defaultconf;
|
||||
wm->userconf = old_wm->userconf;
|
||||
|
||||
/* Simple pointer swapping step. */
|
||||
BLI_remlink(current_wm_list, oldwm);
|
||||
BLI_remlink(readfile_wm_list, wm);
|
||||
SWAP(wmWindowManager, *oldwm, *wm);
|
||||
SWAP(wmWindowManager *, oldwm, wm);
|
||||
BLI_addhead(current_wm_list, oldwm);
|
||||
BLI_addhead(readfile_wm_list, wm);
|
||||
BLI_listbase_clear(&old_wm->keyconfigs);
|
||||
old_wm->addonconf = nullptr;
|
||||
old_wm->defaultconf = nullptr;
|
||||
old_wm->userconf = nullptr;
|
||||
|
||||
/* Don't leave the old pointer in the context. */
|
||||
CTX_wm_manager_set(C, wm);
|
||||
}
|
||||
|
||||
bool has_match = false;
|
||||
|
||||
/* this code could move to setup_appdata */
|
||||
|
||||
/* preserve key configurations in new wm, to preserve their keymaps */
|
||||
wm->keyconfigs = oldwm->keyconfigs;
|
||||
wm->addonconf = oldwm->addonconf;
|
||||
wm->defaultconf = oldwm->defaultconf;
|
||||
wm->userconf = oldwm->userconf;
|
||||
|
||||
BLI_listbase_clear(&oldwm->keyconfigs);
|
||||
oldwm->addonconf = nullptr;
|
||||
oldwm->defaultconf = nullptr;
|
||||
oldwm->userconf = nullptr;
|
||||
|
||||
/* ensure making new keymaps and set space types */
|
||||
/* Ensure new keymaps are made, and space types are set. */
|
||||
wm->init_flag = 0;
|
||||
wm->winactive = nullptr;
|
||||
|
||||
/* Clearing drawable of before deleting any context
|
||||
* to avoid clearing the wrong wm. */
|
||||
wm_window_clear_drawable(oldwm);
|
||||
/* Clearing drawable of old WM before deleting any context to avoid clearing the wrong wm. */
|
||||
wm_window_clear_drawable(old_wm);
|
||||
|
||||
/* Only first `wm` in list has GHOST-windows. */
|
||||
bool has_match = false;
|
||||
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
|
||||
LISTBASE_FOREACH (wmWindow *, oldwin, &oldwm->windows) {
|
||||
if (oldwin->winid == win->winid) {
|
||||
LISTBASE_FOREACH (wmWindow *, old_win, &old_wm->windows) {
|
||||
if (old_win->winid == win->winid) {
|
||||
has_match = true;
|
||||
|
||||
wm_window_substitute_old(oldwm, wm, oldwin, win);
|
||||
wm_file_read_setup_wm_substitute_old_window(old_wm, wm, old_win, win);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* make sure at least one window is kept open so we don't lose the context, check #42303 */
|
||||
/* Ensure that at least one window is kept open so we don't lose the context, see #42303. */
|
||||
if (!has_match) {
|
||||
wm_window_substitute_old(oldwm,
|
||||
wm,
|
||||
static_cast<wmWindow *>(oldwm->windows.first),
|
||||
static_cast<wmWindow *>(wm->windows.first));
|
||||
wm_file_read_setup_wm_substitute_old_window(old_wm,
|
||||
wm,
|
||||
static_cast<wmWindow *>(old_wm->windows.first),
|
||||
static_cast<wmWindow *>(wm->windows.first));
|
||||
}
|
||||
|
||||
wm_close_and_free_all(C, current_wm_list);
|
||||
|
||||
*r_new_wm_list = *readfile_wm_list;
|
||||
wm_setup_data->old_wm = nullptr;
|
||||
wm_close_and_free(C, old_wm);
|
||||
/* Don't handle user counts as this is only ever called once #G_MAIN has already been freed via
|
||||
* #BKE_main_free so any access to ID's referenced by the window-manager (from ID properties)
|
||||
* will crash. See: #100703. */
|
||||
BKE_libblock_free_data(&old_wm->id, false);
|
||||
BKE_libblock_free_data_py(&old_wm->id);
|
||||
MEM_freeN(old_wm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Match old WM with new, 4 cases:
|
||||
* 1) No current WM, no WM in file: Make new default.
|
||||
* 2) No current WM, but WM in file: Keep current WM, do nothing else.
|
||||
* 3) Current WM, but not in file: Keep current WM, update windows with screens from file.
|
||||
* 4) Current WM, and WM in file: Try to keep current GHOST windows, use WM from file.
|
||||
* Finalize setting up the WM for the newly read file, transferring GHOST windows from the old WM
|
||||
* if needed, updating other UI data, etc. And free the old WM if any.
|
||||
*
|
||||
* \param r_new_wm_list: Return argument for the wm list to be used from now on.
|
||||
* Counterpart of #wm_file_read_setup_wm_init.
|
||||
*/
|
||||
static void wm_window_match_do(bContext *C,
|
||||
ListBase *current_wm_list,
|
||||
ListBase *readfile_wm_list,
|
||||
ListBase *r_new_wm_list)
|
||||
static void wm_file_read_setup_wm_finalize(bContext *C,
|
||||
Main *bmain,
|
||||
BlendFileReadWMSetupData *wm_setup_data)
|
||||
{
|
||||
if (BLI_listbase_is_empty(current_wm_list)) {
|
||||
/* case 1 */
|
||||
if (BLI_listbase_is_empty(readfile_wm_list)) {
|
||||
Main *bmain = CTX_data_main(C);
|
||||
/* Neither current, no newly read file have a WM -> add the default one. */
|
||||
wm_add_default(bmain, C);
|
||||
*r_new_wm_list = bmain->wm;
|
||||
BLI_assert(BLI_listbase_count_at_most(&bmain->wm, 2) <= 1);
|
||||
BLI_assert(wm_setup_data != nullptr);
|
||||
wmWindowManager *wm = static_cast<wmWindowManager *>(bmain->wm.first);
|
||||
|
||||
if (wm == nullptr) {
|
||||
/* Add a default WM in case none exists in newly read main (should only happen when opening
|
||||
* an old pre-2.5 .blend file at startup). */
|
||||
wm_add_default(bmain, C);
|
||||
}
|
||||
else if (wm_setup_data->old_wm != nullptr) {
|
||||
if (wm_setup_data->old_wm == wm) {
|
||||
/* Old WM was kept, update it with new workspaces/layouts/screens read from file.
|
||||
*
|
||||
* Happens when not loading UI, or when the newly read file has no WM (pre-2.5 files). */
|
||||
wm_file_read_setup_wm_keep_old(
|
||||
C, bmain, wm_setup_data, wm, (G.fileflags & G_FILE_NO_UI) == 0);
|
||||
}
|
||||
/* case 2 */
|
||||
else {
|
||||
*r_new_wm_list = *readfile_wm_list;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* case 3 */
|
||||
if (BLI_listbase_is_empty(readfile_wm_list)) {
|
||||
/* We've read file without wm, keep current one entirely alive.
|
||||
* Happens when reading pre 2.5 files (no WM back then) */
|
||||
wm_window_match_keep_current_wm(
|
||||
C, current_wm_list, (G.fileflags & G_FILE_NO_UI) == 0, r_new_wm_list);
|
||||
}
|
||||
/* case 4 */
|
||||
else {
|
||||
wm_window_match_replace_by_file_wm(C, current_wm_list, readfile_wm_list, r_new_wm_list);
|
||||
/* Using new WM from read file, try to keep current GHOST windows, transfer keymaps, etc.,
|
||||
* from old WM.
|
||||
*
|
||||
* Also takes care of clearing old WM data (temporarily stored in `wm_setup_data->old_wm`).
|
||||
*/
|
||||
wm_file_read_setup_wm_use_new(C, bmain, wm_setup_data, wm);
|
||||
}
|
||||
}
|
||||
/* Else just using the new WM read from file, nothing to do. */
|
||||
BLI_assert(wm_setup_data->old_wm == nullptr);
|
||||
MEM_delete(wm_setup_data);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
@ -992,6 +964,8 @@ bool WM_file_read(bContext *C, const char *filepath, ReportList *reports)
|
|||
/* NOTE: a matching #wm_read_callback_post_wrapper must be called. */
|
||||
wm_read_callback_pre_wrapper(C, filepath);
|
||||
|
||||
Main *bmain = CTX_data_main(C);
|
||||
|
||||
/* so we can get the error message */
|
||||
errno = 0;
|
||||
|
||||
|
@ -1018,16 +992,22 @@ bool WM_file_read(bContext *C, const char *filepath, ReportList *reports)
|
|||
if (bfd != nullptr) {
|
||||
wm_file_read_pre(use_data, use_userdef);
|
||||
|
||||
/* Put aside screens to match with persistent windows later,
|
||||
* also exit screens and editors. */
|
||||
ListBase wmbase;
|
||||
wm_window_match_init(C, &wmbase);
|
||||
/* Put WM into a stable state for post-readfile processes (kill jobs, removes event handlers,
|
||||
* message bus, and so on). */
|
||||
BlendFileReadWMSetupData *wm_setup_data = wm_file_read_setup_wm_init(C, bmain);
|
||||
|
||||
/* This flag is initialized by the operator but overwritten on read.
|
||||
* need to re-enable it here else drivers and registered scripts won't work. */
|
||||
const int G_f_orig = G.f;
|
||||
|
||||
BKE_blendfile_read_setup(C, bfd, ¶ms, &bf_reports);
|
||||
/* Frees the current main and replaces it with the new one read from file. */
|
||||
BKE_blendfile_read_setup_readfile(
|
||||
C, bfd, ¶ms, wm_setup_data, &bf_reports, false, nullptr);
|
||||
bmain = CTX_data_main(C);
|
||||
|
||||
/* Finalize handling of WM, using the read WM and/or the current WM depending on things like
|
||||
* whether the UI is loaded from the .blend file or not, etc. */
|
||||
wm_file_read_setup_wm_finalize(C, bmain, wm_setup_data);
|
||||
|
||||
if (G.f != G_f_orig) {
|
||||
const int flags_keep = G_FLAG_ALL_RUNTIME;
|
||||
|
@ -1035,11 +1015,6 @@ bool WM_file_read(bContext *C, const char *filepath, ReportList *reports)
|
|||
G.f = (G.f & ~flags_keep) | (G_f_orig & flags_keep);
|
||||
}
|
||||
|
||||
/* #BKE_blendfile_read_result_setup sets new Main into context. */
|
||||
Main *bmain = CTX_data_main(C);
|
||||
|
||||
/* match the read WM with current WM */
|
||||
wm_window_match_do(C, &wmbase, &bmain->wm, &bmain->wm);
|
||||
WM_check(C); /* opens window(s), checks keymaps */
|
||||
|
||||
if (do_history_file_update) {
|
||||
|
@ -1186,6 +1161,9 @@ void wm_homefile_read_ex(bContext *C,
|
|||
const char *app_template = nullptr;
|
||||
bool update_defaults = false;
|
||||
|
||||
/* Current Main is not always available in context here. */
|
||||
Main *bmain = G_MAIN;
|
||||
|
||||
if (filepath_startup_override != nullptr) {
|
||||
/* pass */
|
||||
}
|
||||
|
@ -1241,9 +1219,11 @@ void wm_homefile_read_ex(bContext *C,
|
|||
* so we know this will work if all else fails. */
|
||||
wm_file_read_pre(use_data, use_userdef);
|
||||
|
||||
BlendFileReadWMSetupData *wm_setup_data = nullptr;
|
||||
if (use_data) {
|
||||
/* put aside screens to match with persistent windows later */
|
||||
wm_window_match_init(C, &wmbase);
|
||||
/* Put WM into a stable state for post-readfile processes (kill jobs, removes event handlers,
|
||||
* message bus, and so on). */
|
||||
wm_setup_data = wm_file_read_setup_wm_init(C, bmain);
|
||||
}
|
||||
|
||||
filepath_startup[0] = '\0';
|
||||
|
@ -1333,9 +1313,16 @@ void wm_homefile_read_ex(bContext *C,
|
|||
BlendFileData *bfd = BKE_blendfile_read(filepath_startup, ¶ms, &bf_reports);
|
||||
|
||||
if (bfd != nullptr) {
|
||||
BKE_blendfile_read_setup_ex(
|
||||
C, bfd, ¶ms, &bf_reports, update_defaults && use_data, app_template);
|
||||
/* Frees the current main and replaces it with the new one read from file. */
|
||||
BKE_blendfile_read_setup_readfile(C,
|
||||
bfd,
|
||||
¶ms,
|
||||
wm_setup_data,
|
||||
&bf_reports,
|
||||
update_defaults && use_data,
|
||||
app_template);
|
||||
success = true;
|
||||
bmain = CTX_data_main(C);
|
||||
}
|
||||
}
|
||||
if (success) {
|
||||
|
@ -1364,8 +1351,11 @@ void wm_homefile_read_ex(bContext *C,
|
|||
datatoc_startup_blend, datatoc_startup_blend_size, &read_file_params, nullptr);
|
||||
if (bfd != nullptr) {
|
||||
BlendFileReadReport read_report{};
|
||||
BKE_blendfile_read_setup_ex(C, bfd, &read_file_params, &read_report, true, nullptr);
|
||||
/* Frees the current main and replaces it with the new one read from file. */
|
||||
BKE_blendfile_read_setup_readfile(
|
||||
C, bfd, &read_file_params, wm_setup_data, &read_report, true, nullptr);
|
||||
success = true;
|
||||
bmain = CTX_data_main(C);
|
||||
}
|
||||
|
||||
if (use_data && BLI_listbase_is_empty(&wmbase)) {
|
||||
|
@ -1415,16 +1405,15 @@ void wm_homefile_read_ex(bContext *C,
|
|||
STRNCPY(U.app_template, app_template_override);
|
||||
}
|
||||
|
||||
Main *bmain = CTX_data_main(C);
|
||||
|
||||
if (use_userdef) {
|
||||
/* check userdef before open window, keymaps etc */
|
||||
wm_init_userdef(bmain);
|
||||
}
|
||||
|
||||
if (use_data) {
|
||||
/* match the read WM with current WM */
|
||||
wm_window_match_do(C, &wmbase, &bmain->wm, &bmain->wm);
|
||||
/* Finalize handling of WM, using the read WM and/or the current WM depending on things like
|
||||
* whether the UI is loaded from the .blend file or not, etc. */
|
||||
wm_file_read_setup_wm_finalize(C, bmain, wm_setup_data);
|
||||
}
|
||||
|
||||
if (use_userdef) {
|
||||
|
|
|
@ -38,7 +38,6 @@ void wm_exit_schedule_delayed(const bContext *C);
|
|||
* Context is allowed to be NULL, do not free wm itself (lib_id.c).
|
||||
*/
|
||||
extern void wm_close_and_free(bContext *C, wmWindowManager *);
|
||||
extern void wm_close_and_free_all(bContext *C, ListBase *);
|
||||
|
||||
/**
|
||||
* On startup, it adds all data, for matching.
|
||||
|
|
Loading…
Reference in New Issue
We generally still use
LISTBASE_FOREACH
in C++ code, because writing for loops like you did here does not really improve readability or type safety.