Files
test2/source/blender/editors/space_node/clipboard.cc
Campbell Barton e955c94ed3 License Headers: Set copyright to "Blender Authors", add AUTHORS
Listing the "Blender Foundation" as copyright holder implied the Blender
Foundation holds copyright to files which may include work from many
developers.

While keeping copyright on headers makes sense for isolated libraries,
Blender's own code may be refactored or moved between files in a way
that makes the per file copyright holders less meaningful.

Copyright references to the "Blender Foundation" have been replaced with
"Blender Authors", with the exception of `./extern/` since these this
contains libraries which are more isolated, any changed to license
headers there can be handled on a case-by-case basis.

Some directories in `./intern/` have also been excluded:

- `./intern/cycles/` it's own `AUTHORS` file is planned.
- `./intern/opensubdiv/`.

An "AUTHORS" file has been added, using the chromium projects authors
file as a template.

Design task: #110784

Ref !110783.
2023-08-16 00:20:26 +10:00

367 lines
11 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* 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.hh"
#include "BKE_node_runtime.hh"
#include "BKE_node_tree_update.h"
#include "BKE_report.h"
#include "ED_node.hh"
#include "ED_render.hh"
#include "ED_screen.hh"
#include "NOD_socket.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#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 = "Copy the selected nodes to the internal 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, "The internal 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);
/* Reset socket shape in case a node is copied to a different tree type. */
LISTBASE_FOREACH (bNodeSocket *, socket, &new_node->inputs) {
socket->display_shape = SOCK_DISPLAY_SHAPE_CIRCLE;
}
LISTBASE_FOREACH (bNodeSocket *, socket, &new_node->outputs) {
socket->display_shape = SOCK_DISPLAY_SHAPE_CIRCLE;
}
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);
new_node->flag &= ~NODE_ACTIVE;
/* 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_SCALE_FAC;
for (bNode *new_node : node_map.values()) {
/* Skip the offset for parented nodes since the location is in parent space. */
if (new_node->parent == nullptr) {
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;
}
}
for (bNode *new_node : node_map.values()) {
bke::nodeDeclarationEnsure(&tree, new_node);
}
remap_node_pairing(tree, node_map);
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 = "Paste nodes from the internal 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();
}