Blender V4.3
uvedit_clipboard.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2022 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
21#include "BKE_context.hh"
22#include "BKE_customdata.hh"
23#include "BKE_editmesh.hh"
24#include "BKE_layer.hh"
25#include "BKE_mesh_mapping.hh" /* UvElementMap */
26#include "BKE_report.hh"
27
28#include "DEG_depsgraph.hh"
29
30#include "ED_mesh.hh"
31#include "ED_screen.hh"
32
33#include "WM_api.hh"
34
36#include "uvedit_intern.hh" /* Own include. */
37
38using blender::Vector;
39
41
43 public:
45
46 void append(UvElementMap *element_map, const int cd_loop_uv_offset);
50 bool find_isomorphism(UvElementMap *dest_element_map,
51 int island_index,
52 int cd_loop_uv_offset,
53 blender::Vector<int> &r_label,
54 bool *r_search_abandoned);
55
56 void write_uvs(UvElementMap *element_map,
57 int island_index,
58 const int cd_loop_uv_offset,
60
61 private:
65};
66
68
70{
71 for (const int64_t index : graph.index_range()) {
72 delete graph[index];
73 }
74 graph.clear();
75 offset.clear();
76 uv.clear();
77}
78
79/* Given a `BMLoop`, possibly belonging to an island in a `UvElementMap`,
80 * return the `iso_index` corresponding to it's representation
81 * in the `iso_graph`.
82 *
83 * If the `BMLoop` is not part of the `iso_graph`, return -1.
84 */
85static int iso_index_for_loop(const BMLoop *loop,
86 UvElementMap *element_map,
87 const int island_index)
88{
89 UvElement *element = BM_uv_element_get(element_map, loop);
90 if (!element) {
91 return -1; /* Either unselected, or a different island. */
92 }
93 const int index = BM_uv_element_get_unique_index(element_map, element);
94 const int base_index = BM_uv_element_get_unique_index(
95 element_map, element_map->storage + element_map->island_indices[island_index]);
96 return index - base_index;
97}
98
99/* Add an `iso_edge` to an `iso_graph` between two BMLoops.
100 */
101static void add_iso_edge(
102 GraphISO *graph, BMLoop *loop_v, BMLoop *loop_w, UvElementMap *element_map, int island_index)
103{
104 BLI_assert(loop_v->f == loop_w->f); /* Ensure on the same face. */
105 const int index_v = iso_index_for_loop(loop_v, element_map, island_index);
106 const int index_w = iso_index_for_loop(loop_w, element_map, island_index);
107 BLI_assert(index_v != index_w);
108 if (index_v == -1 || index_w == -1) {
109 return; /* Unselected. */
110 }
111
112 BLI_assert(0 <= index_v && index_v < graph->n);
113 BLI_assert(0 <= index_w && index_w < graph->n);
114
115 graph->add_edge(index_v, index_w);
116}
117
118/* Build an `iso_graph` representation of an island of a `UvElementMap`.
119 */
121 const int island_index,
122 int /*cd_loop_uv_offset*/)
123{
124 GraphISO *g = new GraphISO(element_map->island_total_unique_uvs[island_index]);
125 for (int i = 0; i < g->n; i++) {
126 g->label[i] = i;
127 }
128
129 const int i0 = element_map->island_indices[island_index];
130 const int i1 = i0 + element_map->island_total_uvs[island_index];
131
132 /* Add iso_edges. */
133 for (int i = i0; i < i1; i++) {
134 const UvElement *element = element_map->storage + i;
135 /* Look forward around the current face. */
136 add_iso_edge(g, element->l, element->l->next, element_map, island_index);
137
138 /* Look backward around the current face.
139 * (Required for certain vertex selection cases.)
140 */
141 add_iso_edge(g, element->l->prev, element->l, element_map, island_index);
142 }
143
144 /* TODO: call g->sort_vertices_by_degree() */
145
146 return g;
147}
148
149/* Convert each island inside an `element_map` into an `iso_graph`, and append them to the
150 * clipboard buffer. */
151void UV_ClipboardBuffer::append(UvElementMap *element_map, const int cd_loop_uv_offset)
152{
153 for (int island_index = 0; island_index < element_map->total_islands; island_index++) {
154 offset.append(uv.size());
155 graph.append(build_iso_graph(element_map, island_index, cd_loop_uv_offset));
156
157 /* TODO: Consider iterating over `BM_uv_element_map_ensure_unique_index` instead. */
158 for (int j = 0; j < element_map->island_total_uvs[island_index]; j++) {
159 UvElement *element = element_map->storage + element_map->island_indices[island_index] + j;
160 if (!element->separate) {
161 continue;
162 }
163 float *luv = BM_ELEM_CD_GET_FLOAT_P(element->l, cd_loop_uv_offset);
164 uv.append(std::make_pair(luv[0], luv[1]));
165 }
166 }
167}
168
169/* Write UVs back to an island. */
171 int island_index,
172 const int cd_loop_uv_offset,
174{
175 BLI_assert(label.size() == element_map->island_total_unique_uvs[island_index]);
176
177 /* TODO: Consider iterating over `BM_uv_element_map_ensure_unique_index` instead. */
178 int unique_uv = 0;
179 for (int j = 0; j < element_map->island_total_uvs[island_index]; j++) {
180 int k = element_map->island_indices[island_index] + j;
181 UvElement *element = element_map->storage + k;
182 if (!element->separate) {
183 continue;
184 }
185 BLI_assert(0 <= unique_uv);
186 BLI_assert(unique_uv < label.size());
187 const std::pair<float, float> &source_uv = uv_clipboard->uv[label[unique_uv]];
188 while (element) {
189 float *luv = BM_ELEM_CD_GET_FLOAT_P(element->l, cd_loop_uv_offset);
190 luv[0] = source_uv.first;
191 luv[1] = source_uv.second;
192 element = element->next;
193 if (!element || element->separate) {
194 break;
195 }
196 }
197 unique_uv++;
198 }
199 BLI_assert(unique_uv == label.size());
200}
201
207 const int dest_island_index,
208 GraphISO *graph_source,
209 const int cd_loop_uv_offset,
210 blender::Vector<int> &r_label,
211 bool *r_search_abandoned)
212{
213
214 const int island_total_unique_uvs = dest->island_total_unique_uvs[dest_island_index];
215 if (island_total_unique_uvs != graph_source->n) {
216 return false; /* Isomorphisms can't differ in |iso_vert|. */
217 }
218 r_label.resize(island_total_unique_uvs);
219
220 GraphISO *graph_dest = build_iso_graph(dest, dest_island_index, cd_loop_uv_offset);
221
222 int(*solution)[2] = (int(*)[2])MEM_mallocN(graph_source->n * sizeof(*solution), __func__);
223 int solution_length = 0;
225 graph_source, graph_dest, solution, &solution_length, r_search_abandoned);
226
227 /* TODO: Implement "Best Effort" / "Nearest Match" paste functionality here. */
228
229 if (found) {
230 BLI_assert(solution_length == dest->island_total_unique_uvs[dest_island_index]);
231 for (int i = 0; i < solution_length; i++) {
232 int index_s = solution[i][0];
233 int index_t = solution[i][1];
234 BLI_assert(0 <= index_s && index_s < solution_length);
235 BLI_assert(0 <= index_t && index_t < solution_length);
236 r_label[index_t] = index_s;
237 }
238 }
239
240 MEM_SAFE_FREE(solution);
241 delete graph_dest;
242 return found;
243}
244
246 const int dest_island_index,
247 const int cd_loop_uv_offset,
248 blender::Vector<int> &r_label,
249 bool *r_search_abandoned)
250{
251 for (const int64_t source_island_index : graph.index_range()) {
252 if (::find_isomorphism(dest_element_map,
253 dest_island_index,
254 graph[source_island_index],
255 cd_loop_uv_offset,
256 r_label,
257 r_search_abandoned))
258 {
259 const int island_total_unique_uvs =
260 dest_element_map->island_total_unique_uvs[dest_island_index];
261 const int island_offset = offset[source_island_index];
262 BLI_assert(island_total_unique_uvs == r_label.size());
263 for (int i = 0; i < island_total_unique_uvs; i++) {
264 r_label[i] += island_offset; /* TODO: (minor optimization) Defer offset. */
265 }
266
267 /* TODO: There may be more than one match. How to choose between them? */
268 return true;
269 }
270 }
271
272 return false;
273}
274
275static int uv_copy_exec(bContext *C, wmOperator * /*op*/)
276{
279
280 ViewLayer *view_layer = CTX_data_view_layer(C);
281 Scene *scene = CTX_data_scene(C);
282
284 scene, view_layer, nullptr);
285
286 for (Object *ob : objects) {
288
289 const bool use_seams = false;
291 em->bm, scene, true, false, use_seams, true);
292 if (element_map) {
293 const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_PROP_FLOAT2);
294 uv_clipboard->append(element_map, cd_loop_uv_offset);
295 }
296 BM_uv_element_map_free(element_map);
297 }
298
299 /* TODO: Serialize `UvClipboard` to system clipboard. */
300
301 return OPERATOR_FINISHED;
302}
303
305{
306 /* TODO: Restore `UvClipboard` from system clipboard. */
307 if (!uv_clipboard) {
308 return OPERATOR_FINISHED; /* Nothing to do. */
309 }
310 ViewLayer *view_layer = CTX_data_view_layer(C);
311 Scene *scene = CTX_data_scene(C);
312
314 scene, view_layer, nullptr);
315
316 bool changed_multi = false;
317 int complicated_search = 0;
318 int total_search = 0;
319 for (Object *ob : objects) {
321
322 const bool use_seams = false;
323 const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_PROP_FLOAT2);
324
325 UvElementMap *dest_element_map = BM_uv_element_map_create(
326 em->bm, scene, true, false, use_seams, true);
327
328 if (!dest_element_map) {
329 continue;
330 }
331
332 bool changed = false;
333
334 for (int i = 0; i < dest_element_map->total_islands; i++) {
335 total_search++;
337 bool search_abandoned = false;
338 const bool found = uv_clipboard->find_isomorphism(
339 dest_element_map, i, cd_loop_uv_offset, label, &search_abandoned);
340 if (!found) {
341 if (search_abandoned) {
342 complicated_search++;
343 }
344 continue; /* No source UVs can be found that is isomorphic to this island. */
345 }
346
347 uv_clipboard->write_uvs(dest_element_map, i, cd_loop_uv_offset, label);
348 changed = true; /* UVs were moved. */
349 }
350
351 BM_uv_element_map_free(dest_element_map);
352
353 if (changed) {
354 changed_multi = true;
355
356 DEG_id_tag_update(static_cast<ID *>(ob->data), 0);
357 WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data);
358 }
359 }
360
361 if (complicated_search) {
364 "Skipped %d of %d island(s), geometry was too complicated to detect a match",
365 complicated_search,
366 total_search);
367 }
368
369 return changed_multi ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
370}
371
373{
374 /* identifiers */
375 ot->name = "Copy UVs";
376 ot->description = "Copy selected UV vertices";
377 ot->idname = "UV_OT_copy";
379
380 /* api callbacks */
383}
384
386{
387 /* identifiers */
388 ot->name = "Paste UVs";
389 ot->description = "Paste selected UV vertices";
390 ot->idname = "UV_OT_paste";
392
393 /* api callbacks */
396}
397
399{
400 delete uv_clipboard;
401 uv_clipboard = nullptr;
402}
Scene * CTX_data_scene(const bContext *C)
ViewLayer * CTX_data_view_layer(const bContext *C)
CustomData interface, see also DNA_customdata_types.h.
int CustomData_get_offset(const CustomData *data, eCustomDataType type)
BMEditMesh * BKE_editmesh_from_object(Object *ob)
Return the BMEditMesh for a given object.
Definition editmesh.cc:63
blender::Vector< Object * > BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs(const Scene *scene, ViewLayer *view_layer, const View3D *v3d)
void BKE_reportf(ReportList *reports, eReportType type, const char *format,...) ATTR_PRINTF_FORMAT(3
#define BLI_assert(a)
Definition BLI_assert.h:50
void DEG_id_tag_update(ID *id, unsigned int flags)
@ CD_PROP_FLOAT2
UvElement * BM_uv_element_get(const UvElementMap *element_map, const BMLoop *l)
int BM_uv_element_get_unique_index(UvElementMap *element_map, UvElement *child)
void BM_uv_element_map_free(UvElementMap *element_map)
UvElementMap * BM_uv_element_map_create(BMesh *bm, const Scene *scene, bool uv_selected, bool use_winding, bool use_seams, bool do_islands)
bool ED_operator_uvedit(bContext *C)
#define MEM_SAFE_FREE(v)
@ OPTYPE_UNDO
Definition WM_types.hh:162
@ OPTYPE_REGISTER
Definition WM_types.hh:160
#define NC_GEOM
Definition WM_types.hh:360
#define ND_DATA
Definition WM_types.hh:475
#define BM_ELEM_CD_GET_FLOAT_P(ele, offset)
void write_uvs(UvElementMap *element_map, int island_index, const int cd_loop_uv_offset, const blender::Vector< int > &label)
bool find_isomorphism(UvElementMap *dest_element_map, int island_index, int cd_loop_uv_offset, blender::Vector< int > &r_label, bool *r_search_abandoned)
void append(UvElementMap *element_map, const int cd_loop_uv_offset)
int64_t size() const
void append(const T &value)
void resize(const int64_t new_size)
const char * label
append
draw_view push_constant(Type::INT, "radiance_src") .push_constant(Type capture_info_buf storage_buf(1, Qualifier::READ, "ObjectBounds", "bounds_buf[]") .push_constant(Type draw_view int
void *(* MEM_mallocN)(size_t len, const char *str)
Definition mallocn.cc:44
__int64 int64_t
Definition stdint.h:89
struct BMFace * f
CustomData ldata
Definition DNA_ID.h:413
UvElement * storage
int * island_total_unique_uvs
const char * name
Definition WM_types.hh:990
bool(* poll)(bContext *C) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1042
const char * idname
Definition WM_types.hh:992
int(* exec)(bContext *C, wmOperator *op) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1006
const char * description
Definition WM_types.hh:996
struct ReportList * reports
void UV_OT_copy(wmOperatorType *ot)
void UV_clipboard_free()
static GraphISO * build_iso_graph(UvElementMap *element_map, const int island_index, int)
static void add_iso_edge(GraphISO *graph, BMLoop *loop_v, BMLoop *loop_w, UvElementMap *element_map, int island_index)
static bool find_isomorphism(UvElementMap *dest, const int dest_island_index, GraphISO *graph_source, const int cd_loop_uv_offset, blender::Vector< int > &r_label, bool *r_search_abandoned)
static int uv_copy_exec(bContext *C, wmOperator *)
static int uv_paste_exec(bContext *C, wmOperator *op)
static UV_ClipboardBuffer * uv_clipboard
static int iso_index_for_loop(const BMLoop *loop, UvElementMap *element_map, const int island_index)
void UV_OT_paste(wmOperatorType *ot)
bool ED_uvedit_clipboard_maximum_common_subgraph(GraphISO *g0_input, GraphISO *g1_input, int solution[][2], int *solution_length, bool *r_search_abandoned)
void WM_event_add_notifier(const bContext *C, uint type, void *reference)
wmOperatorType * ot
Definition wm_files.cc:4125