Merge remote-tracking branch 'origin/blender-v2.81-release'
[blender.git] / source / blender / editors / space_node / node_group.c
index 91bb95c9cb463dd9ff2165e42279ac5702a74329..5d020ff5ab4c704c218b1fe207ba588ef7691eb8 100644 (file)
@@ -1,6 +1,4 @@
 /*
- * ***** BEGIN GPL LICENSE BLOCK *****
- *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
  * as published by the Free Software Foundation; either version 2
  *
  * The Original Code is Copyright (C) 2005 Blender Foundation.
  * All rights reserved.
- *
- * The Original Code is: all of this file.
- *
- * Contributor(s): David Millan Escriva, Juho Vepsäläinen, Nathan Letwory
- *
- * ***** END GPL LICENSE BLOCK *****
  */
 
-/** \file blender/editors/space_node/node_group.c
- *  \ingroup spnode
+/** \file
+ * \ingroup spnode
  */
 
 #include <stdlib.h>
 #include "BKE_action.h"
 #include "BKE_animsys.h"
 #include "BKE_context.h"
-#include "BKE_global.h"
 #include "BKE_library.h"
 #include "BKE_main.h"
 #include "BKE_report.h"
 
 #include "DEG_depsgraph_build.h"
 
-#include "ED_node.h"  /* own include */
+#include "ED_node.h" /* own include */
 #include "ED_screen.h"
 #include "ED_render.h"
 
 
 #include "UI_resources.h"
 
-#include "node_intern.h"  /* own include */
+#include "node_intern.h" /* own include */
 #include "NOD_common.h"
 
 static bool node_group_operator_active(bContext *C)
 {
-       if (ED_operator_node_active(C)) {
-               SpaceNode *snode = CTX_wm_space_node(C);
-
-               /* Group operators only defined for standard node tree types.
-                * Disabled otherwise to allow pynodes define their own operators
-                * with same keymap.
-                */
-               if (STREQ(snode->tree_idname, "ShaderNodeTree") ||
-                   STREQ(snode->tree_idname, "CompositorNodeTree") ||
-                   STREQ(snode->tree_idname, "TextureNodeTree"))
-               {
-                       return true;
-               }
-       }
-       return false;
+  if (ED_operator_node_active(C)) {
+    SpaceNode *snode = CTX_wm_space_node(C);
+
+    /* Group operators only defined for standard node tree types.
+     * Disabled otherwise to allow pynodes define their own operators
+     * with same keymap.
+     */
+    if (STREQ(snode->tree_idname, "ShaderNodeTree") ||
+        STREQ(snode->tree_idname, "CompositorNodeTree") ||
+        STREQ(snode->tree_idname, "TextureNodeTree")) {
+      return true;
+    }
+  }
+  return false;
 }
 
 static bool node_group_operator_editable(bContext *C)
 {
-       if (ED_operator_node_editable(C)) {
-               SpaceNode *snode = CTX_wm_space_node(C);
-
-               /* Group operators only defined for standard node tree types.
-                * Disabled otherwise to allow pynodes define their own operators
-                * with same keymap.
-                */
-               if (ED_node_is_shader(snode) ||
-                   ED_node_is_compositor(snode) ||
-                   ED_node_is_texture(snode))
-               {
-                       return true;
-               }
-       }
-       return false;
+  if (ED_operator_node_editable(C)) {
+    SpaceNode *snode = CTX_wm_space_node(C);
+
+    /* Group operators only defined for standard node tree types.
+     * Disabled otherwise to allow pynodes define their own operators
+     * with same keymap.
+     */
+    if (ED_node_is_shader(snode) || ED_node_is_compositor(snode) || ED_node_is_texture(snode)) {
+      return true;
+    }
+  }
+  return false;
 }
 
 static const char *group_ntree_idname(bContext *C)
 {
-       SpaceNode *snode = CTX_wm_space_node(C);
-       return snode->tree_idname;
+  SpaceNode *snode = CTX_wm_space_node(C);
+  return snode->tree_idname;
 }
 
 static const char *group_node_idname(bContext *C)
 {
-       SpaceNode *snode = CTX_wm_space_node(C);
-
-       if (ED_node_is_shader(snode))
-               return "ShaderNodeGroup";
-       else if (ED_node_is_compositor(snode))
-               return "CompositorNodeGroup";
-       else if (ED_node_is_texture(snode))
-               return "TextureNodeGroup";
-
-       return "";
+  SpaceNode *snode = CTX_wm_space_node(C);
+
+  if (ED_node_is_shader(snode)) {
+    return "ShaderNodeGroup";
+  }
+  else if (ED_node_is_compositor(snode)) {
+    return "CompositorNodeGroup";
+  }
+  else if (ED_node_is_texture(snode)) {
+    return "TextureNodeGroup";
+  }
+
+  return "";
 }
 
 static bNode *node_group_get_active(bContext *C, const char *node_idname)
 {
-       SpaceNode *snode = CTX_wm_space_node(C);
-       bNode *node = nodeGetActive(snode->edittree);
-
-       if (node && STREQ(node->idname, node_idname))
-               return node;
-       else
-               return NULL;
+  SpaceNode *snode = CTX_wm_space_node(C);
+  bNode *node = nodeGetActive(snode->edittree);
+
+  if (node && STREQ(node->idname, node_idname)) {
+    return node;
+  }
+  else {
+    return NULL;
+  }
 }
 
 /* ***************** Edit Group operator ************* */
 
 static int node_group_edit_exec(bContext *C, wmOperator *op)
 {
-       SpaceNode *snode = CTX_wm_space_node(C);
-       const char *node_idname = group_node_idname(C);
-       bNode *gnode;
-       const bool exit = RNA_boolean_get(op->ptr, "exit");
+  SpaceNode *snode = CTX_wm_space_node(C);
+  const char *node_idname = group_node_idname(C);
+  bNode *gnode;
+  const bool exit = RNA_boolean_get(op->ptr, "exit");
 
-       ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C));
+  ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C));
 
-       gnode = node_group_get_active(C, node_idname);
+  gnode = node_group_get_active(C, node_idname);
 
-       if (gnode && !exit) {
-               bNodeTree *ngroup = (bNodeTree *)gnode->id;
+  if (gnode && !exit) {
+    bNodeTree *ngroup = (bNodeTree *)gnode->id;
 
-               if (ngroup)
-                       ED_node_tree_push(snode, ngroup, gnode);
-       }
-       else
-               ED_node_tree_pop(snode);
+    if (ngroup) {
+      ED_node_tree_push(snode, ngroup, gnode);
+    }
+  }
+  else {
+    ED_node_tree_pop(snode);
+  }
 
-       WM_event_add_notifier(C, NC_SCENE | ND_NODES, NULL);
+  WM_event_add_notifier(C, NC_SCENE | ND_NODES, NULL);
 
-       return OPERATOR_FINISHED;
+  return OPERATOR_FINISHED;
 }
 
 void NODE_OT_group_edit(wmOperatorType *ot)
 {
-       /* identifiers */
-       ot->name = "Edit Group";
-       ot->description = "Edit node group";
-       ot->idname = "NODE_OT_group_edit";
+  /* identifiers */
+  ot->name = "Edit Group";
+  ot->description = "Edit node group";
+  ot->idname = "NODE_OT_group_edit";
 
-       /* api callbacks */
-       ot->exec = node_group_edit_exec;
-       ot->poll = node_group_operator_active;
+  /* api callbacks */
+  ot->exec = node_group_edit_exec;
+  ot->poll = node_group_operator_active;
 
-       /* flags */
-       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+  /* flags */
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 
-       RNA_def_boolean(ot->srna, "exit", false, "Exit", "");
+  RNA_def_boolean(ot->srna, "exit", false, "Exit", "");
 }
 
 /* ******************** Ungroup operator ********************** */
