Sculpt: Surface Smooth Brush and Mesh Filter
authorPablo Dobarro <pablodp606>
Thu, 26 Mar 2020 15:05:46 +0000 (16:05 +0100)
committerPablo Dobarro <pablodp606@gmail.com>
Thu, 26 Mar 2020 15:13:47 +0000 (16:13 +0100)
This implements the Surface Smooth Brush as a mode inside the Smooth tool,
which uses the HC algorithm from "Improved Laplacian Smoothing of Noisy Surface Meshes".
Comparted to the regular smooth brush with laplacian smooth, this brush removes
the surface while preserving the volume of the object.
The smooth result can be controlled by tweaing the original shape preservation,
displacement and iteration count.
The same surface smooth operation is also available as a mesh filter.

Reviewed By: jbakker

Differential Revision: https://developer.blender.org/D7057

release/scripts/startup/bl_ui/properties_paint_common.py
release/scripts/startup/bl_ui/space_toolsystem_toolbar.py
source/blender/blenkernel/intern/brush.c
source/blender/editors/sculpt_paint/sculpt.c
source/blender/editors/sculpt_paint/sculpt_intern.h
source/blender/makesdna/DNA_brush_types.h
source/blender/makesrna/intern/rna_brush.c

index 34afcc09407af21ce4ffa55ba0d0548d3a03bf8a..3d2474d006ade14ae668ca695ae291edd28a6240 100644 (file)
@@ -665,6 +665,14 @@ def brush_settings(layout, context, brush, popover=False):
             col.prop(brush, "use_multiplane_scrape_dynamic")
             col.prop(brush, "show_multiplane_scrape_planes_preview")
 
+        if brush.sculpt_tool == 'SMOOTH':
+            col = layout.column()
+            col.prop(brush, "smooth_deform_type")
+            if brush.smooth_deform_type == 'SURFACE':
+                col.prop(brush, "surface_smooth_shape_preservation")
+                col.prop(brush, "surface_smooth_current_vertex")
+                col.prop(brush, "surface_smooth_iterations")
+
         if brush.sculpt_tool == 'MASK':
             layout.row().prop(brush, "mask_tool", expand=True)
 
index c501c4f66a149b9dd26e3482f9ad2ab2a292f954..9a7e42bdbce14592c2e91e4952d50e38eb246c3b 100644 (file)
@@ -1031,6 +1031,9 @@ class _defs_sculpt:
             layout.prop(props, "strength")
             layout.prop(props, "deform_axis")
             layout.prop(props, "use_face_sets")
+            if (props.type == "SURFACE_SMOOTH"):
+                layout.prop(props, "surface_smooth_shape_preservation", expand=False)
+                layout.prop(props, "surface_smooth_current_vertex", expand=False)
 
         return dict(
             idname="builtin.mesh_filter",
index e6045c45dc86631064f27b28e995ae1b3d9bfa0d..2306b04602663aa4a58eda02605ba835dca9e2f0 100644 (file)
@@ -1348,6 +1348,9 @@ void BKE_brush_sculpt_reset(Brush *br)
       br->flag &= ~BRUSH_SPACE_ATTEN;
       br->spacing = 5;
       br->alpha = 0.7f;
+      br->surface_smooth_shape_preservation = 0.5f;
+      br->surface_smooth_current_vertex = 0.5f;
+      br->surface_smooth_iterations = 4;
       break;
     case SCULPT_TOOL_SNAKE_HOOK:
       br->alpha = 1.0f;
index ea3fb5624dd25e21ab455eaae04b49af87894bb8..a99b6e44d1845ae9c15ef6d4efb5067117950ed2 100644 (file)
@@ -914,6 +914,7 @@ static bool sculpt_tool_needs_original(const char sculpt_tool)
               SCULPT_TOOL_LAYER,
               SCULPT_TOOL_DRAW_SHARP,
               SCULPT_TOOL_ELASTIC_DEFORM,
+              SCULPT_TOOL_SMOOTH,
               SCULPT_TOOL_POSE);
 }
 
@@ -2859,7 +2860,7 @@ static float bmesh_neighbor_average_mask(BMVert *v, const int cd_vert_mask_offse
   }
 }
 
