rafaelperez
7/5/2019 - 3:36 PM

## Smartalign.py

``````
#----------------------------------------------------------------------------------------------------------
# Wouter Gilsing
# woutergilsing@hotmail.com
# May 2016
# v1.1
#----------------------------------------------------------------------------------------------------------

import nuke
import operator

def alignNodes(direction):

#--------------------------------------
#USER SETTINGS
#when nodes are about to overlap as a result of an alignment, the nodes are placed next to each other instead.
#the multiplier variables define the amount of space that's kept between the nodes
#1 is default, the higher the multiplier, the more space.
#--------------------------------------
multiplierX = 1
multiplierY = 1
#--------------------------------------

selection = nuke.selectedNodes()
dontmove = False

if direction in ['left','right']:
axis = 'x'
index = 0
else:
axis = 'y'
index = 1

#--------------------------------------
#MULTIPLE NODES
#if multiple nodes are selected, all the nodes will align to the node that's the furthest away in the specified direction
#--------------------------------------

if len(selection) > 1:

allPos = [[],[]]
for i in selection:
allPos[0].append(i.knob('xpos').value()+(getScreenSize(i)[0]))
allPos[1].append(i.knob('ypos').value()+(getScreenSize(i)[1]))

#check whether all selected nodes already share the same position values to prevent overlapping
#if so, do nothing
if not allPos[1-index].count(allPos[1-index][0]) == len(allPos[1-index]):
if direction in ["left","up"]:
destination = min(allPos[index])
else:
destination = max(allPos[index])
else:
dontmove = True

#--------------------------------------
#SINGLE NODE
#if only one node is selected, the selected node will snap to the nearest connected node (both input and output) in the specified direction
#--------------------------------------

elif len(selection) == 1:
curNode = selection[0]

#create a list of all the connected nodes
inputNodes = curNode.dependencies()
outputNodes = curNode.dependent()

#remove nodes with hidden inputs and viewer nodes,
#as you probably wouldn't want to snap to those
#not every node has a hide input knob (read node for example), so use a "try" in case it hasn't
for i in outputNodes:
try:
if i.knob('hide_input').value() or i.Class() == 'Viewer':
outputNodes.remove(i)
except:
pass

if curNode.knob('hide_input'):
if curNode.knob('hide_input').value():
inputNodes = []

connectedNodes = inputNodes + outputNodes

#create a list for every connected node containing the following [xpos,ypos,relative xpos, relative ypos, node]
#store those lists in an other list

positions = []

for i in connectedNodes:
xPos = i.xpos() + getScreenSize(i)[0]
yPos = i.ypos() + getScreenSize(i)[1]
curNodexPos = curNode.xpos() + getScreenSize(curNode)[0]
curNodeyPos = curNode.ypos() + getScreenSize(curNode)[1]

positions.append([xPos,yPos,xPos-curNodexPos,yPos-curNodeyPos, i])

# sort the list based on the relative positions
sortedList = sorted(positions, key=operator.itemgetter(index+2))

#remove nodes from list to make sure the first item is the node closest to the curNode
#use the operator module to switch dynamically between ">=" and "<="
#the positiveDirection variable is used later to correctly calculate to offset in case nodes are about to overlap
if direction in ['right','down']:
equation = operator.le
positiveDirection = -1
else:
sortedList.reverse()
equation = operator.ge
positiveDirection = 1

try:
while equation(sortedList[0][index+2],0):
sortedList.pop(0)
except:
pass

#checking whether there are nodes to align to in the desired direction
#if there are none, don't move the node
if len(sortedList) != 0:
destination = sortedList[0][index]

curPosition = [curNodexPos,curNodeyPos]
destinationPosition = [curNodexPos,curNodeyPos]
destinationPosition[index] = destination

#remove the relative positions from the positionlist
for i in range(len(positions)):
positions[i] = [positions[i][:2],positions[i][-1]]

# Making sure the nodes won't overlap after being aligned.
# If they are about to overlap the node will be placed next to the node it tried to snap to.
for i in positions:

#calculate the difference between the destination and the position of the node it will align to
difference = [(abs(i[0][0]-destinationPosition[0]))*1.5,(abs(i[0][1]-destinationPosition[1]))*1.5]

#define the amount of units a node should offsetted to not overlap
offsetX = 0.75 * (3 * getScreenSize(curNode)[0] +  getScreenSize(i[1])[0])
offsetY = 3 * getScreenSize(curNode)[1] +  getScreenSize(i[1])[1]
offsets = [int(offsetX),int(offsetY)]

#check in both directions whether the node is about to overlap:
if difference[0] < offsets[0] and difference[1] < offsets[1]:

multiplier = [multiplierX,multiplierY][index]
offset = positiveDirection * multiplier * offsets[index]

#find out whether the nodes are already very close to each other
#(even closer than they would be after aligning)
#don't move the node if that's the case
if abs(offset) < abs(destination - curPosition[index]):
destination = destination + offset

else:
dontmove = True

#stop looping through the list when a suitable node to align to is found
break
else:
dontmove = True

else:
dontmove = True

#--------------------------------------
#MOVE THE SELECTED NODES
#--------------------------------------

nuke.Undo().name('Align Nodes')
nuke.Undo().begin()

for i in selection:
if not dontmove:
if axis == 'x':
i.setXpos(int(destination-getScreenSize(i)[index]))
else:
i.setYpos(int(destination-getScreenSize(i)[index]))

nuke.Undo().end()

def getScreenSize(node):

#--------------------------------------
#To get the position of a node in the DAG you can use the xpos/ypos knobs.
#However, that position is heavely influenced by the size of the node.
#When horizontally aligned, a Dot node will have a different ypos than a blur node for example.
#To neuralize a nodes postionvalues you have to add the half the nodes screen dimensions to the positionvalues.
#--------------------------------------

return [node.screenWidth()/2, node.screenHeight()/2]

#--------------------------------------