2025-12-01
This commit is contained in:
@@ -0,0 +1,818 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user