Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
authorJuho Vepsalainen <bebraw@gmail.com>2007-12-27 13:17:33 +0300
committerJuho Vepsalainen <bebraw@gmail.com>2007-12-27 13:17:33 +0300
commitd06dc00af908584a02291cffb8704ac61caa12df (patch)
tree8f08c01b2a126bcf41d1e33be40f41f587582a87 /source
parent0dc38a5bd93bf3197083db424c7cd34d402bc9ad (diff)
Toggle links tool for Node Editor
This commit adds a new tool, Toggle Links, to the node editor. This tool allows the user to toggle the status (linked/not linked) between desired sockets. The tool can be used either by using the f key or the menus. This functionality is analogue to one found in object editing modes except for its additional toggle functionality. To use this tool, the user has to first select an input and an output socket. Selecting is done by clicking with right mouse button on a socket. After the tool has been invoked, the link between those two sockets is toggled. The result may vary based on existing linkage. There can be only one input and one output selected at maximum in a node tree. This means that if the user selects a socket while one of the same type is already selected, the old one will be deselected. The tool complements the current way of connecting nodes. One possible use for it is to use it to review output of nodes by using a viewer node. Just select wanted input socket of a viewer node, set it visible and use selection of an output socket in conjuction with f key to show the output in the viewer node. Select another output and hit f to see its output and so on.
Diffstat (limited to 'source')
-rw-r--r--source/blender/blenkernel/BKE_node.h3
-rw-r--r--source/blender/blenkernel/intern/node.c57
-rw-r--r--source/blender/include/BSE_node.h1
-rw-r--r--source/blender/makesdna/DNA_node_types.h7
-rw-r--r--source/blender/src/drawnode.c73
-rw-r--r--source/blender/src/editnode.c136
-rw-r--r--source/blender/src/header_node.c7
-rw-r--r--source/blender/src/toolbox.c2
8 files changed, 222 insertions, 64 deletions
diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h
index 2ffa1d205da..b9df1e45bae 100644
--- a/source/blender/blenkernel/BKE_node.h
+++ b/source/blender/blenkernel/BKE_node.h
@@ -143,6 +143,7 @@ void nodeVerifyType(struct bNodeTree *ntree, struct bNode *node);
void nodeAddToPreview(struct bNode *, float *, int, int);
+void nodeUnlinkNode(struct bNodeTree *ntree, struct bNode *node);
struct bNode *nodeAddNodeType(struct bNodeTree *ntree, int type, struct bNodeTree *ngroup);
void nodeFreeNode(struct bNodeTree *ntree, struct bNode *node);
struct bNode *nodeCopyNode(struct bNodeTree *ntree, struct bNode *node);
@@ -150,6 +151,8 @@ struct bNode *nodeCopyNode(struct bNodeTree *ntree, struct bNode *node);
struct bNodeLink *nodeAddLink(struct bNodeTree *ntree, struct bNode *fromnode, struct bNodeSocket *fromsock, struct bNode *tonode, struct bNodeSocket *tosock);
void nodeRemLink(struct bNodeTree *ntree, struct bNodeLink *link);
+int nodeFindNode(struct bNodeTree *ntree, struct bNodeSocket *sock, struct bNode **nodep, int *sockindex);
+
struct bNodeLink *nodeFindLink(struct bNodeTree *ntree, struct bNodeSocket *from, struct bNodeSocket *to);
int nodeCountSocketLinks(struct bNodeTree *ntree, struct bNodeSocket *sock);
diff --git a/source/blender/blenkernel/intern/node.c b/source/blender/blenkernel/intern/node.c
index 651115b7180..4bcacae9fc5 100644
--- a/source/blender/blenkernel/intern/node.c
+++ b/source/blender/blenkernel/intern/node.c
@@ -483,6 +483,20 @@ bNode *nodeMakeGroupFromSelected(bNodeTree *ntree)
BLI_addtail(&ngroup->nodes, node);
node->locx-= 0.5f*(min[0]+max[0]);
node->locy-= 0.5f*(min[1]+max[1]);
+
+ /* set selin and selout of the nodetree */
+ for(sock= node->inputs.first; sock; sock= sock->next) {
+ if(sock->flag & SOCK_SEL) {
+ ngroup->selin= sock;
+ break;
+ }
+ }
+ for(sock= node->outputs.first; sock; sock= sock->next) {
+ if(sock->flag & SOCK_SEL) {
+ ngroup->selout= sock;
+ break;
+ }
+ }
}
}
@@ -653,7 +667,8 @@ void nodeGroupSocketUseFlags(bNodeTree *ngroup)
}
}
-static void find_node_with_socket(bNodeTree *ntree, bNodeSocket *sock, bNode **nodep, int *sockindex)
+/* finds a node based on given socket */
+int nodeFindNode(bNodeTree *ntree, bNodeSocket *sock, bNode **nodep, int *sockindex)
{
bNode *node;
bNodeSocket *tsock;
@@ -671,13 +686,15 @@ static void find_node_with_socket(bNodeTree *ntree, bNodeSocket *sock, bNode **n
if(tsock)
break;
}
+
if(node) {
*nodep= node;
- *sockindex= index;
- }
- else {
- *nodep= NULL;
+ if(sockindex) *sockindex= index;
+ return 1;
}
+
+ *nodep= NULL;
+ return 0;
}
/* returns 1 if its OK */
@@ -717,7 +734,7 @@ int nodeGroupUnGroup(bNodeTree *ntree, bNode *gnode)
for(link= ntree->links.first; link; link= link->next) {
if(link->tonode==gnode) {
/* link->tosock->tosock is on the node we look for */
- find_node_with_socket(ngroup, link->tosock->tosock, &nextn, &index);
+ nodeFindNode(ngroup, link->tosock->tosock, &nextn, &index);
if(nextn==NULL) printf("wrong stuff!\n");
else if(nextn->new_node==NULL) printf("wrong stuff too!\n");
else {
@@ -727,7 +744,7 @@ int nodeGroupUnGroup(bNodeTree *ntree, bNode *gnode)
}
else if(link->fromnode==gnode) {
/* link->fromsock->tosock is on the node we look for */
- find_node_with_socket(ngroup, link->fromsock->tosock, &nextn, &index);
+ nodeFindNode(ngroup, link->fromsock->tosock, &nextn, &index);
if(nextn==NULL) printf("1 wrong stuff!\n");
else if(nextn->new_node==NULL) printf("1 wrong stuff too!\n");
else {
@@ -898,6 +915,28 @@ bNodeTree *ntreeCopyTree(bNodeTree *ntree, int internal_select)
nnode->flag |= NODE_SELECT;
}
node->flag &= ~NODE_ACTIVE;
+
+ /* deselect original sockets */
+ for(sock= node->inputs.first; sock; sock= sock->next) {
+ if(sock->flag & SOCK_SEL) sock->flag&= ~SOCK_SEL;
+ }
+ for(sock= node->outputs.first; sock; sock= sock->next) {
+ if(sock->flag & SOCK_SEL) sock->flag&= ~SOCK_SEL;
+ }
+
+ /* set tree selin and selout to new sockets */
+ for(sock= nnode->inputs.first; sock; sock= sock->next) {
+ if(sock->flag & SOCK_SEL) {
+ ntree->selin= sock;
+ break;
+ }
+ }
+ for(sock= nnode->outputs.first; sock; sock= sock->next) {
+ if(sock->flag & SOCK_SEL) {
+ ntree->selout= sock;
+ break;
+ }
+ }
}
if(node==last) break;
}
@@ -941,7 +980,7 @@ bNodeTree *ntreeCopyTree(bNodeTree *ntree, int internal_select)
/* ************** Free stuff ********** */
/* goes over entire tree */
-static void node_unlink_node(bNodeTree *ntree, bNode *node)
+void nodeUnlinkNode(bNodeTree *ntree, bNode *node)
{
bNodeLink *link, *next;
bNodeSocket *sock;
@@ -985,7 +1024,7 @@ static void composit_free_node_cache(bNode *node)
void nodeFreeNode(bNodeTree *ntree, bNode *node)
{
- node_unlink_node(ntree, node);
+ nodeUnlinkNode(ntree, node);
BLI_remlink(&ntree->nodes, node);
/* since it is called while free database, node->id is undefined */
diff --git a/source/blender/include/BSE_node.h b/source/blender/include/BSE_node.h
index 171a277e8e6..5862918953c 100644
--- a/source/blender/include/BSE_node.h
+++ b/source/blender/include/BSE_node.h
@@ -72,6 +72,7 @@ void snode_make_group_editable(struct SpaceNode *snode, struct bNode *gnode);
void node_hide(struct SpaceNode *snode);
void node_read_renderlayers(struct SpaceNode *snode);
void clear_scene_in_nodes(struct Scene *sce);
+void node_toggle_link(struct SpaceNode *snode);
void node_transform_ext(int mode, int unused);
void node_shader_default(struct Material *ma);
diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h
index 5f20939fc23..b5084c41884 100644
--- a/source/blender/makesdna/DNA_node_types.h
+++ b/source/blender/makesdna/DNA_node_types.h
@@ -92,7 +92,8 @@ typedef struct bNodeSocket {
#define SOCK_IN_USE 4
/* unavailable is for dynamic sockets */
#define SOCK_UNAVAIL 8
-
+ /* flag for selection status */
+#define SOCK_SEL 16
#
#
typedef struct bNodePreview {
@@ -167,6 +168,10 @@ typedef struct bNodeTree {
ListBase alltypes; /* type definitions */
struct bNodeType *owntype; /* for groups or dynamic trees, no read/write */
+ /* selected input/output socket */
+ bNodeSocket *selin;
+ bNodeSocket *selout;
+
/* callbacks */
void (*timecursor)(int nr);
void (*stats_draw)(char *str);
diff --git a/source/blender/src/drawnode.c b/source/blender/src/drawnode.c
index b0c55d104f3..cc65b10c8e7 100644
--- a/source/blender/src/drawnode.c
+++ b/source/blender/src/drawnode.c
@@ -1988,7 +1988,8 @@ static void draw_nodespace_back(ScrArea *sa, SpaceNode *snode)
}
/* nice AA filled circle */
-static void socket_circle_draw(float x, float y, float size, int type, int select)
+/* this might have some more generic use */
+static void circle_draw(float x, float y, float size, int type, int col[3])
{
/* 16 values of sin function */
static float si[16] = {
@@ -2006,28 +2007,7 @@ static void socket_circle_draw(float x, float y, float size, int type, int selec
};
int a;
- if(select==0) {
- if(type==-1)
- glColor3ub(0, 0, 0);
- else if(type==SOCK_VALUE)
- glColor3ub(160, 160, 160);
- else if(type==SOCK_VECTOR)
- glColor3ub(100, 100, 200);
- else if(type==SOCK_RGBA)
- glColor3ub(200, 200, 40);
- else
- glColor3ub(100, 200, 100);
- }
- else {
- if(type==SOCK_VALUE)
- glColor3ub(200, 200, 200);
- else if(type==SOCK_VECTOR)
- glColor3ub(140, 140, 240);
- else if(type==SOCK_RGBA)
- glColor3ub(240, 240, 100);
- else
- glColor3ub(140, 240, 140);
- }
+ glColor3ub(col[0], col[1], col[2]);
glBegin(GL_POLYGON);
for(a=0; a<16; a++)
@@ -2045,6 +2025,41 @@ static void socket_circle_draw(float x, float y, float size, int type, int selec
glDisable(GL_BLEND);
}
+static void socket_circle_draw(bNodeSocket *sock, float size)
+{
+ int col[3];
+
+ /* choose color based on sock flags */
+ if(sock->flag & SELECT) {
+ if(sock->flag & SOCK_SEL) {
+ col[0]= 240; col[1]= 200; col[2]= 40;}
+ else if(sock->type==SOCK_VALUE) {
+ col[0]= 200; col[1]= 200; col[2]= 200;}
+ else if(sock->type==SOCK_VECTOR) {
+ col[0]= 140; col[1]= 140; col[2]= 240;}
+ else if(sock->type==SOCK_RGBA) {
+ col[0]= 240; col[1]= 240; col[2]= 100;}
+ else {
+ col[0]= 140; col[1]= 240; col[2]= 140;}
+ }
+ else if(sock->flag & SOCK_SEL) {
+ col[0]= 200; col[1]= 160; col[2]= 0;}
+ else {
+ if(sock->type==-1) {
+ col[0]= 0; col[1]= 0; col[2]= 0;}
+ else if(sock->type==SOCK_VALUE) {
+ col[0]= 160; col[1]= 160; col[2]= 160;}
+ else if(sock->type==SOCK_VECTOR) {
+ col[0]= 100; col[1]= 100; col[2]= 200;}
+ else if(sock->type==SOCK_RGBA) {
+ col[0]= 200; col[1]= 200; col[2]= 40;}
+ else {
+ col[0]= 100; col[1]= 200; col[2]= 100;}
+ }
+
+ circle_draw(sock->locx, sock->locy, size, sock->type, col);
+}
+
/* not a callback */
static void node_draw_preview(bNodePreview *preview, rctf *prv)
{
@@ -2459,7 +2474,7 @@ static void node_draw_basis(ScrArea *sa, SpaceNode *snode, bNode *node)
/* socket inputs, buttons */
for(sock= node->inputs.first; sock; sock= sock->next) {
if(!(sock->flag & (SOCK_HIDDEN|SOCK_UNAVAIL))) {
- socket_circle_draw(sock->locx, sock->locy, NODE_SOCKSIZE, sock->type, sock->flag & SELECT);
+ socket_circle_draw(sock, NODE_SOCKSIZE);
if(block && sock->link==NULL) {
float *butpoin= sock->ns.vec;
@@ -2501,7 +2516,7 @@ static void node_draw_basis(ScrArea *sa, SpaceNode *snode, bNode *node)
/* socket outputs */
for(sock= node->outputs.first; sock; sock= sock->next) {
if(!(sock->flag & (SOCK_HIDDEN|SOCK_UNAVAIL))) {
- socket_circle_draw(sock->locx, sock->locy, NODE_SOCKSIZE, sock->type, sock->flag & SELECT);
+ socket_circle_draw(sock, NODE_SOCKSIZE);
BIF_ThemeColor(TH_TEXT);
ofs= 0;
@@ -2589,12 +2604,12 @@ void node_draw_hidden(SpaceNode *snode, bNode *node)
/* sockets */
for(sock= node->inputs.first; sock; sock= sock->next) {
if(!(sock->flag & (SOCK_HIDDEN|SOCK_UNAVAIL)))
- socket_circle_draw(sock->locx, sock->locy, NODE_SOCKSIZE, sock->type, sock->flag & SELECT);
+ socket_circle_draw(sock, NODE_SOCKSIZE);
}
for(sock= node->outputs.first; sock; sock= sock->next) {
if(!(sock->flag & (SOCK_HIDDEN|SOCK_UNAVAIL)))
- socket_circle_draw(sock->locx, sock->locy, NODE_SOCKSIZE, sock->type, sock->flag & SELECT);
+ socket_circle_draw(sock, NODE_SOCKSIZE);
}
}
@@ -2805,10 +2820,10 @@ static void node_draw_group(ScrArea *sa, SpaceNode *snode, bNode *gnode)
/* group sockets */
for(sock= gnode->inputs.first; sock; sock= sock->next)
if(!(sock->flag & (SOCK_HIDDEN|SOCK_UNAVAIL)))
- socket_circle_draw(sock->locx, sock->locy, NODE_SOCKSIZE, sock->type, sock->flag & SELECT);
+ socket_circle_draw(sock, NODE_SOCKSIZE);
for(sock= gnode->outputs.first; sock; sock= sock->next)
if(!(sock->flag & (SOCK_HIDDEN|SOCK_UNAVAIL)))
- socket_circle_draw(sock->locx, sock->locy, NODE_SOCKSIZE, sock->type, sock->flag & SELECT);
+ socket_circle_draw(sock, NODE_SOCKSIZE);
/* and finally the whole tree */
node_draw_nodetree(sa, snode, ngroup);
diff --git a/source/blender/src/editnode.c b/source/blender/src/editnode.c
index a14b0ccc389..d035908994c 100644
--- a/source/blender/src/editnode.c
+++ b/source/blender/src/editnode.c
@@ -830,6 +830,23 @@ static void snode_bg_viewmove(SpaceNode *snode)
window_set_cursor(win, oldcursor);
}
+static void reset_sel_socket(SpaceNode *snode, int in_out)
+{
+ bNode *node;
+ bNodeSocket *sock;
+
+ for(node= snode->edittree->nodes.first; node; node= node->next) {
+ if(in_out & SOCK_IN) {
+ for(sock= node->inputs.first; sock; sock= sock->next)
+ if(sock->flag & SOCK_SEL) sock->flag&= ~SOCK_SEL;
+ }
+ if(in_out & SOCK_OUT) {
+ for(sock= node->outputs.first; sock; sock= sock->next)
+ if(sock->flag & SOCK_SEL) sock->flag&= ~SOCK_SEL;
+ }
+ }
+}
+
/* checks mouse position, and returns found node/socket */
/* type is SOCK_IN and/or SOCK_OUT */
static int find_indicated_socket(SpaceNode *snode, bNode **nodep, bNodeSocket **sockp, int in_out)
@@ -1568,6 +1585,33 @@ static void node_insert_convertor(SpaceNode *snode, bNodeLink *link)
#endif
+static void node_remove_extra_links(SpaceNode *snode, bNodeSocket *tsock, bNodeLink *link)
+{
+ bNodeLink *tlink;
+ bNodeSocket *sock;
+
+ if(tsock && nodeCountSocketLinks(snode->edittree, link->tosock) > tsock->limit) {
+
+ for(tlink= snode->edittree->links.first; tlink; tlink= tlink->next) {
+ if(link!=tlink && tlink->tosock==link->tosock)
+ break;
+ }
+ if(tlink) {
+ /* is there a free input socket with same type? */
+ for(sock= tlink->tonode->inputs.first; sock; sock= sock->next) {
+ if(sock->type==tlink->fromsock->type)
+ if(nodeCountSocketLinks(snode->edittree, sock) < sock->limit)
+ break;
+ }
+ if(sock)
+ tlink->tosock= sock;
+ else {
+ nodeRemLink(snode->edittree, tlink);
+ }
+ }
+ }
+}
+
/* loop that adds a nodelink, called by function below */
/* in_out = starting socket */
static int node_add_link_drag(SpaceNode *snode, bNode *node, bNodeSocket *sock, int in_out)
@@ -1638,35 +1682,12 @@ static int node_add_link_drag(SpaceNode *snode, bNode *node, bNodeSocket *sock,
nodeRemLink(snode->edittree, link);
}
else {
- bNodeLink *tlink;
-
/* send changed events for original tonode and new */
if(link->tonode)
NodeTagChanged(snode->edittree, link->tonode);
/* we might need to remove a link */
- if(in_out==SOCK_OUT) {
- if(tsock && nodeCountSocketLinks(snode->edittree, link->tosock) > tsock->limit) {
-
- for(tlink= snode->edittree->links.first; tlink; tlink= tlink->next) {
- if(link!=tlink && tlink->tosock==link->tosock)
- break;
- }
- if(tlink) {
- /* is there a free input socket with same type? */
- for(tsock= tlink->tonode->inputs.first; tsock; tsock= tsock->next) {
- if(tsock->type==tlink->fromsock->type)
- if(nodeCountSocketLinks(snode->edittree, tsock) < tsock->limit)
- break;
- }
- if(tsock)
- tlink->tosock= tsock;
- else {
- nodeRemLink(snode->edittree, tlink);
- }
- }
- }
- }
+ if(in_out==SOCK_OUT) node_remove_extra_links(snode, tsock, link);
}
ntreeSolveOrder(snode->edittree);
@@ -1733,10 +1754,18 @@ static int node_add_link(SpaceNode *snode)
void node_delete(SpaceNode *snode)
{
bNode *node, *next;
+ bNodeSocket *sock;
for(node= snode->edittree->nodes.first; node; node= next) {
next= node->next;
if(node->flag & SELECT) {
+ /* set selin and selout NULL if the sockets belong to a node to be deleted */
+ for(sock= node->inputs.first; sock; sock= sock->next)
+ if(snode->edittree->selin == sock) snode->edittree->selin= NULL;
+
+ for(sock= node->outputs.first; sock; sock= sock->next)
+ if(snode->edittree->selout == sock) snode->edittree->selout= NULL;
+
/* check id user here, nodeFreeNode is called for free dbase too */
if(node->id)
node->id->us--;
@@ -1826,6 +1855,36 @@ void node_select_linked(SpaceNode *snode, int out)
allqueue(REDRAWNODE, 1);
}
+void node_toggle_link(SpaceNode *snode)
+{
+ bNode *fromnode, *tonode;
+ bNodeLink *remlink, *link;
+ bNodeSocket *outsock= snode->edittree->selout;
+ bNodeSocket *insock= snode->edittree->selin;
+
+ if(!insock || !outsock) return;
+
+ remlink= nodeFindLink(snode->edittree, outsock, insock);
+
+ if(remlink) nodeRemLink(snode->edittree, remlink);
+ else {
+ if(nodeFindNode(snode->edittree, outsock, &fromnode, NULL) &&
+ nodeFindNode(snode->edittree, insock, &tonode, NULL)) {
+ link= nodeAddLink(snode->edittree, fromnode, outsock, tonode, insock);
+ NodeTagChanged(snode->edittree, tonode);
+ node_remove_extra_links(snode, insock, link);
+ }
+ else return;
+ }
+
+ ntreeSolveOrder(snode->edittree);
+ snode_verify_groups(snode);
+ snode_handle_recalc(snode);
+
+ allqueue(REDRAWNODE, 0);
+ BIF_undo_push("Toggle Link");
+}
+
static void node_border_link_delete(SpaceNode *snode)
{
rcti rect;
@@ -2091,6 +2150,8 @@ static int node_uiDoBlocks(ScrArea *sa, short event)
void winqreadnodespace(ScrArea *sa, void *spacedata, BWinEvent *evt)
{
SpaceNode *snode= spacedata;
+ bNode *actnode;
+ bNodeSocket *actsock;
unsigned short event= evt->event;
short val= evt->val, doredraw=0, fromlib= 0;
@@ -2118,7 +2179,29 @@ void winqreadnodespace(ScrArea *sa, void *spacedata, BWinEvent *evt)
break;
case RIGHTMOUSE:
- if(!node_mouse_select(snode, event))
+ if(find_indicated_socket(snode, &actnode, &actsock, SOCK_IN)) {
+ if(actsock->flag & SOCK_SEL) {
+ snode->edittree->selin= NULL;
+ actsock->flag&= ~SOCK_SEL;
+ }
+ else {
+ snode->edittree->selin= actsock;
+ reset_sel_socket(snode, SOCK_IN);
+ actsock->flag|= SOCK_SEL;
+ }
+ }
+ else if(find_indicated_socket(snode, &actnode, &actsock, SOCK_OUT)) {
+ if(actsock->flag & SOCK_SEL) {
+ snode->edittree->selout= NULL;
+ actsock->flag&= ~SOCK_SEL;
+ }
+ else {
+ snode->edittree->selout= actsock;
+ reset_sel_socket(snode, SOCK_OUT);
+ actsock->flag|= SOCK_SEL;
+ }
+ }
+ else if(!node_mouse_select(snode, event))
toolbox_n();
break;
@@ -2194,6 +2277,9 @@ void winqreadnodespace(ScrArea *sa, void *spacedata, BWinEvent *evt)
case EKEY:
snode_handle_recalc(snode);
break;
+ case FKEY:
+ node_toggle_link(snode);
+ break;
case GKEY:
if(fromlib) fromlib= -1;
else {
diff --git a/source/blender/src/header_node.c b/source/blender/src/header_node.c
index ebdd42690f5..7255dcd3286 100644
--- a/source/blender/src/header_node.c
+++ b/source/blender/src/header_node.c
@@ -513,6 +513,9 @@ static void do_node_nodemenu(void *arg, int event)
case 10: /* execute */
addqueue(curarea->win, UI_BUT_EVENT, B_NODE_TREE_EXEC);
break;
+ case 11: /* toggle link */
+ node_toggle_link(snode);
+ break;
}
if(fromlib==-1) error_libdata();
@@ -537,6 +540,10 @@ static uiBlock *node_nodemenu(void *arg_unused)
uiDefIconTextBut(block, BUTM, 1, ICON_BLANK1, "Delete|X", 0, yco-=20, menuwidth, 19, NULL, 0.0, 0.0, 1, 3, "");
uiDefBut(block, SEPR, 0, "", 0, yco-=6, menuwidth, 6, NULL, 0.0, 0.0, 0, 0, "");
+
+ uiDefIconTextBut(block, BUTM, 1, ICON_BLANK1, "Toggle Link|F", 0, yco-=20, menuwidth, 19, NULL, 0.0, 0.0, 1, 11, "");
+
+ uiDefBut(block, SEPR, 0, "", 0, yco-=6, menuwidth, 6, NULL, 0.0, 0.0, 0, 0, "");
uiDefIconTextBut(block, BUTM, 1, ICON_BLANK1, "Make Group|Ctrl G", 0, yco-=20, menuwidth, 19, NULL, 0.0, 0.0, 1, 4, "");
uiDefIconTextBut(block, BUTM, 1, ICON_BLANK1, "Ungroup|Alt G", 0, yco-=20, menuwidth, 19, NULL, 0.0, 0.0, 1, 5, "");
diff --git a/source/blender/src/toolbox.c b/source/blender/src/toolbox.c
index 969b9d4657b..244e1ffd7c3 100644
--- a/source/blender/src/toolbox.c
+++ b/source/blender/src/toolbox.c
@@ -1648,6 +1648,8 @@ static TBitem tb_node_node[]= {
{ 0, "Duplicate|Shift D", TB_SHIFT|'d', NULL},
{ 0, "Delete|X", 'x', NULL},
{ 0, "SEPR", 0, NULL},
+ { 0, "Toggle Link|F", 'f', NULL},
+ { 0, "SEPR", 0, NULL},
{ 0, "Make Group|Ctrl G", TB_CTRL|'g', NULL},
{ 0, "Ungroup|Alt G", TB_ALT|'g', NULL},
{ 0, "Edit Group|Tab", TB_TAB, NULL},