Blender V4.3
clipboard.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include "DNA_space_types.h"
6
7#include "BKE_context.hh"
8#include "BKE_global.hh"
9#include "BKE_lib_id.hh"
10#include "BKE_lib_query.hh"
11#include "BKE_main.hh"
12#include "BKE_main_idmap.hh"
13#include "BKE_node.hh"
14#include "BKE_node_runtime.hh"
15#include "BKE_report.hh"
16
17#include "ED_node.hh"
18#include "ED_render.hh"
19#include "ED_screen.hh"
20
21#include "RNA_access.hh"
22#include "RNA_define.hh"
23
25
26#include "node_intern.hh"
27
29
32 std::string id_name = "";
40 std::string library_path = "";
41
43 std::optional<ID *> new_id = {};
44};
45
53
54 /* Extra info to validate the IDs used by the node on creation. Otherwise it may reference
55 * missing data. */
56
58 std::string id_name;
59 std::string library_name;
60};
61
63 const bNode *from_node = nullptr;
64 const bNode *to_node = nullptr;
65 std::string from_socket;
66 std::string to_socket;
67 int flag = 0;
69};
70
74
75 /* A mapping of all ID references from nodes in the clipboard, to information allowing to find
76 * their valid matching counterpart in current Main data when pasting the nodes back. Entries are
77 * added when adding nodes to the clipboard, and they are updated when pasting the nodes back
78 * into current Main. */
80
82 void clear()
83 {
84 for (NodeClipboardItem &item : this->nodes) {
85 bke::node_free_node(nullptr, item.node);
86 }
87 this->nodes.clear_and_shrink();
88 this->links.clear_and_shrink();
89 this->old_ids_to_idinfo.clear_and_shrink();
90 }
91
102 {
103 bool is_valid = true;
104 IDNameLib_Map *bmain_id_map = nullptr;
105
106 /* Clear any potentially previously found `new_id` valid pointers in #old_ids_to_idinfo values,
107 * and populate a temporary mapping from absolute library paths to existing Library IDs in
108 * given Main. */
109 Map<std::string, Library *> libraries_path_to_id;
110 for (NodeClipboardItemIDInfo &id_info : this->old_ids_to_idinfo.values()) {
111 id_info.new_id.reset();
112 if (!id_info.library_path.empty() && !libraries_path_to_id.contains(id_info.library_path)) {
113 libraries_path_to_id.add(
114 id_info.library_path,
115 static_cast<Library *>(BLI_findstring(&bmain.libraries,
116 id_info.library_path.c_str(),
117 offsetof(Library, runtime.filepath_abs))));
118 }
119 }
120
121 /* Find a new valid ID pointer for all ID usages in given node.
122 *
123 * NOTE: Due to the fact that the clipboard survives file loading, only name (including IDType)
124 * and library-path pairs can be used here.
125 * - UID cannot be trusted across file load.
126 * - ID pointer itself cannot be trusted across undo/redo and file-load. */
127 auto validate_id_fn = [this, &is_valid, &bmain, &bmain_id_map, &libraries_path_to_id](
128 LibraryIDLinkCallbackData *cb_data) -> int {
129 ID *old_id = *(cb_data->id_pointer);
130 if (!old_id) {
131 return IDWALK_RET_NOP;
132 }
133 if (!this->old_ids_to_idinfo.contains(old_id)) {
135 0, "Missing entry in the old ID data of the node clipboard, should not happen");
136 is_valid = false;
137 return IDWALK_RET_NOP;
138 }
139
140 NodeClipboardItemIDInfo &id_info = this->old_ids_to_idinfo.lookup(old_id);
141 if (!id_info.new_id) {
142 if (!bmain_id_map) {
143 bmain_id_map = BKE_main_idmap_create(&bmain, false, nullptr, MAIN_IDMAP_TYPE_NAME);
144 }
145 Library *new_id_lib = libraries_path_to_id.lookup_default(id_info.library_path, nullptr);
146 if (id_info.library_path.empty() || new_id_lib) {
148 bmain_id_map, GS(id_info.id_name.c_str()), id_info.id_name.c_str() + 2, new_id_lib);
149 }
150 else {
151 /* No matching library found, so there is no possible matching ID either. */
152 id_info.new_id = nullptr;
153 }
154 }
155 if (*(id_info.new_id) == nullptr) {
156 is_valid = false;
157 }
158 return IDWALK_RET_NOP;
159 };
160 for (NodeClipboardItem &item : this->nodes) {
162 &bmain,
163 nullptr,
164 nullptr,
165 [&item](LibraryForeachIDData *data) { bke::node_node_foreach_id(item.node, data); },
166 validate_id_fn,
167 nullptr,
169 }
170
171 if (bmain_id_map) {
172 BKE_main_idmap_destroy(bmain_id_map);
173 bmain_id_map = nullptr;
174 }
175 return is_valid;
176 }
177
183 {
184 /* Update all old ID pointers in given node by new, valid ones. */
185 auto update_id_fn = [this](LibraryIDLinkCallbackData *cb_data) -> int {
186 ID *old_id = *(cb_data->id_pointer);
187 if (!old_id) {
188 return IDWALK_RET_NOP;
189 }
190 if (!this->old_ids_to_idinfo.contains(old_id)) {
192 0, "Missing entry in the old ID data of the node clipboard, should not happen");
193 *(cb_data->id_pointer) = nullptr;
194 return IDWALK_RET_NOP;
195 }
196
197 NodeClipboardItemIDInfo &id_info = this->old_ids_to_idinfo.lookup(old_id);
198 if (!id_info.new_id) {
200 0,
201 "Unset new ID value for an old ID reference in the node clipboard, should not happen");
202 *(cb_data->id_pointer) = nullptr;
203 return IDWALK_RET_NOP;
204 }
205 *(cb_data->id_pointer) = *(id_info.new_id);
206 if (cb_data->cb_flag & IDWALK_CB_USER) {
207 id_us_plus(*(cb_data->id_pointer));
208 }
209 return IDWALK_RET_NOP;
210 };
212 nullptr,
213 nullptr,
214 nullptr,
215 [&node](LibraryForeachIDData *data) { bke::node_node_foreach_id(&node, data); },
216 update_id_fn,
217 nullptr,
218 IDWALK_NOP);
219 }
220
222 void copy_add_node(const bNode &node,
225 {
226 /* No ID reference-counting, this node is virtual,
227 * detached from any actual Blender data currently. */
229 nullptr, node, LIB_ID_CREATE_NO_USER_REFCOUNT | LIB_ID_CREATE_NO_MAIN, false, socket_map);
230 node_map.add_new(&node, new_node);
231
232 /* Find a new valid ID pointer for all ID usages in given node. */
233 auto ensure_id_info_fn = [this](LibraryIDLinkCallbackData *cb_data) -> int {
234 ID *old_id = *(cb_data->id_pointer);
235 if (!old_id) {
236 }
237 if (this->old_ids_to_idinfo.contains(old_id)) {
238 return IDWALK_RET_NOP;
239 }
240
242 if (old_id) {
243 id_info.id_name = old_id->name;
244 if (ID_IS_LINKED(old_id)) {
245 id_info.library_path = old_id->lib->runtime.filepath_abs;
246 }
247 }
248 this->old_ids_to_idinfo.add(old_id, std::move(id_info));
249 return IDWALK_RET_NOP;
250 };
252 nullptr,
253 nullptr,
254 nullptr,
255 [&node](LibraryForeachIDData *data) {
256 bke::node_node_foreach_id(const_cast<bNode *>(&node), data);
257 },
258 ensure_id_info_fn,
259 nullptr,
261
263 item.draw_rect = node.runtime->totr;
264 item.node = new_node;
265 this->nodes.append(std::move(item));
266 }
267};
268
270{
271 static NodeClipboard clipboard;
272 return clipboard;
273}
274
275/* -------------------------------------------------------------------- */
280{
281 SpaceNode &snode = *CTX_wm_space_node(C);
282 bNodeTree &tree = *snode.edittree;
283 NodeClipboard &clipboard = get_node_clipboard();
284
285 clipboard.clear();
286
289
291
292 for (const bNode *node : tree.all_nodes()) {
293 if (node->flag & SELECT) {
294 clipboard.copy_add_node(*node, node_map, socket_map);
295 }
296 }
297
298 for (bNode *new_node : node_map.values()) {
299 /* Parent pointer must be redirected to new node or detached if parent is not copied. */
300 if (new_node->parent) {
301 if (node_map.contains(new_node->parent)) {
302 new_node->parent = node_map.lookup(new_node->parent);
303 }
304 else {
305 bke::node_detach_node(&tree, new_node);
306 }
307 }
308 }
309
310 /* Copy links between selected nodes. */
311 LISTBASE_FOREACH (bNodeLink *, link, &tree.links) {
312 BLI_assert(link->tonode);
313 BLI_assert(link->fromnode);
314 if (link->tonode->flag & NODE_SELECT && link->fromnode->flag & NODE_SELECT) {
315 clipboard.links.append({});
316 ClipboardLink &new_link = clipboard.links.last();
317 new_link.flag = link->flag;
318 new_link.to_node = node_map.lookup(link->tonode);
319 new_link.from_node = node_map.lookup(link->fromnode);
320 new_link.to_socket = link->tosock->identifier;
321 new_link.from_socket = link->fromsock->identifier;
322 new_link.multi_input_sort_id = link->multi_input_sort_id;
323 }
324 }
325
326 return OPERATOR_FINISHED;
327}
328
330{
331 ot->name = "Copy to Clipboard";
332 ot->description = "Copy the selected nodes to the internal clipboard";
333 ot->idname = "NODE_OT_clipboard_copy";
334
337
339}
340
343/* -------------------------------------------------------------------- */
348{
349 Main *bmain = CTX_data_main(C);
350 SpaceNode &snode = *CTX_wm_space_node(C);
351 bNodeTree &tree = *snode.edittree;
352 NodeClipboard &clipboard = get_node_clipboard();
353
354 if (clipboard.nodes.is_empty()) {
355 BKE_report(op->reports, RPT_ERROR, "The internal clipboard is empty");
356 return OPERATOR_CANCELLED;
357 }
358
359 if (!clipboard.paste_validate_id_references(*bmain)) {
362 "Some nodes references to other IDs could not be restored, will be left empty");
363 }
364
366
368
371
372 /* copy valid nodes from clipboard */
373 for (NodeClipboardItem &item : clipboard.nodes) {
374 const bNode &node = *item.node;
375 const char *disabled_hint = nullptr;
376 if (node.typeinfo->poll_instance && node.typeinfo->poll_instance(&node, &tree, &disabled_hint))
377 {
378 /* Do not access referenced ID pointers here, as they are still the old ones, which may be
379 * invalid. */
381 &tree, node, LIB_ID_CREATE_NO_USER_REFCOUNT, true, socket_map);
382 /* Update the newly copied node's ID references. */
383 clipboard.paste_update_node_id_references(*new_node);
384 /* Reset socket shape in case a node is copied to a different tree type. */
385 LISTBASE_FOREACH (bNodeSocket *, socket, &new_node->inputs) {
386 socket->display_shape = SOCK_DISPLAY_SHAPE_CIRCLE;
387 }
388 LISTBASE_FOREACH (bNodeSocket *, socket, &new_node->outputs) {
389 socket->display_shape = SOCK_DISPLAY_SHAPE_CIRCLE;
390 }
391 node_map.add_new(&node, new_node);
392 }
393 else {
394 if (disabled_hint) {
396 RPT_ERROR,
397 "Cannot add node %s into node tree %s: %s",
398 node.name,
399 tree.id.name + 2,
400 disabled_hint);
401 }
402 else {
404 RPT_ERROR,
405 "Cannot add node %s into node tree %s",
406 node.name,
407 tree.id.name + 2);
408 }
409 }
410 }
411
412 for (bNode *new_node : node_map.values()) {
413 bke::node_set_selected(new_node, true);
414
415 new_node->flag &= ~NODE_ACTIVE;
416
417 /* The parent pointer must be redirected to new node. */
418 if (new_node->parent) {
419 if (node_map.contains(new_node->parent)) {
420 new_node->parent = node_map.lookup(new_node->parent);
421 }
422 }
423 }
424
425 PropertyRNA *offset_prop = RNA_struct_find_property(op->ptr, "offset");
426 if (RNA_property_is_set(op->ptr, offset_prop)) {
427 float2 center(0);
428 for (NodeClipboardItem &item : clipboard.nodes) {
429 center.x += BLI_rctf_cent_x(&item.draw_rect);
430 center.y += BLI_rctf_cent_y(&item.draw_rect);
431 }
432 /* DPI factor needs to be removed when computing a View2D offset from drawing rects. */
433 center /= clipboard.nodes.size();
434
435 float2 mouse_location;
436 RNA_property_float_get_array(op->ptr, offset_prop, mouse_location);
437 const float2 offset = (mouse_location - center) / UI_SCALE_FAC;
438
439 for (bNode *new_node : node_map.values()) {
440 /* Skip the offset for parented nodes since the location is in parent space. */
441 if (new_node->parent == nullptr) {
442 new_node->locx += offset.x;
443 new_node->locy += offset.y;
444 }
445 }
446 }
447
448 remap_node_pairing(tree, node_map);
449
450 for (bNode *new_node : node_map.values()) {
452 }
453
454 /* Add links between existing nodes. */
455 for (const ClipboardLink &link : clipboard.links) {
456 bNode *from_node = node_map.lookup_default(link.from_node, nullptr);
457 bNode *to_node = node_map.lookup_default(link.to_node, nullptr);
458 if (!from_node || !to_node) {
459 continue;
460 }
461 bNodeSocket *from = bke::node_find_socket(from_node, SOCK_OUT, link.from_socket.c_str());
462 bNodeSocket *to = bke::node_find_socket(to_node, SOCK_IN, link.to_socket.c_str());
463 if (!from || !to) {
464 continue;
465 }
466 bNodeLink *new_link = bke::node_add_link(&tree, from_node, from, to_node, to);
467 new_link->multi_input_sort_id = link.multi_input_sort_id;
468 }
469
470 tree.ensure_topology_cache();
471 for (bNode *new_node : node_map.values()) {
472 /* Update multi input socket indices in case all connected nodes weren't copied. */
474 }
475
477 /* Pasting nodes can create arbitrary new relations because nodes can reference IDs. */
479
480 return OPERATOR_FINISHED;
481}
482
484{
485 const ARegion *region = CTX_wm_region(C);
486 float2 cursor;
487 UI_view2d_region_to_view(&region->v2d, event->mval[0], event->mval[1], &cursor.x, &cursor.y);
488 RNA_float_set_array(op->ptr, "offset", cursor);
489 return node_clipboard_paste_exec(C, op);
490}
491
493{
494 ot->name = "Paste from Clipboard";
495 ot->description = "Paste nodes from the internal clipboard to the active node tree";
496 ot->idname = "NODE_OT_clipboard_paste";
497
501
503
505 ot->srna,
506 "offset",
507 2,
508 nullptr,
509 -FLT_MAX,
510 FLT_MAX,
511 "Location",
512 "The 2D view location for the center of the new nodes, or unchanged if not set",
513 -FLT_MAX,
514 FLT_MAX);
517}
518
521} // namespace blender::ed::space_node
522
524{
525 using namespace blender::ed::space_node;
526 NodeClipboard &clipboard = get_node_clipboard();
527 clipboard.clear();
528}
SpaceNode * CTX_wm_space_node(const bContext *C)
Main * CTX_data_main(const bContext *C)
ARegion * CTX_wm_region(const bContext *C)
wmWindowManager * CTX_wm_manager(const bContext *C)
@ LIB_ID_CREATE_NO_USER_REFCOUNT
@ LIB_ID_CREATE_NO_MAIN
void id_us_plus(ID *id)
Definition lib_id.cc:351
@ IDWALK_RET_NOP
@ IDWALK_CB_USER
void BKE_library_foreach_subdata_id(Main *bmain, ID *owner_id, ID *self_id, blender::FunctionRef< void(LibraryForeachIDData *data)> subdata_foreach_id, blender::FunctionRef< LibraryIDLinkCallback > callback, void *user_data, const int flag)
Definition lib_query.cc:436
@ IDWALK_NOP
@ IDWALK_READONLY
IDNameLib_Map * BKE_main_idmap_create(Main *bmain, bool create_valid_ids_set, Main *old_bmain, int idmap_types) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition main_idmap.cc:77
@ MAIN_IDMAP_TYPE_NAME
void BKE_main_idmap_destroy(IDNameLib_Map *id_map) ATTR_NONNULL()
ID * BKE_main_idmap_lookup_name(IDNameLib_Map *id_map, short id_type, const char *name, const Library *lib) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1
void BKE_reportf(ReportList *reports, eReportType type, const char *format,...) ATTR_PRINTF_FORMAT(3
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:125
#define BLI_assert(a)
Definition BLI_assert.h:50
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:57
void * BLI_findstring(const struct ListBase *listbase, const char *id, int offset) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
#define LISTBASE_FOREACH(type, var, list)
BLI_INLINE float BLI_rctf_cent_y(const struct rctf *rct)
Definition BLI_rect.h:184
BLI_INLINE float BLI_rctf_cent_x(const struct rctf *rct)
Definition BLI_rect.h:180
void DEG_relations_tag_update(Main *bmain)
#define ID_IS_LINKED(_id)
Definition DNA_ID.h:654
@ NODE_SELECT
@ SOCK_OUT
@ SOCK_IN
@ SOCK_DISPLAY_SHAPE_CIRCLE
#define UI_SCALE_FAC
void ED_node_tree_propagate_change(const bContext *C, Main *bmain, bNodeTree *ntree)
Definition node_edit.cc:492
void ED_preview_kill_jobs(wmWindowManager *wm, Main *bmain)
bool ED_operator_node_editable(bContext *C)
bool ED_operator_node_active(bContext *C)
@ PROP_SKIP_SAVE
Definition RNA_types.hh:245
@ PROP_HIDDEN
Definition RNA_types.hh:239
void UI_view2d_region_to_view(const View2D *v2d, float x, float y, float *r_view_x, float *r_view_y) ATTR_NONNULL()
Definition view2d.cc:1663
@ OPTYPE_UNDO
Definition WM_types.hh:162
@ OPTYPE_REGISTER
Definition WM_types.hh:160
bool add(const Key &key, const Value &value)
Definition BLI_map.hh:271
const Value & lookup(const Key &key) const
Definition BLI_map.hh:506
Value lookup_default(const Key &key, const Value &default_value) const
Definition BLI_map.hh:531
ValueIterator values() const
Definition BLI_map.hh:846
void clear_and_shrink()
Definition BLI_map.hh:1003
void add_new(const Key &key, const Value &value)
Definition BLI_map.hh:241
bool contains(const Key &key) const
Definition BLI_map.hh:329
void append(const T &value)
void clear_and_shrink()
void ED_node_clipboard_free()
Definition clipboard.cc:523
#define SELECT
#define offsetof(t, d)
KDTree_3d * tree
#define GS(x)
Definition iris.cc:202
void node_detach_node(bNodeTree *ntree, bNode *node)
Definition node.cc:3122
bNode * node_copy_with_mapping(bNodeTree *dst_tree, const bNode &node_src, int flag, bool use_unique, Map< const bNodeSocket *, bNodeSocket * > &new_socket_map)
Definition node.cc:2696
bNodeLink * node_add_link(bNodeTree *ntree, bNode *fromnode, bNodeSocket *fromsock, bNode *tonode, bNodeSocket *tosock)
Definition node.cc:2912
void node_node_foreach_id(bNode *node, LibraryForeachIDData *data)
Definition node.cc:378
bool node_set_selected(bNode *node, bool select)
Definition node.cc:3863
bool node_declaration_ensure(bNodeTree *ntree, bNode *node)
Definition node.cc:3992
void node_free_node(bNodeTree *tree, bNode *node)
Definition node.cc:3475
bNodeSocket * node_find_socket(bNode *node, eNodeSocketInOut in_out, StringRef identifier)
Definition node.cc:1829
static int node_clipboard_copy_exec(bContext *C, wmOperator *)
Definition clipboard.cc:279
void NODE_OT_clipboard_copy(wmOperatorType *ot)
Definition clipboard.cc:329
void node_select_paired(bNodeTree &node_tree)
bool node_deselect_all(bNodeTree &node_tree)
static int node_clipboard_paste_invoke(bContext *C, wmOperator *op, const wmEvent *event)
Definition clipboard.cc:483
void NODE_OT_clipboard_paste(wmOperatorType *ot)
Definition clipboard.cc:492
void remap_node_pairing(bNodeTree &dst_tree, const Map< const bNode *, bNode * > &node_map)
static int node_clipboard_paste_exec(bContext *C, wmOperator *op)
Definition clipboard.cc:347
static NodeClipboard & get_node_clipboard()
Definition clipboard.cc:269
void update_multi_input_indices_for_removed_links(bNode &node)
void RNA_property_float_get_array(PointerRNA *ptr, PropertyRNA *prop, float *values)
PropertyRNA * RNA_struct_find_property(PointerRNA *ptr, const char *identifier)
bool RNA_property_is_set(PointerRNA *ptr, PropertyRNA *prop)
void RNA_float_set_array(PointerRNA *ptr, const char *name, const float *values)
PropertyRNA * RNA_def_float_array(StructOrFunctionRNA *cont_, const char *identifier, const int len, const float *default_value, const float hardmin, const float hardmax, const char *ui_name, const char *ui_description, const float softmin, const float softmax)
void RNA_def_property_flag(PropertyRNA *prop, PropertyFlag flag)
#define FLT_MAX
Definition stdcycles.h:14
Definition DNA_ID.h:413
struct Library * lib
Definition DNA_ID.h:419
char name[66]
Definition DNA_ID.h:425
char filepath_abs[1024]
Definition DNA_ID.h:509
struct Library_Runtime runtime
Definition DNA_ID.h:535
ListBase libraries
Definition BKE_main.hh:211
struct bNodeTree * edittree
ListBase inputs
ListBase outputs
Vector< NodeClipboardItem > nodes
Definition clipboard.cc:72
void paste_update_node_id_references(bNode &node)
Definition clipboard.cc:182
bool paste_validate_id_references(Main &bmain)
Definition clipboard.cc:101
Map< ID *, NodeClipboardItemIDInfo > old_ids_to_idinfo
Definition clipboard.cc:79
Vector< ClipboardLink > links
Definition clipboard.cc:73
void copy_add_node(const bNode &node, Map< const bNode *, bNode * > &node_map, Map< const bNodeSocket *, bNodeSocket * > &socket_map)
Definition clipboard.cc:222
int mval[2]
Definition WM_types.hh:728
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(* invoke)(bContext *C, wmOperator *op, const wmEvent *event) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1022
int(* exec)(bContext *C, wmOperator *op) ATTR_WARN_UNUSED_RESULT
Definition WM_types.hh:1006
const char * description
Definition WM_types.hh:996
StructRNA * srna
Definition WM_types.hh:1080
struct ReportList * reports
struct PointerRNA * ptr
wmOperatorType * ot
Definition wm_files.cc:4125