Blender V4.5
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 "BLI_listbase.h"
8
9#include "BKE_context.hh"
10#include "BKE_global.hh"
11#include "BKE_lib_id.hh"
12#include "BKE_lib_query.hh"
13#include "BKE_library.hh"
14#include "BKE_main.hh"
15#include "BKE_main_idmap.hh"
17#include "BKE_node.hh"
18#include "BKE_node_runtime.hh"
19#include "BKE_report.hh"
20
21#include "ED_node.hh"
22#include "ED_render.hh"
23#include "ED_screen.hh"
24
25#include "RNA_access.hh"
26#include "RNA_define.hh"
27
29
30#include "node_intern.hh"
31
33
36 std::string id_name;
44 std::string library_path;
45
47 std::optional<ID *> new_id = {};
48};
49
57
58 /* Extra info to validate the IDs used by the node on creation. Otherwise it may reference
59 * missing data. */
60
62 std::string id_name;
63 std::string library_name;
64
66 bool was_active = false;
67};
68
70 const bNode *from_node = nullptr;
71 const bNode *to_node = nullptr;
72 std::string from_socket;
73 std::string to_socket;
74 int flag = 0;
76};
77
81
82 /* A mapping of all ID references from nodes in the clipboard, to information allowing to find
83 * their valid matching counterpart in current Main data when pasting the nodes back. Entries are
84 * added when adding nodes to the clipboard, and they are updated when pasting the nodes back
85 * into current Main. */
87
89 void clear()
90 {
91 for (NodeClipboardItem &item : this->nodes) {
92 bke::node_free_node(nullptr, *item.node);
93 }
94 this->nodes.clear_and_shrink();
95 this->links.clear_and_shrink();
96 this->old_ids_to_idinfo.clear();
97 }
98
109 {
110 bool is_valid = true;
111 IDNameLib_Map *bmain_id_map = nullptr;
112
113 /* Clear any potentially previously found `new_id` valid pointers in #old_ids_to_idinfo values,
114 * and populate a temporary mapping from absolute library paths to existing Library IDs in
115 * given Main. */
116 Map<std::string, Library *> libraries_path_to_id;
117 for (NodeClipboardItemIDInfo &id_info : this->old_ids_to_idinfo.values()) {
118 id_info.new_id.reset();
119 if (!id_info.library_path.empty() && !libraries_path_to_id.contains(id_info.library_path)) {
120 libraries_path_to_id.add(
121 id_info.library_path,
122 blender::bke::library::search_filepath_abs(&bmain.libraries, id_info.library_path));
123 }
124 }
125
126 /* Find a new valid ID pointer for all ID usages in given node.
127 *
128 * NOTE: Due to the fact that the clipboard survives file loading, only name (including IDType)
129 * and library-path pairs can be used here.
130 * - UID cannot be trusted across file load.
131 * - ID pointer itself cannot be trusted across undo/redo and file-load. */
132 auto validate_id_fn = [this, &is_valid, &bmain, &bmain_id_map, &libraries_path_to_id](
133 LibraryIDLinkCallbackData *cb_data) -> int {
134 ID *old_id = *(cb_data->id_pointer);
135 if (!old_id) {
136 return IDWALK_RET_NOP;
137 }
138 if (!this->old_ids_to_idinfo.contains(old_id)) {
140 0, "Missing entry in the old ID data of the node clipboard, should not happen");
141 is_valid = false;
142 return IDWALK_RET_NOP;
143 }
144
145 NodeClipboardItemIDInfo &id_info = this->old_ids_to_idinfo.lookup(old_id);
146 if (!id_info.new_id) {
147 if (!bmain_id_map) {
148 bmain_id_map = BKE_main_idmap_create(&bmain, false, nullptr, MAIN_IDMAP_TYPE_NAME);
149 }
150 Library *new_id_lib = libraries_path_to_id.lookup_default(id_info.library_path, nullptr);
151 if (id_info.library_path.empty() || new_id_lib) {
153 bmain_id_map, GS(id_info.id_name.c_str()), id_info.id_name.c_str() + 2, new_id_lib);
154 }
155 else {
156 /* No matching library found, so there is no possible matching ID either. */
157 id_info.new_id = nullptr;
158 }
159 }
160 if (*(id_info.new_id) == nullptr) {
161 is_valid = false;
162 }
163 return IDWALK_RET_NOP;
164 };
165 for (NodeClipboardItem &item : this->nodes) {
167 &bmain,
168 nullptr,
169 nullptr,
170 [&item](LibraryForeachIDData *data) { bke::node_node_foreach_id(item.node, data); },
171 validate_id_fn,
172 nullptr,
174 }
175
176 if (bmain_id_map) {
177 BKE_main_idmap_destroy(bmain_id_map);
178 bmain_id_map = nullptr;
179 }
180 return is_valid;
181 }
182
188 {
189 /* Update all old ID pointers in given node by new, valid ones. */
190 auto update_id_fn = [this](LibraryIDLinkCallbackData *cb_data) -> int {
191 ID *old_id = *(cb_data->id_pointer);
192 if (!old_id) {
193 return IDWALK_RET_NOP;
194 }
195 if (!this->old_ids_to_idinfo.contains(old_id)) {
197 0, "Missing entry in the old ID data of the node clipboard, should not happen");
198 *(cb_data->id_pointer) = nullptr;
199 return IDWALK_RET_NOP;
200 }
201
202 NodeClipboardItemIDInfo &id_info = this->old_ids_to_idinfo.lookup(old_id);
203 if (!id_info.new_id) {
205 0,
206 "Unset new ID value for an old ID reference in the node clipboard, should not happen");
207 *(cb_data->id_pointer) = nullptr;
208 return IDWALK_RET_NOP;
209 }
210 *(cb_data->id_pointer) = *(id_info.new_id);
211 if (cb_data->cb_flag & IDWALK_CB_USER) {
212 id_us_plus(*(cb_data->id_pointer));
213 }
214 return IDWALK_RET_NOP;
215 };
217 nullptr,
218 nullptr,
219 nullptr,
221 update_id_fn,
222 nullptr,
223 IDWALK_NOP);
224 }
225
227 void copy_add_node(const bNode &node,
230 {
231 /* No ID reference-counting, this node is virtual,
232 * detached from any actual Blender data currently. */
234 nullptr, node, LIB_ID_CREATE_NO_USER_REFCOUNT | LIB_ID_CREATE_NO_MAIN, false, socket_map);
235 node_map.add_new(&node, new_node);
236
237 /* Find a new valid ID pointer for all ID usages in given node. */
238 auto ensure_id_info_fn = [this](LibraryIDLinkCallbackData *cb_data) -> int {
239 ID *old_id = *(cb_data->id_pointer);
240 if (!old_id) {
241 }
242 if (this->old_ids_to_idinfo.contains(old_id)) {
243 return IDWALK_RET_NOP;
244 }
245
247 if (old_id) {
248 id_info.id_name = old_id->name;
249 if (ID_IS_LINKED(old_id)) {
250 id_info.library_path = old_id->lib->runtime->filepath_abs;
251 }
252 }
253 this->old_ids_to_idinfo.add(old_id, std::move(id_info));
254 return IDWALK_RET_NOP;
255 };
257 nullptr,
258 nullptr,
259 nullptr,
260 [&node](LibraryForeachIDData *data) {
261 bke::node_node_foreach_id(const_cast<bNode *>(&node), data);
262 },
263 ensure_id_info_fn,
264 nullptr,
266
268 item.draw_rect = node.runtime->draw_bounds;
269 item.node = new_node;
270 item.was_active = node.flag & NODE_ACTIVE;
271 this->nodes.append(std::move(item));
272 }
273};
274
276{
277 static NodeClipboard clipboard;
278 return clipboard;
279}
280
281/* -------------------------------------------------------------------- */
284
286{
287 SpaceNode &snode = *CTX_wm_space_node(C);
288 bNodeTree &tree = *snode.edittree;
289 NodeClipboard &clipboard = get_node_clipboard();
290
291 clipboard.clear();
292
295
297
298 for (const bNode *node : tree.all_nodes()) {
299 if (node->flag & SELECT) {
300 clipboard.copy_add_node(*node, node_map, socket_map);
301 }
302 }
303
304 for (bNode *new_node : node_map.values()) {
305 /* Parent pointer must be redirected to new node or detached if parent is not copied. */
306 if (new_node->parent) {
307 if (node_map.contains(new_node->parent)) {
308 new_node->parent = node_map.lookup(new_node->parent);
309 }
310 else {
311 bke::node_detach_node(tree, *new_node);
312 }
313 }
314 }
315
316 /* Copy links between selected nodes. */
317 LISTBASE_FOREACH (bNodeLink *, link, &tree.links) {
318 BLI_assert(link->tonode);
319 BLI_assert(link->fromnode);
320 if (link->tonode->flag & NODE_SELECT && link->fromnode->flag & NODE_SELECT) {
321 clipboard.links.append({});
322 ClipboardLink &new_link = clipboard.links.last();
323 new_link.flag = link->flag;
324 new_link.to_node = node_map.lookup(link->tonode);
325 new_link.from_node = node_map.lookup(link->fromnode);
326 new_link.to_socket = link->tosock->identifier;
327 new_link.from_socket = link->fromsock->identifier;
328 new_link.multi_input_sort_id = link->multi_input_sort_id;
329 }
330 }
331
332 return OPERATOR_FINISHED;
333}
334
336{
337 ot->name = "Copy to Clipboard";
338 ot->description = "Copy the selected nodes to the internal clipboard";
339 ot->idname = "NODE_OT_clipboard_copy";
340
343
345}
346
348
349/* -------------------------------------------------------------------- */
352
354{
355 Main *bmain = CTX_data_main(C);
356 SpaceNode &snode = *CTX_wm_space_node(C);
357 bNodeTree &tree = *snode.edittree;
358 NodeClipboard &clipboard = get_node_clipboard();
359
360 if (clipboard.nodes.is_empty()) {
361 BKE_report(op->reports, RPT_ERROR, "The internal clipboard is empty");
362 return OPERATOR_CANCELLED;
363 }
364
365 if (!clipboard.paste_validate_id_references(*bmain)) {
368 "Some nodes references to other IDs could not be restored, will be left empty");
369 }
370
372
374
377
378 bNode *new_active_node = nullptr;
379
380 /* copy valid nodes from clipboard */
381 for (NodeClipboardItem &item : clipboard.nodes) {
382 const bNode &node = *item.node;
383 const char *disabled_hint = nullptr;
384
385 /* Some poll functions (e.g. for the nodegroup node, see #node_group_poll_instance) do require
386 * fully valid node data, including the potential ID pointers. So first create the new copy of
387 * the clipboard node, make it as valid as possible, then call its #poll_instance function, and
388 * discard the new copy if it fails.
389 *
390 * See also #141415.
391 */
392
393 /* Do not access referenced ID pointers here, as they are still the old ones, which may be
394 * invalid. */
396 &tree, node, LIB_ID_CREATE_NO_USER_REFCOUNT, true, socket_map);
397 /* Update the newly copied node's ID references. */
398 clipboard.paste_update_node_id_references(*new_node);
399 /* Reset socket shape in case a node is copied to a different tree type. */
400 LISTBASE_FOREACH (bNodeSocket *, socket, &new_node->inputs) {
401 socket->display_shape = SOCK_DISPLAY_SHAPE_CIRCLE;
402 }
403 LISTBASE_FOREACH (bNodeSocket *, socket, &new_node->outputs) {
404 socket->display_shape = SOCK_DISPLAY_SHAPE_CIRCLE;
405 }
406
407 if (!new_node->typeinfo->poll_instance ||
408 new_node->typeinfo->poll_instance(new_node, &tree, &disabled_hint))
409 {
410 node_map.add_new(&node, new_node);
411 if (item.was_active) {
412 new_active_node = new_node;
413 }
414 }
415 else {
416 if (disabled_hint) {
418 RPT_ERROR,
419 "Cannot add node %s into node tree %s: %s",
420 node.name,
421 tree.id.name + 2,
422 disabled_hint);
423 }
424 else {
426 RPT_ERROR,
427 "Cannot add node %s into node tree %s",
428 node.name,
429 tree.id.name + 2);
430 }
431 bke::node_free_node(&tree, *new_node);
432 }
433 }
434
435 for (bNode *new_node : node_map.values()) {
436 bke::node_set_selected(*new_node, true);
437
438 new_node->flag &= ~NODE_ACTIVE;
439
440 /* The parent pointer must be redirected to new node. */
441 if (new_node->parent) {
442 if (node_map.contains(new_node->parent)) {
443 new_node->parent = node_map.lookup(new_node->parent);
444 }
445 }
446 }
447
448 if (new_active_node) {
449 bke::node_set_active(tree, *new_active_node);
450 }
451
452 PropertyRNA *offset_prop = RNA_struct_find_property(op->ptr, "offset");
453 if (RNA_property_is_set(op->ptr, offset_prop)) {
454 float2 center(0);
455 for (NodeClipboardItem &item : clipboard.nodes) {
456 center.x += BLI_rctf_cent_x(&item.draw_rect);
457 center.y += BLI_rctf_cent_y(&item.draw_rect);
458 }
459 /* DPI factor needs to be removed when computing a View2D offset from drawing rects. */
460 center /= clipboard.nodes.size();
461
462 float2 mouse_location;
463 RNA_property_float_get_array(op->ptr, offset_prop, mouse_location);
464 const float2 offset = (mouse_location - center) / UI_SCALE_FAC;
465
466 for (bNode *new_node : node_map.values()) {
467 new_node->location[0] += offset.x;
468 new_node->location[1] += offset.y;
469 }
470 }
471
472 remap_node_pairing(tree, node_map);
473
474 for (bNode *new_node : node_map.values()) {
476 }
477
478 /* Add links between existing nodes. */
479 for (const ClipboardLink &link : clipboard.links) {
480 bNode *from_node = node_map.lookup_default(link.from_node, nullptr);
481 bNode *to_node = node_map.lookup_default(link.to_node, nullptr);
482 if (!from_node || !to_node) {
483 continue;
484 }
485 bNodeSocket *from = bke::node_find_socket(*from_node, SOCK_OUT, link.from_socket.c_str());
486 bNodeSocket *to = bke::node_find_socket(*to_node, SOCK_IN, link.to_socket.c_str());
487 if (!from || !to) {
488 continue;
489 }
490 bNodeLink &new_link = bke::node_add_link(tree, *from_node, *from, *to_node, *to);
492 }
493
494 tree.ensure_topology_cache();
495 for (bNode *new_node : node_map.values()) {
496 /* Update multi input socket indices in case all connected nodes weren't copied. */
498 }
499
501 /* Pasting nodes can create arbitrary new relations because nodes can reference IDs. */
503
504 return OPERATOR_FINISHED;
505}
506
508 wmOperator *op,
509 const wmEvent *event)
510{
511 const ARegion *region = CTX_wm_region(C);
512 float2 cursor;
513 UI_view2d_region_to_view(&region->v2d, event->mval[0], event->mval[1], &cursor.x, &cursor.y);
514 RNA_float_set_array(op->ptr, "offset", cursor);
515 return node_clipboard_paste_exec(C, op);
516}
517
519{
520 ot->name = "Paste from Clipboard";
521 ot->description = "Paste nodes from the internal clipboard to the active node tree";
522 ot->idname = "NODE_OT_clipboard_paste";
523
527
529
531 ot->srna,
532 "offset",
533 2,
534 nullptr,
535 -FLT_MAX,
536 FLT_MAX,
537 "Location",
538 "The 2D view location for the center of the new nodes, or unchanged if not set",
539 -FLT_MAX,
540 FLT_MAX);
543}
544
546
547} // namespace blender::ed::space_node
548
550{
551 using namespace blender::ed::space_node;
552 NodeClipboard &clipboard = get_node_clipboard();
553 clipboard.clear();
554}
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:353
@ 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 LibraryForeachIDFlag flag)
Definition lib_query.cc:451
@ IDWALK_RET_NOP
@ 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
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
@ MAIN_IDMAP_TYPE_NAME
void BKE_main_ensure_invariants(Main &bmain, std::optional< blender::Span< ID * > > modified_ids=std::nullopt)
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:126
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
#define LISTBASE_FOREACH(type, var, list)
BLI_INLINE float BLI_rctf_cent_y(const struct rctf *rct)
Definition BLI_rect.h:189
BLI_INLINE float BLI_rctf_cent_x(const struct rctf *rct)
Definition BLI_rect.h:185
void DEG_relations_tag_update(Main *bmain)
@ SOCK_OUT
@ SOCK_IN
@ NODE_ACTIVE
@ NODE_SELECT
@ SOCK_DISPLAY_SHAPE_CIRCLE
#define UI_SCALE_FAC
@ OPERATOR_CANCELLED
@ OPERATOR_FINISHED
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:330
@ PROP_HIDDEN
Definition RNA_types.hh:324
#define C
Definition RandGen.cpp:29
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:1667
@ OPTYPE_UNDO
Definition WM_types.hh:182
@ OPTYPE_REGISTER
Definition WM_types.hh:180
BMesh const char void * data
void clear()
Definition BLI_map.hh:1038
ValueIterator values() const &
Definition BLI_map.hh:884
bool add(const Key &key, const Value &value)
Definition BLI_map.hh:295
const Value & lookup(const Key &key) const
Definition BLI_map.hh:545
Value lookup_default(const Key &key, const Value &default_value) const
Definition BLI_map.hh:570
void add_new(const Key &key, const Value &value)
Definition BLI_map.hh:265
bool contains(const Key &key) const
Definition BLI_map.hh:353
void append(const T &value)
void clear_and_shrink()
void ED_node_clipboard_free()
Definition clipboard.cc:549
#define SELECT
KDTree_3d * tree
#define ID_IS_LINKED(_id)
#define GS(a)
Library * search_filepath_abs(ListBase *libraries, blender::StringRef filepath_abs)
Definition library.cc:359
bNodeSocket * node_find_socket(bNode &node, eNodeSocketInOut in_out, StringRef identifier)
Definition node.cc:2864
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:3857
void node_node_foreach_id(bNode *node, LibraryForeachIDData *data)
Definition node.cc:352
void node_free_node(bNodeTree *tree, bNode &node)
Definition node.cc:4579
bool node_set_selected(bNode &node, bool select)
Definition node.cc:4967
void node_detach_node(bNodeTree &ntree, bNode &node)
Definition node.cc:4263
bNodeLink & node_add_link(bNodeTree &ntree, bNode &fromnode, bNodeSocket &fromsock, bNode &tonode, bNodeSocket &tosock)
Definition node.cc:4087
bool node_declaration_ensure(bNodeTree &ntree, bNode &node)
Definition node.cc:5090
void node_set_active(bNodeTree &ntree, bNode &node)
Definition node.cc:4996
void NODE_OT_clipboard_copy(wmOperatorType *ot)
Definition clipboard.cc:335
void node_select_paired(bNodeTree &node_tree)
bool node_deselect_all(bNodeTree &node_tree)
void NODE_OT_clipboard_paste(wmOperatorType *ot)
Definition clipboard.cc:518
static wmOperatorStatus node_clipboard_paste_exec(bContext *C, wmOperator *op)
Definition clipboard.cc:353
static wmOperatorStatus node_clipboard_copy_exec(bContext *C, wmOperator *)
Definition clipboard.cc:285
void remap_node_pairing(bNodeTree &dst_tree, const Map< const bNode *, bNode * > &node_map)
static NodeClipboard & get_node_clipboard()
Definition clipboard.cc:275
void update_multi_input_indices_for_removed_links(bNode &node)
static wmOperatorStatus node_clipboard_paste_invoke(bContext *C, wmOperator *op, const wmEvent *event)
Definition clipboard.cc:507
VecBase< float, 2 > float2
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:404
struct Library * lib
Definition DNA_ID.h:410
char name[66]
Definition DNA_ID.h:415
LibraryRuntimeHandle * runtime
Definition DNA_ID.h:516
ListBase libraries
Definition BKE_main.hh:246
struct bNodeTree * edittree
bNodeTypeHandle * typeinfo
ListBase inputs
char name[64]
bNodeRuntimeHandle * runtime
ListBase outputs
Vector< NodeClipboardItem > nodes
Definition clipboard.cc:79
void paste_update_node_id_references(bNode &node)
Definition clipboard.cc:187
bool paste_validate_id_references(Main &bmain)
Definition clipboard.cc:108
Map< ID *, NodeClipboardItemIDInfo > old_ids_to_idinfo
Definition clipboard.cc:86
Vector< ClipboardLink > links
Definition clipboard.cc:80
void copy_add_node(const bNode &node, Map< const bNode *, bNode * > &node_map, Map< const bNodeSocket *, bNodeSocket * > &socket_map)
Definition clipboard.cc:227
int mval[2]
Definition WM_types.hh:760
struct ReportList * reports
struct PointerRNA * ptr
wmOperatorType * ot
Definition wm_files.cc:4226