Changeset View
Changeset View
Standalone View
Standalone View
source/blender/nodes/intern/node_common.cc
| Show All 24 Lines | |||||
| #include "BKE_node_runtime.hh" | #include "BKE_node_runtime.hh" | ||||
| #include "BKE_node_tree_update.h" | #include "BKE_node_tree_update.h" | ||||
| #include "RNA_types.h" | #include "RNA_types.h" | ||||
| #include "MEM_guardedalloc.h" | #include "MEM_guardedalloc.h" | ||||
| #include "NOD_common.h" | #include "NOD_common.h" | ||||
| #include "NOD_node_declaration.hh" | |||||
| #include "NOD_register.hh" | #include "NOD_register.hh" | ||||
| #include "NOD_socket.h" | |||||
| #include "NOD_socket_declarations.hh" | |||||
| #include "NOD_socket_declarations_geometry.hh" | |||||
| #include "node_common.h" | #include "node_common.h" | ||||
| #include "node_util.h" | #include "node_util.h" | ||||
| using blender::Map; | using blender::Map; | ||||
| using blender::MultiValueMap; | using blender::MultiValueMap; | ||||
| using blender::Set; | using blender::Set; | ||||
| using blender::Stack; | using blender::Stack; | ||||
| using blender::StringRef; | using blender::StringRef; | ||||
| ▲ Show 20 Lines • Show All 73 Lines • ▼ Show 20 Lines | if (node->typeinfo->poll_instance && | ||||
| !node->typeinfo->poll_instance( | !node->typeinfo->poll_instance( | ||||
| const_cast<bNode *>(node), const_cast<bNodeTree *>(nodetree), r_disabled_hint)) { | const_cast<bNode *>(node), const_cast<bNodeTree *>(nodetree), r_disabled_hint)) { | ||||
| return false; | return false; | ||||
| } | } | ||||
| } | } | ||||
| return true; | return true; | ||||
| } | } | ||||
| static void add_new_socket_from_interface(bNodeTree &node_tree, | namespace blender::nodes { | ||||
| bNode &node, | |||||
| const bNodeSocket &interface_socket, | |||||
| const eNodeSocketInOut in_out) | |||||
| { | |||||
| bNodeSocket *socket = nodeAddSocket(&node_tree, | |||||
| &node, | |||||
| in_out, | |||||
| interface_socket.idname, | |||||
| interface_socket.identifier, | |||||
| interface_socket.name); | |||||
| if (interface_socket.typeinfo->interface_init_socket) { | |||||
| interface_socket.typeinfo->interface_init_socket( | |||||
| &node_tree, &interface_socket, &node, socket, "interface"); | |||||
| } | |||||
| } | |||||
| static void update_socket_to_match_interface(bNodeTree &node_tree, | static SocketDeclarationPtr declaration_for_interface_socket(const bNodeSocket &io_socket) | ||||
JacquesLucke: typo | |||||
| bNode &node, | |||||
| bNodeSocket &socket_to_update, | |||||
| const bNodeSocket &interface_socket) | |||||
| { | { | ||||
| strcpy(socket_to_update.name, interface_socket.name); | SocketDeclarationPtr dst; | ||||
| switch (io_socket.type) { | |||||
| const int mask = SOCK_HIDE_VALUE; | case SOCK_FLOAT: { | ||||
| socket_to_update.flag = (socket_to_update.flag & ~mask) | (interface_socket.flag & mask); | const auto &value = *io_socket.default_value_typed<bNodeSocketValueFloat>(); | ||||
| std::unique_ptr<decl::Float> decl = std::make_unique<decl::Float>(); | |||||
| /* Update socket type if necessary */ | decl->subtype = PropertySubType(io_socket.typeinfo->subtype); | ||||
| if (socket_to_update.typeinfo != interface_socket.typeinfo) { | decl->default_value = value.value; | ||||
| nodeModifySocketType(&node_tree, &node, &socket_to_update, interface_socket.idname); | decl->soft_min_value = value.min; | ||||
| } | decl->soft_max_value = value.max; | ||||
| dst = std::move(decl); | |||||
| if (interface_socket.typeinfo->interface_verify_socket) { | break; | ||||
| interface_socket.typeinfo->interface_verify_socket( | |||||
| &node_tree, &interface_socket, &node, &socket_to_update, "interface"); | |||||
| } | } | ||||
| case SOCK_VECTOR: { | |||||
| const auto &value = *io_socket.default_value_typed<bNodeSocketValueVector>(); | |||||
| std::unique_ptr<decl::Vector> decl = std::make_unique<decl::Vector>(); | |||||
| decl->subtype = PropertySubType(io_socket.typeinfo->subtype); | |||||
| decl->default_value = value.value; | |||||
| decl->soft_min_value = value.min; | |||||
| decl->soft_max_value = value.max; | |||||
| dst = std::move(decl); | |||||
| break; | |||||
| } | } | ||||
| case SOCK_RGBA: { | |||||
| /** | const auto &value = *io_socket.default_value_typed<bNodeSocketValueRGBA>(); | ||||
| * Used for group nodes and group input/output nodes to update the list of input or output sockets | std::unique_ptr<decl::Color> decl = std::make_unique<decl::Color>(); | ||||
| * on a node to match the provided interface. Assumes that \a verify_lb is the node's matching | decl->default_value = value.value; | ||||
| * input or output socket list, depending on whether the node is a group input/output or a group | dst = std::move(decl); | ||||
| * node. | break; | ||||
| */ | |||||
| static void group_verify_socket_list(bNodeTree &node_tree, | |||||
| bNode &node, | |||||
| const ListBase &interface_sockets, | |||||
| ListBase &verify_lb, | |||||
| const eNodeSocketInOut in_out, | |||||
| const bool ensure_extend_socket_exists) | |||||
| { | |||||
| ListBase old_sockets = verify_lb; | |||||
| Vector<bNodeSocket *> ordered_old_sockets = old_sockets; | |||||
| BLI_listbase_clear(&verify_lb); | |||||
| LISTBASE_FOREACH (const bNodeSocket *, interface_socket, &interface_sockets) { | |||||
| bNodeSocket *matching_socket = find_matching_socket(old_sockets, interface_socket->identifier); | |||||
| if (matching_socket) { | |||||
| /* If a socket with the same identifier exists in the previous socket list, update it | |||||
| * with the correct name, type, etc. Then move it from the old list to the new one. */ | |||||
| update_socket_to_match_interface(node_tree, node, *matching_socket, *interface_socket); | |||||
| BLI_remlink(&old_sockets, matching_socket); | |||||
| BLI_addtail(&verify_lb, matching_socket); | |||||
| } | } | ||||
| else { | case SOCK_SHADER: { | ||||
| /* If there was no socket with the same identifier already, simply create a new socket | std::unique_ptr<decl::Shader> decl = std::make_unique<decl::Shader>(); | ||||
| * based on the interface socket, which will already add it to the new list. */ | dst = std::move(decl); | ||||
| add_new_socket_from_interface(node_tree, node, *interface_socket, in_out); | break; | ||||
| } | } | ||||
| case SOCK_BOOLEAN: { | |||||
| const auto &value = *io_socket.default_value_typed<bNodeSocketValueBoolean>(); | |||||
| std::unique_ptr<decl::Bool> decl = std::make_unique<decl::Bool>(); | |||||
| decl->default_value = value.value; | |||||
| dst = std::move(decl); | |||||
| break; | |||||
| } | } | ||||
| case SOCK_INT: { | |||||
| if (ensure_extend_socket_exists) { | const auto &value = *io_socket.default_value_typed<bNodeSocketValueInt>(); | ||||
| bNodeSocket *last_socket = static_cast<bNodeSocket *>(old_sockets.last); | std::unique_ptr<decl::Int> decl = std::make_unique<decl::Int>(); | ||||
| if (last_socket != nullptr && STREQ(last_socket->identifier, "__extend__")) { | decl->subtype = PropertySubType(io_socket.typeinfo->subtype); | ||||
| BLI_remlink(&old_sockets, last_socket); | decl->default_value = value.value; | ||||
| BLI_addtail(&verify_lb, last_socket); | decl->soft_min_value = value.min; | ||||
| decl->soft_max_value = value.max; | |||||
| dst = std::move(decl); | |||||
| break; | |||||
| } | } | ||||
| else { | case SOCK_STRING: { | ||||
| nodeAddSocket(&node_tree, &node, in_out, "NodeSocketVirtual", "__extend__", ""); | const auto &value = *io_socket.default_value_typed<bNodeSocketValueString>(); | ||||
| std::unique_ptr<decl::String> decl = std::make_unique<decl::String>(); | |||||
| decl->default_value = value.value; | |||||
| dst = std::move(decl); | |||||
| break; | |||||
| } | } | ||||
| case SOCK_OBJECT: | |||||
| dst = std::make_unique<decl::Object>(); | |||||
| break; | |||||
| case SOCK_IMAGE: | |||||
| dst = std::make_unique<decl::Image>(); | |||||
| break; | |||||
| case SOCK_GEOMETRY: | |||||
| dst = std::make_unique<decl::Geometry>(); | |||||
| break; | |||||
| case SOCK_COLLECTION: | |||||
| dst = std::make_unique<decl::Collection>(); | |||||
| break; | |||||
| case SOCK_TEXTURE: | |||||
| dst = std::make_unique<decl::Texture>(); | |||||
| break; | |||||
| case SOCK_MATERIAL: | |||||
| dst = std::make_unique<decl::Material>(); | |||||
| break; | |||||
| case SOCK_CUSTOM: | |||||
| std::unique_ptr<decl::Custom> decl = std::make_unique<decl::Custom>(); | |||||
| decl->idname_ = io_socket.idname; | |||||
| dst = std::move(decl); | |||||
| break; | |||||
| } | } | ||||
| dst->name = io_socket.name; | |||||
| /* Remove leftover sockets that didn't match the node group's interface. */ | dst->identifier = io_socket.identifier; | ||||
| LISTBASE_FOREACH_MUTABLE (bNodeSocket *, unused_socket, &old_sockets) { | dst->in_out = eNodeSocketInOut(io_socket.in_out); | ||||
| nodeRemoveSocket(&node_tree, &node, unused_socket); | dst->description = io_socket.description; | ||||
| dst->hide_value = io_socket.flag & SOCK_HIDE_VALUE; | |||||
| dst->compact = io_socket.flag & SOCK_COMPACT; | |||||
| return dst; | |||||
| } | } | ||||
| void node_group_declare_dynamic(const bNodeTree & /*node_tree*/, | |||||
| const bNode &node, | |||||
| NodeDeclaration &r_declaration) | |||||
| { | { | ||||
| /* Check if new sockets match the old sockets. */ | const bNodeTree *group = reinterpret_cast<const bNodeTree *>(node.id); | ||||
| int index; | if (!group) { | ||||
| LISTBASE_FOREACH_INDEX (bNodeSocket *, new_socket, &verify_lb, index) { | return; | ||||
| if (index < ordered_old_sockets.size()) { | |||||
| if (ordered_old_sockets[index] != new_socket) { | |||||
| BKE_ntree_update_tag_interface(&node_tree); | |||||
| break; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | } | ||||
| if (ID_IS_LINKED(&group->id) && (group->id.tag & LIB_TAG_MISSING)) { | |||||
| r_declaration.skip_updating_sockets = true; | |||||
| return; | |||||
| } | } | ||||
| r_declaration.skip_updating_sockets = false; | |||||
| void node_group_update(struct bNodeTree *ntree, struct bNode *node) | LISTBASE_FOREACH (const bNodeSocket *, input, &group->inputs) { | ||||
| { | r_declaration.inputs.append(declaration_for_interface_socket(*input)); | ||||
| /* check inputs and outputs, and remove or insert them */ | |||||
| if (node->id == nullptr) { | |||||
| nodeRemoveAllSockets(ntree, node); | |||||
| } | } | ||||
| else if (ID_IS_LINKED(node->id) && (node->id->tag & LIB_TAG_MISSING)) { | LISTBASE_FOREACH (const bNodeSocket *, output, &group->outputs) { | ||||
| /* Missing data-block, leave sockets unchanged so that when it comes back | r_declaration.outputs.append(declaration_for_interface_socket(*output)); | ||||
| * the links remain valid. */ | |||||
| } | |||||
| else { | |||||
| bNodeTree *ngroup = (bNodeTree *)node->id; | |||||
| group_verify_socket_list(*ntree, *node, ngroup->inputs, node->inputs, SOCK_IN, false); | |||||
| group_verify_socket_list(*ntree, *node, ngroup->outputs, node->outputs, SOCK_OUT, false); | |||||
| } | } | ||||
| } | } | ||||
| } // namespace blender::nodes | |||||
| /** \} */ | /** \} */ | ||||
| /* -------------------------------------------------------------------- */ | /* -------------------------------------------------------------------- */ | ||||
| /** \name Node Frame | /** \name Node Frame | ||||
| * \{ */ | * \{ */ | ||||
| static void node_frame_init(bNodeTree * /*ntree*/, bNode *node) | static void node_frame_init(bNodeTree * /*ntree*/, bNode *node) | ||||
| { | { | ||||
| ▲ Show 20 Lines • Show All 164 Lines • ▼ Show 20 Lines | |||||
| } | } | ||||
| /** \} */ | /** \} */ | ||||
| /* -------------------------------------------------------------------- */ | /* -------------------------------------------------------------------- */ | ||||
| /** \name Node #GROUP_INPUT / #GROUP_OUTPUT | /** \name Node #GROUP_INPUT / #GROUP_OUTPUT | ||||
| * \{ */ | * \{ */ | ||||
| static bool is_group_extension_socket(const bNode *node, const bNodeSocket *socket) | |||||
| { | |||||
| return socket->type == SOCK_CUSTOM && ELEM(node->type, NODE_GROUP_OUTPUT, NODE_GROUP_INPUT); | |||||
| } | |||||
| 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 *node_group_input_find_socket(bNode *node, const char *identifier) | ||||
| { | { | ||||
| bNodeSocket *sock; | bNodeSocket *sock; | ||||
| for (sock = (bNodeSocket *)node->outputs.first; sock; sock = sock->next) { | for (sock = (bNodeSocket *)node->outputs.first; sock; sock = sock->next) { | ||||
| if (STREQ(sock->identifier, identifier)) { | if (STREQ(sock->identifier, identifier)) { | ||||
| return sock; | return sock; | ||||
| } | } | ||||
| } | } | ||||
| return nullptr; | return nullptr; | ||||
| } | } | ||||
| void node_group_input_update(bNodeTree *ntree, bNode *node) | namespace blender::nodes { | ||||
| static SocketDeclarationPtr extend_declaration(const eNodeSocketInOut in_out) | |||||
| { | { | ||||
| bNodeSocket *extsock = (bNodeSocket *)node->outputs.last; | std::unique_ptr<decl::Extend> decl = std::make_unique<decl::Extend>(); | ||||
| /* Adding a tree socket and verifying will remove the extension socket! | decl->name = ""; | ||||
| * This list caches the existing links from the extension socket | decl->identifier = "__extend__"; | ||||
| * so they can be recreated after verification. */ | decl->in_out = in_out; | ||||
| Vector<bNodeLink> temp_links; | return decl; | ||||
| /* find links from the extension socket and store them */ | |||||
| LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &ntree->links) { | |||||
| if (nodeLinkIsHidden(link)) { | |||||
| continue; | |||||
| } | } | ||||
| if (link->fromsock == extsock) { | static void group_input_declare_dynamic(const bNodeTree &node_tree, | ||||
| temp_links.append(*link); | const bNode & /*node*/, | ||||
| nodeRemLink(ntree, link); | NodeDeclaration &r_declaration) | ||||
| { | |||||
| LISTBASE_FOREACH (const bNodeSocket *, input, &node_tree.inputs) { | |||||
| r_declaration.outputs.append(declaration_for_interface_socket(*input)); | |||||
| r_declaration.outputs.last()->in_out = SOCK_OUT; | |||||
| } | } | ||||
| r_declaration.outputs.append(extend_declaration(SOCK_OUT)); | |||||
| } | } | ||||
| /* find valid link to expose */ | static void group_output_declare_dynamic(const bNodeTree &node_tree, | ||||
| bNodeLink *exposelink = nullptr; | const bNode & /*node*/, | ||||
| for (bNodeLink &link : temp_links) { | NodeDeclaration &r_declaration) | ||||
| /* XXX Multiple sockets can be connected to the extension socket at once, | { | ||||
| * in that case the arbitrary first link determines name and type. | LISTBASE_FOREACH (const bNodeSocket *, input, &node_tree.outputs) { | ||||
| * This could be improved by choosing the "best" type among all links, | r_declaration.inputs.append(declaration_for_interface_socket(*input)); | ||||
| * whatever that means. | r_declaration.inputs.last()->in_out = SOCK_IN; | ||||
| */ | |||||
| if (!is_group_extension_socket(link.tonode, link.tosock)) { | |||||
| exposelink = &link; | |||||
| break; | |||||
| } | } | ||||
| r_declaration.inputs.append(extend_declaration(SOCK_IN)); | |||||
| } | } | ||||
| if (exposelink) { | static bool group_input_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link) | ||||
| bNodeSocket *gsock = ntreeAddSocketInterfaceFromSocket( | { | ||||
| ntree, exposelink->tonode, exposelink->tosock); | BLI_assert(link->tonode != node); | ||||
| BLI_assert(link->tosock->in_out == SOCK_IN); | |||||
| node_group_input_update(ntree, node); | if (link->fromsock->identifier != StringRef("__extend__")) { | ||||
| bNodeSocket *newsock = node_group_input_find_socket(node, gsock->identifier); | return true; | ||||
| /* redirect links from the extension socket */ | |||||
| for (bNodeLink &link : temp_links) { | |||||
| bNodeLink *newlink = nodeAddLink(ntree, node, newsock, link.tonode, link.tosock); | |||||
| if (newlink->tosock->flag & SOCK_MULTI_INPUT) { | |||||
| newlink->multi_input_socket_index = link.multi_input_socket_index; | |||||
| } | } | ||||
| if (link->tosock->identifier == StringRef("__extend__")) { | |||||
| /* Don't connect to other "extend" sockets. */ | |||||
| return false; | |||||
| } | |||||
| const bNodeSocket *io_socket = ntreeAddSocketInterfaceFromSocket( | |||||
| ntree, link->tonode, link->tosock); | |||||
| if (!io_socket) { | |||||
| return false; | |||||
| } | } | ||||
| update_node_declaration_and_sockets(*ntree, *node); | |||||
| link->fromsock = node_group_input_find_socket(node, io_socket->identifier); | |||||
| return true; | |||||
| } | } | ||||
| group_verify_socket_list(*ntree, *node, ntree->inputs, node->outputs, SOCK_OUT, true); | static bool group_output_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link) | ||||
| { | |||||
| BLI_assert(link->fromnode != node); | |||||
| BLI_assert(link->fromsock->in_out == SOCK_OUT); | |||||
| if (link->tosock->identifier != StringRef("__extend__")) { | |||||
| return true; | |||||
| } | |||||
| if (link->fromsock->identifier == StringRef("__extend__")) { | |||||
| /* Don't connect to other "extend" sockets. */ | |||||
| return false; | |||||
| } | |||||
| const bNodeSocket *io_socket = ntreeAddSocketInterfaceFromSocket( | |||||
| ntree, link->fromnode, link->fromsock); | |||||
| if (!io_socket) { | |||||
| return false; | |||||
| } | |||||
| update_node_declaration_and_sockets(*ntree, *node); | |||||
| link->tosock = node_group_output_find_socket(node, io_socket->identifier); | |||||
| return true; | |||||
| } | } | ||||
| } // namespace blender::nodes | |||||
| void register_node_type_group_input() | void register_node_type_group_input() | ||||
| { | { | ||||
| /* used for all tree types, needs dynamic allocation */ | /* used for all tree types, needs dynamic allocation */ | ||||
| bNodeType *ntype = MEM_cnew<bNodeType>("node type"); | bNodeType *ntype = MEM_cnew<bNodeType>("node type"); | ||||
| ntype->free_self = (void (*)(bNodeType *))MEM_freeN; | ntype->free_self = (void (*)(bNodeType *))MEM_freeN; | ||||
| node_type_base(ntype, NODE_GROUP_INPUT, "Group Input", NODE_CLASS_INTERFACE); | node_type_base(ntype, NODE_GROUP_INPUT, "Group Input", NODE_CLASS_INTERFACE); | ||||
| node_type_size(ntype, 140, 80, 400); | node_type_size(ntype, 140, 80, 400); | ||||
| ntype->initfunc = node_group_input_init; | ntype->declare_dynamic = blender::nodes::group_input_declare_dynamic; | ||||
| ntype->updatefunc = node_group_input_update; | ntype->insert_link = blender::nodes::group_input_insert_link; | ||||
| nodeRegisterType(ntype); | 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 *node_group_output_find_socket(bNode *node, const char *identifier) | ||||
| { | { | ||||
| bNodeSocket *sock; | bNodeSocket *sock; | ||||
| for (sock = (bNodeSocket *)node->inputs.first; sock; sock = sock->next) { | for (sock = (bNodeSocket *)node->inputs.first; sock; sock = sock->next) { | ||||
| if (STREQ(sock->identifier, identifier)) { | if (STREQ(sock->identifier, identifier)) { | ||||
| return sock; | return sock; | ||||
| } | } | ||||
| } | } | ||||
| return nullptr; | return nullptr; | ||||
| } | } | ||||
| void node_group_output_update(bNodeTree *ntree, bNode *node) | |||||
| { | |||||
| bNodeSocket *extsock = (bNodeSocket *)node->inputs.last; | |||||
| /* 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. */ | |||||
| Vector<bNodeLink> temp_links; | |||||
| /* find links to the extension socket and store them */ | |||||
| LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &ntree->links) { | |||||
| if (nodeLinkIsHidden(link)) { | |||||
| continue; | |||||
| } | |||||
| if (link->tosock == extsock) { | |||||
| temp_links.append(*link); | |||||
| nodeRemLink(ntree, link); | |||||
| } | |||||
| } | |||||
| /* find valid link to expose */ | |||||
| bNodeLink *exposelink = nullptr; | |||||
| for (bNodeLink &link : temp_links) { | |||||
| /* 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 (!is_group_extension_socket(link.fromnode, link.fromsock)) { | |||||
| exposelink = &link; | |||||
| break; | |||||
| } | |||||
| } | |||||
| if (exposelink) { | |||||
| /* XXX what if connecting virtual to virtual socket?? */ | |||||
| bNodeSocket *gsock = ntreeAddSocketInterfaceFromSocket( | |||||
| ntree, exposelink->fromnode, exposelink->fromsock); | |||||
| node_group_output_update(ntree, node); | |||||
| bNodeSocket *newsock = node_group_output_find_socket(node, gsock->identifier); | |||||
| /* redirect links to the extension socket */ | |||||
| for (bNodeLink &link : temp_links) { | |||||
| nodeAddLink(ntree, link.fromnode, link.fromsock, node, newsock); | |||||
| } | |||||
| } | |||||
| group_verify_socket_list(*ntree, *node, ntree->outputs, node->inputs, SOCK_IN, true); | |||||
| } | |||||
| void register_node_type_group_output() | void register_node_type_group_output() | ||||
| { | { | ||||
| /* used for all tree types, needs dynamic allocation */ | /* used for all tree types, needs dynamic allocation */ | ||||
| bNodeType *ntype = MEM_cnew<bNodeType>("node type"); | bNodeType *ntype = MEM_cnew<bNodeType>("node type"); | ||||
| ntype->free_self = (void (*)(bNodeType *))MEM_freeN; | ntype->free_self = (void (*)(bNodeType *))MEM_freeN; | ||||
| node_type_base(ntype, NODE_GROUP_OUTPUT, "Group Output", NODE_CLASS_INTERFACE); | node_type_base(ntype, NODE_GROUP_OUTPUT, "Group Output", NODE_CLASS_INTERFACE); | ||||
| node_type_size(ntype, 140, 80, 400); | node_type_size(ntype, 140, 80, 400); | ||||
| ntype->initfunc = node_group_output_init; | ntype->declare_dynamic = blender::nodes::group_output_declare_dynamic; | ||||
| ntype->updatefunc = node_group_output_update; | ntype->insert_link = blender::nodes::group_output_insert_link; | ||||
| ntype->no_muting = true; | ntype->no_muting = true; | ||||
| nodeRegisterType(ntype); | nodeRegisterType(ntype); | ||||
| } | } | ||||
| /** \} */ | /** \} */ | ||||
typo