2012-08-01 19:11:17 +00:00
|
|
|
/*
|
|
|
|
* ***** 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_relationships.c
|
|
|
|
* \ingroup spnode
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
|
2018-01-16 12:06:22 +01:00
|
|
|
#include "DNA_anim_types.h"
|
2012-08-01 19:11:17 +00:00
|
|
|
#include "DNA_node_types.h"
|
|
|
|
|
|
|
|
#include "BLI_math.h"
|
|
|
|
#include "BLI_blenlib.h"
|
2015-08-01 17:39:48 +02:00
|
|
|
#include "BLI_easing.h"
|
2012-08-01 19:11:17 +00:00
|
|
|
|
2018-01-16 12:06:22 +01:00
|
|
|
#include "BKE_animsys.h"
|
2012-08-01 19:11:17 +00:00
|
|
|
#include "BKE_context.h"
|
2013-05-07 15:28:42 +00:00
|
|
|
#include "BKE_global.h"
|
2016-04-06 15:23:26 +02:00
|
|
|
#include "BKE_library.h"
|
|
|
|
#include "BKE_main.h"
|
2012-08-01 19:11:17 +00:00
|
|
|
#include "BKE_node.h"
|
|
|
|
|
2012-08-01 20:39:14 +00:00
|
|
|
#include "ED_node.h" /* own include */
|
2012-08-01 19:11:17 +00:00
|
|
|
#include "ED_screen.h"
|
|
|
|
#include "ED_render.h"
|
2015-08-01 17:39:48 +02:00
|
|
|
#include "ED_util.h"
|
2012-08-01 19:11:17 +00:00
|
|
|
|
|
|
|
#include "RNA_access.h"
|
|
|
|
#include "RNA_define.h"
|
|
|
|
|
|
|
|
#include "WM_api.h"
|
|
|
|
#include "WM_types.h"
|
|
|
|
|
|
|
|
#include "UI_view2d.h"
|
2015-08-01 17:39:48 +02:00
|
|
|
#include "UI_resources.h"
|
2012-08-01 19:11:17 +00:00
|
|
|
|
2015-08-16 17:32:01 +10:00
|
|
|
#include "BLT_translation.h"
|
2014-04-29 11:28:16 +02:00
|
|
|
|
2012-08-01 20:39:14 +00:00
|
|
|
#include "node_intern.h" /* own include */
|
2012-08-01 19:11:17 +00:00
|
|
|
|
2016-02-05 01:39:42 +05:00
|
|
|
/* ****************** Relations helpers *********************** */
|
|
|
|
|
2018-01-16 12:06:22 +01:00
|
|
|
static bool ntree_has_drivers(bNodeTree *ntree)
|
|
|
|
{
|
|
|
|
AnimData *adt = BKE_animdata_from_id(&ntree->id);
|
|
|
|
if (adt == NULL) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return !BLI_listbase_is_empty(&adt->drivers);
|
|
|
|
}
|
|
|
|
|
2016-02-05 01:39:42 +05:00
|
|
|
static bool ntree_check_nodes_connected_dfs(bNodeTree *ntree,
|
|
|
|
bNode *from,
|
|
|
|
bNode *to)
|
|
|
|
{
|
|
|
|
if (from->flag & NODE_TEST) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
from->flag |= NODE_TEST;
|
|
|
|
for (bNodeLink *link = ntree->links.first; link != NULL; link = link->next) {
|
|
|
|
if (link->fromnode == from) {
|
|
|
|
if (link->tonode == to) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (ntree_check_nodes_connected_dfs(ntree, link->tonode, to)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool ntree_check_nodes_connected(bNodeTree *ntree, bNode *from, bNode *to)
|
|
|
|
{
|
|
|
|
if (from == to) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
ntreeNodeFlagSet(ntree, NODE_TEST, false);
|
|
|
|
return ntree_check_nodes_connected_dfs(ntree, from, to);
|
|
|
|
}
|
|
|
|
|
2016-03-23 15:41:36 +01:00
|
|
|
static bool node_group_has_output_dfs(bNode *node)
|
|
|
|
{
|
2016-04-06 15:23:26 +02:00
|
|
|
bNodeTree *ntree = (bNodeTree *)node->id;
|
|
|
|
if (ntree->id.tag & LIB_TAG_DOIT) {
|
2016-03-23 15:41:36 +01:00
|
|
|
return false;
|
|
|
|
}
|
2016-04-06 15:23:26 +02:00
|
|
|
ntree->id.tag |= LIB_TAG_DOIT;
|
2016-03-23 15:41:36 +01:00
|
|
|
for (bNode *current_node = ntree->nodes.first;
|
|
|
|
current_node != NULL;
|
|
|
|
current_node = current_node->next)
|
|
|
|
{
|
|
|
|
if (current_node->type == NODE_GROUP) {
|
2016-04-29 17:37:00 +02:00
|
|
|
if (current_node->id && node_group_has_output_dfs(current_node)) {
|
2016-03-23 15:41:36 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (current_node->flag & NODE_DO_OUTPUT &&
|
|
|
|
current_node->type != NODE_GROUP_OUTPUT)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool node_group_has_output(bNode *node)
|
|
|
|
{
|
2016-04-06 15:23:26 +02:00
|
|
|
Main *bmain = G.main;
|
2016-03-23 15:41:36 +01:00
|
|
|
BLI_assert(node->type == NODE_GROUP);
|
|
|
|
bNodeTree *ntree = (bNodeTree *)node->id;
|
|
|
|
if (ntree == NULL) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-04-06 15:23:26 +02:00
|
|
|
BKE_main_id_tag_listbase(&bmain->nodetree, LIB_TAG_DOIT, false);
|
2016-03-23 15:41:36 +01:00
|
|
|
return node_group_has_output_dfs(node);
|
|
|
|
}
|
|
|
|
|
2016-02-05 01:39:42 +05:00
|
|
|
bool node_connected_to_output(bNodeTree *ntree, bNode *node)
|
|
|
|
{
|
2018-01-16 12:06:22 +01:00
|
|
|
/* Special case for drivers: if node tree has any drivers we assume it is
|
|
|
|
* always to be tagged for update when node changes. Otherwise we will be
|
|
|
|
* doomed to do some deep and nasty deep search of indirect dependencies,
|
|
|
|
* which will be too complicated without real benefit.
|
|
|
|
*/
|
|
|
|
if (ntree_has_drivers(ntree)) {
|
|
|
|
return true;
|
|
|
|
}
|
2016-02-05 01:39:42 +05:00
|
|
|
for (bNode *current_node = ntree->nodes.first;
|
|
|
|
current_node != NULL;
|
|
|
|
current_node = current_node->next)
|
|
|
|
{
|
2016-03-23 15:41:36 +01:00
|
|
|
/* Special case for group nodes -- if modified node connected to a group
|
|
|
|
* with active output inside we consider refresh is needed.
|
|
|
|
*
|
|
|
|
* We could make check more grained here by taking which socket the node
|
|
|
|
* is connected to and so eventually.
|
|
|
|
*/
|
2018-01-16 12:06:22 +01:00
|
|
|
if (current_node->type == NODE_GROUP) {
|
|
|
|
if (current_node->id != NULL &&
|
|
|
|
ntree_has_drivers((bNodeTree *)current_node->id))
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (ntree_check_nodes_connected(ntree, node, current_node) &&
|
|
|
|
node_group_has_output(current_node))
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
2016-03-23 15:41:36 +01:00
|
|
|
}
|
2016-02-05 01:39:42 +05:00
|
|
|
if (current_node->flag & NODE_DO_OUTPUT) {
|
|
|
|
if (ntree_check_nodes_connected(ntree, node, current_node)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2012-08-01 19:11:17 +00:00
|
|
|
/* ****************** Add *********************** */
|
|
|
|
|
|
|
|
|
|
|
|
typedef struct bNodeListItem {
|
|
|
|
struct bNodeListItem *next, *prev;
|
|
|
|
struct bNode *node;
|
|
|
|
} bNodeListItem;
|
|
|
|
|
2015-08-01 17:39:48 +02:00
|
|
|
typedef struct NodeInsertOfsData {
|
|
|
|
bNodeTree *ntree;
|
|
|
|
bNode *insert; /* inserted node */
|
|
|
|
bNode *prev, *next; /* prev/next node in the chain */
|
|
|
|
bNode *insert_parent;
|
|
|
|
|
|
|
|
wmTimer *anim_timer;
|
|
|
|
|
|
|
|
float offset_x; /* offset to apply to node chain */
|
|
|
|
} NodeInsertOfsData;
|
|
|
|
|
2014-09-23 01:28:46 +10:00
|
|
|
static int sort_nodes_locx(const void *a, const void *b)
|
2012-08-01 19:11:17 +00:00
|
|
|
{
|
2014-09-23 01:28:46 +10:00
|
|
|
const bNodeListItem *nli1 = a;
|
|
|
|
const bNodeListItem *nli2 = b;
|
|
|
|
const bNode *node1 = nli1->node;
|
|
|
|
const bNode *node2 = nli2->node;
|
2012-08-01 19:11:17 +00:00
|
|
|
|
|
|
|
if (node1->locx > node2->locx)
|
|
|
|
return 1;
|
|
|
|
else
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-02-05 22:36:15 +11:00
|
|
|
static bool socket_is_available(bNodeTree *UNUSED(ntree), bNodeSocket *sock, const bool allow_used)
|
2012-08-01 19:11:17 +00:00
|
|
|
{
|
|
|
|
if (nodeSocketIsHidden(sock))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (!allow_used && (sock->flag & SOCK_IN_USE))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2014-02-03 18:55:59 +11:00
|
|
|
static bNodeSocket *best_socket_output(bNodeTree *ntree, bNode *node, bNodeSocket *sock_target, const bool allow_multiple)
|
2012-08-01 19:11:17 +00:00
|
|
|
{
|
|
|
|
bNodeSocket *sock;
|
|
|
|
|
|
|
|
/* first look for selected output */
|
|
|
|
for (sock = node->outputs.first; sock; sock = sock->next) {
|
|
|
|
if (!socket_is_available(ntree, sock, allow_multiple))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (sock->flag & SELECT)
|
|
|
|
return sock;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* try to find a socket with a matching name */
|
|
|
|
for (sock = node->outputs.first; sock; sock = sock->next) {
|
|
|
|
if (!socket_is_available(ntree, sock, allow_multiple))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* check for same types */
|
|
|
|
if (sock->type == sock_target->type) {
|
2013-03-18 18:25:05 +00:00
|
|
|
if (STREQ(sock->name, sock_target->name))
|
2012-08-01 19:11:17 +00:00
|
|
|
return sock;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* otherwise settle for the first available socket of the right type */
|
|
|
|
for (sock = node->outputs.first; sock; sock = sock->next) {
|
|
|
|
|
|
|
|
if (!socket_is_available(ntree, sock, allow_multiple))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* check for same types */
|
|
|
|
if (sock->type == sock_target->type) {
|
|
|
|
return sock;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* this is a bit complicated, but designed to prioritize finding
|
|
|
|
* sockets of higher types, such as image, first */
|
|
|
|
static bNodeSocket *best_socket_input(bNodeTree *ntree, bNode *node, int num, int replace)
|
|
|
|
{
|
|
|
|
bNodeSocket *sock;
|
|
|
|
int socktype, maxtype = 0;
|
|
|
|
int a = 0;
|
|
|
|
|
|
|
|
for (sock = node->inputs.first; sock; sock = sock->next) {
|
2012-10-27 11:18:54 +00:00
|
|
|
maxtype = max_ii(sock->type, maxtype);
|
2012-08-01 19:11:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* find sockets of higher 'types' first (i.e. image) */
|
|
|
|
for (socktype = maxtype; socktype >= 0; socktype--) {
|
|
|
|
for (sock = node->inputs.first; sock; sock = sock->next) {
|
|
|
|
|
|
|
|
if (!socket_is_available(ntree, sock, replace)) {
|
|
|
|
a++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sock->type == socktype) {
|
|
|
|
/* increment to make sure we don't keep finding
|
|
|
|
* the same socket on every attempt running this function */
|
|
|
|
a++;
|
|
|
|
if (a > num)
|
|
|
|
return sock;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2016-08-13 16:31:46 +02:00
|
|
|
static bool snode_autoconnect_input(SpaceNode *snode, bNode *node_fr, bNodeSocket *sock_fr, bNode *node_to, bNodeSocket *sock_to, int replace)
|
2012-08-01 19:11:17 +00:00
|
|
|
{
|
|
|
|
bNodeTree *ntree = snode->edittree;
|
|
|
|
|
|
|
|
/* then we can connect */
|
|
|
|
if (replace)
|
|
|
|
nodeRemSocketLinks(ntree, sock_to);
|
|
|
|
|
2016-08-13 16:31:46 +02:00
|
|
|
nodeAddLink(ntree, node_fr, sock_fr, node_to, sock_to);
|
|
|
|
return true;
|
2012-08-01 19:11:17 +00:00
|
|
|
}
|
|
|
|
|
2014-02-03 18:55:59 +11:00
|
|
|
static void snode_autoconnect(SpaceNode *snode, const bool allow_multiple, const bool replace)
|
2012-08-01 19:11:17 +00:00
|
|
|
{
|
|
|
|
bNodeTree *ntree = snode->edittree;
|
|
|
|
ListBase *nodelist = MEM_callocN(sizeof(ListBase), "items_list");
|
|
|
|
bNodeListItem *nli;
|
|
|
|
bNode *node;
|
|
|
|
int i, numlinks = 0;
|
|
|
|
|
|
|
|
for (node = ntree->nodes.first; node; node = node->next) {
|
|
|
|
if (node->flag & NODE_SELECT) {
|
|
|
|
nli = MEM_mallocN(sizeof(bNodeListItem), "temporary node list item");
|
|
|
|
nli->node = node;
|
|
|
|
BLI_addtail(nodelist, nli);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* sort nodes left to right */
|
2014-11-16 13:57:58 +01:00
|
|
|
BLI_listbase_sort(nodelist, sort_nodes_locx);
|
2012-08-01 19:11:17 +00:00
|
|
|
|
|
|
|
for (nli = nodelist->first; nli; nli = nli->next) {
|
|
|
|
bNode *node_fr, *node_to;
|
|
|
|
bNodeSocket *sock_fr, *sock_to;
|
2014-04-11 11:25:41 +10:00
|
|
|
bool has_selected_inputs = false;
|
2012-08-01 19:11:17 +00:00
|
|
|
|
|
|
|
if (nli->next == NULL) break;
|
|
|
|
|
|
|
|
node_fr = nli->node;
|
|
|
|
node_to = nli->next->node;
|
2016-03-09 11:23:56 +01:00
|
|
|
/* corner case: input/output node aligned the wrong way around (T47729) */
|
|
|
|
if (BLI_listbase_is_empty(&node_to->inputs) || BLI_listbase_is_empty(&node_fr->outputs)) {
|
|
|
|
SWAP(bNode *, node_fr, node_to);
|
|
|
|
}
|
2012-08-01 19:11:17 +00:00
|
|
|
|
|
|
|
/* if there are selected sockets, connect those */
|
|
|
|
for (sock_to = node_to->inputs.first; sock_to; sock_to = sock_to->next) {
|
|
|
|
if (sock_to->flag & SELECT) {
|
|
|
|
has_selected_inputs = 1;
|
|
|
|
|
|
|
|
if (!socket_is_available(ntree, sock_to, replace))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* check for an appropriate output socket to connect from */
|
|
|
|
sock_fr = best_socket_output(ntree, node_fr, sock_to, allow_multiple);
|
|
|
|
if (!sock_fr)
|
|
|
|
continue;
|
|
|
|
|
2012-08-22 16:44:32 +00:00
|
|
|
if (snode_autoconnect_input(snode, node_fr, sock_fr, node_to, sock_to, replace)) {
|
|
|
|
numlinks++;
|
|
|
|
}
|
2012-08-01 19:11:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!has_selected_inputs) {
|
|
|
|
/* no selected inputs, connect by finding suitable match */
|
2014-11-16 13:57:58 +01:00
|
|
|
int num_inputs = BLI_listbase_count(&node_to->inputs);
|
2012-08-01 19:11:17 +00:00
|
|
|
|
|
|
|
for (i = 0; i < num_inputs; i++) {
|
|
|
|
|
|
|
|
/* find the best guess input socket */
|
|
|
|
sock_to = best_socket_input(ntree, node_to, i, replace);
|
|
|
|
if (!sock_to)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* check for an appropriate output socket to connect from */
|
|
|
|
sock_fr = best_socket_output(ntree, node_fr, sock_to, allow_multiple);
|
|
|
|
if (!sock_fr)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (snode_autoconnect_input(snode, node_fr, sock_fr, node_to, sock_to, replace)) {
|
2012-08-22 16:44:32 +00:00
|
|
|
numlinks++;
|
2012-08-01 19:11:17 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (numlinks > 0) {
|
2013-05-07 15:28:42 +00:00
|
|
|
ntreeUpdateTree(G.main, ntree);
|
2012-08-01 19:11:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
BLI_freelistN(nodelist);
|
|
|
|
MEM_freeN(nodelist);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* *************************** link viewer op ******************** */
|
|
|
|
|
|
|
|
static int node_link_viewer(const bContext *C, bNode *tonode)
|
|
|
|
{
|
|
|
|
SpaceNode *snode = CTX_wm_space_node(C);
|
|
|
|
bNode *node;
|
|
|
|
bNodeLink *link;
|
|
|
|
bNodeSocket *sock;
|
|
|
|
|
|
|
|
/* context check */
|
2014-02-08 06:07:10 +11:00
|
|
|
if (tonode == NULL || BLI_listbase_is_empty(&tonode->outputs))
|
2012-08-01 19:11:17 +00:00
|
|
|
return OPERATOR_CANCELLED;
|
|
|
|
if (ELEM(tonode->type, CMP_NODE_VIEWER, CMP_NODE_SPLITVIEWER))
|
|
|
|
return OPERATOR_CANCELLED;
|
|
|
|
|
|
|
|
/* get viewer */
|
|
|
|
for (node = snode->edittree->nodes.first; node; node = node->next)
|
|
|
|
if (ELEM(node->type, CMP_NODE_VIEWER, CMP_NODE_SPLITVIEWER))
|
|
|
|
if (node->flag & NODE_DO_OUTPUT)
|
|
|
|
break;
|
|
|
|
/* no viewer, we make one active */
|
|
|
|
if (node == NULL) {
|
|
|
|
for (node = snode->edittree->nodes.first; node; node = node->next) {
|
|
|
|
if (ELEM(node->type, CMP_NODE_VIEWER, CMP_NODE_SPLITVIEWER)) {
|
|
|
|
node->flag |= NODE_DO_OUTPUT;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sock = NULL;
|
|
|
|
|
|
|
|
/* try to find an already connected socket to cycle to the next */
|
|
|
|
if (node) {
|
|
|
|
link = NULL;
|
|
|
|
for (link = snode->edittree->links.first; link; link = link->next)
|
|
|
|
if (link->tonode == node && link->fromnode == tonode)
|
|
|
|
if (link->tosock == node->inputs.first)
|
|
|
|
break;
|
|
|
|
if (link) {
|
|
|
|
/* unlink existing connection */
|
|
|
|
sock = link->fromsock;
|
|
|
|
nodeRemLink(snode->edittree, link);
|
|
|
|
|
|
|
|
/* find a socket after the previously connected socket */
|
|
|
|
for (sock = sock->next; sock; sock = sock->next)
|
|
|
|
if (!nodeSocketIsHidden(sock))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* find a socket starting from the first socket */
|
|
|
|
if (!sock) {
|
|
|
|
for (sock = tonode->outputs.first; sock; sock = sock->next)
|
|
|
|
if (!nodeSocketIsHidden(sock))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sock) {
|
|
|
|
/* add a new viewer if none exists yet */
|
|
|
|
if (!node) {
|
|
|
|
/* XXX location is a quick hack, just place it next to the linked socket */
|
2013-03-18 16:34:57 +00:00
|
|
|
node = node_add_node(C, NULL, CMP_NODE_VIEWER, sock->locx + 100, sock->locy);
|
2012-08-01 19:11:17 +00:00
|
|
|
if (!node)
|
|
|
|
return OPERATOR_CANCELLED;
|
|
|
|
|
|
|
|
link = NULL;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
/* get link to viewer */
|
|
|
|
for (link = snode->edittree->links.first; link; link = link->next)
|
|
|
|
if (link->tonode == node && link->tosock == node->inputs.first)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (link == NULL) {
|
|
|
|
nodeAddLink(snode->edittree, tonode, sock, node, node->inputs.first);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
link->fromnode = tonode;
|
|
|
|
link->fromsock = sock;
|
|
|
|
/* make sure the dependency sorting is updated */
|
|
|
|
snode->edittree->update |= NTREE_UPDATE_LINKS;
|
|
|
|
}
|
2013-05-07 15:28:42 +00:00
|
|
|
ntreeUpdateTree(CTX_data_main(C), snode->edittree);
|
2012-08-01 19:11:17 +00:00
|
|
|
snode_update(snode, node);
|
|
|
|
}
|
|
|
|
|
|
|
|
return OPERATOR_FINISHED;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-07-28 17:06:31 +00:00
|
|
|
static int node_active_link_viewer_exec(bContext *C, wmOperator *UNUSED(op))
|
2012-08-01 19:11:17 +00:00
|
|
|
{
|
|
|
|
SpaceNode *snode = CTX_wm_space_node(C);
|
|
|
|
bNode *node;
|
|
|
|
|
2013-03-18 16:34:57 +00:00
|
|
|
node = nodeGetActive(snode->edittree);
|
2012-08-01 19:11:17 +00:00
|
|
|
|
|
|
|
if (!node)
|
|
|
|
return OPERATOR_CANCELLED;
|
|
|
|
|
2014-11-18 15:51:31 +01:00
|
|
|
ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C));
|
2012-08-01 19:11:17 +00:00
|
|
|
|
|
|
|
if (node_link_viewer(C, node) == OPERATOR_CANCELLED)
|
|
|
|
return OPERATOR_CANCELLED;
|
|
|
|
|
|
|
|
snode_notify(C, snode);
|
|
|
|
|
|
|
|
return OPERATOR_FINISHED;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void NODE_OT_link_viewer(wmOperatorType *ot)
|
|
|
|
{
|
|
|
|
/* identifiers */
|
|
|
|
ot->name = "Link to Viewer Node";
|
|
|
|
ot->description = "Link to viewer node";
|
|
|
|
ot->idname = "NODE_OT_link_viewer";
|
|
|
|
|
|
|
|
/* api callbacks */
|
2013-07-28 17:06:31 +00:00
|
|
|
ot->exec = node_active_link_viewer_exec;
|
2013-06-05 19:06:33 +00:00
|
|
|
ot->poll = composite_node_editable;
|
2012-08-01 19:11:17 +00:00
|
|
|
|
|
|
|
/* flags */
|
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* *************************** add link op ******************** */
|
|
|
|
|
2014-04-29 11:28:16 +02:00
|
|
|
static void node_link_update_header(bContext *C, bNodeLinkDrag *UNUSED(nldrag))
|
|
|
|
{
|
2016-05-14 10:00:52 +02:00
|
|
|
char header[UI_MAX_DRAW_STR];
|
2014-04-29 11:28:16 +02:00
|
|
|
|
2016-05-14 10:00:52 +02:00
|
|
|
BLI_strncpy(header, IFACE_("LMB: drag node link, RMB: cancel"), sizeof(header));
|
2014-04-29 11:28:16 +02:00
|
|
|
ED_area_headerprint(CTX_wm_area(C), header);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int node_count_links(bNodeTree *ntree, bNodeSocket *sock)
|
|
|
|
{
|
|
|
|
bNodeLink *link;
|
|
|
|
int count = 0;
|
|
|
|
for (link = ntree->links.first; link; link = link->next) {
|
|
|
|
if (link->fromsock == sock)
|
|
|
|
++count;
|
|
|
|
if (link->tosock == sock)
|
|
|
|
++count;
|
|
|
|
}
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
Node callback for handling link insertion and swapping of occupied inputs.
Nodes have a feature for moving existing links to unoccupied sockets when connecting
to an already used input. This is based on the standard legacy socket types (value/float,
vector, color/rgba) and works reasonably well for shader, compositor and texture nodes.
For new pynode systems, however, the hardcoded nature of that feature has major drawbacks:
* It does not take different type systems into account, leading to meaningless connections
when sockets are swapped and making the feature useless or outright debilitating.
* Advanced socket behaviors would be possible with a registerable callback, e.g. creating
extensible input lists that move existing connections down to make room for a new link.
Now any handling of new links is done via the 'insert_links' callback, which can also be
registered through the RNA API. For the legacy shader/compo/tex nodes the behavior is the
same, using a C callback.
Note on the 'use_swap' flag: this has been removed because it was meaningless anyway:
It was disabled only for the insert-node-on-link feature, which works only for
completely unconnected nodes anyway, so there would be nothing to swap in the first place.
2015-12-03 12:51:29 +01:00
|
|
|
static void node_remove_extra_links(SpaceNode *snode, bNodeLink *link)
|
2012-08-01 19:11:17 +00:00
|
|
|
{
|
2013-11-06 21:21:37 +00:00
|
|
|
bNodeTree *ntree = snode->edittree;
|
|
|
|
bNodeSocket *from = link->fromsock, *to = link->tosock;
|
2013-11-15 16:54:05 +01:00
|
|
|
bNodeLink *tlink, *tlink_next;
|
Node callback for handling link insertion and swapping of occupied inputs.
Nodes have a feature for moving existing links to unoccupied sockets when connecting
to an already used input. This is based on the standard legacy socket types (value/float,
vector, color/rgba) and works reasonably well for shader, compositor and texture nodes.
For new pynode systems, however, the hardcoded nature of that feature has major drawbacks:
* It does not take different type systems into account, leading to meaningless connections
when sockets are swapped and making the feature useless or outright debilitating.
* Advanced socket behaviors would be possible with a registerable callback, e.g. creating
extensible input lists that move existing connections down to make room for a new link.
Now any handling of new links is done via the 'insert_links' callback, which can also be
registered through the RNA API. For the legacy shader/compo/tex nodes the behavior is the
same, using a C callback.
Note on the 'use_swap' flag: this has been removed because it was meaningless anyway:
It was disabled only for the insert-node-on-link feature, which works only for
completely unconnected nodes anyway, so there would be nothing to swap in the first place.
2015-12-03 12:51:29 +01:00
|
|
|
int to_count = node_count_links(ntree, to);
|
|
|
|
int from_count = node_count_links(ntree, from);
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2013-11-15 16:54:05 +01:00
|
|
|
for (tlink = ntree->links.first; tlink; tlink = tlink_next) {
|
|
|
|
tlink_next = tlink->next;
|
2013-11-06 21:21:37 +00:00
|
|
|
if (tlink == link)
|
|
|
|
continue;
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2013-11-15 16:54:05 +01:00
|
|
|
if (tlink && tlink->fromsock == from) {
|
Node callback for handling link insertion and swapping of occupied inputs.
Nodes have a feature for moving existing links to unoccupied sockets when connecting
to an already used input. This is based on the standard legacy socket types (value/float,
vector, color/rgba) and works reasonably well for shader, compositor and texture nodes.
For new pynode systems, however, the hardcoded nature of that feature has major drawbacks:
* It does not take different type systems into account, leading to meaningless connections
when sockets are swapped and making the feature useless or outright debilitating.
* Advanced socket behaviors would be possible with a registerable callback, e.g. creating
extensible input lists that move existing connections down to make room for a new link.
Now any handling of new links is done via the 'insert_links' callback, which can also be
registered through the RNA API. For the legacy shader/compo/tex nodes the behavior is the
same, using a C callback.
Note on the 'use_swap' flag: this has been removed because it was meaningless anyway:
It was disabled only for the insert-node-on-link feature, which works only for
completely unconnected nodes anyway, so there would be nothing to swap in the first place.
2015-12-03 12:51:29 +01:00
|
|
|
if (from_count > from->limit) {
|
2013-11-06 21:21:37 +00:00
|
|
|
nodeRemLink(ntree, tlink);
|
2013-11-15 16:54:05 +01:00
|
|
|
tlink = NULL;
|
Node callback for handling link insertion and swapping of occupied inputs.
Nodes have a feature for moving existing links to unoccupied sockets when connecting
to an already used input. This is based on the standard legacy socket types (value/float,
vector, color/rgba) and works reasonably well for shader, compositor and texture nodes.
For new pynode systems, however, the hardcoded nature of that feature has major drawbacks:
* It does not take different type systems into account, leading to meaningless connections
when sockets are swapped and making the feature useless or outright debilitating.
* Advanced socket behaviors would be possible with a registerable callback, e.g. creating
extensible input lists that move existing connections down to make room for a new link.
Now any handling of new links is done via the 'insert_links' callback, which can also be
registered through the RNA API. For the legacy shader/compo/tex nodes the behavior is the
same, using a C callback.
Note on the 'use_swap' flag: this has been removed because it was meaningless anyway:
It was disabled only for the insert-node-on-link feature, which works only for
completely unconnected nodes anyway, so there would be nothing to swap in the first place.
2015-12-03 12:51:29 +01:00
|
|
|
--from_count;
|
2013-11-15 16:54:05 +01:00
|
|
|
}
|
2012-08-01 19:11:17 +00:00
|
|
|
}
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2013-11-15 16:54:05 +01:00
|
|
|
if (tlink && tlink->tosock == to) {
|
Node callback for handling link insertion and swapping of occupied inputs.
Nodes have a feature for moving existing links to unoccupied sockets when connecting
to an already used input. This is based on the standard legacy socket types (value/float,
vector, color/rgba) and works reasonably well for shader, compositor and texture nodes.
For new pynode systems, however, the hardcoded nature of that feature has major drawbacks:
* It does not take different type systems into account, leading to meaningless connections
when sockets are swapped and making the feature useless or outright debilitating.
* Advanced socket behaviors would be possible with a registerable callback, e.g. creating
extensible input lists that move existing connections down to make room for a new link.
Now any handling of new links is done via the 'insert_links' callback, which can also be
registered through the RNA API. For the legacy shader/compo/tex nodes the behavior is the
same, using a C callback.
Note on the 'use_swap' flag: this has been removed because it was meaningless anyway:
It was disabled only for the insert-node-on-link feature, which works only for
completely unconnected nodes anyway, so there would be nothing to swap in the first place.
2015-12-03 12:51:29 +01:00
|
|
|
if (to_count > to->limit) {
|
2013-11-06 21:21:37 +00:00
|
|
|
nodeRemLink(ntree, tlink);
|
2013-11-15 16:54:05 +01:00
|
|
|
tlink = NULL;
|
Node callback for handling link insertion and swapping of occupied inputs.
Nodes have a feature for moving existing links to unoccupied sockets when connecting
to an already used input. This is based on the standard legacy socket types (value/float,
vector, color/rgba) and works reasonably well for shader, compositor and texture nodes.
For new pynode systems, however, the hardcoded nature of that feature has major drawbacks:
* It does not take different type systems into account, leading to meaningless connections
when sockets are swapped and making the feature useless or outright debilitating.
* Advanced socket behaviors would be possible with a registerable callback, e.g. creating
extensible input lists that move existing connections down to make room for a new link.
Now any handling of new links is done via the 'insert_links' callback, which can also be
registered through the RNA API. For the legacy shader/compo/tex nodes the behavior is the
same, using a C callback.
Note on the 'use_swap' flag: this has been removed because it was meaningless anyway:
It was disabled only for the insert-node-on-link feature, which works only for
completely unconnected nodes anyway, so there would be nothing to swap in the first place.
2015-12-03 12:51:29 +01:00
|
|
|
--to_count;
|
2013-11-15 16:54:05 +01:00
|
|
|
}
|
2012-08-01 19:11:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-29 11:28:16 +02:00
|
|
|
static void node_link_exit(bContext *C, wmOperator *op, bool apply_links)
|
2012-08-01 19:11:17 +00:00
|
|
|
{
|
|
|
|
SpaceNode *snode = CTX_wm_space_node(C);
|
|
|
|
bNodeTree *ntree = snode->edittree;
|
2014-04-29 11:28:16 +02:00
|
|
|
bNodeLinkDrag *nldrag = op->customdata;
|
|
|
|
LinkData *linkdata;
|
2016-02-05 01:39:42 +05:00
|
|
|
bool do_tag_update = false;
|
|
|
|
|
2015-12-04 09:18:39 +01:00
|
|
|
/* avoid updates while applying links */
|
|
|
|
ntree->is_updating = true;
|
2014-04-29 11:28:16 +02:00
|
|
|
for (linkdata = nldrag->links.first; linkdata; linkdata = linkdata->next) {
|
|
|
|
bNodeLink *link = linkdata->data;
|
2017-08-23 11:47:47 +02:00
|
|
|
|
|
|
|
/* See note below, but basically TEST flag means that the link
|
|
|
|
* was connected to output (or to a node which affects the
|
|
|
|
* output).
|
|
|
|
*/
|
|
|
|
do_tag_update |= (link->flag & NODE_LINK_TEST) != 0;
|
|
|
|
|
2014-04-29 11:28:16 +02:00
|
|
|
if (apply_links && link->tosock && link->fromsock) {
|
Node callback for handling link insertion and swapping of occupied inputs.
Nodes have a feature for moving existing links to unoccupied sockets when connecting
to an already used input. This is based on the standard legacy socket types (value/float,
vector, color/rgba) and works reasonably well for shader, compositor and texture nodes.
For new pynode systems, however, the hardcoded nature of that feature has major drawbacks:
* It does not take different type systems into account, leading to meaningless connections
when sockets are swapped and making the feature useless or outright debilitating.
* Advanced socket behaviors would be possible with a registerable callback, e.g. creating
extensible input lists that move existing connections down to make room for a new link.
Now any handling of new links is done via the 'insert_links' callback, which can also be
registered through the RNA API. For the legacy shader/compo/tex nodes the behavior is the
same, using a C callback.
Note on the 'use_swap' flag: this has been removed because it was meaningless anyway:
It was disabled only for the insert-node-on-link feature, which works only for
completely unconnected nodes anyway, so there would be nothing to swap in the first place.
2015-12-03 12:51:29 +01:00
|
|
|
/* before actually adding the link,
|
|
|
|
* let nodes perform special link insertion handling
|
|
|
|
*/
|
|
|
|
if (link->fromnode->typeinfo->insert_link)
|
|
|
|
link->fromnode->typeinfo->insert_link(ntree, link->fromnode, link);
|
|
|
|
if (link->tonode->typeinfo->insert_link)
|
|
|
|
link->tonode->typeinfo->insert_link(ntree, link->tonode, link);
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2014-04-29 11:28:16 +02:00
|
|
|
/* add link to the node tree */
|
|
|
|
BLI_addtail(&ntree->links, link);
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2014-04-29 11:28:16 +02:00
|
|
|
ntree->update |= NTREE_UPDATE_LINKS;
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2014-04-29 11:28:16 +02:00
|
|
|
/* tag tonode for update */
|
|
|
|
link->tonode->update |= NODE_UPDATE;
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2014-04-29 11:28:16 +02:00
|
|
|
/* we might need to remove a link */
|
Node callback for handling link insertion and swapping of occupied inputs.
Nodes have a feature for moving existing links to unoccupied sockets when connecting
to an already used input. This is based on the standard legacy socket types (value/float,
vector, color/rgba) and works reasonably well for shader, compositor and texture nodes.
For new pynode systems, however, the hardcoded nature of that feature has major drawbacks:
* It does not take different type systems into account, leading to meaningless connections
when sockets are swapped and making the feature useless or outright debilitating.
* Advanced socket behaviors would be possible with a registerable callback, e.g. creating
extensible input lists that move existing connections down to make room for a new link.
Now any handling of new links is done via the 'insert_links' callback, which can also be
registered through the RNA API. For the legacy shader/compo/tex nodes the behavior is the
same, using a C callback.
Note on the 'use_swap' flag: this has been removed because it was meaningless anyway:
It was disabled only for the insert-node-on-link feature, which works only for
completely unconnected nodes anyway, so there would be nothing to swap in the first place.
2015-12-03 12:51:29 +01:00
|
|
|
node_remove_extra_links(snode, link);
|
2016-02-05 01:39:42 +05:00
|
|
|
|
|
|
|
if (link->tonode) {
|
|
|
|
do_tag_update |= (do_tag_update || node_connected_to_output(ntree, link->tonode));
|
|
|
|
}
|
2014-04-29 11:28:16 +02:00
|
|
|
}
|
2016-02-05 01:39:42 +05:00
|
|
|
else {
|
2014-04-29 11:28:16 +02:00
|
|
|
nodeRemLink(ntree, link);
|
2016-02-05 01:39:42 +05:00
|
|
|
}
|
2014-04-29 11:28:16 +02:00
|
|
|
}
|
2015-12-04 09:18:39 +01:00
|
|
|
ntree->is_updating = false;
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2014-04-29 11:28:16 +02:00
|
|
|
ntreeUpdateTree(CTX_data_main(C), ntree);
|
|
|
|
snode_notify(C, snode);
|
2016-02-05 01:39:42 +05:00
|
|
|
if (do_tag_update) {
|
|
|
|
snode_dag_update(C, snode);
|
|
|
|
}
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2014-04-29 11:28:16 +02:00
|
|
|
BLI_remlink(&snode->linkdrag, nldrag);
|
|
|
|
/* links->data pointers are either held by the tree or freed already */
|
|
|
|
BLI_freelistN(&nldrag->links);
|
|
|
|
MEM_freeN(nldrag);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void node_link_find_socket(bContext *C, wmOperator *op, float cursor[2])
|
|
|
|
{
|
|
|
|
SpaceNode *snode = CTX_wm_space_node(C);
|
|
|
|
bNodeLinkDrag *nldrag = op->customdata;
|
2012-08-01 19:11:17 +00:00
|
|
|
bNode *tnode;
|
|
|
|
bNodeSocket *tsock = NULL;
|
|
|
|
LinkData *linkdata;
|
|
|
|
|
2014-04-29 11:28:16 +02:00
|
|
|
if (nldrag->in_out == SOCK_OUT) {
|
|
|
|
if (node_find_indicated_socket(snode, &tnode, &tsock, cursor, SOCK_IN)) {
|
|
|
|
for (linkdata = nldrag->links.first; linkdata; linkdata = linkdata->next) {
|
|
|
|
bNodeLink *link = linkdata->data;
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2014-04-29 11:28:16 +02:00
|
|
|
/* skip if this is already the target socket */
|
|
|
|
if (link->tosock == tsock)
|
|
|
|
continue;
|
|
|
|
/* skip if socket is on the same node as the fromsock */
|
|
|
|
if (tnode && link->fromnode == tnode)
|
|
|
|
continue;
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2014-04-29 11:28:16 +02:00
|
|
|
/* attach links to the socket */
|
|
|
|
link->tonode = tnode;
|
|
|
|
link->tosock = tsock;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
for (linkdata = nldrag->links.first; linkdata; linkdata = linkdata->next) {
|
|
|
|
bNodeLink *link = linkdata->data;
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2014-04-29 11:28:16 +02:00
|
|
|
link->tonode = NULL;
|
|
|
|
link->tosock = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (node_find_indicated_socket(snode, &tnode, &tsock, cursor, SOCK_OUT)) {
|
|
|
|
for (linkdata = nldrag->links.first; linkdata; linkdata = linkdata->next) {
|
|
|
|
bNodeLink *link = linkdata->data;
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2014-04-29 11:28:16 +02:00
|
|
|
/* skip if this is already the target socket */
|
|
|
|
if (link->fromsock == tsock)
|
|
|
|
continue;
|
|
|
|
/* skip if socket is on the same node as the fromsock */
|
|
|
|
if (tnode && link->tonode == tnode)
|
|
|
|
continue;
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2014-04-29 11:28:16 +02:00
|
|
|
/* attach links to the socket */
|
|
|
|
link->fromnode = tnode;
|
|
|
|
link->fromsock = tsock;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
for (linkdata = nldrag->links.first; linkdata; linkdata = linkdata->next) {
|
|
|
|
bNodeLink *link = linkdata->data;
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2014-04-29 11:28:16 +02:00
|
|
|
link->fromnode = NULL;
|
|
|
|
link->fromsock = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* loop that adds a nodelink, called by function below */
|
|
|
|
/* in_out = starting socket */
|
|
|
|
static int node_link_modal(bContext *C, wmOperator *op, const wmEvent *event)
|
|
|
|
{
|
|
|
|
bNodeLinkDrag *nldrag = op->customdata;
|
|
|
|
ARegion *ar = CTX_wm_region(C);
|
|
|
|
float cursor[2];
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2012-08-01 19:11:17 +00:00
|
|
|
UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1],
|
2013-09-05 13:03:03 +00:00
|
|
|
&cursor[0], &cursor[1]);
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2012-08-01 19:11:17 +00:00
|
|
|
switch (event->type) {
|
|
|
|
case MOUSEMOVE:
|
2014-04-29 11:28:16 +02:00
|
|
|
node_link_find_socket(C, op, cursor);
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2014-04-29 11:28:16 +02:00
|
|
|
node_link_update_header(C, nldrag);
|
2012-08-01 19:11:17 +00:00
|
|
|
ED_region_tag_redraw(ar);
|
|
|
|
break;
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2012-08-01 19:11:17 +00:00
|
|
|
case LEFTMOUSE:
|
|
|
|
case RIGHTMOUSE:
|
2012-09-08 08:59:47 +00:00
|
|
|
case MIDDLEMOUSE:
|
|
|
|
{
|
2015-04-07 14:08:30 +02:00
|
|
|
node_link_exit(C, op, true);
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2015-04-07 14:08:30 +02:00
|
|
|
ED_area_headerprint(CTX_wm_area(C), NULL);
|
|
|
|
ED_region_tag_redraw(ar);
|
|
|
|
return OPERATOR_FINISHED;
|
2012-08-01 19:11:17 +00:00
|
|
|
}
|
|
|
|
}
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2012-08-01 19:11:17 +00:00
|
|
|
return OPERATOR_RUNNING_MODAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* return 1 when socket clicked */
|
2014-04-29 11:28:16 +02:00
|
|
|
static bNodeLinkDrag *node_link_init(SpaceNode *snode, float cursor[2], bool detach)
|
2012-08-01 19:11:17 +00:00
|
|
|
{
|
|
|
|
bNode *node;
|
|
|
|
bNodeSocket *sock;
|
|
|
|
bNodeLink *link, *link_next, *oplink;
|
|
|
|
bNodeLinkDrag *nldrag = NULL;
|
|
|
|
LinkData *linkdata;
|
|
|
|
int num_links;
|
|
|
|
|
|
|
|
/* output indicated? */
|
2013-09-05 13:03:03 +00:00
|
|
|
if (node_find_indicated_socket(snode, &node, &sock, cursor, SOCK_OUT)) {
|
2012-08-01 19:11:17 +00:00
|
|
|
nldrag = MEM_callocN(sizeof(bNodeLinkDrag), "drag link op customdata");
|
|
|
|
|
|
|
|
num_links = nodeCountSocketLinks(snode->edittree, sock);
|
|
|
|
if (num_links > 0 && (num_links >= sock->limit || detach)) {
|
|
|
|
/* dragged links are fixed on input side */
|
|
|
|
nldrag->in_out = SOCK_IN;
|
|
|
|
/* detach current links and store them in the operator data */
|
|
|
|
for (link = snode->edittree->links.first; link; link = link_next) {
|
|
|
|
link_next = link->next;
|
|
|
|
if (link->fromsock == sock) {
|
|
|
|
linkdata = MEM_callocN(sizeof(LinkData), "drag link op link data");
|
|
|
|
linkdata->data = oplink = MEM_callocN(sizeof(bNodeLink), "drag link op link");
|
|
|
|
*oplink = *link;
|
|
|
|
oplink->next = oplink->prev = NULL;
|
2013-03-18 16:34:57 +00:00
|
|
|
oplink->flag |= NODE_LINK_VALID;
|
2016-02-05 01:39:42 +05:00
|
|
|
|
|
|
|
/* The link could be disconnected and in that case we
|
|
|
|
* wouldn't be able to check whether tag update is
|
|
|
|
* needed or not when releasing mouse button. So we
|
|
|
|
* cache whether the link affects output or not here
|
|
|
|
* using TEST flag.
|
|
|
|
*/
|
|
|
|
oplink->flag &= ~NODE_LINK_TEST;
|
|
|
|
if (node_connected_to_output(snode->edittree, link->tonode)) {
|
|
|
|
oplink->flag |= NODE_LINK_TEST;
|
|
|
|
}
|
|
|
|
|
2012-08-01 19:11:17 +00:00
|
|
|
BLI_addtail(&nldrag->links, linkdata);
|
|
|
|
nodeRemLink(snode->edittree, link);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
/* dragged links are fixed on output side */
|
|
|
|
nldrag->in_out = SOCK_OUT;
|
|
|
|
/* create a new link */
|
|
|
|
linkdata = MEM_callocN(sizeof(LinkData), "drag link op link data");
|
|
|
|
linkdata->data = oplink = MEM_callocN(sizeof(bNodeLink), "drag link op link");
|
|
|
|
oplink->fromnode = node;
|
|
|
|
oplink->fromsock = sock;
|
2013-03-18 16:34:57 +00:00
|
|
|
oplink->flag |= NODE_LINK_VALID;
|
2016-02-05 01:39:42 +05:00
|
|
|
oplink->flag &= ~NODE_LINK_TEST;
|
|
|
|
if (node_connected_to_output(snode->edittree, node)) {
|
|
|
|
oplink->flag |= NODE_LINK_TEST;
|
|
|
|
}
|
|
|
|
|
2012-08-01 19:11:17 +00:00
|
|
|
BLI_addtail(&nldrag->links, linkdata);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* or an input? */
|
2013-09-05 13:03:03 +00:00
|
|
|
else if (node_find_indicated_socket(snode, &node, &sock, cursor, SOCK_IN)) {
|
2012-08-01 19:11:17 +00:00
|
|
|
nldrag = MEM_callocN(sizeof(bNodeLinkDrag), "drag link op customdata");
|
|
|
|
|
|
|
|
num_links = nodeCountSocketLinks(snode->edittree, sock);
|
|
|
|
if (num_links > 0 && (num_links >= sock->limit || detach)) {
|
|
|
|
/* dragged links are fixed on output side */
|
|
|
|
nldrag->in_out = SOCK_OUT;
|
|
|
|
/* detach current links and store them in the operator data */
|
|
|
|
for (link = snode->edittree->links.first; link; link = link_next) {
|
|
|
|
link_next = link->next;
|
|
|
|
if (link->tosock == sock) {
|
|
|
|
linkdata = MEM_callocN(sizeof(LinkData), "drag link op link data");
|
|
|
|
linkdata->data = oplink = MEM_callocN(sizeof(bNodeLink), "drag link op link");
|
|
|
|
*oplink = *link;
|
|
|
|
oplink->next = oplink->prev = NULL;
|
2013-03-18 16:34:57 +00:00
|
|
|
oplink->flag |= NODE_LINK_VALID;
|
2016-02-05 01:39:42 +05:00
|
|
|
oplink->flag &= ~NODE_LINK_TEST;
|
|
|
|
if (node_connected_to_output(snode->edittree, link->tonode)) {
|
|
|
|
oplink->flag |= NODE_LINK_TEST;
|
|
|
|
}
|
|
|
|
|
2012-08-01 19:11:17 +00:00
|
|
|
BLI_addtail(&nldrag->links, linkdata);
|
|
|
|
nodeRemLink(snode->edittree, link);
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2012-08-01 19:11:17 +00:00
|
|
|
/* send changed event to original link->tonode */
|
|
|
|
if (node)
|
|
|
|
snode_update(snode, node);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
/* dragged links are fixed on input side */
|
|
|
|
nldrag->in_out = SOCK_IN;
|
|
|
|
/* create a new link */
|
|
|
|
linkdata = MEM_callocN(sizeof(LinkData), "drag link op link data");
|
|
|
|
linkdata->data = oplink = MEM_callocN(sizeof(bNodeLink), "drag link op link");
|
|
|
|
oplink->tonode = node;
|
|
|
|
oplink->tosock = sock;
|
2013-03-18 16:34:57 +00:00
|
|
|
oplink->flag |= NODE_LINK_VALID;
|
2016-02-05 01:39:42 +05:00
|
|
|
oplink->flag &= ~NODE_LINK_TEST;
|
|
|
|
if (node_connected_to_output(snode->edittree, node)) {
|
|
|
|
oplink->flag |= NODE_LINK_TEST;
|
|
|
|
}
|
|
|
|
|
2012-08-01 19:11:17 +00:00
|
|
|
BLI_addtail(&nldrag->links, linkdata);
|
|
|
|
}
|
|
|
|
}
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2012-08-01 19:11:17 +00:00
|
|
|
return nldrag;
|
|
|
|
}
|
|
|
|
|
2013-03-13 09:03:46 +00:00
|
|
|
static int node_link_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
2012-08-01 19:11:17 +00:00
|
|
|
{
|
|
|
|
SpaceNode *snode = CTX_wm_space_node(C);
|
|
|
|
ARegion *ar = CTX_wm_region(C);
|
|
|
|
bNodeLinkDrag *nldrag;
|
2013-09-05 13:03:03 +00:00
|
|
|
float cursor[2];
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2014-04-29 11:28:16 +02:00
|
|
|
bool detach = RNA_boolean_get(op->ptr, "detach");
|
2012-08-01 19:11:17 +00:00
|
|
|
|
|
|
|
UI_view2d_region_to_view(&ar->v2d, event->mval[0], event->mval[1],
|
2013-09-05 13:03:03 +00:00
|
|
|
&cursor[0], &cursor[1]);
|
2012-08-01 19:11:17 +00:00
|
|
|
|
2014-11-18 15:51:31 +01:00
|
|
|
ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C));
|
2012-08-01 19:11:17 +00:00
|
|
|
|
2013-09-05 13:03:03 +00:00
|
|
|
nldrag = node_link_init(snode, cursor, detach);
|
2012-08-01 19:11:17 +00:00
|
|
|
|
|
|
|
if (nldrag) {
|
|
|
|
op->customdata = nldrag;
|
|
|
|
BLI_addtail(&snode->linkdrag, nldrag);
|
|
|
|
|
|
|
|
/* add modal handler */
|
|
|
|
WM_event_add_modal_handler(C, op);
|
|
|
|
|
|
|
|
return OPERATOR_RUNNING_MODAL;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH;
|
|
|
|
}
|
|
|
|
|
2013-10-30 23:08:53 +00:00
|
|
|
static void node_link_cancel(bContext *C, wmOperator *op)
|
2012-08-01 19:11:17 +00:00
|
|
|
{
|
|
|
|
SpaceNode *snode = CTX_wm_space_node(C);
|
|
|
|
bNodeLinkDrag *nldrag = op->customdata;
|
|
|
|
|
|
|
|
BLI_remlink(&snode->linkdrag, nldrag);
|
|
|
|
|
|
|
|
BLI_freelistN(&nldrag->links);
|
|
|
|
MEM_freeN(nldrag);
|
|
|
|
}
|
|
|
|
|
|
|
|
void NODE_OT_link(wmOperatorType *ot)
|
|
|
|
{
|
|
|
|
/* identifiers */
|
|
|
|
ot->name = "Link Nodes";
|
|
|
|
ot->idname = "NODE_OT_link";
|
|
|
|
ot->description = "Use the mouse to create a link between two nodes";
|
|
|
|
|
|
|
|
/* api callbacks */
|
|
|
|
ot->invoke = node_link_invoke;
|
|
|
|
ot->modal = node_link_modal;
|
|
|
|
// ot->exec = node_link_exec;
|
2013-06-05 19:06:33 +00:00
|
|
|
ot->poll = ED_operator_node_editable;
|
2012-08-01 19:11:17 +00:00
|
|
|
ot->cancel = node_link_cancel;
|
|
|
|
|
|
|
|
/* flags */
|
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING;
|
|
|
|
|
2014-04-01 11:34:00 +11:00
|
|
|
RNA_def_boolean(ot->srna, "detach", false, "Detach", "Detach and redirect existing links");
|
2012-08-01 19:11:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* ********************** Make Link operator ***************** */
|
|
|
|
|
|
|
|
/* makes a link between selected output and input sockets */
|
|
|
|
static int node_make_link_exec(bContext *C, wmOperator *op)
|
|
|
|
{
|
|
|
|
SpaceNode *snode = CTX_wm_space_node(C);
|
2014-02-03 18:55:59 +11:00
|
|
|
const bool replace = RNA_boolean_get(op->ptr, "replace");
|
2012-08-01 19:11:17 +00:00
|
|
|
|
2014-11-18 15:51:31 +01:00
|
|
|
ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C));
|
2012-08-01 19:11:17 +00:00
|
|
|
|
|
|
|
snode_autoconnect(snode, 1, replace);
|
|
|
|
|
|
|
|
/* deselect sockets after linking */
|
|
|
|
node_deselect_all_input_sockets(snode, 0);
|
|
|
|
node_deselect_all_output_sockets(snode, 0);
|
|
|
|
|
2013-05-07 15:28:42 +00:00
|
|
|
ntreeUpdateTree(CTX_data_main(C), snode->edittree);
|
2012-08-01 19:11:17 +00:00
|
|
|
snode_notify(C, snode);
|
|
|
|
snode_dag_update(C, snode);
|
|
|
|
|
|
|
|
return OPERATOR_FINISHED;
|
|
|
|
}
|
|
|
|
|
|
|
|
void NODE_OT_link_make(wmOperatorType *ot)
|
|
|
|
{
|
|
|
|
/* identifiers */
|
|
|
|
ot->name = "Make Links";
|
|
|
|
ot->description = "Makes a link between selected output in input sockets";
|
|
|
|
ot->idname = "NODE_OT_link_make";
|
|
|
|
|
|
|
|
/* callbacks */
|
|
|
|
ot->exec = node_make_link_exec;
|
2013-06-05 19:06:33 +00:00
|
|
|
ot->poll = ED_operator_node_editable; // XXX we need a special poll which checks that there are selected input/output sockets
|
2012-08-01 19:11:17 +00:00
|
|
|
|
|
|
|
/* flags */
|
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
|
|
|
|
RNA_def_boolean(ot->srna, "replace", 0, "Replace", "Replace socket connections with the new links");
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ********************** Cut Link operator ***************** */
|
2015-11-13 00:03:12 +11:00
|
|
|
static bool cut_links_intersect(bNodeLink *link, float mcoords[][2], int tot)
|
2012-08-01 19:11:17 +00:00
|
|
|
{
|
|
|
|
float coord_array[NODE_LINK_RESOL + 1][2];
|
|
|
|
int i, b;
|
|
|
|
|
|
|
|
if (node_link_bezier_points(NULL, NULL, link, coord_array, NODE_LINK_RESOL)) {
|
|
|
|
|
|
|
|
for (i = 0; i < tot - 1; i++)
|
|
|
|
for (b = 0; b < NODE_LINK_RESOL; b++)
|
2015-11-13 07:25:36 +11:00
|
|
|
if (isect_seg_seg_v2(mcoords[i], mcoords[i + 1], coord_array[b], coord_array[b + 1]) > 0)
|
2012-08-01 19:11:17 +00:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cut_links_exec(bContext *C, wmOperator *op)
|
|
|
|
{
|
|
|
|
SpaceNode *snode = CTX_wm_space_node(C);
|
|
|
|
ARegion *ar = CTX_wm_region(C);
|
|
|
|
float mcoords[256][2];
|
|
|
|
int i = 0;
|
2016-02-05 01:39:42 +05:00
|
|
|
bool do_tag_update = false;
|
2012-08-01 19:11:17 +00:00
|
|
|
|
2013-08-24 10:05:29 +00:00
|
|
|
RNA_BEGIN (op->ptr, itemptr, "path")
|
2012-08-01 19:11:17 +00:00
|
|
|
{
|
|
|
|
float loc[2];
|
|
|
|
|
|
|
|
RNA_float_get_array(&itemptr, "loc", loc);
|
|
|
|
UI_view2d_region_to_view(&ar->v2d, (int)loc[0], (int)loc[1],
|
|
|
|
&mcoords[i][0], &mcoords[i][1]);
|
|
|
|
i++;
|
|
|
|
if (i >= 256) break;
|
|
|
|
}
|
|
|
|
RNA_END;
|
|
|
|
|
|
|
|
if (i > 1) {
|
2014-04-11 11:25:41 +10:00
|
|
|
bool found = false;
|
2012-08-01 19:11:17 +00:00
|
|
|
bNodeLink *link, *next;
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2014-11-18 15:51:31 +01:00
|
|
|
ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C));
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2012-08-01 19:11:17 +00:00
|
|
|
for (link = snode->edittree->links.first; link; link = next) {
|
|
|
|
next = link->next;
|
2013-03-18 16:34:57 +00:00
|
|
|
if (nodeLinkIsHidden(link))
|
|
|
|
continue;
|
2012-08-01 19:11:17 +00:00
|
|
|
|
|
|
|
if (cut_links_intersect(link, mcoords, i)) {
|
2012-08-03 23:44:19 +00:00
|
|
|
|
2014-04-01 11:34:00 +11:00
|
|
|
if (found == false) {
|
2014-11-18 15:51:31 +01:00
|
|
|
/* TODO(sergey): Why did we kill jobs twice? */
|
|
|
|
ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C));
|
2014-04-01 11:34:00 +11:00
|
|
|
found = true;
|
2012-08-03 23:44:19 +00:00
|
|
|
}
|
|
|
|
|
2016-02-05 01:39:42 +05:00
|
|
|
do_tag_update |= (do_tag_update || node_connected_to_output(snode->edittree, link->tonode));
|
|
|
|
|
2012-08-01 19:11:17 +00:00
|
|
|
snode_update(snode, link->tonode);
|
|
|
|
nodeRemLink(snode->edittree, link);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-08-03 23:44:19 +00:00
|
|
|
if (found) {
|
2013-05-07 15:28:42 +00:00
|
|
|
ntreeUpdateTree(CTX_data_main(C), snode->edittree);
|
2012-08-03 23:44:19 +00:00
|
|
|
snode_notify(C, snode);
|
2016-02-05 01:39:42 +05:00
|
|
|
if (do_tag_update) {
|
|
|
|
snode_dag_update(C, snode);
|
|
|
|
}
|
2012-08-03 23:44:19 +00:00
|
|
|
|
|
|
|
return OPERATOR_FINISHED;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return OPERATOR_CANCELLED;
|
|
|
|
}
|
2012-08-01 19:11:17 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH;
|
|
|
|
}
|
|
|
|
|
|
|
|
void NODE_OT_links_cut(wmOperatorType *ot)
|
|
|
|
{
|
2013-01-27 07:23:58 +00:00
|
|
|
ot->name = "Cut Links";
|
2012-08-01 19:11:17 +00:00
|
|
|
ot->idname = "NODE_OT_links_cut";
|
|
|
|
ot->description = "Use the mouse to cut (remove) some links";
|
|
|
|
|
|
|
|
ot->invoke = WM_gesture_lines_invoke;
|
|
|
|
ot->modal = WM_gesture_lines_modal;
|
|
|
|
ot->exec = cut_links_exec;
|
|
|
|
ot->cancel = WM_gesture_lines_cancel;
|
|
|
|
|
2013-06-05 19:06:33 +00:00
|
|
|
ot->poll = ED_operator_node_editable;
|
2012-08-01 19:11:17 +00:00
|
|
|
|
|
|
|
/* flags */
|
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
|
2017-10-16 16:11:04 +11:00
|
|
|
/* properties */
|
|
|
|
PropertyRNA *prop;
|
|
|
|
prop = RNA_def_collection_runtime(ot->srna, "path", &RNA_OperatorMousePath, "Path", "");
|
|
|
|
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
|
|
|
|
|
2012-08-01 19:11:17 +00:00
|
|
|
/* internal */
|
|
|
|
RNA_def_int(ot->srna, "cursor", BC_KNIFECURSOR, 0, INT_MAX, "Cursor", "", 0, INT_MAX);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ********************** Detach links operator ***************** */
|
|
|
|
|
|
|
|
static int detach_links_exec(bContext *C, wmOperator *UNUSED(op))
|
|
|
|
{
|
|
|
|
SpaceNode *snode = CTX_wm_space_node(C);
|
|
|
|
bNodeTree *ntree = snode->edittree;
|
|
|
|
bNode *node;
|
|
|
|
|
2014-11-18 15:51:31 +01:00
|
|
|
ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C));
|
2012-08-01 19:11:17 +00:00
|
|
|
|
|
|
|
for (node = ntree->nodes.first; node; node = node->next) {
|
|
|
|
if (node->flag & SELECT) {
|
|
|
|
nodeInternalRelink(ntree, node);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-07 15:28:42 +00:00
|
|
|
ntreeUpdateTree(CTX_data_main(C), ntree);
|
2012-08-01 19:11:17 +00:00
|
|
|
|
|
|
|
snode_notify(C, snode);
|
|
|
|
snode_dag_update(C, snode);
|
|
|
|
|
|
|
|
return OPERATOR_FINISHED;
|
|
|
|
}
|
|
|
|
|
|
|
|
void NODE_OT_links_detach(wmOperatorType *ot)
|
|
|
|
{
|
|
|
|
ot->name = "Detach Links";
|
|
|
|
ot->idname = "NODE_OT_links_detach";
|
|
|
|
ot->description = "Remove all links to selected nodes, and try to connect neighbor nodes together";
|
|
|
|
|
|
|
|
ot->exec = detach_links_exec;
|
2013-06-05 19:06:33 +00:00
|
|
|
ot->poll = ED_operator_node_editable;
|
2012-08-01 19:11:17 +00:00
|
|
|
|
|
|
|
/* flags */
|
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ****************** Set Parent ******************* */
|
|
|
|
|
|
|
|
static int node_parent_set_exec(bContext *C, wmOperator *UNUSED(op))
|
|
|
|
{
|
|
|
|
SpaceNode *snode = CTX_wm_space_node(C);
|
|
|
|
bNodeTree *ntree = snode->edittree;
|
|
|
|
bNode *frame = nodeGetActive(ntree), *node;
|
|
|
|
if (!frame || frame->type != NODE_FRAME)
|
|
|
|
return OPERATOR_CANCELLED;
|
|
|
|
|
|
|
|
for (node = ntree->nodes.first; node; node = node->next) {
|
|
|
|
if (node == frame)
|
|
|
|
continue;
|
|
|
|
if (node->flag & NODE_SELECT) {
|
|
|
|
nodeDetachNode(node);
|
|
|
|
nodeAttachNode(node, frame);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ED_node_sort(ntree);
|
|
|
|
WM_event_add_notifier(C, NC_NODE | ND_DISPLAY, NULL);
|
|
|
|
|
|
|
|
return OPERATOR_FINISHED;
|
|
|
|
}
|
|
|
|
|
|
|
|
void NODE_OT_parent_set(wmOperatorType *ot)
|
|
|
|
{
|
|
|
|
/* identifiers */
|
|
|
|
ot->name = "Make Parent";
|
|
|
|
ot->description = "Attach selected nodes";
|
|
|
|
ot->idname = "NODE_OT_parent_set";
|
|
|
|
|
|
|
|
/* api callbacks */
|
|
|
|
ot->exec = node_parent_set_exec;
|
2013-06-05 19:06:33 +00:00
|
|
|
ot->poll = ED_operator_node_editable;
|
2012-08-01 19:11:17 +00:00
|
|
|
|
|
|
|
/* flags */
|
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ****************** Join Nodes ******************* */
|
|
|
|
|
|
|
|
/* tags for depth-first search */
|
|
|
|
#define NODE_JOIN_DONE 1
|
|
|
|
#define NODE_JOIN_IS_DESCENDANT 2
|
|
|
|
|
|
|
|
static void node_join_attach_recursive(bNode *node, bNode *frame)
|
|
|
|
{
|
|
|
|
node->done |= NODE_JOIN_DONE;
|
|
|
|
|
|
|
|
if (node == frame) {
|
|
|
|
node->done |= NODE_JOIN_IS_DESCENDANT;
|
|
|
|
}
|
|
|
|
else if (node->parent) {
|
|
|
|
/* call recursively */
|
|
|
|
if (!(node->parent->done & NODE_JOIN_DONE))
|
|
|
|
node_join_attach_recursive(node->parent, frame);
|
|
|
|
|
|
|
|
/* in any case: if the parent is a descendant, so is the child */
|
|
|
|
if (node->parent->done & NODE_JOIN_IS_DESCENDANT)
|
|
|
|
node->done |= NODE_JOIN_IS_DESCENDANT;
|
|
|
|
else if (node->flag & NODE_TEST) {
|
|
|
|
/* if parent is not an decendant of the frame, reattach the node */
|
|
|
|
nodeDetachNode(node);
|
|
|
|
nodeAttachNode(node, frame);
|
|
|
|
node->done |= NODE_JOIN_IS_DESCENDANT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (node->flag & NODE_TEST) {
|
|
|
|
nodeAttachNode(node, frame);
|
|
|
|
node->done |= NODE_JOIN_IS_DESCENDANT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int node_join_exec(bContext *C, wmOperator *UNUSED(op))
|
|
|
|
{
|
|
|
|
SpaceNode *snode = CTX_wm_space_node(C);
|
|
|
|
bNodeTree *ntree = snode->edittree;
|
|
|
|
bNode *node, *frame;
|
|
|
|
|
|
|
|
/* XXX save selection: node_add_node call below sets the new frame as single active+selected node */
|
|
|
|
for (node = ntree->nodes.first; node; node = node->next) {
|
|
|
|
if (node->flag & NODE_SELECT)
|
|
|
|
node->flag |= NODE_TEST;
|
|
|
|
else
|
|
|
|
node->flag &= ~NODE_TEST;
|
|
|
|
}
|
|
|
|
|
2013-03-18 16:34:57 +00:00
|
|
|
frame = node_add_node(C, NULL, NODE_FRAME, 0.0f, 0.0f);
|
2012-08-01 19:11:17 +00:00
|
|
|
|
|
|
|
/* reset tags */
|
|
|
|
for (node = ntree->nodes.first; node; node = node->next)
|
|
|
|
node->done = 0;
|
|
|
|
|
|
|
|
for (node = ntree->nodes.first; node; node = node->next) {
|
|
|
|
if (!(node->done & NODE_JOIN_DONE))
|
|
|
|
node_join_attach_recursive(node, frame);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* restore selection */
|
|
|
|
for (node = ntree->nodes.first; node; node = node->next) {
|
|
|
|
if (node->flag & NODE_TEST)
|
|
|
|
node->flag |= NODE_SELECT;
|
|
|
|
}
|
|
|
|
|
|
|
|
ED_node_sort(ntree);
|
|
|
|
WM_event_add_notifier(C, NC_NODE | ND_DISPLAY, NULL);
|
|
|
|
|
|
|
|
return OPERATOR_FINISHED;
|
|
|
|
}
|
|
|
|
|
|
|
|
void NODE_OT_join(wmOperatorType *ot)
|
|
|
|
{
|
|
|
|
/* identifiers */
|
|
|
|
ot->name = "Join Nodes";
|
|
|
|
ot->description = "Attach selected nodes to a new common frame";
|
|
|
|
ot->idname = "NODE_OT_join";
|
|
|
|
|
|
|
|
/* api callbacks */
|
|
|
|
ot->exec = node_join_exec;
|
2013-06-05 19:06:33 +00:00
|
|
|
ot->poll = ED_operator_node_editable;
|
2012-08-01 19:11:17 +00:00
|
|
|
|
|
|
|
/* flags */
|
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ****************** Attach ******************* */
|
|
|
|
|
2015-08-01 17:39:48 +02:00
|
|
|
static bNode *node_find_frame_to_attach(ARegion *ar, const bNodeTree *ntree, const int mouse_xy[2])
|
2012-08-01 19:11:17 +00:00
|
|
|
{
|
|
|
|
bNode *frame;
|
2013-09-05 13:03:03 +00:00
|
|
|
float cursor[2];
|
2015-08-01 17:39:48 +02:00
|
|
|
|
2013-09-05 13:03:03 +00:00
|
|
|
/* convert mouse coordinates to v2d space */
|
2015-08-01 17:39:48 +02:00
|
|
|
UI_view2d_region_to_view(&ar->v2d, UNPACK2(mouse_xy), &cursor[0], &cursor[1]);
|
2012-08-01 19:11:17 +00:00
|
|
|
|
|
|
|
/* check nodes front to back */
|
|
|
|
for (frame = ntree->nodes.last; frame; frame = frame->prev) {
|
|
|
|
/* skip selected, those are the nodes we want to attach */
|
|
|
|
if ((frame->type != NODE_FRAME) || (frame->flag & NODE_SELECT))
|
|
|
|
continue;
|
2015-08-01 17:39:48 +02:00
|
|
|
if (BLI_rctf_isect_pt_v(&frame->totr, cursor))
|
|
|
|
return frame;
|
2012-08-01 19:11:17 +00:00
|
|
|
}
|
2015-08-01 17:39:48 +02:00
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int node_attach_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
|
|
|
|
{
|
|
|
|
ARegion *ar = CTX_wm_region(C);
|
|
|
|
SpaceNode *snode = CTX_wm_space_node(C);
|
|
|
|
bNodeTree *ntree = snode->edittree;
|
|
|
|
bNode *frame = node_find_frame_to_attach(ar, ntree, event->mval);
|
|
|
|
|
2012-08-01 19:11:17 +00:00
|
|
|
if (frame) {
|
|
|
|
bNode *node, *parent;
|
|
|
|
for (node = ntree->nodes.last; node; node = node->prev) {
|
|
|
|
if (node->flag & NODE_SELECT) {
|
|
|
|
if (node->parent == NULL) {
|
2012-08-05 20:40:26 +00:00
|
|
|
/* disallow moving a parent into its child */
|
2014-04-01 11:34:00 +11:00
|
|
|
if (nodeAttachNodeCheck(frame, node) == false) {
|
2012-08-05 20:40:26 +00:00
|
|
|
/* attach all unparented nodes */
|
|
|
|
nodeAttachNode(node, frame);
|
|
|
|
}
|
2012-08-01 19:11:17 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
/* attach nodes which share parent with the frame */
|
2012-08-05 20:40:26 +00:00
|
|
|
for (parent = frame->parent; parent; parent = parent->parent) {
|
|
|
|
if (parent == node->parent) {
|
2012-08-01 19:11:17 +00:00
|
|
|
break;
|
2012-08-05 20:40:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-08-01 19:11:17 +00:00
|
|
|
if (parent) {
|
2012-08-05 20:40:26 +00:00
|
|
|
/* disallow moving a parent into its child */
|
2014-04-01 11:34:00 +11:00
|
|
|
if (nodeAttachNodeCheck(frame, node) == false) {
|
2012-08-05 20:40:26 +00:00
|
|
|
nodeDetachNode(node);
|
|
|
|
nodeAttachNode(node, frame);
|
|
|
|
}
|
2012-08-01 19:11:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ED_node_sort(ntree);
|
|
|
|
WM_event_add_notifier(C, NC_NODE | ND_DISPLAY, NULL);
|
|
|
|
|
|
|
|
return OPERATOR_FINISHED;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void NODE_OT_attach(wmOperatorType *ot)
|
|
|
|
{
|
|
|
|
/* identifiers */
|
|
|
|
ot->name = "Attach Nodes";
|
|
|
|
ot->description = "Attach active node to a frame";
|
|
|
|
ot->idname = "NODE_OT_attach";
|
|
|
|
|
|
|
|
/* api callbacks */
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2012-08-01 19:11:17 +00:00
|
|
|
ot->invoke = node_attach_invoke;
|
2013-06-05 19:06:33 +00:00
|
|
|
ot->poll = ED_operator_node_editable;
|
2012-08-01 19:11:17 +00:00
|
|
|
|
|
|
|
/* flags */
|
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ****************** Detach ******************* */
|
|
|
|
|
|
|
|
/* tags for depth-first search */
|
|
|
|
#define NODE_DETACH_DONE 1
|
|
|
|
#define NODE_DETACH_IS_DESCENDANT 2
|
|
|
|
|
|
|
|
static void node_detach_recursive(bNode *node)
|
|
|
|
{
|
|
|
|
node->done |= NODE_DETACH_DONE;
|
|
|
|
|
|
|
|
if (node->parent) {
|
|
|
|
/* call recursively */
|
|
|
|
if (!(node->parent->done & NODE_DETACH_DONE))
|
|
|
|
node_detach_recursive(node->parent);
|
|
|
|
|
|
|
|
/* in any case: if the parent is a descendant, so is the child */
|
|
|
|
if (node->parent->done & NODE_DETACH_IS_DESCENDANT)
|
|
|
|
node->done |= NODE_DETACH_IS_DESCENDANT;
|
|
|
|
else if (node->flag & NODE_SELECT) {
|
|
|
|
/* if parent is not a decendant of a selected node, detach */
|
|
|
|
nodeDetachNode(node);
|
|
|
|
node->done |= NODE_DETACH_IS_DESCENDANT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (node->flag & NODE_SELECT) {
|
|
|
|
node->done |= NODE_DETACH_IS_DESCENDANT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* detach the root nodes in the current selection */
|
|
|
|
static int node_detach_exec(bContext *C, wmOperator *UNUSED(op))
|
|
|
|
{
|
|
|
|
SpaceNode *snode = CTX_wm_space_node(C);
|
|
|
|
bNodeTree *ntree = snode->edittree;
|
|
|
|
bNode *node;
|
|
|
|
|
|
|
|
/* reset tags */
|
|
|
|
for (node = ntree->nodes.first; node; node = node->next)
|
|
|
|
node->done = 0;
|
|
|
|
/* detach nodes recursively
|
|
|
|
* relative order is preserved here!
|
|
|
|
*/
|
|
|
|
for (node = ntree->nodes.first; node; node = node->next) {
|
|
|
|
if (!(node->done & NODE_DETACH_DONE))
|
|
|
|
node_detach_recursive(node);
|
|
|
|
}
|
|
|
|
|
|
|
|
ED_node_sort(ntree);
|
|
|
|
WM_event_add_notifier(C, NC_NODE | ND_DISPLAY, NULL);
|
|
|
|
|
|
|
|
return OPERATOR_FINISHED;
|
|
|
|
}
|
|
|
|
|
|
|
|
void NODE_OT_detach(wmOperatorType *ot)
|
|
|
|
{
|
|
|
|
/* identifiers */
|
|
|
|
ot->name = "Detach Nodes";
|
|
|
|
ot->description = "Detach selected nodes from parents";
|
|
|
|
ot->idname = "NODE_OT_detach";
|
|
|
|
|
|
|
|
/* api callbacks */
|
|
|
|
ot->exec = node_detach_exec;
|
2013-06-05 19:06:33 +00:00
|
|
|
ot->poll = ED_operator_node_editable;
|
2012-08-01 19:11:17 +00:00
|
|
|
|
|
|
|
/* flags */
|
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ********************* automatic node insert on dragging ******************* */
|
|
|
|
|
|
|
|
|
|
|
|
/* prevent duplicate testing code below */
|
2013-11-07 09:02:30 +00:00
|
|
|
static bool ed_node_link_conditions(ScrArea *sa, bool test, SpaceNode **r_snode, bNode **r_select)
|
2012-08-01 19:11:17 +00:00
|
|
|
{
|
|
|
|
SpaceNode *snode = sa ? sa->spacedata.first : NULL;
|
2013-11-07 09:02:29 +00:00
|
|
|
bNode *node, *select = NULL;
|
2012-08-01 19:11:17 +00:00
|
|
|
bNodeLink *link;
|
|
|
|
|
2013-11-07 09:02:29 +00:00
|
|
|
*r_snode = snode;
|
|
|
|
*r_select = NULL;
|
2012-08-01 19:11:17 +00:00
|
|
|
|
2013-11-07 09:02:29 +00:00
|
|
|
/* no unlucky accidents */
|
|
|
|
if (sa == NULL || sa->spacetype != SPACE_NODE)
|
|
|
|
return false;
|
2012-08-01 19:11:17 +00:00
|
|
|
|
2013-11-07 09:02:30 +00:00
|
|
|
if (!test) {
|
|
|
|
/* no need to look for a node */
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2012-08-01 19:11:17 +00:00
|
|
|
for (node = snode->edittree->nodes.first; node; node = node->next) {
|
|
|
|
if (node->flag & SELECT) {
|
2013-11-07 09:02:29 +00:00
|
|
|
if (select)
|
2012-08-01 19:11:17 +00:00
|
|
|
break;
|
|
|
|
else
|
2013-11-07 09:02:29 +00:00
|
|
|
select = node;
|
2012-08-01 19:11:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
/* only one selected */
|
2013-11-07 09:02:29 +00:00
|
|
|
if (node || select == NULL)
|
|
|
|
return false;
|
2012-08-01 19:11:17 +00:00
|
|
|
|
|
|
|
/* correct node */
|
2014-02-08 06:07:10 +11:00
|
|
|
if (BLI_listbase_is_empty(&select->inputs) || BLI_listbase_is_empty(&select->outputs))
|
2013-11-07 09:02:29 +00:00
|
|
|
return false;
|
2012-08-01 19:11:17 +00:00
|
|
|
|
|
|
|
/* test node for links */
|
|
|
|
for (link = snode->edittree->links.first; link; link = link->next) {
|
2013-03-18 16:34:57 +00:00
|
|
|
if (nodeLinkIsHidden(link))
|
|
|
|
continue;
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2013-11-07 09:02:29 +00:00
|
|
|
if (link->tonode == select || link->fromnode == select)
|
|
|
|
return false;
|
2012-08-01 19:11:17 +00:00
|
|
|
}
|
|
|
|
|
2013-11-07 09:02:29 +00:00
|
|
|
*r_select = select;
|
|
|
|
return true;
|
2012-08-01 19:11:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* test == 0, clear all intersect flags */
|
|
|
|
void ED_node_link_intersect_test(ScrArea *sa, int test)
|
|
|
|
{
|
|
|
|
bNode *select;
|
2013-11-07 09:02:29 +00:00
|
|
|
SpaceNode *snode;
|
2012-08-01 19:11:17 +00:00
|
|
|
bNodeLink *link, *selink = NULL;
|
2015-08-04 15:25:19 +02:00
|
|
|
float dist_best = FLT_MAX;
|
2012-08-01 19:11:17 +00:00
|
|
|
|
2013-11-07 09:02:30 +00:00
|
|
|
if (!ed_node_link_conditions(sa, test, &snode, &select)) return;
|
2012-08-01 19:11:17 +00:00
|
|
|
|
|
|
|
/* clear flags */
|
|
|
|
for (link = snode->edittree->links.first; link; link = link->next)
|
|
|
|
link->flag &= ~NODE_LINKFLAG_HILITE;
|
|
|
|
|
|
|
|
if (test == 0) return;
|
|
|
|
|
2015-08-04 15:25:19 +02:00
|
|
|
/* find link to select/highlight */
|
2012-08-01 19:11:17 +00:00
|
|
|
for (link = snode->edittree->links.first; link; link = link->next) {
|
2015-08-04 15:25:19 +02:00
|
|
|
float coord_array[NODE_LINK_RESOL + 1][2];
|
|
|
|
|
2013-03-18 16:34:57 +00:00
|
|
|
if (nodeLinkIsHidden(link))
|
|
|
|
continue;
|
2015-08-04 15:25:19 +02:00
|
|
|
|
|
|
|
if (node_link_bezier_points(NULL, NULL, link, coord_array, NODE_LINK_RESOL)) {
|
|
|
|
float dist = FLT_MAX;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
/* loop over link coords to find shortest dist to upper left node edge of a intersected line segment */
|
|
|
|
for (i = 0; i < NODE_LINK_RESOL; i++) {
|
|
|
|
/* check if the node rect intersetcts the line from this point to next one */
|
|
|
|
if (BLI_rctf_isect_segment(&select->totr, coord_array[i], coord_array[i + 1])) {
|
|
|
|
/* store the shortest distance to the upper left edge of all intersetctions found so far */
|
|
|
|
const float node_xy[] = {select->totr.xmin, select->totr.ymax};
|
|
|
|
|
|
|
|
/* to be precise coord_array should be clipped by select->totr,
|
|
|
|
* but not done since there's no real noticeable difference */
|
|
|
|
dist = min_ff(dist_squared_to_line_segment_v2(node_xy, coord_array[i], coord_array[i + 1]),
|
|
|
|
dist);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* we want the link with the shortest distance to node center */
|
|
|
|
if (dist < dist_best) {
|
|
|
|
dist_best = dist;
|
|
|
|
selink = link;
|
|
|
|
}
|
2012-08-01 19:11:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-04 15:25:19 +02:00
|
|
|
if (selink)
|
2012-08-01 19:11:17 +00:00
|
|
|
selink->flag |= NODE_LINKFLAG_HILITE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* assumes sockets in list */
|
|
|
|
static bNodeSocket *socket_best_match(ListBase *sockets)
|
|
|
|
{
|
|
|
|
bNodeSocket *sock;
|
|
|
|
int type, maxtype = 0;
|
|
|
|
|
|
|
|
/* find type range */
|
|
|
|
for (sock = sockets->first; sock; sock = sock->next)
|
2012-10-27 11:18:54 +00:00
|
|
|
maxtype = max_ii(sock->type, maxtype);
|
2012-08-01 19:11:17 +00:00
|
|
|
|
|
|
|
/* try all types, starting from 'highest' (i.e. colors, vectors, values) */
|
|
|
|
for (type = maxtype; type >= 0; --type) {
|
|
|
|
for (sock = sockets->first; sock; sock = sock->next) {
|
|
|
|
if (!nodeSocketIsHidden(sock) && type == sock->type) {
|
|
|
|
return sock;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* no visible sockets, unhide first of highest type */
|
|
|
|
for (type = maxtype; type >= 0; --type) {
|
|
|
|
for (sock = sockets->first; sock; sock = sock->next) {
|
|
|
|
if (type == sock->type) {
|
|
|
|
sock->flag &= ~SOCK_HIDDEN;
|
|
|
|
return sock;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2015-08-01 17:39:48 +02:00
|
|
|
static bool node_parents_offset_flag_enable_cb(bNode *parent, void *UNUSED(userdata))
|
|
|
|
{
|
|
|
|
/* NODE_TEST is used to flag nodes that shouldn't be offset (again) */
|
|
|
|
parent->flag |= NODE_TEST;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void node_offset_apply(bNode *node, const float offset_x)
|
|
|
|
{
|
|
|
|
/* NODE_TEST is used to flag nodes that shouldn't be offset (again) */
|
|
|
|
if ((node->flag & NODE_TEST) == 0) {
|
|
|
|
node->anim_init_locx = node->locx;
|
|
|
|
node->anim_ofsx = (offset_x / UI_DPI_FAC);
|
|
|
|
node->flag |= NODE_TEST;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void node_parent_offset_apply(NodeInsertOfsData *data, bNode *parent, const float offset_x)
|
|
|
|
{
|
|
|
|
bNode *node;
|
|
|
|
|
|
|
|
node_offset_apply(parent, offset_x);
|
|
|
|
|
|
|
|
/* flag all childs as offset to prevent them from being offset
|
|
|
|
* separately (they've already moved with the parent) */
|
|
|
|
for (node = data->ntree->nodes.first; node; node = node->next) {
|
|
|
|
if (nodeIsChildOf(parent, node)) {
|
|
|
|
/* NODE_TEST is used to flag nodes that shouldn't be offset (again) */
|
|
|
|
node->flag |= NODE_TEST;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#define NODE_INSOFS_ANIM_DURATION 0.25f
|
|
|
|
|
|
|
|
/**
|
2015-09-14 02:21:15 +10:00
|
|
|
* Callback that applies NodeInsertOfsData.offset_x to a node or its parent, similar
|
2015-08-01 17:39:48 +02:00
|
|
|
* to node_link_insert_offset_output_chain_cb below, but with slightly different logic
|
|
|
|
*/
|
|
|
|
static bool node_link_insert_offset_frame_chain_cb(
|
|
|
|
bNode *fromnode, bNode *tonode,
|
|
|
|
void *userdata,
|
|
|
|
const bool reversed)
|
|
|
|
{
|
|
|
|
NodeInsertOfsData *data = userdata;
|
|
|
|
bNode *ofs_node = reversed ? fromnode : tonode;
|
|
|
|
|
|
|
|
if (ofs_node->parent && ofs_node->parent != data->insert_parent) {
|
|
|
|
node_offset_apply(ofs_node->parent, data->offset_x);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
node_offset_apply(ofs_node, data->offset_x);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Applies NodeInsertOfsData.offset_x to all childs of \a parent
|
|
|
|
*/
|
|
|
|
static void node_link_insert_offset_frame_chains(
|
|
|
|
const bNodeTree *ntree, const bNode *parent,
|
|
|
|
NodeInsertOfsData *data,
|
|
|
|
const bool reversed)
|
|
|
|
{
|
|
|
|
bNode *node;
|
|
|
|
|
|
|
|
for (node = ntree->nodes.first; node; node = node->next) {
|
|
|
|
if (nodeIsChildOf(parent, node)) {
|
|
|
|
nodeChainIter(ntree, node, node_link_insert_offset_frame_chain_cb, data, reversed);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Callback that applies NodeInsertOfsData.offset_x to a node or its parent,
|
|
|
|
* considering the logic needed for offseting nodes after link insert
|
|
|
|
*/
|
|
|
|
static bool node_link_insert_offset_chain_cb(
|
|
|
|
bNode *fromnode, bNode *tonode,
|
|
|
|
void *userdata,
|
|
|
|
const bool reversed)
|
|
|
|
{
|
|
|
|
NodeInsertOfsData *data = userdata;
|
|
|
|
bNode *ofs_node = reversed ? fromnode : tonode;
|
|
|
|
|
|
|
|
if (data->insert_parent) {
|
|
|
|
if (ofs_node->parent && (ofs_node->parent->flag & NODE_TEST) == 0) {
|
|
|
|
node_parent_offset_apply(data, ofs_node->parent, data->offset_x);
|
|
|
|
node_link_insert_offset_frame_chains(data->ntree, ofs_node->parent, data, reversed);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
node_offset_apply(ofs_node, data->offset_x);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (nodeIsChildOf(data->insert_parent, ofs_node) == false) {
|
|
|
|
data->insert_parent = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (ofs_node->parent) {
|
|
|
|
bNode *node = nodeFindRootParent(ofs_node);
|
|
|
|
node_offset_apply(node, data->offset_x);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
node_offset_apply(ofs_node, data->offset_x);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void node_link_insert_offset_ntree(
|
|
|
|
NodeInsertOfsData *iofsd, ARegion *ar,
|
|
|
|
const int mouse_xy[2], const bool right_alignment)
|
|
|
|
{
|
|
|
|
bNodeTree *ntree = iofsd->ntree;
|
|
|
|
bNode *insert = iofsd->insert;
|
|
|
|
bNode *prev = iofsd->prev, *next = iofsd->next;
|
|
|
|
bNode *init_parent = insert->parent; /* store old insert->parent for restoring later */
|
|
|
|
rctf totr_insert;
|
|
|
|
|
|
|
|
const float min_margin = U.node_margin * UI_DPI_FAC;
|
|
|
|
const float width = NODE_WIDTH(insert);
|
|
|
|
const bool needs_alignment = (next->totr.xmin - prev->totr.xmax) < (width + (min_margin * 2.0f));
|
|
|
|
|
|
|
|
float margin = width;
|
|
|
|
float dist, addval;
|
|
|
|
|
|
|
|
|
|
|
|
/* NODE_TEST will be used later, so disable for all nodes */
|
|
|
|
ntreeNodeFlagSet(ntree, NODE_TEST, false);
|
|
|
|
|
|
|
|
/* insert->totr isn't updated yet, so totr_insert is used to get the correct worldspace coords */
|
|
|
|
node_to_updated_rect(insert, &totr_insert);
|
|
|
|
|
|
|
|
/* frame attachement was't handled yet so we search the frame that the node will be attached to later */
|
|
|
|
insert->parent = node_find_frame_to_attach(ar, ntree, mouse_xy);
|
|
|
|
|
|
|
|
/* this makes sure nodes are also correctly offset when inserting a node on top of a frame
|
|
|
|
* without actually making it a part of the frame (because mouse isn't intersecting it)
|
|
|
|
* - logic here is similar to node_find_frame_to_attach */
|
|
|
|
if (!insert->parent ||
|
|
|
|
(prev->parent && (prev->parent == next->parent) && (prev->parent != insert->parent)))
|
|
|
|
{
|
|
|
|
bNode *frame;
|
|
|
|
rctf totr_frame;
|
|
|
|
|
|
|
|
/* check nodes front to back */
|
|
|
|
for (frame = ntree->nodes.last; frame; frame = frame->prev) {
|
|
|
|
/* skip selected, those are the nodes we want to attach */
|
|
|
|
if ((frame->type != NODE_FRAME) || (frame->flag & NODE_SELECT))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* for some reason frame y coords aren't correct yet */
|
|
|
|
node_to_updated_rect(frame, &totr_frame);
|
|
|
|
|
|
|
|
if (BLI_rctf_isect_x(&totr_frame, totr_insert.xmin) &&
|
|
|
|
BLI_rctf_isect_x(&totr_frame, totr_insert.xmax))
|
|
|
|
{
|
|
|
|
if (BLI_rctf_isect_y(&totr_frame, totr_insert.ymin) ||
|
|
|
|
BLI_rctf_isect_y(&totr_frame, totr_insert.ymax))
|
|
|
|
{
|
|
|
|
/* frame isn't insert->parent actually, but this is needed to make offsetting
|
|
|
|
* nodes work correctly for above checked cases (it is restored later) */
|
|
|
|
insert->parent = frame;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* *** ensure offset at the left (or right for right_alignment case) of insert_node *** */
|
|
|
|
|
|
|
|
dist = right_alignment ? totr_insert.xmin - prev->totr.xmax : next->totr.xmin - totr_insert.xmax;
|
|
|
|
/* distance between insert_node and prev is smaller than min margin */
|
|
|
|
if (dist < min_margin) {
|
|
|
|
addval = (min_margin - dist) * (right_alignment ? 1.0f : -1.0f);
|
|
|
|
|
|
|
|
node_offset_apply(insert, addval);
|
|
|
|
|
|
|
|
totr_insert.xmin += addval;
|
|
|
|
totr_insert.xmax += addval;
|
|
|
|
margin += min_margin;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* *** ensure offset at the right (or left for right_alignment case) of insert_node *** */
|
|
|
|
|
|
|
|
dist = right_alignment ? next->totr.xmin - totr_insert.xmax : totr_insert.xmin - prev->totr.xmax;
|
|
|
|
/* distance between insert_node and next is smaller than min margin */
|
|
|
|
if (dist < min_margin) {
|
|
|
|
addval = (min_margin - dist) * (right_alignment ? 1.0f : -1.0f);
|
|
|
|
if (needs_alignment) {
|
|
|
|
bNode *offs_node = right_alignment ? next : prev;
|
|
|
|
if (!offs_node->parent ||
|
|
|
|
offs_node->parent == insert->parent ||
|
|
|
|
nodeIsChildOf(offs_node->parent, insert))
|
|
|
|
{
|
|
|
|
node_offset_apply(offs_node, addval);
|
|
|
|
}
|
|
|
|
else if (!insert->parent && offs_node->parent) {
|
2015-09-17 15:48:55 +02:00
|
|
|
node_offset_apply(nodeFindRootParent(offs_node), addval);
|
2015-08-01 17:39:48 +02:00
|
|
|
}
|
|
|
|
margin = addval;
|
|
|
|
}
|
|
|
|
/* enough room is available, but we want to ensure the min margin at the right */
|
|
|
|
else {
|
|
|
|
/* offset inserted node so that min margin is kept at the right */
|
|
|
|
node_offset_apply(insert, -addval);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (needs_alignment) {
|
|
|
|
iofsd->insert_parent = insert->parent;
|
|
|
|
iofsd->offset_x = margin;
|
|
|
|
|
|
|
|
/* flag all parents of insert as offset to prevent them from being offset */
|
|
|
|
nodeParentsIter(insert, node_parents_offset_flag_enable_cb, NULL);
|
|
|
|
/* iterate over entire chain and apply offsets */
|
|
|
|
nodeChainIter(ntree, right_alignment ? next : prev, node_link_insert_offset_chain_cb, iofsd, !right_alignment);
|
|
|
|
}
|
|
|
|
|
|
|
|
insert->parent = init_parent;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Modal handler for insert offset animation
|
|
|
|
*/
|
|
|
|
static int node_insert_offset_modal(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
|
|
|
|
{
|
|
|
|
SpaceNode *snode = CTX_wm_space_node(C);
|
|
|
|
NodeInsertOfsData *iofsd = snode->iofsd;
|
|
|
|
bNode *node;
|
2015-08-05 23:46:49 +10:00
|
|
|
float duration;
|
2015-09-21 00:55:37 +02:00
|
|
|
bool redraw = false;
|
2015-08-01 17:39:48 +02:00
|
|
|
|
2016-07-22 04:17:24 +10:00
|
|
|
if (!snode || event->type != TIMER || iofsd == NULL || iofsd->anim_timer != event->customdata)
|
2015-08-01 17:39:48 +02:00
|
|
|
return OPERATOR_PASS_THROUGH;
|
|
|
|
|
2015-08-05 23:46:49 +10:00
|
|
|
duration = (float)iofsd->anim_timer->duration;
|
2015-09-21 00:55:37 +02:00
|
|
|
|
|
|
|
/* handle animation - do this before possibly aborting due to duration, since
|
|
|
|
* main thread might be so busy that node hasn't reached final position yet */
|
|
|
|
for (node = snode->edittree->nodes.first; node; node = node->next) {
|
|
|
|
if (UNLIKELY(node->anim_ofsx)) {
|
|
|
|
const float endval = node->anim_init_locx + node->anim_ofsx;
|
2015-09-25 17:08:33 +02:00
|
|
|
if (IS_EQF(node->locx, endval) == false) {
|
2015-09-21 00:55:37 +02:00
|
|
|
node->locx = BLI_easing_cubic_ease_in_out(duration, node->anim_init_locx, node->anim_ofsx,
|
|
|
|
NODE_INSOFS_ANIM_DURATION);
|
2015-09-25 17:08:33 +02:00
|
|
|
if (node->anim_ofsx < 0) {
|
|
|
|
CLAMP_MIN(node->locx, endval);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
CLAMP_MAX(node->locx, endval);
|
|
|
|
}
|
2015-09-21 00:55:37 +02:00
|
|
|
redraw = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (redraw) {
|
|
|
|
ED_region_tag_redraw(CTX_wm_region(C));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* end timer + free insert offset data */
|
2015-08-01 17:39:48 +02:00
|
|
|
if (duration > NODE_INSOFS_ANIM_DURATION) {
|
|
|
|
WM_event_remove_timer(CTX_wm_manager(C), NULL, iofsd->anim_timer);
|
|
|
|
|
|
|
|
for (node = snode->edittree->nodes.first; node; node = node->next) {
|
|
|
|
node->anim_init_locx = node->anim_ofsx = 0.0f;
|
|
|
|
}
|
|
|
|
|
|
|
|
snode->iofsd = NULL;
|
|
|
|
MEM_freeN(iofsd);
|
|
|
|
|
|
|
|
return (OPERATOR_FINISHED | OPERATOR_PASS_THROUGH);
|
|
|
|
}
|
|
|
|
|
|
|
|
return OPERATOR_RUNNING_MODAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
#undef NODE_INSOFS_ANIM_DURATION
|
|
|
|
|
|
|
|
static int node_insert_offset_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
|
|
{
|
|
|
|
const SpaceNode *snode = CTX_wm_space_node(C);
|
|
|
|
NodeInsertOfsData *iofsd = snode->iofsd;
|
|
|
|
|
|
|
|
if (!iofsd || !iofsd->insert)
|
|
|
|
return OPERATOR_CANCELLED;
|
|
|
|
|
|
|
|
BLI_assert((snode->flag & SNODE_SKIP_INSOFFSET) == 0);
|
|
|
|
|
|
|
|
iofsd->ntree = snode->edittree;
|
|
|
|
iofsd->anim_timer = WM_event_add_timer(CTX_wm_manager(C), CTX_wm_window(C), TIMER, 0.02);
|
|
|
|
|
|
|
|
node_link_insert_offset_ntree(
|
|
|
|
iofsd, CTX_wm_region(C),
|
|
|
|
event->mval, (snode->insert_ofs_dir == SNODE_INSERTOFS_DIR_RIGHT));
|
|
|
|
|
|
|
|
/* add temp handler */
|
|
|
|
WM_event_add_modal_handler(C, op);
|
|
|
|
|
|
|
|
return OPERATOR_RUNNING_MODAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void NODE_OT_insert_offset(wmOperatorType *ot)
|
|
|
|
{
|
|
|
|
/* identifiers */
|
|
|
|
ot->name = "Insert Offset";
|
|
|
|
ot->description = "Automatically offset nodes on insertion";
|
|
|
|
ot->idname = "NODE_OT_insert_offset";
|
|
|
|
|
|
|
|
/* callbacks */
|
|
|
|
ot->invoke = node_insert_offset_invoke;
|
|
|
|
ot->modal = node_insert_offset_modal;
|
|
|
|
ot->poll = ED_operator_node_editable;
|
|
|
|
|
|
|
|
/* flags */
|
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING;
|
|
|
|
}
|
|
|
|
|
2012-08-01 19:11:17 +00:00
|
|
|
/* assumes link with NODE_LINKFLAG_HILITE set */
|
|
|
|
void ED_node_link_insert(ScrArea *sa)
|
|
|
|
{
|
|
|
|
bNode *node, *select;
|
2013-11-07 09:02:29 +00:00
|
|
|
SpaceNode *snode;
|
2012-08-01 19:11:17 +00:00
|
|
|
bNodeLink *link;
|
|
|
|
bNodeSocket *sockto;
|
|
|
|
|
2013-11-07 09:02:30 +00:00
|
|
|
if (!ed_node_link_conditions(sa, true, &snode, &select)) return;
|
2012-08-01 19:11:17 +00:00
|
|
|
|
|
|
|
/* get the link */
|
|
|
|
for (link = snode->edittree->links.first; link; link = link->next)
|
|
|
|
if (link->flag & NODE_LINKFLAG_HILITE)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (link) {
|
2013-04-02 11:59:27 +00:00
|
|
|
bNodeSocket *best_input = socket_best_match(&select->inputs);
|
|
|
|
bNodeSocket *best_output = socket_best_match(&select->outputs);
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2013-04-02 11:59:27 +00:00
|
|
|
if (best_input && best_output) {
|
|
|
|
node = link->tonode;
|
|
|
|
sockto = link->tosock;
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2013-04-02 11:59:27 +00:00
|
|
|
link->tonode = select;
|
|
|
|
link->tosock = best_input;
|
Node callback for handling link insertion and swapping of occupied inputs.
Nodes have a feature for moving existing links to unoccupied sockets when connecting
to an already used input. This is based on the standard legacy socket types (value/float,
vector, color/rgba) and works reasonably well for shader, compositor and texture nodes.
For new pynode systems, however, the hardcoded nature of that feature has major drawbacks:
* It does not take different type systems into account, leading to meaningless connections
when sockets are swapped and making the feature useless or outright debilitating.
* Advanced socket behaviors would be possible with a registerable callback, e.g. creating
extensible input lists that move existing connections down to make room for a new link.
Now any handling of new links is done via the 'insert_links' callback, which can also be
registered through the RNA API. For the legacy shader/compo/tex nodes the behavior is the
same, using a C callback.
Note on the 'use_swap' flag: this has been removed because it was meaningless anyway:
It was disabled only for the insert-node-on-link feature, which works only for
completely unconnected nodes anyway, so there would be nothing to swap in the first place.
2015-12-03 12:51:29 +01:00
|
|
|
node_remove_extra_links(snode, link);
|
2013-04-02 11:59:27 +00:00
|
|
|
link->flag &= ~NODE_LINKFLAG_HILITE;
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2013-04-02 11:59:27 +00:00
|
|
|
nodeAddLink(snode->edittree, select, best_output, node, sockto);
|
2018-06-04 09:31:30 +02:00
|
|
|
|
2015-08-01 17:39:48 +02:00
|
|
|
/* set up insert offset data, it needs stuff from here */
|
|
|
|
if ((snode->flag & SNODE_SKIP_INSOFFSET) == 0) {
|
|
|
|
NodeInsertOfsData *iofsd = MEM_callocN(sizeof(NodeInsertOfsData), __func__);
|
|
|
|
|
|
|
|
iofsd->insert = select;
|
|
|
|
iofsd->prev = link->fromnode;
|
|
|
|
iofsd->next = node;
|
|
|
|
|
|
|
|
snode->iofsd = iofsd;
|
|
|
|
}
|
|
|
|
|
2013-05-07 15:28:42 +00:00
|
|
|
ntreeUpdateTree(G.main, snode->edittree); /* needed for pointers */
|
2013-04-02 11:59:27 +00:00
|
|
|
snode_update(snode, select);
|
|
|
|
ED_node_tag_update_id(snode->id);
|
|
|
|
}
|
2012-08-01 19:11:17 +00:00
|
|
|
}
|
|
|
|
}
|