/* SPDX-License-Identifier: GPL-2.0-or-later * Copyright 2001-2002 NaN Holding BV. All rights reserved. */ /** \file * \ingroup eduv */ #include #include #include #include "MEM_guardedalloc.h" #include "DNA_image_types.h" #include "DNA_material_types.h" #include "DNA_meshdata_types.h" #include "DNA_node_types.h" #include "DNA_object_types.h" #include "DNA_scene_types.h" #include "DNA_space_types.h" #include "BLI_alloca.h" #include "BLI_blenlib.h" #include "BLI_hash.h" #include "BLI_kdopbvh.h" #include "BLI_kdtree.h" #include "BLI_lasso_2d.h" #include "BLI_math.h" #include "BLI_polyfill_2d.h" #include "BLI_utildefines.h" #include "BKE_context.h" #include "BKE_customdata.h" #include "BKE_editmesh.h" #include "BKE_layer.h" #include "BKE_material.h" #include "BKE_mesh.h" #include "BKE_mesh_mapping.h" #include "BKE_report.h" #include "DEG_depsgraph.h" #include "DEG_depsgraph_query.h" #include "ED_image.h" #include "ED_mesh.h" #include "ED_screen.h" #include "ED_select_utils.h" #include "ED_uvedit.h" #include "RNA_access.h" #include "RNA_define.h" #include "RNA_enum_types.h" #include "WM_api.h" #include "WM_types.h" #include "UI_view2d.h" #include "uvedit_intern.h" static void uv_select_all_perform(const Scene *scene, Object *obedit, int action); static void uv_select_all_perform_multi_ex(const Scene *scene, Object **objects, const uint objects_len, int action, const Object *ob_exclude); static void uv_select_all_perform_multi(const Scene *scene, Object **objects, const uint objects_len, int action); static void uv_select_flush_from_tag_face(const Scene *scene, Object *obedit, const bool select); static void uv_select_flush_from_tag_loop(const Scene *scene, Object *obedit, const bool select); static void uv_select_flush_from_loop_edge_flag(const Scene *scene, BMEditMesh *em); static void uv_select_tag_update_for_object(Depsgraph *depsgraph, const ToolSettings *ts, Object *obedit); typedef enum { UV_SSIM_AREA_UV = 1000, UV_SSIM_AREA_3D, UV_SSIM_LENGTH_UV, UV_SSIM_LENGTH_3D, UV_SSIM_SIDES, UV_SSIM_PIN, UV_SSIM_MATERIAL, } eUVSelectSimilar; /* -------------------------------------------------------------------- */ /** \name Active Selection Tracking * * Currently we don't store loops in the selection history, * store face/edge/vert combinations (needed for UV path selection). * \{ */ void ED_uvedit_active_vert_loop_set(BMesh *bm, BMLoop *l) { BM_select_history_clear(bm); BM_select_history_remove(bm, (BMElem *)l->f); BM_select_history_remove(bm, (BMElem *)l->v); BM_select_history_store_notest(bm, (BMElem *)l->f); BM_select_history_store_notest(bm, (BMElem *)l->v); } BMLoop *ED_uvedit_active_vert_loop_get(BMesh *bm) { BMEditSelection *ese = bm->selected.last; if (ese && ese->prev) { BMEditSelection *ese_prev = ese->prev; if ((ese->htype == BM_VERT) && (ese_prev->htype == BM_FACE)) { /* May be NULL. */ return BM_face_vert_share_loop((BMFace *)ese_prev->ele, (BMVert *)ese->ele); } } return NULL; } void ED_uvedit_active_edge_loop_set(BMesh *bm, BMLoop *l) { BM_select_history_clear(bm); BM_select_history_remove(bm, (BMElem *)l->f); BM_select_history_remove(bm, (BMElem *)l->e); BM_select_history_store_notest(bm, (BMElem *)l->f); BM_select_history_store_notest(bm, (BMElem *)l->e); } BMLoop *ED_uvedit_active_edge_loop_get(BMesh *bm) { BMEditSelection *ese = bm->selected.last; if (ese && ese->prev) { BMEditSelection *ese_prev = ese->prev; if ((ese->htype == BM_EDGE) && (ese_prev->htype == BM_FACE)) { /* May be NULL. */ return BM_face_edge_share_loop((BMFace *)ese_prev->ele, (BMEdge *)ese->ele); } } return NULL; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Visibility and Selection Utilities * \{ */ char ED_uvedit_select_mode_get(const Scene *scene) { const ToolSettings *ts = scene->toolsettings; char uv_selectmode = UV_SELECT_VERTEX; if (ts->uv_flag & UV_SYNC_SELECTION) { if (ts->selectmode & SCE_SELECT_VERTEX) { uv_selectmode = UV_SELECT_VERTEX; } else if (ts->selectmode & SCE_SELECT_EDGE) { uv_selectmode = UV_SELECT_EDGE; } else if (ts->selectmode & SCE_SELECT_FACE) { uv_selectmode = UV_SELECT_FACE; } } else { if (ts->uv_selectmode & UV_SELECT_VERTEX) { uv_selectmode = UV_SELECT_VERTEX; } else if (ts->uv_selectmode & UV_SELECT_EDGE) { uv_selectmode = UV_SELECT_EDGE; } else if (ts->uv_selectmode & UV_SELECT_FACE) { uv_selectmode = UV_SELECT_FACE; } } return uv_selectmode; } void ED_uvedit_select_sync_flush(const ToolSettings *ts, BMEditMesh *em, const bool select) { /* bmesh API handles flushing but not on de-select */ if (ts->uv_flag & UV_SYNC_SELECTION) { if (ts->selectmode != SCE_SELECT_FACE) { if (select == false) { EDBM_deselect_flush(em); } else { EDBM_select_flush(em); } } if (select == false) { BM_select_history_validate(em->bm); } } } static void uvedit_vertex_select_tagged(BMEditMesh *em, Scene *scene, bool select, int cd_loop_uv_offset) { BMFace *efa; BMLoop *l; BMIter iter, liter; BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { if (BM_elem_flag_test(l->v, BM_ELEM_TAG)) { uvedit_uv_select_set(scene, em, l, select, false, cd_loop_uv_offset); } } } } bool uvedit_face_visible_test_ex(const ToolSettings *ts, BMFace *efa) { if (ts->uv_flag & UV_SYNC_SELECTION) { return (BM_elem_flag_test(efa, BM_ELEM_HIDDEN) == 0); } return (BM_elem_flag_test(efa, BM_ELEM_HIDDEN) == 0 && BM_elem_flag_test(efa, BM_ELEM_SELECT)); } bool uvedit_face_visible_test(const Scene *scene, BMFace *efa) { return uvedit_face_visible_test_ex(scene->toolsettings, efa); } bool uvedit_face_select_test_ex(const ToolSettings *ts, BMFace *efa, const int cd_loop_uv_offset) { if (ts->uv_flag & UV_SYNC_SELECTION) { return (BM_elem_flag_test(efa, BM_ELEM_SELECT)); } BMLoop *l; MLoopUV *luv; BMIter liter; BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); if (ts->uv_selectmode & UV_SELECT_VERTEX) { if ((luv->flag & MLOOPUV_VERTSEL) == 0) { return false; } } else { if ((luv->flag & MLOOPUV_EDGESEL) == 0) { return false; } } } return true; } bool uvedit_face_select_test(const Scene *scene, BMFace *efa, const int cd_loop_uv_offset) { return uvedit_face_select_test_ex(scene->toolsettings, efa, cd_loop_uv_offset); } void uvedit_face_select_set_with_sticky(const Scene *scene, BMEditMesh *em, BMFace *efa, const bool select, const bool do_history, const int cd_loop_uv_offset) { const ToolSettings *ts = scene->toolsettings; const char sticky = ts->uv_sticky; if (ts->uv_flag & UV_SYNC_SELECTION) { uvedit_face_select_set(scene, em, efa, select, do_history, cd_loop_uv_offset); return; } if (!uvedit_face_visible_test(scene, efa)) { return; } /* NOTE: Previously face selections done in sticky vertex mode selected stray UV vertices * (not part of any face selections). This now uses the sticky location mode logic instead. */ switch (sticky) { case SI_STICKY_DISABLE: { uvedit_face_select_set(scene, em, efa, select, do_history, cd_loop_uv_offset); break; } default: { /* SI_STICKY_LOC and SI_STICKY_VERTEX modes. */ uvedit_face_select_shared_vert(scene, em, efa, select, do_history, cd_loop_uv_offset); } } } void uvedit_face_select_shared_vert(const Scene *scene, BMEditMesh *em, BMFace *efa, const bool select, const bool do_history, const int cd_loop_uv_offset) { BMLoop *l; BMIter liter; BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); if (select) { luv->flag |= MLOOPUV_EDGESEL; uvedit_uv_select_shared_vert( scene, em, l, select, SI_STICKY_LOC, do_history, cd_loop_uv_offset); } else { luv->flag &= ~MLOOPUV_EDGESEL; if (!uvedit_vert_is_face_select_any_other(scene, l, cd_loop_uv_offset)) { uvedit_uv_select_shared_vert( scene, em, l, select, SI_STICKY_LOC, do_history, cd_loop_uv_offset); } } } } void uvedit_face_select_set(const Scene *scene, BMEditMesh *em, BMFace *efa, const bool select, const bool do_history, const int cd_loop_uv_offset) { if (select) { uvedit_face_select_enable(scene, em, efa, do_history, cd_loop_uv_offset); } else { uvedit_face_select_disable(scene, em, efa, cd_loop_uv_offset); } } void uvedit_face_select_enable(const Scene *scene, BMEditMesh *em, BMFace *efa, const bool do_history, const int cd_loop_uv_offset) { const ToolSettings *ts = scene->toolsettings; if (ts->uv_flag & UV_SYNC_SELECTION) { BM_face_select_set(em->bm, efa, true); if (do_history) { BM_select_history_store(em->bm, (BMElem *)efa); } } else { BMLoop *l; MLoopUV *luv; BMIter liter; BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); luv->flag |= (MLOOPUV_VERTSEL | MLOOPUV_EDGESEL); } } } void uvedit_face_select_disable(const Scene *scene, BMEditMesh *em, BMFace *efa, const int cd_loop_uv_offset) { const ToolSettings *ts = scene->toolsettings; if (ts->uv_flag & UV_SYNC_SELECTION) { BM_face_select_set(em->bm, efa, false); } else { BMLoop *l; MLoopUV *luv; BMIter liter; BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); luv->flag &= ~(MLOOPUV_VERTSEL | MLOOPUV_EDGESEL); } } } bool uvedit_edge_select_test_ex(const ToolSettings *ts, BMLoop *l, const int cd_loop_uv_offset) { if (ts->uv_flag & UV_SYNC_SELECTION) { if (ts->selectmode & SCE_SELECT_FACE) { return BM_elem_flag_test(l->f, BM_ELEM_SELECT); } if (ts->selectmode == SCE_SELECT_EDGE) { return BM_elem_flag_test(l->e, BM_ELEM_SELECT); } return BM_elem_flag_test(l->v, BM_ELEM_SELECT) && BM_elem_flag_test(l->next->v, BM_ELEM_SELECT); } MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); if (ts->uv_selectmode & UV_SELECT_VERTEX) { MLoopUV *luv_next = BM_ELEM_CD_GET_VOID_P(l->next, cd_loop_uv_offset); return (luv->flag & MLOOPUV_VERTSEL) && (luv_next->flag & MLOOPUV_VERTSEL); } return (luv->flag & MLOOPUV_EDGESEL); } bool uvedit_edge_select_test(const Scene *scene, BMLoop *l, const int cd_loop_uv_offset) { return uvedit_edge_select_test_ex(scene->toolsettings, l, cd_loop_uv_offset); } void uvedit_edge_select_set_with_sticky(const Scene *scene, BMEditMesh *em, BMLoop *l, const bool select, const bool do_history, const uint cd_loop_uv_offset) { const ToolSettings *ts = scene->toolsettings; if (ts->uv_flag & UV_SYNC_SELECTION) { uvedit_edge_select_set(scene, em, l, select, do_history, cd_loop_uv_offset); return; } const int sticky = ts->uv_sticky; switch (sticky) { case SI_STICKY_DISABLE: { if (uvedit_face_visible_test(scene, l->f)) { uvedit_edge_select_set(scene, em, l, select, do_history, cd_loop_uv_offset); } break; } case SI_STICKY_VERTEX: { uvedit_edge_select_shared_vert( scene, em, l, select, SI_STICKY_VERTEX, do_history, cd_loop_uv_offset); break; } default: { /* SI_STICKY_LOC (Fallback) */ uvedit_edge_select_shared_vert( scene, em, l, select, SI_STICKY_LOC, do_history, cd_loop_uv_offset); break; } } } /** * Selects UV edges and shared vertices according to sticky_flag. * * \param sticky_flag: * - SI_STICKY_LOC: selects all UV edges that share the same mesh vertices and UV coordinates. * - SI_STICKY_VERTEX: selects all UV edges sharing the same mesh vertices. */ void uvedit_edge_select_shared_vert(const Scene *scene, BMEditMesh *em, BMLoop *l, const bool select, const int sticky_flag, const bool do_history, const int cd_loop_uv_offset) { BLI_assert(ELEM(sticky_flag, SI_STICKY_LOC, SI_STICKY_VERTEX)); /* Set edge flags. Rely on this for face visibility checks */ uvedit_edge_select_set_noflush(scene, l, select, sticky_flag, cd_loop_uv_offset); /* Vert selections. */ BMLoop *l_iter = l; do { MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l_iter, cd_loop_uv_offset); if (select && (luv->flag & MLOOPUV_EDGESEL)) { uvedit_uv_select_shared_vert( scene, em, l_iter, true, SI_STICKY_LOC, do_history, cd_loop_uv_offset); uvedit_uv_select_shared_vert( scene, em, l_iter->next, true, SI_STICKY_LOC, do_history, cd_loop_uv_offset); } else if (!select && !(luv->flag & MLOOPUV_EDGESEL)) { if (!uvedit_vert_is_edge_select_any_other(scene, l, cd_loop_uv_offset)) { uvedit_uv_select_shared_vert( scene, em, l_iter, false, SI_STICKY_LOC, do_history, cd_loop_uv_offset); } if (!uvedit_vert_is_edge_select_any_other(scene, l->next, cd_loop_uv_offset)) { uvedit_uv_select_shared_vert( scene, em, l_iter->next, false, SI_STICKY_LOC, do_history, cd_loop_uv_offset); } } } while (((l_iter = l_iter->radial_next) != l) && (sticky_flag != SI_STICKY_LOC)); } /* Set edge flags for required UV edges. */ void uvedit_edge_select_set_noflush(const Scene *scene, BMLoop *l, const bool select, const int sticky_flag, const int cd_loop_uv_offset) { MLoopUV *luv; BMLoop *l_iter = l; do { if (uvedit_face_visible_test(scene, l_iter->f)) { if ((sticky_flag == SI_STICKY_VERTEX) || BM_loop_uv_share_edge_check(l, l_iter, cd_loop_uv_offset)) { luv = BM_ELEM_CD_GET_VOID_P(l_iter, cd_loop_uv_offset); SET_FLAG_FROM_TEST(luv->flag, select, MLOOPUV_EDGESEL); } } } while (((l_iter = l_iter->radial_next) != l) && (sticky_flag != SI_STICKY_DISABLE)); } void uvedit_edge_select_set(const Scene *scene, BMEditMesh *em, BMLoop *l, const bool select, const bool do_history, const int cd_loop_uv_offset) { if (select) { uvedit_edge_select_enable(scene, em, l, do_history, cd_loop_uv_offset); } else { uvedit_edge_select_disable(scene, em, l, cd_loop_uv_offset); } } void uvedit_edge_select_enable(const Scene *scene, BMEditMesh *em, BMLoop *l, const bool do_history, const int cd_loop_uv_offset) { const ToolSettings *ts = scene->toolsettings; if (ts->uv_flag & UV_SYNC_SELECTION) { if (ts->selectmode & SCE_SELECT_FACE) { BM_face_select_set(em->bm, l->f, true); } else if (ts->selectmode & SCE_SELECT_EDGE) { BM_edge_select_set(em->bm, l->e, true); } else { BM_vert_select_set(em->bm, l->e->v1, true); BM_vert_select_set(em->bm, l->e->v2, true); } if (do_history) { BM_select_history_store(em->bm, (BMElem *)l->e); } } else { MLoopUV *luv, *luv_next; luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); luv_next = BM_ELEM_CD_GET_VOID_P(l->next, cd_loop_uv_offset); luv->flag |= (MLOOPUV_VERTSEL | MLOOPUV_EDGESEL); luv_next->flag |= MLOOPUV_VERTSEL; } } void uvedit_edge_select_disable(const Scene *scene, BMEditMesh *em, BMLoop *l, const int cd_loop_uv_offset) { const ToolSettings *ts = scene->toolsettings; if (ts->uv_flag & UV_SYNC_SELECTION) { if (ts->selectmode & SCE_SELECT_FACE) { BM_face_select_set(em->bm, l->f, false); } else if (ts->selectmode & SCE_SELECT_EDGE) { BM_edge_select_set(em->bm, l->e, false); } else { BM_vert_select_set(em->bm, l->e->v1, false); BM_vert_select_set(em->bm, l->e->v2, false); } } else { MLoopUV *luv, *luv_next; luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); luv_next = BM_ELEM_CD_GET_VOID_P(l->next, cd_loop_uv_offset); luv->flag &= ~MLOOPUV_EDGESEL; if ((ts->uv_selectmode & UV_SELECT_VERTEX) == 0) { /* Deselect UV vertex if not part of another edge selection */ MLoopUV *luv_prev = BM_ELEM_CD_GET_VOID_P(l->prev, cd_loop_uv_offset); if (!(luv_next->flag & MLOOPUV_EDGESEL)) { luv_next->flag &= ~MLOOPUV_VERTSEL; } if (!(luv_prev->flag & MLOOPUV_EDGESEL)) { luv->flag &= ~MLOOPUV_VERTSEL; } } else { luv_next->flag &= ~MLOOPUV_VERTSEL; luv->flag &= ~MLOOPUV_VERTSEL; } } } bool uvedit_uv_select_test_ex(const ToolSettings *ts, BMLoop *l, const int cd_loop_uv_offset) { if (ts->uv_flag & UV_SYNC_SELECTION) { if (ts->selectmode & SCE_SELECT_FACE) { return BM_elem_flag_test_bool(l->f, BM_ELEM_SELECT); } if (ts->selectmode & SCE_SELECT_EDGE) { /* Are you looking for `uvedit_edge_select_test(...)` instead? */ } return BM_elem_flag_test_bool(l->v, BM_ELEM_SELECT); } MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); if (ts->selectmode & SCE_SELECT_FACE) { /* Are you looking for `uvedit_face_select_test(...)` instead? */ } if (ts->selectmode & SCE_SELECT_EDGE) { /* Are you looking for `uvedit_edge_select_test(...)` instead? */ } return (luv->flag & MLOOPUV_VERTSEL) != 0; } bool uvedit_uv_select_test(const Scene *scene, BMLoop *l, const int cd_loop_uv_offset) { return uvedit_uv_select_test_ex(scene->toolsettings, l, cd_loop_uv_offset); } void uvedit_uv_select_set_with_sticky(const Scene *scene, BMEditMesh *em, BMLoop *l, const bool select, const bool do_history, const uint cd_loop_uv_offset) { const ToolSettings *ts = scene->toolsettings; if (ts->uv_flag & UV_SYNC_SELECTION) { uvedit_uv_select_set(scene, em, l, select, do_history, cd_loop_uv_offset); return; } const int sticky = ts->uv_sticky; switch (sticky) { case SI_STICKY_DISABLE: { if (uvedit_face_visible_test(scene, l->f)) { uvedit_uv_select_set(scene, em, l, select, do_history, cd_loop_uv_offset); } break; } case SI_STICKY_VERTEX: { uvedit_uv_select_shared_vert( scene, em, l, select, SI_STICKY_VERTEX, do_history, cd_loop_uv_offset); break; } default: { /* SI_STICKY_LOC. */ uvedit_uv_select_shared_vert( scene, em, l, select, SI_STICKY_LOC, do_history, cd_loop_uv_offset); break; } } } /** * Selects shared UVs based on #sticky_flag. * * \param sticky_flag: Type of sticky selection : * - SI_STICKY_LOC: selects all UVs sharing same mesh vertex and UV coordinates. * - SI_STICKY_VERTEX: selects all UVs sharing same mesh vertex. */ void uvedit_uv_select_shared_vert(const Scene *scene, BMEditMesh *em, BMLoop *l, const bool select, const int sticky_flag, const bool do_history, const int cd_loop_uv_offset) { BLI_assert(ELEM(sticky_flag, SI_STICKY_LOC, SI_STICKY_VERTEX)); BMEdge *e_first, *e_iter; e_first = e_iter = l->e; do { BMLoop *l_radial_iter = e_iter->l; if (!l_radial_iter) { continue; /* Skip wire edges with no loops. */ } do { if (l_radial_iter->v == l->v) { if (uvedit_face_visible_test(scene, l_radial_iter->f)) { bool do_select = false; if (sticky_flag == SI_STICKY_VERTEX) { do_select = true; } else if (BM_loop_uv_share_vert_check(l, l_radial_iter, cd_loop_uv_offset)) { do_select = true; } if (do_select) { uvedit_uv_select_set(scene, em, l_radial_iter, select, do_history, cd_loop_uv_offset); } } } } while ((l_radial_iter = l_radial_iter->radial_next) != e_iter->l); } while ((e_iter = BM_DISK_EDGE_NEXT(e_iter, l->v)) != e_first); } void uvedit_uv_select_set(const Scene *scene, BMEditMesh *em, BMLoop *l, const bool select, const bool do_history, const int cd_loop_uv_offset) { if (select) { uvedit_uv_select_enable(scene, em, l, do_history, cd_loop_uv_offset); } else { uvedit_uv_select_disable(scene, em, l, cd_loop_uv_offset); } } void uvedit_uv_select_enable(const Scene *scene, BMEditMesh *em, BMLoop *l, const bool do_history, const int cd_loop_uv_offset) { const ToolSettings *ts = scene->toolsettings; if (ts->selectmode & SCE_SELECT_EDGE) { /* Are you looking for `uvedit_edge_select_set(...)` instead? */ } if (ts->uv_flag & UV_SYNC_SELECTION) { if (ts->selectmode & SCE_SELECT_FACE) { BM_face_select_set(em->bm, l->f, true); } else { BM_vert_select_set(em->bm, l->v, true); } if (do_history) { BM_select_history_store(em->bm, (BMElem *)l->v); } } else { MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); luv->flag |= MLOOPUV_VERTSEL; } } void uvedit_uv_select_disable(const Scene *scene, BMEditMesh *em, BMLoop *l, const int cd_loop_uv_offset) { const ToolSettings *ts = scene->toolsettings; if (ts->uv_flag & UV_SYNC_SELECTION) { if (ts->selectmode & SCE_SELECT_FACE) { BM_face_select_set(em->bm, l->f, false); } else { BM_vert_select_set(em->bm, l->v, false); } } else { MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); luv->flag &= ~MLOOPUV_VERTSEL; } } static BMLoop *uvedit_loop_find_other_radial_loop_with_visible_face(const Scene *scene, BMLoop *l_src, const int cd_loop_uv_offset) { BMLoop *l_other = NULL; BMLoop *l_iter = l_src->radial_next; if (l_iter != l_src) { do { if (uvedit_face_visible_test(scene, l_iter->f) && BM_loop_uv_share_edge_check(l_src, l_iter, cd_loop_uv_offset)) { /* Check UV's are contiguous. */ if (l_other == NULL) { l_other = l_iter; } else { /* Only use when there is a single alternative. */ l_other = NULL; break; } } } while ((l_iter = l_iter->radial_next) != l_src); } return l_other; } static BMLoop *uvedit_loop_find_other_boundary_loop_with_visible_face(const Scene *scene, BMLoop *l_edge, BMVert *v_pivot, const int cd_loop_uv_offset) { BLI_assert(uvedit_loop_find_other_radial_loop_with_visible_face( scene, l_edge, cd_loop_uv_offset) == NULL); BMLoop *l_step = l_edge; l_step = (l_step->v == v_pivot) ? l_step->prev : l_step->next; BMLoop *l_step_last = NULL; do { BLI_assert(BM_vert_in_edge(l_step->e, v_pivot)); l_step_last = l_step; l_step = uvedit_loop_find_other_radial_loop_with_visible_face( scene, l_step, cd_loop_uv_offset); if (l_step) { l_step = (l_step->v == v_pivot) ? l_step->prev : l_step->next; } } while (l_step != NULL); if (l_step_last != NULL) { BLI_assert(uvedit_loop_find_other_radial_loop_with_visible_face( scene, l_step_last, cd_loop_uv_offset) == NULL); } return l_step_last; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Find Nearest Elements * \{ */ bool uv_find_nearest_edge( Scene *scene, Object *obedit, const float co[2], const float penalty, UvNearestHit *hit) { BLI_assert((hit->scale[0] > 0.0f) && (hit->scale[1] > 0.0f)); BMEditMesh *em = BKE_editmesh_from_object(obedit); BMFace *efa; BMLoop *l; BMIter iter, liter; MLoopUV *luv, *luv_next; int i; bool found = false; const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); BM_mesh_elem_index_ensure(em->bm, BM_VERT); BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, efa)) { continue; } BM_ITER_ELEM_INDEX (l, &liter, efa, BM_LOOPS_OF_FACE, i) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); luv_next = BM_ELEM_CD_GET_VOID_P(l->next, cd_loop_uv_offset); float delta[2]; closest_to_line_segment_v2(delta, co, luv->uv, luv_next->uv); sub_v2_v2(delta, co); mul_v2_v2(delta, hit->scale); float dist_test_sq = len_squared_v2(delta); /* Ensures that successive selection attempts will select other edges sharing the same * UV coordinates as the previous selection. */ if ((penalty != 0.0f) && uvedit_edge_select_test(scene, l, cd_loop_uv_offset)) { dist_test_sq = square_f(sqrtf(dist_test_sq) + penalty); } if (dist_test_sq < hit->dist_sq) { hit->ob = obedit; hit->efa = efa; hit->l = l; hit->dist_sq = dist_test_sq; found = true; } } } return found; } bool uv_find_nearest_edge_multi(Scene *scene, Object **objects, const uint objects_len, const float co[2], const float penalty, UvNearestHit *hit) { bool found = false; for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; if (uv_find_nearest_edge(scene, obedit, co, penalty, hit)) { found = true; } } return found; } bool uv_find_nearest_face_ex( Scene *scene, Object *obedit, const float co[2], UvNearestHit *hit, const bool only_in_face) { BLI_assert((hit->scale[0] > 0.0f) && (hit->scale[1] > 0.0f)); BMEditMesh *em = BKE_editmesh_from_object(obedit); bool found = false; const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); BMIter iter; BMFace *efa; BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, efa)) { continue; } float cent[2]; BM_face_uv_calc_center_median(efa, cd_loop_uv_offset, cent); float delta[2]; sub_v2_v2v2(delta, co, cent); mul_v2_v2(delta, hit->scale); const float dist_test_sq = len_squared_v2(delta); if (dist_test_sq < hit->dist_sq) { if (only_in_face) { if (!BM_face_uv_point_inside_test(efa, co, cd_loop_uv_offset)) { continue; } } hit->ob = obedit; hit->efa = efa; hit->dist_sq = dist_test_sq; found = true; } } return found; } bool uv_find_nearest_face(Scene *scene, Object *obedit, const float co[2], UvNearestHit *hit) { return uv_find_nearest_face_ex(scene, obedit, co, hit, false); } bool uv_find_nearest_face_multi_ex(Scene *scene, Object **objects, const uint objects_len, const float co[2], UvNearestHit *hit, const bool only_in_face) { bool found = false; for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; if (uv_find_nearest_face_ex(scene, obedit, co, hit, only_in_face)) { found = true; } } return found; } bool uv_find_nearest_face_multi( Scene *scene, Object **objects, const uint objects_len, const float co[2], UvNearestHit *hit) { return uv_find_nearest_face_multi_ex(scene, objects, objects_len, co, hit, false); } static bool uv_nearest_between(const BMLoop *l, const float co[2], const int cd_loop_uv_offset) { const float *uv_prev = ((MLoopUV *)BM_ELEM_CD_GET_VOID_P(l->prev, cd_loop_uv_offset))->uv; const float *uv_curr = ((MLoopUV *)BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset))->uv; const float *uv_next = ((MLoopUV *)BM_ELEM_CD_GET_VOID_P(l->next, cd_loop_uv_offset))->uv; return ((line_point_side_v2(uv_prev, uv_curr, co) > 0.0f) && (line_point_side_v2(uv_next, uv_curr, co) <= 0.0f)); } bool uv_find_nearest_vert( Scene *scene, Object *obedit, float const co[2], const float penalty_dist, UvNearestHit *hit) { BLI_assert((hit->scale[0] > 0.0f) && (hit->scale[1] > 0.0f)); bool found = false; BMEditMesh *em = BKE_editmesh_from_object(obedit); BMFace *efa; BMIter iter; BM_mesh_elem_index_ensure(em->bm, BM_VERT); const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, efa)) { continue; } BMIter liter; BMLoop *l; int i; BM_ITER_ELEM_INDEX (l, &liter, efa, BM_LOOPS_OF_FACE, i) { MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); float delta[2]; sub_v2_v2v2(delta, co, luv->uv); mul_v2_v2(delta, hit->scale); float dist_test_sq = len_squared_v2(delta); /* Ensures that successive selection attempts will select other vertices sharing the same * UV coordinates */ if ((penalty_dist != 0.0f) && uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) { dist_test_sq = square_f(sqrtf(dist_test_sq) + penalty_dist); } if (dist_test_sq <= hit->dist_sq) { if (dist_test_sq == hit->dist_sq) { if (!uv_nearest_between(l, co, cd_loop_uv_offset)) { continue; } } hit->dist_sq = dist_test_sq; hit->ob = obedit; hit->efa = efa; hit->l = l; found = true; } } } return found; } bool uv_find_nearest_vert_multi(Scene *scene, Object **objects, const uint objects_len, float const co[2], const float penalty_dist, UvNearestHit *hit) { bool found = false; for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; if (uv_find_nearest_vert(scene, obedit, co, penalty_dist, hit)) { found = true; } } return found; } static bool uvedit_nearest_uv(const Scene *scene, Object *obedit, const float co[2], const float scale[2], const bool ignore_selected, float *dist_sq, float r_uv[2]) { BMEditMesh *em = BKE_editmesh_from_object(obedit); BMIter iter; BMFace *efa; const float *uv_best = NULL; float dist_best = *dist_sq; const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, efa)) { continue; } BMLoop *l_iter, *l_first; l_iter = l_first = BM_FACE_FIRST_LOOP(efa); do { if (ignore_selected && uvedit_uv_select_test(scene, l_iter, cd_loop_uv_offset)) { continue; } const float *uv = ((const MLoopUV *)BM_ELEM_CD_GET_VOID_P(l_iter, cd_loop_uv_offset))->uv; float co_tmp[2]; mul_v2_v2v2(co_tmp, scale, uv); const float dist_test = len_squared_v2v2(co, co_tmp); if (dist_best > dist_test) { dist_best = dist_test; uv_best = uv; } } while ((l_iter = l_iter->next) != l_first); } if (uv_best != NULL) { copy_v2_v2(r_uv, uv_best); *dist_sq = dist_best; return true; } return false; } bool ED_uvedit_nearest_uv_multi(const View2D *v2d, const Scene *scene, Object **objects, const uint objects_len, const int mval[2], const bool ignore_selected, float *dist_sq, float r_uv[2]) { bool found = false; float scale[2], offset[2]; UI_view2d_scale_get(v2d, &scale[0], &scale[1]); UI_view2d_view_to_region_fl(v2d, 0.0f, 0.0f, &offset[0], &offset[1]); float co[2]; sub_v2_v2v2(co, (float[2]){UNPACK2(mval)}, offset); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; if (uvedit_nearest_uv(scene, obedit, co, scale, ignore_selected, dist_sq, r_uv)) { found = true; } } return found; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Find Nearest to Element * * These functions are quite specialized, useful when sync select is enabled * and we want to pick an active UV vertex/edge from the active element which may * have multiple UV's split out. * \{ */ BMLoop *uv_find_nearest_loop_from_vert(struct Scene *scene, struct Object *obedit, struct BMVert *v, const float co[2]) { BMEditMesh *em = BKE_editmesh_from_object(obedit); const uint cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); BMIter liter; BMLoop *l; BMLoop *l_found = NULL; float dist_best_sq = FLT_MAX; BM_ITER_ELEM (l, &liter, v, BM_LOOPS_OF_VERT) { if (!uvedit_face_visible_test(scene, l->f)) { continue; } const MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); const float dist_test_sq = len_squared_v2v2(co, luv->uv); if (dist_test_sq < dist_best_sq) { dist_best_sq = dist_test_sq; l_found = l; } } return l_found; } BMLoop *uv_find_nearest_loop_from_edge(struct Scene *scene, struct Object *obedit, struct BMEdge *e, const float co[2]) { BMEditMesh *em = BKE_editmesh_from_object(obedit); const uint cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); BMIter eiter; BMLoop *l; BMLoop *l_found = NULL; float dist_best_sq = FLT_MAX; BM_ITER_ELEM (l, &eiter, e, BM_LOOPS_OF_EDGE) { if (!uvedit_face_visible_test(scene, l->f)) { continue; } const MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); const MLoopUV *luv_next = BM_ELEM_CD_GET_VOID_P(l->next, cd_loop_uv_offset); const float dist_test_sq = dist_squared_to_line_segment_v2(co, luv->uv, luv_next->uv); if (dist_test_sq < dist_best_sq) { dist_best_sq = dist_test_sq; l_found = l; } } return l_found; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Helper functions for UV selection. * \{ */ bool uvedit_vert_is_edge_select_any_other(const Scene *scene, BMLoop *l, const int cd_loop_uv_offset) { BMEdge *e_iter = l->e; do { BMLoop *l_radial_iter = e_iter->l, *l_other; do { if (uvedit_face_visible_test(scene, l_radial_iter->f)) { /* Use #l_other to check if the uvs are connected (share the same uv coordinates) * and #l_radial_iter for the actual edge selection test. */ l_other = (l_radial_iter->v != l->v) ? l_radial_iter->next : l_radial_iter; if (BM_loop_uv_share_vert_check(l, l_other, cd_loop_uv_offset) && uvedit_edge_select_test(scene, l_radial_iter, cd_loop_uv_offset)) { return true; } } } while ((l_radial_iter = l_radial_iter->radial_next) != e_iter->l); } while ((e_iter = BM_DISK_EDGE_NEXT(e_iter, l->v)) != l->e); return false; } bool uvedit_vert_is_face_select_any_other(const Scene *scene, BMLoop *l, const int cd_loop_uv_offset) { BMIter liter; BMLoop *l_iter; BM_ITER_ELEM (l_iter, &liter, l->v, BM_LOOPS_OF_VERT) { if (!uvedit_face_visible_test(scene, l_iter->f) || (l_iter->f == l->f)) { continue; } if (BM_loop_uv_share_vert_check(l, l_iter, cd_loop_uv_offset) && uvedit_face_select_test(scene, l_iter->f, cd_loop_uv_offset)) { return true; } } return false; } bool uvedit_vert_is_all_other_faces_selected(const Scene *scene, BMLoop *l, const int cd_loop_uv_offset) { BMIter liter; BMLoop *l_iter; BM_ITER_ELEM (l_iter, &liter, l->v, BM_LOOPS_OF_VERT) { if (!uvedit_face_visible_test(scene, l_iter->f) || (l_iter->f == l->f)) { continue; } if (BM_loop_uv_share_vert_check(l, l_iter, cd_loop_uv_offset) && !uvedit_face_select_test(scene, l_iter->f, cd_loop_uv_offset)) { return false; } } return true; } /** * Clear specified UV flag (vert/edge/pinned). */ static void bm_uv_flag_clear(const Scene *scene, BMesh *bm, const int flag, const int cd_loop_uv_offset) { BMFace *efa; BMLoop *l; BMIter iter, liter; BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, efa)) { continue; } BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); luv->flag &= ~flag; } } } /** \} */ /* -------------------------------------------------------------------- */ /** \name UV Select-Mode Flushing * * \{ */ void ED_uvedit_selectmode_flush(const Scene *scene, BMEditMesh *em) { const ToolSettings *ts = scene->toolsettings; const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); BLI_assert((ts->uv_flag & UV_SYNC_SELECTION) == 0); UNUSED_VARS_NDEBUG(ts); /* Vertex Mode only. */ if (ts->uv_selectmode & UV_SELECT_VERTEX) { BMFace *efa; BMLoop *l; BMIter iter, liter; BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, efa)) { continue; } BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { MLoopUV *luv, *luv_next; luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); luv_next = BM_ELEM_CD_GET_VOID_P(l->next, cd_loop_uv_offset); if ((luv->flag & MLOOPUV_VERTSEL) && (luv_next->flag & MLOOPUV_VERTSEL)) { luv->flag |= MLOOPUV_EDGESEL; } else { luv->flag &= ~MLOOPUV_EDGESEL; } } } } } /** \} */ /* -------------------------------------------------------------------- */ /** \name UV Flush selection (up/down) * \{ */ void uvedit_select_flush(const Scene *scene, BMEditMesh *em) { /* Careful when using this in face select mode. * For face selections with sticky mode enabled, this can create invalid selection states. */ const ToolSettings *ts = scene->toolsettings; const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); BLI_assert((ts->uv_flag & UV_SYNC_SELECTION) == 0); UNUSED_VARS_NDEBUG(ts); BMFace *efa; BMLoop *l; BMIter iter, liter; BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, efa)) { continue; } BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { MLoopUV *luv, *luv_next; luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); luv_next = BM_ELEM_CD_GET_VOID_P(l->next, cd_loop_uv_offset); if ((luv->flag & MLOOPUV_VERTSEL) && (luv_next->flag & MLOOPUV_VERTSEL)) { luv->flag |= MLOOPUV_EDGESEL; } } } } void uvedit_deselect_flush(const Scene *scene, BMEditMesh *em) { const ToolSettings *ts = scene->toolsettings; const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); BLI_assert((ts->uv_flag & UV_SYNC_SELECTION) == 0); UNUSED_VARS_NDEBUG(ts); BMFace *efa; BMLoop *l; BMIter iter, liter; BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, efa)) { continue; } BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { MLoopUV *luv, *luv_next; luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); luv_next = BM_ELEM_CD_GET_VOID_P(l->next, cd_loop_uv_offset); if (luv->flag & MLOOPUV_EDGESEL) { if (!(luv->flag & MLOOPUV_VERTSEL) || !(luv_next->flag & MLOOPUV_VERTSEL)) { luv->flag &= ~MLOOPUV_EDGESEL; } } } } } /** \} */ /* -------------------------------------------------------------------- */ /** \name Edge Loop Select * \{ */ /** Mode for selecting edge loops at boundaries. */ enum eUVEdgeLoopBoundaryMode { /** Delimit at face corners (don't walk over multiple edges in the same face). */ UV_EDGE_LOOP_BOUNDARY_LOOP = 1, /** Don't delimit, walk over the all connected boundary loops. */ UV_EDGE_LOOP_BOUNDARY_ALL = 2, }; static BMLoop *bm_select_edgeloop_double_side_next(const Scene *scene, BMLoop *l_step, BMVert *v_from, const int cd_loop_uv_offset) { if (l_step->f->len == 4) { BMVert *v_from_next = BM_edge_other_vert(l_step->e, v_from); BMLoop *l_step_over = (v_from == l_step->v) ? l_step->next : l_step->prev; l_step_over = uvedit_loop_find_other_radial_loop_with_visible_face( scene, l_step_over, cd_loop_uv_offset); if (l_step_over) { return (l_step_over->v == v_from_next) ? l_step_over->prev : l_step_over->next; } } return NULL; } static BMLoop *bm_select_edgeloop_single_side_next(const Scene *scene, BMLoop *l_step, BMVert *v_from, const int cd_loop_uv_offset) { BMVert *v_from_next = BM_edge_other_vert(l_step->e, v_from); return uvedit_loop_find_other_boundary_loop_with_visible_face( scene, l_step, v_from_next, cd_loop_uv_offset); } /* TODO(campbell): support this in the BMesh API, as we have for clearing other types. */ static void bm_loop_tags_clear(BMesh *bm) { BMIter iter; BMFace *f; BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { BMIter liter; BMLoop *l_iter; BM_ITER_ELEM (l_iter, &liter, f, BM_LOOPS_OF_FACE) { BM_elem_flag_disable(l_iter, BM_ELEM_TAG); } } } /** * Tag all loops which should be selected, the caller must select. */ static void uv_select_edgeloop_double_side_tag(const Scene *scene, BMEditMesh *em, BMLoop *l_init_pair[2], const int cd_loop_uv_offset) { bm_loop_tags_clear(em->bm); for (int side = 0; side < 2; side++) { BMLoop *l_step_pair[2] = {l_init_pair[0], l_init_pair[1]}; BMVert *v_from = side ? l_step_pair[0]->e->v1 : l_step_pair[0]->e->v2; /* Disable since we start from the same edge. */ BM_elem_flag_disable(l_step_pair[0], BM_ELEM_TAG); BM_elem_flag_disable(l_step_pair[1], BM_ELEM_TAG); while ((l_step_pair[0] != NULL) && (l_step_pair[1] != NULL)) { if (!uvedit_face_visible_test(scene, l_step_pair[0]->f) || !uvedit_face_visible_test(scene, l_step_pair[1]->f) || /* Check loops have not diverged. */ (uvedit_loop_find_other_radial_loop_with_visible_face( scene, l_step_pair[0], cd_loop_uv_offset) != l_step_pair[1])) { break; } BLI_assert(l_step_pair[0]->e == l_step_pair[1]->e); BM_elem_flag_enable(l_step_pair[0], BM_ELEM_TAG); BM_elem_flag_enable(l_step_pair[1], BM_ELEM_TAG); BMVert *v_from_next = BM_edge_other_vert(l_step_pair[0]->e, v_from); /* Walk over both sides, ensure they keep on the same edge. */ for (int i = 0; i < ARRAY_SIZE(l_step_pair); i++) { l_step_pair[i] = bm_select_edgeloop_double_side_next( scene, l_step_pair[i], v_from, cd_loop_uv_offset); } if ((l_step_pair[0] && BM_elem_flag_test(l_step_pair[0], BM_ELEM_TAG)) || (l_step_pair[1] && BM_elem_flag_test(l_step_pair[1], BM_ELEM_TAG))) { break; } v_from = v_from_next; } } } /** * Tag all loops which should be selected, the caller must select. * * \param r_count_by_select: Count the number of unselected and selected loops, * this is needed to implement cycling between #eUVEdgeLoopBoundaryMode. */ static void uv_select_edgeloop_single_side_tag(const Scene *scene, BMEditMesh *em, BMLoop *l_init, const int cd_loop_uv_offset, enum eUVEdgeLoopBoundaryMode boundary_mode, int r_count_by_select[2]) { if (r_count_by_select) { r_count_by_select[0] = r_count_by_select[1] = 0; } bm_loop_tags_clear(em->bm); for (int side = 0; side < 2; side++) { BMLoop *l_step = l_init; BMVert *v_from = side ? l_step->e->v1 : l_step->e->v2; /* Disable since we start from the same edge. */ BM_elem_flag_disable(l_step, BM_ELEM_TAG); while (l_step != NULL) { if (!uvedit_face_visible_test(scene, l_step->f) || /* Check the boundary is still a boundary. */ (uvedit_loop_find_other_radial_loop_with_visible_face( scene, l_step, cd_loop_uv_offset) != NULL)) { break; } if (r_count_by_select != NULL) { r_count_by_select[uvedit_edge_select_test(scene, l_step, cd_loop_uv_offset)] += 1; /* Early exit when mixed could be optional if needed. */ if (r_count_by_select[0] && r_count_by_select[1]) { r_count_by_select[0] = r_count_by_select[1] = -1; break; } } BM_elem_flag_enable(l_step, BM_ELEM_TAG); BMVert *v_from_next = BM_edge_other_vert(l_step->e, v_from); BMFace *f_step_prev = l_step->f; l_step = bm_select_edgeloop_single_side_next(scene, l_step, v_from, cd_loop_uv_offset); if (l_step && BM_elem_flag_test(l_step, BM_ELEM_TAG)) { break; } if (boundary_mode == UV_EDGE_LOOP_BOUNDARY_LOOP) { /* Don't allow walking over the face. */ if (f_step_prev == l_step->f) { break; } } v_from = v_from_next; } } } static int uv_select_edgeloop(Scene *scene, Object *obedit, UvNearestHit *hit, const bool extend) { const ToolSettings *ts = scene->toolsettings; BMEditMesh *em = BKE_editmesh_from_object(obedit); bool select; const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); if (extend) { select = !(uvedit_edge_select_test(scene, hit->l, cd_loop_uv_offset)); } else { select = true; } BMLoop *l_init_pair[2] = { hit->l, uvedit_loop_find_other_radial_loop_with_visible_face(scene, hit->l, cd_loop_uv_offset), }; /* When selecting boundaries, support cycling between selection modes. */ enum eUVEdgeLoopBoundaryMode boundary_mode = UV_EDGE_LOOP_BOUNDARY_LOOP; /* Tag all loops that are part of the edge loop (select after). * This is done so we can */ if (l_init_pair[1] == NULL) { int count_by_select[2]; /* If the loops selected toggle the boundaries. */ uv_select_edgeloop_single_side_tag( scene, em, l_init_pair[0], cd_loop_uv_offset, boundary_mode, count_by_select); if (count_by_select[!select] == 0) { boundary_mode = UV_EDGE_LOOP_BOUNDARY_ALL; /* If the boundary is selected, toggle back to the loop. */ uv_select_edgeloop_single_side_tag( scene, em, l_init_pair[0], cd_loop_uv_offset, boundary_mode, count_by_select); if (count_by_select[!select] == 0) { boundary_mode = UV_EDGE_LOOP_BOUNDARY_LOOP; } } } if (l_init_pair[1] == NULL) { uv_select_edgeloop_single_side_tag( scene, em, l_init_pair[0], cd_loop_uv_offset, boundary_mode, NULL); } else { uv_select_edgeloop_double_side_tag(scene, em, l_init_pair, cd_loop_uv_offset); } /* Apply the selection. */ if (!extend) { uv_select_all_perform(scene, obedit, SEL_DESELECT); } /* Select all tagged loops. */ { BMIter iter; BMFace *f; BM_ITER_MESH (f, &iter, em->bm, BM_FACES_OF_MESH) { BMIter liter; BMLoop *l_iter; BM_ITER_ELEM (l_iter, &liter, f, BM_LOOPS_OF_FACE) { if (BM_elem_flag_test(l_iter, BM_ELEM_TAG)) { if (ts->uv_selectmode == UV_SELECT_VERTEX) { uvedit_uv_select_set_with_sticky(scene, em, l_iter, select, false, cd_loop_uv_offset); uvedit_uv_select_set_with_sticky( scene, em, l_iter->next, select, false, cd_loop_uv_offset); } else { uvedit_edge_select_set_with_sticky( scene, em, l_iter, select, false, cd_loop_uv_offset); } } } } } return (select) ? 1 : -1; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Face Loop Select * \{ */ static int uv_select_faceloop(Scene *scene, Object *obedit, UvNearestHit *hit, const bool extend) { BMEditMesh *em = BKE_editmesh_from_object(obedit); bool select; const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); if (!extend) { uv_select_all_perform(scene, obedit, SEL_DESELECT); } BM_mesh_elem_hflag_disable_all(em->bm, BM_FACE, BM_ELEM_TAG, false); if (extend) { select = !(uvedit_face_select_test(scene, hit->l->f, cd_loop_uv_offset)); } else { select = true; } BMLoop *l_pair[2] = { hit->l, uvedit_loop_find_other_radial_loop_with_visible_face(scene, hit->l, cd_loop_uv_offset), }; for (int side = 0; side < 2; side++) { BMLoop *l_step = l_pair[side]; while (l_step) { if (!uvedit_face_visible_test(scene, l_step->f)) { break; } uvedit_face_select_set_with_sticky(scene, em, l_step->f, select, false, cd_loop_uv_offset); BM_elem_flag_enable(l_step->f, BM_ELEM_TAG); if (l_step->f->len == 4) { BMLoop *l_step_opposite = l_step->next->next; l_step = uvedit_loop_find_other_radial_loop_with_visible_face( scene, l_step_opposite, cd_loop_uv_offset); } else { l_step = NULL; } /* Break iteration when `l_step`: * - is the first loop where we started from. * - tagged using #BM_ELEM_TAG (meaning this loop has been visited in this iteration). */ if (l_step && BM_elem_flag_test(l_step->f, BM_ELEM_TAG)) { break; } } } return (select) ? 1 : -1; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Edge Ring Select * \{ */ static int uv_select_edgering(Scene *scene, Object *obedit, UvNearestHit *hit, const bool extend) { const ToolSettings *ts = scene->toolsettings; BMEditMesh *em = BKE_editmesh_from_object(obedit); const bool use_face_select = (ts->uv_flag & UV_SYNC_SELECTION) ? (ts->selectmode & SCE_SELECT_FACE) : (ts->uv_selectmode & UV_SELECT_FACE); const bool use_vertex_select = (ts->uv_flag & UV_SYNC_SELECTION) ? (ts->selectmode & SCE_SELECT_VERTEX) : (ts->uv_selectmode & UV_SELECT_VERTEX); bool select; const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); if (!extend) { uv_select_all_perform(scene, obedit, SEL_DESELECT); } BM_mesh_elem_hflag_disable_all(em->bm, BM_EDGE, BM_ELEM_TAG, false); if (extend) { select = !(uvedit_edge_select_test(scene, hit->l, cd_loop_uv_offset)); } else { select = true; } BMLoop *l_pair[2] = { hit->l, uvedit_loop_find_other_radial_loop_with_visible_face(scene, hit->l, cd_loop_uv_offset), }; for (int side = 0; side < 2; side++) { BMLoop *l_step = l_pair[side]; /* Disable since we start from the same edge. */ BM_elem_flag_disable(hit->l->e, BM_ELEM_TAG); while (l_step) { if (!uvedit_face_visible_test(scene, l_step->f)) { break; } if (use_face_select) { /* While selecting face loops is now done in a separate function #uv_select_faceloop(), * this check is still kept for edge ring selection, to keep it consistent with how edge * ring selection works in face mode in the 3D viewport. */ uvedit_face_select_set_with_sticky(scene, em, l_step->f, select, false, cd_loop_uv_offset); } else if (use_vertex_select) { uvedit_uv_select_set_with_sticky(scene, em, l_step, select, false, cd_loop_uv_offset); uvedit_uv_select_set_with_sticky( scene, em, l_step->next, select, false, cd_loop_uv_offset); } else { /* Edge select mode */ uvedit_edge_select_set_with_sticky(scene, em, l_step, select, false, cd_loop_uv_offset); } BM_elem_flag_enable(l_step->e, BM_ELEM_TAG); if (l_step->f->len == 4) { BMLoop *l_step_opposite = l_step->next->next; l_step = uvedit_loop_find_other_radial_loop_with_visible_face( scene, l_step_opposite, cd_loop_uv_offset); if (l_step == NULL) { /* Ensure we touch the opposite edge if we can't walk over it. */ l_step = l_step_opposite; } } else { l_step = NULL; } /* Break iteration when `l_step`: * - Is the first loop where we started from. * - Tagged using #BM_ELEM_TAG (meaning this loop has been visited in this iteration). * - Has its corresponding UV edge selected/unselected based on #select. */ if (l_step && BM_elem_flag_test(l_step->e, BM_ELEM_TAG)) { /* Previously this check was not done and this resulted in the final edge in the edge ring * cycle to be skipped during selection (caused by old sticky selection behavior). */ if (select && uvedit_edge_select_test(scene, l_step, cd_loop_uv_offset)) { break; } if (!select && !uvedit_edge_select_test(scene, l_step, cd_loop_uv_offset)) { break; } } } } return (select) ? 1 : -1; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Select Linked * \{ */ static void uv_select_linked_multi(Scene *scene, Object **objects, const uint objects_len, UvNearestHit *hit, const bool extend, bool deselect, const bool toggle, const bool select_faces) { const bool uv_sync_select = (scene->toolsettings->uv_flag & UV_SYNC_SELECTION); /* loop over objects, or just use hit->ob */ for (uint ob_index = 0; ob_index < objects_len; ob_index++) { if (hit && ob_index != 0) { break; } Object *obedit = hit ? hit->ob : objects[ob_index]; BMFace *efa; BMLoop *l; BMIter iter, liter; UvVertMap *vmap; UvMapVert *vlist, *iterv, *startv; int i, stacksize = 0, *stack; uint a; char *flag; BMEditMesh *em = BKE_editmesh_from_object(obedit); const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); BM_mesh_elem_table_ensure(em->bm, BM_FACE); /* we can use this too */ /* NOTE: we had 'use winding' so we don't consider overlapping islands as connected, see T44320 * this made *every* projection split the island into front/back islands. * Keep 'use_winding' to false, see: T50970. * * Better solve this by having a delimit option for select-linked operator, * keeping island-select working as is. */ vmap = BM_uv_vert_map_create(em->bm, !uv_sync_select, false); if (vmap == NULL) { continue; } stack = MEM_mallocN(sizeof(*stack) * (em->bm->totface + 1), "UvLinkStack"); flag = MEM_callocN(sizeof(*flag) * em->bm->totface, "UvLinkFlag"); if (hit == NULL) { /* Use existing selection */ BM_ITER_MESH_INDEX (efa, &iter, em->bm, BM_FACES_OF_MESH, a) { if (uvedit_face_visible_test(scene, efa)) { if (select_faces) { if (BM_elem_flag_test(efa, BM_ELEM_SELECT)) { stack[stacksize] = a; stacksize++; flag[a] = 1; } } else { BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { if (uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) { bool add_to_stack = true; if (uv_sync_select) { /* Special case, vertex/edge & sync select being enabled. * * Without this, a second linked select will 'grow' each time as each new * selection reaches the boundaries of islands that share vertices but not UV's. * * Rules applied here: * - This loops face isn't selected. * - The only other fully selected face is connected or, * - There are no connected fully selected faces UV-connected to this loop. */ BLI_assert(!select_faces); if (uvedit_face_select_test(scene, l->f, cd_loop_uv_offset)) { /* pass */ } else { BMIter liter_other; BMLoop *l_other; BM_ITER_ELEM (l_other, &liter_other, l->v, BM_LOOPS_OF_VERT) { if ((l != l_other) && !BM_loop_uv_share_vert_check(l, l_other, cd_loop_uv_offset) && uvedit_face_select_test(scene, l_other->f, cd_loop_uv_offset)) { add_to_stack = false; break; } } } } if (add_to_stack) { stack[stacksize] = a; stacksize++; flag[a] = 1; break; } } } } } } } else { BM_ITER_MESH_INDEX (efa, &iter, em->bm, BM_FACES_OF_MESH, a) { if (efa == hit->efa) { stack[stacksize] = a; stacksize++; flag[a] = 1; break; } } } while (stacksize > 0) { stacksize--; a = stack[stacksize]; efa = BM_face_at_index(em->bm, a); BM_ITER_ELEM_INDEX (l, &liter, efa, BM_LOOPS_OF_FACE, i) { /* make_uv_vert_map_EM sets verts tmp.l to the indices */ vlist = BM_uv_vert_map_at_index(vmap, BM_elem_index_get(l->v)); startv = vlist; for (iterv = vlist; iterv; iterv = iterv->next) { if (iterv->separate) { startv = iterv; } if (iterv->poly_index == a) { break; } } for (iterv = startv; iterv; iterv = iterv->next) { if ((startv != iterv) && (iterv->separate)) { break; } if (!flag[iterv->poly_index]) { flag[iterv->poly_index] = 1; stack[stacksize] = iterv->poly_index; stacksize++; } } } } /* Toggling - if any of the linked vertices is selected (and visible), we deselect. */ if ((toggle == true) && (extend == false) && (deselect == false)) { BM_ITER_MESH_INDEX (efa, &iter, em->bm, BM_FACES_OF_MESH, a) { bool found_selected = false; if (!flag[a]) { continue; } if (select_faces) { if (BM_elem_flag_test(efa, BM_ELEM_SELECT) && !BM_elem_flag_test(efa, BM_ELEM_HIDDEN)) { found_selected = true; } } else { BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { if (uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) { found_selected = true; break; } } if (found_selected) { deselect = true; break; } } } } #define SET_SELECTION(value) \ if (select_faces) { \ BM_face_select_set(em->bm, efa, value); \ } \ else { \ uvedit_face_select_set(scene, em, efa, value, false, cd_loop_uv_offset); \ } \ (void)0 BM_ITER_MESH_INDEX (efa, &iter, em->bm, BM_FACES_OF_MESH, a) { if (!flag[a]) { if (!extend && !deselect && !toggle) { SET_SELECTION(false); } continue; } if (!deselect) { SET_SELECTION(true); } else { SET_SELECTION(false); } } #undef SET_SELECTION MEM_freeN(stack); MEM_freeN(flag); BM_uv_vert_map_free(vmap); if (uv_sync_select) { if (deselect) { EDBM_deselect_flush(em); } else { if (!select_faces) { EDBM_selectmode_flush(em); } } } } } const float *uvedit_first_selected_uv_from_vertex(Scene *scene, BMVert *eve, const int cd_loop_uv_offset) { BMIter liter; BMLoop *l; BM_ITER_ELEM (l, &liter, eve, BM_LOOPS_OF_VERT) { if (!uvedit_face_visible_test(scene, l->f)) { continue; } if (uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) { MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); return luv->uv; } } return NULL; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Select More/Less Operator * \{ */ static int uv_select_more_less(bContext *C, const bool select) { Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); BMFace *efa; BMLoop *l; BMIter iter, liter; const ToolSettings *ts = scene->toolsettings; uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( view_layer, ((View3D *)NULL), &objects_len); const bool is_uv_face_selectmode = (ts->uv_selectmode == UV_SELECT_FACE); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); bool changed = false; const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); if (ts->uv_flag & UV_SYNC_SELECTION) { if (select) { EDBM_select_more(em, true); } else { EDBM_select_less(em, true); } DEG_id_tag_update(obedit->data, ID_RECALC_SELECT); WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); continue; } if (is_uv_face_selectmode) { /* clear tags */ BM_mesh_elem_hflag_disable_all(em->bm, BM_FACE, BM_ELEM_TAG, false); /* mark loops to be selected */ BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (uvedit_face_visible_test(scene, efa)) { if (select) { #define NEIGHBORING_FACE_IS_SEL 1 #define CURR_FACE_IS_UNSEL 2 int sel_state = 0; BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); if (luv->flag & MLOOPUV_VERTSEL) { sel_state |= NEIGHBORING_FACE_IS_SEL; } else { sel_state |= CURR_FACE_IS_UNSEL; } if (!(luv->flag & MLOOPUV_EDGESEL)) { sel_state |= CURR_FACE_IS_UNSEL; } /* If the current face is not selected and at least one neighboring face is * selected, then tag the current face to grow selection. */ if (sel_state == (NEIGHBORING_FACE_IS_SEL | CURR_FACE_IS_UNSEL)) { BM_elem_flag_enable(efa, BM_ELEM_TAG); changed = true; break; } } #undef NEIGHBORING_FACE_IS_SEL #undef CURR_FACE_IS_UNSEL } else { if (!uvedit_face_select_test(scene, efa, cd_loop_uv_offset)) { continue; } BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { /* Deselect face when at least one of the surrounding faces is not selected */ if (!uvedit_vert_is_all_other_faces_selected(scene, l, cd_loop_uv_offset)) { BM_elem_flag_enable(efa, BM_ELEM_TAG); changed = true; break; } } } } } } else { /* clear tags */ BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { BM_elem_flag_disable(l, BM_ELEM_TAG); } } /* mark loops to be selected */ BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (uvedit_face_visible_test(scene, efa)) { BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); if (((luv->flag & MLOOPUV_VERTSEL) != 0) == select) { BM_elem_flag_enable(l->next, BM_ELEM_TAG); BM_elem_flag_enable(l->prev, BM_ELEM_TAG); changed = true; } } } } } if (changed) { if (is_uv_face_selectmode) { /* Select tagged faces. */ uv_select_flush_from_tag_face(scene, obedit, select); } else { /* Select tagged loops. */ uv_select_flush_from_tag_loop(scene, obedit, select); /* Set/unset edge flags based on selected verts. */ if (select) { uvedit_select_flush(scene, em); } else { uvedit_deselect_flush(scene, 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; } static int uv_select_more_exec(bContext *C, wmOperator *UNUSED(op)) { return uv_select_more_less(C, true); } void UV_OT_select_more(wmOperatorType *ot) { /* identifiers */ ot->name = "Select More"; ot->description = "Select more UV vertices connected to initial selection"; ot->idname = "UV_OT_select_more"; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* api callbacks */ ot->exec = uv_select_more_exec; ot->poll = ED_operator_uvedit_space_image; } static int uv_select_less_exec(bContext *C, wmOperator *UNUSED(op)) { return uv_select_more_less(C, false); } void UV_OT_select_less(wmOperatorType *ot) { /* identifiers */ ot->name = "Select Less"; ot->description = "Deselect UV vertices at the boundary of each selection region"; ot->idname = "UV_OT_select_less"; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* api callbacks */ ot->exec = uv_select_less_exec; ot->poll = ED_operator_uvedit_space_image; } /** \} */ /* -------------------------------------------------------------------- */ /** \name (De)Select All Operator * \{ */ bool uvedit_select_is_any_selected(const Scene *scene, Object *obedit) { const ToolSettings *ts = scene->toolsettings; BMEditMesh *em = BKE_editmesh_from_object(obedit); BMFace *efa; BMLoop *l; BMIter iter, liter; MLoopUV *luv; if (ts->uv_flag & UV_SYNC_SELECTION) { return (em->bm->totvertsel || em->bm->totedgesel || em->bm->totfacesel); } const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, efa)) { continue; } BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); if (luv->flag & MLOOPUV_VERTSEL) { return true; } } } return false; } bool uvedit_select_is_any_selected_multi(const Scene *scene, Object **objects, const uint objects_len) { bool found = false; for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; if (uvedit_select_is_any_selected(scene, obedit)) { found = true; break; } } return found; } static void uv_select_all(const Scene *scene, BMEditMesh *em, bool select_all) { BMFace *efa; BMLoop *l; BMIter iter, liter; MLoopUV *luv; const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); const int uv_select_flags = (MLOOPUV_VERTSEL | MLOOPUV_EDGESEL); BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, efa)) { continue; } BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); SET_FLAG_FROM_TEST(luv->flag, select_all, uv_select_flags); } } } static void uv_select_invert(const Scene *scene, BMEditMesh *em) { const ToolSettings *ts = scene->toolsettings; BLI_assert((ts->uv_flag & UV_SYNC_SELECTION) == 0); const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); BMFace *efa; BMLoop *l; BMIter iter, liter; MLoopUV *luv; char uv_selectmode = ts->uv_selectmode; BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, efa)) { continue; } BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); if (ELEM(uv_selectmode, UV_SELECT_EDGE, UV_SELECT_FACE)) { /* Use #MLOOPUV_EDGESEL to flag edges that must be selected. */ luv->flag ^= MLOOPUV_EDGESEL; luv->flag &= ~MLOOPUV_VERTSEL; } /* Use #MLOOPUV_VERTSEL to flag verts that must be selected. */ else if (ELEM(uv_selectmode, UV_SELECT_VERTEX, UV_SELECT_ISLAND)) { luv->flag ^= MLOOPUV_VERTSEL; luv->flag &= ~MLOOPUV_EDGESEL; } } } /* Flush based on uv vert/edge flags and current UV select mode */ if (ELEM(uv_selectmode, UV_SELECT_EDGE, UV_SELECT_FACE)) { uv_select_flush_from_loop_edge_flag(scene, em); } else if (ELEM(uv_selectmode, UV_SELECT_VERTEX, UV_SELECT_ISLAND)) { uvedit_select_flush(scene, em); } } static void uv_select_all_perform(const Scene *scene, Object *obedit, int action) { const ToolSettings *ts = scene->toolsettings; BMEditMesh *em = BKE_editmesh_from_object(obedit); if (action == SEL_TOGGLE) { action = uvedit_select_is_any_selected(scene, obedit) ? SEL_DESELECT : SEL_SELECT; } if (ts->uv_flag & UV_SYNC_SELECTION) { switch (action) { case SEL_TOGGLE: EDBM_select_toggle_all(em); break; 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; } } else { switch (action) { case SEL_SELECT: uv_select_all(scene, em, true); break; case SEL_DESELECT: uv_select_all(scene, em, false); break; case SEL_INVERT: uv_select_invert(scene, em); break; } } } static void uv_select_all_perform_multi_ex(const Scene *scene, Object **objects, const uint objects_len, int action, const Object *ob_exclude) { if (action == SEL_TOGGLE) { action = uvedit_select_is_any_selected_multi(scene, objects, objects_len) ? SEL_DESELECT : SEL_SELECT; } for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; if (ob_exclude && (obedit == ob_exclude)) { continue; } uv_select_all_perform(scene, obedit, action); } } static void uv_select_all_perform_multi(const Scene *scene, Object **objects, const uint objects_len, int action) { uv_select_all_perform_multi_ex(scene, objects, objects_len, action, NULL); } static int uv_select_all_exec(bContext *C, wmOperator *op) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); Scene *scene = CTX_data_scene(C); const ToolSettings *ts = scene->toolsettings; 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_with_uvs( view_layer, ((View3D *)NULL), &objects_len); uv_select_all_perform_multi(scene, objects, objects_len, action); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; uv_select_tag_update_for_object(depsgraph, ts, obedit); } MEM_freeN(objects); return OPERATOR_FINISHED; } void UV_OT_select_all(wmOperatorType *ot) { /* identifiers */ ot->name = "(De)select All"; ot->description = "Change selection of all UV vertices"; ot->idname = "UV_OT_select_all"; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* api callbacks */ ot->exec = uv_select_all_exec; ot->poll = ED_operator_uvedit; WM_operator_properties_select_all(ot); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Mouse Select Operator * \{ */ static bool uv_mouse_select_multi(bContext *C, Object **objects, uint objects_len, const float co[2], const struct SelectPick_Params *params) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); const ARegion *region = CTX_wm_region(C); Scene *scene = CTX_data_scene(C); const ToolSettings *ts = scene->toolsettings; UvNearestHit hit = UV_NEAREST_HIT_INIT_DIST_PX(®ion->v2d, 75.0f); int selectmode, sticky; bool found_item = false; /* 0 == don't flush, 1 == sel, -1 == deselect; only use when selection sync is enabled. */ int flush = 0; /* Penalty (in pixels) applied to elements that are already selected * so elements that aren't already selected are prioritized. */ const float penalty_dist = 3.0f * U.pixelsize; /* retrieve operation mode */ if (ts->uv_flag & UV_SYNC_SELECTION) { if (ts->selectmode & SCE_SELECT_FACE) { selectmode = UV_SELECT_FACE; } else if (ts->selectmode & SCE_SELECT_EDGE) { selectmode = UV_SELECT_EDGE; } else { selectmode = UV_SELECT_VERTEX; } sticky = SI_STICKY_DISABLE; } else { selectmode = ts->uv_selectmode; sticky = ts->uv_sticky; } /* find nearest element */ if (selectmode == UV_SELECT_VERTEX) { /* find vertex */ found_item = uv_find_nearest_vert_multi(scene, objects, objects_len, co, penalty_dist, &hit); if (found_item) { if ((ts->uv_flag & UV_SYNC_SELECTION) == 0) { BMesh *bm = BKE_editmesh_from_object(hit.ob)->bm; ED_uvedit_active_vert_loop_set(bm, hit.l); } } } else if (selectmode == UV_SELECT_EDGE) { /* find edge */ found_item = uv_find_nearest_edge_multi(scene, objects, objects_len, co, penalty_dist, &hit); if (found_item) { if ((ts->uv_flag & UV_SYNC_SELECTION) == 0) { BMesh *bm = BKE_editmesh_from_object(hit.ob)->bm; ED_uvedit_active_edge_loop_set(bm, hit.l); } } } else if (selectmode == UV_SELECT_FACE) { /* find face */ found_item = uv_find_nearest_face_multi(scene, objects, objects_len, co, &hit); if (!found_item) { /* Fallback, perform a second pass without a limited threshold, * which succeeds as long as the cursor is inside the UV face. * Useful when zoomed in, to select faces with distant screen-space face centers. */ hit.dist_sq = FLT_MAX; found_item = uv_find_nearest_face_multi_ex(scene, objects, objects_len, co, &hit, true); } if (found_item) { BMesh *bm = BKE_editmesh_from_object(hit.ob)->bm; BM_mesh_active_face_set(bm, hit.efa); } } else if (selectmode == UV_SELECT_ISLAND) { found_item = uv_find_nearest_edge_multi(scene, objects, objects_len, co, 0.0f, &hit); if (!found_item) { /* Without this, we can be within the face of an island but too far from an edge, * see face selection comment for details. */ hit.dist_sq = FLT_MAX; found_item = uv_find_nearest_face_multi_ex(scene, objects, objects_len, co, &hit, true); } } bool found = found_item; bool changed = false; bool is_selected = false; if (found) { Object *obedit = hit.ob; BMEditMesh *em = BKE_editmesh_from_object(obedit); const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); if (selectmode == UV_SELECT_FACE) { is_selected = uvedit_face_select_test(scene, hit.efa, cd_loop_uv_offset); } else if (selectmode == UV_SELECT_EDGE) { is_selected = uvedit_edge_select_test(scene, hit.l, cd_loop_uv_offset); } else { /* Vertex or island. For island (if we were using #uv_find_nearest_face_multi_ex, see above), * `hit.l` is NULL, use `hit.efa` instead. */ if (hit.l != NULL) { is_selected = uvedit_uv_select_test(scene, hit.l, cd_loop_uv_offset); } else { is_selected = uvedit_face_select_test(scene, hit.efa, cd_loop_uv_offset); } } } if (params->sel_op == SEL_OP_SET) { if ((found && params->select_passthrough) && is_selected) { found = false; } else if (found || params->deselect_all) { /* Deselect everything. */ uv_select_all_perform_multi(scene, objects, objects_len, SEL_DESELECT); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; uv_select_tag_update_for_object(depsgraph, ts, obedit); } changed = true; } } if (found) { Object *obedit = hit.ob; BMEditMesh *em = BKE_editmesh_from_object(obedit); const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); if (selectmode == UV_SELECT_ISLAND) { const bool extend = params->sel_op == SEL_OP_ADD; const bool deselect = params->sel_op == SEL_OP_SUB; const bool toggle = params->sel_op == SEL_OP_XOR; /* Current behavior of 'extend' * is actually toggling, so pass extend flag as 'toggle' here */ uv_select_linked_multi(scene, objects, objects_len, &hit, extend, deselect, toggle, false); /* TODO: check if this actually changed. */ changed = true; } else { BLI_assert(ELEM(selectmode, UV_SELECT_VERTEX, UV_SELECT_EDGE, UV_SELECT_FACE)); bool select_value = false; switch (params->sel_op) { case SEL_OP_ADD: { select_value = true; break; } case SEL_OP_SUB: { select_value = false; break; } case SEL_OP_XOR: { select_value = !is_selected; break; } case SEL_OP_SET: { /* Deselect has already been performed. */ select_value = true; break; } case SEL_OP_AND: { BLI_assert_unreachable(); /* Doesn't make sense for picking. */ break; } } if (selectmode == UV_SELECT_FACE) { uvedit_face_select_set_with_sticky( scene, em, hit.efa, select_value, true, cd_loop_uv_offset); flush = 1; } else if (selectmode == UV_SELECT_EDGE) { uvedit_edge_select_set_with_sticky( scene, em, hit.l, select_value, true, cd_loop_uv_offset); flush = 1; } else if (selectmode == UV_SELECT_VERTEX) { uvedit_uv_select_set_with_sticky(scene, em, hit.l, select_value, true, cd_loop_uv_offset); flush = 1; } else { BLI_assert_unreachable(); } /* De-selecting an edge may deselect a face too - validate. */ if (ts->uv_flag & UV_SYNC_SELECTION) { if (select_value == false) { BM_select_history_validate(em->bm); } } /* (de)select sticky UV nodes. */ if (sticky != SI_STICKY_DISABLE) { flush = select_value ? 1 : -1; } changed = true; } if (ts->uv_flag & UV_SYNC_SELECTION) { if (flush != 0) { EDBM_selectmode_flush(em); } } else { /* Setting the selection implies a single element, which doesn't need to be flushed. */ if (params->sel_op != SEL_OP_SET) { ED_uvedit_selectmode_flush(scene, em); } } } if (changed && found) { /* Only update the `hit` object as de-selecting all will have refreshed the others. */ Object *obedit = hit.ob; uv_select_tag_update_for_object(depsgraph, ts, obedit); } return changed || found; } static bool uv_mouse_select(bContext *C, const float co[2], const struct SelectPick_Params *params) { 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_with_uvs( view_layer, ((View3D *)NULL), &objects_len); bool changed = uv_mouse_select_multi(C, objects, objects_len, co, params); MEM_freeN(objects); return changed; } static int uv_select_exec(bContext *C, wmOperator *op) { float co[2]; RNA_float_get_array(op->ptr, "location", co); struct SelectPick_Params params = {0}; ED_select_pick_params_from_operator(op->ptr, ¶ms); const bool changed = uv_mouse_select(C, co, ¶ms); if (changed) { return OPERATOR_FINISHED | OPERATOR_PASS_THROUGH; } return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; } static int uv_select_invoke(bContext *C, wmOperator *op, const wmEvent *event) { const ARegion *region = CTX_wm_region(C); float co[2]; UI_view2d_region_to_view(®ion->v2d, event->mval[0], event->mval[1], &co[0], &co[1]); RNA_float_set_array(op->ptr, "location", co); const int retval = uv_select_exec(C, op); return WM_operator_flag_only_pass_through_on_press(retval, event); } void UV_OT_select(wmOperatorType *ot) { /* identifiers */ ot->name = "Select"; ot->description = "Select UV vertices"; ot->idname = "UV_OT_select"; ot->flag = OPTYPE_UNDO; /* api callbacks */ ot->exec = uv_select_exec; ot->invoke = uv_select_invoke; ot->poll = ED_operator_uvedit; /* requires space image */ ot->get_name = ED_select_pick_get_name; /* properties */ PropertyRNA *prop; WM_operator_properties_mouse_select(ot); prop = RNA_def_float_vector( ot->srna, "location", 2, NULL, -FLT_MAX, FLT_MAX, "Location", "Mouse location in normalized coordinates, 0.0 to 1.0 is within the image bounds", -100.0f, 100.0f); RNA_def_property_flag(prop, PROP_SKIP_SAVE); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Shared Edge Loop/Ring Select Operator Functions * \{ */ enum eUVLoopGenericType { UV_LOOP_SELECT = 1, UV_RING_SELECT = 2, }; static int uv_mouse_select_loop_generic_multi(bContext *C, Object **objects, uint objects_len, const float co[2], const bool extend, enum eUVLoopGenericType loop_type) { const ARegion *region = CTX_wm_region(C); Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); Scene *scene = CTX_data_scene(C); const ToolSettings *ts = scene->toolsettings; UvNearestHit hit = UV_NEAREST_HIT_INIT_MAX(®ion->v2d); bool found_item = false; /* 0 == don't flush, 1 == sel, -1 == deselect; only use when selection sync is enabled. */ int flush = 0; /* Find edge. */ found_item = uv_find_nearest_edge_multi(scene, objects, objects_len, co, 0.0f, &hit); if (!found_item) { return OPERATOR_CANCELLED; } Object *obedit = hit.ob; BMEditMesh *em = BKE_editmesh_from_object(obedit); /* Do selection. */ if (!extend) { uv_select_all_perform_multi_ex(scene, objects, objects_len, SEL_DESELECT, obedit); } if (loop_type == UV_LOOP_SELECT) { if (ED_uvedit_select_mode_get(scene) == UV_SELECT_FACE) { flush = uv_select_faceloop(scene, obedit, &hit, extend); } else { flush = uv_select_edgeloop(scene, obedit, &hit, extend); } } else if (loop_type == UV_RING_SELECT) { flush = uv_select_edgering(scene, obedit, &hit, extend); } else { BLI_assert_unreachable(); } if (ts->uv_flag & UV_SYNC_SELECTION) { if (flush == 1) { EDBM_select_flush(em); } else if (flush == -1) { EDBM_deselect_flush(em); } } else { ED_uvedit_selectmode_flush(scene, em); } for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obiter = objects[ob_index]; uv_select_tag_update_for_object(depsgraph, ts, obiter); } return OPERATOR_PASS_THROUGH | OPERATOR_FINISHED; } static int uv_mouse_select_loop_generic(bContext *C, const float co[2], const bool extend, enum eUVLoopGenericType loop_type) { 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_with_uvs( view_layer, ((View3D *)NULL), &objects_len); int ret = uv_mouse_select_loop_generic_multi(C, objects, objects_len, co, extend, loop_type); MEM_freeN(objects); return ret; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Edge Loop Select Operator * \{ */ static int uv_select_loop_exec(bContext *C, wmOperator *op) { float co[2]; RNA_float_get_array(op->ptr, "location", co); const bool extend = RNA_boolean_get(op->ptr, "extend"); return uv_mouse_select_loop_generic(C, co, extend, UV_LOOP_SELECT); } static int uv_select_loop_invoke(bContext *C, wmOperator *op, const wmEvent *event) { const ARegion *region = CTX_wm_region(C); float co[2]; UI_view2d_region_to_view(®ion->v2d, event->mval[0], event->mval[1], &co[0], &co[1]); RNA_float_set_array(op->ptr, "location", co); const int retval = uv_select_loop_exec(C, op); return WM_operator_flag_only_pass_through_on_press(retval, event); } void UV_OT_select_loop(wmOperatorType *ot) { /* identifiers */ ot->name = "Loop Select"; ot->description = "Select a loop of connected UV vertices"; ot->idname = "UV_OT_select_loop"; ot->flag = OPTYPE_UNDO; /* api callbacks */ ot->exec = uv_select_loop_exec; ot->invoke = uv_select_loop_invoke; ot->poll = ED_operator_uvedit; /* requires space image */ /* properties */ PropertyRNA *prop; prop = RNA_def_boolean(ot->srna, "extend", 0, "Extend", "Extend selection rather than clearing the existing selection"); RNA_def_property_flag(prop, PROP_SKIP_SAVE); prop = RNA_def_float_vector( ot->srna, "location", 2, NULL, -FLT_MAX, FLT_MAX, "Location", "Mouse location in normalized coordinates, 0.0 to 1.0 is within the image bounds", -100.0f, 100.0f); RNA_def_property_flag(prop, PROP_SKIP_SAVE); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Edge Ring Select Operator * \{ */ static int uv_select_edge_ring_exec(bContext *C, wmOperator *op) { float co[2]; RNA_float_get_array(op->ptr, "location", co); const bool extend = RNA_boolean_get(op->ptr, "extend"); return uv_mouse_select_loop_generic(C, co, extend, UV_RING_SELECT); } static int uv_select_edge_ring_invoke(bContext *C, wmOperator *op, const wmEvent *event) { const ARegion *region = CTX_wm_region(C); float co[2]; UI_view2d_region_to_view(®ion->v2d, event->mval[0], event->mval[1], &co[0], &co[1]); RNA_float_set_array(op->ptr, "location", co); const int retval = uv_select_edge_ring_exec(C, op); return WM_operator_flag_only_pass_through_on_press(retval, event); } void UV_OT_select_edge_ring(wmOperatorType *ot) { /* identifiers */ ot->name = "Edge Ring Select"; ot->description = "Select an edge ring of connected UV vertices"; ot->idname = "UV_OT_select_edge_ring"; ot->flag = OPTYPE_UNDO; /* api callbacks */ ot->exec = uv_select_edge_ring_exec; ot->invoke = uv_select_edge_ring_invoke; ot->poll = ED_operator_uvedit; /* requires space image */ /* properties */ PropertyRNA *prop; prop = RNA_def_boolean(ot->srna, "extend", 0, "Extend", "Extend selection rather than clearing the existing selection"); RNA_def_property_flag(prop, PROP_SKIP_SAVE); prop = RNA_def_float_vector( ot->srna, "location", 2, NULL, -FLT_MAX, FLT_MAX, "Location", "Mouse location in normalized coordinates, 0.0 to 1.0 is within the image bounds", -100.0f, 100.0f); RNA_def_property_flag(prop, PROP_SKIP_SAVE); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Select Linked Operator * \{ */ static int uv_select_linked_internal(bContext *C, wmOperator *op, const wmEvent *event, bool pick) { const ARegion *region = CTX_wm_region(C); Scene *scene = CTX_data_scene(C); const ToolSettings *ts = scene->toolsettings; ViewLayer *view_layer = CTX_data_view_layer(C); bool extend = true; bool deselect = false; bool select_faces = (ts->uv_flag & UV_SYNC_SELECTION) && (ts->selectmode & SCE_SELECT_FACE); UvNearestHit hit = UV_NEAREST_HIT_INIT_MAX(®ion->v2d); if (pick) { extend = RNA_boolean_get(op->ptr, "extend"); deselect = RNA_boolean_get(op->ptr, "deselect"); } uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( view_layer, ((View3D *)NULL), &objects_len); if (pick) { float co[2]; if (event) { /* invoke */ UI_view2d_region_to_view(®ion->v2d, event->mval[0], event->mval[1], &co[0], &co[1]); RNA_float_set_array(op->ptr, "location", co); } else { /* exec */ RNA_float_get_array(op->ptr, "location", co); } if (!uv_find_nearest_edge_multi(scene, objects, objects_len, co, 0.0f, &hit)) { MEM_freeN(objects); return OPERATOR_CANCELLED; } } if (!extend && !deselect) { uv_select_all_perform_multi(scene, objects, objects_len, SEL_DESELECT); } uv_select_linked_multi( scene, objects, objects_len, pick ? &hit : NULL, extend, deselect, false, select_faces); /* weak!, but works */ Object **objects_free = objects; if (pick) { objects = &hit.ob; objects_len = 1; } for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; DEG_id_tag_update(obedit->data, ID_RECALC_COPY_ON_WRITE | ID_RECALC_SELECT); WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); } MEM_SAFE_FREE(objects_free); return OPERATOR_FINISHED; } static int uv_select_linked_exec(bContext *C, wmOperator *op) { return uv_select_linked_internal(C, op, NULL, false); } void UV_OT_select_linked(wmOperatorType *ot) { /* identifiers */ ot->name = "Select Linked"; ot->description = "Select all UV vertices linked to the active UV map"; ot->idname = "UV_OT_select_linked"; /* api callbacks */ ot->exec = uv_select_linked_exec; ot->poll = ED_operator_uvedit; /* requires space image */ /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Select Linked (Cursor Pick) Operator * \{ */ static int uv_select_linked_pick_invoke(bContext *C, wmOperator *op, const wmEvent *event) { return uv_select_linked_internal(C, op, event, true); } static int uv_select_linked_pick_exec(bContext *C, wmOperator *op) { return uv_select_linked_internal(C, op, NULL, true); } void UV_OT_select_linked_pick(wmOperatorType *ot) { /* identifiers */ ot->name = "Select Linked Pick"; ot->description = "Select all UV vertices linked under the mouse"; ot->idname = "UV_OT_select_linked_pick"; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* api callbacks */ ot->invoke = uv_select_linked_pick_invoke; ot->exec = uv_select_linked_pick_exec; ot->poll = ED_operator_uvedit; /* requires space image */ /* properties */ PropertyRNA *prop; prop = RNA_def_boolean(ot->srna, "extend", 0, "Extend", "Extend selection rather than clearing the existing selection"); RNA_def_property_flag(prop, PROP_SKIP_SAVE); prop = RNA_def_boolean(ot->srna, "deselect", 0, "Deselect", "Deselect linked UV vertices rather than selecting them"); RNA_def_property_flag(prop, PROP_SKIP_SAVE); prop = RNA_def_float_vector( ot->srna, "location", 2, NULL, -FLT_MAX, FLT_MAX, "Location", "Mouse location in normalized coordinates, 0.0 to 1.0 is within the image bounds", -100.0f, 100.0f); RNA_def_property_flag(prop, PROP_SKIP_SAVE); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Select Split Operator * \{ */ /** * \note This is based on similar use case to #MESH_OT_split(), which has a similar effect * but in this case they are not joined to begin with (only having the behavior of being joined) * so its best to call this #uv_select_split() instead of just split(), but assigned to the same * key as #MESH_OT_split - Campbell. */ static int uv_select_split_exec(bContext *C, wmOperator *op) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); const ToolSettings *ts = scene->toolsettings; BMFace *efa; BMLoop *l; BMIter iter, liter; MLoopUV *luv; if (ts->uv_flag & UV_SYNC_SELECTION) { BKE_report(op->reports, RPT_ERROR, "Cannot split selection when sync selection is enabled"); return OPERATOR_CANCELLED; } bool changed_multi = false; uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( view_layer, ((View3D *)NULL), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMesh *bm = BKE_editmesh_from_object(obedit)->bm; bool changed = false; const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV); BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { bool is_sel = false; bool is_unsel = false; if (!uvedit_face_visible_test(scene, efa)) { continue; } /* are we all selected? */ BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); if ((luv->flag & MLOOPUV_VERTSEL) || (luv->flag & MLOOPUV_EDGESEL)) { is_sel = true; } if (!(luv->flag & MLOOPUV_VERTSEL) || !(luv->flag & MLOOPUV_EDGESEL)) { is_unsel = true; } /* we have mixed selection, bail out */ if (is_sel && is_unsel) { break; } } if (is_sel && is_unsel) { BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); luv->flag &= ~(MLOOPUV_VERTSEL | MLOOPUV_EDGESEL); } changed = true; } } if (changed) { changed_multi = true; WM_event_add_notifier(C, NC_SPACE | ND_SPACE_IMAGE, NULL); uv_select_tag_update_for_object(depsgraph, ts, obedit); } } MEM_freeN(objects); return changed_multi ? OPERATOR_FINISHED : OPERATOR_CANCELLED; } void UV_OT_select_split(wmOperatorType *ot) { /* identifiers */ ot->name = "Select Split"; ot->description = "Select only entirely selected faces"; ot->idname = "UV_OT_select_split"; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* api callbacks */ ot->exec = uv_select_split_exec; ot->poll = ED_operator_uvedit; /* requires space image */ } static void uv_select_tag_update_for_object(Depsgraph *depsgraph, const ToolSettings *ts, Object *obedit) { if (ts->uv_flag & UV_SYNC_SELECTION) { DEG_id_tag_update(obedit->data, ID_RECALC_SELECT); WM_main_add_notifier(NC_GEOM | ND_SELECT, obedit->data); } else { Object *obedit_eval = DEG_get_evaluated_object(depsgraph, obedit); BKE_mesh_batch_cache_dirty_tag(obedit_eval->data, BKE_MESH_BATCH_DIRTY_UVEDIT_SELECT); /* Only for region redraw. */ WM_main_add_notifier(NC_GEOM | ND_SELECT, obedit->data); } } /** \} */ /* -------------------------------------------------------------------- */ /** \name Select/Tag Flushing Utils * * Utility functions to flush the uv-selection from tags. * \{ */ /** * helper function for #uv_select_flush_from_tag_loop and uv_select_flush_from_tag_face */ static void uv_select_flush_from_tag_sticky_loc_internal(const Scene *scene, BMEditMesh *em, UvVertMap *vmap, const uint efa_index, BMLoop *l, const bool select, const int cd_loop_uv_offset) { UvMapVert *start_vlist = NULL, *vlist_iter; BMFace *efa_vlist; uvedit_uv_select_set(scene, em, l, select, false, cd_loop_uv_offset); vlist_iter = BM_uv_vert_map_at_index(vmap, BM_elem_index_get(l->v)); while (vlist_iter) { if (vlist_iter->separate) { start_vlist = vlist_iter; } if (efa_index == vlist_iter->poly_index) { break; } vlist_iter = vlist_iter->next; } vlist_iter = start_vlist; while (vlist_iter) { if (vlist_iter != start_vlist && vlist_iter->separate) { break; } if (efa_index != vlist_iter->poly_index) { BMLoop *l_other; efa_vlist = BM_face_at_index(em->bm, vlist_iter->poly_index); /* tf_vlist = BM_ELEM_CD_GET_VOID_P(efa_vlist, cd_poly_tex_offset); */ /* UNUSED */ l_other = BM_iter_at_index( em->bm, BM_LOOPS_OF_FACE, efa_vlist, vlist_iter->loop_of_poly_index); uvedit_uv_select_set(scene, em, l_other, select, false, cd_loop_uv_offset); } vlist_iter = vlist_iter->next; } } /** * Flush the selection from face tags based on sticky and selection modes. * * needed because setting the selection of a face is done in a number of places but it also * needs to respect the sticky modes for the UV verts, so dealing with the sticky modes * is best done in a separate function. * * \note This function is very similar to #uv_select_flush_from_tag_loop, * be sure to update both upon changing. */ static void uv_select_flush_from_tag_face(const Scene *scene, Object *obedit, const bool select) { /* Selecting UV Faces with some modes requires us to change * the selection in other faces (depending on the sticky mode). * * This only needs to be done when the Mesh is not used for * selection (so for sticky modes, vertex or location based). */ const ToolSettings *ts = scene->toolsettings; BMEditMesh *em = BKE_editmesh_from_object(obedit); BMFace *efa; BMLoop *l; BMIter iter, liter; const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); if ((ts->uv_flag & UV_SYNC_SELECTION) == 0 && ELEM(ts->uv_sticky, SI_STICKY_VERTEX, SI_STICKY_LOC)) { struct UvVertMap *vmap; uint efa_index; BM_mesh_elem_table_ensure(em->bm, BM_FACE); vmap = BM_uv_vert_map_create(em->bm, false, false); if (vmap == NULL) { return; } BM_ITER_MESH_INDEX (efa, &iter, em->bm, BM_FACES_OF_MESH, efa_index) { if (BM_elem_flag_test(efa, BM_ELEM_TAG)) { BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); if (select) { luv->flag |= MLOOPUV_EDGESEL; uv_select_flush_from_tag_sticky_loc_internal( scene, em, vmap, efa_index, l, select, cd_loop_uv_offset); } else { luv->flag &= ~MLOOPUV_EDGESEL; if (!uvedit_vert_is_face_select_any_other(scene, l, cd_loop_uv_offset)) { uv_select_flush_from_tag_sticky_loc_internal( scene, em, vmap, efa_index, l, select, cd_loop_uv_offset); } } } } } BM_uv_vert_map_free(vmap); } else { /* SI_STICKY_DISABLE or ts->uv_flag & UV_SYNC_SELECTION */ BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (BM_elem_flag_test(efa, BM_ELEM_TAG)) { uvedit_face_select_set(scene, em, efa, select, false, cd_loop_uv_offset); } } } } /** * Flush the selection from loop tags based on sticky and selection modes. * * needed because setting the selection of a face is done in a number of places but it also needs * to respect the sticky modes for the UV verts, so dealing with the sticky modes is best done * in a separate function. * * \note This function is very similar to #uv_select_flush_from_tag_face, * be sure to update both upon changing. */ static void uv_select_flush_from_tag_loop(const Scene *scene, Object *obedit, const bool select) { /* Selecting UV Loops with some modes requires us to change * the selection in other faces (depending on the sticky mode). * * This only needs to be done when the Mesh is not used for * selection (so for sticky modes, vertex or location based). */ const ToolSettings *ts = scene->toolsettings; BMEditMesh *em = BKE_editmesh_from_object(obedit); BMFace *efa; BMLoop *l; BMIter iter, liter; const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); if ((ts->uv_flag & UV_SYNC_SELECTION) == 0 && ts->uv_sticky == SI_STICKY_VERTEX) { /* Tag all verts as untouched, then touch the ones that have a face center * in the loop and select all MLoopUV's that use a touched vert. */ BM_mesh_elem_hflag_disable_all(em->bm, BM_VERT, BM_ELEM_TAG, false); BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { if (BM_elem_flag_test(l, BM_ELEM_TAG)) { BM_elem_flag_enable(l->v, BM_ELEM_TAG); } } } /* now select tagged verts */ BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { if (BM_elem_flag_test(l->v, BM_ELEM_TAG)) { uvedit_uv_select_set(scene, em, l, select, false, cd_loop_uv_offset); } } } } else if ((ts->uv_flag & UV_SYNC_SELECTION) == 0 && ts->uv_sticky == SI_STICKY_LOC) { struct UvVertMap *vmap; uint efa_index; BM_mesh_elem_table_ensure(em->bm, BM_FACE); vmap = BM_uv_vert_map_create(em->bm, false, false); if (vmap == NULL) { return; } BM_ITER_MESH_INDEX (efa, &iter, em->bm, BM_FACES_OF_MESH, efa_index) { BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { if (BM_elem_flag_test(l, BM_ELEM_TAG)) { uv_select_flush_from_tag_sticky_loc_internal( scene, em, vmap, efa_index, l, select, cd_loop_uv_offset); } } } BM_uv_vert_map_free(vmap); } else { /* SI_STICKY_DISABLE or ts->uv_flag & UV_SYNC_SELECTION */ BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { if (BM_elem_flag_test(l, BM_ELEM_TAG)) { uvedit_uv_select_set(scene, em, l, select, false, cd_loop_uv_offset); } } } } } /** * Flush the selection from UV edge flags based on sticky modes. * * Useful when performing edge selections in different sticky modes, since setting the required * edge flags (#MLOOPUV_EDGESEL) is done manually or using #uvedit_edge_select_set_noflush, * but dealing with sticky modes for vertex selections is best done in a separate function. * * \note Current behavior is selecting only; deselecting can be added but the behavior isn't * required anywhere. */ static void uv_select_flush_from_loop_edge_flag(const Scene *scene, BMEditMesh *em) { const ToolSettings *ts = scene->toolsettings; BMFace *efa; BMLoop *l; BMIter iter, liter; const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); if ((ts->uv_flag & UV_SYNC_SELECTION) == 0 && ELEM(ts->uv_sticky, SI_STICKY_LOC, SI_STICKY_VERTEX)) { /* Use the #MLOOPUV_EDGESEL flag to identify which verts must to be selected */ struct UvVertMap *vmap; uint efa_index; /* Clear UV vert flags */ bm_uv_flag_clear(scene, em->bm, MLOOPUV_VERTSEL, cd_loop_uv_offset); BM_mesh_elem_table_ensure(em->bm, BM_FACE); vmap = BM_uv_vert_map_create(em->bm, false, false); if (vmap == NULL) { return; } BM_ITER_MESH_INDEX (efa, &iter, em->bm, BM_FACES_OF_MESH, efa_index) { if (!uvedit_face_visible_test(scene, efa)) { /* This visibility check could be removed? Simply relying on edge flags to ensure * visibility might be sufficient. */ continue; } BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { MLoopUV *luv; luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); /* Select verts based on UV edge flag. */ if (luv->flag & MLOOPUV_EDGESEL) { uv_select_flush_from_tag_sticky_loc_internal( scene, em, vmap, efa_index, l, true, cd_loop_uv_offset); uv_select_flush_from_tag_sticky_loc_internal( scene, em, vmap, efa_index, l->next, true, cd_loop_uv_offset); } } } BM_uv_vert_map_free(vmap); } else { BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { MLoopUV *luv, *luv_next, *luv_prev; luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); luv_next = BM_ELEM_CD_GET_VOID_P(l->next, cd_loop_uv_offset); luv_prev = BM_ELEM_CD_GET_VOID_P(l->prev, cd_loop_uv_offset); if (luv->flag & MLOOPUV_EDGESEL) { luv->flag |= MLOOPUV_VERTSEL; luv_next->flag |= MLOOPUV_VERTSEL; } else if (!(luv_prev->flag & MLOOPUV_EDGESEL)) { luv->flag &= ~MLOOPUV_VERTSEL; } } } } } /** \} */ /* -------------------------------------------------------------------- */ /** \name Box Select Operator * \{ */ static int uv_box_select_exec(bContext *C, wmOperator *op) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); Scene *scene = CTX_data_scene(C); const ToolSettings *ts = scene->toolsettings; ViewLayer *view_layer = CTX_data_view_layer(C); const ARegion *region = CTX_wm_region(C); BMFace *efa; BMLoop *l; BMIter iter, liter; MLoopUV *luv; rctf rectf; bool pinned; const bool use_face_center = ((ts->uv_flag & UV_SYNC_SELECTION) ? (ts->selectmode == SCE_SELECT_FACE) : (ts->uv_selectmode == UV_SELECT_FACE)); const bool use_edge = ((ts->uv_flag & UV_SYNC_SELECTION) ? (ts->selectmode == SCE_SELECT_EDGE) : (ts->uv_selectmode == UV_SELECT_EDGE)); const bool use_select_linked = !(ts->uv_flag & UV_SYNC_SELECTION) && (ts->uv_selectmode == UV_SELECT_ISLAND); /* get rectangle from operator */ WM_operator_properties_border_to_rctf(op, &rectf); UI_view2d_region_to_view_rctf(®ion->v2d, &rectf, &rectf); const eSelectOp sel_op = RNA_enum_get(op->ptr, "mode"); const bool select = (sel_op != SEL_OP_SUB); const bool use_pre_deselect = SEL_OP_USE_PRE_DESELECT(sel_op); pinned = RNA_boolean_get(op->ptr, "pinned"); bool changed_multi = false; uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( view_layer, ((View3D *)NULL), &objects_len); if (use_pre_deselect) { uv_select_all_perform_multi(scene, objects, objects_len, SEL_DESELECT); } /* don't indent to avoid diff noise! */ for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); bool changed = false; const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); /* do actual selection */ if (use_face_center && !pinned) { /* handle face selection mode */ float cent[2]; BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { /* assume not touched */ BM_elem_flag_disable(efa, BM_ELEM_TAG); if (uvedit_face_visible_test(scene, efa)) { BM_face_uv_calc_center_median(efa, cd_loop_uv_offset, cent); if (BLI_rctf_isect_pt_v(&rectf, cent)) { BM_elem_flag_enable(efa, BM_ELEM_TAG); changed = true; } } } /* (de)selects all tagged faces and deals with sticky modes */ if (changed) { uv_select_flush_from_tag_face(scene, obedit, select); } } else if (use_edge && !pinned) { bool do_second_pass = true; BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, efa)) { continue; } BMLoop *l_prev = BM_FACE_FIRST_LOOP(efa)->prev; MLoopUV *luv_prev = BM_ELEM_CD_GET_VOID_P(l_prev, cd_loop_uv_offset); BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); if (BLI_rctf_isect_pt_v(&rectf, luv->uv) && BLI_rctf_isect_pt_v(&rectf, luv_prev->uv)) { uvedit_edge_select_set_with_sticky( scene, em, l_prev, select, false, cd_loop_uv_offset); changed = true; do_second_pass = false; } l_prev = l; luv_prev = luv; } } /* Do a second pass if no complete edges could be selected. * This matches wire-frame edit-mesh selection in the 3D view. */ if (do_second_pass) { /* Second pass to check if edges partially overlap with the selection area (box). */ BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, efa)) { continue; } BMLoop *l_prev = BM_FACE_FIRST_LOOP(efa)->prev; MLoopUV *luv_prev = BM_ELEM_CD_GET_VOID_P(l_prev, cd_loop_uv_offset); BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); if (BLI_rctf_isect_segment(&rectf, luv_prev->uv, luv->uv)) { uvedit_edge_select_set_with_sticky( scene, em, l_prev, select, false, cd_loop_uv_offset); changed = true; } l_prev = l; luv_prev = luv; } } } } else { /* other selection modes */ changed = true; BM_mesh_elem_hflag_disable_all(em->bm, BM_VERT, BM_ELEM_TAG, false); BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, efa)) { continue; } bool has_selected = false; BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); if ((select) != (uvedit_uv_select_test(scene, l, cd_loop_uv_offset))) { if (!pinned || (ts->uv_flag & UV_SYNC_SELECTION)) { /* UV_SYNC_SELECTION - can't do pinned selection */ if (BLI_rctf_isect_pt_v(&rectf, luv->uv)) { uvedit_uv_select_set(scene, em, l, select, false, cd_loop_uv_offset); BM_elem_flag_enable(l->v, BM_ELEM_TAG); has_selected = true; } } else if (pinned) { if ((luv->flag & MLOOPUV_PINNED) && BLI_rctf_isect_pt_v(&rectf, luv->uv)) { uvedit_uv_select_set(scene, em, l, select, false, cd_loop_uv_offset); BM_elem_flag_enable(l->v, BM_ELEM_TAG); } } } } if (has_selected && use_select_linked) { UvNearestHit hit = { .ob = obedit, .efa = efa, }; uv_select_linked_multi(scene, objects, objects_len, &hit, true, !select, false, false); } } if (ts->uv_sticky == SI_STICKY_VERTEX) { uvedit_vertex_select_tagged(em, scene, select, cd_loop_uv_offset); } } if (changed || use_pre_deselect) { changed_multi = true; if (ts->uv_flag & UV_SYNC_SELECTION) { ED_uvedit_select_sync_flush(ts, em, select); } else { ED_uvedit_selectmode_flush(scene, em); } uv_select_tag_update_for_object(depsgraph, ts, obedit); } } MEM_freeN(objects); return changed_multi ? OPERATOR_FINISHED : OPERATOR_CANCELLED; } void UV_OT_select_box(wmOperatorType *ot) { /* identifiers */ ot->name = "Box Select"; ot->description = "Select UV vertices using box selection"; ot->idname = "UV_OT_select_box"; /* api callbacks */ ot->invoke = WM_gesture_box_invoke; ot->exec = uv_box_select_exec; ot->modal = WM_gesture_box_modal; ot->poll = ED_operator_uvedit_space_image; /* requires space image */ ot->cancel = WM_gesture_box_cancel; /* flags */ ot->flag = OPTYPE_UNDO; /* properties */ RNA_def_boolean(ot->srna, "pinned", 0, "Pinned", "Border select pinned UVs only"); WM_operator_properties_gesture_box(ot); WM_operator_properties_select_operation_simple(ot); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Circle Select Operator * \{ */ static int uv_circle_select_is_point_inside(const float uv[2], const float offset[2], const float ellipse[2]) { /* normalized ellipse: ell[0] = scaleX, ell[1] = scaleY */ const float co[2] = { (uv[0] - offset[0]) * ellipse[0], (uv[1] - offset[1]) * ellipse[1], }; return len_squared_v2(co) < 1.0f; } static int uv_circle_select_is_edge_inside(const float uv_a[2], const float uv_b[2], const float offset[2], const float ellipse[2]) { /* normalized ellipse: ell[0] = scaleX, ell[1] = scaleY */ const float co_a[2] = { (uv_a[0] - offset[0]) * ellipse[0], (uv_a[1] - offset[1]) * ellipse[1], }; const float co_b[2] = { (uv_b[0] - offset[0]) * ellipse[0], (uv_b[1] - offset[1]) * ellipse[1], }; return dist_squared_to_line_segment_v2((const float[2]){0.0f, 0.0f}, co_a, co_b) < 1.0f; } static int uv_circle_select_exec(bContext *C, wmOperator *op) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); SpaceImage *sima = CTX_wm_space_image(C); Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); const ToolSettings *ts = scene->toolsettings; const ARegion *region = CTX_wm_region(C); BMFace *efa; BMLoop *l; BMIter iter, liter; MLoopUV *luv; int x, y, radius, width, height; float zoomx, zoomy; float offset[2], ellipse[2]; const bool use_face_center = ((ts->uv_flag & UV_SYNC_SELECTION) ? (ts->selectmode == SCE_SELECT_FACE) : (ts->uv_selectmode == UV_SELECT_FACE)); const bool use_edge = ((ts->uv_flag & UV_SYNC_SELECTION) ? (ts->selectmode == SCE_SELECT_EDGE) : (ts->uv_selectmode == UV_SELECT_EDGE)); const bool use_select_linked = !(ts->uv_flag & UV_SYNC_SELECTION) && (ts->uv_selectmode == UV_SELECT_ISLAND); /* get operator properties */ x = RNA_int_get(op->ptr, "x"); y = RNA_int_get(op->ptr, "y"); radius = RNA_int_get(op->ptr, "radius"); /* compute ellipse size and location, not a circle since we deal * with non square image. ellipse is normalized, r = 1.0. */ ED_space_image_get_size(sima, &width, &height); ED_space_image_get_zoom(sima, region, &zoomx, &zoomy); ellipse[0] = width * zoomx / radius; ellipse[1] = height * zoomy / radius; UI_view2d_region_to_view(®ion->v2d, x, y, &offset[0], &offset[1]); bool changed_multi = false; uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( view_layer, ((View3D *)NULL), &objects_len); const eSelectOp sel_op = ED_select_op_modal(RNA_enum_get(op->ptr, "mode"), WM_gesture_is_modal_first(op->customdata)); const bool select = (sel_op != SEL_OP_SUB); const bool use_pre_deselect = SEL_OP_USE_PRE_DESELECT(sel_op); if (use_pre_deselect) { uv_select_all_perform_multi(scene, objects, objects_len, SEL_DESELECT); } for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); bool changed = false; const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); /* do selection */ if (use_face_center) { BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { BM_elem_flag_disable(efa, BM_ELEM_TAG); /* assume not touched */ if (select != uvedit_face_select_test(scene, efa, cd_loop_uv_offset)) { float cent[2]; BM_face_uv_calc_center_median(efa, cd_loop_uv_offset, cent); if (uv_circle_select_is_point_inside(cent, offset, ellipse)) { BM_elem_flag_enable(efa, BM_ELEM_TAG); changed = true; } } } /* (de)selects all tagged faces and deals with sticky modes */ if (changed) { uv_select_flush_from_tag_face(scene, obedit, select); } } else if (use_edge) { BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, efa)) { continue; } BMLoop *l_prev = BM_FACE_FIRST_LOOP(efa)->prev; MLoopUV *luv_prev = BM_ELEM_CD_GET_VOID_P(l_prev, cd_loop_uv_offset); BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); if (uv_circle_select_is_edge_inside(luv->uv, luv_prev->uv, offset, ellipse)) { uvedit_edge_select_set_with_sticky( scene, em, l_prev, select, false, cd_loop_uv_offset); changed = true; } l_prev = l; luv_prev = luv; } } } else { BM_mesh_elem_hflag_disable_all(em->bm, BM_VERT, BM_ELEM_TAG, false); BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, efa)) { continue; } bool has_selected = false; BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { if ((select) != (uvedit_uv_select_test(scene, l, cd_loop_uv_offset))) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); if (uv_circle_select_is_point_inside(luv->uv, offset, ellipse)) { changed = true; uvedit_uv_select_set(scene, em, l, select, false, cd_loop_uv_offset); BM_elem_flag_enable(l->v, BM_ELEM_TAG); has_selected = true; } } } if (has_selected && use_select_linked) { UvNearestHit hit = { .ob = obedit, .efa = efa, }; uv_select_linked_multi(scene, objects, objects_len, &hit, true, !select, false, false); } } if (ts->uv_sticky == SI_STICKY_VERTEX) { uvedit_vertex_select_tagged(em, scene, select, cd_loop_uv_offset); } } if (changed || use_pre_deselect) { changed_multi = true; if (ts->uv_flag & UV_SYNC_SELECTION) { ED_uvedit_select_sync_flush(ts, em, select); } else { ED_uvedit_selectmode_flush(scene, em); } uv_select_tag_update_for_object(depsgraph, ts, obedit); } } MEM_freeN(objects); return changed_multi ? OPERATOR_FINISHED : OPERATOR_CANCELLED; } void UV_OT_select_circle(wmOperatorType *ot) { /* identifiers */ ot->name = "Circle Select"; ot->description = "Select UV vertices using circle selection"; ot->idname = "UV_OT_select_circle"; /* api callbacks */ ot->invoke = WM_gesture_circle_invoke; ot->modal = WM_gesture_circle_modal; ot->exec = uv_circle_select_exec; ot->poll = ED_operator_uvedit_space_image; /* requires space image */ ot->cancel = WM_gesture_circle_cancel; ot->get_name = ED_select_circle_get_name; /* flags */ ot->flag = OPTYPE_UNDO; /* properties */ WM_operator_properties_gesture_circle(ot); WM_operator_properties_select_operation_simple(ot); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Lasso Select Operator * \{ */ static bool do_lasso_select_mesh_uv_is_point_inside(const ARegion *region, const rcti *clip_rect, const int mcoords[][2], const int mcoords_len, const float co_test[2]) { int co_screen[2]; if (UI_view2d_view_to_region_clip( ®ion->v2d, co_test[0], co_test[1], &co_screen[0], &co_screen[1]) && BLI_rcti_isect_pt_v(clip_rect, co_screen) && BLI_lasso_is_point_inside( mcoords, mcoords_len, co_screen[0], co_screen[1], V2D_IS_CLIPPED)) { return true; } return false; } static bool do_lasso_select_mesh_uv_is_edge_inside(const ARegion *region, const rcti *clip_rect, const int mcoords[][2], const int mcoords_len, const float co_test_a[2], const float co_test_b[2]) { int co_screen_a[2], co_screen_b[2]; if (UI_view2d_view_to_region_segment_clip( ®ion->v2d, co_test_a, co_test_b, co_screen_a, co_screen_b) && BLI_rcti_isect_segment(clip_rect, co_screen_a, co_screen_b) && BLI_lasso_is_edge_inside( mcoords, mcoords_len, UNPACK2(co_screen_a), UNPACK2(co_screen_b), V2D_IS_CLIPPED)) { return true; } return false; } static bool do_lasso_select_mesh_uv(bContext *C, const int mcoords[][2], const int mcoords_len, const eSelectOp sel_op) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); const ARegion *region = CTX_wm_region(C); Scene *scene = CTX_data_scene(C); const ToolSettings *ts = scene->toolsettings; ViewLayer *view_layer = CTX_data_view_layer(C); const bool use_face_center = ((ts->uv_flag & UV_SYNC_SELECTION) ? (ts->selectmode == SCE_SELECT_FACE) : (ts->uv_selectmode == UV_SELECT_FACE)); const bool use_edge = ((ts->uv_flag & UV_SYNC_SELECTION) ? (ts->selectmode == SCE_SELECT_EDGE) : (ts->uv_selectmode == UV_SELECT_EDGE)); const bool use_select_linked = !(ts->uv_flag & UV_SYNC_SELECTION) && (ts->uv_selectmode == UV_SELECT_ISLAND); const bool select = (sel_op != SEL_OP_SUB); const bool use_pre_deselect = SEL_OP_USE_PRE_DESELECT(sel_op); BMIter iter, liter; BMFace *efa; BMLoop *l; bool changed_multi = false; rcti rect; BLI_lasso_boundbox(&rect, mcoords, mcoords_len); uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( view_layer, ((View3D *)NULL), &objects_len); if (use_pre_deselect) { uv_select_all_perform_multi(scene, objects, objects_len, SEL_DESELECT); } for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; bool changed = false; BMEditMesh *em = BKE_editmesh_from_object(obedit); const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); if (use_face_center) { /* Face Center Select. */ BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { BM_elem_flag_disable(efa, BM_ELEM_TAG); /* assume not touched */ if (select != uvedit_face_select_test(scene, efa, cd_loop_uv_offset)) { float cent[2]; BM_face_uv_calc_center_median(efa, cd_loop_uv_offset, cent); if (do_lasso_select_mesh_uv_is_point_inside(region, &rect, mcoords, mcoords_len, cent)) { BM_elem_flag_enable(efa, BM_ELEM_TAG); changed = true; } } } /* (de)selects all tagged faces and deals with sticky modes */ if (changed) { uv_select_flush_from_tag_face(scene, obedit, select); } } else if (use_edge) { bool do_second_pass = true; BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, efa)) { continue; } BMLoop *l_prev = BM_FACE_FIRST_LOOP(efa)->prev; MLoopUV *luv_prev = BM_ELEM_CD_GET_VOID_P(l_prev, cd_loop_uv_offset); BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); if (do_lasso_select_mesh_uv_is_point_inside( region, &rect, mcoords, mcoords_len, luv->uv) && do_lasso_select_mesh_uv_is_point_inside( region, &rect, mcoords, mcoords_len, luv_prev->uv)) { uvedit_edge_select_set_with_sticky( scene, em, l_prev, select, false, cd_loop_uv_offset); do_second_pass = false; changed = true; } l_prev = l; luv_prev = luv; } } /* Do a second pass if no complete edges could be selected. * This matches wire-frame edit-mesh selection in the 3D view. */ if (do_second_pass) { /* Second pass to check if edges partially overlap with the selection area (lasso). */ BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, efa)) { continue; } BMLoop *l_prev = BM_FACE_FIRST_LOOP(efa)->prev; MLoopUV *luv_prev = BM_ELEM_CD_GET_VOID_P(l_prev, cd_loop_uv_offset); BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); if (do_lasso_select_mesh_uv_is_edge_inside( region, &rect, mcoords, mcoords_len, luv->uv, luv_prev->uv)) { uvedit_edge_select_set_with_sticky( scene, em, l_prev, select, false, cd_loop_uv_offset); changed = true; } l_prev = l; luv_prev = luv; } } } } else { /* Vert Selection. */ BM_mesh_elem_hflag_disable_all(em->bm, BM_VERT, BM_ELEM_TAG, false); BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, efa)) { continue; } bool has_selected = false; BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { if ((select) != (uvedit_uv_select_test(scene, l, cd_loop_uv_offset))) { MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); if (do_lasso_select_mesh_uv_is_point_inside( region, &rect, mcoords, mcoords_len, luv->uv)) { uvedit_uv_select_set(scene, em, l, select, false, cd_loop_uv_offset); changed = true; BM_elem_flag_enable(l->v, BM_ELEM_TAG); has_selected = true; } } } if (has_selected && use_select_linked) { UvNearestHit hit = { .ob = obedit, .efa = efa, }; uv_select_linked_multi(scene, objects, objects_len, &hit, true, !select, false, false); } } if (ts->uv_sticky == SI_STICKY_VERTEX) { uvedit_vertex_select_tagged(em, scene, select, cd_loop_uv_offset); } } if (changed || use_pre_deselect) { changed_multi = true; if (ts->uv_flag & UV_SYNC_SELECTION) { ED_uvedit_select_sync_flush(ts, em, select); } else { ED_uvedit_selectmode_flush(scene, em); } uv_select_tag_update_for_object(depsgraph, ts, obedit); } } MEM_freeN(objects); return changed_multi; } static int uv_lasso_select_exec(bContext *C, wmOperator *op) { int mcoords_len; const int(*mcoords)[2] = WM_gesture_lasso_path_to_array(C, op, &mcoords_len); if (mcoords) { const eSelectOp sel_op = RNA_enum_get(op->ptr, "mode"); bool changed = do_lasso_select_mesh_uv(C, mcoords, mcoords_len, sel_op); MEM_freeN((void *)mcoords); return changed ? OPERATOR_FINISHED : OPERATOR_CANCELLED; } return OPERATOR_PASS_THROUGH; } void UV_OT_select_lasso(wmOperatorType *ot) { ot->name = "Lasso Select UV"; ot->description = "Select UVs using lasso selection"; ot->idname = "UV_OT_select_lasso"; ot->invoke = WM_gesture_lasso_invoke; ot->modal = WM_gesture_lasso_modal; ot->exec = uv_lasso_select_exec; ot->poll = ED_operator_uvedit_space_image; ot->cancel = WM_gesture_lasso_cancel; /* flags */ ot->flag = OPTYPE_UNDO | OPTYPE_DEPENDS_ON_CURSOR; /* properties */ WM_operator_properties_gesture_lasso(ot); WM_operator_properties_select_operation_simple(ot); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Select Pinned UV's Operator * \{ */ static int uv_select_pinned_exec(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); const ToolSettings *ts = scene->toolsettings; /* Use this operator only in vertex mode, since it is not guaranteed that pinned vertices may * form higher selection states (like edges/faces/islands) in other modes. */ if ((ts->uv_flag & UV_SYNC_SELECTION) == 0) { if (ts->uv_selectmode != UV_SELECT_VERTEX) { BKE_report(op->reports, RPT_ERROR, "Pinned vertices can be selected in Vertex Mode only"); return OPERATOR_CANCELLED; } } Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); ViewLayer *view_layer = CTX_data_view_layer(C); BMFace *efa; BMLoop *l; BMIter iter, liter; MLoopUV *luv; uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( view_layer, ((View3D *)NULL), &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_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); bool changed = false; BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, efa)) { continue; } BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); if (luv->flag & MLOOPUV_PINNED) { uvedit_uv_select_enable(scene, em, l, false, cd_loop_uv_offset); changed = true; } } } if ((ts->uv_flag & UV_SYNC_SELECTION) == 0) { ED_uvedit_selectmode_flush(scene, em); } if (changed) { uv_select_tag_update_for_object(depsgraph, ts, obedit); } } MEM_freeN(objects); return OPERATOR_FINISHED; } void UV_OT_select_pinned(wmOperatorType *ot) { /* identifiers */ ot->name = "Selected Pinned"; ot->description = "Select all pinned UV vertices"; ot->idname = "UV_OT_select_pinned"; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* api callbacks */ ot->exec = uv_select_pinned_exec; ot->poll = ED_operator_uvedit; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Select Overlap Operator * \{ */ BLI_INLINE uint overlap_hash(const void *overlap_v) { const BVHTreeOverlap *overlap = overlap_v; /* Designed to treat (A,B) and (B,A) as the same. */ int x = overlap->indexA; int y = overlap->indexB; if (x > y) { SWAP(int, x, y); } return BLI_hash_int_2d(x, y); } BLI_INLINE bool overlap_cmp(const void *a_v, const void *b_v) { const BVHTreeOverlap *a = a_v; const BVHTreeOverlap *b = b_v; return !((a->indexA == b->indexA && a->indexB == b->indexB) || (a->indexA == b->indexB && a->indexB == b->indexA)); } struct UVOverlapData { int ob_index; int face_index; float tri[3][2]; }; /** * Specialized 2D triangle intersection for detecting UV overlap: * * \return * - false when single corners or edges touch (common for UV coordinates). * - true when all corners touch (an exactly overlapping triangle). */ static bool overlap_tri_tri_uv_test(const float t1[3][2], const float t2[3][2], const float endpoint_bias) { float vi[2]; /* Don't use 'isect_tri_tri_v2' here * because it's important to ignore overlap at end-points. */ if (isect_seg_seg_v2_point_ex(t1[0], t1[1], t2[0], t2[1], endpoint_bias, vi) == 1 || isect_seg_seg_v2_point_ex(t1[0], t1[1], t2[1], t2[2], endpoint_bias, vi) == 1 || isect_seg_seg_v2_point_ex(t1[0], t1[1], t2[2], t2[0], endpoint_bias, vi) == 1 || isect_seg_seg_v2_point_ex(t1[1], t1[2], t2[0], t2[1], endpoint_bias, vi) == 1 || isect_seg_seg_v2_point_ex(t1[1], t1[2], t2[1], t2[2], endpoint_bias, vi) == 1 || isect_seg_seg_v2_point_ex(t1[1], t1[2], t2[2], t2[0], endpoint_bias, vi) == 1 || isect_seg_seg_v2_point_ex(t1[2], t1[0], t2[0], t2[1], endpoint_bias, vi) == 1 || isect_seg_seg_v2_point_ex(t1[2], t1[0], t2[1], t2[2], endpoint_bias, vi) == 1) { return true; } /* When none of the segments intersect, checking if either of the triangles corners * is inside the others is almost always sufficient to test if the two triangles intersect. * * However, the `endpoint_bias` on segment intersections causes _exact_ overlapping * triangles not to be detected. * * Resolve this problem at the small cost of calculating the triangle center, see T85508. */ mid_v2_v2v2v2(vi, UNPACK3(t1)); if (isect_point_tri_v2(vi, UNPACK3(t2)) != 0) { return true; } mid_v2_v2v2v2(vi, UNPACK3(t2)); if (isect_point_tri_v2(vi, UNPACK3(t1)) != 0) { return true; } return false; } static int uv_select_overlap(bContext *C, const bool extend) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( view_layer, ((View3D *)NULL), &objects_len); /* Calculate maximum number of tree nodes and prepare initial selection. */ uint uv_tri_len = 0; for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(obedit); BM_mesh_elem_table_ensure(em->bm, BM_FACE); BM_mesh_elem_index_ensure(em->bm, BM_VERT | BM_FACE); BM_mesh_elem_hflag_disable_all(em->bm, BM_FACE, BM_ELEM_TAG, false); if (!extend) { uv_select_all_perform(scene, obedit, SEL_DESELECT); } BMIter iter; BMFace *efa; BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test_ex(scene->toolsettings, efa)) { continue; } uv_tri_len += efa->len - 2; } } struct UVOverlapData *overlap_data = MEM_mallocN(sizeof(struct UVOverlapData) * uv_tri_len, "UvOverlapData"); BVHTree *uv_tree = BLI_bvhtree_new(uv_tri_len, 0.0f, 4, 6); /* Use a global data index when inserting into the BVH. */ int data_index = 0; int face_len_alloc = 3; float(*uv_verts)[2] = MEM_mallocN(sizeof(*uv_verts) * face_len_alloc, "UvOverlapCoords"); uint(*indices)[3] = MEM_mallocN(sizeof(*indices) * (face_len_alloc - 2), "UvOverlapTris"); 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, liter; BMFace *efa; BMLoop *l; const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); /* Triangulate each UV face and store it inside the BVH. */ int face_index; BM_ITER_MESH_INDEX (efa, &iter, em->bm, BM_FACES_OF_MESH, face_index) { if (!uvedit_face_visible_test_ex(scene->toolsettings, efa)) { continue; } const uint face_len = efa->len; const uint tri_len = face_len - 2; if (face_len_alloc < face_len) { MEM_freeN(uv_verts); MEM_freeN(indices); uv_verts = MEM_mallocN(sizeof(*uv_verts) * face_len, "UvOverlapCoords"); indices = MEM_mallocN(sizeof(*indices) * tri_len, "UvOverlapTris"); face_len_alloc = face_len; } int vert_index; BM_ITER_ELEM_INDEX (l, &liter, efa, BM_LOOPS_OF_FACE, vert_index) { MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); copy_v2_v2(uv_verts[vert_index], luv->uv); } BLI_polyfill_calc(uv_verts, face_len, 0, indices); for (int t = 0; t < tri_len; t++) { overlap_data[data_index].ob_index = ob_index; overlap_data[data_index].face_index = face_index; /* BVH needs 3D, overlap data uses 2D. */ const float tri[3][3] = { {UNPACK2(uv_verts[indices[t][0]]), 0.0f}, {UNPACK2(uv_verts[indices[t][1]]), 0.0f}, {UNPACK2(uv_verts[indices[t][2]]), 0.0f}, }; copy_v2_v2(overlap_data[data_index].tri[0], tri[0]); copy_v2_v2(overlap_data[data_index].tri[1], tri[1]); copy_v2_v2(overlap_data[data_index].tri[2], tri[2]); BLI_bvhtree_insert(uv_tree, data_index, &tri[0][0], 3); data_index++; } } } BLI_assert(data_index == uv_tri_len); MEM_freeN(uv_verts); MEM_freeN(indices); BLI_bvhtree_balance(uv_tree); uint tree_overlap_len; BVHTreeOverlap *overlap = BLI_bvhtree_overlap(uv_tree, uv_tree, &tree_overlap_len, NULL, NULL); if (overlap != NULL) { GSet *overlap_set = BLI_gset_new_ex(overlap_hash, overlap_cmp, __func__, tree_overlap_len); for (int i = 0; i < tree_overlap_len; i++) { /* Skip overlaps against yourself. */ if (overlap[i].indexA == overlap[i].indexB) { continue; } /* Skip overlaps that have already been tested. */ if (!BLI_gset_add(overlap_set, &overlap[i])) { continue; } const struct UVOverlapData *o_a = &overlap_data[overlap[i].indexA]; const struct UVOverlapData *o_b = &overlap_data[overlap[i].indexB]; Object *obedit_a = objects[o_a->ob_index]; Object *obedit_b = objects[o_b->ob_index]; BMEditMesh *em_a = BKE_editmesh_from_object(obedit_a); BMEditMesh *em_b = BKE_editmesh_from_object(obedit_b); BMFace *face_a = em_a->bm->ftable[o_a->face_index]; BMFace *face_b = em_b->bm->ftable[o_b->face_index]; const int cd_loop_uv_offset_a = CustomData_get_offset(&em_a->bm->ldata, CD_MLOOPUV); const int cd_loop_uv_offset_b = CustomData_get_offset(&em_b->bm->ldata, CD_MLOOPUV); /* Skip if both faces are already selected. */ if (uvedit_face_select_test(scene, face_a, cd_loop_uv_offset_a) && uvedit_face_select_test(scene, face_b, cd_loop_uv_offset_b)) { continue; } /* Main tri-tri overlap test. */ const float endpoint_bias = -1e-4f; if (overlap_tri_tri_uv_test(o_a->tri, o_b->tri, endpoint_bias)) { uvedit_face_select_enable(scene, em_a, face_a, false, cd_loop_uv_offset_a); uvedit_face_select_enable(scene, em_b, face_b, false, cd_loop_uv_offset_b); } } BLI_gset_free(overlap_set, NULL); MEM_freeN(overlap); } for (uint ob_index = 0; ob_index < objects_len; ob_index++) { uv_select_tag_update_for_object(depsgraph, scene->toolsettings, objects[ob_index]); } BLI_bvhtree_free(uv_tree); MEM_freeN(overlap_data); MEM_freeN(objects); return OPERATOR_FINISHED; } static int uv_select_overlap_exec(bContext *C, wmOperator *op) { bool extend = RNA_boolean_get(op->ptr, "extend"); return uv_select_overlap(C, extend); } void UV_OT_select_overlap(wmOperatorType *ot) { /* identifiers */ ot->name = "Select Overlap"; ot->description = "Select all UV faces which overlap each other"; ot->idname = "UV_OT_select_overlap"; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* api callbacks */ ot->exec = uv_select_overlap_exec; ot->poll = ED_operator_uvedit; /* properties */ RNA_def_boolean(ot->srna, "extend", 0, "Extend", "Extend selection rather than clearing the existing selection"); } /** \} */ /** \name Select Similar Operator * \{ */ static float get_uv_vert_needle(const eUVSelectSimilar type, BMVert *vert, const float ob_m3[3][3], MLoopUV *luv, const int cd_loop_uv_offset) { float result = 0.0f; switch (type) { case UV_SSIM_AREA_UV: { BMFace *f; BMIter iter; BM_ITER_ELEM (f, &iter, vert, BM_FACES_OF_VERT) { result += BM_face_calc_area_uv(f, cd_loop_uv_offset); } } break; case UV_SSIM_AREA_3D: { BMFace *f; BMIter iter; BM_ITER_ELEM (f, &iter, vert, BM_FACES_OF_VERT) { result += BM_face_calc_area_with_mat3(f, ob_m3); } } break; case UV_SSIM_SIDES: { BMEdge *e; BMIter iter; BM_ITER_ELEM (e, &iter, vert, BM_EDGES_OF_VERT) { result += 1.0f; } } break; case UV_SSIM_PIN: return (luv->flag & MLOOPUV_PINNED) ? 1.0f : 0.0f; default: BLI_assert_unreachable(); return false; } return result; } static float get_uv_edge_needle(const eUVSelectSimilar type, BMEdge *edge, const float ob_m3[3][3], MLoopUV *luv_a, MLoopUV *luv_b, const int cd_loop_uv_offset) { float result = 0.0f; switch (type) { case UV_SSIM_AREA_UV: { BMFace *f; BMIter iter; BM_ITER_ELEM (f, &iter, edge, BM_FACES_OF_EDGE) { result += BM_face_calc_area_uv(f, cd_loop_uv_offset); } } break; case UV_SSIM_AREA_3D: { BMFace *f; BMIter iter; BM_ITER_ELEM (f, &iter, edge, BM_FACES_OF_EDGE) { result += BM_face_calc_area_with_mat3(f, ob_m3); } } break; case UV_SSIM_LENGTH_UV: return len_v2v2(luv_a->uv, luv_b->uv); case UV_SSIM_LENGTH_3D: return len_v3v3(edge->v1->co, edge->v2->co); case UV_SSIM_SIDES: { BMEdge *e; BMIter iter; BM_ITER_ELEM (e, &iter, edge, BM_FACES_OF_EDGE) { result += 1.0f; } } break; case UV_SSIM_PIN: if (luv_a->flag & MLOOPUV_PINNED) { result += 1.0f; } if (luv_b->flag & MLOOPUV_PINNED) { result += 1.0f; } break; default: BLI_assert_unreachable(); return false; } return result; } static float get_uv_face_needle(const eUVSelectSimilar type, BMFace *face, const float ob_m3[3][3], const int cd_loop_uv_offset) { float result = 0.0f; switch (type) { case UV_SSIM_AREA_UV: return BM_face_calc_area_uv(face, cd_loop_uv_offset); case UV_SSIM_AREA_3D: return BM_face_calc_area_with_mat3(face, ob_m3); case UV_SSIM_SIDES: return face->len; case UV_SSIM_PIN: { BMLoop *l; BMIter liter; BM_ITER_ELEM (l, &liter, face, BM_LOOPS_OF_FACE) { MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); if (luv->flag & MLOOPUV_PINNED) { result += 1.0f; } } } break; case UV_SSIM_MATERIAL: return face->mat_nr; default: BLI_assert_unreachable(); return false; } return result; } static int uv_select_similar_vert_exec(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); ToolSettings *ts = CTX_data_tool_settings(C); const eUVSelectSimilar type = RNA_enum_get(op->ptr, "type"); const float threshold = RNA_float_get(op->ptr, "threshold"); const eSimilarCmp compare = RNA_enum_get(op->ptr, "compare"); uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( view_layer, ((View3D *)NULL), &objects_len); int max_verts_selected_all = 0; for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(ob); BMFace *face; BMIter iter; BM_ITER_MESH (face, &iter, em->bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, face)) { continue; } max_verts_selected_all += face->len; } /* TODO: Get a tighter bounds */ } int tree_index = 0; KDTree_1d *tree_1d = BLI_kdtree_1d_new(max_verts_selected_all); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(ob); BMesh *bm = em->bm; if (bm->totvertsel == 0) { continue; } const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV); float ob_m3[3][3]; copy_m3_m4(ob_m3, ob->obmat); BMFace *face; BMIter iter; BM_ITER_MESH (face, &iter, em->bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, face)) { continue; } BMLoop *l; BMIter liter; BM_ITER_ELEM (l, &liter, face, BM_LOOPS_OF_FACE) { if (!uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) { continue; } MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); float needle = get_uv_vert_needle(type, l->v, ob_m3, luv, cd_loop_uv_offset); BLI_kdtree_1d_insert(tree_1d, tree_index++, &needle); } } } if (tree_1d != NULL) { BLI_kdtree_1d_deduplicate(tree_1d); BLI_kdtree_1d_balance(tree_1d); } for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(ob); BMesh *bm = em->bm; if (bm->totvertsel == 0) { continue; } bool changed = false; const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV); float ob_m3[3][3]; copy_m3_m4(ob_m3, ob->obmat); BMFace *face; BMIter iter; BM_ITER_MESH (face, &iter, em->bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, face)) { continue; } BMLoop *l; BMIter liter; BM_ITER_ELEM (l, &liter, face, BM_LOOPS_OF_FACE) { if (uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) { continue; /* Already selected. */ } MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); const float needle = get_uv_vert_needle(type, l->v, ob_m3, luv, cd_loop_uv_offset); bool select = ED_select_similar_compare_float_tree(tree_1d, needle, threshold, compare); if (select) { uvedit_uv_select_set(scene, em, l, select, false, cd_loop_uv_offset); changed = true; } } if (changed) { uv_select_tag_update_for_object(depsgraph, ts, ob); } } } MEM_SAFE_FREE(objects); BLI_kdtree_1d_free(tree_1d); return OPERATOR_FINISHED; } static int uv_select_similar_edge_exec(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); ToolSettings *ts = CTX_data_tool_settings(C); const eUVSelectSimilar type = RNA_enum_get(op->ptr, "type"); const float threshold = RNA_float_get(op->ptr, "threshold"); const eSimilarCmp compare = RNA_enum_get(op->ptr, "compare"); uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( view_layer, ((View3D *)NULL), &objects_len); int max_edges_selected_all = 0; for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(ob); BMFace *face; BMIter iter; BM_ITER_MESH (face, &iter, em->bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, face)) { continue; } max_edges_selected_all += face->len; } /* TODO: Get a tighter bounds. */ } int tree_index = 0; KDTree_1d *tree_1d = BLI_kdtree_1d_new(max_edges_selected_all); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(ob); BMesh *bm = em->bm; if (bm->totvertsel == 0) { continue; } const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV); float ob_m3[3][3]; copy_m3_m4(ob_m3, ob->obmat); BMFace *face; BMIter iter; BM_ITER_MESH (face, &iter, em->bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, face)) { continue; } BMLoop *l; BMIter liter; BM_ITER_ELEM (l, &liter, face, BM_LOOPS_OF_FACE) { if (!uvedit_edge_select_test(scene, l, cd_loop_uv_offset)) { continue; } MLoopUV *luv_a = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); MLoopUV *luv_b = BM_ELEM_CD_GET_VOID_P(l->next, cd_loop_uv_offset); float needle = get_uv_edge_needle(type, l->e, ob_m3, luv_a, luv_b, cd_loop_uv_offset); if (tree_1d) { BLI_kdtree_1d_insert(tree_1d, tree_index++, &needle); } } } } if (tree_1d != NULL) { BLI_kdtree_1d_deduplicate(tree_1d); BLI_kdtree_1d_balance(tree_1d); } for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(ob); BMesh *bm = em->bm; if (bm->totvertsel == 0) { continue; } bool changed = false; const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV); float ob_m3[3][3]; copy_m3_m4(ob_m3, ob->obmat); BMFace *face; BMIter iter; BM_ITER_MESH (face, &iter, em->bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, face)) { continue; } BMLoop *l; BMIter liter; BM_ITER_ELEM (l, &liter, face, BM_LOOPS_OF_FACE) { if (uvedit_edge_select_test(scene, l, cd_loop_uv_offset)) { continue; /* Already selected. */ } MLoopUV *luv_a = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); MLoopUV *luv_b = BM_ELEM_CD_GET_VOID_P(l->next, cd_loop_uv_offset); float needle = get_uv_edge_needle(type, l->e, ob_m3, luv_a, luv_b, cd_loop_uv_offset); bool select = ED_select_similar_compare_float_tree(tree_1d, needle, threshold, compare); if (select) { uvedit_edge_select_set(scene, em, l, select, false, cd_loop_uv_offset); changed = true; } } if (changed) { uv_select_tag_update_for_object(depsgraph, ts, ob); } } } MEM_SAFE_FREE(objects); BLI_kdtree_1d_free(tree_1d); return OPERATOR_FINISHED; } static int uv_select_similar_face_exec(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); ToolSettings *ts = CTX_data_tool_settings(C); const eUVSelectSimilar type = RNA_enum_get(op->ptr, "type"); const float threshold = RNA_float_get(op->ptr, "threshold"); const eSimilarCmp compare = RNA_enum_get(op->ptr, "compare"); uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( view_layer, ((View3D *)NULL), &objects_len); int max_faces_selected_all = 0; for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(ob); max_faces_selected_all += em->bm->totfacesel; /* TODO: Get a tighter bounds */ } int tree_index = 0; KDTree_1d *tree_1d = BLI_kdtree_1d_new(max_faces_selected_all); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(ob); BMesh *bm = em->bm; float ob_m3[3][3]; copy_m3_m4(ob_m3, ob->obmat); const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV); BMFace *face; BMIter iter; BM_ITER_MESH (face, &iter, bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, face)) { continue; } if (!uvedit_face_select_test(scene, face, cd_loop_uv_offset)) { continue; } float needle = get_uv_face_needle(type, face, ob_m3, cd_loop_uv_offset); if (tree_1d) { BLI_kdtree_1d_insert(tree_1d, tree_index++, &needle); } } } if (tree_1d != NULL) { BLI_kdtree_1d_deduplicate(tree_1d); BLI_kdtree_1d_balance(tree_1d); } for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *ob = objects[ob_index]; BMEditMesh *em = BKE_editmesh_from_object(ob); BMesh *bm = em->bm; bool changed = false; bool do_history = false; const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV); float ob_m3[3][3]; copy_m3_m4(ob_m3, ob->obmat); BMFace *face; BMIter iter; BM_ITER_MESH (face, &iter, bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, face)) { continue; } if (uvedit_face_select_test(scene, face, cd_loop_uv_offset)) { continue; } float needle = get_uv_face_needle(type, face, ob_m3, cd_loop_uv_offset); bool select = ED_select_similar_compare_float_tree(tree_1d, needle, threshold, compare); if (select) { uvedit_face_select_set(scene, em, face, select, do_history, cd_loop_uv_offset); changed = true; } } if (changed) { uv_select_tag_update_for_object(depsgraph, ts, ob); } } MEM_SAFE_FREE(objects); BLI_kdtree_1d_free(tree_1d); return OPERATOR_FINISHED; } /* Select similar UV faces/edges/verts based on current selection. */ static int uv_select_similar_exec(bContext *C, wmOperator *op) { ToolSettings *ts = CTX_data_tool_settings(C); PropertyRNA *prop = RNA_struct_find_property(op->ptr, "threshold"); if (!RNA_property_is_set(op->ptr, prop)) { RNA_property_float_set(op->ptr, prop, ts->select_thresh); } else { ts->select_thresh = RNA_property_float_get(op->ptr, prop); } int selectmode = (ts->uv_flag & UV_SYNC_SELECTION) ? ts->selectmode : ts->uv_selectmode; if (selectmode & UV_SELECT_EDGE) { return uv_select_similar_edge_exec(C, op); } if (selectmode & UV_SELECT_FACE) { return uv_select_similar_face_exec(C, op); } if (selectmode & UV_SELECT_ISLAND) { // return uv_select_similar_island_exec(C, op); } return uv_select_similar_vert_exec(C, op); } static EnumPropertyItem prop_vert_similar_types[] = {{UV_SSIM_PIN, "PIN", 0, "Pinned", ""}, {0}}; static EnumPropertyItem prop_edge_similar_types[] = { {UV_SSIM_LENGTH_UV, "LENGTH", 0, "Length", ""}, {UV_SSIM_LENGTH_3D, "LENGTH_3D", 0, "Length 3D", ""}, {UV_SSIM_PIN, "PIN", 0, "Pinned", ""}, {0}}; static EnumPropertyItem prop_face_similar_types[] = { {UV_SSIM_AREA_UV, "AREA", 0, "Area", ""}, {UV_SSIM_AREA_3D, "AREA_3D", 0, "Area 3D", ""}, {UV_SSIM_SIDES, "SIDES", 0, "Polygon Sides", ""}, {UV_SSIM_MATERIAL, "MATERIAL", 0, "Material", ""}, {0}}; static EnumPropertyItem prop_similar_compare_types[] = {{SIM_CMP_EQ, "EQUAL", 0, "Equal", ""}, {SIM_CMP_GT, "GREATER", 0, "Greater", ""}, {SIM_CMP_LT, "LESS", 0, "Less", ""}, {0}}; static const EnumPropertyItem *uv_select_similar_type_itemf(bContext *C, PointerRNA *UNUSED(ptr), PropertyRNA *UNUSED(prop), bool *UNUSED(r_free)) { const ToolSettings *ts = CTX_data_tool_settings(C); if (ts) { int selectmode = (ts->uv_flag & UV_SYNC_SELECTION) ? ts->selectmode : ts->uv_selectmode; if (selectmode & UV_SELECT_EDGE) { return prop_edge_similar_types; } if (selectmode & UV_SELECT_FACE) { return prop_face_similar_types; } } return prop_vert_similar_types; } void UV_OT_select_similar(wmOperatorType *ot) { /* identifiers */ ot->name = "Select Similar"; ot->description = "Select similar UVs by property types"; ot->idname = "UV_OT_select_similar"; /* api callbacks */ ot->invoke = WM_menu_invoke; ot->exec = uv_select_similar_exec; ot->poll = ED_operator_uvedit_space_image; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* properties */ PropertyRNA *prop = ot->prop = RNA_def_enum( ot->srna, "type", prop_vert_similar_types, SIMVERT_NORMAL, "Type", ""); RNA_def_enum_funcs(prop, uv_select_similar_type_itemf); RNA_def_enum(ot->srna, "compare", prop_similar_compare_types, SIM_CMP_EQ, "Compare", ""); RNA_def_float(ot->srna, "threshold", 0.0f, 0.0f, 1.0f, "Threshold", "", 0.0f, 1.0f); } /** \} */ /* -------------------------------------------------------------------- */ /** \name Selected Elements as Arrays (Vertex, Edge & Faces) * * These functions return single elements per connected vertex/edge. * So an edge that has two connected edge loops only assigns one loop in the array. * \{ */ BMFace **ED_uvedit_selected_faces(const Scene *scene, BMesh *bm, int len_max, int *r_faces_len) { const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV); CLAMP_MAX(len_max, bm->totface); int faces_len = 0; BMFace **faces = MEM_mallocN(sizeof(*faces) * len_max, __func__); BMIter iter; BMFace *f; BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { if (uvedit_face_visible_test(scene, f)) { if (uvedit_face_select_test(scene, f, cd_loop_uv_offset)) { faces[faces_len++] = f; if (faces_len == len_max) { goto finally; } } } } finally: *r_faces_len = faces_len; if (faces_len != len_max) { faces = MEM_reallocN(faces, sizeof(*faces) * faces_len); } return faces; } BMLoop **ED_uvedit_selected_edges(const Scene *scene, BMesh *bm, int len_max, int *r_edges_len) { const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV); CLAMP_MAX(len_max, bm->totloop); int edges_len = 0; BMLoop **edges = MEM_mallocN(sizeof(*edges) * len_max, __func__); BMIter iter; BMFace *f; /* Clear tag. */ BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { BMIter liter; BMLoop *l_iter; BM_ITER_ELEM (l_iter, &liter, f, BM_LOOPS_OF_FACE) { BM_elem_flag_disable(l_iter, BM_ELEM_TAG); } } BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { if (uvedit_face_visible_test(scene, f)) { BMIter liter; BMLoop *l_iter; BM_ITER_ELEM (l_iter, &liter, f, BM_LOOPS_OF_FACE) { if (!BM_elem_flag_test(l_iter, BM_ELEM_TAG)) { if (uvedit_edge_select_test(scene, l_iter, cd_loop_uv_offset)) { BM_elem_flag_enable(l_iter, BM_ELEM_TAG); edges[edges_len++] = l_iter; if (edges_len == len_max) { goto finally; } /* Tag other connected loops so we don't consider them separate edges. */ if (l_iter != l_iter->radial_next) { BMLoop *l_radial_iter = l_iter->radial_next; do { if (BM_loop_uv_share_edge_check(l_iter, l_radial_iter, cd_loop_uv_offset)) { BM_elem_flag_enable(l_radial_iter, BM_ELEM_TAG); } } while ((l_radial_iter = l_radial_iter->radial_next) != l_iter); } } } } } } finally: *r_edges_len = edges_len; if (edges_len != len_max) { edges = MEM_reallocN(edges, sizeof(*edges) * edges_len); } return edges; } BMLoop **ED_uvedit_selected_verts(const Scene *scene, BMesh *bm, int len_max, int *r_verts_len) { const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV); CLAMP_MAX(len_max, bm->totloop); int verts_len = 0; BMLoop **verts = MEM_mallocN(sizeof(*verts) * len_max, __func__); BMIter iter; BMFace *f; /* Clear tag. */ BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { BMIter liter; BMLoop *l_iter; BM_ITER_ELEM (l_iter, &liter, f, BM_LOOPS_OF_FACE) { BM_elem_flag_disable(l_iter, BM_ELEM_TAG); } } BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { if (uvedit_face_visible_test(scene, f)) { BMIter liter; BMLoop *l_iter; BM_ITER_ELEM (l_iter, &liter, f, BM_LOOPS_OF_FACE) { if (!BM_elem_flag_test(l_iter, BM_ELEM_TAG)) { const MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l_iter, cd_loop_uv_offset); if (luv->flag & MLOOPUV_VERTSEL) { BM_elem_flag_enable(l_iter->v, BM_ELEM_TAG); verts[verts_len++] = l_iter; if (verts_len == len_max) { goto finally; } /* Tag other connected loops so we don't consider them separate vertices. */ BMIter liter_disk; BMLoop *l_disk_iter; BM_ITER_ELEM (l_disk_iter, &liter_disk, l_iter->v, BM_LOOPS_OF_VERT) { if (BM_loop_uv_share_vert_check(l_iter, l_disk_iter, cd_loop_uv_offset)) { BM_elem_flag_enable(l_disk_iter, BM_ELEM_TAG); } } } } } } } finally: *r_verts_len = verts_len; if (verts_len != len_max) { verts = MEM_reallocN(verts, sizeof(*verts) * verts_len); } return verts; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Select Mode UV Vert/Edge/Face/Island Operator * \{ */ /** Deselects UVs that are not part of a complete island selection. * * Use only when sync select disabled. */ static void uv_isolate_selected_islands(const Scene *scene, BMEditMesh *em, const int cd_loop_uv_offset) { BLI_assert((scene->toolsettings->uv_flag & UV_SYNC_SELECTION) == 0); BMFace *efa; BMIter iter, liter; UvElementMap *elementmap = BM_uv_element_map_create(em->bm, scene, false, false, true); if (elementmap == NULL) { return; } int num_islands = elementmap->totalIslands; /* Boolean array that tells if island with index i is completely selected or not. */ bool *is_island_not_selected = MEM_callocN(sizeof(bool) * (num_islands), __func__); BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { BMLoop *l; if (!uvedit_face_visible_test(scene, efa)) { BM_elem_flag_disable(efa, BM_ELEM_TAG); continue; } BM_elem_flag_enable(efa, BM_ELEM_TAG); BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { if (!uvedit_edge_select_test(scene, l, cd_loop_uv_offset)) { UvElement *element = BM_uv_element_get(elementmap, efa, l); is_island_not_selected[element->island] = true; } } } BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { BMLoop *l; if (!BM_elem_flag_test(efa, BM_ELEM_TAG)) { continue; } BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { UvElement *element = BM_uv_element_get(elementmap, efa, l); /* Deselect all elements of islands which are not completely selected. */ if (is_island_not_selected[element->island] == true) { MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); luv->flag &= ~(MLOOPUV_VERTSEL | MLOOPUV_EDGESEL); } } } BM_uv_element_map_free(elementmap); MEM_freeN(is_island_not_selected); } void ED_uvedit_selectmode_clean(const Scene *scene, Object *obedit) { const ToolSettings *ts = scene->toolsettings; BLI_assert((ts->uv_flag & UV_SYNC_SELECTION) == 0); BMEditMesh *em = BKE_editmesh_from_object(obedit); char sticky = ts->uv_sticky; const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV); BMFace *efa; BMLoop *l; BMIter iter, liter; if (ts->uv_selectmode == UV_SELECT_VERTEX) { /* Vertex mode. */ if (sticky != SI_STICKY_DISABLE) { bm_loop_tags_clear(em->bm); BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, efa)) { continue; } BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { if (uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) { BM_elem_flag_enable(l, BM_ELEM_TAG); } } } uv_select_flush_from_tag_loop(scene, obedit, true); } } else if (ts->uv_selectmode == UV_SELECT_EDGE) { /* Edge mode. */ if (sticky != SI_STICKY_DISABLE) { BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, efa)) { continue; } BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { if (uvedit_edge_select_test(scene, l, cd_loop_uv_offset)) { uvedit_edge_select_set_noflush(scene, l, true, sticky, cd_loop_uv_offset); } } } } uv_select_flush_from_loop_edge_flag(scene, em); } else if (ts->uv_selectmode == UV_SELECT_FACE) { /* Face mode. */ BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { BM_elem_flag_disable(efa, BM_ELEM_TAG); if (uvedit_face_visible_test(scene, efa)) { if (uvedit_face_select_test(scene, efa, cd_loop_uv_offset)) { BM_elem_flag_enable(efa, BM_ELEM_TAG); } uvedit_face_select_set(scene, em, efa, false, false, cd_loop_uv_offset); } } uv_select_flush_from_tag_face(scene, obedit, true); } else if (ts->uv_selectmode == UV_SELECT_ISLAND) { /* Island mode. */ uv_isolate_selected_islands(scene, em, cd_loop_uv_offset); } ED_uvedit_selectmode_flush(scene, em); } void ED_uvedit_selectmode_clean_multi(bContext *C) { Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( view_layer, ((View3D *)NULL), &objects_len); for (uint ob_index = 0; ob_index < objects_len; ob_index++) { Object *obedit = objects[ob_index]; ED_uvedit_selectmode_clean(scene, obedit); uv_select_tag_update_for_object(depsgraph, scene->toolsettings, obedit); } MEM_freeN(objects); } static int uv_select_mode_exec(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); ToolSettings *ts = scene->toolsettings; const char new_uv_selectmode = RNA_enum_get(op->ptr, "type"); /* Early exit if no change in current selection mode */ if (new_uv_selectmode == ts->uv_selectmode) { return OPERATOR_CANCELLED; } /* Set new UV select mode. */ ts->uv_selectmode = new_uv_selectmode; /* Handle UV selection states according to new select mode and sticky mode. */ ED_uvedit_selectmode_clean_multi(C); DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE | ID_RECALC_SELECT); WM_main_add_notifier(NC_SCENE | ND_TOOLSETTINGS, NULL); return OPERATOR_FINISHED; } static int uv_select_mode_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) { const ToolSettings *ts = CTX_data_tool_settings(C); const SpaceImage *sima = CTX_wm_space_image(C); /* Could be removed? - Already done in poll callback. */ if ((!sima) || (sima->mode != SI_MODE_UV)) { return OPERATOR_CANCELLED; } /* Pass through when UV sync selection is enabled. * Allow for mesh select-mode key-map. */ if (ts->uv_flag & UV_SYNC_SELECTION) { return OPERATOR_PASS_THROUGH; } return uv_select_mode_exec(C, op); } void UV_OT_select_mode(wmOperatorType *ot) { /* identifiers */ ot->name = "UV Select Mode"; ot->description = "Change UV selection mode"; ot->idname = "UV_OT_select_mode"; /* api callbacks */ ot->invoke = uv_select_mode_invoke; ot->exec = uv_select_mode_exec; ot->poll = ED_operator_uvedit_space_image; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; /* RNA props */ PropertyRNA *prop; ot->prop = prop = RNA_def_enum( ot->srna, "type", rna_enum_mesh_select_mode_uv_items, 0, "Type", ""); RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); } /** \} */