Sculpt/Paint: Bring back some brush tools for paint modes

Since brush assets were merged, all sculpt and paint modes have a unified
"Brush" tool that is used for all brushes. Based on feedback, this mixes brush
types a bit too much, so some specific tools that use brushes should be brought
back. These brush types just behave very different from the main brush types,
and users expect them to be easily accessible from the toolbar. This also makes
them more discoverable and having tools that remember their last used brush can
be useful too (e.g. assigning shortcut to eraser tool to switch back to last
used eraser, rather than a specific eraser brush).

Note that further changes are planned, to get a true "best of both worlds"
design, where people can use a global brush library without having to worry
about tools, but brushes can also be accessed via tool categorization.
The new design works well with both mental models without being a compromise
in-between.

a79f9100a6 already brought back some tool for grease pencil draw mode, with this
commit the design is now applied to all sculpt & paint modes.

See PR for a list of tools that were brought back.

Design Task: https://projects.blender.org/blender/blender/issues/128066
Pull Request: https://projects.blender.org/blender/blender/pulls/128038
This commit is contained in:
Julian Eisel
2024-10-01 18:03:33 +02:00
committed by Julian Eisel
parent eac36baafd
commit 3798852071
27 changed files with 520 additions and 17 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1398,6 +1398,81 @@ class _defs_particle:
class _defs_sculpt:
@ToolDef.from_fn
def mask():
return dict(
idname="builtin_brush.mask",
label="Mask",
icon="brush.sculpt.mask",
options={'USE_BRUSHES'},
brush_type='MASK',
)
@ToolDef.from_fn
def draw_face_sets():
return dict(
idname="builtin_brush.draw_face_sets",
label="Draw Face Sets",
icon="brush.sculpt.draw_face_sets",
options={'USE_BRUSHES'},
brush_type='DRAW_FACE_SETS',
)
@ToolDef.from_fn
def paint():
return dict(
idname="builtin_brush.paint",
label="Paint",
icon="brush.sculpt.paint",
options={'USE_BRUSHES'},
brush_type='PAINT',
)
@staticmethod
def poll_dyntopo(context):
if context is None:
return True
return context.sculpt_object and context.sculpt_object.use_dynamic_topology_sculpting
@ToolDef.from_fn
def dyntopo_density():
return dict(
idname="builtin_brush.simplify",
label="Density",
icon="brush.sculpt.simplify",
options={'USE_BRUSHES'},
brush_type='SIMPLIFY',
)
@staticmethod
def poll_multires(context):
if context is None or context.sculpt_object is None:
return True
for mod in context.sculpt_object.modifiers:
if mod.type == 'MULTIRES':
return True
return False
@ToolDef.from_fn
def multires_eraser():
return dict(
idname="builtin_brush.displacement_eraser",
label="Multires Displacement Eraser",
icon="brush.sculpt.displacement_eraser",
options={'USE_BRUSHES'},
brush_type='DISPLACEMENT_ERASER',
)
@ToolDef.from_fn
def multires_smear():
return dict(
idname="builtin_brush.displacement_smear",
label="Multires Displacement Smear",
icon="brush.sculpt.displacement_smear",
options={'USE_BRUSHES'},
brush_type='DISPLACEMENT_SMEAR',
)
@staticmethod
def draw_lasso_stroke_settings(layout, props, draw_inline, draw_popover):
@@ -1853,6 +1928,36 @@ class _defs_vertex_paint:
(ob.data.use_paint_mask or
ob.data.use_paint_mask_vertex))
@ToolDef.from_fn
def blur():
return dict(
idname="builtin_brush.blur",
label="Blur",
icon="brush.paint_vertex.blur",
options={'USE_BRUSHES'},
brush_type='BLUR',
)
@ToolDef.from_fn
def average():
return dict(
idname="builtin_brush.average",
label="Average",
icon="brush.paint_vertex.average",
options={'USE_BRUSHES'},
brush_type='AVERAGE',
)
@ToolDef.from_fn
def smear():
return dict(
idname="builtin_brush.smear",
label="Smear",
icon="brush.paint_vertex.smear",
options={'USE_BRUSHES'},
brush_type='SMEAR',
)
class _defs_texture_paint:
@@ -1868,12 +1973,62 @@ class _defs_texture_paint:
def brush():
return dict(
idname="builtin.brush",
label="Brush",
label="Paint",
icon="brush.sculpt.paint",
cursor='PAINT_CROSS',
options={'USE_BRUSHES'},
)
@ToolDef.from_fn
def blur():
return dict(
idname="builtin_brush.soften",
label="Blur",
icon="brush.paint_texture.soften",
options={'USE_BRUSHES'},
brush_type='SOFTEN',
)
@ToolDef.from_fn
def smear():
return dict(
idname="builtin_brush.smear",
label="Smear",
icon="brush.paint_texture.smear",
options={'USE_BRUSHES'},
brush_type='SMEAR',
)
@ToolDef.from_fn
def clone():
return dict(
idname="builtin_brush.clone",
label="Clone",
icon="brush.paint_texture.clone",
options={'USE_BRUSHES'},
brush_type='CLONE',
)
@ToolDef.from_fn
def fill():
return dict(
idname="builtin_brush.fill",
label="Fill",
icon="brush.paint_texture.fill",
options={'USE_BRUSHES'},
brush_type='FILL',
)
@ToolDef.from_fn
def mask():
return dict(
idname="builtin_brush.mask",
label="Mask",
icon="brush.paint_texture.mask",
options={'USE_BRUSHES'},
brush_type='MASK',
)
class _defs_weight_paint:
@@ -1890,6 +2045,36 @@ class _defs_weight_paint:
return VIEW3D_PT_tools_active._tools_select
return ()
@ToolDef.from_fn
def blur():
return dict(
idname="builtin_brush.blur",
label="Blur",
icon="brush.paint_weight.blur",
options={'USE_BRUSHES'},
brush_type='BLUR',
)
@ToolDef.from_fn
def average():
return dict(
idname="builtin_brush.average",
label="Average",
icon="brush.paint_weight.average",
options={'USE_BRUSHES'},
brush_type='AVERAGE',
)
@ToolDef.from_fn
def smear():
return dict(
idname="builtin_brush.smear",
label="Smear",
icon="brush.paint_weight.smear",
options={'USE_BRUSHES'},
brush_type='SMEAR',
)
@ToolDef.from_fn
def sample_weight():
def draw_settings(context, layout, _tool):
@@ -2878,6 +3063,17 @@ class _defs_grease_pencil_sculpt:
)
)
@ToolDef.from_fn
def clone():
return dict(
idname="builtin_brush.clone",
label="Clone",
icon="ops.gpencil.sculpt_clone",
options={'USE_BRUSHES'},
brush_type='CLONE',
)
class _defs_gpencil_weight:
# No mode specific tools currently (only general ones).
@@ -2885,8 +3081,35 @@ class _defs_gpencil_weight:
class _defs_grease_pencil_weight:
# No mode specific tools currently (only general ones).
pass
@ToolDef.from_fn
def blur():
return dict(
idname="builtin_brush.blur",
label="Blur",
icon="ops.gpencil.sculpt_blur",
options={'USE_BRUSHES'},
brush_type='BLUR',
)
@ToolDef.from_fn
def average():
return dict(
idname="builtin_brush.average",
label="Average",
icon="ops.gpencil.sculpt_average",
options={'USE_BRUSHES'},
brush_type='AVERAGE',
)
@ToolDef.from_fn
def smear():
return dict(
idname="builtin_brush.smear",
label="Smear",
icon="ops.gpencil.sculpt_smear",
options={'USE_BRUSHES'},
brush_type='SMEAR',
)
class _defs_grease_pencil_vertex:
@@ -2906,10 +3129,87 @@ class _defs_grease_pencil_vertex:
)
)
@ToolDef.from_fn
def blur():
return dict(
idname="builtin_brush.blur",
label="Blur",
icon="brush.paint_vertex.blur",
options={'USE_BRUSHES'},
brush_type='BLUR',
)
@ToolDef.from_fn
def average():
return dict(
idname="builtin_brush.average",
label="Average",
icon="brush.paint_vertex.average",
options={'USE_BRUSHES'},
brush_type='AVERAGE',
)
@ToolDef.from_fn
def smear():
return dict(
idname="builtin_brush.smear",
label="Smear",
icon="brush.paint_vertex.smear",
options={'USE_BRUSHES'},
brush_type='SMEAR',
)
@ToolDef.from_fn
def replace():
return dict(
idname="builtin_brush.replace",
label="Replace",
icon="brush.paint_vertex.replace",
options={'USE_BRUSHES'},
brush_type='REPLACE',
)
class _defs_curves_sculpt:
# No mode specific tools currently (only general ones).
pass
@ToolDef.from_fn
def select():
return dict(
idname="builtin_brush.selection_paint",
label="Selection Paint",
icon="ops.generic.select_paint",
options={'USE_BRUSHES'},
brush_type='SELECTION_PAINT',
)
@ToolDef.from_fn
def density():
return dict(
idname="builtin_brush.density",
label="Density",
icon="ops.curves.sculpt_density",
options={'USE_BRUSHES'},
brush_type='DENSITY',
)
@ToolDef.from_fn
def add():
return dict(
idname="builtin_brush.add",
label="Add",
icon="ops.curves.sculpt_add",
options={'USE_BRUSHES'},
brush_type='ADD',
)
@ToolDef.from_fn
def delete():
return dict(
idname="builtin_brush.delete",
label="Delete",
icon="ops.curves.sculpt_delete",
options={'USE_BRUSHES'},
brush_type='DELETE',
)
class _defs_gpencil_vertex:
@@ -3190,15 +3490,6 @@ class IMAGE_PT_tools_active(ToolSelectPanelHelper, Panel):
# Private tool lists for convenient reuse in `_tools`.
_brush_tool = ToolDef.from_dict(
dict(
idname="builtin.brush",
label="Brush",
icon="brush.sculpt.paint",
options={'USE_BRUSHES'},
)
)
_tools_transform = (
_defs_image_uv_transform.translate,
_defs_image_uv_transform.rotate,
@@ -3253,7 +3544,11 @@ class IMAGE_PT_tools_active(ToolSelectPanelHelper, Panel):
None,
],
'PAINT': [
_brush_tool,
_defs_texture_paint.brush,
_defs_texture_paint.blur,
_defs_texture_paint.smear,
_defs_texture_paint.fill,
_defs_texture_paint.mask,
None,
*_tools_annotate,
],
@@ -3585,6 +3880,24 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel):
],
'SCULPT': [
_brush_tool,
_defs_sculpt.paint,
_defs_sculpt.mask,
_defs_sculpt.draw_face_sets,
lambda context: (
(
_defs_sculpt.dyntopo_density,
)
if _defs_sculpt.poll_dyntopo(context)
else ()
),
lambda context: (
(
_defs_sculpt.multires_eraser,
_defs_sculpt.multires_smear,
)
if _defs_sculpt.poll_multires(context)
else ()
),
None,
(
_defs_sculpt.mask_border,
@@ -3628,6 +3941,7 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel):
],
'SCULPT_GREASE_PENCIL': [
_brush_tool,
_defs_grease_pencil_sculpt.clone,
None,
*_tools_annotate,
lambda context: (
@@ -3637,7 +3951,12 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel):
),
],
'PAINT_TEXTURE': [
_brush_tool,
_defs_texture_paint.brush,
_defs_texture_paint.blur,
_defs_texture_paint.smear,
_defs_texture_paint.clone,
_defs_texture_paint.fill,
_defs_texture_paint.mask,
None,
lambda context: (
VIEW3D_PT_tools_active._tools_select
@@ -3648,6 +3967,9 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel):
],
'PAINT_VERTEX': [
_brush_tool,
_defs_vertex_paint.blur,
_defs_vertex_paint.average,
_defs_vertex_paint.smear,
None,
lambda context: (
VIEW3D_PT_tools_active._tools_select
@@ -3658,6 +3980,9 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel):
],
'PAINT_WEIGHT': [
_brush_tool,
_defs_weight_paint.blur,
_defs_weight_paint.average,
_defs_weight_paint.smear,
_defs_weight_paint.gradient,
None,
(
@@ -3747,6 +4072,9 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel):
],
'WEIGHT_GREASE_PENCIL': [
_brush_tool,
_defs_grease_pencil_weight.blur,
_defs_grease_pencil_weight.average,
_defs_grease_pencil_weight.smear,
None,
*_tools_annotate,
],
@@ -3763,6 +4091,10 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel):
],
'VERTEX_GREASE_PENCIL': [
_brush_tool,
_defs_grease_pencil_vertex.blur,
_defs_grease_pencil_vertex.average,
_defs_grease_pencil_vertex.smear,
_defs_grease_pencil_vertex.replace,
None,
*_tools_annotate,
None,
@@ -3774,6 +4106,10 @@ class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel):
],
'SCULPT_CURVES': [
_brush_tool,
_defs_curves_sculpt.select,
_defs_curves_sculpt.density,
_defs_curves_sculpt.add,
_defs_curves_sculpt.delete,
None,
*_tools_annotate,
],

