From ed42cf32703469bbbfdeeecc3ddab2585219730e Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Wed, 24 Jan 2024 20:59:06 +0100 Subject: [PATCH 1/6] WIP 'save as', 'save' and 'delete' brush assets library editing prototype. DISCLAIMER: This is a highly quickly hacked code to get these basic features testable. A lot more work will be required to get this code 'good'. Also, while functionality itself work (from quick testing), the UI updates of the Asset Shelf does not. User need to manually reload (from the shelf's top-left menu) to see addition/removals of assets. This adds three new operators (to Curves Sculpt mode only currently) to `Save As`, `Save` and `Delete` a brush asset. * **Save As** can be called with any active brush asset, it creates a new copy of it in the 'default user library'. This operator always create a new asset (and asset file) in that library. * **Save** can only be called on an active brush from the 'default user library'. It will replace the source library file by a new one with current brush settings. * **Delete** can only be called on an active brush from the 'default user library'. It will delete the source library file, but keep the current brush data in the editing session. These operators are in the drop-down menu next to the Brush icon/selector, in the `Tool` tab of the properties panel in the 3DView, when in Curves Sculpt mode. --------------------- _There are most likely more issues than listed below, and topics like naming, actual desired behaviors, UI etc. also need to be evaluated._ * The desired behavior when deleting an edtable asset is unclear. Do we want to keep the current session data, or not ? * If yes, it gives a way to undo the asset deletion. * If yes, how to represent that (since this brush is not an asset enaymore)? * If not, how to choose the new active Brush? * No automatic update of the Asset Shelf. Manual update is also worryingly slow for me (takes several seconds for the 'all' library to refresh its content). AFAIK this is already known issue with the current asset system. * No way to say to the Asset Shelf which is the active asset? * The 'write partial' code needs to be fully refactored, it's current behavior is beyond bad (editing of IDs in Main, modification of Main list of IDs even...). Orthogonal topic though, needs its own task (also affects the `library` python API, and copy/paste code, at least). * Somewhat related, current PR will keep linked dependencies linked. These should be rather 'made local' in the written library file. * Current PR only check that a source library blendfile is in the 'editable' area of the 'default user library' and that the file name ends with `.asset.blend` to consider that source file as editable. Maybe we rather want to also check the file content itself, to b e sure it was not manually edited by the user? * Current PR made several 'quick hacks' that need proper (re-)implementation. * This code needs to be made generic to all sculpt/paint modes (known TODO). --- scripts/startup/bl_ui/space_view3d_toolbar.py | 3 + .../blender/asset_system/AS_asset_library.hh | 5 +- source/blender/blenkernel/BKE_paint.hh | 15 + source/blender/blenkernel/intern/paint.cc | 23 ++ .../editors/sculpt_paint/curves_sculpt_ops.cc | 390 ++++++++++++++++++ 5 files changed, 434 insertions(+), 2 deletions(-) diff --git a/scripts/startup/bl_ui/space_view3d_toolbar.py b/scripts/startup/bl_ui/space_view3d_toolbar.py index e25bb7d60cb..7e7e541b82f 100644 --- a/scripts/startup/bl_ui/space_view3d_toolbar.py +++ b/scripts/startup/bl_ui/space_view3d_toolbar.py @@ -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): diff --git a/source/blender/asset_system/AS_asset_library.hh b/source/blender/asset_system/AS_asset_library.hh index 81c77c12bbc..73caec5f4a4 100644 --- a/source/blender/asset_system/AS_asset_library.hh +++ b/source/blender/asset_system/AS_asset_library.hh @@ -108,6 +108,9 @@ class AssetLibrary { */ static void foreach_loaded(FunctionRef 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; diff --git a/source/blender/blenkernel/BKE_paint.hh b/source/blender/blenkernel/BKE_paint.hh index 788c4b78bd4..3988c2632ae 100644 --- a/source/blender/blenkernel/BKE_paint.hh +++ b/source/blender/blenkernel/BKE_paint.hh @@ -8,6 +8,8 @@ * \ingroup bke */ +#include + #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 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`. diff --git a/source/blender/blenkernel/intern/paint.cc b/source/blender/blenkernel/intern/paint.cc index 498d7fdddec..7482c36f145 100644 --- a/source/blender/blenkernel/intern/paint.cc +++ b/source/blender/blenkernel/intern/paint.cc @@ -8,6 +8,7 @@ #include #include +#include #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 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) { diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc index 6b96a5cb757..e5eb0ab026e 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc @@ -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(__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); } /** \} */ -- 2.30.2 From 142936c2cc019971b3d7c6e6392d5a110590bcac Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 26 Jan 2024 17:14:19 +0100 Subject: [PATCH 2/6] Make brush asset operators work with all paint modes --- .../keyconfig/keymap_data/blender_default.py | 2 +- scripts/startup/bl_ui/space_view3d_toolbar.py | 7 +- .../editors/sculpt_paint/curves_sculpt_ops.cc | 441 ------------------ .../blender/editors/sculpt_paint/paint_ops.cc | 441 ++++++++++++++++++ 4 files changed, 446 insertions(+), 445 deletions(-) diff --git a/scripts/presets/keyconfig/keymap_data/blender_default.py b/scripts/presets/keyconfig/keymap_data/blender_default.py index be09a6af59d..f8a45961266 100644 --- a/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -6800,7 +6800,7 @@ def km_asset_shelf_brushes(_params): ) items.extend([ - ("sculpt_curves.brush_asset_select", {"type": 'LEFTMOUSE', "value": 'CLICK'}, None), + ("brush.asset_select", {"type": 'LEFTMOUSE', "value": 'CLICK'}, None), ]) return keymap diff --git a/scripts/startup/bl_ui/space_view3d_toolbar.py b/scripts/startup/bl_ui/space_view3d_toolbar.py index 7e7e541b82f..2ad363a2bc8 100644 --- a/scripts/startup/bl_ui/space_view3d_toolbar.py +++ b/scripts/startup/bl_ui/space_view3d_toolbar.py @@ -62,9 +62,10 @@ 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") + + layout.operator("brush.asset_save_as") + layout.operator("brush.asset_save") + layout.operator("brush.asset_delete") class VIEW3D_MT_brush_gpencil_context_menu(Menu): diff --git a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc index e5eb0ab026e..017c28cc0c8 100644 --- a/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc +++ b/source/blender/editors/sculpt_paint/curves_sculpt_ops.cc @@ -2,20 +2,13 @@ * * 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" #include "BKE_brush.hh" #include "BKE_bvhutils.hh" #include "BKE_context.hh" @@ -23,15 +16,11 @@ #include "BKE_modifier.hh" #include "BKE_object.hh" #include "BKE_paint.hh" -#include "BKE_preferences.h" -#include "BKE_report.h" #include "WM_api.hh" #include "WM_message.hh" #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" @@ -40,10 +29,6 @@ #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" #include "DEG_depsgraph_query.hh" @@ -1171,427 +1156,6 @@ static void SCULPT_CURVES_OT_min_distance_edit(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_DEPENDS_ON_CURSOR; } -/* -------------------------------------------------------------------- */ - -static int brush_asset_select_exec(bContext *C, wmOperator *op) -{ - /* This operator currently covers both cases: the file/asset browser file list and the asset list - * used for the asset-view template. Once the asset list design is used by the Asset Browser, - * this can be simplified to just that case. */ - asset_system::AssetRepresentation *asset = CTX_wm_asset(C); - if (!asset) { - return OPERATOR_CANCELLED; - } - - AssetWeakReference *brush_asset_reference = asset->make_weak_reference(); - Brush *brush = BKE_brush_asset_runtime_ensure(CTX_data_main(C), brush_asset_reference); - - ToolSettings *tool_settings = CTX_data_tool_settings(C); - - if (!BKE_paint_brush_asset_set( - &tool_settings->curves_sculpt->paint, brush, brush_asset_reference)) - { - /* Note brush datablock was still added, so was not a no-op. */ - BKE_report(op->reports, RPT_WARNING, "Unable to select brush, wrong object mode"); - return OPERATOR_FINISHED; - } - - WM_main_add_notifier(NC_SCENE | ND_TOOLSETTINGS, nullptr); - - return OPERATOR_FINISHED; -} - -static void SCULPT_CURVES_OT_brush_asset_select(wmOperatorType *ot) -{ - ot->name = "Select Brush Asset"; - ot->description = "Select a brush asset as currently sculpt/paint tool - TESTING PURPOSE ONLY"; - ot->idname = "SCULPT_CURVES_OT_brush_asset_select"; - - ot->exec = brush_asset_select_exec; - ot->poll = curves_sculpt_poll; - - ot->prop = RNA_def_string( - 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(__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 /* -------------------------------------------------------------------- */ @@ -1606,11 +1170,6 @@ void ED_operatortypes_sculpt_curves() WM_operatortype_append(SCULPT_CURVES_OT_select_random); WM_operatortype_append(SCULPT_CURVES_OT_select_grow); 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); } /** \} */ diff --git a/source/blender/editors/sculpt_paint/paint_ops.cc b/source/blender/editors/sculpt_paint/paint_ops.cc index 755649d4881..5e557bddb06 100644 --- a/source/blender/editors/sculpt_paint/paint_ops.cc +++ b/source/blender/editors/sculpt_paint/paint_ops.cc @@ -12,8 +12,10 @@ #include "MEM_guardedalloc.h" +#include "BLI_fileops.h" #include "BLI_listbase.h" #include "BLI_math_vector.h" +#include "BLI_path_util.h" #include "BLI_string.h" #include "BLI_utildefines.h" @@ -26,14 +28,21 @@ #include "DNA_object_types.h" #include "DNA_scene_types.h" +#include "BLO_writefile.hh" + +#include "BKE_asset.hh" +#include "BKE_blendfile.hh" #include "BKE_brush.hh" #include "BKE_context.hh" #include "BKE_image.h" #include "BKE_lib_id.hh" #include "BKE_main.hh" #include "BKE_paint.hh" +#include "BKE_preferences.h" #include "BKE_report.h" +#include "ED_asset_handle.h" +#include "ED_asset_list.h" #include "ED_image.hh" #include "ED_paint.hh" #include "ED_screen.hh" @@ -45,6 +54,10 @@ #include "RNA_access.hh" #include "RNA_define.hh" +#include "AS_asset_library.h" +#include "AS_asset_library.hh" +#include "AS_asset_representation.hh" + #include "curves_sculpt_intern.hh" #include "paint_intern.hh" #include "sculpt_intern.hh" @@ -1028,6 +1041,430 @@ static void PAINT_OT_brush_select(wmOperatorType *ot) RNA_def_property_flag(prop, PropertyFlag(PROP_HIDDEN | PROP_SKIP_SAVE)); } +/**************************** Brush Assets **********************************/ + +static bool brush_asset_poll(bContext *C) +{ + return BKE_paint_get_active_from_context(C) != nullptr; +} + +static int brush_asset_select_exec(bContext *C, wmOperator *op) +{ + /* This operator currently covers both cases: the file/asset browser file list and the asset list + * used for the asset-view template. Once the asset list design is used by the Asset Browser, + * this can be simplified to just that case. */ + blender::asset_system::AssetRepresentation *asset = CTX_wm_asset(C); + if (!asset) { + return OPERATOR_CANCELLED; + } + + AssetWeakReference *brush_asset_reference = asset->make_weak_reference(); + Brush *brush = BKE_brush_asset_runtime_ensure(CTX_data_main(C), brush_asset_reference); + + Paint *paint = BKE_paint_get_active_from_context(C); + + if (!BKE_paint_brush_asset_set(paint, brush, brush_asset_reference)) { + /* Note brush datablock was still added, so was not a no-op. */ + BKE_report(op->reports, RPT_WARNING, "Unable to select brush, wrong object mode"); + return OPERATOR_FINISHED; + } + + WM_main_add_notifier(NC_SCENE | ND_TOOLSETTINGS, nullptr); + + return OPERATOR_FINISHED; +} + +static void BRUSH_OT_asset_select(wmOperatorType *ot) +{ + ot->name = "Select Brush Asset"; + ot->description = "Select a brush asset as currently sculpt/paint tool - TESTING PURPOSE ONLY"; + ot->idname = "BRUSH_OT_asset_select"; + + ot->exec = brush_asset_select_exec; + ot->poll = brush_asset_poll; + + ot->prop = RNA_def_string( + 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(__func__); + + blender::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 : + blender::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 blender::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; + } + + Paint *paint = BKE_paint_get_active_from_context(C); + Brush *brush = nullptr; + if (!BKE_paint_brush_asset_get(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(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*/) +{ + Paint *paint = BKE_paint_get_active_from_context(C); + Brush *brush = nullptr; + if (!BKE_paint_brush_asset_get(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) +{ + Paint *paint = BKE_paint_get_active_from_context(C); + if (paint == nullptr) { + return false; + } + + Brush *brush = nullptr; + if (BKE_paint_brush_asset_get(paint, &brush).has_value()) { + return brush != nullptr; + } + return false; +} + +static void BRUSH_OT_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 = "BRUSH_OT_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 (!blender::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() || !blender::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) +{ + Paint *paint = BKE_paint_get_active_from_context(C); + Brush *brush = nullptr; + const AssetWeakReference *brush_weak_ref = + BKE_paint_brush_asset_get(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) +{ + Paint *paint = BKE_paint_get_active_from_context(C); + if (paint == nullptr) { + return false; + } + + Brush *brush = nullptr; + const AssetWeakReference *brush_weak_ref = + BKE_paint_brush_asset_get(paint, &brush).value_or(nullptr); + + if (!brush_weak_ref || !brush) { + return false; + } + + return brush_asset_is_editable(*brush_weak_ref); +} + +static void BRUSH_OT_asset_save(wmOperatorType *ot) +{ + ot->name = "Save Brush Asset"; + ot->description = "Re-save the active brush asset in the default asset library"; + ot->idname = "BRUSH_OT_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*/) +{ + Paint *paint = BKE_paint_get_active_from_context(C); + Brush *brush = nullptr; + const AssetWeakReference *brush_weak_ref = + BKE_paint_brush_asset_get(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 BRUSH_OT_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 = "BRUSH_OT_asset_delete"; + + ot->exec = brush_asset_delete_exec; + ot->invoke = WM_operator_confirm; + ot->poll = brush_asset_library_editable_poll; +} + /***** Stencil Control *****/ enum StencilControlMode { @@ -1489,6 +1926,10 @@ void ED_operatortypes_paint() WM_operatortype_append(BRUSH_OT_stencil_control); WM_operatortype_append(BRUSH_OT_stencil_fit_image_aspect); 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_save); + WM_operatortype_append(BRUSH_OT_asset_delete); /* NOTE: particle uses a different system, can be added with existing operators in `wm.py`. */ WM_operatortype_append(PAINT_OT_brush_select); -- 2.30.2 From bfe7be8e9615d1f1605e309e5b5b54f8fd54ca54 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 26 Jan 2024 18:01:53 +0100 Subject: [PATCH 3/6] Add TODO comments regarding file saving --- source/blender/editors/sculpt_paint/paint_ops.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/blender/editors/sculpt_paint/paint_ops.cc b/source/blender/editors/sculpt_paint/paint_ops.cc index 5e557bddb06..06f5fe57706 100644 --- a/source/blender/editors/sculpt_paint/paint_ops.cc +++ b/source/blender/editors/sculpt_paint/paint_ops.cc @@ -1219,6 +1219,8 @@ static bool brush_asset_write_in_library(Main *bmain, 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); -- 2.30.2 From 16e04f8b4b16d9dde84307a6652072b02890f6b4 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 26 Jan 2024 20:28:01 +0100 Subject: [PATCH 4/6] Fix ID template not respecting hide_buttons regarding overrides --- .../editors/interface/interface_templates.cc | 122 +++++++++--------- 1 file changed, 62 insertions(+), 60 deletions(-) diff --git a/source/blender/editors/interface/interface_templates.cc b/source/blender/editors/interface/interface_templates.cc index baf25003bcd..a801914b63d 100644 --- a/source/blender/editors/interface/interface_templates.cc +++ b/source/blender/editors/interface/interface_templates.cc @@ -1374,70 +1374,72 @@ static void template_ID(const bContext *C, template_id_workspace_pin_extra_icon(template_ui, but); - if (ID_IS_LINKED(id)) { - const bool disabled = !BKE_idtype_idcode_is_localizable(GS(id->name)); - if (id->tag & LIB_TAG_INDIRECT) { - but = uiDefIconBut(block, - UI_BTYPE_BUT, - 0, - ICON_LIBRARY_DATA_INDIRECT, - 0, - 0, - UI_UNIT_X, - UI_UNIT_Y, - nullptr, - 0, - 0, - 0, - 0, - TIP_("Indirect library data-block, cannot be made local, " - "Shift + Click to create a library override hierarchy")); + if (!hide_buttons) { + if (ID_IS_LINKED(id)) { + const bool disabled = !BKE_idtype_idcode_is_localizable(GS(id->name)); + if (id->tag & LIB_TAG_INDIRECT) { + but = uiDefIconBut(block, + UI_BTYPE_BUT, + 0, + ICON_LIBRARY_DATA_INDIRECT, + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + nullptr, + 0, + 0, + 0, + 0, + TIP_("Indirect library data-block, cannot be made local, " + "Shift + Click to create a library override hierarchy")); + } + else { + but = uiDefIconBut(block, + UI_BTYPE_BUT, + 0, + ICON_LIBRARY_DATA_DIRECT, + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + nullptr, + 0, + 0, + 0, + 0, + TIP_("Direct linked library data-block, click to make local, " + "Shift + Click to create a library override")); + } + if (disabled) { + UI_but_flag_enable(but, UI_BUT_DISABLED); + } + else { + UI_but_funcN_set( + but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_LOCAL)); + } } - else { - but = uiDefIconBut(block, - UI_BTYPE_BUT, - 0, - ICON_LIBRARY_DATA_DIRECT, - 0, - 0, - UI_UNIT_X, - UI_UNIT_Y, - nullptr, - 0, - 0, - 0, - 0, - TIP_("Direct linked library data-block, click to make local, " - "Shift + Click to create a library override")); - } - if (disabled) { - UI_but_flag_enable(but, UI_BUT_DISABLED); - } - else { + else if (ID_IS_OVERRIDE_LIBRARY(id)) { + but = uiDefIconBut( + block, + UI_BTYPE_BUT, + 0, + ICON_LIBRARY_DATA_OVERRIDE, + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + nullptr, + 0, + 0, + 0, + 0, + TIP_("Library override of linked data-block, click to make fully local, " + "Shift + Click to clear the library override and toggle if it can be edited")); UI_but_funcN_set( - but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_LOCAL)); + but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_OVERRIDE)); } } - else if (ID_IS_OVERRIDE_LIBRARY(id)) { - but = uiDefIconBut( - block, - UI_BTYPE_BUT, - 0, - ICON_LIBRARY_DATA_OVERRIDE, - 0, - 0, - UI_UNIT_X, - UI_UNIT_Y, - nullptr, - 0, - 0, - 0, - 0, - TIP_("Library override of linked data-block, click to make fully local, " - "Shift + Click to clear the library override and toggle if it can be edited")); - UI_but_funcN_set( - but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_OVERRIDE)); - } if ((ID_REAL_USERS(id) > 1) && (hide_buttons == false)) { char numstr[32]; -- 2.30.2 From e2715a35fa489e590f0c820b4ad17cec48d44824 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 26 Jan 2024 17:14:19 +0100 Subject: [PATCH 5/6] Naming and UI organization changes for brush asset operators This is quite heavily WIP. --- .../startup/bl_ui/properties_paint_common.py | 48 ++++++-- scripts/startup/bl_ui/space_view3d.py | 18 --- scripts/startup/bl_ui/space_view3d_toolbar.py | 35 +++--- .../blender/editors/sculpt_paint/paint_ops.cc | 103 +++++++++++------- 4 files changed, 120 insertions(+), 84 deletions(-) diff --git a/scripts/startup/bl_ui/properties_paint_common.py b/scripts/startup/bl_ui/properties_paint_common.py index 077fa936cfa..ab8394b9a66 100644 --- a/scripts/startup/bl_ui/properties_paint_common.py +++ b/scripts/startup/bl_ui/properties_paint_common.py @@ -147,19 +147,49 @@ class BrushSelectPanel(BrushPanel): brush = settings.brush row = layout.row() - large_preview = True - if large_preview: - row.column().template_ID_preview(settings, "brush", new="brush.add", rows=3, cols=8, hide_buttons=False) - else: - row.column().template_ID(settings, "brush", new="brush.add") + # TODO: hide buttons since they are confusing with menu entries. + # But some of this functionality may still be needed. + row.column().template_ID_preview(settings, "brush", new="brush.add", rows=3, cols=8, hide_buttons=True) + + if brush is None: + return + col = row.column() col.menu("VIEW3D_MT_brush_context_menu", icon='DOWNARROW_HLT', text="") - if brush is not None: - col.prop(brush, "use_custom_icon", toggle=True, icon='FILE_IMAGE', text="") + panel = layout.panel("customize", text="Customize", default_closed=True) + if panel: + panel.use_property_split = True + panel.use_property_decorate = False - if brush.use_custom_icon: - layout.prop(brush, "icon_filepath", text="") + # icon + col = panel.column(heading="Custom Icon", align=True) + row = col.row() + row.prop(brush, "use_custom_icon", text="") + sub = row.row() + sub.active = brush.use_custom_icon + sub.prop(brush, "icon_filepath", text="") + + # brush tool + if context.image_paint_object: + panel.prop(brush, "image_tool") + elif context.vertex_paint_object: + panel.prop(brush, "vertex_tool") + elif context.weight_paint_object: + panel.prop(brush, "weight_tool") + elif context.sculpt_object: + panel.prop(brush, "sculpt_tool") + elif context.tool_settings.curves_sculpt: + panel.prop(brush, "curves_sculpt_tool") + + # brush paint modes + col = panel.column(heading="Modes", align=True) + col.prop(brush, "use_paint_sculpt", text="Sculpt") + col.prop(brush, "use_paint_uv_sculpt", text="UV Sculpt") + col.prop(brush, "use_paint_vertex", text="Vertex Paint") + col.prop(brush, "use_paint_weight", text="Weight Paint") + col.prop(brush, "use_paint_image", text="Texture Paint") + col.prop(brush, "use_paint_sculpt_curves", text="Sculpt Curves") class ColorPalettePanel(BrushPanel): diff --git a/scripts/startup/bl_ui/space_view3d.py b/scripts/startup/bl_ui/space_view3d.py index 3fa8ea2c48f..8a72834a5ec 100644 --- a/scripts/startup/bl_ui/space_view3d.py +++ b/scripts/startup/bl_ui/space_view3d.py @@ -3331,23 +3331,6 @@ class VIEW3D_MT_make_links(Menu): layout.operator("object.datalayout_transfer") -class VIEW3D_MT_brush_paint_modes(Menu): - bl_label = "Enabled Modes" - - def draw(self, context): - layout = self.layout - - settings = UnifiedPaintPanel.paint_settings(context) - brush = settings.brush - - layout.prop(brush, "use_paint_sculpt", text="Sculpt") - layout.prop(brush, "use_paint_uv_sculpt", text="UV Sculpt") - layout.prop(brush, "use_paint_vertex", text="Vertex Paint") - layout.prop(brush, "use_paint_weight", text="Weight Paint") - layout.prop(brush, "use_paint_image", text="Texture Paint") - layout.prop(brush, "use_paint_sculpt_curves", text="Sculpt Curves") - - class VIEW3D_MT_paint_vertex(Menu): bl_label = "Paint" @@ -8900,7 +8883,6 @@ classes = ( VIEW3D_MT_object_cleanup, VIEW3D_MT_make_single_user, VIEW3D_MT_make_links, - VIEW3D_MT_brush_paint_modes, VIEW3D_MT_paint_vertex, VIEW3D_MT_hook, VIEW3D_MT_vertex_group, diff --git a/scripts/startup/bl_ui/space_view3d_toolbar.py b/scripts/startup/bl_ui/space_view3d_toolbar.py index 2ad363a2bc8..146aeeff573 100644 --- a/scripts/startup/bl_ui/space_view3d_toolbar.py +++ b/scripts/startup/bl_ui/space_view3d_toolbar.py @@ -43,29 +43,28 @@ class VIEW3D_MT_brush_context_menu(Menu): # skip if no active brush if not brush: - layout.label(text="No Brushes currently available", icon='INFO') + layout.label(text="No brush selected", icon='INFO') return - # brush paint modes - layout.menu("VIEW3D_MT_brush_paint_modes") + # TODO: asset library brushes don't seem to have asset data? + if True or brush.asset_data: + layout.operator("brush.asset_save_as", text="Duplicate Asset...", icon='DUPLICATE') + layout.operator("brush.asset_delete", text="Delete Asset") + else: + layout.operator("brush.asset_save_as", text="Save As Asset...", icon='FILE_TICK') - # brush tool + # TODO: only show this when brush is not local. + # TODO: asset library brushes don't seem to have asset data? + if True or brush.asset_data: + layout.separator() - if context.image_paint_object: - layout.prop_menu_enum(brush, "image_tool") - elif context.vertex_paint_object: - layout.prop_menu_enum(brush, "vertex_tool") - elif context.weight_paint_object: - layout.prop_menu_enum(brush, "weight_tool") - elif context.sculpt_object: - layout.prop_menu_enum(brush, "sculpt_tool") - layout.operator("brush.reset") - elif context.tool_settings.curves_sculpt: - layout.prop_menu_enum(brush, "curves_sculpt_tool") + layout.operator("brush.asset_update", text="Update Asset") + layout.operator("brush.asset_revert", text="Revert to Asset") - layout.operator("brush.asset_save_as") - layout.operator("brush.asset_save") - layout.operator("brush.asset_delete") + # TODO: is this functionality useful at all, better to create a new brush? + # Difference with Revert is not obvious. + if context.sculpt_object: + layout.operator("brush.reset", text="Reset to Defaults") class VIEW3D_MT_brush_gpencil_context_menu(Menu): diff --git a/source/blender/editors/sculpt_paint/paint_ops.cc b/source/blender/editors/sculpt_paint/paint_ops.cc index 06f5fe57706..16255927c87 100644 --- a/source/blender/editors/sculpt_paint/paint_ops.cc +++ b/source/blender/editors/sculpt_paint/paint_ops.cc @@ -1304,6 +1304,7 @@ static int brush_asset_save_as_invoke(bContext *C, wmOperator *op, const wmEvent RNA_string_set(op->ptr, "brush_name", brush->id.name + 2); + /* TODO: add information about the asset library this will be saved to? */ return WM_operator_props_dialog_popup(C, op, 400); } @@ -1341,6 +1342,7 @@ static void BRUSH_OT_asset_save_as(wmOperatorType *ot) "Name used to save the brush asset"); } +/* TODO: this looks expensive for a poll function? */ static bool brush_asset_is_editable(const AssetWeakReference &brush_weak_ref) { /* Fairly simple checks, based on filepath only: @@ -1367,34 +1369,6 @@ static bool brush_asset_is_editable(const AssetWeakReference &brush_weak_ref) return true; } -static int brush_asset_save_exec(bContext *C, wmOperator *op) -{ - Paint *paint = BKE_paint_get_active_from_context(C); - Brush *brush = nullptr; - const AssetWeakReference *brush_weak_ref = - BKE_paint_brush_asset_get(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) { Paint *paint = BKE_paint_get_active_from_context(C); @@ -1413,16 +1387,6 @@ static bool brush_asset_library_editable_poll(bContext *C) return brush_asset_is_editable(*brush_weak_ref); } -static void BRUSH_OT_asset_save(wmOperatorType *ot) -{ - ot->name = "Save Brush Asset"; - ot->description = "Re-save the active brush asset in the default asset library"; - ot->idname = "BRUSH_OT_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*/) { Paint *paint = BKE_paint_get_active_from_context(C); @@ -1467,6 +1431,66 @@ static void BRUSH_OT_asset_delete(wmOperatorType *ot) ot->poll = brush_asset_library_editable_poll; } +static int brush_asset_update_exec(bContext *C, wmOperator *op) +{ + Paint *paint = BKE_paint_get_active_from_context(C); + Brush *brush = nullptr; + const AssetWeakReference *brush_weak_ref = + BKE_paint_brush_asset_get(paint, &brush).value_or(nullptr); + + 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 void BRUSH_OT_asset_update(wmOperatorType *ot) +{ + ot->name = "Update Brush Asset"; + ot->description = "Update the active brush asset in the asset library with current settings"; + ot->idname = "BRUSH_OT_asset_update"; + + ot->exec = brush_asset_update_exec; + ot->poll = brush_asset_library_editable_poll; +} + +static int brush_asset_revert_exec(bContext *C, wmOperator *op) +{ + Paint *paint = BKE_paint_get_active_from_context(C); + Brush *brush = nullptr; + const AssetWeakReference *brush_weak_ref = + BKE_paint_brush_asset_get(paint, &brush).value_or(nullptr); + + /* TODO */ + UNUSED_VARS(brush_weak_ref); + BKE_report(op->reports, RPT_ERROR, "Not implemented yet"); + + return OPERATOR_FINISHED; +} + +static void BRUSH_OT_asset_revert(wmOperatorType *ot) +{ + ot->name = "Revert Brush Asset"; + ot->description = + "Revert the active brush settings to the default values from the asset library"; + ot->idname = "BRUSH_OT_asset_revert"; + + ot->exec = brush_asset_revert_exec; + ot->poll = brush_asset_library_editable_poll; +} + /***** Stencil Control *****/ enum StencilControlMode { @@ -1930,8 +1954,9 @@ 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_save); WM_operatortype_append(BRUSH_OT_asset_delete); + WM_operatortype_append(BRUSH_OT_asset_update); + WM_operatortype_append(BRUSH_OT_asset_revert); /* NOTE: particle uses a different system, can be added with existing operators in `wm.py`. */ WM_operatortype_append(PAINT_OT_brush_select); -- 2.30.2 From 631a2f89e4740106c20f358287bcd0f9c04bb5e2 Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Sat, 27 Jan 2024 16:38:18 +0100 Subject: [PATCH 6/6] Brush Assets: various improvements to operators * Delete Asset now also works on local brushes * Save Asset As automatically adds asset metadata if not present * Fix Save Asset As failing without existing asset directory * Fix missing refresh of asset library on save and delete * Revert to Asset actually implemented * Improve poll functions and graying out * Better error reporting --- scripts/startup/bl_ui/space_view3d_toolbar.py | 20 +- source/blender/editors/asset/ED_asset_list.h | 3 +- .../editors/asset/intern/asset_list.cc | 7 +- .../blender/editors/sculpt_paint/paint_ops.cc | 317 ++++++++++-------- 4 files changed, 202 insertions(+), 145 deletions(-) diff --git a/scripts/startup/bl_ui/space_view3d_toolbar.py b/scripts/startup/bl_ui/space_view3d_toolbar.py index 146aeeff573..a42d1cb6b85 100644 --- a/scripts/startup/bl_ui/space_view3d_toolbar.py +++ b/scripts/startup/bl_ui/space_view3d_toolbar.py @@ -46,25 +46,27 @@ class VIEW3D_MT_brush_context_menu(Menu): layout.label(text="No brush selected", icon='INFO') return - # TODO: asset library brushes don't seem to have asset data? - if True or brush.asset_data: + + # TODO: Need actual check if this is an asset from library. + # TODO: why is brush.asset_data None for these? + is_linked = brush.library + is_override = brush.override_library and brush.override_library.reference + is_asset_brush = is_linked or is_override + + if is_asset_brush: layout.operator("brush.asset_save_as", text="Duplicate Asset...", icon='DUPLICATE') layout.operator("brush.asset_delete", text="Delete Asset") - else: - layout.operator("brush.asset_save_as", text="Save As Asset...", icon='FILE_TICK') - # TODO: only show this when brush is not local. - # TODO: asset library brushes don't seem to have asset data? - if True or brush.asset_data: layout.separator() layout.operator("brush.asset_update", text="Update Asset") layout.operator("brush.asset_revert", text="Revert to Asset") - # TODO: is this functionality useful at all, better to create a new brush? - # Difference with Revert is not obvious. if context.sculpt_object: layout.operator("brush.reset", text="Reset to Defaults") + else: + layout.operator("brush.asset_save_as", text="Save As Asset...", icon='FILE_TICK') + layout.operator("brush.asset_delete", text="Delete") class VIEW3D_MT_brush_gpencil_context_menu(Menu): diff --git a/source/blender/editors/asset/ED_asset_list.h b/source/blender/editors/asset/ED_asset_list.h index f00cf2a3ddb..1fe214d92d8 100644 --- a/source/blender/editors/asset/ED_asset_list.h +++ b/source/blender/editors/asset/ED_asset_list.h @@ -38,7 +38,8 @@ void ED_assetlist_storage_fetch(const struct AssetLibraryReference *library_refe bool ED_assetlist_is_loaded(const struct AssetLibraryReference *library_reference); void ED_assetlist_ensure_previews_job(const struct AssetLibraryReference *library_reference, const struct bContext *C); -void ED_assetlist_clear(const struct AssetLibraryReference *library_reference, struct bContext *C); +void ED_assetlist_clear(const struct AssetLibraryReference *library_reference, + const struct bContext *C); bool ED_assetlist_storage_has_list_for_library(const AssetLibraryReference *library_reference); /** * Tag all asset lists in the storage that show main data as needing an update (re-fetch). diff --git a/source/blender/editors/asset/intern/asset_list.cc b/source/blender/editors/asset/intern/asset_list.cc index 859f83ebf57..561e70d2a44 100644 --- a/source/blender/editors/asset/intern/asset_list.cc +++ b/source/blender/editors/asset/intern/asset_list.cc @@ -117,7 +117,7 @@ class AssetList : NonCopyable { void setup(); void fetch(const bContext &C); void ensurePreviewsJob(const bContext *C); - void clear(bContext *C); + void clear(const bContext *C); AssetHandle asset_get_by_index(int index) const; @@ -260,7 +260,7 @@ void AssetList::ensurePreviewsJob(const bContext *C) } } -void AssetList::clear(bContext *C) +void AssetList::clear(const bContext *C) { /* Based on #ED_fileselect_clear() */ @@ -268,6 +268,7 @@ void AssetList::clear(bContext *C) filelist_readjob_stop(files, CTX_wm_manager(C)); filelist_freelib(files); filelist_clear(files); + filelist_tag_force_reset(files); WM_main_add_notifier(NC_ASSET | ND_ASSET_LIST, nullptr); } @@ -486,7 +487,7 @@ void ED_assetlist_ensure_previews_job(const AssetLibraryReference *library_refer } } -void ED_assetlist_clear(const AssetLibraryReference *library_reference, bContext *C) +void ED_assetlist_clear(const AssetLibraryReference *library_reference, const bContext *C) { AssetList *list = AssetListStorage::lookup_list(*library_reference); if (list) { diff --git a/source/blender/editors/sculpt_paint/paint_ops.cc b/source/blender/editors/sculpt_paint/paint_ops.cc index 46d24c88b50..46826ffa9f6 100644 --- a/source/blender/editors/sculpt_paint/paint_ops.cc +++ b/source/blender/editors/sculpt_paint/paint_ops.cc @@ -36,6 +36,7 @@ #include "BKE_context.hh" #include "BKE_image.h" #include "BKE_lib_id.hh" +#include "BKE_lib_override.hh" #include "BKE_main.hh" #include "BKE_paint.hh" #include "BKE_preferences.h" @@ -43,6 +44,7 @@ #include "ED_asset_handle.h" #include "ED_asset_list.h" +#include "ED_asset_mark_clear.h" #include "ED_image.hh" #include "ED_paint.hh" #include "ED_screen.hh" @@ -986,9 +988,13 @@ static void PAINT_OT_brush_select(wmOperatorType *ot) /**************************** Brush Assets **********************************/ -static bool brush_asset_poll(bContext *C) +static bool brush_asset_select_poll(bContext *C) { - return BKE_paint_get_active_from_context(C) != nullptr; + if (BKE_paint_get_active_from_context(C) == nullptr) { + return false; + } + + return CTX_wm_asset(C) != nullptr; } static int brush_asset_select_exec(bContext *C, wmOperator *op) @@ -997,9 +1003,6 @@ static int brush_asset_select_exec(bContext *C, wmOperator *op) * used for the asset-view template. Once the asset list design is used by the Asset Browser, * this can be simplified to just that case. */ blender::asset_system::AssetRepresentation *asset = CTX_wm_asset(C); - if (!asset) { - return OPERATOR_CANCELLED; - } AssetWeakReference *brush_asset_reference = asset->make_weak_reference(); Brush *brush = BKE_brush_asset_runtime_ensure(CTX_data_main(C), brush_asset_reference); @@ -1025,7 +1028,7 @@ static void BRUSH_OT_asset_select(wmOperatorType *ot) ot->idname = "BRUSH_OT_asset_select"; ot->exec = brush_asset_select_exec; - ot->poll = brush_asset_poll; + ot->poll = brush_asset_select_poll; ot->prop = RNA_def_string( ot->srna, "name", nullptr, MAX_NAME, "Brush Name", "Name of the brush asset to select"); @@ -1052,29 +1055,40 @@ static AssetWeakReference *brush_asset_create_weakref_hack(const bUserAssetLibra static const bUserAssetLibrary *brush_asset_get_editable_library() { - for (const AssetLibraryReference &lib_ref : - blender::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; - } + /* TODO: take into account which one is marked as default. */ + LISTBASE_FOREACH (const bUserAssetLibrary *, asset_library, &U.asset_libraries) { + return asset_library; } return nullptr; } +static void brush_asset_refresh_editable_library(const bContext *C) +{ + const bUserAssetLibrary *user_library = brush_asset_get_editable_library(); + + /* TODO: Should the all library reference be automatically cleared? */ + AssetLibraryReference all_lib_ref = blender::asset_system::all_library_reference(); + ED_assetlist_clear(&all_lib_ref, C); + + /* TODO: this is convoluted, can we create a reference from pointer? */ + for (const AssetLibraryReference &lib_ref : + blender::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) { + ED_assetlist_clear(&lib_ref, C); + return; + } + } + } +} + 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) { + if (user_library == nullptr || user_library->dirpath[0] == '\0') { return ""; } @@ -1082,23 +1096,21 @@ static std::string brush_asset_root_path_for_save() 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"; + return std::string(libpath) + SEP + "Saved" + SEP + "Brushes"; } -static std::string brush_asset_blendfile_path_for_save(const blender::StringRefNull &base_name) +static std::string brush_asset_blendfile_path_for_save(ReportList *reports, + const blender::StringRefNull &base_name) { std::string root_path = brush_asset_root_path_for_save(); - if (root_path.empty()) { + 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 ""; } - 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); @@ -1185,87 +1197,97 @@ static bool brush_asset_write_in_library(Main *bmain, return sucess; } +static bool brush_asset_save_as_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; + } + + const bUserAssetLibrary *user_library = brush_asset_get_editable_library(); + if (user_library == nullptr || user_library->dirpath[0] == '\0') { + CTX_wm_operator_poll_msg_set(C, "No default asset library available to save to"); + return false; + } + + return true; +} + 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)) { + Paint *paint = BKE_paint_get_active_from_context(C); + Brush *brush = (paint) ? BKE_paint_brush(paint) : nullptr; + if (paint == nullptr || brush == nullptr) { 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); + /* Determine file path to save to. */ + PropertyRNA *name_prop = RNA_struct_find_property(op->ptr, "name"); + char name[MAX_NAME] = ""; + if (RNA_property_is_set(op->ptr, name_prop)) { + RNA_property_string_get(op->ptr, name_prop, name); + } + if (name[0] == '\0') { + STRNCPY(name, brush->id.name + 2); + } + const std::string filepath = brush_asset_blendfile_path_for_save(op->reports, name); if (filepath.empty()) { return OPERATOR_CANCELLED; } - Paint *paint = BKE_paint_get_active_from_context(C); - Brush *brush = nullptr; - if (!BKE_paint_brush_asset_get(paint, &brush).has_value()) { - return OPERATOR_CANCELLED; + /* Turn brush into asset if it isn't yet. */ + if (!BKE_paint_brush_is_valid_asset(brush)) { + ED_asset_mark_id(&brush->id); + ED_asset_generate_preview(C, &brush->id); } - BLI_assert(BKE_paint_brush_is_valid_asset(brush)); + /* Save to asset library. */ 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(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; + if (!sucess) { + BKE_report(op->reports, RPT_ERROR, "Failed to write to asset library"); + return OPERATOR_CANCELLED; } - return OPERATOR_CANCELLED; + /* Create weak reference to new datablock. */ + 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); + + /* TODO: maybe not needed, even less so if there is more visual confirmation of change. */ + BKE_reportf(op->reports, RPT_INFO, "Saved \"%s\"", filepath.c_str()); + + Main *bmain = CTX_data_main(C); + brush = BKE_brush_asset_runtime_ensure(bmain, 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. */ + BKE_report(op->reports, RPT_WARNING, "Unable to activate just-saved brush asset"); + } + + brush_asset_refresh_editable_library(C); + WM_main_add_notifier(NC_ASSET | ND_ASSET_LIST | NA_ADDED, nullptr); + + return OPERATOR_FINISHED; } 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 = nullptr; - if (!BKE_paint_brush_asset_get(paint, &brush).has_value()) { - return OPERATOR_CANCELLED; - } + Brush *brush = BKE_paint_brush(paint); - RNA_string_set(op->ptr, "brush_name", brush->id.name + 2); + RNA_string_set(op->ptr, "name", brush->id.name + 2); /* TODO: add information about the asset library this will be saved to? */ + /* TODO: autofocus name? */ return WM_operator_props_dialog_popup(C, op, 400); } -static bool brush_asset_save_as_poll(bContext *C) -{ - Paint *paint = BKE_paint_get_active_from_context(C); - if (paint == nullptr) { - return false; - } - - Brush *brush = nullptr; - if (BKE_paint_brush_asset_get(paint, &brush).has_value()) { - return brush != nullptr; - } - return false; -} - static void BRUSH_OT_asset_save_as(wmOperatorType *ot) { ot->name = "Save As Brush Asset"; @@ -1278,15 +1300,9 @@ static void BRUSH_OT_asset_save_as(wmOperatorType *ot) 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"); + RNA_def_string(ot->srna, "name", nullptr, MAX_NAME, "Name", "Name used to save the brush asset"); } -/* TODO: this looks expensive for a poll function? */ static bool brush_asset_is_editable(const AssetWeakReference &brush_weak_ref) { /* Fairly simple checks, based on filepath only: @@ -1313,51 +1329,57 @@ static bool brush_asset_is_editable(const AssetWeakReference &brush_weak_ref) return true; } -static bool brush_asset_library_editable_poll(bContext *C) +static bool brush_asset_delete_poll(bContext *C) { Paint *paint = BKE_paint_get_active_from_context(C); - if (paint == nullptr) { + Brush *brush = (paint) ? BKE_paint_brush(paint) : nullptr; + if (paint == nullptr || brush == nullptr) { return false; } - Brush *brush = nullptr; - const AssetWeakReference *brush_weak_ref = - BKE_paint_brush_asset_get(paint, &brush).value_or(nullptr); - - if (!brush_weak_ref || !brush) { - return false; + /* Asset brush, check if belongs to an editable blend file. */ + if (paint->brush_asset_reference && BKE_paint_brush_is_valid_asset(brush)) { + if (!brush_asset_is_editable(*paint->brush_asset_reference)) { + CTX_wm_operator_poll_msg_set(C, "Asset blend file is not editable"); + return false; + } } - return brush_asset_is_editable(*brush_weak_ref); + return true; } -static int brush_asset_delete_exec(bContext *C, wmOperator * /*op*/) +static int brush_asset_delete_exec(bContext *C, wmOperator *op) { + Main *bmain = CTX_data_main(C); Paint *paint = BKE_paint_get_active_from_context(C); - Brush *brush = nullptr; - const AssetWeakReference *brush_weak_ref = - BKE_paint_brush_asset_get(paint, &brush).value_or(nullptr); - if (!brush_weak_ref || !brush || !brush_asset_is_editable(*brush_weak_ref)) { - return OPERATOR_CANCELLED; + Brush *brush = BKE_paint_brush(paint); + + if (paint->brush_asset_reference && BKE_paint_brush_is_valid_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"); + } } - BLI_assert(BKE_paint_brush_is_valid_asset(brush)); + /* Delete from session. If local override, also delete linked one. + * TODO: delete both in one step? */ + ID *original_brush = (!ID_IS_LINKED(&brush->id) && ID_IS_OVERRIDE_LIBRARY_REAL(&brush->id)) ? + brush->id.override_library->reference : + nullptr; + BKE_id_delete(bmain, brush); + if (original_brush) { + BKE_id_delete(bmain, original_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); + brush_asset_refresh_editable_library(C); + WM_main_add_notifier(NC_ASSET | ND_ASSET_LIST | NA_REMOVED, 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? */ + /* TODO: activate default brush. */ return OPERATOR_FINISHED; } @@ -1365,14 +1387,32 @@ static int brush_asset_delete_exec(bContext *C, wmOperator * /*op*/) static void BRUSH_OT_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->description = "Delete the active brush asset both from the local session and asset library"; ot->idname = "BRUSH_OT_asset_delete"; ot->exec = brush_asset_delete_exec; ot->invoke = WM_operator_confirm; - ot->poll = brush_asset_library_editable_poll; + ot->poll = brush_asset_delete_poll; +} + +static bool brush_asset_update_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 (!(paint->brush_asset_reference && BKE_paint_brush_is_valid_asset(brush))) { + return false; + } + + if (!brush_asset_is_editable(*paint->brush_asset_reference)) { + CTX_wm_operator_poll_msg_set(C, "Asset blend file is not editable"); + return false; + } + + return true; } static int brush_asset_update_exec(bContext *C, wmOperator *op) @@ -1407,19 +1447,32 @@ static void BRUSH_OT_asset_update(wmOperatorType *ot) ot->idname = "BRUSH_OT_asset_update"; ot->exec = brush_asset_update_exec; - ot->poll = brush_asset_library_editable_poll; + ot->poll = brush_asset_update_poll; } -static int brush_asset_revert_exec(bContext *C, wmOperator *op) +static bool brush_asset_revert_poll(bContext *C) { + /* TODO: check if there is anything to revert? */ Paint *paint = BKE_paint_get_active_from_context(C); - Brush *brush = nullptr; - const AssetWeakReference *brush_weak_ref = - BKE_paint_brush_asset_get(paint, &brush).value_or(nullptr); + Brush *brush = (paint) ? BKE_paint_brush(paint) : nullptr; + if (paint == nullptr || brush == nullptr) { + return false; + } - /* TODO */ - UNUSED_VARS(brush_weak_ref); - BKE_report(op->reports, RPT_ERROR, "Not implemented yet"); + return paint->brush_asset_reference && BKE_paint_brush_is_valid_asset(brush); +} + +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); + + /* TODO: check if doing this for the hierarchy is ok. */ + /* TODO: the overrides don't update immediately when tweaking brush settings. */ + BKE_lib_override_library_id_hierarchy_reset(bmain, &brush->id, false); + + WM_main_add_notifier(NC_BRUSH | NA_EDITED, brush); return OPERATOR_FINISHED; } @@ -1432,7 +1485,7 @@ static void BRUSH_OT_asset_revert(wmOperatorType *ot) ot->idname = "BRUSH_OT_asset_revert"; ot->exec = brush_asset_revert_exec; - ot->poll = brush_asset_library_editable_poll; + ot->poll = brush_asset_revert_poll; } /***** Stencil Control *****/ -- 2.30.2