Camera Track: Nuke to Blender

From RMIT Visual Effects
Jump to: navigation, search

Exporting stuff from one application, and importing to another, is one of the mainstays of VFX and film production. This is called pipeline or I/O (which stands for in/out). It is rarely without problems. Though there are many file standards that should, in theory, make this a painless activity, these standards are not supported by all applications. Neither are they all supported very consistently, with much buginess and inconsistency in how they are implemented. .

The Nuke to Blender camera and object pipeline is not without pain. The task is to import into Blender from Nuke a camera track (i.e. camera motion data) and also a guide object. These will ensure that a movie render from Blender will match perfectly the Nuke scene. In theory, there are many formats that could do this in one go: .fbx (which does not work well) and .abc (the import of which Blender does not support). For this reason, the task was accompanied in tow stages: for the I/O of the camera a .chan format was used (a file format specifically for cameras animation and camera properties) and for the object a simple .obj format was used.

Export camera from Nuke

1. Go the the little folder icon in Nuke's camera properties. Press it and select 'Export chan file'. Save it somewhere rational.

Nuke blender 01.png

Import camera into Blender

1. Go to blender and make a brand new camera (Add / Camera).

Nuke blender 02.png


2. Using the editor menus list, select the 'Text Editor'

Nuke blender 03.png


3. The 'Text Editor' is where scripts can be written, loaded and run. Without a script, It looks like a blank nothingness. In its menu bar a few buttons offer a simple range of functionality.

Nuke blender 04.png

4. Into the blank area, paste the text below (this script authored by Michael 'Kroopson' Krupa):

# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
bl_info = {
    "name": "Import animation from chan file (.chan)",
    "author": "Michael Krupa",
    "version": (0, 9),
    "blender": (2, 5, 9),
    "api": 36079,
    "location": "File > Import > Nuke (.chan)",
    "description": "Import object's animation from nuke",
    "warning": "Might be buggy",
    "wiki_url": ""\
        "",
    "tracker_url": ""\
        "",
    "category": "Import-Export"}
 
""" This script is an importer for the nuke's .chan files"""
 
import bpy
from mathutils import Matrix
from mathutils import Euler
from mathutils import Vector
from math import radians
from math import degrees
from math import atan
from math import tan
 
 
def readChan(context, filepath, Zup, rotOrd):
   
    #check if we have anything selected, if not, finish without doing anything.
    if not bpy.context.active_object:
        return {'FINISHED'}
   
    #get the active object
    obj = bpy.context.active_object
   
    #get the resolution (needed to calculate the camera lens)
    resX = bpy.context.scene.render.resolution_x
    resY = bpy.context.scene.render.resolution_y
    resRatio = float(resY) / float(resX)
   
   
    #prepare the correcting matrix
    rotMat = Matrix.Rotation(radians(90), 4, "X").to_4x4()
   
    #read the file
    f = open(filepath, 'r')
   
    #iterate throug the files lines
    for line in f.readlines():
       
        #reset the target objects matrix (the one from whitch one we'll extract the final transforms)
        mTransMat = Matrix()
        mTransMat.to_4x4()
       
        #strip the line
        data = line.split()
       
        #test if the line is not commented out
        if data[0] != "#":
           
            #set the frame number basing on the chan file
            bpy.context.scene.frame_set(int(data[0]))
           
            #read the translation values basing on the
            vTransl = Vector([float(data[1]), float(data[2]), float(data[3])])
            mTranslMat = Matrix.Translation(vTransl)
            mTranslMat.to_4x4()
           
            #read the rotations, and set the rotation order basing on the order set during the export
            eRot = Euler([ radians(float(data[4])), radians(float(data[5])), radians(float(data[6]))])
            eRot.order = rotOrd
            mRotMat = eRot.to_matrix()
            mRotMat.resize_4x4()
           
            #merge the rotation and translation
            mTransMat = mTranslMat * mRotMat
           
            #correct the world space (nuke's and blenders scene spaces are different)
            if Zup:
                mTransMat = rotMat * mTransMat
           
            #break the matrix into a set of the coordinates
            trns = mTransMat.decompose()
           
            #set the location and the location's keyframe
            obj.location = trns[0]
            obj.keyframe_insert('location')
           
            #convert the rotation to euler angles (or not) basing on the objects rotation mode
            if obj.rotation_mode != "QUATERNION":
                obj.rotation_euler = trns[1].to_euler(obj.rotation_mode)
                obj.keyframe_insert('rotation_euler')
            else:
                obj.rotation_euler = trns[1]
                obj.keyframe_insert('rotation_quaternion')
           
            #if the target object is camera test for the vfov data. If present, calculate the horizontal angle
            #and set the keyframe on camera lens
            if obj.data.type == "PERSP":
                if len(data) > 7:
                    print(data[7])
                    vFov = float(data[7])
                    hFov = atan( 1 / (resRatio / tan(radians(vFov))))
                    obj.data.angle = hFov
                    obj.data.keyframe_insert('lens')
    f.close()
 
    return {'FINISHED'}
 
 
