Stereo
======

How to deal with stereo in NUKE.


Multi View Knob Values
----------------------

Adding values per view in a stereo project is as simple as adding different values to a translate's **x** and **y** knob. Here is a quick primer.

This adds a Transform node and splits out the right view::

	n = nuke.createNode('Transform')
	k = n['translate']
	k.splitView('right')

When splitting out a view, all "un-split" views are controlled together like a "main" view, so if no explicit view is specified, all "un-split" knobs are changed.

The following assigns the value of 1 to the knob's **x** and **y** field for the main view, but not for the split out views::

	k.setValue(1)

.. image:: images/stereoPrimer_01.png

This assigns the value of 2 to only the right view::

	k.setValue(2, view='right')

.. image:: images/stereoPrimer_02.png

This sets the translate values for the main view to x=1 and y=2::

	k.setValue(1, 0)
	k.setValue(2, 1)
	
.. image:: images/stereoPrimer_03.png

While this sets the translate values in the right view to x=3 and y=4::

	k.setValue(3, 0, view='right')
	k.setValue(4, 1, view='right')

.. image:: images/stereoPrimer_04.png

You can create animation in different views in a similar way. This sets the main views to be animated::

	k.setAnimated()

This sets the right view to be animated::

	k.setAnimated(view='right')

Setting keyframes for main and right view at frame 1 and 100::

	k.setValueAt(50, 1)
	k.setValueAt(70, 100)

	k.setValueAt(80, 1, view='right')
	k.setValueAt(110, 100, view='right')

And reading animation values for both views::

	mainX = k.valueAt(50, 0)
	mainY = k.valueAt(50, 1)
	rightX = k.valueAt(50, 0, view='right')
	rightY = k.valueAt(50, 1, view='right')

	print 'left (main) view values at frame 50 are X=%s, Y=%s' % (mainX, mainY)
	print 'right view values at frame 50 are X=%s, Y=%s' % (rightX, rightY)

| ``# Result:``
| ``left (main) view values at frame 50 are X=59.898989898, Y=59.898989898``
| ``right view values at frame 50 are X=94.8484848507, Y=94.8484848507``


Examples
--------

.. _stereocam:

Creating/Converting a Stereo Camera
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Here is an example function to create or convert a camera for stereo work. Start with the usual function definition using two arguments to define the Camera node and the interocular distance.
Set the default value for **node** to **None** (in which case we create a new camera) and the **interoc** to whatever value works for you::

	def stereoCam(node=None, interoc=.6):
	    try:
	        node = node or nuke.selectedNode()
	    except ValueError:
	        # IF NO NODE IS GIVEN AND NOTHING IS SELECTED, CREATE A NEW NODE
	        node = nuke.createNode( 'Camera2' )

Next we get the current script's views. We assume the first view in the list is the left eye, the second the right eye:

	views = nuke.views()
	leftView = views[0]
	rightView = views[1]

Calculate the offset from the camera's current position to the left and right based on the given **interoc** value::

	rightOffset = float(interoc)/2
	leftOffset = -rightOffset

The camera can be driven by the matrix knob or by the transform knobs. Let's start with the first case - grab the matrix knob and copy the current matrix so we can create the second eye with it::

	if node['useMatrix'].value():
	    knob = node['matrix']
	    leftEyeMatrix = node['transform'].value() # GETS MATRIX BUT IN REVERSE ORDER
	    rightEyeMatrix = nuke.math.Matrix4(leftEyeMatrix) # COPY MATRIX

Now move the original matrix to the left to represent the left eye based on the values calculated above, then do the same for the right eye::

	leftEyeMatrix.translate(leftOffset, 0, 0) 
	rightEyeMatrix.translate(rightOffset, 0, 0)

Because **node['transform'].value()** above returned the matrix in reverse order, we need to reverse it again before we assign the new values::

	leftEyeMatrix.transpose()
	rightEyeMatrix.transpose()

If there are only two views in the script, we use the left view as the main view and only split off the right view. If there are more than two views we split both::

	if len(views) > 2:
	    knob.splitView(leftView)
	knob.splitView(rightView)

Now we can assign the new values by cycling through the values of the 4x4 matrix and assigning them one by one::

	for i in range(16):
	    knob.setValueAt(leftEyeMatrix[i], nuke.frame(), i, leftView)
	    knob.setValueAt(rightEyeMatrix[i], nuke.frame(), i, rightView)

This takes care of the case where the camera is driven by the matrix knob. In the other case, things are a little easier but we are doing the same stuff. Grab the transform knob's **x** value and offset it by the values calculated from the interocular argument::

	else:
	    knob = node['translate']
	    # GET THE NEW VALUES FOR LEFT AND RIGHT EYE
	    leftEye = knob.value(0) + leftOffset
	    rightEye = knob.value(0) + rightOffset

