Brush Assets: Save As, Delete, Revert and Update operators #117513
|
@ -147,19 +147,50 @@ class BrushSelectPanel(BrushPanel):
|
||||||
brush = settings.brush
|
brush = settings.brush
|
||||||
|
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
large_preview = True
|
# TODO: hide buttons since they are confusing with menu entries.
|
||||||
if large_preview:
|
# 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=False)
|
row.column().template_ID_preview(settings, "brush", new="brush.add", rows=3, cols=8, hide_buttons=True)
|
||||||
else:
|
|
||||||
row.column().template_ID(settings, "brush", new="brush.add")
|
if brush is None:
|
||||||
|
return
|
||||||
|
|
||||||
col = row.column()
|
col = row.column()
|
||||||
col.menu("VIEW3D_MT_brush_context_menu", icon='DOWNARROW_HLT', text="")
|
col.menu("VIEW3D_MT_brush_context_menu", icon='DOWNARROW_HLT', text="")
|
||||||
|
|
||||||
if brush is not None:
|
header, panel = layout.panel("customize", default_closed=True)
|
||||||
col.prop(brush, "use_custom_icon", toggle=True, icon='FILE_IMAGE', text="")
|
header.label(text="Customize")
|
||||||
|
if panel:
|
||||||
|
panel.use_property_split = True
|
||||||
|
panel.use_property_decorate = False
|
||||||
|
|
||||||
if brush.use_custom_icon:
|
# icon
|
||||||
layout.prop(brush, "icon_filepath", text="")
|
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):
|
class ColorPalettePanel(BrushPanel):
|
||||||
|
|
|
@ -3331,23 +3331,6 @@ class VIEW3D_MT_make_links(Menu):
|
||||||
layout.operator("object.datalayout_transfer")
|
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):
|
class VIEW3D_MT_paint_vertex(Menu):
|
||||||
bl_label = "Paint"
|
bl_label = "Paint"
|
||||||
|
|
||||||
|
@ -8900,7 +8883,6 @@ classes = (
|
||||||
VIEW3D_MT_object_cleanup,
|
VIEW3D_MT_object_cleanup,
|
||||||
VIEW3D_MT_make_single_user,
|
VIEW3D_MT_make_single_user,
|
||||||
VIEW3D_MT_make_links,
|
VIEW3D_MT_make_links,
|
||||||
VIEW3D_MT_brush_paint_modes,
|
|
||||||
VIEW3D_MT_paint_vertex,
|
VIEW3D_MT_paint_vertex,
|
||||||
VIEW3D_MT_hook,
|
VIEW3D_MT_hook,
|
||||||
VIEW3D_MT_vertex_group,
|
VIEW3D_MT_vertex_group,
|
||||||
|
|
|
@ -43,25 +43,30 @@ class VIEW3D_MT_brush_context_menu(Menu):
|
||||||
|
|
||||||
# skip if no active brush
|
# skip if no active brush
|
||||||
if not brush:
|
if not brush:
|
||||||
layout.label(text="No Brushes currently available", icon='INFO')
|
layout.label(text="No brush selected", icon='INFO')
|
||||||
return
|
return
|
||||||
|
|
||||||
# brush paint modes
|
|
||||||
layout.menu("VIEW3D_MT_brush_paint_modes")
|
|
||||||
|
|
||||||
# brush tool
|
# 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 context.image_paint_object:
|
if is_asset_brush:
|
||||||
layout.prop_menu_enum(brush, "image_tool")
|
layout.operator("brush.asset_save_as", text="Duplicate Asset...", icon='DUPLICATE')
|
||||||
elif context.vertex_paint_object:
|
layout.operator("brush.asset_delete", text="Delete Asset")
|
||||||
layout.prop_menu_enum(brush, "vertex_tool")
|
|
||||||
elif context.weight_paint_object:
|
layout.separator()
|
||||||
layout.prop_menu_enum(brush, "weight_tool")
|
|
||||||
elif context.sculpt_object:
|
layout.operator("brush.asset_update", text="Update Asset")
|
||||||
layout.prop_menu_enum(brush, "sculpt_tool")
|
layout.operator("brush.asset_revert", text="Revert to Asset")
|
||||||
layout.operator("brush.reset")
|
|
||||||
elif context.tool_settings.curves_sculpt:
|
if context.sculpt_object:
|
||||||
layout.prop_menu_enum(brush, "curves_sculpt_tool")
|
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):
|
class VIEW3D_MT_brush_gpencil_context_menu(Menu):
|
||||||
|
|
|
@ -107,6 +107,9 @@ class AssetLibrary {
|
||||||
*/
|
*/
|
||||||
static void foreach_loaded(FunctionRef<void(AssetLibrary &)> fn, bool include_all_library);
|
static void foreach_loaded(FunctionRef<void(AssetLibrary &)> fn, bool include_all_library);
|
||||||
|
|
||||||
|
static std::string resolve_asset_weak_reference_to_full_path(
|
||||||
|
const AssetWeakReference &asset_reference);
|
||||||
|
|
||||||
void load_catalogs();
|
void load_catalogs();
|
||||||
|
|
||||||
/** Load catalogs that have changed on disk. */
|
/** Load catalogs that have changed on disk. */
|
||||||
|
@ -166,8 +169,6 @@ class AssetLibrary {
|
||||||
*/
|
*/
|
||||||
AssetIdentifier asset_identifier_from_library(StringRef relative_asset_path);
|
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;
|
eAssetLibraryType library_type() const;
|
||||||
StringRefNull name() const;
|
StringRefNull name() const;
|
||||||
StringRefNull root_path() const;
|
StringRefNull root_path() const;
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
* \ingroup bke
|
* \ingroup bke
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
#include "BLI_array.hh"
|
#include "BLI_array.hh"
|
||||||
#include "BLI_bit_vector.hh"
|
#include "BLI_bit_vector.hh"
|
||||||
#include "BLI_math_matrix_types.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);
|
const Brush *BKE_paint_brush_for_read(const Paint *p);
|
||||||
void BKE_paint_brush_set(Paint *paint, Brush *br);
|
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.
|
* 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`.
|
* \note Takes ownership of the given `weak_asset_reference`.
|
||||||
|
@ -211,6 +220,12 @@ bool BKE_paint_brush_asset_set(Paint *paint,
|
||||||
Brush *brush,
|
Brush *brush,
|
||||||
AssetWeakReference *weak_asset_reference);
|
AssetWeakReference *weak_asset_reference);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the active brush of given paint struct, together with its weak asset reference.
|
||||||
|
* \note Returns unset optional if the active brush is not a valid Brush Asset data..
|
||||||
|
*/
|
||||||
|
std::optional<AssetWeakReference *> BKE_paint_brush_asset_get(Paint *paint, Brush **r_brush);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt to restore a valid active brush in `paint` from brush asset information stored in
|
* Attempt to restore a valid active brush in `paint` from brush asset information stored in
|
||||||
* `paint`.
|
* `paint`.
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
#include "MEM_guardedalloc.h"
|
#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,
|
static void paint_brush_asset_update(Paint &paint,
|
||||||
Brush *brush,
|
Brush *brush,
|
||||||
AssetWeakReference *brush_asset_reference)
|
AssetWeakReference *brush_asset_reference)
|
||||||
|
@ -707,6 +715,21 @@ bool BKE_paint_brush_asset_set(Paint *paint,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<AssetWeakReference *> BKE_paint_brush_asset_get(Paint *paint, Brush **r_brush)
|
||||||
|
{
|
||||||
|
Brush *brush = *r_brush = BKE_paint_brush(paint);
|
||||||
|
|
||||||
|
if (!BKE_paint_brush_is_valid_asset(brush)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paint->brush_asset_reference) {
|
||||||
|
return paint->brush_asset_reference;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
void BKE_paint_brush_asset_restore(Main *bmain, Paint *paint)
|
void BKE_paint_brush_asset_restore(Main *bmain, Paint *paint)
|
||||||
{
|
{
|
||||||
if (paint->brush != nullptr) {
|
if (paint->brush != nullptr) {
|
||||||
|
|
|
@ -61,7 +61,7 @@ void iterate(const AssetLibraryReference &library_reference, AssetListIterFn fn)
|
||||||
void storage_fetch(const AssetLibraryReference *library_reference, const bContext *C);
|
void storage_fetch(const AssetLibraryReference *library_reference, const bContext *C);
|
||||||
bool is_loaded(const AssetLibraryReference *library_reference);
|
bool is_loaded(const AssetLibraryReference *library_reference);
|
||||||
void ensure_previews_job(const AssetLibraryReference *library_reference, const bContext *C);
|
void ensure_previews_job(const AssetLibraryReference *library_reference, const bContext *C);
|
||||||
void clear(const AssetLibraryReference *library_reference, bContext *C);
|
void clear(const AssetLibraryReference *library_reference, const bContext *C);
|
||||||
bool storage_has_list_for_library(const AssetLibraryReference *library_reference);
|
bool 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).
|
* Tag all asset lists in the storage that show main data as needing an update (re-fetch).
|
||||||
|
|
|
@ -116,7 +116,7 @@ class AssetList : NonCopyable {
|
||||||
void setup();
|
void setup();
|
||||||
void fetch(const bContext &C);
|
void fetch(const bContext &C);
|
||||||
void ensurePreviewsJob(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;
|
AssetHandle asset_get_by_index(int index) const;
|
||||||
|
|
||||||
|
@ -257,7 +257,7 @@ void AssetList::ensurePreviewsJob(const bContext *C)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssetList::clear(bContext *C)
|
void AssetList::clear(const bContext *C)
|
||||||
{
|
{
|
||||||
/* Based on #ED_fileselect_clear() */
|
/* Based on #ED_fileselect_clear() */
|
||||||
|
|
||||||
|
@ -265,6 +265,7 @@ void AssetList::clear(bContext *C)
|
||||||
filelist_readjob_stop(files, CTX_wm_manager(C));
|
filelist_readjob_stop(files, CTX_wm_manager(C));
|
||||||
filelist_freelib(files);
|
filelist_freelib(files);
|
||||||
filelist_clear(files);
|
filelist_clear(files);
|
||||||
|
filelist_tag_force_reset(files);
|
||||||
|
|
||||||
WM_main_add_notifier(NC_ASSET | ND_ASSET_LIST, nullptr);
|
WM_main_add_notifier(NC_ASSET | ND_ASSET_LIST, nullptr);
|
||||||
}
|
}
|
||||||
|
@ -477,7 +478,7 @@ void ensure_previews_job(const AssetLibraryReference *library_reference, const b
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void clear(const AssetLibraryReference *library_reference, bContext *C)
|
void clear(const AssetLibraryReference *library_reference, const bContext *C)
|
||||||
{
|
{
|
||||||
AssetList *list = AssetListStorage::lookup_list(*library_reference);
|
AssetList *list = AssetListStorage::lookup_list(*library_reference);
|
||||||
if (list) {
|
if (list) {
|
||||||
|
|
|
@ -1374,6 +1374,7 @@ static void template_ID(const bContext *C,
|
||||||
|
|
||||||
template_id_workspace_pin_extra_icon(template_ui, but);
|
template_id_workspace_pin_extra_icon(template_ui, but);
|
||||||
|
|
||||||
|
if (!hide_buttons) {
|
||||||
if (ID_IS_LINKED(id)) {
|
if (ID_IS_LINKED(id)) {
|
||||||
const bool disabled = !BKE_idtype_idcode_is_localizable(GS(id->name));
|
const bool disabled = !BKE_idtype_idcode_is_localizable(GS(id->name));
|
||||||
if (id->tag & LIB_TAG_INDIRECT) {
|
if (id->tag & LIB_TAG_INDIRECT) {
|
||||||
|
@ -1438,6 +1439,7 @@ static void template_ID(const bContext *C,
|
||||||
UI_but_funcN_set(
|
UI_but_funcN_set(
|
||||||
but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_OVERRIDE));
|
but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_OVERRIDE));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ((ID_REAL_USERS(id) > 1) && (hide_buttons == false)) {
|
if ((ID_REAL_USERS(id) > 1) && (hide_buttons == false)) {
|
||||||
char numstr[32];
|
char numstr[32];
|
||||||
|
|
|
@ -12,8 +12,10 @@
|
||||||
|
|
||||||
#include "MEM_guardedalloc.h"
|
#include "MEM_guardedalloc.h"
|
||||||
|
|
||||||
|
#include "BLI_fileops.h"
|
||||||
#include "BLI_listbase.h"
|
#include "BLI_listbase.h"
|
||||||
#include "BLI_math_vector.h"
|
#include "BLI_math_vector.h"
|
||||||
|
#include "BLI_path_util.h"
|
||||||
#include "BLI_string.h"
|
#include "BLI_string.h"
|
||||||
#include "BLI_utildefines.h"
|
#include "BLI_utildefines.h"
|
||||||
|
|
||||||
|
@ -26,14 +28,23 @@
|
||||||
#include "DNA_object_types.h"
|
#include "DNA_object_types.h"
|
||||||
#include "DNA_scene_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_brush.hh"
|
||||||
#include "BKE_context.hh"
|
#include "BKE_context.hh"
|
||||||
#include "BKE_image.h"
|
#include "BKE_image.h"
|
||||||
#include "BKE_lib_id.hh"
|
#include "BKE_lib_id.hh"
|
||||||
|
#include "BKE_lib_override.hh"
|
||||||
#include "BKE_main.hh"
|
#include "BKE_main.hh"
|
||||||
#include "BKE_paint.hh"
|
#include "BKE_paint.hh"
|
||||||
|
#include "BKE_preferences.h"
|
||||||
#include "BKE_report.h"
|
#include "BKE_report.h"
|
||||||
|
|
||||||
|
#include "ED_asset_handle.hh"
|
||||||
|
#include "ED_asset_list.hh"
|
||||||
|
#include "ED_asset_mark_clear.hh"
|
||||||
#include "ED_image.hh"
|
#include "ED_image.hh"
|
||||||
#include "ED_paint.hh"
|
#include "ED_paint.hh"
|
||||||
#include "ED_screen.hh"
|
#include "ED_screen.hh"
|
||||||
|
@ -45,6 +56,7 @@
|
||||||
#include "RNA_access.hh"
|
#include "RNA_access.hh"
|
||||||
#include "RNA_define.hh"
|
#include "RNA_define.hh"
|
||||||
|
|
||||||
|
#include "AS_asset_library.hh"
|
||||||
#include "AS_asset_representation.hh"
|
#include "AS_asset_representation.hh"
|
||||||
|
|
||||||
#include "curves_sculpt_intern.hh"
|
#include "curves_sculpt_intern.hh"
|
||||||
|
@ -975,9 +987,13 @@ static void PAINT_OT_brush_select(wmOperatorType *ot)
|
||||||
|
|
||||||
/**************************** Brush Assets **********************************/
|
/**************************** 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)
|
static int brush_asset_select_exec(bContext *C, wmOperator *op)
|
||||||
|
@ -986,9 +1002,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,
|
* 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. */
|
* this can be simplified to just that case. */
|
||||||
blender::asset_system::AssetRepresentation *asset = CTX_wm_asset(C);
|
blender::asset_system::AssetRepresentation *asset = CTX_wm_asset(C);
|
||||||
if (!asset) {
|
|
||||||
return OPERATOR_CANCELLED;
|
|
||||||
}
|
|
||||||
|
|
||||||
AssetWeakReference *brush_asset_reference = asset->make_weak_reference();
|
AssetWeakReference *brush_asset_reference = asset->make_weak_reference();
|
||||||
Brush *brush = BKE_brush_asset_runtime_ensure(CTX_data_main(C), brush_asset_reference);
|
Brush *brush = BKE_brush_asset_runtime_ensure(CTX_data_main(C), brush_asset_reference);
|
||||||
|
@ -1014,7 +1027,461 @@ static void BRUSH_OT_asset_select(wmOperatorType *ot)
|
||||||
ot->idname = "BRUSH_OT_asset_select";
|
ot->idname = "BRUSH_OT_asset_select";
|
||||||
|
|
||||||
ot->exec = brush_asset_select_exec;
|
ot->exec = brush_asset_select_exec;
|
||||||
ot->poll = brush_asset_poll;
|
ot->poll = brush_asset_select_poll;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* FIXME Quick dirty hack to generate a weak ref from 'raw' paths.
|
||||||
|
* This needs to be properly implemented in assetlib code.
|
||||||
|
*/
|
||||||
|
static AssetWeakReference *brush_asset_create_weakref_hack(const bUserAssetLibrary *user_asset_lib,
|
||||||
|
std::string &file_path)
|
||||||
|
{
|
||||||
|
AssetWeakReference *asset_weak_ref = MEM_new<AssetWeakReference>(__func__);
|
||||||
|
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
/* 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();
|
||||||
|
blender::ed::asset::list::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) {
|
||||||
|
blender::ed::asset::list::clear(&lib_ref, C);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string brush_asset_root_path_for_save()
|
||||||
|
{
|
||||||
|
const bUserAssetLibrary *user_library = brush_asset_get_editable_library();
|
||||||
|
if (user_library == nullptr || user_library->dirpath[0] == '\0') {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
char libpath[FILE_MAX];
|
||||||
|
BLI_strncpy(libpath, user_library->dirpath, sizeof(libpath));
|
||||||
|
BLI_path_slash_native(libpath);
|
||||||
|
BLI_path_normalize(libpath);
|
||||||
|
|
||||||
|
return std::string(libpath) + SEP + "Saved" + SEP + "Brushes";
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string brush_asset_blendfile_path_for_save(ReportList *reports,
|
||||||
|
const blender::StringRefNull &base_name)
|
||||||
|
{
|
||||||
|
std::string root_path = brush_asset_root_path_for_save();
|
||||||
|
BLI_assert(!root_path.empty());
|
||||||
|
|
||||||
|
if (!BLI_dir_create_recursive(root_path.c_str())) {
|
||||||
|
BKE_report(reports, RPT_ERROR, "Failed to create asset library directory to save brush");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
char base_name_filesafe[FILE_MAXFILE];
|
||||||
|
BLI_strncpy(base_name_filesafe, base_name.c_str(), sizeof(base_name_filesafe));
|
||||||
|
BLI_path_make_safe_filename(base_name_filesafe);
|
||||||
|
|
||||||
|
if (!BLI_is_file((root_path + SEP + base_name_filesafe + BLENDER_ASSET_FILE_SUFFIX).c_str())) {
|
||||||
|
return root_path + SEP + base_name_filesafe + BLENDER_ASSET_FILE_SUFFIX;
|
||||||
|
}
|
||||||
|
int i = 1;
|
||||||
|
while (BLI_is_file((root_path + SEP + base_name_filesafe + "_" + std::to_string(i++) +
|
||||||
|
BLENDER_ASSET_FILE_SUFFIX)
|
||||||
|
.c_str()))
|
||||||
|
;
|
||||||
|
return root_path + SEP + base_name_filesafe + "_" + std::to_string(i - 1) +
|
||||||
|
BLENDER_ASSET_FILE_SUFFIX;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool brush_asset_write_in_library(Main *bmain,
|
||||||
|
Brush *brush,
|
||||||
|
const char *name,
|
||||||
|
const 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);
|
||||||
|
|
||||||
|
/* TODO: check overwriting existing file. */
|
||||||
|
/* TODO: ensure filepath contains only valid characters for file system. */
|
||||||
|
const bool sucess = BKE_blendfile_write_partial(
|
||||||
|
bmain, filepath.c_str(), write_flags, remap_mode, reports);
|
||||||
|
|
||||||
|
if (sucess) {
|
||||||
|
final_full_file_path = std::string(filepath) + SEP + "Brush" + SEP + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
BKE_blendfile_write_partial_end(bmain);
|
||||||
|
|
||||||
|
BKE_blendfile_write_partial_tag_ID(&brush->id, false);
|
||||||
|
brush->id.flag = brush_flag;
|
||||||
|
brush->id.tag = brush_tag;
|
||||||
|
brush->id.us = brush_us;
|
||||||
|
BLI_strncpy(brush->id.name + 2, brush_name.c_str(), sizeof(brush->id.name) - 2);
|
||||||
|
brush->id.override_library = brush_liboverride;
|
||||||
|
brush->id.asset_data = brush_asset_data;
|
||||||
|
|
||||||
|
return sucess;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool brush_asset_save_as_poll(bContext *C)
|
||||||
|
{
|
||||||
|
Paint *paint = BKE_paint_get_active_from_context(C);
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Turn brush into asset if it isn't yet. */
|
||||||
|
if (!BKE_paint_brush_is_valid_asset(brush)) {
|
||||||
|
blender::ed::asset::mark_id(&brush->id);
|
||||||
|
blender::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) {
|
||||||
|
BKE_report(op->reports, RPT_ERROR, "Failed to write to asset library");
|
||||||
|
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 = BKE_paint_brush(paint);
|
||||||
|
|
||||||
|
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 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, "name", nullptr, MAX_NAME, "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 bool brush_asset_delete_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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 true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
brush_asset_refresh_editable_library(C);
|
||||||
|
WM_main_add_notifier(NC_ASSET | ND_ASSET_LIST | NA_REMOVED, nullptr);
|
||||||
|
|
||||||
|
/* TODO: activate default brush. */
|
||||||
|
|
||||||
|
return OPERATOR_FINISHED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void BRUSH_OT_asset_delete(wmOperatorType *ot)
|
||||||
|
{
|
||||||
|
ot->name = "Delete Brush Asset";
|
||||||
|
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_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)
|
||||||
|
{
|
||||||
|
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_update_poll;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = (paint) ? BKE_paint_brush(paint) : nullptr;
|
||||||
|
if (paint == nullptr || brush == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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_revert_poll;
|
||||||
}
|
}
|
||||||
|
|
||||||
/***** Stencil Control *****/
|
/***** Stencil Control *****/
|
||||||
|
@ -1479,6 +1946,10 @@ void ED_operatortypes_paint()
|
||||||
WM_operatortype_append(BRUSH_OT_stencil_fit_image_aspect);
|
WM_operatortype_append(BRUSH_OT_stencil_fit_image_aspect);
|
||||||
WM_operatortype_append(BRUSH_OT_stencil_reset_transform);
|
WM_operatortype_append(BRUSH_OT_stencil_reset_transform);
|
||||||
WM_operatortype_append(BRUSH_OT_asset_select);
|
WM_operatortype_append(BRUSH_OT_asset_select);
|
||||||
|
WM_operatortype_append(BRUSH_OT_asset_save_as);
|
||||||
|
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`. */
|
/* NOTE: particle uses a different system, can be added with existing operators in `wm.py`. */
|
||||||
WM_operatortype_append(PAINT_OT_brush_select);
|
WM_operatortype_append(PAINT_OT_brush_select);
|
||||||
|
|
Loading…
Reference in New Issue