Advertisement
Guest User

Blender 2.82 Export: Adobe After Effects (.jsx)

a guest
Aug 2nd, 2020
166
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 37.32 KB | None | 0 0
  1. # ##### BEGIN GPL LICENSE BLOCK #####
  2. #
  3. #  This program is free software; you can redistribute it and/or
  4. #  modify it under the terms of the GNU General Public License
  5. #  as published by the Free Software Foundation; either version 2
  6. #  of the License, or (at your option) any later version.
  7. #
  8. #  This program is distributed in the hope that it will be useful,
  9. #  but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  11. #  GNU General Public License for more details.
  12. #
  13. #  You should have received a copy of the GNU General Public License
  14. #  along with this program; if not, write to the Free Software Foundation,
  15. #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  16. #
  17. # ##### END GPL LICENSE BLOCK #####
  18.  
  19. # <pep8 compliant>
  20.  
  21. bl_info = {
  22.     "name": "Export: Adobe After Effects (.jsx)",
  23.     "description": "Export cameras, selected objects & camera solution 3D Markers to Adobe After Effects CS3 and above",
  24.     "author": "Bartek Skorupa",
  25.     "version": (0, 6, 3),
  26.     "blender": (2, 82, 0),
  27.     "location": "File > Export > Adobe After Effects (.jsx)",
  28.     "warning": "",
  29.     "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"\
  30.         "Scripts/Import-Export/Adobe_After_Effects",
  31.     "tracker_url": "https://projects.blender.org/tracker/index.php?"\
  32.         "func=detail&aid=29858",
  33.     "category": "Import-Export",
  34.     }
  35.  
  36.  
  37. import bpy
  38. import datetime
  39. from math import degrees
  40. from mathutils import Matrix
  41.  
  42.  
  43. # create list of static blender's data
  44. def get_comp_data(context):
  45.     scene = context.scene
  46.     aspect_x = scene.render.pixel_aspect_x
  47.     aspect_y = scene.render.pixel_aspect_y
  48.     aspect = aspect_x / aspect_y
  49.     start = scene.frame_start
  50.     end = scene.frame_end
  51.     active_cam_frames = get_active_cam_for_each_frame(scene, start, end)
  52.     fps = scene.render.fps
  53.  
  54.     return {
  55.         'scn': scene,
  56.         'width': scene.render.resolution_x,
  57.         'height': scene.render.resolution_y,
  58.         'aspect': aspect,
  59.         'fps': fps,
  60.         'start': start,
  61.         'end': end,
  62.         'duration': (end - start + 1.0) / fps,
  63.         'active_cam_frames': active_cam_frames,
  64.         'curframe': scene.frame_current,
  65.         }
  66.  
  67.  
  68. # create list of active camera for each frame in case active camera is set by markers
  69. def get_active_cam_for_each_frame(scene, start, end):
  70.     active_cam_frames = []
  71.     sorted_markers = []
  72.     markers = scene.timeline_markers
  73.     if markers:
  74.         for marker in markers:
  75.             if marker.camera:
  76.                 sorted_markers.append([marker.frame, marker])
  77.         sorted_markers = sorted(sorted_markers)
  78.  
  79.         if sorted_markers:
  80.             for frame in range(start, end + 1):
  81.                 for m, marker in enumerate(sorted_markers):
  82.                     if marker[0] > frame:
  83.                         if m != 0:
  84.                             active_cam_frames.append(sorted_markers[m - 1][1].camera)
  85.                         else:
  86.                             active_cam_frames.append(marker[1].camera)
  87.                         break
  88.                     elif m == len(sorted_markers) - 1:
  89.                         active_cam_frames.append(marker[1].camera)
  90.     if not active_cam_frames:
  91.         if scene.camera:
  92.             # in this case active_cam_frames array will have legth of 1. This will indicate that there is only one active cam in all frames
  93.             active_cam_frames.append(scene.camera)
  94.  
  95.     return(active_cam_frames)
  96.  
  97.  
  98. # create managable list of selected objects
  99. def get_selected(context):
  100.     cameras = []  # list of selected cameras
  101.     solids = []  # list of all selected meshes that can be exported as AE's solids
  102.     lights = []  # list of all selected lamps that can be exported as AE's lights
  103.     nulls = []  # list of all selected objects exept cameras (will be used to create nulls in AE)
  104.     obs = context.selected_objects
  105.  
  106.     for ob in obs:
  107.         if ob.type == 'CAMERA':
  108.             cameras.append([ob, convert_name(ob.name)])
  109.  
  110.         elif is_plane(ob):
  111.             # not ready yet. is_plane(object) returns False in all cases. This is temporary
  112.             solids.append([ob, convert_name(ob.name)])
  113.  
  114.         elif ob.type == 'LAMP':
  115.             lights.append([ob, ob.data.type + convert_name(ob.name)])  # Type of lamp added to name
  116.  
  117.         else:
  118.             nulls.append([ob, convert_name(ob.name)])
  119.  
  120.     selection = {
  121.         'cameras': cameras,
  122.         'solids': solids,
  123.         'lights': lights,
  124.         'nulls': nulls,
  125.         }
  126.  
  127.     return selection
  128.  
  129.  
  130. # check if object is plane and can be exported as AE's solid
  131. def is_plane(object):
  132.     # work in progress. Not ready yet
  133.     return False
  134.  
  135.  
  136. # convert names of objects to avoid errors in AE.
  137. def convert_name(name):
  138.     name = "_" + name
  139.     '''
  140.    # Digits are not allowed at beginning of AE vars names.
  141.    # This section is commented, as "_" is added at beginning of names anyway.
  142.    # Placeholder for this name modification is left so that it's not ignored if needed
  143.    if name[0].isdigit():
  144.        name = "_" + name
  145.    '''
  146.     name = bpy.path.clean_name(name)
  147.     name = name.replace("-", "_")
  148.  
  149.     return name
  150.  
  151.  
  152. # get object's blender's location rotation and scale and return AE's Position, Rotation/Orientation and scale
  153. # this function will be called for every object for every frame
  154. def convert_transform_matrix(matrix, width, height, aspect, x_rot_correction=False):
  155.  
  156.     # get blender transform data for ob
  157.     b_loc = matrix.to_translation()
  158.     b_rot = matrix.to_euler('ZYX')  # ZYX euler matches AE's orientation and allows to use x_rot_correction
  159.     b_scale = matrix.to_scale()
  160.  
  161.     # convert to AE Position Rotation and Scale
  162.     # Axes in AE are different. AE's X is blender's X, AE's Y is negative Blender's Z, AE's Z is Blender's Y
  163.     x = (b_loc.x * 100.0) / aspect + width / 2.0  # calculate AE's X position
  164.     y = (-b_loc.z * 100.0) + (height / 2.0)  # calculate AE's Y position
  165.     z = b_loc.y * 100.0  # calculate AE's Z position
  166.     # Convert rotations to match AE's orientation.
  167.     rx = degrees(b_rot.x)  # if not x_rot_correction - AE's X orientation = blender's X rotation if 'ZYX' euler.
  168.     ry = -degrees(b_rot.y)  # AE's Y orientation is negative blender's Y rotation if 'ZYX' euler
  169.     rz = -degrees(b_rot.z)  # AE's Z orientation is negative blender's Z rotation if 'ZYX' euler
  170.     if x_rot_correction:
  171.         rx -= 90.0  # In blender - ob of zero rotation lay on floor. In AE layer of zero orientation "stands"
  172.     # Convert scale to AE scale
  173.     sx = b_scale.x * 100.0  # scale of 1.0 is 100% in AE
  174.     sy = b_scale.z * 100.0  # scale of 1.0 is 100% in AE
  175.     sz = b_scale.y * 100.0  # scale of 1.0 is 100% in AE
  176.  
  177.     return x, y, z, rx, ry, rz, sx, sy, sz
  178.  
  179. # get camera's lens and convert to AE's "zoom" value in pixels
  180. # this function will be called for every camera for every frame
  181. #
  182. #
  183. # AE's lens is defined by "zoom" in pixels. Zoom determines focal angle or focal length.
  184. #
  185. # ZOOM VALUE CALCULATIONS:
  186. #
  187. # Given values:
  188. #     - sensor width (camera.data.sensor_width)
  189. #     - sensor height (camera.data.sensor_height)
  190. #     - sensor fit (camera.data.sensor_fit)
  191. #     - lens (blender's lens in mm)
  192. #     - width (width of the composition/scene in pixels)
  193. #     - height (height of the composition/scene in pixels)
  194. #     - PAR (pixel aspect ratio)
  195. #
  196. # Calculations are made using sensor's size and scene/comp dimension (width or height).
  197. # If camera.sensor_fit is set to 'AUTO' or 'HORIZONTAL' - sensor = camera.data.sensor_width, dimension = width.
  198. # If camera.sensor_fit is set to 'VERTICAL' - sensor = camera.data.sensor_height, dimension = height
  199. #
  200. # zoom can be calculated using simple proportions.
  201. #
  202. #                             |
  203. #                           / |
  204. #                         /   |
  205. #                       /     | d
  206. #       s  |\         /       | i
  207. #       e  |  \     /         | m
  208. #       n  |    \ /           | e
  209. #       s  |    / \           | n
  210. #       o  |  /     \         | s
  211. #       r  |/         \       | i
  212. #                       \     | o
  213. #          |     |        \   | n
  214. #          |     |          \ |
  215. #          |     |            |
  216. #           lens |    zoom
  217. #
  218. #    zoom / dimension = lens / sensor   =>
  219. #    zoom = lens * dimension / sensor
  220. #
  221. #    above is true if square pixels are used. If not - aspect compensation is needed, so final formula is:
  222. #    zoom = lens * dimension / sensor * aspect
  223.  
  224.  
  225. def convert_lens(camera, width, height, aspect):
  226.     if camera.data.sensor_fit == 'VERTICAL':
  227.         sensor = camera.data.sensor_height
  228.         dimension = height
  229.     else:
  230.         sensor = camera.data.sensor_width
  231.         dimension = width
  232.  
  233.     zoom = camera.data.lens * dimension / sensor * aspect
  234.  
  235.     return zoom
  236.  
  237. # convert object bundle's matrix. Not ready yet. Temporarily not active
  238. #def get_ob_bundle_matrix_world(cam_matrix_world, bundle_matrix):
  239. #    matrix = cam_matrix_basis
  240. #    return matrix
  241.  
  242.  
  243. # jsx script for AE creation
  244. def write_jsx_file(file, data, selection, include_animation, include_active_cam, include_selected_cams, include_selected_objects, include_cam_bundles):
  245.  
  246.     print("\n---------------------------\n- Export to After Effects -\n---------------------------")
  247.     # store the current frame to restore it at the end of export
  248.     curframe = data['curframe']
  249.     # create array which will contain all keyframes values
  250.     js_data = {
  251.         'times': '',
  252.         'cameras': {},
  253.         'solids': {},  # not ready yet
  254.         'lights': {},
  255.         'nulls': {},
  256.         'bundles_cam': {},
  257.         'bundles_ob': {},  # not ready yet
  258.         }
  259.  
  260.     # create structure for active camera/cameras
  261.     active_cam_name = ''
  262.     if include_active_cam and data['active_cam_frames'] != []:
  263.         # check if more that one active cam exist (true if active cams set by markers)
  264.         if len(data['active_cam_frames']) is 1:
  265.             name_ae = convert_name(data['active_cam_frames'][0].name)  # take name of the only active camera in scene
  266.         else:
  267.             name_ae = 'Active_Camera'
  268.         active_cam_name = name_ae  # store name to be used when creating keyframes for active cam.
  269.         js_data['cameras'][name_ae] = {
  270.             'position': '',
  271.             'position_static': '',
  272.             'position_anim': False,
  273.             'orientation': '',
  274.             'orientation_static': '',
  275.             'orientation_anim': False,
  276.             'zoom': '',
  277.             'zoom_static': '',
  278.             'zoom_anim': False,
  279.             }
  280.  
  281.     # create camera structure for selected cameras
  282.     if include_selected_cams:
  283.         for i, cam in enumerate(selection['cameras']):  # more than one camera can be selected
  284.             if cam[1] != active_cam_name:
  285.                 name_ae = selection['cameras'][i][1]
  286.                 js_data['cameras'][name_ae] = {
  287.                     'position': '',
  288.                     'position_static': '',
  289.                     'position_anim': False,
  290.                     'orientation': '',
  291.                     'orientation_static': '',
  292.                     'orientation_anim': False,
  293.                     'zoom': '',
  294.                     'zoom_static': '',
  295.                     'zoom_anim': False,
  296.                     }
  297.     '''
  298.    # create structure for solids. Not ready yet. Temporarily not active
  299.    for i, obj in enumerate(selection['solids']):
  300.        name_ae = selection['solids'][i][1]
  301.        js_data['solids'][name_ae] = {
  302.            'position': '',
  303.            'orientation': '',
  304.            'rotationX': '',
  305.            'scale': '',
  306.            }
  307.    '''
  308.     # create structure for lights
  309.     for i, obj in enumerate(selection['lights']):
  310.         if include_selected_objects:
  311.             name_ae = selection['lights'][i][1]
  312.             js_data['lights'][name_ae] = {
  313.                 'type': selection['lights'][i][0].data.type,
  314.                 'energy': '',
  315.                 'energy_static': '',
  316.                 'energy_anim': False,
  317.                 'cone_angle': '',
  318.                 'cone_angle_static': '',
  319.                 'cone_angle_anim': False,
  320.                 'cone_feather': '',
  321.                 'cone_feather_static': '',
  322.                 'cone_feather_anim': False,
  323.                 'color': '',
  324.                 'color_static': '',
  325.                 'color_anim': False,
  326.                 'position': '',
  327.                 'position_static': '',
  328.                 'position_anim': False,
  329.                 'orientation': '',
  330.                 'orientation_static': '',
  331.                 'orientation_anim': False,
  332.                 }
  333.  
  334.     # create structure for nulls
  335.     for i, obj in enumerate(selection['nulls']):  # nulls representing blender's obs except cameras, lamps and solids
  336.         if include_selected_objects:
  337.             name_ae = selection['nulls'][i][1]
  338.             js_data['nulls'][name_ae] = {
  339.                 'position': '',
  340.                 'position_static': '',
  341.                 'position_anim': False,
  342.                 'orientation': '',
  343.                 'orientation_static': '',
  344.                 'orientation_anim': False,
  345.                 'scale': '',
  346.                 'scale_static': '',
  347.                 'scale_anim': False,
  348.                 }
  349.  
  350.     # create structure for cam bundles including positions (cam bundles don't move)
  351.     if include_cam_bundles:
  352.         # go through each selected camera and active cameras
  353.         selected_cams = []
  354.         active_cams = []
  355.         if include_active_cam:
  356.             active_cams = data['active_cam_frames']
  357.         if include_selected_cams:
  358.             for cam in selection['cameras']:
  359.                 selected_cams.append(cam[0])
  360.         # list of cameras that will be checked for 'CAMERA SOLVER'
  361.         cams = list(set.union(set(selected_cams), set(active_cams)))
  362.  
  363.         for cam in cams:
  364.             # go through each constraints of this camera
  365.             for constraint in cam.constraints:
  366.                 # does the camera have a Camera Solver constraint
  367.                 if constraint.type == 'CAMERA_SOLVER':
  368.                     # Which movie clip does it use
  369.                     if constraint.use_active_clip:
  370.                         clip = data['scn'].active_clip
  371.                     else:
  372.                         clip = constraint.clip
  373.  
  374.                     # go through each tracking point
  375.                     for track in clip.tracking.tracks:
  376.                         # Does this tracking point have a bundle (has its 3D position been solved)
  377.                         if track.has_bundle:
  378.                             # get the name of the tracker
  379.                             name_ae = convert_name(str(cam.name) + '__' + str(track.name))
  380.                             js_data['bundles_cam'][name_ae] = {
  381.                                 'position': '',
  382.                                 }
  383.                             # bundles are in camera space. Transpose to world space
  384.                             matrix = Matrix.Translation(cam.matrix_basis.copy() * track.bundle)
  385.                             # convert the position into AE space
  386.                             ae_transform = convert_transform_matrix(matrix, data['width'], data['height'], data['aspect'], x_rot_correction=False)
  387.                             js_data['bundles_cam'][name_ae]['position'] += '[%f,%f,%f],' % (ae_transform[0], ae_transform[1], ae_transform[2])
  388.  
  389.     # get all keyframes for each object and store in dico
  390.     if include_animation:
  391.         end = data['end'] + 1
  392.     else:
  393.         end = data['start'] + 1
  394.     for frame in range(data['start'], end):
  395.         print("working on frame: " + str(frame))
  396.         data['scn'].frame_set(frame)
  397.  
  398.         # get time for this loop
  399.         js_data['times'] += '%f ,' % ((frame - data['start']) / data['fps'])
  400.  
  401.         # keyframes for active camera/cameras
  402.         if include_active_cam and data['active_cam_frames'] != []:
  403.             if len(data['active_cam_frames']) == 1:
  404.                 cur_cam_index = 0
  405.             else:
  406.                 cur_cam_index = frame - data['start']
  407.             active_cam = data['active_cam_frames'][cur_cam_index]
  408.             # get cam name
  409.             name_ae = active_cam_name
  410.             # convert cam transform properties to AE space
  411.             ae_transform = convert_transform_matrix(active_cam.matrix_world.copy(), data['width'], data['height'], data['aspect'], x_rot_correction=True)
  412.             # convert Blender's lens to AE's zoom in pixels
  413.             zoom = convert_lens(active_cam, data['width'], data['height'], data['aspect'])
  414.             # store all values in dico
  415.             position = '[%f,%f,%f],' % (ae_transform[0], ae_transform[1], ae_transform[2])
  416.             orientation = '[%f,%f,%f],' % (ae_transform[3], ae_transform[4], ae_transform[5])
  417.             zoom = '%f,' % (zoom)
  418.             js_data['cameras'][name_ae]['position'] += position
  419.             js_data['cameras'][name_ae]['orientation'] += orientation
  420.             js_data['cameras'][name_ae]['zoom'] += zoom
  421.             # Check if properties change values compared to previous frame
  422.             # If property don't change through out the whole animation - keyframes won't be added
  423.             if frame != data['start']:
  424.                 if position != js_data['cameras'][name_ae]['position_static']:
  425.                     js_data['cameras'][name_ae]['position_anim'] = True
  426.                 if orientation != js_data['cameras'][name_ae]['orientation_static']:
  427.                     js_data['cameras'][name_ae]['orientation_anim'] = True
  428.                 if zoom != js_data['cameras'][name_ae]['zoom_static']:
  429.                     js_data['cameras'][name_ae]['zoom_anim'] = True
  430.             js_data['cameras'][name_ae]['position_static'] = position
  431.             js_data['cameras'][name_ae]['orientation_static'] = orientation
  432.             js_data['cameras'][name_ae]['zoom_static'] = zoom
  433.  
  434.         # keyframes for selected cameras
  435.         if include_selected_cams:
  436.             for i, cam in enumerate(selection['cameras']):
  437.                 if cam[1] != active_cam_name:
  438.                     # get cam name
  439.                     name_ae = selection['cameras'][i][1]
  440.                     # convert cam transform properties to AE space
  441.                     ae_transform = convert_transform_matrix(cam[0].matrix_world.copy(), data['width'], data['height'], data['aspect'], x_rot_correction=True)
  442.                     # convert Blender's lens to AE's zoom in pixels
  443.                     zoom = convert_lens(cam[0], data['width'], data['height'], data['aspect'])
  444.                     # store all values in dico
  445.                     position = '[%f,%f,%f],' % (ae_transform[0], ae_transform[1], ae_transform[2])
  446.                     orientation = '[%f,%f,%f],' % (ae_transform[3], ae_transform[4], ae_transform[5])
  447.                     zoom = '%f,' % (zoom)
  448.                     js_data['cameras'][name_ae]['position'] += position
  449.                     js_data['cameras'][name_ae]['orientation'] += orientation
  450.                     js_data['cameras'][name_ae]['zoom'] += zoom
  451.                     # Check if properties change values compared to previous frame
  452.                     # If property don't change through out the whole animation - keyframes won't be added
  453.                     if frame != data['start']:
  454.                         if position != js_data['cameras'][name_ae]['position_static']:
  455.                             js_data['cameras'][name_ae]['position_anim'] = True
  456.                         if orientation != js_data['cameras'][name_ae]['orientation_static']:
  457.                             js_data['cameras'][name_ae]['orientation_anim'] = True
  458.                         if zoom != js_data['cameras'][name_ae]['zoom_static']:
  459.                             js_data['cameras'][name_ae]['zoom_anim'] = True
  460.                     js_data['cameras'][name_ae]['position_static'] = position
  461.                     js_data['cameras'][name_ae]['orientation_static'] = orientation
  462.                     js_data['cameras'][name_ae]['zoom_static'] = zoom
  463.  
  464.         '''
  465.        # keyframes for all solids. Not ready yet. Temporarily not active
  466.        for i, ob in enumerate(selection['solids']):
  467.            #get object name
  468.            name_ae = selection['solids'][i][1]
  469.            #convert ob position to AE space
  470.        '''
  471.  
  472.         # keyframes for all lights.
  473.         if include_selected_objects:
  474.             for i, ob in enumerate(selection['lights']):
  475.                 #get object name
  476.                 name_ae = selection['lights'][i][1]
  477.                 type = selection['lights'][i][0].data.type
  478.                 # convert ob transform properties to AE space
  479.                 ae_transform = convert_transform_matrix(ob[0].matrix_world.copy(), data['width'], data['height'], data['aspect'], x_rot_correction=True)
  480.                 color = ob[0].data.color
  481.                 # store all values in dico
  482.                 position = '[%f,%f,%f],' % (ae_transform[0], ae_transform[1], ae_transform[2])
  483.                 orientation = '[%f,%f,%f],' % (ae_transform[3], ae_transform[4], ae_transform[5])
  484.                 energy = '[%f],' % (ob[0].data.energy * 100.0)
  485.                 color = '[%f,%f,%f],' % (color[0], color[1], color[2])
  486.                 js_data['lights'][name_ae]['position'] += position
  487.                 js_data['lights'][name_ae]['orientation'] += orientation
  488.                 js_data['lights'][name_ae]['energy'] += energy
  489.                 js_data['lights'][name_ae]['color'] += color
  490.                 # Check if properties change values compared to previous frame
  491.                 # If property don't change through out the whole animation - keyframes won't be added
  492.                 if frame != data['start']:
  493.                     if position != js_data['lights'][name_ae]['position_static']:
  494.                         js_data['lights'][name_ae]['position_anim'] = True
  495.                     if orientation != js_data['lights'][name_ae]['orientation_static']:
  496.                         js_data['lights'][name_ae]['orientation_anim'] = True
  497.                     if energy != js_data['lights'][name_ae]['energy_static']:
  498.                         js_data['lights'][name_ae]['energy_anim'] = True
  499.                     if color != js_data['lights'][name_ae]['color_static']:
  500.                         js_data['lights'][name_ae]['color_anim'] = True
  501.                 js_data['lights'][name_ae]['position_static'] = position
  502.                 js_data['lights'][name_ae]['orientation_static'] = orientation
  503.                 js_data['lights'][name_ae]['energy_static'] = energy
  504.                 js_data['lights'][name_ae]['color_static'] = color
  505.                 if type == 'SPOT':
  506.                     cone_angle = '[%f],' % (degrees(ob[0].data.spot_size))
  507.                     cone_feather = '[%f],' % (ob[0].data.spot_blend * 100.0)
  508.                     js_data['lights'][name_ae]['cone_angle'] += cone_angle
  509.                     js_data['lights'][name_ae]['cone_feather'] += cone_feather
  510.                     # Check if properties change values compared to previous frame
  511.                     # If property don't change through out the whole animation - keyframes won't be added
  512.                     if frame != data['start']:
  513.                         if cone_angle != js_data['lights'][name_ae]['cone_angle_static']:
  514.                             js_data['lights'][name_ae]['cone_angle_anim'] = True
  515.                         if orientation != js_data['lights'][name_ae]['cone_feather_static']:
  516.                             js_data['lights'][name_ae]['cone_feather_anim'] = True
  517.                     js_data['lights'][name_ae]['cone_angle_static'] = cone_angle
  518.                     js_data['lights'][name_ae]['cone_feather_static'] = cone_feather
  519.  
  520.         # keyframes for all nulls
  521.         if include_selected_objects:
  522.             for i, ob in enumerate(selection['nulls']):
  523.                 # get object name
  524.                 name_ae = selection['nulls'][i][1]
  525.                 # convert ob transform properties to AE space
  526.                 ae_transform = convert_transform_matrix(ob[0].matrix_world.copy(), data['width'], data['height'], data['aspect'], x_rot_correction=True)
  527.                 # store all values in dico
  528.                 position = '[%f,%f,%f],' % (ae_transform[0], ae_transform[1], ae_transform[2])
  529.                 orientation = '[%f,%f,%f],' % (ae_transform[3], ae_transform[4], ae_transform[5])
  530.                 scale = '[%f,%f,%f],' % (ae_transform[6], ae_transform[7], ae_transform[8])
  531.                 js_data['nulls'][name_ae]['position'] += position
  532.                 js_data['nulls'][name_ae]['orientation'] += orientation
  533.                 js_data['nulls'][name_ae]['scale'] += scale
  534.                 # Check if properties change values compared to previous frame
  535.                 # If property don't change through out the whole animation - keyframes won't be added
  536.                 if frame != data['start']:
  537.                     if position != js_data['nulls'][name_ae]['position_static']:
  538.                         js_data['nulls'][name_ae]['position_anim'] = True
  539.                     if orientation != js_data['nulls'][name_ae]['orientation_static']:
  540.                         js_data['nulls'][name_ae]['orientation_anim'] = True
  541.                     if scale != js_data['nulls'][name_ae]['scale_static']:
  542.                         js_data['nulls'][name_ae]['scale_anim'] = True
  543.                 js_data['nulls'][name_ae]['position_static'] = position
  544.                 js_data['nulls'][name_ae]['orientation_static'] = orientation
  545.                 js_data['nulls'][name_ae]['scale_static'] = scale
  546.  
  547.         # keyframes for all object bundles. Not ready yet.
  548.         #
  549.         #
  550.         #
  551.  
  552.     # ---- write JSX file
  553.     jsx_file = open(file, 'w')
  554.  
  555.     # make the jsx executable in After Effects (enable double click on jsx)
  556.     jsx_file.write('#target AfterEffects\n\n')
  557.     # Script's header
  558.     jsx_file.write('/**************************************\n')
  559.     jsx_file.write('Scene : %s\n' % data['scn'].name)
  560.     jsx_file.write('Resolution : %i x %i\n' % (data['width'], data['height']))
  561.     jsx_file.write('Duration : %f\n' % (data['duration']))
  562.     jsx_file.write('FPS : %f\n' % (data['fps']))
  563.     jsx_file.write('Date : %s\n' % datetime.datetime.now())
  564.     jsx_file.write('Exported with io_export_after_effects.py\n')
  565.     jsx_file.write('**************************************/\n\n\n\n')
  566.  
  567.     # wrap in function
  568.     jsx_file.write("function compFromBlender(){\n")
  569.     # create new comp
  570.     jsx_file.write('\nvar compName = prompt("Blender Comp\'s Name \\nEnter Name of newly created Composition","BlendComp","Composition\'s Name");\n')
  571.     jsx_file.write('if (compName){')  # Continue only if comp name is given. If not - terminate
  572.     jsx_file.write('\nvar newComp = app.project.items.addComp(compName, %i, %i, %f, %f, %i);' %
  573.                    (data['width'], data['height'], data['aspect'], data['duration'], data['fps']))
  574.     jsx_file.write('\nnewComp.displayStartTime = %f;\n\n\n' % ((data['start'] + 1.0) / data['fps']))
  575.  
  576.     # create camera bundles (nulls)
  577.     jsx_file.write('// **************  CAMERA 3D MARKERS  **************\n\n\n')
  578.     for i, obj in enumerate(js_data['bundles_cam']):
  579.         name_ae = obj
  580.         jsx_file.write('var %s = newComp.layers.addNull();\n' % (name_ae))
  581.         jsx_file.write('%s.threeDLayer = true;\n' % name_ae)
  582.         jsx_file.write('%s.source.name = "%s";\n' % (name_ae, name_ae))
  583.         jsx_file.write('%s.property("position").setValue(%s);\n\n\n' % (name_ae, js_data['bundles_cam'][obj]['position']))
  584.  
  585.     # create object bundles (not ready yet)
  586.  
  587.     # create objects (nulls)
  588.     jsx_file.write('// **************  OBJECTS  **************\n\n\n')
  589.     for i, obj in enumerate(js_data['nulls']):
  590.         name_ae = obj
  591.         jsx_file.write('var %s = newComp.layers.addNull();\n' % (name_ae))
  592.         jsx_file.write('%s.threeDLayer = true;\n' % name_ae)
  593.         jsx_file.write('%s.source.name = "%s";\n' % (name_ae, name_ae))
  594.         # Set values of properties, add kyeframes only where needed
  595.         if include_animation and js_data['nulls'][name_ae]['position_anim']:
  596.             jsx_file.write('%s.property("position").setValuesAtTimes([%s],[%s]);\n' % (name_ae, js_data['times'], js_data['nulls'][obj]['position']))
  597.         else:
  598.             jsx_file.write('%s.property("position").setValue(%s);\n' % (name_ae, js_data['nulls'][obj]['position_static']))
  599.         if include_animation and js_data['nulls'][name_ae]['orientation_anim']:
  600.             jsx_file.write('%s.property("orientation").setValuesAtTimes([%s],[%s]);\n' % (name_ae, js_data['times'], js_data['nulls'][obj]['orientation']))
  601.         else:
  602.             jsx_file.write('%s.property("orientation").setValue(%s);\n' % (name_ae, js_data['nulls'][obj]['orientation_static']))
  603.         if include_animation and js_data['nulls'][name_ae]['scale_anim']:
  604.             jsx_file.write('%s.property("scale").setValuesAtTimes([%s],[%s]);\n\n\n' % (name_ae, js_data['times'], js_data['nulls'][obj]['scale']))
  605.         else:
  606.             jsx_file.write('%s.property("scale").setValue(%s);\n\n\n' % (name_ae, js_data['nulls'][obj]['scale_static']))
  607.     # create solids (not ready yet)
  608.  
  609.     # create lights
  610.     jsx_file.write('// **************  LIGHTS  **************\n\n\n')
  611.     for i, obj in enumerate(js_data['lights']):
  612.         name_ae = obj
  613.         jsx_file.write('var %s = newComp.layers.addLight("%s", [0.0, 0.0]);\n' % (name_ae, name_ae))
  614.         jsx_file.write('%s.autoOrient = AutoOrientType.NO_AUTO_ORIENT;\n' % name_ae)
  615.         # Set values of properties, add kyeframes only where needed
  616.         if include_animation and js_data['lights'][name_ae]['position_anim']:
  617.             jsx_file.write('%s.property("position").setValuesAtTimes([%s],[%s]);\n' % (name_ae, js_data['times'], js_data['lights'][obj]['position']))
  618.         else:
  619.             jsx_file.write('%s.property("position").setValue(%s);\n' % (name_ae, js_data['lights'][obj]['position_static']))
  620.         if include_animation and js_data['lights'][name_ae]['orientation_anim']:
  621.             jsx_file.write('%s.property("orientation").setValuesAtTimes([%s],[%s]);\n' % (name_ae, js_data['times'], js_data['lights'][obj]['orientation']))
  622.         else:
  623.             jsx_file.write('%s.property("orientation").setValue(%s);\n' % (name_ae, js_data['lights'][obj]['orientation_static']))
  624.         if include_animation and js_data['lights'][name_ae]['energy_anim']:
  625.             jsx_file.write('%s.property("intensity").setValuesAtTimes([%s],[%s]);\n' % (name_ae, js_data['times'], js_data['lights'][obj]['energy']))
  626.         else:
  627.             jsx_file.write('%s.property("intensity").setValue(%s);\n' % (name_ae, js_data['lights'][obj]['energy_static']))
  628.         if include_animation and js_data['lights'][name_ae]['color_anim']:
  629.             jsx_file.write('%s.property("Color").setValuesAtTimes([%s],[%s]);\n' % (name_ae, js_data['times'], js_data['lights'][obj]['color']))
  630.         else:
  631.             jsx_file.write('%s.property("Color").setValue(%s);\n' % (name_ae, js_data['lights'][obj]['color_static']))
  632.             if js_data['lights'][obj]['type'] == 'SPOT':
  633.                 if include_animation and js_data['lights'][name_ae]['cone_angle_anim']:
  634.                     jsx_file.write('%s.property("Cone Angle").setValuesAtTimes([%s],[%s]);\n' % (name_ae, js_data['times'], js_data['lights'][obj]['cone_angle']))
  635.                 else:
  636.                     jsx_file.write('%s.property("Cone Angle").setValue(%s);\n' % (name_ae, js_data['lights'][obj]['cone_angle_static']))
  637.                 if include_animation and js_data['lights'][name_ae]['cone_feather_anim']:
  638.                     jsx_file.write('%s.property("Cone Feather").setValuesAtTimes([%s],[%s]);\n' % (name_ae, js_data['times'], js_data['lights'][obj]['cone_feather']))
  639.                 else:
  640.                     jsx_file.write('%s.property("Cone Feather").setValue(%s);\n' % (name_ae, js_data['lights'][obj]['cone_feather_static']))
  641.         jsx_file.write('\n\n')
  642.  
  643.     # create cameras
  644.     jsx_file.write('// **************  CAMERAS  **************\n\n\n')
  645.     for i, cam in enumerate(js_data['cameras']):  # more than one camera can be selected
  646.         name_ae = cam
  647.         jsx_file.write('var %s = newComp.layers.addCamera("%s",[0,0]);\n' % (name_ae, name_ae))
  648.         jsx_file.write('%s.autoOrient = AutoOrientType.NO_AUTO_ORIENT;\n' % name_ae)
  649.         # Set values of properties, add kyeframes only where needed
  650.         if include_animation and js_data['cameras'][name_ae]['position_anim']:
  651.             jsx_file.write('%s.property("position").setValuesAtTimes([%s],[%s]);\n' % (name_ae, js_data['times'], js_data['cameras'][cam]['position']))
  652.         else:
  653.             jsx_file.write('%s.property("position").setValue(%s);\n' % (name_ae, js_data['cameras'][cam]['position_static']))
  654.         if include_animation and js_data['cameras'][name_ae]['orientation_anim']:
  655.             jsx_file.write('%s.property("orientation").setValuesAtTimes([%s],[%s]);\n' % (name_ae, js_data['times'], js_data['cameras'][cam]['orientation']))
  656.         else:
  657.             jsx_file.write('%s.property("orientation").setValue(%s);\n' % (name_ae, js_data['cameras'][cam]['orientation_static']))
  658.         if include_animation and js_data['cameras'][name_ae]['zoom_anim']:
  659.             jsx_file.write('%s.property("zoom").setValuesAtTimes([%s],[%s]);\n\n\n' % (name_ae, js_data['times'], js_data['cameras'][cam]['zoom']))
  660.         else:
  661.             jsx_file.write('%s.property("zoom").setValue(%s);\n\n\n' % (name_ae, js_data['cameras'][cam]['zoom_static']))
  662.  
  663.     # Exit import if no comp name given
  664.     jsx_file.write('\n}else{alert ("Exit Import Blender animation data \\nNo Comp\'s name has been chosen","EXIT")};')
  665.     # Close function
  666.     jsx_file.write("}\n\n\n")
  667.     # Execute function. Wrap in "undo group" for easy undoing import process
  668.     jsx_file.write('app.beginUndoGroup("Import Blender animation data");\n')
  669.     jsx_file.write('compFromBlender();\n')  # execute function
  670.     jsx_file.write('app.endUndoGroup();\n\n\n')
  671.     jsx_file.close()
  672.  
  673.     data['scn'].frame_set(curframe)  # set current frame of animation in blender to state before export
  674.  
  675. ##########################################
  676. # DO IT
  677. ##########################################
  678.  
  679.  
  680. def main(file, context, include_animation, include_active_cam, include_selected_cams, include_selected_objects, include_cam_bundles):
  681.     data = get_comp_data(context)
  682.     selection = get_selected(context)
  683.     write_jsx_file(file, data, selection, include_animation, include_active_cam, include_selected_cams, include_selected_objects, include_cam_bundles)
  684.     print ("\nExport to After Effects Completed")
  685.     return {'FINISHED'}
  686.  
  687. ##########################################
  688. # ExportJsx class register/unregister
  689. ##########################################
  690.  
  691. from bpy_extras.io_utils import ExportHelper
  692. from bpy.props import StringProperty, BoolProperty
  693.  
  694.  
  695. class ExportJsx(bpy.types.Operator, ExportHelper):
  696.     """Export selected cameras and objects animation to After Effects"""
  697.     bl_idname = "export.jsx"
  698.     bl_label = "Export to Adobe After Effects"
  699.     filename_ext = ".jsx"
  700.     filter_glob = StringProperty(default="*.jsx", options={'HIDDEN'})
  701.  
  702.     include_animation = BoolProperty(
  703.             name="Animation",
  704.             description="Animate Exported Cameras and Objects",
  705.             default=True,
  706.             )
  707.     include_active_cam = BoolProperty(
  708.             name="Active Camera",
  709.             description="Include Active Camera",
  710.             default=True,
  711.             )
  712.     include_selected_cams = BoolProperty(
  713.             name="Selected Cameras",
  714.             description="Add Selected Cameras",
  715.             default=True,
  716.             )
  717.     include_selected_objects = BoolProperty(
  718.             name="Selected Objects",
  719.             description="Export Selected Objects",
  720.             default=True,
  721.             )
  722.     include_cam_bundles = BoolProperty(
  723.             name="Camera 3D Markers",
  724.             description="Include 3D Markers of Camera Motion Solution for selected cameras",
  725.             default=True,
  726.             )
  727. #    include_ob_bundles = BoolProperty(
  728. #            name="Objects 3D Markers",
  729. #            description="Include 3D Markers of Object Motion Solution for selected cameras",
  730. #            default=True,
  731. #            )
  732.  
  733.     def draw(self, context):
  734.         layout = self.layout
  735.  
  736.         box = layout.box()
  737.         box.label(text="Animation:")
  738.         box.prop(self, 'include_animation')
  739.         box.label(text='Include Cameras and Objects:')
  740.         box.prop(self, 'include_active_cam')
  741.         box.prop(self, 'include_selected_cams')
  742.         box.prop(self, 'include_selected_objects')
  743.         box.label(text="Include Tracking Data:")
  744.         box.prop(self, 'include_cam_bundles')
  745. #        box.prop(self, 'include_ob_bundles')
  746.  
  747.     @classmethod
  748.     def poll(cls, context):
  749.         active = context.active_object
  750.         selected = context.selected_objects
  751.         camera = context.scene.camera
  752.         ok = selected or camera
  753.         return ok
  754.  
  755.     def execute(self, context):
  756.         return main(self.filepath, context, self.include_animation, self.include_active_cam, self.include_selected_cams, self.include_selected_objects, self.include_cam_bundles)
  757.  
  758.  
  759. def menu_func(self, context):
  760.     self.layout.operator(ExportJsx.bl_idname, text="Adobe After Effects (.jsx)")
  761.  
  762. def register():
  763.     bpy.utils.register_class(ExportJsx)
  764.     bpy.types.TOPBAR_MT_file_export.append(menu_func)
  765. def unregister():
  766.     bpy.utils.unregister_class(ExportJsx)
  767.     bpy.types.TOPBAR_MT_file_export.remove(menu_func)
  768. if __name__ == "__main__":
  769.     register()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement