diff options
Diffstat (limited to 'source/blender/editors/mesh/editmesh_select.cc')
-rw-r--r-- | source/blender/editors/mesh/editmesh_select.cc | 5353 |
1 files changed, 5353 insertions, 0 deletions
diff --git a/source/blender/editors/mesh/editmesh_select.cc b/source/blender/editors/mesh/editmesh_select.cc new file mode 100644 index 00000000000..d5392c7984d --- /dev/null +++ b/source/blender/editors/mesh/editmesh_select.cc @@ -0,0 +1,5353 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2004 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edmesh + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_bitmap.h" +#include "BLI_heap.h" +#include "BLI_linklist.h" +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_math_bits.h" +#include "BLI_rand.h" +#include "BLI_string.h" +#include "BLI_utildefines_stack.h" +#include "BLI_vector.hh" + +#include "BKE_context.h" +#include "BKE_customdata.h" +#include "BKE_deform.h" +#include "BKE_editmesh.h" +#include "BKE_layer.h" +#include "BKE_report.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "RNA_access.h" +#include "RNA_define.h" +#include "RNA_enum_types.h" + +#include "ED_mesh.h" +#include "ED_object.h" +#include "ED_screen.h" +#include "ED_select_utils.h" +#include "ED_transform.h" +#include "ED_view3d.h" + +#include "BLT_translation.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" + +#include "UI_resources.h" + +#include "bmesh_tools.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" + +#include "DRW_select_buffer.h" + +#include "mesh_intern.h" /* own include */ + +/* use bmesh operator flags for a few operators */ +#define BMO_ELE_TAG 1 + +/* -------------------------------------------------------------------- */ +/** \name Select Mirror + * \{ */ + +void EDBM_select_mirrored(BMEditMesh *em, + const Mesh *me, + const int axis, + const bool extend, + int *r_totmirr, + int *r_totfail) +{ + BMesh *bm = em->bm; + BMIter iter; + int totmirr = 0; + int totfail = 0; + bool use_topology = me->editflag & ME_EDIT_MIRROR_TOPO; + + *r_totmirr = *r_totfail = 0; + + /* select -> tag */ + if (bm->selectmode & SCE_SELECT_VERTEX) { + BMVert *v; + BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { + BM_elem_flag_set(v, BM_ELEM_TAG, BM_elem_flag_test(v, BM_ELEM_SELECT)); + } + } + else if (em->selectmode & SCE_SELECT_EDGE) { + BMEdge *e; + BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) { + BM_elem_flag_set(e, BM_ELEM_TAG, BM_elem_flag_test(e, BM_ELEM_SELECT)); + } + } + else { + BMFace *f; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + BM_elem_flag_set(f, BM_ELEM_TAG, BM_elem_flag_test(f, BM_ELEM_SELECT)); + } + } + + EDBM_verts_mirror_cache_begin(em, axis, true, true, false, use_topology); + + if (!extend) { + EDBM_flag_disable_all(em, BM_ELEM_SELECT); + } + + if (bm->selectmode & SCE_SELECT_VERTEX) { + BMVert *v; + BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { + if (!BM_elem_flag_test(v, BM_ELEM_HIDDEN) && BM_elem_flag_test(v, BM_ELEM_TAG)) { + BMVert *v_mirr = EDBM_verts_mirror_get(em, v); + if (v_mirr && !BM_elem_flag_test(v_mirr, BM_ELEM_HIDDEN)) { + BM_vert_select_set(bm, v_mirr, true); + totmirr++; + } + else { + totfail++; + } + } + } + } + else if (em->selectmode & SCE_SELECT_EDGE) { + BMEdge *e; + BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) { + if (!BM_elem_flag_test(e, BM_ELEM_HIDDEN) && BM_elem_flag_test(e, BM_ELEM_TAG)) { + BMEdge *e_mirr = EDBM_verts_mirror_get_edge(em, e); + if (e_mirr && !BM_elem_flag_test(e_mirr, BM_ELEM_HIDDEN)) { + BM_edge_select_set(bm, e_mirr, true); + totmirr++; + } + else { + totfail++; + } + } + } + } + else { + BMFace *f; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (!BM_elem_flag_test(f, BM_ELEM_HIDDEN) && BM_elem_flag_test(f, BM_ELEM_TAG)) { + BMFace *f_mirr = EDBM_verts_mirror_get_face(em, f); + if (f_mirr && !BM_elem_flag_test(f_mirr, BM_ELEM_HIDDEN)) { + BM_face_select_set(bm, f_mirr, true); + totmirr++; + } + else { + totfail++; + } + } + } + } + + EDBM_verts_mirror_cache_end(em); + + *r_totmirr = totmirr; + *r_totfail = totfail; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Back-Buffer OpenGL Selection + * \{ */ + +static BMElem *edbm_select_id_bm_elem_get(Base **bases, const uint sel_id, uint *r_base_index) +{ + uint elem_id; + char elem_type = 0; + bool success = DRW_select_buffer_elem_get(sel_id, &elem_id, r_base_index, &elem_type); + + if (success) { + Object *obedit = bases[*r_base_index]->object; + BMEditMesh *em = BKE_editmesh_from_object(obedit); + + switch (elem_type) { + case SCE_SELECT_FACE: + return (BMElem *)BM_face_at_index_find_or_table(em->bm, elem_id); + case SCE_SELECT_EDGE: + return (BMElem *)BM_edge_at_index_find_or_table(em->bm, elem_id); + case SCE_SELECT_VERTEX: + return (BMElem *)BM_vert_at_index_find_or_table(em->bm, elem_id); + default: + BLI_assert(0); + return nullptr; + } + } + + return nullptr; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Find Nearest Vert/Edge/Face + * + * \note Screen-space manhattan distances are used here, + * since its faster and good enough for the purpose of selection. + * + * \note \a dist_bias is used so we can bias against selected items. + * when choosing between elements of a single type, but return the real distance + * to avoid the bias interfering with distance comparisons when mixing types. + * \{ */ + +#define FIND_NEAR_SELECT_BIAS 5 +#define FIND_NEAR_CYCLE_THRESHOLD_MIN 3 + +struct NearestVertUserData_Hit { + float dist; + float dist_bias; + int index; + BMVert *vert; +}; + +struct NearestVertUserData { + float mval_fl[2]; + bool use_select_bias; + bool use_cycle; + int cycle_index_prev; + + NearestVertUserData_Hit hit; + NearestVertUserData_Hit hit_cycle; +}; + +static void findnearestvert__doClosest(void *userData, + BMVert *eve, + const float screen_co[2], + int index) +{ + NearestVertUserData *data = static_cast<NearestVertUserData *>(userData); + float dist_test, dist_test_bias; + + dist_test = dist_test_bias = len_manhattan_v2v2(data->mval_fl, screen_co); + + if (data->use_select_bias && BM_elem_flag_test(eve, BM_ELEM_SELECT)) { + dist_test_bias += FIND_NEAR_SELECT_BIAS; + } + + if (dist_test_bias < data->hit.dist_bias) { + data->hit.dist_bias = dist_test_bias; + data->hit.dist = dist_test; + data->hit.index = index; + data->hit.vert = eve; + } + + if (data->use_cycle) { + if ((data->hit_cycle.vert == nullptr) && (index > data->cycle_index_prev) && + (dist_test_bias < FIND_NEAR_CYCLE_THRESHOLD_MIN)) { + data->hit_cycle.dist_bias = dist_test_bias; + data->hit_cycle.dist = dist_test; + data->hit_cycle.index = index; + data->hit_cycle.vert = eve; + } + } +} + +BMVert *EDBM_vert_find_nearest_ex(ViewContext *vc, + float *dist_px_manhattan_p, + const bool use_select_bias, + bool use_cycle, + Base **bases, + uint bases_len, + uint *r_base_index) +{ + uint base_index = 0; + + if (!XRAY_FLAG_ENABLED(vc->v3d)) { + uint dist_px_manhattan_test = (uint)ED_view3d_backbuf_sample_size_clamp(vc->region, + *dist_px_manhattan_p); + uint index; + BMVert *eve; + + /* No afterqueue (yet), so we check it now, otherwise the bm_xxxofs indices are bad. */ + { + DRW_select_buffer_context_create(bases, bases_len, SCE_SELECT_VERTEX); + + index = DRW_select_buffer_find_nearest_to_point( + vc->depsgraph, vc->region, vc->v3d, vc->mval, 1, UINT_MAX, &dist_px_manhattan_test); + + if (index) { + eve = (BMVert *)edbm_select_id_bm_elem_get(bases, index, &base_index); + } + else { + eve = nullptr; + } + } + + if (eve) { + if (dist_px_manhattan_test < *dist_px_manhattan_p) { + if (r_base_index) { + *r_base_index = base_index; + } + *dist_px_manhattan_p = dist_px_manhattan_test; + return eve; + } + } + return nullptr; + } + + NearestVertUserData data = {{0}}; + const NearestVertUserData_Hit *hit = nullptr; + const eV3DProjTest clip_flag = RV3D_CLIPPING_ENABLED(vc->v3d, vc->rv3d) ? + V3D_PROJ_TEST_CLIP_DEFAULT : + V3D_PROJ_TEST_CLIP_DEFAULT & ~V3D_PROJ_TEST_CLIP_BB; + BMesh *prev_select_bm = nullptr; + + static struct { + int index; + const BMVert *elem; + const BMesh *bm; + } prev_select = {0}; + + data.mval_fl[0] = vc->mval[0]; + data.mval_fl[1] = vc->mval[1]; + data.use_select_bias = use_select_bias; + data.use_cycle = use_cycle; + + for (; base_index < bases_len; base_index++) { + Base *base_iter = bases[base_index]; + ED_view3d_viewcontext_init_object(vc, base_iter->object); + if (use_cycle && prev_select.bm == vc->em->bm && + prev_select.elem == BM_vert_at_index_find_or_table(vc->em->bm, prev_select.index)) { + data.cycle_index_prev = prev_select.index; + /* No need to compare in the rest of the loop. */ + use_cycle = false; + } + else { + data.cycle_index_prev = 0; + } + + data.hit.dist = data.hit_cycle.dist = data.hit.dist_bias = data.hit_cycle.dist_bias = + *dist_px_manhattan_p; + + ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); + mesh_foreachScreenVert(vc, findnearestvert__doClosest, &data, clip_flag); + + hit = (data.use_cycle && data.hit_cycle.vert) ? &data.hit_cycle : &data.hit; + + if (hit->dist < *dist_px_manhattan_p) { + if (r_base_index) { + *r_base_index = base_index; + } + *dist_px_manhattan_p = hit->dist; + prev_select_bm = vc->em->bm; + } + } + + if (hit == nullptr) { + return nullptr; + } + + prev_select.index = hit->index; + prev_select.elem = hit->vert; + prev_select.bm = prev_select_bm; + + return hit->vert; +} + +BMVert *EDBM_vert_find_nearest(ViewContext *vc, float *dist_px_manhattan_p) +{ + BKE_view_layer_synced_ensure(vc->scene, vc->view_layer); + Base *base = BKE_view_layer_base_find(vc->view_layer, vc->obact); + return EDBM_vert_find_nearest_ex(vc, dist_px_manhattan_p, false, false, &base, 1, nullptr); +} + +/* find the distance to the edge we already have */ +struct NearestEdgeUserData_ZBuf { + float mval_fl[2]; + float dist; + const BMEdge *edge_test; +}; + +static void find_nearest_edge_center__doZBuf(void *userData, + BMEdge *eed, + const float screen_co_a[2], + const float screen_co_b[2], + int UNUSED(index)) +{ + NearestEdgeUserData_ZBuf *data = static_cast<NearestEdgeUserData_ZBuf *>(userData); + + if (eed == data->edge_test) { + float dist_test; + float screen_co_mid[2]; + + mid_v2_v2v2(screen_co_mid, screen_co_a, screen_co_b); + dist_test = len_manhattan_v2v2(data->mval_fl, screen_co_mid); + + if (dist_test < data->dist) { + data->dist = dist_test; + } + } +} + +struct NearestEdgeUserData_Hit { + float dist; + float dist_bias; + int index; + BMEdge *edge; + + /* edges only, un-biased manhattan distance to which ever edge we pick + * (not used for choosing) */ + float dist_center_px_manhattan; +}; + +struct NearestEdgeUserData { + ViewContext vc; + float mval_fl[2]; + bool use_select_bias; + bool use_cycle; + int cycle_index_prev; + + NearestEdgeUserData_Hit hit; + NearestEdgeUserData_Hit hit_cycle; +}; + +/* NOTE: uses v3d, so needs active 3d window. */ +static void find_nearest_edge__doClosest( + void *userData, BMEdge *eed, const float screen_co_a[2], const float screen_co_b[2], int index) +{ + NearestEdgeUserData *data = static_cast<NearestEdgeUserData *>(userData); + float dist_test, dist_test_bias; + + float fac = line_point_factor_v2(data->mval_fl, screen_co_a, screen_co_b); + float screen_co[2]; + + if (fac <= 0.0f) { + fac = 0.0f; + copy_v2_v2(screen_co, screen_co_a); + } + else if (fac >= 1.0f) { + fac = 1.0f; + copy_v2_v2(screen_co, screen_co_b); + } + else { + interp_v2_v2v2(screen_co, screen_co_a, screen_co_b, fac); + } + + dist_test = dist_test_bias = len_manhattan_v2v2(data->mval_fl, screen_co); + + if (data->use_select_bias && BM_elem_flag_test(eed, BM_ELEM_SELECT)) { + dist_test_bias += FIND_NEAR_SELECT_BIAS; + } + + if (data->vc.rv3d->rflag & RV3D_CLIPPING) { + float vec[3]; + + interp_v3_v3v3(vec, eed->v1->co, eed->v2->co, fac); + if (ED_view3d_clipping_test(data->vc.rv3d, vec, true)) { + return; + } + } + + if (dist_test_bias < data->hit.dist_bias) { + float screen_co_mid[2]; + + data->hit.dist_bias = dist_test_bias; + data->hit.dist = dist_test; + data->hit.index = index; + data->hit.edge = eed; + + mid_v2_v2v2(screen_co_mid, screen_co_a, screen_co_b); + data->hit.dist_center_px_manhattan = len_manhattan_v2v2(data->mval_fl, screen_co_mid); + } + + if (data->use_cycle) { + if ((data->hit_cycle.edge == nullptr) && (index > data->cycle_index_prev) && + (dist_test_bias < FIND_NEAR_CYCLE_THRESHOLD_MIN)) { + float screen_co_mid[2]; + + data->hit_cycle.dist_bias = dist_test_bias; + data->hit_cycle.dist = dist_test; + data->hit_cycle.index = index; + data->hit_cycle.edge = eed; + + mid_v2_v2v2(screen_co_mid, screen_co_a, screen_co_b); + data->hit_cycle.dist_center_px_manhattan = len_manhattan_v2v2(data->mval_fl, screen_co_mid); + } + } +} + +BMEdge *EDBM_edge_find_nearest_ex(ViewContext *vc, + float *dist_px_manhattan_p, + float *r_dist_center_px_manhattan, + const bool use_select_bias, + bool use_cycle, + BMEdge **r_eed_zbuf, + Base **bases, + uint bases_len, + uint *r_base_index) +{ + uint base_index = 0; + + if (!XRAY_FLAG_ENABLED(vc->v3d)) { + uint dist_px_manhattan_test = (uint)ED_view3d_backbuf_sample_size_clamp(vc->region, + *dist_px_manhattan_p); + uint index; + BMEdge *eed; + + /* No afterqueue (yet), so we check it now, otherwise the bm_xxxofs indices are bad. */ + { + DRW_select_buffer_context_create(bases, bases_len, SCE_SELECT_EDGE); + + index = DRW_select_buffer_find_nearest_to_point( + vc->depsgraph, vc->region, vc->v3d, vc->mval, 1, UINT_MAX, &dist_px_manhattan_test); + + if (index) { + eed = (BMEdge *)edbm_select_id_bm_elem_get(bases, index, &base_index); + } + else { + eed = nullptr; + } + } + + if (r_eed_zbuf) { + *r_eed_zbuf = eed; + } + + /* exception for faces (verts don't need this) */ + if (r_dist_center_px_manhattan && eed) { + NearestEdgeUserData_ZBuf data; + + data.mval_fl[0] = vc->mval[0]; + data.mval_fl[1] = vc->mval[1]; + data.dist = FLT_MAX; + data.edge_test = eed; + + ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); + + mesh_foreachScreenEdge(vc, + find_nearest_edge_center__doZBuf, + &data, + V3D_PROJ_TEST_CLIP_DEFAULT | V3D_PROJ_TEST_CLIP_CONTENT_DEFAULT); + + *r_dist_center_px_manhattan = data.dist; + } + /* end exception */ + + if (eed) { + if (dist_px_manhattan_test < *dist_px_manhattan_p) { + if (r_base_index) { + *r_base_index = base_index; + } + *dist_px_manhattan_p = dist_px_manhattan_test; + return eed; + } + } + return nullptr; + } + + NearestEdgeUserData data = {{0}}; + const NearestEdgeUserData_Hit *hit = nullptr; + /* interpolate along the edge before doing a clipping plane test */ + const eV3DProjTest clip_flag = V3D_PROJ_TEST_CLIP_DEFAULT & ~V3D_PROJ_TEST_CLIP_BB; + BMesh *prev_select_bm = nullptr; + + static struct { + int index; + const BMEdge *elem; + const BMesh *bm; + } prev_select = {0}; + + data.vc = *vc; + data.mval_fl[0] = vc->mval[0]; + data.mval_fl[1] = vc->mval[1]; + data.use_select_bias = use_select_bias; + data.use_cycle = use_cycle; + + for (; base_index < bases_len; base_index++) { + Base *base_iter = bases[base_index]; + ED_view3d_viewcontext_init_object(vc, base_iter->object); + if (use_cycle && prev_select.bm == vc->em->bm && + prev_select.elem == BM_edge_at_index_find_or_table(vc->em->bm, prev_select.index)) { + data.cycle_index_prev = prev_select.index; + /* No need to compare in the rest of the loop. */ + use_cycle = false; + } + else { + data.cycle_index_prev = 0; + } + + data.hit.dist = data.hit_cycle.dist = data.hit.dist_bias = data.hit_cycle.dist_bias = + *dist_px_manhattan_p; + + ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); + mesh_foreachScreenEdge( + vc, find_nearest_edge__doClosest, &data, clip_flag | V3D_PROJ_TEST_CLIP_CONTENT_DEFAULT); + + hit = (data.use_cycle && data.hit_cycle.edge) ? &data.hit_cycle : &data.hit; + + if (hit->dist < *dist_px_manhattan_p) { + if (r_base_index) { + *r_base_index = base_index; + } + *dist_px_manhattan_p = hit->dist; + prev_select_bm = vc->em->bm; + } + } + + if (hit == nullptr) { + return nullptr; + } + + if (r_dist_center_px_manhattan) { + *r_dist_center_px_manhattan = hit->dist_center_px_manhattan; + } + + prev_select.index = hit->index; + prev_select.elem = hit->edge; + prev_select.bm = prev_select_bm; + + return hit->edge; +} + +BMEdge *EDBM_edge_find_nearest(ViewContext *vc, float *dist_px_manhattan_p) +{ + BKE_view_layer_synced_ensure(vc->scene, vc->view_layer); + Base *base = BKE_view_layer_base_find(vc->view_layer, vc->obact); + return EDBM_edge_find_nearest_ex( + vc, dist_px_manhattan_p, nullptr, false, false, nullptr, &base, 1, nullptr); +} + +/* find the distance to the face we already have */ +struct NearestFaceUserData_ZBuf { + float mval_fl[2]; + float dist_px_manhattan; + const BMFace *face_test; +}; + +static void find_nearest_face_center__doZBuf(void *userData, + BMFace *efa, + const float screen_co[2], + int UNUSED(index)) +{ + NearestFaceUserData_ZBuf *data = static_cast<NearestFaceUserData_ZBuf *>(userData); + + if (efa == data->face_test) { + const float dist_test = len_manhattan_v2v2(data->mval_fl, screen_co); + + if (dist_test < data->dist_px_manhattan) { + data->dist_px_manhattan = dist_test; + } + } +} + +struct NearestFaceUserData_Hit { + float dist; + float dist_bias; + int index; + BMFace *face; +}; + +struct NearestFaceUserData { + float mval_fl[2]; + bool use_select_bias; + bool use_cycle; + int cycle_index_prev; + + NearestFaceUserData_Hit hit; + NearestFaceUserData_Hit hit_cycle; +}; + +static void findnearestface__doClosest(void *userData, + BMFace *efa, + const float screen_co[2], + int index) +{ + NearestFaceUserData *data = static_cast<NearestFaceUserData *>(userData); + float dist_test, dist_test_bias; + + dist_test = dist_test_bias = len_manhattan_v2v2(data->mval_fl, screen_co); + + if (data->use_select_bias && BM_elem_flag_test(efa, BM_ELEM_SELECT)) { + dist_test_bias += FIND_NEAR_SELECT_BIAS; + } + + if (dist_test_bias < data->hit.dist_bias) { + data->hit.dist_bias = dist_test_bias; + data->hit.dist = dist_test; + data->hit.index = index; + data->hit.face = efa; + } + + if (data->use_cycle) { + if ((data->hit_cycle.face == nullptr) && (index > data->cycle_index_prev) && + (dist_test_bias < FIND_NEAR_CYCLE_THRESHOLD_MIN)) { + data->hit_cycle.dist_bias = dist_test_bias; + data->hit_cycle.dist = dist_test; + data->hit_cycle.index = index; + data->hit_cycle.face = efa; + } + } +} + +BMFace *EDBM_face_find_nearest_ex(ViewContext *vc, + float *dist_px_manhattan_p, + float *r_dist_center, + const bool use_zbuf_single_px, + const bool use_select_bias, + bool use_cycle, + BMFace **r_efa_zbuf, + Base **bases, + uint bases_len, + uint *r_base_index) +{ + uint base_index = 0; + + if (!XRAY_FLAG_ENABLED(vc->v3d)) { + float dist_test; + uint index; + BMFace *efa; + + { + uint dist_px_manhattan_test = 0; + if (*dist_px_manhattan_p != 0.0f && (use_zbuf_single_px == false)) { + dist_px_manhattan_test = (uint)ED_view3d_backbuf_sample_size_clamp(vc->region, + *dist_px_manhattan_p); + } + + DRW_select_buffer_context_create(bases, bases_len, SCE_SELECT_FACE); + + if (dist_px_manhattan_test == 0) { + index = DRW_select_buffer_sample_point(vc->depsgraph, vc->region, vc->v3d, vc->mval); + dist_test = 0.0f; + } + else { + index = DRW_select_buffer_find_nearest_to_point( + vc->depsgraph, vc->region, vc->v3d, vc->mval, 1, UINT_MAX, &dist_px_manhattan_test); + dist_test = dist_px_manhattan_test; + } + + if (index) { + efa = (BMFace *)edbm_select_id_bm_elem_get(bases, index, &base_index); + } + else { + efa = nullptr; + } + } + + if (r_efa_zbuf) { + *r_efa_zbuf = efa; + } + + /* exception for faces (verts don't need this) */ + if (r_dist_center && efa) { + NearestFaceUserData_ZBuf data; + + data.mval_fl[0] = vc->mval[0]; + data.mval_fl[1] = vc->mval[1]; + data.dist_px_manhattan = FLT_MAX; + data.face_test = efa; + + ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); + + mesh_foreachScreenFace( + vc, find_nearest_face_center__doZBuf, &data, V3D_PROJ_TEST_CLIP_DEFAULT); + + *r_dist_center = data.dist_px_manhattan; + } + /* end exception */ + + if (efa) { + if (dist_test < *dist_px_manhattan_p) { + if (r_base_index) { + *r_base_index = base_index; + } + *dist_px_manhattan_p = dist_test; + return efa; + } + } + return nullptr; + } + + NearestFaceUserData data = {{0}}; + const NearestFaceUserData_Hit *hit = nullptr; + const eV3DProjTest clip_flag = V3D_PROJ_TEST_CLIP_DEFAULT; + BMesh *prev_select_bm = nullptr; + + static struct { + int index; + const BMFace *elem; + const BMesh *bm; + } prev_select = {0}; + + data.mval_fl[0] = vc->mval[0]; + data.mval_fl[1] = vc->mval[1]; + data.use_select_bias = use_select_bias; + data.use_cycle = use_cycle; + + for (; base_index < bases_len; base_index++) { + Base *base_iter = bases[base_index]; + ED_view3d_viewcontext_init_object(vc, base_iter->object); + if (use_cycle && prev_select.bm == vc->em->bm && + prev_select.elem == BM_face_at_index_find_or_table(vc->em->bm, prev_select.index)) { + data.cycle_index_prev = prev_select.index; + /* No need to compare in the rest of the loop. */ + use_cycle = false; + } + else { + data.cycle_index_prev = 0; + } + + data.hit.dist = data.hit_cycle.dist = data.hit.dist_bias = data.hit_cycle.dist_bias = + *dist_px_manhattan_p; + + ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); + mesh_foreachScreenFace(vc, findnearestface__doClosest, &data, clip_flag); + + hit = (data.use_cycle && data.hit_cycle.face) ? &data.hit_cycle : &data.hit; + + if (hit->dist < *dist_px_manhattan_p) { + if (r_base_index) { + *r_base_index = base_index; + } + *dist_px_manhattan_p = hit->dist; + prev_select_bm = vc->em->bm; + } + } + + if (hit == nullptr) { + return nullptr; + } + + if (r_dist_center) { + *r_dist_center = hit->dist; + } + + prev_select.index = hit->index; + prev_select.elem = hit->face; + prev_select.bm = prev_select_bm; + + return hit->face; +} + +BMFace *EDBM_face_find_nearest(ViewContext *vc, float *dist_px_manhattan_p) +{ + BKE_view_layer_synced_ensure(vc->scene, vc->view_layer); + Base *base = BKE_view_layer_base_find(vc->view_layer, vc->obact); + return EDBM_face_find_nearest_ex( + vc, dist_px_manhattan_p, nullptr, false, false, false, nullptr, &base, 1, nullptr); +} + +#undef FIND_NEAR_SELECT_BIAS +#undef FIND_NEAR_CYCLE_THRESHOLD_MIN + +/* best distance based on screen coords. + * use em->selectmode to define how to use + * selected vertices and edges get disadvantage + * return 1 if found one + */ +static bool unified_findnearest(ViewContext *vc, + Base **bases, + const uint bases_len, + int *r_base_index, + BMVert **r_eve, + BMEdge **r_eed, + BMFace **r_efa) +{ + BMEditMesh *em = vc->em; + + const bool use_cycle = !WM_cursor_test_motion_and_update(vc->mval); + const float dist_init = ED_view3d_select_dist_px(); + /* since edges select lines, we give dots advantage of ~20 pix */ + const float dist_margin = (dist_init / 2); + float dist = dist_init; + + struct { + struct { + BMVert *ele; + int base_index; + } v; + struct { + BMEdge *ele; + int base_index; + } e, e_zbuf; + struct { + BMFace *ele; + int base_index; + } f, f_zbuf; + } hit = {{nullptr}}; + + /* no afterqueue (yet), so we check it now, otherwise the em_xxxofs indices are bad */ + + if ((dist > 0.0f) && (em->selectmode & SCE_SELECT_FACE)) { + float dist_center = 0.0f; + float *dist_center_p = (em->selectmode & (SCE_SELECT_EDGE | SCE_SELECT_VERTEX)) ? + &dist_center : + nullptr; + + uint base_index = 0; + BMFace *efa_zbuf = nullptr; + BMFace *efa_test = EDBM_face_find_nearest_ex( + vc, &dist, dist_center_p, true, true, use_cycle, &efa_zbuf, bases, bases_len, &base_index); + + if (efa_test && dist_center_p) { + dist = min_ff(dist_margin, dist_center); + } + if (efa_test) { + hit.f.base_index = base_index; + hit.f.ele = efa_test; + } + if (efa_zbuf) { + hit.f_zbuf.base_index = base_index; + hit.f_zbuf.ele = efa_zbuf; + } + } + + if ((dist > 0.0f) && (em->selectmode & SCE_SELECT_EDGE)) { + float dist_center = 0.0f; + float *dist_center_p = (em->selectmode & SCE_SELECT_VERTEX) ? &dist_center : nullptr; + + uint base_index = 0; + BMEdge *eed_zbuf = nullptr; + BMEdge *eed_test = EDBM_edge_find_nearest_ex( + vc, &dist, dist_center_p, true, use_cycle, &eed_zbuf, bases, bases_len, &base_index); + + if (eed_test && dist_center_p) { + dist = min_ff(dist_margin, dist_center); + } + if (eed_test) { + hit.e.base_index = base_index; + hit.e.ele = eed_test; + } + if (eed_zbuf) { + hit.e_zbuf.base_index = base_index; + hit.e_zbuf.ele = eed_zbuf; + } + } + + if ((dist > 0.0f) && (em->selectmode & SCE_SELECT_VERTEX)) { + uint base_index = 0; + BMVert *eve_test = EDBM_vert_find_nearest_ex( + vc, &dist, true, use_cycle, bases, bases_len, &base_index); + + if (eve_test) { + hit.v.base_index = base_index; + hit.v.ele = eve_test; + } + } + + /* Return only one of 3 pointers, for front-buffer redraws. */ + if (hit.v.ele) { + hit.f.ele = nullptr; + hit.e.ele = nullptr; + } + else if (hit.e.ele) { + hit.f.ele = nullptr; + } + + /* there may be a face under the cursor, who's center if too far away + * use this if all else fails, it makes sense to select this */ + if ((hit.v.ele || hit.e.ele || hit.f.ele) == 0) { + if (hit.e_zbuf.ele) { + hit.e.base_index = hit.e_zbuf.base_index; + hit.e.ele = hit.e_zbuf.ele; + } + else if (hit.f_zbuf.ele) { + hit.f.base_index = hit.f_zbuf.base_index; + hit.f.ele = hit.f_zbuf.ele; + } + } + + /* Only one element type will be non-null. */ + BLI_assert(((hit.v.ele != nullptr) + (hit.e.ele != nullptr) + (hit.f.ele != nullptr)) <= 1); + + if (hit.v.ele) { + *r_base_index = hit.v.base_index; + } + if (hit.e.ele) { + *r_base_index = hit.e.base_index; + } + if (hit.f.ele) { + *r_base_index = hit.f.base_index; + } + + *r_eve = hit.v.ele; + *r_eed = hit.e.ele; + *r_efa = hit.f.ele; + + return (hit.v.ele || hit.e.ele || hit.f.ele); +} + +#undef FAKE_SELECT_MODE_BEGIN +#undef FAKE_SELECT_MODE_END + +bool EDBM_unified_findnearest(ViewContext *vc, + Base **bases, + const uint bases_len, + int *r_base_index, + BMVert **r_eve, + BMEdge **r_eed, + BMFace **r_efa) +{ + return unified_findnearest(vc, bases, bases_len, r_base_index, r_eve, r_eed, r_efa); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Alternate Find Nearest Vert/Edge (optional boundary) + * + * \note This uses ray-cast method instead of back-buffer, + * currently used for poly-build. + * \{ */ + +bool EDBM_unified_findnearest_from_raycast(ViewContext *vc, + Base **bases, + const uint bases_len, + bool use_boundary_vertices, + bool use_boundary_edges, + int *r_base_index_vert, + int *r_base_index_edge, + int *r_base_index_face, + BMVert **r_eve, + BMEdge **r_eed, + BMFace **r_efa) +{ + const float mval_fl[2] = {float(vc->mval[0]), float(vc->mval[1])}; + float ray_origin[3], ray_direction[3]; + + struct { + uint base_index; + BMElem *ele; + } best = {0, nullptr}; + /* Currently unused, keep since we may want to pick the best. */ + UNUSED_VARS(best); + + struct { + uint base_index; + BMElem *ele; + } best_vert = {0, nullptr}; + + struct { + uint base_index; + BMElem *ele; + } best_edge = {0, nullptr}; + + struct { + uint base_index; + BMElem *ele; + } best_face = {0, nullptr}; + + if (ED_view3d_win_to_ray_clipped( + vc->depsgraph, vc->region, vc->v3d, mval_fl, ray_origin, ray_direction, true)) { + float dist_sq_best = FLT_MAX; + float dist_sq_best_vert = FLT_MAX; + float dist_sq_best_edge = FLT_MAX; + float dist_sq_best_face = FLT_MAX; + + const bool use_vert = (r_eve != nullptr); + const bool use_edge = (r_eed != nullptr); + const bool use_face = (r_efa != nullptr); + + for (uint base_index = 0; base_index < bases_len; base_index++) { + Base *base_iter = bases[base_index]; + Object *obedit = base_iter->object; + + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + float imat3[3][3]; + + ED_view3d_viewcontext_init_object(vc, obedit); + copy_m3_m4(imat3, obedit->obmat); + invert_m3(imat3); + + const float(*coords)[3] = nullptr; + { + Mesh *me_eval = (Mesh *)DEG_get_evaluated_id(vc->depsgraph, + static_cast<ID *>(obedit->data)); + if (me_eval->runtime.edit_data) { + coords = me_eval->runtime.edit_data->vertexCos; + } + } + + if (coords != nullptr) { + BM_mesh_elem_index_ensure(bm, BM_VERT); + } + + if ((use_boundary_vertices || use_boundary_edges) && (use_vert || use_edge)) { + BMEdge *e; + BMIter eiter; + BM_ITER_MESH (e, &eiter, bm, BM_EDGES_OF_MESH) { + if ((BM_elem_flag_test(e, BM_ELEM_HIDDEN) == false) && (BM_edge_is_boundary(e))) { + if (use_vert && use_boundary_vertices) { + for (uint j = 0; j < 2; j++) { + BMVert *v = *((&e->v1) + j); + float point[3]; + mul_v3_m4v3(point, obedit->obmat, coords ? coords[BM_elem_index_get(v)] : v->co); + const float dist_sq_test = dist_squared_to_ray_v3_normalized( + ray_origin, ray_direction, point); + if (dist_sq_test < dist_sq_best_vert) { + dist_sq_best_vert = dist_sq_test; + best_vert.base_index = base_index; + best_vert.ele = (BMElem *)v; + } + if (dist_sq_test < dist_sq_best) { + dist_sq_best = dist_sq_test; + best.base_index = base_index; + best.ele = (BMElem *)v; + } + } + } + + if (use_edge && use_boundary_edges) { + float point[3]; +#if 0 + const float dist_sq_test = dist_squared_ray_to_seg_v3( + ray_origin, ray_direction, e->v1->co, e->v2->co, point, &depth); +#else + if (coords) { + mid_v3_v3v3( + point, coords[BM_elem_index_get(e->v1)], coords[BM_elem_index_get(e->v2)]); + } + else { + mid_v3_v3v3(point, e->v1->co, e->v2->co); + } + mul_m4_v3(obedit->obmat, point); + const float dist_sq_test = dist_squared_to_ray_v3_normalized( + ray_origin, ray_direction, point); + if (dist_sq_test < dist_sq_best_edge) { + dist_sq_best_edge = dist_sq_test; + best_edge.base_index = base_index; + best_edge.ele = (BMElem *)e; + } + if (dist_sq_test < dist_sq_best) { + dist_sq_best = dist_sq_test; + best.base_index = base_index; + best.ele = (BMElem *)e; + } +#endif + } + } + } + } + /* Non boundary case. */ + if (use_vert && !use_boundary_vertices) { + BMVert *v; + BMIter viter; + BM_ITER_MESH (v, &viter, bm, BM_VERTS_OF_MESH) { + if (BM_elem_flag_test(v, BM_ELEM_HIDDEN) == false) { + float point[3]; + mul_v3_m4v3(point, obedit->obmat, coords ? coords[BM_elem_index_get(v)] : v->co); + const float dist_sq_test = dist_squared_to_ray_v3_normalized( + ray_origin, ray_direction, point); + if (dist_sq_test < dist_sq_best_vert) { + dist_sq_best_vert = dist_sq_test; + best_vert.base_index = base_index; + best_vert.ele = (BMElem *)v; + } + if (dist_sq_test < dist_sq_best) { + dist_sq_best = dist_sq_test; + best.base_index = base_index; + best.ele = (BMElem *)v; + } + } + } + } + + if (use_edge && !use_boundary_edges) { + BMEdge *e; + BMIter eiter; + BM_ITER_MESH (e, &eiter, bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(e, BM_ELEM_HIDDEN) == false) { + float point[3]; + if (coords) { + mid_v3_v3v3( + point, coords[BM_elem_index_get(e->v1)], coords[BM_elem_index_get(e->v2)]); + } + else { + mid_v3_v3v3(point, e->v1->co, e->v2->co); + } + mul_m4_v3(obedit->obmat, point); + const float dist_sq_test = dist_squared_to_ray_v3_normalized( + ray_origin, ray_direction, point); + if (dist_sq_test < dist_sq_best_edge) { + dist_sq_best_edge = dist_sq_test; + best_edge.base_index = base_index; + best_edge.ele = (BMElem *)e; + } + if (dist_sq_test < dist_sq_best) { + dist_sq_best = dist_sq_test; + best.base_index = base_index; + best.ele = (BMElem *)e; + } + } + } + } + + if (use_face) { + BMFace *f; + BMIter fiter; + BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN) == false) { + float point[3]; + if (coords) { + BM_face_calc_center_median_vcos(bm, f, point, coords); + } + else { + BM_face_calc_center_median(f, point); + } + mul_m4_v3(obedit->obmat, point); + const float dist_sq_test = dist_squared_to_ray_v3_normalized( + ray_origin, ray_direction, point); + if (dist_sq_test < dist_sq_best_face) { + dist_sq_best_face = dist_sq_test; + best_face.base_index = base_index; + best_face.ele = (BMElem *)f; + } + if (dist_sq_test < dist_sq_best) { + dist_sq_best = dist_sq_test; + best.base_index = base_index; + best.ele = (BMElem *)f; + } + } + } + } + } + } + + *r_base_index_vert = best_vert.base_index; + *r_base_index_edge = best_edge.base_index; + *r_base_index_face = best_face.base_index; + + if (r_eve) { + *r_eve = nullptr; + } + if (r_eed) { + *r_eed = nullptr; + } + if (r_efa) { + *r_efa = nullptr; + } + + if (best_vert.ele) { + *r_eve = (BMVert *)best_vert.ele; + } + if (best_edge.ele) { + *r_eed = (BMEdge *)best_edge.ele; + } + if (best_face.ele) { + *r_efa = (BMFace *)best_face.ele; + } + + return (best_vert.ele != nullptr || best_edge.ele != nullptr || best_face.ele != nullptr); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Similar Region Operator + * \{ */ + +static int edbm_select_similar_region_exec(bContext *C, wmOperator *op) +{ + Object *obedit = CTX_data_edit_object(C); + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + bool changed = false; + + /* group vars */ + int(*group_index)[2]; + int group_tot; + int i; + + if (bm->totfacesel < 2) { + BKE_report(op->reports, RPT_ERROR, "No face regions selected"); + return OPERATOR_CANCELLED; + } + + int *groups_array = static_cast<int *>( + MEM_mallocN(sizeof(*groups_array) * bm->totfacesel, __func__)); + group_tot = BM_mesh_calc_face_groups( + bm, groups_array, &group_index, nullptr, nullptr, nullptr, BM_ELEM_SELECT, BM_VERT); + + BM_mesh_elem_table_ensure(bm, BM_FACE); + + for (i = 0; i < group_tot; i++) { + ListBase faces_regions; + int tot; + + const int fg_sta = group_index[i][0]; + const int fg_len = group_index[i][1]; + int j; + BMFace **fg = static_cast<BMFace **>(MEM_mallocN(sizeof(*fg) * fg_len, __func__)); + + for (j = 0; j < fg_len; j++) { + fg[j] = BM_face_at_index(bm, groups_array[fg_sta + j]); + } + + tot = BM_mesh_region_match(bm, fg, fg_len, &faces_regions); + + MEM_freeN(fg); + + if (tot) { + LinkData *link; + while ((link = static_cast<LinkData *>(BLI_pophead(&faces_regions)))) { + BMFace *f, **faces = static_cast<BMFace **>(link->data); + while ((f = *(faces++))) { + BM_face_select_set(bm, f, true); + } + MEM_freeN(link->data); + MEM_freeN(link); + + changed = true; + } + } + } + + MEM_freeN(groups_array); + MEM_freeN(group_index); + + if (changed) { + DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + } + else { + BKE_report(op->reports, RPT_WARNING, "No matching face regions found"); + } + + return OPERATOR_FINISHED; +} + +void MESH_OT_select_similar_region(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Select Similar Regions"; + ot->idname = "MESH_OT_select_similar_region"; + ot->description = "Select similar face regions to the current selection"; + + /* api callbacks */ + ot->exec = edbm_select_similar_region_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Mode Vert/Edge/Face Operator + * \{ */ + +static int edbm_select_mode_exec(bContext *C, wmOperator *op) +{ + const int type = RNA_enum_get(op->ptr, "type"); + const int action = RNA_enum_get(op->ptr, "action"); + const bool use_extend = RNA_boolean_get(op->ptr, "use_extend"); + const bool use_expand = RNA_boolean_get(op->ptr, "use_expand"); + + if (EDBM_selectmode_toggle_multi(C, type, action, use_extend, use_expand)) { + return OPERATOR_FINISHED; + } + return OPERATOR_CANCELLED; +} + +static int edbm_select_mode_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + /* Bypass when in UV non sync-select mode, fall through to keymap that edits. */ + if (CTX_wm_space_image(C)) { + ToolSettings *ts = CTX_data_tool_settings(C); + if ((ts->uv_flag & UV_SYNC_SELECTION) == 0) { + return OPERATOR_PASS_THROUGH; + } + /* Bypass when no action is needed. */ + if (!RNA_struct_property_is_set(op->ptr, "type")) { + return OPERATOR_CANCELLED; + } + } + + /* detecting these options based on shift/ctrl here is weak, but it's done + * to make this work when clicking buttons or menus */ + if (!RNA_struct_property_is_set(op->ptr, "use_extend")) { + RNA_boolean_set(op->ptr, "use_extend", event->modifier & KM_SHIFT); + } + if (!RNA_struct_property_is_set(op->ptr, "use_expand")) { + RNA_boolean_set(op->ptr, "use_expand", event->modifier & KM_CTRL); + } + + return edbm_select_mode_exec(C, op); +} + +static char *edbm_select_mode_get_description(bContext *UNUSED(C), + wmOperatorType *UNUSED(op), + PointerRNA *values) +{ + const int type = RNA_enum_get(values, "type"); + + /* Because the special behavior for shift and ctrl click depend on user input, they may be + * incorrect if the operator is used from a script or from a special button. So only return the + * specialized descriptions if only the "type" is set, which conveys that the operator is meant + * to be used with the logic in the `invoke` method. */ + if (RNA_struct_property_is_set(values, "type") && + !RNA_struct_property_is_set(values, "use_extend") && + !RNA_struct_property_is_set(values, "use_expand") && + !RNA_struct_property_is_set(values, "action")) { + switch (type) { + case SCE_SELECT_VERTEX: + return BLI_strdup(TIP_( + "Vertex select - Shift-Click for multiple modes, Ctrl-Click contracts selection")); + case SCE_SELECT_EDGE: + return BLI_strdup( + TIP_("Edge select - Shift-Click for multiple modes, " + "Ctrl-Click expands/contracts selection depending on the current mode")); + case SCE_SELECT_FACE: + return BLI_strdup( + TIP_("Face select - Shift-Click for multiple modes, Ctrl-Click expands selection")); + } + } + + return nullptr; +} + +void MESH_OT_select_mode(wmOperatorType *ot) +{ + PropertyRNA *prop; + + static const EnumPropertyItem actions_items[] = { + {0, "DISABLE", 0, "Disable", "Disable selected markers"}, + {1, "ENABLE", 0, "Enable", "Enable selected markers"}, + {2, "TOGGLE", 0, "Toggle", "Toggle disabled flag for selected markers"}, + {0, nullptr, 0, nullptr, nullptr}, + }; + + /* identifiers */ + ot->name = "Select Mode"; + ot->idname = "MESH_OT_select_mode"; + ot->description = "Change selection mode"; + + /* api callbacks */ + ot->invoke = edbm_select_mode_invoke; + ot->exec = edbm_select_mode_exec; + ot->poll = ED_operator_editmesh; + ot->get_description = edbm_select_mode_get_description; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + /* Hide all, not to show redo panel. */ + prop = RNA_def_boolean(ot->srna, "use_extend", false, "Extend", ""); + RNA_def_property_flag(prop, PropertyFlag(PROP_HIDDEN | PROP_SKIP_SAVE)); + prop = RNA_def_boolean(ot->srna, "use_expand", false, "Expand", ""); + RNA_def_property_flag(prop, PropertyFlag(PROP_HIDDEN | PROP_SKIP_SAVE)); + ot->prop = prop = RNA_def_enum(ot->srna, "type", rna_enum_mesh_select_mode_items, 0, "Type", ""); + RNA_def_property_flag(prop, PropertyFlag(PROP_HIDDEN | PROP_SKIP_SAVE)); + + prop = RNA_def_enum( + ot->srna, "action", actions_items, 2, "Action", "Selection action to execute"); + RNA_def_property_flag(prop, PROP_HIDDEN); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Loop (Non Modal) Operator + * \{ */ + +static void walker_select_count(BMEditMesh *em, + int walkercode, + void *start, + int r_count_by_select[2]) +{ + BMesh *bm = em->bm; + BMElem *ele; + BMWalker walker; + + r_count_by_select[0] = r_count_by_select[1] = 0; + + BMW_init(&walker, + bm, + walkercode, + BMW_MASK_NOP, + BMW_MASK_NOP, + BMW_MASK_NOP, + BMW_FLAG_TEST_HIDDEN, + BMW_NIL_LAY); + + for (ele = static_cast<BMElem *>(BMW_begin(&walker, start)); ele; + ele = static_cast<BMElem *>(BMW_step(&walker))) { + r_count_by_select[BM_elem_flag_test(ele, BM_ELEM_SELECT) ? 1 : 0] += 1; + + /* Early exit when mixed (could be optional if needed. */ + if (r_count_by_select[0] && r_count_by_select[1]) { + r_count_by_select[0] = r_count_by_select[1] = -1; + break; + } + } + + BMW_end(&walker); +} + +static void walker_select(BMEditMesh *em, int walkercode, void *start, const bool select) +{ + BMesh *bm = em->bm; + BMElem *ele; + BMWalker walker; + + BMW_init(&walker, + bm, + walkercode, + BMW_MASK_NOP, + BMW_MASK_NOP, + BMW_MASK_NOP, + BMW_FLAG_TEST_HIDDEN, + BMW_NIL_LAY); + + for (ele = static_cast<BMElem *>(BMW_begin(&walker, start)); ele; + ele = static_cast<BMElem *>(BMW_step(&walker))) { + if (!select) { + BM_select_history_remove(bm, ele); + } + BM_elem_select_set(bm, ele, select); + } + BMW_end(&walker); +} + +static int edbm_loop_multiselect_exec(bContext *C, wmOperator *op) +{ + const bool is_ring = RNA_boolean_get(op->ptr, "ring"); + const Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( + scene, view_layer, CTX_wm_view3d(C), &objects_len); + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *obedit = objects[ob_index]; + BMEditMesh *em = BKE_editmesh_from_object(obedit); + + if (em->bm->totedgesel == 0) { + continue; + } + + BMEdge *eed; + int edindex; + BMIter iter; + int totedgesel = 0; + + BM_ITER_MESH (eed, &iter, em->bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(eed, BM_ELEM_SELECT)) { + totedgesel++; + } + } + + BMEdge **edarray = static_cast<BMEdge **>( + MEM_mallocN(sizeof(BMEdge *) * totedgesel, "edge array")); + edindex = 0; + + BM_ITER_MESH (eed, &iter, em->bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(eed, BM_ELEM_SELECT)) { + edarray[edindex] = eed; + edindex++; + } + } + + if (is_ring) { + for (edindex = 0; edindex < totedgesel; edindex += 1) { + eed = edarray[edindex]; + walker_select(em, BMW_EDGERING, eed, true); + } + EDBM_selectmode_flush(em); + } + else { + for (edindex = 0; edindex < totedgesel; edindex += 1) { + eed = edarray[edindex]; + bool non_manifold = BM_edge_face_count_is_over(eed, 2); + if (non_manifold) { + walker_select(em, BMW_EDGELOOP_NONMANIFOLD, eed, true); + } + else { + walker_select(em, BMW_EDGELOOP, eed, true); + } + } + EDBM_selectmode_flush(em); + } + MEM_freeN(edarray); + // if (EM_texFaceCheck()) + + DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + } + MEM_freeN(objects); + + return OPERATOR_FINISHED; +} + +void MESH_OT_loop_multi_select(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Multi Select Loops"; + ot->idname = "MESH_OT_loop_multi_select"; + ot->description = "Select a loop of connected edges by connection type"; + + /* api callbacks */ + ot->exec = edbm_loop_multiselect_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + RNA_def_boolean(ot->srna, "ring", 0, "Ring", ""); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Loop (Cursor Pick) Operator + * \{ */ + +static void mouse_mesh_loop_face(BMEditMesh *em, BMEdge *eed, bool select, bool select_clear) +{ + if (select_clear) { + EDBM_flag_disable_all(em, BM_ELEM_SELECT); + } + + walker_select(em, BMW_FACELOOP, eed, select); +} + +static void mouse_mesh_loop_edge_ring(BMEditMesh *em, BMEdge *eed, bool select, bool select_clear) +{ + if (select_clear) { + EDBM_flag_disable_all(em, BM_ELEM_SELECT); + } + + walker_select(em, BMW_EDGERING, eed, select); +} + +static void mouse_mesh_loop_edge( + BMEditMesh *em, BMEdge *eed, bool select, bool select_clear, bool select_cycle) +{ + bool edge_boundary = false; + bool non_manifold = BM_edge_face_count_is_over(eed, 2); + + /* Cycle between BMW_EDGELOOP / BMW_EDGEBOUNDARY. */ + if (select_cycle && BM_edge_is_boundary(eed)) { + int count_by_select[2]; + + /* If the loops selected toggle the boundaries. */ + walker_select_count(em, BMW_EDGELOOP, eed, count_by_select); + if (count_by_select[!select] == 0) { + edge_boundary = true; + + /* If the boundaries selected, toggle back to the loop. */ + walker_select_count(em, BMW_EDGEBOUNDARY, eed, count_by_select); + if (count_by_select[!select] == 0) { + edge_boundary = false; + } + } + } + + if (select_clear) { + EDBM_flag_disable_all(em, BM_ELEM_SELECT); + } + + if (edge_boundary) { + walker_select(em, BMW_EDGEBOUNDARY, eed, select); + } + else if (non_manifold) { + walker_select(em, BMW_EDGELOOP_NONMANIFOLD, eed, select); + } + else { + walker_select(em, BMW_EDGELOOP, eed, select); + } +} + +static bool mouse_mesh_loop( + bContext *C, const int mval[2], bool extend, bool deselect, bool toggle, bool ring) +{ + Base *basact = nullptr; + BMVert *eve = nullptr; + BMEdge *eed = nullptr; + BMFace *efa = nullptr; + + ViewContext vc; + BMEditMesh *em; + bool select = true; + bool select_clear = false; + bool select_cycle = true; + float mvalf[2]; + + em_setup_viewcontext(C, &vc); + mvalf[0] = (float)(vc.mval[0] = mval[0]); + mvalf[1] = (float)(vc.mval[1] = mval[1]); + + BMEditMesh *em_original = vc.em; + const short selectmode = em_original->selectmode; + em_original->selectmode = SCE_SELECT_EDGE; + + uint bases_len; + Base **bases = BKE_view_layer_array_from_bases_in_edit_mode( + vc.scene, vc.view_layer, vc.v3d, &bases_len); + + { + int base_index = -1; + if (EDBM_unified_findnearest(&vc, bases, bases_len, &base_index, &eve, &eed, &efa)) { + basact = bases[base_index]; + ED_view3d_viewcontext_init_object(&vc, basact->object); + em = vc.em; + } + else { + em = nullptr; + } + } + + em_original->selectmode = selectmode; + + if (em == nullptr || eed == nullptr) { + MEM_freeN(bases); + return false; + } + + if (extend == false && deselect == false && toggle == false) { + select_clear = true; + } + + if (extend) { + select = true; + } + else if (deselect) { + select = false; + } + else if (select_clear || (BM_elem_flag_test(eed, BM_ELEM_SELECT) == 0)) { + select = true; + } + else if (toggle) { + select = false; + select_cycle = false; + } + + if (select_clear) { + for (uint base_index = 0; base_index < bases_len; base_index++) { + Base *base_iter = bases[base_index]; + Object *ob_iter = base_iter->object; + BMEditMesh *em_iter = BKE_editmesh_from_object(ob_iter); + + if (em_iter->bm->totvertsel == 0) { + continue; + } + + if (em_iter == em) { + continue; + } + + EDBM_flag_disable_all(em_iter, BM_ELEM_SELECT); + DEG_id_tag_update(static_cast<ID *>(ob_iter->data), ID_RECALC_SELECT); + } + } + + if (em->selectmode & SCE_SELECT_FACE) { + mouse_mesh_loop_face(em, eed, select, select_clear); + } + else { + if (ring) { + mouse_mesh_loop_edge_ring(em, eed, select, select_clear); + } + else { + mouse_mesh_loop_edge(em, eed, select, select_clear, select_cycle); + } + } + + EDBM_selectmode_flush(em); + + /* sets as active, useful for other tools */ + if (select) { + if (em->selectmode & SCE_SELECT_VERTEX) { + /* Find nearest vert from mouse + * (initialize to large values in case only one vertex can be projected) */ + float v1_co[2], v2_co[2]; + float length_1 = FLT_MAX; + float length_2 = FLT_MAX; + + /* We can't be sure this has already been set... */ + ED_view3d_init_mats_rv3d(vc.obedit, vc.rv3d); + + if (ED_view3d_project_float_object(vc.region, eed->v1->co, v1_co, V3D_PROJ_TEST_CLIP_NEAR) == + V3D_PROJ_RET_OK) { + length_1 = len_squared_v2v2(mvalf, v1_co); + } + + if (ED_view3d_project_float_object(vc.region, eed->v2->co, v2_co, V3D_PROJ_TEST_CLIP_NEAR) == + V3D_PROJ_RET_OK) { + length_2 = len_squared_v2v2(mvalf, v2_co); + } +#if 0 + printf("mouse to v1: %f\nmouse to v2: %f\n", + len_squared_v2v2(mvalf, v1_co), + len_squared_v2v2(mvalf, v2_co)); +#endif + BM_select_history_store(em->bm, (length_1 < length_2) ? eed->v1 : eed->v2); + } + else if (em->selectmode & SCE_SELECT_EDGE) { + BM_select_history_store(em->bm, eed); + } + else if (em->selectmode & SCE_SELECT_FACE) { + /* Select the face of eed which is the nearest of mouse. */ + BMFace *f; + BMIter iterf; + float best_dist = FLT_MAX; + efa = nullptr; + + /* We can't be sure this has already been set... */ + ED_view3d_init_mats_rv3d(vc.obedit, vc.rv3d); + + BM_ITER_ELEM (f, &iterf, eed, BM_FACES_OF_EDGE) { + if (BM_elem_flag_test(f, BM_ELEM_SELECT)) { + float cent[3]; + float co[2], tdist; + + BM_face_calc_center_median(f, cent); + if (ED_view3d_project_float_object(vc.region, cent, co, V3D_PROJ_TEST_CLIP_NEAR) == + V3D_PROJ_RET_OK) { + tdist = len_squared_v2v2(mvalf, co); + if (tdist < best_dist) { + // printf("Best face: %p (%f)\n", f, tdist); + best_dist = tdist; + efa = f; + } + } + } + } + if (efa) { + BM_mesh_active_face_set(em->bm, efa); + BM_select_history_store(em->bm, efa); + } + } + } + + MEM_freeN(bases); + + DEG_id_tag_update(static_cast<ID *>(vc.obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, vc.obedit->data); + + return true; +} + +static int edbm_select_loop_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + + view3d_operator_needs_opengl(C); + + if (mouse_mesh_loop(C, + event->mval, + RNA_boolean_get(op->ptr, "extend"), + RNA_boolean_get(op->ptr, "deselect"), + RNA_boolean_get(op->ptr, "toggle"), + RNA_boolean_get(op->ptr, "ring"))) { + return OPERATOR_FINISHED; + } + return OPERATOR_CANCELLED; +} + +void MESH_OT_loop_select(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Loop Select"; + ot->idname = "MESH_OT_loop_select"; + ot->description = "Select a loop of connected edges"; + + /* api callbacks */ + ot->invoke = edbm_select_loop_invoke; + ot->poll = ED_operator_editmesh_region_view3d; + + /* flags */ + ot->flag = OPTYPE_UNDO; + + /* properties */ + PropertyRNA *prop; + + prop = RNA_def_boolean(ot->srna, "extend", 0, "Extend Select", "Extend the selection"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + prop = RNA_def_boolean(ot->srna, "deselect", 0, "Deselect", "Remove from the selection"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + prop = RNA_def_boolean(ot->srna, "toggle", 0, "Toggle Select", "Toggle the selection"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + prop = RNA_def_boolean(ot->srna, "ring", 0, "Select Ring", "Select ring"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); +} + +void MESH_OT_edgering_select(wmOperatorType *ot) +{ + /* description */ + ot->name = "Edge Ring Select"; + ot->idname = "MESH_OT_edgering_select"; + ot->description = "Select an edge ring"; + + /* callbacks */ + ot->invoke = edbm_select_loop_invoke; + ot->poll = ED_operator_editmesh_region_view3d; + + /* flags */ + ot->flag = OPTYPE_UNDO; + + /* Properties. */ + PropertyRNA *prop; + prop = RNA_def_boolean(ot->srna, "extend", 0, "Extend", "Extend the selection"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + prop = RNA_def_boolean(ot->srna, "deselect", 0, "Deselect", "Remove from the selection"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + prop = RNA_def_boolean(ot->srna, "toggle", 0, "Toggle Select", "Toggle the selection"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + prop = RNA_def_boolean(ot->srna, "ring", 1, "Select Ring", "Select ring"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name (De)Select All Operator + * \{ */ + +static int edbm_select_all_exec(bContext *C, wmOperator *op) +{ + const Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + int action = RNA_enum_get(op->ptr, "action"); + + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( + scene, view_layer, CTX_wm_view3d(C), &objects_len); + + if (action == SEL_TOGGLE) { + action = SEL_SELECT; + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *obedit = objects[ob_index]; + BMEditMesh *em = BKE_editmesh_from_object(obedit); + if (em->bm->totvertsel || em->bm->totedgesel || em->bm->totfacesel) { + action = SEL_DESELECT; + break; + } + } + } + + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *obedit = objects[ob_index]; + BMEditMesh *em = BKE_editmesh_from_object(obedit); + switch (action) { + case SEL_SELECT: + EDBM_flag_enable_all(em, BM_ELEM_SELECT); + break; + case SEL_DESELECT: + EDBM_flag_disable_all(em, BM_ELEM_SELECT); + break; + case SEL_INVERT: + EDBM_select_swap(em); + EDBM_selectmode_flush(em); + break; + } + DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + } + + MEM_freeN(objects); + + return OPERATOR_FINISHED; +} + +void MESH_OT_select_all(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "(De)select All"; + ot->idname = "MESH_OT_select_all"; + ot->description = "(De)select all vertices, edges or faces"; + + /* api callbacks */ + ot->exec = edbm_select_all_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + WM_operator_properties_select_all(ot); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Interior Faces Operator + * \{ */ + +static int edbm_faces_select_interior_exec(bContext *C, wmOperator *UNUSED(op)) +{ + const Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( + scene, view_layer, CTX_wm_view3d(C), &objects_len); + + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *obedit = objects[ob_index]; + BMEditMesh *em = BKE_editmesh_from_object(obedit); + + if (!EDBM_select_interior_faces(em)) { + continue; + } + + DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + } + MEM_freeN(objects); + + return OPERATOR_FINISHED; +} + +void MESH_OT_select_interior_faces(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Select Interior Faces"; + ot->idname = "MESH_OT_select_interior_faces"; + ot->description = "Select faces where all edges have more than 2 face users"; + + /* api callbacks */ + ot->exec = edbm_faces_select_interior_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Picking API + * + * Here actual select happens, + * Gets called via generic mouse select operator. + * \{ */ + +bool EDBM_select_pick(bContext *C, const int mval[2], const SelectPick_Params *params) +{ + ViewContext vc; + + int base_index_active = -1; + BMVert *eve = nullptr; + BMEdge *eed = nullptr; + BMFace *efa = nullptr; + + /* setup view context for argument to callbacks */ + em_setup_viewcontext(C, &vc); + vc.mval[0] = mval[0]; + vc.mval[1] = mval[1]; + + uint bases_len = 0; + Base **bases = BKE_view_layer_array_from_bases_in_edit_mode( + vc.scene, vc.view_layer, vc.v3d, &bases_len); + + bool changed = false; + bool found = unified_findnearest(&vc, bases, bases_len, &base_index_active, &eve, &eed, &efa); + + if (params->sel_op == SEL_OP_SET) { + BMElem *ele = efa ? (BMElem *)efa : (eed ? (BMElem *)eed : (BMElem *)eve); + if ((found && params->select_passthrough) && BM_elem_flag_test(ele, BM_ELEM_SELECT)) { + found = false; + } + else if (found || params->deselect_all) { + /* Deselect everything. */ + for (uint base_index = 0; base_index < bases_len; base_index++) { + Base *base_iter = bases[base_index]; + Object *ob_iter = base_iter->object; + EDBM_flag_disable_all(BKE_editmesh_from_object(ob_iter), BM_ELEM_SELECT); + DEG_id_tag_update(static_cast<ID *>(ob_iter->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, ob_iter->data); + } + changed = true; + } + } + + if (found) { + Base *basact = bases[base_index_active]; + ED_view3d_viewcontext_init_object(&vc, basact->object); + + if (efa) { + switch (params->sel_op) { + case SEL_OP_ADD: { + BM_mesh_active_face_set(vc.em->bm, efa); + + /* Work-around: deselect first, so we can guarantee it will + * be active even if it was already selected. */ + BM_select_history_remove(vc.em->bm, efa); + BM_face_select_set(vc.em->bm, efa, false); + BM_select_history_store(vc.em->bm, efa); + BM_face_select_set(vc.em->bm, efa, true); + break; + } + case SEL_OP_SUB: { + BM_select_history_remove(vc.em->bm, efa); + BM_face_select_set(vc.em->bm, efa, false); + break; + } + case SEL_OP_XOR: { + BM_mesh_active_face_set(vc.em->bm, efa); + if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) { + BM_select_history_store(vc.em->bm, efa); + BM_face_select_set(vc.em->bm, efa, true); + } + else { + BM_select_history_remove(vc.em->bm, efa); + BM_face_select_set(vc.em->bm, efa, false); + } + break; + } + case SEL_OP_SET: { + BM_mesh_active_face_set(vc.em->bm, efa); + if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) { + BM_select_history_store(vc.em->bm, efa); + BM_face_select_set(vc.em->bm, efa, true); + } + break; + } + case SEL_OP_AND: { + BLI_assert_unreachable(); /* Doesn't make sense for picking. */ + break; + } + } + } + else if (eed) { + + switch (params->sel_op) { + case SEL_OP_ADD: { + /* Work-around: deselect first, so we can guarantee it will + * be active even if it was already selected. */ + BM_select_history_remove(vc.em->bm, eed); + BM_edge_select_set(vc.em->bm, eed, false); + BM_select_history_store(vc.em->bm, eed); + BM_edge_select_set(vc.em->bm, eed, true); + break; + } + case SEL_OP_SUB: { + BM_select_history_remove(vc.em->bm, eed); + BM_edge_select_set(vc.em->bm, eed, false); + break; + } + case SEL_OP_XOR: { + if (!BM_elem_flag_test(eed, BM_ELEM_SELECT)) { + BM_select_history_store(vc.em->bm, eed); + BM_edge_select_set(vc.em->bm, eed, true); + } + else { + BM_select_history_remove(vc.em->bm, eed); + BM_edge_select_set(vc.em->bm, eed, false); + } + break; + } + case SEL_OP_SET: { + if (!BM_elem_flag_test(eed, BM_ELEM_SELECT)) { + BM_select_history_store(vc.em->bm, eed); + BM_edge_select_set(vc.em->bm, eed, true); + } + break; + } + case SEL_OP_AND: { + BLI_assert_unreachable(); /* Doesn't make sense for picking. */ + break; + } + } + } + else if (eve) { + switch (params->sel_op) { + case SEL_OP_ADD: { + /* Work-around: deselect first, so we can guarantee it will + * be active even if it was already selected. */ + BM_select_history_remove(vc.em->bm, eve); + BM_vert_select_set(vc.em->bm, eve, false); + BM_select_history_store(vc.em->bm, eve); + BM_vert_select_set(vc.em->bm, eve, true); + break; + } + case SEL_OP_SUB: { + BM_select_history_remove(vc.em->bm, eve); + BM_vert_select_set(vc.em->bm, eve, false); + break; + } + case SEL_OP_XOR: { + if (!BM_elem_flag_test(eve, BM_ELEM_SELECT)) { + BM_select_history_store(vc.em->bm, eve); + BM_vert_select_set(vc.em->bm, eve, true); + } + else { + BM_select_history_remove(vc.em->bm, eve); + BM_vert_select_set(vc.em->bm, eve, false); + } + break; + } + case SEL_OP_SET: { + if (!BM_elem_flag_test(eve, BM_ELEM_SELECT)) { + BM_select_history_store(vc.em->bm, eve); + BM_vert_select_set(vc.em->bm, eve, true); + } + break; + } + case SEL_OP_AND: { + BLI_assert_unreachable(); /* Doesn't make sense for picking. */ + break; + } + } + } + + EDBM_selectmode_flush(vc.em); + + if (efa) { + /* Change active material on object. */ + if (efa->mat_nr != vc.obedit->actcol - 1) { + vc.obedit->actcol = efa->mat_nr + 1; + vc.em->mat_nr = efa->mat_nr; + WM_event_add_notifier(C, NC_MATERIAL | ND_SHADING_LINKS, nullptr); + } + + /* Change active face-map on object. */ + if (!BLI_listbase_is_empty(&vc.obedit->fmaps)) { + const int cd_fmap_offset = CustomData_get_offset(&vc.em->bm->pdata, CD_FACEMAP); + if (cd_fmap_offset != -1) { + int map = *((int *)BM_ELEM_CD_GET_VOID_P(efa, cd_fmap_offset)); + if ((map < -1) || (map > BLI_listbase_count_at_most(&vc.obedit->fmaps, map))) { + map = -1; + } + map += 1; + if (map != vc.obedit->actfmap) { + /* We may want to add notifiers later, + * currently select update handles redraw. */ + vc.obedit->actfmap = map; + } + } + } + } + + /* Changing active object is handy since it allows us to + * switch UV layers, vgroups for eg. */ + BKE_view_layer_synced_ensure(vc.scene, vc.view_layer); + if (BKE_view_layer_active_base_get(vc.view_layer) != basact) { + ED_object_base_activate(C, basact); + } + + DEG_id_tag_update(static_cast<ID *>(vc.obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, vc.obedit->data); + + changed = true; + } + + MEM_freeN(bases); + + return changed; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Mode Utilities + * \{ */ + +static void edbm_strip_selections(BMEditMesh *em) +{ + BMEditSelection *ese, *nextese; + + if (!(em->selectmode & SCE_SELECT_VERTEX)) { + ese = static_cast<BMEditSelection *>(em->bm->selected.first); + while (ese) { + nextese = ese->next; + if (ese->htype == BM_VERT) { + BLI_freelinkN(&(em->bm->selected), ese); + } + ese = nextese; + } + } + if (!(em->selectmode & SCE_SELECT_EDGE)) { + ese = static_cast<BMEditSelection *>(em->bm->selected.first); + while (ese) { + nextese = ese->next; + if (ese->htype == BM_EDGE) { + BLI_freelinkN(&(em->bm->selected), ese); + } + ese = nextese; + } + } + if (!(em->selectmode & SCE_SELECT_FACE)) { + ese = static_cast<BMEditSelection *>(em->bm->selected.first); + while (ese) { + nextese = ese->next; + if (ese->htype == BM_FACE) { + BLI_freelinkN(&(em->bm->selected), ese); + } + ese = nextese; + } + } +} + +void EDBM_selectmode_set(BMEditMesh *em) +{ + BMVert *eve; + BMEdge *eed; + BMFace *efa; + BMIter iter; + + em->bm->selectmode = em->selectmode; + + /* strip BMEditSelections from em->selected that are not relevant to new mode */ + edbm_strip_selections(em); + + if (em->bm->totvertsel == 0 && em->bm->totedgesel == 0 && em->bm->totfacesel == 0) { + return; + } + + if (em->selectmode & SCE_SELECT_VERTEX) { + if (em->bm->totvertsel) { + EDBM_select_flush(em); + } + } + else if (em->selectmode & SCE_SELECT_EDGE) { + /* deselect vertices, and select again based on edge select */ + BM_ITER_MESH (eve, &iter, em->bm, BM_VERTS_OF_MESH) { + BM_vert_select_set(em->bm, eve, false); + } + + if (em->bm->totedgesel) { + BM_ITER_MESH (eed, &iter, em->bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(eed, BM_ELEM_SELECT)) { + BM_edge_select_set(em->bm, eed, true); + } + } + + /* selects faces based on edge status */ + EDBM_selectmode_flush(em); + } + } + else if (em->selectmode & SCE_SELECT_FACE) { + /* Deselect edges, and select again based on face select. */ + BM_ITER_MESH (eed, &iter, em->bm, BM_EDGES_OF_MESH) { + BM_edge_select_set(em->bm, eed, false); + } + + if (em->bm->totfacesel) { + BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(efa, BM_ELEM_SELECT)) { + BM_face_select_set(em->bm, efa, true); + } + } + } + } +} + +void EDBM_selectmode_convert(BMEditMesh *em, + const short selectmode_old, + const short selectmode_new) +{ + BMesh *bm = em->bm; + + BMVert *eve; + BMEdge *eed; + BMFace *efa; + BMIter iter; + + /* first tag-to-select, then select --- this avoids a feedback loop */ + + /* Have to find out what the selection-mode was previously. */ + if (selectmode_old == SCE_SELECT_VERTEX) { + if (bm->totvertsel == 0) { + /* pass */ + } + else if (selectmode_new == SCE_SELECT_EDGE) { + /* flush up (vert -> edge) */ + + /* select all edges associated with every selected vert */ + BM_ITER_MESH (eed, &iter, bm, BM_EDGES_OF_MESH) { + BM_elem_flag_set(eed, BM_ELEM_TAG, BM_edge_is_any_vert_flag_test(eed, BM_ELEM_SELECT)); + } + + BM_ITER_MESH (eed, &iter, bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(eed, BM_ELEM_TAG)) { + BM_edge_select_set(bm, eed, true); + } + } + } + else if (selectmode_new == SCE_SELECT_FACE) { + /* flush up (vert -> face) */ + + /* select all faces associated with every selected vert */ + BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { + BM_elem_flag_set(efa, BM_ELEM_TAG, BM_face_is_any_vert_flag_test(efa, BM_ELEM_SELECT)); + } + + BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(efa, BM_ELEM_TAG)) { + BM_face_select_set(bm, efa, true); + } + } + } + } + else if (selectmode_old == SCE_SELECT_EDGE) { + if (bm->totedgesel == 0) { + /* pass */ + } + else if (selectmode_new == SCE_SELECT_FACE) { + /* flush up (edge -> face) */ + + /* select all faces associated with every selected edge */ + BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { + BM_elem_flag_set(efa, BM_ELEM_TAG, BM_face_is_any_edge_flag_test(efa, BM_ELEM_SELECT)); + } + + BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(efa, BM_ELEM_TAG)) { + BM_face_select_set(bm, efa, true); + } + } + } + else if (selectmode_new == SCE_SELECT_VERTEX) { + /* flush down (edge -> vert) */ + + BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) { + if (!BM_vert_is_all_edge_flag_test(eve, BM_ELEM_SELECT, true)) { + BM_vert_select_set(bm, eve, false); + } + } + /* deselect edges without both verts selected */ + BM_mesh_deselect_flush(bm); + } + } + else if (selectmode_old == SCE_SELECT_FACE) { + if (bm->totfacesel == 0) { + /* pass */ + } + else if (selectmode_new == SCE_SELECT_EDGE) { + /* flush down (face -> edge) */ + + BM_ITER_MESH (eed, &iter, bm, BM_EDGES_OF_MESH) { + if (!BM_edge_is_all_face_flag_test(eed, BM_ELEM_SELECT, true)) { + BM_edge_select_set(bm, eed, false); + } + } + /* Deselect faces without edges selected. */ + BM_mesh_deselect_flush(bm); + } + else if (selectmode_new == SCE_SELECT_VERTEX) { + /* flush down (face -> vert) */ + + BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) { + if (!BM_vert_is_all_face_flag_test(eve, BM_ELEM_SELECT, true)) { + BM_vert_select_set(bm, eve, false); + } + } + /* deselect faces without verts selected */ + BM_mesh_deselect_flush(bm); + } + } +} + +bool EDBM_selectmode_toggle_multi(bContext *C, + const short selectmode_new, + const int action, + const bool use_extend, + const bool use_expand) +{ + Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + ToolSettings *ts = CTX_data_tool_settings(C); + Object *obedit = CTX_data_edit_object(C); + BMEditMesh *em = nullptr; + bool ret = false; + + if (obedit && obedit->type == OB_MESH) { + em = BKE_editmesh_from_object(obedit); + } + + if (em == nullptr) { + return ret; + } + + bool only_update = false; + switch (action) { + case -1: + /* already set */ + break; + case 0: /* disable */ + /* check we have something to do */ + if ((em->selectmode & selectmode_new) == 0) { + only_update = true; + break; + } + em->selectmode &= ~selectmode_new; + break; + case 1: /* enable */ + /* check we have something to do */ + if ((em->selectmode & selectmode_new) != 0) { + only_update = true; + break; + } + em->selectmode |= selectmode_new; + break; + case 2: /* toggle */ + /* can't disable this flag if its the only one set */ + if (em->selectmode == selectmode_new) { + only_update = true; + break; + } + em->selectmode ^= selectmode_new; + break; + default: + BLI_assert(0); + break; + } + + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( + scene, view_layer, CTX_wm_view3d(C), &objects_len); + + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *ob_iter = objects[ob_index]; + BMEditMesh *em_iter = BKE_editmesh_from_object(ob_iter); + if (em_iter != em) { + em_iter->selectmode = em->selectmode; + } + } + + if (only_update) { + MEM_freeN(objects); + return false; + } + + if (use_extend == 0 || em->selectmode == 0) { + if (use_expand) { + const short selmode_max = highest_order_bit_s(ts->selectmode); + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *ob_iter = objects[ob_index]; + BMEditMesh *em_iter = BKE_editmesh_from_object(ob_iter); + EDBM_selectmode_convert(em_iter, selmode_max, selectmode_new); + } + } + } + + switch (selectmode_new) { + case SCE_SELECT_VERTEX: + if (use_extend == 0 || em->selectmode == 0) { + em->selectmode = SCE_SELECT_VERTEX; + } + ret = true; + break; + case SCE_SELECT_EDGE: + if (use_extend == 0 || em->selectmode == 0) { + em->selectmode = SCE_SELECT_EDGE; + } + ret = true; + break; + case SCE_SELECT_FACE: + if (use_extend == 0 || em->selectmode == 0) { + em->selectmode = SCE_SELECT_FACE; + } + ret = true; + break; + default: + BLI_assert(0); + break; + } + + if (ret == true) { + ts->selectmode = em->selectmode; + em = nullptr; + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *ob_iter = objects[ob_index]; + BMEditMesh *em_iter = BKE_editmesh_from_object(ob_iter); + em_iter->selectmode = ts->selectmode; + EDBM_selectmode_set(em_iter); + DEG_id_tag_update(static_cast<ID *>(ob_iter->data), + ID_RECALC_COPY_ON_WRITE | ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, ob_iter->data); + } + WM_main_add_notifier(NC_SCENE | ND_TOOLSETTINGS, nullptr); + DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE); + } + + MEM_freeN(objects); + return ret; +} + +bool EDBM_selectmode_set_multi(bContext *C, const short selectmode) +{ + BLI_assert(selectmode != 0); + bool changed = false; + + { + Object *obedit = CTX_data_edit_object(C); + BMEditMesh *em = nullptr; + if (obedit && obedit->type == OB_MESH) { + em = BKE_editmesh_from_object(obedit); + } + if (em == nullptr) { + return changed; + } + } + + ViewLayer *view_layer = CTX_data_view_layer(C); + Scene *scene = CTX_data_scene(C); + ToolSettings *ts = scene->toolsettings; + + if (ts->selectmode != selectmode) { + ts->selectmode = selectmode; + changed = true; + } + + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( + scene, view_layer, CTX_wm_view3d(C), &objects_len); + + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *ob_iter = objects[ob_index]; + BMEditMesh *em_iter = BKE_editmesh_from_object(ob_iter); + if (em_iter->selectmode != ts->selectmode) { + em_iter->selectmode = ts->selectmode; + EDBM_selectmode_set(em_iter); + DEG_id_tag_update(static_cast<ID *>(ob_iter->data), + ID_RECALC_COPY_ON_WRITE | ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, ob_iter->data); + changed = true; + } + } + MEM_freeN(objects); + + if (changed) { + WM_main_add_notifier(NC_SCENE | ND_TOOLSETTINGS, nullptr); + DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE); + } + return changed; +} + +bool EDBM_selectmode_disable(Scene *scene, + BMEditMesh *em, + const short selectmode_disable, + const short selectmode_fallback) +{ + /* note essential, but switch out of vertex mode since the + * selected regions won't be nicely isolated after flushing */ + if (em->selectmode & selectmode_disable) { + if (em->selectmode == selectmode_disable) { + em->selectmode = selectmode_fallback; + } + else { + em->selectmode &= ~selectmode_disable; + } + scene->toolsettings->selectmode = em->selectmode; + EDBM_selectmode_set(em); + + WM_main_add_notifier(NC_SCENE | ND_TOOLSETTINGS, scene); + + return true; + } + return false; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Toggle + * \{ */ + +bool EDBM_deselect_by_material(BMEditMesh *em, const short index, const bool select) +{ + BMIter iter; + BMFace *efa; + bool changed = false; + + BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(efa, BM_ELEM_HIDDEN)) { + continue; + } + if (efa->mat_nr == index) { + changed = true; + BM_face_select_set(em->bm, efa, select); + } + } + return changed; +} + +void EDBM_select_toggle_all(BMEditMesh *em) /* exported for UV */ +{ + if (em->bm->totvertsel || em->bm->totedgesel || em->bm->totfacesel) { + EDBM_flag_disable_all(em, BM_ELEM_SELECT); + } + else { + EDBM_flag_enable_all(em, BM_ELEM_SELECT); + } +} + +void EDBM_select_swap(BMEditMesh *em) /* exported for UV */ +{ + BMIter iter; + BMVert *eve; + BMEdge *eed; + BMFace *efa; + + if (em->bm->selectmode & SCE_SELECT_VERTEX) { + BM_ITER_MESH (eve, &iter, em->bm, BM_VERTS_OF_MESH) { + if (BM_elem_flag_test(eve, BM_ELEM_HIDDEN)) { + continue; + } + BM_vert_select_set(em->bm, eve, !BM_elem_flag_test(eve, BM_ELEM_SELECT)); + } + } + else if (em->selectmode & SCE_SELECT_EDGE) { + BM_ITER_MESH (eed, &iter, em->bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(eed, BM_ELEM_HIDDEN)) { + continue; + } + BM_edge_select_set(em->bm, eed, !BM_elem_flag_test(eed, BM_ELEM_SELECT)); + } + } + else { + BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(efa, BM_ELEM_HIDDEN)) { + continue; + } + BM_face_select_set(em->bm, efa, !BM_elem_flag_test(efa, BM_ELEM_SELECT)); + } + } +} + +bool EDBM_mesh_deselect_all_multi_ex(Base **bases, const uint bases_len) +{ + bool changed_multi = false; + for (uint base_index = 0; base_index < bases_len; base_index++) { + Base *base_iter = bases[base_index]; + Object *ob_iter = base_iter->object; + BMEditMesh *em_iter = BKE_editmesh_from_object(ob_iter); + + if (em_iter->bm->totvertsel == 0) { + continue; + } + + EDBM_flag_disable_all(em_iter, BM_ELEM_SELECT); + DEG_id_tag_update(static_cast<ID *>(ob_iter->data), ID_RECALC_SELECT); + changed_multi = true; + } + return changed_multi; +} + +bool EDBM_mesh_deselect_all_multi(bContext *C) +{ + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + ViewContext vc; + ED_view3d_viewcontext_init(C, &vc, depsgraph); + uint bases_len = 0; + Base **bases = BKE_view_layer_array_from_bases_in_edit_mode_unique_data( + vc.scene, vc.view_layer, vc.v3d, &bases_len); + bool changed_multi = EDBM_mesh_deselect_all_multi_ex(bases, bases_len); + MEM_freeN(bases); + return changed_multi; +} + +bool EDBM_selectmode_disable_multi_ex(Scene *scene, + Base **bases, + const uint bases_len, + const short selectmode_disable, + const short selectmode_fallback) +{ + bool changed_multi = false; + for (uint base_index = 0; base_index < bases_len; base_index++) { + Base *base_iter = bases[base_index]; + Object *ob_iter = base_iter->object; + BMEditMesh *em_iter = BKE_editmesh_from_object(ob_iter); + + if (EDBM_selectmode_disable(scene, em_iter, selectmode_disable, selectmode_fallback)) { + changed_multi = true; + } + } + return changed_multi; +} + +bool EDBM_selectmode_disable_multi(bContext *C, + const short selectmode_disable, + const short selectmode_fallback) +{ + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + Scene *scene = CTX_data_scene(C); + ViewContext vc; + ED_view3d_viewcontext_init(C, &vc, depsgraph); + uint bases_len = 0; + Base **bases = BKE_view_layer_array_from_bases_in_edit_mode_unique_data( + vc.scene, vc.view_layer, nullptr, &bases_len); + bool changed_multi = EDBM_selectmode_disable_multi_ex( + scene, bases, bases_len, selectmode_disable, selectmode_fallback); + MEM_freeN(bases); + return changed_multi; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Interior Faces + * + * Overview of the algorithm: + * - Groups faces surrounded by edges with 3+ faces using them. + * - Calculates a cost of each face group comparing its angle with the faces + * connected to its non-manifold edges. + * - Mark the face group as interior, and mark connected face groups for recalculation. + * - Continue to remove the face groups with the highest 'cost'. + * + * \{ */ + +struct BMFaceLink { + struct BMFaceLink *next, *prev; + BMFace *face; + float area; +}; + +static bool bm_interior_loop_filter_fn(const BMLoop *l, void *UNUSED(user_data)) +{ + if (BM_elem_flag_test(l->e, BM_ELEM_TAG)) { + return false; + } + return true; +} +static bool bm_interior_edge_is_manifold_except_face_index(BMEdge *e, + int face_index, + BMLoop *r_l_pair[2]) +{ + + BMLoop *l_iter = e->l; + int loop_index = 0; + do { + BMFace *f = l_iter->f; + int i = BM_elem_index_get(f); + if (!ELEM(i, -1, face_index)) { + if (loop_index == 2) { + return false; + } + r_l_pair[loop_index++] = l_iter; + } + } while ((l_iter = l_iter->radial_next) != e->l); + return (loop_index == 2); +} + +/** + * Calculate the cost of the face group. + * A higher value means it's more likely to remove first. + */ +static float bm_interior_face_group_calc_cost(ListBase *ls, const float *edge_lengths) +{ + /* Dividing by the area is important so larger face groups (which will become the outer shell) + * aren't detected as having a high cost. */ + float area = 0.0f; + float cost = 0.0f; + bool found = false; + LISTBASE_FOREACH (BMFaceLink *, f_link, ls) { + BMFace *f = f_link->face; + area += f_link->area; + int i = BM_elem_index_get(f); + BLI_assert(i != -1); + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + if (BM_elem_flag_test(l_iter->e, BM_ELEM_TAG)) { + float cost_test = 0.0f; + int cost_count = 0; + /* All other faces. */ + BMLoop *l_radial_iter = l_iter; + do { + int i_other = BM_elem_index_get(l_radial_iter->f); + if (!ELEM(i_other, -1, i)) { + float angle = angle_normalized_v3v3(f->no, l_radial_iter->f->no); + /* Ignore face direction since in the case on non-manifold faces connecting edges, + * the face flipping may not be meaningful. */ + if (angle > DEG2RADF(90)) { + angle = DEG2RADF(180) - angle; + } + /* Avoid calculating it inline, pass in pre-calculated edge lengths. */ +#if 0 + cost_test += BM_edge_calc_length(l_iter->e) * angle; +#else + BLI_assert(edge_lengths[BM_elem_index_get(l_iter->e)] != -1.0f); + cost_test += edge_lengths[BM_elem_index_get(l_iter->e)] * angle; +#endif + cost_count += 1; + } + } while ((l_radial_iter = l_radial_iter->radial_next) != l_iter); + + if (cost_count >= 2) { + cost += cost_test; + found = true; + } + } + } while ((l_iter = l_iter->next) != l_first); + } + return found ? cost / area : FLT_MAX; +} + +bool EDBM_select_interior_faces(BMEditMesh *em) +{ + BMesh *bm = em->bm; + BMIter iter; + bool changed = false; + + float *edge_lengths = static_cast<float *>( + MEM_mallocN(sizeof(*edge_lengths) * bm->totedge, __func__)); + + { + bool has_nonmanifold = false; + BMEdge *e; + int i; + BM_ITER_MESH_INDEX (e, &iter, bm, BM_EDGES_OF_MESH, i) { + const bool is_over = BM_edge_face_count_is_over(e, 2); + if (is_over) { + BM_elem_flag_enable(e, BM_ELEM_TAG); + has_nonmanifold = true; + edge_lengths[i] = BM_edge_calc_length(e); + } + else { + BM_elem_flag_disable(e, BM_ELEM_TAG); + edge_lengths[i] = -1.0; + } + + BM_elem_index_set(e, i); /* set_inline */ + } + bm->elem_index_dirty &= ~BM_EDGE; + + if (has_nonmanifold == false) { + MEM_freeN(edge_lengths); + return false; + } + } + + /* group vars */ + int(*fgroup_index)[2]; + int fgroup_len; + + int *fgroup_array = static_cast<int *>( + MEM_mallocN(sizeof(*fgroup_array) * bm->totface, __func__)); + fgroup_len = BM_mesh_calc_face_groups( + bm, fgroup_array, &fgroup_index, bm_interior_loop_filter_fn, nullptr, nullptr, 0, BM_EDGE); + + int *fgroup_recalc_stack = static_cast<int *>( + MEM_mallocN(sizeof(*fgroup_recalc_stack) * fgroup_len, __func__)); + STACK_DECLARE(fgroup_recalc_stack); + STACK_INIT(fgroup_recalc_stack, fgroup_len); + + BM_mesh_elem_table_ensure(bm, BM_FACE); + + { + BMFace *f; + BM_ITER_MESH (f, &iter, em->bm, BM_FACES_OF_MESH) { + BM_elem_index_set(f, -1); /* set_dirty! */ + } + } + bm->elem_index_dirty |= BM_FACE; + + ListBase *fgroup_listbase = static_cast<ListBase *>( + MEM_callocN(sizeof(*fgroup_listbase) * fgroup_len, __func__)); + BMFaceLink *f_link_array = static_cast<BMFaceLink *>( + MEM_callocN(sizeof(*f_link_array) * bm->totface, __func__)); + + for (int i = 0; i < fgroup_len; i++) { + const int fg_sta = fgroup_index[i][0]; + const int fg_len = fgroup_index[i][1]; + for (int j = 0; j < fg_len; j++) { + const int face_index = fgroup_array[fg_sta + j]; + BMFace *f = BM_face_at_index(bm, face_index); + BM_elem_index_set(f, i); + + BMFaceLink *f_link = &f_link_array[face_index]; + f_link->face = f; + f_link->area = BM_face_calc_area(f); + BLI_addtail(&fgroup_listbase[i], f_link); + } + } + + MEM_freeN(fgroup_array); + MEM_freeN(fgroup_index); + + Heap *fgroup_heap = BLI_heap_new_ex(fgroup_len); + HeapNode **fgroup_table = static_cast<HeapNode **>( + MEM_mallocN(sizeof(*fgroup_table) * fgroup_len, __func__)); + bool *fgroup_dirty = static_cast<bool *>( + MEM_callocN(sizeof(*fgroup_dirty) * fgroup_len, __func__)); + + for (int i = 0; i < fgroup_len; i++) { + const float cost = bm_interior_face_group_calc_cost(&fgroup_listbase[i], edge_lengths); + if (cost != FLT_MAX) { + fgroup_table[i] = BLI_heap_insert(fgroup_heap, -cost, POINTER_FROM_INT(i)); + } + else { + fgroup_table[i] = nullptr; + } + } + + /* Avoid re-running cost calculations for large face-groups which will end up forming the + * outer shell and not be considered interior. + * As these face groups become increasingly bigger - their chance of being considered + * interior reduces as does the time to calculate their cost. + * + * This delays recalculating them until they are considered can dates to remove + * which becomes less and less likely as they increase in area. */ + +#define USE_DELAY_FACE_GROUP_COST_CALC + + while (true) { + +#if defined(USE_DELAY_FACE_GROUP_COST_CALC) + while (!BLI_heap_is_empty(fgroup_heap)) { + HeapNode *node_min = BLI_heap_top(fgroup_heap); + const int i = POINTER_AS_INT(BLI_heap_node_ptr(node_min)); + if (fgroup_dirty[i]) { + const float cost = bm_interior_face_group_calc_cost(&fgroup_listbase[i], edge_lengths); + if (cost != FLT_MAX) { + /* The cost may have improves (we may be able to skip this), + * however the cost should _never_ make this a choice. */ + BLI_assert(-BLI_heap_node_value(node_min) >= cost); + BLI_heap_node_value_update(fgroup_heap, fgroup_table[i], -cost); + } + else { + BLI_heap_remove(fgroup_heap, fgroup_table[i]); + fgroup_table[i] = nullptr; + } + fgroup_dirty[i] = false; + } + else { + break; + } + } +#endif + + if (BLI_heap_is_empty(fgroup_heap)) { + break; + } + + const int i_min = POINTER_AS_INT(BLI_heap_pop_min(fgroup_heap)); + BLI_assert(fgroup_table[i_min] != nullptr); + BLI_assert(fgroup_dirty[i_min] == false); + fgroup_table[i_min] = nullptr; + changed = true; + + BMFaceLink *f_link; + while ((f_link = static_cast<BMFaceLink *>(BLI_pophead(&fgroup_listbase[i_min])))) { + BMFace *f = f_link->face; + BM_face_select_set(bm, f, true); + BM_elem_index_set(f, -1); /* set-dirty */ + + BMLoop *l_iter, *l_first; + + /* Loop over edges face edges, merging groups which are no longer separated + * by non-manifold edges (when manifold check ignores faces from this group). */ + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + BMLoop *l_pair[2]; + if (bm_interior_edge_is_manifold_except_face_index(l_iter->e, i_min, l_pair)) { + BM_elem_flag_disable(l_iter->e, BM_ELEM_TAG); + + int i_a = BM_elem_index_get(l_pair[0]->f); + int i_b = BM_elem_index_get(l_pair[1]->f); + if (i_a != i_b) { + /* Only for predictable results that don't depend on the order of radial loops, + * not essential. */ + if (i_a > i_b) { + SWAP(int, i_a, i_b); + } + + /* Merge the groups. */ + LISTBASE_FOREACH (LinkData *, n, &fgroup_listbase[i_b]) { + BMFace *f_iter = static_cast<BMFace *>(n->data); + BM_elem_index_set(f_iter, i_a); + } + BLI_movelisttolist(&fgroup_listbase[i_a], &fgroup_listbase[i_b]); + + /* This may have been added to 'fgroup_recalc_stack', instead of removing it, + * just check the heap node isn't nullptr before recalculating. */ + BLI_heap_remove(fgroup_heap, fgroup_table[i_b]); + fgroup_table[i_b] = nullptr; + /* Keep the dirty flag as-is for 'i_b', because it may be in the 'fgroup_recalc_stack' + * and we don't want to add it again. + * Instead rely on the 'fgroup_table[i_b]' being nullptr as a secondary check. */ + + if (fgroup_dirty[i_a] == false) { + BLI_assert(fgroup_table[i_a] != nullptr); + STACK_PUSH(fgroup_recalc_stack, i_a); + fgroup_dirty[i_a] = true; + } + } + } + + /* Mark all connected groups for re-calculation. */ + BMLoop *l_radial_iter = l_iter->radial_next; + if (l_radial_iter != l_iter) { + do { + int i_other = BM_elem_index_get(l_radial_iter->f); + if (!ELEM(i_other, -1, i_min)) { + if ((fgroup_table[i_other] != nullptr) && (fgroup_dirty[i_other] == false)) { +#if !defined(USE_DELAY_FACE_GROUP_COST_CALC) + STACK_PUSH(fgroup_recalc_stack, i_other); +#endif + fgroup_dirty[i_other] = true; + } + } + } while ((l_radial_iter = l_radial_iter->radial_next) != l_iter); + } + + } while ((l_iter = l_iter->next) != l_first); + } + + for (int index = 0; index < STACK_SIZE(fgroup_recalc_stack); index++) { + const int i = fgroup_recalc_stack[index]; + if (fgroup_table[i] != nullptr && fgroup_dirty[i] == true) { + /* First update edge tags. */ + const float cost = bm_interior_face_group_calc_cost(&fgroup_listbase[i], edge_lengths); + if (cost != FLT_MAX) { + BLI_heap_node_value_update(fgroup_heap, fgroup_table[i], -cost); + } + else { + BLI_heap_remove(fgroup_heap, fgroup_table[i]); + fgroup_table[i] = nullptr; + } + } + fgroup_dirty[i] = false; + } + STACK_CLEAR(fgroup_recalc_stack); + } + + MEM_freeN(edge_lengths); + MEM_freeN(f_link_array); + MEM_freeN(fgroup_listbase); + MEM_freeN(fgroup_recalc_stack); + MEM_freeN(fgroup_table); + MEM_freeN(fgroup_dirty); + + BLI_heap_free(fgroup_heap, nullptr); + + return changed; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Linked Operator + * + * Support delimiting on different edge properties. + * \{ */ + +/* so we can have last-used default depend on selection mode (rare exception!) */ +#define USE_LINKED_SELECT_DEFAULT_HACK + +struct DelimitData { + int cd_loop_type; + int cd_loop_offset; +}; + +static bool select_linked_delimit_test(BMEdge *e, int delimit, const DelimitData *delimit_data) +{ + BLI_assert(delimit); + + if (delimit & BMO_DELIM_SEAM) { + if (BM_elem_flag_test(e, BM_ELEM_SEAM)) { + return true; + } + } + + if (delimit & BMO_DELIM_SHARP) { + if (BM_elem_flag_test(e, BM_ELEM_SMOOTH) == 0) { + return true; + } + } + + if (delimit & BMO_DELIM_NORMAL) { + if (!BM_edge_is_contiguous(e)) { + return true; + } + } + + if (delimit & BMO_DELIM_MATERIAL) { + if (e->l && e->l->radial_next != e->l) { + const short mat_nr = e->l->f->mat_nr; + BMLoop *l_iter = e->l->radial_next; + do { + if (l_iter->f->mat_nr != mat_nr) { + return true; + } + } while ((l_iter = l_iter->radial_next) != e->l); + } + } + + if (delimit & BMO_DELIM_UV) { + if (BM_edge_is_contiguous_loop_cd( + e, delimit_data->cd_loop_type, delimit_data->cd_loop_offset) == 0) { + return true; + } + } + + return false; +} + +#ifdef USE_LINKED_SELECT_DEFAULT_HACK +/** + * Gets the default from the operator fallback to own last-used value + * (selected based on mode) + */ +static int select_linked_delimit_default_from_op(wmOperator *op, const int select_mode) +{ + static char delimit_last_store[2] = {0, BMO_DELIM_SEAM}; + int delimit_last_index = (select_mode & (SCE_SELECT_VERTEX | SCE_SELECT_EDGE)) == 0; + char *delimit_last = &delimit_last_store[delimit_last_index]; + PropertyRNA *prop_delimit = RNA_struct_find_property(op->ptr, "delimit"); + int delimit; + + if (RNA_property_is_set(op->ptr, prop_delimit)) { + delimit = RNA_property_enum_get(op->ptr, prop_delimit); + *delimit_last = delimit; + } + else { + delimit = *delimit_last; + RNA_property_enum_set(op->ptr, prop_delimit, delimit); + } + return delimit; +} +#endif + +static void select_linked_delimit_validate(BMesh *bm, int *delimit) +{ + if ((*delimit) & BMO_DELIM_UV) { + if (!CustomData_has_layer(&bm->ldata, CD_MLOOPUV)) { + (*delimit) &= ~BMO_DELIM_UV; + } + } +} + +static void select_linked_delimit_begin(BMesh *bm, int delimit) +{ + DelimitData delimit_data = {0}; + + if (delimit & BMO_DELIM_UV) { + delimit_data.cd_loop_type = CD_MLOOPUV; + delimit_data.cd_loop_offset = CustomData_get_offset(&bm->ldata, delimit_data.cd_loop_type); + if (delimit_data.cd_loop_offset == -1) { + delimit &= ~BMO_DELIM_UV; + } + } + + /* grr, shouldn't need to alloc BMO flags here */ + BM_mesh_elem_toolflags_ensure(bm); + + { + BMIter iter; + BMEdge *e; + + BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) { + const bool is_walk_ok = (select_linked_delimit_test(e, delimit, &delimit_data) == false); + + BMO_edge_flag_set(bm, e, BMO_ELE_TAG, is_walk_ok); + } + } +} + +static void select_linked_delimit_end(BMEditMesh *em) +{ + BMesh *bm = em->bm; + + BM_mesh_elem_toolflags_clear(bm); +} + +static int edbm_select_linked_exec(bContext *C, wmOperator *op) +{ + Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + +#ifdef USE_LINKED_SELECT_DEFAULT_HACK + const int delimit_init = select_linked_delimit_default_from_op(op, + scene->toolsettings->selectmode); +#else + const int delimit_init = RNA_enum_get(op->ptr, "delimit"); +#endif + + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( + scene, view_layer, CTX_wm_view3d(C), &objects_len); + + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *obedit = objects[ob_index]; + + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + BMIter iter; + BMWalker walker; + + int delimit = delimit_init; + + select_linked_delimit_validate(bm, &delimit); + + if (delimit) { + select_linked_delimit_begin(em->bm, delimit); + } + + if (em->selectmode & SCE_SELECT_VERTEX) { + BMVert *v; + + BM_ITER_MESH (v, &iter, em->bm, BM_VERTS_OF_MESH) { + BM_elem_flag_set(v, BM_ELEM_TAG, BM_elem_flag_test(v, BM_ELEM_SELECT)); + } + + /* exclude all delimited verts */ + if (delimit) { + BMEdge *e; + BM_ITER_MESH (e, &iter, em->bm, BM_EDGES_OF_MESH) { + if (!BMO_edge_flag_test(bm, e, BMO_ELE_TAG)) { + /* Check the edge for selected faces, + * this supports stepping off isolated vertices which would otherwise be ignored. */ + if (BM_edge_is_any_face_flag_test(e, BM_ELEM_SELECT)) { + BM_elem_flag_disable(e->v1, BM_ELEM_TAG); + BM_elem_flag_disable(e->v2, BM_ELEM_TAG); + } + } + } + } + + BMW_init(&walker, + em->bm, + delimit ? BMW_LOOP_SHELL_WIRE : BMW_VERT_SHELL, + BMW_MASK_NOP, + delimit ? BMO_ELE_TAG : BMW_MASK_NOP, + BMW_MASK_NOP, + BMW_FLAG_TEST_HIDDEN, + BMW_NIL_LAY); + + if (delimit) { + BM_ITER_MESH (v, &iter, em->bm, BM_VERTS_OF_MESH) { + if (BM_elem_flag_test(v, BM_ELEM_TAG)) { + BMElem *ele_walk; + BMW_ITER (ele_walk, &walker, v) { + if (ele_walk->head.htype == BM_LOOP) { + BMVert *v_step = ((BMLoop *)ele_walk)->v; + BM_vert_select_set(em->bm, v_step, true); + BM_elem_flag_disable(v_step, BM_ELEM_TAG); + } + else { + BMEdge *e_step = (BMEdge *)ele_walk; + BLI_assert(ele_walk->head.htype == BM_EDGE); + BM_edge_select_set(em->bm, e_step, true); + BM_elem_flag_disable(e_step->v1, BM_ELEM_TAG); + BM_elem_flag_disable(e_step->v2, BM_ELEM_TAG); + } + } + } + } + } + else { + BM_ITER_MESH (v, &iter, em->bm, BM_VERTS_OF_MESH) { + if (BM_elem_flag_test(v, BM_ELEM_TAG)) { + BMEdge *e_walk; + BMW_ITER (e_walk, &walker, v) { + BM_edge_select_set(em->bm, e_walk, true); + BM_elem_flag_disable(e_walk, BM_ELEM_TAG); + } + } + } + } + + BMW_end(&walker); + + EDBM_selectmode_flush(em); + } + else if (em->selectmode & SCE_SELECT_EDGE) { + BMEdge *e; + + if (delimit) { + BM_ITER_MESH (e, &iter, em->bm, BM_EDGES_OF_MESH) { + /* Check the edge for selected faces, + * this supports stepping off isolated edges which would otherwise be ignored. */ + BM_elem_flag_set(e, + BM_ELEM_TAG, + (BM_elem_flag_test(e, BM_ELEM_SELECT) && + (BMO_edge_flag_test(bm, e, BMO_ELE_TAG) || + !BM_edge_is_any_face_flag_test(e, BM_ELEM_SELECT)))); + } + } + else { + BM_ITER_MESH (e, &iter, em->bm, BM_EDGES_OF_MESH) { + BM_elem_flag_set(e, BM_ELEM_TAG, BM_elem_flag_test(e, BM_ELEM_SELECT)); + } + } + + BMW_init(&walker, + em->bm, + delimit ? BMW_LOOP_SHELL_WIRE : BMW_VERT_SHELL, + BMW_MASK_NOP, + delimit ? BMO_ELE_TAG : BMW_MASK_NOP, + BMW_MASK_NOP, + BMW_FLAG_TEST_HIDDEN, + BMW_NIL_LAY); + + if (delimit) { + BM_ITER_MESH (e, &iter, em->bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(e, BM_ELEM_TAG)) { + BMElem *ele_walk; + BMW_ITER (ele_walk, &walker, e) { + if (ele_walk->head.htype == BM_LOOP) { + BMLoop *l_step = (BMLoop *)ele_walk; + BM_edge_select_set(em->bm, l_step->e, true); + BM_edge_select_set(em->bm, l_step->prev->e, true); + BM_elem_flag_disable(l_step->e, BM_ELEM_TAG); + } + else { + BMEdge *e_step = (BMEdge *)ele_walk; + BLI_assert(ele_walk->head.htype == BM_EDGE); + BM_edge_select_set(em->bm, e_step, true); + BM_elem_flag_disable(e_step, BM_ELEM_TAG); + } + } + } + } + } + else { + BM_ITER_MESH (e, &iter, em->bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(e, BM_ELEM_TAG)) { + BMEdge *e_walk; + BMW_ITER (e_walk, &walker, e) { + BM_edge_select_set(em->bm, e_walk, true); + BM_elem_flag_disable(e_walk, BM_ELEM_TAG); + } + } + } + } + + BMW_end(&walker); + + EDBM_selectmode_flush(em); + } + else { + BMFace *f; + + BM_ITER_MESH (f, &iter, em->bm, BM_FACES_OF_MESH) { + BM_elem_flag_set(f, BM_ELEM_TAG, BM_elem_flag_test(f, BM_ELEM_SELECT)); + } + + BMW_init(&walker, + bm, + BMW_ISLAND, + BMW_MASK_NOP, + delimit ? BMO_ELE_TAG : BMW_MASK_NOP, + BMW_MASK_NOP, + BMW_FLAG_TEST_HIDDEN, + BMW_NIL_LAY); + + BM_ITER_MESH (f, &iter, em->bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_TAG)) { + BMFace *f_walk; + BMW_ITER (f_walk, &walker, f) { + BM_face_select_set(bm, f_walk, true); + BM_elem_flag_disable(f_walk, BM_ELEM_TAG); + } + } + } + + BMW_end(&walker); + } + + if (delimit) { + select_linked_delimit_end(em); + } + + DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + } + + MEM_freeN(objects); + + return OPERATOR_FINISHED; +} + +void MESH_OT_select_linked(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Select Linked All"; + ot->idname = "MESH_OT_select_linked"; + ot->description = "Select all vertices connected to the current selection"; + + /* api callbacks */ + ot->exec = edbm_select_linked_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + prop = RNA_def_enum_flag(ot->srna, + "delimit", + rna_enum_mesh_delimit_mode_items, + BMO_DELIM_SEAM, + "Delimit", + "Delimit selected region"); +#ifdef USE_LINKED_SELECT_DEFAULT_HACK + RNA_def_property_flag(prop, PROP_SKIP_SAVE); +#else + UNUSED_VARS(prop); +#endif +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Linked (Cursor Pick) Operator + * \{ */ + +static int edbm_select_linked_pick_exec(bContext *C, wmOperator *op); + +static void edbm_select_linked_pick_ex(BMEditMesh *em, BMElem *ele, bool sel, int delimit) +{ + BMesh *bm = em->bm; + BMWalker walker; + + select_linked_delimit_validate(bm, &delimit); + + if (delimit) { + select_linked_delimit_begin(bm, delimit); + } + + /* NOTE: logic closely matches #edbm_select_linked_exec, keep in sync. */ + + if (ele->head.htype == BM_VERT) { + BMVert *eve = (BMVert *)ele; + + BMW_init(&walker, + bm, + delimit ? BMW_LOOP_SHELL_WIRE : BMW_VERT_SHELL, + BMW_MASK_NOP, + delimit ? BMO_ELE_TAG : BMW_MASK_NOP, + BMW_MASK_NOP, + BMW_FLAG_TEST_HIDDEN, + BMW_NIL_LAY); + + if (delimit) { + BMElem *ele_walk; + BMW_ITER (ele_walk, &walker, eve) { + if (ele_walk->head.htype == BM_LOOP) { + BMVert *v_step = ((BMLoop *)ele_walk)->v; + BM_vert_select_set(bm, v_step, sel); + } + else { + BMEdge *e_step = (BMEdge *)ele_walk; + BLI_assert(ele_walk->head.htype == BM_EDGE); + BM_edge_select_set(bm, e_step, sel); + } + } + } + else { + BMEdge *e_walk; + BMW_ITER (e_walk, &walker, eve) { + BM_edge_select_set(bm, e_walk, sel); + } + } + + BMW_end(&walker); + + EDBM_selectmode_flush(em); + } + else if (ele->head.htype == BM_EDGE) { + BMEdge *eed = (BMEdge *)ele; + + BMW_init(&walker, + bm, + delimit ? BMW_LOOP_SHELL_WIRE : BMW_VERT_SHELL, + BMW_MASK_NOP, + delimit ? BMO_ELE_TAG : BMW_MASK_NOP, + BMW_MASK_NOP, + BMW_FLAG_TEST_HIDDEN, + BMW_NIL_LAY); + + if (delimit) { + BMElem *ele_walk; + BMW_ITER (ele_walk, &walker, eed) { + if (ele_walk->head.htype == BM_LOOP) { + BMEdge *e_step = ((BMLoop *)ele_walk)->e; + BM_edge_select_set(bm, e_step, sel); + } + else { + BMEdge *e_step = (BMEdge *)ele_walk; + BLI_assert(ele_walk->head.htype == BM_EDGE); + BM_edge_select_set(bm, e_step, sel); + } + } + } + else { + BMEdge *e_walk; + BMW_ITER (e_walk, &walker, eed) { + BM_edge_select_set(bm, e_walk, sel); + } + } + + BMW_end(&walker); + + EDBM_selectmode_flush(em); + } + else if (ele->head.htype == BM_FACE) { + BMFace *efa = (BMFace *)ele; + + BMW_init(&walker, + bm, + BMW_ISLAND, + BMW_MASK_NOP, + delimit ? BMO_ELE_TAG : BMW_MASK_NOP, + BMW_MASK_NOP, + BMW_FLAG_TEST_HIDDEN, + BMW_NIL_LAY); + + { + BMFace *f_walk; + BMW_ITER (f_walk, &walker, efa) { + BM_face_select_set(bm, f_walk, sel); + BM_elem_flag_disable(f_walk, BM_ELEM_TAG); + } + } + + BMW_end(&walker); + } + + if (delimit) { + select_linked_delimit_end(em); + } +} + +static int edbm_select_linked_pick_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + ViewContext vc; + Base *basact = nullptr; + BMVert *eve; + BMEdge *eed; + BMFace *efa; + const bool sel = !RNA_boolean_get(op->ptr, "deselect"); + int index; + + if (RNA_struct_property_is_set(op->ptr, "index")) { + return edbm_select_linked_pick_exec(C, op); + } + + /* #unified_findnearest needs OpenGL. */ + view3d_operator_needs_opengl(C); + + /* setup view context for argument to callbacks */ + em_setup_viewcontext(C, &vc); + + uint bases_len; + Base **bases = BKE_view_layer_array_from_bases_in_edit_mode( + vc.scene, vc.view_layer, vc.v3d, &bases_len); + + { + bool has_edges = false; + for (uint base_index = 0; base_index < bases_len; base_index++) { + Object *ob_iter = bases[base_index]->object; + ED_view3d_viewcontext_init_object(&vc, ob_iter); + if (vc.em->bm->totedge) { + has_edges = true; + } + } + if (has_edges == false) { + MEM_freeN(bases); + return OPERATOR_CANCELLED; + } + } + + vc.mval[0] = event->mval[0]; + vc.mval[1] = event->mval[1]; + + /* return warning! */ + { + int base_index = -1; + const bool ok = unified_findnearest(&vc, bases, bases_len, &base_index, &eve, &eed, &efa); + if (!ok) { + MEM_freeN(bases); + return OPERATOR_CANCELLED; + } + basact = bases[base_index]; + } + + ED_view3d_viewcontext_init_object(&vc, basact->object); + BMEditMesh *em = vc.em; + BMesh *bm = em->bm; + +#ifdef USE_LINKED_SELECT_DEFAULT_HACK + int delimit = select_linked_delimit_default_from_op(op, vc.scene->toolsettings->selectmode); +#else + int delimit = RNA_enum_get(op->ptr, "delimit"); +#endif + + BMElem *ele = EDBM_elem_from_selectmode(em, eve, eed, efa); + + edbm_select_linked_pick_ex(em, ele, sel, delimit); + + /* To support redo. */ + { + /* Note that the `base_index` can't be used as the index depends on the 3D Viewport + * which might not be available on redo. */ + BM_mesh_elem_index_ensure(bm, ele->head.htype); + int object_index; + index = EDBM_elem_to_index_any_multi(vc.scene, vc.view_layer, em, ele, &object_index); + BLI_assert(object_index >= 0); + RNA_int_set(op->ptr, "object_index", object_index); + RNA_int_set(op->ptr, "index", index); + } + + DEG_id_tag_update(static_cast<ID *>(basact->object->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, basact->object->data); + + MEM_freeN(bases); + return OPERATOR_FINISHED; +} + +static int edbm_select_linked_pick_exec(bContext *C, wmOperator *op) +{ + Object *obedit = nullptr; + BMElem *ele; + + { + const Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + /* Intentionally wrap negative values so the lookup fails. */ + const uint object_index = (uint)RNA_int_get(op->ptr, "object_index"); + const uint index = (uint)RNA_int_get(op->ptr, "index"); + ele = EDBM_elem_from_index_any_multi(scene, view_layer, object_index, index, &obedit); + } + + if (ele == nullptr) { + return OPERATOR_CANCELLED; + } + + BMEditMesh *em = BKE_editmesh_from_object(obedit); + const bool sel = !RNA_boolean_get(op->ptr, "deselect"); + +#ifdef USE_LINKED_SELECT_DEFAULT_HACK + int delimit = select_linked_delimit_default_from_op(op, em->selectmode); +#else + int delimit = RNA_enum_get(op->ptr, "delimit"); +#endif + + edbm_select_linked_pick_ex(em, ele, sel, delimit); + + DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + + return OPERATOR_FINISHED; +} + +void MESH_OT_select_linked_pick(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Select Linked"; + ot->idname = "MESH_OT_select_linked_pick"; + ot->description = "(De)select all vertices linked to the edge under the mouse cursor"; + + /* api callbacks */ + ot->invoke = edbm_select_linked_pick_invoke; + ot->exec = edbm_select_linked_pick_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_boolean(ot->srna, "deselect", 0, "Deselect", ""); + prop = RNA_def_enum_flag(ot->srna, + "delimit", + rna_enum_mesh_delimit_mode_items, + BMO_DELIM_SEAM, + "Delimit", + "Delimit selected region"); +#ifdef USE_LINKED_SELECT_DEFAULT_HACK + RNA_def_property_flag(prop, PROP_SKIP_SAVE); +#endif + + /* use for redo */ + prop = RNA_def_int(ot->srna, "object_index", -1, -1, INT_MAX, "", "", 0, INT_MAX); + RNA_def_property_flag(prop, PropertyFlag(PROP_HIDDEN | PROP_SKIP_SAVE)); + prop = RNA_def_int(ot->srna, "index", -1, -1, INT_MAX, "", "", 0, INT_MAX); + RNA_def_property_flag(prop, PropertyFlag(PROP_HIDDEN | PROP_SKIP_SAVE)); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Face by Sides Operator + * \{ */ + +static int edbm_select_face_by_sides_exec(bContext *C, wmOperator *op) +{ + const Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + uint objects_len = 0; + const bool extend = RNA_boolean_get(op->ptr, "extend"); + const int numverts = RNA_int_get(op->ptr, "number"); + const int type = RNA_enum_get(op->ptr, "type"); + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( + scene, view_layer, CTX_wm_view3d(C), &objects_len); + + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *obedit = objects[ob_index]; + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMFace *efa; + BMIter iter; + + if (!extend) { + EDBM_flag_disable_all(em, BM_ELEM_SELECT); + } + + BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { + bool select; + + switch (type) { + case 0: + select = (efa->len < numverts); + break; + case 1: + select = (efa->len == numverts); + break; + case 2: + select = (efa->len > numverts); + break; + case 3: + select = (efa->len != numverts); + break; + default: + BLI_assert(0); + select = false; + break; + } + + if (select) { + BM_face_select_set(em->bm, efa, true); + } + } + + EDBM_selectmode_flush(em); + + DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + } + + MEM_freeN(objects); + return OPERATOR_FINISHED; +} + +void MESH_OT_select_face_by_sides(wmOperatorType *ot) +{ + static const EnumPropertyItem type_items[] = { + {0, "LESS", 0, "Less Than", ""}, + {1, "EQUAL", 0, "Equal To", ""}, + {2, "GREATER", 0, "Greater Than", ""}, + {3, "NOTEQUAL", 0, "Not Equal To", ""}, + {0, nullptr, 0, nullptr, nullptr}, + }; + + /* identifiers */ + ot->name = "Select Faces by Sides"; + ot->description = "Select vertices or faces by the number of polygon sides"; + ot->idname = "MESH_OT_select_face_by_sides"; + + /* api callbacks */ + ot->exec = edbm_select_face_by_sides_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + RNA_def_int(ot->srna, "number", 4, 3, INT_MAX, "Number of Vertices", "", 3, INT_MAX); + RNA_def_enum(ot->srna, "type", type_items, 1, "Type", "Type of comparison to make"); + RNA_def_boolean(ot->srna, "extend", true, "Extend", "Extend the selection"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Loose Operator + * \{ */ + +static int edbm_select_loose_exec(bContext *C, wmOperator *op) +{ + const Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + const bool extend = RNA_boolean_get(op->ptr, "extend"); + + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( + scene, view_layer, CTX_wm_view3d(C), &objects_len); + + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *obedit = objects[ob_index]; + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + BMIter iter; + + if (!extend) { + EDBM_flag_disable_all(em, BM_ELEM_SELECT); + } + + if (em->selectmode & SCE_SELECT_VERTEX) { + BMVert *eve; + BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) { + if (!eve->e) { + BM_vert_select_set(bm, eve, true); + } + } + } + + if (em->selectmode & SCE_SELECT_EDGE) { + BMEdge *eed; + BM_ITER_MESH (eed, &iter, bm, BM_EDGES_OF_MESH) { + if (BM_edge_is_wire(eed)) { + BM_edge_select_set(bm, eed, true); + } + } + } + + if (em->selectmode & SCE_SELECT_FACE) { + BMFace *efa; + BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { + BMIter liter; + BMLoop *l; + bool is_loose = true; + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + if (!BM_edge_is_boundary(l->e)) { + is_loose = false; + break; + } + } + if (is_loose) { + BM_face_select_set(bm, efa, true); + } + } + } + + EDBM_selectmode_flush(em); + + DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + } + + MEM_freeN(objects); + + return OPERATOR_FINISHED; +} + +void MESH_OT_select_loose(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Select Loose Geometry"; + ot->description = "Select loose geometry based on the selection mode"; + ot->idname = "MESH_OT_select_loose"; + + /* api callbacks */ + ot->exec = edbm_select_loose_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* props */ + RNA_def_boolean(ot->srna, "extend", false, "Extend", "Extend the selection"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Mirror Operator + * \{ */ + +static int edbm_select_mirror_exec(bContext *C, wmOperator *op) +{ + const Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + const int axis_flag = RNA_enum_get(op->ptr, "axis"); + const bool extend = RNA_boolean_get(op->ptr, "extend"); + Object *obedit_active = CTX_data_edit_object(C); + BMEditMesh *em_active = BKE_editmesh_from_object(obedit_active); + const int select_mode = em_active->bm->selectmode; + int tot_mirr = 0, tot_fail = 0; + + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( + scene, view_layer, CTX_wm_view3d(C), &objects_len); + + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *obedit = objects[ob_index]; + BMEditMesh *em = BKE_editmesh_from_object(obedit); + + if (em->bm->totvertsel == 0) { + continue; + } + + int tot_mirr_iter = 0, tot_fail_iter = 0; + + for (int axis = 0; axis < 3; axis++) { + if ((1 << axis) & axis_flag) { + EDBM_select_mirrored(em, + static_cast<const Mesh *>(obedit->data), + axis, + extend, + &tot_mirr_iter, + &tot_fail_iter); + } + } + + if (tot_mirr_iter) { + EDBM_selectmode_flush(em); + + DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + } + + tot_fail += tot_fail_iter; + tot_mirr += tot_mirr_iter; + } + MEM_freeN(objects); + + if (tot_mirr || tot_fail) { + ED_mesh_report_mirror_ex(op, tot_mirr, tot_fail, select_mode); + } + return OPERATOR_FINISHED; +} + +void MESH_OT_select_mirror(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Select Mirror"; + ot->description = "Select mesh items at mirrored locations"; + ot->idname = "MESH_OT_select_mirror"; + + /* api callbacks */ + ot->exec = edbm_select_mirror_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* props */ + RNA_def_enum_flag(ot->srna, "axis", rna_enum_axis_flag_xyz_items, (1 << 0), "Axis", ""); + + RNA_def_boolean(ot->srna, "extend", 0, "Extend", "Extend the existing selection"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select More Operator + * \{ */ + +static int edbm_select_more_exec(bContext *C, wmOperator *op) +{ + const Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + const bool use_face_step = RNA_boolean_get(op->ptr, "use_face_step"); + + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( + scene, view_layer, CTX_wm_view3d(C), &objects_len); + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *obedit = objects[ob_index]; + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + + if ((bm->totvertsel == 0) && (bm->totedgesel == 0) && (bm->totfacesel == 0)) { + continue; + } + + EDBM_select_more(em, use_face_step); + DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + } + + MEM_freeN(objects); + return OPERATOR_FINISHED; +} + +void MESH_OT_select_more(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Select More"; + ot->idname = "MESH_OT_select_more"; + ot->description = "Select more vertices, edges or faces connected to initial selection"; + + /* api callbacks */ + ot->exec = edbm_select_more_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_boolean( + ot->srna, "use_face_step", true, "Face Step", "Connected faces (instead of edges)"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select More Operator + * \{ */ + +static int edbm_select_less_exec(bContext *C, wmOperator *op) +{ + const Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + const bool use_face_step = RNA_boolean_get(op->ptr, "use_face_step"); + + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( + scene, view_layer, CTX_wm_view3d(C), &objects_len); + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *obedit = objects[ob_index]; + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + + if ((bm->totvertsel == 0) && (bm->totedgesel == 0) && (bm->totfacesel == 0)) { + continue; + } + + EDBM_select_less(em, use_face_step); + DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + } + + MEM_freeN(objects); + return OPERATOR_FINISHED; +} + +void MESH_OT_select_less(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Select Less"; + ot->idname = "MESH_OT_select_less"; + ot->description = "Deselect vertices, edges or faces at the boundary of each selection region"; + + /* api callbacks */ + ot->exec = edbm_select_less_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_boolean( + ot->srna, "use_face_step", true, "Face Step", "Connected faces (instead of edges)"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select N'th Operator + * \{ */ + +/** + * Check if we're connected to another selected edge. + */ +static bool bm_edge_is_select_isolated(BMEdge *e) +{ + BMIter viter; + BMVert *v; + + BM_ITER_ELEM (v, &viter, e, BM_VERTS_OF_EDGE) { + BMIter eiter; + BMEdge *e_other; + + BM_ITER_ELEM (e_other, &eiter, v, BM_EDGES_OF_VERT) { + if ((e_other != e) && BM_elem_flag_test(e_other, BM_ELEM_SELECT)) { + return false; + } + } + } + return true; +} + +/* Walk all reachable elements of the same type as h_act in breadth-first + * order, starting from h_act. Deselects elements if the depth when they + * are reached is not a multiple of "nth". */ +static void walker_deselect_nth(BMEditMesh *em, + const CheckerIntervalParams *op_params, + BMHeader *h_act) +{ + BMElem *ele; + BMesh *bm = em->bm; + BMWalker walker; + BMIter iter; + int walktype = 0, itertype = 0, flushtype = 0; + short mask_vert = 0, mask_edge = 0, mask_face = 0; + + /* No active element from which to start - nothing to do */ + if (h_act == nullptr) { + return; + } + + /* Determine which type of iter, walker, and select flush to use + * based on type of the elements being deselected */ + switch (h_act->htype) { + case BM_VERT: + itertype = BM_VERTS_OF_MESH; + walktype = BMW_CONNECTED_VERTEX; + flushtype = SCE_SELECT_VERTEX; + mask_vert = BMO_ELE_TAG; + break; + case BM_EDGE: + /* When an edge has no connected-selected edges, + * use face-stepping (supports edge-rings) */ + itertype = BM_EDGES_OF_MESH; + walktype = bm_edge_is_select_isolated((BMEdge *)h_act) ? BMW_FACE_SHELL : BMW_VERT_SHELL; + flushtype = SCE_SELECT_EDGE; + mask_edge = BMO_ELE_TAG; + break; + case BM_FACE: + itertype = BM_FACES_OF_MESH; + walktype = BMW_ISLAND; + flushtype = SCE_SELECT_FACE; + mask_face = BMO_ELE_TAG; + break; + } + + /* grr, shouldn't need to alloc BMO flags here */ + BM_mesh_elem_toolflags_ensure(bm); + + /* Walker restrictions uses BMO flags, not header flags, + * so transfer BM_ELEM_SELECT from HFlags onto a BMO flag layer. */ + BMO_push(bm, nullptr); + BM_ITER_MESH (ele, &iter, bm, itertype) { + if (BM_elem_flag_test(ele, BM_ELEM_SELECT)) { + BMO_elem_flag_enable(bm, (BMElemF *)ele, BMO_ELE_TAG); + } + } + + /* Walk over selected elements starting at active */ + BMW_init(&walker, + bm, + walktype, + mask_vert, + mask_edge, + mask_face, + BMW_FLAG_NOP, /* don't use BMW_FLAG_TEST_HIDDEN here since we want to desel all */ + BMW_NIL_LAY); + + /* use tag to avoid touching the same verts twice */ + BM_ITER_MESH (ele, &iter, bm, itertype) { + BM_elem_flag_disable(ele, BM_ELEM_TAG); + } + + BLI_assert(walker.order == BMW_BREADTH_FIRST); + for (ele = static_cast<BMElem *>(BMW_begin(&walker, h_act)); ele != nullptr; + ele = static_cast<BMElem *>(BMW_step(&walker))) { + if (!BM_elem_flag_test(ele, BM_ELEM_TAG)) { + /* Deselect elements that aren't at "nth" depth from active */ + const int depth = BMW_current_depth(&walker) - 1; + if (!WM_operator_properties_checker_interval_test(op_params, depth)) { + BM_elem_select_set(bm, ele, false); + } + BM_elem_flag_enable(ele, BM_ELEM_TAG); + } + } + BMW_end(&walker); + + BMO_pop(bm); + + /* Flush selection up */ + EDBM_selectmode_flush_ex(em, flushtype); +} + +static void deselect_nth_active(BMEditMesh *em, BMVert **r_eve, BMEdge **r_eed, BMFace **r_efa) +{ + BMIter iter; + BMElem *ele; + + *r_eve = nullptr; + *r_eed = nullptr; + *r_efa = nullptr; + + EDBM_selectmode_flush(em); + ele = BM_mesh_active_elem_get(em->bm); + + if (ele && BM_elem_flag_test(ele, BM_ELEM_SELECT)) { + switch (ele->head.htype) { + case BM_VERT: + *r_eve = (BMVert *)ele; + return; + case BM_EDGE: + *r_eed = (BMEdge *)ele; + return; + case BM_FACE: + *r_efa = (BMFace *)ele; + return; + } + } + + if (em->selectmode & SCE_SELECT_VERTEX) { + BMVert *v; + BM_ITER_MESH (v, &iter, em->bm, BM_VERTS_OF_MESH) { + if (BM_elem_flag_test(v, BM_ELEM_SELECT)) { + *r_eve = v; + return; + } + } + } + else if (em->selectmode & SCE_SELECT_EDGE) { + BMEdge *e; + BM_ITER_MESH (e, &iter, em->bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(e, BM_ELEM_SELECT)) { + *r_eed = e; + return; + } + } + } + else if (em->selectmode & SCE_SELECT_FACE) { + BMFace *f = BM_mesh_active_face_get(em->bm, true, false); + if (f && BM_elem_flag_test(f, BM_ELEM_SELECT)) { + *r_efa = f; + return; + } + } +} + +static bool edbm_deselect_nth(BMEditMesh *em, const CheckerIntervalParams *op_params) +{ + BMVert *v; + BMEdge *e; + BMFace *f; + + deselect_nth_active(em, &v, &e, &f); + + if (v) { + walker_deselect_nth(em, op_params, &v->head); + return true; + } + if (e) { + walker_deselect_nth(em, op_params, &e->head); + return true; + } + if (f) { + walker_deselect_nth(em, op_params, &f->head); + return true; + } + + return false; +} + +static int edbm_select_nth_exec(bContext *C, wmOperator *op) +{ + const Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + CheckerIntervalParams op_params; + WM_operator_properties_checker_interval_from_op(op, &op_params); + bool found_active_elt = false; + + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( + scene, view_layer, CTX_wm_view3d(C), &objects_len); + + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *obedit = objects[ob_index]; + BMEditMesh *em = BKE_editmesh_from_object(obedit); + + if ((em->bm->totvertsel == 0) && (em->bm->totedgesel == 0) && (em->bm->totfacesel == 0)) { + continue; + } + + if (edbm_deselect_nth(em, &op_params) == true) { + found_active_elt = true; + EDBMUpdate_Params params{}; + params.calc_looptri = false; + params.calc_normals = false; + params.is_destructive = false; + EDBM_update(static_cast<Mesh *>(obedit->data), ¶ms); + } + } + MEM_freeN(objects); + + if (!found_active_elt) { + BKE_report(op->reports, RPT_ERROR, "Mesh object(s) have no active vertex/edge/face"); + return OPERATOR_CANCELLED; + } + + return OPERATOR_FINISHED; +} + +void MESH_OT_select_nth(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Checker Deselect"; + ot->idname = "MESH_OT_select_nth"; + ot->description = "Deselect every Nth element starting from the active vertex, edge or face"; + + /* api callbacks */ + ot->exec = edbm_select_nth_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + WM_operator_properties_checker_interval(ot, false); +} + +void em_setup_viewcontext(bContext *C, ViewContext *vc) +{ + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + ED_view3d_viewcontext_init(C, vc, depsgraph); + + if (vc->obedit) { + vc->em = BKE_editmesh_from_object(vc->obedit); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Sharp Edges Operator + * \{ */ + +static int edbm_select_sharp_edges_exec(bContext *C, wmOperator *op) +{ + /* Find edges that have exactly two neighboring faces, + * check the angle between those faces, and if angle is + * small enough, select the edge + */ + const float angle_limit_cos = cosf(RNA_float_get(op->ptr, "sharpness")); + + const Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( + scene, view_layer, CTX_wm_view3d(C), &objects_len); + + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *obedit = objects[ob_index]; + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMIter iter; + BMEdge *e; + BMLoop *l1, *l2; + + BM_ITER_MESH (e, &iter, em->bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(e, BM_ELEM_HIDDEN) == false && BM_edge_loop_pair(e, &l1, &l2)) { + /* edge has exactly two neighboring faces, check angle */ + const float angle_cos = dot_v3v3(l1->f->no, l2->f->no); + + if (angle_cos < angle_limit_cos) { + BM_edge_select_set(em->bm, e, true); + } + } + } + + if ((em->bm->selectmode & (SCE_SELECT_VERTEX | SCE_SELECT_EDGE)) == 0) { + /* Since we can't select individual edges, select faces connected to them. */ + EDBM_selectmode_convert(em, SCE_SELECT_EDGE, SCE_SELECT_FACE); + } + else { + EDBM_selectmode_flush(em); + } + DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + } + MEM_freeN(objects); + + return OPERATOR_FINISHED; +} + +void MESH_OT_edges_select_sharp(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Select Sharp Edges"; + ot->description = "Select all sharp enough edges"; + ot->idname = "MESH_OT_edges_select_sharp"; + + /* api callbacks */ + ot->exec = edbm_select_sharp_edges_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* props */ + prop = RNA_def_float_rotation(ot->srna, + "sharpness", + 0, + nullptr, + DEG2RADF(0.01f), + DEG2RADF(180.0f), + "Sharpness", + "", + DEG2RADF(1.0f), + DEG2RADF(180.0f)); + RNA_def_property_float_default(prop, DEG2RADF(30.0f)); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Linked Flat Faces Operator + * \{ */ + +static int edbm_select_linked_flat_faces_exec(bContext *C, wmOperator *op) +{ + const Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( + scene, view_layer, CTX_wm_view3d(C), &objects_len); + const float angle_limit_cos = cosf(RNA_float_get(op->ptr, "sharpness")); + + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *obedit = objects[ob_index]; + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + + if (bm->totfacesel == 0) { + continue; + } + + blender::Vector<BMFace *> stack; + + BMIter iter, liter, liter2; + BMFace *f; + BMLoop *l, *l2; + + BM_mesh_elem_hflag_disable_all(bm, BM_FACE, BM_ELEM_TAG, false); + + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if ((BM_elem_flag_test(f, BM_ELEM_HIDDEN) != 0) || + (BM_elem_flag_test(f, BM_ELEM_TAG) != 0) || + (BM_elem_flag_test(f, BM_ELEM_SELECT) == 0)) { + continue; + } + + BLI_assert(stack.is_empty()); + + do { + BM_face_select_set(bm, f, true); + + BM_elem_flag_enable(f, BM_ELEM_TAG); + + BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) { + BM_ITER_ELEM (l2, &liter2, l, BM_LOOPS_OF_LOOP) { + float angle_cos; + + if (BM_elem_flag_test(l2->f, BM_ELEM_TAG) || + BM_elem_flag_test(l2->f, BM_ELEM_HIDDEN)) { + continue; + } + + angle_cos = dot_v3v3(f->no, l2->f->no); + + if (angle_cos > angle_limit_cos) { + stack.append(l2->f); + } + } + } + } while (!stack.is_empty() && (f = stack.pop_last())); + } + + DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + } + MEM_freeN(objects); + + return OPERATOR_FINISHED; +} + +void MESH_OT_faces_select_linked_flat(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Select Linked Flat Faces"; + ot->description = "Select linked faces by angle"; + ot->idname = "MESH_OT_faces_select_linked_flat"; + + /* api callbacks */ + ot->exec = edbm_select_linked_flat_faces_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* props */ + prop = RNA_def_float_rotation(ot->srna, + "sharpness", + 0, + nullptr, + DEG2RADF(0.01f), + DEG2RADF(180.0f), + "Sharpness", + "", + DEG2RADF(1.0f), + DEG2RADF(180.0f)); + RNA_def_property_float_default(prop, DEG2RADF(1.0f)); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Non-Manifold Operator + * \{ */ + +static int edbm_select_non_manifold_exec(bContext *C, wmOperator *op) +{ + const bool use_extend = RNA_boolean_get(op->ptr, "extend"); + const bool use_wire = RNA_boolean_get(op->ptr, "use_wire"); + const bool use_boundary = RNA_boolean_get(op->ptr, "use_boundary"); + const bool use_multi_face = RNA_boolean_get(op->ptr, "use_multi_face"); + const bool use_non_contiguous = RNA_boolean_get(op->ptr, "use_non_contiguous"); + const bool use_verts = RNA_boolean_get(op->ptr, "use_verts"); + + const Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( + scene, view_layer, CTX_wm_view3d(C), &objects_len); + + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *obedit = objects[ob_index]; + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMVert *v; + BMEdge *e; + BMIter iter; + + if (!use_extend) { + EDBM_flag_disable_all(em, BM_ELEM_SELECT); + } + + /* Selects isolated verts, and edges that do not have 2 neighboring + * faces + */ + + if (em->selectmode == SCE_SELECT_FACE) { + BKE_report(op->reports, RPT_ERROR, "Does not work in face selection mode"); + MEM_freeN(objects); + return OPERATOR_CANCELLED; + } + + if (use_verts) { + BM_ITER_MESH (v, &iter, em->bm, BM_VERTS_OF_MESH) { + if (!BM_elem_flag_test(v, BM_ELEM_HIDDEN)) { + if (!BM_vert_is_manifold(v)) { + BM_vert_select_set(em->bm, v, true); + } + } + } + } + + if (use_wire || use_boundary || use_multi_face || use_non_contiguous) { + BM_ITER_MESH (e, &iter, em->bm, BM_EDGES_OF_MESH) { + if (!BM_elem_flag_test(e, BM_ELEM_HIDDEN)) { + if ((use_wire && BM_edge_is_wire(e)) || (use_boundary && BM_edge_is_boundary(e)) || + (use_non_contiguous && (BM_edge_is_manifold(e) && !BM_edge_is_contiguous(e))) || + (use_multi_face && (BM_edge_face_count_is_over(e, 2)))) { + /* check we never select perfect edge (in test above) */ + BLI_assert(!(BM_edge_is_manifold(e) && BM_edge_is_contiguous(e))); + + BM_edge_select_set(em->bm, e, true); + } + } + } + } + + DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + + EDBM_selectmode_flush(em); + } + MEM_freeN(objects); + + return OPERATOR_FINISHED; +} + +void MESH_OT_select_non_manifold(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Select Non-Manifold"; + ot->description = "Select all non-manifold vertices or edges"; + ot->idname = "MESH_OT_select_non_manifold"; + + /* api callbacks */ + ot->exec = edbm_select_non_manifold_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* props */ + RNA_def_boolean(ot->srna, "extend", true, "Extend", "Extend the selection"); + /* edges */ + RNA_def_boolean(ot->srna, "use_wire", true, "Wire", "Wire edges"); + RNA_def_boolean(ot->srna, "use_boundary", true, "Boundaries", "Boundary edges"); + RNA_def_boolean( + ot->srna, "use_multi_face", true, "Multiple Faces", "Edges shared by more than two faces"); + RNA_def_boolean(ot->srna, + "use_non_contiguous", + true, + "Non Contiguous", + "Edges between faces pointing in alternate directions"); + /* verts */ + RNA_def_boolean( + ot->srna, "use_verts", true, "Vertices", "Vertices connecting multiple face regions"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Random Operator + * \{ */ + +static int edbm_select_random_exec(bContext *C, wmOperator *op) +{ + const bool select = (RNA_enum_get(op->ptr, "action") == SEL_SELECT); + const float randfac = RNA_float_get(op->ptr, "ratio"); + const int seed = WM_operator_properties_select_random_seed_increment_get(op); + + const Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( + scene, view_layer, CTX_wm_view3d(C), &objects_len); + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *obedit = objects[ob_index]; + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMIter iter; + int seed_iter = seed; + + /* This gives a consistent result regardless of object order. */ + if (ob_index) { + seed_iter += BLI_ghashutil_strhash_p(obedit->id.name); + } + + if (em->selectmode & SCE_SELECT_VERTEX) { + int elem_map_len = 0; + BMVert **elem_map = static_cast<BMVert **>( + MEM_mallocN(sizeof(*elem_map) * em->bm->totvert, __func__)); + BMVert *eve; + BM_ITER_MESH (eve, &iter, em->bm, BM_VERTS_OF_MESH) { + if (!BM_elem_flag_test(eve, BM_ELEM_HIDDEN)) { + elem_map[elem_map_len++] = eve; + } + } + + BLI_array_randomize(elem_map, sizeof(*elem_map), elem_map_len, seed_iter); + const int count_select = elem_map_len * randfac; + for (int i = 0; i < count_select; i++) { + BM_vert_select_set(em->bm, elem_map[i], select); + } + MEM_freeN(elem_map); + } + else if (em->selectmode & SCE_SELECT_EDGE) { + int elem_map_len = 0; + BMEdge **elem_map = static_cast<BMEdge **>( + MEM_mallocN(sizeof(*elem_map) * em->bm->totedge, __func__)); + BMEdge *eed; + BM_ITER_MESH (eed, &iter, em->bm, BM_EDGES_OF_MESH) { + if (!BM_elem_flag_test(eed, BM_ELEM_HIDDEN)) { + elem_map[elem_map_len++] = eed; + } + } + BLI_array_randomize(elem_map, sizeof(*elem_map), elem_map_len, seed_iter); + const int count_select = elem_map_len * randfac; + for (int i = 0; i < count_select; i++) { + BM_edge_select_set(em->bm, elem_map[i], select); + } + MEM_freeN(elem_map); + } + else { + int elem_map_len = 0; + BMFace **elem_map = static_cast<BMFace **>( + MEM_mallocN(sizeof(*elem_map) * em->bm->totface, __func__)); + BMFace *efa; + BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { + if (!BM_elem_flag_test(efa, BM_ELEM_HIDDEN)) { + elem_map[elem_map_len++] = efa; + } + } + BLI_array_randomize(elem_map, sizeof(*elem_map), elem_map_len, seed_iter); + const int count_select = elem_map_len * randfac; + for (int i = 0; i < count_select; i++) { + BM_face_select_set(em->bm, elem_map[i], select); + } + MEM_freeN(elem_map); + } + + if (select) { + /* was EDBM_select_flush, but it over select in edge/face mode */ + EDBM_selectmode_flush(em); + } + else { + EDBM_deselect_flush(em); + } + + DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + } + + MEM_freeN(objects); + return OPERATOR_FINISHED; +} + +void MESH_OT_select_random(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Select Random"; + ot->description = "Randomly select vertices"; + ot->idname = "MESH_OT_select_random"; + + /* api callbacks */ + ot->exec = edbm_select_random_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* props */ + WM_operator_properties_select_random(ot); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Ungrouped Operator + * \{ */ + +static bool edbm_select_ungrouped_poll(bContext *C) +{ + if (ED_operator_editmesh(C)) { + Object *obedit = CTX_data_edit_object(C); + BMEditMesh *em = BKE_editmesh_from_object(obedit); + const int cd_dvert_offset = CustomData_get_offset(&em->bm->vdata, CD_MDEFORMVERT); + + const ListBase *defbase = BKE_object_defgroup_list(obedit); + if ((em->selectmode & SCE_SELECT_VERTEX) == 0) { + CTX_wm_operator_poll_msg_set(C, "Must be in vertex selection mode"); + } + else if (BLI_listbase_is_empty(defbase) || cd_dvert_offset == -1) { + CTX_wm_operator_poll_msg_set(C, "No weights/vertex groups on object"); + } + else { + return true; + } + } + return false; +} + +static int edbm_select_ungrouped_exec(bContext *C, wmOperator *op) +{ + const bool extend = RNA_boolean_get(op->ptr, "extend"); + const Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( + scene, view_layer, CTX_wm_view3d(C), &objects_len); + + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *obedit = objects[ob_index]; + BMEditMesh *em = BKE_editmesh_from_object(obedit); + + const int cd_dvert_offset = CustomData_get_offset(&em->bm->vdata, CD_MDEFORMVERT); + + if (cd_dvert_offset == -1) { + continue; + } + + BMVert *eve; + BMIter iter; + + bool changed = false; + + if (!extend) { + if (em->bm->totvertsel) { + EDBM_flag_disable_all(em, BM_ELEM_SELECT); + changed = true; + } + } + + BM_ITER_MESH (eve, &iter, em->bm, BM_VERTS_OF_MESH) { + if (!BM_elem_flag_test(eve, BM_ELEM_HIDDEN)) { + MDeformVert *dv = static_cast<MDeformVert *>(BM_ELEM_CD_GET_VOID_P(eve, cd_dvert_offset)); + /* no dv or dv set with no weight */ + if (ELEM(nullptr, dv, dv->dw)) { + BM_vert_select_set(em->bm, eve, true); + changed = true; + } + } + } + + if (changed) { + EDBM_selectmode_flush(em); + DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + } + } + MEM_freeN(objects); + return OPERATOR_FINISHED; +} + +void MESH_OT_select_ungrouped(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Select Ungrouped"; + ot->idname = "MESH_OT_select_ungrouped"; + ot->description = "Select vertices without a group"; + + /* api callbacks */ + ot->exec = edbm_select_ungrouped_exec; + ot->poll = edbm_select_ungrouped_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_boolean(ot->srna, "extend", false, "Extend", "Extend the selection"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Axis Operator + * \{ */ + +enum { + SELECT_AXIS_POS = 0, + SELECT_AXIS_NEG = 1, + SELECT_AXIS_ALIGN = 2, +}; + +static int edbm_select_axis_exec(bContext *C, wmOperator *op) +{ + Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + Object *obedit = CTX_data_edit_object(C); + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMVert *v_act = BM_mesh_active_vert_get(em->bm); + const int orientation = RNA_enum_get(op->ptr, "orientation"); + const int axis = RNA_enum_get(op->ptr, "axis"); + const int sign = RNA_enum_get(op->ptr, "sign"); + + if (v_act == nullptr) { + BKE_report( + op->reports, RPT_WARNING, "This operator requires an active vertex (last selected)"); + return OPERATOR_CANCELLED; + } + + const float limit = RNA_float_get(op->ptr, "threshold"); + + float value; + float axis_mat[3][3]; + + /* 3D view variables may be nullptr, (no need to check in poll function). */ + ED_transform_calc_orientation_from_type_ex(scene, + view_layer, + CTX_wm_view3d(C), + CTX_wm_region_view3d(C), + obedit, + obedit, + orientation, + V3D_AROUND_ACTIVE, + axis_mat); + + const float *axis_vector = axis_mat[axis]; + + { + float vertex_world[3]; + mul_v3_m4v3(vertex_world, obedit->obmat, v_act->co); + value = dot_v3v3(axis_vector, vertex_world); + } + + if (sign == SELECT_AXIS_NEG) { + value += limit; + } + else if (sign == SELECT_AXIS_POS) { + value -= limit; + } + + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode( + scene, view_layer, CTX_wm_view3d(C), &objects_len); + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *obedit_iter = objects[ob_index]; + BMEditMesh *em_iter = BKE_editmesh_from_object(obedit_iter); + BMesh *bm = em_iter->bm; + + if (bm->totvert == bm->totvertsel) { + continue; + } + + BMIter iter; + BMVert *v; + bool changed = false; + + BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { + if (!BM_elem_flag_test(v, BM_ELEM_HIDDEN | BM_ELEM_SELECT)) { + float v_iter_world[3]; + mul_v3_m4v3(v_iter_world, obedit_iter->obmat, v->co); + const float value_iter = dot_v3v3(axis_vector, v_iter_world); + switch (sign) { + case SELECT_AXIS_ALIGN: + if (fabsf(value_iter - value) < limit) { + BM_vert_select_set(bm, v, true); + changed = true; + } + break; + case SELECT_AXIS_NEG: + if (value_iter < value) { + BM_vert_select_set(bm, v, true); + changed = true; + } + break; + case SELECT_AXIS_POS: + if (value_iter > value) { + BM_vert_select_set(bm, v, true); + changed = true; + } + break; + } + } + } + if (changed) { + EDBM_selectmode_flush(em_iter); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit_iter->data); + DEG_id_tag_update(static_cast<ID *>(obedit_iter->data), ID_RECALC_SELECT); + } + } + MEM_freeN(objects); + return OPERATOR_FINISHED; +} + +void MESH_OT_select_axis(wmOperatorType *ot) +{ + static const EnumPropertyItem axis_sign_items[] = { + {SELECT_AXIS_POS, "POS", 0, "Positive Axis", ""}, + {SELECT_AXIS_NEG, "NEG", 0, "Negative Axis", ""}, + {SELECT_AXIS_ALIGN, "ALIGN", 0, "Aligned Axis", ""}, + {0, nullptr, 0, nullptr, nullptr}, + }; + + /* identifiers */ + ot->name = "Select Axis"; + ot->description = "Select all data in the mesh on a single axis"; + ot->idname = "MESH_OT_select_axis"; + + /* api callbacks */ + ot->exec = edbm_select_axis_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + RNA_def_enum(ot->srna, + "orientation", + rna_enum_transform_orientation_items, + V3D_ORIENT_LOCAL, + "Axis Mode", + "Axis orientation"); + RNA_def_enum(ot->srna, "sign", axis_sign_items, SELECT_AXIS_POS, "Axis Sign", "Side to select"); + RNA_def_enum(ot->srna, + "axis", + rna_enum_axis_xyz_items, + 0, + "Axis", + "Select the axis to compare each vertex on"); + RNA_def_float( + ot->srna, "threshold", 0.0001f, 0.000001f, 50.0f, "Threshold", "", 0.00001f, 10.0f); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Region to Loop Operator + * \{ */ + +static int edbm_region_to_loop_exec(bContext *C, wmOperator *UNUSED(op)) +{ + const Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( + scene, view_layer, CTX_wm_view3d(C), &objects_len); + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *obedit = objects[ob_index]; + BMEditMesh *em = BKE_editmesh_from_object(obedit); + + if (em->bm->totfacesel == 0) { + continue; + } + BMFace *f; + BMEdge *e; + BMIter iter; + + BM_mesh_elem_hflag_disable_all(em->bm, BM_EDGE, BM_ELEM_TAG, false); + + BM_ITER_MESH (f, &iter, em->bm, BM_FACES_OF_MESH) { + BMLoop *l1, *l2; + BMIter liter1, liter2; + + BM_ITER_ELEM (l1, &liter1, f, BM_LOOPS_OF_FACE) { + int tot = 0, totsel = 0; + + BM_ITER_ELEM (l2, &liter2, l1->e, BM_LOOPS_OF_EDGE) { + tot++; + totsel += BM_elem_flag_test(l2->f, BM_ELEM_SELECT) != 0; + } + + if ((tot != totsel && totsel > 0) || (totsel == 1 && tot == 1)) { + BM_elem_flag_enable(l1->e, BM_ELEM_TAG); + } + } + } + + EDBM_flag_disable_all(em, BM_ELEM_SELECT); + + BM_ITER_MESH (e, &iter, em->bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(e, BM_ELEM_TAG)) { + BM_edge_select_set(em->bm, e, true); + } + } + + /* If in face-only select mode, switch to edge select mode so that + * an edge-only selection is not inconsistent state */ + if (em->selectmode == SCE_SELECT_FACE) { + em->selectmode = SCE_SELECT_EDGE; + EDBM_selectmode_set(em); + EDBM_selectmode_to_scene(C); + } + + DEG_id_tag_update(&obedit->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + } + MEM_freeN(objects); + + return OPERATOR_FINISHED; +} + +void MESH_OT_region_to_loop(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Select Boundary Loop"; + ot->idname = "MESH_OT_region_to_loop"; + ot->description = "Select boundary edges around the selected faces"; + + /* api callbacks */ + ot->exec = edbm_region_to_loop_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Loop to Region Operator + * \{ */ + +static int loop_find_region(BMLoop *l, int flag, GSet *visit_face_set, BMFace ***region_out) +{ + blender::Vector<BMFace *> stack; + blender::Vector<BMFace *> region; + + stack.append(l->f); + BLI_gset_insert(visit_face_set, l->f); + + while (!stack.is_empty()) { + BMIter liter1, liter2; + BMLoop *l1, *l2; + + BMFace *f = stack.pop_last(); + region.append(f); + + BM_ITER_ELEM (l1, &liter1, f, BM_LOOPS_OF_FACE) { + if (BM_elem_flag_test(l1->e, flag)) { + continue; + } + + BM_ITER_ELEM (l2, &liter2, l1->e, BM_LOOPS_OF_EDGE) { + /* avoids finding same region twice + * (otherwise) the logic works fine without */ + if (BM_elem_flag_test(l2->f, BM_ELEM_TAG)) { + continue; + } + + if (BLI_gset_add(visit_face_set, l2->f)) { + stack.append(l2->f); + } + } + } + } + + BMFace **region_alloc = static_cast<BMFace **>( + MEM_malloc_arrayN(region.size(), sizeof(BMFace *), __func__)); + memcpy(region_alloc, region.data(), region.as_span().size_in_bytes()); + *region_out = region_alloc; + return region.size(); +} + +static int verg_radial(const void *va, const void *vb) +{ + const BMEdge *e_a = *((const BMEdge **)va); + const BMEdge *e_b = *((const BMEdge **)vb); + + const int a = BM_edge_face_count(e_a); + const int b = BM_edge_face_count(e_b); + + if (a > b) { + return -1; + } + if (a < b) { + return 1; + } + return 0; +} + +/** + * This function leaves faces tagged which are a part of the new region. + * + * \note faces already tagged are ignored, to avoid finding the same regions twice: + * important when we have regions with equal face counts, see: T40309 + */ +static int loop_find_regions(BMEditMesh *em, const bool selbigger) +{ + GSet *visit_face_set; + BMIter iter; + const int edges_len = em->bm->totedgesel; + BMEdge *e; + int count = 0, i; + + visit_face_set = BLI_gset_ptr_new_ex(__func__, edges_len); + BMEdge **edges = static_cast<BMEdge **>(MEM_mallocN(sizeof(*edges) * edges_len, __func__)); + + i = 0; + BM_ITER_MESH (e, &iter, em->bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(e, BM_ELEM_SELECT)) { + edges[i++] = e; + BM_elem_flag_enable(e, BM_ELEM_TAG); + } + else { + BM_elem_flag_disable(e, BM_ELEM_TAG); + } + } + + /* sort edges by radial cycle length */ + qsort(edges, edges_len, sizeof(*edges), verg_radial); + + for (i = 0; i < edges_len; i++) { + BMIter liter; + BMLoop *l; + BMFace **region = nullptr, **region_out; + int c, tot = 0; + + e = edges[i]; + + if (!BM_elem_flag_test(e, BM_ELEM_TAG)) { + continue; + } + + BM_ITER_ELEM (l, &liter, e, BM_LOOPS_OF_EDGE) { + if (BLI_gset_haskey(visit_face_set, l->f)) { + continue; + } + + c = loop_find_region(l, BM_ELEM_SELECT, visit_face_set, ®ion_out); + + if (!region || (selbigger ? c >= tot : c < tot)) { + /* this region is the best seen so far */ + tot = c; + if (region) { + /* free the previous best */ + MEM_freeN(region); + } + /* track the current region as the new best */ + region = region_out; + } + else { + /* this region is not as good as best so far, just free it */ + MEM_freeN(region_out); + } + } + + if (region) { + int j; + + for (j = 0; j < tot; j++) { + BM_elem_flag_enable(region[j], BM_ELEM_TAG); + BM_ITER_ELEM (l, &liter, region[j], BM_LOOPS_OF_FACE) { + BM_elem_flag_disable(l->e, BM_ELEM_TAG); + } + } + + count += tot; + + MEM_freeN(region); + } + } + + MEM_freeN(edges); + BLI_gset_free(visit_face_set, nullptr); + + return count; +} + +static int edbm_loop_to_region_exec(bContext *C, wmOperator *op) +{ + const bool select_bigger = RNA_boolean_get(op->ptr, "select_bigger"); + + const Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( + scene, view_layer, CTX_wm_view3d(C), &objects_len); + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *obedit = objects[ob_index]; + BMEditMesh *em = BKE_editmesh_from_object(obedit); + + if (em->bm->totedgesel == 0) { + continue; + } + + BMIter iter; + BMFace *f; + + /* find the set of regions with smallest number of total faces */ + BM_mesh_elem_hflag_disable_all(em->bm, BM_FACE, BM_ELEM_TAG, false); + const int a = loop_find_regions(em, select_bigger); + const int b = loop_find_regions(em, !select_bigger); + + BM_mesh_elem_hflag_disable_all(em->bm, BM_FACE, BM_ELEM_TAG, false); + loop_find_regions(em, ((a <= b) != select_bigger) ? select_bigger : !select_bigger); + + EDBM_flag_disable_all(em, BM_ELEM_SELECT); + + BM_ITER_MESH (f, &iter, em->bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_TAG) && !BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + BM_face_select_set(em->bm, f, true); + } + } + + EDBM_selectmode_flush(em); + + DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + } + MEM_freeN(objects); + + return OPERATOR_FINISHED; +} + +void MESH_OT_loop_to_region(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Select Loop Inner-Region"; + ot->idname = "MESH_OT_loop_to_region"; + ot->description = "Select region of faces inside of a selected loop of edges"; + + /* api callbacks */ + ot->exec = edbm_loop_to_region_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_boolean(ot->srna, + "select_bigger", + 0, + "Select Bigger", + "Select bigger regions instead of smaller ones"); +} + +/** \} */ |