Brush Assets: Save As, Delete, Revert and Update operators #117513

Merged
Brecht Van Lommel merged 10 commits from mont29/blender:bap-saveas-asset into brush-assets-project 2024-01-28 00:01:17 +01:00
5 changed files with 434 additions and 2 deletions
Showing only changes of commit ed42cf3270 - Show all commits

View File

@ -62,6 +62,9 @@ class VIEW3D_MT_brush_context_menu(Menu):
layout.operator("brush.reset")
elif context.tool_settings.curves_sculpt:
layout.prop_menu_enum(brush, "curves_sculpt_tool")
layout.operator("sculpt_curves.brush_asset_save_as")
layout.operator("sculpt_curves.brush_asset_save")
layout.operator("sculpt_curves.brush_asset_delete")
class VIEW3D_MT_brush_gpencil_context_menu(Menu):

View File

@ -108,6 +108,9 @@ class AssetLibrary {
*/
static void foreach_loaded(FunctionRef<void(AssetLibrary &)> fn, bool include_all_library);
static std::string resolve_asset_weak_reference_to_full_path(
const AssetWeakReference &asset_reference);
void load_catalogs();
/** Load catalogs that have changed on disk. */
@ -167,8 +170,6 @@ class AssetLibrary {
*/
AssetIdentifier asset_identifier_from_library(StringRef relative_asset_path);
std::string resolve_asset_weak_reference_to_full_path(const AssetWeakReference &asset_reference);
eAssetLibraryType library_type() const;
StringRefNull name() const;
StringRefNull root_path() const;

View File

@ -8,6 +8,8 @@
* \ingroup bke
*/
#include <optional>
#include "BLI_array.hh"
#include "BLI_bit_vector.hh"
#include "BLI_math_matrix_types.hh"
@ -203,6 +205,13 @@ Brush *BKE_paint_brush(Paint *paint);
const Brush *BKE_paint_brush_for_read(const Paint *p);
void BKE_paint_brush_set(Paint *paint, Brush *br);
/**
* Check if the given brush is a valid Brush Asset.
*
* A valid brush Asset is either an actual asset, or a local liboverride of a linked brush asset.
*/
bool BKE_paint_brush_is_valid_asset(const Brush *brush);
/**
* Set the active brush of given paint struct, and store the weak asset reference to it.
* \note Takes ownership of the given `weak_asset_reference`.
@ -211,6 +220,12 @@ bool BKE_paint_brush_asset_set(Paint *paint,
Brush *brush,
AssetWeakReference *weak_asset_reference);
/**
* Get the active brush of given paint struct, together with its weak asset reference.
* \note Returns unset optional if the active brush is not a valid Brush Asset data..
*/
std::optional<AssetWeakReference *> BKE_paint_brush_asset_get(Paint *paint, Brush **r_brush);
/**
* Attempt to restore a valid active brush in `paint` from brush asset information stored in
* `paint`.

View File

@ -8,6 +8,7 @@
#include <cstdlib>
#include <cstring>
#include <optional>
#include "MEM_guardedalloc.h"
@ -673,6 +674,13 @@ void BKE_paint_brush_set(Paint *p, Brush *br)
}
}
bool BKE_paint_brush_is_valid_asset(const Brush *brush)
{
return brush && (ID_IS_ASSET(&brush->id) ||
(!ID_IS_LINKED(&brush->id) && ID_IS_OVERRIDE_LIBRARY_REAL(&brush->id) &&
ID_IS_ASSET(brush->id.override_library->reference)));
}
static void paint_brush_asset_update(Paint &paint,
Brush *brush,
AssetWeakReference *brush_asset_reference)
@ -707,6 +715,21 @@ bool BKE_paint_brush_asset_set(Paint *paint,
return true;
}
std::optional<AssetWeakReference *> BKE_paint_brush_asset_get(Paint *paint, Brush **r_brush)
{
Brush *brush = *r_brush = BKE_paint_brush(paint);
if (!BKE_paint_brush_is_valid_asset(brush)) {
return {};
}
if (paint->brush_asset_reference) {
return paint->brush_asset_reference;
}
return {};
}
void BKE_paint_brush_asset_restore(Main *bmain, Paint *paint)
{
if (paint->brush != nullptr) {

View File

@ -2,12 +2,17 @@
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_fileops.h"
#include "BLI_kdtree.h"
#include "BLI_path_util.h"
#include "BLI_rand.hh"
#include "BLI_string.h"
#include "BLI_task.hh"
#include "BLI_utildefines.h"
#include "BLI_vector_set.hh"
#include "BLO_writefile.hh"
#include "BKE_asset.hh"
#include "BKE_attribute.hh"
#include "BKE_blendfile.hh"
@ -18,6 +23,7 @@
#include "BKE_modifier.hh"
#include "BKE_object.hh"
#include "BKE_paint.hh"
#include "BKE_preferences.h"
#include "BKE_report.h"
#include "WM_api.hh"
@ -25,6 +31,7 @@
#include "WM_toolsystem.hh"
#include "ED_asset_handle.h"
#include "ED_asset_list.h"
#include "ED_curves.hh"
#include "ED_curves_sculpt.hh"
#include "ED_image.hh"
@ -33,6 +40,8 @@
#include "ED_space_api.hh"
#include "ED_view3d.hh"
#include "AS_asset_library.h"
#include "AS_asset_library.hh"
#include "AS_asset_representation.hh"
#include "DEG_depsgraph.hh"
@ -1205,6 +1214,384 @@ static void SCULPT_CURVES_OT_brush_asset_select(wmOperatorType *ot)
ot->srna, "name", nullptr, MAX_NAME, "Brush Name", "Name of the brush asset to select");
}
/* FIXME Quick dirty hack to generate a weak ref from 'raw' paths.
* 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)
{
AssetWeakReference *asset_weak_ref = MEM_new<AssetWeakReference>(__func__);
StringRefNull asset_root_path = user_asset_lib->dirpath;
BLI_assert(file_path.find(asset_root_path) == 0);
std::string relative_asset_path = file_path.substr(size_t(asset_root_path.size()) + 1);
asset_weak_ref->asset_library_type = ASSET_LIBRARY_CUSTOM;
asset_weak_ref->asset_library_identifier = BLI_strdup(user_asset_lib->name);
asset_weak_ref->relative_asset_identifier = BLI_strdup(relative_asset_path.c_str());
return asset_weak_ref;
}
static const bUserAssetLibrary *brush_asset_get_editable_library()
{
for (const AssetLibraryReference &lib_ref : asset_system::all_valid_asset_library_refs()) {
if (lib_ref.type != ASSET_LIBRARY_CUSTOM) {
continue;
}
/* TODO: should be a preference (flag in bUserAssetLibrary or so). */
const bUserAssetLibrary *user_library = BKE_preferences_asset_library_find_index(
&U, lib_ref.custom_library_index);
if (user_library) {
return user_library;
}
}
return nullptr;
}
static std::string brush_asset_root_path_for_save()
{
std::string root_path;
const bUserAssetLibrary *user_library = brush_asset_get_editable_library();
if (!user_library) {
return "";
}
char libpath[FILE_MAX];
BLI_strncpy(libpath, user_library->dirpath, sizeof(libpath));
BLI_path_slash_native(libpath);
BLI_path_normalize(libpath);
root_path = libpath;
if (!BLI_is_dir(root_path.c_str())) {
return "";
}
return root_path + SEP + "Saved" + SEP + "Brushes";
}
static std::string brush_asset_blendfile_path_for_save(const blender::StringRefNull &base_name)
{
std::string root_path = brush_asset_root_path_for_save();
if (root_path.empty()) {
return "";
}
BLI_dir_create_recursive(root_path.c_str());
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,
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;
BKE_blendfile_write_partial_tag_ID(&brush->id, true);
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 int brush_asset_save_as_exec(bContext *C, wmOperator *op)
{
PropertyRNA *name_prop = RNA_struct_find_property(op->ptr, "brush_name");
if (!RNA_property_is_set(op->ptr, name_prop)) {
return OPERATOR_CANCELLED;
}
char name[MAX_NAME];
RNA_property_string_get(op->ptr, name_prop, name);
const std::string filepath = brush_asset_blendfile_path_for_save(name);
if (filepath.empty()) {
return OPERATOR_CANCELLED;
}
ToolSettings *tool_settings = CTX_data_tool_settings(C);
Brush *brush = nullptr;
if (!BKE_paint_brush_asset_get(&tool_settings->curves_sculpt->paint, &brush).has_value()) {
return OPERATOR_CANCELLED;
}
BLI_assert(BKE_paint_brush_is_valid_asset(brush));
std::string final_full_asset_filepath;
const bool sucess = brush_asset_write_in_library(
CTX_data_main(C), brush, name, filepath, final_full_asset_filepath, op->reports);
if (sucess) {
const bUserAssetLibrary *asset_lib = brush_asset_get_editable_library();
AssetWeakReference *new_brush_weak_ref = brush_asset_create_weakref_hack(
asset_lib, final_full_asset_filepath);
Main *bmain = CTX_data_main(C);
brush = BKE_brush_asset_runtime_ensure(bmain, new_brush_weak_ref);
if (!BKE_paint_brush_asset_set(
&tool_settings->curves_sculpt->paint, brush, new_brush_weak_ref))
{
/* Note brush sset was still saved in editable asset library, so was not a no-op. */
BKE_report(op->reports, RPT_WARNING, "Unable to activate just-saved brush asset");
}
/* TODO Is this the right way to update the asset shelf?
* Also, how to update the active item there? */
/* FIXME does not seem to work. Presumably because context here does not have assetlib info? */
const AssetLibraryReference *library = CTX_wm_asset_library_ref(C);
ED_assetlist_clear(library, C);
return OPERATOR_FINISHED;
}
return OPERATOR_CANCELLED;
}
static int brush_asset_save_as_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
{
ToolSettings *tool_settings = CTX_data_tool_settings(C);
Brush *brush = nullptr;
if (!BKE_paint_brush_asset_get(&tool_settings->curves_sculpt->paint, &brush).has_value()) {
return OPERATOR_CANCELLED;
}
RNA_string_set(op->ptr, "brush_name", brush->id.name + 2);
return WM_operator_props_dialog_popup(C, op, 400);
}
static bool brush_asset_save_as_poll(bContext *C)
{
if (!curves_sculpt_poll(C)) {
return false;
}
ToolSettings *tool_settings = CTX_data_tool_settings(C);
Brush *brush = nullptr;
if (BKE_paint_brush_asset_get(&tool_settings->curves_sculpt->paint, &brush).has_value()) {
return brush != nullptr;
}
return false;
}
static void SCULPT_CURVES_OT_brush_asset_save_as(wmOperatorType *ot)
{
ot->name = "Save As Brush Asset";
ot->description =
"Save a copy of the active brush asset into the default asset library, and make it the "
"active brush";
ot->idname = "SCULPT_CURVES_OT_brush_asset_save_as";
ot->exec = brush_asset_save_as_exec;
ot->invoke = brush_asset_save_as_invoke;
ot->poll = brush_asset_save_as_poll;
RNA_def_string(ot->srna,
"brush_name",
nullptr,
MAX_NAME,
"Brush Asset Name",
"Name used to save the brush asset");
}
static bool brush_asset_is_editable(const AssetWeakReference &brush_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(&brush_weak_ref, path_buffer, &dir, &group, &name);
if (!StringRef(dir).endswith(BLENDER_ASSET_FILE_SUFFIX)) {
return false;
}
std::string root_path_for_save = brush_asset_root_path_for_save();
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 int brush_asset_save_exec(bContext *C, wmOperator *op)
{
ToolSettings *tool_settings = CTX_data_tool_settings(C);
Brush *brush = nullptr;
const AssetWeakReference *brush_weak_ref =
BKE_paint_brush_asset_get(&tool_settings->curves_sculpt->paint, &brush).value_or(nullptr);
if (!brush_weak_ref || !brush || !brush_asset_is_editable(*brush_weak_ref)) {
return OPERATOR_CANCELLED;
}
char path_buffer[FILE_MAX_LIBEXTRA];
char *filepath;
AS_asset_full_path_explode_from_weak_ref(
brush_weak_ref, path_buffer, &filepath, nullptr, nullptr);
BLI_assert(BKE_paint_brush_is_valid_asset(brush));
std::string final_full_asset_filepath;
brush_asset_write_in_library(CTX_data_main(C),
brush,
brush->id.name + 2,
filepath,
final_full_asset_filepath,
op->reports);
return OPERATOR_FINISHED;
}
static bool brush_asset_library_editable_poll(bContext *C)
{
if (!curves_sculpt_poll(C)) {
return false;
}
ToolSettings *tool_settings = CTX_data_tool_settings(C);
Brush *brush = nullptr;
const AssetWeakReference *brush_weak_ref =
BKE_paint_brush_asset_get(&tool_settings->curves_sculpt->paint, &brush).value_or(nullptr);
if (!brush_weak_ref || !brush) {
return false;
}
return brush_asset_is_editable(*brush_weak_ref);
}
static void SCULPT_CURVES_OT_brush_asset_save(wmOperatorType *ot)
{
ot->name = "Save Brush Asset";
ot->description = "Re-save the active brush asset in the default asset library";
ot->idname = "SCULPT_CURVES_OT_brush_asset_save";
ot->exec = brush_asset_save_exec;
ot->poll = brush_asset_library_editable_poll;
}
static int brush_asset_delete_exec(bContext *C, wmOperator *op)
{
ToolSettings *tool_settings = CTX_data_tool_settings(C);
Brush *brush = nullptr;
const AssetWeakReference *brush_weak_ref =
BKE_paint_brush_asset_get(&tool_settings->curves_sculpt->paint, &brush).value_or(nullptr);
if (!brush_weak_ref || !brush || !brush_asset_is_editable(*brush_weak_ref)) {
return OPERATOR_CANCELLED;
}
BLI_assert(BKE_paint_brush_is_valid_asset(brush));
char path_buffer[FILE_MAX_LIBEXTRA];
char *filepath;
AS_asset_full_path_explode_from_weak_ref(
brush_weak_ref, path_buffer, &filepath, nullptr, nullptr);
BLI_delete(filepath, false, false);
/* TODO Is this the right way to update the asset shelf?
* Also, how to update the active item there? */
/* FIXME does not seem to work. Presumably because context here does not have assetlib info? */
const AssetLibraryReference *library = CTX_wm_asset_library_ref(C);
ED_assetlist_clear(library, C);
/* FIXME
* How to choose which brush asset should be made active then? */
return OPERATOR_FINISHED;
}
static void SCULPT_CURVES_OT_brush_asset_delete(wmOperatorType *ot)
{
ot->name = "Delete Brush Asset";
ot->description =
"Remove the active brush asset from the default asset library, but keeps it in the current "
"editing session (so it can be saved again to undo deletion)";
ot->idname = "SCULPT_CURVES_OT_brush_asset_delete";
ot->exec = brush_asset_delete_exec;
ot->invoke = WM_operator_confirm;
ot->poll = brush_asset_library_editable_poll;
}
} // namespace blender::ed::sculpt_paint
/* -------------------------------------------------------------------- */
@ -1221,6 +1608,9 @@ void ED_operatortypes_sculpt_curves()
WM_operatortype_append(SCULPT_CURVES_OT_min_distance_edit);
WM_operatortype_append(SCULPT_CURVES_OT_brush_asset_select);
WM_operatortype_append(SCULPT_CURVES_OT_brush_asset_save_as);
WM_operatortype_append(SCULPT_CURVES_OT_brush_asset_save);
WM_operatortype_append(SCULPT_CURVES_OT_brush_asset_delete);
}
/** \} */