-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathgrabs.py
1057 lines (943 loc) · 46.8 KB
/
grabs.py
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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import bpy
import os
from . import vseqf
from . import timeline
from . import parenting
from . import fades
from . import vu_meter
marker_area_height = 40
marker_grab_distance = 100
class SequencePlaceHolder(object):
sequence = None
name = ''
frame_final_start = 0
frame_final_end = 0
frame_final_duration = 0
frame_start = 0
channel = 0
select = False
select_left_handle = False
select_right_handle = False
rippled = False
parent_data = None
def get_click_mode(context):
#Had to implement this because blender does not HAVE this setting when using a custom keymap. argh.
try:
click_mode = context.window_manager.keyconfigs.active.preferences.select_mouse
except:
click_mode = 'LEFT'
return click_mode
def move_sequence_position(context, sequence, offset_x, offset_y, start_channel, start_frame_start, start_frame_final_start, start_frame_final_end):
#Move a sequence by a given offset
new_start = start_frame_final_start + offset_x
new_end = start_frame_final_end + offset_x
channel = start_channel + offset_y
if channel < 1:
channel = 1
while timeline.sequencer_area_filled(new_start, new_end, channel, channel, [sequence]):
channel = channel + 1
sequence.channel = channel
sequence.frame_start = start_frame_start + offset_x
def move_sequence_left_handle(context, sequence, offset_x, start_channel, start_frame_start, start_frame_final_start, start_frame_final_end, fix_fades=False, only_fix=False):
#Move a sequence left handle and keep it behaving properly
frame_final_end = start_frame_final_end
frame_start_backup = sequence.frame_start
sequence.channel = start_channel
sequence.frame_start = frame_start_backup
new_start = start_frame_final_start + offset_x
if sequence.frame_duration == 1 and sequence.type in ['IMAGE', 'ADJUSTMENT', 'MULTICAM', 'TEXT', 'COLOR']:
#Account for odd behavior of images and unbound effect strips
if new_start >= sequence.frame_final_end:
#Prevent left handle from being moved beyond ending point of strip
new_start = sequence.frame_final_end - 1
if sequence.frame_final_start != new_start:
sequence.frame_start = new_start
if sequence.frame_final_end != frame_final_end:
sequence.frame_final_end = int(frame_final_end)
else: #Normal strip
if new_start >= frame_final_end + sequence.frame_offset_end:
#Prevent left handle from being moved beyond ending point of strip
new_start = frame_final_end - 1 + sequence.frame_offset_end
if sequence.type == 'SOUND':
#Prevent sound strip beginning from being dragged beyond start point
if new_start < sequence.frame_start:
new_start = sequence.frame_start
new_position = start_frame_start
if sequence.frame_final_start != new_start:
sequence.frame_final_start = int(new_start)
if sequence.frame_start != new_position:
sequence.frame_start = int(new_position)
if fix_fades:
fades.fix_fade_in(context, sequence, start_frame_final_start)
def move_sequence_right_handle(context, sequence, offset_x, start_channel, start_frame_final_end, fix_fades=False, only_fix=False):
#Move sequence right handle and keep it behaving properly
frame_start_backup = sequence.frame_start
sequence.channel = start_channel
sequence.frame_start = frame_start_backup
new_end = start_frame_final_end + offset_x
if new_end <= sequence.frame_final_start + 1 - sequence.frame_offset_start:
#Prevent right handle from being moved beyond start point of strip
new_end = sequence.frame_final_start + 1 - sequence.frame_offset_start
if sequence.type == 'SOUND':
if new_end > sequence.frame_start + sequence.frame_duration:
new_end = sequence.frame_start + sequence.frame_duration
sequence.frame_final_end = int(new_end)
if fix_fades:
fades.fix_fade_out(context, sequence, start_frame_final_end)
def move_sequence(context, sequence, offset_x, offset_y, select_left, select_right, start_channel, start_frame_start, start_frame_final_start, start_frame_final_end, ripple=False, fix_fades=False, only_fix=False):
if not select_left and not select_right and not only_fix: #Move strip
#check this first for efficiency since probably the most strips will be only middle-selected
move_sequence_position(context, sequence, offset_x, offset_y, start_channel, start_frame_start, start_frame_final_start, start_frame_final_end)
return
new_channel = start_channel + offset_y
if select_left or select_right and not ripple:
#make sequences that are having the handles adjusted behave better
new_start = sequence.frame_final_start
new_end = sequence.frame_final_end
while timeline.sequencer_area_filled(new_start, new_end, new_channel, new_channel, [sequence]):
new_channel = new_channel + 1
if new_channel != sequence.channel:
old_frame_start = sequence.frame_start
sequence.channel = new_channel
if sequence.frame_start != old_frame_start:
#For some reason, the first time a grab is run, the channel setting doesnt work right... double check and fix if needed
sequence.frame_start = old_frame_start
if select_left: #Move left handle
move_sequence_left_handle(context, sequence, offset_x, new_channel, start_frame_start, start_frame_final_start, start_frame_final_end, fix_fades=fix_fades, only_fix=only_fix)
if select_right: #Move right handle
move_sequence_right_handle(context, sequence, offset_x, new_channel, start_frame_final_end, fix_fades=fix_fades, only_fix=only_fix)
def find_data_by_name(name, sequences):
#finds the sequence data matching the given name.
for seq in sequences:
if seq.sequence.name == name:
return seq
return False
def copy_sequence(sequence):
data = SequencePlaceHolder()
data.sequence = sequence
data.name = sequence.name
data.frame_final_start = sequence.frame_final_start
data.frame_final_end = sequence.frame_final_end
data.frame_final_duration = sequence.frame_final_duration
data.frame_start = sequence.frame_start
data.channel = sequence.channel
data.select = sequence.select
data.select_left_handle = sequence.select_left_handle
data.select_right_handle = sequence.select_right_handle
return data
def copy_sequences(sequences):
sequences_data = []
for sequence in sequences:
sequences_data.append(copy_sequence(sequence))
return sequences_data
def grab_starting_data(sequences):
data = {}
for sequence in sequences:
data[sequence.name] = copy_sequence(sequence)
return data
def move_sequences(context, starting_data, offset_x, offset_y, grabbed_sequences, fix_fades=False, ripple=False, ripple_pop=False, move_root=True, child_edges=False):
ripple_offset = 0
right_edges = []
#Adjust grabbed strips
for sequence in grabbed_sequences:
data = starting_data[sequence.name]
move_sequence(context, sequence, offset_x, offset_y, data.select_left_handle, data.select_right_handle, data.channel, data.frame_start, data.frame_final_start, data.frame_final_end, ripple=ripple, fix_fades=fix_fades, only_fix=not move_root)
right_edges.append(sequence.frame_final_end)
if ripple:
if sequence.select_left_handle and not sequence.select_right_handle and len(grabbed_sequences) == 1:
#special ripple slide if only one sequence and left handle grabbed
frame_start = data.frame_final_start
ripple_offset = ripple_offset + frame_start - sequence.frame_final_start
sequence.frame_start = data.frame_start + ripple_offset
#offset_x = ripple_offset
else:
if ripple_pop and sequence.channel != data.channel:
#ripple 'pop'
ripple_offset = sequence.frame_final_duration
ripple_offset = 0 - ripple_offset
else:
ripple_offset = data.frame_final_end - sequence.frame_final_end
ripple_offset = 0 - ripple_offset
if vseqf.parenting():
#Adjust children of grabbed sequence
children = parenting.get_recursive(sequence, [])
root_offset_x = sequence.frame_start - data.frame_start
root_offset_y = sequence.channel - data.channel
for child in children:
if child == sequence:
continue
child_data = starting_data[child.name]
if child.parent == sequence.name:
#Primary children
if data.select_left_handle or data.select_right_handle:
#Move edges along with parent if applicable
if context.scene.vseqf.move_edges:
if child_data.frame_final_start == data.frame_final_start:
select_left = data.select_left_handle
else:
select_left = False
if child_data.frame_final_end == data.frame_final_end:
select_right = data.select_right_handle
else:
select_right = False
if select_left or select_right:
move_sequence(context, child, offset_x, offset_y, select_left, select_right, child_data.channel, child_data.frame_start, child_data.frame_final_start, child_data.frame_final_end, ripple=ripple, fix_fades=fix_fades)
if not data.select_right_handle:
child.frame_start = child_data.frame_start + ripple_offset
else:
move_sequence(context, child, root_offset_x, root_offset_y, False, False, child_data.channel, child_data.frame_start, child_data.frame_final_start, child_data.frame_final_end)
else:
#Children of children, only move them if the root sequence has moved
move_sequence(context, child, root_offset_x, root_offset_y, False, False, child_data.channel, child_data.frame_start, child_data.frame_final_start, child_data.frame_final_end)
if child_edges:
#Snap edges of children to edge of parent
if sequence.select_left_handle:
child.frame_final_start = sequence.frame_final_start
if sequence.select_right_handle:
child.frame_final_end = sequence.frame_final_end
return ripple_offset
def grab_ripple_markers(ripple_markers, ripple, ripple_offset):
for marker_data in ripple_markers:
marker, original_frame = marker_data
if ripple:
marker.frame = original_frame + ripple_offset
else:
marker.frame = original_frame
def grab_ripple_sequences(starting_data, ripple_sequences, ripple, ripple_offset):
for sequence in ripple_sequences:
data = starting_data[sequence.name]
if ripple:
data.rippled = True
new_channel = data.channel
while timeline.sequencer_area_filled(data.frame_final_start + ripple_offset, data.frame_final_end + ripple_offset, new_channel, new_channel, [sequence]):
new_channel = new_channel + 1
sequence.channel = new_channel
sequence.frame_start = data.frame_start + ripple_offset
if data.rippled and not ripple:
#fix sequence locations when ripple is disabled
new_channel = data.channel
new_start = data.frame_final_start
new_end = data.frame_final_end
while timeline.sequencer_area_filled(new_start, new_end, new_channel, new_channel, [sequence]):
new_channel = new_channel + 1
sequence.channel = new_channel
sequence.frame_start = data.frame_start
if sequence.frame_start == data.frame_start and sequence.channel == data.channel:
#unfortunately, there seems to be a limitation in blender preventing me from putting the strip back where it should be... keep trying until the grabbed strips are out of the way.
data.rippled = False
def ripple_timeline(sequencer, sequences, start_frame, ripple_amount, select_ripple=True, markers=[]):
"""Moves all given sequences starting after the frame given as 'start_frame', by moving them forward by 'ripple_amount' frames.
'select_ripple' will select all sequences that were moved."""
to_change = []
for sequence in sequences:
if not timeline.is_locked(sequencer, sequence) and sequence.frame_final_end > start_frame - ripple_amount and sequence.frame_final_start > start_frame:
to_change.append([sequence, sequence.channel, sequence.frame_start + ripple_amount, True])
for seq in to_change:
sequence = seq[0]
sequence.channel = seq[1]
if not hasattr(sequence, 'input_1'):
sequence.frame_start = seq[2]
if select_ripple:
sequence.select = True
if (sequence.frame_start != seq[2] or sequence.channel != seq[1]) and seq[3]:
seq[3] = False
to_change.append(seq)
if markers:
for marker in markers:
if marker.frame >= (start_frame - ripple_amount):
marker.frame = marker.frame + ripple_amount
def near_marker(context, frame):
if context.scene.timeline_markers:
markers = sorted(context.scene.timeline_markers, key=lambda x: abs(x.frame - frame))
marker = markers[0]
if abs(marker.frame - frame) <= marker_grab_distance:
return marker
return None
def on_sequence(frame, channel, sequence):
if frame >= sequence.frame_final_start and frame <= sequence.frame_final_end and int(channel) == sequence.channel:
return True
else:
return False
class VSEQFSelectGrabTool(bpy.types.WorkSpaceTool):
bl_space_type = 'SEQUENCE_EDITOR'
bl_context_mode = 'SEQUENCER' #Also could be PREVIEW or SEQUENCER_PREVIEW
bl_idname = "vseqf.select_grab_tool"
bl_label = "Move Plus"
bl_description = (
"Select and move a strip, with parenting and extra features."
)
bl_icon = "ops.generic.select"
bl_widget = None
bl_keymap = (
("vseqf.select_grab", {"type": 'RIGHTMOUSE', "value": 'PRESS'}, None),
("vseqf.select_grab", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
("vseqf.select_grab", {"type": 'RIGHTMOUSE', "value": 'PRESS', "ctrl": True}, None),
("vseqf.select_grab", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True}, None),
("vseqf.select_grab", {"type": 'RIGHTMOUSE', "value": 'PRESS', "alt": True}, None),
("vseqf.select_grab", {"type": 'LEFTMOUSE', "value": 'PRESS', "alt": True}, None),
("vseqf.select_grab", {"type": 'RIGHTMOUSE', "value": 'PRESS', "ctrl": True, "alt": True}, None),
("vseqf.select_grab", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True, "alt": True}, None),
)
def draw_settings(self, layout, tool):
props = tool.operator_properties("vseqf.select_grab")
layout.prop(props, "mode")
class VSEQFSelectGrab(bpy.types.Operator):
"""Replacement for the right and left-click select operator and context menu"""
bl_idname = "vseqf.select_grab"
bl_label = "Grab/Move Sequence"
mouse_start_x = 0
mouse_start_y = 0
mouse_start_region_x = 0
mouse_start_region_y = 0
selected = []
_timer = None
click_mode = None
@classmethod
def poll(cls, context):
return not context.scene.sequence_editor.selected_retiming_keys
def modal(self, context, event):
region = context.region
view = region.view2d
move_target = 10
if event.type == 'MOUSEMOVE':
delta_x = abs(self.mouse_start_x - event.mouse_x)
delta_y = abs(self.mouse_start_y - event.mouse_y)
if delta_x > move_target or delta_y > move_target:
location = view.region_to_view(self.mouse_start_region_x, self.mouse_start_region_y)
click_frame, click_channel = location
is_near_marker = near_marker(context, click_frame)
if event.mouse_region_y <= marker_area_height:
if is_near_marker:
bpy.ops.vseqf.quickmarkers_move(frame=is_near_marker.frame)
else:
bpy.ops.vseqf.grab('INVOKE_DEFAULT')
return {'FINISHED'}
else:
return {'RUNNING_MODAL'}
elif event.type in {'TIMER', 'TIMER0', 'TIMER1', 'TIMER2', 'TIMER_JOBS', 'TIMER_AUTOSAVE', 'TIMER_REPORT', 'TIMERREGION', 'NONE', 'INBETWEEN_MOUSEMOVE'}:
return {'RUNNING_MODAL'}
else:
return {'CANCELLED'}
def execute(self, context):
del context
return {'FINISHED'}
def invoke(self, context, event):
bpy.ops.ed.undo_push()
prefs = vseqf.get_prefs()
self.click_mode = get_click_mode(context)
if self.click_mode == 'RIGHT' and event.type == 'LEFTMOUSE':
#in RCS, left click on squencer, move cursor if nothing is clickd on
region = context.region
view = region.view2d
location = view.region_to_view(event.mouse_region_x, event.mouse_region_y)
click_frame, click_channel = location
clicked_sequence = None
for sequence in context.scene.sequence_editor.sequences:
if on_sequence(click_frame, click_channel, sequence):
clicked_sequence = sequence
break
if not clicked_sequence:
bpy.ops.anim.change_frame('INVOKE_DEFAULT')
return {'FINISHED'}
if event.type == 'RIGHTMOUSE':
#right click, maybe do context menus
if prefs.context_menu:
bpy.ops.vseqf.context_menu('INVOKE_DEFAULT')
else:
bpy.ops.wm.call_menu(name="SEQUENCER_MT_context_menu")
if self.click_mode == 'LEFT':
return {'FINISHED'}
self.selected = []
original_selected_sequences = timeline.current_selected(context)
for sequence in original_selected_sequences:
self.selected.append([sequence, sequence.select_left_handle, sequence.select_right_handle])
if event.mouse_region_y > marker_area_height:
if event.ctrl and event.alt:
return {'FINISHED'}
elif event.ctrl:
bpy.ops.sequencer.select('INVOKE_DEFAULT', deselect_all=False, linked_time=True)
return {'FINISHED'}
elif event.alt:
bpy.ops.sequencer.select('INVOKE_DEFAULT', deselect_all=True, linked_handle=True)
else:
bpy.ops.sequencer.select('INVOKE_DEFAULT', deselect_all=True)
selected_sequences = timeline.current_selected(context)
if not selected_sequences:
bpy.ops.sequencer.select_box('INVOKE_DEFAULT')
return {'FINISHED'}
prefs = vseqf.get_prefs()
if prefs.threepoint:
active = timeline.current_active(context)
if active and active.type == 'MOVIE':
#look for a clip editor area and set the active clip to the selected sequence if one exists that shares the same source.
newclip = None
for clip in bpy.data.movieclips:
if os.path.normpath(bpy.path.abspath(clip.filepath)) == os.path.normpath(bpy.path.abspath(active.filepath)):
newclip = clip
break
if newclip:
for area in context.screen.areas:
if area.type == 'CLIP_EDITOR':
area.spaces[0].clip = newclip
if context.scene.vseqf.select_children:
to_select = []
for sequence in selected_sequences:
to_select = parenting.get_recursive(sequence, to_select)
for seq in to_select:
seq.select = sequence.select
seq.select_left_handle = sequence.select_left_handle
seq.select_right_handle = sequence.select_right_handle
self.mouse_start_x = event.mouse_x
self.mouse_start_y = event.mouse_y
self.mouse_start_region_x = event.mouse_region_x
self.mouse_start_region_y = event.mouse_region_y
self._timer = context.window_manager.event_timer_add(time_step=0.05, window=context.window)
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
class VSEQFGrabAdd(bpy.types.Operator):
"""Modal operator designed to run in tandem with the built-in grab operator."""
bl_idname = "vseqf.grabadd"
bl_label = "Runs in tandem with the grab operator in the vse, adds functionality."
mode: bpy.props.StringProperty()
snap_cursor_to_edge = False
grabbed_sequences = []
ripple_sequences = []
target_grab_sequence = None
target_grab_variable = ''
target_grab_start = 0
target_grab_channel = 1
sequences = []
starting_data = {}
ripple_markers = []
pos_x_start = 0
pos_y_start = 0
pos_x = 0
pos_y = 0
ripple = False
ripple_pop = False
can_pop = False
alt_pressed = False
view2d = None
prefs = None
_timer = None
_handle = None
cancelled = False
start_frame = 0
start_overlay_frame = 0
snap_edge = None
snap_edge_sequence = None
secondary_snap_edge = None
secondary_snap_edge_sequence = None
timeline_start = 1
timeline_end = 1
timeline_height = 1
ripple_start = 0
ripple_left = 0
def vseqf_grab_draw(self, context):
#Callback function to draw overlays in sequencer when grab is activated
colors = context.preferences.themes[0].user_interface
text_color = list(colors.wcol_text.text_sel)+[1]
if self.mode == 'SLIP':
mode = 'Slip'
else:
if self.ripple:
mode = 'Ripple'
if self.ripple_pop:
mode = 'Ripple-Pop'
else:
mode = 'Grab'
view = context.region.view2d
for seq in self.grabbed_sequences:
sequence = seq
window_x, window_y = view.view_to_region(sequence.frame_final_start, sequence.channel)
vseqf.draw_text(window_x, window_y - 6, 12, mode, text_color)
def reset_markers(self):
for marker_data in self.ripple_markers:
marker, original_frame = marker_data
marker.frame = original_frame
def reset_sequences(self):
#used when cancelling, puts everything back to where it was at the beginning by first moving it somewhere safe, then to the true location
self.reset_markers()
timeline_length = self.timeline_end - self.timeline_start
for sequence in self.sequences:
data = self.starting_data[sequence.name]
if not hasattr(sequence, 'input_1'):
sequence.channel = data.channel + self.timeline_height
sequence.frame_start = data.frame_start + timeline_length
sequence.frame_final_start = data.frame_final_start + timeline_length
sequence.frame_final_end = data.frame_final_end + timeline_length
else:
sequence.channel = data.channel + self.timeline_height
for sequence in self.sequences:
data = self.starting_data[sequence.name]
if not hasattr(sequence, 'input_1'):
sequence.channel = data.channel
sequence.frame_start = data.frame_start
else:
sequence.channel = data.channel
def modal(self, context, event):
release_confirm = bpy.context.preferences.inputs.use_drag_immediately
reset_sequences = False
if event.type == 'TIMER':
pass
if event.type == 'E':
#doesnt seem to work unfortunately... events other than timer are not being passed
if not context.screen.is_animation_playing:
if self.snap_cursor_to_edge:
self.snap_cursor_to_edge = False
context.scene.frame_current = self.start_frame
context.scene.sequence_editor.overlay_frame = self.start_overlay_frame
else:
self.snap_cursor_to_edge = True
if self.mode != 'SLIP':
#prevent ripple and edge snap while in slip mode
if event.alt:
self.alt_pressed = True
else:
if self.alt_pressed:
reset_sequences = True
if self.can_pop:
self.alt_pressed = False
if self.ripple and self.ripple_pop:
self.ripple = False
self.ripple_pop = False
elif self.ripple:
self.ripple_pop = True
else:
self.ripple = True
else:
self.alt_pressed = False
self.ripple = not self.ripple
self.ripple_pop = False
if self.snap_cursor_to_edge:
if self.snap_edge:
if self.snap_edge == 'left':
frame = self.snap_edge_sequence.frame_final_start
else:
frame = self.snap_edge_sequence.frame_final_end - 1
context.scene.frame_current = frame
if self.secondary_snap_edge:
if self.secondary_snap_edge == 'left':
overlay_frame = self.secondary_snap_edge_sequence.frame_final_start
else:
overlay_frame = self.secondary_snap_edge_sequence.frame_final_end - 1
context.scene.sequence_editor.overlay_frame = overlay_frame - frame
offset_x = 0
pos_y = self.target_grab_sequence.channel
if self.target_grab_variable == 'frame_start':
pos_x = self.target_grab_sequence.frame_start
offset_x = pos_x - self.target_grab_start
elif self.target_grab_variable == 'frame_final_end':
pos_x = self.target_grab_sequence.frame_final_end
offset_x = pos_x - self.target_grab_start
elif self.target_grab_variable == 'frame_final_start':
pos_x = self.target_grab_sequence.frame_final_start
offset_x = pos_x - self.target_grab_start
if self.target_grab_sequence.select_left_handle or self.target_grab_sequence.select_right_handle:
offset_y = 0
else:
offset_y = pos_y - self.target_grab_channel
if reset_sequences:
self.reset_sequences()
ripple_offset = move_sequences(context, self.starting_data, offset_x, offset_y, self.grabbed_sequences, ripple_pop=self.ripple_pop, fix_fades=False, ripple=self.ripple, move_root=False)
grab_ripple_sequences(self.starting_data, self.ripple_sequences, self.ripple, ripple_offset)
if context.scene.vseqf.ripple_markers:
grab_ripple_markers(self.ripple_markers, self.ripple, ripple_offset)
if event.type in ['RIGHTMOUSE', 'ESC']:
#cancel movement and put everything back
if not self.cancelled:
self.cancelled = True
current_frame = context.scene.frame_current
self.ripple = False
self.ripple_pop = False
self.reset_sequences()
#bpy.ops.ed.undo()
if not context.screen.is_animation_playing:
context.scene.frame_current = self.start_frame
context.scene.sequence_editor.overlay_frame = self.start_overlay_frame
else:
if context.scene.frame_current != current_frame:
bpy.ops.screen.animation_play()
context.scene.frame_current = current_frame
bpy.ops.screen.animation_play()
self.remove_draw_handler()
return {'CANCELLED'}
if event.type in ['LEFTMOUSE', 'RET'] or (release_confirm and event.value == 'RELEASE'):
vu_meter.vu_meter_calculate(context.scene)
self.remove_draw_handler()
vseqf.redraw_sequencers()
if self.prefs.fades:
fix_fades = True
else:
fix_fades = False
ripple_offset = move_sequences(context, self.starting_data, offset_x, offset_y, self.grabbed_sequences, ripple_pop=self.ripple_pop, fix_fades=fix_fades, ripple=self.ripple, move_root=False)
grab_ripple_sequences(self.starting_data, self.ripple_sequences, self.ripple, ripple_offset)
if context.scene.vseqf.ripple_markers:
grab_ripple_markers(self.ripple_markers, self.ripple, ripple_offset)
if not context.screen.is_animation_playing:
if self.snap_edge:
context.scene.frame_current = self.start_frame
context.scene.sequence_editor.overlay_frame = self.start_overlay_frame
elif self.ripple and self.ripple_pop:
context.scene.frame_current = self.ripple_left
return {'FINISHED'}
return {'PASS_THROUGH'}
def remove_draw_handler(self):
bpy.types.SpaceSequenceEditor.draw_handler_remove(self._handle, 'WINDOW')
def invoke(self, context, event):
sequencer = context.scene.sequence_editor
self.start_frame = context.scene.frame_current
self.start_overlay_frame = sequencer.overlay_frame
self.cancelled = False
self.prefs = vseqf.get_prefs()
region = context.region
self.view2d = region.view2d
self.pos_x, self.pos_y = self.view2d.region_to_view(event.mouse_region_x, event.mouse_region_y)
self.pos_x_start = self.pos_x
self.pos_y_start = self.pos_y
self.sequences = []
self.grabbed_sequences = []
self.ripple_sequences = []
sequences = timeline.current_sequences(context)
self.timeline_start = timeline.find_sequences_start(sequences)
self.timeline_end = timeline.find_sequences_end(sequences)
self.ripple_start = self.timeline_end
self.timeline_height = timeline.find_timeline_height(sequences)
is_parenting = vseqf.parenting()
to_move = []
selected_sequences = timeline.current_selected(context)
for sequence in selected_sequences:
if sequence.select_right_handle:
ripple_point = sequence.frame_final_end
else:
ripple_point = sequence.frame_final_start
if ripple_point < self.ripple_start and not hasattr(sequence, 'input_1') and not timeline.is_locked(sequencer, sequence):
self.ripple_start = ripple_point
self.ripple_left = ripple_point
if is_parenting:
to_move = parenting.get_recursive(sequence, to_move)
else:
to_move.append(sequence)
#store markers to ripple
self.ripple_markers = []
for marker in context.scene.timeline_markers:
if marker.frame >= self.ripple_start:
self.ripple_markers.append([marker, marker.frame])
self.starting_data = grab_starting_data(sequences)
#generate grabbed sequences and ripple sequences lists
for sequence in sequences:
if not timeline.is_locked(sequencer, sequence) and not hasattr(sequence, 'input_1'):
self.sequences.append(sequence)
if sequence.select:
self.grabbed_sequences.append(sequence)
else:
if is_parenting and sequence in to_move:
pass
elif sequence.frame_final_start >= self.ripple_start:
self.ripple_sequences.append(sequence)
self._timer = context.window_manager.event_timer_add(time_step=0.01, window=context.window)
self.ripple_sequences.sort(key=lambda x: x.frame_final_start)
self.grabbed_sequences.sort(key=lambda x: x.frame_final_start)
grabbed_left = False
grabbed_right = False
grabbed_center = False
for sequence in self.grabbed_sequences:
if sequence.select and not (sequence.select_left_handle or sequence.select_right_handle):
grabbed_center = sequence
else:
if sequence.select_left_handle:
grabbed_left = sequence
if sequence.select_right_handle:
grabbed_right = sequence
if grabbed_center:
self.target_grab_variable = 'frame_start'
self.target_grab_sequence = grabbed_center
self.target_grab_start = grabbed_center.frame_start
self.target_grab_channel = grabbed_center.channel
else:
if grabbed_right:
self.target_grab_variable = 'frame_final_end'
self.target_grab_sequence = grabbed_right
self.target_grab_start = grabbed_right.frame_final_end
self.target_grab_channel = grabbed_right.channel
if grabbed_left:
self.target_grab_variable = 'frame_final_start'
self.target_grab_sequence = grabbed_left
self.target_grab_start = grabbed_left.frame_final_start
self.target_grab_channel = grabbed_left.channel
#Determine the snap edges
if not context.screen.is_animation_playing:
self.snap_cursor_to_edge = context.scene.vseqf.snap_cursor_to_edge
else:
self.snap_cursor_to_edge = False
self.snap_edge = None
self.snap_edge_sequence = None
self.secondary_snap_edge = None
self.secondary_snap_edge_sequence = None
#Determine number of selected edges in grabbed sequences:
selected_edges = []
for sequence in self.grabbed_sequences:
if sequence.select_left_handle:
selected_edges.append([sequence, 'left'])
if sequence.select_right_handle:
selected_edges.append([sequence, 'right'])
if len(selected_edges) == 1:
#only one edge is grabbed, snap to it
self.snap_edge_sequence = selected_edges[0][0]
self.snap_edge = selected_edges[0][1]
elif len(selected_edges) == 2:
#two sequence edges are selected
#if one sequence is active, make that primary
active = timeline.current_active(context)
if selected_edges[0][0] == active and selected_edges[1][0] != active:
self.snap_edge = selected_edges[0][1]
self.snap_edge_sequence = selected_edges[0][0]
self.secondary_snap_edge = selected_edges[1][1]
self.secondary_snap_edge_sequence = selected_edges[1][0]
elif selected_edges[1][0] == active and selected_edges[0][0] != active:
self.snap_edge = selected_edges[1][1]
self.snap_edge_sequence = selected_edges[1][0]
self.secondary_snap_edge = selected_edges[0][1]
self.secondary_snap_edge_sequence = selected_edges[0][0]
else:
#neither sequence is active, or both are the same sequence, make rightmost primary, leftmost secondary
if selected_edges[0][1] == 'left':
first_frame = selected_edges[0][0].frame_final_start
else:
first_frame = selected_edges[0][0].frame_final_end
if selected_edges[1][1] == 'left':
second_frame = selected_edges[1][0].frame_final_start
else:
second_frame = selected_edges[1][0].frame_final_end
if first_frame > second_frame:
self.snap_edge = selected_edges[0][1]
self.snap_edge_sequence = selected_edges[0][0]
self.secondary_snap_edge = selected_edges[1][1]
self.secondary_snap_edge_sequence = selected_edges[1][0]
else:
self.snap_edge = selected_edges[1][1]
self.snap_edge_sequence = selected_edges[1][0]
self.secondary_snap_edge = selected_edges[0][1]
self.secondary_snap_edge_sequence = selected_edges[0][0]
if not self.target_grab_sequence:
#nothing selected... is this possible?
return {'CANCELLED'}
if len(self.grabbed_sequences) == 1 and not (self.grabbed_sequences[0].select_left_handle or self.grabbed_sequences[0].select_right_handle):
self.can_pop = True
else:
self.can_pop = False
context.window_manager.modal_handler_add(self)
args = (context, )
self._handle = bpy.types.SpaceSequenceEditor.draw_handler_add(self.vseqf_grab_draw, args, 'WINDOW', 'POST_PIXEL')
return {'RUNNING_MODAL'}
class VSEQFGrab(bpy.types.Operator):
"""Wrapper operator for the built-in grab operator, runs the added features as well as the original."""
bl_idname = "vseqf.grab"
bl_label = "Replacement for the default grab operator with more features"
bl_options = {"UNDO"}
mode: bpy.props.StringProperty("")
def execute(self, context):
del context
bpy.ops.vseqf.grabadd('INVOKE_DEFAULT', mode=self.mode)
if self.mode == "TIME_EXTEND":
bpy.ops.transform.transform("INVOKE_DEFAULT", mode=self.mode)
elif self.mode == "SLIP":
bpy.ops.sequencer.slip('INVOKE_DEFAULT')
else:
bpy.ops.transform.seq_slide('INVOKE_DEFAULT')
self.mode = ''
return {'FINISHED'}
class VSEQFContextMenu(bpy.types.Operator):
bl_idname = "vseqf.context_menu"
bl_label = "Open Context Menu"
click_mode = None
def invoke(self, context, event):
self.click_mode = get_click_mode(context)
if event.type == 'RIGHTMOUSE':
if self.click_mode == 'RIGHT':
return {'CANCELLED'}
self.context_menu(context, event)
return {'FINISHED'}
def context_menu(self, context, event):
region = context.region
view = region.view2d
distance_multiplier = 15
location = view.region_to_view(event.mouse_region_x, event.mouse_region_y)
click_frame, click_channel = location
active = timeline.current_active(context)
#determine distance scale
width = region.width
left, bottom = view.region_to_view(0, 0)
right, bottom = view.region_to_view(width, 0)
shown_width = right - left
frame_px = width / shown_width
distance = distance_multiplier / frame_px
if abs(click_frame - context.scene.frame_current) <= distance:
#clicked on cursor
bpy.ops.wm.call_menu(name='VSEQF_MT_context_cursor')
elif event.mouse_region_y <= marker_area_height:
is_near_marker = near_marker(context, click_frame)
if is_near_marker:
#clicked on marker
context.scene.vseqf.current_marker_frame = is_near_marker.frame
bpy.ops.wm.call_menu(name='VSEQF_MT_context_marker')
elif active and on_sequence(click_frame, click_channel, active):
#clicked on sequence
active_size = active.frame_final_duration * frame_px
if abs(click_frame - active.frame_final_start) <= distance * 2 and active_size > 60:
bpy.ops.wm.call_menu(name='VSEQF_MT_context_sequence_left')
elif abs(click_frame - active.frame_final_end) <= distance * 2 and active_size > 60:
bpy.ops.wm.call_menu(name='VSEQF_MT_context_sequence_right')
else:
bpy.ops.wm.call_menu(name="VSEQF_MT_context_sequence")
else:
#clicked on empty area
bpy.ops.wm.call_menu(name='VSEQF_MT_context_none')
class VSEQFDoubleUndo(bpy.types.Operator):
"""Undo previous action"""
bl_idname = "vseqf.undo"
bl_label = "Undo previous action"
def execute(self, context):
bpy.ops.ed.undo()
return {'FINISHED'}
class VSEQFContextMarker(bpy.types.Menu):
bl_idname = 'VSEQF_MT_context_marker'
bl_label = 'Marker Operations'
def draw(self, context):
layout = self.layout
layout.operator('vseqf.undo', text='Undo')
layout.separator()
if timeline.inside_meta_strip():
layout.operator('vseqf.meta_exit')
layout.separator()
frame = context.scene.vseqf.current_marker_frame
marker = None
for timeline_marker in context.scene.timeline_markers:
if timeline_marker.frame == frame:
marker = timeline_marker
if marker:
layout.operator('vseqf.quickmarkers_delete', text='Delete Marker').frame = frame
row = layout.row()
row.operator_context = 'INVOKE_DEFAULT'
row.operator('vseqf.quickmarkers_rename')
layout.operator('vseqf.quickmarkers_jump', text='Jump Cursor To This Marker').frame = frame
layout.operator('vseqf.quickmarkers_move').frame = frame
props = layout.operator('vseqf.quickmarkers_move', text='Move Marker To Cursor')
props.frame = frame
props.to_cursor = True
class VSEQFContextCursor(bpy.types.Menu):
bl_idname = "VSEQF_MT_context_cursor"
bl_label = "Cursor Operations"
def draw(self, context):
layout = self.layout
layout.operator('vseqf.undo', text='Undo')
layout.separator()
if timeline.inside_meta_strip():
layout.operator('vseqf.meta_exit')
layout.separator()
props = layout.operator("sequencer.strip_jump", text="Jump to Previous Strip")
props.next = False
props.center = False
props = layout.operator("sequencer.strip_jump", text="Jump to Next Strip")
props.next = True
props.center = False
layout.separator()
layout.label(text='Snap:')
layout.operator('vseqf.quicksnaps', text='Cursor To Nearest Second').type = 'cursor_to_seconds'
sequence = timeline.current_active(context)
if sequence:
layout.separator()
layout.operator('vseqf.quicksnaps', text='Cursor To Beginning Of Sequence').type = 'cursor_to_beginning'
layout.operator('vseqf.quicksnaps', text='Cursor To End Of Sequence').type = 'cursor_to_end'
layout.operator('vseqf.quicksnaps', text='Selected To Cursor').type = 'selection_to_cursor'
layout.operator('vseqf.quicksnaps', text='Sequence Beginning To Cursor').type = 'begin_to_cursor'
layout.operator('vseqf.quicksnaps', text='Sequence End To Cursor').type = 'end_to_cursor'
markers = context.scene.timeline_markers
if len(markers) > 0:
layout.separator()
layout.operator('vseqf.skip_timeline', text='Jump to Closest Marker').type = 'CLOSEMARKER'
layout.operator('vseqf.skip_timeline', text='Jump to Previous Marker').type = 'LASTMARKER'
layout.operator('vseqf.skip_timeline', text='Jump to Next Marker').type = 'NEXTMARKER'
class VSEQFContextNone(bpy.types.Menu):
bl_idname = 'VSEQF_MT_context_none'
bl_label = "Operations On Sequence Editor"
def draw(self, context):
del context
layout = self.layout
layout.operator('vseqf.undo', text='Undo')
layout.separator()
if timeline.inside_meta_strip():
layout.operator('vseqf.meta_exit')
layout.separator()
layout.menu('SEQUENCER_MT_add')
layout.menu('VSEQF_MT_quickzooms_menu')
layout.menu('VSEQF_MT_quicktimeline_menu')
class VSEQFContextSequenceLeft(bpy.types.Menu):
bl_idname = "VSEQF_MT_context_sequence_left"
bl_label = "Operations On Left Handle"
def draw(self, context):
strip = timeline.current_active(context)
layout = self.layout
layout.operator('vseqf.undo', text='Undo')
if timeline.inside_meta_strip():
layout.separator()
layout.operator('vseqf.meta_exit')
if strip:
layout.separator()
layout.prop(context.scene.vseqf, 'fade')
layout.operator('vseqf.quickfades_set', text='Set Fade In').type = 'in'
props = layout.operator('vseqf.quickfades_clear', text='Clear Fade In')
props.direction = 'in'
props.active_only = True