Sculpt: Mask Filter and Dirty Mask generator
authorPablo Dobarro <pablodp606@gmail.com>
Mon, 9 Sep 2019 14:20:40 +0000 (16:20 +0200)
committerPablo Dobarro <pablodp606@gmail.com>
Mon, 9 Sep 2019 14:30:12 +0000 (16:30 +0200)
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

release/scripts/presets/keyconfig/keymap_data/blender_default.py
release/scripts/startup/bl_ui/space_view3d.py
source/blender/editors/sculpt_paint/sculpt.c
source/blender/editors/sculpt_paint/sculpt_intern.h

index 6d4c3616ca4328c3eba9d783fb356303d2fda56b..328edf3a1fbd8038f8e186ebdbf2d1c55c4e0fe0 100644 (file)
@@ -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:
index 69fa5bc8f4bd0bc8e81eb3da55726a587cef97fe..5351fe07ed3fb7a7ebde32a96167c5326068b230 100644 (file)
@@ -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,
index da1ee8c33101c3902e773109babc23629f26748e..69b230409937952f48e29eebcdbe97f421229daf 100644 (file)
@@ -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);
 }
index a248476fd490d2da23c373eaab550e4903874b61..359929b70056bfa89e2fa8d987c14e7e1327dedd 100644 (file)
@@ -179,6 +179,8 @@ typedef struct SculptThreadedTaskData {
   float filter_strength;
   int *node_mask;
 
+  float *prev_mask;
+
   ThreadMutex mutex;
 
 } SculptThreadedTaskData;