This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/blender/nodes/intern/node_common.c
Lukas Tönne 586cf8b190 Nodes: Adds button to groups to change type of sockets.
The menu lists all socket types that are valid for the node tree.
Changing a socket type updates all instances of the group and keeps
existing links to the socket.
If changing the socket type leads to incorrect node connections the
links are flagged as invalid (red) and ignored but not removed. This is
so users don't lose information and can then fix resulting issues.
For example: Changing a Color socket to a Shader socket can cause an
invalid Shader-to-Color connection.

Implementation details:
The new `NODE_OT_tree_socket_change_type` operator uses the generic
`rna_node_socket_type_itemf` function to list all eligible socket types.
It uses the tree type's `valid_socket_type` callback to test for valid
types. In addition it also checks the subtype, because multiple RNA
types are registered for the same base type. The `valid_socket_type`
callback has been modified slightly to accept full socket types instead
of just the base type enum, so that custom (python) socket types can be
used by this operator.

The `nodeModifySocketType` function is now called when group nodes
encounter a socket type mismatch, instead of replacing the socket
entirely. This ensures that links are kept to/from group nodes as well
as group input/output nodes. The `nodeModifySocketType` function now
also takes a full `bNodeSocketType` instead of just the base and subtype
enum (a shortcut `nodeModifySocketTypeStatic` exists for when only
static types are used).

Differential Revision: https://developer.blender.org/D10912
2021-07-06 18:36:11 +01:00

631 lines
18 KiB
C

