WIP: UV Editor Edge Slide #110868

Closed
Melissa-Goon wants to merge 2 commits from Melissa-Goon/blender:gsoc2023-edge-slide into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
12 changed files with 678 additions and 3 deletions
Showing only changes of commit 4f2d0463a9 - Show all commits

View File

@ -6033,6 +6033,7 @@ def km_transform_modal_map(params):
("CONS_OFF", {"type": 'C', "value": 'PRESS'}, None),
("TRANSLATE", {"type": 'G', "value": 'PRESS'}, None),
("VERT_EDGE_SLIDE", {"type": 'G', "value": 'PRESS'}, None),
("UV_EDGE_SLIDE",{"type": 'G',"value": 'PRESS'}, None),
("ROTATE", {"type": 'R', "value": 'PRESS'}, None),
("TRACKBALL", {"type": 'R', "value": 'PRESS'}, None),
("RESIZE", {"type": 'S', "value": 'PRESS'}, None),

View File

@ -4223,6 +4223,7 @@ def km_transform_modal_map(_params):
("CONS_OFF", {"type": 'C', "value": 'PRESS'}, None),
("TRANSLATE", {"type": 'G', "value": 'PRESS'}, None),
("VERT_EDGE_SLIDE", {"type": 'G', "value": 'PRESS'}, None),
("UV_EDGE_SLIDE", {"type": 'G', "value": 'PRESS'}, None),
("ROTATE", {"type": 'R', "value": 'PRESS'}, None),
("TRACKBALL", {"type": 'R', "value": 'PRESS'}, None),
("RESIZE", {"type": 'S', "value": 'PRESS'}, None),

View File

@ -539,6 +539,10 @@ class IMAGE_MT_uvs_context_menu(Menu):
layout.operator_enum("uv.align", "axis") # W, 2/3/4.
layout.operator_context = 'INVOKE_DEFAULT'
layout.operator("transform.uv_edge_slide", text="UV Edge Slide")
layout.separator()
# Remove

View File

