FBX IO: Vertex position access with attributes
[blender-addons.git] / storypencil / synchro.py
blobc016328d7d1614825425aa51372285080e21603c
1 # SPDX-FileCopyrightText: 2022-2023 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 from typing import List, Sequence, Tuple
7 import bpy
8 import functools
9 import os
10 from bpy.app.handlers import persistent
12 from bpy.types import (
13 Context,
14 MetaSequence,
15 Operator,
16 PropertyGroup,
17 SceneSequence,
18 Window,
19 WindowManager,
21 from bpy.props import (
22 BoolProperty,
23 IntProperty,
24 StringProperty,
26 from .scene_tools import STORYPENCIL_OT_NewScene
27 from .render import STORYPENCIL_OT_RenderAction
28 from .sound import delete_sounds, send_sound_to_strip_scene
30 def window_id(window: Window) -> str:
31 """ Get Window's ID.
33 :param window: the Window to consider
34 :return: the Window's ID
35 """
36 return str(window.as_pointer())
39 def get_window_from_id(wm: WindowManager, win_id: str) -> Window:
40 """Get a Window object from its ID (serialized ptr).
42 :param wm: a WindowManager holding Windows
43 :param win_id: the ID of the Window to get
44 :return: the Window matching the given ID, None otherwise
45 """
46 return next((w for w in wm.windows if w and window_id(w) == win_id), None)
49 def get_main_windows_list(wm: WindowManager) -> Sequence[Window]:
50 """Get all the Main Windows held by the given WindowManager `wm`"""
51 return [w for w in wm.windows if w and w.parent is None]
54 def join_win_ids(ids: List[str]) -> str:
55 """Join Windows IDs in a single string"""
56 return ";".join(ids)
59 def split_win_ids(ids: str) -> List[str]:
60 """Split a Windows IDs string into individual IDs"""
61 return ids.split(";")
64 class STORYPENCIL_OT_SetSyncMainOperator(Operator):
65 bl_idname = "storypencil.sync_set_main"
66 bl_label = "Set as Sync Main"
67 bl_description = "Set this Window as main for Synchronization"
68 bl_options = {'INTERNAL'}
70 win_id: bpy.props.StringProperty(
71 name="Window ID",
72 default="",
73 options=set(),
74 description="Main window ID",
77 def copy_settings(self, main_window, secondary_window):
78 if main_window is None or secondary_window is None:
79 return
80 secondary_window.scene.storypencil_main_workspace = main_window.scene.storypencil_main_workspace
81 secondary_window.scene.storypencil_main_scene = main_window.scene.storypencil_main_scene
82 secondary_window.scene.storypencil_edit_workspace = main_window.scene.storypencil_edit_workspace
84 def execute(self, context):
85 options = context.window_manager.storypencil_settings
86 options.main_window_id = self.win_id
87 wm = bpy.context.window_manager
88 scene = context.scene
89 wm['storypencil_use_new_window'] = scene.storypencil_use_new_window
91 main_windows = get_main_windows_list(wm)
92 main_window = get_main_window(wm)
93 secondary_window = get_secondary_window(wm)
94 # Active sync
95 options.active = True
96 if secondary_window is None:
97 # Open a new window
98 if len(main_windows) < 2:
99 bpy.ops.storypencil.create_secondary_window()
100 secondary_window = get_secondary_window(wm)
101 self.copy_settings(get_main_window(wm), secondary_window)
102 return {'FINISHED'}
103 else:
104 # Reuse the existing window
105 secondary_window = get_not_main_window(wm)
106 else:
107 # Open new secondary
108 if len(main_windows) < 2:
109 bpy.ops.storypencil.create_secondary_window()
110 secondary_window = get_secondary_window(wm)
111 self.copy_settings(get_main_window(wm), secondary_window)
112 return {'FINISHED'}
113 else:
114 # Reuse the existing window
115 secondary_window = get_not_main_window(wm)
117 if secondary_window:
118 enable_secondary_window(wm, window_id(secondary_window))
119 win_id = window_id(secondary_window)
120 self.copy_settings(get_main_window(wm), secondary_window)
121 bpy.ops.storypencil.sync_window_bring_front(win_id=win_id)
123 return {'FINISHED'}
126 class STORYPENCIL_OT_AddSecondaryWindowOperator(Operator):
127 bl_idname = "storypencil.create_secondary_window"
128 bl_label = "Create Secondary Window"
129 bl_description = "Create a Secondary Main Window and enable Synchronization"
130 bl_options = {'INTERNAL'}
132 def execute(self, context):
133 # store existing windows
134 windows = set(context.window_manager.windows[:])
135 bpy.ops.wm.window_new_main()
136 # get newly created window by comparing to previous list
137 new_window = (set(context.window_manager.windows[:]) - windows).pop()
138 # activate sync system and enable sync for this window
139 toggle_secondary_window(context.window_manager, window_id(new_window))
140 context.window_manager.storypencil_settings.active = True
141 # trigger initial synchronization to open the current Sequence's Scene
142 on_frame_changed()
143 # Configure the new window
144 self.configure_new_secondary_window(context, new_window)
146 return {'FINISHED'}
148 def configure_new_secondary_window(self, context, new_window):
149 wrk_name = context.scene.storypencil_edit_workspace.name
150 # Open the 2D workspace
151 blendpath = os.path.dirname(bpy.app.binary_path)
152 version = bpy.app.version
153 version_full = str(version[0]) + '.' + str(version[1])
154 template = os.path.join("scripts", "startup",
155 "bl_app_templates_system")
156 template = os.path.join(template, wrk_name, "startup.blend")
157 template_path = os.path.join(blendpath, version_full, template)
158 # Check if workspace exist and add it if missing
159 for wk in bpy.data.workspaces:
160 if wk.name == wrk_name:
161 new_window.workspace = wk
162 return
163 with context.temp_override(window=new_window):
164 bpy.ops.workspace.append_activate(idname=wrk_name, filepath=template_path)
167 class STORYPENCIL_OT_WindowBringFront(Operator):
168 bl_idname = "storypencil.sync_window_bring_front"
169 bl_label = "Bring Window Front"
170 bl_description = "Bring a Window to Front"
171 bl_options = {'INTERNAL'}
173 win_id: bpy.props.StringProperty()
175 def execute(self, context):
176 win = get_window_from_id(context.window_manager, self.win_id)
177 if not win:
178 return {'CANCELLED'}
179 with context.temp_override(window=win):
180 bpy.ops.wm.window_fullscreen_toggle()
181 bpy.ops.wm.window_fullscreen_toggle()
182 return {'FINISHED'}
185 class STORYPENCIL_OT_WindowCloseOperator(Operator):
186 bl_idname = "storypencil.close_secondary_window"
187 bl_label = "Close Window"
188 bl_description = "Close a specific Window"
189 bl_options = {'INTERNAL'}
191 win_id: bpy.props.StringProperty()
193 def execute(self, context):
194 win = get_window_from_id(context.window_manager, self.win_id)
195 if not win:
196 return {'CANCELLED'}
197 with context.temp_override(window=win):
198 bpy.ops.wm.window_close()
199 return {'FINISHED'}
202 def validate_sync(window_manager: WindowManager) -> bool:
204 Ensure synchronization system is functional, with a valid main window.
205 Disable it otherwise and return the system status.
207 if not window_manager.storypencil_settings.active:
208 return False
209 if not get_window_from_id(window_manager, window_manager.storypencil_settings.main_window_id):
210 window_manager.storypencil_settings.active = False
211 return window_manager.storypencil_settings.active
214 def get_secondary_window_indices(wm: WindowManager) -> List[str]:
215 """Get secondary Windows indices as a list of IDs
217 :param wm: the WindowManager to consider
218 :return: the list of secondary Windows IDs
220 return split_win_ids(wm.storypencil_settings.secondary_windows_ids)
223 def is_secondary_window(window_manager: WindowManager, win_id: str) -> bool:
224 """Return wether the Window identified by 'win_id' is a secondary window.
226 :return: whether this Window is a sync secondary
228 return win_id in get_secondary_window_indices(window_manager)
231 def enable_secondary_window(wm: WindowManager, win_id: str):
232 """Enable the secondary status of a Window.
234 :param wm: the WindowManager instance
235 :param win_id: the id of the window
237 secondary_indices = get_secondary_window_indices(wm)
238 win_id_str = win_id
239 # Delete old indice if exist
240 if win_id_str in secondary_indices:
241 secondary_indices.remove(win_id_str)
243 # Add indice
244 secondary_indices.append(win_id_str)
246 # rebuild the whole list of valid secondary windows
247 secondary_indices = [
248 idx for idx in secondary_indices if get_window_from_id(wm, idx)]
250 wm.storypencil_settings.secondary_windows_ids = join_win_ids(secondary_indices)
253 def toggle_secondary_window(wm: WindowManager, win_id: str):
254 """Toggle the secondary status of a Window.
256 :param wm: the WindowManager instance
257 :param win_id: the id of the window
259 secondary_indices = get_secondary_window_indices(wm)
260 win_id_str = win_id
261 if win_id_str in secondary_indices:
262 secondary_indices.remove(win_id_str)
263 else:
264 secondary_indices.append(win_id_str)
266 # rebuild the whole list of valid secondary windows
267 secondary_indices = [
268 idx for idx in secondary_indices if get_window_from_id(wm, idx)]
270 wm.storypencil_settings.secondary_windows_ids = join_win_ids(secondary_indices)
273 def get_main_window(wm: WindowManager) -> Window:
274 """Get the Window used to drive the synchronization system
276 :param wm: the WindowManager instance
277 :returns: the main Window or None
279 return get_window_from_id(wm=wm, win_id=wm.storypencil_settings.main_window_id)
282 def get_secondary_window(wm: WindowManager) -> Window:
283 """Get the first secondary Window
285 :param wm: the WindowManager instance
286 :returns: the Window or None
288 for w in wm.windows:
289 win_id = window_id(w)
290 if is_secondary_window(wm, win_id):
291 return w
293 return None
296 def get_not_main_window(wm: WindowManager) -> Window:
297 """Get the first not main Window
299 :param wm: the WindowManager instance
300 :returns: the Window or None
302 for w in wm.windows:
303 win_id = window_id(w)
304 if win_id != wm.storypencil_settings.main_window_id:
305 return w
307 return None
310 def get_main_strip(wm: WindowManager) -> SceneSequence:
311 """Get Scene Strip at current time in Main window
313 :param wm: the WindowManager instance
314 :returns: the Strip at current time or None
316 context = bpy.context
317 scene = context.scene
318 main_scene = scene.storypencil_main_scene
320 if main_scene.storypencil_use_new_window:
321 main_window = get_main_window(wm=wm)
322 if not main_window or not main_window.scene.sequence_editor:
323 return None
324 seq_editor = main_window.scene.sequence_editor
325 return seq_editor.sequences.get(wm.storypencil_settings.main_strip_name, None)
326 else:
327 seq_editor = main_scene.sequence_editor.sequences
328 for strip in seq_editor:
329 if strip.type != 'SCENE':
330 continue
331 if strip.scene.name == scene.name:
332 return strip
334 return None
337 class STORYPENCIL_OT_SyncToggleSecondary(Operator):
338 bl_idname = "storypencil.sync_toggle_secondary"
339 bl_label = "Toggle Secondary Window Status"
340 bl_description = "Enable/Disable synchronization for a specific Window"
341 bl_options = {'INTERNAL'}
343 win_id: bpy.props.StringProperty(name="Window Index")
345 def execute(self, context):
346 wm = context.window_manager
347 toggle_secondary_window(wm, self.win_id)
348 return {'FINISHED'}
351 def get_sequences_at_frame(
352 frame: int,
353 sequences: Sequence[Sequence]) -> Sequence[bpy.types.Sequence]:
354 """ Get all sequencer strips at given frame.
356 :param frame: the frame to consider
358 return [s for s in sequences if frame >= s.frame_start + s.frame_offset_start and
359 frame < s.frame_start + s.frame_offset_start + s.frame_final_duration and
360 s.type == 'SCENE']
363 def get_sequence_at_frame(
364 frame: int,
365 sequences: Sequence[bpy.types.Sequence] = None,
366 skip_muted: bool = True,
367 ) -> Tuple[bpy.types.Sequence, int]:
369 Get the higher sequence strip in channels stack at current frame.
370 Recursively enters scene sequences and returns the original frame in the
371 returned strip's time referential.
373 :param frame: the frame to consider
374 :param skip_muted: skip muted strips
375 :returns: the sequence strip and the frame in strip's time referential
378 strips = get_sequences_at_frame(frame, sequences or bpy.context.sequences)
380 # exclude muted strips
381 if skip_muted:
382 strips = [strip for strip in strips if not strip.mute]
384 if not strips:
385 return None, frame
387 # Remove strip not scene type. Switch is only with Scenes
388 for strip in strips:
389 if strip.type != 'SCENE':
390 strips.remove(strip)
392 # consider higher strip in stack
393 strip = sorted(strips, key=lambda x: x.channel)[-1]
394 # go deeper when current strip is a MetaSequence
395 if isinstance(strip, MetaSequence):
396 return get_sequence_at_frame(frame, strip.sequences, skip_muted)
397 if isinstance(strip, SceneSequence):
398 # apply time offset to get in sequence's referential
399 frame = frame - strip.frame_start + strip.scene.frame_start
400 # enter scene's sequencer if used as input
401 if strip.scene_input == 'SEQUENCER':
402 return get_sequence_at_frame(frame, strip.scene.sequence_editor.sequences)
403 return strip, frame
406 def set_scene_frame(scene, frame, force_update_main=False):
408 Set `scene` frame_current to `frame` if different.
410 :param scene: the scene to update
411 :param frame: the frame value
412 :param force_update_main: whether to force the update of main scene
414 options = bpy.context.window_manager.storypencil_settings
415 if scene.frame_current != frame:
416 scene.frame_current = int(frame)
417 scene.frame_set(int(frame))
418 if force_update_main:
419 update_sync(
420 bpy.context, bpy.context.window_manager.storypencil_settings.main_window_id)
423 def setup_window_from_scene_strip(window: Window, strip: SceneSequence):
424 """Change the Scene and camera of `window` based on `strip`.
426 :param window: [description]
427 :param scene_strip: [description]
429 if window.scene != strip.scene:
430 window.scene = strip.scene
431 if strip.scene_camera and strip.scene_camera != window.scene.camera:
432 strip.scene.camera = strip.scene_camera
435 @persistent
436 def on_frame_changed(*args):
438 React to current frame changes and synchronize secondary windows.
440 # ensure context is fully initialized, i.e not '_RestrictData
441 if not isinstance(bpy.context, Context):
442 return
444 # happens in some cases (not sure why)
445 if not bpy.context.window:
446 return
448 wm = bpy.context.window_manager
450 # early return if synchro is disabled / not available
451 if not validate_sync(wm) or len(bpy.data.scenes) < 2:
452 return
454 # get current window id
455 update_sync(bpy.context)
458 def update_sync(context: Context, win_id=None):
459 """ Update synchronized Windows based on the current `context`.
461 :param context: the context
462 :param win_id: specify a window id (context.window is used otherwise)
464 wm = context.window_manager
466 if not win_id:
467 win_id = window_id(context.window)
469 main_scene = get_window_from_id(
470 wm, wm.storypencil_settings.main_window_id).scene
471 if not main_scene.sequence_editor:
472 return
474 # return if scene's sequence editor has no sequences
475 sequences = main_scene.sequence_editor.sequences
476 if not sequences:
477 return
479 # bidirectionnal sync: change main time from secondary window
480 if (
481 win_id != wm.storypencil_settings.main_window_id
482 and is_secondary_window(wm, win_id)
484 # get strip under time cursor in main window
485 strip, old_frame = get_sequence_at_frame(
486 main_scene.frame_current,
487 sequences=sequences
489 # only do bidirectional sync if secondary window matches the strip at current time in main
490 if not isinstance(strip, SceneSequence) or strip.scene != context.scene:
491 return
493 # calculate offset
494 frame_offset = context.scene.frame_current - old_frame
495 if frame_offset == 0:
496 return
498 new_main_frame = main_scene.frame_current + frame_offset
499 update_main_time = True
500 # check if a valid scene strip is available under new frame before changing main time
501 f_start = strip.frame_start + strip.frame_offset_start
502 f_end = f_start + strip.frame_final_duration
503 if new_main_frame < f_start or new_main_frame >= f_end:
504 new_strip, _ = get_sequence_at_frame(
505 new_main_frame,
506 main_scene.sequence_editor.sequences,
508 update_main_time = isinstance(new_strip, SceneSequence)
509 if update_main_time:
510 # update main time change in the next event loop + force the sync system update
511 # because Blender won't trigger a frame_changed event to avoid infinite recursion
512 bpy.app.timers.register(
513 functools.partial(set_scene_frame, main_scene,
514 new_main_frame, True)
517 return
519 # return if current window is not main window
520 if win_id != wm.storypencil_settings.main_window_id:
521 return
523 secondary_windows = [
524 get_window_from_id(wm, win_id)
525 for win_id
526 in get_secondary_window_indices(wm)
527 if win_id and win_id != wm.storypencil_settings.main_window_id
530 # only work with at least 2 windows
531 if not secondary_windows:
532 return
534 seq, frame = get_sequence_at_frame(main_scene.frame_current, sequences)
536 # return if no sequence at current time or not a scene strip
537 if not isinstance(seq, SceneSequence) or not seq.scene:
538 wm.storypencil_settings.main_strip_name = ""
539 return
541 wm.storypencil_settings.main_strip_name = seq.name
542 # change the scene on secondary windows
543 # warning: only one window's scene can be changed in this event loop,
544 # otherwise it may crashes Blender randomly
545 for idx, win in enumerate(secondary_windows):
546 if not win:
547 continue
548 # change first secondary window immediately
549 if idx == 0:
550 setup_window_from_scene_strip(win, seq)
551 else:
552 # trigger change in next event loop for other windows
553 bpy.app.timers.register(
554 functools.partial(setup_window_from_scene_strip, win, seq)
557 set_scene_frame(seq.scene, frame)
560 def sync_all_windows(wm: WindowManager):
561 """Enable synchronization on all main windows held by `wm`."""
562 wm.storypencil_settings.secondary_windows_ids = join_win_ids([
563 window_id(w)
564 for w
565 in get_main_windows_list(wm)
569 @persistent
570 def sync_autoconfig(*args):
571 """Autoconfigure synchronization system.
572 If a window contains a VSE area on a scene with a valid sequence_editor,
573 makes it main window and enable synchronization on all other main windows.
575 main_windows = get_main_windows_list(bpy.context.window_manager)
576 # don't try to go any further if only one main window
577 if len(main_windows) < 2:
578 return
580 # look for a main window with a valid sequence editor
581 main = next(
584 for win in main_windows
585 if win.scene.sequence_editor
586 and any(area.type == 'SEQUENCE_EDITOR' for area in win.screen.areas)
588 None
590 # if any, set as main and activate sync on all other windows
591 if main:
592 bpy.context.window_manager.storypencil_settings.main_window_id = window_id(
593 main)
594 sync_all_windows(bpy.context.window_manager)
595 bpy.context.window_manager.storypencil_settings.active = True
598 def sync_active_update(self, context):
599 """ Update function for WindowManager.storypencil_settings.active. """
600 # ensure main window is valid, using current context's window if none is set
601 if (
602 self.active
603 and (
604 not self.main_window_id
605 or not get_window_from_id(context.window_manager, self.main_window_id)
608 self.main_window_id = window_id(context.window)
609 # automatically sync all other windows if nothing was previously set
610 if not self.secondary_windows_ids:
611 sync_all_windows(context.window_manager)
613 on_frame_changed()
616 def draw_sync_header(self, context):
617 """Draw Window sync tools header."""
619 wm = context.window_manager
620 self.layout.separator()
621 if wm.get('storypencil_use_new_window') is not None:
622 new_window = wm['storypencil_use_new_window']
623 else:
624 new_window = False
626 if not new_window:
627 if context.scene.storypencil_main_workspace:
628 if context.scene.storypencil_main_workspace.name != context.workspace.name:
629 if context.area.ui_type == 'DOPESHEET':
630 row = self.layout.row(align=True)
631 row.operator(STORYPENCIL_OT_Switch.bl_idname,
632 text="Back To VSE")
635 def draw_sync_sequencer_header(self, context):
636 """Draw Window sync tools header."""
637 if context.space_data.view_type != 'SEQUENCER':
638 return
640 wm = context.window_manager
641 layout = self.layout
642 layout.separator()
643 row = layout.row(align=True)
644 row.label(text="Scenes:")
645 if context.scene.storypencil_use_new_window:
646 row.operator(STORYPENCIL_OT_SetSyncMainOperator.bl_idname, text="Edit")
647 else:
648 row.operator(STORYPENCIL_OT_Switch.bl_idname, text="Edit")
650 row.separator()
651 layout.operator_context = 'INVOKE_REGION_WIN'
652 row.operator(STORYPENCIL_OT_NewScene.bl_idname, text="New")
654 layout.operator_context = 'INVOKE_DEFAULT'
655 row.separator(factor=0.5)
656 row.operator(STORYPENCIL_OT_RenderAction.bl_idname, text="Render")
659 class STORYPENCIL_PG_Settings(PropertyGroup):
661 PropertyGroup with storypencil settings.
663 active: BoolProperty(
664 name="Synchronize",
665 description=(
666 "Automatically open current Sequence's Scene in other "
667 "Main Windows and activate Time Synchronization"),
668 default=False,
669 update=sync_active_update
672 main_window_id: StringProperty(
673 name="Main Window ID",
674 description="ID of the window driving the Synchronization",
675 default="",
678 secondary_windows_ids: StringProperty(
679 name="Secondary Windows",
680 description="Serialized Secondary Window Indices",
681 default="",
684 active_window_index: IntProperty(
685 name="Active Window Index",
686 description="Index for using Window Manager's windows in a UIList",
687 default=0
690 main_strip_name: StringProperty(
691 name="Main Strip Name",
692 description="Scene Strip at current time in the Main window",
693 default="",
696 show_main_strip_range: BoolProperty(
697 name="Show Main Strip Range in Secondary Windows",
698 description="Draw main Strip's in/out markers in synchronized secondary Windows",
699 default=True,
703 # -------------------------------------------------------------
704 # Switch manually between Main and Edit Scene and Layout
706 # -------------------------------------------------------------
707 class STORYPENCIL_OT_Switch(Operator):
708 bl_idname = "storypencil.switch"
709 bl_label = "Switch"
710 bl_description = "Switch workspace"
711 bl_options = {'REGISTER', 'UNDO'}
713 # Get active strip
714 def act_strip(self, context):
715 scene = context.scene
716 sequences = scene.sequence_editor.sequences
717 if not sequences:
718 return None
719 # Get strip under time cursor
720 strip, old_frame = get_sequence_at_frame(
721 scene.frame_current, sequences=sequences)
722 return strip
724 # ------------------------------
725 # Poll
726 # ------------------------------
727 @classmethod
728 def poll(cls, context):
729 scene = context.scene
730 if scene.storypencil_main_workspace is None or scene.storypencil_main_scene is None:
731 return False
732 if scene.storypencil_edit_workspace is None:
733 return False
735 return True
737 # ------------------------------
738 # Execute button action
739 # ------------------------------
740 def execute(self, context):
741 wm = context.window_manager
742 scene = context.scene
743 wm['storypencil_use_new_window'] = scene.storypencil_use_new_window
745 # Switch to Main
746 if scene.storypencil_main_workspace.name != context.workspace.name:
747 cfra_prv = scene.frame_current
748 prv_pin = None
749 if scene.storypencil_main_workspace is not None:
750 if scene.storypencil_main_workspace.use_pin_scene:
751 scene.storypencil_main_workspace.use_pin_scene = False
753 context.window.workspace = scene.storypencil_main_workspace
755 if scene.storypencil_main_scene is not None:
756 context.window.scene = scene.storypencil_main_scene
757 strip = self.act_strip(context)
758 if strip:
759 context.window.scene.frame_current = int(cfra_prv + strip.frame_start) - 1
761 # Delete sounds
762 if scene.storypencil_copy_sounds:
763 delete_sounds(scene)
765 #bpy.ops.sequencer.reload()
766 else:
767 # Switch to Edit
768 strip = self.act_strip(context)
769 # save camera
770 if strip is not None and strip.type == "SCENE":
771 # Save data
772 strip.scene.storypencil_main_workspace = scene.storypencil_main_workspace
773 strip.scene.storypencil_main_scene = scene.storypencil_main_scene
774 strip.scene.storypencil_edit_workspace = scene.storypencil_edit_workspace
776 # Set workspace and Scene
777 cfra_prv = scene.frame_current
778 if scene.storypencil_edit_workspace.use_pin_scene:
779 scene.storypencil_edit_workspace.use_pin_scene = False
781 context.window.workspace = scene.storypencil_edit_workspace
782 context.window.workspace.update_tag()
784 context.window.scene = strip.scene
785 # Copy sounds
786 if scene.storypencil_copy_sounds:
787 send_sound_to_strip_scene(strip, clear_sequencer=True, skip_mute=scene.storypencil_skip_sound_mute)
790 active_frame = cfra_prv - strip.frame_start + 1
791 if active_frame < strip.scene.frame_start:
792 active_frame = strip.scene.frame_start
793 context.window.scene.frame_current = int(active_frame)
795 # Set camera
796 if strip.scene_input == 'CAMERA':
797 for screen in bpy.data.screens:
798 for area in screen.areas:
799 if area.type == 'VIEW_3D':
800 # select camera as view
801 if strip and strip.scene.camera is not None:
802 area.spaces.active.region_3d.view_perspective = 'CAMERA'
804 if scene.storypencil_copy_sounds:
805 bpy.ops.sequencer.reload()
807 return {"FINISHED"}
810 class STORYPENCIL_OT_TabSwitch(Operator):
811 bl_idname = "storypencil.tabswitch"
812 bl_label = "Switch using tab key"
813 bl_description = "Wrapper used to handle the Tab key to switch"
814 bl_options = {'INTERNAL'}
816 def execute(self, context):
817 # For meta strips the tab key must be processed by other operator, so
818 # just pass through to the next operator in the stack.
819 if context.active_sequence_strip and context.active_sequence_strip.type == 'META':
820 return {'PASS_THROUGH'}
822 if context.scene.sequence_editor and context.scene.sequence_editor.meta_stack:
823 return {'PASS_THROUGH'}
825 if context.scene.storypencil_use_new_window:
826 bpy.ops.storypencil.sync_set_main('INVOKE_DEFAULT', True)
827 else:
828 scene = context.scene
829 sequences = scene.sequence_editor.sequences
830 if sequences:
831 # Get strip under time cursor
832 strip, old_frame = get_sequence_at_frame(
833 scene.frame_current, sequences=sequences)
834 if strip and strip.type == 'SCENE':
835 bpy.ops.storypencil.switch('INVOKE_DEFAULT', True)
837 return {'FINISHED'}