WIP: Paint: Vertex loop select #108770
|
@ -4566,6 +4566,12 @@ def km_weight_paint_vertex_selection(params):
|
|||
{"properties": [("select", False)]}),
|
||||
("paint.vert_select_more", {"type": 'NUMPAD_PLUS', "value": 'PRESS', "ctrl": True}, None),
|
||||
("paint.vert_select_less", {"type": 'NUMPAD_MINUS', "value": 'PRESS', "ctrl": True}, None),
|
||||
("paint.vert_select_loop", {"type": "LEFTMOUSE", "value": 'PRESS', "alt": True},
|
||||
{"properties": [('extend', False), ('select', True)]}),
|
||||
("paint.vert_select_loop", {"type": "LEFTMOUSE", "value": 'PRESS', "alt": True, "shift": True},
|
||||
{"properties": [('extend', True), ('select', True)]}),
|
||||
("paint.vert_select_loop", {"type": "LEFTMOUSE", "value": 'PRESS', "alt": True, "shift": True, "ctrl": True},
|
||||
{"properties": [('extend', True), ('select', False)]}),
|
||||
])
|
||||
|
||||
return keymap
|
||||
|
|
|
@ -464,6 +464,7 @@ void paintvert_select_more(struct Mesh *mesh, bool face_step);
|
|||
void paintvert_select_less(struct Mesh *mesh, bool face_step);
|
||||
void paintvert_hide(struct bContext *C, struct Object *ob, bool unselected);
|
||||
void paintvert_reveal(struct bContext *C, struct Object *ob, bool select);
|
||||
void paintvert_select_loop(struct bContext *C, struct Object *ob, const int mval[2], bool select);
|
||||
|
||||
/* mirrtopo */
|
||||
typedef struct MirrTopoStore_t {
|
||||
|
|
|
@ -352,18 +352,18 @@ 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])
|
||||
static int find_closest_edge(ARegion *region,
|
||||
blender::Span<blender::int2> edges,
|
||||
blender::Span<int> edge_indices,
|
||||
blender::Span<blender::float3> vert_positions,
|
||||
const int mval[2])
|
||||
{
|
||||
using namespace blender;
|
||||
int closest_edge_index;
|
||||
int closest_edge_index = -1;
|
||||
|
||||
const float2 mval_f = {float(mval[0]), float(mval[1])};
|
||||
float min_distance = FLT_MAX;
|
||||
for (const int i : poly_edges) {
|
||||
for (const int i : edge_indices) {
|
||||
float2 screen_coordinate;
|
||||
const int2 edge = edges[i];
|
||||
const float3 edge_vert_average = math::midpoint(vert_positions[edge[0]],
|
||||
|
@ -480,7 +480,7 @@ void paintface_select_loop(bContext *C, Object *ob, const int mval[2], const boo
|
|||
const Span<int2> edges = mesh->edges();
|
||||
|
||||
const IndexRange poly = polys[poly_pick_index];
|
||||
const int closest_edge_index = find_closest_edge_in_poly(
|
||||
const int closest_edge_index = find_closest_edge(
|
||||
region, edges, corner_edges.slice(poly), verts, mval);
|
||||
|
||||
Array<int> edge_to_poly_offsets;
|
||||
|
@ -1249,3 +1249,158 @@ void paintvert_reveal(bContext *C, Object *ob, const bool select)
|
|||
paintvert_flush_flags(ob);
|
||||
paintvert_tag_select_update(C, ob);
|
||||
}
|
||||
|
||||
static int find_opposing_edge(const int edge_index,
|
||||
const int vert,
|
||||
const blender::GroupedSpan<int> vert_to_edge_map,
|
||||
const blender::GroupedSpan<int> edge_to_poly_map)
|
||||
{
|
||||
using namespace blender;
|
||||
const Span<int> edge_polys = edge_to_poly_map[edge_index];
|
||||
/* The opposing edge is the one that shares no polys with the current edge. */
|
||||
for (const int vert_edge : vert_to_edge_map[vert]) {
|
||||
bool is_opposing = true;
|
||||
if (edge_to_poly_map[vert_edge].size() != 2) {
|
||||
continue;
|
||||
}
|
||||
for (const int edge_poly : edge_to_poly_map[vert_edge]) {
|
||||
is_opposing &= !edge_polys.contains(edge_poly);
|
||||
}
|
||||
if (is_opposing) {
|
||||
return vert_edge;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static bool follow_edge_loop(const int start_vert,
|
||||
const int start_edge_index,
|
||||
const blender::GroupedSpan<int> vert_to_edge_map,
|
||||
const blender::GroupedSpan<int> edge_to_poly_map,
|
||||
const blender::Span<blender::int2> edges,
|
||||
const blender::VArray<bool> &hide_edge,
|
||||
blender::VectorSet<int> &r_edges)
|
||||
{
|
||||
int current_vert = start_vert;
|
||||
int current_edge_index = start_edge_index;
|
||||
|
||||
while (true) {
|
||||
|
||||
r_edges.add(current_edge_index);
|
||||
|
||||
current_edge_index = find_opposing_edge(
|
||||
current_edge_index, current_vert, vert_to_edge_map, edge_to_poly_map);
|
||||
|
||||
if (current_edge_index < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (edges[current_edge_index][0] == current_vert) {
|
||||
current_vert = edges[current_edge_index][1];
|
||||
}
|
||||
else if (edges[current_edge_index][1] == current_vert) {
|
||||
current_vert = edges[current_edge_index][0];
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Can only trace a loop if vert has exactly 4 edges. */
|
||||
if (vert_to_edge_map[current_vert].size() != 4) {
|
||||
/* The edge that leads to that vertex is still valid. */
|
||||
r_edges.add(current_edge_index);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hide_edge[current_edge_index]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Means the code looped around the mesh. */
|
||||
if (r_edges.contains(current_edge_index)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void paintvert_select_loop(bContext *C,
|
||||
Object *ob,
|
||||
const int region_coordinates[2],
|
||||
const bool select)
|
||||
{
|
||||
using namespace blender;
|
||||
|
||||
Mesh *mesh = BKE_mesh_from_object(ob);
|
||||
if (mesh == nullptr || mesh->totvert == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint picked_vert;
|
||||
if (!ED_mesh_pick_vert(
|
||||
C, ob, region_coordinates, ED_MESH_PICK_DEFAULT_VERT_DIST, true, &picked_vert))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const Span<float3> verts = mesh->vert_positions();
|
||||
const Span<int2> edges = mesh->edges();
|
||||
const OffsetIndices polys = mesh->polys();
|
||||
const Span<int> corner_edges = mesh->corner_edges();
|
||||
|
||||
Array<int> vert_to_edge_offsets;
|
||||
Array<int> vert_to_edge_indices;
|
||||
const GroupedSpan<int> vert_to_edge_map = bke::mesh::build_vert_to_edge_map(
|
||||
edges, mesh->totvert, vert_to_edge_offsets, vert_to_edge_indices);
|
||||
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);
|
||||
|
||||
const Span<int> vert_edges = vert_to_edge_map[picked_vert];
|
||||
ARegion *region = CTX_wm_region(C);
|
||||
RegionView3D *rv3d = static_cast<RegionView3D *>(region->regiondata);
|
||||
ED_view3d_init_mats_rv3d(ob, rv3d);
|
||||
const int closest_edge = find_closest_edge(region, edges, vert_edges, verts, region_coordinates);
|
||||
|
||||
VectorSet<int> edges_to_select;
|
||||
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
|
||||
const VArray<bool> hide_edge = *attributes.lookup_or_default<bool>(
|
||||
".hide_edge", ATTR_DOMAIN_EDGE, false);
|
||||
|
||||
const bool traced_full_loop = follow_edge_loop(edges[closest_edge][0],
|
||||
closest_edge,
|
||||
vert_to_edge_map,
|
||||
edge_to_poly_map,
|
||||
edges,
|
||||
hide_edge,
|
||||
edges_to_select);
|
||||
|
||||
if (!traced_full_loop) {
|
||||
follow_edge_loop(edges[closest_edge][1],
|
||||
closest_edge,
|
||||
vert_to_edge_map,
|
||||
edge_to_poly_map,
|
||||
edges,
|
||||
hide_edge,
|
||||
edges_to_select);
|
||||
}
|
||||
|
||||
bke::SpanAttributeWriter<bool> select_vert = attributes.lookup_or_add_for_write_span<bool>(
|
||||
".select_vert", ATTR_DOMAIN_POINT);
|
||||
|
||||
VectorSet<int> verts_to_select;
|
||||
|
||||
for (const int edge_index : edges_to_select.as_span()) {
|
||||
const int2 edge = edges[edge_index];
|
||||
verts_to_select.add(edge[0]);
|
||||
verts_to_select.add(edge[1]);
|
||||
}
|
||||
|
||||
select_vert.span.fill_indices(verts_to_select.as_span(), select);
|
||||
|
||||
select_vert.finish();
|
||||
paintvert_flush_flags(ob);
|
||||
paintvert_tag_select_update(C, ob);
|
||||
}
|
||||
|
|
|
@ -380,6 +380,7 @@ void PAINT_OT_vert_select_linked(wmOperatorType *ot);
|
|||
void PAINT_OT_vert_select_linked_pick(wmOperatorType *ot);
|
||||
void PAINT_OT_vert_select_more(wmOperatorType *ot);
|
||||
void PAINT_OT_vert_select_less(wmOperatorType *ot);
|
||||
void PAINT_OT_vert_select_loop(wmOperatorType *ot);
|
||||
|
||||
bool vert_paint_poll(bContext *C);
|
||||
bool mask_paint_poll(bContext *C);
|
||||
|
|
|
@ -1524,6 +1524,7 @@ void ED_operatortypes_paint(void)
|
|||
WM_operatortype_append(PAINT_OT_vert_select_linked_pick);
|
||||
WM_operatortype_append(PAINT_OT_vert_select_more);
|
||||
WM_operatortype_append(PAINT_OT_vert_select_less);
|
||||
WM_operatortype_append(PAINT_OT_vert_select_loop);
|
||||
|
||||
/* vertex */
|
||||
WM_operatortype_append(PAINT_OT_vertex_paint_toggle);
|
||||
|
|
|
@ -964,6 +964,34 @@ void PAINT_OT_vert_select_less(wmOperatorType *ot)
|
|||
ot->srna, "face_step", true, "Face Step", "Also deselect faces that only touch on a corner");
|
||||
}
|
||||
|
||||
static int paintvert_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) {
|
||||
paintvert_deselect_all_visible(CTX_data_active_object(C), SEL_DESELECT, false);
|
||||
}
|
||||
view3d_operator_needs_opengl(C);
|
||||
paintvert_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_vert_select_loop(wmOperatorType *ot)
|
||||
{
|
||||
ot->name = "Select Loop";
|
||||
ot->description = "Select vert loop under the cursor";
|
||||
ot->idname = "PAINT_OT_vert_select_loop";
|
||||
|
||||
ot->invoke = paintvert_select_loop_invoke;
|
||||
ot->poll = vert_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 face_select_hide_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
const bool unselected = RNA_boolean_get(op->ptr, "unselected");
|
||||
|
|
Loading…
Reference in New Issue