diff options
author | Campbell Barton <ideasman42@gmail.com> | 2020-07-07 10:29:17 +0300 |
---|---|---|
committer | Campbell Barton <ideasman42@gmail.com> | 2020-07-09 11:43:23 +0300 |
commit | ea5fe7abc183c1e53d327f97280f589499fe60bb (patch) | |
tree | f6c53f4e97481d7470918708ffc740e1570bcdf6 /source | |
parent | 0b8221683fa9b4d228728dcf3c2e95389315a4a6 (diff) |
UV: path selection support
This adds support for path selection for vertex edge & face selection
modes, matching mesh editing behavior, useful with the UV rip tool.
Region select & edge tagging are currently not supported,
although they could be added eventually.
Diffstat (limited to 'source')
-rw-r--r-- | source/blender/bmesh/CMakeLists.txt | 2 | ||||
-rw-r--r-- | source/blender/bmesh/bmesh_tools.h | 1 | ||||
-rw-r--r-- | source/blender/bmesh/tools/bmesh_path_uv.c | 432 | ||||
-rw-r--r-- | source/blender/bmesh/tools/bmesh_path_uv.h | 47 | ||||
-rw-r--r-- | source/blender/editors/include/ED_uvedit.h | 6 | ||||
-rw-r--r-- | source/blender/editors/mesh/mesh_intern.h | 1 | ||||
-rw-r--r-- | source/blender/editors/uvedit/CMakeLists.txt | 3 | ||||
-rw-r--r-- | source/blender/editors/uvedit/uvedit_intern.h | 3 | ||||
-rw-r--r-- | source/blender/editors/uvedit/uvedit_ops.c | 1 | ||||
-rw-r--r-- | source/blender/editors/uvedit/uvedit_path.c | 676 | ||||
-rw-r--r-- | source/blender/editors/uvedit/uvedit_select.c | 63 |
11 files changed, 1234 insertions, 1 deletions
diff --git a/source/blender/bmesh/CMakeLists.txt b/source/blender/bmesh/CMakeLists.txt index 09b8a06bff3..43d12e8eeed 100644 --- a/source/blender/bmesh/CMakeLists.txt +++ b/source/blender/bmesh/CMakeLists.txt @@ -153,6 +153,8 @@ set(SRC tools/bmesh_path.h tools/bmesh_path_region.c tools/bmesh_path_region.h + tools/bmesh_path_uv.c + tools/bmesh_path_uv.h tools/bmesh_region_match.c tools/bmesh_region_match.h tools/bmesh_separate.c diff --git a/source/blender/bmesh/bmesh_tools.h b/source/blender/bmesh/bmesh_tools.h index d0e91d033fb..a0dc6abf009 100644 --- a/source/blender/bmesh/bmesh_tools.h +++ b/source/blender/bmesh/bmesh_tools.h @@ -36,6 +36,7 @@ extern "C" { #include "tools/bmesh_edgesplit.h" #include "tools/bmesh_path.h" #include "tools/bmesh_path_region.h" +#include "tools/bmesh_path_uv.h" #include "tools/bmesh_region_match.h" #include "tools/bmesh_separate.h" #include "tools/bmesh_triangulate.h" diff --git a/source/blender/bmesh/tools/bmesh_path_uv.c b/source/blender/bmesh/tools/bmesh_path_uv.c new file mode 100644 index 00000000000..b2999b305e0 --- /dev/null +++ b/source/blender/bmesh/tools/bmesh_path_uv.c @@ -0,0 +1,432 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup bmesh + * + * Find a path between 2 elements in UV space. + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_heap_simple.h" +#include "BLI_linklist.h" +#include "BLI_math.h" + +#include "DNA_meshdata_types.h" + +#include "bmesh.h" +#include "bmesh_path_uv.h" /* own include */ +#include "intern/bmesh_query.h" +#include "intern/bmesh_query_uv.h" + +/* -------------------------------------------------------------------- */ +/** \name Generic Helpers + * \{ */ + +/** + * Use skip options when we want to start measuring from a boundary. + * + * See #step_cost_3_v3_ex in bmesh_path.c which follows the same logic. + */ +static float step_cost_3_v2_ex( + const float v1[2], const float v2[2], const float v3[2], bool skip_12, bool skip_23) +{ + float d1[2], d2[2]; + + /* The cost is based on the simple sum of the length of the two edges. */ + sub_v2_v2v2(d1, v2, v1); + sub_v2_v2v2(d2, v3, v2); + const float cost_12 = normalize_v2(d1); + const float cost_23 = normalize_v2(d2); + const float cost = ((skip_12 ? 0.0f : cost_12) + (skip_23 ? 0.0f : cost_23)); + + /* But is biased to give higher values to sharp turns, so that it will take paths with + * fewer "turns" when selecting between equal-weighted paths between the two edges. */ + return cost * (1.0f + 0.5f * (2.0f - sqrtf(fabsf(dot_v2v2(d1, d2))))); +} + +static float UNUSED_FUNCTION(step_cost_3_v2)(const float v1[2], + const float v2[2], + const float v3[2]) +{ + return step_cost_3_v2_ex(v1, v2, v3, false, false); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name BM_mesh_calc_path_uv_vert + * \{ */ + +static void looptag_add_adjacent_uv(HeapSimple *heap, + BMLoop *l_a, + BMLoop **loops_prev, + float *cost, + const struct BMCalcPathUVParams *params) +{ + BLI_assert(params->aspect_y != 0.0f); + const uint cd_loop_uv_offset = params->cd_loop_uv_offset; + const int l_a_index = BM_elem_index_get(l_a); + const MLoopUV *luv_a = BM_ELEM_CD_GET_VOID_P(l_a, cd_loop_uv_offset); + const float uv_a[2] = {luv_a->uv[0], luv_a->uv[1] / params->aspect_y}; + + { + BMIter liter; + BMLoop *l; + /* Loop over faces of face, but do so by first looping over loops. */ + BM_ITER_ELEM (l, &liter, l_a->v, BM_LOOPS_OF_VERT) { + const MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); + if (equals_v2v2(luv_a->uv, luv->uv)) { + /* 'l_a' is already tagged, tag all adjacent. */ + BM_elem_flag_enable(l, BM_ELEM_TAG); + BMLoop *l_b = l->next; + do { + if (!BM_elem_flag_test(l_b, BM_ELEM_TAG)) { + const MLoopUV *luv_b = BM_ELEM_CD_GET_VOID_P(l_b, cd_loop_uv_offset); + const float uv_b[2] = {luv_b->uv[0], luv_b->uv[1] / params->aspect_y}; + /* We know 'l_b' is not visited, check it out! */ + const int l_b_index = BM_elem_index_get(l_b); + const float cost_cut = params->use_topology_distance ? 1.0f : len_v2v2(uv_a, uv_b); + const float cost_new = cost[l_a_index] + cost_cut; + + if (cost[l_b_index] > cost_new) { + cost[l_b_index] = cost_new; + loops_prev[l_b_index] = l_a; + BLI_heapsimple_insert(heap, cost_new, l_b); + } + } + /* This means we only step onto `l->prev` & `l->next`. */ + if (params->use_step_face == false) { + if (l_b == l->next) { + l_b = l->prev->prev; + } + } + } while ((l_b = l_b->next) != l); + } + } + } +} + +struct LinkNode *BM_mesh_calc_path_uv_vert(BMesh *bm, + BMLoop *l_src, + BMLoop *l_dst, + const struct BMCalcPathUVParams *params, + bool (*filter_fn)(BMLoop *, void *), + void *user_data) +{ + LinkNode *path = NULL; + /* BM_ELEM_TAG flag is used to store visited edges */ + BMIter viter; + HeapSimple *heap; + float *cost; + BMLoop **loops_prev; + int i = 0, totloop; + BMFace *f; + + /* Note, would pass BM_EDGE except we are looping over all faces anyway. */ + // BM_mesh_elem_index_ensure(bm, BM_LOOP); // NOT NEEDED FOR FACETAG + + BM_ITER_MESH (f, &viter, bm, BM_FACES_OF_MESH) { + BMLoop *l_first = BM_FACE_FIRST_LOOP(f); + BMLoop *l_iter = l_first; + do { + BM_elem_flag_set(l_iter, BM_ELEM_TAG, !filter_fn(l_iter, user_data)); + BM_elem_index_set(l_iter, i); /* set_inline */ + i += 1; + } while ((l_iter = l_iter->next) != l_first); + } + bm->elem_index_dirty &= ~BM_LOOP; + + /* Allocate. */ + totloop = bm->totloop; + loops_prev = MEM_callocN(sizeof(*loops_prev) * totloop, __func__); + cost = MEM_mallocN(sizeof(*cost) * totloop, __func__); + + copy_vn_fl(cost, totloop, 1e20f); + + /* Regular dijkstra shortest path, but over UV loops instead of vertices. */ + heap = BLI_heapsimple_new(); + BLI_heapsimple_insert(heap, 0.0f, l_src); + cost[BM_elem_index_get(l_src)] = 0.0f; + + BMLoop *l = NULL; + while (!BLI_heapsimple_is_empty(heap)) { + l = BLI_heapsimple_pop_min(heap); + + if (l->v == l_dst->v) { + break; + } + + if (!BM_elem_flag_test(l, BM_ELEM_TAG)) { + /* Adjacent loops are tagged while stepping to avoid 2x loops. */ + BM_elem_flag_enable(l, BM_ELEM_TAG); + looptag_add_adjacent_uv(heap, l, loops_prev, cost, params); + } + } + + if (l->v == l_dst->v) { + do { + BLI_linklist_prepend(&path, l); + } while ((l = loops_prev[BM_elem_index_get(l)])); + } + + MEM_freeN(loops_prev); + MEM_freeN(cost); + BLI_heapsimple_free(heap, NULL); + + return path; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name BM_mesh_calc_path_uv_edge + * \{ */ + +/* TODO(campbell): not very urgent. */ + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name BM_mesh_calc_path_uv_face + * \{ */ + +static float facetag_cut_cost_edge_uv(BMFace *f_a, + BMFace *f_b, + BMLoop *l_edge, + const void *const f_endpoints[2], + const float aspect_v2[2], + const int cd_loop_uv_offset) +{ + float f_a_cent[2]; + float f_b_cent[2]; + float e_cent[2]; + + BM_face_uv_calc_center_median_weighted(f_a, aspect_v2, cd_loop_uv_offset, f_a_cent); + BM_face_uv_calc_center_median_weighted(f_b, aspect_v2, cd_loop_uv_offset, f_b_cent); + + const float *co_v1 = ((const MLoopUV *)BM_ELEM_CD_GET_VOID_P(l_edge, cd_loop_uv_offset))->uv; + const float *co_v2 = + ((const MLoopUV *)BM_ELEM_CD_GET_VOID_P(l_edge->next, cd_loop_uv_offset))->uv; + +#if 0 + mid_v2_v2v2(e_cent, co_v1, co_v2); +#else + /* For triangle fans it gives better results to pick a point on the edge. */ + { + float ix_e[2], factor; + isect_line_line_v2_point(co_v1, co_v2, f_a_cent, f_b_cent, ix_e); + factor = line_point_factor_v2(ix_e, co_v1, co_v2); + if (factor < 0.0f) { + copy_v2_v2(e_cent, co_v1); + } + else if (factor > 1.0f) { + copy_v2_v2(e_cent, co_v2); + } + else { + copy_v2_v2(e_cent, ix_e); + } + } +#endif + + /* Apply aspect before calculating cost. */ + mul_v2_v2(f_a_cent, aspect_v2); + mul_v2_v2(f_b_cent, aspect_v2); + mul_v2_v2(e_cent, aspect_v2); + + return step_cost_3_v2_ex( + f_a_cent, e_cent, f_b_cent, (f_a == f_endpoints[0]), (f_b == f_endpoints[1])); +} + +static float facetag_cut_cost_vert_uv(BMFace *f_a, + BMFace *f_b, + BMLoop *l_vert, + const void *const f_endpoints[2], + const float aspect_v2[2], + const int cd_loop_uv_offset) +{ + float f_a_cent[2]; + float f_b_cent[2]; + float v_cent[2]; + + BM_face_uv_calc_center_median_weighted(f_a, aspect_v2, cd_loop_uv_offset, f_a_cent); + BM_face_uv_calc_center_median_weighted(f_b, aspect_v2, cd_loop_uv_offset, f_b_cent); + + copy_v2_v2(v_cent, ((const MLoopUV *)BM_ELEM_CD_GET_VOID_P(l_vert, cd_loop_uv_offset))->uv); + + mul_v2_v2(f_a_cent, aspect_v2); + mul_v2_v2(f_b_cent, aspect_v2); + mul_v2_v2(v_cent, aspect_v2); + + return step_cost_3_v2_ex( + f_a_cent, v_cent, f_b_cent, (f_a == f_endpoints[0]), (f_b == f_endpoints[1])); +} + +static void facetag_add_adjacent_uv(HeapSimple *heap, + BMFace *f_a, + BMFace **faces_prev, + float *cost, + const void *const f_endpoints[2], + const float aspect_v2[2], + const struct BMCalcPathUVParams *params) +{ + const uint cd_loop_uv_offset = params->cd_loop_uv_offset; + const int f_a_index = BM_elem_index_get(f_a); + + /* Loop over faces of face, but do so by first looping over loops. */ + { + BMIter liter; + BMLoop *l_a; + + BM_ITER_ELEM (l_a, &liter, f_a, BM_LOOPS_OF_FACE) { + BMLoop *l_first, *l_iter; + + /* Check there is an adjacent face to loop over. */ + if (l_a != l_a->radial_next) { + l_iter = l_first = l_a->radial_next; + do { + BMFace *f_b = l_iter->f; + if (!BM_elem_flag_test(f_b, BM_ELEM_TAG)) { + if (BM_loop_uv_share_edge_check(l_a, l_iter, cd_loop_uv_offset)) { + /* We know 'f_b' is not visited, check it out! */ + const int f_b_index = BM_elem_index_get(f_b); + const float cost_cut = + params->use_topology_distance ? + 1.0f : + facetag_cut_cost_edge_uv( + f_a, f_b, l_iter, f_endpoints, aspect_v2, cd_loop_uv_offset); + const float cost_new = cost[f_a_index] + cost_cut; + + if (cost[f_b_index] > cost_new) { + cost[f_b_index] = cost_new; + faces_prev[f_b_index] = f_a; + BLI_heapsimple_insert(heap, cost_new, f_b); + } + } + } + } while ((l_iter = l_iter->radial_next) != l_first); + } + } + } + + if (params->use_step_face) { + BMIter liter; + BMLoop *l_a; + + BM_ITER_ELEM (l_a, &liter, f_a, BM_LOOPS_OF_FACE) { + BMIter litersub; + BMLoop *l_b; + BM_ITER_ELEM (l_b, &litersub, l_a->v, BM_LOOPS_OF_VERT) { + if ((l_a != l_b) && !BM_loop_share_edge_check(l_a, l_b)) { + BMFace *f_b = l_b->f; + if (!BM_elem_flag_test(f_b, BM_ELEM_TAG)) { + if (BM_loop_uv_share_vert_check(l_a, l_b, cd_loop_uv_offset)) { + /* We know 'f_b' is not visited, check it out! */ + const int f_b_index = BM_elem_index_get(f_b); + const float cost_cut = + params->use_topology_distance ? + 1.0f : + facetag_cut_cost_vert_uv( + f_a, f_b, l_a, f_endpoints, aspect_v2, cd_loop_uv_offset); + const float cost_new = cost[f_a_index] + cost_cut; + + if (cost[f_b_index] > cost_new) { + cost[f_b_index] = cost_new; + faces_prev[f_b_index] = f_a; + BLI_heapsimple_insert(heap, cost_new, f_b); + } + } + } + } + } + } + } +} + +struct LinkNode *BM_mesh_calc_path_uv_face(BMesh *bm, + BMFace *f_src, + BMFace *f_dst, + const struct BMCalcPathUVParams *params, + bool (*filter_fn)(BMFace *, void *), + void *user_data) +{ + const float aspect_v2[2] = {1.0f, 1.0f / params->aspect_y}; + LinkNode *path = NULL; + /* BM_ELEM_TAG flag is used to store visited edges */ + BMIter fiter; + HeapSimple *heap; + float *cost; + BMFace **faces_prev; + int i = 0, totface; + + /* Start measuring face path at the face edges, ignoring their centers. */ + const void *const f_endpoints[2] = {f_src, f_dst}; + + /* Note, would pass BM_EDGE except we are looping over all faces anyway. */ + // BM_mesh_elem_index_ensure(bm, BM_LOOP); // NOT NEEDED FOR FACETAG + + { + BMFace *f; + BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) { + BM_elem_flag_set(f, BM_ELEM_TAG, !filter_fn(f, user_data)); + BM_elem_index_set(f, i); /* set_inline */ + i += 1; + } + bm->elem_index_dirty &= ~BM_FACE; + } + + /* Allocate. */ + totface = bm->totface; + faces_prev = MEM_callocN(sizeof(*faces_prev) * totface, __func__); + cost = MEM_mallocN(sizeof(*cost) * totface, __func__); + + copy_vn_fl(cost, totface, 1e20f); + + /* Regular dijkstra shortest path, but over UV faces instead of vertices. */ + heap = BLI_heapsimple_new(); + BLI_heapsimple_insert(heap, 0.0f, f_src); + cost[BM_elem_index_get(f_src)] = 0.0f; + + BMFace *f = NULL; + while (!BLI_heapsimple_is_empty(heap)) { + f = BLI_heapsimple_pop_min(heap); + + if (f == f_dst) { + break; + } + + if (!BM_elem_flag_test(f, BM_ELEM_TAG)) { + /* Adjacent loops are tagged while stepping to avoid 2x loops. */ + BM_elem_flag_enable(f, BM_ELEM_TAG); + facetag_add_adjacent_uv(heap, f, faces_prev, cost, f_endpoints, aspect_v2, params); + } + } + + if (f == f_dst) { + do { + BLI_linklist_prepend(&path, f); + } while ((f = faces_prev[BM_elem_index_get(f)])); + } + + MEM_freeN(faces_prev); + MEM_freeN(cost); + BLI_heapsimple_free(heap, NULL); + + return path; +} + +/** \} */ diff --git a/source/blender/bmesh/tools/bmesh_path_uv.h b/source/blender/bmesh/tools/bmesh_path_uv.h new file mode 100644 index 00000000000..c7c5768f7d0 --- /dev/null +++ b/source/blender/bmesh/tools/bmesh_path_uv.h @@ -0,0 +1,47 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __BMESH_PATH_UV_H__ +#define __BMESH_PATH_UV_H__ + +/** \file + * \ingroup bmesh + */ + +struct BMCalcPathUVParams { + uint use_topology_distance : 1; + uint use_step_face : 1; + uint cd_loop_uv_offset; + float aspect_y; +}; + +struct LinkNode *BM_mesh_calc_path_uv_vert(BMesh *bm, + BMLoop *l_src, + BMLoop *l_dst, + const struct BMCalcPathUVParams *params, + bool (*filter_fn)(BMLoop *, void *), + void *user_data) ATTR_WARN_UNUSED_RESULT + ATTR_NONNULL(1, 2, 3, 5); + +struct LinkNode *BM_mesh_calc_path_uv_face(BMesh *bm, + BMFace *f_src, + BMFace *f_dst, + const struct BMCalcPathUVParams *params, + bool (*filter_fn)(BMFace *, void *), + void *user_data) ATTR_WARN_UNUSED_RESULT + ATTR_NONNULL(1, 2, 3, 5); + +#endif /* __BMESH_PATH_UV_H__ */ diff --git a/source/blender/editors/include/ED_uvedit.h b/source/blender/editors/include/ED_uvedit.h index 04781bba08f..cce788efa58 100644 --- a/source/blender/editors/include/ED_uvedit.h +++ b/source/blender/editors/include/ED_uvedit.h @@ -177,6 +177,12 @@ bool ED_uvedit_nearest_uv_multi(const struct Scene *scene, void ED_uvedit_get_aspect(struct Object *obedit, float *r_aspx, float *r_aspy); +void ED_uvedit_active_vert_loop_set(struct BMesh *bm, struct BMLoop *l); +struct BMLoop *ED_uvedit_active_vert_loop_get(struct BMesh *bm); + +void ED_uvedit_active_edge_loop_set(struct BMesh *bm, struct BMLoop *l); +struct BMLoop *ED_uvedit_active_edge_loop_get(struct BMesh *bm); + /* uvedit_unwrap_ops.c */ void ED_uvedit_live_unwrap_begin(struct Scene *scene, struct Object *obedit); void ED_uvedit_live_unwrap_re_solve(void); diff --git a/source/blender/editors/mesh/mesh_intern.h b/source/blender/editors/mesh/mesh_intern.h index bebad312454..cec425d687d 100644 --- a/source/blender/editors/mesh/mesh_intern.h +++ b/source/blender/editors/mesh/mesh_intern.h @@ -76,6 +76,7 @@ struct BMElem *EDBM_elem_from_selectmode(struct BMEditMesh *em, struct BMVert *eve, struct BMEdge *eed, struct BMFace *efa); + int EDBM_elem_to_index_any(struct BMEditMesh *em, struct BMElem *ele); struct BMElem *EDBM_elem_from_index_any(struct BMEditMesh *em, int index); diff --git a/source/blender/editors/uvedit/CMakeLists.txt b/source/blender/editors/uvedit/CMakeLists.txt index 3bd6b8732f4..a39234561c2 100644 --- a/source/blender/editors/uvedit/CMakeLists.txt +++ b/source/blender/editors/uvedit/CMakeLists.txt @@ -39,8 +39,9 @@ set(SRC uvedit_buttons.c uvedit_draw.c uvedit_ops.c - uvedit_rip.c uvedit_parametrizer.c + uvedit_path.c + uvedit_rip.c uvedit_select.c uvedit_smart_stitch.c uvedit_unwrap_ops.c diff --git a/source/blender/editors/uvedit/uvedit_intern.h b/source/blender/editors/uvedit/uvedit_intern.h index 1d90cc5ae91..8b99292029e 100644 --- a/source/blender/editors/uvedit/uvedit_intern.h +++ b/source/blender/editors/uvedit/uvedit_intern.h @@ -109,6 +109,9 @@ void UV_OT_unwrap(struct wmOperatorType *ot); void UV_OT_rip(struct wmOperatorType *ot); void UV_OT_stitch(struct wmOperatorType *ot); +/* uvedit_path.c */ +void UV_OT_shortest_path_pick(struct wmOperatorType *ot); + /* uvedit_select.c */ bool uvedit_select_is_any_selected(struct Scene *scene, struct Object *obedit); diff --git a/source/blender/editors/uvedit/uvedit_ops.c b/source/blender/editors/uvedit/uvedit_ops.c index 3b913a49a16..5ae32601747 100644 --- a/source/blender/editors/uvedit/uvedit_ops.c +++ b/source/blender/editors/uvedit/uvedit_ops.c @@ -2089,6 +2089,7 @@ void ED_operatortypes_uvedit(void) WM_operatortype_append(UV_OT_rip); WM_operatortype_append(UV_OT_stitch); + WM_operatortype_append(UV_OT_shortest_path_pick); WM_operatortype_append(UV_OT_seams_from_islands); WM_operatortype_append(UV_OT_mark_seam); diff --git a/source/blender/editors/uvedit/uvedit_path.c b/source/blender/editors/uvedit/uvedit_path.c new file mode 100644 index 00000000000..3a7badc3e7b --- /dev/null +++ b/source/blender/editors/uvedit/uvedit_path.c @@ -0,0 +1,676 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup eduv + * + * \note The logic in this file closely follows editmesh_path.c + */ + +#include <math.h> +#include <stdlib.h> +#include <string.h> + +#include "BLI_linklist.h" +#include "DNA_windowmanager_types.h" +#include "MEM_guardedalloc.h" + +#include "BLI_ghash.h" +#include "BLI_linklist_stack.h" +#include "BLI_math.h" +#include "BLI_math_vector.h" +#include "BLI_utildefines.h" + +#include "DNA_image_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_node_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" +#include "DNA_space_types.h" + +#include "BKE_context.h" +#include "BKE_customdata.h" +#include "BKE_editmesh.h" +#include "BKE_layer.h" +#include "BKE_mesh.h" +#include "BKE_report.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" + +#include "ED_screen.h" +#include "ED_transform.h" +#include "ED_uvedit.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "UI_view2d.h" + +#include "intern/bmesh_marking.h" +#include "uvedit_intern.h" + +#include "bmesh_tools.h" + +#define USE_FILL + +/* -------------------------------------------------------------------- */ +/** \name Local Utilities + * \{ */ + +/** + * Support edge-path using vert-path calculation code. + * + * Cheat! Pick 2 closest loops and do vertex path, + * in practices only obscure/contrived cases will make give noticeably worse behavior. + * + * While the code below is a bit awkward, it's significantly less overhead than + * adding full edge selection which is nearly the same as vertex path in the case of UV's. + */ +static void bm_loop_calc_vert_pair_from_edge_pair(const int cd_loop_uv_offset, + const float aspect_y, + BMElem **ele_src_p, + BMElem **ele_dst_p, + BMElem **r_ele_dst_final) +{ + BMLoop *l_src = (BMLoop *)*ele_src_p; + BMLoop *l_dst = (BMLoop *)*ele_dst_p; + + const MLoopUV *luv_src_v1 = BM_ELEM_CD_GET_VOID_P(l_src, cd_loop_uv_offset); + const MLoopUV *luv_src_v2 = BM_ELEM_CD_GET_VOID_P(l_src->next, cd_loop_uv_offset); + const MLoopUV *luv_dst_v1 = BM_ELEM_CD_GET_VOID_P(l_dst, cd_loop_uv_offset); + const MLoopUV *luv_dst_v2 = BM_ELEM_CD_GET_VOID_P(l_dst->next, cd_loop_uv_offset); + + const float uv_src_v1[2] = {luv_src_v1->uv[0], luv_src_v1->uv[1] / aspect_y}; + const float uv_src_v2[2] = {luv_src_v2->uv[0], luv_src_v2->uv[1] / aspect_y}; + const float uv_dst_v1[2] = {luv_dst_v1->uv[0], luv_dst_v1->uv[1] / aspect_y}; + const float uv_dst_v2[2] = {luv_dst_v2->uv[0], luv_dst_v2->uv[1] / aspect_y}; + + struct { + int src_index; + int dst_index; + float len_sq; + } tests[4] = { + {0, 0, len_squared_v2v2(uv_src_v1, uv_dst_v1)}, + {0, 1, len_squared_v2v2(uv_src_v1, uv_dst_v2)}, + {1, 0, len_squared_v2v2(uv_src_v2, uv_dst_v1)}, + {1, 1, len_squared_v2v2(uv_src_v2, uv_dst_v2)}, + }; + int i_best = 0; + for (int i = 1; i < ARRAY_SIZE(tests); i++) { + if (tests[i].len_sq < tests[i_best].len_sq) { + i_best = i; + } + } + + *ele_src_p = (BMElem *)(tests[i_best].src_index ? l_src->next : l_src); + *ele_dst_p = (BMElem *)(tests[i_best].dst_index ? l_dst->next : l_dst); + + /* Ensure the edge is selected, not just the vertices up until we hit it. */ + *r_ele_dst_final = (BMElem *)(tests[i_best].dst_index ? l_dst : l_dst->next); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Path Select Struct & Properties + * \{ */ + +struct PathSelectParams { + /** ensure the active element is the last selected item (handy for picking) */ + bool track_active; + bool use_topology_distance; + bool use_face_step; +#ifdef USE_FILL + bool use_fill; +#endif + struct CheckerIntervalParams interval_params; +}; + +struct UserData_UV { + Scene *scene; + uint cd_loop_uv_offset; +}; + +static void path_select_properties(wmOperatorType *ot) +{ + RNA_def_boolean(ot->srna, + "use_face_step", + false, + "Face Stepping", + "Traverse connected faces (includes diagonals and edge-rings)"); + RNA_def_boolean(ot->srna, + "use_topology_distance", + false, + "Topology Distance", + "Find the minimum number of steps, ignoring spatial distance"); +#ifdef USE_FILL + RNA_def_boolean(ot->srna, + "use_fill", + false, + "Fill Region", + "Select all paths between the source/destination elements"); +#endif + + WM_operator_properties_checker_interval(ot, true); +} + +static void path_select_params_from_op(wmOperator *op, struct PathSelectParams *op_params) +{ + op_params->track_active = false; + op_params->use_face_step = RNA_boolean_get(op->ptr, "use_face_step"); +#ifdef USE_FILL + op_params->use_fill = RNA_boolean_get(op->ptr, "use_fill"); +#endif + op_params->use_topology_distance = RNA_boolean_get(op->ptr, "use_topology_distance"); + WM_operator_properties_checker_interval_from_op(op, &op_params->interval_params); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UV Vert Path + * \{ */ + +/* callbacks */ +static bool looptag_filter_cb(BMLoop *l, void *user_data_v) +{ + struct UserData_UV *user_data = user_data_v; + return uvedit_face_visible_test(user_data->scene, l->f); +} +static bool looptag_test_cb(BMLoop *l, void *user_data_v) +{ + /* All connected loops are selected or we return false. */ + struct UserData_UV *user_data = user_data_v; + const uint cd_loop_uv_offset = user_data->cd_loop_uv_offset; + const MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); + BMIter iter; + BMLoop *l_iter; + BM_ITER_ELEM (l_iter, &iter, l->v, BM_LOOPS_OF_VERT) { + if (looptag_filter_cb(l_iter, user_data)) { + const MLoopUV *luv_iter = BM_ELEM_CD_GET_VOID_P(l_iter, cd_loop_uv_offset); + if (equals_v2v2(luv->uv, luv_iter->uv)) { + if ((luv_iter->flag & MLOOPUV_VERTSEL) == 0) { + return false; + } + } + } + } + return true; +} +static void looptag_set_cb(BMLoop *l, bool val, void *user_data_v) +{ + struct UserData_UV *user_data = user_data_v; + const uint cd_loop_uv_offset = user_data->cd_loop_uv_offset; + const MLoopUV *luv = BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset); + BMIter iter; + BMLoop *l_iter; + BM_ITER_ELEM (l_iter, &iter, l->v, BM_LOOPS_OF_VERT) { + if (looptag_filter_cb(l_iter, user_data)) { + MLoopUV *luv_iter = BM_ELEM_CD_GET_VOID_P(l_iter, cd_loop_uv_offset); + if (equals_v2v2(luv->uv, luv_iter->uv)) { + SET_FLAG_FROM_TEST(luv_iter->flag, val, MLOOPUV_VERTSEL); + } + } + } +} + +static void mouse_mesh_uv_shortest_path_vert(Scene *scene, + Object *obedit, + const struct PathSelectParams *op_params, + BMLoop *l_src, + BMLoop *l_dst, + BMLoop *l_dst_add_to_path, + const float aspect_y, + const int cd_loop_uv_offset) +{ + const ToolSettings *ts = scene->toolsettings; + const bool use_fake_edge_select = (ts->uv_selectmode & UV_SELECT_EDGE); + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + + struct UserData_UV user_data = { + .scene = scene, + .cd_loop_uv_offset = cd_loop_uv_offset, + }; + + const struct BMCalcPathUVParams params = { + .use_topology_distance = false, + .aspect_y = aspect_y, + .cd_loop_uv_offset = cd_loop_uv_offset, + }; + LinkNode *path = BM_mesh_calc_path_uv_vert( + bm, l_src, l_dst, ¶ms, looptag_filter_cb, &user_data); + /* TODO: false when we support region selection. */ + bool is_path_ordered = true; + + BMLoop *l_dst_last = l_dst; + + if (path) { + if ((l_dst_add_to_path != NULL) && (BLI_linklist_index(path, l_dst_add_to_path) == -1)) { + /* Weak, we could find the last and append after that. */ + BLI_linklist_reverse(&path); + BLI_linklist_prepend(&path, l_dst_add_to_path); + BLI_linklist_reverse(&path); + } + + /* toggle the flag */ + bool all_set = true; + LinkNode *node = path; + do { + if (!looptag_test_cb((BMLoop *)node->link, &user_data)) { + all_set = false; + break; + } + } while ((node = node->next)); + + int depth = -1; + node = path; + do { + if ((is_path_ordered == false) || + WM_operator_properties_checker_interval_test(&op_params->interval_params, depth)) { + looptag_set_cb((BMLoop *)node->link, !all_set, &user_data); + if (is_path_ordered) { + l_dst_last = node->link; + } + } + } while ((void)depth++, (node = node->next)); + + BLI_linklist_free(path, NULL); + } + else { + const bool is_act = !looptag_test_cb(l_dst, &user_data); + looptag_set_cb(l_dst, is_act, &user_data); /* switch the face option */ + } + + if (op_params->track_active) { + /* Fake edge selection. */ + if (use_fake_edge_select) { + ED_uvedit_active_edge_loop_set(bm, l_dst_last); + } + else { + ED_uvedit_active_vert_loop_set(bm, l_dst_last); + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UV Face Path + * \{ */ + +/* callbacks */ +static bool facetag_filter_cb(BMFace *f, void *user_data_v) +{ + struct UserData_UV *user_data = user_data_v; + return uvedit_face_visible_test(user_data->scene, f); +} +static bool facetag_test_cb(BMFace *f, void *user_data_v) +{ + /* All connected loops are selected or we return false. */ + struct UserData_UV *user_data = user_data_v; + const uint cd_loop_uv_offset = user_data->cd_loop_uv_offset; + BMIter iter; + BMLoop *l_iter; + BM_ITER_ELEM (l_iter, &iter, f, BM_LOOPS_OF_FACE) { + const MLoopUV *luv_iter = BM_ELEM_CD_GET_VOID_P(l_iter, cd_loop_uv_offset); + if ((luv_iter->flag & MLOOPUV_VERTSEL) == 0) { + return false; + } + } + return true; +} +static void facetag_set_cb(BMFace *f, bool val, void *user_data_v) +{ + struct UserData_UV *user_data = user_data_v; + const uint cd_loop_uv_offset = user_data->cd_loop_uv_offset; + BMIter iter; + BMLoop *l_iter; + BM_ITER_ELEM (l_iter, &iter, f, BM_LOOPS_OF_FACE) { + MLoopUV *luv_iter = BM_ELEM_CD_GET_VOID_P(l_iter, cd_loop_uv_offset); + SET_FLAG_FROM_TEST(luv_iter->flag, val, MLOOPUV_VERTSEL); + } +} + +static void mouse_mesh_uv_shortest_path_face(Scene *scene, + Object *obedit, + const struct PathSelectParams *op_params, + BMFace *f_src, + BMFace *f_dst, + const float aspect_y, + const int cd_loop_uv_offset) +{ + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + + struct UserData_UV user_data = { + .scene = scene, + .cd_loop_uv_offset = cd_loop_uv_offset, + }; + + const struct BMCalcPathUVParams params = { + .use_topology_distance = false, + .aspect_y = aspect_y, + .cd_loop_uv_offset = cd_loop_uv_offset, + }; + LinkNode *path = BM_mesh_calc_path_uv_face( + bm, f_src, f_dst, ¶ms, facetag_filter_cb, &user_data); + /* TODO: false when we support region selection. */ + bool is_path_ordered = true; + + BMFace *f_dst_last = f_dst; + + if (path) { + /* toggle the flag */ + bool all_set = true; + LinkNode *node = path; + do { + if (!facetag_test_cb((BMFace *)node->link, &user_data)) { + all_set = false; + break; + } + } while ((node = node->next)); + + int depth = -1; + node = path; + do { + if ((is_path_ordered == false) || + WM_operator_properties_checker_interval_test(&op_params->interval_params, depth)) { + facetag_set_cb((BMFace *)node->link, !all_set, &user_data); + if (is_path_ordered) { + f_dst_last = node->link; + } + } + } while ((void)depth++, (node = node->next)); + + BLI_linklist_free(path, NULL); + } + else { + const bool is_act = !facetag_test_cb(f_dst, &user_data); + facetag_set_cb(f_dst, is_act, &user_data); /* switch the face option */ + } + + if (op_params->track_active) { + /* Unlike other types, we can track active without it being selected. */ + BM_mesh_active_face_set(bm, f_dst_last); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Main Operator for vert/edge/face tag + * \{ */ + +static int uv_shortest_path_pick_exec(bContext *C, wmOperator *op); + +static bool uv_shortest_path_pick_ex(Scene *scene, + Depsgraph *depsgraph, + Object *obedit, + const struct PathSelectParams *op_params, + BMElem *ele_src, + BMElem *ele_dst, + const float aspect_y, + const int cd_loop_uv_offset) +{ + bool ok = false; + + if (ELEM(NULL, ele_src, ele_dst) || (ele_src->head.htype != ele_dst->head.htype)) { + /* pass */ + } + else if (ele_src->head.htype == BM_FACE) { + mouse_mesh_uv_shortest_path_face(scene, + obedit, + op_params, + (BMFace *)ele_src, + (BMFace *)ele_dst, + aspect_y, + cd_loop_uv_offset); + ok = true; + } + else if (ele_src->head.htype == BM_LOOP) { + const ToolSettings *ts = scene->toolsettings; + BMElem *ele_dst_final = NULL; + if (ts->uv_selectmode & UV_SELECT_EDGE) { + bm_loop_calc_vert_pair_from_edge_pair( + cd_loop_uv_offset, aspect_y, &ele_src, &ele_dst, &ele_dst_final); + } + mouse_mesh_uv_shortest_path_vert(scene, + obedit, + op_params, + (BMLoop *)ele_src, + (BMLoop *)ele_dst, + (BMLoop *)ele_dst_final, + aspect_y, + cd_loop_uv_offset); + ok = true; + } + + if (ok) { + Object *obedit_eval = DEG_get_evaluated_object(depsgraph, obedit); + BKE_mesh_batch_cache_dirty_tag(obedit_eval->data, BKE_MESH_BATCH_DIRTY_UVEDIT_SELECT); + /* Only for region redraw. */ + WM_main_add_notifier(NC_GEOM | ND_SELECT, obedit->data); + } + + return ok; +} + +static int uv_shortest_path_pick_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + Scene *scene = CTX_data_scene(C); + const ToolSettings *ts = scene->toolsettings; + + /* We could support this, it needs further testing. */ + if (ts->uv_flag & UV_SYNC_SELECTION) { + BKE_report(op->reports, RPT_ERROR, "Sync selection doesn't support path select"); + return OPERATOR_CANCELLED; + } + + if (RNA_struct_property_is_set(op->ptr, "index")) { + return uv_shortest_path_pick_exec(C, op); + } + + struct PathSelectParams op_params; + path_select_params_from_op(op, &op_params); + + /* Set false if we support edge tagging. */ + op_params.track_active = true; + + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + + float co[2]; + + const ARegion *region = CTX_wm_region(C); + + Object *obedit = CTX_data_edit_object(C); + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV); + + float aspect_y; + { + float aspx, aspy; + ED_uvedit_get_aspect(obedit, &aspx, &aspy); + aspect_y = aspx / aspy; + } + + UI_view2d_region_to_view(®ion->v2d, event->mval[0], event->mval[1], &co[0], &co[1]); + + BMElem *ele_src = NULL, *ele_dst = NULL; + + if (ts->uv_selectmode & UV_SELECT_FACE) { + UvNearestHit hit = UV_NEAREST_HIT_INIT; + if (!uv_find_nearest_face(scene, obedit, co, &hit)) { + return OPERATOR_CANCELLED; + } + + BMFace *f_src = BM_mesh_active_face_get(bm, false, false); + /* Check selection? */ + + ele_src = (BMElem *)f_src; + ele_dst = (BMElem *)hit.efa; + } + else if (ts->uv_selectmode & UV_SELECT_EDGE) { + UvNearestHit hit = UV_NEAREST_HIT_INIT; + if (!uv_find_nearest_edge(scene, obedit, co, &hit)) { + return OPERATOR_CANCELLED; + } + + BMLoop *l_src = ED_uvedit_active_edge_loop_get(bm); + const MLoopUV *luv_src_v1 = BM_ELEM_CD_GET_VOID_P(l_src, cd_loop_uv_offset); + const MLoopUV *luv_src_v2 = BM_ELEM_CD_GET_VOID_P(l_src->next, cd_loop_uv_offset); + if ((luv_src_v1->flag & MLOOPUV_VERTSEL) == 0 && (luv_src_v2->flag & MLOOPUV_VERTSEL) == 0) { + l_src = NULL; + } + + ele_src = (BMElem *)l_src; + ele_dst = (BMElem *)hit.l; + } + else { + UvNearestHit hit = UV_NEAREST_HIT_INIT; + if (!uv_find_nearest_vert(scene, obedit, co, 0.0f, &hit)) { + return OPERATOR_CANCELLED; + } + + BMLoop *l_src = ED_uvedit_active_vert_loop_get(bm); + const MLoopUV *luv_src = BM_ELEM_CD_GET_VOID_P(l_src, cd_loop_uv_offset); + if ((luv_src->flag & MLOOPUV_VERTSEL) == 0) { + l_src = NULL; + } + + ele_src = (BMElem *)l_src; + ele_dst = (BMElem *)hit.l; + } + + if (ele_src == NULL || ele_dst == NULL) { + return OPERATOR_CANCELLED; + } + + uv_shortest_path_pick_ex( + scene, depsgraph, obedit, &op_params, ele_src, ele_dst, aspect_y, cd_loop_uv_offset); + + /* To support redo. */ + int index; + if (ts->uv_selectmode & UV_SELECT_FACE) { + BM_mesh_elem_index_ensure(bm, BM_FACE); + index = BM_elem_index_get(ele_dst); + } + else if (ts->uv_selectmode & UV_SELECT_EDGE) { + BM_mesh_elem_index_ensure(bm, BM_LOOP); + index = BM_elem_index_get(ele_dst); + } + else { + BM_mesh_elem_index_ensure(bm, BM_LOOP); + index = BM_elem_index_get(ele_dst); + } + RNA_int_set(op->ptr, "index", index); + + return OPERATOR_FINISHED; +} + +static int 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; + Object *obedit = CTX_data_edit_object(C); + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_MLOOPUV); + + float aspect_y; + { + float aspx, aspy; + ED_uvedit_get_aspect(obedit, &aspx, &aspy); + aspect_y = aspx / aspy; + } + + const int index = RNA_int_get(op->ptr, "index"); + + BMElem *ele_src, *ele_dst; + + if (ts->uv_selectmode & UV_SELECT_FACE) { + if (index < 0 || index >= bm->totface) { + return OPERATOR_CANCELLED; + } + if (!(ele_src = (BMElem *)BM_mesh_active_face_get(bm, false, false)) || + !(ele_dst = (BMElem *)BM_face_at_index_find_or_table(bm, index))) { + return OPERATOR_CANCELLED; + } + } + if (ts->uv_selectmode & UV_SELECT_EDGE) { + if (index < 0 || index >= bm->totloop) { + return OPERATOR_CANCELLED; + } + if (!(ele_src = (BMElem *)ED_uvedit_active_edge_loop_get(bm)) || + !(ele_dst = (BMElem *)BM_loop_at_index_find(bm, index))) { + return OPERATOR_CANCELLED; + } + } + else { + if (index < 0 || index >= bm->totloop) { + return OPERATOR_CANCELLED; + } + if (!(ele_src = (BMElem *)ED_uvedit_active_vert_loop_get(bm)) || + !(ele_dst = (BMElem *)BM_loop_at_index_find(bm, index))) { + return OPERATOR_CANCELLED; + } + } + + struct PathSelectParams op_params; + path_select_params_from_op(op, &op_params); + op_params.track_active = true; + + if (!uv_shortest_path_pick_ex( + scene, depsgraph, obedit, &op_params, ele_src, ele_dst, aspect_y, cd_loop_uv_offset)) { + return OPERATOR_CANCELLED; + } + + return OPERATOR_FINISHED; +} + +void UV_OT_shortest_path_pick(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Pick Shortest Path"; + ot->idname = "UV_OT_shortest_path_pick"; + ot->description = "Select shortest path between two selections"; + + /* api callbacks */ + ot->invoke = uv_shortest_path_pick_invoke; + ot->exec = uv_shortest_path_pick_exec; + ot->poll = ED_operator_uvedit; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + path_select_properties(ot); + + /* use for redo */ + prop = RNA_def_int(ot->srna, "index", -1, -1, INT_MAX, "", "", 0, INT_MAX); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); +} + +/** \} */ diff --git a/source/blender/editors/uvedit/uvedit_select.c b/source/blender/editors/uvedit/uvedit_select.c index afc5739fcbd..a589114fd46 100644 --- a/source/blender/editors/uvedit/uvedit_select.c +++ b/source/blender/editors/uvedit/uvedit_select.c @@ -88,6 +88,59 @@ static void uv_select_tag_update_for_object(Depsgraph *depsgraph, Object *obedit); /* -------------------------------------------------------------------- */ +/** \name Active Selection Tracking + * + * Currently we don't store loops in the selection history, + * store face/edge/vert combinations (needed for UV path selection). + * \{ */ + +void ED_uvedit_active_vert_loop_set(BMesh *bm, BMLoop *l) +{ + BM_select_history_clear(bm); + BM_select_history_remove(bm, (BMElem *)l->f); + BM_select_history_remove(bm, (BMElem *)l->v); + BM_select_history_store_notest(bm, (BMElem *)l->f); + BM_select_history_store_notest(bm, (BMElem *)l->v); +} + +BMLoop *ED_uvedit_active_vert_loop_get(BMesh *bm) +{ + BMEditSelection *ese = bm->selected.last; + if (ese && ese->prev) { + BMEditSelection *ese_prev = ese->prev; + if ((ese->htype == BM_VERT) && (ese_prev->htype == BM_FACE)) { + /* May be NULL. */ + return BM_face_vert_share_loop((BMFace *)ese_prev->ele, (BMVert *)ese->ele); + } + } + return NULL; +} + +void ED_uvedit_active_edge_loop_set(BMesh *bm, BMLoop *l) +{ + BM_select_history_clear(bm); + BM_select_history_remove(bm, (BMElem *)l->f); + BM_select_history_remove(bm, (BMElem *)l->e); + BM_select_history_store_notest(bm, (BMElem *)l->f); + BM_select_history_store_notest(bm, (BMElem *)l->e); +} + +BMLoop *ED_uvedit_active_edge_loop_get(BMesh *bm) +{ + BMEditSelection *ese = bm->selected.last; + if (ese && ese->prev) { + BMEditSelection *ese_prev = ese->prev; + if ((ese->htype == BM_EDGE) && (ese_prev->htype == BM_FACE)) { + /* May be NULL. */ + return BM_face_edge_share_loop((BMFace *)ese_prev->ele, (BMEdge *)ese->ele); + } + } + return NULL; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Visibility and Selection Utilities * \{ */ @@ -1531,6 +1584,11 @@ static int uv_mouse_select_multi(bContext *C, hituv[hit.lindex] = hit.luv->uv; hitlen = hit.efa->len; + + if ((ts->uv_flag & UV_SYNC_SELECTION) == 0) { + BMesh *bm = BKE_editmesh_from_object(hit.ob)->bm; + ED_uvedit_active_vert_loop_set(bm, hit.l); + } } } else if (selectmode == UV_SELECT_EDGE) { @@ -1550,6 +1608,11 @@ static int uv_mouse_select_multi(bContext *C, hituv[(hit.lindex + 1) % hit.efa->len] = hit.luv_next->uv; hitlen = hit.efa->len; + + if ((ts->uv_flag & UV_SYNC_SELECTION) == 0) { + BMesh *bm = BKE_editmesh_from_object(hit.ob)->bm; + ED_uvedit_active_edge_loop_set(bm, hit.l); + } } } else if (selectmode == UV_SELECT_FACE) { |