[Bf-blender-cvs] [2ec025d7be3] master: VSE: Add operators to add and remove fades

Richard Antalik noreply at git.blender.org
Sat Sep 14 03:00:51 CEST 2019


Commit: 2ec025d7be3ca9f3fd2fe0ac844ef70c69e7af55
Author: Richard Antalik
Date:   Fri Sep 13 17:24:02 2019 -0700
Branches: master
https://developer.blender.org/rB2ec025d7be3ca9f3fd2fe0ac844ef70c69e7af55

VSE: Add operators to add and remove fades

Fades add:
Adds or updates a fade animation for either visual or audio strips.
    Fade options:
    - In, Out, In and Out create a fade animation of the given duration from
    the start of the sequence, to the end of the sequence, or on boths sides
    - From playhead: the fade animation goes from the start of sequences under the playhead to the playhead
    - To playhead: the fade animation goes from the playhead to the end of sequences under the playhead
    By default, the duration of the fade is 1 second.

Fades clear:
Removes fade animation from selected sequences.
Removes opacity or volume animation on selected sequences and resets the
property to a value of 1.0. Works on all types of sequences.

Author: gdquest

Reviewed By: ISS

Differential Revision: https://developer.blender.org/D5166

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

M	release/scripts/startup/bl_operators/sequencer.py
M	release/scripts/startup/bl_ui/space_sequencer.py

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

diff --git a/release/scripts/startup/bl_operators/sequencer.py b/release/scripts/startup/bl_operators/sequencer.py
index b1152157cf8..1da96834a5a 100644
--- a/release/scripts/startup/bl_operators/sequencer.py
+++ b/release/scripts/startup/bl_operators/sequencer.py
@@ -20,6 +20,8 @@
 
 import bpy
 from bpy.types import Operator
+from mathutils import Vector
+from math import floor
 
 from bpy.props import IntProperty
 
@@ -136,8 +138,240 @@ class SequencerDeinterlaceSelectedMovies(Operator):
         return {'FINISHED'}
 
 
