Skip to content

Instantly share code, notes, and snippets.

@rafaelperez
Created July 5, 2019 15:36
Show Gist options
  • Save rafaelperez/a93a15117d94fef2b9b7c7c1e5ea4b09 to your computer and use it in GitHub Desktop.
Save rafaelperez/a93a15117d94fef2b9b7c7c1e5ea4b09 to your computer and use it in GitHub Desktop.
#----------------------------------------------------------------------------------------------------------
# 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]
#--------------------------------------
#EXAMPLE MENU.PY CODE
#--------------------------------------
'''
import W_smartAlign
menuBar = nuke.menu("Nuke")
menuBar.addCommand("Edit/Node/Align/Left", 'W_smartAlign.alignNodes("left")', "Alt+left", shortcutContext=2)
menuBar.addCommand("Edit/Node/Align/Right", 'W_smartAlign.alignNodes("right")', "Alt+right", shortcutContext=2)
menuBar.addCommand("Edit/Node/Align/Up", 'W_smartAlign.alignNodes("up")', "Alt+up", shortcutContext=2)
menuBar.addCommand("Edit/Node/Align/Down", 'W_smartAlign.alignNodes("down")', "Alt+down", shortcutContext=2)
'''
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment