Sculpt: Add per-brush input samples

This pull request adds the ability for users to specify input samples
on a per brush basis. The existing field in the main `Paint` struct
forces all brushes of a particular tool type to use the same value.
A new field was added to the `Brush` struct to allow for this value
to be specified there instead, and a corresponding unified value in
`UnifiedPaintSettings` has been created to allow users to use the
same value across all brushes.

Addresses #108109

Pull Request: https://projects.blender.org/blender/blender/pulls/117080
This commit is contained in:
Sean Kim 2024-01-30 05:08:23 +01:00 committed by Hans Goudey
parent dd7362d6e2
commit a2b3fe5e01
15 changed files with 138 additions and 19 deletions

View File

@ -362,7 +362,14 @@ class StrokePanel(BrushPanel):
col.row().prop(brush, "jitter_unit", expand=True)
col.separator()
col.prop(settings, "input_samples")
UnifiedPaintPanel.prop_unified(
layout,
context,
brush,
"input_samples",
unified_name="use_unified_input_samples",
slider=True,
)
class SmoothStrokePanel(BrushPanel):

View File

@ -29,7 +29,7 @@ extern "C" {
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 16
#define BLENDER_FILE_SUBVERSION 17
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and cancel loading the file, showing a warning to

View File

@ -152,6 +152,9 @@ void BKE_brush_alpha_set(Scene *scene, Brush *brush, float alpha);
float BKE_brush_weight_get(const Scene *scene, const Brush *brush);
void BKE_brush_weight_set(const Scene *scene, Brush *brush, float value);
int BKE_brush_input_samples_get(const Scene *scene, const Brush *brush);
void BKE_brush_input_samples_set(const Scene *scene, Brush *brush, int value);
bool BKE_brush_use_locked_size(const Scene *scene, const Brush *brush);
bool BKE_brush_use_alpha_pressure(const Brush *brush);
bool BKE_brush_use_size_pressure(const Brush *brush);

View File

@ -2451,6 +2451,25 @@ void BKE_brush_weight_set(const Scene *scene, Brush *brush, float value)
}
}
int BKE_brush_input_samples_get(const Scene *scene, const Brush *brush)
{
UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings;
return (ups->flag & UNIFIED_PAINT_INPUT_SAMPLES) ? ups->input_samples : brush->input_samples;
}
void BKE_brush_input_samples_set(const Scene *scene, Brush *brush, int value)
{
UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings;
if (ups->flag & UNIFIED_PAINT_INPUT_SAMPLES) {
ups->input_samples = value;
}
else {
brush->input_samples = value;
}
}
void BKE_brush_scale_unprojected_radius(float *unprojected_radius,
int new_brush_size,
int old_brush_size)

View File