@@ -185,857 +179,904 @@ void NODE_OT_group_edit(wmOperatorType *ot)
 /* returns 1 if its OK */
 static int node_group_ungroup(Main *bmain, bNodeTree *ntree, bNode *gnode)
 {
-       bNodeLink *link, *linkn, *tlink;
-       bNode *node, *nextnode;
-       bNodeTree *ngroup, *wgroup;
-       ListBase anim_basepaths = {NULL, NULL};
-       LinkNode *nodes_delayed_free = NULL;
-
-       ngroup = (bNodeTree *)gnode->id;
-
-       /* clear new pointers, set in copytree */
-       for (node = ntree->nodes.first; node; node = node->next)
-               node->new_node = NULL;
-
-       /* wgroup is a temporary copy of the NodeTree we're merging in
-        * - all of wgroup's nodes are transferred across to their new home
-        * - ngroup (i.e. the source NodeTree) is left unscathed
-        * - temp copy. don't change ID usercount
-        */
-       wgroup = ntreeCopyTree_ex(ngroup, bmain, false);
-
-       /* Add the nodes into the ntree */
-       for (node = wgroup->nodes.first; node; node = nextnode) {
-               nextnode = node->next;
-
-               /* Remove interface nodes.
-                * This also removes remaining links to and from interface nodes.
-                */
-               if (ELEM(node->type, NODE_GROUP_INPUT, NODE_GROUP_OUTPUT)) {
-                       /* We must delay removal since sockets will reference this node. see: T52092 */
-                       BLI_linklist_prepend(&nodes_delayed_free, node);
-               }
-
-               /* keep track of this node's RNA "base" path (the part of the path identifying the node)
-                * if the old nodetree has animation data which potentially covers this node
-                */
-               if (wgroup->adt) {
-                       PointerRNA ptr;
-                       char *path;
-
-                       RNA_pointer_create(&wgroup->id, &RNA_Node, node, &ptr);
-                       path = RNA_path_from_ID_to_struct(&ptr);
-
-                       if (path)
-                               BLI_addtail(&anim_basepaths, BLI_genericNodeN(path));
-               }
-
-               /* migrate node */
-               BLI_remlink(&wgroup->nodes, node);
-               BLI_addtail(&ntree->nodes, node);
-
-               /* ensure unique node name in the node tree */
-               nodeUniqueName(ntree, node);
-
-               if (!node->parent) {
-                       node->locx += gnode->locx;
-                       node->locy += gnode->locy;
-               }
-
-               node->flag |= NODE_SELECT;
-       }
-
-       /* Add internal links to the ntree */
-       for (link = wgroup->links.first; link; link = linkn) {
-               linkn = link->next;
-               BLI_remlink(&wgroup->links, link);
-               BLI_addtail(&ntree->links, link);
-       }
-
-       /* and copy across the animation,
-        * note that the animation data's action can be NULL here */
-       if (wgroup->adt) {
-               LinkData *ld, *ldn = NULL;
-               bAction *waction;
-
-               /* firstly, wgroup needs to temporary dummy action
-                * that can be destroyed, as it shares copies */
-               waction = wgroup->adt->action = BKE_action_copy(bmain, wgroup->adt->action);
-
-               /* now perform the moving */
-               BKE_animdata_separate_by_basepath(bmain, &wgroup->id, &ntree->id, &anim_basepaths);
-
-               /* paths + their wrappers need to be freed */
-               for (ld = anim_basepaths.first; ld; ld = ldn) {
-                       ldn = ld->next;
-
-                       MEM_freeN(ld->data);
-                       BLI_freelinkN(&anim_basepaths, ld);
-               }
-
-               /* free temp action too */
-               if (waction) {
-                       BKE_id_free(bmain, waction);
-                       wgroup->adt->action = NULL;
-               }
-       }
-
-       /* free the group tree (takes care of user count) */
-       BKE_id_free(bmain, wgroup);
-
-       /* restore external links to and from the gnode */
-       /* note: the nodes have been copied to intermediate wgroup first (so need to use new_node),
-        *       then transferred to ntree (new_node pointers remain valid).
-        */
-
-       /* input links */
-       for (link = ngroup->links.first; link; link = link->next) {
-               if (link->fromnode->type == NODE_GROUP_INPUT) {
-                       const char *identifier = link->fromsock->identifier;
-                       int num_external_links = 0;
-
-                       /* find external links to this input */
-                       for (tlink = ntree->links.first; tlink; tlink = tlink->next) {
-                               if (tlink->tonode == gnode && STREQ(tlink->tosock->identifier, identifier)) {
-                                       nodeAddLink(ntree, tlink->fromnode, tlink->fromsock, link->tonode->new_node, link->tosock->new_sock);
-                                       ++num_external_links;
-                               }
-                       }
-
-                       /* if group output is not externally linked,
-                        * convert the constant input value to ensure somewhat consistent behavior */
-                       if (num_external_links == 0) {
-                               /* XXX TODO bNodeSocket *sock = node_group_find_input_socket(gnode, identifier);
-                               BLI_assert(sock);*/
-
-                               /* XXX TODO
-                                * nodeSocketCopy(ntree, link->tosock->new_sock, link->tonode->new_node,
-                                *                ntree, sock, gnode);*/
-                       }
-               }
-       }
-
-       /* output links */
-       for (link = ntree->links.first; link; link = link->next) {
-               if (link->fromnode == gnode) {
-                       const char *identifier = link->fromsock->identifier;
-                       int num_internal_links = 0;
-
-                       /* find internal links to this output */
-                       for (tlink = ngroup->links.first; tlink; tlink = tlink->next) {
-                               /* only use active output node */
-                               if (tlink->tonode->type == NODE_GROUP_OUTPUT && (tlink->tonode->flag & NODE_DO_OUTPUT)) {
-                                       if (STREQ(tlink->tosock->identifier, identifier)) {
-                                               nodeAddLink(ntree, tlink->fromnode->new_node, tlink->fromsock->new_sock, link->tonode, link->tosock);
-                                               ++num_internal_links;
-                                       }
-                               }
-                       }
-
-                       /* if group output is not internally linked,
-                        * convert the constant output value to ensure somewhat consistent behavior */
-                       if (num_internal_links == 0) {
-                               /* XXX TODO bNodeSocket *sock = node_group_find_output_socket(gnode, identifier);
-                               BLI_assert(sock);*/
-
-                               /* XXX TODO
-                                * nodeSocketCopy(ntree, link->tosock, link->tonode, ntree, sock, gnode); */
-                       }
-               }
-       }
-
-       while (nodes_delayed_free) {
-               node = BLI_linklist_pop(&nodes_delayed_free);
-               nodeDeleteNode(bmain, ntree, node);
-       }
-
-       /* delete the group instance */
-       nodeDeleteNode(bmain, ntree, gnode);
-
-       ntree->update |= NTREE_UPDATE_NODES | NTREE_UPDATE_LINKS;
-
-       return 1;
+  bNodeLink *link, *linkn, *tlink;
+  bNode *node, *nextnode;
+  bNodeTree *ngroup, *wgroup;
+  ListBase anim_basepaths = {NULL, NULL};
+  LinkNode *nodes_delayed_free = NULL;
+
+  ngroup = (bNodeTree *)gnode->id;
+
+  /* clear new pointers, set in copytree */
+  for (node = ntree->nodes.first; node; node = node->next) {
+    node->new_node = NULL;
+  }
+
+  /* wgroup is a temporary copy of the NodeTree we're merging in
+   * - all of wgroup's nodes are transferred across to their new home
+   * - ngroup (i.e. the source NodeTree) is left unscathed
+   * - temp copy. don't change ID usercount
+   */
+  wgroup = ntreeCopyTree_ex_new_pointers(ngroup, bmain, false);
+
+  /* Add the nodes into the ntree */
+  for (node = wgroup->nodes.first; node; node = nextnode) {
+    nextnode = node->next;
+
+    /* Remove interface nodes.
+     * This also removes remaining links to and from interface nodes.
+     */
+    if (ELEM(node->type, NODE_GROUP_INPUT, NODE_GROUP_OUTPUT)) {
+      /* We must delay removal since sockets will reference this node. see: T52092 */
+      BLI_linklist_prepend(&nodes_delayed_free, node);
+    }
+
+    /* keep track of this node's RNA "base" path (the part of the path identifying the node)
+     * if the old nodetree has animation data which potentially covers this node
+     */
+    if (wgroup->adt) {
+      PointerRNA ptr;
+      char *path;
+
+      RNA_pointer_create(&wgroup->id, &RNA_Node, node, &ptr);
+      path = RNA_path_from_ID_to_struct(&ptr);
+
+      if (path) {
+        BLI_addtail(&anim_basepaths, BLI_genericNodeN(path));
+      }
+    }
+
+    /* migrate node */
+    BLI_remlink(&wgroup->nodes, node);
+    BLI_addtail(&ntree->nodes, node);
+
+    /* ensure unique node name in the node tree */
+    nodeUniqueName(ntree, node);
+
+    if (!node->parent) {
+      node->locx += gnode->locx;
+      node->locy += gnode->locy;
+    }
+
+    node->flag |= NODE_SELECT;
+  }
+
+  bNodeLink *glinks_first = ntree->links.last;
+
+  /* Add internal links to the ntree */
+  for (link = wgroup->links.first; link; link = linkn) {
+    linkn = link->next;
+    BLI_remlink(&wgroup->links, link);
+    BLI_addtail(&ntree->links, link);
+  }
+
+  bNodeLink *glinks_last = ntree->links.last;
+
+  /* and copy across the animation,
+   * note that the animation data's action can be NULL here */
+  if (wgroup->adt) {
+    LinkData *ld, *ldn = NULL;
+    bAction *waction;
+
+    /* firstly, wgroup needs to temporary dummy action
+     * that can be destroyed, as it shares copies */
+    waction = wgroup->adt->action = BKE_action_copy(bmain, wgroup->adt->action);
+
+    /* now perform the moving */
+    BKE_animdata_separate_by_basepath(bmain, &wgroup->id, &ntree->id, &anim_basepaths);
+
+    /* paths + their wrappers need to be freed */
+    for (ld = anim_basepaths.first; ld; ld = ldn) {
+      ldn = ld->next;
+
+      MEM_freeN(ld->data);
+      BLI_freelinkN(&anim_basepaths, ld);
+    }
+
+    /* free temp action too */
+    if (waction) {
+      BKE_id_free(bmain, waction);
+      wgroup->adt->action = NULL;
+    }
+  }
+
+  /* free the group tree (takes care of user count) */
+  BKE_id_free(bmain, wgroup);
+
+  /* restore external links to and from the gnode */
+
+  /* input links */
+  if (glinks_first != NULL) {
+    for (link = glinks_first->next; link != glinks_last->next; link = link->next) {
+      if (link->fromnode->type == NODE_GROUP_INPUT) {
+        const char *identifier = link->fromsock->identifier;
+        int num_external_links = 0;
+
+        /* find external links to this input */
+        for (tlink = ntree->links.first; tlink != glinks_first->next; tlink = tlink->next) {
+          if (tlink->tonode == gnode && STREQ(tlink->tosock->identifier, identifier)) {
+            nodeAddLink(ntree, tlink->fromnode, tlink->fromsock, link->tonode, link->tosock);
+            num_external_links++;
+          }
+        }
+
+        /* if group output is not externally linked,
+         * convert the constant input value to ensure somewhat consistent behavior */
+        if (num_external_links == 0) {
+          /* XXX TODO bNodeSocket *sock = node_group_find_input_socket(gnode, identifier);
+          BLI_assert(sock);*/
+
+          /* XXX TODO
+           * nodeSocketCopy(ntree, link->tosock->new_sock, link->tonode->new_node,
+           *                ntree, sock, gnode);*/
+        }
+      }
+    }
+
+    /* Also iterate over new links to cover passthrough links. */
+    glinks_last = ntree->links.last;
+
+    /* output links */
+    for (link = ntree->links.first; link != glinks_first->next; link = link->next) {
+      if (link->fromnode == gnode) {
+        const char *identifier = link->fromsock->identifier;
+        int num_internal_links = 0;
+
+        /* find internal links to this output */
+        for (tlink = glinks_first->next; tlink != glinks_last->next; tlink = tlink->next) {
+          /* only use active output node */
+          if (tlink->tonode->type == NODE_GROUP_OUTPUT && (tlink->tonode->flag & NODE_DO_OUTPUT)) {
+            if (STREQ(tlink->tosock->identifier, identifier)) {
+              nodeAddLink(ntree, tlink->fromnode, tlink->fromsock, link->tonode, link->tosock);
+              num_internal_links++;
+            }
+          }
+        }
+
+        /* if group output is not internally linked,
+         * convert the constant output value to ensure somewhat consistent behavior */
+        if (num_internal_links == 0) {
+          /* XXX TODO bNodeSocket *sock = node_group_find_output_socket(gnode, identifier);
+          BLI_assert(sock);*/
+
+          /* XXX TODO
+           * nodeSocketCopy(ntree, link->tosock, link->tonode, ntree, sock, gnode); */
+        }
+      }
+    }
+  }
+
+  while (nodes_delayed_free) {
+    node = BLI_linklist_pop(&nodes_delayed_free);
+    nodeRemoveNode(bmain, ntree, node, false);
+  }
+
+  /* delete the group instance */
+  nodeRemoveNode(bmain, ntree, gnode, false);
+
+  ntree->update |= NTREE_UPDATE_NODES | NTREE_UPDATE_LINKS;
+
+  return 1;
 }
 
