WIP: Brush assets project #106303

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

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
27 changed files with 821 additions and 75 deletions
Showing only changes of commit 0c95e0e627 - Show all commits

View File

@ -6675,6 +6675,21 @@ def km_node_link_modal_map(_params):
return keymap
def km_asset_shelf_brushes(_params):
items = []
keymap = (
"Asset Shelf",
{"space_type": 'EMPTY', "region_type": 'WINDOW'},
{"items": items},
)
items.extend([
("sculpt_curves.brush_asset_select", {"type": 'LEFTMOUSE', "value": 'CLICK'}, None),
])
return keymap
# Fallback for gizmos that don't have custom a custom key-map.
def km_generic_gizmo(_params):
keymap = (
@ -8470,6 +8485,9 @@ def generate_keymaps(params=None):
km_curve_pen_modal_map(params),
km_node_link_modal_map(params),
# Asset Shelf Keymaps.
km_asset_shelf_brushes(params),
# Gizmos.
km_generic_gizmo(params),
km_generic_gizmo_drag(params),

View File

@ -987,6 +987,18 @@ class VIEW3D_HT_header(Header):
sub.popover(panel="VIEW3D_PT_shading", text="")
class VIEW3D_AST_brush_asset_shelf(bpy.types.AssetShelf):
bl_space_type = "VIEW_3D"
@classmethod
def poll(cls, context):
return context.object and context.object.mode == 'SCULPT_CURVES'
@classmethod
def asset_poll(cls, asset):
return asset.file_data.id_type == 'BRUSH'
class VIEW3D_MT_editor_menus(Menu):
bl_label = ""
@ -8389,6 +8401,7 @@ class VIEW3D_AST_sculpt_brushes(bpy.types.AssetShelf):
classes = (
VIEW3D_AST_brush_asset_shelf,
VIEW3D_HT_header,
VIEW3D_HT_tool_header,
VIEW3D_MT_editor_menus,

View File

@ -31,6 +31,9 @@ struct UnifiedPaintSettings;
void BKE_brush_system_init();
void BKE_brush_system_exit();
Brush *BKE_brush_asset_runtime_ensure(Main *bmain,
const AssetWeakReference *brush_asset_reference);
/* Data-block functions. */
/**

View File

@ -82,6 +82,17 @@ bool BKE_lib_override_library_is_user_edited(const ID *id);
*/
bool BKE_lib_override_library_is_system_defined(const Main *bmain, const ID *id);
/**
* Count the amount of liboverride IDs of given `id_type`, using a refererence linked ID from given
* `library`, that are user-edited.
*
* \param r_reports If not NULL, add one report for each relevant ID.
*/
int BKE_lib_override_user_edited_from_library_count(struct Main *bmain,
const short id_type,
struct Library *library,
struct ReportList *r_reports);
/**
* Check if given Override Property for given ID is animated (through a F-Curve in an Action, or
* from a driver).

View File

@ -25,6 +25,7 @@
#include "bmesh.h"
struct AssetWeakReference;
struct BMFace;
struct BMesh;
struct BlendDataReader;
@ -164,7 +165,7 @@ PaintCurve *BKE_paint_curve_add(Main *bmain, const char *name);
/**
* Call when entering each respective paint mode.
*/
bool BKE_paint_ensure(ToolSettings *ts, Paint **r_paint);
bool BKE_paint_ensure(Main *bmain, ToolSettings *ts, Paint **r_paint);
void BKE_paint_init(Main *bmain, Scene *sce, ePaintMode mode, const uchar col[3]);
void BKE_paint_free(Paint *p);
/**
@ -179,7 +180,7 @@ void BKE_paint_runtime_init(const ToolSettings *ts, Paint *paint);
void BKE_paint_cavity_curve_preset(Paint *p, int preset);
eObjectMode BKE_paint_object_mode_from_paintmode(ePaintMode mode);
bool BKE_paint_ensure_from_paintmode(Scene *sce, ePaintMode mode);
bool BKE_paint_ensure_from_paintmode(Main *bmain, Scene *sce, ePaintMode mode);
Paint *BKE_paint_get_active_from_paintmode(Scene *sce, ePaintMode mode);
const EnumPropertyItem *BKE_paint_get_tool_enum_from_paintmode(ePaintMode mode);
const char *BKE_paint_get_tool_enum_translation_context_from_paintmode(ePaintMode mode);
@ -192,6 +193,14 @@ ePaintMode BKE_paintmode_get_from_tool(const bToolRef *tref);
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);
/** 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`. */
void BKE_paint_brush_asset_set(Paint *p, Brush *br, AssetWeakReference *weak_asset_reference);
/** Attempt to restore a valid active brush in `p` from brush asset informations stored in `p`. */
void BKE_paint_brush_asset_restore(Main *bmain, Paint *p);
Palette *BKE_paint_palette(Paint *paint);
void BKE_paint_palette_set(Paint *p, Palette *palette);
void BKE_paint_curve_set(Brush *br, PaintCurve *pc);
@ -866,7 +875,7 @@ int BKE_sculpt_mask_layers_ensure(Depsgraph *depsgraph,
Main *bmain,
Object *ob,
MultiresModifierData *mmd);
void BKE_sculpt_toolsettings_data_ensure(Scene *scene);
void BKE_sculpt_toolsettings_data_ensure(Main *bmain, Scene *scene);
PBVH *BKE_sculpt_object_pbvh_ensure(Depsgraph *depsgraph, Object *ob);

View File

@ -306,14 +306,195 @@ static bool reuse_bmain_data_remapper_is_id_remapped(IDRemapper *remapper, ID *i
return false;
}
static Library *reuse_bmain_data_dependencies_new_library_get(ReuseOldBMainData *reuse_data,
Library *old_lib)
{
if (old_lib == nullptr) {
return nullptr;
}
IDRemapper *remapper = reuse_data->remapper;
Library *new_lib = old_lib;
IDRemapperApplyResult result = BKE_id_remapper_apply(
remapper, reinterpret_cast<ID **>(&new_lib), ID_REMAP_APPLY_DEFAULT);
if (result == ID_REMAP_RESULT_SOURCE_UNAVAILABLE) {
/* No matching new library found. Avoid nullptr case when original data was linked, which would
* match against all local IDs. */
}
BLI_assert_msg(result == ID_REMAP_RESULT_SOURCE_UNASSIGNED || new_lib != nullptr,
"`new_lib` should only ever be NULL here in case the library of the old linked "
"ID is the newly opened .blend file.");
return new_lib;
}
static int reuse_bmain_data_dependencies_process_cb(LibraryIDLinkCallbackData *cb_data)
{
ID *id = *cb_data->id_pointer;
if (id == nullptr) {
return IDWALK_RET_NOP;
}
ReuseOldBMainData *reuse_data = static_cast<ReuseOldBMainData *>(cb_data->user_data);
Main *new_bmain = reuse_data->new_bmain;
Main *old_bmain = reuse_data->old_bmain;
/* First check if it has already been remapped. */
IDRemapper *remapper = reuse_bmain_data_remapper_ensure(reuse_data);
if (reuse_bmain_data_remapper_is_id_remapped(remapper, id)) {
return IDWALK_RET_STOP_RECURSION;
}
ListBase *new_lb = which_libbase(new_bmain, GS(id->name));
ListBase *old_lb = which_libbase(old_bmain, GS(id->name));
/* if ID is already in the new_bmain, this should have been detected by the check on `remapper`
* above. */
BLI_assert(BLI_findindex(new_lb, id) < 0);
BLI_assert(BLI_findindex(old_lb, id) >= 0);
/* There may be a new library pointer in new_bmain, matching a library in old_bmain, even
* though pointer values are not the same. So we need to check new linked IDs in new_bmain
* against both potential library pointers. */
Library *old_id_new_lib = reuse_bmain_data_dependencies_new_library_get(reuse_data, id->lib);
if (ID_IS_LINKED(id)) {
/* In case of linked ID, a 'new' version of the same data may already exist in new_bmain. In
* such case, do not move the old linked ID, but remap its usages to the new one instead. */
for (ID *id_iter = static_cast<ID *>(new_lb->last); id_iter != nullptr;
id_iter = static_cast<ID *>(id_iter->prev))
{
if (!ELEM(id_iter->lib, id->lib, old_id_new_lib)) {
continue;
}
if (!STREQ(id_iter->name + 2, id->name + 2)) {
continue;
}
/* NOTE: In case the old ID was from a library that is the newly opened .blend file (i.e.
* `old_id_new_lib` is NULL), this will remap to a local new ID in new_bmain.
*
* This has a potential impact on other ported over linked IDs (which are not allowed to
* use local data), and liboverrides (which are not allowed to have a local reference).
*
* Such cases are checked and 'fixed' later by the call to
* #reuse_bmain_data_invalid_local_usages_fix. */
BKE_id_remapper_add(remapper, id, id_iter);
return IDWALK_RET_STOP_RECURSION;
}
}
BLI_remlink_safe(old_lb, id);
BKE_main_namemap_remove_name(old_bmain, id, id->name + 2);
id->lib = old_id_new_lib;
BLI_addtail(new_lb, id);
BKE_id_new_name_validate(new_bmain, new_lb, id, nullptr, true);
/* Remap to itself, to avoid re-processing this ID again. */
BKE_id_remapper_add(remapper, id, id);
return IDWALK_RET_NOP;
}
/* Selectively 'import' data from old BMain into new BMain, provided it does not conflict with data
* already present in the new BMain (name-wise and library-wise).
*
* Dependencies from moved over old data are also imported into the new BMain, (unless, in case of
* linked data, a matching linked ID is already available in new BMain).
*
* When a conflict is found, usages of the conflicted ID by the old data are stored in the
* `remapper` of `ReuseOldBMainData` to be remapped to the matching data in the new BMain later.
*
* NOTE: This function will never remove any original new data from the new BMain, it only moves
* (some of) the old data to the new BMain.
*/
static void reuse_old_bmain_data_for_blendfile(ReuseOldBMainData *reuse_data, const short id_code)
{
Main *new_bmain = reuse_data->new_bmain;
Main *old_bmain = reuse_data->old_bmain;
ListBase *new_lb = which_libbase(new_bmain, id_code);
ListBase *old_lb = which_libbase(old_bmain, id_code);
IDRemapper *remapper = reuse_bmain_data_remapper_ensure(reuse_data);
/* Bring back local existing IDs from old_lb into new_lb, if there are no name/library
* conflicts. */
for (ID *old_id_iter_next, *old_id_iter = static_cast<ID *>(old_lb->first);
old_id_iter != nullptr;
old_id_iter = old_id_iter_next)
{
old_id_iter_next = static_cast<ID *>(old_id_iter->next);
/* Fully local assets are never re-used, since in this case the old file can be considered as
* an asset repository, and its assets should be accessed through the asset system by other
* files. */
if (!ID_IS_LINKED(old_id_iter) && !ID_IS_OVERRIDE_LIBRARY(old_id_iter) &&
ID_IS_ASSET(old_id_iter)) {
continue;
}
/* All other IDs can be re-used, provided there is no name/library conflict (i.e. the new bmain
* does not already have the 'same' data). */
bool can_use_old_id = true;
Library *old_id_new_lib = reuse_bmain_data_dependencies_new_library_get(reuse_data,
old_id_iter->lib);
for (ID *new_id_iter = static_cast<ID *>(new_lb->first); new_id_iter != nullptr;
new_id_iter = static_cast<ID *>(new_id_iter->next))
{
if (!ELEM(new_id_iter->lib, old_id_iter->lib, old_id_new_lib)) {
continue;
}
if (!STREQ(old_id_iter->name + 2, new_id_iter->name + 2)) {
continue;
}
/* In case we found a matching ID in new_bmain, it can be considered as 'the same'
* as the old ID, so usages of old ID ported over to new main can be remapped.
*
* This is only valid if the old ID was linked though. */
if (ID_IS_LINKED(old_id_iter)) {
BKE_id_remapper_add(remapper, old_id_iter, new_id_iter);
}
can_use_old_id = false;
break;
}
if (can_use_old_id) {
BLI_remlink_safe(old_lb, old_id_iter);
BKE_main_namemap_remove_name(old_bmain, old_id_iter, old_id_iter->name + 2);
BLI_addtail(new_lb, old_id_iter);
old_id_iter->lib = old_id_new_lib;
BKE_id_new_name_validate(new_bmain, new_lb, old_id_iter, nullptr, true);
BKE_lib_libblock_session_uuid_renew(old_id_iter);
/* Remap to itself, to avoid re-processing this ID again. */
BKE_id_remapper_add(remapper, old_id_iter, old_id_iter);
/* Port over dependencies of re-used ID, unless matching already existing ones in
* new_bmain can be found.
*
* NOTE : No pointers are remapped here, this code only moves dependencies from old_bmain
* to new_bmain if needed, and add necessary remapping rules to the reuse_data.remapper. */
BKE_library_foreach_ID_link(new_bmain,
old_id_iter,
reuse_bmain_data_dependencies_process_cb,
reuse_data,
IDWALK_RECURSE | IDWALK_DO_LIBRARY_POINTER);
}
}
}
/** Does a complete replacement of data in `new_bmain` by data from `old_bmain. Original new data
* are moved to the `old_bmain`, and will be freed together with it.
*
* WARNING: Currently only expects to work on local data, won't work properly if some of the IDs of
* given type are linked.
*
* NOTE: There is no support at all for potential dependencies of the IDs moved around. This is not
* expected to be necessary for the current use cases (UI-related IDs). */
* NOTE: Unlike with #reuse_old_bmain_data_for_blendfile, there is no support at all for potential
* dependencies of the IDs moved around. This is not expected to be necessary for the current use
* cases (UI-related IDs). */
static void swap_old_bmain_data_for_blendfile(ReuseOldBMainData *reuse_data, const short id_code)
{
Main *new_bmain = reuse_data->new_bmain;
@ -720,6 +901,12 @@ static void setup_app_data(bContext *C,
}
BKE_main_idmap_destroy(reuse_data.id_map);
if (!is_startup) {
/* Keeping old brushes has different conditions, and different behavior, than UI-like ID
* types when actually reading a blendfile. */
reuse_old_bmain_data_for_blendfile(&reuse_data, ID_BR);
}
}
/* Logic for 'track_undo_scene' is to keep using the scene which the active screen has, as long

View File

@ -14,13 +14,17 @@
#include "DNA_material_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_space_types.h"
#include "BLI_listbase.h"
#include "BLI_math_rotation.h"
#include "BLI_rand.h"
#include "BLO_readfile.h"
#include "BLT_translation.h"
#include "BKE_blendfile_link_append.h"
#include "BKE_bpath.h"
#include "BKE_brush.hh"
#include "BKE_colortools.h"
@ -36,6 +40,8 @@
#include "BKE_preview_image.hh"
#include "BKE_texture.h"
#include "AS_asset_library.h"
#include "IMB_colormanagement.h"
#include "IMB_imbuf.h"
#include "IMB_imbuf_types.h"
@ -376,39 +382,6 @@ static void brush_blend_read_after_liblink(BlendLibReader * /*reader*/, ID *id)
}
}
static int brush_undo_preserve_cb(LibraryIDLinkCallbackData *cb_data)
{
BlendLibReader *reader = (BlendLibReader *)cb_data->user_data;
ID *self_id = cb_data->self_id;
ID *id_old = *cb_data->id_pointer;
/* Old data has not been remapped to new values of the pointers, if we want to keep the old
* pointer here we need its new address. */
ID *id_old_new = id_old != nullptr ? BLO_read_get_new_id_address(
reader, self_id, ID_IS_LINKED(self_id), id_old) :
nullptr;
BLI_assert(id_old_new == nullptr || ELEM(id_old, id_old_new, id_old_new->orig_id));
if (cb_data->cb_flag & IDWALK_CB_USER) {
id_us_plus_no_lib(id_old_new);
id_us_min(id_old);
}
*cb_data->id_pointer = id_old_new;
return IDWALK_RET_NOP;
}
static void brush_undo_preserve(BlendLibReader *reader, ID *id_new, ID *id_old)
{
/* Whole Brush is preserved across undo-steps. */
BKE_lib_id_swap(nullptr, id_new, id_old, false, 0);
/* `id_new` now has content from `id_old`, we need to ensure those old ID pointers are valid.
* NOTE: Since we want to re-use all old pointers here, code is much simpler than for Scene. */
BKE_library_foreach_ID_link(nullptr, id_new, brush_undo_preserve_cb, reader, IDWALK_NOP);
/* NOTE: We do not swap IDProperties, as dealing with potential ID pointers in those would be
* fairly delicate. */
std::swap(id_new->properties, id_old->properties);
}
IDTypeInfo IDType_ID_BR = {
/*id_code*/ ID_BR,
/*id_filter*/ FILTER_ID_BR,
@ -417,7 +390,7 @@ IDTypeInfo IDType_ID_BR = {
/*name*/ "Brush",
/*name_plural*/ "brushes",
/*translation_context*/ BLT_I18NCONTEXT_ID_BRUSH,
/*flags*/ IDTYPE_FLAGS_NO_ANIMDATA,
/*flags*/ IDTYPE_FLAGS_NO_ANIMDATA | IDTYPE_FLAGS_NO_MEMFILE_UNDO,
/*asset_type_info*/ nullptr,
/*init_data*/ brush_init_data,
@ -433,7 +406,7 @@ IDTypeInfo IDType_ID_BR = {
/*blend_read_data*/ brush_blend_read_data,
/*blend_read_after_liblink*/ brush_blend_read_after_liblink,
/*blend_read_undo_preserve*/ brush_undo_preserve,
/*blend_read_undo_preserve*/ nullptr,
/*lib_override_apply_post*/ nullptr,
};
@ -510,6 +483,63 @@ static void brush_defaults(Brush *brush)
#undef FROM_DEFAULT_PTR
}
Brush *BKE_brush_asset_runtime_ensure(Main *bmain, const AssetWeakReference *brush_asset_reference)
{
BLI_assert(brush_asset_reference != nullptr);
char asset_full_path_buffer[FILE_MAX_LIBEXTRA];
char *asset_lib_path, *asset_group, *asset_name;
AS_asset_full_path_explode_from_weak_ref(
brush_asset_reference, asset_full_path_buffer, &asset_lib_path, &asset_group, &asset_name);
if (asset_lib_path == nullptr && asset_group == nullptr && asset_name == nullptr) {
return nullptr;
}
BLI_assert(STREQ(asset_group, IDType_ID_BR.name));
BLI_assert(asset_name != nullptr);
/* If the weakreference resolves to a null library path, assume that we are in local asset case.
*/
if (asset_lib_path == nullptr) {
Brush *local_brush_asset = reinterpret_cast<Brush *>(
BLI_findstring(&bmain->brushes, asset_name, offsetof(ID, name) + 2));
if (local_brush_asset == nullptr || !ID_IS_ASSET(local_brush_asset)) {
return nullptr;
}
return local_brush_asset;
}
LibraryLink_Params lapp_parameters = {nullptr};
lapp_parameters.bmain = bmain;
BlendfileLinkAppendContext *lapp_context = BKE_blendfile_link_append_context_new(
&lapp_parameters);
BKE_blendfile_link_append_context_flag_set(lapp_context, BLO_LIBLINK_FORCE_INDIRECT, true);
BKE_blendfile_link_append_context_flag_set(lapp_context, FILE_LINK, true);
BKE_blendfile_link_append_context_library_add(lapp_context, asset_lib_path, nullptr);
BlendfileLinkAppendContextItem *lapp_item = BKE_blendfile_link_append_context_item_add(
lapp_context, asset_name, ID_BR, nullptr);
BKE_blendfile_link_append_context_item_library_index_enable(lapp_context, lapp_item, 0);
BKE_blendfile_link(lapp_context, nullptr);
BKE_blendfile_override(
lapp_context,
static_cast<eBKELibLinkOverride>(BKE_LIBLINK_OVERRIDE_USE_EXISTING_LIBOVERRIDES |
BKE_LIBLINK_OVERRIDE_CREATE_RUNTIME),
nullptr);
Brush *liboverride_brush = reinterpret_cast<Brush *>(
BKE_blendfile_link_append_context_item_liboverrideid_get(lapp_context, lapp_item));
BKE_blendfile_link_append_context_free(lapp_context);
return liboverride_brush;
}
/* Datablock add/copy/free/make_local */
Brush *BKE_brush_add(Main *bmain, const char *name, const eObjectMode ob_mode)

View File

@ -362,6 +362,41 @@ bool BKE_lib_override_library_is_system_defined(const Main *bmain, const ID *id)
return false;
}
int BKE_lib_override_user_edited_from_library_count(Main *bmain,
const short id_type,
Library *library,
ReportList *r_reports)
{
ListBase *lb = which_libbase(bmain, id_type);
int num_user_edited = 0;
for (ID *id_iter = static_cast<ID *>(lb->first); id_iter != nullptr;
id_iter = static_cast<ID *>(id_iter->next))
{
if (ID_IS_LINKED(id_iter)) {
break;
}
if (!ID_IS_OVERRIDE_LIBRARY(id_iter)) {
continue;
}
if (id_iter->override_library->reference->lib != library) {
continue;
}
if (BKE_lib_override_library_is_user_edited(id_iter)) {
/* NOTE: If changes have been saved in a draft, then the local override is based on said
* draft (using the linked ID from the draft file as reference), so there should be no user
* edited changes anymore. */
num_user_edited++;
if (r_reports) {
BKE_report(r_reports, RPT_INFO, id_iter->name + 2);
}
}
}
return num_user_edited;
}
bool BKE_lib_override_library_property_is_animated(
const ID *id,
const IDOverrideLibraryProperty *liboverride_prop,

View File

@ -11,6 +11,7 @@
#include "MEM_guardedalloc.h"
#include "DNA_asset_types.h"
#include "DNA_brush_types.h"
#include "DNA_defaults.h"
#include "DNA_gpencil_legacy_types.h"
@ -35,6 +36,7 @@
#include "BLT_translation.h"
#include "BKE_asset.h"
#include "BKE_attribute.h"
#include "BKE_attribute.hh"
#include "BKE_brush.hh"
@ -309,7 +311,7 @@ void BKE_paint_reset_overlay_invalid(ePaintOverlayControlFlags flag)
overlay_flags &= ~(flag);
}
bool BKE_paint_ensure_from_paintmode(Scene *sce, ePaintMode mode)
bool BKE_paint_ensure_from_paintmode(Main *bmain, Scene *sce, ePaintMode mode)
{
ToolSettings *ts = sce->toolsettings;
Paint **paint_ptr = nullptr;
@ -354,7 +356,7 @@ bool BKE_paint_ensure_from_paintmode(Scene *sce, ePaintMode mode)
break;
}
if (paint_ptr) {
BKE_paint_ensure(ts, paint_ptr);
BKE_paint_ensure(bmain, ts, paint_ptr);
return true;
}
return false;
@ -668,6 +670,50 @@ void BKE_paint_brush_set(Paint *p, Brush *br)
}
}
static void paint_brush_asset_update(Paint *p,
Brush *br,
AssetWeakReference *brush_asset_reference)
{
if (p->brush_asset_reference != nullptr) {
BKE_asset_weak_reference_free(&p->brush_asset_reference);
}
if (br == nullptr || br != p->brush || !ID_IS_OVERRIDE_LIBRARY_REAL(p->brush) ||
!(ID_IS_ASSET(p->brush) || ID_IS_ASSET(p->brush->id.override_library->reference)))
{
BKE_asset_weak_reference_free(&brush_asset_reference);
return;
}
p->brush_asset_reference = brush_asset_reference;
}
void BKE_paint_brush_asset_set(Paint *p, Brush *br, AssetWeakReference *weak_asset_reference)
{
BKE_paint_brush_set(p, br);
paint_brush_asset_update(p, br, weak_asset_reference);
}
void BKE_paint_brush_asset_restore(Main *bmain, Paint *p)
{
if (p->brush != nullptr) {
return;
}
if (p->brush_asset_reference == nullptr) {
return;
}
AssetWeakReference *brush_asset_reference = p->brush_asset_reference;
p->brush_asset_reference = nullptr;
Brush *brush_asset = BKE_brush_asset_runtime_ensure(bmain, brush_asset_reference);
/* Will either re-assign the brush_asset_reference to `p`, or free it if loading a brush ID from
* it failed. */
BKE_paint_brush_asset_set(p, brush_asset, brush_asset_reference);
}
void BKE_paint_runtime_init(const ToolSettings *ts, Paint *paint)
{
if (paint == &ts->imapaint.paint) {
@ -1091,7 +1137,7 @@ eObjectMode BKE_paint_object_mode_from_paintmode(ePaintMode mode)
}
}
bool BKE_paint_ensure(ToolSettings *ts, Paint **r_paint)
bool BKE_paint_ensure(Main *bmain, ToolSettings *ts, Paint **r_paint)
{
Paint *paint = nullptr;
if (*r_paint) {
@ -1125,6 +1171,7 @@ bool BKE_paint_ensure(ToolSettings *ts, Paint **r_paint)
BLI_assert(paint_test.runtime.tool_offset == (*r_paint)->runtime.tool_offset);
#endif
}
BKE_paint_brush_asset_restore(bmain, *r_paint);
return true;
}
@ -1172,6 +1219,7 @@ bool BKE_paint_ensure(ToolSettings *ts, Paint **r_paint)
*r_paint = paint;
BKE_paint_runtime_init(ts, paint);
BKE_paint_brush_asset_restore(bmain, paint);
return false;
}
@ -1181,7 +1229,7 @@ void BKE_paint_init(Main *bmain, Scene *sce, ePaintMode mode, const uchar col[3]
UnifiedPaintSettings *ups = &sce->toolsettings->unified_paint_settings;
Paint *paint = BKE_paint_get_active_from_paintmode(sce, mode);
BKE_paint_ensure_from_paintmode(sce, mode);
BKE_paint_ensure_from_paintmode(bmain, sce, mode);
/* If there's no brush, create one */
if (PAINT_MODE_HAS_BRUSH(mode)) {
@ -1211,6 +1259,9 @@ void BKE_paint_free(Paint *paint)
{
BKE_curvemapping_free(paint->cavity_curve);
MEM_SAFE_FREE(paint->tool_slots);
if (paint->brush_asset_reference != nullptr) {
BKE_asset_weak_reference_free(&paint->brush_asset_reference);
}
}
void BKE_paint_copy(Paint *src, Paint *tar, const int flag)
@ -1219,6 +1270,8 @@ void BKE_paint_copy(Paint *src, Paint *tar, const int flag)
tar->cavity_curve = BKE_curvemapping_copy(src->cavity_curve);
tar->tool_slots = static_cast<PaintToolSlot *>(MEM_dupallocN(src->tool_slots));
tar->brush_asset_reference = BKE_asset_weak_reference_copy(src->brush_asset_reference);
if ((flag & LIB_ID_CREATE_NO_USER_REFCOUNT) == 0) {
id_us_plus((ID *)tar->brush);
id_us_plus((ID *)tar->palette);
@ -1247,6 +1300,9 @@ void BKE_paint_blend_write(BlendWriter *writer, Paint *p)
if (p->cavity_curve) {
BKE_curvemapping_blend_write(writer, p->cavity_curve);
}
if (p->brush_asset_reference) {
BKE_asset_weak_reference_write(writer, p->brush_asset_reference);
}
BLO_write_struct_array(writer, PaintToolSlot, p->tool_slots_len, p->tool_slots);
}
@ -1264,6 +1320,11 @@ void BKE_paint_blend_read_data(BlendDataReader *reader, const Scene *scene, Pain
BKE_paint_cavity_curve_preset(p, CURVE_PRESET_LINE);
}
BLO_read_data_address(reader, &p->brush_asset_reference);
if (p->brush_asset_reference) {
BKE_asset_weak_reference_read(reader, p->brush_asset_reference);
}
BLO_read_data_address(reader, &p->tool_slots);
/* Workaround for invalid data written in older versions. */
@ -2062,9 +2123,9 @@ int BKE_sculpt_mask_layers_ensure(Depsgraph *depsgraph,
return ret;
}
void BKE_sculpt_toolsettings_data_ensure(Scene *scene)
void BKE_sculpt_toolsettings_data_ensure(Main *bmain, Scene *scene)
{
BKE_paint_ensure(scene->toolsettings, (Paint **)&scene->toolsettings->sculpt);
BKE_paint_ensure(bmain, scene->toolsettings, (Paint **)&scene->toolsettings->sculpt);
Sculpt *sd = scene->toolsettings->sculpt;

View File

@ -1017,6 +1017,9 @@ static void scene_foreach_path(ID *id, BPathForeachPathData *bpath_data)
if (scene->ed != nullptr) {
SEQ_for_each_callback(&scene->ed->seqbase, seq_foreach_path_callback, bpath_data);
}
/* TODO: Also handle `asset_weak_reference` here? Probably not, conceptually this is not a file
* path. */
}
static void scene_blend_write(BlendWriter *writer, ID *id, const void *id_address)

View File

@ -2844,10 +2844,10 @@ void do_versions_after_linking_280(FileData *fd, Main *bmain)
ToolSettings *ts = scene->toolsettings;
/* Ensure new Paint modes. */
BKE_paint_ensure_from_paintmode(scene, PAINT_MODE_GPENCIL);
BKE_paint_ensure_from_paintmode(scene, PAINT_MODE_VERTEX_GPENCIL);
BKE_paint_ensure_from_paintmode(scene, PAINT_MODE_SCULPT_GPENCIL);
BKE_paint_ensure_from_paintmode(scene, PAINT_MODE_WEIGHT_GPENCIL);
BKE_paint_ensure_from_paintmode(bmain, scene, PAINT_MODE_GPENCIL);
BKE_paint_ensure_from_paintmode(bmain, scene, PAINT_MODE_VERTEX_GPENCIL);
BKE_paint_ensure_from_paintmode(bmain, scene, PAINT_MODE_SCULPT_GPENCIL);
BKE_paint_ensure_from_paintmode(bmain, scene, PAINT_MODE_WEIGHT_GPENCIL);
/* Set default Draw brush. */
if (brush != nullptr) {
@ -2888,9 +2888,9 @@ void do_versions_after_linking_280(FileData *fd, Main *bmain)
/* Reset all grease pencil brushes. */
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
/* Ensure new Paint modes. */
BKE_paint_ensure_from_paintmode(scene, PAINT_MODE_VERTEX_GPENCIL);
BKE_paint_ensure_from_paintmode(scene, PAINT_MODE_SCULPT_GPENCIL);
BKE_paint_ensure_from_paintmode(scene, PAINT_MODE_WEIGHT_GPENCIL);
BKE_paint_ensure_from_paintmode(bmain, scene, PAINT_MODE_VERTEX_GPENCIL);
BKE_paint_ensure_from_paintmode(bmain, scene, PAINT_MODE_SCULPT_GPENCIL);
BKE_paint_ensure_from_paintmode(bmain, scene, PAINT_MODE_WEIGHT_GPENCIL);
}
}

View File

@ -481,9 +481,9 @@ void BLO_update_defaults_startup_blend(Main *bmain, const char *app_template)
BKE_brush_gpencil_weight_presets(bmain, scene->toolsettings, true);
/* Ensure new Paint modes. */
BKE_paint_ensure_from_paintmode(scene, PAINT_MODE_VERTEX_GPENCIL);
BKE_paint_ensure_from_paintmode(scene, PAINT_MODE_SCULPT_GPENCIL);
BKE_paint_ensure_from_paintmode(scene, PAINT_MODE_WEIGHT_GPENCIL);
BKE_paint_ensure_from_paintmode(bmain, scene, PAINT_MODE_VERTEX_GPENCIL);
BKE_paint_ensure_from_paintmode(bmain, scene, PAINT_MODE_SCULPT_GPENCIL);
BKE_paint_ensure_from_paintmode(bmain, scene, PAINT_MODE_WEIGHT_GPENCIL);
/* Enable cursor. */
GpPaint *gp_paint = scene->toolsettings->gp_paint;

View File

@ -43,6 +43,10 @@ void ED_asset_handle_get_full_library_path(
/* `1024` for #FILE_MAX,
* rely on warnings to let us know if this gets out of sync. */
char r_full_lib_path[1024]);
void ED_asset_handle_get_full_path(const AssetHandle *asset_handle,
/* `1024 + 66` for #FILE_MAX_LIBEXTRA,
* rely on warnings to let us know if this gets out of sync. */
char r_full_lib_path[1024 + 66]);
#ifdef __cplusplus
}

