Merge branch 'blender-v4.0-release'
[blender-addons.git] / power_sequencer / operators / concatenate_strips.py
blobff7af8a48fa84f2c9d2ffc8ccde9111bd20ae775
1 # SPDX-FileCopyrightText: 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
3 # SPDX-License-Identifier: GPL-3.0-or-later
5 import bpy
6 from operator import attrgetter
8 from .utils.global_settings import SequenceTypes
9 from .utils.functions import (
10 find_sequences_after,
11 get_mouse_frame_and_channel,
12 ripple_move,
14 from .utils.doc import doc_name, doc_idname, doc_brief, doc_description
17 def find_sequences_before(context, strip):
18 """
19 Returns a list of sequences that are before the strip in the current context
20 """
21 return [s for s in context.sequences if s.frame_final_end <= strip.frame_final_start]
24 class POWER_SEQUENCER_OT_concatenate_strips(bpy.types.Operator):
25 """
26 *brief* Remove space between strips
28 Concatenates selected strips in a channel, i.e. removes the gap between them. If a single
29 strip is selected, either the next strip in the channel will be concatenated, or all
30 strips in the channel will be concatenated depending on which shortcut is used.
31 """
33 doc = {
34 "name": doc_name(__qualname__),
35 "demo": "https://i.imgur.com/YyEL8YP.gif",
36 "description": doc_description(__doc__),
37 "shortcuts": [
39 {"type": "C", "value": "PRESS"},
40 {"concatenate_all": False, "is_towards_left": True},
41 ("Concatenate and select the next strip in the channel"),
44 {"type": "C", "value": "PRESS", "shift": True},
45 {"concatenate_all": True, "is_towards_left": True},
46 "Concatenate all strips in selected channels",
49 {"type": "C", "value": "PRESS", "alt": True},
50 {"concatenate_all": False, "is_towards_left": False},
51 ("Concatenate and select the previous strip in the channel towards the right"),
54 {"type": "C", "value": "PRESS", "shift": True, "alt": True},
55 {"concatenate_all": True, "is_towards_left": False},
56 "Shift Alt C; Concatenate all strips in channel towards the right",
59 "keymap": "Sequencer",
61 bl_idname = doc_idname(__qualname__)
62 bl_label = doc["name"]
63 bl_description = doc_brief(doc["description"])
64 bl_options = {"REGISTER", "UNDO"}
66 concatenate_all: bpy.props.BoolProperty(
67 name="Concatenate all strips in channel",
68 description=("If only one strip selected, concatenate" " the entire channel"),
69 default=False,
71 is_towards_left: bpy.props.BoolProperty(
72 name="To Left",
73 description="Concatenate strips moving them back in time (default) or forward in time",
74 default=True,
76 do_ripple: bpy.props.BoolProperty(
77 name="Ripple Edit",
78 description="Ripple the time offset to strips after the concatenated one",
79 default=False,
82 @classmethod
83 def poll(cls, context):
84 return context.sequences
86 def invoke(self, context, event):
87 if not context.selected_sequences:
88 frame, channel = get_mouse_frame_and_channel(context, event)
89 bpy.ops.power_sequencer.select_closest_to_mouse(frame=frame, channel=channel)
90 return self.execute(context)
92 def execute(self, context):
93 selection = context.selected_sequences
94 channels = {s.channel for s in selection}
96 is_one_strip_per_channel = len(selection) == len(channels)
97 if is_one_strip_per_channel:
98 for s in selection:
99 candidates = (
100 find_sequences_before(context, s)
101 if not self.is_towards_left
102 else find_sequences_after(context, s)
104 to_concatenate = [
105 strip
106 for strip in candidates
107 if strip.channel == s.channel
108 and not strip.lock
109 and strip.type in SequenceTypes.CUTABLE
111 if not to_concatenate:
112 continue
113 self.concatenate(context, s, to_concatenate)
115 else:
116 for channel in channels:
117 to_concatenate = [s for s in selection if s.channel == channel]
118 strip_target = (
119 min(to_concatenate, key=lambda s: s.frame_final_start)
120 if self.is_towards_left
121 else max(to_concatenate, key=lambda s: s.frame_final_start)
123 to_concatenate.remove(strip_target)
124 self.concatenate(context, strip_target, to_concatenate, force_all=True)
126 return {"FINISHED"}
128 def concatenate(self, context, strip_target, sequences, force_all=False):
129 to_concatenate = sorted(sequences, key=attrgetter("frame_final_start"))
130 to_concatenate = (
131 list(reversed(to_concatenate)) if not self.is_towards_left else to_concatenate
133 to_concatenate = (
134 [to_concatenate[0]] if not (self.concatenate_all or force_all) else to_concatenate
137 attribute_target = "frame_final_end" if self.is_towards_left else "frame_final_start"
138 attribute_concat = "frame_final_start" if self.is_towards_left else "frame_final_end"
139 concatenate_start = getattr(strip_target, attribute_target)
140 last_gap = 0
141 for s in to_concatenate:
142 if isinstance(s, bpy.types.EffectSequence):
143 concatenate_start = (
144 s.frame_final_end - last_gap
145 if self.is_towards_left
146 else s.frame_final_start - last_gap
148 continue
149 concat_strip_frame = getattr(s, attribute_concat)
150 gap = concat_strip_frame - concatenate_start
151 if self.do_ripple and self.is_towards_left:
152 ripple_move(context, [s], -gap)
153 else:
154 s.frame_start -= gap
155 concatenate_start = s.frame_final_end if self.is_towards_left else s.frame_final_start
156 last_gap = gap
158 if not (self.concatenate_all or force_all):
159 strip_target.select = False
160 to_concatenate[0].select = True