Cleanup: Simplify node clipboard, use Vector instead of ListBase
- Move from blenkernel to the node editor, the only place it was used - Use two vectors instead of ListBase - Remove define for validating the clipboard, which shouldn't be skipped - Comment formatting, other small cleanups to whitespace Differential Revision: https://developer.blender.org/D16880
This commit is contained in:
parent
81935098f1
commit
c8741a3c03
@ -713,6 +713,8 @@ bNode *node_copy_with_mapping(bNodeTree *dst_tree,
|
||||
|
||||
bNode *node_copy(bNodeTree *dst_tree, const bNode &src_node, int flag, bool use_unique);
|
||||
|
||||
void node_free_node(bNodeTree *tree, bNode *node);
|
||||
|
||||
} // namespace blender::bke
|
||||
|
||||
#endif
|
||||
@ -865,20 +867,6 @@ bool nodeDeclarationEnsureOnOutdatedNode(struct bNodeTree *ntree, struct bNode *
|
||||
*/
|
||||
void nodeSocketDeclarationsUpdate(struct bNode *node);
|
||||
|
||||
/**
|
||||
* Node Clipboard.
|
||||
*/
|
||||
void BKE_node_clipboard_clear(void);
|
||||
void BKE_node_clipboard_free(void);
|
||||
/**
|
||||
* Return false when one or more ID's are lost.
|
||||
*/
|
||||
bool BKE_node_clipboard_validate(void);
|
||||
void BKE_node_clipboard_add_node(struct bNode *node);
|
||||
void BKE_node_clipboard_add_link(struct bNodeLink *link);
|
||||
const struct ListBase *BKE_node_clipboard_get_nodes(void);
|
||||
const struct ListBase *BKE_node_clipboard_get_links(void);
|
||||
|
||||
/**
|
||||
* Node Instance Hash.
|
||||
*/
|
||||
|
@ -118,7 +118,6 @@ static CLG_LogRef LOG = {"bke.node"};
|
||||
static void ntree_set_typeinfo(bNodeTree *ntree, bNodeTreeType *typeinfo);
|
||||
static void node_socket_copy(bNodeSocket *sock_dst, const bNodeSocket *sock_src, const int flag);
|
||||
static void free_localized_node_groups(bNodeTree *ntree);
|
||||
static void node_free_node(bNodeTree *ntree, bNode *node);
|
||||
static void node_socket_interface_free(bNodeTree * /*ntree*/,
|
||||
bNodeSocket *sock,
|
||||
const bool do_id_user);
|
||||
@ -243,7 +242,7 @@ static void ntree_free_data(ID *id)
|
||||
BLI_freelistN(&ntree->links);
|
||||
|
||||
LISTBASE_FOREACH_MUTABLE (bNode *, node, &ntree->nodes) {
|
||||
node_free_node(ntree, node);
|
||||
blender::bke::node_free_node(ntree, node);
|
||||
}
|
||||
|
||||
/* free interface sockets */
|
||||
@ -2951,12 +2950,14 @@ void nodeRebuildIDVector(bNodeTree *node_tree)
|
||||
}
|
||||
}
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
/**
|
||||
* Free the node itself.
|
||||
*
|
||||
* \note: ID user refcounting and changing the `nodes_by_id` vector are up to the caller.
|
||||
*/
|
||||
static void node_free_node(bNodeTree *ntree, bNode *node)
|
||||
void node_free_node(bNodeTree *ntree, bNode *node)
|
||||
{
|
||||
/* since it is called while free database, node->id is undefined */
|
||||
|
||||
@ -3011,6 +3012,8 @@ static void node_free_node(bNodeTree *ntree, bNode *node)
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::bke
|
||||
|
||||
void ntreeFreeLocalNode(bNodeTree *ntree, bNode *node)
|
||||
{
|
||||
/* For removing nodes while editing localized node trees. */
|
||||
@ -3021,7 +3024,7 @@ void ntreeFreeLocalNode(bNodeTree *ntree, bNode *node)
|
||||
nodeUnlinkNode(ntree, node);
|
||||
node_unlink_attached(ntree, node);
|
||||
|
||||
node_free_node(ntree, node);
|
||||
blender::bke::node_free_node(ntree, node);
|
||||
nodeRebuildIDVector(ntree);
|
||||
}
|
||||
|
||||
@ -3081,7 +3084,7 @@ void nodeRemoveNode(Main *bmain, bNodeTree *ntree, bNode *node, bool do_id_user)
|
||||
node_unlink_attached(ntree, node);
|
||||
|
||||
/* Free node itself. */
|
||||
node_free_node(ntree, node);
|
||||
blender::bke::node_free_node(ntree, node);
|
||||
nodeRebuildIDVector(ntree);
|
||||
}
|
||||
|
||||
@ -3649,149 +3652,6 @@ void nodeInternalLinks(bNode *node, bNodeLink ***r_links, int *r_len)
|
||||
*r_len = node->runtime->internal_links.size();
|
||||
}
|
||||
|
||||
/* ************** Node Clipboard *********** */
|
||||
|
||||
#define USE_NODE_CB_VALIDATE
|
||||
|
||||
#ifdef USE_NODE_CB_VALIDATE
|
||||
/**
|
||||
* This data structure is to validate the node on creation,
|
||||
* otherwise we may reference missing data.
|
||||
*
|
||||
* Currently its only used for ID's, but nodes may one day
|
||||
* reference other pointers which need validation.
|
||||
*/
|
||||
struct bNodeClipboardExtraInfo {
|
||||
struct bNodeClipboardExtraInfo *next, *prev;
|
||||
ID *id;
|
||||
char id_name[MAX_ID_NAME];
|
||||
char library_name[FILE_MAX];
|
||||
};
|
||||
#endif /* USE_NODE_CB_VALIDATE */
|
||||
|
||||
struct bNodeClipboard {
|
||||
ListBase nodes;
|
||||
|
||||
#ifdef USE_NODE_CB_VALIDATE
|
||||
ListBase nodes_extra_info;
|
||||
#endif
|
||||
|
||||
ListBase links;
|
||||
};
|
||||
|
||||
static bNodeClipboard node_clipboard = {{nullptr}};
|
||||
|
||||
void BKE_node_clipboard_clear()
|
||||
{
|
||||
LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &node_clipboard.links) {
|
||||
nodeRemLink(nullptr, link);
|
||||
}
|
||||
BLI_listbase_clear(&node_clipboard.links);
|
||||
|
||||
LISTBASE_FOREACH_MUTABLE (bNode *, node, &node_clipboard.nodes) {
|
||||
node_free_node(nullptr, node);
|
||||
}
|
||||
BLI_listbase_clear(&node_clipboard.nodes);
|
||||
|
||||
#ifdef USE_NODE_CB_VALIDATE
|
||||
BLI_freelistN(&node_clipboard.nodes_extra_info);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool BKE_node_clipboard_validate()
|
||||
{
|
||||
bool ok = true;
|
||||
|
||||
#ifdef USE_NODE_CB_VALIDATE
|
||||
bNodeClipboardExtraInfo *node_info;
|
||||
bNode *node;
|
||||
|
||||
/* lists must be aligned */
|
||||
BLI_assert(BLI_listbase_count(&node_clipboard.nodes) ==
|
||||
BLI_listbase_count(&node_clipboard.nodes_extra_info));
|
||||
|
||||
for (node = (bNode *)node_clipboard.nodes.first,
|
||||
node_info = (bNodeClipboardExtraInfo *)node_clipboard.nodes_extra_info.first;
|
||||
node;
|
||||
node = (bNode *)node->next, node_info = (bNodeClipboardExtraInfo *)node_info->next) {
|
||||
/* validate the node against the stored node info */
|
||||
|
||||
/* re-assign each loop since we may clear,
|
||||
* open a new file where the ID is valid, and paste again */
|
||||
node->id = node_info->id;
|
||||
|
||||
/* currently only validate the ID */
|
||||
if (node->id) {
|
||||
/* We want to search into current blend file, so using G_MAIN is valid here too. */
|
||||
ListBase *lb = which_libbase(G_MAIN, GS(node_info->id_name));
|
||||
BLI_assert(lb != nullptr);
|
||||
|
||||
if (BLI_findindex(lb, node_info->id) == -1) {
|
||||
/* May assign null. */
|
||||
node->id = (ID *)BLI_findstring(lb, node_info->id_name + 2, offsetof(ID, name) + 2);
|
||||
|
||||
if (node->id == nullptr) {
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* USE_NODE_CB_VALIDATE */
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
void BKE_node_clipboard_add_node(bNode *node)
|
||||
{
|
||||
#ifdef USE_NODE_CB_VALIDATE
|
||||
/* add extra info */
|
||||
bNodeClipboardExtraInfo *node_info = (bNodeClipboardExtraInfo *)MEM_mallocN(
|
||||
sizeof(bNodeClipboardExtraInfo), __func__);
|
||||
|
||||
node_info->id = node->id;
|
||||
if (node->id) {
|
||||
BLI_strncpy(node_info->id_name, node->id->name, sizeof(node_info->id_name));
|
||||
if (ID_IS_LINKED(node->id)) {
|
||||
BLI_strncpy(
|
||||
node_info->library_name, node->id->lib->filepath_abs, sizeof(node_info->library_name));
|
||||
}
|
||||
else {
|
||||
node_info->library_name[0] = '\0';
|
||||
}
|
||||
}
|
||||
else {
|
||||
node_info->id_name[0] = '\0';
|
||||
node_info->library_name[0] = '\0';
|
||||
}
|
||||
BLI_addtail(&node_clipboard.nodes_extra_info, node_info);
|
||||
/* end extra info */
|
||||
#endif /* USE_NODE_CB_VALIDATE */
|
||||
|
||||
/* add node */
|
||||
BLI_addtail(&node_clipboard.nodes, node);
|
||||
}
|
||||
|
||||
void BKE_node_clipboard_add_link(bNodeLink *link)
|
||||
{
|
||||
BLI_addtail(&node_clipboard.links, link);
|
||||
}
|
||||
|
||||
const ListBase *BKE_node_clipboard_get_nodes()
|
||||
{
|
||||
return &node_clipboard.nodes;
|
||||
}
|
||||
|
||||
const ListBase *BKE_node_clipboard_get_links()
|
||||
{
|
||||
return &node_clipboard.links;
|
||||
}
|
||||
|
||||
void BKE_node_clipboard_free()
|
||||
{
|
||||
BKE_node_clipboard_validate();
|
||||
BKE_node_clipboard_clear();
|
||||
}
|
||||
|
||||
/* Node Instance Hash */
|
||||
|
||||
const bNodeInstanceKey NODE_INSTANCE_KEY_BASE = {5381};
|
||||
|
@ -42,6 +42,10 @@ ENUM_OPERATORS(NodeBorder, NODE_RIGHT)
|
||||
#define NODE_EDGE_PAN_DELAY 0.5f
|
||||
#define NODE_EDGE_PAN_ZOOM_INFLUENCE 0.5f
|
||||
|
||||
/* clipboard.cc */
|
||||
|
||||
void ED_node_clipboard_free(void);
|
||||
|
||||
/* space_node.cc */
|
||||
|
||||
void ED_node_cursor_location_get(const struct SpaceNode *snode, float value[2]);
|
||||
|
@ -31,6 +31,7 @@ set(INC
|
||||
set(SRC
|
||||
add_menu_assets.cc
|
||||
add_node_search.cc
|
||||
clipboard.cc
|
||||
drawnode.cc
|
||||
link_drag_search.cc
|
||||
node_add.cc
|
||||
|
301
source/blender/editors/space_node/clipboard.cc
Normal file
301
source/blender/editors/space_node/clipboard.cc
Normal file
@ -0,0 +1,301 @@
|
||||
/* 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 "DEG_depsgraph_build.h"
|
||||
|
||||
#include "node_intern.hh"
|
||||
|
||||
namespace blender::ed::space_node {
|
||||
|
||||
struct NodeClipboardItem {
|
||||
bNode *node;
|
||||
|
||||
/* 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(bNode *node)
|
||||
{
|
||||
NodeClipboardItem item;
|
||||
item.node = node;
|
||||
item.id = node->id;
|
||||
if (item.id) {
|
||||
item.id_name = node->id->name;
|
||||
if (ID_IS_LINKED(node->id)) {
|
||||
item.library_name = 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 (bNode *node : tree.all_nodes()) {
|
||||
if (node->flag & SELECT) {
|
||||
/* No ID reference counting, this node is virtual, detached from any actual Blender data. */
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
for (bNode *new_node : node_map.values()) {
|
||||
clipboard.add_node(new_node);
|
||||
|
||||
/* 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);
|
||||
|
||||
/* calculate "barycenter" for placing on mouse cursor */
|
||||
float2 center = {0.0f, 0.0f};
|
||||
for (const NodeClipboardItem &item : clipboard.nodes) {
|
||||
center.x += BLI_rctf_cent_x(&item.node->runtime->totr);
|
||||
center.y += BLI_rctf_cent_y(&item.node->runtime->totr);
|
||||
}
|
||||
center /= clipboard.nodes.size();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
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->exec = node_clipboard_paste_exec;
|
||||
ot->poll = ED_operator_node_editable;
|
||||
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
} // 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();
|
||||
}
|
@ -2203,216 +2203,6 @@ void NODE_OT_node_copy_color(wmOperatorType *ot)
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Node Copy to Clipboard Operator
|
||||
* \{ */
|
||||
|
||||
static int node_clipboard_copy_exec(bContext *C, wmOperator * /*op*/)
|
||||
{
|
||||
SpaceNode *snode = CTX_wm_space_node(C);
|
||||
bNodeTree *ntree = snode->edittree;
|
||||
|
||||
ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C));
|
||||
|
||||
/* clear current clipboard */
|
||||
BKE_node_clipboard_clear();
|
||||
|
||||
Map<const bNode *, bNode *> node_map;
|
||||
Map<const bNodeSocket *, bNodeSocket *> socket_map;
|
||||
|
||||
for (bNode *node : ntree->all_nodes()) {
|
||||
if (node->flag & SELECT) {
|
||||
/* No ID refcounting, 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);
|
||||
}
|
||||
}
|
||||
|
||||
for (bNode *new_node : node_map.values()) {
|
||||
BKE_node_clipboard_add_node(new_node);
|
||||
|
||||
/* 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(ntree, new_node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Copy links between selected nodes. */
|
||||
LISTBASE_FOREACH (bNodeLink *, link, &ntree->links) {
|
||||
BLI_assert(link->tonode);
|
||||
BLI_assert(link->fromnode);
|
||||
if (link->tonode->flag & NODE_SELECT && link->fromnode->flag & NODE_SELECT) {
|
||||
bNodeLink *newlink = MEM_cnew<bNodeLink>(__func__);
|
||||
newlink->flag = link->flag;
|
||||
newlink->tonode = node_map.lookup(link->tonode);
|
||||
newlink->tosock = socket_map.lookup(link->tosock);
|
||||
newlink->fromnode = node_map.lookup(link->fromnode);
|
||||
newlink->fromsock = socket_map.lookup(link->fromsock);
|
||||
newlink->multi_input_socket_index = link->multi_input_socket_index;
|
||||
|
||||
BKE_node_clipboard_add_link(newlink);
|
||||
}
|
||||
}
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
void NODE_OT_clipboard_copy(wmOperatorType *ot)
|
||||
{
|
||||
/* identifiers */
|
||||
ot->name = "Copy to Clipboard";
|
||||
ot->description = "Copies selected nodes to the clipboard";
|
||||
ot->idname = "NODE_OT_clipboard_copy";
|
||||
|
||||
/* api callbacks */
|
||||
ot->exec = node_clipboard_copy_exec;
|
||||
ot->poll = ED_operator_node_active;
|
||||
|
||||
/* flags */
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Node Paste from Clipboard
|
||||
* \{ */
|
||||
|
||||
static int node_clipboard_paste_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
SpaceNode *snode = CTX_wm_space_node(C);
|
||||
bNodeTree *ntree = snode->edittree;
|
||||
|
||||
/* validate pointers in the clipboard */
|
||||
bool is_clipboard_valid = BKE_node_clipboard_validate();
|
||||
const ListBase *clipboard_nodes_lb = BKE_node_clipboard_get_nodes();
|
||||
const ListBase *clipboard_links_lb = BKE_node_clipboard_get_links();
|
||||
|
||||
if (BLI_listbase_is_empty(clipboard_nodes_lb)) {
|
||||
BKE_report(op->reports, RPT_ERROR, "Clipboard is empty");
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
/* only warn */
|
||||
if (is_clipboard_valid == false) {
|
||||
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));
|
||||
|
||||
/* deselect old nodes */
|
||||
node_deselect_all(*ntree);
|
||||
|
||||
/* calculate "barycenter" for placing on mouse cursor */
|
||||
float2 center = {0.0f, 0.0f};
|
||||
int num_nodes = 0;
|
||||
LISTBASE_FOREACH_INDEX (bNode *, node, clipboard_nodes_lb, num_nodes) {
|
||||
center.x += BLI_rctf_cent_x(&node->runtime->totr);
|
||||
center.y += BLI_rctf_cent_y(&node->runtime->totr);
|
||||
}
|
||||
mul_v2_fl(center, 1.0 / num_nodes);
|
||||
|
||||
Map<const bNode *, bNode *> node_map;
|
||||
Map<const bNodeSocket *, bNodeSocket *> socket_map;
|
||||
|
||||
/* copy valid nodes from clipboard */
|
||||
LISTBASE_FOREACH (bNode *, node, clipboard_nodes_lb) {
|
||||
const char *disabled_hint = nullptr;
|
||||
if (node->typeinfo->poll_instance &&
|
||||
node->typeinfo->poll_instance(node, ntree, &disabled_hint)) {
|
||||
bNode *new_node = bke::node_copy_with_mapping(
|
||||
ntree, *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,
|
||||
ntree->id.name + 2,
|
||||
disabled_hint);
|
||||
}
|
||||
else {
|
||||
BKE_reportf(op->reports,
|
||||
RPT_ERROR,
|
||||
"Cannot add node %s into node tree %s",
|
||||
node->name,
|
||||
ntree->id.name + 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (bNode *new_node : node_map.values()) {
|
||||
/* pasted nodes are selected */
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Add links between existing nodes. */
|
||||
LISTBASE_FOREACH (bNodeLink *, link, clipboard_links_lb) {
|
||||
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(ntree,
|
||||
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;
|
||||
}
|
||||
}
|
||||
ntree->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, snode->edittree);
|
||||
/* Pasting nodes can create arbitrary new relations, because nodes can reference IDs. */
|
||||
DEG_relations_tag_update(bmain);
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
void NODE_OT_clipboard_paste(wmOperatorType *ot)
|
||||
{
|
||||
/* identifiers */
|
||||
ot->name = "Paste from Clipboard";
|
||||
ot->description = "Pastes nodes from the clipboard to the active node tree";
|
||||
ot->idname = "NODE_OT_clipboard_paste";
|
||||
|
||||
/* api callbacks */
|
||||
ot->exec = node_clipboard_paste_exec;
|
||||
ot->poll = ED_operator_node_editable;
|
||||
|
||||
/* flags */
|
||||
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Node-Tree Add Interface Socket Operator
|
||||
* \{ */
|
||||
|
@ -537,7 +537,7 @@ void WM_exit_ex(bContext *C, const bool do_python)
|
||||
BKE_tracking_clipboard_free();
|
||||
BKE_mask_clipboard_free();
|
||||
BKE_vfont_clipboard_free();
|
||||
BKE_node_clipboard_free();
|
||||
ED_node_clipboard_free();
|
||||
UV_clipboard_free();
|
||||
|
||||
#ifdef WITH_COMPOSITOR_CPU
|
||||
|
Loading…
Reference in New Issue
Block a user