diff options
author | Campbell Barton <ideasman42@gmail.com> | 2011-12-05 23:22:57 +0400 |
---|---|---|
committer | Campbell Barton <ideasman42@gmail.com> | 2011-12-05 23:22:57 +0400 |
commit | f3e9b8354f69b2dffdceb1869628a1df3164aafa (patch) | |
tree | 5e846f5505a4af06578d4477f97e1e318c63044f | |
parent | 27a5698154d13c0986bde99a31a54da76b35ac08 (diff) |
moving Nuke *.chan io camera animation import/export scripts into trunk, this is a very simple foramt and nice to have with tracking release
[[Split portion of a mixed commit.]]
-rw-r--r-- | io_anim_nuke_chan/__init__.py | 143 | ||||
-rw-r--r-- | io_anim_nuke_chan/export_nuke_chan.py | 96 | ||||
-rw-r--r-- | io_anim_nuke_chan/import_nuke_chan.py | 116 |
3 files changed, 355 insertions, 0 deletions
diff --git a/io_anim_nuke_chan/__init__.py b/io_anim_nuke_chan/__init__.py new file mode 100644 index 00000000..142fb4c7 --- /dev/null +++ b/io_anim_nuke_chan/__init__.py @@ -0,0 +1,143 @@ +# ##### 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": "Nuke Animation Format (.chan)", + "author": "Michael Krupa", + "version": (1, 0), + "blender": (2, 6, 0), + "api": 36079, + "location": "File > Import/Export > Nuke (.chan)", + "description": "Import/Export object's animation with nuke", + "warning": "", + "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/" + "Scripts/Import-Export/Nuke", + "tracker_url": "http://projects.blender.org/tracker/?" + "func=detail&atid=467&aid=28368&group_id=153", + "category": "Import-Export"} + + +# To support reload properly, try to access a package var, +# if it's there, reload everything +if "bpy" in locals(): + import imp + if "import_nuke_chan" in locals(): + imp.reload(import_nuke_chan) + if "export_nuke_chan" in locals(): + imp.reload(export_nuke_chan) + + +import bpy +from bpy.types import Operator +from bpy_extras.io_utils import ImportHelper, ExportHelper +from bpy.props import (StringProperty, + BoolProperty, + EnumProperty) + +# property shared by both operators +rot_ord = EnumProperty( + name="Rotation order", + description="Choose the export rotation order", + items=(('XYZ', "XYZ", "XYZ"), + ('XZY', "XZY", "XZY"), + ('YXZ', "YXZ", "YXZ"), + ('YZX', "YZX", "YZX"), + ('ZXY', "ZXY", "ZXY"), + ('ZYX', "ZYX", "ZYX"), + ), + default='XYZ') + + +class ImportChan(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'}) + + rot_ord = rot_ord + z_up = BoolProperty( + name="Make Z up", + description="Switch the Y and Z axis", + default=True) + + @classmethod + def poll(cls, context): + return context.active_object is not None + + def execute(self, context): + from . import import_nuke_chan + return import_nuke_chan.read_chan(context, + self.filepath, + self.z_up, + self.rot_ord) + + +class ExportChan(Operator, ExportHelper): + '''Export the animation to .chan file, readable by nuke and houdini. ''' \ + '''The exporter uses frames from the frames range''' + bl_idname = "export.export_chan" + bl_label = "Export chan file" + + filename_ext = ".chan" + filter_glob = StringProperty(default="*.chan", options={'HIDDEN'}) + y_up = BoolProperty( + name="Make Y up", + description="Switch the Y and Z axis", + default=True) + rot_ord = rot_ord + + @classmethod + def poll(cls, context): + return context.active_object is not None + + def execute(self, context): + from . import export_nuke_chan + return export_nuke_chan.save_chan(context, + self.filepath, + self.y_up, + self.rot_ord) + + +def menu_func_import(self, context): + self.layout.operator(ImportChan.bl_idname, text="Nuke (.chan)") + + +def menu_func_export(self, context): + self.layout.operator(ExportChan.bl_idname, text="Nuke (.chan)") + + +def register(): + bpy.utils.register_class(ImportChan) + bpy.utils.register_class(ExportChan) + bpy.types.INFO_MT_file_import.append(menu_func_import) + bpy.types.INFO_MT_file_export.append(menu_func_export) + + +def unregister(): + bpy.utils.unregister_class(ImportChan) + bpy.utils.unregister_class(ExportChan) + bpy.types.INFO_MT_file_import.remove(menu_func_import) + bpy.types.INFO_MT_file_export.remove(menu_func_export) + + +if __name__ == "__main__": + register() diff --git a/io_anim_nuke_chan/export_nuke_chan.py b/io_anim_nuke_chan/export_nuke_chan.py new file mode 100644 index 00000000..3760e342 --- /dev/null +++ b/io_anim_nuke_chan/export_nuke_chan.py @@ -0,0 +1,96 @@ +# ##### 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 ##### + +""" This script is an exporter to the nuke's .chan files. +It takes the currently active object and writes it's transformation data +into a text file with .chan extension.""" + +from mathutils import Matrix +from math import radians, degrees, atan2 + + +def save_chan(context, filepath, y_up, rot_ord): + + # get the active scene and object + scene = context.scene + obj = context.active_object + + # get the range of an animation + f_start = scene.frame_start + f_end = scene.frame_end + + # get the resolution (needed by nuke) + res_x = scene.render.resolution_x + res_y = scene.render.resolution_y + res_ratio = res_y / res_x + + # prepare the correcting matrix + rot_mat = Matrix.Rotation(radians(-90.0), 4, 'X').to_4x4() + + filehandle = open(filepath, 'w') + fw = filehandle.write + + # iterate the frames + for frame in range(f_start, f_end, 1): + + # set the current frame + scene.frame_set(frame) + + # get the objects world matrix + mat = obj.matrix_world.copy() + + # if the setting is proper use the rotation matrix + # to flip the Z and Y axis + if y_up: + mat = rot_mat * mat + + # create the first component of a new line, the frame number + fw("%i\t" % frame) + + # create transform component + t = mat.to_translation() + fw("%f\t%f\t%f\t" % t[:]) + + # create rotation component + r = mat.to_euler(rot_ord) + + fw("%f\t%f\t%f\t" % (degrees(r[0]), degrees(r[1]), degrees(r[2]))) + + # if we have a camera, add the focal length + if obj.type == 'CAMERA': + # I've found via the experiments that this is a blenders + # default sensor size (in mm) + sensor_x = 32.0 + # the vertical sensor size we get by multiplying the sensor_x by + # resolution ratio + sensor_y = sensor_x * res_ratio + cam_lens = obj.data.lens + # calculate the vertical field of view + # we know the vertical size of (virtual) sensor, the focal length + # of the camera so all we need to do is to feed this data to + # atan2 function whitch returns the degree (in radians) of + # an angle formed by a triangle with two legs of a given lengths + vfov = degrees(atan2(sensor_y / 2, cam_lens))*2 + fw("%f" % vfov) + + fw("\n") + + # after the whole loop close the file + filehandle.close() + + return {'FINISHED'} diff --git a/io_anim_nuke_chan/import_nuke_chan.py b/io_anim_nuke_chan/import_nuke_chan.py new file mode 100644 index 00000000..59eeba05 --- /dev/null +++ b/io_anim_nuke_chan/import_nuke_chan.py @@ -0,0 +1,116 @@ +# ##### 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 ##### + +""" This script is an importer for the nuke's .chan files""" + +from mathutils import Vector, Matrix, Euler +from math import radians, tan + + +def read_chan(context, filepath, z_up, rot_ord): + + # get the active object + scene = context.scene + obj = context.active_object + + # get the resolution (needed to calculate the camera lens) + res_x = scene.render.resolution_x + res_y = scene.render.resolution_y + res_ratio = res_y / res_x + + # prepare the correcting matrix + rot_mat = Matrix.Rotation(radians(90.0), 4, 'X').to_4x4() + + # read the file + filehandle = open(filepath, 'r') + + # iterate throug the files lines + for line in filehandle: + # reset the target objects matrix + # (the one from whitch one we'll extract the final transforms) + m_trans_mat = Matrix() + + # strip the line + data = line.split() + + # test if the line is not commented out + if data and not data[0].startswith("#"): + + # set the frame number basing on the chan file + scene.frame_set(int(data[0])) + + # read the translation values from the first three columns of line + v_transl = Vector((float(data[1]), + float(data[2]), + float(data[3]))) + translation_mat = Matrix.Translation(v_transl) + translation_mat.to_4x4() + + # read the rotations, and set the rotation order basing on the order + # set during the export (it's not being saved in the chan file + # you have to keep it noted somewhere + # the actual objects rotation order doesn't matter since the + # rotations are being extracted from the matrix afterwards + e_rot = Euler((radians(float(data[4])), + radians(float(data[5])), + radians(float(data[6])))) + e_rot.order = rot_ord + mrot_mat = e_rot.to_matrix() + mrot_mat.resize_4x4() + + # merge the rotation and translation + m_trans_mat = translation_mat * mrot_mat + + # correct the world space + # (nuke's and blenders scene spaces are different) + if z_up: + m_trans_mat = rot_mat * m_trans_mat + + # break the matrix into a set of the coordinates + trns = m_trans_mat.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_quaternion = trns[1] + obj.keyframe_insert("rotation_quaternion") + elif obj.rotation_mode == 'AXIS_ANGLE': + tmp_rot = trns[1].to_axis_angle() + obj.rotation_axis_angle = (tmp_rot[1], ) + tmp_rot[0][:] + obj.keyframe_insert("rotation_axis_angle") + del tmp_rot + else: + obj.rotation_euler = trns[1].to_euler(obj.rotation_mode) + obj.keyframe_insert("rotation_euler") + + + # check if the object is camera and fov data is present + if obj.type == 'CAMERA' and len(data) > 7: + v_fov = float(data[7]) + sensor_v = 32.0 + sensor_h = sensor_v * res_ratio + lenslen = ((sensor_h / 2.0) / tan(radians(v_fov / 2.0))) + obj.data.lens = lenslen + obj.data.keyframe_insert("lens") + filehandle.close() + + return {'FINISHED'} |