Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,59 @@
# SPDX-FileCopyrightText: 2023 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
set(INC
../include
../../makesrna
../../../../intern/eigen
# RNA_prototypes.hh
${CMAKE_BINARY_DIR}/source/blender/makesrna
)
set(INC_SYS
)
set(SRC
uvedit_buttons.cc
uvedit_clipboard.cc
uvedit_clipboard_graph_iso.cc
uvedit_draw.cc
uvedit_islands.cc
uvedit_ops.cc
uvedit_path.cc
uvedit_rip.cc
uvedit_select.cc
uvedit_smart_stitch.cc
uvedit_unwrap_ops.cc
uvedit_clipboard_graph_iso.hh
uvedit_intern.hh
)
set(LIB
PRIVATE bf::blenkernel
PRIVATE bf::blenlib
PRIVATE bf::blentranslation
PRIVATE bf::bmesh
PRIVATE bf::depsgraph
PRIVATE bf::dna
PRIVATE bf::geometry
PRIVATE bf::gpu
PRIVATE bf::imbuf
PRIVATE bf::intern::guardedalloc
PRIVATE bf::nodes
PRIVATE bf::windowmanager
)
if(WITH_UV_SLIM)
list(APPEND LIB
bf_intern_slim
)
add_definitions(-DWITH_UV_SLIM)
endif()
blender_add_lib(bf_editor_uvedit "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
# RNA_prototypes.hh
add_dependencies(bf_editor_uvedit bf_rna)

View File

@@ -0,0 +1,261 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eduv
*/
#include <cstring>
#include "MEM_guardedalloc.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "DNA_space_types.h"
#include "BLI_listbase.h"
#include "BLI_math_vector.h"
#include "BLI_string_utf8.h"
#include "BLI_utildefines.h"
#include "BLT_translation.hh"
#include "BKE_context.hh"
#include "BKE_customdata.hh"
#include "BKE_editmesh.hh"
#include "BKE_layer.hh"
#include "BKE_screen.hh"
#include "DEG_depsgraph.hh"
#include "ED_image.hh"
#include "ED_uvedit.hh"
#include "UI_interface.hh"
#include "UI_interface_layout.hh"
#include "WM_api.hh"
#include "WM_types.hh"
namespace blender {
#define B_UVEDIT_VERTEX 3
/* UV Utilities */
static int uvedit_center(Scene *scene, const Span<Object *> objects, float center[2])
{
BMFace *f;
BMLoop *l;
BMIter iter, liter;
float *luv;
int tot = 0;
zero_v2(center);
for (Object *obedit : objects) {
BMEditMesh *em = BKE_editmesh_from_object(obedit);
const BMUVOffsets offsets = BM_uv_map_offsets_get(em->bm);
BM_ITER_MESH (f, &iter, em->bm, BM_FACES_OF_MESH) {
if (!uvedit_face_visible_test(scene, f)) {
continue;
}
BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
if (uvedit_uv_select_test(scene, em->bm, l, offsets)) {
luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
add_v2_v2(center, luv);
tot++;
}
}
}
}
if (tot > 0) {
center[0] /= tot;
center[1] /= tot;
}
return tot;
}
static void uvedit_translate(Scene *scene, const Span<Object *> objects, const float delta[2])
{
BMFace *f;
BMLoop *l;
BMIter iter, liter;
float *luv;
for (Object *obedit : objects) {
BMEditMesh *em = BKE_editmesh_from_object(obedit);
const BMUVOffsets offsets = BM_uv_map_offsets_get(em->bm);
BM_ITER_MESH (f, &iter, em->bm, BM_FACES_OF_MESH) {
if (!uvedit_face_visible_test(scene, f)) {
continue;
}
BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
if (uvedit_uv_select_test(scene, em->bm, l, offsets)) {
luv = BM_ELEM_CD_GET_FLOAT_P(l, offsets.uv);
add_v2_v2(luv, delta);
}
}
}
}
}
/* Button Functions, using an evil static variable */
static float uvedit_old_center[2];
static void uvedit_vertex_buttons(const bContext *C, ui::Block *block)
{
SpaceImage *sima = CTX_wm_space_image(C);
const Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
float center[2];
int imx, imy, step, digits;
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs(
*bmain, scene, CTX_data_view_layer(C), CTX_wm_view3d(C));
ED_space_image_get_size(sima, &imx, &imy);
if (uvedit_center(scene, objects, center)) {
float range_xy[2][2] = {
{-10.0f, 10.0f},
{-10.0f, 10.0f},
};
copy_v2_v2(uvedit_old_center, center);
/* expand UI range by center */
CLAMP_MAX(range_xy[0][0], uvedit_old_center[0]);
CLAMP_MIN(range_xy[0][1], uvedit_old_center[0]);
CLAMP_MAX(range_xy[1][0], uvedit_old_center[1]);
CLAMP_MIN(range_xy[1][1], uvedit_old_center[1]);
if (!(sima->flag & SI_COORDFLOATS)) {
uvedit_old_center[0] *= imx;
uvedit_old_center[1] *= imy;
mul_v2_fl(range_xy[0], imx);
mul_v2_fl(range_xy[1], imy);
}
if (sima->flag & SI_COORDFLOATS) {
step = 1;
digits = 3;
}
else {
step = 100;
digits = 2;
}
ui::Button *but;
int y = 0;
block_align_begin(block);
but = uiDefButV(block,
ui::ButtonType::Num,
IFACE_("X:"),
0,
y -= UI_UNIT_Y,
200,
UI_UNIT_Y,
&uvedit_old_center[0],
UNPACK2(range_xy[0]),
"");
button_retval_set(but, B_UVEDIT_VERTEX);
button_number_step_size_set(but, step);
button_number_precision_set(but, digits);
but = uiDefButV(block,
ui::ButtonType::Num,
IFACE_("Y:"),
0,
y -= UI_UNIT_Y,
200,
UI_UNIT_Y,
&uvedit_old_center[1],
UNPACK2(range_xy[1]),
"");
button_retval_set(but, B_UVEDIT_VERTEX);
button_number_step_size_set(but, step);
button_number_precision_set(but, digits);
block_align_end(block);
}
}
static void do_uvedit_vertex(bContext *C, void * /*arg*/, int event)
{
SpaceImage *sima = CTX_wm_space_image(C);
const Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
float center[2], delta[2];
int imx, imy;
if (event != B_UVEDIT_VERTEX) {
return;
}
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs(
*bmain, scene, CTX_data_view_layer(C), CTX_wm_view3d(C));
ED_space_image_get_size(sima, &imx, &imy);
uvedit_center(scene, objects, center);
if (sima->flag & SI_COORDFLOATS) {
delta[0] = uvedit_old_center[0] - center[0];
delta[1] = uvedit_old_center[1] - center[1];
}
else {
delta[0] = uvedit_old_center[0] / imx - center[0];
delta[1] = uvedit_old_center[1] / imy - center[1];
}
uvedit_translate(scene, objects, delta);
WM_event_add_notifier(C, NC_IMAGE, sima->image);
for (Object *obedit : objects) {
DEG_id_tag_update(obedit->data, ID_RECALC_GEOMETRY);
}
}
/* Panels */
static bool image_panel_uv_poll(const bContext *C, PanelType * /*pt*/)
{
SpaceImage *sima = CTX_wm_space_image(C);
if (sima->mode != SI_MODE_UV) {
return false;
}
Object *obedit = CTX_data_edit_object(C);
return ED_uvedit_test(obedit);
}
static void image_panel_uv(const bContext *C, Panel *panel)
{
ui::Block *block = panel->layout->absolute().block();
block_func_handle_set(block, do_uvedit_vertex, nullptr);
uvedit_vertex_buttons(C, block);
}
void ED_uvedit_buttons_register(ARegionType *art)
{
PanelType *pt = MEM_new_zeroed<PanelType>(__func__);
STRNCPY_UTF8(pt->idname, "IMAGE_PT_uv");
STRNCPY_UTF8(pt->label, N_("UV Vertex")); /* XXX C panels unavailable through RNA bpy.types! */
/* Could be 'Item' matching 3D view, avoid new tab for two buttons. */
STRNCPY_UTF8(pt->category, "Image");
pt->draw = image_panel_uv;
pt->poll = image_panel_uv_poll;
BLI_addtail(&art->paneltypes, pt);
}
} // namespace blender

View File

