Fix: Image Editor can have no default active brush

Prior to this commit, determining the default brush asset used for a
particular mode was based on the object's mode. This is slightly
incorrect for the Image Editor, since it may be in Paint Mode no matter
the underlying object type. To fix this, use the runtime `PaintMode`
enum for determining these default values instead of `eObjectType` and
store the `PaintMode` on `PaintRuntime` for easy access.

Additionally, inside the toolsystem, prevent accidentally unsetting
the default brush by checking for the presence of the asset when
loading an `AssetWeakReference`.

Pull Request: https://projects.blender.org/blender/blender/pulls/144765
This commit is contained in:
Sean Kim
2025-08-19 22:44:24 +02:00
committed by Sean Kim
parent c7e2368d6c
commit 6e82e403df
19 changed files with 101 additions and 65 deletions

View File

@@ -65,6 +65,7 @@ struct MultiresModifierData;
struct Object;
struct Paint;
struct PaintCurve;
enum class PaintMode : int8_t;
struct PaintModeSettings;
struct Palette;
struct PaletteColor;
@@ -90,27 +91,6 @@ extern const uchar PAINT_CURSOR_SCULPT_CURVES[3];
extern const uchar PAINT_CURSOR_PAINT_GREASE_PENCIL[3];
extern const uchar PAINT_CURSOR_SCULPT_GREASE_PENCIL[3];
enum class PaintMode : int8_t {
Sculpt = 0,
/** Vertex color. */
Vertex = 1,
Weight = 2,
/** 3D view (projection painting). */
Texture3D = 3,
/** Image space (2D painting). */
Texture2D = 4,
GPencil = 6,
/* Grease Pencil Vertex Paint */
VertexGPencil = 7,
SculptGPencil = 8,
WeightGPencil = 9,
/** Curves. */
SculptCurves = 10,
/** Keep last. */
Invalid = 11,
};
/* overlay invalidation */
enum ePaintOverlayControlFlags {
PAINT_OVERLAY_INVALID_TEXTURE_PRIMARY = 1,
@@ -206,7 +186,7 @@ bool BKE_paint_use_unified_color(const Paint *paint);
Brush *BKE_paint_brush(Paint *paint);
const Brush *BKE_paint_brush_for_read(const Paint *paint);
Brush *BKE_paint_brush_from_essentials(Main *bmain, eObjectMode ob_mode, const char *name);
Brush *BKE_paint_brush_from_essentials(Main *bmain, PaintMode paint_mode, const char *name);
/**
* Check if brush \a brush may be set/activated for \a paint. Passing null for \a brush will return
@@ -231,6 +211,8 @@ 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.
*
* \return False if unable to set the brush to the provided asset reference. True otherwise.
*/
bool BKE_paint_brush_set(Main *bmain,
Paint *paint,
@@ -242,7 +224,7 @@ void BKE_paint_previous_asset_reference_set(Paint *paint,
void BKE_paint_previous_asset_reference_clear(Paint *paint);
std::optional<AssetWeakReference> BKE_paint_brush_type_default_reference(
eObjectMode ob_mode, std::optional<int> brush_type);
PaintMode paint_mode, std::optional<int> brush_type);
void BKE_paint_brushes_set_default_references(ToolSettings *ts);
/**
* Make sure the active brush asset is available as active brush, importing it if necessary. If
@@ -263,7 +245,7 @@ Brush *BKE_paint_eraser_brush(Paint *paint);
const Brush *BKE_paint_eraser_brush_for_read(const Paint *paint);
bool BKE_paint_eraser_brush_set(Paint *paint, Brush *brush);
Brush *BKE_paint_eraser_brush_from_essentials(Main *bmain, eObjectMode ob_mode, const char *name);
Brush *BKE_paint_eraser_brush_from_essentials(Main *bmain, PaintMode paint_mode, const char *name);
bool BKE_paint_eraser_brush_set_default(Main *bmain, Paint *paint);
bool BKE_paint_eraser_brush_set_essentials(Main *bmain, Paint *paint, const char *name);

View File

@@ -18,11 +18,33 @@ class ColorSpace;
}
} // namespace blender
struct AssetWeakReference;
enum class PaintMode : int8_t {
Sculpt = 0,
/** Vertex color. */
Vertex = 1,
Weight = 2,
/** 3D view (projection painting). */
Texture3D = 3,
/** Image space (2D painting). */
Texture2D = 4,
GPencil = 6,
/* Grease Pencil Vertex Paint */
VertexGPencil = 7,
SculptGPencil = 8,
WeightGPencil = 9,
/** Curves. */
SculptCurves = 10,
/** Keep last. */
/* TODO: Shift the ordering so that invalid is first so that zero-initialization makes sense. */
Invalid = 11,
};
namespace blender::bke {
struct PaintRuntime : NonCopyable, NonMovable {
bool initialized = false;
uint16_t ob_mode = 0;
PaintMode paint_mode = PaintMode::Invalid;
AssetWeakReference *previous_active_brush_reference = nullptr;
blender::float2 last_rake = float2(0.0f, 0.0f);

View File

@@ -678,7 +678,7 @@ bool BKE_paint_brush_set(Main *bmain,
/* Ensure we have a brush with appropriate mode to assign.
* Could happen if contents of asset blend were manually changed. */
if (!BKE_paint_brush_poll(paint, brush)) {
if (brush == nullptr || !BKE_paint_brush_poll(paint, brush)) {
return false;
}
@@ -715,26 +715,28 @@ bool BKE_paint_brush_set(Paint *paint, Brush *brush)
return true;
}
static const char *paint_brush_essentials_asset_file_name_from_obmode(const eObjectMode ob_mode)
static const char *paint_brush_essentials_asset_file_name_from_paint_mode(
const PaintMode paint_mode)
{
switch (ob_mode) {
case OB_MODE_SCULPT:
switch (paint_mode) {
case PaintMode::Sculpt:
return "essentials_brushes-mesh_sculpt.blend";
case OB_MODE_VERTEX_PAINT:
case PaintMode::Vertex:
return "essentials_brushes-mesh_vertex.blend";
case OB_MODE_WEIGHT_PAINT:
case PaintMode::Weight:
return "essentials_brushes-mesh_weight.blend";
case OB_MODE_TEXTURE_PAINT:
case PaintMode::Texture2D:
case PaintMode::Texture3D:
return "essentials_brushes-mesh_texture.blend";
case OB_MODE_PAINT_GREASE_PENCIL:
case PaintMode::GPencil:
return "essentials_brushes-gp_draw.blend";
case OB_MODE_SCULPT_GREASE_PENCIL:
case PaintMode::SculptGPencil:
return "essentials_brushes-gp_sculpt.blend";
case OB_MODE_WEIGHT_GREASE_PENCIL:
case PaintMode::WeightGPencil:
return "essentials_brushes-gp_weight.blend";
case OB_MODE_VERTEX_GREASE_PENCIL:
case PaintMode::VertexGPencil:
return "essentials_brushes-gp_vertex.blend";
case OB_MODE_SCULPT_CURVES:
case PaintMode::SculptCurves:
return "essentials_brushes-curve_sculpt.blend";
default:
return nullptr;
@@ -742,9 +744,10 @@ static const char *paint_brush_essentials_asset_file_name_from_obmode(const eObj
}
static AssetWeakReference *paint_brush_asset_reference_ptr_from_essentials(
const char *name, const eObjectMode ob_mode)
const char *name, const PaintMode paint_mode)
{
const char *essentials_file_name = paint_brush_essentials_asset_file_name_from_obmode(ob_mode);
const char *essentials_file_name = paint_brush_essentials_asset_file_name_from_paint_mode(
paint_mode);
if (!essentials_file_name) {
return nullptr;
}
@@ -758,9 +761,10 @@ static AssetWeakReference *paint_brush_asset_reference_ptr_from_essentials(
}
static std::optional<AssetWeakReference> paint_brush_asset_reference_from_essentials(
const char *name, const eObjectMode ob_mode)
const char *name, const PaintMode paint_mode)
{
const char *essentials_file_name = paint_brush_essentials_asset_file_name_from_obmode(ob_mode);
const char *essentials_file_name = paint_brush_essentials_asset_file_name_from_paint_mode(
paint_mode);
if (!essentials_file_name) {
return {};
}
@@ -773,10 +777,10 @@ static std::optional<AssetWeakReference> paint_brush_asset_reference_from_essent
return weak_ref;
}
Brush *BKE_paint_brush_from_essentials(Main *bmain, const eObjectMode ob_mode, const char *name)
Brush *BKE_paint_brush_from_essentials(Main *bmain, const PaintMode paint_mode, const char *name)
{
std::optional<AssetWeakReference> weak_ref = paint_brush_asset_reference_from_essentials(
name, ob_mode);
name, paint_mode);
if (!weak_ref) {
return nullptr;
}
@@ -792,7 +796,7 @@ static void paint_brush_set_essentials_reference(Paint *paint, const char *name)
BLI_assert(paint->runtime->initialized);
paint->brush_asset_reference = paint_brush_asset_reference_ptr_from_essentials(
name, eObjectMode(paint->runtime->ob_mode));
name, paint->runtime->paint_mode);
paint->brush = nullptr;
}
@@ -803,12 +807,12 @@ static void paint_eraser_brush_set_essentials_reference(Paint *paint, const char
BLI_assert(paint->runtime->initialized);
paint->eraser_brush_asset_reference = paint_brush_asset_reference_ptr_from_essentials(
name, eObjectMode(paint->runtime->ob_mode));
name, paint->runtime->paint_mode);
paint->eraser_brush = nullptr;
}
static void paint_brush_default_essentials_name_get(
eObjectMode ob_mode,
const PaintMode paint_mode,
std::optional<int> brush_type,
blender::StringRefNull *r_name,
blender::StringRefNull *r_eraser_name = nullptr)
@@ -816,8 +820,8 @@ static void paint_brush_default_essentials_name_get(
const char *name = "";
const char *eraser_name = "";
switch (ob_mode) {
case OB_MODE_SCULPT:
switch (paint_mode) {
case PaintMode::Sculpt:
name = "Draw";
if (brush_type) {
switch (eBrushSculptType(*brush_type)) {
@@ -844,7 +848,7 @@ static void paint_brush_default_essentials_name_get(
}
}
break;
case OB_MODE_VERTEX_PAINT:
case PaintMode::Vertex:
name = "Paint Hard";
if (brush_type) {
switch (eBrushVertexPaintType(*brush_type)) {
@@ -863,7 +867,7 @@ static void paint_brush_default_essentials_name_get(
}
}
break;
case OB_MODE_WEIGHT_PAINT:
case PaintMode::Weight:
name = "Paint";
if (brush_type) {
switch (eBrushWeightPaintType(*brush_type)) {
@@ -882,7 +886,8 @@ static void paint_brush_default_essentials_name_get(
}
}
break;
case OB_MODE_TEXTURE_PAINT:
case PaintMode::Texture2D:
case PaintMode::Texture3D:
name = "Paint Hard";
if (brush_type) {
switch (eBrushImagePaintType(*brush_type)) {
@@ -906,7 +911,7 @@ static void paint_brush_default_essentials_name_get(
}
}
break;
case OB_MODE_SCULPT_CURVES:
case PaintMode::SculptCurves:
name = "Comb";
if (brush_type) {
switch (eBrushCurvesSculptType(*brush_type)) {
@@ -927,7 +932,7 @@ static void paint_brush_default_essentials_name_get(
}
}
break;
case OB_MODE_PAINT_GREASE_PENCIL:
case PaintMode::GPencil:
name = "Pencil";
/* Different default brush for some brush types. */
if (brush_type) {
@@ -946,7 +951,7 @@ static void paint_brush_default_essentials_name_get(
}
eraser_name = "Eraser Soft";
break;
case OB_MODE_VERTEX_GREASE_PENCIL:
case PaintMode::VertexGPencil:
name = "Paint";
if (brush_type) {
switch (eBrushGPVertexType(*brush_type)) {
@@ -972,7 +977,7 @@ static void paint_brush_default_essentials_name_get(
}
}
break;
case OB_MODE_SCULPT_GREASE_PENCIL:
case PaintMode::SculptGPencil:
name = "Smooth";
if (brush_type) {
switch (eBrushGPSculptType(*brush_type)) {
@@ -984,7 +989,7 @@ static void paint_brush_default_essentials_name_get(
}
}
break;
case OB_MODE_WEIGHT_GREASE_PENCIL:
case PaintMode::WeightGPencil:
name = "Paint";
if (brush_type) {
switch (eBrushGPWeightType(*brush_type)) {
@@ -1015,16 +1020,16 @@ static void paint_brush_default_essentials_name_get(
}
std::optional<AssetWeakReference> BKE_paint_brush_type_default_reference(
eObjectMode ob_mode, std::optional<int> brush_type)
const PaintMode paint_mode, std::optional<int> brush_type)
{
blender::StringRefNull name;
paint_brush_default_essentials_name_get(ob_mode, brush_type, &name, nullptr);
paint_brush_default_essentials_name_get(paint_mode, brush_type, &name, nullptr);
if (name.is_empty()) {
return {};
}
return paint_brush_asset_reference_from_essentials(name.c_str(), ob_mode);
return paint_brush_asset_reference_from_essentials(name.c_str(), paint_mode);
}
static void paint_brush_set_default_reference(Paint *paint,
@@ -1041,7 +1046,7 @@ static void paint_brush_set_default_reference(Paint *paint,
blender::StringRefNull eraser_name;
paint_brush_default_essentials_name_get(
eObjectMode(paint->runtime->ob_mode), std::nullopt, &name, &eraser_name);
paint->runtime->paint_mode, std::nullopt, &name, &eraser_name);
if (do_regular && !name.is_empty()) {
paint_brush_set_essentials_reference(paint, name.c_str());
@@ -1188,10 +1193,12 @@ bool BKE_paint_eraser_brush_set(Paint *paint, Brush *brush)
return true;
}
Brush *BKE_paint_eraser_brush_from_essentials(Main *bmain, eObjectMode ob_mode, const char *name)
Brush *BKE_paint_eraser_brush_from_essentials(Main *bmain,
const PaintMode paint_mode,
const char *name)
{
std::optional<AssetWeakReference> weak_ref = paint_brush_asset_reference_from_essentials(
name, ob_mode);
name, paint_mode);
if (!weak_ref) {
return {};
}
@@ -1220,30 +1227,41 @@ static void paint_runtime_init(const ToolSettings *ts, Paint *paint)
if (paint == &ts->imapaint.paint) {
paint->runtime->ob_mode = OB_MODE_TEXTURE_PAINT;
/* Note: This is an odd case where 3D Texture paint and Image Paint share the same struct.
* It would be equally valid to assign PaintMode::Texture2D to this. */
paint->runtime->paint_mode = PaintMode::Texture3D;
}
else if (ts->sculpt && paint == &ts->sculpt->paint) {
paint->runtime->ob_mode = OB_MODE_SCULPT;
paint->runtime->paint_mode = PaintMode::Sculpt;
}
else if (ts->vpaint && paint == &ts->vpaint->paint) {
paint->runtime->ob_mode = OB_MODE_VERTEX_PAINT;
paint->runtime->paint_mode = PaintMode::Vertex;
}
else if (ts->wpaint && paint == &ts->wpaint->paint) {
paint->runtime->ob_mode = OB_MODE_WEIGHT_PAINT;
paint->runtime->paint_mode = PaintMode::Weight;
}
else if (ts->gp_paint && paint == &ts->gp_paint->paint) {
paint->runtime->ob_mode = OB_MODE_PAINT_GREASE_PENCIL;
paint->runtime->paint_mode = PaintMode::GPencil;
}
else if (ts->gp_vertexpaint && paint == &ts->gp_vertexpaint->paint) {
paint->runtime->ob_mode = OB_MODE_VERTEX_GREASE_PENCIL;
paint->runtime->paint_mode = PaintMode::VertexGPencil;
}
else if (ts->gp_sculptpaint && paint == &ts->gp_sculptpaint->paint) {
paint->runtime->ob_mode = OB_MODE_SCULPT_GREASE_PENCIL;
paint->runtime->paint_mode = PaintMode::SculptGPencil;
}
else if (ts->gp_weightpaint && paint == &ts->gp_weightpaint->paint) {
paint->runtime->ob_mode = OB_MODE_WEIGHT_GREASE_PENCIL;
paint->runtime->paint_mode = PaintMode::WeightGPencil;
}
else if (ts->curves_sculpt && paint == &ts->curves_sculpt->paint) {
paint->runtime->ob_mode = OB_MODE_SCULPT_CURVES;
paint->runtime->paint_mode = PaintMode::SculptCurves;
}
else {
BLI_assert_unreachable();

View File

@@ -85,6 +85,7 @@
#include "BKE_node.hh"
#include "BKE_node_legacy_types.hh"
#include "BKE_paint.hh"
#include "BKE_paint_types.hh"
#include "BKE_pointcache.h"
#include "BKE_report.hh"
#include "BKE_rigidbody.h"

View File

@@ -61,6 +61,7 @@
#include "BKE_node_runtime.hh"
#include "BKE_node_tree_update.hh"
#include "BKE_paint.hh"
#include "BKE_paint_types.hh"
#include "BKE_screen.hh"
#include "BKE_workspace.hh"

View File

@@ -13,6 +13,7 @@
#include "BKE_global.hh"
#include "BKE_gpencil_legacy.h"
#include "BKE_paint.hh"
#include "BKE_paint_types.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"

View File

@@ -43,6 +43,7 @@
#include "BKE_curveprofile.h"
#include "BKE_movieclip.h"
#include "BKE_paint.hh"
#include "BKE_paint_types.hh"
#include "BKE_report.hh"
#include "BKE_scene.hh"
#include "BKE_screen.hh"

View File

@@ -19,6 +19,7 @@
#include "BKE_modifier.hh"
#include "BKE_object.hh"
#include "BKE_paint.hh"
#include "BKE_paint_types.hh"
#include "BLT_translation.hh"

View File

@@ -15,6 +15,7 @@
#include "BKE_material.hh"
#include "BKE_object_deform.h"
#include "BKE_paint.hh"
#include "BKE_paint_types.hh"
#include "BKE_report.hh"
#include "BKE_screen.hh"

View File

@@ -9,6 +9,7 @@
#include "BKE_curves.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_paint.hh"
#include "BKE_paint_types.hh"
#include "DNA_brush_enums.h"
#include "DNA_brush_types.h"
@@ -116,7 +117,7 @@ void SmoothOperation::on_stroke_extended(const bContext &C, const InputSample &e
const Brush &brush = [&]() -> const Brush & {
if (temp_smooth_) {
const Brush *brush = BKE_paint_brush_from_essentials(
CTX_data_main(&C), OB_MODE_SCULPT_GREASE_PENCIL, "Smooth");
CTX_data_main(&C), PaintMode::SculptGPencil, "Smooth");
BLI_assert(brush != nullptr);
return *brush;
}

View File

@@ -25,6 +25,7 @@
#include "BKE_context.hh"
#include "BKE_lib_id.hh"
#include "BKE_paint.hh"
#include "BKE_paint_types.hh"
#include "ED_paint.hh"
#include "ED_view3d.hh"

View File

@@ -29,6 +29,7 @@
#include "BKE_library.hh"
#include "BKE_main.hh"
#include "BKE_paint.hh"
#include "BKE_paint_types.hh"
#include "BKE_report.hh"
#include "ED_image.hh"

View File

@@ -37,6 +37,7 @@
#include "BKE_mesh_sample.hh"
#include "BKE_object.hh"
#include "BKE_paint.hh"
#include "BKE_paint_types.hh"
#include "BKE_report.hh"
#include "DEG_depsgraph_query.hh"

View File

@@ -55,6 +55,7 @@
#include "BKE_multires.hh"
#include "BKE_object.hh"
#include "BKE_paint.hh"
#include "BKE_paint_types.hh"
#include "BKE_scene.hh"
#include "BKE_subdiv_ccg.hh"
#include "BKE_subsurf.hh"

View File

@@ -42,6 +42,7 @@
#include "BKE_context.hh"
#include "BKE_image.hh"
#include "BKE_paint.hh"
#include "BKE_paint_types.hh"
#include "BKE_undo_system.hh"
#include "DEG_depsgraph.hh"

View File

@@ -12,6 +12,7 @@
#include "BKE_library.hh"
#include "BKE_object.hh"
#include "BKE_paint.hh"
#include "BKE_paint_types.hh"
#include "BKE_scene.hh"
#include "BKE_screen.hh"

View File

@@ -389,6 +389,7 @@ static EnumPropertyItem rna_enum_gpencil_brush_modes_items[] = {
# include "BKE_layer.hh"
# include "BKE_material.hh"
# include "BKE_paint.hh"
# include "BKE_paint_types.hh"
# include "BKE_preview_image.hh"
# include "WM_api.hh"

View File

@@ -24,6 +24,7 @@
# include "BKE_global.hh"
# include "BKE_paint.hh"
# include "BKE_paint_types.hh"
# include "BKE_report.hh"
# include "BKE_workspace.hh"

View File

@@ -393,7 +393,7 @@ static void toolsystem_brush_activate_from_toolref_for_object_paint(Main *bmain,
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),
return BKE_paint_brush_type_default_reference(paint->runtime->paint_mode,
tref_rt->brush_type);
}();
@@ -413,8 +413,7 @@ static void toolsystem_brush_activate_from_toolref_for_object_paint(Main *bmain,
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);
return BKE_paint_brush_type_default_reference(paint->runtime->paint_mode, std::nullopt);
}();
if (main_brush_asset_reference) {