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