@@ -0,0 +1,406 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eduv
*
* Attempt to find a graph isomorphism between the topology of two different UV islands.
*
* \note On terminology, for the purposes of this file:
* * An iso_graph is a "Graph" in Graph Theory.
* * An iso_graph has an unordered set of iso_verts.
* * An iso_graph has an unordered set of iso_edges.
* * An iso_vert is a "Vertex" in Graph Theory
* * Each iso_vert has a label.
* * An iso_edge is an "Edge" in Graph Theory
* * Each iso_edge connects two iso_verts.
* * An iso_edge is undirected.
*/
#include "BKE_context.hh"
#include "BKE_customdata.hh"
#include "BKE_editmesh.hh"
#include "BKE_layer.hh"
#include "BKE_mesh_mapping.hh" /* UvElementMap */
#include "BKE_report.hh"
#include "DEG_depsgraph.hh"
#include "ED_mesh.hh"
#include "ED_screen.hh"
#include "WM_api.hh"
#include "uvedit_clipboard_graph_iso.hh"
#include "uvedit_intern.hh" /* Own include. */
namespace blender {
void UV_clipboard_free();
class UV_ClipboardBuffer {
public:
~UV_ClipboardBuffer();
void append(UvElementMap *element_map, const int cd_loop_uv_offset);
/**
* \return True when found.
*/
bool find_isomorphism(UvElementMap *dest_element_map,
int island_index,
int cd_loop_uv_offset,
Vector<int> &r_label,
bool *r_search_abandoned);
void write_uvs(UvElementMap *element_map,
int island_index,
const int cd_loop_uv_offset,
const Vector<int> &label);
private:
Vector<GraphISO *> graph;
Vector<int> offset;
Vector<std::pair<float, float>> uv;
};
static UV_ClipboardBuffer *uv_clipboard = nullptr;
UV_ClipboardBuffer::~UV_ClipboardBuffer()
{
for (const int64_t index : graph.index_range()) {
delete graph[index];
}
graph.clear();
offset.clear();
uv.clear();
}
/* Given a `BMLoop`, possibly belonging to an island in a `UvElementMap`,
* return the `iso_index` corresponding to it's representation
* in the `iso_graph`.
*
* If the `BMLoop` is not part of the `iso_graph`, return -1.
*/
static int iso_index_for_loop(const BMLoop *loop,
UvElementMap *element_map,
const int island_index)
{
UvElement *element = BM_uv_element_get(element_map, loop);
if (!element) {
return -1; /* Either unselected, or a different island. */
}
const int index = BM_uv_element_get_unique_index(element_map, element);
const int base_index = BM_uv_element_get_unique_index(
element_map, element_map->storage + element_map->island_indices[island_index]);
return index - base_index;
}
/* Add an `iso_edge` to an `iso_graph` between two BMLoops.
*/
static void add_iso_edge(
GraphISO *graph, BMLoop *loop_v, BMLoop *loop_w, UvElementMap *element_map, int island_index)
{
BLI_assert(loop_v->f == loop_w->f); /* Ensure on the same face. */
const int index_v = iso_index_for_loop(loop_v, element_map, island_index);
const int index_w = iso_index_for_loop(loop_w, element_map, island_index);
BLI_assert(index_v != index_w);
if (index_v == -1 || index_w == -1) {
return; /* Unselected. */
}
BLI_assert(0 <= index_v && index_v < graph->n);
BLI_assert(0 <= index_w && index_w < graph->n);
graph->add_edge(index_v, index_w);
}
/* Build an `iso_graph` representation of an island of a `UvElementMap`.
*/
static GraphISO *build_iso_graph(UvElementMap *element_map,
const int island_index,
int /*cd_loop_uv_offset*/)
{
GraphISO *g = new GraphISO(element_map->island_total_unique_uvs[island_index]);
for (int i = 0; i < g->n; i++) {
g->label[i] = i;
}
const int i0 = element_map->island_indices[island_index];
const int i1 = i0 + element_map->island_total_uvs[island_index];
/* Add iso_edges. */
for (int i = i0; i < i1; i++) {
const UvElement *element = element_map->storage + i;
/* Look forward around the current face. */
add_iso_edge(g, element->l, element->l->next, element_map, island_index);
/* Look backward around the current face.
* (Required for certain vertex selection cases.)
*/
add_iso_edge(g, element->l->prev, element->l, element_map, island_index);
}
/* TODO: call g->sort_vertices_by_degree() */
return g;
}
/* Convert each island inside an `element_map` into an `iso_graph`, and append them to the
* clipboard buffer. */
void UV_ClipboardBuffer::append(UvElementMap *element_map, const int cd_loop_uv_offset)
{
for (int island_index = 0; island_index < element_map->total_islands; island_index++) {
offset.append(uv.size());
graph.append(build_iso_graph(element_map, island_index, cd_loop_uv_offset));
/* TODO: Consider iterating over `BM_uv_element_map_ensure_unique_index` instead. */
for (int j = 0; j < element_map->island_total_uvs[island_index]; j++) {
UvElement *element = element_map->storage + element_map->island_indices[island_index] + j;
if (!element->separate) {
continue;
}
float *luv = BM_ELEM_CD_GET_FLOAT_P(element->l, cd_loop_uv_offset);
uv.append(std::make_pair(luv[0], luv[1]));
}
}
}
/* Write UVs back to an island. */
void UV_ClipboardBuffer::write_uvs(UvElementMap *element_map,
int island_index,
const int cd_loop_uv_offset,
const Vector<int> &label)
{
BLI_assert(label.size() == element_map->island_total_unique_uvs[island_index]);
/* TODO: Consider iterating over `BM_uv_element_map_ensure_unique_index` instead. */
int unique_uv = 0;
for (int j = 0; j < element_map->island_total_uvs[island_index]; j++) {
int k = element_map->island_indices[island_index] + j;
UvElement *element = element_map->storage + k;
if (!element->separate) {
continue;
}
BLI_assert(0 <= unique_uv);
BLI_assert(unique_uv < label.size());
const std::pair<float, float> &source_uv = uv_clipboard->uv[label[unique_uv]];
while (element) {
float *luv = BM_ELEM_CD_GET_FLOAT_P(element->l, cd_loop_uv_offset);
luv[0] = source_uv.first;
luv[1] = source_uv.second;
element = element->next;
if (!element || element->separate) {
break;
}
}
unique_uv++;
}
BLI_assert(unique_uv == label.size());
}
/**
* Call the external isomorphism solver.
* \return True when found.
*/
static bool find_isomorphism(UvElementMap *dest,
const int dest_island_index,
GraphISO *graph_source,
const int cd_loop_uv_offset,
Vector<int> &r_label,
bool *r_search_abandoned)
{
const int island_total_unique_uvs = dest->island_total_unique_uvs[dest_island_index];
if (island_total_unique_uvs != graph_source->n) {
return false; /* Isomorphisms can't differ in |iso_vert|. */
}
r_label.resize(island_total_unique_uvs);
GraphISO *graph_dest = build_iso_graph(dest, dest_island_index, cd_loop_uv_offset);
int (*solution)[2] = MEM_new_array_uninitialized<int[2]>(graph_source->n, __func__);
int solution_length = 0;
const bool found = ED_uvedit_clipboard_maximum_common_subgraph(
graph_source, graph_dest, solution, &solution_length, r_search_abandoned);
/* TODO: Implement "Best Effort" / "Nearest Match" paste functionality here. */
if (found) {
BLI_assert(solution_length == dest->island_total_unique_uvs[dest_island_index]);
for (int i = 0; i < solution_length; i++) {
int index_s = solution[i][0];
int index_t = solution[i][1];
BLI_assert(0 <= index_s && index_s < solution_length);
BLI_assert(0 <= index_t && index_t < solution_length);
r_label[index_t] = index_s;
}
}
MEM_SAFE_DELETE(solution);
delete graph_dest;
return found;
}
bool UV_ClipboardBuffer::find_isomorphism(UvElementMap *dest_element_map,
const int dest_island_index,
const int cd_loop_uv_offset,
Vector<int> &r_label,
bool *r_search_abandoned)
{
for (const int64_t source_island_index : graph.index_range()) {
if (blender::find_isomorphism(dest_element_map,
dest_island_index,
graph[source_island_index],
cd_loop_uv_offset,
r_label,
r_search_abandoned))
{
const int island_total_unique_uvs =
dest_element_map->island_total_unique_uvs[dest_island_index];
const int island_offset = offset[source_island_index];
BLI_assert(island_total_unique_uvs == r_label.size());
for (int i = 0; i < island_total_unique_uvs; i++) {
r_label[i] += island_offset; /* TODO: (minor optimization) Defer offset. */
}
/* TODO: There may be more than one match. How to choose between them? */
return true;
}
}
return false;
}
static wmOperatorStatus uv_copy_exec(bContext *C, wmOperator * /*op*/)
{
UV_clipboard_free();
uv_clipboard = new UV_ClipboardBuffer();
const Main *bmain = CTX_data_main(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Scene *scene = CTX_data_scene(C);
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs(
*bmain, scene, view_layer, nullptr);
for (Object *ob : objects) {
BMEditMesh *em = BKE_editmesh_from_object(ob);
const bool use_seams = false;
UvElementMap *element_map = BM_uv_element_map_create(
em->bm, scene, true, false, use_seams, true);
if (element_map) {
const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_PROP_FLOAT2);
uv_clipboard->append(element_map, cd_loop_uv_offset);
}
BM_uv_element_map_free(element_map);
}
/* TODO: Serialize `UvClipboard` to system clipboard. */
return OPERATOR_FINISHED;
}
static wmOperatorStatus uv_paste_exec(bContext *C, wmOperator *op)
{
/* TODO: Restore `UvClipboard` from system clipboard. */
if (!uv_clipboard) {
return OPERATOR_FINISHED; /* Nothing to do. */
}
const Main *bmain = CTX_data_main(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Scene *scene = CTX_data_scene(C);
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs(
*bmain, scene, view_layer, nullptr);
bool changed_multi = false;
int complicated_search = 0;
int total_search = 0;
for (Object *ob : objects) {
BMEditMesh *em = BKE_editmesh_from_object(ob);
const bool use_seams = false;
const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_PROP_FLOAT2);
UvElementMap *dest_element_map = BM_uv_element_map_create(
em->bm, scene, true, false, use_seams, true);
if (!dest_element_map) {
continue;
}
bool changed = false;
for (int i = 0; i < dest_element_map->total_islands; i++) {
total_search++;
Vector<int> label;
bool search_abandoned = false;
const bool found = uv_clipboard->find_isomorphism(
dest_element_map, i, cd_loop_uv_offset, label, &search_abandoned);
if (!found) {
if (search_abandoned) {
complicated_search++;
}
continue; /* No source UVs can be found that is isomorphic to this island. */
}
uv_clipboard->write_uvs(dest_element_map, i, cd_loop_uv_offset, label);
changed = true; /* UVs were moved. */
}
BM_uv_element_map_free(dest_element_map);
if (changed) {
changed_multi = true;
DEG_id_tag_update(ob->data, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data);
}
}
if (complicated_search) {
BKE_reportf(op->reports,
RPT_WARNING,
"Skipped %d of %d island(s), geometry was too complicated to detect a match",
complicated_search,
total_search);
}
return changed_multi ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
}
void UV_OT_copy(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Copy UVs";
ot->description = "Copy selected UV vertices";
ot->idname = "UV_OT_copy";
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* API callbacks. */
ot->exec = uv_copy_exec;
ot->poll = ED_operator_uvedit;
}
void UV_OT_paste(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Paste UVs";
ot->description = "Paste selected UV vertices";
ot->idname = "UV_OT_paste";
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* API callbacks. */
ot->exec = uv_paste_exec;
ot->poll = ED_operator_uvedit;
}
void UV_clipboard_free()
{
delete uv_clipboard;
uv_clipboard = nullptr;
}
} // namespace blender

