1 # SPDX-FileCopyrightText: 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
3 # SPDX-License-Identifier: GPL-3.0-or-later
6 from operator
import attrgetter
8 from .utils
.doc
import doc_name
, doc_idname
, doc_brief
, doc_description
9 from .utils
.functions
import slice_selection
12 class POWER_SEQUENCER_OT_gap_remove(bpy
.types
.Operator
):
14 Remove gaps, starting from the first frame, with the ability to ignore locked strips
18 "name": doc_name(__qualname__
),
20 "description": doc_description(__doc__
),
22 "keymap": "Sequencer",
24 bl_idname
= doc_idname(__qualname__
)
25 bl_label
= doc
["name"]
26 bl_description
= doc_brief(doc
["description"])
27 bl_options
= {"REGISTER", "UNDO"}
29 ignore_locked
: bpy
.props
.BoolProperty(
30 name
="Ignore Locked Strips",
31 description
="Remove gaps without moving locked strips",
34 all
: bpy
.props
.BoolProperty(
36 description
="Remove all gaps starting from the time cursor",
39 frame
: bpy
.props
.IntProperty(
41 description
="Frame to remove gaps from, defaults at the time cursor",
44 move_time_cursor
: bpy
.props
.BoolProperty(
45 name
="Move Time Cursor",
46 description
="Move the time cursor when closing the gap",
51 def poll(cls
, context
):
52 return context
.sequences
54 def execute(self
, context
):
55 frame
= self
.frame
if self
.frame
>= 0 else context
.scene
.frame_current
57 [s
for s
in context
.sequences
if not s
.lock
]
59 else context
.sequences
62 s
for s
in sequences
if s
.frame_final_start
>= frame
or s
.frame_final_end
> frame
64 sequence_blocks
= slice_selection(context
, sequences
)
65 if not sequence_blocks
:
68 gap_frame
= self
.find_gap_frame(context
, frame
, sequence_blocks
[0])
72 first_block_start
= min(
73 sequence_blocks
[0], key
=attrgetter("frame_final_start")
76 sequence_blocks
[1:] if first_block_start
<= gap_frame
else sequence_blocks
79 self
.gaps_remove(context
, blocks_after_gap
, gap_frame
)
80 if self
.move_time_cursor
:
81 context
.scene
.frame_current
= gap_frame
84 def find_gap_frame(self
, context
, frame
, sorted_sequences
):
86 Finds and returns the frame at which the gap starts.
87 Takes a list sequences sorted by frame_final_start.
89 strips_start
= min(sorted_sequences
, key
=attrgetter("frame_final_start")).frame_final_start
90 strips_end
= max(sorted_sequences
, key
=attrgetter("frame_final_end")).frame_final_end
93 if strips_start
> frame
:
94 strips_before_frame_start
= [s
for s
in context
.sequences
if s
.frame_final_end
<= frame
]
96 if strips_before_frame_start
:
98 strips_before_frame_start
, key
=attrgetter("frame_final_end")
100 gap_frame
= frame_target
if frame_target
< strips_start
else frame
102 gap_frame
= strips_end
105 def gaps_remove(self
, context
, sequence_blocks
, gap_frame_start
):
107 Recursively removes gaps between blocks of sequences.
110 gap_frame
= gap_frame_start
111 for block
in sequence_blocks
:
112 gap_size
= block
[0].frame_final_start
- gap_frame
118 s
.frame_start
-= gap_size
119 except AttributeError:
122 self
.move_markers(context
, gap_frame
, gap_size
)
125 gap_frame
= block
[-1].frame_final_end
127 def move_markers(self
, context
, gap_frame
, gap_size
):
128 markers
= (m
for m
in context
.scene
.timeline_markers
if m
.frame
> gap_frame
)
130 m
.frame
-= min({gap_size
, m
.frame
- gap_frame
})