diff --git a/io_export_after_effects.py b/io_export_after_effects.py index e5a26438d0cc39a88c6d8b468ba59369aac502fd..7aec1d1430de20a7006c0b892a3f52a0196ad3c0 100644 --- a/io_export_after_effects.py +++ b/io_export_after_effects.py @@ -22,8 +22,8 @@ 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, (Adi Samsonoff)", - "version": (0, 0, 66), + "author": "Bartek Skorupa", + "version": (0, 0, 67), "blender": (2, 80, 0), "location": "File > Export > Adobe After Effects (.jsx)", "warning": "", @@ -34,13 +34,14 @@ bl_info = { import bpy +import os import datetime from math import degrees, floor -from mathutils import Matrix +from mathutils import Matrix, Vector, Color -# create list of static blender's data def get_comp_data(context): + """Create list of static blender's data""" scene = context.scene aspect_x = scene.render.pixel_aspect_x aspect_y = scene.render.pixel_aspect_y @@ -64,8 +65,8 @@ def get_comp_data(context): } -# 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): + """Create list of active camera for each frame in case active camera is set by markers""" active_cam_frames = [] sorted_markers = [] markers = scene.timeline_markers @@ -80,7 +81,8 @@ def get_active_cam_for_each_frame(scene, start, end): for m, marker in enumerate(sorted_markers): if marker[0] > frame: if m != 0: - active_cam_frames.append(sorted_markers[m - 1][1].camera) + active_cam_frames.append( + sorted_markers[m - 1][1].camera) else: active_cam_frames.append(marker[1].camera) break @@ -88,36 +90,41 @@ def get_active_cam_for_each_frame(scene, start, end): 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 + # in this case active_cam_frames array will have length 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 managable 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 exept cameras (will be used to create nulls in AE) + """Create manageable list of selected objects""" + cameras = [] # List of selected cameras + solids = [] # List of selected meshes exported as AE solids + images = [] # List of selected meshes exported as AE AV layers + lights = [] # List of selected lights exported as AE lights + nulls = [] # List of 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)]) + cameras.append(ob) + + elif is_image_plane(ob): + images.append(ob) 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)]) + solids.append(ob) elif ob.type == 'LIGHT': - lights.append([ob, ob.data.type + convert_name(ob.name)]) # Type of lamp added to name + lights.append(ob) else: - nulls.append([ob, convert_name(ob.name)]) + nulls.append(ob) selection = { 'cameras': cameras, + 'images': images, 'solids': solids, 'lights': lights, 'nulls': nulls, @@ -126,14 +133,149 @@ def get_selected(context): 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 +def get_first_material(obj): + for slot in obj.material_slots: + if slot.material is not None: + return slot.material + + +def get_image_node(mat): + for node in mat.node_tree.nodes: + if node.type == "TEX_IMAGE": + return node.image + + +def get_plane_color(obj): + """Get the object's emission and base color, or 0.5 gray if no color is found.""" + if obj.active_material is None: + color = (0.5,) * 3 + elif obj.active_material: + from bpy_extras import node_shader_utils + wrapper = node_shader_utils.PrincipledBSDFWrapper(obj.active_material) + color = Color(wrapper.base_color[:3]) + wrapper.emission_color + + return '[%f,%f,%f]' % (color[0], color[1], color[2]) + + +def is_plane(obj): + """Check if object is a plane + + Makes a few assumptions: + - The mesh has exactly one quad face + - The mesh is a rectangle + + For now this doesn't account for shear, which could happen e.g. if the + vertices are rotated, and the object is scaled non-uniformly... + """ + if obj.type != 'MESH': + return False + + if len(obj.data.polygons) != 1: + return False + + if len(obj.data.polygons[0].vertices) != 4: + return False + + v1, v2, v3, v4 = (obj.data.vertices[v].co for v in obj.data.polygons[0].vertices) + + # Check that poly is a parallelogram + if -v1 + v2 + v4 != v3: + return False + + # Check that poly has at least one right angle + if (v2-v1).dot(v4-v1) != 0.0: + return False + + # If my calculations are correct, that should make it a rectangle + return True + + +def is_image_plane(obj): + """Check if object is a plane with an image + + Makes a few assumptions: + - The mesh is a plane + - The mesh has exactly one material + - There is only one image in this material node tree + """ + if not is_plane(obj): + return False + + if not len(obj.material_slots): + return False + + mat = get_first_material(obj) + if mat is None: + return False + + img = get_image_node(mat) + if img is None: + return False + + if len(obj.data.vertices) == 4: + return True + + +def get_image_filepath(obj): + mat = get_first_material(obj) + img = get_image_node(mat) + filepath = img.filepath + filepath = bpy.path.abspath(filepath) + filepath = os.path.abspath(filepath) + filepath = filepath.replace('\\', '\\\\') + return filepath + + +def get_image_size(obj): + mat = get_first_material(obj) + img = get_image_node(mat) + return img.size + + +def get_plane_matrix(obj): + """Get object's polygon local matrix from vertices.""" + v1, v2, v3, v4 = (obj.data.vertices[v].co for v in obj.data.polygons[0].vertices) + + p0 = obj.matrix_world @ v1 + px = obj.matrix_world @ v2 - p0 + py = obj.matrix_world @ v4 - p0 + + rot_mat = Matrix((px, py, px.cross(py))).transposed().to_4x4() + trans_mat = Matrix.Translation(p0 + (px + py) / 2.0) + mat = trans_mat @ rot_mat + + return mat + + +def get_image_plane_matrix(obj): + """Get object's polygon local matrix from uvs. + + This will only work if uvs occupy all space, to get bounds + """ + for p_i, p in enumerate(obj.data.uv_layers.active.data): + if p.uv == Vector((0, 0)): + p0 = p_i + elif p.uv == Vector((1, 0)): + px = p_i + elif p.uv == Vector((0, 1)): + py = p_i + + verts = obj.data.vertices + loops = obj.data.loops + + p0 = obj.matrix_world @ verts[loops[p0].vertex_index].co + px = obj.matrix_world @ verts[loops[px].vertex_index].co - p0 + py = obj.matrix_world @ verts[loops[py].vertex_index].co - p0 + + rot_mat = Matrix((px, py, px.cross(py))).transposed().to_4x4() + trans_mat = Matrix.Translation(p0 + (px + py) / 2.0) + mat = trans_mat @ rot_mat + + return mat -# convert names of objects to avoid errors in AE. def convert_name(name): + """Convert names of objects to avoid errors in AE""" name = "_" + name ''' # Digits are not allowed at beginning of AE vars names. @@ -148,38 +290,51 @@ def convert_name(name): 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): +def convert_transform_matrix(matrix, width, height, aspect, + x_rot_correction=False, ae_size=100.0): + """Convert from Blender's Location, Rotation and Scale + to AE's Position, Rotation/Orientation and Scale - # get blender transform data for ob - b_loc = matrix.to_translation() + This function will be called for every object for every frame + """ + + scale_mat = Matrix.Scale(width, 4) + + # Get blender transform data for ob + b_loc = (scale_mat @ 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 * 100.0) / aspect + width / 2.0 # calculate AE's X position - y = (-b_loc.z * 100.0) + (height / 2.0) # calculate AE's Y position - z = b_loc.y * 100.0 # calculate AE's Z position + # Convert to AE Position Rotation and Scale. Axes in AE are different: + # AE's X is Blender's X, + # AE's Y is Blender's -Z, + # AE's Z is Blender's Y + x = (b_loc.x * ae_size / 100.0) / aspect + width / 2.0 + y = (-b_loc.z * ae_size / 100.0) + (height / 2.0) + z = (b_loc.y * ae_size / 100.0) + # 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 not x_rot_correction + rx = degrees(b_rot.x) # AE's X orientation = blender's X rotation if 'ZYX' euler. + ry = -degrees(b_rot.y) # AE's Y orientation = -blender's Y rotation if 'ZYX' euler + rz = -degrees(b_rot.z) # AE's Z orientation = -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 + # In Blender, ob of zero rotation lays on floor. + # In AE, layer of zero orientation "stands" + rx -= 90.0 + # Convert scale to AE scale. ae_size is a global multiplier. + sx = b_scale.x * ae_size + sy = b_scale.y * ae_size + sz = b_scale.z * ae_size return x, y, z, rx, ry, rz, sx, sy, sz -# get camera's lens and convert to AE's "zoom" value in pixels +# 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. +# AE's lens is defined by "zoom" in pixels. +# Zoom determines focal angle or focal length. # # ZOOM VALUE CALCULATIONS: # @@ -193,10 +348,16 @@ def convert_transform_matrix(matrix, width, height, aspect, x_rot_correction=Fal # - 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 +# If camera.sensor_fit is set to 'HORIZONTAL': +# sensor = camera.data.sensor_width, dimension = width. +# If camera.sensor_fit is set to 'AUTO' +# and the vertical size is greater than the horizontal size: +# sensor = camera.data.sensor_width, dimension = width. # -# zoom can be calculated using simple proportions. +# If camera.sensor_fit is set to 'VERTICAL': +# sensor = camera.data.sensor_height, dimension = height +# +# Zoom can be calculated using simple proportions. # # | # / | @@ -214,15 +375,18 @@ def convert_transform_matrix(matrix, width, height, aspect, x_rot_correction=Fal # | | | # lens | zoom # -# zoom / dimension = lens / sensor => -# zoom = lens * dimension / sensor +# 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 +# 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': + if (camera.data.sensor_fit == 'VERTICAL' + or camera.data.sensor_fit == 'AUTO' + and (width / height) * aspect < 0): sensor = camera.data.sensor_height dimension = height else: @@ -234,37 +398,44 @@ def convert_lens(camera, width, height, 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): +# 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): +def write_jsx_file(file, data, selection, include_animation, + include_active_cam, include_selected_cams, + include_selected_objects, include_cam_bundles, + include_image_planes, ae_size): + """jsx script for AE creation""" print("\n---------------------------\n- Export to After Effects -\n---------------------------") - # store the current frame to restore it at the end of export + # Store the current frame to restore it at the end of export curframe = data['curframe'] - # create array which will contain all keyframes values + # Create array which will contain all keyframes values js_data = { 'times': '', 'cameras': {}, - 'solids': {}, # not ready yet + 'images': {}, + 'solids': {}, 'lights': {}, 'nulls': {}, 'bundles_cam': {}, 'bundles_ob': {}, # not ready yet } - # create structure for active camera/cameras + # 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 include_active_cam and data['active_cam_frames']: + # Check if more than one active cam exists + # (True if active cams set by markers) if len(data['active_cam_frames']) == 1: - name_ae = convert_name(data['active_cam_frames'][0].name) # take name of the only active camera in scene + # Take name of the only active camera in scene + name_ae = convert_name(data['active_cam_frames'][0].name) else: name_ae = 'Active_Camera' - active_cam_name = name_ae # store name to be used when creating keyframes for active cam. + # Store name to be used when creating keyframes for active cam + active_cam_name = name_ae js_data['cameras'][name_ae] = { 'position': '', 'position_static': '', @@ -277,11 +448,12 @@ def write_jsx_file(file, data, selection, include_animation, include_active_cam, 'zoom_anim': False, } - # create camera structure for selected cameras + # 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] + for obj in selection['cameras']: + # More than one camera can be selected + if convert_name(obj.name) != active_cam_name: + name_ae = convert_name(obj.name) js_data['cameras'][name_ae] = { 'position': '', 'position_static': '', @@ -293,35 +465,56 @@ def write_jsx_file(file, data, selection, include_animation, include_active_cam, '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] + + # Create structure for solids + for obj in selection['solids']: + name_ae = convert_name(obj.name) js_data['solids'][name_ae] = { 'position': '', + 'position_static': '', + 'position_anim': False, 'orientation': '', - 'rotationX': '', + 'orientation_static': '', + 'orientation_anim': False, 'scale': '', + 'scale_static': '', + 'scale_anim': False, } - ''' - # create structure for lights - for i, obj in enumerate(selection['lights']): + + # Create structure for images + for obj in selection['images']: + name_ae = convert_name(obj.name) + js_data['images'][name_ae] = { + 'position': '', + 'position_static': '', + 'position_anim': False, + 'orientation': '', + 'orientation_static': '', + 'orientation_anim': False, + 'scale': '', + 'scale_static': '', + 'scale_anim': False, + 'filepath': '', + } + + # Create structure for lights + for obj in selection['lights']: if include_selected_objects: - name_ae = selection['lights'][i][1] + name_ae = obj.data.type + convert_name(obj.name) 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, + 'type': obj.data.type, + 'intensity': '', + 'intensity_static': '', + 'intensity_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, @@ -330,10 +523,11 @@ def write_jsx_file(file, data, selection, include_animation, include_active_cam, 'orientation_anim': False, } - # create structure for nulls - for i, obj in enumerate(selection['nulls']): # nulls representing blender's obs except cameras, lights and solids + # Create structure for nulls + # nulls representing blender's obs except cameras, lights and solids + for obj in selection['nulls']: if include_selected_objects: - name_ae = selection['nulls'][i][1] + name_ae = convert_name(obj.name) js_data['nulls'][name_ae] = { 'position': '', 'position_static': '', @@ -346,23 +540,24 @@ def write_jsx_file(file, data, selection, include_animation, include_active_cam, 'scale_anim': False, } - # create structure for cam bundles including positions (cam bundles don't move) + # Create structure for cam bundles including positions + # (cam bundles don't move) if include_cam_bundles: - # go through each selected camera and active cameras + # 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' + selected_cams.append(cam) + # 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 + # Go through each constraints of this camera for constraint in cam.constraints: - # does the camera have a Camera Solver constraint + # Does the camera have a Camera Solver constraint if constraint.type == 'CAMERA_SOLVER': # Which movie clip does it use if constraint.use_active_clip: @@ -370,22 +565,28 @@ def write_jsx_file(file, data, selection, include_animation, include_active_cam, else: clip = constraint.clip - # go through each tracking point + # Go through each tracking point for track in clip.tracking.tracks: - # Does this tracking point have a bundle (has its 3D position been solved) + # 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)) + # 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'], x_rot_correction=False) - 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 + # 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: @@ -394,154 +595,259 @@ def write_jsx_file(file, data, selection, include_animation, include_active_cam, 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']) + # Get time for this loop + js_data['times'] += '%f,' % ((frame - data['start']) / data['fps']) - # keyframes for active camera/cameras + # 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 + # 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'], x_rot_correction=True) - # 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]) + # 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 + js_camera = js_data['cameras'][name_ae] + js_camera['position'] += position + js_camera['orientation'] += orientation + js_camera['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 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 position != js_camera['position_static']: + js_camera['position_anim'] = True + if orientation != js_camera['orientation_static']: + js_camera['orientation_anim'] = True + if zoom != js_camera['zoom_static']: + js_camera['zoom_anim'] = True + js_camera['position_static'] = position + js_camera['orientation_static'] = orientation + js_camera['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'], x_rot_correction=True) - # 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]) + for obj in selection['cameras']: + if convert_name(obj.name) != active_cam_name: + # Get cam name + name_ae = convert_name(obj.name) + # Convert cam transform properties to AE space + ae_transform = convert_transform_matrix( + obj.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(obj, 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 + js_camera = js_data['cameras'][name_ae] + js_camera['position'] += position + js_camera['orientation'] += orientation + js_camera['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 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 position != js_camera['position_static']: + js_camera['position_anim'] = True + if orientation != js_camera['orientation_static']: + js_camera['orientation_anim'] = True + if zoom != js_camera['zoom_static']: + js_camera['zoom_anim'] = True + js_camera['position_static'] = position + js_camera['orientation_static'] = orientation + js_camera['zoom_static'] = zoom + + # Keyframes for all solids. + if include_selected_objects: + for obj in selection['solids']: + # Get object name + name_ae = convert_name(obj.name) + # Convert obj transform properties to AE space + plane_matrix = get_plane_matrix(obj) + ae_transform = convert_transform_matrix( + plane_matrix, 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]) + # plane_width, plane_height, _ = plane_matrix.to_scale() + scale = '[%f,%f,%f],' % (ae_transform[6], + ae_transform[7] * data['width'] / data['height'], + ae_transform[8]) + js_solid = js_data['solids'][name_ae] + js_solid['color'] = get_plane_color(obj) + js_solid['width'] = data['width'] + js_solid['height'] = data['height'] + js_solid['position'] += position + js_solid['orientation'] += orientation + js_solid['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_solid['position_static']: + js_solid['position_anim'] = True + if orientation != js_solid['orientation_static']: + js_solid['orientation_anim'] = True + if scale != js_solid['scale_static']: + js_solid['scale_anim'] = True + js_solid['position_static'] = position + js_solid['orientation_static'] = orientation + js_solid['scale_static'] = scale + + # 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'], x_rot_correction=True) - 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) + for obj in selection['lights']: + # Get object name + name_ae = obj.data.type + convert_name(obj.name) + type = obj.data.type + # Convert ob transform properties to AE space + ae_transform = convert_transform_matrix( + obj.matrix_world.copy(), data['width'], data['height'], + data['aspect'], True, ae_size) + color = obj.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],' % (obj.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 + js_light = js_data['lights'][name_ae] + js_light['position'] += position + js_light['orientation'] += orientation + js_light['intensity'] += energy + js_light['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 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 position != js_light['position_static']: + js_light['position_anim'] = True + if orientation != js_light['orientation_static']: + js_light['orientation_anim'] = True + if energy != js_light['intensity_static']: + js_light['intensity_anim'] = True + if color != js_light['Color_static']: + js_light['Color_anim'] = True + js_light['position_static'] = position + js_light['orientation_static'] = orientation + js_light['intensity_static'] = energy + js_light['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 + cone_angle = '[%f],' % (degrees(obj.data.spot_size)) + cone_feather = '[%f],' % (obj.data.spot_blend * 100.0) + js_light['Cone Angle'] += cone_angle + js_light['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 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 cone_angle != js_light['Cone Angle_static']: + js_light['Cone Angle_anim'] = True + if cone_feather != js_light['Cone Feather_static']: + js_light['Cone Feather_anim'] = True + js_light['Cone Angle_static'] = cone_angle + js_light['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'], x_rot_correction=True) - # 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 + for obj in selection['nulls']: + # Get object name + name_ae = convert_name(obj.name) + # Convert obj transform properties to AE space + ae_transform = convert_transform_matrix(obj.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_null = js_data['nulls'][name_ae] + js_null['position'] += position + js_null['orientation'] += orientation + js_null['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_null['position_static']: + js_null['position_anim'] = True + if orientation != js_null['orientation_static']: + js_null['orientation_anim'] = True + if scale != js_null['scale_static']: + js_null['scale_anim'] = True + js_null['position_static'] = position + js_null['orientation_static'] = orientation + js_null['scale_static'] = scale + + # Keyframes for all images + if include_image_planes: + for obj in selection['images']: + # Get object name + name_ae = convert_name(obj.name) + # Convert obj transform properties to AE space + plane_matrix = get_image_plane_matrix(obj) + ae_transform = convert_transform_matrix( + plane_matrix, 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]) + image_width, image_height = get_image_size(obj) + ratio_to_comp = image_width / data['width'] + scale = '[%f,%f,%f],' % (ae_transform[6] / ratio_to_comp, + ae_transform[7] / ratio_to_comp + * image_width / image_height, + ae_transform[8]) + js_image = js_data['images'][name_ae] + js_image['position'] += position + js_image['orientation'] += orientation + js_image['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 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 + if position != js_image['position_static']: + js_image['position_anim'] = True + if orientation != js_image['orientation_static']: + js_image['orientation_anim'] = True + if scale != js_image['scale_static']: + js_image['scale_anim'] = True + js_image['position_static'] = position + js_image['orientation_static'] = orientation + js_image['scale_static'] = scale + js_image['filepath'] = get_image_filepath(obj) # keyframes for all object bundles. Not ready yet. # @@ -551,7 +857,7 @@ def write_jsx_file(file, data, selection, include_animation, include_active_cam, # ---- write JSX file jsx_file = open(file, 'w') - # make the jsx executable in After Effects (enable double click on jsx) + # 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') @@ -563,138 +869,185 @@ def write_jsx_file(file, data, selection, include_animation, include_active_cam, jsx_file.write('Exported with io_export_after_effects.py\n') jsx_file.write('**************************************/\n\n\n\n') - # wrap in function + # 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 + # Create new comp + if bpy.data.filepath: + comp_name = convert_name( + os.path.splitext(os.path.basename(bpy.data.filepath))[0]) + else: + comp_name = "BlendComp" + jsx_file.write('\nvar compName = prompt("Blender Comp\'s Name \\nEnter Name of newly created Composition","%s","Composition\'s Name");\n' % comp_name) + 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') + for name_ae, obj in js_data['bundles_cam'].items(): 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'])) + jsx_file.write('%s.property("position").setValue(%s);\n\n' + % (name_ae, obj['position'])) + jsx_file.write('\n') - # create object bundles (not ready yet) + # 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 + # Create objects (nulls) + jsx_file.write('// ************** OBJECTS **************\n\n') + for name_ae, obj in js_data['nulls'].items(): 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'])) + # Set values of properties, add keyframes only where needed + for prop in ("position", "orientation", "scale"): + if include_animation and obj[prop + '_anim']: + jsx_file.write( + '%s.property("%s").setValuesAtTimes([%s],[%s]);\n' + % (name_ae, prop, js_data['times'], obj[prop])) + else: + jsx_file.write( + '%s.property("%s").setValue(%s);\n' + % (name_ae, prop, obj[prop + '_static'])) + jsx_file.write('\n') + jsx_file.write('\n') + + # Create solids + jsx_file.write('// ************** SOLIDS **************\n\n') + for name_ae, obj in js_data['solids'].items(): + jsx_file.write( + 'var %s = newComp.layers.addSolid(%s,"%s",%i,%i,%f);\n' % ( + name_ae, + obj['color'], + name_ae, + obj['width'], + obj['height'], + 1.0)) + 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 keyframes only where needed + for prop in ("position", "orientation", "scale"): + if include_animation and obj[prop + '_anim']: + jsx_file.write( + '%s.property("%s").setValuesAtTimes([%s],[%s]);\n' + % (name_ae, prop, js_data['times'], obj[prop])) + else: + jsx_file.write( + '%s.property("%s").setValue(%s);\n' + % (name_ae, prop, obj[prop + '_static'])) + jsx_file.write('\n') + jsx_file.write('\n') + + # Create images + jsx_file.write('// ************** IMAGES **************\n\n') + for name_ae, obj in js_data['images'].items(): + jsx_file.write( + 'var newFootage = app.project.importFile(new ImportOptions(File("%s")))\n' + % (obj['filepath'])) + jsx_file.write( + 'var %s = newComp.layers.add(newFootage);\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 keyframes only where needed + for prop in ("position", "orientation", "scale"): + if include_animation and obj[prop + '_anim']: + jsx_file.write( + '%s.property("%s").setValuesAtTimes([%s],[%s]);\n' + % (name_ae, prop, js_data['times'], obj[prop])) + else: + jsx_file.write( + '%s.property("%s").setValue(%s);\n' + % (name_ae, prop, obj[prop + '_static'])) + jsx_file.write('\n') + jsx_file.write('\n') + + # Create lights + jsx_file.write('// ************** LIGHTS **************\n\n') + for name_ae, obj in js_data['lights'].items(): + 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 keyframes only where needed + props = ["position", "orientation", "intensity", "Color"] + if obj['type'] == 'SPOT': + props.extend(("Cone Angle", "Cone Feather")) + for prop in props: + if include_animation and obj[prop + '_anim']: + jsx_file.write( + '%s.property("%s").setValuesAtTimes([%s],[%s]);\n' + % (name_ae, prop, js_data['times'], obj[prop])) + else: + jsx_file.write( + '%s.property("%s").setValue(%s);\n' + % (name_ae, prop, obj[prop + '_static'])) + jsx_file.write('\n') + jsx_file.write('\n') + + # Create cameras + jsx_file.write('// ************** CAMERAS **************\n\n') + for name_ae, obj in js_data['cameras'].items(): + # More than one camera can be selected + 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 keyframes only where needed + for prop in ("position", "orientation", "zoom"): + if include_animation and obj[prop + '_anim']: + jsx_file.write( + '%s.property("%s").setValuesAtTimes([%s],[%s]);\n' + % (name_ae, prop, js_data['times'], obj[prop])) + else: + jsx_file.write( + '%s.property("%s").setValue(%s);\n' + % (name_ae, prop, obj[prop + '_static'])) + jsx_file.write('\n') + jsx_file.write('\n') # 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")};') + jsx_file.write('\n}else{alert ("Exit Import Blender animation data \\nNo Comp 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('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 -########################################## - + # Set current frame of animation in blender to state before export + data['scn'].frame_set(curframe) -def main(file, context, include_animation, include_active_cam, include_selected_cams, include_selected_objects, include_cam_bundles): - 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) - 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 +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" + bl_options = {'PRESET', 'UNDO'} filename_ext = ".jsx" filter_glob: StringProperty(default="*.jsx", options={'HIDDEN'}) @@ -723,40 +1076,63 @@ class ExportJsx(bpy.types.Operator, ExportHelper): description="Include 3D Markers of Camera Motion Solution for selected cameras", default=True, ) + include_image_planes: BoolProperty( + name="Image Planes", + description="Include image mesh objects", + 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="Scale", + description="Size of AE Composition (pixels per 1 BU)", + default=100.0, + min=0.0, + soft_max=10000, + ) def draw(self, context): layout = self.layout box = layout.box() - box.label(text='Animation:') - box.prop(self, 'include_animation') - box.label(text='Include Cameras and Objects:') + 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_image_planes') + box = layout.box() + box.prop(self, 'include_animation') + box = layout.box() + box.label(text='Transform') + box.prop(self, 'ae_size') + box = layout.box() + 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 + return selected or camera 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) + data = get_comp_data(context) + selection = get_selected(context) + write_jsx_file(self.filepath, data, selection, self.include_animation, + self.include_active_cam, self.include_selected_cams, + self.include_selected_objects, self.include_cam_bundles, + self.include_image_planes, self.ae_size) + print("\nExport to After Effects Completed") + return {'FINISHED'} def menu_func(self, context): - self.layout.operator(ExportJsx.bl_idname, text="Adobe After Effects (.jsx)") + self.layout.operator( + ExportJsx.bl_idname, text="Adobe After Effects (.jsx)") def register(): @@ -768,5 +1144,6 @@ def unregister(): bpy.utils.unregister_class(ExportJsx) bpy.types.TOPBAR_MT_file_export.remove(menu_func) + if __name__ == "__main__": register()