View File

@@ -0,0 +1,444 @@
/* SPDX-FileCopyrightText: 2019 Stefano Quer
* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Originally 6846114 from https://github.com/stefanoquer/graphISO/blob/master/v3
* graphISO: Tools to compute the Maximum Common Subgraph between two graphs.
*/
#include "uvedit_clipboard_graph_iso.hh"
#include "BLI_assert.h"
#include "MEM_guardedalloc.h"
#include <algorithm>
#include <climits>
namespace blender {
#define L 0
#define R 1
#define LL 2
#define RL 3
#define ADJ 4
#define P 5
#define W 6
#define IRL 7
#define BDS 8
GraphISO::GraphISO(int n)
{
this->n = n;
label = MEM_new_array_uninitialized<uint>(n, __func__);
adjmat = MEM_new_array_uninitialized<uint8_t *>(n, __func__);
/* \note Allocation of `n * n` bytes total! */
for (int i = 0; i < n; i++) {
/* Caution, are you trying to change the representation of adjmat?
* Consider `Vector<std::pair<int, int>> adjmat;` instead.
* Better still is to use a different algorithm. See for example:
* https://www.uni-ulm.de/fileadmin/website_uni_ulm/iui.inst.190/Mitarbeiter/toran/beatcs09.pdf
*/
adjmat[i] = MEM_new_array_zeroed<uint8_t>(n * sizeof *adjmat[i], __func__);
}
degree = nullptr;
}
GraphISO::~GraphISO()
{
for (int i = 0; i < n; i++) {
MEM_delete(adjmat[i]);
}
MEM_delete(adjmat);
MEM_delete(label);
if (degree) {
MEM_delete(degree);
}
}
void GraphISO::add_edge(int v, int w)
{
BLI_assert(v != w);
adjmat[v][w] = 1;
adjmat[w][v] = 1;
}
void GraphISO::calculate_degrees() const
{
if (degree) {
return;
}
degree = MEM_new_array_uninitialized<uint>(n, __func__);
for (int v = 0; v < n; v++) {
int row_count = 0;
for (int w = 0; w < n; w++) {
if (adjmat[v][w]) {
row_count++;
}
}
degree[v] = row_count;
}
}
class GraphISO_DegreeCompare {
public:
GraphISO_DegreeCompare(const GraphISO *g)
{
this->g = g;
}
const GraphISO *g;
bool operator()(int i, int j) const
{
return g->degree[i] < g->degree[j];
}
};
GraphISO *GraphISO::sort_vertices_by_degree() const
{
calculate_degrees();
int *vv = MEM_new_array_uninitialized<int>(n, __func__);
for (int i = 0; i < n; i++) {
vv[i] = i;
}
/* Currently ordering iso_verts by degree.
* Instead should order iso_verts by frequency of degree. */
GraphISO_DegreeCompare compare_obj(this);
std::sort(vv, vv + n, compare_obj);
GraphISO *subg = new GraphISO(n);
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
subg->adjmat[i][j] = adjmat[vv[i]][vv[j]];
}
}
for (int i = 0; i < n; i++) {
subg->label[i] = label[vv[i]];
}
subg->calculate_degrees();
MEM_delete(vv);
return subg;
}
static void update_incumbent(uint8_t cur[][2], int inc[][2], int cur_pos, int *inc_pos)
{
if (cur_pos > *inc_pos) {
*inc_pos = cur_pos;
for (int i = 0; i < cur_pos; i++) {
inc[i][L] = cur[i][L];
inc[i][R] = cur[i][R];
}
}
}
static void add_bidomain(uint8_t domains[][BDS],
int *bd_pos,
uint8_t left_i,
uint8_t right_i,
uint8_t left_len,
uint8_t right_len,
uint8_t is_adjacent,
uint8_t cur_pos)
{
domains[*bd_pos][L] = left_i;
domains[*bd_pos][R] = right_i;
domains[*bd_pos][LL] = left_len;
domains[*bd_pos][RL] = right_len;
domains[*bd_pos][ADJ] = is_adjacent;
domains[*bd_pos][P] = cur_pos;
domains[*bd_pos][W] = UINT8_MAX;
domains[*bd_pos][IRL] = right_len;
(*bd_pos)++;
}
static int calc_bound(const uint8_t domains[][BDS], int bd_pos, int cur_pos)
{
int bound = 0;
for (int i = bd_pos - 1; i >= 0 && domains[i][P] == cur_pos; i--) {
bound += std::min(domains[i][LL], domains[i][IRL]);
}
return bound;
}
static int partition(uint8_t *arr, int start, int len, const uint8_t *adjrow)
{
int i = 0;
for (int j = 0; j < len; j++) {
if (adjrow[arr[start + j]]) {
std::swap(arr[start + i], arr[start + j]);
i++;
}
}
return i;
}
static void generate_next_domains(uint8_t domains[][BDS],
int *bd_pos,
int cur_pos,
uint8_t *left,
uint8_t *right,
uint8_t v,
uint8_t w,
int inc_pos,
uint8_t **adjmat0,
uint8_t **adjmat1)
{
int i;
int bd_backup = *bd_pos;
int bound = 0;
uint8_t *bd;
for (i = *bd_pos - 1, bd = &domains[i][L]; i >= 0 && bd[P] == cur_pos - 1;
i--, bd = &domains[i][L])
{
uint8_t l_len = partition(left, bd[L], bd[LL], adjmat0[v]);
uint8_t r_len = partition(right, bd[R], bd[RL], adjmat1[w]);
if (bd[LL] - l_len && bd[RL] - r_len) {
add_bidomain(domains,
bd_pos,
bd[L] + l_len,
bd[R] + r_len,
bd[LL] - l_len,
bd[RL] - r_len,
bd[ADJ],
uint8_t(cur_pos));
bound += std::min(bd[LL] - l_len, bd[RL] - r_len);
}
if (l_len && r_len) {
add_bidomain(domains, bd_pos, bd[L], bd[R], l_len, r_len, true, uint8_t(cur_pos));
bound += std::min(l_len, r_len);
}
}
if (cur_pos + bound <= inc_pos) {
*bd_pos = bd_backup;
}
}
static uint8_t select_next_v(uint8_t *left, uint8_t *bd)
{
uint8_t min = UINT8_MAX;
uint8_t idx = UINT8_MAX;
if (bd[RL] != bd[IRL]) {
return left[bd[L] + bd[LL]];
}
for (uint8_t i = 0; i < bd[LL]; i++) {
if (left[bd[L] + i] < min) {
min = left[bd[L] + i];
idx = i;
}
}
std::swap(left[bd[L] + idx], left[bd[L] + bd[LL] - 1]);
bd[LL]--;
bd[RL]--;
return min;
}
static uint8_t find_min_value(const uint8_t *arr, uint8_t start_idx, uint8_t len)
{
uint8_t min_v = UINT8_MAX;
for (int i = 0; i < len; i++) {
min_v = std::min(arr[start_idx + i], min_v);
}
return min_v;
}
static void select_bidomain(uint8_t domains[][BDS],
int bd_pos,
const uint8_t *left,
int current_matching_size,
bool connected)
{
int i;
int min_size = INT_MAX;
int min_tie_breaker = INT_MAX;
int best = INT_MAX;
uint8_t *bd;
for (i = bd_pos - 1, bd = &domains[i][L]; i >= 0 && bd[P] == current_matching_size;
i--, bd = &domains[i][L])
{
if (connected && current_matching_size > 0 && !bd[ADJ]) {
continue;
}
int len = bd[LL] > bd[RL] ? bd[LL] : bd[RL];
if (len < min_size) {
min_size = len;
min_tie_breaker = find_min_value(left, bd[L], bd[LL]);
best = i;
}
else if (len == min_size) {
int tie_breaker = find_min_value(left, bd[L], bd[LL]);
if (tie_breaker < min_tie_breaker) {
min_tie_breaker = tie_breaker;
best = i;
}
}
}
if (best != INT_MAX && best != bd_pos - 1) {
uint8_t tmp[BDS];
for (i = 0; i < BDS; i++) {
tmp[i] = domains[best][i];
}
for (i = 0; i < BDS; i++) {
domains[best][i] = domains[bd_pos - 1][i];
}
for (i = 0; i < BDS; i++) {
domains[bd_pos - 1][i] = tmp[i];
}
}
}
static uint8_t select_next_w(const uint8_t *right, uint8_t *bd)
{
uint8_t min = UINT8_MAX;
uint8_t idx = UINT8_MAX;
for (uint8_t i = 0; i < bd[RL] + 1; i++) {
if ((right[bd[R] + i] > bd[W] || bd[W] == UINT8_MAX) && right[bd[R] + i] < min) {
min = right[bd[R] + i];
idx = i;
}
}
if (idx == UINT8_MAX) {
bd[RL]++;
}
return idx;
}
static void maximum_common_subgraph_internal(int incumbent[][2],
int *inc_pos,
uint8_t **adjmat0,
int n0,
uint8_t **adjmat1,
int n1,
bool *r_search_abandoned)
{
int min = std::min(n0, n1);
uint8_t (*cur)[2] = MEM_new_array_uninitialized<uint8_t[2]>(min, __func__);
uint8_t (*domains)[BDS] = MEM_new_array_uninitialized<uint8_t[BDS]>(min * min, __func__);
uint8_t *left = MEM_new_array_uninitialized<uint8_t>(n0, __func__);
uint8_t *right = MEM_new_array_uninitialized<uint8_t>(n1, __func__);
uint8_t v, w, *bd;
int bd_pos = 0;
for (int i = 0; i < n0; i++) {
left[i] = i;
}
for (int i = 0; i < n1; i++) {
right[i] = i;
}
add_bidomain(domains, &bd_pos, 0, 0, n0, n1, 0, 0);
int iteration_count = 0;
while (bd_pos > 0) {
if (iteration_count++ > 10000000) {
/* Unlikely to find a solution, may as well give up.
* Can occur with moderate sized inputs where the graph has lots of symmetry, e.g. a cube
* subdivided 3x times.
*/
*r_search_abandoned = true;
*inc_pos = 0;
break;
}
bd = &domains[bd_pos - 1][L];
if (calc_bound(domains, bd_pos, bd[P]) + bd[P] <= *inc_pos ||
(bd[LL] == 0 && bd[RL] == bd[IRL]))
{
bd_pos--;
}
else {
const bool connected = false;
select_bidomain(domains, bd_pos, left, domains[bd_pos - 1][P], connected);
v = select_next_v(left, bd);
if ((bd[W] = select_next_w(right, bd)) != UINT8_MAX) {
w = right[bd[R] + bd[W]]; /* Swap the W after the bottom of the current right domain. */
right[bd[R] + bd[W]] = right[bd[R] + bd[RL]];
right[bd[R] + bd[RL]] = w;
bd[W] = w; /* Store the W used for this iteration. */
cur[bd[P]][L] = v;
cur[bd[P]][R] = w;
update_incumbent(cur, incumbent, bd[P] + uint8_t(1), inc_pos);
generate_next_domains(
domains, &bd_pos, bd[P] + 1, left, right, v, w, *inc_pos, adjmat0, adjmat1);
}
}
}
MEM_delete(cur);
MEM_delete(domains);
MEM_delete(right);
MEM_delete(left);
}
static bool check_automorphism(const GraphISO *g0,
const GraphISO *g1,
int solution[][2],
int *solution_length)
{
if (g0->n != g1->n) {
return false;
}
for (int i = 0; i < g0->n; i++) {
if (g0->label[i] != g1->label[i]) {
return false;
}
for (int j = 0; j < g0->n; j++) {
if (g0->adjmat[i][j] != g1->adjmat[i][j]) {
return false;
}
}
solution[i][0] = i;
solution[i][1] = i;
}
*solution_length = g0->n;
return true;
}
bool ED_uvedit_clipboard_maximum_common_subgraph(GraphISO *g0_input,
GraphISO *g1_input,
int solution[][2],
int *solution_length,
bool *r_search_abandoned)
{
if (check_automorphism(g0_input, g1_input, solution, solution_length)) {
return true;
}
int n0 = g0_input->n;
int n1 = g1_input->n;
int min_size = std::min(n0, n1);
if (min_size >= UINT8_MAX - 2) {
return false;
}
GraphISO *g0 = g0_input->sort_vertices_by_degree();
GraphISO *g1 = g1_input->sort_vertices_by_degree();
int sol_len = 0;
maximum_common_subgraph_internal(
solution, &sol_len, g0->adjmat, n0, g1->adjmat, n1, r_search_abandoned);
*solution_length = sol_len;
bool result = (sol_len == n0);
if (result) {
for (int i = 0; i < sol_len; i++) {
solution[i][0] = g0->label[solution[i][0]]; /* Index from input. */
solution[i][1] = g1->label[solution[i][1]];
}
}
delete g1;
delete g0;
return result;
}
} // namespace blender

