Clipboard feature for nodes. With the Copy operator a copy of all selected nodes...
authorLukas Toenne <lukas.toenne@googlemail.com>
Thu, 2 Aug 2012 09:52:37 +0000 (09:52 +0000)
committerLukas Toenne <lukas.toenne@googlemail.com>
Thu, 2 Aug 2012 09:52:37 +0000 (09:52 +0000)
Currently does not support copying of animation data. This would require copying of individual fcurves etc. between data block, which is not implemented yet.

Also it is currently possible to circumvent some constraints of the nodes, in particular for node groups (e.g. no groups inside groups, render layer not inside groups).

release/scripts/startup/bl_ui/space_node.py
source/blender/blenkernel/BKE_node.h
source/blender/blenkernel/intern/node.c
source/blender/editors/space_node/node_edit.c
source/blender/editors/space_node/node_intern.h
source/blender/editors/space_node/node_ops.c

index e66d8f70c81886e52795871419a715c364958928..adcf5a9e9dfe0bdd4d348f3ceefa590ca611747d 100644 (file)
@@ -94,6 +94,10 @@ class NODE_HT_header(Header):
         if toolsettings.snap_node_element != 'INCREMENT':
             row.prop(toolsettings, "snap_target", text="")
 
+        row = layout.row(align=True)
+        row.operator("node.clipboard_copy", text="", icon='COPYDOWN')
+        row.operator("node.clipboard_paste", text="", icon='PASTEDOWN')
+
         layout.template_running_jobs()
 
 
index 93b5e74898797c26ce048e845d3c72863169de1a..00bdbbf453777ae0568fa24191bc003d5acf9247 100644 (file)
@@ -384,6 +384,13 @@ void            nodeFreePreview(struct bNode *node);
 int             nodeSocketIsHidden(struct bNodeSocket *sock);
 void            nodeSocketSetType(struct bNodeSocket *sock, int type);
 
+/* Node Clipboard */
+void nodeClipboardClear(void);
+void nodeClipboardAddNode(struct bNode *node);
+void nodeClipboardAddLink(struct bNodeLink *link);
+const struct ListBase *nodeClipboardGetNodes(void);
+const struct ListBase *nodeClipboardGetLinks(void);
+
 /* ************** NODE TYPE ACCESS *************** */
 
 struct bNodeTemplate nodeMakeTemplate(struct bNode *node);
index 592bc10424f54dc2d07d59ddbf78f5d20589d538..565e2de79f8a8ef1242d99df4a8ad8c41aaef23f 100644 (file)
@@ -347,9 +347,12 @@ bNode *nodeCopyNode(struct bNodeTree *ntree, struct bNode *node)
        bNodeSocket *sock, *oldsock;
 
        *nnode = *node;
-       nodeUniqueName(ntree, nnode);
-       
-       BLI_addtail(&ntree->nodes, nnode);
+       /* can be called for nodes outside a node tree (e.g. clipboard) */
+       if (ntree) {
+               nodeUniqueName(ntree, nnode);
+
+               BLI_addtail(&ntree->nodes, nnode);
+       }
 
        BLI_duplicatelist(&nnode->inputs, &node->inputs);
        oldsock = node->inputs.first;
@@ -390,7 +393,8 @@ bNode *nodeCopyNode(struct bNodeTree *ntree, struct bNode *node)
        nnode->new_node = NULL;
        nnode->preview = NULL;
        
-       ntree->update |= NTREE_UPDATE_NODES;
+       if (ntree)
+               ntree->update |= NTREE_UPDATE_NODES;
        
        return nnode;
 }
@@ -417,7 +421,7 @@ bNodeLink *nodeAddLink(bNodeTree *ntree, bNode *fromnode, bNodeSocket *fromsock,
                                from = -1;  /* OK but flip */
                }
        }