+class SequencerFadesClear(Operator):
+    """Removes fade animation from selected sequences.
+    Removes opacity or volume animation on selected sequences and resets the
+    property to a value of 1.0. Works on all types of sequences.
+    """
+    bl_idname = "sequencer.fades_clear"
+    bl_label = "Clear Fades"
+    bl_description = "Removes fade animation from selected sequences."
+    bl_options = {'REGISTER', 'UNDO'}
+
+    @classmethod
+    def poll(cls, context):
+        return context.scene and context.scene.sequence_editor and context.scene.sequence_editor.active_strip
+
+    def execute(self, context):
+        fcurves = context.scene.animation_data.action.fcurves
+
+        for sequence in context.selected_sequences:
+            animated_property = 'volume' if hasattr(sequence, 'volume') else 'blend_alpha'
+            for curve in fcurves:
+                if not curve.data_path.endswith(animated_property):
+                    continue
+                # Ensure the fcurve corresponds to the selected sequence
+                if sequence == eval("bpy.context.scene." + curve.data_path.replace('.' + animated_property, '')):
+                    fcurves.remove(curve)
+            setattr(sequence, animated_property, 1.0)
+        return {'FINISHED'}
+
+
+class SequencerFadesAdd(Operator):
+    """Adds or updates a fade animation for either visual or audio strips.
+    Fade options:
+    - In, Out, In and Out create a fade animation of the given duration from
+    the start of the sequence, to the end of the sequence, or on boths sides
+    - From playhead: the fade animation goes from the start of sequences under the playhead to the playhead
+    - To playhead: the fade animation goes from the playhead to the end of sequences under the playhead
+    By default, the duration of the fade is 1 second.
+    """
+    bl_idname = "sequencer.fades_add"
+    bl_label = "Add Fades"
+    bl_description = "Adds or updates a fade animation for either visual or audio strips."
+    bl_options = {'REGISTER', 'UNDO'}
+
+    duration_seconds: bpy.props.FloatProperty(
+        name="Fade Duration",
+        description="Duration of the fade in seconds",
+        default=1.0,
+        min=0.01)
+    type: bpy.props.EnumProperty(
+        items=[('IN_OUT', 'Fade In And Out', 'Fade selected strips in and out'),
+               ('IN', 'Fade In', 'Fade in selected strips'),
+               ('OUT', 'Fade Out', 'Fade out selected strips'),
+               ('CURSOR_FROM', 'From Playhead', 'Fade from the time cursor to the end of overlapping sequences'),
+               ('CURSOR_TO', 'To Playhead', 'Fade from the start of sequences under the time cursor to the current frame')],
+        name="Fade type",
+        description="Fade in, out, both in and out, to, or from the playhead. Default is both in and out.",
+        default='IN_OUT')
+
+    @classmethod
+    def poll(cls, context):
+        # Can't use context.selected_sequences as it can have an impact on performances
+        return context.scene and context.scene.sequence_editor and context.scene.sequence_editor.active_strip
+
+    def execute(self, context):
+        # We must create a scene action first if there's none
+        scene = context.scene
+        if not scene.animation_data:
+            scene.animation_data_create()
+        if not scene.animation_data.action:
+            action = bpy.data.actions.new(scene.name + "Action")
+            scene.animation_data.action = action
+
+        sequences = context.selected_sequences
+        if self.type in ['CURSOR_TO', 'CURSOR_FROM']:
+            sequences = [s for s in sequences
+                         if s.frame_final_start < context.scene.frame_current < s.frame_final_end]
+
+        max_duration = min(sequences, key=lambda s: s.frame_final_duration).frame_final_duration
+        max_duration = floor(max_duration / 2.0) if self.type == 'IN_OUT' else max_duration
+
+        faded_sequences = []
+        for sequence in sequences:
+            duration = self.calculate_fade_duration(context, sequence)
+            duration = min(duration, max_duration)
+            if not self.is_long_enough(sequence, duration):
+                continue
+
+            animated_property = 'volume' if hasattr(sequence, 'volume') else 'blend_alpha'
+            fade_fcurve = self.fade_find_or_create_fcurve(context, sequence, animated_property)
+            fades = self.calculate_fades(sequence, fade_fcurve, animated_property, duration)
+            self.fade_animation_clear(context, fade_fcurve, fades)
+            self.fade_animation_create(fade_fcurve, fades)
+            faded_sequences.append(sequence)
+
+        sequence_string = "sequence" if len(faded_sequences) == 1 else "sequences"
+        self.report({"INFO"}, "Added fade animation to {} {}.".format(len(faded_sequences), sequence_string))
+        return {"FINISHED"}
+
+    def calculate_fade_duration(self, context, sequence):
+        frame_current = context.scene.frame_current
+        duration = 0.0
+        if self.type == 'CURSOR_TO':
+            duration = abs(frame_current - sequence.frame_final_start)
+        elif self.type == 'CURSOR_FROM':
+            duration = abs(sequence.frame_final_end - frame_current)
+        else:
+            duration = calculate_duration_frames(context, self.duration_seconds)
+        return max(1, duration)
+
+    def is_long_enough(self, sequence, duration=0.0):
+        minimum_duration = (duration * 2
+                            if self.type == 'IN_OUT' else
+                            duration)
+        return sequence.frame_final_duration >= minimum_duration
+
+    def calculate_fades(self, sequence, fade_fcurve, animated_property, duration):
+        """
+        Returns a list of Fade objects
+        """
+        fades = []
+        if self.type in ['IN', 'IN_OUT', 'CURSOR_TO']:
+            fade = Fade(sequence, fade_fcurve, 'IN', animated_property, duration)
+            fades.append(fade)
+        if self.type in ['OUT', 'IN_OUT', 'CURSOR_FROM']:
+            fade = Fade(sequence, fade_fcurve, 'OUT', animated_property, duration)
+            fades.append(fade)
+        return fades
+
+    def fade_find_or_create_fcurve(self, context, sequence, animated_property):
+        """
+        Iterates over all the fcurves until it finds an fcurve with a data path
+        that corresponds to the sequence.
+        Returns the matching FCurve or creates a new one if the function can't find a match.
+        """
+        fade_fcurve = None
+        fcurves = context.scene.animation_data.action.fcurves
+        searched_data_path = sequence.path_from_id(animated_property)
+        for fcurve in fcurves:
+            if fcurve.data_path == searched_data_path:
+                fade_fcurve = fcurve
+                break
+        if not fade_fcurve:
+            fade_fcurve = fcurves.new(data_path=searched_data_path)
+        return fade_fcurve
+
+    def fade_animation_clear(self, context, fade_fcurve, fades):
+        """
+        Removes existing keyframes in the fades' time range, in fast mode, without
+        updating the fcurve
+        """
+        keyframe_points = fade_fcurve.keyframe_points
+        for fade in fades:
+            for keyframe in keyframe_points:
+                # The keyframe points list doesn't seem to always update as the
+                # operator re-runs Leading to trying to remove nonexistent keyframes
+                try:
+                    if fade.start.x < keyframe.co[0] <= fade.end.x:
+                            keyframe_points.remove(keyframe, fast=True)
+                except Exception:
+                    pass
+            fade_fcurve.update()
+
+    def fade_animation_create(self, fade_fcurve, fades):
+        """
+        Inserts keyframes in the fade_fcurve in fast mode using the Fade objects.
+        Updates the fcurve after having inserted all keyframes to finish the animation.
+        """
+        keyframe_points = fade_fcurve.keyframe_points
+        for fade in fades:
+            for point in (fade.start, fade.end):
+                keyframe_points.insert(frame=point.x, value=point.y, options={'FAST'})
+        fade_fcurve.update()
+        # The graph editor and the audio waveforms only redraw upon "moving" a keyframe
+        keyframe_points[-1].co = keyframe_points[-1].co
+
+
+class Fade:
+    """
+    Data structure to represent fades
+    """
+    type = ''
+    animated_property = ''
+    duration = -1
+    max_value = 1.0
+    start, end = Vector((0, 0)), Vector((0, 0))
+
+    def __init__(self, sequence, fade_fcurve, type, animated_property, duration):
+        self.type = type
+        self.animated_property = animated_property
+        self.duration = duration
+        self.max_value = self.calculate_max_value(sequence, fade_fcurve)
+
+        if type == 'IN':
+            self.start = Vector((sequence.frame_final_start, 0.0))
+            self.end = Vector((sequence.frame_final_start + self.duration, self.max_value))
+        elif type == 'OUT':
+            self.start = Vector((sequence.frame_final_end - self.duration, self.max_value))
+            self.end = Vector((sequence.frame_final_end, 0.0))
+
+    def calculate_max_value(self, sequence, fade_fcurve):
+        """
+        Returns the maximum Y coordinate the fade animation should use for a given sequence
+        Uses either the sequence's value for the animated property, or the next keyframe after the fade
+        """
+        max_value = 0.0
+
+        if not fade_fcurve.keyframe_points:
+            max_value = getattr(sequence, self.animated_property, 1.0)
+        else:
+            if self.type == 'IN':


@@ Diff output truncated at 10240 characters. @@



More information about the Bf-blender-cvs mailing list