diff --git a/scripts/startup/bl_ui/space_image.py b/scripts/startup/bl_ui/space_image.py index 0463417383b..c4c130e7908 100644 --- a/scripts/startup/bl_ui/space_image.py +++ b/scripts/startup/bl_ui/space_image.py @@ -870,15 +870,6 @@ class IMAGE_HT_header(Header): if tool_settings.use_uv_select_sync: layout.template_edit_mode_selection() - - layout.prop(tool_settings, "use_uv_select_island", icon_only=True) - - # Currently this only works for edge-select & face-select modes. - row = layout.row() - mesh_select_mode = tool_settings.mesh_select_mode - if mesh_select_mode[0]: - row.active = False - row.prop(tool_settings, "uv_sticky_select_mode", icon_only=True) else: row = layout.row(align=True) uv_select_mode = tool_settings.uv_select_mode[:] @@ -889,8 +880,8 @@ class IMAGE_HT_header(Header): row.operator("uv.select_mode", text="", icon='UV_FACESEL', depress=(uv_select_mode == 'FACE')).type = 'FACE' - layout.prop(tool_settings, "use_uv_select_island", icon_only=True) - layout.prop(tool_settings, "uv_sticky_select_mode", icon_only=True) + layout.prop(tool_settings, "use_uv_select_island", icon_only=True) + layout.prop(tool_settings, "uv_sticky_select_mode", icon_only=True) IMAGE_MT_editor_menus.draw_collapsible(context, layout) diff --git a/source/blender/blenloader/intern/versioning_defaults.cc b/source/blender/blenloader/intern/versioning_defaults.cc index 2846e31d583..c139b3632ea 100644 --- a/source/blender/blenloader/intern/versioning_defaults.cc +++ b/source/blender/blenloader/intern/versioning_defaults.cc @@ -454,6 +454,8 @@ static void blo_update_defaults_scene(Main *bmain, Scene *scene) copy_v2_fl2(scene->safe_areas.title, 0.1f, 0.05f); copy_v2_fl2(scene->safe_areas.action, 0.035f, 0.035f); + ts->uv_flag |= UV_FLAG_SELECT_SYNC; + /* Default Rotate Increment. */ const float default_snap_angle_increment = DEG2RADF(5.0f); ts->snap_angle_increment_2d = default_snap_angle_increment; diff --git a/source/blender/bmesh/CMakeLists.txt b/source/blender/bmesh/CMakeLists.txt index f6666489f42..0b4c5afb704 100644 --- a/source/blender/bmesh/CMakeLists.txt +++ b/source/blender/bmesh/CMakeLists.txt @@ -109,6 +109,8 @@ set(SRC intern/bmesh_structure.cc intern/bmesh_structure.hh intern/bmesh_structure_inline.hh + intern/bmesh_uvselect.cc + intern/bmesh_uvselect.hh intern/bmesh_walkers.cc intern/bmesh_walkers.hh intern/bmesh_walkers_impl.cc diff --git a/source/blender/bmesh/bmesh.hh b/source/blender/bmesh/bmesh.hh index bcf93601f57..4c7c000d0de 100644 --- a/source/blender/bmesh/bmesh.hh +++ b/source/blender/bmesh/bmesh.hh @@ -206,6 +206,7 @@ #include "intern/bmesh_polygon_edgenet.hh" // IWYU pragma: export #include "intern/bmesh_query.hh" // IWYU pragma: export #include "intern/bmesh_query_uv.hh" // IWYU pragma: export +#include "intern/bmesh_uvselect.hh" // IWYU pragma: export #include "intern/bmesh_walkers.hh" // IWYU pragma: export #include "intern/bmesh_inline.hh" // IWYU pragma: export diff --git a/source/blender/bmesh/bmesh_class.hh b/source/blender/bmesh/bmesh_class.hh index b9ad60f97e9..3960b596d04 100644 --- a/source/blender/bmesh/bmesh_class.hh +++ b/source/blender/bmesh/bmesh_class.hh @@ -356,6 +356,21 @@ struct BMesh { bool use_toolflags; + /** + * Used when the UV select sync tool-setting is enabled (see: #UV_FLAG_SELECT_SYNC). + * + * When true, UV selection flags are "valid" (see: #BM_ELEM_SELECT_UV & #BM_ELEM_SELECT_UV_EDGE). + * Otherwise UV selection is read from vertex/edge/face selection flags used in the viewport. + * + * Notes: + * - This should be cleared aggressively when there is no need + * to store a separate UV selection to avoid unnecessary overhead. + * - Clear using #BM_mesh_uvselect_clear (instead of setting directly). + * + - See `bmesh_uvselect.hh` for a more comprehensive explanation. + */ + bool uv_select_sync_valid; + int toolflag_index; CustomData vdata, edata, ldata, pdata; @@ -505,7 +520,11 @@ enum { */ BM_ELEM_TAG = (1 << 4), - BM_ELEM_DRAW = (1 << 5), /* edge display */ + /** + * Used for #BMLoop for loop-vertex selection & #BMFace when the face is selected. + * The #BMLoop also stores edge selection: #BM_ELEM_SELECT_UV_EDGE. + */ + BM_ELEM_SELECT_UV = (1 << 5), /** Spare tag, assumed dirty, use define in each function to name based on use. */ BM_ELEM_TAG_ALT = (1 << 6), @@ -518,6 +537,9 @@ enum { BM_ELEM_INTERNAL_TAG = (1 << 7), }; +/* Only for #BMLoop to select an edge. */ +#define BM_ELEM_SELECT_UV_EDGE BM_ELEM_SEAM + struct BPy_BMGeneric; extern void bpy_bm_generic_invalidate(struct BPy_BMGeneric *self); diff --git a/source/blender/bmesh/intern/bmesh_construct.cc b/source/blender/bmesh/intern/bmesh_construct.cc index a5d310580f8..3a4d5a4e476 100644 --- a/source/blender/bmesh/intern/bmesh_construct.cc +++ b/source/blender/bmesh/intern/bmesh_construct.cc @@ -320,20 +320,23 @@ void BM_elem_attrs_copy(BMesh *bm, const BMCustomDataCopyMap &map, const BMVert { BLI_assert(src != dst); CustomData_bmesh_copy_block(bm->vdata, map, src->head.data, &dst->head.data); - dst->head.hflag = (dst->head.hflag & BM_ELEM_SELECT) | (src->head.hflag & ~BM_ELEM_SELECT); + constexpr char hflag_mask = BM_ELEM_SELECT; + dst->head.hflag = (dst->head.hflag & hflag_mask) | (src->head.hflag & ~hflag_mask); copy_v3_v3(dst->no, src->no); } void BM_elem_attrs_copy(BMesh *bm, const BMCustomDataCopyMap &map, const BMEdge *src, BMEdge *dst) { BLI_assert(src != dst); CustomData_bmesh_copy_block(bm->edata, map, src->head.data, &dst->head.data); - dst->head.hflag = (dst->head.hflag & BM_ELEM_SELECT) | (src->head.hflag & ~BM_ELEM_SELECT); + constexpr char hflag_mask = BM_ELEM_SELECT; + dst->head.hflag = (dst->head.hflag & hflag_mask) | (src->head.hflag & ~hflag_mask); } void BM_elem_attrs_copy(BMesh *bm, const BMCustomDataCopyMap &map, const BMFace *src, BMFace *dst) { BLI_assert(src != dst); CustomData_bmesh_copy_block(bm->pdata, map, src->head.data, &dst->head.data); - dst->head.hflag = (dst->head.hflag & BM_ELEM_SELECT) | (src->head.hflag & ~BM_ELEM_SELECT); + constexpr char hflag_mask = BM_ELEM_SELECT | BM_ELEM_SELECT_UV; + dst->head.hflag = (dst->head.hflag & hflag_mask) | (src->head.hflag & ~hflag_mask); copy_v3_v3(dst->no, src->no); dst->mat_nr = src->mat_nr; } @@ -341,35 +344,40 @@ void BM_elem_attrs_copy(BMesh *bm, const BMCustomDataCopyMap &map, const BMLoop { BLI_assert(src != dst); CustomData_bmesh_copy_block(bm->ldata, map, src->head.data, &dst->head.data); - dst->head.hflag = (dst->head.hflag & BM_ELEM_SELECT) | (src->head.hflag & ~BM_ELEM_SELECT); + constexpr char hflag_mask = BM_ELEM_SELECT | BM_ELEM_SELECT_UV | BM_ELEM_SELECT_UV_EDGE; + dst->head.hflag = (dst->head.hflag & hflag_mask) | (src->head.hflag & ~hflag_mask); } void BM_elem_attrs_copy(BMesh *bm, const BMVert *src, BMVert *dst) { BLI_assert(src != dst); CustomData_bmesh_copy_block(bm->vdata, src->head.data, &dst->head.data); - dst->head.hflag = (dst->head.hflag & BM_ELEM_SELECT) | (src->head.hflag & ~BM_ELEM_SELECT); + constexpr char hflag_mask = BM_ELEM_SELECT; + dst->head.hflag = (dst->head.hflag & hflag_mask) | (src->head.hflag & ~hflag_mask); copy_v3_v3(dst->no, src->no); } void BM_elem_attrs_copy(BMesh *bm, const BMEdge *src, BMEdge *dst) { BLI_assert(src != dst); CustomData_bmesh_copy_block(bm->edata, src->head.data, &dst->head.data); - dst->head.hflag = (dst->head.hflag & BM_ELEM_SELECT) | (src->head.hflag & ~BM_ELEM_SELECT); + constexpr char hflag_mask = BM_ELEM_SELECT; + dst->head.hflag = (dst->head.hflag & hflag_mask) | (src->head.hflag & ~hflag_mask); } void BM_elem_attrs_copy(BMesh *bm, const BMFace *src, BMFace *dst) { BLI_assert(src != dst); + constexpr char hflag_mask = BM_ELEM_SELECT | BM_ELEM_SELECT_UV; CustomData_bmesh_copy_block(bm->pdata, src->head.data, &dst->head.data); - dst->head.hflag = (dst->head.hflag & BM_ELEM_SELECT) | (src->head.hflag & ~BM_ELEM_SELECT); + dst->head.hflag = (dst->head.hflag & hflag_mask) | (src->head.hflag & ~hflag_mask); copy_v3_v3(dst->no, src->no); dst->mat_nr = src->mat_nr; } void BM_elem_attrs_copy(BMesh *bm, const BMLoop *src, BMLoop *dst) { BLI_assert(src != dst); + constexpr char hflag_mask = BM_ELEM_SELECT | BM_ELEM_SELECT_UV | BM_ELEM_SELECT_UV_EDGE; CustomData_bmesh_copy_block(bm->ldata, src->head.data, &dst->head.data); - dst->head.hflag = (dst->head.hflag & BM_ELEM_SELECT) | (src->head.hflag & ~BM_ELEM_SELECT); + dst->head.hflag = (dst->head.hflag & hflag_mask) | (src->head.hflag & ~hflag_mask); } void BM_elem_select_copy(BMesh *bm_dst, void *ele_dst_v, const void *ele_src_v) diff --git a/source/blender/bmesh/intern/bmesh_core.cc b/source/blender/bmesh/intern/bmesh_core.cc index 9e9bb4aede4..03352f7a3f7 100644 --- a/source/blender/bmesh/intern/bmesh_core.cc +++ b/source/blender/bmesh/intern/bmesh_core.cc @@ -147,7 +147,7 @@ BMEdge *BM_edge_create( #endif e->head.htype = BM_EDGE; - e->head.hflag = BM_ELEM_SMOOTH | BM_ELEM_DRAW; + e->head.hflag = BM_ELEM_SMOOTH; e->head.api_flag = 0; /* allocate flags */ diff --git a/source/blender/bmesh/intern/bmesh_marking.cc b/source/blender/bmesh/intern/bmesh_marking.cc index e9134cea294..afce20d483c 100644 --- a/source/blender/bmesh/intern/bmesh_marking.cc +++ b/source/blender/bmesh/intern/bmesh_marking.cc @@ -24,6 +24,7 @@ #include "BLI_task.h" #include "bmesh.hh" +#include "bmesh_query_uv.hh" #include "bmesh_structure.hh" /* For '_FLAG_OVERLAP'. */ @@ -239,6 +240,17 @@ static bool bm_edge_is_face_visible_any(const BMEdge *e) /** \} */ +bool BM_mesh_select_is_mixed(const BMesh *bm) +{ + if (bm->selectmode & SCE_SELECT_VERTEX) { + return !ELEM(bm->totvertsel, 0, bm->totvert); + } + if (bm->selectmode & SCE_SELECT_EDGE) { + return !ELEM(bm->totedgesel, 0, bm->totedge); + } + return !ELEM(bm->totfacesel, 0, bm->totface); +} + void BM_mesh_select_mode_clean_ex(BMesh *bm, const short selectmode) { if (selectmode & SCE_SELECT_VERTEX) { diff --git a/source/blender/bmesh/intern/bmesh_marking.hh b/source/blender/bmesh/intern/bmesh_marking.hh index deb324c5be7..f68e3126be2 100644 --- a/source/blender/bmesh/intern/bmesh_marking.hh +++ b/source/blender/bmesh/intern/bmesh_marking.hh @@ -92,6 +92,11 @@ void BM_face_select_set(BMesh *bm, BMFace *f, bool select); void BM_edge_select_set_noflush(BMesh *bm, BMEdge *e, bool select); void BM_face_select_set_noflush(BMesh *bm, BMFace *f, bool select); +/** + * Return true when there are a mix of selected/unselected elements. + */ +bool BM_mesh_select_is_mixed(const BMesh *bm); + /** * \brief Select Mode Clean * diff --git a/source/blender/bmesh/intern/bmesh_mesh_convert.cc b/source/blender/bmesh/intern/bmesh_mesh_convert.cc index 0360c5f1a09..fc729262b9b 100644 --- a/source/blender/bmesh/intern/bmesh_mesh_convert.cc +++ b/source/blender/bmesh/intern/bmesh_mesh_convert.cc @@ -117,6 +117,11 @@ using blender::bke::AttrDomain; bool BM_attribute_stored_in_bmesh_builtin(const StringRef name) { + /* TODO: increase the size of the `ELEM` macro or split out the `.` case. */ + if (ELEM(name, ".uv_select_vert", ".uv_select_edge", ".uv_select_face")) { + return true; + } + return ELEM(name, "position", ".edge_verts", @@ -432,6 +437,14 @@ void BM_mesh_bm_from_me(BMesh *bm, const Mesh *mesh, const BMeshFromMeshParams * const VArraySpan sharp_edges = *attributes.lookup("sharp_edge", AttrDomain::Edge); const VArraySpan uv_seams = *attributes.lookup("uv_seam", AttrDomain::Edge); + const VArraySpan uv_select_vert = *attributes.lookup(".uv_select_vert", + AttrDomain::Corner); + const VArraySpan uv_select_edge = *attributes.lookup(".uv_select_edge", + AttrDomain::Corner); + const VArraySpan uv_select_face = *attributes.lookup(".uv_select_face", AttrDomain::Face); + + const bool need_uv_select = is_new && (!uv_select_vert.is_empty() && !uv_select_vert.is_empty()); + const Span positions = mesh->vert_positions(); Array vtable(mesh->verts_num); for (const int i : positions.index_range()) { @@ -553,11 +566,27 @@ void BM_mesh_bm_from_me(BMesh *bm, const Mesh *mesh, const BMeshFromMeshParams * BM_elem_index_set(l_iter, totloops++); /* set_ok */ mesh_attributes_copy_to_bmesh_block(bm->ldata, loop_info, j, l_iter->head); + + if (need_uv_select) { + if (uv_select_vert[j]) { + BM_elem_flag_enable(l_iter, BM_ELEM_SELECT_UV); + } + if (uv_select_edge[j]) { + BM_elem_flag_enable(l_iter, BM_ELEM_SELECT_UV_EDGE); + } + } + j++; } while ((l_iter = l_iter->next) != l_first); mesh_attributes_copy_to_bmesh_block(bm->pdata, poly_info, i, f->head); + if (need_uv_select) { + if (uv_select_face[i]) { + BM_elem_flag_enable(f, BM_ELEM_SELECT_UV); + } + } + if (params->calc_face_normal) { BM_face_normal_update(f); } @@ -566,6 +595,8 @@ void BM_mesh_bm_from_me(BMesh *bm, const Mesh *mesh, const BMeshFromMeshParams * bm->elem_index_dirty &= ~(BM_FACE | BM_LOOP); /* Added in order, clear dirty flag. */ } + bm->uv_select_sync_valid = need_uv_select; + /* -------------------------------------------------------------------- */ /* MSelect clears the array elements (to avoid adding multiple times). * @@ -1353,6 +1384,7 @@ static void bm_to_mesh_faces(const BMesh &bm, MutableSpan select_poly, MutableSpan hide_poly, MutableSpan sharp_faces, + MutableSpan uv_select_face, MutableSpan material_indices) { BKE_mesh_face_offsets_ensure_alloc(&mesh); @@ -1384,10 +1416,19 @@ static void bm_to_mesh_faces(const BMesh &bm, sharp_faces[face_i] = !BM_elem_flag_test(bm_faces[face_i], BM_ELEM_SMOOTH); } } + if (!uv_select_face.is_empty()) { + for (const int face_i : range) { + uv_select_face[face_i] = BM_elem_flag_test(bm_faces[face_i], BM_ELEM_SELECT_UV); + } + } }); } -static void bm_to_mesh_loops(const BMesh &bm, const Span bm_loops, Mesh &mesh) +static void bm_to_mesh_loops(const BMesh &bm, + const Span bm_loops, + Mesh &mesh, + MutableSpan uv_select_vert, + MutableSpan uv_select_edge) { CustomData_free_layer_named(&mesh.corner_data, ".corner_vert"); CustomData_free_layer_named(&mesh.corner_data, ".corner_edge"); @@ -1396,8 +1437,11 @@ static void bm_to_mesh_loops(const BMesh &bm, const Span bm_loop CustomData_add_layer_named( &mesh.corner_data, CD_PROP_INT32, CD_CONSTRUCT, mesh.corners_num, ".corner_edge"); const Vector info = bm_to_mesh_copy_info_calc(bm.ldata, mesh.corner_data); + MutableSpan dst_corner_verts = mesh.corner_verts_for_write(); MutableSpan dst_corner_edges = mesh.corner_edges_for_write(); + + const bool need_uv_select = !uv_select_vert.is_empty() && !uv_select_edge.is_empty(); threading::parallel_for(dst_corner_verts.index_range(), 1024, [&](const IndexRange range) { for (const int loop_i : range) { const BMLoop &src_loop = *bm_loops[loop_i]; @@ -1405,6 +1449,14 @@ static void bm_to_mesh_loops(const BMesh &bm, const Span bm_loop dst_corner_edges[loop_i] = BM_elem_index_get(src_loop.e); bmesh_block_copy_to_mesh_attributes(info, loop_i, src_loop.head.data); } + + if (need_uv_select) { + for (const int loop_i : range) { + const BMLoop &src_loop = *bm_loops[loop_i]; + uv_select_vert[loop_i] = BM_elem_flag_test(&src_loop, BM_ELEM_SELECT_UV); + uv_select_edge[loop_i] = BM_elem_flag_test(&src_loop, BM_ELEM_SELECT_UV_EDGE); + } + } }); } @@ -1434,6 +1486,11 @@ void BM_mesh_bm_to_me(Main *bmain, BMesh *bm, Mesh *mesh, const BMeshToMeshParam bool need_sharp_edge = false; bool need_sharp_face = false; bool need_uv_seams = false; + const bool need_uv_select = (bm->uv_select_sync_valid && + /* Avoid redundant layer creation if there is no selection, + * although a "Select All" / "De-select All" clears + * #BMesh::uv_select_sync_valid so it's often not needed. */ + (bm->totvertsel != 0)); Array vert_table; Array edge_table; Array face_table; @@ -1492,6 +1549,9 @@ void BM_mesh_bm_to_me(Main *bmain, BMesh *bm, Mesh *mesh, const BMeshToMeshParam bke::SpanAttributeWriter select_poly; bke::SpanAttributeWriter hide_poly; bke::SpanAttributeWriter sharp_face; + bke::SpanAttributeWriter uv_select_vert; + bke::SpanAttributeWriter uv_select_edge; + bke::SpanAttributeWriter uv_select_face; bke::SpanAttributeWriter material_index; if (need_select_vert) { select_vert = attrs.lookup_or_add_for_write_only_span(".select_vert", AttrDomain::Point); @@ -1520,6 +1580,14 @@ void BM_mesh_bm_to_me(Main *bmain, BMesh *bm, Mesh *mesh, const BMeshToMeshParam if (need_sharp_face) { sharp_face = attrs.lookup_or_add_for_write_only_span("sharp_face", AttrDomain::Face); } + if (need_uv_select) { + uv_select_vert = attrs.lookup_or_add_for_write_only_span(".uv_select_vert", + AttrDomain::Corner); + uv_select_edge = attrs.lookup_or_add_for_write_only_span(".uv_select_edge", + AttrDomain::Corner); + uv_select_face = attrs.lookup_or_add_for_write_only_span(".uv_select_face", + AttrDomain::Face); + } if (need_material_index) { material_index = attrs.lookup_or_add_for_write_only_span("material_index", AttrDomain::Face); @@ -1551,13 +1619,14 @@ void BM_mesh_bm_to_me(Main *bmain, BMesh *bm, Mesh *mesh, const BMeshToMeshParam select_poly.span, hide_poly.span, sharp_face.span, + uv_select_face.span, material_index.span); if (bm->act_face) { mesh->act_face = BM_elem_index_get(bm->act_face); } }, [&]() { - bm_to_mesh_loops(*bm, loop_table, *mesh); + bm_to_mesh_loops(*bm, loop_table, *mesh, uv_select_vert.span, uv_select_edge.span); /* Topology could be changed, ensure #CD_MDISPS are ok. */ multires_topology_changed(mesh); for (const int i : loop_layers_not_to_copy) { @@ -1620,6 +1689,9 @@ void BM_mesh_bm_to_me(Main *bmain, BMesh *bm, Mesh *mesh, const BMeshToMeshParam select_poly.finish(); hide_poly.finish(); sharp_face.finish(); + uv_select_vert.finish(); + uv_select_edge.finish(); + uv_select_face.finish(); material_index.finish(); } @@ -1660,6 +1732,7 @@ void BM_mesh_bm_to_me_compact(BMesh &bm, bool need_sharp_edge = false; bool need_sharp_face = false; bool need_uv_seams = false; + const bool need_uv_select = bm.uv_select_sync_valid; Array vert_table; Array edge_table; @@ -1713,6 +1786,9 @@ void BM_mesh_bm_to_me_compact(BMesh &bm, bke::SpanAttributeWriter select_poly; bke::SpanAttributeWriter hide_poly; bke::SpanAttributeWriter sharp_face; + bke::SpanAttributeWriter uv_select_vert; + bke::SpanAttributeWriter uv_select_edge; + bke::SpanAttributeWriter uv_select_face; bke::SpanAttributeWriter material_index; if (add_mesh_attributes) { @@ -1747,6 +1823,14 @@ void BM_mesh_bm_to_me_compact(BMesh &bm, if (need_sharp_face) { sharp_face = attrs.lookup_or_add_for_write_only_span("sharp_face", AttrDomain::Face); } + if (need_uv_select) { + uv_select_vert = attrs.lookup_or_add_for_write_only_span(".uv_select_vert", + AttrDomain::Corner); + uv_select_edge = attrs.lookup_or_add_for_write_only_span(".uv_select_edge", + AttrDomain::Corner); + uv_select_face = attrs.lookup_or_add_for_write_only_span(".uv_select_face", + AttrDomain::Face); + } if (need_material_index) { material_index = attrs.lookup_or_add_for_write_only_span("material_index", AttrDomain::Face); @@ -1773,13 +1857,14 @@ void BM_mesh_bm_to_me_compact(BMesh &bm, select_poly.span, hide_poly.span, sharp_face.span, + uv_select_face.span, material_index.span); if (bm.act_face) { mesh.act_face = BM_elem_index_get(bm.act_face); } }, [&]() { - bm_to_mesh_loops(bm, loop_table, mesh); + bm_to_mesh_loops(bm, loop_table, mesh, uv_select_vert.span, uv_select_edge.span); for (const int i : loop_layers_not_to_copy) { bm.ldata.layers[i].flag &= ~CD_FLAG_NOCOPY; } @@ -1795,6 +1880,9 @@ void BM_mesh_bm_to_me_compact(BMesh &bm, select_poly.finish(); hide_poly.finish(); sharp_face.finish(); + uv_select_vert.finish(); + uv_select_edge.finish(); + uv_select_face.finish(); material_index.finish(); } } diff --git a/source/blender/bmesh/intern/bmesh_uvselect.cc b/source/blender/bmesh/intern/bmesh_uvselect.cc new file mode 100644 index 00000000000..6b698b0501a --- /dev/null +++ b/source/blender/bmesh/intern/bmesh_uvselect.cc @@ -0,0 +1,2520 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup bmesh + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_scene_types.h" + +#include "BLI_listbase.h" +#include "BLI_math_bits.h" + +#include "bmesh.hh" +#include "bmesh_structure.hh" + +/* -------------------------------------------------------------------- */ +/** \name Internal Utilities + * \{ */ + +static void bm_mesh_uvselect_disable_all(BMesh *bm) +{ + /* In practically all cases it's best to check #BM_ELEM_HIDDEN + * In this case the intent is to re-generate the selection, so clear all. */ + BMIter iter; + BMFace *f; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + BM_elem_flag_disable(l_iter, BM_ELEM_SELECT_UV | BM_ELEM_SELECT_UV_EDGE); + } while ((l_iter = l_iter->next) != l_first); + BM_elem_flag_disable(f, BM_ELEM_SELECT_UV); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UV Selection Functions (low level) + * \{ */ + +bool BM_loop_vert_uvselect_test(const BMLoop *l) +{ + return (!BM_elem_flag_test(l->f, BM_ELEM_HIDDEN) && BM_elem_flag_test(l, BM_ELEM_SELECT_UV)); +} +bool BM_loop_edge_uvselect_test(const BMLoop *l) +{ + return (!BM_elem_flag_test(l->f, BM_ELEM_HIDDEN) && + BM_elem_flag_test(l, BM_ELEM_SELECT_UV_EDGE)); +} + +bool BM_face_uvselect_test(const BMFace *f) +{ + return (!BM_elem_flag_test(f, BM_ELEM_HIDDEN) && BM_elem_flag_test(f, BM_ELEM_SELECT_UV)); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UV Selection Connectivity Checks + * \{ */ + +bool BM_loop_vert_uvselect_check_other_loop_vert(BMLoop *l, + const char hflag, + const int cd_loop_uv_offset) +{ + BLI_assert(ELEM(hflag, BM_ELEM_SELECT_UV, BM_ELEM_TAG)); + BMVert *v = l->v; + BLI_assert(v->e); + const BMEdge *e_iter, *e_first; + e_iter = e_first = v->e; + do { + if (e_iter->l == nullptr) { + continue; + } + BMLoop *l_first = e_iter->l; + BMLoop *l_iter = l_first; + do { + if (BM_elem_flag_test(l_iter->f, BM_ELEM_HIDDEN)) { + continue; + } + if (l_iter->v != v) { + continue; + } + if (l_iter != l) { + if (BM_elem_flag_test(l_iter, hflag)) { + if (BM_loop_uv_share_vert_check(l, l_iter, cd_loop_uv_offset)) { + return true; + } + } + } + } while ((l_iter = l_iter->radial_next) != l_first); + } while ((e_iter = bmesh_disk_edge_next(e_iter, v)) != e_first); + return false; +} + +bool BM_loop_vert_uvselect_check_other_loop_edge(BMLoop *l, + const char hflag, + const int cd_loop_uv_offset) +{ + BLI_assert(ELEM(hflag, BM_ELEM_SELECT_UV_EDGE, BM_ELEM_TAG)); + BMVert *v = l->v; + BLI_assert(v->e); + const BMEdge *e_iter, *e_first; + e_iter = e_first = v->e; + do { + if (e_iter->l == nullptr) { + continue; + } + BMLoop *l_first = e_iter->l; + BMLoop *l_iter = l_first; + do { + if (BM_elem_flag_test(l_iter->f, BM_ELEM_HIDDEN)) { + continue; + } + if (l_iter->v != v) { + continue; + } + /* Connected to a selected edge. */ + if (l_iter != l) { + if (BM_elem_flag_test(l_iter, hflag) || BM_elem_flag_test(l_iter->prev, hflag)) { + if (BM_loop_uv_share_vert_check(l, l_iter, cd_loop_uv_offset)) { + return true; + } + } + } + } while ((l_iter = l_iter->radial_next) != l_first); + } while ((e_iter = bmesh_disk_edge_next(e_iter, v)) != e_first); + return false; +} + +bool BM_loop_vert_uvselect_check_other_edge(BMLoop *l, + const char hflag, + const int cd_loop_uv_offset) +{ + BLI_assert(ELEM(hflag, BM_ELEM_SELECT, BM_ELEM_TAG)); + BMVert *v = l->v; + BLI_assert(v->e); + const BMEdge *e_iter, *e_first; + e_iter = e_first = v->e; + do { + if (e_iter->l == nullptr) { + continue; + } + BMLoop *l_first = e_iter->l; + BMLoop *l_iter = l_first; + do { + if (BM_elem_flag_test(l_iter->f, BM_ELEM_HIDDEN)) { + continue; + } + if (l_iter->v != v) { + continue; + } + /* Connected to a selected edge. */ + if (l_iter != l) { + if (((!BM_elem_flag_test(l_iter->e, BM_ELEM_HIDDEN)) && + BM_elem_flag_test(l_iter->e, hflag)) || + ((!BM_elem_flag_test(l_iter->prev->e, BM_ELEM_HIDDEN)) && + BM_elem_flag_test(l_iter->prev->e, hflag))) + { + if (BM_loop_uv_share_vert_check(l, l_iter, cd_loop_uv_offset)) { + return true; + } + } + } + } while ((l_iter = l_iter->radial_next) != l_first); + } while ((e_iter = bmesh_disk_edge_next(e_iter, v)) != e_first); + return false; +} + +bool BM_loop_vert_uvselect_check_other_face(BMLoop *l, + const char hflag, + const int cd_loop_uv_offset) +{ + BLI_assert(ELEM(hflag, BM_ELEM_SELECT, BM_ELEM_SELECT_UV, BM_ELEM_TAG)); + BMVert *v = l->v; + BLI_assert(v->e); + const BMEdge *e_iter, *e_first; + e_iter = e_first = v->e; + do { + if (e_iter->l == nullptr) { + continue; + } + BMLoop *l_first = e_iter->l; + BMLoop *l_iter = l_first; + do { + if (BM_elem_flag_test(l_iter->f, BM_ELEM_HIDDEN)) { + continue; + } + if (l_iter->v != v) { + continue; + } + if (l_iter != l) { + if (BM_elem_flag_test(l_iter->f, hflag)) { + if (BM_loop_uv_share_vert_check(l, l_iter, cd_loop_uv_offset)) { + return true; + } + } + } + } while ((l_iter = l_iter->radial_next) != l_first); + } while ((e_iter = bmesh_disk_edge_next(e_iter, v)) != e_first); + return false; +} + +bool BM_loop_edge_uvselect_check_other_loop_edge(BMLoop *l, + const char hflag, + const int cd_loop_uv_offset) +{ + BLI_assert(ELEM(hflag, BM_ELEM_SELECT, BM_ELEM_SELECT_UV_EDGE, BM_ELEM_TAG)); + BMLoop *l_iter = l; + do { + if (BM_elem_flag_test(l_iter->f, BM_ELEM_HIDDEN)) { + continue; + } + if (l_iter != l) { + if (BM_elem_flag_test(l_iter, hflag)) { + if (BM_loop_uv_share_edge_check(l, l_iter, cd_loop_uv_offset)) { + return true; + } + } + } + } while ((l_iter = l_iter->radial_next) != l); + return false; +} + +bool BM_loop_edge_uvselect_check_other_face(BMLoop *l, + const char hflag, + const int cd_loop_uv_offset) +{ + BLI_assert(ELEM(hflag, BM_ELEM_SELECT, BM_ELEM_SELECT_UV)); + BMLoop *l_iter = l; + do { + if (BM_elem_flag_test(l_iter->f, BM_ELEM_HIDDEN)) { + continue; + } + if (l_iter != l) { + if (BM_elem_flag_test(l_iter->f, hflag)) { + if (BM_loop_uv_share_edge_check(l, l_iter, cd_loop_uv_offset)) { + return true; + } + } + } + } while ((l_iter = l_iter->radial_next) != l); + return false; +} + +bool BM_face_uvselect_check_edges_all(BMFace *f) +{ + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + return false; + } + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + if (!BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV_EDGE)) { + return false; + } + } while ((l_iter = l_iter->next) != l_first); + return true; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UV Selection Functions + * \{ */ + +void BM_loop_vert_uvselect_set_noflush(BMesh *bm, BMLoop *l, bool select) +{ + /* Only select if it's valid, otherwise the result wont be used. */ + BLI_assert(bm->uv_select_sync_valid); + UNUSED_VARS_NDEBUG(bm); + + /* Selecting when hidden must be prevented by the caller. + * Allow de-selecting as this may be useful at times. */ + BLI_assert(!BM_elem_flag_test(l->f, BM_ELEM_HIDDEN) || (select == false)); + + /* NOTE: don't do any flushing here as it's too expensive to walk over connected geometry. + * These can be handled in separate operations. */ + BM_elem_flag_set(l, BM_ELEM_SELECT_UV, select); +} + +void BM_loop_edge_uvselect_set_noflush(BMesh *bm, BMLoop *l, bool select) +{ + /* Only select if it's valid, otherwise the result wont be used. */ + BLI_assert(bm->uv_select_sync_valid); + UNUSED_VARS_NDEBUG(bm); + + /* Selecting when hidden must be prevented by the caller. + * Allow de-selecting as this may be useful at times. */ + BLI_assert(!BM_elem_flag_test(l->f, BM_ELEM_HIDDEN) || (select == false)); + + /* NOTE: don't do any flushing here as it's too expensive to walk over connected geometry. + * These can be handled in separate operations. */ + BM_elem_flag_set(l, BM_ELEM_SELECT_UV_EDGE, select); +} + +void BM_loop_edge_uvselect_set(BMesh *bm, BMLoop *l, bool select) +{ + BM_loop_edge_uvselect_set_noflush(bm, l, select); + + BM_loop_vert_uvselect_set_noflush(bm, l, select); + BM_loop_vert_uvselect_set_noflush(bm, l->next, select); +} + +void BM_face_uvselect_set_noflush(BMesh *bm, BMFace *f, bool select) +{ + /* Only select if it's valid, otherwise the result wont be used. */ + BLI_assert(bm->uv_select_sync_valid); + UNUSED_VARS_NDEBUG(bm); + + /* Selecting when hidden must be prevented by the caller. + * Allow de-selecting as this may be useful at times. */ + BLI_assert(!BM_elem_flag_test(f, BM_ELEM_HIDDEN) || (select == false)); + + /* NOTE: don't do any flushing here as it's too expensive to walk over connected geometry. + * These can be handled in separate operations. */ + BM_elem_flag_set(f, BM_ELEM_SELECT_UV, select); +} + +void BM_face_uvselect_set(BMesh *bm, BMFace *f, bool select) +{ + BM_face_uvselect_set_noflush(bm, f, select); + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + BM_loop_vert_uvselect_set_noflush(bm, l_iter, select); + BM_loop_edge_uvselect_set_noflush(bm, l_iter, select); + } while ((l_iter = l_iter->next) != l_first); +} + +bool BM_mesh_uvselect_clear(BMesh *bm) +{ + if (bm->uv_select_sync_valid == false) { + return false; + } + bm->uv_select_sync_valid = false; + return true; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UV Selection Functions (Shared) + * \{ */ + +void BM_loop_vert_uvselect_set_shared(BMesh *bm, + BMLoop *l, + bool select, + const int cd_loop_uv_offset) +{ + BM_loop_vert_uvselect_set_noflush(bm, l, select); + + BMVert *v = l->v; + BLI_assert(v->e); + const BMEdge *e_iter, *e_first; + e_iter = e_first = v->e; + do { + if (e_iter->l == nullptr) { + continue; + } + BMLoop *l_first = e_iter->l; + BMLoop *l_iter = l_first; + do { + if (BM_elem_flag_test(l_iter->f, BM_ELEM_HIDDEN)) { + continue; + } + if (l_iter->v != v) { + continue; + } + if (l_iter != l) { + if (BM_elem_flag_test_bool(l_iter, BM_ELEM_SELECT_UV) != select) { + if (BM_loop_uv_share_vert_check(l, l_iter, cd_loop_uv_offset)) { + BM_loop_vert_uvselect_set_noflush(bm, l_iter, select); + } + } + } + } while ((l_iter = l_iter->radial_next) != l_first); + } while ((e_iter = bmesh_disk_edge_next(e_iter, v)) != e_first); +} + +void BM_loop_edge_uvselect_set_shared(BMesh *bm, + BMLoop *l, + bool select, + const int cd_loop_uv_offset) +{ + BM_loop_edge_uvselect_set_noflush(bm, l, select); + + BMLoop *l_iter = l->radial_next; + /* Check it's not a boundary. */ + if (l_iter != l) { + do { + if (BM_elem_flag_test(l_iter->f, BM_ELEM_HIDDEN)) { + continue; + } + if (BM_elem_flag_test_bool(l_iter, BM_ELEM_SELECT_UV_EDGE) != select) { + if (BM_loop_uv_share_edge_check(l, l_iter, cd_loop_uv_offset)) { + BM_loop_edge_uvselect_set_noflush(bm, l_iter, select); + } + } + } while ((l_iter = l_iter->radial_next) != l); + } +} + +void BM_face_uvselect_set_shared(BMesh *bm, BMFace *f, bool select, const int cd_loop_uv_offset) +{ + BM_face_uvselect_set_noflush(bm, f, select); + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + BM_loop_vert_uvselect_set_shared(bm, l_iter, select, cd_loop_uv_offset); + BM_loop_edge_uvselect_set_shared(bm, l_iter, select, cd_loop_uv_offset); + } while ((l_iter = l_iter->next) != l_first); +} + +void BM_mesh_uvselect_set_elem_shared(BMesh *bm, + bool select, + const int cd_loop_uv_offset, + const blender::Span loop_verts, + const blender::Span loop_edges, + const blender::Span faces) +{ + /* TODO: this could be optimized to reduce traversal of connected UV's for every element. */ + + for (BMLoop *l_vert : loop_verts) { + BM_loop_vert_uvselect_set_shared(bm, l_vert, select, cd_loop_uv_offset); + } + for (BMLoop *l_edge : loop_edges) { + BM_loop_edge_uvselect_set_shared(bm, l_edge, select, cd_loop_uv_offset); + + if (select) { + BM_loop_vert_uvselect_set_shared(bm, l_edge, select, cd_loop_uv_offset); + BM_loop_vert_uvselect_set_shared(bm, l_edge->next, select, cd_loop_uv_offset); + } + } + for (BMFace *f : faces) { + if (select) { + BM_face_uvselect_set_shared(bm, f, select, cd_loop_uv_offset); + } + else { + BM_face_uvselect_set_noflush(bm, f, select); + } + } + + /* Only de-select shared elements if they are no longer connected to a selection. */ + if (!select) { + for (BMLoop *l_edge : loop_edges) { + if (BM_elem_flag_test(l_edge->f, BM_ELEM_HIDDEN)) { + continue; + } + /* If any of the vertices from the edges are no longer connected to a selected edge + * de-select the entire vertex.. */ + for (BMLoop *l_edge_vert : {l_edge, l_edge->next}) { + if (!BM_loop_vert_uvselect_check_other_loop_edge( + l_edge_vert, BM_ELEM_SELECT_UV_EDGE, cd_loop_uv_offset)) + { + BM_loop_vert_uvselect_set_shared(bm, l_edge_vert, false, cd_loop_uv_offset); + } + } + } + + /* De-select edge pass. */ + for (BMFace *f : faces) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + if (!BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV)) { + /* Already handled. */ + continue; + } + if (!BM_loop_edge_uvselect_check_other_face(l_iter, BM_ELEM_SELECT_UV, cd_loop_uv_offset)) + { + BM_loop_edge_uvselect_set_shared(bm, l_iter, false, cd_loop_uv_offset); + } + } while ((l_iter = l_iter->next) != l_first); + } + + /* De-select vert pass. */ + for (BMFace *f : faces) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + if (!BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV_EDGE)) { + /* Already handled. */ + continue; + } + if (!BM_loop_vert_uvselect_check_other_loop_edge( + l_iter, BM_ELEM_SELECT_UV_EDGE, cd_loop_uv_offset)) + { + BM_loop_vert_uvselect_set_shared(bm, l_iter, false, cd_loop_uv_offset); + } + } while ((l_iter = l_iter->next) != l_first); + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UV Selection Picking Versions of Selection Functions + * + * These functions differ in that they perform all necessary flushing but do so only on + * local elements. This is only practical with a small number of elements since it'd + * be inefficient on large selections. + * + * Note that we *could* also support selecting face-corners from the 3D viewport + * using these functions, however that's not yet supported. + * + * Selection Modes & Flushing + * ========================== + * + * Picking an edge in face-select mode or a vertex in edge-select mode is not supported. + * This is logical because the user cannot select a single vertex in face select mode. + * As these functions are exposed publicly for picking, this makes some sense. + * + * Internally however, these functions are currently used by #BM_mesh_uvselect_set_elem_from_mesh, + * which corrects "isolated" elements which should not be selected based on the selection-mode. + * \{ */ + +static void bm_vert_uvselect_set_pick(BMesh *bm, + BMVert *v, + const bool select, + const BMUVSelectPickParams & /*uv_pick_params*/, + bool caller_handles_edge_or_face_mode) +{ + if (caller_handles_edge_or_face_mode == false) { + /* With de-selection, isolated vertices/edges wont be de-selected. + * In practice users should not be picking edges when in face select mode. */ + BLI_assert_msg(bm->selectmode & (SCE_SELECT_VERTEX), + "Picking verts in edge or face-select mode is not supported."); + } + /* NOTE: it doesn't make sense to check `uv_pick_params.shared` in this context because, + * unlike edges and faces, a vertex is logically connected to all corners that use it, + * so there is no way to use the UV coordinates to differentiate one UV region from another. */ + + if (BM_elem_flag_test(v, BM_ELEM_HIDDEN)) { + return; + } + + /* Must be connected to edges. */ + if (v->e == nullptr) { + return; + } + + if (select) { + const BMEdge *e_iter, *e_first; + e_iter = e_first = v->e; + do { + if (e_iter->l == nullptr) { + continue; + } + BMLoop *l_radial_iter, *l_radial_first; + l_radial_iter = l_radial_first = e_iter->l; + do { + if (BM_elem_flag_test(l_radial_iter->f, BM_ELEM_HIDDEN)) { + continue; + } + if (v != l_radial_iter->v) { + continue; + } + /* Select vertex. */ + BM_loop_vert_uvselect_set_noflush(bm, l_radial_iter, true); + + /* Select edges if adjacent vertices are selected. */ + if (BM_elem_flag_test(l_radial_iter->next, BM_ELEM_SELECT_UV)) { + BM_loop_edge_uvselect_set_noflush(bm, l_radial_iter, true); + } + if (BM_elem_flag_test(l_radial_iter->prev, BM_ELEM_SELECT_UV)) { + BM_loop_edge_uvselect_set_noflush(bm, l_radial_iter->prev, true); + } + /* Select face if all edges are selected. */ + if (!BM_elem_flag_test(l_radial_iter->f, BM_ELEM_HIDDEN) && + !BM_elem_flag_test(l_radial_iter->f, BM_ELEM_SELECT_UV)) + { + if (BM_face_uvselect_check_edges_all(l_radial_iter->f)) { + BM_face_uvselect_set_noflush(bm, l_radial_iter->f, true); + } + } + } while ((l_radial_iter = l_radial_iter->radial_next) != l_radial_first); + } while ((e_iter = bmesh_disk_edge_next(e_iter, v)) != e_first); + } + else { + const BMEdge *e_iter, *e_first; + e_iter = e_first = v->e; + do { + if (e_iter->l == nullptr) { + continue; + } + BMLoop *l_radial_iter, *l_radial_first; + l_radial_iter = l_radial_first = e_iter->l; + do { + if (BM_elem_flag_test(l_radial_iter->f, BM_ELEM_HIDDEN)) { + continue; + } + if (v != l_radial_iter->v) { + continue; + } + /* Deselect vertex. */ + BM_loop_vert_uvselect_set_noflush(bm, l_radial_iter, false); + /* Deselect edges. */ + BM_loop_edge_uvselect_set_noflush(bm, l_radial_iter, false); + BM_loop_edge_uvselect_set_noflush(bm, l_radial_iter->prev, false); + /* Deselect connected face. */ + BM_face_uvselect_set_noflush(bm, l_radial_iter->f, false); + } while ((l_radial_iter = l_radial_iter->radial_next) != l_radial_first); + } while ((e_iter = bmesh_disk_edge_next(e_iter, v)) != e_first); + } +} + +static void bm_edge_uvselect_set_pick(BMesh *bm, + BMEdge *e, + const bool select, + const BMUVSelectPickParams &uv_pick_params, + const bool caller_handles_face_mode) +{ + if (caller_handles_face_mode == false) { + /* With de-selection, isolated vertices/edges wont be de-selected. + * In practice users should not be picking edges when in face select mode. */ + BLI_assert_msg(bm->selectmode & (SCE_SELECT_VERTEX | SCE_SELECT_EDGE), + "Picking edges in face-select mode is not supported."); + } + + if (BM_elem_flag_test(e, BM_ELEM_HIDDEN)) { + return; + } + + /* Must be connected to faces. */ + if (e->l == nullptr) { + return; + } + + if (uv_pick_params.shared == false) { + BMLoop *l_iter, *l_first; + + if (select) { + bool any_faces_unselected = false; + l_iter = l_first = e->l; + do { + if (BM_elem_flag_test(l_iter->f, BM_ELEM_HIDDEN)) { + continue; + } + + BM_loop_edge_uvselect_set_noflush(bm, l_iter, true); + + BM_loop_vert_uvselect_set_noflush(bm, l_iter, true); + BM_loop_vert_uvselect_set_noflush(bm, l_iter->next, true); + + if (any_faces_unselected == false) { + if (!BM_elem_flag_test(l_iter->f, BM_ELEM_SELECT_UV)) { + any_faces_unselected = true; + } + } + } while ((l_iter = l_iter->radial_next) != l_first); + + /* Flush selection to faces when all edges in connected faces are now selected. */ + if (any_faces_unselected) { + l_iter = l_first = e->l; + do { + if (BM_elem_flag_test(l_iter->f, BM_ELEM_HIDDEN)) { + continue; + } + if (!BM_elem_flag_test(l_iter->f, BM_ELEM_SELECT_UV)) { + if (BM_face_uvselect_check_edges_all(l_iter->f)) { + BM_face_uvselect_set_noflush(bm, l_iter->f, true); + } + } + } while ((l_iter = l_iter->radial_next) != l_first); + } + } + else { + l_iter = l_first = e->l; + do { + if (BM_elem_flag_test(l_iter->f, BM_ELEM_HIDDEN)) { + continue; + } + BM_loop_edge_uvselect_set_noflush(bm, l_iter, false); + if (!BM_elem_flag_test(l_iter->prev, BM_ELEM_SELECT_UV_EDGE)) { + BM_loop_vert_uvselect_set_noflush(bm, l_iter, false); + } + if (!BM_elem_flag_test(l_iter->next, BM_ELEM_SELECT_UV_EDGE)) { + BM_loop_vert_uvselect_set_noflush(bm, l_iter->next, false); + } + BM_face_uvselect_set_noflush(bm, l_iter->f, false); + } while ((l_iter = l_iter->radial_next) != l_first); + } + return; + } + + /* NOTE(@ideasman42): this is awkward as the edge may reference multiple island bounds. + * - De-selecting will de-select all which makes sense. + * - Selecting will also select all which is not likely to be all that useful for users. + * + * We could attempt to use the surrounding selection to *guess* which UV island selection + * to extend but this seems error prone as it depends on the order elements are selected + * so it's it only likely to work in some situations. + * + * To *properly* solve this we would be better off to support picking edge+face (loop) + * combinations from the 3D viewport, so picking the edge would determine the loop which would + * be selected, but this is a much bigger change. + * + * In practice users are likely to prefer face selection when working with UV islands anyway. */ + + BMLoop *l_iter, *l_first; + + if (select) { + bool any_faces_unselected = false; + l_iter = l_first = e->l; + do { + if (BM_elem_flag_test(l_iter->f, BM_ELEM_HIDDEN)) { + continue; + } + + BM_loop_edge_uvselect_set_noflush(bm, l_iter, true); + + BM_loop_vert_uvselect_set_noflush(bm, l_iter, true); + BM_loop_vert_uvselect_set_noflush(bm, l_iter->next, true); + + if (any_faces_unselected == false) { + if (!BM_elem_flag_test(l_iter->f, BM_ELEM_SELECT_UV)) { + any_faces_unselected = true; + } + } + } while ((l_iter = l_iter->radial_next) != l_first); + + /* Flush selection to faces when all edges in connected faces are now selected. */ + if (any_faces_unselected) { + l_iter = l_first = e->l; + do { + if (BM_elem_flag_test(l_iter->f, BM_ELEM_HIDDEN)) { + continue; + } + if (!BM_elem_flag_test(l_iter->f, BM_ELEM_SELECT_UV)) { + if (BM_face_uvselect_check_edges_all(l_iter->f)) { + BM_face_uvselect_set_noflush(bm, l_iter->f, true); + } + } + } while ((l_iter = l_iter->radial_next) != l_first); + } + } + else { + l_iter = l_first = e->l; + do { + if (BM_elem_flag_test(l_iter->f, BM_ELEM_HIDDEN)) { + continue; + } + BM_loop_edge_uvselect_set_noflush(bm, l_iter, false); + if (!BM_elem_flag_test(l_iter->prev, BM_ELEM_SELECT_UV_EDGE)) { + BM_loop_vert_uvselect_set_noflush(bm, l_iter, false); + } + if (!BM_elem_flag_test(l_iter->next, BM_ELEM_SELECT_UV_EDGE)) { + BM_loop_vert_uvselect_set_noflush(bm, l_iter->next, false); + } + BM_face_uvselect_set_noflush(bm, l_iter->f, false); + } while ((l_iter = l_iter->radial_next) != l_first); + + /* Ensure connected vertices remain selected when they are connected to selected edges. */ + l_iter = l_first = e->l; + do { + if (BM_elem_flag_test(l_iter->f, BM_ELEM_HIDDEN)) { + continue; + } + for (BMLoop *l_edge_vert : {l_iter, l_iter->next}) { + if (BM_elem_flag_test(l_edge_vert, BM_ELEM_SELECT_UV)) { + /* This was not de-selected. */ + continue; + } + if (BM_loop_vert_uvselect_check_other_loop_edge( + l_edge_vert, BM_ELEM_SELECT_UV_EDGE, uv_pick_params.cd_loop_uv_offset)) + { + BM_loop_vert_uvselect_set_noflush(bm, l_edge_vert, true); + } + else { + /* It's possible there are isolated selected vertices, + * although in edge select mode this should not happen. */ + BM_loop_vert_uvselect_set_shared( + bm, l_edge_vert, false, uv_pick_params.cd_loop_uv_offset); + } + } + } while ((l_iter = l_iter->radial_next) != l_first); + } +} + +static void bm_face_uvselect_set_pick(BMesh *bm, + BMFace *f, + const bool select, + const BMUVSelectPickParams &uv_pick_params) +{ + /* Picking faces is valid in all selection modes. */ + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + return; + } + + BMLoop *l_iter, *l_first; + + if (uv_pick_params.shared == false) { + BM_face_uvselect_set(bm, f, select); + return; + } + + if (select) { + BM_face_uvselect_set_noflush(bm, f, true); + + /* Setting these values first. */ + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + BM_loop_vert_uvselect_set_noflush(bm, l_iter, true); + BM_loop_edge_uvselect_set_noflush(bm, l_iter, true); + } while ((l_iter = l_iter->next) != l_first); + + /* Set other values. */ + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + BM_loop_vert_uvselect_set_shared(bm, l_iter, true, uv_pick_params.cd_loop_uv_offset); + BM_loop_edge_uvselect_set_shared(bm, l_iter, true, uv_pick_params.cd_loop_uv_offset); + } while ((l_iter = l_iter->next) != l_first); + } + else { + BM_face_uvselect_set_noflush(bm, f, false); + + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + BM_loop_vert_uvselect_set_noflush(bm, l_iter, false); + BM_loop_edge_uvselect_set_noflush(bm, l_iter, false); + /* Vertex. */ + if (BM_loop_vert_uvselect_check_other_face( + l_iter, BM_ELEM_SELECT_UV, uv_pick_params.cd_loop_uv_offset)) + { + BM_loop_vert_uvselect_set_noflush(bm, l_iter, true); + } + else { + BM_loop_vert_uvselect_set_shared(bm, l_iter, false, uv_pick_params.cd_loop_uv_offset); + } + /* Edge. */ + if (BM_loop_edge_uvselect_check_other_face( + l_iter, BM_ELEM_SELECT_UV, uv_pick_params.cd_loop_uv_offset)) + { + BM_loop_edge_uvselect_set_noflush(bm, l_iter, true); + } + else { + BM_loop_edge_uvselect_set_shared(bm, l_iter, false, uv_pick_params.cd_loop_uv_offset); + } + } while ((l_iter = l_iter->next) != l_first); + } +} + +void BM_vert_uvselect_set_pick(BMesh *bm, + BMVert *v, + bool select, + const BMUVSelectPickParams ¶ms) +{ + const bool caller_handles_edge_or_face_mode = false; + bm_vert_uvselect_set_pick(bm, v, select, params, caller_handles_edge_or_face_mode); +} +void BM_edge_uvselect_set_pick(BMesh *bm, + BMEdge *e, + bool select, + const BMUVSelectPickParams ¶ms) +{ + const bool caller_handles_face_mode = false; + bm_edge_uvselect_set_pick(bm, e, select, params, caller_handles_face_mode); +} +void BM_face_uvselect_set_pick(BMesh *bm, + BMFace *f, + bool select, + const BMUVSelectPickParams ¶ms) +{ + /* Picking faces is valid in all modes. */ + bm_face_uvselect_set_pick(bm, f, select, params); +} + +/** + * Ensure isolated elements aren't selected which should be unselected based on `select_mode`. + * + * Regarding Picking + * ================= + * + * Run this when picking a vertex in edge selection mode or an edge in face select mode. + * + * This is not supported by individual picking, however when operating on many elements, + * it's useful to be able to support this so users of the API can select vertices for example + * Without it failing entirely because the users has the mesh in edge/face selection mode. + */ +static void bm_mesh_uvselect_mode_flush_down_deselect_only(BMesh *bm, + const short select_mode, + const int cd_loop_uv_offset, + const bool shared, + const bool check_verts, + const bool check_edges) +{ + if (!(check_verts || check_edges)) { + return; + } + + /* No additional work needed. */ + bool do_check = false; + if (select_mode & SCE_SELECT_VERTEX) { + /* Pass. */ + } + else if (select_mode & SCE_SELECT_EDGE) { + if (check_verts) { + do_check = true; + } + } + else if (select_mode & SCE_SELECT_FACE) { + if (check_verts || check_edges) { + do_check = true; + } + } + + if (do_check == false) { + return; + } + + /* This requires a fairly specific kind of flushing. + * - It's only necessary to flush down (faces -> edges, edges -> verts). + * - Only select/deselect is needed. + * Do this inline. + */ + if (select_mode & SCE_SELECT_EDGE) { + /* Deselect isolated vertices. */ + BMIter iter; + BMFace *f; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + /* Only handle faces that are partially selected. */ + if (BM_elem_flag_test(f, BM_ELEM_SELECT_UV)) { + continue; + } + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + if (BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV) && + /* Skip the UV check if either edge is selected. */ + !(BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV_EDGE) || + BM_elem_flag_test(l_iter->prev, BM_ELEM_SELECT_UV_EDGE))) + { + if ((shared == false) || !BM_loop_vert_uvselect_check_other_loop_edge( + l_iter, BM_ELEM_SELECT_UV_EDGE, cd_loop_uv_offset)) + { + BM_elem_flag_disable(l_iter, BM_ELEM_SELECT_UV); + } + } + } while ((l_iter = l_iter->next) != l_first); + } + } + else if (select_mode & SCE_SELECT_FACE) { + /* Deselect isolated vertices & edges. */ + BMIter iter; + BMFace *f; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + /* Only handle faces that are partially selected. */ + if (BM_elem_flag_test(f, BM_ELEM_SELECT_UV)) { + continue; + } + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + if (BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV_EDGE)) { + if (!BM_loop_edge_uvselect_check_other_face( + l_iter, BM_ELEM_SELECT_UV, cd_loop_uv_offset)) + { + BM_elem_flag_disable(l_iter, BM_ELEM_SELECT_UV_EDGE); + } + } + } while ((l_iter = l_iter->next) != l_first); + bool e_prev_select = BM_elem_flag_test(l_iter->prev, BM_ELEM_SELECT_UV_EDGE); + l_iter = l_first; + do { + const bool e_iter_select = BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV_EDGE); + /* Skip the UV check if either edge is selected. */ + if (BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV) && !(e_prev_select || e_iter_select)) { + if ((shared == false) || !BM_loop_vert_uvselect_check_other_face( + l_iter, BM_ELEM_SELECT_UV, cd_loop_uv_offset)) + { + BM_elem_flag_disable(l_iter, BM_ELEM_SELECT_UV); + } + } + e_prev_select = e_iter_select; + } while ((l_iter = l_iter->next) != l_first); + } + } +} + +void BM_mesh_uvselect_set_elem_from_mesh(BMesh *bm, + const bool select, + const BMUVSelectPickParams ¶ms, + const blender::VectorList &verts, + const blender::VectorList &edges, + const blender::VectorList &faces) +{ + const bool check_verts = !verts.is_empty(); + const bool check_edges = !edges.is_empty(); + + /* TODO(@ideasman42): select picking may be slow because it does flushing too. + * Although in practice it seems fast-enough. This should be handled more efficiently. */ + + for (BMVert *v : verts) { + bm_vert_uvselect_set_pick(bm, v, select, params, true); + } + for (BMEdge *e : edges) { + bm_edge_uvselect_set_pick(bm, e, select, params, true); + } + for (BMFace *f : faces) { + bm_face_uvselect_set_pick(bm, f, select, params); + } + + bm_mesh_uvselect_mode_flush_down_deselect_only( + bm, bm->selectmode, params.cd_loop_uv_offset, params.shared, check_verts, check_edges); +} + +void BM_mesh_uvselect_set_elem_from_mesh(BMesh *bm, + bool select, + const BMUVSelectPickParams ¶ms, + blender::Span verts, + blender::Span edges, + blender::Span faces) +{ + const bool check_verts = !verts.is_empty(); + const bool check_edges = !edges.is_empty(); + + for (BMVert *v : verts) { + BM_vert_uvselect_set_pick(bm, v, select, params); + } + for (BMEdge *e : edges) { + BM_edge_uvselect_set_pick(bm, e, select, params); + } + for (BMFace *f : faces) { + BM_face_uvselect_set_pick(bm, f, select, params); + } + + bm_mesh_uvselect_mode_flush_down_deselect_only( + bm, bm->selectmode, params.cd_loop_uv_offset, params.shared, check_verts, check_edges); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UV Selection Flushing (Only Select/De-Select) + * \{ */ + +void BM_mesh_uvselect_flush_from_loop_verts_only_select(BMesh *bm) +{ + BMIter iter; + BMFace *f; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + bool all_select = true; + do { + if (BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV) && + BM_elem_flag_test(l_iter->next, BM_ELEM_SELECT_UV)) + { + BM_loop_edge_uvselect_set_noflush(bm, l_iter, true); + } + else { + all_select = false; + } + } while ((l_iter = l_iter->next) != l_first); + if (all_select) { + BM_face_uvselect_set_noflush(bm, f, true); + } + } +} + +void BM_mesh_uvselect_flush_from_loop_verts_only_deselect(BMesh *bm) +{ + BMIter iter; + BMFace *f; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + bool all_select = true; + do { + if (BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV) && + BM_elem_flag_test(l_iter->next, BM_ELEM_SELECT_UV)) + { + /* Pass. */ + } + else { + BM_loop_edge_uvselect_set_noflush(bm, l_iter, false); + all_select = false; + } + } while ((l_iter = l_iter->next) != l_first); + if (all_select == false) { + BM_face_uvselect_set_noflush(bm, f, false); + } + } +} + +void BM_mesh_uvselect_flush_from_loop_edges_only_select(BMesh *bm) +{ + BMIter iter; + BMFace *f; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + bool all_select = true; + do { + if (BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV_EDGE)) { + BM_loop_edge_uvselect_set(bm, l_iter, true); + } + else { + all_select = false; + } + } while ((l_iter = l_iter->next) != l_first); + if (all_select) { + BM_face_uvselect_set_noflush(bm, f, true); + } + } +} + +void BM_mesh_uvselect_flush_from_loop_edges_only_deselect(BMesh *bm) +{ + BMIter iter; + BMFace *f; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + bool all_select = true; + do { + BM_loop_vert_uvselect_set_noflush(bm, + l_iter, + (BM_elem_flag_test(l_iter->prev, BM_ELEM_SELECT_UV_EDGE) || + BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV_EDGE))); + + if (BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV_EDGE)) { + /* Pass. */ + } + else { + BM_loop_edge_uvselect_set_noflush(bm, l_iter, false); + all_select = false; + } + } while ((l_iter = l_iter->next) != l_first); + if (all_select == false) { + BM_face_uvselect_set_noflush(bm, f, false); + } + } +} + +void BM_mesh_uvselect_flush_from_faces_only_select(BMesh *bm) +{ + BMIter iter; + BMFace *f; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + + if (!BM_elem_flag_test(f, BM_ELEM_SELECT_UV)) { + continue; + } + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + BM_loop_vert_uvselect_set_noflush(bm, l_iter, true); + BM_loop_edge_uvselect_set_noflush(bm, l_iter, true); + } while ((l_iter = l_iter->next) != l_first); + } +} + +void BM_mesh_uvselect_flush_from_faces_only_deselect(BMesh *bm) +{ + BMIter iter; + BMFace *f; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + + if (BM_elem_flag_test(f, BM_ELEM_SELECT_UV)) { + continue; + } + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + BM_loop_vert_uvselect_set_noflush(bm, l_iter, false); + BM_loop_edge_uvselect_set_noflush(bm, l_iter, false); + } while ((l_iter = l_iter->next) != l_first); + } +} + +void BM_mesh_uvselect_flush_shared_only_select(BMesh *bm, const int cd_loop_uv_offset) +{ + BLI_assert(cd_loop_uv_offset >= 0); + BMIter iter; + BMFace *f; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + if (!BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV)) { + if (BM_loop_vert_uvselect_check_other_loop_vert( + l_iter, BM_ELEM_SELECT_UV, cd_loop_uv_offset)) + { + BM_loop_vert_uvselect_set_noflush(bm, l_iter, true); + } + } + if (!BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV_EDGE)) { + if (BM_loop_edge_uvselect_check_other_loop_edge( + l_iter, BM_ELEM_SELECT_UV_EDGE, cd_loop_uv_offset)) + { + BM_loop_edge_uvselect_set_noflush(bm, l_iter, true); + } + } + } while ((l_iter = l_iter->next) != l_first); + } +} + +void BM_mesh_uvselect_flush_shared_only_deselect(BMesh *bm, const int cd_loop_uv_offset) +{ + BLI_assert(cd_loop_uv_offset >= 0); + BMIter iter; + BMFace *f; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + if (BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV)) { + if (!BM_loop_vert_uvselect_check_other_loop_vert( + l_iter, BM_ELEM_SELECT_UV, cd_loop_uv_offset)) + { + BM_loop_vert_uvselect_set_noflush(bm, l_iter, false); + } + } + if (BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV_EDGE)) { + if (!BM_loop_edge_uvselect_check_other_loop_edge( + l_iter, BM_ELEM_SELECT_UV_EDGE, cd_loop_uv_offset)) + { + BM_loop_edge_uvselect_set_noflush(bm, l_iter, false); + } + } + } while ((l_iter = l_iter->next) != l_first); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UV Selection Flushing (Between Elements) + * \{ */ + +void BM_mesh_uvselect_flush_from_loop_verts(BMesh *bm) +{ + BMIter iter; + BMFace *f; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + + bool select_all = true; + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + const bool select = (BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV) && + BM_elem_flag_test(l_iter->next, BM_ELEM_SELECT_UV)); + BM_loop_edge_uvselect_set_noflush(bm, l_iter, select); + if (select == false) { + select_all = false; + } + } while ((l_iter = l_iter->next) != l_first); + BM_face_uvselect_set_noflush(bm, f, select_all); + } +} + +void BM_mesh_uvselect_flush_from_loop_edges(BMesh *bm, bool flush_down) +{ + BMIter iter; + BMFace *f; + + /* Clear vert/face select. */ + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + + if (flush_down) { + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + BM_loop_vert_uvselect_set_noflush(bm, l_iter, false); + } while ((l_iter = l_iter->next) != l_first); + } + BM_face_uvselect_set_noflush(bm, f, false); + } + + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + + bool select_all = true; + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + const bool select_edge = BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV_EDGE); + if (select_edge) { + if (flush_down) { + BM_loop_vert_uvselect_set_noflush(bm, l_iter, true); + BM_loop_vert_uvselect_set_noflush(bm, l_iter->next, true); + } + } + else { + select_all = false; + } + } while ((l_iter = l_iter->next) != l_first); + if (select_all) { + BM_face_uvselect_set_noflush(bm, f, true); + } + } +} + +void BM_mesh_uvselect_flush_from_faces(BMesh *bm, bool flush_down) +{ + if (!flush_down) { + return; /* NOP. */ + } + + BMIter iter; + BMFace *f; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + + const bool select_face = BM_elem_flag_test(f, BM_ELEM_SELECT_UV); + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + BM_loop_vert_uvselect_set_noflush(bm, l_iter, select_face); + BM_loop_edge_uvselect_set_noflush(bm, l_iter, select_face); + } while ((l_iter = l_iter->next) != l_first); + } +} + +void BM_mesh_uvselect_flush_from_verts(BMesh *bm, const bool select) +{ + if (select) { + BM_mesh_uvselect_flush_from_loop_verts_only_select(bm); + } + else { + BM_mesh_uvselect_flush_from_loop_verts_only_deselect(bm); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UV Selection Flushing (Selection Mode Aware) + * \{ */ + +void BM_mesh_uvselect_mode_flush_ex(BMesh *bm, const short selectmode, const bool flush_down) +{ + if (selectmode & SCE_SELECT_VERTEX) { + BM_mesh_uvselect_flush_from_loop_verts(bm); + } + else if (selectmode & SCE_SELECT_EDGE) { + BM_mesh_uvselect_flush_from_loop_edges(bm, flush_down); + } + else { + BM_mesh_uvselect_flush_from_faces(bm, flush_down); + } +} + +void BM_mesh_uvselect_mode_flush(BMesh *bm) +{ + BM_mesh_uvselect_mode_flush_ex(bm, bm->selectmode, false); +} + +void BM_mesh_uvselect_mode_flush_only_select(BMesh *bm) +{ + if (bm->selectmode & SCE_SELECT_VERTEX) { + BM_mesh_uvselect_flush_from_loop_verts_only_select(bm); + } + else if (bm->selectmode & SCE_SELECT_EDGE) { + BM_mesh_uvselect_flush_from_loop_edges_only_select(bm); + } + else { + /* Pass (nothing to do for faces). */ + } +} + +void BM_mesh_uvselect_mode_flush_update(BMesh *bm, + const short selectmode_old, + const short selectmode_new, + const int cd_loop_uv_offset) +{ + + if (highest_order_bit_s(selectmode_old) >= highest_order_bit_s(selectmode_new)) { + + if ((selectmode_old & SCE_SELECT_VERTEX) == 0 && (selectmode_new & SCE_SELECT_VERTEX)) { + /* When changing from edge/face to vertex selection, + * new edges/faces may be selected based on the vertex selection. */ + BM_mesh_uvselect_flush_from_loop_verts(bm); + } + else if ((selectmode_old & SCE_SELECT_EDGE) == 0 && (selectmode_new & SCE_SELECT_EDGE)) { + /* When changing from face to edge selection, + * new faces may be selected based on the edge selection. */ + BM_mesh_uvselect_flush_from_loop_edges(bm, false); + } + + /* Pass, no need to do anything when moving from edge to vertex mode (for e.g.). */ + return; + } + + bool do_flush_deselect_down = false; + if (selectmode_old & SCE_SELECT_VERTEX) { + if ((selectmode_new & SCE_SELECT_VERTEX) == 0) { + do_flush_deselect_down = true; + } + } + else if (selectmode_old & SCE_SELECT_EDGE) { + if ((selectmode_new & SCE_SELECT_EDGE) == 0) { + do_flush_deselect_down = true; + } + } + + if (do_flush_deselect_down == false) { + return; + } + + /* Perform two passes: + * + * - De-select all elements where the underlying elements are not selected. + * - De select any isolated elements. + * + * NOTE: As the mesh will have already had it's isolated elements de-selected, + * it may seem like this pass shouldn't be needed in UV space, + * however a vert/edge may be isolated in UV space while being connected to a + * selected edge/face in 3D space. + */ + + /* First pass: match underlying mesh. */ + BMIter iter; + BMFace *f; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + bool select_face = true; + do { + if (BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV)) { + if (!BM_elem_flag_test(l_iter->v, BM_ELEM_SELECT)) { + BM_elem_flag_disable(l_iter, BM_ELEM_SELECT_UV); + } + } + if (BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV_EDGE)) { + if (!BM_elem_flag_test(l_iter->e, BM_ELEM_SELECT)) { + BM_elem_flag_disable(l_iter, BM_ELEM_SELECT_UV_EDGE); + select_face = false; + } + } + else { + select_face = false; + } + } while ((l_iter = l_iter->next) != l_first); + + if (select_face == false) { + BM_elem_flag_disable(f, BM_ELEM_SELECT_UV); + } + } + + /* Second Pass: Ensure isolated elements are not selected. */ + if (cd_loop_uv_offset != -1) { + const bool shared = true; + const bool check_verts = (bm->totvertsel != 0); + const bool check_edges = (bm->totedgesel != 0); + bm_mesh_uvselect_mode_flush_down_deselect_only( + bm, selectmode_new, cd_loop_uv_offset, shared, check_verts, check_edges); + } +} + +void BM_mesh_uvselect_flush_post_subdivide(BMesh *bm, const int cd_loop_uv_offset) +{ + { + BMIter iter; + BMFace *f; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_SELECT)) { + BM_face_uvselect_set(bm, f, true); + } + } + } + + const bool use_edges = bm->selectmode & (SCE_SELECT_VERTEX | SCE_SELECT_EDGE); + if (use_edges) { + BMIter iter; + BMEdge *e; + BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) { + if (e->l == nullptr) { + continue; + } + if (BM_elem_flag_test(e, BM_ELEM_SELECT) && + /* This will have been handled if an attached face is selected. */ + !BM_edge_is_any_face_flag_test(e, BM_ELEM_SELECT)) + { + BMLoop *l_radial_iter, *l_radial_first; + l_radial_iter = l_radial_first = e->l; + do { + BM_loop_edge_uvselect_set(bm, l_radial_iter, true); + } while ((l_radial_iter = l_radial_iter->radial_next) != l_radial_first); + } + } + } + + /* Now select any "shared" UV's that are connected to an edge or face. */ + if (cd_loop_uv_offset != -1) { + BMIter iter; + BMFace *f; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + if (BM_elem_flag_test(f, BM_ELEM_SELECT_UV)) { + continue; + } + BMLoop *l_iter, *l_first; + + /* Setting these values first. */ + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + /* With vertex select mode, only handle vertices, then flush to edges -> faces. */ + if ((bm->selectmode & SCE_SELECT_VERTEX) == 0) { + /* Check edges first, since a selected edge also indicates a selected vertex. */ + if (!BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV_EDGE) && + BM_loop_edge_uvselect_check_other_loop_edge( + l_iter, BM_ELEM_SELECT_UV_EDGE, cd_loop_uv_offset)) + { + /* Check the other radial edge. */ + BM_loop_edge_uvselect_set(bm, l_iter, true); + } + } + /* Check the other radial vertex (a selected edge will have done this). */ + if (!BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV)) { + if (BM_loop_vert_uvselect_check_other_loop_vert( + l_iter, BM_ELEM_SELECT_UV, cd_loop_uv_offset)) + { + BM_loop_vert_uvselect_set_noflush(bm, l_iter, true); + } + } + } while ((l_iter = l_iter->next) != l_first); + } + } + + /* It's possible selecting a vertex or edge will cause other elements to have become selected. + * Flush up if necessary. */ + BM_mesh_uvselect_mode_flush_only_select(bm); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UV Selection Flushing (From/To Mesh) + * \{ */ + +/* Sticky Vertex. */ + +static void bm_mesh_uvselect_flush_from_mesh_sticky_vert_for_vert_mode(BMesh *bm) +{ + BMIter iter; + BMFace *f; + + /* UV select flags may be dirty, overwrite all. */ + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + const bool v_select = BM_elem_flag_test(l_iter->v, BM_ELEM_SELECT); + const bool e_select = BM_elem_flag_test(l_iter->e, BM_ELEM_SELECT); + BM_elem_flag_set(l_iter, BM_ELEM_SELECT_UV, v_select); + BM_elem_flag_set(l_iter, BM_ELEM_SELECT_UV_EDGE, e_select); + } while ((l_iter = l_iter->next) != l_first); + BM_elem_flag_set(f, BM_ELEM_SELECT_UV, BM_elem_flag_test(f, BM_ELEM_SELECT)); + } + bm->uv_select_sync_valid = true; +} + +static void bm_mesh_uvselect_flush_from_mesh_sticky_vert_for_edge_mode(BMesh *bm) +{ + BMIter iter; + BMFace *f; + + /* Clearing all makes the following logic simpler as + * since we only need to select UV's connected to selected edges. */ + bm_mesh_uvselect_disable_all(bm); + + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + if (BM_elem_flag_test(l_iter->e, BM_ELEM_SELECT)) { + BM_elem_flag_enable(l_iter, BM_ELEM_SELECT_UV_EDGE); + for (BMLoop *l_edge_vert : {l_iter, l_iter->next}) { + if (!BM_elem_flag_test(l_edge_vert, BM_ELEM_SELECT_UV)) { + BM_elem_flag_enable(l_edge_vert, BM_ELEM_SELECT_UV); + } + } + } + } while ((l_iter = l_iter->next) != l_first); + + if (BM_elem_flag_test(f, BM_ELEM_SELECT)) { + BM_elem_flag_enable(f, BM_ELEM_SELECT_UV); + } + } + bm->uv_select_sync_valid = true; +} + +static void bm_mesh_uvselect_flush_from_mesh_sticky_vert_for_face_mode(BMesh *bm) +{ + BMIter iter; + BMFace *f; + + /* Clearing all makes the following logic simpler as + * since we only need to select UV's connected to selected edges. */ + bm_mesh_uvselect_disable_all(bm); + + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + + if (BM_elem_flag_test(f, BM_ELEM_SELECT)) { + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + BM_elem_flag_enable(l_iter, BM_ELEM_SELECT_UV); + BM_elem_flag_enable(l_iter, BM_ELEM_SELECT_UV_EDGE); + + } while ((l_iter = l_iter->next) != l_first); + BM_elem_flag_enable(f, BM_ELEM_SELECT_UV); + } + } + bm->uv_select_sync_valid = true; +} + +/* Sticky Location. */ + +static void bm_mesh_uvselect_flush_from_mesh_sticky_location_for_vert_mode( + BMesh *bm, const int /*cd_loop_uv_offset*/) +{ + /* In this particular case use the same logic for sticky vertices, + * unlike faces & edges we can't know which island a selected vertex belongs to. + * + * NOTE: arguably this is only true for an isolated vertex selection, + * if there are surrounding selected edges/faces the vertex could only select UV's + * connected to those selected regions. However, if this logic was followed (at-run-time) + * it would mean that de-selecting a face could suddenly cause the vertex + * (attached to that face on another UV island) to become selected. + * Since that would be unexpected for users - just use this simple logic here. */ + bm_mesh_uvselect_flush_from_mesh_sticky_vert_for_vert_mode(bm); +} + +static void bm_mesh_uvselect_flush_from_mesh_sticky_location_for_edge_mode( + BMesh *bm, const int cd_loop_uv_offset) +{ + BMIter iter; + BMFace *f; + + /* UV select flags may be dirty, overwrite all. */ + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + bool e_prev_select = BM_elem_flag_test(l_iter->prev->e, BM_ELEM_SELECT); + do { + const bool e_iter_select = BM_elem_flag_test(l_iter->e, BM_ELEM_SELECT); + const bool v_iter_select = (BM_elem_flag_test(l_iter->v, BM_ELEM_SELECT) && + ((e_prev_select || e_iter_select) || + /* This is a more expensive check, order last. */ + BM_loop_vert_uvselect_check_other_edge( + l_iter, BM_ELEM_SELECT, cd_loop_uv_offset))); + + BM_elem_flag_set(l_iter, BM_ELEM_SELECT_UV, v_iter_select); + BM_elem_flag_set(l_iter, BM_ELEM_SELECT_UV_EDGE, e_iter_select); + e_prev_select = e_iter_select; + } while ((l_iter = l_iter->next) != l_first); + + const bool f_select = BM_elem_flag_test(f, BM_ELEM_SELECT); + BM_elem_flag_set(f, BM_ELEM_SELECT_UV, f_select); + } + bm->uv_select_sync_valid = true; +} + +static void bm_mesh_uvselect_flush_from_mesh_sticky_location_for_face_mode( + BMesh *bm, const int cd_loop_uv_offset) +{ + BMIter iter; + BMFace *f; + + /* UV select flags may be dirty, overwrite all. */ + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + + if (BM_elem_flag_test(f, BM_ELEM_SELECT)) { + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + BM_elem_flag_enable(l_iter, BM_ELEM_SELECT_UV | BM_ELEM_SELECT_UV_EDGE); + } while ((l_iter = l_iter->next) != l_first); + BM_elem_flag_enable(f, BM_ELEM_SELECT_UV); + } + else { + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + const bool v_iter_select = (BM_elem_flag_test(l_iter->v, BM_ELEM_SELECT) && + BM_loop_vert_uvselect_check_other_face( + l_iter, BM_ELEM_SELECT, cd_loop_uv_offset)); + const bool e_iter_select = (BM_elem_flag_test(l_iter->e, BM_ELEM_SELECT) && + BM_loop_edge_uvselect_check_other_face( + l_iter, BM_ELEM_SELECT, cd_loop_uv_offset)); + + BM_elem_flag_set(l_iter, BM_ELEM_SELECT_UV, v_iter_select); + BM_elem_flag_set(l_iter, BM_ELEM_SELECT_UV_EDGE, e_iter_select); + } while ((l_iter = l_iter->next) != l_first); + BM_elem_flag_disable(f, BM_ELEM_SELECT_UV); + } + } + bm->uv_select_sync_valid = true; +} + +/* Public API. */ + +void BM_mesh_uvselect_sync_from_mesh_sticky_location(BMesh *bm, const int cd_loop_uv_offset) +{ + if (bm->selectmode & SCE_SELECT_VERTEX) { + bm_mesh_uvselect_flush_from_mesh_sticky_location_for_vert_mode(bm, cd_loop_uv_offset); + } + else if (bm->selectmode & SCE_SELECT_EDGE) { + bm_mesh_uvselect_flush_from_mesh_sticky_location_for_edge_mode(bm, cd_loop_uv_offset); + } + else { /* `SCE_SELECT_FACE` */ + bm_mesh_uvselect_flush_from_mesh_sticky_location_for_face_mode(bm, cd_loop_uv_offset); + } + + BLI_assert(bm->uv_select_sync_valid); +} + +void BM_mesh_uvselect_sync_from_mesh_sticky_disabled(BMesh *bm) +{ + /* The mode is ignored when sticky selection is disabled, + * Always use the selection from the mesh. */ + bm_mesh_uvselect_flush_from_mesh_sticky_vert_for_vert_mode(bm); + BLI_assert(bm->uv_select_sync_valid); +} + +void BM_mesh_uvselect_sync_from_mesh_sticky_vert(BMesh *bm) +{ + if (bm->selectmode & SCE_SELECT_VERTEX) { + bm_mesh_uvselect_flush_from_mesh_sticky_vert_for_vert_mode(bm); + } + else if (bm->selectmode & SCE_SELECT_EDGE) { + bm_mesh_uvselect_flush_from_mesh_sticky_vert_for_edge_mode(bm); + } + else { /* `SCE_SELECT_FACE` */ + bm_mesh_uvselect_flush_from_mesh_sticky_vert_for_face_mode(bm); + } + BLI_assert(bm->uv_select_sync_valid); +} + +void BM_mesh_uvselect_sync_to_mesh(BMesh *bm) +{ + BLI_assert(bm->uv_select_sync_valid); + + /* Prevent clearing the selection from removing all selection history. + * This will be validated after flushing. */ + BM_SELECT_HISTORY_BACKUP(bm); + + BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_SELECT, false); + + if (bm->selectmode & SCE_SELECT_VERTEX) { + /* Simple, no need to worry about edge selection. */ + + /* Copy loop-vert to vert, then flush. */ + BMIter iter; + BMFace *f; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + if (BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV)) { + BM_vert_select_set(bm, l_iter->v, true); + } + } while ((l_iter = l_iter->next) != l_first); + } + + BM_mesh_select_flush_from_verts(bm, true); + } + else if (bm->selectmode & SCE_SELECT_EDGE) { + BMIter iter; + BMFace *f; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + /* Technically this should only need to check the edge + * because when a vertex isn't selected, it's connected edges shouldn't be. + * Check both in the unlikely case of an invalid selection. */ + bool face_select = true; + + do { + /* This requires the edges to have already been flushed to the vertices (assert next). */ + if (BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV)) { + BM_vert_select_set(bm, l_iter->v, true); + } + else { + face_select = false; + } + + if (BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV_EDGE)) { + /* If this fails, we've missed flushing. */ + BLI_assert(BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV) && + BM_elem_flag_test(l_iter->next, BM_ELEM_SELECT_UV)); + BM_edge_select_set(bm, l_iter->e, true); + } + else { + face_select = false; + } + } while ((l_iter = l_iter->next) != l_first); + if (face_select) { + BM_face_select_set_noflush(bm, f, true); + } + } + + /* It's possible that a face which is *not* UV-selected + * ends up with all it's edges selected. + * Perform the edge to face flush inline. */ + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + + /* If the face is hidden, we can't selected, + * If the face is already selected, it can be skipped here. */ + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN | BM_ELEM_SELECT)) { + continue; + } + bool face_select = true; + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + if (!BM_elem_flag_test(l_iter->e, BM_ELEM_SELECT)) { + face_select = false; + break; + } + } while ((l_iter = l_iter->next) != l_first); + + if (face_select) { + BM_face_select_set_noflush(bm, f, true); + } + } + } + else { /* `bm->selectmode & SCE_SELECT_FACE` */ + BMIter iter; + BMFace *f; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + bool face_select = true; + do { + if (!BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV) || + !BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV_EDGE)) + { + face_select = false; + break; + } + } while ((l_iter = l_iter->next) != l_first); + if (face_select) { + BM_face_select_set(bm, f, true); + } + } + } + + BM_SELECT_HISTORY_RESTORE(bm); + + BM_select_history_validate(bm); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UV Selection Validation + * + * Split the validity checks into categories. + * + * - UV selection and viewport selection are in sync. + * Where a selected UV-vertex must have it's viewport-vertex selected too. + * Where a selected viewport-vertex must have at least one selected UV. + * + * This is core to UV sync-select functioning properly. + * + * Failure to properly sync is likely to result in bugs where UV's aren't handled properly + * although it should not cause crashes. + * + * - UV selection flushing. + * Where the relationship between selected elements makes sense. + * - An face cannot be selected when one of it's vertices is de-selected. + * - An edge cannot be selected if one of it's vertices is de-selected. + * ... etc ... + * This is much the same as selection flushing for viewport selection. + * + * - Contiguous UV selection + * Where co-located UV's are all either selected or de-selected. + * + * Failure to select co-located UV's is *not* an error (on a data-correctness level) rather, + * it's something that's applied on a "tool" level - depending on UV sticky options. + * Depending on the tools, it may be intended that UV selection be contiguous across UV's. + * \{ */ + +/* Asserting can be useful to inspect the values while debugging. */ +#if 0 /* Useful when debugging. */ +# define MAYBE_ASSERT BLI_assert(0) +#elif 0 /* Can also be useful. */ +# define MAYBE_ASSERT printf(AT "\n") +#else +# define MAYBE_ASSERT +#endif + +#define INCF_MAYBE_ASSERT(var) \ + { \ + MAYBE_ASSERT; \ + (var) += 1; \ + } \ + ((void)0) + +static void bm_mesh_loop_clear_tag(BMesh *bm) +{ + BMIter fiter; + BMFace *f; + BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) { + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + BM_elem_flag_disable(l_iter, BM_ELEM_TAG); + } while ((l_iter = l_iter->next) != l_first); + } +} + +/** + * Check UV vertices and edges are synchronized with the viewport selection. + * + * UV face selection isn't checked here since this is handled as part of flushing checks. + */ +static bool bm_mesh_uvselect_check_viewport_sync(BMesh *bm, UVSelectValidateInfo_Sync &info_sub) +{ + bool is_valid = true; + + /* Vertices. */ + { + int &error_count = info_sub.count_uv_vert_any_selected_with_vert_unselected; + BLI_assert(error_count == 0); + BMIter fiter; + BMFace *f; + BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + if (BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV)) { + if (!BM_elem_flag_test(l_iter->v, BM_ELEM_SELECT)) { + INCF_MAYBE_ASSERT(error_count); + } + } + } while ((l_iter = l_iter->next) != l_first); + } + if (error_count) { + is_valid = false; + } + } + + { + int &error_count = info_sub.count_uv_vert_none_selected_with_vert_selected; + BLI_assert(error_count == 0); + BMIter viter; + BMIter liter; + + BMVert *v; + BM_ITER_MESH (v, &viter, bm, BM_VERTS_OF_MESH) { + if (BM_elem_flag_test(v, BM_ELEM_HIDDEN)) { + continue; + } + if (!BM_elem_flag_test(v, BM_ELEM_SELECT)) { + continue; + } + + bool any_loop_selected = false; + { + BMLoop *l; + BM_ITER_ELEM (l, &liter, v, BM_LOOPS_OF_VERT) { + if (BM_elem_flag_test(l, BM_ELEM_SELECT_UV)) { + any_loop_selected = true; + break; + } + } + } + + if (any_loop_selected == false) { + INCF_MAYBE_ASSERT(error_count); + } + } + if (error_count) { + is_valid = false; + } + } + + /* Edges. */ + { + int &error_count = info_sub.count_uv_edge_any_selected_with_edge_unselected; + BLI_assert(error_count == 0); + BMIter fiter; + BMFace *f; + BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + if (BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV_EDGE)) { + if (!BM_elem_flag_test(l_iter->v, BM_ELEM_SELECT)) { + INCF_MAYBE_ASSERT(error_count); + } + } + } while ((l_iter = l_iter->next) != l_first); + } + if (error_count) { + is_valid = false; + } + } + + /* When vertex selection is enabled, it's possible for UV's + * that don't form a selected UV edge to form a selected viewport edge. + * So, it only makes sense to perform this check in edge selection mode. */ + if ((bm->selectmode & SCE_SELECT_VERTEX) == 0) { + int &error_count = info_sub.count_uv_edge_none_selected_with_edge_selected; + BLI_assert(error_count == 0); + BMIter eiter; + + BMEdge *e; + BM_ITER_MESH (e, &eiter, bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(e, BM_ELEM_HIDDEN)) { + continue; + } + if (!BM_elem_flag_test(e, BM_ELEM_SELECT)) { + continue; + } + if (e->l == nullptr) { + continue; + } + bool any_loop_selected = false; + BMLoop *l_iter = e->l; + do { + if (BM_elem_flag_test(l_iter->f, BM_ELEM_HIDDEN)) { + continue; + } + if (BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV_EDGE)) { + any_loop_selected = true; + break; + } + } while ((l_iter = l_iter->radial_next) != e->l); + if (any_loop_selected == false) { + INCF_MAYBE_ASSERT(error_count); + } + } + if (error_count) { + is_valid = false; + } + } + + return is_valid; +} + +static bool bm_mesh_uvselect_check_flush(BMesh *bm, UVSelectValidateInfo_Flush &info_sub) +{ + bool is_valid = true; + + /* Vertices are flushed to edges. */ + { + int &error_count_selected = info_sub.count_uv_edge_selected_with_any_verts_unselected; + int &error_count_unselected = info_sub.count_uv_edge_unselected_with_all_verts_selected; + BLI_assert(error_count_selected == 0 && error_count_unselected == 0); + BMIter fiter; + BMFace *f; + BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + const bool v_curr_select = BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV); + const bool v_next_select = BM_elem_flag_test(l_iter->next, BM_ELEM_SELECT_UV); + if (BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV_EDGE)) { + if (!v_curr_select || !v_next_select) { + INCF_MAYBE_ASSERT(error_count_selected); + } + } + else { + if (v_curr_select && v_next_select) { + /* Only an error in with vertex selection mode. */ + if (bm->selectmode & SCE_SELECT_VERTEX) { + INCF_MAYBE_ASSERT(error_count_unselected); + } + } + } + } while ((l_iter = l_iter->next) != l_first); + } + if (error_count_selected || error_count_unselected) { + is_valid = false; + } + } + + /* Vertices & edges are flushed to faces. */ + { + int &error_count_verts_selected = info_sub.count_uv_face_selected_with_any_verts_unselected; + int &error_count_verts_unselected = info_sub.count_uv_face_unselected_with_all_verts_selected; + + int &error_count_edges_selected = info_sub.count_uv_face_selected_with_any_edges_unselected; + int &error_count_edges_unselected = info_sub.count_uv_face_unselected_with_all_edges_selected; + + BLI_assert(error_count_verts_selected == 0 && error_count_verts_unselected == 0); + BLI_assert(error_count_edges_selected == 0 && error_count_edges_unselected == 0); + BMIter fiter; + BMFace *f; + BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + int uv_vert_select = 0; + int uv_edge_select = 0; + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + if (BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV)) { + uv_vert_select += 1; + } + + if (BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV_EDGE)) { + uv_edge_select += 1; + } + } while ((l_iter = l_iter->next) != l_first); + + if (BM_elem_flag_test(f, BM_ELEM_SELECT_UV)) { + if (uv_vert_select != f->len) { + INCF_MAYBE_ASSERT(error_count_verts_selected); + } + if (uv_edge_select != f->len) { + INCF_MAYBE_ASSERT(error_count_edges_selected); + } + } + else { + /* Only an error with vertex or edge selection modes. */ + if (bm->selectmode & SCE_SELECT_VERTEX) { + if (uv_vert_select == f->len) { + INCF_MAYBE_ASSERT(error_count_verts_unselected); + } + } + else if (bm->selectmode & SCE_SELECT_EDGE) { + if (uv_edge_select == f->len) { + INCF_MAYBE_ASSERT(error_count_edges_unselected); + } + } + } + } + + if (error_count_verts_selected || error_count_verts_unselected) { + is_valid = false; + } + if (error_count_edges_selected || error_count_edges_unselected) { + is_valid = false; + } + } + + return is_valid; +} + +static bool bm_mesh_uvselect_check_contiguous(BMesh *bm, + const int cd_loop_uv_offset, + UVSelectValidateInfo_Contiguous &info_sub) +{ + bool is_valid = true; + enum { + UV_IS_SELECTED = 1 << 0, + UV_IS_UNSELECTED = 1 << 1, + }; + + BLI_assert(cd_loop_uv_offset != -1); + + /* Handle vertices. */ + { + int &error_count = info_sub.count_uv_vert_non_contiguous_selected; + BLI_assert(error_count == 0); + + bm_mesh_loop_clear_tag(bm); + + auto loop_vert_select_test_fn = [&cd_loop_uv_offset](BMLoop *l_base) -> int { + BMIter liter; + BMLoop *l_other; + + BM_elem_flag_enable(l_base, BM_ELEM_TAG); + + int select_test = 0; + + BM_ITER_ELEM (l_other, &liter, l_base->v, BM_LOOPS_OF_VERT) { + /* Ignore all hidden. */ + if (BM_elem_flag_test(l_other->f, BM_ELEM_HIDDEN)) { + continue; + } + if (BM_elem_flag_test(l_other, BM_ELEM_TAG)) { + continue; + } + if (!BM_loop_uv_share_vert_check(l_base, l_other, cd_loop_uv_offset)) { + continue; + } + select_test |= BM_elem_flag_test(l_other, BM_ELEM_SELECT_UV) ? UV_IS_SELECTED : + UV_IS_UNSELECTED; + if (select_test == (UV_IS_SELECTED | UV_IS_UNSELECTED)) { + break; + } + } + return select_test; + }; + BMIter fiter; + BMFace *f; + BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + if (BM_elem_flag_test(l_iter, BM_ELEM_TAG)) { + continue; + } + if (loop_vert_select_test_fn(l_iter) == (UV_IS_SELECTED | UV_IS_UNSELECTED)) { + INCF_MAYBE_ASSERT(error_count); + } + } while ((l_iter = l_iter->next) != l_first); + } + if (error_count) { + is_valid = false; + } + } + + /* Handle edges. */ + { + int &error_count = info_sub.count_uv_edge_non_contiguous_selected; + BLI_assert(error_count == 0); + bm_mesh_loop_clear_tag(bm); + + auto loop_edge_select_test_fn = [&cd_loop_uv_offset](BMLoop *l_base) -> int { + BM_elem_flag_enable(l_base, BM_ELEM_TAG); + + int select_test = 0; + if (l_base->radial_next != l_base) { + BMLoop *l_other = l_base->radial_next; + do { + /* Ignore all hidden. */ + if (BM_elem_flag_test(l_other->f, BM_ELEM_HIDDEN)) { + continue; + } + if (BM_elem_flag_test(l_other, BM_ELEM_TAG)) { + continue; + } + if (!BM_loop_uv_share_edge_check(l_base, l_other, cd_loop_uv_offset)) { + continue; + } + + select_test |= BM_elem_flag_test(l_other, BM_ELEM_SELECT_UV_EDGE) ? UV_IS_SELECTED : + UV_IS_UNSELECTED; + if (select_test == (UV_IS_SELECTED | UV_IS_UNSELECTED)) { + break; + } + } while ((l_other = l_other->radial_next) != l_base); + } + return select_test; + }; + BMIter fiter; + BMFace *f; + BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + if (BM_elem_flag_test(l_iter, BM_ELEM_TAG)) { + continue; + } + if (loop_edge_select_test_fn(l_iter) == (UV_IS_SELECTED | UV_IS_UNSELECTED)) { + INCF_MAYBE_ASSERT(error_count); + } + } while ((l_iter = l_iter->next) != l_first); + } + if (error_count) { + is_valid = false; + } + } + return is_valid; +} + +/** + * Checks using both flush & contiguous. + */ +static bool bm_mesh_uvselect_check_flush_and_contiguous( + BMesh *bm, const int cd_loop_uv_offset, UVSelectValidateInfo_FlushAndContiguous &info_sub) +{ + bool is_valid = true; + + /* Check isolated selection. */ + if ((bm->selectmode & SCE_SELECT_EDGE) && (bm->selectmode & SCE_SELECT_VERTEX) == 0) { + int &error_count = info_sub.count_uv_vert_isolated_in_edge_or_face_mode; + BLI_assert(error_count == 0); + + if (bm->selectmode & SCE_SELECT_EDGE) { + /* All selected UV's must have at least one selected edge. */ + BMIter fiter; + BMFace *f; + BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + /* Only check selected vertices. */ + if (BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV)) { + if (!BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV_EDGE) && + !BM_elem_flag_test(l_iter->prev, BM_ELEM_SELECT_UV_EDGE) && + !BM_loop_vert_uvselect_check_other_loop_edge( + l_iter, BM_ELEM_SELECT_UV_EDGE, cd_loop_uv_offset)) + { + INCF_MAYBE_ASSERT(error_count); + } + } + } while ((l_iter = l_iter->next) != l_first); + } + } + if (error_count) { + is_valid = false; + } + } + + if ((bm->selectmode & SCE_SELECT_FACE) && + (bm->selectmode & (SCE_SELECT_VERTEX | SCE_SELECT_EDGE)) == 0) + { + int &error_count_vert = info_sub.count_uv_vert_isolated_in_face_mode; + int &error_count_edge = info_sub.count_uv_edge_isolated_in_face_mode; + BLI_assert(error_count_vert == 0 && error_count_edge == 0); + + /* All selected UV's must have at least one selected edge. */ + BMIter fiter; + BMFace *f; + BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + /* If this face is selected, there is no need to search over it's verts. */ + if (BM_elem_flag_test(f, BM_ELEM_SELECT_UV)) { + continue; + } + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + /* Only check selected vertices. */ + if (BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV)) { + if (!BM_loop_vert_uvselect_check_other_face( + l_iter, BM_ELEM_SELECT_UV, cd_loop_uv_offset)) + { + INCF_MAYBE_ASSERT(error_count_vert); + } + } + /* Only check selected edges. */ + if (BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV_EDGE)) { + if (!BM_loop_edge_uvselect_check_other_face( + l_iter, BM_ELEM_SELECT_UV, cd_loop_uv_offset)) + { + INCF_MAYBE_ASSERT(error_count_edge); + } + } + } while ((l_iter = l_iter->next) != l_first); + } + + if (error_count_vert || error_count_edge) { + is_valid = false; + } + } + return is_valid; +} + +#undef INCF_MAYBE_ASSERT +#undef MAYBE_ASSERT + +bool BM_mesh_uvselect_is_valid(BMesh *bm, + const int cd_loop_uv_offset, + const bool check_sync, + const bool check_flush, + const bool check_contiguous, + UVSelectValidateInfo *info_p) +{ + /* Correctness is as follows: + * + * - UV selection must match the viewport selection. + * - If a vertex is selected at least one if it's UV verts must be selected. + * - If an edge is selected at least one of it's UV verts must be selected. + * + * - UV selection must be flushed. + * + * Notes: + * - When all vertices of a face are selected in the viewport + * (and therefor the face) is selected, it's possible the UV face is *not* selected, + * because the vertices in the viewport may be selected because of other selected UV's, + * not part of the UV's associated with the face. + * + * Therefor it is possible for a viewport face to be selected + * with an unselected UV face. + */ + + UVSelectValidateInfo _info_fallback = {}; + UVSelectValidateInfo &info = info_p ? *info_p : _info_fallback; + + bool is_valid = true; + if (check_sync) { + BLI_assert(bm->uv_select_sync_valid); + if (!bm_mesh_uvselect_check_viewport_sync(bm, info.sync)) { + is_valid = false; + } + } + + if (check_flush) { + if (!bm_mesh_uvselect_check_flush(bm, info.flush)) { + is_valid = false; + } + } + + if (check_contiguous) { + if (!bm_mesh_uvselect_check_contiguous(bm, cd_loop_uv_offset, info.contiguous)) { + is_valid = false; + } + } + + if (check_flush && check_contiguous) { + if (!bm_mesh_uvselect_check_flush_and_contiguous(bm, cd_loop_uv_offset, info.flush_contiguous)) + { + is_valid = false; + } + } + return is_valid; +} + +/** \} */ diff --git a/source/blender/bmesh/intern/bmesh_uvselect.hh b/source/blender/bmesh/intern/bmesh_uvselect.hh new file mode 100644 index 00000000000..d2b509a8c95 --- /dev/null +++ b/source/blender/bmesh/intern/bmesh_uvselect.hh @@ -0,0 +1,580 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "bmesh_class.hh" + +#include "BLI_vector_list.hh" + +/** \file + * \ingroup bmesh + * + * Overview + * ======== + * + * The `BM_uvselect_*` API deals with synchronizing selection + * between UV's and selected vertices edges & faces, + * where a selected vertex in the 3D viewport may only have some of its + * UV vertices selected in the UV editor. + * + * Supporting this involves flushing in both directions depending on the selection being edited. + * + * \note See #78393 for a user-level overview of this functionality. + * This describes the motivation to synchronize selection between UV's and the mesh. + * + * \note A short-hand term for vertex/edge/face selection used + * in this file is View3D abbreviated to `v3d`, since this is the section + * manipulated in the viewport, e.g. #BM_mesh_uvselect_sync_to_mesh. + * + * \note This is quite involved. As a last resort the UV selection can always be cleared + * and re-set from the mesh (v3d) selection, however it's good to keep UV selection + * if possible because resetting may extend vertex selection to other UV islands. + * + * Terms + * ===== + * + * - Synchronized Selection (abbreviated to "sync"). See #BMesh::uv_select_sync_valid. + * When the UV synchronized data is valid, it means there is a valid relationship + * between the UV selection flags (#BM_ELEM_SELECT_UV & #BM_ELEM_SELECT_UV_EDGE) + * and the meshes selection (#BM_ELEM_SELECT_UV). + * + * - When the UV selection changes (from the UV editor) + * this needs to be synchronized to the mesh. + * - When the base-selection flags change (from the 3D viewport) + * this needs to be synchronized to the UV's. + * Synchronizing in this direction may be lossy, although (depending on the operation), + * support for maintaining a synchronized selection may be possible. + * + * - Flushing Selection ("flush") + * When an element is selected or de-selected, the selection state + * of connected geometry may change too. + * So, de-selecting a vertex must de-select all faces that use that vertex. + * + * The rules for flushing may depend on the selection mode. + * When de-selecting a face in vertex-select-mode, all its vertices & edges + * must also be de-selected. When de-selecting a face in face-select-mode, + * only vertices and edges no longer connected to any selected faces will be de-selected. + * + * Since applying these rules while selecting individual elements is often impractical, + * it's common to adjust the selection, then flush based on the selection mode afterwards. + * + * - Flushing up: + * Flushing the selection from [verts -> edges/faces], [edges -> faces]. + * - Flushing down: + * Flushing the selection from [faces -> verts/edges], [edges -> verts]. + * + * - Isolated vertex or edge selection: + * When a vertex or edge is selected without being connected to a selected face. + * + * UV Selection Flags + * ================== + * + * - UV selection uses: + * - #BM_ELEM_SELECT_UV & #BM_ELEM_SELECT_UV_EDGE for #BMLoop + * to define selected vertices & edges. + * - #BM_ELEM_SELECT_UV for #BMFace. + * + * Hidden Flags + * ============ + * + * Unlike viewport selection there is no requirement for hidden elements not to be selected. + * Therefor, UV selection checks must check the underlying geometry is not hidden. + * In practice this means hidden faces must be assumed unselected, + * since UV's are part of the faces (there is no such thing as a hidden face-corner) + * and any hidden edge or vertex causes connected faces to be hidden. + * + * UV Selection Flushing + * ===================== + * + * Selection setting functions flush down (unless the `_noflush(...)` version is used), + * this means selecting a face also selects all verts & edges, + * selecting an edge selects its vertices. + * + * However it's expected the selection is flushed, + * de-selecting a vertex or edge must de-select it's faces (flushing up). + * For this, there are various flushing functions, + * exactly what is needed depends on the selection operation performed and the selection mode. + * + * There are also situations that shouldn't be allowed such as a single selected vertex in face + * select mode. + * + * Flushing & Synchronizing + * ======================== + * + * Properly handling the selection state is important for operators that adjust the UV selection. + * This typically involves the following steps: + * + * - The UV selection changes. + * - The UV selection must be flushed between elements to ensure the selection is valid, + * (see: `BM_mesh_uvselect_flush_*` & `BM_mesh_uvselect_mode_flush_*` functions). + * - The UV selection must be synchronized to the mesh selection + * (see #BM_mesh_uvselect_sync_to_mesh). + * - The mesh must then flush selection to its elements + * (see: `BM_mesh_select_flush_*` & `BM_mesh_select_mode_flush_*` functions). + * + * Valid State + * =========== + * + * For a valid state: + * - A selected UV-vertex must have its underlying mesh vertex selected. + * - A selected mesh-vertex must have at least one UV-vertex selected. + * + * This is *mostly* true for edges/faces too, however there cases where + * the UV selection causes an edge/face to be selected in mesh space but not UV space. + * + * See #BM_mesh_uvselect_is_valid for details. + * + * Clearing the Valid State + * ======================== + * + * As already noted, tools should maintain the synchronized UV selection where possible. + * However when this information *isn't* needed it should be cleared aggressively + * (see #BM_mesh_uvselect_clear), since it adds both computation & memory overhead. + * + * For actions that overwrite the selection such as selecting or de-selecting all, + * it's safe to "clear" the data, other actions such as adding new geometry that replaces + * the selection can also safely "clear" the UV selection. + * + * In practice users modeling in the 3D viewport are likely to clear the UV selection data + * since selecting the mesh without extending the selection is effectively a "De-select All". + * So the chances this data persists when it's not needed over many editing operations are low. + */ + +/* -------------------------------------------------------------------- */ +/** \name UV Selection Functions (low level) + * + * Selection checking functions. + * These should be used instead of checking #BM_ELEM_SELECT_UV, + * so hidden geometry is never considered selected. + * \{ */ + +bool BM_face_uvselect_test(const BMFace *f); +bool BM_loop_vert_uvselect_test(const BMLoop *l); +bool BM_loop_edge_uvselect_test(const BMLoop *l); + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UV Selection Connectivity Checks + * + * Regarding the `hflag` parameter: this is typically set to: + * - #BM_ELEM_SELECT for mesh selection. + * - #BM_ELEM_SELECT_UV for selected UV vertices. + * - #BM_ELEM_SELECT_UV_EDGE for selected UV edges. + * - #BM_ELEM_SELECT_TAG to allow the caller to use a separate non-selection flag. + * + * Each function asserts that a supported `hflag` is passed in. + * \{ */ + +bool BM_loop_vert_uvselect_check_other_loop_vert(BMLoop *l, char hflag, int cd_loop_uv_offset); +bool BM_loop_vert_uvselect_check_other_loop_edge(BMLoop *l, char hflag, int cd_loop_uv_offset); +bool BM_loop_vert_uvselect_check_other_edge(BMLoop *l, char hflag, int cd_loop_uv_offset); +bool BM_loop_vert_uvselect_check_other_face(BMLoop *l, char hflag, int cd_loop_uv_offset); +bool BM_loop_edge_uvselect_check_other_loop_edge(BMLoop *l, char hflag, int cd_loop_uv_offset); +bool BM_loop_edge_uvselect_check_other_face(BMLoop *l, char hflag, int cd_loop_uv_offset); + +bool BM_face_uvselect_check_edges_all(BMFace *f); + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UV Selection Functions + * \{ */ + +/** Set the UV selection flag for `f` without flushing down to edges & vertices. */ +void BM_face_uvselect_set_noflush(BMesh *bm, BMFace *f, bool select); +/** Set the UV selection flag for `f` & flush down to edges & vertices. */ +void BM_face_uvselect_set(BMesh *bm, BMFace *f, bool select); + +/** Set the UV selection flag for `e` without flushing down to vertices. */ +void BM_loop_edge_uvselect_set_noflush(BMesh *bm, BMLoop *l, bool select); +/** Set the UV selection flag for `e` & flush down to vertices. */ +void BM_loop_edge_uvselect_set(BMesh *bm, BMLoop *l, bool select); +/** + * Set the UV selection flag for `v` without flushing down. + * since there is nothing to flush down to. + */ +void BM_loop_vert_uvselect_set_noflush(BMesh *bm, BMLoop *l, bool select); + +/** + * Call this function when selecting mesh elements in the viewport and + * the relationship with UV's is lost. + * + * \return True if UV select is cleared (a change was made). + * + * This has two purposes: + * + * - Maintaining the UV selection isn't needed: + * Some operations such as adding a new mesh primitive clear the selection, + * selecting all geometry from the new primitive. + * In this case a UV selection is redundant & should be cleared. + * + * - Maintaining the UV selection isn't supported: + * Some selection operations don't support maintaining a valid UV selection, + * in that case it's necessary to clear the UV selection otherwise tools may + * seem to be broken if they aren't operating on the selection properly. + * + * NOTE(@ideasman42): It's worth noting that in this case clearing the selection is "lossy", + * users may wish that all selection operations would handle UV selection data too. + * Supporting additional operations is always possible, at the time of writing it's + * impractical to do so, see: #131642 design task for details. + * + * Internally this marks the UV selection data as invalid, + * using the mesh selection as the "source-of-truth". + * + * \note By convention call this immediately after flushing. + * + * \note In many cases the UV selection can be maintained and this function removed, + * although it adds some complexity & overhead. + * See #UVSyncSelectFromMesh. + * + * \note Calls to this function that should *not* be removed in favor of supporting UV selection, + * this should be mentioned in a code-comment, making it clear this is not a limitation to *fix*. + */ +bool BM_mesh_uvselect_clear(BMesh *bm); + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UV Selection Functions (Shared) + * \{ */ + +void BM_loop_vert_uvselect_set_shared(BMesh *bm, BMLoop *l, bool select, int cd_loop_uv_offset); +void BM_loop_edge_uvselect_set_shared(BMesh *bm, BMLoop *l, bool select, int cd_loop_uv_offset); +void BM_face_uvselect_set_shared(BMesh *bm, BMFace *f, bool select, int cd_loop_uv_offset); + +void BM_mesh_uvselect_set_elem_shared(BMesh *bm, + bool select, + int cd_loop_uv_offset, + blender::Span loop_verts, + blender::Span loop_edges, + blender::Span faces); + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UV Selection Picking + * \{ */ + +struct BMUVSelectPickParams { + /** + * The custom data offset for the active UV layer. + * May be -1, in this case UV connectivity checks are skipped. + */ + int cd_loop_uv_offset = -1; + /** + * If true, selection changes propagate to all other UV elements + * that share the same UV coordinates (contiguous selection). + * + * Typically derived from #ToolSettings::uv_sticky, although in some cases + * it's assumed to be true (when switching selection modes for example) + * because the tool settings aren't available in that context. + * + * A boolean can be used since "Shared Vertex" (uv_sticky mode) + * can check the meshes vertex selection directly. + */ + bool shared = true; +}; + +void BM_vert_uvselect_set_pick(BMesh *bm, + BMVert *v, + bool select, + const BMUVSelectPickParams ¶ms); +void BM_edge_uvselect_set_pick(BMesh *bm, + BMEdge *e, + bool select, + const BMUVSelectPickParams ¶ms); +void BM_face_uvselect_set_pick(BMesh *bm, + BMFace *f, + bool select, + const BMUVSelectPickParams ¶ms); + +/** + * Select/deselect elements in the viewport, + * then integrate the selection with the UV selection, + * without clearing an re-initializing the synchronized state. + * (likely to re-select islands bounds from a user-perspective). + */ +void BM_mesh_uvselect_set_elem_from_mesh(BMesh *bm, + bool select, + const BMUVSelectPickParams ¶ms, + blender::Span verts, + blender::Span edges, + blender::Span faces); +/** \copydoc #BM_mesh_uvselect_set_elem_from_mesh. */ +void BM_mesh_uvselect_set_elem_from_mesh(BMesh *bm, + bool select, + const BMUVSelectPickParams ¶ms, + const blender::VectorList &verts, + const blender::VectorList &edges, + const blender::VectorList &faces); + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UV Selection Flushing (Only Select/De-Select) + * + * \note In most cases flushing assumes selection has already been flushed down. + * + * This means: + * - A selected edge must have both UV vertices selected. + * - A selected faces has all its edges & vertices selected. + * + * It's often useful to call #BM_mesh_uvselect_flush_shared_only_select + * after using these non-UV-coordinate aware flushing functions. + * \{ */ + +void BM_mesh_uvselect_flush_from_loop_verts_only_select(BMesh *bm); +void BM_mesh_uvselect_flush_from_loop_verts_only_deselect(BMesh *bm); +void BM_mesh_uvselect_flush_from_loop_edges_only_select(BMesh *bm); +void BM_mesh_uvselect_flush_from_loop_edges_only_deselect(BMesh *bm); +void BM_mesh_uvselect_flush_from_faces_only_select(BMesh *bm); +void BM_mesh_uvselect_flush_from_faces_only_deselect(BMesh *bm); + +/** + * A useful utility so simple selection operations can be performed on edges/faces, + * afterwards this can be used to select UV's that are connected. + * This avoids having to use more involved UV connectivity aware logic inline. + */ +void BM_mesh_uvselect_flush_shared_only_select(BMesh *bm, int cd_loop_uv_offset); +void BM_mesh_uvselect_flush_shared_only_deselect(BMesh *bm, int cd_loop_uv_offset); + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UV Selection Flushing (Between Elements) + * + * Regarding the `flush_down` argument. + * + * Primitive UV selection functions always flush down: + * - #BM_face_uvselect_set + * - #BM_loop_edge_uvselect_set + * - #BM_loop_vert_uvselect_set + * + * This means it's often only necessary to flush up after the selection has been changed. + * \{ */ + +/** + * Mode independent UV selection/de-selection flush from UV vertices. + * + * \param select: When true, flush the selection state to de-selected elements, + * otherwise perform the opposite, flushing de-selection. + * + * \note The caller may need to run #BM_mesh_uvselect_flush_shared_only_select afterwards. + */ +void BM_mesh_uvselect_flush_from_loop_verts(BMesh *bm); +/** + * Mode independent UV selection/de-selection flush from UV edges. + * + * Flush from loop edges up to faces and optionally down to vertices (when `flush_down` is true). + * + * \note The caller may need to run #BM_mesh_uvselect_flush_shared_only_select afterwards. + */ +void BM_mesh_uvselect_flush_from_loop_edges(BMesh *bm, bool flush_down); +/** + * Mode independent UV selection/de-selection flush from UV faces. + * + * Flush from faces down to edges & vertices (when `flush_down` is true). + * + * \note The caller may need to run #BM_mesh_uvselect_flush_shared_only_select afterwards. + */ +void BM_mesh_uvselect_flush_from_faces(BMesh *bm, bool flush_down); + +/** + * Mode independent UV selection/de-selection flush from UV vertices. + * + * Use this when it's know geometry was only selected/de-selected. + * + * \note An equivalent to #BM_mesh_select_flush_from_verts for the UV selection. + */ +void BM_mesh_uvselect_flush_from_verts(BMesh *bm, bool select); + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UV Selection Flushing (Selection Mode Aware) + * \{ */ + +/** + * \param flush_down: See #BMSelectFlushFlag::Down for notes on flushing down. + */ +void BM_mesh_uvselect_mode_flush_ex(BMesh *bm, const short selectmode, bool flush_down); +void BM_mesh_uvselect_mode_flush(BMesh *bm); + +/** + * Select elements based on the selection mode. + * (flushes the selection *up* based on the mode). + * + * - With vertex selection mode enabled: flush up to edges and faces. + * - With edge selection mode enabled: flush to faces. + * - With *only* face selection mode enabled: do nothing. + * + * \note An "only deselect" version function could be added, it's not needed at the moment.a + */ +void BM_mesh_uvselect_mode_flush_only_select(BMesh *bm); + +/** + * When the select mode changes, update to ensure the selection is valid. + * So single vertices aren't selected in edge-select mode for example. + * + * The mesh selection flushing must have already run. + */ +void BM_mesh_uvselect_mode_flush_update(BMesh *bm, + short selectmode_old, + short selectmode_new, + int cd_loop_uv_offset); + +/** + * A specialized flushing that fills in selection information after subdividing. + * + * It's important this runs: + * - After subdivision. + * - After the mesh selection has already been flushed. + * + * \note Intended to be a generic utility to be used in any situation + * new geometry is created by splitting existing geometry. + */ +void BM_mesh_uvselect_flush_post_subdivide(BMesh *bm, int cd_loop_uv_offset); + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UV Selection Flushing (From/To Mesh) + * \{ */ + +/* From 3D viewport to UV selection. + * + * These functions correspond to #ToolSettings::uv_sticky options. */ + +void BM_mesh_uvselect_sync_from_mesh_sticky_location(BMesh *bm, int cd_loop_uv_offset); +void BM_mesh_uvselect_sync_from_mesh_sticky_disabled(BMesh *bm); +void BM_mesh_uvselect_sync_from_mesh_sticky_vert(BMesh *bm); + +/** + * Synchronize selection: from the UV selection to the 3D viewport. + * + * \note #BMesh::uv_select_sync_valid must be true. + */ +void BM_mesh_uvselect_sync_to_mesh(BMesh *bm); + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UV Selection Validation + * \{ */ + +/** + * UV/Mesh Synchronization + * + * Check the selection has been properly synchronized between the mesh and the UV's. + * + * \note It is essential for this to be correct and return no errors. + * Other checks are useful to ensure the selection state meets the expectations of the caller + * but the state is not invalid - as it is when the selection is out-of-sync. + */ +struct UVSelectValidateInfo_Sync { + /** When a vertex is unselected none of it's UV's may be selected. */ + int count_uv_vert_any_selected_with_vert_unselected = 0; + /** When a vertex is selected at least one UV must be selected. */ + int count_uv_vert_none_selected_with_vert_selected = 0; + + /** When a edge is unselected none of it's UV's may be selected. */ + int count_uv_edge_any_selected_with_edge_unselected = 0; + /** When a edge is selected at least one UV must be selected. */ + int count_uv_edge_none_selected_with_edge_selected = 0; +}; + +/** + * Flushing between elements. + * + * Check the selection has been properly flushing between elements. + */ +struct UVSelectValidateInfo_Flush { + /** Edges are selected without selected vertices. */ + int count_uv_edge_selected_with_any_verts_unselected = 0; + /** Edges are unselected with all selected vertices. */ + int count_uv_edge_unselected_with_all_verts_selected = 0; + + /** Faces are selected without selected vertices. */ + int count_uv_face_selected_with_any_verts_unselected = 0; + /** Faces are unselected with all selected vertices. */ + int count_uv_face_unselected_with_all_verts_selected = 0; + + /** Faces are selected without selected edges. */ + int count_uv_face_selected_with_any_edges_unselected = 0; + /** Faces are unselected with all selected edges. */ + int count_uv_face_unselected_with_all_edges_selected = 0; +}; + +/** + * Contiguous. + * + * Check the selected UV's are contiguous, + * in situations where it's expected selecting a UV will select all "connected" UV's + * (UV's sharing the same vertex with the same UV coordinate). + */ +struct UVSelectValidateInfo_Contiguous { + /** When a vertices connected UV's are co-located without matching selection. */ + int count_uv_vert_non_contiguous_selected = 0; + /** When a edges connected UV's are co-located without matching selection. */ + int count_uv_edge_non_contiguous_selected = 0; +}; + +/** + * Flush & contiguous. + * + * In some cases it's necessary to check flushing and contiguous UV's are correct. + */ +struct UVSelectValidateInfo_FlushAndContiguous { + /** A vertex is selected in edge/face modes without being part of a selected edge/face. */ + int count_uv_vert_isolated_in_edge_or_face_mode = 0; + /** A vertex is selected in face modes without being part of a selected face. */ + int count_uv_vert_isolated_in_face_mode = 0; + /** An edge is selected in face modes without being part of a selected face. */ + int count_uv_edge_isolated_in_face_mode = 0; +}; + +struct UVSelectValidateInfo { + UVSelectValidateInfo_Sync sync; + + UVSelectValidateInfo_Flush flush; + UVSelectValidateInfo_Contiguous contiguous; + UVSelectValidateInfo_FlushAndContiguous flush_contiguous; +}; + +/** + * Check the UV selection is valid, mainly for debugging & testing purposes. + * + * The primary check which should remain valid is: `check_sync`, + * if there is ever a selected vertex without any selected UV's or a selected + * UV without it's vertex being selected (and similar kinds of issues), + * then the selection is out-of-sync, which Blender should *never* allow. + * + * While an invalid selection should not crash, tools that operate on selection + * may behave unpredictably. + * + * The other checks may be desired or not although this depends more on the situation. + * + * \param cd_loop_uv_offset: The UV custom-data layer to check. + * Ignored when -1 (UV checks wont be used). + * + * \param check_sync: When true, check the selection is synchronized + * between the UV and mesh selection. This should practically always be true, + * as it doesn't make sense to check the UV selection if valid otherwise, + * unless the UV selection is being set and has not yet been synchronized. + * \param check_flush: When true, check the selection is flushed based on #BMesh::selectmode. + * \param check_contiguous: When true, check that UV selection is contiguous. + * Note that this is not considered an *error* since users may cause this to happen and + * tools are expected to work properly, however some operations are expected to maintain + * a contiguous selection. This check is included to ensure those operations are working. + */ +bool BM_mesh_uvselect_is_valid(BMesh *bm, + int cd_loop_uv_offset, + bool check_sync, + bool check_flush, + bool check_contiguous, + UVSelectValidateInfo *info); + +/** \} */ diff --git a/source/blender/bmesh/operators/bmo_subdivide.cc b/source/blender/bmesh/operators/bmo_subdivide.cc index bab38b9eb03..023f39de1c6 100644 --- a/source/blender/bmesh/operators/bmo_subdivide.cc +++ b/source/blender/bmesh/operators/bmo_subdivide.cc @@ -1383,6 +1383,23 @@ void BM_mesh_esubdivide(BMesh *bm, break; } } + if (edge_hflag & BM_ELEM_SELECT) { + /* TODO(@ideasman42): the current behavior for face selection flushing + * can cause parts of disconnected UV islands to become selected. + * This is caused by the underlying geometry becoming selected. + * while this is not a bug in UV selection uses are likely to + * notice this problem when dealing with the UV selections. + * This can be observed after subdividing the default "Suzanne" model + * when the UV island of one of the ears is selected. + * + * We may want to change the resulting selection after a subdivision + * to avoid this problem occurring. */ + + if (bm->uv_select_sync_valid) { + const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_PROP_FLOAT2); + BM_mesh_uvselect_flush_post_subdivide(bm, cd_loop_uv_offset); + } + } BMO_op_finish(bm, &op); } diff --git a/source/blender/draw/engines/overlay/overlay_mesh.hh b/source/blender/draw/engines/overlay/overlay_mesh.hh index 3a34449f354..aafcafad8b8 100644 --- a/source/blender/draw/engines/overlay/overlay_mesh.hh +++ b/source/blender/draw/engines/overlay/overlay_mesh.hh @@ -604,6 +604,8 @@ class MeshUVs : Overlay { const bool hide_faces = space_image->flag & SI_NO_DRAWFACES; select_face_ = !show_mesh_analysis_ && !hide_faces; + /* FIXME: Always showing verts in edge mode when `uv_select_sync_valid`. + * needs investigation. */ if (tool_setting->uv_flag & UV_FLAG_SELECT_SYNC) { const char sel_mode_3d = tool_setting->selectmode; if (tool_setting->uv_sticky == UV_STICKY_VERT) { diff --git a/source/blender/draw/intern/mesh_extractors/extract_mesh.cc b/source/blender/draw/intern/mesh_extractors/extract_mesh.cc index a186f5d0090..c94a2abd65e 100644 --- a/source/blender/draw/intern/mesh_extractors/extract_mesh.cc +++ b/source/blender/draw/intern/mesh_extractors/extract_mesh.cc @@ -37,7 +37,9 @@ void mesh_render_data_face_flag(const MeshRenderData &mr, if (efa == mr.efa_act_uv) { eattr.v_flag |= VFLAG_FACE_UV_ACTIVE; } - if ((offsets.uv != -1) && uvedit_face_select_test_ex(mr.toolsettings, efa, offsets)) { + if ((offsets.uv != -1) && (!BM_elem_flag_test(efa, BM_ELEM_HIDDEN)) && + uvedit_face_select_test_ex(mr.toolsettings, mr.bm, efa, offsets)) + { eattr.v_flag |= VFLAG_FACE_UV_SELECT; } @@ -61,7 +63,7 @@ void mesh_render_data_loop_flag(const MeshRenderData &mr, if (BM_ELEM_CD_GET_BOOL(l, offsets.pin)) { eattr.v_flag |= VFLAG_VERT_UV_PINNED; } - if (uvedit_uv_select_test_ex(mr.toolsettings, l, offsets)) { + if (uvedit_uv_select_test_ex(mr.toolsettings, mr.bm, l, offsets)) { eattr.v_flag |= VFLAG_VERT_UV_SELECT; } } @@ -74,7 +76,7 @@ void mesh_render_data_loop_edge_flag(const MeshRenderData &mr, if (offsets.uv == -1) { return; } - if (uvedit_edge_select_test_ex(mr.toolsettings, l, offsets)) { + if (uvedit_edge_select_test_ex(mr.toolsettings, mr.bm, l, offsets)) { eattr.v_flag |= VFLAG_EDGE_UV_SELECT; eattr.v_flag |= VFLAG_VERT_UV_SELECT; } diff --git a/source/blender/editors/include/ED_mesh.hh b/source/blender/editors/include/ED_mesh.hh index 101e79aaf3e..2b639240b5f 100644 --- a/source/blender/editors/include/ED_mesh.hh +++ b/source/blender/editors/include/ED_mesh.hh @@ -212,6 +212,9 @@ UvVertMap *BM_uv_vert_map_create(BMesh *bm, bool use_select); void EDBM_flag_enable_all(BMEditMesh *em, char hflag); void EDBM_flag_disable_all(BMEditMesh *em, char hflag); +/** \copydoc #BM_uvselect_clear */ +bool EDBM_uvselect_clear(BMEditMesh *em); + bool BMBVH_EdgeVisible(const BMBVHTree *tree, const BMEdge *e, const Depsgraph *depsgraph, diff --git a/source/blender/editors/include/ED_uvedit.hh b/source/blender/editors/include/ED_uvedit.hh index 536b31b126c..30cee804842 100644 --- a/source/blender/editors/include/ED_uvedit.hh +++ b/source/blender/editors/include/ED_uvedit.hh @@ -9,14 +9,17 @@ #pragma once #include "BLI_function_ref.hh" +#include "BLI_vector_list.hh" #include "BKE_customdata.hh" struct ARegion; struct ARegionType; +struct BMEdge; struct BMEditMesh; struct BMFace; struct BMLoop; +struct BMVert; struct BMesh; struct Image; struct ImageUser; @@ -83,22 +86,117 @@ bool ED_uvedit_test(Object *obedit); /* `uvedit_select.cc` */ +namespace blender::ed::uv { + +/** + * Abstract away the details of syncing selection from the mesh (viewport) + * to a UV state which is "synchronized". + * + * Where practical (see note below) this is a preferred alternative to clearing the + * UV selection state and re-initializing it from the mesh, because there may be UV's + * selected on one UV island and not another, even though the vertices are shared. + * Flushing and re-initializing will set both, losing the users selection. + * + * Note that what is considered practical is open to interpretation, + * picking individual elements and basic selection actions should be supported. + * Selection actions such as random or by vertex group... isn't so practical. + */ +class UVSyncSelectFromMesh : NonCopyable { + private: + char uv_sticky_; + BMesh &bm_; + + blender::VectorList bm_verts_select_; + blender::VectorList bm_edges_select_; + blender::VectorList bm_faces_select_; + + blender::VectorList bm_verts_deselect_; + blender::VectorList bm_edges_deselect_; + blender::VectorList bm_faces_deselect_; + + public: + UVSyncSelectFromMesh(BMesh &bm, char uv_sticky) : uv_sticky_(uv_sticky), bm_(bm) {} + UVSyncSelectFromMesh(const UVSyncSelectFromMesh &) = delete; + + static std::unique_ptr create_if_needed(const ToolSettings &ts, BMesh &bm); + void apply(); + + /* Select. */ + + void vert_select_enable(BMVert *v); + void edge_select_enable(BMEdge *f); + void face_select_enable(BMFace *f); + + /* De-Select. */ + + void vert_select_disable(BMVert *v); + void edge_select_disable(BMEdge *f); + void face_select_disable(BMFace *f); + + /* Select set. */ + + void vert_select_set(BMVert *v, bool value); + void edge_select_set(BMEdge *f, bool value); + void face_select_set(BMFace *f, bool value); +}; + +} // namespace blender::ed::uv + +bool ED_uvedit_sync_uvselect_ignore(const ToolSettings *ts); +bool ED_uvedit_sync_uvselect_is_valid_or_ignore(const ToolSettings *ts, const BMesh *bm); +void ED_uvedit_sync_uvselect_ensure_if_needed(const ToolSettings *ts, BMesh *bm); + /* Visibility and selection tests. */ bool uvedit_face_visible_test_ex(const ToolSettings *ts, const BMFace *efa); bool uvedit_face_select_test_ex(const ToolSettings *ts, + const BMesh *bm, const BMFace *efa, const BMUVOffsets &offsets); bool uvedit_edge_select_test_ex(const ToolSettings *ts, + const BMesh *bm, const BMLoop *l, const BMUVOffsets &offsets); -bool uvedit_uv_select_test_ex(const ToolSettings *ts, const BMLoop *l, const BMUVOffsets &offsets); +bool uvedit_uv_select_test_ex(const ToolSettings *ts, + const BMesh *bm, + const BMLoop *l, + const BMUVOffsets &offsets); bool uvedit_face_visible_test(const Scene *scene, const BMFace *efa); -bool uvedit_face_select_test(const Scene *scene, const BMFace *efa, const BMUVOffsets &offsets); -bool uvedit_edge_select_test(const Scene *scene, const BMLoop *l, const BMUVOffsets &offsets); -bool uvedit_uv_select_test(const Scene *scene, const BMLoop *l, const BMUVOffsets &offsets); +bool uvedit_face_select_test(const Scene *scene, + const BMesh *bm, + const BMFace *efa, + const BMUVOffsets &offsets); +bool uvedit_edge_select_test(const Scene *scene, + const BMesh *bm, + const BMLoop *l, + const BMUVOffsets &offsets); +bool uvedit_uv_select_test(const Scene *scene, + const BMesh *bm, + const BMLoop *l, + const BMUVOffsets &offsets); + +/* Low level loop selection, this ignores the selection modes. */ + +bool uvedit_loop_vert_select_get(const ToolSettings *ts, + const BMesh *bm, + const BMLoop *l, + const BMUVOffsets &offsets); +bool uvedit_loop_edge_select_get(const ToolSettings *ts, + const BMesh *bm, + const BMLoop *l, + const BMUVOffsets &offsets); +void uvedit_loop_vert_select_set(const ToolSettings *ts, + const BMesh *bm, + BMLoop *l, + const bool select, + const BMUVOffsets &offsets); +void uvedit_loop_edge_select_set(const ToolSettings *ts, + const BMesh *bm, + BMLoop *l, + const bool select, + const BMUVOffsets &offsets); /* Individual UV element selection functions. */ @@ -197,6 +295,7 @@ void uvedit_uv_select_shared_vert(const Scene *scene, * Sets required UV edge flags as specified by the `sticky_flag`. */ void uvedit_edge_select_set_noflush(const Scene *scene, + BMesh *bm, BMLoop *l, const bool select, const int sticky_flag, @@ -240,10 +339,10 @@ BMLoop **ED_uvedit_selected_edges(const Scene *scene, BMesh *bm, int len_max, in BMLoop **ED_uvedit_selected_verts(const Scene *scene, BMesh *bm, int len_max, int *r_verts_len); void ED_uvedit_active_vert_loop_set(BMesh *bm, BMLoop *l); -BMLoop *ED_uvedit_active_vert_loop_get(BMesh *bm); +BMLoop *ED_uvedit_active_vert_loop_get(const ToolSettings *ts, BMesh *bm); void ED_uvedit_active_edge_loop_set(BMesh *bm, BMLoop *l); -BMLoop *ED_uvedit_active_edge_loop_get(BMesh *bm); +BMLoop *ED_uvedit_active_edge_loop_get(const ToolSettings *ts, BMesh *bm); /** * Intentionally don't return #UV_SELECT_ISLAND as it's not an element type. @@ -255,6 +354,8 @@ void ED_uvedit_select_sync_flush(const ToolSettings *ts, BMesh *bm, bool select) /* `uvedit_unwrap_ops.cc` */ +void ED_uvedit_deselect_all(const Scene *scene, Object *obedit, int action); + void ED_uvedit_get_aspect(Object *obedit, float *r_aspx, float *r_aspy); /** diff --git a/source/blender/editors/mesh/editmesh_add.cc b/source/blender/editors/mesh/editmesh_add.cc index 7d9f6bbf785..5521a70e6cb 100644 --- a/source/blender/editors/mesh/editmesh_add.cc +++ b/source/blender/editors/mesh/editmesh_add.cc @@ -74,6 +74,8 @@ static void make_prim_finish(bContext *C, /* Primitive has all verts selected, use vert select flush * to push this up to edges & faces. */ EDBM_selectmode_flush_ex(em, SCE_SELECT_VERTEX); + /* TODO(@ideasman42): maintain UV sync for newly created data. */ + EDBM_uvselect_clear(em); /* Only recalculate edit-mode tessellation if we are staying in edit-mode. */ EDBMUpdate_Params params{}; diff --git a/source/blender/editors/mesh/editmesh_add_gizmo.cc b/source/blender/editors/mesh/editmesh_add_gizmo.cc index 221457dfd46..57edaa14c4e 100644 --- a/source/blender/editors/mesh/editmesh_add_gizmo.cc +++ b/source/blender/editors/mesh/editmesh_add_gizmo.cc @@ -343,6 +343,9 @@ static wmOperatorStatus add_primitive_cube_gizmo_exec(bContext *C, wmOperator *o } EDBM_selectmode_flush_ex(em, SCE_SELECT_VERTEX); + /* TODO(@ideasman42): maintain UV sync for newly created data. */ + EDBM_uvselect_clear(em); + EDBMUpdate_Params params{}; params.calc_looptris = true; params.calc_normals = false; diff --git a/source/blender/editors/mesh/editmesh_bevel.cc b/source/blender/editors/mesh/editmesh_bevel.cc index 293f599397a..bd5c747a588 100644 --- a/source/blender/editors/mesh/editmesh_bevel.cc +++ b/source/blender/editors/mesh/editmesh_bevel.cc @@ -427,6 +427,7 @@ static void edbm_bevel_exit(bContext *C, wmOperator *op) if ((em->selectmode & SCE_SELECT_FACE) == 0) { EDBM_selectmode_flush(em); } + EDBM_uvselect_clear(em); } if (opdata->is_modal) { diff --git a/source/blender/editors/mesh/editmesh_bisect.cc b/source/blender/editors/mesh/editmesh_bisect.cc index af096f72ef1..207a130b29e 100644 --- a/source/blender/editors/mesh/editmesh_bisect.cc +++ b/source/blender/editors/mesh/editmesh_bisect.cc @@ -387,7 +387,10 @@ static wmOperatorStatus mesh_bisect_exec(bContext *C, wmOperator *op) params.calc_normals = false; params.is_destructive = true; EDBM_update(static_cast(obedit->data), ¶ms); + EDBM_selectmode_flush(em); + EDBM_uvselect_clear(em); + ret = OPERATOR_FINISHED; } } diff --git a/source/blender/editors/mesh/editmesh_intersect.cc b/source/blender/editors/mesh/editmesh_intersect.cc index 1f9d14078bf..621ecce1d08 100644 --- a/source/blender/editors/mesh/editmesh_intersect.cc +++ b/source/blender/editors/mesh/editmesh_intersect.cc @@ -100,7 +100,9 @@ static void edbm_intersect_select(BMEditMesh *em, Mesh *mesh, bool do_select) BM_edge_select_set(em->bm, e, true); } } + EDBM_selectmode_flush(em); + EDBM_uvselect_clear(em); } } diff --git a/source/blender/editors/mesh/editmesh_knife.cc b/source/blender/editors/mesh/editmesh_knife.cc index 4be9280c9fd..c3198923a25 100644 --- a/source/blender/editors/mesh/editmesh_knife.cc +++ b/source/blender/editors/mesh/editmesh_knife.cc @@ -4096,6 +4096,8 @@ static void knifetool_finish_single_post(KnifeTool_OpData * /*kcd*/, Object *ob) { BMEditMesh *em = BKE_editmesh_from_object(ob); EDBM_selectmode_flush(em); + EDBM_uvselect_clear(em); + EDBMUpdate_Params params{}; params.calc_looptris = true; params.calc_normals = true; diff --git a/source/blender/editors/mesh/editmesh_loopcut.cc b/source/blender/editors/mesh/editmesh_loopcut.cc index 48528331cb9..f0c5ba5d2ab 100644 --- a/source/blender/editors/mesh/editmesh_loopcut.cc +++ b/source/blender/editors/mesh/editmesh_loopcut.cc @@ -245,9 +245,12 @@ static void ringsel_finish(bContext *C, wmOperator *op) } EDBM_selectmode_flush(lcd->em); + DEG_id_tag_update(static_cast(lcd->ob->data), ID_RECALC_SELECT); WM_event_add_notifier(C, NC_GEOM | ND_SELECT, lcd->ob->data); } + + EDBM_uvselect_clear(em); } } diff --git a/source/blender/editors/mesh/editmesh_path.cc b/source/blender/editors/mesh/editmesh_path.cc index 8f4c6a53688..51f1c0c0303 100644 --- a/source/blender/editors/mesh/editmesh_path.cc +++ b/source/blender/editors/mesh/editmesh_path.cc @@ -261,6 +261,7 @@ static void mouse_mesh_shortest_path_vert(Scene * /*scene*/, } EDBM_selectmode_flush(em); + EDBM_uvselect_clear(em); if (op_params->track_active) { /* even if this is selected it may not be in the selection list */ @@ -470,6 +471,7 @@ static void mouse_mesh_shortest_path_edge( } EDBM_selectmode_flush(em); + EDBM_uvselect_clear(em); if (op_params->track_active) { /* even if this is selected it may not be in the selection list */ @@ -605,6 +607,7 @@ static void mouse_mesh_shortest_path_face(Scene * /*scene*/, } EDBM_selectmode_flush(em); + EDBM_uvselect_clear(em); if (op_params->track_active) { /* even if this is selected it may not be in the selection list */ diff --git a/source/blender/editors/mesh/editmesh_rip_edge.cc b/source/blender/editors/mesh/editmesh_rip_edge.cc index fd3e6be292e..eeeb3af915e 100644 --- a/source/blender/editors/mesh/editmesh_rip_edge.cc +++ b/source/blender/editors/mesh/editmesh_rip_edge.cc @@ -197,6 +197,36 @@ static wmOperatorStatus edbm_rip_edge_invoke(bContext *C, } BM_elem_flag_enable(v_new, BM_ELEM_TAG); /* prevent further splitting */ + /* When UV sync select is enabled, the wrong UV's will be selected + * because the existing loops will have the selection and the new ones won't. + * transfer the selection state to the new loops. */ + if (bm->uv_select_sync_valid) { + if (e_best->l) { + BMLoop *l_iter, *l_first; + l_iter = l_first = e_best->l; + do { + bool was_select = false; + if (l_iter->next->e == e_new) { + if (BM_elem_flag_test(l_iter, BM_ELEM_SELECT_UV)) { + BM_loop_edge_uvselect_set(bm, l_iter->next, false); + was_select = true; + } + } + else { + BLI_assert(l_iter->prev->e == e_new); + if (BM_elem_flag_test(l_iter->prev, BM_ELEM_SELECT_UV)) { + BM_loop_edge_uvselect_set(bm, l_iter->prev, false); + was_select = true; + } + } + if (was_select) { + BM_loop_edge_uvselect_set(bm, l_iter, true); + } + + } while ((l_iter = l_iter->radial_next) != l_first); + } + } + changed = true; } } diff --git a/source/blender/editors/mesh/editmesh_select.cc b/source/blender/editors/mesh/editmesh_select.cc index 2a3f6e730c9..b9db300d715 100644 --- a/source/blender/editors/mesh/editmesh_select.cc +++ b/source/blender/editors/mesh/editmesh_select.cc @@ -45,6 +45,7 @@ #include "ED_screen.hh" #include "ED_select_utils.hh" #include "ED_transform.hh" +#include "ED_uvedit.hh" #include "ED_view3d.hh" #include "BLT_translation.hh" @@ -1758,6 +1759,7 @@ static wmOperatorStatus edbm_loop_multiselect_exec(bContext *C, wmOperator *op) } if (changed) { EDBM_selectmode_flush(em); + EDBM_uvselect_clear(em); } } else { @@ -1773,6 +1775,7 @@ static wmOperatorStatus edbm_loop_multiselect_exec(bContext *C, wmOperator *op) } if (changed) { EDBM_selectmode_flush(em); + EDBM_uvselect_clear(em); } } MEM_freeN(edarray); @@ -1958,6 +1961,7 @@ static bool mouse_mesh_loop( } EDBM_selectmode_flush(em); + EDBM_uvselect_clear(em); /* Sets as active, useful for other tools. */ if (select) { @@ -2139,10 +2143,16 @@ static wmOperatorStatus edbm_select_all_exec(bContext *C, wmOperator *op) EDBM_flag_disable_all(em, BM_ELEM_SELECT); break; case SEL_INVERT: - EDBM_select_swap(em); - EDBM_selectmode_flush(em); + if (em->bm->uv_select_sync_valid) { + ED_uvedit_deselect_all(scene, obedit, SEL_INVERT); + } + else { + EDBM_select_swap(em); + EDBM_selectmode_flush(em); + } break; } + DEG_id_tag_update(static_cast(obedit->data), ID_RECALC_SELECT); WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); } @@ -2260,6 +2270,12 @@ bool EDBM_select_pick(bContext *C, const int mval[2], const SelectPick_Params &p BMEditMesh *em = vc.em; BMesh *bm = em->bm; + const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_PROP_FLOAT2); + const BMUVSelectPickParams uv_pick_params = { + /*cd_loop_uv_offset*/ cd_loop_uv_offset, + /*shared*/ vc.scene->toolsettings->uv_sticky == UV_STICKY_LOCATION, + }; + if (efa) { switch (params.sel_op) { case SEL_OP_ADD: { @@ -2271,6 +2287,9 @@ bool EDBM_select_pick(bContext *C, const int mval[2], const SelectPick_Params &p BM_face_select_set(bm, efa, false); BM_select_history_store(bm, efa); BM_face_select_set(bm, efa, true); + if (bm->uv_select_sync_valid) { + BM_face_uvselect_set_pick(bm, efa, true, uv_pick_params); + } break; } case SEL_OP_SUB: { @@ -2283,10 +2302,16 @@ bool EDBM_select_pick(bContext *C, const int mval[2], const SelectPick_Params &p if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) { BM_select_history_store(bm, efa); BM_face_select_set(bm, efa, true); + if (bm->uv_select_sync_valid) { + BM_face_uvselect_set_pick(bm, efa, true, uv_pick_params); + } } else { BM_select_history_remove(bm, efa); BM_face_select_set(bm, efa, false); + if (bm->uv_select_sync_valid) { + BM_face_uvselect_set_pick(bm, efa, false, uv_pick_params); + } } break; } @@ -2296,6 +2321,7 @@ bool EDBM_select_pick(bContext *C, const int mval[2], const SelectPick_Params &p BM_select_history_store(bm, efa); BM_face_select_set(bm, efa, true); } + /* UV select will have been cleared. */ break; } case SEL_OP_AND: { @@ -2314,21 +2340,33 @@ bool EDBM_select_pick(bContext *C, const int mval[2], const SelectPick_Params &p BM_edge_select_set(bm, eed, false); BM_select_history_store(bm, eed); BM_edge_select_set(bm, eed, true); + if (bm->uv_select_sync_valid) { + BM_edge_uvselect_set_pick(bm, eed, true, uv_pick_params); + } break; } case SEL_OP_SUB: { BM_select_history_remove(bm, eed); BM_edge_select_set(bm, eed, false); + if (bm->uv_select_sync_valid) { + BM_edge_uvselect_set_pick(bm, eed, false, uv_pick_params); + } break; } case SEL_OP_XOR: { if (!BM_elem_flag_test(eed, BM_ELEM_SELECT)) { BM_select_history_store(bm, eed); BM_edge_select_set(bm, eed, true); + if (bm->uv_select_sync_valid) { + BM_edge_uvselect_set_pick(bm, eed, true, uv_pick_params); + } } else { BM_select_history_remove(bm, eed); BM_edge_select_set(bm, eed, false); + if (bm->uv_select_sync_valid) { + BM_edge_uvselect_set_pick(bm, eed, false, uv_pick_params); + } } break; } @@ -2354,21 +2392,33 @@ bool EDBM_select_pick(bContext *C, const int mval[2], const SelectPick_Params &p BM_vert_select_set(bm, eve, false); BM_select_history_store(bm, eve); BM_vert_select_set(bm, eve, true); + if (bm->uv_select_sync_valid) { + BM_vert_uvselect_set_pick(bm, eve, true, uv_pick_params); + } break; } case SEL_OP_SUB: { BM_select_history_remove(bm, eve); BM_vert_select_set(bm, eve, false); + if (bm->uv_select_sync_valid) { + BM_vert_uvselect_set_pick(bm, eve, false, uv_pick_params); + } break; } case SEL_OP_XOR: { if (!BM_elem_flag_test(eve, BM_ELEM_SELECT)) { BM_select_history_store(bm, eve); BM_vert_select_set(bm, eve, true); + if (bm->uv_select_sync_valid) { + BM_vert_uvselect_set_pick(bm, eve, true, uv_pick_params); + } } else { BM_select_history_remove(bm, eve); BM_vert_select_set(bm, eve, false); + if (bm->uv_select_sync_valid) { + BM_vert_uvselect_set_pick(bm, eve, false, uv_pick_params); + } } break; } @@ -2458,6 +2508,7 @@ void EDBM_selectmode_set(BMEditMesh *em, const short selectmode) BMFace *efa; BMIter iter; + const short selectmode_prev = em->selectmode; em->selectmode = selectmode; em->bm->selectmode = selectmode; @@ -2504,6 +2555,13 @@ void EDBM_selectmode_set(BMEditMesh *em, const short selectmode) } } } + + if (em->bm->uv_select_sync_valid) { + /* NOTE(@ideasman42): this could/should use the "sticky" tool setting. + * Although in practice it's OK to assume "connected" sticky in this case. */ + const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_PROP_FLOAT2); + BM_mesh_uvselect_mode_flush_update(em->bm, selectmode_prev, selectmode, cd_loop_uv_offset); + } } void EDBM_selectmode_convert(BMEditMesh *em, @@ -2694,13 +2752,39 @@ bool EDBM_selectmode_toggle_multi(bContext *C, return false; } + /* WARNING: unfortunately failing to ensure this causes problems in *some* cases. + * Adding UV data has negative performance impacts, but failing to do this means + * switching to the UV editor *might* should strange selection. + * Since we can't know if users will proceed to do UV editing after switching modes, + * ensure the UV data. + * + * Even though the data is added, it's only added if it's needed, + * so selecting all/none or when there are no UV's. + * + * Failing to do this means switching from face to vertex selection modes + * will leave vertices on adjacent islands selected - which seems like a bug. */ + bool use_uv_select_ensure = false; + + /* Only do this when sync-select is enabled so users can have better + * performance when editing high poly meshes. */ + if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { + /* Only when flushing down. */ + if ((bitscan_forward_i(selectmode_new) < bitscan_forward_i(selectmode_old))) { + use_uv_select_ensure = true; + } + } + if (use_extend == false || selectmode_new == 0) { if (use_expand) { const short selectmode_max = highest_order_bit_s(selectmode_old); for (Object *ob_iter : objects) { BMEditMesh *em_iter = BKE_editmesh_from_object(ob_iter); EDBM_selectmode_convert(em_iter, selectmode_max, selectmode_toggle); + /* NOTE: This could be supported, but converting UV's too is reasonably complicated. + * This can be considered a low priority TODO. */ + EDBM_uvselect_clear(em_iter); } + use_uv_select_ensure = false; } } @@ -2730,14 +2814,25 @@ bool EDBM_selectmode_toggle_multi(bContext *C, if (ret == true) { BLI_assert(selectmode_new != 0); - ts->selectmode = selectmode_new; for (Object *ob_iter : objects) { BMEditMesh *em_iter = BKE_editmesh_from_object(ob_iter); + + if (use_uv_select_ensure) { + if (BM_mesh_select_is_mixed(em_iter->bm)) { + ED_uvedit_sync_uvselect_ensure_if_needed(ts, em_iter->bm); + } + else { + EDBM_uvselect_clear(em_iter); + } + } + EDBM_selectmode_set(em_iter, selectmode_new); DEG_id_tag_update(static_cast(ob_iter->data), ID_RECALC_SYNC_TO_EVAL | ID_RECALC_SELECT); WM_event_add_notifier(C, NC_GEOM | ND_SELECT, ob_iter->data); } + + ts->selectmode = selectmode_new; WM_main_add_notifier(NC_SCENE | ND_TOOLSETTINGS, nullptr); DEG_id_tag_update(&scene->id, ID_RECALC_SYNC_TO_EVAL); } @@ -3633,6 +3728,8 @@ static wmOperatorStatus edbm_select_linked_exec(bContext *C, wmOperator *op) select_linked_delimit_end(em); } + EDBM_uvselect_clear(em); + DEG_id_tag_update(static_cast(obedit->data), ID_RECALC_SELECT); WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); } @@ -3787,6 +3884,8 @@ static void edbm_select_linked_pick_ex(BMEditMesh *em, BMElem *ele, bool sel, in BMW_end(&walker); } + EDBM_uvselect_clear(em); + if (delimit) { select_linked_delimit_end(em); } @@ -4036,6 +4135,8 @@ static wmOperatorStatus edbm_select_by_pole_count_exec(bContext *C, wmOperator * if (changed) { EDBM_selectmode_flush(em); + EDBM_uvselect_clear(em); + DEG_id_tag_update(static_cast(obedit->data), ID_RECALC_SELECT); WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); } @@ -4114,6 +4215,8 @@ static wmOperatorStatus edbm_select_face_by_sides_exec(bContext *C, wmOperator * if (changed) { EDBM_selectmode_flush(em); + EDBM_uvselect_clear(em); + DEG_id_tag_update(static_cast(obedit->data), ID_RECALC_SELECT); WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); } @@ -4225,6 +4328,7 @@ static wmOperatorStatus edbm_select_loose_exec(bContext *C, wmOperator *op) if (changed) { EDBM_selectmode_flush(em); + EDBM_uvselect_clear(em); DEG_id_tag_update(static_cast(obedit->data), ID_RECALC_SELECT); WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); @@ -4294,6 +4398,7 @@ static wmOperatorStatus edbm_select_mirror_exec(bContext *C, wmOperator *op) if (tot_mirr_iter) { EDBM_selectmode_flush(em); + EDBM_uvselect_clear(em); DEG_id_tag_update(static_cast(obedit->data), ID_RECALC_SELECT); WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); @@ -4930,6 +5035,8 @@ static wmOperatorStatus edbm_select_nth_exec(bContext *C, wmOperator *op) } if (edbm_deselect_nth(em, &op_params) == true) { + EDBM_uvselect_clear(em); + found_active_elt = true; EDBMUpdate_Params params{}; params.calc_looptris = false; @@ -5021,6 +5128,8 @@ static wmOperatorStatus edbm_select_sharp_edges_exec(bContext *C, wmOperator *op else { EDBM_selectmode_flush(em); } + EDBM_uvselect_clear(em); + DEG_id_tag_update(static_cast(obedit->data), ID_RECALC_SELECT); WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); } @@ -5230,6 +5339,7 @@ static wmOperatorStatus edbm_select_non_manifold_exec(bContext *C, wmOperator *o WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); EDBM_selectmode_flush(em); + EDBM_uvselect_clear(em); } } @@ -5355,6 +5465,7 @@ static wmOperatorStatus edbm_select_random_exec(bContext *C, wmOperator *op) else { EDBM_select_flush_from_verts(em, false); } + EDBM_uvselect_clear(em); DEG_id_tag_update(static_cast(obedit->data), ID_RECALC_SELECT); WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); @@ -5451,6 +5562,8 @@ static wmOperatorStatus edbm_select_ungrouped_exec(bContext *C, wmOperator *op) if (changed) { EDBM_selectmode_flush(em); + EDBM_uvselect_clear(em); + DEG_id_tag_update(static_cast(obedit->data), ID_RECALC_SELECT); WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); } @@ -5578,6 +5691,8 @@ static wmOperatorStatus edbm_select_axis_exec(bContext *C, wmOperator *op) } if (changed) { EDBM_selectmode_flush(em_iter); + EDBM_uvselect_clear(em); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit_iter->data); DEG_id_tag_update(static_cast(obedit_iter->data), ID_RECALC_SELECT); } @@ -5897,6 +6012,7 @@ static wmOperatorStatus edbm_loop_to_region_exec(bContext *C, wmOperator *op) if (changed) { EDBM_selectmode_flush(em); + EDBM_uvselect_clear(em); DEG_id_tag_update(static_cast(obedit->data), ID_RECALC_SELECT); WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); @@ -6018,6 +6134,7 @@ static wmOperatorStatus edbm_select_by_attribute_exec(bContext *C, wmOperator * if (changed) { EDBM_selectmode_flush(em); + EDBM_uvselect_clear(em); DEG_id_tag_update(static_cast(obedit->data), ID_RECALC_SELECT); WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); diff --git a/source/blender/editors/mesh/editmesh_select_similar.cc b/source/blender/editors/mesh/editmesh_select_similar.cc index 61e79f6e34e..1abc4e793d0 100644 --- a/source/blender/editors/mesh/editmesh_select_similar.cc +++ b/source/blender/editors/mesh/editmesh_select_similar.cc @@ -454,6 +454,8 @@ static wmOperatorStatus similar_face_select_exec(bContext *C, wmOperator *op) if (changed) { EDBM_selectmode_flush(em); + EDBM_uvselect_clear(em); + EDBMUpdate_Params params{}; params.calc_looptris = false; params.calc_normals = false; @@ -479,6 +481,8 @@ static wmOperatorStatus similar_face_select_exec(bContext *C, wmOperator *op) } } EDBM_selectmode_flush(em); + EDBM_uvselect_clear(em); + EDBMUpdate_Params params{}; params.calc_looptris = false; params.calc_normals = false; @@ -894,6 +898,8 @@ static wmOperatorStatus similar_edge_select_exec(bContext *C, wmOperator *op) if (changed) { EDBM_selectmode_flush(em); + EDBM_uvselect_clear(em); + EDBMUpdate_Params params{}; params.calc_looptris = false; params.calc_normals = false; @@ -919,6 +925,8 @@ static wmOperatorStatus similar_edge_select_exec(bContext *C, wmOperator *op) } } EDBM_selectmode_flush(em); + EDBM_uvselect_clear(em); + EDBMUpdate_Params params{}; params.calc_looptris = false; params.calc_normals = false; diff --git a/source/blender/editors/mesh/editmesh_tools.cc b/source/blender/editors/mesh/editmesh_tools.cc index 14873f54e0d..b852ec248e7 100644 --- a/source/blender/editors/mesh/editmesh_tools.cc +++ b/source/blender/editors/mesh/editmesh_tools.cc @@ -5535,6 +5535,7 @@ static wmOperatorStatus edbm_tris_convert_to_quads_exec(bContext *C, wmOperator EDBM_selectmode_flush_ex(em, em->selectmode); } } + EDBM_uvselect_clear(em); BM_custom_loop_normals_from_vector_layer(em->bm, false); @@ -5765,6 +5766,7 @@ static wmOperatorStatus edbm_decimate_exec(bContext *C, wmOperator *op) selectmode |= SCE_SELECT_EDGE; } EDBM_selectmode_flush_ex(em, selectmode); + EDBM_uvselect_clear(em); } EDBMUpdate_Params params{}; params.calc_looptris = true; @@ -6419,6 +6421,7 @@ static wmOperatorStatus edbm_delete_edgeloop_exec(bContext *C, wmOperator *op) BM_mesh_elem_hflag_enable_test(em->bm, BM_FACE, BM_ELEM_SELECT, true, false, BM_ELEM_TAG); EDBM_selectmode_flush_ex(em, SCE_SELECT_VERTEX); + EDBM_uvselect_clear(em); EDBMUpdate_Params params{}; params.calc_looptris = true; @@ -7726,7 +7729,9 @@ static wmOperatorStatus edbm_convex_hull_exec(bContext *C, wmOperator *op) params.calc_normals = false; params.is_destructive = true; EDBM_update(static_cast(obedit->data), ¶ms); + EDBM_selectmode_flush(em); + EDBM_uvselect_clear(em); } return OPERATOR_FINISHED; @@ -7823,7 +7828,9 @@ static wmOperatorStatus mesh_symmetrize_exec(bContext *C, wmOperator *op) params.calc_normals = calc_normals; params.is_destructive = true; EDBM_update(static_cast(obedit->data), ¶ms); + EDBM_selectmode_flush(em); + EDBM_uvselect_clear(em); } return OPERATOR_FINISHED; diff --git a/source/blender/editors/mesh/editmesh_undo.cc b/source/blender/editors/mesh/editmesh_undo.cc index 3fe30ed1610..4b94ca6fb7d 100644 --- a/source/blender/editors/mesh/editmesh_undo.cc +++ b/source/blender/editors/mesh/editmesh_undo.cc @@ -853,7 +853,24 @@ static void *undomesh_from_editmesh(UndoMesh *um, } /* Uncomment for troubleshooting. */ - // BM_mesh_is_valid(em->bm); + if (false) { + BM_mesh_is_valid(em->bm); + + /* Ensure UV's are in a valid state. */ + if (em->bm->uv_select_sync_valid) { + const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_PROP_FLOAT2); + bool check_flush = true; + /* This should check the sticky mode too (currently the scene isn't available). */ + bool check_contiguous = (cd_loop_uv_offset != -1); + UVSelectValidateInfo info; + bool is_valid = BM_mesh_uvselect_is_valid( + em->bm, cd_loop_uv_offset, true, check_flush, check_contiguous, &info); + if (is_valid == false) { + fprintf(stderr, "ERROR: UV sync check failed!\n"); + } + // BLI_assert(is_valid); + } + } CustomData_MeshMasks cd_mask_extra{}; cd_mask_extra.vmask = CD_MASK_SHAPE_KEYINDEX; diff --git a/source/blender/editors/mesh/editmesh_utils.cc b/source/blender/editors/mesh/editmesh_utils.cc index 25ac06ed875..ca938aa59b8 100644 --- a/source/blender/editors/mesh/editmesh_utils.cc +++ b/source/blender/editors/mesh/editmesh_utils.cc @@ -408,6 +408,7 @@ void EDBM_select_more(BMEditMesh *em, const bool use_face_step) BMO_op_finish(em->bm, &bmop); EDBM_selectmode_flush(em); + EDBM_uvselect_clear(em); } void EDBM_select_less(BMEditMesh *em, const bool use_face_step) @@ -430,6 +431,7 @@ void EDBM_select_less(BMEditMesh *em, const bool use_face_step) BMO_op_finish(em->bm, &bmop); EDBM_selectmode_flush(em); + EDBM_uvselect_clear(em); /* only needed for select less, ensure we don't have isolated elements remaining */ BM_mesh_select_mode_clean(em->bm); @@ -438,11 +440,26 @@ void EDBM_select_less(BMEditMesh *em, const bool use_face_step) void EDBM_flag_disable_all(BMEditMesh *em, const char hflag) { BM_mesh_elem_hflag_disable_all(em->bm, BM_VERT | BM_EDGE | BM_FACE, hflag, false); + + /* Keep this as there is no need to maintain UV selection when all are disabled. */ + if (hflag & BM_ELEM_SELECT) { + EDBM_uvselect_clear(em); + } } void EDBM_flag_enable_all(BMEditMesh *em, const char hflag) { BM_mesh_elem_hflag_enable_all(em->bm, BM_VERT | BM_EDGE | BM_FACE, hflag, true); + + /* Keep this as there is no need to maintain UV selection when all are enabled. */ + if (hflag & BM_ELEM_SELECT) { + EDBM_uvselect_clear(em); + } +} + +bool EDBM_uvselect_clear(BMEditMesh *em) +{ + return BM_mesh_uvselect_clear(em->bm); } /** \} */ @@ -650,6 +667,7 @@ static void bm_uv_assign_island(UvElementMap *element_map, static int bm_uv_edge_select_build_islands(UvElementMap *element_map, const Scene *scene, + const BMesh *bm, UvElement *islandbuf, uint *map, bool uv_selected, @@ -694,7 +712,7 @@ static int bm_uv_edge_select_build_islands(UvElementMap *element_map, while (element) { /* Scan forwards around the BMFace that contains element->l. */ - if (!uv_selected || uvedit_edge_select_test(scene, element->l, offsets)) { + if (!uv_selected || uvedit_edge_select_test(scene, bm, element->l, offsets)) { UvElement *next = BM_uv_element_get(element_map, element->l->next); if (next && next->island == INVALID_ISLAND) { UvElement *tail = element_map->head_table[next - element_map->storage]; @@ -710,7 +728,7 @@ static int bm_uv_edge_select_build_islands(UvElementMap *element_map, } /* Scan backwards around the BMFace that contains element->l. */ - if (!uv_selected || uvedit_edge_select_test(scene, element->l->prev, offsets)) { + if (!uv_selected || uvedit_edge_select_test(scene, bm, element->l->prev, offsets)) { UvElement *prev = BM_uv_element_get(element_map, element->l->prev); if (prev && prev->island == INVALID_ISLAND) { UvElement *tail = element_map->head_table[prev - element_map->storage]; @@ -766,7 +784,7 @@ static void bm_uv_build_islands(UvElementMap *element_map, scene->toolsettings->uv_selectmode & UV_SELECT_EDGE; if (use_uv_edge_connectivity) { nislands = bm_uv_edge_select_build_islands( - element_map, scene, islandbuf, map, uv_selected, uv_offsets); + element_map, scene, bm, islandbuf, map, uv_selected, uv_offsets); islandbufsize = totuv; } @@ -784,7 +802,7 @@ static void bm_uv_build_islands(UvElementMap *element_map, BMLoop *l; BMIter liter; BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { - if (uv_selected && !uvedit_uv_select_test(scene, l, uv_offsets)) { + if (uv_selected && !uvedit_uv_select_test(scene, bm, l, uv_offsets)) { continue; } @@ -1013,7 +1031,7 @@ UvElementMap *BM_uv_element_map_create(BMesh *bm, else { BMLoop *l; BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { - if (uvedit_uv_select_test(scene, l, offsets)) { + if (uvedit_uv_select_test(scene, bm, l, offsets)) { totuv++; } } @@ -1048,7 +1066,7 @@ UvElementMap *BM_uv_element_map_create(BMesh *bm, int i; BMLoop *l; BM_ITER_ELEM_INDEX (l, &liter, efa, BM_LOOPS_OF_FACE, i) { - if (uv_selected && !uvedit_uv_select_test(scene, l, offsets)) { + if (uv_selected && !uvedit_uv_select_test(scene, bm, l, offsets)) { continue; } @@ -1084,7 +1102,7 @@ UvElementMap *BM_uv_element_map_create(BMesh *bm, newvlist = v; const float *uv = static_cast(BM_ELEM_CD_GET_VOID_P(v->l, offsets.uv)); - bool uv_vert_sel = uvedit_uv_select_test(scene, v->l, offsets); + bool uv_vert_sel = uvedit_uv_select_test(scene, bm, v->l, offsets); UvElement *lastv = nullptr; UvElement *iterv = vlist; @@ -1104,7 +1122,7 @@ UvElementMap *BM_uv_element_map_create(BMesh *bm, if (connected) { /* Check if the uv loops share the same selection state (if not, they are not connected * as they have been ripped or other edit commands have separated them). */ - const bool uv2_vert_sel = uvedit_uv_select_test(scene, iterv->l, offsets); + const bool uv2_vert_sel = uvedit_uv_select_test(scene, bm, iterv->l, offsets); connected = (uv_vert_sel == uv2_vert_sel); } @@ -1566,6 +1584,7 @@ bool EDBM_mesh_hide(BMEditMesh *em, bool swap) if (changed) { EDBM_selectmode_flush(em); + EDBM_uvselect_clear(em); } return changed; @@ -1635,6 +1654,53 @@ bool EDBM_mesh_reveal(BMEditMesh *em, bool select) } } + if (em->bm->uv_select_sync_valid) { + BMesh *bm = em->bm; + /* NOTE(@ideasman42): this could/should use the "sticky" tool setting. + * Although in practice it's OK to assume "connected" sticky in this case. */ + const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_PROP_FLOAT2); + if (cd_loop_uv_offset == -1) { + /* Not expected but not an error either, clear if the UV's have been removed. */ + EDBM_uvselect_clear(em); + } + else { + BMIter iter; + BMFace *f; + + if (em->selectmode & SCE_SELECT_VERTEX) { + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + if (BM_elem_flag_test(l_iter->v, BM_ELEM_TAG)) { + BM_loop_vert_uvselect_set_shared(bm, l_iter, select, cd_loop_uv_offset); + } + } while ((l_iter = l_iter->next) != l_first); + } + } + else if (em->selectmode & SCE_SELECT_EDGE) { + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + if (BM_elem_flag_test(l_iter->e, BM_ELEM_TAG)) { + BM_loop_edge_uvselect_set_shared(bm, l_iter, select, cd_loop_uv_offset); + } + } while ((l_iter = l_iter->next) != l_first); + } + } + else { + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_TAG)) { + BM_face_uvselect_set_shared(bm, f, select, cd_loop_uv_offset); + } + } + } + + BM_mesh_uvselect_mode_flush(bm); + } + } + EDBM_selectmode_flush(em); /* hidden faces can have invalid normals */ diff --git a/source/blender/editors/object/object_vgroup.cc b/source/blender/editors/object/object_vgroup.cc index 46bbe665cbb..94dc4647837 100644 --- a/source/blender/editors/object/object_vgroup.cc +++ b/source/blender/editors/object/object_vgroup.cc @@ -1154,6 +1154,8 @@ static void vgroup_select_verts(const ToolSettings &tool_settings, /* This has to be called, because this function operates on vertices only. * Vertices to edges/faces. */ EDBM_select_flush_from_verts(em, select); + + EDBM_uvselect_clear(em); } } else { diff --git a/source/blender/editors/space_view3d/view3d_select.cc b/source/blender/editors/space_view3d/view3d_select.cc index 6dafe659a0a..def785b4648 100644 --- a/source/blender/editors/space_view3d/view3d_select.cc +++ b/source/blender/editors/space_view3d/view3d_select.cc @@ -81,6 +81,7 @@ #include "ED_screen.hh" #include "ED_sculpt.hh" #include "ED_select_utils.hh" +#include "ED_uvedit.hh" #include "UI_interface.hh" #include "UI_resources.hh" @@ -105,6 +106,7 @@ using blender::Array; using blender::int2; using blender::Span; using blender::Vector; +using blender::ed::uv::UVSyncSelectFromMesh; /* -------------------------------------------------------------------- */ /** \name Public Utilities @@ -250,6 +252,7 @@ static bool edbm_backbuf_check_and_select_verts(EditSelectBuf_Cache *esel, Depsgraph *depsgraph, Object *ob, BMEditMesh *em, + UVSyncSelectFromMesh *uv_selctx, const eSelectOp sel_op) { BMVert *eve; @@ -270,6 +273,10 @@ static bool edbm_backbuf_check_and_select_verts(EditSelectBuf_Cache *esel, const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside); if (sel_op_result != -1) { BM_vert_select_set(em->bm, eve, sel_op_result); + if (uv_selctx) { + uv_selctx->vert_select_set(eve, sel_op_result); + } + changed = true; } } @@ -282,6 +289,7 @@ static bool edbm_backbuf_check_and_select_edges(EditSelectBuf_Cache *esel, Depsgraph *depsgraph, Object *ob, BMEditMesh *em, + UVSyncSelectFromMesh *uv_selctx, const eSelectOp sel_op) { BMEdge *eed; @@ -303,6 +311,10 @@ static bool edbm_backbuf_check_and_select_edges(EditSelectBuf_Cache *esel, if (sel_op_result != -1) { BM_edge_select_set(em->bm, eed, sel_op_result); changed = true; + + if (uv_selctx) { + uv_selctx->edge_select_set(eed, sel_op_result); + } } } index++; @@ -314,6 +326,7 @@ static bool edbm_backbuf_check_and_select_faces(EditSelectBuf_Cache *esel, Depsgraph *depsgraph, Object *ob, BMEditMesh *em, + UVSyncSelectFromMesh *uv_selctx, const eSelectOp sel_op) { BMFace *efa; @@ -334,6 +347,10 @@ static bool edbm_backbuf_check_and_select_faces(EditSelectBuf_Cache *esel, const int sel_op_result = ED_select_op_action_deselected(sel_op, is_select, is_inside); if (sel_op_result != -1) { BM_face_select_set(em->bm, efa, sel_op_result); + if (uv_selctx) { + uv_selctx->face_select_set(efa, sel_op_result); + } + changed = true; } } @@ -419,6 +436,9 @@ struct LassoSelectUserData { eSelectOp sel_op; eBezTriple_Flag select_flag; + /** Only for edit-mesh selection. */ + UVSyncSelectFromMesh *uv_selctx = nullptr; + /* runtime */ int pass; bool is_done; @@ -722,6 +742,10 @@ static void do_lasso_select_mesh__doSelectVert(void *user_data, const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); if (sel_op_result != -1) { BM_vert_select_set(data->vc->em->bm, eve, sel_op_result); + if (data->uv_selctx) { + data->uv_selctx->vert_select_set(eve, sel_op_result); + } + data->is_changed = true; } } @@ -753,6 +777,10 @@ static void do_lasso_select_mesh__doSelectEdge_pass0(void *user_data, const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); if (sel_op_result != -1) { BM_edge_select_set(data->vc->em->bm, eed, sel_op_result); + if (data->uv_selctx) { + data->uv_selctx->edge_select_set(eed, sel_op_result); + } + data->is_done = true; data->is_changed = true; } @@ -780,6 +808,10 @@ static void do_lasso_select_mesh__doSelectEdge_pass1(void *user_data, const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); if (sel_op_result != -1) { BM_edge_select_set(data->vc->em->bm, eed, sel_op_result); + if (data->uv_selctx) { + data->uv_selctx->edge_select_set(eed, sel_op_result); + } + data->is_changed = true; } } @@ -797,6 +829,10 @@ static void do_lasso_select_mesh__doSelectFace(void *user_data, const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); if (sel_op_result != -1) { BM_face_select_set(data->vc->em->bm, efa, sel_op_result); + if (data->uv_selctx) { + data->uv_selctx->face_select_set(efa, sel_op_result); + } + data->is_changed = true; } } @@ -823,6 +859,10 @@ static bool do_lasso_select_mesh(const ViewContext *vc, } } + std::unique_ptr uv_selctx = UVSyncSelectFromMesh::create_if_needed( + *ts, *vc->em->bm); + data.uv_selctx = uv_selctx.get(); + /* for non zbuf projections, don't change the GL state */ ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); @@ -843,7 +883,7 @@ static bool do_lasso_select_mesh(const ViewContext *vc, if (ts->selectmode & SCE_SELECT_VERTEX) { if (use_zbuf) { data.is_changed |= edbm_backbuf_check_and_select_verts( - esel, vc->depsgraph, vc->obedit, vc->em, sel_op); + esel, vc->depsgraph, vc->obedit, vc->em, data.uv_selctx, sel_op); } else { mesh_foreachScreenVert( @@ -877,7 +917,7 @@ static bool do_lasso_select_mesh(const ViewContext *vc, if (ts->selectmode & SCE_SELECT_FACE) { if (use_zbuf) { data.is_changed |= edbm_backbuf_check_and_select_faces( - esel, vc->depsgraph, vc->obedit, vc->em, sel_op); + esel, vc->depsgraph, vc->obedit, vc->em, data.uv_selctx, sel_op); } else { mesh_foreachScreenFace( @@ -888,6 +928,11 @@ static bool do_lasso_select_mesh(const ViewContext *vc, if (data.is_changed) { EDBM_selectmode_flush(vc->em); } + + if (data.uv_selctx) { + data.uv_selctx->apply(); + } + return data.is_changed; } @@ -3691,6 +3736,9 @@ struct BoxSelectUserData { eSelectOp sel_op; eBezTriple_Flag select_flag; + /** Only for edit-mesh selection. */ + UVSyncSelectFromMesh *uv_selctx; + /* runtime */ bool is_done; bool is_changed; @@ -3954,6 +4002,10 @@ static void do_mesh_box_select__doSelectVert(void *user_data, const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); if (sel_op_result != -1) { BM_vert_select_set(data->vc->em->bm, eve, sel_op_result); + if (data->uv_selctx) { + data->uv_selctx->vert_select_set(eve, sel_op_result); + } + data->is_changed = true; } } @@ -3986,6 +4038,10 @@ static void do_mesh_box_select__doSelectEdge_pass0(void *user_data, const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); if (sel_op_result != -1) { BM_edge_select_set(data->vc->em->bm, eed, sel_op_result); + if (data->uv_selctx) { + data->uv_selctx->edge_select_set(eed, sel_op_result); + } + data->is_done = true; data->is_changed = true; } @@ -4013,6 +4069,10 @@ static void do_mesh_box_select__doSelectEdge_pass1(void *user_data, const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); if (sel_op_result != -1) { BM_edge_select_set(data->vc->em->bm, eed, sel_op_result); + if (data->uv_selctx) { + data->uv_selctx->edge_select_set(eed, sel_op_result); + } + data->is_changed = true; } } @@ -4027,6 +4087,10 @@ static void do_mesh_box_select__doSelectFace(void *user_data, const int sel_op_result = ED_select_op_action_deselected(data->sel_op, is_select, is_inside); if (sel_op_result != -1) { BM_face_select_set(data->vc->em->bm, efa, sel_op_result); + if (data->uv_selctx) { + data->uv_selctx->face_select_set(efa, sel_op_result); + } + data->is_changed = true; } } @@ -4047,6 +4111,10 @@ static bool do_mesh_box_select(const ViewContext *vc, } } + std::unique_ptr uv_selctx = UVSyncSelectFromMesh::create_if_needed( + *ts, *vc->em->bm); + data.uv_selctx = uv_selctx.get(); + /* for non zbuf projections, don't change the GL state */ ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); @@ -4067,7 +4135,7 @@ static bool do_mesh_box_select(const ViewContext *vc, if (ts->selectmode & SCE_SELECT_VERTEX) { if (use_zbuf) { data.is_changed |= edbm_backbuf_check_and_select_verts( - esel, vc->depsgraph, vc->obedit, vc->em, sel_op); + esel, vc->depsgraph, vc->obedit, vc->em, data.uv_selctx, sel_op); } else { mesh_foreachScreenVert( @@ -4101,7 +4169,7 @@ static bool do_mesh_box_select(const ViewContext *vc, if (ts->selectmode & SCE_SELECT_FACE) { if (use_zbuf) { data.is_changed |= edbm_backbuf_check_and_select_faces( - esel, vc->depsgraph, vc->obedit, vc->em, sel_op); + esel, vc->depsgraph, vc->obedit, vc->em, data.uv_selctx, sel_op); } else { mesh_foreachScreenFace( @@ -4112,6 +4180,11 @@ static bool do_mesh_box_select(const ViewContext *vc, if (data.is_changed) { EDBM_selectmode_flush(vc->em); } + + if (data.uv_selctx) { + data.uv_selctx->apply(); + } + return data.is_changed; } @@ -4650,6 +4723,9 @@ struct CircleSelectUserData { float radius_squared; eBezTriple_Flag select_flag; + /** Only for edit-mesh selection. */ + UVSyncSelectFromMesh *uv_selctx = nullptr; + /* runtime */ bool is_changed; }; @@ -4685,6 +4761,10 @@ static void mesh_circle_doSelectVert(void *user_data, if (len_squared_v2v2(data->mval_fl, screen_co) <= data->radius_squared) { BM_vert_select_set(data->vc->em->bm, eve, data->select); + if (data->uv_selctx) { + data->uv_selctx->vert_select_set(eve, data->select); + } + data->is_changed = true; } } @@ -4698,6 +4778,10 @@ static void mesh_circle_doSelectEdge(void *user_data, if (edge_inside_circle(data->mval_fl, data->radius, screen_co_a, screen_co_b)) { BM_edge_select_set(data->vc->em->bm, eed, data->select); + if (data->uv_selctx) { + data->uv_selctx->edge_select_set(eed, data->select); + } + data->is_changed = true; } } @@ -4710,6 +4794,10 @@ static void mesh_circle_doSelectFace(void *user_data, if (len_squared_v2v2(data->mval_fl, screen_co) <= data->radius_squared) { BM_face_select_set(data->vc->em->bm, efa, data->select); + if (data->uv_selctx) { + data->uv_selctx->face_select_set(efa, data->select); + } + data->is_changed = true; } } @@ -4734,6 +4822,11 @@ static bool mesh_circle_select(const ViewContext *vc, changed = true; } } + + std::unique_ptr uv_selctx = UVSyncSelectFromMesh::create_if_needed( + *ts, *vc->em->bm); + data.uv_selctx = uv_selctx.get(); + const bool select = (sel_op != SEL_OP_SUB); ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); /* for foreach's screen/vert projection */ @@ -4759,8 +4852,12 @@ static bool mesh_circle_select(const ViewContext *vc, if (ts->selectmode & SCE_SELECT_VERTEX) { if (use_zbuf) { if (esel->select_bitmap != nullptr) { - changed |= edbm_backbuf_check_and_select_verts( - esel, vc->depsgraph, vc->obedit, vc->em, select ? SEL_OP_ADD : SEL_OP_SUB); + changed |= edbm_backbuf_check_and_select_verts(esel, + vc->depsgraph, + vc->obedit, + vc->em, + data.uv_selctx, + select ? SEL_OP_ADD : SEL_OP_SUB); } } else { @@ -4771,8 +4868,12 @@ static bool mesh_circle_select(const ViewContext *vc, if (ts->selectmode & SCE_SELECT_EDGE) { if (use_zbuf) { if (esel->select_bitmap != nullptr) { - changed |= edbm_backbuf_check_and_select_edges( - esel, vc->depsgraph, vc->obedit, vc->em, select ? SEL_OP_ADD : SEL_OP_SUB); + changed |= edbm_backbuf_check_and_select_edges(esel, + vc->depsgraph, + vc->obedit, + vc->em, + data.uv_selctx, + select ? SEL_OP_ADD : SEL_OP_SUB); } } else { @@ -4787,8 +4888,12 @@ static bool mesh_circle_select(const ViewContext *vc, if (ts->selectmode & SCE_SELECT_FACE) { if (use_zbuf) { if (esel->select_bitmap != nullptr) { - changed |= edbm_backbuf_check_and_select_faces( - esel, vc->depsgraph, vc->obedit, vc->em, select ? SEL_OP_ADD : SEL_OP_SUB); + changed |= edbm_backbuf_check_and_select_faces(esel, + vc->depsgraph, + vc->obedit, + vc->em, + data.uv_selctx, + select ? SEL_OP_ADD : SEL_OP_SUB); } } else { @@ -4801,6 +4906,11 @@ static bool mesh_circle_select(const ViewContext *vc, if (changed) { BM_mesh_select_mode_flush_ex(vc->em->bm, vc->em->selectmode, BMSelectFlushFlag::None); } + + if (data.uv_selctx) { + data.uv_selctx->apply(); + } + return changed; } diff --git a/source/blender/editors/transform/transform_convert_mesh_uv.cc b/source/blender/editors/transform/transform_convert_mesh_uv.cc index 56fde09961f..db431194c84 100644 --- a/source/blender/editors/transform/transform_convert_mesh_uv.cc +++ b/source/blender/editors/transform/transform_convert_mesh_uv.cc @@ -110,7 +110,7 @@ static void uv_set_connectivity_distance(const ToolSettings *ts, BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) { float dist; - bool uv_vert_sel = uvedit_uv_select_test_ex(ts, l, offsets); + bool uv_vert_sel = uvedit_uv_select_test_ex(ts, bm, l, offsets); if (uv_vert_sel) { BLI_LINKSTACK_PUSH(queue, l); @@ -296,7 +296,7 @@ static void createTransUVs(bContext *C, TransInfo *t) /* Make sure that the loop element flag is cleared for when we use it in * uv_set_connectivity_distance later. */ BM_elem_flag_disable(l, BM_ELEM_TAG); - if (uvedit_uv_select_test(scene, l, offsets)) { + if (uvedit_uv_select_test(scene, em->bm, l, offsets)) { countsel++; if (island_center) { @@ -359,7 +359,7 @@ static void createTransUVs(bContext *C, TransInfo *t) } BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { - const bool selected = uvedit_uv_select_test(scene, l, offsets); + const bool selected = uvedit_uv_select_test(scene, em->bm, l, offsets); float (*luv)[2]; const float *center = nullptr; float prop_distance = FLT_MAX; @@ -871,7 +871,7 @@ Array transform_mesh_uv_edge_slide_data_create(const Tra if (check_edge) { BMLoop *l_edge = l_dst == l->prev ? l_dst : l; - if (!uvedit_edge_select_test_ex(t->settings, l_edge, offsets)) { + if (!uvedit_edge_select_test_ex(t->settings, bm, l_edge, offsets)) { continue; } } diff --git a/source/blender/editors/uvedit/uvedit_buttons.cc b/source/blender/editors/uvedit/uvedit_buttons.cc index ebd77eb46ca..da86c89e8db 100644 --- a/source/blender/editors/uvedit/uvedit_buttons.cc +++ b/source/blender/editors/uvedit/uvedit_buttons.cc @@ -66,7 +66,7 @@ static int uvedit_center(Scene *scene, const Span objects, float cente } BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) { - if (uvedit_uv_select_test(scene, l, offsets)) { + if (uvedit_uv_select_test(scene, em->bm, l, offsets)) { luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv); add_v2_v2(center, luv); tot++; @@ -101,7 +101,7 @@ static void uvedit_translate(Scene *scene, const Span objects, const f } BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) { - if (uvedit_uv_select_test(scene, l, offsets)) { + if (uvedit_uv_select_test(scene, em->bm, l, offsets)) { luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv); add_v2_v2(luv, delta); } diff --git a/source/blender/editors/uvedit/uvedit_intern.hh b/source/blender/editors/uvedit/uvedit_intern.hh index 87f707d32fe..04fefd592bc 100644 --- a/source/blender/editors/uvedit/uvedit_intern.hh +++ b/source/blender/editors/uvedit/uvedit_intern.hh @@ -87,17 +87,22 @@ BMLoop *uv_find_nearest_loop_from_vert(Scene *scene, Object *obedit, BMVert *v, BMLoop *uv_find_nearest_loop_from_edge(Scene *scene, Object *obedit, BMEdge *e, const float co[2]); bool uvedit_vert_is_edge_select_any_other(const ToolSettings *ts, + const BMesh *bm, const BMLoop *l, const BMUVOffsets &offsets); bool uvedit_vert_is_face_select_any_other(const ToolSettings *ts, + const BMesh *bm, const BMLoop *l, const BMUVOffsets &offsets); +bool uvedit_edge_is_face_select_any_other(const ToolSettings *ts, + const BMesh *bm, + const BMLoop *l, + const BMUVOffsets &offsets); + bool uvedit_vert_is_all_other_faces_selected(const ToolSettings *ts, + const BMesh *bm, const BMLoop *l, const BMUVOffsets &offsets); -bool uvedit_edge_is_face_select_any_other(const ToolSettings *ts, - const BMLoop *l, - const BMUVOffsets &offsets); /* utility tool functions */ @@ -131,6 +136,9 @@ void UV_OT_shortest_path_select(wmOperatorType *ot); /* `uvedit_select.cc` */ void uvedit_select_prepare_custom_data(const Scene *scene, BMesh *bm); +void uvedit_select_prepare_sync_select(const Scene *scene, BMesh *bm); + +void uvedit_select_prepare_UNUSED(const Scene *scene, BMesh *bm); bool uvedit_select_is_any_selected(const Scene *scene, BMesh *bm); bool uvedit_select_is_any_selected_multi(const Scene *scene, blender::Span objects); @@ -139,6 +147,7 @@ bool uvedit_select_is_any_selected_multi(const Scene *scene, blender::Spanbm, l, offsets)) { uvs.append(BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv)); } } @@ -1575,7 +1575,7 @@ static bool uv_snap_uvs_to_adjacent_unselected(Scene *scene, Object *obedit) if (uvedit_face_visible_test(scene, f)) { BM_elem_flag_enable(f, BM_ELEM_TAG); BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) { - BM_elem_flag_set(l, BM_ELEM_TAG, uvedit_uv_select_test(scene, l, offsets)); + BM_elem_flag_set(l, BM_ELEM_TAG, uvedit_uv_select_test(scene, bm, l, offsets)); } } else { @@ -1764,7 +1764,7 @@ static wmOperatorStatus uv_pin_exec(bContext *C, wmOperator *op) BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { - if (uvedit_uv_select_test(scene, l, offsets)) { + if (uvedit_uv_select_test(scene, em->bm, l, offsets)) { changed = true; if (invert) { BM_ELEM_CD_SET_BOOL(l, offsets.pin, !BM_ELEM_CD_GET_BOOL(l, offsets.pin)); @@ -1840,6 +1840,127 @@ static bool bm_face_is_all_uv_sel(BMFace *f, bool select_test, const BMUVOffsets return true; } +static bool uv_mesh_hide_sync_select(const ToolSettings *ts, Object *ob, BMEditMesh *em, bool swap) +{ + const bool select_to_hide = !swap; + BMesh *bm = em->bm; + bool changed = false; + + if (bm->uv_select_sync_valid == false || ED_uvedit_sync_uvselect_ignore(ts)) { + /* Simple case, no need to synchronize UV's, forward to mesh hide. */ + changed = EDBM_mesh_hide(em, swap); + } + else { + /* For vertices & edges hiding faces immediately causes a feedback loop, + * where hiding doesn't work predictably as values are being both read and written to. + * Perform two passes, use tagging. */ + + /* Vertex and edge modes use almost the same logic. */ + if (em->selectmode & (SCE_SELECT_VERTEX | SCE_SELECT_EDGE)) { + BMIter iter; + BM_mesh_elem_hflag_disable_all(bm, BM_FACE, BM_ELEM_TAG, false); + + if (em->selectmode & SCE_SELECT_VERTEX) { + BMFace *f; + BM_ITER_MESH (f, &iter, em->bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + if ((BM_elem_flag_test_bool(l_iter->v, BM_ELEM_SELECT) == select_to_hide) && + (BM_elem_flag_test_bool(l_iter, BM_ELEM_SELECT_UV) == select_to_hide)) + { + BM_elem_flag_enable(l_iter->f, BM_ELEM_TAG); + changed = true; + break; + } + } while ((l_iter = l_iter->next) != l_first); + } + } + else { + BLI_assert(em->selectmode & SCE_SELECT_EDGE); + BMFace *f; + BM_ITER_MESH (f, &iter, em->bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + if ((BM_elem_flag_test_bool(l_iter->e, BM_ELEM_SELECT) == select_to_hide) && + (BM_elem_flag_test_bool(l_iter, BM_ELEM_SELECT_UV_EDGE) == select_to_hide)) + { + BM_elem_flag_enable(l_iter->f, BM_ELEM_TAG); + changed = true; + break; + } + } while ((l_iter = l_iter->next) != l_first); + } + } + + if (changed) { + BMFace *f; + BM_ITER_MESH (f, &iter, em->bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_TAG)) { + BM_elem_hide_set(bm, f, true); + } + } + if (swap) { + /* Without re-selecting, the faces vertices are de-selected when hiding adjacent faces. + * + * TODO(@ideasman42): consider a more elegant solution of ensuring + * faces at the boundaries don't get their vertices de-selected. + * This is low-priority as it's no a bottleneck. */ + BM_ITER_MESH (f, &iter, em->bm, BM_FACES_OF_MESH) { + if (!BM_elem_flag_test(f, BM_ELEM_TAG)) { + BM_face_select_set(bm, f, true); + } + } + } + } + } + else { + BLI_assert(em->selectmode & SCE_SELECT_FACE); + BMIter iter; + BMFace *f; + BM_ITER_MESH (f, &iter, em->bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + continue; + } + if (BM_elem_flag_test_bool(f, BM_ELEM_SELECT_UV) == select_to_hide) { + BM_elem_hide_set(bm, f, true); + changed = true; + } + } + } + + if (changed) { + if (swap) { + EDBM_selectmode_flush(em); + } + else { + EDBM_flag_disable_all(em, BM_ELEM_SELECT); + } + /* Clearing is OK even when hiding unselected + * as the remaining geometry is entirely selected. */ + EDBM_uvselect_clear(em); + } + } + + if (changed) { + Mesh *mesh = static_cast(ob->data); + EDBMUpdate_Params params = {0}; + params.calc_looptris = true; + params.calc_normals = false; + params.is_destructive = false; + EDBM_update(mesh, ¶ms); + } + + return changed; +} + static wmOperatorStatus uv_hide_exec(bContext *C, wmOperator *op) { ViewLayer *view_layer = CTX_data_view_layer(C); @@ -1870,14 +1991,7 @@ static wmOperatorStatus uv_hide_exec(bContext *C, wmOperator *op) const BMUVOffsets offsets = BM_uv_map_offsets_get(em->bm); if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { - if (EDBM_mesh_hide(em, swap)) { - Mesh *mesh = static_cast(ob->data); - EDBMUpdate_Params params = {0}; - params.calc_looptris = true; - params.calc_normals = false; - params.is_destructive = false; - EDBM_update(mesh, ¶ms); - } + uv_mesh_hide_sync_select(ts, ob, em, swap); continue; } @@ -2273,7 +2387,7 @@ static wmOperatorStatus uv_seams_from_islands_exec(bContext *C, wmOperator *op) if (l_iter == l_iter->radial_next) { continue; } - if (!uvedit_edge_select_test(scene, l_iter, offsets)) { + if (!uvedit_edge_select_test(scene, em->bm, l_iter, offsets)) { continue; } @@ -2364,7 +2478,7 @@ static wmOperatorStatus uv_mark_seam_exec(bContext *C, wmOperator *op) BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { if (uvedit_face_visible_test(scene, efa)) { BM_ITER_ELEM (loop, &liter, efa, BM_LOOPS_OF_FACE) { - if (uvedit_edge_select_test(scene, loop, offsets)) { + if (uvedit_edge_select_test(scene, bm, loop, offsets)) { BM_elem_flag_set(loop->e, BM_ELEM_SEAM, flag_set); changed = true; } @@ -2518,8 +2632,8 @@ static bool uv_copy_mirrored_faces( for (const auto &[f_dst, f_src] : face_map.items()) { /* Skip unless both faces have all their UVs selected. */ - if (!uvedit_face_select_test(scene, f_dst, offsets) || - !uvedit_face_select_test(scene, f_src, offsets)) + if (!uvedit_face_select_test(scene, bm, f_dst, offsets) || + !uvedit_face_select_test(scene, bm, f_src, offsets)) { continue; } diff --git a/source/blender/editors/uvedit/uvedit_path.cc b/source/blender/editors/uvedit/uvedit_path.cc index c424b76631c..2c8328d4bb2 100644 --- a/source/blender/editors/uvedit/uvedit_path.cc +++ b/source/blender/editors/uvedit/uvedit_path.cc @@ -129,7 +129,7 @@ static bool verttag_test_cb(BMLoop *l, void *user_data_v) if (verttag_filter_cb(l_iter, user_data)) { const float *luv_iter = BM_ELEM_CD_GET_FLOAT_P(l_iter, cd_loop_uv_offset); if (equals_v2v2(luv, luv_iter)) { - if (!uvedit_uv_select_test(scene, l_iter, user_data->offsets)) { + if (!uvedit_uv_select_test(scene, user_data->bm, l_iter, user_data->offsets)) { return false; } } @@ -259,7 +259,7 @@ static bool edgetag_test_cb(BMLoop *l, void *user_data_v) BM_ITER_ELEM (l_iter, &iter, l->e, BM_LOOPS_OF_EDGE) { if (edgetag_filter_cb(l_iter, user_data)) { if (BM_loop_uv_share_edge_check(l, l_iter, user_data->offsets.uv)) { - if (!uvedit_edge_select_test(scene, l_iter, user_data->offsets)) { + if (!uvedit_edge_select_test(scene, user_data->bm, l_iter, user_data->offsets)) { return false; } } @@ -376,7 +376,7 @@ static bool facetag_test_cb(BMFace *f, void *user_data_v) BMIter iter; BMLoop *l_iter; BM_ITER_ELEM (l_iter, &iter, f, BM_LOOPS_OF_FACE) { - if (!uvedit_edge_select_test(scene, l_iter, user_data->offsets)) { + if (!uvedit_edge_select_test(scene, user_data->bm, l_iter, user_data->offsets)) { return false; } } @@ -611,17 +611,17 @@ static wmOperatorStatus uv_shortest_path_pick_invoke(bContext *C, else if (uv_selectmode & UV_SELECT_EDGE) { /* Edge selection. */ BMLoop *l_src = nullptr; - if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { + if ((ts->uv_flag & UV_FLAG_SELECT_SYNC) && (bm->uv_select_sync_valid == false)) { BMEdge *e_src = BM_mesh_active_edge_get(bm); if (e_src != nullptr) { l_src = uv_find_nearest_loop_from_edge(scene, obedit, e_src, co); } } else { - l_src = ED_uvedit_active_edge_loop_get(bm); + l_src = ED_uvedit_active_edge_loop_get(ts, bm); if (l_src != nullptr) { - if (!uvedit_uv_select_test(scene, l_src, offsets) && - !uvedit_uv_select_test(scene, l_src->next, offsets)) + if (!uvedit_uv_select_test(scene, bm, l_src, offsets) && + !uvedit_uv_select_test(scene, bm, l_src->next, offsets)) { l_src = nullptr; } @@ -634,16 +634,16 @@ static wmOperatorStatus uv_shortest_path_pick_invoke(bContext *C, else { /* Vertex selection. */ BMLoop *l_src = nullptr; - if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { + if ((ts->uv_flag & UV_FLAG_SELECT_SYNC) && (bm->uv_select_sync_valid == false)) { BMVert *v_src = BM_mesh_active_vert_get(bm); if (v_src != nullptr) { l_src = uv_find_nearest_loop_from_vert(scene, obedit, v_src, co); } } else { - l_src = ED_uvedit_active_vert_loop_get(bm); + l_src = ED_uvedit_active_vert_loop_get(ts, bm); if (l_src != nullptr) { - if (!uvedit_uv_select_test(scene, l_src, offsets)) { + if (!uvedit_uv_select_test(scene, bm, l_src, offsets)) { l_src = nullptr; } } @@ -689,6 +689,7 @@ static wmOperatorStatus uv_shortest_path_pick_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 char uv_selectmode = ED_uvedit_select_mode_get(scene); @@ -725,7 +726,7 @@ static wmOperatorStatus uv_shortest_path_pick_exec(bContext *C, wmOperator *op) if (index < 0 || index >= bm->totloop) { return OPERATOR_CANCELLED; } - if (!(ele_src = (BMElem *)ED_uvedit_active_edge_loop_get(bm)) || + if (!(ele_src = (BMElem *)ED_uvedit_active_edge_loop_get(ts, bm)) || !(ele_dst = (BMElem *)BM_loop_at_index_find(bm, index))) { return OPERATOR_CANCELLED; @@ -735,7 +736,7 @@ static wmOperatorStatus uv_shortest_path_pick_exec(bContext *C, wmOperator *op) if (index < 0 || index >= bm->totloop) { return OPERATOR_CANCELLED; } - if (!(ele_src = (BMElem *)ED_uvedit_active_vert_loop_get(bm)) || + if (!(ele_src = (BMElem *)ED_uvedit_active_vert_loop_get(ts, bm)) || !(ele_dst = (BMElem *)BM_loop_at_index_find(bm, index))) { return OPERATOR_CANCELLED; diff --git a/source/blender/editors/uvedit/uvedit_rip.cc b/source/blender/editors/uvedit/uvedit_rip.cc index 9ee87a1e6d7..66b29397136 100644 --- a/source/blender/editors/uvedit/uvedit_rip.cc +++ b/source/blender/editors/uvedit/uvedit_rip.cc @@ -31,6 +31,7 @@ #include "DEG_depsgraph.hh" +#include "ED_mesh.hh" #include "ED_screen.hh" #include "ED_transform.hh" #include "ED_uvedit.hh" @@ -738,12 +739,19 @@ static bool uv_rip_pairs_calc_center_and_direction(UVRipPairs *rip, */ static bool uv_rip_object(Scene *scene, Object *obedit, const float co[2], const float aspect_y) { - Mesh *mesh = (Mesh *)obedit->data; - BMEditMesh *em = mesh->runtime->edit_mesh.get(); + const ToolSettings *ts = scene->toolsettings; + + BMEditMesh *em = BKE_editmesh_from_object(obedit); BMesh *bm = em->bm; - const char *active_uv_name = CustomData_get_active_layer_name(&bm->ldata, CD_PROP_FLOAT2); - BM_uv_map_attr_vert_select_ensure(bm, active_uv_name); - BM_uv_map_attr_edge_select_ensure(bm, active_uv_name); + + if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { + uvedit_select_prepare_sync_select(scene, bm); + BLI_assert(bm->uv_select_sync_valid); + } + else { + uvedit_select_prepare_custom_data(scene, bm); + } + const BMUVOffsets offsets = BM_uv_map_offsets_get(bm); BMFace *efa; @@ -768,11 +776,11 @@ static bool uv_rip_object(Scene *scene, Object *obedit, const float co[2], const if (BM_elem_flag_test(efa, BM_ELEM_TAG)) { bool is_all = true; BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { - if (BM_ELEM_CD_GET_BOOL(l, offsets.select_vert)) { - if (BM_ELEM_CD_GET_BOOL(l, offsets.select_edge)) { + if (uvedit_loop_vert_select_get(ts, bm, l, offsets)) { + if (uvedit_loop_edge_select_get(ts, bm, l, offsets)) { UL(l)->is_select_edge = true; } - else if (!BM_ELEM_CD_GET_BOOL(l->prev, offsets.select_edge)) { + else if (!uvedit_loop_edge_select_get(ts, bm, l->prev, offsets)) { /* #bm_loop_uv_select_single_vert_validate validates below. */ UL(l)->is_select_vert_single = true; is_all = false; @@ -816,12 +824,12 @@ static bool uv_rip_object(Scene *scene, Object *obedit, const float co[2], const BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { if (!UL(l)->is_select_all) { - if (BM_ELEM_CD_GET_BOOL(l, offsets.select_vert)) { - BM_ELEM_CD_SET_BOOL(l, offsets.select_vert, false); + if (uvedit_loop_vert_select_get(ts, bm, l, offsets)) { + uvedit_loop_vert_select_set(ts, bm, l, false, offsets); changed = true; } - if (BM_ELEM_CD_GET_BOOL(l, offsets.select_edge)) { - BM_ELEM_CD_SET_BOOL(l, offsets.select_edge, false); + if (uvedit_loop_edge_select_get(ts, bm, l, offsets)) { + uvedit_loop_edge_select_set(ts, bm, l, false, offsets); changed = true; } } @@ -884,7 +892,12 @@ static bool uv_rip_object(Scene *scene, Object *obedit, const float co[2], const } } if (changed) { - uvedit_select_flush_from_verts(scene, bm, false); + if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { + BM_mesh_uvselect_flush_from_loop_verts(bm); + } + else { + uvedit_select_flush_from_verts(scene, bm, false); + } } return changed; } @@ -899,16 +912,27 @@ static wmOperatorStatus uv_rip_exec(bContext *C, wmOperator *op) { SpaceImage *sima = CTX_wm_space_image(C); Scene *scene = CTX_data_scene(C); + const ToolSettings *ts = scene->toolsettings; ViewLayer *view_layer = CTX_data_view_layer(C); - if (scene->toolsettings->uv_flag & UV_FLAG_SELECT_SYNC) { + if (ts->uv_sticky == UV_STICKY_VERT) { /* "Rip" is logically incompatible with sync-select. * Report an error instead of "poll" so this is reported when the tool is used, * with #131642 implemented, this can be made to work. */ - BKE_report(op->reports, RPT_ERROR, "Rip is not compatible with sync selection"); + BKE_report(op->reports, RPT_ERROR, "Rip is not compatible with vertex sticky selection"); return OPERATOR_CANCELLED; } + if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { + /* Important because in sync selection we *must* be able to de-select individual loops. */ + if (ED_uvedit_sync_uvselect_ignore(ts)) { + BKE_report(op->reports, + RPT_ERROR, + "Rip is only compatible with sync-select with vertex/edge selection"); + return OPERATOR_CANCELLED; + } + } + bool changed_multi = false; float co[2]; @@ -925,6 +949,12 @@ static wmOperatorStatus uv_rip_exec(bContext *C, wmOperator *op) Vector objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( scene, view_layer, nullptr); + if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { + /* While this is almost always true, any mis-match (from multiple scenes for example). + * Will not work properly. */ + EDBM_selectmode_set_multi_ex(scene, objects, ts->selectmode); + } + for (Object *obedit : objects) { if (uv_rip_object(scene, obedit, co, aspect_y)) { changed_multi = true; diff --git a/source/blender/editors/uvedit/uvedit_select.cc b/source/blender/editors/uvedit/uvedit_select.cc index fa1b71d67ed..e6cd441f74a 100644 --- a/source/blender/editors/uvedit/uvedit_select.cc +++ b/source/blender/editors/uvedit/uvedit_select.cc @@ -30,6 +30,7 @@ #include "BLI_polyfill_2d.h" #include "BLI_polyfill_2d_beautify.h" #include "BLI_utildefines.h" +#include "BLI_vector_list.hh" #include "BLT_translation.hh" @@ -67,8 +68,6 @@ using blender::int2; using blender::Span; using blender::Vector; -static void uv_select_all_perform(const Scene *scene, Object *obedit, int action); - static void uv_select_all_perform_multi_ex(const Scene *scene, Span objects, int action, @@ -112,9 +111,57 @@ void ED_uvedit_active_vert_loop_set(BMesh *bm, BMLoop *l) BM_select_history_store_notest(bm, (BMElem *)l->v); } -BMLoop *ED_uvedit_active_vert_loop_get(BMesh *bm) +BMLoop *ED_uvedit_active_vert_loop_get(const ToolSettings *ts, BMesh *bm) { BMEditSelection *ese = static_cast(bm->selected.last); + if ((ts->uv_flag & UV_FLAG_SELECT_SYNC) && bm->uv_select_sync_valid) { + if (ese && ese->htype == BM_VERT) { + BMVert *v = (BMVert *)ese->ele; + + BMLoop *l; + BMIter liter; + + /* Prioritize face, edge then vert selection. + * This may be overkill, even so, be deterministic and favor loops connected to selection. */ + BMLoop *l_select_vert = nullptr; + BMLoop *l_select_edge = nullptr; + BMLoop *l_select_edge_pair = nullptr; + BMLoop *l_select_face = nullptr; + + BM_ITER_ELEM (l, &liter, v, BM_LOOPS_OF_VERT) { + if (BM_elem_flag_test(l->f, BM_ELEM_HIDDEN)) { + continue; + } + if (BM_elem_flag_test(l, BM_ELEM_SELECT_UV)) { + const bool select_edge_prev = BM_loop_edge_uvselect_test(l->prev); + const bool select_edge_next = BM_loop_edge_uvselect_test(l); + const bool select_face = BM_elem_flag_test(l->f, BM_ELEM_SELECT_UV); + l_select_vert = l; + if (select_edge_prev || select_edge_next) { + l_select_edge_pair = l; + } + if (select_edge_prev && select_edge_next) { + l_select_edge_pair = l; + } + if (select_face) { + l_select_face = l; + } + } + } + if (l_select_face) { + return l_select_face; + } + if (l_select_edge_pair) { + return l_select_edge_pair; + } + if (l_select_edge) { + return l_select_edge; + } + return l_select_vert; + } + return nullptr; + } + if (ese && ese->prev) { BMEditSelection *ese_prev = ese->prev; if ((ese->htype == BM_VERT) && (ese_prev->htype == BM_FACE)) { @@ -134,9 +181,42 @@ void ED_uvedit_active_edge_loop_set(BMesh *bm, BMLoop *l) BM_select_history_store_notest(bm, (BMElem *)l->e); } -BMLoop *ED_uvedit_active_edge_loop_get(BMesh *bm) +BMLoop *ED_uvedit_active_edge_loop_get(const ToolSettings *ts, BMesh *bm) { BMEditSelection *ese = static_cast(bm->selected.last); + if ((ts->uv_flag & UV_FLAG_SELECT_SYNC) && bm->uv_select_sync_valid) { + if (ese && ese->htype == BM_EDGE) { + BMEdge *e = (BMEdge *)ese->ele; + + BMLoop *l; + BMIter liter; + + /* Prioritize face then edge selection. + * This may be overkill, even so, be deterministic and favor loops connected to selection. */ + BMLoop *l_select_vert = nullptr; + BMLoop *l_select_face = nullptr; + + BM_ITER_ELEM (l, &liter, e, BM_LOOPS_OF_EDGE) { + if (BM_elem_flag_test(l->f, BM_ELEM_HIDDEN)) { + continue; + } + if (BM_elem_flag_test(l, BM_ELEM_SELECT_UV_EDGE)) { + const bool select_face = BM_elem_flag_test(l->f, BM_ELEM_SELECT_UV); + l_select_vert = l; + if (select_face) { + l_select_face = l; + } + } + } + + if (l_select_face) { + return l_select_face; + } + return l_select_vert; + } + return nullptr; + } + if (ese && ese->prev) { BMEditSelection *ese_prev = ese->prev; if ((ese->htype == BM_EDGE) && (ese_prev->htype == BM_FACE)) { @@ -153,6 +233,66 @@ BMLoop *ED_uvedit_active_edge_loop_get(BMesh *bm) /** \name Visibility and Selection Utilities * \{ */ +bool ED_uvedit_sync_uvselect_ignore(const ToolSettings *ts) +{ + BLI_assert(ts->uv_flag & UV_FLAG_SELECT_SYNC); + if (ts->uv_sticky == UV_STICKY_VERT) { + /* In this case use the original mesh selection. */ + return true; + } + return false; +} + +bool ED_uvedit_sync_uvselect_is_valid_or_ignore(const ToolSettings *ts, const BMesh *bm) +{ + return bm->uv_select_sync_valid || ED_uvedit_sync_uvselect_ignore(ts); +} + +static void uvedit_sync_uvselect_flush_from_v3d(const ToolSettings *ts, BMesh *bm) +{ + BLI_assert(!bm->uv_select_sync_valid); + + /* Otherwise, ensure UV select is up to date. */ + switch (ts->uv_sticky) { + case UV_STICKY_LOCATION: { + const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_PROP_FLOAT2); + BM_mesh_uvselect_sync_from_mesh_sticky_location(bm, cd_loop_uv_offset); + break; + } + case UV_STICKY_DISABLE: { + BM_mesh_uvselect_sync_from_mesh_sticky_disabled(bm); + break; + } + case UV_STICKY_VERT: { + BM_mesh_uvselect_sync_from_mesh_sticky_vert(bm); + break; + } + } +} + +void ED_uvedit_sync_uvselect_ensure_if_needed(const ToolSettings *ts, BMesh *bm) +{ + /* Select sync wont be needed when mode switching. */ + if (ED_uvedit_sync_uvselect_ignore(ts)) { + bm->uv_select_sync_valid = false; + return; + } + + /* In most cases the caller will ensure this, + * check here to allow for this to be called outside of the UV editor. */ + if (!CustomData_has_layer(&bm->ldata, CD_PROP_FLOAT2)) { + bm->uv_select_sync_valid = false; + return; + } + + /* Select sync already calculated. */ + if (bm->uv_select_sync_valid) { + return; + } + + uvedit_sync_uvselect_flush_from_v3d(ts, bm); +} + char ED_uvedit_select_mode_get(const Scene *scene) { const ToolSettings *ts = scene->toolsettings; @@ -210,18 +350,28 @@ void ED_uvedit_select_sync_flush(const ToolSettings *ts, BMesh *bm, const bool s { /* bmesh API handles flushing but not on de-select */ if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { - if (ts->selectmode != SCE_SELECT_FACE) { - if (select == false) { - BM_mesh_select_flush_from_verts(bm, false); + if (bm->uv_select_sync_valid) { + BM_mesh_uvselect_mode_flush(bm); + if (ts->uv_sticky == UV_STICKY_LOCATION) { + const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_PROP_FLOAT2); + BM_mesh_uvselect_flush_shared_only_select(bm, cd_loop_uv_offset); } - else { - if (ts->selectmode & SCE_SELECT_VERTEX) { - BM_mesh_select_flush_from_verts(bm, true); + BM_mesh_uvselect_sync_to_mesh(bm); + } + else { + if (ts->selectmode != SCE_SELECT_FACE) { + if (select == false) { + BM_mesh_select_flush_from_verts(bm, false); } else { - /* Use instead of #BM_mesh_select_flush_from_verts so selecting edges doesn't - * flush vertex to face selection, see: #117320. */ - BM_mesh_select_mode_flush(bm); + if (ts->selectmode & SCE_SELECT_VERTEX) { + BM_mesh_select_flush_from_verts(bm, true); + } + else { + /* Use instead of #BM_mesh_select_flush so selecting edges doesn't + * flush vertex to face selection, see: #117320. */ + BM_mesh_select_mode_flush(bm); + } } } } @@ -263,13 +413,19 @@ bool uvedit_face_visible_test(const Scene *scene, const BMFace *efa) } bool uvedit_face_select_test_ex(const ToolSettings *ts, + const BMesh *bm, const BMFace *efa, const BMUVOffsets &offsets) { BLI_assert(offsets.select_vert >= 0); BLI_assert(offsets.select_edge >= 0); if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { - return BM_elem_flag_test(efa, BM_ELEM_SELECT); + if (bm->uv_select_sync_valid == false || ED_uvedit_sync_uvselect_ignore(ts)) { + return BM_elem_flag_test(efa, BM_ELEM_SELECT); + } + /* Caller checks for visibility. */ + BLI_assert(!BM_elem_flag_test(efa, BM_ELEM_HIDDEN)); + return BM_elem_flag_test(efa, BM_ELEM_SELECT_UV); } const int cd_offset = (ts->uv_selectmode & UV_SELECT_VERT) ? offsets.select_vert : @@ -283,9 +439,12 @@ bool uvedit_face_select_test_ex(const ToolSettings *ts, } while ((l_iter = l_iter->next) != l_first); return true; } -bool uvedit_face_select_test(const Scene *scene, const BMFace *efa, const BMUVOffsets &offsets) +bool uvedit_face_select_test(const Scene *scene, + const BMesh *bm, + const BMFace *efa, + const BMUVOffsets &offsets) { - return uvedit_face_select_test_ex(scene->toolsettings, efa, offsets); + return uvedit_face_select_test_ex(scene->toolsettings, bm, efa, offsets); } void uvedit_face_select_set_with_sticky( @@ -294,8 +453,11 @@ void uvedit_face_select_set_with_sticky( const ToolSettings *ts = scene->toolsettings; const char sticky = ts->uv_sticky; if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { - uvedit_face_select_set(scene, bm, efa, select, offsets); - return; + if (ED_uvedit_sync_uvselect_ignore(ts)) { + uvedit_face_select_set(scene, bm, efa, select, offsets); + return; + } + BLI_assert(ED_uvedit_sync_uvselect_is_valid_or_ignore(ts, bm)); } if (!uvedit_face_visible_test(scene, efa)) { return; @@ -314,20 +476,68 @@ void uvedit_face_select_set_with_sticky( } } -void uvedit_face_select_shared_vert( - const Scene *scene, BMesh *bm, BMFace *efa, const bool select, const BMUVOffsets &offsets) +void uvedit_face_select_shared_vert(const Scene *scene, + BMesh *bm, + BMFace *efa, + const bool select, + + const BMUVOffsets &offsets) { + const ToolSettings *ts = scene->toolsettings; BMLoop *l; BMIter liter; + if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { + if (ts->uv_sticky == UV_STICKY_VERT) { + BM_face_uvselect_set_noflush(bm, efa, select); + return; + } + BLI_assert(ED_uvedit_sync_uvselect_is_valid_or_ignore(ts, bm)); + + /* NOTE: the logic is different enough to split out, + * mainly because it's possible to de-select a face but have all it's edges selected. + * + * NOTE: An alternative to this function would be to simply set the face selection + * and flush the entire mesh afterwards, mentioning this because the checks here are + * fairly involved. */ + + if (ts->uv_sticky == UV_STICKY_DISABLE) { + BM_face_uvselect_set_noflush(bm, efa, select); + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + BM_loop_vert_uvselect_set_noflush(bm, l, select); + BM_loop_edge_uvselect_set_noflush(bm, l, select); + } + } + else if (ts->uv_sticky == UV_STICKY_LOCATION) { + BM_face_uvselect_set_noflush(bm, efa, select); + if (select) { + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + BM_loop_vert_uvselect_set_shared(bm, l, true, offsets.uv); + BM_loop_edge_uvselect_set_shared(bm, l, true, offsets.uv); + } + } + else { + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + if (!BM_loop_vert_uvselect_check_other_face(l, BM_ELEM_SELECT_UV, offsets.uv)) { + BM_loop_vert_uvselect_set_shared(bm, l, false, offsets.uv); + } + if (!BM_loop_edge_uvselect_check_other_face(l, BM_ELEM_SELECT_UV, offsets.uv)) { + BM_loop_edge_uvselect_set_shared(bm, l, false, offsets.uv); + } + } + } + } + return; + } + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + BM_ELEM_CD_SET_BOOL(l, offsets.select_edge, select); + if (select) { - BM_ELEM_CD_SET_BOOL(l, offsets.select_edge, true); uvedit_uv_select_shared_vert(scene, bm, l, select, UV_STICKY_LOCATION, offsets); } else { - BM_ELEM_CD_SET_BOOL(l, offsets.select_edge, false); - if (!uvedit_vert_is_face_select_any_other(scene->toolsettings, l, offsets)) { + if (!uvedit_vert_is_face_select_any_other(ts, bm, l, offsets)) { uvedit_uv_select_shared_vert(scene, bm, l, select, UV_STICKY_LOCATION, offsets); } } @@ -355,7 +565,12 @@ void uvedit_face_select_enable(const Scene *scene, const ToolSettings *ts = scene->toolsettings; if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { - BM_face_select_set(bm, efa, true); + if (ED_uvedit_sync_uvselect_ignore(ts)) { + BM_face_select_set(bm, efa, true); + } + else { + BM_face_uvselect_set(bm, efa, true); + } } else { BMLoop *l; @@ -378,7 +593,12 @@ void uvedit_face_select_disable(const Scene *scene, const ToolSettings *ts = scene->toolsettings; if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { - BM_face_select_set(bm, efa, false); + if (ED_uvedit_sync_uvselect_ignore(ts)) { + BM_face_select_set(bm, efa, false); + } + else { + BM_face_uvselect_set(bm, efa, false); + } } else { BMLoop *l; @@ -392,20 +612,21 @@ void uvedit_face_select_disable(const Scene *scene, } bool uvedit_edge_select_test_ex(const ToolSettings *ts, + const BMesh *bm, const BMLoop *l, const BMUVOffsets &offsets) { BLI_assert(offsets.select_vert >= 0); BLI_assert(offsets.select_edge >= 0); if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { - if (ts->selectmode == SCE_SELECT_FACE) { + if ((bm->uv_select_sync_valid == false) && (ts->selectmode == SCE_SELECT_FACE)) { /* Face only is a special case that can respect sticky modes. */ switch (ts->uv_sticky) { case UV_STICKY_LOCATION: { if (BM_elem_flag_test(l->f, BM_ELEM_SELECT)) { return true; } - if (uvedit_edge_is_face_select_any_other(ts, l, offsets)) { + if (uvedit_edge_is_face_select_any_other(ts, bm, l, offsets)) { return true; } return false; @@ -421,14 +642,18 @@ bool uvedit_edge_select_test_ex(const ToolSettings *ts, BLI_assert_unreachable(); } - if ((ts->selectmode & ~SCE_SELECT_VERTEX) == SCE_SELECT_FACE) { - return BM_elem_flag_test(l->f, BM_ELEM_SELECT); + if (bm->uv_select_sync_valid == false || ED_uvedit_sync_uvselect_ignore(ts)) { + if (ts->selectmode & SCE_SELECT_FACE) { + return BM_elem_flag_test(l->f, BM_ELEM_SELECT); + } + if ((ts->selectmode & ~SCE_SELECT_FACE) == 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); } - if ((ts->selectmode & ~SCE_SELECT_FACE) == 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); + + return BM_elem_flag_test(l, BM_ELEM_SELECT_UV_EDGE); } if (ts->uv_selectmode & UV_SELECT_VERT) { @@ -438,18 +663,28 @@ bool uvedit_edge_select_test_ex(const ToolSettings *ts, return BM_ELEM_CD_GET_BOOL(l, offsets.select_edge); } -bool uvedit_edge_select_test(const Scene *scene, const BMLoop *l, const BMUVOffsets &offsets) +bool uvedit_edge_select_test(const Scene *scene, + const BMesh *bm, + const BMLoop *l, + const BMUVOffsets &offsets) { - return uvedit_edge_select_test_ex(scene->toolsettings, l, offsets); + return uvedit_edge_select_test_ex(scene->toolsettings, bm, l, offsets); } -void uvedit_edge_select_set_with_sticky( - const Scene *scene, BMesh *bm, BMLoop *l, const bool select, const BMUVOffsets &offsets) +void uvedit_edge_select_set_with_sticky(const Scene *scene, + BMesh *bm, + BMLoop *l, + const bool select, + + const BMUVOffsets &offsets) { const ToolSettings *ts = scene->toolsettings; if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { - uvedit_edge_select_set(scene, bm, l, select, offsets); - return; + if (ED_uvedit_sync_uvselect_ignore(ts)) { + uvedit_edge_select_set(scene, bm, l, select, offsets); + return; + } + BLI_assert(ED_uvedit_sync_uvselect_is_valid_or_ignore(ts, bm)); } const int sticky = ts->uv_sticky; @@ -472,6 +707,42 @@ void uvedit_edge_select_set_with_sticky( } } +static bool UNUSED_FUNCTION(bm_loop_select_vert_check_internal)(const Scene *scene, + BMesh *bm, + BMLoop *l, + const BMUVOffsets &offsets) +{ + const ToolSettings *ts = scene->toolsettings; + if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { + if (bm->uv_select_sync_valid == false || ED_uvedit_sync_uvselect_ignore(ts)) { + /* Use mesh selection. */ + return BM_elem_flag_test_bool(l->v, BM_ELEM_SELECT); + } + /* Caller checks for visibility. */ + BLI_assert(!BM_elem_flag_test(l->f, BM_ELEM_HIDDEN)); + return BM_elem_flag_test_bool(l, BM_ELEM_SELECT_UV); + } + return BM_ELEM_CD_GET_BOOL(l, offsets.select_vert); +} + +static bool bm_loop_select_edge_check_internal(const Scene *scene, + BMesh *bm, + BMLoop *l, + const BMUVOffsets &offsets) +{ + const ToolSettings *ts = scene->toolsettings; + if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { + if (bm->uv_select_sync_valid == false || ED_uvedit_sync_uvselect_ignore(ts)) { + /* Use mesh selection. */ + return BM_elem_flag_test_bool(l->e, BM_ELEM_SELECT); + } + /* Caller checks for visibility. */ + BLI_assert(!BM_elem_flag_test(l->f, BM_ELEM_HIDDEN)); + return BM_elem_flag_test_bool(l, BM_ELEM_SELECT_UV_EDGE); + } + return BM_ELEM_CD_GET_BOOL(l, offsets.select_edge); +} + void uvedit_edge_select_shared_vert(const Scene *scene, BMesh *bm, BMLoop *l, @@ -481,52 +752,64 @@ void uvedit_edge_select_shared_vert(const Scene *scene, { BLI_assert(ELEM(sticky_flag, UV_STICKY_LOCATION, UV_STICKY_VERT)); /* Set edge flags. Rely on this for face visibility checks */ - uvedit_edge_select_set_noflush(scene, l, select, sticky_flag, offsets); + uvedit_edge_select_set_noflush(scene, bm, l, select, sticky_flag, offsets); /* Vert selections. */ BMLoop *l_iter = l; do { if (select) { - if (BM_ELEM_CD_GET_BOOL(l_iter, offsets.select_edge)) { + if (bm_loop_select_edge_check_internal(scene, bm, l_iter, offsets)) { uvedit_uv_select_shared_vert(scene, bm, l_iter, true, UV_STICKY_LOCATION, offsets); uvedit_uv_select_shared_vert(scene, bm, l_iter->next, true, UV_STICKY_LOCATION, offsets); } } else { - if (!BM_ELEM_CD_GET_BOOL(l_iter, offsets.select_edge)) { - if (!uvedit_vert_is_edge_select_any_other(scene->toolsettings, l, offsets)) { + if (!bm_loop_select_edge_check_internal(scene, bm, l_iter, offsets)) { + if (!uvedit_vert_is_edge_select_any_other(scene->toolsettings, bm, l, offsets)) { uvedit_uv_select_shared_vert(scene, bm, l_iter, false, UV_STICKY_LOCATION, offsets); } - if (!uvedit_vert_is_edge_select_any_other(scene->toolsettings, l->next, offsets)) { + if (!uvedit_vert_is_edge_select_any_other(scene->toolsettings, bm, l->next, offsets)) { uvedit_uv_select_shared_vert( scene, bm, l_iter->next, false, UV_STICKY_LOCATION, offsets); } } } - } while (((l_iter = l_iter->radial_next) != l) && (sticky_flag != UV_STICKY_LOCATION)); } void uvedit_edge_select_set_noflush(const Scene *scene, + BMesh *bm, BMLoop *l, const bool select, const int sticky_flag, const BMUVOffsets &offsets) { - BLI_assert(offsets.uv >= 0); - BLI_assert(offsets.select_edge >= 0); + const ToolSettings *ts = scene->toolsettings; + if ((ts->uv_flag & UV_FLAG_SELECT_SYNC) == 0) { + BLI_assert(offsets.uv >= 0); + BLI_assert(offsets.select_edge >= 0); + } BMLoop *l_iter = l; do { if (uvedit_face_visible_test(scene, l_iter->f)) { if ((sticky_flag == UV_STICKY_VERT) || BM_loop_uv_share_edge_check(l, l_iter, offsets.uv)) { - BM_ELEM_CD_SET_BOOL(l_iter, offsets.select_edge, select); + if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { + BM_loop_edge_uvselect_set_noflush(bm, l_iter, select); + } + else { + BM_ELEM_CD_SET_BOOL(l_iter, offsets.select_edge, select); + } } } } while (((l_iter = l_iter->radial_next) != l) && (sticky_flag != UV_STICKY_DISABLE)); } -void uvedit_edge_select_set( - const Scene *scene, BMesh *bm, BMLoop *l, const bool select, const BMUVOffsets &offsets) +void uvedit_edge_select_set(const Scene *scene, + BMesh *bm, + BMLoop *l, + const bool select, + + const BMUVOffsets &offsets) { if (select) { uvedit_edge_select_enable(scene, bm, l, offsets); @@ -547,15 +830,20 @@ void uvedit_edge_select_enable(const Scene *scene, BLI_assert(offsets.select_edge >= 0); if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { - if (ts->selectmode & SCE_SELECT_FACE) { - BM_face_select_set(bm, l->f, true); - } - else if (ts->selectmode & SCE_SELECT_EDGE) { - BM_edge_select_set(bm, l->e, true); + if (ED_uvedit_sync_uvselect_ignore(ts)) { + if (ts->selectmode & SCE_SELECT_FACE) { + BM_face_select_set(bm, l->f, true); + } + else if (ts->selectmode & SCE_SELECT_EDGE) { + BM_edge_select_set(bm, l->e, true); + } + else { + BM_vert_select_set(bm, l->e->v1, true); + BM_vert_select_set(bm, l->e->v2, true); + } } else { - BM_vert_select_set(bm, l->e->v1, true); - BM_vert_select_set(bm, l->e->v2, true); + BM_loop_edge_uvselect_set(bm, l, true); } } else { @@ -571,22 +859,41 @@ void uvedit_edge_select_disable(const Scene *scene, const BMUVOffsets &offsets) { const ToolSettings *ts = scene->toolsettings; - BLI_assert(offsets.select_vert >= 0); - BLI_assert(offsets.select_edge >= 0); if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { - if (ts->selectmode & SCE_SELECT_FACE) { - BM_face_select_set(bm, l->f, false); - } - else if (ts->selectmode & SCE_SELECT_EDGE) { - BM_edge_select_set(bm, l->e, false); + if (ED_uvedit_sync_uvselect_ignore(ts)) { + if (ts->selectmode & SCE_SELECT_FACE) { + BM_face_select_set(bm, l->f, false); + } + else if (ts->selectmode & SCE_SELECT_EDGE) { + BM_edge_select_set(bm, l->e, false); + } + else { + BM_vert_select_set(bm, l->e->v1, false); + BM_vert_select_set(bm, l->e->v2, false); + } } else { - BM_vert_select_set(bm, l->e->v1, false); - BM_vert_select_set(bm, l->e->v2, false); + BM_loop_edge_uvselect_set_noflush(bm, l, false); + if ((ts->selectmode & SCE_SELECT_VERTEX) == 0) { + /* Deselect UV vertex if not part of another edge selection */ + if (!BM_elem_flag_test(l->prev, BM_ELEM_SELECT_UV_EDGE)) { + BM_loop_vert_uvselect_set_noflush(bm, l, false); + } + if (!BM_elem_flag_test(l->next, BM_ELEM_SELECT_UV_EDGE)) { + BM_loop_vert_uvselect_set_noflush(bm, l->next, false); + } + } + else { + BM_loop_vert_uvselect_set_noflush(bm, l, false); + BM_loop_vert_uvselect_set_noflush(bm, l->next, false); + } } } else { + BLI_assert(offsets.select_vert >= 0); + BLI_assert(offsets.select_edge >= 0); + BM_ELEM_CD_SET_BOOL(l, offsets.select_edge, false); if ((ts->uv_selectmode & UV_SELECT_VERT) == 0) { /* Deselect UV vertex if not part of another edge selection */ @@ -604,19 +911,22 @@ void uvedit_edge_select_disable(const Scene *scene, } } -bool uvedit_uv_select_test_ex(const ToolSettings *ts, const BMLoop *l, const BMUVOffsets &offsets) +bool uvedit_uv_select_test_ex(const ToolSettings *ts, + const BMesh *bm, + const BMLoop *l, + const BMUVOffsets &offsets) { BLI_assert(offsets.select_vert >= 0); if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { - if (ts->selectmode == SCE_SELECT_FACE) { + if ((bm->uv_select_sync_valid == false) && (ts->selectmode == SCE_SELECT_FACE)) { /* Face only is a special case that can respect sticky modes. */ switch (ts->uv_sticky) { case UV_STICKY_LOCATION: { if (BM_elem_flag_test(l->f, BM_ELEM_SELECT)) { return true; } - if (uvedit_vert_is_face_select_any_other(ts, l, offsets)) { + if (uvedit_vert_is_face_select_any_other(ts, bm, l, offsets)) { return true; } return false; @@ -632,14 +942,17 @@ bool uvedit_uv_select_test_ex(const ToolSettings *ts, const BMLoop *l, const BMU BLI_assert_unreachable(); } - if ((ts->selectmode & ~SCE_SELECT_FACE) == SCE_SELECT_EDGE) { + if (bm->uv_select_sync_valid) { + /* Pass. */ + } + else if ((ts->selectmode & ~SCE_SELECT_FACE) == SCE_SELECT_EDGE) { /* Edge/Face is a special case that can respect sticky modes. */ switch (ts->uv_sticky) { case UV_STICKY_LOCATION: { if (BM_elem_flag_test(l->f, BM_ELEM_SELECT)) { return true; } - if (uvedit_vert_is_edge_select_any_other(ts, l, offsets)) { + if (uvedit_vert_is_edge_select_any_other(ts, bm, l, offsets)) { return true; } return false; @@ -656,13 +969,16 @@ bool uvedit_uv_select_test_ex(const ToolSettings *ts, const BMLoop *l, const BMU BLI_assert_unreachable(); } - if (ts->selectmode & SCE_SELECT_FACE) { - return BM_elem_flag_test_bool(l->f, BM_ELEM_SELECT); + if (bm->uv_select_sync_valid == false || ED_uvedit_sync_uvselect_ignore(ts)) { + 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); } - 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); + return BM_elem_flag_test_bool(l, BM_ELEM_SELECT_UV); } if (ts->selectmode & SCE_SELECT_FACE) { @@ -676,9 +992,12 @@ bool uvedit_uv_select_test_ex(const ToolSettings *ts, const BMLoop *l, const BMU return BM_ELEM_CD_GET_BOOL(l, offsets.select_vert); } -bool uvedit_uv_select_test(const Scene *scene, const BMLoop *l, const BMUVOffsets &offsets) +bool uvedit_uv_select_test(const Scene *scene, + const BMesh *bm, + const BMLoop *l, + const BMUVOffsets &offsets) { - return uvedit_uv_select_test_ex(scene->toolsettings, l, offsets); + return uvedit_uv_select_test_ex(scene->toolsettings, bm, l, offsets); } void uvedit_uv_select_set_with_sticky( @@ -686,8 +1005,10 @@ void uvedit_uv_select_set_with_sticky( { const ToolSettings *ts = scene->toolsettings; if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { - uvedit_uv_select_set(scene, bm, l, select, offsets); - return; + if (ED_uvedit_sync_uvselect_ignore(ts)) { + uvedit_uv_select_set(scene, bm, l, select, offsets); + return; + } } const int sticky = ts->uv_sticky; @@ -715,6 +1036,7 @@ void uvedit_uv_select_shared_vert(const Scene *scene, BMLoop *l, const bool select, const int sticky_flag, + const BMUVOffsets &offsets) { BLI_assert(ELEM(sticky_flag, UV_STICKY_LOCATION, UV_STICKY_VERT)); @@ -747,8 +1069,12 @@ void uvedit_uv_select_shared_vert(const Scene *scene, } while ((e_iter = BM_DISK_EDGE_NEXT(e_iter, l->v)) != e_first); } -void uvedit_uv_select_set( - const Scene *scene, BMesh *bm, BMLoop *l, const bool select, const BMUVOffsets &offsets) +void uvedit_uv_select_set(const Scene *scene, + BMesh *bm, + BMLoop *l, + const bool select, + + const BMUVOffsets &offsets) { if (select) { uvedit_uv_select_enable(scene, bm, l, offsets); @@ -761,21 +1087,26 @@ void uvedit_uv_select_set( void uvedit_uv_select_enable(const Scene *scene, BMesh *bm, BMLoop *l, const BMUVOffsets &offsets) { const ToolSettings *ts = scene->toolsettings; - BLI_assert(offsets.select_vert >= 0); if (ts->selectmode & SCE_SELECT_EDGE) { /* Are you looking for `uvedit_edge_select_set(...)` instead? */ } if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { - if (ts->selectmode & SCE_SELECT_FACE) { - BM_face_select_set(bm, l->f, true); + if (ED_uvedit_sync_uvselect_ignore(ts)) { + if (ts->selectmode & SCE_SELECT_FACE) { + BM_face_select_set(bm, l->f, true); + } + else { + BM_vert_select_set(bm, l->v, true); + } } else { - BM_vert_select_set(bm, l->v, true); + BM_loop_vert_uvselect_set_noflush(bm, l, true); } } else { + BLI_assert(offsets.select_vert >= 0); BM_ELEM_CD_SET_BOOL(l, offsets.select_vert, true); } } @@ -783,17 +1114,22 @@ void uvedit_uv_select_enable(const Scene *scene, BMesh *bm, BMLoop *l, const BMU void uvedit_uv_select_disable(const Scene *scene, BMesh *bm, BMLoop *l, const BMUVOffsets &offsets) { const ToolSettings *ts = scene->toolsettings; - BLI_assert(offsets.select_vert >= 0); if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { - if (ts->selectmode & SCE_SELECT_FACE) { - BM_face_select_set(bm, l->f, false); + if (ED_uvedit_sync_uvselect_ignore(ts)) { + if (ts->selectmode & SCE_SELECT_FACE) { + BM_face_select_set(bm, l->f, false); + } + else { + BM_vert_select_set(bm, l->v, false); + } } else { - BM_vert_select_set(bm, l->v, false); + BM_loop_vert_uvselect_set_noflush(bm, l, false); } } else { + BLI_assert(offsets.select_vert >= 0); BM_ELEM_CD_SET_BOOL(l, offsets.select_vert, false); } } @@ -855,6 +1191,70 @@ static BMLoop *uvedit_loop_find_other_boundary_loop_with_visible_face(const Scen /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Low Level Selection API + * \{ */ + +bool uvedit_loop_vert_select_get(const ToolSettings *ts, + const BMesh *bm, + const BMLoop *l, + const BMUVOffsets &offsets) +{ + if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { + BLI_assert(bm->uv_select_sync_valid); + UNUSED_VARS_NDEBUG(bm); + return BM_elem_flag_test_bool(l, BM_ELEM_SELECT_UV); + } + return BM_ELEM_CD_GET_BOOL(l, offsets.select_vert); +} + +bool uvedit_loop_edge_select_get(const ToolSettings *ts, + const BMesh *bm, + const BMLoop *l, + const BMUVOffsets &offsets) +{ + if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { + BLI_assert(bm->uv_select_sync_valid); + UNUSED_VARS_NDEBUG(bm); + /* Caller checks for visibility. */ + BLI_assert(!BM_elem_flag_test(l->f, BM_ELEM_HIDDEN)); + return BM_elem_flag_test_bool(l, BM_ELEM_SELECT_UV_EDGE); + } + return BM_ELEM_CD_GET_BOOL(l, offsets.select_edge); +} + +void uvedit_loop_vert_select_set(const ToolSettings *ts, + const BMesh *bm, + BMLoop *l, + const bool select, + const BMUVOffsets &offsets) +{ + if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { + BLI_assert(bm->uv_select_sync_valid); + UNUSED_VARS_NDEBUG(bm); + BM_elem_flag_set(l, BM_ELEM_SELECT_UV, select); + return; + } + BM_ELEM_CD_SET_BOOL(l, offsets.select_vert, select); +} + +void uvedit_loop_edge_select_set(const ToolSettings *ts, + const BMesh *bm, + BMLoop *l, + const bool select, + const BMUVOffsets &offsets) +{ + if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { + BLI_assert(bm->uv_select_sync_valid); + UNUSED_VARS_NDEBUG(bm); + BM_elem_flag_set(l, BM_ELEM_SELECT_UV_EDGE, select); + return; + } + BM_ELEM_CD_SET_BOOL(l, offsets.select_edge, select); +} + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name Find Nearest Elements * \{ */ @@ -921,7 +1321,7 @@ bool uv_find_nearest_edge( /* 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, offsets)) { + if ((penalty != 0.0f) && uvedit_edge_select_test(scene, bm, l, offsets)) { dist_test_sq = square_f(sqrtf(dist_test_sq) + penalty); } if (dist_test_sq < hit->dist_sq) { @@ -1069,7 +1469,7 @@ bool uv_find_nearest_vert( /* 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, offsets)) { + if ((penalty_dist != 0.0f) && uvedit_uv_select_test(scene, bm, l, offsets)) { dist_test_sq = square_f(sqrtf(dist_test_sq) + penalty_dist); } @@ -1130,7 +1530,7 @@ static bool uvedit_nearest_uv(const Scene *scene, 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, offsets)) { + if (ignore_selected && uvedit_uv_select_test(scene, bm, l_iter, offsets)) { continue; } @@ -1254,7 +1654,25 @@ void uvedit_select_prepare_custom_data(const Scene *scene, BMesh *bm) BM_uv_map_attr_edge_select_ensure(bm, active_uv_name); } +void uvedit_select_prepare_sync_select(const Scene *scene, BMesh *bm) +{ + ED_uvedit_sync_uvselect_ensure_if_needed(scene->toolsettings, bm); +} + +/* We may want to use this eventually. */ +void uvedit_select_prepare_UNUSED(const Scene *scene, BMesh *bm) +{ + const ToolSettings *ts = scene->toolsettings; + if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { + uvedit_select_prepare_sync_select(scene, bm); + } + else { + uvedit_select_prepare_custom_data(scene, bm); + } +} + bool uvedit_vert_is_edge_select_any_other(const ToolSettings *ts, + const BMesh *bm, const BMLoop *l, const BMUVOffsets &offsets) { @@ -1273,7 +1691,7 @@ bool uvedit_vert_is_edge_select_any_other(const ToolSettings *ts, * and #l_radial_iter for the actual edge selection test. */ BMLoop *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, offsets.uv) && - uvedit_edge_select_test_ex(ts, l_radial_iter, offsets)) + uvedit_edge_select_test_ex(ts, bm, l_radial_iter, offsets)) { return true; } @@ -1284,6 +1702,7 @@ bool uvedit_vert_is_edge_select_any_other(const ToolSettings *ts, } bool uvedit_edge_is_face_select_any_other(const ToolSettings *ts, + const BMesh *bm, const BMLoop *l, const BMUVOffsets &offsets) { @@ -1297,7 +1716,7 @@ bool uvedit_edge_is_face_select_any_other(const ToolSettings *ts, continue; } if (BM_loop_uv_share_edge_check(l, l_radial_iter, offsets.uv) && - uvedit_face_select_test_ex(ts, l_radial_iter->f, offsets)) + uvedit_face_select_test_ex(ts, bm, l_radial_iter->f, offsets)) { return true; } @@ -1307,6 +1726,7 @@ bool uvedit_edge_is_face_select_any_other(const ToolSettings *ts, } bool uvedit_vert_is_face_select_any_other(const ToolSettings *ts, + const BMesh *bm, const BMLoop *l, const BMUVOffsets &offsets) { @@ -1314,11 +1734,11 @@ bool uvedit_vert_is_face_select_any_other(const ToolSettings *ts, BMIter liter; BMLoop *l_iter; BM_ITER_ELEM (l_iter, &liter, l->v, BM_LOOPS_OF_VERT) { - if ((l_iter->f == l->f) || !uvedit_face_visible_test_ex(ts, l_iter->f)) { + if (!uvedit_face_visible_test_ex(ts, l_iter->f) || (l_iter->f == l->f)) { continue; } if (BM_loop_uv_share_vert_check(l, l_iter, offsets.uv) && - uvedit_face_select_test_ex(ts, l_iter->f, offsets)) + uvedit_face_select_test_ex(ts, bm, l_iter->f, offsets)) { return true; } @@ -1327,6 +1747,7 @@ bool uvedit_vert_is_face_select_any_other(const ToolSettings *ts, } bool uvedit_vert_is_all_other_faces_selected(const ToolSettings *ts, + const BMesh *bm, const BMLoop *l, const BMUVOffsets &offsets) { @@ -1338,7 +1759,7 @@ bool uvedit_vert_is_all_other_faces_selected(const ToolSettings *ts, continue; } if (BM_loop_uv_share_vert_check(l, l_iter, offsets.uv) && - !uvedit_face_select_test_ex(ts, l_iter->f, offsets)) + !uvedit_face_select_test_ex(ts, bm, l_iter->f, offsets)) { return false; } @@ -1366,6 +1787,116 @@ static void bm_clear_uv_vert_selection(const Scene *scene, BMesh *bm, const BMUV /** \} */ +/* -------------------------------------------------------------------- */ +/** \name UV Select Abstraction API + * + * This exists to support selecting UVs from the 3D viewport, + * to abstract away details regarding which selections modes are enabled. + * \{ */ + +namespace blender::ed::uv { + +std::unique_ptr UVSyncSelectFromMesh::create_if_needed( + const ToolSettings &ts, BMesh &bm) +{ + if ((ts.uv_flag & UV_FLAG_SELECT_SYNC) == 0) { + return nullptr; + } + if (ED_uvedit_sync_uvselect_ignore(&ts)) { + return nullptr; + } + if (bm.uv_select_sync_valid == false) { + return nullptr; + } + const int cd_loop_uv_offset = CustomData_get_active_layer(&bm.ldata, CD_PROP_FLOAT2); + if (cd_loop_uv_offset == -1) { + return nullptr; + } + return std::make_unique(bm, ts.uv_sticky); +} + +void UVSyncSelectFromMesh::apply() +{ + const int cd_loop_uv_offset = CustomData_get_active_layer(&bm_.ldata, CD_PROP_FLOAT2); + BLI_assert(cd_loop_uv_offset != -1); + + const bool shared = uv_sticky_ == UV_STICKY_LOCATION; + const BMUVSelectPickParams uv_pick_params = { + /*cd_loop_uv_offset*/ cd_loop_uv_offset, + /*shared*/ shared, + }; + + BM_mesh_uvselect_set_elem_from_mesh( + &bm_, false, uv_pick_params, bm_verts_deselect_, bm_edges_deselect_, bm_faces_deselect_); + + BM_mesh_uvselect_set_elem_from_mesh( + &bm_, true, uv_pick_params, bm_verts_select_, bm_edges_select_, bm_faces_select_); +} + +/* Select. */ + +void UVSyncSelectFromMesh::vert_select_enable(BMVert *v) +{ + bm_verts_select_.append(v); +} +void UVSyncSelectFromMesh::edge_select_enable(BMEdge *f) +{ + bm_edges_select_.append(f); +} +void UVSyncSelectFromMesh::face_select_enable(BMFace *f) +{ + bm_faces_select_.append(f); +} + +/* De-Select. */ + +void UVSyncSelectFromMesh::vert_select_disable(BMVert *v) +{ + bm_verts_deselect_.append(v); +} +void UVSyncSelectFromMesh::edge_select_disable(BMEdge *f) +{ + bm_edges_deselect_.append(f); +} +void UVSyncSelectFromMesh::face_select_disable(BMFace *f) +{ + bm_faces_deselect_.append(f); +} + +/* Select set. */ + +void UVSyncSelectFromMesh::vert_select_set(BMVert *v, bool value) +{ + if (value) { + bm_verts_select_.append(v); + } + else { + bm_verts_deselect_.append(v); + } +} +void UVSyncSelectFromMesh::edge_select_set(BMEdge *f, bool value) +{ + if (value) { + bm_edges_select_.append(f); + } + else { + bm_edges_deselect_.append(f); + } +} +void UVSyncSelectFromMesh::face_select_set(BMFace *f, bool value) +{ + if (value) { + bm_faces_select_.append(f); + } + else { + bm_faces_deselect_.append(f); + } +} + +} // namespace blender::ed::uv + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name UV Select-Mode Flushing * @@ -1588,7 +2119,7 @@ static void uv_select_edgeloop_single_side_tag(const Scene *scene, } if (r_count_by_select != nullptr) { - r_count_by_select[uvedit_edge_select_test(scene, l_step, offsets)] += 1; + r_count_by_select[uvedit_edge_select_test(scene, bm, l_step, offsets)] += 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; @@ -1632,7 +2163,7 @@ static int uv_select_edgeloop(Scene *scene, Object *obedit, UvNearestHit *hit, c const BMUVOffsets offsets = BM_uv_map_offsets_get(bm); if (extend) { - select = !uvedit_edge_select_test(scene, hit->l, offsets); + select = !uvedit_edge_select_test(scene, bm, hit->l, offsets); } else { select = true; @@ -1674,7 +2205,11 @@ static int uv_select_edgeloop(Scene *scene, Object *obedit, UvNearestHit *hit, c /* Apply the selection. */ if (!extend) { - uv_select_all_perform(scene, obedit, SEL_DESELECT); + ED_uvedit_deselect_all(scene, obedit, SEL_DESELECT); + } + + if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { + uvedit_select_prepare_sync_select(scene, bm); } /* Select all tagged loops. */ @@ -1686,12 +2221,17 @@ static int uv_select_edgeloop(Scene *scene, Object *obedit, UvNearestHit *hit, c 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_VERT) { - uvedit_uv_select_set_with_sticky(scene, bm, l_iter, select, offsets); - uvedit_uv_select_set_with_sticky(scene, bm, l_iter->next, select, offsets); + if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { + uvedit_edge_select_set_with_sticky(scene, bm, l_iter, select, offsets); } else { - uvedit_edge_select_set_with_sticky(scene, bm, l_iter, select, offsets); + if (ts->uv_selectmode == UV_SELECT_VERT) { + uvedit_uv_select_set_with_sticky(scene, bm, l_iter, select, offsets); + uvedit_uv_select_set_with_sticky(scene, bm, l_iter->next, select, offsets); + } + else { + uvedit_edge_select_set_with_sticky(scene, bm, l_iter, select, offsets); + } } } } @@ -1713,22 +2253,22 @@ static int uv_select_faceloop(Scene *scene, Object *obedit, UvNearestHit *hit, c BMesh *bm = BKE_editmesh_from_object(obedit)->bm; bool select; + if (!extend) { + ED_uvedit_deselect_all(scene, obedit, SEL_DESELECT); + } + if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { - /* Pass. */ + uvedit_select_prepare_sync_select(scene, bm); } else { uvedit_select_prepare_custom_data(scene, bm); } const BMUVOffsets offsets = BM_uv_map_offsets_get(bm); - if (!extend) { - uv_select_all_perform(scene, obedit, SEL_DESELECT); - } - BM_mesh_elem_hflag_disable_all(bm, BM_FACE, BM_ELEM_TAG, false); if (extend) { - select = !uvedit_face_select_test(scene, hit->l->f, offsets); + select = !uvedit_face_select_test(scene, bm, hit->l->f, offsets); } else { select = true; @@ -1788,22 +2328,22 @@ static int uv_select_edgering(Scene *scene, Object *obedit, UvNearestHit *hit, c (ts->uv_selectmode & UV_SELECT_VERT); bool select; + if (!extend) { + ED_uvedit_deselect_all(scene, obedit, SEL_DESELECT); + } + if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { - /* Pass. */ + uvedit_select_prepare_sync_select(scene, bm); } else { uvedit_select_prepare_custom_data(scene, bm); } const BMUVOffsets offsets = BM_uv_map_offsets_get(bm); - if (!extend) { - uv_select_all_perform(scene, obedit, SEL_DESELECT); - } - BM_mesh_elem_hflag_disable_all(bm, BM_EDGE, BM_ELEM_TAG, false); if (extend) { - select = !uvedit_edge_select_test(scene, hit->l, offsets); + select = !uvedit_edge_select_test(scene, bm, hit->l, offsets); } else { select = true; @@ -1859,10 +2399,10 @@ static int uv_select_edgering(Scene *scene, Object *obedit, UvNearestHit *hit, c 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, offsets)) { + if (select && uvedit_edge_select_test(scene, bm, l_step, offsets)) { break; } - if (!select && !uvedit_edge_select_test(scene, l_step, offsets)) { + if (!select && !uvedit_edge_select_test(scene, bm, l_step, offsets)) { break; } } @@ -1895,7 +2435,8 @@ static void uv_select_linked_multi(Scene *scene, BLI_assert(hflag == BM_ELEM_SELECT); } - const bool uv_sync_select = (scene->toolsettings->uv_flag & UV_FLAG_SELECT_SYNC); + const ToolSettings *ts = scene->toolsettings; + const bool uv_select_sync = (ts->uv_flag & UV_FLAG_SELECT_SYNC); /* loop over objects, or just use hit->ob */ for (const int ob_index : objects.index_range()) { @@ -1913,8 +2454,8 @@ static void uv_select_linked_multi(Scene *scene, char *flag; BMesh *bm = BKE_editmesh_from_object(obedit)->bm; - if (uv_sync_select) { - /* Pass. */ + if (uv_select_sync) { + uvedit_select_prepare_sync_select(scene, bm); } else { uvedit_select_prepare_custom_data(scene, bm); @@ -1929,7 +2470,7 @@ static void uv_select_linked_multi(Scene *scene, * * Better solve this by having a delimit option for select-linked operator, * keeping island-select working as is. */ - UvVertMap *vmap = BM_uv_vert_map_create(bm, !uv_sync_select); + UvVertMap *vmap = BM_uv_vert_map_create(bm, !uv_select_sync); if (vmap == nullptr) { continue; } @@ -1950,9 +2491,9 @@ static void uv_select_linked_multi(Scene *scene, } else { BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { - if (uvedit_uv_select_test(scene, l, offsets)) { + if (uvedit_uv_select_test(scene, bm, l, offsets)) { bool add_to_stack = true; - if (uv_sync_select) { + if (uv_select_sync) { /* Special case, vertex/edge & sync select being enabled. * * Without this, a second linked select will 'grow' each time as each new @@ -1964,7 +2505,7 @@ static void uv_select_linked_multi(Scene *scene, * - There are no connected fully selected faces UV-connected to this loop. */ BLI_assert(!select_faces); - if (uvedit_face_select_test(scene, l->f, offsets)) { + if (uvedit_face_select_test(scene, bm, l->f, offsets)) { /* pass */ } else { @@ -1972,7 +2513,7 @@ static void uv_select_linked_multi(Scene *scene, 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, offsets.uv) && - uvedit_face_select_test(scene, l_other->f, offsets)) + uvedit_face_select_test(scene, bm, l_other->f, offsets)) { add_to_stack = false; break; @@ -2057,7 +2598,7 @@ static void uv_select_linked_multi(Scene *scene, } else { BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { - if (uvedit_uv_select_test(scene, l, offsets)) { + if (uvedit_uv_select_test(scene, bm, l, offsets)) { found_selected = true; break; } @@ -2090,7 +2631,7 @@ static void uv_select_linked_multi(Scene *scene, * In this case it's important perform selection in two passes, * otherwise the final vertex/edge selection around UV island boundaries * will contain a mixed selection depending on the order of faces. */ - const bool needs_multi_pass = uv_sync_select && + const bool needs_multi_pass = uv_select_sync && (scene->toolsettings->selectmode & (SCE_SELECT_VERTEX | SCE_SELECT_EDGE)) && (deselect == false); @@ -2139,13 +2680,27 @@ static void uv_select_linked_multi(Scene *scene, MEM_freeN(flag); BM_uv_vert_map_free(vmap); - if (uv_sync_select) { - if (deselect) { - BM_mesh_select_flush_from_verts(bm, false); + if (uv_select_sync) { + if (ED_uvedit_sync_uvselect_ignore(ts)) { + if (deselect) { + BM_mesh_select_flush_from_verts(bm, false); + } + else { + if (!select_faces) { + BM_mesh_select_mode_flush(bm); + } + } } else { - if (!select_faces) { - BM_mesh_select_mode_flush(bm); + BLI_assert(ED_uvedit_sync_uvselect_is_valid_or_ignore(ts, bm)); + if (bm->uv_select_sync_valid) { + if (deselect) { + BM_mesh_uvselect_flush_from_faces_only_deselect(bm); + } + else { + BM_mesh_uvselect_flush_from_faces_only_select(bm); + } + BM_mesh_uvselect_sync_to_mesh(bm); } } } @@ -2173,6 +2728,7 @@ static void uv_select_linked_multi_for_select_island(Scene *scene, } const float *uvedit_first_selected_uv_from_vertex(Scene *scene, + const BMesh *bm, BMVert *eve, const BMUVOffsets &offsets) { @@ -2184,7 +2740,7 @@ const float *uvedit_first_selected_uv_from_vertex(Scene *scene, continue; } - if (uvedit_uv_select_test(scene, l, offsets)) { + if (uvedit_uv_select_test(scene, bm, l, offsets)) { float *luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv); return luv; } @@ -2212,7 +2768,9 @@ static wmOperatorStatus uv_select_more_less(bContext *C, const bool select) Vector objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( scene, view_layer, nullptr); - const bool is_uv_face_selectmode = (ts->uv_selectmode == UV_SELECT_FACE); + const bool is_uv_face_selectmode = (ts->uv_flag & UV_FLAG_SELECT_SYNC) ? + (ts->selectmode == SCE_SELECT_FACE) : + (ts->uv_selectmode == UV_SELECT_FACE); for (Object *obedit : objects) { BMesh *bm = BKE_editmesh_from_object(obedit)->bm; @@ -2220,14 +2778,14 @@ static wmOperatorStatus uv_select_more_less(bContext *C, const bool select) bool changed = false; if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { - /* Pass. */ + uvedit_select_prepare_sync_select(scene, bm); } else { uvedit_select_prepare_custom_data(scene, bm); } const BMUVOffsets offsets = BM_uv_map_offsets_get(bm); - if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { + if ((ts->uv_flag & UV_FLAG_SELECT_SYNC) && (bm->uv_select_sync_valid == false)) { BMEditMesh *em = BKE_editmesh_from_object(obedit); if (select) { EDBM_select_more(em, true); @@ -2257,14 +2815,14 @@ static wmOperatorStatus uv_select_more_less(bContext *C, const bool select) int sel_state = 0; BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { - if (BM_ELEM_CD_GET_BOOL(l, offsets.select_vert)) { + if (uvedit_loop_vert_select_get(ts, bm, l, offsets)) { sel_state |= NEIGHBORING_FACE_IS_SEL; } else { sel_state |= CURR_FACE_IS_UNSEL; } - if (!BM_ELEM_CD_GET_BOOL(l, offsets.select_edge)) { + if (!uvedit_loop_edge_select_get(ts, bm, l, offsets)) { sel_state |= CURR_FACE_IS_UNSEL; } @@ -2281,12 +2839,12 @@ static wmOperatorStatus uv_select_more_less(bContext *C, const bool select) #undef CURR_FACE_IS_UNSEL } else { - if (!uvedit_face_select_test(scene, efa, offsets)) { + if (!uvedit_face_select_test(scene, bm, efa, offsets)) { 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(ts, l, offsets)) { + if (!uvedit_vert_is_all_other_faces_selected(ts, bm, l, offsets)) { BM_elem_flag_enable(efa, BM_ELEM_TAG); changed = true; break; @@ -2310,7 +2868,7 @@ static wmOperatorStatus uv_select_more_less(bContext *C, const bool select) if (uvedit_face_visible_test(scene, efa)) { BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { - if (BM_ELEM_CD_GET_BOOL(l, offsets.select_vert) == select) { + if (uvedit_loop_vert_select_get(ts, bm, l, offsets) == select) { BM_elem_flag_enable(l->next, BM_ELEM_TAG); BM_elem_flag_enable(l->prev, BM_ELEM_TAG); changed = true; @@ -2329,8 +2887,25 @@ static wmOperatorStatus uv_select_more_less(bContext *C, const bool select) /* Select tagged loops. */ uv_select_flush_from_tag_loop(scene, obedit, select); /* Set/unset edge flags based on selected verts. */ - uvedit_select_flush_from_verts(scene, bm, select); + if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { + /* Pass. */ + } + else { + uvedit_select_flush_from_verts(scene, bm, select); + } } + + if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { + BLI_assert(bm->uv_select_sync_valid); /* Already handled. */ + if (select) { + BM_mesh_uvselect_flush_from_loop_verts_only_select(bm); + } + else { + BM_mesh_uvselect_flush_from_loop_verts_only_deselect(bm); + } + BM_mesh_uvselect_sync_to_mesh(bm); + } + DEG_id_tag_update(static_cast(obedit->data), ID_RECALC_SELECT); WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); } @@ -2423,7 +2998,12 @@ bool uvedit_select_is_any_selected_multi(const Scene *scene, const Spantoolsettings; + BMesh *bm = em->bm; + if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { + /* Clear all partial selection as there is no need for it. */ + bm->uv_select_sync_valid = false; + if (select_all) { EDBM_flag_enable_all(em, BM_ELEM_SELECT); } @@ -2437,10 +3017,10 @@ static void uv_select_all(const Scene *scene, BMEditMesh *em, bool select_all) BMLoop *l; BMIter iter, liter; - uvedit_select_prepare_custom_data(scene, em->bm); - const BMUVOffsets offsets = BM_uv_map_offsets_get(em->bm); + uvedit_select_prepare_custom_data(scene, bm); + const BMUVOffsets offsets = BM_uv_map_offsets_get(bm); - BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { + BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { if (!uvedit_face_visible_test(scene, efa)) { continue; } @@ -2453,12 +3033,6 @@ static void uv_select_all(const Scene *scene, BMEditMesh *em, bool select_all) static void uv_select_toggle_all(const Scene *scene, BMEditMesh *em) { - const ToolSettings *ts = scene->toolsettings; - if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { - EDBM_select_toggle_all(em); - return; - } - bool select_any = uvedit_select_is_any_selected(scene, em->bm); uv_select_all(scene, em, !select_any); } @@ -2466,13 +3040,99 @@ static void uv_select_toggle_all(const Scene *scene, BMEditMesh *em) static void uv_select_invert(const Scene *scene, BMEditMesh *em) { const ToolSettings *ts = scene->toolsettings; + BMesh *bm = em->bm; + if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { - EDBM_select_swap(em); - EDBM_selectmode_flush(em); + if (ED_uvedit_sync_uvselect_ignore(ts)) { + bm->uv_select_sync_valid = false; + } + /* If selection wasn't synced, there is no need to sync. */ + if (bm->uv_select_sync_valid == false) { + EDBM_select_swap(em); + EDBM_selectmode_flush(em); + return; + } + + /* Invert */ + BMIter iter; + BMFace *efa; + + if (bm->selectmode & SCE_SELECT_VERTEX) { + BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(efa, BM_ELEM_HIDDEN)) { + continue; + } + BMIter liter; + BMLoop *l; + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + BM_loop_vert_uvselect_set_noflush(bm, l, !BM_elem_flag_test(l, BM_ELEM_SELECT_UV)); + } + } + /* Flush vertices to edges & faces. */ + BM_mesh_uvselect_mode_flush(bm); + } + else if (em->selectmode & SCE_SELECT_EDGE) { + const BMUVOffsets offsets = BM_uv_map_offsets_get(bm); + + BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(efa, BM_ELEM_HIDDEN)) { + continue; + } + BMIter liter; + BMLoop *l; + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + /* No need to flush edges, as they will all be flipped. */ + BM_loop_edge_uvselect_set_noflush(bm, l, !BM_elem_flag_test(l, BM_ELEM_SELECT_UV_EDGE)); + /* Flush back afterwards. */ + BM_loop_vert_uvselect_set_noflush(bm, l, false); + } + } + BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(efa, BM_ELEM_HIDDEN)) { + continue; + } + bool face_select = true; + BMIter liter; + BMLoop *l; + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + if (BM_elem_flag_test(l, BM_ELEM_SELECT_UV_EDGE)) { + if (ts->uv_sticky == UV_STICKY_LOCATION) { + BM_loop_vert_uvselect_set_shared(bm, l, true, offsets.uv); + BM_loop_vert_uvselect_set_shared(bm, l->next, true, offsets.uv); + } + else { + BM_loop_vert_uvselect_set_noflush(bm, l, true); + BM_loop_vert_uvselect_set_noflush(bm, l->next, true); + } + } + else { + face_select = false; + } + } + BM_face_uvselect_set_noflush(bm, efa, face_select); + } + /* Edges are flushed to faces inline. */ + } + else { + BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(efa, BM_ELEM_HIDDEN)) { + continue; + } + BM_face_uvselect_set(bm, efa, !BM_elem_flag_test(efa, BM_ELEM_SELECT_UV)); + } + + if (ts->uv_sticky == UV_STICKY_LOCATION) { + const BMUVOffsets offsets = BM_uv_map_offsets_get(bm); + BM_mesh_uvselect_flush_shared_only_select(bm, offsets.uv); + } + } + + /* NOTE: no need to run: #BM_mesh_uvselect_flush_shared_only_select + * because inverting doesn't change the sticky state. */ + BM_mesh_uvselect_sync_to_mesh(bm); return; } - BMesh *bm = em->bm; uvedit_select_prepare_custom_data(scene, bm); const BMUVOffsets offsets = BM_uv_map_offsets_get(bm); BMFace *efa; @@ -2509,10 +3169,21 @@ static void uv_select_invert(const Scene *scene, BMEditMesh *em) } } -static void uv_select_all_perform(const Scene *scene, Object *obedit, int action) +void ED_uvedit_deselect_all(const Scene *scene, Object *obedit, int action) { + const ToolSettings *ts = scene->toolsettings; BMEditMesh *em = BKE_editmesh_from_object(obedit); + /* In the case of where the selection is all or none, there is no need to hold + * a separate state for UV's and the mesh. */ + if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { + if (em->bm->uv_select_sync_valid) { + if (ELEM(action, SEL_SELECT, SEL_DESELECT)) { + EDBM_uvselect_clear(em); + } + } + } + switch (action) { case SEL_TOGGLE: { uv_select_toggle_all(scene, em); @@ -2546,7 +3217,7 @@ static void uv_select_all_perform_multi_ex(const Scene *scene, if (ob_exclude && (obedit == ob_exclude)) { continue; } - uv_select_all_perform(scene, obedit, action); + ED_uvedit_deselect_all(scene, obedit, action); } } @@ -2702,19 +3373,19 @@ static bool uv_mouse_select_multi(bContext *C, const BMUVOffsets offsets = BM_uv_map_offsets_get(bm); if (selectmode == UV_SELECT_FACE) { - is_selected = uvedit_face_select_test(scene, hit.efa, offsets); + is_selected = uvedit_face_select_test(scene, bm, hit.efa, offsets); } else if (selectmode == UV_SELECT_EDGE) { - is_selected = uvedit_edge_select_test(scene, hit.l, offsets); + is_selected = uvedit_edge_select_test(scene, bm, hit.l, offsets); } 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 != nullptr) { - is_selected = uvedit_uv_select_test(scene, hit.l, offsets); + is_selected = uvedit_uv_select_test(scene, bm, hit.l, offsets); } else { - is_selected = uvedit_face_select_test(scene, hit.efa, offsets); + is_selected = uvedit_face_select_test(scene, bm, hit.efa, offsets); } } } @@ -2744,6 +3415,7 @@ static bool uv_mouse_select_multi(bContext *C, uvedit_select_prepare_custom_data(scene, bm); } const BMUVOffsets offsets = BM_uv_map_offsets_get(bm); + BMElem *ele_active = nullptr; if (use_select_linked) { const bool extend = params.sel_op == SEL_OP_ADD; @@ -2783,6 +3455,10 @@ static bool uv_mouse_select_multi(bContext *C, } } + if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { + uvedit_select_prepare_sync_select(scene, bm); + } + if (selectmode == UV_SELECT_FACE) { uvedit_face_select_set_with_sticky(scene, bm, hit.efa, select_value, offsets); flush = 1; @@ -2802,19 +3478,15 @@ static bool uv_mouse_select_multi(bContext *C, /* De-selecting an edge may deselect a face too - validate. */ if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { if (select_value) { - BMElem *ele = nullptr; + /* Postpone setting active until it's known if the underlying element is selected. */ if (selectmode == UV_SELECT_FACE) { - ele = (BMElem *)hit.efa; + ele_active = (BMElem *)hit.efa; } else if (selectmode == UV_SELECT_EDGE) { - ele = (BMElem *)hit.l->e; + ele_active = (BMElem *)hit.l->e; } else if (selectmode == UV_SELECT_VERT) { - ele = (BMElem *)hit.l->v; - } - /* Expected to be true, harmless if it's not. */ - if (ele && BM_elem_flag_test(ele, BM_ELEM_SELECT)) { - BM_select_history_store(bm, ele); + ele_active = (BMElem *)hit.l->v; } } else { @@ -2832,7 +3504,27 @@ static bool uv_mouse_select_multi(bContext *C, if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { if (flush != 0) { - BM_mesh_select_mode_flush(bm); + if (bm->uv_select_sync_valid) { + /* TODO: the picking should edge deselection to faces for e.g. */ + + /* NOTE: currently face selection handles all flushing itself. + * Flushing face mode will dis-connect the shared vertices unless + * shared locations are re-applied afterwards. */ + if (selectmode != UV_SELECT_FACE) { + BM_mesh_uvselect_mode_flush(bm); + } + + BM_mesh_uvselect_sync_to_mesh(bm); + } + else { + BM_mesh_select_mode_flush(bm); + } + } + + if (ele_active) { + if (BM_elem_flag_test(ele_active, BM_ELEM_SELECT)) { + BM_select_history_store(bm, ele_active); + } } } else { @@ -2980,11 +3672,15 @@ static wmOperatorStatus uv_mouse_select_loop_generic_multi(bContext *C, } if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { - if (flush == 1) { - BM_mesh_select_flush_from_verts(bm, true); + if (ED_uvedit_sync_uvselect_ignore(ts)) { + if (flush != 0) { + BM_mesh_select_flush_from_verts(bm, flush == 1 ? true : false); + } } - else if (flush == -1) { - BM_mesh_select_flush_from_verts(bm, false); + else { + if (flush != 0) { + ED_uvedit_select_sync_flush(ts, bm, flush == 1 ? true : false); + } } } else { @@ -3155,7 +3851,8 @@ static wmOperatorStatus uv_select_linked_internal(bContext *C, ViewLayer *view_layer = CTX_data_view_layer(C); bool extend = true; bool deselect = false; - bool select_faces = (ts->uv_flag & UV_FLAG_SELECT_SYNC) && (ts->selectmode & SCE_SELECT_FACE); + bool select_faces = (ts->uv_flag & UV_FLAG_SELECT_SYNC) && (ts->selectmode & SCE_SELECT_FACE) && + (ts->uv_sticky == UV_STICKY_VERT); UvNearestHit hit = region ? uv_nearest_hit_init_max(®ion->v2d) : uv_nearest_hit_init_max_default(); @@ -3319,8 +4016,14 @@ static wmOperatorStatus uv_select_split_exec(bContext *C, wmOperator *op) BMIter iter, liter; if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { - BKE_report(op->reports, RPT_ERROR, "Cannot split selection when sync selection is enabled"); - return OPERATOR_CANCELLED; + /* Face selection. */ + if (ts->uv_sticky == UV_STICKY_VERT) { + BKE_report( + op->reports, + RPT_ERROR, + "Cannot split selection with \"Sync Select\" and \"Shared Vertex\" selection enabled"); + return OPERATOR_CANCELLED; + } } bool changed_multi = false; @@ -3333,7 +4036,12 @@ static wmOperatorStatus uv_select_split_exec(bContext *C, wmOperator *op) bool changed = false; - uvedit_select_prepare_custom_data(scene, bm); + if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { + uvedit_select_prepare_sync_select(scene, bm); + } + else { + uvedit_select_prepare_custom_data(scene, bm); + } const BMUVOffsets offsets = BM_uv_map_offsets_get(bm); BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { @@ -3346,15 +4054,13 @@ static wmOperatorStatus uv_select_split_exec(bContext *C, wmOperator *op) /* are we all selected? */ BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + const bool select_vert = uvedit_loop_vert_select_get(ts, bm, l, offsets); + const bool select_edge = uvedit_loop_edge_select_get(ts, bm, l, offsets); - if (BM_ELEM_CD_GET_BOOL(l, offsets.select_vert) || - BM_ELEM_CD_GET_BOOL(l, offsets.select_edge)) - { + if (select_vert || select_edge) { is_sel = true; } - if (!BM_ELEM_CD_GET_BOOL(l, offsets.select_vert) || - !BM_ELEM_CD_GET_BOOL(l, offsets.select_edge)) - { + if (!select_vert || !select_edge) { is_unsel = true; } @@ -3365,9 +4071,11 @@ static wmOperatorStatus uv_select_split_exec(bContext *C, wmOperator *op) } if (is_sel && is_unsel) { + /* No need to deselect the face (with sync-select) as it wont be selected, + * since it already has a mixed selection. */ BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { - BM_ELEM_CD_SET_BOOL(l, offsets.select_vert, false); - BM_ELEM_CD_SET_BOOL(l, offsets.select_edge, false); + uvedit_loop_vert_select_set(ts, bm, l, false, offsets); + uvedit_loop_edge_select_set(ts, bm, l, false, offsets); } changed = true; @@ -3425,7 +4133,7 @@ static void uv_select_tag_update_for_object(Depsgraph *depsgraph, /** * 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( +static void uvedit_uv_select_flush_from_tag_sticky_loc_internal( const Scene *scene, BMesh *bm, BMLoop *l, const bool select, const BMUVOffsets &offsets) { uvedit_uv_select_set(scene, bm, l, select, offsets); @@ -3479,7 +4187,7 @@ static void uv_select_flush_from_tag_face(const Scene *scene, Object *obedit, co BMIter iter, liter; if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { - /* Pass. */ + uvedit_select_prepare_sync_select(scene, bm); } else { uvedit_select_prepare_custom_data(scene, bm); @@ -3488,8 +4196,10 @@ static void uv_select_flush_from_tag_face(const Scene *scene, Object *obedit, co bool use_sticky = true; if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { - /* Use the mesh selection directly. */ - use_sticky = false; + if (ED_uvedit_sync_uvselect_ignore(ts)) { + /* Use the mesh selection directly. */ + use_sticky = false; + } } if (ts->uv_sticky == UV_STICKY_DISABLE) { /* No need for sticky calculation when it's disabled. */ @@ -3502,15 +4212,19 @@ static void uv_select_flush_from_tag_face(const Scene *scene, Object *obedit, co continue; } + if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { + BM_face_uvselect_set_noflush(bm, efa, select); + } + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + uvedit_loop_edge_select_set(ts, bm, l, select, offsets); + if (select) { - BM_ELEM_CD_SET_BOOL(l, offsets.select_edge, true); - uv_select_flush_from_tag_sticky_loc_internal(scene, bm, l, select, offsets); + uvedit_uv_select_flush_from_tag_sticky_loc_internal(scene, bm, l, select, offsets); } else { - BM_ELEM_CD_SET_BOOL(l, offsets.select_edge, false); - if (!uvedit_vert_is_face_select_any_other(ts, l, offsets)) { - uv_select_flush_from_tag_sticky_loc_internal(scene, bm, l, select, offsets); + if (!uvedit_vert_is_face_select_any_other(ts, bm, l, offsets)) { + uvedit_uv_select_flush_from_tag_sticky_loc_internal(scene, bm, l, select, offsets); } } } @@ -3550,15 +4264,18 @@ static void uv_select_flush_from_tag_loop(const Scene *scene, Object *obedit, co BMLoop *l; BMIter iter, liter; + const bool use_mesh_select = (ts->uv_flag & UV_FLAG_SELECT_SYNC) && + (bm->uv_select_sync_valid == false); + if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { - /* Pass. */ + uvedit_select_prepare_sync_select(scene, bm); } else { uvedit_select_prepare_custom_data(scene, bm); } const BMUVOffsets offsets = BM_uv_map_offsets_get(bm); - if ((ts->uv_flag & UV_FLAG_SELECT_SYNC) == 0 && ts->uv_sticky == UV_STICKY_VERT) { + if ((use_mesh_select == false) && ts->uv_sticky == UV_STICKY_VERT) { /* Tag all verts as untouched, then touch the ones that have a face center * in the loop and select all UVs that use a touched vert. */ BM_mesh_elem_hflag_disable_all(bm, BM_VERT, BM_ELEM_TAG, false); @@ -3580,11 +4297,11 @@ static void uv_select_flush_from_tag_loop(const Scene *scene, Object *obedit, co } } } - else if ((ts->uv_flag & UV_FLAG_SELECT_SYNC) == 0 && ts->uv_sticky == UV_STICKY_LOCATION) { + else if ((use_mesh_select == false) && (ts->uv_sticky == UV_STICKY_LOCATION)) { BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { 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, bm, l, select, offsets); + uvedit_uv_select_flush_from_tag_sticky_loc_internal(scene, bm, l, select, offsets); } } } @@ -3618,7 +4335,7 @@ static void uv_select_flush_from_loop_edge_flag(const Scene *scene, BMesh *bm) BMIter iter, liter; if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { - /* Pass. */ + uvedit_select_prepare_sync_select(scene, bm); } else { uvedit_select_prepare_custom_data(scene, bm); @@ -3627,8 +4344,10 @@ static void uv_select_flush_from_loop_edge_flag(const Scene *scene, BMesh *bm) bool use_sticky = true; if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { - /* Use the mesh selection directly. */ - use_sticky = false; + if (ts->uv_sticky == UV_STICKY_VERT) { + /* Use the mesh selection directly. */ + use_sticky = false; + } } if (ts->uv_sticky == UV_STICKY_DISABLE) { /* No need for sticky calculation when it's disabled. */ @@ -3650,22 +4369,27 @@ static void uv_select_flush_from_loop_edge_flag(const Scene *scene, BMesh *bm) BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { /* Select verts based on UV edge flag. */ if (BM_ELEM_CD_GET_BOOL(l, offsets.select_edge)) { - uv_select_flush_from_tag_sticky_loc_internal(scene, bm, l, true, offsets); - uv_select_flush_from_tag_sticky_loc_internal(scene, bm, l->next, true, offsets); + uvedit_uv_select_flush_from_tag_sticky_loc_internal(scene, bm, l, true, offsets); + uvedit_uv_select_flush_from_tag_sticky_loc_internal(scene, bm, l->next, true, offsets); } } } } else { - BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { - BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { + BM_mesh_uvselect_flush_from_loop_edges(bm, false); + } + else { + BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { - if (BM_ELEM_CD_GET_BOOL(l, offsets.select_edge)) { - BM_ELEM_CD_SET_BOOL(l, offsets.select_vert, true); - BM_ELEM_CD_SET_BOOL(l->next, offsets.select_vert, true); - } - else if (!BM_ELEM_CD_GET_BOOL(l->prev, offsets.select_edge)) { - BM_ELEM_CD_SET_BOOL(l->next, offsets.select_vert, false); + if (BM_ELEM_CD_GET_BOOL(l, offsets.select_edge)) { + BM_ELEM_CD_SET_BOOL(l, offsets.select_vert, true); + BM_ELEM_CD_SET_BOOL(l->next, offsets.select_vert, true); + } + else if (!BM_ELEM_CD_GET_BOOL(l->prev, offsets.select_edge)) { + BM_ELEM_CD_SET_BOOL(l->next, offsets.select_vert, false); + } } } } @@ -3724,9 +4448,8 @@ static wmOperatorStatus uv_box_select_exec(bContext *C, wmOperator *op) bool changed = false; if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { - /* Pass. */ - /* NOTE: sync selection can't do pinned. */ + uvedit_select_prepare_sync_select(scene, bm); } else { uvedit_select_prepare_custom_data(scene, bm); @@ -3843,7 +4566,7 @@ static wmOperatorStatus uv_box_select_exec(bContext *C, wmOperator *op) bool has_selected = false; BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv); - if (select != uvedit_uv_select_test(scene, l, offsets)) { + if (select != uvedit_uv_select_test(scene, bm, l, offsets)) { if (!pinned || (ts->uv_flag & UV_FLAG_SELECT_SYNC)) { /* UV_FLAG_SELECT_SYNC - can't do pinned selection */ if (BLI_rctf_isect_pt_v(&rectf, luv)) { @@ -4006,7 +4729,7 @@ static wmOperatorStatus uv_circle_select_exec(bContext *C, wmOperator *op) bool changed = false; if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { - /* Pass. */ + uvedit_select_prepare_sync_select(scene, bm); } else { uvedit_select_prepare_custom_data(scene, bm); @@ -4026,7 +4749,7 @@ static wmOperatorStatus uv_circle_select_exec(bContext *C, wmOperator *op) } else { BM_elem_flag_disable(efa, BM_ELEM_TAG); - if (select == uvedit_face_select_test(scene, efa, offsets)) { + if (select == uvedit_face_select_test(scene, bm, efa, offsets)) { continue; } } @@ -4058,7 +4781,7 @@ static wmOperatorStatus uv_circle_select_exec(bContext *C, wmOperator *op) } BMLoop *l_prev = BM_FACE_FIRST_LOOP(efa)->prev; - float *luv_prev = BM_ELEM_CD_GET_FLOAT_P(l_prev, offsets.uv); + const float *luv_prev = BM_ELEM_CD_GET_FLOAT_P(l_prev, offsets.uv); bool has_selected = false; BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { @@ -4086,7 +4809,7 @@ static wmOperatorStatus uv_circle_select_exec(bContext *C, wmOperator *op) } bool has_selected = false; BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { - if (select != uvedit_uv_select_test(scene, l, offsets)) { + if (select != uvedit_uv_select_test(scene, bm, l, offsets)) { luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv); if (uv_circle_select_is_point_inside(luv, offset, ellipse)) { changed = true; @@ -4226,7 +4949,7 @@ static bool do_lasso_select_mesh_uv(bContext *C, const Span mcoords, const BMesh *bm = BKE_editmesh_from_object(obedit)->bm; if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { - /* Pass. */ + uvedit_select_prepare_sync_select(scene, bm); } else { uvedit_select_prepare_custom_data(scene, bm); @@ -4245,7 +4968,7 @@ static bool do_lasso_select_mesh_uv(bContext *C, const Span mcoords, const } else { BM_elem_flag_disable(efa, BM_ELEM_TAG); - if (select == uvedit_face_select_test(scene, efa, offsets)) { + if (select == uvedit_face_select_test(scene, bm, efa, offsets)) { continue; } } @@ -4336,7 +5059,7 @@ static bool do_lasso_select_mesh_uv(bContext *C, const Span mcoords, const } bool has_selected = false; BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { - if (select != uvedit_uv_select_test(scene, l, offsets)) { + if (select != uvedit_uv_select_test(scene, bm, l, offsets)) { float *luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv); if (do_lasso_select_mesh_uv_is_point_inside(region, &rect, mcoords, luv)) { uvedit_uv_select_set(scene, bm, l, select, offsets); @@ -4444,7 +5167,7 @@ static wmOperatorStatus uv_select_pinned_exec(bContext *C, wmOperator *op) bool changed = false; if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { - /* Pass. */ + uvedit_select_prepare_sync_select(scene, bm); } else { uvedit_select_prepare_custom_data(scene, bm); @@ -4464,7 +5187,10 @@ static wmOperatorStatus uv_select_pinned_exec(bContext *C, wmOperator *op) } } } - if ((ts->uv_flag & UV_FLAG_SELECT_SYNC) == 0) { + if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { + ED_uvedit_select_sync_flush(ts, bm, true); + } + else { ED_uvedit_selectmode_flush(scene, bm); } @@ -4586,7 +5312,7 @@ static wmOperatorStatus uv_select_overlap(bContext *C, const bool extend) BM_mesh_elem_index_ensure(bm, BM_VERT | BM_FACE); BM_mesh_elem_hflag_disable_all(bm, BM_FACE, BM_ELEM_TAG, false); if (!extend) { - uv_select_all_perform(scene, obedit, SEL_DESELECT); + ED_uvedit_deselect_all(scene, obedit, SEL_DESELECT); } BMIter iter; @@ -4732,8 +5458,8 @@ static wmOperatorStatus uv_select_overlap(bContext *C, const bool extend) const BMUVOffsets offsets_b = BM_uv_map_offsets_get(bm_b); /* Skip if both faces are already selected. */ - if (uvedit_face_select_test(scene, face_a, offsets_a) && - uvedit_face_select_test(scene, face_b, offsets_b)) + if (uvedit_face_select_test(scene, bm_a, face_a, offsets_a) && + uvedit_face_select_test(scene, bm_b, face_b, offsets_b)) { continue; } @@ -5012,7 +5738,7 @@ static wmOperatorStatus uv_select_similar_vert_exec(bContext *C, wmOperator *op) BMLoop *l; BMIter liter; BM_ITER_ELEM (l, &liter, face, BM_LOOPS_OF_FACE) { - if (!uvedit_uv_select_test(scene, l, offsets)) { + if (!uvedit_uv_select_test(scene, bm, l, offsets)) { continue; } float needle = get_uv_vert_needle(type, l->v, ob_m3, l, offsets); @@ -5035,6 +5761,10 @@ static wmOperatorStatus uv_select_similar_vert_exec(bContext *C, wmOperator *op) } } + if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { + uvedit_select_prepare_sync_select(scene, bm); + } + bool changed = false; const BMUVOffsets offsets = BM_uv_map_offsets_get(bm); @@ -5050,7 +5780,7 @@ static wmOperatorStatus uv_select_similar_vert_exec(bContext *C, wmOperator *op) BMLoop *l; BMIter liter; BM_ITER_ELEM (l, &liter, face, BM_LOOPS_OF_FACE) { - if (uvedit_uv_select_test(scene, l, offsets)) { + if (uvedit_uv_select_test(scene, bm, l, offsets)) { continue; /* Already selected. */ } const float needle = get_uv_vert_needle(type, l->v, ob_m3, l, offsets); @@ -5063,7 +5793,13 @@ static wmOperatorStatus uv_select_similar_vert_exec(bContext *C, wmOperator *op) } if (changed) { if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { - BM_mesh_select_flush_from_verts(bm, true); + if (bm->uv_select_sync_valid) { + BM_mesh_uvselect_flush_from_loop_verts_only_select(bm); + BM_mesh_uvselect_sync_to_mesh(bm); + } + else { + BM_mesh_select_flush_from_verts(bm, true); + } } else { uvedit_select_flush_from_verts(scene, bm, true); @@ -5126,7 +5862,7 @@ static wmOperatorStatus uv_select_similar_edge_exec(bContext *C, wmOperator *op) BMLoop *l; BMIter liter; BM_ITER_ELEM (l, &liter, face, BM_LOOPS_OF_FACE) { - if (!uvedit_edge_select_test(scene, l, offsets)) { + if (!uvedit_edge_select_test(scene, bm, l, offsets)) { continue; } @@ -5152,6 +5888,10 @@ static wmOperatorStatus uv_select_similar_edge_exec(bContext *C, wmOperator *op) } } + if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { + uvedit_select_prepare_sync_select(scene, bm); + } + bool changed = false; const BMUVOffsets offsets = BM_uv_map_offsets_get(bm); float ob_m3[3][3]; @@ -5166,7 +5906,7 @@ static wmOperatorStatus uv_select_similar_edge_exec(bContext *C, wmOperator *op) BMLoop *l; BMIter liter; BM_ITER_ELEM (l, &liter, face, BM_LOOPS_OF_FACE) { - if (uvedit_edge_select_test(scene, l, offsets)) { + if (uvedit_edge_select_test(scene, bm, l, offsets)) { continue; /* Already selected. */ } @@ -5180,7 +5920,13 @@ static wmOperatorStatus uv_select_similar_edge_exec(bContext *C, wmOperator *op) } if (changed) { if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { - BM_mesh_select_flush_from_verts(bm, true); + if (bm->uv_select_sync_valid) { + BM_mesh_uvselect_flush_from_loop_verts_only_select(bm); + BM_mesh_uvselect_sync_to_mesh(bm); + } + else { + BM_mesh_select_flush_from_verts(bm, true); + } } else { uvedit_select_flush_from_verts(scene, bm, true); @@ -5235,7 +5981,7 @@ static wmOperatorStatus uv_select_similar_face_exec(bContext *C, wmOperator *op) if (!uvedit_face_visible_test(scene, face)) { continue; } - if (!uvedit_face_select_test(scene, face, offsets)) { + if (!uvedit_face_select_test(scene, bm, face, offsets)) { continue; } @@ -5261,6 +6007,10 @@ static wmOperatorStatus uv_select_similar_face_exec(bContext *C, wmOperator *op) } } + if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { + uvedit_select_prepare_sync_select(scene, bm); + } + bool changed = false; const BMUVOffsets offsets = BM_uv_map_offsets_get(bm); @@ -5273,7 +6023,7 @@ static wmOperatorStatus uv_select_similar_face_exec(bContext *C, wmOperator *op) if (!uvedit_face_visible_test(scene, face)) { continue; } - if (uvedit_face_select_test(scene, face, offsets)) { + if (uvedit_face_select_test(scene, bm, face, offsets)) { continue; } @@ -5287,7 +6037,13 @@ static wmOperatorStatus uv_select_similar_face_exec(bContext *C, wmOperator *op) } if (changed) { if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { - BM_mesh_select_flush_from_verts(bm, true); + if (bm->uv_select_sync_valid) { + BM_mesh_uvselect_flush_from_loop_verts_only_select(bm); + BM_mesh_uvselect_sync_to_mesh(bm); + } + else { + BM_mesh_select_flush_from_verts(bm, true); + } } else { uvedit_select_flush_from_verts(scene, bm, true); @@ -5300,10 +6056,10 @@ static wmOperatorStatus uv_select_similar_face_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } -static bool uv_island_selected(const Scene *scene, FaceIsland *island) +static bool uv_island_selected(const Scene *scene, const BMesh *bm, FaceIsland *island) { BLI_assert(island && island->faces_len); - return uvedit_face_select_test(scene, island->faces[0], island->offsets); + return uvedit_face_select_test(scene, bm, island->faces[0], island->offsets); } static wmOperatorStatus uv_select_similar_island_exec(bContext *C, wmOperator *op) @@ -5342,13 +6098,15 @@ static wmOperatorStatus uv_select_similar_island_exec(bContext *C, wmOperator *o for (const int ob_index : objects.index_range()) { Object *obedit = objects[ob_index]; + BMesh *bm = BKE_editmesh_from_object(obedit)->bm; + float ob_m3[3][3]; copy_m3_m4(ob_m3, obedit->object_to_world().ptr()); int index; LISTBASE_FOREACH_INDEX (FaceIsland *, island, &island_list_ptr[ob_index], index) { island_array[index] = island; - if (!uv_island_selected(scene, island)) { + if (!uv_island_selected(scene, bm, island)) { continue; } float needle = get_uv_island_needle(type, island, ob_m3, island->offsets); @@ -5367,6 +6125,11 @@ static wmOperatorStatus uv_select_similar_island_exec(bContext *C, wmOperator *o for (const int ob_index : objects.index_range()) { Object *obedit = objects[ob_index]; BMesh *bm = BKE_editmesh_from_object(obedit)->bm; + + if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { + uvedit_select_prepare_sync_select(scene, bm); + } + float ob_m3[3][3]; copy_m3_m4(ob_m3, obedit->object_to_world().ptr()); @@ -5374,7 +6137,7 @@ static wmOperatorStatus uv_select_similar_island_exec(bContext *C, wmOperator *o int index; LISTBASE_FOREACH_INDEX (FaceIsland *, island, &island_list_ptr[ob_index], index) { island_array[tot_island_index++] = island; /* To deallocate later. */ - if (uv_island_selected(scene, island)) { + if (uv_island_selected(scene, bm, island)) { continue; } float needle = get_uv_island_needle(type, island, ob_m3, island->offsets); @@ -5390,7 +6153,13 @@ static wmOperatorStatus uv_select_similar_island_exec(bContext *C, wmOperator *o if (changed) { if (ts->uv_flag & UV_FLAG_SELECT_SYNC) { - BM_mesh_select_flush_from_verts(bm, true); + if (bm->uv_select_sync_valid) { + BM_mesh_uvselect_flush_from_loop_verts_only_select(bm); + BM_mesh_uvselect_sync_to_mesh(bm); + } + else { + BM_mesh_select_flush_from_verts(bm, true); + } } else { uvedit_select_flush_from_verts(scene, bm, true); @@ -5553,7 +6322,7 @@ BMFace **ED_uvedit_selected_faces(const Scene *scene, BMesh *bm, int len_max, in 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, offsets)) { + if (uvedit_face_select_test(scene, bm, f, offsets)) { faces[faces_len++] = f; if (faces_len == len_max) { goto finally; @@ -5597,7 +6366,7 @@ BMLoop **ED_uvedit_selected_edges(const Scene *scene, BMesh *bm, int len_max, in 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, offsets)) { + if (uvedit_edge_select_test(scene, bm, l_iter, offsets)) { BM_elem_flag_enable(l_iter, BM_ELEM_TAG); edges[edges_len++] = l_iter; @@ -5714,7 +6483,7 @@ void ED_uvedit_selectmode_clean(const Scene *scene, Object *obedit) continue; } BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { - if (uvedit_uv_select_test(scene, l, offsets)) { + if (uvedit_uv_select_test(scene, bm, l, offsets)) { BM_elem_flag_enable(l, BM_ELEM_TAG); } } @@ -5731,8 +6500,8 @@ void ED_uvedit_selectmode_clean(const Scene *scene, Object *obedit) continue; } BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { - if (uvedit_edge_select_test(scene, l, offsets)) { - uvedit_edge_select_set_noflush(scene, l, true, sticky, offsets); + if (uvedit_edge_select_test(scene, bm, l, offsets)) { + uvedit_edge_select_set_noflush(scene, bm, l, true, sticky, offsets); } } } @@ -5745,7 +6514,7 @@ void ED_uvedit_selectmode_clean(const Scene *scene, Object *obedit) BM_ITER_MESH (efa, &iter, 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, offsets)) { + if (uvedit_face_select_test(scene, bm, efa, offsets)) { BM_elem_flag_enable(efa, BM_ELEM_TAG); } uvedit_face_select_set(scene, bm, efa, false, offsets); @@ -5787,7 +6556,6 @@ void ED_uvedit_sticky_selectmode_update(bContext *C) ViewLayer *view_layer = CTX_data_view_layer(C); Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - Vector objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( scene, view_layer, nullptr); for (Object *obedit : objects) { diff --git a/source/blender/editors/uvedit/uvedit_smart_stitch.cc b/source/blender/editors/uvedit/uvedit_smart_stitch.cc index d6ef5a31ad0..59c94c11fa8 100644 --- a/source/blender/editors/uvedit/uvedit_smart_stitch.cc +++ b/source/blender/editors/uvedit/uvedit_smart_stitch.cc @@ -2081,7 +2081,7 @@ static StitchState *stitch_init(bContext *C, BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { BM_ITER_ELEM_INDEX (l, &liter, efa, BM_LOOPS_OF_FACE, i) { - if (uvedit_uv_select_test(scene, l, offsets)) { + if (uvedit_uv_select_test(scene, em->bm, l, offsets)) { UvElement *element = BM_uv_element_get(state->element_map, l); if (element) { stitch_select_uv(element, state, 1); @@ -2103,7 +2103,7 @@ static StitchState *stitch_init(bContext *C, } BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { - if (uvedit_edge_select_test(scene, l, offsets)) { + if (uvedit_edge_select_test(scene, em->bm, l, offsets)) { UvEdge *edge = uv_edge_get(l, state); if (edge) { stitch_select_edge(edge, state, true); diff --git a/source/blender/editors/uvedit/uvedit_unwrap_ops.cc b/source/blender/editors/uvedit/uvedit_unwrap_ops.cc index 0ab740e85f9..ac68cf07805 100644 --- a/source/blender/editors/uvedit/uvedit_unwrap_ops.cc +++ b/source/blender/editors/uvedit/uvedit_unwrap_ops.cc @@ -464,7 +464,7 @@ static bool uvedit_have_selection(const Scene *scene, BMEditMesh *em, const Unwr } BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { - if (uvedit_uv_select_test(scene, l, offsets)) { + if (uvedit_uv_select_test(scene, em->bm, l, offsets)) { break; } } @@ -533,6 +533,7 @@ float ED_uvedit_get_aspect_y(Object *ob) } static bool uvedit_is_face_affected(const Scene *scene, + const BMesh *bm, BMFace *efa, const UnwrapOptions *options, const BMUVOffsets &offsets) @@ -549,7 +550,7 @@ static bool uvedit_is_face_affected(const Scene *scene, BMLoop *l; BMIter iter; BM_ITER_ELEM (l, &iter, efa, BM_LOOPS_OF_FACE) { - if (uvedit_uv_select_test(scene, l, offsets)) { + if (uvedit_uv_select_test(scene, bm, l, offsets)) { return true; } } @@ -563,6 +564,7 @@ static bool uvedit_is_face_affected(const Scene *scene, */ static void uvedit_prepare_pinned_indices(ParamHandle *handle, const Scene *scene, + const BMesh *bm, BMFace *efa, const UnwrapOptions *options, const BMUVOffsets &offsets) @@ -572,7 +574,7 @@ static void uvedit_prepare_pinned_indices(ParamHandle *handle, BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { bool pin = BM_ELEM_CD_GET_BOOL(l, offsets.pin); if (options->pin_unselected && !pin) { - pin = !uvedit_uv_select_test(scene, l, offsets); + pin = !uvedit_uv_select_test(scene, bm, l, offsets); } if (pin) { int bmvertindex = BM_elem_index_get(l->v); @@ -584,6 +586,7 @@ static void uvedit_prepare_pinned_indices(ParamHandle *handle, static void construct_param_handle_face_add(ParamHandle *handle, const Scene *scene, + const BMesh *bm, BMFace *efa, blender::geometry::ParamKey face_index, const UnwrapOptions *options, @@ -612,7 +615,7 @@ static void construct_param_handle_face_add(ParamHandle *handle, co[i] = l->v->co; uv[i] = luv; pin[i] = BM_ELEM_CD_GET_BOOL(l, offsets.pin); - select[i] = uvedit_uv_select_test(scene, l, offsets); + select[i] = uvedit_uv_select_test(scene, bm, l, offsets); if (options->pin_unselected && !select[i]) { pin[i] = true; } @@ -705,15 +708,15 @@ static ParamHandle *construct_param_handle(const Scene *scene, const int cd_weight_index = BKE_object_defgroup_name_index(ob, options->weight_group); BM_ITER_MESH_INDEX (efa, &iter, bm, BM_FACES_OF_MESH, i) { - if (uvedit_is_face_affected(scene, efa, options, offsets)) { - uvedit_prepare_pinned_indices(handle, scene, efa, options, offsets); + if (uvedit_is_face_affected(scene, bm, efa, options, offsets)) { + uvedit_prepare_pinned_indices(handle, scene, bm, efa, options, offsets); } } BM_ITER_MESH_INDEX (efa, &iter, bm, BM_FACES_OF_MESH, i) { - if (uvedit_is_face_affected(scene, efa, options, offsets)) { + if (uvedit_is_face_affected(scene, bm, efa, options, offsets)) { construct_param_handle_face_add( - handle, scene, efa, i, options, offsets, cd_weight_offset, cd_weight_index); + handle, scene, bm, efa, i, options, offsets, cd_weight_offset, cd_weight_index); } } @@ -762,15 +765,22 @@ static ParamHandle *construct_param_handle_multi(const Scene *scene, const int cd_weight_index = BKE_object_defgroup_name_index(obedit, options->weight_group); BM_ITER_MESH_INDEX (efa, &iter, bm, BM_FACES_OF_MESH, i) { - if (uvedit_is_face_affected(scene, efa, options, offsets)) { - uvedit_prepare_pinned_indices(handle, scene, efa, options, offsets); + if (uvedit_is_face_affected(scene, bm, efa, options, offsets)) { + uvedit_prepare_pinned_indices(handle, scene, bm, efa, options, offsets); } } BM_ITER_MESH_INDEX (efa, &iter, bm, BM_FACES_OF_MESH, i) { - if (uvedit_is_face_affected(scene, efa, options, offsets)) { - construct_param_handle_face_add( - handle, scene, efa, i + offset, options, offsets, cd_weight_offset, cd_weight_index); + if (uvedit_is_face_affected(scene, bm, efa, options, offsets)) { + construct_param_handle_face_add(handle, + scene, + bm, + efa, + i + offset, + options, + offsets, + cd_weight_offset, + cd_weight_index); } } @@ -786,6 +796,7 @@ static ParamHandle *construct_param_handle_multi(const Scene *scene, } static void texface_from_original_index(const Scene *scene, + const BMesh *bm, const BMUVOffsets &offsets, BMFace *efa, int index, @@ -809,7 +820,7 @@ static void texface_from_original_index(const Scene *scene, float *luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv); *r_uv = luv; *r_pin = BM_ELEM_CD_GET_BOOL(l, offsets.pin); - *r_select = uvedit_uv_select_test(scene, l, offsets); + *r_select = uvedit_uv_select_test(scene, bm, l, offsets); break; } } @@ -982,6 +993,7 @@ static ParamHandle *construct_param_handle_subsurfed(const Scene *scene, * If the vertex exists in the, we pass the original uv pointer to the solver, thus * flushing the solution to the edit mesh. */ texface_from_original_index(scene, + em->bm, offsets, origFace, origVertIndices[poly_corner_verts[0]], @@ -989,6 +1001,7 @@ static ParamHandle *construct_param_handle_subsurfed(const Scene *scene, &pin[0], &select[0]); texface_from_original_index(scene, + em->bm, offsets, origFace, origVertIndices[poly_corner_verts[1]], @@ -996,6 +1009,7 @@ static ParamHandle *construct_param_handle_subsurfed(const Scene *scene, &pin[1], &select[1]); texface_from_original_index(scene, + em->bm, offsets, origFace, origVertIndices[poly_corner_verts[2]], @@ -1003,6 +1017,7 @@ static ParamHandle *construct_param_handle_subsurfed(const Scene *scene, &pin[2], &select[2]); texface_from_original_index(scene, + em->bm, offsets, origFace, origVertIndices[poly_corner_verts[3]], @@ -1393,6 +1408,7 @@ static float uv_nearest_grid_tile_distance(const int udim_grid[2], } static bool island_has_pins(const Scene *scene, + const BMesh *bm, FaceIsland *island, const blender::geometry::UVPackIsland_Params *params) { @@ -1410,7 +1426,7 @@ static bool island_has_pins(const Scene *scene, if (BM_ELEM_CD_GET_BOOL(l, pin_offset)) { return true; } - if (pin_unselected && !uvedit_uv_select_test(scene, l, island->offsets)) { + if (pin_unselected && !uvedit_uv_select_test(scene, bm, l, island->offsets)) { return true; } } @@ -1484,7 +1500,7 @@ static void uvedit_pack_islands_multi(const Scene *scene, /* Remove from linked list and append to blender::Vector. */ LISTBASE_FOREACH_MUTABLE (FaceIsland *, island, &island_list) { BLI_remlink(&island_list, island); - const bool pinned = island_has_pins(scene, island, params); + const bool pinned = island_has_pins(scene, bm, island, params); if (ignore_pinned && pinned) { MEM_freeN(island->faces); MEM_freeN(island); @@ -2651,7 +2667,7 @@ static void uv_map_clip_correct(const Scene *scene, continue; } - if (only_selected_uvs && !uvedit_face_select_test(scene, efa, offsets)) { + if (only_selected_uvs && !uvedit_face_select_test(scene, em->bm, efa, offsets)) { continue; } @@ -2668,7 +2684,7 @@ static void uv_map_clip_correct(const Scene *scene, continue; } - if (only_selected_uvs && !uvedit_face_select_test(scene, efa, offsets)) { + if (only_selected_uvs && !uvedit_face_select_test(scene, em->bm, efa, offsets)) { continue; } @@ -2706,7 +2722,7 @@ static void uv_map_clip_correct(const Scene *scene, continue; } - if (only_selected_uvs && !uvedit_face_select_test(scene, efa, offsets)) { + if (only_selected_uvs && !uvedit_face_select_test(scene, em->bm, efa, offsets)) { continue; } @@ -3242,7 +3258,7 @@ static wmOperatorStatus smart_project_exec(bContext *C, wmOperator *op) } if (only_selected_uvs) { - if (!uvedit_face_select_test(scene, efa, offsets)) { + if (!uvedit_face_select_test(scene, em->bm, efa, offsets)) { uvedit_face_select_disable(scene, em->bm, efa, offsets); continue; } @@ -3835,7 +3851,7 @@ static float uv_sphere_project(const Scene *scene, } if (only_selected_uvs) { - if (!uvedit_face_select_test(scene, efa, offsets)) { + if (!uvedit_face_select_test(scene, bm, efa, offsets)) { uvedit_face_select_disable(scene, bm, efa, offsets); continue; /* Unselected UV, ignore. */ } @@ -4011,7 +4027,7 @@ static float uv_cylinder_project(const Scene *scene, } if (only_selected_uvs) { - if (!uvedit_face_select_test(scene, efa, offsets)) { + if (!uvedit_face_select_test(scene, bm, efa, offsets)) { uvedit_face_select_disable(scene, bm, efa, offsets); continue; /* Unselected UV, ignore. */ } @@ -4107,7 +4123,7 @@ static wmOperatorStatus cylinder_project_exec(bContext *C, wmOperator *op) continue; } - if (only_selected_uvs && !uvedit_face_select_test(scene, efa, offsets)) { + if (only_selected_uvs && !uvedit_face_select_test(scene, em->bm, efa, offsets)) { uvedit_face_select_disable(scene, em->bm, efa, offsets); continue; } @@ -4192,7 +4208,7 @@ static void uvedit_unwrap_cube_project(const Scene *scene, if (use_select && !BM_elem_flag_test(efa, BM_ELEM_SELECT)) { continue; } - if (only_selected_uvs && !uvedit_face_select_test(scene, efa, offsets)) { + if (only_selected_uvs && !uvedit_face_select_test(scene, bm, efa, offsets)) { uvedit_face_select_disable(scene, bm, efa, offsets); continue; } diff --git a/source/blender/geometry/intern/mesh_boolean.cc b/source/blender/geometry/intern/mesh_boolean.cc index fe3b58d5053..88b83123511 100644 --- a/source/blender/geometry/intern/mesh_boolean.cc +++ b/source/blender/geometry/intern/mesh_boolean.cc @@ -906,7 +906,7 @@ static Mesh *mesh_boolean_mesh_arr(Span meshes, * \{ */ /* has no meaning for faces, do this so we can tell which face is which */ -#define BM_FACE_TAG BM_ELEM_DRAW +#define BM_FACE_TAG BM_ELEM_SELECT_UV /** * Function use to say what operand a face is part of, based on the `BM_FACE_TAG` diff --git a/source/blender/makesdna/DNA_scene_defaults.h b/source/blender/makesdna/DNA_scene_defaults.h index ae563565076..b3cf4367e02 100644 --- a/source/blender/makesdna/DNA_scene_defaults.h +++ b/source/blender/makesdna/DNA_scene_defaults.h @@ -377,6 +377,7 @@ .select_thresh = 0.01f, \ \ .selectmode = SCE_SELECT_VERTEX, \ + .uv_flag = UV_FLAG_SELECT_SYNC, \ .uv_selectmode = UV_SELECT_VERT, \ .autokey_mode = AUTOKEY_MODE_NORMAL, \ \ diff --git a/source/blender/modifiers/intern/MOD_boolean.cc b/source/blender/modifiers/intern/MOD_boolean.cc index 34efc7fa0db..ebe890b1ed4 100644 --- a/source/blender/modifiers/intern/MOD_boolean.cc +++ b/source/blender/modifiers/intern/MOD_boolean.cc @@ -156,7 +156,7 @@ static Mesh *get_quick_mesh( } /* has no meaning for faces, do this so we can tell which face is which */ -#define BM_FACE_TAG BM_ELEM_DRAW +#define BM_FACE_TAG BM_ELEM_SELECT_UV /** * Compare selected/unselected. diff --git a/source/blender/python/bmesh/bmesh_py_types.cc b/source/blender/python/bmesh/bmesh_py_types.cc index f2d8c0ac502..fc2673382a5 100644 --- a/source/blender/python/bmesh/bmesh_py_types.cc +++ b/source/blender/python/bmesh/bmesh_py_types.cc @@ -15,6 +15,7 @@ #include "DNA_material_types.h" #include "DNA_mesh_types.h" #include "DNA_object_types.h" +#include "DNA_scene_types.h" #include "BKE_customdata.hh" #include "BKE_global.hh" @@ -86,6 +87,15 @@ PyC_FlagSet bpy_bm_hflag_all_flags[] = { {0, nullptr}, }; +/* This could/should be shared with `scene.toolsettings.uv_sticky_select_mode`. + * however it relies on using the RNA API. */ +static PyC_StringEnumItems bpy_bm_uv_select_sticky_items[] = { + {UV_STICKY_LOCATION, "SHARED_LOCATION"}, + {UV_STICKY_DISABLE, "DISABLED"}, + {UV_STICKY_VERT, "SHARED_VERTEX"}, + {0, nullptr}, +}; + /* py-type definitions * ******************* */ @@ -125,6 +135,12 @@ PyDoc_STRVAR( "Seam for UV unwrapping.\n" "\n" ":type: bool\n"); +PyDoc_STRVAR( + /* Wrap. */ + bpy_bm_elem_uv_select_doc, + "UV selected state of this element.\n" + "\n" + ":type: bool\n"); static PyObject *bpy_bm_elem_hflag_get(BPy_BMElem *self, void *flag) { @@ -404,6 +420,34 @@ static int bpy_bmesh_select_history_set(BPy_BMesh *self, PyObject *value, void * return BPy_BMEditSel_Assign(self, value); } +PyDoc_STRVAR( + /* Wrap. */ + bpy_bmesh_uv_select_sync_valid_doc, + "When true, the UV selection has been synchronized. " + "Setting to False means the UV selection will be ignored. " + "While setting to true is supported it is up to the script author to " + "ensure a correct selection state before doing so.\n" + ":type: " + "bool\n"); +static PyObject *bpy_bmesh_uv_select_sync_valid_get(BPy_BMesh *self, void * /*closure*/) +{ + BPY_BM_CHECK_OBJ(self); + + return PyBool_FromLong(self->bm->uv_select_sync_valid); +} + +static int bpy_bmesh_uv_select_sync_valid_set(BPy_BMesh *self, PyObject *value, void * /*closure*/) +{ + BPY_BM_CHECK_INT(self); + + int param; + if ((param = PyC_Long_AsBool(value)) == -1) { + return -1; + } + self->bm->uv_select_sync_valid = param; + return 0; +} + /* Vert * ^^^^ */ @@ -808,6 +852,12 @@ static PyGetSetDef bpy_bmesh_getseters[] = { bpy_bmesh_select_history_doc, nullptr}, + {"uv_select_sync_valid", + (getter)bpy_bmesh_uv_select_sync_valid_get, + (setter)bpy_bmesh_uv_select_sync_valid_set, + bpy_bmesh_uv_select_sync_valid_doc, + nullptr}, + /* readonly checks */ {"is_wrapped", (getter)bpy_bmesh_is_wrapped_get, @@ -979,6 +1029,11 @@ static PyGetSetDef bpy_bmface_getseters[] = { (setter)bpy_bm_elem_hflag_set, bpy_bm_elem_tag_doc, (void *)BM_ELEM_TAG}, + {"uv_select", + (getter)bpy_bm_elem_hflag_get, + (setter)bpy_bm_elem_hflag_set, + bpy_bm_elem_uv_select_doc, + (void *)BM_ELEM_SELECT_UV}, {"index", (getter)bpy_bm_elem_index_get, (setter)bpy_bm_elem_index_set, @@ -1046,6 +1101,16 @@ static PyGetSetDef bpy_bmloop_getseters[] = { (setter)bpy_bm_elem_hflag_set, bpy_bm_elem_tag_doc, (void *)BM_ELEM_TAG}, + {"uv_select_vert", + (getter)bpy_bm_elem_hflag_get, + (setter)bpy_bm_elem_hflag_set, + bpy_bm_elem_uv_select_doc, + (void *)BM_ELEM_SELECT_UV}, + {"uv_select_edge", + (getter)bpy_bm_elem_hflag_get, + (setter)bpy_bm_elem_hflag_set, + bpy_bm_elem_uv_select_doc, + (void *)BM_ELEM_SELECT_UV_EDGE}, {"index", (getter)bpy_bm_elem_index_get, (setter)bpy_bm_elem_index_set, @@ -1500,6 +1565,492 @@ static PyObject *bpy_bmesh_select_flush(BPy_BMesh *self, PyObject *value) Py_RETURN_NONE; } +/* ---------------------------------------------------------------------- */ +/** \name UV Sync Selection + * \{ */ + +PyDoc_STRVAR( + /* Wrap. */ + bpy_bmesh_uv_select_flush_mode_doc, + ".. method:: uv_select_flush_mode(flush_down=False)\n" + "\n" + " Flush selection based on the current mode current :class:`BMesh.select_mode`.\n" + "\n" + " :arg flush_down: Flush selection down from faces to edges & verts or from edges to verts. " + "This option is ignored when vertex selection mode is enabled.\n" + " :type flush_down: bool\n"); +static PyObject *bpy_bmesh_uv_select_flush_mode(BPy_BMesh *self, PyObject *args, PyObject *kw) +{ + BPY_BM_CHECK_OBJ(self); + BMesh *bm = self->bm; + + bool flush_down = false; + static const char *kwlist[] = { + "flush_down", + nullptr, + }; + if (!PyArg_ParseTupleAndKeywords(args, + kw, + "|$" + "O&" /* `flush_down` */ + ":uv_select_flush_mode", + (char **)kwlist, + PyC_ParseBool, + &flush_down)) + { + return nullptr; + } + + BM_mesh_uvselect_mode_flush_ex(bm, bm->selectmode, flush_down); + Py_RETURN_NONE; +} + +PyDoc_STRVAR( + /* Wrap. */ + bpy_bmesh_uv_select_flush_doc, + ".. method:: uv_select_flush(select)\n" + "\n" + " Flush selection from UV vertices to edges & faces independent of the selection mode.\n" + "\n" + " :arg select: Flush selection or de-selected elements.\n" + " :type select: bool\n" + "\n" + " .. note::\n" + "\n" + " - |UV_SELECT_SYNC_TO_MESH_NEEDED|\n"); +static PyObject *bpy_bmesh_uv_select_flush(BPy_BMesh *self, PyObject *value) +{ + const char *error_prefix = "uv_select_flush(...)"; + int param; + + BPY_BM_CHECK_OBJ(self); + + if ((param = PyC_Long_AsBool(value)) == -1) { + return nullptr; + } + BMesh *bm = self->bm; + /* While sync doesn't need to be valid, + * failing to make it valid causes selection functions to assert, so require it to be valid. */ + if (bpy_bm_check_uv_select_sync_valid(bm, error_prefix) == -1) { + return nullptr; + } + BM_mesh_uvselect_flush_from_verts(bm, param); + Py_RETURN_NONE; +} + +PyDoc_STRVAR( + /* Wrap. */ + bpy_bmesh_uv_select_flush_shared_doc, + ".. method:: uv_select_flush_shared(select)\n" + "\n" + " Flush selection from UV vertices to contiguous UV's independent of the selection mode.\n" + "\n" + " :arg select: Flush selection or de-selected elements.\n" + " :type select: bool\n" + "\n" + " .. note::\n" + "\n" + " - |UV_SELECT_SYNC_TO_MESH_NEEDED|\n"); +static PyObject *bpy_bmesh_uv_select_flush_shared(BPy_BMesh *self, PyObject *value) +{ + const char *error_prefix = "uv_select_flush_shared(...)"; + int param; + + BPY_BM_CHECK_OBJ(self); + + if ((param = PyC_Long_AsBool(value)) == -1) { + return nullptr; + } + BMesh *bm = self->bm; + /* While sync doesn't need to be valid, + * failing to make it valid causes selection functions to assert, so require it to be valid. */ + if (bpy_bm_check_uv_select_sync_valid(bm, error_prefix) == -1) { + return nullptr; + } + if (param) { + BM_mesh_uvselect_flush_shared_only_select(bm, param); + } + else { + BM_mesh_uvselect_flush_shared_only_deselect(bm, param); + } + Py_RETURN_NONE; +} + +PyDoc_STRVAR( + /* Wrap. */ + bpy_bmesh_uv_select_sync_from_mesh_doc, + ".. method:: uv_select_sync_from_mesh(/, *, " + "sticky_select_mode='SHARED_LOCATION')\n" + "\n" + " Sync selection from mesh to UVs.\n" + "\n" + " :arg sticky_select_mode: Behavior when flushing from the mesh to UV selection " + "|UV_STICKY_SELECT_MODE_REF|. " + "This should only be used when preparing to create a UV selection.\n" + " :type sticky_select_mode: |UV_STICKY_SELECT_MODE_TYPE|\n" + "\n" + " .. note::\n" + "\n" + " - |UV_SELECT_SYNC_TO_MESH_NEEDED|\n"); +static PyObject *bpy_bmesh_uv_select_sync_from_mesh(BPy_BMesh *self, PyObject *args, PyObject *kw) +{ + static const char *kwlist[] = { + "sticky_select_mode", + nullptr, + }; + + BPY_BM_CHECK_OBJ(self); + + PyC_StringEnum uv_sticky_select_mode = {bpy_bm_uv_select_sticky_items, UV_STICKY_LOCATION}; + + if (!PyArg_ParseTupleAndKeywords(args, + kw, + "|$" /* Optional keyword only arguments. */ + "O&" /* `sticky_select_mode` */ + ":uv_select_sync_from_mesh", + (char **)kwlist, + PyC_ParseStringEnum, + &uv_sticky_select_mode)) + { + return nullptr; + } + + BMesh *bm = self->bm; + switch (uv_sticky_select_mode.value_found) { + case UV_STICKY_LOCATION: { + const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_PROP_FLOAT2); + if (cd_loop_uv_offset == -1) { + PyErr_SetString(PyExc_ValueError, "sticky_select_mode='SHARED_LOCATION' requires UV's"); + return nullptr; + } + BM_mesh_uvselect_sync_from_mesh_sticky_location(bm, cd_loop_uv_offset); + break; + } + case UV_STICKY_DISABLE: { + BM_mesh_uvselect_sync_from_mesh_sticky_disabled(bm); + break; + } + case UV_STICKY_VERT: { + BM_mesh_uvselect_sync_from_mesh_sticky_vert(bm); + break; + } + } + + Py_RETURN_NONE; +} + +PyDoc_STRVAR( + /* Wrap. */ + bpy_bmesh_uv_select_sync_to_mesh_doc, + ".. method:: uv_select_sync_to_mesh()\n" + "\n" + " Sync selection from UVs to the mesh.\n"); +static PyObject *bpy_bmesh_uv_select_sync_to_mesh(BPy_BMesh *self) +{ + const char *error_prefix = "uv_select_sync_to_mesh(...)"; + + BPY_BM_CHECK_OBJ(self); + + BMesh *bm = self->bm; + if (bpy_bm_check_uv_select_sync_valid(bm, error_prefix) == -1) { + return nullptr; + } + BM_mesh_uvselect_sync_to_mesh(bm); + Py_RETURN_NONE; +} + +PyDoc_STRVAR( + /* Wrap. */ + bpy_bmesh_uv_select_foreach_set_doc, + ".. method:: uv_select_foreach_set(select, /, *, " + "loop_verts=(), loop_edges=(), faces=(), sticky_select_mode='SHARED_LOCATION')\n" + "\n" + " Set the UV selection state for loop-vertices, loop-edges & faces.\n" + "\n" + " This is a close equivalent to selecting in the UV editor.\n" + "\n" + " :arg select: The selection state to set.\n" + " :type select: bool\n" + " :arg loop_verts: Loop verts to operate on.\n" + " :type loop_verts: Iterable[:class:`bmesh.types.BMLoop`]\n" + " :arg loop_edges: Loop edges to operate on.\n" + " :type loop_edges: Iterable[:class:`bmesh.types.BMLoop`]\n" + " :arg faces: Faces to operate on.\n" + " :type faces: Iterable[:class:`bmesh.types.BMFace`]\n" + " :arg sticky_select_mode: See |UV_STICKY_SELECT_MODE_REF|.\n" + " :type sticky_select_mode: |UV_STICKY_SELECT_MODE_TYPE|\n" + "\n" + " .. note::\n" + "\n" + " - |UV_SELECT_FLUSH_MODE_NEEDED|\n" + " - |UV_SELECT_SYNC_TO_MESH_NEEDED|\n"); +static PyObject *bpy_bmesh_uv_select_foreach_set(BPy_BMesh *self, PyObject *args, PyObject *kw) +{ + const char *error_prefix = "uv_select_foreach_set(...)"; + static const char *kwlist[] = { + "", /* `select` */ + "loop_verts", + "loop_edges", + "faces", + "sticky_select_mode", + nullptr, + }; + BMesh *bm; + bool use_select = false; + PyObject *py_loop_verts = nullptr; + PyObject *py_loop_edges = nullptr; + PyObject *py_faces = nullptr; + PyC_StringEnum uv_sticky_select_mode = {bpy_bm_uv_select_sticky_items, UV_STICKY_LOCATION}; + + BPY_BM_CHECK_OBJ(self); + + if (!PyArg_ParseTupleAndKeywords(args, + kw, + "O&" /* `select` */ + "|$" /* Optional keyword only arguments. */ + "O" /* `loop_verts` */ + "O" /* `loop_edges` */ + "O" /* `faces` */ + "O&" /* `sticky_select_mode` */ + ":uv_select_foreach_set", + (char **)kwlist, + PyC_ParseBool, + &use_select, + &py_loop_verts, + &py_loop_edges, + &py_faces, + PyC_ParseStringEnum, + &uv_sticky_select_mode)) + { + return nullptr; + } + + bm = self->bm; + if (bpy_bm_check_uv_select_sync_valid(bm, error_prefix) == -1) { + return nullptr; + } + const bool shared = uv_sticky_select_mode.value_found == UV_STICKY_LOCATION; + const int cd_loop_uv_offset = shared ? bpy_bm_uv_layer_offset_or_error(bm, error_prefix) : -1; + if (shared && (cd_loop_uv_offset == -1)) { + return nullptr; + } + + Py_ssize_t loop_vert_array_num = 0; + Py_ssize_t loop_edge_array_num = 0; + Py_ssize_t face_array_num = 0; + BMLoop **loop_vert_array = nullptr; + BMLoop **loop_edge_array = nullptr; + BMFace **face_array = nullptr; + + bool ok = true; + if (ok && py_loop_verts) { + BMesh *bm_test = nullptr; + if (!(loop_vert_array = BPy_BMLoop_PySeq_As_Array(&bm_test, + py_loop_verts, + 0, + PY_SSIZE_T_MAX, + &loop_vert_array_num, + true, + true, + error_prefix))) + { + ok = false; + } + else if (bm_test && bpy_bm_check_bm_match_or_error(bm, bm_test, error_prefix) == -1) { + ok = false; + } + } + if (ok && py_loop_edges) { + BMesh *bm_test = nullptr; + if (!(loop_edge_array = BPy_BMLoop_PySeq_As_Array(&bm_test, + py_loop_edges, + 0, + PY_SSIZE_T_MAX, + &loop_edge_array_num, + true, + true, + error_prefix))) + { + ok = false; + } + else if (bm_test && bpy_bm_check_bm_match_or_error(bm, bm_test, error_prefix) == -1) { + ok = false; + } + } + if (ok && py_faces) { + BMesh *bm_test = nullptr; + if (!(face_array = BPy_BMFace_PySeq_As_Array( + &bm_test, py_faces, 0, PY_SSIZE_T_MAX, &face_array_num, true, true, error_prefix))) + { + ok = false; + } + else if (bm_test && bpy_bm_check_bm_match_or_error(bm, bm_test, error_prefix) == -1) { + ok = false; + } + } + + /* TODO: support different "sticky" modes. */ + if (ok) { + BM_mesh_uvselect_set_elem_shared(bm, + use_select, + cd_loop_uv_offset, + blender::Span(loop_vert_array, loop_vert_array_num), + blender::Span(loop_edge_array, loop_edge_array_num), + blender::Span(face_array, face_array_num)); + } + + PyMem_FREE(loop_vert_array); + PyMem_FREE(loop_edge_array); + PyMem_FREE(face_array); + + if (ok == false) { + /* The error has been raised. */ + return nullptr; + } + Py_RETURN_NONE; +} + +PyDoc_STRVAR( + /* Wrap. */ + bpy_bmesh_uv_select_foreach_set_from_mesh_doc, + ".. method:: uv_select_foreach_set_from_mesh(select, /, *, " + "verts=(), edges=(), faces=(), sticky_select_mode='SHARED_LOCATION')\n" + "\n" + " Select or de-select mesh elements, updating the UV selection.\n" + "\n" + " An equivalent to selecting from the 3D viewport " + "for selection operations that support maintaining a synchronized UV selection.\n" + "\n" + " :arg select: The selection state to set.\n" + " :type select: bool\n" + " :arg verts: Verts to operate on.\n" + " :type verts: Iterable[:class:`bmesh.types.BMVert`]\n" + " :arg edges: Edges to operate on.\n" + " :type edges: Iterable[:class:`bmesh.types.BMEdge`]\n" + " :arg faces: Faces to operate on.\n" + " :type faces: Iterable[:class:`bmesh.types.BMFace`]\n" + " :arg sticky_select_mode: See |UV_STICKY_SELECT_MODE_REF|.\n" + " :type sticky_select_mode: |UV_STICKY_SELECT_MODE_TYPE|\n"); +static PyObject *bpy_bmesh_uv_select_foreach_set_from_mesh(BPy_BMesh *self, + PyObject *args, + PyObject *kw) +{ + const char *error_prefix = "uv_select_foreach_set_from_mesh(...)"; + static const char *kwlist[] = { + "", /* `select` */ + "verts", + "edges", + "faces", + "sticky_select_mode", + nullptr, + }; + bool use_select = false; + PyObject *py_verts = nullptr; + PyObject *py_edges = nullptr; + PyObject *py_faces = nullptr; + PyC_StringEnum uv_sticky_select_mode = {bpy_bm_uv_select_sticky_items, UV_STICKY_LOCATION}; + + BPY_BM_CHECK_OBJ(self); + + if (!PyArg_ParseTupleAndKeywords(args, + kw, + "O&" /* `select` */ + "|$" /* Optional keyword only arguments. */ + "O" /* `verts` */ + "O" /* `edges` */ + "O" /* `faces` */ + "O&" /* `sticky_select_mode` */ + ":uv_select_foreach_set_from_mesh", + (char **)kwlist, + PyC_ParseBool, + &use_select, + &py_verts, + &py_edges, + &py_faces, + PyC_ParseStringEnum, + &uv_sticky_select_mode)) + { + return nullptr; + } + + BMesh *bm = self->bm; + if (bpy_bm_check_uv_select_sync_valid(bm, error_prefix) == -1) { + return nullptr; + } + const bool shared = uv_sticky_select_mode.value_found == UV_STICKY_LOCATION; + const int cd_loop_uv_offset = shared ? bpy_bm_uv_layer_offset_or_error(bm, error_prefix) : -1; + if (shared && (cd_loop_uv_offset == -1)) { + return nullptr; + } + + Py_ssize_t vert_array_num = 0; + Py_ssize_t edge_array_num = 0; + Py_ssize_t face_array_num = 0; + BMVert **vert_array = nullptr; + BMEdge **edge_array = nullptr; + BMFace **face_array = nullptr; + + bool ok = true; + if (ok && py_verts) { + BMesh *bm_test = nullptr; + if (!(vert_array = BPy_BMVert_PySeq_As_Array( + &bm_test, py_verts, 0, PY_SSIZE_T_MAX, &vert_array_num, true, true, error_prefix))) + { + ok = false; + } + else if (bm_test && bpy_bm_check_bm_match_or_error(bm, bm_test, error_prefix) == -1) { + ok = false; + } + } + if (ok && py_edges) { + BMesh *bm_test = nullptr; + if (!(edge_array = BPy_BMEdge_PySeq_As_Array( + &bm_test, py_edges, 0, PY_SSIZE_T_MAX, &edge_array_num, true, true, error_prefix))) + { + ok = false; + } + else if (bm_test && bpy_bm_check_bm_match_or_error(bm, bm_test, error_prefix) == -1) { + ok = false; + } + } + if (ok && py_faces) { + BMesh *bm_test = nullptr; + if (!(face_array = BPy_BMFace_PySeq_As_Array( + &bm_test, py_faces, 0, PY_SSIZE_T_MAX, &face_array_num, true, true, error_prefix))) + { + ok = false; + } + else if (bm_test && bpy_bm_check_bm_match_or_error(bm, bm_test, error_prefix) == -1) { + ok = false; + } + } + + if (ok) { + const BMUVSelectPickParams uv_pick_params = { + /*cd_loop_uv_offset*/ cd_loop_uv_offset, + /*shared*/ shared, + }; + BM_mesh_uvselect_set_elem_from_mesh(bm, + use_select, + uv_pick_params, + blender::Span(vert_array, vert_array_num), + blender::Span(edge_array, edge_array_num), + blender::Span(face_array, face_array_num)); + } + + PyMem_FREE(vert_array); + PyMem_FREE(edge_array); + PyMem_FREE(face_array); + + if (ok == false) { + /* The error has been raised. */ + return nullptr; + } + Py_RETURN_NONE; +} + +/** \} */ + PyDoc_STRVAR( /* Wrap. */ bpy_bmesh_normal_update_doc, @@ -2185,6 +2736,34 @@ static PyObject *bpy_bmface_copy(BPy_BMFace *self, PyObject *args, PyObject *kw) return nullptr; } +PyDoc_STRVAR( + /* Wrap. */ + bpy_bmface_uv_select_set_doc, + ".. method:: uv_select_set(select)\n" + "\n" + " Select the face.\n" + "\n" + " :arg select: Select or de-select.\n" + " :type select: bool\n" + "\n" + " .. note::\n" + "\n" + " Currently this only flushes down, so selecting a face will select all its " + "vertices but de-selecting a vertex " + " won't de-select all the faces that use it, before finishing with a mesh " + "typically flushing is still needed.\n"); +static PyObject *bpy_bmface_uv_select_set(BPy_BMFace *self, PyObject *value) +{ + BMesh *bm = self->bm; + BPY_BM_CHECK_OBJ(self); + int param; + if ((param = PyC_Long_AsBool(value)) == -1) { + return nullptr; + } + BM_face_uvselect_set(bm, self->f, param); + Py_RETURN_NONE; +} + PyDoc_STRVAR( /* Wrap. */ bpy_bmface_calc_area_doc, @@ -2423,6 +3002,65 @@ static PyObject *bpy_bmloop_copy_from_face_interp(BPy_BMLoop *self, PyObject *ar Py_RETURN_NONE; } +PyDoc_STRVAR( + /* Wrap. */ + bpy_bmloop_uv_select_vert_set_doc, + ".. method:: uv_select_vert_set(select)\n" + "\n" + " Select the UV vertex.\n" + "\n" + " :arg select: Select or de-select.\n" + " :type select: bool\n" + "\n" + " .. note::\n" + "\n" + " Currently this only flushes down, so selecting an edge will select all its " + "vertices but de-selecting a vertex " + " won't de-select the edges & faces that use it, before finishing with a mesh " + "typically flushing with :class:`bmesh.types.BMesh.uv_select_flush_mode` is still needed.\n"); +static PyObject *bpy_bmloop_uv_select_vert_set(BPy_BMLoop *self, PyObject *value) +{ + BMesh *bm = self->bm; + BPY_BM_CHECK_OBJ(self); + int param; + if ((param = PyC_Long_AsBool(value)) == -1) { + return nullptr; + } + + /* There is no flushing version of this function. */ + BM_loop_vert_uvselect_set_noflush(bm, self->l, param); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR( + /* Wrap. */ + bpy_bmloop_uv_select_edge_set_doc, + ".. method:: uv_select_edge_set(select)\n" + "\n" + " Set the UV edge selection state.\n" + "\n" + " :arg select: Select or de-select.\n" + " :type select: bool\n" + "\n" + " .. note::\n" + "\n" + " This only flushes down, so selecting an edge will select all its " + "vertices but de-selecting a vertex " + "won't de-select the faces that use it, before finishing with a mesh " + "typically flushing with :class:`bmesh.types.BMesh.uv_select_flush_mode` is still needed.\n"); +static PyObject *bpy_bmloop_uv_select_edge_set(BPy_BMLoop *self, PyObject *value) +{ + BMesh *bm = self->bm; + BPY_BM_CHECK_OBJ(self); + int param; + if ((param = PyC_Long_AsBool(value)) == -1) { + return nullptr; + } + BM_loop_edge_uvselect_set(bm, self->l, param); + Py_RETURN_NONE; +} + PyDoc_STRVAR( /* Wrap. */ bpy_bmloop_calc_angle_doc, @@ -3196,12 +3834,45 @@ static PyMethodDef bpy_bmesh_methods[] = { bpy_bmesh_from_mesh_doc}, {"to_mesh", (PyCFunction)bpy_bmesh_to_mesh, METH_VARARGS, bpy_bmesh_to_mesh_doc}, - /* meshdata */ + /* Mesh select methods. */ {"select_flush_mode", (PyCFunction)bpy_bmesh_select_flush_mode, METH_VARARGS | METH_KEYWORDS, bpy_bmesh_select_flush_mode_doc}, {"select_flush", (PyCFunction)bpy_bmesh_select_flush, METH_O, bpy_bmesh_select_flush_doc}, + + /* UV select methods. */ + {"uv_select_flush_mode", + (PyCFunction)bpy_bmesh_uv_select_flush_mode, + METH_VARARGS | METH_KEYWORDS, + bpy_bmesh_uv_select_flush_mode_doc}, + {"uv_select_flush", + (PyCFunction)bpy_bmesh_uv_select_flush, + METH_O, + bpy_bmesh_uv_select_flush_doc}, + {"uv_select_flush_shared", + (PyCFunction)bpy_bmesh_uv_select_flush_shared, + METH_O, + bpy_bmesh_uv_select_flush_shared_doc}, + + {"uv_select_sync_from_mesh", + (PyCFunction)bpy_bmesh_uv_select_sync_from_mesh, + METH_VARARGS | METH_KEYWORDS, + bpy_bmesh_uv_select_sync_from_mesh_doc}, + {"uv_select_sync_to_mesh", + (PyCFunction)bpy_bmesh_uv_select_sync_to_mesh, + METH_NOARGS, + bpy_bmesh_uv_select_sync_to_mesh_doc}, + {"uv_select_foreach_set", + (PyCFunction)bpy_bmesh_uv_select_foreach_set, + METH_VARARGS | METH_KEYWORDS, + bpy_bmesh_uv_select_foreach_set_doc}, + {"uv_select_foreach_set_from_mesh", + (PyCFunction)bpy_bmesh_uv_select_foreach_set_from_mesh, + METH_VARARGS | METH_KEYWORDS, + bpy_bmesh_uv_select_foreach_set_from_mesh_doc}, + + /* meshdata */ {"normal_update", (PyCFunction)bpy_bmesh_normal_update, METH_NOARGS, @@ -3294,6 +3965,8 @@ static PyMethodDef bpy_bmface_methods[] = { {"copy", (PyCFunction)bpy_bmface_copy, METH_VARARGS | METH_KEYWORDS, bpy_bmface_copy_doc}, + {"uv_select_set", (PyCFunction)bpy_bmface_uv_select_set, METH_O, bpy_bmface_uv_select_set_doc}, + {"calc_area", (PyCFunction)bpy_bmface_calc_area, METH_NOARGS, bpy_bmface_calc_area_doc}, {"calc_perimeter", (PyCFunction)bpy_bmface_calc_perimeter, @@ -3344,6 +4017,15 @@ static PyMethodDef bpy_bmloop_methods[] = { METH_VARARGS, bpy_bmloop_copy_from_face_interp_doc}, + {"uv_select_vert_set", + (PyCFunction)bpy_bmloop_uv_select_vert_set, + METH_O, + bpy_bmloop_uv_select_vert_set_doc}, + {"uv_select_edge_set", + (PyCFunction)bpy_bmloop_uv_select_edge_set, + METH_O, + bpy_bmloop_uv_select_edge_set_doc}, + {"calc_angle", (PyCFunction)bpy_bmloop_calc_angle, METH_NOARGS, bpy_bmloop_calc_angle_doc}, {"calc_normal", (PyCFunction)bpy_bmloop_calc_normal, METH_NOARGS, bpy_bmloop_calc_normal_doc}, {"calc_tangent", @@ -4164,10 +4846,28 @@ void BPy_BM_init_types() /* bmesh.types submodule * ********************* */ +/* This exists to declare substitutions. */ +PyDoc_STRVAR( + /* Wrap. */ + BPy_BM_types_module_doc, + "\n" + ".. |UV_STICKY_SELECT_MODE_REF| replace:: " + "(:class:`bpy.types.ToolSettings.uv_sticky_select_mode` which may be passed in directly).\n" + "\n" + ".. |UV_STICKY_SELECT_MODE_TYPE| replace:: " + "Literal['SHARED_LOCATION', 'DISABLED', 'SHARED_VERTEX']\n" + "\n" + ".. |UV_SELECT_FLUSH_MODE_NEEDED| replace:: " + "This function selection-mode independent, " + "typically :class:`bmesh.types.BMesh.uv_select_flush_mode` should be called afterwards.\n" + "\n" + ".. |UV_SELECT_SYNC_TO_MESH_NEEDED| replace:: " + "This function doesn't flush the selection to the mesh, " + "typically :class:`bmesh.types.BMesh.uv_select_sync_to_mesh` should be called afterwards.\n"); static PyModuleDef BPy_BM_types_module_def = { /*m_base*/ PyModuleDef_HEAD_INIT, /*m_name*/ "bmesh.types", - /*m_doc*/ nullptr, + /*m_doc*/ BPy_BM_types_module_doc, /*m_size*/ 0, /*m_methods*/ nullptr, /*m_slots*/ nullptr, @@ -4479,6 +5179,34 @@ int bpy_bm_generic_valid_check_source(BMesh *bm_source, return ret; } +int bpy_bm_check_uv_select_sync_valid(BMesh *bm, const char *error_prefix) +{ + int ret = 0; + if (bm->uv_select_sync_valid == false) { + PyErr_Format(PyExc_ValueError, "%s: bm.uv_select_sync_valid: must be true", error_prefix); + ret = -1; + } + return ret; +} + +int bpy_bm_uv_layer_offset_or_error(BMesh *bm, const char *error_prefix) +{ + const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_PROP_FLOAT2); + if (cd_loop_uv_offset == -1) { + PyErr_Format(PyExc_ValueError, "%s: failed, no UV layer found", error_prefix); + } + return cd_loop_uv_offset; +} + +int bpy_bm_check_bm_match_or_error(BMesh *bm_a, BMesh *bm_b, const char *error_prefix) +{ + if (bm_a != bm_b) { + PyErr_Format(PyExc_ValueError, "%s: elements must be from a singe BMesh", error_prefix); + return -1; + } + return 0; +} + void bpy_bm_generic_invalidate(BPy_BMGeneric *self) { self->bm = nullptr; diff --git a/source/blender/python/bmesh/bmesh_py_types.hh b/source/blender/python/bmesh/bmesh_py_types.hh index 49bb3205967..e97e1077f7c 100644 --- a/source/blender/python/bmesh/bmesh_py_types.hh +++ b/source/blender/python/bmesh/bmesh_py_types.hh @@ -228,6 +228,11 @@ enum { const char *error_prefix, void **args, uint args_tot) ATTR_NONNULL(1, 2); +[[nodiscard]] int bpy_bm_check_uv_select_sync_valid(BMesh *bm, const char *error_prefix); +[[nodiscard]] int bpy_bm_uv_layer_offset_or_error(BMesh *bm, const char *error_prefix); +[[nodiscard]] int bpy_bm_check_bm_match_or_error(BMesh *bm_a, + BMesh *bm_b, + const char *error_prefix); #define BPY_BM_CHECK_OBJ(obj) \ if (UNLIKELY(bpy_bm_generic_valid_check((BPy_BMGeneric *)obj) == -1)) { \ diff --git a/source/blender/python/bmesh/bmesh_py_utils.cc b/source/blender/python/bmesh/bmesh_py_utils.cc index 8ec1e03afe8..345e7ae6d14 100644 --- a/source/blender/python/bmesh/bmesh_py_utils.cc +++ b/source/blender/python/bmesh/bmesh_py_utils.cc @@ -23,6 +23,7 @@ #include "bmesh_py_utils.hh" /* own include */ #include "../generic/py_capi_utils.hh" +#include "../generic/python_compat.hh" #include "../generic/python_utildefines.hh" PyDoc_STRVAR( @@ -764,6 +765,134 @@ static PyObject *bpy_bm_utils_loop_separate(PyObject * /*self*/, BPy_BMLoop *val Py_RETURN_NONE; } +PyDoc_STRVAR( + /* Wrap. */ + bpy_bm_utils_uv_select_check_doc, + ".. method:: uv_select_check(bm, /, *, sync=True, flush=False, contiguous=False)\n" + "\n" + " Split an edge, return the newly created data.\n" + "\n" + " :arg sync: Check the data is properly synchronized between UV's and the underlying mesh. " + "Failure to synchronize with the mesh selection may cause tools not to behave properly.\n" + " :type sync: bool\n" + " :arg flush: Check the selection has been properly flushed between elements " + "(based on the current :class:`BMesh.select_mode`).\n" + " :type flush: bool\n" + " :arg contiguous: Check connected UV's and edges have a matching selection state.\n" + " :type contiguous: bool\n" + " :return: An error dictionary or None when there are no errors found.\n" + " :rtype: dict[str, int] | None\n"); +static PyObject *bpy_bm_utils_uv_select_check(PyObject * /*self*/, PyObject *args, PyObject *kwds) +{ + const char *error_prefix = "uv_select_check(...)"; + BPy_BMesh *py_bm; + bool check_sync = true; + bool check_contiguous = false; + bool check_flush = false; + + static const char *_keywords[] = { + "", + "sync", + "flush", + "contiguous", + nullptr, + }; + static _PyArg_Parser _parser = { + PY_ARG_PARSER_HEAD_COMPAT() + "O!" /* `bm` */ + "|$" /* Optional keyword only arguments. */ + "O&" /* `sync` */ + "O&" /* `flush` */ + "O&" /* `contiguous` */ + ":uv_select_check", + _keywords, + nullptr, + }; + if (!_PyArg_ParseTupleAndKeywordsFast(args, + kwds, + &_parser, + &BPy_BMesh_Type, + &py_bm, + PyC_ParseBool, + &check_sync, + PyC_ParseBool, + &check_flush, + PyC_ParseBool, + &check_contiguous)) + { + return nullptr; + } + + BPY_BM_CHECK_OBJ(py_bm); + + BMesh *bm = py_bm->bm; + if (check_sync) { + if (bpy_bm_check_uv_select_sync_valid(bm, error_prefix) == -1) { + return nullptr; + } + } + + const int cd_loop_uv_offset = check_contiguous ? + CustomData_get_offset(&bm->ldata, CD_PROP_FLOAT2) : + -1; + if (check_contiguous) { + if (cd_loop_uv_offset == -1) { + PyErr_SetString(PyExc_ValueError, "contiguous=True for a mesh without UV coordinates"); + return nullptr; + } + } + + UVSelectValidateInfo info = {}; + const bool is_valid = BM_mesh_uvselect_is_valid( + bm, cd_loop_uv_offset, check_sync, check_flush, check_contiguous, &info); + if (is_valid) { + Py_RETURN_NONE; + } + + PyObject *result = PyDict_New(); + +#define DICT_ADD_INT_MEMBER(info_struct, member) \ + PyDict_SetItemString(result, STRINGIFY(member), PyLong_FromLong(info_struct.member)) + + { + UVSelectValidateInfo_Sync &info_sub = info.sync; + DICT_ADD_INT_MEMBER(info_sub, count_uv_vert_any_selected_with_vert_unselected); + DICT_ADD_INT_MEMBER(info_sub, count_uv_vert_none_selected_with_vert_selected); + + DICT_ADD_INT_MEMBER(info_sub, count_uv_edge_any_selected_with_edge_unselected); + DICT_ADD_INT_MEMBER(info_sub, count_uv_edge_none_selected_with_edge_selected); + } + + if (check_flush) { + UVSelectValidateInfo_Flush &info_sub = info.flush; + DICT_ADD_INT_MEMBER(info_sub, count_uv_edge_selected_with_any_verts_unselected); + DICT_ADD_INT_MEMBER(info_sub, count_uv_edge_unselected_with_all_verts_selected); + + DICT_ADD_INT_MEMBER(info_sub, count_uv_face_selected_with_any_verts_unselected); + DICT_ADD_INT_MEMBER(info_sub, count_uv_face_unselected_with_all_verts_selected); + + DICT_ADD_INT_MEMBER(info_sub, count_uv_face_selected_with_any_edges_unselected); + DICT_ADD_INT_MEMBER(info_sub, count_uv_face_unselected_with_all_edges_selected); + } + + if (check_contiguous) { + UVSelectValidateInfo_Contiguous &info_sub = info.contiguous; + DICT_ADD_INT_MEMBER(info_sub, count_uv_vert_non_contiguous_selected); + DICT_ADD_INT_MEMBER(info_sub, count_uv_edge_non_contiguous_selected); + } + + if (check_flush && check_contiguous) { + UVSelectValidateInfo_FlushAndContiguous &info_sub = info.flush_contiguous; + DICT_ADD_INT_MEMBER(info_sub, count_uv_vert_isolated_in_edge_or_face_mode); + DICT_ADD_INT_MEMBER(info_sub, count_uv_vert_isolated_in_face_mode); + DICT_ADD_INT_MEMBER(info_sub, count_uv_edge_isolated_in_face_mode); + } + +#undef DICT_ADD_INT_MEMBER + + return result; +} + #ifdef __GNUC__ # ifdef __clang__ # pragma clang diagnostic push @@ -821,6 +950,10 @@ static PyMethodDef BPy_BM_utils_methods[] = { (PyCFunction)bpy_bm_utils_loop_separate, METH_O, bpy_bm_utils_loop_separate_doc}, + {"uv_select_check", + (PyCFunction)bpy_bm_utils_uv_select_check, + METH_VARARGS | METH_KEYWORDS, + bpy_bm_utils_uv_select_check_doc}, {nullptr, nullptr, 0, nullptr}, }; diff --git a/tests/python/bl_pyapi_bmesh.py b/tests/python/bl_pyapi_bmesh.py index 3a7d4e4c5fe..fe5924589f8 100644 --- a/tests/python/bl_pyapi_bmesh.py +++ b/tests/python/bl_pyapi_bmesh.py @@ -3,10 +3,45 @@ # SPDX-License-Identifier: Apache-2.0 # ./blender.bin --background --python tests/python/bl_pyapi_bmesh.py -- --verbose + +__all__ = ( + "main", +) + import bmesh import unittest +# ------------------------------------------------------------------------------ +# Internal Utilities + +def save_to_blend_file_for_testing(bm): + """ + Useful for inspecting test data. + """ + import bpy + from bpy import context + + bpy.ops.wm.read_factory_settings(use_empty=True) + me = bpy.data.meshes.new("test output") + bm.to_mesh(me) + ob = bpy.data.objects.new("", me) + + view_layer = context.view_layer + layer_collection = context.layer_collection or view_layer.active_layer_collection + scene_collection = layer_collection.collection + + scene_collection.objects.link(ob) + ob.select_set(True) + view_layer.objects.active = ob + + # Write to the $CWD. + bpy.ops.wm.save_as_mainfile(filepath="bl_pyapi_bmesh.blend") + + +# ------------------------------------------------------------------------------ +# Basic Tests + class TestBMeshBasic(unittest.TestCase): def test_create_uvsphere(self): @@ -25,7 +60,635 @@ class TestBMeshBasic(unittest.TestCase): bm.free() -if __name__ == "__main__": +# ------------------------------------------------------------------------------ +# UV Selection + +def bm_uv_select_check_or_empty( + bm, + sync=False, + flush=False, + contiguous=False, +): + return bmesh.utils.uv_select_check( + bm, + sync=sync, + flush=flush, + contiguous=contiguous, + ) or {} + + +def bm_uv_select_check_non_zero( + bm, /, *, + sync=False, + flush=False, + contiguous=False, +): + """ + Remove all zero keys, so it's convenient to isolate failures. + """ + result = bmesh.utils.uv_select_check( + bm, + sync=sync, + flush=flush, + contiguous=contiguous, + ) + if result is not None: + return {key: value for key, value in result.items() if value != 0} + return {} + + +def bm_uv_select_set_all(bm, /, *, select): + for f in bm.faces: + f.uv_select = select + for l in f.loops: + l.uv_select_vert = select + l.uv_select_edge = select + + +def bm_uv_layer_from_coords(bm, uv_layer): + for face in bm.faces: + for loop in face.loops: + loop_uv = loop[uv_layer] + # Use XY position of the vertex as a uv coordinate. + loop_uv.uv = loop.vert.co.xy + + +def bm_loop_select_count_vert_edge_face(bm): + """ + Return a tuple of UV selection counts (vert, edge, face). + Use for tests. + """ + mesh_vert = 0 + mesh_edge = 0 + mesh_face = 0 + + uv_vert = 0 + uv_edge = 0 + uv_face = 0 + + for v in bm.verts: + if v.hide: + continue + if v.select: + mesh_vert += 1 + for e in bm.edges: + if e.hide: + continue + if e.select: + mesh_edge += 1 + + for f in bm.faces: + if f.hide: + continue + if f.select: + mesh_face += 1 + + if f.uv_select: + uv_face += 1 + for l in f.loops: + if l.uv_select_vert: + uv_vert += 1 + if l.uv_select_edge: + uv_edge += 1 + + return (uv_vert, uv_edge, uv_face), (mesh_vert, mesh_edge, mesh_face) + + +def bm_uv_select_reset(bm, /, *, select): + bm_uv_select_set_all(bm, select=select) + bm.uv_select_sync_to_mesh() + + +class TestBMeshUVSelectSimple(unittest.TestCase): + + def test_uv_grid(self): + bm = bmesh.new() + bmesh.ops.create_grid( + bm, + x_segments=3, + y_segments=4, + size=1.0, + ) + self.assertEqual(len(bm.verts), 20) + self.assertEqual(len(bm.edges), 31) + self.assertEqual(len(bm.faces), 12) + + # Nothing selected. + bm.uv_select_sync_valid = True + self.assertEqual(bm_uv_select_check_or_empty(bm, sync=True), {}) + + # All verts selected, no UV's selected. + for v in bm.verts: + v.select = True + + bm.uv_select_sync_valid = True + self.assertEqual(bm_uv_select_check_or_empty(bm, sync=True).get( + "count_uv_vert_none_selected_with_vert_selected", 0), 20) + + # No verts selected, all UV's selected. + for v in bm.verts: + v.select = False + for f in bm.faces: + for l in f.loops: + l.uv_select_vert = True + + bm.uv_select_sync_valid = True + self.assertTrue( + bm_uv_select_check_or_empty(bm, sync=True).get("count_uv_vert_any_selected_with_vert_unselected", 0), + 48) + + bm.free() + + def test_uv_contiguous_verts(self): + from mathutils import Vector + bm = bmesh.new() + bmesh.ops.create_grid(bm, x_segments=2, y_segments=2, size=1.0) + self.assertEqual((len(bm.verts), len(bm.edges), len(bm.faces)), (9, 12, 4)) + + faces = list(bm.faces) + + # Sort faces so the order is always predictable. + vector_dot = Vector((0.95, 0.05, 0.0)) + faces.sort(key=lambda f: vector_dot.dot(f.calc_center_median())) + + # Checker de-select UV's, each face has an isolated selection. + for i, f in enumerate(faces): + do_select = bool(i % 2) + for l in f.loops: + l.uv_select_vert = do_select + l.uv_select_edge = do_select + + bm.uv_select_sync_valid = True + result = bm_uv_select_check_or_empty(bm, sync=True, flush=True, contiguous=False) + self.assertTrue(result.get("count_uv_vert_any_selected_with_vert_unselected", 0), 48) + + bm.free() + + def test_uv_select_flush_mode(self): + bm = bmesh.new() + + # Do a NOP empty mesh check. + bm.uv_select_sync_valid = True + bm.uv_select_flush_mode() + bm.uv_select_sync_to_mesh() + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((0, 0, 0), (0, 0, 0))) + self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True), {}) + + bmesh.ops.create_grid(bm, x_segments=3, y_segments=3, size=1.0) + # Needed for methods that act on UV select. + bm.uv_select_sync_valid = True + + # Do a NOP. + bm.uv_select_flush_mode() + bm.uv_select_sync_to_mesh() + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((0, 0, 0), (0, 0, 0))) + self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True), {}) + + # Simple tests that selects all elements in a mode: `VERT`. + bm.select_mode = {'VERT'} + bm_uv_select_set_all(bm, select=False) + # Select only verts. + for f in bm.faces: + for l in f.loops: + l.uv_select_vert = True + bm.uv_select_flush_mode() + bm.uv_select_sync_to_mesh() + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((36, 36, 9), (16, 24, 9))) + self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True), {}) + + # Simple tests that selects all elements in a mode: `EDGE`. + bm.select_mode = {'EDGE'} + bm_uv_select_set_all(bm, select=False) + # Select only edges.. + for f in bm.faces: + for l in f.loops: + l.uv_select_edge_set(True) + bm.uv_select_flush_mode() + bm.uv_select_sync_to_mesh() + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((36, 36, 9), (16, 24, 9))) + self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True), {}) + + # Simple tests that selects all elements in a mode: `FACE`. + bm.select_mode = {'FACE'} + bm_uv_select_set_all(bm, select=False) + # Select only faces. + for f in bm.faces: + f.uv_select_set(True) + bm.uv_select_flush_mode() + bm.uv_select_sync_to_mesh() + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((36, 36, 9), (16, 24, 9))) + self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True), {}) + + # TODO: Complex mixed selection. + + def test_uv_select_flush(self): + from mathutils import Vector + bm = bmesh.new() + + # Do a NOP empty mesh check. + bm.uv_select_sync_valid = True + bm.uv_select_flush(True) + bm.uv_select_sync_to_mesh() + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((0, 0, 0), (0, 0, 0))) + self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True), {}) + + bmesh.ops.create_grid(bm, x_segments=3, y_segments=3, size=1.0) + # Needed for methods that act on UV select. + bm.uv_select_sync_valid = True + self.assertEqual((len(bm.verts), len(bm.edges), len(bm.faces)), (16, 24, 9)) + + # Do a NOP check. + bm.uv_select_flush(True) + bm.uv_select_sync_to_mesh() + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((0, 0, 0), (0, 0, 0))) + self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True), {}) + + faces = list(bm.faces) + + # Sort faces so the order is always predictable. + vector_dot = Vector((0.95, 0.05, 0.0)) + faces.sort(key=lambda f: vector_dot.dot(f.calc_center_median())) + + f_center = faces[len(faces) // 2] + self.assertEqual(f_center.calc_center_median().to_tuple(6), (0.0, 0.0, 0.0)) + + uv_layer = bm.loops.layers.uv.new() + bm_uv_layer_from_coords(bm, uv_layer) + + # Select 4 vertices. + for l in f_center.loops: + l.uv_select_vert = True + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((4, 0, 0), (0, 0, 0))) + # Check. + self.assertEqual( + bm_uv_select_check_non_zero(bm, sync=True, flush=True), + { + "count_uv_edge_unselected_with_all_verts_selected": 4, + "count_uv_face_unselected_with_all_verts_selected": 1, + "count_uv_vert_any_selected_with_vert_unselected": 4, + }, + ) + + bm.uv_select_flush(True) + bm.uv_select_sync_to_mesh() + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((4, 4, 1), (4, 4, 1))) + self.assertEqual( + bm_uv_select_check_non_zero(bm, sync=True, flush=True, contiguous=True), + # Not actually an error as the UV's have intentionally been selected in isolation. + { + "count_uv_vert_non_contiguous_selected": 5, + }, + ) + + # De-select those 4, ensure the selection remains false afterwards. + for l in f_center.loops: + l.uv_select_vert = False + + bm.uv_select_flush(False) + bm.uv_select_sync_to_mesh() + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((0, 0, 0), (0, 0, 0))) + self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True), {}) + self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True, contiguous=True), {}) + + # Select a single faces UV's bottom left hand corner (as well as adjacent UV's). + for f in faces: + for l in f.loops: + xy = l.vert.co.xy[:] + if xy[0] > 0.0 or xy[1] > 0.0: + continue + l.uv_select_vert = True + + bm.uv_select_flush(True) + bm.uv_select_sync_to_mesh() + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((9, 6, 1), (4, 4, 1))) + self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True, contiguous=True), {}) + + # Ensure flushing de-selection does nothing when there is nothing to do. + bm.uv_select_flush(False) + bm.uv_select_sync_to_mesh() + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((9, 6, 1), (4, 4, 1))) + self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True), {}) + + self.assertTrue(bm.uv_select_sync_valid) + + bm.free() + + def test_uv_select_sync_from_mesh(self): + bm = bmesh.new() + uv_layer = bm.loops.layers.uv.new() + del uv_layer + + # Do a NOP empty mesh check. + bm.select_flush(True) + bm.uv_select_sync_from_mesh() + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((0, 0, 0), (0, 0, 0))) + self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True), {}) + + bmesh.ops.create_grid(bm, x_segments=4, y_segments=4, size=2.0) + # Needed for methods that act on UV select. + bm.uv_select_sync_valid = True + + # Deselect all verts and flush back to the mesh. + for v in bm.verts: + v.select = False + bm.select_flush(True) + bm.uv_select_sync_from_mesh() + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((0, 0, 0), (0, 0, 0))) + self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True), {}) + + # Select all verts and flush back to the mesh. + for v in bm.verts: + v.select = True + bm.select_flush(True) + bm.uv_select_sync_from_mesh() + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((64, 64, 16), (25, 40, 16))) + self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True), {}) + + # TODO: Complex mixed selection. + + def test_uv_select_sync_to_mesh(self): + # Even though this is called in other tests, + # perform some additional checks here such as checking hide is respected. + + bm = bmesh.new() + uv_layer = bm.loops.layers.uv.new() + del uv_layer + + # Do a NOP empty mesh check. + bm.select_flush(True) + bm.uv_select_sync_from_mesh() + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((0, 0, 0), (0, 0, 0))) + self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True), {}) + + bmesh.ops.create_grid(bm, x_segments=4, y_segments=4, size=2.0) + # Needed for methods that act on UV select. + bm.uv_select_sync_valid = True + + # Select a single faces UV's bottom left hand corner (as well as adjacent UV's). + for f in bm.faces: + for l in f.loops: + l.uv_select_vert = True + + bm.uv_select_flush(True) + bm.uv_select_sync_to_mesh() + + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((64, 64, 16), (25, 40, 16))) + self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True), {}) + + # Hide all geometry, then check syncing doesn't select them. + for v in bm.verts: + v.hide = True + for e in bm.edges: + e.hide = True + for f in bm.faces: + f.hide = True + + bm.uv_select_flush(True) + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((0, 0, 0), (0, 0, 0))) + self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True), {}) + + bm.uv_select_sync_to_mesh() + # Nothing should be selected because the mesh is hidden. + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((0, 0, 0), (0, 0, 0))) + + def test_uv_select_foreach_set(self): + # Select UV's directly, similar to selecting in the UV editor. + bm = bmesh.new() + uv_layer = bm.loops.layers.uv.new() + bm.uv_select_sync_valid = True + + # Do a NOP empty mesh check. + bm.uv_select_foreach_set(True) + + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((0, 0, 0), (0, 0, 0))) + self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True), {}) + + # Do a NOP empty mesh check with empty arguments. + bm.uv_select_foreach_set(True, loop_verts=[], loop_edges=[], faces=[]) + + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((0, 0, 0), (0, 0, 0))) + self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True), {}) + + # Use 3 segments to avoid central vertices (simplifies above/below tests when picking half the mesh). + bmesh.ops.create_grid(bm, x_segments=3, y_segments=3, size=2.0) + bm_uv_layer_from_coords(bm, uv_layer) + + # Select all vertices with X below 0.0. + verts_x_pos = [] + verts_x_neg = [] + for v in bm.verts: + (verts_x_pos if v.co.x > 0.0 else verts_x_neg).append(v) + self.assertEqual((len(verts_x_neg), len(verts_x_pos)), (8, 8)) + + verts_x_pos_as_set = set(verts_x_pos) + + # Other elements from the verts (to pass to selection). + faces_x_pos = [ + f for f in bm.faces + # Find the loop that spans positive edges. + if len(set(l.vert for l in f.loops) & verts_x_pos_as_set) == 4 + ] + self.assertEqual(len(faces_x_pos), 3) + + loop_edges_x_pos = [ + l for f in faces_x_pos + for l in f.loops + ] + self.assertEqual(len(loop_edges_x_pos), 12) + + loop_verts_x_pos = [next(iter(v.link_loops)) for v in verts_x_pos] + self.assertEqual(len(loop_verts_x_pos), 8) + + # NOTE: regarding allowing `count_uv_vert_non_contiguous_selected` when de-selecting edges & faces. + # This occurs because of `uv_select_flush_mode` which doesn't take `contiguous` UV's into account. + + # --------------- + # Select by Verts + bm_uv_select_reset(bm, select=False) + bm.uv_select_foreach_set(True, loop_verts=loop_verts_x_pos) + bm.uv_select_flush(True) + bm.uv_select_sync_to_mesh() + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((18, 15, 3), (8, 10, 3))) + self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True, contiguous=True), {}) + # ------------------ + # De-Select by Verts + bm_uv_select_reset(bm, select=True) + bm.uv_select_foreach_set(False, loop_verts=loop_verts_x_pos) + bm.uv_select_flush(False) + bm.uv_select_sync_to_mesh() + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((18, 15, 3), (8, 10, 3))) + self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True, contiguous=True), {}) + + # --------------- + # Select by Edges + bm.select_mode = {'EDGE'} + bm_uv_select_reset(bm, select=False) + bm.uv_select_foreach_set(True, loop_edges=loop_edges_x_pos) + bm.uv_select_flush_mode(flush_down=True) + bm.uv_select_sync_to_mesh() + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((18, 15, 3), (8, 10, 3))) + self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True, contiguous=True), {}) + + # ------------------ + # De-Select by Edges + bm_uv_select_reset(bm, select=True) + bm.uv_select_foreach_set(False, loop_edges=loop_edges_x_pos) + bm.uv_select_flush_mode(flush_down=True) + bm.uv_select_sync_to_mesh() + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((24, 21, 3), (12, 14, 3))) + self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True, contiguous=True), { + "count_uv_vert_non_contiguous_selected": 4, # Not an error, to be expected. + }) + + # --------------- + # Select by Faces + bm.select_mode = {'FACE'} + bm_uv_select_reset(bm, select=False) + bm.uv_select_foreach_set(True, faces=faces_x_pos) + bm.uv_select_flush_mode(flush_down=True) + bm.uv_select_sync_to_mesh() + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((12, 12, 3), (8, 10, 3))) + self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True, contiguous=True), { + "count_uv_vert_non_contiguous_selected": 4, # Not an error, to be expected. + }) + + # ------------------ + # De-Select by Faces + bm_uv_select_reset(bm, select=True) + bm.uv_select_foreach_set(False, faces=faces_x_pos) + bm.uv_select_flush_mode(flush_down=True) + bm.uv_select_sync_to_mesh() + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((24, 24, 6), (12, 17, 6))) + self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True, contiguous=True), { + "count_uv_vert_non_contiguous_selected": 4, # Not an error, to be expected. + }) + + # save_to_blend_file_for_testing(bm) + + def test_uv_select_foreach_set_from_mesh(self): + """ + Select mesh elements, similar to selecting in the viewport, + which is then flushed to the UV editor. + """ + # Select geometry directly, similar to selecting in the 3D viewport. + bm = bmesh.new() + uv_layer = bm.loops.layers.uv.new() + bm.uv_select_sync_valid = True + + # Do a NOP empty mesh check. + bm.uv_select_foreach_set_from_mesh(True) + + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((0, 0, 0), (0, 0, 0))) + self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True, contiguous=True), {}) + + # Do a NOP empty mesh check with empty arguments. + bm.uv_select_foreach_set_from_mesh(True, verts=[], edges=[], faces=[]) + + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((0, 0, 0), (0, 0, 0))) + self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True, contiguous=True), {}) + + # Use 3 segments to avoid central vertices (simplifies above/below tests when picking half the mesh). + bmesh.ops.create_grid(bm, x_segments=3, y_segments=3, size=2.0) + bm_uv_layer_from_coords(bm, uv_layer) + + # Select all vertices with X below 0.0. + verts_x_pos = [] + verts_x_neg = [] + for v in bm.verts: + (verts_x_pos if v.co.x > 0.0 else verts_x_neg).append(v) + self.assertEqual((len(verts_x_neg), len(verts_x_pos)), (8, 8)) + + verts_x_pos_as_set = set(verts_x_pos) + + # Other elements from the verts (to pass to selection). + faces_x_pos = [ + f for f in bm.faces + # Find the loop that spans positive edges. + if len(set(l.vert for l in f.loops) & verts_x_pos_as_set) == 4 + ] + self.assertEqual(len(faces_x_pos), 3) + + edges_x_pos = [ + e for e in bm.edges + if len(set(e.verts) & verts_x_pos_as_set) == 2 + ] + self.assertEqual(len(edges_x_pos), 10) + + loop_verts_x_pos = [next(iter(v.link_loops)) for v in verts_x_pos] + self.assertEqual(len(loop_verts_x_pos), 8) + + # NOTE: regarding allowing `count_uv_vert_non_contiguous_selected` when de-selecting edges & faces. + # This occurs because of `uv_select_flush_mode` which doesn't take `contiguous` UV's into account. + + # --------------- + # Select by Verts + bm_uv_select_reset(bm, select=False) + bm.uv_select_foreach_set_from_mesh(True, verts=verts_x_pos) + bm.uv_select_flush(True) + bm.uv_select_sync_to_mesh() + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((18, 15, 3), (8, 10, 3))) + self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True, contiguous=True), {}) + # ------------------ + # De-Select by Verts + bm_uv_select_reset(bm, select=True) + bm.uv_select_foreach_set_from_mesh(False, verts=verts_x_pos) + bm.uv_select_flush(False) + bm.uv_select_sync_to_mesh() + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((18, 15, 3), (8, 10, 3))) + self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True, contiguous=True), {}) + + # --------------- + # Select by Edges + bm.select_mode = {'EDGE'} + bm_uv_select_reset(bm, select=False) + bm.uv_select_foreach_set_from_mesh(True, edges=edges_x_pos) + bm.uv_select_flush_mode(flush_down=True) + bm.uv_select_sync_to_mesh() + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((18, 15, 3), (8, 10, 3))) + self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True, contiguous=True), {}) + + # ------------------ + # De-Select by Edges + bm_uv_select_reset(bm, select=True) + bm.uv_select_foreach_set_from_mesh(False, edges=edges_x_pos) + bm.uv_select_flush_mode(flush_down=True) + bm.uv_select_sync_to_mesh() + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((24, 21, 3), (12, 14, 3))) + self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True, contiguous=True), { + "count_uv_vert_non_contiguous_selected": 4, # Not an error, to be expected. + }) + + # --------------- + # Select by Faces + bm.select_mode = {'FACE'} + bm_uv_select_reset(bm, select=False) + bm.uv_select_foreach_set_from_mesh(True, faces=faces_x_pos) + bm.uv_select_flush_mode(flush_down=True) + bm.uv_select_sync_to_mesh() + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((12, 12, 3), (8, 10, 3))) + self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True, contiguous=True), { + "count_uv_vert_non_contiguous_selected": 4, # Not an error, to be expected. + }) + + # ------------------ + # De-Select by Faces + bm_uv_select_reset(bm, select=True) + bm.uv_select_foreach_set_from_mesh(False, faces=faces_x_pos) + bm.uv_select_flush_mode(flush_down=True) + bm.uv_select_sync_to_mesh() + self.assertEqual(bm_loop_select_count_vert_edge_face(bm), ((24, 24, 6), (12, 17, 6))) + self.assertEqual(bm_uv_select_check_non_zero(bm, sync=True, flush=True, contiguous=True), { + "count_uv_vert_non_contiguous_selected": 4, # Not an error, to be expected. + }) + + # save_to_blend_file_for_testing(bm) + + +def main(): import sys sys.argv = [__file__] + (sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []) unittest.main() + + +if __name__ == "__main__": + main()