@ -66,6 +66,7 @@ enum eTfmMode {
TFM_EDGE_SLIDE,
TFM_VERT_SLIDE,
TFM_SEQ_SLIDE,
TFM_UV_EDGE_SLIDE,
TFM_BONE_ENVELOPE_DIST,
TFM_NORMAL_ROTATION,
TFM_GPENCIL_OPACITY,

View File

@ -71,6 +71,7 @@ set(SRC
transform_mode_edge_rotate_normal.cc
transform_mode_edge_seq_slide.cc
transform_mode_edge_slide.cc
transform_mode_edge_uv_slide.cc
transform_mode_gpopacity.cc
transform_mode_gpshrinkfatten.cc
transform_mode_maskshrinkfatten.cc

View File

@ -532,7 +532,8 @@ static void viewRedrawPost(bContext *C, TransInfo *t)
UVCALC_TRANSFORM_CORRECT;
if ((t->data_type == &TransConvertType_Mesh) &&
(t->settings->uvcalc_flag & uvcalc_correct_flag)) {
(t->settings->uvcalc_flag & uvcalc_correct_flag))
{
WM_event_add_notifier(C, NC_GEOM | ND_DATA, nullptr);
}
@ -637,6 +638,7 @@ static bool transform_modal_item_poll(const wmOperator *op, int value)
case TFM_MODAL_ROTATE:
case TFM_MODAL_RESIZE:
case TFM_MODAL_VERT_EDGE_SLIDE:
case TFM_MODAL_UV_EDGE_SLIDE:
case TFM_MODAL_TRACKBALL:
case TFM_MODAL_ROTATE_NORMALS: {
if (!transform_mode_is_changeable(t->mode)) {
@ -750,6 +752,7 @@ wmKeyMap *transform_modal_keymap(wmKeyConfig *keyconf)
{TFM_MODAL_NODE_ATTACH_OFF, "NODE_ATTACH_OFF", 0, "Node Attachment (Off)", ""},
{TFM_MODAL_TRANSLATE, "TRANSLATE", 0, "Move", ""},
{TFM_MODAL_VERT_EDGE_SLIDE, "VERT_EDGE_SLIDE", 0, "Vert/Edge Slide", ""},
{TFM_MODAL_UV_EDGE_SLIDE, "UV_EDGE_SLIDE", 0, "UV Edge Slide", ""},
{TFM_MODAL_ROTATE, "ROTATE", 0, "Rotate", ""},
{TFM_MODAL_TRACKBALL, "TRACKBALL", 0, "TrackBall", ""},
{TFM_MODAL_RESIZE, "RESIZE", 0, "Resize", ""},
@ -1087,6 +1090,71 @@ int transformEvent(TransInfo *t, const wmEvent *event)
handled = true;
break;
case TFM_MODAL_UV_EDGE_SLIDE:
/* only switch when... */
if (!transform_mode_is_changeable(t->mode)) {
break;
}
if ((event->val == TFM_MODAL_TRANSLATE && t->mode == TFM_TRANSLATION) ||
(event->val == TFM_MODAL_RESIZE && t->mode == TFM_RESIZE))
{
if (t->data_type == &TransConvertType_Tracking) {
restoreTransObjects(t);
t->flag ^= T_ALT_TRANSFORM;
t->redraw |= TREDRAW_HARD;
handled = true;
}
break;
}
if ((event->val == TFM_MODAL_ROTATE && t->mode == TFM_ROTATION) ||
(event->val == TFM_MODAL_TRACKBALL && t->mode == TFM_TRACKBALL) ||
(event->val == TFM_MODAL_ROTATE_NORMALS && t->mode == TFM_NORMAL_ROTATION) ||
(event->val == TFM_MODAL_UV_EDGE_SLIDE && ELEM(t->mode, TFM_UV_EDGE_SLIDE)))
{
break;
}
if (event->val == TFM_MODAL_ROTATE_NORMALS && t->data_type != &TransConvertType_Mesh) {
break;
}
restoreTransObjects(t);
resetTransModal(t);
resetTransRestrictions(t);
if (event->val == TFM_MODAL_TRANSLATE) {
transform_mode_init(t, nullptr, TFM_TRANSLATION);
}
else if (event->val == TFM_MODAL_ROTATE) {
transform_mode_init(t, nullptr, TFM_ROTATION);
}
else if (event->val == TFM_MODAL_TRACKBALL) {
transform_mode_init(t, nullptr, TFM_TRACKBALL);
}
else if (event->val == TFM_MODAL_ROTATE_NORMALS) {
transform_mode_init(t, nullptr, TFM_NORMAL_ROTATION);
}
else if (event->val == TFM_MODAL_RESIZE) {
/* Scale isn't normally very useful after extrude along normals, see #39756 */
if ((t->con.mode & CON_APPLY) && (t->orient[t->orient_curr].type == V3D_ORIENT_NORMAL)) {
stopConstraint(t);
}
transform_mode_init(t, nullptr, TFM_RESIZE);
}
else {
transform_mode_init(t, nullptr, TFM_UV_EDGE_SLIDE);
}
/* Need to reinitialize after mode change. */
initSnapping(t, nullptr);
applyMouseInput(t, &t->mouse, t->mval, t->values);
t->redraw |= TREDRAW_HARD;
handled = true;
break;
case TFM_MODAL_SNAP_INV_ON:
if (!(t->modifiers & MOD_SNAP_INVERT)) {
t->modifiers |= MOD_SNAP_INVERT;

View File

@ -280,6 +280,8 @@ enum {
TFM_MODAL_EDIT_SNAP_SOURCE_ON = 34,
TFM_MODAL_EDIT_SNAP_SOURCE_OFF = 35,
TFM_MODAL_UV_EDGE_SLIDE = 36,
};
/** \} */

View File

@ -1142,6 +1142,8 @@ static TransModeInfo *mode_info_get(TransInfo *t, const int mode)
return &TransMode_rotatenormal;
case TFM_GPENCIL_OPACITY:
return &TransMode_gpopacity;
case TFM_UV_EDGE_SLIDE:
return &TransMode_edgeslideuv;
}
return nullptr;
}

View File

@ -146,6 +146,9 @@ extern TransModeInfo TransMode_seqslide;
extern TransModeInfo TransMode_edgeslide;
void transform_mode_edge_slide_reproject_input(TransInfo *t);
/* transform_mode_edge_uv_slide.cc */
extern TransModeInfo TransMode_edgeslideuv;
/* `transform_mode_gpopacity.cc` */
extern TransModeInfo TransMode_gpopacity;

View File

@ -0,0 +1,565 @@
/* SPDX-FileCopyrightText: 2023 Blender Foundation
Melissa-Goon marked this conversation as resolved Outdated

2001-2002 NaN shouldn't be copyright holder, use:

/* SPDX-FileCopyrightText: 2023 Blender Foundation
 *
 * SPDX-License-Identifier: GPL-2.0-or-later */
`2001-2002 NaN` shouldn't be copyright holder, use: ``` /* SPDX-FileCopyrightText: 2023 Blender Foundation * * SPDX-License-Identifier: GPL-2.0-or-later */ ```
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edtransform
*/
#include <cstdlib>
#include "MEM_guardedalloc.h"
#include "BLI_math_vector_types.hh"
#include "BLI_string.h"
#include "BLI_utildefines_stack.h"
#include "BKE_context.h"
#include "BKE_editmesh.h"
#include "BKE_editmesh_bvh.h"
#include "BKE_layer.h"
#include "BKE_unit.h"
#include "GPU_immediate.h"
#include "GPU_matrix.h"
#include "GPU_state.h"
#include "ED_mesh.hh"
#include "ED_screen.hh"
#include "ED_uvedit.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "RNA_access.hh"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "UI_view2d.hh"
#include "BLT_translation.h"
#include "transform.hh"
#include "transform_constraints.hh"
#include "transform_convert.hh"
#include "transform_mode.hh"
#include "transform_snap.hh"
/* -------------------------------------------------------------------- */
/** \name Transform (Edge UV Slide)
* \{ */
struct EdgeSlideUV {
/** Loops associated with this UV which are being transformed. */
Melissa-Goon marked this conversation as resolved Outdated

Can be struct EdgeSlideUV { .. }; (this was a C-convention that changed since your project started and code moved to C++).

Also, add doxygen comments before each member, helps readability and clarifies the purpose.

  /** Loops associated with this UV which are being transformed. */
  blender::Vector<BMLoop *> loops;
  /** <unsure of what this comment should be...> */
  BMLoop *side_loop[2];
  /** A pair of target locations in UV space to slide betwen. */
  float sides[2][2];
  /** The original location (never changes), used to re-calculate the slide location. */
  float orig_uv[2];
Can be `struct EdgeSlideUV { .. };` (this was a C-convention that changed since your project started and code moved to C++). Also, add doxygen comments before each member, helps readability and clarifies the purpose. ``` /** Loops associated with this UV which are being transformed. */ blender::Vector<BMLoop *> loops; /** <unsure of what this comment should be...> */ BMLoop *side_loop[2]; /** A pair of target locations in UV space to slide betwen. */ float sides[2][2]; /** The original location (never changes), used to re-calculate the slide location. */ float orig_uv[2]; ```
blender::Vector<BMLoop *> loops;
/** A pair of loops whose UVs are target locations in UV space to slide betwen. */
BMLoop *slide_loop[2];
Melissa-Goon marked this conversation as resolved Outdated

sides is not a very meaningful name, without checking the type is could be an index or represent loops themselves.

Could be slide_uv for e.g.

Also, suggest to use float2 sides[2] which means you can assign them more easily.

    slide_uv[i].sides[0][0] = side0[0];
    slide_uv[i].sides[0][1] = side0[1];

Can then be replaced with:

    slide_uv[i].sides[0] = side0;
`sides` is not a very meaningful name, without checking the type is could be an index or represent loops themselves. Could be `slide_uv` for e.g. Also, suggest to use `float2 sides[2]` which means you can assign them more easily. ``` slide_uv[i].sides[0][0] = side0[0]; slide_uv[i].sides[0][1] = side0[1]; ``` Can then be replaced with: ``` slide_uv[i].sides[0] = side0; ```
/** A pair of target locations in UV space to slide betwen. */
blender::float2 slide_uv_target[2];
/** The original location (never changes), used to re-calculate the slide location. */
blender::float2 orig_uv;
};
static void freeEdgeSlideUVs(TransInfo * /*t*/,
TransDataContainer * /*tc*/,
TransCustomData *custom_data)
{
blender::Vector<EdgeSlideUV> *esuvs = static_cast<blender::Vector<EdgeSlideUV> *>(
Melissa-Goon marked this conversation as resolved Outdated

Note then this is expected to be nullptr (also NULL -> nullptr).

Note then this is expected to be `nullptr` (also NULL -> nullptr).
custom_data->data);
if (esuvs == nullptr) {
return;
}
delete esuvs;
custom_data->data = nullptr;
}
static void calc_side_loops_from_single_terminating_uv(BMEdge *edge,
BMLoop *orig_loop,
Melissa-Goon marked this conversation as resolved Outdated

Should be static.

Should be `static`.
EdgeSlideUV &esuv,
BMUVOffsets offsets)
{
BMEdge *e = (edge == nullptr) ? orig_loop->e : edge;
BMLoop *loop_v, *loop_side;
BMIter lv_iter, side_iter;
BMVert *v_orig = orig_loop->v;
int i = 0;
Melissa-Goon marked this conversation as resolved Outdated

Explain why this is needed. It also seems likely that using the first 2 loops would give arbitrary results (as the first 2 may be different depending on the internal ordering of data), this is is not a problem explain why in a comment.

Explain why this is needed. It also seems likely that using the first 2 loops would give arbitrary results (as the first 2 may be different depending on the internal ordering of data), this is is not a problem explain why in a comment.
/* Ignore non-manifold edges. */
if (!BM_edge_is_manifold(e)) {
Melissa-Goon marked this conversation as resolved Outdated

From reading the body of this code it looks like there is some chance i enters the for loop as 1 then gets set to 2 or 3.. which would cause out of bounds access for esuv.side_loop[i].

If the logic guarantees this is never the case, add assertions this never happens and note why.

From reading the body of this code it looks like there is some chance `i` enters the for loop as `1` then gets set to 2 or 3.. which would cause out of bounds access for `esuv.side_loop[i]`. If the logic guarantees this is never the case, add assertions this never happens and note why.
return;
}
Melissa-Goon marked this conversation as resolved Outdated

Suggestion: use a for loop to access next/prev members, avoids duplicating code.

for (int dir = 0; dir < 2; dir++) {
  BMLoop *l_other = dir ? l->next : l->prev;
  /* ... snip ... */
}

This avoids code duplication.

Suggestion: use a for loop to access `next/prev` members, avoids duplicating code. ``` for (int dir = 0; dir < 2; dir++) { BMLoop *l_other = dir ? l->next : l->prev; /* ... snip ... */ } ``` This avoids code duplication.
/*Clear tags*/
BM_ITER_ELEM (loop_v, &lv_iter, v_orig, BM_LOOPS_OF_VERT) {
BM_elem_flag_disable(loop_v->next, BM_ELEM_TAG_ALT);
BM_elem_flag_disable(loop_v->prev, BM_ELEM_TAG_ALT);
}
BM_ITER_ELEM (loop_v, &lv_iter, v_orig, BM_LOOPS_OF_VERT) {
if (BM_loop_uv_share_vert_check(loop_v, orig_loop, offsets.uv) &&
(loop_v->f == e->l->f || loop_v->f == e->l->radial_next->f))
{
for (int dir = 0; dir < 2; dir++) {
BMLoop *l_other = dir ? loop_v->next : loop_v->prev;
if (!BM_ELEM_CD_GET_BOOL(l_other, offsets.select_vert) &&
!BM_elem_flag_test(l_other, BM_ELEM_TAG_ALT) && l_other->v != e->v1 &&
l_other->v != e->v2)
{
/*We should never find more than 2 loops that share faces with the edge. */
BLI_assert(i < 2);
esuv.slide_loop[i] = l_other;
i++;
/*Tag all loops of the side so the same side isn't picked twice. */
BM_ITER_ELEM (loop_side, &side_iter, l_other->v, BM_LOOPS_OF_VERT) {
if (BM_loop_uv_share_vert_check(loop_side, l_other, offsets.uv)) {
BM_elem_flag_enable(loop_side, BM_ELEM_TAG_ALT);
}
}
}
}
}
}
Melissa-Goon marked this conversation as resolved Outdated

Should be static.

Should be `static`.
}
static void calc_side_loops_from_pair(BMLoop *orig_loop, EdgeSlideUV &esuv, BMUVOffsets offsets)
{
BMLoop *loop_v, *loop_side;
BMIter lv_iter, side_iter;
BMVert *v_orig = orig_loop->v;
int i = 0;
/*Clear tags*/
BM_ITER_ELEM (loop_v, &lv_iter, v_orig, BM_LOOPS_OF_VERT) {
BM_elem_flag_disable(loop_v->next, BM_ELEM_TAG_ALT);
BM_elem_flag_disable(loop_v->prev, BM_ELEM_TAG_ALT);
}
BM_ITER_ELEM (loop_v, &lv_iter, v_orig, BM_LOOPS_OF_VERT) {
/*Pick the first two unselected edges encountered to slide along. */
Melissa-Goon marked this conversation as resolved Outdated

Suggestion: use a for loop to access next/prev members, avoids duplicating code. (see other comment).

Suggestion: use a for loop to access `next/prev` members, avoids duplicating code. (see other comment).
if (i == 2) {
break;
}
if (BM_loop_uv_share_vert_check(loop_v, orig_loop, offsets.uv)) {
for (int dir = 0; dir < 2; dir++) {
BMLoop *l_other = dir ? loop_v->next : loop_v->prev;
if (!BM_ELEM_CD_GET_BOOL(l_other, offsets.select_vert) &&
!BM_elem_flag_test(l_other, BM_ELEM_TAG_ALT))
{
esuv.slide_loop[i] = l_other;
i++;
/*Tag all loops of the side so the same side isn't picked twice. */
BM_ITER_ELEM (loop_side, &side_iter, l_other->v, BM_LOOPS_OF_VERT) {
if (BM_loop_uv_share_vert_check(loop_side, l_other, offsets.uv)) {
BM_elem_flag_enable(loop_side, BM_ELEM_TAG_ALT);
Melissa-Goon marked this conversation as resolved Outdated

Should be static.

Should be `static`.
}
}
}
}
}
}
}
static void flip_and_set_sides(blender::Vector<EdgeSlideUV> &slide_uv, BMUVOffsets offsets)
{
BMLoop *l_iter;
BMIter lv_iter;
BMVert *v_orig;
for (int i = 0; i < slide_uv.size(); i++) {
/*Initialize as selected uv. */
float *uv = BM_ELEM_CD_GET_FLOAT_P(slide_uv[i].loops[0], offsets.uv);
slide_uv[i].slide_uv_target[0] = uv;
slide_uv[i].slide_uv_target[1] = uv;
slide_uv[i].orig_uv = uv;
if (i != 0) {
/* Side 0 is tagged with BM_ELEM_TAG and side 1 is tagged with BM_ELEM_TAG_ALT. */
bool flip = false;
if (slide_uv[i].slide_loop[0]->v != slide_uv[i].loops[0]->v) {
v_orig = slide_uv[i].slide_loop[0]->v;
BM_ITER_ELEM (l_iter, &lv_iter, v_orig, BM_LOOPS_OF_VERT) {
if (BM_loop_uv_share_vert_check(l_iter, slide_uv[i].slide_loop[0], offsets.uv)) {
if (l_iter->next->v == slide_uv[i].loops[0]->v ||
l_iter->prev->v == slide_uv[i].loops[0]->v)
{
/*Only look at faces adjacent to the edges. */
if (BM_elem_flag_test(l_iter->f, BM_ELEM_TAG_ALT)) {
/*Flip if wrong tag found. */
flip = true;
}
}
}
}
}
if (slide_uv[i].slide_loop[1]->v != slide_uv[i].loops[0]->v) {
v_orig = slide_uv[i].slide_loop[1]->v;
BM_ITER_ELEM (l_iter, &lv_iter, v_orig, BM_LOOPS_OF_VERT) {
if (BM_loop_uv_share_vert_check(l_iter, slide_uv[i].slide_loop[1], offsets.uv)) {
if (l_iter->next->v == slide_uv[i].loops[0]->v ||
l_iter->prev->v == slide_uv[i].loops[0]->v)
{
/*Only look at faces adjacent to the edges. */
if (BM_elem_flag_test(l_iter->f, BM_ELEM_TAG)) {
/*Flip if wrong tag found. */
flip = true;
}
}
}
}
}
if (flip) {
BMLoop *s = slide_uv[i].slide_loop[0];
slide_uv[i].slide_loop[0] = slide_uv[i].slide_loop[1];
slide_uv[i].slide_loop[1] = s;
}
}
float *side0 = BM_ELEM_CD_GET_FLOAT_P(slide_uv[i].slide_loop[0], offsets.uv);
float *side1 = BM_ELEM_CD_GET_FLOAT_P(slide_uv[i].slide_loop[1], offsets.uv);
slide_uv[i].slide_uv_target[0] = side0;
slide_uv[i].slide_uv_target[1] = side1;
/*Tag faces*/
v_orig = slide_uv[i].slide_loop[0]->v;
BM_ITER_ELEM (l_iter, &lv_iter, v_orig, BM_LOOPS_OF_VERT) {
if (BM_loop_uv_share_vert_check(l_iter, slide_uv[i].slide_loop[0], offsets.uv)) {
if (l_iter->next->v == slide_uv[i].loops[0]->v ||
l_iter->prev->v == slide_uv[i].loops[0]->v)
{
BM_elem_flag_enable(l_iter->f, BM_ELEM_TAG);
}
}
}
v_orig = slide_uv[i].slide_loop[1]->v;
BM_ITER_ELEM (l_iter, &lv_iter, v_orig, BM_LOOPS_OF_VERT) {
if (BM_loop_uv_share_vert_check(l_iter, slide_uv[i].slide_loop[1], offsets.uv)) {
if (l_iter->next->v == slide_uv[i].loops[0]->v ||
l_iter->prev->v == slide_uv[i].loops[0]->v)
{
BM_elem_flag_enable(l_iter->f, BM_ELEM_TAG_ALT);
}
}
}
}
}
static blender::Vector<EdgeSlideUV> *create_EdgeSlideUVs(TransInfo *t, TransDataContainer *tc)
{
Scene *scene = t->scene;
BMEditMesh *em = BKE_editmesh_from_object(tc->obedit);
const BMUVOffsets offsets = BM_uv_map_get_offsets(em->bm);
BMFace *efa;
BMLoop *l;
BMIter iter, liter;
blender::Vector<EdgeSlideUV> *slide_edge_loops = new blender::Vector<EdgeSlideUV>;
/*Clear tags*/
BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
BM_elem_flag_disable(efa, BM_ELEM_TAG);
BM_elem_flag_disable(efa, BM_ELEM_TAG_ALT);
BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
BM_elem_flag_disable(l, BM_ELEM_TAG);
BM_elem_flag_disable(l, BM_ELEM_TAG_ALT);
}
}
BM_ITER_MESH (efa, &iter, em->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_flag_test(l, BM_ELEM_TAG)) {
continue;
}
if (uvedit_uv_select_test(scene, l, offsets)) {
blender::Vector<EdgeSlideUV> slide_uv;
EdgeSlideUV esuv;
BMLoop *loop_v, *fan_iter;
BMIter lv_iter;
BMVert *v_orig = l->v;
blender::Vector<BMLoop *> connected_loops;
blender::Set<BMEdge *> unique_edges_orig, unique_edges_iter;
/* Initialize*/
esuv.slide_loop[0] = l;
esuv.slide_loop[1] = l;
/*Check how many other selected UVs are connected. */
BM_ITER_ELEM (loop_v, &lv_iter, v_orig, BM_LOOPS_OF_VERT) {
if (BM_loop_uv_share_vert_check(loop_v, l, offsets.uv)) {
esuv.loops.append(loop_v);
BM_elem_flag_enable(loop_v, BM_ELEM_TAG);
if (uvedit_uv_select_test(scene, loop_v->next, offsets) &&
!BM_elem_flag_test(loop_v->next, BM_ELEM_TAG))
{
connected_loops.append(loop_v->next);
fan_iter = loop_v;
unique_edges_orig.add(loop_v->e);
}
else if (uvedit_uv_select_test(scene, loop_v->prev, offsets) &&
!BM_elem_flag_test(loop_v->prev, BM_ELEM_TAG))
{
connected_loops.append(loop_v->prev);
fan_iter = loop_v;
unique_edges_orig.add(loop_v->prev->e);
}
}
}
/*Any vertex with 3+ connected selected edges is ignored. */
if (unique_edges_orig.size() > 2) {
continue;
}
if (unique_edges_orig.size() == 0) {
calc_side_loops_from_single_terminating_uv(nullptr, l, esuv, offsets);
Melissa-Goon marked this conversation as resolved Outdated

*picky* All text comments should be C-style, surrounding spaces & be full sentences.

/* Any vertex with 3+ connected selected edges is ignored. */

\*picky\* All text comments should be C-style, surrounding spaces & be full sentences. `/* Any vertex with 3+ connected selected edges is ignored. */`
}
else if (unique_edges_orig.size() == 1) {
calc_side_loops_from_single_terminating_uv(
*unique_edges_orig.begin(), fan_iter, esuv, offsets);
}
else if (unique_edges_orig.size() == 2) {
calc_side_loops_from_pair(l, esuv, offsets);
}
blender::Vector<EdgeSlideUV> slide_uv1;
blender::Vector<EdgeSlideUV> slide_uv2;
slide_uv1.append(esuv);
slide_uv2.append(esuv);
for (int i = 0; i < connected_loops.size(); i++) {
unique_edges_iter = unique_edges_orig;
BMLoop *connected_loop = connected_loops[i];
bool has_next = true;
if (BM_elem_flag_test(connected_loops[i], BM_ELEM_TAG)) {
continue;
} /*Prevents from accessing the same direction twice. */
if (slide_uv1.size() != 1 && slide_uv2.size() != 1) {
break;
} /*Stops when 2 sides are found. */
blender::Vector<EdgeSlideUV> *slide_uv_temp = (slide_uv1.size() == 1) ? &slide_uv1 :
&slide_uv2;
while (has_next) {
blender::Set<BMEdge *> prev_unique_edges = unique_edges_iter;
unique_edges_iter.clear();
EdgeSlideUV esuv_next;
BMVert *v_curr = connected_loop->v;
BMLoop *loop_curr = connected_loop;
has_next = false;
/* Initialize*/
esuv_next.slide_loop[0] = connected_loop;
esuv_next.slide_loop[1] = connected_loop;
BM_ITER_ELEM (loop_v, &lv_iter, v_curr, BM_LOOPS_OF_VERT) {
if (BM_loop_uv_share_vert_check(loop_curr, loop_v, offsets.uv)) {
esuv_next.loops.append(loop_v);
BM_elem_flag_enable(loop_v, BM_ELEM_TAG);
if (uvedit_uv_select_test(scene, loop_v->next, offsets) &&
!BM_elem_flag_test(loop_v->next, BM_ELEM_TAG))
{
connected_loop = loop_v->next;
unique_edges_iter.add(loop_v->e);
fan_iter = loop_v;
has_next = true;
}
else if (uvedit_uv_select_test(scene, loop_v->prev, offsets) &&
!BM_elem_flag_test(loop_v->prev, BM_ELEM_TAG))
{
connected_loop = loop_v->prev;
fan_iter = loop_v;
unique_edges_iter.add(loop_v->prev->e);
has_next = true;
}
}
}
/*Any vertex with 3+ connected selected edges is ignored. */
if (unique_edges_iter.size() > 1) {
break;
}
EdgeSlideUV previous = slide_uv_temp->last();
/*We didn't find a connected loop. */
if (connected_loop == loop_curr) {
/*Find edge connecting previous point to current*/
BMEdge *edge = nullptr;
for (const auto &e : prev_unique_edges) {
if (e->v1 == connected_loop->v || e->v2 == connected_loop->v) {
edge = e;
}
}
/*We should find a connecting edge*/
BLI_assert(edge != nullptr);
calc_side_loops_from_single_terminating_uv(edge, connected_loop, esuv_next, offsets);
}
else {
calc_side_loops_from_pair(loop_curr, esuv_next, offsets);
}
slide_uv_temp->append(esuv_next);
}
}
/*Flip slide_uv1 and remove the last element and combine it with slide_uv2. */
std::reverse(slide_uv1.begin(), slide_uv1.end());
slide_uv1.pop_last();
slide_uv.reserve(slide_uv1.size() + slide_uv2.size());
slide_uv.insert(slide_uv.end(), slide_uv1.begin(), slide_uv1.end());
slide_uv.insert(slide_uv.end(), slide_uv2.begin(), slide_uv2.end());
flip_and_set_sides(slide_uv, offsets);
slide_edge_loops->insert(slide_edge_loops->end(), slide_uv.begin(), slide_uv.end());
}
}
}
return slide_edge_loops;
}
static void applyEdgeUVSlide(TransInfo *t)
{
float final = (fabsf(t->values[0]) > 1) ? 1 : fabsf(t->values[0]);
int side_index = t->values[0] < 0;
FOREACH_TRANS_DATA_CONTAINER (t, tc) {
BMEditMesh *em = BKE_editmesh_from_object(tc->obedit);
const BMUVOffsets offsets = BM_uv_map_get_offsets(em->bm);
blender::Vector<EdgeSlideUV> *slide_edge_loops = static_cast<blender::Vector<EdgeSlideUV> *>(
tc->custom.mode.data);
for (int i = 0; i < slide_edge_loops->size(); i++) {
for (int j = 0; j < (*slide_edge_loops)[i].loops.size(); j++) {
float *side = (*slide_edge_loops)[i].slide_uv_target[side_index];
float *orig = (*slide_edge_loops)[i].orig_uv;
float *luv = BM_ELEM_CD_GET_FLOAT_P((*slide_edge_loops)[i].loops[j], offsets.uv);
interp_v2_v2v2(luv, orig, side, fabsf(final));
}
}
DEG_id_tag_update(static_cast<ID *>(tc->obedit->data), ID_RECALC_GEOMETRY);
WM_main_add_notifier(NC_GEOM | ND_DATA, tc->obedit->data);
}
}
static void calcEdgeSlideCustomPointsUV(TransInfo *t)
{
float max_len = 0;
float *start_uv, *end_uv;
int start_region[2], end_region[2];
blender::Vector<EdgeSlideUV> slide_edge_loops = *(static_cast<blender::Vector<EdgeSlideUV> *>(
TRANS_DATA_CONTAINER_FIRST_OK(t)->custom.mode.data));
/* Find the longest slide edge to set the range of the mouse input. */
for (int i = 0; i < slide_edge_loops.size(); i++) {
float side0_len = len_squared_v2v2(slide_edge_loops[i].slide_uv_target[0],
slide_edge_loops[i].orig_uv);
Melissa-Goon marked this conversation as resolved Outdated

Include an explanation for what this loop does (why find the longest edge?).

Include an explanation for what this loop does (why find the longest edge?).
float side1_len = len_squared_v2v2(slide_edge_loops[i].slide_uv_target[1],
Melissa-Goon marked this conversation as resolved Outdated

When finding the maximum length, use len_squared_v2v2, avoids an unnecessary sqrtf.

When finding the maximum length, use `len_squared_v2v2`, avoids an unnecessary `sqrtf`.
slide_edge_loops[i].orig_uv);
float side_len_max;
int side_max;
if (side0_len >= side1_len) {
side_len_max = side0_len;
side_max = 0;
}
else {
side_len_max = side1_len;
side_max = 0;
}
if (side_len_max > max_len) {
max_len = side_len_max;
Melissa-Goon marked this conversation as resolved Outdated

(*slide_edge_loops) seems redundant, slide_edge_loops can be used here.

`(*slide_edge_loops)` seems redundant, `slide_edge_loops` can be used here.
start_uv = slide_edge_loops[i].orig_uv;
end_uv = slide_edge_loops[i].slide_uv_target[side_max];
}
}
UI_view2d_view_to_region(
&t->region->v2d, start_uv[0], start_uv[1], &start_region[0], &start_region[1]);
UI_view2d_view_to_region(&t->region->v2d, end_uv[0], end_uv[1], &end_region[0], &end_region[1]);
setCustomPoints(t, &t->mouse, start_region, end_region);
Melissa-Goon marked this conversation as resolved Outdated

Missing return.

Missing return.
applyMouseInput(t, &t->mouse, t->mval, t->values);
}
static eRedrawFlag handleEventEdgeSlideUV(TransInfo *t, const wmEvent *event)
{
if (event->type == MOUSEMOVE) {
Melissa-Goon marked this conversation as resolved Outdated

source/blender/editors/transform/transform_mode_edge_uv_slide.cc:513:1: error: control reaches end of non-void function [-Werror=return-type]

`source/blender/editors/transform/transform_mode_edge_uv_slide.cc:513:1: error: control reaches end of non-void function [-Werror=return-type]`
calcEdgeSlideCustomPointsUV(t);
return TREDRAW_NOTHING;
}
return TREDRAW_NOTHING;
}
static void initEdgeUVSlide(TransInfo *t, wmOperator * /*op*/)
{
blender::Vector<EdgeSlideUV> *esd;
t->mode = TFM_UV_EDGE_SLIDE;
bool ok = false;
FOREACH_TRANS_DATA_CONTAINER (t, tc) {
esd = create_EdgeSlideUVs(t, tc);
if (esd) {
tc->custom.mode.data = esd;
tc->custom.mode.free_cb = freeEdgeSlideUVs;
ok = true;
}
}
if (!ok) {
t->state = TRANS_CANCEL;
return;
}
t->idx_max = 1;
t->num.flag = 0;
t->num.idx_max = t->idx_max;
calcEdgeSlideCustomPointsUV(t);
initMouseInputMode(t, &t->mouse, INPUT_CUSTOM_RATIO_FLIP);
}
/** \} */
TransModeInfo TransMode_edgeslideuv = {
/*flags*/ T_NO_CONSTRAINT,
/*init_fn*/ initEdgeUVSlide,
/*transform_fn*/ applyEdgeUVSlide,
/*transform_matrix_fn*/ nullptr,
/*handle_event_fn*/ handleEventEdgeSlideUV,
/*snap_distance_fn*/ nullptr,
/*snap_apply_fn*/ nullptr,
/*draw_fn*/ nullptr,
};