-
 static int node_group_ungroup_exec(bContext *C, wmOperator *op)
 {
-       Main *bmain = CTX_data_main(C);
-       SpaceNode *snode = CTX_wm_space_node(C);
-       const char *node_idname = group_node_idname(C);
-       bNode *gnode;
-
-       ED_preview_kill_jobs(CTX_wm_manager(C), bmain);
-
-       gnode = node_group_get_active(C, node_idname);
-       if (!gnode)
-               return OPERATOR_CANCELLED;
-
-       if (gnode->id && node_group_ungroup(bmain, snode->edittree, gnode)) {
-               ntreeUpdateTree(bmain, snode->nodetree);
-       }
-       else {
-               BKE_report(op->reports, RPT_WARNING, "Cannot ungroup");
-               return OPERATOR_CANCELLED;
-       }
-
-       snode_notify(C, snode);
-       snode_dag_update(C, snode);
-
-       return OPERATOR_FINISHED;
+  Main *bmain = CTX_data_main(C);
+  SpaceNode *snode = CTX_wm_space_node(C);
+  const char *node_idname = group_node_idname(C);
+  bNode *gnode;
+
+  ED_preview_kill_jobs(CTX_wm_manager(C), bmain);
+
+  gnode = node_group_get_active(C, node_idname);
+  if (!gnode) {
+    return OPERATOR_CANCELLED;
+  }
+
+  if (gnode->id && node_group_ungroup(bmain, snode->edittree, gnode)) {
+    ntreeUpdateTree(bmain, snode->nodetree);
+  }
+  else {
+    BKE_report(op->reports, RPT_WARNING, "Cannot ungroup");
+    return OPERATOR_CANCELLED;
+  }
+
+  snode_notify(C, snode);
+  snode_dag_update(C, snode);
+
+  return OPERATOR_FINISHED;
 }
 
 void NODE_OT_group_ungroup(wmOperatorType *ot)
 {
-       /* identifiers */
-       ot->name = "Ungroup";
-       ot->description = "Ungroup selected nodes";
-       ot->idname = "NODE_OT_group_ungroup";
+  /* identifiers */
+  ot->name = "Ungroup";
+  ot->description = "Ungroup selected nodes";
+  ot->idname = "NODE_OT_group_ungroup";
 
-       /* api callbacks */
-       ot->exec = node_group_ungroup_exec;
-       ot->poll = node_group_operator_editable;
+  /* api callbacks */
+  ot->exec = node_group_ungroup_exec;
+  ot->poll = node_group_operator_editable;
 
-       /* flags */
-       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+  /* flags */
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 }
 
 /* ******************** Separate operator ********************** */
 
 /* returns 1 if its OK */
 static int node_group_separate_selected(
-        Main *bmain, bNodeTree *ntree, bNodeTree *ngroup, float offx, float offy, int make_copy)
+    Main *bmain, bNodeTree *ntree, bNodeTree *ngroup, float offx, float offy, int make_copy)
 {
-       bNodeLink *link, *link_next;
-       bNode *node, *node_next, *newnode;
-       ListBase anim_basepaths = {NULL, NULL};
-
-       /* deselect all nodes in the target tree */
-       for (node = ntree->nodes.first; node; node = node->next)
-               nodeSetSelected(node, false);
-
-       /* clear new pointers, set in nodeCopyNode */
-       for (node = ngroup->nodes.first; node; node = node->next)
-               node->new_node = NULL;
-
-       /* add selected nodes into the ntree */
-       for (node = ngroup->nodes.first; node; node = node_next) {
-               node_next = node->next;
-               if (!(node->flag & NODE_SELECT))
-                       continue;
-
-               /* ignore interface nodes */
-               if (ELEM(node->type, NODE_GROUP_INPUT, NODE_GROUP_OUTPUT)) {
-                       nodeSetSelected(node, false);
-                       continue;
-               }
-
-               if (make_copy) {
-                       /* make a copy */
-                       newnode = nodeCopyNode(ngroup, node);
-               }
-               else {
-                       /* use the existing node */
-                       newnode = node;
-               }
-
-               /* keep track of this node's RNA "base" path (the part of the path identifying the node)
-                * if the old nodetree has animation data which potentially covers this node
-                */
-               if (ngroup->adt) {
-                       PointerRNA ptr;
-                       char *path;
-
-                       RNA_pointer_create(&ngroup->id, &RNA_Node, newnode, &ptr);
-                       path = RNA_path_from_ID_to_struct(&ptr);
-
-                       if (path)
-                               BLI_addtail(&anim_basepaths, BLI_genericNodeN(path));
-               }
-
-               /* ensure valid parent pointers, detach if parent stays inside the group */
-               if (newnode->parent && !(newnode->parent->flag & NODE_SELECT))
-                       nodeDetachNode(newnode);
-
-               /* migrate node */
-               BLI_remlink(&ngroup->nodes, newnode);
-               BLI_addtail(&ntree->nodes, newnode);
-
-               /* ensure unique node name in the node tree */
-               nodeUniqueName(ntree, newnode);
-
-               if (!newnode->parent) {
-                       newnode->locx += offx;
-                       newnode->locy += offy;
-               }
-       }
-
-       /* add internal links to the ntree */
-       for (link = ngroup->links.first; link; link = link_next) {
-               const bool fromselect = (link->fromnode && (link->fromnode->flag & NODE_SELECT));
-               const bool toselect = (link->tonode && (link->tonode->flag & NODE_SELECT));
-               link_next = link->next;
-
-               if (make_copy) {
-                       /* make a copy of internal links */
-                       if (fromselect && toselect)
-                               nodeAddLink(ntree, link->fromnode->new_node, link->fromsock->new_sock, link->tonode->new_node, link->tosock->new_sock);
-               }
-               else {
-                       /* move valid links over, delete broken links */
-                       if (fromselect && toselect) {
-                               BLI_remlink(&ngroup->links, link);
-                               BLI_addtail(&ntree->links, link);
-                       }
-                       else if (fromselect || toselect) {
-                               nodeRemLink(ngroup, link);
-                       }
-               }
-       }
-
-       /* and copy across the animation,
-        * note that the animation data's action can be NULL here */
-       if (ngroup->adt) {
-               LinkData *ld, *ldn = NULL;
-
-               /* now perform the moving */
-               BKE_animdata_separate_by_basepath(bmain, &ngroup->id, &ntree->id, &anim_basepaths);
-
-               /* paths + their wrappers need to be freed */
-               for (ld = anim_basepaths.first; ld; ld = ldn) {
-                       ldn = ld->next;
-
-                       MEM_freeN(ld->data);
-                       BLI_freelinkN(&anim_basepaths, ld);
-               }
-       }
-
-       ntree->update |= NTREE_UPDATE_NODES | NTREE_UPDATE_LINKS;
-       if (!make_copy)
-               ngroup->update |= NTREE_UPDATE_NODES | NTREE_UPDATE_LINKS;
-
-       return 1;
+  bNodeLink *link, *link_next;
+  bNode *node, *node_next, *newnode;
+  ListBase anim_basepaths = {NULL, NULL};
+
+  /* deselect all nodes in the target tree */
+  for (node = ntree->nodes.first; node; node = node->next) {
+    nodeSetSelected(node, false);
+  }
+
+  /* clear new pointers, set in BKE_node_copy_ex(). */
+  for (node = ngroup->nodes.first; node; node = node->next) {
+    node->new_node = NULL;
+  }
+
+  /* add selected nodes into the ntree */
+  for (node = ngroup->nodes.first; node; node = node_next) {
+    node_next = node->next;
+    if (!(node->flag & NODE_SELECT)) {
+      continue;
+    }
+
+    /* ignore interface nodes */
+    if (ELEM(node->type, NODE_GROUP_INPUT, NODE_GROUP_OUTPUT)) {
+      nodeSetSelected(node, false);
+      continue;
+    }
+
+    if (make_copy) {
+      /* make a copy */
+      newnode = BKE_node_copy_store_new_pointers(ngroup, node, LIB_ID_COPY_DEFAULT);
+    }
+    else {
+      /* use the existing node */
+      newnode = node;
+    }
+
+    /* keep track of this node's RNA "base" path (the part of the path identifying the node)
+     * if the old nodetree has animation data which potentially covers this node
+     */
+    if (ngroup->adt) {
+      PointerRNA ptr;
+      char *path;
+
+      RNA_pointer_create(&ngroup->id, &RNA_Node, newnode, &ptr);
+      path = RNA_path_from_ID_to_struct(&ptr);
+
+      if (path) {
+        BLI_addtail(&anim_basepaths, BLI_genericNodeN(path));
+      }
+    }
+
+    /* ensure valid parent pointers, detach if parent stays inside the group */
+    if (newnode->parent && !(newnode->parent->flag & NODE_SELECT)) {
+      nodeDetachNode(newnode);
+    }
+
+    /* migrate node */
+    BLI_remlink(&ngroup->nodes, newnode);
+    BLI_addtail(&ntree->nodes, newnode);
+
+    /* ensure unique node name in the node tree */
+    nodeUniqueName(ntree, newnode);
+
+    if (!newnode->parent) {
+      newnode->locx += offx;
+      newnode->locy += offy;
+    }
+  }
+
+  /* add internal links to the ntree */
+  for (link = ngroup->links.first; link; link = link_next) {
+    const bool fromselect = (link->fromnode && (link->fromnode->flag & NODE_SELECT));
+    const bool toselect = (link->tonode && (link->tonode->flag & NODE_SELECT));
+    link_next = link->next;
+
+    if (make_copy) {
+      /* make a copy of internal links */
+      if (fromselect && toselect) {
+        nodeAddLink(ntree,
+                    link->fromnode->new_node,
+                    link->fromsock->new_sock,
+                    link->tonode->new_node,
+                    link->tosock->new_sock);
+      }
+    }
+    else {
+      /* move valid links over, delete broken links */
+      if (fromselect && toselect) {
+        BLI_remlink(&ngroup->links, link);
+        BLI_addtail(&ntree->links, link);
+      }
+      else if (fromselect || toselect) {
+        nodeRemLink(ngroup, link);
+      }
+    }
+  }
+
+  /* and copy across the animation,
+   * note that the animation data's action can be NULL here */
+  if (ngroup->adt) {
+    LinkData *ld, *ldn = NULL;
+
+    /* now perform the moving */
+    BKE_animdata_separate_by_basepath(bmain, &ngroup->id, &ntree->id, &anim_basepaths);
+
+    /* paths + their wrappers need to be freed */
+    for (ld = anim_basepaths.first; ld; ld = ldn) {
+      ldn = ld->next;
+
+      MEM_freeN(ld->data);
+      BLI_freelinkN(&anim_basepaths, ld);
+    }
+  }
+
+  ntree->update |= NTREE_UPDATE_NODES | NTREE_UPDATE_LINKS;
+  if (!make_copy) {
+    ngroup->update |= NTREE_UPDATE_NODES | NTREE_UPDATE_LINKS;
+  }
+
+  return 1;
 }
 
 typedef enum eNodeGroupSeparateType {
-       NODE_GS_COPY,
-       NODE_GS_MOVE
+  NODE_GS_COPY,
+  NODE_GS_MOVE,
 } eNodeGroupSeparateType;
 
 /* Operator Property */
 static const EnumPropertyItem node_group_separate_types[] = {
-       {NODE_GS_COPY, "COPY", 0, "Copy", "Copy to parent node tree, keep group intact"},
-       {NODE_GS_MOVE, "MOVE", 0, "Move", "Move to parent node tree, remove from group"},
-       {0, NULL, 0, NULL, NULL}
+    {NODE_GS_COPY, "COPY", 0, "Copy", "Copy to parent node tree, keep group intact"},
+    {NODE_GS_MOVE, "MOVE", 0, "Move", "Move to parent node tree, remove from group"},
+    {0, NULL, 0, NULL, NULL},
 };
 
 static int node_group_separate_exec(bContext *C, wmOperator *op)
 {
-       Main *bmain = CTX_data_main(C);
-       SpaceNode *snode = CTX_wm_space_node(C);
-       bNodeTree *ngroup, *nparent;
-       int type = RNA_enum_get(op->ptr, "type");
-       float offx, offy;
-
-       ED_preview_kill_jobs(CTX_wm_manager(C), bmain);
-
-       /* are we inside of a group? */
-       ngroup = snode->edittree;
-       nparent = ED_node_tree_get(snode, 1);
-       if (!nparent) {
-               BKE_report(op->reports, RPT_WARNING, "Not inside node group");
-               return OPERATOR_CANCELLED;
-       }
-       /* get node tree offset */
-       snode_group_offset(snode, &offx, &offy);
-
-       switch (type) {
-               case NODE_GS_COPY:
-                       if (!node_group_separate_selected(bmain, nparent, ngroup, offx, offy, 1)) {
-                               BKE_report(op->reports, RPT_WARNING, "Cannot separate nodes");
-                               return OPERATOR_CANCELLED;
-                       }
-                       break;
-               case NODE_GS_MOVE:
-                       if (!node_group_separate_selected(bmain, nparent, ngroup, offx, offy, 0)) {
-                               BKE_report(op->reports, RPT_WARNING, "Cannot separate nodes");
-                               return OPERATOR_CANCELLED;
-                       }
-                       break;
-       }
-
-       /* switch to parent tree */
-       ED_node_tree_pop(snode);
-
-       ntreeUpdateTree(CTX_data_main(C), snode->nodetree);
-
-       snode_notify(C, snode);
-       snode_dag_update(C, snode);
-
-       return OPERATOR_FINISHED;
+  Main *bmain = CTX_data_main(C);
+  SpaceNode *snode = CTX_wm_space_node(C);
+  bNodeTree *ngroup, *nparent;
+  int type = RNA_enum_get(op->ptr, "type");
+  float offx, offy;
+
+  ED_preview_kill_jobs(CTX_wm_manager(C), bmain);
+
+  /* are we inside of a group? */
+  ngroup = snode->edittree;
+  nparent = ED_node_tree_get(snode, 1);
+  if (!nparent) {
+    BKE_report(op->reports, RPT_WARNING, "Not inside node group");
+    return OPERATOR_CANCELLED;
+  }
+  /* get node tree offset */
+  snode_group_offset(snode, &offx, &offy);
+
+  switch (type) {
+    case NODE_GS_COPY:
+      if (!node_group_separate_selected(bmain, nparent, ngroup, offx, offy, true)) {
+        BKE_report(op->reports, RPT_WARNING, "Cannot separate nodes");
+        return OPERATOR_CANCELLED;
+      }
+      break;
+    case NODE_GS_MOVE:
+      if (!node_group_separate_selected(bmain, nparent, ngroup, offx, offy, false)) {
+        BKE_report(op->reports, RPT_WARNING, "Cannot separate nodes");
+        return OPERATOR_CANCELLED;
+      }
+      break;
+  }
+
+  /* switch to parent tree */
+  ED_node_tree_pop(snode);
+
+  ntreeUpdateTree(CTX_data_main(C), snode->nodetree);
+
+  snode_notify(C, snode);
+  snode_dag_update(C, snode);
+
+  return OPERATOR_FINISHED;
 }
 
