diff --git a/scripts/presets/keyconfig/keymap_data/blender_default.py b/scripts/presets/keyconfig/keymap_data/blender_default.py index c4ba8924173..bbac7beb549 100644 --- a/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -4410,6 +4410,8 @@ def km_face_mask(params): {"properties": [("deselect", False)]}), ("paint.face_select_linked_pick", {"type": 'L', "value": 'PRESS', "shift": True}, {"properties": [("deselect", True)]}), + ("paint.face_select_more", {"type": 'NUMPAD_PLUS', "value": 'PRESS', "ctrl": True}, None), + ("paint.face_select_less", {"type": 'NUMPAD_MINUS', "value": 'PRESS', "ctrl": True}, None), ]) return keymap diff --git a/scripts/startup/bl_ui/space_view3d.py b/scripts/startup/bl_ui/space_view3d.py index b60988353fc..204459dba6d 100644 --- a/scripts/startup/bl_ui/space_view3d.py +++ b/scripts/startup/bl_ui/space_view3d.py @@ -2011,6 +2011,9 @@ class VIEW3D_MT_select_paint_mask(Menu): layout.operator("paint.face_select_all", text="None").action = 'DESELECT' layout.operator("paint.face_select_all", text="Invert").action = 'INVERT' + layout.operator("paint.face_select_more") + layout.operator("paint.face_select_less") + layout.separator() layout.operator("view3d.select_box") diff --git a/source/blender/editors/include/ED_mesh.h b/source/blender/editors/include/ED_mesh.h index 1d00f4f288d..0fb4e20c3cf 100644 --- a/source/blender/editors/include/ED_mesh.h +++ b/source/blender/editors/include/ED_mesh.h @@ -419,6 +419,11 @@ void paintface_select_linked(struct bContext *C, struct Object *ob, const int mval[2], bool select); +/** Grow the selection of faces. + * \param face_step If true will also select faces that only touch on the corner. + */ +void paintface_select_more(struct Mesh *mesh, bool face_step); +void paintface_select_less(struct Mesh *mesh, bool face_step); bool paintface_minmax(struct Object *ob, float r_min[3], float r_max[3]); void paintface_hide(struct bContext *C, struct Object *ob, bool unselected); diff --git a/source/blender/editors/mesh/editface.cc b/source/blender/editors/mesh/editface.cc index cc4d950d64d..06efa802708 100644 --- a/source/blender/editors/mesh/editface.cc +++ b/source/blender/editors/mesh/editface.cc @@ -350,6 +350,131 @@ void paintface_select_linked(bContext *C, Object *ob, const int mval[2], const b paintface_flush_flags(C, ob, true, false); } +static bool poly_has_selected_neighbor(blender::Span poly_edges, + blender::Span edges, + blender::Span select_vert, + const bool face_step) +{ + for (const int edge_index : poly_edges) { + const MEdge &edge = edges[edge_index]; + /* If a poly is selected, all of its verts are selected too, meaning that neighboring faces + * will have some vertices selected. */ + if (face_step) { + if (select_vert[edge.v1] || select_vert[edge.v2]) { + return true; + } + } + else { + if (select_vert[edge.v1] && select_vert[edge.v2]) { + return true; + } + } + } + return false; +} + +void paintface_select_more(Mesh *mesh, const bool face_step) +{ + using namespace blender; + + bke::MutableAttributeAccessor attributes = mesh->attributes_for_write(); + bke::SpanAttributeWriter select_poly = attributes.lookup_or_add_for_write_span( + ".select_poly", ATTR_DOMAIN_FACE); + bke::SpanAttributeWriter select_vert = attributes.lookup_or_add_for_write_span( + ".select_vert", ATTR_DOMAIN_POINT); + const VArray hide_poly = attributes.lookup_or_default( + ".hide_poly", ATTR_DOMAIN_FACE, false); + + const Span polys = mesh->polys(); + const Span corner_edges = mesh->corner_edges(); + const Span edges = mesh->edges(); + + threading::parallel_for(select_poly.span.index_range(), 1024, [&](const IndexRange range) { + for (const int i : range) { + if (select_poly.span[i] || hide_poly[i]) { + continue; + } + const MPoly &poly = polys[i]; + if (poly_has_selected_neighbor(corner_edges.slice(poly.loopstart, poly.totloop), + edges, + select_vert.span, + face_step)) { + select_poly.span[i] = true; + } + } + }); + + select_poly.finish(); + select_vert.finish(); +} + +static bool poly_has_unselected_neighbor(blender::Span poly_edges, + blender::Span edges, + blender::BitSpan verts_of_unselected_faces, + const bool face_step) +{ + for (const int edge_index : poly_edges) { + const MEdge &edge = edges[edge_index]; + if (face_step) { + if (verts_of_unselected_faces[edge.v1] || verts_of_unselected_faces[edge.v2]) { + return true; + } + } + else { + if (verts_of_unselected_faces[edge.v1] && verts_of_unselected_faces[edge.v2]) { + return true; + } + } + } + return false; +} + +void paintface_select_less(Mesh *mesh, const bool face_step) +{ + using namespace blender; + + bke::MutableAttributeAccessor attributes = mesh->attributes_for_write(); + bke::SpanAttributeWriter select_poly = attributes.lookup_or_add_for_write_span( + ".select_poly", ATTR_DOMAIN_FACE); + const VArray hide_poly = attributes.lookup_or_default( + ".hide_poly", ATTR_DOMAIN_FACE, false); + + const Span polys = mesh->polys(); + const Span corner_verts = mesh->corner_verts(); + const Span corner_edges = mesh->corner_edges(); + const Span edges = mesh->edges(); + + BitVector<> verts_of_unselected_faces(mesh->totvert); + + /* Find all vertices of unselected faces to help find neighboring faces after. */ + for (const int i : polys.index_range()) { + if (select_poly.span[i]) { + continue; + } + const MPoly &poly = polys[i]; + for (const int vert : corner_verts.slice(poly.loopstart, poly.totloop)) { + verts_of_unselected_faces[vert].set(true); + } + } + + threading::parallel_for(polys.index_range(), 1024, [&](const IndexRange range) { + for (const int i : range) { + if (!select_poly.span[i] || hide_poly[i]) { + continue; + } + const MPoly &poly = polys[i]; + if (poly_has_unselected_neighbor(corner_edges.slice(poly.loopstart, poly.totloop), + edges, + verts_of_unselected_faces, + face_step)) { + select_poly.span[i] = false; + } + } + }); + + select_poly.finish(); +} + bool paintface_deselect_all_visible(bContext *C, Object *ob, int action, bool flush_flags) { using namespace blender; diff --git a/source/blender/editors/sculpt_paint/paint_intern.h b/source/blender/editors/sculpt_paint/paint_intern.h index f1071695908..e8751d12ba7 100644 --- a/source/blender/editors/sculpt_paint/paint_intern.h +++ b/source/blender/editors/sculpt_paint/paint_intern.h @@ -378,6 +378,8 @@ void BRUSH_OT_sculpt_curves_falloff_preset(struct wmOperatorType *ot); void PAINT_OT_face_select_linked(struct wmOperatorType *ot); void PAINT_OT_face_select_linked_pick(struct wmOperatorType *ot); void PAINT_OT_face_select_all(struct wmOperatorType *ot); +void PAINT_OT_face_select_more(struct wmOperatorType *ot); +void PAINT_OT_face_select_less(struct wmOperatorType *ot); void PAINT_OT_face_select_hide(struct wmOperatorType *ot); void PAINT_OT_face_vert_reveal(struct wmOperatorType *ot); diff --git a/source/blender/editors/sculpt_paint/paint_ops.cc b/source/blender/editors/sculpt_paint/paint_ops.cc index ca102b5a0b8..03d8b7cdb8d 100644 --- a/source/blender/editors/sculpt_paint/paint_ops.cc +++ b/source/blender/editors/sculpt_paint/paint_ops.cc @@ -1518,6 +1518,8 @@ void ED_operatortypes_paint(void) WM_operatortype_append(PAINT_OT_face_select_linked); WM_operatortype_append(PAINT_OT_face_select_linked_pick); WM_operatortype_append(PAINT_OT_face_select_all); + WM_operatortype_append(PAINT_OT_face_select_more); + WM_operatortype_append(PAINT_OT_face_select_less); WM_operatortype_append(PAINT_OT_face_select_hide); WM_operatortype_append(PAINT_OT_face_vert_reveal); diff --git a/source/blender/editors/sculpt_paint/paint_utils.c b/source/blender/editors/sculpt_paint/paint_utils.c index ba9a1c75446..9080d47e9c7 100644 --- a/source/blender/editors/sculpt_paint/paint_utils.c +++ b/source/blender/editors/sculpt_paint/paint_utils.c @@ -693,6 +693,68 @@ void PAINT_OT_face_select_all(wmOperatorType *ot) WM_operator_properties_select_all(ot); } +static int paint_select_more_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + Mesh *mesh = BKE_mesh_from_object(ob); + if (mesh == NULL || mesh->totpoly == 0) { + return OPERATOR_CANCELLED; + } + + const bool face_step = RNA_boolean_get(op->ptr, "face_step"); + paintface_select_more(mesh, face_step); + paintface_flush_flags(C, ob, true, false); + + ED_region_tag_redraw(CTX_wm_region(C)); + return OPERATOR_FINISHED; +} + +void PAINT_OT_face_select_more(wmOperatorType *ot) +{ + ot->name = "Select More"; + ot->description = "Select Faces connected to existing selection"; + ot->idname = "PAINT_OT_face_select_more"; + + ot->exec = paint_select_more_exec; + ot->poll = facemask_paint_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_boolean( + ot->srna, "face_step", true, "Face Step", "Also select faces that only touch on a corner"); +} + +static int paint_select_less_exec(bContext *C, wmOperator *op) +{ + Object *ob = CTX_data_active_object(C); + Mesh *mesh = BKE_mesh_from_object(ob); + if (mesh == NULL || mesh->totpoly == 0) { + return OPERATOR_CANCELLED; + } + + const bool face_step = RNA_boolean_get(op->ptr, "face_step"); + paintface_select_less(mesh, face_step); + paintface_flush_flags(C, ob, true, false); + + ED_region_tag_redraw(CTX_wm_region(C)); + return OPERATOR_FINISHED; +} + +void PAINT_OT_face_select_less(wmOperatorType *ot) +{ + ot->name = "Select Less"; + ot->description = "Deselect Faces connected to existing selection"; + ot->idname = "PAINT_OT_face_select_less"; + + ot->exec = paint_select_less_exec; + ot->poll = facemask_paint_poll; + + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_boolean( + ot->srna, "face_step", true, "Face Step", "Also deselect faces that only touch on a corner"); +} + static int vert_select_all_exec(bContext *C, wmOperator *op) { Object *ob = CTX_data_active_object(C);