View File

@ -57,3 +57,16 @@ void ED_asset_handle_get_full_library_path(const AssetHandle *asset_handle,
BLI_strncpy(r_full_lib_path, library_path.c_str(), FILE_MAX);
}
void ED_asset_handle_get_full_path(const AssetHandle *asset_handle,
char r_full_lib_path[FILE_MAX_LIBEXTRA])
{
*r_full_lib_path = '\0';
std::string library_path = asset_handle->file_data->asset->get_identifier().full_path();
if (library_path.empty()) {
return;
}
BLI_strncpy(r_full_lib_path, library_path.c_str(), FILE_MAX);
}

View File

@ -395,8 +395,8 @@ static int gpencil_paintmode_toggle_exec(bContext *C, wmOperator *op)
if (mode == OB_MODE_PAINT_GPENCIL_LEGACY) {
/* Be sure we have brushes and Paint settings.
* Need Draw and Vertex (used for Tint). */
BKE_paint_ensure(ts, (Paint **)&ts->gp_paint);
BKE_paint_ensure(ts, (Paint **)&ts->gp_vertexpaint);
BKE_paint_ensure(bmain, ts, (Paint **)&ts->gp_paint);
BKE_paint_ensure(bmain, ts, (Paint **)&ts->gp_vertexpaint);
BKE_brush_gpencil_paint_presets(bmain, ts, false);
@ -510,7 +510,7 @@ static int gpencil_sculptmode_toggle_exec(bContext *C, wmOperator *op)
if (mode == OB_MODE_SCULPT_GPENCIL_LEGACY) {
/* Be sure we have brushes. */
BKE_paint_ensure(ts, (Paint **)&ts->gp_sculptpaint);
BKE_paint_ensure(bmain, ts, (Paint **)&ts->gp_sculptpaint);
const bool reset_mode = (ts->gp_sculptpaint->paint.brush == nullptr);
BKE_brush_gpencil_sculpt_presets(bmain, ts, reset_mode);
@ -624,7 +624,7 @@ static int gpencil_weightmode_toggle_exec(bContext *C, wmOperator *op)
if (mode == OB_MODE_WEIGHT_GPENCIL_LEGACY) {
/* Be sure we have brushes. */
BKE_paint_ensure(ts, (Paint **)&ts->gp_weightpaint);
BKE_paint_ensure(bmain, ts, (Paint **)&ts->gp_weightpaint);
const bool reset_mode = (ts->gp_weightpaint->paint.brush == nullptr);
BKE_brush_gpencil_weight_presets(bmain, ts, reset_mode);
@ -731,8 +731,8 @@ static int gpencil_vertexmode_toggle_exec(bContext *C, wmOperator *op)
if (mode == OB_MODE_VERTEX_GPENCIL_LEGACY) {
/* Be sure we have brushes.
* Need Draw as well (used for Palettes). */
BKE_paint_ensure(ts, (Paint **)&ts->gp_paint);
BKE_paint_ensure(ts, (Paint **)&ts->gp_vertexpaint);
BKE_paint_ensure(bmain, ts, (Paint **)&ts->gp_paint);
BKE_paint_ensure(bmain, ts, (Paint **)&ts->gp_vertexpaint);
const bool reset_mode = (ts->gp_vertexpaint->paint.brush == nullptr);
BKE_brush_gpencil_vertex_presets(bmain, ts, reset_mode);

View File

@ -1428,7 +1428,7 @@ void ED_gpencil_add_defaults(bContext *C, Object *ob)
Main *bmain = CTX_data_main(C);
ToolSettings *ts = CTX_data_tool_settings(C);
BKE_paint_ensure(ts, (Paint **)&ts->gp_paint);
BKE_paint_ensure(bmain, ts, (Paint **)&ts->gp_paint);
Paint *paint = &ts->gp_paint->paint;
/* if not exist, create a new one */
if ((paint->brush == nullptr) || (paint->brush->gpencil_settings == nullptr)) {

View File

@ -4,8 +4,12 @@
set(INC
../include
../asset
../uvedit
../../asset_system
../../blenkernel
../../blenlib
../../blenloader
../../blentranslation
../../bmesh
../../depsgraph

View File

@ -8,6 +8,8 @@
#include "BLI_utildefines.h"
#include "BLI_vector_set.hh"
#include "BKE_asset.h"
#include "BKE_blendfile.h"
#include "BKE_brush.hh"
#include "BKE_bvhutils.h"
#include "BKE_context.h"
@ -20,6 +22,7 @@
#include "WM_message.hh"
#include "WM_toolsystem.h"
#include "ED_asset_handle.h"
#include "ED_curves.hh"
#include "ED_curves_sculpt.hh"
#include "ED_image.hh"
@ -28,6 +31,8 @@
#include "ED_space_api.hh"
#include "ED_view3d.hh"
#include "AS_asset_representation.hh"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
@ -283,7 +288,8 @@ static void curves_sculptmode_enter(bContext *C)
wmMsgBus *mbus = CTX_wm_message_bus(C);
Object *ob = CTX_data_active_object(C);
BKE_paint_ensure(scene->toolsettings, (Paint **)&scene->toolsettings->curves_sculpt);
BKE_paint_ensure(
CTX_data_main(C), scene->toolsettings, (Paint **)&scene->toolsettings->curves_sculpt);
CurvesSculpt *curves_sculpt = scene->toolsettings->curves_sculpt;
ob->mode = OB_MODE_SCULPT_CURVES;
@ -1167,6 +1173,55 @@ 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*/)
{
char *brush_name = nullptr;
char asset_libpath[FILE_MAX];
char asset_libpath_full[FILE_MAX_LIBEXTRA];
bool asset_handle_valid;
const AssetHandle asset_handle = CTX_wm_asset_handle(C, &asset_handle_valid);
if (!asset_handle_valid) {
return OPERATOR_CANCELLED;
}
AssetRepresentationHandle *asset = ED_asset_handle_get_representation(&asset_handle);
AssetWeakReference *brush_asset_reference = asset->make_weak_reference();
ED_asset_handle_get_full_path(&asset_handle, asset_libpath_full);
BKE_blendfile_library_path_explode(asset_libpath_full, asset_libpath, nullptr, &brush_name);
BLI_assert(brush_name != nullptr);
Brush *brush = BKE_brush_asset_runtime_ensure(CTX_data_main(C), brush_asset_reference);
ToolSettings *tool_settings = CTX_data_tool_settings(C);
/* Either takes ownership of the brush_asset_reference, or frees it. */
BKE_paint_brush_asset_set(&tool_settings->curves_sculpt->paint, brush, brush_asset_reference);
return OPERATOR_FINISHED;
}
/**
* 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.
*/
static void SCULPT_CURVES_OT_brush_asset_select(struct wmOperatorType *ot)
{
/* identifiers */
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";
/* api callbacks */
ot->exec = brush_asset_select_exec;
ot->poll = CURVES_SCULPT_mode_poll;
ot->prop = RNA_def_string(
ot->srna, "name", nullptr, MAX_NAME, "Brush Name", "name of the brush asset to select");
}
} // namespace blender::ed::sculpt_paint
/* -------------------------------------------------------------------- */
@ -1181,6 +1236,8 @@ 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);
}
/** \} */

View File

@ -192,7 +192,7 @@ static void grease_pencil_draw_mode_enter(bContext *C)
Object *ob = CTX_data_active_object(C);
GpPaint *grease_pencil_paint = scene->toolsettings->gp_paint;
BKE_paint_ensure(scene->toolsettings, (Paint **)&grease_pencil_paint);
BKE_paint_ensure(CTX_data_main(C), scene->toolsettings, (Paint **)&grease_pencil_paint);
ob->mode = OB_MODE_PAINT_GREASE_PENCIL;

View File

@ -553,7 +553,8 @@ void get_brush_alpha_data(const Scene *scene,
void init_stroke(Depsgraph *depsgraph, Object *ob);
void init_session_data(const ToolSettings *ts, Object *ob);
void init_session(Depsgraph *depsgraph, Scene *scene, Object *ob, eObjectMode object_mode);
void init_session(
Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob, eObjectMode object_mode);
Vector<PBVHNode *> pbvh_gather_generic(Object *ob, VPaint *wp, Sculpt *sd, Brush *brush);

View File

@ -209,10 +209,11 @@ void init_stroke(Depsgraph *depsgraph, Object *ob)
}
/* Toggle operator for turning vertex paint mode on or off (copied from sculpt.cc) */
void init_session(Depsgraph *depsgraph, Scene *scene, Object *ob, eObjectMode object_mode)
void init_session(
Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob, eObjectMode object_mode)
{
/* Create persistent sculpt mode data */
BKE_sculpt_toolsettings_data_ensure(scene);
BKE_sculpt_toolsettings_data_ensure(bmain, scene);
BLI_assert(ob->sculpt == nullptr);
ob->sculpt = MEM_new<SculptSession>(__func__);
@ -336,7 +337,7 @@ void mode_enter_generic(
const ePaintMode paint_mode = PAINT_MODE_VERTEX;
ED_mesh_color_ensure(me, nullptr);
BKE_paint_ensure(scene->toolsettings, (Paint **)&scene->toolsettings->vpaint);
BKE_paint_ensure(bmain, scene->toolsettings, (Paint **)&scene->toolsettings->vpaint);
Paint *paint = BKE_paint_get_active_from_paintmode(scene, paint_mode);
ED_paint_cursor_start(paint, vertex_paint_poll);
BKE_paint_init(bmain, scene, paint_mode, PAINT_CURSOR_VERTEX_PAINT);
@ -344,7 +345,7 @@ void mode_enter_generic(
else if (mode_flag == OB_MODE_WEIGHT_PAINT) {
const ePaintMode paint_mode = PAINT_MODE_WEIGHT;
BKE_paint_ensure(scene->toolsettings, (Paint **)&scene->toolsettings->wpaint);
BKE_paint_ensure(bmain, scene->toolsettings, (Paint **)&scene->toolsettings->wpaint);
Paint *paint = BKE_paint_get_active_from_paintmode(scene, paint_mode);
ED_paint_cursor_start(paint, weight_paint_poll);
BKE_paint_init(bmain, scene, paint_mode, PAINT_CURSOR_WEIGHT_PAINT);
@ -366,7 +367,7 @@ void mode_enter_generic(
BKE_sculptsession_free(ob);
}
vwpaint::init_session(depsgraph, scene, ob, mode_flag);
vwpaint::init_session(bmain, depsgraph, scene, ob, mode_flag);
/* Flush object mode. */
DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE);

View File

@ -267,7 +267,7 @@ static void SCULPT_OT_symmetrize(wmOperatorType *ot)
static void sculpt_init_session(Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob)
{
/* Create persistent sculpt mode data. */
BKE_sculpt_toolsettings_data_ensure(scene);
BKE_sculpt_toolsettings_data_ensure(bmain, scene);
/* Create sculpt mode session data. */
if (ob->sculpt != nullptr) {

View File

@ -543,6 +543,11 @@ typedef struct Library {
*/
char filepath_abs[1024];
/**
* Weak reference to the Brush Asset.
*/
struct AssetWeakReference *asset_repository_weak_reference;
/** Set for indirectly linked libraries, used in the outliner and while reading. */
struct Library *parent;

View File

@ -948,6 +948,13 @@ typedef struct PaintToolSlot {
typedef struct Paint {
struct Brush *brush;
/**
* A weak asset reference to the #brush, if not NULL.
* Used to attempt restoring the active brush from the AssetLibrary system, typically on
* file load.
*/
struct AssetWeakReference *brush_asset_reference;
/**
* Each tool has its own active brush,
* The currently active tool is defined by the current 'brush'.

View File

@ -770,7 +770,12 @@ static void rna_Brush_reset_icon(Brush *br)
static void rna_Brush_update(Main * /*bmain*/, Scene * /*scene*/, PointerRNA *ptr)
{
Brush *br = (Brush *)ptr->data;
Brush *br = (Brush *)ptr->owner_id;
if (ID_IS_OVERRIDE_LIBRARY_REAL(&br->id)) {
br->id.tag |= LIB_TAG_LIBOVERRIDE_AUTOREFRESH;
}
WM_main_add_notifier(NC_BRUSH | NA_EDITED, br);
// WM_main_add_notifier(NC_SPACE | ND_SPACE_VIEW3D, nullptr);
}
@ -2146,14 +2151,18 @@ static void rna_def_curves_sculpt_options(BlenderRNA *brna)
RNA_def_struct_sdna(srna, "BrushCurvesSculptSettings");
RNA_def_struct_ui_text(srna, "Curves Sculpt Brush Settings", "");
RNA_define_lib_overridable(true);
prop = RNA_def_property(srna, "add_amount", PROP_INT, PROP_NONE);
RNA_def_property_range(prop, 1, INT32_MAX);
RNA_def_property_ui_text(prop, "Count", "Number of curves added by the Add brush");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "points_per_curve", PROP_INT, PROP_NONE);
RNA_def_property_range(prop, 2, INT32_MAX);
RNA_def_property_ui_text(
prop, "Points per Curve", "Number of control points in a newly added curve");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "scale_uniform", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "flag", BRUSH_CURVES_SCULPT_FLAG_SCALE_UNIFORM);
@ -2161,17 +2170,20 @@ static void rna_def_curves_sculpt_options(BlenderRNA *brna)
"Scale Uniform",
"Grow or shrink curves by changing their size uniformly instead of "
"using trimming or extrapolation");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "minimum_length", PROP_FLOAT, PROP_DISTANCE);
RNA_def_property_range(prop, 0.0f, FLT_MAX);
RNA_def_property_ui_text(
prop, "Minimum Length", "Avoid shrinking curves shorter than this length");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "interpolate_length", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(
prop, nullptr, "flag", BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_LENGTH);
RNA_def_property_ui_text(
prop, "Interpolate Length", "Use length of the curves in close proximity");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "interpolate_point_count", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(
@ -2179,11 +2191,13 @@ static void rna_def_curves_sculpt_options(BlenderRNA *brna)
RNA_def_property_ui_text(prop,
"Interpolate Point Count",
"Use the number of points from the curves in close proximity");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "interpolate_shape", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "flag", BRUSH_CURVES_SCULPT_FLAG_INTERPOLATE_SHAPE);
RNA_def_property_ui_text(
prop, "Interpolate Shape", "Use shape of the curves in close proximity");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "curve_length", PROP_FLOAT, PROP_DISTANCE);
RNA_def_property_range(prop, 0.0, FLT_MAX);
@ -2191,28 +2205,35 @@ static void rna_def_curves_sculpt_options(BlenderRNA *brna)
prop,
"Curve Length",
"Length of newly added curves when it is not interpolated from other curves");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "minimum_distance", PROP_FLOAT, PROP_DISTANCE);
RNA_def_property_range(prop, 0.0f, FLT_MAX);
RNA_def_property_ui_range(prop, 0.0, 1000.0f, 0.001, 2);
RNA_def_property_ui_text(
prop, "Minimum Distance", "Goal distance between curve roots for the Density brush");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "density_add_attempts", PROP_INT, PROP_NONE);
RNA_def_property_range(prop, 0, INT32_MAX);
RNA_def_property_ui_text(
prop, "Density Add Attempts", "How many times the Density brush tries to add a new curve");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "density_mode", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, density_mode_items);
RNA_def_property_ui_text(
prop, "Density Mode", "Determines whether the brush adds or removes curves");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "curve_parameter_falloff", PROP_POINTER, PROP_NONE);
RNA_def_property_struct_type(prop, "CurveMapping");
RNA_def_property_ui_text(prop,
"Curve Parameter Falloff",
"Falloff that is applied from the tip to the root of each curve");
RNA_def_property_update(prop, 0, "rna_Brush_update");
RNA_define_lib_overridable(false);
}
static void rna_def_brush(BlenderRNA *brna)
@ -2560,6 +2581,8 @@ static void rna_def_brush(BlenderRNA *brna)
srna, "Brush", "Brush data-block for storing brush settings for painting and sculpting");
RNA_def_struct_ui_icon(srna, ICON_BRUSH_DATA);
RNA_define_lib_overridable(true);
/* enums */
prop = RNA_def_property(srna, "blend", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, prop_blend_items);
@ -3867,6 +3890,8 @@ static void rna_def_brush(BlenderRNA *brna)
RNA_def_property_struct_type(prop, "BrushCurvesSculptSettings");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_ui_text(prop, "Curves Sculpt Settings", "");
RNA_define_lib_overridable(false);
}
/**

View File

@ -145,6 +145,11 @@ static void wm_history_file_write();
static void wm_test_autorun_revert_action_exec(bContext *C);
static bool wm_operator_open_file_draft_check_dialog(bContext *C,
wmOperator *op,
const int num_user_edited_lost,
ReportList *user_edited_lost_reports);
static CLG_LogRef LOG = {"wm.files"};
/* -------------------------------------------------------------------- */
@ -2766,6 +2771,7 @@ static int operator_state_dispatch(bContext *C, wmOperator *op, OperatorDispatch
enum {
OPEN_MAINFILE_STATE_DISCARD_CHANGES,
OPEN_MAINFILE_STATE_SELECT_FILE_PATH,
OPEN_MAINFILE_STATE_DISCARD_ASSET_DRAFTS,
OPEN_MAINFILE_STATE_OPEN,
};
@ -2783,7 +2789,7 @@ static int wm_open_mainfile__discard_changes(bContext *C, wmOperator *op)
set_next_operator_state(op, OPEN_MAINFILE_STATE_SELECT_FILE_PATH);
}
else {
set_next_operator_state(op, OPEN_MAINFILE_STATE_OPEN);
set_next_operator_state(op, OPEN_MAINFILE_STATE_DISCARD_ASSET_DRAFTS);
}
if (wm_operator_close_file_dialog_if_needed(C, op, wm_open_mainfile_after_dialog_callback)) {
@ -2823,6 +2829,44 @@ static int wm_open_mainfile__select_file_path(bContext *C, wmOperator *op)
return OPERATOR_RUNNING_MODAL;
}
static int wm_open_mainfile__discard_asset_drafts(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
char filepath[FILE_MAX];
RNA_string_get(op->ptr, "filepath", filepath);
set_next_operator_state(op, OPEN_MAINFILE_STATE_OPEN);
Library *lib = static_cast<Library *>(
BLI_findstring(&bmain->libraries, filepath, offsetof(Library, filepath_abs)));
if (lib == nullptr) {
return wm_open_mainfile_dispatch(C, op);
}
/* If new to-be-opened blendfile was a library of the currently opened one, check for potential
* persistent edited assets that would be reset to their library status (only Brushes IDs
* currently). */
ReportList *reports = MEM_cnew<ReportList>(__func__);
BKE_reports_init(reports, RPT_STORE);
const int num_user_edited_brushes = BKE_lib_override_user_edited_from_library_count(
bmain, ID_BR, lib, reports);
if (num_user_edited_brushes > 0) {
/* NOTE: steals ownership of `user_edited_lost_reports` regardless of its result. */
if (wm_operator_open_file_draft_check_dialog(C, op, num_user_edited_brushes, reports)) {
return OPERATOR_INTERFACE;
}
reports = nullptr;
}
else {
BKE_reports_clear(reports);
MEM_delete(reports);
}
return wm_open_mainfile_dispatch(C, op);
}
static int wm_open_mainfile__open(bContext *C, wmOperator *op)
{
char filepath[FILE_MAX];
@ -2855,6 +2899,7 @@ static int wm_open_mainfile__open(bContext *C, wmOperator *op)
static OperatorDispatchTarget wm_open_mainfile_dispatch_targets[] = {
{OPEN_MAINFILE_STATE_DISCARD_CHANGES, wm_open_mainfile__discard_changes},
{OPEN_MAINFILE_STATE_SELECT_FILE_PATH, wm_open_mainfile__select_file_path},
{OPEN_MAINFILE_STATE_DISCARD_ASSET_DRAFTS, wm_open_mainfile__discard_asset_drafts},
{OPEN_MAINFILE_STATE_OPEN, wm_open_mainfile__open},
{0, nullptr},
};
@ -4334,3 +4379,217 @@ bool wm_operator_close_file_dialog_if_needed(bContext *C,
}
/** \} */
/**
* \name Open Asset Library File Dialog.
*
* This handles cases where user is opening a file that is an asset library, which assets are used
* in the 'user preference' way (i.e. assets are linked, and have runtime-only, session-persistant,
* user-editable overrides of these.
*
* Special warning is necessary because when opening the library file, all non-drafted user edits
* of the relevant assets will be lost. */
/** \{ */
static const char *open_file_draft_check_dialog_name = "open_file_draft_check_popup";
typedef struct wmOpenDraftCheckCallback {
IDProperty *op_properties;
ReportList *user_edited_lost_reports;
int num_user_edited_lost;
char new_filepath[FILE_MAX];
} wmOpenDraftCheckCallback;
static void wm_block_open_file_draft_cancel(bContext *C, void *arg_block, void * /*arg_data*/)
{
wmWindow *win = CTX_wm_window(C);
UI_popup_block_close(C, win, static_cast<uiBlock *>(arg_block));
}
static void wm_block_open_file_draft_discard(bContext *C, void *arg_block, void *arg_data)
{
wmGenericCallback *callback = WM_generic_callback_steal((wmGenericCallback *)arg_data);
/* Close the popup before executing the callback. Otherwise
* the popup might be closed by the callback, which will lead
* to a crash. */
wmWindow *win = CTX_wm_window(C);
UI_popup_block_close(C, win, static_cast<uiBlock *>(arg_block));
callback->exec(C, callback->user_data);
WM_generic_callback_free(callback);
}
static void wm_block_open_file_draft_save(bContext *C, void *arg_block, void *arg_data)
{
wmGenericCallback *callback = WM_generic_callback_steal((wmGenericCallback *)arg_data);
bool execute_callback = true;
wmWindow *win = CTX_wm_window(C);
UI_popup_block_close(C, win, static_cast<uiBlock *>(arg_block));
/* TODO: Actually save the edited lost local runtime assets overrides into drafts. */
if (execute_callback) {
callback->exec(C, callback->user_data);
}
WM_generic_callback_free(callback);
}
static void wm_block_open_file_draft_cancel_button(uiBlock *block, wmGenericCallback *post_action)
{
uiBut *but = uiDefIconTextBut(
block, UI_BTYPE_BUT, 0, 0, IFACE_("Cancel"), 0, 0, 0, UI_UNIT_Y, 0, 0, 0, 0, 0, "");
UI_but_func_set(but, wm_block_open_file_draft_cancel, block, post_action);
UI_but_drawflag_disable(but, UI_BUT_TEXT_LEFT);
}
static void wm_block_open_file_draft_discard_button(uiBlock *block, wmGenericCallback *post_action)
{
uiBut *but = uiDefIconTextBut(
block, UI_BTYPE_BUT, 0, 0, IFACE_("Ignore"), 0, 0, 0, UI_UNIT_Y, 0, 0, 0, 0, 0, "");
UI_but_func_set(but, wm_block_open_file_draft_discard, block, post_action);
UI_but_drawflag_disable(but, UI_BUT_TEXT_LEFT);
}
static void wm_block_open_file_draft_save_button(uiBlock *block, wmGenericCallback *post_action)
{
uiBut *but = uiDefIconTextBut(
block, UI_BTYPE_BUT, 0, 0, IFACE_("Save To Draft"), 0, 0, 0, UI_UNIT_Y, 0, 0, 0, 0, 0, "");
UI_but_func_set(but, wm_block_open_file_draft_save, block, post_action);
UI_but_drawflag_disable(but, UI_BUT_TEXT_LEFT);
UI_but_flag_enable(but, UI_BUT_ACTIVE_DEFAULT);
}
static void wm_open_file_after_draft_check_dialog_callback(bContext *C, void *user_data)
{
wmOpenDraftCheckCallback *callback_data = static_cast<wmOpenDraftCheckCallback *>(user_data);
WM_operator_name_call_with_properties(
C, "WM_OT_open_mainfile", WM_OP_INVOKE_DEFAULT, callback_data->op_properties, nullptr);
}
static uiBlock *block_create__open_draft_check_file_dialog(struct bContext *C,
struct ARegion *region,
void *arg1)
{
wmGenericCallback *post_action = static_cast<wmGenericCallback *>(arg1);
wmOpenDraftCheckCallback *callback_data = static_cast<wmOpenDraftCheckCallback *>(
post_action->user_data);
uiBlock *block = UI_block_begin(C, region, open_file_draft_check_dialog_name, UI_EMBOSS);
UI_block_flag_enable(
block, UI_BLOCK_KEEP_OPEN | UI_BLOCK_LOOP | UI_BLOCK_NO_WIN_CLIP | UI_BLOCK_NUMSELECT);
UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP);
uiLayout *layout = uiItemsAlertBox(block, 34, ALERT_ICON_QUESTION);
/* Title. */
uiItemL_ex(layout, TIP_("Save User-Edited Assets to Draft?"), ICON_NONE, true, false);
char message[2048];
SNPRINTF(message,
TIP_("The following %d assets have local edits that will be lost"),
callback_data->num_user_edited_lost);
uiItemL(layout, message, ICON_NONE);
uiItemL(layout, "by opening the chosen Blender Asset Library file", ICON_NONE);
uiItemL(layout, callback_data->new_filepath, ICON_NONE);
/* Draw available report messages. */
LISTBASE_FOREACH (Report *, report, &callback_data->user_edited_lost_reports->list) {
uiLayout *row = uiLayoutColumn(layout, false);
uiLayoutSetScaleY(row, 0.6f);
uiItemS(row);
uiItemL_ex(row, report->message, ICON_NONE, false, true);
}
uiItemS_ex(layout, 4.0f);
/* Buttons. */
#ifdef _WIN32
const bool windows_layout = true;
#else
const bool windows_layout = false;
#endif
if (windows_layout) {
/* Windows standard layout. */
uiLayout *split = uiLayoutSplit(layout, 0.0f, true);
uiLayoutSetScaleY(split, 1.2f);
uiLayoutColumn(split, false);
wm_block_open_file_draft_save_button(block, post_action);
uiLayoutColumn(split, false);
wm_block_open_file_draft_discard_button(block, post_action);
uiLayoutColumn(split, false);
wm_block_open_file_draft_cancel_button(block, post_action);
}
else {
/* Non-Windows layout (macOS and Linux). */
uiLayout *split = uiLayoutSplit(layout, 0.3f, true);
uiLayoutSetScaleY(split, 1.2f);
uiLayoutColumn(split, false);
wm_block_open_file_draft_discard_button(block, post_action);
uiLayout *split_right = uiLayoutSplit(split, 0.1f, true);
uiLayoutColumn(split_right, false);
/* Empty space. */
uiLayoutColumn(split_right, false);
wm_block_open_file_draft_cancel_button(block, post_action);
uiLayoutColumn(split_right, false);
wm_block_open_file_draft_save_button(block, post_action);
}
UI_block_bounds_set_centered(block, int(14 * U.scale_factor));
return block;
}
static void wm_free_open_file_draft_check_callback(void *user_data)
{
wmOpenDraftCheckCallback *callback_data = static_cast<wmOpenDraftCheckCallback *>(user_data);
IDP_FreeProperty(callback_data->op_properties);
BKE_reports_clear(callback_data->user_edited_lost_reports);
MEM_delete(callback_data->user_edited_lost_reports);
MEM_delete(callback_data);
}
/* NOTE: steals ownership of `user_edited_lost_reports`. */
static bool wm_operator_open_file_draft_check_dialog(bContext *C,
wmOperator *op,
const int num_user_edited_lost,
ReportList *user_edited_lost_reports)
{
wmOpenDraftCheckCallback *callback_data = MEM_cnew<wmOpenDraftCheckCallback>(__func__);
callback_data->op_properties = IDP_CopyProperty(op->properties);
RNA_string_get(op->ptr, "filepath", callback_data->new_filepath);
callback_data->num_user_edited_lost = num_user_edited_lost;
callback_data->user_edited_lost_reports = user_edited_lost_reports;
wmGenericCallback *callback = MEM_cnew<wmGenericCallback>(__func__);
callback->exec = wm_open_file_after_draft_check_dialog_callback;
callback->user_data = callback_data;
callback->free_user_data = wm_free_open_file_draft_check_callback;
if (!UI_popup_block_name_exists(CTX_wm_screen(C), open_file_draft_check_dialog_name)) {
UI_popup_block_invoke(
C, block_create__open_draft_check_file_dialog, callback, free_post_file_close_action);
return true;
}
WM_generic_callback_free(callback);
return false;
}
/** \} */

View File

@ -187,7 +187,7 @@ static void toolsystem_ref_link(bContext *C, WorkSpace *workspace, bToolRef *tre
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
if (workspace == WM_window_get_active_workspace(win)) {
Scene *scene = WM_window_get_active_scene(win);
BKE_paint_ensure_from_paintmode(scene, paint_mode);
BKE_paint_ensure_from_paintmode(bmain, scene, paint_mode);
Paint *paint = BKE_paint_get_active_from_paintmode(scene, paint_mode);
Brush *brush = BKE_paint_toolslots_brush_get(paint, slot_index);
if (brush == nullptr) {