Node Wrangler: Added proper labels for merging shader nodes
[blender-addons.git] / storypencil / synchro.py
blob6994252261401d7fc2ad848164344c9789f0c536
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 from typing import List, Sequence, Tuple
5 import bpy
6 import functools
7 import os
8 from bpy.app.handlers import persistent
10 from bpy.types import (
11 Context,
12 MetaSequence,
13 Operator,
14 PropertyGroup,
15 SceneSequence,
16 Window,
17 WindowManager,
19 from bpy.props import (
20 BoolProperty,
21 IntProperty,
22 StringProperty,
24 from .scene_tools import STORYPENCIL_OT_NewScene
25 from .render import STORYPENCIL_OT_RenderAction
26 from .sound import delete_sounds, send_sound_to_strip_scene
28 def window_id(window: Window) -> str:
29 """ Get Window's ID.
31 :param window: the Window to consider
32 :return: the Window's ID
33 """
34 return str(window.as_pointer())
37 def get_window_from_id(wm: WindowManager, win_id: str) -> Window:
38 """Get a Window object from its ID (serialized ptr).
40 :param wm: a WindowManager holding Windows
41 :param win_id: the ID of the Window to get
42 :return: the Window matching the given ID, None otherwise
43 """
44 return next((w for w in wm.windows if w and window_id(w) == win_id), None)
47 def get_main_windows_list(wm: WindowManager) -> Sequence[Window]:
48 """Get all the Main Windows held by the given WindowManager `wm`"""
49 return [w for w in wm.windows if w and w.parent is None]
52 def join_win_ids(ids: List[str]) -> str:
53 """Join Windows IDs in a single string"""
54 return ";".join(ids)
57 def split_win_ids(ids: str) -> List[str]:
58 """Split a Windows IDs string into individual IDs"""
59 return ids.split(";")
62 class STORYPENCIL_OT_SetSyncMainOperator(Operator):
63 bl_idname = "storypencil.sync_set_main"
64 bl_label = "Set as Sync Main"
65 bl_description = "Set this Window as main for Synchronization"
66 bl_options = {'INTERNAL'}
68 win_id: bpy.props.StringProperty(
69 name="Window ID",
70 default="",
71 options=set(),
72 description="Main window ID",
75 def copy_settings(self, main_window, secondary_window):
76 if main_window is None or secondary_window is None:
77 return
78 secondary_window.scene.storypencil_main_workspace = main_window.scene.storypencil_main_workspace
79 secondary_window.scene.storypencil_main_scene = main_window.scene.storypencil_main_scene
80 secondary_window.scene.storypencil_edit_workspace = main_window.scene.storypencil_edit_workspace
82 def execute(self, context):
83 options = context.window_manager.storypencil_settings
84 options.main_window_id = self.win_id
85 wm = bpy.context.window_manager
86 scene = context.scene
87 wm['storypencil_use_new_window'] = scene.storypencil_use_new_window
89 main_windows = get_main_windows_list(wm)
90 main_window = get_main_window(wm)
91 secondary_window = get_secondary_window(wm)
92 # Active sync
93 options.active = True
94 if secondary_window is None:
95 # Open a new window
96 if len(main_windows) < 2:
97 bpy.ops.storypencil.create_secondary_window()
98 secondary_window = get_secondary_window(wm)
99 self.copy_settings(get_main_window(wm), secondary_window)
100 return {'FINISHED'}
101 else:
102 # Reuse the existing window
103 secondary_window = get_not_main_window(wm)
104 else:
105 # Open new secondary
106 if len(main_windows) < 2:
107 bpy.ops.storypencil.create_secondary_window()
108 secondary_window = get_secondary_window(wm)
109 self.copy_settings(get_main_window(wm), secondary_window)
110 return {'FINISHED'}
111 else:
112 # Reuse the existing window
113 secondary_window = get_not_main_window(wm)
115 if secondary_window:
116 enable_secondary_window(wm, window_id(secondary_window))
117 win_id = window_id(secondary_window)
118 self.copy_settings(get_main_window(wm), secondary_window)
119 bpy.ops.storypencil.sync_window_bring_front(win_id=win_id)
121 return {'FINISHED'}
124 class STORYPENCIL_OT_AddSecondaryWindowOperator(Operator):
125 bl_idname = "storypencil.create_secondary_window"
126 bl_label = "Create Secondary Window"
127 bl_description = "Create a Secondary Main Window and enable Synchronization"
128 bl_options = {'INTERNAL'}
130 def execute(self, context):
131 # store existing windows
132 windows = set(context.window_manager.windows[:])
133 bpy.ops.wm.window_new_main()
134 # get newly created window by comparing to previous list
135 new_window = (set(context.window_manager.windows[:]) - windows).pop()
136 # activate sync system and enable sync for this window
137 toggle_secondary_window(context.window_manager, window_id(new_window))
138 context.window_manager.storypencil_settings.active = True
139 # trigger initial synchronization to open the current Sequence's Scene
140 on_frame_changed()
141 # Configure the new window
142 self.configure_new_secondary_window(context, new_window)
144 return {'FINISHED'}
146 def configure_new_secondary_window(self, context, new_window):
147 wrk_name = context.scene.storypencil_edit_workspace.name
148 # Open the 2D workspace
149 blendpath = os.path.dirname(bpy.app.binary_path)
150 version = bpy.app.version
151 version_full = str(version[0]) + '.' + str(version[1])
152 template = os.path.join("scripts", "startup",
153 "bl_app_templates_system")
154 template = os.path.join(template, wrk_name, "startup.blend")
155 template_path = os.path.join(blendpath, version_full, template)
156 # Check if workspace exist and add it if missing
157 for wk in bpy.data.workspaces:
158 if wk.name == wrk_name:
159 new_window.workspace = wk
160 return
161 with context.temp_override(window=new_window):
162 bpy.ops.workspace.append_activate(context, idname=wrk_name, filepath=template_path)
165 class STORYPENCIL_OT_WindowBringFront(Operator):
166 bl_idname = "storypencil.sync_window_bring_front"
167 bl_label = "Bring Window Front"
168 bl_description = "Bring a Window to Front"
169 bl_options = {'INTERNAL'}
171 win_id: bpy.props.StringProperty()
173 def execute(self, context):
174 win = get_window_from_id(context.window_manager, self.win_id)
175 if not win:
176 return {'CANCELLED'}
177 with context.temp_override(window=win):
178 bpy.ops.wm.window_fullscreen_toggle()
179 bpy.ops.wm.window_fullscreen_toggle()
180 return {'FINISHED'}
183 class STORYPENCIL_OT_WindowCloseOperator(Operator):
184 bl_idname = "storypencil.close_secondary_window"
185 bl_label = "Close Window"
186 bl_description = "Close a specific Window"
187 bl_options = {'INTERNAL'}
189 win_id: bpy.props.StringProperty()
191 def execute(self, context):
192 win = get_window_from_id(context.window_manager, self.win_id)
193 if not win:
194 return {'CANCELLED'}
195 with context.temp_override(window=win):
196 bpy.ops.wm.window_close()
197 return {'FINISHED'}
200 def validate_sync(window_manager: WindowManager) -> bool:
202 Ensure synchronization system is functional, with a valid main window.
203 Disable it otherwise and return the system status.
205 if not window_manager.storypencil_settings.active:
206 return False
207 if not get_window_from_id(window_manager, window_manager.storypencil_settings.main_window_id):
208 window_manager.storypencil_settings.active = False
209 return window_manager.storypencil_settings.active
212 def get_secondary_window_indices(wm: WindowManager) -> List[str]:
213 """Get secondary Windows indices as a list of IDs
215 :param wm: the WindowManager to consider
216 :return: the list of secondary Windows IDs
218 return split_win_ids(wm.storypencil_settings.secondary_windows_ids)
221 def is_secondary_window(window_manager: WindowManager, win_id: str) -> bool:
222 """Return wether the Window identified by 'win_id' is a secondary window.
224 :return: whether this Window is a sync secondary
226 return win_id in get_secondary_window_indices(window_manager)
229 def enable_secondary_window(wm: WindowManager, win_id: str):
230 """Enable the secondary status of a Window.
232 :param wm: the WindowManager instance
233 :param win_id: the id of the window
235 secondary_indices = get_secondary_window_indices(wm)
236 win_id_str = win_id
237 # Delete old indice if exist
238 if win_id_str in secondary_indices:
239 secondary_indices.remove(win_id_str)
241 # Add indice
242 secondary_indices.append(win_id_str)
244 # rebuild the whole list of valid secondary windows
245 secondary_indices = [
246 idx for idx in secondary_indices if get_window_from_id(wm, idx)]
248 wm.storypencil_settings.secondary_windows_ids = join_win_ids(secondary_indices)
251 def toggle_secondary_window(wm: WindowManager, win_id: str):
252 """Toggle the secondary status of a Window.
254 :param wm: the WindowManager instance
255 :param win_id: the id of the window
257 secondary_indices = get_secondary_window_indices(wm)
258 win_id_str = win_id
259 if win_id_str in secondary_indices:
260 secondary_indices.remove(win_id_str)
261 else:
262 secondary_indices.append(win_id_str)
264 # rebuild the whole list of valid secondary windows
265 secondary_indices = [
266 idx for idx in secondary_indices if get_window_from_id(wm, idx)]
268 wm.storypencil_settings.secondary_windows_ids = join_win_ids(secondary_indices)
271 def get_main_window(wm: WindowManager) -> Window:
272 """Get the Window used to drive the synchronization system
274 :param wm: the WindowManager instance
275 :returns: the main Window or None
277 return get_window_from_id(wm=wm, win_id=wm.storypencil_settings.main_window_id)
280 def get_secondary_window(wm: WindowManager) -> Window:
281 """Get the first secondary Window
283 :param wm: the WindowManager instance
284 :returns: the Window or None
286 for w in wm.windows:
287 win_id = window_id(w)
288 if is_secondary_window(wm, win_id):
289 return w
291 return None
294 def get_not_main_window(wm: WindowManager) -> Window:
295 """Get the first not main Window
297 :param wm: the WindowManager instance
298 :returns: the Window or None
300 for w in wm.windows:
301 win_id = window_id(w)
302 if win_id != wm.storypencil_settings.main_window_id:
303 return w
305 return None
308 def get_main_strip(wm: WindowManager) -> SceneSequence:
309 """Get Scene Strip at current time in Main window
311 :param wm: the WindowManager instance
312 :returns: the Strip at current time or None
314 context = bpy.context
315 scene = context.scene
316 main_scene = scene.storypencil_main_scene
318 if main_scene.storypencil_use_new_window:
319 main_window = get_main_window(wm=wm)
320 if not main_window or not main_window.scene.sequence_editor:
321 return None
322 seq_editor = main_window.scene.sequence_editor
323 return seq_editor.sequences.get(wm.storypencil_settings.main_strip_name, None)
324 else:
325 seq_editor = main_scene.sequence_editor.sequences
326 for strip in seq_editor:
327 if strip.type != 'SCENE':
328 continue
329 if strip.scene.name == scene.name:
330 return strip
332 return None
335 class STORYPENCIL_OT_SyncToggleSecondary(Operator):
336 bl_idname = "storypencil.sync_toggle_secondary"
337 bl_label = "Toggle Secondary Window Status"
338 bl_description = "Enable/Disable synchronization for a specific Window"
339 bl_options = {'INTERNAL'}
341 win_id: bpy.props.StringProperty(name="Window Index")
343 def execute(self, context):
344 wm = context.window_manager
345 toggle_secondary_window(wm, self.win_id)
346 return {'FINISHED'}
349 def get_sequences_at_frame(
350 frame: int,
351 sequences: Sequence[Sequence]) -> Sequence[bpy.types.Sequence]:
352 """ Get all sequencer strips at given frame.
354 :param frame: the frame to consider
356 return [s for s in sequences if frame >= s.frame_start + s.frame_offset_start and
357 frame < s.frame_start + s.frame_offset_start + s.frame_final_duration and
358 s.type == 'SCENE']
361 def get_sequence_at_frame(
362 frame: int,
363 sequences: Sequence[bpy.types.Sequence] = None,
364 skip_muted: bool = True,
365 ) -> Tuple[bpy.types.Sequence, int]:
367 Get the higher sequence strip in channels stack at current frame.
368 Recursively enters scene sequences and returns the original frame in the
369 returned strip's time referential.
371 :param frame: the frame to consider
372 :param skip_muted: skip muted strips
373 :returns: the sequence strip and the frame in strip's time referential
376 strips = get_sequences_at_frame(frame, sequences or bpy.context.sequences)
378 # exclude muted strips
379 if skip_muted:
380 strips = [strip for strip in strips if not strip.mute]
382 if not strips:
383 return None, frame
385 # Remove strip not scene type. Switch is only with Scenes
386 for strip in strips:
387 if strip.type != 'SCENE':
388 strips.remove(strip)
390 # consider higher strip in stack
391 strip = sorted(strips, key=lambda x: x.channel)[-1]
392 # go deeper when current strip is a MetaSequence
393 if isinstance(strip, MetaSequence):
394 return get_sequence_at_frame(frame, strip.sequences, skip_muted)
395 if isinstance(strip, SceneSequence):
396 # apply time offset to get in sequence's referential
397 frame = frame - strip.frame_start + strip.scene.frame_start
398 # enter scene's sequencer if used as input
399 if strip.scene_input == 'SEQUENCER':
400 return get_sequence_at_frame(frame, strip.scene.sequence_editor.sequences)
401 return strip, frame
404 def set_scene_frame(scene, frame, force_update_main=False):
406 Set `scene` frame_current to `frame` if different.
408 :param scene: the scene to update
409 :param frame: the frame value
410 :param force_update_main: whether to force the update of main scene
412 options = bpy.context.window_manager.storypencil_settings
413 if scene.frame_current != frame:
414 scene.frame_current = int(frame)
415 scene.frame_set(int(frame))
416 if force_update_main:
417 update_sync(
418 bpy.context, bpy.context.window_manager.storypencil_settings.main_window_id)
421 def setup_window_from_scene_strip(window: Window, strip: SceneSequence):
422 """Change the Scene and camera of `window` based on `strip`.
424 :param window: [description]
425 :param scene_strip: [description]
427 if window.scene != strip.scene:
428 window.scene = strip.scene
429 if strip.scene_camera and strip.scene_camera != window.scene.camera:
430 strip.scene.camera = strip.scene_camera
433 @persistent
434 def on_frame_changed(*args):
436 React to current frame changes and synchronize secondary windows.
438 # ensure context is fully initialized, i.e not '_RestrictData
439 if not isinstance(bpy.context, Context):
440 return
442 # happens in some cases (not sure why)
443 if not bpy.context.window:
444 return
446 wm = bpy.context.window_manager
448 # early return if synchro is disabled / not available
449 if not validate_sync(wm) or len(bpy.data.scenes) < 2:
450 return
452 # get current window id
453 update_sync(bpy.context)
456 def update_sync(context: Context, win_id=None):
457 """ Update synchronized Windows based on the current `context`.
459 :param context: the context
460 :param win_id: specify a window id (context.window is used otherwise)
462 wm = context.window_manager
464 if not win_id:
465 win_id = window_id(context.window)
467 main_scene = get_window_from_id(
468 wm, wm.storypencil_settings.main_window_id).scene
469 if not main_scene.sequence_editor:
470 return
472 # return if scene's sequence editor has no sequences
473 sequences = main_scene.sequence_editor.sequences
474 if not sequences:
475 return
477 # bidirectionnal sync: change main time from secondary window
478 if (
479 win_id != wm.storypencil_settings.main_window_id
480 and is_secondary_window(wm, win_id)
482 # get strip under time cursor in main window
483 strip, old_frame = get_sequence_at_frame(
484 main_scene.frame_current,
485 sequences=sequences
487 # only do bidirectional sync if secondary window matches the strip at current time in main
488 if not isinstance(strip, SceneSequence) or strip.scene != context.scene:
489 return
491 # calculate offset
492 frame_offset = context.scene.frame_current - old_frame
493 if frame_offset == 0:
494 return
496 new_main_frame = main_scene.frame_current + frame_offset
497 update_main_time = True
498 # check if a valid scene strip is available under new frame before changing main time
499 f_start = strip.frame_start + strip.frame_offset_start
500 f_end = f_start + strip.frame_final_duration
501 if new_main_frame < f_start or new_main_frame >= f_end:
502 new_strip, _ = get_sequence_at_frame(
503 new_main_frame,
504 main_scene.sequence_editor.sequences,
506 update_main_time = isinstance(new_strip, SceneSequence)
507 if update_main_time:
508 # update main time change in the next event loop + force the sync system update
509 # because Blender won't trigger a frame_changed event to avoid infinite recursion
510 bpy.app.timers.register(
511 functools.partial(set_scene_frame, main_scene,
512 new_main_frame, True)
515 return
517 # return if current window is not main window
518 if win_id != wm.storypencil_settings.main_window_id:
519 return
521 secondary_windows = [
522 get_window_from_id(wm, win_id)
523 for win_id
524 in get_secondary_window_indices(wm)
525 if win_id and win_id != wm.storypencil_settings.main_window_id
528 # only work with at least 2 windows
529 if not secondary_windows:
530 return
532 seq, frame = get_sequence_at_frame(main_scene.frame_current, sequences)
534 # return if no sequence at current time or not a scene strip
535 if not isinstance(seq, SceneSequence) or not seq.scene:
536 wm.storypencil_settings.main_strip_name = ""
537 return
539 wm.storypencil_settings.main_strip_name = seq.name
540 # change the scene on secondary windows
541 # warning: only one window's scene can be changed in this event loop,
542 # otherwise it may crashes Blender randomly
543 for idx, win in enumerate(secondary_windows):
544 if not win:
545 continue
546 # change first secondary window immediately
547 if idx == 0:
548 setup_window_from_scene_strip(win, seq)
549 else:
550 # trigger change in next event loop for other windows
551 bpy.app.timers.register(
552 functools.partial(setup_window_from_scene_strip, win, seq)
555 set_scene_frame(seq.scene, frame)
558 def sync_all_windows(wm: WindowManager):
559 """Enable synchronization on all main windows held by `wm`."""
560 wm.storypencil_settings.secondary_windows_ids = join_win_ids([
561 window_id(w)
562 for w
563 in get_main_windows_list(wm)
567 @persistent
568 def sync_autoconfig(*args):
569 """Autoconfigure synchronization system.
570 If a window contains a VSE area on a scene with a valid sequence_editor,
571 makes it main window and enable synchronization on all other main windows.
573 main_windows = get_main_windows_list(bpy.context.window_manager)
574 # don't try to go any further if only one main window
575 if len(main_windows) < 2:
576 return
578 # look for a main window with a valid sequence editor
579 main = next(
582 for win in main_windows
583 if win.scene.sequence_editor
584 and any(area.type == 'SEQUENCE_EDITOR' for area in win.screen.areas)
586 None
588 # if any, set as main and activate sync on all other windows
589 if main:
590 bpy.context.window_manager.storypencil_settings.main_window_id = window_id(
591 main)
592 sync_all_windows(bpy.context.window_manager)
593 bpy.context.window_manager.storypencil_settings.active = True
596 def sync_active_update(self, context):
597 """ Update function for WindowManager.storypencil_settings.active. """
598 # ensure main window is valid, using current context's window if none is set
599 if (
600 self.active
601 and (
602 not self.main_window_id
603 or not get_window_from_id(context.window_manager, self.main_window_id)
606 self.main_window_id = window_id(context.window)
607 # automatically sync all other windows if nothing was previously set
608 if not self.secondary_windows_ids:
609 sync_all_windows(context.window_manager)
611 on_frame_changed()
614 def draw_sync_header(self, context):
615 """Draw Window sync tools header."""
617 wm = context.window_manager
618 self.layout.separator()
619 if wm.get('storypencil_use_new_window') is not None:
620 new_window = wm['storypencil_use_new_window']
621 else:
622 new_window = False
624 if not new_window:
625 if context.scene.storypencil_main_workspace:
626 if context.scene.storypencil_main_workspace.name != context.workspace.name:
627 if context.area.ui_type == 'DOPESHEET':
628 row = self.layout.row(align=True)
629 row.operator(STORYPENCIL_OT_Switch.bl_idname,
630 text="Back To VSE")
633 def draw_sync_sequencer_header(self, context):
634 """Draw Window sync tools header."""
635 if context.space_data.view_type != 'SEQUENCER':
636 return
638 wm = context.window_manager
639 layout = self.layout
640 layout.separator()
641 row = layout.row(align=True)
642 row.label(text="Scenes:")
643 if context.scene.storypencil_use_new_window:
644 row.operator(STORYPENCIL_OT_SetSyncMainOperator.bl_idname, text="Edit")
645 else:
646 row.operator(STORYPENCIL_OT_Switch.bl_idname, text="Edit")
648 row.separator()
649 layout.operator_context = 'INVOKE_REGION_WIN'
650 row.operator(STORYPENCIL_OT_NewScene.bl_idname, text="New")
652 layout.operator_context = 'INVOKE_DEFAULT'
653 row.separator(factor=0.5)
654 row.operator(STORYPENCIL_OT_RenderAction.bl_idname, text="Render")
657 class STORYPENCIL_PG_Settings(PropertyGroup):
659 PropertyGroup with storypencil settings.
661 active: BoolProperty(
662 name="Synchronize",
663 description=(
664 "Automatically open current Sequence's Scene in other "
665 "Main Windows and activate Time Synchronization"),
666 default=False,
667 update=sync_active_update
670 main_window_id: StringProperty(
671 name="Main Window ID",
672 description="ID of the window driving the Synchronization",
673 default="",
676 secondary_windows_ids: StringProperty(
677 name="Secondary Windows",
678 description="Serialized Secondary Window Indices",
679 default="",
682 active_window_index: IntProperty(
683 name="Active Window Index",
684 description="Index for using Window Manager's windows in a UIList",
685 default=0
688 main_strip_name: StringProperty(
689 name="Main Strip Name",
690 description="Scene Strip at current time in the Main window",
691 default="",
694 show_main_strip_range: BoolProperty(
695 name="Show Main Strip Range in Secondary Windows",
696 description="Draw main Strip's in/out markers in synchronized secondary Windows",
697 default=True,
701 # -------------------------------------------------------------
702 # Switch manually between Main and Edit Scene and Layout
704 # -------------------------------------------------------------
705 class STORYPENCIL_OT_Switch(Operator):
706 bl_idname = "storypencil.switch"
707 bl_label = "Switch"
708 bl_description = "Switch workspace"
709 bl_options = {'REGISTER', 'UNDO'}
711 # Get active strip
712 def act_strip(self, context):
713 scene = context.scene
714 sequences = scene.sequence_editor.sequences
715 if not sequences:
716 return None
717 # Get strip under time cursor
718 strip, old_frame = get_sequence_at_frame(
719 scene.frame_current, sequences=sequences)
720 return strip
722 # ------------------------------
723 # Poll
724 # ------------------------------
725 @classmethod
726 def poll(cls, context):
727 scene = context.scene
728 if scene.storypencil_main_workspace is None or scene.storypencil_main_scene is None:
729 return False
730 if scene.storypencil_edit_workspace is None:
731 return False
733 return True
735 # ------------------------------
736 # Execute button action
737 # ------------------------------
738 def execute(self, context):
739 wm = context.window_manager
740 scene = context.scene
741 wm['storypencil_use_new_window'] = scene.storypencil_use_new_window
743 # Switch to Main
744 if scene.storypencil_main_workspace.name != context.workspace.name:
745 cfra_prv = scene.frame_current
746 prv_pin = None
747 if scene.storypencil_main_workspace is not None:
748 if scene.storypencil_main_workspace.use_pin_scene:
749 scene.storypencil_main_workspace.use_pin_scene = False
751 context.window.workspace = scene.storypencil_main_workspace
753 if scene.storypencil_main_scene is not None:
754 context.window.scene = scene.storypencil_main_scene
755 strip = self.act_strip(context)
756 if strip:
757 context.window.scene.frame_current = int(cfra_prv + strip.frame_start) - 1
759 # Delete sounds
760 if scene.storypencil_copy_sounds:
761 delete_sounds(scene)
763 #bpy.ops.sequencer.reload()
764 else:
765 # Switch to Edit
766 strip = self.act_strip(context)
767 # save camera
768 if strip is not None and strip.type == "SCENE":
769 # Save data
770 strip.scene.storypencil_main_workspace = scene.storypencil_main_workspace
771 strip.scene.storypencil_main_scene = scene.storypencil_main_scene
772 strip.scene.storypencil_edit_workspace = scene.storypencil_edit_workspace
774 # Set workspace and Scene
775 cfra_prv = scene.frame_current
776 if scene.storypencil_edit_workspace.use_pin_scene:
777 scene.storypencil_edit_workspace.use_pin_scene = False
779 context.window.workspace = scene.storypencil_edit_workspace
780 context.window.workspace.update_tag()
782 context.window.scene = strip.scene
783 # Copy sounds
784 if scene.storypencil_copy_sounds:
785 send_sound_to_strip_scene(strip, clear_sequencer=True, skip_mute=scene.storypencil_skip_sound_mute)
788 active_frame = cfra_prv - strip.frame_start + 1
789 if active_frame < strip.scene.frame_start:
790 active_frame = strip.scene.frame_start
791 context.window.scene.frame_current = int(active_frame)
793 # Set camera
794 if strip.scene_input == 'CAMERA':
795 for screen in bpy.data.screens:
796 for area in screen.areas:
797 if area.type == 'VIEW_3D':
798 # select camera as view
799 if strip and strip.scene.camera is not None:
800 area.spaces.active.region_3d.view_perspective = 'CAMERA'
802 if scene.storypencil_copy_sounds:
803 bpy.ops.sequencer.reload()
805 return {"FINISHED"}
808 class STORYPENCIL_OT_TabSwitch(Operator):
809 bl_idname = "storypencil.tabswitch"
810 bl_label = "Switch using tab key"
811 bl_description = "Wrapper used to handle the Tab key to switch"
812 bl_options = {'INTERNAL'}
814 def execute(self, context):
815 # For meta strips the tab key must be processed by other operator, so
816 # just pass through to the next operator in the stack.
817 if context.active_sequence_strip and context.active_sequence_strip.type == 'META':
818 return {'PASS_THROUGH'}
820 if context.scene.sequence_editor and context.scene.sequence_editor.meta_stack:
821 return {'PASS_THROUGH'}
823 if context.scene.storypencil_use_new_window:
824 bpy.ops.storypencil.sync_set_main('INVOKE_DEFAULT', True)
825 else:
826 scene = context.scene
827 sequences = scene.sequence_editor.sequences
828 if sequences:
829 # Get strip under time cursor
830 strip, old_frame = get_sequence_at_frame(
831 scene.frame_current, sequences=sequences)
832 if strip and strip.type == 'SCENE':
833 bpy.ops.storypencil.switch('INVOKE_DEFAULT', True)
835 return {'FINISHED'}