Blender Kitsu: Include Episode in Shot Builder Paths & Improve 3d_start #192

Merged
Nick Alberelli merged 6 commits from TinyNick/blender-studio-pipeline:feature/shot-builder-episode-support into main 2024-01-05 21:27:24 +01:00
11 changed files with 59 additions and 82 deletions

View File

@ -34,7 +34,9 @@ Sets are more tricky to define, since they can differ even on a shot level. In p
## Shots ## Shots
**Location:** `{project root}/pro/shots/{sequence number}/{shot identifier}/{shot identifier}-{task identifier}.blend` **Standard Location:** `{project root}/pro/shots/{sequence number}/{shot identifier}/{shot identifier}-{task identifier}.blend`
**TV Show Location:** `{project root}/pro/shots/{episode identifier}/{sequence number}/{shot identifier}/{shot identifier}-{task identifier}.blend`
## ##
Example: `prop-dresser_wood.faded-modeling.png` Example: `prop-dresser_wood.faded-modeling.png`

View File

@ -49,7 +49,7 @@ class KITSU_PT_vi3d_anim_tools(bpy.types.Panel):
def poll(cls, context: bpy.types.Context) -> bool: def poll(cls, context: bpy.types.Context) -> bool:
return bool( return bool(
prefs.session_auth(context) prefs.session_auth(context)
and cache.task_type_active_get().name == 'Animation' and cache.task_type_active_get().name in ['Animation', 'Layout']
) )
def draw(self, context: bpy.types.Context) -> None: def draw(self, context: bpy.types.Context) -> None:

View File

@ -87,7 +87,6 @@ class KITSU_OT_con_episodes_load(bpy.types.Operator):
return bool(prefs.session_auth(context) and cache.project_active_get()) return bool(prefs.session_auth(context) and cache.project_active_get())
def execute(self, context: bpy.types.Context) -> Set[str]: def execute(self, context: bpy.types.Context) -> Set[str]:
# Store vars to check if project / seq / shot changed. # Store vars to check if project / seq / shot changed.
zep_prev_id = cache.episode_active_get().id zep_prev_id = cache.episode_active_get().id
@ -303,7 +302,17 @@ class KITSU_OT_con_detect_context(bpy.types.Operator):
# Update kitsu metadata. # Update kitsu metadata.
filepath = Path(bpy.data.filepath) filepath = Path(bpy.data.filepath)
active_project = cache.project_active_get() active_project = cache.project_active_get()
# TODO REFACTOR THIS WHOLE THING, BAD HACK
# Path is different for tvshow
if (
active_project.production_type == 'tvshow'
and filepath.parents[3].name == bkglobals.SHOT_DIR_NAME
):
category = filepath.parents[3].name
else:
category = filepath.parents[2].name category = filepath.parents[2].name
item_group = filepath.parents[1].name item_group = filepath.parents[1].name
item = filepath.parents[0].name item = filepath.parents[0].name
item_task_type = filepath.stem.split(bkglobals.FILE_DELIMITER)[-1] item_task_type = filepath.stem.split(bkglobals.FILE_DELIMITER)[-1]

View File

@ -288,14 +288,9 @@ def get_frame_range(): # TODO return type
# Pull update for shot. # Pull update for shot.
cache.shot_active_pull_update() cache.shot_active_pull_update()
if "3d_start" not in active_shot.data: kitsu_3d_start = active_shot.get_3d_start()
logger.warning( frame_in = kitsu_3d_start
"Failed to check frame range. Shot %s missing '3d_start' attribute on server", frame_out = kitsu_3d_start + int(active_shot.nb_frames) - 1
active_shot.name,
)
return
frame_in = int(active_shot.data["3d_start"])
frame_out = int(active_shot.data["3d_start"]) + int(active_shot.nb_frames) - 1
return frame_in, frame_out return frame_in, frame_out

View File

@ -493,12 +493,11 @@ def draw_frame_range_warning(self, context):
layout.label( layout.label(
text="Frame Range on server does not match the active shot. Please 'pull' the correct frame range from the server" text="Frame Range on server does not match the active shot. Please 'pull' the correct frame range from the server"
) )
layout.label( layout.label(text=f" File Frame Range: {context.scene.frame_start}-{context.scene.frame_end}")
text=f" File Frame Range: {context.scene.frame_start}-{context.scene.frame_end}"
)
if active_shot: if active_shot:
kitsu_3d_start = active_shot.get_3d_start()
layout.label( layout.label(
text=f' Server Frame Range: {int(active_shot.data["3d_start"])}-{int(active_shot.data["3d_start"]) + int(active_shot.nb_frames) - 1}' text=f'Server Frame Range: {kitsu_3d_start}-{kitsu_3d_start + int(active_shot.nb_frames) - 1}'
) )
else: else:
layout.label(text=f' Server Frame Range: not found') layout.label(text=f' Server Frame Range: not found')

