Blender Kitsu: Add Import Edit Render Operator for Shots #236

Merged
Nick Alberelli merged 6 commits from TinyNick/blender-studio-pipeline:feature/import_edit_export into main 2024-02-19 21:02:44 +01:00
5 changed files with 218 additions and 63 deletions
Showing only changes of commit 4111cb613c - Show all commits

View File

@ -6,6 +6,16 @@ from pathlib import Path
def edit_export_get_latest(context: bpy.types.Context): def edit_export_get_latest(context: bpy.types.Context):
"""Find latest export in editorial export directory""" """Find latest export in editorial export directory"""
files_list = edit_exports_get_all(context)
if len(files_list) >= 1:
files_list = sorted(files_list, reverse=True)
return files_list[0]
return None
def edit_exports_get_all(context: bpy.types.Context):
"""Find latest export in editorial export directory"""
addon_prefs = prefs.addon_prefs_get(context) addon_prefs = prefs.addon_prefs_get(context)
edit_export_path = Path(addon_prefs.edit_export_dir) edit_export_path = Path(addon_prefs.edit_export_dir)
@ -16,10 +26,7 @@ def edit_export_get_latest(context: bpy.types.Context):
if f.is_file() if f.is_file()
and edit_export_is_valid_edit_name(addon_prefs.edit_export_file_pattern, f.name) and edit_export_is_valid_edit_name(addon_prefs.edit_export_file_pattern, f.name)
] ]
if len(files_list) >= 1: return files_list
files_list = sorted(files_list, reverse=True)
return files_list[0]
return None
def edit_export_is_valid_edit_name(file_pattern: str, filename: str) -> bool: def edit_export_is_valid_edit_name(file_pattern: str, filename: str) -> bool:
@ -31,3 +38,51 @@ def edit_export_is_valid_edit_name(file_pattern: str, filename: str) -> bool:
if match: if match:
return True return True
return False return False
def edit_export_import_latest(
context: bpy.types.Context, shot
) -> list[bpy.types.Sequence]: # TODO add info to shot
"""Loads latest export from editorial department"""
addon_prefs = prefs.addon_prefs_get(context)
strip_channel = 1
latest_file = edit_export_get_latest(context)
if not latest_file:
return None
# Check if Kitsu server returned empty shot
if shot.id == '':
return None
strip_filepath = latest_file.as_posix()
strip_frame_start = addon_prefs.shot_builder_frame_offset
scene = context.scene
if not scene.sequence_editor:
scene.sequence_editor_create()
seq_editor = scene.sequence_editor
movie_strip = seq_editor.sequences.new_movie(
latest_file.name,
strip_filepath,
strip_channel + 1,
strip_frame_start,
fit_method="ORIGINAL",
)
sound_strip = seq_editor.sequences.new_sound(
latest_file.name,
strip_filepath,
strip_channel,
strip_frame_start,
)
new_strips = [movie_strip, sound_strip]
# Update shift frame range prop.
frame_in = shot.data.get("frame_in")
frame_3d_start = shot.get_3d_start()
frame_3d_offset = frame_3d_start - addon_prefs.shot_builder_frame_offset
edit_export_offset = addon_prefs.edit_export_frame_offset
# Set sequence strip start kitsu data.
for strip in new_strips:
strip.frame_start = (
-frame_in + (strip_frame_start * 2) + frame_3d_offset + edit_export_offset
)
return new_strips

View File

