[Bf-extensions-cvs] [afb79c2] master: After Effects export: refactor object types into classes

Damien Picard noreply at git.blender.org
Wed May 25 12:43:15 CEST 2022


Commit: afb79c2611c0ffe6a09cd35d899c6e2bad73d59b
Author: Damien Picard
Date:   Tue Jun 15 10:27:31 2021 +0200
Branches: master
https://developer.blender.org/rBACafb79c2611c0ffe6a09cd35d899c6e2bad73d59b

After Effects export: refactor object types into classes

Use classes for each object type, which include data and animation
collection, and script generation, instead of long and similar
functions for each of these steps.

Also:
- add option to export solids;
- do not systematically prefix all AE object names with an underscore,
  as this is not so useful and can be frustrating in comps.

===================================================================

M	io_export_after_effects.py

===================================================================

diff --git a/io_export_after_effects.py b/io_export_after_effects.py
index 8d73d19..4861de9 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",
-    "version": (0, 0, 70),
+    "author": "Bartek Skorupa, Damien Picard (@pioverfour)",
+    "version": (0, 1, 0),
     "blender": (2, 80, 0),
     "location": "File > Export > Adobe After Effects (.jsx)",
     "warning": "",
@@ -61,7 +61,7 @@ def get_comp_data(context):
         'end': end,
         'duration': (end - start + 1.0) / fps,
         'active_cam_frames': active_cam_frames,
-        'curframe': scene.frame_current,
+        'frame_current': scene.frame_current,
         }
 
 
@@ -97,40 +97,285 @@ def get_active_cam_for_each_frame(scene, start, end):
     return(active_cam_frames)
 
 
-def get_selected(context):
-    """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
+class ObjectExport():
+    """Base exporter class
 
