diff --git a/source/blender/bmesh/intern/bmesh_marking.cc b/source/blender/bmesh/intern/bmesh_marking.cc index eb507458259..e9134cea294 100644 --- a/source/blender/bmesh/intern/bmesh_marking.cc +++ b/source/blender/bmesh/intern/bmesh_marking.cc @@ -407,14 +407,108 @@ static void bm_mesh_select_mode_flush_edge_to_face(BMesh *bm) bm->totfacesel += chunk_data.delta_selection_len; } +/** + * Flush down from edges to verts. + * + * \note This is not typically needed as settling the selection flushes down. + */ +static void bm_mesh_select_mode_flush_edge_to_vert(BMesh *bm) +{ + BMIter iter; + BMEdge *e; + bool any_select = false; + BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(e, BM_ELEM_HIDDEN)) { + continue; + } + if (BM_elem_flag_test(e, BM_ELEM_SELECT)) { + any_select = true; + } + else { + BM_vert_select_set(bm, e->v1, false); + BM_vert_select_set(bm, e->v2, false); + } + } + if (any_select) { + BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(e, BM_ELEM_HIDDEN)) { + continue; + } + if (BM_elem_flag_test(e, BM_ELEM_SELECT)) { + BM_vert_select_set(bm, e->v1, true); + BM_vert_select_set(bm, e->v2, true); + } + } + } +} + +/** + * Flush down from faces to verts & edges. + * + * \note This is not typically needed as settling the selection flushes down. + */ +static void bm_mesh_select_mode_flush_face_to_vert_and_edge(BMesh *bm) +{ + BMIter iter; + BMFace *f; + bool any_select = false; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + if (BM_elem_flag_test(f, BM_ELEM_SELECT)) { + any_select = true; + } + else { + BMLoop *l_first = BM_FACE_FIRST_LOOP(f); + BMLoop *l_iter = l_first; + do { + BM_vert_select_set(bm, l_iter->v, false); + BM_edge_select_set_noflush(bm, l_iter->e, false); + } while ((l_iter = l_iter->next) != l_first); + } + } + if (any_select) { + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + if (BM_elem_flag_test(f, BM_ELEM_SELECT)) { + BMLoop *l_first = BM_FACE_FIRST_LOOP(f); + BMLoop *l_iter = l_first; + do { + BM_vert_select_set(bm, l_iter->v, true); + BM_edge_select_set_noflush(bm, l_iter->e, true); + } while ((l_iter = l_iter->next) != l_first); + } + } + } +} + void BM_mesh_select_mode_flush_ex(BMesh *bm, const short selectmode, BMSelectFlushFlag flag) { - if (selectmode & SCE_SELECT_VERTEX) { - bm_mesh_select_mode_flush_vert_to_edge(bm); + const bool flush_down = bool(flag & BMSelectFlushFlag::Down); + if (flush_down) { + if (selectmode & SCE_SELECT_VERTEX) { + /* Pass. */ + } + else if (selectmode & SCE_SELECT_EDGE) { + bm_mesh_select_mode_flush_edge_to_vert(bm); + } + else if (selectmode & SCE_SELECT_FACE) { + bm_mesh_select_mode_flush_face_to_vert_and_edge(bm); + } } - if (selectmode & (SCE_SELECT_VERTEX | SCE_SELECT_EDGE)) { - bm_mesh_select_mode_flush_edge_to_face(bm); + /* Always flush up. */ + { + if (selectmode & SCE_SELECT_VERTEX) { + bm_mesh_select_mode_flush_vert_to_edge(bm); + } + + if (selectmode & (SCE_SELECT_VERTEX | SCE_SELECT_EDGE)) { + bm_mesh_select_mode_flush_edge_to_face(bm); + } } /* Remove any deselected elements from the BMEditSelection */ @@ -434,7 +528,7 @@ void BM_mesh_select_mode_flush_ex(BMesh *bm, const short selectmode, BMSelectFlu void BM_mesh_select_mode_flush(BMesh *bm) { - BM_mesh_select_mode_flush_ex(bm, bm->selectmode, BMSelectFlushFlag_All); + BM_mesh_select_mode_flush_ex(bm, bm->selectmode, BMSelectFlushFlag_Default); } /** \} */ diff --git a/source/blender/bmesh/intern/bmesh_marking.hh b/source/blender/bmesh/intern/bmesh_marking.hh index e609525c312..deb324c5be7 100644 --- a/source/blender/bmesh/intern/bmesh_marking.hh +++ b/source/blender/bmesh/intern/bmesh_marking.hh @@ -21,13 +21,24 @@ enum class BMSelectFlushFlag : uint8_t { RecalcLenVert = (1 << 0), RecalcLenEdge = (1 << 1), RecalcLenFace = (1 << 2), + /** + * Flush selection down, depending on the selection mode. + * + * Disabled by default as edge & face selection functions flush down: + * (functions #BM_edge_select_set & #BM_face_select_set). + * However selection logic needs to take care to perform de-selection *before* selection, + * otherwise flushing down *is* needed. + */ + Down = (1 << 3), }; -ENUM_OPERATORS(BMSelectFlushFlag, BMSelectFlushFlag::RecalcLenFace) +ENUM_OPERATORS(BMSelectFlushFlag, BMSelectFlushFlag::Down) #define BMSelectFlushFlag_All \ (BMSelectFlushFlag::RecalcLenVert | BMSelectFlushFlag::RecalcLenEdge | \ BMSelectFlushFlag::RecalcLenFace) +#define BMSelectFlushFlag_Default BMSelectFlushFlag_All + /* Geometry hiding code. */ #define BM_elem_hide_set(bm, ele, hide) _bm_elem_hide_set(bm, &(ele)->head, hide) @@ -105,6 +116,9 @@ void BM_mesh_select_mode_set(BMesh *bm, int selectmode); * Makes sure to flush selections 'upwards' * (ie: all verts of an edge selects the edge and so on). * This should only be called by system and not tool authors. + * + * \note Flushing down can be enabled for edge/face modes + * by enabling #BMSelectFlushFlag:Down for `flag`. */ void BM_mesh_select_mode_flush_ex(BMesh *bm, short selectmode, BMSelectFlushFlag flag); void BM_mesh_select_mode_flush(BMesh *bm); diff --git a/source/blender/python/bmesh/bmesh_py_types.cc b/source/blender/python/bmesh/bmesh_py_types.cc index c652e3a59f9..dc325853b4f 100644 --- a/source/blender/python/bmesh/bmesh_py_types.cc +++ b/source/blender/python/bmesh/bmesh_py_types.cc @@ -1438,15 +1438,42 @@ static PyObject *bpy_bmesh_from_mesh(BPy_BMesh *self, PyObject *args, PyObject * PyDoc_STRVAR( /* Wrap. */ bpy_bmesh_select_flush_mode_doc, - ".. method:: select_flush_mode()\n" + ".. method:: select_flush_mode(/, *, down=False)\n" "\n" - " flush selection based on the current mode current " - ":class:`bmesh.types.BMesh.select_mode`.\n"); -static PyObject *bpy_bmesh_select_flush_mode(BPy_BMesh *self) + " Flush selection based on the current mode current " + ":class:`bmesh.types.BMesh.select_mode`.\n" + "\n" + " :arg down: Flush selection down from faces to edges & verts or from edges to verts. " + "This option is ignored when vertex selection mode is enabled.\n" + " :type down: bool\n"); +static PyObject *bpy_bmesh_select_flush_mode(BPy_BMesh *self, PyObject *args, PyObject *kw) { BPY_BM_CHECK_OBJ(self); - BM_mesh_select_mode_flush(self->bm); + bool down = false; + BMSelectFlushFlag flag = BMSelectFlushFlag_Default; + + static const char *kwlist[] = { + "down", + nullptr, + }; + if (!PyArg_ParseTupleAndKeywords(args, + kw, + "|$" + "O&" /* `down` */ + ":select_flush_mode", + (char **)kwlist, + PyC_ParseBool, + &down)) + { + return nullptr; + } + + if (down) { + flag |= BMSelectFlushFlag::Down; + } + + BM_mesh_select_mode_flush_ex(self->bm, self->bm->selectmode, flag); Py_RETURN_NONE; } @@ -3172,7 +3199,7 @@ static PyMethodDef bpy_bmesh_methods[] = { /* meshdata */ {"select_flush_mode", (PyCFunction)bpy_bmesh_select_flush_mode, - METH_NOARGS, + METH_VARARGS | METH_KEYWORDS, bpy_bmesh_select_flush_mode_doc}, {"select_flush", (PyCFunction)bpy_bmesh_select_flush, METH_O, bpy_bmesh_select_flush_doc}, {"normal_update",