Sculpt/Paint: Bring back support for multiple brush based tools

Design: https://projects.blender.org/blender/blender/issues/126032

The brush assets project merged all brush based tools into a single, "Brush"
tool. After feedback, we want to bring back some of the previous brush based
tools. For example in grease pencil draw mode, not having an eraser or fill tool
available, and having to go through all the brush assets instead made the
workflow more cumbersome, and features less discoverable (an eraser tool in the
toolbar is quite easy to find, a brush deep down in the asset library may not
be).

With this commit we can add back some tools for specific brush types in sculpt &
paint modes. The follow up commit will start with the eraser and fill tools for
grease pencil draw mode, but more tools in other modes are expected to follow.

For every brush type that has a tool in the toolbar, the last used brush is
remembered. This is the biggest part of the changes here.

Brush asset popups will only show the brushes supported by the active tool for
now. The permanent asset shelf region displays all brushes. Activating a brush
that isn't compatible with the current tool will also activate the general
"Brush" tool, but while the brush tool is active we never switch to another one
(e.g. activating an eraser brush will keep the "Brush" tool active). All this
might change after further feedback.

Pull Request: https://projects.blender.org/blender/blender/pulls/125449
This commit is contained in:
Julian Eisel
2024-09-20 18:08:53 +02:00
parent 2ba13677ad
commit a38c96b92c
14 changed files with 668 additions and 78 deletions

View File

@@ -10,17 +10,48 @@ class BrushAssetShelf:
bl_options = {'DEFAULT_VISIBLE', 'NO_ASSET_DRAG', 'STORE_ENABLED_CATALOGS_IN_PREFERENCES'}
bl_activate_operator = "BRUSH_OT_asset_activate"
bl_default_preview_size = 48
brush_type_prop = None
tool_prop = None
mode_prop = None
@classmethod
def poll(cls, context):
return hasattr(context, "object") and context.object and context.object.mode == cls.mode
@classmethod
def brush_type_poll(cls, context, asset):
from bl_ui.space_toolsystem_common import ToolSelectPanelHelper
tool = ToolSelectPanelHelper.tool_active_from_context(context)
if not tool or tool.brush_type == 'ANY':
return True
if not cls.brush_type_prop or not cls.tool_prop:
return True
asset_brush_type = asset.metadata.get(cls.brush_type_prop)
# Asset metadata doesn't store a brush type. Only show it when the tool doesn't require a
# certain brush type.
if asset_brush_type is None:
return False
brush_type_items = bpy.types.Brush.bl_rna.properties[cls.tool_prop].enum_items
return brush_type_items[asset_brush_type].identifier == tool.brush_type
@classmethod
def asset_poll(cls, asset):
if asset.id_type != 'BRUSH':
return False
if hasattr(cls, "mode_prop"):
return asset.metadata.get(cls.mode_prop, False)
if cls.mode_prop and not asset.metadata.get(cls.mode_prop, False):
return False
context = bpy.context
is_asset_shelf_region = context.region and context.region.type == 'ASSET_SHELF'
# Show all brushes in the permanent asset shelf region. Otherwise filter out brushes that
# are incompatible with the tool.
if not is_asset_shelf_region and not cls.brush_type_poll(context, asset):
return False
return True
@classmethod
@@ -28,7 +59,7 @@ class BrushAssetShelf:
# Only show active highlight when using the brush tool.
from bl_ui.space_toolsystem_common import ToolSelectPanelHelper
tool = ToolSelectPanelHelper.tool_active_from_context(bpy.context)
if not tool or tool.idname != "builtin.brush":
if not tool or not tool.use_brushes:
return None
paint_settings = UnifiedPaintPanel.paint_settings(bpy.context)

View File

@@ -1718,6 +1718,8 @@ class ImageAssetShelf(BrushAssetShelf):
class IMAGE_AST_brush_paint(ImageAssetShelf, AssetShelf):
mode_prop = "use_paint_image"
brush_type_prop = "image_brush_type"
tool_prop = "image_tool"
@classmethod
def poll(cls, context):

View File

@@ -1036,7 +1036,7 @@ def _activate_by_item(context, space_type, item, index, *, as_fallback=False):
cursor=item.cursor or 'DEFAULT',
options=item.options or set(),
gizmo_group=gizmo_group,
brush_type=item.brush_type or "",
brush_type=item.brush_type or 'ANY',
data_block=item.data_block or "",
operator=item.operator or "",
index=index,

View File