Split the knobs as required::

        if len( views ) > 2:
            knob.splitView( leftView )
        knob.splitView( rightView )

Assign the new values to the new views::

	knob.setValue( leftEye, 0, view=leftView )
	knob.setValue( rightEye, 0, view=rightView )

The final code:

.. literalinclude:: examples/stereoCam.py

.. _stereosetup-ref-label:

Setting Up Stereo
^^^^^^^^^^^^^^^^^

Here is a way to automatically force new NUKE projects to be stereo (or generally multi view) setups.
First, we write a function that turns a NUKE project into a multi view project with all the desired parameters, then we hook up that function to a callback making sure it is run every time a new root node is created.

The knob we need to modify to create new views is **nuke.root().knob('views')**.
This knob does not have any dedicated methods to set views, so let's examine it's script syntax for a default stereo project::

	viewKnob = nuke.root().knob('views')
	print viewKnob.toScript()

	# Result:
	left #ff0000
	right #00ff00

The **toScript** method converts the knob to it's script syntax, the way it's written in the .nk file, and is a good way to analyse complex knobs. In this case, it tells us the root's view names and associated colors in hex code. The views are delimited by line breaks.
With this info we can now construct our own list of names and color values, then use the **fromScript** method to initialize the knob with our values.

Let's test this first::

	viewKnob = nuke.root().knob('views')
	viewKnob.fromScript( 'testView #ff0000')

| This sets the root's **view** knob to have a single view called *testView* and assigns a primary red to it.
| In reality, it is much more convenient to be able to assign rgb values rather than hex values, so let's write a little converter to avoid having to worry about hex values in the future - using Python's string formatting this is pretty simple.

This converts an integer value to a hex value::

	intValue = 255 
	hexValue = '%x' % intValue
	print hexValue

	# Result:
	ff

So let's do this for r, g and b::

	rgb = (255, 0, 0)
	hexCol = '#%02x%02x%02x' % rgb
	print hexCol

	# Result:
	#ff0000

Since NUKE mostly works in floating point values, let's modify this so we can input normalized values (0-1) rather than 8 bit integers (0-255)::

	col = (.5,.3,.2)
	rgb = tuple([ int(v*255) for v in col ])
	hexCol = '#%02x%02x%02x' % rgb
	print hexCol

	# Result:
	#7f4c33

The above converts each normalized floating point value in **col** to an 8 bit integer using list comprehension. The output is a list which is converted to a tuple before being stored in **rgb** - this is essential for the subsequent string formatting requirements.

Let's throw it all together to build our own script syntax for a custom view called **testView** and assign a dark red color::

	name = 'testView'
	col = (.5, 0, 0)
	rgb = tuple([ int(v*255) for v in col ])
	hexCol = '#%02x%02x%02x' % rgb
	view = '%s %s' % (name, hexCol)
	# NOW INITIALIZE THE VIEW KNOB WITH THE NEW VIEW
	nuke.root().knob('views').fromScript(view)

If we want more than one view, we just throw a line break in between the views::

	newViews = []

	name = 'testView'
	col = (.5, 0, 0)
	rgb = tuple([ int(v*255) for v in col ])
	hexCol = '#%02x%02x%02x' % rgb
	view = '%s %s' % (name, hexCol)
	newViews.append(view)

	name2 = 'testView2'
	col2 = (0, .5, 0)
	rgb2 = tuple([ int(v*255) for v in col2 ])
	hexCol2 = '#%02x%02x%02x' % rgb2
	view2 = '%s %s' % (name2, hexCol2)
	newViews.append(view2)

	nuke.root().knob('views').fromScript('\n'.join(newViews))

Now let's tidy this up, add the nuke **import**, and make a function that let's the user define a list of views where each view is a tuple with the view's name followed by it's color:

.. literalinclude::  examples/setUpMultiView.py

Run the function to test it. Note that the above function defines the left and right views with green and red colors as the default, so you could run the function without any arguments as well if you're happy with those settings::
	setUpMultiView()

If you want the left view to be red and the right one to be green, call the function with different values::

	setUpMultiView([ ('left', (1,0,0)), ('right', (0,1,0)) ])

The latter creates two views called **left** and **right** and assigns a primary red and green respectively.

.. image:: images/setUpMultiView_01.png

All the hard work is done.

| If you want NUKE to run this script on start-up, so that new sessions are automatically in stereo mode, hook up the function to the **onUserCreate** callback.
| See :ref:`stereocallback-ref-label`