View File

@@ -0,0 +1,45 @@
/* SPDX-FileCopyrightText: 2019 Stefano Quer
* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Originally 6846114 from https://github.com/stefanoquer/graphISO/blob/master/v3
* graphISO: Tools to compute the Maximum Common Subgraph between two graphs.
*/
/** \file
* \ingroup eduv
*/
#pragma once
#include "BLI_sys_types.h"
namespace blender {
/* A thin representation of a "Graph" in graph theory. */
class GraphISO {
public:
GraphISO(int n);
~GraphISO();
int n;
uint8_t **adjmat;
uint *label;
mutable uint *degree;
void add_edge(int v, int w);
GraphISO *sort_vertices_by_degree() const;
private:
void calculate_degrees() const;
};
/**
* Find the maximum common subgraph between two graphs.
* (Can be used to find graph ismorphism.)
* \return True when found.
*/
bool ED_uvedit_clipboard_maximum_common_subgraph(
GraphISO *, GraphISO *, int solution[][2], int *solution_length, bool *r_search_abandoned);
} // namespace blender

View File

@@ -0,0 +1,97 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eduv
*/
#include "BLI_math_vector.h"
#include "DNA_screen_types.h"
#include "DNA_userdef_types.h"
#include "GPU_immediate.hh"
#include "GPU_matrix.hh"
#include "GPU_state.hh"
#include "UI_view2d.hh"
#include "ED_uvedit.hh"
namespace blender {
/* ------------------------- */
void ED_image_draw_cursor(ARegion *region, const float cursor[2])
{
float zoom[2], x_fac, y_fac;
ui::view2d_scale_get_inverse(&region->v2d, &zoom[0], &zoom[1]);
mul_v2_fl(zoom, 256.0f * UI_SCALE_FAC);
x_fac = zoom[0];
y_fac = zoom[1];
GPU_line_width(1.0f);
GPU_matrix_translate_2fv(cursor);
const uint shdr_pos = GPU_vertformat_attr_add(
immVertexFormat(), "pos", gpu::VertAttrType::SFLOAT_32_32);
immBindBuiltinProgram(GPU_SHADER_3D_LINE_DASHED_UNIFORM_COLOR);
float viewport_size[4];
GPU_viewport_size_get_f(viewport_size);
immUniform2f("viewport_size", viewport_size[2] / UI_SCALE_FAC, viewport_size[3] / UI_SCALE_FAC);
immUniform1i("colors_len", 2); /* "advanced" mode */
immUniform4f("color", 1.0f, 0.0f, 0.0f, 1.0f);
immUniform4f("color2", 1.0f, 1.0f, 1.0f, 1.0f);
immUniform1f("dash_width", 8.0f);
immUniform1f("udash_factor", 0.5f);
immBegin(GPU_PRIM_LINES, 8);
immVertex2f(shdr_pos, -0.05f * x_fac, 0.0f);
immVertex2f(shdr_pos, 0.0f, 0.05f * y_fac);
immVertex2f(shdr_pos, 0.0f, 0.05f * y_fac);
immVertex2f(shdr_pos, 0.05f * x_fac, 0.0f);
immVertex2f(shdr_pos, 0.05f * x_fac, 0.0f);
immVertex2f(shdr_pos, 0.0f, -0.05f * y_fac);
immVertex2f(shdr_pos, 0.0f, -0.05f * y_fac);
immVertex2f(shdr_pos, -0.05f * x_fac, 0.0f);
immEnd();
immUniform4f("color", 1.0f, 1.0f, 1.0f, 1.0f);
immUniform4f("color2", 0.0f, 0.0f, 0.0f, 1.0f);
immUniform1f("dash_width", 2.0f);
immUniform1f("udash_factor", 0.5f);
immBegin(GPU_PRIM_LINES, 8);
immVertex2f(shdr_pos, -0.020f * x_fac, 0.0f);
immVertex2f(shdr_pos, -0.1f * x_fac, 0.0f);
immVertex2f(shdr_pos, 0.1f * x_fac, 0.0f);
immVertex2f(shdr_pos, 0.020f * x_fac, 0.0f);
immVertex2f(shdr_pos, 0.0f, -0.020f * y_fac);
immVertex2f(shdr_pos, 0.0f, -0.1f * y_fac);
immVertex2f(shdr_pos, 0.0f, 0.1f * y_fac);
immVertex2f(shdr_pos, 0.0f, 0.020f * y_fac);
immEnd();
immUnbindProgram();
GPU_matrix_translate_2f(-cursor[0], -cursor[1]);
}
} // namespace blender

View File