@@ -9548,51 +9548,71 @@ class View3DAssetShelf(BrushAssetShelf):
class VIEW3D_AST_brush_sculpt(View3DAssetShelf, bpy.types.AssetShelf):
mode = 'SCULPT'
mode_prop = "use_paint_sculpt"
brush_type_prop = "sculpt_brush_type"
tool_prop = "sculpt_tool"
class VIEW3D_AST_brush_sculpt_curves(View3DAssetShelf, bpy.types.AssetShelf):
mode = 'SCULPT_CURVES'
mode_prop = "use_paint_sculpt_curves"
brush_type_prop = "curves_sculpt_brush_type"
tool_prop = "curves_sculpt_tool"
class VIEW3D_AST_brush_vertex_paint(View3DAssetShelf, bpy.types.AssetShelf):
mode = 'VERTEX_PAINT'
mode_prop = "use_paint_vertex"
brush_type_prop = "vertex_brush_type"
tool_prop = "vertex_tool"
class VIEW3D_AST_brush_weight_paint(View3DAssetShelf, bpy.types.AssetShelf):
mode = 'WEIGHT_PAINT'
mode_prop = "use_paint_weight"
brush_type_prop = "weight_brush_type"
tool_prop = "weight_tool"
class VIEW3D_AST_brush_texture_paint(View3DAssetShelf, bpy.types.AssetShelf):
mode = 'TEXTURE_PAINT'
mode_prop = "use_paint_image"
brush_type_prop = "image_brush_type"
tool_prop = "image_tool"
class VIEW3D_AST_brush_gpencil_paint(View3DAssetShelf, bpy.types.AssetShelf):
mode = 'PAINT_GPENCIL'
mode_prop = "use_paint_grease_pencil"
brush_type_prop = "gpencil_brush_type"
tool_prop = "gpencil_tool"
class VIEW3D_AST_brush_grease_pencil_paint(View3DAssetShelf, bpy.types.AssetShelf):
mode = 'PAINT_GREASE_PENCIL'
mode_prop = "use_paint_grease_pencil"
brush_type_prop = "gpencil_brush_type"
tool_prop = "gpencil_tool"
class VIEW3D_AST_brush_gpencil_sculpt(View3DAssetShelf, bpy.types.AssetShelf):
mode = 'SCULPT_GPENCIL'
mode_prop = "use_sculpt_grease_pencil"
brush_type_prop = "gpencil_sculpt_brush_type"
tool_prop = "gpencil_sculpt_tool"
class VIEW3D_AST_brush_gpencil_vertex(View3DAssetShelf, bpy.types.AssetShelf):
mode = 'VERTEX_GPENCIL'
mode_prop = "use_vertex_grease_pencil"
brush_type_prop = "gpencil_vertex_brush_type"
tool_prop = "gpencil_vertex_tool"
class VIEW3D_AST_brush_gpencil_weight(View3DAssetShelf, bpy.types.AssetShelf):
mode = 'WEIGHT_GPENCIL'
mode_prop = "use_weight_grease_pencil"
brush_type_prop = "gpencil_weight_brush_type"
tool_prop = "gpencil_weight_tool"
classes = (

View File

@@ -29,6 +29,7 @@
#include "BKE_pbvh.hh"
#include "BKE_subdiv_ccg.hh"
struct AssetWeakReference;
struct BMFace;
struct BMLog;
struct BMVert;
@@ -199,6 +200,8 @@ const EnumPropertyItem *BKE_paint_get_tool_enum_from_paintmode(PaintMode mode);
uint BKE_paint_get_brush_type_offset_from_paintmode(PaintMode mode);
std::optional<int> BKE_paint_get_brush_type_from_obmode(const Brush *brush,
const eObjectMode ob_mode);
std::optional<int> BKE_paint_get_brush_type_from_paintmode(const Brush *brush,
const PaintMode mode);
Paint *BKE_paint_get_active(Scene *sce, ViewLayer *view_layer);
Paint *BKE_paint_get_active_from_context(const bContext *C);
PaintMode BKE_paintmode_get_active_from_context(const bContext *C);
@@ -216,11 +219,25 @@ Brush *BKE_paint_brush_from_essentials(Main *bmain, const char *name);
*
* \return True on success. If \a brush is already active, this is considered a success (the brush
* asset reference will still be updated).
*
* \note #WM_toolsystem_activate_brush_and_tool() might be the preferable way to change the active
* brush. It also lets the toolsystem decide if the active tool should be changed given the type of
* brush, and it updates the "last used brush" for the previous tool. #BKE_paint_brush_set() should
* only be called to force a brush to be active, circumventing the tool system.
*/
bool BKE_paint_brush_set(Paint *paint, Brush *brush);
/**
* Version of #BKE_paint_brush_set() that takes an asset reference instead of a brush, importing
* the brush if necessary.
*/
bool BKE_paint_brush_set(Main *bmain,
Paint *paint,
const AssetWeakReference *brush_asset_reference);
bool BKE_paint_brush_set_default(Main *bmain, Paint *paint);
bool BKE_paint_brush_set_essentials(Main *bmain, Paint *paint, const char *name);
std::optional<AssetWeakReference> BKE_paint_brush_type_default_reference(
eObjectMode ob_mode, std::optional<int> brush_type);
void BKE_paint_brushes_set_default_references(ToolSettings *ts);
void BKE_paint_brushes_validate(Main *bmain, Paint *paint);

View File

@@ -602,7 +602,11 @@ PaintMode BKE_paintmode_get_from_tool(const bToolRef *tref)
return PaintMode::Invalid;
}
static bool paint_brush_set_from_asset_reference(Main *bmain, Paint *paint)
/**
* After changing #Paint.brush_asset_reference, call this to activate the matching brush, importing
* it if necessary. Has no effect if #Paint.brush is set already.
*/
static bool paint_brush_update_from_asset_reference(Main *bmain, Paint *paint)
{
/* Don't resolve this during file read, it will be done after. */
if (bmain->is_locked_for_linking) {
@@ -642,6 +646,52 @@ const Brush *BKE_paint_brush_for_read(const Paint *paint)
return paint ? paint->brush : nullptr;
}
static AssetWeakReference *asset_reference_create_from_brush(Brush *brush)
{
if (std::optional<AssetWeakReference> weak_ref = blender::bke::asset_edit_weak_reference_from_id(
brush->id))
{
return MEM_new<AssetWeakReference>(__func__, *weak_ref);
}
return nullptr;
}
bool BKE_paint_brush_set(Main *bmain,
Paint *paint,
const AssetWeakReference *brush_asset_reference)
{
/* Don't resolve this during file read, it will be done after. */
if (bmain->is_locked_for_linking) {
return false;
}
Brush *brush = reinterpret_cast<Brush *>(
blender::bke::asset_edit_id_from_weak_reference(*bmain, ID_BR, *brush_asset_reference));
BLI_assert(brush == nullptr || blender::bke::asset_edit_id_is_editable(brush->id));
/* Ensure we have a brush with appropriate mode to assign.
* Could happen if contents of asset blend was manually changed. */
if (brush && (paint->runtime.ob_mode & brush->ob_mode) == 0) {
return false;
}
/* Update the brush itself. */
paint->brush = brush;
/* Update the brush asset reference. */
{
MEM_delete(paint->brush_asset_reference);
paint->brush_asset_reference = nullptr;
if (brush != nullptr) {
BLI_assert(blender::bke::asset_edit_weak_reference_from_id(brush->id) ==
*brush_asset_reference);
paint->brush_asset_reference = MEM_new<AssetWeakReference>(__func__, *brush_asset_reference);
}
}
return true;
}
bool BKE_paint_brush_set(Paint *paint, Brush *brush)
{
if (paint == nullptr) {
@@ -655,18 +705,32 @@ bool BKE_paint_brush_set(Paint *paint, Brush *brush)
MEM_delete(paint->brush_asset_reference);
paint->brush_asset_reference = nullptr;
if (brush != nullptr) {
std::optional<AssetWeakReference> weak_ref = blender::bke::asset_edit_weak_reference_from_id(
brush->id);
if (weak_ref.has_value()) {
paint->brush_asset_reference = MEM_new<AssetWeakReference>(__func__, *weak_ref);
}
paint->brush_asset_reference = asset_reference_create_from_brush(brush);
}
return true;
}
static AssetWeakReference *paint_brush_asset_reference_from_essentials(const char *name)
{
AssetWeakReference *weak_ref = MEM_new<AssetWeakReference>(__func__);
weak_ref->asset_library_type = eAssetLibraryType::ASSET_LIBRARY_ESSENTIALS;
weak_ref->asset_library_identifier = nullptr;
weak_ref->relative_asset_identifier = BLI_sprintfN("brushes/essentials_brushes.blend/Brush/%s",
name);
return weak_ref;
}
static void paint_brush_asset_reference_from_essentials(const char *name,
AssetWeakReference *r_weak_ref)
{
r_weak_ref->asset_library_type = eAssetLibraryType::ASSET_LIBRARY_ESSENTIALS;
r_weak_ref->asset_library_identifier = nullptr;
r_weak_ref->relative_asset_identifier = BLI_sprintfN("brushes/essentials_brushes.blend/Brush/%s",
name);
}
Brush *BKE_paint_brush_from_essentials(Main *bmain, const char *name)
{
AssetWeakReference weak_ref;
@@ -683,11 +747,7 @@ static void paint_brush_set_essentials_reference(Paint *paint, const char *name)
/* Set brush asset reference to a named brush in the essentials asset library. */
MEM_delete(paint->brush_asset_reference);
AssetWeakReference *weak_ref = MEM_new<AssetWeakReference>(__func__);
weak_ref->asset_library_type = eAssetLibraryType::ASSET_LIBRARY_ESSENTIALS;
weak_ref->relative_asset_identifier = BLI_sprintfN("brushes/essentials_brushes.blend/Brush/%s",
name);
paint->brush_asset_reference = weak_ref;
paint->brush_asset_reference = paint_brush_asset_reference_from_essentials(name);
paint->brush = nullptr;
}
@@ -696,28 +756,20 @@ static void paint_eraser_brush_set_essentials_reference(Paint *paint, const char
/* Set brush asset reference to a named brush in the essentials asset library. */
MEM_delete(paint->eraser_brush_asset_reference);
AssetWeakReference *weak_ref = MEM_new<AssetWeakReference>(__func__);
weak_ref->asset_library_type = eAssetLibraryType::ASSET_LIBRARY_ESSENTIALS;
weak_ref->relative_asset_identifier = BLI_sprintfN("brushes/essentials_brushes.blend/Brush/%s",
name);
paint->eraser_brush_asset_reference = weak_ref;
paint->eraser_brush_asset_reference = paint_brush_asset_reference_from_essentials(name);
paint->eraser_brush = nullptr;
}
static void paint_brush_set_default_reference(Paint *paint,
const bool do_regular = true,
const bool do_eraser = true)
static void paint_brush_default_essentials_name_get(
eObjectMode ob_mode,
std::optional<int> brush_type,
blender::StringRefNull *r_name,
blender::StringRefNull *r_eraser_name = nullptr)
{
if (!paint->runtime.initialized) {
/* Can happen when loading old file where toolsettings are created in versioning, without
* calling #paint_runtime_init(). Will be done later when necessary. */
return;
}
const char *name = "";
const char *eraser_name = "";
const char *name = nullptr;
const char *eraser_name = nullptr;
switch (paint->runtime.ob_mode) {
switch (ob_mode) {
case OB_MODE_SCULPT:
name = "Draw";
break;
@@ -735,6 +787,17 @@ static void paint_brush_set_default_reference(Paint *paint,
break;
case OB_MODE_PAINT_GPENCIL_LEGACY:
name = "Pencil";
/* Different default brush for some brush types. */
if (brush_type) {
switch (*brush_type) {
case GPAINT_BRUSH_TYPE_ERASE:
name = "Eraser Hard";
break;
case GPAINT_BRUSH_TYPE_FILL:
name = "Fill Area";
break;
}
}
eraser_name = "Eraser Soft";
break;
case OB_MODE_VERTEX_GPENCIL_LEGACY:
@@ -748,14 +811,51 @@ static void paint_brush_set_default_reference(Paint *paint,
break;
default:
BLI_assert_unreachable();
return;
break;
}
if (do_regular && name) {
paint_brush_set_essentials_reference(paint, name);
*r_name = name;
if (r_eraser_name) {
*r_eraser_name = eraser_name;
}
if (do_eraser && eraser_name) {
paint_eraser_brush_set_essentials_reference(paint, eraser_name);
}
std::optional<AssetWeakReference> BKE_paint_brush_type_default_reference(
eObjectMode ob_mode, std::optional<int> brush_type)
{
blender::StringRefNull name;
paint_brush_default_essentials_name_get(ob_mode, brush_type, &name, nullptr);
if (name.is_empty()) {
return {};
}
AssetWeakReference asset_reference;
paint_brush_asset_reference_from_essentials(name.c_str(), &asset_reference);
return asset_reference;
}
static void paint_brush_set_default_reference(Paint *paint,
const bool do_regular = true,
const bool do_eraser = true)
{
if (!paint->runtime.initialized) {
/* Can happen when loading old file where toolsettings are created in versioning, without
* calling #paint_runtime_init(). Will be done later when necessary. */
return;
}
blender::StringRefNull name;
blender::StringRefNull eraser_name;
paint_brush_default_essentials_name_get(
eObjectMode(paint->runtime.ob_mode), std::nullopt, &name, nullptr);
if (do_regular && !name.is_empty()) {
paint_brush_set_essentials_reference(paint, name.c_str());
}
if (do_eraser && !eraser_name.is_empty()) {
paint_eraser_brush_set_essentials_reference(paint, eraser_name.c_str());
}
}
@@ -791,13 +891,13 @@ void BKE_paint_brushes_set_default_references(ToolSettings *ts)
bool BKE_paint_brush_set_default(Main *bmain, Paint *paint)
{
paint_brush_set_default_reference(paint, true, false);
return paint_brush_set_from_asset_reference(bmain, paint);
return paint_brush_update_from_asset_reference(bmain, paint);
}
bool BKE_paint_brush_set_essentials(Main *bmain, Paint *paint, const char *name)
{
paint_brush_set_essentials_reference(paint, name);
return paint_brush_set_from_asset_reference(bmain, paint);
return paint_brush_update_from_asset_reference(bmain, paint);
}
void BKE_paint_brushes_validate(Main *bmain, Paint *paint)
@@ -999,6 +1099,37 @@ std::optional<int> BKE_paint_get_brush_type_from_obmode(const Brush *brush,
}
}
std::optional<int> BKE_paint_get_brush_type_from_paintmode(const Brush *brush,
const PaintMode mode)
{
switch (mode) {
case PaintMode::Texture2D:
case PaintMode::Texture3D:
return brush->image_brush_type;
case PaintMode::Sculpt:
return brush->sculpt_brush_type;
case PaintMode::Vertex:
return brush->vertex_brush_type;
case PaintMode::Weight:
return brush->weight_brush_type;
case PaintMode::GPencil:
return brush->gpencil_brush_type;
case PaintMode::VertexGPencil:
return brush->gpencil_vertex_brush_type;
case PaintMode::SculptGPencil:
return brush->gpencil_sculpt_brush_type;
case PaintMode::WeightGPencil:
return brush->gpencil_weight_brush_type;
case PaintMode::SculptCurves:
return brush->curves_sculpt_brush_type;
case PaintMode::SculptGreasePencil:
return brush->gpencil_sculpt_brush_type;
case PaintMode::Invalid:
default:
return {};
}
}
PaintCurve *BKE_paint_curve_add(Main *bmain, const char *name)
{
PaintCurve *pc = static_cast<PaintCurve *>(BKE_id_new(bmain, ID_PC, name));
@@ -1365,7 +1496,7 @@ bool BKE_paint_ensure(Main *bmain, ToolSettings *ts, Paint **r_paint)
BLI_assert(paint_test.runtime.ob_mode == (*r_paint)->runtime.ob_mode);
#endif
}
paint_brush_set_from_asset_reference(bmain, *r_paint);
paint_brush_update_from_asset_reference(bmain, *r_paint);
paint_eraser_brush_set_from_asset_reference(bmain, *r_paint);
return true;
}
@@ -1437,7 +1568,17 @@ void BKE_paint_free(Paint *paint)
{
BKE_curvemapping_free(paint->cavity_curve);
MEM_delete(paint->brush_asset_reference);
MEM_delete(paint->tool_brush_bindings.main_brush_asset_reference);
MEM_delete(paint->eraser_brush_asset_reference);
LISTBASE_FOREACH_MUTABLE (NamedBrushAssetReference *,
brush_ref,
&paint->tool_brush_bindings.active_brush_per_brush_type)
{
MEM_delete(brush_ref->name);
MEM_delete(brush_ref->brush_asset_reference);
MEM_delete(brush_ref);
}
}
void BKE_paint_copy(const Paint *src, Paint *dst, const int flag)
@@ -1449,10 +1590,23 @@ void BKE_paint_copy(const Paint *src, Paint *dst, const int flag)
dst->brush_asset_reference = MEM_new<AssetWeakReference>(__func__,
*src->brush_asset_reference);
}
if (src->tool_brush_bindings.main_brush_asset_reference) {
dst->tool_brush_bindings.main_brush_asset_reference = MEM_new<AssetWeakReference>(
__func__, *src->tool_brush_bindings.main_brush_asset_reference);
}
if (src->eraser_brush_asset_reference) {
dst->eraser_brush_asset_reference = MEM_new<AssetWeakReference>(
__func__, *src->eraser_brush_asset_reference);
}
BLI_duplicatelist(&dst->tool_brush_bindings.active_brush_per_brush_type,
&src->tool_brush_bindings.active_brush_per_brush_type);
LISTBASE_FOREACH (
NamedBrushAssetReference *, brush_ref, &dst->tool_brush_bindings.active_brush_per_brush_type)
{
brush_ref->name = BLI_strdup(brush_ref->name);
brush_ref->brush_asset_reference = MEM_new<AssetWeakReference>(
__func__, *brush_ref->brush_asset_reference);
}
if ((flag & LIB_ID_CREATE_NO_USER_REFCOUNT) == 0) {
id_us_plus((ID *)dst->palette);
@@ -1482,6 +1636,25 @@ void BKE_paint_blend_write(BlendWriter *writer, Paint *paint)
if (paint->eraser_brush_asset_reference) {
BKE_asset_weak_reference_write(writer, paint->eraser_brush_asset_reference);
}
{
/* Write tool system bindings. */
ToolSystemBrushBindings &tool_brush_bindings = paint->tool_brush_bindings;
if (tool_brush_bindings.main_brush_asset_reference) {
BKE_asset_weak_reference_write(writer, tool_brush_bindings.main_brush_asset_reference);
}
BLO_write_struct_list(
writer, NamedBrushAssetReference, &tool_brush_bindings.active_brush_per_brush_type);
LISTBASE_FOREACH (
NamedBrushAssetReference *, brush_ref, &tool_brush_bindings.active_brush_per_brush_type)
{
BLO_write_string(writer, brush_ref->name);
if (brush_ref->brush_asset_reference) {
BKE_asset_weak_reference_write(writer, brush_ref->brush_asset_reference);
}
}
}
}
void BKE_paint_blend_read_data(BlendDataReader *reader, const Scene *scene, Paint *paint)
@@ -1498,12 +1671,34 @@ void BKE_paint_blend_read_data(BlendDataReader *reader, const Scene *scene, Pain
if (paint->brush_asset_reference) {
BKE_asset_weak_reference_read(reader, paint->brush_asset_reference);
}
BLO_read_struct(reader, AssetWeakReference, &paint->eraser_brush_asset_reference);
if (paint->eraser_brush_asset_reference) {
BKE_asset_weak_reference_read(reader, paint->eraser_brush_asset_reference);
}
{
/* Read tool system bindings. */
ToolSystemBrushBindings &tool_brush_bindings = paint->tool_brush_bindings;
BLO_read_struct(reader, AssetWeakReference, &tool_brush_bindings.main_brush_asset_reference);
if (tool_brush_bindings.main_brush_asset_reference) {
BKE_asset_weak_reference_read(reader, tool_brush_bindings.main_brush_asset_reference);
}
BLO_read_struct_list(
reader, NamedBrushAssetReference, &tool_brush_bindings.active_brush_per_brush_type);
LISTBASE_FOREACH (
NamedBrushAssetReference *, brush_ref, &tool_brush_bindings.active_brush_per_brush_type)
{
BLO_read_string(reader, &brush_ref->name);
BLO_read_struct(reader, AssetWeakReference, &brush_ref->brush_asset_reference);
if (brush_ref->brush_asset_reference) {
BKE_asset_weak_reference_read(reader, brush_ref->brush_asset_reference);
}
}
}
paint->paint_cursor = nullptr;
paint_runtime_init(scene->toolsettings, paint);
}

View File

@@ -9,6 +9,7 @@
#include "DNA_brush_types.h"
#include "DNA_scene_types.h"
#include "DNA_space_types.h"
#include "DNA_workspace_types.h"
#include "BKE_asset.hh"
#include "BKE_asset_edit.hh"
@@ -65,7 +66,10 @@ static int brush_asset_activate_exec(bContext *C, wmOperator *op)
Paint *paint = BKE_paint_get_active_from_context(C);
if (!BKE_paint_brush_set(paint, brush)) {
/* Activate brush through tool system rather than calling #BKE_paint_brush_set() directly, to let
* the tool system switch tools if necessary, and update which brush was the last recently used
* one for the current tool. */
if (!WM_toolsystem_activate_brush_and_tool(C, paint, brush)) {
/* Note brush datablock was still added, so was not a no-op. */
BKE_report(op->reports, RPT_WARNING, "Unable to activate brush, wrong object mode");
return OPERATOR_FINISHED;
@@ -73,7 +77,6 @@ static int brush_asset_activate_exec(bContext *C, wmOperator *op)
WM_main_add_notifier(NC_ASSET | NA_ACTIVATED, nullptr);
WM_main_add_notifier(NC_SCENE | ND_TOOLSETTINGS, nullptr);
WM_toolsystem_ref_set_by_id(C, "builtin.brush");
return OPERATOR_FINISHED;
}
@@ -260,7 +263,7 @@ static int brush_asset_save_as_exec(bContext *C, wmOperator *op)
brush = reinterpret_cast<Brush *>(
bke::asset_edit_id_from_weak_reference(*bmain, ID_BR, brush_asset_reference));
if (!BKE_paint_brush_set(paint, brush)) {
if (!WM_toolsystem_activate_brush_and_tool(C, paint, brush)) {
/* Note brush asset 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");
}

View File

@@ -925,6 +925,38 @@ typedef struct Paint_Runtime {
char _pad[2];
} Paint_Runtime;
typedef struct NamedBrushAssetReference {
struct NamedBrushAssetReference *next, *prev;
const char *name;
struct AssetWeakReference *brush_asset_reference;
} NamedBrushAssetReference;
/**
* For the tool system: Storage to remember the last active brush for specific tools.
*
* This stores a "main" brush reference, which is used for any tool that uses brushes but isn't
* limited to a specific brush type, and a list of brush references identified by the brush type,
* for tools that are limited to a brush type.
*
* The tool system updates these fields as the active brush or active tool changes. It also
* determines the brush to remember/restore on tool changes and activates it.
*/
typedef struct ToolSystemBrushBindings {
struct AssetWeakReference *main_brush_asset_reference;
/**
* The tool system exposes tools for some brush types, like an eraser tool to access eraser
* brushes. Switching between tools should remember the last used brush for a brush type, e.g.
* which eraser was used last by the eraser tool.
*
* Note that multiple tools may use the same brush type, for example primitive draw tools (to
* draw rectangles, circles, lines, etc.) all use a "DRAW" brush, which will then be shared
* amongst them.
*/
ListBase active_brush_per_brush_type; /* #NamedBrushAssetReference */
} ToolSystemBrushBindings;
/** Paint Tool Base. */
typedef struct Paint {
/**
@@ -944,6 +976,8 @@ typedef struct Paint {
struct Brush *eraser_brush;
struct AssetWeakReference *eraser_brush_asset_reference;
ToolSystemBrushBindings tool_brush_bindings;
struct Palette *palette;
/** Cavity curve. */
struct CurveMapping *cavity_curve;

View File

@@ -41,8 +41,15 @@ typedef struct bToolRef_Runtime {
/** One of these 4 must be defined. */
char keymap[64];
char gizmo_group[64];
char brush_type[64];
char data_block[64];
/**
* The brush type this tool is limited too, if #TOOLREF_FLAG_USE_BRUSHES is set. Note that this
* is a different enum in different modes, e.g. #eBrushSculptType in sculpt mode,
* #eBrushVertexPaintType in vertex paint mode.
*
* -1 means any brush type may be used (0 is used by brush type enums of some modes).
*/
int brush_type;
/** Keymap for #bToolRef.idname_fallback, if set. */
char keymap_fallback[64];

View File

@@ -406,6 +406,11 @@ void rna_userdef_is_dirty_update_impl();
*/
void rna_userdef_is_dirty_update(Main *bmain, Scene *scene, PointerRNA *ptr);
const EnumPropertyItem *rna_WorkSpaceTool_brush_type_itemf(bContext *C,
PointerRNA *ptr,
PropertyRNA *prop,
bool *r_free);
/* API functions */
void RNA_api_action(StructRNA *srna);

View File

@@ -28,6 +28,7 @@
# include "BLI_listbase.h"
# include "BKE_global.hh"
# include "BKE_paint.hh"
# include "DNA_object_types.h"
# include "DNA_screen_types.h"
@@ -197,16 +198,42 @@ static bool rna_WorkSpaceTool_use_brushes_get(PointerRNA *ptr)
return (tref->runtime) ? ((tref->runtime->flag & TOOLREF_FLAG_USE_BRUSHES) != 0) : false;
}
static void rna_WorkSpaceTool_brush_type_get(PointerRNA *ptr, char *value)
static int rna_WorkSpaceTool_brush_type_get(PointerRNA *ptr)
{
bToolRef *tref = static_cast<bToolRef *>(ptr->data);
strcpy(value, tref->runtime ? tref->runtime->brush_type : "");
return tref->runtime ? tref->runtime->brush_type : -1;
}
static int rna_WorkSpaceTool_brush_type_length(PointerRNA *ptr)
const EnumPropertyItem *rna_WorkSpaceTool_brush_type_itemf(bContext *C,
PointerRNA *ptr,
PropertyRNA * /*prop*/,
bool *r_free)
{
bToolRef *tref = static_cast<bToolRef *>(ptr->data);
return tref->runtime ? strlen(tref->runtime->brush_type) : 0;
PaintMode paint_mode = [&]() {
if (ptr->type == &RNA_WorkSpaceTool) {
const bToolRef *tref = static_cast<bToolRef *>(ptr->data);
return BKE_paintmode_get_from_tool(tref);
}
return C ? BKE_paintmode_get_active_from_context(C) : PaintMode::Invalid;
}();
EnumPropertyItem *items = nullptr;
int totitem = 0;
EnumPropertyItem unset_item = {
-1, "ANY", 0, "Any", "Donnot limit this tool to a specific brush type"};
RNA_enum_item_add(&items, &totitem, &unset_item);
if (paint_mode != PaintMode::Invalid) {
const EnumPropertyItem *valid_items = BKE_paint_get_tool_enum_from_paintmode(paint_mode);
RNA_enum_items_add(&items, &totitem, valid_items);
}
RNA_enum_item_end(&items, &totitem);
*r_free = true;
return items;
}
static void rna_WorkSpaceTool_widget_get(PointerRNA *ptr, char *value)
@@ -324,14 +351,15 @@ static void rna_def_workspace_tool(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Uses Brushes", "");
RNA_def_property_boolean_funcs(prop, "rna_WorkSpaceTool_use_brushes_get", nullptr);
prop = RNA_def_property(srna, "brush_type", PROP_STRING, PROP_NONE);
prop = RNA_def_property(srna, "brush_type", PROP_ENUM, PROP_NONE);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_ui_text(prop,
"Brush Type",
"If the tool uses brushes and is limited to a specific brush type, the "
"identifier of the brush type");
RNA_def_property_string_funcs(
prop, "rna_WorkSpaceTool_brush_type_get", "rna_WorkSpaceTool_brush_type_length", nullptr);
RNA_def_property_enum_items(prop, rna_enum_dummy_DEFAULT_items);
RNA_def_property_enum_funcs(
prop, "rna_WorkSpaceTool_brush_type_get", nullptr, "rna_WorkSpaceTool_brush_type_itemf");
RNA_define_verify_sdna(true);

View File

@@ -37,7 +37,7 @@ static void rna_WorkSpaceTool_setup(ID *id,
int cursor,
const char *keymap,
const char *gizmo_group,
const char *brush_type,
int brush_type,
const char *data_block,
const char *op_idname,
int index,
@@ -50,9 +50,9 @@ static void rna_WorkSpaceTool_setup(ID *id,
tref_rt.cursor = cursor;
STRNCPY(tref_rt.keymap, keymap);
STRNCPY(tref_rt.gizmo_group, gizmo_group);
STRNCPY(tref_rt.brush_type, brush_type);
STRNCPY(tref_rt.data_block, data_block);
STRNCPY(tref_rt.op, op_idname);
tref_rt.brush_type = brush_type;
tref_rt.index = index;
tref_rt.flag = options;
@@ -146,7 +146,11 @@ void RNA_api_workspace_tool(StructRNA *srna)
RNA_def_property_enum_items(parm, rna_enum_window_cursor_items);
RNA_def_string(func, "keymap", nullptr, KMAP_MAX_NAME, "Key Map", "");
RNA_def_string(func, "gizmo_group", nullptr, MAX_NAME, "Gizmo Group", "");
RNA_def_string(func, "brush_type", nullptr, MAX_NAME, "Brush Type", "");
parm = RNA_def_property(func, "brush_type", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(parm, rna_enum_dummy_NULL_items);
RNA_def_property_enum_funcs(parm, nullptr, nullptr, "rna_WorkSpaceTool_brush_type_itemf");
RNA_def_property_enum_default(parm, -1);
RNA_def_property_ui_text(parm, "Brush Type", "Limit this tool to a specific type of brush");
RNA_def_string(func, "data_block", nullptr, MAX_NAME, "Data Block", "");
RNA_def_string(func, "operator", nullptr, MAX_NAME, "Operator", "");
RNA_def_int(func, "index", 0, INT_MIN, INT_MAX, "Index", "", INT_MIN, INT_MAX);

View File

@@ -7,10 +7,10 @@
* \ingroup wm
*/
#include "BLI_compiler_attrs.h"
struct Brush;
struct IDProperty;
struct Main;
struct Paint;
struct PointerRNA;
struct Scene;
struct ScrArea;
@@ -51,6 +51,15 @@ bToolRef *WM_toolsystem_ref_set_by_id(bContext *C, const char *name);
bToolRef_Runtime *WM_toolsystem_runtime_from_context(const bContext *C);
bToolRef_Runtime *WM_toolsystem_runtime_find(WorkSpace *workspace, const bToolKey *tkey);
/**
* Activate the brush through the tool system. This will call #BKE_paint_brush_set() with \a brush,
* but it will also switch to the tool appropriate for this brush type (if necessary) and update
* the current tool-brush references to remember the last used brush for that tool.
*
* \return True if the brush was successfully activated.
*/
bool WM_toolsystem_activate_brush_and_tool(bContext *C, Paint *paint, Brush *brush);
void WM_toolsystem_unlink(bContext *C, WorkSpace *workspace, const bToolKey *tkey);
void WM_toolsystem_refresh(const bContext *C, WorkSpace *workspace, const bToolKey *tkey);
void WM_toolsystem_reinit(bContext *C, WorkSpace *workspace, const bToolKey *tkey);

View File

@@ -19,12 +19,14 @@
#include "BLI_utildefines.h"
#include "DNA_ID.h"
#include "DNA_brush_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_space_types.h"
#include "DNA_windowmanager_types.h"
#include "DNA_workspace_types.h"
#include "BKE_asset_edit.hh"
#include "BKE_brush.hh"
#include "BKE_context.hh"
#include "BKE_idprop.hh"
@@ -135,6 +137,257 @@ void WM_toolsystem_unlink(bContext *C, WorkSpace *workspace, const bToolKey *tke
}
}
/* -------------------------------------------------------------------- */
/** \name Brush Tools
* \{ */
static std::optional<blender::StringRefNull> find_tool_id_from_brush_type_id(const bContext *C,
const int brush_type)
{
const WorkSpace *workspace = CTX_wm_workspace(C);
LISTBASE_FOREACH (const bToolRef *, tref, &workspace->tools) {
if (tref->runtime && (tref->runtime->brush_type != -1) &&
tref->runtime->brush_type == brush_type)
{
return tref->idname;
}
}
return {};
}
static const char *brush_type_identifier_get(const int brush_type, const PaintMode paint_mode)
{
const EnumPropertyItem *type_enum = BKE_paint_get_tool_enum_from_paintmode(paint_mode);
const int item_idx = RNA_enum_from_value(type_enum, brush_type);
if (item_idx == -1) {
return "";
}
return type_enum[item_idx].identifier;
}
static bool brush_type_is_compatible_with_active_tool(bContext *C, const int brush_type)
{
if (!WM_toolsystem_active_tool_is_brush(C)) {
return false;
}
const bToolRef *active_tool = WM_toolsystem_ref_from_context(C);
BLI_assert(BKE_paintmode_get_active_from_context(C) == BKE_paintmode_get_from_tool(active_tool));
/* Tool supports any brush type, no need to check further. */
if (active_tool->runtime->brush_type == -1) {
return true;
}
return active_tool->runtime->brush_type == brush_type;
}
static NamedBrushAssetReference *toolsystem_brush_type_binding_lookup(const Paint *paint,
const char *brush_type_name)
{
return static_cast<NamedBrushAssetReference *>(
BLI_findstring_ptr(&paint->tool_brush_bindings.active_brush_per_brush_type,
brush_type_name,
offsetof(NamedBrushAssetReference, name)));
}
/**
* Update the bindings so the main brush reference matches the currently active brush.
*/
static void toolsystem_main_brush_binding_update_from_active(Paint *paint)
{
MEM_delete(paint->tool_brush_bindings.main_brush_asset_reference);
paint->tool_brush_bindings.main_brush_asset_reference = nullptr;
if (paint->brush != nullptr) {
if (std::optional<AssetWeakReference> brush_asset_reference =
blender::bke::asset_edit_weak_reference_from_id(paint->brush->id))
{
paint->tool_brush_bindings.main_brush_asset_reference = MEM_new<AssetWeakReference>(
__func__, *brush_asset_reference);
}
}
}
static void toolsystem_brush_type_binding_update(Paint *paint,
const PaintMode paint_mode,
const int brush_type)
{
if (paint->brush == nullptr) {
return;
}
const char *brush_type_name = brush_type_identifier_get(brush_type, paint_mode);
if (!brush_type_name || !brush_type_name[0]) {
return;
}
/* Update existing reference. */
if (NamedBrushAssetReference *existing_brush_ref = toolsystem_brush_type_binding_lookup(
paint, brush_type_name))
{
MEM_delete(existing_brush_ref->brush_asset_reference);
existing_brush_ref->brush_asset_reference = MEM_new<AssetWeakReference>(
__func__, *paint->brush_asset_reference);
}
/* Add new reference. */
else {
NamedBrushAssetReference *new_brush_ref = MEM_cnew<NamedBrushAssetReference>(__func__);
new_brush_ref->name = BLI_strdup(brush_type_name);
new_brush_ref->brush_asset_reference = MEM_new<AssetWeakReference>(
__func__, *paint->brush_asset_reference);
BLI_addhead(&paint->tool_brush_bindings.active_brush_per_brush_type, new_brush_ref);
}
}
bool WM_toolsystem_activate_brush_and_tool(bContext *C, Paint *paint, Brush *brush)
{
const bToolRef *active_tool = WM_toolsystem_ref_from_context(C);
const PaintMode paint_mode = BKE_paintmode_get_active_from_context(C);
if (!BKE_paint_brush_set(paint, brush)) {
return false;
}
if (active_tool->runtime->brush_type == -1) {
/* Only update the main brush binding to reference the newly active brush. */
toolsystem_main_brush_binding_update_from_active(paint);
return true;
}
toolsystem_brush_type_binding_update(paint, paint_mode, active_tool->runtime->brush_type);
/* If necessary, find a compatible tool to switch to. */
{
std::optional<int> brush_type = BKE_paint_get_brush_type_from_paintmode(brush, paint_mode);
if (!brush_type) {
BLI_assert_unreachable();
WM_toolsystem_ref_set_by_id(C, "builtin.brush");
return true;
}
if (!brush_type_is_compatible_with_active_tool(C, *brush_type)) {
std::optional<blender::StringRefNull> compatible_tool = find_tool_id_from_brush_type_id(
C, *brush_type);
WM_toolsystem_ref_set_by_id(C, compatible_tool.value_or("builtin.brush").c_str());
}
return true;
}
}
static void toolsystem_brush_activate_from_toolref_for_object_particle(const bContext *C,
const WorkSpace *workspace,
const bToolRef *tref)
{
const Main *bmain = CTX_data_main(C);
const bToolRef_Runtime *tref_rt = tref->runtime;
if (!tref_rt->data_block[0]) {
return;
}
const EnumPropertyItem *items = rna_enum_particle_edit_hair_brush_items;
const int i = RNA_enum_from_identifier(items, tref_rt->data_block);
if (i == -1) {
return;
}
const wmWindowManager *wm = static_cast<wmWindowManager *>(bmain->wm.first);
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
if (workspace == WM_window_get_active_workspace(win)) {
Scene *scene = WM_window_get_active_scene(win);
ToolSettings *ts = scene->toolsettings;
ts->particle.brushtype = items[i].value;
}
}
}
static void toolsystem_brush_activate_from_toolref_for_object_paint(const bContext *C,
const WorkSpace *workspace,
const bToolRef *tref)
{
Main *bmain = CTX_data_main(C);
bToolRef_Runtime *tref_rt = tref->runtime;
const PaintMode paint_mode = BKE_paintmode_get_from_tool(tref);
BLI_assert(paint_mode != PaintMode::Invalid);
wmWindowManager *wm = static_cast<wmWindowManager *>(bmain->wm.first);
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
if (workspace != WM_window_get_active_workspace(win)) {
continue;
}
Scene *scene = WM_window_get_active_scene(win);
BKE_paint_ensure_from_paintmode(bmain, scene, paint_mode);
Paint *paint = BKE_paint_get_active_from_paintmode(scene, paint_mode);
/* Attempt to re-activate a brush remembered for this brush type, as stored in a brush
* binding. */
if (tref_rt->brush_type != -1) {
std::optional<AssetWeakReference> brush_asset_reference =
[&]() -> std::optional<AssetWeakReference> {
const char *brush_type_name = brush_type_identifier_get(tref_rt->brush_type, paint_mode);
const NamedBrushAssetReference *brush_ref = toolsystem_brush_type_binding_lookup(
paint, brush_type_name);
if (brush_ref && brush_ref->brush_asset_reference) {
return *brush_ref->brush_asset_reference;
}
/* No remembered brush found for this type, use a default for the type. */
return BKE_paint_brush_type_default_reference(eObjectMode(paint->runtime.ob_mode),
tref_rt->brush_type);
}();
if (brush_asset_reference) {
BKE_paint_brush_set(bmain, paint, &*brush_asset_reference);
}
}
/* Re-activate the main brush, regardless of the brush type. */
else {
if (paint->tool_brush_bindings.main_brush_asset_reference) {
BKE_paint_brush_set(bmain, paint, paint->tool_brush_bindings.main_brush_asset_reference);
toolsystem_main_brush_binding_update_from_active(paint);
}
else {
std::optional<AssetWeakReference> main_brush_asset_reference =
[&]() -> std::optional<AssetWeakReference> {
if (paint->tool_brush_bindings.main_brush_asset_reference) {
return *paint->tool_brush_bindings.main_brush_asset_reference;
}
return BKE_paint_brush_type_default_reference(eObjectMode(paint->runtime.ob_mode),
std::nullopt);
}();
if (main_brush_asset_reference) {
BKE_paint_brush_set(bmain, paint, &*main_brush_asset_reference);
toolsystem_main_brush_binding_update_from_active(paint);
}
}
}
}
}
/**
* Activate a brush compatible with \a tref, call when the active tool changes.
*/
static void toolsystem_brush_activate_from_toolref(const bContext *C,
WorkSpace *workspace,
bToolRef *tref)
{
BLI_assert(tref->runtime->flag & TOOLREF_FLAG_USE_BRUSHES);
if (tref->space_type == SPACE_VIEW3D) {
if (tref->mode == CTX_MODE_PARTICLE) {
toolsystem_brush_activate_from_toolref_for_object_particle(C, workspace, tref);
}
else {
toolsystem_brush_activate_from_toolref_for_object_paint(C, workspace, tref);
}
}
}
/** \} */
static void toolsystem_ref_link(const bContext *C, WorkSpace *workspace, bToolRef *tref)
{
bToolRef_Runtime *tref_rt = tref->runtime;
@@ -157,25 +410,7 @@ static void toolsystem_ref_link(const bContext *C, WorkSpace *workspace, bToolRe
}
if (tref_rt->flag & TOOLREF_FLAG_USE_BRUSHES) {
Main *bmain = CTX_data_main(C);
if ((tref->space_type == SPACE_VIEW3D) && (tref->mode == CTX_MODE_PARTICLE) &&
tref_rt->data_block[0])
{
const EnumPropertyItem *items = rna_enum_particle_edit_hair_brush_items;
const int i = RNA_enum_from_identifier(items, tref_rt->data_block);
if (i != -1) {
const int value = items[i].value;
wmWindowManager *wm = static_cast<wmWindowManager *>(bmain->wm.first);
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
if (workspace == WM_window_get_active_workspace(win)) {
Scene *scene = WM_window_get_active_scene(win);
ToolSettings *ts = scene->toolsettings;
ts->particle.brushtype = value;
}
}
}
}
toolsystem_brush_activate_from_toolref(C, workspace, tref);
}
}