static 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_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); }
/* /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// */ static int connect_nodes(Main *bmain, bNodeTree *ntree, bNode *node_fr, bNodeSocket *sock_fr, bNode *node_to, bNodeSocket *sock_to) { bNodeLink *link = nodeAddLink(ntree, node_fr, sock_fr, node_to, sock_to); ntreeUpdateTree(bmain, ntree); if (!(link->flag & NODE_LINK_VALID)) { nodeRemLink(ntree, link); return 0; } return 1; } /* connect_nodes() */
/* disconnect socket from the node it is connected to */ static void node_socket_disconnect(Main *bmain, bNodeTree *ntree, bNode *node_to, bNodeSocket *sock_to) { if (!sock_to->link) return; nodeRemLink(ntree, sock_to->link); nodeUpdate(ntree, node_to); ntreeUpdateTree(ntree); ED_node_generic_update(bmain, ntree, node_to); }
/* disconnect socket from the node it is connected to */ static void node_socket_disconnect(Main *bmain, bNodeTree *ntree, bNode *node_to, bNodeSocket *sock_to) { if (!sock_to->link) return; nodeRemLink(ntree, sock_to->link); sock_to->flag |= SOCK_COLLAPSED; nodeUpdate(ntree, node_to); ntreeUpdateTree(bmain, ntree); ED_node_tag_update_nodetree(bmain, ntree); }
/* Re-link displacement output to unconnected normal sockets via bump node. * This way material with have proper displacement in the viewport. */ static void ntree_shader_relink_displacement(bNodeTree *ntree, short compatibility) { if (compatibility != NODE_NEW_SHADING) { /* We can only deal with new shading system here. */ return; } bNode *displacement_node; bNodeSocket *displacement_socket; bNodeLink *displacement_link; if (!ntree_shader_has_displacement(ntree, &displacement_node, &displacement_socket, &displacement_link)) { /* There is no displacement output connected, nothing to re-link. */ return; } /* We have to disconnect displacement output socket, otherwise we'll have * cycles in the Cycles material :) */ nodeRemLink(ntree, displacement_link); /* We can't connect displacement to normal directly, use bump node for that * and hope that it gives good enough approximation. */ bNode *bump_node = nodeAddStaticNode(NULL, ntree, SH_NODE_BUMP); bNodeSocket *bump_input_socket = ntree_shader_node_find_input(bump_node, "Height"); bNodeSocket *bump_output_socket = ntree_shader_node_find_output(bump_node, "Normal"); BLI_assert(bump_input_socket != NULL); BLI_assert(bump_output_socket != NULL); /* Connect bump node to where displacement output was originally * connected to. */ nodeAddLink(ntree, displacement_node, displacement_socket, bump_node, bump_input_socket); /* Connect all free-standing Normal inputs. */ ntree_shader_link_builtin_normal(ntree, bump_node, bump_output_socket, displacement_node, displacement_socket); /* TODO(sergey): Reconnect Geometry Info->Normal sockets to the new * bump node. */ /* We modified the tree, it needs to be updated now. */ ntreeUpdateTree(G.main, ntree); }
static void node_rem_socklist_links(bNodeTree *ntree, ListBase *lb) { bNodeLink *link, *next; bNodeSocket *sock; if (!lb) return; for (sock= lb->first; sock; sock= sock->next) { for (link= ntree->links.first; link; link= next) { next= link->next; if (link->fromsock==sock || link->tosock==sock) { nodeRemLink(ntree, link); } } } }
static void node_group_make_insert_selected(const bContext *C, bNodeTree *ntree, bNode *gnode) { 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; int 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(&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) { 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; int 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; int 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; }
/* returns 1 if its OK */ static int node_group_separate_selected(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) { int fromselect = (link->fromnode && (link->fromnode->flag & NODE_SELECT)); int 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(&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; }
/* add new node connected to this socket, or replace an existing one */ static void node_socket_add_replace(const bContext *C, bNodeTree *ntree, bNode *node_to, bNodeSocket *sock_to, int type, NodeLinkItem *item) { bNode *node_from; bNodeSocket *sock_from_tmp; bNode *node_prev = NULL; /* unlink existing node */ if (sock_to->link) { node_prev = sock_to->link->fromnode; nodeRemLink(ntree, sock_to->link); } /* find existing node that we can use */ for (node_from = ntree->nodes.first; node_from; node_from = node_from->next) if (node_from->type == type) break; if (node_from) if (node_from->inputs.first || node_from->typeinfo->draw_buttons || node_from->typeinfo->draw_buttons_ex) node_from = NULL; if (node_prev && node_prev->type == type && node_link_item_compare(node_prev, item)) { /* keep the previous node if it's the same type */ node_from = node_prev; } else if (!node_from) { node_from = nodeAddStaticNode(C, ntree, type); if (node_prev != NULL) { /* If we're replacing existing node, use it's location. */ node_from->locx = node_prev->locx; node_from->locy = node_prev->locy; node_from->offsetx = node_prev->offsetx; node_from->offsety = node_prev->offsety; } else { /* Avoid exact intersection of nodes. * TODO(sergey): Still not ideal, but better than nothing. */ int index = BLI_findindex(&node_to->inputs, sock_to); BLI_assert(index != -1); node_from->locx = node_to->locx - (node_from->typeinfo->width + 50); node_from->locy = node_to->locy - (node_from->typeinfo->height * index); } node_link_item_apply(node_from, item); } nodeSetActive(ntree, node_from); /* add link */ sock_from_tmp = BLI_findlink(&node_from->outputs, item->socket_index); nodeAddLink(ntree, node_from, sock_from_tmp, node_to, sock_to); sock_to->flag &= ~SOCK_COLLAPSED; /* copy input sockets from previous node */ if (node_prev && node_from != node_prev) { bNodeSocket *sock_prev, *sock_from; for (sock_prev = node_prev->inputs.first; sock_prev; sock_prev = sock_prev->next) { for (sock_from = node_from->inputs.first; sock_from; sock_from = sock_from->next) { if (nodeCountSocketLinks(ntree, sock_from) >= sock_from->limit) continue; if (STREQ(sock_prev->name, sock_from->name) && sock_prev->type == sock_from->type) { bNodeLink *link = sock_prev->link; if (link && link->fromnode) { nodeAddLink(ntree, link->fromnode, link->fromsock, node_from, sock_from); nodeRemLink(ntree, link); } node_socket_copy_default_value(sock_from, sock_prev); } } } /* also preserve mapping for texture nodes */ if (node_from->typeinfo->nclass == NODE_CLASS_TEXTURE && node_prev->typeinfo->nclass == NODE_CLASS_TEXTURE) { memcpy(node_from->storage, node_prev->storage, sizeof(NodeTexBase)); } /* remove node */ node_remove_linked(ntree, node_prev); } nodeUpdate(ntree, node_from); nodeUpdate(ntree, node_to); ntreeUpdateTree(CTX_data_main(C), ntree); ED_node_tag_update_nodetree(CTX_data_main(C), ntree); }
/* returns 1 if its OK */ int node_group_ungroup(bNodeTree *ntree, bNode *gnode) { bNodeLink *link, *linkn; bNode *node, *nextn; bNodeTree *ngroup, *wgroup; ListBase anim_basepaths = {NULL, NULL}; ngroup= (bNodeTree *)gnode->id; if(ngroup==NULL) return 0; /* 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 */ wgroup= ntreeCopyTree(ngroup); /* add the nodes into the ntree */ for(node= wgroup->nodes.first; node; node= nextn) { nextn= node->next; /* 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 (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); node->locx+= gnode->locx; node->locy+= gnode->locy; node->flag |= NODE_SELECT; } /* restore external links to and from the gnode */ for(link= ntree->links.first; link; link= link->next) { if (link->fromnode==gnode) { if (link->fromsock->groupsock) { bNodeSocket *gsock= link->fromsock->groupsock; if (gsock->link) { if (gsock->link->fromnode) { /* NB: using the new internal copies here! the groupsock pointer still maps to the old tree */ link->fromnode = (gsock->link->fromnode ? gsock->link->fromnode->new_node : NULL); link->fromsock = gsock->link->fromsock->new_sock; } else { /* group output directly maps to group input */ bNodeSocket *insock= node_group_find_input(gnode, gsock->link->fromsock); if (insock->link) { link->fromnode = insock->link->fromnode; link->fromsock = insock->link->fromsock; } } } else { /* copy the default input value from the group socket default to the external socket */ convert_socket_value(gsock, link->tosock); } } } } /* remove internal output links, these are not used anymore */ for(link=wgroup->links.first; link; link= linkn) { linkn = link->next; if (!link->tonode) nodeRemLink(wgroup, link); } /* restore links from internal nodes */ for(link= wgroup->links.first; link; link= link->next) { /* indicates link to group input */ if (!link->fromnode) { /* NB: can't use find_group_node_input here, * because gnode sockets still point to the old tree! */ bNodeSocket *insock; for (insock= gnode->inputs.first; insock; insock= insock->next) if (insock->groupsock->new_sock == link->fromsock) break; if (insock->link) { link->fromnode = insock->link->fromnode; link->fromsock = insock->link->fromsock; } else { /* copy the default input value from the group node socket default to the internal socket */ convert_socket_value(insock, link->tosock); nodeRemLink(wgroup, link); } } } /* 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 */ 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 = copy_action(wgroup->adt->action); /* now perform the moving */ BKE_animdata_separate_by_basepath(&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 */ free_libblock(&G.main->action, waction); } /* delete the group instance. this also removes old input links! */ nodeFreeNode(ntree, gnode); /* free the group tree (takes care of user count) */ free_libblock(&G.main->nodetree, wgroup); ntree->update |= NTREE_UPDATE_NODES|NTREE_UPDATE_LINKS; ntreeUpdateTree(ntree); return 1; }
/* add new node connected to this socket, or replace an existing one */ static void node_socket_add_replace(Main *bmain, bNodeTree *ntree, bNode *node_to, bNodeSocket *sock_to, bNodeTemplate *ntemp, int sock_num) { bNode *node_from; bNodeSocket *sock_from; bNode *node_prev = NULL; /* unlink existing node */ if (sock_to->link) { node_prev = sock_to->link->fromnode; nodeRemLink(ntree, sock_to->link); } /* find existing node that we can use */ for (node_from=ntree->nodes.first; node_from; node_from=node_from->next) if (node_from->type == ntemp->type) break; if (node_from) if (!(node_from->inputs.first == NULL && !(node_from->typeinfo->flag & NODE_OPTIONS))) node_from = NULL; if (node_prev && node_prev->type == ntemp->type && (ntemp->type != NODE_GROUP || node_prev->id == &ntemp->ngroup->id)) { /* keep the previous node if it's the same type */ node_from = node_prev; } else if (!node_from) { node_from= nodeAddNode(ntree, ntemp); node_from->locx = node_to->locx - (node_from->typeinfo->width + 50); node_from->locy = node_to->locy; if (node_from->id) id_us_plus(node_from->id); } nodeSetActive(ntree, node_from); /* add link */ sock_from = BLI_findlink(&node_from->outputs, sock_num); nodeAddLink(ntree, node_from, sock_from, node_to, sock_to); /* copy input sockets from previous node */ if (node_prev && node_from != node_prev) { bNodeSocket *sock_prev, *sock_from; for (sock_prev=node_prev->inputs.first; sock_prev; sock_prev=sock_prev->next) { for (sock_from=node_from->inputs.first; sock_from; sock_from=sock_from->next) { if (nodeCountSocketLinks(ntree, sock_from) >= sock_from->limit) continue; if (strcmp(sock_prev->name, sock_from->name) == 0 && sock_prev->type == sock_from->type) { bNodeLink *link = sock_prev->link; if (link && link->fromnode) { nodeAddLink(ntree, link->fromnode, link->fromsock, node_from, sock_from); nodeRemLink(ntree, link); } node_socket_free_default_value(sock_from->type, sock_from->default_value); sock_from->default_value = node_socket_make_default_value(sock_from->type); node_socket_copy_default_value(sock_from->type, sock_from->default_value, sock_prev->default_value); } } } /* also preserve mapping for texture nodes */ if (node_from->typeinfo->nclass == NODE_CLASS_TEXTURE && node_prev->typeinfo->nclass == NODE_CLASS_TEXTURE) { memcpy(node_from->storage, node_prev->storage, sizeof(NodeTexBase)); } /* remove node */ node_remove_linked(ntree, node_prev); } nodeUpdate(ntree, node_from); nodeUpdate(ntree, node_to); ntreeUpdateTree(ntree); ED_node_generic_update(bmain, ntree, node_to); }