-       else {
+       else if (ntree) {
                /* check tree sockets */
                for (sock = ntree->inputs.first; sock; sock = sock->next)
                        if (sock == fromsock)
@@ -446,7 +450,7 @@ bNodeLink *nodeAddLink(bNodeTree *ntree, bNode *fromnode, bNodeSocket *fromsock,
                                to = -1;  /* OK but flip */
                }
        }
-       else {
+       else if (ntree) {
                /* check tree sockets */
                for (sock = ntree->outputs.first; sock; sock = sock->next)
                        if (sock == tosock)
@@ -464,7 +468,8 @@ bNodeLink *nodeAddLink(bNodeTree *ntree, bNode *fromnode, bNodeSocket *fromsock,
        
        if (from >= 0 && to >= 0) {
                link = MEM_callocN(sizeof(bNodeLink), "link");
-               BLI_addtail(&ntree->links, link);
+               if (ntree)
+                       BLI_addtail(&ntree->links, link);
                link->fromnode = fromnode;
                link->fromsock = fromsock;
                link->tonode = tonode;
@@ -472,26 +477,32 @@ bNodeLink *nodeAddLink(bNodeTree *ntree, bNode *fromnode, bNodeSocket *fromsock,
        }
        else if (from <= 0 && to <= 0) {
                link = MEM_callocN(sizeof(bNodeLink), "link");
-               BLI_addtail(&ntree->links, link);
+               if (ntree)
+                       BLI_addtail(&ntree->links, link);
                link->fromnode = tonode;
                link->fromsock = tosock;
                link->tonode = fromnode;
                link->tosock = fromsock;
        }
        
-       ntree->update |= NTREE_UPDATE_LINKS;
+       if (ntree)
+               ntree->update |= NTREE_UPDATE_LINKS;
        
        return link;
 }
 
 void nodeRemLink(bNodeTree *ntree, bNodeLink *link)
 {
-       BLI_remlink(&ntree->links, link);
+       /* can be called for links outside a node tree (e.g. clipboard) */
+       if (ntree)
+               BLI_remlink(&ntree->links, link);
+
        if (link->tosock)
                link->tosock->link = NULL;
        MEM_freeN(link);
        
-       ntree->update |= NTREE_UPDATE_LINKS;
+       if (ntree)
+               ntree->update |= NTREE_UPDATE_LINKS;
 }
 
 void nodeRemSocketLinks(bNodeTree *ntree, bNodeSocket *sock)
@@ -885,20 +896,24 @@ static void node_unlink_attached(bNodeTree *ntree, bNode *parent)
 
 void nodeFreeNode(bNodeTree *ntree, bNode *node)
 {
-       bNodeTreeType *treetype = ntreeGetType(ntree->type);
        bNodeSocket *sock, *nextsock;
        
-       /* remove all references to this node */
-       nodeUnlinkNode(ntree, node);
-       node_unlink_attached(ntree, node);
-       
-       BLI_remlink(&ntree->nodes, node);
+       /* can be called for nodes outside a node tree (e.g. clipboard) */
+       if (ntree) {
+               bNodeTreeType *treetype = ntreeGetType(ntree->type);
+
+               /* remove all references to this node */
+               nodeUnlinkNode(ntree, node);
+               node_unlink_attached(ntree, node);
+
+               BLI_remlink(&ntree->nodes, node);
+
+               if (treetype->free_node_cache)
+                       treetype->free_node_cache(ntree, node);
+       }
        
        /* since it is called while free database, node->id is undefined */
        
-       if (treetype->free_node_cache)
-               treetype->free_node_cache(ntree, node);
-       
        if (node->typeinfo && node->typeinfo->freestoragefunc)
                node->typeinfo->freestoragefunc(node);
        
@@ -917,7 +932,8 @@ void nodeFreeNode(bNodeTree *ntree, bNode *node)
 
        MEM_freeN(node);
        
-       ntree->update |= NTREE_UPDATE_NODES;
+       if (ntree)
+               ntree->update |= NTREE_UPDATE_NODES;
 }
 
 /* do not free ntree itself here, BKE_libblock_free calls this function too */
@@ -1388,6 +1404,53 @@ void nodeSocketSetType(bNodeSocket *sock, int type)
        node_socket_free_default_value(old_type, old_default_value);
 }
 
+/* ************** Node Clipboard *********** */
+
+typedef struct bNodeClipboard {
+       ListBase nodes;
+       ListBase links;
+} bNodeClipboard;
+
+bNodeClipboard node_clipboard;
+
+void nodeClipboardClear(void)
+{
+       bNode *node, *node_next;
+       bNodeLink *link, *link_next;
+       
+       for (link = node_clipboard.links.first; link; link=link_next) {
+               link_next = link->next;
+               nodeRemLink(NULL, link);
+       }
+       node_clipboard.links.first = node_clipboard.links.last = NULL;
+       
+       for (node = node_clipboard.nodes.first; node; node=node_next) {
+               node_next = node->next;
+               nodeFreeNode(NULL, node);
+       }
+       node_clipboard.nodes.first = node_clipboard.nodes.last = NULL;
+}
+
+void nodeClipboardAddNode(bNode *node)
+{
+       BLI_addtail(&node_clipboard.nodes, node);
+}
+
+void nodeClipboardAddLink(bNodeLink *link)
+{
+       BLI_addtail(&node_clipboard.links, link);
+}
+
+const ListBase *nodeClipboardGetNodes(void)
+{
+       return &node_clipboard.nodes;
+}
+
+const ListBase *nodeClipboardGetLinks(void)
+{
+       return &node_clipboard.links;
+}
+
 /* ************** dependency stuff *********** */
 
 /* node is guaranteed to be not checked before */
@@ -2085,6 +2148,10 @@ static void free_typeinfos(ListBase *list)
 
 void init_nodesystem(void) 
 {
+       /* init clipboard */
+       node_clipboard.nodes.first = node_clipboard.nodes.last = NULL;
+       node_clipboard.links.first = node_clipboard.links.last = NULL;
+       
        registerCompositNodes(ntreeGetType(NTREE_COMPOSIT));
        registerShaderNodes(ntreeGetType(NTREE_SHADER));
        registerTextureNodes(ntreeGetType(NTREE_TEXTURE));
index 4b554185933f91d4ee67cab85152708394d369a2..fa8fa13a6458c20b8c63be9623c30cde91383f70 100644 (file)
@@ -2215,3 +2215,156 @@ void NODE_OT_node_copy_color(wmOperatorType *ot)
        /* flags */
        ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 }
+
+/* ****************** Copy to clipboard ******************* */
+
+static int node_clipboard_copy_exec(bContext *C, wmOperator *UNUSED(op))
+{
+       SpaceNode *snode = CTX_wm_space_node(C);
+       bNodeTree *ntree = snode->edittree;
+       bNode *node, *newnode;
+       bNodeLink *link, *newlink;
+
+       ED_preview_kill_jobs(C);
+
+       /* clear current clipboard */
+       nodeClipboardClear();
+
+       for (node = ntree->nodes.first; node; node = node->next) {
+               if (node->flag & SELECT) {
+                       newnode = nodeCopyNode(NULL, node);
+                       nodeClipboardAddNode(newnode);
+               }
+       }
+
+       /* copy links between selected nodes
+        * NB: this depends on correct node->new_node and sock->new_sock pointers from above copy!
+        */
+       for (link = ntree->links.first; link; link = link->next) {
+               /* This creates new links between copied nodes. */
+               if (link->tonode && (link->tonode->flag & NODE_SELECT) &&
+                   link->fromnode && (link->fromnode->flag & NODE_SELECT))
+               {
+                       newlink = MEM_callocN(sizeof(bNodeLink), "bNodeLink");
+                       newlink->flag = link->flag;
+                       newlink->tonode = link->tonode->new_node;
+                       newlink->tosock = link->tosock->new_sock;
+                       newlink->fromnode = link->fromnode->new_node;
+                       newlink->fromsock = link->fromsock->new_sock;
+
+                       nodeClipboardAddLink(newlink);
+               }
+       }
+
+       /* reparent copied nodes */
+       for (node = ntree->nodes.first; node; node = node->next) {
+               if ((node->flag & SELECT) && node->parent) {
+                       if (node->parent->flag & SELECT)
+                               node->parent = node->parent->new_node;
+                       else
+                               node->parent = NULL;
+               }
+       }
+
+       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;
+}
+
+/* ****************** Paste from clipboard ******************* */
+
+static int node_clipboard_paste_exec(bContext *C, wmOperator *UNUSED(op))
+{
+       SpaceNode *snode = CTX_wm_space_node(C);
+       bNodeTree *ntree = snode->edittree;
+       bNode *node;
+       bNodeLink *link;
+       int num_nodes;
+       float centerx, centery;
+
+       ED_preview_kill_jobs(C);
+
+       /* deselect old nodes */
+       node_deselect_all(snode);
+
+       /* calculate "barycenter" for placing on mouse cursor */
+       num_nodes = 0;
+       centerx = centery = 0.0f;
+       for (node = nodeClipboardGetNodes()->first; node; node = node->next) {
+               ++num_nodes;
+               centerx += node->locx + 0.5f * node->width;
+               centery += node->locy - 0.5f * node->height;
+       }
+       centerx /= num_nodes;
+       centery /= num_nodes;
+
+       /* copy nodes from clipboard */
+       for (node = nodeClipboardGetNodes()->first; node; node = node->next) {
+               bNode *newnode = nodeCopyNode(ntree, node);
+
+               /* pasted nodes are selected */
+               node_select(newnode);
+
+               /* place nodes around the mouse cursor */
+               newnode->locx += snode->mx - centerx;
+               newnode->locy += snode->my - centery;
+       }
+
+       for (link = nodeClipboardGetLinks()->first; link; link = link->next) {
+               nodeAddLink(ntree, link->fromnode->new_node, link->fromsock->new_sock,
+                           link->tonode->new_node, link->tosock->new_sock);
+       }
+
+       /* reparent copied nodes */
+       for (node = nodeClipboardGetNodes()->first; node; node = node->next) {
+               if (node->new_node->parent)
+                       node->new_node->parent = node->new_node->parent->new_node;
+       }
+
+       ntreeUpdateTree(snode->edittree);
+
+       snode_notify(C, snode);
+       snode_dag_update(C, snode);
+
+       return OPERATOR_FINISHED;
+}
+
+static int node_clipboard_paste_invoke(bContext *C, wmOperator *op, wmEvent *event)
+{
+       ARegion *ar = CTX_wm_region(C);
+       SpaceNode *snode = CTX_wm_space_node(C);
+
+       /* convert mouse coordinates to v2d space */
+       UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1], &snode->mx, &snode->my);
+
+       return node_clipboard_paste_exec(C, op);
+}
+
+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->invoke = node_clipboard_paste_invoke;
+       ot->poll = ED_operator_node_active;
+
+       /* flags */
+       ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO;
+}
index 5c6a91195a63bdb0b2956510be998eecec627f9c..7077229fae1c0957672797b51c7569add604a87b 100644 (file)
@@ -200,6 +200,10 @@ void NODE_OT_output_file_add_socket(struct wmOperatorType *ot);
 void NODE_OT_output_file_remove_active_socket(struct wmOperatorType *ot);
 void NODE_OT_output_file_move_active_socket(struct wmOperatorType *ot);
 