-static void grids_neighbor_average(SculptSession *ss, float result[3], int index)
+static void SCULPT_neighbor_coords_average(SculptSession *ss, float result[3], int index)
 {
   float avg[3] = {0.0f, 0.0f, 0.0f};
   int total = 0;
@@ -3155,7 +3156,7 @@ static void do_smooth_brush_multires_task_cb_ex(void *__restrict userdata,
       }
       else {
         float avg[3], val[3];
-        grids_neighbor_average(ss, avg, vd.index);
+        SCULPT_neighbor_coords_average(ss, avg, vd.index);
         sub_v3_v3v3(val, avg, vd.co);
         madd_v3_v3v3fl(val, vd.co, val, fade);
         sculpt_clip(sd, ss, vd.co, val);
@@ -3254,6 +3255,153 @@ static void do_smooth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnod
   SculptSession *ss = ob->sculpt;
   smooth(sd, ob, nodes, totnode, ss->cache->bstrength, false);
 }
+/* HC Smooth Algorithm. */
+/* From: Improved Laplacian Smoothing of Noisy Surface Meshes */
+
+static void surface_smooth_laplacian_step(SculptSession *ss,
+                                          float *disp,
+                                          const float co[3],
+                                          float (*laplacian_disp)[3],
+                                          const int v_index,
+                                          const float origco[3],
+                                          const float alpha)
+{
+  float laplacian_smooth_co[3];
+  float weigthed_o[3], weigthed_q[3], d[3];
+  SCULPT_neighbor_coords_average(ss, laplacian_smooth_co, v_index);
+
+  mul_v3_v3fl(weigthed_o, origco, alpha);
+  mul_v3_v3fl(weigthed_q, co, 1.0f - alpha);
+  add_v3_v3v3(d, weigthed_o, weigthed_q);
+  sub_v3_v3v3(laplacian_disp[v_index], laplacian_smooth_co, d);
+
+  sub_v3_v3v3(disp, laplacian_smooth_co, co);
+}
+
+static void surface_smooth_displace_step(SculptSession *ss,
+                                         float *co,
+                                         float (*laplacian_disp)[3],
+                                         const int v_index,
+                                         const float beta,
+                                         const float fade)
+{
+  float b_avg[3] = {0.0f, 0.0f, 0.0f};
+  float b_current_vertex[3];
+  int total = 0;
+  SculptVertexNeighborIter ni;
+  sculpt_vertex_neighbors_iter_begin(ss, v_index, ni)
+  {
+    add_v3_v3(b_avg, laplacian_disp[ni.index]);
+    total++;
+  }
+  sculpt_vertex_neighbors_iter_end(ni);
+  if (total > 0) {
+    mul_v3_v3fl(b_current_vertex, b_avg, (1.0f - beta) / (float)total);
+    madd_v3_v3fl(b_current_vertex, laplacian_disp[v_index], beta);
+    mul_v3_fl(b_current_vertex, clamp_f(fade, 0.0f, 1.0f));
+    sub_v3_v3(co, b_current_vertex);
+  }
+}
+
+static void do_surface_smooth_brush_laplacian_task_cb_ex(void *__restrict userdata,
+                                                         const int n,
+                                                         const TaskParallelTLS *__restrict tls)
+{
+  SculptThreadedTaskData *data = userdata;
+  SculptSession *ss = data->ob->sculpt;
+  const Brush *brush = data->brush;
+  const float bstrength = ss->cache->bstrength;
+  float alpha = brush->surface_smooth_shape_preservation;
+
+  PBVHVertexIter vd;
+  SculptOrigVertData orig_data;
+
+  SculptBrushTest test;
+  SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
+      ss, &test, data->brush->falloff_shape);
+
+  SCULPT_orig_vert_data_init(&orig_data, data->ob, data->nodes[n]);
+
+  BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
+  {
+    SCULPT_orig_vert_data_update(&orig_data, &vd);
+    if (sculpt_brush_test_sq_fn(&test, vd.co)) {
+      const float fade =
+          bstrength *
+          SCULPT_brush_strength_factor(
+              ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, 0.0f, vd.index, tls->thread_id);
+
+      float disp[3];
+      surface_smooth_laplacian_step(ss,
+                                    disp,
+                                    vd.co,
+                                    ss->cache->surface_smooth_laplacian_disp,
+                                    vd.index,
+                                    orig_data.co,
+                                    alpha);
+      madd_v3_v3fl(vd.co, disp, clamp_f(fade, 0.0f, 1.0f));
+    }
+    BKE_pbvh_vertex_iter_end;
+  }
+}
+
+static void do_surface_smooth_brush_displace_task_cb_ex(void *__restrict userdata,
+                                                        const int n,
+                                                        const TaskParallelTLS *__restrict tls)
+{
+  SculptThreadedTaskData *data = userdata;
+  SculptSession *ss = data->ob->sculpt;
+  const Brush *brush = data->brush;
+  const float bstrength = ss->cache->bstrength;
+  const float beta = brush->surface_smooth_current_vertex;
+
+  PBVHVertexIter vd;
+
+  SculptBrushTest test;
+  SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
+      ss, &test, data->brush->falloff_shape);
+
+  BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
+  {
+    if (sculpt_brush_test_sq_fn(&test, vd.co)) {
+      const float fade =
+          bstrength *
+          SCULPT_brush_strength_factor(
+              ss, brush, vd.co, sqrtf(test.dist), vd.no, vd.fno, 0.0f, vd.index, tls->thread_id);
+      surface_smooth_displace_step(
+          ss, vd.co, ss->cache->surface_smooth_laplacian_disp, vd.index, beta, fade);
+    }
+  }
+  BKE_pbvh_vertex_iter_end;
+}
+
+static void do_surface_smooth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
+{
+  Brush *brush = BKE_paint_brush(&sd->paint);
+  SculptSession *ss = ob->sculpt;
+
+  if (ss->cache->mirror_symmetry_pass == 0 && ss->cache->radial_symmetry_pass == 0) {
+    ss->cache->surface_smooth_laplacian_disp = MEM_callocN(
+        SCULPT_vertex_count_get(ss) * 3 * sizeof(float), "HC smooth laplacian b");
+  }
+
+  /* Threaded loop over nodes. */
+  SculptThreadedTaskData data = {
+      .sd = sd,
+      .ob = ob,
+      .brush = brush,
+      .nodes = nodes,
+  };
+
+  PBVHParallelSettings settings;
+  BKE_pbvh_parallel_range_settings(&settings, (sd->flags & SCULPT_USE_OPENMP), totnode);
+  for (int i = 0; i < brush->surface_smooth_iterations; i++) {
+    BKE_pbvh_parallel_range(
+        0, totnode, &data, do_surface_smooth_brush_laplacian_task_cb_ex, &settings);
+    BKE_pbvh_parallel_range(
+        0, totnode, &data, do_surface_smooth_brush_displace_task_cb_ex, &settings);
+  }
+}
 
 static void do_mask_brush_draw_task_cb_ex(void *__restrict userdata,
                                           const int n,
@@ -6010,7 +6158,12 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
         do_draw_brush(sd, ob, nodes, totnode);
         break;
       case SCULPT_TOOL_SMOOTH:
-        do_smooth_brush(sd, ob, nodes, totnode);
+        if (brush->smooth_deform_type == BRUSH_SMOOTH_DEFORM_LAPLACIAN) {
+          do_smooth_brush(sd, ob, nodes, totnode);
+        }
+        else if (brush->smooth_deform_type == BRUSH_SMOOTH_DEFORM_SURFACE) {
+          do_surface_smooth_brush(sd, ob, nodes, totnode);
+        }
         break;
       case SCULPT_TOOL_CREASE:
         do_crease_brush(sd, ob, nodes, totnode);
@@ -9178,7 +9331,9 @@ static void sculpt_filter_cache_free(SculptSession *ss)
   if (ss->filter_cache->automask) {
     MEM_freeN(ss->filter_cache->automask);
   }
-
+  if (ss->filter_cache->surface_smooth_laplacian_disp) {
+    MEM_freeN(ss->filter_cache->surface_smooth_laplacian_disp);
+  }
   MEM_freeN(ss->filter_cache);
   ss->filter_cache = NULL;
 }
