Using Python To Scale Nodes In Nuke

In this guide we’ll look at a simplified version of the python script used in NodeGraphBuddy to scale selected nodes. You can download the full script, which also adjusts backdrops, and has many other features now as part of the BuddySystem!


Important Nuke Concepts

Let’s cover a few important things before we get into the code

The Knobs

xpos = a knob that stores the position of the left side of a node

ypos = a knob that stores the position of the top side of a node

screenWidth = a knob that stores the width of a node as displayed in the node graph

screenHeight = a knob that stores the height of a node as displayed in the node graph

Scaling As Movement

When we talk about scaling nodes what we're really doing is moving them in relation to a collective center point, via their x and y positions, so that it looks like the distance between them all is changing. The one exception is with a backdrop node, which we do want to actually change the overall size of


The Plan

I love a plan, let’s break down the things our scaling script needs to do:

  1. Find the Position of Each Node

  2. Find the Collective Center of All Nodes

  3. Calculating the New Positions

  4. Applying the New Positions (Scale)

We’ll start by only scaling horizontally (x position) to keep it really simple, and then expand at the end to include vertical scaling as well

1) Finding the Position of Each Node

It’s important to find the position of each node selected, so we can later use them in step 2 to find the collective center of them all

  • To get the positions we first define a variable that stores all the currently selected nodes

  • Then we will make an empty list to store all the x positions as data for later use

  • Lastly we will run a for loop on the selected nodes that gets each nodes x position, and appends (adds it) to the list

# Get selected nodes
selected = nuke.selectedNodes()

# An empty list to store x center data
all_x_centers = []

# Loop through selected nodes and get their centers, then add to all_x_centers list
for n in selected:
    center_x = n.xpos()
    all_x_centers.append(center_x)

2) Find the Collective Center of All Nodes

How do we find the center of a bunch of nodes?

It's easier than you think! Using a bit of simple math we can calculate the average (mean) position of all the selected nodes. You can do this by adding (sum in python) all the values in our all_x_centers list together, and then dividing by the amount of nodes selected (len in python.) The result is the center of all nodes selected!

  • Knowing that we can add this formula to the end of the script pivot_x = sum(all_x_centers) / len(selected)

# Get selected nodes
selected = nuke.selectedNodes()

# An empty list to store x center data
all_x_centers = []

# Loop through selected nodes and get their centers, then add to all_x_centers list
for n in selected:
    center_x = n.xpos()
    all_x_centers.append(center_x)

# Calculate the collective center and store in variable
pivot_x = sum(all_x_centers) / len(selected)

3) Calculating the New Positions

This is the magic formula! For every single node, we'll apply this logic to find its new scaled value:

new_center_x = pivot_x + (node_center_x - pivot_x) * scale_factor

Let's break that formula down:

  • (node_center_x - pivot_x) : This calculates the distance of the node's current center from the central pivot point. If the node is to the right of the pivot, this value is positive, if it's to the left, it's negative

  • * scale_factor : This takes that distance and multiplies it. With a factor of 1.1, it increases the distance by 10%, pushing the node away from the center. If the factor were 0.9, it would decrease the distance by 10%, pulling it closer

  • pivot_x + : The new scaled distance is added back to the pivot points position. This gives the node its new center position

  • new_center_x = : This is a variable to store the result of the formula for later use

So we add the following lines of code next

  • A scale_factor variable that stores the amount we want to scale by

  • Another for loop that iterates through the selected nodes again, but this time it calculates the above magic formula for each node, and puts it in the new_center_x variable

# Get selected nodes
selected = nuke.selectedNodes()

# An empty list to store x center data
all_x_centers = []

# Loop through selected nodes and get their centers, then add to all_x_centers list
for n in selected:
    center_x = n.xpos()
    all_x_centers.append(center_x)

# Calculate the collective center and store in variable
pivot_x = sum(all_x_centers) / len(selected)

# Define scale amount. 1.1=10% larger, 0.9=10% smaller
scale_factor = 1.1 

# Loop through all the selected nodes and update scale
for node in selected:
    # Get the center of the current node
    node_center_x = node.xpos()
    # Calculate the new center
    new_center_x = pivot_x + (node_center_x - pivot_x) * scale_factor

4) Applying the New Positions (Scale)

The last step is to simply apply the result of the for loop to the same node it is currently looping through

We do this with setXpos, instead of knob(‘xpos’).setValue(new_value) to preserve undo capability, after the script is run. We also use int to force the value to be an integer, instead of a float, otherwise we could get a type error when trying to apply the new value

Add the following line of code at the end of the second for loop. Then select some nodes, and run the full script in nukes script editor!

  • node.setXpos(int(new_center_x))

# Get selected nodes
selected = nuke.selectedNodes()

# An empty list to store x center data
all_x_centers = []

# Loop through selected nodes and get their centers, then add to all_x_centers list
for n in selected:
    center_x = n.xpos()
    all_x_centers.append(center_x)

# Calculate the collective center and store in variable
pivot_x = sum(all_x_centers) / len(selected)

