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