Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLukas Toenne <lukas.toenne@googlemail.com>2013-03-18 20:34:57 +0400
committerLukas Toenne <lukas.toenne@googlemail.com>2013-03-18 20:34:57 +0400
commit4638e5f99a9ba59ad0b8a1fd52b12e876480b9e8 (patch)
tree2444f12b4612440f44cf02835cdf5951b6564e92 /source/blender/nodes/intern/node_common.c
parent7bfef29f2f2a1b262d28abdc6e30fcd9c1f1caad (diff)
Merge of the PyNodes branch (aka "custom nodes") into trunk.
PyNodes opens up the node system in Blender to scripters and adds a number of UI-level improvements. === Dynamic node type registration === Node types can now be added at runtime, using the RNA registration mechanism from python. This enables addons such as render engines to create a complete user interface with nodes. Examples of how such nodes can be defined can be found in my personal wiki docs atm [1] and as a script template in release/scripts/templates_py/custom_nodes.py [2]. === Node group improvements === Each node editor now has a tree history of edited node groups, which allows opening and editing nested node groups. The node editor also supports pinning now, so that different spaces can be used to edit different node groups simultaneously. For more ramblings and rationale see (really old) blog post on code.blender.org [3]. The interface of node groups has been overhauled. Sockets of a node group are no longer displayed in columns on either side, but instead special input/output nodes are used to mirror group sockets inside a node tree. This solves the problem of long node lines in groups and allows more adaptable node layout. Internal sockets can be exposed from a group by either connecting to the extension sockets in input/output nodes (shown as empty circle) or by adding sockets from the node property bar in the "Interface" panel. Further details such as the socket name can also be changed there. [1] http://wiki.blender.org/index.php/User:Phonybone/Python_Nodes [2] http://projects.blender.org/scm/viewvc.php/trunk/blender/release/scripts/templates_py/custom_nodes.py?view=markup&root=bf-blender [3] http://code.blender.org/index.php/2012/01/improving-node-group-interface-editing/
Diffstat (limited to 'source/blender/nodes/intern/node_common.c')
-rw-r--r--source/blender/nodes/intern/node_common.c514
1 files changed, 259 insertions, 255 deletions
diff --git a/source/blender/nodes/intern/node_common.c b/source/blender/nodes/intern/node_common.c
index d59061dfc0a..85e2f775ad9 100644
--- a/source/blender/nodes/intern/node_common.c
+++ b/source/blender/nodes/intern/node_common.c
@@ -31,19 +31,22 @@
#include <string.h>
+#include <stddef.h>
#include "DNA_node_types.h"
#include "BLI_listbase.h"
+#include "BLI_math.h"
+#include "BLI_path_util.h"
#include "BLI_string.h"
#include "BLI_utildefines.h"
#include "BLF_translation.h"
#include "BKE_global.h"
+#include "BKE_idprop.h"
#include "BKE_library.h"
#include "BKE_main.h"
-#include "BLI_math.h"
#include "BKE_node.h"
#include "RNA_access.h"
@@ -55,247 +58,110 @@
#include "node_util.h"
#include "node_exec.h"
#include "NOD_socket.h"
+#include "NOD_common.h"
+
/**** Group ****/
-bNodeSocket *node_group_find_input(bNode *gnode, bNodeSocket *gsock)
+bNodeSocket *node_group_find_input_socket(bNode *groupnode, const char *identifier)
{
bNodeSocket *sock;
- for (sock=gnode->inputs.first; sock; sock=sock->next)
- if (sock->groupsock == gsock)
+ for (sock=groupnode->inputs.first; sock; sock=sock->next)
+ if (strcmp(sock->identifier, identifier)==0)
return sock;
return NULL;
}
-bNodeSocket *node_group_find_output(bNode *gnode, bNodeSocket *gsock)
+bNodeSocket *node_group_find_output_socket(bNode *groupnode, const char *identifier)
{
bNodeSocket *sock;
- for (sock=gnode->outputs.first; sock; sock=sock->next)
- if (sock->groupsock == gsock)
+ for (sock=groupnode->outputs.first; sock; sock=sock->next)
+ if (strcmp(sock->identifier, identifier)==0)
return sock;
return NULL;
}
-bNodeSocket *node_group_add_extern_socket(bNodeTree *UNUSED(ntree), ListBase *lb, int in_out, bNodeSocket *gsock)
-{
- bNodeSocket *sock;
-
- if (gsock->flag & SOCK_INTERNAL)
- return NULL;
-
- sock= MEM_callocN(sizeof(bNodeSocket), "sock");
-
- /* make a copy of the group socket */
- *sock = *gsock;
- sock->link = NULL;
- sock->next = sock->prev = NULL;
- sock->new_sock = NULL;
-
- /* group sockets are dynamically added */
- sock->flag |= SOCK_DYNAMIC|SOCK_COLLAPSED;
-
- sock->own_index = gsock->own_index;
- sock->groupsock = gsock;
- sock->limit = (in_out==SOCK_IN ? 1 : 0xFFF);
-
- sock->default_value = node_socket_make_default_value(sock->type);
- node_socket_copy_default_value(sock->type, sock->default_value, gsock->default_value);
-
- if (lb)
- BLI_addtail(lb, sock);
-
- return sock;
-}
-
-bNodeSocket *node_group_add_socket(bNodeTree *ngroup, const char *name, int type, int in_out)
-{
- bNodeSocketType *stype = ntreeGetSocketType(type);
- bNodeSocket *gsock = MEM_callocN(sizeof(bNodeSocket), "bNodeSocket");
-
- BLI_strncpy(gsock->name, name, sizeof(gsock->name));
- gsock->type = type;
- /* group sockets are dynamically added */
- gsock->flag |= SOCK_DYNAMIC|SOCK_COLLAPSED;
-
- gsock->next = gsock->prev = NULL;
- gsock->new_sock = NULL;
- gsock->link = NULL;
- /* assign new unique index */
- gsock->own_index = ngroup->cur_index++;
- gsock->limit = (in_out==SOCK_IN ? 0xFFF : 1);
-
- if (stype->value_structsize > 0)
- gsock->default_value = MEM_callocN(stype->value_structsize, "default socket value");
-
- BLI_addtail(in_out==SOCK_IN ? &ngroup->inputs : &ngroup->outputs, gsock);
-
- ngroup->update |= (in_out==SOCK_IN ? NTREE_UPDATE_GROUP_IN : NTREE_UPDATE_GROUP_OUT);
-
- return gsock;
-}
-
-bNodeSocket *node_group_expose_socket(bNodeTree *ngroup, bNodeSocket *sock, int in_out)
-{
- bNodeSocket *gsock= node_group_add_socket(ngroup, sock->name, sock->type, in_out);
-
- /* initialize the default value. */
- node_socket_copy_default_value(gsock->type, gsock->default_value, sock->default_value);
-
- return gsock;
-}
-
-void node_group_expose_all_sockets(bNodeTree *ngroup)
-{
- bNode *node;
- bNodeSocket *sock, *gsock;
-
- for (node=ngroup->nodes.first; node; node=node->next) {
- for (sock=node->inputs.first; sock; sock=sock->next) {
- if (!sock->link && !nodeSocketIsHidden(sock)) {
- gsock = node_group_add_socket(ngroup, sock->name, sock->type, SOCK_IN);
-
- /* initialize the default value. */
- node_socket_copy_default_value(gsock->type, gsock->default_value, sock->default_value);
-
- sock->link = nodeAddLink(ngroup, NULL, gsock, node, sock);
- }
- }
- for (sock=node->outputs.first; sock; sock=sock->next) {
- if (nodeCountSocketLinks(ngroup, sock)==0 && !nodeSocketIsHidden(sock)) {
- gsock = node_group_add_socket(ngroup, sock->name, sock->type, SOCK_OUT);
-
- /* initialize the default value. */
- node_socket_copy_default_value(gsock->type, gsock->default_value, sock->default_value);
-
- gsock->link = nodeAddLink(ngroup, node, sock, NULL, gsock);
- }
- }
- }
-}
-
-void node_group_remove_socket(bNodeTree *ngroup, bNodeSocket *gsock, int in_out)
-{
- nodeRemSocketLinks(ngroup, gsock);
-
- switch (in_out) {
- case SOCK_IN:
- BLI_remlink(&ngroup->inputs, gsock);
- ngroup->update |= NTREE_UPDATE_GROUP_IN;
- break;
- case SOCK_OUT:
- BLI_remlink(&ngroup->outputs, gsock);
- ngroup->update |= NTREE_UPDATE_GROUP_OUT;
- break;
- }
-
- if (gsock->default_value)
- MEM_freeN(gsock->default_value);
-
- MEM_freeN(gsock);
-}
-
/* groups display their internal tree name as label */
const char *node_group_label(bNode *node)
{
return (node->id)? node->id->name + 2: IFACE_("Missing Datablock");
}
-int node_group_valid(bNodeTree *ntree, bNodeTemplate *ntemp)
+int node_group_poll_instance(bNode *node, bNodeTree *nodetree)
{
- bNodeTemplate childtemp;
- bNode *node;
-
- /* regular groups cannot be recursive */
- if (ntree == ntemp->ngroup)
- return 0;
-
- /* make sure all children are valid */
- for (node=ntemp->ngroup->nodes.first; node; node=node->next) {
- childtemp = nodeMakeTemplate(node);
- if (!nodeValid(ntree, &childtemp))
- return 0;
- }
-
- return 1;
-}
-
-bNodeTemplate node_group_template(bNode *node)
-{
- bNodeTemplate ntemp;
- ntemp.type = NODE_GROUP;
- ntemp.ngroup = (bNodeTree *)node->id;
- return ntemp;
+ bNodeTree *grouptree = (bNodeTree*)node->id;
+ if (grouptree)
+ return nodeGroupPoll(nodetree, grouptree);
+ else
+ return 1;
}
-void node_group_init(bNodeTree *ntree, bNode *node, bNodeTemplate *ntemp)
+int nodeGroupPoll(bNodeTree *nodetree, bNodeTree *grouptree)
{
- node->id = (ID *)ntemp->ngroup;
+ bNode *node;
+ int valid = 1;
- /* NB: group socket input/output roles are inverted internally!
- * Group "inputs" work as outputs in links and vice versa.
- */
- if (ntemp->ngroup) {
- bNodeSocket *gsock;
- for (gsock=ntemp->ngroup->inputs.first; gsock; gsock=gsock->next)
- node_group_add_extern_socket(ntree, &node->inputs, SOCK_IN, gsock);
- for (gsock=ntemp->ngroup->outputs.first; gsock; gsock=gsock->next)
- node_group_add_extern_socket(ntree, &node->outputs, SOCK_OUT, gsock);
+ if (nodetree == grouptree)
+ return 0;
+
+ for (node=grouptree->nodes.first; node; node=node->next) {
+ if (!node->typeinfo->poll_instance(node, nodetree)) {
+ valid = 0;
+ break;
+ }
}
+ return valid;
}
-static bNodeSocket *group_verify_socket(bNodeTree *ntree, ListBase *lb, int in_out, bNodeSocket *gsock)
+/* used for both group nodes and interface nodes */
+static bNodeSocket *group_verify_socket(bNodeTree *ntree, bNode *gnode, bNodeSocket *iosock, ListBase *verify_lb, int in_out)
{
bNodeSocket *sock;
- /* group sockets tagged as internal are not exposed ever */
- if (gsock->flag & SOCK_INTERNAL)
- return NULL;
-
- for (sock= lb->first; sock; sock= sock->next) {
- if (sock->own_index==gsock->own_index)
+ for (sock= verify_lb->first; sock; sock= sock->next) {
+ if (strcmp(sock->identifier, iosock->identifier)==0)
break;
}
if (sock) {
- sock->groupsock = gsock;
-
- BLI_strncpy(sock->name, gsock->name, sizeof(sock->name));
- if (gsock->type != sock->type)
- nodeSocketSetType(sock, gsock->type);
-
- /* XXX hack: group socket input/output roles are inverted internally,
- * need to change the limit value when making actual node sockets from them.
- */
- sock->limit = (in_out==SOCK_IN ? 1 : 0xFFF);
-
- BLI_remlink(lb, sock);
-
- return sock;
+ strcpy(sock->name, iosock->name);
}
else {
- return node_group_add_extern_socket(ntree, NULL, in_out, gsock);
+ sock = nodeAddSocket(ntree, gnode, in_out, iosock->idname, iosock->identifier, iosock->name);
+
+ if (iosock->typeinfo->interface_init_socket)
+ iosock->typeinfo->interface_init_socket(ntree, iosock, gnode, sock, "interface");
}
+
+ /* remove from list temporarily, to distinguish from orphaned sockets */
+ BLI_remlink(verify_lb, sock);
+
+ return sock;
}
-static void group_verify_socket_list(bNodeTree *ntree, bNode *node, ListBase *lb, int in_out, ListBase *glb)
+/* used for both group nodes and interface nodes */
+static void group_verify_socket_list(bNodeTree *ntree, bNode *gnode,
+ ListBase *iosock_lb, ListBase *verify_lb, int in_out)
{
- bNodeSocket *sock, *nextsock, *gsock;
+ bNodeSocket *iosock, *sock, *nextsock;
/* step by step compare */
- for (gsock= glb->first; gsock; gsock=gsock->next) {
+
+ iosock = iosock_lb->first;
+ for (; iosock; iosock = iosock->next) {
/* abusing new_sock pointer for verification here! only used inside this function */
- gsock->new_sock= group_verify_socket(ntree, lb, in_out, gsock);
+ iosock->new_sock = group_verify_socket(ntree, gnode, iosock, verify_lb, in_out);
}
/* leftovers are removed */
- for (sock=lb->first; sock; sock=nextsock) {
- nextsock=sock->next;
- if (sock->flag & SOCK_DYNAMIC)
- nodeRemoveSocket(ntree, node, sock);
+ for (sock = verify_lb->first; sock; sock = nextsock) {
+ nextsock = sock->next;
+ nodeRemoveSocket(ntree, gnode, sock);
}
/* and we put back the verified sockets */
- for (gsock= glb->first; gsock; gsock=gsock->next) {
- if (gsock->new_sock) {
- BLI_addtail(lb, gsock->new_sock);
- gsock->new_sock = NULL;
+ iosock = iosock_lb->first;
+ for (; iosock; iosock = iosock->next) {
+ if (iosock->new_sock) {
+ BLI_addtail(verify_lb, iosock->new_sock);
+ iosock->new_sock = NULL;
}
}
}
@@ -304,59 +170,16 @@ static void group_verify_socket_list(bNodeTree *ntree, bNode *node, ListBase *lb
void node_group_verify(struct bNodeTree *ntree, struct bNode *node, struct ID *id)
{
/* check inputs and outputs, and remove or insert them */
- if (node->id==id) {
+ if (id == node->id) {
bNodeTree *ngroup= (bNodeTree *)node->id;
- group_verify_socket_list(ntree, node, &node->inputs, SOCK_IN, &ngroup->inputs);
- group_verify_socket_list(ntree, node, &node->outputs, SOCK_OUT, &ngroup->outputs);
+ group_verify_socket_list(ntree, node, &ngroup->inputs, &node->inputs, SOCK_IN);
+ group_verify_socket_list(ntree, node, &ngroup->outputs, &node->outputs, SOCK_OUT);
}
}
-struct bNodeTree *node_group_edit_get(bNode *node)
-{
- if (node->flag & NODE_GROUP_EDIT)
- return (bNodeTree *)node->id;
- else
- return NULL;
-}
-
-struct bNodeTree *node_group_edit_set(bNode *node, int edit)
-{
- if (edit) {
- bNodeTree *ngroup= (bNodeTree *)node->id;
- if (ngroup) {
- if (ngroup->id.lib)
- ntreeMakeLocal(ngroup);
-
- node->flag |= NODE_GROUP_EDIT;
- }
- return ngroup;
- }
- else {
- node->flag &= ~NODE_GROUP_EDIT;
- return NULL;
- }
-}
-
-void node_group_edit_clear(bNode *node)
-{
- bNodeTree *ngroup= (bNodeTree *)node->id;
- bNode *inode;
-
- node->flag &= ~NODE_GROUP_EDIT;
-
- if (ngroup)
- for (inode=ngroup->nodes.first; inode; inode=inode->next)
- nodeGroupEditClear(inode);
-}
-
-static void UNUSED_FUNCTION(node_group_link)(bNodeTree *ntree, bNodeSocket *sock, int in_out)
-{
- node_group_expose_socket(ntree, sock, in_out);
-}
-
/**** FRAME ****/
-static void node_frame_init(bNodeTree *UNUSED(ntree), bNode *node, bNodeTemplate *UNUSED(ntemp))
+static void node_frame_init(bNodeTree *UNUSED(ntree), bNode *node)
{
NodeFrame *data = (NodeFrame *)MEM_callocN(sizeof(NodeFrame), "frame node storage");
node->storage = data;
@@ -366,19 +189,19 @@ static void node_frame_init(bNodeTree *UNUSED(ntree), bNode *node, bNodeTemplate
data->label_size = 20;
}
-void register_node_type_frame(bNodeTreeType *ttype)
+void register_node_type_frame(void)
{
/* frame type is used for all tree types, needs dynamic allocation */
bNodeType *ntype= MEM_callocN(sizeof(bNodeType), "frame node type");
- node_type_base(ttype, ntype, NODE_FRAME, "Frame", NODE_CLASS_LAYOUT, NODE_BACKGROUND|NODE_OPTIONS);
+ node_type_base(ntype, NODE_FRAME, "Frame", NODE_CLASS_LAYOUT, NODE_BACKGROUND|NODE_OPTIONS);
node_type_init(ntype, node_frame_init);
node_type_storage(ntype, "NodeFrame", node_free_standard_storage, node_copy_standard_storage);
node_type_size(ntype, 150, 100, 0);
node_type_compatibility(ntype, NODE_OLD_SHADING|NODE_NEW_SHADING);
ntype->needs_free = 1;
- nodeRegisterType(ttype, ntype);
+ nodeRegisterType(ntype);
}
@@ -403,34 +226,34 @@ static void node_reroute_update_internal_links(bNodeTree *ntree, bNode *node)
BLI_addtail(&node->internal_links, link);
}
-static void node_reroute_init(bNodeTree *ntree, bNode *node, bNodeTemplate *UNUSED(ntemp))
+static void node_reroute_init(bNodeTree *ntree, bNode *node)
{
/* Note: Cannot use socket templates for this, since it would reset the socket type
* on each file read via the template verification procedure.
*/
- nodeAddSocket(ntree, node, SOCK_IN, "Input", SOCK_RGBA);
- nodeAddSocket(ntree, node, SOCK_OUT, "Output", SOCK_RGBA);
+ nodeAddStaticSocket(ntree, node, SOCK_IN, SOCK_RGBA, PROP_NONE, "Input", "Input");
+ nodeAddStaticSocket(ntree, node, SOCK_OUT, SOCK_RGBA, PROP_NONE, "Output", "Output");
}
-void register_node_type_reroute(bNodeTreeType *ttype)
+void register_node_type_reroute(void)
{
/* frame type is used for all tree types, needs dynamic allocation */
bNodeType *ntype= MEM_callocN(sizeof(bNodeType), "frame node type");
- node_type_base(ttype, ntype, NODE_REROUTE, "Reroute", NODE_CLASS_LAYOUT, 0);
+ node_type_base(ntype, NODE_REROUTE, "Reroute", NODE_CLASS_LAYOUT, 0);
node_type_init(ntype, node_reroute_init);
node_type_internal_links(ntype, node_reroute_update_internal_links);
ntype->needs_free = 1;
- nodeRegisterType(ttype, ntype);
+ nodeRegisterType(ntype);
}
static void node_reroute_inherit_type_recursive(bNodeTree *ntree, bNode *node)
{
bNodeSocket *input = node->inputs.first;
bNodeSocket *output = node->outputs.first;
- int type = SOCK_FLOAT;
bNodeLink *link;
+ int type = SOCK_FLOAT;
/* XXX it would be a little bit more efficient to restrict actual updates
* to rerout nodes connected to an updated node, but there's no reliable flag
@@ -446,6 +269,8 @@ static void node_reroute_inherit_type_recursive(bNodeTree *ntree, bNode *node)
bNode *tonode = link->tonode;
if (!tonode || !fromnode)
continue;
+ if (nodeLinkIsHidden(link))
+ continue;
if (tonode == node && fromnode->type == NODE_REROUTE && !fromnode->done)
node_reroute_inherit_type_recursive(ntree, fromnode);
@@ -462,9 +287,13 @@ static void node_reroute_inherit_type_recursive(bNodeTree *ntree, bNode *node)
/* arbitrary, could also test output->type, both are the same */
if (input->type != type) {
+ PointerRNA input_ptr, output_ptr;
/* same type for input/output */
- nodeSocketSetType(input, type);
- nodeSocketSetType(output, type);
+ RNA_pointer_create((ID *)ntree, &RNA_NodeSocket, input, &input_ptr);
+ RNA_pointer_create((ID *)ntree, &RNA_NodeSocket, output, &output_ptr);
+
+ RNA_enum_set(&input_ptr, "type", type);
+ RNA_enum_set(&output_ptr, "type", type);
}
}
@@ -484,9 +313,8 @@ void ntree_update_reroute_nodes(bNodeTree *ntree)
node_reroute_inherit_type_recursive(ntree, node);
}
-void BKE_node_tree_unlink_id_cb(void *calldata, struct ID *UNUSED(owner_id), struct bNodeTree *ntree)
+void BKE_node_tree_unlink_id(ID *id, struct bNodeTree *ntree)
{
- ID *id = (ID *)calldata;
bNode *node;
for (node = ntree->nodes.first; node; node = node->next) {
@@ -495,3 +323,179 @@ void BKE_node_tree_unlink_id_cb(void *calldata, struct ID *UNUSED(owner_id), str
}
}
}
+
+/**** GROUP_INPUT / GROUP_OUTPUT ****/
+
+static void node_group_input_init(bNodeTree *ntree, bNode *node)
+{
+ node_group_input_verify(ntree, node, (ID *)ntree);
+}
+
+bNodeSocket *node_group_input_find_socket(bNode *node, const char *identifier)
+{
+ bNodeSocket *sock;
+ for (sock=node->outputs.first; sock; sock=sock->next)
+ if (strcmp(sock->identifier, identifier)==0)
+ return sock;
+ return NULL;
+}
+
+void node_group_input_verify(bNodeTree *ntree, bNode *node, ID *id)
+{
+ /* check inputs and outputs, and remove or insert them */
+ if (id == (ID *)ntree) {
+ /* value_in_out inverted for interface nodes to get correct socket value_property */
+ group_verify_socket_list(ntree, node, &ntree->inputs, &node->outputs, SOCK_OUT);
+
+ /* add virtual extension socket */
+ nodeAddSocket(ntree, node, SOCK_OUT, "NodeSocketVirtual", "__extend__", "");
+ }
+}
+
+static void node_group_input_update(bNodeTree *ntree, bNode *node)
+{
+ bNodeSocket *extsock = node->outputs.last;
+ bNodeLink *link;
+ /* Adding a tree socket and verifying will remove the extension socket!
+ * This list caches the existing links from the extension socket
+ * so they can be recreated after verification.
+ */
+ ListBase tmplinks;
+
+ /* find links from the extension socket and store them */
+ tmplinks.first = tmplinks.last = NULL;
+ for (link = ntree->links.first; link; link = link->next) {
+ if (nodeLinkIsHidden(link))
+ continue;
+ if (link->fromsock == extsock) {
+ bNodeLink *tlink = MEM_callocN(sizeof(bNodeLink), "temporary link");
+ *tlink = *link;
+ BLI_addtail(&tmplinks, tlink);
+ }
+ }
+
+ if (tmplinks.first) {
+ bNodeSocket *gsock, *newsock;
+ /* XXX Multiple sockets can be connected to the extension socket at once,
+ * in that case the arbitrary first link determines name and type.
+ * This could be improved by choosing the "best" type among all links,
+ * whatever that means.
+ */
+ bNodeLink *exposelink = tmplinks.first;
+
+ /* XXX what if connecting virtual to virtual socket?? */
+ gsock = ntreeAddSocketInterfaceFromSocket(ntree, exposelink->tonode, exposelink->tosock);
+
+ node_group_input_verify(ntree, node, (ID *)ntree);
+ newsock = node_group_input_find_socket(node, gsock->identifier);
+
+ /* redirect links from the extension socket */
+ for (link = tmplinks.first; link; link = link->next) {
+ nodeAddLink(ntree, node, newsock, link->tonode, link->tosock);
+ }
+
+ BLI_freelistN(&tmplinks);
+ }
+}
+
+void register_node_type_group_input(void)
+{
+ /* used for all tree types, needs dynamic allocation */
+ bNodeType *ntype= MEM_callocN(sizeof(bNodeType), "node type");
+
+ node_type_base(ntype, NODE_GROUP_INPUT, "Group Input", NODE_CLASS_INTERFACE, NODE_OPTIONS);
+ node_type_size(ntype, 140, 80, 200);
+ node_type_init(ntype, node_group_input_init);
+ node_type_update(ntype, node_group_input_update, node_group_input_verify);
+ node_type_compatibility(ntype, NODE_OLD_SHADING|NODE_NEW_SHADING);
+
+ ntype->needs_free = 1;
+ nodeRegisterType(ntype);
+}
+
+static void node_group_output_init(bNodeTree *ntree, bNode *node)
+{
+ node_group_output_verify(ntree, node, (ID *)ntree);
+}
+
+bNodeSocket *node_group_output_find_socket(bNode *node, const char *identifier)
+{
+ bNodeSocket *sock;
+ for (sock=node->inputs.first; sock; sock=sock->next)
+ if (strcmp(sock->identifier, identifier)==0)
+ return sock;
+ return NULL;
+}
+
+void node_group_output_verify(bNodeTree *ntree, bNode *node, ID *id)
+{
+ /* check inputs and outputs, and remove or insert them */
+ if (id == (ID *)ntree) {
+ /* value_in_out inverted for interface nodes to get correct socket value_property */
+ group_verify_socket_list(ntree, node, &ntree->outputs, &node->inputs, SOCK_IN);
+
+ /* add virtual extension socket */
+ nodeAddSocket(ntree, node, SOCK_IN, "NodeSocketVirtual", "__extend__", "");
+ }
+}
+
+static void node_group_output_update(bNodeTree *ntree, bNode *node)
+{
+ bNodeSocket *extsock = node->inputs.last;
+ bNodeLink *link;
+ /* Adding a tree socket and verifying will remove the extension socket!
+ * This list caches the existing links to the extension socket
+ * so they can be recreated after verification.
+ */
+ ListBase tmplinks;
+
+ /* find links to the extension socket and store them */
+ tmplinks.first = tmplinks.last = NULL;
+ for (link = ntree->links.first; link; link = link->next) {
+ if (nodeLinkIsHidden(link))
+ continue;
+ if (link->tosock == extsock) {
+ bNodeLink *tlink = MEM_callocN(sizeof(bNodeLink), "temporary link");
+ *tlink = *link;
+ BLI_addtail(&tmplinks, tlink);
+ }
+ }
+
+ if (tmplinks.first) {
+ bNodeSocket *gsock, *newsock;
+ /* XXX Multiple sockets can be connected to the extension socket at once,
+ * in that case the arbitrary first link determines name and type.
+ * This could be improved by choosing the "best" type among all links,
+ * whatever that means.
+ */
+ bNodeLink *exposelink = tmplinks.first;
+
+ /* XXX what if connecting virtual to virtual socket?? */
+ gsock = ntreeAddSocketInterfaceFromSocket(ntree, exposelink->fromnode, exposelink->fromsock);
+
+ node_group_output_verify(ntree, node, (ID *)ntree);
+ newsock = node_group_output_find_socket(node, gsock->identifier);
+
+ /* redirect links to the extension socket */
+ for (link = tmplinks.first; link; link = link->next) {
+ nodeAddLink(ntree, link->fromnode, link->fromsock, node, newsock);
+ }
+
+ BLI_freelistN(&tmplinks);
+ }
+}
+
+void register_node_type_group_output(void)
+{
+ /* used for all tree types, needs dynamic allocation */
+ bNodeType *ntype= MEM_callocN(sizeof(bNodeType), "node type");
+
+ node_type_base(ntype, NODE_GROUP_OUTPUT, "Group Output", NODE_CLASS_INTERFACE, NODE_OPTIONS);
+ node_type_size(ntype, 140, 80, 200);
+ node_type_init(ntype, node_group_output_init);
+ node_type_update(ntype, node_group_output_update, node_group_output_verify);
+ node_type_compatibility(ntype, NODE_OLD_SHADING|NODE_NEW_SHADING);
+
+ ntype->needs_free = 1;
+ nodeRegisterType(ntype);
+}