Contact sheets are something I use all the time to run out wedges of certain looks & compare my work against other shots in the sequence. Although, the Contact Sheet node is kind of… basic. It lacks a few features & requires a lot of initial setup to actually be useful. The aim of this article is to share some knowledge on the defaults I have in my contact sheet node & how to set contact sheets up more-speedily.
First, let’s look at the Contact sheet node itself and note a few things.
- Upon creating this node, my format is set to 3072 x 2048. Why is the resolution of the contact sheet so wrong?
- 3 rows and 2 columns gives us 6 images. I usually only need 4 at most, but this seems like an arbitrary default
- Does anyone read a book from bottom to top? Not sure why the Row Order is set to that…
Let’s fix this. We can add a couple lines of Python in our menu.py to make these defaults more sensible & to do some heavy lifting for us.
nuke.knobDefault("ContactSheet.width", '{"input.width * columns"}')
nuke.knobDefault("ContactSheet.height", '{"input.height * rows"}')
nuke.knobDefault("ContactSheet.roworder", 'TopBottom')
nuke.knobDefault("ContactSheet.colorder", 'LeftRight')
nuke.knobDefault("ContactSheet.rows", '{"ceil(inputs/columns)"}')
nuke.knobDefault("ContactSheet.columns", '{"ceil(sqrt(inputs))"}')
I’ll explain what’s going on line-by-line:
Line 1 is setting the width of the Contact Sheet to whatever is plugged into the first input, multiplied by the number of columns. If we have 2 images side-by-side, it makes sense to keep them at their original resolution to start with. By adding the curly brackets with quotation marks inside, we’re telling Nuke that we want these values to be an expression rather than just to use the value. This will keep the knob value updating as we plug / unplug things.
Line 2 is the same thing as Line 1, except we’re setting the height of the Contact Sheet, multiplied by the number of rows.
Line 3 is changing our Row Order knob to go Top to Bottom. Much more sensible.
Line 4 is setting the Column Order knob to be Left to Right. This is the default, but it doesn’t hurt to set whilst we’re here, just in case a studio has any different settings in their global Nuke environment menu.py
Line 5 is where things start to get exciting. We’re setting an expression on the rows knob that will automate what this value should be! Starting in the middle of the parentheses, inputs is counting how many inputs the Contact Sheet node has plugged in. We’re then dividing it by the value of the columns knob. We’re wrapping up this simple expression with ceil, which rounds the value up to the highest integer (round number). This is needed for when we have an un-even number of inputs connected to our Contact Sheet. For example, if we have 3 inputs, the expression inside the parentheses would return 1.5. Because the Contact Sheet can’t display half an image, it would just return 1. Using ceil round this decimal up to 2!
Line 6 is automating what the value of the columns knob should be. We’ve already covered what ceil does, and the rest is simple. We’re finding the square root of the number of connected inputs.
After adding these 6 lines into your menu.py, restart Nuke and create a Contact Sheet node. Try plugging multiple inputs in and watch the magic happen!
However, there is a downside to this. What if you want to make a Contact Sheet of the 2K plates for every shot in a sequence — let’s say 30 shots. Your Contact Sheet’s resolution will be humongous, and it’ll take an age to load! Using Python, we can add a resolution multiplier to scale this back down.
def OnCScreation():
cs = nuke.thisNode()
k = nuke.Double_Knob('resMult', "Resolution Multiplier")
k.setRange(0.1,2)
k.setValue(1)
if cs != None:
cs.addKnob(k)
nuke.addOnCreate(OnCScreation, nodeClass="ContactSheet")
We’re starting by creating a function, which we’re naming OnCScreation. This function:
- Looks at the current node
- Assigns a Floating Point Slider knob called ‘resMult’ with a human-readable label “Resolution Multiplier”, to the variable k
- Sets the range of the slider from 0.1 to 2 (gives us the flexibility to go down to 1/10th the resolution, or double)
- Sets the default value of this slider to 1
- —-
- Does a small bit of error-checking to make sure the “current node” is not equal to None, then
- Creates the Floating Point Slider knob, as we defined above.
- —-
- Our last line is saying: when a node with the class “ContactSheet” is created, run the OnCScreation function.
Add the code to your menu.py, restart Nuke, and create a new Contact Sheet node. You’ll see a User tab with our fancy new slider!
Although, we haven’t hooked it up to anything yet. Let’s go back to our first two lines of code and modify them to take this slider into account.
nuke.knobDefault("ContactSheet.width", '{"input.width * columns * resMult"}')
nuke.knobDefault("ContactSheet.height", '{"input.height * rows * resMult"}')
We’re adding a second multiplication to our expression, to multiply the value of our slider by the value of the rest of the expression. Simple! Here is our final code for our automatic Contact Sheet!
# CONTACT SHEET MODIFICATION
nuke.knobDefault("ContactSheet.width", '{"input.width * columns * resMult"}')
nuke.knobDefault("ContactSheet.height", '{"input.height * rows * resMult"}')
nuke.knobDefault("ContactSheet.roworder", 'TopBottom')
nuke.knobDefault("ContactSheet.colorder", 'LeftRight')
nuke.knobDefault("ContactSheet.rows", '{"ceil(inputs/columns)"}')
nuke.knobDefault("ContactSheet.columns", '{"ceil(sqrt(inputs))"}')
def OnCScreation():
cs = nuke.thisNode()
k = nuke.Double_Knob('resMult', "Resolution Multiplier")
k.setRange(0.1,2)
k.setValue(1)
if cs != None:
cs.addKnob(k)
nuke.addOnCreate(OnCScreation, nodeClass="ContactSheet")
Let’s not stop there — how about we create defaults for different types of text we might want to add? How about we also add a menu option to automatically create this text & hook up our fancy new automated contact sheet node to a selection of nodes in our GUI?
Here is the final code, commented for clarity! Add it to your menu.py, restart Nuke, and voila!
Note: there are a few double-ups in my code for the sake of clarity. Feel free to combine everything into one function for speed & efficiency!
——————–
If you liked this post, and would like to gain a better understanding of the fundamentals of Python in Nuke, check out my course: Python for Nuke 101.