Refactor: Paint: Split stroke brush toggles from inversion

Historically, the `BrushStrokeMode` enum has been used to indicate a
singular temporary property about the stroke. Toggles for using various
different brush types (smoothing, mask, eraser) were combined with the
generic idea of inverting a brush stroke.

This leads to limitations, such as being unable to specify multiple
properties at once (e.g. inversion & smoothing), as well as generally
conflating two separate ideas.

This commit makes the following changes to better represent how these
modes are conceptually used in the paint systems:
* Use `enum class` for `BrushStrokeMode` so it cannot be confused with
  the new `TemporaryBrushToggleType` `enum`.
* The existing `BrushStrokeMode` only indicates whether a brush stroke
  is inverted or not.
* The new `TemporaryBrushToggleType` represents the brush or brush type
  that is being used during the stroke.

Note that this still does not allow us to decouple the idea of
temporarily toggling a brush from the actual stroke itself, though this
separation of concerns helps to move us in that direction.

Pull Request: https://projects.blender.org/blender/blender/pulls/153056
This commit is contained in:
Sean Kim 2026-01-22 22:06:25 +01:00 • committed by Sean Kim
parent 0b422e45b1
commit c946d9355b
20 changed files with 178 additions and 114 deletions

View file

@ -4053,11 +4053,11 @@ def km_grease_pencil_brush_stroke(_params):
items.extend([
("grease_pencil.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
("grease_pencil.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
{"properties": [("mode", 'ERASE')]}),
{"properties": [("brush_toggle", 'ERASE')]}),
("grease_pencil.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
{"properties": [("mode", 'SMOOTH')]}),
{"properties": [("brush_toggle", 'SMOOTH')]}),
("grease_pencil.brush_stroke", {"type": 'ERASER', "value": 'PRESS'},
{"properties": [("mode", 'ERASE')]}),
{"properties": [("brush_toggle", 'ERASE')]}),
# Increase/Decrease brush size
("brush.scale_size", {"type": 'LEFT_BRACKET', "value": 'PRESS', "repeat": True},
{"properties": [("scalar", 0.9)]}),
@ -4213,7 +4213,7 @@ def km_grease_pencil_sculpt_mode(params):
("grease_pencil.sculpt_paint", {"type": 'LEFTMOUSE', "value": 'PRESS',
"ctrl": True}, {"properties": [("mode", 'INVERT')]}),
("grease_pencil.sculpt_paint", {"type": 'LEFTMOUSE', "value": 'PRESS',
"shift": True}, {"properties": [("mode", 'SMOOTH')]}),
"shift": True}, {"properties": [("brush_toggle", 'SMOOTH')]}),
# Selection mode
("wm.context_toggle", {"type": 'ONE', "value": 'PRESS'},
{"properties": [("data_path", "scene.tool_settings.use_gpencil_select_mask_point")]}),
@ -4982,7 +4982,7 @@ def km_image_paint(params):
("paint.image_paint", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
{"properties": [("mode", 'INVERT')]}),
("paint.image_paint", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
{"properties": [("mode", 'SMOOTH')]}),
{"properties": [("brush_toggle", 'SMOOTH')]}),
("paint.brush_colors_flip", {"type": 'X', "value": 'PRESS'}, None),
("paint.grab_clone", {"type": 'RIGHTMOUSE', "value": 'PRESS'}, None),
("paint.sample_color",
@ -5038,7 +5038,7 @@ def km_vertex_paint(params):
("paint.vertex_paint", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
{"properties": [("mode", 'INVERT')]}),
("paint.vertex_paint", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
{"properties": [("mode", 'SMOOTH')]}),
{"properties": [("brush_toggle", 'SMOOTH')]}),
("paint.brush_colors_flip", {"type": 'X', "value": 'PRESS'}, None),
("paint.sample_color", {"type": 'X', "value": 'PRESS', "shift": True}, {"properties": [("merged", False)]}),
("paint.sample_color",
@ -5100,7 +5100,7 @@ def km_weight_paint(params):
("paint.weight_paint", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
{"properties": [("mode", 'INVERT')]}),
("paint.weight_paint", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
{"properties": [("mode", 'SMOOTH')]}),
{"properties": [("brush_toggle", 'SMOOTH')]}),
("paint.weight_sample", {"type": 'X', "value": 'PRESS', "shift": True}, None),
("paint.weight_sample_group", {"type": 'X', "value": 'PRESS', "ctrl": True, "shift": True}, None),
("paint.weight_gradient", {"type": 'A', "value": 'PRESS', "shift": True, "alt": True},
@ -5226,9 +5226,9 @@ def km_sculpt(params):
("sculpt.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
{"properties": [("mode", 'INVERT')]}),
("sculpt.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
{"properties": [("mode", 'SMOOTH')]}),
{"properties": [("brush_toggle", 'SMOOTH')]}),
("sculpt.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "alt": True},
{"properties": [("mode", 'MASK')]}),
{"properties": [("brush_toggle", 'MASK')]}),
# Expand
("sculpt.expand", {"type": 'A', "value": 'PRESS', "shift": True},
{"properties": [
@ -5429,7 +5429,7 @@ def km_sculpt_curves(params):
("sculpt_curves.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
{"properties": [("mode", 'INVERT')]}),
("sculpt_curves.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
{"properties": [("mode", 'SMOOTH')]}),
{"properties": [("brush_toggle", 'SMOOTH')]}),
("curves.set_selection_domain", {"type": 'ONE', "value": 'PRESS'}, {"properties": [("domain", 'POINT')]}),
("curves.set_selection_domain", {"type": 'TWO', "value": 'PRESS'}, {"properties": [("domain", 'CURVE')]}),
*_template_paint_radial_control("curves_sculpt"),

View file

@ -2814,7 +2814,7 @@ def km_vertex_paint(params):
("paint.vertex_paint", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
{"properties": [("mode", 'INVERT')]}),
("paint.vertex_paint", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
{"properties": [("mode", 'SMOOTH')]}),
{"properties": [("brush_toggle", 'SMOOTH')]}),
# Colors
("paint.sample_color", {"type": 'I', "value": 'PRESS'}, {"properties": [("merged", False)]}),
("paint.sample_color", {"type": 'I', "value": 'PRESS', "shift": True}, {"properties": [("merged", True)]}),
@ -2873,7 +2873,7 @@ def km_weight_paint(params):
("paint.weight_paint", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
{"properties": [("mode", 'INVERT')]}),
("paint.weight_paint", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
{"properties": [("mode", 'SMOOTH')]}),
{"properties": [("brush_toggle", 'SMOOTH')]}),
# Weight
("paint.weight_sample", {"type": 'I', "value": 'PRESS'}, None),
("paint.weight_sample_group", {"type": 'I', "value": 'PRESS', "alt": True}, None),
@ -2930,9 +2930,9 @@ def km_sculpt(params):
("sculpt.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
{"properties": [("mode", 'INVERT')]}),
("sculpt.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
{"properties": [("mode", 'SMOOTH')]}),
{"properties": [("brush_toggle", 'SMOOTH')]}),
("sculpt.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "alt": True},
{"properties": [("mode", 'MASK')]}),
{"properties": [("brush_toggle", 'MASK')]}),
# Expand
("sculpt.expand", {"type": 'A', "value": 'PRESS', "shift": True},
{"properties": [
@ -3426,7 +3426,7 @@ def km_sculpt_curves(params):
("sculpt_curves.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
{"properties": [("mode", 'INVERT')]}),
("sculpt_curves.brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True},
{"properties": [("mode", 'SMOOTH')]}),
{"properties": [("brush_toggle", 'SMOOTH')]}),
# Selection modes
("curves.set_selection_domain", {"type": 'ONE', "value": 'PRESS'}, {"properties": [("domain", 'POINT')]}),
("curves.set_selection_domain", {"type": 'TWO', "value": 'PRESS'}, {"properties": [("domain", 'CURVE')]}),

View file

@ -805,7 +805,7 @@ static bool use_add_density_mode(const BrushStrokeMode brush_mode,
const eBrushCurvesSculptDensityMode density_mode = eBrushCurvesSculptDensityMode(
brush.curves_sculpt_settings->density_mode);
const bool use_invert = brush_mode == BRUSH_STROKE_INVERT;
const bool use_invert = brush_mode == BrushStrokeMode::Invert;
if (density_mode == BRUSH_CURVES_SCULPT_DENSITY_MODE_ADD) {
return !use_invert;

View file

@ -502,7 +502,8 @@ std::unique_ptr<CurvesSculptStrokeOperation> new_grow_shrink_operation(
const Brush &brush = *BKE_paint_brush_for_read(&scene.toolsettings->curves_sculpt->paint);
const bool use_scale_uniform = brush.curves_sculpt_settings->flag &
BRUSH_CURVES_SCULPT_FLAG_SCALE_UNIFORM;
const bool use_grow = (brush_mode == BRUSH_STROKE_INVERT) == ((brush.flag & BRUSH_DIR_IN) != 0);
const bool use_grow = (brush_mode == BrushStrokeMode::Invert) ==
((brush.flag & BRUSH_DIR_IN) != 0);
if (use_grow) {
if (use_scale_uniform) {

View file

@ -73,7 +73,7 @@ std::unique_ptr<CurvesSculptStrokeOperation> new_snake_hook_operation();
std::unique_ptr<CurvesSculptStrokeOperation> new_grow_shrink_operation(BrushStrokeMode brush_mode,
const Scene &scene);
std::unique_ptr<CurvesSculptStrokeOperation> new_selection_paint_operation(
BrushStrokeMode brush_mode, const Scene &scene);
BrushStrokeMode brush_mode, BrushSwitchMode brush_switch_mode, const Scene &scene);
std::unique_ptr<CurvesSculptStrokeOperation> new_pinch_operation(BrushStrokeMode brush_mode,
const Scene &scene);
std::unique_ptr<CurvesSculptStrokeOperation> new_smooth_operation();

View file

@ -130,15 +130,16 @@ static std::unique_ptr<CurvesSculptStrokeOperation> start_brush_operation(
const Object &object,
const StrokeExtension &stroke_start)
{
const BrushStrokeMode mode = BrushStrokeMode(RNA_enum_get(op.ptr, "mode"));
const auto mode = BrushStrokeMode(RNA_enum_get(op.ptr, "mode"));
const auto brush_switch_mode = BrushSwitchMode(RNA_enum_get(op.ptr, "brush_toggle"));
const CurvesSculpt &curves_sculpt = *scene.toolsettings->curves_sculpt;
const Brush &brush = *BKE_paint_brush_for_read(&curves_sculpt.paint);
const eBrushCurvesSculptType brush_type = eBrushCurvesSculptType(brush.curves_sculpt_brush_type);
if (mode == BRUSH_STROKE_SMOOTH) {
if (brush_switch_mode == BrushSwitchMode::Smooth) {
if (brush_type == CURVES_SCULPT_BRUSH_TYPE_SELECTION_PAINT) {
/* The selection brush uses the BRUSH_STROKE_SMOOTH mode to indicate that the current
* selection should be added to. It should not toggle to the smooth brush itself. */
/* The selection brush uses the BrushSwitchMode::Smooth mode to indicate that the
* current selection should be added to. It should not toggle to the smooth brush itself. */
}
else {
return new_smooth_operation();
@ -157,7 +158,7 @@ static std::unique_ptr<CurvesSculptStrokeOperation> start_brush_operation(
case CURVES_SCULPT_BRUSH_TYPE_GROW_SHRINK:
return new_grow_shrink_operation(mode, scene);
case CURVES_SCULPT_BRUSH_TYPE_SELECTION_PAINT:
return new_selection_paint_operation(mode, scene);
return new_selection_paint_operation(mode, brush_switch_mode, scene);
case CURVES_SCULPT_BRUSH_TYPE_PINCH:
return new_pinch_operation(mode, scene);
case CURVES_SCULPT_BRUSH_TYPE_SMOOTH:

View file

@ -290,7 +290,7 @@ std::unique_ptr<CurvesSculptStrokeOperation> new_pinch_operation(const BrushStro
{
const Brush &brush = *BKE_paint_brush_for_read(&scene.toolsettings->curves_sculpt->paint);
const bool invert_pinch = (brush_mode == BRUSH_STROKE_INVERT) !=
const bool invert_pinch = (brush_mode == BrushStrokeMode::Invert) !=
((brush.flag & BRUSH_DIR_IN) != 0);
return std::make_unique<PinchOperation>(invert_pinch);
}

View file

@ -391,12 +391,12 @@ void SelectionPaintOperation::on_stroke_extended(const PaintStroke &stroke,
}
std::unique_ptr<CurvesSculptStrokeOperation> new_selection_paint_operation(
const BrushStrokeMode brush_mode, const Scene &scene)
const BrushStrokeMode brush_mode, const BrushSwitchMode brush_switch_mode, const Scene &scene)
{
Brush &brush = *BKE_paint_brush(&scene.toolsettings->curves_sculpt->paint);
const bool use_select = ELEM(brush_mode, BRUSH_STROKE_INVERT) ==
const bool use_select = ELEM(brush_mode, BrushStrokeMode::Invert) ==
((brush.flag & BRUSH_DIR_IN) != 0);
const bool clear_selection = use_select && brush_mode != BRUSH_STROKE_SMOOTH;
const bool clear_selection = use_select && brush_switch_mode != BrushSwitchMode::Smooth;
return std::make_unique<SelectionPaintOperation>(use_select, clear_selection);
}

View file

@ -110,11 +110,12 @@ static std::unique_ptr<GreasePencilStrokeOperation> get_stroke_operation(bContex
const Paint *paint = BKE_paint_get_active_from_context(&C);
const Brush &brush = *BKE_paint_brush_for_read(paint);
const PaintMode mode = BKE_paintmode_get_active_from_context(&C);
const BrushStrokeMode stroke_mode = BrushStrokeMode(RNA_enum_get(op->ptr, "mode"));
const auto stroke_mode = BrushStrokeMode(RNA_enum_get(op->ptr, "mode"));
const auto brush_switch_mode = BrushSwitchMode(RNA_enum_get(op->ptr, "brush_toggle"));
if (mode == PaintMode::GPencil) {
if (eBrushGPaintType(brush.gpencil_brush_type) == GPAINT_BRUSH_TYPE_DRAW &&
stroke_mode == BRUSH_STROKE_ERASE)
brush_switch_mode == BrushSwitchMode::Erase)
{
/* Special case: We're using the draw tool but with the eraser mode, so create an erase
* operation. */
@ -130,12 +131,12 @@ static std::unique_ptr<GreasePencilStrokeOperation> get_stroke_operation(bContex
/* Fill tool keymap uses the paint operator to draw fill guides. */
return greasepencil::new_paint_operation(/* do_fill_guides = */ true);
case GPAINT_BRUSH_TYPE_TINT:
return greasepencil::new_tint_operation(stroke_mode == BRUSH_STROKE_ERASE);
return greasepencil::new_tint_operation(brush_switch_mode == BrushSwitchMode::Erase);
}
}
else if (mode == PaintMode::SculptGPencil) {
if (stroke_mode == BRUSH_STROKE_SMOOTH) {
if (brush_switch_mode == BrushSwitchMode::Smooth) {
return greasepencil::new_smooth_operation(stroke_mode, true);
}
switch (eBrushGPSculptType(brush.gpencil_sculpt_brush_type)) {
@ -259,14 +260,14 @@ static wmOperatorStatus grease_pencil_brush_stroke_invoke(bContext *C,
const wmEvent *event)
{
if (event->tablet.active == EVT_TABLET_ERASER) {
RNA_enum_set(op->ptr, "mode", BRUSH_STROKE_ERASE);
RNA_enum_set(op->ptr, "brush_toggle", int(BrushSwitchMode::Erase));
}
const bool use_duplicate_previous_key = [&]() -> bool {
const Paint *paint = BKE_paint_get_active_from_context(C);
const Brush &brush = *BKE_paint_brush_for_read(paint);
const PaintMode mode = BKE_paintmode_get_active_from_context(C);
const BrushStrokeMode stroke_mode = BrushStrokeMode(RNA_enum_get(op->ptr, "mode"));
const auto brush_switch_mode = BrushSwitchMode(RNA_enum_get(op->ptr, "brush_toggle"));
if (mode == PaintMode::GPencil) {
/* For the eraser and tint tool, we don't want auto-key to create an empty keyframe, so we
@ -279,7 +280,7 @@ static wmOperatorStatus grease_pencil_brush_stroke_invoke(bContext *C,
}
/* Same for the temporary eraser when using the draw tool. */
if (eBrushGPaintType(brush.gpencil_brush_type) == GPAINT_BRUSH_TYPE_DRAW &&
stroke_mode == BRUSH_STROKE_ERASE)
brush_switch_mode == BrushSwitchMode::Erase)
{
return true;
}

View file

@ -209,7 +209,7 @@ bool is_brush_inverted(const Brush &brush, const BrushStrokeMode stroke_mode)
{
/* The basic setting is the brush's setting. During runtime, the user can hold down the Ctrl key
* to invert the basic behavior. */
return bool(brush.flag & BRUSH_DIR_IN) ^ (stroke_mode == BrushStrokeMode::BRUSH_STROKE_INVERT);
return bool(brush.flag & BRUSH_DIR_IN) ^ (stroke_mode == BrushStrokeMode::Invert);
}
DeltaProjectionFunc get_screen_projection_fn(const GreasePencilStrokeParams &params,

View file

@ -23,7 +23,7 @@ class DrawWeightPaintOperation : public WeightPaintOperation {
/* Get the add/subtract mode of the draw brush. */
this->invert_brush_weight = (this->brush->flag & BRUSH_DIR_IN) != 0;
if (this->stroke_mode == BRUSH_STROKE_INVERT) {
if (this->stroke_mode == BrushStrokeMode::Invert) {
this->invert_brush_weight = !this->invert_brush_weight;
}

View file

@ -1607,7 +1607,7 @@ void paint_2d_stroke(void *ps,
painter->firsttouch = false;
}
void *paint_2d_new_stroke(bContext *C, wmOperator *op, int mode)
void *paint_2d_new_stroke(bContext *C, wmOperator *op, const BrushStrokeMode mode)
{
Scene *scene = CTX_data_scene(C);
SpaceImage *sima = CTX_wm_space_image(C);
@ -1694,7 +1694,7 @@ void *paint_2d_new_stroke(bContext *C, wmOperator *op, int mode)
paint_brush_init_tex(s->brush);
/* create painter */
s->painter = brush_painter_2d_new(scene, paint, s->brush, mode == BRUSH_STROKE_INVERT);
s->painter = brush_painter_2d_new(scene, paint, s->brush, mode == BrushStrokeMode::Invert);
return s;
}

View file

@ -50,8 +50,12 @@ namespace ed::sculpt_paint::image::ops::paint {
class AbstractPaintMode {
public:
virtual ~AbstractPaintMode() = default;
virtual void *paint_new_stroke(
bContext *C, wmOperator *op, Object *ob, const float mouse[2], int mode) = 0;
virtual void *paint_new_stroke(bContext *C,
wmOperator *op,
Object *ob,
const float mouse[2],
BrushStrokeMode mode,
BrushSwitchMode brush_switch_mode) = 0;
virtual void paint_stroke(bContext *C,
void *stroke_handle,
float prev_mouse[2],
@ -81,8 +85,12 @@ class AbstractPaintMode {
class ImagePaintMode : public AbstractPaintMode {
public:
void *paint_new_stroke(
bContext *C, wmOperator *op, Object * /*ob*/, const float /*mouse*/[2], int mode) override
void *paint_new_stroke(bContext *C,
wmOperator *op,
Object * /*ob*/,
const float /*mouse*/[2],
const BrushStrokeMode mode,
const BrushSwitchMode /*brush_switch_mode*/) override
{
return paint_2d_new_stroke(C, op, mode);
}
@ -141,10 +149,14 @@ class ImagePaintMode : public AbstractPaintMode {
class ProjectionPaintMode : public AbstractPaintMode {
public:
void *paint_new_stroke(
bContext *C, wmOperator * /*op*/, Object *ob, const float mouse[2], int mode) override
void *paint_new_stroke(bContext *C,
wmOperator * /*op*/,
Object *ob,
const float mouse[2],
BrushStrokeMode mode,
BrushSwitchMode brush_switch_mode) override
{
return paint_proj_new_stroke(C, ob, mouse, mode);
return paint_proj_new_stroke(C, ob, mouse, mode, brush_switch_mode);
}
void paint_stroke(bContext *C,
@ -291,7 +303,8 @@ static std::unique_ptr<PaintOperation> texture_paint_init(bContext *C,
ToolSettings *settings = scene->toolsettings;
std::unique_ptr<PaintOperation> pop = std::make_unique<PaintOperation>();
Brush *brush = BKE_paint_brush(&settings->imapaint.paint);
int mode = RNA_enum_get(op->ptr, "mode");
auto mode = BrushStrokeMode(RNA_enum_get(op->ptr, "mode"));
auto brush_switch_mode = BrushSwitchMode(RNA_enum_get(op->ptr, "brush_toggle"));
pop->vc = ED_view3d_viewcontext_init(C, depsgraph);
copy_v2_v2(pop->prevmouse, mouse);
@ -315,7 +328,7 @@ static std::unique_ptr<PaintOperation> texture_paint_init(bContext *C,
pop->mode = MEM_new<ImagePaintMode>("ImagePaintMode");
}
pop->stroke_handle = pop->mode->paint_new_stroke(C, op, ob, mouse, mode);
pop->stroke_handle = pop->mode->paint_new_stroke(C, op, ob, mouse, mode, brush_switch_mode);
if (!pop->stroke_handle) {
return nullptr;
}
@ -549,7 +562,7 @@ static wmOperatorStatus paint_exec(bContext *C, wmOperator *op)
bool dummy;
float dummy_location[3];
int stroke_mode = RNA_enum_get(op->ptr, "mode");
BrushStrokeMode stroke_mode = BrushStrokeMode(RNA_enum_get(op->ptr, "mode"));
float zoomx;
float zoomy;
get_imapaint_zoom(C, &zoomx, &zoomy);

View file

@ -293,6 +293,7 @@ struct ProjPaintState {
short brush_type;
short blend;
BrushStrokeMode mode;
BrushSwitchMode brush_switch_mode;
float brush_size;
Object *ob;
@ -5085,7 +5086,7 @@ static void do_projectpaint_soften_f(ProjPaintState *ps,
if (LIKELY(accum_tot != 0)) {
mul_v4_fl(rgba, 1.0f / accum_tot);
if (ps->mode == BRUSH_STROKE_INVERT) {
if (ps->mode == BrushStrokeMode::Invert) {
/* subtract blurred image from normal image gives high pass filter */
sub_v3_v3v3(rgba, projPixel->pixel.f_pt, rgba);
@ -5148,7 +5149,7 @@ static void do_projectpaint_soften(ProjPaintState *ps,
mul_v4_fl(rgba, 1.0f / accum_tot);
if (ps->mode == BRUSH_STROKE_INVERT) {
if (ps->mode == BrushStrokeMode::Invert) {
float rgba_pixel[4];
straight_uchar_to_premul_float(rgba_pixel, projPixel->pixel.ch_pt);
@ -5905,7 +5906,7 @@ static void paint_proj_stroke_ps(const bContext * /*C*/,
paint_brush_color_get(paint,
brush,
ps_handle->initial_hsv_jitter,
ps->mode == BRUSH_STROKE_INVERT,
ps->mode == BrushStrokeMode::Invert,
distance,
pressure,
ps->paint_color_linear);
@ -5940,7 +5941,7 @@ static void paint_proj_stroke_ps(const bContext * /*C*/,
else if (ps->brush_type == IMAGE_PAINT_BRUSH_TYPE_MASK) {
ps->stencil_value = brush->weight;
if ((ps->mode == BRUSH_STROKE_INVERT) ^
if ((ps->mode == BrushStrokeMode::Invert) ^
((scene->toolsettings->imapaint.flag & IMAGEPAINT_PROJECT_LAYER_STENCIL_INV) != 0))
{
ps->stencil_value = 1.0f - ps->stencil_value;
@ -5997,28 +5998,33 @@ void paint_proj_stroke(const bContext *C,
}
/* initialize project paint settings from context */
static void project_state_init(bContext *C, Object *ob, ProjPaintState *ps, int mode)
static void project_state_init(bContext *C,
Object *ob,
ProjPaintState *ps,
const BrushStrokeMode mode,
const BrushSwitchMode brush_switch_mode)
{
Scene *scene = CTX_data_scene(C);
ToolSettings *settings = scene->toolsettings;
/* brush */
ps->mode = BrushStrokeMode(mode);
ps->mode = mode;
ps->brush_switch_mode = brush_switch_mode;
ps->paint = BKE_paint_get_active_from_context(C);
ps->brush = BKE_paint_brush(&settings->imapaint.paint);
if (ps->brush) {
Brush *brush = ps->brush;
ps->brush_type = brush->image_brush_type;
ps->blend = brush->blend;
if (mode == BRUSH_STROKE_SMOOTH) {
if (brush_switch_mode == BrushSwitchMode::Smooth) {
ps->brush_type = IMAGE_PAINT_BRUSH_TYPE_SOFTEN;
}
/* only check for inversion for the soften brush, elsewhere,
* a resident brush inversion flag can cause issues */
if (ps->brush_type == IMAGE_PAINT_BRUSH_TYPE_SOFTEN) {
ps->mode = (((ps->mode == BRUSH_STROKE_INVERT) ^ ((brush->flag & BRUSH_DIR_IN) != 0)) ?
BRUSH_STROKE_INVERT :
BRUSH_STROKE_NORMAL);
ps->mode = (((ps->mode == BrushStrokeMode::Invert) ^ ((brush->flag & BRUSH_DIR_IN) != 0)) ?
BrushStrokeMode::Invert :
BrushStrokeMode::Normal);
ps->blurkernel = paint_new_blur_kernel(brush, true);
}
@ -6108,7 +6114,11 @@ static void project_state_init(bContext *C, Object *ob, ProjPaintState *ps, int
ps->dither = settings->imapaint.dither;
}
void *paint_proj_new_stroke(bContext *C, Object *ob, const float mouse[2], int mode)
void *paint_proj_new_stroke(bContext *C,
Object *ob,
const float mouse[2],
const BrushStrokeMode mode,
const BrushSwitchMode brush_switch_mode)
{
ProjStrokeHandle *ps_handle;
Scene *scene = CTX_data_scene(C);
@ -6124,7 +6134,7 @@ void *paint_proj_new_stroke(bContext *C, Object *ob, const float mouse[2], int m
ps_handle->initial_hsv_jitter = seed_hsv_jitter();
}
if (mode == BRUSH_STROKE_INVERT) {
if (mode == BrushStrokeMode::Invert) {
/* Bypass regular stroke logic. */
if (ps_handle->brush->image_brush_type == IMAGE_PAINT_BRUSH_TYPE_CLONE) {
view3d_operator_needs_gpu(C);
@ -6166,7 +6176,7 @@ void *paint_proj_new_stroke(bContext *C, Object *ob, const float mouse[2], int m
for (int i = 0; i < ps_handle->ps_views_tot; i++) {
ProjPaintState *ps = ps_handle->ps_views[i];
project_state_init(C, ob, ps, mode);
project_state_init(C, ob, ps, mode, brush_switch_mode);
if (ps->ob == nullptr) {
ps_handle->ps_views_tot = i + 1;
@ -6288,7 +6298,7 @@ static wmOperatorStatus texture_paint_camera_project_exec(bContext *C, wmOperato
return OPERATOR_CANCELLED;
}
project_state_init(C, ob, &ps, BRUSH_STROKE_NORMAL);
project_state_init(C, ob, &ps, BrushStrokeMode::Normal, BrushSwitchMode::None);
if (image == nullptr) {
BKE_report(op->reports, RPT_ERROR, "Image could not be found");

View file

@ -74,12 +74,17 @@ using ColorManagedDisplay = ocio::Display;
/* paint_stroke.cc */
enum BrushStrokeMode {
BRUSH_STROKE_NORMAL,
BRUSH_STROKE_INVERT,
BRUSH_STROKE_SMOOTH,
BRUSH_STROKE_ERASE,
BRUSH_STROKE_MASK,
enum class BrushStrokeMode : int8_t {
Normal = 0,
Invert = 1,
};
/* Indicates a brush that the stroke will switch to for the duration of the stroke */
enum class BrushSwitchMode : int8_t {
None = 0,
Smooth = 1,
Erase = 2,
Mask = 3,
};
namespace ed::sculpt_paint {
@ -160,7 +165,8 @@ struct PaintStroke : NonCopyable, NonMovable {
float cached_size_pressure_ = 0.0f;
/* last pressure will store last pressure value for use in interpolation for space strokes */
float last_pressure_ = 0.0f;
int stroke_mode_ = 0;
BrushStrokeMode stroke_mode_ = BrushStrokeMode::Normal;
BrushSwitchMode brush_switch_mode_ = BrushSwitchMode::None;
float last_tablet_event_pressure_ = 0.0f;
@ -207,7 +213,7 @@ struct PaintStroke : NonCopyable, NonMovable {
bool stroke_inverted() const
{
return stroke_mode_ == BRUSH_STROKE_INVERT;
return stroke_mode_ == BrushStrokeMode::Invert;
}
float stroke_distance() const
@ -290,7 +296,7 @@ void paint_stroke_jitter_pos(Paint *paint,
PaintMode mode,
const Brush &brush,
float pressure,
int stroke_mode,
BrushStrokeMode stroke_mode,
float zoom_2d,
const float mval[2],
float r_mouse_out[2]);
@ -307,7 +313,9 @@ bool paint_supports_dynamic_size(const Brush &br, PaintMode mode);
* Return true if the brush size can change during paint (normally used for pressure).
*/
bool paint_supports_dynamic_tex_coords(const Brush &br, PaintMode mode);
bool paint_supports_smooth_stroke(const Brush &brush, PaintMode mode, int stroke_mode);
bool paint_supports_smooth_stroke(const Brush &brush,
PaintMode mode,
BrushSwitchMode brush_switch_mode);
bool paint_supports_texture(PaintMode mode);
/**
@ -445,7 +453,7 @@ void set_imapaintpartial(ImagePaintPartialRedraw *ippr);
void imapaint_region_tiles(
ImBuf *ibuf, int x, int y, int w, int h, int *tx, int *ty, int *tw, int *th);
bool get_imapaint_zoom(bContext *C, float *zoomx, float *zoomy);
void *paint_2d_new_stroke(bContext *, wmOperator *, int mode);
void *paint_2d_new_stroke(bContext *, wmOperator *, BrushStrokeMode mode);
void paint_2d_redraw(const bContext *C, void *ps, bool final);
void paint_2d_stroke_done(void *ps);
void paint_2d_stroke(void *ps,
@ -466,7 +474,11 @@ void paint_2d_bucket_fill(const bContext *C,
void *ps);
void paint_2d_gradient_fill(
const bContext *C, Brush *br, const float mouse_init[2], const float mouse_final[2], void *ps);
void *paint_proj_new_stroke(bContext *C, Object *ob, const float mouse[2], int mode);
void *paint_proj_new_stroke(bContext *C,
Object *ob,
const float mouse[2],
BrushStrokeMode mode,
BrushSwitchMode brush_switch_mode);
void paint_proj_stroke(const bContext *C,
void *ps_handle_p,
const float prev_pos[2],

View file

@ -445,12 +445,12 @@ void paint_stroke_jitter_pos(Paint *paint,
PaintMode mode,
const Brush &brush,
float pressure,
int stroke_mode,
BrushStrokeMode stroke_mode,
float zoom_2d,
const float mval[2],
float r_mouse_out[2])
{
if (paint_stroke_use_jitter(mode, brush, stroke_mode == BRUSH_STROKE_INVERT)) {
if (paint_stroke_use_jitter(mode, brush, stroke_mode == BrushStrokeMode::Invert)) {
float factor = zoom_2d;
if (brush.flag & BRUSH_JITTER_PRESSURE) {
@ -580,14 +580,14 @@ void PaintStroke::add_step(bContext *C, wmOperator *op, const float2 mval, float
static bool paint_smooth_stroke(const Brush &brush,
const PaintSample *sample,
const PaintMode mode,
const int stroke_mode,
const BrushSwitchMode brush_switch_mode,
float zoom_2d,
float2 last_mouse_position,
float last_pressure,
float2 &r_mouse,
float &r_pressure)
{
if (paint_supports_smooth_stroke(brush, mode, stroke_mode)) {
if (paint_supports_smooth_stroke(brush, mode, brush_switch_mode)) {
const float radius = brush.smooth_stroke_radius * zoom_2d;
const float u = brush.smooth_stroke_factor;
@ -868,7 +868,8 @@ PaintStroke::PaintStroke(bContext *C, wmOperator *op, int event_type) : event_ty
this->object = CTX_data_active_object(C);
this->scene = CTX_data_scene(C);
stroke_mode_ = RNA_enum_get(op->ptr, "mode");
stroke_mode_ = BrushStrokeMode(RNA_enum_get(op->ptr, "mode"));
brush_switch_mode_ = BrushSwitchMode(RNA_enum_get(op->ptr, "brush_toggle"));
original_ = paint_brush_type_raycast_original(*this->brush,
BKE_paintmode_get_active_from_context(C));
@ -895,9 +896,9 @@ PaintStroke::PaintStroke(bContext *C, wmOperator *op, int event_type) : event_ty
BKE_image_pool_release_ibuf(this->brush->mtex.tex->ima, tex_ibuf, nullptr);
}
if (stroke_mode_ == BRUSH_STROKE_INVERT) {
if (stroke_mode_ == BrushStrokeMode::Invert) {
if (this->brush->flag & BRUSH_CURVE) {
RNA_enum_set(op->ptr, "mode", BRUSH_STROKE_NORMAL);
RNA_enum_set(op->ptr, "mode", int(BrushStrokeMode::Normal));
}
}
/* initialize here */
@ -1050,13 +1051,15 @@ bool paint_supports_dynamic_size(const Brush &br, const PaintMode mode)
return true;
}
bool paint_supports_smooth_stroke(const Brush &brush, const PaintMode mode, int stroke_mode)
bool paint_supports_smooth_stroke(const Brush &brush,
const PaintMode mode,
const BrushSwitchMode brush_switch_mode)
{
/* The grease pencil draw tool needs to enable this when the `stroke_mode` is set to
* `BRUSH_STROKE_SMOOTH`. */
* `BrushSwitchMode::Smooth`. */
if (mode == PaintMode::GPencil &&
eBrushGPaintType(brush.gpencil_brush_type) == GPAINT_BRUSH_TYPE_DRAW &&
stroke_mode == BRUSH_STROKE_SMOOTH)
brush_switch_mode == BrushSwitchMode::Smooth)
{
return true;
}
@ -1485,7 +1488,7 @@ wmOperatorStatus PaintStroke::modal(bContext *C, wmOperator *op, const wmEvent *
* here. */
br = BKE_paint_brush(paint);
if (paint_supports_smooth_stroke(*br, mode, stroke_mode_)) {
if (paint_supports_smooth_stroke(*br, mode, brush_switch_mode_)) {
stroke_cursor_ = WM_paint_cursor_activate(
SPACE_TYPE_ANY, RGN_TYPE_ANY, paint_brush_cursor_poll, paint_draw_smooth_cursor, this);
@ -1518,14 +1521,14 @@ wmOperatorStatus PaintStroke::modal(bContext *C, wmOperator *op, const wmEvent *
/* Handles shift-key active smooth toggling during a grease pencil stroke. */
if (mode == PaintMode::GPencil) {
if (event->modifier & KM_SHIFT) {
stroke_mode_ = BRUSH_STROKE_SMOOTH;
brush_switch_mode_ = BrushSwitchMode::Smooth;
if (!stroke_cursor_) {
stroke_cursor_ = WM_paint_cursor_activate(
SPACE_TYPE_ANY, RGN_TYPE_ANY, paint_brush_cursor_poll, paint_draw_smooth_cursor, this);
}
}
else {
stroke_mode_ = BRUSH_STROKE_NORMAL;
stroke_mode_ = BrushStrokeMode::Normal;
if (stroke_cursor_ != nullptr) {
WM_paint_cursor_end(static_cast<wmPaintCursor *>(stroke_cursor_));
stroke_cursor_ = nullptr;
@ -1579,7 +1582,7 @@ wmOperatorStatus PaintStroke::modal(bContext *C, wmOperator *op, const wmEvent *
if (paint_smooth_stroke(*this->brush,
&sample_average,
mode,
stroke_mode_,
brush_switch_mode_,
zoom_2d_,
this->last_mouse_position,
last_pressure_,

View file

@ -141,23 +141,32 @@ bool paint_get_tex_pixel(const MTex *mtex,
void paint_stroke_operator_properties(wmOperatorType *ot)
{
static const EnumPropertyItem stroke_mode_items[] = {
{BRUSH_STROKE_NORMAL, "NORMAL", 0, "Regular", "Apply brush normally"},
{BRUSH_STROKE_INVERT,
{int(BrushStrokeMode::Normal), "NORMAL", 0, "Regular", "Apply brush normally"},
{int(BrushStrokeMode::Invert),
"INVERT",
0,
"Invert",
"Invert action of brush for duration of stroke"},
{BRUSH_STROKE_SMOOTH,
{0},
};
static const EnumPropertyItem temporary_brush_toggle_items[] = {
{int(BrushSwitchMode::None), "None", 0, "None", "Apply brush normally"},
{int(BrushSwitchMode::Smooth),
"SMOOTH",
0,
"Smooth",
"Switch brush to smooth mode for duration of stroke"},
{BRUSH_STROKE_ERASE,
"Switch to smooth brush for duration of stroke"},
{int(BrushSwitchMode::Erase),
"ERASE",
0,
"Erase",
"Switch brush to erase mode for duration of stroke"},
{BRUSH_STROKE_MASK, "MASK", 0, "Mask", "Switch brush to mask mode for duration of stroke"},
"Switch to erase brush for duration of stroke"},
{int(BrushSwitchMode::Mask),
"MASK",
0,
"Mask",
"Switch to mask brush for duration of stroke"},
{0},
};
@ -169,12 +178,21 @@ void paint_stroke_operator_properties(wmOperatorType *ot)
prop = RNA_def_enum(ot->srna,
"mode",
stroke_mode_items,
BRUSH_STROKE_NORMAL,
int(BrushStrokeMode::Normal),
"Stroke Mode",
"Action taken when a paint stroke is made");
RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_OPERATOR_DEFAULT);
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_enum(ot->srna,
"brush_toggle",
temporary_brush_toggle_items,
int(BrushSwitchMode::None),
"Temporary Brush Toggle Type",
"Brush to use for duration of stroke");
RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_OPERATOR_DEFAULT);
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
/* TODO: Pen flip logic should likely be combined into the stroke mode logic instead of being
* an entirely separate concept. */
prop = RNA_def_boolean(

View file

@ -397,7 +397,6 @@ void update_cache_invariants(
Object &ob = *stroke->object;
float mat[3][3];
float view_dir[3] = {0.0f, 0.0f, 1.0f};
int mode;
/* VW paint needs to allocate stroke cache before update is called. */
if (!ss.cache) {
@ -416,9 +415,11 @@ void update_cache_invariants(
zero_v2(cache->initial_mouse);
}
mode = RNA_enum_get(op->ptr, "mode");
cache->invert = mode == BRUSH_STROKE_INVERT;
cache->alt_smooth = mode == BRUSH_STROKE_SMOOTH;
const auto mode = BrushStrokeMode(RNA_enum_get(op->ptr, "mode"));
cache->invert = mode == BrushStrokeMode::Invert;
const auto brush_switch_mode = BrushSwitchMode(RNA_enum_get(op->ptr, "brush_toggle"));
cache->alt_smooth = brush_switch_mode == BrushSwitchMode::Smooth;
/* not very nice, but with current events system implementation
* we can't handle brush appearance inversion hotkey separately (sergey) */
if (cache->invert) {
@ -949,8 +950,8 @@ static std::unique_ptr<VPaintData> vpaint_init_vpaint(wmOperator *op,
brush.falloff_angle,
(brush.flag & BRUSH_FRONTFACE_FALLOFF) != 0);
vpd->paintcol = vpaint_get_current_col(vp,
(RNA_enum_get(op->ptr, "mode") == BRUSH_STROKE_INVERT));
vpd->paintcol = vpaint_get_current_col(
vp, (BrushStrokeMode(RNA_enum_get(op->ptr, "mode")) == BrushStrokeMode::Invert));
vpd->is_texbrush = !(brush.vertex_brush_type == VPAINT_BRUSH_TYPE_BLUR) && brush.mtex.tex;

View file

@ -4269,15 +4269,14 @@ static void cache_paint_invariants_update(StrokeCache &cache, const Brush &brush
* smooth). */
static bool sculpt_needs_connectivity_info(const Sculpt &sd,
const Brush &brush,
const Object &object,
int stroke_mode)
const Object &object)
{
SculptSession &ss = *object.runtime->sculpt_session;
const bke::pbvh::Tree *pbvh = bke::object::pbvh_get(object);
if (pbvh && auto_mask::is_enabled(sd, object, &brush)) {
return true;
}
return ((stroke_mode == BRUSH_STROKE_SMOOTH) || (ss.cache && ss.cache->alt_smooth) ||
return ((ss.cache && ss.cache->alt_smooth) ||
(brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_SMOOTH) || (brush.autosmooth_factor > 0) ||
((brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_MASK) &&
(brush.mask_tool == BRUSH_MASK_SMOOTH)) ||
@ -4300,7 +4299,7 @@ void SCULPT_stroke_modifiers_check(
using namespace blender::ed::sculpt_paint;
SculptSession &ss = *ob.runtime->sculpt_session;
bool need_pmap = brush && sculpt_needs_connectivity_info(sd, *brush, ob, 0);
bool need_pmap = brush && sculpt_needs_connectivity_info(sd, *brush, ob);
if (ss.shapekey_active || ss.deform_modifiers_active ||
(!BKE_sculptsession_use_pbvh_draw(&ob, rv3d) && need_pmap))
{
@ -4932,7 +4931,10 @@ struct SculptPaintStroke final : public PaintStroke {
wm_ = CTX_wm_manager(C);
}
void stroke_cache_init(BrushStrokeMode stroke_mode, bool pen_flip, const float mval[2]);
void stroke_cache_init(BrushStrokeMode stroke_mode,
BrushSwitchMode brush_switch_mode,
bool pen_flip,
const float mval[2]);
void stroke_cache_update(PointerRNA *ptr);
bool get_location(float out[3], const float mouse[2], bool force_original) override;
@ -5501,6 +5503,7 @@ bool color_supported_check(const Scene &scene, Object &object, ReportList *repor
}
void SculptPaintStroke::stroke_cache_init(const BrushStrokeMode stroke_mode,
const BrushSwitchMode brush_switch_mode,
const bool pen_flip,
const float mval[2])
{
@ -5539,9 +5542,9 @@ void SculptPaintStroke::stroke_cache_init(const BrushStrokeMode stroke_mode,
cache->initial_normal = ss.cursor_sampled_normal.value_or(ss.cursor_normal);
cache->pen_flip = pen_flip;
cache->invert = stroke_mode == BRUSH_STROKE_INVERT;
cache->alt_smooth = stroke_mode == BRUSH_STROKE_SMOOTH;
cache->alt_mask = stroke_mode == BRUSH_STROKE_MASK;
cache->invert = stroke_mode == BrushStrokeMode::Invert;
cache->alt_smooth = brush_switch_mode == BrushSwitchMode::Smooth;
cache->alt_mask = brush_switch_mode == BrushSwitchMode::Mask;
cache->normal_weight = brush->normal_weight;
/* Interpret invert as following normal, for grab brushes. */
@ -5676,7 +5679,8 @@ bool SculptPaintStroke::test_start(wmOperator *op, const float mval[2])
ED_view3d_init_mats_rv3d(&ob, this->vc.rv3d);
stroke_cache_init((BrushStrokeMode)RNA_enum_get(op->ptr, "mode"),
stroke_cache_init(BrushStrokeMode(RNA_enum_get(op->ptr, "mode")),
BrushSwitchMode(RNA_enum_get(op->ptr, "brush_toggle")),
RNA_boolean_get(op->ptr, "pen_flip"),
mval);
if (brush && brush_type_is_paint(brush->sculpt_brush_type)) {

View file

@ -203,7 +203,7 @@ struct StrokeCache {
* Whether the modifier key that controls inverting brush behavior is active currently.
* Generally signals a change in behavior for brushes.
*
* \see BrushStrokeMode::BRUSH_STROKE_INVERT.
* \see BrushStrokeMode::Invert.
*/
bool invert = false;
float pressure = 0.0f;
@ -395,7 +395,7 @@ struct StrokeCache {
* Whether the modifier key that controls smoothing is active currently.
* Generally signals a change in behavior for different brushes.
*
* \see BrushStrokeMode::BRUSH_STROKE_SMOOTH.
* \see BrushSwitchMode::Smooth.
*/
bool alt_smooth = false;
@ -403,7 +403,7 @@ struct StrokeCache {
* Whether the modifier key that controls masking is active currently.
* Switches the active brush to the mask brush during the stroke.
*
* \see BrushStrokeMode::BRUSH_STROKE_MASK.
* \see BrushSwitchMode::Mask.
*/
bool alt_mask = false;