Animation: Weight Paint select more/less for faces #105607
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<int> poly_edges,
|
||||
blender::Span<MEdge> edges,
|
||||
blender::Span<bool> 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<bool> select_poly = attributes.lookup_or_add_for_write_span<bool>(
|
||||
".select_poly", ATTR_DOMAIN_FACE);
|
||||
bke::SpanAttributeWriter<bool> select_vert = attributes.lookup_or_add_for_write_span<bool>(
|
||||
".select_vert", ATTR_DOMAIN_POINT);
|
||||
const VArray<bool> hide_poly = attributes.lookup_or_default<bool>(
|
||||
".hide_poly", ATTR_DOMAIN_FACE, false);
|
||||
|
||||
const Span<MPoly> polys = mesh->polys();
|
||||
const Span<int> corner_edges = mesh->corner_edges();
|
||||
const Span<MEdge> 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<int> poly_edges,
|
||||
blender::Span<MEdge> 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<bool> select_poly = attributes.lookup_or_add_for_write_span<bool>(
|
||||
".select_poly", ATTR_DOMAIN_FACE);
|
||||
const VArray<bool> hide_poly = attributes.lookup_or_default<bool>(
|
||||
".hide_poly", ATTR_DOMAIN_FACE, false);
|
||||
|
||||
const Span<MPoly> polys = mesh->polys();
|
||||
ChrisLend marked this conversation as resolved
Hans Goudey
commented
`poly_loops` is generally the name for a span containing the loops of a single polygon. The `poly` argument could be removed by slicing the `loops` span before passing it to the function.
|
||||
const Span<int> corner_verts = mesh->corner_verts();
|
||||
const Span<int> corner_edges = mesh->corner_edges();
|
||||
const Span<MEdge> 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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue
The condition can be avoided by doing something like:
Not sure if it's worth it though, you choose @ChrisLend. Could be applied below as well.
had a look at it but I think it's a bit clearer if the bool is set explicitly so I left it as is