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