-static int node_group_separate_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *UNUSED(event))
+static int node_group_separate_invoke(bContext *C,
+                                      wmOperator *UNUSED(op),
+                                      const wmEvent *UNUSED(event))
 {
-       uiPopupMenu *pup = UI_popup_menu_begin(C, CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Separate"), ICON_NONE);
-       uiLayout *layout = UI_popup_menu_layout(pup);
+  uiPopupMenu *pup = UI_popup_menu_begin(
+      C, CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Separate"), ICON_NONE);
+  uiLayout *layout = UI_popup_menu_layout(pup);
 
-       uiLayoutSetOperatorContext(layout, WM_OP_EXEC_DEFAULT);
-       uiItemEnumO(layout, "NODE_OT_group_separate", NULL, 0, "type", NODE_GS_COPY);
-       uiItemEnumO(layout, "NODE_OT_group_separate", NULL, 0, "type", NODE_GS_MOVE);
+  uiLayoutSetOperatorContext(layout, WM_OP_EXEC_DEFAULT);
+  uiItemEnumO(layout, "NODE_OT_group_separate", NULL, 0, "type", NODE_GS_COPY);
+  uiItemEnumO(layout, "NODE_OT_group_separate", NULL, 0, "type", NODE_GS_MOVE);
 
-       UI_popup_menu_end(C, pup);
+  UI_popup_menu_end(C, pup);
 
-       return OPERATOR_INTERFACE;
+  return OPERATOR_INTERFACE;
 }
 
 void NODE_OT_group_separate(wmOperatorType *ot)
 {
-       /* identifiers */
-       ot->name = "Separate";
-       ot->description = "Separate selected nodes from the node group";
-       ot->idname = "NODE_OT_group_separate";
+  /* identifiers */
+  ot->name = "Separate";
+  ot->description = "Separate selected nodes from the node group";
+  ot->idname = "NODE_OT_group_separate";
 
-       /* api callbacks */
-       ot->invoke = node_group_separate_invoke;
-       ot->exec = node_group_separate_exec;
-       ot->poll = node_group_operator_editable;
+  /* api callbacks */
+  ot->invoke = node_group_separate_invoke;
+  ot->exec = node_group_separate_exec;
+  ot->poll = node_group_operator_editable;
 
-       /* flags */
-       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+  /* flags */
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 
-       RNA_def_enum(ot->srna, "type", node_group_separate_types, NODE_GS_COPY, "Type", "");
+  RNA_def_enum(ot->srna, "type", node_group_separate_types, NODE_GS_COPY, "Type", "");
 }
 
 /* ****************** Make Group operator ******************* */
 
 static bool node_group_make_use_node(bNode *node, bNode *gnode)
 {
-       return (node != gnode &&
-               !ELEM(node->type, NODE_GROUP_INPUT, NODE_GROUP_OUTPUT) &&
-               (node->flag & NODE_SELECT));
+  return (node != gnode && !ELEM(node->type, NODE_GROUP_INPUT, NODE_GROUP_OUTPUT) &&
+          (node->flag & NODE_SELECT));
 }
 
-static bool node_group_make_test_selected(bNodeTree *ntree, bNode *gnode, const char *ntree_idname, struct ReportList *reports)
+static bool node_group_make_test_selected(bNodeTree *ntree,
+                                          bNode *gnode,
+                                          const char *ntree_idname,
+                                          struct ReportList *reports)
 {
-       bNodeTree *ngroup;
-       bNode *node;
-       bNodeLink *link;
-       int ok = true;
-
-       /* make a local pseudo node tree to pass to the node poll functions */
-       ngroup = ntreeAddTree(NULL, "Pseudo Node Group", ntree_idname);
-
-       /* check poll functions for selected nodes */
-       for (node = ntree->nodes.first; node; node = node->next) {
-               if (node_group_make_use_node(node, gnode)) {
-                       if (node->typeinfo->poll_instance && !node->typeinfo->poll_instance(node, ngroup)) {
-                               BKE_reportf(reports, RPT_WARNING, "Can not add node '%s' in a group", node->name);
-                               ok = false;
-                               break;
-                       }
-               }
-
-               node->done = 0;
-       }
-
-       /* free local pseudo node tree again */
-       ntreeFreeTree(ngroup);
-       MEM_freeN(ngroup);
-       if (!ok)
-               return false;
-
-       /* check if all connections are OK, no unselected node has both
-        * inputs and outputs to a selection */
-       for (link = ntree->links.first; link; link = link->next) {
-               if (node_group_make_use_node(link->fromnode, gnode))
-                       link->tonode->done |= 1;
-               if (node_group_make_use_node(link->tonode, gnode))
-                       link->fromnode->done |= 2;
-       }
-       for (node = ntree->nodes.first; node; node = node->next) {
-               if (!(node->flag & NODE_SELECT) &&
-                   node != gnode &&
-                   node->done == 3)
-               {
-                       return false;
-               }
-       }
-       return true;
+  bNodeTree *ngroup;
+  bNode *node;
+  bNodeLink *link;
+  int ok = true;
+
+  /* make a local pseudo node tree to pass to the node poll functions */
+  ngroup = ntreeAddTree(NULL, "Pseudo Node Group", ntree_idname);
+
+  /* check poll functions for selected nodes */
+  for (node = ntree->nodes.first; node; node = node->next) {
+    if (node_group_make_use_node(node, gnode)) {
+      if (node->typeinfo->poll_instance && !node->typeinfo->poll_instance(node, ngroup)) {
+        BKE_reportf(reports, RPT_WARNING, "Can not add node '%s' in a group", node->name);
+        ok = false;
+        break;
+      }
+    }
+
+    node->done = 0;
+  }
+
+  /* free local pseudo node tree again */
+  ntreeFreeTree(ngroup);
+  MEM_freeN(ngroup);
+  if (!ok) {
+    return false;
+  }
+
+  /* check if all connections are OK, no unselected node has both
+   * inputs and outputs to a selection */
+  for (link = ntree->links.first; link; link = link->next) {
+    if (node_group_make_use_node(link->fromnode, gnode)) {
+      link->tonode->done |= 1;
+    }
+    if (node_group_make_use_node(link->tonode, gnode)) {
+      link->fromnode->done |= 2;
+    }
+  }
+  for (node = ntree->nodes.first; node; node = node->next) {
+    if (!(node->flag & NODE_SELECT) && node != gnode && node->done == 3) {
+      return false;
+    }
+  }
+  return true;
 }
 
 static int node_get_selected_minmax(bNodeTree *ntree, bNode *gnode, float *min, float *max)
 {
-       bNode *node;
-       float loc[2];
-       int totselect = 0;
-
-       INIT_MINMAX2(min, max);
-       for (node = ntree->nodes.first; node; node = node->next) {
-               if (node_group_make_use_node(node, gnode)) {
-                       nodeToView(node, 0.0f, 0.0f, &loc[0], &loc[1]);
-                       minmax_v2v2_v2(min, max, loc);
-                       ++totselect;
-               }
-       }
-
-       /* sane min/max if no selected nodes */
-       if (totselect == 0) {
-               min[0] = min[1] = max[0] = max[1] = 0.0f;
-       }
-
-       return totselect;
+  bNode *node;
+  float loc[2];
+  int totselect = 0;
+
+  INIT_MINMAX2(min, max);
+  for (node = ntree->nodes.first; node; node = node->next) {
+    if (node_group_make_use_node(node, gnode)) {
+      nodeToView(node, 0.0f, 0.0f, &loc[0], &loc[1]);
+      minmax_v2v2_v2(min, max, loc);
+      totselect++;
+    }
+  }
+
+  /* sane min/max if no selected nodes */
+  if (totselect == 0) {
+    min[0] = min[1] = max[0] = max[1] = 0.0f;
+  }
+
+  return totselect;
 }
 
 static void node_group_make_insert_selected(const bContext *C, bNodeTree *ntree, bNode *gnode)
 {
-       Main *bmain = CTX_data_main(C);
-       bNodeTree *ngroup = (bNodeTree *)gnode->id;
-       bNodeLink *link, *linkn;
-       bNode *node, *nextn;
-       bNodeSocket *sock;
-       ListBase anim_basepaths = {NULL, NULL};
-       float min[2], max[2], center[2];
-       int totselect;
-       bool expose_all = false;
-       bNode *input_node, *output_node;
-
-       /* XXX rough guess, not nice but we don't have access to UI constants here ... */
-       static const float offsetx = 200;
-       static const float offsety = 0.0f;
-
-       /* deselect all nodes in the target tree */
-       for (node = ngroup->nodes.first; node; node = node->next)
-               nodeSetSelected(node, false);
-
-       totselect = node_get_selected_minmax(ntree, gnode, min, max);
-       add_v2_v2v2(center, min, max);
-       mul_v2_fl(center, 0.5f);
-
-       /* auto-add interface for "solo" nodes */
-       if (totselect == 1)
-               expose_all = true;
-
-       /* move nodes over */
-       for (node = ntree->nodes.first; node; node = nextn) {
-               nextn = node->next;
-               if (node_group_make_use_node(node, gnode)) {
-                       /* keep track of this node's RNA "base" path (the part of the pat identifying the node)
-                        * if the old nodetree has animation data which potentially covers this node
-                        */
-                       if (ntree->adt) {
-                               PointerRNA ptr;
-                               char *path;
-
-                               RNA_pointer_create(&ntree->id, &RNA_Node, node, &ptr);
-                               path = RNA_path_from_ID_to_struct(&ptr);
-
-                               if (path)
-                                       BLI_addtail(&anim_basepaths, BLI_genericNodeN(path));
-                       }
-
-                       /* ensure valid parent pointers, detach if parent stays outside the group */
-                       if (node->parent && !(node->parent->flag & NODE_SELECT))
-                               nodeDetachNode(node);
-
-                       /* change node-collection membership */
-                       BLI_remlink(&ntree->nodes, node);
-                       BLI_addtail(&ngroup->nodes, node);
-
-                       /* ensure unique node name in the ngroup */
-                       nodeUniqueName(ngroup, node);
-               }
-       }
-
-       /* move animation data over */
-       if (ntree->adt) {
-               LinkData *ld, *ldn = NULL;
-
-               BKE_animdata_separate_by_basepath(bmain, &ntree->id, &ngroup->id, &anim_basepaths);
-
-               /* paths + their wrappers need to be freed */
-               for (ld = anim_basepaths.first; ld; ld = ldn) {
-                       ldn = ld->next;
-
-                       MEM_freeN(ld->data);
-                       BLI_freelinkN(&anim_basepaths, ld);
-               }
-       }
-
-       /* node groups don't use internal cached data */
-       ntreeFreeCache(ngroup);
-
-       /* create input node */
-       input_node = nodeAddStaticNode(C, ngroup, NODE_GROUP_INPUT);
-       input_node->locx = min[0] - center[0] - offsetx;
-       input_node->locy = -offsety;
-
-       /* create output node */
-       output_node = nodeAddStaticNode(C, ngroup, NODE_GROUP_OUTPUT);
-       output_node->locx = max[0] - center[0] + offsetx;
-       output_node->locy = -offsety;
-
-       /* relink external sockets */
-       for (link = ntree->links.first; link; link = linkn) {
-               int fromselect = node_group_make_use_node(link->fromnode, gnode);
-               int toselect = node_group_make_use_node(link->tonode, gnode);
-
-               linkn = link->next;
-
-               if ((fromselect && link->tonode == gnode) || (toselect && link->fromnode == gnode)) {
-                       /* remove all links to/from the gnode.
-                        * this can remove link information, but there's no general way to preserve it.
-                        */
-                       nodeRemLink(ntree, link);
-               }
-               else if (fromselect && toselect) {
-                       BLI_remlink(&ntree->links, link);
-                       BLI_addtail(&ngroup->links, link);
-               }
-               else if (toselect) {
-                       bNodeSocket *iosock = ntreeAddSocketInterfaceFromSocket(ngroup, link->tonode, link->tosock);
-                       bNodeSocket *input_sock;
-
-                       /* update the group node and interface node sockets,
-                        * so the new interface socket can be linked.
-                        */
-                       node_group_verify(ntree, gnode, (ID *)ngroup);
-                       node_group_input_verify(ngroup, input_node, (ID *)ngroup);
-
-                       /* create new internal link */
-                       input_sock = node_group_input_find_socket(input_node, iosock->identifier);
-                       nodeAddLink(ngroup, input_node, input_sock, link->tonode, link->tosock);
-
-                       /* redirect external link */
-                       link->tonode = gnode;
-                       link->tosock = node_group_find_input_socket(gnode, iosock->identifier);
-               }
-               else if (fromselect) {
-                       /* First check whether the source of this link is already connected to an output.
-                        * If yes, reuse that output instead of duplicating it. */
-                       bool connected = false;
-                       bNodeLink *olink;
-                       for (olink = ngroup->links.first; olink; olink = olink->next) {
-                               if (olink->fromsock == link->fromsock && olink->tonode == output_node) {
-                                       bNodeSocket *output_sock = node_group_find_output_socket(gnode, olink->tosock->identifier);
-                                       link->fromnode = gnode;
-                                       link->fromsock = output_sock;
-                                       connected = true;
-                               }
-                       }
-
-                       if (!connected) {
-                               bNodeSocket *iosock = ntreeAddSocketInterfaceFromSocket(ngroup, link->fromnode, link->fromsock);
-                               bNodeSocket *output_sock;
-
-                               /* update the group node and interface node sockets,
-                                * so the new interface socket can be linked.
-                                */
-                               node_group_verify(ntree, gnode, (ID *)ngroup);
-                               node_group_output_verify(ngroup, output_node, (ID *)ngroup);
-
-                               /* create new internal link */
-                               output_sock = node_group_output_find_socket(output_node, iosock->identifier);
-                               nodeAddLink(ngroup, link->fromnode, link->fromsock, output_node, output_sock);
-
-                               /* redirect external link */
-                               link->fromnode = gnode;
-                               link->fromsock = node_group_find_output_socket(gnode, iosock->identifier);
-                       }
-               }
-       }
-
-       /* move nodes in the group to the center */
-       for (node = ngroup->nodes.first; node; node = node->next) {
-               if (node_group_make_use_node(node, gnode) && !node->parent) {
-                       node->locx -= center[0];
-                       node->locy -= center[1];
-               }
-       }
-
-       /* expose all unlinked sockets too */
-       if (expose_all) {
-               for (node = ngroup->nodes.first; node; node = node->next) {
-                       if (node_group_make_use_node(node, gnode)) {
-                               for (sock = node->inputs.first; sock; sock = sock->next) {
-                                       bNodeSocket *iosock, *input_sock;
-                                       bool skip = false;
-                                       for (link = ngroup->links.first; link; link = link->next) {
-                                               if (link->tosock == sock) {
-                                                       skip = true;
-                                                       break;
-                                               }
-                                       }
-                                       if (skip)
-                                               continue;
-
-                                       iosock = ntreeAddSocketInterfaceFromSocket(ngroup, node, sock);
-
-                                       node_group_input_verify(ngroup, input_node, (ID *)ngroup);
-
-                                       /* create new internal link */
-                                       input_sock = node_group_input_find_socket(input_node, iosock->identifier);
-                                       nodeAddLink(ngroup, input_node, input_sock, node, sock);
-                               }
-
-                               for (sock = node->outputs.first; sock; sock = sock->next) {
-                                       bNodeSocket *iosock, *output_sock;
-                                       bool skip = false;
-                                       for (link = ngroup->links.first; link; link = link->next)
-                                               if (link->fromsock == sock)
-                                                       skip = true;
-                                       if (skip)
-                                               continue;
-
-                                       iosock = ntreeAddSocketInterfaceFromSocket(ngroup, node, sock);
-
-                                       node_group_output_verify(ngroup, output_node, (ID *)ngroup);
-
-                                       /* create new internal link */
-                                       output_sock = node_group_output_find_socket(output_node, iosock->identifier);
-                                       nodeAddLink(ngroup, node, sock, output_node, output_sock);
-                               }
-                       }
-               }
-       }
-
-       /* update of the group tree */
-       ngroup->update |= NTREE_UPDATE | NTREE_UPDATE_LINKS;
-       /* update of the tree containing the group instance node */
-       ntree->update |= NTREE_UPDATE_NODES | NTREE_UPDATE_LINKS;
+  Main *bmain = CTX_data_main(C);
+  bNodeTree *ngroup = (bNodeTree *)gnode->id;
+  bNodeLink *link, *linkn;
+  bNode *node, *nextn;
+  bNodeSocket *sock;
+  ListBase anim_basepaths = {NULL, NULL};
+  float min[2], max[2], center[2];
+  int totselect;
+  bool expose_visible = false;
+  bNode *input_node, *output_node;
+
+  /* XXX rough guess, not nice but we don't have access to UI constants here ... */
+  static const float offsetx = 200;
+  static const float offsety = 0.0f;
+
+  /* deselect all nodes in the target tree */
+  for (node = ngroup->nodes.first; node; node = node->next) {
+    nodeSetSelected(node, false);
+  }
+
+  totselect = node_get_selected_minmax(ntree, gnode, min, max);
+  add_v2_v2v2(center, min, max);
+  mul_v2_fl(center, 0.5f);
+
+  /* auto-add interface for "solo" nodes */
+  if (totselect == 1) {
+    expose_visible = true;
+  }
+
+  /* move nodes over */
+  for (node = ntree->nodes.first; node; node = nextn) {
+    nextn = node->next;
+    if (node_group_make_use_node(node, gnode)) {
+      /* keep track of this node's RNA "base" path (the part of the pat identifying the node)
+       * if the old nodetree has animation data which potentially covers this node
+       */
+      if (ntree->adt) {
+        PointerRNA ptr;
+        char *path;
+
+        RNA_pointer_create(&ntree->id, &RNA_Node, node, &ptr);
+        path = RNA_path_from_ID_to_struct(&ptr);
+
+        if (path) {
+          BLI_addtail(&anim_basepaths, BLI_genericNodeN(path));
+        }
+      }
+
+      /* ensure valid parent pointers, detach if parent stays outside the group */
+      if (node->parent && !(node->parent->flag & NODE_SELECT)) {
+        nodeDetachNode(node);
+      }
+
+      /* change node-collection membership */
+      BLI_remlink(&ntree->nodes, node);
+      BLI_addtail(&ngroup->nodes, node);
+
+      /* ensure unique node name in the ngroup */
+      nodeUniqueName(ngroup, node);
+    }
+  }
+
+  /* move animation data over */
+  if (ntree->adt) {
+    LinkData *ld, *ldn = NULL;
+
+    BKE_animdata_separate_by_basepath(bmain, &ntree->id, &ngroup->id, &anim_basepaths);
+
+    /* paths + their wrappers need to be freed */
+    for (ld = anim_basepaths.first; ld; ld = ldn) {
+      ldn = ld->next;
+
+      MEM_freeN(ld->data);
+      BLI_freelinkN(&anim_basepaths, ld);
+    }
+  }
+
+  /* node groups don't use internal cached data */
+  ntreeFreeCache(ngroup);
+
+  /* create input node */
+  input_node = nodeAddStaticNode(C, ngroup, NODE_GROUP_INPUT);
+  input_node->locx = min[0] - center[0] - offsetx;
+  input_node->locy = -offsety;
+
+  /* create output node */
+  output_node = nodeAddStaticNode(C, ngroup, NODE_GROUP_OUTPUT);
+  output_node->locx = max[0] - center[0] + offsetx;
+  output_node->locy = -offsety;
+
+  /* relink external sockets */
+  for (link = ntree->links.first; link; link = linkn) {
+    int fromselect = node_group_make_use_node(link->fromnode, gnode);
+    int toselect = node_group_make_use_node(link->tonode, gnode);
+
+    linkn = link->next;
+
+    if ((fromselect && link->tonode == gnode) || (toselect && link->fromnode == gnode)) {
+      /* remove all links to/from the gnode.
+       * this can remove link information, but there's no general way to preserve it.
+       */
+      nodeRemLink(ntree, link);
+    }
+    else if (fromselect && toselect) {
+      BLI_remlink(&ntree->links, link);
+      BLI_addtail(&ngroup->links, link);
+    }
+    else if (toselect) {
+      bNodeSocket *iosock = ntreeAddSocketInterfaceFromSocket(ngroup, link->tonode, link->tosock);
+      bNodeSocket *input_sock;
+
+      /* update the group node and interface node sockets,
+       * so the new interface socket can be linked.
+       */
+      node_group_update(ntree, gnode);
+      node_group_input_update(ngroup, input_node);
+
+      /* create new internal link */
+      input_sock = node_group_input_find_socket(input_node, iosock->identifier);
+      nodeAddLink(ngroup, input_node, input_sock, link->tonode, link->tosock);
+
+      /* redirect external link */
+      link->tonode = gnode;
+      link->tosock = node_group_find_input_socket(gnode, iosock->identifier);
+    }
+    else if (fromselect) {
+      /* First check whether the source of this link is already connected to an output.
+       * If yes, reuse that output instead of duplicating it. */
+      bool connected = false;
+      bNodeLink *olink;
+      for (olink = ngroup->links.first; olink; olink = olink->next) {
+        if (olink->fromsock == link->fromsock && olink->tonode == output_node) {
+          bNodeSocket *output_sock = node_group_find_output_socket(gnode,
+                                                                   olink->tosock->identifier);
+          link->fromnode = gnode;
+          link->fromsock = output_sock;
+          connected = true;
+        }
+      }
+
+      if (!connected) {
+        bNodeSocket *iosock = ntreeAddSocketInterfaceFromSocket(
+            ngroup, link->fromnode, link->fromsock);
+        bNodeSocket *output_sock;
+
+        /* update the group node and interface node sockets,
+         * so the new interface socket can be linked.
+         */
+        node_group_update(ntree, gnode);
+        node_group_output_update(ngroup, output_node);
+
+        /* create new internal link */
+        output_sock = node_group_output_find_socket(output_node, iosock->identifier);
+        nodeAddLink(ngroup, link->fromnode, link->fromsock, output_node, output_sock);
+
+        /* redirect external link */
+        link->fromnode = gnode;
+        link->fromsock = node_group_find_output_socket(gnode, iosock->identifier);
+      }
+    }
+  }
+
+  /* move nodes in the group to the center */
+  for (node = ngroup->nodes.first; node; node = node->next) {
+    if (node_group_make_use_node(node, gnode) && !node->parent) {
+      node->locx -= center[0];
+      node->locy -= center[1];
+    }
+  }
+
+  /* expose all unlinked sockets too but only the visible ones*/
+  if (expose_visible) {
+    for (node = ngroup->nodes.first; node; node = node->next) {
+      if (node_group_make_use_node(node, gnode)) {
+        for (sock = node->inputs.first; sock; sock = sock->next) {
+          bNodeSocket *iosock, *input_sock;
+          bool skip = false;
+          for (link = ngroup->links.first; link; link = link->next) {
+            if (link->tosock == sock) {
+              skip = true;
+              break;
+            }
+          }
+          if (sock->flag & (SOCK_HIDDEN | SOCK_UNAVAIL)) {
+            skip = true;
+          }
+          if (skip) {
+            continue;
+          }
+
+          iosock = ntreeAddSocketInterfaceFromSocket(ngroup, node, sock);
+
+          node_group_input_update(ngroup, input_node);
+
+          /* create new internal link */
+          input_sock = node_group_input_find_socket(input_node, iosock->identifier);
+          nodeAddLink(ngroup, input_node, input_sock, node, sock);
+        }
+
+        for (sock = node->outputs.first; sock; sock = sock->next) {
+          bNodeSocket *iosock, *output_sock;
+          bool skip = false;
+          for (link = ngroup->links.first; link; link = link->next) {
+            if (link->fromsock == sock) {
+              skip = true;
+            }
+          }
+          if (sock->flag & (SOCK_HIDDEN | SOCK_UNAVAIL)) {
+            skip = true;
+          }
+          if (skip) {
+            continue;
+          }
+
+          iosock = ntreeAddSocketInterfaceFromSocket(ngroup, node, sock);
+
+          node_group_output_update(ngroup, output_node);
+
+          /* create new internal link */
+          output_sock = node_group_output_find_socket(output_node, iosock->identifier);
+          nodeAddLink(ngroup, node, sock, output_node, output_sock);
+        }
+      }
+    }
+  }
+
+  /* update of the group tree */
+  ngroup->update |= NTREE_UPDATE | NTREE_UPDATE_LINKS;
+  /* update of the tree containing the group instance node */
+  ntree->update |= NTREE_UPDATE_NODES | NTREE_UPDATE_LINKS;
 }
 
-static bNode *node_group_make_from_selected(const bContext *C, bNodeTree *ntree, const char *ntype, const char *ntreetype)
+static bNode *node_group_make_from_selected(const bContext *C,
+                                            bNodeTree *ntree,
+                                            const char *ntype,
+                                            const char *ntreetype)
 {
-       Main *bmain = CTX_data_main(C);
-       bNode *gnode;
-       bNodeTree *ngroup;
-       float min[2], max[2];
-       int totselect;
+  Main *bmain = CTX_data_main(C);
+  bNode *gnode;
+  bNodeTree *ngroup;
+  float min[2], max[2];
+  int totselect;
 
-       totselect = node_get_selected_minmax(ntree, NULL, min, max);
-       /* don't make empty group */
-       if (totselect == 0)
-               return NULL;
+  totselect = node_get_selected_minmax(ntree, NULL, min, max);
+  /* don't make empty group */
+  if (totselect == 0) {
+    return NULL;
+  }
 
-       /* new nodetree */
-       ngroup = ntreeAddTree(bmain, "NodeGroup", ntreetype);
+  /* new nodetree */
+  ngroup = ntreeAddTree(bmain, "NodeGroup", ntreetype);
 
-       /* make group node */
-       gnode = nodeAddNode(C, ntree, ntype);
-       gnode->id = (ID *)ngroup;
+  /* make group node */
+  gnode = nodeAddNode(C, ntree, ntype);
+  gnode->id = (ID *)ngroup;
 
-       gnode->locx = 0.5f * (min[0] + max[0]);
-       gnode->locy = 0.5f * (min[1] + max[1]);
+  gnode->locx = 0.5f * (min[0] + max[0]);
+  gnode->locy = 0.5f * (min[1] + max[1]);
 
-       node_group_make_insert_selected(C, ntree, gnode);
+  node_group_make_insert_selected(C, ntree, gnode);
 
-       /* update of the tree containing the group instance node */
-       ntree->update |= NTREE_UPDATE_NODES;
+  /* update of the tree containing the group instance node */
+  ntree->update |= NTREE_UPDATE_NODES;
 
-       return gnode;
+  return gnode;
 }
 
 static int node_group_make_exec(bContext *C, wmOperator *op)
 {
-       SpaceNode *snode = CTX_wm_space_node(C);
-       bNodeTree *ntree = snode->edittree;
-       const char *ntree_idname = group_ntree_idname(C);
-       const char *node_idname = group_node_idname(C);
-       bNodeTree *ngroup;
-       bNode *gnode;
-       Main *bmain = CTX_data_main(C);
+  SpaceNode *snode = CTX_wm_space_node(C);
+  bNodeTree *ntree = snode->edittree;
+  const char *ntree_idname = group_ntree_idname(C);
+  const char *node_idname = group_node_idname(C);
+  bNodeTree *ngroup;
+  bNode *gnode;
+  Main *bmain = CTX_data_main(C);
 
-       ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C));
+  ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C));
 
