Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPablo Dobarro <pablodp606@gmail.com>2019-09-09 17:20:40 +0300
committerPablo Dobarro <pablodp606@gmail.com>2019-09-09 17:30:12 +0300
commit13206a6dc04eaf6f4b5eea28c135df842f43542b (patch)
tree2cca9338972c2051c69926dc23d2e27f92f37a06
parent87c7135da5dbc2df058e433b9df98de454806268 (diff)
Sculpt: Mask Filter and Dirty Mask generator
The mask filter operator modifies the whole paint mask. In includes multiple operations like smooth, grow or contrast accessible from a pie menu. The dirty mask generator is similar to Dirty Vertex Colors, but it generates a paint mask. It can be used to mask cavities in the sculpt. Reviewed By: brecht Differential Revision: https://developer.blender.org/D5496
-rw-r--r--release/scripts/presets/keyconfig/keymap_data/blender_default.py1
-rw-r--r--release/scripts/startup/bl_ui/space_view3d.py61
-rw-r--r--source/blender/editors/sculpt_paint/sculpt.c474
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_intern.h2
4 files changed, 508 insertions, 30 deletions
diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
index 6d4c3616ca4..328edf3a1fb 100644
--- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py
+++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
@@ -3900,6 +3900,7 @@ def km_sculpt(params):
{"properties": [("data_path", 'tool_settings.sculpt.brush.use_smooth_stroke')]}),
op_menu("VIEW3D_MT_angle_control", {"type": 'R', "value": 'PRESS'}),
op_panel("VIEW3D_PT_sculpt_context_menu", params.context_menu_event),
+ op_menu_pie("VIEW3D_MT_sculpt_mask_edit_pie", {"type" : 'A', "value": 'PRESS'})
])
if params.legacy:
diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py
index 69fa5bc8f4b..5351fe07ed3 100644
--- a/release/scripts/startup/bl_ui/space_view3d.py
+++ b/release/scripts/startup/bl_ui/space_view3d.py
@@ -2845,6 +2845,36 @@ class VIEW3D_MT_sculpt(Menu):
props = layout.operator("view3d.select_box", text="Box Mask")
props = layout.operator("paint.mask_lasso_gesture", text="Lasso Mask")
+ layout.separator()
+
+ props = layout.operator("sculpt.mask_filter", text='Smooth Mask')
+ props.filter_type = 'SMOOTH'
+ props.auto_iteration_count = True;
+
+ props = layout.operator("sculpt.mask_filter", text='Sharpen Mask')
+ props.filter_type = 'SHARPEN'
+ props.auto_iteration_count = True;
+
+ props = layout.operator("sculpt.mask_filter", text='Grow Mask')
+ props.filter_type = 'GROW'
+ props.auto_iteration_count = True;
+
+ props = layout.operator("sculpt.mask_filter", text='Shrink Mask')
+ props.filter_type = 'SHRINK'
+ props.auto_iteration_count = True;
+
+ props = layout.operator("sculpt.mask_filter", text='Increase Contrast')
+ props.filter_type = 'CONTRAST_INCREASE'
+ props.auto_iteration_count = False;
+
+ props = layout.operator("sculpt.mask_filter", text='Decrease Contrast')
+ props.filter_type = 'CONTRAST_DECREASE'
+ props.auto_iteration_count = False;
+
+ layout.separator()
+
+ props = layout.operator("sculpt.dirty_mask", text='Dirty Mask')
+
class VIEW3D_MT_particle(Menu):
bl_label = "Particle"
@@ -4783,6 +4813,36 @@ class VIEW3D_MT_proportional_editing_falloff_pie(Menu):
pie.prop(tool_settings, "proportional_edit_falloff", expand=True)
+class VIEW3D_MT_sculpt_mask_edit_pie(Menu):
+ bl_label = "Mask Edit"
+
+ def draw(self, _context):
+ layout = self.layout
+ pie = layout.menu_pie()
+
+ op = pie.operator("paint.mask_flood_fill", text='Invert Mask')
+ op.mode = 'INVERT'
+ op = pie.operator("paint.mask_flood_fill", text='Clear Mask')
+ op.mode = 'VALUE'
+ op = pie.operator("sculpt.mask_filter", text='Smooth Mask')
+ op.filter_type = 'SMOOTH'
+ op.auto_iteration_count = True;
+ op = pie.operator("sculpt.mask_filter", text='Sharpen Mask')
+ op.filter_type = 'SHARPEN'
+ op.auto_iteration_count = True;
+ op = pie.operator("sculpt.mask_filter", text='Grow Mask')
+ op.filter_type = 'GROW'
+ op.auto_iteration_count = True;
+ op = pie.operator("sculpt.mask_filter", text='Shrink Mask')
+ op.filter_type = 'SHRINK'
+ op.auto_iteration_count = True;
+ op = pie.operator("sculpt.mask_filter", text='Increase Contrast')
+ op.filter_type = 'CONTRAST_INCREASE'
+ op.auto_iteration_count = False;
+ op = pie.operator("sculpt.mask_filter", text='Decrease Contrast')
+ op.filter_type = 'CONTRAST_DECREASE'
+ op.auto_iteration_count = False;
+
# ********** Panel **********
@@ -6766,6 +6826,7 @@ classes = (
VIEW3D_MT_snap_pie,
VIEW3D_MT_orientations_pie,
VIEW3D_MT_proportional_editing_falloff_pie,
+ VIEW3D_MT_sculpt_mask_edit_pie,
VIEW3D_PT_active_tool,
VIEW3D_PT_active_tool_duplicate,
VIEW3D_PT_view3d_properties,
diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c
index da1ee8c3310..69b23040993 100644
--- a/source/blender/editors/sculpt_paint/sculpt.c
+++ b/source/blender/editors/sculpt_paint/sculpt.c
@@ -116,8 +116,6 @@ static void sculpt_vertex_random_access_init(SculptSession *ss)
}
}
-#if 0 /* UNUSED */
-
static int sculpt_active_vertex_get(SculptSession *ss)
{
switch (BKE_pbvh_type(ss->pbvh)) {
@@ -142,7 +140,6 @@ static int sculpt_vertex_count_get(SculptSession *ss)
}
}
-
static void sculpt_vertex_normal_get(SculptSession *ss, int index, float no[3])
{
switch (BKE_pbvh_type(ss->pbvh)) {
@@ -156,20 +153,6 @@ static void sculpt_vertex_normal_get(SculptSession *ss, int index, float no[3])
}
}
-static void sculpt_vertex_co_set(SculptSession *ss, int index, float co[3])
-{
- switch (BKE_pbvh_type(ss->pbvh)) {
- case PBVH_FACES:
- copy_v3_v3(ss->mvert[index].co, co);
- return;
- case PBVH_BMESH:
- copy_v3_v3(BM_vert_at_index(BKE_pbvh_get_bmesh(ss->pbvh), index)->co, co);
- return;
- default:
- return;
- }
-}
-
static void sculpt_vertex_mask_set(SculptSession *ss, int index, float mask)
{
BMVert *v;
@@ -204,6 +187,20 @@ static float sculpt_vertex_mask_get(SculptSession *ss, int index)
}
}
+static void sculpt_vertex_co_set(SculptSession *ss, int index, float co[3])
+{
+ switch (BKE_pbvh_type(ss->pbvh)) {
+ case PBVH_FACES:
+ copy_v3_v3(ss->mvert[index].co, co);
+ return;
+ case PBVH_BMESH:
+ copy_v3_v3(BM_vert_at_index(BKE_pbvh_get_bmesh(ss->pbvh), index)->co, co);
+ return;
+ default:
+ return;
+ }
+}
+
static void sculpt_vertex_tag_update(SculptSession *ss, int index)
{
switch (BKE_pbvh_type(ss->pbvh)) {
@@ -217,7 +214,7 @@ static void sculpt_vertex_tag_update(SculptSession *ss, int index)
}
}
-# define SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY 256
+#define SCULPT_VERTEX_NEIGHBOR_FIXED_CAPACITY 256
typedef struct SculptVertexNeighborIter {
int *neighbors;
@@ -320,19 +317,29 @@ static void sculpt_vertex_neighbors_get(SculptSession *ss,
}
}
-# define sculpt_vertex_neighbors_iter_begin(ss, v_index, neighbor_iterator) \
- sculpt_vertex_neighbors_get(ss, v_index, &neighbor_iterator); \
- for (neighbor_iterator.i = 0; neighbor_iterator.i < neighbor_iterator.size; \
- neighbor_iterator.i++) { \
- neighbor_iterator.index = ni.neighbors[ni.i];
+#define sculpt_vertex_neighbors_iter_begin(ss, v_index, neighbor_iterator) \
+ sculpt_vertex_neighbors_get(ss, v_index, &neighbor_iterator); \
+ for (neighbor_iterator.i = 0; neighbor_iterator.i < neighbor_iterator.size; \
+ neighbor_iterator.i++) { \
+ neighbor_iterator.index = ni.neighbors[ni.i];
-# define sculpt_vertex_neighbors_iter_end(neighbor_iterator) \
- } \
- if (neighbor_iterator.neighbors != neighbor_iterator.neighbors_fixed) { \
- MEM_freeN(neighbor_iterator.neighbors); \
- }
+#define sculpt_vertex_neighbors_iter_end(neighbor_iterator) \
+ } \
+ if (neighbor_iterator.neighbors != neighbor_iterator.neighbors_fixed) { \
+ MEM_freeN(neighbor_iterator.neighbors); \
+ }
-#endif /* UNUSED */
+/* Utils */
+static void sculpt_vertex_mask_clamp(SculptSession *ss, int index, float min, float max)
+{
+ float mask = sculpt_vertex_mask_get(ss, index);
+ if (mask > max) {
+ sculpt_vertex_mask_set(ss, index, max);
+ }
+ else if (mask < min) {
+ sculpt_vertex_mask_set(ss, index, min);
+ }
+}
/** \name Tool Capabilities
*
@@ -7267,7 +7274,6 @@ static void SCULPT_OT_set_detail_size(wmOperatorType *ot)
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
-
static void filter_cache_init_task_cb(void *__restrict userdata,
const int i,
const TaskParallelTLS *__restrict UNUSED(tls))
@@ -7608,6 +7614,412 @@ static void SCULPT_OT_mesh_filter(struct wmOperatorType *ot)
"Apply the deformation in the selected axis");
}
+typedef enum eSculptMaskFilterTypes {
+ MASK_FILTER_SMOOTH = 0,
+ MASK_FILTER_SHARPEN = 1,
+ MASK_FILTER_GROW = 2,
+ MASK_FILTER_SHRINK = 3,
+ MASK_FILTER_CONTRAST_INCREASE = 5,
+ MASK_FILTER_CONTRAST_DECREASE = 6,
+} eSculptMaskFilterTypes;
+
+EnumPropertyItem prop_mask_filter_types[] = {
+ {MASK_FILTER_SMOOTH, "SMOOTH", 0, "Smooth Mask", "Smooth mask"},
+ {MASK_FILTER_SHARPEN, "SHARPEN", 0, "Sharpen Mask", "Sharpen mask"},
+ {MASK_FILTER_GROW, "GROW", 0, "Grow Mask", "Grow mask"},
+ {MASK_FILTER_SHRINK, "SHRINK", 0, "Shrink Mask", "Shrink mask"},
+ {MASK_FILTER_CONTRAST_INCREASE,
+ "CONTRAST_INCREASE",
+ 0,
+ "Increase contrast",
+ "Increase the contrast of the paint mask"},
+ {MASK_FILTER_CONTRAST_DECREASE,
+ "CONTRAST_DECREASE",
+ 0,
+ "Decrease contrast",
+ "Decrease the contrast of the paint mask"},
+ {0, NULL, 0, NULL, NULL},
+};
+
+static void mask_filter_task_cb(void *__restrict userdata,
+ const int i,
+ const TaskParallelTLS *__restrict UNUSED(tls))
+{
+ SculptThreadedTaskData *data = userdata;
+ SculptSession *ss = data->ob->sculpt;
+ PBVHNode *node = data->nodes[i];
+ bool update = false;
+
+ const int mode = data->filter_type;
+ float contrast = 0.0f;
+
+ PBVHVertexIter vd;
+
+ if (mode == MASK_FILTER_CONTRAST_INCREASE) {
+ contrast = 0.1f;
+ }
+
+ if (mode == MASK_FILTER_CONTRAST_DECREASE) {
+ contrast = -0.1f;
+ }
+
+ BKE_pbvh_vertex_iter_begin(ss->pbvh, node, vd, PBVH_ITER_UNIQUE)
+ {
+ float val;
+ float delta, gain, offset, max, min;
+ float prev_val = *vd.mask;
+ SculptVertexNeighborIter ni;
+ switch (mode) {
+ case MASK_FILTER_SMOOTH:
+ if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES) {
+ val = neighbor_average_mask(ss, vd.index) - *vd.mask;
+ }
+ else {
+ val = bmesh_neighbor_average_mask(vd.bm_vert, vd.cd_vert_mask_offset) - *vd.mask;
+ }
+ *vd.mask += val;
+ break;
+ case MASK_FILTER_SHARPEN:
+ if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES) {
+ val = neighbor_average_mask(ss, vd.index) - *vd.mask;
+ }
+ else {
+ val = bmesh_neighbor_average_mask(vd.bm_vert, vd.cd_vert_mask_offset) - *vd.mask;
+ }
+ if (*vd.mask > 0.5f) {
+ *vd.mask += 0.05f;
+ }
+ else {
+ *vd.mask -= 0.05f;
+ }
+ *vd.mask += val / 2;
+ break;
+ case MASK_FILTER_GROW:
+ max = 0.0f;
+ sculpt_vertex_neighbors_iter_begin(ss, vd.index, ni)
+ {
+ float vmask_f = data->prev_mask[ni.index];
+ if (vmask_f > max) {
+ max = vmask_f;
+ }
+ }
+ sculpt_vertex_neighbors_iter_end(ni);
+ *vd.mask = max;
+ break;
+ case MASK_FILTER_SHRINK:
+ min = 1.0f;
+ sculpt_vertex_neighbors_iter_begin(ss, vd.index, ni)
+ {
+ float vmask_f = data->prev_mask[ni.index];
+ if (vmask_f < min) {
+ min = vmask_f;
+ }
+ }
+ sculpt_vertex_neighbors_iter_end(ni);
+ *vd.mask = min;
+ break;
+ case MASK_FILTER_CONTRAST_INCREASE:
+ case MASK_FILTER_CONTRAST_DECREASE:
+ delta = contrast / 2.0f;
+ gain = 1.0f - delta * 2.0f;
+ if (contrast > 0) {
+ gain = 1.0f / ((gain != 0.0f) ? gain : FLT_EPSILON);
+ offset = gain * (-delta);
+ }
+ else {
+ delta *= -1;
+ offset = gain * (delta);
+ }
+ *vd.mask = gain * (*vd.mask) + offset;
+ break;
+ }
+ CLAMP(*vd.mask, 0.0f, 1.0f);
+ if (*vd.mask != prev_val) {
+ update = true;
+ }
+ if (vd.mvert) {
+ vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+ }
+ }
+ BKE_pbvh_vertex_iter_end;
+
+ if (update) {
+ BKE_pbvh_node_mark_redraw(node);
+ }
+}
+
+static int sculpt_mask_filter_exec(bContext *C, wmOperator *op)
+{
+ ARegion *ar = CTX_wm_region(C);
+ Object *ob = CTX_data_active_object(C);
+ SculptSession *ss = ob->sculpt;
+ Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
+ PBVH *pbvh = ob->sculpt->pbvh;
+ PBVHNode **nodes;
+ Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
+ int totnode;
+ int filter_type = RNA_enum_get(op->ptr, "filter_type");
+
+ BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true);
+
+ sculpt_vertex_random_access_init(ss);
+
+ if (!ob->sculpt->pmap) {
+ return OPERATOR_CANCELLED;
+ }
+
+ int num_verts = sculpt_vertex_count_get(ss);
+
+ BKE_pbvh_search_gather(pbvh, NULL, NULL, &nodes, &totnode);
+ sculpt_undo_push_begin("Mask filter");
+
+ for (int i = 0; i < totnode; i++) {
+ sculpt_undo_push_node(ob, nodes[i], SCULPT_UNDO_MASK);
+ }
+
+ float *prev_mask = NULL;
+ int iterations = RNA_int_get(op->ptr, "iterations");
+
+ /* Auto iteration count calculates the number of iteration based on the vertices of the mesh to
+ * avoid adding an unnecesary ammount of undo steps when using the operator from a shortcut. One
+ * iteration per 50000 vertices in the mesh should be fine in most cases. Maybe we want this to
+ * be configurable */
+ if (RNA_boolean_get(op->ptr, "auto_iteration_count")) {
+ iterations = (int)(num_verts / 50000.0f) + 1;
+ }
+
+ for (int i = 0; i < iterations; i++) {
+ if (ELEM(filter_type, MASK_FILTER_GROW, MASK_FILTER_SHRINK)) {
+ prev_mask = MEM_mallocN((unsigned long)num_verts * sizeof(float), "prevmask");
+ for (int j = 0; j < num_verts; j++) {
+ prev_mask[j] = sculpt_vertex_mask_get(ss, j);
+ }
+ }
+
+ SculptThreadedTaskData data = {
+ .sd = sd,
+ .ob = ob,
+ .nodes = nodes,
+ .filter_type = filter_type,
+ .prev_mask = prev_mask,
+ };
+
+ TaskParallelSettings settings;
+ BLI_parallel_range_settings_defaults(&settings);
+ settings.use_threading = ((sd->flags & SCULPT_USE_OPENMP) && totnode > SCULPT_THREADED_LIMIT);
+ BLI_task_parallel_range(0, totnode, &data, mask_filter_task_cb, &settings);
+
+ if (ELEM(filter_type, MASK_FILTER_GROW, MASK_FILTER_SHRINK)) {
+ MEM_freeN(prev_mask);
+ }
+ }
+
+ if (nodes) {
+ MEM_freeN(nodes);
+ }
+
+ sculpt_undo_push_end();
+
+ ED_region_tag_redraw(ar);
+ WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob);
+ return OPERATOR_FINISHED;
+}
+
+static void SCULPT_OT_mask_filter(struct wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Mask Filter";
+ ot->idname = "SCULPT_OT_mask_filter";
+ ot->description = "Applies a filter to modify the current mask";
+
+ /* api callbacks */
+ ot->exec = sculpt_mask_filter_exec;
+ ot->poll = sculpt_mode_poll;
+
+ ot->flag = OPTYPE_REGISTER;
+
+ /* rna */
+ RNA_def_enum(ot->srna,
+ "filter_type",
+ prop_mask_filter_types,
+ MASK_FILTER_SMOOTH,
+ "Type",
+ "Filter that is going to be applied to the mask");
+ RNA_def_int(ot->srna,
+ "iterations",
+ 1,
+ 1,
+ 100,
+ "Iterations",
+ "Number of times that the filter is going to be applied",
+ 1,
+ 100);
+ RNA_def_boolean(
+ ot->srna,
+ "auto_iteration_count",
+ false,
+ "Auto Iteration Count",
+ "Use a automatic number of iterations based on the number of vertices of the sculpt");
+}
+
+static float neighbor_dirty_mask(SculptSession *ss, const int vert)
+{
+ int total = 0;
+ float avg[3];
+ zero_v3(avg);
+
+ SculptVertexNeighborIter ni;
+ sculpt_vertex_neighbors_iter_begin(ss, vert, ni)
+ {
+ float normalized[3];
+ sub_v3_v3v3(normalized, sculpt_vertex_co_get(ss, ni.index), sculpt_vertex_co_get(ss, vert));
+ normalize_v3(normalized);
+ add_v3_v3(avg, normalized);
+ total++;
+ }
+ sculpt_vertex_neighbors_iter_end(ni);
+
+ if (total > 0) {
+ mul_v3_fl(avg, 1.0f / total);
+ float normal[3];
+ sculpt_vertex_normal_get(ss, vert, normal);
+ float dot = dot_v3v3(avg, normal);
+ float angle = max_ff(saacosf(dot), 0.0f);
+ return angle;
+ }
+ return 0;
+}
+
+static void dirty_mask_task_cb(void *__restrict userdata,
+ const int i,
+ const TaskParallelTLS *__restrict UNUSED(tls))
+{
+ SculptThreadedTaskData *data = userdata;
+ SculptSession *ss = data->ob->sculpt;
+ PBVHNode *node = data->nodes[i];
+ PBVHVertexIter vd;
+ BKE_pbvh_vertex_iter_begin(ss->pbvh, node, vd, PBVH_ITER_UNIQUE)
+ {
+ float val;
+ val = neighbor_dirty_mask(ss, vd.index);
+ data->prev_mask[vd.index] = val;
+ if (vd.mvert) {
+ vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+ }
+ }
+ BKE_pbvh_vertex_iter_end;
+ BKE_pbvh_node_mark_redraw(node);
+}
+
+static int sculpt_dirty_mask_exec(bContext *C, wmOperator *op)
+{
+ ARegion *ar = CTX_wm_region(C);
+ Object *ob = CTX_data_active_object(C);
+ SculptSession *ss = ob->sculpt;
+ Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
+ PBVH *pbvh = ob->sculpt->pbvh;
+ PBVHNode **nodes;
+ Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
+ int totnode;
+
+ BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true);
+
+ sculpt_vertex_random_access_init(ss);
+
+ if (!ob->sculpt->pmap) {
+ return OPERATOR_CANCELLED;
+ }
+
+ int num_verts = sculpt_vertex_count_get(ss);
+
+ BKE_pbvh_search_gather(pbvh, NULL, NULL, &nodes, &totnode);
+ sculpt_undo_push_begin("Dirty Mask");
+
+ for (int i = 0; i < totnode; i++) {
+ sculpt_undo_push_node(ob, nodes[i], SCULPT_UNDO_MASK);
+ }
+
+ float *prev_mask = NULL;
+
+ prev_mask = MEM_mallocN((unsigned long)num_verts * sizeof(float), "prevmask");
+ for (int j = 0; j < num_verts; j++) {
+ prev_mask[j] = sculpt_vertex_mask_get(ss, j);
+ }
+
+ SculptThreadedTaskData data = {
+ .sd = sd,
+ .ob = ob,
+ .nodes = nodes,
+ .prev_mask = prev_mask,
+ };
+
+ TaskParallelSettings settings;
+ BLI_parallel_range_settings_defaults(&settings);
+ settings.use_threading = ((sd->flags & SCULPT_USE_OPENMP) && totnode > SCULPT_THREADED_LIMIT);
+ BLI_task_parallel_range(0, totnode, &data, dirty_mask_task_cb, &settings);
+
+ float min = FLT_MAX;
+ float max = FLT_MIN;
+ for (int i = 0; i < num_verts; i++) {
+ float val = prev_mask[i];
+ if (val < min) {
+ min = val;
+ }
+ if (val > max) {
+ max = val;
+ }
+ }
+
+ float range = max - min;
+ if (range < 0.0001f) {
+ range = 0;
+ }
+ else {
+ range = 1.0f / range;
+ }
+
+ bool dirty_only = RNA_boolean_get(op->ptr, "dirty_only");
+ for (int i = 0; i < num_verts; i++) {
+ sculpt_vertex_mask_set(
+ ss, i, sculpt_vertex_mask_get(ss, i) + (1 - ((prev_mask[i] - min) * range)));
+ if (dirty_only) {
+ sculpt_vertex_mask_set(ss, i, fminf(sculpt_vertex_mask_get(ss, i), 0.5f) * 2.0f);
+ }
+ sculpt_vertex_mask_clamp(ss, i, 0.0f, 1.0f);
+ }
+ MEM_freeN(prev_mask);
+
+ if (nodes) {
+ MEM_freeN(nodes);
+ }
+
+ sculpt_undo_push_end();
+
+ ED_region_tag_redraw(ar);
+
+ WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob);
+
+ return OPERATOR_FINISHED;
+}
+
+static void SCULPT_OT_dirty_mask(struct wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Dirty Mask";
+ ot->idname = "SCULPT_OT_dirty_mask";
+ ot->description = "Generates a mask based on the geometry cavity and pointiness";
+
+ /* api callbacks */
+ ot->exec = sculpt_dirty_mask_exec;
+ ot->poll = sculpt_mode_poll;
+
+ ot->flag = OPTYPE_REGISTER;
+
+ /* rna */
+ RNA_def_boolean(
+ ot->srna, "dirty_only", false, "Dirty Only", "Don't calculate cleans for convex areas");
+}
+
void ED_operatortypes_sculpt(void)
{
WM_operatortype_append(SCULPT_OT_brush_stroke);
@@ -7620,4 +8032,6 @@ void ED_operatortypes_sculpt(void)
WM_operatortype_append(SCULPT_OT_sample_detail_size);
WM_operatortype_append(SCULPT_OT_set_detail_size);
WM_operatortype_append(SCULPT_OT_mesh_filter);
+ WM_operatortype_append(SCULPT_OT_mask_filter);
+ WM_operatortype_append(SCULPT_OT_dirty_mask);
}
diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h
index a248476fd49..359929b7005 100644
--- a/source/blender/editors/sculpt_paint/sculpt_intern.h
+++ b/source/blender/editors/sculpt_paint/sculpt_intern.h
@@ -179,6 +179,8 @@ typedef struct SculptThreadedTaskData {
float filter_strength;
int *node_mask;
+ float *prev_mask;
+
ThreadMutex mutex;
} SculptThreadedTaskData;