[Bf-blender-cvs] [721fc9c1c95] master: UV: implement copy and paste for uv

Chris Blackbourn noreply at git.blender.org
Sun Nov 13 00:56:27 CET 2022


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.quer at 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. @@



More information about the Bf-blender-cvs mailing list