-       if (!node_group_make_test_selected(ntree, NULL, ntree_idname, op->reports))
-               return OPERATOR_CANCELLED;
+  if (!node_group_make_test_selected(ntree, NULL, ntree_idname, op->reports)) {
+    return OPERATOR_CANCELLED;
+  }
 
-       gnode = node_group_make_from_selected(C, ntree, node_idname, ntree_idname);
+  gnode = node_group_make_from_selected(C, ntree, node_idname, ntree_idname);
 
-       if (gnode) {
-               ngroup = (bNodeTree *)gnode->id;
+  if (gnode) {
+    ngroup = (bNodeTree *)gnode->id;
 
-               nodeSetActive(ntree, gnode);
-               if (ngroup) {
-                       ED_node_tree_push(snode, ngroup, gnode);
-                       ntreeUpdateTree(bmain, ngroup);
-               }
-       }
+    nodeSetActive(ntree, gnode);
+    if (ngroup) {
+      ED_node_tree_push(snode, ngroup, gnode);
+      ntreeUpdateTree(bmain, ngroup);
+    }
+  }
 
-       ntreeUpdateTree(bmain, ntree);
+  ntreeUpdateTree(bmain, ntree);
 
-       snode_notify(C, snode);
-       snode_dag_update(C, snode);
+  snode_notify(C, snode);
+  snode_dag_update(C, snode);
 
