diff --git a/io_export_after_effects.py b/io_export_after_effects.py
new file mode 100644
index 0000000000000000000000000000000000000000..c0afe51f124c9082f6bf200645a40df11d81bcef
--- /dev/null
+++ b/io_export_after_effects.py
@@ -0,0 +1,779 @@
+# ##### 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 #####
+
+# <pep8 compliant>
+
+bl_info = {
+    "name": "Export: Adobe After Effects (.jsx)",
+    "description": "Export cameras, selected objects & camera solution "
+        "3D Markers to Adobe After Effects CS3 and above",
+    "author": "Bartek Skorupa",
+    "version": (0, 65),
+    "blender": (2, 79, 0),
+    "location": "File > Export > Adobe After Effects (.jsx)",
+    "warning": "",
+    "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
+                "Scripts/Import-Export/Adobe_After_Effects",
+    "category": "Import-Export",
+}
+
+
+import bpy
+import datetime
+from math import degrees, floor
+from mathutils import Matrix
+
+
+# create list of static blender's data
+def get_comp_data(context):
+    scene = context.scene
+    aspect_x = scene.render.pixel_aspect_x
+    aspect_y = scene.render.pixel_aspect_y
+    aspect = aspect_x / aspect_y
+    start = scene.frame_start
+    end = scene.frame_end
+    active_cam_frames = get_active_cam_for_each_frame(scene, start, end)
+    fps = floor(scene.render.fps / (scene.render.fps_base) * 1000.0) / 1000.0
+
+    return {
+        'scn': scene,
+        'width': scene.render.resolution_x,
+        'height': scene.render.resolution_y,
+        'aspect': aspect,
+        'fps': fps,
+        'start': start,
+        'end': end,
+        'duration': (end - start + 1.0) / fps,
+        'active_cam_frames': active_cam_frames,
+        'curframe': scene.frame_current,
+        }
+
+
+# create list of active camera for each frame in case active camera is set by markers
+def get_active_cam_for_each_frame(scene, start, end):
+    active_cam_frames = []
+    sorted_markers = []
+    markers = scene.timeline_markers
+    if markers:
+        for marker in markers:
+            if marker.camera:
+                sorted_markers.append([marker.frame, marker])
+        sorted_markers = sorted(sorted_markers)
+
+        if sorted_markers:
+            for frame in range(start, end + 1):
+                for m, marker in enumerate(sorted_markers):
+                    if marker[0] > frame:
+                        if m != 0:
+                            active_cam_frames.append(sorted_markers[m - 1][1].camera)
+                        else:
+                            active_cam_frames.append(marker[1].camera)
+                        break
+                    elif m == len(sorted_markers) - 1:
+                        active_cam_frames.append(marker[1].camera)
+    if not active_cam_frames:
+        if scene.camera:
+            # 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
+            active_cam_frames.append(scene.camera)
+
+    return(active_cam_frames)
+
+
+# create manageable list of selected objects
+def get_selected(context):
+    cameras = []  # list of selected cameras
+    solids = []  # list of all selected meshes that can be exported as AE's solids
+    lights = []  # list of all selected lamps that can be exported as AE's lights
+    nulls = []  # list of all selected objects except cameras (will be used to create nulls in AE)
+    obs = context.selected_objects
+
+    for ob in obs:
+        if ob.type == 'CAMERA':
+            cameras.append([ob, convert_name(ob.name)])
+
+        elif is_plane(ob):
+            # not ready yet. is_plane(object) returns False in all cases. This is temporary
+            solids.append([ob, convert_name(ob.name)])
+
+        elif ob.type == 'LIGHT':
+            lights.append([ob, ob.data.type + convert_name(ob.name)])  # Type of lamp added to name
+
+        else:
+            nulls.append([ob, convert_name(ob.name)])
+
+    selection = {
+        'cameras': cameras,
+        'solids': solids,
+        'lights': lights,
+        'nulls': nulls,
+        }
+
+    return selection
+
+
+# check if object is plane and can be exported as AE's solid
+def is_plane(object):
+    # work in progress. Not ready yet
+    return False
+
+
+# convert names of objects to avoid errors in AE.
+def convert_name(name):
+    name = "_" + name
+    '''
+    # Digits are not allowed at beginning of AE vars names.
+    # This section is commented, as "_" is added at beginning of names anyway.
+    # Placeholder for this name modification is left so that it's not ignored if needed
+    if name[0].isdigit():
+        name = "_" + name
+    '''
+    name = bpy.path.clean_name(name)
+    name = name.replace("-", "_")
+
+    return name
+
+
+# get object's blender's location rotation and scale and return AE's Position, Rotation/Orientation and scale
+# this function will be called for every object for every frame
+def convert_transform_matrix(matrix, width, height, aspect, x_rot_correction=False, ae_size=100.0):
+
+    # get blender transform data for ob
+    b_loc = matrix.to_translation()
+    b_rot = matrix.to_euler('ZYX')  # ZYX euler matches AE's orientation and allows to use x_rot_correction
+    b_scale = matrix.to_scale()
+
+    # convert to AE Position Rotation and Scale
+    # 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
+    x = (b_loc.x * ae_size) / aspect + width / 2.0  # calculate AE's X position
+    y = (-b_loc.z * ae_size) + (height / 2.0)  # calculate AE's Y position
+    z = b_loc.y * ae_size  # calculate AE's Z position
+    # Convert rotations to match AE's orientation.
+    rx = degrees(b_rot.x)  # if not x_rot_correction - AE's X orientation = blender's X rotation if 'ZYX' euler.
+    ry = -degrees(b_rot.y)  # AE's Y orientation is negative blender's Y rotation if 'ZYX' euler
+    rz = -degrees(b_rot.z)  # AE's Z orientation is negative blender's Z rotation if 'ZYX' euler
+    if x_rot_correction:
+        rx -= 90.0  # In blender - ob of zero rotation lay on floor. In AE layer of zero orientation "stands"
+    # Convert scale to AE scale
+    sx = b_scale.x * 100.0  # scale of 1.0 is 100% in AE
+    sy = b_scale.z * 100.0  # scale of 1.0 is 100% in AE
+    sz = b_scale.y * 100.0  # scale of 1.0 is 100% in AE
+
+    return x, y, z, rx, ry, rz, sx, sy, sz
+
+# get camera's lens and convert to AE's "zoom" value in pixels
+# this function will be called for every camera for every frame
+#
+#
+# AE's lens is defined by "zoom" in pixels. Zoom determines focal angle or focal length.
+#
+# ZOOM VALUE CALCULATIONS:
+#
+# Given values:
+#     - sensor width (camera.data.sensor_width)
+#     - sensor height (camera.data.sensor_height)
+#     - sensor fit (camera.data.sensor_fit)
+#     - lens (blender's lens in mm)
+#     - width (width of the composition/scene in pixels)
+#     - height (height of the composition/scene in pixels)
+#     - PAR (pixel aspect ratio)
+#
+# Calculations are made using sensor's size and scene/comp dimension (width or height).
+# If camera.sensor_fit is set to 'AUTO' or 'HORIZONTAL' - sensor = camera.data.sensor_width, dimension = width.
+# If camera.sensor_fit is set to 'VERTICAL' - sensor = camera.data.sensor_height, dimension = height
+#
+# zoom can be calculated using simple proportions.
+#
+#                             |
+#                           / |
+#                         /   |
+#                       /     | d
+#       s  |\         /       | i
+#       e  |  \     /         | m
+#       n  |    \ /           | e
+#       s  |    / \           | n
+#       o  |  /     \         | s
+#       r  |/         \       | i
+#                       \     | o
+#          |     |        \   | n
+#          |     |          \ |
+#          |     |            |
+#           lens |    zoom
+#
+#    zoom / dimension = lens / sensor   =>
+#    zoom = lens * dimension / sensor
+#
+#    above is true if square pixels are used. If not - aspect compensation is needed, so final formula is:
+#    zoom = lens * dimension / sensor * aspect
+
+
+def convert_lens(camera, width, height, aspect):
+    if camera.data.sensor_fit == 'VERTICAL':
+        sensor = camera.data.sensor_height
+        dimension = height
+    else:
+        sensor = camera.data.sensor_width
+        dimension = width
+
+    zoom = camera.data.lens * dimension / sensor * aspect
+
+    return zoom
+
+# convert object bundle's matrix. Not ready yet. Temporarily not active
+#def get_ob_bundle_matrix_world(cam_matrix_world, bundle_matrix):
+#    matrix = cam_matrix_basis
+#    return matrix
+
+
+# jsx script for AE creation
+def write_jsx_file(file, data, selection, include_animation, include_active_cam, include_selected_cams, include_selected_objects, include_cam_bundles, ae_size):
+
+    print("\n---------------------------\n- Export to After Effects -\n---------------------------")
+    # store the current frame to restore it at the end of export
+    curframe = data['curframe']
+    # create array which will contain all keyframes values
+    js_data = {
+        'times': '',
+        'cameras': {},
+        'solids': {},  # not ready yet
+        'lights': {},
+        'nulls': {},
+        'bundles_cam': {},
+        'bundles_ob': {},  # not ready yet
+        }
+
+    # create structure for active camera/cameras
+    active_cam_name = ''
+    if include_active_cam and data['active_cam_frames'] != []:
+        # check if more that one active cam exist (true if active cams set by markers)
+        if len(data['active_cam_frames']) is 1:
+            name_ae = convert_name(data['active_cam_frames'][0].name)  # take name of the only active camera in scene
+        else:
+            name_ae = 'Active_Camera'
+        active_cam_name = name_ae  # store name to be used when creating keyframes for active cam.
+        js_data['cameras'][name_ae] = {
+            'position': '',
+            'position_static': '',
+            'position_anim': False,
+            'orientation': '',
+            'orientation_static': '',
+            'orientation_anim': False,
+            'zoom': '',
+            'zoom_static': '',
+            'zoom_anim': False,
+            }
+
+    # create camera structure for selected cameras
+    if include_selected_cams:
+        for i, cam in enumerate(selection['cameras']):  # more than one camera can be selected
+            if cam[1] != active_cam_name:
+                name_ae = selection['cameras'][i][1]
+                js_data['cameras'][name_ae] = {
+                    'position': '',
+                    'position_static': '',
+                    'position_anim': False,
+                    'orientation': '',
+                    'orientation_static': '',
+                    'orientation_anim': False,
+                    'zoom': '',
+                    'zoom_static': '',
+                    'zoom_anim': False,
+                    }
+    '''
+    # create structure for solids. Not ready yet. Temporarily not active
+    for i, obj in enumerate(selection['solids']):
+        name_ae = selection['solids'][i][1]
+        js_data['solids'][name_ae] = {
+            'position': '',
+            'orientation': '',
+            'rotationX': '',
+            'scale': '',
+            }
+    '''
+    # create structure for lights
+    for i, obj in enumerate(selection['lights']):
+        if include_selected_objects:
+            name_ae = selection['lights'][i][1]
+            js_data['lights'][name_ae] = {
+                'type': selection['lights'][i][0].data.type,
+                'energy': '',
+                'energy_static': '',
+                'energy_anim': False,
+                'cone_angle': '',
+                'cone_angle_static': '',
+                'cone_angle_anim': False,
+                'cone_feather': '',
+                'cone_feather_static': '',
+                'cone_feather_anim': False,
+                'color': '',
+                'color_static': '',
+                'color_anim': False,
+                'position': '',
+                'position_static': '',
+                'position_anim': False,
+                'orientation': '',
+                'orientation_static': '',
+                'orientation_anim': False,
+                }
+
+    # create structure for nulls
+    for i, obj in enumerate(selection['nulls']):  # nulls representing blender's obs except cameras, lamps and solids
+        if include_selected_objects:
+            name_ae = selection['nulls'][i][1]
+            js_data['nulls'][name_ae] = {
+                'position': '',
+                'position_static': '',
+                'position_anim': False,
+                'orientation': '',
+                'orientation_static': '',
+                'orientation_anim': False,
+                'scale': '',
+                'scale_static': '',
+                'scale_anim': False,
+                }
+
+    # create structure for cam bundles including positions (cam bundles don't move)
+    if include_cam_bundles:
+        # go through each selected camera and active cameras
+        selected_cams = []
+        active_cams = []
+        if include_active_cam:
+            active_cams = data['active_cam_frames']
+        if include_selected_cams:
+            for cam in selection['cameras']:
+                selected_cams.append(cam[0])
+        # list of cameras that will be checked for 'CAMERA SOLVER'
+        cams = list(set.union(set(selected_cams), set(active_cams)))
+
+        for cam in cams:
+            # go through each constraints of this camera
+            for constraint in cam.constraints:
+                # does the camera have a Camera Solver constraint
+                if constraint.type == 'CAMERA_SOLVER':
+                    # Which movie clip does it use
+                    if constraint.use_active_clip:
+                        clip = data['scn'].active_clip
+                    else:
+                        clip = constraint.clip
+
+                    # go through each tracking point
+                    for track in clip.tracking.tracks:
+                        # Does this tracking point have a bundle (has its 3D position been solved)
+                        if track.has_bundle:
+                            # get the name of the tracker
+                            name_ae = convert_name(str(cam.name) + '__' + str(track.name))
+                            js_data['bundles_cam'][name_ae] = {
+                                'position': '',
+                                }
+                            # bundles are in camera space. Transpose to world space
+                            matrix = Matrix.Translation(cam.matrix_basis.copy() * track.bundle)
+                            # convert the position into AE space
+                            ae_transform = convert_transform_matrix(matrix, data['width'], data['height'], data['aspect'], False, ae_size)
+                            js_data['bundles_cam'][name_ae]['position'] += '[%f,%f,%f],' % (ae_transform[0], ae_transform[1], ae_transform[2])
+
+    # get all keyframes for each object and store in dico
+    if include_animation:
+        end = data['end'] + 1
+    else:
+        end = data['start'] + 1
+    for frame in range(data['start'], end):
+        print("working on frame: " + str(frame))
+        data['scn'].frame_set(frame)
+
+        # get time for this loop
+        js_data['times'] += '%f ,' % ((frame - data['start']) / data['fps'])
+
+        # keyframes for active camera/cameras
+        if include_active_cam and data['active_cam_frames'] != []:
+            if len(data['active_cam_frames']) == 1:
+                cur_cam_index = 0
+            else:
+                cur_cam_index = frame - data['start']
+            active_cam = data['active_cam_frames'][cur_cam_index]
+            # get cam name
+            name_ae = active_cam_name
+            # convert cam transform properties to AE space
+            ae_transform = convert_transform_matrix(active_cam.matrix_world.copy(), data['width'], data['height'], data['aspect'], True, ae_size)
+            # convert Blender's lens to AE's zoom in pixels
+            zoom = convert_lens(active_cam, data['width'], data['height'], data['aspect'])
+            # store all values in dico
+            position = '[%f,%f,%f],' % (ae_transform[0], ae_transform[1], ae_transform[2])
+            orientation = '[%f,%f,%f],' % (ae_transform[3], ae_transform[4], ae_transform[5])
+            zoom = '%f,' % (zoom)
+            js_data['cameras'][name_ae]['position'] += position
+            js_data['cameras'][name_ae]['orientation'] += orientation
+            js_data['cameras'][name_ae]['zoom'] += zoom
+            # Check if properties change values compared to previous frame
+            # If property don't change through out the whole animation - keyframes won't be added
+            if frame != data['start']:
+                if position != js_data['cameras'][name_ae]['position_static']:
+                    js_data['cameras'][name_ae]['position_anim'] = True
+                if orientation != js_data['cameras'][name_ae]['orientation_static']:
+                    js_data['cameras'][name_ae]['orientation_anim'] = True
+                if zoom != js_data['cameras'][name_ae]['zoom_static']:
+                    js_data['cameras'][name_ae]['zoom_anim'] = True
+            js_data['cameras'][name_ae]['position_static'] = position
+            js_data['cameras'][name_ae]['orientation_static'] = orientation
+            js_data['cameras'][name_ae]['zoom_static'] = zoom
+
+        # keyframes for selected cameras
+        if include_selected_cams:
+            for i, cam in enumerate(selection['cameras']):
+                if cam[1] != active_cam_name:
+                    # get cam name
+                    name_ae = selection['cameras'][i][1]
+                    # convert cam transform properties to AE space
+                    ae_transform = convert_transform_matrix(cam[0].matrix_world.copy(), data['width'], data['height'], data['aspect'], True, ae_size)
+                    # convert Blender's lens to AE's zoom in pixels
+                    zoom = convert_lens(cam[0], data['width'], data['height'], data['aspect'])
+                    # store all values in dico
+                    position = '[%f,%f,%f],' % (ae_transform[0], ae_transform[1], ae_transform[2])
+                    orientation = '[%f,%f,%f],' % (ae_transform[3], ae_transform[4], ae_transform[5])
+                    zoom = '%f,' % (zoom)
+                    js_data['cameras'][name_ae]['position'] += position
+                    js_data['cameras'][name_ae]['orientation'] += orientation
+                    js_data['cameras'][name_ae]['zoom'] += zoom
+                    # Check if properties change values compared to previous frame
+                    # If property don't change through out the whole animation - keyframes won't be added
+                    if frame != data['start']:
+                        if position != js_data['cameras'][name_ae]['position_static']:
+                            js_data['cameras'][name_ae]['position_anim'] = True
+                        if orientation != js_data['cameras'][name_ae]['orientation_static']:
+                            js_data['cameras'][name_ae]['orientation_anim'] = True
+                        if zoom != js_data['cameras'][name_ae]['zoom_static']:
+                            js_data['cameras'][name_ae]['zoom_anim'] = True
+                    js_data['cameras'][name_ae]['position_static'] = position
+                    js_data['cameras'][name_ae]['orientation_static'] = orientation
+                    js_data['cameras'][name_ae]['zoom_static'] = zoom
+
+        '''
+        # keyframes for all solids. Not ready yet. Temporarily not active
+        for i, ob in enumerate(selection['solids']):
+            #get object name
+            name_ae = selection['solids'][i][1]
+            #convert ob position to AE space
+        '''
+
+        # keyframes for all lights.
+        if include_selected_objects:
+            for i, ob in enumerate(selection['lights']):
+                #get object name
+                name_ae = selection['lights'][i][1]
+                type = selection['lights'][i][0].data.type
+                # convert ob transform properties to AE space
+                ae_transform = convert_transform_matrix(ob[0].matrix_world.copy(), data['width'], data['height'], data['aspect'], True, ae_size)
+                color = ob[0].data.color
+                # store all values in dico
+                position = '[%f,%f,%f],' % (ae_transform[0], ae_transform[1], ae_transform[2])
+                orientation = '[%f,%f,%f],' % (ae_transform[3], ae_transform[4], ae_transform[5])
+                energy = '[%f],' % (ob[0].data.energy * 100.0)
+                color = '[%f,%f,%f],' % (color[0], color[1], color[2])
+                js_data['lights'][name_ae]['position'] += position
+                js_data['lights'][name_ae]['orientation'] += orientation
+                js_data['lights'][name_ae]['energy'] += energy
+                js_data['lights'][name_ae]['color'] += color
+                # Check if properties change values compared to previous frame
+                # If property don't change through out the whole animation - keyframes won't be added
+                if frame != data['start']:
+                    if position != js_data['lights'][name_ae]['position_static']:
+                        js_data['lights'][name_ae]['position_anim'] = True
+                    if orientation != js_data['lights'][name_ae]['orientation_static']:
+                        js_data['lights'][name_ae]['orientation_anim'] = True
+                    if energy != js_data['lights'][name_ae]['energy_static']:
+                        js_data['lights'][name_ae]['energy_anim'] = True
+                    if color != js_data['lights'][name_ae]['color_static']:
+                        js_data['lights'][name_ae]['color_anim'] = True
+                js_data['lights'][name_ae]['position_static'] = position
+                js_data['lights'][name_ae]['orientation_static'] = orientation
+                js_data['lights'][name_ae]['energy_static'] = energy
+                js_data['lights'][name_ae]['color_static'] = color
+                if type == 'SPOT':
+                    cone_angle = '[%f],' % (degrees(ob[0].data.spot_size))
+                    cone_feather = '[%f],' % (ob[0].data.spot_blend * 100.0)
+                    js_data['lights'][name_ae]['cone_angle'] += cone_angle
+                    js_data['lights'][name_ae]['cone_feather'] += cone_feather
+                    # Check if properties change values compared to previous frame
+                    # If property don't change through out the whole animation - keyframes won't be added
+                    if frame != data['start']:
+                        if cone_angle != js_data['lights'][name_ae]['cone_angle_static']:
+                            js_data['lights'][name_ae]['cone_angle_anim'] = True
+                        if orientation != js_data['lights'][name_ae]['cone_feather_static']:
+                            js_data['lights'][name_ae]['cone_feather_anim'] = True
+                    js_data['lights'][name_ae]['cone_angle_static'] = cone_angle
+                    js_data['lights'][name_ae]['cone_feather_static'] = cone_feather
+
+        # keyframes for all nulls
+        if include_selected_objects:
+            for i, ob in enumerate(selection['nulls']):
+                # get object name
+                name_ae = selection['nulls'][i][1]
+                # convert ob transform properties to AE space
+                ae_transform = convert_transform_matrix(ob[0].matrix_world.copy(), data['width'], data['height'], data['aspect'], True, ae_size)
+                # store all values in dico
+                position = '[%f,%f,%f],' % (ae_transform[0], ae_transform[1], ae_transform[2])
+                orientation = '[%f,%f,%f],' % (ae_transform[3], ae_transform[4], ae_transform[5])
+                scale = '[%f,%f,%f],' % (ae_transform[6], ae_transform[7], ae_transform[8])
+                js_data['nulls'][name_ae]['position'] += position
+                js_data['nulls'][name_ae]['orientation'] += orientation
+                js_data['nulls'][name_ae]['scale'] += scale
+                # Check if properties change values compared to previous frame
+                # If property don't change through out the whole animation - keyframes won't be added
+                if frame != data['start']:
+                    if position != js_data['nulls'][name_ae]['position_static']:
+                        js_data['nulls'][name_ae]['position_anim'] = True
+                    if orientation != js_data['nulls'][name_ae]['orientation_static']:
+                        js_data['nulls'][name_ae]['orientation_anim'] = True
+                    if scale != js_data['nulls'][name_ae]['scale_static']:
+                        js_data['nulls'][name_ae]['scale_anim'] = True
+                js_data['nulls'][name_ae]['position_static'] = position
+                js_data['nulls'][name_ae]['orientation_static'] = orientation
+                js_data['nulls'][name_ae]['scale_static'] = scale
+
+        # keyframes for all object bundles. Not ready yet.
+        #
+        #
+        #
+
+    # ---- write JSX file
+    jsx_file = open(file, 'w')
+
+    # make the jsx executable in After Effects (enable double click on jsx)
+    jsx_file.write('#target AfterEffects\n\n')
+    # Script's header
+    jsx_file.write('/**************************************\n')
+    jsx_file.write('Scene : %s\n' % data['scn'].name)
+    jsx_file.write('Resolution : %i x %i\n' % (data['width'], data['height']))
+    jsx_file.write('Duration : %f\n' % (data['duration']))
+    jsx_file.write('FPS : %f\n' % (data['fps']))
+    jsx_file.write('Date : %s\n' % datetime.datetime.now())
+    jsx_file.write('Exported with io_export_after_effects.py\n')
+    jsx_file.write('**************************************/\n\n\n\n')
+
+    # wrap in function
+    jsx_file.write("function compFromBlender(){\n")
+    # create new comp
+    jsx_file.write('\nvar compName = prompt("Blender Comp\'s Name \\nEnter Name of newly created Composition","BlendComp","Composition\'s Name");\n')
+    jsx_file.write('if (compName){')  # Continue only if comp name is given. If not - terminate
+    jsx_file.write('\nvar newComp = app.project.items.addComp(compName, %i, %i, %f, %f, %f);' %
+                   (data['width'], data['height'], data['aspect'], data['duration'], data['fps']))
+    jsx_file.write('\nnewComp.displayStartTime = %f;\n\n\n' % ((data['start'] + 1.0) / data['fps']))
+
+    # create camera bundles (nulls)
+    jsx_file.write('// **************  CAMERA 3D MARKERS  **************\n\n\n')
+    for i, obj in enumerate(js_data['bundles_cam']):
+        name_ae = obj
+        jsx_file.write('var %s = newComp.layers.addNull();\n' % (name_ae))
+        jsx_file.write('%s.threeDLayer = true;\n' % name_ae)
+        jsx_file.write('%s.source.name = "%s";\n' % (name_ae, name_ae))
+        jsx_file.write('%s.property("position").setValue(%s);\n\n\n' % (name_ae, js_data['bundles_cam'][obj]['position']))
+
+    # create object bundles (not ready yet)
+
+    # create objects (nulls)
+    jsx_file.write('// **************  OBJECTS  **************\n\n\n')
+    for i, obj in enumerate(js_data['nulls']):
+        name_ae = obj
+        jsx_file.write('var %s = newComp.layers.addNull();\n' % (name_ae))
+        jsx_file.write('%s.threeDLayer = true;\n' % name_ae)
+        jsx_file.write('%s.source.name = "%s";\n' % (name_ae, name_ae))
+        # Set values of properties, add kyeframes only where needed
+        if include_animation and js_data['nulls'][name_ae]['position_anim']:
+            jsx_file.write('%s.property("position").setValuesAtTimes([%s],[%s]);\n' % (name_ae, js_data['times'], js_data['nulls'][obj]['position']))
+        else:
+            jsx_file.write('%s.property("position").setValue(%s);\n' % (name_ae, js_data['nulls'][obj]['position_static']))
+        if include_animation and js_data['nulls'][name_ae]['orientation_anim']:
+            jsx_file.write('%s.property("orientation").setValuesAtTimes([%s],[%s]);\n' % (name_ae, js_data['times'], js_data['nulls'][obj]['orientation']))
+        else:
+            jsx_file.write('%s.property("orientation").setValue(%s);\n' % (name_ae, js_data['nulls'][obj]['orientation_static']))
+        if include_animation and js_data['nulls'][name_ae]['scale_anim']:
+            jsx_file.write('%s.property("scale").setValuesAtTimes([%s],[%s]);\n\n\n' % (name_ae, js_data['times'], js_data['nulls'][obj]['scale']))
+        else:
+            jsx_file.write('%s.property("scale").setValue(%s);\n\n\n' % (name_ae, js_data['nulls'][obj]['scale_static']))
+    # create solids (not ready yet)
+
+    # create lights
+    jsx_file.write('// **************  LIGHTS  **************\n\n\n')
+    for i, obj in enumerate(js_data['lights']):
+        name_ae = obj
+        jsx_file.write('var %s = newComp.layers.addLight("%s", [0.0, 0.0]);\n' % (name_ae, name_ae))
+        jsx_file.write('%s.autoOrient = AutoOrientType.NO_AUTO_ORIENT;\n' % name_ae)
+        # Set values of properties, add kyeframes only where needed
+        if include_animation and js_data['lights'][name_ae]['position_anim']:
+            jsx_file.write('%s.property("position").setValuesAtTimes([%s],[%s]);\n' % (name_ae, js_data['times'], js_data['lights'][obj]['position']))
+        else:
+            jsx_file.write('%s.property("position").setValue(%s);\n' % (name_ae, js_data['lights'][obj]['position_static']))
+        if include_animation and js_data['lights'][name_ae]['orientation_anim']:
+            jsx_file.write('%s.property("orientation").setValuesAtTimes([%s],[%s]);\n' % (name_ae, js_data['times'], js_data['lights'][obj]['orientation']))
+        else:
+            jsx_file.write('%s.property("orientation").setValue(%s);\n' % (name_ae, js_data['lights'][obj]['orientation_static']))
+        if include_animation and js_data['lights'][name_ae]['energy_anim']:
+            jsx_file.write('%s.property("intensity").setValuesAtTimes([%s],[%s]);\n' % (name_ae, js_data['times'], js_data['lights'][obj]['energy']))
+        else:
+            jsx_file.write('%s.property("intensity").setValue(%s);\n' % (name_ae, js_data['lights'][obj]['energy_static']))
+        if include_animation and js_data['lights'][name_ae]['color_anim']:
+            jsx_file.write('%s.property("Color").setValuesAtTimes([%s],[%s]);\n' % (name_ae, js_data['times'], js_data['lights'][obj]['color']))
+        else:
+            jsx_file.write('%s.property("Color").setValue(%s);\n' % (name_ae, js_data['lights'][obj]['color_static']))
+            if js_data['lights'][obj]['type'] == 'SPOT':
+                if include_animation and js_data['lights'][name_ae]['cone_angle_anim']:
+                    jsx_file.write('%s.property("Cone Angle").setValuesAtTimes([%s],[%s]);\n' % (name_ae, js_data['times'], js_data['lights'][obj]['cone_angle']))
+                else:
+                    jsx_file.write('%s.property("Cone Angle").setValue(%s);\n' % (name_ae, js_data['lights'][obj]['cone_angle_static']))
+                if include_animation and js_data['lights'][name_ae]['cone_feather_anim']:
+                    jsx_file.write('%s.property("Cone Feather").setValuesAtTimes([%s],[%s]);\n' % (name_ae, js_data['times'], js_data['lights'][obj]['cone_feather']))
+                else:
+                    jsx_file.write('%s.property("Cone Feather").setValue(%s);\n' % (name_ae, js_data['lights'][obj]['cone_feather_static']))
+        jsx_file.write('\n\n')
+
+    # create cameras
+    jsx_file.write('// **************  CAMERAS  **************\n\n\n')
+    for i, cam in enumerate(js_data['cameras']):  # more than one camera can be selected
+        name_ae = cam
+        jsx_file.write('var %s = newComp.layers.addCamera("%s",[0,0]);\n' % (name_ae, name_ae))
+        jsx_file.write('%s.autoOrient = AutoOrientType.NO_AUTO_ORIENT;\n' % name_ae)
+        # Set values of properties, add kyeframes only where needed
+        if include_animation and js_data['cameras'][name_ae]['position_anim']:
+            jsx_file.write('%s.property("position").setValuesAtTimes([%s],[%s]);\n' % (name_ae, js_data['times'], js_data['cameras'][cam]['position']))
+        else:
+            jsx_file.write('%s.property("position").setValue(%s);\n' % (name_ae, js_data['cameras'][cam]['position_static']))
+        if include_animation and js_data['cameras'][name_ae]['orientation_anim']:
+            jsx_file.write('%s.property("orientation").setValuesAtTimes([%s],[%s]);\n' % (name_ae, js_data['times'], js_data['cameras'][cam]['orientation']))
+        else:
+            jsx_file.write('%s.property("orientation").setValue(%s);\n' % (name_ae, js_data['cameras'][cam]['orientation_static']))
+        if include_animation and js_data['cameras'][name_ae]['zoom_anim']:
+            jsx_file.write('%s.property("zoom").setValuesAtTimes([%s],[%s]);\n\n\n' % (name_ae, js_data['times'], js_data['cameras'][cam]['zoom']))
+        else:
+            jsx_file.write('%s.property("zoom").setValue(%s);\n\n\n' % (name_ae, js_data['cameras'][cam]['zoom_static']))
+
+    # Exit import if no comp name given
+    jsx_file.write('\n}else{alert ("Exit Import Blender animation data \\nNo Comp\'s name has been chosen","EXIT")};')
+    # Close function
+    jsx_file.write("}\n\n\n")
+    # Execute function. Wrap in "undo group" for easy undoing import process
+    jsx_file.write('app.beginUndoGroup("Import Blender animation data");\n')
+    jsx_file.write('compFromBlender();\n')  # execute function
+    jsx_file.write('app.endUndoGroup();\n\n\n')
+    jsx_file.close()
+
+    data['scn'].frame_set(curframe)  # set current frame of animation in blender to state before export
+
+##########################################
+# DO IT
+##########################################
+
+
+def main(file, context, include_animation, include_active_cam, include_selected_cams, include_selected_objects, include_cam_bundles, ae_size):
+    data = get_comp_data(context)
+    selection = get_selected(context)
+    write_jsx_file(file, data, selection, include_animation, include_active_cam, include_selected_cams, include_selected_objects, include_cam_bundles, ae_size)
+    print ("\nExport to After Effects Completed")
+    return {'FINISHED'}
+
+##########################################
+# ExportJsx class register/unregister
+##########################################
+
+from bpy_extras.io_utils import ExportHelper
+from bpy.props import StringProperty, BoolProperty, FloatProperty
+
+
+class ExportJsx(bpy.types.Operator, ExportHelper):
+    """Export selected cameras and objects animation to After Effects"""
+    bl_idname = "export.jsx"
+    bl_label = "Export to Adobe After Effects"
+    filename_ext = ".jsx"
+    filter_glob: StringProperty(default="*.jsx", options={'HIDDEN'})
+
+    include_animation: BoolProperty(
+            name="Animation",
+            description="Animate Exported Cameras and Objects",
+            default=True,
+            )
+    include_active_cam: BoolProperty(
+            name="Active Camera",
+            description="Include Active Camera",
+            default=True,
+            )
+    include_selected_cams: BoolProperty(
+            name="Selected Cameras",
+            description="Add Selected Cameras",
+            default=True,
+            )
+    include_selected_objects: BoolProperty(
+            name="Selected Objects",
+            description="Export Selected Objects",
+            default=True,
+            )
+    include_cam_bundles: BoolProperty(
+            name="Camera 3D Markers",
+            description="Include 3D Markers of Camera Motion Solution for selected cameras",
+            default=True,
+            )
+#    include_ob_bundles = BoolProperty(
+#            name="Objects 3D Markers",
+#            description="Include 3D Markers of Object Motion Solution for selected cameras",
+#            default=True,
+#            )
+    ae_size: FloatProperty(
+            name="AE Size",
+            description="Size of AE Composition (pixels per 1BU)",
+            default=100.0,
+            )
+
+    def draw(self, context):
+        layout = self.layout
+
+        box = layout.box()
+        box.label(text='Size fo AE Comp (pixels per 1 BU)')
+        box.prop(self, 'ae_size')
+        box.label(text='Animation:')
+        box.prop(self, 'include_animation')
+        box.label(text='Include Cameras and Objects:')
+        box.prop(self, 'include_active_cam')
+        box.prop(self, 'include_selected_cams')
+        box.prop(self, 'include_selected_objects')
+        box.label(text="Include Tracking Data:")
+        box.prop(self, 'include_cam_bundles')
+#        box.prop(self, 'include_ob_bundles')
+
+    @classmethod
+    def poll(cls, context):
+        active = context.active_object
+        selected = context.selected_objects
+        camera = context.scene.camera
+        ok = selected or camera
+        return ok
+
+    def execute(self, context):
+        return main(self.filepath, context, self.include_animation, self.include_active_cam, self.include_selected_cams, self.include_selected_objects, self.include_cam_bundles, self.ae_size)
+
+
+def menu_func(self, context):
+    self.layout.operator(ExportJsx.bl_idname, text="Adobe After Effects (.jsx)")
+
+
+def register():
+    bpy.utils.register_class(ExportJsx)
+    bpy.types.TOPBAR_MT_file_export.append(menu_func)
+
+
+def unregister():
+    bpy.utils.unregister_class(ExportJsx)
+    bpy.types.TOPBAR_MT_file_export.remove(menu_func)
+
+if __name__ == "__main__":
+    register()