Campbell Barton
e8da6131fd
Move copyright text to SPDX-FileCopyrightText or set to the Blender Foundation so "make check_licenses" now runs without warnings.
838 lines
29 KiB
Python
838 lines
29 KiB
Python
# SPDX-FileCopyrightText: 2022-2023 Blender Foundation
|
|
#
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
from typing import List, Sequence, Tuple
|
|
|
|
import bpy
|
|
import functools
|
|
import os
|
|
from bpy.app.handlers import persistent
|
|
|
|
from bpy.types import (
|
|
Context,
|
|
MetaSequence,
|
|
Operator,
|
|
PropertyGroup,
|
|
SceneSequence,
|
|
Window,
|
|
WindowManager,
|
|
)
|
|
from bpy.props import (
|
|
BoolProperty,
|
|
IntProperty,
|
|
StringProperty,
|
|
)
|
|
from .scene_tools import STORYPENCIL_OT_NewScene
|
|
from .render import STORYPENCIL_OT_RenderAction
|
|
from .sound import delete_sounds, send_sound_to_strip_scene
|
|
|
|
def window_id(window: Window) -> str:
|
|
""" Get Window's ID.
|
|
|
|
:param window: the Window to consider
|
|
:return: the Window's ID
|
|
"""
|
|
return str(window.as_pointer())
|
|
|
|
|
|
def get_window_from_id(wm: WindowManager, win_id: str) -> Window:
|
|
"""Get a Window object from its ID (serialized ptr).
|
|
|
|
:param wm: a WindowManager holding Windows
|
|
:param win_id: the ID of the Window to get
|
|
:return: the Window matching the given ID, None otherwise
|
|
"""
|
|
return next((w for w in wm.windows if w and window_id(w) == win_id), None)
|
|
|
|
|
|
def get_main_windows_list(wm: WindowManager) -> Sequence[Window]:
|
|
"""Get all the Main Windows held by the given WindowManager `wm`"""
|
|
return [w for w in wm.windows if w and w.parent is None]
|
|
|
|
|
|
def join_win_ids(ids: List[str]) -> str:
|
|
"""Join Windows IDs in a single string"""
|
|
return ";".join(ids)
|
|
|
|
|
|
def split_win_ids(ids: str) -> List[str]:
|
|
"""Split a Windows IDs string into individual IDs"""
|
|
return ids.split(";")
|
|
|
|
|
|
class STORYPENCIL_OT_SetSyncMainOperator(Operator):
|
|
bl_idname = "storypencil.sync_set_main"
|
|
bl_label = "Set as Sync Main"
|
|
bl_description = "Set this Window as main for Synchronization"
|
|
bl_options = {'INTERNAL'}
|
|
|
|
win_id: bpy.props.StringProperty(
|
|
name="Window ID",
|
|
default="",
|
|
options=set(),
|
|
description="Main window ID",
|
|
)
|
|
|
|
def copy_settings(self, main_window, secondary_window):
|
|
if main_window is None or secondary_window is None:
|
|
return
|
|
secondary_window.scene.storypencil_main_workspace = main_window.scene.storypencil_main_workspace
|
|
secondary_window.scene.storypencil_main_scene = main_window.scene.storypencil_main_scene
|
|
secondary_window.scene.storypencil_edit_workspace = main_window.scene.storypencil_edit_workspace
|
|
|
|
def execute(self, context):
|
|
options = context.window_manager.storypencil_settings
|
|
options.main_window_id = self.win_id
|
|
wm = bpy.context.window_manager
|
|
scene = context.scene
|
|
wm['storypencil_use_new_window'] = scene.storypencil_use_new_window
|
|
|
|
main_windows = get_main_windows_list(wm)
|
|
main_window = get_main_window(wm)
|
|
secondary_window = get_secondary_window(wm)
|
|
# Active sync
|
|
options.active = True
|
|
if secondary_window is None:
|
|
# Open a new window
|
|
if len(main_windows) < 2:
|
|
bpy.ops.storypencil.create_secondary_window()
|
|
secondary_window = get_secondary_window(wm)
|
|
self.copy_settings(get_main_window(wm), secondary_window)
|
|
return {'FINISHED'}
|
|
else:
|
|
# Reuse the existing window
|
|
secondary_window = get_not_main_window(wm)
|
|
else:
|
|
# Open new secondary
|
|
if len(main_windows) < 2:
|
|
bpy.ops.storypencil.create_secondary_window()
|
|
secondary_window = get_secondary_window(wm)
|
|
self.copy_settings(get_main_window(wm), secondary_window)
|
|
return {'FINISHED'}
|
|
else:
|
|
# Reuse the existing window
|
|
secondary_window = get_not_main_window(wm)
|
|
|
|
if secondary_window:
|
|
enable_secondary_window(wm, window_id(secondary_window))
|
|
win_id = window_id(secondary_window)
|
|
self.copy_settings(get_main_window(wm), secondary_window)
|
|
bpy.ops.storypencil.sync_window_bring_front(win_id=win_id)
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class STORYPENCIL_OT_AddSecondaryWindowOperator(Operator):
|
|
bl_idname = "storypencil.create_secondary_window"
|
|
bl_label = "Create Secondary Window"
|
|
bl_description = "Create a Secondary Main Window and enable Synchronization"
|
|
bl_options = {'INTERNAL'}
|
|
|
|
def execute(self, context):
|
|
# store existing windows
|
|
windows = set(context.window_manager.windows[:])
|
|
bpy.ops.wm.window_new_main()
|
|
# get newly created window by comparing to previous list
|
|
new_window = (set(context.window_manager.windows[:]) - windows).pop()
|
|
# activate sync system and enable sync for this window
|
|
toggle_secondary_window(context.window_manager, window_id(new_window))
|
|
context.window_manager.storypencil_settings.active = True
|
|
# trigger initial synchronization to open the current Sequence's Scene
|
|
on_frame_changed()
|
|
# Configure the new window
|
|
self.configure_new_secondary_window(context, new_window)
|
|
|
|
return {'FINISHED'}
|
|
|
|
def configure_new_secondary_window(self, context, new_window):
|
|
wrk_name = context.scene.storypencil_edit_workspace.name
|
|
# Open the 2D workspace
|
|
blendpath = os.path.dirname(bpy.app.binary_path)
|
|
version = bpy.app.version
|
|
version_full = str(version[0]) + '.' + str(version[1])
|
|
template = os.path.join("scripts", "startup",
|
|
"bl_app_templates_system")
|
|
template = os.path.join(template, wrk_name, "startup.blend")
|
|
template_path = os.path.join(blendpath, version_full, template)
|
|
# Check if workspace exist and add it if missing
|
|
for wk in bpy.data.workspaces:
|
|
if wk.name == wrk_name:
|
|
new_window.workspace = wk
|
|
return
|
|
with context.temp_override(window=new_window):
|
|
bpy.ops.workspace.append_activate(idname=wrk_name, filepath=template_path)
|
|
|
|
|
|
class STORYPENCIL_OT_WindowBringFront(Operator):
|
|
bl_idname = "storypencil.sync_window_bring_front"
|
|
bl_label = "Bring Window Front"
|
|
bl_description = "Bring a Window to Front"
|
|
bl_options = {'INTERNAL'}
|
|
|
|
win_id: bpy.props.StringProperty()
|
|
|
|
def execute(self, context):
|
|
win = get_window_from_id(context.window_manager, self.win_id)
|
|
if not win:
|
|
return {'CANCELLED'}
|
|
with context.temp_override(window=win):
|
|
bpy.ops.wm.window_fullscreen_toggle()
|
|
bpy.ops.wm.window_fullscreen_toggle()
|
|
return {'FINISHED'}
|
|
|
|
|
|
class STORYPENCIL_OT_WindowCloseOperator(Operator):
|
|
bl_idname = "storypencil.close_secondary_window"
|
|
bl_label = "Close Window"
|
|
bl_description = "Close a specific Window"
|
|
bl_options = {'INTERNAL'}
|
|
|
|
win_id: bpy.props.StringProperty()
|
|
|
|
def execute(self, context):
|
|
win = get_window_from_id(context.window_manager, self.win_id)
|
|
if not win:
|
|
return {'CANCELLED'}
|
|
with context.temp_override(window=win):
|
|
bpy.ops.wm.window_close()
|
|
return {'FINISHED'}
|
|
|
|
|
|
def validate_sync(window_manager: WindowManager) -> bool:
|
|
"""
|
|
Ensure synchronization system is functional, with a valid main window.
|
|
Disable it otherwise and return the system status.
|
|
"""
|
|
if not window_manager.storypencil_settings.active:
|
|
return False
|
|
if not get_window_from_id(window_manager, window_manager.storypencil_settings.main_window_id):
|
|
window_manager.storypencil_settings.active = False
|
|
return window_manager.storypencil_settings.active
|
|
|
|
|
|
def get_secondary_window_indices(wm: WindowManager) -> List[str]:
|
|
"""Get secondary Windows indices as a list of IDs
|
|
|
|
:param wm: the WindowManager to consider
|
|
:return: the list of secondary Windows IDs
|
|
"""
|
|
return split_win_ids(wm.storypencil_settings.secondary_windows_ids)
|
|
|
|
|
|
def is_secondary_window(window_manager: WindowManager, win_id: str) -> bool:
|
|
"""Return wether the Window identified by 'win_id' is a secondary window.
|
|
|
|
:return: whether this Window is a sync secondary
|
|
"""
|
|
return win_id in get_secondary_window_indices(window_manager)
|
|
|
|
|
|
def enable_secondary_window(wm: WindowManager, win_id: str):
|
|
"""Enable the secondary status of a Window.
|
|
|
|
:param wm: the WindowManager instance
|
|
:param win_id: the id of the window
|
|
"""
|
|
secondary_indices = get_secondary_window_indices(wm)
|
|
win_id_str = win_id
|
|
# Delete old indice if exist
|
|
if win_id_str in secondary_indices:
|
|
secondary_indices.remove(win_id_str)
|
|
|
|
# Add indice
|
|
secondary_indices.append(win_id_str)
|
|
|
|
# rebuild the whole list of valid secondary windows
|
|
secondary_indices = [
|
|
idx for idx in secondary_indices if get_window_from_id(wm, idx)]
|
|
|
|
wm.storypencil_settings.secondary_windows_ids = join_win_ids(secondary_indices)
|
|
|
|
|
|
def toggle_secondary_window(wm: WindowManager, win_id: str):
|
|
"""Toggle the secondary status of a Window.
|
|
|
|
:param wm: the WindowManager instance
|
|
:param win_id: the id of the window
|
|
"""
|
|
secondary_indices = get_secondary_window_indices(wm)
|
|
win_id_str = win_id
|
|
if win_id_str in secondary_indices:
|
|
secondary_indices.remove(win_id_str)
|
|
else:
|
|
secondary_indices.append(win_id_str)
|
|
|
|
# rebuild the whole list of valid secondary windows
|
|
secondary_indices = [
|
|
idx for idx in secondary_indices if get_window_from_id(wm, idx)]
|
|
|
|
wm.storypencil_settings.secondary_windows_ids = join_win_ids(secondary_indices)
|
|
|
|
|
|
def get_main_window(wm: WindowManager) -> Window:
|
|
"""Get the Window used to drive the synchronization system
|
|
|
|
:param wm: the WindowManager instance
|
|
:returns: the main Window or None
|
|
"""
|
|
return get_window_from_id(wm=wm, win_id=wm.storypencil_settings.main_window_id)
|
|
|
|
|
|
def get_secondary_window(wm: WindowManager) -> Window:
|
|
"""Get the first secondary Window
|
|
|
|
:param wm: the WindowManager instance
|
|
:returns: the Window or None
|
|
"""
|
|
for w in wm.windows:
|
|
win_id = window_id(w)
|
|
if is_secondary_window(wm, win_id):
|
|
return w
|
|
|
|
return None
|
|
|
|
|
|
def get_not_main_window(wm: WindowManager) -> Window:
|
|
"""Get the first not main Window
|
|
|
|
:param wm: the WindowManager instance
|
|
:returns: the Window or None
|
|
"""
|
|
for w in wm.windows:
|
|
win_id = window_id(w)
|
|
if win_id != wm.storypencil_settings.main_window_id:
|
|
return w
|
|
|
|
return None
|
|
|
|
|
|
def get_main_strip(wm: WindowManager) -> SceneSequence:
|
|
"""Get Scene Strip at current time in Main window
|
|
|
|
:param wm: the WindowManager instance
|
|
:returns: the Strip at current time or None
|
|
"""
|
|
context = bpy.context
|
|
scene = context.scene
|
|
main_scene = scene.storypencil_main_scene
|
|
|
|
if main_scene.storypencil_use_new_window:
|
|
main_window = get_main_window(wm=wm)
|
|
if not main_window or not main_window.scene.sequence_editor:
|
|
return None
|
|
seq_editor = main_window.scene.sequence_editor
|
|
return seq_editor.sequences.get(wm.storypencil_settings.main_strip_name, None)
|
|
else:
|
|
seq_editor = main_scene.sequence_editor.sequences
|
|
for strip in seq_editor:
|
|
if strip.type != 'SCENE':
|
|
continue
|
|
if strip.scene.name == scene.name:
|
|
return strip
|
|
|
|
return None
|
|
|
|
|
|
class STORYPENCIL_OT_SyncToggleSecondary(Operator):
|
|
bl_idname = "storypencil.sync_toggle_secondary"
|
|
bl_label = "Toggle Secondary Window Status"
|
|
bl_description = "Enable/Disable synchronization for a specific Window"
|
|
bl_options = {'INTERNAL'}
|
|
|
|
win_id: bpy.props.StringProperty(name="Window Index")
|
|
|
|
def execute(self, context):
|
|
wm = context.window_manager
|
|
toggle_secondary_window(wm, self.win_id)
|
|
return {'FINISHED'}
|
|
|
|
|
|
def get_sequences_at_frame(
|
|
frame: int,
|
|
sequences: Sequence[Sequence]) -> Sequence[bpy.types.Sequence]:
|
|
""" Get all sequencer strips at given frame.
|
|
|
|
:param frame: the frame to consider
|
|
"""
|
|
return [s for s in sequences if frame >= s.frame_start + s.frame_offset_start and
|
|
frame < s.frame_start + s.frame_offset_start + s.frame_final_duration and
|
|
s.type == 'SCENE']
|
|
|
|
|
|
def get_sequence_at_frame(
|
|
frame: int,
|
|
sequences: Sequence[bpy.types.Sequence] = None,
|
|
skip_muted: bool = True,
|
|
) -> Tuple[bpy.types.Sequence, int]:
|
|
"""
|
|
Get the higher sequence strip in channels stack at current frame.
|
|
Recursively enters scene sequences and returns the original frame in the
|
|
returned strip's time referential.
|
|
|
|
:param frame: the frame to consider
|
|
:param skip_muted: skip muted strips
|
|
:returns: the sequence strip and the frame in strip's time referential
|
|
"""
|
|
|
|
strips = get_sequences_at_frame(frame, sequences or bpy.context.sequences)
|
|
|
|
# exclude muted strips
|
|
if skip_muted:
|
|
strips = [strip for strip in strips if not strip.mute]
|
|
|
|
if not strips:
|
|
return None, frame
|
|
|
|
# Remove strip not scene type. Switch is only with Scenes
|
|
for strip in strips:
|
|
if strip.type != 'SCENE':
|
|
strips.remove(strip)
|
|
|
|
# consider higher strip in stack
|
|
strip = sorted(strips, key=lambda x: x.channel)[-1]
|
|
# go deeper when current strip is a MetaSequence
|
|
if isinstance(strip, MetaSequence):
|
|
return get_sequence_at_frame(frame, strip.sequences, skip_muted)
|
|
if isinstance(strip, SceneSequence):
|
|
# apply time offset to get in sequence's referential
|
|
frame = frame - strip.frame_start + strip.scene.frame_start
|
|
# enter scene's sequencer if used as input
|
|
if strip.scene_input == 'SEQUENCER':
|
|
return get_sequence_at_frame(frame, strip.scene.sequence_editor.sequences)
|
|
return strip, frame
|
|
|
|
|
|
def set_scene_frame(scene, frame, force_update_main=False):
|
|
"""
|
|
Set `scene` frame_current to `frame` if different.
|
|
|
|
:param scene: the scene to update
|
|
:param frame: the frame value
|
|
:param force_update_main: whether to force the update of main scene
|
|
"""
|
|
options = bpy.context.window_manager.storypencil_settings
|
|
if scene.frame_current != frame:
|
|
scene.frame_current = int(frame)
|
|
scene.frame_set(int(frame))
|
|
if force_update_main:
|
|
update_sync(
|
|
bpy.context, bpy.context.window_manager.storypencil_settings.main_window_id)
|
|
|
|
|
|
def setup_window_from_scene_strip(window: Window, strip: SceneSequence):
|
|
"""Change the Scene and camera of `window` based on `strip`.
|
|
|
|
:param window: [description]
|
|
:param scene_strip: [description]
|
|
"""
|
|
if window.scene != strip.scene:
|
|
window.scene = strip.scene
|
|
if strip.scene_camera and strip.scene_camera != window.scene.camera:
|
|
strip.scene.camera = strip.scene_camera
|
|
|
|
|
|
@persistent
|
|
def on_frame_changed(*args):
|
|
"""
|
|
React to current frame changes and synchronize secondary windows.
|
|
"""
|
|
# ensure context is fully initialized, i.e not '_RestrictData
|
|
if not isinstance(bpy.context, Context):
|
|
return
|
|
|
|
# happens in some cases (not sure why)
|
|
if not bpy.context.window:
|
|
return
|
|
|
|
wm = bpy.context.window_manager
|
|
|
|
# early return if synchro is disabled / not available
|
|
if not validate_sync(wm) or len(bpy.data.scenes) < 2:
|
|
return
|
|
|
|
# get current window id
|
|
update_sync(bpy.context)
|
|
|
|
|
|
def update_sync(context: Context, win_id=None):
|
|
""" Update synchronized Windows based on the current `context`.
|
|
|
|
:param context: the context
|
|
:param win_id: specify a window id (context.window is used otherwise)
|
|
"""
|
|
wm = context.window_manager
|
|
|
|
if not win_id:
|
|
win_id = window_id(context.window)
|
|
|
|
main_scene = get_window_from_id(
|
|
wm, wm.storypencil_settings.main_window_id).scene
|
|
if not main_scene.sequence_editor:
|
|
return
|
|
|
|
# return if scene's sequence editor has no sequences
|
|
sequences = main_scene.sequence_editor.sequences
|
|
if not sequences:
|
|
return
|
|
|
|
# bidirectionnal sync: change main time from secondary window
|
|
if (
|
|
win_id != wm.storypencil_settings.main_window_id
|
|
and is_secondary_window(wm, win_id)
|
|
):
|
|
# get strip under time cursor in main window
|
|
strip, old_frame = get_sequence_at_frame(
|
|
main_scene.frame_current,
|
|
sequences=sequences
|
|
)
|
|
# only do bidirectional sync if secondary window matches the strip at current time in main
|
|
if not isinstance(strip, SceneSequence) or strip.scene != context.scene:
|
|
return
|
|
|
|
# calculate offset
|
|
frame_offset = context.scene.frame_current - old_frame
|
|
if frame_offset == 0:
|
|
return
|
|
|
|
new_main_frame = main_scene.frame_current + frame_offset
|
|
update_main_time = True
|
|
# check if a valid scene strip is available under new frame before changing main time
|
|
f_start = strip.frame_start + strip.frame_offset_start
|
|
f_end = f_start + strip.frame_final_duration
|
|
if new_main_frame < f_start or new_main_frame >= f_end:
|
|
new_strip, _ = get_sequence_at_frame(
|
|
new_main_frame,
|
|
main_scene.sequence_editor.sequences,
|
|
)
|
|
update_main_time = isinstance(new_strip, SceneSequence)
|
|
if update_main_time:
|
|
# update main time change in the next event loop + force the sync system update
|
|
# because Blender won't trigger a frame_changed event to avoid infinite recursion
|
|
bpy.app.timers.register(
|
|
functools.partial(set_scene_frame, main_scene,
|
|
new_main_frame, True)
|
|
)
|
|
|
|
return
|
|
|
|
# return if current window is not main window
|
|
if win_id != wm.storypencil_settings.main_window_id:
|
|
return
|
|
|
|
secondary_windows = [
|
|
get_window_from_id(wm, win_id)
|
|
for win_id
|
|
in get_secondary_window_indices(wm)
|
|
if win_id and win_id != wm.storypencil_settings.main_window_id
|
|
]
|
|
|
|
# only work with at least 2 windows
|
|
if not secondary_windows:
|
|
return
|
|
|
|
seq, frame = get_sequence_at_frame(main_scene.frame_current, sequences)
|
|
|
|
# return if no sequence at current time or not a scene strip
|
|
if not isinstance(seq, SceneSequence) or not seq.scene:
|
|
wm.storypencil_settings.main_strip_name = ""
|
|
return
|
|
|
|
wm.storypencil_settings.main_strip_name = seq.name
|
|
# change the scene on secondary windows
|
|
# warning: only one window's scene can be changed in this event loop,
|
|
# otherwise it may crashes Blender randomly
|
|
for idx, win in enumerate(secondary_windows):
|
|
if not win:
|
|
continue
|
|
# change first secondary window immediately
|
|
if idx == 0:
|
|
setup_window_from_scene_strip(win, seq)
|
|
else:
|
|
# trigger change in next event loop for other windows
|
|
bpy.app.timers.register(
|
|
functools.partial(setup_window_from_scene_strip, win, seq)
|
|
)
|
|
|
|
set_scene_frame(seq.scene, frame)
|
|
|
|
|
|
def sync_all_windows(wm: WindowManager):
|
|
"""Enable synchronization on all main windows held by `wm`."""
|
|
wm.storypencil_settings.secondary_windows_ids = join_win_ids([
|
|
window_id(w)
|
|
for w
|
|
in get_main_windows_list(wm)
|
|
])
|
|
|
|
|
|
@persistent
|
|
def sync_autoconfig(*args):
|
|
"""Autoconfigure synchronization system.
|
|
If a window contains a VSE area on a scene with a valid sequence_editor,
|
|
makes it main window and enable synchronization on all other main windows.
|
|
"""
|
|
main_windows = get_main_windows_list(bpy.context.window_manager)
|
|
# don't try to go any further if only one main window
|
|
if len(main_windows) < 2:
|
|
return
|
|
|
|
# look for a main window with a valid sequence editor
|
|
main = next(
|
|
(
|
|
win
|
|
for win in main_windows
|
|
if win.scene.sequence_editor
|
|
and any(area.type == 'SEQUENCE_EDITOR' for area in win.screen.areas)
|
|
),
|
|
None
|
|
)
|
|
# if any, set as main and activate sync on all other windows
|
|
if main:
|
|
bpy.context.window_manager.storypencil_settings.main_window_id = window_id(
|
|
main)
|
|
sync_all_windows(bpy.context.window_manager)
|
|
bpy.context.window_manager.storypencil_settings.active = True
|
|
|
|
|
|
def sync_active_update(self, context):
|
|
""" Update function for WindowManager.storypencil_settings.active. """
|
|
# ensure main window is valid, using current context's window if none is set
|
|
if (
|
|
self.active
|
|
and (
|
|
not self.main_window_id
|
|
or not get_window_from_id(context.window_manager, self.main_window_id)
|
|
)
|
|
):
|
|
self.main_window_id = window_id(context.window)
|
|
# automatically sync all other windows if nothing was previously set
|
|
if not self.secondary_windows_ids:
|
|
sync_all_windows(context.window_manager)
|
|
|
|
on_frame_changed()
|
|
|
|
|
|
def draw_sync_header(self, context):
|
|
"""Draw Window sync tools header."""
|
|
|
|
wm = context.window_manager
|
|
self.layout.separator()
|
|
if wm.get('storypencil_use_new_window') is not None:
|
|
new_window = wm['storypencil_use_new_window']
|
|
else:
|
|
new_window = False
|
|
|
|
if not new_window:
|
|
if context.scene.storypencil_main_workspace:
|
|
if context.scene.storypencil_main_workspace.name != context.workspace.name:
|
|
if context.area.ui_type == 'DOPESHEET':
|
|
row = self.layout.row(align=True)
|
|
row.operator(STORYPENCIL_OT_Switch.bl_idname,
|
|
text="Back To VSE")
|
|
|
|
|
|
def draw_sync_sequencer_header(self, context):
|
|
"""Draw Window sync tools header."""
|
|
if context.space_data.view_type != 'SEQUENCER':
|
|
return
|
|
|
|
wm = context.window_manager
|
|
layout = self.layout
|
|
layout.separator()
|
|
row = layout.row(align=True)
|
|
row.label(text="Scenes:")
|
|
if context.scene.storypencil_use_new_window:
|
|
row.operator(STORYPENCIL_OT_SetSyncMainOperator.bl_idname, text="Edit")
|
|
else:
|
|
row.operator(STORYPENCIL_OT_Switch.bl_idname, text="Edit")
|
|
|
|
row.separator()
|
|
layout.operator_context = 'INVOKE_REGION_WIN'
|
|
row.operator(STORYPENCIL_OT_NewScene.bl_idname, text="New")
|
|
|
|
layout.operator_context = 'INVOKE_DEFAULT'
|
|
row.separator(factor=0.5)
|
|
row.operator(STORYPENCIL_OT_RenderAction.bl_idname, text="Render")
|
|
|
|
|
|
class STORYPENCIL_PG_Settings(PropertyGroup):
|
|
"""
|
|
PropertyGroup with storypencil settings.
|
|
"""
|
|
active: BoolProperty(
|
|
name="Synchronize",
|
|
description=(
|
|
"Automatically open current Sequence's Scene in other "
|
|
"Main Windows and activate Time Synchronization"),
|
|
default=False,
|
|
update=sync_active_update
|
|
)
|
|
|
|
main_window_id: StringProperty(
|
|
name="Main Window ID",
|
|
description="ID of the window driving the Synchronization",
|
|
default="",
|
|
)
|
|
|
|
secondary_windows_ids: StringProperty(
|
|
name="Secondary Windows",
|
|
description="Serialized Secondary Window Indices",
|
|
default="",
|
|
)
|
|
|
|
active_window_index: IntProperty(
|
|
name="Active Window Index",
|
|
description="Index for using Window Manager's windows in a UIList",
|
|
default=0
|
|
)
|
|
|
|
main_strip_name: StringProperty(
|
|
name="Main Strip Name",
|
|
description="Scene Strip at current time in the Main window",
|
|
default="",
|
|
)
|
|
|
|
show_main_strip_range: BoolProperty(
|
|
name="Show Main Strip Range in Secondary Windows",
|
|
description="Draw main Strip's in/out markers in synchronized secondary Windows",
|
|
default=True,
|
|
)
|
|
|
|
|
|
# -------------------------------------------------------------
|
|
# Switch manually between Main and Edit Scene and Layout
|
|
#
|
|
# -------------------------------------------------------------
|
|
class STORYPENCIL_OT_Switch(Operator):
|
|
bl_idname = "storypencil.switch"
|
|
bl_label = "Switch"
|
|
bl_description = "Switch workspace"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
# Get active strip
|
|
def act_strip(self, context):
|
|
scene = context.scene
|
|
sequences = scene.sequence_editor.sequences
|
|
if not sequences:
|
|
return None
|
|
# Get strip under time cursor
|
|
strip, old_frame = get_sequence_at_frame(
|
|
scene.frame_current, sequences=sequences)
|
|
return strip
|
|
|
|
# ------------------------------
|
|
# Poll
|
|
# ------------------------------
|
|
@classmethod
|
|
def poll(cls, context):
|
|
scene = context.scene
|
|
if scene.storypencil_main_workspace is None or scene.storypencil_main_scene is None:
|
|
return False
|
|
if scene.storypencil_edit_workspace is None:
|
|
return False
|
|
|
|
return True
|
|
|
|
# ------------------------------
|
|
# Execute button action
|
|
# ------------------------------
|
|
def execute(self, context):
|
|
wm = context.window_manager
|
|
scene = context.scene
|
|
wm['storypencil_use_new_window'] = scene.storypencil_use_new_window
|
|
|
|
# Switch to Main
|
|
if scene.storypencil_main_workspace.name != context.workspace.name:
|
|
cfra_prv = scene.frame_current
|
|
prv_pin = None
|
|
if scene.storypencil_main_workspace is not None:
|
|
if scene.storypencil_main_workspace.use_pin_scene:
|
|
scene.storypencil_main_workspace.use_pin_scene = False
|
|
|
|
context.window.workspace = scene.storypencil_main_workspace
|
|
|
|
if scene.storypencil_main_scene is not None:
|
|
context.window.scene = scene.storypencil_main_scene
|
|
strip = self.act_strip(context)
|
|
if strip:
|
|
context.window.scene.frame_current = int(cfra_prv + strip.frame_start) - 1
|
|
|
|
# Delete sounds
|
|
if scene.storypencil_copy_sounds:
|
|
delete_sounds(scene)
|
|
|
|
#bpy.ops.sequencer.reload()
|
|
else:
|
|
# Switch to Edit
|
|
strip = self.act_strip(context)
|
|
# save camera
|
|
if strip is not None and strip.type == "SCENE":
|
|
# Save data
|
|
strip.scene.storypencil_main_workspace = scene.storypencil_main_workspace
|
|
strip.scene.storypencil_main_scene = scene.storypencil_main_scene
|
|
strip.scene.storypencil_edit_workspace = scene.storypencil_edit_workspace
|
|
|
|
# Set workspace and Scene
|
|
cfra_prv = scene.frame_current
|
|
if scene.storypencil_edit_workspace.use_pin_scene:
|
|
scene.storypencil_edit_workspace.use_pin_scene = False
|
|
|
|
context.window.workspace = scene.storypencil_edit_workspace
|
|
context.window.workspace.update_tag()
|
|
|
|
context.window.scene = strip.scene
|
|
# Copy sounds
|
|
if scene.storypencil_copy_sounds:
|
|
send_sound_to_strip_scene(strip, clear_sequencer=True, skip_mute=scene.storypencil_skip_sound_mute)
|
|
|
|
|
|
active_frame = cfra_prv - strip.frame_start + 1
|
|
if active_frame < strip.scene.frame_start:
|
|
active_frame = strip.scene.frame_start
|
|
context.window.scene.frame_current = int(active_frame)
|
|
|
|
# Set camera
|
|
if strip.scene_input == 'CAMERA':
|
|
for screen in bpy.data.screens:
|
|
for area in screen.areas:
|
|
if area.type == 'VIEW_3D':
|
|
# select camera as view
|
|
if strip and strip.scene.camera is not None:
|
|
area.spaces.active.region_3d.view_perspective = 'CAMERA'
|
|
|
|
if scene.storypencil_copy_sounds:
|
|
bpy.ops.sequencer.reload()
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
class STORYPENCIL_OT_TabSwitch(Operator):
|
|
bl_idname = "storypencil.tabswitch"
|
|
bl_label = "Switch using tab key"
|
|
bl_description = "Wrapper used to handle the Tab key to switch"
|
|
bl_options = {'INTERNAL'}
|
|
|
|
def execute(self, context):
|
|
# For meta strips the tab key must be processed by other operator, so
|
|
# just pass through to the next operator in the stack.
|
|
if context.active_sequence_strip and context.active_sequence_strip.type == 'META':
|
|
return {'PASS_THROUGH'}
|
|
|
|
if context.scene.sequence_editor and context.scene.sequence_editor.meta_stack:
|
|
return {'PASS_THROUGH'}
|
|
|
|
if context.scene.storypencil_use_new_window:
|
|
bpy.ops.storypencil.sync_set_main('INVOKE_DEFAULT', True)
|
|
else:
|
|
scene = context.scene
|
|
sequences = scene.sequence_editor.sequences
|
|
if sequences:
|
|
# Get strip under time cursor
|
|
strip, old_frame = get_sequence_at_frame(
|
|
scene.frame_current, sequences=sequences)
|
|
if strip and strip.type == 'SCENE':
|
|
bpy.ops.storypencil.switch('INVOKE_DEFAULT', True)
|
|
|
|
return {'FINISHED'}
|