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.
This commit is contained in:
@@ -850,6 +850,7 @@ def km_uv_editor(params):
|
||||
{"properties": [("extend", False)]}),
|
||||
("uv.select_loop", {"type": params.select_mouse, "value": params.select_mouse_value, "shift": True, "alt": True},
|
||||
{"properties": [("extend", True)]}),
|
||||
("uv.shortest_path_pick", {"type": params.select_mouse, "value": params.select_mouse_value, "ctrl": True}, None),
|
||||
("uv.select_split", {"type": 'Y', "value": 'PRESS'}, None),
|
||||
("uv.select_box", {"type": 'B', "value": 'PRESS'},
|
||||
{"properties": [("pinned", False)]}),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
432
source/blender/bmesh/tools/bmesh_path_uv.c
Normal file
432
source/blender/bmesh/tools/bmesh_path_uv.c
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
47
source/blender/bmesh/tools/bmesh_path_uv.h
Normal file
47
source/blender/bmesh/tools/bmesh_path_uv.h
Normal file
@@ -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__ */
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
676
source/blender/editors/uvedit/uvedit_path.c
Normal file
676
source/blender/editors/uvedit/uvedit_path.c
Normal file
@@ -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);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
@@ -87,6 +87,59 @@ static void uv_select_tag_update_for_object(Depsgraph *depsgraph,
|
||||
const ToolSettings *ts,
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user