Commit: 721fc9c1c95017d55785ea42e7ba473a0285b9ad Author: Chris Blackbourn Date: Sun Nov 13 12:27:28 2022 +1300 Branches: master https://developer.blender.org/rB721fc9c1c95017d55785ea42e7ba473a0285b9ad
UV: implement copy and paste for uv Implement a new topology-based copy and paste solution for UVs. Usage notes: * Open the UV Editor * Use the selection tools to select a Quad joined to a Triangle joined to another Quad. * From the menu, choose UV > UV Copy * The UV co-ordinates for your quad<=>tri<=>quad are now stored internally * Use the selection tools to select a different Quad joined to a Triangle joined to a Quad. * (Optional) From the menu, choose UV > Split > Selection * From the menu, choose UV > UV Paste * The UV co-ordinates for the new selection will be moved to match the stored UVs. Repeat selection / UV Paste steps as many times as desired. For performance considerations, see https://en.wikipedia.org/wiki/Graph_isomorphism_problem In theory, UV Copy and Paste should work with all UV selection modes. Please report any problems. A copy has been made of the Graph Isomorphism code from https://github.com/stefanoquer/graphISO Copyright (c) 2019 Stefano Quer stefano.q...@polito.it GPL v3 or later. Additional integration code Copyright (c) 2022 by Blender Foundation, GPL v2 or later. Maniphest Tasks: T77911 Differential Revision: https://developer.blender.org/D16278 =================================================================== M release/scripts/startup/bl_ui/space_image.py M source/blender/blenkernel/BKE_mesh_mapping.h M source/blender/editors/uvedit/CMakeLists.txt A source/blender/editors/uvedit/uvedit_clipboard.cc A source/blender/editors/uvedit/uvedit_clipboard_graph_iso.cc A source/blender/editors/uvedit/uvedit_clipboard_graph_iso.hh M source/blender/editors/uvedit/uvedit_intern.h M source/blender/editors/uvedit/uvedit_ops.c M source/blender/windowmanager/intern/wm_init_exit.c =================================================================== diff --git a/release/scripts/startup/bl_ui/space_image.py b/release/scripts/startup/bl_ui/space_image.py index fcbd7bb423d..f4c64831b3e 100644 --- a/release/scripts/startup/bl_ui/space_image.py +++ b/release/scripts/startup/bl_ui/space_image.py @@ -440,6 +440,11 @@ class IMAGE_MT_uvs(Menu): layout.separator() + layout.operator("uv.copy") + layout.operator("uv.paste") + + layout.separator() + layout.menu("IMAGE_MT_uvs_showhide") layout.separator() diff --git a/source/blender/blenkernel/BKE_mesh_mapping.h b/source/blender/blenkernel/BKE_mesh_mapping.h index 705158bec0b..c5c81b31b79 100644 --- a/source/blender/blenkernel/BKE_mesh_mapping.h +++ b/source/blender/blenkernel/BKE_mesh_mapping.h @@ -343,6 +343,9 @@ int *BKE_mesh_calc_smoothgroups(const struct MEdge *medge, #endif #ifdef __cplusplus + +# include "DNA_meshdata_types.h" /* MPoly */ + namespace blender::bke::mesh_topology { Array<int> build_loop_to_poly_map(Span<MPoly> polys, int loops_num); diff --git a/source/blender/editors/uvedit/CMakeLists.txt b/source/blender/editors/uvedit/CMakeLists.txt index 4574c745d93..9a9f79b7972 100644 --- a/source/blender/editors/uvedit/CMakeLists.txt +++ b/source/blender/editors/uvedit/CMakeLists.txt @@ -21,6 +21,8 @@ set(INC set(SRC uvedit_buttons.c + uvedit_clipboard.cc + uvedit_clipboard_graph_iso.cc uvedit_draw.c uvedit_islands.cc uvedit_ops.c @@ -30,6 +32,7 @@ set(SRC uvedit_smart_stitch.c uvedit_unwrap_ops.c + uvedit_clipboard_graph_iso.hh uvedit_intern.h ) diff --git a/source/blender/editors/uvedit/uvedit_clipboard.cc b/source/blender/editors/uvedit/uvedit_clipboard.cc new file mode 100644 index 00000000000..7e6d6a2e0d0 --- /dev/null +++ b/source/blender/editors/uvedit/uvedit_clipboard.cc @@ -0,0 +1,369 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2022 Blender Foundation. All rights reserved. */ + +/** \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 "BLI_math.h" + +#include "BKE_context.h" +#include "BKE_customdata.h" +#include "BKE_editmesh.h" +#include "BKE_layer.h" +#include "BKE_mesh_mapping.h" /* UvElementMap */ + +#include "DEG_depsgraph.h" + +#include "ED_mesh.h" +#include "ED_screen.h" +#include "ED_uvedit.h" /* Own include. */ + +#include "WM_api.h" + +#include "uvedit_clipboard_graph_iso.hh" +#include "uvedit_intern.h" /* linker, extern "C" */ + +extern "C" { +void UV_clipboard_free(void); +} + +class UV_ClipboardBuffer { + public: + ~UV_ClipboardBuffer(); + + void append(UvElementMap *element_map, const int cd_loop_uv_offset); + bool find_isomorphism(UvElementMap *dest_element_map, + int island_index, + blender::Vector<int> &r_label, + int cd_loop_uv_offset); + + void write_uvs(UvElementMap *element_map, + int island_index, + const int cd_loop_uv_offset, + const blender::Vector<int> &label); + + private: + blender::Vector<GraphISO *> graph; + blender::Vector<int> offset; + blender::Vector<std::pair<float, float>> uv; +}; + +static UV_ClipboardBuffer *uv_clipboard = nullptr; + +UV_ClipboardBuffer::~UV_ClipboardBuffer() +{ + for (const int 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->f, 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`. + */ +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; + } + MLoopUV *luv = static_cast<MLoopUV *>(BM_ELEM_CD_GET_VOID_P(element->l, cd_loop_uv_offset)); + uv.append(std::make_pair(luv->uv[0], luv->uv[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 blender::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) { + MLoopUV *luv = static_cast<MLoopUV *>(BM_ELEM_CD_GET_VOID_P(element->l, cd_loop_uv_offset)); + luv->uv[0] = source_uv.first; + luv->uv[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. */ +static bool find_isomorphism(UvElementMap *dest, + const int dest_island_index, + GraphISO *graph_source, + blender::Vector<int> &r_label, + int cd_loop_uv_offset) +{ + + 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] = (int(*)[2])MEM_mallocN(graph_source->n * sizeof(*solution), __func__); + int solution_length = 0; + const bool result = ED_uvedit_clipboard_maximum_common_subgraph( + graph_source, graph_dest, solution, &solution_length); + + /* Todo: Implement "Best Effort" / "Nearest Match" paste functionality here. */ + + if (result) { + 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_FREE(solution); + delete graph_dest; + return result; +} + +bool UV_ClipboardBuffer::find_isomorphism(UvElementMap *dest_element_map, + int dest_island_index, + blender::Vector<int> &r_label, + @@ Diff output truncated at 10240 characters. @@ _______________________________________________ Bf-blender-cvs mailing list Bf-blender-cvs@blender.org List details, subscription details or unsubscribe: https://lists.blender.org/mailman/listinfo/bf-blender-cvs