From cd176b2617bd3ede969c3aa218ee54a79fc69f27 Mon Sep 17 00:00:00 2001 From: Nathan Lovato Date: Sat, 23 Jan 2021 18:17:35 -0600 Subject: Update power sequencer to v2.0.1 Changelog: https://github.com/GDQuest/blender-power-sequencer/blob/master/CHANGELOG.md#power-sequencer-201 Commit range: https://github.com/GDQuest/blender-power-sequencer/compare/1.5.0...2.0.1 --- power_sequencer/operators/__init__.py | 12 +- power_sequencer/operators/channel_offset.py | 95 ++++++++----- .../operators/cut_strips_under_cursor.py | 13 +- power_sequencer/operators/delete_direct.py | 13 +- .../operators/expand_to_surrounding_cuts.py | 35 ++--- power_sequencer/operators/fade_add.py | 49 ++----- power_sequencer/operators/fade_clear.py | 10 +- power_sequencer/operators/gap_remove.py | 16 +-- power_sequencer/operators/make_hold_frame.py | 8 +- .../operators/scene_create_from_selection.py | 7 +- power_sequencer/operators/scene_merge_from.py | 4 +- .../operators/select_all_left_or_right.py | 2 +- power_sequencer/operators/snap.py | 6 +- power_sequencer/operators/snap_selection.py | 14 +- .../operators/trim_left_or_right_handles.py | 11 +- power_sequencer/operators/utils/functions.py | 149 +++++++++------------ power_sequencer/operators/value_offset.py | 94 +++++++++++++ 17 files changed, 270 insertions(+), 268 deletions(-) create mode 100644 power_sequencer/operators/value_offset.py (limited to 'power_sequencer/operators') diff --git a/power_sequencer/operators/__init__.py b/power_sequencer/operators/__init__.py index 064ba9b3..406c8635 100755 --- a/power_sequencer/operators/__init__.py +++ b/power_sequencer/operators/__init__.py @@ -22,17 +22,13 @@ def get_operator_classes(): """Returns the list of operators in the add-on""" this_file = os.path.dirname(__file__) module_files = [ - f - for f in os.listdir(this_file) - if f.endswith(".py") and not f.startswith("__init__") + f for f in os.listdir(this_file) if f.endswith(".py") and not f.startswith("__init__") ] module_paths = ["." + os.path.splitext(f)[0] for f in module_files] classes = [] for path in module_paths: module = importlib.import_module(path, package="power_sequencer.operators") - operator_names = [ - entry for entry in dir(module) if entry.startswith("POWER_SEQUENCER_OT") - ] + operator_names = [entry for entry in dir(module) if entry.startswith("POWER_SEQUENCER_OT")] classes.extend([getattr(module, name) for name in operator_names]) return classes @@ -41,9 +37,7 @@ doc = { "sequencer.refresh_all": { "name": "Refresh All", "description": "", - "shortcuts": [ - ({"type": "R", "value": "PRESS", "shift": True}, {}, "Refresh All") - ], + "shortcuts": [({"type": "R", "value": "PRESS", "shift": True}, {}, "Refresh All")], "demo": "", "keymap": "Sequencer", } diff --git a/power_sequencer/operators/channel_offset.py b/power_sequencer/operators/channel_offset.py index 35da1b47..48305c4b 100644 --- a/power_sequencer/operators/channel_offset.py +++ b/power_sequencer/operators/channel_offset.py @@ -14,17 +14,12 @@ # You should have received a copy of the GNU General Public License along with Power Sequencer. If # not, see . # -import bpy from operator import attrgetter -from .utils.doc import doc_name, doc_idname, doc_brief, doc_description -from .utils.functions import ( - slice_selection, - get_frame_range, - get_channel_range, - trim_strips, - find_strips_in_range, -) +import bpy + +from .utils.doc import doc_brief, doc_description, doc_idname, doc_name +from .utils.functions import find_strips_in_range, move_selection, trim_strips class POWER_SEQUENCER_OT_channel_offset(bpy.types.Operator): @@ -39,7 +34,7 @@ class POWER_SEQUENCER_OT_channel_offset(bpy.types.Operator): "shortcuts": [ ( {"type": "UP_ARROW", "value": "PRESS", "alt": True}, - {"direction": "up"}, + {"direction": "up", "trim_target_channel": False}, "Move to Open Channel Above", ), ( @@ -49,7 +44,7 @@ class POWER_SEQUENCER_OT_channel_offset(bpy.types.Operator): ), ( {"type": "DOWN_ARROW", "value": "PRESS", "alt": True}, - {"direction": "down"}, + {"direction": "down", "trim_target_channel": False}, "Move to Open Channel Below", ), ( @@ -79,35 +74,71 @@ class POWER_SEQUENCER_OT_channel_offset(bpy.types.Operator): description="Trim strips to make space in the target channel", default=False, ) + keep_selection_offset: bpy.props.BoolProperty( + name="Keep selection offset", + description="The selected strips preserve their relative positions", + default=True, + ) @classmethod def poll(cls, context): return context.selected_sequences def execute(self, context): + + max_channel = 32 + min_channel = 1 + + if self.direction == "up": + channel_offset = +1 + limit_channel = max_channel + comparison_function = min + + if self.direction == "down": + channel_offset = -1 + limit_channel = min_channel + comparison_function = max + selection = [s for s in context.selected_sequences if not s.lock] + if not selection: return {"FINISHED"} - selection_blocks = slice_selection(context, selection) - for block in selection_blocks: - sequences = sorted(block, key=attrgetter("channel", "frame_final_start")) - frame_start, frame_end = get_frame_range(sequences) - channel_start, channel_end = get_channel_range(sequences) - - if self.trim_target_channel: - to_delete, to_trim = find_strips_in_range(frame_start, frame_end, context.sequences) - channel_trim = ( - channel_end + 1 if self.direction == "up" else max(1, channel_start - 1) - ) - to_trim = [s for s in to_trim if s.channel == channel_trim] - to_delete = [s for s in to_delete if s.channel == channel_trim] - trim_strips(context, frame_start, frame_end, to_trim, to_delete) - - if self.direction == "up": - for s in reversed(sequences): - s.channel += 1 - elif self.direction == "down": - for s in sequences: - s.channel = max(1, s.channel - 1) + sequences = sorted(selection, key=attrgetter("channel", "frame_final_start")) + if self.direction == "up": + sequences = [s for s in reversed(sequences)] + + head = sequences[0] + if not self.keep_selection_offset or ( + head.channel != limit_channel and self.keep_selection_offset + ): + for s in sequences: + if self.trim_target_channel: + channel_trim = s.channel + channel_offset + strips_in_trim_channel = [ + sequence + for sequence in context.sequences + if (sequence.channel == channel_trim) + ] + if strips_in_trim_channel: + to_delete, to_trim = find_strips_in_range( + s.frame_final_start, s.frame_final_end, strips_in_trim_channel + ) + trim_strips( + context, s.frame_final_start, s.frame_final_end, to_trim, to_delete + ) + + if not self.keep_selection_offset: + s.channel = comparison_function(limit_channel, s.channel + channel_offset) + if s.channel == limit_channel: + move_selection(context, [s], 0, 0) + + if self.keep_selection_offset: + start_frame = head.frame_final_start + x_difference = 0 + while not head.channel == limit_channel: + move_selection(context, sequences, -x_difference, channel_offset) + x_difference = head.frame_final_start - start_frame + if x_difference == 0: + break return {"FINISHED"} diff --git a/power_sequencer/operators/cut_strips_under_cursor.py b/power_sequencer/operators/cut_strips_under_cursor.py index d7e1141a..3b54a17b 100644 --- a/power_sequencer/operators/cut_strips_under_cursor.py +++ b/power_sequencer/operators/cut_strips_under_cursor.py @@ -30,9 +30,7 @@ class POWER_SEQUENCER_OT_split_strips_under_cursor(bpy.types.Operator): "name": doc_name(__qualname__), "demo": "https://i.imgur.com/ZyEd0jD.gif", "description": doc_description(__doc__), - "shortcuts": [ - ({"type": "K", "value": "PRESS"}, {}, "Cut All Strips Under Cursor") - ], + "shortcuts": [({"type": "K", "value": "PRESS"}, {}, "Cut All Strips Under Cursor")], "keymap": "Sequencer", } bl_idname = doc_idname(__qualname__) @@ -66,10 +64,5 @@ class POWER_SEQUENCER_OT_split_strips_under_cursor(bpy.types.Operator): deselect = False if deselect: bpy.ops.sequencer.select_all(action="DESELECT") - ( - context.selected_sequences - or bpy.ops.power_sequencer.select_strips_under_cursor() - ) - return bpy.ops.sequencer.split( - frame=context.scene.frame_current, side=self.side - ) + (context.selected_sequences or bpy.ops.power_sequencer.select_strips_under_cursor()) + return bpy.ops.sequencer.split(frame=context.scene.frame_current, side=self.side) diff --git a/power_sequencer/operators/delete_direct.py b/power_sequencer/operators/delete_direct.py index b39ae12f..4c6e4d2d 100644 --- a/power_sequencer/operators/delete_direct.py +++ b/power_sequencer/operators/delete_direct.py @@ -50,9 +50,7 @@ class POWER_SEQUENCER_OT_delete_direct(bpy.types.Operator): bl_description = doc_brief(doc["description"]) bl_options = {"REGISTER", "UNDO"} - is_removing_transitions: bpy.props.BoolProperty( - name="Remove Transitions", default=False - ) + is_removing_transitions: bpy.props.BoolProperty(name="Remove Transitions", default=False) @classmethod def poll(cls, context): @@ -61,17 +59,12 @@ class POWER_SEQUENCER_OT_delete_direct(bpy.types.Operator): def invoke(self, context, event): frame, channel = get_mouse_frame_and_channel(context, event) if not context.selected_sequences: - bpy.ops.power_sequencer.select_closest_to_mouse( - frame=frame, channel=channel - ) + bpy.ops.power_sequencer.select_closest_to_mouse(frame=frame, channel=channel) return self.execute(context) def execute(self, context): selection = context.selected_sequences - if ( - self.is_removing_transitions - and bpy.ops.power_sequencer.transitions_remove.poll() - ): + if self.is_removing_transitions and bpy.ops.power_sequencer.transitions_remove.poll(): bpy.ops.power_sequencer.transitions_remove() bpy.ops.sequencer.delete() diff --git a/power_sequencer/operators/expand_to_surrounding_cuts.py b/power_sequencer/operators/expand_to_surrounding_cuts.py index 1435b23c..53de8bf7 100644 --- a/power_sequencer/operators/expand_to_surrounding_cuts.py +++ b/power_sequencer/operators/expand_to_surrounding_cuts.py @@ -33,11 +33,7 @@ class POWER_SEQUENCER_OT_expand_to_surrounding_cuts(bpy.types.Operator): "demo": "", "description": doc_description(__doc__), "shortcuts": [ - ( - {"type": "E", "value": "PRESS", "ctrl": True}, - {}, - "Expand to Surrounding Cuts", - ) + ({"type": "E", "value": "PRESS", "ctrl": True}, {}, "Expand to Surrounding Cuts",) ], "keymap": "Sequencer", } @@ -68,37 +64,24 @@ class POWER_SEQUENCER_OT_expand_to_surrounding_cuts(bpy.types.Operator): sequences_frame_start = min( sequences, key=lambda s: s.frame_final_start ).frame_final_start - sequences_frame_end = max( - sequences, key=lambda s: s.frame_final_end - ).frame_final_end + sequences_frame_end = max(sequences, key=lambda s: s.frame_final_end).frame_final_end frame_left, frame_right = find_closest_cuts( context, sequences_frame_start, sequences_frame_end ) - if ( - sequences_frame_start == frame_left - and sequences_frame_end == frame_right - ): + if sequences_frame_start == frame_left and sequences_frame_end == frame_right: continue - to_extend_left = [ - s for s in sequences if s.frame_final_start == sequences_frame_start - ] - to_extend_right = [ - s for s in sequences if s.frame_final_end == sequences_frame_end - ] + to_extend_left = [s for s in sequences if s.frame_final_start == sequences_frame_start] + to_extend_right = [s for s in sequences if s.frame_final_end == sequences_frame_end] for s in to_extend_left: s.frame_final_start = ( - frame_left - if frame_left < sequences_frame_start - else sequences_frame_start + frame_left if frame_left < sequences_frame_start else sequences_frame_start ) for s in to_extend_right: s.frame_final_end = ( - frame_right - if frame_right > sequences_frame_end - else sequences_frame_end + frame_right if frame_right > sequences_frame_end else sequences_frame_end ) return {"FINISHED"} @@ -110,8 +93,6 @@ def find_closest_cuts(context, frame_min, frame_max): ).frame_final_end frame_right = min( context.sequences, - key=lambda s: s.frame_final_start - if s.frame_final_start >= frame_max - else 1000000, + key=lambda s: s.frame_final_start if s.frame_final_start >= frame_max else 1000000, ).frame_final_start return frame_left, frame_right diff --git a/power_sequencer/operators/fade_add.py b/power_sequencer/operators/fade_add.py index 1f4d0bfa..dd75c864 100644 --- a/power_sequencer/operators/fade_add.py +++ b/power_sequencer/operators/fade_add.py @@ -51,10 +51,7 @@ class POWER_SEQUENCER_OT_fade_add(bpy.types.Operator): 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, + name="Fade Duration", description="Duration of the fade in seconds", default=1.0, min=0.01, ) type: bpy.props.EnumProperty( items=[ @@ -98,12 +95,8 @@ class POWER_SEQUENCER_OT_fade_add(bpy.types.Operator): 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 - ) + 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: @@ -113,15 +106,9 @@ class POWER_SEQUENCER_OT_fade_add(bpy.types.Operator): if not self.is_long_enough(sequence, duration): continue - animated_property = ( - "volume" if hasattr(sequence, "volume") else "blend_alpha" - ) - fade_fcurve = fade_find_or_create_fcurve( - context, sequence, animated_property - ) - fades = self.calculate_fades( - sequence, fade_fcurve, animated_property, duration - ) + animated_property = "volume" if hasattr(sequence, "volume") else "blend_alpha" + fade_fcurve = fade_find_or_create_fcurve(context, sequence, animated_property) + fades = self.calculate_fades(sequence, fade_fcurve, animated_property, duration) fade_animation_clear(context, fade_fcurve, fades) fade_animation_create(fade_fcurve, fades) faded_sequences.append(sequence) @@ -129,9 +116,7 @@ class POWER_SEQUENCER_OT_fade_add(bpy.types.Operator): sequence_string = "sequence" if len(faded_sequences) == 1 else "sequences" self.report( {"INFO"}, - "Added fade animation to {} {}.".format( - len(faded_sequences), sequence_string - ), + "Added fade animation to {} {}.".format(len(faded_sequences), sequence_string), ) return {"FINISHED"} @@ -232,13 +217,9 @@ class Fade: if type == "IN": self.start = Vector((sequence.frame_final_start, 0.0)) - self.end = Vector( - (sequence.frame_final_start + self.duration, self.max_value) - ) + 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.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): @@ -253,15 +234,11 @@ class Fade: else: if self.type == "IN": fade_end = sequence.frame_final_start + self.duration - keyframes = ( - k for k in fade_fcurve.keyframe_points if k.co[0] >= fade_end - ) + keyframes = (k for k in fade_fcurve.keyframe_points if k.co[0] >= fade_end) if self.type == "OUT": fade_start = sequence.frame_final_end - self.duration keyframes = ( - k - for k in reversed(fade_fcurve.keyframe_points) - if k.co[0] <= fade_start + k for k in reversed(fade_fcurve.keyframe_points) if k.co[0] <= fade_start ) try: max_value = next(keyframes).co[1] @@ -275,6 +252,4 @@ class Fade: def calculate_duration_frames(context, duration_seconds): - return round( - duration_seconds * context.scene.render.fps / context.scene.render.fps_base - ) + return round(duration_seconds * context.scene.render.fps / context.scene.render.fps_base) diff --git a/power_sequencer/operators/fade_clear.py b/power_sequencer/operators/fade_clear.py index 06b320b0..02516633 100644 --- a/power_sequencer/operators/fade_clear.py +++ b/power_sequencer/operators/fade_clear.py @@ -32,11 +32,7 @@ class POWER_SEQUENCER_OT_fade_clear(bpy.types.Operator): "demo": "", "description": doc_description(__doc__), "shortcuts": [ - ( - {"type": "F", "value": "PRESS", "alt": True, "ctrl": True}, - {}, - "Clear Fades", - ) + ({"type": "F", "value": "PRESS", "alt": True, "ctrl": True}, {}, "Clear Fades",) ], "keymap": "Sequencer", } @@ -53,9 +49,7 @@ class POWER_SEQUENCER_OT_fade_clear(bpy.types.Operator): fcurves = context.scene.animation_data.action.fcurves for sequence in context.selected_sequences: - animated_property = ( - "volume" if hasattr(sequence, "volume") else "blend_alpha" - ) + animated_property = "volume" if hasattr(sequence, "volume") else "blend_alpha" data_path = sequence.path_from_id() + "." + animated_property fcurve_map = { curve.data_path: curve diff --git a/power_sequencer/operators/gap_remove.py b/power_sequencer/operators/gap_remove.py index 4a736051..825bfc0d 100644 --- a/power_sequencer/operators/gap_remove.py +++ b/power_sequencer/operators/gap_remove.py @@ -71,9 +71,7 @@ class POWER_SEQUENCER_OT_gap_remove(bpy.types.Operator): else context.sequences ) sequences = [ - s - for s in sequences - if s.frame_final_start >= frame or s.frame_final_end > frame + s for s in sequences if s.frame_final_start >= frame or s.frame_final_end > frame ] sequence_blocks = slice_selection(context, sequences) if not sequence_blocks: @@ -100,18 +98,12 @@ class POWER_SEQUENCER_OT_gap_remove(bpy.types.Operator): Finds and returns the frame at which the gap starts. Takes a list sequences sorted by frame_final_start. """ - strips_start = min( - sorted_sequences, key=attrgetter("frame_final_start") - ).frame_final_start - strips_end = max( - sorted_sequences, key=attrgetter("frame_final_end") - ).frame_final_end + strips_start = min(sorted_sequences, key=attrgetter("frame_final_start")).frame_final_start + strips_end = max(sorted_sequences, key=attrgetter("frame_final_end")).frame_final_end gap_frame = -1 if strips_start > frame: - strips_before_frame_start = [ - s for s in context.sequences if s.frame_final_end <= frame - ] + strips_before_frame_start = [s for s in context.sequences if s.frame_final_end <= frame] frame_target = 0 if strips_before_frame_start: frame_target = max( diff --git a/power_sequencer/operators/make_hold_frame.py b/power_sequencer/operators/make_hold_frame.py index c6893d04..22c2eecf 100644 --- a/power_sequencer/operators/make_hold_frame.py +++ b/power_sequencer/operators/make_hold_frame.py @@ -77,9 +77,7 @@ class POWER_SEQUENCER_OT_make_hold_frame(bpy.types.Operator): try: next_strip_start = next( s - for s in sorted( - context.sequences, key=operator.attrgetter("frame_final_start") - ) + for s in sorted(context.sequences, key=operator.attrgetter("frame_final_start")) if s.frame_final_start > active.frame_final_end ).frame_final_start offset = next_strip_start - active.frame_final_end @@ -90,9 +88,7 @@ class POWER_SEQUENCER_OT_make_hold_frame(bpy.types.Operator): source_blend_type = active.blend_type sequencer.split(frame=scene.frame_current, type="SOFT", side="RIGHT") transform.seq_slide(value=(offset, 0)) - sequencer.split( - frame=scene.frame_current + offset + 1, type="SOFT", side="LEFT" - ) + sequencer.split(frame=scene.frame_current + offset + 1, type="SOFT", side="LEFT") transform.seq_slide(value=(-offset, 0)) sequencer.meta_make() diff --git a/power_sequencer/operators/scene_create_from_selection.py b/power_sequencer/operators/scene_create_from_selection.py index b615dae9..cd774d0d 100644 --- a/power_sequencer/operators/scene_create_from_selection.py +++ b/power_sequencer/operators/scene_create_from_selection.py @@ -67,7 +67,6 @@ class POWER_SEQUENCER_OT_scene_create_from_selection(bpy.types.Operator): context.window.scene.name = context.selected_sequences[0].name new_scene_name = context.window.scene.name - ###after full copy also unselected strips are in the sequencer... Delete those strips bpy.ops.sequencer.select_all(action="INVERT") bpy.ops.power_sequencer.delete_direct() @@ -85,8 +84,10 @@ class POWER_SEQUENCER_OT_scene_create_from_selection(bpy.types.Operator): bpy.ops.power_sequencer.delete_direct() bpy.ops.sequencer.scene_strip_add( - frame_start=selection_start_frame, channel=selection_start_channel, scene=new_scene_name + frame_start=selection_start_frame, + channel=selection_start_channel, + scene=new_scene_name, ) scene_strip = context.selected_sequences[0] - # scene_strip.use_sequence = True + # scene_strip.use_sequence = True return {"FINISHED"} diff --git a/power_sequencer/operators/scene_merge_from.py b/power_sequencer/operators/scene_merge_from.py index 16b2710d..5c3f40b0 100644 --- a/power_sequencer/operators/scene_merge_from.py +++ b/power_sequencer/operators/scene_merge_from.py @@ -73,9 +73,7 @@ class POWER_SEQUENCER_OT_merge_from_scene_strip(bpy.types.Operator): context.window.scene = strip_scene bpy.ops.scene.delete() context.window.scene = start_scene - self.report( - type={"WARNING"}, message="Merged scenes lose all their animation data." - ) + self.report(type={"WARNING"}, message="Merged scenes lose all their animation data.") return {"FINISHED"} diff --git a/power_sequencer/operators/select_all_left_or_right.py b/power_sequencer/operators/select_all_left_or_right.py index b2e08bb8..01e35306 100644 --- a/power_sequencer/operators/select_all_left_or_right.py +++ b/power_sequencer/operators/select_all_left_or_right.py @@ -62,4 +62,4 @@ class POWER_SEQUENCER_OT_select_all_left_or_right(bpy.types.Operator): return context.sequences def execute(self, context): - return bpy.ops.sequencer.select_side_of_frame("INVOKE_DEFAULT", side=self.side) + return bpy.ops.sequencer.select("INVOKE_DEFAULT", left_right=self.side) diff --git a/power_sequencer/operators/snap.py b/power_sequencer/operators/snap.py index 42b029d1..13d5e66e 100644 --- a/power_sequencer/operators/snap.py +++ b/power_sequencer/operators/snap.py @@ -32,11 +32,7 @@ class POWER_SEQUENCER_OT_snap(bpy.types.Operator): "demo": "", "description": doc_description(__doc__), "shortcuts": [ - ( - {"type": "S", "value": "PRESS", "shift": True}, - {}, - "Snap sequences to cursor", - ) + ({"type": "S", "value": "PRESS", "shift": True}, {}, "Snap sequences to cursor",) ], "keymap": "Sequencer", } diff --git a/power_sequencer/operators/snap_selection.py b/power_sequencer/operators/snap_selection.py index 5900579b..eb2851b5 100644 --- a/power_sequencer/operators/snap_selection.py +++ b/power_sequencer/operators/snap_selection.py @@ -15,7 +15,7 @@ # not, see . # import bpy -from .utils.functions import get_sequences_under_cursor, apply_time_offset +from .utils.functions import get_sequences_under_cursor, move_selection from .utils.doc import doc_name, doc_idname, doc_brief, doc_description @@ -32,11 +32,7 @@ class POWER_SEQUENCER_OT_snap_selection(bpy.types.Operator): "demo": "", "description": doc_description(__doc__), "shortcuts": [ - ( - {"type": "S", "value": "PRESS", "alt": True}, - {}, - "Snap selection to cursor", - ) + ({"type": "S", "value": "PRESS", "alt": True}, {}, "Snap selection to cursor",) ], "keymap": "Sequencer", } @@ -55,9 +51,7 @@ class POWER_SEQUENCER_OT_snap_selection(bpy.types.Operator): if context.selected_sequences else get_sequences_under_cursor(context) ) - frame_first = min( - sequences, key=lambda s: s.frame_final_start - ).frame_final_start + frame_first = min(sequences, key=lambda s: s.frame_final_start).frame_final_start time_offset = context.scene.frame_current - frame_first - apply_time_offset(context, sequences, time_offset) + move_selection(context, sequences, time_offset) return {"FINISHED"} diff --git a/power_sequencer/operators/trim_left_or_right_handles.py b/power_sequencer/operators/trim_left_or_right_handles.py index b93bee53..bbbb8bd5 100644 --- a/power_sequencer/operators/trim_left_or_right_handles.py +++ b/power_sequencer/operators/trim_left_or_right_handles.py @@ -78,16 +78,9 @@ class POWER_SEQUENCER_OT_trim_left_or_right_handles(bpy.types.Operator): frame_current = context.scene.frame_current # Only select sequences under the time cursor - sequences = ( - context.selected_sequences - if context.selected_sequences - else context.sequences - ) + sequences = context.selected_sequences if context.selected_sequences else context.sequences for s in sequences: - s.select = ( - s.frame_final_start <= frame_current - and s.frame_final_end >= frame_current - ) + s.select = s.frame_final_start <= frame_current and s.frame_final_end >= frame_current sequences = [s for s in sequences if s.select] if not sequences: return {"FINISHED"} diff --git a/power_sequencer/operators/utils/functions.py b/power_sequencer/operators/utils/functions.py index 3462b767..5b33067c 100644 --- a/power_sequencer/operators/utils/functions.py +++ b/power_sequencer/operators/utils/functions.py @@ -21,6 +21,9 @@ import bpy from .global_settings import SequenceTypes +max_channel = 32 +min_channel = 1 + def calculate_distance(x1, y1, x2, y2): return sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) @@ -34,10 +37,8 @@ def find_linked(context, sequences, selected_sequences): """ Takes a list of sequences and returns a list of all the sequences and effects that are linked in time - Args: - - sequences: a list of sequences - + - sequences: a list of sequences Returns a list of all the linked sequences, but not the sequences passed to the function """ start, end = get_frame_range(sequences, selected_sequences) @@ -99,7 +100,7 @@ def find_sequences_after(context, sequence): """ Finds the strips following the sequences passed to the function Args: - - Sequences, the sequences to check + - Sequences, the sequences to check Returns all the strips after the sequence in the current context """ return [s for s in context.sequences if s.frame_final_start > sequence.frame_final_start] @@ -133,12 +134,10 @@ def find_snap_candidate(context, frame=0): def find_strips_mouse(context, frame, channel, select_linked=False): """ Finds a list of sequences to select based on the frame and channel the mouse cursor is at - Args: - - frame: the frame the mouse or cursor is on - - channel: the channel the mouse is hovering - - select_linked: find and append the sequences linked in time if True - + - frame: the frame the mouse or cursor is on + - channel: the channel the mouse is hovering + - select_linked: find and append the sequences linked in time if True Returns the sequence(s) under the mouse cursor as a list Returns an empty list if nothing found """ @@ -201,10 +200,9 @@ def get_mouse_frame_and_channel(context, event): def is_in_range(context, sequence, start, end): """ Checks if a single sequence's start or end is in the range - Args: - - sequence: the sequence to check for - - start, end: the start and end frames + - sequence: the sequence to check for + - start, end: the start and end frames Returns True if the sequence is within the range, False otherwise """ s_start = sequence.frame_final_start @@ -225,47 +223,32 @@ def set_preview_range(context, start, end): scene.frame_preview_end = end -def slice_selection(context, sequences): +def slice_selection(context, sequences, range_block=0): """ Takes a list of sequences and breaks it down into multiple lists of connected sequences - Returns a list of lists of sequences, each list corresponding to a block of sequences that are connected in time and sorted by frame_final_start """ - # Find when 2 sequences are not connected in time if not sequences: return [] - break_ids = [0] + # Indicates the index number of the lists from the "broken_selection" list + index = -1 + block_end = 0 + broken_selection = [] sorted_sequences = sorted(sequences, key=attrgetter("frame_final_start")) - last_sequence = sorted_sequences[0] - last_biggest_frame_end = last_sequence.frame_final_end - index = 0 + for s in sorted_sequences: - if s.frame_final_start > last_biggest_frame_end + 1: - break_ids.append(index) - last_biggest_frame_end = max(last_biggest_frame_end, s.frame_final_end) - last_sequence = s - index += 1 - - # Create lists - break_ids.append(len(sorted_sequences)) - cuts_count = len(break_ids) - 1 - broken_selection = [] - index = 0 - while index < cuts_count: - temp_list = [] - index_range = range(break_ids[index], break_ids[index + 1] - 1) - if len(index_range) == 0: - temp_list.append(sorted_sequences[break_ids[index]]) - else: - for counter in range(break_ids[index], break_ids[index + 1]): - temp_list.append(sorted_sequences[counter]) - if temp_list: - broken_selection.append(temp_list) - index += 1 + if not broken_selection or (block_end + 1 + range_block < s.frame_final_start): + broken_selection.append([s]) + block_end = s.frame_final_end + index += 1 + continue + block_end = max(block_end, s.frame_final_end) + broken_selection[index].append(s) + return broken_selection @@ -278,8 +261,10 @@ def trim_strips(context, frame_start, frame_end, to_trim, to_delete=[]): trim_end = max(frame_start, frame_end) to_trim = [s for s in to_trim if s.type in SequenceTypes.CUTABLE] + initial_selection = context.selected_sequences for s in to_trim: + strips_in_target_channel = [] # Cut strip longer than the trim range in three is_strip_longer_than_trim_range = ( s.frame_final_start < trim_start and s.frame_final_end > trim_end @@ -290,6 +275,13 @@ def trim_strips(context, frame_start, frame_end, to_trim, to_delete=[]): bpy.ops.sequencer.split(frame=trim_start, type="SOFT", side="RIGHT") bpy.ops.sequencer.split(frame=trim_end, type="SOFT", side="LEFT") to_delete.append(context.selected_sequences[0]) + + for c in context.sequences: + if c.channel == s.channel: + strips_in_target_channel.append(c) + + if s in initial_selection: + initial_selection.append(strips_in_target_channel[0]) continue # Resize strips that overlap the trim range @@ -298,20 +290,12 @@ def trim_strips(context, frame_start, frame_end, to_trim, to_delete=[]): elif s.frame_final_end > trim_start and s.frame_final_start < trim_start: s.frame_final_end = trim_start - delete_strips(to_delete) - return {"FINISHED"} - - -def delete_strips(to_delete): - """ - Deletes the list of sequences `to_delete` - """ - if not to_delete: - return - bpy.ops.sequencer.select_all(action="DESELECT") for s in to_delete: + bpy.context.sequences.remove(s) + + for s in initial_selection: s.select = True - bpy.ops.sequencer.delete() + return {"FINISHED"} def find_closest_surrounding_cuts(context, frame): @@ -378,30 +362,7 @@ def ripple_move(context, sequences, duration_frames, delete=False): else: to_ripple = set(to_ripple + sequences) - # Use the built-in seq_slide operator to move strips, for best performances - initial_selection = context.selected_sequences - bpy.ops.sequencer.select_all(action="DESELECT") - for s in to_ripple: - s.select = True - bpy.ops.transform.seq_slide(value=(duration_frames, 0)) - bpy.ops.sequencer.select_all(action="DESELECT") - for s in initial_selection: - s.select = True - - -def apply_time_offset(context, sequences=[], offset=0): - """Offsets a list of sequences in time using bpy.ops.transform.seq_slide. Mutates and restores the - user's selection. Use this function to ensure maximum performances and avoid having to figure - out the logic to move strips in the right order. - """ - selection = context.selected_sequences - bpy.ops.sequencer.select_all(action="DESELECT") - for s in sequences: - s.select = True - bpy.ops.transform.seq_slide(value=(offset, 0)) - bpy.ops.sequencer.select_all(action="DESELECT") - for s in selection: - s.select = True + move_selection(context, to_ripple, duration_frames, 0) def find_strips_in_range(frame_start, frame_end, sequences, find_overlapping=True): @@ -409,18 +370,18 @@ def find_strips_in_range(frame_start, frame_end, sequences, find_overlapping=Tru Returns a tuple of two lists: (strips_inside_range, strips_overlapping_range) strips_inside_range are strips entirely contained in the frame range. strips_overlapping_range are strips that only overlap the frame range. - Args: - - frame_start, the start of the frame range - - frame_end, the end of the frame range - - sequences (optional): only work with these sequences. - If it doesn't receive any, the function works with all the sequences in the current context - - find_overlapping (optional): find and return a list of strips that overlap the + - frame_start, the start of the frame range + - frame_end, the end of the frame range + - sequences (optional): only work with these sequences. + If it doesn't receive any, the function works with all the sequences in the current context + - find_overlapping (optional): find and return a list of strips that overlap the frame range - """ strips_inside_range = [] strips_overlapping_range = [] + if not sequences: + sequences = bpy.context.sequences for s in sequences: if ( frame_start <= s.frame_final_start <= frame_end @@ -429,8 +390,8 @@ def find_strips_in_range(frame_start, frame_end, sequences, find_overlapping=Tru strips_inside_range.append(s) elif find_overlapping: if ( - frame_start <= s.frame_final_end <= frame_end - or frame_start <= s.frame_final_start <= frame_end + frame_start < s.frame_final_end <= frame_end + or frame_start <= s.frame_final_start < frame_end ): strips_overlapping_range.append(s) @@ -438,3 +399,19 @@ def find_strips_in_range(frame_start, frame_end, sequences, find_overlapping=Tru if s.frame_final_start < frame_start and s.frame_final_end > frame_end: strips_overlapping_range.append(s) return strips_inside_range, strips_overlapping_range + + +def move_selection(context, sequences, frame_offset, channel_offset=0): + """Offsets the selected `sequences` horizontally and vertically and preserves + the current selected sequences. + """ + if not sequences: + return + initial_selection = context.selected_sequences + bpy.ops.sequencer.select_all(action="DESELECT") + for s in sequences: + s.select = True + bpy.ops.transform.seq_slide(value=(frame_offset, channel_offset)) + bpy.ops.sequencer.select_all(action="DESELECT") + for s in initial_selection: + s.select = True diff --git a/power_sequencer/operators/value_offset.py b/power_sequencer/operators/value_offset.py new file mode 100644 index 00000000..debd2ef6 --- /dev/null +++ b/power_sequencer/operators/value_offset.py @@ -0,0 +1,94 @@ +# +# Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors +# +# This file is part of Power Sequencer. +# +# Power Sequencer is free software: you can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# Power Sequencer is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along with Power Sequencer. If +# not, see . +# +import bpy + +from .utils.doc import doc_brief, doc_description, doc_idname, doc_name +from .utils.functions import convert_duration_to_frames + + +class POWER_SEQUENCER_OT_value_offset(bpy.types.Operator): + """Instantly offset selected strips, either using frames or seconds. Allows to + nudge the selection quickly, using keyboard shortcuts. + """ + + doc = { + "name": doc_name(__qualname__), + "demo": "", + "description": doc_description(__doc__), + "shortcuts": [ + ( + {"type": "LEFT_ARROW", "value": "PRESS", "shift": True, "alt": True}, + {"direction": "left"}, + "Offset the selection to the left.", + ), + ( + {"type": "RIGHT_ARROW", "value": "PRESS", "shift": True, "alt": True}, + {"direction": "right"}, + "Offset the selection to the right.", + ), + ], + "keymap": "Sequencer", + } + bl_idname = doc_idname(__qualname__) + bl_label = doc["name"] + bl_description = doc_brief(doc["description"]) + bl_options = {"REGISTER", "UNDO"} + + direction: bpy.props.EnumProperty( + items=[ + ("left", "left", "Move the selection to the left"), + ("right", "right", "Move the selection to the right"), + ], + name="Direction", + description="Move the selection given frames or seconds", + default="right", + options={"HIDDEN"}, + ) + value_type: bpy.props.EnumProperty( + items=[ + ("seconds", "Seconds", "Move with the value as seconds"), + ("frames", "Frames", "Move with the value as frames"), + ], + name="Value Type", + description="Toggle between offset in frames or seconds", + default="seconds", + ) + offset: bpy.props.FloatProperty( + name="Offset", + description="Offset amount to apply", + default=1.0, + step=5, + precision=3, + ) + + @classmethod + def poll(cls, context): + return context.selected_sequences + + def invoke(self, context, event): + self.offset = abs(self.offset) + if self.direction == "left": + self.offset *= -1.0 + return self.execute(context) + + def execute(self, context): + offset_frames = ( + convert_duration_to_frames(context, self.offset) + if self.value_type == "seconds" + else self.offset + ) + return bpy.ops.transform.seq_slide(value=(offset_frames, 0)) -- cgit v1.2.3