WIP: Brush assets project #106303

Draft
Julian Eisel wants to merge 357 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.
4 changed files with 240 additions and 96 deletions
Showing only changes of commit b8bc1c56f7 - Show all commits

View File

@ -52,6 +52,7 @@ class VIEW3D_MT_brush_context_menu(Menu):
layout.separator()
layout.operator("brush.asset_edit_metadata", text="Edit Metadata")
layout.operator("brush.asset_update", text="Update Asset")
layout.operator("brush.asset_revert", text="Revert to Asset")
else:

View File

@ -33,6 +33,7 @@
#include "DNA_ID_enums.h"
struct bUserAssetLibrary;
struct AssetMetaData;
struct AssetWeakReference;
struct ID;
struct Main;
@ -52,14 +53,11 @@ Main *asset_edit_main(const ID &id);
bool asset_edit_id_is_editable(const ID &id);
std::optional<std::string> asset_edit_id_save_as(
Main &global_main,
const ID &id,
StringRef name,
std::optional<blender::asset_system::CatalogID> catalog_id,
std::optional<std::string> catalog_simple_name,
const bUserAssetLibrary &user_library,
ReportList &reports);
std::optional<std::string> asset_edit_id_save_as(Main &global_main,
const ID &id,
StringRef 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);

View File

