From feae12a83a36bbfe61319660d08a7b4b9052c209 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Sat, 1 Oct 2022 22:12:55 -0500 Subject: Cleanup: Move more files using mesh runtime data to C++ In preparation for moving mesh runtime data out of DNA. --- source/blender/editors/include/ED_mesh.h | 2 +- source/blender/editors/include/ED_view3d.h | 3 +- source/blender/editors/mesh/CMakeLists.txt | 2 +- source/blender/editors/mesh/editmesh_loopcut.c | 2 +- source/blender/editors/mesh/editmesh_select.c | 5344 ------------------- source/blender/editors/mesh/editmesh_select.cc | 5353 ++++++++++++++++++++ source/blender/editors/mesh/mesh_intern.h | 2 +- source/blender/editors/space_view3d/CMakeLists.txt | 2 +- .../editors/space_view3d/view3d_iterators.c | 883 ---- .../editors/space_view3d/view3d_iterators.cc | 886 ++++ 10 files changed, 6246 insertions(+), 6233 deletions(-) delete mode 100644 source/blender/editors/mesh/editmesh_select.c create mode 100644 source/blender/editors/mesh/editmesh_select.cc delete mode 100644 source/blender/editors/space_view3d/view3d_iterators.c create mode 100644 source/blender/editors/space_view3d/view3d_iterators.cc (limited to 'source/blender/editors') diff --git a/source/blender/editors/include/ED_mesh.h b/source/blender/editors/include/ED_mesh.h index 26743a2bd08..a47ffe0b99a 100644 --- a/source/blender/editors/include/ED_mesh.h +++ b/source/blender/editors/include/ED_mesh.h @@ -197,7 +197,7 @@ void EDBM_automerge_and_split(struct Object *obedit, /** Export for ED_undo_sys. */ void ED_mesh_undosys_type(struct UndoType *ut); -/* editmesh_select.c */ +/* editmesh_select.cc */ void EDBM_select_mirrored(struct BMEditMesh *em, const struct Mesh *me, diff --git a/source/blender/editors/include/ED_view3d.h b/source/blender/editors/include/ED_view3d.h index 7c7b5771a04..9eceb5260ed 100644 --- a/source/blender/editors/include/ED_view3d.h +++ b/source/blender/editors/include/ED_view3d.h @@ -228,6 +228,7 @@ typedef enum { /** outside range (mainly for short), (can't avoid) */ V3D_PROJ_RET_OVERFLOW = 6, } eV3DProjStatus; +ENUM_OPERATORS(eV3DProjStatus, V3D_PROJ_RET_OVERFLOW); /* some clipping tests are optional */ typedef enum { @@ -350,7 +351,7 @@ void ED_view3d_cursor_snap_draw_util(struct RegionView3D *rv3d, const uchar color_point[4], eSnapMode snap_elem_type); -/* view3d_iterators.c */ +/* view3d_iterators.cc */ /* foreach iterators */ diff --git a/source/blender/editors/mesh/CMakeLists.txt b/source/blender/editors/mesh/CMakeLists.txt index 218564eaf30..c1049e32d4c 100644 --- a/source/blender/editors/mesh/CMakeLists.txt +++ b/source/blender/editors/mesh/CMakeLists.txt @@ -45,7 +45,7 @@ set(SRC editmesh_preselect_elem.c editmesh_rip.c editmesh_rip_edge.c - editmesh_select.c + editmesh_select.cc editmesh_select_similar.c editmesh_tools.c editmesh_undo.c diff --git a/source/blender/editors/mesh/editmesh_loopcut.c b/source/blender/editors/mesh/editmesh_loopcut.c index 591e06be80c..46d6cb4d006 100644 --- a/source/blender/editors/mesh/editmesh_loopcut.c +++ b/source/blender/editors/mesh/editmesh_loopcut.c @@ -226,7 +226,7 @@ static void ringsel_finish(bContext *C, wmOperator *op) } else { /* XXX Is this piece of code ever used now? Simple loop select is now - * in editmesh_select.c (around line 1000)... */ + * in editmesh_select.cc (around line 1000)... */ /* sets as active, useful for other tools */ if (em->selectmode & SCE_SELECT_VERTEX) { /* low priority TODO: get vertrex close to mouse. */ diff --git a/source/blender/editors/mesh/editmesh_select.c b/source/blender/editors/mesh/editmesh_select.c deleted file mode 100644 index 34f7301c4b1..00000000000 --- a/source/blender/editors/mesh/editmesh_select.c +++ /dev/null @@ -1,5344 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2004 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup edmesh - */ - -#include "MEM_guardedalloc.h" - -#include "BLI_array.h" -#include "BLI_bitmap.h" -#include "BLI_heap.h" -#include "BLI_linklist.h" -#include "BLI_linklist_stack.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 "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 NULL; - } - } - - return NULL; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \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; - - struct NearestVertUserData_Hit hit; - struct NearestVertUserData_Hit hit_cycle; -}; - -static void findnearestvert__doClosest(void *userData, - BMVert *eve, - const float screen_co[2], - int index) -{ - struct NearestVertUserData *data = 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 == NULL) && (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 = NULL; - } - } - - 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 NULL; - } - - struct NearestVertUserData data = {{0}}; - const struct NearestVertUserData_Hit *hit = NULL; - 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 = NULL; - - 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 == NULL) { - return NULL; - } - - 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, NULL); -} - -/* 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)) -{ - struct NearestEdgeUserData_ZBuf *data = 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; - - struct NearestEdgeUserData_Hit hit; - struct 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) -{ - struct NearestEdgeUserData *data = 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 == NULL) && (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 = NULL; - } - } - - if (r_eed_zbuf) { - *r_eed_zbuf = eed; - } - - /* exception for faces (verts don't need this) */ - if (r_dist_center_px_manhattan && eed) { - struct 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 NULL; - } - - struct NearestEdgeUserData data = {{0}}; - const struct NearestEdgeUserData_Hit *hit = NULL; - /* 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 = NULL; - - 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 == NULL) { - return NULL; - } - - 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, NULL, false, false, NULL, &base, 1, NULL); -} - -/* 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)) -{ - struct NearestFaceUserData_ZBuf *data = 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; - - struct NearestFaceUserData_Hit hit; - struct NearestFaceUserData_Hit hit_cycle; -}; - -static void findnearestface__doClosest(void *userData, - BMFace *efa, - const float screen_co[2], - int index) -{ - struct NearestFaceUserData *data = 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 == NULL) && (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 = NULL; - } - } - - if (r_efa_zbuf) { - *r_efa_zbuf = efa; - } - - /* exception for faces (verts don't need this) */ - if (r_dist_center && efa) { - struct 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 NULL; - } - - struct NearestFaceUserData data = {{0}}; - const struct NearestFaceUserData_Hit *hit = NULL; - const eV3DProjTest clip_flag = V3D_PROJ_TEST_CLIP_DEFAULT; - BMesh *prev_select_bm = NULL; - - 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 == NULL) { - return NULL; - } - - 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, NULL, false, false, false, NULL, &base, 1, NULL); -} - -#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 = {{NULL}}; - - /* 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 : - NULL; - - uint base_index = 0; - BMFace *efa_zbuf = NULL; - 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 : NULL; - - uint base_index = 0; - BMEdge *eed_zbuf = NULL; - 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 = NULL; - hit.e.ele = NULL; - } - else if (hit.e.ele) { - hit.f.ele = NULL; - } - - /* 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 != NULL) + (hit.e.ele != NULL) + (hit.f.ele != NULL)) <= 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, - struct BMVert **r_eve, - struct BMEdge **r_eed, - struct BMFace **r_efa) -{ - - const float mval_fl[2] = {UNPACK2(vc->mval)}; - float ray_origin[3], ray_direction[3]; - - struct { - uint base_index; - BMElem *ele; - } best = {0, NULL}; - /* Currently unused, keep since we may want to pick the best. */ - UNUSED_VARS(best); - - struct { - uint base_index; - BMElem *ele; - } best_vert = {0, NULL}; - - struct { - uint base_index; - BMElem *ele; - } best_edge = {0, NULL}; - - struct { - uint base_index; - BMElem *ele; - } best_face = {0, NULL}; - - 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 != NULL); - const bool use_edge = (r_eed != NULL); - const bool use_face = (r_efa != NULL); - - 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] = NULL; - { - Mesh *me_eval = (Mesh *)DEG_get_evaluated_id(vc->depsgraph, obedit->data); - if (me_eval->runtime.edit_data) { - coords = me_eval->runtime.edit_data->vertexCos; - } - } - - if (coords != NULL) { - 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 = NULL; - } - if (r_eed) { - *r_eed = NULL; - } - if (r_efa) { - *r_efa = NULL; - } - - 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 != NULL || best_edge.ele != NULL || best_face.ele != NULL); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \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 *groups_array; - 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; - } - - groups_array = MEM_mallocN(sizeof(*groups_array) * bm->totfacesel, __func__); - group_tot = BM_mesh_calc_face_groups( - bm, groups_array, &group_index, NULL, NULL, NULL, 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 = 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 = BLI_pophead(&faces_regions))) { - BMFace *f, **faces = 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(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(struct bContext *UNUSED(C), - struct wmOperatorType *UNUSED(op), - struct 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 NULL; -} - -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, NULL, 0, NULL, NULL}, - }; - - /* 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, PROP_HIDDEN | PROP_SKIP_SAVE); - prop = RNA_def_boolean(ot->srna, "use_expand", false, "Expand", ""); - RNA_def_property_flag(prop, 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, 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 = BMW_begin(&walker, start); ele; ele = 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 = BMW_begin(&walker, start); ele; ele = 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; - BMEdge **edarray; - 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++; - } - } - - edarray = 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(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 = NULL; - BMVert *eve = NULL; - BMEdge *eed = NULL; - BMFace *efa = NULL; - - 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 = NULL; - } - } - - em_original->selectmode = selectmode; - - if (em == NULL || eed == NULL) { - 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(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 = NULL; - - /* 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(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(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(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 struct SelectPick_Params *params) -{ - ViewContext vc; - - int base_index_active = -1; - BMVert *eve = NULL; - BMEdge *eed = NULL; - BMFace *efa = NULL; - - /* 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(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, NULL); - } - - /* 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(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 = 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 = 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 = 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 = NULL; - bool ret = false; - - if (obedit && obedit->type == OB_MESH) { - em = BKE_editmesh_from_object(obedit); - } - - if (em == NULL) { - 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 = NULL; - 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(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, NULL); - 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 = NULL; - if (obedit && obedit->type == OB_MESH) { - em = BKE_editmesh_from_object(obedit); - } - if (em == NULL) { - 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(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, NULL); - 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(struct 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(ob_iter->data, ID_RECALC_SELECT); - changed_multi = true; - } - return changed_multi; -} - -bool EDBM_mesh_deselect_all_multi(struct 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, - struct 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(struct 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, NULL, &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 (struct 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 = 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_array; - int(*fgroup_index)[2]; - int fgroup_len; - - fgroup_array = 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, NULL, NULL, 0, BM_EDGE); - - int *fgroup_recalc_stack = 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 = MEM_callocN(sizeof(*fgroup_listbase) * fgroup_len, __func__); - struct BMFaceLink *f_link_array = 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); - - struct 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 = MEM_mallocN(sizeof(*fgroup_table) * fgroup_len, __func__); - bool *fgroup_dirty = 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] = NULL; - } - } - - /* 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] = NULL; - } - 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] != NULL); - BLI_assert(fgroup_dirty[i_min] == false); - fgroup_table[i_min] = NULL; - changed = true; - - struct BMFaceLink *f_link; - while ((f_link = 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 = 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 NULL before recalculating. */ - BLI_heap_remove(fgroup_heap, fgroup_table[i_b]); - fgroup_table[i_b] = NULL; - /* 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 NULL as a secondary check. */ - - if (fgroup_dirty[i_a] == false) { - BLI_assert(fgroup_table[i_a] != NULL); - 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] != NULL) && (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] != NULL && 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] = NULL; - } - } - 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, NULL); - - 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 struct 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) -{ - struct 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(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 = NULL; - 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(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 = NULL; - 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 == NULL) { - 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(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, PROP_HIDDEN | PROP_SKIP_SAVE); - prop = RNA_def_int(ot->srna, "index", -1, -1, INT_MAX, "", "", 0, INT_MAX); - RNA_def_property_flag(prop, 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(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, NULL, 0, NULL, NULL}, - }; - - /* 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(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, obedit->data, axis, extend, &tot_mirr_iter, &tot_fail_iter); - } - } - - if (tot_mirr_iter) { - EDBM_selectmode_flush(em); - - DEG_id_tag_update(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(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(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 struct 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 == NULL) { - 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, NULL); - 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 = BMW_begin(&walker, h_act); ele != NULL; ele = 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 = NULL; - *r_eed = NULL; - *r_efa = NULL; - - 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 struct 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); - struct 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; - EDBM_update(obedit->data, - &(const struct EDBMUpdate_Params){ - .calc_looptri = false, - .calc_normals = false, - .is_destructive = false, - }); - } - } - 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(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, - NULL, - 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; - } - - BLI_LINKSTACK_DECLARE(stack, BMFace *); - - BMIter iter, liter, liter2; - BMFace *f; - BMLoop *l, *l2; - - BM_mesh_elem_hflag_disable_all(bm, BM_FACE, BM_ELEM_TAG, false); - - BLI_LINKSTACK_INIT(stack); - - 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(BLI_LINKSTACK_SIZE(stack) == 0); - - 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) { - BLI_LINKSTACK_PUSH(stack, l2->f); - } - } - } - } while ((f = BLI_LINKSTACK_POP(stack))); - } - - BLI_LINKSTACK_FREE(stack); - - DEG_id_tag_update(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, - NULL, - 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(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 = 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 = 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 = 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(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 = BM_ELEM_CD_GET_VOID_P(eve, cd_dvert_offset); - /* no dv or dv set with no weight */ - if (ELEM(NULL, dv, dv->dw)) { - BM_vert_select_set(em->bm, eve, true); - changed = true; - } - } - } - - if (changed) { - EDBM_selectmode_flush(em); - DEG_id_tag_update(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 == NULL) { - 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 NULL, (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(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, NULL, 0, NULL, NULL}, - }; - - /* 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) -{ - BMFace **region = NULL; - BMFace **stack = NULL; - BLI_array_declare(region); - BLI_array_declare(stack); - BMFace *f; - - BLI_array_append(stack, l->f); - BLI_gset_insert(visit_face_set, l->f); - - while (BLI_array_len(stack) > 0) { - BMIter liter1, liter2; - BMLoop *l1, *l2; - - f = BLI_array_pop(stack); - BLI_array_append(region, 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)) { - BLI_array_append(stack, l2->f); - } - } - } - } - - BLI_array_free(stack); - - *region_out = region; - return BLI_array_len(region); -} - -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, **edges; - int count = 0, i; - - visit_face_set = BLI_gset_ptr_new_ex(__func__, edges_len); - edges = 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 = NULL, **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, NULL); - - 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(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"); -} - -/** \} */ 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(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(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(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(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(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(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( + 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(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(BLI_pophead(&faces_regions)))) { + BMFace *f, **faces = static_cast(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(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(BMW_begin(&walker, start)); ele; + ele = static_cast(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(BMW_begin(&walker, start)); ele; + ele = static_cast(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( + 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(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(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(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(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(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(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(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(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(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(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(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(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(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( + 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( + 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( + 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( + MEM_callocN(sizeof(*fgroup_listbase) * fgroup_len, __func__)); + BMFaceLink *f_link_array = static_cast( + 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( + MEM_mallocN(sizeof(*fgroup_table) * fgroup_len, __func__)); + bool *fgroup_dirty = static_cast( + 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(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(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(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(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(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(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(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(obedit->data), + axis, + extend, + &tot_mirr_iter, + &tot_fail_iter); + } + } + + if (tot_mirr_iter) { + EDBM_selectmode_flush(em); + + DEG_id_tag_update(static_cast(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(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(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(BMW_begin(&walker, h_act)); ele != nullptr; + ele = static_cast(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(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(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 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(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(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( + 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( + 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( + 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(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(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(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(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 stack; + blender::Vector 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( + 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(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(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"); +} + +/** \} */ diff --git a/source/blender/editors/mesh/mesh_intern.h b/source/blender/editors/mesh/mesh_intern.h index 9f90ccc30ab..e6505715324 100644 --- a/source/blender/editors/mesh/mesh_intern.h +++ b/source/blender/editors/mesh/mesh_intern.h @@ -192,7 +192,7 @@ void MESH_OT_loopcut(struct wmOperatorType *ot); void MESH_OT_rip(struct wmOperatorType *ot); void MESH_OT_rip_edge(struct wmOperatorType *ot); -/* *** editmesh_select.c *** */ +/* *** editmesh_select.cc *** */ void MESH_OT_select_similar(struct wmOperatorType *ot); void MESH_OT_select_similar_region(struct wmOperatorType *ot); diff --git a/source/blender/editors/space_view3d/CMakeLists.txt b/source/blender/editors/space_view3d/CMakeLists.txt index 97e39c00c48..579e27b9259 100644 --- a/source/blender/editors/space_view3d/CMakeLists.txt +++ b/source/blender/editors/space_view3d/CMakeLists.txt @@ -46,7 +46,7 @@ set(SRC view3d_gizmo_ruler.c view3d_gizmo_tool_generic.c view3d_header.c - view3d_iterators.c + view3d_iterators.cc view3d_navigate.c view3d_navigate_dolly.c view3d_navigate_fly.c diff --git a/source/blender/editors/space_view3d/view3d_iterators.c b/source/blender/editors/space_view3d/view3d_iterators.c deleted file mode 100644 index 34f68e87880..00000000000 --- a/source/blender/editors/space_view3d/view3d_iterators.c +++ /dev/null @@ -1,883 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup spview3d - */ - -#include "DNA_armature_types.h" -#include "DNA_curve_types.h" -#include "DNA_lattice_types.h" -#include "DNA_mesh_types.h" -#include "DNA_meshdata_types.h" -#include "DNA_meta_types.h" -#include "DNA_object_types.h" -#include "DNA_scene_types.h" - -#include "BLI_math_geom.h" -#include "BLI_rect.h" -#include "BLI_utildefines.h" - -#include "BKE_DerivedMesh.h" -#include "BKE_action.h" -#include "BKE_armature.h" -#include "BKE_curve.h" -#include "BKE_displist.h" -#include "BKE_editmesh.h" -#include "BKE_mesh.h" -#include "BKE_mesh_iterators.h" -#include "BKE_mesh_runtime.h" -#include "BKE_mesh_wrapper.h" -#include "BKE_modifier.h" - -#include "DEG_depsgraph.h" -#include "DEG_depsgraph_query.h" - -#include "bmesh.h" - -#include "ED_armature.h" -#include "ED_screen.h" -#include "ED_view3d.h" - -/* -------------------------------------------------------------------- */ -/** \name Internal Clipping Utilities - * \{ */ - -/** - * Calculate clipping planes to use when #V3D_PROJ_TEST_CLIP_CONTENT is enabled. - * - * Planes are selected from the viewpoint using `clip_flag` - * to detect which planes should be applied (maximum 6). - * - * \return The number of planes written into `planes`. - */ -static int content_planes_from_clip_flag(const ARegion *region, - const Object *ob, - const eV3DProjTest clip_flag, - float planes[6][4]) -{ - BLI_assert(clip_flag & V3D_PROJ_TEST_CLIP_CONTENT); - - float *clip_xmin = NULL, *clip_xmax = NULL; - float *clip_ymin = NULL, *clip_ymax = NULL; - float *clip_zmin = NULL, *clip_zmax = NULL; - - int planes_len = 0; - - /* The order of `planes` has been selected based on the likelihood of points being fully - * outside the plane to increase the chance of an early exit in #clip_segment_v3_plane_n. - * With "near" being most likely and "far" being unlikely. - * - * Otherwise the order of axes in `planes` isn't significant. */ - - if (clip_flag & V3D_PROJ_TEST_CLIP_NEAR) { - clip_zmin = planes[planes_len++]; - } - if (clip_flag & V3D_PROJ_TEST_CLIP_WIN) { - clip_xmin = planes[planes_len++]; - clip_xmax = planes[planes_len++]; - clip_ymin = planes[planes_len++]; - clip_ymax = planes[planes_len++]; - } - if (clip_flag & V3D_PROJ_TEST_CLIP_FAR) { - clip_zmax = planes[planes_len++]; - } - - BLI_assert(planes_len <= 6); - if (planes_len != 0) { - RegionView3D *rv3d = region->regiondata; - float projmat[4][4]; - ED_view3d_ob_project_mat_get(rv3d, ob, projmat); - planes_from_projmat(projmat, clip_xmin, clip_xmax, clip_ymin, clip_ymax, clip_zmin, clip_zmax); - } - return planes_len; -} - -/** - * Edge projection is more involved since part of the edge may be behind the view - * or extend beyond the far limits. In the case of single points, these can be ignored. - * However it just may still be visible on screen, so constrained the edge to planes - * defined by the port to ensure both ends of the edge can be projected, see T32214. - * - * \note This is unrelated to #V3D_PROJ_TEST_CLIP_BB which must be checked separately. - */ -static bool view3d_project_segment_to_screen_with_content_clip_planes( - const ARegion *region, - const float v_a[3], - const float v_b[3], - const eV3DProjTest clip_flag, - const rctf *win_rect, - const float content_planes[][4], - const int content_planes_len, - /* Output. */ - float r_screen_co_a[2], - float r_screen_co_b[2]) -{ - /* Clipping already handled, no need to check in projection. */ - eV3DProjTest clip_flag_nowin = clip_flag & ~V3D_PROJ_TEST_CLIP_WIN; - - const eV3DProjStatus status_a = ED_view3d_project_float_object( - region, v_a, r_screen_co_a, clip_flag_nowin); - const eV3DProjStatus status_b = ED_view3d_project_float_object( - region, v_b, r_screen_co_b, clip_flag_nowin); - - if ((status_a == V3D_PROJ_RET_OK) && (status_b == V3D_PROJ_RET_OK)) { - if (clip_flag & V3D_PROJ_TEST_CLIP_WIN) { - if (!BLI_rctf_isect_segment(win_rect, r_screen_co_a, r_screen_co_b)) { - return false; - } - } - } - else { - if (content_planes_len == 0) { - return false; - } - - /* Both too near, ignore. */ - if ((status_a & V3D_PROJ_TEST_CLIP_NEAR) && (status_b & V3D_PROJ_TEST_CLIP_NEAR)) { - return false; - } - - /* Both too far, ignore. */ - if ((status_a & V3D_PROJ_TEST_CLIP_FAR) && (status_b & V3D_PROJ_TEST_CLIP_FAR)) { - return false; - } - - /* Simple cases have been ruled out, clip by viewport planes, then re-project. */ - float v_a_clip[3], v_b_clip[3]; - if (!clip_segment_v3_plane_n( - v_a, v_b, content_planes, content_planes_len, v_a_clip, v_b_clip)) { - return false; - } - - if ((ED_view3d_project_float_object(region, v_a_clip, r_screen_co_a, clip_flag_nowin) != - V3D_PROJ_RET_OK) || - (ED_view3d_project_float_object(region, v_b_clip, r_screen_co_b, clip_flag_nowin) != - V3D_PROJ_RET_OK)) { - return false; - } - - /* No need for #V3D_PROJ_TEST_CLIP_WIN check here, - * clipping the segment by planes handle this. */ - } - - return true; -} - -/** - * Project an edge, points that fail to project are tagged with #IS_CLIPPED. - */ -static bool view3d_project_segment_to_screen_with_clip_tag(const ARegion *region, - const float v_a[3], - const float v_b[3], - const eV3DProjTest clip_flag, - /* Output. */ - float r_screen_co_a[2], - float r_screen_co_b[2]) -{ - int count = 0; - - if (ED_view3d_project_float_object(region, v_a, r_screen_co_a, clip_flag) == V3D_PROJ_RET_OK) { - count++; - } - else { - r_screen_co_a[0] = IS_CLIPPED; /* weak */ - /* screen_co_a[1]: intentionally don't set this so we get errors on misuse */ - } - - if (ED_view3d_project_float_object(region, v_b, r_screen_co_b, clip_flag) == V3D_PROJ_RET_OK) { - count++; - } - else { - r_screen_co_b[0] = IS_CLIPPED; /* weak */ - /* screen_co_b[1]: intentionally don't set this so we get errors on misuse */ - } - - /* Caller may want to know this value, for now it's not needed. */ - return count != 0; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Private User Data Structures - * \{ */ - -typedef struct foreachScreenObjectVert_userData { - void (*func)(void *userData, MVert *mv, const float screen_co[2], int index); - void *userData; - ViewContext vc; - MVert *verts; - const bool *hide_vert; - eV3DProjTest clip_flag; -} foreachScreenObjectVert_userData; - -typedef struct foreachScreenVert_userData { - void (*func)(void *userData, BMVert *eve, const float screen_co[2], int index); - void *userData; - ViewContext vc; - eV3DProjTest clip_flag; -} foreachScreenVert_userData; - -/* user data structures for derived mesh callbacks */ -typedef struct foreachScreenEdge_userData { - void (*func)(void *userData, - BMEdge *eed, - const float screen_co_a[2], - const float screen_co_b[2], - int index); - void *userData; - ViewContext vc; - eV3DProjTest clip_flag; - - rctf win_rect; /* copy of: vc.region->winx/winy, use for faster tests, minx/y will always be 0 */ - - /** - * Clip plans defined by the view bounds, - * use when #V3D_PROJ_TEST_CLIP_CONTENT is enabled. - */ - float content_planes[6][4]; - int content_planes_len; -} foreachScreenEdge_userData; - -typedef struct foreachScreenFace_userData { - void (*func)(void *userData, BMFace *efa, const float screen_co_b[2], int index); - void *userData; - ViewContext vc; - eV3DProjTest clip_flag; -} foreachScreenFace_userData; - -/** - * \note foreach functions should be called while drawing or directly after - * if not, #ED_view3d_init_mats_rv3d() can be used for selection tools - * but would not give correct results with dupli's for eg. which don't - * use the object matrix in the usual way. - */ - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Edit-Mesh: For Each Screen Vertex - * \{ */ - -static void meshobject_foreachScreenVert__mapFunc(void *userData, - int index, - const float co[3], - const float UNUSED(no[3])) -{ - foreachScreenObjectVert_userData *data = userData; - if (data->hide_vert && data->hide_vert[index]) { - return; - } - MVert *mv = &data->verts[index]; - - float screen_co[2]; - - if (ED_view3d_project_float_object(data->vc.region, co, screen_co, data->clip_flag) != - V3D_PROJ_RET_OK) { - return; - } - - data->func(data->userData, mv, screen_co, index); -} - -void meshobject_foreachScreenVert( - ViewContext *vc, - void (*func)(void *userData, MVert *eve, const float screen_co[2], int index), - void *userData, - eV3DProjTest clip_flag) -{ - BLI_assert((clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) == 0); - foreachScreenObjectVert_userData data; - Mesh *me; - - Scene *scene_eval = DEG_get_evaluated_scene(vc->depsgraph); - Object *ob_eval = DEG_get_evaluated_object(vc->depsgraph, vc->obact); - - me = mesh_get_eval_final(vc->depsgraph, scene_eval, ob_eval, &CD_MASK_BAREMESH); - - ED_view3d_check_mats_rv3d(vc->rv3d); - - data.vc = *vc; - data.func = func; - data.userData = userData; - data.clip_flag = clip_flag; - data.verts = BKE_mesh_verts_for_write((Mesh *)vc->obact->data); - data.hide_vert = (const bool *)CustomData_get_layer_named( - &me->vdata, CD_PROP_BOOL, ".hide_vert"); - - if (clip_flag & V3D_PROJ_TEST_CLIP_BB) { - ED_view3d_clipping_local(vc->rv3d, vc->obact->obmat); - } - - BKE_mesh_foreach_mapped_vert(me, meshobject_foreachScreenVert__mapFunc, &data, MESH_FOREACH_NOP); -} - -static void mesh_foreachScreenVert__mapFunc(void *userData, - int index, - const float co[3], - const float UNUSED(no[3])) -{ - foreachScreenVert_userData *data = userData; - BMVert *eve = BM_vert_at_index(data->vc.em->bm, index); - if (UNLIKELY(BM_elem_flag_test(eve, BM_ELEM_HIDDEN))) { - return; - } - - float screen_co[2]; - if (ED_view3d_project_float_object(data->vc.region, co, screen_co, data->clip_flag) != - V3D_PROJ_RET_OK) { - return; - } - - data->func(data->userData, eve, screen_co, index); -} - -void mesh_foreachScreenVert( - ViewContext *vc, - void (*func)(void *userData, BMVert *eve, const float screen_co[2], int index), - void *userData, - eV3DProjTest clip_flag) -{ - foreachScreenVert_userData data; - - Mesh *me = editbmesh_get_eval_cage_from_orig( - vc->depsgraph, vc->scene, vc->obedit, &CD_MASK_BAREMESH); - me = BKE_mesh_wrapper_ensure_subdivision(me); - - ED_view3d_check_mats_rv3d(vc->rv3d); - - data.vc = *vc; - data.func = func; - data.userData = userData; - data.clip_flag = clip_flag; - - if (clip_flag & V3D_PROJ_TEST_CLIP_BB) { - ED_view3d_clipping_local(vc->rv3d, vc->obedit->obmat); /* for local clipping lookups */ - } - - BM_mesh_elem_table_ensure(vc->em->bm, BM_VERT); - BKE_mesh_foreach_mapped_vert(me, mesh_foreachScreenVert__mapFunc, &data, MESH_FOREACH_NOP); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Edit-Mesh: For Each Screen Mesh Edge - * \{ */ - -static void mesh_foreachScreenEdge__mapFunc(void *userData, - int index, - const float v_a[3], - const float v_b[3]) -{ - foreachScreenEdge_userData *data = userData; - BMEdge *eed = BM_edge_at_index(data->vc.em->bm, index); - if (UNLIKELY(BM_elem_flag_test(eed, BM_ELEM_HIDDEN))) { - return; - } - - float screen_co_a[2], screen_co_b[2]; - if (!view3d_project_segment_to_screen_with_content_clip_planes(data->vc.region, - v_a, - v_b, - data->clip_flag, - &data->win_rect, - data->content_planes, - data->content_planes_len, - screen_co_a, - screen_co_b)) { - return; - } - - data->func(data->userData, eed, screen_co_a, screen_co_b, index); -} - -void mesh_foreachScreenEdge(ViewContext *vc, - void (*func)(void *userData, - BMEdge *eed, - const float screen_co_a[2], - const float screen_co_b[2], - int index), - void *userData, - eV3DProjTest clip_flag) -{ - foreachScreenEdge_userData data; - - Mesh *me = editbmesh_get_eval_cage_from_orig( - vc->depsgraph, vc->scene, vc->obedit, &CD_MASK_BAREMESH); - me = BKE_mesh_wrapper_ensure_subdivision(me); - - ED_view3d_check_mats_rv3d(vc->rv3d); - - data.vc = *vc; - - data.win_rect.xmin = 0; - data.win_rect.ymin = 0; - data.win_rect.xmax = vc->region->winx; - data.win_rect.ymax = vc->region->winy; - - data.func = func; - data.userData = userData; - data.clip_flag = clip_flag; - - if (clip_flag & V3D_PROJ_TEST_CLIP_BB) { - ED_view3d_clipping_local(vc->rv3d, vc->obedit->obmat); /* for local clipping lookups */ - } - - if (clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) { - data.content_planes_len = content_planes_from_clip_flag( - vc->region, vc->obedit, clip_flag, data.content_planes); - } - else { - data.content_planes_len = 0; - } - - BM_mesh_elem_table_ensure(vc->em->bm, BM_EDGE); - BKE_mesh_foreach_mapped_edge(me, vc->em->bm->totedge, mesh_foreachScreenEdge__mapFunc, &data); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Edit-Mesh: For Each Screen Edge (Bounding Box Clipped) - * \{ */ - -/** - * Only call for bound-box clipping. - * Otherwise call #mesh_foreachScreenEdge__mapFunc - */ -static void mesh_foreachScreenEdge_clip_bb_segment__mapFunc(void *userData, - int index, - const float v_a[3], - const float v_b[3]) -{ - foreachScreenEdge_userData *data = userData; - BMEdge *eed = BM_edge_at_index(data->vc.em->bm, index); - if (UNLIKELY(BM_elem_flag_test(eed, BM_ELEM_HIDDEN))) { - return; - } - - BLI_assert(data->clip_flag & V3D_PROJ_TEST_CLIP_BB); - - float v_a_clip[3], v_b_clip[3]; - if (!clip_segment_v3_plane_n(v_a, v_b, data->vc.rv3d->clip_local, 4, v_a_clip, v_b_clip)) { - return; - } - - float screen_co_a[2], screen_co_b[2]; - if (!view3d_project_segment_to_screen_with_content_clip_planes(data->vc.region, - v_a_clip, - v_b_clip, - data->clip_flag, - &data->win_rect, - data->content_planes, - data->content_planes_len, - screen_co_a, - screen_co_b)) { - return; - } - - data->func(data->userData, eed, screen_co_a, screen_co_b, index); -} - -void mesh_foreachScreenEdge_clip_bb_segment(ViewContext *vc, - void (*func)(void *userData, - BMEdge *eed, - const float screen_co_a[2], - const float screen_co_b[2], - int index), - void *userData, - eV3DProjTest clip_flag) -{ - foreachScreenEdge_userData data; - - Mesh *me = editbmesh_get_eval_cage_from_orig( - vc->depsgraph, vc->scene, vc->obedit, &CD_MASK_BAREMESH); - me = BKE_mesh_wrapper_ensure_subdivision(me); - - ED_view3d_check_mats_rv3d(vc->rv3d); - - data.vc = *vc; - - data.win_rect.xmin = 0; - data.win_rect.ymin = 0; - data.win_rect.xmax = vc->region->winx; - data.win_rect.ymax = vc->region->winy; - - data.func = func; - data.userData = userData; - data.clip_flag = clip_flag; - - if (clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) { - data.content_planes_len = content_planes_from_clip_flag( - vc->region, vc->obedit, clip_flag, data.content_planes); - } - else { - data.content_planes_len = 0; - } - - BM_mesh_elem_table_ensure(vc->em->bm, BM_EDGE); - - if ((clip_flag & V3D_PROJ_TEST_CLIP_BB) && (vc->rv3d->clipbb != NULL)) { - ED_view3d_clipping_local(vc->rv3d, vc->obedit->obmat); /* for local clipping lookups. */ - BKE_mesh_foreach_mapped_edge( - me, vc->em->bm->totedge, mesh_foreachScreenEdge_clip_bb_segment__mapFunc, &data); - } - else { - BKE_mesh_foreach_mapped_edge(me, vc->em->bm->totedge, mesh_foreachScreenEdge__mapFunc, &data); - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Edit-Mesh: For Each Screen Face Center - * \{ */ - -static void mesh_foreachScreenFace__mapFunc(void *userData, - int index, - const float cent[3], - const float UNUSED(no[3])) -{ - foreachScreenFace_userData *data = userData; - BMFace *efa = BM_face_at_index(data->vc.em->bm, index); - if (UNLIKELY(BM_elem_flag_test(efa, BM_ELEM_HIDDEN))) { - return; - } - - float screen_co[2]; - if (ED_view3d_project_float_object(data->vc.region, cent, screen_co, data->clip_flag) != - V3D_PROJ_RET_OK) { - return; - } - - data->func(data->userData, efa, screen_co, index); -} - -void mesh_foreachScreenFace( - ViewContext *vc, - void (*func)(void *userData, BMFace *efa, const float screen_co_b[2], int index), - void *userData, - const eV3DProjTest clip_flag) -{ - BLI_assert((clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) == 0); - foreachScreenFace_userData data; - - Mesh *me = editbmesh_get_eval_cage_from_orig( - vc->depsgraph, vc->scene, vc->obedit, &CD_MASK_BAREMESH); - me = BKE_mesh_wrapper_ensure_subdivision(me); - ED_view3d_check_mats_rv3d(vc->rv3d); - - data.vc = *vc; - data.func = func; - data.userData = userData; - data.clip_flag = clip_flag; - - BM_mesh_elem_table_ensure(vc->em->bm, BM_FACE); - - if (me->runtime.subsurf_face_dot_tags != NULL) { - BKE_mesh_foreach_mapped_subdiv_face_center( - me, mesh_foreachScreenFace__mapFunc, &data, MESH_FOREACH_NOP); - } - else { - BKE_mesh_foreach_mapped_face_center( - me, mesh_foreachScreenFace__mapFunc, &data, MESH_FOREACH_NOP); - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Edit-Nurbs: For Each Screen Vertex - * \{ */ - -void nurbs_foreachScreenVert(ViewContext *vc, - void (*func)(void *userData, - Nurb *nu, - BPoint *bp, - BezTriple *bezt, - int beztindex, - bool handles_visible, - const float screen_co_b[2]), - void *userData, - const eV3DProjTest clip_flag) -{ - Curve *cu = vc->obedit->data; - Nurb *nu; - int i; - ListBase *nurbs = BKE_curve_editNurbs_get(cu); - /* If no point in the triple is selected, the handles are invisible. */ - const bool only_selected = (vc->v3d->overlay.handle_display == CURVE_HANDLE_SELECTED); - - ED_view3d_check_mats_rv3d(vc->rv3d); - - if (clip_flag & V3D_PROJ_TEST_CLIP_BB) { - ED_view3d_clipping_local(vc->rv3d, vc->obedit->obmat); /* for local clipping lookups */ - } - - for (nu = nurbs->first; nu; nu = nu->next) { - if (nu->type == CU_BEZIER) { - for (i = 0; i < nu->pntsu; i++) { - BezTriple *bezt = &nu->bezt[i]; - - if (bezt->hide == 0) { - const bool handles_visible = (vc->v3d->overlay.handle_display != CURVE_HANDLE_NONE) && - (!only_selected || BEZT_ISSEL_ANY(bezt)); - float screen_co[2]; - - if (!handles_visible) { - if (ED_view3d_project_float_object(vc->region, - bezt->vec[1], - screen_co, - V3D_PROJ_RET_CLIP_BB | V3D_PROJ_RET_CLIP_WIN) == - V3D_PROJ_RET_OK) { - func(userData, nu, NULL, bezt, 1, false, screen_co); - } - } - else { - if (ED_view3d_project_float_object(vc->region, - bezt->vec[0], - screen_co, - V3D_PROJ_RET_CLIP_BB | V3D_PROJ_RET_CLIP_WIN) == - V3D_PROJ_RET_OK) { - func(userData, nu, NULL, bezt, 0, true, screen_co); - } - if (ED_view3d_project_float_object(vc->region, - bezt->vec[1], - screen_co, - V3D_PROJ_RET_CLIP_BB | V3D_PROJ_RET_CLIP_WIN) == - V3D_PROJ_RET_OK) { - func(userData, nu, NULL, bezt, 1, true, screen_co); - } - if (ED_view3d_project_float_object(vc->region, - bezt->vec[2], - screen_co, - V3D_PROJ_RET_CLIP_BB | V3D_PROJ_RET_CLIP_WIN) == - V3D_PROJ_RET_OK) { - func(userData, nu, NULL, bezt, 2, true, screen_co); - } - } - } - } - } - else { - for (i = 0; i < nu->pntsu * nu->pntsv; i++) { - BPoint *bp = &nu->bp[i]; - - if (bp->hide == 0) { - float screen_co[2]; - if (ED_view3d_project_float_object( - vc->region, bp->vec, screen_co, V3D_PROJ_RET_CLIP_BB | V3D_PROJ_RET_CLIP_WIN) == - V3D_PROJ_RET_OK) { - func(userData, nu, bp, NULL, -1, false, screen_co); - } - } - } - } - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Edit-Meta: For Each Screen Meta-Element - * \{ */ - -void mball_foreachScreenElem(struct ViewContext *vc, - void (*func)(void *userData, - struct MetaElem *ml, - const float screen_co_b[2]), - void *userData, - const eV3DProjTest clip_flag) -{ - MetaBall *mb = (MetaBall *)vc->obedit->data; - MetaElem *ml; - - ED_view3d_check_mats_rv3d(vc->rv3d); - - for (ml = mb->editelems->first; ml; ml = ml->next) { - float screen_co[2]; - if (ED_view3d_project_float_object(vc->region, &ml->x, screen_co, clip_flag) == - V3D_PROJ_RET_OK) { - func(userData, ml, screen_co); - } - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Edit-Lattice: For Each Screen Vertex - * \{ */ - -void lattice_foreachScreenVert(ViewContext *vc, - void (*func)(void *userData, BPoint *bp, const float screen_co[2]), - void *userData, - const eV3DProjTest clip_flag) -{ - Object *obedit = vc->obedit; - Lattice *lt = obedit->data; - BPoint *bp = lt->editlatt->latt->def; - DispList *dl = obedit->runtime.curve_cache ? - BKE_displist_find(&obedit->runtime.curve_cache->disp, DL_VERTS) : - NULL; - const float *co = dl ? dl->verts : NULL; - int i, N = lt->editlatt->latt->pntsu * lt->editlatt->latt->pntsv * lt->editlatt->latt->pntsw; - - ED_view3d_check_mats_rv3d(vc->rv3d); - - if (clip_flag & V3D_PROJ_TEST_CLIP_BB) { - ED_view3d_clipping_local(vc->rv3d, obedit->obmat); /* for local clipping lookups */ - } - - for (i = 0; i < N; i++, bp++, co += 3) { - if (bp->hide == 0) { - float screen_co[2]; - if (ED_view3d_project_float_object(vc->region, dl ? co : bp->vec, screen_co, clip_flag) == - V3D_PROJ_RET_OK) { - func(userData, bp, screen_co); - } - } - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Edit-Armature: For Each Screen Bone - * \{ */ - -void armature_foreachScreenBone(struct ViewContext *vc, - void (*func)(void *userData, - struct EditBone *ebone, - const float screen_co_a[2], - const float screen_co_b[2]), - void *userData, - const eV3DProjTest clip_flag) -{ - bArmature *arm = vc->obedit->data; - EditBone *ebone; - - ED_view3d_check_mats_rv3d(vc->rv3d); - - float content_planes[6][4]; - int content_planes_len; - rctf win_rect; - - if (clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) { - content_planes_len = content_planes_from_clip_flag( - vc->region, vc->obedit, clip_flag, content_planes); - win_rect.xmin = 0; - win_rect.ymin = 0; - win_rect.xmax = vc->region->winx; - win_rect.ymax = vc->region->winy; - } - else { - content_planes_len = 0; - } - - for (ebone = arm->edbo->first; ebone; ebone = ebone->next) { - if (!EBONE_VISIBLE(arm, ebone)) { - continue; - } - - float screen_co_a[2], screen_co_b[2]; - const float *v_a = ebone->head, *v_b = ebone->tail; - - if (clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) { - if (!view3d_project_segment_to_screen_with_content_clip_planes(vc->region, - v_a, - v_b, - clip_flag, - &win_rect, - content_planes, - content_planes_len, - screen_co_a, - screen_co_b)) { - continue; - } - } - else { - if (!view3d_project_segment_to_screen_with_clip_tag( - vc->region, v_a, v_b, clip_flag, screen_co_a, screen_co_b)) { - continue; - } - } - - func(userData, ebone, screen_co_a, screen_co_b); - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Pose: For Each Screen Bone - * \{ */ - -void pose_foreachScreenBone(struct ViewContext *vc, - void (*func)(void *userData, - struct bPoseChannel *pchan, - const float screen_co_a[2], - const float screen_co_b[2]), - void *userData, - const eV3DProjTest clip_flag) -{ - /* Almost _exact_ copy of #armature_foreachScreenBone */ - - const Object *ob_eval = DEG_get_evaluated_object(vc->depsgraph, vc->obact); - const bArmature *arm_eval = ob_eval->data; - bPose *pose = vc->obact->pose; - bPoseChannel *pchan; - - ED_view3d_check_mats_rv3d(vc->rv3d); - - float content_planes[6][4]; - int content_planes_len; - rctf win_rect; - - if (clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) { - content_planes_len = content_planes_from_clip_flag( - vc->region, ob_eval, clip_flag, content_planes); - win_rect.xmin = 0; - win_rect.ymin = 0; - win_rect.xmax = vc->region->winx; - win_rect.ymax = vc->region->winy; - } - else { - content_planes_len = 0; - } - - for (pchan = pose->chanbase.first; pchan; pchan = pchan->next) { - if (!PBONE_VISIBLE(arm_eval, pchan->bone)) { - continue; - } - - bPoseChannel *pchan_eval = BKE_pose_channel_find_name(ob_eval->pose, pchan->name); - float screen_co_a[2], screen_co_b[2]; - const float *v_a = pchan_eval->pose_head, *v_b = pchan_eval->pose_tail; - - if (clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) { - if (!view3d_project_segment_to_screen_with_content_clip_planes(vc->region, - v_a, - v_b, - clip_flag, - &win_rect, - content_planes, - content_planes_len, - screen_co_a, - screen_co_b)) { - continue; - } - } - else { - if (!view3d_project_segment_to_screen_with_clip_tag( - vc->region, v_a, v_b, clip_flag, screen_co_a, screen_co_b)) { - continue; - } - } - - func(userData, pchan, screen_co_a, screen_co_b); - } -} - -/** \} */ diff --git a/source/blender/editors/space_view3d/view3d_iterators.cc b/source/blender/editors/space_view3d/view3d_iterators.cc new file mode 100644 index 00000000000..139ac9de6e4 --- /dev/null +++ b/source/blender/editors/space_view3d/view3d_iterators.cc @@ -0,0 +1,886 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup spview3d + */ + +#include "DNA_armature_types.h" +#include "DNA_curve_types.h" +#include "DNA_lattice_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_meta_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + +#include "BLI_math_geom.h" +#include "BLI_rect.h" +#include "BLI_utildefines.h" + +#include "BKE_DerivedMesh.h" +#include "BKE_action.h" +#include "BKE_armature.h" +#include "BKE_curve.h" +#include "BKE_displist.h" +#include "BKE_editmesh.h" +#include "BKE_mesh.h" +#include "BKE_mesh_iterators.h" +#include "BKE_mesh_runtime.h" +#include "BKE_mesh_wrapper.h" +#include "BKE_modifier.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" + +#include "bmesh.h" + +#include "ED_armature.h" +#include "ED_screen.h" +#include "ED_view3d.h" + +/* -------------------------------------------------------------------- */ +/** \name Internal Clipping Utilities + * \{ */ + +/** + * Calculate clipping planes to use when #V3D_PROJ_TEST_CLIP_CONTENT is enabled. + * + * Planes are selected from the viewpoint using `clip_flag` + * to detect which planes should be applied (maximum 6). + * + * \return The number of planes written into `planes`. + */ +static int content_planes_from_clip_flag(const ARegion *region, + const Object *ob, + const eV3DProjTest clip_flag, + float planes[6][4]) +{ + BLI_assert(clip_flag & V3D_PROJ_TEST_CLIP_CONTENT); + + float *clip_xmin = nullptr, *clip_xmax = nullptr; + float *clip_ymin = nullptr, *clip_ymax = nullptr; + float *clip_zmin = nullptr, *clip_zmax = nullptr; + + int planes_len = 0; + + /* The order of `planes` has been selected based on the likelihood of points being fully + * outside the plane to increase the chance of an early exit in #clip_segment_v3_plane_n. + * With "near" being most likely and "far" being unlikely. + * + * Otherwise the order of axes in `planes` isn't significant. */ + + if (clip_flag & V3D_PROJ_TEST_CLIP_NEAR) { + clip_zmin = planes[planes_len++]; + } + if (clip_flag & V3D_PROJ_TEST_CLIP_WIN) { + clip_xmin = planes[planes_len++]; + clip_xmax = planes[planes_len++]; + clip_ymin = planes[planes_len++]; + clip_ymax = planes[planes_len++]; + } + if (clip_flag & V3D_PROJ_TEST_CLIP_FAR) { + clip_zmax = planes[planes_len++]; + } + + BLI_assert(planes_len <= 6); + if (planes_len != 0) { + RegionView3D *rv3d = static_cast(region->regiondata); + float projmat[4][4]; + ED_view3d_ob_project_mat_get(rv3d, ob, projmat); + planes_from_projmat(projmat, clip_xmin, clip_xmax, clip_ymin, clip_ymax, clip_zmin, clip_zmax); + } + return planes_len; +} + +/** + * Edge projection is more involved since part of the edge may be behind the view + * or extend beyond the far limits. In the case of single points, these can be ignored. + * However it just may still be visible on screen, so constrained the edge to planes + * defined by the port to ensure both ends of the edge can be projected, see T32214. + * + * \note This is unrelated to #V3D_PROJ_TEST_CLIP_BB which must be checked separately. + */ +static bool view3d_project_segment_to_screen_with_content_clip_planes( + const ARegion *region, + const float v_a[3], + const float v_b[3], + const eV3DProjTest clip_flag, + const rctf *win_rect, + const float content_planes[][4], + const int content_planes_len, + /* Output. */ + float r_screen_co_a[2], + float r_screen_co_b[2]) +{ + /* Clipping already handled, no need to check in projection. */ + eV3DProjTest clip_flag_nowin = clip_flag & ~V3D_PROJ_TEST_CLIP_WIN; + + const eV3DProjStatus status_a = ED_view3d_project_float_object( + region, v_a, r_screen_co_a, clip_flag_nowin); + const eV3DProjStatus status_b = ED_view3d_project_float_object( + region, v_b, r_screen_co_b, clip_flag_nowin); + + if ((status_a == V3D_PROJ_RET_OK) && (status_b == V3D_PROJ_RET_OK)) { + if (clip_flag & V3D_PROJ_TEST_CLIP_WIN) { + if (!BLI_rctf_isect_segment(win_rect, r_screen_co_a, r_screen_co_b)) { + return false; + } + } + } + else { + if (content_planes_len == 0) { + return false; + } + + /* Both too near, ignore. */ + if ((status_a & V3D_PROJ_TEST_CLIP_NEAR) && (status_b & V3D_PROJ_TEST_CLIP_NEAR)) { + return false; + } + + /* Both too far, ignore. */ + if ((status_a & V3D_PROJ_TEST_CLIP_FAR) && (status_b & V3D_PROJ_TEST_CLIP_FAR)) { + return false; + } + + /* Simple cases have been ruled out, clip by viewport planes, then re-project. */ + float v_a_clip[3], v_b_clip[3]; + if (!clip_segment_v3_plane_n( + v_a, v_b, content_planes, content_planes_len, v_a_clip, v_b_clip)) { + return false; + } + + if ((ED_view3d_project_float_object(region, v_a_clip, r_screen_co_a, clip_flag_nowin) != + V3D_PROJ_RET_OK) || + (ED_view3d_project_float_object(region, v_b_clip, r_screen_co_b, clip_flag_nowin) != + V3D_PROJ_RET_OK)) { + return false; + } + + /* No need for #V3D_PROJ_TEST_CLIP_WIN check here, + * clipping the segment by planes handle this. */ + } + + return true; +} + +/** + * Project an edge, points that fail to project are tagged with #IS_CLIPPED. + */ +static bool view3d_project_segment_to_screen_with_clip_tag(const ARegion *region, + const float v_a[3], + const float v_b[3], + const eV3DProjTest clip_flag, + /* Output. */ + float r_screen_co_a[2], + float r_screen_co_b[2]) +{ + int count = 0; + + if (ED_view3d_project_float_object(region, v_a, r_screen_co_a, clip_flag) == V3D_PROJ_RET_OK) { + count++; + } + else { + r_screen_co_a[0] = IS_CLIPPED; /* weak */ + /* screen_co_a[1]: intentionally don't set this so we get errors on misuse */ + } + + if (ED_view3d_project_float_object(region, v_b, r_screen_co_b, clip_flag) == V3D_PROJ_RET_OK) { + count++; + } + else { + r_screen_co_b[0] = IS_CLIPPED; /* weak */ + /* screen_co_b[1]: intentionally don't set this so we get errors on misuse */ + } + + /* Caller may want to know this value, for now it's not needed. */ + return count != 0; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Private User Data Structures + * \{ */ + +struct foreachScreenObjectVert_userData { + void (*func)(void *userData, MVert *mv, const float screen_co[2], int index); + void *userData; + ViewContext vc; + MVert *verts; + const bool *hide_vert; + eV3DProjTest clip_flag; +}; + +struct foreachScreenVert_userData { + void (*func)(void *userData, BMVert *eve, const float screen_co[2], int index); + void *userData; + ViewContext vc; + eV3DProjTest clip_flag; +}; + +/* user data structures for derived mesh callbacks */ +struct foreachScreenEdge_userData { + void (*func)(void *userData, + BMEdge *eed, + const float screen_co_a[2], + const float screen_co_b[2], + int index); + void *userData; + ViewContext vc; + eV3DProjTest clip_flag; + + rctf win_rect; /* copy of: vc.region->winx/winy, use for faster tests, minx/y will always be 0 */ + + /** + * Clip plans defined by the view bounds, + * use when #V3D_PROJ_TEST_CLIP_CONTENT is enabled. + */ + float content_planes[6][4]; + int content_planes_len; +}; + +struct foreachScreenFace_userData { + void (*func)(void *userData, BMFace *efa, const float screen_co_b[2], int index); + void *userData; + ViewContext vc; + eV3DProjTest clip_flag; +}; + +/** + * \note foreach functions should be called while drawing or directly after + * if not, #ED_view3d_init_mats_rv3d() can be used for selection tools + * but would not give correct results with dupli's for eg. which don't + * use the object matrix in the usual way. + */ + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Edit-Mesh: For Each Screen Vertex + * \{ */ + +static void meshobject_foreachScreenVert__mapFunc(void *userData, + int index, + const float co[3], + const float UNUSED(no[3])) +{ + foreachScreenObjectVert_userData *data = static_cast( + userData); + if (data->hide_vert && data->hide_vert[index]) { + return; + } + MVert *mv = &data->verts[index]; + + float screen_co[2]; + + if (ED_view3d_project_float_object(data->vc.region, co, screen_co, data->clip_flag) != + V3D_PROJ_RET_OK) { + return; + } + + data->func(data->userData, mv, screen_co, index); +} + +void meshobject_foreachScreenVert( + ViewContext *vc, + void (*func)(void *userData, MVert *eve, const float screen_co[2], int index), + void *userData, + eV3DProjTest clip_flag) +{ + BLI_assert((clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) == 0); + foreachScreenObjectVert_userData data; + Mesh *me; + + Scene *scene_eval = DEG_get_evaluated_scene(vc->depsgraph); + Object *ob_eval = DEG_get_evaluated_object(vc->depsgraph, vc->obact); + + me = mesh_get_eval_final(vc->depsgraph, scene_eval, ob_eval, &CD_MASK_BAREMESH); + + ED_view3d_check_mats_rv3d(vc->rv3d); + + data.vc = *vc; + data.func = func; + data.userData = userData; + data.clip_flag = clip_flag; + data.verts = BKE_mesh_verts_for_write((Mesh *)vc->obact->data); + data.hide_vert = (const bool *)CustomData_get_layer_named( + &me->vdata, CD_PROP_BOOL, ".hide_vert"); + + if (clip_flag & V3D_PROJ_TEST_CLIP_BB) { + ED_view3d_clipping_local(vc->rv3d, vc->obact->obmat); + } + + BKE_mesh_foreach_mapped_vert(me, meshobject_foreachScreenVert__mapFunc, &data, MESH_FOREACH_NOP); +} + +static void mesh_foreachScreenVert__mapFunc(void *userData, + int index, + const float co[3], + const float UNUSED(no[3])) +{ + foreachScreenVert_userData *data = static_cast(userData); + BMVert *eve = BM_vert_at_index(data->vc.em->bm, index); + if (UNLIKELY(BM_elem_flag_test(eve, BM_ELEM_HIDDEN))) { + return; + } + + float screen_co[2]; + if (ED_view3d_project_float_object(data->vc.region, co, screen_co, data->clip_flag) != + V3D_PROJ_RET_OK) { + return; + } + + data->func(data->userData, eve, screen_co, index); +} + +void mesh_foreachScreenVert( + ViewContext *vc, + void (*func)(void *userData, BMVert *eve, const float screen_co[2], int index), + void *userData, + eV3DProjTest clip_flag) +{ + foreachScreenVert_userData data; + + Mesh *me = editbmesh_get_eval_cage_from_orig( + vc->depsgraph, vc->scene, vc->obedit, &CD_MASK_BAREMESH); + me = BKE_mesh_wrapper_ensure_subdivision(me); + + ED_view3d_check_mats_rv3d(vc->rv3d); + + data.vc = *vc; + data.func = func; + data.userData = userData; + data.clip_flag = clip_flag; + + if (clip_flag & V3D_PROJ_TEST_CLIP_BB) { + ED_view3d_clipping_local(vc->rv3d, vc->obedit->obmat); /* for local clipping lookups */ + } + + BM_mesh_elem_table_ensure(vc->em->bm, BM_VERT); + BKE_mesh_foreach_mapped_vert(me, mesh_foreachScreenVert__mapFunc, &data, MESH_FOREACH_NOP); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Edit-Mesh: For Each Screen Mesh Edge + * \{ */ + +static void mesh_foreachScreenEdge__mapFunc(void *userData, + int index, + const float v_a[3], + const float v_b[3]) +{ + foreachScreenEdge_userData *data = static_cast(userData); + BMEdge *eed = BM_edge_at_index(data->vc.em->bm, index); + if (UNLIKELY(BM_elem_flag_test(eed, BM_ELEM_HIDDEN))) { + return; + } + + float screen_co_a[2], screen_co_b[2]; + if (!view3d_project_segment_to_screen_with_content_clip_planes(data->vc.region, + v_a, + v_b, + data->clip_flag, + &data->win_rect, + data->content_planes, + data->content_planes_len, + screen_co_a, + screen_co_b)) { + return; + } + + data->func(data->userData, eed, screen_co_a, screen_co_b, index); +} + +void mesh_foreachScreenEdge(ViewContext *vc, + void (*func)(void *userData, + BMEdge *eed, + const float screen_co_a[2], + const float screen_co_b[2], + int index), + void *userData, + eV3DProjTest clip_flag) +{ + foreachScreenEdge_userData data; + + Mesh *me = editbmesh_get_eval_cage_from_orig( + vc->depsgraph, vc->scene, vc->obedit, &CD_MASK_BAREMESH); + me = BKE_mesh_wrapper_ensure_subdivision(me); + + ED_view3d_check_mats_rv3d(vc->rv3d); + + data.vc = *vc; + + data.win_rect.xmin = 0; + data.win_rect.ymin = 0; + data.win_rect.xmax = vc->region->winx; + data.win_rect.ymax = vc->region->winy; + + data.func = func; + data.userData = userData; + data.clip_flag = clip_flag; + + if (clip_flag & V3D_PROJ_TEST_CLIP_BB) { + ED_view3d_clipping_local(vc->rv3d, vc->obedit->obmat); /* for local clipping lookups */ + } + + if (clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) { + data.content_planes_len = content_planes_from_clip_flag( + vc->region, vc->obedit, clip_flag, data.content_planes); + } + else { + data.content_planes_len = 0; + } + + BM_mesh_elem_table_ensure(vc->em->bm, BM_EDGE); + BKE_mesh_foreach_mapped_edge(me, vc->em->bm->totedge, mesh_foreachScreenEdge__mapFunc, &data); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Edit-Mesh: For Each Screen Edge (Bounding Box Clipped) + * \{ */ + +/** + * Only call for bound-box clipping. + * Otherwise call #mesh_foreachScreenEdge__mapFunc + */ +static void mesh_foreachScreenEdge_clip_bb_segment__mapFunc(void *userData, + int index, + const float v_a[3], + const float v_b[3]) +{ + foreachScreenEdge_userData *data = static_cast(userData); + BMEdge *eed = BM_edge_at_index(data->vc.em->bm, index); + if (UNLIKELY(BM_elem_flag_test(eed, BM_ELEM_HIDDEN))) { + return; + } + + BLI_assert(data->clip_flag & V3D_PROJ_TEST_CLIP_BB); + + float v_a_clip[3], v_b_clip[3]; + if (!clip_segment_v3_plane_n(v_a, v_b, data->vc.rv3d->clip_local, 4, v_a_clip, v_b_clip)) { + return; + } + + float screen_co_a[2], screen_co_b[2]; + if (!view3d_project_segment_to_screen_with_content_clip_planes(data->vc.region, + v_a_clip, + v_b_clip, + data->clip_flag, + &data->win_rect, + data->content_planes, + data->content_planes_len, + screen_co_a, + screen_co_b)) { + return; + } + + data->func(data->userData, eed, screen_co_a, screen_co_b, index); +} + +void mesh_foreachScreenEdge_clip_bb_segment(ViewContext *vc, + void (*func)(void *userData, + BMEdge *eed, + const float screen_co_a[2], + const float screen_co_b[2], + int index), + void *userData, + eV3DProjTest clip_flag) +{ + foreachScreenEdge_userData data; + + Mesh *me = editbmesh_get_eval_cage_from_orig( + vc->depsgraph, vc->scene, vc->obedit, &CD_MASK_BAREMESH); + me = BKE_mesh_wrapper_ensure_subdivision(me); + + ED_view3d_check_mats_rv3d(vc->rv3d); + + data.vc = *vc; + + data.win_rect.xmin = 0; + data.win_rect.ymin = 0; + data.win_rect.xmax = vc->region->winx; + data.win_rect.ymax = vc->region->winy; + + data.func = func; + data.userData = userData; + data.clip_flag = clip_flag; + + if (clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) { + data.content_planes_len = content_planes_from_clip_flag( + vc->region, vc->obedit, clip_flag, data.content_planes); + } + else { + data.content_planes_len = 0; + } + + BM_mesh_elem_table_ensure(vc->em->bm, BM_EDGE); + + if ((clip_flag & V3D_PROJ_TEST_CLIP_BB) && (vc->rv3d->clipbb != nullptr)) { + ED_view3d_clipping_local(vc->rv3d, vc->obedit->obmat); /* for local clipping lookups. */ + BKE_mesh_foreach_mapped_edge( + me, vc->em->bm->totedge, mesh_foreachScreenEdge_clip_bb_segment__mapFunc, &data); + } + else { + BKE_mesh_foreach_mapped_edge(me, vc->em->bm->totedge, mesh_foreachScreenEdge__mapFunc, &data); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Edit-Mesh: For Each Screen Face Center + * \{ */ + +static void mesh_foreachScreenFace__mapFunc(void *userData, + int index, + const float cent[3], + const float UNUSED(no[3])) +{ + foreachScreenFace_userData *data = static_cast(userData); + BMFace *efa = BM_face_at_index(data->vc.em->bm, index); + if (UNLIKELY(BM_elem_flag_test(efa, BM_ELEM_HIDDEN))) { + return; + } + + float screen_co[2]; + if (ED_view3d_project_float_object(data->vc.region, cent, screen_co, data->clip_flag) != + V3D_PROJ_RET_OK) { + return; + } + + data->func(data->userData, efa, screen_co, index); +} + +void mesh_foreachScreenFace( + ViewContext *vc, + void (*func)(void *userData, BMFace *efa, const float screen_co_b[2], int index), + void *userData, + const eV3DProjTest clip_flag) +{ + BLI_assert((clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) == 0); + foreachScreenFace_userData data; + + Mesh *me = editbmesh_get_eval_cage_from_orig( + vc->depsgraph, vc->scene, vc->obedit, &CD_MASK_BAREMESH); + me = BKE_mesh_wrapper_ensure_subdivision(me); + ED_view3d_check_mats_rv3d(vc->rv3d); + + data.vc = *vc; + data.func = func; + data.userData = userData; + data.clip_flag = clip_flag; + + BM_mesh_elem_table_ensure(vc->em->bm, BM_FACE); + + if (me->runtime.subsurf_face_dot_tags != nullptr) { + BKE_mesh_foreach_mapped_subdiv_face_center( + me, mesh_foreachScreenFace__mapFunc, &data, MESH_FOREACH_NOP); + } + else { + BKE_mesh_foreach_mapped_face_center( + me, mesh_foreachScreenFace__mapFunc, &data, MESH_FOREACH_NOP); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Edit-Nurbs: For Each Screen Vertex + * \{ */ + +void nurbs_foreachScreenVert(ViewContext *vc, + void (*func)(void *userData, + Nurb *nu, + BPoint *bp, + BezTriple *bezt, + int beztindex, + bool handles_visible, + const float screen_co_b[2]), + void *userData, + const eV3DProjTest clip_flag) +{ + Curve *cu = static_cast(vc->obedit->data); + int i; + ListBase *nurbs = BKE_curve_editNurbs_get(cu); + /* If no point in the triple is selected, the handles are invisible. */ + const bool only_selected = (vc->v3d->overlay.handle_display == CURVE_HANDLE_SELECTED); + + ED_view3d_check_mats_rv3d(vc->rv3d); + + if (clip_flag & V3D_PROJ_TEST_CLIP_BB) { + ED_view3d_clipping_local(vc->rv3d, vc->obedit->obmat); /* for local clipping lookups */ + } + + LISTBASE_FOREACH (Nurb *, nu, nurbs) { + if (nu->type == CU_BEZIER) { + for (i = 0; i < nu->pntsu; i++) { + BezTriple *bezt = &nu->bezt[i]; + + if (bezt->hide == 0) { + const bool handles_visible = (vc->v3d->overlay.handle_display != CURVE_HANDLE_NONE) && + (!only_selected || BEZT_ISSEL_ANY(bezt)); + float screen_co[2]; + + if (!handles_visible) { + if (ED_view3d_project_float_object( + vc->region, + bezt->vec[1], + screen_co, + eV3DProjTest(V3D_PROJ_RET_CLIP_BB | V3D_PROJ_RET_CLIP_WIN)) == + V3D_PROJ_RET_OK) { + func(userData, nu, nullptr, bezt, 1, false, screen_co); + } + } + else { + if (ED_view3d_project_float_object( + vc->region, + bezt->vec[0], + screen_co, + eV3DProjTest(V3D_PROJ_RET_CLIP_BB | V3D_PROJ_RET_CLIP_WIN)) == + V3D_PROJ_RET_OK) { + func(userData, nu, nullptr, bezt, 0, true, screen_co); + } + if (ED_view3d_project_float_object( + vc->region, + bezt->vec[1], + screen_co, + eV3DProjTest(V3D_PROJ_RET_CLIP_BB | V3D_PROJ_RET_CLIP_WIN)) == + V3D_PROJ_RET_OK) { + func(userData, nu, nullptr, bezt, 1, true, screen_co); + } + if (ED_view3d_project_float_object( + vc->region, + bezt->vec[2], + screen_co, + eV3DProjTest(V3D_PROJ_RET_CLIP_BB | V3D_PROJ_RET_CLIP_WIN)) == + V3D_PROJ_RET_OK) { + func(userData, nu, nullptr, bezt, 2, true, screen_co); + } + } + } + } + } + else { + for (i = 0; i < nu->pntsu * nu->pntsv; i++) { + BPoint *bp = &nu->bp[i]; + + if (bp->hide == 0) { + float screen_co[2]; + if (ED_view3d_project_float_object( + vc->region, + bp->vec, + screen_co, + eV3DProjTest(V3D_PROJ_RET_CLIP_BB | V3D_PROJ_RET_CLIP_WIN)) == V3D_PROJ_RET_OK) { + func(userData, nu, bp, nullptr, -1, false, screen_co); + } + } + } + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Edit-Meta: For Each Screen Meta-Element + * \{ */ + +void mball_foreachScreenElem(ViewContext *vc, + void (*func)(void *userData, + MetaElem *ml, + const float screen_co_b[2]), + void *userData, + const eV3DProjTest clip_flag) +{ + MetaBall *mb = (MetaBall *)vc->obedit->data; + + ED_view3d_check_mats_rv3d(vc->rv3d); + + LISTBASE_FOREACH (MetaElem *, ml, mb->editelems) { + float screen_co[2]; + if (ED_view3d_project_float_object(vc->region, &ml->x, screen_co, clip_flag) == + V3D_PROJ_RET_OK) { + func(userData, ml, screen_co); + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Edit-Lattice: For Each Screen Vertex + * \{ */ + +void lattice_foreachScreenVert(ViewContext *vc, + void (*func)(void *userData, BPoint *bp, const float screen_co[2]), + void *userData, + const eV3DProjTest clip_flag) +{ + Object *obedit = vc->obedit; + Lattice *lt = static_cast(obedit->data); + BPoint *bp = lt->editlatt->latt->def; + DispList *dl = obedit->runtime.curve_cache ? + BKE_displist_find(&obedit->runtime.curve_cache->disp, DL_VERTS) : + nullptr; + const float *co = dl ? dl->verts : nullptr; + int i, N = lt->editlatt->latt->pntsu * lt->editlatt->latt->pntsv * lt->editlatt->latt->pntsw; + + ED_view3d_check_mats_rv3d(vc->rv3d); + + if (clip_flag & V3D_PROJ_TEST_CLIP_BB) { + ED_view3d_clipping_local(vc->rv3d, obedit->obmat); /* for local clipping lookups */ + } + + for (i = 0; i < N; i++, bp++, co += 3) { + if (bp->hide == 0) { + float screen_co[2]; + if (ED_view3d_project_float_object(vc->region, dl ? co : bp->vec, screen_co, clip_flag) == + V3D_PROJ_RET_OK) { + func(userData, bp, screen_co); + } + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Edit-Armature: For Each Screen Bone + * \{ */ + +void armature_foreachScreenBone(ViewContext *vc, + void (*func)(void *userData, + EditBone *ebone, + const float screen_co_a[2], + const float screen_co_b[2]), + void *userData, + const eV3DProjTest clip_flag) +{ + bArmature *arm = static_cast(vc->obedit->data); + + ED_view3d_check_mats_rv3d(vc->rv3d); + + float content_planes[6][4]; + int content_planes_len; + rctf win_rect; + + if (clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) { + content_planes_len = content_planes_from_clip_flag( + vc->region, vc->obedit, clip_flag, content_planes); + win_rect.xmin = 0; + win_rect.ymin = 0; + win_rect.xmax = vc->region->winx; + win_rect.ymax = vc->region->winy; + } + else { + content_planes_len = 0; + } + + LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) { + if (!EBONE_VISIBLE(arm, ebone)) { + continue; + } + + float screen_co_a[2], screen_co_b[2]; + const float *v_a = ebone->head, *v_b = ebone->tail; + + if (clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) { + if (!view3d_project_segment_to_screen_with_content_clip_planes(vc->region, + v_a, + v_b, + clip_flag, + &win_rect, + content_planes, + content_planes_len, + screen_co_a, + screen_co_b)) { + continue; + } + } + else { + if (!view3d_project_segment_to_screen_with_clip_tag( + vc->region, v_a, v_b, clip_flag, screen_co_a, screen_co_b)) { + continue; + } + } + + func(userData, ebone, screen_co_a, screen_co_b); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Pose: For Each Screen Bone + * \{ */ + +void pose_foreachScreenBone(ViewContext *vc, + void (*func)(void *userData, + bPoseChannel *pchan, + const float screen_co_a[2], + const float screen_co_b[2]), + void *userData, + const eV3DProjTest clip_flag) +{ + /* Almost _exact_ copy of #armature_foreachScreenBone */ + + const Object *ob_eval = DEG_get_evaluated_object(vc->depsgraph, vc->obact); + const bArmature *arm_eval = static_cast(ob_eval->data); + bPose *pose = vc->obact->pose; + + ED_view3d_check_mats_rv3d(vc->rv3d); + + float content_planes[6][4]; + int content_planes_len; + rctf win_rect; + + if (clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) { + content_planes_len = content_planes_from_clip_flag( + vc->region, ob_eval, clip_flag, content_planes); + win_rect.xmin = 0; + win_rect.ymin = 0; + win_rect.xmax = vc->region->winx; + win_rect.ymax = vc->region->winy; + } + else { + content_planes_len = 0; + } + + LISTBASE_FOREACH (bPoseChannel *, pchan, &pose->chanbase) { + if (!PBONE_VISIBLE(arm_eval, pchan->bone)) { + continue; + } + + bPoseChannel *pchan_eval = BKE_pose_channel_find_name(ob_eval->pose, pchan->name); + float screen_co_a[2], screen_co_b[2]; + const float *v_a = pchan_eval->pose_head, *v_b = pchan_eval->pose_tail; + + if (clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) { + if (!view3d_project_segment_to_screen_with_content_clip_planes(vc->region, + v_a, + v_b, + clip_flag, + &win_rect, + content_planes, + content_planes_len, + screen_co_a, + screen_co_b)) { + continue; + } + } + else { + if (!view3d_project_segment_to_screen_with_clip_tag( + vc->region, v_a, v_b, clip_flag, screen_co_a, screen_co_b)) { + continue; + } + } + + func(userData, pchan, screen_co_a, screen_co_b); + } +} + +/** \} */ -- cgit v1.2.3