819 lines
28 KiB
Python
819 lines
28 KiB
Python
# SPDX-FileCopyrightText: 2021 Blender Studio Tools Authors
|
|
#
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
import hashlib
|
|
import sys
|
|
import os
|
|
|
|
from typing import Optional, Set
|
|
from pathlib import Path
|
|
|
|
import bpy
|
|
|
|
from . import cache, bkglobals, propsdata
|
|
from .props import get_safely_string_prop
|
|
|
|
# TODO: restructure this to not access ops_playblast_data.
|
|
from .playblast import opsdata as ops_playblast_data
|
|
from .types import Session
|
|
from .logger import LoggerFactory
|
|
from .auth.ops import (
|
|
KITSU_OT_session_end,
|
|
KITSU_OT_session_start,
|
|
)
|
|
from .context.ops import KITSU_OT_con_productions_load
|
|
from .lookdev.prefs import LOOKDEV_preferences
|
|
from .util import addon_prefs_get
|
|
|
|
logger = LoggerFactory.getLogger()
|
|
|
|
|
|
def draw_file_path(
|
|
self, layout: bpy.types.UILayout, bool_prop: bool, bool_prop_name: str, path_prop_name: str
|
|
) -> None:
|
|
# Draw auto-filled file path with option to override/edit this path
|
|
icon = "MODIFIER_ON" if bool_prop else "MODIFIER_OFF"
|
|
seq_row = layout.row(align=True)
|
|
seq_row.prop(self, path_prop_name)
|
|
seq_row.prop(self, bool_prop_name, icon=icon, text="")
|
|
|
|
|
|
class KITSU_task(bpy.types.PropertyGroup):
|
|
# name: StringProperty() -> Instantiated by default
|
|
id: bpy.props.StringProperty(name="Task ID", default="")
|
|
entity_id: bpy.props.StringProperty(name="Entity ID", default="")
|
|
entity_name: bpy.props.StringProperty(name="Entity Name", default="")
|
|
task_type_id: bpy.props.StringProperty(name="Task Type ID", default="")
|
|
task_type_name: bpy.props.StringProperty(name="Task Type Name", default="")
|
|
|
|
|
|
class KITSU_media_update_search_paths(bpy.types.PropertyGroup):
|
|
# name: StringProperty() -> Instantiated by default
|
|
filepath: bpy.props.StringProperty(
|
|
name="Media Update Search Path",
|
|
default="",
|
|
subtype="DIR_PATH",
|
|
description="Top level directory path in which to search for media updates",
|
|
)
|
|
|
|
|
|
class KITSU_OT_prefs_media_search_path_add(bpy.types.Operator):
|
|
""""""
|
|
|
|
bl_idname = "kitsu.prefs_media_search_path_add"
|
|
bl_label = "Add Path"
|
|
bl_description = "Adds new entry to media update search paths list"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
def execute(self, context: bpy.types.Context) -> Set[str]:
|
|
addon_prefs = addon_prefs_get(context)
|
|
media_update_search_paths = addon_prefs.media_update_search_paths
|
|
|
|
item = media_update_search_paths.add()
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
class KITSU_OT_prefs_media_search_path_remove(bpy.types.Operator):
|
|
""""""
|
|
|
|
bl_idname = "kitsu.prefs_media_search_path_remove"
|
|
bl_label = "Removes Path"
|
|
bl_description = "Removes Path from media udpate search paths list"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
index: bpy.props.IntProperty(
|
|
name="Index",
|
|
description="Refers to index that will be removed from collection property",
|
|
)
|
|
|
|
def execute(self, context: bpy.types.Context) -> Set[str]:
|
|
addon_prefs = addon_prefs_get(context)
|
|
media_update_search_paths = addon_prefs.media_update_search_paths
|
|
|
|
media_update_search_paths.remove(self.index)
|
|
|
|
return {"FINISHED"}
|
|
|
|
|
|
class KITSU_addon_preferences(bpy.types.AddonPreferences):
|
|
"""
|
|
Addon preferences to kitsu. Holds variables that are important for authentication and configuring
|
|
how some of the operators work.
|
|
"""
|
|
|
|
def get_metadatastrip_file(self) -> str:
|
|
res_dir = bkglobals.RES_DIR_PATH
|
|
return res_dir.joinpath("metastrip.mp4").as_posix()
|
|
|
|
metadatastrip_file: bpy.props.StringProperty( # type: ignore
|
|
name="Metadata Strip File",
|
|
description=(
|
|
"Filepath to black .mp4 file that will be used as metastrip for shots in the sequence editor"
|
|
),
|
|
default="",
|
|
subtype="FILE_PATH",
|
|
get=get_metadatastrip_file,
|
|
)
|
|
|
|
def get_datadir(self) -> Path:
|
|
"""Returns a Path where persistent application data can be stored.
|
|
|
|
# linux: ~/.local/share
|
|
# macOS: ~/Library/Application Support
|
|
# windows: C:/Users/<USER>/AppData/Roaming
|
|
"""
|
|
# This function is copied from the edit_breakdown addon by Inês Almeida and Francesco Siddi.
|
|
|
|
home = Path.home()
|
|
|
|
if sys.platform == "win32":
|
|
return home / "AppData/Roaming"
|
|
elif sys.platform == "linux":
|
|
return home / ".local/share"
|
|
elif sys.platform == "darwin":
|
|
return home / "Library/Application Support"
|
|
|
|
def get_thumbnails_dir(self) -> str:
|
|
"""Generate a path based on get_datadir and the current file name.
|
|
|
|
The path is constructed by combining the OS application data dir,
|
|
"blender_kitsu" and a hashed version of the filepath.
|
|
"""
|
|
# This function is copied and modified from the edit_breakdown addon by Inês Almeida and Francesco Siddi.
|
|
|
|
hashed_filepath = hashlib.md5(bpy.data.filepath.encode()).hexdigest()
|
|
storage_dir = self.get_datadir() / "blender_kitsu" / "thumbnails" / hashed_filepath
|
|
return storage_dir.as_posix()
|
|
|
|
def get_sqe_render_dir(self) -> str:
|
|
hashed_filepath = hashlib.md5(bpy.data.filepath.encode()).hexdigest()
|
|
storage_dir = self.get_datadir() / "blender_kitsu" / "sqe_render" / hashed_filepath
|
|
return storage_dir.absolute().as_posix()
|
|
|
|
def get_config_dir(self) -> str:
|
|
if not self.is_project_root_valid:
|
|
return ""
|
|
return self.project_root_path.joinpath("pipeline/blender_kitsu").as_posix()
|
|
|
|
def init_playblast_file_model(self, context: bpy.types.Context) -> None:
|
|
ops_playblast_data.init_playblast_file_model(context)
|
|
|
|
bl_idname = __package__
|
|
|
|
host: bpy.props.StringProperty( # type: ignore
|
|
name="Host",
|
|
default="",
|
|
)
|
|
|
|
email: bpy.props.StringProperty( # type: ignore
|
|
name="Email",
|
|
default="",
|
|
)
|
|
|
|
passwd: bpy.props.StringProperty( # type: ignore
|
|
name="Password", default="", options={"HIDDEN", "SKIP_SAVE"}, subtype="PASSWORD"
|
|
)
|
|
|
|
thumbnail_dir: bpy.props.StringProperty( # type: ignore
|
|
name="Thumbnail Directory",
|
|
description="Directory in which thumbnails will be saved",
|
|
default="",
|
|
subtype="DIR_PATH",
|
|
get=get_thumbnails_dir,
|
|
)
|
|
|
|
sqe_render_dir: bpy.props.StringProperty( # type: ignore
|
|
name="Sequence Editor Render Directory",
|
|
description="Directory in which sequence renders will be saved",
|
|
default="",
|
|
subtype="DIR_PATH",
|
|
get=get_sqe_render_dir,
|
|
)
|
|
|
|
lookdev: bpy.props.PointerProperty( # type: ignore
|
|
name="Lookdev Preferences",
|
|
type=LOOKDEV_preferences,
|
|
description="Metadata that is required for lookdev",
|
|
)
|
|
|
|
def set_shot_playblast_root_dir(self, input):
|
|
self.bl_system_properties_get(do_create=True)['shot_playblast_root_dir'] = input
|
|
return
|
|
|
|
def get_shot_playblast_root_dir(
|
|
self,
|
|
) -> str:
|
|
path = get_safely_string_prop(self.bl_system_properties_get(), 'shot_playblast_root_dir')
|
|
if path == "" and self.project_root_path:
|
|
dir = self.project_root_path.joinpath("shared/editorial/footage/pro/")
|
|
if dir.exists():
|
|
return dir.as_posix()
|
|
return path
|
|
|
|
shot_playblast_root_dir: bpy.props.StringProperty( # type: ignore
|
|
name="Shot Playblasts",
|
|
description="Directory path to shot playblast root folder. Should point to: {project}/editorial/footage/pro",
|
|
default="",
|
|
subtype="DIR_PATH",
|
|
update=init_playblast_file_model,
|
|
get=get_shot_playblast_root_dir,
|
|
set=set_shot_playblast_root_dir,
|
|
)
|
|
|
|
def set_seq_playblast_root_dir(self, input):
|
|
self.bl_system_properties_get(do_create=True)['seq_playblast_root_dir'] = input
|
|
return
|
|
|
|
def get_seq_playblast_root_dir(
|
|
self,
|
|
) -> str:
|
|
path = get_safely_string_prop(self.bl_system_properties_get(), 'seq_playblast_root_dir')
|
|
if path == "" and self.project_root_path:
|
|
dir = self.project_root_path.joinpath("shared/editorial/footage/pre/")
|
|
if dir.exists():
|
|
return dir.as_posix()
|
|
return path
|
|
|
|
seq_playblast_root_dir: bpy.props.StringProperty( # type: ignore
|
|
name="Sequence Playblasts",
|
|
description="Directory path to sequence playblast root folder. Should point to: {project}/editorial/footage/pre",
|
|
default="",
|
|
subtype="DIR_PATH",
|
|
get=get_seq_playblast_root_dir,
|
|
set=set_seq_playblast_root_dir,
|
|
options=set(),
|
|
)
|
|
|
|
def set_frames_root_dir(self, input):
|
|
self.bl_system_properties_get(do_create=True)['frames_root_dir'] = input
|
|
return
|
|
|
|
def get_frames_root_dir(
|
|
self,
|
|
) -> str:
|
|
path = get_safely_string_prop(self.bl_system_properties_get(), 'frames_root_dir')
|
|
if path == "" and self.project_root_path:
|
|
dir = self.project_root_path.joinpath("shared/editorial/footage/post/")
|
|
if dir.exists():
|
|
return dir.as_posix()
|
|
return path
|
|
|
|
frames_root_dir: bpy.props.StringProperty( # type: ignore
|
|
name="Rendered Frames",
|
|
description="Directory path to rendered frames root folder. Should point to: {project}/editorial/footage/post",
|
|
default="",
|
|
subtype="DIR_PATH",
|
|
get=get_frames_root_dir,
|
|
set=set_frames_root_dir,
|
|
)
|
|
|
|
project_root_dir: bpy.props.StringProperty( # type: ignore
|
|
name="Project Root Directory",
|
|
description=(
|
|
"Directory path to the root of the project"
|
|
"In this directory blender kitsu searches for the svn/ & shared/ directories"
|
|
"Directory should follow `you_project_name/` format without any subdirectories"
|
|
),
|
|
default="/data/gold/",
|
|
subtype="DIR_PATH",
|
|
)
|
|
config_dir: bpy.props.StringProperty( # type: ignore
|
|
name="Config Directory",
|
|
description=(
|
|
"Configuration directory of blender_kitsu"
|
|
"See readme.md how you can configurate the addon on a per project basis"
|
|
),
|
|
default="",
|
|
subtype="DIR_PATH",
|
|
get=get_config_dir,
|
|
)
|
|
|
|
project_active_id: bpy.props.StringProperty( # type: ignore
|
|
name="Project Active ID",
|
|
description="Server Id that refers to the last active project",
|
|
default="",
|
|
)
|
|
|
|
enable_debug: bpy.props.BoolProperty( # type: ignore
|
|
name="Enable Debug Operators",
|
|
description="Enables Operatots that provide debug functionality",
|
|
)
|
|
show_advanced: bpy.props.BoolProperty( # type: ignore
|
|
name="Show Advanced Settings",
|
|
description="Show advanced settings that should already have good defaults",
|
|
)
|
|
shot_builder_show_advanced: bpy.props.BoolProperty( # type: ignore
|
|
name="Show Advanced Settings",
|
|
description="Show advanced settings that should already have good defaults",
|
|
)
|
|
|
|
def set_shot_pattern(self, input):
|
|
self.bl_system_properties_get(do_create=True)['shot_pattern'] = input
|
|
return
|
|
|
|
def get_shot_pattern(
|
|
self,
|
|
) -> str:
|
|
active_project = cache.project_active_get()
|
|
pattern = get_safely_string_prop(self.bl_system_properties_get(), 'shot_pattern')
|
|
if pattern == "":
|
|
if active_project.production_type == bkglobals.KITSU_TV_PROJECT:
|
|
return "<Episode>_<Sequence>_<Counter>"
|
|
return "<Sequence>_<Counter>"
|
|
return pattern
|
|
|
|
shot_pattern: bpy.props.StringProperty( # type: ignore
|
|
name="Shot Pattern",
|
|
description="Pattern to define how Bulk Init will name the shots. Supported wildcards: <Project>, <Episode>, <Sequence>, <Counter>",
|
|
default="<Sequence>_<Counter>",
|
|
get=get_shot_pattern,
|
|
set=set_shot_pattern,
|
|
)
|
|
|
|
shot_counter_digits: bpy.props.IntProperty( # type: ignore
|
|
name="Shot Counter Digits",
|
|
description="How many digits the counter should contain",
|
|
default=4,
|
|
min=0,
|
|
)
|
|
shot_counter_increment: bpy.props.IntProperty( # type: ignore
|
|
name="Shot Counter Increment",
|
|
description="By which Increment counter should be increased",
|
|
default=10,
|
|
step=5,
|
|
min=0,
|
|
)
|
|
pb_open_webbrowser: bpy.props.BoolProperty( # type: ignore
|
|
name="Open Webbrowser after Playblast",
|
|
description="Toggle if the default webbrowser should be opened to kitsu after playblast creation",
|
|
default=False,
|
|
)
|
|
|
|
pb_open_vse: bpy.props.BoolProperty( # type: ignore
|
|
name="Open Sequence Editor after Playblast",
|
|
description="Toggle if the movie clip should be loaded in the seqeuence editor in a seperate scene after playblast creation",
|
|
default=False,
|
|
)
|
|
|
|
pb_manual_burn_in: bpy.props.BoolProperty( # type: ignore
|
|
name="Manual Playblast Burn-Ins",
|
|
description=(
|
|
"Blender Kitsu will override all Shot/Sequence playblasts with it's own metadata burn in. "
|
|
"This includes frame, lens, Shot name & Animator name at font size of 24. "
|
|
"To use a file's metadata burn in settings during playblast enable this option"
|
|
),
|
|
default=False,
|
|
)
|
|
|
|
media_update_search_paths: bpy.props.CollectionProperty(type=KITSU_media_update_search_paths)
|
|
|
|
production_path: bpy.props.StringProperty( # type: ignore
|
|
name="Production Root",
|
|
description="The location to load configuration files from when "
|
|
"they couldn't be found in any parent folder of the current "
|
|
"file. Folder must contain a sub-folder named `shot-builder` "
|
|
"that holds the configuration files",
|
|
subtype='DIR_PATH',
|
|
)
|
|
|
|
def set_edit_export_dir(self, input):
|
|
self.bl_system_properties_get(do_create=True)['edit_export_dir'] = input
|
|
return
|
|
|
|
def get_edit_export_dir(
|
|
self,
|
|
) -> str:
|
|
path = get_safely_string_prop(self.bl_system_properties_get(), 'edit_export_dir')
|
|
active_project = cache.project_active_get()
|
|
if path == "" and self.project_root_path:
|
|
if active_project.production_type == bkglobals.KITSU_TV_PROJECT:
|
|
dir = self.project_root_path.joinpath("shared/editorial/export/<episode>/")
|
|
else:
|
|
dir = self.project_root_path.joinpath("shared/editorial/export/")
|
|
return dir.as_posix()
|
|
return path
|
|
|
|
edit_export_dir: bpy.props.StringProperty( # type: ignore
|
|
name="Edit Export Directory",
|
|
options={"HIDDEN", "SKIP_SAVE"},
|
|
description="Directory path to editorial's export folder containing storyboard/animatic renders. Path should be similar to '{project}/shared/editorial/export/'",
|
|
subtype="DIR_PATH",
|
|
get=get_edit_export_dir,
|
|
set=set_edit_export_dir,
|
|
)
|
|
|
|
def set_edit_export_file_pattern(self, input):
|
|
self.bl_system_properties_get(do_create=True)['edit_export_file_pattern'] = input
|
|
return
|
|
|
|
def get_edit_export_file_pattern(
|
|
self,
|
|
) -> str:
|
|
active_project = cache.project_active_get()
|
|
pattern = get_safely_string_prop(self.bl_system_properties_get(), 'edit_export_file_pattern')
|
|
if pattern == "" and active_project:
|
|
proj_name = active_project.name.replace(' ', bkglobals.SPACE_REPLACER).lower()
|
|
# HACK for Project Gold at Blender Studio
|
|
if proj_name == "project_gold":
|
|
return f"gold-edit-v###.mp4"
|
|
return f"{proj_name}-edit-v###.mp4"
|
|
return pattern
|
|
|
|
edit_export_file_pattern: bpy.props.StringProperty( # type: ignore
|
|
name="Edit Export File Pattern",
|
|
options={"HIDDEN", "SKIP_SAVE"},
|
|
description=(
|
|
"File pattern for latest editorial export file. "
|
|
"Typically '{proj_name}-edit-v###.mp4' where # represents a number. "
|
|
"Pattern must contain exactly v### representing the version, pattern must end in .mp4"
|
|
),
|
|
default="",
|
|
get=get_edit_export_file_pattern,
|
|
set=set_edit_export_file_pattern,
|
|
)
|
|
|
|
edit_export_frame_offset: bpy.props.IntProperty( # type: ignore
|
|
name="Edit Export Offset",
|
|
description="Shift Editorial Export by this frame-range after import",
|
|
default=-101,
|
|
)
|
|
|
|
shot_builder_frame_offset: bpy.props.IntProperty( # type: ignore
|
|
name="Start Frame Offset",
|
|
description="All Shots built by 'Shot_builder' should begin at this frame",
|
|
default=101,
|
|
)
|
|
|
|
session: Session = Session()
|
|
|
|
tasks: bpy.props.CollectionProperty(type=KITSU_task)
|
|
|
|
version_control: bpy.props.BoolProperty( # type: ignore
|
|
name="Use Version Control",
|
|
description="Indicates if SVN or GIT-LFS is being used for version control. If disabled local backups of production files will be created when Publishing to Kitsu",
|
|
default=True,
|
|
)
|
|
|
|
####################
|
|
# Render Review
|
|
####################
|
|
|
|
def set_farm_dir(self, input):
|
|
self.bl_system_properties_get(do_create=True)['farm_output_dir'] = input
|
|
return
|
|
|
|
def get_farm_dir(
|
|
self,
|
|
) -> str:
|
|
path = get_safely_string_prop(self.bl_system_properties_get(), 'farm_output_dir')
|
|
if path == "" and self.project_root_path:
|
|
dir = self.project_root_path.joinpath("render/")
|
|
if dir.exists():
|
|
return dir.as_posix()
|
|
return path
|
|
|
|
farm_output_dir: bpy.props.StringProperty( # type: ignore
|
|
name="Farm Output Directory",
|
|
description="Directory used as 'Render Output Root' when submitting job to Flamenco. Usually points to: {project}/render/",
|
|
default="",
|
|
subtype="DIR_PATH",
|
|
get=get_farm_dir,
|
|
set=set_farm_dir,
|
|
)
|
|
|
|
shot_dir_name: bpy.props.StringProperty(
|
|
name="Shot Directory Name",
|
|
description="Name of the shot directory",
|
|
default="shots"
|
|
)
|
|
|
|
seq_dir_name: bpy.props.StringProperty(
|
|
name="Sequence Directory Name",
|
|
description="Name of the sequence directory",
|
|
default="sequences"
|
|
)
|
|
|
|
asset_dir_name: bpy.props.StringProperty(
|
|
name="Asset Directory Name",
|
|
description="Name of the asset directory",
|
|
default="assets"
|
|
)
|
|
|
|
edit_dir_name: bpy.props.StringProperty(
|
|
name="Edit Directory Name",
|
|
description="Name of the edit directory",
|
|
default="edit"
|
|
)
|
|
|
|
shot_name_filter: bpy.props.StringProperty( # type: ignore
|
|
name="Shot Name Filter",
|
|
description="Shot name must include this string, otherwise it will be ignored",
|
|
default="",
|
|
)
|
|
use_video: bpy.props.BoolProperty(
|
|
name="Use Video",
|
|
description="Load video versions of renders rather than image sequences for faster playback",
|
|
)
|
|
use_video_latest_only: bpy.props.BoolProperty(
|
|
default=True,
|
|
name="Latest Only",
|
|
description="Only load video files for the latest versions by default, to avoid running out of memory and crashing",
|
|
)
|
|
|
|
match_latest_length: bpy.props.BoolProperty( # type: ignore
|
|
default=True,
|
|
name="Match Latest Length",
|
|
description="Only include renders that are matching the last rendered length for a given shot, (i.e. missing frames)",
|
|
)
|
|
|
|
versions_max_count: bpy.props.IntProperty(
|
|
name="Max Versions",
|
|
description="Desired number of versions to load for each shot",
|
|
min=1,
|
|
max=128,
|
|
default=32,
|
|
)
|
|
|
|
def draw(self, context: bpy.types.Context) -> None:
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
layout.use_property_decorate = False
|
|
flow = layout.grid_flow(
|
|
row_major=True, columns=0, even_columns=True, even_rows=True, align=False
|
|
)
|
|
col = flow.column()
|
|
project_active = cache.project_active_get()
|
|
|
|
# Login.
|
|
box = col.box()
|
|
box.label(text="Login and Host Settings", icon="URL")
|
|
if not self.session.is_auth():
|
|
box.row().prop(self, "host")
|
|
box.row().prop(self, "email")
|
|
box.row().prop(self, "passwd")
|
|
box.row().operator(KITSU_OT_session_start.bl_idname, text="Login", icon="PLAY")
|
|
else:
|
|
row = box.row()
|
|
row.prop(self, "host")
|
|
row.enabled = False
|
|
box.row().label(text=f"Logged in: {self.session.email}")
|
|
box.row().operator(KITSU_OT_session_end.bl_idname, text="Logout", icon="PANEL_CLOSE")
|
|
|
|
# Project
|
|
box = col.box()
|
|
box.label(text="Project", icon="FILEBROWSER")
|
|
row = box.row(align=True)
|
|
|
|
if not project_active:
|
|
prod_load_text = "Select Project"
|
|
else:
|
|
prod_load_text = project_active.name
|
|
|
|
row.operator(
|
|
KITSU_OT_con_productions_load.bl_idname,
|
|
text=prod_load_text,
|
|
icon="DOWNARROW_HLT",
|
|
)
|
|
box.row().prop(self, "project_root_dir")
|
|
box.prop(self, 'shot_dir_name')
|
|
box.prop(self, 'seq_dir_name')
|
|
box.prop(self, 'asset_dir_name')
|
|
box.prop(self, 'edit_dir_name')
|
|
|
|
# Previews
|
|
box = col.box()
|
|
box.label(text="Previews", icon="RENDER_ANIMATION")
|
|
box.row().prop(self, "shot_playblast_root_dir")
|
|
box.row().prop(self, "seq_playblast_root_dir")
|
|
box.row().prop(self, "frames_root_dir")
|
|
box.row().prop(self, "pb_open_webbrowser")
|
|
box.row().prop(self, "pb_open_vse")
|
|
box.row().prop(self, "pb_manual_burn_in")
|
|
|
|
# Editorial Settings
|
|
box = col.box()
|
|
box.label(text="Editorial", icon="SEQ_SEQUENCER")
|
|
box.row().prop(self, "edit_export_dir", text="Export Directory")
|
|
file_pattern_row = box.row(align=True)
|
|
file_pattern_row.alert = not self.is_edit_export_pattern_valid
|
|
file_pattern_row.prop(self, "edit_export_file_pattern", text="Export File Pattern")
|
|
box.row().prop(self, "edit_export_frame_offset")
|
|
|
|
# Render Review
|
|
self.draw_render_review(col)
|
|
|
|
# Shot_Builder settings.
|
|
box = col.box()
|
|
box.label(text="Shot Builder", icon="MOD_BUILD")
|
|
box.prop(self, "shot_builder_frame_offset")
|
|
row = box.row(align=True)
|
|
# Avoids circular import error
|
|
from .shot_builder.ops import (
|
|
KITSU_OT_build_config_save_settings,
|
|
KITSU_OT_build_config_save_hooks,
|
|
KITSU_OT_build_config_save_templates,
|
|
)
|
|
|
|
box.row().operator(KITSU_OT_build_config_save_hooks.bl_idname, icon='FILE_SCRIPT')
|
|
box.row().operator(KITSU_OT_build_config_save_settings.bl_idname, icon="TEXT")
|
|
box.row().operator(KITSU_OT_build_config_save_templates.bl_idname, icon="FILE_BLEND")
|
|
|
|
# Lookdev tools settings.
|
|
self.lookdev.draw(context, col)
|
|
|
|
# Sequence editor include paths.
|
|
box = col.box()
|
|
box.label(text="Media Update Search Paths", icon="SEQUENCE")
|
|
box.label(
|
|
text="Only the movie strips that have their source media coming from one of these folders (recursive) will be checked for media updates"
|
|
)
|
|
|
|
for i, item in enumerate(self.media_update_search_paths):
|
|
row = box.row()
|
|
row.prop(item, "filepath", text="")
|
|
row.operator(
|
|
KITSU_OT_prefs_media_search_path_remove.bl_idname,
|
|
text="",
|
|
icon="X",
|
|
emboss=False,
|
|
).index = i
|
|
row = box.row()
|
|
row.alignment = "LEFT"
|
|
row.operator(
|
|
KITSU_OT_prefs_media_search_path_add.bl_idname,
|
|
text="",
|
|
icon="ADD",
|
|
emboss=False,
|
|
)
|
|
|
|
# Misc settings.
|
|
box = col.box()
|
|
box.label(text="Miscellaneous", icon="MODIFIER")
|
|
box.row().prop(self, "thumbnail_dir")
|
|
box.row().prop(self, "sqe_render_dir")
|
|
box.row().prop(self, "enable_debug")
|
|
box.row().prop(self, "show_advanced")
|
|
|
|
if self.show_advanced:
|
|
box.row().prop(self, "shot_pattern")
|
|
box.row().prop(self, "shot_counter_digits")
|
|
box.row().prop(self, "shot_counter_increment")
|
|
box.row().prop(self, "version_control")
|
|
|
|
def draw_render_review(self, layout: bpy.types.UILayout) -> None:
|
|
box = layout.box()
|
|
box.label(text="Render Review", icon="FILEBROWSER")
|
|
|
|
# Farm outpur dir.
|
|
box.row().prop(self, "farm_output_dir")
|
|
|
|
if not self.farm_output_dir:
|
|
row = box.row()
|
|
row.label(text="Please specify the Farm Output Directory", icon="ERROR")
|
|
|
|
if not bpy.data.filepath and self.farm_output_dir.startswith("//"):
|
|
row = box.row()
|
|
row.label(
|
|
text="In order to use a relative path the current file needs to be saved.",
|
|
icon="ERROR",
|
|
)
|
|
|
|
box.row().prop(self, "shot_name_filter")
|
|
box.row().prop(self, "versions_max_count", slider=True)
|
|
|
|
@property
|
|
def shot_playblast_root_path(self) -> Optional[Path]:
|
|
if not self.is_playblast_root_valid:
|
|
return None
|
|
return Path(os.path.abspath(bpy.path.abspath(self.shot_playblast_root_dir)))
|
|
|
|
@property
|
|
def seq_playblast_root_path(self) -> Optional[Path]:
|
|
if not self.is_playblast_root_valid:
|
|
return None
|
|
return Path(os.path.abspath(bpy.path.abspath(self.seq_playblast_root_dir)))
|
|
|
|
@property
|
|
def is_playblast_root_valid(self) -> bool:
|
|
if not self.shot_playblast_root_dir:
|
|
return False
|
|
|
|
if not bpy.data.filepath and self.shot_playblast_root_dir.startswith("//"):
|
|
return False
|
|
|
|
return True
|
|
|
|
@property
|
|
def is_edit_export_root_valid(self) -> bool:
|
|
if self.edit_export_dir.strip() == "":
|
|
return False
|
|
edit_export_path = Path(self.edit_export_dir)
|
|
if '<episode>' in edit_export_path.parts:
|
|
edit_export_path = propsdata.get_edit_export_dir()
|
|
if edit_export_path.parent.exists():
|
|
return True
|
|
if not edit_export_path.exists():
|
|
return False
|
|
return True
|
|
|
|
@property
|
|
def is_edit_export_pattern_valid(self) -> bool:
|
|
if not self.edit_export_file_pattern.endswith(".mp4"):
|
|
return False
|
|
if not "###" in self.edit_export_file_pattern:
|
|
return False
|
|
return True
|
|
|
|
@property
|
|
def project_root_path(self) -> Optional[Path]:
|
|
if not self.project_root_dir:
|
|
return None
|
|
return Path(os.path.abspath(bpy.path.abspath(self.project_root_dir)))
|
|
|
|
@property
|
|
def is_project_root_valid(self) -> bool:
|
|
if not self.project_root_dir:
|
|
return False
|
|
|
|
if not bpy.data.filepath and self.project_root_dir.startswith("//"):
|
|
return False
|
|
|
|
return True
|
|
|
|
@property
|
|
def is_config_dir_valid(self) -> bool:
|
|
if not self.config_dir:
|
|
return False
|
|
|
|
if not bpy.data.filepath and self.config_dir.startswith("//"):
|
|
return False
|
|
|
|
return True
|
|
|
|
@property
|
|
def farm_output_path(self) -> Optional[Path]:
|
|
if not self.is_farm_output_valid:
|
|
return None
|
|
return Path(os.path.abspath(bpy.path.abspath(self.farm_output_dir)))
|
|
|
|
@property
|
|
def is_farm_output_valid(self) -> bool:
|
|
|
|
# Check if file is saved.
|
|
if not self.farm_output_dir:
|
|
return False
|
|
|
|
if not bpy.data.filepath and self.farm_output_dir.startswith("//"):
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def session_get(context: bpy.types.Context) -> Session:
|
|
"""
|
|
Shortcut to get session from blender_kitsu addon preferences
|
|
"""
|
|
prefs = context.preferences.addons[__package__].preferences
|
|
return prefs.session # type: ignore
|
|
|
|
|
|
def project_root_dir_get(context: bpy.types.Context):
|
|
addon_prefs = addon_prefs_get(context)
|
|
return Path(addon_prefs.project_root_dir).joinpath('svn').resolve()
|
|
|
|
|
|
def session_auth(context: bpy.types.Context) -> bool:
|
|
"""
|
|
Shortcut to check if session is authorized
|
|
"""
|
|
return session_get(context).is_auth()
|
|
|
|
|
|
# ---------REGISTER ----------.
|
|
|
|
classes = [
|
|
KITSU_OT_prefs_media_search_path_remove,
|
|
KITSU_OT_prefs_media_search_path_add,
|
|
KITSU_task,
|
|
KITSU_media_update_search_paths,
|
|
KITSU_addon_preferences,
|
|
]
|
|
|
|
|
|
def register():
|
|
for cls in classes:
|
|
bpy.utils.register_class(cls)
|
|
|
|
|
|
def unregister():
|
|
# Log user out.
|
|
addon_prefs = bpy.context.preferences.addons[__package__].preferences
|
|
if addon_prefs.session.is_auth():
|
|
addon_prefs.session.end()
|
|
|
|
# Unregister classes.
|
|
for cls in reversed(classes):
|
|
bpy.utils.unregister_class(cls)
|