Paint: Disable pressure sensitivity for certain brushes

A subset of brushes behave as "anchored" brushes, in that they do not
apply to the surface continually underneath the cursor, but have a
starting point and then are influenced by the mouse movement. These
brushes behave oddly with tablet pressure sensitivity, as they cannot
modulate over the course of the stroke without causing odd behavior.

Currently, the pressure is only sampled at the very beginning of the
stroke, which makes it difficult to control intuitively. Further work
can be done to improve this behavior (e.g. D6603).

The full list of brush types affected is below:
* Grab
* Snake Hook
* Elastic Deform
* Pose
* Boundary
* Thumb
* Rotate
* Cloth with Grab deformation

Resolves #83697

Pull Request: https://projects.blender.org/blender/blender/pulls/146825
This commit is contained in:
Sean Kim
2025-09-26 16:41:01 +02:00
committed by Sean Kim
parent 9c7ace059a
commit 0e2316c2bc
6 changed files with 80 additions and 11 deletions

View File

@@ -783,17 +783,19 @@ def brush_settings(layout, context, brush, popover=False):
row = layout.row(align=True)
row.prop(brush, "hardness", slider=True)
row.prop(brush, "invert_hardness_pressure", text="")
row.prop(brush, "use_hardness_pressure", text="")
if capabilities.has_hardness_pressure:
row.prop(brush, "invert_hardness_pressure", text="")
row.prop(brush, "use_hardness_pressure", text="")
# auto_smooth_factor and use_inverse_smooth_pressure
if capabilities.has_auto_smooth:
pressure_name = "use_inverse_smooth_pressure" if capabilities.has_auto_smooth_pressure else None
UnifiedPaintPanel.prop_unified(
layout,
context,
brush,
"auto_smooth_factor",
pressure_name="use_inverse_smooth_pressure",
pressure_name=pressure_name,
slider=True,
)
@@ -1080,6 +1082,7 @@ def brush_shared_settings(layout, context, brush, popover=False):
size_mode = False
strength = False
strength_pressure = False
size_pressure = False
weight = False
direction = False
@@ -1089,6 +1092,7 @@ def brush_shared_settings(layout, context, brush, popover=False):
blend_mode = brush.image_paint_capabilities.has_color
size = brush.image_paint_capabilities.has_radius
strength = strength_pressure = True
size_pressure = True
# Sculpt #
if mode == 'SCULPT':
@@ -1098,6 +1102,7 @@ def brush_shared_settings(layout, context, brush, popover=False):
strength = True
strength_pressure = brush.sculpt_capabilities.has_strength_pressure
direction = brush.sculpt_capabilities.has_direction
size_pressure = brush.sculpt_capabilities.has_size_pressure
# Vertex Paint #
if mode == 'PAINT_VERTEX':
@@ -1106,6 +1111,7 @@ def brush_shared_settings(layout, context, brush, popover=False):
size = True
strength = True
strength_pressure = True
size_pressure = True
# Weight Paint #
if mode == 'PAINT_WEIGHT':
@@ -1113,6 +1119,7 @@ def brush_shared_settings(layout, context, brush, popover=False):
size = True
weight = brush.weight_paint_capabilities.has_weight
strength = strength_pressure = True
size_pressure = True
# Only draw blend mode for the Draw tool, because for other tools it is pointless. D5928#137944
if brush.weight_brush_type == 'DRAW':
blend_mode = True
@@ -1124,6 +1131,7 @@ def brush_shared_settings(layout, context, brush, popover=False):
strength = tool not in {'ADD', 'DELETE'}
direction = tool in {'GROW_SHRINK', 'SELECTION_PAINT'}
strength_pressure = tool not in {'SLIDE', 'ADD', 'DELETE'}
size_pressure = True
# Grease Pencil #
if mode == 'PAINT_GREASE_PENCIL':
@@ -1135,6 +1143,7 @@ def brush_shared_settings(layout, context, brush, popover=False):
if mode == 'SCULPT_GREASE_PENCIL':
size = True
strength = True
size_pressure = True
### Draw settings. ###
ups = UnifiedPaintPanel.paint_settings(context).unified_paint_settings
@@ -1159,14 +1168,16 @@ def brush_shared_settings(layout, context, brush, popover=False):
size_prop = "unprojected_size"
if size or size_mode:
if size:
pressure_name = "use_pressure_size" if size_pressure else None
curve_visibility_name = "show_size_curve" if size_pressure else None
UnifiedPaintPanel.prop_unified(
layout,
context,
brush,
size_prop,
unified_name="use_unified_size",
pressure_name="use_pressure_size",
curve_visibility_name="show_size_curve",
pressure_name=pressure_name,
curve_visibility_name=curve_visibility_name,
text="Size",
slider=True,
)

View File

@@ -257,6 +257,9 @@ bool supports_secondary_cursor_color(const Brush &brush);
bool supports_smooth_stroke(const Brush &brush);
bool supports_space_attenuation(const Brush &brush);
bool supports_strength_pressure(const Brush &brush);
bool supports_size_pressure(const Brush &brush);
bool supports_auto_smooth_pressure(const Brush &brush);
bool supports_hardness_pressure(const Brush &brush);
bool supports_inverted_direction(const Brush &brush);
bool supports_gravity(const Brush &brush);
bool supports_tilt(const Brush &brush);

View File

