Python for Nuke:
Extracting V-Ray Render Elements from OpenEXR



Introduction

The goal of this exercise was to create custom Nuke gizmos to simplify repetitive compositing tasks.

For my project, I wanted to automate the setup of extracting V-Ray render elements from an OpenEXR file.

I found that The Foundry had done something similar in their documentation here, so I began by using this example to introduce myself to the way that Nuke and Python speak to one another.



V-Ray Image Math

V-Ray for Maya features a built-in "Render Elements" function, which allows the user to render out any number of premade channels with their beauty pass, to be used in compositing.

Using a certain set of these elements allows the user to recreate the beauty pass, and edit any one aspect of the image as they go along.

X

+
X

+
+

+
=

The process of extracting each of these layers and combining them back together can become quite tedious, so it is my goal to automate the process.



The function: shuffleExr

My script currently undergoes the following process:

1. Taking a given Read node, it queries the channels of the image, then extracts them into a list of usable layers.
2. Using a for loop, it creates a shuffle node for each layer, and sets the "in" value to extract that respective layer.
3. Creates merge nodes to connect the necessary layers, using a list of node names built in the for loop.


# Chad Fetzer - Advanced Application Scripting - TECH 312 - Professor Malcolm Kesson - V-Ray EXR Extraction
read = nuke.thisNode()
knob = read.input(0)
nodename =  knob.name()
def shuffleExr(read):
    #This function, the primary one, identifies the read node, identifies its channels, then breaks those channels down into the necessary layers.
    node = nuke.toNode(nodename)
    channels = node.channels()
    layers = list( set([c.split('.')[0] for c in channels]) )
    layers.sort()

    p = nuke.Panel('Verify Elements')
    p.addEnumerationPulldown('SSS', ' '.join(layers))
    p.addEnumerationPulldown('depth', ' '.join(layers))
    p.addEnumerationPulldown('diffuse', ' '.join(layers))
    p.addEnumerationPulldown('rawGI', ' '.join(layers))
    p.addEnumerationPulldown('rawLight', ' '.join(layers))
    p.addEnumerationPulldown('reflect', ' '.join(layers))
    p.addEnumerationPulldown('refract', ' '.join(layers))
    p.addEnumerationPulldown('specular', ' '.join(layers))
    if not p.show():
        return
    shuffleMake(node, p)
    
def shuffleMake(node, p):
    #This function creates a shuffle node, labels it for each respective layer, sets the value of the shuffle to that layer, then moves the node into position.
    shuffleSSS = nuke.nodes.Shuffle(label=p.value('SSS'), inputs=[node], hide_input='True')
    shuffleSSS['in'].setValue(p.value('SSS'))
    shuffleSSS.setXYpos(-400,400)
    shuffleDiff = nuke.nodes.Shuffle(label=p.value('diffuse'), inputs=[node], hide_input='True')
    shuffleDiff['in'].setValue(p.value('diffuse'))
    shuffleDiff.setXYpos(-300,-150)
    shuffleGI = nuke.nodes.Shuffle(label=p.value('rawGI'), inputs=[node], hide_input='True')
    shuffleGI['in'].setValue(p.value('rawGI'))
    shuffleGI.setXYpos(-400,-150)
    shuffleLight = nuke.nodes.Shuffle(label=p.value('rawLight'), inputs=[node], hide_input='True')
    shuffleLight['in'].setValue(p.value('rawLight'))
    shuffleLight.setXYpos(-200,-150)
    shuffleRefl = nuke.nodes.Shuffle(label=p.value('reflect'), inputs=[node], hide_input='True')
    shuffleRefl['in'].setValue(p.value('reflect'))
    shuffleRefl.setXYpos(-350,100)
    shuffleRefr = nuke.nodes.Shuffle(label=p.value('refract'), inputs=[node], hide_input='True')
    shuffleRefr['in'].setValue(p.value('refract'))
    shuffleRefr.setXYpos(-80,250)
    shuffleSpec = nuke.nodes.Shuffle(label=p.value('specular'), inputs=[node], hide_input='True')
    shuffleSpec['in'].setValue(p.value('specular'))
    shuffleSpec.setXYpos(-100,0)
    
    mergeCreate(shuffleSSS, shuffleDiff, shuffleGI, shuffleLight, shuffleRefl, shuffleRefr, shuffleSpec)
   
def mergeCreate(shuffleSSS, shuffleDiff, shuffleGI, shuffleLight, shuffleRefl, shuffleRefr, shuffleSpec):
    #This function will create the merge nodes for each operation, then use setXYpos to move each one into place.
    mergeGI = nuke.nodes.Merge(operation="multiply", inputs=[shuffleDiff, shuffleGI])
    mergeGI.setXYpos(-350,-50)
    mergeLight = nuke.nodes.Merge(operation="multiply", inputs=[shuffleDiff, shuffleLight])
    mergeLight.setXYpos(-250,-50)
    mergeDiff = nuke.nodes.Merge(operation="plus", inputs=[mergeLight, mergeGI])
    mergeDiff.setXYpos(-300,0)
    mergeSpec = nuke.nodes.Merge(operation="plus", inputs=[mergeDiff, shuffleSpec])
    mergeSpec.setXYpos(-200,125)
    mergeRefl = nuke.nodes.Merge(operation="plus", inputs=[shuffleRefl, mergeSpec])
    mergeRefl.setXYpos(-270,250)
    mergeRefr = nuke.nodes.Merge(operation="plus", inputs=[shuffleRefr, mergeRefl])
    mergeRefr.setXYpos(-185,400)
    mergeSSS = nuke.nodes.Merge(operation="plus", inputs=[shuffleSSS, mergeRefr])
    mergeSSS.setXYpos(-260,500)

shuffleExr(nodename)		


Results

When the button is clicked, this window pops up, which allows the user to verify each layers so that there is no guesswork in the script.

After running the script, this is the result:


Thoughts

I really enjoyed this project. It feels good to write something that I can use in my day-to-day work. Understanding the interaction between Nuke and Python was tricky at first, but in the end I feel that I accomplished what I set out to do. This project has helped me understand the importance for every artist in a studio to know how to script in order to solve quick problems and automate processes.