Files
test/source/blender/blenkernel/intern/asset_edit.cc
Julian Eisel 21aeb9e454 Fix: Duplicating brush asset wouldn't display it as active
Was using the old, not the modified name of the brush to build the brush
asset reference, that is used to identify the active brush asset.

The new brush was actually activated, but some of the UI like the asset
shelf would display it as such.
2024-07-16 11:57:26 +02:00

462 lines
15 KiB
C++

/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*/
#include <memory>
#include <utility>
#include "BLI_fileops.h"
#include "BLI_path_util.h"
#include "BLI_string.h"
#include "BLI_vector.hh"
#include "DNA_asset_types.h"
#include "DNA_space_types.h"
#include "AS_asset_identifier.hh"
#include "AS_asset_library.hh"
#include "BKE_asset.hh"
#include "BKE_asset_edit.hh"
#include "BKE_blendfile.hh"
#include "BKE_blendfile_link_append.hh"
#include "BKE_global.hh"
#include "BKE_idtype.hh"
#include "BKE_lib_id.hh"
#include "BKE_lib_remap.hh"
#include "BKE_library.hh"
#include "BKE_main.hh"
#include "BKE_packedFile.h"
#include "BKE_preferences.h"
#include "BKE_report.hh"
#include "BLO_read_write.hh"
#include "BLO_readfile.hh"
#include "BLO_writefile.hh"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_build.hh"
#include "MEM_guardedalloc.h"
namespace blender::bke {
static ID *asset_link_id(Main &global_main,
const ID_Type id_type,
const char *filepath,
const char *asset_name)
{
/* Load asset from asset library. */
LibraryLink_Params lapp_params{};
lapp_params.bmain = &global_main;
BlendfileLinkAppendContext *lapp_context = BKE_blendfile_link_append_context_new(&lapp_params);
BKE_blendfile_link_append_context_flag_set(lapp_context, BLO_LIBLINK_FORCE_INDIRECT, true);
BKE_blendfile_link_append_context_library_add(lapp_context, filepath, nullptr);
BlendfileLinkAppendContextItem *lapp_item = BKE_blendfile_link_append_context_item_add(
lapp_context, asset_name, id_type, nullptr);
BKE_blendfile_link_append_context_item_library_index_enable(lapp_context, lapp_item, 0);
BKE_blendfile_link(lapp_context, nullptr);
ID *local_asset = BKE_blendfile_link_append_context_item_newid_get(lapp_context, lapp_item);
BKE_blendfile_link_append_context_free(lapp_context);
/* Verify that the name matches. It must for referencing the same asset again to work. */
BLI_assert(local_asset == nullptr || STREQ(local_asset->name + 2, asset_name));
/* Tag library as being editable. */
if (local_asset && local_asset->lib) {
local_asset->lib->runtime.tag |= LIBRARY_ASSET_EDITABLE;
if ((local_asset->lib->runtime.tag & LIBRARY_IS_ASSET_EDIT_FILE) &&
StringRef(filepath).endswith(BLENDER_ASSET_FILE_SUFFIX) &&
BKE_preferences_asset_library_containing_path(&U, filepath) &&
BLI_file_is_writable(filepath))
{
local_asset->lib->runtime.tag |= LIBRARY_ASSET_FILE_WRITABLE;
}
}
return local_asset;
}
static std::string asset_root_path_for_save(const bUserAssetLibrary &user_library,
const ID_Type id_type)
{
BLI_assert(user_library.dirpath[0] != '\0');
char libpath[FILE_MAX];
STRNCPY(libpath, user_library.dirpath);
BLI_path_slash_native(libpath);
BLI_path_normalize(libpath);
/* Capitalize folder name. Ideally this would already available in
* the type info to work correctly with multiple words. */
const IDTypeInfo *id_type_info = BKE_idtype_get_info_from_idcode(id_type);
std::string name = id_type_info->name_plural;
name[0] = BLI_toupper_ascii(name[0]);
return std::string(libpath) + SEP + "Saved" + SEP + name;
}
static std::string asset_blendfile_path_for_save(const bUserAssetLibrary &user_library,
const StringRef base_name,
const ID_Type id_type,
ReportList &reports)
{
std::string root_path = asset_root_path_for_save(user_library, id_type);
BLI_assert(!root_path.empty());
if (!BLI_dir_create_recursive(root_path.c_str())) {
BKE_report(&reports, RPT_ERROR, "Failed to create asset library directory to save asset");
return "";
}
/* Make sure filename only contains valid characters for file-system. */
char base_name_filesafe[FILE_MAXFILE];
BLI_strncpy(base_name_filesafe,
base_name.data(),
std::min(sizeof(base_name_filesafe), size_t(base_name.size() + 1)));
BLI_path_make_safe_filename(base_name_filesafe);
const std::string filepath = root_path + SEP + base_name_filesafe + BLENDER_ASSET_FILE_SUFFIX;
if (!BLI_is_file(filepath.c_str())) {
return filepath;
}
/* Avoid overwriting existing file by adding number suffix. */
for (int i = 1;; i++) {
const std::string filepath = root_path + SEP + base_name_filesafe + "_" + std::to_string(i++) +
BLENDER_ASSET_FILE_SUFFIX;
if (!BLI_is_file((filepath.c_str()))) {
return filepath;
}
}
return "";
}
static void asset_main_create_expander(void * /*handle*/, Main * /*bmain*/, void *vid)
{
ID *id = static_cast<ID *>(vid);
if (id && (id->tag & LIB_TAG_DOIT) == 0) {
id->tag |= LIB_TAG_NEED_EXPAND | LIB_TAG_DOIT;
}
}
static Main *asset_main_create_from_ID(Main &bmain_src, ID &id_asset, ID **id_asset_new)
{
/* Tag asset ID and its dependencies. */
ID *id_src;
FOREACH_MAIN_ID_BEGIN (&bmain_src, id_src) {
id_src->tag &= ~(LIB_TAG_NEED_EXPAND | LIB_TAG_DOIT);
}
FOREACH_MAIN_ID_END;
id_asset.tag |= LIB_TAG_NEED_EXPAND | LIB_TAG_DOIT;
BLO_expand_main(nullptr, &bmain_src, asset_main_create_expander);
/* Create main and copy all tagged datablocks. */
Main *bmain_dst = BKE_main_new();
STRNCPY(bmain_dst->filepath, bmain_src.filepath);
bmain_dst->is_asset_edit_file = true;
blender::bke::id::IDRemapper id_remapper;
FOREACH_MAIN_ID_BEGIN (&bmain_src, id_src) {
if (id_src->tag & LIB_TAG_DOIT) {
/* Note that this will not copy Library datablocks, and all copied
* datablocks will become local as a result. */
ID *id_dst = BKE_id_copy_ex(bmain_dst,
id_src,
nullptr,
LIB_ID_CREATE_NO_USER_REFCOUNT | LIB_ID_CREATE_NO_DEG_TAG |
((id_src == &id_asset) ? LIB_ID_COPY_ASSET_METADATA : 0));
id_remapper.add(id_src, id_dst);
if (id_src == &id_asset) {
*id_asset_new = id_dst;
}
}
else {
id_remapper.add(id_src, nullptr);
}
id_src->tag &= ~(LIB_TAG_NEED_EXPAND | LIB_TAG_DOIT);
}
FOREACH_MAIN_ID_END;
/* Remap datablock pointers. */
BKE_libblock_remap_multiple_raw(bmain_dst, id_remapper, ID_REMAP_SKIP_USER_CLEAR);
/* Compute reference counts. */
ID *id_dst;
FOREACH_MAIN_ID_BEGIN (bmain_dst, id_dst) {
id_dst->tag &= ~LIB_TAG_NO_USER_REFCOUNT;
}
FOREACH_MAIN_ID_END;
BKE_main_id_refcount_recompute(bmain_dst, false);
return bmain_dst;
}
static bool asset_write_in_library(Main &bmain,
const ID &id_const,
const StringRef name,
const StringRefNull filepath,
std::string &final_full_file_path,
ReportList &reports)
{
ID &id = const_cast<ID &>(id_const);
ID *new_id = nullptr;
Main *new_main = asset_main_create_from_ID(bmain, id, &new_id);
std::string new_name = name;
BKE_libblock_rename(new_main, new_id, new_name.c_str());
id_fake_user_set(new_id);
BlendFileWriteParams blend_file_write_params{};
blend_file_write_params.remap_mode = BLO_WRITE_PATH_REMAP_RELATIVE;
BKE_packedfile_pack_all(new_main, nullptr, false);
const int write_flags = G_FILE_COMPRESS | G_FILE_ASSET_EDIT_FILE;
const bool success = BLO_write_file(
new_main, filepath.c_str(), write_flags, &blend_file_write_params, &reports);
if (success) {
const IDTypeInfo *idtype = BKE_idtype_get_info_from_id(&id);
final_full_file_path = std::string(filepath) + SEP + std::string(idtype->name) + SEP + name;
}
BKE_main_free(new_main);
return success;
}
static void asset_reload(Main &global_main, Library *lib, ReportList &reports)
{
/* Fill fresh main database with same datablock as before. */
LibraryLink_Params lapp_params{};
lapp_params.bmain = &global_main;
BlendfileLinkAppendContext *lapp_context = BKE_blendfile_link_append_context_new(&lapp_params);
BKE_blendfile_link_append_context_flag_set(
lapp_context, BLO_LIBLINK_FORCE_INDIRECT | BLO_LIBLINK_USE_PLACEHOLDERS, true);
BKE_blendfile_link_append_context_library_add(lapp_context, lib->runtime.filepath_abs, nullptr);
BKE_blendfile_library_relocate(lapp_context, &reports, lib, true);
BKE_blendfile_link_append_context_free(lapp_context);
/* Clear temporary tag from relocation. */
BKE_main_id_tag_all(&global_main, LIB_TAG_PRE_EXISTING, false);
/* Recreate dependency graph to include new IDs. */
DEG_relations_tag_update(&global_main);
}
static AssetWeakReference asset_weak_reference_for_user_library(
const bUserAssetLibrary &user_library,
const short idcode,
const char *idname,
const char *filepath)
{
AssetWeakReference weak_ref;
weak_ref.asset_library_type = ASSET_LIBRARY_CUSTOM;
weak_ref.asset_library_identifier = BLI_strdup(user_library.name);
/* BLI_path_rel requires a trailing slash. */
char user_library_dirpath[FILE_MAX];
STRNCPY(user_library_dirpath, user_library.dirpath);
BLI_path_slash_ensure(user_library_dirpath, sizeof(user_library_dirpath));
char relative_filepath[FILE_MAX];
STRNCPY(relative_filepath, filepath);
BLI_path_rel(relative_filepath, user_library_dirpath);
const char *asset_blend_path = relative_filepath + 2; /* Strip out // prefix. */
weak_ref.relative_asset_identifier = BLI_sprintfN(
"%s/%s/%s", asset_blend_path, BKE_idtype_idcode_to_name(idcode), idname);
return weak_ref;
}
static AssetWeakReference asset_weak_reference_for_essentials(const short idcode,
const char *idname,
const char *filepath)
{
AssetWeakReference weak_ref;
weak_ref.asset_library_type = ASSET_LIBRARY_ESSENTIALS;
weak_ref.relative_asset_identifier = BLI_sprintfN("%s/%s/%s/%s",
BKE_idtype_idcode_to_name_plural(idcode),
BLI_path_basename(filepath),
BKE_idtype_idcode_to_name(idcode),
idname);
return weak_ref;
}
std::optional<std::string> asset_edit_id_save_as(Main &global_main,
const ID &id,
const StringRefNull name,
const bUserAssetLibrary &user_library,
AssetWeakReference &new_weak_ref,
ReportList &reports)
{
const std::string filepath = asset_blendfile_path_for_save(
user_library, name, GS(id.name), reports);
std::string final_full_asset_filepath;
const bool success = asset_write_in_library(
global_main, id, name, filepath, final_full_asset_filepath, reports);
if (!success) {
BKE_report(&reports, RPT_ERROR, "Failed to write to asset library");
return std::nullopt;
}
new_weak_ref = asset_weak_reference_for_user_library(
user_library, GS(id.name), name.c_str(), filepath.c_str());
BKE_reportf(&reports, RPT_INFO, "Saved \"%s\"", filepath.c_str());
return final_full_asset_filepath;
}
bool asset_edit_id_save(Main &global_main, const ID &id, ReportList &reports)
{
if (!asset_edit_id_is_editable(id)) {
return false;
}
std::string final_full_asset_filepath;
const bool success = asset_write_in_library(global_main,
id,
id.name + 2,
id.lib->runtime.filepath_abs,
final_full_asset_filepath,
reports);
if (!success) {
BKE_report(&reports, RPT_ERROR, "Failed to write to asset library");
return false;
}
return true;
}
bool asset_edit_id_revert(Main &global_main, ID &id, ReportList &reports)
{
if (!asset_edit_id_is_editable(id)) {
return false;
}
/* Reload entire main, including texture dependencies. This relies on there
* being only a single asset per blend file. */
asset_reload(global_main, id.lib, reports);
return true;
}
bool asset_edit_id_delete(Main &global_main, ID &id, ReportList &reports)
{
if (asset_edit_id_is_editable(id)) {
if (BLI_delete(id.lib->runtime.filepath_abs, false, false) != 0) {
BKE_report(&reports, RPT_ERROR, "Failed to delete asset library file");
return false;
}
}
BKE_id_delete(&global_main, &id);
return true;
}
ID *asset_edit_id_from_weak_reference(Main &global_main,
const ID_Type id_type,
const AssetWeakReference &weak_ref)
{
/* Don't do this in file load. */
BLI_assert(!global_main.is_locked_for_linking);
char asset_full_path_buffer[FILE_MAX_LIBEXTRA];
char *asset_lib_path, *asset_group, *asset_name;
AS_asset_full_path_explode_from_weak_ref(
&weak_ref, asset_full_path_buffer, &asset_lib_path, &asset_group, &asset_name);
if (asset_lib_path == nullptr && asset_group == nullptr && asset_name == nullptr) {
return nullptr;
}
/* If this is the same file as we have open, use local datablock. */
if (asset_lib_path && STREQ(asset_lib_path, global_main.filepath)) {
asset_lib_path = nullptr;
}
BLI_assert(asset_name != nullptr);
/* Test if asset has been loaded already. */
ID *local_asset = BKE_libblock_find_name_and_library_filepath(
&global_main, id_type, asset_name, asset_lib_path);
if (local_asset) {
return local_asset;
}
/* Try linking in the required file. */
if (asset_lib_path == nullptr) {
return nullptr;
}
return asset_link_id(global_main, id_type, asset_lib_path, asset_name);
}
std::optional<AssetWeakReference> asset_edit_weak_reference_from_id(const ID &id)
{
/* Brush is local to the file. */
if (!id.lib) {
AssetWeakReference weak_ref;
weak_ref.asset_library_type = eAssetLibraryType::ASSET_LIBRARY_LOCAL;
weak_ref.relative_asset_identifier = BLI_sprintfN(
"%s/%s", BKE_idtype_idcode_to_name(GS(id.name)), id.name + 2);
return weak_ref;
}
if (!asset_edit_id_is_editable(id)) {
return std::nullopt;
}
const bUserAssetLibrary *user_library = BKE_preferences_asset_library_containing_path(
&U, id.lib->runtime.filepath_abs);
const short idcode = GS(id.name);
if (user_library && user_library->dirpath[0]) {
return asset_weak_reference_for_user_library(
*user_library, idcode, id.name + 2, id.lib->runtime.filepath_abs);
}
return asset_weak_reference_for_essentials(idcode, id.name + 2, id.lib->runtime.filepath_abs);
}
bool asset_edit_id_is_editable(const ID &id)
{
return (id.lib && (id.lib->runtime.tag & LIBRARY_ASSET_EDITABLE));
}
bool asset_edit_id_is_writable(const ID &id)
{
return asset_edit_id_is_editable(id) && (id.lib->runtime.tag & LIBRARY_ASSET_FILE_WRITABLE);
}
} // namespace blender::bke