Export_3ds: Improved distance cue node search
[blender-addons.git] / power_sequencer / operators / gap_remove.py
blob540ea1932de359b7ee96220f003a93db36f1aef5
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.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):
13 """
14 Remove gaps, starting from the first frame, with the ability to ignore locked strips
15 """
17 doc = {
18 "name": doc_name(__qualname__),
19 "demo": "",
20 "description": doc_description(__doc__),
21 "shortcuts": [],
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",
32 default=True,
34 all: bpy.props.BoolProperty(
35 name="Remove All",
36 description="Remove all gaps starting from the time cursor",
37 default=False,
39 frame: bpy.props.IntProperty(
40 name="Frame",
41 description="Frame to remove gaps from, defaults at the time cursor",
42 default=-1,
44 move_time_cursor: bpy.props.BoolProperty(
45 name="Move Time Cursor",
46 description="Move the time cursor when closing the gap",
47 default=False,
50 @classmethod
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
56 sequences = (
57 [s for s in context.sequences if not s.lock]
58 if self.ignore_locked
59 else context.sequences
61 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:
66 return {"FINISHED"}
68 gap_frame = self.find_gap_frame(context, frame, sequence_blocks[0])
69 if gap_frame == -1:
70 return {"FINISHED"}
72 first_block_start = min(
73 sequence_blocks[0], key=attrgetter("frame_final_start")
74 ).frame_final_start
75 blocks_after_gap = (
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
82 return {"FINISHED"}
84 def find_gap_frame(self, context, frame, sorted_sequences):
85 """
86 Finds and returns the frame at which the gap starts.
87 Takes a list sequences sorted by frame_final_start.
88 """
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
92 gap_frame = -1
93 if strips_start > frame:
94 strips_before_frame_start = [s for s in context.sequences if s.frame_final_end <= frame]
95 frame_target = 0
96 if strips_before_frame_start:
97 frame_target = max(
98 strips_before_frame_start, key=attrgetter("frame_final_end")
99 ).frame_final_end
100 gap_frame = frame_target if frame_target < strips_start else frame
101 else:
102 gap_frame = strips_end
103 return gap_frame
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
113 if gap_size < 1:
114 continue
116 for s in block:
117 try:
118 s.frame_start -= gap_size
119 except AttributeError:
120 continue
122 self.move_markers(context, gap_frame, gap_size)
123 if not self.all:
124 break
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)
129 for m in markers:
130 m.frame -= min({gap_size, m.frame - gap_frame})