Paint: Add loop select for faces
Add loop selection identical to the behavior of ALT+Click in Edit Mode. * ALT click: select loop * ALT Shift click: add loop while retaining current selection * ALT Shift Ctrl click: deselect loop Pull Request: https://projects.blender.org/blender/blender/pulls/107653
This commit is contained in:
committed by
Christoph Lendenfeld
parent
50bfe1dfe3
commit
98d48c16a3
@@ -4510,6 +4510,12 @@ def km_face_mask(params):
|
||||
{"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),
|
||||
("paint.face_select_loop", {"type": params.select_mouse, "value": 'PRESS', "alt": True},
|
||||
{"properties": [('extend', False), ('select', True)]}),
|
||||
("paint.face_select_loop", {"type": params.select_mouse, "value": 'PRESS', "alt": True, "shift": True},
|
||||
{"properties": [('extend', True), ('select', True)]}),
|
||||
("paint.face_select_loop", {"type": params.select_mouse, "value": 'PRESS', "alt": True, "shift": True, "ctrl": True},
|
||||
{"properties": [('extend', True), ('select', False)]}),
|
||||
])
|
||||
|
||||
return keymap
|
||||
@@ -5110,8 +5116,6 @@ def km_weight_paint(params):
|
||||
("paint.weight_paint", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
|
||||
("paint.weight_sample", {"type": params.action_mouse, "value": 'PRESS', "ctrl": True}, None),
|
||||
("paint.weight_sample_group", {"type": params.action_mouse, "value": 'PRESS', "shift": True}, None),
|
||||
("paint.weight_gradient", {"type": 'LEFTMOUSE', "value": 'PRESS', "alt": True},
|
||||
{"properties": [("type", 'LINEAR')]}),
|
||||
("paint.weight_gradient", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True, "alt": True},
|
||||
{"properties": [("type", 'RADIAL')]}),
|
||||
("paint.weight_set", {"type": 'K', "value": 'PRESS', "shift": True}, None),
|
||||
|
||||
@@ -427,6 +427,8 @@ void paintface_select_linked(struct bContext *C,
|
||||
struct Object *ob,
|
||||
const int mval[2],
|
||||
bool select);
|
||||
|
||||
void paintface_select_loop(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.
|
||||
|
||||
@@ -12,7 +12,9 @@
|
||||
#include "BLI_bitmap.h"
|
||||
#include "BLI_blenlib.h"
|
||||
#include "BLI_math.h"
|
||||
#include "BLI_math_vector.hh"
|
||||
#include "BLI_task.hh"
|
||||
#include "BLI_vector_set.hh"
|
||||
|
||||
#include "IMB_imbuf.h"
|
||||
#include "IMB_imbuf_types.h"
|
||||
@@ -350,6 +352,177 @@ void paintface_select_linked(bContext *C, Object *ob, const int mval[2], const b
|
||||
paintface_flush_flags(C, ob, true, false);
|
||||
}
|
||||
|
||||
static int find_closest_edge_in_poly(ARegion *region,
|
||||
blender::Span<blender::int2> edges,
|
||||
blender::Span<int> poly_edges,
|
||||
blender::Span<blender::float3> vert_positions,
|
||||
const int mval[2])
|
||||
{
|
||||
using namespace blender;
|
||||
int closest_edge_index;
|
||||
|
||||
const float2 mval_f = {float(mval[0]), float(mval[1])};
|
||||
float min_distance = FLT_MAX;
|
||||
for (const int i : poly_edges) {
|
||||
float2 screen_coordinate;
|
||||
const int2 edge = edges[i];
|
||||
const float3 edge_vert_average = math::midpoint(vert_positions[edge[0]],
|
||||
vert_positions[edge[1]]);
|
||||
eV3DProjStatus status = ED_view3d_project_float_object(
|
||||
region, edge_vert_average, screen_coordinate, V3D_PROJ_TEST_CLIP_DEFAULT);
|
||||
if (status != V3D_PROJ_RET_OK) {
|
||||
continue;
|
||||
}
|
||||
const float distance = math::distance_squared(mval_f, screen_coordinate);
|
||||
if (distance < min_distance) {
|
||||
min_distance = distance;
|
||||
closest_edge_index = i;
|
||||
}
|
||||
}
|
||||
return closest_edge_index;
|
||||
}
|
||||
|
||||
static int get_opposing_edge_index(const blender::IndexRange poly,
|
||||
const blender::Span<int> corner_edges,
|
||||
const int current_edge_index)
|
||||
{
|
||||
const int index_in_poly = corner_edges.slice(poly).first_index(current_edge_index);
|
||||
/* Assumes that edge index of opposing face edge is always off by 2 on quads. */
|
||||
if (index_in_poly >= 2) {
|
||||
return corner_edges[poly[index_in_poly - 2]];
|
||||
}
|
||||
/* Cannot be out of bounds because of the preceding if statement: if i < 2 then i+2 < 4. */
|
||||
return corner_edges[poly[index_in_poly + 2]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Follow quads around the mesh by finding opposing edges.
|
||||
* \return True if the search has looped back on itself, finding the same index twice.
|
||||
*/
|
||||
static bool follow_face_loop(const int poly_start_index,
|
||||
const int edge_start_index,
|
||||
const blender::OffsetIndices<int> polys,
|
||||
const blender::VArray<bool> &hide_poly,
|
||||
const blender::Span<int> corner_edges,
|
||||
const blender::GroupedSpan<int> edge_to_poly_map,
|
||||
blender::VectorSet<int> &r_loop_polys)
|
||||
{
|
||||
using namespace blender;
|
||||
int current_poly_index = poly_start_index;
|
||||
int current_edge_index = edge_start_index;
|
||||
|
||||
while (current_edge_index > 0) {
|
||||
int next_poly_index = -1;
|
||||
|
||||
for (const int poly_index : edge_to_poly_map[current_edge_index]) {
|
||||
if (poly_index != current_poly_index) {
|
||||
next_poly_index = poly_index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Edge might only have 1 poly connected. */
|
||||
if (next_poly_index == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Only works on quads. */
|
||||
if (polys[next_poly_index].size() != 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Happens if we looped around the mesh. */
|
||||
if (r_loop_polys.contains(next_poly_index)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Hidden polygons stop selection. */
|
||||
if (hide_poly[next_poly_index]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
r_loop_polys.add(next_poly_index);
|
||||
|
||||
const IndexRange next_poly = polys[next_poly_index];
|
||||
current_edge_index = get_opposing_edge_index(next_poly, corner_edges, current_edge_index);
|
||||
current_poly_index = next_poly_index;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void paintface_select_loop(bContext *C, Object *ob, const int mval[2], const bool select)
|
||||
{
|
||||
using namespace blender;
|
||||
|
||||
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
|
||||
ViewContext vc;
|
||||
ED_view3d_viewcontext_init(C, &vc, depsgraph);
|
||||
ED_view3d_select_id_validate(&vc);
|
||||
|
||||
Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob);
|
||||
if (!ob_eval) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint poly_pick_index = uint(-1);
|
||||
if (!ED_mesh_pick_face(C, ob, mval, ED_MESH_PICK_DEFAULT_FACE_DIST, &poly_pick_index)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ARegion *region = CTX_wm_region(C);
|
||||
RegionView3D *rv3d = static_cast<RegionView3D *>(region->regiondata);
|
||||
ED_view3d_init_mats_rv3d(ob_eval, rv3d);
|
||||
|
||||
Mesh *mesh = BKE_mesh_from_object(ob);
|
||||
const Span<int> corner_edges = mesh->corner_edges();
|
||||
const Span<float3> verts = mesh->vert_positions();
|
||||
const OffsetIndices polys = mesh->polys();
|
||||
const Span<int2> edges = mesh->edges();
|
||||
|
||||
const IndexRange poly = polys[poly_pick_index];
|
||||
const int closest_edge_index = find_closest_edge_in_poly(
|
||||
region, edges, corner_edges.slice(poly), verts, mval);
|
||||
|
||||
Array<int> edge_to_poly_offsets;
|
||||
Array<int> edge_to_poly_indices;
|
||||
const GroupedSpan<int> edge_to_poly_map = bke::mesh::build_edge_to_poly_map(
|
||||
polys, corner_edges, mesh->totedge, edge_to_poly_offsets, edge_to_poly_indices);
|
||||
|
||||
VectorSet<int> polys_to_select;
|
||||
|
||||
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
|
||||
const VArray<bool> hide_poly = *attributes.lookup_or_default<bool>(
|
||||
".hide_poly", ATTR_DOMAIN_FACE, false);
|
||||
|
||||
const Span<int> polys_to_closest_edge = edge_to_poly_map[closest_edge_index];
|
||||
const bool traced_full_loop = follow_face_loop(polys_to_closest_edge[0],
|
||||
closest_edge_index,
|
||||
polys,
|
||||
hide_poly,
|
||||
corner_edges,
|
||||
edge_to_poly_map,
|
||||
polys_to_select);
|
||||
|
||||
if (!traced_full_loop) {
|
||||
/* Trace the other way. */
|
||||
follow_face_loop(polys_to_closest_edge[1],
|
||||
closest_edge_index,
|
||||
polys,
|
||||
hide_poly,
|
||||
corner_edges,
|
||||
edge_to_poly_map,
|
||||
polys_to_select);
|
||||
}
|
||||
|
||||
bke::SpanAttributeWriter<bool> select_poly = attributes.lookup_or_add_for_write_span<bool>(
|
||||
".select_poly", ATTR_DOMAIN_FACE);
|
||||
|
||||
select_poly.span.fill_indices(polys_to_select.as_span(), select);
|
||||
|
||||
select_poly.finish();
|
||||
paintface_flush_flags(C, ob, true, false);
|
||||
}
|
||||
|
||||
static bool poly_has_selected_neighbor(blender::Span<int> poly_edges,
|
||||
blender::Span<blender::int2> edges,
|
||||
blender::Span<bool> select_vert,
|
||||
|
||||
@@ -369,6 +369,7 @@ void PAINT_OT_face_select_all(wmOperatorType *ot);
|
||||
void PAINT_OT_face_select_more(wmOperatorType *ot);
|
||||
void PAINT_OT_face_select_less(wmOperatorType *ot);
|
||||
void PAINT_OT_face_select_hide(wmOperatorType *ot);
|
||||
void PAINT_OT_face_select_loop(wmOperatorType *ot);
|
||||
|
||||
void PAINT_OT_face_vert_reveal(wmOperatorType *ot);
|
||||
|
||||
|
||||
@@ -389,7 +389,8 @@ static int palette_color_add_exec(bContext *C, wmOperator * /*op*/)
|
||||
PAINT_MODE_TEXTURE_3D,
|
||||
PAINT_MODE_TEXTURE_2D,
|
||||
PAINT_MODE_VERTEX,
|
||||
PAINT_MODE_SCULPT)) {
|
||||
PAINT_MODE_SCULPT))
|
||||
{
|
||||
copy_v3_v3(color->rgb, BKE_brush_color_get(scene, brush));
|
||||
color->value = 0.0;
|
||||
}
|
||||
@@ -1540,6 +1541,7 @@ void ED_operatortypes_paint(void)
|
||||
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_select_loop);
|
||||
|
||||
WM_operatortype_append(PAINT_OT_face_vert_reveal);
|
||||
|
||||
|
||||
@@ -767,6 +767,34 @@ void PAINT_OT_face_select_less(wmOperatorType *ot)
|
||||
ot->srna, "face_step", true, "Face Step", "Also deselect faces that only touch on a corner");
|
||||
}
|
||||
|
||||
static int paintface_select_loop_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
||||
{
|
||||
const bool select = RNA_boolean_get(op->ptr, "select");
|
||||
const bool extend = RNA_boolean_get(op->ptr, "extend");
|
||||
if (!extend) {
|
||||
paintface_deselect_all_visible(C, CTX_data_active_object(C), SEL_DESELECT, false);
|
||||
}
|
||||
view3d_operator_needs_opengl(C);
|
||||
paintface_select_loop(C, CTX_data_active_object(C), event->mval, select);
|
||||
ED_region_tag_redraw(CTX_wm_region(C));
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
void PAINT_OT_face_select_loop(wmOperatorType *ot)
|
||||
{
|
||||
ot->name = "Select Loop";
|
||||
ot->description = "Select face loop under the cursor";
|
||||
ot->idname = "PAINT_OT_face_select_loop";
|
||||
|
||||
ot->invoke = paintface_select_loop_invoke;
|
||||
ot->poll = facemask_paint_poll;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
|
||||
RNA_def_boolean(ot->srna, "select", true, "Select", "If false, faces will be deselected");
|
||||
RNA_def_boolean(ot->srna, "extend", false, "Extend", "Extend the selection");
|
||||
}
|
||||
|
||||
static int vert_select_all_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
Object *ob = CTX_data_active_object(C);
|
||||
|
||||
Reference in New Issue
Block a user