Files
test/source/blender/blenkernel/BKE_blendfile.hh

436 lines
18 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bke
*/
#include "BKE_main.hh"
#include "BLI_function_ref.hh"
#include "BLI_map.hh"
#include "BLI_utility_mixins.hh"
#include <string>
struct bContext;
struct BlendFileData;
struct BlendFileReadParams;
struct BlendFileReadReport;
struct BlendFileReadWMSetupData;
struct ID;
struct IDNameLib_Map;
struct Library;
struct LibraryIDLinkCallbackData;
struct MemFile;
struct ReportList;
struct UserDef;
struct WorkspaceConfigFileData;
/**
* The suffix used for blend-files managed by the asset system.
*/
#define BLENDER_ASSET_FILE_SUFFIX ".asset.blend"
/**
* Check whether given path ends with a blend file compatible extension
* (`.blend`, `.ble` or `.blend.gz`).
*
* \param str: The path to check.
* \return true is this path ends with a blender file extension.
*/
bool BKE_blendfile_extension_check(const char *str);
/**
* Try to explode given path into its 'library components'
* (i.e. a .blend file, id type/group, and data-block itself).
*
* \param path: the full path to explode.
* \param r_dir: the string that'll contain path up to blend file itself ('library' path).
* WARNING! Must be at least #FILE_MAX_LIBEXTRA long (it also stores group and name strings)!
* \param r_group: a pointer within `r_dir` to the 'group' part of the path, if any ('\0'
* terminated). May be NULL.
* \param r_name: a pointer within `r_dir` to the data-block name, if any ('\0' terminated). May be
* NULL.
* \return true if path contains a blend file.
*/
bool BKE_blendfile_library_path_explode(const char *path,
char *r_dir,
char **r_group,
char **r_name);
/**
* Check whether a given path is actually a Blender-readable, valid .blend file.
*
* \note Currently does attempt to open and read (part of) the given file.
*/
bool BKE_blendfile_is_readable(const char *path, ReportList *reports);
/**
* Shared setup function that makes the data from `bfd` into the current blend file,
* replacing the contents of #G.main.
* 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_readfile(bContext *C,
BlendFileData *bfd,
const BlendFileReadParams *params,
BlendFileReadWMSetupData *wm_setup_data,
BlendFileReadReport *reports,
bool startup_update_defaults,
const char *startup_app_template);
/**
* Simpler version of #BKE_blendfile_read_setup_readfile used when reading undo steps from
* memfile.
*/
void BKE_blendfile_read_setup_undo(bContext *C,
BlendFileData *bfd,
const BlendFileReadParams *params,
BlendFileReadReport *reports);
/**
* \return Blend file data, this must be passed to
* #BKE_blendfile_read_setup_readfile/#BKE_blendfile_read_setup_undo when non-NULL.
*/
BlendFileData *BKE_blendfile_read(const char *filepath,
const BlendFileReadParams *params,
BlendFileReadReport *reports);
/**
* \return Blend file data, this must be passed to
* #BKE_blendfile_read_setup_readfile/#BKE_blendfile_read_setup_undo when non-NULL.
*/
BlendFileData *BKE_blendfile_read_from_memory(const void *file_buf,
int file_buf_size,
const BlendFileReadParams *params,
ReportList *reports);
/**
* \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.
*/
BlendFileData *BKE_blendfile_read_from_memfile(Main *bmain,
MemFile *memfile,
const BlendFileReadParams *params,
ReportList *reports);
/**
* Utility to make a file 'empty' used for startup to optionally give an empty file.
* Handy for tests.
*/
void BKE_blendfile_read_make_empty(bContext *C);
/**
* Only read the #UserDef from a .blend.
*/
UserDef *BKE_blendfile_userdef_read(const char *filepath, ReportList *reports);
UserDef *BKE_blendfile_userdef_read_from_memory(const void *file_buf,
int file_buf_size,
ReportList *reports);
UserDef *BKE_blendfile_userdef_from_defaults();
/**
* Only write the #UserDef in a `.blend`.
* \return success.
*/
bool BKE_blendfile_userdef_write(const char *filepath, ReportList *reports);
/**
* Only write the #UserDef in a `.blend`, merging with the existing blend file.
* \return success.
*
* \note In the future we should re-evaluate user preferences,
* possibly splitting out system/hardware specific preferences.
*/
bool BKE_blendfile_userdef_write_app_template(const char *filepath, ReportList *reports);
bool BKE_blendfile_userdef_write_all(ReportList *reports);
WorkspaceConfigFileData *BKE_blendfile_workspace_config_read(const char *filepath,
const void *file_buf,
int file_buf_size,
ReportList *reports);
void BKE_blendfile_workspace_config_data_free(WorkspaceConfigFileData *workspace_config);
namespace blender::bke::blendfile {
/**
* Partial blendfile writing.
*
* This wrapper around the Main struct is designed to have a very short life span, during which it
* will contain independent copies of the IDs that are added to it.
*
* In general, the #G_MAIN data should not change while such a context exists, otherwise mapping
* info between the context content and the G_MAIN content cannot be kept up-to-date.
*
* The context can then be written to disk, and destroyed.
*
* It also has advanced ways to handle ID dependencies (and libraries for linked IDs), by allowing
* specific handling for each dependency individually. By using the `dependencies_filter_cb`
* optional parameter of #id_add, it is possible to skip (ignore) certain dependencies, or make
* linked ones local in the context, etc.
*
* Design task: #122061
*/
class PartialWriteContext : NonCopyable, NonMovable {
public:
/** The temp Main itself, storing all IDs copied into this partial write context. */
Main bmain = {};
private:
/**
* The filepath that should be used as root for IDs _added_ to the context, when handling
* remapping of their relative filepaths.
*
* Typically, the current G_MAIN's filepath.
*
* \note Currently always also copied into the temp `bmain.filepath`,
* as this simplifies remapping of relative file-paths.
* This may change in the future, if context can be loaded from external blend-files.
*/
std::string reference_root_filepath_;
/**
* This mapping only contains entries for IDs in the context which have a known matching ID in
* current G_MAIN.
*
* It is used to avoid adding several time a same ID (e.g. as a dependency of several other added
* IDs).
*/
IDNameLib_Map *matching_uid_map_;
/** A mapping from the absolute library paths to the #Library IDs in the context. */
blender::Map<std::string, Library *> libraries_map_;
public:
/* Passing a reference root filepath is mandatory, for remapping of relative paths to work as
* expected. */
PartialWriteContext() = delete;
PartialWriteContext(StringRefNull reference_root_filepath);
~PartialWriteContext();
/**
* Control how to handle IDs and their dependencies when they are added to this context.
*
* \note For linked IDs, if #MAKE_LOCAL is not used, the library ID pointer is _not_ considered
* nor handled as a regular dependency. Instead, the library is _always_ added to the context
* data, and never duplicated. Also, library matching always happens based on absolute filepath.
*
* \warning Heterogeneous usages of these operations flags during a same PartialWriteContext
* session may not generate expected results. Typically, once an ID has been added to the context
* as 'matching' counterpart of the source Main (i.e. sharing the same session UID), it will not
* be re-processed further if found again as dependency of another ID, or added explicitly as
* root ID.
* So e.g. if an ID is added (explicitly or implicitly) but none of its dependencies are (using
* `CLEAR_DEPENDENCIES`), re-adding the same ID (explicitly or implicitly) with e.g.
* `ADD_DEPENDENCIES` set will __not__ add its dependencies.
* This is not expected to be an issue in current use-cases.
*/
enum IDAddOperations {
NOP = 0,
/**
* Do not keep linked info (library and/or liboverride references).
*
* \warning By default, when #ADD_DEPENDENCIES is defined, this will also apply to all
* dependencies as well.
*
* \note Often required when only a small subset of the ID dependencies are also added to the
* context (i.e. many of the added data's ID pointers are set to `nullptr`). Otherwise, some
* areas not expecting nullptr (like LibOverride data) may assert or error on load of the
* partial written blendfile.
*/
MAKE_LOCAL = 1 << 0,
/**
* Set the 'fake user' flag to the added ID. Ensures that it is never auto-removed from the
* context, and always written to disk.
*/
SET_FAKE_USER = 1 << 1,
/**
* Set the 'clipboard' flag to the added ID. Ensures that it is treated as potential source
* data for a 'paste ID' operation.
*/
SET_CLIPBOARD_MARK = 1 << 4,
/**
* Clear all dependency IDs that are not in the partial write context. Mutually exclusive with
* #ADD_DEPENDENCIES.
*
* WARNING: This also means that dependencies like obdata, shape-keys or actions are not
* duplicated either.
*
* NOTE: Either #CLEAR_DEPENDENCIES or #ADD_DEPENDENCIES must be specified in the final
* operation flags for all ID dependencies. This can be achieved by
*/
CLEAR_DEPENDENCIES = 1 << 8,
/**
* Also add (or reuse if already there) dependency IDs into the partial write context. Mutually
* exclusive with #CLEAR_DEPENDENCIES.
*/
ADD_DEPENDENCIES = 1 << 9,
/**
* For each explicitly added IDs (i.e. these with a fake user), ensure all of their
* dependencies are independent copies, instead of being shared with other explicitly added
* IDs. Only relevant with #ADD_DEPENDENCIES.
*
* \warning Implies that the `session_uid` of these duplicated dependencies will be different
* than their source data.
*/
DUPLICATE_DEPENDENCIES = 1 << 10,
/**
* Operation flags that are (by default) inherited by all dependencies.
*
* \note This will be (partially) superseded by masked-out values from #MASK_PER_ID_USAGES
* below.
*/
MASK_INHERITED = (MAKE_LOCAL | CLEAR_DEPENDENCIES | ADD_DEPENDENCIES | DUPLICATE_DEPENDENCIES),
/**
* Operation flags that are defined by the #dependencies_filter_cb callback, if given.
*
* \note This mask is applied on top of the filter from #MASK_INHERITED, for ID dependencies
* of explicitly added data.
*/
MASK_PER_ID_USAGE = (MAKE_LOCAL | SET_FAKE_USER | SET_CLIPBOARD_MARK | CLEAR_DEPENDENCIES |
ADD_DEPENDENCIES),
};
/**
* Options passed to the #id_add method.
*/
struct IDAddOptions {
IDAddOperations operations;
};
/**
* Add a copy of the given ID to the partial write context.
*
* \note The duplicated ID will have the same session_uid as its source. In case a matching ID
* already exists in the context, it is returned instead of duplicating it again.
*
* \param options: Control how the added ID (and its dependencies) are handled. See
* #IDAddOptions and #IDAddOperations above for details.
* If no #dependencies_filter_cb callback is specified, #options.operations must contain
* either #CLEAR_DEPENDENCIES or #ADD_DEPENDENCIES.
* \param dependencies_filter_cb: Optional, a callback called for each ID usages, which returns
* specific operations flags for each ID usage.
* Currently, only accepted return values are the ones included in #MASK_PER_ID_USAGE.
* Returned flags must always contain either #CLEAR_DEPENDENCIES or #ADD_DEPENDENCIES.
*
* \return The pointer to the duplicated ID in the partial write context.
*/
ID *id_add(const ID *id,
IDAddOptions options,
blender::FunctionRef<IDAddOperations(LibraryIDLinkCallbackData *cb_data,
IDAddOptions options)> dependencies_filter_cb =
nullptr);
/**
* Add and return a new ID into the partial write context.
*
* NOTE: Since this ID is _created_ in the partial write buffer, by definition it has no matching
* counterpart in the current G_MAIN. Therefore, there is no need to add it to
* #matching_uid_map_, and its `session_uid` is not guaranteed to be constant (as it may be
* preempted later by another ID added from the current G_MAIN).
*
* \param options: Control how the created ID is handled. See #IDAddOptions and #IDAddOperations
* above for details, note that the only relevant operation flags currently are the
* #SET_FAKE_USER and #SET_CLIPBOARD_MARK ones.
*/
ID *id_create(short id_type, StringRefNull id_name, Library *library, IDAddOptions options);
/**
* Delete the copy of the given ID from the partial write context.
*
* \note The search is based on the #ID.session_uid of the given ID. This means that if
* `duplicate_depencies` option was used when adding the ID, these independent dependencies
* duplicates cannot be removed directly from the context. Use #remove_unused for this.
*
* \note No dependencies will be removed. Use #remove_unused to remove all unused IDs from the
* current context.
*/
void id_delete(const ID *id);
/**
* Remove all unused IDs from the current context.
*
* \param clear_extra_user: If `true`, the runtime tag ensuring that IDs are written on disk will
* be cleared. In other words, only IDs flagged with 'fake user' and their dependencies
* will be kept. Allows to also remove IDs that were added to this context during the same
* editing session, and were not flagged as 'fake user'.
*/
void remove_unused(bool clear_extra_user = false);
/**
* Fully empty the partial write context.
*/
void clear();
/**
* Debug: Check if the current partial write context is fully valid.
*
* Currently, check if any ID in the context still has relations to IDs not in the context.
*
* \return false if the context is invalid.
*/
bool is_valid();
/**
* Write the content of the current context as a blendfile on disk.
*
* \return `true` on success.
*/
bool write(const char *write_filepath, int write_flags, int remap_mode, ReportList &reports);
bool write(const char *write_filepath, ReportList &reports);
/* TODO: To allow editing an existing external blendfile:
* - API to load a context from a blendfile.
* - API to 'match' a context's content with another Main database's content (based on ID
* names and libraries).
* - API to replace the matching context IDs by a 'new version' (similar to 'add_id', but
* ensuring that the context ID, if it already exists, is a pristine copy of the given source
* one).
* - Rework the remapping of relative filepaths, since data already existing in the
* loaded-from-disk temp context will have different root-path than the data from current
* G_MAIN.
*/
private:
/**
* In case an explicitly added ID has the same session_uid as an existing one in current
* context, the added one should be able to 'steal' that session_uid in the context, and
* re-assign a new one to the other ID.
*/
void preempt_session_uid(ID *ctx_id, unsigned int session_uid);
/**
* Ensures that given ID will be written on disk (within current context), by either setting the
* 'fake user' flag, or the (runtime-only, cleared on next file load) 'extra user' tag, depending
* on whether #SET_FAKE_USER is set or not.
*
* Also handles the setting of the #ID_FLAG_CLIPBOARD_MARK flag if #SET_CLIPBOARD_MARK is set.
*/
void process_added_id(ID *ctx_id, const IDAddOperations operations);
/**
* Utils for #PartialWriteContext::id_add, only adds (duplicate) the given source ID into
* current context.
*/
ID *id_add_copy(const ID *id, bool regenerate_session_uid);
/** Make given context ID local to the context. */
void make_local(ID *ctx_id, int make_local_flags);
/**
* Ensure that the given ID's library has a matching Library ID in the context, copying the
* current `ctx_id->lib` one if needed.
*/
Library *ensure_library(ID *ctx_id);
/**
* Ensure that the given library path has a matching Library ID in the context, creating a new
* one if needed.
*/
Library *ensure_library(StringRefNull library_absolute_path);
};
ENUM_OPERATORS(PartialWriteContext::IDAddOperations,
PartialWriteContext::IDAddOperations::MASK_INHERITED);
} // namespace blender::bke::blendfile