io_scene_3ds: Update for Blender 3.x #2
@ -1,239 +0,0 @@
|
|||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
# ----------------------------------------------
|
|
||||||
# Define Addon info
|
|
||||||
# ----------------------------------------------
|
|
||||||
bl_info = {
|
|
||||||
"name": "Storypencil - Storyboard Tools",
|
|
||||||
"description": "Storyboard tools",
|
|
||||||
"author": "Antonio Vazquez, Matias Mendiola, Daniel Martinez Lara, Rodrigo Blaas",
|
|
||||||
"version": (1, 1, 1),
|
|
||||||
"blender": (3, 3, 0),
|
|
||||||
"location": "",
|
|
||||||
"warning": "",
|
|
||||||
"category": "Sequencer",
|
|
||||||
}
|
|
||||||
|
|
||||||
# ----------------------------------------------
|
|
||||||
# Import modules
|
|
||||||
# ----------------------------------------------
|
|
||||||
if "bpy" in locals():
|
|
||||||
import importlib
|
|
||||||
|
|
||||||
importlib.reload(utils)
|
|
||||||
importlib.reload(synchro)
|
|
||||||
importlib.reload(dopesheet_overlay)
|
|
||||||
importlib.reload(scene_tools)
|
|
||||||
importlib.reload(render)
|
|
||||||
importlib.reload(ui)
|
|
||||||
else:
|
|
||||||
from . import utils
|
|
||||||
from . import synchro
|
|
||||||
from . import dopesheet_overlay
|
|
||||||
from . import scene_tools
|
|
||||||
from . import render
|
|
||||||
from . import ui
|
|
||||||
|
|
||||||
import bpy
|
|
||||||
from bpy.types import (
|
|
||||||
Scene,
|
|
||||||
WindowManager,
|
|
||||||
WorkSpace,
|
|
||||||
)
|
|
||||||
from bpy.props import (
|
|
||||||
BoolProperty,
|
|
||||||
IntProperty,
|
|
||||||
PointerProperty,
|
|
||||||
StringProperty,
|
|
||||||
EnumProperty,
|
|
||||||
)
|
|
||||||
|
|
||||||
# --------------------------------------------------------------
|
|
||||||
# Register all operators, props and panels
|
|
||||||
# --------------------------------------------------------------
|
|
||||||
classes = (
|
|
||||||
synchro.STORYPENCIL_PG_Settings,
|
|
||||||
scene_tools.STORYPENCIL_OT_Setup,
|
|
||||||
scene_tools.STORYPENCIL_OT_NewScene,
|
|
||||||
synchro.STORYPENCIL_OT_WindowBringFront,
|
|
||||||
synchro.STORYPENCIL_OT_WindowCloseOperator,
|
|
||||||
synchro.STORYPENCIL_OT_SyncToggleSecondary,
|
|
||||||
synchro.STORYPENCIL_OT_SetSyncMainOperator,
|
|
||||||
synchro.STORYPENCIL_OT_AddSecondaryWindowOperator,
|
|
||||||
synchro.STORYPENCIL_OT_Switch,
|
|
||||||
synchro.STORYPENCIL_OT_TabSwitch,
|
|
||||||
render.STORYPENCIL_OT_RenderAction,
|
|
||||||
ui.STORYPENCIL_PT_Settings,
|
|
||||||
ui.STORYPENCIL_PT_SettingsNew,
|
|
||||||
ui.STORYPENCIL_PT_RenderPanel,
|
|
||||||
ui.STORYPENCIL_PT_General,
|
|
||||||
ui.STORYPENCIL_MT_extra_options,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def save_mode(self, context):
|
|
||||||
wm = context.window_manager
|
|
||||||
wm['storypencil_use_new_window'] = context.scene.storypencil_use_new_window
|
|
||||||
# Close all secondary windows
|
|
||||||
if context.scene.storypencil_use_new_window is False:
|
|
||||||
c = context.copy()
|
|
||||||
for win in context.window_manager.windows:
|
|
||||||
# Don't close actual window
|
|
||||||
if win == context.window:
|
|
||||||
continue
|
|
||||||
win_id = str(win.as_pointer())
|
|
||||||
if win_id != wm.storypencil_settings.main_window_id and win.parent is None:
|
|
||||||
c["window"] = win
|
|
||||||
bpy.ops.wm.window_close(c)
|
|
||||||
|
|
||||||
|
|
||||||
addon_keymaps = []
|
|
||||||
def register_keymaps():
|
|
||||||
addon = bpy.context.window_manager.keyconfigs.addon
|
|
||||||
km = addon.keymaps.new(name="Sequencer", space_type="SEQUENCE_EDITOR")
|
|
||||||
kmi = km.keymap_items.new(
|
|
||||||
idname="storypencil.tabswitch",
|
|
||||||
type="TAB",
|
|
||||||
value="PRESS",
|
|
||||||
shift=False, ctrl=False, alt = False, oskey=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
addon_keymaps.append((km, kmi))
|
|
||||||
|
|
||||||
def unregister_keymaps():
|
|
||||||
for km, kmi in addon_keymaps:
|
|
||||||
km.keymap_items.remove(kmi)
|
|
||||||
addon_keymaps.clear()
|
|
||||||
|
|
||||||
def register():
|
|
||||||
from bpy.utils import register_class
|
|
||||||
for cls in classes:
|
|
||||||
register_class(cls)
|
|
||||||
register_keymaps()
|
|
||||||
|
|
||||||
Scene.storypencil_scene_duration = IntProperty(
|
|
||||||
name="Scene Duration",
|
|
||||||
description="Default Duration for new Scene",
|
|
||||||
default=48,
|
|
||||||
min=1,
|
|
||||||
soft_max=250,
|
|
||||||
)
|
|
||||||
|
|
||||||
Scene.storypencil_use_new_window = BoolProperty(name="Open in new window",
|
|
||||||
description="Use secondary main window to edit scenes",
|
|
||||||
default=False,
|
|
||||||
update=save_mode)
|
|
||||||
|
|
||||||
Scene.storypencil_main_workspace = PointerProperty(type=WorkSpace,
|
|
||||||
description="Main Workspace used for editing Storyboard")
|
|
||||||
Scene.storypencil_main_scene = PointerProperty(type=Scene,
|
|
||||||
description="Main Scene used for editing Storyboard")
|
|
||||||
Scene.storypencil_edit_workspace = PointerProperty(type=WorkSpace,
|
|
||||||
description="Workspace used for changing drawings")
|
|
||||||
|
|
||||||
Scene.storypencil_base_scene = PointerProperty(type=Scene,
|
|
||||||
description="Template Scene used for creating new scenes")
|
|
||||||
|
|
||||||
Scene.storypencil_render_render_path = StringProperty(name="Output Path", subtype='FILE_PATH', maxlen=256,
|
|
||||||
description="Directory/name to save files")
|
|
||||||
|
|
||||||
Scene.storypencil_name_prefix = StringProperty(name="Scene Name Prefix", maxlen=20, default="")
|
|
||||||
|
|
||||||
Scene.storypencil_name_suffix = StringProperty(name="Scene Name Suffix", maxlen=20, default="")
|
|
||||||
|
|
||||||
Scene.storypencil_render_onlyselected = BoolProperty(name="Render only Selected Strips",
|
|
||||||
description="Render only the selected strips",
|
|
||||||
default=True)
|
|
||||||
|
|
||||||
Scene.storypencil_render_channel = IntProperty(name="Channel",
|
|
||||||
description="Channel to set the new rendered video",
|
|
||||||
default=5, min=1, max=128)
|
|
||||||
|
|
||||||
Scene.storypencil_add_render_strip = BoolProperty(name="Import Rendered Strips",
|
|
||||||
description="Add a Strip with the render",
|
|
||||||
default=True)
|
|
||||||
|
|
||||||
Scene.storypencil_render_step = IntProperty(name="Image Steps",
|
|
||||||
description="Minimum frames number to generate images between keyframes (0 to disable)",
|
|
||||||
default=0, min=0, max=128)
|
|
||||||
|
|
||||||
Scene.storypencil_render_numbering = EnumProperty(name="Image Numbering",
|
|
||||||
items=(
|
|
||||||
('1', "Frame", "Use real frame number"),
|
|
||||||
('2', "Consecutive", "Use sequential numbering"),
|
|
||||||
),
|
|
||||||
description="Defines how frame is named")
|
|
||||||
|
|
||||||
Scene.storypencil_add_render_byfolder = BoolProperty(name="Folder by Strip",
|
|
||||||
description="Create a separated folder for each strip",
|
|
||||||
default=True)
|
|
||||||
|
|
||||||
WindowManager.storypencil_settings = PointerProperty(
|
|
||||||
type=synchro.STORYPENCIL_PG_Settings,
|
|
||||||
name="Storypencil settings",
|
|
||||||
description="Storypencil tool settings",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Append Handlers
|
|
||||||
bpy.app.handlers.frame_change_post.clear()
|
|
||||||
bpy.app.handlers.frame_change_post.append(synchro.on_frame_changed)
|
|
||||||
bpy.app.handlers.load_post.append(synchro.sync_autoconfig)
|
|
||||||
|
|
||||||
bpy.context.window_manager.storypencil_settings.active = False
|
|
||||||
bpy.context.window_manager.storypencil_settings.main_window_id = ""
|
|
||||||
bpy.context.window_manager.storypencil_settings.secondary_windows_ids = ""
|
|
||||||
|
|
||||||
# UI integration in dopesheet header
|
|
||||||
bpy.types.DOPESHEET_HT_header.append(synchro.draw_sync_header)
|
|
||||||
dopesheet_overlay.register()
|
|
||||||
|
|
||||||
synchro.sync_autoconfig()
|
|
||||||
|
|
||||||
# UI integration in VSE header
|
|
||||||
bpy.types.SEQUENCER_HT_header.remove(synchro.draw_sync_sequencer_header)
|
|
||||||
bpy.types.SEQUENCER_HT_header.append(synchro.draw_sync_sequencer_header)
|
|
||||||
|
|
||||||
bpy.types.SEQUENCER_MT_add.append(scene_tools.draw_new_scene)
|
|
||||||
bpy.types.VIEW3D_MT_draw_gpencil.append(scene_tools.setup_storyboard)
|
|
||||||
|
|
||||||
|
|
||||||
def unregister():
|
|
||||||
unregister_keymaps()
|
|
||||||
|
|
||||||
from bpy.utils import unregister_class
|
|
||||||
for cls in reversed(classes):
|
|
||||||
unregister_class(cls)
|
|
||||||
|
|
||||||
# Remove Handlers
|
|
||||||
if bpy.app.handlers.frame_change_post:
|
|
||||||
bpy.app.handlers.frame_change_post.remove(synchro.on_frame_changed)
|
|
||||||
bpy.app.handlers.load_post.remove(synchro.sync_autoconfig)
|
|
||||||
|
|
||||||
# remove UI integration
|
|
||||||
bpy.types.DOPESHEET_HT_header.remove(synchro.draw_sync_header)
|
|
||||||
dopesheet_overlay.unregister()
|
|
||||||
bpy.types.SEQUENCER_HT_header.remove(synchro.draw_sync_sequencer_header)
|
|
||||||
|
|
||||||
bpy.types.SEQUENCER_MT_add.remove(scene_tools.draw_new_scene)
|
|
||||||
bpy.types.VIEW3D_MT_draw_gpencil.remove(scene_tools.setup_storyboard)
|
|
||||||
|
|
||||||
del Scene.storypencil_scene_duration
|
|
||||||
del WindowManager.storypencil_settings
|
|
||||||
|
|
||||||
del Scene.storypencil_base_scene
|
|
||||||
del Scene.storypencil_main_workspace
|
|
||||||
del Scene.storypencil_main_scene
|
|
||||||
del Scene.storypencil_edit_workspace
|
|
||||||
|
|
||||||
del Scene.storypencil_render_render_path
|
|
||||||
del Scene.storypencil_name_prefix
|
|
||||||
del Scene.storypencil_name_suffix
|
|
||||||
del Scene.storypencil_render_onlyselected
|
|
||||||
del Scene.storypencil_render_channel
|
|
||||||
del Scene.storypencil_render_step
|
|
||||||
del Scene.storypencil_add_render_strip
|
|
||||||
del Scene.storypencil_render_numbering
|
|
||||||
del Scene.storypencil_add_render_byfolder
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
register()
|
|
@ -1,179 +0,0 @@
|
|||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
import typing
|
|
||||||
|
|
||||||
import bpy
|
|
||||||
import gpu
|
|
||||||
from gpu_extras.batch import batch_for_shader
|
|
||||||
|
|
||||||
from .utils import (redraw_all_areas_by_type)
|
|
||||||
from .synchro import (is_secondary_window, window_id, get_main_strip)
|
|
||||||
|
|
||||||
Int3 = typing.Tuple[int, int, int]
|
|
||||||
|
|
||||||
Float2 = typing.Tuple[float, float]
|
|
||||||
Float3 = typing.Tuple[float, float, float]
|
|
||||||
Float4 = typing.Tuple[float, float, float, float]
|
|
||||||
|
|
||||||
|
|
||||||
class LineDrawer:
|
|
||||||
def __init__(self):
|
|
||||||
self._format = gpu.types.GPUVertFormat()
|
|
||||||
self._pos_id = self._format.attr_add(
|
|
||||||
id="pos", comp_type="F32", len=2, fetch_mode="FLOAT"
|
|
||||||
)
|
|
||||||
self._color_id = self._format.attr_add(
|
|
||||||
id="color", comp_type="F32", len=4, fetch_mode="FLOAT"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
|
|
||||||
|
|
||||||
def draw(
|
|
||||||
self,
|
|
||||||
coords: typing.List[Float2],
|
|
||||||
indices: typing.List[Int3],
|
|
||||||
color: Float4,
|
|
||||||
):
|
|
||||||
if not coords:
|
|
||||||
return
|
|
||||||
|
|
||||||
gpu.state.blend_set('ALPHA')
|
|
||||||
|
|
||||||
self.shader.uniform_float("color", color)
|
|
||||||
|
|
||||||
batch = batch_for_shader(self.shader, 'TRIS', {"pos": coords}, indices=indices)
|
|
||||||
batch.program_set(self.shader)
|
|
||||||
batch.draw()
|
|
||||||
|
|
||||||
gpu.state.blend_set('NONE')
|
|
||||||
|
|
||||||
|
|
||||||
def get_scene_strip_in_out(strip):
|
|
||||||
""" Return the in and out keyframe of the given strip in the scene time reference"""
|
|
||||||
shot_in = strip.scene.frame_start + strip.frame_offset_start
|
|
||||||
shot_out = shot_in + strip.frame_final_duration - 1
|
|
||||||
return (shot_in, shot_out)
|
|
||||||
|
|
||||||
|
|
||||||
def draw_callback_px(line_drawer: LineDrawer):
|
|
||||||
context = bpy.context
|
|
||||||
region = context.region
|
|
||||||
|
|
||||||
wm = context.window_manager
|
|
||||||
|
|
||||||
if (
|
|
||||||
not wm.storypencil_settings.active
|
|
||||||
or not wm.storypencil_settings.show_main_strip_range
|
|
||||||
or not is_secondary_window(wm, window_id(context.window))
|
|
||||||
):
|
|
||||||
return
|
|
||||||
|
|
||||||
# get main strip driving the sync
|
|
||||||
strip = get_main_strip(wm)
|
|
||||||
|
|
||||||
if not strip or strip.scene != context.scene:
|
|
||||||
return
|
|
||||||
|
|
||||||
xwin1, ywin1 = region.view2d.region_to_view(0, 0)
|
|
||||||
one_pixel_further_x = region.view2d.region_to_view(1, 1)[0]
|
|
||||||
pixel_size_x = one_pixel_further_x - xwin1
|
|
||||||
rect_width = 1
|
|
||||||
|
|
||||||
shot_in, shot_out = get_scene_strip_in_out(strip)
|
|
||||||
key_coords_in = [
|
|
||||||
(
|
|
||||||
shot_in - rect_width * pixel_size_x,
|
|
||||||
ywin1,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
shot_in + rect_width * pixel_size_x,
|
|
||||||
ywin1,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
shot_in + rect_width * pixel_size_x,
|
|
||||||
ywin1 + context.region.height,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
shot_in - rect_width * pixel_size_x,
|
|
||||||
ywin1 + context.region.height,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
key_coords_out = [
|
|
||||||
(
|
|
||||||
shot_out - rect_width * pixel_size_x,
|
|
||||||
ywin1,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
shot_out + rect_width * pixel_size_x,
|
|
||||||
ywin1,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
shot_out + rect_width * pixel_size_x,
|
|
||||||
ywin1 + context.region.height,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
shot_out - rect_width * pixel_size_x,
|
|
||||||
ywin1 + context.region.height,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
indices = [(0, 1, 2), (2, 0, 3)]
|
|
||||||
# Draw the IN frame in green
|
|
||||||
# hack: in certain cases, opengl draw state is invalid for the first drawn item
|
|
||||||
# resulting in a non-colored line
|
|
||||||
# => draw it a first time with a null alpha, so that the second one is drawn correctly
|
|
||||||
line_drawer.draw(key_coords_in, indices, (0, 0, 0, 0))
|
|
||||||
line_drawer.draw(key_coords_in, indices, (0.3, 0.99, 0.4, 0.5))
|
|
||||||
# Draw the OUT frame un red
|
|
||||||
line_drawer.draw(key_coords_out, indices, (0.99, 0.3, 0.4, 0.5))
|
|
||||||
|
|
||||||
|
|
||||||
def tag_redraw_all_dopesheets():
|
|
||||||
redraw_all_areas_by_type(bpy.context, 'DOPESHEET')
|
|
||||||
|
|
||||||
|
|
||||||
# This is a list so it can be changed instead of set
|
|
||||||
# if it is only changed, it does not have to be declared as a global everywhere
|
|
||||||
cb_handle = []
|
|
||||||
|
|
||||||
|
|
||||||
def callback_enable():
|
|
||||||
if cb_handle:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Doing GPU stuff in the background crashes Blender, so let's not.
|
|
||||||
if bpy.app.background:
|
|
||||||
return
|
|
||||||
|
|
||||||
line_drawer = LineDrawer()
|
|
||||||
# POST_VIEW allow to work in time coordinate (1 unit = 1 frame)
|
|
||||||
cb_handle[:] = (
|
|
||||||
bpy.types.SpaceDopeSheetEditor.draw_handler_add(
|
|
||||||
draw_callback_px, (line_drawer,), 'WINDOW', 'POST_VIEW'
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
tag_redraw_all_dopesheets()
|
|
||||||
|
|
||||||
|
|
||||||
def callback_disable():
|
|
||||||
if not cb_handle:
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
bpy.types.SpaceDopeSheetEditor.draw_handler_remove(cb_handle[0], 'WINDOW')
|
|
||||||
except ValueError:
|
|
||||||
# Thrown when already removed.
|
|
||||||
pass
|
|
||||||
cb_handle.clear()
|
|
||||||
|
|
||||||
tag_redraw_all_dopesheets()
|
|
||||||
|
|
||||||
|
|
||||||
def register():
|
|
||||||
callback_enable()
|
|
||||||
|
|
||||||
|
|
||||||
def unregister():
|
|
||||||
callback_disable()
|
|
@ -1,281 +0,0 @@
|
|||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
import bpy
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
from bpy.types import Operator
|
|
||||||
from .utils import get_keyframe_list
|
|
||||||
|
|
||||||
# ------------------------------------------------------
|
|
||||||
# Button: Render VSE
|
|
||||||
# ------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
class STORYPENCIL_OT_RenderAction(Operator):
|
|
||||||
bl_idname = "storypencil.render_vse"
|
|
||||||
bl_label = "Render Strips"
|
|
||||||
bl_description = "Render VSE strips"
|
|
||||||
|
|
||||||
# Extension by FFMPEG container type
|
|
||||||
video_ext = {
|
|
||||||
"MPEG1": ".mpg",
|
|
||||||
"MPEG2": ".dvd",
|
|
||||||
"MPEG4": ".mp4",
|
|
||||||
"AVI": ".avi",
|
|
||||||
"QUICKTIME": ".mov",
|
|
||||||
"DV": ".dv",
|
|
||||||
"OGG": ".ogv",
|
|
||||||
"MKV": ".mkv",
|
|
||||||
"FLASH": ".flv",
|
|
||||||
"WEBM": ".webm"
|
|
||||||
}
|
|
||||||
# Extension by image format
|
|
||||||
image_ext = {
|
|
||||||
"BMP": ".bmp",
|
|
||||||
"IRIS": ".rgb",
|
|
||||||
"PNG": ".png",
|
|
||||||
"JPEG": ".jpg",
|
|
||||||
"JPEG2000": ".jp2",
|
|
||||||
"TARGA": ".tga",
|
|
||||||
"TARGA_RAW": ".tga",
|
|
||||||
"CINEON": ".cin",
|
|
||||||
"DPX": ".dpx",
|
|
||||||
"OPEN_EXR_MULTILAYER": ".exr",
|
|
||||||
"OPEN_EXR": ".exr",
|
|
||||||
"HDR": ".hdr",
|
|
||||||
"TIFF": ".tif",
|
|
||||||
"WEBP": ".webp"
|
|
||||||
}
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
|
||||||
# Format an int adding 4 zero padding
|
|
||||||
# --------------------------------------------------------------------
|
|
||||||
def format_to4(self, value):
|
|
||||||
return f"{value:04}"
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
|
||||||
# Add frames every N frames
|
|
||||||
# --------------------------------------------------------------------
|
|
||||||
def add_missing_frames(self, sq, step, keyframe_list):
|
|
||||||
missing = []
|
|
||||||
lk = len(keyframe_list)
|
|
||||||
if lk == 0:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Add mid frames
|
|
||||||
if step > 0:
|
|
||||||
for i in range(0, lk - 1):
|
|
||||||
dist = keyframe_list[i + 1] - keyframe_list[i]
|
|
||||||
if dist > step:
|
|
||||||
delta = int(dist / step)
|
|
||||||
e = 1
|
|
||||||
for x in range(1, delta):
|
|
||||||
missing.append(keyframe_list[i] + (step * e))
|
|
||||||
e += 1
|
|
||||||
|
|
||||||
keyframe_list.extend(missing)
|
|
||||||
keyframe_list.sort()
|
|
||||||
|
|
||||||
# ------------------------------
|
|
||||||
# Execute
|
|
||||||
# ------------------------------
|
|
||||||
def execute(self, context):
|
|
||||||
scene = bpy.context.scene
|
|
||||||
image_settings = scene.render.image_settings
|
|
||||||
is_video_output = image_settings.file_format in {
|
|
||||||
'FFMPEG', 'AVI_JPEG', 'AVI_RAW'}
|
|
||||||
step = scene.storypencil_render_step
|
|
||||||
|
|
||||||
sequences = scene.sequence_editor.sequences_all
|
|
||||||
prv_start = scene.frame_start
|
|
||||||
prv_end = scene.frame_end
|
|
||||||
prv_frame = bpy.context.scene.frame_current
|
|
||||||
|
|
||||||
prv_path = scene.render.filepath
|
|
||||||
prv_format = image_settings.file_format
|
|
||||||
prv_use_file_extension = scene.render.use_file_extension
|
|
||||||
prv_ffmpeg_format = scene.render.ffmpeg.format
|
|
||||||
rootpath = scene.storypencil_render_render_path
|
|
||||||
only_selected = scene.storypencil_render_onlyselected
|
|
||||||
channel = scene.storypencil_render_channel
|
|
||||||
|
|
||||||
context.window.cursor_set('WAIT')
|
|
||||||
|
|
||||||
# Create list of selected strips because the selection is changed when adding new strips
|
|
||||||
Strips = []
|
|
||||||
for sq in sequences:
|
|
||||||
if sq.type == 'SCENE':
|
|
||||||
if only_selected is False or sq.select is True:
|
|
||||||
Strips.append(sq)
|
|
||||||
|
|
||||||
# Sort strips
|
|
||||||
Strips = sorted(Strips, key=lambda strip: strip.frame_start)
|
|
||||||
|
|
||||||
# For video, clear BL_proxy folder because sometimes the video
|
|
||||||
# is not rendered as expected if this folder has data.
|
|
||||||
# This ensure the output video is correct.
|
|
||||||
if is_video_output:
|
|
||||||
proxy_folder = os.path.join(rootpath, "BL_proxy")
|
|
||||||
if os.path.exists(proxy_folder):
|
|
||||||
for filename in os.listdir(proxy_folder):
|
|
||||||
file_path = os.path.join(proxy_folder, filename)
|
|
||||||
try:
|
|
||||||
if os.path.isfile(file_path) or os.path.islink(file_path):
|
|
||||||
os.unlink(file_path)
|
|
||||||
elif os.path.isdir(file_path):
|
|
||||||
shutil.rmtree(file_path)
|
|
||||||
except Exception as e:
|
|
||||||
print('Failed to delete %s. Reason: %s' %
|
|
||||||
(file_path, e))
|
|
||||||
|
|
||||||
try:
|
|
||||||
Videos = []
|
|
||||||
Sheets = []
|
|
||||||
# Read all strips and render the output
|
|
||||||
for sq in Strips:
|
|
||||||
strip_name = sq.name
|
|
||||||
strip_scene = sq.scene
|
|
||||||
scene.frame_start = int(sq.frame_start + sq.frame_offset_start)
|
|
||||||
scene.frame_end = int(scene.frame_start + sq.frame_final_duration - 1) # Image
|
|
||||||
if is_video_output is False:
|
|
||||||
# Get list of any keyframe
|
|
||||||
strip_start = sq.frame_offset_start
|
|
||||||
if strip_start < strip_scene.frame_start:
|
|
||||||
strip_start = strip_scene.frame_start
|
|
||||||
|
|
||||||
strip_end = strip_start + sq.frame_final_duration - 1
|
|
||||||
keyframe_list = get_keyframe_list(
|
|
||||||
strip_scene, strip_start, strip_end)
|
|
||||||
self.add_missing_frames(sq, step, keyframe_list)
|
|
||||||
|
|
||||||
scene.render.use_file_extension = True
|
|
||||||
foldername = strip_name
|
|
||||||
if scene.storypencil_add_render_byfolder is True:
|
|
||||||
root_folder = os.path.join(rootpath, foldername)
|
|
||||||
else:
|
|
||||||
root_folder = rootpath
|
|
||||||
|
|
||||||
frame_nrr = 0
|
|
||||||
print("Render:" + strip_name + "/" + strip_scene.name)
|
|
||||||
print("Image From:", strip_start, "To", strip_end)
|
|
||||||
for key in range(int(strip_start), int(strip_end) + 1):
|
|
||||||
if key not in keyframe_list:
|
|
||||||
continue
|
|
||||||
|
|
||||||
keyframe = key + sq.frame_start
|
|
||||||
if scene.use_preview_range:
|
|
||||||
if keyframe < scene.frame_preview_start:
|
|
||||||
continue
|
|
||||||
if keyframe > scene.frame_preview_end:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
if keyframe < scene.frame_start:
|
|
||||||
continue
|
|
||||||
if keyframe > scene.frame_end:
|
|
||||||
break
|
|
||||||
# For frame name use only the number
|
|
||||||
if scene.storypencil_render_numbering == '1':
|
|
||||||
# Real
|
|
||||||
framename = strip_name + '.' + self.format_to4(key)
|
|
||||||
else:
|
|
||||||
# Consecutive
|
|
||||||
frame_nrr += 1
|
|
||||||
framename = strip_name + '.' + \
|
|
||||||
self.format_to4(frame_nrr)
|
|
||||||
|
|
||||||
filepath = os.path.join(root_folder, framename)
|
|
||||||
|
|
||||||
sheet = os.path.realpath(filepath)
|
|
||||||
sheet = bpy.path.ensure_ext(
|
|
||||||
sheet, self.image_ext[image_settings.file_format])
|
|
||||||
Sheets.append([sheet, keyframe])
|
|
||||||
|
|
||||||
scene.render.filepath = filepath
|
|
||||||
|
|
||||||
# Render Frame
|
|
||||||
scene.frame_set(int(keyframe - 1.0), subframe=0.0)
|
|
||||||
bpy.ops.render.render(
|
|
||||||
animation=False, write_still=True)
|
|
||||||
|
|
||||||
# Add strip with the corresponding length
|
|
||||||
if scene.storypencil_add_render_strip:
|
|
||||||
frame_start = sq.frame_start + key - 1
|
|
||||||
index = keyframe_list.index(key)
|
|
||||||
if index < len(keyframe_list) - 1:
|
|
||||||
key_next = keyframe_list[index + 1]
|
|
||||||
frame_end = frame_start + (key_next - key)
|
|
||||||
else:
|
|
||||||
frame_end = scene.frame_end + 1
|
|
||||||
|
|
||||||
if index == 0 and frame_start > scene.frame_start:
|
|
||||||
frame_start = scene.frame_start
|
|
||||||
|
|
||||||
if frame_end < frame_start:
|
|
||||||
frame_end = frame_start
|
|
||||||
image_ext = self.image_ext[image_settings.file_format]
|
|
||||||
bpy.ops.sequencer.image_strip_add(directory=root_folder,
|
|
||||||
files=[
|
|
||||||
{"name": framename + image_ext}],
|
|
||||||
frame_start=int(frame_start),
|
|
||||||
frame_end=int(frame_end),
|
|
||||||
channel=channel)
|
|
||||||
else:
|
|
||||||
print("Render:" + strip_name + "/" + strip_scene.name)
|
|
||||||
print("Video From:", scene.frame_start,
|
|
||||||
"To", scene.frame_end)
|
|
||||||
# Video
|
|
||||||
filepath = os.path.join(rootpath, strip_name)
|
|
||||||
|
|
||||||
if image_settings.file_format == 'FFMPEG':
|
|
||||||
ext = self.video_ext[scene.render.ffmpeg.format]
|
|
||||||
else:
|
|
||||||
ext = '.avi'
|
|
||||||
|
|
||||||
if not filepath.endswith(ext):
|
|
||||||
filepath += ext
|
|
||||||
|
|
||||||
scene.render.use_file_extension = False
|
|
||||||
scene.render.filepath = filepath
|
|
||||||
|
|
||||||
# Render Animation
|
|
||||||
bpy.ops.render.render(animation=True)
|
|
||||||
|
|
||||||
# Add video to add strip later
|
|
||||||
if scene.storypencil_add_render_strip:
|
|
||||||
Videos.append(
|
|
||||||
[filepath, sq.frame_start + sq.frame_offset_start])
|
|
||||||
|
|
||||||
# Add pending video Strips
|
|
||||||
for vid in Videos:
|
|
||||||
bpy.ops.sequencer.movie_strip_add(filepath=vid[0],
|
|
||||||
frame_start=int(vid[1]),
|
|
||||||
channel=channel)
|
|
||||||
|
|
||||||
scene.frame_start = prv_start
|
|
||||||
scene.frame_end = prv_end
|
|
||||||
scene.render.use_file_extension = prv_use_file_extension
|
|
||||||
image_settings.file_format = prv_format
|
|
||||||
scene.render.ffmpeg.format = prv_ffmpeg_format
|
|
||||||
|
|
||||||
scene.render.filepath = prv_path
|
|
||||||
scene.frame_set(int(prv_frame))
|
|
||||||
|
|
||||||
context.window.cursor_set('DEFAULT')
|
|
||||||
|
|
||||||
return {'FINISHED'}
|
|
||||||
|
|
||||||
except:
|
|
||||||
print("Unexpected error:" + str(sys.exc_info()))
|
|
||||||
self.report({'ERROR'}, "Unable to render")
|
|
||||||
scene.frame_start = prv_start
|
|
||||||
scene.frame_end = prv_end
|
|
||||||
scene.render.use_file_extension = prv_use_file_extension
|
|
||||||
image_settings.file_format = prv_format
|
|
||||||
|
|
||||||
scene.render.filepath = prv_path
|
|
||||||
scene.frame_set(int(prv_frame))
|
|
||||||
context.window.cursor_set('DEFAULT')
|
|
||||||
return {'FINISHED'}
|
|
@ -1,173 +0,0 @@
|
|||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
import bpy
|
|
||||||
import os
|
|
||||||
|
|
||||||
from bpy.types import (
|
|
||||||
Operator,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# -------------------------------------------------------------
|
|
||||||
# Add a new scene and set to new strip
|
|
||||||
#
|
|
||||||
# -------------------------------------------------------------
|
|
||||||
class STORYPENCIL_OT_NewScene(Operator):
|
|
||||||
bl_idname = "storypencil.new_scene"
|
|
||||||
bl_label = "New Scene"
|
|
||||||
bl_description = "Create a new scene base on template scene"
|
|
||||||
bl_options = {'REGISTER', 'UNDO'}
|
|
||||||
|
|
||||||
scene_name: bpy.props.StringProperty(default="Scene")
|
|
||||||
|
|
||||||
# ------------------------------
|
|
||||||
# Poll
|
|
||||||
# ------------------------------
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context):
|
|
||||||
scene = context.scene
|
|
||||||
scene_base = scene.storypencil_base_scene
|
|
||||||
if scene_base is not None and scene_base.name in bpy.data.scenes:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def invoke(self, context, event):
|
|
||||||
return context.window_manager.invoke_props_dialog(self)
|
|
||||||
|
|
||||||
def draw(self, context):
|
|
||||||
layout = self.layout
|
|
||||||
col = layout.column()
|
|
||||||
col.prop(self, "scene_name", text="Scene Name")
|
|
||||||
|
|
||||||
def format_to3(self, value):
|
|
||||||
return f"{value:03}"
|
|
||||||
|
|
||||||
# ------------------------------
|
|
||||||
# Execute button action
|
|
||||||
# ------------------------------
|
|
||||||
def execute(self, context):
|
|
||||||
scene_prv = context.scene
|
|
||||||
cfra_prv = scene_prv.frame_current
|
|
||||||
scene_base = scene_prv.storypencil_base_scene
|
|
||||||
|
|
||||||
# Set context to base scene and duplicate
|
|
||||||
context.window.scene = scene_base
|
|
||||||
bpy.ops.scene.new(type='FULL_COPY')
|
|
||||||
scene_new = context.window.scene
|
|
||||||
new_name = scene_prv.storypencil_name_prefix + \
|
|
||||||
self.scene_name + scene_prv.storypencil_name_suffix
|
|
||||||
id = 0
|
|
||||||
while new_name in bpy.data.scenes:
|
|
||||||
id += 1
|
|
||||||
new_name = scene_prv.storypencil_name_prefix + self.scene_name + \
|
|
||||||
scene_prv.storypencil_name_suffix + '.' + self.format_to3(id)
|
|
||||||
|
|
||||||
scene_new.name = new_name
|
|
||||||
# Set duration of new scene
|
|
||||||
scene_new.frame_end = scene_new.frame_start + \
|
|
||||||
scene_prv.storypencil_scene_duration - 1
|
|
||||||
|
|
||||||
# Back to original scene
|
|
||||||
context.window.scene = scene_prv
|
|
||||||
scene_prv.frame_current = cfra_prv
|
|
||||||
bpy.ops.sequencer.scene_strip_add(
|
|
||||||
frame_start=cfra_prv, scene=scene_new.name)
|
|
||||||
|
|
||||||
scene_new.update_tag()
|
|
||||||
scene_prv.update_tag()
|
|
||||||
|
|
||||||
return {"FINISHED"}
|
|
||||||
|
|
||||||
|
|
||||||
def draw_new_scene(self, context):
|
|
||||||
"""Add menu options."""
|
|
||||||
|
|
||||||
self.layout.operator_context = 'INVOKE_REGION_WIN'
|
|
||||||
row = self.layout.row(align=True)
|
|
||||||
row.operator(STORYPENCIL_OT_NewScene.bl_idname, text="New Template Scene")
|
|
||||||
|
|
||||||
|
|
||||||
def setup_storyboard(self, context):
|
|
||||||
"""Add Setup menu option."""
|
|
||||||
# For security, check if this is the default template.
|
|
||||||
is_gpencil = context.active_object and context.active_object.name == 'Stroke'
|
|
||||||
if is_gpencil and context.workspace.name in ('2D Animation', '2D Full Canvas') and context.scene.name == 'Scene':
|
|
||||||
if "Video Editing" not in bpy.data.workspaces:
|
|
||||||
row = self.layout.row(align=True)
|
|
||||||
row.separator()
|
|
||||||
row = self.layout.row(align=True)
|
|
||||||
row.operator(STORYPENCIL_OT_Setup.bl_idname,
|
|
||||||
text="Setup Storyboard Session")
|
|
||||||
|
|
||||||
|
|
||||||
# -------------------------------------------------------------
|
|
||||||
# Setup all environment
|
|
||||||
#
|
|
||||||
# -------------------------------------------------------------
|
|
||||||
class STORYPENCIL_OT_Setup(Operator):
|
|
||||||
bl_idname = "storypencil.setup"
|
|
||||||
bl_label = "Setup"
|
|
||||||
bl_description = "Configure all settings for a storyboard session"
|
|
||||||
bl_options = {'REGISTER', 'UNDO'}
|
|
||||||
|
|
||||||
# ------------------------------
|
|
||||||
# Poll
|
|
||||||
# ------------------------------
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def get_workspace(self, type):
|
|
||||||
for wrk in bpy.data.workspaces:
|
|
||||||
if wrk.name == type:
|
|
||||||
return wrk
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
# ------------------------------
|
|
||||||
# Execute button action
|
|
||||||
# ------------------------------
|
|
||||||
def execute(self, context):
|
|
||||||
scene_base = context.scene
|
|
||||||
# Create Workspace
|
|
||||||
templatepath = None
|
|
||||||
if "Video Editing" not in bpy.data.workspaces:
|
|
||||||
template_path = None
|
|
||||||
for path in bpy.utils.app_template_paths():
|
|
||||||
template_path = path
|
|
||||||
|
|
||||||
filepath = os.path.join(
|
|
||||||
template_path, "Video_Editing", "startup.blend")
|
|
||||||
bpy.ops.workspace.append_activate(
|
|
||||||
idname="Video Editing", filepath=filepath)
|
|
||||||
# Create New scene
|
|
||||||
bpy.ops.scene.new()
|
|
||||||
scene_edit = context.scene
|
|
||||||
scene_edit.name = 'Edit'
|
|
||||||
# Rename original base scene
|
|
||||||
scene_base.name = 'Base'
|
|
||||||
# Setup Edit scene settings
|
|
||||||
scene_edit.storypencil_main_workspace = self.get_workspace(
|
|
||||||
"Video Editing")
|
|
||||||
scene_edit.storypencil_main_scene = scene_edit
|
|
||||||
scene_edit.storypencil_base_scene = scene_base
|
|
||||||
scene_edit.storypencil_edit_workspace = self.get_workspace(
|
|
||||||
"2D Animation")
|
|
||||||
|
|
||||||
# Add a new strip (need set the area context)
|
|
||||||
context.window.scene = scene_edit
|
|
||||||
area_prv = context.area.ui_type
|
|
||||||
context.area.ui_type = 'SEQUENCE_EDITOR'
|
|
||||||
prv_frame = scene_edit.frame_current
|
|
||||||
|
|
||||||
scene_edit.frame_current = scene_edit.frame_start
|
|
||||||
bpy.ops.storypencil.new_scene()
|
|
||||||
|
|
||||||
context.area.ui_type = area_prv
|
|
||||||
scene_edit.frame_current = prv_frame
|
|
||||||
|
|
||||||
scene_edit.update_tag()
|
|
||||||
bpy.ops.sequencer.reload()
|
|
||||||
|
|
||||||
return {"FINISHED"}
|
|
@ -1,794 +0,0 @@
|
|||||||
# 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
|
|
||||||
|
|
||||||
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(context, idname=wk_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
|
|
||||||
"""
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
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]
|
|
||||||
|
|
||||||
|
|
||||||
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.menu("STORYPENCIL_MT_extra_options", icon='DOWNARROW_HLT', text="")
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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
|
|
||||||
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'
|
|
||||||
|
|
||||||
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):
|
|
||||||
if context.scene.storypencil_use_new_window:
|
|
||||||
bpy.ops.storypencil.sync_set_main('INVOKE_DEFAULT', True)
|
|
||||||
else:
|
|
||||||
bpy.ops.storypencil.switch('INVOKE_DEFAULT', True)
|
|
||||||
|
|
||||||
return {'FINISHED'}
|
|
@ -1,211 +0,0 @@
|
|||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
import bpy
|
|
||||||
|
|
||||||
from bpy.types import (
|
|
||||||
Menu,
|
|
||||||
Panel,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .synchro import get_main_window, validate_sync, window_id
|
|
||||||
|
|
||||||
|
|
||||||
class STORYPENCIL_MT_extra_options(Menu):
|
|
||||||
bl_label = "Scene Settings"
|
|
||||||
|
|
||||||
def draw(self, context):
|
|
||||||
layout = self.layout
|
|
||||||
wm = bpy.context.window_manager
|
|
||||||
scene = context.scene
|
|
||||||
layout.prop(scene, "storypencil_use_new_window")
|
|
||||||
|
|
||||||
# If no main window nothing else to do
|
|
||||||
if not get_main_window(wm):
|
|
||||||
return
|
|
||||||
|
|
||||||
win_id = window_id(context.window)
|
|
||||||
row = self.layout.row(align=True)
|
|
||||||
if not validate_sync(window_manager=wm) or win_id == wm.storypencil_settings.main_window_id:
|
|
||||||
row = layout.row()
|
|
||||||
row.prop(wm.storypencil_settings, "active",
|
|
||||||
text="Timeline Synchronization")
|
|
||||||
row.active = scene.storypencil_use_new_window
|
|
||||||
|
|
||||||
row = layout.row()
|
|
||||||
row.prop(wm.storypencil_settings,
|
|
||||||
"show_main_strip_range", text="Show Strip Range")
|
|
||||||
row.active = scene.storypencil_use_new_window
|
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------
|
|
||||||
# Defines UI panel
|
|
||||||
# ------------------------------------------------------
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
# Define panel class for manual switch parameters.
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
class STORYPENCIL_PT_Settings(Panel):
|
|
||||||
bl_idname = "STORYPENCIL_PT_Settings"
|
|
||||||
bl_label = "Settings"
|
|
||||||
bl_space_type = 'SEQUENCE_EDITOR'
|
|
||||||
bl_region_type = 'UI'
|
|
||||||
bl_category = 'Storypencil'
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context):
|
|
||||||
if context.space_data.view_type != 'SEQUENCER':
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
# ------------------------------
|
|
||||||
# Draw UI
|
|
||||||
# ------------------------------
|
|
||||||
def draw(self, context):
|
|
||||||
layout = self.layout
|
|
||||||
layout.use_property_split = True
|
|
||||||
layout.use_property_decorate = False
|
|
||||||
|
|
||||||
|
|
||||||
class STORYPENCIL_PT_General(Panel):
|
|
||||||
bl_idname = "STORYPENCIL_PT_General"
|
|
||||||
bl_label = "General"
|
|
||||||
bl_space_type = 'SEQUENCE_EDITOR'
|
|
||||||
bl_region_type = 'UI'
|
|
||||||
bl_category = 'Storypencil'
|
|
||||||
bl_options = {'DEFAULT_CLOSED'}
|
|
||||||
bl_parent_id = "STORYPENCIL_PT_Settings"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context):
|
|
||||||
if context.space_data.view_type != 'SEQUENCER':
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
# ------------------------------
|
|
||||||
# Draw UI
|
|
||||||
# ------------------------------
|
|
||||||
def draw(self, context):
|
|
||||||
layout = self.layout
|
|
||||||
layout.use_property_split = True
|
|
||||||
layout.use_property_decorate = False
|
|
||||||
scene = context.scene
|
|
||||||
|
|
||||||
setup_ready = scene.storypencil_main_workspace is not None
|
|
||||||
row = layout.row()
|
|
||||||
row.alert = not setup_ready
|
|
||||||
row.prop(scene, "storypencil_main_workspace", text="VSE Workspace")
|
|
||||||
|
|
||||||
row = layout.row()
|
|
||||||
if scene.storypencil_main_scene is None:
|
|
||||||
row.alert = True
|
|
||||||
row.prop(scene, "storypencil_main_scene", text="VSE Scene")
|
|
||||||
|
|
||||||
layout.separator()
|
|
||||||
|
|
||||||
row = layout.row()
|
|
||||||
if scene.storypencil_main_workspace and scene.storypencil_edit_workspace:
|
|
||||||
if scene.storypencil_main_workspace.name == scene.storypencil_edit_workspace.name:
|
|
||||||
row.alert = True
|
|
||||||
if scene.storypencil_edit_workspace is None:
|
|
||||||
row.alert = True
|
|
||||||
row.prop(scene, "storypencil_edit_workspace", text="Drawing Workspace")
|
|
||||||
|
|
||||||
|
|
||||||
class STORYPENCIL_PT_RenderPanel(Panel):
|
|
||||||
bl_label = "Render Strips"
|
|
||||||
bl_space_type = 'SEQUENCE_EDITOR'
|
|
||||||
bl_region_type = 'UI'
|
|
||||||
bl_category = 'Storypencil'
|
|
||||||
bl_parent_id = "STORYPENCIL_PT_Settings"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context):
|
|
||||||
if context.space_data.view_type != 'SEQUENCER':
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def draw(self, context):
|
|
||||||
layout = self.layout
|
|
||||||
layout.use_property_split = True
|
|
||||||
layout.use_property_decorate = False
|
|
||||||
|
|
||||||
scene = context.scene
|
|
||||||
settings = scene.render.image_settings
|
|
||||||
|
|
||||||
is_video = settings.file_format in {'FFMPEG', 'AVI_JPEG', 'AVI_RAW'}
|
|
||||||
row = layout.row()
|
|
||||||
if scene.storypencil_render_render_path is None:
|
|
||||||
row.alert = True
|
|
||||||
row.prop(scene, "storypencil_render_render_path")
|
|
||||||
|
|
||||||
row = layout.row()
|
|
||||||
row.prop(scene, "storypencil_render_onlyselected")
|
|
||||||
|
|
||||||
row = layout.row()
|
|
||||||
row.prop(scene.render.image_settings, "file_format")
|
|
||||||
|
|
||||||
if settings.file_format == 'FFMPEG':
|
|
||||||
row = layout.row()
|
|
||||||
row.prop(scene.render.ffmpeg, "format")
|
|
||||||
|
|
||||||
row = layout.row()
|
|
||||||
row.enabled = is_video
|
|
||||||
row.prop(scene.render.ffmpeg, "audio_codec")
|
|
||||||
|
|
||||||
row = layout.row()
|
|
||||||
row.prop(scene, "storypencil_add_render_strip")
|
|
||||||
|
|
||||||
row = layout.row()
|
|
||||||
row.enabled = scene.storypencil_add_render_strip
|
|
||||||
row.prop(scene, "storypencil_render_channel")
|
|
||||||
|
|
||||||
if not is_video:
|
|
||||||
row = layout.row()
|
|
||||||
row.prop(scene, "storypencil_render_step")
|
|
||||||
|
|
||||||
row = layout.row()
|
|
||||||
row.prop(scene, "storypencil_render_numbering")
|
|
||||||
|
|
||||||
row = layout.row()
|
|
||||||
row.prop(scene, "storypencil_add_render_byfolder")
|
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
# Define panel class for new base scene creation.
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
class STORYPENCIL_PT_SettingsNew(Panel):
|
|
||||||
bl_idname = "STORYPENCIL_PT_SettingsNew"
|
|
||||||
bl_label = "New Scenes"
|
|
||||||
bl_space_type = 'SEQUENCE_EDITOR'
|
|
||||||
bl_region_type = 'UI'
|
|
||||||
bl_category = 'Storypencil'
|
|
||||||
bl_parent_id = "STORYPENCIL_PT_Settings"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context):
|
|
||||||
if context.space_data.view_type != 'SEQUENCER':
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
# ------------------------------
|
|
||||||
# Draw UI
|
|
||||||
# ------------------------------
|
|
||||||
def draw(self, context):
|
|
||||||
layout = self.layout
|
|
||||||
layout.use_property_split = True
|
|
||||||
layout.use_property_decorate = False
|
|
||||||
scene = context.scene
|
|
||||||
row = layout.row()
|
|
||||||
row.prop(scene, "storypencil_name_prefix", text="Name Prefix")
|
|
||||||
row = layout.row()
|
|
||||||
row.prop(scene, "storypencil_name_suffix", text="Name Suffix")
|
|
||||||
row = layout.row()
|
|
||||||
row.prop(scene, "storypencil_scene_duration", text="Frames")
|
|
||||||
|
|
||||||
row = layout.row()
|
|
||||||
if scene.storypencil_base_scene is None:
|
|
||||||
row.alert = True
|
|
||||||
row.prop(scene, "storypencil_base_scene", text="Template Scene")
|
|
@ -1,110 +0,0 @@
|
|||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
import bpy
|
|
||||||
import math
|
|
||||||
|
|
||||||
|
|
||||||
def redraw_areas_by_type(window, area_type, region_type='WINDOW'):
|
|
||||||
"""Redraw `window`'s areas matching the given `area_type` and optionnal `region_type`."""
|
|
||||||
for area in window.screen.areas:
|
|
||||||
if area.type == area_type:
|
|
||||||
for region in area.regions:
|
|
||||||
if region.type == region_type:
|
|
||||||
region.tag_redraw()
|
|
||||||
|
|
||||||
|
|
||||||
def redraw_all_areas_by_type(context, area_type, region_type='WINDOW'):
|
|
||||||
"""Redraw areas in all windows matching the given `area_type` and optionnal `region_type`."""
|
|
||||||
for window in context.window_manager.windows:
|
|
||||||
redraw_areas_by_type(window, area_type, region_type)
|
|
||||||
|
|
||||||
|
|
||||||
def get_selected_keyframes(context):
|
|
||||||
"""Get list of selected keyframes for any object in the scene. """
|
|
||||||
keys = []
|
|
||||||
|
|
||||||
for ob in context.scene.objects:
|
|
||||||
if ob.type == 'GPENCIL':
|
|
||||||
for gpl in ob.data.layers:
|
|
||||||
for gpf in gpl.frames:
|
|
||||||
if gpf.select:
|
|
||||||
keys.append(gpf.frame_number)
|
|
||||||
|
|
||||||
elif ob.animation_data is not None and ob.animation_data.action is not None:
|
|
||||||
action = ob.animation_data.action
|
|
||||||
for fcu in action.fcurves:
|
|
||||||
for kp in fcu.keyframe_points:
|
|
||||||
if kp.select_control_point:
|
|
||||||
keys.append(int(kp.co[0]))
|
|
||||||
|
|
||||||
keys.sort()
|
|
||||||
unique_keys = list(set(keys))
|
|
||||||
return unique_keys
|
|
||||||
|
|
||||||
|
|
||||||
def find_collections_recursive(root, collections=None):
|
|
||||||
# Initialize the result once
|
|
||||||
if collections is None:
|
|
||||||
collections = []
|
|
||||||
|
|
||||||
def recurse(parent, result):
|
|
||||||
result.append(parent)
|
|
||||||
# Look over children at next level
|
|
||||||
for child in parent.children:
|
|
||||||
recurse(child, result)
|
|
||||||
|
|
||||||
recurse(root, collections)
|
|
||||||
|
|
||||||
return collections
|
|
||||||
|
|
||||||
|
|
||||||
def get_keyframe_list(scene, frame_start, frame_end):
|
|
||||||
"""Get list of frames for any gpencil object in the scene and meshes. """
|
|
||||||
keys = []
|
|
||||||
root = scene.view_layers[0].layer_collection
|
|
||||||
collections = find_collections_recursive(root)
|
|
||||||
|
|
||||||
for laycol in collections:
|
|
||||||
if laycol.exclude is True or laycol.collection.hide_render is True:
|
|
||||||
continue
|
|
||||||
for ob in laycol.collection.objects:
|
|
||||||
if ob.hide_render:
|
|
||||||
continue
|
|
||||||
if ob.type == 'GPENCIL':
|
|
||||||
for gpl in ob.data.layers:
|
|
||||||
if gpl.hide:
|
|
||||||
continue
|
|
||||||
for gpf in gpl.frames:
|
|
||||||
if frame_start <= gpf.frame_number <= frame_end:
|
|
||||||
keys.append(gpf.frame_number)
|
|
||||||
|
|
||||||
# Animation at object level
|
|
||||||
if ob.animation_data is not None and ob.animation_data.action is not None:
|
|
||||||
action = ob.animation_data.action
|
|
||||||
for fcu in action.fcurves:
|
|
||||||
for kp in fcu.keyframe_points:
|
|
||||||
if frame_start <= int(kp.co[0]) <= frame_end:
|
|
||||||
keys.append(int(kp.co[0]))
|
|
||||||
|
|
||||||
# Animation at datablock level
|
|
||||||
if ob.type != 'GPENCIL':
|
|
||||||
data = ob.data
|
|
||||||
if data and data.animation_data is not None and data.animation_data.action is not None:
|
|
||||||
action = data.animation_data.action
|
|
||||||
for fcu in action.fcurves:
|
|
||||||
for kp in fcu.keyframe_points:
|
|
||||||
if frame_start <= int(kp.co[0]) <= frame_end:
|
|
||||||
keys.append(int(kp.co[0]))
|
|
||||||
|
|
||||||
# Scene Markers
|
|
||||||
for m in scene.timeline_markers:
|
|
||||||
if frame_start <= m.frame <= frame_end and m.camera is not None:
|
|
||||||
keys.append(int(m.frame))
|
|
||||||
|
|
||||||
# If no animation or markers, must add first frame
|
|
||||||
if len(keys) == 0:
|
|
||||||
keys.append(int(frame_start))
|
|
||||||
|
|
||||||
unique_keys = list(set(keys))
|
|
||||||
unique_keys.sort()
|
|
||||||
return unique_keys
|
|
Reference in New Issue
Block a user