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:
-rw-r--r--release/scripts/startup/bl_ui/space_view3d_toolbar.py7
-rw-r--r--source/blender/blenkernel/intern/brush.c9
-rw-r--r--source/blender/blenloader/intern/versioning_defaults.c8
-rw-r--r--source/blender/editors/sculpt_paint/paint_cursor.c82
-rw-r--r--source/blender/editors/sculpt_paint/sculpt.c348
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_intern.h6
-rw-r--r--source/blender/makesdna/DNA_brush_types.h17
-rw-r--r--source/blender/makesrna/intern/rna_brush.c22
8 files changed, 496 insertions, 3 deletions
diff --git a/release/scripts/startup/bl_ui/space_view3d_toolbar.py b/release/scripts/startup/bl_ui/space_view3d_toolbar.py
index efd26c2daf8..63e5aa53077 100644
--- a/release/scripts/startup/bl_ui/space_view3d_toolbar.py
+++ b/release/scripts/startup/bl_ui/space_view3d_toolbar.py
@@ -430,6 +430,13 @@ class VIEW3D_PT_tools_brush(Panel, View3DPaintPanel):
col.separator()
row = col.row()
row.prop(brush, "use_grab_active_vertex")
+ elif brush.sculpt_tool == 'MULTIPLANE_SCRAPE':
+ row = col.row()
+ row.prop(brush, "multiplane_scrape_angle")
+ row = col.row()
+ row.prop(brush, "use_multiplane_scrape_dynamic")
+ row = col.row()
+ row.prop(brush, "show_multiplane_scrape_planes_preview")
# topology_rake_factor
if (
diff --git a/source/blender/blenkernel/intern/brush.c b/source/blender/blenkernel/intern/brush.c
index eebed72103f..794876ec444 100644
--- a/source/blender/blenkernel/intern/brush.c
+++ b/source/blender/blenkernel/intern/brush.c
@@ -938,6 +938,14 @@ void BKE_brush_sculpt_reset(Brush *br)
br->curve_preset = BRUSH_CURVE_SPHERE;
br->spacing = 6;
break;
+ case SCULPT_TOOL_MULTIPLANE_SCRAPE:
+ br->flag2 |= BRUSH_MULTIPLANE_SCRAPE_DYNAMIC | BRUSH_MULTIPLANE_SCRAPE_PLANES_PREVIEW;
+ br->alpha = 0.7f;
+ br->normal_radius_factor = 0.70f;
+ br->multiplane_scrape_angle = 60;
+ br->curve_preset = BRUSH_CURVE_SMOOTH;
+ br->spacing = 5;
+ break;
case SCULPT_TOOL_CREASE:
br->flag |= BRUSH_DIR_IN;
br->alpha = 0.25;
@@ -1010,6 +1018,7 @@ void BKE_brush_sculpt_reset(Brush *br)
case SCULPT_TOOL_FLATTEN:
case SCULPT_TOOL_FILL:
case SCULPT_TOOL_SCRAPE:
+ case SCULPT_TOOL_MULTIPLANE_SCRAPE:
br->add_col[0] = 1.0f;
br->add_col[1] = 0.39f;
br->add_col[2] = 0.39f;
diff --git a/source/blender/blenloader/intern/versioning_defaults.c b/source/blender/blenloader/intern/versioning_defaults.c
index 05758b446ad..45ec6eef813 100644
--- a/source/blender/blenloader/intern/versioning_defaults.c
+++ b/source/blender/blenloader/intern/versioning_defaults.c
@@ -509,6 +509,14 @@ void BLO_update_defaults_startup_blend(Main *bmain, const char *app_template)
brush->sculpt_tool = SCULPT_TOOL_POSE;
}
+ brush_name = "Multiplane Scrape";
+ brush = BLI_findstring(&bmain->brushes, brush_name, offsetof(ID, name) + 2);
+ if (!brush) {
+ brush = BKE_brush_add(bmain, brush_name, OB_MODE_SCULPT);
+ id_us_min(&brush->id);
+ brush->sculpt_tool = SCULPT_TOOL_MULTIPLANE_SCRAPE;
+ }
+
brush_name = "Simplify";
brush = BLI_findstring(&bmain->brushes, brush_name, offsetof(ID, name) + 2);
if (!brush) {
diff --git a/source/blender/editors/sculpt_paint/paint_cursor.c b/source/blender/editors/sculpt_paint/paint_cursor.c
index b9af33e7ad6..ac738f326a3 100644
--- a/source/blender/editors/sculpt_paint/paint_cursor.c
+++ b/source/blender/editors/sculpt_paint/paint_cursor.c
@@ -1214,6 +1214,70 @@ static void sculpt_geometry_preview_lines_draw(const uint gpuattr, SculptSession
}
}
+static void sculpt_multiplane_scrape_preview_draw(const uint gpuattr,
+ SculptSession *ss,
+ float *outline_col,
+ float outline_alpha)
+{
+ float local_mat_inv[4][4];
+ invert_m4_m4(local_mat_inv, ss->cache->stroke_local_mat);
+ GPU_matrix_mul(local_mat_inv);
+ float angle = ss->cache->multiplane_scrape_sampled_angle;
+ if (ss->cache->pen_flip || ss->cache->invert) {
+ angle = -angle;
+ }
+
+ float offset = ss->cache->radius * 0.25f;
+
+ float p[3] = {0.0f, 0.0f, ss->cache->radius};
+ float y_axis[3] = {0.0f, 1.0f, 0.0f};
+ float p_l[3];
+ float p_r[3];
+ float area_center[3] = {0.0f, 0.0f, 0.0f};
+ rotate_v3_v3v3fl(p_r, p, y_axis, DEG2RADF((angle + 180) * 0.5f));
+ rotate_v3_v3v3fl(p_l, p, y_axis, DEG2RADF(-(angle + 180) * 0.5f));
+
+ immBegin(GPU_PRIM_LINES, 14);
+ immVertex3f(gpuattr, area_center[0], area_center[1] + offset, area_center[2]);
+ immVertex3f(gpuattr, p_r[0], p_r[1] + offset, p_r[2]);
+ immVertex3f(gpuattr, area_center[0], area_center[1] + offset, area_center[2]);
+ immVertex3f(gpuattr, p_l[0], p_l[1] + offset, p_l[2]);
+
+ immVertex3f(gpuattr, area_center[0], area_center[1] - offset, area_center[2]);
+ immVertex3f(gpuattr, p_r[0], p_r[1] - offset, p_r[2]);
+ immVertex3f(gpuattr, area_center[0], area_center[1] - offset, area_center[2]);
+ immVertex3f(gpuattr, p_l[0], p_l[1] - offset, p_l[2]);
+
+ immVertex3f(gpuattr, area_center[0], area_center[1] - offset, area_center[2]);
+ immVertex3f(gpuattr, area_center[0], area_center[1] + offset, area_center[2]);
+
+ immVertex3f(gpuattr, p_r[0], p_r[1] - offset, p_r[2]);
+ immVertex3f(gpuattr, p_r[0], p_r[1] + offset, p_r[2]);
+
+ immVertex3f(gpuattr, p_l[0], p_l[1] - offset, p_l[2]);
+ immVertex3f(gpuattr, p_l[0], p_l[1] + offset, p_l[2]);
+
+ immEnd();
+
+ immUniformColor3fvAlpha(outline_col, outline_alpha * 0.1f);
+ immBegin(GPU_PRIM_TRIS, 12);
+ immVertex3f(gpuattr, area_center[0], area_center[1] + offset, area_center[2]);
+ immVertex3f(gpuattr, p_r[0], p_r[1] + offset, p_r[2]);
+ immVertex3f(gpuattr, p_r[0], p_r[1] - offset, p_r[2]);
+ immVertex3f(gpuattr, area_center[0], area_center[1] + offset, area_center[2]);
+ immVertex3f(gpuattr, area_center[0], area_center[1] - offset, area_center[2]);
+ immVertex3f(gpuattr, p_r[0], p_r[1] - offset, p_r[2]);
+
+ immVertex3f(gpuattr, area_center[0], area_center[1] + offset, area_center[2]);
+ immVertex3f(gpuattr, p_l[0], p_l[1] + offset, p_l[2]);
+ immVertex3f(gpuattr, p_l[0], p_l[1] - offset, p_l[2]);
+ immVertex3f(gpuattr, area_center[0], area_center[1] + offset, area_center[2]);
+ immVertex3f(gpuattr, area_center[0], area_center[1] - offset, area_center[2]);
+ immVertex3f(gpuattr, p_l[0], p_l[1] - offset, p_l[2]);
+
+ immEnd();
+}
+
static bool paint_use_2d_cursor(ePaintMode mode)
{
if (mode >= PAINT_MODE_TEXTURE_3D) {
@@ -1516,6 +1580,24 @@ static void paint_draw_cursor(bContext *C, int x, int y, void *UNUSED(unused))
}
}
+ if (brush->sculpt_tool == SCULPT_TOOL_MULTIPLANE_SCRAPE &&
+ brush->flag2 & BRUSH_MULTIPLANE_SCRAPE_PLANES_PREVIEW && !ss->cache->first_time) {
+ GPU_matrix_push_projection();
+ ED_view3d_draw_setup_view(CTX_wm_window(C),
+ CTX_data_depsgraph_pointer(C),
+ CTX_data_scene(C),
+ ar,
+ CTX_wm_view3d(C),
+ NULL,
+ NULL,
+ NULL);
+ GPU_matrix_push();
+ GPU_matrix_mul(vc.obact->obmat);
+ sculpt_multiplane_scrape_preview_draw(pos, ss, outline_col, outline_alpha);
+ GPU_matrix_pop();
+ GPU_matrix_pop_projection();
+ }
+
wmWindowViewport(win);
}
}
diff --git a/source/blender/editors/sculpt_paint/sculpt.c b/source/blender/editors/sculpt_paint/sculpt.c
index fcc2a7a08b3..6cda6ccac06 100644
--- a/source/blender/editors/sculpt_paint/sculpt.c
+++ b/source/blender/editors/sculpt_paint/sculpt.c
@@ -1771,6 +1771,10 @@ static float brush_strength(const Sculpt *sd,
return 0.125f * alpha * flip * pressure * overlap * feather;
}
+ case SCULPT_TOOL_MULTIPLANE_SCRAPE:
+ overlap = (1.0f + overlap) / 2.0f;
+ return alpha * flip * pressure * overlap * feather;
+
case SCULPT_TOOL_FILL:
case SCULPT_TOOL_SCRAPE:
case SCULPT_TOOL_FLATTEN:
@@ -4843,6 +4847,341 @@ static void do_clay_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
BKE_pbvh_parallel_range(0, totnode, &data, do_clay_brush_task_cb_ex, &settings);
}
+/* -------------------------------------------------------------------- */
+
+/** \name Sculpt Multiplane Scrape Brush
+ * \{ */
+
+typedef struct MultiplaneScrapeSampleData {
+ float area_cos[2][3];
+ float area_nos[2][3];
+ int area_count[2];
+} MultiplaneScrapeSampleData;
+
+static void calc_multiplane_scrape_surface_task_cb(void *__restrict userdata,
+ const int n,
+ const TaskParallelTLS *__restrict tls)
+{
+ SculptThreadedTaskData *data = userdata;
+ SculptSession *ss = data->ob->sculpt;
+ const Brush *brush = data->brush;
+ MultiplaneScrapeSampleData *mssd = tls->userdata_chunk;
+ float(*mat)[4] = data->mat;
+
+ PBVHVertexIter vd;
+
+ SculptBrushTest test;
+ SculptBrushTestFn sculpt_brush_test_sq_fn = sculpt_brush_test_init_with_falloff_shape(
+ ss, &test, brush->falloff_shape);
+
+ /* Apply the brush normal radius to the test before sampling */
+ float test_radius = sqrtf(test.radius_squared);
+ test_radius *= brush->normal_radius_factor;
+ test.radius_squared = test_radius * test_radius;
+
+ BKE_pbvh_vertex_iter_begin(ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE)
+ {
+
+ if (sculpt_brush_test_sq_fn(&test, vd.co)) {
+ float local_co[3];
+ float normal[3];
+ if (vd.no) {
+ normal_short_to_float_v3(normal, vd.no);
+ }
+ else {
+ copy_v3_v3(normal, vd.fno);
+ }
+ mul_v3_m4v3(local_co, mat, vd.co);
+ /* Use the brush falloff to weight the sampled normals */
+ const float fade = tex_strength(ss,
+ brush,
+ vd.co,
+ sqrtf(test.dist),
+ vd.no,
+ vd.fno,
+ vd.mask ? *vd.mask : 0.0f,
+ vd.index,
+ tls->thread_id);
+
+ /* Sample the normal and area of the +X and -X axis individually */
+ if (local_co[0] > 0.0f) {
+ madd_v3_v3fl(mssd->area_nos[0], normal, fade);
+ add_v3_v3(mssd->area_cos[0], vd.co);
+ mssd->area_count[0]++;
+ }
+ else {
+ madd_v3_v3fl(mssd->area_nos[1], normal, fade);
+ add_v3_v3(mssd->area_cos[1], vd.co);
+ mssd->area_count[1]++;
+ }
+ }
+ BKE_pbvh_vertex_iter_end;
+ }
+}
+
+static void calc_multiplane_scrape_surface_reduce(const void *__restrict UNUSED(userdata),
+ void *__restrict chunk_join,
+ void *__restrict chunk)
+{
+ MultiplaneScrapeSampleData *join = chunk_join;
+ MultiplaneScrapeSampleData *mssd = chunk;
+
+ add_v3_v3(join->area_cos[0], mssd->area_cos[0]);
+ add_v3_v3(join->area_cos[1], mssd->area_cos[1]);
+
+ add_v3_v3(join->area_nos[0], mssd->area_nos[0]);
+ add_v3_v3(join->area_nos[1], mssd->area_nos[1]);
+
+ join->area_count[0] += mssd->area_count[0];
+ join->area_count[1] += mssd->area_count[1];
+}
+
+static void do_multiplane_scrape_brush_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;
+ float(*mat)[4] = data->mat;
+ float(*scrape_planes)[4] = data->multiplane_scrape_planes;
+
+ float angle = data->multiplane_scrape_angle;
+
+ PBVHVertexIter vd;
+ float(*proxy)[3];
+ const float bstrength = fabsf(ss->cache->bstrength);
+
+ proxy = BKE_pbvh_node_add_proxy(ss->pbvh, data->nodes[n])->co;
+
+ 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)) {
+ float local_co[3];
+ bool deform = false;
+
+ mul_v3_m4v3(local_co, mat, vd.co);
+
+ if (local_co[0] > 0.0f) {
+ deform = !plane_point_side(vd.co, scrape_planes[0]);
+ }
+ else {
+ deform = !plane_point_side(vd.co, scrape_planes[1]);
+ }
+
+ if (angle < 0.0f) {
+ deform = true;
+ }
+
+ if (deform) {
+ float intr[3];
+ float val[3];
+
+ if (local_co[0] > 0.0f) {
+ closest_to_plane_normalized_v3(intr, scrape_planes[0], vd.co);
+ }
+ else {
+ closest_to_plane_normalized_v3(intr, scrape_planes[1], vd.co);
+ }
+
+ sub_v3_v3v3(val, intr, vd.co);
+ if (plane_trim(ss->cache, brush, val)) {
+ /* Deform the local space along the Y axis to avoid artifacts on curved strokes */
+ /* This produces a not round brush tip */
+ local_co[1] *= 2.0f;
+ const float fade = bstrength * tex_strength(ss,
+ brush,
+ vd.co,
+ len_v3(local_co),
+ vd.no,
+ vd.fno,
+ vd.mask ? *vd.mask : 0.0f,
+ vd.index,
+ tls->thread_id);
+
+ mul_v3_v3fl(proxy[vd.i], val, fade);
+
+ if (vd.mvert) {
+ vd.mvert->flag |= ME_VERT_PBVH_UPDATE;
+ }
+ }
+ }
+ }
+ }
+ BKE_pbvh_vertex_iter_end;
+}
+
+static void do_multiplane_scrape_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
+{
+ SculptSession *ss = ob->sculpt;
+ Brush *brush = BKE_paint_brush(&sd->paint);
+
+ const bool flip = (ss->cache->bstrength < 0.0f);
+ const float radius = flip ? -ss->cache->radius : ss->cache->radius;
+ const float offset = get_offset(sd, ss);
+ const float displace = -radius * offset;
+
+ float area_no_sp[3]; /* the sculpt-plane normal (whatever its set to) */
+ float area_no[3]; /* geometry normal */
+ float area_co[3];
+
+ float temp[3];
+ float mat[4][4];
+
+ calc_sculpt_plane(sd, ob, nodes, totnode, area_no_sp, area_co);
+
+ if (brush->sculpt_plane != SCULPT_DISP_DIR_AREA || (brush->flag & BRUSH_ORIGINAL_NORMAL)) {
+ calc_area_normal(sd, ob, nodes, totnode, area_no);
+ }
+ else {
+ copy_v3_v3(area_no, area_no_sp);
+ }
+
+ /* delay the first daub because grab delta is not setup */
+ if (ss->cache->first_time) {
+ return;
+ }
+
+ if (is_zero_v3(ss->cache->grab_delta_symmetry)) {
+ return;
+ }
+
+ mul_v3_v3v3(temp, area_no_sp, ss->cache->scale);
+ mul_v3_fl(temp, displace);
+ add_v3_v3(area_co, temp);
+
+ /* Init brush local space matrix */
+ cross_v3_v3v3(mat[0], area_no, ss->cache->grab_delta_symmetry);
+ mat[0][3] = 0;
+ cross_v3_v3v3(mat[1], area_no, mat[0]);
+ mat[1][3] = 0;
+ copy_v3_v3(mat[2], area_no);
+ mat[2][3] = 0;
+ copy_v3_v3(mat[3], ss->cache->location);
+ mat[3][3] = 1;
+ normalize_m4(mat);
+ invert_m4(mat);
+
+ float angle = brush->multiplane_scrape_angle;
+
+ /* Update matrix for the cursor preview */
+ if (ss->cache->mirror_symmetry_pass == 0) {
+ copy_m4_m4(ss->cache->stroke_local_mat, mat);
+ }
+
+ /* Dynamic mode */
+
+ if (brush->flag2 & BRUSH_MULTIPLANE_SCRAPE_DYNAMIC) {
+ /* Sample the individual normal and area center of the two areas at both sides of the cursor */
+ SculptThreadedTaskData sample_data = {
+ .sd = NULL,
+ .ob = ob,
+ .brush = brush,
+ .nodes = nodes,
+ .totnode = totnode,
+ .mat = mat,
+ };
+
+ MultiplaneScrapeSampleData mssd = {{{0}}};
+
+ PBVHParallelSettings sample_settings;
+ BKE_pbvh_parallel_range_settings(&sample_settings, (sd->flags & SCULPT_USE_OPENMP), totnode);
+ sample_settings.func_reduce = calc_multiplane_scrape_surface_reduce;
+ sample_settings.userdata_chunk = &mssd;
+ sample_settings.userdata_chunk_size = sizeof(MultiplaneScrapeSampleData);
+
+ BKE_pbvh_parallel_range(
+ 0, totnode, &sample_data, calc_multiplane_scrape_surface_task_cb, &sample_settings);
+
+ float sampled_plane_normals[2][3];
+ float sampled_plane_co[2][3];
+ float sampled_cv[2][3];
+ float mid_co[3];
+
+ /* Use the area center of both planes to detect if we are sculpting along a concave or convex
+ * edge */
+ mul_v3_v3fl(sampled_plane_co[0], mssd.area_cos[0], 1.0f / (float)mssd.area_count[0]);
+ mul_v3_v3fl(sampled_plane_co[1], mssd.area_cos[1], 1.0f / (float)mssd.area_count[1]);
+ mid_v3_v3v3(mid_co, sampled_plane_co[0], sampled_plane_co[1]);
+
+ /* Calculate the scrape planes angle based on the sampled normals */
+ mul_v3_v3fl(sampled_plane_normals[0], mssd.area_nos[0], 1.0f / (float)mssd.area_count[0]);
+ mul_v3_v3fl(sampled_plane_normals[1], mssd.area_nos[1], 1.0f / (float)mssd.area_count[1]);
+ normalize_v3(sampled_plane_normals[0]);
+ normalize_v3(sampled_plane_normals[1]);
+
+ float sampled_angle = angle_v3v3(sampled_plane_normals[0], sampled_plane_normals[1]);
+ copy_v3_v3(sampled_cv[0], area_no);
+ sub_v3_v3v3(sampled_cv[1], ss->cache->location, mid_co);
+
+ sampled_angle += DEG2RADF(brush->multiplane_scrape_angle) * ss->cache->pressure;
+
+ /* Invert the angle if we are sculpting along a concave edge */
+ if (dot_v3v3(sampled_cv[0], sampled_cv[1]) < 0.0f) {
+ sampled_angle = -sampled_angle;
+ }
+
+ /* In dynamic mode, set the angle to 0 when inverting the brush, so you can trim plane surfaces
+ * without changing the brush */
+ if (flip) {
+ sampled_angle = 0.0f;
+ }
+ else {
+ copy_v3_v3(area_co, ss->cache->location);
+ }
+
+ angle = RAD2DEGF(sampled_angle);
+ }
+ else {
+
+ /* Standard mode: Scrape with the brush property fixed angle */
+ copy_v3_v3(area_co, ss->cache->location);
+ if (flip) {
+ angle = -angle;
+ }
+ }
+
+ /* Set the angle for the cursor preview */
+ ss->cache->multiplane_scrape_sampled_angle = angle;
+
+ SculptThreadedTaskData data = {
+ .sd = sd,
+ .ob = ob,
+ .brush = brush,
+ .nodes = nodes,
+ .mat = mat,
+ .multiplane_scrape_angle = angle,
+ };
+
+ /* Calculate the final left and right scrape planes */
+ float plane_no[3];
+ float plane_no_rot[3];
+ float y_axis[3] = {0.0f, 1.0f, 0.0f};
+ float mat_inv[4][4];
+ invert_m4_m4(mat_inv, mat);
+
+ mul_v3_mat3_m4v3(plane_no, mat, area_no);
+ rotate_v3_v3v3fl(plane_no_rot, plane_no, y_axis, DEG2RADF(-angle * 0.5f));
+ mul_v3_mat3_m4v3(plane_no, mat_inv, plane_no_rot);
+ normalize_v3(plane_no);
+ plane_from_point_normal_v3(data.multiplane_scrape_planes[1], area_co, plane_no);
+
+ mul_v3_mat3_m4v3(plane_no, mat, area_no);
+ rotate_v3_v3v3fl(plane_no_rot, plane_no, y_axis, DEG2RADF(angle * 0.5f));
+ mul_v3_mat3_m4v3(plane_no, mat_inv, plane_no_rot);
+ normalize_v3(plane_no);
+ plane_from_point_normal_v3(data.multiplane_scrape_planes[0], area_co, plane_no);
+
+ PBVHParallelSettings settings;
+ BKE_pbvh_parallel_range_settings(&settings, (sd->flags & SCULPT_USE_OPENMP), totnode);
+ BKE_pbvh_parallel_range(0, totnode, &data, do_multiplane_scrape_brush_task_cb_ex, &settings);
+}
+
/** \} */
static void do_clay_strips_brush_task_cb_ex(void *__restrict userdata,
@@ -5468,6 +5807,9 @@ static void do_brush_action(Sculpt *sd, Object *ob, Brush *brush, UnifiedPaintSe
case SCULPT_TOOL_CLAY_STRIPS:
do_clay_strips_brush(sd, ob, nodes, totnode);
break;
+ case SCULPT_TOOL_MULTIPLANE_SCRAPE:
+ do_multiplane_scrape_brush(sd, ob, nodes, totnode);
+ break;
case SCULPT_TOOL_FILL:
do_fill_brush(sd, ob, nodes, totnode);
break;
@@ -5997,6 +6339,8 @@ static const char *sculpt_tool_name(Sculpt *sd)
return "Elastic Deform Brush";
case SCULPT_TOOL_POSE:
return "Pose Brush";
+ case SCULPT_TOOL_MULTIPLANE_SCRAPE:
+ return "Multiplane Scrape Brush";
}
return "Sculpting";
@@ -6260,6 +6604,7 @@ static void sculpt_update_brush_delta(UnifiedPaintSettings *ups, Object *ob, Bru
SCULPT_TOOL_ELASTIC_DEFORM,
SCULPT_TOOL_NUDGE,
SCULPT_TOOL_CLAY_STRIPS,
+ SCULPT_TOOL_MULTIPLANE_SCRAPE,
SCULPT_TOOL_SNAKE_HOOK,
SCULPT_TOOL_POSE,
SCULPT_TOOL_THUMB) ||
@@ -6295,6 +6640,7 @@ static void sculpt_update_brush_delta(UnifiedPaintSettings *ups, Object *ob, Bru
add_v3_v3(cache->grab_delta, delta);
break;
case SCULPT_TOOL_CLAY_STRIPS:
+ case SCULPT_TOOL_MULTIPLANE_SCRAPE:
case SCULPT_TOOL_NUDGE:
case SCULPT_TOOL_SNAKE_HOOK:
if (brush->flag & BRUSH_ANCHORED) {
@@ -7759,7 +8105,7 @@ static int sculpt_symmetrize_exec(bContext *C, wmOperator *UNUSED(op))
break;
case PBVH_FACES:
/* Mesh Symmetrize */
- ED_sculpt_undo_geometry_begin(ob);
+ ED_sculpt_undo_geometry_begin(ob, "mesh symmetrize");
Mesh *mesh = ob->data;
Mesh *mesh_mirror;
MirrorModifierData mmd = {0};
diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.h b/source/blender/editors/sculpt_paint/sculpt_intern.h
index 93e4a777569..0b25ab31ce0 100644
--- a/source/blender/editors/sculpt_paint/sculpt_intern.h
+++ b/source/blender/editors/sculpt_paint/sculpt_intern.h
@@ -206,6 +206,9 @@ typedef struct SculptThreadedTaskData {
float *pose_factor;
float (*transform_rot)[4], (*transform_trans)[4], (*transform_trans_inv)[4];
+ float multiplane_scrape_angle;
+ float multiplane_scrape_planes[2][4];
+
float max_distance_squared;
float nearest_vertex_search_co[3];
@@ -392,6 +395,9 @@ typedef struct StrokeCache {
float *automask;
+ float stroke_local_mat[4][4];
+ float multiplane_scrape_sampled_angle;
+
rcti previous_r; /* previous redraw rectangle */
rcti current_r; /* current redraw rectangle */
diff --git a/source/blender/makesdna/DNA_brush_types.h b/source/blender/makesdna/DNA_brush_types.h
index 67bca60e7e1..c55ab81a733 100644
--- a/source/blender/makesdna/DNA_brush_types.h
+++ b/source/blender/makesdna/DNA_brush_types.h
@@ -247,7 +247,9 @@ typedef struct Brush {
int size;
/** General purpose flags. */
int flag;
+ int flag2;
int sampling_flag;
+
/** Pressure influence for mask. */
int mask_pressure;
/** Jitter the position of the brush. */
@@ -288,7 +290,8 @@ typedef struct Brush {
/** Source for fill tool color gradient application. */
char gradient_fill_mode;
- char _pad[5];
+ char _pad0;
+
/** Projection shape (sphere, circle). */
char falloff_shape;
float falloff_angle;
@@ -307,7 +310,7 @@ typedef struct Brush {
char mask_tool;
/** Active grease pencil tool. */
char gpencil_tool;
- char _pad0[1];
+ char _pad1[5];
float autosmooth_factor;
@@ -332,6 +335,9 @@ typedef struct Brush {
/* pose */
float pose_offset;
+ /* multiplane scrape */
+ float multiplane_scrape_angle;
+
/* overlay */
int texture_overlay_alpha;
int mask_overlay_alpha;
@@ -445,6 +451,12 @@ typedef enum eBrushSamplingFlags {
BRUSH_PAINT_ANTIALIASING = (1 << 0),
} eBrushSamplingFlags;
+/* Brush.flag2 */
+typedef enum eBrushFlags2 {
+ BRUSH_MULTIPLANE_SCRAPE_DYNAMIC = (1 << 0),
+ BRUSH_MULTIPLANE_SCRAPE_PLANES_PREVIEW = (1 << 1),
+} eBrushFlags2;
+
typedef enum {
BRUSH_MASK_PRESSURE_RAMP = (1 << 1),
BRUSH_MASK_PRESSURE_CUTOFF = (1 << 2),
@@ -488,6 +500,7 @@ typedef enum eBrushSculptTool {
SCULPT_TOOL_DRAW_SHARP = 20,
SCULPT_TOOL_ELASTIC_DEFORM = 21,
SCULPT_TOOL_POSE = 22,
+ SCULPT_TOOL_MULTIPLANE_SCRAPE = 23,
} eBrushSculptTool;
/* Brush.uv_sculpt_tool */
diff --git a/source/blender/makesrna/intern/rna_brush.c b/source/blender/makesrna/intern/rna_brush.c
index 3f269299461..86b1ed92349 100644
--- a/source/blender/makesrna/intern/rna_brush.c
+++ b/source/blender/makesrna/intern/rna_brush.c
@@ -68,6 +68,7 @@ static const EnumPropertyItem sculpt_stroke_method_items[] = {
{0, NULL, 0, NULL, NULL},
};
+/* clang-format off */
const EnumPropertyItem rna_enum_brush_sculpt_tool_items[] = {
{SCULPT_TOOL_DRAW, "DRAW", ICON_BRUSH_SCULPT_DRAW, "Draw", ""},
{SCULPT_TOOL_DRAW_SHARP, "DRAW_SHARP", ICON_BRUSH_SCULPT_DRAW, "Draw Sharp", ""},
@@ -82,6 +83,7 @@ const EnumPropertyItem rna_enum_brush_sculpt_tool_items[] = {
{SCULPT_TOOL_FLATTEN, "FLATTEN", ICON_BRUSH_FLATTEN, "Flatten", ""},
{SCULPT_TOOL_FILL, "FILL", ICON_BRUSH_FILL, "Fill", ""},
{SCULPT_TOOL_SCRAPE, "SCRAPE", ICON_BRUSH_SCRAPE, "Scrape", ""},
+ {SCULPT_TOOL_MULTIPLANE_SCRAPE, "MULTIPLANE_SCRAPE", ICON_BRUSH_SCRAPE, "Multiplane Scrape", ""},
{SCULPT_TOOL_PINCH, "PINCH", ICON_BRUSH_PINCH, "Pinch", ""},
{0, "", 0, NULL, NULL},
{SCULPT_TOOL_GRAB, "GRAB", ICON_BRUSH_GRAB, "Grab", ""},
@@ -96,6 +98,7 @@ const EnumPropertyItem rna_enum_brush_sculpt_tool_items[] = {
{SCULPT_TOOL_MASK, "MASK", ICON_BRUSH_MASK, "Mask", ""},
{0, NULL, 0, NULL, NULL},
};
+/* clang-format on */
const EnumPropertyItem rna_enum_brush_uv_sculpt_tool_items[] = {
{UV_SCULPT_TOOL_GRAB, "GRAB", 0, "Grab", "Grab UVs"},
@@ -1878,6 +1881,12 @@ 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, "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);
+ RNA_def_property_ui_text(prop, "Plane Angle", "Angle between the planes of the crease");
+ RNA_def_property_update(prop, 0, "rna_Brush_update");
+
prop = RNA_def_property(srna, "auto_smooth_factor", PROP_FLOAT, PROP_FACTOR);
RNA_def_property_float_sdna(prop, NULL, "autosmooth_factor");
RNA_def_property_float_default(prop, 0);
@@ -2026,6 +2035,19 @@ static void rna_def_brush(BlenderRNA *brna)
prop = RNA_def_property(srna, "use_paint_antialiasing", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "sampling_flag", BRUSH_PAINT_ANTIALIASING);
RNA_def_property_ui_text(prop, "Antialasing", "Smooths the edges of the strokes");
+
+ prop = RNA_def_property(srna, "use_multiplane_scrape_dynamic", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flag2", BRUSH_MULTIPLANE_SCRAPE_DYNAMIC);
+ RNA_def_property_ui_text(prop,
+ "Dynamic Mode",
+ "The angle between the planes changes during the stroke to fit the "
+ "surface under the cursor");
+ RNA_def_property_update(prop, 0, "rna_Brush_update");
+
+ prop = RNA_def_property(srna, "show_multiplane_scrape_planes_preview", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flag2", BRUSH_MULTIPLANE_SCRAPE_PLANES_PREVIEW);
+ RNA_def_property_ui_text(
+ prop, "Show Cursor Preview", "Preview the scrape planes in the cursor during the stroke");
RNA_def_property_update(prop, 0, "rna_Brush_update");
prop = RNA_def_property(srna, "use_pressure_strength", PROP_BOOLEAN, PROP_NONE);