# Define scale amount. 1.1=10% larger, 0.9=10% smaller
scale_factor = 1.1 

# Loop through all the selected nodes and update scale
for node in selected:
    # Get the center of the current node
    node_center_x = node.xpos()
    # Calculate the new center
    new_center_x = pivot_x + (node_center_x - pivot_x) * scale_factor
    # set the new x position
    node.setXpos(int(new_center_x))

The Plan Is Falling Apart :(

Oh No!

Our horizontal scaling script sometimes works, but with certain nodes like dots, the scaling isn’t correct, and seems to get worse the more we scale something…. why is this? Have the maths Gods forsaken us? This is something that confused me for a long, long time, and was the main stumbling point of fully developing this script. I was making the incorrect assumption that a nodes x/y positions were its geometric center, when it turns out that these values actually represent the top left corner instead. This horrible mistake on my part meant that the script worked for nodes of the same size, but if you mixed in a smaller dot, a labeled node, or something like a camera, the math would appear to fall apart. Thankfully the math was correct, and just needed a little bit more to get the real geometric center of each node, then everything magically fell into place!

1) Finding The Center Of Each Node For Real This Time. I Promise!

This is where the screenWidth and ScreenHeight knobs finally come into play

We can divide the width of the node as it is displayed to the user by 2, to get half of it, which is the local horizontal center of the node. Let’s add that to our previous script and see what happens!

  • In the first for loop add + n.screenWidth() / 2 at the end of the center_x line. This will add half the screen width to the x position, and give us the true global horizontal center of the node

  • In the second for loop add + n.screenWidth() / 2 at the end of the node_center_x line. This will add half the screen width to the x position, and give us the true global horizontal center of the node

  • In the second for loop add new_xpos = new_center_x - node.screenWidth() / 2 just before the final line. This will subtract half the screen width by the new center, and give us the the new scaled x position

# Get selected nodes
selected = nuke.selectedNodes()

# An empty list to store x center data
all_x_centers = []

# Loop through selected nodes and get their centers, then add to all_x_centers list
for n in selected:
    center_x = n.xpos() + n.screenWidth() / 2
    all_x_centers.append(center_x)

# Calculate the collective center and store in variable
pivot_x = sum(all_x_centers) / len(selected)

# Define scale amount. 1.1=10% larger, 0.9=10% smaller
scale_factor = 1.1 

# Loop through all the selected nodes and update scale
for node in selected:
    # Get the center of the current node
    node_center_x = node.xpos() + node.screenWidth() / 2
    # Calculate the new center
    new_center_x = pivot_x + (node_center_x - pivot_x) * scale_factor
    # Calculate the new x position 
    new_xpos = new_center_x - node.screenWidth() / 2
    # set the new x position
    node.setXpos(int(new_xpos))

The Plan Succeeds!

Let’s test that script again!

Look at that, it now scales nodes of all sizes correctly! That was the final missing piece of the puzzle. By calculating the true center of each node, and not directly using the top left corner, the script now treats every node uniformly, and nodes that are aligned before scaling, remain aligned after!


The Complete Script

Below is the entire script, containing the concepts we talked about above. I’ve duplicated the horizontal (xpos) logic to for the vertical axis (ypos) as well, but it is identical otherwise. Now when you run the script it will scale uniformly in both directions!

# Get selected nodes
selected = nuke.selectedNodes()

# Empty lists to hold all our center results
all_x_centers = []
all_y_centers = []

# Loop through selected nodes, get their centers, then add to lists
for n in selected:
    center_x = n.xpos() + n.screenWidth() / 2
    center_y = n.ypos() + n.screenWidth() / 2  
    all_x_centers.append(center_x)
    all_y_centers.append(center_y)

# Find the collective center pivot point      
pivot_x = sum(all_x_centers) / len(selected)
pivot_y = sum(all_y_centers) / len(selected)

# Set scale factor. 1.1 = 10% increase, 0.9 = 10% decrease
scale_factor = 1.1

# Loop through each selected node and scale it
for node in selected:
    # Get the current center of this specific node
    node_center_x = node.xpos() + node.screenWidth() / 2
    node_center_y = node.ypos() + node.screenHeight() / 2

    # Use the scaling formula to find the new center position
    new_center_x = pivot_x + (node_center_x - pivot_x) * scale_factor
    new_center_y = pivot_y + (node_center_y - pivot_y) * scale_factor

    # Calculate the node's new top-left corner from its new center
    new_xpos = new_center_x - node.screenWidth() / 2
    new_ypos = new_center_y - node.screenHeight() / 2
    
    # Set the final scaled position
    node.setXpos(int(new_xpos))
    node.setYpos(int(new_ypos))

That’s All!

I hope this helps! If I had a walkthrough like this when developing NodeGraphBuddy, it wouldn’t have taken so long! Don’t forget to check out the full script to see all the other features, like scaling backdrops, zoom based scale factor, and biasing the scale to different sides of the selection!

Next
Next

Your Viewer Is A Super Node With Input Process