From e10eb83758cd279425752a5469f593a85bf02a2b Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Mon, 18 Dec 2023 14:02:58 -0500 Subject: [PATCH 01/55] Blender Kitsu: Shot Builder 2 Intial Commit --- scripts-blender/addons/blender_kitsu/__init__.py | 4 +++- .../addons/blender_kitsu/shot_builder_2/__init__.py | 11 +++++++++++ .../addons/blender_kitsu/shot_builder_2/core.py | 0 .../addons/blender_kitsu/shot_builder_2/ops.py | 11 +++++++++++ .../addons/blender_kitsu/shot_builder_2/ui.py | 12 ++++++++++++ 5 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 scripts-blender/addons/blender_kitsu/shot_builder_2/__init__.py create mode 100644 scripts-blender/addons/blender_kitsu/shot_builder_2/core.py create mode 100644 scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py create mode 100644 scripts-blender/addons/blender_kitsu/shot_builder_2/ui.py diff --git a/scripts-blender/addons/blender_kitsu/__init__.py b/scripts-blender/addons/blender_kitsu/__init__.py index 261e504b..3b69c443 100644 --- a/scripts-blender/addons/blender_kitsu/__init__.py +++ b/scripts-blender/addons/blender_kitsu/__init__.py @@ -24,6 +24,7 @@ dependencies.preload_modules() from . import ( shot_builder, + shot_builder_2, lookdev, bkglobals, types, @@ -98,6 +99,7 @@ def register(): playblast.register() anim.register() shot_builder.register() + shot_builder_2.register() LoggerLevelManager.configure_levels() logger.info("Registered blender-kitsu") @@ -116,7 +118,7 @@ def unregister(): lookdev.unregister() playblast.unregister() shot_builder.unregister() - + shot_builder_2.unregister() LoggerLevelManager.restore_levels() diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/__init__.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/__init__.py new file mode 100644 index 00000000..096e0f91 --- /dev/null +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/__init__.py @@ -0,0 +1,11 @@ +from . import ops, ui + + +def register(): + ops.register() + ui.register() + + +def unregister(): + ops.unregister() + ui.unregister() diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py new file mode 100644 index 00000000..e69de29b diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py new file mode 100644 index 00000000..7eb4ece2 --- /dev/null +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py @@ -0,0 +1,11 @@ +import bpy + + +def register(): + for cls in classes: + bpy.utils.register_class(cls) + + +def unregister(): + for cls in reversed(classes): + bpy.utils.unregister_class(cls) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/ui.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/ui.py new file mode 100644 index 00000000..e9130257 --- /dev/null +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/ui.py @@ -0,0 +1,12 @@ +import bpy + + + +def register(): + for cls in classes: + bpy.utils.register_class(cls) + + +def unregister(): + for cls in reversed(classes): + bpy.utils.unregister_class(cls) -- 2.30.2 From 2ff886999ceb11e847275e16d851dc1af9c9f867 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Mon, 18 Dec 2023 14:02:59 -0500 Subject: [PATCH 02/55] Blender Kitsu: Add Function to Return Shot Enum for Given Sequence --- scripts-blender/addons/blender_kitsu/cache.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/scripts-blender/addons/blender_kitsu/cache.py b/scripts-blender/addons/blender_kitsu/cache.py index 994e3beb..1171c808 100644 --- a/scripts-blender/addons/blender_kitsu/cache.py +++ b/scripts-blender/addons/blender_kitsu/cache.py @@ -269,6 +269,18 @@ def get_shots_enum_for_active_seq( return _shot_enum_list +def get_shots_enum_for_seq( + self: bpy.types.Operator, context: bpy.types.Context, sequence: Sequence +) -> List[Tuple[str, str, str]]: + global _shot_enum_list + + _shot_enum_list.clear() + _shot_enum_list.extend( + [(s.id, s.name, s.description or "") for s in sequence.get_all_shots()] + ) + return _shot_enum_list + + def get_assetypes_enum_list( self: bpy.types.Operator, context: bpy.types.Context ) -> List[Tuple[str, str, str]]: -- 2.30.2 From edbfc84b2ca7ecb591a6c6c671db550c98293f62 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Mon, 18 Dec 2023 14:02:59 -0500 Subject: [PATCH 03/55] Blender Kitsu: Add Function to Return Task Type Enum for Given Shot --- scripts-blender/addons/blender_kitsu/cache.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/scripts-blender/addons/blender_kitsu/cache.py b/scripts-blender/addons/blender_kitsu/cache.py index 1171c808..7f770acf 100644 --- a/scripts-blender/addons/blender_kitsu/cache.py +++ b/scripts-blender/addons/blender_kitsu/cache.py @@ -355,6 +355,20 @@ def get_shot_task_types_enum( return _task_types_shots_enum_list +def get_shot_task_types_enum_for_shot( # TODO Rename + self: bpy.types.Operator, context: bpy.types.Context, shot: Shot +) -> List[Tuple[str, str, str]]: + # TODO why do we have global variables here can't I just return the items directly? We clear the list every time how is this a cahe? + global _task_types_shots_enum_list + + items = [(t.id, t.name, "") for t in shot.get_all_task_types()] + + _task_types_shots_enum_list.clear() + _task_types_shots_enum_list.extend(items) + + return _task_types_shots_enum_list + + def get_all_task_statuses_enum( self: bpy.types.Operator, context: bpy.types.Context ) -> List[Tuple[str, str, str]]: -- 2.30.2 From bc30d2ee266606c7ba399d87413a8bd6eee6b226 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Mon, 18 Dec 2023 14:02:59 -0500 Subject: [PATCH 04/55] Blender Kitsu: Add Build New Shot Operator --- .../blender_kitsu/shot_builder_2/ops.py | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py index 7eb4ece2..ed37a9ce 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py @@ -1,4 +1,133 @@ import bpy +from typing import List, Any, Tuple, Set, cast +from blender_kitsu import prefs, cache + + +active_project = None + + +def get_shots_for_seq( + self: Any, context: bpy.types.Context +) -> List[Tuple[str, str, str]]: + if not self.seq_id: + return [] + seq = active_project.get_sequence(self.seq_id) + return cache.get_shots_enum_for_seq(self, context, seq) + + +def get_tasks_for_shot( + self: Any, context: bpy.types.Context +) -> List[Tuple[str, str, str]]: + global active_project + if not self.seq_id: + return [] + shot = active_project.get_shot(self.shot_id) + return cache.get_shot_task_types_enum_for_shot(self, context, shot) + + +class KITSU_OT_build_new_shot(bpy.types.Operator): + bl_idname = "kitsu.build_new_shot" + bl_label = "Build New Shot" + bl_description = "" # TODO Description + bl_options = {"REGISTER"} + + _timer = None + _built_shot = False + _add_vse_area = False + _file_path = '' + production_name: bpy.props.StringProperty( # type: ignore + name="Production", + description="Name of the production to create a shot file for", + options=set(), + ) + + seq_id: bpy.props.EnumProperty( + name="Sequence ID", + description="Sequence ID of the shot to build", + items=cache.get_sequences_enum_list, + ) + + shot_id: bpy.props.EnumProperty( + name="Shot ID", + description="Shot ID of the shot to build", + items=get_shots_for_seq, + ) + + task_type: bpy.props.EnumProperty( + name="Task", + description="Task to create the shot file for", + items=get_tasks_for_shot, + ) + + auto_save: bpy.props.BoolProperty( + name="Save after building.", + description="Automatically save build file after 'Shot Builder' is complete.", + default=True, + ) + + def draw(self, context: bpy.types.Context) -> None: + layout = self.layout + row = layout.row() + row.enabled = False + row.prop(self, "production_name") + layout.prop(self, "seq_id") + layout.prop(self, "shot_id") + layout.prop(self, "task_type") + layout.prop(self, "auto_save") + + def invoke(self, context: bpy.types.Context, event: bpy.types.Event) -> Set[str]: + global active_project + addon_prefs = prefs.addon_prefs_get(bpy.context) + project = cache.project_active_get() + active_project = project + + if addon_prefs.session.is_auth() is False: + self.report( + {'ERROR'}, + "Must be logged into Kitsu to continue. \nCheck login status in 'Blender Kitsu' addon preferences.", + ) + return {'CANCELLED'} + + if project.id == "": + self.report( + {'ERROR'}, + "Operator is not able to determine the Kitsu production's name. \nCheck project is selected in 'Blender Kitsu' addon preferences.", + ) + return {'CANCELLED'} + + if not addon_prefs.is_project_root_valid: + self.report( + {'ERROR'}, + "Operator is not able to determine the project root directory. \nCheck project root directiory is configured in 'Blender Kitsu' addon preferences.", + ) + return {'CANCELLED'} + + self.production_name = project.name + + return cast( + Set[str], context.window_manager.invoke_props_dialog(self, width=400) + ) + + def _get_task_type_for_shot(self, context, shot): + for task_type in shot.get_all_task_types(): + if task_type.id == self.task_type: + return task_type + + def execute(self, context: bpy.types.Context): + global active_project + sequence = active_project.get_sequence(self.seq_id) + shot = active_project.get_shot(self.shot_id) + task_type = self._get_task_type_for_shot(context, shot) + + print("Create shot with the following details") # TODO Remove + print(f"Seq Name: '{sequence.name}' Seq ID: '{self.seq_id}'") + print(f"Shot Name: '{shot.name}' Shot ID: '{self.shot_id}'") + print(f"Task Type Name: '{task_type.name}' Task Type ID: `{self.task_type}`") + self.report({"INFO"}, f"Execution Complete") # TODO remove + return {"FINISHED"} + + +classes = (KITSU_OT_build_new_shot,) def register(): -- 2.30.2 From ea39a22703c327ea4d25e7a29a0a2cf50c72271c Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Mon, 18 Dec 2023 14:02:59 -0500 Subject: [PATCH 05/55] Blender Kitsu: Add Temp UI Panel --- .../addons/blender_kitsu/shot_builder_2/ui.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/ui.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/ui.py index e9130257..1ab5bd2f 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/ui.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/ui.py @@ -1,6 +1,18 @@ import bpy +class KITSU_PT_new_shot_panel(bpy.types.Panel): # TODO Remove (for testing only) + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_label = "New Shot" + bl_category = "New Shot" + + def draw(self, context): + self.layout.operator("kitsu.build_new_shot") + + +classes = (KITSU_PT_new_shot_panel,) + def register(): for cls in classes: -- 2.30.2 From 8e85afd56af11504ecd8458b4144579f64b0d941 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Mon, 18 Dec 2023 14:02:59 -0500 Subject: [PATCH 06/55] Blender Kitsu: Add In File Delimiter to `bkglobals` --- scripts-blender/addons/blender_kitsu/bkglobals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts-blender/addons/blender_kitsu/bkglobals.py b/scripts-blender/addons/blender_kitsu/bkglobals.py index 3bc93677..23c0fe14 100644 --- a/scripts-blender/addons/blender_kitsu/bkglobals.py +++ b/scripts-blender/addons/blender_kitsu/bkglobals.py @@ -29,7 +29,7 @@ SHOT_DIR_NAME = "shots" SEQ_DIR_NAME = "sequences" ASSET_DIR_NAME = "assets" -FILE_DELIMITER = '-' +IN_FILE_DELIMITER = "-" ASSET_TASK_MAPPING = { "geometry": "Geometry", -- 2.30.2 From cf1384c4823779a6d2ef27d74a35bba754917760 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Mon, 18 Dec 2023 14:02:59 -0500 Subject: [PATCH 07/55] Blender Kitsu: Add File Name to Core and New File Operator --- .../blender_kitsu/shot_builder_2/core.py | 41 +++++++++++++++++++ .../blender_kitsu/shot_builder_2/ops.py | 17 ++++++-- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py index e69de29b..4e591f70 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py @@ -0,0 +1,41 @@ +import bpy +from pathlib import Path + +from blender_kitsu.types import ( + Sequence, + Shot, + TaskType, +) + +from blender_kitsu import bkglobals, prefs + + +def get_task_type(task_type: TaskType) -> str: + for key, value in bkglobals.SHOT_TASK_MAPPING.items(): + if value == task_type.name: + return key + + +def get_shot_task_name(shot: Shot, task_type: TaskType) -> str: + return f"{shot.name}{bkglobals.IN_FILE_DELIMITER}{get_task_type(task_type)}" + + +def get_file_dir(seq: Sequence, shot: Shot, task_type: TaskType) -> Path: + """Returns Path to Directory for Current Shot, will ensure that + file path exists if it does not. + + Args: + seq (Sequence): Sequence Class from blender_kitsu.types + shot (Shot): Shot Class from blender_kitsu.types + task_type TaskType Class from blender_kitsu.types + + Returns: + Path: Returns Path for Shot Directory + """ + addon_prefs = prefs.addon_prefs_get(bpy.context) + project_root_dir = Path(addon_prefs.project_root_dir).resolve() + all_shots_dir = project_root_dir.joinpath('pro').joinpath('shots') + shot_dir = all_shots_dir.joinpath(seq.name).joinpath(shot.name) + if not shot_dir.exists(): + shot_dir.mkdir(parents=True) + return shot_dir diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py index ed37a9ce..a7387326 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py @@ -1,7 +1,8 @@ import bpy +from pathlib import Path from typing import List, Any, Tuple, Set, cast from blender_kitsu import prefs, cache - +from .core import get_shot_task_name, get_file_dir active_project = None @@ -114,13 +115,23 @@ class KITSU_OT_build_new_shot(bpy.types.Operator): return task_type def execute(self, context: bpy.types.Context): + # Get Properties global active_project - sequence = active_project.get_sequence(self.seq_id) + seq = active_project.get_sequence(self.seq_id) shot = active_project.get_shot(self.shot_id) task_type = self._get_task_type_for_shot(context, shot) + # Scene Naming + shot_task_name = get_shot_task_name(shot, task_type) + context.scene.name = shot_task_name + + # File Path + # TODO Only run if saving file + dir = get_file_dir(seq, shot, task_type) + shot_file_path_str = dir.joinpath(shot_task_name).__str__() + print("Create shot with the following details") # TODO Remove - print(f"Seq Name: '{sequence.name}' Seq ID: '{self.seq_id}'") + print(f"Seq Name: '{seq.name}' Seq ID: '{self.seq_id}'") print(f"Shot Name: '{shot.name}' Shot ID: '{self.shot_id}'") print(f"Task Type Name: '{task_type.name}' Task Type ID: `{self.task_type}`") self.report({"INFO"}, f"Execution Complete") # TODO remove -- 2.30.2 From d48007b273f18b0466a63ae935c006b1dbd5171b Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Mon, 18 Dec 2023 14:02:59 -0500 Subject: [PATCH 08/55] Blender Kitsu: Fix Typo in when using FILE_DELIMITER --- scripts-blender/addons/blender_kitsu/shot_builder_2/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py index 4e591f70..bf902d56 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py @@ -17,7 +17,7 @@ def get_task_type(task_type: TaskType) -> str: def get_shot_task_name(shot: Shot, task_type: TaskType) -> str: - return f"{shot.name}{bkglobals.IN_FILE_DELIMITER}{get_task_type(task_type)}" + return f"{shot.name}{bkglobals.FILE_DELIMITER}{get_task_type(task_type)}" def get_file_dir(seq: Sequence, shot: Shot, task_type: TaskType) -> Path: -- 2.30.2 From d978f575c56b4bb8cca6e87c42f5e9ea21f63071 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Mon, 18 Dec 2023 14:02:59 -0500 Subject: [PATCH 09/55] Blender Kitsu: Shot Builder Fix Bug in Get Tasks/Shots for UI --- .../blender_kitsu/shot_builder_2/ops.py | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py index a7387326..a168faba 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py @@ -10,20 +10,24 @@ active_project = None def get_shots_for_seq( self: Any, context: bpy.types.Context ) -> List[Tuple[str, str, str]]: - if not self.seq_id: - return [] - seq = active_project.get_sequence(self.seq_id) - return cache.get_shots_enum_for_seq(self, context, seq) + if self.seq_id != '': + seq = active_project.get_sequence(self.seq_id) + shot_enum = cache.get_shots_enum_for_seq(self, context, seq) + if shot_enum != []: + return shot_enum + return [('NONE', "No Shots Found", '')] def get_tasks_for_shot( self: Any, context: bpy.types.Context ) -> List[Tuple[str, str, str]]: global active_project - if not self.seq_id: - return [] - shot = active_project.get_shot(self.shot_id) - return cache.get_shot_task_types_enum_for_shot(self, context, shot) + if not (self.shot_id == '' or self.shot_id == 'NONE'): + shot = active_project.get_shot(self.shot_id) + task_enum = cache.get_shot_task_types_enum_for_shot(self, context, shot) + if task_enum != []: + return task_enum + return [('NONE', "No Tasks Found", '')] class KITSU_OT_build_new_shot(bpy.types.Operator): -- 2.30.2 From 854c0f4951cf2411dd800e96472dd15f1a05e5aa Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Mon, 18 Dec 2023 14:02:59 -0500 Subject: [PATCH 10/55] Fix typo in bkglobals --- scripts-blender/addons/blender_kitsu/bkglobals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts-blender/addons/blender_kitsu/bkglobals.py b/scripts-blender/addons/blender_kitsu/bkglobals.py index 23c0fe14..32e4505e 100644 --- a/scripts-blender/addons/blender_kitsu/bkglobals.py +++ b/scripts-blender/addons/blender_kitsu/bkglobals.py @@ -29,7 +29,7 @@ SHOT_DIR_NAME = "shots" SEQ_DIR_NAME = "sequences" ASSET_DIR_NAME = "assets" -IN_FILE_DELIMITER = "-" +FILE_DELIMITER = "-" ASSET_TASK_MAPPING = { "geometry": "Geometry", -- 2.30.2 From b5078f7be30d42b1c8ab560efb46ae98af89e571 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Mon, 18 Dec 2023 14:03:47 -0500 Subject: [PATCH 11/55] Expand Shot/TaskType Classes with New Functions --- scripts-blender/addons/blender_kitsu/types.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/scripts-blender/addons/blender_kitsu/types.py b/scripts-blender/addons/blender_kitsu/types.py index 683fdd4c..0b42eb98 100644 --- a/scripts-blender/addons/blender_kitsu/types.py +++ b/scripts-blender/addons/blender_kitsu/types.py @@ -25,6 +25,8 @@ from typing import Any, Dict, List, Optional, Union, Tuple, TypeVar import gazu from .logger import LoggerFactory +from . import bkglobals + logger = LoggerFactory.getLogger() @@ -565,6 +567,12 @@ class Shot(Entity): gazu.shot.update_shot(asdict(self)) return self + def get_shot_task_name(self, task_type: TaskType) -> str: # + return f"{self.name}{bkglobals.FILE_DELIMITER}{task_type.get_short_name()}" + + def get_output_collection_name(self, task_type: TaskType) -> str: + return f"{self.get_shot_task_name(task_type)}{bkglobals.FILE_DELIMITER}output" + def update_data(self, data: Dict[str, Any]) -> Shot: gazu.shot.update_shot_data(asdict(self), data=data) if not self.data: @@ -714,6 +722,11 @@ class TaskType(Entity): if t["for_entity"] == "Sequence" ] + def get_short_name(self) -> str: + for key, value in bkglobals.SHOT_TASK_MAPPING.items(): + if value == self.name: + return key + def __bool__(self) -> bool: return bool(self.id) -- 2.30.2 From 9592c2b491ed38c09aeb2e94ab080f04968e73f9 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Mon, 18 Dec 2023 14:03:47 -0500 Subject: [PATCH 12/55] Remove Core Functions that were moved to Shot/TaskType Classes --- .../addons/blender_kitsu/shot_builder_2/core.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py index bf902d56..e39c46b7 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py @@ -10,16 +10,6 @@ from blender_kitsu.types import ( from blender_kitsu import bkglobals, prefs -def get_task_type(task_type: TaskType) -> str: - for key, value in bkglobals.SHOT_TASK_MAPPING.items(): - if value == task_type.name: - return key - - -def get_shot_task_name(shot: Shot, task_type: TaskType) -> str: - return f"{shot.name}{bkglobals.FILE_DELIMITER}{get_task_type(task_type)}" - - def get_file_dir(seq: Sequence, shot: Shot, task_type: TaskType) -> Path: """Returns Path to Directory for Current Shot, will ensure that file path exists if it does not. -- 2.30.2 From cb7886db6b79bc0d6720a06e6497cf7e0c0c4064 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Mon, 18 Dec 2023 14:03:47 -0500 Subject: [PATCH 13/55] Add Core Function to get project root path --- .../addons/blender_kitsu/shot_builder_2/core.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py index e39c46b7..1c863b9d 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py @@ -6,8 +6,11 @@ from blender_kitsu.types import ( Shot, TaskType, ) +from blender_kitsu import prefs -from blender_kitsu import bkglobals, prefs +def get_project_root_path() -> Path: + addon_prefs = prefs.addon_prefs_get(bpy.context) + return Path(addon_prefs.project_root_dir).resolve() def get_file_dir(seq: Sequence, shot: Shot, task_type: TaskType) -> Path: @@ -22,8 +25,7 @@ def get_file_dir(seq: Sequence, shot: Shot, task_type: TaskType) -> Path: Returns: Path: Returns Path for Shot Directory """ - addon_prefs = prefs.addon_prefs_get(bpy.context) - project_root_dir = Path(addon_prefs.project_root_dir).resolve() + project_root_dir = get_project_root_path() all_shots_dir = project_root_dir.joinpath('pro').joinpath('shots') shot_dir = all_shots_dir.joinpath(seq.name).joinpath(shot.name) if not shot_dir.exists(): -- 2.30.2 From bf6c1a13525853edc1ddfb9f3b60aceaba30de98 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Mon, 18 Dec 2023 14:03:47 -0500 Subject: [PATCH 14/55] Add Render Engine Settings --- .../addons/blender_kitsu/shot_builder_2/core.py | 8 ++++++++ .../addons/blender_kitsu/shot_builder_2/ops.py | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py index 1c863b9d..73b87c84 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py @@ -31,3 +31,11 @@ def get_file_dir(seq: Sequence, shot: Shot, task_type: TaskType) -> Path: if not shot_dir.exists(): shot_dir.mkdir(parents=True) return shot_dir + + +def set_render_engine(scene: bpy.types.Scene, engine='CYCLES'): + """ + By default we set Cycles as the renderer. + """ + scene.render.engine = engine + diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py index a168faba..f8524b86 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py @@ -134,6 +134,14 @@ class KITSU_OT_build_new_shot(bpy.types.Operator): dir = get_file_dir(seq, shot, task_type) shot_file_path_str = dir.joinpath(shot_task_name).__str__() + # Set Render Settings + if ( + task_type.get_short_name() == 'anim' + ): # TODO get anim from a constant instead + set_render_engine(context.scene, 'BLENDER_WORKBENCH') + else: + set_render_engine(context.scene) + print("Create shot with the following details") # TODO Remove print(f"Seq Name: '{seq.name}' Seq ID: '{self.seq_id}'") print(f"Shot Name: '{shot.name}' Shot ID: '{self.shot_id}'") -- 2.30.2 From 19f8af05fe03b417cc127c2b9c1644027353ff3f Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Mon, 18 Dec 2023 14:03:47 -0500 Subject: [PATCH 15/55] Fix getting shot_task_name in ops --- scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py index f8524b86..4e57de58 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py @@ -126,7 +126,7 @@ class KITSU_OT_build_new_shot(bpy.types.Operator): task_type = self._get_task_type_for_shot(context, shot) # Scene Naming - shot_task_name = get_shot_task_name(shot, task_type) + shot_task_name = shot.get_shot_task_name(task_type) context.scene.name = shot_task_name # File Path -- 2.30.2 From 61fec9f80fb1dc4b6e86156cb0fe05c3ae856f75 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Mon, 18 Dec 2023 14:03:47 -0500 Subject: [PATCH 16/55] Make Imports Relative (for easy of development only) --- scripts-blender/addons/blender_kitsu/shot_builder_2/core.py | 2 +- scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py index 73b87c84..3eaa4c6c 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py @@ -1,7 +1,7 @@ import bpy from pathlib import Path -from blender_kitsu.types import ( +from ..types import ( Sequence, Shot, TaskType, diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py index 4e57de58..1a8a4ca4 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py @@ -1,7 +1,7 @@ import bpy from pathlib import Path from typing import List, Any, Tuple, Set, cast -from blender_kitsu import prefs, cache +from .. import prefs, cache from .core import get_shot_task_name, get_file_dir active_project = None -- 2.30.2 From 339e340a26516245ece3eae777000da9d17474a7 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Mon, 18 Dec 2023 14:03:47 -0500 Subject: [PATCH 17/55] Add Output Collection --- .../blender_kitsu/shot_builder_2/core.py | 20 +++++++++++++++++++ .../blender_kitsu/shot_builder_2/ops.py | 1 + 2 files changed, 21 insertions(+) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py index 3eaa4c6c..9d4dae07 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py @@ -39,3 +39,23 @@ def set_render_engine(scene: bpy.types.Scene, engine='CYCLES'): """ scene.render.engine = engine +def task_type_anim_output_collection( + scene: bpy.types.Scene, shot: Shot, task_type: TaskType +) -> bpy.types.Collection: + collections = bpy.data.collections + output_col_name = shot.get_output_collection_name(task_type) + + if not collections.get(output_col_name): + bpy.data.collections.new(name=output_col_name) + output_collection = collections.get(output_col_name) + + output_collection.use_fake_user = True + if not scene.collection.children.get(output_col_name): + scene.collection.children.link(output_collection) + + for view_layer in scene.view_layers: + view_layer_output_collection = view_layer.layer_collection.children.get( + output_col_name + ) + view_layer_output_collection.exclude = True + return output_collection diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py index 1a8a4ca4..67de49be 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py @@ -142,6 +142,7 @@ class KITSU_OT_build_new_shot(bpy.types.Operator): else: set_render_engine(context.scene) + output_col = task_type_anim_output_collection(context.scene, shot, task_type) print("Create shot with the following details") # TODO Remove print(f"Seq Name: '{seq.name}' Seq ID: '{self.seq_id}'") print(f"Shot Name: '{shot.name}' Shot ID: '{self.shot_id}'") -- 2.30.2 From cae0541f7780d5eb435aad36c097c1431dd7ba55 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Mon, 18 Dec 2023 14:03:47 -0500 Subject: [PATCH 18/55] Link Camera Rig --- .../blender_kitsu/shot_builder_2/core.py | 52 +++++++++++++++++++ .../blender_kitsu/shot_builder_2/ops.py | 9 +++- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py index 9d4dae07..fc8be7ae 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py @@ -6,8 +6,15 @@ from ..types import ( Shot, TaskType, ) + from blender_kitsu import prefs +################# +# Constants +################# +CAMERA_NAME = 'CAM-camera' + + def get_project_root_path() -> Path: addon_prefs = prefs.addon_prefs_get(bpy.context) return Path(addon_prefs.project_root_dir).resolve() @@ -39,6 +46,51 @@ def set_render_engine(scene: bpy.types.Scene, engine='CYCLES'): """ scene.render.engine = engine + +def link_camera_rig( + scene: bpy.types.Scene, + output_collection: bpy.types.Collection, +): + """ + Function to load the camera rig. The rig will be added to the output collection + of the shot and the camera will be set as active camera. + """ + # Load camera rig. + project_path = get_project_root_path() + path = f"{project_path}/pro/assets/cam/camera_rig.blend" + + if not Path(path).exists(): + camera_data = bpy.data.cameras.new(name=CAMERA_NAME) + camera_object = bpy.data.objects.new(name=CAMERA_NAME, object_data=camera_data) + scene.collection.objects.link(camera_object) + output_collection.objects.link(camera_object) + return + + collection_name = ( + "CA-camera_rig" # TODO Rename the asset itself, this breaks convention + ) + bpy.ops.wm.link( + filepath=path, + directory=path + "/Collection", + filename=collection_name, + ) + + # bpy.ops.object.make_override_library() + + camera_col = bpy.data.collections.get(collection_name) + override_camera_col = camera_col.override_hierarchy_create( + bpy.context.scene, bpy.context.view_layer, do_fully_editable=True + ) + + # Make library override. + output_collection.children.link(override_camera_col) + # scene.collection.children.link(override_camera_col) + + # Set the camera of the camera rig as active scene camera. + camera = override_camera_col.objects.get(CAMERA_NAME) + scene.camera = camera + + def task_type_anim_output_collection( scene: bpy.types.Scene, shot: Shot, task_type: TaskType ) -> bpy.types.Collection: diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py index 67de49be..45fdec7b 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py @@ -2,7 +2,12 @@ import bpy from pathlib import Path from typing import List, Any, Tuple, Set, cast from .. import prefs, cache -from .core import get_shot_task_name, get_file_dir +from .core import ( + get_file_dir, + set_render_engine, + link_camera_rig, + task_type_anim_output_collection, +) active_project = None @@ -143,6 +148,8 @@ class KITSU_OT_build_new_shot(bpy.types.Operator): set_render_engine(context.scene) output_col = task_type_anim_output_collection(context.scene, shot, task_type) + link_camera_rig(context.scene, output_col) + print("Create shot with the following details") # TODO Remove print(f"Seq Name: '{seq.name}' Seq ID: '{self.seq_id}'") print(f"Shot Name: '{shot.name}' Shot ID: '{self.shot_id}'") -- 2.30.2 From 487205f80740be299efea82783b43c24e21fb4d5 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Mon, 18 Dec 2023 14:03:47 -0500 Subject: [PATCH 19/55] Improve Linking Collection into Current Scene --- .../blender_kitsu/shot_builder_2/core.py | 45 +++++++++++++------ 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py index fc8be7ae..18511dbc 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py @@ -47,6 +47,34 @@ def set_render_engine(scene: bpy.types.Scene, engine='CYCLES'): scene.render.engine = engine +def link_and_override_collection( + file_path: str, collection_name: str, scene: bpy.types.Scene +) -> bpy.types.Collection: + """_summary_ + + Args: + file_path (str): File Path to .blend file to link from + collection_name (str): Name of collection to link from given filepath + scene (bpy.types.Scene): Current Scene to link collection to + + Returns: + bpy.types.Collection: Overriden Collection linked to Scene Collection + """ + bpy.ops.wm.link( + filepath=file_path, + directory=file_path + "/Collection", + filename=collection_name, + instance_collections=False, + ) + camera_col = bpy.data.collections.get(collection_name) + override_camera_col = camera_col.override_hierarchy_create( + scene, bpy.context.view_layer, do_fully_editable=True + ) + scene.collection.children.unlink(camera_col) + # Make library override. + return override_camera_col + + def link_camera_rig( scene: bpy.types.Scene, output_collection: bpy.types.Collection, @@ -69,22 +97,11 @@ def link_camera_rig( collection_name = ( "CA-camera_rig" # TODO Rename the asset itself, this breaks convention ) - bpy.ops.wm.link( - filepath=path, - directory=path + "/Collection", - filename=collection_name, + + override_camera_col = link_and_override_collection( + file_path=path, collection_name=collection_name, scene=scene ) - - # bpy.ops.object.make_override_library() - - camera_col = bpy.data.collections.get(collection_name) - override_camera_col = camera_col.override_hierarchy_create( - bpy.context.scene, bpy.context.view_layer, do_fully_editable=True - ) - - # Make library override. output_collection.children.link(override_camera_col) - # scene.collection.children.link(override_camera_col) # Set the camera of the camera rig as active scene camera. camera = override_camera_col.objects.get(CAMERA_NAME) -- 2.30.2 From 9b3e7ca3ebcf0eca3d9ae782884bc557db461f4d Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Mon, 18 Dec 2023 14:03:47 -0500 Subject: [PATCH 20/55] Create Scene in Shot Builder --- .../addons/blender_kitsu/shot_builder_2/core.py | 17 +++++++++++++++++ .../addons/blender_kitsu/shot_builder_2/ops.py | 5 +++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py index 18511dbc..0dcadd30 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py @@ -47,6 +47,23 @@ def set_render_engine(scene: bpy.types.Scene, engine='CYCLES'): scene.render.engine = engine +def create_scene(scene_name: str) -> bpy.types.Scene: + print(f"create scene with name {scene_name}") + scene = bpy.data.scenes.new(name=scene_name) + + bpy.context.window.scene = scene + remove_other_scenes(scene_name) + return scene + + +def remove_other_scenes(keep_scene_name: str) -> None: + for scene in bpy.data.scenes: + if scene.name == keep_scene_name: + continue + print(f"remove scene {scene.name}") + bpy.data.scenes.remove(scene) + + def link_and_override_collection( file_path: str, collection_name: str, scene: bpy.types.Scene ) -> bpy.types.Collection: diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py index 45fdec7b..099128f4 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py @@ -7,6 +7,7 @@ from .core import ( set_render_engine, link_camera_rig, task_type_anim_output_collection, + create_scene, ) active_project = None @@ -130,9 +131,9 @@ class KITSU_OT_build_new_shot(bpy.types.Operator): shot = active_project.get_shot(self.shot_id) task_type = self._get_task_type_for_shot(context, shot) - # Scene Naming + # Set Up Scene + Naming shot_task_name = shot.get_shot_task_name(task_type) - context.scene.name = shot_task_name + create_scene(shot_task_name) # File Path # TODO Only run if saving file -- 2.30.2 From 6cb6a165c058975870951c72d6c0f442d0e40b6e Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Mon, 18 Dec 2023 14:03:47 -0500 Subject: [PATCH 21/55] Set Scene Resolution & FPS --- .../addons/blender_kitsu/shot_builder_2/core.py | 10 ++++++++++ .../addons/blender_kitsu/shot_builder_2/ops.py | 4 +++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py index 0dcadd30..826725da 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py @@ -7,6 +7,8 @@ from ..types import ( TaskType, ) +from ..cache import Project + from blender_kitsu import prefs ################# @@ -64,6 +66,14 @@ def remove_other_scenes(keep_scene_name: str) -> None: bpy.data.scenes.remove(scene) +def set_resolution_and_fps(project: Project, scene: bpy.types.Scene): + scene.render.fps = project.fps # set fps + resolution = project.resolution.split('x') + scene.render.resolution_y = resolution[0] + scene.render.resolution_x = resolution[1] + scene.render.resolution_percentage = 100 + + def link_and_override_collection( file_path: str, collection_name: str, scene: bpy.types.Scene ) -> bpy.types.Collection: diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py index 099128f4..1afbc716 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py @@ -8,6 +8,7 @@ from .core import ( link_camera_rig, task_type_anim_output_collection, create_scene, + set_resolution_and_fps, ) active_project = None @@ -133,7 +134,8 @@ class KITSU_OT_build_new_shot(bpy.types.Operator): # Set Up Scene + Naming shot_task_name = shot.get_shot_task_name(task_type) - create_scene(shot_task_name) + scene = create_scene(shot_task_name) + set_resolution_and_fps(active_project, scene) # File Path # TODO Only run if saving file -- 2.30.2 From e25c2bd40bb7c8c81e7aa4bac17a5eb1f60ca241 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Mon, 18 Dec 2023 14:03:47 -0500 Subject: [PATCH 22/55] Fix bug in set_shot_scene --- .../addons/blender_kitsu/shot_builder_2/core.py | 16 ++++++---------- .../addons/blender_kitsu/shot_builder_2/ops.py | 4 ++-- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py index 826725da..ac4ff223 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py @@ -49,22 +49,18 @@ def set_render_engine(scene: bpy.types.Scene, engine='CYCLES'): scene.render.engine = engine -def create_scene(scene_name: str) -> bpy.types.Scene: +def set_shot_scene(scene_name: str) -> bpy.types.Scene: print(f"create scene with name {scene_name}") - scene = bpy.data.scenes.new(name=scene_name) - - bpy.context.window.scene = scene - remove_other_scenes(scene_name) - return scene - - -def remove_other_scenes(keep_scene_name: str) -> None: + keep_scene = bpy.data.scenes.new(name=scene_name) for scene in bpy.data.scenes: - if scene.name == keep_scene_name: + if scene.name == scene_name: continue print(f"remove scene {scene.name}") bpy.data.scenes.remove(scene) + bpy.context.window.scene = keep_scene + return keep_scene + def set_resolution_and_fps(project: Project, scene: bpy.types.Scene): scene.render.fps = project.fps # set fps diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py index 1afbc716..8ce03605 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py @@ -7,7 +7,7 @@ from .core import ( set_render_engine, link_camera_rig, task_type_anim_output_collection, - create_scene, + set_shot_scene, set_resolution_and_fps, ) @@ -134,7 +134,7 @@ class KITSU_OT_build_new_shot(bpy.types.Operator): # Set Up Scene + Naming shot_task_name = shot.get_shot_task_name(task_type) - scene = create_scene(shot_task_name) + scene = set_shot_scene(shot_task_name) set_resolution_and_fps(active_project, scene) # File Path -- 2.30.2 From 70417efcb30d1ba2388af4b8c51e195a91f5e72b Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Mon, 18 Dec 2023 14:03:47 -0500 Subject: [PATCH 23/55] Fix bug in set resolution_and_fps --- scripts-blender/addons/blender_kitsu/shot_builder_2/core.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py index ac4ff223..295102a9 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py @@ -63,10 +63,10 @@ def set_shot_scene(scene_name: str) -> bpy.types.Scene: def set_resolution_and_fps(project: Project, scene: bpy.types.Scene): - scene.render.fps = project.fps # set fps + scene.render.fps = int(project.fps) # set fps resolution = project.resolution.split('x') - scene.render.resolution_y = resolution[0] - scene.render.resolution_x = resolution[1] + scene.render.resolution_y = int(resolution[0]) + scene.render.resolution_x = int(resolution[1]) scene.render.resolution_percentage = 100 -- 2.30.2 From 83a6045cf208caff4e54a9c71eba88af729df18d Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Mon, 18 Dec 2023 14:03:47 -0500 Subject: [PATCH 24/55] Add Function to Set Frame Range --- .../addons/blender_kitsu/shot_builder_2/core.py | 10 ++++++++++ .../addons/blender_kitsu/shot_builder_2/ops.py | 2 ++ 2 files changed, 12 insertions(+) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py index 295102a9..27cd1c0f 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py @@ -70,6 +70,16 @@ def set_resolution_and_fps(project: Project, scene: bpy.types.Scene): scene.render.resolution_percentage = 100 +def set_frame_range(shot: Shot, scene: bpy.types.Scene): + start_3d = ( + int(shot.data.get("3d_start")) + if shot.data.get("3d_start") + else 101 # TODO Set in Constants + ) + scene.frame_start = start_3d + scene.frame_end = start_3d + shot.nb_frames - 1 + + def link_and_override_collection( file_path: str, collection_name: str, scene: bpy.types.Scene ) -> bpy.types.Collection: diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py index 8ce03605..a6d7402f 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py @@ -9,6 +9,7 @@ from .core import ( task_type_anim_output_collection, set_shot_scene, set_resolution_and_fps, + set_frame_range, ) active_project = None @@ -136,6 +137,7 @@ class KITSU_OT_build_new_shot(bpy.types.Operator): shot_task_name = shot.get_shot_task_name(task_type) scene = set_shot_scene(shot_task_name) set_resolution_and_fps(active_project, scene) + set_frame_range(shot, scene) # File Path # TODO Only run if saving file -- 2.30.2 From eef16cf6a1b4d79e74682147b64c4d5e26b04f63 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Mon, 18 Dec 2023 14:03:47 -0500 Subject: [PATCH 25/55] Create Output Collection based on Dict --- .../addons/blender_kitsu/bkglobals.py | 17 +++++++++++++++++ .../addons/blender_kitsu/shot_builder_2/core.py | 2 +- .../addons/blender_kitsu/shot_builder_2/ops.py | 10 ++++++---- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/bkglobals.py b/scripts-blender/addons/blender_kitsu/bkglobals.py index 32e4505e..17e26251 100644 --- a/scripts-blender/addons/blender_kitsu/bkglobals.py +++ b/scripts-blender/addons/blender_kitsu/bkglobals.py @@ -90,3 +90,20 @@ RES_DIR_PATH = Path(os.path.abspath(__file__)).parent.joinpath("res") SCENE_NAME_PLAYBLAST = "playblast_playback" PLAYBLAST_DEFAULT_STATUS = "Todo" + +########################### +# Shot Builder Properties +########################### + +OUTPUT_COL_CREATE = { + "anim": True, + "comp": False, + "fx": True, + "layout": True, + "lighting": True, + "previz": True, + "rendering": False, + "smear_to_mesh": False, + "storyboard": True, +} + diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py index 27cd1c0f..a5b53dd2 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py @@ -141,7 +141,7 @@ def link_camera_rig( scene.camera = camera -def task_type_anim_output_collection( +def task_type_output_collection( scene: bpy.types.Scene, shot: Shot, task_type: TaskType ) -> bpy.types.Collection: collections = bpy.data.collections diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py index a6d7402f..467dd225 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py @@ -1,4 +1,5 @@ import bpy +from .. import bkglobals from pathlib import Path from typing import List, Any, Tuple, Set, cast from .. import prefs, cache @@ -6,7 +7,7 @@ from .core import ( get_file_dir, set_render_engine, link_camera_rig, - task_type_anim_output_collection, + task_type_output_collection, set_shot_scene, set_resolution_and_fps, set_frame_range, @@ -132,6 +133,7 @@ class KITSU_OT_build_new_shot(bpy.types.Operator): seq = active_project.get_sequence(self.seq_id) shot = active_project.get_shot(self.shot_id) task_type = self._get_task_type_for_shot(context, shot) + task_short_name = task_type.get_short_name() # Set Up Scene + Naming shot_task_name = shot.get_shot_task_name(task_type) @@ -146,14 +148,14 @@ class KITSU_OT_build_new_shot(bpy.types.Operator): # Set Render Settings if ( - task_type.get_short_name() == 'anim' + task_short_name == 'anim' ): # TODO get anim from a constant instead set_render_engine(context.scene, 'BLENDER_WORKBENCH') else: set_render_engine(context.scene) - output_col = task_type_anim_output_collection(context.scene, shot, task_type) - link_camera_rig(context.scene, output_col) + if bkglobals.OUTPUT_COL_CREATE.get(task_short_name): + output_col = task_type_output_collection(context.scene, shot, task_type) print("Create shot with the following details") # TODO Remove print(f"Seq Name: '{seq.name}' Seq ID: '{self.seq_id}'") -- 2.30.2 From 87f984bcea92ec863bb5bb5d8e0170e8737bfe6d Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Mon, 18 Dec 2023 14:03:47 -0500 Subject: [PATCH 26/55] Only Link Camera Rig if 'anim' task type --- .../addons/blender_kitsu/shot_builder_2/ops.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py index 467dd225..e2c0b45a 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py @@ -147,15 +147,17 @@ class KITSU_OT_build_new_shot(bpy.types.Operator): shot_file_path_str = dir.joinpath(shot_task_name).__str__() # Set Render Settings - if ( - task_short_name == 'anim' - ): # TODO get anim from a constant instead + if task_short_name == 'anim': # TODO get anim from a constant instead set_render_engine(context.scene, 'BLENDER_WORKBENCH') else: set_render_engine(context.scene) if bkglobals.OUTPUT_COL_CREATE.get(task_short_name): - output_col = task_type_output_collection(context.scene, shot, task_type) + output_col = task_type_output_collection( # TODO imporve + context.scene, shot, task_type + ) + if task_short_name == 'anim': + link_camera_rig(context.scene, output_col) print("Create shot with the following details") # TODO Remove print(f"Seq Name: '{seq.name}' Seq ID: '{self.seq_id}'") -- 2.30.2 From c3c5e711732440c2bc56df7a8b9f1339933c3dba Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Mon, 18 Dec 2023 14:03:47 -0500 Subject: [PATCH 27/55] Add Map/Func to Link Output Collections --- .../addons/blender_kitsu/bkglobals.py | 11 +++++++ .../blender_kitsu/shot_builder_2/core.py | 32 ++++++++++++++----- .../blender_kitsu/shot_builder_2/ops.py | 9 ++++-- 3 files changed, 42 insertions(+), 10 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/bkglobals.py b/scripts-blender/addons/blender_kitsu/bkglobals.py index 17e26251..6e42b070 100644 --- a/scripts-blender/addons/blender_kitsu/bkglobals.py +++ b/scripts-blender/addons/blender_kitsu/bkglobals.py @@ -107,3 +107,14 @@ OUTPUT_COL_CREATE = { "storyboard": True, } +OUTPUT_COL_LINK_MAPPING = { + "anim": None, + "comp": ['anim', 'fx', 'lighting'], + "fx": ['anim', 'lighting'], + "layout": None, + "lighting": ['anim'], + "previz": None, + "rendering": None, + "smear_to_mesh": None, + "storyboard": None, +} diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py index a5b53dd2..ef754c36 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py @@ -1,6 +1,7 @@ import bpy from pathlib import Path +from .. import bkglobals from ..types import ( Sequence, Shot, @@ -80,6 +81,16 @@ def set_frame_range(shot: Shot, scene: bpy.types.Scene): scene.frame_end = start_3d + shot.nb_frames - 1 +def link_collection(file_path: str, collection_name: str): + bpy.ops.wm.link( + filepath=file_path, + directory=file_path + "/Collection", + filename=collection_name, + instance_collections=False, + ) + return bpy.data.collections.get(collection_name) + + def link_and_override_collection( file_path: str, collection_name: str, scene: bpy.types.Scene ) -> bpy.types.Collection: @@ -93,13 +104,7 @@ def link_and_override_collection( Returns: bpy.types.Collection: Overriden Collection linked to Scene Collection """ - bpy.ops.wm.link( - filepath=file_path, - directory=file_path + "/Collection", - filename=collection_name, - instance_collections=False, - ) - camera_col = bpy.data.collections.get(collection_name) + camera_col = link_collection(file_path, collection_name) override_camera_col = camera_col.override_hierarchy_create( scene, bpy.context.view_layer, do_fully_editable=True ) @@ -141,7 +146,7 @@ def link_camera_rig( scene.camera = camera -def task_type_output_collection( +def create_task_type_output_collection( scene: bpy.types.Scene, shot: Shot, task_type: TaskType ) -> bpy.types.Collection: collections = bpy.data.collections @@ -161,3 +166,14 @@ def task_type_output_collection( ) view_layer_output_collection.exclude = True return output_collection + + +def link_task_type_output_collections(shot: Shot, task_short_name: str): + # TODO TEST IF THIS WORKS + for short_name in bkglobals.OUTPUT_COL_LINK_MAPPING.get(task_short_name): + external_filepath = dir.joinpath(short_name) + if not external_filepath.exists(): + print(f"Unable to link output collection for {external_filepath.name}") + file_path = external_filepath.__str__() + colection_name = shot.get_shot_task_name(short_name) + link_collection(file_path, colection_name) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py index e2c0b45a..92f6aeb2 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py @@ -7,10 +7,11 @@ from .core import ( get_file_dir, set_render_engine, link_camera_rig, - task_type_output_collection, + create_task_type_output_collection, set_shot_scene, set_resolution_and_fps, set_frame_range, + link_task_type_output_collections, ) active_project = None @@ -152,13 +153,17 @@ class KITSU_OT_build_new_shot(bpy.types.Operator): else: set_render_engine(context.scene) + # Create Output Collection & Link Camera if bkglobals.OUTPUT_COL_CREATE.get(task_short_name): - output_col = task_type_output_collection( # TODO imporve + output_col = create_task_type_output_collection( # TODO imporve context.scene, shot, task_type ) if task_short_name == 'anim': link_camera_rig(context.scene, output_col) + # Link External Output Collections + link_task_type_output_collections(shot, task_short_name) + print("Create shot with the following details") # TODO Remove print(f"Seq Name: '{seq.name}' Seq ID: '{self.seq_id}'") print(f"Shot Name: '{shot.name}' Shot ID: '{self.shot_id}'") -- 2.30.2 From 327244d9fa74fc3d49cef6259597cc11ae3d93b9 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Mon, 18 Dec 2023 16:14:51 -0500 Subject: [PATCH 28/55] Load Editorial if Avaliable & Add Dict to bkglobals --- .../addons/blender_kitsu/bkglobals.py | 12 +++ .../blender_kitsu/shot_builder_2/editorial.py | 80 +++++++++++++++++++ .../blender_kitsu/shot_builder_2/ops.py | 5 ++ 3 files changed, 97 insertions(+) create mode 100644 scripts-blender/addons/blender_kitsu/shot_builder_2/editorial.py diff --git a/scripts-blender/addons/blender_kitsu/bkglobals.py b/scripts-blender/addons/blender_kitsu/bkglobals.py index 6e42b070..05a11c82 100644 --- a/scripts-blender/addons/blender_kitsu/bkglobals.py +++ b/scripts-blender/addons/blender_kitsu/bkglobals.py @@ -118,3 +118,15 @@ OUTPUT_COL_LINK_MAPPING = { "smear_to_mesh": None, "storyboard": None, } + +LOAD_EDITORIAL_REF = { + "anim": True, + "comp": False, + "fx": False, + "layout": True, + "lighting": False, + "previz": False, + "rendering": False, + "smear_to_mesh": False, + "storyboard": False, +} diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/editorial.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/editorial.py new file mode 100644 index 00000000..8c9f7548 --- /dev/null +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/editorial.py @@ -0,0 +1,80 @@ +import bpy +from .. import prefs +from pathlib import Path +import re + + +def editorial_export_get_latest( + context: bpy.types.Context, shot +) -> list[bpy.types.Sequence]: # TODO add info to shot + """Loads latest export from editorial department""" + addon_prefs = prefs.addon_prefs_get(context) + strip_channel = 1 + latest_file = editorial_export_check_latest(context) + if not latest_file: + return None + # Check if Kitsu server returned empty shot + if shot.id == '': + return None + strip_filepath = latest_file.as_posix() + strip_frame_start = addon_prefs.shot_builder_frame_offset + + scene = context.scene + if not scene.sequence_editor: + scene.sequence_editor_create() + seq_editor = scene.sequence_editor + movie_strip = seq_editor.sequences.new_movie( + latest_file.name, + strip_filepath, + strip_channel + 1, + strip_frame_start, + fit_method="FIT", + ) + sound_strip = seq_editor.sequences.new_sound( + latest_file.name, + strip_filepath, + strip_channel, + strip_frame_start, + ) + new_strips = [movie_strip, sound_strip] + + # Update shift frame range prop. + frame_in = shot.data.get("frame_in") + frame_3d_start = shot.data.get("3d_start") + frame_3d_offset = frame_3d_start - addon_prefs.shot_builder_frame_offset + edit_export_offset = addon_prefs.edit_export_frame_offset + + # Set sequence strip start kitsu data. + for strip in new_strips: + strip.frame_start = ( + -frame_in + (strip_frame_start * 2) + frame_3d_offset + edit_export_offset + ) + return new_strips + + +def editorial_export_check_latest(context: bpy.types.Context): + """Find latest export in editorial export directory""" + addon_prefs = prefs.addon_prefs_get(context) + + edit_export_path = Path(addon_prefs.edit_export_dir) + + files_list = [ + f + for f in edit_export_path.iterdir() + if f.is_file() + and editorial_export_is_valid_edit_name( + addon_prefs.edit_export_file_pattern, f.name + ) + ] + if len(files_list) >= 1: + files_list = sorted(files_list, reverse=True) + return files_list[0] + return None + + +def editorial_export_is_valid_edit_name(file_pattern: str, filename: str) -> bool: + """Verify file name matches file pattern set in preferences""" + match = re.search(file_pattern, filename) + if match: + return True + return False diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py index 92f6aeb2..fb59e4b0 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py @@ -14,6 +14,8 @@ from .core import ( link_task_type_output_collections, ) +from .editorial import editorial_export_get_latest + active_project = None @@ -164,6 +166,9 @@ class KITSU_OT_build_new_shot(bpy.types.Operator): # Link External Output Collections link_task_type_output_collections(shot, task_short_name) + if bkglobals.LOAD_EDITORIAL_REF.get(task_short_name): + editorial_export_get_latest(context, shot) + print("Create shot with the following details") # TODO Remove print(f"Seq Name: '{seq.name}' Seq ID: '{self.seq_id}'") print(f"Shot Name: '{shot.name}' Shot ID: '{self.shot_id}'") -- 2.30.2 From 6dc0611227217b4d60490eea8d897f0fa18d7da7 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Mon, 18 Dec 2023 16:18:21 -0500 Subject: [PATCH 29/55] Fix bug in `link_task_type_output_collections()` --- scripts-blender/addons/blender_kitsu/shot_builder_2/core.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py index ef754c36..65ada1e4 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py @@ -170,6 +170,8 @@ def create_task_type_output_collection( def link_task_type_output_collections(shot: Shot, task_short_name: str): # TODO TEST IF THIS WORKS + if bkglobals.OUTPUT_COL_LINK_MAPPING.get(task_short_name) == None: + return for short_name in bkglobals.OUTPUT_COL_LINK_MAPPING.get(task_short_name): external_filepath = dir.joinpath(short_name) if not external_filepath.exists(): -- 2.30.2 From 0730d3b59d7071f5a54553ece6e952f1be4f2bee Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Mon, 18 Dec 2023 17:12:09 -0500 Subject: [PATCH 30/55] Move `project_root_dir_get()` to Prefs --- scripts-blender/addons/blender_kitsu/prefs.py | 5 +++++ .../addons/blender_kitsu/shot_builder_2/core.py | 13 ++++--------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/prefs.py b/scripts-blender/addons/blender_kitsu/prefs.py index 17d66644..3e91ccb6 100644 --- a/scripts-blender/addons/blender_kitsu/prefs.py +++ b/scripts-blender/addons/blender_kitsu/prefs.py @@ -527,6 +527,11 @@ def addon_prefs_get(context: bpy.types.Context) -> bpy.types.AddonPreferences: return context.preferences.addons["blender_kitsu"].preferences +def project_root_dir_get(context: bpy.types.Context): + addon_prefs = addon_prefs_get(context) + return Path(addon_prefs.project_root_dir).resolve() + + def session_auth(context: bpy.types.Context) -> bool: """ Shortcut to check if zession is authorized diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py index 65ada1e4..ea2e7dca 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py @@ -10,7 +10,7 @@ from ..types import ( from ..cache import Project -from blender_kitsu import prefs +from .. import prefs ################# # Constants @@ -18,11 +18,6 @@ from blender_kitsu import prefs CAMERA_NAME = 'CAM-camera' -def get_project_root_path() -> Path: - addon_prefs = prefs.addon_prefs_get(bpy.context) - return Path(addon_prefs.project_root_dir).resolve() - - def get_file_dir(seq: Sequence, shot: Shot, task_type: TaskType) -> Path: """Returns Path to Directory for Current Shot, will ensure that file path exists if it does not. @@ -35,7 +30,7 @@ def get_file_dir(seq: Sequence, shot: Shot, task_type: TaskType) -> Path: Returns: Path: Returns Path for Shot Directory """ - project_root_dir = get_project_root_path() + project_root_dir = prefs.project_root_dir_get(bpy.context) all_shots_dir = project_root_dir.joinpath('pro').joinpath('shots') shot_dir = all_shots_dir.joinpath(seq.name).joinpath(shot.name) if not shot_dir.exists(): @@ -122,7 +117,7 @@ def link_camera_rig( of the shot and the camera will be set as active camera. """ # Load camera rig. - project_path = get_project_root_path() + project_path = prefs.project_root_dir_get(bpy.context) path = f"{project_path}/pro/assets/cam/camera_rig.blend" if not Path(path).exists(): @@ -173,7 +168,7 @@ def link_task_type_output_collections(shot: Shot, task_short_name: str): if bkglobals.OUTPUT_COL_LINK_MAPPING.get(task_short_name) == None: return for short_name in bkglobals.OUTPUT_COL_LINK_MAPPING.get(task_short_name): - external_filepath = dir.joinpath(short_name) + external_filepath = shot.get_shot_filepath(bpy.context, short_name) if not external_filepath.exists(): print(f"Unable to link output collection for {external_filepath.name}") file_path = external_filepath.__str__() -- 2.30.2 From 51a64cdaab799a8a73cc048b4148a934ed6fac0e Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Mon, 18 Dec 2023 17:15:08 -0500 Subject: [PATCH 31/55] Add Functions to get File_path from Shot Class --- .../addons/blender_kitsu/shot_builder_2/ops.py | 4 +--- scripts-blender/addons/blender_kitsu/types.py | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py index fb59e4b0..8faa46be 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py @@ -4,7 +4,6 @@ from pathlib import Path from typing import List, Any, Tuple, Set, cast from .. import prefs, cache from .core import ( - get_file_dir, set_render_engine, link_camera_rig, create_task_type_output_collection, @@ -146,8 +145,7 @@ class KITSU_OT_build_new_shot(bpy.types.Operator): # File Path # TODO Only run if saving file - dir = get_file_dir(seq, shot, task_type) - shot_file_path_str = dir.joinpath(shot_task_name).__str__() + shot_file_path_str = shot.get_shot_filepath(context, task_type) # Set Render Settings if task_short_name == 'anim': # TODO get anim from a constant instead diff --git a/scripts-blender/addons/blender_kitsu/types.py b/scripts-blender/addons/blender_kitsu/types.py index 0b42eb98..12dec209 100644 --- a/scripts-blender/addons/blender_kitsu/types.py +++ b/scripts-blender/addons/blender_kitsu/types.py @@ -22,11 +22,11 @@ from __future__ import annotations import inspect from dataclasses import asdict, dataclass, field from typing import Any, Dict, List, Optional, Union, Tuple, TypeVar - +from pathlib import Path import gazu from .logger import LoggerFactory from . import bkglobals - +from . import prefs logger = LoggerFactory.getLogger() @@ -573,6 +573,18 @@ class Shot(Entity): def get_output_collection_name(self, task_type: TaskType) -> str: return f"{self.get_shot_task_name(task_type)}{bkglobals.FILE_DELIMITER}output" + def get_shot_dir(self, context) -> str: + project_root_dir = prefs.project_root_dir_get(context) + all_shots_dir = project_root_dir.joinpath('pro').joinpath('shots') + seq = self.get_sequence() + shot_dir = all_shots_dir.joinpath(seq.name).joinpath(self.name) + return shot_dir.__str__() + + def get_shot_filepath(self, context, task_type: TaskType) -> str: + shot_task_name = self.get_shot_task_name(task_type) + file_name = shot_task_name + '.blend' + return Path(self.get_shot_dir(context)).joinpath(file_name).__str__() + 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 447cdb485923b1aa889f2dfefc398e1f3a3b9f9b Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Mon, 18 Dec 2023 17:24:13 -0500 Subject: [PATCH 32/55] Save File After Building --- .../addons/blender_kitsu/shot_builder_2/file_save.py | 12 ++++++++++++ .../addons/blender_kitsu/shot_builder_2/ops.py | 8 ++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 scripts-blender/addons/blender_kitsu/shot_builder_2/file_save.py diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/file_save.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/file_save.py new file mode 100644 index 00000000..49ac105f --- /dev/null +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/file_save.py @@ -0,0 +1,12 @@ +from pathlib import Path +import bpy + + +def save_shot_builder_file(file_path: str): + """Save Shot File within Folder of matching name. + Set Shot File to relative Paths.""" + if Path(file_path).exists(): + raise Exception(f"Cannot Overwrite Existing Shot File {file_path}") + dir_path = Path(file_path).parent + dir_path.mkdir(parents=True, exist_ok=True) + bpy.ops.wm.save_mainfile(filepath=file_path, relative_remap=True) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py index 8faa46be..a11ce8e8 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py @@ -14,6 +14,7 @@ from .core import ( ) from .editorial import editorial_export_get_latest +from .file_save import save_shot_builder_file active_project = None @@ -75,7 +76,7 @@ class KITSU_OT_build_new_shot(bpy.types.Operator): items=get_tasks_for_shot, ) - auto_save: bpy.props.BoolProperty( + save_file: bpy.props.BoolProperty( name="Save after building.", description="Automatically save build file after 'Shot Builder' is complete.", default=True, @@ -89,7 +90,7 @@ class KITSU_OT_build_new_shot(bpy.types.Operator): layout.prop(self, "seq_id") layout.prop(self, "shot_id") layout.prop(self, "task_type") - layout.prop(self, "auto_save") + layout.prop(self, "save_file") def invoke(self, context: bpy.types.Context, event: bpy.types.Event) -> Set[str]: global active_project @@ -167,6 +168,9 @@ class KITSU_OT_build_new_shot(bpy.types.Operator): if bkglobals.LOAD_EDITORIAL_REF.get(task_short_name): editorial_export_get_latest(context, shot) + # Save File + if self.save_file: + save_shot_builder_file(file_path=shot_file_path_str) print("Create shot with the following details") # TODO Remove print(f"Seq Name: '{seq.name}' Seq ID: '{self.seq_id}'") print(f"Shot Name: '{shot.name}' Shot ID: '{self.shot_id}'") -- 2.30.2 From 28bde6eaadc8d8cf20a5593d20ab00b12cda0e87 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Tue, 19 Dec 2023 09:29:40 -0500 Subject: [PATCH 33/55] Fix bug in set project resolution --- scripts-blender/addons/blender_kitsu/shot_builder_2/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py index ea2e7dca..14916c7d 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py @@ -61,8 +61,8 @@ def set_shot_scene(scene_name: str) -> bpy.types.Scene: def set_resolution_and_fps(project: Project, scene: bpy.types.Scene): scene.render.fps = int(project.fps) # set fps resolution = project.resolution.split('x') - scene.render.resolution_y = int(resolution[0]) - scene.render.resolution_x = int(resolution[1]) + scene.render.resolution_x = int(resolution[0]) + scene.render.resolution_y = int(resolution[1]) scene.render.resolution_percentage = 100 -- 2.30.2 From b3113068b3bc0bbc90617ed898bd7ba10e9fb23c Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Tue, 19 Dec 2023 09:52:41 -0500 Subject: [PATCH 34/55] Add .blend Files to LFS --- scripts-blender/addons/blender_kitsu/.gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 scripts-blender/addons/blender_kitsu/.gitattributes diff --git a/scripts-blender/addons/blender_kitsu/.gitattributes b/scripts-blender/addons/blender_kitsu/.gitattributes new file mode 100644 index 00000000..80a1944b --- /dev/null +++ b/scripts-blender/addons/blender_kitsu/.gitattributes @@ -0,0 +1 @@ +*.blend filter=lfs diff=lfs merge=lfs -text -- 2.30.2 From 48ba235beee8420fab3f2ec715d0fb5b4bb2956b Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Tue, 19 Dec 2023 09:53:12 -0500 Subject: [PATCH 35/55] Ignore .blend1 files --- scripts-blender/addons/blender_kitsu/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 scripts-blender/addons/blender_kitsu/.gitignore diff --git a/scripts-blender/addons/blender_kitsu/.gitignore b/scripts-blender/addons/blender_kitsu/.gitignore new file mode 100644 index 00000000..2621fabc --- /dev/null +++ b/scripts-blender/addons/blender_kitsu/.gitignore @@ -0,0 +1 @@ +*.blend1 \ No newline at end of file -- 2.30.2 From a6736b1cd6f4f704f5a46fa61abdfe3cc5f9392f Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Tue, 19 Dec 2023 11:05:19 -0500 Subject: [PATCH 36/55] Add Template .blend files to load Shot UI from per task layer --- .../blender_kitsu/shot_builder_2/ops.py | 5 ++++ .../blender_kitsu/shot_builder_2/template.py | 26 +++++++++++++++++++ .../shot_builder_2/templates/anim.blend | 3 +++ .../shot_builder_2/templates/lighting.blend | 3 +++ 4 files changed, 37 insertions(+) create mode 100644 scripts-blender/addons/blender_kitsu/shot_builder_2/template.py create mode 100644 scripts-blender/addons/blender_kitsu/shot_builder_2/templates/anim.blend create mode 100644 scripts-blender/addons/blender_kitsu/shot_builder_2/templates/lighting.blend diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py index a11ce8e8..a959e569 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py @@ -15,6 +15,7 @@ from .core import ( from .editorial import editorial_export_get_latest from .file_save import save_shot_builder_file +from .template import open_template_for_task_type active_project = None @@ -138,6 +139,10 @@ class KITSU_OT_build_new_shot(bpy.types.Operator): task_type = self._get_task_type_for_shot(context, shot) task_short_name = task_type.get_short_name() + # Open Template File + # TODO DEBUG WHY THIS CAUSES CRASHES + # open_template_for_task_type(task_short_name) + # Set Up Scene + Naming shot_task_name = shot.get_shot_task_name(task_type) scene = set_shot_scene(shot_task_name) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/template.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/template.py new file mode 100644 index 00000000..528f3361 --- /dev/null +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/template.py @@ -0,0 +1,26 @@ +import bpy +from pathlib import Path + + +# TODO add ability for custom templates +def get_template_dir() -> Path: + return Path(__file__).absolute().parent.joinpath("templates") + + +def get_template_files() -> list[Path]: + dir = get_template_dir() + return list(dir.glob('*.blend')) + + +def get_template_for_task_type(task_short_name: str) -> Path: + for file in get_template_files(): + if file.stem == task_short_name: + return file + + +def open_template_for_task_type(task_short_name: str) -> bool: + file_path = get_template_for_task_type(task_short_name) + if file_path.exists() and file_path is not None: + bpy.ops.wm.open_mainfile(filepath=file_path.__str__()) + return True + return False diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/templates/anim.blend b/scripts-blender/addons/blender_kitsu/shot_builder_2/templates/anim.blend new file mode 100644 index 00000000..4aa9fd43 --- /dev/null +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/templates/anim.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cd32511d5d9cd143192271c82dd64bd75428e1fad5ecf340e59bd192c1e6a938 +size 976636 diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/templates/lighting.blend b/scripts-blender/addons/blender_kitsu/shot_builder_2/templates/lighting.blend new file mode 100644 index 00000000..2f341783 --- /dev/null +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/templates/lighting.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c658f9c8099fc1a7114cee942d94f2627b5b631abb9c06fba781bee93489ae1e +size 229732 -- 2.30.2 From cc0daa8ed4a05ee465ec0ec354e6acb33096d9f4 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Tue, 19 Dec 2023 14:20:43 -0500 Subject: [PATCH 37/55] Add Scripts to Index Current Dir of Assets --- scripts/index_assets/README.md | 10 ++ scripts/index_assets/blender_index_assets.py | 66 +++++++++++ scripts/index_assets/run_index_assets.py | 110 +++++++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 scripts/index_assets/README.md create mode 100644 scripts/index_assets/blender_index_assets.py create mode 100755 scripts/index_assets/run_index_assets.py diff --git a/scripts/index_assets/README.md b/scripts/index_assets/README.md new file mode 100644 index 00000000..3530d05c --- /dev/null +++ b/scripts/index_assets/README.md @@ -0,0 +1,10 @@ +# Index Assets + +This tool will create a dictionary of all data-blocks (collections only) [marked as an Asset](https://docs.blender.org/manual/en/latest/files/asset_libraries/introduction.html#asset-create) and stores this information in a JSON file at `you_project/svn/pro/assets/asset_index.json`. This JSON File is used by other tools like the Blender Kitsu Add-On to quickly discover assets to use in the shot builder. + +To run this tool use the following command, followed by the path to your project's root folder + +```bash +./run_index_assets.py your_project/ +``` + diff --git a/scripts/index_assets/blender_index_assets.py b/scripts/index_assets/blender_index_assets.py new file mode 100644 index 00000000..cbec0086 --- /dev/null +++ b/scripts/index_assets/blender_index_assets.py @@ -0,0 +1,66 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +from pathlib import Path +import bpy +import json +import sys + + +def load_json_file(json_file_path: str) -> dict: + """Finds JSON file with asset index if any exist + + Args: + json_file_path (str): Path to existing JSON File + + Returns: + dict: Dictionary with the existing JSON File, else blank + """ + asset_index_json = Path(json_file_path) + if asset_index_json.exists(): + return json.load(open(asset_index_json)) + return {} + + +def dump_json_file(asset_dict: dict, json_file_path: str) -> None: + """Save Asset Index to JSON File at provided path + + Args: + asset_dict (dict): Dictionary of Asset Items + json_file_path (str): Path to Save JSON File + """ + with open(json_file_path, 'w') as json_file: + json.dump(asset_dict, json_file, indent=4) + + +def find_save_assets(): + """Find all collections marked as asset in the current + .blend file, and add them to a dictionary, saved as a JSON""" + + argv = sys.argv + json_file_path = argv[argv.index("--") + 1 :][0] + + asset_dict = load_json_file(json_file_path) + for col in bpy.data.collections: + if col.asset_data: + print(f"Found Asset {col.name}") + asset_dict[col.name] = { + 'type': type(col).bl_rna.name, + 'filepath': bpy.data.filepath, + } + print('Asset Index Completed') + dump_json_file(asset_dict, json_file_path) + + +find_save_assets() diff --git a/scripts/index_assets/run_index_assets.py b/scripts/index_assets/run_index_assets.py new file mode 100755 index 00000000..61d3a0ef --- /dev/null +++ b/scripts/index_assets/run_index_assets.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +from pathlib import Path +import argparse +import os +import platform +import subprocess +import sys + + +def cancel_program(message: str) -> None: + """Cancel Execution of this file""" + print(message) + sys.exit(0) + + +parser = argparse.ArgumentParser() +parser.add_argument( + "path", + help="Path to a file(s) or folder(s) on which to perform crawl. In format of '{my_project}/'", +) + + +def get_bbatch_script_path() -> str: + """Returns path to script that runs with bbatch""" + dir = Path(__file__).parent.absolute() + return dir.joinpath("blender_index_assets.py").__str__() + + +def get_blender_path(project_path: Path) -> str: + """Get the path to a project's blender executable + + Args: + project_path (Path): Path Object, containing project's root path + + Returns: + str: Path to blender executable as a string + """ + # TODO get this from the run_blender.py script instead (new logic needs tobe added to run_blender.py first) + local_blender_path = project_path.joinpath('local').joinpath('blender') + system_name = platform.system().lower() + blender_path_base = local_blender_path / system_name + if system_name == 'linux': + blender_path = blender_path_base / 'blender' + elif system_name == 'darwin': + blender_path = ( + blender_path_base / 'Blender.app' / 'Contents' / 'MacOS' / 'Blender' + ) + elif system_name == 'windows': + blender_path = blender_path_base / 'blender.exe' + return blender_path.absolute().__str__() + + +def index_assets(): + """Crawl the Asset Library of a provided Blender Studio Pipeline Project and + index all assets into a dictionary using a script executed by bbatch""" + args = parser.parse_args() + project_path = Path(args.path) + if not project_path.exists(): + cancel_program("Provided Path does not exist") + asset_dir = ( + project_path.joinpath("svn").joinpath("pro").joinpath("assets").absolute() + ) + if not asset_dir.exists(): + cancel_program("Asset Library does not exist at provided path") + asset_dir_path = asset_dir.__str__() + json_file_path = asset_dir.joinpath("asset_index.json").__str__() + script_path = get_bbatch_script_path() + project_blender = get_blender_path(project_path) + print(project_blender) + os.chdir("../bbatch") + cmd_list = ( + 'python', + '-m', + 'bbatch', + asset_dir_path, + '--ask', + '--exec', + project_blender, + "--nosave", + "--recursive", + '--script', + script_path, + "--args", + f'{json_file_path}', + ) + process = subprocess.Popen(cmd_list, shell=False) + if process.wait() != 0: + cancel_program(f"Asset Index Failed!") + print("Asset Index Completed Successfully") + print(f"Index File: '{json_file_path}'") + return 0 + + +if __name__ == "__main__": + index_assets() -- 2.30.2 From de0a57107c75813e5af7ee4f5d9b8f06deb07fa6 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Tue, 19 Dec 2023 15:54:46 -0500 Subject: [PATCH 38/55] Load Assets with Shot Builder using Asset Index JSON --- .../addons/blender_kitsu/bkglobals.py | 11 ++++ .../blender_kitsu/shot_builder_2/assets.py | 60 +++++++++++++++++++ .../blender_kitsu/shot_builder_2/core.py | 8 +-- .../blender_kitsu/shot_builder_2/ops.py | 4 ++ scripts-blender/addons/blender_kitsu/types.py | 5 ++ 5 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 scripts-blender/addons/blender_kitsu/shot_builder_2/assets.py diff --git a/scripts-blender/addons/blender_kitsu/bkglobals.py b/scripts-blender/addons/blender_kitsu/bkglobals.py index 05a11c82..28a740b4 100644 --- a/scripts-blender/addons/blender_kitsu/bkglobals.py +++ b/scripts-blender/addons/blender_kitsu/bkglobals.py @@ -95,6 +95,8 @@ PLAYBLAST_DEFAULT_STATUS = "Todo" # Shot Builder Properties ########################### +# TODO add documentation and move other shot builder props here + OUTPUT_COL_CREATE = { "anim": True, "comp": False, @@ -130,3 +132,12 @@ LOAD_EDITORIAL_REF = { "smear_to_mesh": False, "storyboard": False, } + +ASSET_TYPE_TO_OVERRIDE = { + "CH": True, # Character + "PR": True, # Rigged Prop + "LI": True, # Library/Environment Asset + "SE": False, # Set + "LG": True, # Lighting Rig + "CA": True, # Camera Rig +} diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/assets.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/assets.py new file mode 100644 index 00000000..717cc6a5 --- /dev/null +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/assets.py @@ -0,0 +1,60 @@ +import bpy +from .. import prefs +from pathlib import Path +import json +from ..types import Shot +from .core import link_and_override_collection, link_data_block +from .. import bkglobals + + +def get_asset_index_file() -> str: + svn_project_root_dir = prefs.project_root_dir_get(bpy.context) + asset_index_file = ( + Path(svn_project_root_dir) + .joinpath("pro") + .joinpath("assets") + .joinpath("asset_index.json") + ) + if asset_index_file.exists(): + return asset_index_file.__str__() + + +def get_assset_index() -> dict: + asset_index_file = get_asset_index_file() + if asset_index_file is None: + return + return json.load(open(asset_index_file)) + + +def get_shot_assets( + scene: bpy.types.Scene, + output_collection: bpy.types.Collection, + shot: Shot, +): + asset_index = get_assset_index() + if asset_index is None: + return + assets = shot.get_all_assets() + asset_slugs = [ + asset.data.get("slug") for asset in assets if asset.data.get("slug") is not None + ] + if asset_slugs == []: + print("No asset slugs found on Kitsu Server. Assets will not be loaded") + for key, value in asset_index.items(): + if key in asset_slugs: + filepath = value.get('filepath') + data_type = value.get('type') + if bkglobals.ASSET_TYPE_TO_OVERRIDE.get(key.split('-')[0]): + if data_type != "Collection": + print(f"Cannot load {key} because it is not a collection") + continue + linked_collection = link_and_override_collection( + collection_name=key, file_path=filepath, scene=scene + ) + print(f"'{key}': Succesfully Linked & Overriden") + else: + linked_collection = link_data_block( + file_path=filepath, collection_name=key, data_block_type=data_type + ) + print(f"'{key}': Succesfully Linked") + output_collection.children.link(linked_collection) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py index 14916c7d..820865d6 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py @@ -76,10 +76,10 @@ def set_frame_range(shot: Shot, scene: bpy.types.Scene): scene.frame_end = start_3d + shot.nb_frames - 1 -def link_collection(file_path: str, collection_name: str): +def link_data_block(file_path: str, collection_name: str, data_block_type: str): bpy.ops.wm.link( filepath=file_path, - directory=file_path + "/Collection", + directory=file_path + "/" + data_block_type, filename=collection_name, instance_collections=False, ) @@ -99,7 +99,7 @@ def link_and_override_collection( Returns: bpy.types.Collection: Overriden Collection linked to Scene Collection """ - camera_col = link_collection(file_path, collection_name) + camera_col = link_data_block(file_path, collection_name, "Collection") override_camera_col = camera_col.override_hierarchy_create( scene, bpy.context.view_layer, do_fully_editable=True ) @@ -173,4 +173,4 @@ def link_task_type_output_collections(shot: Shot, task_short_name: str): print(f"Unable to link output collection for {external_filepath.name}") file_path = external_filepath.__str__() colection_name = shot.get_shot_task_name(short_name) - link_collection(file_path, colection_name) + link_data_block(file_path, colection_name) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py index a959e569..1f88cae7 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py @@ -16,6 +16,7 @@ from .core import ( from .editorial import editorial_export_get_latest from .file_save import save_shot_builder_file from .template import open_template_for_task_type +from .assets import get_shot_assets active_project = None @@ -167,6 +168,9 @@ class KITSU_OT_build_new_shot(bpy.types.Operator): if task_short_name == 'anim': link_camera_rig(context.scene, output_col) + # Load Assets + get_shot_assets(scene=scene, output_collection=output_col, shot=shot) + # Link External Output Collections link_task_type_output_collections(shot, task_short_name) diff --git a/scripts-blender/addons/blender_kitsu/types.py b/scripts-blender/addons/blender_kitsu/types.py index 12dec209..bbaad10d 100644 --- a/scripts-blender/addons/blender_kitsu/types.py +++ b/scripts-blender/addons/blender_kitsu/types.py @@ -560,6 +560,11 @@ class Shot(Entity): def get_all_tasks(self) -> List[Task]: return [Task.from_dict(t) for t in gazu.task.all_tasks_for_shot(asdict(self))] + def get_all_assets(self) -> List[Asset]: + return [ + Asset.from_dict(t) for t in gazu.asset.all_assets_for_shot(asdict(self)) + ] + def get_sequence(self) -> Sequence: return Sequence.from_dict(gazu.shot.get_sequence_from_shot(asdict(self))) -- 2.30.2 From acbe2bb6cdda91d6375abb767df5b310c216c6bd Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Tue, 19 Dec 2023 17:16:00 -0500 Subject: [PATCH 39/55] Load Workspaces from Template Files to Update UI --- .../blender_kitsu/shot_builder_2/assets.py | 2 +- .../blender_kitsu/shot_builder_2/core.py | 11 ++--- .../blender_kitsu/shot_builder_2/ops.py | 7 ++-- .../blender_kitsu/shot_builder_2/template.py | 42 ++++++++++++++++++- 4 files changed, 51 insertions(+), 11 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/assets.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/assets.py index 717cc6a5..68c20671 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/assets.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/assets.py @@ -54,7 +54,7 @@ def get_shot_assets( print(f"'{key}': Succesfully Linked & Overriden") else: linked_collection = link_data_block( - file_path=filepath, collection_name=key, data_block_type=data_type + file_path=filepath, data_block_name=key, data_block_type=data_type ) print(f"'{key}': Succesfully Linked") output_collection.children.link(linked_collection) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py index 820865d6..9ed1a5f8 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py @@ -45,7 +45,7 @@ def set_render_engine(scene: bpy.types.Scene, engine='CYCLES'): scene.render.engine = engine -def set_shot_scene(scene_name: str) -> bpy.types.Scene: +def set_shot_scene(context: bpy.types.Context, scene_name: str) -> bpy.types.Scene: print(f"create scene with name {scene_name}") keep_scene = bpy.data.scenes.new(name=scene_name) for scene in bpy.data.scenes: @@ -54,7 +54,7 @@ def set_shot_scene(scene_name: str) -> bpy.types.Scene: print(f"remove scene {scene.name}") bpy.data.scenes.remove(scene) - bpy.context.window.scene = keep_scene + context.window.scene = keep_scene return keep_scene @@ -76,14 +76,15 @@ def set_frame_range(shot: Shot, scene: bpy.types.Scene): scene.frame_end = start_3d + shot.nb_frames - 1 -def link_data_block(file_path: str, collection_name: str, data_block_type: str): +def link_data_block(file_path: str, data_block_name: str, data_block_type: str): bpy.ops.wm.link( filepath=file_path, directory=file_path + "/" + data_block_type, - filename=collection_name, + filename=data_block_name, instance_collections=False, ) - return bpy.data.collections.get(collection_name) + # TODO This doesn't return anything but collections + return bpy.data.collections.get(data_block_name) def link_and_override_collection( diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py index 1f88cae7..8075bc0d 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py @@ -15,7 +15,7 @@ from .core import ( from .editorial import editorial_export_get_latest from .file_save import save_shot_builder_file -from .template import open_template_for_task_type +from .template import replace_workspace_with_template from .assets import get_shot_assets active_project = None @@ -141,12 +141,11 @@ class KITSU_OT_build_new_shot(bpy.types.Operator): task_short_name = task_type.get_short_name() # Open Template File - # TODO DEBUG WHY THIS CAUSES CRASHES - # open_template_for_task_type(task_short_name) + replace_workspace_with_template(context, task_short_name) # Set Up Scene + Naming shot_task_name = shot.get_shot_task_name(task_type) - scene = set_shot_scene(shot_task_name) + scene = set_shot_scene(context, shot_task_name) set_resolution_and_fps(active_project, scene) set_frame_range(shot, scene) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/template.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/template.py index 528f3361..928d32e2 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/template.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/template.py @@ -1,5 +1,6 @@ import bpy from pathlib import Path +from .core import link_data_block # TODO add ability for custom templates @@ -19,8 +20,47 @@ def get_template_for_task_type(task_short_name: str) -> Path: def open_template_for_task_type(task_short_name: str) -> bool: + # TODO THIS DOESN'T WORK BECAUSE CHANGE THE OPEN FILE MESSES UP THE CONTEXT SOMEHOW file_path = get_template_for_task_type(task_short_name) if file_path.exists() and file_path is not None: - bpy.ops.wm.open_mainfile(filepath=file_path.__str__()) + bpy.ops.wm.open_mainfile('EXEC_DEFAULT', filepath=file_path.__str__()) return True return False + + +def replace_workspace_with_template(context: bpy.types.Context, task_short_name: str): + file_path = get_template_for_task_type(task_short_name).resolve().absolute() + remove_prefix = "REMOVE-" + if not file_path.exists(): + return + + # Mark Existing Workspaces for Removal + for workspace in bpy.data.workspaces: + if workspace.name.startswith(remove_prefix): + continue + workspace.name = remove_prefix + workspace.name + + file_path_str = file_path.__str__() + with bpy.data.libraries.load(file_path_str) as (data_from, data_to): + for workspace in data_from.workspaces: + bpy.ops.wm.append( + filepath=file_path_str, + directory=file_path_str + "/" + 'WorkSpace', + filename=str(workspace), + ) + + for lib in bpy.data.libraries: + if lib.filepath == file_path_str: + bpy.data.libraries.remove(bpy.data.libraries.get(lib.name)) + break + + workspaces_to_remove = [] + for workspace in bpy.data.workspaces: + if workspace.name.startswith(remove_prefix): + workspaces_to_remove.append(workspace) + + # context.window.workspace = workspace + for workspace in workspaces_to_remove: + with context.temp_override(workspace=workspace): + bpy.ops.workspace.delete() + return True -- 2.30.2 From af2ec3c7372d5fc3bb0ba2eb745884a0283d1431 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Wed, 20 Dec 2023 15:09:47 -0500 Subject: [PATCH 40/55] Add Support for Hooks --- .../shot_builder_2/hook_examples/hooks.py | 51 +++++ .../blender_kitsu/shot_builder_2/hooks.py | 195 ++++++++++++++++++ .../blender_kitsu/shot_builder_2/ops.py | 14 +- 3 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 scripts-blender/addons/blender_kitsu/shot_builder_2/hook_examples/hooks.py create mode 100644 scripts-blender/addons/blender_kitsu/shot_builder_2/hooks.py diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/hook_examples/hooks.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/hook_examples/hooks.py new file mode 100644 index 00000000..37b0bdd4 --- /dev/null +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/hook_examples/hooks.py @@ -0,0 +1,51 @@ +import bpy + +from blender_kitsu.shot_builder_2.hooks import hook +from blender_kitsu.types import Shot, Asset + +import logging + +''' +Arguments to use in hooks + scene: bpy.types.Scene # current scene + shot: Shot class from blender_kitsu.types.py + prod_path: str # path to production root dir (your_project/svn/) + shot_path: str # path to shot file (your_project/svn/pro/shots/{sequence_name}/{shot_name}/{shot_task_name}.blend}) + +Notes + matching_task_type = ['anim', 'lighting', 'fx', 'comp'] # either use list or just one string + output_col_name = shot.get_output_collection_name(task_type="anim") + + + + +''' + +logger = logging.getLogger(__name__) + +# ---------- Global Hook ---------- + + +@hook() +def set_eevee_render_engine(scene: bpy.types.Scene, **kwargs): + """ + By default, we set EEVEE as the renderer. + """ + scene.render.engine = 'BLENDER_EEVEE' + print("HOOK SET RENDER ENGINE") + + +# ---------- Overrides for animation files ---------- + + +@hook(match_task_type='anim') +def test_args( + scene: bpy.types.Scene, shot: Shot, prod_path: str, shot_path: str, **kwargs +): + """ + Set output parameters for animation rendering + """ + print(f"Scene = {scene.name}") + print(f"Shot = {shot.name}") + print(f"Prod Path = {prod_path}") + print(f"Shot Path = {shot_path}") diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/hooks.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/hooks.py new file mode 100644 index 00000000..3a1a9e8d --- /dev/null +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/hooks.py @@ -0,0 +1,195 @@ +import sys +import pathlib +from typing import * + +import typing +import types +from collections.abc import Iterable +import importlib +from .. import prefs +import logging + +logger = logging.getLogger(__name__) + + +class Wildcard: + pass + + +class DoNotMatch: + pass + + +MatchCriteriaType = typing.Union[ + str, typing.List[str], typing.Type[Wildcard], typing.Type[DoNotMatch] +] +""" +The MatchCriteriaType is a type definition for the parameters of the `hook` decorator. + +The matching parameters can use multiple types to detect how the matching criteria +would work. + +* `str`: would perform an exact string match. +* `typing.Iterator[str]`: would perform an exact string match with any of the given strings. +* `typing.Type[Wildcard]`: would match any type for this parameter. This would be used so a hook + is called for any value. +* `typing.Type[DoNotMatch]`: would ignore this hook when matching the hook parameter. This is the default + value for the matching criteria and would normally not be set directly in a + production configuration. +""" + +MatchingRulesType = typing.Dict[str, MatchCriteriaType] +""" +Hooks are stored as `_shot_builder_rules' attribute on the function. +The MatchingRulesType is the type definition of the `_shot_builder_rules` attribute. +""" + +HookFunction = typing.Callable[[typing.Any], None] + + +def _match_hook_parameter( + hook_criteria: MatchCriteriaType, match_query: typing.Optional[str] +) -> bool: + if hook_criteria == None: + return True + if hook_criteria == DoNotMatch: + return match_query is None + if hook_criteria == Wildcard: + return True + if isinstance(hook_criteria, str): + return match_query == hook_criteria + if isinstance(hook_criteria, list): + return match_query in hook_criteria + logger.error(f"Incorrect matching criteria {hook_criteria}, {match_query}") + return False + + +class Hooks: + def __init__(self): + self._hooks: typing.List[HookFunction] = [] + + def matches( + self, + hook: HookFunction, + match_task_type: typing.Optional[str] = None, + match_asset_type: typing.Optional[str] = None, + **kwargs: typing.Optional[str], + ) -> bool: + assert not kwargs + rules = typing.cast(MatchingRulesType, getattr(hook, '_shot_builder_rules')) + return all( + ( + _match_hook_parameter(rules['match_task_type'], match_task_type), + _match_hook_parameter(rules['match_asset_type'], match_asset_type), + ) + ) + + def filter(self, **kwargs: typing.Optional[str]) -> typing.Iterator[HookFunction]: + for hook in self._hooks: + if self.matches(hook=hook, **kwargs): + yield hook + + def execute_hooks( + self, match_task_type: str = None, match_asset_type: str = None, *args, **kwargs + ) -> None: + for hook in self._hooks: + if self.matches( + hook, match_task_type=match_task_type, match_asset_type=match_asset_type + ): + hook(*args, **kwargs) + + def load_hooks(self, context): + root_dir = prefs.project_root_dir_get(context) + shot_builder_config_dir = root_dir.joinpath("pro/assets/scripts/shot-builder") + if not shot_builder_config_dir.exists(): + raise Exception("Shot Builder Hooks directory does not exist") + paths = [ + shot_builder_config_dir.resolve().__str__() # TODO Make variable path + ] # TODO Set path to where hooks are stored + with SystemPathInclude(paths) as _include: + try: + import hooks as production_hooks + + importlib.reload(production_hooks) + self.register_hooks(production_hooks) + except ModuleNotFoundError: + raise Exception("Production has no `hooks.py` configuration file") + + return False + + def register(self, func: HookFunction) -> None: + logger.info(f"registering hook '{func.__name__}'") + self._hooks.append(func) + + def register_hooks(self, module: types.ModuleType) -> None: + """ + Register all hooks inside the given module. + """ + for module_item_str in dir(module): + module_item = getattr(module, module_item_str) + if not isinstance(module_item, types.FunctionType): + continue + if module_item.__module__ != module.__name__: + continue + if not hasattr(module_item, "_shot_builder_rules"): + continue + self.register(module_item) + + +def hook( + match_task_type: MatchCriteriaType = None, + match_asset_type: MatchCriteriaType = None, +) -> typing.Callable[[types.FunctionType], types.FunctionType]: + """ + Decorator to add custom logic when building a shot. + + Hooks are used to extend the configuration that would be not part of the core logic of the shot builder tool. + """ + rules = { + 'match_task_type': match_task_type, + 'match_asset_type': match_asset_type, + } + + def wrapper(func: types.FunctionType) -> types.FunctionType: + setattr(func, '_shot_builder_rules', rules) + return func + + return wrapper + + +class SystemPathInclude: + """ + Resource class to temporary include system paths to `sys.paths`. + + Usage: + ``` + paths = [pathlib.Path("/home/guest/my_python_scripts")] + with SystemPathInclude(paths) as t: + import my_module + reload(my_module) + ``` + + It is possible to nest multiple SystemPathIncludes. + """ + + def __init__(self, paths_to_add: List[pathlib.Path]): + # TODO: Check if all paths exist and are absolute. + self.__paths = paths_to_add + self.__original_sys_path: List[str] = [] + + def __enter__(self): + self.__original_sys_path = sys.path + new_sys_path = [] + for path_to_add in self.__paths: + # Do not add paths that are already in the sys path. + # Report this to the logger as this might indicate wrong usage. + path_to_add_str = str(path_to_add) + if path_to_add_str in self.__original_sys_path: + logger.warn(f"{path_to_add_str} already added to `sys.path`") + continue + new_sys_path.append(path_to_add_str) + new_sys_path.extend(self.__original_sys_path) + sys.path = new_sys_path + + def __exit__(self, exc_type, exc_value, exc_traceback): + sys.path = self.__original_sys_path diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py index 8075bc0d..5e7b286d 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py @@ -17,6 +17,7 @@ from .editorial import editorial_export_get_latest from .file_save import save_shot_builder_file from .template import replace_workspace_with_template from .assets import get_shot_assets +from .hooks import Hooks active_project = None @@ -139,6 +140,7 @@ class KITSU_OT_build_new_shot(bpy.types.Operator): shot = active_project.get_shot(self.shot_id) task_type = self._get_task_type_for_shot(context, shot) task_short_name = task_type.get_short_name() + shot_file_path_str = shot.get_shot_filepath(context, task_type) # Open Template File replace_workspace_with_template(context, task_short_name) @@ -151,7 +153,6 @@ class KITSU_OT_build_new_shot(bpy.types.Operator): # File Path # TODO Only run if saving file - shot_file_path_str = shot.get_shot_filepath(context, task_type) # Set Render Settings if task_short_name == 'anim': # TODO get anim from a constant instead @@ -176,6 +177,17 @@ class KITSU_OT_build_new_shot(bpy.types.Operator): if bkglobals.LOAD_EDITORIAL_REF.get(task_short_name): editorial_export_get_latest(context, shot) + # Run Hooks + hooks_instance = Hooks() + hooks_instance.load_hooks(context) + hooks_instance.execute_hooks( + match_task_type=task_short_name, + scene=context.scene, + shot=shot, + prod_path=prefs.project_root_dir_get(context), + shot_path=shot_file_path_str, + ) + # Save File if self.save_file: save_shot_builder_file(file_path=shot_file_path_str) -- 2.30.2 From dacae6e8a838c541f58249635c935c95e0c5fc89 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Thu, 21 Dec 2023 13:02:09 -0500 Subject: [PATCH 41/55] Remove all existing data when building shots (shot builder can be run from saved/dirty files) --- .../blender_kitsu/shot_builder_2/core.py | 18 ++++++++++++++++++ .../addons/blender_kitsu/shot_builder_2/ops.py | 1 + 2 files changed, 19 insertions(+) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py index 9ed1a5f8..ebf71db5 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py @@ -45,8 +45,26 @@ def set_render_engine(scene: bpy.types.Scene, engine='CYCLES'): scene.render.engine = engine +def remove_all_data(): + for lib in bpy.data.libraries: + bpy.data.libraries.remove(lib) + + for col in bpy.data.collections: + bpy.data.collections.remove(col) + + for obj in bpy.data.objects: + bpy.data.objects.remove(obj) + + bpy.ops.outliner.orphans_purge( + 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: print(f"create scene with name {scene_name}") + for scene in bpy.data.scenes: + scene.name = 'REMOVE-' + scene.name + keep_scene = bpy.data.scenes.new(name=scene_name) for scene in bpy.data.scenes: if scene.name == scene_name: diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py index 5e7b286d..3b4fcad0 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py @@ -148,6 +148,7 @@ class KITSU_OT_build_new_shot(bpy.types.Operator): # Set Up Scene + Naming shot_task_name = shot.get_shot_task_name(task_type) scene = set_shot_scene(context, shot_task_name) + remove_all_data() set_resolution_and_fps(active_project, scene) set_frame_range(shot, scene) -- 2.30.2 From bba3209067feb5beff67e7ad647456265300d777 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Thu, 21 Dec 2023 13:02:57 -0500 Subject: [PATCH 42/55] Fix 3d_Start bug and more core logic to `core.py` --- .../addons/blender_kitsu/shot_builder_2/core.py | 15 ++++++++++----- .../blender_kitsu/shot_builder_2/editorial.py | 3 ++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py index ebf71db5..17ce9087 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py @@ -84,13 +84,18 @@ def set_resolution_and_fps(project: Project, scene: bpy.types.Scene): 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 101 # TODO Set in Constants + + def set_frame_range(shot: Shot, scene: bpy.types.Scene): - start_3d = ( - int(shot.data.get("3d_start")) - if shot.data.get("3d_start") - else 101 # TODO Set in Constants - ) + start_3d = get_3d_start(shot) scene.frame_start = start_3d + if not shot.nb_frames: + raise Exception(f"{shot.name} has missing frame duration information") scene.frame_end = start_3d + shot.nb_frames - 1 diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/editorial.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/editorial.py index 8c9f7548..5c445df7 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/editorial.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/editorial.py @@ -2,6 +2,7 @@ import bpy from .. import prefs from pathlib import Path import re +from .core import get_3d_start def editorial_export_get_latest( @@ -40,7 +41,7 @@ def editorial_export_get_latest( # Update shift frame range prop. frame_in = shot.data.get("frame_in") - frame_3d_start = shot.data.get("3d_start") + frame_3d_start = get_3d_start(shot) frame_3d_offset = frame_3d_start - addon_prefs.shot_builder_frame_offset edit_export_offset = addon_prefs.edit_export_frame_offset -- 2.30.2 From 0aebca2d361f8f4f7c5571c643e233e2fbed6589 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Thu, 21 Dec 2023 13:13:34 -0500 Subject: [PATCH 43/55] Add missing templates --- .../addons/blender_kitsu/shot_builder_2/templates/comp.blend | 3 +++ .../addons/blender_kitsu/shot_builder_2/templates/fx.blend | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 scripts-blender/addons/blender_kitsu/shot_builder_2/templates/comp.blend create mode 100644 scripts-blender/addons/blender_kitsu/shot_builder_2/templates/fx.blend diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/templates/comp.blend b/scripts-blender/addons/blender_kitsu/shot_builder_2/templates/comp.blend new file mode 100644 index 00000000..56908010 --- /dev/null +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/templates/comp.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6516c83b7fdb0b9107ce03a3249887cd321f3559ddf1bad8a0dbe020a678b833 +size 660896 diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/templates/fx.blend b/scripts-blender/addons/blender_kitsu/shot_builder_2/templates/fx.blend new file mode 100644 index 00000000..51bbc8a2 --- /dev/null +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/templates/fx.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9191ac4d29d8e8276f08a48e2702cffa66eb02f2ddf27bc47857db6998d87056 +size 667192 -- 2.30.2 From 04f368b45f9c6036ae1b2852b640eca5b309491a Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Thu, 21 Dec 2023 13:13:59 -0500 Subject: [PATCH 44/55] Add operator to save hook files for user --- .../blender_kitsu/shot_builder_2/ops.py | 64 ++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py index 3b4fcad0..fe976254 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py @@ -11,6 +11,7 @@ from .core import ( set_resolution_and_fps, set_frame_range, link_task_type_output_collections, + remove_all_data, ) from .editorial import editorial_export_get_latest @@ -45,6 +46,67 @@ def get_tasks_for_shot( return [('NONE', "No Tasks Found", '')] +class KITSU_OT_save_shot_builder_hooks(bpy.types.Operator): + bl_idname = "kitsu.save_shot_builder_hooks" + bl_label = "Save Shot Builder Hook File" + bl_description = "Save hook.py file to `your_project/svn/pro/assets/scripts/shot-builder` directory. Hooks are used to customize shot builder behaviour." + + def execute(self, context: bpy.types.Context): + addon_prefs = prefs.addon_prefs_get(context) + project = cache.project_active_get() + if addon_prefs.session.is_auth() is False: + self.report( + {'ERROR'}, + "Must be logged into Kitsu to continue. \nCheck login status in 'Blender Kitsu' addon preferences.", + ) + return {'CANCELLED'} + + if project.id == "": + self.report( + {'ERROR'}, + "Operator is not able to determine the Kitsu production's name. \nCheck project is selected in 'Blender Kitsu' addon preferences.", + ) + return {'CANCELLED'} + + if not addon_prefs.is_project_root_valid: + self.report( + {'ERROR'}, + "Operator is not able to determine the project root directory. \nCheck project root directiory is configured in 'Blender Kitsu' addon preferences.", + ) + return {'CANCELLED'} + + root_dir = prefs.project_root_dir_get(context) + config_dir = root_dir.joinpath("pro/assets/scripts/shot-builder") + if not config_dir.exists(): + config_dir.mkdir(parents=True) + hook_file_path = config_dir.joinpath("hooks.py") + if hook_file_path.exists(): + self.report( + {'WARNING'}, + "File already exists, cannot overwrite", + ) + return {'CANCELLED'} + + config_dir = Path(__file__).parent + example_hooks_path = config_dir.joinpath("hook_examples/hooks.py") + if not example_hooks_path.exists(): + self.report( + {'ERROR'}, + "Cannot find example hook file", + ) + return {'CANCELLED'} + + with example_hooks_path.open() as source: + # Read contents + contents = source.read() + + # Write contents to target file + with hook_file_path.open('w') as target: + target.write(contents) + self.report({'INFO'}, f"Hook File saved to {hook_file_path}") + return {'FINISHED'} + + class KITSU_OT_build_new_shot(bpy.types.Operator): bl_idname = "kitsu.build_new_shot" bl_label = "Build New Shot" @@ -200,7 +262,7 @@ class KITSU_OT_build_new_shot(bpy.types.Operator): return {"FINISHED"} -classes = (KITSU_OT_build_new_shot,) +classes = (KITSU_OT_build_new_shot, KITSU_OT_save_shot_builder_hooks) def register(): -- 2.30.2 From 01492ccda8c66179124ea7c9dca7c8fb598c9884 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Thu, 21 Dec 2023 13:37:05 -0500 Subject: [PATCH 45/55] Replace Old Shot Builder Module --- .../addons/blender_kitsu/__init__.py | 3 - scripts-blender/addons/blender_kitsu/prefs.py | 10 +- .../blender_kitsu/shot_builder/__init__.py | 59 +-- .../shot_builder/anim_setup/core.py | 34 -- .../shot_builder/anim_setup/ops.py | 35 -- .../blender_kitsu/shot_builder/asset.py | 50 -- .../assets.py | 0 .../shot_builder/builder/__init__.py | 100 ---- .../shot_builder/builder/build_step.py | 38 -- .../shot_builder/builder/init_asset.py | 25 - .../shot_builder/builder/init_shot.py | 19 - .../shot_builder/builder/invoke_hook.py | 21 - .../shot_builder/builder/new_scene.py | 30 -- .../shot_builder/builder/save_file.py | 30 -- .../builder/set_render_settings.py | 27 - .../shot_builder/connectors/__init__.py | 19 - .../shot_builder/connectors/connector.py | 108 ---- .../shot_builder/connectors/default.py | 49 -- .../shot_builder/connectors/kitsu.py | 248 ---------- .../{shot_builder_2 => shot_builder}/core.py | 0 .../blender_kitsu/shot_builder/docs/README.md | 246 ---------- .../shot_builder/docs/examples/README.md | 4 - .../shot_builder/docs/examples/assets.py | 30 -- .../shot_builder/docs/examples/config.py | 13 - .../shot_builder/docs/examples/hooks.py | 170 ------- .../docs/examples/shot-builder/README.md | 6 - .../shot_builder/docs/examples/shots.py | 41 -- .../editorial.py | 0 .../shot_builder/editorial/__init__.py | 30 -- .../shot_builder/editorial/core.py | 82 ---- .../shot_builder/editorial/ops.py | 42 -- .../file_save.py | 0 .../hook_examples/hooks.py | 0 .../blender_kitsu/shot_builder/hooks.py | 115 ++++- .../blender_kitsu/shot_builder/operators.py | 298 ------------ .../{shot_builder_2 => shot_builder}/ops.py | 0 .../blender_kitsu/shot_builder/project.py | 460 ------------------ .../shot_builder/render_settings.py | 29 -- .../addons/blender_kitsu/shot_builder/shot.py | 64 --- .../blender_kitsu/shot_builder/sys_utils.py | 64 --- .../blender_kitsu/shot_builder/task_type.py | 27 - .../template.py | 0 .../templates/anim.blend | 0 .../templates/comp.blend | 0 .../templates/fx.blend | 0 .../templates/lighting.blend | 0 .../addons/blender_kitsu/shot_builder/ui.py | 48 +- .../addons/blender_kitsu/shot_builder/vars.py | 1 - .../blender_kitsu/shot_builder_2/__init__.py | 11 - .../blender_kitsu/shot_builder_2/hooks.py | 195 -------- .../addons/blender_kitsu/shot_builder_2/ui.py | 24 - 51 files changed, 124 insertions(+), 2781 deletions(-) delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder/anim_setup/core.py delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder/anim_setup/ops.py delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder/asset.py rename scripts-blender/addons/blender_kitsu/{shot_builder_2 => shot_builder}/assets.py (100%) delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder/builder/__init__.py delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder/builder/build_step.py delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder/builder/init_asset.py delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder/builder/init_shot.py delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder/builder/invoke_hook.py delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder/builder/new_scene.py delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder/builder/save_file.py delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder/builder/set_render_settings.py delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder/connectors/__init__.py delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder/connectors/connector.py delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder/connectors/default.py delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder/connectors/kitsu.py rename scripts-blender/addons/blender_kitsu/{shot_builder_2 => shot_builder}/core.py (100%) delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder/docs/README.md delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder/docs/examples/README.md delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder/docs/examples/assets.py delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder/docs/examples/config.py delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder/docs/examples/hooks.py delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder/docs/examples/shot-builder/README.md delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder/docs/examples/shots.py rename scripts-blender/addons/blender_kitsu/{shot_builder_2 => shot_builder}/editorial.py (100%) delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder/editorial/__init__.py delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder/editorial/core.py delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder/editorial/ops.py rename scripts-blender/addons/blender_kitsu/{shot_builder_2 => shot_builder}/file_save.py (100%) rename scripts-blender/addons/blender_kitsu/{shot_builder_2 => shot_builder}/hook_examples/hooks.py (100%) delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder/operators.py rename scripts-blender/addons/blender_kitsu/{shot_builder_2 => shot_builder}/ops.py (100%) delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder/project.py delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder/render_settings.py delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder/shot.py delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder/sys_utils.py delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder/task_type.py rename scripts-blender/addons/blender_kitsu/{shot_builder_2 => shot_builder}/template.py (100%) rename scripts-blender/addons/blender_kitsu/{shot_builder_2 => shot_builder}/templates/anim.blend (100%) rename scripts-blender/addons/blender_kitsu/{shot_builder_2 => shot_builder}/templates/comp.blend (100%) rename scripts-blender/addons/blender_kitsu/{shot_builder_2 => shot_builder}/templates/fx.blend (100%) rename scripts-blender/addons/blender_kitsu/{shot_builder_2 => shot_builder}/templates/lighting.blend (100%) delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder/vars.py delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder_2/__init__.py delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder_2/hooks.py delete mode 100644 scripts-blender/addons/blender_kitsu/shot_builder_2/ui.py diff --git a/scripts-blender/addons/blender_kitsu/__init__.py b/scripts-blender/addons/blender_kitsu/__init__.py index 3b69c443..7f01af7d 100644 --- a/scripts-blender/addons/blender_kitsu/__init__.py +++ b/scripts-blender/addons/blender_kitsu/__init__.py @@ -24,7 +24,6 @@ dependencies.preload_modules() from . import ( shot_builder, - shot_builder_2, lookdev, bkglobals, types, @@ -99,7 +98,6 @@ def register(): playblast.register() anim.register() shot_builder.register() - shot_builder_2.register() LoggerLevelManager.configure_levels() logger.info("Registered blender-kitsu") @@ -118,7 +116,6 @@ def unregister(): lookdev.unregister() playblast.unregister() shot_builder.unregister() - shot_builder_2.unregister() LoggerLevelManager.restore_levels() diff --git a/scripts-blender/addons/blender_kitsu/prefs.py b/scripts-blender/addons/blender_kitsu/prefs.py index 3e91ccb6..46f2df2a 100644 --- a/scripts-blender/addons/blender_kitsu/prefs.py +++ b/scripts-blender/addons/blender_kitsu/prefs.py @@ -40,7 +40,7 @@ from .auth.ops import ( ) from .context.ops import KITSU_OT_con_productions_load from .lookdev.prefs import LOOKDEV_preferences -from .shot_builder.editorial.core import editorial_export_check_latest +from .shot_builder.editorial import editorial_export_check_latest logger = LoggerFactory.getLogger() @@ -339,12 +339,6 @@ class KITSU_addon_preferences(bpy.types.AddonPreferences): default="ANI-", ) - user_exec_code: bpy.props.StringProperty( # type: ignore - name="Post Execution Command", - description="Run this command after shot_builder is complete, but before the file is saved.", - default="", - ) - session: Session = Session() tasks: bpy.props.CollectionProperty(type=KITSU_task) @@ -439,7 +433,7 @@ class KITSU_addon_preferences(bpy.types.AddonPreferences): start_frame_row.prop(self, "shot_builder_frame_offset", text="") box.row().prop(self, "shot_builder_armature_prefix") box.row().prop(self, "shot_builder_action_prefix") - box.row().prop(self, "user_exec_code") + box.operator("kitsu.save_shot_builder_hooks", icon='FILE_SCRIPT') # Misc settings. box = layout.box() diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/__init__.py b/scripts-blender/addons/blender_kitsu/shot_builder/__init__.py index 9f91351e..987cb692 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder/__init__.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder/__init__.py @@ -1,62 +1,15 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -# - -from .ui import * -from .connectors.kitsu import * -from .operators import * import bpy -from .anim_setup import ops as anim_setup_ops # TODO Fix Registraion -from .editorial import ops as editorial_ops # TODO Fix Registraion - -# import logging -# logging.basicConfig(level=logging.DEBUG) - - -# bl_info = { -# 'name': 'Shot Builder', -# "author": "Jeroen Bakker", -# 'version': (0, 1), -# 'blender': (2, 90, 0), -# 'location': 'Addon Preferences panel and file new menu', -# 'description': 'Shot builder production tool.', -# 'category': 'Studio', -# } - - -classes = ( - KitsuPreferences, - SHOTBUILDER_OT_NewShotFile, -) +from . import ops, ui +from .ui import topbar_file_new_draw_handler def register(): - anim_setup_ops.register() - editorial_ops.register() - for cls in classes: - bpy.utils.register_class(cls) bpy.types.TOPBAR_MT_file_new.append(topbar_file_new_draw_handler) + ops.register() + ui.register() def unregister(): - anim_setup_ops.unregister() - editorial_ops.unregister() bpy.types.TOPBAR_MT_file_new.remove(topbar_file_new_draw_handler) - for cls in classes: - bpy.utils.unregister_class(cls) + ops.unregister() + ui.unregister() diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/anim_setup/core.py b/scripts-blender/addons/blender_kitsu/shot_builder/anim_setup/core.py deleted file mode 100644 index ccb4668b..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder/anim_setup/core.py +++ /dev/null @@ -1,34 +0,0 @@ -import bpy -import re -from pathlib import Path -from typing import Set -from ... import prefs -from ... import cache - - -def animation_workspace_vse_area_add(context: bpy.types.Context): - """Split smallest 3D View in current workspace""" - for workspace in [ - workspace for workspace in bpy.data.workspaces if workspace.name == "Animation" - ]: - context.window.workspace = workspace - context.view_layer.update() - areas = workspace.screens[0].areas - view_3d_areas = sorted( - [area for area in areas if area.ui_type == "VIEW_3D"], - key=lambda x: x.width, - reverse=False, - ) - small_view_3d = view_3d_areas[0] - with context.temp_override(window=context.window, area=small_view_3d): - bpy.ops.screen.area_split(direction='HORIZONTAL', factor=0.5) - small_view_3d.ui_type = "SEQUENCE_EDITOR" - small_view_3d.spaces[0].view_type = "PREVIEW" - - -def animation_workspace_delete_others(): - """Delete any workspace that is not an animation workspace""" - for ws in bpy.data.workspaces: - if ws.name != "Animation": - with bpy.context.temp_override(workspace=ws): - bpy.ops.workspace.delete() diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/anim_setup/ops.py b/scripts-blender/addons/blender_kitsu/shot_builder/anim_setup/ops.py deleted file mode 100644 index dbc6640c..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder/anim_setup/ops.py +++ /dev/null @@ -1,35 +0,0 @@ -import bpy -from typing import Set -from .core import animation_workspace_delete_others, animation_workspace_vse_area_add -class ANIM_SETUP_OT_setup_workspaces(bpy.types.Operator): - bl_idname = "anim_setup.setup_workspaces" - bl_label = "Setup Workspace" - bl_description = "Sets up the workspaces for the animation task" - - def execute(self, context: bpy.types.Context) -> Set[str]: - animation_workspace_delete_others(self, context) - self.report({"INFO"}, "Deleted non Animation workspaces") - return {"FINISHED"} - -class ANIM_SETUP_OT_animation_workspace_vse_area_add(bpy.types.Operator): - bl_idname = "anim_setup.animation_workspace_vse_area_add" - bl_label = "Split Viewport" - bl_description = "Split smallest 3D View in current workspace" - - def execute(self, context: bpy.types.Context) -> Set[str]: - animation_workspace_vse_area_add(self, context) - return {"FINISHED"} - -classes = [ - ANIM_SETUP_OT_setup_workspaces, - ANIM_SETUP_OT_animation_workspace_vse_area_add -] - - -def register(): - for cls in classes: - bpy.utils.register_class(cls) - -def unregister(): - for cls in classes: - bpy.utils.unregister_class(cls) \ No newline at end of file diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/asset.py b/scripts-blender/addons/blender_kitsu/shot_builder/asset.py deleted file mode 100644 index f1734e25..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder/asset.py +++ /dev/null @@ -1,50 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -# - - -class Asset: - """ - Container to hold data where the asset can be located in the production repository. - - path: absolute path to the blend file containing this asset. - - """ - - asset_type = "" - code = "" - name = "" - path = "{production.path}/assets/{asset.asset_type}/{asset.code}/{asset.code}.blend" - collection = "{asset.code}" - - def __str__(self) -> str: - return self.name - - -class AssetRef: - """ - Reference to an asset from an external system. - """ - - def __init__(self, name: str = "", code: str = ""): - self.name = name - self.code = code - - def __str__(self) -> str: - return self.name diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/assets.py b/scripts-blender/addons/blender_kitsu/shot_builder/assets.py similarity index 100% rename from scripts-blender/addons/blender_kitsu/shot_builder_2/assets.py rename to scripts-blender/addons/blender_kitsu/shot_builder/assets.py diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/builder/__init__.py b/scripts-blender/addons/blender_kitsu/shot_builder/builder/__init__.py deleted file mode 100644 index 0cd620b2..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder/builder/__init__.py +++ /dev/null @@ -1,100 +0,0 @@ -from ..project import Production -from ..task_type import TaskType -from ..asset import Asset, AssetRef -from .build_step import BuildStep, BuildContext -from .init_asset import InitAssetStep -from .init_shot import InitShotStep -from .set_render_settings import SetRenderSettingsStep -from .new_scene import NewSceneStep -from .invoke_hook import InvokeHookStep - -import bpy - -import typing -import logging - -logger = logging.getLogger(__name__) - - -class ShotBuilder: - def __init__( - self, - context: bpy.types.Context, - production: Production, - task_type: TaskType, - shot_name: str, - ): - self._steps: typing.List[BuildStep] = [] - - shot = production.get_shot(context, shot_name) - assert shot - render_settings = production.get_render_settings(context, shot) - self.build_context = BuildContext( - context=context, - production=production, - shot=shot, - render_settings=render_settings, - task_type=task_type, - ) - - def __find_asset(self, asset_ref: AssetRef) -> typing.Optional[Asset]: - for asset_class in self.build_context.production.assets: - asset = typing.cast(Asset, asset_class()) - logger.debug(f"{asset_ref.name}, {asset.name}") - if asset_ref.name == asset.name: - return asset - return None - - def create_build_steps(self) -> None: - self._steps.append(InitShotStep()) - self._steps.append(NewSceneStep()) - self._steps.append(SetRenderSettingsStep()) - - production = self.build_context.production - task_type = self.build_context.task_type - - # Add global hooks. - for hook in production.hooks.filter(): - self._steps.append(InvokeHookStep(hook)) - - # Add task specific hooks. - for hook in production.hooks.filter(match_task_type=task_type.name): - self._steps.append(InvokeHookStep(hook)) - - context = self.build_context.context - shot = self.build_context.shot - - # Collect assets that should be loaded. - asset_refs = production.get_assets_for_shot(context, shot) - assets = [] - for asset_ref in asset_refs: - asset = self.__find_asset(asset_ref) - if asset is None: - logger.warning(f"cannot determine repository data for {asset_ref}") - continue - assets.append(asset) - - # Sort the assets on asset_type and asset.code). - assets.sort(key=lambda asset: (asset.asset_type, asset.code)) - - # Build asset specific build steps. - for asset in assets: - self._steps.append(InitAssetStep(asset)) - # Add asset specific hooks. - for hook in production.hooks.filter( - match_task_type=task_type.name, match_asset_type=asset.asset_type - ): - self._steps.append(InvokeHookStep(hook)) - - def build(self) -> None: - num_steps = len(self._steps) - step_number = 1 - build_context = self.build_context - window_manager = build_context.context.window_manager - window_manager.progress_begin(min=0, max=num_steps) - for step in self._steps: - logger.info(f"Building step [{step_number}/{num_steps}]: {step} ") - step.execute(build_context=build_context) - window_manager.progress_update(value=step_number) - step_number += 1 - window_manager.progress_end() diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/builder/build_step.py b/scripts-blender/addons/blender_kitsu/shot_builder/builder/build_step.py deleted file mode 100644 index 123377b6..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder/builder/build_step.py +++ /dev/null @@ -1,38 +0,0 @@ -import bpy -import typing - -from ..project import Production -from ..shot import Shot -from ..task_type import TaskType -from ..render_settings import RenderSettings -from ..asset import Asset - - -class BuildContext: - def __init__(self, context: bpy.types.Context, production: Production, shot: Shot, render_settings: RenderSettings, task_type: TaskType): - self.context = context - self.production = production - self.shot = shot - self.task_type = task_type - self.render_settings = render_settings - self.asset: typing.Optional[Asset] = None - self.scene: typing.Optional[bpy.types.Scene] = None - - def as_dict(self) -> typing.Dict[str, typing.Any]: - return { - 'context': self.context, - 'scene': self.scene, - 'production': self.production, - 'shot': self.shot, - 'task_type': self.task_type, - 'render_settings': self.render_settings, - 'asset': self.asset, - } - - -class BuildStep: - def __str__(self) -> str: - return "unnamed build step" - - def execute(self, build_context: BuildContext) -> None: - raise NotImplementedError() diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/builder/init_asset.py b/scripts-blender/addons/blender_kitsu/shot_builder/builder/init_asset.py deleted file mode 100644 index e0f36731..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder/builder/init_asset.py +++ /dev/null @@ -1,25 +0,0 @@ -from ..builder.build_step import BuildStep, BuildContext -from ..asset import * -from ..project import * -from ..shot import * - -import bpy - -import logging - -logger = logging.getLogger(__name__) - - -class InitAssetStep(BuildStep): - def __init__(self, asset: Asset): - self.__asset = asset - - def __str__(self) -> str: - return f"init asset \"{self.__asset.name}\"" - - def execute(self, build_context: BuildContext) -> None: - build_context.asset = self.__asset - self.__asset.path = self.__asset.path.format_map(build_context.as_dict()) - self.__asset.collection = self.__asset.collection.format_map( - build_context.as_dict() - ) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/builder/init_shot.py b/scripts-blender/addons/blender_kitsu/shot_builder/builder/init_shot.py deleted file mode 100644 index eaee45fa..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder/builder/init_shot.py +++ /dev/null @@ -1,19 +0,0 @@ -from ..builder.build_step import BuildStep, BuildContext -from ..asset import * -from ..project import * -from ..shot import * - -import bpy - -import logging - -logger = logging.getLogger(__name__) - - -class InitShotStep(BuildStep): - def __str__(self) -> str: - return "init shot" - - def execute(self, build_context: BuildContext) -> None: - shot = build_context.shot - shot.file_path = shot.file_path_format.format_map(build_context.as_dict()) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/builder/invoke_hook.py b/scripts-blender/addons/blender_kitsu/shot_builder/builder/invoke_hook.py deleted file mode 100644 index 0ec908e5..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder/builder/invoke_hook.py +++ /dev/null @@ -1,21 +0,0 @@ -from ..builder.build_step import BuildStep, BuildContext -from ..hooks import HookFunction -import bpy - -import typing -import types -import logging - -logger = logging.getLogger(__name__) - - -class InvokeHookStep(BuildStep): - def __init__(self, hook: HookFunction): - self._hook = hook - - def __str__(self) -> str: - return f"invoke hook [{self._hook.__name__}]" - - def execute(self, build_context: BuildContext) -> None: - params = build_context.as_dict() - self._hook(**params) # type: ignore diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/builder/new_scene.py b/scripts-blender/addons/blender_kitsu/shot_builder/builder/new_scene.py deleted file mode 100644 index 1d4c4b94..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder/builder/new_scene.py +++ /dev/null @@ -1,30 +0,0 @@ -from ..builder.build_step import BuildStep, BuildContext -from ..render_settings import RenderSettings -import bpy - -import logging - -logger = logging.getLogger(__name__) - - -class NewSceneStep(BuildStep): - def __str__(self) -> str: - return f"new scene" - - def execute(self, build_context: BuildContext) -> None: - production = build_context.production - scene_name = production.scene_name_format.format_map( - build_context.as_dict()) - logger.debug(f"create scene with name {scene_name}") - scene = bpy.data.scenes.new(name=scene_name) - - bpy.context.window.scene = scene - build_context.scene = scene - - self.__remove_other_scenes(build_context) - - def __remove_other_scenes(self, build_context: BuildContext) -> None: - for scene in bpy.data.scenes: - if scene != build_context.scene: - logger.debug(f"remove scene {scene.name}") - bpy.data.scenes.remove(scene) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/builder/save_file.py b/scripts-blender/addons/blender_kitsu/shot_builder/builder/save_file.py deleted file mode 100644 index f4f7c8fb..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder/builder/save_file.py +++ /dev/null @@ -1,30 +0,0 @@ -from ..builder.build_step import BuildStep, BuildContext -from ..asset import * -from ..project import * -from ..shot import * -import pathlib - -import bpy - -import logging - -logger = logging.getLogger(__name__) - - -def save_shot_builder_file(file_path: str): - """Save Shot File within Folder of matching name. - Set Shot File to relative Paths.""" - dir_path = pathlib.Path(file_path) - dir_path.mkdir(parents=True, exist_ok=True) - bpy.ops.wm.save_mainfile(filepath=file_path, relative_remap=True) - - -class SaveFileStep(BuildStep): - def __str__(self) -> str: - return "save file" - - def execute(self, build_context: BuildContext) -> None: - shot = build_context.shot - file_path = pathlib.Path(shot.file_path) - save_shot_builder_file(file_path) - logger.info(f"save file {shot.file_path}") diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/builder/set_render_settings.py b/scripts-blender/addons/blender_kitsu/shot_builder/builder/set_render_settings.py deleted file mode 100644 index b9782d20..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder/builder/set_render_settings.py +++ /dev/null @@ -1,27 +0,0 @@ -from ..builder.build_step import BuildStep, BuildContext -import bpy - -import typing -import logging - -logger = logging.getLogger(__name__) - - -class SetRenderSettingsStep(BuildStep): - def __str__(self) -> str: - return f"set render settings" - - def execute(self, build_context: BuildContext) -> None: - scene = typing.cast(bpy.types.Scene, build_context.scene) - render_settings = build_context.render_settings - logger.debug( - f"set render resolution to {render_settings.width}x{render_settings.height}") - scene.render.resolution_x = render_settings.width - scene.render.resolution_y = render_settings.height - scene.render.resolution_percentage = 100 - - shot = build_context.shot - scene.frame_start = shot.frame_start - scene.frame_current = shot.frame_start - scene.frame_end = shot.frame_start + shot.frames -1 - logger.debug(f"set frame range to ({scene.frame_start}-{scene.frame_end})") diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/connectors/__init__.py b/scripts-blender/addons/blender_kitsu/shot_builder/connectors/__init__.py deleted file mode 100644 index b07da1ed..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder/connectors/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -# \ No newline at end of file diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/connectors/connector.py b/scripts-blender/addons/blender_kitsu/shot_builder/connectors/connector.py deleted file mode 100644 index 23bd8f36..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder/connectors/connector.py +++ /dev/null @@ -1,108 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -# -""" -This module contains the Connector class. It is an abstract base class for concrete connectors. -""" - -from ..shot import Shot, ShotRef -from..asset import Asset, AssetRef -from..task_type import TaskType -from..render_settings import RenderSettings -from typing import * - - -if TYPE_CHECKING: - from..project import Production - from..properties import ShotBuilderPreferences - - -class Connector: - """ - A Connector is used to retrieve data from a source. This source can be an external system. - - Connectors can be configured for productions in its `shot-builder/config.py` file. - - # Members - - _production: reference to the production that we want to read data for. - _preference: reference to the add-on preference to read settings for. - Connectors can add settings to the add-on preferences. - - # Class Members - - PRODUCTION_KEYS: Connectors can register production configuration keys that will be loaded from the production config file. - When keys are added the content will be read and stored in the production. - - # Usage - - Concrete connectors only overrides methods that they support. All non-overridden methods will raise an - NotImplementerError. - - - Example of using predefined connectors in a production config file: - ```shot-builder/config.py - from ..connectors.default import DefaultConnector - from ..connectors.kitsu import KitsuConnector - - PRODUCTION_NAME = DefaultConnector - TASK_TYPES = KitsuConnector - KITSU_PROJECT_ID = "...." - ``` - """ - PRODUCTION_KEYS: Set[str] = set() - - def __init__(self, production: 'Production', preferences: 'ShotBuilderPreferences'): - self._production = production - self._preferences = preferences - - def get_name(self) -> str: - """ - Retrieve the production name using the connector. - """ - raise NotImplementedError( - f"{self.__class__.__name__} does not support retrieval of production name") - - def get_task_types(self) -> List[TaskType]: - """ - Retrieve the task types using the connector. - """ - raise NotImplementedError( - f"{self.__class__.__name__} does not support retrieval of task types") - - def get_shots(self) -> List[ShotRef]: - """ - Retrieve the shots using the connector. - """ - raise NotImplementedError( - f"{self.__class__.__name__} does not support retrieval of shots") - - def get_assets_for_shot(self, shot: Shot) -> List[AssetRef]: - """ - Retrieve the sequences using the connector. - """ - raise NotImplementedError( - f"{self.__class__.__name__} does not support retrieval of assets for a shot") - - def get_render_settings(self, shot: Shot) -> RenderSettings: - """ - Retrieve the render settings for the given shot. - """ - raise NotImplementedError( - f"{self.__class__.__name__} does not support retrieval of render settings for a shot") diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/connectors/default.py b/scripts-blender/addons/blender_kitsu/shot_builder/connectors/default.py deleted file mode 100644 index e655094d..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder/connectors/default.py +++ /dev/null @@ -1,49 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -# -from ..shot import Shot, ShotRef -from ..asset import Asset, AssetRef -from ..task_type import TaskType -from ..render_settings import RenderSettings -from ..connectors.connector import Connector -from typing import * - - -class DefaultConnector(Connector): - """ - Default connector is a connector that returns the defaults for the shot builder add-on. - """ - - def get_name(self) -> str: - return "unnamed production" - - def get_shots(self) -> List[ShotRef]: - return [] - - def get_assets_for_shot(self, shot: Shot) -> List[AssetRef]: - return [] - - def get_task_types(self) -> List[TaskType]: - return [TaskType("anim"), TaskType("lighting"), TaskType("comp"), TaskType("fx")] - - def get_render_settings(self, shot: Shot) -> RenderSettings: - """ - Retrieve the render settings for the given shot. - """ - return RenderSettings(width=1920, height=1080, frames_per_second=24.0) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/connectors/kitsu.py b/scripts-blender/addons/blender_kitsu/shot_builder/connectors/kitsu.py deleted file mode 100644 index 8bc50536..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder/connectors/kitsu.py +++ /dev/null @@ -1,248 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -# -import bpy -from .. import vars -from ..shot import Shot, ShotRef -from ..asset import Asset, AssetRef -from ..task_type import TaskType -from ..render_settings import RenderSettings -from ..connectors.connector import Connector -import requests -from ... import cache -import gazu - -import typing -import logging - -logger = logging.getLogger(__name__) - - -class KitsuException(Exception): - pass - - -class KitsuPreferences(bpy.types.PropertyGroup): - backend: bpy.props.StringProperty( # type: ignore - name="Server URL", - description="Kitsu server address", - default="https://kitsu.blender.cloud/api", - ) - - username: bpy.props.StringProperty( # type: ignore - name="Username", - description="Username to connect to Kitsu", - ) - - password: bpy.props.StringProperty( # type: ignore - name="Password", - description="Password to connect to Kitsu", - subtype='PASSWORD', - ) - - def draw(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None: - layout.label(text="Kitsu") - layout.prop(self, "backend") - layout.prop(self, "username") - layout.prop(self, "password") - - def _validate(self): - if not (self.backend and self.username and self.password): - raise KitsuException( - "Kitsu connector has not been configured in the add-on preferences" - ) - - -class KitsuDataContainer: - def __init__(self, data: typing.Dict[str, typing.Optional[str]]): - self._data = data - - def get_parent_id(self) -> typing.Optional[str]: - return self._data['parent_id'] - - def get_id(self) -> str: - return str(self._data['id']) - - def get_name(self) -> str: - return str(self._data['name']) - - def get_code(self) -> typing.Optional[str]: - return self._data['code'] - - def get_description(self) -> str: - result = self._data['description'] - if result is None: - return "" - return result - - -class KitsuProject(KitsuDataContainer): - def get_resolution(self) -> typing.Tuple[int, int]: - """ - Get the resolution and decode it to (width, height) - """ - res_str = str(self._data['resolution']) - splitted = res_str.split("x") - return (int(splitted[0]), int(splitted[1])) - - -class KitsuSequenceRef(ShotRef): - def __init__(self, kitsu_id: str, name: str, code: str): - super().__init__(name=name, code=code) - self.kitsu_id = kitsu_id - - def sync_data(self, shot: Shot) -> None: - shot.sequence_code = self.name - - -class KitsuShotRef(ShotRef): - def __init__( - self, - kitsu_id: str, - name: str, - code: str, - frame_start: int, - frames: int, - frame_end: int, - frames_per_second: float, - sequence: KitsuSequenceRef, - ): - super().__init__(name=name, code=code) - self.kitsu_id = kitsu_id - self.frame_start = frame_start - self.frames = frames - self.frame_end = frame_end - self.frames_per_second = frames_per_second - self.sequence = sequence - - def sync_data(self, shot: Shot) -> None: - shot.name = self.name - shot.code = self.code - shot.kitsu_id = self.kitsu_id - shot.frame_start = self.frame_start - shot.frames = self.frames - shot.frame_end = self.frame_end - shot.frames_per_second = self.frames_per_second - self.sequence.sync_data(shot) - - -class KitsuConnector(Connector): - # PRODUCTION_KEYS = {'KITSU_PROJECT_ID'} - - def __init__(self, **kwargs): - super().__init__(**kwargs) - - def __get_production_data(self) -> KitsuProject: - production = cache.project_active_get() - project = KitsuProject(typing.cast(typing.Dict[str, typing.Any], production)) - return project - - def get_name(self) -> str: - production = self.__get_production_data() - return production.get_name() - - def get_task_types(self) -> typing.List[TaskType]: - project = cache.project_active_get() - task_types = project.task_types - import pprint - - pprint.pprint(task_types) - return [] - - def get_shots(self) -> typing.List[ShotRef]: - project = cache.project_active_get() - kitsu_sequences = gazu.shot.all_sequences_for_project(project.id) - - sequence_lookup = { - sequence_data['id']: KitsuSequenceRef( - kitsu_id=sequence_data['id'], - name=sequence_data['name'], - code=sequence_data['code'], - ) - for sequence_data in kitsu_sequences - } - - kitsu_shots = gazu.shot.all_shots_for_project(project.id) - shots: typing.List[ShotRef] = [] - - for shot_data in kitsu_shots: - # Initialize default values - frame_start = vars.DEFAULT_FRAME_START - frame_end = 0 - - # shot_data['data'] can be None - if shot_data['data']: - # If 3d_start key not found use default start frame. - frame_start = int( - shot_data['data'].get('3d_start', vars.DEFAULT_FRAME_START) - ) - frame_end = ( - int(shot_data['data'].get('3d_start', vars.DEFAULT_FRAME_START)) - + shot_data['nb_frames'] - - 1 - ) - - # If 3d_start and 3d_out available use that to calculate frames. - # If not try shot_data['nb_frames'] or 0 -> invalid. - frames = int( - (frame_end - frame_start + 1) - if frame_end - else shot_data['nb_frames'] or 0 - ) - if frames < 0: - logger.error( - "%s duration is negative: %i. Check frame range information on Kitsu", - shot_data['name'], - frames, - ) - frames = 0 - - shots.append( - KitsuShotRef( - kitsu_id=shot_data['id'], - name=shot_data['name'], - code=shot_data['code'], - frame_start=frame_start, - frames=frames, - frame_end=frame_end, - frames_per_second=24.0, - sequence=sequence_lookup[shot_data['parent_id']], - ) - ) - - return shots - - def get_assets_for_shot(self, shot: Shot) -> typing.List[AssetRef]: - kitsu_assets = gazu.asset.all_assets_for_shot(shot.kitsu_id) - - return [ - AssetRef(name=asset_data['name'], code=asset_data['code']) - for asset_data in kitsu_assets - ] - - def get_render_settings(self, shot: Shot) -> RenderSettings: - """ - Retrieve the render settings for the given shot. - """ - project = cache.project_active_get() - return RenderSettings( - width=int(project.resolution.split('x')[0]), - height=int(project.resolution.split('x')[1]), - frames_per_second=project.fps, - ) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/core.py b/scripts-blender/addons/blender_kitsu/shot_builder/core.py similarity index 100% rename from scripts-blender/addons/blender_kitsu/shot_builder_2/core.py rename to scripts-blender/addons/blender_kitsu/shot_builder/core.py diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/docs/README.md b/scripts-blender/addons/blender_kitsu/shot_builder/docs/README.md deleted file mode 100644 index 66b55528..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder/docs/README.md +++ /dev/null @@ -1,246 +0,0 @@ -# Project Description (DRAFT) - -Shot Builder is an Add-on that helps studios to work with task specific -Blend-files. The shot builder is part of the shot-tools repository. The main functionalities are - -* Build blend files for a specific task and shot. -* Sync data back from work files to places like kitsu, or `edit.blend`. - -## Design Principles - -The main design principles are: - -* The core-tool can be installed as an add-on, but the (production specific) - configuration should be part of the production repository. -* The configuration files are a collection of python files. The API between - the configuration files and the add-on should be easy to use as pipeline - TDs working on the production should be able to work with it. -* TDs/artists should be able to handle issues during building without looking - at how the add-on is structured. -* The tool contains connectors that can be configured to read/write data - from the system/file that is the main location of the data. For example - The start and end time of a shot could be stored in an external production tracking application. - -## Connectors - -Connectors are components that can be used to read or write to files or -systems. The connectors will add flexibility to the add-on so it could be used -in multiple productions or studios. - -In the configuration files the TD can setup the connectors that are used for -the production. Possible connectors would be: - -* Connector for text based config files (json/yaml). -* Connector for kitsu (https://www.cg-wire.com/en/kitsu.html). -* Connector for blend files. - -## Layering & Hooks - -The configuration of the tool is layered. When building a work file for a sequence -there are multiple ways to change the configuration. - -* Configuration for the production. -* Configuration for the asset that is needed. -* Configuration for the asset type of the loaded asset. -* Configuration for the sequence. -* Configuration for the shot. -* Configuration for the task type. - -For any combination of these configurations hooks can be defined. - -``` -@shot_tools.hook(match_asset_name='Spring', match_shot_code='02_020A') -def hook_Spring_02_020A(asset: shot_tools.Asset, shot: shot_tools.Shot, **kwargs) -> None: - """ - Specific overrides when Spring is loaded in 02_020A. - """ - -@shot_tools.hook(match_task_type='anim') -def hook_task_anim(task: shot_tools.Task, shot: shot_tools.Shot, **kwargs) -> None: - """ - Specific overrides for any animation task. - """ -``` - -### Data - -All hooks must have Python’s `**kwargs` parameter. The `kwargs` contains -the context at the moment the hook is invoked. The context can contain the -following items. - -* `production`: `shot_tools.Production`: Include the name of the production - and the location on the filesystem. -* `task`: `shot_tools.Task`: The task (combination of task_type and shot) -* `task_type`: `shot_tools.TaskType`: Is part of the `task`. -* `sequence`: `shot_tools.Sequence`: Is part of `shot`. -* `shot`: `shot_tools.Shot` Is part of `task`. -* `asset`: `shot_tools.Asset`: Only available during asset loading phase. -* `asset_type`: `shot_tools.AssetType`: Only available during asset loading phase. - -### Execution Order - -The add-on will internally create a list containing the hooks that needs to be -executed for the command in a sensible order. It will then execute them in that -order. - -By default the next order will be used: - -* Production wide hooks -* Asset Type hooks -* Asset hooks -* Sequence hooks -* Shot hooks -* Task type hooks - -A hook with a single ‘match’ rule will be run in the corresponding phase. A hook with -multiple ‘match’ rules will be run in the last matching phase. For example, a hook with -‘asset’ and ‘task type’ match rules will be run in the ‘task type’ phase. - -#### Events - -Order of execution can be customized by adding the optional `run_before` -or `run_after` parameters. - -``` -@shot_tools.hook(match_task_type='anim', - requires={shot_tools.events.AssetsLoaded, hook_task_other_anim}, - is_required_by={shot_tools.events.ShotOverrides}) -def hook_task_anim(task: shot_tools.Task, shot: shot_tools.Shot, **kwargs) -> None: - """ - Specific overrides for any animation task run after all assets have been loaded. - """ -``` - -Events could be: - -* `shot_tools.events.BuildStart` -* `shot_tools.events.ProductionSettingsLoaded` -* `shot_tools.events.AssetsLoaded` -* `shot_tools.events.AssetTypeOverrides` -* `shot_tools.events.SequenceOverrides` -* `shot_tools.events.ShotOverrides` -* `shot_tools.events.TaskTypeOverrides` -* `shot_tools.events.BuildFinished` -* `shot_tools.events.HookStart` -* `shot_tools.events.HookEnd` - -During usage we should see which one of these or other events are needed. - -`shot_tools.events.BuildStart`, `shot_tools.events.ProductionSettingsLoaded` -and `shot_tools.events.HookStart` can only be used in the `run_after` -parameter. `shot_tools.events.BuildFinished`, `shot_tools.events.HookFinished` -can only be used in the `run_before` parameter. - - -## API - -The shot builder has an API between the add-on and the configuration files. This -API contains convenience functions and classes to hide complexity and makes -sure that the configuration files are easy to maintain. - -``` -register_task_type(task_type="anim") -register_task_type(task_type="lighting") -``` - -``` -# shot_tool/characters.py -class Asset(shot_tool.some_module.Asset): - asset_file = "/{asset_type}/{name}/{name}.blend" - collection = “{class_name}” - name = “{class_name}” - -class Character(Asset): - asset_type = ‘char’ - - -class Ellie(Character): - collection = “{class_name}-{variant_name}” - variants = {‘default’, ‘short_hair’} - -class Victoria(Character): pass -class Rex(Character): pass - -# shot_tool/shots.py -class Shot_01_020_A(shot_tool.some_module.Shot): - shot_id = ‘01_020_A’ - assets = { - characters.Ellie(variant=”short_hair”), - characters.Rex, - sets.LogOverChasm, - } - -class AllHumansShot(shot_tool.some_module.Shot): - assets = { - characters.Ellie(variant=”short_hair”), - characters.Rex, - characters.Victoria, - } - -class Shot_01_035_A(AllHumansShot): - assets = { - sets.Camp, - } - -``` - -This API is structured/implemented in a way that it keeps track of what -is being done. This will be used when an error occurs so a descriptive -error message can be generated that would help the TD to solve the issue more -quickly. The goal would be that the error messages are descriptive enough to -direct the TD into the direction where the actual cause is. And when possible -propose several solutions to fix it. - -## Setting up the tool - -The artist/TD can configure their current local project directory in the add-on preferences. -This can then be used for new blend files. The project associated with an opened (so existing) -blend file can be found automatically by iterating over parent directories until a Shot Builder -configuration file is found. Project-specific settings are not configured/stored in the add-on, -but in this configuration file. - -The add-on will look in the root of the production repository to locate the -main configuration file `/project_root_directory/pro/shot-builder/config.py`. This file contains general -settings about the production, including: - -* The name of the production for reporting back to the user when needed. -* Naming standards to test against when reporting deviations. -* Location of other configuration (`tasks.py`, `assets.py`) relative to the `shot-builder` directory of the production. -* Configuration of the needed connectors. - -### Directory Layout -``` bash -└── project-name/ # Project Root Directory - └── pro/ - ├── assets/ - ├── shot-builder/ - │ ├── assets.py - │ ├── config.py - │ ├── hooks.py - │ └── shots.py - └── shots/ -``` - -## Usage - -Any artist can open a shot file via the `File` menu. A modal panel appears -where the user can select the task type and sequence/shot. When the file -already exists, it will be opened. When the file doesn't exist, the file -will be built. - -In the future other use cases will also be accessible, such as: - -* Syncing data back from a work file to the source of the data. -* Report of errors/differences between the shot file and the configuration. - -## Open Issues - -### Security - -* Security keys needed by connectors need to be stored somewhere. The easy - place is to place inside the production repository, but that isn't secure - Anyone with access to the repository could misuse the keys to access the - connector. Other solution might be to use the OS key store or retrieve the - keys from an online service authenticated by the blender cloud add-on. - - We could use `keyring` to access OS key stores. \ No newline at end of file diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/docs/examples/README.md b/scripts-blender/addons/blender_kitsu/shot_builder/docs/examples/README.md deleted file mode 100644 index d894e6fe..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder/docs/examples/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Example configuration files - -This folder contains an example shot builder configuration. It shows the part -that a TD would do to incorporate the shot builder in a production. \ No newline at end of file diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/docs/examples/assets.py b/scripts-blender/addons/blender_kitsu/shot_builder/docs/examples/assets.py deleted file mode 100644 index de3619c3..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder/docs/examples/assets.py +++ /dev/null @@ -1,30 +0,0 @@ -from blender_kitsu.shot_builder.asset import Asset - - -class ProductionAsset(Asset): - path = "{production.path}/assets/{asset.asset_type}/{asset.code}/{asset.code}.blend" # Path to most assets - color_tag = "NONE" - - -# Categories -class Character(ProductionAsset): - asset_type = "chars" - collection = "CH-{asset.code}" # Prefix for characters - - -class Prop(ProductionAsset): - asset_type = "props" - collection = "PR-{asset.code}" # Prefix for props - - -# Assets -class MyCharacter(Character): - name = "My Character" # Name on Kitsu Server - code = "mycharacter" # Name of Collection without prefix (e.g. CH-mycharacter) - path = "{production.path}/assets/{asset.asset_type}/mycharacter/publish/mycharacter.v001.blend" # This asset has a custom path - color_tag = "COLOR_01" - - -class MyProp(Prop): - name = "MyProp" - code = "myprop" diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/docs/examples/config.py b/scripts-blender/addons/blender_kitsu/shot_builder/docs/examples/config.py deleted file mode 100644 index 890e2fc5..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder/docs/examples/config.py +++ /dev/null @@ -1,13 +0,0 @@ -from blender_kitsu.shot_builder.connectors.kitsu import KitsuConnector - -PRODUCTION_NAME = KitsuConnector -SHOTS = KitsuConnector -ASSETS = KitsuConnector -RENDER_SETTINGS = KitsuConnector - -# Formatting rules -# ---------------- - -# The name of the scene in blender where the shot is build in. -SCENE_NAME_FORMAT = "{shot.name}.{task_type}" -SHOT_NAME_FORMAT = "{shot.name}" diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/docs/examples/hooks.py b/scripts-blender/addons/blender_kitsu/shot_builder/docs/examples/hooks.py deleted file mode 100644 index 409e9915..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder/docs/examples/hooks.py +++ /dev/null @@ -1,170 +0,0 @@ -import bpy -from blender_kitsu.shot_builder.hooks import hook, Wildcard -from blender_kitsu.shot_builder.asset import Asset -from blender_kitsu.shot_builder.shot import Shot -from blender_kitsu.shot_builder.project import Production -from pathlib import Path -import logging - -logger = logging.getLogger(__name__) - -# ---------- Global Hook ---------- - - -CAMERA_NAME = 'CAM-camera' - - -@hook() -def set_cycles_render_engine(scene: bpy.types.Scene, **kwargs): - """ - By default we set Cycles as the renderer. - """ - scene.render.engine = 'CYCLES' - - -# ---------- Overrides for animation files ---------- - - -@hook(match_task_type='anim') -def task_type_anim_set_workbench(scene: bpy.types.Scene, **kwargs): - """ - Override of the render engine to Workbench when building animation files. - """ - scene.render.engine = 'BLENDER_WORKBENCH' - - -# ---------- Create output collection for animation files ---------- - - -def _add_camera_rig( - scene: bpy.types.Scene, - production: Production, - shot: Shot, -): - """ - Function to load the camera rig. The rig will be added to the output collection - of the shot and the camera will be set as active camera. - """ - # Load camera rig. - path = f"{production.path}/assets/cam/camera_rig.blend" - - if not Path(path).exists(): - camera_data = bpy.data.cameras.new(name=CAMERA_NAME) - camera_object = bpy.data.objects.new(name=CAMERA_NAME, object_data=camera_data) - shot.output_collection.objects.link(camera_object) - return - - collection_name = "CA-camera_rig" - bpy.ops.wm.link( - filepath=path, - directory=path + "/Collection", - filename=collection_name, - ) - # Keep the active object name as this would also be the name of the collection after enabling library override. - active_object_name = bpy.context.active_object.name - - # Make library override. - bpy.ops.object.make_override_library() - - # Add camera collection to the output collection - asset_collection = bpy.data.collections[active_object_name] - shot.output_collection.children.link(asset_collection) - - # Set the camera of the camera rig as active scene camera. - camera = bpy.data.objects[CAMERA_NAME] - scene.camera = camera - - -@hook(match_task_type='anim') -def task_type_anim_output_collection( - scene: bpy.types.Scene, production: Production, shot: Shot, task_type: str, **kwargs -): - """ - Animations are stored in an output collection. This collection will be linked - by the lighting file. - - Also loads the camera rig. - """ - output_collection = bpy.data.collections.new( - name=shot.get_output_collection_name(shot=shot, task_type=task_type) - ) - shot.output_collection = output_collection - output_collection.use_fake_user = True - scene.collection.children.link(output_collection) - - _add_camera_rig(scene, production, shot) - - -@hook(match_task_type='lighting') -def link_anim_output_collection( - scene: bpy.types.Scene, production: Production, shot: Shot, **kwargs -): - """ - Link in the animation output collection from the animation file. - """ - anim_collection = bpy.data.collections.new(name="animation") - scene.collection.children.link(anim_collection) - anim_file_path = shot.get_anim_file_path(production, shot) - anim_output_collection_name = shot.get_output_collection_name( - shot=shot, task_type="anim" - ) - result = bpy.ops.wm.link( - filepath=anim_file_path, - directory=anim_file_path + "/Collection", - filename=anim_output_collection_name, - ) - assert result == {'FINISHED'} - - # Move the anim output collection from scene collection to the animation collection. - anim_output_collection = bpy.data.objects[anim_output_collection_name] - anim_collection.objects.link(anim_output_collection) - scene.collection.objects.unlink(anim_output_collection) - - # Use animation camera as active scene camera. - camera = bpy.data.objects['CAM-camera'] - scene.camera = camera - - -# ---------- Asset loading and linking ---------- - - -@hook(match_task_type='anim', match_asset_type=['chars', 'props']) -def link_char_prop_for_anim(scene: bpy.types.Scene, shot: Shot, asset: Asset, **kwargs): - """ - Loading a character or prop for an animation file. - """ - collection_names = [] - if asset.code == 'notepad_pencil': - collection_names.append("PR-pencil") - collection_names.append("PR-notepad") - else: - collection_names.append(asset.collection) - - for collection_name in collection_names: - logger.info("link asset") - bpy.ops.wm.link( - filepath=str(asset.path), - directory=str(asset.path) + "/Collection", - filename=collection_name, - ) - # Keep the active object name as this would also be the name of the collection after enabling library override. - active_object_name = bpy.context.active_object.name - - # Make library override. - bpy.ops.object.make_override_library() - - # Add overridden collection to the output collection. - asset_collection = bpy.data.collections[active_object_name] - shot.output_collection.children.link(asset_collection) - - -@hook(match_task_type=Wildcard, match_asset_type='sets') -def link_set(asset: Asset, **kwargs): - """ - Load the set of the shot. - """ - bpy.ops.wm.link( - filepath=str(asset.path), - directory=str(asset.path) + "/Collection", - filename=asset.collection, - ) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/docs/examples/shot-builder/README.md b/scripts-blender/addons/blender_kitsu/shot_builder/docs/examples/shot-builder/README.md deleted file mode 100644 index 8d737cdb..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder/docs/examples/shot-builder/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Example configuration files - -This folder contains an example shot builder configuration. It shows the part -that a TD would do to incorporate the shot builder in a production. - - diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/docs/examples/shots.py b/scripts-blender/addons/blender_kitsu/shot_builder/docs/examples/shots.py deleted file mode 100644 index 7aace594..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder/docs/examples/shots.py +++ /dev/null @@ -1,41 +0,0 @@ -from blender_kitsu.shot_builder.shot import Shot -from blender_kitsu.shot_builder.project import Production - - -class ProductionShot(Shot): - def get_anim_file_path(self, production: Production, shot: Shot) -> str: - """Get the animation file path for this given shot.""" - return self.file_path_format.format_map( - {'production': production, 'shot': shot, 'task_type': "anim"} - ) - - def get_lighting_file_path(self, production: Production, shot: Shot) -> str: - """Get the lighting file path for this given shot.""" - return self.file_path_format.format_map( - {'production': production, 'shot': shot, 'task_type': "lighting"} - ) - - def get_output_collection_name(self, shot: Shot, task_type: str) -> str: - """Get the collection name where the output is stored.""" - return f"{shot.name}.{task_type}.output" - - def is_valid(self) -> bool: - """Check if this shot contains all data, so it could be selected - for shot building. - """ - if not super().is_valid(): - return False - return True - - # Assuming path to file is in `project_name/svn/pro/shot/sequence_name/shot_name` - # Render Ouput path should be `project_name/shared/shot_frames/sequence_name/shot_name/` - - def get_render_output_dir(self) -> str: - return f"//../../../../../shared/shot_frames/{self.sequence_code}/{self.name}/{self.name}.lighting" - - def get_comp_output_dir(self) -> str: - return f"//../../../../../shared/shot_frames/{self.sequence_code}/{self.name}/{self.name}.comp" - - -class GenericShot(ProductionShot): - is_generic = True diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/editorial.py b/scripts-blender/addons/blender_kitsu/shot_builder/editorial.py similarity index 100% rename from scripts-blender/addons/blender_kitsu/shot_builder_2/editorial.py rename to scripts-blender/addons/blender_kitsu/shot_builder/editorial.py diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/editorial/__init__.py b/scripts-blender/addons/blender_kitsu/shot_builder/editorial/__init__.py deleted file mode 100644 index 9e92c71a..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder/editorial/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -# - -import bpy -from . import ops - - -def register(): - ops.register() - - -def unregister(): - ops.unregister() diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/editorial/core.py b/scripts-blender/addons/blender_kitsu/shot_builder/editorial/core.py deleted file mode 100644 index 08109d83..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder/editorial/core.py +++ /dev/null @@ -1,82 +0,0 @@ -import bpy -import re -from pathlib import Path -from typing import Set -from ... import prefs -from ... import cache - - -def editorial_export_get_latest( - context: bpy.types.Context, shot -) -> list[bpy.types.Sequence]: # TODO add info to shot - """Loads latest export from editorial department""" - addon_prefs = prefs.addon_prefs_get(context) - strip_channel = 1 - latest_file = editorial_export_check_latest(context) - if not latest_file: - return None - # Check if Kitsu server returned empty shot - if shot.get("id") == '': - return None - strip_filepath = latest_file.as_posix() - strip_frame_start = addon_prefs.shot_builder_frame_offset - - scene = context.scene - if not scene.sequence_editor: - scene.sequence_editor_create() - seq_editor = scene.sequence_editor - movie_strip = seq_editor.sequences.new_movie( - latest_file.name, - strip_filepath, - strip_channel + 1, - strip_frame_start, - fit_method="FIT", - ) - sound_strip = seq_editor.sequences.new_sound( - latest_file.name, - strip_filepath, - strip_channel, - strip_frame_start, - ) - new_strips = [movie_strip, sound_strip] - - # Update shift frame range prop. - frame_in = shot["data"].get("frame_in") - frame_3d_start = shot["data"].get("3d_start") - frame_3d_offset = frame_3d_start - addon_prefs.shot_builder_frame_offset - edit_export_offset = addon_prefs.edit_export_frame_offset - - # Set sequence strip start kitsu data. - for strip in new_strips: - strip.frame_start = ( - -frame_in + (strip_frame_start * 2) + frame_3d_offset + edit_export_offset - ) - return new_strips - - -def editorial_export_check_latest(context: bpy.types.Context): - """Find latest export in editorial export directory""" - addon_prefs = prefs.addon_prefs_get(context) - - edit_export_path = Path(addon_prefs.edit_export_dir) - - files_list = [ - f - for f in edit_export_path.iterdir() - if f.is_file() - and editorial_export_is_valid_edit_name( - addon_prefs.edit_export_file_pattern, f.name - ) - ] - if len(files_list) >= 1: - files_list = sorted(files_list, reverse=True) - return files_list[0] - return None - - -def editorial_export_is_valid_edit_name(file_pattern: str, filename: str) -> bool: - """Verify file name matches file pattern set in preferences""" - match = re.search(file_pattern, filename) - if match: - return True - return False diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/editorial/ops.py b/scripts-blender/addons/blender_kitsu/shot_builder/editorial/ops.py deleted file mode 100644 index c58c5970..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder/editorial/ops.py +++ /dev/null @@ -1,42 +0,0 @@ -import bpy -from typing import Set -from .core import editorial_export_get_latest -from ... import cache -import gazu - - -class ANIM_SETUP_OT_load_latest_editorial(bpy.types.Operator): - bl_idname = "asset_setup.load_latest_editorial" - bl_label = "Load Editorial Export" - bl_description = ( - "Loads latest edit from shot_preview_folder " - "Shifts edit so current shot starts at 3d_start metadata shot key from Kitsu" - ) - - def execute(self, context: bpy.types.Context) -> Set[str]: - cache_shot = cache.shot_active_get() - shot = gazu.shot.get_shot(cache_shot.id) # TODO INEFFICENT TO LOAD SHOT TWICE - strips = editorial_export_get_latest(context, shot) - if strips is None: - self.report( - {"ERROR"}, f"No valid editorial export in editorial export path." - ) - return {"CANCELLED"} - - self.report({"INFO"}, f"Loaded latest edit: {strips[0].name}") - return {"FINISHED"} - - -classes = [ - ANIM_SETUP_OT_load_latest_editorial, -] - - -def register(): - for cls in classes: - bpy.utils.register_class(cls) - - -def unregister(): - for cls in classes: - bpy.utils.unregister_class(cls) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/file_save.py b/scripts-blender/addons/blender_kitsu/shot_builder/file_save.py similarity index 100% rename from scripts-blender/addons/blender_kitsu/shot_builder_2/file_save.py rename to scripts-blender/addons/blender_kitsu/shot_builder/file_save.py diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/hook_examples/hooks.py b/scripts-blender/addons/blender_kitsu/shot_builder/hook_examples/hooks.py similarity index 100% rename from scripts-blender/addons/blender_kitsu/shot_builder_2/hook_examples/hooks.py rename to scripts-blender/addons/blender_kitsu/shot_builder/hook_examples/hooks.py diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/hooks.py b/scripts-blender/addons/blender_kitsu/shot_builder/hooks.py index 0dd27478..3a1a9e8d 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder/hooks.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder/hooks.py @@ -1,8 +1,14 @@ +import sys +import pathlib +from typing import * + import typing import types from collections.abc import Iterable - +import importlib +from .. import prefs import logging + logger = logging.getLogger(__name__) @@ -44,6 +50,8 @@ HookFunction = typing.Callable[[typing.Any], None] def _match_hook_parameter( hook_criteria: MatchCriteriaType, match_query: typing.Optional[str] ) -> bool: + if hook_criteria == None: + return True if hook_criteria == DoNotMatch: return match_query is None if hook_criteria == Wildcard: @@ -60,10 +68,6 @@ class Hooks: def __init__(self): self._hooks: typing.List[HookFunction] = [] - def register(self, func: HookFunction) -> None: - logger.info(f"registering hook '{func.__name__}'") - self._hooks.append(func) - def matches( self, hook: HookFunction, @@ -73,7 +77,6 @@ class Hooks: ) -> bool: assert not kwargs rules = typing.cast(MatchingRulesType, getattr(hook, '_shot_builder_rules')) - return all( ( _match_hook_parameter(rules['match_task_type'], match_task_type), @@ -86,32 +89,56 @@ class Hooks: if self.matches(hook=hook, **kwargs): yield hook + def execute_hooks( + self, match_task_type: str = None, match_asset_type: str = None, *args, **kwargs + ) -> None: + for hook in self._hooks: + if self.matches( + hook, match_task_type=match_task_type, match_asset_type=match_asset_type + ): + hook(*args, **kwargs) -def _register_hook(func: types.FunctionType) -> None: - from .project import get_active_production + def load_hooks(self, context): + root_dir = prefs.project_root_dir_get(context) + shot_builder_config_dir = root_dir.joinpath("pro/assets/scripts/shot-builder") + if not shot_builder_config_dir.exists(): + raise Exception("Shot Builder Hooks directory does not exist") + paths = [ + shot_builder_config_dir.resolve().__str__() # TODO Make variable path + ] # TODO Set path to where hooks are stored + with SystemPathInclude(paths) as _include: + try: + import hooks as production_hooks - production = get_active_production() - production.hooks.register(func) + importlib.reload(production_hooks) + self.register_hooks(production_hooks) + except ModuleNotFoundError: + raise Exception("Production has no `hooks.py` configuration file") + return False -def register_hooks(module: types.ModuleType) -> None: - """ - Register all hooks inside the given module. - """ - for module_item_str in dir(module): - module_item = getattr(module, module_item_str) - if not isinstance(module_item, types.FunctionType): - continue - if module_item.__module__ != module.__name__: - continue - if not hasattr(module_item, "_shot_builder_rules"): - continue - _register_hook(module_item) + def register(self, func: HookFunction) -> None: + logger.info(f"registering hook '{func.__name__}'") + self._hooks.append(func) + + def register_hooks(self, module: types.ModuleType) -> None: + """ + Register all hooks inside the given module. + """ + for module_item_str in dir(module): + module_item = getattr(module, module_item_str) + if not isinstance(module_item, types.FunctionType): + continue + if module_item.__module__ != module.__name__: + continue + if not hasattr(module_item, "_shot_builder_rules"): + continue + self.register(module_item) def hook( - match_task_type: MatchCriteriaType = DoNotMatch, - match_asset_type: MatchCriteriaType = DoNotMatch, + match_task_type: MatchCriteriaType = None, + match_asset_type: MatchCriteriaType = None, ) -> typing.Callable[[types.FunctionType], types.FunctionType]: """ Decorator to add custom logic when building a shot. @@ -128,3 +155,41 @@ def hook( return func return wrapper + + +class SystemPathInclude: + """ + Resource class to temporary include system paths to `sys.paths`. + + Usage: + ``` + paths = [pathlib.Path("/home/guest/my_python_scripts")] + with SystemPathInclude(paths) as t: + import my_module + reload(my_module) + ``` + + It is possible to nest multiple SystemPathIncludes. + """ + + def __init__(self, paths_to_add: List[pathlib.Path]): + # TODO: Check if all paths exist and are absolute. + self.__paths = paths_to_add + self.__original_sys_path: List[str] = [] + + def __enter__(self): + self.__original_sys_path = sys.path + new_sys_path = [] + for path_to_add in self.__paths: + # Do not add paths that are already in the sys path. + # Report this to the logger as this might indicate wrong usage. + path_to_add_str = str(path_to_add) + if path_to_add_str in self.__original_sys_path: + logger.warn(f"{path_to_add_str} already added to `sys.path`") + continue + new_sys_path.append(path_to_add_str) + new_sys_path.extend(self.__original_sys_path) + sys.path = new_sys_path + + def __exit__(self, exc_type, exc_value, exc_traceback): + sys.path = self.__original_sys_path diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/operators.py b/scripts-blender/addons/blender_kitsu/shot_builder/operators.py deleted file mode 100644 index d566ad74..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder/operators.py +++ /dev/null @@ -1,298 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -# -import pathlib -from typing import * -import bpy -import gazu -from .shot import ShotRef -from .project import ( - ensure_loaded_production, - get_active_production, -) -from .builder import ShotBuilder -from .task_type import TaskType -from .. import prefs, cache -from .anim_setup.core import ( - animation_workspace_delete_others, - animation_workspace_vse_area_add, -) -from .editorial.core import editorial_export_get_latest -from .builder.save_file import save_shot_builder_file - - -_production_task_type_items: List[Tuple[str, str, str]] = [] - - -def production_task_type_items( - self: Any, context: bpy.types.Context -) -> List[Tuple[str, str, str]]: - global _production_task_type_items - return _production_task_type_items - - -_production_seq_id_items: List[Tuple[str, str, str]] = [] - - -def production_seq_id_items( - self: Any, context: bpy.types.Context -) -> List[Tuple[str, str, str]]: - global _production_seq_id_items - return _production_seq_id_items - - -_production_shots: List[ShotRef] = [] - - -def production_shots( - self: Any, context: bpy.types.Context -) -> List[Tuple[str, str, str]]: - global _production_shots - return _production_shots - - -_production_shot_id_items_for_seq: List[Tuple[str, str, str]] = [] - - -def production_shot_id_items_for_seq( - self: Any, context: bpy.types.Context -) -> List[Tuple[str, str, str]]: - global _production_shot_id_items_for_seq - global _production_shot_id_items - - if not self.seq_id or not _production_shots: - return [] - - shots_for_seq: List[Tuple(str, str, str)] = [ - (s.name, s.name, "") - for s in _production_shots - if s.sequence.name == self.seq_id - ] - - _production_shot_id_items_for_seq.clear() - _production_shot_id_items_for_seq.extend(shots_for_seq) - - return _production_shot_id_items_for_seq - - -def reset_shot_id_enum(self: Any, context: bpy.types.Context) -> None: - production_shot_id_items_for_seq(self, context) - global _production_shot_id_items_for_seq - if _production_shot_id_items_for_seq: - self.shot_id = _production_shot_id_items_for_seq[0][0] - - -class SHOTBUILDER_OT_NewShotFile(bpy.types.Operator): - """Build a new shot file""" - - bl_idname = "shotbuilder.new_shot_file" - bl_label = "New Production Shot File" - - _timer = None - _built_shot = False - _add_vse_area = False - _file_path = '' - - production_root: bpy.props.StringProperty( # type: ignore - name="Production Root", description="Root of the production", subtype='DIR_PATH' - ) - - production_name: bpy.props.StringProperty( # type: ignore - name="Production", - description="Name of the production to create a shot file for", - options=set(), - ) - - seq_id: bpy.props.EnumProperty( # type: ignore - name="Sequence ID", - description="Sequence ID of the shot to build", - items=production_seq_id_items, - update=reset_shot_id_enum, - ) - - shot_id: bpy.props.EnumProperty( # type: ignore - name="Shot ID", - description="Shot ID of the shot to build", - items=production_shot_id_items_for_seq, - ) - - task_type: bpy.props.EnumProperty( # type: ignore - name="Task", - description="Task to create the shot file for", - items=production_task_type_items, - ) - auto_save: bpy.props.BoolProperty( - name="Save after building.", - description="Automatically save build file after 'Shot Builder' is complete.", - default=True, - ) - - def modal(self, context, event): - if event.type == 'TIMER' and not self._add_vse_area: - # Show Storyboard/Animatic from VSE - """Running as Modal Event because functions within execute() function like - animation_workspace_delete_others() changed UI context that needs to be refreshed. - https://docs.blender.org/api/current/info_gotcha.html#no-updates-after-changing-ui-context - """ - # TODO this is a hack, should be inherient to above builder - # TODO fix during refactor - if self.task_type == 'anim': - animation_workspace_vse_area_add(context) - self._add_vse_area = True - - if self._built_shot and self._add_vse_area: - if self.auto_save: - file_path = pathlib.Path() - try: - save_shot_builder_file(self._file_path) - self.report( - {"INFO"}, f"Saved Shot{self.shot_id} at {self._file_path}" - ) - return {'FINISHED'} - except FileExistsError: - self.report( - {"ERROR"}, - f"Cannot create a file/folder when that file/folder already exists {file_path}", - ) - return {'CANCELLED'} - self.report({"INFO"}, f"Built Shot {self.shot_id}, file is not saved!") - return {'FINISHED'} - - return {'PASS_THROUGH'} - - def invoke(self, context: bpy.types.Context, event: bpy.types.Event) -> Set[str]: - addon_prefs = prefs.addon_prefs_get(bpy.context) - project = cache.project_active_get() - - if addon_prefs.session.is_auth() is False: - self.report( - {'ERROR'}, - "Must be logged into Kitsu to continue. \nCheck login status in 'Blender Kitsu' addon preferences.", - ) - return {'CANCELLED'} - - if project.id == "": - self.report( - {'ERROR'}, - "Operator is not able to determine the Kitsu production's name. \nCheck project is selected in 'Blender Kitsu' addon preferences.", - ) - return {'CANCELLED'} - - if not addon_prefs.is_project_root_valid: - self.report( - {'ERROR'}, - "Operator is not able to determine the project root directory. \nCheck project root directiory is configured in 'Blender Kitsu' addon preferences.", - ) - return {'CANCELLED'} - - self.production_root = addon_prefs.project_root_dir - self.production_name = project.name - - if not ensure_loaded_production(context): - self.report( - {'ERROR'}, - "Shot builder configuration files not found in current project directory. \nCheck addon preferences to ensure project root contains shot_builder config.", - ) - return {'CANCELLED'} - - production = get_active_production() - - global _production_task_type_items - _production_task_type_items = production.get_task_type_items(context=context) - - global _production_seq_id_items - _production_seq_id_items = production.get_seq_items(context=context) - - global _production_shots - _production_shots = production.get_shots(context=context) - - return cast( - Set[str], context.window_manager.invoke_props_dialog(self, width=400) - ) - - def execute(self, context: bpy.types.Context) -> Set[str]: - addon_prefs = bpy.context.preferences.addons["blender_kitsu"].preferences - wm = context.window_manager - self._timer = wm.event_timer_add(0.1, window=context.window) - wm.modal_handler_add(self) - if not self.production_root: - self.report( - {'ERROR'}, - "Shot builder can only be started from the File menu. Shortcuts like CTRL-N don't work", - ) - return {'CANCELLED'} - - if self._built_shot: - return {'RUNNING_MODAL'} - ensure_loaded_production(context) - production = get_active_production() - shot_builder = ShotBuilder( - context=context, - production=production, - shot_name=self.shot_id, - task_type=TaskType(self.task_type), - ) - shot_builder.create_build_steps() - shot_builder.build() - - active_project = cache.project_active_get() - - # Build Kitsu Context - sequence = gazu.shot.get_sequence_by_name(active_project.id, self.seq_id) - shot = gazu.shot.get_shot_by_name(sequence, self.shot_id) - - # TODO this is a hack, should be inherient to above builder - # TODO fix during refactor - if self.task_type == 'anim': - # Load EDIT - editorial_export_get_latest(context, shot) - # Load Anim Workspace - animation_workspace_delete_others() - - # Initilize armatures - for obj in [obj for obj in bpy.data.objects if obj.type == "ARMATURE"]: - base_name = obj.name.split(addon_prefs.shot_builder_armature_prefix)[-1] - new_action = bpy.data.actions.new( - f"{addon_prefs.shot_builder_action_prefix}{base_name}.{self.shot_id}.v001" - ) - new_action.use_fake_user = True - obj.animation_data.action = new_action - - # Set Shot Frame Range - context.scene.frame_start = int(shot["data"].get("3d_start")) - context.scene.frame_end = ( - int(shot["data"].get("3d_start")) + shot.get('nb_frames') - 1 - ) - - # Run User Script - exec(addon_prefs.user_exec_code) - - self._file_path = shot_builder.build_context.shot.file_path - self._built_shot = True - return {'RUNNING_MODAL'} - - def draw(self, context: bpy.types.Context) -> None: - layout = self.layout - row = layout.row() - row.enabled = False - row.prop(self, "production_name") - layout.prop(self, "seq_id") - layout.prop(self, "shot_id") - layout.prop(self, "task_type") - layout.prop(self, "auto_save") diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py b/scripts-blender/addons/blender_kitsu/shot_builder/ops.py similarity index 100% rename from scripts-blender/addons/blender_kitsu/shot_builder_2/ops.py rename to scripts-blender/addons/blender_kitsu/shot_builder/ops.py diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/project.py b/scripts-blender/addons/blender_kitsu/shot_builder/project.py deleted file mode 100644 index 2cd71cb3..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder/project.py +++ /dev/null @@ -1,460 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -# - -import importlib -from collections import defaultdict - -import bpy - -from .task_type import * -from .shot import Shot, ShotRef -from .render_settings import RenderSettings -from .asset import Asset, AssetRef -from .sys_utils import * -from .hooks import Hooks, register_hooks -from .connectors.default import DefaultConnector -from .connectors.connector import Connector -import os - -from .. import prefs -from pathlib import Path - -from typing import * -import types - -logger = logging.getLogger(__name__) - - -class Production: - """ - Class containing data and methods for a production. - - # Data members # - path: contains the path to the root of the production. - task_types: contains a list of `TaskType`s or a Connector to retrieve that are defined for this - production. By default the task_types are prefilled with anim and light. - name: human readable name of the production. - - """ - - __ATTRNAMES_SUPPORTING_CONNECTOR = ['task_types', 'shots', 'name'] - - def __init__(self, production_path: pathlib.Path): - self.path = production_path - self.task_types: List[TaskType] = [] - self.task_types_connector = DefaultConnector - self.shots_connector = DefaultConnector - self.assets: List[type] = [] - self.shots: List[Shot] = [] - self.name = "" - self.name_connector = DefaultConnector - self.render_settings_connector = DefaultConnector - self.config: Dict[str, Any] = {} - self.__shot_lookup: Dict[str, Shot] = {} - self.hooks: Hooks = Hooks() - self.shot_data_synced = False - - self.scene_name_format = "{shot.sequence_code}_{shot.code}.{task_type}" - self.shot_name_format = "{shot.sequence_code}_{shot.code}" - self.file_name_format = ( - "{production.path}shots/{shot.code}/{shot.code}.{task_type}.blend" - ) - - def __create_connector( - self, connector_cls: Type[Connector], context: bpy.types.Context - ) -> Connector: - # TODO: Cache connector - preferences = context.preferences.addons["blender_kitsu"].preferences - return connector_cls(production=self, preferences=preferences) - - def __format_shot_name(self, shot: Shot) -> str: - return self.shot_name_format.format(shot=shot) - - def get_task_type_items( - self, context: bpy.types.Context - ) -> List[Tuple[str, str, str]]: - """ - Get the list of task types items to be used in an item function of a - `bpy.props.EnumProperty` - """ - if not self.task_types: - connector = self.__create_connector( - self.task_types_connector, context=context - ) - self.task_types = connector.get_task_types() - return [ - (task_type.name, task_type.name, task_type.name) - for task_type in self.task_types - ] - - def get_assets_for_shot( - self, context: bpy.types.Context, shot: Shot - ) -> List[AssetRef]: - connector = self.__create_connector(self.shots_connector, context=context) - - return connector.get_assets_for_shot(shot) - - def get_shots(self, context: bpy.types.Context) -> List[ShotRef]: - connector = self.__create_connector(self.shots_connector, context=context) - return connector.get_shots() - - def get_shot(self, context: bpy.types.Context, shot_name: str) -> Optional[Shot]: - self._ensure_shot_data(context) - - for shot in self.shots: - if shot.name == shot_name: - return shot - return None - - def _ensure_shot_data(self, context: bpy.types.Context) -> None: - if self.shot_data_synced: - return - # Find a generic shot definition. This class will be used as template - # when no specific shot definition could be found. - generic_shot_class = None - for shot in self.shots: - if shot.is_generic: - generic_shot_class = shot.__class__ - break - - shot_refs = self.get_shots(context) - for shot_ref in shot_refs: - logger.debug(f"Finding shot definition for {shot_ref.name}") - for shot in self.shots: - if shot.name == shot_ref.name: - logger.debug(f"Shot definition found for {shot_ref.name}") - shot_ref.sync_data(shot) - break - else: - logger.info(f"No shot definition found for {shot_ref.name}") - if generic_shot_class: - logger.info(f"Using generic shot class") - shot = generic_shot_class() - shot_ref.sync_data(shot) - shot.is_generic = False - self.shots.append(shot) - - self.shot_data_synced = True - - def get_render_settings( - self, context: bpy.types.Context, shot: Shot - ) -> RenderSettings: - connector = self.__create_connector(self.shots_connector, context=context) - return connector.get_render_settings(shot) - - def get_shot_items(self, context: bpy.types.Context) -> List[Tuple[str, str, str]]: - """ - Get the list of shot items to be used in an item function of a - `bpy.props.EnumProperty` to select a shot. - """ - result = [] - self._ensure_shot_data(context) - sequences: Dict[str, List[Shot]] = defaultdict(list) - for shot in self.shots: - if not shot.is_valid(): - continue - sequences[shot.sequence_code].append(shot) - - sorted_sequences = sorted(sequences.keys()) - for sequence in sorted_sequences: - result.append(("", sequence, sequence)) - for shot in sorted(sequences[sequence], key=lambda x: x.name): - result.append((shot.name, self.__format_shot_name(shot), shot.name)) - - return result - - def get_seq_items(self, context: bpy.types.Context) -> List[Tuple[str, str, str]]: - """ - Get the list of seq items to be used in an item function of a - `bpy.props.EnumProperty` to select a shot. - """ - shots = self.get_shots(context) - sequences = list(set([s.sequence for s in shots])) - sequences.sort(key=lambda seq: seq.name) - - return [(seq.name, seq.name, "") for seq in sequences] - - def get_name(self, context: bpy.types.Context) -> str: - """ - Get the name of the production - """ - if not self.name: - connector = self.__create_connector(self.name_connector, context=context) - self.name = connector.get_name() - return self.name - - # TODO: Use visitor pattern. - def __load_name(self, main_config_mod: types.ModuleType) -> None: - name = getattr(main_config_mod, "PRODUCTION_NAME", None) - if name is None: - return - - # Extract task types from a list of strings - if isinstance(name, str): - self.name = name - return - - if issubclass(name, Connector): - self.name = "" - self.name_connector = name - return - - logger.warn( - "Skip loading of production name. Incorrect configuration detected." - ) - - def __load_task_types(self, main_config_mod: types.ModuleType) -> None: - task_types = getattr(main_config_mod, "TASK_TYPES", None) - if task_types is None: - return - - # Extract task types from a list of strings - if isinstance(task_types, list): - self.task_types = [TaskType(task_type) for task_type in task_types] - return - - if issubclass(task_types, Connector): - self.task_types = task_types - - logger.warn("Skip loading of task_types. Incorrect configuration detected.") - - def __load_shots_connector(self, main_config_mod: types.ModuleType) -> None: - shots = getattr(main_config_mod, "SHOTS", None) - if shots is None: - return - - # Extract task types from a list of strings - if issubclass(shots, Connector): - self.shots_connector = shots - return - - logger.warn("Skip loading of shots. Incorrect configuration detected.") - - def __load_connector_keys(self, main_config_mod: types.ModuleType) -> None: - connectors = set() - for attrname in Production.__ATTRNAMES_SUPPORTING_CONNECTOR: - connector = getattr(self, f"{attrname}_connector") - connectors.add(connector) - - connector_keys = set() - for connector in connectors: - for key in connector.PRODUCTION_KEYS: - connector_keys.add(key) - - for connector_key in connector_keys: - if hasattr(main_config_mod, connector_key): - self.config[connector_key] = getattr(main_config_mod, connector_key) - - def __load_render_settings(self, main_config_mod: types.ModuleType) -> None: - render_settings = getattr(main_config_mod, "RENDER_SETTINGS", None) - if render_settings is None: - return - - if issubclass(render_settings, Connector): - self.render_settings_connector = render_settings - return - - logger.warn("Skip loading of render settings. Incorrect configuration detected") - - def __load_formatting_strings(self, main_config_mod: types.ModuleType) -> None: - self.shot_name_format = getattr( - main_config_mod, "SHOT_NAME_FORMAT", self.scene_name_format - ) - self.scene_name_format = getattr( - main_config_mod, "SCENE_NAME_FORMAT", self.scene_name_format - ) - self.file_name_format = getattr( - main_config_mod, "FILE_NAME_FORMAT", self.file_name_format - ) - - def _load_config(self, main_config_mod: types.ModuleType) -> None: - self.__load_name(main_config_mod) - self.__load_task_types(main_config_mod) - self.__load_shots_connector(main_config_mod) - self.__load_connector_keys(main_config_mod) - self.__load_render_settings(main_config_mod) - self.__load_formatting_strings(main_config_mod) - - def _load_asset_definitions(self, asset_mod: types.ModuleType) -> None: - """ - Load all assets from the given module. - """ - self.assets = [] - for module_item_str in dir(asset_mod): - module_item = getattr(asset_mod, module_item_str) - if module_item.__class__ != type: - continue - if not issubclass(module_item, Asset): - continue - if not hasattr(module_item, "name"): - continue - logger.info(f"loading asset config {module_item}") - self.assets.append(module_item) - # TODO: only add assets that are leaves - - def _load_shot_definitions(self, shot_mod: types.ModuleType) -> None: - """ - Load all assets from the given module. - """ - self.shots = [] - for module_item_str in dir(shot_mod): - module_item = getattr(shot_mod, module_item_str) - if module_item.__class__ != type: - continue - if not issubclass(module_item, Shot): - continue - if not hasattr(module_item, "name"): - continue - logger.info(f"loading shot config {module_item}") - self.shots.append(module_item()) - - -_PRODUCTION: Optional[Production] = None - - -def is_valid_production_root(path: pathlib.Path) -> bool: - """ - Test if the given project path is configured correctly. - - A valid project path contains a subfolder with the name `shot-builder` - holding configuration files. - """ - if not path.is_absolute(): - return False - if not path.exists(): - return False - if not path.is_dir(): - return False - config_file_path = get_production_config_file_path(path) - return config_file_path.exists() - - -def get_production_config_dir_path(path: pathlib.Path) -> pathlib.Path: - """ - Get the production configuration dir path. - """ - return path / "shot-builder" - - -def get_production_config_file_path(path: pathlib.Path) -> pathlib.Path: - """ - Get the production configuration file path. - """ - return get_production_config_dir_path(path) / "config.py" - - -def _find_production_root(path: pathlib.Path) -> Optional[pathlib.Path]: - """ - Given a path try to find the production root - """ - if is_valid_production_root(path): - return path - try: - parent_path = path.parents[0] - return _find_production_root(parent_path) - except IndexError: - return None - - -# TODO: return type is optional -def get_production_root(context: bpy.types.Context) -> Optional[pathlib.Path]: - """ - Determine the project root based on the current file. - When current file isn't part of a project the project root - configured in the add-on will be used. - """ - current_file = pathlib.Path(bpy.data.filepath) - production_root = _find_production_root(current_file) - if production_root: - return production_root - - addon_prefs = prefs.addon_prefs_get(bpy.context) - production_root = Path(addon_prefs.project_root_dir) - if is_valid_production_root(production_root): - return production_root - return None - - -def ensure_loaded_production(context: bpy.types.Context) -> bool: - """ - Ensure that the production of the current context is loaded. - - Returns if the production of for the given context is loaded. - """ - global _PRODUCTION - addon_prefs = prefs.addon_prefs_get(bpy.context) - base_path = Path(addon_prefs.project_root_dir) - production_root = os.path.join( - base_path, "pro" - ) # TODO Fix during refactor should use base_path - if is_valid_production_root(Path(production_root)): - logger.debug(f"loading new production configuration from '{production_root}'.") - __load_production_configuration(context, Path(production_root)) - return True - return False - - -def __load_production_configuration( - context: bpy.types.Context, production_path: pathlib.Path -) -> bool: - global _PRODUCTION - _PRODUCTION = Production(production_path) - paths = [production_path / "shot-builder"] - with SystemPathInclude(paths) as _include: - try: - import config as production_config - - importlib.reload(production_config) - _PRODUCTION._load_config(production_config) - except ModuleNotFoundError: - logger.warning("Production has no `config.py` configuration file") - - try: - import shots as production_shots - - importlib.reload(production_shots) - _PRODUCTION._load_shot_definitions(production_shots) - except ModuleNotFoundError: - logger.warning("Production has no `shots.py` configuration file") - - try: - import assets as production_assets - - importlib.reload(production_assets) - _PRODUCTION._load_asset_definitions(production_assets) - except ModuleNotFoundError: - logger.warning("Production has no `assets.py` configuration file") - - try: - import hooks as production_hooks - - importlib.reload(production_hooks) - register_hooks(production_hooks) - except ModuleNotFoundError: - logger.warning("Production has no `hooks.py` configuration file") - pass - - return False - - -def get_active_production() -> Production: - global _PRODUCTION - assert _PRODUCTION - return _PRODUCTION diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/render_settings.py b/scripts-blender/addons/blender_kitsu/shot_builder/render_settings.py deleted file mode 100644 index 1a396d95..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder/render_settings.py +++ /dev/null @@ -1,29 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -# - -from ..shot_builder.asset import Asset -from typing import * - - -class RenderSettings: - def __init__(self, width: int, height: int, frames_per_second: float): - self.width = width - self.height = height - self.frames_per_second = frames_per_second diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/shot.py b/scripts-blender/addons/blender_kitsu/shot_builder/shot.py deleted file mode 100644 index 8416f641..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder/shot.py +++ /dev/null @@ -1,64 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -# - -import typing - - -class Shot: - is_generic = False - kitsu_id = "" - sequence_code = "" - name = "" - code = "" - frame_start = 0 - frames = 0 - # Frame_end will be stored for debugging only. - frame_end = 0 - frames_per_second = 24.0 - file_path_format = "{production.path}/shots/{shot.sequence_code}/{shot.name}/{shot.name}.{task_type}.blend" - file_path = "" - - def is_valid(self) -> bool: - """ - Check if this shot contains all data so it could be selected - for shot building. - - When not valid it won't be shown in the shot selection field. - """ - if not self.name: - return False - - if self.frames <= 0: - return False - - return True - - -class ShotRef: - """ - Reference to an asset from an external system. - """ - - def __init__(self, name: str = "", code: str = ""): - self.name = name - self.code = code - - def sync_data(self, shot: Shot) -> None: - pass diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/sys_utils.py b/scripts-blender/addons/blender_kitsu/shot_builder/sys_utils.py deleted file mode 100644 index ee023d33..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder/sys_utils.py +++ /dev/null @@ -1,64 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -# - -import sys -import pathlib -import logging -from typing import * - -logger = logging.getLogger(__name__) - - -class SystemPathInclude: - """ - Resource class to temporary include system paths to `sys.paths`. - - Usage: - ``` - paths = [pathlib.Path("/home/guest/my_python_scripts")] - with SystemPathInclude(paths) as t: - import my_module - reload(my_module) - ``` - - It is possible to nest multiple SystemPathIncludes. - """ - - def __init__(self, paths_to_add: List[pathlib.Path]): - # TODO: Check if all paths exist and are absolute. - self.__paths = paths_to_add - self.__original_sys_path: List[str] = [] - - def __enter__(self): - self.__original_sys_path = sys.path - new_sys_path = [] - for path_to_add in self.__paths: - # Do not add paths that are already in the sys path. - # Report this to the logger as this might indicate wrong usage. - path_to_add_str = str(path_to_add) - if path_to_add_str in self.__original_sys_path: - logger.warn(f"{path_to_add_str} already added to `sys.path`") - continue - new_sys_path.append(path_to_add_str) - new_sys_path.extend(self.__original_sys_path) - sys.path = new_sys_path - - def __exit__(self, exc_type, exc_value, exc_traceback): - sys.path = self.__original_sys_path diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/task_type.py b/scripts-blender/addons/blender_kitsu/shot_builder/task_type.py deleted file mode 100644 index 21fe9b6e..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder/task_type.py +++ /dev/null @@ -1,27 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -# - - -class TaskType: - def __init__(self, task_name: str): - self.name = task_name - - def __str__(self) -> str: - return self.name diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/template.py b/scripts-blender/addons/blender_kitsu/shot_builder/template.py similarity index 100% rename from scripts-blender/addons/blender_kitsu/shot_builder_2/template.py rename to scripts-blender/addons/blender_kitsu/shot_builder/template.py diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/templates/anim.blend b/scripts-blender/addons/blender_kitsu/shot_builder/templates/anim.blend similarity index 100% rename from scripts-blender/addons/blender_kitsu/shot_builder_2/templates/anim.blend rename to scripts-blender/addons/blender_kitsu/shot_builder/templates/anim.blend diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/templates/comp.blend b/scripts-blender/addons/blender_kitsu/shot_builder/templates/comp.blend similarity index 100% rename from scripts-blender/addons/blender_kitsu/shot_builder_2/templates/comp.blend rename to scripts-blender/addons/blender_kitsu/shot_builder/templates/comp.blend diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/templates/fx.blend b/scripts-blender/addons/blender_kitsu/shot_builder/templates/fx.blend similarity index 100% rename from scripts-blender/addons/blender_kitsu/shot_builder_2/templates/fx.blend rename to scripts-blender/addons/blender_kitsu/shot_builder/templates/fx.blend diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/templates/lighting.blend b/scripts-blender/addons/blender_kitsu/shot_builder/templates/lighting.blend similarity index 100% rename from scripts-blender/addons/blender_kitsu/shot_builder_2/templates/lighting.blend rename to scripts-blender/addons/blender_kitsu/shot_builder/templates/lighting.blend diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/ui.py b/scripts-blender/addons/blender_kitsu/shot_builder/ui.py index 15b72ff8..caa65303 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder/ui.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder/ui.py @@ -1,27 +1,31 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -# import bpy -from typing import * -from .operators import * +from typing import Any def topbar_file_new_draw_handler(self: Any, context: bpy.types.Context) -> None: layout = self.layout - op = layout.operator(SHOTBUILDER_OT_NewShotFile.bl_idname, text="Shot File") + op = layout.operator("kitsu.build_new_shot", text="Shot File") + + +class KITSU_PT_new_shot_panel(bpy.types.Panel): # TODO Remove (for testing only) + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_label = "New Shot" + bl_category = "New Shot" + + def draw(self, context): + self.layout.operator("kitsu.build_new_shot") + self.layout.operator("kitsu.save_shot_builder_hooks") + + +classes = (KITSU_PT_new_shot_panel,) + + +def register(): + for cls in classes: + bpy.utils.register_class(cls) + + +def unregister(): + for cls in reversed(classes): + bpy.utils.unregister_class(cls) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/vars.py b/scripts-blender/addons/blender_kitsu/shot_builder/vars.py deleted file mode 100644 index 397f8302..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder/vars.py +++ /dev/null @@ -1 +0,0 @@ -DEFAULT_FRAME_START: int = 101 diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/__init__.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/__init__.py deleted file mode 100644 index 096e0f91..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -from . import ops, ui - - -def register(): - ops.register() - ui.register() - - -def unregister(): - ops.unregister() - ui.unregister() diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/hooks.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/hooks.py deleted file mode 100644 index 3a1a9e8d..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/hooks.py +++ /dev/null @@ -1,195 +0,0 @@ -import sys -import pathlib -from typing import * - -import typing -import types -from collections.abc import Iterable -import importlib -from .. import prefs -import logging - -logger = logging.getLogger(__name__) - - -class Wildcard: - pass - - -class DoNotMatch: - pass - - -MatchCriteriaType = typing.Union[ - str, typing.List[str], typing.Type[Wildcard], typing.Type[DoNotMatch] -] -""" -The MatchCriteriaType is a type definition for the parameters of the `hook` decorator. - -The matching parameters can use multiple types to detect how the matching criteria -would work. - -* `str`: would perform an exact string match. -* `typing.Iterator[str]`: would perform an exact string match with any of the given strings. -* `typing.Type[Wildcard]`: would match any type for this parameter. This would be used so a hook - is called for any value. -* `typing.Type[DoNotMatch]`: would ignore this hook when matching the hook parameter. This is the default - value for the matching criteria and would normally not be set directly in a - production configuration. -""" - -MatchingRulesType = typing.Dict[str, MatchCriteriaType] -""" -Hooks are stored as `_shot_builder_rules' attribute on the function. -The MatchingRulesType is the type definition of the `_shot_builder_rules` attribute. -""" - -HookFunction = typing.Callable[[typing.Any], None] - - -def _match_hook_parameter( - hook_criteria: MatchCriteriaType, match_query: typing.Optional[str] -) -> bool: - if hook_criteria == None: - return True - if hook_criteria == DoNotMatch: - return match_query is None - if hook_criteria == Wildcard: - return True - if isinstance(hook_criteria, str): - return match_query == hook_criteria - if isinstance(hook_criteria, list): - return match_query in hook_criteria - logger.error(f"Incorrect matching criteria {hook_criteria}, {match_query}") - return False - - -class Hooks: - def __init__(self): - self._hooks: typing.List[HookFunction] = [] - - def matches( - self, - hook: HookFunction, - match_task_type: typing.Optional[str] = None, - match_asset_type: typing.Optional[str] = None, - **kwargs: typing.Optional[str], - ) -> bool: - assert not kwargs - rules = typing.cast(MatchingRulesType, getattr(hook, '_shot_builder_rules')) - return all( - ( - _match_hook_parameter(rules['match_task_type'], match_task_type), - _match_hook_parameter(rules['match_asset_type'], match_asset_type), - ) - ) - - def filter(self, **kwargs: typing.Optional[str]) -> typing.Iterator[HookFunction]: - for hook in self._hooks: - if self.matches(hook=hook, **kwargs): - yield hook - - def execute_hooks( - self, match_task_type: str = None, match_asset_type: str = None, *args, **kwargs - ) -> None: - for hook in self._hooks: - if self.matches( - hook, match_task_type=match_task_type, match_asset_type=match_asset_type - ): - hook(*args, **kwargs) - - def load_hooks(self, context): - root_dir = prefs.project_root_dir_get(context) - shot_builder_config_dir = root_dir.joinpath("pro/assets/scripts/shot-builder") - if not shot_builder_config_dir.exists(): - raise Exception("Shot Builder Hooks directory does not exist") - paths = [ - shot_builder_config_dir.resolve().__str__() # TODO Make variable path - ] # TODO Set path to where hooks are stored - with SystemPathInclude(paths) as _include: - try: - import hooks as production_hooks - - importlib.reload(production_hooks) - self.register_hooks(production_hooks) - except ModuleNotFoundError: - raise Exception("Production has no `hooks.py` configuration file") - - return False - - def register(self, func: HookFunction) -> None: - logger.info(f"registering hook '{func.__name__}'") - self._hooks.append(func) - - def register_hooks(self, module: types.ModuleType) -> None: - """ - Register all hooks inside the given module. - """ - for module_item_str in dir(module): - module_item = getattr(module, module_item_str) - if not isinstance(module_item, types.FunctionType): - continue - if module_item.__module__ != module.__name__: - continue - if not hasattr(module_item, "_shot_builder_rules"): - continue - self.register(module_item) - - -def hook( - match_task_type: MatchCriteriaType = None, - match_asset_type: MatchCriteriaType = None, -) -> typing.Callable[[types.FunctionType], types.FunctionType]: - """ - Decorator to add custom logic when building a shot. - - Hooks are used to extend the configuration that would be not part of the core logic of the shot builder tool. - """ - rules = { - 'match_task_type': match_task_type, - 'match_asset_type': match_asset_type, - } - - def wrapper(func: types.FunctionType) -> types.FunctionType: - setattr(func, '_shot_builder_rules', rules) - return func - - return wrapper - - -class SystemPathInclude: - """ - Resource class to temporary include system paths to `sys.paths`. - - Usage: - ``` - paths = [pathlib.Path("/home/guest/my_python_scripts")] - with SystemPathInclude(paths) as t: - import my_module - reload(my_module) - ``` - - It is possible to nest multiple SystemPathIncludes. - """ - - def __init__(self, paths_to_add: List[pathlib.Path]): - # TODO: Check if all paths exist and are absolute. - self.__paths = paths_to_add - self.__original_sys_path: List[str] = [] - - def __enter__(self): - self.__original_sys_path = sys.path - new_sys_path = [] - for path_to_add in self.__paths: - # Do not add paths that are already in the sys path. - # Report this to the logger as this might indicate wrong usage. - path_to_add_str = str(path_to_add) - if path_to_add_str in self.__original_sys_path: - logger.warn(f"{path_to_add_str} already added to `sys.path`") - continue - new_sys_path.append(path_to_add_str) - new_sys_path.extend(self.__original_sys_path) - sys.path = new_sys_path - - def __exit__(self, exc_type, exc_value, exc_traceback): - sys.path = self.__original_sys_path diff --git a/scripts-blender/addons/blender_kitsu/shot_builder_2/ui.py b/scripts-blender/addons/blender_kitsu/shot_builder_2/ui.py deleted file mode 100644 index 1ab5bd2f..00000000 --- a/scripts-blender/addons/blender_kitsu/shot_builder_2/ui.py +++ /dev/null @@ -1,24 +0,0 @@ -import bpy - - -class KITSU_PT_new_shot_panel(bpy.types.Panel): # TODO Remove (for testing only) - bl_space_type = "VIEW_3D" - bl_region_type = "UI" - bl_label = "New Shot" - bl_category = "New Shot" - - def draw(self, context): - self.layout.operator("kitsu.build_new_shot") - - -classes = (KITSU_PT_new_shot_panel,) - - -def register(): - for cls in classes: - bpy.utils.register_class(cls) - - -def unregister(): - for cls in reversed(classes): - bpy.utils.unregister_class(cls) -- 2.30.2 From 2cb80648b61135676d6d0211a7ff9e643dcb2dde Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Thu, 21 Dec 2023 13:44:22 -0500 Subject: [PATCH 46/55] Set Frame Start via bkglobals --- scripts-blender/addons/blender_kitsu/shot_builder/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/core.py b/scripts-blender/addons/blender_kitsu/shot_builder/core.py index 17ce9087..5ae95baa 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder/core.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder/core.py @@ -88,7 +88,7 @@ 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 101 # TODO Set in Constants + return bkglobals.FRAME_START def set_frame_range(shot: Shot, scene: bpy.types.Scene): -- 2.30.2 From 779451b65d272fea1f9d7cc408abbc7c31295af4 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Thu, 21 Dec 2023 14:18:45 -0500 Subject: [PATCH 47/55] Fix Bug in Hooks.py --- .../addons/blender_kitsu/shot_builder/hook_examples/hooks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/hook_examples/hooks.py b/scripts-blender/addons/blender_kitsu/shot_builder/hook_examples/hooks.py index 37b0bdd4..b9efe7d9 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder/hook_examples/hooks.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder/hook_examples/hooks.py @@ -1,6 +1,6 @@ import bpy -from blender_kitsu.shot_builder_2.hooks import hook +from blender_kitsu.shot_builder.hooks import hook from blender_kitsu.types import Shot, Asset import logging @@ -14,7 +14,7 @@ Arguments to use in hooks Notes matching_task_type = ['anim', 'lighting', 'fx', 'comp'] # either use list or just one string - output_col_name = shot.get_output_collection_name(task_type="anim") + output_col_name = shot.get_output_collection_name(task_type_short_name="anim") -- 2.30.2 From 2242b9a251bc3bc05a37a60b89258c03d42b4c3d Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Thu, 21 Dec 2023 14:18:55 -0500 Subject: [PATCH 48/55] Fix bug in `link_task_type_output_collections` --- .../addons/blender_kitsu/shot_builder/core.py | 20 +++++++++------- .../addons/blender_kitsu/shot_builder/ops.py | 24 +++++++++---------- .../blender_kitsu/shot_builder/template.py | 14 ++++++----- scripts-blender/addons/blender_kitsu/types.py | 13 +++++----- 4 files changed, 37 insertions(+), 34 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/core.py b/scripts-blender/addons/blender_kitsu/shot_builder/core.py index 5ae95baa..bd979c4c 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder/core.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder/core.py @@ -169,7 +169,7 @@ def create_task_type_output_collection( scene: bpy.types.Scene, shot: Shot, task_type: TaskType ) -> bpy.types.Collection: collections = bpy.data.collections - output_col_name = shot.get_output_collection_name(task_type) + output_col_name = shot.get_output_collection_name(task_type.get_short_name()) if not collections.get(output_col_name): bpy.data.collections.new(name=output_col_name) @@ -187,14 +187,16 @@ def create_task_type_output_collection( return output_collection -def link_task_type_output_collections(shot: Shot, task_short_name: str): - # TODO TEST IF THIS WORKS - if bkglobals.OUTPUT_COL_LINK_MAPPING.get(task_short_name) == None: +def link_task_type_output_collections(shot: Shot, task_type: TaskType): + task_type_short_name = task_type.get_short_name() + if bkglobals.OUTPUT_COL_LINK_MAPPING.get(task_type_short_name) == None: return - for short_name in bkglobals.OUTPUT_COL_LINK_MAPPING.get(task_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) - if not external_filepath.exists(): - print(f"Unable to link output collection for {external_filepath.name}") + if not Path(external_filepath).exists(): + print( + f"Unable to link output collection for {Path(external_filepath).name}" + ) file_path = external_filepath.__str__() - colection_name = shot.get_shot_task_name(short_name) - link_data_block(file_path, colection_name) + colection_name = shot.get_output_collection_name(short_name) + link_data_block(file_path, colection_name, 'Collection') diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/ops.py b/scripts-blender/addons/blender_kitsu/shot_builder/ops.py index fe976254..3e73db40 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder/ops.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder/ops.py @@ -201,14 +201,14 @@ class KITSU_OT_build_new_shot(bpy.types.Operator): seq = active_project.get_sequence(self.seq_id) shot = active_project.get_shot(self.shot_id) task_type = self._get_task_type_for_shot(context, shot) - task_short_name = task_type.get_short_name() - shot_file_path_str = shot.get_shot_filepath(context, task_type) + task_type_short_name = task_type.get_short_name() + shot_file_path_str = shot.get_shot_filepath(context, task_type_short_name) # Open Template File - replace_workspace_with_template(context, task_short_name) + replace_workspace_with_template(context, task_type_short_name) # Set Up Scene + Naming - shot_task_name = shot.get_shot_task_name(task_type) + shot_task_name = shot.get_shot_task_name(task_type.get_short_name()) scene = set_shot_scene(context, shot_task_name) remove_all_data() set_resolution_and_fps(active_project, scene) @@ -218,33 +218,33 @@ class KITSU_OT_build_new_shot(bpy.types.Operator): # TODO Only run if saving file # Set Render Settings - if task_short_name == 'anim': # TODO get anim from a constant instead + if task_type_short_name == 'anim': # TODO get anim from a constant instead set_render_engine(context.scene, 'BLENDER_WORKBENCH') else: set_render_engine(context.scene) # Create Output Collection & Link Camera - if bkglobals.OUTPUT_COL_CREATE.get(task_short_name): + if bkglobals.OUTPUT_COL_CREATE.get(task_type_short_name): output_col = create_task_type_output_collection( # TODO imporve context.scene, shot, task_type ) - if task_short_name == 'anim': + if task_type_short_name == 'anim': link_camera_rig(context.scene, output_col) - # Load Assets - get_shot_assets(scene=scene, output_collection=output_col, shot=shot) + # Load Assets + get_shot_assets(scene=scene, output_collection=output_col, shot=shot) # Link External Output Collections - link_task_type_output_collections(shot, task_short_name) + link_task_type_output_collections(shot, task_type) - if bkglobals.LOAD_EDITORIAL_REF.get(task_short_name): + if bkglobals.LOAD_EDITORIAL_REF.get(task_type_short_name): editorial_export_get_latest(context, shot) # Run Hooks hooks_instance = Hooks() hooks_instance.load_hooks(context) hooks_instance.execute_hooks( - match_task_type=task_short_name, + match_task_type=task_type_short_name, scene=context.scene, shot=shot, prod_path=prefs.project_root_dir_get(context), diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/template.py b/scripts-blender/addons/blender_kitsu/shot_builder/template.py index 928d32e2..5d83a841 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder/template.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder/template.py @@ -13,23 +13,25 @@ def get_template_files() -> list[Path]: return list(dir.glob('*.blend')) -def get_template_for_task_type(task_short_name: str) -> Path: +def get_template_for_task_type(task_type_short_name: str) -> Path: for file in get_template_files(): - if file.stem == task_short_name: + if file.stem == task_type_short_name: return file -def open_template_for_task_type(task_short_name: str) -> bool: +def open_template_for_task_type(task_type_short_name: str) -> bool: # TODO THIS DOESN'T WORK BECAUSE CHANGE THE OPEN FILE MESSES UP THE CONTEXT SOMEHOW - file_path = get_template_for_task_type(task_short_name) + file_path = get_template_for_task_type(task_type_short_name) if file_path.exists() and file_path is not None: bpy.ops.wm.open_mainfile('EXEC_DEFAULT', filepath=file_path.__str__()) return True return False -def replace_workspace_with_template(context: bpy.types.Context, task_short_name: str): - file_path = get_template_for_task_type(task_short_name).resolve().absolute() +def replace_workspace_with_template( + context: bpy.types.Context, task_type_short_name: str +): + file_path = get_template_for_task_type(task_type_short_name).resolve().absolute() remove_prefix = "REMOVE-" if not file_path.exists(): return diff --git a/scripts-blender/addons/blender_kitsu/types.py b/scripts-blender/addons/blender_kitsu/types.py index bbaad10d..3b147414 100644 --- a/scripts-blender/addons/blender_kitsu/types.py +++ b/scripts-blender/addons/blender_kitsu/types.py @@ -572,11 +572,11 @@ class Shot(Entity): gazu.shot.update_shot(asdict(self)) return self - def get_shot_task_name(self, task_type: TaskType) -> str: # - return f"{self.name}{bkglobals.FILE_DELIMITER}{task_type.get_short_name()}" + def get_shot_task_name(self, task_type_short_name: str) -> str: # + return f"{self.name}{bkglobals.FILE_DELIMITER}{task_type_short_name}" - def get_output_collection_name(self, task_type: TaskType) -> str: - return f"{self.get_shot_task_name(task_type)}{bkglobals.FILE_DELIMITER}output" + 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" def get_shot_dir(self, context) -> str: project_root_dir = prefs.project_root_dir_get(context) @@ -585,9 +585,8 @@ class Shot(Entity): shot_dir = all_shots_dir.joinpath(seq.name).joinpath(self.name) return shot_dir.__str__() - def get_shot_filepath(self, context, task_type: TaskType) -> str: - shot_task_name = self.get_shot_task_name(task_type) - file_name = shot_task_name + '.blend' + def get_shot_filepath(self, context, task_type_short_name: str) -> str: + file_name = self.get_shot_task_name(task_type_short_name) + '.blend' return Path(self.get_shot_dir(context)).joinpath(file_name).__str__() def update_data(self, data: Dict[str, Any]) -> Shot: -- 2.30.2 From d33b3aa94d8511a2513394484c7023b8de7ebbac Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Thu, 21 Dec 2023 14:21:22 -0500 Subject: [PATCH 49/55] Improve Report for Successful Shot Build --- scripts-blender/addons/blender_kitsu/shot_builder/ops.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/ops.py b/scripts-blender/addons/blender_kitsu/shot_builder/ops.py index 3e73db40..b0793fcf 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder/ops.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder/ops.py @@ -258,7 +258,9 @@ class KITSU_OT_build_new_shot(bpy.types.Operator): print(f"Seq Name: '{seq.name}' Seq ID: '{self.seq_id}'") print(f"Shot Name: '{shot.name}' Shot ID: '{self.shot_id}'") print(f"Task Type Name: '{task_type.name}' Task Type ID: `{self.task_type}`") - self.report({"INFO"}, f"Execution Complete") # TODO remove + self.report( + {"INFO"}, f"Successfully Built Shot:`{shot.name}` Task: `{task_type.name}`" + ) return {"FINISHED"} -- 2.30.2 From a0030aeb811bbf4e21b83cb938dccc3706ef602f Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Thu, 21 Dec 2023 14:21:44 -0500 Subject: [PATCH 50/55] Remove Debug Prints --- scripts-blender/addons/blender_kitsu/shot_builder/ops.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/ops.py b/scripts-blender/addons/blender_kitsu/shot_builder/ops.py index b0793fcf..1cfefad1 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder/ops.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder/ops.py @@ -254,10 +254,7 @@ class KITSU_OT_build_new_shot(bpy.types.Operator): # Save File if self.save_file: save_shot_builder_file(file_path=shot_file_path_str) - print("Create shot with the following details") # TODO Remove - print(f"Seq Name: '{seq.name}' Seq ID: '{self.seq_id}'") - print(f"Shot Name: '{shot.name}' Shot ID: '{self.shot_id}'") - print(f"Task Type Name: '{task_type.name}' Task Type ID: `{self.task_type}`") + self.report( {"INFO"}, f"Successfully Built Shot:`{shot.name}` Task: `{task_type.name}`" ) -- 2.30.2 From 037fb753594134f3e9cc8ab4d36dacce995ac9f7 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Thu, 21 Dec 2023 14:23:20 -0500 Subject: [PATCH 51/55] Cleaar old TODOs --- scripts-blender/addons/blender_kitsu/shot_builder/ops.py | 5 +---- .../addons/blender_kitsu/shot_builder/template.py | 9 --------- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/ops.py b/scripts-blender/addons/blender_kitsu/shot_builder/ops.py index 1cfefad1..d0c95df3 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder/ops.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder/ops.py @@ -214,9 +214,6 @@ class KITSU_OT_build_new_shot(bpy.types.Operator): set_resolution_and_fps(active_project, scene) set_frame_range(shot, scene) - # File Path - # TODO Only run if saving file - # Set Render Settings if task_type_short_name == 'anim': # TODO get anim from a constant instead set_render_engine(context.scene, 'BLENDER_WORKBENCH') @@ -225,7 +222,7 @@ class KITSU_OT_build_new_shot(bpy.types.Operator): # Create Output Collection & Link Camera if bkglobals.OUTPUT_COL_CREATE.get(task_type_short_name): - output_col = create_task_type_output_collection( # TODO imporve + output_col = create_task_type_output_collection( context.scene, shot, task_type ) if task_type_short_name == 'anim': diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/template.py b/scripts-blender/addons/blender_kitsu/shot_builder/template.py index 5d83a841..f7fb122b 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder/template.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder/template.py @@ -19,15 +19,6 @@ def get_template_for_task_type(task_type_short_name: str) -> Path: return file -def open_template_for_task_type(task_type_short_name: str) -> bool: - # TODO THIS DOESN'T WORK BECAUSE CHANGE THE OPEN FILE MESSES UP THE CONTEXT SOMEHOW - file_path = get_template_for_task_type(task_type_short_name) - if file_path.exists() and file_path is not None: - bpy.ops.wm.open_mainfile('EXEC_DEFAULT', filepath=file_path.__str__()) - return True - return False - - def replace_workspace_with_template( context: bpy.types.Context, task_type_short_name: str ): -- 2.30.2 From a2efddf4b5822f61f9a7d76bf02d5e7d5ad1cf7b Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Thu, 21 Dec 2023 14:25:26 -0500 Subject: [PATCH 52/55] Remove Temp UI Panel --- .../blender_kitsu/shot_builder/__init__.py | 4 +--- .../addons/blender_kitsu/shot_builder/ui.py | 24 ------------------- 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/__init__.py b/scripts-blender/addons/blender_kitsu/shot_builder/__init__.py index 987cb692..fb9bf211 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder/__init__.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder/__init__.py @@ -1,15 +1,13 @@ import bpy -from . import ops, ui +from . import ops from .ui import topbar_file_new_draw_handler def register(): bpy.types.TOPBAR_MT_file_new.append(topbar_file_new_draw_handler) ops.register() - ui.register() def unregister(): bpy.types.TOPBAR_MT_file_new.remove(topbar_file_new_draw_handler) ops.unregister() - ui.unregister() diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/ui.py b/scripts-blender/addons/blender_kitsu/shot_builder/ui.py index caa65303..6b0c701b 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder/ui.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder/ui.py @@ -5,27 +5,3 @@ from typing import Any def topbar_file_new_draw_handler(self: Any, context: bpy.types.Context) -> None: layout = self.layout op = layout.operator("kitsu.build_new_shot", text="Shot File") - - -class KITSU_PT_new_shot_panel(bpy.types.Panel): # TODO Remove (for testing only) - bl_space_type = "VIEW_3D" - bl_region_type = "UI" - bl_label = "New Shot" - bl_category = "New Shot" - - def draw(self, context): - self.layout.operator("kitsu.build_new_shot") - self.layout.operator("kitsu.save_shot_builder_hooks") - - -classes = (KITSU_PT_new_shot_panel,) - - -def register(): - for cls in classes: - bpy.utils.register_class(cls) - - -def unregister(): - for cls in reversed(classes): - bpy.utils.unregister_class(cls) -- 2.30.2 From 23cf3c521c2618f1af02d2f01153d05e38ed21e3 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Thu, 21 Dec 2023 14:25:50 -0500 Subject: [PATCH 53/55] Add Description to `KITSU_OT_build_new_shot` --- scripts-blender/addons/blender_kitsu/shot_builder/ops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/ops.py b/scripts-blender/addons/blender_kitsu/shot_builder/ops.py index d0c95df3..b1b9cdba 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder/ops.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder/ops.py @@ -110,7 +110,7 @@ class KITSU_OT_save_shot_builder_hooks(bpy.types.Operator): class KITSU_OT_build_new_shot(bpy.types.Operator): bl_idname = "kitsu.build_new_shot" bl_label = "Build New Shot" - bl_description = "" # TODO Description + bl_description = "Build a New Shot file, based on infromation from KITSU Server" bl_options = {"REGISTER"} _timer = None -- 2.30.2 From 9ee761769b1d6923766c59ff479fb82821506ea9 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Thu, 21 Dec 2023 14:26:25 -0500 Subject: [PATCH 54/55] Clear old TODOs --- scripts-blender/addons/blender_kitsu/shot_builder/hooks.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/shot_builder/hooks.py b/scripts-blender/addons/blender_kitsu/shot_builder/hooks.py index 3a1a9e8d..4496f101 100644 --- a/scripts-blender/addons/blender_kitsu/shot_builder/hooks.py +++ b/scripts-blender/addons/blender_kitsu/shot_builder/hooks.py @@ -103,9 +103,7 @@ class Hooks: shot_builder_config_dir = root_dir.joinpath("pro/assets/scripts/shot-builder") if not shot_builder_config_dir.exists(): raise Exception("Shot Builder Hooks directory does not exist") - paths = [ - shot_builder_config_dir.resolve().__str__() # TODO Make variable path - ] # TODO Set path to where hooks are stored + paths = [shot_builder_config_dir.resolve().__str__()] with SystemPathInclude(paths) as _include: try: import hooks as production_hooks -- 2.30.2 From 1b5bc01a65235ff4006b4989fa00f37a50a5e128 Mon Sep 17 00:00:00 2001 From: Nick Alberelli Date: Thu, 21 Dec 2023 17:34:50 -0500 Subject: [PATCH 55/55] Update README with new Shot Building Instructions --- .../addons/blender_kitsu/README.md | 336 +++++------------- 1 file changed, 93 insertions(+), 243 deletions(-) diff --git a/scripts-blender/addons/blender_kitsu/README.md b/scripts-blender/addons/blender_kitsu/README.md index 72cb9827..249faba2 100644 --- a/scripts-blender/addons/blender_kitsu/README.md +++ b/scripts-blender/addons/blender_kitsu/README.md @@ -7,28 +7,45 @@ blender-kitsu is a Blender Add-on to interact with Kitsu from within Blender. It - [Table of Contents](#table-of-contents) - [Installation](#installation) - [How to get started](#how-to-get-started) + - [**Setup Login Data**](#setup-login-data) + - [**Setup Project Settings**](#setup-project-settings) + - [**Setup Animation Tools**](#setup-animation-tools) + - [**Setup Lookdev Tools**](#setup-lookdev-tools) + - [**Setup Media Search Paths**](#setup-media-search-paths) + - [**Setup Miscellaneous**](#setup-miscellaneous) - [Features](#features) - - [Sequence Editor](#sequence-editor) - - [Metadata](#metadata) - - [Push](#push) - - [Pull](#pull) - - [Multi Edit](#multi-edit) - - [Shot as Image Sequence](#shot-as-image-sequence) - - [General Sequence Editor Tools](#general-sequence-editor-tools) - - [Context](#context) - - [Animation Tools](#animation-tools) - - [Lookdev Tools](#lookdev-tools) - - [Error System](#error-system) + - [Sequence Editor](#sequence-editor) + - [Metastrips](#metastrips) + - [Create a Metastrip](#create-a-metastrip) + - [Initialize a Shot](#initialize-a-shot) + - [Metadata](#metadata) + - [Push](#push) + - [Pull](#pull) + - [Multi Edit](#multi-edit) + - [Shot as Image Sequence](#shot-as-image-sequence) + - [Advanced Settings](#advanced-settings) + - [General Sequence Editor Tools](#general-sequence-editor-tools) + - [Context](#context) + - [Animation Tools](#animation-tools) + - [Lookdev Tools](#lookdev-tools) + - [Error System](#error-system) - [Shot Builder](#shot-builder) + - [Features](#features) + - [Getting Started](#getting-started) + - [Shot Setup](#shot-setup) + - [Asset Setup](#asset-setup) + - [Kitsu Server](#kitsu-server) + - [Asset Index](#asset-index) + - [Example asset_index.json](#example-asset_indexjson) + - [Hooks Setup](#hooks-setup) + - [Editorial Exports](#editorial-exports) + - [Run Shot Builder](#run-shot-builder) - [Development](#development) - [Update Dependencies](#update-dependencies) - [Troubleshoot](#troubleshoot) - [Credits](#credits) -blender-kitsu is a Blender Add-on to interact with Kitsu from within Blender. It also has features that are not directly related to Kitsu but support certain aspects of the Blender Studio Pipeline. - -[Blender-Kitsu blogpost](https://studio.blender.org/blog/kitsu-addon-for-blender/) ## Table of Contents @@ -274,250 +291,83 @@ blender-kitsu has different checks that are performed during file load or during ![image info](/media/addons/blender_kitsu/error_animation.jpg) ## Shot Builder -Shot Builder is an Add-on that helps studios to work with task specific -Blend-files. The shot builder is part of the shot-tools repository. The main functionalities are +Shot Builder is a Feature of the Blender Kitsu Add-on To automatically build shot files, using the data from Kitsu server and the file structures defined on the [Blender Studio](https://studio.blender.org/pipeline/naming-conventions/svn-folder-structure) website. -* Build blend files for a specific task and shot. -* Sync data back from work files to places like kitsu, or `edit.blend`. +### Features + - Saves a 'Shot' File for each of the Shot's Task Types on Kitsu Server. + - Automatically Names Scenes based on Shot and Task Type names + - Creates output collections for certain Task Types (anim, fx, layout, lighting, previz, storyboard) + - Links output collections between Task Types based on [Shot Assembly](https://studio.blender.org/pipeline/pipeline-overview/shot-production/shot-assembly) specifications + - Loads Editorial Export (defined in preferences) into Shot file's VSE area (if available) + - Loads Assets via `asset_index.json` file stored at `your_production/svn/pro/assets/asset_index.json` + - Executes a `hook.py` file stored at `your_production/svn/pro/assets/shot-builder/hooks.py` -### Design Principles +### Getting Started +#### Shot Setup +The Shot Builder requires shot data including Name, Frame Rate, and Duration to be stored on a Kitsu Server. Please follow the [Sequence Editor](#sequence-editor) guide to create metastrips and Push that data to the Kitsu server or follow the [Kitsu First Production](https://kitsu.cg-wire.com/first_production/) to manually enter this data into the Kitsu Server. -The main design principles are: +#### Asset Setup +##### Kitsu Server +The Shot Builder requires all Asset to be stored on the Kitsu Server with a [Metadata Column](https://kitsu.cg-wire.com/production_advanced/#create-custom-metadata-columns) with the exact name `slug` exactly matching the name of the asset's collection. -* The core-tool can be installed as an add-on, but the (production specific) - configuration should be part of the production repository. -* The configuration files are a collection of python files. The API between - the configuration files and the add-on should be easy to use as pipeline - TDs working on the production should be able to work with it. -* TDs/artists should be able to handle issues during building without looking - at how the add-on is structured. -* The tool contains connectors that can be configured to read/write data - from the system/file that is the main location of the data. For example - The start and end time of a shot could be stored in an external production tracking application. +Assets needs to be associated with each shot in your production. Please follow the [Kitsu Breakdown](https://kitsu.cg-wire.com/getting-started-production/) guide to Cast your assets to shots. -### Connectors +##### Asset Index +To match Assets File to the casting breakdown on the Kitsu server, we need to create an Asset Index. This is a json file that contains the mapping of the asset's name to the asset's filepath. Any collection Marked as an Asset in Blender in the directory `your_project/svn/pro/assets` will be added to this index. -Connectors are components that can be used to read or write to files or -systems. The connectors will add flexibility to the add-on so it could be used -in multiple productions or studios. - -In the configuration files the TD can setup the connectors that are used for -the production. Possible connectors would be: - -* Connector for text based config files (json/yaml). -* Connector for kitsu (https://www.cg-wire.com/en/kitsu.html). -* Connector for blend files. - -### Layering & Hooks - -The configuration of the tool is layered. When building a work file for a sequence -there are multiple ways to change the configuration. - -* Configuration for the production. -* Configuration for the asset that is needed. -* Configuration for the asset type of the loaded asset. -* Configuration for the sequence. -* Configuration for the shot. -* Configuration for the task type. - -For any combination of these configurations hooks can be defined. - -``` -@shot_tools.hook(match_asset_name='Spring', match_shot_code='02_020A') -def hook_Spring_02_020A(asset: shot_tools.Asset, shot: shot_tools.Shot, **kwargs) -> None: - """ - Specific overrides when Spring is loaded in 02_020A. - """ - -@shot_tools.hook(match_task_type='anim') -def hook_task_anim(task: shot_tools.Task, shot: shot_tools.Shot, **kwargs) -> None: - """ - Specific overrides for any animation task. - """ -``` - -#### Data - -All hooks must have Python’s `**kwargs` parameter. The `kwargs` contains -the context at the moment the hook is invoked. The context can contain the -following items. - -* `production`: `shot_tools.Production`: Include the name of the production - and the location on the filesystem. -* `task`: `shot_tools.Task`: The task (combination of task_type and shot) -* `task_type`: `shot_tools.TaskType`: Is part of the `task`. -* `sequence`: `shot_tools.Sequence`: Is part of `shot`. -* `shot`: `shot_tools.Shot` Is part of `task`. -* `asset`: `shot_tools.Asset`: Only available during asset loading phase. -* `asset_type`: `shot_tools.AssetType`: Only available during asset loading phase. - -#### Execution Order - -The add-on will internally create a list containing the hooks that needs to be -executed for the command in a sensible order. It will then execute them in that -order. - -By default the next order will be used: - -* Production wide hooks -* Asset Type hooks -* Asset hooks -* Sequence hooks -* Shot hooks -* Task type hooks - -A hook with a single ‘match’ rule will be run in the corresponding phase. A hook with -multiple ‘match’ rules will be run in the last matching phase. For example, a hook with -‘asset’ and ‘task type’ match rules will be run in the ‘task type’ phase. - -###### Events - -Order of execution can be customized by adding the optional `run_before` -or `run_after` parameters. - -``` -@shot_tools.hook(match_task_type='anim', - requires={shot_tools.events.AssetsLoaded, hook_task_other_anim}, - is_required_by={shot_tools.events.ShotOverrides}) -def hook_task_anim(task: shot_tools.Task, shot: shot_tools.Shot, **kwargs) -> None: - """ - Specific overrides for any animation task run after all assets have been loaded. - """ -``` - -Events could be: - -* `shot_tools.events.BuildStart` -* `shot_tools.events.ProductionSettingsLoaded` -* `shot_tools.events.AssetsLoaded` -* `shot_tools.events.AssetTypeOverrides` -* `shot_tools.events.SequenceOverrides` -* `shot_tools.events.ShotOverrides` -* `shot_tools.events.TaskTypeOverrides` -* `shot_tools.events.BuildFinished` -* `shot_tools.events.HookStart` -* `shot_tools.events.HookEnd` - -During usage we should see which one of these or other events are needed. - -`shot_tools.events.BuildStart`, `shot_tools.events.ProductionSettingsLoaded` -and `shot_tools.events.HookStart` can only be used in the `run_after` -parameter. `shot_tools.events.BuildFinished`, `shot_tools.events.HookFinished` -can only be used in the `run_before` parameter. - - -### API - -The shot builder has an API between the add-on and the configuration files. This -API contains convenience functions and classes to hide complexity and makes -sure that the configuration files are easy to maintain. - -``` -register_task_type(task_type="anim") -register_task_type(task_type="lighting") -``` - -``` -# shot_tool/characters.py -class Asset(shot_tool.some_module.Asset): - asset_file = "/{asset_type}/{name}/{name}.blend" - collection = “{class_name}” - name = “{class_name}” - -class Character(Asset): - asset_type = ‘char’ - - -class Ellie(Character): - collection = “{class_name}-{variant_name}” - variants = {‘default’, ‘short_hair’} - -class Victoria(Character): pass -class Rex(Character): pass - -# shot_tool/shots.py -class Shot_01_020_A(shot_tool.some_module.Shot): - shot_id = ‘01_020_A’ - assets = { - characters.Ellie(variant=”short_hair”), - characters.Rex, - sets.LogOverChasm, +##### Example `asset_index.json` +```json +{ + "CH-rain": { + "type": "Collection", + "filepath": "your_project/svn/pro/assets/chars/rain/rain.blend" + }, + "CH-snow": { + "type": "Collection", + "filepath": "your_project/svn/pro/assets/chars/snow/snow.blend" } - -class AllHumansShot(shot_tool.some_module.Shot): - assets = { - characters.Ellie(variant=”short_hair”), - characters.Rex, - characters.Victoria, - } - -class Shot_01_035_A(AllHumansShot): - assets = { - sets.Camp, - } - +} ``` -This API is structured/implemented in a way that it keeps track of what -is being done. This will be used when an error occurs so a descriptive -error message can be generated that would help the TD to solve the issue more -quickly. The goal would be that the error messages are descriptive enough to -direct the TD into the direction where the actual cause is. And when possible -propose several solutions to fix it. +To create/update the Asset Index: +1. Enter Asset Index directory `cd blender-studio-pipeline/scripts/index_assets` +2. Run using `./run_index_assets.py your_poduction` replace `your_production` with the path to your project's root directory +3. This will create an index file at `your_production/svn/pro/assets/asset_index.py` -### Setting up the tool +#### Hooks Setup +Shot Builder uses hooks to extend the functionality of the shot builder. To create a hook file +1. Open `Edit>Preferences>Add-Ons` +2. Search for the `Blender Kitsu` Add-On +3. In the `Blender Kitsu` Add-On preferences find the `Shot Builder` section +4. Run the Operator `Save Shot Builder Hook File` +5. Edit the file `your_project/svn/pro/assets/scripts/shot-builder/hooks.py` to customize your hooks. -The artist/TD can configure their current local project directory in the add-on preferences. -This can then be used for new blend files. The project associated with an opened (so existing) -blend file can be found automatically by iterating over parent directories until a Shot Builder -configuration file is found. Project-specific settings are not configured/stored in the add-on, -but in this configuration file. - -The add-on will look in the root of the production repository to locate the -main configuration file `/project_root_directory/pro/shot-builder/config.py`. This file contains general -settings about the production, including: - -* The name of the production for reporting back to the user when needed. -* Naming standards to test against when reporting deviations. -* Location of other configuration (`tasks.py`, `assets.py`) relative to the `shot-builder` directory of the production. -* Configuration of the needed connectors. - -#### Directory Layout -``` bash -└── project-name/ # Project Root Directory - └── pro/ - ├── assets/ - ├── shot-builder/ - │ ├── assets.py - │ ├── config.py - │ ├── hooks.py - │ └── shots.py - └── shots/ ``` +Arguments to use in hooks + scene: bpy.types.Scene # current scene + shot: Shot class from blender_kitsu.types.py + prod_path: str # path to production root dir (your_project/svn/) + shot_path: str # path to shot file (your_project/svn/pro/shots/{sequence_name}/{shot_name}/{shot_task_name}.blend) + +Notes + matching_task_type = ['anim', 'lighting', 'fx', 'comp'] # either use list or just one string + output_col_name = shot.get_output_collection_name(task_type_short_name="anim") -### Usage +``` +#### Editorial Exports +Shot Builder can load Exports from Editorial to the .blend's VSE for reference. -Any artist can open a shot file via the `File` menu. A modal panel appears -where the user can select the task type and sequence/shot. When the file -already exists, it will be opened. When the file doesn't exist, the file -will be built. +1. Open `Edit>Preferences>Add-Ons` +2. Search for the `Blender Kitsu` Add-On +3. In the `Blender Kitsu` Add-On preferences find the `Shot Builder` section +4. Set your `Editorial Export Directory` to `your_project/shared/editorial/export/` +5. Set your `Editorial File Pattern` to `your_project_v\d\d\d.mp4` where `\d` represents a digit. This pattern matches a file named `your_movie_v001.mp4`. -In the future other use cases will also be accessible, such as: - -* Syncing data back from a work file to the source of the data. -* Report of errors/differences between the shot file and the configuration. - -### Open Issues - -#### Security - -* Security keys needed by connectors need to be stored somewhere. The easy - place is to place inside the production repository, but that isn't secure - Anyone with access to the repository could misuse the keys to access the - connector. Other solution might be to use the OS key store or retrieve the - keys from an online service authenticated by the blender cloud add-on. - - We could use `keyring` to access OS key stores. +#### Run Shot Builder +1. Open Blender +2. Select File>New +3. From dialogue box, select the desired Sequence/Shot/Task Type +4. Hit `ok` to run the tool. The tool will create a new file in the directory `your_project/svn/pro/shots/{sequence_name}/{shot_name}/{shot_name}+{task_type_name}.blend` ## Development ### Update Dependencies -- 2.30.2