1137 lines
31 KiB
C
1137 lines
31 KiB
C
/*
|
|
* ***** 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
|
|
* 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) 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
|
|
*/
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "DNA_node_types.h"
|
|
#include "DNA_object_types.h"
|
|
#include "DNA_anim_types.h"
|
|
|
|
#include "BLI_blenlib.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 "ED_node.h" /* own include */
|
|
#include "ED_screen.h"
|
|
#include "ED_render.h"
|
|
|
|
#include "RNA_access.h"
|
|
#include "RNA_define.h"
|
|
#include "RNA_enum_types.h"
|
|
|
|
#include "WM_api.h"
|
|
#include "WM_types.h"
|
|
|
|
#include "UI_resources.h"
|
|
|
|
#include "node_intern.h" /* own include */
|
|
#include "NOD_socket.h"
|
|
|
|
static EnumPropertyItem socket_in_out_items[] = {
|
|
{ SOCK_IN, "SOCK_IN", 0, "Input", "" },
|
|
{ SOCK_OUT, "SOCK_OUT", 0, "Output", "" },
|
|
{ 0, NULL, 0, NULL, NULL },
|
|
};
|
|
|
|
/* ***************** Edit Group operator ************* */
|
|
|
|
void snode_make_group_editable(SpaceNode *snode, bNode *gnode)
|
|
{
|
|
bNode *node;
|
|
|
|
/* make sure nothing has group editing on */
|
|
for (node = snode->nodetree->nodes.first; node; node = node->next) {
|
|
nodeGroupEditClear(node);
|
|
|
|
/* while we're here, clear texture active */
|
|
if (node->typeinfo->nclass == NODE_CLASS_TEXTURE) {
|
|
/* this is not 100% sure to be reliable, see comment on the flag */
|
|
node->flag &= ~NODE_ACTIVE_TEXTURE;
|
|
}
|
|
}
|
|
|
|
if (gnode == NULL) {
|
|
/* with NULL argument we do a toggle */
|
|
if (snode->edittree == snode->nodetree)
|
|
gnode = nodeGetActive(snode->nodetree);
|
|
}
|
|
|
|
if (gnode) {
|
|
snode->edittree = nodeGroupEditSet(gnode, 1);
|
|
|
|
/* deselect all other nodes, so we can also do grabbing of entire subtree */
|
|
for (node = snode->nodetree->nodes.first; node; node = node->next) {
|
|
node_deselect(node);
|
|
|
|
if (node->typeinfo->nclass == NODE_CLASS_TEXTURE) {
|
|
/* this is not 100% sure to be reliable, see comment on the flag */
|
|
node->flag &= ~NODE_ACTIVE_TEXTURE;
|
|
}
|
|
}
|
|
node_select(gnode);
|
|
}
|
|
else
|
|
snode->edittree = snode->nodetree;
|
|
}
|
|
|
|
static int node_group_edit_exec(bContext *C, wmOperator *UNUSED(op))
|
|
{
|
|
SpaceNode *snode = CTX_wm_space_node(C);
|
|
|
|
ED_preview_kill_jobs(C);
|
|
|
|
if (snode->nodetree == snode->edittree) {
|
|
bNode *gnode = nodeGetActive(snode->edittree);
|
|
snode_make_group_editable(snode, gnode);
|
|
}
|
|
else
|
|
snode_make_group_editable(snode, NULL);
|
|
|
|
WM_event_add_notifier(C, NC_SCENE | ND_NODES, NULL);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
static int node_group_edit_invoke(bContext *C, wmOperator *op, wmEvent *UNUSED(event))
|
|
{
|
|
SpaceNode *snode = CTX_wm_space_node(C);
|
|
bNode *gnode;
|
|
|
|
/* XXX callback? */
|
|
if (snode->nodetree == snode->edittree) {
|
|
gnode = nodeGetActive(snode->edittree);
|
|
if (gnode && gnode->id && GS(gnode->id->name) == ID_NT && gnode->id->lib) {
|
|
uiPupMenuOkee(C, op->type->idname, "Make group local?");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
}
|
|
|
|
return node_group_edit_exec(C, op);
|
|
}
|
|
|
|
void NODE_OT_group_edit(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Edit Group";
|
|
ot->description = "Edit node group";
|
|
ot->idname = "NODE_OT_group_edit";
|
|
|
|
/* api callbacks */
|
|
ot->invoke = node_group_edit_invoke;
|
|
ot->exec = node_group_edit_exec;
|
|
ot->poll = ED_operator_node_active;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
/* ***************** Add Group Socket operator ************* */
|
|
|
|
static int node_group_socket_add_exec(bContext *C, wmOperator *op)
|
|
{
|
|
SpaceNode *snode = CTX_wm_space_node(C);
|
|
int in_out = -1;
|
|
char name[MAX_NAME] = "";
|
|
int type = SOCK_FLOAT;
|
|
bNodeTree *ngroup = snode->edittree;
|
|
/* bNodeSocket *sock; */ /* UNUSED */
|
|
|
|
ED_preview_kill_jobs(C);
|
|
|
|
if (RNA_struct_property_is_set(op->ptr, "name"))
|
|
RNA_string_get(op->ptr, "name", name);
|
|
|
|
if (RNA_struct_property_is_set(op->ptr, "type"))
|
|
type = RNA_enum_get(op->ptr, "type");
|
|
|
|
if (RNA_struct_property_is_set(op->ptr, "in_out"))
|
|
in_out = RNA_enum_get(op->ptr, "in_out");
|
|
else
|
|
return OPERATOR_CANCELLED;
|
|
|
|
/* using placeholder subtype first */
|
|
/* sock = */ /* UNUSED */ node_group_add_socket(ngroup, name, type, in_out);
|
|
|
|
ntreeUpdateTree(ngroup);
|
|
|
|
snode_notify(C, snode);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void NODE_OT_group_socket_add(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Add Group Socket";
|
|
ot->description = "Add node group socket";
|
|
ot->idname = "NODE_OT_group_socket_add";
|
|
|
|
/* api callbacks */
|
|
ot->exec = node_group_socket_add_exec;
|
|
ot->poll = ED_operator_node_active;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
RNA_def_enum(ot->srna, "in_out", socket_in_out_items, SOCK_IN, "Socket Type", "Input or Output");
|
|
RNA_def_string(ot->srna, "name", "", MAX_NAME, "Name", "Group socket name");
|
|
RNA_def_enum(ot->srna, "type", node_socket_type_items, SOCK_FLOAT, "Type", "Type of the group socket");
|
|
}
|
|
|
|
/* ***************** Remove Group Socket operator ************* */
|
|
|
|
static int node_group_socket_remove_exec(bContext *C, wmOperator *op)
|
|
{
|
|
SpaceNode *snode = CTX_wm_space_node(C);
|
|
int index = -1;
|
|
int in_out = -1;
|
|
bNodeTree *ngroup = snode->edittree;
|
|
bNodeSocket *sock;
|
|
|
|
ED_preview_kill_jobs(C);
|
|
|
|
if (RNA_struct_property_is_set(op->ptr, "index"))
|
|
index = RNA_int_get(op->ptr, "index");
|
|
else
|
|
return OPERATOR_CANCELLED;
|
|
|
|
if (RNA_struct_property_is_set(op->ptr, "in_out"))
|
|
in_out = RNA_enum_get(op->ptr, "in_out");
|
|
else
|
|
return OPERATOR_CANCELLED;
|
|
|
|
sock = (bNodeSocket *)BLI_findlink(in_out == SOCK_IN ? &ngroup->inputs : &ngroup->outputs, index);
|
|
if (sock) {
|
|
node_group_remove_socket(ngroup, sock, in_out);
|
|
ntreeUpdateTree(ngroup);
|
|
|
|
snode_notify(C, snode);
|
|
}
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void NODE_OT_group_socket_remove(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Remove Group Socket";
|
|
ot->description = "Remove a node group socket";
|
|
ot->idname = "NODE_OT_group_socket_remove";
|
|
|
|
/* api callbacks */
|
|
ot->exec = node_group_socket_remove_exec;
|
|
ot->poll = ED_operator_node_active;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
RNA_def_int(ot->srna, "index", 0, 0, INT_MAX, "Index", "", 0, INT_MAX);
|
|
RNA_def_enum(ot->srna, "in_out", socket_in_out_items, SOCK_IN, "Socket Type", "Input or Output");
|
|
}
|
|
|
|
/* ***************** Move Group Socket Up operator ************* */
|
|
|
|
static int node_group_socket_move_up_exec(bContext *C, wmOperator *op)
|
|
{
|
|
SpaceNode *snode = CTX_wm_space_node(C);
|
|
int index = -1;
|
|
int in_out = -1;
|
|
bNodeTree *ngroup = snode->edittree;
|
|
bNodeSocket *sock, *prev;
|
|
|
|
ED_preview_kill_jobs(C);
|
|
|
|
if (RNA_struct_property_is_set(op->ptr, "index"))
|
|
index = RNA_int_get(op->ptr, "index");
|
|
else
|
|
return OPERATOR_CANCELLED;
|
|
|
|
if (RNA_struct_property_is_set(op->ptr, "in_out"))
|
|
in_out = RNA_enum_get(op->ptr, "in_out");
|
|
else
|
|
return OPERATOR_CANCELLED;
|
|
|
|
/* swap */
|
|
if (in_out == SOCK_IN) {
|
|
sock = (bNodeSocket *)BLI_findlink(&ngroup->inputs, index);
|
|
prev = sock->prev;
|
|
/* can't move up the first socket */
|
|
if (!prev)
|
|
return OPERATOR_CANCELLED;
|
|
BLI_remlink(&ngroup->inputs, sock);
|
|
BLI_insertlinkbefore(&ngroup->inputs, prev, sock);
|
|
|
|
ngroup->update |= NTREE_UPDATE_GROUP_IN;
|
|
}
|
|
else if (in_out == SOCK_OUT) {
|
|
sock = (bNodeSocket *)BLI_findlink(&ngroup->outputs, index);
|
|
prev = sock->prev;
|
|
/* can't move up the first socket */
|
|
if (!prev)
|
|
return OPERATOR_CANCELLED;
|
|
BLI_remlink(&ngroup->outputs, sock);
|
|
BLI_insertlinkbefore(&ngroup->outputs, prev, sock);
|
|
|
|
ngroup->update |= NTREE_UPDATE_GROUP_OUT;
|
|
}
|
|
ntreeUpdateTree(ngroup);
|
|
|
|
snode_notify(C, snode);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void NODE_OT_group_socket_move_up(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Move Group Socket Up";
|
|
ot->description = "Move up node group socket";
|
|
ot->idname = "NODE_OT_group_socket_move_up";
|
|
|
|
/* api callbacks */
|
|
ot->exec = node_group_socket_move_up_exec;
|
|
ot->poll = ED_operator_node_active;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
RNA_def_int(ot->srna, "index", 0, 0, INT_MAX, "Index", "", 0, INT_MAX);
|
|
RNA_def_enum(ot->srna, "in_out", socket_in_out_items, SOCK_IN, "Socket Type", "Input or Output");
|
|
}
|
|
|
|
/* ***************** Move Group Socket Up operator ************* */
|
|
|
|
static int node_group_socket_move_down_exec(bContext *C, wmOperator *op)
|
|
{
|
|
SpaceNode *snode = CTX_wm_space_node(C);
|
|
int index = -1;
|
|
int in_out = -1;
|
|
bNodeTree *ngroup = snode->edittree;
|
|
bNodeSocket *sock, *next;
|
|
|
|
ED_preview_kill_jobs(C);
|
|
|
|
if (RNA_struct_property_is_set(op->ptr, "index"))
|
|
index = RNA_int_get(op->ptr, "index");
|
|
else
|
|
return OPERATOR_CANCELLED;
|
|
|
|
if (RNA_struct_property_is_set(op->ptr, "in_out"))
|
|
in_out = RNA_enum_get(op->ptr, "in_out");
|
|
else
|
|
return OPERATOR_CANCELLED;
|
|
|
|
/* swap */
|
|
if (in_out == SOCK_IN) {
|
|
sock = (bNodeSocket *)BLI_findlink(&ngroup->inputs, index);
|
|
next = sock->next;
|
|
/* can't move down the last socket */
|
|
if (!next)
|
|
return OPERATOR_CANCELLED;
|
|
BLI_remlink(&ngroup->inputs, sock);
|
|
BLI_insertlinkafter(&ngroup->inputs, next, sock);
|
|
|
|
ngroup->update |= NTREE_UPDATE_GROUP_IN;
|
|
}
|
|
else if (in_out == SOCK_OUT) {
|
|
sock = (bNodeSocket *)BLI_findlink(&ngroup->outputs, index);
|
|
next = sock->next;
|
|
/* can't move down the last socket */
|
|
if (!next)
|
|
return OPERATOR_CANCELLED;
|
|
BLI_remlink(&ngroup->outputs, sock);
|
|
BLI_insertlinkafter(&ngroup->outputs, next, sock);
|
|
|
|
ngroup->update |= NTREE_UPDATE_GROUP_OUT;
|
|
}
|
|
ntreeUpdateTree(ngroup);
|
|
|
|
snode_notify(C, snode);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void NODE_OT_group_socket_move_down(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Move Group Socket Down";
|
|
ot->description = "Move down node group socket";
|
|
ot->idname = "NODE_OT_group_socket_move_down";
|
|
|
|
/* api callbacks */
|
|
ot->exec = node_group_socket_move_down_exec;
|
|
ot->poll = ED_operator_node_active;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
RNA_def_int(ot->srna, "index", 0, 0, INT_MAX, "Index", "", 0, INT_MAX);
|
|
RNA_def_enum(ot->srna, "in_out", socket_in_out_items, SOCK_IN, "Socket Type", "Input or Output");
|
|
}
|
|
|
|
/* ******************** Ungroup operator ********************** */
|
|
|
|
/* returns 1 if its OK */
|
|
static 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
|
|
* - temp copy. don't change ID usercount
|
|
*/
|
|
wgroup = ntreeCopyTree_ex(ngroup, FALSE);
|
|
|
|
/* 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 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 nodee tree */
|
|
nodeUniqueName(ntree, 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 */
|
|
node_socket_convert_default_value(link->tosock->type, link->tosock->default_value, gsock->type, gsock->default_value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* 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 = linkn) {
|
|
linkn = 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 */
|
|
node_socket_convert_default_value(link->tosock->type, link->tosock->default_value, insock->type, insock->default_value);
|
|
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,
|
|
* 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(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 */
|
|
if (waction) {
|
|
BKE_libblock_free(&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) */
|
|
BKE_libblock_free(&G.main->nodetree, wgroup);
|
|
|
|
ntree->update |= NTREE_UPDATE_NODES | NTREE_UPDATE_LINKS;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int node_group_ungroup_exec(bContext *C, wmOperator *op)
|
|
{
|
|
SpaceNode *snode = CTX_wm_space_node(C);
|
|
bNode *gnode;
|
|
|
|
ED_preview_kill_jobs(C);
|
|
|
|
/* are we inside of a group? */
|
|
gnode = node_tree_get_editgroup(snode->nodetree);
|
|
if (gnode)
|
|
snode_make_group_editable(snode, NULL);
|
|
|
|
gnode = nodeGetActive(snode->edittree);
|
|
if (gnode == NULL)
|
|
return OPERATOR_CANCELLED;
|
|
|
|
if (gnode->type != NODE_GROUP) {
|
|
BKE_report(op->reports, RPT_WARNING, "Not a group");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
else if (node_group_ungroup(snode->nodetree, gnode)) {
|
|
ntreeUpdateTree(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";
|
|
|
|
/* api callbacks */
|
|
ot->exec = node_group_ungroup_exec;
|
|
ot->poll = ED_operator_node_active;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
/* ******************** Separate operator ********************** */
|
|
|
|
/* returns 1 if its OK */
|
|
static int node_group_separate_selected(bNodeTree *ntree, bNode *gnode, int make_copy)
|
|
{
|
|
bNodeLink *link, *link_next;
|
|
bNode *node, *node_next, *newnode;
|
|
bNodeTree *ngroup;
|
|
ListBase anim_basepaths = {NULL, NULL};
|
|
|
|
ngroup = (bNodeTree *)gnode->id;
|
|
if (ngroup == NULL) return 0;
|
|
|
|
/* deselect all nodes in the target tree */
|
|
for (node = ntree->nodes.first; node; node = node->next)
|
|
node_deselect(node);
|
|
|
|
/* 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) {
|
|
|
|
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);
|
|
|
|
newnode->locx += gnode->locx;
|
|
newnode->locy += gnode->locy;
|
|
}
|
|
else {
|
|
/* ensure valid parent pointers, detach if child stays inside the group */
|
|
if (node->parent && (node->parent->flag & NODE_SELECT))
|
|
nodeDetachNode(node);
|
|
}
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
typedef enum eNodeGroupSeparateType {
|
|
NODE_GS_COPY,
|
|
NODE_GS_MOVE
|
|
} eNodeGroupSeparateType;
|
|
|
|
/* Operator Property */
|
|
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}
|
|
};
|
|
|
|
static int node_group_separate_exec(bContext *C, wmOperator *op)
|
|
{
|
|
SpaceNode *snode = CTX_wm_space_node(C);
|
|
bNode *gnode;
|
|
int type = RNA_enum_get(op->ptr, "type");
|
|
|
|
ED_preview_kill_jobs(C);
|
|
|
|
/* are we inside of a group? */
|
|
gnode = node_tree_get_editgroup(snode->nodetree);
|
|
if (!gnode) {
|
|
BKE_report(op->reports, RPT_WARNING, "Not inside node group");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
switch (type) {
|
|
case NODE_GS_COPY:
|
|
if (!node_group_separate_selected(snode->nodetree, gnode, 1)) {
|
|
BKE_report(op->reports, RPT_WARNING, "Cannot separate nodes");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
break;
|
|
case NODE_GS_MOVE:
|
|
if (!node_group_separate_selected(snode->nodetree, gnode, 0)) {
|
|
BKE_report(op->reports, RPT_WARNING, "Cannot separate nodes");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* switch to parent tree */
|
|
snode_make_group_editable(snode, NULL);
|
|
|
|
ntreeUpdateTree(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), wmEvent *UNUSED(event))
|
|
{
|
|
uiPopupMenu *pup = uiPupMenuBegin(C, "Separate", ICON_NONE);
|
|
uiLayout *layout = uiPupMenuLayout(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);
|
|
|
|
uiPupMenuEnd(C, pup);
|
|
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
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";
|
|
|
|
/* api callbacks */
|
|
ot->invoke = node_group_separate_invoke;
|
|
ot->exec = node_group_separate_exec;
|
|
ot->poll = ED_operator_node_active;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
RNA_def_enum(ot->srna, "type", node_group_separate_types, NODE_GS_COPY, "Type", "");
|
|
}
|
|
|
|
/* ****************** Make Group operator ******************* */
|
|
|
|
static int node_group_make_test(bNodeTree *ntree, bNode *gnode)
|
|
{
|
|
bNode *node;
|
|
bNodeLink *link;
|
|
int totnode = 0;
|
|
|
|
/* is there something to group? also do some clearing */
|
|
for (node = ntree->nodes.first; node; node = node->next) {
|
|
if (node == gnode)
|
|
continue;
|
|
|
|
if (node->flag & NODE_SELECT) {
|
|
/* no groups in groups */
|
|
if (node->type == NODE_GROUP)
|
|
return 0;
|
|
totnode++;
|
|
}
|
|
|
|
node->done = 0;
|
|
}
|
|
if (totnode == 0) return 0;
|
|
|
|
/* 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 (link->fromnode && link->tonode && link->fromnode->flag & NODE_SELECT && link->fromnode != gnode)
|
|
link->tonode->done |= 1;
|
|
if (link->fromnode && link->tonode && link->tonode->flag & NODE_SELECT && link->tonode != gnode)
|
|
link->fromnode->done |= 2;
|
|
}
|
|
|
|
for (node = ntree->nodes.first; node; node = node->next) {
|
|
if (node == gnode)
|
|
continue;
|
|
if ((node->flag & NODE_SELECT) == 0)
|
|
if (node->done == 3)
|
|
break;
|
|
}
|
|
if (node)
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
static void node_get_selected_minmax(bNodeTree *ntree, bNode *gnode, float *min, float *max)
|
|
{
|
|
bNode *node;
|
|
INIT_MINMAX2(min, max);
|
|
for (node = ntree->nodes.first; node; node = node->next) {
|
|
if (node == gnode)
|
|
continue;
|
|
if (node->flag & NODE_SELECT) {
|
|
DO_MINMAX2((&node->locx), min, max);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int node_group_make_insert_selected(bNodeTree *ntree, bNode *gnode)
|
|
{
|
|
bNodeTree *ngroup = (bNodeTree *)gnode->id;
|
|
bNodeLink *link, *linkn;
|
|
bNode *node, *nextn;
|
|
bNodeSocket *gsock;
|
|
ListBase anim_basepaths = {NULL, NULL};
|
|
float min[2], max[2];
|
|
|
|
/* deselect all nodes in the target tree */
|
|
for (node = ngroup->nodes.first; node; node = node->next)
|
|
node_deselect(node);
|
|
|
|
node_get_selected_minmax(ntree, gnode, min, max);
|
|
|
|
/* move nodes over */
|
|
for (node = ntree->nodes.first; node; node = nextn) {
|
|
nextn = node->next;
|
|
if (node == gnode)
|
|
continue;
|
|
if (node->flag & NODE_SELECT) {
|
|
/* 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);
|
|
|
|
node->locx -= 0.5f * (min[0] + max[0]);
|
|
node->locy -= 0.5f * (min[1] + max[1]);
|
|
}
|
|
else {
|
|
/* if the parent is to be inserted but not the child, detach properly */
|
|
if (node->parent && (node->parent->flag & NODE_SELECT))
|
|
nodeDetachNode(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);
|
|
|
|
/* relink external sockets */
|
|
for (link = ntree->links.first; link; link = linkn) {
|
|
int fromselect = (link->fromnode && (link->fromnode->flag & NODE_SELECT) && link->fromnode != gnode);
|
|
int toselect = (link->tonode && (link->tonode->flag & NODE_SELECT) && 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) {
|
|
gsock = node_group_expose_socket(ngroup, link->tosock, SOCK_IN);
|
|
link->tosock->link = nodeAddLink(ngroup, NULL, gsock, link->tonode, link->tosock);
|
|
link->tosock = node_group_add_extern_socket(ntree, &gnode->inputs, SOCK_IN, gsock);
|
|
link->tonode = gnode;
|
|
}
|
|
else if (fromselect) {
|
|
/* search for existing group node socket */
|
|
for (gsock = ngroup->outputs.first; gsock; gsock = gsock->next)
|
|
if (gsock->link && gsock->link->fromsock == link->fromsock)
|
|
break;
|
|
if (!gsock) {
|
|
gsock = node_group_expose_socket(ngroup, link->fromsock, SOCK_OUT);
|
|
gsock->link = nodeAddLink(ngroup, link->fromnode, link->fromsock, NULL, gsock);
|
|
link->fromsock = node_group_add_extern_socket(ntree, &gnode->outputs, SOCK_OUT, gsock);
|
|
}
|
|
else
|
|
link->fromsock = node_group_find_output(gnode, gsock);
|
|
link->fromnode = gnode;
|
|
}
|
|
}
|
|
|
|
/* update of the group tree */
|
|
ngroup->update |= NTREE_UPDATE;
|
|
/* update of the tree containing the group instance node */
|
|
ntree->update |= NTREE_UPDATE_NODES | NTREE_UPDATE_LINKS;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static bNode *node_group_make_from_selected(bNodeTree *ntree)
|
|
{
|
|
bNode *gnode;
|
|
bNodeTree *ngroup;
|
|
float min[2], max[2];
|
|
bNodeTemplate ntemp;
|
|
|
|
node_get_selected_minmax(ntree, NULL, min, max);
|
|
|
|
/* new nodetree */
|
|
ngroup = ntreeAddTree("NodeGroup", ntree->type, NODE_GROUP);
|
|
|
|
/* make group node */
|
|
ntemp.type = NODE_GROUP;
|
|
ntemp.ngroup = ngroup;
|
|
gnode = nodeAddNode(ntree, &ntemp);
|
|
gnode->locx = 0.5f * (min[0] + max[0]);
|
|
gnode->locy = 0.5f * (min[1] + max[1]);
|
|
|
|
node_group_make_insert_selected(ntree, gnode);
|
|
|
|
/* update of the tree containing the group instance node */
|
|
ntree->update |= NTREE_UPDATE_NODES;
|
|
|
|
return gnode;
|
|
}
|
|
|
|
typedef enum eNodeGroupMakeType {
|
|
NODE_GM_NEW,
|
|
NODE_GM_INSERT
|
|
} eNodeGroupMakeType;
|
|
|
|
/* Operator Property */
|
|
EnumPropertyItem node_group_make_types[] = {
|
|
{NODE_GM_NEW, "NEW", 0, "New", "Create a new node group from selected nodes"},
|
|
{NODE_GM_INSERT, "INSERT", 0, "Insert", "Insert into active node group"},
|
|
{0, NULL, 0, NULL, NULL}
|
|
};
|
|
|
|
static int node_group_make_exec(bContext *C, wmOperator *op)
|
|
{
|
|
SpaceNode *snode = CTX_wm_space_node(C);
|
|
bNode *gnode;
|
|
int type = RNA_enum_get(op->ptr, "type");
|
|
|
|
if (snode->edittree != snode->nodetree) {
|
|
BKE_report(op->reports, RPT_WARNING, "Cannot add a new group in a group");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
/* for time being... is too complex to handle */
|
|
if (snode->treetype == NTREE_COMPOSIT) {
|
|
for (gnode = snode->nodetree->nodes.first; gnode; gnode = gnode->next) {
|
|
if (gnode->flag & SELECT)
|
|
if (gnode->type == CMP_NODE_R_LAYERS)
|
|
break;
|
|
}
|
|
|
|
if (gnode) {
|
|
BKE_report(op->reports, RPT_WARNING, "Cannot add a Render Layers node in a group");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
}
|
|
|
|
ED_preview_kill_jobs(C);
|
|
|
|
switch (type) {
|
|
case NODE_GM_NEW:
|
|
if (node_group_make_test(snode->nodetree, NULL)) {
|
|
gnode = node_group_make_from_selected(snode->nodetree);
|
|
}
|
|
else {
|
|
BKE_report(op->reports, RPT_WARNING, "Cannot make group");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
break;
|
|
case NODE_GM_INSERT:
|
|
gnode = nodeGetActive(snode->nodetree);
|
|
if (!gnode || gnode->type != NODE_GROUP) {
|
|
BKE_report(op->reports, RPT_WARNING, "No active group node");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
if (node_group_make_test(snode->nodetree, gnode)) {
|
|
node_group_make_insert_selected(snode->nodetree, gnode);
|
|
}
|
|
else {
|
|
BKE_report(op->reports, RPT_WARNING, "Cannot insert into group");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (gnode) {
|
|
nodeSetActive(snode->nodetree, gnode);
|
|
snode_make_group_editable(snode, gnode);
|
|
}
|
|
|
|
if (gnode)
|
|
ntreeUpdateTree((bNodeTree *)gnode->id);
|
|
ntreeUpdateTree(snode->nodetree);
|
|
|
|
snode_notify(C, snode);
|
|
snode_dag_update(C, snode);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
static int node_group_make_invoke(bContext *C, wmOperator *UNUSED(op), wmEvent *UNUSED(event))
|
|
{
|
|
SpaceNode *snode = CTX_wm_space_node(C);
|
|
bNode *act = nodeGetActive(snode->edittree);
|
|
uiPopupMenu *pup = uiPupMenuBegin(C, "Make Group", ICON_NONE);
|
|
uiLayout *layout = uiPupMenuLayout(pup);
|
|
|
|
uiLayoutSetOperatorContext(layout, WM_OP_EXEC_DEFAULT);
|
|
uiItemEnumO(layout, "NODE_OT_group_make", NULL, 0, "type", NODE_GM_NEW);
|
|
|
|
/* if active node is a group, add insert option */
|
|
if (act && act->type == NODE_GROUP) {
|
|
uiItemEnumO(layout, "NODE_OT_group_make", NULL, 0, "type", NODE_GM_INSERT);
|
|
}
|
|
|
|
uiPupMenuEnd(C, pup);
|
|
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
void NODE_OT_group_make(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Group";
|
|
ot->description = "Make group from selected nodes";
|
|
ot->idname = "NODE_OT_group_make";
|
|
|
|
/* api callbacks */
|
|
ot->invoke = node_group_make_invoke;
|
|
ot->exec = node_group_make_exec;
|
|
ot->poll = ED_operator_node_active;
|
|
|
|
/* flags */
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
RNA_def_enum(ot->srna, "type", node_group_make_types, NODE_GM_NEW, "Type", "");
|
|
}
|