/*
* 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
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2007 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup nodes
*/
#include <stddef.h>
#include <string.h>
#include "DNA_node_types.h"
#include "BLI_listbase.h"
#include "BLI_string.h"
#include "BLI_utildefines.h"
#include "BLT_translation.h"
#include "BKE_node.h"
#include "RNA_types.h"
#include "MEM_guardedalloc.h"
#include "NOD_common.h"
#include "node_common.h"
#include "node_util.h"
enum {
REFINE_FORWARD = 1 << 0,
REFINE_BACKWARD = 1 << 1,
};
/* -------------------------------------------------------------------- */
/** \name Node Group
* \{ */
bNodeSocket *node_group_find_input_socket(bNode *groupnode, const char *identifier)
{
bNodeSocket *sock;
for (sock = groupnode->inputs.first; sock; sock = sock->next) {
if (STREQ(sock->identifier, identifier)) {
return sock;
}
}
return NULL;
}
bNodeSocket *node_group_find_output_socket(bNode *groupnode, const char *identifier)
{
bNodeSocket *sock;
for (sock = groupnode->outputs.first; sock; sock = sock->next) {
if (STREQ(sock->identifier, identifier)) {
return sock;
}
}
return NULL;
}
/* groups display their internal tree name as label */
void node_group_label(bNodeTree *UNUSED(ntree), bNode *node, char *label, int maxlen)
{
BLI_strncpy(label, (node->id) ? node->id->name + 2 : IFACE_("Missing Data-Block"), maxlen);
}
bool node_group_poll_instance(bNode *node, bNodeTree *nodetree, const char **disabled_hint)
{
if (node->typeinfo->poll(node->typeinfo, nodetree, disabled_hint)) {
bNodeTree *grouptree = (bNodeTree *)node->id;
if (grouptree) {
return nodeGroupPoll(nodetree, grouptree, disabled_hint);
}
return true; /* without a linked node tree, group node is always ok */
}
return false;
}
bool nodeGroupPoll(bNodeTree *nodetree, bNodeTree *grouptree, const char **r_disabled_hint)
{
bNode *node;
bool valid = true;
/* unspecified node group, generally allowed
* (if anything, should be avoided on operator level)
*/
if (grouptree == NULL) {
return true;
}
if (nodetree == grouptree) {
*r_disabled_hint = "Nesting a node group inside of itself is not allowed";
return false;
}
for (node = grouptree->nodes.first; node; node = node->next) {
if (node->typeinfo->poll_instance &&
!node->typeinfo->poll_instance(node, nodetree, r_disabled_hint)) {
valid = false;
break;
}
}
return valid;
}
/* 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;
for (sock = verify_lb->first; sock; sock = sock->next) {
if (STREQ(sock->identifier, iosock->identifier)) {
break;
}
}
if (sock) {
strcpy(sock->name, iosock->name);
const int mask = SOCK_HIDE_VALUE;
sock->flag = (sock->flag & ~mask) | (iosock->flag & mask);
/* Update socket type if necessary */
if (sock->typeinfo != iosock->typeinfo) {
nodeModifySocketType(ntree, gnode, sock, iosock->idname);
/* Flag the tree to make sure link validity is updated after type changes. */
ntree->update |= NTREE_UPDATE_LINKS;
}
if (iosock->typeinfo->interface_verify_socket) {
iosock->typeinfo->interface_verify_socket(ntree, iosock, gnode, sock, "interface");
}
}
else {
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;
}
/* 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 *iosock, *sock, *nextsock;
/* step by step compare */
iosock = iosock_lb->first;
for (; iosock; iosock = iosock->next) {
/* abusing new_sock pointer for verification here! only used inside this function */
iosock->new_sock = group_verify_socket(ntree, gnode, iosock, verify_lb, in_out);
}
/* leftovers are removed */
for (sock = verify_lb->first; sock; sock = nextsock) {
nextsock = sock->next;
nodeRemoveSocket(ntree, gnode, sock);
}
/* and we put back the verified sockets */
iosock = iosock_lb->first;
for (; iosock; iosock = iosock->next) {
if (iosock->new_sock) {
BLI_addtail(verify_lb, iosock->new_sock);
iosock->new_sock = NULL;
}
}
}
/* make sure all group node in ntree, which use ngroup, are sync'd */
void node_group_update(struct bNodeTree *ntree, struct bNode *node)
{
/* check inputs and outputs, and remove or insert them */
if (node->id == NULL) {
nodeRemoveAllSockets(ntree, node);
}
else if ((ID_IS_LINKED(node->id) && (node->id->tag & LIB_TAG_MISSING))) {
/* Missing datablock, leave sockets unchanged so that when it comes back
* the links remain valid. */
}
else {
bNodeTree *ngroup = (bNodeTree *)node->id;
group_verify_socket_list(ntree, node, &ngroup->inputs, &node->inputs, SOCK_IN);
group_verify_socket_list(ntree, node, &ngroup->outputs, &node->outputs, SOCK_OUT);
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Node Frame
* \{ */
static void node_frame_init(bNodeTree *UNUSED(ntree), bNode *node)
{
NodeFrame *data = (NodeFrame *)MEM_callocN(sizeof(NodeFrame), "frame node storage");
node->storage = data;
data->flag |= NODE_FRAME_SHRINK;
data->label_size = 20;
}
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");
ntype->free_self = (void (*)(bNodeType *))MEM_freeN;
node_type_base(ntype, NODE_FRAME, "Frame", NODE_CLASS_LAYOUT, NODE_BACKGROUND);
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);
nodeRegisterType(ntype);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Node Re-Route
* \{ */
/* simple, only a single input and output here */
static void node_reroute_update_internal_links(bNodeTree *ntree, bNode *node)
{
bNodeLink *link;
/* Security check! */
if (!ntree) {
return;
}
link = MEM_callocN(sizeof(bNodeLink), "internal node link");
link->fromnode = node;
link->fromsock = node->inputs.first;
link->tonode = node;
link->tosock = node->outputs.first;
/* internal link is always valid */
link->flag |= NODE_LINK_VALID;
BLI_addtail(&node->internal_links, link);
}
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.
*/
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(void)
{
/* frame type is used for all tree types, needs dynamic allocation */
bNodeType *ntype = MEM_callocN(sizeof(bNodeType), "frame node type");
ntype->free_self = (void (*)(bNodeType *))MEM_freeN;
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);
nodeRegisterType(ntype);
}
static void node_reroute_inherit_type_recursive(bNodeTree *ntree, bNode *node, int flag)
{
bNodeSocket *input = node->inputs.first;
bNodeSocket *output = node->outputs.first;
bNodeLink *link;
int type = SOCK_FLOAT;
const char *type_idname = nodeStaticSocketType(type, PROP_NONE);
/* XXX it would be a little bit more efficient to restrict actual updates
* to reroute nodes connected to an updated node, but there's no reliable flag
* to indicate updated nodes (node->update is not set on linking).
*/
node->done = 1;
/* recursive update */
for (link = ntree->links.first; link; link = link->next) {
bNode *fromnode = link->fromnode;
bNode *tonode = link->tonode;
if (!tonode || !fromnode) {
continue;
}
if (nodeLinkIsHidden(link)) {
continue;
}
if (flag & REFINE_FORWARD) {
if (tonode == node && fromnode->type == NODE_REROUTE && !fromnode->done) {
node_reroute_inherit_type_recursive(ntree, fromnode, REFINE_FORWARD);
}
}
if (flag & REFINE_BACKWARD) {
if (fromnode == node && tonode->type == NODE_REROUTE && !tonode->done) {
node_reroute_inherit_type_recursive(ntree, tonode, REFINE_BACKWARD);
}
}
}
/* determine socket type from unambiguous input/output connection if possible */
if (nodeSocketLinkLimit(input) == 1 && input->link) {
type = input->link->fromsock->type;
type_idname = nodeStaticSocketType(type, PROP_NONE);
}
else if (nodeSocketLinkLimit(output) == 1 && output->link) {
type = output->link->tosock->type;
type_idname = nodeStaticSocketType(type, PROP_NONE);
}
if (input->type != type) {
bNodeSocket *ninput = nodeAddSocket(ntree, node, SOCK_IN, type_idname, "input", "Input");
for (link = ntree->links.first; link; link = link->next) {
if (link->tosock == input) {
link->tosock = ninput;
ninput->link = link;
}
}
nodeRemoveSocket(ntree, node, input);
}
if (output->type != type) {
bNodeSocket *noutput = nodeAddSocket(ntree, node, SOCK_OUT, type_idname, "output", "Output");
for (link = ntree->links.first; link; link = link->next) {
if (link->fromsock == output) {
link->fromsock = noutput;
}
}
nodeRemoveSocket(ntree, node, output);
}
nodeUpdateInternalLinks(ntree, node);
}
/* Global update function for Reroute node types.
* This depends on connected nodes, so must be done as a tree-wide update.
*/
void ntree_update_reroute_nodes(bNodeTree *ntree)
{
bNode *node;
/* clear tags */
for (node = ntree->nodes.first; node; node = node->next) {
node->done = 0;
}
for (node = ntree->nodes.first; node; node = node->next) {
if (node->type == NODE_REROUTE && !node->done) {
node_reroute_inherit_type_recursive(ntree, node, REFINE_FORWARD | REFINE_BACKWARD);
}
}
}
static bool node_is_connected_to_output_recursive(bNodeTree *ntree, bNode *node)
{
bNodeLink *link;
/* avoid redundant checks, and infinite loops in case of cyclic node links */
if (node->done) {
return false;
}
node->done = 1;
/* main test, done before child loop so it catches output nodes themselves as well */
if (node->typeinfo->nclass == NODE_CLASS_OUTPUT && node->flag & NODE_DO_OUTPUT) {
return true;
}
/* test all connected nodes, first positive find is sufficient to return true */
for (link = ntree->links.first; link; link = link->next) {
if (link->fromnode == node) {
if (node_is_connected_to_output_recursive(ntree, link->tonode)) {
return true;
}
}
}
return false;
}
bool BKE_node_is_connected_to_output(bNodeTree *ntree, bNode *node)
{
bNode *tnode;
/* clear flags */
for (tnode = ntree->nodes.first; tnode; tnode = tnode->next) {
tnode->done = 0;
}
return node_is_connected_to_output_recursive(ntree, node);
}
void BKE_node_tree_unlink_id(ID *id, struct bNodeTree *ntree)
{
bNode *node;
for (node = ntree->nodes.first; node; node = node->next) {
if (node->id == id) {
node->id = NULL;
}
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Node #GROUP_INPUT / #GROUP_OUTPUT
* \{ */
static void node_group_input_init(bNodeTree *ntree, bNode *node)
{
node_group_input_update(ntree, node);
}
bNodeSocket *node_group_input_find_socket(bNode *node, const char *identifier)
{
bNodeSocket *sock;
for (sock = node->outputs.first; sock; sock = sock->next) {
if (STREQ(sock->identifier, identifier)) {
return sock;
}
}
return NULL;
}
void node_group_input_update(bNodeTree *ntree, bNode *node)
{
bNodeSocket *extsock = node->outputs.last;
bNodeLink *link, *linknext, *exposelink;
/* 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 */
BLI_listbase_clear(&tmplinks);
for (link = ntree->links.first; link; link = linknext) {
linknext = link->next;
if (nodeLinkIsHidden(link)) {
continue;
}
if (link->fromsock == extsock) {
bNodeLink *tlink = MEM_callocN(sizeof(bNodeLink), "temporary link");
*tlink = *link;
BLI_addtail(&tmplinks, tlink);
nodeRemLink(ntree, link);
}
}
/* find valid link to expose */
exposelink = NULL;
for (link = tmplinks.first; link; link = link->next) {
/* 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.
*/
if (link->tosock->type != SOCK_CUSTOM) {
exposelink = link;
break;
}
}
if (exposelink) {
bNodeSocket *gsock, *newsock;
gsock = ntreeAddSocketInterfaceFromSocket(ntree, exposelink->tonode, exposelink->tosock);
node_group_input_update(ntree, node);
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);
/* check inputs and outputs, and remove or insert them */
{
/* 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__", "");
}
}
void register_node_type_group_input(void)
{
/* used for all tree types, needs dynamic allocation */
bNodeType *ntype = MEM_callocN(sizeof(bNodeType), "node type");
ntype->free_self = (void (*)(bNodeType *))MEM_freeN;
node_type_base(ntype, NODE_GROUP_INPUT, "Group Input", NODE_CLASS_INTERFACE, 0);
node_type_size(ntype, 140, 80, 400);
node_type_init(ntype, node_group_input_init);
node_type_update(ntype, node_group_input_update);
nodeRegisterType(ntype);
}
static void node_group_output_init(bNodeTree *ntree, bNode *node)
{
node_group_output_update(ntree, node);
}
bNodeSocket *node_group_output_find_socket(bNode *node, const char *identifier)
{
bNodeSocket *sock;
for (sock = node->inputs.first; sock; sock = sock->next) {
if (STREQ(sock->identifier, identifier)) {
return sock;
}
}
return NULL;
}
void node_group_output_update(bNodeTree *ntree, bNode *node)
{
bNodeSocket *extsock = node->inputs.last;
bNodeLink *link, *linknext, *exposelink;
/* 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 */
BLI_listbase_clear(&tmplinks);
for (link = ntree->links.first; link; link = linknext) {
linknext = link->next;
if (nodeLinkIsHidden(link)) {
continue;
}
if (link->tosock == extsock) {
bNodeLink *tlink = MEM_callocN(sizeof(bNodeLink), "temporary link");
*tlink = *link;
BLI_addtail(&tmplinks, tlink);
nodeRemLink(ntree, link);
}
}
/* find valid link to expose */
exposelink = NULL;
for (link = tmplinks.first; link; link = link->next) {
/* 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.
*/
if (link->fromsock->type != SOCK_CUSTOM) {
exposelink = link;
break;
}
}
if (exposelink) {
bNodeSocket *gsock, *newsock;
/* XXX what if connecting virtual to virtual socket?? */
gsock = ntreeAddSocketInterfaceFromSocket(ntree, exposelink->fromnode, exposelink->fromsock);
node_group_output_update(ntree, node);
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);
/* check inputs and outputs, and remove or insert them */
{
/* 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__", "");
}
}
void register_node_type_group_output(void)
{
/* used for all tree types, needs dynamic allocation */
bNodeType *ntype = MEM_callocN(sizeof(bNodeType), "node type");
ntype->free_self = (void (*)(bNodeType *))MEM_freeN;
node_type_base(ntype, NODE_GROUP_OUTPUT, "Group Output", NODE_CLASS_INTERFACE, 0);
node_type_size(ntype, 140, 80, 400);
node_type_init(ntype, node_group_output_init);
node_type_update(ntype, node_group_output_update);
nodeRegisterType(ntype);
}
/** \} */