WIP: Brush assets project #106303

Draft
Julian Eisel wants to merge 358 commits from brush-assets-project into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
9 changed files with 619 additions and 465 deletions
Showing only changes of commit ba71fa3909 - Show all commits

View File

@ -80,15 +80,3 @@ void BKE_asset_metadata_read(BlendDataReader *reader, AssetMetaData *asset_data)
void BKE_asset_weak_reference_write(BlendWriter *writer, const AssetWeakReference *weak_ref);
void BKE_asset_weak_reference_read(BlendDataReader *reader, AssetWeakReference *weak_ref);
/**
* Database of assets that are weakly reference by scene data,
* currently used for brush assets and their dependencies.
*/
Main *BKE_asset_weak_reference_main(const ID *id);
ID *BKE_asset_weak_reference_ensure(Main &global_main,
ID_Type id_type,
const AssetWeakReference &weak_ref);
void BKE_asset_weak_reference_main_reload(Main &global_main, Main &main);
void BKE_asset_weak_reference_main_free(Main &global_main, Main &asset_main);
void BKE_asset_weak_reference_main_free_all();

View File

@ -0,0 +1,69 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*/
#pragma once
/**
* Editing of datablocks from asset libraries, separate from the current open
* blend file.
*
* Each asset blend file is loaded into a separate main database, including the
* asset datablocks and their dependencies. These datablocks are all tagged with
* LIB_TAG_ASSET_MAIN. These can not be linked with other datablocks in the
* current blend file.
*
* For editable assets in user asset libraries, each asset is stored in its own
* blend file. This way the blend file can be easily saved, reloaded and deleted.
*
* This mechanism is currently only used for brush assets.
*/
#include <optional>
#include <string>
#include "AS_asset_catalog.hh"
#include "DNA_ID_enums.h"
struct bUserAssetLibrary;
struct AssetWeakReference;
struct ID;
struct Main;
struct ReportList;
namespace blender::bke {
/** Get datablock from weak reference, loading the blend file as needed. */
ID *asset_edit_id_from_weak_reference(Main &global_main,
ID_Type id_type,
const AssetWeakReference &weak_ref);
/** Get main database that a given asset datablock corresponds to. */
Main *asset_edit_main(const ID *id);
/** Asset editing operations. */
bool asset_edit_id_is_editable(const ID *id);
std::optional<std::string> asset_edit_id_save_as(
Main &global_main,
const ID *id,
const char *name,
std::optional<blender::asset_system::CatalogID> catalog_id,
std::optional<std::string> catalog_simple_name,
const bUserAssetLibrary *user_library,
ReportList *reports);
bool asset_edit_id_save(Main &global_main, const ID *id, ReportList *reports);
bool asset_edit_id_revert(Main &global_main, const ID *id, ReportList *reports);
bool asset_edit_id_delete(Main &global_main, const ID *id, ReportList *reports);
/** Clean up on exit. */
void asset_edit_main_free_all();
} // namespace blender::bke

View File