@@ -0,0 +1,215 @@
/* SPDX-FileCopyrightText: 2008 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eduv
*/
#pragma once
#include "BKE_customdata.hh"
namespace blender {
struct BMVert;
struct BMEdge;
struct BMFace;
struct BMLoop;
struct Object;
struct Scene;
struct SpaceImage;
struct ToolSettings;
struct wmOperatorType;
struct View2D;
/* find nearest */
struct UvNearestHit {
/** Only for `*_multi(..)` versions of functions. */
Object *ob;
/** Always set if we have a hit. */
BMFace *efa;
BMLoop *l;
/**
* Needs to be set before calling nearest functions.
*
* \note When #uv_nearest_hit_init_dist_px or #uv_nearest_hit_init_max are used,
* this value is pixels squared.
*/
float dist_sq;
/** Scale the UVs to account for aspect ratio from the image view. */
float scale[2];
};
UvNearestHit uv_nearest_hit_init_dist_px(const View2D *v2d, float dist_px);
UvNearestHit uv_nearest_hit_init_max(const View2D *v2d);
UvNearestHit uv_nearest_hit_init_max_default();
/**
* A utility to set the vertex or edge in #UvNearestHit, useful when face-select
* is used as a fallback, but the caller expects to be able to access an element
* that would be "picked" based on the current selection mode.
*
* - Does nothing when `uv_selectmode` is #UV_SELECT_FACE.
* - Only call this when #UvNearestHit::efa has been set.
*/
void uv_nearest_hit_elem_set_from_face(const float co[2], UvNearestHit *hit, short uv_selectmode);
bool uv_find_nearest_vert(
Scene *scene, Object *obedit, const float co[2], float penalty_dist, UvNearestHit *hit);
bool uv_find_nearest_vert_multi(Scene *scene,
Span<Object *> objects,
const float co[2],
float penalty_dist,
UvNearestHit *hit);
bool uv_find_nearest_edge(
Scene *scene, Object *obedit, const float co[2], float penalty, UvNearestHit *hit);
bool uv_find_nearest_edge_multi(
Scene *scene, Span<Object *> objects, const float co[2], float penalty, UvNearestHit *hit);
/**
* \param only_in_face: when true, only hit faces which `co` is inside.
* This gives users a result they might expect, especially when zoomed in.
*
* \note Concave faces can cause odd behavior, although in practice this isn't often an issue.
* The center can be outside the face, in this case the distance to the center
* could cause the face to be considered too far away.
* If this becomes an issue we could track the distance to the faces closest edge.
*/
bool uv_find_nearest_face_ex(
Scene *scene, Object *obedit, const float co[2], UvNearestHit *hit, bool only_in_face);
bool uv_find_nearest_face(Scene *scene, Object *obedit, const float co[2], UvNearestHit *hit);
bool uv_find_nearest_face_multi_ex(
Scene *scene, Span<Object *> objects, const float co[2], UvNearestHit *hit, bool only_in_face);
bool uv_find_nearest_face_multi(Scene *scene,
Span<Object *> objects,
const float co[2],
UvNearestHit *hit);
BMLoop *uv_find_nearest_loop_from_vert(Scene *scene, Object *obedit, BMVert *v, const float co[2]);
BMLoop *uv_find_nearest_loop_from_edge(Scene *scene, Object *obedit, BMEdge *e, const float co[2]);
bool uvedit_vert_is_edge_select_any_other(const ToolSettings *ts,
const BMesh *bm,
const BMLoop *l,
const BMUVOffsets &offsets);
bool uvedit_vert_is_face_select_any_other(const ToolSettings *ts,
const BMesh *bm,
const BMLoop *l,
const BMUVOffsets &offsets);
bool uvedit_edge_is_face_select_any_other(const ToolSettings *ts,
const BMesh *bm,
const BMLoop *l,
const BMUVOffsets &offsets);
bool uvedit_vert_is_all_other_faces_selected(const ToolSettings *ts,
const BMesh *bm,
const BMLoop *l,
const BMUVOffsets &offsets);
[[nodiscard]] bool uvedit_vert_select_get_no_sync(const ToolSettings *ts,
const BMesh *bm,
const BMLoop *l);
[[nodiscard]] bool uvedit_edge_select_get_no_sync(const ToolSettings *ts,
const BMesh *bm,
const BMLoop *l);
[[nodiscard]] bool uvedit_face_select_get_no_sync(const ToolSettings *ts,
const BMesh *bm,
const BMFace *f);
void uvedit_vert_select_set_no_sync(const ToolSettings *ts,
const BMesh *bm,
BMLoop *l,
bool select);
void uvedit_edge_select_set_no_sync(const ToolSettings *ts,
const BMesh *bm,
BMLoop *l,
bool select);
void uvedit_face_select_set_no_sync(const ToolSettings *ts,
const BMesh *bm,
BMFace *f,
bool select);
/* utility tool functions */
void uvedit_live_unwrap_update(SpaceImage *sima, Scene *scene, Object *obedit);
/* operators */
void UV_OT_average_islands_scale(wmOperatorType *ot);
void UV_OT_cube_project(wmOperatorType *ot);
void UV_OT_cylinder_project(wmOperatorType *ot);
void UV_OT_project_from_view(wmOperatorType *ot);
void UV_OT_minimize_stretch(wmOperatorType *ot);
void UV_OT_pack_islands(wmOperatorType *ot);
void UV_OT_reset(wmOperatorType *ot);
void UV_OT_sphere_project(wmOperatorType *ot);
void UV_OT_unwrap(wmOperatorType *ot);
void UV_OT_rip(wmOperatorType *ot);
void UV_OT_stitch(wmOperatorType *ot);
void UV_OT_smart_project(wmOperatorType *ot);
void UV_OT_copy_mirrored_faces(wmOperatorType *ot);
/* uvedit_copy_paste.cc */
void UV_OT_copy(wmOperatorType *ot);
void UV_OT_paste(wmOperatorType *ot);
/* `uvedit_path.cc` */
void UV_OT_shortest_path_pick(wmOperatorType *ot);
void UV_OT_shortest_path_select(wmOperatorType *ot);
/* `uvedit_select.cc` */
void uvedit_select_prepare_custom_data(const Scene *scene, BMesh *bm);
void uvedit_select_prepare_sync_select(const Scene *scene, BMesh *bm);
void uvedit_select_prepare(const Scene *scene, BMesh *bm);
bool uvedit_select_is_any_selected(const Scene *scene, BMesh *bm);
bool uvedit_select_is_any_selected_multi(const Scene *scene, Span<Object *> objects);
/**
* \warning This returns first selected UV,
* not ideal in many cases since there could be multiple.
*/
const float *uvedit_first_selected_uv_from_vertex(Scene *scene,
const BMesh *bm,
BMVert *eve,
const BMUVOffsets &offsets);
void UV_OT_select_all(wmOperatorType *ot);
void UV_OT_select(wmOperatorType *ot);
void UV_OT_select_loop(wmOperatorType *ot);
void UV_OT_select_edge_ring(wmOperatorType *ot);
void UV_OT_select_linked(wmOperatorType *ot);
void UV_OT_select_linked_pick(wmOperatorType *ot);
void UV_OT_select_split(wmOperatorType *ot);
void UV_OT_select_pinned(wmOperatorType *ot);
void UV_OT_select_box(wmOperatorType *ot);
void UV_OT_select_lasso(wmOperatorType *ot);
void UV_OT_select_circle(wmOperatorType *ot);
void UV_OT_select_more(wmOperatorType *ot);
void UV_OT_select_less(wmOperatorType *ot);
void UV_OT_select_overlap(wmOperatorType *ot);
void UV_OT_select_by_winding(wmOperatorType *ot);
void UV_OT_select_similar(wmOperatorType *ot);
void UV_OT_select_tile(wmOperatorType *ot);
void UV_OT_custom_region_set(wmOperatorType *ot);
/* Used only when UV sync select is disabled. */
void UV_OT_select_mode(wmOperatorType *ot);
/**
* Stitch the selected UV islands together for the unwrap "Original Bounds" option.
* Uses a fixed configuration: vertex mode, snap islands, only selected UVs,
* ignore fully seam-bounded islands, no distance limit, and no draw preview.
*
* \return false if stitching could not be initialized.
*/
bool uv_stitch_selected_islands_for_original_bounds(const Scene *scene, Span<Object *> objects);
} // namespace blender

View File