-       /* We broke relations in node tree, need to rebuild them in the grahes. */
-       DEG_relations_tag_update(bmain);
+  /* We broke relations in node tree, need to rebuild them in the grahes. */
+  DEG_relations_tag_update(bmain);
 
-       return OPERATOR_FINISHED;
+  return OPERATOR_FINISHED;
 }
 
 void NODE_OT_group_make(wmOperatorType *ot)
 {
-       /* identifiers */
-       ot->name = "Make Group";
-       ot->description = "Make group from selected nodes";
-       ot->idname = "NODE_OT_group_make";
+  /* identifiers */
+  ot->name = "Make Group";
+  ot->description = "Make group from selected nodes";
+  ot->idname = "NODE_OT_group_make";
 
-       /* api callbacks */
-       ot->exec = node_group_make_exec;
-       ot->poll = node_group_operator_editable;
+  /* api callbacks */
+  ot->exec = node_group_make_exec;
+  ot->poll = node_group_operator_editable;
 
-       /* flags */
-       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+  /* flags */
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 }
 
 /* ****************** Group Insert operator ******************* */
 
 static int node_group_insert_exec(bContext *C, wmOperator *op)
 {
-       SpaceNode *snode = CTX_wm_space_node(C);
-       bNodeTree *ntree = snode->edittree;
-       bNodeTree *ngroup;
-       const char *node_idname = group_node_idname(C);
-       bNode *gnode;
-       Main *bmain = CTX_data_main(C);
+  SpaceNode *snode = CTX_wm_space_node(C);
+  bNodeTree *ntree = snode->edittree;
+  bNodeTree *ngroup;
+  const char *node_idname = group_node_idname(C);
+  bNode *gnode;
+  Main *bmain = CTX_data_main(C);
 
-       ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C));
+  ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C));
 
