1 # SPDX-License-Identifier: GPL-3.0-or-later
2 # Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
4 from operator
import attrgetter
6 from .utils
.doc
import doc_name
, doc_idname
, doc_brief
, doc_description
7 from .utils
.functions
import slice_selection
10 class POWER_SEQUENCER_OT_gap_remove(bpy
.types
.Operator
):
12 Remove gaps, starting from the first frame, with the ability to ignore locked strips
16 "name": doc_name(__qualname__
),
18 "description": doc_description(__doc__
),
20 "keymap": "Sequencer",
22 bl_idname
= doc_idname(__qualname__
)
23 bl_label
= doc
["name"]
24 bl_description
= doc_brief(doc
["description"])
25 bl_options
= {"REGISTER", "UNDO"}
27 ignore_locked
: bpy
.props
.BoolProperty(
28 name
="Ignore Locked Strips",
29 description
="Remove gaps without moving locked strips",
32 all
: bpy
.props
.BoolProperty(
34 description
="Remove all gaps starting from the time cursor",
37 frame
: bpy
.props
.IntProperty(
39 description
="Frame to remove gaps from, defaults at the time cursor",
42 move_time_cursor
: bpy
.props
.BoolProperty(
43 name
="Move Time Cursor",
44 description
="Move the time cursor when closing the gap",
49 def poll(cls
, context
):
50 return context
.sequences
52 def execute(self
, context
):
53 frame
= self
.frame
if self
.frame
>= 0 else context
.scene
.frame_current
55 [s
for s
in context
.sequences
if not s
.lock
]
57 else context
.sequences
60 s
for s
in sequences
if s
.frame_final_start
>= frame
or s
.frame_final_end
> frame
62 sequence_blocks
= slice_selection(context
, sequences
)
63 if not sequence_blocks
:
66 gap_frame
= self
.find_gap_frame(context
, frame
, sequence_blocks
[0])
70 first_block_start
= min(
71 sequence_blocks
[0], key
=attrgetter("frame_final_start")
74 sequence_blocks
[1:] if first_block_start
<= gap_frame
else sequence_blocks
77 self
.gaps_remove(context
, blocks_after_gap
, gap_frame
)
78 if self
.move_time_cursor
:
79 context
.scene
.frame_current
= gap_frame
82 def find_gap_frame(self
, context
, frame
, sorted_sequences
):
84 Finds and returns the frame at which the gap starts.
85 Takes a list sequences sorted by frame_final_start.
87 strips_start
= min(sorted_sequences
, key
=attrgetter("frame_final_start")).frame_final_start
88 strips_end
= max(sorted_sequences
, key
=attrgetter("frame_final_end")).frame_final_end
91 if strips_start
> frame
:
92 strips_before_frame_start
= [s
for s
in context
.sequences
if s
.frame_final_end
<= frame
]
94 if strips_before_frame_start
:
96 strips_before_frame_start
, key
=attrgetter("frame_final_end")
98 gap_frame
= frame_target
if frame_target
< strips_start
else frame
100 gap_frame
= strips_end
103 def gaps_remove(self
, context
, sequence_blocks
, gap_frame_start
):
105 Recursively removes gaps between blocks of sequences.
108 gap_frame
= gap_frame_start
109 for block
in sequence_blocks
:
110 gap_size
= block
[0].frame_final_start
- gap_frame
116 s
.frame_start
-= gap_size
117 except AttributeError:
120 self
.move_markers(context
, gap_frame
, gap_size
)
123 gap_frame
= block
[-1].frame_final_end
125 def move_markers(self
, context
, gap_frame
, gap_size
):
126 markers
= (m
for m
in context
.scene
.timeline_markers
if m
.frame
> gap_frame
)
128 m
.frame
-= min({gap_size
, m
.frame
- gap_frame
})