@ -1,12 +1,14 @@
import bpy import bpy
from bpy.types import Sequence, Context
import os import os
from typing import Set from typing import Set, List
from pathlib import Path from pathlib import Path
from .. import cache, prefs, util from .. import cache, prefs, util
from ..types import Task, TaskStatus from ..types import Task, TaskStatus
from ..playblast.core import override_render_path, override_render_format from ..playblast.core import override_render_path, override_render_format
from . import opsdata from . import opsdata
from ..logger import LoggerFactory from ..logger import LoggerFactory
from .core import edit_export_import_latest, edit_exports_get_all, edit_export_get_latest
logger = LoggerFactory.getLogger() logger = LoggerFactory.getLogger()
@ -52,7 +54,7 @@ class KITSU_OT_edit_render_publish(bpy.types.Operator):
cls.poll_message_set("Edit Render Directory is Invalid, see Add-On preferences") cls.poll_message_set("Edit Render Directory is Invalid, see Add-On preferences")
return False return False
if not addon_prefs.is_edit_render_pattern_valid: if not addon_prefs.is_edit_render_pattern_valid:
cls.poll_message_set("Edit Export File Pattern is Invalid, see Add-On preferences") cls.poll_message_set("Edit Render File Pattern is Invalid, see Add-On preferences")
return False return False
return True return True
@ -195,10 +197,135 @@ class KITSU_OT_edit_render_increment_version(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
class KITSU_OT_edit_render_import_latest(bpy.types.Operator):
bl_idname = "kitsu.edit_render_import_latest"
bl_label = "Import Latest Edit Render"
bl_description = "" # TODO
_existing_edit_renders = []
_removed_movie = 0
_removed_audio = 0
_latest_render_name = ""
@classmethod
def poll(cls, context: bpy.types.Context) -> bool:
if not prefs.session_auth(context):
cls.poll_message_set("Login to a Kitsu Server")
return False
if not cache.project_active_get():
cls.poll_message_set("Select an active project")
return False
if cache.shot_active_get().id == "":
cls.poll_message_set("Please set an active shot in Kitsu Context UI")
return False
if not prefs.addon_prefs_get(context).is_edit_render_root_valid:
cls.poll_message_set("Edit Render Directory is Invalid, see Add-On Preferences")
return False
return True
def get_filepath(self, strip):
if hasattr(strip, "filepath"):
return strip.filepath
if hasattr(strip, "sound"):
return strip.sound.filepath
def compare_strip_to_path(self, strip: Sequence, compare_path: Path) -> bool:
strip_path = Path(bpy.path.abspath(self.get_filepath(strip)))
return bool(compare_path.absolute() == strip_path.absolute())
def compare_strip_to_paths(self, strip: Sequence, compare_paths: List[Path]) -> bool:
for compare_path in compare_paths:
if self.compare_strip_to_path(strip, compare_path):
return True
return False
def get_existing_edit_renders(
self, context: Context, all_edit_render_paths: List[Path]
) -> List[Sequence]:
sequences = context.scene.sequence_editor.sequences
# Collect Existing Edit Renders
for strip in sequences:
if self.compare_strip_to_paths(strip, all_edit_render_paths):
self._existing_edit_renders.append(strip)
return self._existing_edit_renders
def check_if_latest_edit_render_is_imported(self, context: Context) -> bool:
# Check if latest edit render is already loaded.
for strip in self._existing_edit_renders:
latest_edit_render_path = edit_export_get_latest(context)
if self.compare_strip_to_path(strip, latest_edit_render_path):
self._latest_render_name = latest_edit_render_path.name
return True
def remove_existing_edit_renders(self, context: Context) -> None:
# Remove Existing Strips to make way for new Strip
sequences = context.scene.sequence_editor.sequences
for strip in self._existing_edit_renders:
if strip.type == "MOVIE":
self._removed_movie += 1
if strip.type == "SOUND":
self._removed_audio += 1
sequences.remove(strip)
def execute(self, context: bpy.types.Context) -> Set[str]:
# Reset Values
self._existing_edit_renders = []
self._removed_movie = 0
self._removed_audio = 0
self._latest_render_name = ""
addon_prefs = prefs.addon_prefs_get(context)
# Get paths to all edit renders
all_edit_render_paths = edit_exports_get_all(context)
if all_edit_render_paths == []:
self.report(
{"WARNING"},
f"No Edit Renders found in '{addon_prefs.edit_export_dir}' using pattern '{addon_prefs.edit_export_file_pattern}' See Add-On Preferences",
)
return {"CANCELLED"}
# Collect all existing edit renders
self.get_existing_edit_renders(context, all_edit_render_paths)
# Stop latest render is already imported
if self.check_if_latest_edit_render_is_imported(context):
self.report(
{"WARNING"},
f"Latest Editorial Render already loaded '{self._latest_render_name}'",
)
return {"CANCELLED"}
# Remove old edit renders
self.remove_existing_edit_renders(context)
# Import new edit render
shot = cache.shot_active_get()
strips = edit_export_import_latest(context, shot)
if strips is None:
self.report({"WARNING"}, f"Loaded Latest Editorial Render failed to import!")
return {"CANCELLED"}
# Report.
if self._removed_movie > 0 or self._removed_audio > 0:
removed_msg = (
f"Removed {self._removed_movie} Movie Strips and {self._removed_audio} Audio Strips"
)
self.report(
{"INFO"}, f"Loaded Latest Editorial Render, '{strips[0].name}'. {removed_msg}"
)
else:
self.report({"INFO"}, f"Loaded Latest Editorial Render, '{strips[0].name}'")
return {"FINISHED"}
classes = [ classes = [
KITSU_OT_edit_render_publish, KITSU_OT_edit_render_publish,
KITSU_OT_edit_render_set_version, KITSU_OT_edit_render_set_version,
KITSU_OT_edit_render_increment_version, KITSU_OT_edit_render_increment_version,
KITSU_OT_edit_render_import_latest,
] ]

View File

@ -6,6 +6,7 @@ from .ops import (
KITSU_OT_edit_render_set_version, KITSU_OT_edit_render_set_version,
KITSU_OT_edit_render_increment_version, KITSU_OT_edit_render_increment_version,
KITSU_OT_edit_render_publish, KITSU_OT_edit_render_publish,
KITSU_OT_edit_render_import_latest,
) )
from ..generic.ops import KITSU_OT_open_path from ..generic.ops import KITSU_OT_open_path
@ -71,9 +72,34 @@ class KITSU_PT_edit_render_publish(bpy.types.Panel):
) )
classes = [ class KITSU_PT_edit_render_tools(bpy.types.Panel):
KITSU_PT_edit_render_publish, """
] Panel in sequence editor that exposes a set of tools that are used to load the latest edit
"""
bl_category = "Kitsu"
bl_label = "General Tools"
bl_space_type = "SEQUENCE_EDITOR"
bl_region_type = "UI"
bl_options = {"DEFAULT_CLOSED"}
bl_order = 50
@classmethod
def poll(cls, context: bpy.types.Context) -> bool:
if not prefs.session_auth(context):
return False
if not (context_core.is_sequence_context() or context_core.is_shot_context()):
return False
return True
def draw(self, context: bpy.types.Context) -> None:
box = self.layout.box()
box.label(text="General", icon="MODIFIER")
box.operator(KITSU_OT_edit_render_import_latest.bl_idname)
classes = [KITSU_PT_edit_render_publish, KITSU_PT_edit_render_tools]
def register(): def register():

View File

@ -1,53 +0,0 @@
import bpy
from .. import prefs
from pathlib import Path
import re
from ..edit import core as edit_core
def edit_export_import_latest(
context: bpy.types.Context, shot
) -> list[bpy.types.Sequence]: # TODO add info to shot
"""Loads latest export from editorial department"""
addon_prefs = prefs.addon_prefs_get(context)
strip_channel = 1
latest_file = edit_core.edit_export_get_latest(context)
if not latest_file:
return None
# Check if Kitsu server returned empty shot
if shot.id == '':
return None
strip_filepath = latest_file.as_posix()
strip_frame_start = addon_prefs.shot_builder_frame_offset
scene = context.scene
if not scene.sequence_editor:
scene.sequence_editor_create()
seq_editor = scene.sequence_editor
movie_strip = seq_editor.sequences.new_movie(
latest_file.name,
strip_filepath,
strip_channel + 1,
strip_frame_start,
fit_method="ORIGINAL",
)
sound_strip = seq_editor.sequences.new_sound(
latest_file.name,
strip_filepath,
strip_channel,
strip_frame_start,
)
new_strips = [movie_strip, sound_strip]
# Update shift frame range prop.
frame_in = shot.data.get("frame_in")
frame_3d_start = shot.get_3d_start()
frame_3d_offset = frame_3d_start - addon_prefs.shot_builder_frame_offset
edit_export_offset = addon_prefs.edit_export_frame_offset
# Set sequence strip start kitsu data.
for strip in new_strips:
strip.frame_start = (
-frame_in + (strip_frame_start * 2) + frame_3d_offset + edit_export_offset
)
return new_strips

View File

@ -16,7 +16,7 @@ from .core import (
) )
from ..context import core as context_core from ..context import core as context_core
from .editorial import edit_export_import_latest from ..edit.core import edit_export_import_latest
from .file_save import save_shot_builder_file from .file_save import save_shot_builder_file
from .template import replace_workspace_with_template from .template import replace_workspace_with_template
from .assets import get_shot_assets from .assets import get_shot_assets