This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/blender/editors/space_node/clipboard.cc

346 lines
9.9 KiB
C++

/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "DNA_space_types.h"
#include "BKE_context.h"
#include "BKE_global.h"
#include "BKE_lib_id.h"
#include "BKE_main.h"
#include "BKE_node.h"
#include "BKE_node_runtime.hh"
#include "BKE_node_tree_update.h"
#include "BKE_report.h"
#include "ED_node.h"
#include "ED_node.hh"
#include "ED_render.h"
#include "ED_screen.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "DEG_depsgraph_build.h"
#include "node_intern.hh"
namespace blender::ed::space_node {
struct NodeClipboardItem {
bNode *node;
/**
* The offset and size of the node from when it was drawn. Stored here since it doesn't remain
* valid for the nodes in the clipboard.
*/
rctf draw_rect;
/* Extra info to validate the node on creation. Otherwise we may reference missing data. */
ID *id;
std::string id_name;
std::string library_name;
};
struct NodeClipboard {
Vector<NodeClipboardItem> nodes;
Vector<bNodeLink> links;
void clear()
{
for (NodeClipboardItem &item : this->nodes) {
bke::node_free_node(nullptr, item.node);
}
this->nodes.clear_and_shrink();
this->links.clear_and_shrink();
}
/**
* Replace node IDs that are no longer available in the current file. Return false when one or
* more IDs are lost.
*/
bool validate()
{
bool ok = true;
for (NodeClipboardItem &item : this->nodes) {
bNode &node = *item.node;
/* Reassign each loop since we may clear, open a new file where the ID is valid, and paste
* again. */
node.id = item.id;
if (node.id) {
const ListBase *lb = which_libbase(G_MAIN, GS(item.id_name.c_str()));
if (BLI_findindex(lb, item.id) == -1) {
/* May assign null. */
node.id = static_cast<ID *>(
BLI_findstring(lb, item.id_name.c_str() + 2, offsetof(ID, name) + 2));
if (!node.id) {
ok = false;
}
}
}
}
return ok;
}
void add_node(const bNode &node,
Map<const bNode *, bNode *> &node_map,
Map<const bNodeSocket *, bNodeSocket *> &socket_map)
{
/* No ID reference-counting, this node is virtual,
* detached from any actual Blender data currently. */
bNode *new_node = bke::node_copy_with_mapping(
nullptr, node, LIB_ID_CREATE_NO_USER_REFCOUNT | LIB_ID_CREATE_NO_MAIN, false, socket_map);
node_map.add_new(&node, new_node);
NodeClipboardItem item;
item.draw_rect = node.runtime->totr;
item.node = new_node;
item.id = new_node->id;
if (item.id) {
item.id_name = new_node->id->name;
if (ID_IS_LINKED(new_node->id)) {
item.library_name = new_node->id->lib->filepath_abs;
}
}
this->nodes.append(std::move(item));
}
};
static NodeClipboard &get_node_clipboard()
{
static NodeClipboard clipboard;
return clipboard;
}
/* -------------------------------------------------------------------- */
/** \name Copy
* \{ */
static int node_clipboard_copy_exec(bContext *C, wmOperator * /*op*/)
{
SpaceNode &snode = *CTX_wm_space_node(C);
bNodeTree &tree = *snode.edittree;
NodeClipboard &clipboard = get_node_clipboard();
clipboard.clear();
Map<const bNode *, bNode *> node_map;
Map<const bNodeSocket *, bNodeSocket *> socket_map;
for (const bNode *node : tree.all_nodes()) {
if (node->flag & SELECT) {
clipboard.add_node(*node, node_map, socket_map);
}
}
for (bNode *new_node : node_map.values()) {
/* Parent pointer must be redirected to new node or detached if parent is not copied. */
if (new_node->parent) {
if (node_map.contains(new_node->parent)) {
new_node->parent = node_map.lookup(new_node->parent);
}
else {
nodeDetachNode(&tree, new_node);
}
}
}
/* Copy links between selected nodes. */
LISTBASE_FOREACH (bNodeLink *, link, &tree.links) {
BLI_assert(link->tonode);
BLI_assert(link->fromnode);
if (link->tonode->flag & NODE_SELECT && link->fromnode->flag & NODE_SELECT) {
bNodeLink new_link{};
new_link.flag = link->flag;
new_link.tonode = node_map.lookup(link->tonode);
new_link.tosock = socket_map.lookup(link->tosock);
new_link.fromnode = node_map.lookup(link->fromnode);
new_link.fromsock = socket_map.lookup(link->fromsock);
new_link.multi_input_socket_index = link->multi_input_socket_index;
clipboard.links.append(new_link);
}
}
return OPERATOR_FINISHED;
}
void NODE_OT_clipboard_copy(wmOperatorType *ot)
{
ot->name = "Copy to Clipboard";
ot->description = "Copies selected nodes to the clipboard";
ot->idname = "NODE_OT_clipboard_copy";
ot->exec = node_clipboard_copy_exec;
ot->poll = ED_operator_node_active;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Paste
* \{ */
static int node_clipboard_paste_exec(bContext *C, wmOperator *op)
{
SpaceNode &snode = *CTX_wm_space_node(C);
bNodeTree &tree = *snode.edittree;
NodeClipboard &clipboard = get_node_clipboard();
const bool is_valid = clipboard.validate();
if (clipboard.nodes.is_empty()) {
BKE_report(op->reports, RPT_ERROR, "Clipboard is empty");
return OPERATOR_CANCELLED;
}
if (!is_valid) {
BKE_report(op->reports,
RPT_WARNING,
"Some nodes references could not be restored, will be left empty");
}
ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C));
node_deselect_all(tree);
Map<const bNode *, bNode *> node_map;
Map<const bNodeSocket *, bNodeSocket *> socket_map;
/* copy valid nodes from clipboard */
for (NodeClipboardItem &item : clipboard.nodes) {
const bNode &node = *item.node;
const char *disabled_hint = nullptr;
if (node.typeinfo->poll_instance &&
node.typeinfo->poll_instance(&node, &tree, &disabled_hint)) {
bNode *new_node = bke::node_copy_with_mapping(
&tree, node, LIB_ID_COPY_DEFAULT, true, socket_map);
node_map.add_new(&node, new_node);
}
else {
if (disabled_hint) {
BKE_reportf(op->reports,
RPT_ERROR,
"Cannot add node %s into node tree %s: %s",
node.name,
tree.id.name + 2,
disabled_hint);
}
else {
BKE_reportf(op->reports,
RPT_ERROR,
"Cannot add node %s into node tree %s",
node.name,
tree.id.name + 2);
}
}
}
for (bNode *new_node : node_map.values()) {
nodeSetSelected(new_node, true);
/* The parent pointer must be redirected to new node. */
if (new_node->parent) {
if (node_map.contains(new_node->parent)) {
new_node->parent = node_map.lookup(new_node->parent);
}
}
}
PropertyRNA *offset_prop = RNA_struct_find_property(op->ptr, "offset");
if (RNA_property_is_set(op->ptr, offset_prop)) {
float2 center(0);
for (NodeClipboardItem &item : clipboard.nodes) {
center.x += BLI_rctf_cent_x(&item.draw_rect);
center.y += BLI_rctf_cent_y(&item.draw_rect);
}
/* DPI factor needs to be removed when computing a View2D offset from drawing rects. */
center /= clipboard.nodes.size();
float2 mouse_location;
RNA_property_float_get_array(op->ptr, offset_prop, mouse_location);
const float2 offset = (mouse_location - center) / UI_DPI_FAC;
for (bNode *new_node : node_map.values()) {
new_node->locx += offset.x;
new_node->locy += offset.y;
}
}
/* Add links between existing nodes. */
for (const bNodeLink &link : clipboard.links) {
const bNode *fromnode = link.fromnode;
const bNode *tonode = link.tonode;
if (node_map.lookup_key_ptr(fromnode) && node_map.lookup_key_ptr(tonode)) {
bNodeLink *new_link = nodeAddLink(&tree,
node_map.lookup(fromnode),
socket_map.lookup(link.fromsock),
node_map.lookup(tonode),
socket_map.lookup(link.tosock));
new_link->multi_input_socket_index = link.multi_input_socket_index;
}
}
tree.ensure_topology_cache();
for (bNode *new_node : node_map.values()) {
/* Update multi input socket indices in case all connected nodes weren't copied. */
update_multi_input_indices_for_removed_links(*new_node);
}
Main *bmain = CTX_data_main(C);
ED_node_tree_propagate_change(C, bmain, &tree);
/* Pasting nodes can create arbitrary new relations because nodes can reference IDs. */
DEG_relations_tag_update(bmain);
return OPERATOR_FINISHED;
}
static int node_clipboard_paste_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
const ARegion *region = CTX_wm_region(C);
float2 cursor;
UI_view2d_region_to_view(&region->v2d, event->mval[0], event->mval[1], &cursor.x, &cursor.y);
RNA_float_set_array(op->ptr, "offset", cursor);
return node_clipboard_paste_exec(C, op);
}
void NODE_OT_clipboard_paste(wmOperatorType *ot)
{
ot->name = "Paste from Clipboard";
ot->description = "Pastes nodes from the clipboard to the active node tree";
ot->idname = "NODE_OT_clipboard_paste";
ot->invoke = node_clipboard_paste_invoke;
ot->exec = node_clipboard_paste_exec;
ot->poll = ED_operator_node_editable;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
PropertyRNA *prop = RNA_def_float_array(
ot->srna,
"offset",
2,
nullptr,
-FLT_MAX,
FLT_MAX,
"Location",
"The 2D view location for the center of the new nodes, or unchanged if not set",
-FLT_MAX,
FLT_MAX);
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
RNA_def_property_flag(prop, PROP_HIDDEN);
}
/** \} */
} // namespace blender::ed::space_node
void ED_node_clipboard_free()
{
using namespace blender::ed::space_node;
NodeClipboard &clipboard = get_node_clipboard();
clipboard.validate();
clipboard.clear();
}