Network Editor, Layout All and respect “pinned” nodes’ positions

tldr; I scripted my custom “layout all nodes” which respects fixed positions of “pinned” nodes. The code is below.

I was still struggling with the Network Editor’s “layout all” nodes. I liked it most of the times but sometimes I wanted some nodes (or branches) to stay where they were. The best solution I came with was this:

  • hotkey to pin(unpin) selected node(s) to their position … I do this by setComment
  • I pin just one node in a branch (or single node, camera, light, ropnet) … sometimes the last “OUT”, sometimes the first “Object merge” …
  • I have replaced the L hotkey to run “my layout tool” which goes through these python steps:
    • 1) find all the pinned nodes and remember their positions
    • 2) classic layoutChildren(), which gives nice layout to the branches
    • 3) reposition the branches to their previously stored pins
    • 4) as a bonus, it places chopnets next to appropriate channelsop (naming convention applies)

RFEs

  • Michael Goldfarb filed two RFEs (thanks again) and if you like this idea, you may refer to them:
    • RFE – #87728 – pin nodes
    • RFE – #87729 – link node positions

This script pins/unpins the selected node(s)
I use the Ctrl-L hotkey

### swap pinned / unpinned state
for node in hou.selectedNodes():
    node.setGenericFlag(hou.nodeFlag.DisplayComment,True)

    if node.comment() == "`" :
        node.setComment("")
    else:
        node.setComment("`")

This script layouts all the nodes
I use the L hotkey

import hou
import toolutils

def getConnectedNodes(node,got):
    iteration = []

    inout = []
    try:
        inout += node.inputs()
    except:
        has_no_inputs = 1
        
    try:
        inout += node.outputs()
    except:
        has_no_outputs = 1

    for n in inout:
        if n not in got:
            got.append(n)
            iteration.append(n)
            iteration += getConnectedNodes(n,got)

    return iteration

    
################################
########   the pins    #########
################################

parm_pane = hou.ui.curDesktop().paneTabOfType(hou.paneTabType.Parm)
containernode = parm_pane.currentNode().parent()

childrenArr = containernode.children()
pinStatesArr = []
pinNodesArr = []
positionsArr = []

# store all positions

for child in childrenArr:
    pinned = 0
    if child.comment() == "`" :
        pinned = 1
        pinNodesArr.append( child )
        
    pinStatesArr.append( pinned )
    positionsArr.append( child.position() )
    
# store pinned positions

for pinNode in pinNodesArr:
    pos = pinNode.position()
    comment = str(pos[0]) + "," + str(pos[1])
    pinNode.setComment(comment)

    
# classic layoutChildren()

containernode.layoutChildren()

# reposition the branches

for pinNode in pinNodesArr:

    pinpos = pinNode.comment()
    pinpos = pinpos.split(",")
    pinpos = [ float(pinpos[0]) , float(pinpos[1]) ]
    
    allConnected = []
    allConnected = getConnectedNodes(pinNode,allConnected)
    
    pos = pinNode.position()
    move = [ pinpos[0] - pos[0] , pinpos[1] - pos[1] ]
    
    for connected in allConnected:
        try:
            connected.move(move)
        except:
            no_connected = 1
        
    # cleanup
    pinNode.setPosition(pinpos)
    pinNode.setComment("`")
   
    
    
################################
########  chop to chan  ########
################################


# define offset
offsetx = 3
offsety = 0

containerpath = containernode.path()

# find all chopnets
chopnets = hou.node(containerpath).glob("chopnet_*")

# for all
for ch in chopnets:
    xxx,task = ch.name().split("_")
    
    # appropriate channel node
    path_channel = containerpath + "/channel_" + task
    path_chopnet = containerpath + "/chopnet_" + task
    
    node_channel = hou.node(path_channel)
    node_chopnet = hou.node(path_chopnet)    
    
    # get
    posx = node_channel.position()[0] + offsetx
    posy = node_channel.position()[1] + offsety

    # set
    node_chopnet.setPosition( [posx,posy] )