diff --git a/.arcconfig b/.arcconfig deleted file mode 100644 index b5d479d..0000000 --- a/.arcconfig +++ /dev/null @@ -1,7 +0,0 @@ -{ - "project_id" : "Blender Addons Contrib", - "conduit_uri" : "https://developer.blender.org/", - "phabricator.uri" : "https://developer.blender.org/", - "git.default-relative-commit" : "origin/master", - "arc.land.update.default" : "rebase" -} diff --git a/.gitea/default_merge_message/REBASE_TEMPLATE.md b/.gitea/default_merge_message/REBASE_TEMPLATE.md new file mode 100644 index 0000000..87a0937 --- /dev/null +++ b/.gitea/default_merge_message/REBASE_TEMPLATE.md @@ -0,0 +1,5 @@ +${CommitTitle} + +${CommitBody} + +Pull Request #${PullRequestIndex} diff --git a/.gitea/default_merge_message/SQUASH_TEMPLATE.md b/.gitea/default_merge_message/SQUASH_TEMPLATE.md new file mode 100644 index 0000000..36123d4 --- /dev/null +++ b/.gitea/default_merge_message/SQUASH_TEMPLATE.md @@ -0,0 +1,3 @@ +${PullRequestTitle} + +Pull Request #${PullRequestIndex} diff --git a/.gitea/pull_request_template.yaml b/.gitea/pull_request_template.yaml new file mode 100644 index 0000000..7ca55da --- /dev/null +++ b/.gitea/pull_request_template.yaml @@ -0,0 +1,19 @@ +name: Pull Request +about: Contribute to add-ons bundled with Blender +body: + - type: markdown + attributes: + value: | + ### Instructions + + * [Contributing a new add-on](https://wiki.blender.org/wiki/Process/Addons) + * [Contributing code](https://wiki.blender.org/index.php/Dev:Doc/Process/Contributing_Code) + * [Effective code review](https://wiki.blender.org/index.php/Dev:Doc/Tools/Code_Review) + + By submitting code here, you agree that the code is (compatible with) GNU GPL v2 or later. + + - type: textarea + id: body + attributes: + label: "Description" + hide_label: true diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 93a2956..4b8e99e 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,5 +1,4 @@ -This repository is only used as a mirror of git.blender.org. Blender development happens on -https://developer.blender.org. +This repository is only used as a mirror. Blender development happens on projects.blender.org. To get started with contributing code, please see: https://wiki.blender.org/wiki/Process/Contributing_Code diff --git a/.github/stale.yml b/.github/stale.yml index 9c563f2..db14bfd 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -15,8 +15,7 @@ staleLabel: stale # Comment to post when closing a stale Issue or Pull Request. closeComment: > This issue has been automatically closed, because this repository is only - used as a mirror of git.blender.org. Blender development happens on - developer.blender.org. + used as a mirror. Blender development happens on projects.blender.org. To get started contributing code, please read: https://wiki.blender.org/wiki/Process/Contributing_Code diff --git a/animation_motion_trail.py b/animation_motion_trail.py index 65be2db..d5b0bba 100644 --- a/animation_motion_trail.py +++ b/animation_motion_trail.py @@ -16,8 +16,6 @@ # # ##### END GPL LICENSE BLOCK ##### -# - bl_info = { "name": "Motion Trail", diff --git a/development_ui_classes.py b/development_ui_classes.py index 6516098..24918c0 100644 --- a/development_ui_classes.py +++ b/development_ui_classes.py @@ -16,8 +16,6 @@ # # ##### END GPL LICENSE BLOCK ##### -# - bl_info = { "name": "UI Classes Overview", "author": "lijenstina", diff --git a/io_export_after_effects.py b/io_export_after_effects.py index bca17f7..0db40e8 100644 --- a/io_export_after_effects.py +++ b/io_export_after_effects.py @@ -16,14 +16,12 @@ # # ##### END GPL LICENSE BLOCK ##### -# - 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, 69), + "author": "Bartek Skorupa, Damien Picard (@pioverfour)", + "version": (0, 1, 3), "blender": (2, 80, 0), "location": "File > Export > Adobe After Effects (.jsx)", "warning": "", @@ -36,101 +34,350 @@ bl_info = { import bpy import os import datetime -from math import degrees, floor +from math import degrees from mathutils import Matrix, Vector, Color -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 - aspect = aspect_x / aspect_y - start = scene.frame_start - end = scene.frame_end - active_cam_frames = get_active_cam_for_each_frame(scene, start, end) - fps = floor(scene.render.fps / (scene.render.fps_base) * 1000.0) / 1000.0 +def get_camera_frame_ranges(scene, start, end): + """Get frame ranges for each marker in the timeline - return { - 'scn': scene, - 'width': scene.render.resolution_x, - 'height': scene.render.resolution_y, - 'aspect': aspect, - 'fps': fps, - 'start': start, - 'end': end, - 'duration': (end - start + 1.0) / fps, - 'active_cam_frames': active_cam_frames, - 'curframe': scene.frame_current, - } + For this, start at the end of the timeline, + iterate through each camera-bound marker in reverse, + and get the range from this marker to the end of the previous range. + """ + markers = sorted((m for m in scene.timeline_markers if m.camera is not None), + key=lambda m:m.frame, reverse=True) + + if len(markers) <= 1: + return [[[start, end], scene.camera],] + + camera_frame_ranges = [] + current_frame = end + for m in markers: + if m.frame < current_frame: + camera_frame_ranges.append([[m.frame, current_frame + 1], m.camera]) + current_frame = m.frame - 1 + camera_frame_ranges.reverse() + camera_frame_ranges[0][0][0] = start + return camera_frame_ranges -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 - if markers: - for marker in markers: - if marker.camera: - sorted_markers.append([marker.frame, marker]) - sorted_markers = sorted(sorted_markers) +class ObjectExport(): + """Base exporter class - if sorted_markers: - for frame in range(start, end + 1): - for m, marker in enumerate(sorted_markers): - if marker[0] > frame: - if m != 0: - active_cam_frames.append( - sorted_markers[m - 1][1].camera) - else: - active_cam_frames.append(marker[1].camera) - break - elif m == len(sorted_markers) - 1: - active_cam_frames.append(marker[1].camera) - if not active_cam_frames: - if scene.camera: - # in this case active_cam_frames array will have length of 1. This - # will indicate that there is only one active cam in all frames - active_cam_frames.append(scene.camera) + 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 = {} - 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 - - for ob in obs: - if ob.type == 'CAMERA': - cameras.append(ob) - - elif is_image_plane(ob): - images.append(ob) - - elif is_plane(ob): - solids.append(ob) - - elif ob.type == 'LIGHT': - lights.append(ob) + def get_prop_keyframe(self, prop_name, value, time): + """Get keyframe for given property, only if different from previous value""" + prop_keys = self.keyframes.setdefault(prop_name, []) + if len(prop_keys) == 0: + prop_keys.append([time, value, False]) + return + if value != prop_keys[-1][1]: + prop_keys.append([time, value, False]) + # Store which keys should hold, that is, which are + # the first in a series of identical values else: - nulls.append(ob) + prop_keys[-1][2] = True - selection = { - 'cameras': cameras, - 'images': images, - 'solids': solids, - 'lights': lights, - 'nulls': nulls, - } + def get_keyframe(self, context, width, height, aspect, time, ae_size): + """Store animation for the current frame""" + ae_transform = convert_transform_matrix(self.obj.matrix_world, + width, height, aspect, ae_size) - return selection + self.get_prop_keyframe('position', ae_transform[0:3], time) + self.get_prop_keyframe('orientation', ae_transform[3:6], time) + self.get_prop_keyframe('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_anim_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_anim_script(self, include_animation): + """Get the part of the JSX script encoding animation""" + anim_script = "" + + # 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(" ", "") + anim_script += ( + f'{self.name_ae}.property("{prop}").setValuesAtTimes([{times}],[{values}]);\n') + + # Set to HOLD the frames after which animation is fixed + # for several frames, to avoid interpolation errors + if any(k[2] for k in keys): + anim_script += ( + f'var hold_frames = {[i + 1 for i, k in enumerate(keys) if k[2]]};\n' + 'for (var i = 0; i < hold_frames.length; i++) {\n' + f' {self.name_ae}.property("{prop}").setInterpolationTypeAtKey(hold_frames[i], KeyframeInterpolationType.HOLD);\n' + '}\n') + + # No animation for this property + else: + value = str(keys[0][1]).replace(" ", "") + anim_script += ( + f'{self.name_ae}.property("{prop}").setValue({value});\n') + + anim_script += '\n' + + return anim_script + + def get_post_script(self): + """This is only used in lights as a post-treatment after animation""" + return "" + +class CameraExport(ObjectExport): + def __init__(self, obj, start_time=None, end_time=None): + super().__init__(obj) + self.start_time = start_time + self.end_time = end_time + + def get_keyframe(self, context, width, height, aspect, time, ae_size): + ae_transform = convert_transform_matrix(self.obj.matrix_world, + width, height, aspect, ae_size) + zoom = convert_lens(self.obj, width, height, + aspect) + + self.get_prop_keyframe('position', ae_transform[0:3], time) + self.get_prop_keyframe('orientation', ae_transform[3:6], time) + self.get_prop_keyframe('zoom', zoom, time) + + def get_type_script(self): + type_script = f'var {self.name_ae} = newComp.layers.addCamera("{self.name_ae}",[0,0]);\n' + # Restrict time range when multiple cameras are used (markers) + if self.start_time is not None: + type_script += f'{self.name_ae}.inPoint = {self.start_time};\n' + type_script += f'{self.name_ae}.outPoint = {self.end_time};\n' + type_script += f'{self.name_ae}.autoOrient = AutoOrientType.NO_AUTO_ORIENT;\n' + return type_script + + +class LightExport(ObjectExport): + def get_keyframe(self, context, width, height, aspect, time, ae_size): + ae_transform = convert_transform_matrix(self.obj.matrix_world, + width, height, aspect, 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('position', ae_transform[0:3], time) + if self.type in {'SPOT', 'SUN'}: + self.get_prop_keyframe('orientation', ae_transform[3:6], time) + self.get_prop_keyframe('intensity', intensity, time) + self.get_prop_keyframe('Color', color, time) + if self.type == 'SPOT': + cone_angle = degrees(self.obj.data.spot_size) + self.get_prop_keyframe('Cone Angle', cone_angle, time) + cone_feather = self.obj.data.spot_blend * 100.0 + self.get_prop_keyframe('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, width, height, aspect, 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 / width, 4) + + ae_transform = convert_transform_matrix(plane_matrix, + width, height, aspect, 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 / width + scale = ae_transform[6:9] + if image_height != 0.0: + scale[1] *= image_width / image_height + if ratio_to_comp != 0.0: + scale[0] /= ratio_to_comp + scale[1] /= ratio_to_comp + + self.get_prop_keyframe('position', ae_transform[0:3], time) + self.get_prop_keyframe('orientation', ae_transform[3:6], time) + self.get_prop_keyframe('scale', scale, time) + self.get_prop_keyframe('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, width, height, aspect, 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 / width, 4) + + ae_transform = convert_transform_matrix(plane_matrix, + width, height, aspect, 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 = width + if not hasattr(self, 'height'): + self.height = height + + scale = ae_transform[6:9] + scale[1] *= width / height + + self.get_prop_keyframe('position', ae_transform[0:3], time) + self.get_prop_keyframe('orientation', ae_transform[3:6], time) + self.get_prop_keyframe('scale', scale, time) + self.get_prop_keyframe('opacity', opacity, time) + + def get_type_script(self): + type_script = f'var {self.name_ae} = newComp.layers.addSolid({self.color},"{self.name_ae}",{self.width},{self.height},1.0);\n' + type_script += f'{self.name_ae}.source.name = "{self.name_ae}";\n' + type_script += f'{self.name_ae}.source.parentFolder = footageFolder;\n' + type_script += f'{self.name_ae}.threeDLayer = true;\n' + return type_script + + +class CamBundleExport(ObjectExport): + def __init__(self, obj, track): + self.obj = obj + self.track = track + self.name_ae = convert_name(f'{obj.name}__{track.name}') + self.keyframes = {} + + def get_keyframe(self, context, width, height, aspect, time, ae_size): + # Bundles are in camera space. + # Transpose to world space + matrix = self.obj.matrix_basis @ Matrix.Translation(self.track.bundle) + # Convert the position into AE space + ae_transform = convert_transform_matrix(matrix, + width, height, aspect, ae_size) + + self.get_prop_keyframe('position', ae_transform[0:3], time) + self.get_prop_keyframe('orientation', ae_transform[3:6], time) + + def get_type_script(self): + 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_camera_bundles(scene, camera): + cam_bundles = [] + + for constraint in camera.constraints: + if constraint.type == 'CAMERA_SOLVER': + # Which movie clip does it use + if constraint.use_active_clip: + clip = scene.active_clip + else: + clip = constraint.clip + + # Go through each tracking point + for track in clip.tracking.tracks: + # Does this tracking point have a bundle + # (has its 3D position been solved) + if track.has_bundle: + cam_bundles.append(CamBundleExport(camera, track)) + + return cam_bundles + + +def get_selected(context, include_active_cam, include_selected_cams, + include_selected_objects, include_cam_bundles, + include_image_planes, include_solids): + """Create manageable list of selected objects""" + cameras = [] + solids = [] # Meshes exported as AE solids + images = [] # Meshes exported as AE AV layers + lights = [] # Lights exported as AE lights + cam_bundles = [] # Camera trackers exported as AE nulls + nulls = [] # Remaining objects exported as AE nulls + + scene = context.scene + fps = scene.render.fps / scene.render.fps_base + + if context.scene.camera is not None: + if include_active_cam: + for frame_range, camera in get_camera_frame_ranges( + context.scene, + context.scene.frame_start, context.scene.frame_end): + + if (include_cam_bundles + and camera not in (cam.obj for cam in cameras)): + cam_bundles.extend( + get_camera_bundles(context.scene, camera)) + + cameras.append( + CameraExport(camera, + (frame_range[0] - scene.frame_start) / fps, + (frame_range[1] - scene.frame_start) / fps)) + + for obj in context.selected_objects: + if obj.type == 'CAMERA': + # Ignore camera if already selected + if obj in (cam.obj for cam in cameras): + continue + if include_selected_cams: + cameras.append(CameraExport(obj)) + if include_cam_bundles: + cam_bundles.extend(get_camera_bundles(context.scene, obj)) + + elif include_image_planes and is_image_plane(obj): + images.append(ImageExport(obj)) + + elif include_solids and is_plane(obj): + solids.append(SolidExport(obj)) + + elif include_selected_objects: + if obj.type == 'LIGHT': + lights.append(LightExport(obj)) + else: + nulls.append(ObjectExport(obj)) + + return {'cameras': cameras, + 'images': images, + 'solids': solids, + 'lights': lights, + 'nulls': nulls, + 'cam_bundles': cam_bundles} def get_first_material(obj): @@ -154,7 +401,7 @@ def get_plane_color(obj): 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]) + return str(list(color)) def is_plane(obj): @@ -197,11 +444,12 @@ def is_image_plane(obj): - The mesh is a plane - The mesh has exactly one material - There is only one image in this material node tree + - The rectangle is UV unwrapped and its UV is occupying the whole space """ if not is_plane(obj): return False - if not len(obj.material_slots): + if len(obj.material_slots) == 0: return False mat = get_first_material(obj) @@ -212,9 +460,13 @@ def is_image_plane(obj): if img is None: return False - if len(obj.data.vertices) == 4: - return True + if len(obj.data.vertices) != 4: + return False + if not get_image_plane_matrix(obj): + return False + + return True def get_image_filepath(obj): mat = get_first_material(obj) @@ -252,6 +504,7 @@ def get_image_plane_matrix(obj): This will only work if uvs occupy all space, to get bounds """ + p0, px, py = None, None, None for p_i, p in enumerate(obj.data.uv_layers.active.data): if p.uv == Vector((0, 0)): p0 = p_i @@ -260,6 +513,9 @@ def get_image_plane_matrix(obj): elif p.uv == Vector((0, 1)): py = p_i + if None in (p0, px, py): + return False + verts = obj.data.vertices loops = obj.data.loops @@ -276,31 +532,22 @@ def get_image_plane_matrix(obj): 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. - # This section is commented, as "_" is added at beginning of names anyway. - # Placeholder for this name modification is left so that it's not ignored if needed - if name[0].isdigit(): + if not name[0].isalpha(): name = "_" + name - ''' name = bpy.path.clean_name(name) name = name.replace("-", "_") return name -def convert_transform_matrix(matrix, width, height, aspect, - x_rot_correction=False, ae_size=100.0): +def convert_transform_matrix(matrix, width, height, aspect, ae_size=100.0): """Convert from Blender's Location, Rotation and Scale to AE's Position, Rotation/Orientation and Scale This function will be called for every object for every frame """ - scale_mat = Matrix.Scale(width, 4) - - # Get blender transform data for ob + # Get blender transform data for object b_loc = matrix.to_translation() b_rot = matrix.to_euler('ZYX') # ZYX euler matches AE's orientation and allows to use x_rot_correction b_scale = matrix.to_scale() @@ -314,20 +561,19 @@ def convert_transform_matrix(matrix, width, height, aspect, z = (b_loc.y * 100.0) * ae_size / 100.0 # Convert rotations to match AE's orientation. - # If not x_rot_correction - rx = degrees(b_rot.x) # AE's X orientation = blender's X rotation if 'ZYX' euler. + # In Blender, object of zero rotation lays on floor. + # In AE, layer of zero orientation "stands", so subtract 90 degrees + rx = degrees(b_rot.x) - 90.0 # 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: - # 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 + return [x, y, z, rx, ry, rz, sx, sy, sz] + # Get camera's lens and convert to AE's "zoom" value in pixels # this function will be called for every camera for every frame @@ -387,7 +633,6 @@ def convert_transform_matrix(matrix, width, height, aspect, # aspect compensation is needed, so final formula is: # zoom = lens * dimension / sensor * aspect - def convert_lens(camera, width, height, aspect): if camera.data.sensor_fit == 'VERTICAL': sensor = camera.data.sensor_height @@ -411,639 +656,95 @@ def convert_lens(camera, width, height, aspect): # return matrix -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): +def write_jsx_file(context, file, selection, include_animation, 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 - curframe = data['curframe'] - # Create array which will contain all keyframes values - js_data = { - 'times': '', - 'cameras': {}, - 'images': {}, - 'solids': {}, - 'lights': {}, - 'nulls': {}, - 'bundles_cam': {}, - 'bundles_ob': {}, # not ready yet - } + print("\n---------------------------\n" + "- Export to After Effects -\n" + "---------------------------") - # Create structure for active camera/cameras - active_cam_name = '' - 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: - # Take name of the only active camera in scene - name_ae = convert_name(data['active_cam_frames'][0].name) - else: - name_ae = 'Active_Camera' - # Store name to be used when creating keyframes for active cam - active_cam_name = name_ae - js_data['cameras'][name_ae] = { - 'position': '', - 'position_static': '', - 'position_anim': False, - 'orientation': '', - 'orientation_static': '', - 'orientation_anim': False, - 'zoom': '', - 'zoom_static': '', - 'zoom_anim': False, - } - - # Create camera structure for selected cameras - if include_selected_cams: - for 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': '', - 'position_anim': False, - 'orientation': '', - 'orientation_static': '', - 'orientation_anim': False, - 'zoom': '', - 'zoom_static': '', - 'zoom_anim': False, - } - - # 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': '', - 'orientation_static': '', - 'orientation_anim': False, - 'scale': '', - 'scale_static': '', - 'scale_anim': False, - } - - # 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 = obj.data.type + convert_name(obj.name) - js_data['lights'][name_ae] = { - '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, - 'orientation': '', - 'orientation_static': '', - 'orientation_anim': False, - } - - # 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 = convert_name(obj.name) - js_data['nulls'][name_ae] = { - 'position': '', - 'position_static': '', - 'position_anim': False, - 'orientation': '', - 'orientation_static': '', - 'orientation_anim': False, - 'scale': '', - 'scale_static': '', - 'scale_anim': False, - } - - # Create structure for cam bundles including positions - # (cam bundles don't move) - if include_cam_bundles: - # Go through each selected camera and active cameras - selected_cams = [] - active_cams = [] - if include_active_cam: - active_cams = data['active_cam_frames'] - if include_selected_cams: - for cam in selection['cameras']: - selected_cams.append(cam) - # List of cameras that will be checked for 'CAMERA SOLVER' - cams = list(set.union(set(selected_cams), set(active_cams))) - - for cam in cams: - # Go through each constraints of this camera - for constraint in cam.constraints: - # Does the camera have a Camera Solver constraint - if constraint.type == 'CAMERA_SOLVER': - # Which movie clip does it use - if constraint.use_active_clip: - clip = data['scn'].active_clip - else: - clip = constraint.clip - - # Go through each tracking point - for track in clip.tracking.tracks: - # Does this tracking point have a bundle - # (has its 3D position been solved) - if track.has_bundle: - # Get the name of the tracker - name_ae = convert_name(str(cam.name) + '__' + - str(track.name)) - js_data['bundles_cam'][name_ae] = { - 'position': '', - } - # Bundles are in camera space. - # Transpose to world space - matrix = Matrix.Translation(cam.matrix_basis.copy() - @ track.bundle) - # Convert the position into AE space - ae_transform = (convert_transform_matrix( - matrix, data['width'], data['height'], - data['aspect'], False, ae_size)) - js_data['bundles_cam'][name_ae]['position'] += ('[%f,%f,%f],' % (ae_transform[0], ae_transform[1], ae_transform[2])) - - # Get all keyframes for each object and store in dico + # Create list of static blender data + scene = context.scene + width = scene.render.resolution_x + height = scene.render.resolution_y + aspect_x = scene.render.pixel_aspect_x + aspect_y = scene.render.pixel_aspect_y + aspect = aspect_x / aspect_y if include_animation: - end = data['end'] + 1 + frame_end = scene.frame_end + 1 else: - end = data['start'] + 1 - for frame in range(data['start'], end): - print("working on frame: " + str(frame)) - data['scn'].frame_set(frame) + frame_end = scene.frame_start + 1 + fps = scene.render.fps / scene.render.fps_base + duration = (frame_end - scene.frame_start) / fps + + # Store the current frame to restore it at the end of export + frame_current = scene.frame_current + + # Get all keyframes for each object + for frame in range(scene.frame_start, frame_end): + print("Working on frame: " + str(frame)) + scene.frame_set(frame) # Get time for this loop - js_data['times'] += '%f,' % ((frame - data['start']) / data['fps']) + time = (frame - scene.frame_start) / fps - # Keyframes for active camera/cameras - if include_active_cam and data['active_cam_frames'] != []: - if len(data['active_cam_frames']) == 1: - cur_cam_index = 0 - else: - cur_cam_index = frame - data['start'] - active_cam = data['active_cam_frames'][cur_cam_index] - # Get cam name - name_ae = active_cam_name - # Convert cam transform properties to AE space - ae_transform = (convert_transform_matrix( - active_cam.matrix_world.copy(), data['width'], data['height'], - data['aspect'], True, ae_size)) - # Convert Blender's lens to AE's zoom in pixels - zoom = convert_lens(active_cam, data['width'], data['height'], - data['aspect']) - # Store all values in dico - position = '[%f,%f,%f],' % (ae_transform[0], ae_transform[1], - ae_transform[2]) - orientation = '[%f,%f,%f],' % (ae_transform[3], ae_transform[4], - ae_transform[5]) - zoom = '%f,' % (zoom) - js_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 frame != data['start']: - 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 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_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 frame != data['start']: - 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) - # 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) - # 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 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_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 frame != data['start']: - 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(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 frame != data['start']: - 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 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) - # 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) - # 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 frame != data['start']: - 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. - # - # - # + for obj_type in selection.values(): + for obj in obj_type: + obj.get_keyframe(context, width, height, aspect, time, ae_size) # ---- write JSX file - jsx_file = open(file, 'w') + with open(file, 'w') as jsx_file: + # Make the jsx executable in After Effects (enable double click on jsx) + jsx_file.write('#target AfterEffects\n\n') + # Script's header + jsx_file.write('/**************************************\n') + jsx_file.write(f'Scene : {scene.name}\n') + jsx_file.write(f'Resolution : {width} x {height}\n') + jsx_file.write(f'Duration : {duration}\n') + jsx_file.write(f'FPS : {fps}\n') + jsx_file.write(f'Date : {datetime.datetime.now()}\n') + jsx_file.write('Exported with io_export_after_effects.py\n') + jsx_file.write('**************************************/\n\n\n\n') - # Make the jsx executable in After Effects (enable double click on jsx) - jsx_file.write('#target AfterEffects\n\n') - # Script's header - jsx_file.write('/**************************************\n') - jsx_file.write('Scene : %s\n' % data['scn'].name) - jsx_file.write('Resolution : %i x %i\n' % (data['width'], data['height'])) - jsx_file.write('Duration : %f\n' % (data['duration'])) - jsx_file.write('FPS : %f\n' % (data['fps'])) - jsx_file.write('Date : %s\n' % datetime.datetime.now()) - jsx_file.write('Exported with io_export_after_effects.py\n') - jsx_file.write('**************************************/\n\n\n\n') + # Wrap in function + jsx_file.write("function compFromBlender(){\n") - # Wrap in function - jsx_file.write("function compFromBlender(){\n") - # 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' - % (name_ae, obj['position'])) - jsx_file.write('\n') - - # Create object bundles (not ready yet) - - # 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 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(): + # 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(f'\nvar compName = prompt("Blender Comp\'s Name \\nEnter Name of newly created Composition","{comp_name}","Composition\'s Name");\n') + jsx_file.write('if (compName){') + # Continue only if comp name is given. If not - terminate 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') + f'\nvar newComp = app.project.items.addComp(compName, {width}, ' + f'{height}, {aspect}, {duration}, {fps});') + jsx_file.write(f"\nnewComp.displayStartTime = {scene.frame_start / fps};\n\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') + jsx_file.write('var footageFolder = app.project.items.addFolder(compName + "_layers")\n\n\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') + # Write each object's creation script + for obj_type in ('cam_bundles', 'nulls', 'solids', 'images', 'lights', 'cameras'): + if len(selection[obj_type]): + type_name = 'CAMERA 3D MARKERS' if obj_type == 'cam_bundles' else obj_type.upper() + jsx_file.write(f'// ************** {type_name} **************\n\n') + for obj in selection[obj_type]: + jsx_file.write(obj.get_obj_script(include_animation)) + 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) + # Exit import if no comp name given + 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('app.endUndoGroup();\n\n\n') - # 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 name has been chosen","EXIT")};') - # Close function - jsx_file.write("}\n\n\n") - # Execute function. Wrap in "undo group" for easy undoing import process - jsx_file.write('app.beginUndoGroup("Import Blender animation data");\n') - jsx_file.write('compFromBlender();\n') # Execute function - jsx_file.write('app.endUndoGroup();\n\n\n') - jsx_file.close() - - # Set current frame of animation in blender to state before export - data['scn'].frame_set(curframe) + # Restore current frame of animation in blender to state before export + scene.frame_set(frame_current) ########################################## @@ -1093,6 +794,11 @@ class ExportJsx(bpy.types.Operator, ExportHelper): description="Include image mesh objects", default=True, ) + include_solids: BoolProperty( + name="Solids", + description="Include rectangles as solids", + default=True, + ) # include_ob_bundles = BoolProperty( # name="Objects 3D Markers", # description="Include 3D Markers of Object Motion Solution for selected cameras", @@ -1111,19 +817,24 @@ class ExportJsx(bpy.types.Operator, ExportHelper): box = layout.box() 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.prop(self, 'include_image_planes') + col = box.column(align=True) + col.prop(self, 'include_active_cam') + col.prop(self, 'include_selected_cams') + col.prop(self, 'include_selected_objects') + col.prop(self, 'include_image_planes') + col.prop(self, 'include_solids') + + box = layout.box() + box.label(text='Include Tracking Data') + box.prop(self, 'include_cam_bundles') +# box.prop(self, 'include_ob_bundles') + 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): @@ -1132,12 +843,14 @@ class ExportJsx(bpy.types.Operator, ExportHelper): return selected or camera def execute(self, context): - 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) + selection = get_selected(context, self.include_active_cam, + self.include_selected_cams, + self.include_selected_objects, + self.include_cam_bundles, + self.include_image_planes, + self.include_solids) + write_jsx_file(context, self.filepath, selection, + self.include_animation, self.ae_size) print("\nExport to After Effects Completed") return {'FINISHED'} diff --git a/io_scene_3ds/__init__.py b/io_scene_3ds/__init__.py index 0137dd2..3912ac1 100644 --- a/io_scene_3ds/__init__.py +++ b/io_scene_3ds/__init__.py @@ -16,8 +16,6 @@ # # ##### END GPL LICENSE BLOCK ##### -# - from bpy_extras.io_utils import ( ImportHelper, ExportHelper, @@ -34,8 +32,8 @@ import bpy bl_info = { "name": "Autodesk 3DS format", "author": "Bob Holcomb, Campbell Barton, Andreas Atteneder, Sebastian Schrand", - "version": (2, 1, 0), - "blender": (2, 82, 0), + "version": (2, 2, 0), + "blender": (3, 0, 0), "location": "File > Import", "description": "Import 3DS, meshes, uvs, materials, textures, " "cameras & lamps", diff --git a/io_scene_3ds/export_3ds.py b/io_scene_3ds/export_3ds.py index 0ee332a..2a22d74 100644 --- a/io_scene_3ds/export_3ds.py +++ b/io_scene_3ds/export_3ds.py @@ -16,8 +16,6 @@ # # ##### END GPL LICENSE BLOCK ##### -# - # Script copyright (C) Bob Holcomb # Contributors: Campbell Barton, Bob Holcomb, Richard Lärkäng, Damien McGinnes, Mark Stijnman, Sebastian Sille @@ -647,8 +645,8 @@ def make_material_chunk(material, image): name_str = material.name if material else "None" - if image: - name_str += image.name + #if image: + # name_str += image.name name.add_variable("name", _3ds_string(sane_name(name_str))) material_chunk.add_subchunk(name) @@ -672,6 +670,7 @@ def make_material_chunk(material, image): material_chunk.add_subchunk(make_percent_subchunk(MATSHIN2, wrap.specular)) material_chunk.add_subchunk(make_percent_subchunk(MATSHIN3, wrap.metallic)) material_chunk.add_subchunk(make_percent_subchunk(MATTRANS, 1 - wrap.alpha)) + material_chunk.add_subchunk(make_percent_subchunk(MATSELFILPCT, wrap.emission_strength)) material_chunk.add_subchunk(shading) if wrap.base_color_texture: @@ -706,13 +705,10 @@ def make_material_chunk(material, image): normal = [wrap.normalmap_texture] bump = wrap.normalmap_strength b_pct = min(bump, 1) - bumpval = min(999, (bump * 100)) # 3ds max bump = 999 - strength = _3ds_chunk(MAT_BUMP_PERCENT) - strength.add_variable("bump_pct", _3ds_ushort(int(bumpval))) matmap = make_material_texture_chunk(MAT_BUMPMAP, normal, b_pct) if matmap: material_chunk.add_subchunk(matmap) - material_chunk.add_subchunk(strength) + material_chunk.add_subchunk(make_percent_subchunk(MAT_BUMP_PERCENT, b_pct)) if wrap.roughness_texture: roughness = [wrap.roughness_texture] @@ -722,7 +718,7 @@ def make_material_chunk(material, image): material_chunk.add_subchunk(matmap) if wrap.emission_color_texture: - e_pct = sum(wrap.emission_color[:]) * .25 + e_pct = wrap.emission_strength emission = [wrap.emission_color_texture] matmap = make_material_texture_chunk(MAT_SELFIMAP, emission, e_pct) if matmap: @@ -909,8 +905,8 @@ def make_faces_chunk(tri_list, mesh, materialDict): context_face_array = unique_mats[ma, img][1] except: name_str = ma if ma else "None" - if img: - name_str += img + #if img: + # name_str += img context_face_array = _3ds_array() unique_mats[ma, img] = _3ds_string(sane_name(name_str)), context_face_array @@ -1171,7 +1167,7 @@ def save(operator, ): import time - from bpy_extras.io_utils import create_derived_objects, free_derived_objects + #from bpy_extras.io_utils import create_derived_objects, free_derived_objects """Save the Blender scene to a 3ds file.""" @@ -1187,7 +1183,7 @@ def save(operator, scene = context.scene layer = context.view_layer - #depsgraph = context.evaluated_depsgraph_get() + depsgraph = context.evaluated_depsgraph_get() # Initialize the main chunk (primary): primary = _3ds_chunk(PRIMARY) @@ -1235,7 +1231,9 @@ def save(operator, for ob in objects: # get derived objects - free, derived = create_derived_objects(scene, ob) + #free, derived = create_derived_objects(scene, ob) + derived_dict = bpy_extras.io_utils.create_derived_objects(depsgraph, [ob]) + derived = derived_dict.get(ob) if derived is None: continue @@ -1244,7 +1242,6 @@ def save(operator, if ob.type not in {'MESH', 'CURVE', 'SURFACE', 'FONT', 'META'}: continue - #ob_derived_eval = ob_derived.evaluated_get(depsgraph) try: data = ob_derived.to_mesh() except: @@ -1288,8 +1285,8 @@ def save(operator, # ob_derived_eval.to_mesh_clear() - if free: - free_derived_objects(ob) + #if free: + # free_derived_objects(ob) # Make material chunks for all materials used in the meshes: for ma_image in materialDict.values(): diff --git a/io_scene_3ds/import_3ds.py b/io_scene_3ds/import_3ds.py index 193f53e..8dfa475 100644 --- a/io_scene_3ds/import_3ds.py +++ b/io_scene_3ds/import_3ds.py @@ -16,8 +16,6 @@ # # ##### END GPL LICENSE BLOCK ##### -# - # Script copyright (C) Bob Holcomb # Contributors: Bob Holcomb, Richard L?rk?ng, Damien McGinnes, Sebastian Sille # Campbell Barton, Mario Lapin, Dominique Lorre, Andreas Atteneder @@ -65,6 +63,7 @@ MAT_SHIN2 = 0xA041 # Shininess of the object/material (percent) MAT_SHIN3 = 0xA042 # Reflection of the object/material (percent) MAT_TRANSPARENCY = 0xA050 # Transparency value of material (percent) MAT_SELF_ILLUM = 0xA080 # Self Illumination value of material +MATSELFILPCT = 0xA084 # Self illumination strength (percent) MAT_WIRE = 0xA085 # Only render's wireframe MAT_TEXTURE_MAP = 0xA200 # This is a header for a new texture map @@ -467,6 +466,7 @@ def process_next_chunk(context, file, previous_chunk, imported_objects, IMAGE_SE pct = 50 contextWrapper.emission_color = contextMaterial.line_color[:3] + contextWrapper.emission_strength = contextMaterial.line_priority / 100 contextWrapper.base_color = contextMaterial.diffuse_color[:3] contextWrapper.specular = contextMaterial.specular_intensity contextWrapper.roughness = contextMaterial.roughness @@ -669,6 +669,18 @@ def process_next_chunk(context, file, previous_chunk, imported_objects, IMAGE_SE print("Cannot read material transparency") new_chunk.bytes_read += temp_chunk.bytes_read + elif new_chunk.ID == MATSELFILPCT: + read_chunk(file, temp_chunk) + if temp_chunk.ID == PERCENTAGE_SHORT: + temp_data = file.read(SZ_U_SHORT) + temp_chunk.bytes_read += SZ_U_SHORT + contextMaterial.line_priority = int(struct.unpack('H', temp_data)[0]) + elif temp_chunk.ID == PERCENTAGE_FLOAT: + temp_data = file.read(SZ_FLOAT) + temp_chunk.bytes_read += SZ_FLOAT + contextMaterial.line_priority = (float(struct.unpack('f', temp_data)[0]) * 100) + new_chunk.bytes_read += temp_chunk.bytes_read + elif new_chunk.ID == MAT_TEXTURE_MAP: read_texture(new_chunk, temp_chunk, "Diffuse", "COLOR") @@ -686,11 +698,18 @@ def process_next_chunk(context, file, previous_chunk, imported_objects, IMAGE_SE read_texture(new_chunk, temp_chunk, "Bump", "NORMAL") elif new_chunk.ID == MAT_BUMP_PERCENT: - temp_data = file.read(SZ_U_SHORT) - new_chunk.bytes_read += SZ_U_SHORT - contextWrapper.normalmap_strength = (float(struct.unpack(' - bl_info = { "name": "Open Street Map (.osm)", "author": "Michael Anthrax Schlachter, ideasman42, littleneo", diff --git a/io_vector/__init__.py b/io_vector/__init__.py index 6abf2d5..6986b03 100644 --- a/io_vector/__init__.py +++ b/io_vector/__init__.py @@ -16,8 +16,6 @@ # # ##### END GPL LICENSE BLOCK ##### -# - bl_info = { "name": "Adobe Illustrator / PDF / SVG", "author": "Howard Trickey", diff --git a/io_vector/art2polyarea.py b/io_vector/art2polyarea.py index b49d3a2..27624e7 100644 --- a/io_vector/art2polyarea.py +++ b/io_vector/art2polyarea.py @@ -16,8 +16,6 @@ # # ##### END GPL LICENSE BLOCK ##### -# - """Convert an Art object to a list of PolyArea objects. """ diff --git a/io_vector/geom.py b/io_vector/geom.py index a7eb4fe..c2f3366 100644 --- a/io_vector/geom.py +++ b/io_vector/geom.py @@ -16,8 +16,6 @@ # # ##### END GPL LICENSE BLOCK ##### -# - """Geometry classes and operations. Also, vector file representation (Art). """ diff --git a/io_vector/import_vecfile.py b/io_vector/import_vecfile.py index 163eefc..db8674b 100644 --- a/io_vector/import_vecfile.py +++ b/io_vector/import_vecfile.py @@ -16,8 +16,6 @@ # # ##### END GPL LICENSE BLOCK ##### -# - """Importing a vector file into Model format. """ diff --git a/io_vector/model.py b/io_vector/model.py index a3eb2aa..a714e83 100644 --- a/io_vector/model.py +++ b/io_vector/model.py @@ -16,8 +16,6 @@ # # ##### END GPL LICENSE BLOCK ##### -# - """Manipulations of Models. """ diff --git a/io_vector/offset.py b/io_vector/offset.py index 4e860b6..df04d2f 100644 --- a/io_vector/offset.py +++ b/io_vector/offset.py @@ -16,8 +16,6 @@ # # ##### END GPL LICENSE BLOCK ##### -# - """Creating offset polygons inside faces.""" __author__ = "howard.trickey@gmail.com" diff --git a/io_vector/pdf.py b/io_vector/pdf.py index e2e3199..4e8f2f4 100644 --- a/io_vector/pdf.py +++ b/io_vector/pdf.py @@ -16,8 +16,6 @@ # # ##### END GPL LICENSE BLOCK ##### -# - """Functions for dealing with PDF files. """ diff --git a/io_vector/svg.py b/io_vector/svg.py index 4a2012b..1ef5443 100644 --- a/io_vector/svg.py +++ b/io_vector/svg.py @@ -16,8 +16,6 @@ # # ##### END GPL LICENSE BLOCK ##### -# - """Reading SVG file format. """ diff --git a/io_vector/triquad.py b/io_vector/triquad.py index 88affa8..edced67 100644 --- a/io_vector/triquad.py +++ b/io_vector/triquad.py @@ -16,8 +16,6 @@ # # ##### END GPL LICENSE BLOCK ##### -# - from . import geom import math diff --git a/io_vector/vecfile.py b/io_vector/vecfile.py index 808a84e..594255a 100644 --- a/io_vector/vecfile.py +++ b/io_vector/vecfile.py @@ -16,8 +16,6 @@ # # ##### END GPL LICENSE BLOCK ##### -# - """Reading various vector file formats. Functions for classifying files, tokenizing, and parsing them. diff --git a/mesh_show_vgroup_weights.py b/mesh_show_vgroup_weights.py index f16adcd..ca603d1 100644 --- a/mesh_show_vgroup_weights.py +++ b/mesh_show_vgroup_weights.py @@ -17,8 +17,6 @@ # # ***** END GPL LICENCE BLOCK ***** -# (Thanks to CodemanX on IRC) - bl_info = { "name": "Show Vertex Groups/Weights", "author": "Jason van Gumster (Fweeb), Bartius Crouch, CoDEmanX", diff --git a/mocap/__init__.py b/mocap/__init__.py index 710db64..fecbf8c 100644 --- a/mocap/__init__.py +++ b/mocap/__init__.py @@ -16,8 +16,6 @@ # # ##### END GPL LICENSE BLOCK ##### -# - bl_info = { "name": "Motion Capture Tools", "author": "Benjy Cook", diff --git a/mocap/mocap_constraints.py b/mocap/mocap_constraints.py index 3d3e4a6..6d4a34c 100644 --- a/mocap/mocap_constraints.py +++ b/mocap/mocap_constraints.py @@ -16,8 +16,6 @@ # # ##### END GPL LICENSE BLOCK ##### -# - import bpy from mathutils import Vector from bpy_extras import anim_utils diff --git a/mocap/mocap_tools.py b/mocap/mocap_tools.py index cc85529..04a4261 100644 --- a/mocap/mocap_tools.py +++ b/mocap/mocap_tools.py @@ -16,8 +16,6 @@ # # ##### END GPL LICENSE BLOCK ##### -# - from math import sqrt, radians, floor, ceil import bpy import time diff --git a/mocap/retarget.py b/mocap/retarget.py index 4fa5d2c..5c963c0 100644 --- a/mocap/retarget.py +++ b/mocap/retarget.py @@ -16,8 +16,6 @@ # # ##### END GPL LICENSE BLOCK ##### -# - import bpy from mathutils import Vector, Matrix from math import radians diff --git a/object_facemap_auto/__init__.py b/object_facemap_auto/__init__.py index 0a309bc..1085633 100644 --- a/object_facemap_auto/__init__.py +++ b/object_facemap_auto/__init__.py @@ -16,8 +16,6 @@ # # ##### END GPL LICENSE BLOCK ##### -# - bl_info = { "name": "Auto Face Map Widgets", "author": "Campbell Barton", diff --git a/object_facemap_auto/auto_fmap_ops.py b/object_facemap_auto/auto_fmap_ops.py index df2434f..4ed3aef 100644 --- a/object_facemap_auto/auto_fmap_ops.py +++ b/object_facemap_auto/auto_fmap_ops.py @@ -16,8 +16,6 @@ # # ##### END GPL LICENSE BLOCK ##### -# - import bpy from bpy.types import ( Operator, diff --git a/object_facemap_auto/auto_fmap_utils.py b/object_facemap_auto/auto_fmap_utils.py index 0a02907..ba825c0 100644 --- a/object_facemap_auto/auto_fmap_utils.py +++ b/object_facemap_auto/auto_fmap_utils.py @@ -16,8 +16,6 @@ # # ##### END GPL LICENSE BLOCK ##### -# - # Use so we can develop modules without reloading the add-on. diff --git a/object_facemap_auto/auto_fmap_widgets.py b/object_facemap_auto/auto_fmap_widgets.py index 6522eb4..24ddf10 100644 --- a/object_facemap_auto/auto_fmap_widgets.py +++ b/object_facemap_auto/auto_fmap_widgets.py @@ -16,8 +16,6 @@ # # ##### END GPL LICENSE BLOCK ##### -# - ''' Face map manipulator: diff --git a/object_facemap_auto/auto_fmap_widgets_xform.py b/object_facemap_auto/auto_fmap_widgets_xform.py index 71b99cf..5b4ae1f 100644 --- a/object_facemap_auto/auto_fmap_widgets_xform.py +++ b/object_facemap_auto/auto_fmap_widgets_xform.py @@ -16,8 +16,6 @@ # # ##### END GPL LICENSE BLOCK ##### -# - import bpy import math diff --git a/object_fracture_crack/process/cell_calc.py b/object_fracture_crack/process/cell_calc.py index 2e47eaf..87fba05 100644 --- a/object_fracture_crack/process/cell_calc.py +++ b/object_fracture_crack/process/cell_calc.py @@ -16,8 +16,6 @@ # # ##### END GPL LICENSE BLOCK ##### -# - # Script copyright (C) Blender Foundation 2012 diff --git a/render_cube_map.py b/render_cube_map.py index 06417ba..363bc02 100644 --- a/render_cube_map.py +++ b/render_cube_map.py @@ -16,8 +16,6 @@ # # ======================= END GPL LICENSE BLOCK ======================== -# - # ######################################## # Render Cube Map # diff --git a/render_to_print.py b/render_to_print.py index 52d2fd5..fdc54cd 100644 --- a/render_to_print.py +++ b/render_to_print.py @@ -16,8 +16,6 @@ # # ##### END GPL LICENSE BLOCK ##### -# - bl_info = { "name": "Render to Print", "author": "Marco Crippa , Dealga McArdle, zebus3d", diff --git a/system_keyboard_svg.py b/system_keyboard_svg.py index bf6ab61..db14916 100644 --- a/system_keyboard_svg.py +++ b/system_keyboard_svg.py @@ -16,8 +16,6 @@ # # ##### END GPL LICENSE BLOCK ##### -# - # this script creates Keyboard layout images of the current keyboard configuration. # first implementation done by jbakker # version 0.2 - file manager directory on export, modified the SVG layout (lijenstina)