@ -65,6 +65,7 @@ set(SRC
intern/armature_update.cc
intern/asset.cc
intern/asset_weak_reference.cc
intern/asset_edit.cc
intern/attribute.cc
intern/attribute_access.cc
intern/attribute_math.cc
@ -333,6 +334,7 @@ set(SRC
BKE_appdir.hh
BKE_armature.hh
BKE_asset.hh
BKE_asset_edit.hh
BKE_attribute.h
BKE_attribute.hh
BKE_attribute_math.hh

View File

@ -0,0 +1,518 @@
/* 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_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_idtype.hh"
#include "BKE_lib_id.hh"
#include "BKE_lib_remap.hh"
#include "BKE_main.hh"
#include "BKE_preferences.h"
#include "BKE_report.hh"
#include "BLO_read_write.hh"
#include "BLO_readfile.hh"
#include "BLO_writefile.hh"
#include "DNA_asset_types.h"
#include "MEM_guardedalloc.h"
namespace blender::bke {
/**
* Asset library blend file, with editable contents.
*/
struct AssetEditBlend {
std::string filepath;
Main *main;
bool is_editable = false;
AssetEditBlend(const std::string &filepath);
~AssetEditBlend();
AssetEditBlend(const AssetEditBlend &) = delete;
AssetEditBlend(AssetEditBlend &&other);
AssetEditBlend &operator=(AssetEditBlend &&other);
ID *ensure_id(ID_Type id_type, const char *asset_name);
void reload(Main &global_main);
void clear_users(Main &global_main);
};
AssetEditBlend::AssetEditBlend(const std::string &filepath)
: filepath(std::move(filepath)), main(BKE_main_new())
{
this->main->is_asset_weak_reference_main = true;
BLI_assert(!BLI_path_is_rel(filepath.c_str()));
/* Fairly simple check based on filepath only.
* - Ends with `.asset.bend` extensions.
* - Is located in user asset library.
*
* TODO?
* - Check file contents.
* - Check file is writable.
*/
this->is_editable = StringRef(filepath).endswith(BLENDER_ASSET_FILE_SUFFIX) &&
BKE_preferences_asset_library_containing_path(&U, filepath.c_str());
}
AssetEditBlend::~AssetEditBlend()
{
if (main) {
BKE_main_free(main);
}
}
AssetEditBlend::AssetEditBlend(AssetEditBlend &&other)
: filepath(std::exchange(other.filepath, "")), main(std::exchange(other.main, nullptr))
{
}
AssetEditBlend &AssetEditBlend::operator=(AssetEditBlend &&other)
{
if (this == &other) {
return *this;
}
this->filepath = std::exchange(other.filepath, "");
this->main = std::exchange(other.main, nullptr);
return *this;
}
ID *AssetEditBlend::ensure_id(const ID_Type id_type, const char *asset_name)
{
/* Check if we have the asset already. */
ID *local_asset = BKE_libblock_find_name(this->main, id_type, asset_name);
if (local_asset) {
BLI_assert(ID_IS_ASSET(local_asset));
return local_asset;
}
/* Load asset from asset library. */
LibraryLink_Params lapp_params{};
lapp_params.bmain = this->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_flag_set(lapp_context, 0, true);
BKE_blendfile_link_append_context_library_add(lapp_context, filepath.c_str(), 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);
BKE_blendfile_append(lapp_context, nullptr);
local_asset = BKE_blendfile_link_append_context_item_newid_get(lapp_context, lapp_item);
BKE_blendfile_link_append_context_free(lapp_context);
BKE_main_id_tag_all(this->main, LIB_TAG_ASSET_MAIN, true);
/* 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));
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];
BLI_strncpy(libpath, user_library.dirpath, sizeof(libpath));
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(ReportList *reports,
const bUserAssetLibrary &user_library,
const StringRefNull base_name,
const ID_Type id_type)
{
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 brush");
return "";
}
char base_name_filesafe[FILE_MAXFILE];
BLI_strncpy(base_name_filesafe, base_name.c_str(), sizeof(base_name_filesafe));
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;
}
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 bool asset_write_in_library(Main *bmain,
const ID *id_const,
const char *name,
const StringRefNull filepath,
const std::optional<asset_system::CatalogID> catalog,
const std::optional<StringRefNull> catalog_simple_name,
std::string &final_full_file_path,
ReportList *reports)
{
/* XXX
* FIXME
*
* This code is _pure evil_. It does in-place manipulation on IDs in global Main database,
* temporarilly remove them and add them back...
*
* Use it as-is for now (in a similar way as python API or copy-to-buffer works). Nut the whole
* 'BKE_blendfile_write_partial' code needs to be completely refactored.
*
* Ideas:
* - Have `BKE_blendfile_write_partial_begin` return a new temp Main.
* - Replace `BKE_blendfile_write_partial_tag_ID` by API to add IDs to this temp Main.
* + This should _duplicate_ the ID, not remove the original one from the source Main!
* - Have API to automatically also duplicate dependencies into temp Main.
* + Have options to e.g. make all duplicated IDs 'local' (i.e. remove their library data).
* - `BKE_blendfile_write_partial` then simply write the given temp main.
* - `BKE_blendfile_write_partial_end` frees the temp Main.
*/
ID *id = const_cast<ID *>(id_const);
const short prev_flag = id->flag;
const int prev_tag = id->tag;
const int prev_us = id->us;
const std::string prev_name = id->name + 2;
IDOverrideLibrary *prev_liboverride = id->override_library;
AssetMetaData *asset_data = id->asset_data;
const int write_flags = 0; /* Could use #G_FILE_COMPRESS ? */
const eBLO_WritePathRemap remap_mode = BLO_WRITE_PATH_REMAP_RELATIVE;
BKE_blendfile_write_partial_begin(bmain);
id->flag |= LIB_FAKEUSER;
id->tag &= ~LIB_TAG_RUNTIME;
id->us = 1;
BLI_strncpy(id->name + 2, name, sizeof(id->name) - 2);
if (!ID_IS_ASSET(id)) {
id->asset_data = id->override_library->reference->asset_data;
}
id->override_library = nullptr;
if (catalog) {
id->asset_data->catalog_id = *catalog;
}
if (catalog_simple_name) {
STRNCPY(id->asset_data->catalog_simple_name, catalog_simple_name->c_str());
}
BKE_blendfile_write_partial_tag_ID(id, true);
/* TODO: check overwriting existing file. */
/* TODO: ensure filepath contains only valid characters for file system. */
const bool sucess = BKE_blendfile_write_partial(
bmain, filepath.c_str(), write_flags, remap_mode, reports);
if (sucess) {
final_full_file_path = std::string(filepath) + SEP + "Brush" + SEP + name;
}
BKE_blendfile_write_partial_end(bmain);
BKE_blendfile_write_partial_tag_ID(id, false);
id->flag = prev_flag;
id->tag = prev_tag;
id->us = prev_us;
BLI_strncpy(id->name + 2, prev_name.c_str(), sizeof(id->name) - 2);
id->override_library = prev_liboverride;
id->asset_data = asset_data;
return sucess;
}
void AssetEditBlend::reload(Main &global_main)
{
Main *old_main = this->main;
this->main = BKE_main_new();
this->main->is_asset_weak_reference_main = true;
/* Fill fresh main database with same datablock as before. */
LibraryLink_Params lapp_params{};
lapp_params.bmain = this->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_flag_set(lapp_context, 0, true);
BKE_blendfile_link_append_context_library_add(lapp_context, this->filepath.c_str(), nullptr);
/* Requests all existing datablocks to be appended again. */
ID *old_id;
FOREACH_MAIN_ID_BEGIN (old_main, old_id) {
ID_Type old_id_code = GS(old_id->name);
if (BKE_idtype_idcode_is_linkable(old_id_code)) {
BlendfileLinkAppendContextItem *lapp_item = BKE_blendfile_link_append_context_item_add(
lapp_context, old_id->name + 2, old_id_code, nullptr);
BKE_blendfile_link_append_context_item_library_index_enable(lapp_context, lapp_item, 0);
}
}
FOREACH_MAIN_ID_END;
BKE_blendfile_link(lapp_context, nullptr);
BKE_blendfile_append(lapp_context, nullptr);
BKE_blendfile_link_append_context_free(lapp_context);
BKE_main_id_tag_all(this->main, LIB_TAG_ASSET_MAIN, true);
/* Remap old to new. */
bke::id::IDRemapper mappings;
FOREACH_MAIN_ID_BEGIN (old_main, old_id) {
ID *new_id = BKE_libblock_find_name(this->main, GS(old_id->name), old_id->name + 2);
mappings.add(old_id, new_id);
}
FOREACH_MAIN_ID_END;
BKE_libblock_remap_multiple(&global_main, mappings, 0);
/* Free old database. */
BKE_main_free(old_main);
}
void AssetEditBlend::clear_users(Main &global_main)
{
/* Remap old to null pointer. */
bke::id::IDRemapper mappings;
ID *old_id;
FOREACH_MAIN_ID_BEGIN (this->main, old_id) {
mappings.add(old_id, nullptr);
}
FOREACH_MAIN_ID_END;
BKE_libblock_remap_multiple(&global_main, mappings, 0);
}
/**
* Public API
*/
static Vector<AssetEditBlend> &asset_edit_blend_get_all()
{
static Vector<AssetEditBlend> mains;
return mains;
}
static AssetEditBlend *asset_edit_blend_from_id(const ID *id)
{
BLI_assert(id->tag & LIB_TAG_ASSET_MAIN);
for (AssetEditBlend &asset_blend : asset_edit_blend_get_all()) {
/* TODO: Look into make this whole thing more efficient. */
ListBase *lb = which_libbase(asset_blend.main, GS(id->name));
LISTBASE_FOREACH (ID *, other_id, lb) {
if (id == other_id) {
return &asset_blend;
}
}
}
BLI_assert_unreachable();
return nullptr;
}
Main *asset_edit_main(const ID *id)
{
const AssetEditBlend *asset_blend = asset_edit_blend_from_id(id);
return (asset_blend) ? asset_blend->main : nullptr;
}
static AssetEditBlend &asset_edit_blend_file_ensure(const StringRef filepath)
{
for (AssetEditBlend &asset_blend : asset_edit_blend_get_all()) {
if (asset_blend.filepath == filepath) {
return asset_blend;
}
}
asset_edit_blend_get_all().append_as(filepath);
return asset_edit_blend_get_all().last();
}
std::optional<std::string> asset_edit_id_save_as(Main &global_main,
const ID *id,
const char *name,
std::optional<asset_system::CatalogID> catalog_id,
std::optional<std::string> catalog_simple_name,
const bUserAssetLibrary *user_library,
ReportList *reports)
{
const std::string filepath = asset_blendfile_path_for_save(
reports, *user_library, name, GS(id->name));
/* Save to asset library. */
Main *asset_main = BKE_main_from_id(&global_main, id);
std::string final_full_asset_filepath;
const bool success = asset_write_in_library(asset_main,
id,
name,
filepath,
catalog_id,
catalog_simple_name,
final_full_asset_filepath,
reports);
if (!success) {
BKE_report(reports, RPT_ERROR, "Failed to write to asset library");
return std::nullopt;
}
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)
{
AssetEditBlend *asset_blend = asset_edit_blend_from_id(id);
if (asset_blend == nullptr) {
return false;
}
std::string final_full_asset_filepath;
const bool success = asset_write_in_library(asset_blend->main,
id,
id->name + 2,
asset_blend->filepath.c_str(),
std::nullopt,
std::nullopt,
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, const ID *id, ReportList * /*reports*/)
{
AssetEditBlend *asset_blend = asset_edit_blend_from_id(id);
if (asset_blend == nullptr) {
return false;
}
/* Reload entire main, including texture dependencies. This relies on there
* being only a single brush asset per blend file. */
asset_blend->reload(global_main);
return true;
}
bool asset_edit_id_delete(Main &global_main, const ID *id, ReportList *reports)
{
AssetEditBlend *asset_blend = asset_edit_blend_from_id(id);
if (asset_blend == nullptr) {
return false;
}
if (BLI_delete(asset_blend->filepath.c_str(), false, false) != 0) {
BKE_report(reports, RPT_ERROR, "Failed to delete asset library file");
return false;
}
asset_blend->clear_users(global_main);
int index = 0;
for (AssetEditBlend &asset_blend_iter : asset_edit_blend_get_all()) {
if (&asset_blend_iter == asset_blend) {
asset_edit_blend_get_all().remove(index);
break;
}
index++;
}
return true;
}
ID *asset_edit_id_from_weak_reference(Main &global_main,
const ID_Type id_type,
const AssetWeakReference &weak_ref)
{
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;
}
BLI_assert(asset_name != nullptr);
/* Find asset in current blend file. */
if (asset_lib_path == nullptr) {
ID *local_asset = BKE_libblock_find_name(&global_main, id_type, asset_name);
BLI_assert(local_asset == nullptr || ID_IS_ASSET(local_asset));
return local_asset;
}
/* If weak reference resolves to a null library path, assume we are in local asset case. */
AssetEditBlend &asset_blend = asset_edit_blend_file_ensure(asset_lib_path);
return asset_blend.ensure_id(id_type, asset_name);
}
bool asset_edit_id_is_editable(const ID *id)
{
if (!(id->tag & LIB_TAG_ASSET_MAIN)) {
return false;
}
const AssetEditBlend *asset_blend = asset_edit_blend_from_id(id);
return (asset_blend) ? asset_blend->is_editable : false;
}
void asset_edit_main_free_all()
{
asset_edit_blend_get_all().clear_and_shrink();
}
} // namespace blender::bke

View File

@ -129,225 +129,3 @@ void BKE_asset_weak_reference_read(BlendDataReader *reader, AssetWeakReference *
BLO_read_data_address(reader, &weak_ref->asset_library_identifier);
BLO_read_data_address(reader, &weak_ref->relative_asset_identifier);
}
/* Main database for storing assets that are weak referenced.
*
* This avoids mixing asset datablocks in the regular main, which leads to naming conflicts and
* confusing user interface. */
struct AssetWeakReferenceMain {
std::string filepath;
Main *main;
AssetWeakReferenceMain(std::string filepath)
: filepath(std::move(filepath)), main(BKE_main_new())
{
main->is_asset_weak_reference_main = true;
BLI_assert(!BLI_path_is_rel(filepath.c_str()));
}
AssetWeakReferenceMain(const AssetWeakReferenceMain &) = delete;
AssetWeakReferenceMain(AssetWeakReferenceMain &&other)
: filepath(std::exchange(other.filepath, "")), main(std::exchange(other.main, nullptr))
{
}
AssetWeakReferenceMain &operator=(AssetWeakReferenceMain &&other)
{
if (this == &other) {
return *this;
}
this->filepath = std::exchange(other.filepath, "");
this->main = std::exchange(other.main, nullptr);
return *this;
}
~AssetWeakReferenceMain()
{
if (main) {
BKE_main_free(main);
}
}
void reload(Main &global_main);
void clear_users(Main &global_main);
};
void AssetWeakReferenceMain::reload(Main &global_main)
{
Main *old_main = this->main;
this->main = BKE_main_new();
this->main->is_asset_weak_reference_main = true;
/* Fill fresh main database with same datablock as before. */
LibraryLink_Params lapp_params{};
lapp_params.bmain = this->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_flag_set(lapp_context, 0, true);
BKE_blendfile_link_append_context_library_add(lapp_context, this->filepath.c_str(), nullptr);
/* Requests all existing datablocks to be appended again. */
ID *old_id;
FOREACH_MAIN_ID_BEGIN (old_main, old_id) {
ID_Type old_id_code = GS(old_id->name);
if (BKE_idtype_idcode_is_linkable(old_id_code)) {
BlendfileLinkAppendContextItem *lapp_item = BKE_blendfile_link_append_context_item_add(
lapp_context, old_id->name + 2, old_id_code, nullptr);
BKE_blendfile_link_append_context_item_library_index_enable(lapp_context, lapp_item, 0);
}
}
FOREACH_MAIN_ID_END;
BKE_blendfile_link(lapp_context, nullptr);
BKE_blendfile_append(lapp_context, nullptr);
BKE_blendfile_link_append_context_free(lapp_context);
BKE_main_id_tag_all(this->main, LIB_TAG_ASSET_MAIN, true);
/* Remap old to new. */
bke::id::IDRemapper mappings;
FOREACH_MAIN_ID_BEGIN (old_main, old_id) {
ID *new_id = BKE_libblock_find_name(this->main, GS(old_id->name), old_id->name + 2);
mappings.add(old_id, new_id);
}
FOREACH_MAIN_ID_END;
BKE_libblock_remap_multiple(&global_main, mappings, 0);
/* Free old database. */
BKE_main_free(old_main);
}
void AssetWeakReferenceMain::clear_users(Main &global_main)
{
/* Remap old to null pointer. */
bke::id::IDRemapper mappings;
ID *old_id;
FOREACH_MAIN_ID_BEGIN (this->main, old_id) {
mappings.add(old_id, nullptr);
}
FOREACH_MAIN_ID_END;
BKE_libblock_remap_multiple(&global_main, mappings, 0);
}
static Vector<AssetWeakReferenceMain> &get_weak_reference_mains()
{
static Vector<AssetWeakReferenceMain> mains;
return mains;
}
Main *BKE_asset_weak_reference_main(const ID *id)
{
BLI_assert(id->tag & LIB_TAG_ASSET_MAIN);
for (const AssetWeakReferenceMain &weak_ref_main : get_weak_reference_mains()) {
/* TODO: Look into make this whole thing more efficient. */
ListBase *lb = which_libbase(weak_ref_main.main, GS(id->name));
LISTBASE_FOREACH (ID *, other_id, lb) {
if (id == other_id) {
return weak_ref_main.main;
}
}
}
BLI_assert_unreachable();
return nullptr;
}
static Main &asset_weak_reference_main_ensure(const StringRef filepath)
{
for (const AssetWeakReferenceMain &weak_ref_main : get_weak_reference_mains()) {
if (weak_ref_main.filepath == filepath) {
return *weak_ref_main.main;
}
}
get_weak_reference_mains().append_as(filepath);
return *get_weak_reference_mains().last().main;
}
void BKE_asset_weak_reference_main_reload(Main &global_main, Main &asset_main)
{
for (AssetWeakReferenceMain &weak_ref_main : get_weak_reference_mains()) {
if (weak_ref_main.main == &asset_main) {
weak_ref_main.reload(global_main);
return;
}
}
BLI_assert_unreachable();
}
void BKE_asset_weak_reference_main_free(Main &global_main, Main &asset_main)
{
int index = 0;
for (AssetWeakReferenceMain &weak_ref_main : get_weak_reference_mains()) {
if (weak_ref_main.main == &asset_main) {
weak_ref_main.clear_users(global_main);
get_weak_reference_mains().remove(index);
return;
}
index++;
}
BLI_assert_unreachable();
}
void BKE_asset_weak_reference_main_free_all()
{
get_weak_reference_mains().clear_and_shrink();
}
ID *BKE_asset_weak_reference_ensure(Main &global_main,
const ID_Type id_type,
const AssetWeakReference &weak_ref)
{
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;
}
BLI_assert(asset_name != nullptr);
/* If weak reference resolves to a null library path, assume we are in local asset case. */
Main &bmain = asset_lib_path ? asset_weak_reference_main_ensure(asset_lib_path) : global_main;
/* Check if we have the asset already, or if it's global main and there is nothing we can add. */
ID *local_asset = BKE_libblock_find_name(&bmain, id_type, asset_name);
if (local_asset || asset_lib_path == nullptr) {
BLI_assert(local_asset == nullptr || ID_IS_ASSET(local_asset));
return local_asset;
}
/* Load asset from asset library. */
LibraryLink_Params lapp_params{};
lapp_params.bmain = &bmain;
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_flag_set(lapp_context, 0, true);
BKE_blendfile_link_append_context_library_add(lapp_context, asset_lib_path, 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);
BKE_blendfile_append(lapp_context, nullptr);
local_asset = BKE_blendfile_link_append_context_item_newid_get(lapp_context, lapp_item);
BKE_blendfile_link_append_context_free(lapp_context);
BKE_main_id_tag_all(&bmain, LIB_TAG_ASSET_MAIN, true);
/* 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));
return local_asset;
}

View File

@ -22,7 +22,7 @@
#include "IMB_moviecache.hh"
#include "BKE_addon.h"
#include "BKE_asset.hh"
#include "BKE_asset_edit.hh"
#include "BKE_blender.hh" /* own include */
#include "BKE_blender_user_menu.hh" /* own include */
#include "BKE_blender_version.h" /* own include */
@ -64,7 +64,7 @@ void BKE_blender_free()
/* Free separate data-bases after #BKE_blender_globals_clear in case any data-blocks in the
* global main use pointers to asset main data-blocks when they're freed. That generally
* shouldn't happen but it's better to be safe. */
BKE_asset_weak_reference_main_free_all();
blender::bke::asset_edit_main_free_all();
if (G.log.file != nullptr) {
fclose(static_cast<FILE *>(G.log.file));

View File

@ -24,7 +24,7 @@
#include "DNA_ID.h"
#include "BKE_asset.hh"
#include "BKE_asset_edit.hh"
#include "BKE_bpath.hh"
#include "BKE_global.hh"
#include "BKE_idtype.hh"
@ -972,7 +972,7 @@ Main *BKE_main_from_id(Main *global_main, const ID *id, const bool verify)
return nullptr;
}
if (id->tag & LIB_TAG_ASSET_MAIN) {
return BKE_asset_weak_reference_main(id);
return blender::bke::asset_edit_main(id);
}
if (verify) {

View File

@ -41,6 +41,7 @@
#include "BLT_translation.hh"
#include "BKE_asset.hh"
#include "BKE_asset_edit.hh"
#include "BKE_attribute.hh"
#include "BKE_brush.hh"
#include "BKE_ccg.h"
@ -620,8 +621,8 @@ static bool paint_brush_set_from_asset_reference(Main *bmain, Paint *paint)
return false;
}
Brush *brush = reinterpret_cast<Brush *>(
BKE_asset_weak_reference_ensure(*bmain, ID_BR, *paint->brush_asset_reference));
Brush *brush = reinterpret_cast<Brush *>(blender::bke::asset_edit_id_from_weak_reference(
*bmain, ID_BR, *paint->brush_asset_reference));
BLI_assert(brush == nullptr || (brush->id.tag & LIB_TAG_ASSET_MAIN));
/* Ensure we have a brush with appropriate mode to assign.

View File

@ -29,6 +29,7 @@
#include "BLO_writefile.hh"
#include "BKE_asset.hh"
#include "BKE_asset_edit.hh"
#include "BKE_blendfile.hh"
#include "BKE_brush.hh"
#include "BKE_context.hh"
@ -739,7 +740,7 @@ static int brush_asset_select_exec(bContext *C, wmOperator *op)
AssetWeakReference brush_asset_reference = asset->make_weak_reference();
Brush *brush = reinterpret_cast<Brush *>(
BKE_asset_weak_reference_ensure(*bmain, ID_BR, brush_asset_reference));
blender::bke::asset_edit_id_from_weak_reference(*bmain, ID_BR, brush_asset_reference));
Paint *paint = BKE_paint_get_active_from_context(C);
@ -771,7 +772,7 @@ static void BRUSH_OT_asset_select(wmOperatorType *ot)
* This needs to be properly implemented in assetlib code.
*/
static AssetWeakReference brush_asset_create_weakref_hack(const bUserAssetLibrary *user_asset_lib,
std::string &file_path)
const std::string &file_path)
{
AssetWeakReference asset_weak_ref{};
@ -819,127 +820,6 @@ static void refresh_asset_library(const bContext *C, const bUserAssetLibrary &us
}
}
static std::string brush_asset_root_path_for_save(const bUserAssetLibrary &user_library)
{
if (user_library.dirpath[0] == '\0') {
return "";
}
char libpath[FILE_MAX];
BLI_strncpy(libpath, user_library.dirpath, sizeof(libpath));
BLI_path_slash_native(libpath);
BLI_path_normalize(libpath);
return std::string(libpath) + SEP + "Saved" + SEP + "Brushes";
}
static std::string brush_asset_blendfile_path_for_save(ReportList *reports,
const bUserAssetLibrary &user_library,
const StringRefNull base_name)
{
std::string root_path = brush_asset_root_path_for_save(user_library);
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 brush");
return "";
}
char base_name_filesafe[FILE_MAXFILE];
BLI_strncpy(base_name_filesafe, base_name.c_str(), sizeof(base_name_filesafe));
BLI_path_make_safe_filename(base_name_filesafe);
if (!BLI_is_file((root_path + SEP + base_name_filesafe + BLENDER_ASSET_FILE_SUFFIX).c_str())) {
return root_path + SEP + base_name_filesafe + BLENDER_ASSET_FILE_SUFFIX;
}
int i = 1;
while (BLI_is_file((root_path + SEP + base_name_filesafe + "_" + std::to_string(i++) +
BLENDER_ASSET_FILE_SUFFIX)
.c_str()))
;
return root_path + SEP + base_name_filesafe + "_" + std::to_string(i - 1) +
BLENDER_ASSET_FILE_SUFFIX;
}
static bool brush_asset_write_in_library(Main *bmain,
Brush *brush,
const char *name,
const StringRefNull filepath,
const std::optional<asset_system::CatalogID> catalog,
const std::optional<StringRefNull> catalog_simple_name,
std::string &final_full_file_path,
ReportList *reports)
{
/* XXX
* FIXME
*
* This code is _pure evil_. It does in-place manipulation on IDs in global Main database,
* temporarilly remove them and add them back...
*
* Use it as-is for now (in a similar way as python API or copy-to-buffer works). Nut the whole
* 'BKE_blendfile_write_partial' code needs to be completely refactored.
*
* Ideas:
* - Have `BKE_blendfile_write_partial_begin` return a new temp Main.
* - Replace `BKE_blendfile_write_partial_tag_ID` by API to add IDs to this temp Main.
* + This should _duplicate_ the ID, not remove the original one from the source Main!
* - Have API to automatically also duplicate dependencies into temp Main.
* + Have options to e.g. make all duplicated IDs 'local' (i.e. remove their library data).
* - `BKE_blendfile_write_partial` then simply write the given temp main.
* - `BKE_blendfile_write_partial_end` frees the temp Main.
*/
const short brush_flag = brush->id.flag;
const int brush_tag = brush->id.tag;
const int brush_us = brush->id.us;
const std::string brush_name = brush->id.name + 2;
IDOverrideLibrary *brush_liboverride = brush->id.override_library;
AssetMetaData *brush_asset_data = brush->id.asset_data;
const int write_flags = 0; /* Could use #G_FILE_COMPRESS ? */
const eBLO_WritePathRemap remap_mode = BLO_WRITE_PATH_REMAP_RELATIVE;
BKE_blendfile_write_partial_begin(bmain);
brush->id.flag |= LIB_FAKEUSER;
brush->id.tag &= ~LIB_TAG_RUNTIME;
brush->id.us = 1;
BLI_strncpy(brush->id.name + 2, name, sizeof(brush->id.name) - 2);
if (!ID_IS_ASSET(&brush->id)) {
brush->id.asset_data = brush->id.override_library->reference->asset_data;
}
brush->id.override_library = nullptr;
if (catalog) {
brush->id.asset_data->catalog_id = *catalog;
}
if (catalog_simple_name) {
STRNCPY(brush->id.asset_data->catalog_simple_name, catalog_simple_name->c_str());
}
BKE_blendfile_write_partial_tag_ID(&brush->id, true);
/* TODO: check overwriting existing file. */
/* TODO: ensure filepath contains only valid characters for file system. */
const bool sucess = BKE_blendfile_write_partial(
bmain, filepath.c_str(), write_flags, remap_mode, reports);
if (sucess) {
final_full_file_path = std::string(filepath) + SEP + "Brush" + SEP + name;
}
BKE_blendfile_write_partial_end(bmain);
BKE_blendfile_write_partial_tag_ID(&brush->id, false);
brush->id.flag = brush_flag;
brush->id.tag = brush_tag;
brush->id.us = brush_us;
BLI_strncpy(brush->id.name + 2, brush_name.c_str(), sizeof(brush->id.name) - 2);
brush->id.override_library = brush_liboverride;
brush->id.asset_data = brush_asset_data;
return sucess;
}
static bool brush_asset_save_as_poll(bContext *C)
{
Paint *paint = BKE_paint_get_active_from_context(C);
@ -956,38 +836,6 @@ static bool brush_asset_save_as_poll(bContext *C)
return true;
}
static bool asset_is_editable(const AssetWeakReference &asset_weak_ref)
{
/* Fairly simple checks, based on filepath only:
* - The blendlib filepath ends up with the `.asset.blend` extension.
* - The blendlib is located in the expected sub-directory of the editable asset library.
*
* TODO: Right now no check is done on file content, e.g. to ensure that the blendlib file has
* not been manually edited by the user (that it does not have any UI IDs e.g.). */
char path_buffer[FILE_MAX_LIBEXTRA];
char *dir, *group, *name;
AS_asset_full_path_explode_from_weak_ref(&asset_weak_ref, path_buffer, &dir, &group, &name);
if (!StringRef(dir).endswith(BLENDER_ASSET_FILE_SUFFIX)) {
return false;
}
const bUserAssetLibrary *library = BKE_preferences_asset_library_find_by_name(
&U, asset_weak_ref.asset_library_identifier);
if (!library) {
return false;
}
std::string root_path_for_save = brush_asset_root_path_for_save(*library);
if (root_path_for_save.empty() || !StringRef(dir).startswith(root_path_for_save)) {
return false;
}
/* TODO: Do we want more checks here? E.g. check actual content of the file? */
return true;
}
static const bUserAssetLibrary *get_asset_library_from_prop(PointerRNA &ptr)
{
const int enum_value = RNA_enum_get(&ptr, "asset_library_reference");
@ -1048,18 +896,6 @@ static int brush_asset_save_as_exec(bContext *C, wmOperator *op)
if (!user_library) {
return OPERATOR_CANCELLED;
}
const std::string filepath = brush_asset_blendfile_path_for_save(
op->reports, *user_library, name);
if (filepath.empty()) {
return OPERATOR_CANCELLED;
}
/* Turn brush into asset if it isn't yet. */
if (!ID_IS_ASSET(brush)) {
asset::mark_id(&brush->id);
asset::generate_preview(C, &brush->id);
}
BLI_assert(ID_IS_ASSET(brush));
asset_system::AssetLibrary *library = AS_asset_library_load(
CTX_data_main(C), user_library_to_library_ref(*user_library));
@ -1068,6 +904,13 @@ static int brush_asset_save_as_exec(bContext *C, wmOperator *op)
return OPERATOR_CANCELLED;
}
/* Turn brush into asset if it isn't yet. */
if (!ID_IS_ASSET(&brush->id)) {
asset::mark_id(&brush->id);
asset::generate_preview(C, &brush->id);
}
BLI_assert(ID_IS_ASSET(&brush->id));
/* Add asset to catalog. */
char catalog_path[MAX_NAME];
RNA_string_get(op->ptr, "catalog_path", catalog_path);
@ -1082,33 +925,20 @@ static int brush_asset_save_as_exec(bContext *C, wmOperator *op)
catalog_simple_name = catalog.simple_name;
}
library->catalog_service().write_to_disk(filepath);
const std::optional<std::string> final_full_asset_filepath = blender::bke::asset_edit_id_save_as(
*bmain, &brush->id, name, catalog_id, catalog_simple_name, user_library, op->reports);
/* Save to asset library. */
Main *asset_main = BKE_main_from_id(bmain, &brush->id);
std::string final_full_asset_filepath;
const bool success = brush_asset_write_in_library(asset_main,
brush,
name,
filepath,
catalog_id,
catalog_simple_name,
final_full_asset_filepath,
op->reports);
if (!success) {
BKE_report(op->reports, RPT_ERROR, "Failed to write to asset library");
if (!final_full_asset_filepath) {
return OPERATOR_CANCELLED;
}
AssetWeakReference new_brush_weak_ref = brush_asset_create_weakref_hack(
user_library, final_full_asset_filepath);
library->catalog_service().write_to_disk(*final_full_asset_filepath);
BKE_reportf(op->reports, RPT_INFO, "Saved \"%s\"", filepath.c_str());
AssetWeakReference new_brush_weak_ref = brush_asset_create_weakref_hack(
user_library, *final_full_asset_filepath);
brush = reinterpret_cast<Brush *>(
BKE_asset_weak_reference_ensure(*bmain, ID_BR, new_brush_weak_ref));
blender::bke::asset_edit_id_from_weak_reference(*bmain, ID_BR, new_brush_weak_ref));
if (!BKE_paint_brush_asset_set(paint, brush, new_brush_weak_ref)) {
/* Note brush sset was still saved in editable asset library, so was not a no-op. */
@ -1219,7 +1049,7 @@ static bool brush_asset_delete_poll(bContext *C)
/* Asset brush, check if belongs to an editable blend file. */
if (paint->brush_asset_reference && ID_IS_ASSET(brush)) {
if (!asset_is_editable(*paint->brush_asset_reference)) {
if (!blender::bke::asset_edit_id_is_editable(&brush->id)) {
CTX_wm_operator_poll_msg_set(C, "Asset blend file is not editable");
return false;
}
@ -1233,7 +1063,6 @@ static int brush_asset_delete_exec(bContext *C, wmOperator *op)
Paint *paint = BKE_paint_get_active_from_context(C);
Brush *brush = BKE_paint_brush(paint);
Main *bmain = CTX_data_main(C);
Main *asset_main = BKE_main_from_id(bmain, &brush->id);
bUserAssetLibrary *library = BKE_preferences_asset_library_find_by_name(
&U, paint->brush_asset_reference->asset_library_identifier);
@ -1241,21 +1070,7 @@ static int brush_asset_delete_exec(bContext *C, wmOperator *op)
return OPERATOR_CANCELLED;
}
if (paint->brush_asset_reference && ID_IS_ASSET(brush)) {
/* Delete from asset library on disk. */
char path_buffer[FILE_MAX_LIBEXTRA];
char *filepath;
AS_asset_full_path_explode_from_weak_ref(
paint->brush_asset_reference, path_buffer, &filepath, nullptr, nullptr);
if (BLI_delete(filepath, false, false) != 0) {
BKE_report(op->reports, RPT_ERROR, "Failed to delete asset library file");
}
}
if (asset_main != bmain) {
BKE_asset_weak_reference_main_free(*bmain, *asset_main);
}
blender::bke::asset_edit_id_delete(*bmain, &brush->id, op->reports);
refresh_asset_library(C, *library);
@ -1306,7 +1121,7 @@ static bool brush_asset_update_poll(bContext *C)
return false;
}
if (!asset_is_editable(*paint->brush_asset_reference)) {
if (!blender::bke::asset_edit_id_is_editable(&brush->id)) {
CTX_wm_operator_poll_msg_set(C, "Asset blend file is not editable");
return false;
}
@ -1327,23 +1142,9 @@ static int brush_asset_update_exec(bContext *C, wmOperator *op)
return OPERATOR_CANCELLED;
}
char path_buffer[FILE_MAX_LIBEXTRA];
char *filepath;
AS_asset_full_path_explode_from_weak_ref(
asset_weak_ref, path_buffer, &filepath, nullptr, nullptr);
BLI_assert(ID_IS_ASSET(brush));
Main *asset_main = BKE_main_from_id(bmain, &brush->id);
std::string final_full_asset_filepath;
brush_asset_write_in_library(asset_main,
brush,
brush->id.name + 2,
filepath,
std::nullopt,
std::nullopt,
final_full_asset_filepath,
op->reports);
blender::bke::asset_edit_id_save(*bmain, &brush->id, op->reports);
refresh_asset_library(C, *user_library);
WM_main_add_notifier(NC_ASSET | ND_ASSET_LIST | NA_EDITED, nullptr);
@ -1373,16 +1174,13 @@ static bool brush_asset_revert_poll(bContext *C)
return paint->brush_asset_reference && (brush->id.tag & LIB_TAG_ASSET_MAIN);
}
static int brush_asset_revert_exec(bContext *C, wmOperator * /*op*/)
static int brush_asset_revert_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Paint *paint = BKE_paint_get_active_from_context(C);
Brush *brush = BKE_paint_brush(paint);
Main *asset_main = BKE_main_from_id(bmain, &brush->id);
/* Reload entire main, including texture dependencies. This relies on there
* being only a single brush asset per blend file. */
BKE_asset_weak_reference_main_reload(*bmain, *asset_main);
blender::bke::asset_edit_id_revert(*bmain, &brush->id, op->reports);
WM_main_add_notifier(NC_BRUSH | NA_EDITED, nullptr);
WM_main_add_notifier(NC_TEXTURE | ND_NODES, nullptr);