@@ -9191,6 +9346,7 @@ typedef enum eSculptMeshFilterTypes {
   MESH_FILTER_RANDOM = 4,
   MESH_FILTER_RELAX = 5,
   MESH_FILTER_RELAX_FACE_SETS = 6,
+  MESH_FILTER_SURFACE_SMOOTH = 7,
 } eSculptMeshFilterTypes;
 
 static EnumPropertyItem prop_mesh_filter_types[] = {
@@ -9205,6 +9361,11 @@ static EnumPropertyItem prop_mesh_filter_types[] = {
      0,
      "Relax Face Sets",
      "Smooth the edges of all the Face Sets"},
+    {MESH_FILTER_SURFACE_SMOOTH,
+     "SURFACE_SMOOTH",
+     0,
+     "Surface Smooth",
+     "Smooth the surface of the mesh, preserving the volume"},
     {0, NULL, 0, NULL, NULL},
 };
 
@@ -9223,8 +9384,11 @@ static EnumPropertyItem prop_mesh_filter_deform_axis_items[] = {
 
 static bool sculpt_mesh_filter_needs_pmap(int filter_type, bool use_face_sets)
 {
-  return use_face_sets ||
-         ELEM(filter_type, MESH_FILTER_SMOOTH, MESH_FILTER_RELAX, MESH_FILTER_RELAX_FACE_SETS);
+  return use_face_sets || ELEM(filter_type,
+              MESH_FILTER_SMOOTH,
+              MESH_FILTER_RELAX,
+              MESH_FILTER_RELAX_FACE_SETS,
+              MESH_FILTER_SURFACE_SMOOTH);
 }
 
 static void mesh_filter_task_cb(void *__restrict userdata,
@@ -9296,7 +9460,7 @@ static void mesh_filter_task_cb(void *__restrict userdata,
             bmesh_neighbor_average(avg, vd.bm_vert);
             break;
           case PBVH_GRIDS:
-            grids_neighbor_average(ss, avg, vd.index);
+            SCULPT_neighbor_coords_average(ss, avg, vd.index);
             break;
         }
         sub_v3_v3v3(val, avg, orig_co);
@@ -9357,6 +9521,16 @@ static void mesh_filter_task_cb(void *__restrict userdata,
         sub_v3_v3v3(disp, val, vd.co);
         break;
       }
+      case MESH_FILTER_SURFACE_SMOOTH: {
+        surface_smooth_laplacian_step(ss,
+                                      disp,
+                                      vd.co,
+                                      ss->filter_cache->surface_smooth_laplacian_disp,
+                                      vd.index,
+                                      orig_data.co,
+                                      ss->filter_cache->surface_smooth_shape_preservation);
+        break;
+      }
     }
 
     for (int it = 0; it < 3; it++) {
@@ -9365,7 +9539,12 @@ static void mesh_filter_task_cb(void *__restrict userdata,
       }
     }
 
-    add_v3_v3v3(final_pos, orig_co, disp);
+    if (filter_type == MESH_FILTER_SURFACE_SMOOTH) {
+      madd_v3_v3v3fl(final_pos, vd.co, disp, clamp_f(fade, 0.0f, 1.0f));
+    }
+    else {
+      add_v3_v3v3(final_pos, orig_co, disp);
+    }
     copy_v3_v3(vd.co, final_pos);
     if (vd.mvert) {
       vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
@@ -9376,6 +9555,32 @@ static void mesh_filter_task_cb(void *__restrict userdata,
   BKE_pbvh_node_mark_update(node);
 }
 
+static void mesh_filter_surface_smooth_displace_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 fade = vd.mask ? *vd.mask : 0.0f;
+    fade = 1.0f - fade;
+    fade *= data->filter_strength;
+    if (fade == 0.0f) {
+      continue;
+    }
+    surface_smooth_displace_step(ss,
+                                 vd.co,
+                                 ss->filter_cache->surface_smooth_laplacian_disp,
+                                 vd.index,
+                                 ss->filter_cache->surface_smooth_current_vertex,
+                                 clamp_f(fade, 0.0f, 1.0f));
+  }
+  BKE_pbvh_vertex_iter_end;
+}
+
 static int sculpt_mesh_filter_modal(bContext *C, wmOperator *op, const wmEvent *event)
 {
   Object *ob = CTX_data_active_object(C);
@@ -9418,6 +9623,14 @@ static int sculpt_mesh_filter_modal(bContext *C, wmOperator *op, const wmEvent *
       &settings, (sd->flags & SCULPT_USE_OPENMP), ss->filter_cache->totnode);
   BKE_pbvh_parallel_range(0, ss->filter_cache->totnode, &data, mesh_filter_task_cb, &settings);
 
+  if (filter_type == MESH_FILTER_SURFACE_SMOOTH) {
+    BKE_pbvh_parallel_range(0,
+                            ss->filter_cache->totnode,
+                            &data,
+                            mesh_filter_surface_smooth_displace_task_cb,
+                            &settings);
+  }
+
   ss->filter_cache->iteration_count++;
 
   if (ss->deform_modifiers_active || ss->shapekey_active) {
@@ -9480,6 +9693,15 @@ static int sculpt_mesh_filter_invoke(bContext *C, wmOperator *op, const wmEvent
     ss->filter_cache->active_face_set = SCULPT_FACE_SET_NONE;
   }
 
+  if (RNA_enum_get(op->ptr, "type") == MESH_FILTER_SURFACE_SMOOTH) {
+    ss->filter_cache->surface_smooth_laplacian_disp = MEM_mallocN(
+        3 * sizeof(float) * SCULPT_vertex_count_get(ss), "surface smooth disp");
+    ss->filter_cache->surface_smooth_shape_preservation = RNA_float_get(
+        op->ptr, "surface_smooth_shape_preservation");
+    ss->filter_cache->surface_smooth_current_vertex = RNA_float_get(
+        op->ptr, "surface_smooth_current_vertex");
+  }
+
   ss->filter_cache->enabled_axis[0] = deform_axis & MESH_FILTER_DEFORM_X;
   ss->filter_cache->enabled_axis[1] = deform_axis & MESH_FILTER_DEFORM_Y;
   ss->filter_cache->enabled_axis[2] = deform_axis & MESH_FILTER_DEFORM_Z;
@@ -9532,6 +9754,26 @@ static void SCULPT_OT_mesh_filter(struct wmOperatorType *ot)
                              false,
                              "Use Face Sets",
                              "Apply the filter only to the Face Mask under the cursor");
+
+  /* Surface Smooth Mesh Filter properties. */
+  RNA_def_float(ot->srna,
+                "surface_smooth_shape_preservation",
+                0.5f,
+                0.0f,
+                1.0f,
+                "Shape Preservation",
+                "How much of the original shape is preserved when smoothing",
+                0.0f,
+                1.0f);
+  RNA_def_float(ot->srna,
+                "surface_smooth_current_vertex",
+                0.5f,
+                0.0f,
+                1.0f,
+                "Per Vertex Displacement",
+                "How much the position of each individual vertex influences the final result",
+                0.0f,
+                1.0f);
 }
 
 typedef enum eSculptMaskFilterTypes {
index b8235dc5c957d945517aceb0403dd25bbd9966a3..d8e29d0e773fe237552888bc874cda7042cf19f3 100644 (file)
@@ -618,6 +618,10 @@ typedef struct StrokeCache {
   float initial_normal[3];
   float true_initial_normal[3];
 
+  /* Surface Smooth Brush */
+  /* Stores the displacement produced by the laplacian step of HC smooth. */
+  float (*surface_smooth_laplacian_disp)[3];
+
   float vertex_rotation; /* amount to rotate the vertices when using rotate brush */
   struct Dial *dial;
 
@@ -650,6 +654,11 @@ typedef struct FilterCache {
    * achieve certain effects. */
   int iteration_count;
 
+  /* Stores the displacement produced by the laplacian step of HC smooth. */
+  float (*surface_smooth_laplacian_disp)[3];
+  float surface_smooth_shape_preservation;
+  float surface_smooth_current_vertex;
+
   /* unmasked nodes */
   PBVHNode **nodes;
   int totnode;
index 1fad828e8878b39b43bc274c9e42e306e90f25e8..59e9d3be58d06550b35bc600924754164af4c1eb 100644 (file)
@@ -281,6 +281,11 @@ typedef enum eBrushClothDeformType {
   BRUSH_CLOTH_DEFORM_EXPAND = 6,
 } eBrushClothDeformType;
 
+typedef enum eBrushSmoothDeformType {
+  BRUSH_SMOOTH_DEFORM_LAPLACIAN = 0,
+  BRUSH_SMOOTH_DEFORM_SURFACE = 1,
+} eBrushSmoothDeformType;
+
 typedef enum eBrushClothForceFalloffType {
   BRUSH_CLOTH_FORCE_FALLOFF_RADIAL = 0,
   BRUSH_CLOTH_FORCE_FALLOFF_PLANE = 1,
@@ -473,6 +478,12 @@ typedef struct Brush {
   float cloth_sim_limit;
   float cloth_sim_falloff;
 
+  /* smooth */
+  int smooth_deform_type;
+  float surface_smooth_shape_preservation;
+  float surface_smooth_current_vertex;
+  int surface_smooth_iterations;
+
   /* multiplane scrape */
   float multiplane_scrape_angle;
 
index a40917184879f4cabd8fcd21529f90c38edd6457..1a163c9e2eb25222db1d04251a278b70f44e0e3a 100644 (file)
@@ -1794,6 +1794,20 @@ static void rna_def_brush(BlenderRNA *brna)
       {0, NULL, 0, NULL, NULL},
   };
 
+  static const EnumPropertyItem brush_smooth_deform_type_items[] = {
+      {BRUSH_SMOOTH_DEFORM_LAPLACIAN,
+       "LAPLACIAN",
+       0,
+       "Laplacian",
+       "Smooths the surface and the volume"},
+      {BRUSH_SMOOTH_DEFORM_SURFACE,
+       "SURFACE",
+       0,
+       "Surface",
+       "Smooths the surface of the mesh, preserving the volue"},
+      {0, NULL, 0, NULL, NULL},
+  };
+
   srna = RNA_def_struct(brna, "Brush", "ID");
   RNA_def_struct_ui_text(
       srna, "Brush", "Brush data-block for storing brush settings for painting and sculpting");
@@ -1909,6 +1923,11 @@ static void rna_def_brush(BlenderRNA *brna)
       prop, "Force Falloff", "Shape used in the brush to apply force to the cloth");
   RNA_def_property_update(prop, 0, "rna_Brush_update");
 
+  prop = RNA_def_property(srna, "smooth_deform_type", PROP_ENUM, PROP_NONE);
+  RNA_def_property_enum_items(prop, brush_smooth_deform_type_items);
+  RNA_def_property_ui_text(prop, "Deformation", "Deformation type that is used in the brush");
+  RNA_def_property_update(prop, 0, "rna_Brush_update");
+
   prop = RNA_def_property(srna, "jitter_unit", PROP_ENUM, PROP_NONE); /* as an enum */
   RNA_def_property_enum_bitflag_sdna(prop, NULL, "flag");
   RNA_def_property_enum_items(prop, brush_jitter_unit_items);
@@ -2101,6 +2120,29 @@ static void rna_def_brush(BlenderRNA *brna)
       prop, "Pose Origin Offset", "Offset of the pose origin in relation to the brush radius");
   RNA_def_property_update(prop, 0, "rna_Brush_update");
 
+  prop = RNA_def_property(srna, "surface_smooth_shape_preservation", PROP_FLOAT, PROP_FACTOR);
+  RNA_def_property_float_sdna(prop, NULL, "surface_smooth_shape_preservation");
+  RNA_def_property_range(prop, 0.0f, 1.0f);
+  RNA_def_property_ui_text(
+      prop, "Shape Preservation", "How much of the original shape is preserved when smoothing");
+  RNA_def_property_update(prop, 0, "rna_Brush_update");
+
+  prop = RNA_def_property(srna, "surface_smooth_current_vertex", PROP_FLOAT, PROP_FACTOR);
+  RNA_def_property_float_sdna(prop, NULL, "surface_smooth_current_vertex");
+  RNA_def_property_range(prop, 0.0f, 1.0f);
+  RNA_def_property_ui_text(
+      prop,
+      "Per Vertex Displacement",
+      "How much the position of each individual vertex influences the final result");
+  RNA_def_property_update(prop, 0, "rna_Brush_update");
+
+  prop = RNA_def_property(srna, "surface_smooth_iterations", PROP_INT, PROP_UNSIGNED);
+  RNA_def_property_int_sdna(prop, NULL, "surface_smooth_iterations");
+  RNA_def_property_range(prop, 1, 10);
+  RNA_def_property_ui_range(prop, 1, 10, 1, 3);
+  RNA_def_property_ui_text(prop, "Iterations", "Number of smoothing iterations per brush step");
+  RNA_def_property_update(prop, 0, "rna_Brush_update");
+
   prop = RNA_def_property(srna, "multiplane_scrape_angle", PROP_FLOAT, PROP_FACTOR);
   RNA_def_property_float_sdna(prop, NULL, "multiplane_scrape_angle");
   RNA_def_property_range(prop, 0.0f, 160.0f);