View File

@@ -821,18 +821,113 @@ static void paint_brush_default_essentials_name_get(
switch (ob_mode) {
case OB_MODE_SCULPT:
name = "Draw";
if (brush_type) {
switch (eBrushSculptType(*brush_type)) {
case SCULPT_BRUSH_TYPE_MASK:
name = "Mask";
break;
case SCULPT_BRUSH_TYPE_DRAW_FACE_SETS:
name = "Face Set Paint";
break;
case SCULPT_BRUSH_TYPE_PAINT:
name = "Paint Hard";
break;
case SCULPT_BRUSH_TYPE_SIMPLIFY:
name = "Density";
break;
case SCULPT_BRUSH_TYPE_DISPLACEMENT_ERASER:
name = "Erase Multires Displacement";
break;
case SCULPT_BRUSH_TYPE_DISPLACEMENT_SMEAR:
name = "Smear Multires Displacement";
break;
default:
break;
}
}
break;
case OB_MODE_VERTEX_PAINT:
name = "Paint Hard";
name = "Paint";
if (brush_type) {
switch (eBrushVertexPaintType(*brush_type)) {
case VPAINT_BRUSH_TYPE_BLUR:
name = "Blur";
break;
case VPAINT_BRUSH_TYPE_AVERAGE:
name = "Average";
break;
case VPAINT_BRUSH_TYPE_SMEAR:
name = "Smear";
break;
case VPAINT_BRUSH_TYPE_DRAW:
/* Use default, don't override. */
break;
}
}
break;
case OB_MODE_WEIGHT_PAINT:
name = "Paint";
if (brush_type) {
switch (eBrushWeightPaintType(*brush_type)) {
case WPAINT_BRUSH_TYPE_BLUR:
name = "Blur";
break;
case WPAINT_BRUSH_TYPE_AVERAGE:
name = "Average";
break;
case WPAINT_BRUSH_TYPE_SMEAR:
name = "Smear";
break;
case WPAINT_BRUSH_TYPE_DRAW:
/* Use default, don't override. */
break;
}
}
break;
case OB_MODE_TEXTURE_PAINT:
name = "Paint Hard";
if (brush_type) {
switch (eBrushImagePaintType(*brush_type)) {
case IMAGE_PAINT_BRUSH_TYPE_SOFTEN:
name = "Blur";
break;
case IMAGE_PAINT_BRUSH_TYPE_SMEAR:
name = "Smear";
break;
case IMAGE_PAINT_BRUSH_TYPE_FILL:
name = "Fill";
break;
case IMAGE_PAINT_BRUSH_TYPE_MASK:
name = "Mask";
break;
case IMAGE_PAINT_BRUSH_TYPE_CLONE:
name = "Clone";
break;
case IMAGE_PAINT_BRUSH_TYPE_DRAW:
break;
}
}
break;
case OB_MODE_SCULPT_CURVES:
name = "Comb";
if (brush_type) {
switch (eBrushCurvesSculptType(*brush_type)) {
case CURVES_SCULPT_BRUSH_TYPE_ADD:
name = "Add";
break;
case CURVES_SCULPT_BRUSH_TYPE_DELETE:
name = "Delete";
break;
case CURVES_SCULPT_BRUSH_TYPE_DENSITY:
name = "Density";
break;
case CURVES_SCULPT_BRUSH_TYPE_SELECTION_PAINT:
name = "Select";
break;
default:
break;
}
}
break;
case OB_MODE_PAINT_GPENCIL_LEGACY:
name = "Pencil";
@@ -855,12 +950,60 @@ static void paint_brush_default_essentials_name_get(
break;
case OB_MODE_VERTEX_GPENCIL_LEGACY:
name = "Paint";
if (brush_type) {
switch (eBrushGPVertexType(*brush_type)) {
case GPVERTEX_BRUSH_TYPE_BLUR:
name = "Blur";
break;
case GPVERTEX_BRUSH_TYPE_AVERAGE:
name = "Average";
break;
case GPVERTEX_BRUSH_TYPE_SMEAR:
name = "Smear";
break;
case GPVERTEX_BRUSH_TYPE_REPLACE:
name = "Replace";
break;
case GPVERTEX_BRUSH_TYPE_DRAW:
/* Use default, don't override. */
break;
case GPVERTEX_BRUSH_TYPE_TINT:
/* Unused brush type. */
BLI_assert_unreachable();
break;
}
}
break;
case OB_MODE_SCULPT_GPENCIL_LEGACY:
name = "Smooth";
if (brush_type) {
switch (eBrushGPSculptType(*brush_type)) {
case GPSCULPT_BRUSH_TYPE_CLONE:
name = "Clone";
break;
default:
break;
}
}
break;
case OB_MODE_WEIGHT_GPENCIL_LEGACY:
name = "Paint";
if (brush_type) {
switch (eBrushGPWeightType(*brush_type)) {
case GPWEIGHT_BRUSH_TYPE_BLUR:
name = "Blur";
break;
case GPWEIGHT_BRUSH_TYPE_AVERAGE:
name = "Average";
break;
case GPWEIGHT_BRUSH_TYPE_SMEAR:
name = "Smear";
break;
case GPWEIGHT_BRUSH_TYPE_DRAW:
/* Use default, don't override. */
break;
}
}
break;
default:
BLI_assert_unreachable();

View File

@@ -26,6 +26,18 @@ set_property(GLOBAL PROPERTY ICON_GEOM_NAMES
# BEGIN ICON_GEOM_NAMES
brush.gpencil_draw.erase
brush.gpencil_draw.fill
brush.paint_texture.clone
brush.paint_texture.soften
brush.paint_texture.smear
brush.paint_texture.fill
brush.paint_texture.mask
brush.paint_vertex.average
brush.paint_vertex.blur
brush.paint_vertex.replace
brush.paint_vertex.smear
brush.paint_weight.average
brush.paint_weight.blur
brush.paint_weight.smear
brush.particle.add
brush.particle.comb
brush.particle.cut
@@ -33,7 +45,12 @@ set_property(GLOBAL PROPERTY ICON_GEOM_NAMES
brush.particle.puff
brush.particle.smooth
brush.particle.weight
brush.sculpt.displacement_eraser
brush.sculpt.displacement_smear
brush.sculpt.draw_face_sets
brush.sculpt.mask
brush.sculpt.paint
brush.sculpt.simplify
brush.uv_sculpt.grab
brush.uv_sculpt.pinch
brush.uv_sculpt.relax
@@ -47,6 +64,9 @@ set_property(GLOBAL PROPERTY ICON_GEOM_NAMES
ops.curve.pen
ops.curve.radius
ops.curve.vertex_random
ops.curves.sculpt_add
ops.curves.sculpt_delete
ops.curves.sculpt_density
ops.generic.cursor
ops.generic.select
ops.generic.select_box
@@ -69,6 +89,10 @@ set_property(GLOBAL PROPERTY ICON_GEOM_NAMES
ops.gpencil.primitive_line
ops.gpencil.primitive_polyline
ops.gpencil.radius
ops.gpencil.sculpt_average
ops.gpencil.sculpt_blur
ops.gpencil.sculpt_clone
ops.gpencil.sculpt_smear
ops.gpencil.stroke_trim
ops.gpencil.transform_fill
ops.mesh.bevel