-    for ob in obs:
-        if ob.type == 'CAMERA':
-            cameras.append(ob)
+    Collects data about an object and outputs the proper JSX script for AE.
+    """
+    def __init__(self, obj):
+        self.obj = obj
+        self.name_ae = convert_name(self.obj.name)
+        self.keyframes = {}
+
+    def get_prop_keyframe(self, context, prop_name, value, time):
+        """Set keyframe for given property"""
+        prop_keys = self.keyframes.setdefault(prop_name, [])
+        if not len(prop_keys) or value != prop_keys[-1][1]:
+            prop_keys.append((time, value))
+
+    def get_keyframe(self, context, data, time, ae_size):
+        """Store animation for the current frame"""
+        ae_transform = convert_transform_matrix(self.obj.matrix_world,
+                                                data['width'], data['height'],
+                                                data['aspect'], True, ae_size)
+
+        self.get_prop_keyframe(context, 'position', ae_transform[0:3], time)
+        self.get_prop_keyframe(context, 'orientation', ae_transform[3:6], time)
+        self.get_prop_keyframe(context, 'scale', ae_transform[6:9], time)
+
+    def get_obj_script(self, include_animation):
+        """Get the JSX script for the object"""
+        return self.get_type_script() + self.get_prop_script(include_animation) + self.get_post_script()
+
+    def get_type_script(self):
+        """Get the basic part of the JSX script"""
+        type_script = f'var {self.name_ae} = newComp.layers.addNull();\n'
+        type_script += f'{self.name_ae}.threeDLayer = true;\n'
+        type_script += f'{self.name_ae}.source.name = "{self.name_ae}";\n'
+        return type_script
+
+    def get_prop_script(self, include_animation):
+        """Get the part of the JSX script encoding animation"""
+        prop_script = ""
 
-        elif is_image_plane(ob):
-            images.append(ob)
+        # Set values of properties, add keyframes only where needed
+        for prop, keys in self.keyframes.items():
+            if include_animation and len(keys) > 1:
+                times = ",".join((str(k[0]) for k in keys))
+                values = ",".join((str(k[1]) for k in keys)).replace(" ", "")
+                prop_script += (
+                    f'{self.name_ae}.property("{prop}").setValuesAtTimes([{times}],[{values}]);\n')
+            else:
+                value = str(keys[0][1]).replace(" ", "")
+                prop_script += (
+                    f'{self.name_ae}.property("{prop}").setValue({value});\n')
+        prop_script += '\n'
+
+        return prop_script
+
+    def get_post_script(self):
+        """This is only used in lights as a post-treatment after animation"""
+        return ""
+
+class CameraExport(ObjectExport):
+    def get_keyframe(self, context, data, time, ae_size):
+        ae_transform = convert_transform_matrix(self.obj.matrix_world,
+                                                data['width'], data['height'],
+                                                data['aspect'], True, ae_size)
+        zoom = convert_lens(self.obj, data['width'], data['height'],
+                            data['aspect'])
+
+        self.get_prop_keyframe(context, 'position', ae_transform[0:3], time)
+        self.get_prop_keyframe(context, 'orientation', ae_transform[3:6], time)
+        self.get_prop_keyframe(context, 'zoom', zoom, time)
+
+    def get_type_script(self):
+        type_script = f'var {self.name_ae} = newComp.layers.addCamera("{self.name_ae}",[0,0]);\n'
+        type_script += f'{self.name_ae}.autoOrient = AutoOrientType.NO_AUTO_ORIENT;\n'
+        return type_script
+
+
+class LightExport(ObjectExport):
+    def get_keyframe(self, context, data, time, ae_size):
+        ae_transform = convert_transform_matrix(self.obj.matrix_world,
+                                                data['width'], data['height'],
+                                                data['aspect'], True, ae_size)
+        self.type = self.obj.data.type
+        color = list(self.obj.data.color)
+        intensity = self.obj.data.energy * 10.0
+
+        self.get_prop_keyframe(context, 'position', ae_transform[0:3], time)
+        if self.type in {'SPOT', 'SUN'}:
+            self.get_prop_keyframe(context, 'orientation', ae_transform[3:6], time)
+        self.get_prop_keyframe(context, 'intensity', intensity, time)
+        self.get_prop_keyframe(context, 'Color', color, time)
+        if self.type == 'SPOT':
+            cone_angle = degrees(self.obj.data.spot_size)
+            self.get_prop_keyframe(context, 'Cone Angle', cone_angle, time)
+            cone_feather = self.obj.data.spot_blend * 100.0
+            self.get_prop_keyframe(context, 'Cone Feather', cone_feather, time)
+
+    def get_type_script(self):
+        type_script = f'var {self.name_ae} = newComp.layers.addLight("{self.name_ae}", [0.0, 0.0]);\n'
+        type_script += f'{self.name_ae}.autoOrient = AutoOrientType.NO_AUTO_ORIENT;\n'
+        type_script += f'{self.name_ae}.lightType = LightType.SPOT;\n'
+        return type_script
+
+    def get_post_script(self):
+        """Set light type _after_ the orientation, otherwise the property is hidden in AE..."""
+        if self.obj.data.type == 'SUN':
+            post_script = f'{self.name_ae}.lightType = LightType.PARALLEL;\n'
+        elif self.obj.data.type == 'SPOT':
+            post_script = f'{self.name_ae}.lightType = LightType.SPOT;\n'
+        else:
+            post_script = f'{self.name_ae}.lightType = LightType.POINT;\n'
+        return post_script
+
+
+class ImageExport(ObjectExport):
+    def get_keyframe(self, context, data, time, ae_size):
+        # Convert obj transform properties to AE space
+        plane_matrix = get_image_plane_matrix(self.obj)
+        # Scale plane to account for AE's transforms
+        plane_matrix = plane_matrix @ Matrix.Scale(100.0 / data['width'], 4)
+
+        ae_transform = convert_transform_matrix(plane_matrix, data['width'],
+                                                data['height'], data['aspect'],
+                                                True, ae_size)
+        opacity = 0.0 if self.obj.hide_render else 100.0
+
+        if not hasattr(self, 'filepath'):
+            self.filepath = get_image_filepath(self.obj)
+
+        image_width, image_height = get_image_size(self.obj)
+        ratio_to_comp = image_width / data['width']
+        scale = ae_transform[6:9]
+        scale[0] /= ratio_to_comp
+        scale[1] = scale[1] / ratio_to_comp * image_width / image_height
+
+        self.get_prop_keyframe(context, 'position', ae_transform[0:3], time)
+        self.get_prop_keyframe(context, 'orientation', ae_transform[3:6], time)
+        self.get_prop_keyframe(context, 'scale', scale, time)
+        self.get_prop_keyframe(context, 'opacity', opacity, time)
+
+    def get_type_script(self):
+        type_script = f'var newFootage = app.project.importFile(new ImportOptions(File("{self.filepath}")));\n'
+        type_script += 'newFootage.parentFolder = footageFolder;\n'
+        type_script += f'var {self.name_ae} = newComp.layers.add(newFootage);\n'
+        type_script += f'{self.name_ae}.threeDLayer = true;\n'
+        type_script += f'{self.name_ae}.source.name = "{self.name_ae}";\n'
+        return type_script
+
+
+class SolidExport(ObjectExport):
+    def get_keyframe(self, context, data, time, ae_size):
+        # Convert obj transform properties to AE space
+        plane_matrix = get_plane_matrix(self.obj)
+        # Scale plane to account for AE's transforms
+        plane_matrix = plane_matrix @ Matrix.Scale(100.0 / data['width'], 4)
+
+        ae_transform = convert_transform_matrix(plane_matrix, data['width'],
+                                                data['height'], data['aspect'],
+                                                True, ae_size)
+        opacity = 0.0 if self.obj.hide_render else 100.0
+        if not hasattr(self, 'color'):
+            self.color = get_plane_color(self.obj)
+        if not hasattr(self, 'width'):
+            self.width = data['width']
+        if not hasattr(self, 'height'):
+            self.height = data['height']
+
+        scale = ae_transform[6:9]
+        scale[1] *= data['width'] / data['height']
+
+        self.get_prop_keyframe(context, 'position', ae_transform[0:3], time)
+        self.get_prop_keyframe(context, 'orientation', ae_transform[3:6], time)
+        self.get_prop_keyframe(context, 'scale', scale, time)
+        self.get_prop_keyframe(context, 'opacity', opacity, time)
+
+    def get_type_script(self):
+        type_script = f'var {self.name_ae} = newComp.layers.addSolid({self.c

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-extensions-cvs mailing list