Files
test2/source/blender/editors/uvedit/uvedit_select.cc
Campbell Barton 5850fad63a Fix: UV box/lasso/circle selection fails with edge & face modes enabled
When select-sync was used with both edge & face modes enabled,
vertex selection logic was used which resulted in no visible selection.

Now edge selection is used when both edge and face modes are enabled.

Ref !148181
2025-10-16 17:35:16 +11:00

6749 lines
207 KiB
C++

/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eduv
*/
#include <cmath>
#include <cstdlib>
#include <cstring>
#include "MEM_guardedalloc.h"
#include "DNA_node_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_space_types.h"
#include "BLI_hash.h"
#include "BLI_heap.h"
#include "BLI_kdopbvh.hh"
#include "BLI_kdtree.h"
#include "BLI_lasso_2d.hh"
#include "BLI_listbase.h"
#include "BLI_math_geom.h"
#include "BLI_math_matrix.h"
#include "BLI_math_vector.h"
#include "BLI_memarena.h"
#include "BLI_polyfill_2d.h"
#include "BLI_polyfill_2d_beautify.h"
#include "BLI_utildefines.h"
#include "BLI_vector_list.hh"
#include "BLT_translation.hh"
#include "BKE_context.hh"
#include "BKE_customdata.hh"
#include "BKE_editmesh.hh"
#include "BKE_layer.hh"
#include "BKE_material.hh"
#include "BKE_mesh.hh"
#include "BKE_mesh_mapping.hh"
#include "BKE_report.hh"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_query.hh"
#include "ED_image.hh"
#include "ED_mesh.hh"
#include "ED_screen.hh"
#include "ED_select_utils.hh"
#include "ED_uvedit.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "RNA_enum_types.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "UI_view2d.hh"
#include "uvedit_intern.hh"
using blender::Array;
using blender::int2;
using blender::Span;
using blender::Vector;
static void uv_select_all_perform_multi_ex(const Scene *scene,
Span<Object *> objects,
int action,
const Object *ob_exclude);
static void uv_select_all_perform_multi(const Scene *scene, Span<Object *> objects, int action);
static void uv_select_flush_from_tag_face(const Scene *scene, Object *obedit, const bool select);
static void uv_select_flush_from_tag_loop(const Scene *scene, Object *obedit, const bool select);
static void uv_select_flush_from_loop_edge_flag(const Scene *scene, BMesh *bm);
static void uv_select_tag_update_for_object(Depsgraph *depsgraph,
const ToolSettings *ts,
Object *obedit);
enum eUVSelectSimilar {
UV_SSIM_AREA_UV = 1000,
UV_SSIM_AREA_3D,
UV_SSIM_FACE,
UV_SSIM_LENGTH_UV,
UV_SSIM_LENGTH_3D,
UV_SSIM_MATERIAL,
UV_SSIM_OBJECT,
UV_SSIM_PIN,
UV_SSIM_SIDES,
UV_SSIM_WINDING,
};
/* -------------------------------------------------------------------- */
/** \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(const ToolSettings *ts, BMesh *bm)
{
BMEditSelection *ese = static_cast<BMEditSelection *>(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)) {
/* May be null. */
return BM_face_vert_share_loop((BMFace *)ese_prev->ele, (BMVert *)ese->ele);
}
}
return nullptr;
}
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(const ToolSettings *ts, BMesh *bm)
{
BMEditSelection *ese = static_cast<BMEditSelection *>(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)) {
/* May be null. */
return BM_face_edge_share_loop((BMFace *)ese_prev->ele, (BMEdge *)ese->ele);
}
}
return nullptr;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \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;
char uv_selectmode = UV_SELECT_VERT;
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
if (ts->selectmode & SCE_SELECT_VERTEX) {
uv_selectmode = UV_SELECT_VERT;
}
else if (ts->selectmode & SCE_SELECT_EDGE) {
uv_selectmode = UV_SELECT_EDGE;
}
else if (ts->selectmode & SCE_SELECT_FACE) {
uv_selectmode = UV_SELECT_FACE;
}
}
else {
if (ts->uv_selectmode & UV_SELECT_VERT) {
uv_selectmode = UV_SELECT_VERT;
}
else if (ts->uv_selectmode & UV_SELECT_EDGE) {
uv_selectmode = UV_SELECT_EDGE;
}
else if (ts->uv_selectmode & UV_SELECT_FACE) {
uv_selectmode = UV_SELECT_FACE;
}
}
return uv_selectmode;
}
bool ED_uvedit_select_island_check(const ToolSettings *ts)
{
if ((ts->uv_flag & UV_FLAG_SELECT_ISLAND) == 0) {
return false;
}
/* NOTE: when "strict" only return true when it's possible to select an island in isolation.
* At the moment none of the callers require this however it may be necessary to ignore the
* "island" selection option for some operations in the future.
* This could be exposed as an argument. */
const bool strict = false;
if (strict) {
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
if (ts->selectmode & (SCE_SELECT_VERTEX | SCE_SELECT_EDGE)) {
return false;
}
}
}
return true;
}
void ED_uvedit_select_sync_flush(const ToolSettings *ts, BMesh *bm, const bool select)
{
/* bmesh API handles flushing but not on de-select */
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
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);
}
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 {
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);
}
}
}
}
if (select == false) {
BM_select_history_validate(bm);
}
}
}
static void uvedit_vertex_select_tagged(BMesh *bm, const Scene *scene, bool select)
{
BMFace *efa;
BMLoop *l;
BMIter iter, liter;
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->v, BM_ELEM_TAG)) {
uvedit_uv_select_set(scene, bm, l, select);
}
}
}
}
bool uvedit_face_visible_test_ex(const ToolSettings *ts, const BMFace *efa)
{
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
return (BM_elem_flag_test(efa, BM_ELEM_HIDDEN) == 0);
}
return (BM_elem_flag_test(efa, BM_ELEM_HIDDEN) == 0 && BM_elem_flag_test(efa, BM_ELEM_SELECT));
}
bool uvedit_face_visible_test(const Scene *scene, const BMFace *efa)
{
return uvedit_face_visible_test_ex(scene->toolsettings, efa);
}
bool uvedit_face_select_test_ex(const ToolSettings *ts, const BMesh *bm, const BMFace *efa)
{
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
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);
}
if (ts->uv_selectmode == UV_SELECT_FACE) {
if (!BM_elem_flag_test(efa, BM_ELEM_SELECT_UV)) {
return false;
}
return true;
}
const char hflag_test = (ts->uv_selectmode & UV_SELECT_VERT) ? BM_ELEM_SELECT_UV :
BM_ELEM_SELECT_UV_EDGE;
const BMLoop *l_first = BM_FACE_FIRST_LOOP(efa);
const BMLoop *l_iter = l_first;
do {
if (!BM_elem_flag_test(l_iter, hflag_test)) {
return false;
}
} while ((l_iter = l_iter->next) != l_first);
return true;
}
bool uvedit_face_select_test(const Scene *scene, const BMesh *bm, const BMFace *efa)
{
return uvedit_face_select_test_ex(scene->toolsettings, bm, efa);
}
void uvedit_face_select_set_with_sticky(
const Scene *scene, BMesh *bm, BMFace *efa, const bool select, const BMUVOffsets &offsets)
{
const ToolSettings *ts = scene->toolsettings;
const char sticky = ts->uv_sticky;
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
if (ED_uvedit_sync_uvselect_ignore(ts)) {
uvedit_face_select_set(scene, bm, efa, select);
return;
}
BLI_assert(ED_uvedit_sync_uvselect_is_valid_or_ignore(ts, bm));
}
if (!uvedit_face_visible_test(scene, efa)) {
return;
}
/* NOTE: Previously face selections done in sticky vertex mode selected stray UV vertices
* (not part of any face selections). This now uses the sticky location mode logic instead. */
switch (sticky) {
case UV_STICKY_DISABLE: {
uvedit_face_select_set(scene, bm, efa, select);
break;
}
default: {
/* UV_STICKY_LOCATION and UV_STICKY_VERT modes. */
uvedit_face_select_shared_vert(scene, bm, efa, select, 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;
}
uvedit_face_select_set_no_sync(ts, bm, efa, select);
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
uvedit_edge_select_set_no_sync(ts, bm, l, select);
if (select) {
uvedit_uv_select_shared_vert(scene, bm, l, select, UV_STICKY_LOCATION, offsets);
}
else {
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);
}
}
}
}
void uvedit_face_select_set(const Scene *scene, BMesh *bm, BMFace *efa, const bool select)
{
if (select) {
uvedit_face_select_enable(scene, bm, efa);
}
else {
uvedit_face_select_disable(scene, bm, efa);
}
}
void uvedit_face_select_enable(const Scene *scene, BMesh *bm, BMFace *efa)
{
const ToolSettings *ts = scene->toolsettings;
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
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;
BMIter liter;
uvedit_face_select_set_no_sync(ts, bm, efa, true);
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
uvedit_vert_select_set_no_sync(ts, bm, l, true);
uvedit_edge_select_set_no_sync(ts, bm, l, true);
}
}
}
void uvedit_face_select_disable(const Scene *scene, BMesh *bm, BMFace *efa)
{
const ToolSettings *ts = scene->toolsettings;
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
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;
BMIter liter;
uvedit_face_select_set_no_sync(ts, bm, efa, false);
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
uvedit_vert_select_set_no_sync(ts, bm, l, false);
uvedit_edge_select_set_no_sync(ts, bm, l, false);
}
}
}
bool uvedit_edge_select_test_ex(const ToolSettings *ts,
const BMesh *bm,
const BMLoop *l,
const BMUVOffsets &offsets)
{
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
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, bm, l, offsets)) {
return true;
}
return false;
}
case UV_STICKY_DISABLE: {
return BM_elem_flag_test_bool(l->f, BM_ELEM_SELECT);
}
default: {
/* #UV_STICKY_VERT */
return BM_elem_flag_test_bool(l->e, BM_ELEM_SELECT);
}
}
BLI_assert_unreachable();
}
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);
}
return BM_elem_flag_test(l, BM_ELEM_SELECT_UV_EDGE);
}
if (ts->uv_selectmode & UV_SELECT_VERT) {
return uvedit_vert_select_get_no_sync(ts, bm, l) &&
uvedit_vert_select_get_no_sync(ts, bm, l->next);
}
return uvedit_edge_select_get_no_sync(ts, bm, l);
}
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, bm, l, 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) {
if (ED_uvedit_sync_uvselect_ignore(ts)) {
uvedit_edge_select_set(scene, bm, l, select);
return;
}
BLI_assert(ED_uvedit_sync_uvselect_is_valid_or_ignore(ts, bm));
}
const int sticky = ts->uv_sticky;
switch (sticky) {
case UV_STICKY_DISABLE: {
if (uvedit_face_visible_test(scene, l->f)) {
uvedit_edge_select_set(scene, bm, l, select);
}
break;
}
case UV_STICKY_VERT: {
uvedit_edge_select_shared_vert(scene, bm, l, select, UV_STICKY_VERT, offsets);
break;
}
default: {
/* UV_STICKY_LOCATION (Fallback) */
uvedit_edge_select_shared_vert(scene, bm, l, select, UV_STICKY_LOCATION, offsets);
break;
}
}
}
static bool UNUSED_FUNCTION(bm_loop_select_vert_check_internal)(const Scene *scene,
BMesh *bm,
BMLoop *l)
{
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 uvedit_vert_select_get_no_sync(ts, bm, l);
}
static bool bm_loop_select_edge_check_internal(const Scene *scene, BMesh *bm, BMLoop *l)
{
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 uvedit_edge_select_get_no_sync(ts, bm, l);
}
void uvedit_edge_select_shared_vert(const Scene *scene,
BMesh *bm,
BMLoop *l,
const bool select,
const int sticky_flag,
const BMUVOffsets &offsets)
{
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, bm, l, select, sticky_flag, offsets);
/* Vert selections. */
BMLoop *l_iter = l;
do {
if (select) {
if (bm_loop_select_edge_check_internal(scene, bm, l_iter)) {
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_loop_select_edge_check_internal(scene, bm, l_iter)) {
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, 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)
{
const ToolSettings *ts = scene->toolsettings;
if ((ts->uv_flag & UV_FLAG_SELECT_SYNC) == 0) {
BLI_assert(offsets.uv >= 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)) {
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
BM_loop_edge_uvselect_set_noflush(bm, l_iter, select);
}
else {
uvedit_edge_select_set_no_sync(ts, bm, l, 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)
{
if (select) {
uvedit_edge_select_enable(scene, bm, l);
}
else {
uvedit_edge_select_disable(scene, bm, l);
}
}
void uvedit_edge_select_enable(const Scene *scene, BMesh *bm, BMLoop *l)
{
const ToolSettings *ts = scene->toolsettings;
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
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_loop_edge_uvselect_set(bm, l, true);
}
}
else {
uvedit_vert_select_set_no_sync(ts, bm, l, true);
uvedit_vert_select_set_no_sync(ts, bm, l->next, true);
uvedit_edge_select_set_no_sync(ts, bm, l, true);
}
}
void uvedit_edge_select_disable(const Scene *scene, BMesh *bm, BMLoop *l)
{
const ToolSettings *ts = scene->toolsettings;
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
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_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 {
uvedit_edge_select_set_no_sync(ts, bm, l, false);
if ((ts->uv_selectmode & UV_SELECT_VERT) == 0) {
/* Deselect UV vertex if not part of another edge selection */
if (!uvedit_edge_select_get_no_sync(ts, bm, l->next)) {
uvedit_vert_select_set_no_sync(ts, bm, l->next, false);
}
if (!uvedit_edge_select_get_no_sync(ts, bm, l->prev)) {
uvedit_vert_select_set_no_sync(ts, bm, l, false);
}
}
else {
uvedit_vert_select_set_no_sync(ts, bm, l, false);
uvedit_vert_select_set_no_sync(ts, bm, l->next, false);
}
}
}
bool uvedit_uv_select_test_ex(const ToolSettings *ts,
const BMesh *bm,
const BMLoop *l,
const BMUVOffsets &offsets)
{
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
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, bm, l, offsets)) {
return true;
}
return false;
}
case UV_STICKY_DISABLE: {
return BM_elem_flag_test_bool(l->f, BM_ELEM_SELECT);
}
default: {
/* #UV_STICKY_VERT */
return BM_elem_flag_test_bool(l->v, BM_ELEM_SELECT);
}
}
BLI_assert_unreachable();
}
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, bm, l, offsets)) {
return true;
}
return false;
}
case UV_STICKY_DISABLE: {
return BM_elem_flag_test(l->e, BM_ELEM_SELECT) ||
BM_elem_flag_test(l->prev->e, BM_ELEM_SELECT);
}
default: {
/* #UV_STICKY_VERT */
return BM_elem_flag_test_bool(l->v, BM_ELEM_SELECT);
}
}
BLI_assert_unreachable();
}
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);
}
return BM_elem_flag_test_bool(l, BM_ELEM_SELECT_UV);
}
if (ts->selectmode & SCE_SELECT_FACE) {
/* Are you looking for `uvedit_face_select_test(...)` instead? */
}
if (ts->selectmode & SCE_SELECT_EDGE) {
/* Are you looking for `uvedit_edge_select_test(...)` instead? */
}
return uvedit_vert_select_get_no_sync(ts, bm, l);
}
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, bm, l, offsets);
}
void uvedit_uv_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) {
if (ED_uvedit_sync_uvselect_ignore(ts)) {
uvedit_uv_select_set(scene, bm, l, select);
return;
}
}
const int sticky = ts->uv_sticky;
switch (sticky) {
case UV_STICKY_DISABLE: {
if (uvedit_face_visible_test(scene, l->f)) {
uvedit_uv_select_set(scene, bm, l, select);
}
break;
}
case UV_STICKY_VERT: {
uvedit_uv_select_shared_vert(scene, bm, l, select, UV_STICKY_VERT, offsets);
break;
}
default: {
/* UV_STICKY_LOCATION. */
uvedit_uv_select_shared_vert(scene, bm, l, select, UV_STICKY_LOCATION, offsets);
break;
}
}
}
void uvedit_uv_select_shared_vert(const Scene *scene,
BMesh *bm,
BMLoop *l,
const bool select,
const int sticky_flag,
const BMUVOffsets &offsets)
{
BLI_assert(ELEM(sticky_flag, UV_STICKY_LOCATION, UV_STICKY_VERT));
BLI_assert(offsets.uv >= 0);
BMEdge *e_first, *e_iter;
e_first = e_iter = l->e;
do {
BMLoop *l_radial_iter = e_iter->l;
if (!l_radial_iter) {
continue; /* Skip wire edges with no loops. */
}
do {
if (l_radial_iter->v == l->v) {
if (uvedit_face_visible_test(scene, l_radial_iter->f)) {
bool do_select = false;
if (sticky_flag == UV_STICKY_VERT) {
do_select = true;
}
else if (BM_loop_uv_share_vert_check(l, l_radial_iter, offsets.uv)) {
do_select = true;
}
if (do_select) {
uvedit_uv_select_set(scene, bm, l_radial_iter, select);
}
}
}
} while ((l_radial_iter = l_radial_iter->radial_next) != e_iter->l);
} while ((e_iter = BM_DISK_EDGE_NEXT(e_iter, l->v)) != e_first);
}
void uvedit_uv_select_set(const Scene *scene, BMesh *bm, BMLoop *l, const bool select)
{
if (select) {
uvedit_uv_select_enable(scene, bm, l);
}
else {
uvedit_uv_select_disable(scene, bm, l);
}
}
void uvedit_uv_select_enable(const Scene *scene, BMesh *bm, BMLoop *l)
{
const ToolSettings *ts = scene->toolsettings;
if (ts->selectmode & SCE_SELECT_EDGE) {
/* Are you looking for `uvedit_edge_select_set(...)` instead? */
}
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
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_loop_vert_uvselect_set_noflush(bm, l, true);
}
}
else {
uvedit_vert_select_set_no_sync(ts, bm, l, true);
}
}
void uvedit_uv_select_disable(const Scene *scene, BMesh *bm, BMLoop *l)
{
const ToolSettings *ts = scene->toolsettings;
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
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_loop_vert_uvselect_set_noflush(bm, l, false);
}
}
else {
uvedit_vert_select_set_no_sync(ts, bm, l, false);
}
}
static BMLoop *uvedit_loop_find_other_radial_loop_with_visible_face(const Scene *scene,
BMLoop *l_src,
const BMUVOffsets &offsets)
{
BLI_assert(offsets.uv >= 0);
BMLoop *l_other = nullptr;
BMLoop *l_iter = l_src->radial_next;
if (l_iter != l_src) {
do {
if (uvedit_face_visible_test(scene, l_iter->f) &&
BM_loop_uv_share_edge_check(l_src, l_iter, offsets.uv))
{
/* Check UVs are contiguous. */
if (l_other == nullptr) {
l_other = l_iter;
}
else {
/* Only use when there is a single alternative. */
l_other = nullptr;
break;
}
}
} while ((l_iter = l_iter->radial_next) != l_src);
}
return l_other;
}
static BMLoop *uvedit_loop_find_other_boundary_loop_with_visible_face(const Scene *scene,
BMLoop *l_edge,
BMVert *v_pivot,
const BMUVOffsets &offsets)
{
BLI_assert(uvedit_loop_find_other_radial_loop_with_visible_face(scene, l_edge, offsets) ==
nullptr);
BMLoop *l_step = l_edge;
l_step = (l_step->v == v_pivot) ? l_step->prev : l_step->next;
BMLoop *l_step_last = nullptr;
do {
BLI_assert(BM_vert_in_edge(l_step->e, v_pivot));
l_step_last = l_step;
l_step = uvedit_loop_find_other_radial_loop_with_visible_face(scene, l_step, offsets);
if (l_step) {
l_step = (l_step->v == v_pivot) ? l_step->prev : l_step->next;
}
} while (l_step != nullptr);
if (l_step_last != nullptr) {
BLI_assert(uvedit_loop_find_other_radial_loop_with_visible_face(scene, l_step_last, offsets) ==
nullptr);
}
return l_step_last;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Low Level Selection API
* \{ */
bool uvedit_loop_vert_select_get(const ToolSettings *ts, const BMesh *bm, const BMLoop *l)
{
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 uvedit_vert_select_get_no_sync(ts, bm, l);
}
bool uvedit_loop_edge_select_get(const ToolSettings *ts, const BMesh *bm, const BMLoop *l)
{
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 uvedit_edge_select_get_no_sync(ts, bm, l);
}
void uvedit_loop_vert_select_set(const ToolSettings *ts,
const BMesh *bm,
BMLoop *l,
const bool select)
{
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;
}
uvedit_vert_select_set_no_sync(ts, bm, l, select);
}
void uvedit_loop_edge_select_set(const ToolSettings *ts,
const BMesh *bm,
BMLoop *l,
const bool select)
{
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;
}
uvedit_edge_select_set_no_sync(ts, bm, l, select);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Find Nearest Elements
* \{ */
UvNearestHit uv_nearest_hit_init_dist_px(const View2D *v2d, const float dist_px)
{
UvNearestHit hit = {nullptr};
hit.dist_sq = square_f(U.pixelsize * dist_px);
hit.scale[0] = UI_view2d_scale_get_x(v2d);
hit.scale[1] = UI_view2d_scale_get_y(v2d);
return hit;
}
UvNearestHit uv_nearest_hit_init_max(const View2D *v2d)
{
UvNearestHit hit = {nullptr};
hit.dist_sq = FLT_MAX;
hit.scale[0] = UI_view2d_scale_get_x(v2d);
hit.scale[1] = UI_view2d_scale_get_y(v2d);
return hit;
}
UvNearestHit uv_nearest_hit_init_max_default()
{
UvNearestHit hit = {nullptr};
hit.dist_sq = FLT_MAX;
hit.scale[0] = 1.0f;
hit.scale[1] = 1.0f;
return hit;
}
bool uv_find_nearest_edge(
Scene *scene, Object *obedit, const float co[2], const float penalty, UvNearestHit *hit)
{
BLI_assert((hit->scale[0] > 0.0f) && (hit->scale[1] > 0.0f));
BMesh *bm = BKE_editmesh_from_object(obedit)->bm;
BMFace *efa;
BMLoop *l;
BMIter iter, liter;
float *luv, *luv_next;
int i;
bool found = false;
const BMUVOffsets offsets = BM_uv_map_offsets_get(bm);
BLI_assert(offsets.uv >= 0);
BM_mesh_elem_index_ensure(bm, BM_VERT);
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
if (!uvedit_face_visible_test(scene, efa)) {
continue;
}
BM_ITER_ELEM_INDEX (l, &liter, efa, BM_LOOPS_OF_FACE, i) {
luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
luv_next = BM_ELEM_CD_GET_FLOAT_P(l->next, offsets.uv);
float delta[2];
closest_to_line_segment_v2(delta, co, luv, luv_next);
sub_v2_v2(delta, co);
mul_v2_v2(delta, hit->scale);
float dist_test_sq = len_squared_v2(delta);
/* Ensures that successive selection attempts will select other edges sharing the same
* UV coordinates as the previous selection. */
if ((penalty != 0.0f) && uvedit_edge_select_test(scene, bm, l, offsets)) {
dist_test_sq = square_f(sqrtf(dist_test_sq) + penalty);
}
if (dist_test_sq < hit->dist_sq) {
hit->ob = obedit;
hit->efa = efa;
hit->l = l;
hit->dist_sq = dist_test_sq;
found = true;
}
}
}
return found;
}
bool uv_find_nearest_edge_multi(Scene *scene,
const Span<Object *> objects,
const float co[2],
const float penalty,
UvNearestHit *hit)
{
bool found = false;
for (Object *obedit : objects) {
if (uv_find_nearest_edge(scene, obedit, co, penalty, hit)) {
found = true;
}
}
return found;
}
bool uv_find_nearest_face_ex(
Scene *scene, Object *obedit, const float co[2], UvNearestHit *hit, const bool only_in_face)
{
BLI_assert((hit->scale[0] > 0.0f) && (hit->scale[1] > 0.0f));
BMesh *bm = BKE_editmesh_from_object(obedit)->bm;
bool found = false;
const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_PROP_FLOAT2);
BMIter iter;
BMFace *efa;
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
if (!uvedit_face_visible_test(scene, efa)) {
continue;
}
float cent[2];
BM_face_uv_calc_center_median(efa, cd_loop_uv_offset, cent);
float delta[2];
sub_v2_v2v2(delta, co, cent);
mul_v2_v2(delta, hit->scale);
const float dist_test_sq = len_squared_v2(delta);
if (dist_test_sq < hit->dist_sq) {
if (only_in_face) {
if (!BM_face_uv_point_inside_test(efa, co, cd_loop_uv_offset)) {
continue;
}
}
hit->ob = obedit;
hit->efa = efa;
hit->dist_sq = dist_test_sq;
found = true;
}
}
return found;
}
bool uv_find_nearest_face(Scene *scene, Object *obedit, const float co[2], UvNearestHit *hit)
{
return uv_find_nearest_face_ex(scene, obedit, co, hit, false);
}
bool uv_find_nearest_face_multi_ex(Scene *scene,
const Span<Object *> objects,
const float co[2],
UvNearestHit *hit,
const bool only_in_face)
{
bool found = false;
for (Object *obedit : objects) {
if (uv_find_nearest_face_ex(scene, obedit, co, hit, only_in_face)) {
found = true;
}
}
return found;
}
bool uv_find_nearest_face_multi(Scene *scene,
const Span<Object *> objects,
const float co[2],
UvNearestHit *hit)
{
return uv_find_nearest_face_multi_ex(scene, objects, co, hit, false);
}
static bool uv_nearest_between(const BMLoop *l, const float co[2], const int cd_loop_uv_offset)
{
const float *uv_prev = BM_ELEM_CD_GET_FLOAT_P(l->prev, cd_loop_uv_offset);
const float *uv_curr = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
const float *uv_next = BM_ELEM_CD_GET_FLOAT_P(l->next, cd_loop_uv_offset);
return ((line_point_side_v2(uv_prev, uv_curr, co) > 0.0f) &&
(line_point_side_v2(uv_next, uv_curr, co) <= 0.0f));
}
bool uv_find_nearest_vert(
Scene *scene, Object *obedit, float const co[2], const float penalty_dist, UvNearestHit *hit)
{
BLI_assert((hit->scale[0] > 0.0f) && (hit->scale[1] > 0.0f));
bool found = false;
BMesh *bm = BKE_editmesh_from_object(obedit)->bm;
BMFace *efa;
BMIter iter;
BM_mesh_elem_index_ensure(bm, BM_VERT);
const BMUVOffsets offsets = BM_uv_map_offsets_get(bm);
BLI_assert(offsets.uv >= 0);
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
if (!uvedit_face_visible_test(scene, efa)) {
continue;
}
BMIter liter;
BMLoop *l;
int i;
BM_ITER_ELEM_INDEX (l, &liter, efa, BM_LOOPS_OF_FACE, i) {
float *luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
float delta[2];
sub_v2_v2v2(delta, co, luv);
mul_v2_v2(delta, hit->scale);
float dist_test_sq = len_squared_v2(delta);
/* Ensures that successive selection attempts will select other vertices sharing the same
* UV coordinates */
if ((penalty_dist != 0.0f) && uvedit_uv_select_test(scene, bm, l, offsets)) {
dist_test_sq = square_f(sqrtf(dist_test_sq) + penalty_dist);
}
if (dist_test_sq <= hit->dist_sq) {
if (dist_test_sq == hit->dist_sq) {
if (!uv_nearest_between(l, co, offsets.uv)) {
continue;
}
}
hit->dist_sq = dist_test_sq;
hit->ob = obedit;
hit->efa = efa;
hit->l = l;
found = true;
}
}
}
return found;
}
bool uv_find_nearest_vert_multi(Scene *scene,
const Span<Object *> objects,
float const co[2],
const float penalty_dist,
UvNearestHit *hit)
{
bool found = false;
for (Object *obedit : objects) {
if (uv_find_nearest_vert(scene, obedit, co, penalty_dist, hit)) {
found = true;
}
}
return found;
}
static bool uvedit_nearest_uv(const Scene *scene,
Object *obedit,
const float co[2],
const float scale[2],
const bool ignore_selected,
float *dist_sq,
float r_uv[2])
{
BMesh *bm = BKE_editmesh_from_object(obedit)->bm;
BMIter iter;
BMFace *efa;
const float *uv_best = nullptr;
float dist_best = *dist_sq;
const BMUVOffsets offsets = BM_uv_map_offsets_get(bm);
BLI_assert(offsets.uv >= 0);
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
if (!uvedit_face_visible_test(scene, efa)) {
continue;
}
BMLoop *l_iter, *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(efa);
do {
if (ignore_selected && uvedit_uv_select_test(scene, bm, l_iter, offsets)) {
continue;
}
const float *uv = BM_ELEM_CD_GET_FLOAT_P(l_iter, offsets.uv);
float co_tmp[2];
mul_v2_v2v2(co_tmp, scale, uv);
const float dist_test = len_squared_v2v2(co, co_tmp);
if (dist_best > dist_test) {
dist_best = dist_test;
uv_best = uv;
}
} while ((l_iter = l_iter->next) != l_first);
}
if (uv_best != nullptr) {
copy_v2_v2(r_uv, uv_best);
*dist_sq = dist_best;
return true;
}
return false;
}
bool ED_uvedit_nearest_uv_multi(const View2D *v2d,
const Scene *scene,
const Span<Object *> objects,
const float mval_fl[2],
const bool ignore_selected,
float *dist_sq,
float r_uv[2])
{
bool found = false;
float scale[2], offset[2];
UI_view2d_scale_get(v2d, &scale[0], &scale[1]);
UI_view2d_view_to_region_fl(v2d, 0.0f, 0.0f, &offset[0], &offset[1]);
float co[2];
sub_v2_v2v2(co, mval_fl, offset);
for (Object *obedit : objects) {
if (uvedit_nearest_uv(scene, obedit, co, scale, ignore_selected, dist_sq, r_uv)) {
found = true;
}
}
return found;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Find Nearest to Element
*
* These functions are quite specialized, useful when sync select is enabled
* and we want to pick an active UV vertex/edge from the active element which may
* have multiple UVs split out.
* \{ */
BMLoop *uv_find_nearest_loop_from_vert(Scene *scene, Object *obedit, BMVert *v, const float co[2])
{
BMesh *bm = BKE_editmesh_from_object(obedit)->bm;
const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_PROP_FLOAT2);
BMIter liter;
BMLoop *l;
BMLoop *l_found = nullptr;
float dist_best_sq = FLT_MAX;
BM_ITER_ELEM (l, &liter, v, BM_LOOPS_OF_VERT) {
if (!uvedit_face_visible_test(scene, l->f)) {
continue;
}
const float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
const float dist_test_sq = len_squared_v2v2(co, luv);
if (dist_test_sq < dist_best_sq) {
dist_best_sq = dist_test_sq;
l_found = l;
}
}
return l_found;
}
BMLoop *uv_find_nearest_loop_from_edge(Scene *scene, Object *obedit, BMEdge *e, const float co[2])
{
BMesh *bm = BKE_editmesh_from_object(obedit)->bm;
const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_PROP_FLOAT2);
BMIter eiter;
BMLoop *l;
BMLoop *l_found = nullptr;
float dist_best_sq = FLT_MAX;
BM_ITER_ELEM (l, &eiter, e, BM_LOOPS_OF_EDGE) {
if (!uvedit_face_visible_test(scene, l->f)) {
continue;
}
const float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
const float *luv_next = BM_ELEM_CD_GET_FLOAT_P(l->next, cd_loop_uv_offset);
const float dist_test_sq = dist_squared_to_line_segment_v2(co, luv, luv_next);
if (dist_test_sq < dist_best_sq) {
dist_best_sq = dist_test_sq;
l_found = l;
}
}
return l_found;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Helper functions for UV selection.
* \{ */
static bool uvedit_select_pin_ok_or_report(const Scene *scene, ReportList *reports)
{
if (ED_uvedit_select_mode_get(scene) != UV_SELECT_VERT) {
BKE_report(reports, RPT_ERROR, "Pinned vertices can be selected in Vertex Mode only");
return false;
}
return true;
}
void uvedit_select_prepare_custom_data(const Scene *scene, BMesh *bm)
{
const ToolSettings *ts = scene->toolsettings;
BLI_assert((ts->uv_flag & UV_FLAG_SELECT_SYNC) == 0);
UNUSED_VARS_NDEBUG(ts);
const char *active_uv_name = CustomData_get_active_layer_name(&bm->ldata, CD_PROP_FLOAT2);
BLI_assert(active_uv_name);
UNUSED_VARS_NDEBUG(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)
{
BLI_assert(offsets.uv >= 0);
BMEdge *e_iter = l->e;
do {
BMLoop *l_radial_iter = e_iter->l;
if (!l_radial_iter) {
continue; /* Skip wire edges with no loops. */
}
do {
if (!uvedit_face_visible_test_ex(ts, l_radial_iter->f)) {
continue;
}
/* Use #l_other to check if the uvs are connected (share the same uv coordinates)
* and #l_radial_iter for the actual edge selection test. */
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, bm, l_radial_iter, offsets))
{
return true;
}
} while ((l_radial_iter = l_radial_iter->radial_next) != e_iter->l);
} while ((e_iter = BM_DISK_EDGE_NEXT(e_iter, l->v)) != l->e);
return false;
}
bool uvedit_edge_is_face_select_any_other(const ToolSettings *ts,
const BMesh *bm,
const BMLoop *l,
const BMUVOffsets &offsets)
{
BLI_assert(offsets.uv >= 0);
BMLoop *l_radial_iter = l->radial_next;
if (l_radial_iter == l) {
return false;
}
do {
if (!uvedit_face_visible_test_ex(ts, l_radial_iter->f)) {
continue;
}
if (BM_loop_uv_share_edge_check(l, l_radial_iter, offsets.uv) &&
uvedit_face_select_test_ex(ts, bm, l_radial_iter->f))
{
return true;
}
} while ((l_radial_iter = l_radial_iter->radial_next) != l);
return false;
}
bool uvedit_vert_is_face_select_any_other(const ToolSettings *ts,
const BMesh *bm,
const BMLoop *l,
const BMUVOffsets &offsets)
{
BLI_assert(offsets.uv >= 0);
BMIter liter;
BMLoop *l_iter;
BM_ITER_ELEM (l_iter, &liter, l->v, BM_LOOPS_OF_VERT) {
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, bm, l_iter->f))
{
return true;
}
}
return false;
}
bool uvedit_vert_is_all_other_faces_selected(const ToolSettings *ts,
const BMesh *bm,
const BMLoop *l,
const BMUVOffsets &offsets)
{
BLI_assert(offsets.uv >= 0);
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)) {
continue;
}
if (BM_loop_uv_share_vert_check(l, l_iter, offsets.uv) &&
!uvedit_face_select_test_ex(ts, bm, l_iter->f))
{
return false;
}
}
return true;
}
static void bm_clear_uv_vert_selection(const Scene *scene, BMesh *bm)
{
const ToolSettings *ts = scene->toolsettings;
BMFace *efa;
BMLoop *l;
BMIter iter, liter;
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
if (!uvedit_face_visible_test(scene, efa)) {
continue;
}
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
uvedit_vert_select_set_no_sync(ts, bm, l, false);
}
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name UV Selection Non-Sync API
*
* \note this is for non-sync selection,
* where different rules apply and there is no expectation a selected UV
* implies it's base mesh selection flag also be set.
*
* \{ */
bool uvedit_vert_select_get_no_sync(const ToolSettings *ts, const BMesh *bm, const BMLoop *l)
{
BLI_assert(bm && (ts->uv_flag & UV_FLAG_SELECT_SYNC) == 0);
UNUSED_VARS_NDEBUG(ts, bm);
return BM_elem_flag_test_bool(l, BM_ELEM_SELECT_UV);
}
bool uvedit_edge_select_get_no_sync(const ToolSettings *ts, const BMesh *bm, const BMLoop *l)
{
BLI_assert(bm && (ts->uv_flag & UV_FLAG_SELECT_SYNC) == 0);
UNUSED_VARS_NDEBUG(ts, bm);
return BM_elem_flag_test_bool(l, BM_ELEM_SELECT_UV_EDGE);
}
bool uvedit_face_select_get_no_sync(const ToolSettings *ts, const BMesh *bm, const BMFace *f)
{
BLI_assert(bm && (ts->uv_flag & UV_FLAG_SELECT_SYNC) == 0);
UNUSED_VARS_NDEBUG(ts, bm);
return BM_elem_flag_test_bool(f, BM_ELEM_SELECT_UV);
}
void uvedit_vert_select_set_no_sync(const ToolSettings *ts,
const BMesh *bm,
BMLoop *l,
bool select)
{
BLI_assert(bm && (ts->uv_flag & UV_FLAG_SELECT_SYNC) == 0);
BLI_assert(BM_elem_flag_test(l->f, BM_ELEM_HIDDEN) == 0);
UNUSED_VARS_NDEBUG(ts, bm);
BM_elem_flag_set(l, BM_ELEM_SELECT_UV, select);
}
void uvedit_edge_select_set_no_sync(const ToolSettings *ts,
const BMesh *bm,
BMLoop *l,
bool select)
{
BLI_assert(bm && (ts->uv_flag & UV_FLAG_SELECT_SYNC) == 0);
BLI_assert(BM_elem_flag_test(l->f, BM_ELEM_HIDDEN) == 0);
UNUSED_VARS_NDEBUG(ts, bm);
BM_elem_flag_set(l, BM_ELEM_SELECT_UV_EDGE, select);
}
void uvedit_face_select_set_no_sync(const ToolSettings *ts,
const BMesh *bm,
BMFace *f,
bool select)
{
BLI_assert(bm && (ts->uv_flag & UV_FLAG_SELECT_SYNC) == 0);
BLI_assert(BM_elem_flag_test(f, BM_ELEM_HIDDEN) == 0);
UNUSED_VARS_NDEBUG(ts, bm);
BM_elem_flag_set(f, BM_ELEM_SELECT_UV, select);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \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> 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<UVSyncSelectFromMesh>(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
*
* \{ */
void ED_uvedit_selectmode_flush(const Scene *scene, BMesh *bm)
{
const ToolSettings *ts = scene->toolsettings;
BLI_assert((ts->uv_flag & UV_FLAG_SELECT_SYNC) == 0);
UNUSED_VARS_NDEBUG(ts);
uvedit_select_prepare_custom_data(scene, bm);
/* Vertex Mode only. */
if (ts->uv_selectmode & UV_SELECT_VERT) {
BMFace *efa;
BMLoop *l;
BMIter iter, liter;
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
if (!uvedit_face_visible_test(scene, efa)) {
continue;
}
bool select_all = true;
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
bool edge_selected = uvedit_vert_select_get_no_sync(ts, bm, l) &&
uvedit_vert_select_get_no_sync(ts, bm, l->next);
uvedit_edge_select_set_no_sync(ts, bm, l, edge_selected);
if (!edge_selected) {
select_all = false;
}
}
uvedit_face_select_set_no_sync(ts, bm, efa, select_all);
}
}
else if (ts->uv_selectmode & UV_SELECT_EDGE) {
BMFace *efa;
BMLoop *l;
BMIter iter, liter;
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
if (!uvedit_face_visible_test(scene, efa)) {
continue;
}
bool select_all = true;
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
if (!uvedit_edge_select_get_no_sync(ts, bm, l)) {
select_all = false;
break;
}
}
uvedit_face_select_set_no_sync(ts, bm, efa, select_all);
}
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name UV Flush selection (up/down)
* \{ */
void uvedit_select_flush_from_verts(const Scene *scene, BMesh *bm, const bool select)
{
const ToolSettings *ts = scene->toolsettings;
BLI_assert((ts->uv_flag & UV_FLAG_SELECT_SYNC) == 0);
UNUSED_VARS_NDEBUG(ts);
uvedit_select_prepare_custom_data(scene, bm);
if (select) {
/* Careful when using this in face select mode.
* For face selections with sticky mode enabled, this can create invalid selection states. */
BMFace *efa;
BMLoop *l;
BMIter iter, liter;
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
if (!uvedit_face_visible_test(scene, efa)) {
continue;
}
bool select_all = true;
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
if (uvedit_vert_select_get_no_sync(ts, bm, l) &&
uvedit_vert_select_get_no_sync(ts, bm, l->next))
{
uvedit_edge_select_set_no_sync(ts, bm, l, true);
}
else {
select_all = false;
}
}
if (select_all) {
uvedit_face_select_set_no_sync(ts, bm, efa, true);
}
}
}
else {
BMFace *efa;
BMLoop *l;
BMIter iter, liter;
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
if (!uvedit_face_visible_test(scene, efa)) {
continue;
}
bool select_all = true;
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
if (!uvedit_vert_select_get_no_sync(ts, bm, l) ||
!uvedit_vert_select_get_no_sync(ts, bm, l->next))
{
uvedit_edge_select_set_no_sync(ts, bm, l, false);
select_all = false;
}
}
if (select_all == false) {
uvedit_face_select_set_no_sync(ts, bm, efa, false);
}
}
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Edge Loop Select
* \{ */
/** Mode for selecting edge loops at boundaries. */
enum eUVEdgeLoopBoundaryMode {
/** Delimit at face corners (don't walk over multiple edges in the same face). */
UV_EDGE_LOOP_BOUNDARY_LOOP = 1,
/** Don't delimit, walk over the all connected boundary loops. */
UV_EDGE_LOOP_BOUNDARY_ALL = 2,
};
static BMLoop *bm_select_edgeloop_double_side_next(const Scene *scene,
BMLoop *l_step,
BMVert *v_from,
const BMUVOffsets &offsets)
{
if (l_step->f->len == 4) {
BMVert *v_from_next = BM_edge_other_vert(l_step->e, v_from);
BMLoop *l_step_over = (v_from == l_step->v) ? l_step->next : l_step->prev;
l_step_over = uvedit_loop_find_other_radial_loop_with_visible_face(
scene, l_step_over, offsets);
if (l_step_over) {
return (l_step_over->v == v_from_next) ? l_step_over->prev : l_step_over->next;
}
}
return nullptr;
}
static BMLoop *bm_select_edgeloop_single_side_next(const Scene *scene,
BMLoop *l_step,
BMVert *v_from,
const BMUVOffsets &offsets)
{
BMVert *v_from_next = BM_edge_other_vert(l_step->e, v_from);
return uvedit_loop_find_other_boundary_loop_with_visible_face(
scene, l_step, v_from_next, offsets);
}
/* TODO(@ideasman42): support this in the BMesh API, as we have for clearing other types. */
static void bm_loop_tags_clear(BMesh *bm)
{
BMIter iter;
BMFace *f;
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
BMIter liter;
BMLoop *l_iter;
BM_ITER_ELEM (l_iter, &liter, f, BM_LOOPS_OF_FACE) {
BM_elem_flag_disable(l_iter, BM_ELEM_TAG);
}
}
}
/**
* Tag all loops which should be selected, the caller must select.
*/
static void uv_select_edgeloop_double_side_tag(const Scene *scene,
BMesh *bm,
BMLoop *l_init_pair[2],
const BMUVOffsets &offsets)
{
bm_loop_tags_clear(bm);
for (int side = 0; side < 2; side++) {
BMLoop *l_step_pair[2] = {l_init_pair[0], l_init_pair[1]};
BMVert *v_from = side ? l_step_pair[0]->e->v1 : l_step_pair[0]->e->v2;
/* Disable since we start from the same edge. */
BM_elem_flag_disable(l_step_pair[0], BM_ELEM_TAG);
BM_elem_flag_disable(l_step_pair[1], BM_ELEM_TAG);
while ((l_step_pair[0] != nullptr) && (l_step_pair[1] != nullptr)) {
if (!uvedit_face_visible_test(scene, l_step_pair[0]->f) ||
!uvedit_face_visible_test(scene, l_step_pair[1]->f) ||
/* Check loops have not diverged. */
(uvedit_loop_find_other_radial_loop_with_visible_face(scene, l_step_pair[0], offsets) !=
l_step_pair[1]))
{
break;
}
BLI_assert(l_step_pair[0]->e == l_step_pair[1]->e);
BM_elem_flag_enable(l_step_pair[0], BM_ELEM_TAG);
BM_elem_flag_enable(l_step_pair[1], BM_ELEM_TAG);
BMVert *v_from_next = BM_edge_other_vert(l_step_pair[0]->e, v_from);
/* Walk over both sides, ensure they keep on the same edge. */
for (int i = 0; i < ARRAY_SIZE(l_step_pair); i++) {
l_step_pair[i] = bm_select_edgeloop_double_side_next(
scene, l_step_pair[i], v_from, offsets);
}
if ((l_step_pair[0] && BM_elem_flag_test(l_step_pair[0], BM_ELEM_TAG)) ||
(l_step_pair[1] && BM_elem_flag_test(l_step_pair[1], BM_ELEM_TAG)))
{
break;
}
v_from = v_from_next;
}
}
}
/**
* Tag all loops which should be selected, the caller must select.
*
* \param r_count_by_select: Count the number of unselected and selected loops,
* this is needed to implement cycling between #eUVEdgeLoopBoundaryMode.
*/
static void uv_select_edgeloop_single_side_tag(const Scene *scene,
BMesh *bm,
BMLoop *l_init,
const BMUVOffsets &offsets,
enum eUVEdgeLoopBoundaryMode boundary_mode,
int r_count_by_select[2])
{
if (r_count_by_select) {
r_count_by_select[0] = r_count_by_select[1] = 0;
}
bm_loop_tags_clear(bm);
for (int side = 0; side < 2; side++) {
BMLoop *l_step = l_init;
BMVert *v_from = side ? l_step->e->v1 : l_step->e->v2;
/* Disable since we start from the same edge. */
BM_elem_flag_disable(l_step, BM_ELEM_TAG);
while (l_step != nullptr) {
if (!uvedit_face_visible_test(scene, l_step->f) ||
/* Check the boundary is still a boundary. */
(uvedit_loop_find_other_radial_loop_with_visible_face(scene, l_step, offsets) !=
nullptr))
{
break;
}
if (r_count_by_select != nullptr) {
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;
break;
}
}
BM_elem_flag_enable(l_step, BM_ELEM_TAG);
BMVert *v_from_next = BM_edge_other_vert(l_step->e, v_from);
BMFace *f_step_prev = l_step->f;
l_step = bm_select_edgeloop_single_side_next(scene, l_step, v_from, offsets);
if (l_step && BM_elem_flag_test(l_step, BM_ELEM_TAG)) {
break;
}
if (boundary_mode == UV_EDGE_LOOP_BOUNDARY_LOOP) {
/* Don't allow walking over the face. */
if (f_step_prev == l_step->f) {
break;
}
}
v_from = v_from_next;
}
}
}
static int uv_select_edgeloop(Scene *scene, Object *obedit, UvNearestHit *hit, const bool extend)
{
const ToolSettings *ts = scene->toolsettings;
BMesh *bm = BKE_editmesh_from_object(obedit)->bm;
bool select;
/* NOTE: this is a special case, even when sync select is enabled,
* the flags are used then flushed to the vertices.
* So these need to be ensured even though the layers aren't used afterwards. */
const BMUVOffsets offsets = BM_uv_map_offsets_get(bm);
if (extend) {
select = !uvedit_edge_select_test(scene, bm, hit->l, offsets);
}
else {
select = true;
}
BMLoop *l_init_pair[2] = {
hit->l,
uvedit_loop_find_other_radial_loop_with_visible_face(scene, hit->l, offsets),
};
/* When selecting boundaries, support cycling between selection modes. */
enum eUVEdgeLoopBoundaryMode boundary_mode = UV_EDGE_LOOP_BOUNDARY_LOOP;
/* Tag all loops that are part of the edge loop (select after).
* This is done so we can */
if (l_init_pair[1] == nullptr) {
int count_by_select[2];
/* If the loops selected toggle the boundaries. */
uv_select_edgeloop_single_side_tag(
scene, bm, l_init_pair[0], offsets, boundary_mode, count_by_select);
if (count_by_select[!select] == 0) {
boundary_mode = UV_EDGE_LOOP_BOUNDARY_ALL;
/* If the boundary is selected, toggle back to the loop. */
uv_select_edgeloop_single_side_tag(
scene, bm, l_init_pair[0], offsets, boundary_mode, count_by_select);
if (count_by_select[!select] == 0) {
boundary_mode = UV_EDGE_LOOP_BOUNDARY_LOOP;
}
}
}
if (l_init_pair[1] == nullptr) {
uv_select_edgeloop_single_side_tag(scene, bm, l_init_pair[0], offsets, boundary_mode, nullptr);
}
else {
uv_select_edgeloop_double_side_tag(scene, bm, l_init_pair, offsets);
}
/* Apply the selection. */
if (!extend) {
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. */
{
BMIter iter;
BMFace *f;
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
BMIter liter;
BMLoop *l_iter;
BM_ITER_ELEM (l_iter, &liter, f, BM_LOOPS_OF_FACE) {
if (BM_elem_flag_test(l_iter, BM_ELEM_TAG)) {
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
uvedit_edge_select_set_with_sticky(scene, bm, l_iter, select, offsets);
}
else {
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);
}
}
}
}
}
}
return select ? 1 : -1;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Face Loop Select
* \{ */
static int uv_select_faceloop(Scene *scene, Object *obedit, UvNearestHit *hit, const bool extend)
{
const ToolSettings *ts = scene->toolsettings;
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) {
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_mesh_elem_hflag_disable_all(bm, BM_FACE, BM_ELEM_TAG, false);
if (extend) {
select = !uvedit_face_select_test(scene, bm, hit->l->f);
}
else {
select = true;
}
BMLoop *l_pair[2] = {
hit->l,
uvedit_loop_find_other_radial_loop_with_visible_face(scene, hit->l, offsets),
};
for (int side = 0; side < 2; side++) {
BMLoop *l_step = l_pair[side];
while (l_step) {
if (!uvedit_face_visible_test(scene, l_step->f)) {
break;
}
uvedit_face_select_set_with_sticky(scene, bm, l_step->f, select, offsets);
BM_elem_flag_enable(l_step->f, BM_ELEM_TAG);
if (l_step->f->len == 4) {
BMLoop *l_step_opposite = l_step->next->next;
l_step = uvedit_loop_find_other_radial_loop_with_visible_face(
scene, l_step_opposite, offsets);
}
else {
l_step = nullptr;
}
/* Break iteration when `l_step`:
* - is the first loop where we started from.
* - tagged using #BM_ELEM_TAG (meaning this loop has been visited in this iteration). */
if (l_step && BM_elem_flag_test(l_step->f, BM_ELEM_TAG)) {
break;
}
}
}
return (select) ? 1 : -1;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Edge Ring Select
* \{ */
static int uv_select_edgering(Scene *scene, Object *obedit, UvNearestHit *hit, const bool extend)
{
const ToolSettings *ts = scene->toolsettings;
BMesh *bm = BKE_editmesh_from_object(obedit)->bm;
const bool use_face_select = (ts->uv_flag & UV_FLAG_SELECT_SYNC) ?
(ts->selectmode & SCE_SELECT_FACE) :
(ts->uv_selectmode & UV_SELECT_FACE);
const bool use_vertex_select = (ts->uv_flag & UV_FLAG_SELECT_SYNC) ?
(ts->selectmode & SCE_SELECT_VERTEX) :
(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) {
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_mesh_elem_hflag_disable_all(bm, BM_EDGE, BM_ELEM_TAG, false);
if (extend) {
select = !uvedit_edge_select_test(scene, bm, hit->l, offsets);
}
else {
select = true;
}
BMLoop *l_pair[2] = {
hit->l,
uvedit_loop_find_other_radial_loop_with_visible_face(scene, hit->l, offsets),
};
for (int side = 0; side < 2; side++) {
BMLoop *l_step = l_pair[side];
/* Disable since we start from the same edge. */
BM_elem_flag_disable(hit->l->e, BM_ELEM_TAG);
while (l_step) {
if (!uvedit_face_visible_test(scene, l_step->f)) {
break;
}
if (use_face_select) {
/* While selecting face loops is now done in a separate function #uv_select_faceloop(),
* this check is still kept for edge ring selection, to keep it consistent with how edge
* ring selection works in face mode in the 3D viewport. */
uvedit_face_select_set_with_sticky(scene, bm, l_step->f, select, offsets);
}
else if (use_vertex_select) {
uvedit_uv_select_set_with_sticky(scene, bm, l_step, select, offsets);
uvedit_uv_select_set_with_sticky(scene, bm, l_step->next, select, offsets);
}
else {
/* Edge select mode */
uvedit_edge_select_set_with_sticky(scene, bm, l_step, select, offsets);
}
BM_elem_flag_enable(l_step->e, BM_ELEM_TAG);
if (l_step->f->len == 4) {
BMLoop *l_step_opposite = l_step->next->next;
l_step = uvedit_loop_find_other_radial_loop_with_visible_face(
scene, l_step_opposite, offsets);
if (l_step == nullptr) {
/* Ensure we touch the opposite edge if we can't walk over it. */
l_step = l_step_opposite;
}
}
else {
l_step = nullptr;
}
/* Break iteration when `l_step`:
* - Is the first loop where we started from.
* - Tagged using #BM_ELEM_TAG (meaning this loop has been visited in this iteration).
* - Has its corresponding UV edge selected/unselected based on #select. */
if (l_step && BM_elem_flag_test(l_step->e, BM_ELEM_TAG)) {
/* Previously this check was not done and this resulted in the final edge in the edge ring
* cycle to be skipped during selection (caused by old sticky selection behavior). */
if (select && uvedit_edge_select_test(scene, bm, l_step, offsets)) {
break;
}
if (!select && !uvedit_edge_select_test(scene, bm, l_step, offsets)) {
break;
}
}
}
}
return (select) ? 1 : -1;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select Linked
* \{ */
static void uv_select_linked_multi(const Scene *scene,
const Span<Object *> objects,
UvNearestHit *hit,
const bool extend,
bool deselect,
const bool toggle,
const bool select_faces,
const char hflag)
{
if (select_faces) {
BLI_assert(ELEM(hflag, BM_ELEM_SELECT, BM_ELEM_TAG));
}
else {
/* Tagging could be supported for other elements but currently isn't needed. */
BLI_assert(hflag == BM_ELEM_SELECT);
}
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()) {
if (hit && ob_index != 0) {
break;
}
Object *obedit = hit ? hit->ob : objects[ob_index];
BMFace *efa;
BMLoop *l;
BMIter iter, liter;
UvMapVert *vlist, *iterv, *startv;
int i, stacksize = 0, *stack;
uint a;
char *flag;
BMesh *bm = BKE_editmesh_from_object(obedit)->bm;
if (uv_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_mesh_elem_table_ensure(bm, BM_FACE); /* we can use this too */
/* NOTE: we had 'use winding' so we don't consider overlapping islands as connected, see #44320
* this made *every* projection split the island into front/back islands.
* Keep 'use_winding' to false, see: #50970.
*
* 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_select_sync);
if (vmap == nullptr) {
continue;
}
stack = MEM_malloc_arrayN<int>(bm->totface + 1, "UvLinkStack");
flag = MEM_calloc_arrayN<char>(bm->totface, "UvLinkFlag");
if (hit == nullptr) {
/* Use existing selection */
BM_ITER_MESH_INDEX (efa, &iter, bm, BM_FACES_OF_MESH, a) {
if (uvedit_face_visible_test(scene, efa)) {
if (select_faces) {
if (BM_elem_flag_test(efa, hflag)) {
stack[stacksize] = a;
stacksize++;
flag[a] = 1;
}
}
else {
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
if (uvedit_uv_select_test(scene, bm, l, offsets)) {
bool add_to_stack = true;
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
* selection reaches the boundaries of islands that share vertices but not UVs.
*
* Rules applied here:
* - This loops face isn't selected.
* - The only other fully selected face is connected or,
* - There are no connected fully selected faces UV-connected to this loop.
*/
BLI_assert(!select_faces);
if (uvedit_face_select_test(scene, bm, l->f)) {
/* pass */
}
else {
BMIter liter_other;
BMLoop *l_other;
BM_ITER_ELEM (l_other, &liter_other, l->v, BM_LOOPS_OF_VERT) {
if ((l != l_other) && !BM_loop_uv_share_vert_check(l, l_other, offsets.uv) &&
uvedit_face_select_test(scene, bm, l_other->f))
{
add_to_stack = false;
break;
}
}
}
}
if (add_to_stack) {
stack[stacksize] = a;
stacksize++;
flag[a] = 1;
break;
}
}
}
}
}
}
}
else {
BM_ITER_MESH_INDEX (efa, &iter, bm, BM_FACES_OF_MESH, a) {
if (efa == hit->efa) {
stack[stacksize] = a;
stacksize++;
flag[a] = 1;
break;
}
}
}
while (stacksize > 0) {
stacksize--;
a = stack[stacksize];
efa = BM_face_at_index(bm, a);
BM_ITER_ELEM_INDEX (l, &liter, efa, BM_LOOPS_OF_FACE, i) {
/* make_uv_vert_map_EM sets verts tmp.l to the indices */
vlist = BM_uv_vert_map_at_index(vmap, BM_elem_index_get(l->v));
startv = vlist;
for (iterv = vlist; iterv; iterv = iterv->next) {
if (iterv->separate) {
startv = iterv;
}
if (iterv->face_index == a) {
break;
}
}
for (iterv = startv; iterv; iterv = iterv->next) {
if ((startv != iterv) && (iterv->separate)) {
break;
}
if (!flag[iterv->face_index]) {
flag[iterv->face_index] = 1;
stack[stacksize] = iterv->face_index;
stacksize++;
}
}
}
}
/* Toggling - if any of the linked vertices is selected (and visible), we deselect. */
if ((toggle == true) && (extend == false) && (deselect == false)) {
BM_ITER_MESH_INDEX (efa, &iter, bm, BM_FACES_OF_MESH, a) {
bool found_selected = false;
if (!flag[a]) {
continue;
}
if (select_faces) {
if (!BM_elem_flag_test(efa, BM_ELEM_HIDDEN)) {
if (BM_elem_flag_test(efa, hflag)) {
found_selected = true;
}
}
}
else {
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
if (uvedit_uv_select_test(scene, bm, l, offsets)) {
found_selected = true;
break;
}
}
if (found_selected) {
deselect = true;
break;
}
}
}
}
#define SET_SELECTION(value) \
if (select_faces) { \
if (hflag == BM_ELEM_SELECT) { \
BM_face_select_set(bm, efa, value); \
} \
else { \
BM_elem_flag_set(efa, hflag, value); \
} \
} \
else { \
uvedit_face_select_set(scene, bm, efa, value); \
} \
(void)0
/* When sync-select is enabled in vertex or edge selection modes,
* selecting an islands faces may select vertices or edges on other UV islands.
* 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_select_sync &&
(scene->toolsettings->selectmode &
(SCE_SELECT_VERTEX | SCE_SELECT_EDGE)) &&
(deselect == false);
const bool deselect_elem = !extend && !deselect && !toggle;
if (needs_multi_pass == false) {
BM_ITER_MESH_INDEX (efa, &iter, bm, BM_FACES_OF_MESH, a) {
if (!flag[a]) {
if (deselect_elem) {
SET_SELECTION(false);
}
continue;
}
if (deselect) {
SET_SELECTION(false);
}
else {
SET_SELECTION(true);
}
}
}
else {
/* The same as the previous block, just use multiple passes.
* It just so happens that multi-pass is only needed when selecting (deselect==false). */
BLI_assert(deselect == false);
/* Pass 1 (de-select). */
if (deselect_elem) {
BM_ITER_MESH_INDEX (efa, &iter, bm, BM_FACES_OF_MESH, a) {
if (!flag[a]) {
SET_SELECTION(false);
}
}
}
/* Pass 2 (select). */
BM_ITER_MESH_INDEX (efa, &iter, bm, BM_FACES_OF_MESH, a) {
if (!flag[a]) {
continue;
}
SET_SELECTION(true);
}
}
#undef SET_SELECTION
MEM_freeN(stack);
MEM_freeN(flag);
BM_uv_vert_map_free(vmap);
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 {
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);
}
}
}
}
}
/**
* A wrapper for #uv_select_linked_multi that uses defaults for UV island selection.
*/
static void uv_select_linked_multi_for_select_island(const Scene *scene,
const Span<Object *> objects,
Object *obedit,
BMFace *efa,
const bool deselect,
const bool select_faces,
const char hflag)
{
const bool extend = true;
const bool toggle = false;
UvNearestHit hit = {};
hit.ob = obedit;
hit.efa = efa;
uv_select_linked_multi(scene, objects, &hit, extend, deselect, toggle, select_faces, hflag);
}
const float *uvedit_first_selected_uv_from_vertex(Scene *scene,
const BMesh *bm,
BMVert *eve,
const BMUVOffsets &offsets)
{
BMIter liter;
BMLoop *l;
BM_ITER_ELEM (l, &liter, eve, BM_LOOPS_OF_VERT) {
if (!uvedit_face_visible_test(scene, l->f)) {
continue;
}
if (uvedit_uv_select_test(scene, bm, l, offsets)) {
float *luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
return luv;
}
}
return nullptr;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select More/Less Operator
* \{ */
static wmOperatorStatus uv_select_more_less(bContext *C, const bool select)
{
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
BMFace *efa;
BMLoop *l;
BMIter iter, liter;
const ToolSettings *ts = scene->toolsettings;
Vector<Object *> 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_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;
bool changed = false;
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);
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);
}
else {
EDBM_select_less(em, true);
}
DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_SELECT);
WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data);
continue;
}
if (is_uv_face_selectmode) {
/* clear tags */
BM_mesh_elem_hflag_disable_all(bm, BM_FACE, BM_ELEM_TAG, false);
/* mark loops to be selected */
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
if (uvedit_face_visible_test(scene, efa)) {
if (select) {
#define NEIGHBORING_FACE_IS_SEL 1
#define CURR_FACE_IS_UNSEL 2
int sel_state = 0;
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
if (uvedit_loop_vert_select_get(ts, bm, l)) {
sel_state |= NEIGHBORING_FACE_IS_SEL;
}
else {
sel_state |= CURR_FACE_IS_UNSEL;
}
if (!uvedit_loop_edge_select_get(ts, bm, l)) {
sel_state |= CURR_FACE_IS_UNSEL;
}
/* If the current face is not selected and at least one neighboring face is
* selected, then tag the current face to grow selection. */
if (sel_state == (NEIGHBORING_FACE_IS_SEL | CURR_FACE_IS_UNSEL)) {
BM_elem_flag_enable(efa, BM_ELEM_TAG);
changed = true;
break;
}
}
#undef NEIGHBORING_FACE_IS_SEL
#undef CURR_FACE_IS_UNSEL
}
else {
if (!uvedit_face_select_test(scene, bm, efa)) {
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, bm, l, offsets)) {
BM_elem_flag_enable(efa, BM_ELEM_TAG);
changed = true;
break;
}
}
}
}
}
}
else {
/* clear tags */
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
BM_elem_flag_disable(l, BM_ELEM_TAG);
}
}
/* mark loops to be selected */
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
if (uvedit_face_visible_test(scene, efa)) {
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
if (uvedit_loop_vert_select_get(ts, bm, l) == select) {
BM_elem_flag_enable(l->next, BM_ELEM_TAG);
BM_elem_flag_enable(l->prev, BM_ELEM_TAG);
changed = true;
}
}
}
}
}
if (changed) {
if (is_uv_face_selectmode) {
/* Select tagged faces. */
uv_select_flush_from_tag_face(scene, obedit, select);
}
else {
/* Select tagged loops. */
uv_select_flush_from_tag_loop(scene, obedit, select);
/* Set/unset edge flags based on selected verts. */
if (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<ID *>(obedit->data), ID_RECALC_SELECT);
WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data);
}
}
return OPERATOR_FINISHED;
}
static wmOperatorStatus uv_select_more_exec(bContext *C, wmOperator * /*op*/)
{
return uv_select_more_less(C, true);
}
void UV_OT_select_more(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Select More";
ot->description = "Select more UV vertices connected to initial selection";
ot->idname = "UV_OT_select_more";
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* API callbacks. */
ot->exec = uv_select_more_exec;
ot->poll = ED_operator_uvedit_space_image;
}
static wmOperatorStatus uv_select_less_exec(bContext *C, wmOperator * /*op*/)
{
return uv_select_more_less(C, false);
}
void UV_OT_select_less(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Select Less";
ot->description = "Deselect UV vertices at the boundary of each selection region";
ot->idname = "UV_OT_select_less";
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* API callbacks. */
ot->exec = uv_select_less_exec;
ot->poll = ED_operator_uvedit_space_image;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name (De)Select All Operator
* \{ */
bool uvedit_select_is_any_selected(const Scene *scene, BMesh *bm)
{
const ToolSettings *ts = scene->toolsettings;
BMFace *efa;
BMLoop *l;
BMIter iter, liter;
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
return (bm->totvertsel || bm->totedgesel || bm->totfacesel);
}
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
if (!uvedit_face_visible_test(scene, efa)) {
continue;
}
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
if (uvedit_vert_select_get_no_sync(ts, bm, l)) {
return true;
}
}
}
return false;
}
bool uvedit_select_is_any_selected_multi(const Scene *scene, const Span<Object *> objects)
{
bool found = false;
for (Object *obedit : objects) {
BMesh *bm = BKE_editmesh_from_object(obedit)->bm;
if (uvedit_select_is_any_selected(scene, bm)) {
found = true;
break;
}
}
return found;
}
static void uv_select_all(const Scene *scene, BMEditMesh *em, bool select_all)
{
const ToolSettings *ts = scene->toolsettings;
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);
}
else {
EDBM_flag_disable_all(em, BM_ELEM_SELECT);
}
return;
}
BMFace *efa;
BMLoop *l;
BMIter iter, liter;
uvedit_select_prepare_custom_data(scene, bm);
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
if (!uvedit_face_visible_test(scene, efa)) {
continue;
}
uvedit_face_select_set_no_sync(ts, bm, efa, select_all);
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
uvedit_vert_select_set_no_sync(ts, bm, l, select_all);
uvedit_edge_select_set_no_sync(ts, bm, l, select_all);
}
}
}
static void uv_select_toggle_all(const Scene *scene, BMEditMesh *em)
{
bool select_any = uvedit_select_is_any_selected(scene, em->bm);
uv_select_all(scene, em, !select_any);
}
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) {
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;
}
uvedit_select_prepare_custom_data(scene, bm);
BMFace *efa;
BMLoop *l;
BMIter iter, liter;
char uv_selectmode = ts->uv_selectmode;
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
if (!uvedit_face_visible_test(scene, efa)) {
continue;
}
bool select_all = true;
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
if (uv_selectmode & (UV_SELECT_EDGE | UV_SELECT_FACE)) {
/* Use UV edge selection to find vertices and edges that must be selected. */
bool es = !uvedit_edge_select_get_no_sync(ts, bm, l);
uvedit_edge_select_set_no_sync(ts, bm, l, es);
uvedit_vert_select_set_no_sync(ts, bm, l, false);
if (es == false) {
select_all = false;
}
}
/* Use UV vertex selection to find vertices and edges that must be selected. */
else {
BLI_assert(uv_selectmode & UV_SELECT_VERT);
bool vs = !uvedit_vert_select_get_no_sync(ts, bm, l);
uvedit_vert_select_set_no_sync(ts, bm, l, vs);
uvedit_edge_select_set_no_sync(ts, bm, l, false);
if (vs == false) {
select_all = false;
}
}
}
uvedit_face_select_set_no_sync(ts, bm, efa, select_all);
}
/* Flush based on uv vert/edge flags and current UV select mode */
if (ELEM(uv_selectmode, UV_SELECT_EDGE, UV_SELECT_FACE)) {
uv_select_flush_from_loop_edge_flag(scene, bm);
}
else {
uvedit_select_flush_from_verts(scene, bm, true);
}
}
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);
break;
}
case SEL_SELECT: {
uv_select_all(scene, em, true);
break;
}
case SEL_DESELECT: {
uv_select_all(scene, em, false);
break;
}
case SEL_INVERT: {
uv_select_invert(scene, em);
break;
}
}
}
static void uv_select_all_perform_multi_ex(const Scene *scene,
const Span<Object *> objects,
int action,
const Object *ob_exclude)
{
if (action == SEL_TOGGLE) {
action = uvedit_select_is_any_selected_multi(scene, objects) ? SEL_DESELECT : SEL_SELECT;
}
for (Object *obedit : objects) {
if (ob_exclude && (obedit == ob_exclude)) {
continue;
}
ED_uvedit_deselect_all(scene, obedit, action);
}
}
static void uv_select_all_perform_multi(const Scene *scene, Span<Object *> objects, int action)
{
uv_select_all_perform_multi_ex(scene, objects, action, nullptr);
}
static wmOperatorStatus uv_select_all_exec(bContext *C, wmOperator *op)
{
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
Scene *scene = CTX_data_scene(C);
const ToolSettings *ts = scene->toolsettings;
ViewLayer *view_layer = CTX_data_view_layer(C);
int action = RNA_enum_get(op->ptr, "action");
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs(
scene, view_layer, nullptr);
uv_select_all_perform_multi(scene, objects, action);
for (Object *obedit : objects) {
uv_select_tag_update_for_object(depsgraph, ts, obedit);
}
return OPERATOR_FINISHED;
}
void UV_OT_select_all(wmOperatorType *ot)
{
/* identifiers */
ot->name = "(De)select All";
ot->description = "Change selection of all UV vertices";
ot->idname = "UV_OT_select_all";
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* API callbacks. */
ot->exec = uv_select_all_exec;
ot->poll = ED_operator_uvedit;
WM_operator_properties_select_all(ot);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Mouse Select Operator
* \{ */
static bool uv_mouse_select_multi(bContext *C,
const Span<Object *> objects,
const float co[2],
const SelectPick_Params &params)
{
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
const ARegion *region = CTX_wm_region(C);
Scene *scene = CTX_data_scene(C);
const ToolSettings *ts = scene->toolsettings;
UvNearestHit hit = region ? uv_nearest_hit_init_dist_px(&region->v2d, 75.0f) :
uv_nearest_hit_init_max_default();
int selectmode, sticky;
bool found_item = false;
/* 0 == don't flush, 1 == sel, -1 == deselect; only use when selection sync is enabled. */
int flush = 0;
const bool use_select_linked = ED_uvedit_select_island_check(ts);
/* Penalty (in pixels) applied to elements that are already selected
* so elements that aren't already selected are prioritized. */
const float penalty_dist = 3.0f * U.pixelsize;
/* retrieve operation mode */
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
if (ts->selectmode & SCE_SELECT_FACE) {
selectmode = UV_SELECT_FACE;
}
else if (ts->selectmode & SCE_SELECT_EDGE) {
selectmode = UV_SELECT_EDGE;
}
else {
selectmode = UV_SELECT_VERT;
}
sticky = UV_STICKY_DISABLE;
}
else {
selectmode = ts->uv_selectmode;
sticky = ts->uv_sticky;
}
/* find nearest element */
if (use_select_linked) {
found_item = uv_find_nearest_edge_multi(scene, objects, co, 0.0f, &hit);
if (!found_item) {
/* Without this, we can be within the face of an island but too far from an edge,
* see face selection comment for details. */
hit.dist_sq = FLT_MAX;
found_item = uv_find_nearest_face_multi_ex(scene, objects, co, &hit, true);
}
}
else if (selectmode == UV_SELECT_VERT) {
/* find vertex */
found_item = uv_find_nearest_vert_multi(scene, objects, co, penalty_dist, &hit);
if (found_item) {
if ((ts->uv_flag & UV_FLAG_SELECT_SYNC) == 0) {
BMesh *bm = BKE_editmesh_from_object(hit.ob)->bm;
ED_uvedit_active_vert_loop_set(bm, hit.l);
}
}
}
else if (selectmode == UV_SELECT_EDGE) {
/* find edge */
found_item = uv_find_nearest_edge_multi(scene, objects, co, penalty_dist, &hit);
if (found_item) {
if ((ts->uv_flag & UV_FLAG_SELECT_SYNC) == 0) {
BMesh *bm = BKE_editmesh_from_object(hit.ob)->bm;
ED_uvedit_active_edge_loop_set(bm, hit.l);
}
}
}
else if (selectmode == UV_SELECT_FACE) {
/* find face */
found_item = uv_find_nearest_face_multi(scene, objects, co, &hit);
if (!found_item) {
/* Fallback, perform a second pass without a limited threshold,
* which succeeds as long as the cursor is inside the UV face.
* Useful when zoomed in, to select faces with distant screen-space face centers. */
hit.dist_sq = FLT_MAX;
found_item = uv_find_nearest_face_multi_ex(scene, objects, co, &hit, true);
}
if (found_item) {
BMesh *bm = BKE_editmesh_from_object(hit.ob)->bm;
BM_mesh_active_face_set(bm, hit.efa);
}
}
bool found = found_item;
bool changed = false;
bool is_selected = false;
if (found) {
Object *obedit = hit.ob;
BMesh *bm = BKE_editmesh_from_object(obedit)->bm;
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
/* Pass. */
}
else {
uvedit_select_prepare_custom_data(scene, bm);
}
const BMUVOffsets offsets = BM_uv_map_offsets_get(bm);
if (selectmode == UV_SELECT_FACE) {
is_selected = uvedit_face_select_test(scene, bm, hit.efa);
}
else if (selectmode == UV_SELECT_EDGE) {
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, bm, hit.l, offsets);
}
else {
is_selected = uvedit_face_select_test(scene, bm, hit.efa);
}
}
}
if (params.sel_op == SEL_OP_SET) {
if ((found && params.select_passthrough) && is_selected) {
found = false;
}
else if (found || params.deselect_all) {
/* Deselect everything. */
uv_select_all_perform_multi(scene, objects, SEL_DESELECT);
for (Object *obedit : objects) {
uv_select_tag_update_for_object(depsgraph, ts, obedit);
}
changed = true;
}
}
if (found) {
Object *obedit = hit.ob;
BMesh *bm = BKE_editmesh_from_object(obedit)->bm;
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
/* Pass. */
}
else {
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;
const bool deselect = params.sel_op == SEL_OP_SUB;
const bool toggle = params.sel_op == SEL_OP_XOR;
/* Current behavior of 'extend'
* is actually toggling, so pass extend flag as 'toggle' here */
uv_select_linked_multi(
scene, objects, &hit, extend, deselect, toggle, false, BM_ELEM_SELECT);
/* TODO: check if this actually changed. */
changed = true;
}
else {
BLI_assert(ELEM(selectmode, UV_SELECT_VERT, UV_SELECT_EDGE, UV_SELECT_FACE));
bool select_value = false;
switch (params.sel_op) {
case SEL_OP_ADD: {
select_value = true;
break;
}
case SEL_OP_SUB: {
select_value = false;
break;
}
case SEL_OP_XOR: {
select_value = !is_selected;
break;
}
case SEL_OP_SET: {
/* Deselect has already been performed. */
select_value = true;
break;
}
case SEL_OP_AND: {
BLI_assert_unreachable(); /* Doesn't make sense for picking. */
break;
}
}
if (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;
}
else if (selectmode == UV_SELECT_EDGE) {
uvedit_edge_select_set_with_sticky(scene, bm, hit.l, select_value, offsets);
flush = 1;
}
else if (selectmode == UV_SELECT_VERT) {
uvedit_uv_select_set_with_sticky(scene, bm, hit.l, select_value, offsets);
flush = 1;
}
else {
BLI_assert_unreachable();
}
/* De-selecting an edge may deselect a face too - validate. */
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
if (select_value) {
/* Postpone setting active until it's known if the underlying element is selected. */
if (selectmode == UV_SELECT_FACE) {
ele_active = (BMElem *)hit.efa;
}
else if (selectmode == UV_SELECT_EDGE) {
ele_active = (BMElem *)hit.l->e;
}
else if (selectmode == UV_SELECT_VERT) {
ele_active = (BMElem *)hit.l->v;
}
}
else {
BM_select_history_validate(bm);
}
}
/* (de)select sticky UV nodes. */
if (sticky != UV_STICKY_DISABLE) {
flush = select_value ? 1 : -1;
}
changed = true;
}
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
if (flush != 0) {
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 {
/* Setting the selection implies a single element, which doesn't need to be flushed. */
if (params.sel_op != SEL_OP_SET) {
ED_uvedit_selectmode_flush(scene, bm);
}
}
}
if (changed && found) {
/* Only update the `hit` object as de-selecting all will have refreshed the others. */
Object *obedit = hit.ob;
uv_select_tag_update_for_object(depsgraph, ts, obedit);
}
return changed || found;
}
static bool uv_mouse_select(bContext *C, const float co[2], const SelectPick_Params &params)
{
const Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs(
scene, view_layer, nullptr);
bool changed = uv_mouse_select_multi(C, objects, co, params);
return changed;
}
static wmOperatorStatus uv_select_exec(bContext *C, wmOperator *op)
{
float co[2];
RNA_float_get_array(op->ptr, "location", co);
const SelectPick_Params params = ED_select_pick_params_from_operator(op->ptr);
const bool changed = uv_mouse_select(C, co, params);
if (changed) {
return OPERATOR_FINISHED | OPERATOR_PASS_THROUGH;
}
return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH;
}
static wmOperatorStatus uv_select_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
const ARegion *region = CTX_wm_region(C);
float co[2];
UI_view2d_region_to_view(&region->v2d, event->mval[0], event->mval[1], &co[0], &co[1]);
RNA_float_set_array(op->ptr, "location", co);
const wmOperatorStatus retval = uv_select_exec(C, op);
return WM_operator_flag_only_pass_through_on_press(retval, event);
}
void UV_OT_select(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Select";
ot->description = "Select UV vertices";
ot->idname = "UV_OT_select";
ot->flag = OPTYPE_UNDO;
/* API callbacks. */
ot->exec = uv_select_exec;
ot->invoke = uv_select_invoke;
ot->poll = ED_operator_uvedit; /* requires space image */
ot->get_name = ED_select_pick_get_name;
/* properties */
PropertyRNA *prop;
WM_operator_properties_mouse_select(ot);
prop = RNA_def_float_vector(
ot->srna,
"location",
2,
nullptr,
-FLT_MAX,
FLT_MAX,
"Location",
"Mouse location in normalized coordinates, 0.0 to 1.0 is within the image bounds",
-100.0f,
100.0f);
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Shared Edge Loop/Ring Select Operator Functions
* \{ */
enum eUVLoopGenericType {
UV_LOOP_SELECT = 1,
UV_RING_SELECT = 2,
};
static wmOperatorStatus uv_mouse_select_loop_generic_multi(bContext *C,
const Span<Object *> objects,
const float co[2],
const bool extend,
enum eUVLoopGenericType loop_type)
{
const ARegion *region = CTX_wm_region(C);
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
Scene *scene = CTX_data_scene(C);
const ToolSettings *ts = scene->toolsettings;
UvNearestHit hit = region ? uv_nearest_hit_init_max(&region->v2d) :
uv_nearest_hit_init_max_default();
bool found_item = false;
/* 0 == don't flush, 1 == sel, -1 == deselect; only use when selection sync is enabled. */
int flush = 0;
/* Find edge. */
found_item = uv_find_nearest_edge_multi(scene, objects, co, 0.0f, &hit);
if (!found_item) {
return OPERATOR_CANCELLED;
}
Object *obedit = hit.ob;
BMesh *bm = BKE_editmesh_from_object(obedit)->bm;
/* Do selection. */
if (!extend) {
uv_select_all_perform_multi_ex(scene, objects, SEL_DESELECT, obedit);
}
if (loop_type == UV_LOOP_SELECT) {
if (ED_uvedit_select_mode_get(scene) == UV_SELECT_FACE) {
flush = uv_select_faceloop(scene, obedit, &hit, extend);
}
else {
flush = uv_select_edgeloop(scene, obedit, &hit, extend);
}
}
else if (loop_type == UV_RING_SELECT) {
flush = uv_select_edgering(scene, obedit, &hit, extend);
}
else {
BLI_assert_unreachable();
}
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
if (ED_uvedit_sync_uvselect_ignore(ts)) {
if (flush != 0) {
BM_mesh_select_flush_from_verts(bm, flush == 1 ? true : false);
}
}
else {
if (flush != 0) {
ED_uvedit_select_sync_flush(ts, bm, flush == 1 ? true : false);
}
}
}
else {
ED_uvedit_selectmode_flush(scene, bm);
}
for (Object *ob : objects) {
uv_select_tag_update_for_object(depsgraph, ts, ob);
}
return OPERATOR_PASS_THROUGH | OPERATOR_FINISHED;
}
static wmOperatorStatus uv_mouse_select_loop_generic(bContext *C,
const float co[2],
const bool extend,
enum eUVLoopGenericType loop_type)
{
const Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs(
scene, view_layer, nullptr);
wmOperatorStatus ret = uv_mouse_select_loop_generic_multi(C, objects, co, extend, loop_type);
return ret;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Edge Loop Select Operator
* \{ */
static wmOperatorStatus uv_select_loop_exec(bContext *C, wmOperator *op)
{
float co[2];
RNA_float_get_array(op->ptr, "location", co);
const bool extend = RNA_boolean_get(op->ptr, "extend");
return uv_mouse_select_loop_generic(C, co, extend, UV_LOOP_SELECT);
}
static wmOperatorStatus uv_select_loop_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
const ARegion *region = CTX_wm_region(C);
float co[2];
UI_view2d_region_to_view(&region->v2d, event->mval[0], event->mval[1], &co[0], &co[1]);
RNA_float_set_array(op->ptr, "location", co);
const wmOperatorStatus retval = uv_select_loop_exec(C, op);
return WM_operator_flag_only_pass_through_on_press(retval, event);
}
void UV_OT_select_loop(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Loop Select";
ot->description = "Select a loop of connected UV vertices";
ot->idname = "UV_OT_select_loop";
ot->flag = OPTYPE_UNDO;
/* API callbacks. */
ot->exec = uv_select_loop_exec;
ot->invoke = uv_select_loop_invoke;
ot->poll = ED_operator_uvedit; /* requires space image */
/* properties */
PropertyRNA *prop;
prop = RNA_def_boolean(ot->srna,
"extend",
false,
"Extend",
"Extend selection rather than clearing the existing selection");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_float_vector(
ot->srna,
"location",
2,
nullptr,
-FLT_MAX,
FLT_MAX,
"Location",
"Mouse location in normalized coordinates, 0.0 to 1.0 is within the image bounds",
-100.0f,
100.0f);
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Edge Ring Select Operator
* \{ */
static wmOperatorStatus uv_select_edge_ring_exec(bContext *C, wmOperator *op)
{
float co[2];
RNA_float_get_array(op->ptr, "location", co);
const bool extend = RNA_boolean_get(op->ptr, "extend");
return uv_mouse_select_loop_generic(C, co, extend, UV_RING_SELECT);
}
static wmOperatorStatus uv_select_edge_ring_invoke(bContext *C,
wmOperator *op,
const wmEvent *event)
{
const ARegion *region = CTX_wm_region(C);
float co[2];
UI_view2d_region_to_view(&region->v2d, event->mval[0], event->mval[1], &co[0], &co[1]);
RNA_float_set_array(op->ptr, "location", co);
const wmOperatorStatus retval = uv_select_edge_ring_exec(C, op);
return WM_operator_flag_only_pass_through_on_press(retval, event);
}
void UV_OT_select_edge_ring(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Edge Ring Select";
ot->description = "Select an edge ring of connected UV vertices";
ot->idname = "UV_OT_select_edge_ring";
ot->flag = OPTYPE_UNDO;
/* API callbacks. */
ot->exec = uv_select_edge_ring_exec;
ot->invoke = uv_select_edge_ring_invoke;
ot->poll = ED_operator_uvedit; /* requires space image */
/* properties */
PropertyRNA *prop;
prop = RNA_def_boolean(ot->srna,
"extend",
false,
"Extend",
"Extend selection rather than clearing the existing selection");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_float_vector(
ot->srna,
"location",
2,
nullptr,
-FLT_MAX,
FLT_MAX,
"Location",
"Mouse location in normalized coordinates, 0.0 to 1.0 is within the image bounds",
-100.0f,
100.0f);
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select Linked Operator
* \{ */
static wmOperatorStatus uv_select_linked_internal(bContext *C,
wmOperator *op,
const wmEvent *event,
bool pick)
{
const ARegion *region = CTX_wm_region(C);
Scene *scene = CTX_data_scene(C);
const ToolSettings *ts = scene->toolsettings;
ViewLayer *view_layer = CTX_data_view_layer(C);
bool extend = true;
bool deselect = false;
bool select_faces = (ts->uv_flag & UV_FLAG_SELECT_SYNC) && (ts->selectmode & SCE_SELECT_FACE) &&
(ts->uv_sticky == UV_STICKY_VERT);
UvNearestHit hit = region ? uv_nearest_hit_init_max(&region->v2d) :
uv_nearest_hit_init_max_default();
if (pick) {
extend = RNA_boolean_get(op->ptr, "extend");
deselect = RNA_boolean_get(op->ptr, "deselect");
}
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs(
scene, view_layer, nullptr);
if (pick) {
float co[2];
if (event) {
/* invoke */
UI_view2d_region_to_view(&region->v2d, event->mval[0], event->mval[1], &co[0], &co[1]);
RNA_float_set_array(op->ptr, "location", co);
}
else {
/* exec */
RNA_float_get_array(op->ptr, "location", co);
}
if (!uv_find_nearest_edge_multi(scene, objects, co, 0.0f, &hit)) {
return OPERATOR_CANCELLED;
}
}
if (!extend && !deselect) {
uv_select_all_perform_multi(scene, objects, SEL_DESELECT);
}
uv_select_linked_multi(scene,
objects,
pick ? &hit : nullptr,
extend,
deselect,
false,
select_faces,
BM_ELEM_SELECT);
if (pick) {
DEG_id_tag_update(static_cast<ID *>(hit.ob->data), ID_RECALC_SYNC_TO_EVAL | ID_RECALC_SELECT);
WM_event_add_notifier(C, NC_GEOM | ND_SELECT, hit.ob->data);
}
else {
for (Object *obedit : objects) {
DEG_id_tag_update(static_cast<ID *>(obedit->data),
ID_RECALC_SYNC_TO_EVAL | ID_RECALC_SELECT);
WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data);
}
}
return OPERATOR_FINISHED;
}
static wmOperatorStatus uv_select_linked_exec(bContext *C, wmOperator *op)
{
return uv_select_linked_internal(C, op, nullptr, false);
}
void UV_OT_select_linked(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Select Linked";
ot->description = "Select all UV vertices linked to the active UV map";
ot->idname = "UV_OT_select_linked";
/* API callbacks. */
ot->exec = uv_select_linked_exec;
ot->poll = ED_operator_uvedit; /* requires space image */
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select Linked (Cursor Pick) Operator
* \{ */
static wmOperatorStatus uv_select_linked_pick_invoke(bContext *C,
wmOperator *op,
const wmEvent *event)
{
return uv_select_linked_internal(C, op, event, true);
}
static wmOperatorStatus uv_select_linked_pick_exec(bContext *C, wmOperator *op)
{
return uv_select_linked_internal(C, op, nullptr, true);
}
void UV_OT_select_linked_pick(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Select Linked Pick";
ot->description = "Select all UV vertices linked under the mouse";
ot->idname = "UV_OT_select_linked_pick";
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* API callbacks. */
ot->invoke = uv_select_linked_pick_invoke;
ot->exec = uv_select_linked_pick_exec;
ot->poll = ED_operator_uvedit; /* requires space image */
/* properties */
PropertyRNA *prop;
prop = RNA_def_boolean(ot->srna,
"extend",
false,
"Extend",
"Extend selection rather than clearing the existing selection");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_boolean(ot->srna,
"deselect",
false,
"Deselect",
"Deselect linked UV vertices rather than selecting them");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_float_vector(
ot->srna,
"location",
2,
nullptr,
-FLT_MAX,
FLT_MAX,
"Location",
"Mouse location in normalized coordinates, 0.0 to 1.0 is within the image bounds",
-100.0f,
100.0f);
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select Split Operator
* \{ */
/**
* NOTE(@ideasman42): This is based on similar use case to #MESH_OT_split(),
* which has a similar effect but in this case they are not joined to begin with
* (only having the behavior of being joined) so its best to call this #uv_select_split()
* instead of just split(), but assigned to the same key as #MESH_OT_split.
*/
static wmOperatorStatus uv_select_split_exec(bContext *C, wmOperator *op)
{
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
const ToolSettings *ts = scene->toolsettings;
BMFace *efa;
BMLoop *l;
BMIter iter, liter;
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
/* 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;
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs(
scene, view_layer, nullptr);
for (Object *obedit : objects) {
BMesh *bm = BKE_editmesh_from_object(obedit)->bm;
bool changed = false;
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
uvedit_select_prepare_sync_select(scene, bm);
}
else {
uvedit_select_prepare_custom_data(scene, bm);
}
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
bool is_sel = false;
bool is_unsel = false;
if (!uvedit_face_visible_test(scene, efa)) {
continue;
}
/* are we all selected? */
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
const bool select_vert = uvedit_loop_vert_select_get(ts, bm, l);
const bool select_edge = uvedit_loop_edge_select_get(ts, bm, l);
if (select_vert || select_edge) {
is_sel = true;
}
if (!select_vert || !select_edge) {
is_unsel = true;
}
/* we have mixed selection, bail out */
if (is_sel && is_unsel) {
break;
}
}
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) {
uvedit_loop_vert_select_set(ts, bm, l, false);
uvedit_loop_edge_select_set(ts, bm, l, false);
}
changed = true;
}
}
if (changed) {
changed_multi = true;
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_IMAGE, nullptr);
uv_select_tag_update_for_object(depsgraph, ts, obedit);
}
}
return changed_multi ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
}
void UV_OT_select_split(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Select Split";
ot->description = "Select only entirely selected faces";
ot->idname = "UV_OT_select_split";
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* API callbacks. */
ot->exec = uv_select_split_exec;
ot->poll = ED_operator_uvedit; /* requires space image */
}
static void uv_select_tag_update_for_object(Depsgraph *depsgraph,
const ToolSettings *ts,
Object *obedit)
{
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
DEG_id_tag_update(static_cast<ID *>(obedit->data), ID_RECALC_SELECT);
WM_main_add_notifier(NC_GEOM | ND_SELECT, obedit->data);
}
else {
Object *obedit_eval = DEG_get_evaluated(depsgraph, obedit);
BKE_mesh_batch_cache_dirty_tag(static_cast<Mesh *>(obedit_eval->data),
BKE_MESH_BATCH_DIRTY_UVEDIT_SELECT);
/* Only for region redraw. */
WM_main_add_notifier(NC_GEOM | ND_SELECT, obedit->data);
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select/Tag Flushing Utils
*
* Utility functions to flush the uv-selection from tags.
* \{ */
/**
* helper function for #uv_select_flush_from_tag_loop and uv_select_flush_from_tag_face
*/
static void 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);
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 (!(l_iter->v == v && l_iter != l)) {
continue;
}
if (!uvedit_face_visible_test(scene, l_iter->f)) {
continue;
}
if (BM_loop_uv_share_vert_check(l, l_iter, offsets.uv)) {
uvedit_uv_select_set(scene, bm, l_iter, select);
}
} while ((l_iter = l_iter->radial_next) != l_first);
} while ((e_iter = BM_DISK_EDGE_NEXT(e_iter, v)) != e_first);
}
/**
* Flush the selection from face tags based on sticky and selection modes.
*
* needed because setting the selection of a face is done in a number of places but it also
* needs to respect the sticky modes for the UV verts, so dealing with the sticky modes
* is best done in a separate function.
*
* \note This function is very similar to #uv_select_flush_from_tag_loop,
* be sure to update both upon changing.
*/
static void uv_select_flush_from_tag_face(const Scene *scene, Object *obedit, const bool select)
{
/* Selecting UV Faces with some modes requires us to change
* the selection in other faces (depending on the sticky mode).
*
* This only needs to be done when the Mesh is not used for
* selection (so for sticky modes, vertex or location based). */
const ToolSettings *ts = scene->toolsettings;
BMesh *bm = BKE_editmesh_from_object(obedit)->bm;
BMFace *efa;
BMLoop *l;
BMIter iter, liter;
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);
bool use_sticky = true;
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
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. */
use_sticky = false;
}
if (use_sticky) {
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
if (!BM_elem_flag_test(efa, BM_ELEM_TAG)) {
continue;
}
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
BM_face_uvselect_set_noflush(bm, efa, select);
}
else {
uvedit_face_select_set_no_sync(ts, bm, efa, select);
}
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
uvedit_loop_edge_select_set(ts, bm, l, select);
if (select) {
uvedit_uv_select_flush_from_tag_sticky_loc_internal(scene, bm, l, select, offsets);
}
else {
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);
}
}
}
}
}
else {
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
if (!BM_elem_flag_test(efa, BM_ELEM_TAG)) {
continue;
}
uvedit_face_select_set(scene, bm, efa, select);
}
}
}
/**
* Flush the selection from loop tags based on sticky and selection modes.
*
* needed because setting the selection of a face is done in a number of places but it also needs
* to respect the sticky modes for the UV verts, so dealing with the sticky modes is best done
* in a separate function.
*
* \note This function is very similar to #uv_select_flush_from_tag_face,
* be sure to update both upon changing.
*/
static void uv_select_flush_from_tag_loop(const Scene *scene, Object *obedit, const bool select)
{
/* Selecting UV Loops with some modes requires us to change
* the selection in other faces (depending on the sticky mode).
*
* This only needs to be done when the Mesh is not used for
* selection (so for sticky modes, vertex or location based). */
const ToolSettings *ts = scene->toolsettings;
BMesh *bm = BKE_editmesh_from_object(obedit)->bm;
BMFace *efa;
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) {
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 ((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);
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)) {
BM_elem_flag_enable(l->v, BM_ELEM_TAG);
}
}
}
/* now select tagged verts */
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
bool tag_all = true;
bool tag_any = false;
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
if (BM_elem_flag_test(l->v, BM_ELEM_TAG)) {
uvedit_uv_select_set(scene, bm, l, select);
tag_any = true;
}
else {
tag_all = false;
}
}
if (select) {
if (tag_all && uvedit_face_visible_test(scene, efa)) {
uvedit_face_select_set_no_sync(ts, bm, efa, true);
}
}
else {
if (tag_any && uvedit_face_visible_test(scene, efa)) {
uvedit_face_select_set_no_sync(ts, bm, efa, false);
}
}
}
}
else if ((use_mesh_select == false) && (ts->uv_sticky == UV_STICKY_LOCATION)) {
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
bool tag_all = true;
bool tag_any = false;
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
if (BM_elem_flag_test(l, BM_ELEM_TAG)) {
uvedit_uv_select_flush_from_tag_sticky_loc_internal(scene, bm, l, select, offsets);
tag_any = true;
}
else {
tag_all = false;
}
}
if (select) {
if (tag_all && uvedit_face_visible_test(scene, efa)) {
uvedit_face_select_set_no_sync(ts, bm, efa, true);
}
}
else {
if (tag_any && uvedit_face_visible_test(scene, efa)) {
uvedit_face_select_set_no_sync(ts, bm, efa, false);
}
}
}
}
else { /* UV_STICKY_DISABLE or ts->uv_flag & UV_FLAG_SELECT_SYNC */
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)) {
uvedit_uv_select_set(scene, bm, l, select);
}
}
}
}
}
/**
* Flush the selection from UV edges based on sticky modes.
*
* Useful when performing edge selections in different sticky modes, since setting the required
* edge selection is done manually or using #uvedit_edge_select_set_noflush,
* but dealing with sticky modes for vertex selections is best done in a separate function.
*
* \note Current behavior is selecting only; deselecting can be added but the behavior isn't
* required anywhere.
*/
static void uv_select_flush_from_loop_edge_flag(const Scene *scene, BMesh *bm)
{
const ToolSettings *ts = scene->toolsettings;
BMFace *efa;
BMLoop *l;
BMIter iter, liter;
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);
bool use_sticky = true;
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
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. */
use_sticky = false;
}
if (use_sticky) {
/* Use UV edge selection to identify which verts must to be selected */
/* Clear UV vert flags */
bm_clear_uv_vert_selection(scene, bm);
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
if (!uvedit_face_visible_test(scene, efa)) {
/* This visibility check could be removed? Simply relying on edge flags to ensure
* visibility might be sufficient. */
continue;
}
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
/* Select verts based on UV edge flag. */
if (uvedit_edge_select_get_no_sync(ts, bm, l)) {
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 {
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) {
bool select_all = true;
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
if (uvedit_edge_select_get_no_sync(ts, bm, l)) {
uvedit_vert_select_set_no_sync(ts, bm, l, true);
uvedit_vert_select_set_no_sync(ts, bm, l->next, true);
}
else if (!uvedit_edge_select_get_no_sync(ts, bm, l->prev)) {
uvedit_vert_select_set_no_sync(ts, bm, l->next, false);
select_all = false;
}
}
uvedit_face_select_set_no_sync(ts, bm, efa, select_all);
}
}
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Box Select Operator
* \{ */
static wmOperatorStatus uv_box_select_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
const Scene *scene = CTX_data_scene(C);
const bool pinned = RNA_boolean_get(op->ptr, "pinned");
if (pinned) {
if (!uvedit_select_pin_ok_or_report(scene, op->reports)) {
return OPERATOR_CANCELLED;
}
}
return WM_gesture_box_invoke(C, op, event);
}
static wmOperatorStatus uv_box_select_exec(bContext *C, wmOperator *op)
{
const Scene *scene = CTX_data_scene(C);
const ToolSettings *ts = scene->toolsettings;
const bool pinned = RNA_boolean_get(op->ptr, "pinned");
/* Note that face selection uses the face-center. */
const char uv_select_mode = ED_uvedit_select_mode_get(scene);
const bool use_select_linked = pinned ? false : ED_uvedit_select_island_check(ts);
if (pinned) {
if (!uvedit_select_pin_ok_or_report(scene, op->reports)) {
return OPERATOR_CANCELLED;
}
}
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
const ARegion *region = CTX_wm_region(C);
BMFace *efa;
BMLoop *l;
BMIter iter, liter;
float *luv;
rctf rectf;
/* get rectangle from operator */
WM_operator_properties_border_to_rctf(op, &rectf);
UI_view2d_region_to_view_rctf(&region->v2d, &rectf, &rectf);
const eSelectOp sel_op = eSelectOp(RNA_enum_get(op->ptr, "mode"));
const bool select = (sel_op != SEL_OP_SUB);
const bool use_pre_deselect = SEL_OP_USE_PRE_DESELECT(sel_op);
bool changed_multi = false;
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs(
scene, view_layer, nullptr);
if (use_pre_deselect) {
uv_select_all_perform_multi(scene, objects, SEL_DESELECT);
}
/* don't indent to avoid diff noise! */
for (Object *obedit : objects) {
BMesh *bm = BKE_editmesh_from_object(obedit)->bm;
bool changed = false;
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);
/* do actual selection */
if (pinned && offsets.pin == -1) {
/* Special case, nothing is pinned so it's known in advance that nothing will be selected.
* Still run the code after this block finishes as the UV's may have been de-selected. */
}
else if (uv_select_mode == UV_SELECT_FACE) {
/* Handle face selection (face center). */
if (use_select_linked) {
BM_mesh_elem_hflag_disable_all(bm, BM_FACE, BM_ELEM_TAG, false);
}
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
if (use_select_linked) {
if (BM_elem_flag_test(efa, BM_ELEM_TAG)) {
continue;
}
}
else {
BM_elem_flag_disable(efa, BM_ELEM_TAG);
}
bool has_selected = false;
if (uvedit_face_visible_test(scene, efa)) {
float cent[2];
BM_face_uv_calc_center_median(efa, offsets.uv, cent);
if (BLI_rctf_isect_pt_v(&rectf, cent)) {
BM_elem_flag_enable(efa, BM_ELEM_TAG);
has_selected = true;
changed = true;
}
}
if (has_selected && use_select_linked) {
uv_select_linked_multi_for_select_island(
scene, objects, obedit, efa, !select, true, BM_ELEM_TAG);
}
}
/* (de)selects all tagged faces and deals with sticky modes */
if (changed) {
uv_select_flush_from_tag_face(scene, obedit, select);
}
}
else if (uv_select_mode == UV_SELECT_EDGE) {
bool do_second_pass = true;
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
if (!uvedit_face_visible_test(scene, efa)) {
continue;
}
BMLoop *l_prev = BM_FACE_FIRST_LOOP(efa)->prev;
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) {
luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
if (BLI_rctf_isect_pt_v(&rectf, luv) && BLI_rctf_isect_pt_v(&rectf, luv_prev)) {
uvedit_edge_select_set_with_sticky(scene, bm, l_prev, select, offsets);
do_second_pass = false;
has_selected = true;
changed = true;
}
l_prev = l;
luv_prev = luv;
}
if (has_selected && use_select_linked) {
uv_select_linked_multi_for_select_island(
scene, objects, obedit, efa, !select, false, BM_ELEM_SELECT);
}
}
/* Do a second pass if no complete edges could be selected.
* This matches wire-frame edit-mesh selection in the 3D view. */
if (do_second_pass) {
/* Second pass to check if edges partially overlap with the selection area (box). */
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
if (!uvedit_face_visible_test(scene, efa)) {
continue;
}
BMLoop *l_prev = BM_FACE_FIRST_LOOP(efa)->prev;
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) {
luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
if (BLI_rctf_isect_segment(&rectf, luv_prev, luv)) {
uvedit_edge_select_set_with_sticky(scene, bm, l_prev, select, offsets);
has_selected = true;
changed = true;
}
l_prev = l;
luv_prev = luv;
}
if (has_selected && use_select_linked) {
uv_select_linked_multi_for_select_island(
scene, objects, obedit, efa, !select, false, BM_ELEM_SELECT);
}
}
}
}
else {
/* Handle vert selection. */
BLI_assert(uv_select_mode == UV_SELECT_VERT);
changed = true;
BM_mesh_elem_hflag_disable_all(bm, BM_VERT, BM_ELEM_TAG, false);
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
if (!uvedit_face_visible_test(scene, efa)) {
continue;
}
bool has_selected = false;
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
if (select != uvedit_uv_select_test(scene, bm, l, offsets)) {
if (BLI_rctf_isect_pt_v(&rectf, luv)) {
if (!pinned || BM_ELEM_CD_GET_BOOL(l, offsets.pin)) {
uvedit_uv_select_set(scene, bm, l, select);
BM_elem_flag_enable(l->v, BM_ELEM_TAG);
has_selected = true;
}
}
}
}
if (has_selected && use_select_linked) {
uv_select_linked_multi_for_select_island(
scene, objects, obedit, efa, !select, false, BM_ELEM_SELECT);
}
}
if (ts->uv_sticky == UV_STICKY_VERT) {
uvedit_vertex_select_tagged(bm, scene, select);
}
}
if (changed || use_pre_deselect) {
changed_multi = true;
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
ED_uvedit_select_sync_flush(ts, bm, select);
}
else {
ED_uvedit_selectmode_flush(scene, bm);
}
uv_select_tag_update_for_object(depsgraph, ts, obedit);
}
}
return changed_multi ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
}
void UV_OT_select_box(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Box Select";
ot->description = "Select UV vertices using box selection";
ot->idname = "UV_OT_select_box";
/* API callbacks. */
ot->invoke = uv_box_select_invoke;
ot->exec = uv_box_select_exec;
ot->modal = WM_gesture_box_modal;
ot->poll = ED_operator_uvedit_space_image; /* requires space image */
ot->cancel = WM_gesture_box_cancel;
/* flags */
ot->flag = OPTYPE_UNDO;
/* properties */
RNA_def_boolean(ot->srna, "pinned", false, "Pinned", "Border select pinned UVs only");
WM_operator_properties_gesture_box(ot);
WM_operator_properties_select_operation_simple(ot);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Circle Select Operator
* \{ */
static bool uv_circle_select_is_point_inside(const float uv[2],
const float offset[2],
const float ellipse[2])
{
/* normalized ellipse: ell[0] = scaleX, ell[1] = scaleY */
const float co[2] = {
(uv[0] - offset[0]) * ellipse[0],
(uv[1] - offset[1]) * ellipse[1],
};
return len_squared_v2(co) < 1.0f;
}
static bool uv_circle_select_is_edge_inside(const float uv_a[2],
const float uv_b[2],
const float offset[2],
const float ellipse[2])
{
/* normalized ellipse: ell[0] = scaleX, ell[1] = scaleY */
const float co_a[2] = {
(uv_a[0] - offset[0]) * ellipse[0],
(uv_a[1] - offset[1]) * ellipse[1],
};
const float co_b[2] = {
(uv_b[0] - offset[0]) * ellipse[0],
(uv_b[1] - offset[1]) * ellipse[1],
};
const float co_zero[2] = {0.0f, 0.0f};
return dist_squared_to_line_segment_v2(co_zero, co_a, co_b) < 1.0f;
}
static wmOperatorStatus uv_circle_select_exec(bContext *C, wmOperator *op)
{
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
SpaceImage *sima = CTX_wm_space_image(C);
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
const ToolSettings *ts = scene->toolsettings;
const ARegion *region = CTX_wm_region(C);
BMFace *efa;
BMLoop *l;
BMIter iter, liter;
float *luv;
int x, y, radius, width, height;
float zoomx, zoomy;
float offset[2], ellipse[2];
/* Note that face selection uses the face-center. */
const char uv_select_mode = ED_uvedit_select_mode_get(scene);
const bool use_select_linked = ED_uvedit_select_island_check(ts);
/* get operator properties */
x = RNA_int_get(op->ptr, "x");
y = RNA_int_get(op->ptr, "y");
radius = RNA_int_get(op->ptr, "radius");
/* compute ellipse size and location, not a circle since we deal
* with non square image. ellipse is normalized, r = 1.0. */
ED_space_image_get_size(sima, &width, &height);
ED_space_image_get_zoom(sima, region, &zoomx, &zoomy);
ellipse[0] = width * zoomx / radius;
ellipse[1] = height * zoomy / radius;
UI_view2d_region_to_view(&region->v2d, x, y, &offset[0], &offset[1]);
bool changed_multi = false;
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs(
scene, view_layer, nullptr);
const eSelectOp sel_op = ED_select_op_modal(
eSelectOp(RNA_enum_get(op->ptr, "mode")),
WM_gesture_is_modal_first(static_cast<wmGesture *>(op->customdata)));
const bool select = (sel_op != SEL_OP_SUB);
const bool use_pre_deselect = SEL_OP_USE_PRE_DESELECT(sel_op);
if (use_pre_deselect) {
uv_select_all_perform_multi(scene, objects, SEL_DESELECT);
}
for (Object *obedit : objects) {
BMesh *bm = BKE_editmesh_from_object(obedit)->bm;
bool changed = false;
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);
/* do selection */
if (uv_select_mode == UV_SELECT_FACE) {
/* Handle face selection (face center). */
if (use_select_linked) {
BM_mesh_elem_hflag_disable_all(bm, BM_FACE, BM_ELEM_TAG, false);
}
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
if (use_select_linked) {
if (BM_elem_flag_test(efa, BM_ELEM_TAG)) {
continue;
}
}
else {
BM_elem_flag_disable(efa, BM_ELEM_TAG);
if (select == uvedit_face_select_test(scene, bm, efa)) {
continue;
}
}
bool has_selected = false;
float cent[2];
BM_face_uv_calc_center_median(efa, offsets.uv, cent);
if (uv_circle_select_is_point_inside(cent, offset, ellipse)) {
BM_elem_flag_enable(efa, BM_ELEM_TAG);
has_selected = true;
changed = true;
}
if (has_selected && use_select_linked) {
uv_select_linked_multi_for_select_island(
scene, objects, obedit, efa, !select, true, BM_ELEM_TAG);
}
}
/* (de)selects all tagged faces and deals with sticky modes */
if (changed) {
uv_select_flush_from_tag_face(scene, obedit, select);
}
}
else if (uv_select_mode == UV_SELECT_EDGE) {
/* Handle edge selection. */
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
if (!uvedit_face_visible_test(scene, efa)) {
continue;
}
BMLoop *l_prev = BM_FACE_FIRST_LOOP(efa)->prev;
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) {
luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
if (uv_circle_select_is_edge_inside(luv, luv_prev, offset, ellipse)) {
uvedit_edge_select_set_with_sticky(scene, bm, l_prev, select, offsets);
has_selected = true;
changed = true;
}
l_prev = l;
luv_prev = luv;
}
if (has_selected && use_select_linked) {
uv_select_linked_multi_for_select_island(
scene, objects, obedit, efa, !select, false, BM_ELEM_SELECT);
}
}
}
else {
/* Handle vert selection. */
BLI_assert(uv_select_mode == UV_SELECT_VERT);
BM_mesh_elem_hflag_disable_all(bm, BM_VERT, BM_ELEM_TAG, false);
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
if (!uvedit_face_visible_test(scene, efa)) {
continue;
}
bool has_selected = false;
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
if (select != uvedit_uv_select_test(scene, 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;
uvedit_uv_select_set(scene, bm, l, select);
BM_elem_flag_enable(l->v, BM_ELEM_TAG);
has_selected = true;
}
}
}
if (has_selected && use_select_linked) {
uv_select_linked_multi_for_select_island(
scene, objects, obedit, efa, !select, false, BM_ELEM_SELECT);
}
}
if (ts->uv_sticky == UV_STICKY_VERT) {
uvedit_vertex_select_tagged(bm, scene, select);
}
}
if (changed || use_pre_deselect) {
changed_multi = true;
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
ED_uvedit_select_sync_flush(ts, bm, select);
}
else {
ED_uvedit_selectmode_flush(scene, bm);
}
uv_select_tag_update_for_object(depsgraph, ts, obedit);
}
}
return changed_multi ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
}
void UV_OT_select_circle(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Circle Select";
ot->description = "Select UV vertices using circle selection";
ot->idname = "UV_OT_select_circle";
/* API callbacks. */
ot->invoke = WM_gesture_circle_invoke;
ot->modal = WM_gesture_circle_modal;
ot->exec = uv_circle_select_exec;
ot->poll = ED_operator_uvedit_space_image; /* requires space image */
ot->cancel = WM_gesture_circle_cancel;
ot->get_name = ED_select_circle_get_name;
/* flags */
ot->flag = OPTYPE_UNDO;
/* properties */
WM_operator_properties_gesture_circle(ot);
WM_operator_properties_select_operation_simple(ot);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Lasso Select Operator
* \{ */
static bool do_lasso_select_mesh_uv_is_point_inside(const ARegion *region,
const rcti *clip_rect,
const Span<int2> mcoords,
const float co_test[2])
{
int co_screen[2];
if (UI_view2d_view_to_region_clip(
&region->v2d, co_test[0], co_test[1], &co_screen[0], &co_screen[1]) &&
BLI_rcti_isect_pt_v(clip_rect, co_screen) &&
BLI_lasso_is_point_inside(mcoords, co_screen[0], co_screen[1], V2D_IS_CLIPPED))
{
return true;
}
return false;
}
static bool do_lasso_select_mesh_uv_is_edge_inside(const ARegion *region,
const rcti *clip_rect,
const Span<int2> mcoords,
const float co_test_a[2],
const float co_test_b[2])
{
int co_screen_a[2], co_screen_b[2];
if (UI_view2d_view_to_region_segment_clip(
&region->v2d, co_test_a, co_test_b, co_screen_a, co_screen_b) &&
BLI_rcti_isect_segment(clip_rect, co_screen_a, co_screen_b) &&
BLI_lasso_is_edge_inside(
mcoords, UNPACK2(co_screen_a), UNPACK2(co_screen_b), V2D_IS_CLIPPED))
{
return true;
}
return false;
}
static bool do_lasso_select_mesh_uv(bContext *C, const Span<int2> mcoords, const eSelectOp sel_op)
{
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
const ARegion *region = CTX_wm_region(C);
Scene *scene = CTX_data_scene(C);
const ToolSettings *ts = scene->toolsettings;
ViewLayer *view_layer = CTX_data_view_layer(C);
/* Note that face selection uses the face-center. */
const char uv_select_mode = ED_uvedit_select_mode_get(scene);
const bool use_select_linked = ED_uvedit_select_island_check(ts);
const bool select = (sel_op != SEL_OP_SUB);
const bool use_pre_deselect = SEL_OP_USE_PRE_DESELECT(sel_op);
BMIter iter, liter;
BMFace *efa;
BMLoop *l;
bool changed_multi = false;
rcti rect;
BLI_lasso_boundbox(&rect, mcoords);
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs(
scene, view_layer, nullptr);
if (use_pre_deselect) {
uv_select_all_perform_multi(scene, objects, SEL_DESELECT);
}
for (Object *obedit : objects) {
bool changed = false;
BMesh *bm = BKE_editmesh_from_object(obedit)->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);
if (uv_select_mode == UV_SELECT_FACE) {
/* Handle face selection (face center). */
if (use_select_linked) {
BM_mesh_elem_hflag_disable_all(bm, BM_FACE, BM_ELEM_TAG, false);
}
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
if (use_select_linked) {
if (BM_elem_flag_test(efa, BM_ELEM_TAG)) {
continue;
}
}
else {
BM_elem_flag_disable(efa, BM_ELEM_TAG);
if (select == uvedit_face_select_test(scene, bm, efa)) {
continue;
}
}
bool has_selected = false;
float cent[2];
BM_face_uv_calc_center_median(efa, offsets.uv, cent);
if (do_lasso_select_mesh_uv_is_point_inside(region, &rect, mcoords, cent)) {
BM_elem_flag_enable(efa, BM_ELEM_TAG);
has_selected = true;
changed = true;
}
if (has_selected && use_select_linked) {
uv_select_linked_multi_for_select_island(
scene, objects, obedit, efa, !select, true, BM_ELEM_TAG);
}
}
/* (de)selects all tagged faces and deals with sticky modes */
if (changed) {
uv_select_flush_from_tag_face(scene, obedit, select);
}
}
else if (uv_select_mode == UV_SELECT_EDGE) {
/* Handle edge selection. */
bool do_second_pass = true;
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
if (!uvedit_face_visible_test(scene, efa)) {
continue;
}
bool has_selected = false;
BMLoop *l_prev = BM_FACE_FIRST_LOOP(efa)->prev;
float *luv_prev = BM_ELEM_CD_GET_FLOAT_P(l_prev, offsets.uv);
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
float *luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
if (do_lasso_select_mesh_uv_is_point_inside(region, &rect, mcoords, luv) &&
do_lasso_select_mesh_uv_is_point_inside(region, &rect, mcoords, luv_prev))
{
uvedit_edge_select_set_with_sticky(scene, bm, l_prev, select, offsets);
do_second_pass = false;
has_selected = true;
changed = true;
}
l_prev = l;
luv_prev = luv;
}
if (has_selected && use_select_linked) {
uv_select_linked_multi_for_select_island(
scene, objects, obedit, efa, !select, false, BM_ELEM_SELECT);
}
}
/* Do a second pass if no complete edges could be selected.
* This matches wire-frame edit-mesh selection in the 3D view. */
if (do_second_pass) {
/* Second pass to check if edges partially overlap with the selection area (lasso). */
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
if (!uvedit_face_visible_test(scene, efa)) {
continue;
}
BMLoop *l_prev = BM_FACE_FIRST_LOOP(efa)->prev;
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) {
float *luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
if (do_lasso_select_mesh_uv_is_edge_inside(region, &rect, mcoords, luv, luv_prev)) {
uvedit_edge_select_set_with_sticky(scene, bm, l_prev, select, offsets);
has_selected = true;
changed = true;
}
l_prev = l;
luv_prev = luv;
}
if (has_selected && use_select_linked) {
uv_select_linked_multi_for_select_island(
scene, objects, obedit, efa, !select, false, BM_ELEM_SELECT);
}
}
}
}
else {
/* Handle vert selection. */
BLI_assert(uv_select_mode == UV_SELECT_VERT);
BM_mesh_elem_hflag_disable_all(bm, BM_VERT, BM_ELEM_TAG, false);
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
if (!uvedit_face_visible_test(scene, efa)) {
continue;
}
bool has_selected = false;
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
if (select != uvedit_uv_select_test(scene, 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);
changed = true;
BM_elem_flag_enable(l->v, BM_ELEM_TAG);
has_selected = true;
}
}
}
if (has_selected && use_select_linked) {
uv_select_linked_multi_for_select_island(
scene, objects, obedit, efa, !select, false, BM_ELEM_SELECT);
}
}
if (ts->uv_sticky == UV_STICKY_VERT) {
uvedit_vertex_select_tagged(bm, scene, select);
}
}
if (changed || use_pre_deselect) {
changed_multi = true;
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
ED_uvedit_select_sync_flush(ts, bm, select);
}
else {
ED_uvedit_selectmode_flush(scene, bm);
}
uv_select_tag_update_for_object(depsgraph, ts, obedit);
}
}
return changed_multi;
}
static wmOperatorStatus uv_lasso_select_exec(bContext *C, wmOperator *op)
{
Array<int2> mcoords = WM_gesture_lasso_path_to_array(C, op);
if (mcoords.is_empty()) {
return OPERATOR_PASS_THROUGH;
}
const eSelectOp sel_op = eSelectOp(RNA_enum_get(op->ptr, "mode"));
bool changed = do_lasso_select_mesh_uv(C, mcoords, sel_op);
return changed ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
}
void UV_OT_select_lasso(wmOperatorType *ot)
{
ot->name = "Lasso Select UV";
ot->description = "Select UVs using lasso selection";
ot->idname = "UV_OT_select_lasso";
ot->invoke = WM_gesture_lasso_invoke;
ot->modal = WM_gesture_lasso_modal;
ot->exec = uv_lasso_select_exec;
ot->poll = ED_operator_uvedit_space_image;
ot->cancel = WM_gesture_lasso_cancel;
/* flags */
ot->flag = OPTYPE_UNDO | OPTYPE_DEPENDS_ON_CURSOR;
/* properties */
WM_operator_properties_gesture_lasso(ot);
WM_operator_properties_select_operation_simple(ot);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select Pinned UVs Operator
* \{ */
static wmOperatorStatus uv_select_pinned_exec(bContext *C, wmOperator *op)
{
const Scene *scene = CTX_data_scene(C);
const ToolSettings *ts = scene->toolsettings;
/* Use this operator only in vertex mode, since it is not guaranteed that pinned vertices may
* form higher selection states (like edges/faces/islands) in other modes. */
if (!uvedit_select_pin_ok_or_report(scene, op->reports)) {
return OPERATOR_CANCELLED;
}
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
BMFace *efa;
BMLoop *l;
BMIter iter, liter;
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs(
scene, view_layer, nullptr);
for (Object *obedit : objects) {
BMesh *bm = BKE_editmesh_from_object(obedit)->bm;
const char *active_uv_name = CustomData_get_active_layer_name(&bm->ldata, CD_PROP_FLOAT2);
if (!BM_uv_map_attr_pin_exists(bm, active_uv_name)) {
continue;
}
bool changed = false;
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) {
if (!uvedit_face_visible_test(scene, efa)) {
continue;
}
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
if (BM_ELEM_CD_GET_BOOL(l, offsets.pin)) {
uvedit_uv_select_enable(scene, bm, l);
changed = true;
}
}
}
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
ED_uvedit_select_sync_flush(ts, bm, true);
}
else {
ED_uvedit_selectmode_flush(scene, bm);
}
if (changed) {
uv_select_tag_update_for_object(depsgraph, ts, obedit);
}
}
return OPERATOR_FINISHED;
}
void UV_OT_select_pinned(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Selected Pinned";
ot->description = "Select all pinned UV vertices";
ot->idname = "UV_OT_select_pinned";
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* API callbacks. */
ot->exec = uv_select_pinned_exec;
ot->poll = ED_operator_uvedit;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select Overlap Operator
* \{ */
BLI_INLINE uint overlap_hash(const void *overlap_v)
{
const BVHTreeOverlap *overlap = static_cast<const BVHTreeOverlap *>(overlap_v);
/* Designed to treat (A,B) and (B,A) as the same. */
int x = overlap->indexA;
int y = overlap->indexB;
if (x > y) {
std::swap(x, y);
}
return BLI_hash_int_2d(x, y);
}
BLI_INLINE bool overlap_cmp(const void *a_v, const void *b_v)
{
const BVHTreeOverlap *a = static_cast<const BVHTreeOverlap *>(a_v);
const BVHTreeOverlap *b = static_cast<const BVHTreeOverlap *>(b_v);
return !((a->indexA == b->indexA && a->indexB == b->indexB) ||
(a->indexA == b->indexB && a->indexB == b->indexA));
}
struct UVOverlapData {
int ob_index;
int face_index;
float tri[3][2];
};
/**
* Specialized 2D triangle intersection for detecting UV overlap:
*
* \return
* - false when single corners or edges touch (common for UV coordinates).
* - true when all corners touch (an exactly overlapping triangle).
*/
static bool overlap_tri_tri_uv_test(const float t1[3][2],
const float t2[3][2],
const float endpoint_bias)
{
float vi[2];
/* Don't use 'isect_tri_tri_v2' here
* because it's important to ignore overlap at end-points. */
if (isect_seg_seg_v2_point_ex(t1[0], t1[1], t2[0], t2[1], endpoint_bias, vi) == 1 ||
isect_seg_seg_v2_point_ex(t1[0], t1[1], t2[1], t2[2], endpoint_bias, vi) == 1 ||
isect_seg_seg_v2_point_ex(t1[0], t1[1], t2[2], t2[0], endpoint_bias, vi) == 1 ||
isect_seg_seg_v2_point_ex(t1[1], t1[2], t2[0], t2[1], endpoint_bias, vi) == 1 ||
isect_seg_seg_v2_point_ex(t1[1], t1[2], t2[1], t2[2], endpoint_bias, vi) == 1 ||
isect_seg_seg_v2_point_ex(t1[1], t1[2], t2[2], t2[0], endpoint_bias, vi) == 1 ||
isect_seg_seg_v2_point_ex(t1[2], t1[0], t2[0], t2[1], endpoint_bias, vi) == 1 ||
isect_seg_seg_v2_point_ex(t1[2], t1[0], t2[1], t2[2], endpoint_bias, vi) == 1)
{
return true;
}
/* When none of the segments intersect, checking if either of the triangles corners
* is inside the others is almost always sufficient to test if the two triangles intersect.
*
* However, the `endpoint_bias` on segment intersections causes _exact_ overlapping
* triangles not to be detected.
*
* Resolve this problem at the small cost of calculating the triangle center, see #85508. */
mid_v2_v2v2v2(vi, UNPACK3(t1));
if (isect_point_tri_v2(vi, UNPACK3(t2)) != 0) {
return true;
}
mid_v2_v2v2v2(vi, UNPACK3(t2));
if (isect_point_tri_v2(vi, UNPACK3(t1)) != 0) {
return true;
}
return false;
}
static wmOperatorStatus uv_select_overlap(bContext *C, const bool extend)
{
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs(
scene, view_layer, nullptr);
/* Calculate maximum number of tree nodes and prepare initial selection. */
uint uv_tri_len = 0;
for (Object *obedit : objects) {
BMesh *bm = BKE_editmesh_from_object(obedit)->bm;
BM_mesh_elem_table_ensure(bm, BM_FACE);
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) {
ED_uvedit_deselect_all(scene, obedit, SEL_DESELECT);
}
BMIter iter;
BMFace *efa;
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
if (!uvedit_face_visible_test_ex(scene->toolsettings, efa)) {
continue;
}
uv_tri_len += efa->len - 2;
}
}
UVOverlapData *overlap_data = MEM_malloc_arrayN<UVOverlapData>(uv_tri_len, "UvOverlapData");
BVHTree *uv_tree = BLI_bvhtree_new(uv_tri_len, 0.0f, 4, 6);
/* Use a global data index when inserting into the BVH. */
int data_index = 0;
int face_len_alloc = 3;
float (*uv_verts)[2] = static_cast<float (*)[2]>(
MEM_mallocN(sizeof(*uv_verts) * face_len_alloc, "UvOverlapCoords"));
uint(*indices)[3] = static_cast<uint(*)[3]>(
MEM_mallocN(sizeof(*indices) * (face_len_alloc - 2), "UvOverlapTris"));
MemArena *arena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
Heap *heap = BLI_heap_new_ex(BLI_POLYFILL_ALLOC_NGON_RESERVE);
for (const int ob_index : objects.index_range()) {
Object *obedit = objects[ob_index];
BMesh *bm = BKE_editmesh_from_object(obedit)->bm;
BMIter iter, liter;
BMFace *efa;
BMLoop *l;
const int cd_loop_uv_offset = CustomData_get_offset(&bm->ldata, CD_PROP_FLOAT2);
/* Triangulate each UV face and store it inside the BVH. */
int face_index;
BM_ITER_MESH_INDEX (efa, &iter, bm, BM_FACES_OF_MESH, face_index) {
if (!uvedit_face_visible_test_ex(scene->toolsettings, efa)) {
continue;
}
const uint face_len = efa->len;
const uint tri_len = face_len - 2;
if (face_len_alloc < face_len) {
MEM_freeN(uv_verts);
MEM_freeN(indices);
uv_verts = static_cast<float (*)[2]>(
MEM_mallocN(sizeof(*uv_verts) * face_len, "UvOverlapCoords"));
indices = static_cast<uint(*)[3]>(
MEM_mallocN(sizeof(*indices) * tri_len, "UvOverlapTris"));
face_len_alloc = face_len;
}
int vert_index;
BM_ITER_ELEM_INDEX (l, &liter, efa, BM_LOOPS_OF_FACE, vert_index) {
float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
copy_v2_v2(uv_verts[vert_index], luv);
}
/* The UV coordinates winding could be positive of negative,
* determine it automatically. */
const int coords_sign = 0;
BLI_polyfill_calc_arena(uv_verts, face_len, coords_sign, indices, arena);
/* A beauty fill is necessary to remove degenerate triangles that may be produced from the
* above poly-fill (see #103913), otherwise the overlap tests can fail. */
BLI_polyfill_beautify(uv_verts, face_len, indices, arena, heap);
for (int t = 0; t < tri_len; t++) {
overlap_data[data_index].ob_index = ob_index;
overlap_data[data_index].face_index = face_index;
/* BVH needs 3D, overlap data uses 2D. */
const float tri[3][3] = {
{UNPACK2(uv_verts[indices[t][0]]), 0.0f},
{UNPACK2(uv_verts[indices[t][1]]), 0.0f},
{UNPACK2(uv_verts[indices[t][2]]), 0.0f},
};
copy_v2_v2(overlap_data[data_index].tri[0], tri[0]);
copy_v2_v2(overlap_data[data_index].tri[1], tri[1]);
copy_v2_v2(overlap_data[data_index].tri[2], tri[2]);
BLI_bvhtree_insert(uv_tree, data_index, &tri[0][0], 3);
data_index++;
}
BLI_memarena_clear(arena);
BLI_heap_clear(heap, nullptr);
}
}
BLI_assert(data_index == uv_tri_len);
BLI_memarena_free(arena);
BLI_heap_free(heap, nullptr);
MEM_freeN(uv_verts);
MEM_freeN(indices);
BLI_bvhtree_balance(uv_tree);
uint tree_overlap_len;
BVHTreeOverlap *overlap = BLI_bvhtree_overlap_self(uv_tree, &tree_overlap_len, nullptr, nullptr);
if (overlap != nullptr) {
GSet *overlap_set = BLI_gset_new_ex(overlap_hash, overlap_cmp, __func__, tree_overlap_len);
for (int i = 0; i < tree_overlap_len; i++) {
/* Skip overlaps against yourself. */
if (overlap[i].indexA == overlap[i].indexB) {
continue;
}
/* Skip overlaps that have already been tested. */
if (!BLI_gset_add(overlap_set, &overlap[i])) {
continue;
}
const UVOverlapData *o_a = &overlap_data[overlap[i].indexA];
const UVOverlapData *o_b = &overlap_data[overlap[i].indexB];
Object *obedit_a = objects[o_a->ob_index];
Object *obedit_b = objects[o_b->ob_index];
BMesh *bm_a = BKE_editmesh_from_object(obedit_a)->bm;
BMesh *bm_b = BKE_editmesh_from_object(obedit_b)->bm;
BMFace *face_a = bm_a->ftable[o_a->face_index];
BMFace *face_b = bm_b->ftable[o_b->face_index];
if (scene->toolsettings->uv_flag & UV_FLAG_SELECT_SYNC) {
/* Pass. */
}
/* Skip if both faces are already selected. */
if (uvedit_face_select_test(scene, bm_a, face_a) &&
uvedit_face_select_test(scene, bm_b, face_b))
{
continue;
}
/* Main tri-tri overlap test. */
const float endpoint_bias = -1e-4f;
if (overlap_tri_tri_uv_test(o_a->tri, o_b->tri, endpoint_bias)) {
uvedit_face_select_enable(scene, bm_a, face_a);
uvedit_face_select_enable(scene, bm_b, face_b);
}
}
BLI_gset_free(overlap_set, nullptr);
MEM_freeN(overlap);
}
for (Object *object : objects) {
uv_select_tag_update_for_object(depsgraph, scene->toolsettings, object);
}
BLI_bvhtree_free(uv_tree);
MEM_freeN(overlap_data);
return OPERATOR_FINISHED;
}
static wmOperatorStatus uv_select_overlap_exec(bContext *C, wmOperator *op)
{
bool extend = RNA_boolean_get(op->ptr, "extend");
return uv_select_overlap(C, extend);
}
void UV_OT_select_overlap(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Select Overlap";
ot->description = "Select all UV faces which overlap each other";
ot->idname = "UV_OT_select_overlap";
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* API callbacks. */
ot->exec = uv_select_overlap_exec;
ot->poll = ED_operator_uvedit;
/* properties */
RNA_def_boolean(ot->srna,
"extend",
false,
"Extend",
"Extend selection rather than clearing the existing selection");
}
/** \} */
/** \name Select Similar Operator
* \{ */
static float get_uv_vert_needle(const eUVSelectSimilar type,
BMVert *vert,
const float ob_m3[3][3],
BMLoop *loop,
const BMUVOffsets &offsets)
{
BLI_assert(offsets.pin >= 0);
BLI_assert(offsets.uv >= 0);
float result = 0.0f;
switch (type) {
case UV_SSIM_AREA_UV: {
BMFace *f;
BMIter iter;
BM_ITER_ELEM (f, &iter, vert, BM_FACES_OF_VERT) {
result += BM_face_calc_area_uv(f, offsets.uv);
}
break;
}
case UV_SSIM_AREA_3D: {
BMFace *f;
BMIter iter;
BM_ITER_ELEM (f, &iter, vert, BM_FACES_OF_VERT) {
result += BM_face_calc_area_with_mat3(f, ob_m3);
}
break;
}
case UV_SSIM_SIDES: {
BMEdge *e;
BMIter iter;
BM_ITER_ELEM (e, &iter, vert, BM_EDGES_OF_VERT) {
result += 1.0f;
}
break;
}
case UV_SSIM_PIN:
return BM_ELEM_CD_GET_BOOL(loop, offsets.pin) ? 1.0f : 0.0f;
default:
BLI_assert_unreachable();
return false;
}
return result;
}
static float get_uv_edge_needle(const eUVSelectSimilar type,
BMEdge *edge,
const float ob_m3[3][3],
BMLoop *loop_a,
BMLoop *loop_b,
const BMUVOffsets &offsets)
{
BLI_assert(offsets.pin >= 0);
BLI_assert(offsets.uv >= 0);
float result = 0.0f;
switch (type) {
case UV_SSIM_AREA_UV: {
BMFace *f;
BMIter iter;
BM_ITER_ELEM (f, &iter, edge, BM_FACES_OF_EDGE) {
result += BM_face_calc_area_uv(f, offsets.uv);
}
break;
}
case UV_SSIM_AREA_3D: {
BMFace *f;
BMIter iter;
BM_ITER_ELEM (f, &iter, edge, BM_FACES_OF_EDGE) {
result += BM_face_calc_area_with_mat3(f, ob_m3);
}
break;
}
case UV_SSIM_LENGTH_UV: {
float *luv_a = BM_ELEM_CD_GET_FLOAT_P(loop_a, offsets.uv);
float *luv_b = BM_ELEM_CD_GET_FLOAT_P(loop_b, offsets.uv);
return len_v2v2(luv_a, luv_b);
}
case UV_SSIM_LENGTH_3D:
return len_v3v3(edge->v1->co, edge->v2->co);
case UV_SSIM_SIDES: {
BMEdge *e;
BMIter iter;
BM_ITER_ELEM (e, &iter, edge, BM_FACES_OF_EDGE) {
result += 1.0f;
}
break;
}
case UV_SSIM_PIN: {
if (BM_ELEM_CD_GET_BOOL(loop_a, offsets.pin)) {
result += 1.0f;
}
if (BM_ELEM_CD_GET_BOOL(loop_b, offsets.pin)) {
result += 1.0f;
}
break;
}
default:
BLI_assert_unreachable();
return false;
}
return result;
}
static float get_uv_face_needle(const eUVSelectSimilar type,
BMFace *face,
int ob_index,
const float ob_m3[3][3],
const BMUVOffsets &offsets)
{
BLI_assert(offsets.pin >= 0);
BLI_assert(offsets.uv >= 0);
float result = 0.0f;
switch (type) {
case UV_SSIM_AREA_UV:
return BM_face_calc_area_uv(face, offsets.uv);
case UV_SSIM_AREA_3D:
return BM_face_calc_area_with_mat3(face, ob_m3);
case UV_SSIM_SIDES:
return face->len;
case UV_SSIM_OBJECT:
return ob_index;
case UV_SSIM_PIN: {
BMLoop *l;
BMIter liter;
BM_ITER_ELEM (l, &liter, face, BM_LOOPS_OF_FACE) {
if (BM_ELEM_CD_GET_BOOL(l, offsets.pin)) {
result += 1.0f;
}
}
break;
}
case UV_SSIM_MATERIAL:
return face->mat_nr;
case UV_SSIM_WINDING:
return signum_i(BM_face_calc_area_uv_signed(face, offsets.uv));
default:
BLI_assert_unreachable();
return false;
}
return result;
}
static float get_uv_island_needle(const eUVSelectSimilar type,
const FaceIsland *island,
const float ob_m3[3][3],
const BMUVOffsets &offsets)
{
BLI_assert(offsets.uv >= 0);
float result = 0.0f;
switch (type) {
case UV_SSIM_AREA_UV:
for (int i = 0; i < island->faces_len; i++) {
result += BM_face_calc_area_uv(island->faces[i], offsets.uv);
}
break;
case UV_SSIM_AREA_3D:
for (int i = 0; i < island->faces_len; i++) {
result += BM_face_calc_area_with_mat3(island->faces[i], ob_m3);
}
break;
case UV_SSIM_FACE:
return island->faces_len;
default:
BLI_assert_unreachable();
return false;
}
return result;
}
static wmOperatorStatus uv_select_similar_vert_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
ToolSettings *ts = CTX_data_tool_settings(C);
const eUVSelectSimilar type = eUVSelectSimilar(RNA_enum_get(op->ptr, "type"));
const float threshold = RNA_float_get(op->ptr, "threshold");
const eSimilarCmp compare = eSimilarCmp(RNA_enum_get(op->ptr, "compare"));
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs(
scene, view_layer, nullptr);
int max_verts_selected_all = 0;
for (Object *ob : objects) {
BMesh *bm = BKE_editmesh_from_object(ob)->bm;
BMFace *face;
BMIter iter;
BM_ITER_MESH (face, &iter, bm, BM_FACES_OF_MESH) {
if (!uvedit_face_visible_test(scene, face)) {
continue;
}
max_verts_selected_all += face->len;
}
/* TODO: Get a tighter bounds */
}
int tree_index = 0;
KDTree_1d *tree_1d = BLI_kdtree_1d_new(max_verts_selected_all);
for (Object *ob : objects) {
BMesh *bm = BKE_editmesh_from_object(ob)->bm;
if (bm->totvertsel == 0) {
continue;
}
const BMUVOffsets offsets = BM_uv_map_offsets_get(bm);
float ob_m3[3][3];
copy_m3_m4(ob_m3, ob->object_to_world().ptr());
BMFace *face;
BMIter iter;
BM_ITER_MESH (face, &iter, bm, BM_FACES_OF_MESH) {
if (!uvedit_face_visible_test(scene, face)) {
continue;
}
BMLoop *l;
BMIter liter;
BM_ITER_ELEM (l, &liter, face, BM_LOOPS_OF_FACE) {
if (!uvedit_uv_select_test(scene, bm, l, offsets)) {
continue;
}
float needle = get_uv_vert_needle(type, l->v, ob_m3, l, offsets);
BLI_kdtree_1d_insert(tree_1d, tree_index++, &needle);
}
}
}
if (tree_1d != nullptr) {
BLI_kdtree_1d_deduplicate(tree_1d);
BLI_kdtree_1d_balance(tree_1d);
}
for (Object *ob : objects) {
BMesh *bm = BKE_editmesh_from_object(ob)->bm;
if (bm->totvertsel == 0) {
/* No selection means no visible UV's unless sync-select is enabled. */
if (!(ts->uv_flag & UV_FLAG_SELECT_SYNC)) {
continue;
}
}
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];
copy_m3_m4(ob_m3, ob->object_to_world().ptr());
BMFace *face;
BMIter iter;
BM_ITER_MESH (face, &iter, bm, BM_FACES_OF_MESH) {
if (!uvedit_face_visible_test(scene, face)) {
continue;
}
BMLoop *l;
BMIter liter;
BM_ITER_ELEM (l, &liter, face, BM_LOOPS_OF_FACE) {
if (uvedit_uv_select_test(scene, bm, l, offsets)) {
continue; /* Already selected. */
}
const float needle = get_uv_vert_needle(type, l->v, ob_m3, l, offsets);
bool select = ED_select_similar_compare_float_tree(tree_1d, needle, threshold, compare);
if (select) {
uvedit_uv_select_set(scene, bm, l, select);
changed = true;
}
}
}
if (changed) {
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
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);
}
uv_select_tag_update_for_object(depsgraph, ts, ob);
}
}
BLI_kdtree_1d_free(tree_1d);
return OPERATOR_FINISHED;
}
static wmOperatorStatus uv_select_similar_edge_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
ToolSettings *ts = CTX_data_tool_settings(C);
const eUVSelectSimilar type = eUVSelectSimilar(RNA_enum_get(op->ptr, "type"));
const float threshold = RNA_float_get(op->ptr, "threshold");
const eSimilarCmp compare = eSimilarCmp(RNA_enum_get(op->ptr, "compare"));
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs(
scene, view_layer, nullptr);
int max_edges_selected_all = 0;
for (Object *ob : objects) {
BMesh *bm = BKE_editmesh_from_object(ob)->bm;
BMFace *face;
BMIter iter;
BM_ITER_MESH (face, &iter, bm, BM_FACES_OF_MESH) {
if (!uvedit_face_visible_test(scene, face)) {
continue;
}
max_edges_selected_all += face->len;
}
/* TODO: Get a tighter bounds. */
}
int tree_index = 0;
KDTree_1d *tree_1d = BLI_kdtree_1d_new(max_edges_selected_all);
for (Object *ob : objects) {
BMesh *bm = BKE_editmesh_from_object(ob)->bm;
if (bm->totvertsel == 0) {
continue;
}
const BMUVOffsets offsets = BM_uv_map_offsets_get(bm);
float ob_m3[3][3];
copy_m3_m4(ob_m3, ob->object_to_world().ptr());
BMFace *face;
BMIter iter;
BM_ITER_MESH (face, &iter, bm, BM_FACES_OF_MESH) {
if (!uvedit_face_visible_test(scene, face)) {
continue;
}
BMLoop *l;
BMIter liter;
BM_ITER_ELEM (l, &liter, face, BM_LOOPS_OF_FACE) {
if (!uvedit_edge_select_test(scene, bm, l, offsets)) {
continue;
}
float needle = get_uv_edge_needle(type, l->e, ob_m3, l, l->next, offsets);
if (tree_1d) {
BLI_kdtree_1d_insert(tree_1d, tree_index++, &needle);
}
}
}
}
if (tree_1d != nullptr) {
BLI_kdtree_1d_deduplicate(tree_1d);
BLI_kdtree_1d_balance(tree_1d);
}
for (Object *ob : objects) {
BMesh *bm = BKE_editmesh_from_object(ob)->bm;
if (bm->totvertsel == 0) {
/* No selection means no visible UV's unless sync-select is enabled. */
if (!(ts->uv_flag & UV_FLAG_SELECT_SYNC)) {
continue;
}
}
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];
copy_m3_m4(ob_m3, ob->object_to_world().ptr());
BMFace *face;
BMIter iter;
BM_ITER_MESH (face, &iter, bm, BM_FACES_OF_MESH) {
if (!uvedit_face_visible_test(scene, face)) {
continue;
}
BMLoop *l;
BMIter liter;
BM_ITER_ELEM (l, &liter, face, BM_LOOPS_OF_FACE) {
if (uvedit_edge_select_test(scene, bm, l, offsets)) {
continue; /* Already selected. */
}
float needle = get_uv_edge_needle(type, l->e, ob_m3, l, l->next, offsets);
bool select = ED_select_similar_compare_float_tree(tree_1d, needle, threshold, compare);
if (select) {
uvedit_edge_select_set(scene, bm, l, select);
changed = true;
}
}
}
if (changed) {
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
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);
}
uv_select_tag_update_for_object(depsgraph, ts, ob);
}
}
BLI_kdtree_1d_free(tree_1d);
return OPERATOR_FINISHED;
}
static wmOperatorStatus uv_select_similar_face_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
ToolSettings *ts = CTX_data_tool_settings(C);
const eUVSelectSimilar type = eUVSelectSimilar(RNA_enum_get(op->ptr, "type"));
const float threshold = RNA_float_get(op->ptr, "threshold");
const eSimilarCmp compare = eSimilarCmp(RNA_enum_get(op->ptr, "compare"));
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs(
scene, view_layer, nullptr);
int max_faces_selected_all = 0;
for (Object *ob : objects) {
BMesh *bm = BKE_editmesh_from_object(ob)->bm;
max_faces_selected_all += bm->totfacesel;
/* TODO: Get a tighter bounds */
}
int tree_index = 0;
KDTree_1d *tree_1d = BLI_kdtree_1d_new(max_faces_selected_all);
for (const int ob_index : objects.index_range()) {
Object *ob = objects[ob_index];
BMesh *bm = BKE_editmesh_from_object(ob)->bm;
if (bm->totvertsel == 0) {
continue;
}
float ob_m3[3][3];
copy_m3_m4(ob_m3, ob->object_to_world().ptr());
const BMUVOffsets offsets = BM_uv_map_offsets_get(bm);
BMFace *face;
BMIter iter;
BM_ITER_MESH (face, &iter, bm, BM_FACES_OF_MESH) {
if (!uvedit_face_visible_test(scene, face)) {
continue;
}
if (!uvedit_face_select_test(scene, bm, face)) {
continue;
}
float needle = get_uv_face_needle(type, face, ob_index, ob_m3, offsets);
if (tree_1d) {
BLI_kdtree_1d_insert(tree_1d, tree_index++, &needle);
}
}
}
if (tree_1d != nullptr) {
BLI_kdtree_1d_deduplicate(tree_1d);
BLI_kdtree_1d_balance(tree_1d);
}
for (const int ob_index : objects.index_range()) {
Object *ob = objects[ob_index];
BMesh *bm = BKE_editmesh_from_object(ob)->bm;
if (bm->totvertsel == 0) {
/* No selection means no visible UV's unless sync-select is enabled. */
if (!(ts->uv_flag & UV_FLAG_SELECT_SYNC)) {
continue;
}
}
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];
copy_m3_m4(ob_m3, ob->object_to_world().ptr());
BMFace *face;
BMIter iter;
BM_ITER_MESH (face, &iter, bm, BM_FACES_OF_MESH) {
if (!uvedit_face_visible_test(scene, face)) {
continue;
}
if (uvedit_face_select_test(scene, bm, face)) {
continue;
}
float needle = get_uv_face_needle(type, face, ob_index, ob_m3, offsets);
bool select = ED_select_similar_compare_float_tree(tree_1d, needle, threshold, compare);
if (select) {
uvedit_face_select_set(scene, bm, face, select);
changed = true;
}
}
if (changed) {
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
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);
}
uv_select_tag_update_for_object(depsgraph, ts, ob);
}
}
BLI_kdtree_1d_free(tree_1d);
return OPERATOR_FINISHED;
}
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, bm, island->faces[0]);
}
static wmOperatorStatus uv_select_similar_island_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
ToolSettings *ts = CTX_data_tool_settings(C);
const eUVSelectSimilar type = eUVSelectSimilar(RNA_enum_get(op->ptr, "type"));
const float threshold = RNA_float_get(op->ptr, "threshold");
const eSimilarCmp compare = eSimilarCmp(RNA_enum_get(op->ptr, "compare"));
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs(
scene, view_layer, nullptr);
ListBase *island_list_ptr = MEM_calloc_arrayN<ListBase>(objects.size(), __func__);
int island_list_len = 0;
const bool face_selected = !(scene->toolsettings->uv_flag & UV_FLAG_SELECT_SYNC);
for (const int ob_index : objects.index_range()) {
Object *obedit = objects[ob_index];
BMesh *bm = BKE_editmesh_from_object(obedit)->bm;
const BMUVOffsets offsets = BM_uv_map_offsets_get(bm);
float aspect_y = 1.0f; /* Placeholder value, aspect doesn't change connectivity. */
island_list_len += bm_mesh_calc_uv_islands(
scene, bm, &island_list_ptr[ob_index], face_selected, false, false, aspect_y, offsets);
}
FaceIsland **island_array = static_cast<FaceIsland **>(
MEM_callocN(sizeof(*island_array) * island_list_len, __func__));
int tree_index = 0;
KDTree_1d *tree_1d = BLI_kdtree_1d_new(island_list_len);
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, bm, island)) {
continue;
}
float needle = get_uv_island_needle(type, island, ob_m3, island->offsets);
if (tree_1d) {
BLI_kdtree_1d_insert(tree_1d, tree_index++, &needle);
}
}
}
if (tree_1d != nullptr) {
BLI_kdtree_1d_deduplicate(tree_1d);
BLI_kdtree_1d_balance(tree_1d);
}
int tot_island_index = 0;
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());
bool changed = false;
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, bm, island)) {
continue;
}
float needle = get_uv_island_needle(type, island, ob_m3, island->offsets);
bool select = ED_select_similar_compare_float_tree(tree_1d, needle, threshold, compare);
if (!select) {
continue;
}
for (int j = 0; j < island->faces_len; j++) {
uvedit_face_select_set(scene, bm, island->faces[j], select);
}
changed = true;
}
if (changed) {
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
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);
}
uv_select_tag_update_for_object(depsgraph, ts, obedit);
}
}
BLI_assert(tot_island_index == island_list_len);
for (int i = 0; i < island_list_len; i++) {
MEM_SAFE_FREE(island_array[i]->faces);
MEM_SAFE_FREE(island_array[i]);
}
MEM_SAFE_FREE(island_array);
MEM_SAFE_FREE(island_list_ptr);
BLI_kdtree_1d_free(tree_1d);
return OPERATOR_FINISHED;
}
/* Select similar UV faces/edges/verts based on current selection. */
static wmOperatorStatus uv_select_similar_exec(bContext *C, wmOperator *op)
{
ToolSettings *ts = CTX_data_tool_settings(C);
PropertyRNA *prop = RNA_struct_find_property(op->ptr, "threshold");
const bool use_select_linked = ED_uvedit_select_island_check(ts);
if (!RNA_property_is_set(op->ptr, prop)) {
RNA_property_float_set(op->ptr, prop, ts->select_thresh);
}
else {
ts->select_thresh = RNA_property_float_get(op->ptr, prop);
}
const int selectmode = (ts->uv_flag & UV_FLAG_SELECT_SYNC) ? ts->selectmode : ts->uv_selectmode;
if (use_select_linked) {
return uv_select_similar_island_exec(C, op);
}
if (selectmode & UV_SELECT_FACE) {
return uv_select_similar_face_exec(C, op);
}
if (selectmode & UV_SELECT_EDGE) {
return uv_select_similar_edge_exec(C, op);
}
/* #UV_SELECT_VERT */
return uv_select_similar_vert_exec(C, op);
}
static EnumPropertyItem uv_select_similar_type_items[] = {
{UV_SSIM_PIN, "PIN", 0, "Pinned", ""},
{UV_SSIM_LENGTH_UV, "LENGTH", 0, "Length", "Edge length in UV space"},
{UV_SSIM_LENGTH_3D, "LENGTH_3D", 0, "Length 3D", "Length of edge in 3D space"},
{UV_SSIM_AREA_UV, "AREA", 0, "Area", "Face area in UV space"},
{UV_SSIM_AREA_3D, "AREA_3D", 0, "Area 3D", "Area of face in 3D space"},
{UV_SSIM_MATERIAL, "MATERIAL", 0, "Material", ""},
{UV_SSIM_OBJECT, "OBJECT", 0, "Object", ""},
{UV_SSIM_SIDES, "SIDES", 0, "Polygon Sides", ""},
{UV_SSIM_WINDING,
"WINDING",
0,
"Winding",
"Face direction defined by (clockwise or anti-clockwise winding (facing up or facing down)"},
{UV_SSIM_FACE, "FACE", 0, "Amount of Faces in Island", ""},
{0}};
static EnumPropertyItem prop_similar_compare_types[] = {{SIM_CMP_EQ, "EQUAL", 0, "Equal", ""},
{SIM_CMP_GT, "GREATER", 0, "Greater", ""},
{SIM_CMP_LT, "LESS", 0, "Less", ""},
{0}};
static const EnumPropertyItem *uv_select_similar_type_itemf(bContext *C,
PointerRNA * /*ptr*/,
PropertyRNA * /*prop*/,
bool *r_free)
{
EnumPropertyItem *item = nullptr;
int totitem = 0;
const ToolSettings *ts = CTX_data_tool_settings(C);
if (ts) {
const bool use_select_linked = ED_uvedit_select_island_check(ts);
const int selectmode = (ts->uv_flag & UV_FLAG_SELECT_SYNC) ? ts->selectmode :
ts->uv_selectmode;
/* TODO: co-exist with selection modes. */
if (use_select_linked) {
RNA_enum_items_add_value(&item, &totitem, uv_select_similar_type_items, UV_SSIM_AREA_UV);
RNA_enum_items_add_value(&item, &totitem, uv_select_similar_type_items, UV_SSIM_AREA_3D);
RNA_enum_items_add_value(&item, &totitem, uv_select_similar_type_items, UV_SSIM_FACE);
}
else if (selectmode & UV_SELECT_FACE) {
RNA_enum_items_add_value(&item, &totitem, uv_select_similar_type_items, UV_SSIM_AREA_UV);
RNA_enum_items_add_value(&item, &totitem, uv_select_similar_type_items, UV_SSIM_AREA_3D);
RNA_enum_items_add_value(&item, &totitem, uv_select_similar_type_items, UV_SSIM_MATERIAL);
RNA_enum_items_add_value(&item, &totitem, uv_select_similar_type_items, UV_SSIM_OBJECT);
RNA_enum_items_add_value(&item, &totitem, uv_select_similar_type_items, UV_SSIM_SIDES);
RNA_enum_items_add_value(&item, &totitem, uv_select_similar_type_items, UV_SSIM_WINDING);
}
else if (selectmode & UV_SELECT_EDGE) {
RNA_enum_items_add_value(&item, &totitem, uv_select_similar_type_items, UV_SSIM_LENGTH_UV);
RNA_enum_items_add_value(&item, &totitem, uv_select_similar_type_items, UV_SSIM_LENGTH_3D);
RNA_enum_items_add_value(&item, &totitem, uv_select_similar_type_items, UV_SSIM_PIN);
}
else {
/* #UV_SELECT_VERT */
RNA_enum_items_add_value(&item, &totitem, uv_select_similar_type_items, UV_SSIM_PIN);
}
}
else {
RNA_enum_items_add_value(&item, &totitem, uv_select_similar_type_items, UV_SSIM_PIN);
}
RNA_enum_item_end(&item, &totitem);
*r_free = true;
return item;
}
void UV_OT_select_similar(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Select Similar";
ot->description = "Select similar UVs by property types";
ot->idname = "UV_OT_select_similar";
/* API callbacks. */
ot->invoke = WM_menu_invoke;
ot->exec = uv_select_similar_exec;
ot->poll = ED_operator_uvedit_space_image;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
PropertyRNA *prop = ot->prop = RNA_def_enum(
ot->srna, "type", uv_select_similar_type_items, SIMVERT_NORMAL, "Type", "");
RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_MESH);
RNA_def_enum_funcs(prop, uv_select_similar_type_itemf);
RNA_def_enum(ot->srna, "compare", prop_similar_compare_types, SIM_CMP_EQ, "Compare", "");
RNA_def_float(ot->srna, "threshold", 0.0f, 0.0f, 1.0f, "Threshold", "", 0.0f, 1.0f);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Selected Elements as Arrays (Vertex, Edge & Faces)
*
* These functions return single elements per connected vertex/edge.
* So an edge that has two connected edge loops only assigns one loop in the array.
* \{ */
BMFace **ED_uvedit_selected_faces(const Scene *scene, BMesh *bm, int len_max, int *r_faces_len)
{
CLAMP_MAX(len_max, bm->totface);
int faces_len = 0;
BMFace **faces = MEM_malloc_arrayN<BMFace *>(len_max, __func__);
BMIter iter;
BMFace *f;
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
if (uvedit_face_visible_test(scene, f)) {
if (uvedit_face_select_test(scene, bm, f)) {
faces[faces_len++] = f;
if (faces_len == len_max) {
goto finally;
}
}
}
}
finally:
*r_faces_len = faces_len;
if (faces_len != len_max) {
faces = static_cast<BMFace **>(MEM_reallocN(faces, sizeof(*faces) * faces_len));
}
return faces;
}
BMLoop **ED_uvedit_selected_edges(const Scene *scene, BMesh *bm, int len_max, int *r_edges_len)
{
const BMUVOffsets offsets = BM_uv_map_offsets_get(bm);
BLI_assert(offsets.uv >= 0);
CLAMP_MAX(len_max, bm->totloop);
int edges_len = 0;
BMLoop **edges = MEM_malloc_arrayN<BMLoop *>(len_max, __func__);
BMIter iter;
BMFace *f;
/* Clear tag. */
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
BMIter liter;
BMLoop *l_iter;
BM_ITER_ELEM (l_iter, &liter, f, BM_LOOPS_OF_FACE) {
BM_elem_flag_disable(l_iter, BM_ELEM_TAG);
}
}
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
if (uvedit_face_visible_test(scene, f)) {
BMIter liter;
BMLoop *l_iter;
BM_ITER_ELEM (l_iter, &liter, f, BM_LOOPS_OF_FACE) {
if (!BM_elem_flag_test(l_iter, BM_ELEM_TAG)) {
if (uvedit_edge_select_test(scene, bm, l_iter, offsets)) {
BM_elem_flag_enable(l_iter, BM_ELEM_TAG);
edges[edges_len++] = l_iter;
if (edges_len == len_max) {
goto finally;
}
/* Tag other connected loops so we don't consider them separate edges. */
if (l_iter != l_iter->radial_next) {
BMLoop *l_radial_iter = l_iter->radial_next;
do {
if (BM_loop_uv_share_edge_check(l_iter, l_radial_iter, offsets.uv)) {
BM_elem_flag_enable(l_radial_iter, BM_ELEM_TAG);
}
} while ((l_radial_iter = l_radial_iter->radial_next) != l_iter);
}
}
}
}
}
}
finally:
*r_edges_len = edges_len;
if (edges_len != len_max) {
edges = static_cast<BMLoop **>(MEM_reallocN(edges, sizeof(*edges) * edges_len));
}
return edges;
}
BMLoop **ED_uvedit_selected_verts(const Scene *scene, BMesh *bm, int len_max, int *r_verts_len)
{
const ToolSettings *ts = scene->toolsettings;
const BMUVOffsets offsets = BM_uv_map_offsets_get(bm);
BLI_assert(offsets.uv >= 0);
CLAMP_MAX(len_max, bm->totloop);
int verts_len = 0;
BMLoop **verts = MEM_malloc_arrayN<BMLoop *>(len_max, __func__);
BMIter iter;
BMFace *f;
/* Clear tag. */
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
BMIter liter;
BMLoop *l_iter;
BM_ITER_ELEM (l_iter, &liter, f, BM_LOOPS_OF_FACE) {
BM_elem_flag_disable(l_iter, BM_ELEM_TAG);
}
}
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
if (uvedit_face_visible_test(scene, f)) {
BMIter liter;
BMLoop *l_iter;
BM_ITER_ELEM (l_iter, &liter, f, BM_LOOPS_OF_FACE) {
if (!BM_elem_flag_test(l_iter, BM_ELEM_TAG)) {
if (uvedit_vert_select_get_no_sync(ts, bm, l_iter)) {
BM_elem_flag_enable(l_iter->v, BM_ELEM_TAG);
verts[verts_len++] = l_iter;
if (verts_len == len_max) {
goto finally;
}
/* Tag other connected loops so we don't consider them separate vertices. */
BMIter liter_disk;
BMLoop *l_disk_iter;
BM_ITER_ELEM (l_disk_iter, &liter_disk, l_iter->v, BM_LOOPS_OF_VERT) {
if (BM_loop_uv_share_vert_check(l_iter, l_disk_iter, offsets.uv)) {
BM_elem_flag_enable(l_disk_iter, BM_ELEM_TAG);
}
}
}
}
}
}
}
finally:
*r_verts_len = verts_len;
if (verts_len != len_max) {
verts = static_cast<BMLoop **>(MEM_reallocN(verts, sizeof(*verts) * verts_len));
}
return verts;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select Mode UV Vert/Edge/Face/Island Operator
* \{ */
void ED_uvedit_selectmode_clean(const Scene *scene, Object *obedit)
{
const ToolSettings *ts = scene->toolsettings;
BLI_assert((ts->uv_flag & UV_FLAG_SELECT_SYNC) == 0);
BMesh *bm = BKE_editmesh_from_object(obedit)->bm;
char sticky = ts->uv_sticky;
uvedit_select_prepare_custom_data(scene, bm);
const BMUVOffsets offsets = BM_uv_map_offsets_get(bm);
BMFace *efa;
BMLoop *l;
BMIter iter, liter;
if (ts->uv_selectmode == UV_SELECT_VERT) {
/* Vertex mode. */
if (sticky != UV_STICKY_DISABLE) {
bm_loop_tags_clear(bm);
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
if (!uvedit_face_visible_test(scene, efa)) {
continue;
}
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
if (uvedit_uv_select_test(scene, bm, l, offsets)) {
BM_elem_flag_enable(l, BM_ELEM_TAG);
}
}
}
uv_select_flush_from_tag_loop(scene, obedit, true);
}
}
else if (ts->uv_selectmode == UV_SELECT_EDGE) {
/* Edge mode. */
if (sticky != UV_STICKY_DISABLE) {
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
if (!uvedit_face_visible_test(scene, efa)) {
continue;
}
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
if (uvedit_edge_select_test(scene, bm, l, offsets)) {
uvedit_edge_select_set_noflush(scene, bm, l, true, sticky, offsets);
}
}
}
}
uv_select_flush_from_loop_edge_flag(scene, bm);
}
else if (ts->uv_selectmode == UV_SELECT_FACE) {
/* Face mode. */
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, bm, efa)) {
BM_elem_flag_enable(efa, BM_ELEM_TAG);
}
uvedit_face_select_set(scene, bm, efa, false);
}
}
uv_select_flush_from_tag_face(scene, obedit, true);
}
ED_uvedit_selectmode_flush(scene, bm);
}
void ED_uvedit_selectmode_clean_multi(bContext *C)
{
Scene *scene = CTX_data_scene(C);
ToolSettings *ts = scene->toolsettings;
ViewLayer *view_layer = CTX_data_view_layer(C);
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs(
scene, view_layer, nullptr);
for (Object *obedit : objects) {
ED_uvedit_selectmode_clean(scene, obedit);
uv_select_tag_update_for_object(depsgraph, ts, obedit);
}
}
void ED_uvedit_sticky_selectmode_update(bContext *C)
{
Scene *scene = CTX_data_scene(C);
ToolSettings *ts = scene->toolsettings;
if ((ts->uv_flag & UV_FLAG_SELECT_SYNC) == 0) {
return;
}
/* Only for edge/face select modes. */
if (ts->selectmode & SCE_SELECT_VERTEX) {
return;
}
ViewLayer *view_layer = CTX_data_view_layer(C);
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs(
scene, view_layer, nullptr);
for (Object *obedit : objects) {
uv_select_tag_update_for_object(depsgraph, ts, obedit);
}
}
static wmOperatorStatus uv_select_mode_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
ToolSettings *ts = scene->toolsettings;
const char new_uv_selectmode = RNA_enum_get(op->ptr, "type");
/* Early exit if no change in current selection mode */
if (new_uv_selectmode == ts->uv_selectmode) {
return OPERATOR_CANCELLED;
}
/* Set new UV select mode. */
ts->uv_selectmode = new_uv_selectmode;
/* Handle UV selection states according to new select mode and sticky mode. */
ED_uvedit_selectmode_clean_multi(C);
DEG_id_tag_update(&scene->id, ID_RECALC_SYNC_TO_EVAL | ID_RECALC_SELECT);
WM_main_add_notifier(NC_SCENE | ND_TOOLSETTINGS, nullptr);
return OPERATOR_FINISHED;
}
static wmOperatorStatus uv_select_mode_invoke(bContext *C,
wmOperator *op,
const wmEvent * /*event*/)
{
const ToolSettings *ts = CTX_data_tool_settings(C);
const SpaceImage *sima = CTX_wm_space_image(C);
/* Could be removed? - Already done in poll callback. */
if ((!sima) || (sima->mode != SI_MODE_UV)) {
return OPERATOR_CANCELLED;
}
/* Pass through when UV sync selection is enabled.
* Allow for mesh select-mode key-map. */
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
return OPERATOR_PASS_THROUGH;
}
return uv_select_mode_exec(C, op);
}
void UV_OT_select_mode(wmOperatorType *ot)
{
/* identifiers */
ot->name = "UV Select Mode";
ot->description = "Change UV selection mode";
ot->idname = "UV_OT_select_mode";
/* API callbacks. */
ot->invoke = uv_select_mode_invoke;
ot->exec = uv_select_mode_exec;
ot->poll = ED_operator_uvedit_space_image;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* RNA props */
PropertyRNA *prop;
ot->prop = prop = RNA_def_enum(
ot->srna, "type", rna_enum_mesh_select_mode_uv_items, 0, "Type", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
}
static wmOperatorStatus uv_custom_region_set_exec(bContext *C, wmOperator *op)
{
const Scene *scene = CTX_data_scene(C);
const ARegion *region = CTX_wm_region(C);
ToolSettings *ts = scene->toolsettings;
WM_operator_properties_border_to_rctf(op, &ts->uv_custom_region);
UI_view2d_region_to_view_rctf(&region->v2d, &ts->uv_custom_region, &ts->uv_custom_region);
ts->uv_flag |= UV_FLAG_CUSTOM_REGION;
return OPERATOR_FINISHED;
}
void UV_OT_custom_region_set(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Set User Region";
ot->description = "Set the boundaries of the user region";
ot->idname = "UV_OT_custom_region_set";
/* API callbacks. */
ot->invoke = WM_gesture_box_invoke;
ot->exec = uv_custom_region_set_exec;
ot->modal = WM_gesture_box_modal;
ot->poll = ED_operator_uvedit_space_image;
ot->cancel = WM_gesture_box_cancel;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
WM_operator_properties_gesture_box(ot);
}
/** \} */