+/* Note: clipboard_cut is a simple macro of copy + delete */
+void NODE_OT_clipboard_copy(struct wmOperatorType *ot);
+void NODE_OT_clipboard_paste(struct wmOperatorType *ot);
+
 extern const char *node_context_dir[];
 
 // XXXXXX
index 0ce72848c568968544cf67ac9dfa4ce21033f8b4..2efeebbfc75641001e6373f58b09864d3ba148dd 100644 (file)
@@ -113,6 +113,9 @@ void node_operatortypes(void)
        WM_operatortype_append(NODE_OT_join);
        WM_operatortype_append(NODE_OT_attach);
        WM_operatortype_append(NODE_OT_detach);
+       
+       WM_operatortype_append(NODE_OT_clipboard_copy);
+       WM_operatortype_append(NODE_OT_clipboard_paste);
 }
 
 void ED_operatormacros_node(void)
@@ -281,5 +284,8 @@ void node_keymap(struct wmKeyConfig *keyconf)
        WM_keymap_add_item(keymap, "NODE_OT_read_fullsamplelayers", RKEY, KM_PRESS, KM_SHIFT, 0);
        WM_keymap_add_item(keymap, "NODE_OT_render_changed", ZKEY, KM_PRESS, 0, 0);
        
+       WM_keymap_add_item(keymap, "NODE_OT_clipboard_copy", CKEY, KM_PRESS, KM_CTRL, 0);
+       WM_keymap_add_item(keymap, "NODE_OT_clipboard_paste", VKEY, KM_PRESS, KM_CTRL, 0);
+       
        transform_keymap_for_space(keyconf, keymap, SPACE_NODE);
 }