/* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * The Original Code is Copyright (C) 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_utildefines_stack.h" #include "BKE_context.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 "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, 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 manhatten 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; } } } /** * Nearest vertex under the cursor. * * \param r_dist: (in/out), minimal distance to the nearest and at the end, actual distance * \param use_select_bias: * - When true, selected vertices are given a 5 pixel bias * to make them further than unselect verts. * - When false, unselected vertices are given the bias. * \param use_cycle: Cycle over elements within #FIND_NEAR_CYCLE_THRESHOLD_MIN in order of index. */ BMVert *EDBM_vert_find_nearest_ex(ViewContext *vc, float *r_dist, 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 = (uint)ED_view3d_backbuf_sample_size_clamp(vc->region, *r_dist); 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); if (index) { eve = (BMVert *)edbm_select_id_bm_elem_get(bases, index, &base_index); } else { eve = NULL; } } if (eve) { if (dist_px < *r_dist) { if (r_base_index) { *r_base_index = base_index; } *r_dist = dist_px; return eve; } } return NULL; } else { 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 = *r_dist; 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 < *r_dist) { if (r_base_index) { *r_base_index = base_index; } *r_dist = 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 *r_dist) { Base *base = BKE_view_layer_base_find(vc->view_layer, vc->obact); return EDBM_vert_find_nearest_ex(vc, r_dist, 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 manhatten distance to which ever edge we pick * (not used for choosing) */ float dist_center; }; 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 = 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 = len_manhattan_v2v2(data->mval_fl, screen_co_mid); } } } BMEdge *EDBM_edge_find_nearest_ex(ViewContext *vc, float *r_dist, float *r_dist_center, 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 = (uint)ED_view3d_backbuf_sample_size_clamp(vc->region, *r_dist); 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); 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 && 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); *r_dist_center = data.dist; } /* end exception */ if (eed) { if (dist_px < *r_dist) { if (r_base_index) { *r_base_index = base_index; } *r_dist = dist_px; return eed; } } return NULL; } else { 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 = *r_dist; ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); mesh_foreachScreenEdge(vc, find_nearest_edge__doClosest, &data, clip_flag); hit = (data.use_cycle && data.hit_cycle.edge) ? &data.hit_cycle : &data.hit; if (hit->dist < *r_dist) { if (r_base_index) { *r_base_index = base_index; } *r_dist = hit->dist; prev_select_bm = vc->em->bm; } } if (hit == NULL) { return NULL; } if (r_dist_center) { *r_dist_center = hit->dist_center; } 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 *r_dist) { Base *base = BKE_view_layer_base_find(vc->view_layer, vc->obact); return EDBM_edge_find_nearest_ex(vc, r_dist, 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; 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) { data->dist = 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 *r_dist, float *r_dist_center, 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 = 0.0f; uint index; BMFace *efa; { DRW_select_buffer_context_create(bases, bases_len, SCE_SELECT_FACE); index = DRW_select_buffer_sample_point(vc->depsgraph, vc->region, vc->v3d, vc->mval); 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 = 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; } /* end exception */ if (efa) { if (dist_test < *r_dist) { if (r_base_index) { *r_base_index = base_index; } *r_dist = dist_test; return efa; } } return NULL; } else { 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 = *r_dist; 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 < *r_dist) { if (r_base_index) { *r_base_index = base_index; } *r_dist = 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 *r_dist) { Base *base = BKE_view_layer_base_find(vc->view_layer, vc->obact); return EDBM_face_find_nearest_ex(vc, r_dist, NULL, 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; static short mval_prev[2] = {-1, -1}; /* only cycle while the mouse remains still */ const bool use_cycle = ((mval_prev[0] == vc->mval[0]) && (mval_prev[1] == vc->mval[1])); 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, 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 frontbuffer 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; } } mval_prev[0] = vc->mval[0]; mval_prev[1] = vc->mval[1]; /* 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 backbuffer, * 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, 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; } else { 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->shift); } if (!RNA_struct_property_is_set(op->ptr, "use_expand")) { RNA_boolean_set(op->ptr, "use_expand", event->ctrl); } return edbm_select_mode_exec(C, op); } 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; /* 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, const bool select, const bool select_mix, int *r_totsel, int *r_totunsel) { BMesh *bm = em->bm; BMElem *ele; BMWalker walker; int tot[2] = {0, 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)) { tot[(BM_elem_flag_test_bool(ele, BM_ELEM_SELECT) != select)] += 1; if (!select_mix && tot[0] && tot[1]) { tot[0] = tot[1] = -1; break; } } *r_totsel = tot[0]; *r_totunsel = tot[1]; 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"); 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( 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]; 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; /* cycle between BMW_EDGELOOP / BMW_EDGEBOUNDARY */ if (select_cycle && BM_edge_is_boundary(eed)) { int tot[2]; /* if the loops selected toggle the boundaries */ walker_select_count(em, BMW_EDGELOOP, eed, select, false, &tot[0], &tot[1]); if (tot[select] == 0) { edge_boundary = true; /* if the boundaries selected, toggle back to the loop */ walker_select_count(em, BMW_EDGEBOUNDARY, eed, select, false, &tot[0], &tot[1]); if (tot[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 { 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.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; } else { 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 */ RNA_def_boolean(ot->srna, "extend", 0, "Extend Select", "Extend the selection"); RNA_def_boolean(ot->srna, "deselect", 0, "Deselect", "Remove from the selection"); RNA_def_boolean(ot->srna, "toggle", 0, "Toggle Select", "Toggle the selection"); RNA_def_boolean(ot->srna, "ring", 0, "Select Ring", "Select ring"); } 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; RNA_def_boolean(ot->srna, "extend", 0, "Extend", "Extend the selection"); RNA_def_boolean(ot->srna, "deselect", 0, "Deselect", "Remove from the selection"); RNA_def_boolean(ot->srna, "toggle", 0, "Toggle Select", "Toggle the selection"); RNA_def_boolean(ot->srna, "ring", 1, "Select Ring", "Select ring"); } /** \} */ /* -------------------------------------------------------------------- */ /** \name (De)Select All Operator * \{ */ static int edbm_select_all_exec(bContext *C, wmOperator *op) { 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( 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)) { 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( 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], bool extend, bool deselect, bool toggle) { 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.view_layer, vc.v3d, &bases_len); bool ok = false; if (unified_findnearest(&vc, bases, bases_len, &base_index_active, &eve, &eed, &efa)) { Base *basact = bases[base_index_active]; ED_view3d_viewcontext_init_object(&vc, basact->object); /* Deselect everything */ if (extend == false && deselect == false && toggle == false) { 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); if (basact->object != ob_iter) { DEG_id_tag_update(ob_iter->data, ID_RECALC_SELECT); WM_event_add_notifier(C, NC_GEOM | ND_SELECT, ob_iter->data); } } } if (efa) { if (extend) { /* set the last selected face */ 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); } else if (deselect) { BM_select_history_remove(vc.em->bm, efa); BM_face_select_set(vc.em->bm, efa, false); } else { /* set the last selected face */ 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 if (toggle) { BM_select_history_remove(vc.em->bm, efa); BM_face_select_set(vc.em->bm, efa, false); } } } else if (eed) { if (extend) { /* 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); } else if (deselect) { BM_select_history_remove(vc.em->bm, eed); BM_edge_select_set(vc.em->bm, eed, false); } else { 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 if (toggle) { BM_select_history_remove(vc.em->bm, eed); BM_edge_select_set(vc.em->bm, eed, false); } } } else if (eve) { if (extend) { /* 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); } else if (deselect) { BM_select_history_remove(vc.em->bm, eve); BM_vert_select_set(vc.em->bm, eve, false); } else { 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 if (toggle) { BM_select_history_remove(vc.em->bm, eve); BM_vert_select_set(vc.em->bm, eve, false); } } } 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. */ if (vc.view_layer->basact != 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); ok = true; } MEM_freeN(bases); return ok; } /** \} */ /* -------------------------------------------------------------------- */ /** \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; } } } /* when switching select mode, makes sure selection is consistent for editing */ /* also for paranoia checks to make sure edge or face mode works */ 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 eges, 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); } } } } } /** * Expand & Contract the Selection * (used when changing modes and Ctrl key held) * * Flush the selection up: * - vert -> edge * - vert -> face * - edge -> face * * Flush the selection down: * - face -> edge * - face -> vert * - edge -> vert */ 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 selectionmode 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); } } } /* user facing function, does notification */ 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( 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( 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; } } if (changed) { WM_main_add_notifier(NC_SCENE | ND_TOOLSETTINGS, NULL); DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE); } return changed; } /** * Use to disable a selectmode if its enabled, Using another mode as a fallback * if the disabled mode is the only mode set. * * \return true if the mode is 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 wont 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; } else { 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.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.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 it's angle with the faces * connected to it's 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, 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( 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_finednearest needs ogl */ 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.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 */ BM_mesh_elem_index_ensure(bm, ele->head.htype); index = EDBM_elem_to_index_any(em, ele); /* TODO(MULTI_EDIT), index doesn't know which object, * index selections isn't very common. */ 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 = CTX_data_edit_object(C); BMEditMesh *em = BKE_editmesh_from_object(obedit); BMesh *bm = em->bm; int index; const bool sel = !RNA_boolean_get(op->ptr, "deselect"); index = RNA_int_get(op->ptr, "index"); if (index < 0 || index >= (bm->totvert + bm->totedge + bm->totface)) { return OPERATOR_CANCELLED; } BMElem *ele = EDBM_elem_from_index_any(em, index); #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, "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) { 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( 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) { 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( 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) { 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( 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) { 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( 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) { 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( 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; } else if (e) { walker_deselect_nth(em, op_params, &e->head); return true; } else if (f) { walker_deselect_nth(em, op_params, &f->head); return true; } return false; } static int edbm_select_nth_exec(bContext *C, wmOperator *op) { 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( 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_generic(obedit->data, false, false); } } MEM_freeN(objects); if (!found_active_elt) { BKE_report(op->reports, RPT_ERROR, (objects_len == 1 ? "Mesh has no active vert/edge/face" : "Meshes have no active vert/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")); 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( 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) { 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( 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"); 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( 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 3+ 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, "percent") / 100.0f; const int seed = WM_operator_properties_select_random_seed_increment_get(op); 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( 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); } RNG *rng = BLI_rng_new_srandom(seed_iter); if (em->selectmode & SCE_SELECT_VERTEX) { BMVert *eve; BM_ITER_MESH (eve, &iter, em->bm, BM_VERTS_OF_MESH) { if (!BM_elem_flag_test(eve, BM_ELEM_HIDDEN) && BLI_rng_get_float(rng) < randfac) { BM_vert_select_set(em->bm, eve, select); } } } else if (em->selectmode & SCE_SELECT_EDGE) { BMEdge *eed; BM_ITER_MESH (eed, &iter, em->bm, BM_EDGES_OF_MESH) { if (!BM_elem_flag_test(eed, BM_ELEM_HIDDEN) && BLI_rng_get_float(rng) < randfac) { BM_edge_select_set(em->bm, eed, select); } } } else { BMFace *efa; BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!BM_elem_flag_test(efa, BM_ELEM_HIDDEN) && BLI_rng_get_float(rng) < randfac) { BM_face_select_set(em->bm, efa, select); } } } BLI_rng_free(rng); 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); 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(&obedit->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"); 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( 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(C, axis_mat, scene, CTX_wm_region_view3d(C), obedit, obedit, orientation, 0, V3D_AROUND_ACTIVE); 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( 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)) { 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( 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 apart 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"); 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( 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"); } /** \} */