@ -194,12 +194,11 @@ static bool asset_write_in_library(Main *bmain,
const ID &id_const,
const StringRef 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
/* TODO: Comment seems to be resolved by separate #Main storage?
* XXX
* FIXME
*
* This code is _pure evil_. It does in-place manipulation on IDs in global Main database,
@ -224,8 +223,8 @@ static bool asset_write_in_library(Main *bmain,
const int prev_tag = id.tag;
const int prev_us = id.us;
const std::string prev_name = id.name + 2;
/* TODO: Remove library overrides stuff now that they are not used for brush assets. */
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;
@ -235,18 +234,8 @@ static bool asset_write_in_library(Main *bmain,
id.tag &= ~LIB_TAG_RUNTIME;
id.us = 1;
BLI_strncpy(id.name + 2, name.data(), std::min(sizeof(id.name) - 2, size_t(name.size())));
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. */
@ -266,7 +255,6 @@ static bool asset_write_in_library(Main *bmain,
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;
}
@ -379,8 +367,6 @@ static AssetEditBlend &asset_edit_blend_file_ensure(const StringRef filepath)
std::optional<std::string> asset_edit_id_save_as(Main &global_main,
const ID &id,
const StringRef name,
std::optional<asset_system::CatalogID> catalog_id,
std::optional<std::string> catalog_simple_name,
const bUserAssetLibrary &user_library,
ReportList &reports)
{
@ -391,14 +377,8 @@ std::optional<std::string> asset_edit_id_save_as(Main &global_main,
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);
const bool success = asset_write_in_library(
asset_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;
@ -421,8 +401,6 @@ bool asset_edit_id_save(Main & /*global_main*/, const ID &id, ReportList &report
id,
id.name + 2,
asset_blend->filepath.c_str(),
std::nullopt,
std::nullopt,
final_full_asset_filepath,
reports);

View File

@ -788,23 +788,49 @@ static AssetWeakReference brush_asset_create_weakref_hack(const bUserAssetLibrar
return asset_weak_ref;
}
static void refresh_asset_library(const bContext *C, const bUserAssetLibrary &user_library)
static std::optional<AssetLibraryReference> library_to_library_ref(
const asset_system::AssetLibrary &library)
{
for (const AssetLibraryReference &ref : asset_system::all_valid_asset_library_refs()) {
const std::string root_path = AS_asset_library_root_path_from_library_ref(ref);
/* Use #BLI_path_cmp_normalized because `library.root_path()` ends with a slash while
* `root_path` doesn't. */
if (BLI_path_cmp_normalized(root_path.c_str(), library.root_path().c_str()) == 0) {
return ref;
}
}
return std::nullopt;
}
static AssetLibraryReference user_library_to_library_ref(const bUserAssetLibrary &user_library)
{
AssetLibraryReference library_ref{};
library_ref.custom_library_index = BLI_findindex(&U.asset_libraries, &user_library);
library_ref.type = ASSET_LIBRARY_CUSTOM;
return library_ref;
}
static const bUserAssetLibrary *library_ref_to_user_library(
const AssetLibraryReference &library_ref)
{
if (library_ref.type != ASSET_LIBRARY_CUSTOM) {
return nullptr;
}
return static_cast<const bUserAssetLibrary *>(
BLI_findlink(&U.asset_libraries, library_ref.custom_library_index));
}
static void refresh_asset_library(const bContext *C, const AssetLibraryReference &library_ref)
{
asset::list::clear(&library_ref, C);
/* TODO: Should the all library reference be automatically cleared? */
AssetLibraryReference all_lib_ref = asset_system::all_library_reference();
asset::list::clear(&all_lib_ref, C);
}
/* TODO: this is convoluted, can we create a reference from pointer? */
for (const AssetLibraryReference &lib_ref : asset_system::all_valid_asset_library_refs()) {
if (lib_ref.type == ASSET_LIBRARY_CUSTOM) {
const bUserAssetLibrary *ref_user_library = BKE_preferences_asset_library_find_index(
&U, lib_ref.custom_library_index);
if (ref_user_library == &user_library) {
asset::list::clear(&lib_ref, C);
return;
}
}
}
static void refresh_asset_library(const bContext *C, const bUserAssetLibrary &user_library)
{
refresh_asset_library(C, user_library_to_library_ref(user_library));
}
static bool brush_asset_save_as_poll(bContext *C)
@ -858,14 +884,6 @@ static asset_system::AssetCatalog &asset_library_ensure_catalogs_in_path(
return *library.catalog_service().find_catalog_by_path(path);
}
static AssetLibraryReference user_library_to_library_ref(const bUserAssetLibrary &user_library)
{
AssetLibraryReference library_ref{};
library_ref.custom_library_index = BLI_findindex(&U.asset_libraries, &user_library);
library_ref.type = ASSET_LIBRARY_CUSTOM;
return library_ref;
}
static int brush_asset_save_as_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
@ -905,19 +923,15 @@ static int brush_asset_save_as_exec(bContext *C, wmOperator *op)
char catalog_path[MAX_NAME];
RNA_string_get(op->ptr, "catalog_path", catalog_path);
std::optional<asset_system::CatalogID> catalog_id;
std::optional<StringRefNull> catalog_simple_name;
AssetMetaData &meta_data = *brush->id.asset_data;
if (catalog_path[0]) {
const asset_system::AssetCatalog &catalog = asset_library_ensure_catalogs_in_path(
*library, catalog_path);
catalog_id = catalog.catalog_id;
catalog_simple_name = catalog.simple_name;
BKE_asset_metadata_catalog_id_set(&meta_data, catalog.catalog_id, catalog.simple_name.c_str());
}
const std::optional<std::string> final_full_asset_filepath = bke::asset_edit_id_save_as(
*bmain, brush->id, name, catalog_id, catalog_simple_name, *user_library, *op->reports);
*bmain, brush->id, name, *user_library, *op->reports);
if (!final_full_asset_filepath) {
return OPERATOR_CANCELLED;
}
@ -942,20 +956,6 @@ static int brush_asset_save_as_exec(bContext *C, wmOperator *op)
return OPERATOR_FINISHED;
}
static std::optional<AssetLibraryReference> library_to_library_ref(
const asset_system::AssetLibrary &library)
{
for (const AssetLibraryReference &ref : asset_system::all_valid_asset_library_refs()) {
const std::string root_path = AS_asset_library_root_path_from_library_ref(ref);
/* Use #BLI_path_cmp_normalized because `library.root_path()` ends with a slash while
* `root_path` doesn't. */
if (BLI_path_cmp_normalized(root_path.c_str(), library.root_path().c_str()) == 0) {
return ref;
}
}
return std::nullopt;
}
static bool library_is_editable(const AssetLibraryReference &library)
{
if (library.type == ASSET_LIBRARY_ESSENTIALS) {
@ -967,12 +967,10 @@ static bool library_is_editable(const AssetLibraryReference &library)
static int brush_asset_save_as_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
{
Paint *paint = BKE_paint_get_active_from_context(C);
Brush *brush = BKE_paint_brush(paint);
const AssetWeakReference &brush_weak_ref = *paint->brush_asset_reference;
const asset_system::AssetRepresentation *asset = asset::find_asset_from_weak_ref(
*C, brush_weak_ref, op->reports);
if (!asset) {
BLI_assert_unreachable();
return OPERATOR_CANCELLED;
}
const asset_system::AssetLibrary &library = asset->owner_asset_library();
@ -982,7 +980,7 @@ static int brush_asset_save_as_invoke(bContext *C, wmOperator *op, const wmEvent
return OPERATOR_CANCELLED;
}
RNA_string_set(op->ptr, "name", brush->id.name + 2);
RNA_string_set(op->ptr, "name", asset->get_name().c_str());
/* If the library isn't saved from the operator's last execution, find the current library or the
* first library if the current library isn't editable. */
@ -1027,27 +1025,20 @@ static const EnumPropertyItem *rna_asset_library_reference_itemf(bContext * /*C*
return items;
}
static void visit_asset_catalog_for_search_fn(
const bContext *C,
PointerRNA *ptr,
PropertyRNA * /*prop*/,
const char *edit_text,
FunctionRef<void(StringPropertySearchVisitParams)> visit_fn)
static void visit_library_catalogs_catalog_for_search(
const Main &bmain,
const bUserAssetLibrary &user_library,
const StringRef edit_text,
const FunctionRef<void(StringPropertySearchVisitParams)> visit_fn)
{
/* NOTE: Using the all library would also be a valid choice. */
const bUserAssetLibrary *user_library = get_asset_library_from_prop(*ptr);
if (!user_library) {
return;
}
asset_system::AssetLibrary *library = AS_asset_library_load(
CTX_data_main(C), user_library_to_library_ref(*user_library));
const asset_system::AssetLibrary *library = AS_asset_library_load(
&bmain, user_library_to_library_ref(user_library));
if (!library) {
return;
}
if (edit_text && edit_text[0] != '\0') {
asset_system::AssetCatalogPath edit_path = edit_text;
if (!edit_text.is_empty()) {
const asset_system::AssetCatalogPath edit_path = edit_text;
if (!library->catalog_service().find_catalog_by_path(edit_path)) {
visit_fn(StringPropertySearchVisitParams{edit_path.str(), std::nullopt, ICON_ADD});
}
@ -1059,6 +1050,20 @@ static void visit_asset_catalog_for_search_fn(
});
}
static void visit_library_prop_catalogs_catalog_for_search_fn(
const bContext *C,
PointerRNA *ptr,
PropertyRNA * /*prop*/,
const char *edit_text,
FunctionRef<void(StringPropertySearchVisitParams)> visit_fn)
{
/* NOTE: Using the all library would also be a valid choice. */
if (const bUserAssetLibrary *user_library = get_asset_library_from_prop(*ptr)) {
visit_library_catalogs_catalog_for_search(
*CTX_data_main(C), *user_library, edit_text, visit_fn);
}
}
static void BRUSH_OT_asset_save_as(wmOperatorType *ot)
{
ot->name = "Save as Brush Asset";
@ -1081,7 +1086,168 @@ static void BRUSH_OT_asset_save_as(wmOperatorType *ot)
prop = RNA_def_string(
ot->srna, "catalog_path", nullptr, MAX_NAME, "Catalog", "Catalog to use for the new asset");
RNA_def_property_string_search_func_runtime(
prop, visit_asset_catalog_for_search_fn, PROP_STRING_SEARCH_SUGGESTION);
prop, visit_library_prop_catalogs_catalog_for_search_fn, PROP_STRING_SEARCH_SUGGESTION);
}
static int brush_asset_edit_metadata_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
const Paint *paint = BKE_paint_get_active_from_context(C);
const Brush *brush = (paint) ? BKE_paint_brush_for_read(paint) : nullptr;
BLI_assert(ID_IS_ASSET(&brush->id));
const AssetWeakReference &brush_weak_ref = *paint->brush_asset_reference;
const asset_system::AssetRepresentation *asset = asset::find_asset_from_weak_ref(
*C, brush_weak_ref, op->reports);
if (!asset) {
return OPERATOR_CANCELLED;
}
const asset_system::AssetLibrary &library_const = asset->owner_asset_library();
const AssetLibraryReference library_ref = *library_to_library_ref(library_const);
asset_system::AssetLibrary *library = AS_asset_library_load(bmain, library_ref);
char catalog_path[MAX_NAME];
RNA_string_get(op->ptr, "catalog_path", catalog_path);
AssetMetaData &meta_data = *brush->id.asset_data;
MEM_SAFE_FREE(meta_data.author);
meta_data.author = RNA_string_get_alloc(op->ptr, "author", nullptr, 0, nullptr);
MEM_SAFE_FREE(meta_data.description);
meta_data.description = RNA_string_get_alloc(op->ptr, "description", nullptr, 0, nullptr);
if (catalog_path[0]) {
const asset_system::AssetCatalog &catalog = asset_library_ensure_catalogs_in_path(
*library, catalog_path);
BKE_asset_metadata_catalog_id_set(&meta_data, catalog.catalog_id, catalog.simple_name.c_str());
}
if (!bke::asset_edit_id_save(*bmain, brush->id, *op->reports)) {
return OPERATOR_CANCELLED;
}
char asset_full_path_buffer[FILE_MAX_LIBEXTRA];
char *file_path = nullptr;
AS_asset_full_path_explode_from_weak_ref(
&brush_weak_ref, asset_full_path_buffer, &file_path, nullptr, nullptr);
if (!file_path) {
BLI_assert_unreachable();
return OPERATOR_CANCELLED;
}
library->catalog_service().write_to_disk(file_path);
refresh_asset_library(C, library_ref);
WM_main_add_notifier(NC_ASSET | ND_ASSET_LIST | NA_EDITED, nullptr);
return OPERATOR_FINISHED;
}
static int brush_asset_edit_metadata_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
{
const Paint *paint = BKE_paint_get_active_from_context(C);
const AssetWeakReference &brush_weak_ref = *paint->brush_asset_reference;
const asset_system::AssetRepresentation *asset = asset::find_asset_from_weak_ref(
*C, brush_weak_ref, op->reports);
if (!asset) {
return OPERATOR_CANCELLED;
}
const asset_system::AssetLibrary &library = asset->owner_asset_library();
const AssetMetaData &meta_data = asset->get_metadata();
if (!RNA_struct_property_is_set(op->ptr, "catalog_path")) {
const asset_system::CatalogID &id = meta_data.catalog_id;
if (const asset_system::AssetCatalog *catalog = library.catalog_service().find_catalog(id)) {
RNA_string_set(op->ptr, "catalog_path", catalog->path.c_str());
}
}
if (!RNA_struct_property_is_set(op->ptr, "author")) {
RNA_string_set(op->ptr, "author", meta_data.author ? meta_data.author : "");
}
if (!RNA_struct_property_is_set(op->ptr, "description")) {
RNA_string_set(op->ptr, "description", meta_data.description ? meta_data.description : "");
}
return WM_operator_props_dialog_popup(C, op, 400, std::nullopt, IFACE_("Edit Metadata"));
}
static void visit_active_library_catalogs_catalog_for_search_fn(
const bContext *C,
PointerRNA * /*ptr*/,
PropertyRNA * /*prop*/,
const char *edit_text,
FunctionRef<void(StringPropertySearchVisitParams)> visit_fn)
{
const Paint *paint = BKE_paint_get_active_from_context(C);
const AssetWeakReference &brush_weak_ref = *paint->brush_asset_reference;
const asset_system::AssetRepresentation *asset = asset::find_asset_from_weak_ref(
*C, brush_weak_ref, nullptr);
if (!asset) {
return;
}
const asset_system::AssetLibrary &library = asset->owner_asset_library();
/* NOTE: Using the all library would also be a valid choice. */
visit_library_catalogs_catalog_for_search(
*CTX_data_main(C),
*library_ref_to_user_library(*library_to_library_ref(library)),
edit_text,
visit_fn);
}
static bool brush_asset_edit_metadata_poll(bContext *C)
{
Paint *paint = BKE_paint_get_active_from_context(C);
Brush *brush = (paint) ? BKE_paint_brush(paint) : nullptr;
if (paint == nullptr || brush == nullptr) {
return false;
}
if (!ID_IS_ASSET(&brush->id)) {
BLI_assert_unreachable();
return false;
}
const AssetWeakReference *brush_weak_ref = paint->brush_asset_reference;
if (!brush_weak_ref) {
BLI_assert_unreachable();
return false;
}
const asset_system::AssetRepresentation *asset = asset::find_asset_from_weak_ref(
*C, *brush_weak_ref, nullptr);
if (!asset) {
BLI_assert_unreachable();
return false;
}
const std::optional<AssetLibraryReference> library_ref = library_to_library_ref(
asset->owner_asset_library());
if (!library_ref) {
BLI_assert_unreachable();
return false;
}
if (!library_is_editable(*library_ref)) {
CTX_wm_operator_poll_msg_set(C, "Asset library is not editable");
return false;
}
if (!bke::asset_edit_id_is_editable(brush->id)) {
CTX_wm_operator_poll_msg_set(C, "Asset file is not editable");
return false;
}
return true;
}
static void BRUSH_OT_asset_edit_metadata(wmOperatorType *ot)
{
ot->name = "Edit Metadata";
ot->description = "Edit asset information like the catalog, preview image, tags, or author";
ot->idname = "BRUSH_OT_asset_edit_metadata";
ot->exec = brush_asset_edit_metadata_exec;
ot->invoke = brush_asset_edit_metadata_invoke;
ot->poll = brush_asset_edit_metadata_poll;
PropertyRNA *prop = RNA_def_string(
ot->srna, "catalog_path", nullptr, MAX_NAME, "Catalog", "The asset's catalog path");
RNA_def_property_string_search_func_runtime(
prop, visit_active_library_catalogs_catalog_for_search_fn, PROP_STRING_SEARCH_SUGGESTION);
RNA_def_string(ot->srna, "author", nullptr, MAX_NAME, "Author", "");
RNA_def_string(ot->srna, "description", nullptr, MAX_NAME, "Description", "");
}
static bool brush_asset_delete_poll(bContext *C)
@ -1710,6 +1876,7 @@ void ED_operatortypes_paint()
WM_operatortype_append(BRUSH_OT_stencil_reset_transform);
WM_operatortype_append(BRUSH_OT_asset_select);
WM_operatortype_append(BRUSH_OT_asset_save_as);
WM_operatortype_append(BRUSH_OT_asset_edit_metadata);
WM_operatortype_append(BRUSH_OT_asset_delete);
WM_operatortype_append(BRUSH_OT_asset_update);
WM_operatortype_append(BRUSH_OT_asset_revert);