@ -1248,10 +1248,6 @@ void BKE_paint_blend_write(BlendWriter *writer, Paint *p)
void BKE_paint_blend_read_data(BlendDataReader *reader, const Scene *scene, Paint *p)
{
if (p->num_input_samples < 1) {
p->num_input_samples = 1;
}
BLO_read_data_address(reader, &p->cavity_curve);
if (p->cavity_curve) {
BKE_curvemapping_blend_read(reader, p->cavity_curve);

View File

@ -2789,6 +2789,55 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 401, 17)) {
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
ToolSettings *ts = scene->toolsettings;
int input_sample_values[10];
input_sample_values[0] = ts->imapaint.paint.num_input_samples;
input_sample_values[1] = ts->sculpt != nullptr ? ts->sculpt->paint.num_input_samples : 1;
input_sample_values[2] = ts->curves_sculpt != nullptr ?
ts->curves_sculpt->paint.num_input_samples :
1;
input_sample_values[3] = ts->uvsculpt != nullptr ? ts->uvsculpt->paint.num_input_samples : 1;
input_sample_values[4] = ts->gp_paint != nullptr ? ts->gp_paint->paint.num_input_samples : 1;
input_sample_values[5] = ts->gp_vertexpaint != nullptr ?
ts->gp_vertexpaint->paint.num_input_samples :
1;
input_sample_values[6] = ts->gp_sculptpaint != nullptr ?
ts->gp_sculptpaint->paint.num_input_samples :
1;
input_sample_values[7] = ts->gp_weightpaint != nullptr ?
ts->gp_weightpaint->paint.num_input_samples :
1;
input_sample_values[8] = ts->vpaint != nullptr ? ts->vpaint->paint.num_input_samples : 1;
input_sample_values[9] = ts->wpaint != nullptr ? ts->wpaint->paint.num_input_samples : 1;
int unified_value = 1;
for (int i = 0; i < 10; i++) {
if (input_sample_values[i] != 1) {
if (unified_value == 1) {
unified_value = input_sample_values[i];
}
else {
/* In the case of a user having multiple tools with different num_input_value values
* set we cannot support this in the single UnifiedPaintSettings value, so fallback
* to 1 instead of deciding that one value is more canonical than the other.
*/
break;
}
}
}
ts->unified_paint_settings.input_samples = unified_value;
}
LISTBASE_FOREACH (Brush *, brush, &bmain->brushes) {
brush->input_samples = 1;
}
}
/**
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.

View File

@ -387,6 +387,11 @@ static void blo_update_defaults_scene(Main *bmain, Scene *scene)
if (ts->sculpt) {
ts->sculpt->automasking_boundary_edges_propagation_steps = 1;
}
/* Ensure input_samples has a correct default value of 1. */
if (ts->unified_paint_settings.input_samples == 0) {
ts->unified_paint_settings.input_samples = 1;
}
}
void BLO_update_defaults_startup_blend(Main *bmain, const char *app_template)
@ -679,6 +684,9 @@ void BLO_update_defaults_startup_blend(Main *bmain, const char *app_template)
/* Enable anti-aliasing by default. */
brush->sampling_flag |= BRUSH_PAINT_ANTIALIASING;
/* By default, each brush should use a single input sample. */
brush->input_samples = 1;
}
{

View File

@ -1158,10 +1158,10 @@ wmKeyMap *paint_stroke_modal_keymap(wmKeyConfig *keyconf)
}
static void paint_stroke_add_sample(
const Paint *paint, PaintStroke *stroke, float x, float y, float pressure)
PaintStroke *stroke, int input_samples, float x, float y, float pressure)
{
PaintSample *sample = &stroke->samples[stroke->cur_sample];
int max_samples = std::clamp(paint->num_input_samples, 1, PAINT_MAX_INPUT_SAMPLES);
int max_samples = std::clamp(input_samples, 1, PAINT_MAX_INPUT_SAMPLES);
sample->mouse[0] = x;
sample->mouse[1] = y;
@ -1431,6 +1431,7 @@ static void paint_stroke_line_constrain(PaintStroke *stroke, float mouse[2])
int paint_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event, PaintStroke **stroke_p)
{
Scene *scene = CTX_data_scene(C);
Paint *p = BKE_paint_get_active_from_context(C);
PaintMode mode = BKE_paintmode_get_active_from_context(C);
PaintStroke *stroke = *stroke_p;
@ -1460,7 +1461,8 @@ int paint_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event, PaintS
stroke->last_tablet_event_pressure = pressure;
}
paint_stroke_add_sample(p, stroke, event->mval[0], event->mval[1], pressure);
int input_samples = BKE_brush_input_samples_get(scene, br);
paint_stroke_add_sample(stroke, input_samples, event->mval[0], event->mval[1], pressure);
paint_stroke_sample_average(stroke, &sample_average);
/* Tilt. */

View File

@ -66,6 +66,7 @@
\
.jitter = 0.0f, \
\
.input_samples = 1, \
/* Dash */ \
.dash_ratio = 1.0f, \
.dash_samples = 20, \

View File