@@ -0,0 +1,176 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eduv
*
* Utilities for manipulating UV islands.
*
* \note This is similar to `GEO_uv_parametrizer.hh`,
* however the data structures there don't support arbitrary topology
* such as an edge with 3 or more faces using it.
* This API uses #BMesh data structures and doesn't have limitations for manifold meshes.
*/
#include "BLI_listbase.h"
#include "BLI_math_vector.h"
#include "BKE_editmesh.hh"
#include "DNA_image_types.h"
#include "ED_uvedit.hh" /* Own include. */
namespace blender {
/* -------------------------------------------------------------------- */
/** \name UDIM packing helper functions
* \{ */
bool uv_coords_isect_udim(const Image *image, const int udim_grid[2], const float coords[2])
{
const float coords_floor[2] = {floorf(coords[0]), floorf(coords[1])};
const bool is_tiled_image = image && (image->source == IMA_SRC_TILED);
if (coords[0] < udim_grid[0] && coords[0] > 0 && coords[1] < udim_grid[1] && coords[1] > 0) {
return true;
}
/* Check if selection lies on a valid UDIM image tile. */
if (is_tiled_image) {
for (const ImageTile &tile : image->tiles) {
const int tile_index = tile.tile_number - 1001;
const int target_x = (tile_index % 10);
const int target_y = (tile_index / 10);
if (coords_floor[0] == target_x && coords_floor[1] == target_y) {
return true;
}
}
}
/* Probably not required since UDIM grid checks for 1001. */
else if (image && !is_tiled_image) {
if (is_zero_v2(coords_floor)) {
return true;
}
}
return false;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Calculate UV Islands
* \{ */
struct SharedUVLoopData {
BMUVOffsets offsets;
bool use_seams;
};
static bool bm_loop_uv_shared_edge_check(const BMLoop *l_a, const BMLoop *l_b, void *user_data)
{
const SharedUVLoopData *data = static_cast<const SharedUVLoopData *>(user_data);
if (data->use_seams) {
if (BM_elem_flag_test(l_a->e, BM_ELEM_SEAM)) {
return false;
}
}
return BM_loop_uv_share_edge_check(
const_cast<BMLoop *>(l_a), const_cast<BMLoop *>(l_b), data->offsets.uv);
}
/**
* Returns true if `efa` is able to be affected by a packing operation, given various parameters.
*
* Checks if it's (not) hidden, and optionally selected, and/or UV selected.
*
* Will eventually be superseded by `BM_uv_element_map_create()`.
*
* Loosely based on `uvedit_is_face_affected`, but "bug-compatible" with previous code.
*/
static bool uvedit_is_face_affected_for_calc_uv_islands(const Scene *scene,
const BMesh *bm,
BMFace *efa,
const bool only_selected_faces,
const bool only_selected_uvs)
{
if (BM_elem_flag_test(efa, BM_ELEM_HIDDEN)) {
return false;
}
if (only_selected_faces) {
if (only_selected_uvs) {
return BM_elem_flag_test(efa, BM_ELEM_SELECT) && uvedit_face_select_test(scene, bm, efa);
}
return BM_elem_flag_test(efa, BM_ELEM_SELECT);
}
return true;
}
int bm_mesh_calc_uv_islands(const Scene *scene,
BMesh *bm,
ListBaseT<FaceIsland> *island_list,
const bool only_selected_faces,
const bool only_selected_uvs,
const bool use_seams,
const float aspect_y,
const BMUVOffsets &uv_offsets)
{
BLI_assert(uv_offsets.uv >= 0);
int island_added = 0;
BM_mesh_elem_table_ensure(bm, BM_FACE);
int *groups_array = MEM_new_array_uninitialized<int>(bm->totface, __func__);
int (*group_index)[2];
/* Set the tag for `BM_mesh_calc_face_groups`. */
BMFace *f;
BMIter iter;
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
const bool face_affected = uvedit_is_face_affected_for_calc_uv_islands(
scene, bm, f, only_selected_faces, only_selected_uvs);
BM_elem_flag_set(f, BM_ELEM_TAG, face_affected);
}
SharedUVLoopData user_data = {{0}};
user_data.offsets = uv_offsets;
user_data.use_seams = use_seams;
const int group_len = BM_mesh_calc_face_groups(bm,
groups_array,
&group_index,
nullptr,
bm_loop_uv_shared_edge_check,
&user_data,
BM_ELEM_TAG,
BM_EDGE);
for (int i = 0; i < group_len; i++) {
const int faces_start = group_index[i][0];
const int faces_len = group_index[i][1];
BMFace **faces = MEM_new_array_uninitialized<BMFace *>(faces_len, __func__);
for (int j = 0; j < faces_len; j++) {
faces[j] = BM_face_at_index(bm, groups_array[faces_start + j]);
}
FaceIsland *island = MEM_new_zeroed<FaceIsland>(__func__);
island->faces = faces;
island->faces_len = faces_len;
island->offsets = uv_offsets;
island->aspect_y = aspect_y;
BLI_addtail(island_list, island);
island_added += 1;
}
MEM_delete(groups_array);
MEM_delete(group_index);
return island_added;
}
/** \} */
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,897 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup eduv
*
* \note The logic in this file closely follows `editmesh_path.cc`.
*/
#include <cstdlib>
#include <cstring>
#include "BLI_linklist.h"
#include "BLI_math_vector.h"
#include "BLI_utildefines.h"
#include "MEM_guardedalloc.h"
#include "DNA_mesh_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_windowmanager_types.h"
#include "BKE_context.hh"
#include "BKE_customdata.hh"
#include "BKE_editmesh.hh"
#include "BKE_layer.hh"
#include "BKE_mesh.hh"
#include "BKE_report.hh"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_query.hh"
#include "ED_object.hh"
#include "ED_screen.hh"
#include "ED_uvedit.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "UI_view2d.hh"
#include "intern/bmesh_marking.hh"
#include "uvedit_intern.hh"
#include "bmesh_tools.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Path Select Struct & Properties
* \{ */
namespace {
struct PathSelectParams {
/** ensure the active element is the last selected item (handy for picking) */
bool track_active;
bool use_topology_distance;
bool use_face_step;
bool use_fill;
CheckerIntervalParams interval_params;
};
struct UserData_UV {
Scene *scene;
BMesh *bm;
BMUVOffsets offsets;
};
} // namespace
static void path_select_properties(wmOperatorType *ot)
{
RNA_def_boolean(ot->srna,
"use_face_step",
false,
"Face Stepping",
"Traverse connected faces (includes diagonals and edge-rings)");
RNA_def_boolean(ot->srna,
"use_topology_distance",
false,
"Topology Distance",
"Find the minimum number of steps, ignoring spatial distance");
RNA_def_boolean(ot->srna,
"use_fill",
false,
"Fill Region",
"Select all paths between the source/destination elements");
WM_operator_properties_checker_interval(ot, true);
}
static void path_select_params_from_op(wmOperator *op, PathSelectParams *op_params)
{
op_params->track_active = false;
op_params->use_face_step = RNA_boolean_get(op->ptr, "use_face_step");
op_params->use_fill = RNA_boolean_get(op->ptr, "use_fill");
op_params->use_topology_distance = RNA_boolean_get(op->ptr, "use_topology_distance");
WM_operator_properties_checker_interval_from_op(op, &op_params->interval_params);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name UV Vert Path
* \{ */
/* callbacks */
static bool verttag_filter_cb(BMLoop *l, void *user_data_v)
{
UserData_UV *user_data = static_cast<UserData_UV *>(user_data_v);
return uvedit_face_visible_test(user_data->scene, l->f);
}
static bool verttag_test_cb(BMLoop *l, void *user_data_v)
{
/* All connected loops are selected or we return false. */
UserData_UV *user_data = static_cast<UserData_UV *>(user_data_v);
const Scene *scene = user_data->scene;
const int cd_loop_uv_offset = user_data->offsets.uv;
const float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
BMIter iter;
BMLoop *l_iter;
BM_ITER_ELEM (l_iter, &iter, l->v, BM_LOOPS_OF_VERT) {
if (verttag_filter_cb(l_iter, user_data)) {
const float *luv_iter = BM_ELEM_CD_GET_FLOAT_P(l_iter, cd_loop_uv_offset);
if (equals_v2v2(luv, luv_iter)) {
if (!uvedit_uv_select_test(scene, user_data->bm, l_iter, user_data->offsets)) {
return false;
}
}
}
}
return true;
}
static void verttag_set_cb(BMLoop *l, bool val, void *user_data_v)
{
UserData_UV *user_data = static_cast<UserData_UV *>(user_data_v);
const Scene *scene = user_data->scene;
BMesh *bm = user_data->bm;
const uint cd_loop_uv_offset = user_data->offsets.uv;
const float *luv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
BMIter iter;
BMLoop *l_iter;
BM_ITER_ELEM (l_iter, &iter, l->v, BM_LOOPS_OF_VERT) {
if (verttag_filter_cb(l_iter, user_data)) {
const float *luv_iter = BM_ELEM_CD_GET_FLOAT_P(l_iter, cd_loop_uv_offset);
if (equals_v2v2(luv, luv_iter)) {
uvedit_uv_select_set(scene, bm, l_iter, val);
}
}
}
}
static int mouse_mesh_uv_shortest_path_vert(Scene *scene,
Object *obedit,
const PathSelectParams *op_params,
BMLoop *l_src,
BMLoop *l_dst,
const float aspect_y,
const BMUVOffsets &offsets)
{
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BMesh *bm = em->bm;
int flush = 0;
UserData_UV user_data = {};
user_data.scene = scene;
user_data.bm = bm;
user_data.offsets = offsets;
BMCalcPathUVParams params{};
params.use_topology_distance = op_params->use_topology_distance;
params.use_step_face = op_params->use_face_step;
params.aspect_y = aspect_y;
params.cd_loop_uv_offset = offsets.uv;
LinkNode *path = nullptr;
bool is_path_ordered = false;
if (l_src != l_dst) {
if (op_params->use_fill) {
path = BM_mesh_calc_path_uv_region_vert(bm,
reinterpret_cast<BMElem *>(l_src),
reinterpret_cast<BMElem *>(l_dst),
params.cd_loop_uv_offset,
verttag_filter_cb,
&user_data);
}
else {
is_path_ordered = true;
path = BM_mesh_calc_path_uv_vert(bm, l_src, l_dst, &params, verttag_filter_cb, &user_data);
}
}
BMLoop *l_dst_last = l_dst;
if (path) {
/* toggle the flag */
bool all_set = true;
LinkNode *node = path;
do {
if (!verttag_test_cb(static_cast<BMLoop *>(node->link), &user_data)) {
all_set = false;
break;
}
} while ((node = node->next));
int depth = -1;
node = path;
do {
if ((is_path_ordered == false) ||
WM_operator_properties_checker_interval_test(&op_params->interval_params, depth))
{
verttag_set_cb(static_cast<BMLoop *>(node->link), !all_set, &user_data);
if (is_path_ordered) {
l_dst_last = static_cast<BMLoop *>(node->link);
}
}
} while ((void)depth++, (node = node->next));
BLI_linklist_free(path, nullptr);
flush = all_set ? -1 : 1;
}
else {
const bool is_act = !verttag_test_cb(l_dst, &user_data);
verttag_set_cb(l_dst, is_act, &user_data); /* switch the face option */
}
if (op_params->track_active) {
ED_uvedit_active_vert_loop_set(bm, l_dst_last);
}
return flush;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name UV Edge Path
* \{ */
/* callbacks */
static bool edgetag_filter_cb(BMLoop *l, void *user_data_v)
{
UserData_UV *user_data = static_cast<UserData_UV *>(user_data_v);
return uvedit_face_visible_test(user_data->scene, l->f);
}
static bool edgetag_test_cb(BMLoop *l, void *user_data_v)
{
/* All connected loops (UV) are selected or we return false. */
UserData_UV *user_data = static_cast<UserData_UV *>(user_data_v);
const Scene *scene = user_data->scene;
BMIter iter;
BMLoop *l_iter;
BM_ITER_ELEM (l_iter, &iter, l->e, BM_LOOPS_OF_EDGE) {
if (edgetag_filter_cb(l_iter, user_data)) {
if (BM_loop_uv_share_edge_check(l, l_iter, user_data->offsets.uv)) {
if (!uvedit_edge_select_test(scene, user_data->bm, l_iter, user_data->offsets)) {
return false;
}
}
}
}
return true;
}
static void edgetag_set_cb(BMLoop *l, bool val, void *user_data_v)
{
UserData_UV *user_data = static_cast<UserData_UV *>(user_data_v);
const Scene *scene = user_data->scene;
BMesh *bm = user_data->bm;
uvedit_edge_select_set_with_sticky(scene, bm, l, val, user_data->offsets);
}
static int mouse_mesh_uv_shortest_path_edge(Scene *scene,
Object *obedit,
const PathSelectParams *op_params,
BMLoop *l_src,
BMLoop *l_dst,
const float aspect_y,
const BMUVOffsets &offsets)
{
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BMesh *bm = em->bm;
int flush = 0;
UserData_UV user_data = {};
user_data.scene = scene;
user_data.bm = bm;
user_data.offsets = offsets;
BMCalcPathUVParams params = {};
params.use_topology_distance = op_params->use_topology_distance;
params.use_step_face = op_params->use_face_step;
params.aspect_y = aspect_y;
params.cd_loop_uv_offset = offsets.uv;
LinkNode *path = nullptr;
bool is_path_ordered = false;
if (l_src != l_dst) {
if (op_params->use_fill) {
path = BM_mesh_calc_path_uv_region_edge(bm,
reinterpret_cast<BMElem *>(l_src),
reinterpret_cast<BMElem *>(l_dst),
params.cd_loop_uv_offset,
edgetag_filter_cb,
&user_data);
}
else {
is_path_ordered = true;
path = BM_mesh_calc_path_uv_edge(bm, l_src, l_dst, &params, edgetag_filter_cb, &user_data);
}
}
BMLoop *l_dst_last = l_dst;
if (path) {
/* toggle the flag */
bool all_set = true;
LinkNode *node = path;
do {
if (!edgetag_test_cb(static_cast<BMLoop *>(node->link), &user_data)) {
all_set = false;
break;
}
} while ((node = node->next));
int depth = -1;
node = path;
do {
if ((is_path_ordered == false) ||
WM_operator_properties_checker_interval_test(&op_params->interval_params, depth))
{
edgetag_set_cb(static_cast<BMLoop *>(node->link), !all_set, &user_data);
if (is_path_ordered) {
l_dst_last = static_cast<BMLoop *>(node->link);
}
}
} while ((void)depth++, (node = node->next));
BLI_linklist_free(path, nullptr);
flush = all_set ? -1 : 1;
}
else {
const bool is_act = !edgetag_test_cb(l_dst, &user_data);
edgetag_set_cb(l_dst, is_act, &user_data); /* switch the face option */
}
if (op_params->track_active) {
ED_uvedit_active_edge_loop_set(bm, l_dst_last);
}
return flush;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name UV Face Path
* \{ */
/* callbacks */
static bool facetag_filter_cb(BMFace *f, void *user_data_v)
{
UserData_UV *user_data = static_cast<UserData_UV *>(user_data_v);
return uvedit_face_visible_test(user_data->scene, f);
}
static bool facetag_test_cb(BMFace *f, void *user_data_v)
{
/* All connected loops are selected or we return false. */
UserData_UV *user_data = static_cast<UserData_UV *>(user_data_v);
const Scene *scene = user_data->scene;
BMIter iter;
BMLoop *l_iter;
BM_ITER_ELEM (l_iter, &iter, f, BM_LOOPS_OF_FACE) {
if (!uvedit_edge_select_test(scene, user_data->bm, l_iter, user_data->offsets)) {
return false;
}
}
return true;
}
static void facetag_set_cb(BMFace *f, bool val, void *user_data_v)
{
UserData_UV *user_data = static_cast<UserData_UV *>(user_data_v);
const Scene *scene = user_data->scene;
BMesh *bm = user_data->bm;
uvedit_face_select_set_with_sticky(scene, bm, f, val, user_data->offsets);
}
static int mouse_mesh_uv_shortest_path_face(Scene *scene,
Object *obedit,
const PathSelectParams *op_params,
BMFace *f_src,
BMFace *f_dst,
const float aspect_y,
const BMUVOffsets &offsets)
{
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BMesh *bm = em->bm;
int flush = 0;
UserData_UV user_data = {};
user_data.scene = scene;
user_data.bm = bm;
user_data.offsets = offsets;
BMCalcPathUVParams params = {};
params.use_topology_distance = op_params->use_topology_distance;
params.use_step_face = op_params->use_face_step;
params.aspect_y = aspect_y;
params.cd_loop_uv_offset = offsets.uv;
LinkNode *path = nullptr;
bool is_path_ordered = false;
if (f_src != f_dst) {
if (op_params->use_fill) {
path = BM_mesh_calc_path_uv_region_face(bm,
reinterpret_cast<BMElem *>(f_src),
reinterpret_cast<BMElem *>(f_dst),
params.cd_loop_uv_offset,
facetag_filter_cb,
&user_data);
}
else {
is_path_ordered = true;
path = BM_mesh_calc_path_uv_face(bm, f_src, f_dst, &params, facetag_filter_cb, &user_data);
}
}
BMFace *f_dst_last = f_dst;
if (path) {
/* toggle the flag */
bool all_set = true;
LinkNode *node = path;
do {
if (!facetag_test_cb(static_cast<BMFace *>(node->link), &user_data)) {
all_set = false;
break;
}
} while ((node = node->next));
int depth = -1;
node = path;
do {
if ((is_path_ordered == false) ||
WM_operator_properties_checker_interval_test(&op_params->interval_params, depth))
{
facetag_set_cb(static_cast<BMFace *>(node->link), !all_set, &user_data);
if (is_path_ordered) {
f_dst_last = static_cast<BMFace *>(node->link);
}
}
} while ((void)depth++, (node = node->next));
BLI_linklist_free(path, nullptr);
flush = all_set ? -1 : 1;
}
else {
const bool is_act = !facetag_test_cb(f_dst, &user_data);
facetag_set_cb(f_dst, is_act, &user_data); /* switch the face option */
}
if (op_params->track_active) {
/* Unlike other types, we can track active without it being selected. */
BM_mesh_active_face_set(bm, f_dst_last);
}
return flush;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Main Operator for vert/edge/face tag
* \{ */
static wmOperatorStatus uv_shortest_path_pick_exec(bContext *C, wmOperator *op);
static bool uv_shortest_path_pick_ex(Scene *scene,
Depsgraph *depsgraph,
Object *obedit,
const PathSelectParams *op_params,
BMElem *ele_src,
BMElem *ele_dst,
const float aspect_y,
const BMUVOffsets &offsets)
{
const ToolSettings *ts = scene->toolsettings;
const char uv_selectmode = ED_uvedit_select_mode_get(scene);
bool ok = false;
int flush = 0;
if (ELEM(nullptr, ele_src, ele_dst) || (ele_src->head.htype != ele_dst->head.htype)) {
/* pass */
}
else if (ele_src->head.htype == BM_FACE) {
flush = mouse_mesh_uv_shortest_path_face(scene,
obedit,
op_params,
reinterpret_cast<BMFace *>(ele_src),
reinterpret_cast<BMFace *>(ele_dst),
aspect_y,
offsets);
ok = true;
}
else if (ele_src->head.htype == BM_LOOP) {
if (uv_selectmode & UV_SELECT_EDGE) {
flush = mouse_mesh_uv_shortest_path_edge(scene,
obedit,
op_params,
reinterpret_cast<BMLoop *>(ele_src),
reinterpret_cast<BMLoop *>(ele_dst),
aspect_y,
offsets);
}
else {
flush = mouse_mesh_uv_shortest_path_vert(scene,
obedit,
op_params,
reinterpret_cast<BMLoop *>(ele_src),
reinterpret_cast<BMLoop *>(ele_dst),
aspect_y,
offsets);
}
ok = true;
}
if (ok) {
if (flush != 0) {
const bool select = (flush == 1);
BMesh *bm = BKE_editmesh_from_object(obedit)->bm;
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
ED_uvedit_select_sync_flush(scene->toolsettings, bm, select);
}
else {
ED_uvedit_selectmode_flush(scene, bm);
}
}
if (ts->uv_flag & UV_FLAG_SELECT_SYNC) {
DEG_id_tag_update(obedit->data, ID_RECALC_SELECT);
}
else {
Object *obedit_eval = DEG_get_evaluated(depsgraph, obedit);
BKE_mesh_batch_cache_dirty_tag(id_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);
}
return ok;
}
static wmOperatorStatus uv_shortest_path_pick_invoke(bContext *C,
wmOperator *op,
const wmEvent *event)
{
const Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
const ToolSettings *ts = scene->toolsettings;
const char uv_selectmode = ED_uvedit_select_mode_get(scene);
/* We could support this, it needs further testing. */
if (RNA_struct_property_is_set(op->ptr, "index")) {
return uv_shortest_path_pick_exec(C, op);
}
PathSelectParams op_params;
path_select_params_from_op(op, &op_params);
/* Set false if we support edge tagging. */
op_params.track_active = true;
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
Vector<Object *> objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs(
*bmain, scene, view_layer, nullptr);
float co[2];
const ARegion *region = CTX_wm_region(C);
ui::view2d_region_to_view(&region->v2d, event->mval[0], event->mval[1], &co[0], &co[1]);
BMElem *ele_src = nullptr, *ele_dst = nullptr;
/* Detect the hit. */
UvNearestHit hit = uv_nearest_hit_init_max(&region->v2d);
bool hit_found = false;
if (uv_selectmode == UV_SELECT_FACE) {
if (uv_find_nearest_face_multi(scene, objects, co, &hit)) {
hit_found = true;
}
}
else if (uv_selectmode & UV_SELECT_EDGE) {
if (uv_find_nearest_edge_multi(scene, objects, co, 0.0f, &hit)) {
hit_found = true;
}
}
else {
if (uv_find_nearest_vert_multi(scene, objects, co, 0.0f, &hit)) {
hit_found = true;
}
}
bool changed = false;
if (hit_found) {
/* This may not be the active object. */
Object *obedit = hit.ob;
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BMesh *bm = em->bm;
const BMUVOffsets offsets = BM_uv_map_offsets_get(bm);
/* Respond to the hit. */
if (uv_selectmode == UV_SELECT_FACE) {
/* Face selection. */
BMFace *f_src = BM_mesh_active_face_get(bm, false, false);
/* Check selection? */
ele_src = reinterpret_cast<BMElem *>(f_src);
ele_dst = reinterpret_cast<BMElem *>(hit.efa);
}
else if (uv_selectmode & UV_SELECT_EDGE) {
/* Edge selection. */
BMLoop *l_src = nullptr;
if ((ts->uv_flag & UV_FLAG_SELECT_SYNC) && (bm->uv_select_sync_valid == false)) {
BMEdge *e_src = BM_mesh_active_edge_get(bm);
if (e_src != nullptr) {
l_src = uv_find_nearest_loop_from_edge(scene, obedit, e_src, co);
}
}
else {
l_src = ED_uvedit_active_edge_loop_get(ts, bm);
if (l_src != nullptr) {
if (!uvedit_uv_select_test(scene, bm, l_src, offsets) &&
!uvedit_uv_select_test(scene, bm, l_src->next, offsets))
{
l_src = nullptr;
}
ele_src = reinterpret_cast<BMElem *>(l_src);
}
}
ele_src = reinterpret_cast<BMElem *>(l_src);
ele_dst = reinterpret_cast<BMElem *>(hit.l);
}
else {
/* Vertex selection. */
BMLoop *l_src = nullptr;
if ((ts->uv_flag & UV_FLAG_SELECT_SYNC) && (bm->uv_select_sync_valid == false)) {
BMVert *v_src = BM_mesh_active_vert_get(bm);
if (v_src != nullptr) {
l_src = uv_find_nearest_loop_from_vert(scene, obedit, v_src, co);
}
}
else {
l_src = ED_uvedit_active_vert_loop_get(ts, bm);
if (l_src != nullptr) {
if (!uvedit_uv_select_test(scene, bm, l_src, offsets)) {
l_src = nullptr;
}
}
}
ele_src = reinterpret_cast<BMElem *>(l_src);
ele_dst = reinterpret_cast<BMElem *>(hit.l);
}
if (ele_src && ele_dst) {
/* Always use the active object, not `obedit` as the active defines the UV display. */
const float aspect_y = ED_uvedit_get_aspect_y(CTX_data_edit_object(C));
uv_shortest_path_pick_ex(
scene, depsgraph, obedit, &op_params, ele_src, ele_dst, aspect_y, offsets);
/* Store the object and it's index so redo is possible. */
int index;
if (uv_selectmode & UV_SELECT_FACE) {
BM_mesh_elem_index_ensure(bm, BM_FACE);
index = BM_elem_index_get(ele_dst);
}
else if (uv_selectmode & UV_SELECT_EDGE) {
BM_mesh_elem_index_ensure(bm, BM_LOOP);
index = BM_elem_index_get(ele_dst);
}
else {
BM_mesh_elem_index_ensure(bm, BM_LOOP);
index = BM_elem_index_get(ele_dst);
}
const int object_index = ed::object::object_in_mode_to_index(
*bmain, scene, view_layer, OB_MODE_EDIT, obedit);
BLI_assert(object_index != -1);
RNA_int_set(op->ptr, "object_index", object_index);
RNA_int_set(op->ptr, "index", index);
changed = true;
}
}
return changed ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
}
static wmOperatorStatus uv_shortest_path_pick_exec(bContext *C, wmOperator *op)
{
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
const Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
const ToolSettings *ts = scene->toolsettings;
ViewLayer *view_layer = CTX_data_view_layer(C);
const char uv_selectmode = ED_uvedit_select_mode_get(scene);
const int object_index = RNA_int_get(op->ptr, "object_index");
const int index = RNA_int_get(op->ptr, "index");
if (object_index == -1) {
return OPERATOR_CANCELLED;
}
Object *obedit = ed::object::object_in_mode_from_index(
*bmain, scene, view_layer, OB_MODE_EDIT, object_index);
if (obedit == nullptr) {
return OPERATOR_CANCELLED;
}
BMEditMesh *em = BKE_editmesh_from_object(obedit);
BMesh *bm = em->bm;
const BMUVOffsets offsets = BM_uv_map_offsets_get(bm);
BMElem *ele_src, *ele_dst;
/* NOLINTBEGIN: bugprone-assignment-in-if-condition */
if (uv_selectmode & UV_SELECT_FACE) {
if (index < 0 || index >= bm->totface) {
return OPERATOR_CANCELLED;
}
if (!(ele_src = reinterpret_cast<BMElem *>(BM_mesh_active_face_get(bm, false, false))) ||
!(ele_dst = reinterpret_cast<BMElem *>(BM_face_at_index_find_or_table(bm, index))))
{
return OPERATOR_CANCELLED;
}
}
else if (uv_selectmode & UV_SELECT_EDGE) {
if (index < 0 || index >= bm->totloop) {
return OPERATOR_CANCELLED;
}
if (!(ele_src = reinterpret_cast<BMElem *>(ED_uvedit_active_edge_loop_get(ts, bm))) ||
!(ele_dst = reinterpret_cast<BMElem *>(BM_loop_at_index_find(bm, index))))
{
return OPERATOR_CANCELLED;
}
}
else {
if (index < 0 || index >= bm->totloop) {
return OPERATOR_CANCELLED;
}
if (!(ele_src = reinterpret_cast<BMElem *>(ED_uvedit_active_vert_loop_get(ts, bm))) ||
!(ele_dst = reinterpret_cast<BMElem *>(BM_loop_at_index_find(bm, index))))
{
return OPERATOR_CANCELLED;
}
}
/* NOLINTEND: bugprone-assignment-in-if-condition */
/* Always use the active object, not `obedit` as the active defines the UV display. */
const float aspect_y = ED_uvedit_get_aspect_y(CTX_data_edit_object(C));
PathSelectParams op_params;
path_select_params_from_op(op, &op_params);
op_params.track_active = true;
if (!uv_shortest_path_pick_ex(
scene, depsgraph, obedit, &op_params, ele_src, ele_dst, aspect_y, offsets))
{
return OPERATOR_CANCELLED;
}
return OPERATOR_FINISHED;
}
void UV_OT_shortest_path_pick(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Pick Shortest Path";
ot->idname = "UV_OT_shortest_path_pick";
ot->description = "Select shortest path between two selections";
/* API callbacks. */
ot->invoke = uv_shortest_path_pick_invoke;
ot->exec = uv_shortest_path_pick_exec;
ot->poll = ED_operator_uvedit_space_image;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
path_select_properties(ot);
/* use for redo */
prop = RNA_def_int(ot->srna, "object_index", -1, -1, INT_MAX, "", "", 0, INT_MAX);
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
prop = RNA_def_int(ot->srna, "index", -1, -1, INT_MAX, "", "", 0, INT_MAX);
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select Path Between Existing Selection
* \{ */
static wmOperatorStatus uv_shortest_path_select_exec(bContext *C, wmOperator *op)
{
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
const Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
const char uv_selectmode = ED_uvedit_select_mode_get(scene);
bool found_valid_elements = false;
const float aspect_y = ED_uvedit_get_aspect_y(CTX_data_edit_object(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(
*bmain, scene, view_layer, nullptr);
for (Object *obedit : objects) {
BMesh *bm = BKE_editmesh_from_object(obedit)->bm;
const BMUVOffsets offsets = BM_uv_map_offsets_get(bm);
BMElem *ele_src = nullptr, *ele_dst = nullptr;
/* Find 2x elements. */
{
BMElem **ele_array = nullptr;
int ele_array_len = 0;
if (uv_selectmode & UV_SELECT_FACE) {
ele_array = reinterpret_cast<BMElem **>(
ED_uvedit_selected_faces(scene, bm, 3, &ele_array_len));
}
else if (uv_selectmode & UV_SELECT_EDGE) {
ele_array = reinterpret_cast<BMElem **>(
ED_uvedit_selected_edges(scene, bm, 3, &ele_array_len));
}
else {
ele_array = reinterpret_cast<BMElem **>(
ED_uvedit_selected_verts(scene, bm, 3, &ele_array_len));
}
if (ele_array_len == 2) {
ele_src = ele_array[0];
ele_dst = ele_array[1];
}
MEM_delete(ele_array);
}
if (ele_src && ele_dst) {
PathSelectParams op_params;
path_select_params_from_op(op, &op_params);
uv_shortest_path_pick_ex(
scene, depsgraph, obedit, &op_params, ele_src, ele_dst, aspect_y, offsets);
found_valid_elements = true;
}
}
if (!found_valid_elements) {
BKE_report(
op->reports, RPT_WARNING, "Path selection requires two matching elements to be selected");
return OPERATOR_CANCELLED;
}
return OPERATOR_FINISHED;
}
void UV_OT_shortest_path_select(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Select Shortest Path";
ot->idname = "UV_OT_shortest_path_select";
ot->description = "Select shortest path between two vertices/edges/faces";
/* API callbacks. */
ot->exec = uv_shortest_path_select_exec;
ot->poll = ED_operator_uvedit_space_image;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
path_select_properties(ot);
}
/** \} */
} // namespace blender

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff