diff --git a/.arcconfig b/.arcconfig new file mode 100644 index 0000000000000000000000000000000000000000..b5d479dd7535026bfbcd341030417b1dfdfda5a4 --- /dev/null +++ b/.arcconfig @@ -0,0 +1,7 @@ +{ + "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 deleted file mode 100644 index 87a09370e270f126d99e81d1f74eea9371fd14b1..0000000000000000000000000000000000000000 --- a/.gitea/default_merge_message/REBASE_TEMPLATE.md +++ /dev/null @@ -1,5 +0,0 @@ -${CommitTitle} - -${CommitBody} - -Pull Request #${PullRequestIndex} diff --git a/.gitea/default_merge_message/SQUASH_TEMPLATE.md b/.gitea/default_merge_message/SQUASH_TEMPLATE.md deleted file mode 100644 index 36123d4d8ce23f55d1df1a86e13a851640fb8e09..0000000000000000000000000000000000000000 --- a/.gitea/default_merge_message/SQUASH_TEMPLATE.md +++ /dev/null @@ -1,3 +0,0 @@ -${PullRequestTitle} - -Pull Request #${PullRequestIndex} diff --git a/.gitea/pull_request_template.yaml b/.gitea/pull_request_template.yaml deleted file mode 100644 index 7ca55daf0ca14e77c239dbe6811832dacb040bcb..0000000000000000000000000000000000000000 --- a/.gitea/pull_request_template.yaml +++ /dev/null @@ -1,19 +0,0 @@ -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 4b8e99e669aae89cd5ec21428391ad41ee188436..93a29565e542631d1fdd1748944ce39c5ba97ce4 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,4 +1,5 @@ -This repository is only used as a mirror. Blender development happens on projects.blender.org. +This repository is only used as a mirror of git.blender.org. Blender development happens on +https://developer.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 db14bfd9d31d3cddddea7974cade7a1224fdef87..9c563f2dee008a93d6a01384accbb8a99728075b 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -15,7 +15,8 @@ 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. Blender development happens on projects.blender.org. + used as a mirror of git.blender.org. Blender development happens on + developer.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 d5b0bba729e75bd7db8b33c96417a6f0dd712db1..65be2db6bf2c2bfde0fb813a17e05802aeee9bf5 100644 --- a/animation_motion_trail.py +++ b/animation_motion_trail.py @@ -16,6 +16,8 @@ # # ##### END GPL LICENSE BLOCK ##### +# <pep8 compliant> + bl_info = { "name": "Motion Trail", diff --git a/development_ui_classes.py b/development_ui_classes.py index 24918c00c513e8b1951a0d8b60dd1e9ae9200cb6..65160980179c7760905414ce988b149dd52e04cf 100644 --- a/development_ui_classes.py +++ b/development_ui_classes.py @@ -16,6 +16,8 @@ # # ##### END GPL LICENSE BLOCK ##### +# <pep8 compliant> + bl_info = { "name": "UI Classes Overview", "author": "lijenstina", diff --git a/io_export_after_effects.py b/io_export_after_effects.py index 0db40e8b7daa6db9bf1b9b88b5650edda5224845..bca17f7bc2e5d33393daeb4c53a90dacf9ab3d4e 100644 --- a/io_export_after_effects.py +++ b/io_export_after_effects.py @@ -16,12 +16,14 @@ # # ##### END GPL LICENSE BLOCK ##### +# <pep8 compliant> + 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, Damien Picard (@pioverfour)", - "version": (0, 1, 3), + "author": "Bartek Skorupa", + "version": (0, 0, 69), "blender": (2, 80, 0), "location": "File > Export > Adobe After Effects (.jsx)", "warning": "", @@ -34,350 +36,101 @@ bl_info = { import bpy import os import datetime -from math import degrees +from math import degrees, floor from mathutils import Matrix, Vector, Color -def get_camera_frame_ranges(scene, start, end): - """Get frame ranges for each marker in the timeline - - 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 - - -class ObjectExport(): - """Base exporter class - - 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, 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: - prop_keys[-1][2] = True - - 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) +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 + + 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, + } + + +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) + + 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) + + 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 - 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) + for ob in obs: + if ob.type == 'CAMERA': + cameras.append(ob) - 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() + elif is_image_plane(ob): + images.append(ob) - 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 + elif is_plane(ob): + solids.append(ob) - def get_anim_script(self, include_animation): - """Get the part of the JSX script encoding animation""" - anim_script = "" + elif ob.type == 'LIGHT': + lights.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(" ", "") - 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 + nulls.append(ob) + selection = { + 'cameras': cameras, + 'images': images, + 'solids': solids, + 'lights': lights, + 'nulls': nulls, + } -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} + return selection def get_first_material(obj): @@ -401,7 +154,7 @@ def get_plane_color(obj): wrapper = node_shader_utils.PrincipledBSDFWrapper(obj.active_material) color = Color(wrapper.base_color[:3]) + wrapper.emission_color - return str(list(color)) + return '[%f,%f,%f]' % (color[0], color[1], color[2]) def is_plane(obj): @@ -444,12 +197,11 @@ 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 len(obj.material_slots) == 0: + if not len(obj.material_slots): return False mat = get_first_material(obj) @@ -460,13 +212,9 @@ def is_image_plane(obj): if img is None: return False - if len(obj.data.vertices) != 4: - return False + if len(obj.data.vertices) == 4: + return True - if not get_image_plane_matrix(obj): - return False - - return True def get_image_filepath(obj): mat = get_first_material(obj) @@ -504,7 +252,6 @@ 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 @@ -513,9 +260,6 @@ 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 @@ -532,22 +276,31 @@ def get_image_plane_matrix(obj): def convert_name(name): """Convert names of objects to avoid errors in AE""" - if not name[0].isalpha(): + 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(): name = "_" + name + ''' name = bpy.path.clean_name(name) name = name.replace("-", "_") return name -def convert_transform_matrix(matrix, width, height, aspect, ae_size=100.0): +def convert_transform_matrix(matrix, width, height, aspect, + x_rot_correction=False, ae_size=100.0): """Convert from Blender's Location, Rotation and Scale to AE's Position, Rotation/Orientation and Scale This function will be called for every object for every frame """ - # Get blender transform data for object + scale_mat = Matrix.Scale(width, 4) + + # Get blender transform data for ob 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() @@ -561,19 +314,20 @@ def convert_transform_matrix(matrix, width, height, aspect, ae_size=100.0): z = (b_loc.y * 100.0) * ae_size / 100.0 # Convert rotations to match AE's orientation. - # 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. + # If not x_rot_correction + rx = degrees(b_rot.x) # AE's X orientation = blender's X rotation if 'ZYX' euler. ry = -degrees(b_rot.y) # AE's Y orientation = -blender's Y rotation if 'ZYX' euler rz = -degrees(b_rot.z) # AE's Z orientation = -blender's Z rotation if 'ZYX' euler - + if x_rot_correction: + # 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 @@ -633,6 +387,7 @@ def convert_transform_matrix(matrix, width, height, aspect, ae_size=100.0): # 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 @@ -656,95 +411,639 @@ def convert_lens(camera, width, height, aspect): # return matrix -def write_jsx_file(context, file, selection, include_animation, ae_size): +def write_jsx_file(file, data, selection, include_animation, + include_active_cam, include_selected_cams, + include_selected_objects, include_cam_bundles, + include_image_planes, ae_size): """jsx script for AE creation""" - print("\n---------------------------\n" - "- Export to After Effects -\n" - "---------------------------") - - # 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 + 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 + } + + # 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 if include_animation: - frame_end = scene.frame_end + 1 + end = data['end'] + 1 else: - 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) + end = data['start'] + 1 + for frame in range(data['start'], end): + print("working on frame: " + str(frame)) + data['scn'].frame_set(frame) # Get time for this loop - time = (frame - scene.frame_start) / fps + js_data['times'] += '%f,' % ((frame - data['start']) / data['fps']) - for obj_type in selection.values(): - for obj in obj_type: - obj.get_keyframe(context, width, height, aspect, time, ae_size) + # 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. + # + # + # # ---- write JSX file - 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') - - # 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(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 = open(file, 'w') + + # 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") + # 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(): + jsx_file.write( + 'var %s = newComp.layers.addSolid(%s,"%s",%i,%i,%f);\n' % ( + name_ae, + obj['color'], + name_ae, + obj['width'], + obj['height'], + 1.0)) + jsx_file.write( + '%s.threeDLayer = true;\n' % name_ae) + jsx_file.write( + '%s.source.name = "%s";\n' % (name_ae, name_ae)) + # Set values of properties, add keyframes only where needed + for prop in ("position", "orientation", "scale"): + if include_animation and obj[prop + '_anim']: + jsx_file.write( + '%s.property("%s").setValuesAtTimes([%s],[%s]);\n' + % (name_ae, prop, js_data['times'], obj[prop])) + else: + jsx_file.write( + '%s.property("%s").setValue(%s);\n' + % (name_ae, prop, obj[prop + '_static'])) + jsx_file.write('\n') + jsx_file.write('\n') + + # Create images + jsx_file.write('// ************** IMAGES **************\n\n') + for name_ae, obj in js_data['images'].items(): + jsx_file.write( + 'var newFootage = app.project.importFile(new ImportOptions(File("%s")))\n' + % (obj['filepath'])) + jsx_file.write( + 'var %s = newComp.layers.add(newFootage);\n' % (name_ae)) + jsx_file.write( + '%s.threeDLayer = true;\n' % name_ae) + jsx_file.write( + '%s.source.name = "%s";\n' % (name_ae, name_ae)) + # Set values of properties, add keyframes only where needed + for prop in ("position", "orientation", "scale"): + if include_animation and obj[prop + '_anim']: + jsx_file.write( + '%s.property("%s").setValuesAtTimes([%s],[%s]);\n' + % (name_ae, prop, js_data['times'], obj[prop])) + else: + jsx_file.write( + '%s.property("%s").setValue(%s);\n' + % (name_ae, prop, obj[prop + '_static'])) + jsx_file.write('\n') + jsx_file.write('\n') + + # Create lights + jsx_file.write('// ************** LIGHTS **************\n\n') + for name_ae, obj in js_data['lights'].items(): + jsx_file.write( + 'var %s = newComp.layers.addLight("%s", [0.0, 0.0]);\n' + % (name_ae, name_ae)) + jsx_file.write( + '%s.autoOrient = AutoOrientType.NO_AUTO_ORIENT;\n' + % name_ae) + # Set values of properties, add keyframes only where needed + props = ["position", "orientation", "intensity", "Color"] + if obj['type'] == 'SPOT': + props.extend(("Cone Angle", "Cone Feather")) + for prop in props: + if include_animation and obj[prop + '_anim']: + jsx_file.write( + '%s.property("%s").setValuesAtTimes([%s],[%s]);\n' + % (name_ae, prop, js_data['times'], obj[prop])) + else: + jsx_file.write( + '%s.property("%s").setValue(%s);\n' + % (name_ae, prop, obj[prop + '_static'])) + jsx_file.write('\n') + jsx_file.write('\n') + + # Create cameras + jsx_file.write('// ************** CAMERAS **************\n\n') + for name_ae, obj in js_data['cameras'].items(): + # More than one camera can be selected jsx_file.write( - 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") - - jsx_file.write('var footageFolder = app.project.items.addFolder(compName + "_layers")\n\n\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') - - # 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') - - # Restore current frame of animation in blender to state before export - scene.frame_set(frame_current) + 'var %s = newComp.layers.addCamera("%s",[0,0]);\n' + % (name_ae, name_ae)) + jsx_file.write( + '%s.autoOrient = AutoOrientType.NO_AUTO_ORIENT;\n' + % name_ae) + + # Set values of properties, add keyframes only where needed + for prop in ("position", "orientation", "zoom"): + if include_animation and obj[prop + '_anim']: + jsx_file.write( + '%s.property("%s").setValuesAtTimes([%s],[%s]);\n' + % (name_ae, prop, js_data['times'], obj[prop])) + else: + jsx_file.write( + '%s.property("%s").setValue(%s);\n' + % (name_ae, prop, obj[prop + '_static'])) + jsx_file.write('\n') + jsx_file.write('\n') + + # Exit import if no comp name given + jsx_file.write('\n}else{alert ("Exit Import Blender animation data \\nNo Comp 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) ########################################## @@ -794,11 +1093,6 @@ 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", @@ -817,24 +1111,19 @@ class ExportJsx(bpy.types.Operator, ExportHelper): box = layout.box() box.label(text='Include Cameras and Objects') - 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.prop(self, 'include_active_cam') + box.prop(self, 'include_selected_cams') + box.prop(self, 'include_selected_objects') + box.prop(self, 'include_image_planes') box = layout.box() box.prop(self, 'include_animation') - box = layout.box() box.label(text='Transform') box.prop(self, 'ae_size') + box = layout.box() + box.label(text='Include Tracking Data:') + box.prop(self, 'include_cam_bundles') +# box.prop(self, 'include_ob_bundles') @classmethod def poll(cls, context): @@ -843,14 +1132,12 @@ class ExportJsx(bpy.types.Operator, ExportHelper): return selected or camera def execute(self, context): - 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) + data = get_comp_data(context) + selection = get_selected(context) + write_jsx_file(self.filepath, data, selection, self.include_animation, + self.include_active_cam, self.include_selected_cams, + self.include_selected_objects, self.include_cam_bundles, + self.include_image_planes, self.ae_size) print("\nExport to After Effects Completed") return {'FINISHED'} diff --git a/io_scene_3ds/__init__.py b/io_scene_3ds/__init__.py index 3912ac147d814bd87de343dce6ba44db604c5ee8..0137dd222f5ad263adf7eaac0cf110c02008724d 100644 --- a/io_scene_3ds/__init__.py +++ b/io_scene_3ds/__init__.py @@ -16,6 +16,8 @@ # # ##### END GPL LICENSE BLOCK ##### +# <pep8-80 compliant> + from bpy_extras.io_utils import ( ImportHelper, ExportHelper, @@ -32,8 +34,8 @@ import bpy bl_info = { "name": "Autodesk 3DS format", "author": "Bob Holcomb, Campbell Barton, Andreas Atteneder, Sebastian Schrand", - "version": (2, 2, 0), - "blender": (3, 0, 0), + "version": (2, 1, 0), + "blender": (2, 82, 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 2a22d742cfedc4b390c78994b53031614e361f8c..0ee332a03b6d180a13dcfdcfc273baac7e3152b1 100644 --- a/io_scene_3ds/export_3ds.py +++ b/io_scene_3ds/export_3ds.py @@ -16,6 +16,8 @@ # # ##### END GPL LICENSE BLOCK ##### +# <pep8 compliant> + # Script copyright (C) Bob Holcomb # Contributors: Campbell Barton, Bob Holcomb, Richard Lärkäng, Damien McGinnes, Mark Stijnman, Sebastian Sille @@ -645,8 +647,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) @@ -670,7 +672,6 @@ 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: @@ -705,10 +706,13 @@ 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(make_percent_subchunk(MAT_BUMP_PERCENT, b_pct)) + material_chunk.add_subchunk(strength) if wrap.roughness_texture: roughness = [wrap.roughness_texture] @@ -718,7 +722,7 @@ def make_material_chunk(material, image): material_chunk.add_subchunk(matmap) if wrap.emission_color_texture: - e_pct = wrap.emission_strength + e_pct = sum(wrap.emission_color[:]) * .25 emission = [wrap.emission_color_texture] matmap = make_material_texture_chunk(MAT_SELFIMAP, emission, e_pct) if matmap: @@ -905,8 +909,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 @@ -1167,7 +1171,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.""" @@ -1183,7 +1187,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) @@ -1231,9 +1235,7 @@ def save(operator, for ob in objects: # get derived objects - #free, derived = create_derived_objects(scene, ob) - derived_dict = bpy_extras.io_utils.create_derived_objects(depsgraph, [ob]) - derived = derived_dict.get(ob) + free, derived = create_derived_objects(scene, ob) if derived is None: continue @@ -1242,6 +1244,7 @@ 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: @@ -1285,8 +1288,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 8dfa475e84f0265811340eed4df40ab818208067..193f53e71deaaa751a6ade7d4dd5d89d3545e4c7 100644 --- a/io_scene_3ds/import_3ds.py +++ b/io_scene_3ds/import_3ds.py @@ -16,6 +16,8 @@ # # ##### END GPL LICENSE BLOCK ##### +# <pep8 compliant> + # Script copyright (C) Bob Holcomb # Contributors: Bob Holcomb, Richard L?rk?ng, Damien McGinnes, Sebastian Sille # Campbell Barton, Mario Lapin, Dominique Lorre, Andreas Atteneder @@ -63,7 +65,6 @@ 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 @@ -466,7 +467,6 @@ 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,18 +669,6 @@ 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") @@ -698,18 +686,11 @@ 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: - 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 - contextWrapper.normalmap_strength = (float(struct.unpack('<H', temp_data)[0]) / 100) - elif temp_chunk.ID == PERCENTAGE_FLOAT: - temp_data = file.read(SZ_FLOAT) - temp_chunk.bytes_read += SZ_FLOAT - contextWrapper.normalmap_strength = float(struct.unpack('f', temp_data)[0]) + temp_data = file.read(SZ_U_SHORT) + new_chunk.bytes_read += SZ_U_SHORT + contextWrapper.normalmap_strength = (float(struct.unpack('<H', temp_data)[0]) / 100) new_chunk.bytes_read += temp_chunk.bytes_read - elif new_chunk.ID == MAT_SHIN_MAP: read_texture(new_chunk, temp_chunk, "Shininess", "ROUGHNESS") diff --git a/io_scene_open_street_map.py b/io_scene_open_street_map.py index 96136bf7027c45150f03c70907f19682657c0be2..204f41a5a83b43efe4154a13c942eec6f218f277 100644 --- a/io_scene_open_street_map.py +++ b/io_scene_open_street_map.py @@ -16,6 +16,8 @@ # # ***** END GPL LICENCE BLOCK ***** +# <pep8 compliant> + 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 6986b034b2426058442e7419054a39994af1609c..6abf2d503b243927e990886b4c550fc75e0e9184 100644 --- a/io_vector/__init__.py +++ b/io_vector/__init__.py @@ -16,6 +16,8 @@ # # ##### END GPL LICENSE BLOCK ##### +# <pep8 compliant> + bl_info = { "name": "Adobe Illustrator / PDF / SVG", "author": "Howard Trickey", diff --git a/io_vector/art2polyarea.py b/io_vector/art2polyarea.py index 27624e7b4f995ee335a7141400d2d25ef911973e..b49d3a2ecff0c8e21830176872ca2d91dfd28f44 100644 --- a/io_vector/art2polyarea.py +++ b/io_vector/art2polyarea.py @@ -16,6 +16,8 @@ # # ##### END GPL LICENSE BLOCK ##### +# <pep8 compliant> + """Convert an Art object to a list of PolyArea objects. """ diff --git a/io_vector/geom.py b/io_vector/geom.py index c2f3366c6131e784e5f508914f70bc980c410612..a7eb4fedd139e65ffb754458a4ab3af47dd417bb 100644 --- a/io_vector/geom.py +++ b/io_vector/geom.py @@ -16,6 +16,8 @@ # # ##### END GPL LICENSE BLOCK ##### +# <pep8 compliant> + """Geometry classes and operations. Also, vector file representation (Art). """ diff --git a/io_vector/import_vecfile.py b/io_vector/import_vecfile.py index db8674be5d501b76ec955231a957618cfae47e01..163eefce0e300b7a721b8c736788f07a3fa1965b 100644 --- a/io_vector/import_vecfile.py +++ b/io_vector/import_vecfile.py @@ -16,6 +16,8 @@ # # ##### END GPL LICENSE BLOCK ##### +# <pep8 compliant> + """Importing a vector file into Model format. """ diff --git a/io_vector/model.py b/io_vector/model.py index a714e832ca73a37957aa668baf453a67e2bfe2ee..a3eb2aacc5d68141459a608a145e44612f4c6a2c 100644 --- a/io_vector/model.py +++ b/io_vector/model.py @@ -16,6 +16,8 @@ # # ##### END GPL LICENSE BLOCK ##### +# <pep8 compliant> + """Manipulations of Models. """ diff --git a/io_vector/offset.py b/io_vector/offset.py index df04d2f05c31ab1eb687040cfffc9dfa0203016d..4e860b67ca382b5ab855b167043efadeda52591c 100644 --- a/io_vector/offset.py +++ b/io_vector/offset.py @@ -16,6 +16,8 @@ # # ##### END GPL LICENSE BLOCK ##### +# <pep8 compliant> + """Creating offset polygons inside faces.""" __author__ = "howard.trickey@gmail.com" diff --git a/io_vector/pdf.py b/io_vector/pdf.py index 4e8f2f493bafb72805f5cdc3c00f3bc8e9d39833..e2e319917d81ab9e5ebb527b062268f3294a8ce2 100644 --- a/io_vector/pdf.py +++ b/io_vector/pdf.py @@ -16,6 +16,8 @@ # # ##### END GPL LICENSE BLOCK ##### +# <pep8 compliant> + """Functions for dealing with PDF files. """ diff --git a/io_vector/svg.py b/io_vector/svg.py index 1ef5443a5d72ffe089cff8134cc0d372863ef6d8..4a2012b006c3a6085b299c0ae549b014ac974b4f 100644 --- a/io_vector/svg.py +++ b/io_vector/svg.py @@ -16,6 +16,8 @@ # # ##### END GPL LICENSE BLOCK ##### +# <pep8 compliant> + """Reading SVG file format. """ diff --git a/io_vector/triquad.py b/io_vector/triquad.py index edced675c2b4daea5fd526d3d69b72de81a13aec..88affa8985e4b04f4f880da071ab81e7886b572b 100644 --- a/io_vector/triquad.py +++ b/io_vector/triquad.py @@ -16,6 +16,8 @@ # # ##### END GPL LICENSE BLOCK ##### +# <pep8 compliant> + from . import geom import math diff --git a/io_vector/vecfile.py b/io_vector/vecfile.py index 594255a83ae60e6d3074351fd5d0f85614f339c9..808a84e3f3f51ee42632e06d313e067f91055c6a 100644 --- a/io_vector/vecfile.py +++ b/io_vector/vecfile.py @@ -16,6 +16,8 @@ # # ##### END GPL LICENSE BLOCK ##### +# <pep8 compliant> + """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 ca603d11a77d00841c774979d6ad9facc03d98ba..f16adcd8b3fc0aa6d29e7623953f5d1c09ba31ad 100644 --- a/mesh_show_vgroup_weights.py +++ b/mesh_show_vgroup_weights.py @@ -17,6 +17,8 @@ # # ***** END GPL LICENCE BLOCK ***** +# <pep8 compliant> (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 fecbf8c55ab3ead5416c3ac16b7e29cd4ab148ab..710db64011917d3c6fd1083dd6e2d88475a09b62 100644 --- a/mocap/__init__.py +++ b/mocap/__init__.py @@ -16,6 +16,8 @@ # # ##### END GPL LICENSE BLOCK ##### +# <pep8 compliant> + bl_info = { "name": "Motion Capture Tools", "author": "Benjy Cook", diff --git a/mocap/mocap_constraints.py b/mocap/mocap_constraints.py index 6d4a34cff604baba4236f4d090149ef4f6fac5c7..3d3e4a60cf653d2899fe08479915fe2ad3b6e71b 100644 --- a/mocap/mocap_constraints.py +++ b/mocap/mocap_constraints.py @@ -16,6 +16,8 @@ # # ##### END GPL LICENSE BLOCK ##### +# <pep8 compliant> + 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 04a4261370d7814c770202ed75df902fc590f277..cc85529e9da4b4a26e4ea1b629b5883ac66aef9a 100644 --- a/mocap/mocap_tools.py +++ b/mocap/mocap_tools.py @@ -16,6 +16,8 @@ # # ##### END GPL LICENSE BLOCK ##### +# <pep8 compliant> + from math import sqrt, radians, floor, ceil import bpy import time diff --git a/mocap/retarget.py b/mocap/retarget.py index 5c963c056d01d267acaf77551977f84ebe9a665b..4fa5d2ce88de6ce642346a122935faa19a660e72 100644 --- a/mocap/retarget.py +++ b/mocap/retarget.py @@ -16,6 +16,8 @@ # # ##### END GPL LICENSE BLOCK ##### +# <pep8 compliant> + 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 1085633f9787ee7130863a4f20bef2d0e0cf3b46..0a309bc69803c3bb8353fbfcfbab03f80aff10d7 100644 --- a/object_facemap_auto/__init__.py +++ b/object_facemap_auto/__init__.py @@ -16,6 +16,8 @@ # # ##### END GPL LICENSE BLOCK ##### +# <pep8 compliant> + 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 4ed3aef0c8099eb5a7884182217764bdcaa2e492..df2434fee972337ae07bb0d06349dd3e49649a1e 100644 --- a/object_facemap_auto/auto_fmap_ops.py +++ b/object_facemap_auto/auto_fmap_ops.py @@ -16,6 +16,8 @@ # # ##### END GPL LICENSE BLOCK ##### +# <pep8 compliant> + 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 ba825c0972528570de5b7fc8145180ee8a56331c..0a02907ffcfe2a92c68f1542fdfda6c4773d417a 100644 --- a/object_facemap_auto/auto_fmap_utils.py +++ b/object_facemap_auto/auto_fmap_utils.py @@ -16,6 +16,8 @@ # # ##### END GPL LICENSE BLOCK ##### +# <pep8 compliant> + # 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 24ddf103229d8c25faaf6a98fd1c2456596c144e..6522eb42a88eaf1b571aed3f4b0edf8f645bc1b4 100644 --- a/object_facemap_auto/auto_fmap_widgets.py +++ b/object_facemap_auto/auto_fmap_widgets.py @@ -16,6 +16,8 @@ # # ##### END GPL LICENSE BLOCK ##### +# <pep8 compliant> + ''' Face map manipulator: diff --git a/object_facemap_auto/auto_fmap_widgets_xform.py b/object_facemap_auto/auto_fmap_widgets_xform.py index 5b4ae1fecb128941d407f8bcd6c09e628b2ea669..71b99cf463c125d2888a733b49a17e3464dbfc8a 100644 --- a/object_facemap_auto/auto_fmap_widgets_xform.py +++ b/object_facemap_auto/auto_fmap_widgets_xform.py @@ -16,6 +16,8 @@ # # ##### END GPL LICENSE BLOCK ##### +# <pep8 compliant> + import bpy import math diff --git a/object_fracture_crack/process/cell_calc.py b/object_fracture_crack/process/cell_calc.py index 87fba051c56a5904e498fda92a6aef910ddeb1c8..2e47eaff16ecb1494421b9b0e731d9ad998422c1 100644 --- a/object_fracture_crack/process/cell_calc.py +++ b/object_fracture_crack/process/cell_calc.py @@ -16,6 +16,8 @@ # # ##### END GPL LICENSE BLOCK ##### +# <pep8 compliant> + # Script copyright (C) Blender Foundation 2012 diff --git a/render_cube_map.py b/render_cube_map.py index 363bc02d40ca2d69d5e7427dae470bd60886fcc9..06417bab2bbf46210ea13e9be39472a5fe96aff1 100644 --- a/render_cube_map.py +++ b/render_cube_map.py @@ -16,6 +16,8 @@ # # ======================= END GPL LICENSE BLOCK ======================== +# <pep8 compliant> + # ######################################## # Render Cube Map # diff --git a/render_to_print.py b/render_to_print.py index fdc54cd657fed96164464a1484bbfb64137add17..52d2fd5277d72bed36bbdd1cf475ae2502763e79 100644 --- a/render_to_print.py +++ b/render_to_print.py @@ -16,6 +16,8 @@ # # ##### END GPL LICENSE BLOCK ##### +# <pep8 compliant> + bl_info = { "name": "Render to Print", "author": "Marco Crippa <thekrypt77@tiscali.it>, Dealga McArdle, zebus3d", diff --git a/system_keyboard_svg.py b/system_keyboard_svg.py index db14916559dce927e7231205f9b870ac2b12ba15..bf6ab61c03ba907f7348fdbf225b409bb07d972a 100644 --- a/system_keyboard_svg.py +++ b/system_keyboard_svg.py @@ -16,6 +16,8 @@ # # ##### END GPL LICENSE BLOCK ##### +# <pep8 compliant> + # 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)