@ -204,6 +204,9 @@ typedef struct Brush {
int flag2;
int sampling_flag;
/** Number of samples used to smooth the stroke. */
int input_samples;
/** Pressure influence for mask. */
int mask_pressure;
/** Jitter the position of the brush. */
@ -260,7 +263,7 @@ typedef struct Brush {
/** Source for fill tool color gradient application. */
char gradient_fill_mode;
char _pad0[5];
char _pad0;
/** Projection shape (sphere, circle). */
char falloff_shape;

View File

@ -308,6 +308,7 @@
#define _DNA_DEFAULTS_UnifiedPaintSettings \
{ \
.size = 50, \
.input_samples = 1, \
.unprojected_radius = 0.29, \
.alpha = 0.5f, \
.weight = 0.5f, \

View File

@ -967,8 +967,12 @@ typedef struct Paint {
/** Enum #ePaintFlags. */
int flags;
/** Paint stroke can use up to PAINT_MAX_INPUT_SAMPLES inputs to smooth the stroke. */
int num_input_samples;
/**
* Paint stroke can use up to PAINT_MAX_INPUT_SAMPLES inputs to smooth the stroke.
* This value is now DEPRECATED, please refer to the Brush and UnifiedPaintSetting values
* instead.
*/
int num_input_samples DNA_DEPRECATED;
/** Flags used for symmetry. */
int symmetry_flags;
@ -1338,8 +1342,12 @@ typedef struct UnifiedPaintSettings {
/** Unified brush secondary color. */
float secondary_rgb[3];
/** Unified brush stroke input samples. */
int input_samples;
/** User preferences for sculpt and paint. */
int flag;
char _pad[4];
/* Rake rotation. */
@ -1411,6 +1419,7 @@ typedef enum {
UNIFIED_PAINT_ALPHA = (1 << 1),
UNIFIED_PAINT_WEIGHT = (1 << 5),
UNIFIED_PAINT_COLOR = (1 << 6),
UNIFIED_PAINT_INPUT_SAMPLES = (1 << 7),
/** Only used if unified size is enabled, mirrors the brush flag #BRUSH_LOCK_SIZE. */
UNIFIED_PAINT_BRUSH_LOCK_SIZE = (1 << 2),

View File

@ -2769,6 +2769,16 @@ static void rna_def_brush(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Unprojected Radius", "Radius of brush in Blender units");
RNA_def_property_update(prop, 0, "rna_Brush_size_update");
prop = RNA_def_property(srna, "input_samples", PROP_INT, PROP_UNSIGNED);
RNA_def_property_int_sdna(prop, nullptr, "input_samples");
RNA_def_property_range(prop, 1, PAINT_MAX_INPUT_SAMPLES);
RNA_def_property_ui_range(prop, 1, PAINT_MAX_INPUT_SAMPLES, 1, -1);
RNA_def_property_ui_text(
prop,
"Input Samples",
"Number of input samples to average together to smooth the brush stroke");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "jitter", PROP_FLOAT, PROP_NONE);
RNA_def_property_float_sdna(prop, nullptr, "jitter");
RNA_def_property_range(prop, 0.0f, 1000.0f);

View File

@ -4182,6 +4182,13 @@ static void rna_def_unified_paint_settings(BlenderRNA *brna)
RNA_def_property_ui_text(
prop, "Use Unified Color", "Instead of per-brush color, the color is shared across brushes");
prop = RNA_def_property(srna, "use_unified_input_samples", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "flag", UNIFIED_PAINT_INPUT_SAMPLES);
RNA_def_property_ui_text(
prop,
"Use Unified Input Samples",
"Instead of per-brush input samples, the value is shared across brushes");
/* unified paint settings that override the equivalent settings
* from the active brush */
prop = RNA_def_property(srna, "size", PROP_INT, PROP_PIXEL);
@ -4232,6 +4239,17 @@ static void rna_def_unified_paint_settings(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Secondary Color", "");
RNA_def_property_update(prop, 0, "rna_UnifiedPaintSettings_update");
prop = RNA_def_property(srna, "input_samples", PROP_INT, PROP_UNSIGNED);
RNA_def_property_int_sdna(prop, nullptr, "input_samples");
RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE);
RNA_def_property_range(prop, 1, PAINT_MAX_INPUT_SAMPLES);
RNA_def_property_ui_range(prop, 1, PAINT_MAX_INPUT_SAMPLES, 1, -1);
RNA_def_property_ui_text(
prop,
"Input Samples",
"Number of input samples to average together to smooth the brush stroke");
RNA_def_property_update(prop, 0, "rna_UnifiedPaintSettings_update");
prop = RNA_def_property(srna, "use_locked_size", PROP_ENUM, PROP_NONE); /* as an enum */
RNA_def_property_enum_bitflag_sdna(prop, nullptr, "flag");
RNA_def_property_enum_items(prop, brush_size_unit_items);

View File

@ -674,13 +674,6 @@ static void rna_def_paint(BlenderRNA *brna)
"Update the geometry when it enters the view, providing faster view navigation");
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, nullptr);
prop = RNA_def_property(srna, "input_samples", PROP_INT, PROP_UNSIGNED);
RNA_def_property_int_sdna(prop, nullptr, "num_input_samples");
RNA_def_property_ui_range(prop, 1, PAINT_MAX_INPUT_SAMPLES, 1, -1);
RNA_def_property_ui_text(
prop, "Input Samples", "Average multiple input samples together to smooth the brush stroke");
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, nullptr);
prop = RNA_def_property(srna, "use_symmetry_x", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "symmetry_flags", PAINT_SYMM_X);
RNA_def_property_ui_text(prop, "Symmetry X", "Mirror brush across the X axis");