From 0e2316c2bccbdd0a39d868790d065c7f3f3a76c6 Mon Sep 17 00:00:00 2001 From: Sean Kim Date: Fri, 26 Sep 2025 16:41:01 +0200 Subject: [PATCH] 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 --- .../startup/bl_ui/properties_paint_common.py | 21 +++++++++--- source/blender/blenkernel/BKE_brush.hh | 3 ++ source/blender/blenkernel/intern/brush.cc | 34 ++++++++++++++++++- .../editors/sculpt_paint/paint_stroke.cc | 4 +-- source/blender/editors/sculpt_paint/sculpt.cc | 8 +++-- source/blender/makesrna/intern/rna_brush.cc | 21 ++++++++++++ 6 files changed, 80 insertions(+), 11 deletions(-) diff --git a/scripts/startup/bl_ui/properties_paint_common.py b/scripts/startup/bl_ui/properties_paint_common.py index ba54019cd65..710cf4acf1b 100644 --- a/scripts/startup/bl_ui/properties_paint_common.py +++ b/scripts/startup/bl_ui/properties_paint_common.py @@ -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, ) diff --git a/source/blender/blenkernel/BKE_brush.hh b/source/blender/blenkernel/BKE_brush.hh index 510942a6dee..94c181f2ec1 100644 --- a/source/blender/blenkernel/BKE_brush.hh +++ b/source/blender/blenkernel/BKE_brush.hh @@ -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); diff --git a/source/blender/blenkernel/intern/brush.cc b/source/blender/blenkernel/intern/brush.cc index c31ca681698..01c260f7f9d 100644 --- a/source/blender/blenkernel/intern/brush.cc +++ b/source/blender/blenkernel/intern/brush.cc @@ -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) { diff --git a/source/blender/editors/sculpt_paint/paint_stroke.cc b/source/blender/editors/sculpt_paint/paint_stroke.cc index e4ca791eec4..51480dd502b 100644 --- a/source/blender/editors/sculpt_paint/paint_stroke.cc +++ b/source/blender/editors/sculpt_paint/paint_stroke.cc @@ -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 */ diff --git a/source/blender/editors/sculpt_paint/sculpt.cc b/source/blender/editors/sculpt_paint/sculpt.cc index 501c8b5d31d..ef3cf398ee3 100644 --- a/source/blender/editors/sculpt_paint/sculpt.cc +++ b/source/blender/editors/sculpt_paint/sculpt.cc @@ -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; diff --git a/source/blender/makesrna/intern/rna_brush.cc b/source/blender/makesrna/intern/rna_brush.cc index 6158db84e76..1b7ac07fdf5 100644 --- a/source/blender/makesrna/intern/rna_brush.cc +++ b/source/blender/makesrna/intern/rna_brush.cc @@ -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(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(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(ptr->data); + return blender::bke::brush::supports_hardness_pressure(*br); +} + static bool rna_BrushCapabilitiesSculpt_has_direction_get(PointerRNA *ptr) { const Brush *br = static_cast(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");