from bpy_extras.io_utils import ImportHelper
from bpy.props import StringProperty, BoolProperty, EnumProperty
 
 
class ImportChan(bpy.types.Operator, ImportHelper):
    '''Import animation from .chan file, exported from nuke or houdini. The importer uses frame numbers from the file.'''
    bl_idname = "import_scene.import_chan"
    bl_label = "Import chan file"
   
    filename_ext = ".chan"
 
    filter_glob = StringProperty(default="*.chan", options={'HIDDEN'})
   
    Zup = BoolProperty(name="Make Z up", description="Switch the Y and Z axis.", default=True)
    rotOrd = EnumProperty(items=(('XYZ', "XYZ", "XYZ"),
                               ('XZY', "XZY", "XZY"),
                               ('YXZ', "YXZ", "YXZ"),
                               ('YZX', "YZX", "YZX"),
                               ('ZXY', "ZXY", "ZXY"),
                               ('ZYX', "ZYX", "ZYX"),
                               ),
                        name="Rotation order",
                        description="Choose the rotation order with whitch the chan has been made",
                        default='XYZ')
   
    @classmethod
    def poll(cls, context):
        return context.active_object != None
 
    def execute(self, context):
        return readChan(context, self.filepath, self.Zup, self.rotOrd)
 
 
def menu_func_import(self, context):
    self.layout.operator(ImportChan.bl_idname, text="Nuke (.chan)")
 
 
def register():
    bpy.utils.register_class(ImportChan)
    bpy.types.INFO_MT_file_import.append(menu_func_import)
 
 
def unregister():
    bpy.utils.unregister_class(ImportChan)
    bpy.types.INFO_MT_file_import.remove(menu_func_import)
 
 
if __name__ == "__main__":
    register()
    bpy.ops.import_scene.import_chan('INVOKE_DEFAULT')

5. Press the 'Run Script' button.

Nuke blender 05.png

6. You will then be taken to the load script window. Set the 'Rotation Order' drop down menu to 'ZXY' (this will match Nuke's camera).

Nuke blender 06.png

7. Using the 'System Bookmarks' on the left, navigate to your .Chan file and press 'Import chan file'.

Nuke blender 07.png

8. Using the editor menus list, navigate back to the '3D View'. If you scrub the timeline, you will see that the camera is now animated.

9. From the 'Camera Properties' change the 'Lens / Perspective', right click on 'Focal Length' and select 'Clear Keyframes'. This will remove its animation values. Change 'Focal Length' to match that of Nuke's camera.

Nuke blender 08.png

Export / import object

1. Attach a WriteGeo to the Nuke scene. From the drop down 'file type' knob, select .obj. From the 'file' knob navigate to your project folder and name the exported obj rational.

Nuke blender 09.png

2. In Blender, go to 'File / Import / Wavefront (.obj)'. From there, nagigate to your .obj file.