@@ -1885,9 +1885,41 @@ bool supports_space_attenuation(const Brush &brush)
SCULPT_BRUSH_TYPE_SMOOTH,
SCULPT_BRUSH_TYPE_SNAKE_HOOK);
}
/**
* A helper method for classifying a certain subset of brush types.
*
* Certain sculpt deformations are 'grab-like' in that they behave as if they have an anchored
* start point.
*/
static bool is_grab_tool(const Brush &brush)
{
return (brush.sculpt_brush_type == SCULPT_BRUSH_TYPE_CLOTH &&
brush.cloth_deform_type == BRUSH_CLOTH_DEFORM_GRAB) ||
ELEM(brush.sculpt_brush_type,
SCULPT_BRUSH_TYPE_GRAB,
SCULPT_BRUSH_TYPE_SNAKE_HOOK,
SCULPT_BRUSH_TYPE_ELASTIC_DEFORM,
SCULPT_BRUSH_TYPE_POSE,
SCULPT_BRUSH_TYPE_BOUNDARY,
SCULPT_BRUSH_TYPE_THUMB,
SCULPT_BRUSH_TYPE_ROTATE);
}
bool supports_strength_pressure(const Brush &brush)
{
return !ELEM(brush.sculpt_brush_type, SCULPT_BRUSH_TYPE_GRAB, SCULPT_BRUSH_TYPE_SNAKE_HOOK);
return !is_grab_tool(brush);
}
bool supports_size_pressure(const Brush &brush)
{
return !is_grab_tool(brush);
}
bool supports_auto_smooth_pressure(const Brush &brush)
{
return !is_grab_tool(brush);
}
bool supports_hardness_pressure(const Brush &brush)
{
return !is_grab_tool(brush);
}
bool supports_inverted_direction(const Brush &brush)
{

View File

@@ -1114,9 +1114,7 @@ bool paint_supports_dynamic_size(const Brush &br, const PaintMode mode)
switch (mode) {
case PaintMode::Sculpt:
if (sculpt_is_grab_tool(br)) {
return false;
}
return bke::brush::supports_size_pressure(br);
break;
case PaintMode::Texture2D: /* fall through */

View File

@@ -3422,7 +3422,9 @@ static void do_brush_action(const Depsgraph &depsgraph,
if (!ELEM(brush.sculpt_brush_type, SCULPT_BRUSH_TYPE_SMOOTH, SCULPT_BRUSH_TYPE_MASK) &&
brush.autosmooth_factor > 0)
{
if (brush.flag & BRUSH_INVERSE_SMOOTH_PRESSURE) {
if (bke::brush::supports_auto_smooth_pressure(brush) &&
brush.flag & BRUSH_INVERSE_SMOOTH_PRESSURE)
{
brushes::do_smooth_brush(
depsgraph, sd, ob, node_mask, brush.autosmooth_factor * (1.0f - ss.cache->pressure));
}
@@ -4290,7 +4292,9 @@ static void brush_delta_update(const Depsgraph &depsgraph,
static void cache_paint_invariants_update(StrokeCache &cache, const Brush &brush)
{
cache.hardness = brush.hardness;
if (brush.paint_flags & BRUSH_PAINT_HARDNESS_PRESSURE) {
if (bke::brush::supports_hardness_pressure(brush) &&
brush.paint_flags & BRUSH_PAINT_HARDNESS_PRESSURE)
{
cache.hardness *= brush.paint_flags & BRUSH_PAINT_HARDNESS_PRESSURE_INVERT ?
1.0f - cache.pressure :
cache.pressure;

View File

@@ -552,6 +552,24 @@ static bool rna_BrushCapabilitiesSculpt_has_strength_pressure_get(PointerRNA *pt
return blender::bke::brush::supports_strength_pressure(*br);
}
static bool rna_BrushCapabilitiesSculpt_has_size_pressure_get(PointerRNA *ptr)
{
const Brush *br = static_cast<const Brush *>(ptr->data);
return blender::bke::brush::supports_size_pressure(*br);
}
static bool rna_BrushCapabilitiesSculpt_has_auto_smooth_pressure_get(PointerRNA *ptr)
{
const Brush *br = static_cast<const Brush *>(ptr->data);
return blender::bke::brush::supports_auto_smooth_pressure(*br);
}
static bool rna_BrushCapabilitiesSculpt_has_hardness_pressure_get(PointerRNA *ptr)
{
const Brush *br = static_cast<const Brush *>(ptr->data);
return blender::bke::brush::supports_hardness_pressure(*br);
}
static bool rna_BrushCapabilitiesSculpt_has_direction_get(PointerRNA *ptr)
{
const Brush *br = static_cast<const Brush *>(ptr->data);
@@ -1169,6 +1187,9 @@ static void rna_def_sculpt_capabilities(BlenderRNA *brna)
SCULPT_BRUSH_CAPABILITY(has_smooth_stroke, "Has Smooth Stroke");
SCULPT_BRUSH_CAPABILITY(has_space_attenuation, "Has Space Attenuation");
SCULPT_BRUSH_CAPABILITY(has_strength_pressure, "Has Strength Pressure");
SCULPT_BRUSH_CAPABILITY(has_size_pressure, "Has Size Pressure");
SCULPT_BRUSH_CAPABILITY(has_auto_smooth_pressure, "Has Auto-Smooth Pressure");
SCULPT_BRUSH_CAPABILITY(has_hardness_pressure, "Has Hardness Pressure");
SCULPT_BRUSH_CAPABILITY(has_direction, "Has Direction");
SCULPT_BRUSH_CAPABILITY(has_gravity, "Has Gravity");
SCULPT_BRUSH_CAPABILITY(has_tilt, "Has Tilt");