Files
test2/source/blender/blenkernel/BKE_blendfile.hh
Aras Pranckevicius facb17b0e3 Cleanup: BLI ENUM_OPERATORS cleanup/robustness
BLI code for enums that are meant to be used as "bit flags" defined
an ENUM_OPERATORS macro in BLI_utildefines.h. This cleans up things
related to said macro:

- Move it out into a separate BLI_enum_flags.hh header, instead of
  "random bag of things" that is the current place,
- Update it to no longer need manual indication of highest individual
  bit value. This originally was added in a31a87f89 (2020 Oct), in
  order to silence some UBSan warnings that were coming
  from GPU related structures (looking at current GPU code, I don't
  think this is happening anymore). However, that caused actual
  user-visible bugs due to incorrectly specified max. enum bit value,
  and today 14% of all usages have incorrect highest individual
  bit value spelled out.
    - I have reviewed all usages of operator ~ and none of them are
      used for directly producing a DNA-serialized value; all the
      usages are for masking out other bits for which the new ~
      behavior that just flips all bits is fine.
- Make the macro define flag_is_set() function to ease check of bits
  that are set in C++ enum class cases; update existing cases to use
  that instead of three other ways that were used.

Pull Request: https://projects.blender.org/blender/blender/pulls/148230
2025-10-17 12:57:50 +02:00

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_enum_flags.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(Main &reference_main);
~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);
} // namespace blender::bke::blendfile