View File

@ -55,9 +55,7 @@ def remove_all_data():
for obj in bpy.data.objects: for obj in bpy.data.objects:
bpy.data.objects.remove(obj) bpy.data.objects.remove(obj)
bpy.ops.outliner.orphans_purge( bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True)
do_local_ids=True, do_linked_ids=True, do_recursive=True
)
def set_shot_scene(context: bpy.types.Context, scene_name: str) -> bpy.types.Scene: def set_shot_scene(context: bpy.types.Context, scene_name: str) -> bpy.types.Scene:
@ -84,19 +82,12 @@ def set_resolution_and_fps(project: Project, scene: bpy.types.Scene):
scene.render.resolution_percentage = 100 scene.render.resolution_percentage = 100
def get_3d_start(shot: Shot):
if shot.data and shot.data.get("3d_start"): # shot.data and
return int(shot.data.get("3d_start"))
else:
return bkglobals.FRAME_START
def set_frame_range(shot: Shot, scene: bpy.types.Scene): def set_frame_range(shot: Shot, scene: bpy.types.Scene):
start_3d = get_3d_start(shot) kitsu_start_3d = shot.get_3d_start()
scene.frame_start = start_3d scene.frame_start = kitsu_start_3d
if not shot.nb_frames: if not shot.nb_frames:
raise Exception(f"{shot.name} has missing frame duration information") raise Exception(f"{shot.name} has missing frame duration information")
scene.frame_end = start_3d + shot.nb_frames - 1 scene.frame_end = kitsu_start_3d + shot.nb_frames - 1
def link_data_block(file_path: str, data_block_name: str, data_block_type: str): def link_data_block(file_path: str, data_block_name: str, data_block_type: str):
@ -151,9 +142,7 @@ def link_camera_rig(
output_collection.objects.link(camera_object) output_collection.objects.link(camera_object)
return return
collection_name = ( collection_name = "CA-camera_rig" # TODO Rename the asset itself, this breaks convention
"CA-camera_rig" # TODO Rename the asset itself, this breaks convention
)
override_camera_col = link_and_override_collection( override_camera_col = link_and_override_collection(
file_path=path, collection_name=collection_name, scene=scene file_path=path, collection_name=collection_name, scene=scene
@ -180,9 +169,7 @@ def create_task_type_output_collection(
scene.collection.children.link(output_collection) scene.collection.children.link(output_collection)
for view_layer in scene.view_layers: for view_layer in scene.view_layers:
view_layer_output_collection = view_layer.layer_collection.children.get( view_layer_output_collection = view_layer.layer_collection.children.get(output_col_name)
output_col_name
)
view_layer_output_collection.exclude = True view_layer_output_collection.exclude = True
return output_collection return output_collection
@ -192,11 +179,9 @@ def link_task_type_output_collections(shot: Shot, task_type: TaskType):
if bkglobals.OUTPUT_COL_LINK_MAPPING.get(task_type_short_name) == None: if bkglobals.OUTPUT_COL_LINK_MAPPING.get(task_type_short_name) == None:
return return
for short_name in bkglobals.OUTPUT_COL_LINK_MAPPING.get(task_type_short_name): for short_name in bkglobals.OUTPUT_COL_LINK_MAPPING.get(task_type_short_name):
external_filepath = shot.get_shot_filepath(bpy.context, short_name) external_filepath = shot.get_filepath(bpy.context, short_name)
if not Path(external_filepath).exists(): if not Path(external_filepath).exists():
print( print(f"Unable to link output collection for {Path(external_filepath).name}")
f"Unable to link output collection for {Path(external_filepath).name}"
)
file_path = external_filepath.__str__() file_path = external_filepath.__str__()
colection_name = shot.get_output_collection_name(short_name) colection_name = shot.get_output_collection_name(short_name)
link_data_block(file_path, colection_name, 'Collection') link_data_block(file_path, colection_name, 'Collection')

View File

@ -2,7 +2,6 @@ import bpy
from .. import prefs from .. import prefs
from pathlib import Path from pathlib import Path
import re import re
from .core import get_3d_start
def editorial_export_get_latest( def editorial_export_get_latest(
@ -41,7 +40,7 @@ def editorial_export_get_latest(
# Update shift frame range prop. # Update shift frame range prop.
frame_in = shot.data.get("frame_in") frame_in = shot.data.get("frame_in")
frame_3d_start = get_3d_start(shot) frame_3d_start = shot.get_3d_start()
frame_3d_offset = frame_3d_start - addon_prefs.shot_builder_frame_offset frame_3d_offset = frame_3d_start - addon_prefs.shot_builder_frame_offset
edit_export_offset = addon_prefs.edit_export_frame_offset edit_export_offset = addon_prefs.edit_export_frame_offset

View File

@ -23,9 +23,7 @@ from .hooks import Hooks
active_project = None active_project = None
def get_shots_for_seq( def get_shots_for_seq(self: Any, context: bpy.types.Context) -> List[Tuple[str, str, str]]:
self: Any, context: bpy.types.Context
) -> List[Tuple[str, str, str]]:
if self.seq_id != '': if self.seq_id != '':
seq = active_project.get_sequence(self.seq_id) seq = active_project.get_sequence(self.seq_id)
shot_enum = cache.get_shots_enum_for_seq(self, context, seq) shot_enum = cache.get_shots_enum_for_seq(self, context, seq)
@ -34,9 +32,7 @@ def get_shots_for_seq(
return [('NONE', "No Shots Found", '')] return [('NONE', "No Shots Found", '')]
def get_tasks_for_shot( def get_tasks_for_shot(self: Any, context: bpy.types.Context) -> List[Tuple[str, str, str]]:
self: Any, context: bpy.types.Context
) -> List[Tuple[str, str, str]]:
global active_project global active_project
if not (self.shot_id == '' or self.shot_id == 'NONE'): if not (self.shot_id == '' or self.shot_id == 'NONE'):
shot = active_project.get_shot(self.shot_id) shot = active_project.get_shot(self.shot_id)
@ -186,9 +182,7 @@ class KITSU_OT_build_new_shot(bpy.types.Operator):
self.production_name = project.name self.production_name = project.name
return cast( return cast(Set[str], context.window_manager.invoke_props_dialog(self, width=400))
Set[str], context.window_manager.invoke_props_dialog(self, width=400)
)
def _get_task_type_for_shot(self, context, shot): def _get_task_type_for_shot(self, context, shot):
for task_type in shot.get_all_task_types(): for task_type in shot.get_all_task_types():
@ -202,13 +196,13 @@ class KITSU_OT_build_new_shot(bpy.types.Operator):
shot = active_project.get_shot(self.shot_id) shot = active_project.get_shot(self.shot_id)
task_type = self._get_task_type_for_shot(context, shot) task_type = self._get_task_type_for_shot(context, shot)
task_type_short_name = task_type.get_short_name() task_type_short_name = task_type.get_short_name()
shot_file_path_str = shot.get_shot_filepath(context, task_type_short_name) shot_file_path_str = shot.get_filepath(context, task_type_short_name)
# Open Template File # Open Template File
replace_workspace_with_template(context, task_type_short_name) replace_workspace_with_template(context, task_type_short_name)
# Set Up Scene + Naming # Set Up Scene + Naming
shot_task_name = shot.get_shot_task_name(task_type.get_short_name()) shot_task_name = shot.get_task_name(task_type.get_short_name())
scene = set_shot_scene(context, shot_task_name) scene = set_shot_scene(context, shot_task_name)
remove_all_data() remove_all_data()
set_resolution_and_fps(active_project, scene) set_resolution_and_fps(active_project, scene)
@ -250,9 +244,7 @@ class KITSU_OT_build_new_shot(bpy.types.Operator):
if self.save_file: if self.save_file:
save_shot_builder_file(file_path=shot_file_path_str) save_shot_builder_file(file_path=shot_file_path_str)
self.report( self.report({"INFO"}, f"Successfully Built Shot:`{shot.name}` Task: `{task_type.name}`")
{"INFO"}, f"Successfully Built Shot:`{shot.name}` Task: `{task_type.name}`"
)
return {"FINISHED"} return {"FINISHED"}

View File

@ -1868,22 +1868,8 @@ class KITSU_OT_sqe_pull_edit(bpy.types.Operator):
def _apply_strip_slip_from_shot( def _apply_strip_slip_from_shot(
self, context: bpy.types.Context, strip: bpy.types.Sequence, shot: Shot self, context: bpy.types.Context, strip: bpy.types.Sequence, shot: Shot
) -> None: ) -> None:
if "3d_start" not in shot.data:
logger.warning(
"%s no update to frame_start_offset. '3d_start' key not in shot.data",
shot.name,
)
return
if not shot.data["3d_start"]:
logger.warning(
"%s no update to frame_start_offset. '3d_start' key invalid value: %i",
shot.name,
shot.data["3d_start"],
)
return
# get offset # get offset
offset = strip.kitsu_frame_start - int(shot.data["3d_start"]) offset = strip.kitsu_frame_start - int(shot.get_3d_start())
# Deselect everything. # Deselect everything.
if context.selected_sequences: if context.selected_sequences:

View File

@ -33,11 +33,7 @@ logger = LoggerFactory.getLogger()
def shot_meta(strip: bpy.types.Sequence, shot: Shot) -> None: def shot_meta(strip: bpy.types.Sequence, shot: Shot) -> None:
# Update shot info. # Update shot info.
# Only set 3d_start if none is found kitsu_3d_start = shot.get_3d_start()
try:
kitsu_3d_start = shot.data["3d_start"]
except:
kitsu_3d_start = bkglobals.FRAME_START
shot.name = strip.kitsu.shot_name shot.name = strip.kitsu.shot_name
shot.description = strip.kitsu.shot_description shot.description = strip.kitsu.shot_description
shot.data["frame_in"] = strip.frame_final_start shot.data["frame_in"] = strip.frame_final_start

View File

@ -596,22 +596,36 @@ class Shot(Entity):
gazu.shot.update_shot(asdict(self)) gazu.shot.update_shot(asdict(self))
return self return self
def get_shot_task_name(self, task_type_short_name: str) -> str: # def get_3d_start(self) -> int:
try:
logger.info(f"3d_start not found on server, defaulting to '{bkglobals.FRAME_START}'")
return int(self.data["3d_start"])
except:
return bkglobals.FRAME_START
def get_task_name(self, task_type_short_name: str) -> str: #
return f"{self.name}{bkglobals.FILE_DELIMITER}{task_type_short_name}" return f"{self.name}{bkglobals.FILE_DELIMITER}{task_type_short_name}"
def get_output_collection_name(self, task_type_short_name: str) -> str: def get_output_collection_name(self, task_type_short_name: str) -> str:
return f"{self.get_shot_task_name(task_type_short_name)}{bkglobals.FILE_DELIMITER}output" return f"{self.get_task_name(task_type_short_name)}{bkglobals.FILE_DELIMITER}output"
def get_shot_dir(self, context) -> str: def get_dir(self, context) -> str:
project_root_dir = prefs.project_root_dir_get(context) project_root_dir = prefs.project_root_dir_get(context)
all_shots_dir = project_root_dir.joinpath('pro').joinpath('shots') all_shots_dir = project_root_dir.joinpath('pro').joinpath('shots')
# Add Episode to Path if avaliable
if self.episode_id:
base_dir = all_shots_dir.joinpath(self.episode_name)
else:
base_dir = all_shots_dir
seq = self.get_sequence() seq = self.get_sequence()
shot_dir = all_shots_dir.joinpath(seq.name).joinpath(self.name) shot_dir = base_dir.joinpath(seq.name).joinpath(self.name)
return shot_dir.__str__() return shot_dir.__str__()
def get_shot_filepath(self, context, task_type_short_name: str) -> str: def get_filepath(self, context, task_type_short_name: str) -> str:
file_name = self.get_shot_task_name(task_type_short_name) + '.blend' file_name = self.get_task_name(task_type_short_name) + '.blend'
return Path(self.get_shot_dir(context)).joinpath(file_name).__str__() return Path(self.get_dir(context)).joinpath(file_name).__str__()
def update_data(self, data: Dict[str, Any]) -> Shot: def update_data(self, data: Dict[str, Any]) -> Shot:
gazu.shot.update_shot_data(asdict(self), data=data) gazu.shot.update_shot_data(asdict(self), data=data)