Cleanup: quiet warnings with descriptions ending with a '.'
[blender-addons.git] / power_sequencer / operators / gap_remove.py
blob64070667789c34231837f6bb34db886b10d51b19
1 # SPDX-License-Identifier: GPL-3.0-or-later
2 # Copyright (C) 2016-2020 by Nathan Lovato, Daniel Oakey, Razvan Radulescu, and contributors
3 import bpy
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):
11 """
12 Remove gaps, starting from the first frame, with the ability to ignore locked strips
13 """
15 doc = {
16 "name": doc_name(__qualname__),
17 "demo": "",
18 "description": doc_description(__doc__),
19 "shortcuts": [],
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",
30 default=True,
32 all: bpy.props.BoolProperty(
33 name="Remove All",
34 description="Remove all gaps starting from the time cursor",
35 default=False,
37 frame: bpy.props.IntProperty(
38 name="Frame",
39 description="Frame to remove gaps from, defaults at the time cursor",
40 default=-1,
42 move_time_cursor: bpy.props.BoolProperty(
43 name="Move Time Cursor",
44 description="Move the time cursor when closing the gap",
45 default=False,
48 @classmethod
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
54 sequences = (
55 [s for s in context.sequences if not s.lock]
56 if self.ignore_locked
57 else context.sequences
59 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:
64 return {"FINISHED"}
66 gap_frame = self.find_gap_frame(context, frame, sequence_blocks[0])
67 if gap_frame == -1:
68 return {"FINISHED"}
70 first_block_start = min(
71 sequence_blocks[0], key=attrgetter("frame_final_start")
72 ).frame_final_start
73 blocks_after_gap = (
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
80 return {"FINISHED"}
82 def find_gap_frame(self, context, frame, sorted_sequences):
83 """
84 Finds and returns the frame at which the gap starts.
85 Takes a list sequences sorted by frame_final_start.
86 """
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
90 gap_frame = -1
91 if strips_start > frame:
92 strips_before_frame_start = [s for s in context.sequences if s.frame_final_end <= frame]
93 frame_target = 0
94 if strips_before_frame_start:
95 frame_target = max(
96 strips_before_frame_start, key=attrgetter("frame_final_end")
97 ).frame_final_end
98 gap_frame = frame_target if frame_target < strips_start else frame
99 else:
100 gap_frame = strips_end
101 return gap_frame
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
111 if gap_size < 1:
112 continue
114 for s in block:
115 try:
116 s.frame_start -= gap_size
117 except AttributeError:
118 continue
120 self.move_markers(context, gap_frame, gap_size)
121 if not self.all:
122 break
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)
127 for m in markers:
128 m.frame -= min({gap_size, m.frame - gap_frame})