From 20d15983a4abcad9ea97830ac377cc8cd0681dfa Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Thu, 28 Mar 2024 15:23:34 -0400 Subject: [PATCH 01/16] Add Playblast Dir/File to Shot Class --- scripts-blender/addons/blender_kitsu/types.py | 46 ++++++++++++++++--- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/types.py b/scripts-blender/addons/blender_kitsu/types.py index 1fa37ceb..88a56858 100644 --- a/scripts-blender/addons/blender_kitsu/types.py +++ b/scripts-blender/addons/blender_kitsu/types.py @@ -27,6 +27,8 @@ import gazu from .logger import LoggerFactory from . import bkglobals from . import prefs +from .models import FileListModel +import mimetypes logger = LoggerFactory.getLogger() @@ -614,24 +616,54 @@ class Shot(Entity): def get_output_collection_name(self, task_type_short_name: str) -> str: return f"{self.get_task_name(task_type_short_name)}{bkglobals.DELIMITER}output" - def get_dir(self, context) -> str: - project_root_dir = prefs.project_root_dir_get(context) - all_shots_dir = project_root_dir.joinpath('pro').joinpath('shots') - + def get_shot_folder_tree(self, base_path: Path) -> str: # Add Episode to Path if available if self.episode_id: - base_dir = all_shots_dir.joinpath(self.episode_name) + base_dir = base_path.joinpath(self.episode_name) else: - base_dir = all_shots_dir + base_dir = base_path seq = self.get_sequence() shot_dir = base_dir.joinpath(seq.name).joinpath(self.name) - return shot_dir.__str__() + return shot_dir + + def get_dir(self, context) -> str: + project_root_dir = prefs.project_root_dir_get(context) + all_shots_dir = project_root_dir.joinpath('pro').joinpath('shots') + return str(self.get_shot_folder_tree(all_shots_dir)) def get_filepath(self, context, task_type_short_name: str) -> str: file_name = self.get_task_name(task_type_short_name) + '.blend' return Path(self.get_dir(context)).joinpath(file_name).__str__() + def get_playblast_dir(self, context, task_type_short_name: str) -> str: + addon_prefs = prefs.addon_prefs_get(context) + playblsat_dir = addon_prefs.shot_playblast_root_dir + shot_dir = self.get_shot_folder_tree(Path(playblsat_dir)) + task_dir = shot_dir.joinpath(self.name + bkglobals.DELIMITER + task_type_short_name) + return task_dir.__str__() + + def get_latest_playblast_file(self, context, task_type_short_name: str): + filemodel = FileListModel() + filemodel.reset() + playblast_dir = Path(self.get_playblast_dir(context, task_type_short_name)) + filemodel.root_path = playblast_dir + if len(filemodel.items) < 1: + return + + playblast_files = set() + for file in filemodel.items: + filepath = playblast_dir.joinpath(file) + if mimetypes.guess_type(filepath)[0].startswith('video'): + playblast_files.add(filepath) + + playblast_files = sorted(playblast_files, key=lambda x: str(x), reverse=True) + file = playblast_files[0] + + if not file.exists(): + return + return str(file) + def update_data(self, data: Dict[str, Any]) -> Shot: gazu.shot.update_shot_data(asdict(self), data=data) if not self.data: -- 2.30.2 From 1993392f00fed66df4f5c6a052d343ce2815a3c1 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Thu, 28 Mar 2024 15:23:55 -0400 Subject: [PATCH 02/16] Add Operator to Import Playblasts --- .../addons/blender_kitsu/sqe/ops.py | 98 ++++++++++++++++--- .../addons/blender_kitsu/sqe/ui.py | 2 + 2 files changed, 87 insertions(+), 13 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/sqe/ops.py b/scripts-blender/addons/blender_kitsu/sqe/ops.py index 8d160d21..6cdd1225 100644 --- a/scripts-blender/addons/blender_kitsu/sqe/ops.py +++ b/scripts-blender/addons/blender_kitsu/sqe/ops.py @@ -20,6 +20,8 @@ import os import re + +from bpy.types import Context import gazu import contextlib import colorsys @@ -2195,24 +2197,93 @@ class KITSU_OT_sqe_scan_for_media_updates(bpy.types.Operator): return {"FINISHED"} +def get_used_channels(self: Any, context: bpy.types.Context, edit_text: str = "") -> List[str]: + used_channels = [] + for seq in context.scene.sequence_editor.sequences_all: + used_channels.append(seq.channel) + + aval_channels = [] + for channel in range(1, 100): + if channel not in used_channels: + aval_channels.append(channel) + + return [f"{channel}" for channel in aval_channels] + + +class KITSU_OT_shot_import_playblast(bpy.types.Operator): + bl_idname = "kitsu.import_playblast" + bl_label = "Import Playblast" + bl_description = "Import playblast for selected metadatastrip" + bl_options = {"REGISTER", "UNDO"} + + channel_selection: bpy.props.StringProperty( # type: ignore + name="Channel", + description="Choose an empty target channel to place playblasts onto", + search=get_used_channels, + search_options={'SORT'}, + ) + + @classmethod + def poll(cls, context: bpy.types.Context) -> bool: + sqe = context.scene.sequence_editor + if not sqe: + return False + if not cache.project_active_get(): + cls.poll_message_set("No Kitsu Project Found check Add-on Preferences") + return False + for strip in context.selected_sequences: + if strip.kitsu.shot_id == "": + cls.poll_message_set("Selected strip {strip.name} is not metadata strip'") + return False + if len(bpy.context.selected_sequences) == 0: + cls.poll_message_set("Please select one or more metadata strips") + return False + return True + + def invoke(self, context, event): + channels = [int(x) for x in get_used_channels(self, context)] + strip = context.selected_sequences[0] + channel = min(channels, key=lambda x: abs(x - strip.channel)) + self.channel_selection = f"{channel}" + return context.window_manager.invoke_props_dialog(self, width=500) + + def draw(self, context: bpy.types.Context) -> None: + layout = self.layout + layout.prop(self, "channel_selection") + + def execute(self, context: Context) -> Set[str] | Set[int]: + succeeded: Set[str] = set() + failed: Set[str] = set() + sequences = context.scene.sequence_editor.sequences + for metadata_strip in context.selected_sequences: + task_type_short_name = "anim" # TODO make variable + # TODO add try except if ID is not valid, do same for shot as image sequence. + shot = Shot.by_id(metadata_strip.kitsu.shot_id) + filepath = shot.get_latest_playblast_file(context, task_type_short_name) + if filepath: + playblast = sequences.new_movie( + name=Path(filepath).name, + filepath=filepath, + frame_start=int(metadata_strip.frame_start), + channel=int(self.channel_selection), + ) + if playblast.frame_final_end > metadata_strip.frame_final_end: + playblast.frame_final_end = metadata_strip.frame_final_end + + succeeded.add(metadata_strip.name) + else: + failed.add(metadata_strip.name) + + self.report({"INFO"}, "Completed!") + return {'FINISHED'} + + class KITSU_OT_shot_image_sequence(bpy.types.Operator): bl_idname = "kitsu.shot_image_sequence" bl_label = "Shot as Image Sequence" bl_description = "Import image sequences for selected clips" bl_options = {"REGISTER", "UNDO"} - def get_used_channels(self: Any, context: bpy.types.Context, edit_text: str) -> List[str]: - used_channels = [] - for seq in context.scene.sequence_editor.sequences_all: - used_channels.append(seq.channel) - - aval_channels = [] - for channel in range(1, 100): - if channel not in used_channels: - aval_channels.append(channel) - - return [f"{channel}" for channel in aval_channels] - channel_selection: bpy.props.StringProperty( name="Channel", description="Choose an empty target channel to place image sequences onto", @@ -2268,7 +2339,7 @@ class KITSU_OT_shot_image_sequence(bpy.types.Operator): scene.view_settings.view_transform = 'Filmic' def invoke(self, context, event): - channels = [int(x) for x in self.get_used_channels(context, "")] + channels = [int(x) for x in get_used_channels(self, context)] strip = context.selected_sequences[0] channel = min(channels, key=lambda x: abs(x - strip.channel)) self.channel_selection = f"{channel}" @@ -2595,6 +2666,7 @@ classes = [ KITSU_OT_sqe_change_strip_source, KITSU_OT_sqe_clear_update_indicators, KITSU_OT_shot_image_sequence, + KITSU_OT_shot_import_playblast, ] diff --git a/scripts-blender/addons/blender_kitsu/sqe/ui.py b/scripts-blender/addons/blender_kitsu/sqe/ui.py index 9c1d1220..cacc7a90 100644 --- a/scripts-blender/addons/blender_kitsu/sqe/ui.py +++ b/scripts-blender/addons/blender_kitsu/sqe/ui.py @@ -51,6 +51,7 @@ from ..sqe.ops import ( KITSU_OT_sqe_change_strip_source, KITSU_OT_sqe_clear_update_indicators, KITSU_OT_shot_image_sequence, + KITSU_OT_shot_import_playblast, ) from pathlib import Path @@ -685,6 +686,7 @@ class KITSU_PT_sqe_general_tools(bpy.types.Panel): box.label(text="General", icon="MODIFIER") box.operator(KITSU_OT_shot_image_sequence.bl_idname) + box.operator(KITSU_OT_shot_import_playblast.bl_idname) # Scan for outdated media and reset operator. row = box.row(align=True) row.operator( -- 2.30.2 From 84c504f6bc1d4f49311272bf431c5e2ea472fdd4 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Tue, 2 Apr 2024 14:01:05 -0400 Subject: [PATCH 03/16] Add Playblast and Image Sequence to Metadata strip Panel --- .../addons/blender_kitsu/sqe/ui.py | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/sqe/ui.py b/scripts-blender/addons/blender_kitsu/sqe/ui.py index cacc7a90..68c91380 100644 --- a/scripts-blender/addons/blender_kitsu/sqe/ui.py +++ b/scripts-blender/addons/blender_kitsu/sqe/ui.py @@ -122,6 +122,7 @@ class KITSU_PT_sqe_shot_tools(bpy.types.Panel): if self.poll_pull(context): self.draw_pull(context) + self.draw_media(context) if self.poll_debug(context): self.draw_debug(context) @@ -640,6 +641,30 @@ class KITSU_PT_sqe_shot_tools(bpy.types.Panel): icon="MODIFIER_ON", ) + def draw_media(self, context: bpy.types.Context) -> None: + + sel_metadata_strips = [strip for strip in context.selected_sequences if strip.kitsu.linked] + + noun = get_selshots_noun(len(sel_metadata_strips), prefix=f"{len(sel_metadata_strips)}") + playblast = "Playblast" if len(sel_metadata_strips) <= 1 else "Playblasts" + + sequence = "Sequence" if len(sel_metadata_strips) <= 1 else "Sequences" + + # Create box. + layout = self.layout + box = layout.box() + box.label(text="Media", icon="RENDER_ANIMATION") + box.operator( + KITSU_OT_shot_import_playblast.bl_idname, + text=f"Import {noun} {playblast}", + icon="FILE_MOVIE", + ) + box.operator( + KITSU_OT_shot_image_sequence.bl_idname, + text=f"Import {noun} Image {sequence}", + icon="RENDER_RESULT", + ) + class KITSU_PT_sqe_general_tools(bpy.types.Panel): """ @@ -685,8 +710,6 @@ class KITSU_PT_sqe_general_tools(bpy.types.Panel): box = layout.box() box.label(text="General", icon="MODIFIER") - box.operator(KITSU_OT_shot_image_sequence.bl_idname) - box.operator(KITSU_OT_shot_import_playblast.bl_idname) # Scan for outdated media and reset operator. row = box.row(align=True) row.operator( -- 2.30.2 From 5963c924feef627a66160e8c919b5c94bb5cf2e5 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Tue, 2 Apr 2024 14:23:43 -0400 Subject: [PATCH 04/16] Load Image Sequence from Metadata Strip not Media Strip --- scripts-blender/addons/blender_kitsu/sqe/ops.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/sqe/ops.py b/scripts-blender/addons/blender_kitsu/sqe/ops.py index 6cdd1225..3bff47c4 100644 --- a/scripts-blender/addons/blender_kitsu/sqe/ops.py +++ b/scripts-blender/addons/blender_kitsu/sqe/ops.py @@ -2431,9 +2431,9 @@ class KITSU_OT_shot_image_sequence(bpy.types.Operator): new_strip.name = f"{self.get_shot_name(strip)}{self.file_type.lower()}" new_strip.colorspace_settings.name = new_strip.colorspace_settings.name - def get_shot_seq_directory(self, context, strip): + def get_shot_seq_directory(self, context, filepath): addon_prefs = prefs.addon_prefs_get(context) - path_string = os.path.realpath(bpy.path.abspath(strip.filepath)) + path_string = os.path.realpath(bpy.path.abspath(filepath)) path = Path( path_string.replace( addon_prefs.shot_playblast_root_dir, addon_prefs.frames_root_dir @@ -2457,8 +2457,11 @@ class KITSU_OT_shot_image_sequence(bpy.types.Operator): if self.set_color_space: self.set_scene_colorspace(context) - for strip in [strip for strip in context.selected_sequences if strip.type == 'MOVIE']: - directory = self.get_shot_seq_directory(context, strip) + for strip in [strip for strip in context.selected_sequences if strip.kitsu.shot_id != '']: + shot = Shot().by_id(strip.kitsu.shot_id) + # TODO pass task type as variable + filepath = shot.get_latest_playblast_file(context, "anim") + directory = self.get_shot_seq_directory(context, filepath) if not directory.exists(): self.report({"ERROR"}, f"{directory._str} does not exist") return {"CANCELLED"} -- 2.30.2 From 9cd953fff32d871f0bffebcb1b72fc49f5425ab9 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Tue, 2 Apr 2024 14:39:09 -0400 Subject: [PATCH 05/16] Add Task Type to Playblast Operator Pop-Up --- .../addons/blender_kitsu/sqe/ops.py | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/sqe/ops.py b/scripts-blender/addons/blender_kitsu/sqe/ops.py index 3bff47c4..f236d09c 100644 --- a/scripts-blender/addons/blender_kitsu/sqe/ops.py +++ b/scripts-blender/addons/blender_kitsu/sqe/ops.py @@ -2210,6 +2210,15 @@ def get_used_channels(self: Any, context: bpy.types.Context, edit_text: str = "" return [f"{channel}" for channel in aval_channels] +def get_shot_task_types_enum_list(self, context: bpy.types.Context) -> List[Tuple[str, str, str]]: + active_project = cache.project_active_get() + return [ + (t.id, t.name, "") + for t in TaskType.all_shot_task_types() + if t.id in active_project.task_types + ] + + class KITSU_OT_shot_import_playblast(bpy.types.Operator): bl_idname = "kitsu.import_playblast" bl_label = "Import Playblast" @@ -2223,6 +2232,12 @@ class KITSU_OT_shot_import_playblast(bpy.types.Operator): search_options={'SORT'}, ) + task_type: bpy.props.EnumProperty( # type: ignore + name="Task Type", + description="Choose a task type to import playblasts for", + items=get_shot_task_types_enum_list, + ) + @classmethod def poll(cls, context: bpy.types.Context) -> bool: sqe = context.scene.sequence_editor @@ -2250,15 +2265,16 @@ class KITSU_OT_shot_import_playblast(bpy.types.Operator): def draw(self, context: bpy.types.Context) -> None: layout = self.layout layout.prop(self, "channel_selection") + layout.prop(self, "task_type") def execute(self, context: Context) -> Set[str] | Set[int]: succeeded: Set[str] = set() failed: Set[str] = set() sequences = context.scene.sequence_editor.sequences for metadata_strip in context.selected_sequences: - task_type_short_name = "anim" # TODO make variable # TODO add try except if ID is not valid, do same for shot as image sequence. shot = Shot.by_id(metadata_strip.kitsu.shot_id) + task_type_short_name = TaskType.by_id(self.task_type).get_short_name() filepath = shot.get_latest_playblast_file(context, task_type_short_name) if filepath: playblast = sequences.new_movie( @@ -2274,7 +2290,7 @@ class KITSU_OT_shot_import_playblast(bpy.types.Operator): else: failed.add(metadata_strip.name) - self.report({"INFO"}, "Completed!") + self.report({"INFO"}, "Completed!") # TODO add report if failure return {'FINISHED'} -- 2.30.2 From aa0648cd496b824087801b73c3bffc9bf2aa3f6e Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Tue, 2 Apr 2024 14:41:27 -0400 Subject: [PATCH 06/16] Add Task Type to Image Sequence Operator Pop-Up --- scripts-blender/addons/blender_kitsu/sqe/ops.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/scripts-blender/addons/blender_kitsu/sqe/ops.py b/scripts-blender/addons/blender_kitsu/sqe/ops.py index f236d09c..33b939d3 100644 --- a/scripts-blender/addons/blender_kitsu/sqe/ops.py +++ b/scripts-blender/addons/blender_kitsu/sqe/ops.py @@ -2321,6 +2321,12 @@ class KITSU_OT_shot_image_sequence(bpy.types.Operator): default=True, ) + task_type: bpy.props.EnumProperty( # type: ignore + name="Task Type", + description="Choose a task type to import playblasts for", + items=get_shot_task_types_enum_list, + ) + @classmethod def poll(cls, context: bpy.types.Context) -> bool: sqe = context.scene.sequence_editor @@ -2363,6 +2369,7 @@ class KITSU_OT_shot_image_sequence(bpy.types.Operator): def draw(self, context: bpy.types.Context) -> None: layout = self.layout + layout.prop(self, "task_type") layout.prop(self, "channel_selection") layout.prop(self, "file_type") layout.prop(self, "set_color_space") @@ -2476,12 +2483,15 @@ class KITSU_OT_shot_image_sequence(bpy.types.Operator): for strip in [strip for strip in context.selected_sequences if strip.kitsu.shot_id != '']: shot = Shot().by_id(strip.kitsu.shot_id) # TODO pass task type as variable - filepath = shot.get_latest_playblast_file(context, "anim") + task_type_short_name = TaskType.by_id(self.task_type).get_short_name() + filepath = shot.get_latest_playblast_file(context, task_type_short_name) directory = self.get_shot_seq_directory(context, filepath) if not directory.exists(): self.report({"ERROR"}, f"{directory._str} does not exist") return {"CANCELLED"} self.import_strip(context, strip, directory, channel) + + # TODO ADD REPORTING return {"FINISHED"} -- 2.30.2 From e75bd6d8aac2f6faddbad159bf9b90841fee4050 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Wed, 3 Apr 2024 09:39:38 -0400 Subject: [PATCH 07/16] Only use metadata strip in Image Sequence Import --- .../addons/blender_kitsu/sqe/ops.py | 34 +++++++------------ 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/sqe/ops.py b/scripts-blender/addons/blender_kitsu/sqe/ops.py index 33b939d3..69941c1e 100644 --- a/scripts-blender/addons/blender_kitsu/sqe/ops.py +++ b/scripts-blender/addons/blender_kitsu/sqe/ops.py @@ -2382,23 +2382,13 @@ class KITSU_OT_shot_image_sequence(bpy.types.Operator): return match.group(1) return - def get_metadata_strip(self, context, strip): - name = self.get_shot_name(strip) - for strip in context.scene.sequence_editor.sequences_all: - if strip.name == name: - return strip - return - - def import_strip(self, context, strip, directory, channel): + def import_strip(self, context, metadata_strip, directory, channel): # https://blender.stackexchange.com/questions/286946/how-to-add-image-sequence-in-sequencer-via-python - frame_start = strip.frame_final_start - frame_end = strip.frame_final_end + frame_start = metadata_strip.frame_final_start + frame_end = metadata_strip.frame_final_end files = [] - metadata_strip = self.get_metadata_strip(context, strip) - if not metadata_strip: - self.report({'ERROR'}, f"No Metadata Strip found for {strip.name}") - return {'CANCELLED'} + shot = Shot.by_id(metadata_strip.kitsu.shot_id) start_frame = ( shot.data.get('3d_start') if shot.data.get('3d_start') else bkglobals.FRAME_START @@ -2407,7 +2397,7 @@ class KITSU_OT_shot_image_sequence(bpy.types.Operator): if file.name.endswith("mp4"): continue frame_number = int(file.name.split(".")[0]) - duration = strip.frame_duration + start_frame + duration = metadata_strip.frame_duration + start_frame if self.file_type in file.name and frame_number < duration: files.append({"name": file.name}) @@ -2439,19 +2429,19 @@ class KITSU_OT_shot_image_sequence(bpy.types.Operator): fit_method='FIT', ) if len_strip + 1 != len(context.scene.sequence_editor.sequences_all): - print(f"Failed to import image sequence for {strip.name}") + print(f"Failed to import image sequence for {metadata_strip.name}") return new_strip = context.selected_sequences[0] - new_strip.animation_offset_end = strip.animation_offset_end - new_strip.animation_offset_start = strip.animation_offset_start + new_strip.animation_offset_end = metadata_strip.animation_offset_end + new_strip.animation_offset_start = metadata_strip.animation_offset_start - new_strip.frame_offset_end = strip.frame_offset_end - new_strip.frame_offset_start = strip.frame_offset_start - new_strip.frame_start = strip.frame_start + new_strip.frame_offset_end = metadata_strip.frame_offset_end + new_strip.frame_offset_start = metadata_strip.frame_offset_start + new_strip.frame_start = metadata_strip.frame_start new_strip.channel = channel - new_strip.name = f"{self.get_shot_name(strip)}{self.file_type.lower()}" + new_strip.name = f"{self.get_shot_name(metadata_strip)}{self.file_type.lower()}" new_strip.colorspace_settings.name = new_strip.colorspace_settings.name def get_shot_seq_directory(self, context, filepath): -- 2.30.2 From 7dd9df3665a8b5d6f97b215cf9c2a91a8f2b2cc9 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Wed, 3 Apr 2024 09:48:09 -0400 Subject: [PATCH 08/16] Add Better Reporting for Import Image Sequence --- .../addons/blender_kitsu/sqe/ops.py | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/sqe/ops.py b/scripts-blender/addons/blender_kitsu/sqe/ops.py index 69941c1e..3fb31e73 100644 --- a/scripts-blender/addons/blender_kitsu/sqe/ops.py +++ b/scripts-blender/addons/blender_kitsu/sqe/ops.py @@ -2456,6 +2456,8 @@ class KITSU_OT_shot_image_sequence(bpy.types.Operator): def execute(self, context: bpy.types.Context) -> Set[str]: # Get closest empty channel + successes = [] + failed = [] channel = int(self.channel_selection) addon_prefs = prefs.addon_prefs_get(context) if not ( @@ -2470,18 +2472,42 @@ class KITSU_OT_shot_image_sequence(bpy.types.Operator): if self.set_color_space: self.set_scene_colorspace(context) - for strip in [strip for strip in context.selected_sequences if strip.kitsu.shot_id != '']: + metadata_strips = [ + strip for strip in context.selected_sequences if strip.kitsu.shot_id != '' + ] + + for strip in metadata_strips: shot = Shot().by_id(strip.kitsu.shot_id) # TODO pass task type as variable task_type_short_name = TaskType.by_id(self.task_type).get_short_name() filepath = shot.get_latest_playblast_file(context, task_type_short_name) directory = self.get_shot_seq_directory(context, filepath) if not directory.exists(): - self.report({"ERROR"}, f"{directory._str} does not exist") - return {"CANCELLED"} + failed.append(str(directory)) + continue self.import_strip(context, strip, directory, channel) + successes.append(str(directory)) - # TODO ADD REPORTING + if len(metadata_strips) == 1: + if len(failed) == 1: + self.report( + {"ERROR"}, f"Failed to import Image Sequence `{failed[0]}` does not exist" + ) + return {"CANCELLED"} + if len(successes) == 1: + self.report({"INFO"}, f"Imported Image Sequence from `{successes[0]}`") + return {"FINISHED"} + + report_str = f"Imported {len(successes)} Image Sequences" + report_state = "INFO" + if failed: + report_state = "WARNING" + report_str += f" | Failed: {len(failed)}" + + self.report( + {report_state}, + report_str, + ) return {"FINISHED"} -- 2.30.2 From c1458df2c4fe0ba0dbe3201e99888745994dff09 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Wed, 3 Apr 2024 09:54:12 -0400 Subject: [PATCH 09/16] Add Better Reporting for Import Playblast --- .../addons/blender_kitsu/sqe/ops.py | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/sqe/ops.py b/scripts-blender/addons/blender_kitsu/sqe/ops.py index 3fb31e73..2c04dadf 100644 --- a/scripts-blender/addons/blender_kitsu/sqe/ops.py +++ b/scripts-blender/addons/blender_kitsu/sqe/ops.py @@ -2248,7 +2248,7 @@ class KITSU_OT_shot_import_playblast(bpy.types.Operator): return False for strip in context.selected_sequences: if strip.kitsu.shot_id == "": - cls.poll_message_set("Selected strip {strip.name} is not metadata strip'") + cls.poll_message_set(f"Selected strip {strip.name} is not metadata strip'") return False if len(bpy.context.selected_sequences) == 0: cls.poll_message_set("Please select one or more metadata strips") @@ -2271,7 +2271,10 @@ class KITSU_OT_shot_import_playblast(bpy.types.Operator): succeeded: Set[str] = set() failed: Set[str] = set() sequences = context.scene.sequence_editor.sequences - for metadata_strip in context.selected_sequences: + metadata_strips = [ + strip for strip in context.selected_sequences if strip.kitsu.shot_id != '' + ] + for metadata_strip in metadata_strips: # TODO add try except if ID is not valid, do same for shot as image sequence. shot = Shot.by_id(metadata_strip.kitsu.shot_id) task_type_short_name = TaskType.by_id(self.task_type).get_short_name() @@ -2290,7 +2293,29 @@ class KITSU_OT_shot_import_playblast(bpy.types.Operator): else: failed.add(metadata_strip.name) - self.report({"INFO"}, "Completed!") # TODO add report if failure + if len(metadata_strips) == 1: + if len(failed) == 1: + self.report({"WARNING"}, f"Failed to import Playblast `{failed[0]}` does not exist") + return {"CANCELLED"} + if len(succeeded) == 1: + self.report({"INFO"}, f"Imported Playblast from `{succeeded[0]}`") + return {"FINISHED"} + + report_str = f"Imported {len(succeeded)} Playblast" + report_state = "INFO" + if failed: + report_state = "WARNING" + report_str += f" | Failed: {len(failed)}" + + self.report( + {report_state}, + report_str, + ) + + self.report( + {report_state}, + report_str, + ) return {'FINISHED'} -- 2.30.2 From a6b8d633acc14f7fb7cdcd03ddbb9a878b28b5e4 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Wed, 3 Apr 2024 09:54:34 -0400 Subject: [PATCH 10/16] Add Metadata poll to Import Image Sequence --- scripts-blender/addons/blender_kitsu/sqe/ops.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/sqe/ops.py b/scripts-blender/addons/blender_kitsu/sqe/ops.py index 2c04dadf..f04d7165 100644 --- a/scripts-blender/addons/blender_kitsu/sqe/ops.py +++ b/scripts-blender/addons/blender_kitsu/sqe/ops.py @@ -2360,9 +2360,9 @@ class KITSU_OT_shot_image_sequence(bpy.types.Operator): if not cache.project_active_get(): cls.poll_message_set("No Kitsu Project Found check Add-on Preferences") return False - for sqe in context.selected_sequences: - if sqe.type != 'MOVIE': - cls.poll_message_set("Selected strips must be 'MOVIE'") + for strip in context.selected_sequences: + if strip.kitsu.shot_id == "": + cls.poll_message_set(f"Selected strip {strip.name} is not metadata strip'") return False if len(bpy.context.selected_sequences) == 0: cls.poll_message_set("Please select a 'MOVIE' strip") -- 2.30.2 From 645e6de936ec08f8c07c00cdfb76e3d147dc46f2 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Wed, 3 Apr 2024 09:54:57 -0400 Subject: [PATCH 11/16] Improve Variable Naming for Import Image Sequence --- scripts-blender/addons/blender_kitsu/sqe/ops.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/sqe/ops.py b/scripts-blender/addons/blender_kitsu/sqe/ops.py index f04d7165..6f979b9a 100644 --- a/scripts-blender/addons/blender_kitsu/sqe/ops.py +++ b/scripts-blender/addons/blender_kitsu/sqe/ops.py @@ -2481,7 +2481,7 @@ class KITSU_OT_shot_image_sequence(bpy.types.Operator): def execute(self, context: bpy.types.Context) -> Set[str]: # Get closest empty channel - successes = [] + succeeded = [] failed = [] channel = int(self.channel_selection) addon_prefs = prefs.addon_prefs_get(context) @@ -2511,19 +2511,19 @@ class KITSU_OT_shot_image_sequence(bpy.types.Operator): failed.append(str(directory)) continue self.import_strip(context, strip, directory, channel) - successes.append(str(directory)) + succeeded.append(str(directory)) if len(metadata_strips) == 1: if len(failed) == 1: self.report( - {"ERROR"}, f"Failed to import Image Sequence `{failed[0]}` does not exist" + {"WARNING"}, f"Failed to import Image Sequence `{failed[0]}` does not exist" ) return {"CANCELLED"} - if len(successes) == 1: - self.report({"INFO"}, f"Imported Image Sequence from `{successes[0]}`") + if len(succeeded) == 1: + self.report({"INFO"}, f"Imported Image Sequence from `{succeeded[0]}`") return {"FINISHED"} - report_str = f"Imported {len(successes)} Image Sequences" + report_str = f"Imported {len(succeeded)} Image Sequences" report_state = "INFO" if failed: report_state = "WARNING" -- 2.30.2 From 2f51443f7ce5c56824910406ad32905b2fa5b8c7 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Wed, 3 Apr 2024 10:08:27 -0400 Subject: [PATCH 12/16] Update Blender Kitsu README --- .../blender_kitsu/Shot_as_Image_Sequence.jpg | 3 --- .../addons/blender_kitsu/import_playblast.jpg | 3 +++ .../media/addons/blender_kitsu/media_panel.jpg | 3 +++ .../blender_kitsu/shot_image_sequence.jpg | 3 +++ scripts-blender/addons/blender_kitsu/README.md | 18 +++++++++++++----- 5 files changed, 22 insertions(+), 8 deletions(-) delete mode 100644 docs/media/addons/blender_kitsu/Shot_as_Image_Sequence.jpg create mode 100644 docs/media/addons/blender_kitsu/import_playblast.jpg create mode 100644 docs/media/addons/blender_kitsu/media_panel.jpg create mode 100644 docs/media/addons/blender_kitsu/shot_image_sequence.jpg diff --git a/docs/media/addons/blender_kitsu/Shot_as_Image_Sequence.jpg b/docs/media/addons/blender_kitsu/Shot_as_Image_Sequence.jpg deleted file mode 100644 index fe330736..00000000 --- a/docs/media/addons/blender_kitsu/Shot_as_Image_Sequence.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:32179fa31e68bb3db9f399f276aef2cc099900f362e23083fc2f844b5b731df2 -size 30881 diff --git a/docs/media/addons/blender_kitsu/import_playblast.jpg b/docs/media/addons/blender_kitsu/import_playblast.jpg new file mode 100644 index 00000000..e7ee390b --- /dev/null +++ b/docs/media/addons/blender_kitsu/import_playblast.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af9b32a64d397be45f3a93da07fc9363bf0d4e0bda508a7085d4dc698018edad +size 21621 diff --git a/docs/media/addons/blender_kitsu/media_panel.jpg b/docs/media/addons/blender_kitsu/media_panel.jpg new file mode 100644 index 00000000..8e8f98bd --- /dev/null +++ b/docs/media/addons/blender_kitsu/media_panel.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:237b450829e64323e789ee236308ff18558f76dace1510b6e3f03f2c05e2dd11 +size 12367 diff --git a/docs/media/addons/blender_kitsu/shot_image_sequence.jpg b/docs/media/addons/blender_kitsu/shot_image_sequence.jpg new file mode 100644 index 00000000..fd7341f3 --- /dev/null +++ b/docs/media/addons/blender_kitsu/shot_image_sequence.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:303d50daa2fb716348e248c2fca1b6e216658774b925e6ce5841694fc336c6b5 +size 26377 diff --git a/scripts-blender/addons/blender_kitsu/README.md b/scripts-blender/addons/blender_kitsu/README.md index 52a01be6..ed4c415a 100644 --- a/scripts-blender/addons/blender_kitsu/README.md +++ b/scripts-blender/addons/blender_kitsu/README.md @@ -14,7 +14,7 @@ blender-kitsu is a Blender Add-on to interact with Kitsu from within Blender. It - [Push](#push) - [Pull](#pull) - [Multi Edit](#multi-edit) - - [Shot as Image Sequence](#shot-as-image-sequence) + - [Import Media](#import-media) - [General Sequence Editor Tools](#general-sequence-editor-tools) - [Context](#context) - [Animation Tools](#animation-tools) @@ -192,13 +192,21 @@ The `Multi Edit` panel only appears when you select multiple metadata strips tha ![image info](/media/addons/blender_kitsu/sqe_multi_edit.jpg) It is meant to be way to quickly setup lots of shots if they don't exist on Kitsu yet. You specify the sequence all shots should belong to and adjust the `Shot Counter Start` value. In the preview property you can see how all shots will be named when you execute the `Multi Edit Strip` operator.
+##### Import Media +A collection of operators to Import media based on the Shot associated with the selected metadata strip(s).
-##### Shot as Image Sequence -The `Shot as Image Sequence` Operator will replace a playblast from your Playblast Root directory with an image sequence located in the Frames Root directory. The Shots Directory and the Frames Directory should have matching folder structures. Typically the format is `/{sequence_name}/{shot_name}/{shot_name}-{shot_task}/` +![Media Panel](/media/addons/blender_kitsu/media_panel.jpg) +##### Import Playblast +With a metadata strip selected `Import Playblast` Operator will find an image sequence of a given task type located in the Frames Root directory. +![Import Playblast](/media/addons/blender_kitsu/import_playblast.jpg) -![Shot as Image Sequence](/media/addons/blender_kitsu/Shot_as_Image_Sequence.jpg) +Use this operator to import image sequences that have been approved via the [Render Review Add-On](/addons/render_review) Image Sequences can be loaded as either `EXR` or `JPG` sequences. -Use this operator to replace playblasts with image sequences that have been approved via the [Render Review Add-On](/addons/render_review) Image Sequences can be loaded as either `EXR` or `JPG` sequences. +##### Import Image Sequence +With a metadata strip selected `Import Image Sequence` Operator will find an image sequence of a given task type located in the Frames Root directory. +![Import Image Sequence](/media/addons/blender_kitsu/shot_image_sequence.jpg) + +Use this operator to import image sequences that have been approved via the [Render Review Add-On](/addons/render_review) Image Sequences can be loaded as either `EXR` or `JPG` sequences. ###### Advanced Settings If you check the `Advanced` checkbox next to the counter value, you have access to advance settings to customize the operator even more. -- 2.30.2 From 0fb13ec169b9e56f651d4c9b53eefcbaaf85f52c Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Wed, 3 Apr 2024 10:12:40 -0400 Subject: [PATCH 13/16] Update Usage Docs with Playblast Import --- docs/user-guide/project_tools/usage-playblast.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/user-guide/project_tools/usage-playblast.md b/docs/user-guide/project_tools/usage-playblast.md index 5702ddd4..632dc16c 100644 --- a/docs/user-guide/project_tools/usage-playblast.md +++ b/docs/user-guide/project_tools/usage-playblast.md @@ -16,6 +16,7 @@ For each new task type, Anim/Layout etc needs to be added manually, then it can Returning to your edit .blend file, we can now load the playblast from the animation file into the edit. 1. Open your edit .blend file inside the directory `/your_project_name/svn/edit` -2. From the Sequencer Header select `Add>Movie` -3. Navigate to the directory of the playblast for your shot's .blend file `your_project_name/shared/footage/pro/{sequence_name}/{shot_name}/{file_name}` and select the `.mp4` file -4. Place the new shot at the same timing as the corresponding metastrip \ No newline at end of file +2. Select the Metadata Strip associated with your shot +3. From the Sequencer Side Panel select `Import 1 Shot Playblast` +3. Select the Task Type you would like to load the playblast from and an empty channel +4. Your new playblast will be imported with the same timing as the corresponding metadata strip \ No newline at end of file -- 2.30.2 From a47317c58ee34fe881ef20bbd2ded592b933e461 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Wed, 3 Apr 2024 10:12:56 -0400 Subject: [PATCH 14/16] Update Usage Docs with Image Sequence Import --- docs/user-guide/project_tools/usage-render-review.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user-guide/project_tools/usage-render-review.md b/docs/user-guide/project_tools/usage-render-review.md index a10244c1..ee910f74 100644 --- a/docs/user-guide/project_tools/usage-render-review.md +++ b/docs/user-guide/project_tools/usage-render-review.md @@ -16,4 +16,4 @@ Renders approved by the render review Add-On can be automatically imported into 1. Open your Edit .blend file 2. Select the video strip representing the shot that has an approved render. In the Kitsu Sidebar under General Tools select, `^` to load the next playblast from that shot automatically, which is an **mp4 preview** of your final render -3. Select the video strips representing all the shots you have approved renders for. Use `Shot as Image Sequence` to import the final image sequences for each shot as EXR or JPG and load it to a new channel in the VSE +3. Select the metadata strips representing all the shots you have approved renders for. Use `Import Image Sequence` operator to import the final image sequences for each shot as EXR or JPG and load it to a new channel in the VSE -- 2.30.2 From f1c89b56f8314b19b8b5ce46ce28bf7ccedb2fe3 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Wed, 3 Apr 2024 10:30:54 -0400 Subject: [PATCH 15/16] Update class names, bl_idnames & descriptions for import operators --- .../addons/blender_kitsu/sqe/ops.py | 18 +++++++++--------- scripts-blender/addons/blender_kitsu/sqe/ui.py | 8 ++++---- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/sqe/ops.py b/scripts-blender/addons/blender_kitsu/sqe/ops.py index 6f979b9a..5ec197c1 100644 --- a/scripts-blender/addons/blender_kitsu/sqe/ops.py +++ b/scripts-blender/addons/blender_kitsu/sqe/ops.py @@ -2219,10 +2219,10 @@ def get_shot_task_types_enum_list(self, context: bpy.types.Context) -> List[Tupl ] -class KITSU_OT_shot_import_playblast(bpy.types.Operator): - bl_idname = "kitsu.import_playblast" +class KITSU_OT_sqe_import_playblast(bpy.types.Operator): + bl_idname = "kitsu.sqe_import_playblast" bl_label = "Import Playblast" - bl_description = "Import playblast for selected metadatastrip" + bl_description = "Import playblast for selected metadata strips" bl_options = {"REGISTER", "UNDO"} channel_selection: bpy.props.StringProperty( # type: ignore @@ -2319,10 +2319,10 @@ class KITSU_OT_shot_import_playblast(bpy.types.Operator): return {'FINISHED'} -class KITSU_OT_shot_image_sequence(bpy.types.Operator): - bl_idname = "kitsu.shot_image_sequence" - bl_label = "Shot as Image Sequence" - bl_description = "Import image sequences for selected clips" +class KITSU_OT_sqe_import_image_sequence(bpy.types.Operator): + bl_idname = "kitsu.sqe_import_image_sequence" + bl_label = "Import Image Sequence" + bl_description = "Import Image Sequence for selected metadata strips" bl_options = {"REGISTER", "UNDO"} channel_selection: bpy.props.StringProperty( @@ -2735,8 +2735,8 @@ classes = [ KITSU_OT_sqe_scan_for_media_updates, KITSU_OT_sqe_change_strip_source, KITSU_OT_sqe_clear_update_indicators, - KITSU_OT_shot_image_sequence, - KITSU_OT_shot_import_playblast, + KITSU_OT_sqe_import_image_sequence, + KITSU_OT_sqe_import_playblast, ] diff --git a/scripts-blender/addons/blender_kitsu/sqe/ui.py b/scripts-blender/addons/blender_kitsu/sqe/ui.py index 68c91380..f0486c6e 100644 --- a/scripts-blender/addons/blender_kitsu/sqe/ui.py +++ b/scripts-blender/addons/blender_kitsu/sqe/ui.py @@ -50,8 +50,8 @@ from ..sqe.ops import ( KITSU_OT_sqe_scan_for_media_updates, KITSU_OT_sqe_change_strip_source, KITSU_OT_sqe_clear_update_indicators, - KITSU_OT_shot_image_sequence, - KITSU_OT_shot_import_playblast, + KITSU_OT_sqe_import_image_sequence, + KITSU_OT_sqe_import_playblast, ) from pathlib import Path @@ -655,12 +655,12 @@ class KITSU_PT_sqe_shot_tools(bpy.types.Panel): box = layout.box() box.label(text="Media", icon="RENDER_ANIMATION") box.operator( - KITSU_OT_shot_import_playblast.bl_idname, + KITSU_OT_sqe_import_playblast.bl_idname, text=f"Import {noun} {playblast}", icon="FILE_MOVIE", ) box.operator( - KITSU_OT_shot_image_sequence.bl_idname, + KITSU_OT_sqe_import_image_sequence.bl_idname, text=f"Import {noun} Image {sequence}", icon="RENDER_RESULT", ) -- 2.30.2 From 9a9db3191915493f87508bf3a565d378d2179142 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Wed, 3 Apr 2024 11:17:52 -0400 Subject: [PATCH 16/16] Update Import Image Sequence Image --- docs/media/addons/blender_kitsu/shot_image_sequence.jpg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/media/addons/blender_kitsu/shot_image_sequence.jpg b/docs/media/addons/blender_kitsu/shot_image_sequence.jpg index fd7341f3..7e995fdb 100644 --- a/docs/media/addons/blender_kitsu/shot_image_sequence.jpg +++ b/docs/media/addons/blender_kitsu/shot_image_sequence.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:303d50daa2fb716348e248c2fca1b6e216658774b925e6ce5841694fc336c6b5 -size 26377 +oid sha256:552f020232601d6feab4036dd1f825cd23c73c2dfc25f87b0059a4c9c015636d +size 28019 -- 2.30.2