-       gnode = node_group_get_active(C, node_idname);
+  gnode = node_group_get_active(C, node_idname);
 
-       if (!gnode || !gnode->id)
-               return OPERATOR_CANCELLED;
+  if (!gnode || !gnode->id) {
+    return OPERATOR_CANCELLED;
+  }
 
-       ngroup = (bNodeTree *)gnode->id;
-       if (!node_group_make_test_selected(ntree, gnode, ngroup->idname, op->reports))
-               return OPERATOR_CANCELLED;
+  ngroup = (bNodeTree *)gnode->id;
+  if (!node_group_make_test_selected(ntree, gnode, ngroup->idname, op->reports)) {
+    return OPERATOR_CANCELLED;
+  }
 
-       node_group_make_insert_selected(C, ntree, gnode);
+  node_group_make_insert_selected(C, ntree, gnode);
 
-       nodeSetActive(ntree, gnode);
-       ED_node_tree_push(snode, ngroup, gnode);
-       ntreeUpdateTree(bmain, ngroup);
+  nodeSetActive(ntree, gnode);
+  ED_node_tree_push(snode, ngroup, gnode);
+  ntreeUpdateTree(bmain, ngroup);
 
-       ntreeUpdateTree(bmain, ntree);
+  ntreeUpdateTree(bmain, ntree);
 
-       snode_notify(C, snode);
-       snode_dag_update(C, snode);
+  snode_notify(C, snode);
+  snode_dag_update(C, snode);
 
-       return OPERATOR_FINISHED;
+  return OPERATOR_FINISHED;
 }
 
 void NODE_OT_group_insert(wmOperatorType *ot)
 {
-       /* identifiers */
-       ot->name = "Group Insert";
-       ot->description = "Insert selected nodes into a node group";
-       ot->idname = "NODE_OT_group_insert";
+  /* identifiers */
+  ot->name = "Group Insert";
+  ot->description = "Insert selected nodes into a node group";
+  ot->idname = "NODE_OT_group_insert";
 
-       /* api callbacks */
-       ot->exec = node_group_insert_exec;
-       ot->poll = node_group_operator_editable;
+  /* api callbacks */
+  ot->exec = node_group_insert_exec;
+  ot->poll = node_group_operator_editable;
 
-       /* flags */
-       ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+  /* flags */
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 }