From 62d72bd0b50814162290e133b84ba1265c705abc Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Tue, 7 Oct 2025 01:41:16 +0000 Subject: [PATCH] UV: initial implementation of UV sync select Support sync selection in the UV editor, with face-corner selection, so it's possible to select individual UV vertices/edges in the UV editor without UV's attached to the same underlying edge also becoming selected. There is limited support for maintaining the UV selection when selecting from the 3D viewport, common operations such as picking & box/circle/lasso select support this, however other selection operations such as "Select Random" or "Select Similar" will clear this data, causing all UV's connected to selected mesh elements to become selected. We may add support for additional operators as needed. Details: - UV Sync Selection is now enabled by default. - In edit-mode the UV selection is stored in BMLoop/BMFace which are written to custom-data layers when converted to a Mesh. - To avoid unnecessary overhead - this data is created on demand. Operators may clear this data - selecting all or none do so, as there is no reason to store this data for a uniform selection. - The Python API includes functions to synchronize the selection to/from UV's as well as flushing based on the mode. - Python scripts that manipulate the selection will either need to clear this synchronized state or maintain it. See: - Design task: #78393. - Implementation task: #131642. Ref !138197 --- scripts/startup/bl_ui/space_image.py | 13 +- .../blenloader/intern/versioning_defaults.cc | 2 + source/blender/bmesh/CMakeLists.txt | 2 + source/blender/bmesh/bmesh.hh | 1 + source/blender/bmesh/bmesh_class.hh | 24 +- .../blender/bmesh/intern/bmesh_construct.cc | 24 +- source/blender/bmesh/intern/bmesh_core.cc | 2 +- source/blender/bmesh/intern/bmesh_marking.cc | 12 + source/blender/bmesh/intern/bmesh_marking.hh | 5 + .../bmesh/intern/bmesh_mesh_convert.cc | 94 +- source/blender/bmesh/intern/bmesh_uvselect.cc | 2520 +++++++++++++++++ source/blender/bmesh/intern/bmesh_uvselect.hh | 580 ++++ .../blender/bmesh/operators/bmo_subdivide.cc | 17 + .../draw/engines/overlay/overlay_mesh.hh | 2 + .../intern/mesh_extractors/extract_mesh.cc | 8 +- source/blender/editors/include/ED_mesh.hh | 3 + source/blender/editors/include/ED_uvedit.hh | 113 +- source/blender/editors/mesh/editmesh_add.cc | 2 + .../editors/mesh/editmesh_add_gizmo.cc | 3 + source/blender/editors/mesh/editmesh_bevel.cc | 1 + .../blender/editors/mesh/editmesh_bisect.cc | 3 + .../editors/mesh/editmesh_intersect.cc | 2 + source/blender/editors/mesh/editmesh_knife.cc | 2 + .../blender/editors/mesh/editmesh_loopcut.cc | 3 + source/blender/editors/mesh/editmesh_path.cc | 3 + .../blender/editors/mesh/editmesh_rip_edge.cc | 30 + .../blender/editors/mesh/editmesh_select.cc | 123 +- .../editors/mesh/editmesh_select_similar.cc | 8 + source/blender/editors/mesh/editmesh_tools.cc | 7 + source/blender/editors/mesh/editmesh_undo.cc | 19 +- source/blender/editors/mesh/editmesh_utils.cc | 82 +- .../blender/editors/object/object_vgroup.cc | 2 + .../editors/space_view3d/view3d_select.cc | 130 +- .../transform/transform_convert_mesh_uv.cc | 8 +- .../blender/editors/uvedit/uvedit_buttons.cc | 4 +- .../blender/editors/uvedit/uvedit_intern.hh | 15 +- .../blender/editors/uvedit/uvedit_islands.cc | 5 +- source/blender/editors/uvedit/uvedit_ops.cc | 146 +- source/blender/editors/uvedit/uvedit_path.cc | 25 +- source/blender/editors/uvedit/uvedit_rip.cc | 60 +- .../blender/editors/uvedit/uvedit_select.cc | 1270 +++++++-- .../editors/uvedit/uvedit_smart_stitch.cc | 4 +- .../editors/uvedit/uvedit_unwrap_ops.cc | 64 +- .../blender/geometry/intern/mesh_boolean.cc | 2 +- source/blender/makesdna/DNA_scene_defaults.h | 1 + .../blender/modifiers/intern/MOD_boolean.cc | 2 +- source/blender/python/bmesh/bmesh_py_types.cc | 732 ++++- source/blender/python/bmesh/bmesh_py_types.hh | 5 + source/blender/python/bmesh/bmesh_py_utils.cc | 133 + tests/python/bl_pyapi_bmesh.py | 665 ++++- 50 files changed, 6592 insertions(+), 391 deletions(-) create mode 100644 source/blender/bmesh/intern/bmesh_uvselect.cc create mode 100644 source/blender/bmesh/intern/bmesh_uvselect.hh 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()