View File

@ -71,6 +71,7 @@ static const char OP_EDGE_CREASE[] = "TRANSFORM_OT_edge_crease";
static const char OP_VERT_CREASE[] = "TRANSFORM_OT_vert_crease";
static const char OP_EDGE_BWEIGHT[] = "TRANSFORM_OT_edge_bevelweight";
static const char OP_SEQ_SLIDE[] = "TRANSFORM_OT_seq_slide";
static const char OP_UV_EDGE_SLIDE[] = "TRANSFORM_OT_uv_edge_slide";
static const char OP_NORMAL_ROTATION[] = "TRANSFORM_OT_rotate_normal";
static void TRANSFORM_OT_translate(wmOperatorType *ot);
@ -92,6 +93,7 @@ static void TRANSFORM_OT_edge_crease(wmOperatorType *ot);
static void TRANSFORM_OT_vert_crease(wmOperatorType *ot);
static void TRANSFORM_OT_edge_bevelweight(wmOperatorType *ot);
static void TRANSFORM_OT_seq_slide(wmOperatorType *ot);
static void TRANSFORM_OT_uv_edge_slide(wmOperatorType *ot);
static void TRANSFORM_OT_rotate_normal(wmOperatorType *ot);
static TransformModeItem transform_modes[] = {
@ -114,6 +116,7 @@ static TransformModeItem transform_modes[] = {
{OP_VERT_CREASE, TFM_VERT_CREASE, TRANSFORM_OT_vert_crease},
{OP_EDGE_BWEIGHT, TFM_BWEIGHT, TRANSFORM_OT_edge_bevelweight},
{OP_SEQ_SLIDE, TFM_SEQ_SLIDE, TRANSFORM_OT_seq_slide},
{OP_UV_EDGE_SLIDE, TFM_UV_EDGE_SLIDE, TRANSFORM_OT_uv_edge_slide},
{OP_NORMAL_ROTATION, TFM_NORMAL_ROTATION, TRANSFORM_OT_rotate_normal},
{nullptr, 0},
};
@ -151,6 +154,7 @@ const EnumPropertyItem rna_enum_transform_mode_types[] = {
{TFM_ALIGN, "ALIGN", 0, "Align", ""},
{TFM_EDGE_SLIDE, "EDGESLIDE", 0, "Edge Slide", ""},
{TFM_SEQ_SLIDE, "SEQSLIDE", 0, "Sequence Slide", ""},
{TFM_UV_EDGE_SLIDE, "EDGEUVSLIDE", 0, "Edge Slide UV", ""},
{TFM_GPENCIL_OPACITY, "GPENCIL_OPACITY", 0, "Grease Pencil Opacity", ""},
{0, nullptr, 0, nullptr, nullptr},
};
@ -567,7 +571,8 @@ static bool transform_poll_property(const bContext *C, wmOperator *op, const Pro
/* Special case: show constraint axis if we don't have values,
* needed for mirror operator. */
if (STREQ(prop_id, "constraint_axis") &&
(RNA_struct_find_property(op->ptr, "value") == nullptr)) {
(RNA_struct_find_property(op->ptr, "value") == nullptr))
{
return true;
}
@ -1308,6 +1313,27 @@ static void TRANSFORM_OT_edge_bevelweight(wmOperatorType *ot)
Transform_Properties(ot, P_SNAP);
}
static void TRANSFORM_OT_uv_edge_slide(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Edge Slide";
ot->description = "Slide an edge loop along a mesh";
ot->idname = OP_UV_EDGE_SLIDE;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* api callbacks */
ot->invoke = transform_invoke;
ot->exec = transform_exec;
ot->modal = transform_modal;
ot->cancel = transform_cancel;
ot->poll = ED_operator_uvedit;
ot->poll_property = transform_poll_property;
WM_operatortype_props_advanced_begin(ot);
RNA_def_float_factor(ot->srna, "value", 0, -1.0f, 1.0f, "Factor", "", -1.0f, 1.0f);
}
static void TRANSFORM_OT_seq_slide(wmOperatorType *ot)
{
/* identifiers */

View File

@ -642,7 +642,8 @@ static UVRipPairs *uv_rip_pairs_from_loop(BMLoop *l_init,
do {
/* Not a boundary and visible. */
if (!UL(l_radial_iter)->is_select_edge &&
BM_elem_flag_test(l_radial_iter->f, BM_ELEM_TAG)) {
BM_elem_flag_test(l_radial_iter->f, BM_ELEM_TAG))
{
BMLoop *l_other = (l_radial_iter->v == l_step->v) ? l_radial_iter :
l_radial_iter->next;
BLI_assert(l_other->v == l_step->v);