diff --git a/blender_kitsu/__init__.py b/blender_kitsu/__init__.py index 770c0cfa..b8494292 100644 --- a/blender_kitsu/__init__.py +++ b/blender_kitsu/__init__.py @@ -20,6 +20,7 @@ import bpy from blender_kitsu import ( + shot_builder, lookdev, bkglobals, types, @@ -92,6 +93,7 @@ def register(): # tasks.register() playblast.register() anim.register() + shot_builder.register() LoggerLevelManager.configure_levels() logger.info("Registered blender-kitsu") @@ -109,6 +111,7 @@ def unregister(): prefs.unregister() lookdev.unregister() playblast.unregister() + shot_builder.unregister() LoggerLevelManager.restore_levels() diff --git a/blender_kitsu/prefs.py b/blender_kitsu/prefs.py index e4eb2a78..5eb158bb 100644 --- a/blender_kitsu/prefs.py +++ b/blender_kitsu/prefs.py @@ -285,6 +285,15 @@ class KITSU_addon_preferences(bpy.types.AddonPreferences): type=KITSU_media_update_search_paths ) + production_path: bpy.props.StringProperty( # type: ignore + name="Production Root", + description="The location to load configuration files from when " + "they couldn't be found in any parent folder of the current " + "file. Folder must contain a sub-folder named `shot-builder` " + "that holds the configuration files", + subtype='DIR_PATH', + ) + session: Session = Session() tasks: bpy.props.CollectionProperty(type=KITSU_task) diff --git a/shot_builder/__init__.py b/blender_kitsu/shot_builder/__init__.py similarity index 72% rename from shot_builder/__init__.py rename to blender_kitsu/shot_builder/__init__.py index e282903b..cb825046 100644 --- a/shot_builder/__init__.py +++ b/blender_kitsu/shot_builder/__init__.py @@ -1,58 +1,56 @@ -# ##### 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.ui import * -from shot_builder.connectors.kitsu import * -from shot_builder.operators import * -from shot_builder.properties import * -import bpy - -# 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, - ShotBuilderPreferences, - SHOTBUILDER_OT_NewShotFile, -) - - -def register(): - for cls in classes: - bpy.utils.register_class(cls) - bpy.types.TOPBAR_MT_file_new.append(topbar_file_new_draw_handler) - - -def unregister(): - bpy.types.TOPBAR_MT_file_new.remove(topbar_file_new_draw_handler) - for cls in classes: - bpy.utils.unregister_class(cls) +# ##### 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 blender_kitsu.shot_builder.ui import * +from blender_kitsu.shot_builder.connectors.kitsu import * +from blender_kitsu.shot_builder.operators import * +import bpy + +# 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, +) + + +def register(): + for cls in classes: + bpy.utils.register_class(cls) + bpy.types.TOPBAR_MT_file_new.append(topbar_file_new_draw_handler) + + +def unregister(): + bpy.types.TOPBAR_MT_file_new.remove(topbar_file_new_draw_handler) + for cls in classes: + bpy.utils.unregister_class(cls) diff --git a/shot_builder/asset.py b/blender_kitsu/shot_builder/asset.py similarity index 100% rename from shot_builder/asset.py rename to blender_kitsu/shot_builder/asset.py diff --git a/shot_builder/builder/__init__.py b/blender_kitsu/shot_builder/builder/__init__.py similarity index 81% rename from shot_builder/builder/__init__.py rename to blender_kitsu/shot_builder/builder/__init__.py index 1aa70b6b..056ea561 100644 --- a/shot_builder/builder/__init__.py +++ b/blender_kitsu/shot_builder/builder/__init__.py @@ -1,13 +1,13 @@ -from shot_builder.project import Production -from shot_builder.task_type import TaskType -from shot_builder.asset import Asset, AssetRef -from shot_builder.builder.build_step import BuildStep, BuildContext -from shot_builder.builder.init_asset import InitAssetStep -from shot_builder.builder.init_shot import InitShotStep -from shot_builder.builder.set_render_settings import SetRenderSettingsStep -from shot_builder.builder.new_scene import NewSceneStep -from shot_builder.builder.invoke_hook import InvokeHookStep -from shot_builder.builder.save_file import SaveFileStep +from blender_kitsu.shot_builder.project import Production +from blender_kitsu.shot_builder.task_type import TaskType +from blender_kitsu.shot_builder.asset import Asset, AssetRef +from blender_kitsu.shot_builder.builder.build_step import BuildStep, BuildContext +from blender_kitsu.shot_builder.builder.init_asset import InitAssetStep +from blender_kitsu.shot_builder.builder.init_shot import InitShotStep +from blender_kitsu.shot_builder.builder.set_render_settings import SetRenderSettingsStep +from blender_kitsu.shot_builder.builder.new_scene import NewSceneStep +from blender_kitsu.shot_builder.builder.invoke_hook import InvokeHookStep +from blender_kitsu.shot_builder.builder.save_file import SaveFileStep import bpy diff --git a/shot_builder/builder/build_step.py b/blender_kitsu/shot_builder/builder/build_step.py similarity index 77% rename from shot_builder/builder/build_step.py rename to blender_kitsu/shot_builder/builder/build_step.py index dc873546..9bcbf343 100644 --- a/shot_builder/builder/build_step.py +++ b/blender_kitsu/shot_builder/builder/build_step.py @@ -1,11 +1,11 @@ import bpy import typing -from shot_builder.project import Production -from shot_builder.shot import Shot -from shot_builder.task_type import TaskType -from shot_builder.render_settings import RenderSettings -from shot_builder.asset import Asset +from blender_kitsu.shot_builder.project import Production +from blender_kitsu.shot_builder.shot import Shot +from blender_kitsu.shot_builder.task_type import TaskType +from blender_kitsu.shot_builder.render_settings import RenderSettings +from blender_kitsu.shot_builder.asset import Asset class BuildContext: diff --git a/shot_builder/builder/init_asset.py b/blender_kitsu/shot_builder/builder/init_asset.py similarity index 70% rename from shot_builder/builder/init_asset.py rename to blender_kitsu/shot_builder/builder/init_asset.py index 962be600..65267f84 100644 --- a/shot_builder/builder/init_asset.py +++ b/blender_kitsu/shot_builder/builder/init_asset.py @@ -1,7 +1,7 @@ -from shot_builder.builder.build_step import BuildStep, BuildContext -from shot_builder.asset import * -from shot_builder.project import * -from shot_builder.shot import * +from blender_kitsu.shot_builder.builder.build_step import BuildStep, BuildContext +from blender_kitsu.shot_builder.asset import * +from blender_kitsu.shot_builder.project import * +from blender_kitsu.shot_builder.shot import * import bpy diff --git a/shot_builder/builder/init_shot.py b/blender_kitsu/shot_builder/builder/init_shot.py similarity index 60% rename from shot_builder/builder/init_shot.py rename to blender_kitsu/shot_builder/builder/init_shot.py index 557e43bf..ff2cc8ef 100644 --- a/shot_builder/builder/init_shot.py +++ b/blender_kitsu/shot_builder/builder/init_shot.py @@ -1,7 +1,7 @@ -from shot_builder.builder.build_step import BuildStep, BuildContext -from shot_builder.asset import * -from shot_builder.project import * -from shot_builder.shot import * +from blender_kitsu.shot_builder.builder.build_step import BuildStep, BuildContext +from blender_kitsu.shot_builder.asset import * +from blender_kitsu.shot_builder.project import * +from blender_kitsu.shot_builder.shot import * import bpy diff --git a/shot_builder/builder/invoke_hook.py b/blender_kitsu/shot_builder/builder/invoke_hook.py similarity index 75% rename from shot_builder/builder/invoke_hook.py rename to blender_kitsu/shot_builder/builder/invoke_hook.py index 15fcd45b..797c442d 100644 --- a/shot_builder/builder/invoke_hook.py +++ b/blender_kitsu/shot_builder/builder/invoke_hook.py @@ -1,5 +1,5 @@ -from shot_builder.builder.build_step import BuildStep, BuildContext -from shot_builder.hooks import HookFunction +from blender_kitsu.shot_builder.builder.build_step import BuildStep, BuildContext +from blender_kitsu.shot_builder.hooks import HookFunction import bpy import typing diff --git a/shot_builder/builder/new_scene.py b/blender_kitsu/shot_builder/builder/new_scene.py similarity index 85% rename from shot_builder/builder/new_scene.py rename to blender_kitsu/shot_builder/builder/new_scene.py index c3024c40..502bf51d 100644 --- a/shot_builder/builder/new_scene.py +++ b/blender_kitsu/shot_builder/builder/new_scene.py @@ -1,5 +1,5 @@ -from shot_builder.builder.build_step import BuildStep, BuildContext -from shot_builder.render_settings import RenderSettings +from blender_kitsu.shot_builder.builder.build_step import BuildStep, BuildContext +from blender_kitsu.shot_builder.render_settings import RenderSettings import bpy import logging diff --git a/shot_builder/builder/save_file.py b/blender_kitsu/shot_builder/builder/save_file.py similarity index 69% rename from shot_builder/builder/save_file.py rename to blender_kitsu/shot_builder/builder/save_file.py index a167d135..0122c915 100644 --- a/shot_builder/builder/save_file.py +++ b/blender_kitsu/shot_builder/builder/save_file.py @@ -1,7 +1,7 @@ -from shot_builder.builder.build_step import BuildStep, BuildContext -from shot_builder.asset import * -from shot_builder.project import * -from shot_builder.shot import * +from blender_kitsu.shot_builder.builder.build_step import BuildStep, BuildContext +from blender_kitsu.shot_builder.asset import * +from blender_kitsu.shot_builder.project import * +from blender_kitsu.shot_builder.shot import * import pathlib import bpy diff --git a/shot_builder/builder/set_render_settings.py b/blender_kitsu/shot_builder/builder/set_render_settings.py similarity index 91% rename from shot_builder/builder/set_render_settings.py rename to blender_kitsu/shot_builder/builder/set_render_settings.py index f82753fb..a18cb23a 100644 --- a/shot_builder/builder/set_render_settings.py +++ b/blender_kitsu/shot_builder/builder/set_render_settings.py @@ -1,4 +1,4 @@ -from shot_builder.builder.build_step import BuildStep, BuildContext +from blender_kitsu.shot_builder.builder.build_step import BuildStep, BuildContext import bpy import typing diff --git a/shot_builder/connectors/__init__.py b/blender_kitsu/shot_builder/connectors/__init__.py similarity index 100% rename from shot_builder/connectors/__init__.py rename to blender_kitsu/shot_builder/connectors/__init__.py diff --git a/shot_builder/connectors/connector.py b/blender_kitsu/shot_builder/connectors/connector.py similarity index 86% rename from shot_builder/connectors/connector.py rename to blender_kitsu/shot_builder/connectors/connector.py index 08e159c5..1222821a 100644 --- a/shot_builder/connectors/connector.py +++ b/blender_kitsu/shot_builder/connectors/connector.py @@ -21,16 +21,16 @@ This module contains the Connector class. It is an abstract base class for concrete connectors. """ -from shot_builder.shot import Shot, ShotRef -from shot_builder.asset import Asset, AssetRef -from shot_builder.task_type import TaskType -from shot_builder.render_settings import RenderSettings +from blender_kitsu.shot_builder.shot import Shot, ShotRef +from blender_kitsu.shot_builder.asset import Asset, AssetRef +from blender_kitsu.shot_builder.task_type import TaskType +from blender_kitsu.shot_builder.render_settings import RenderSettings from typing import * if TYPE_CHECKING: - from shot_builder.project import Production - from shot_builder.properties import ShotBuilderPreferences + from blender_kitsu.shot_builder.project import Production + from blender_kitsu.shot_builder.properties import ShotBuilderPreferences class Connector: @@ -58,8 +58,8 @@ class Connector: Example of using predefined connectors in a production config file: ```shot-builder/config.py - from shot_builder.connectors.default import DefaultConnector - from shot_builder.connectors.kitsu import KitsuConnector + from blender_kitsu.shot_builder.connectors.default import DefaultConnector + from blender_kitsu.shot_builder.connectors.kitsu import KitsuConnector PRODUCTION_NAME = DefaultConnector TASK_TYPES = KitsuConnector diff --git a/shot_builder/connectors/default.py b/blender_kitsu/shot_builder/connectors/default.py similarity index 83% rename from shot_builder/connectors/default.py rename to blender_kitsu/shot_builder/connectors/default.py index 6d55e942..9080090a 100644 --- a/shot_builder/connectors/default.py +++ b/blender_kitsu/shot_builder/connectors/default.py @@ -17,11 +17,11 @@ # ##### END GPL LICENSE BLOCK ##### # -from shot_builder.shot import Shot, ShotRef -from shot_builder.asset import Asset, AssetRef -from shot_builder.task_type import TaskType -from shot_builder.render_settings import RenderSettings -from shot_builder.connectors.connector import Connector +from blender_kitsu.shot_builder.shot import Shot, ShotRef +from blender_kitsu.shot_builder.asset import Asset, AssetRef +from blender_kitsu.shot_builder.task_type import TaskType +from blender_kitsu.shot_builder.render_settings import RenderSettings +from blender_kitsu.shot_builder.connectors.connector import Connector from typing import * diff --git a/shot_builder/connectors/kitsu.py b/blender_kitsu/shot_builder/connectors/kitsu.py similarity index 71% rename from shot_builder/connectors/kitsu.py rename to blender_kitsu/shot_builder/connectors/kitsu.py index 5caf8798..cb8858d9 100644 --- a/shot_builder/connectors/kitsu.py +++ b/blender_kitsu/shot_builder/connectors/kitsu.py @@ -18,13 +18,16 @@ # import bpy -from shot_builder import vars -from shot_builder.shot import Shot, ShotRef -from shot_builder.asset import Asset, AssetRef -from shot_builder.task_type import TaskType -from shot_builder.render_settings import RenderSettings -from shot_builder.connectors.connector import Connector +from blender_kitsu.shot_builder import vars +from blender_kitsu.shot_builder.shot import Shot, ShotRef +from blender_kitsu.shot_builder.asset import Asset, AssetRef +from blender_kitsu.shot_builder.task_type import TaskType +from blender_kitsu.shot_builder.render_settings import RenderSettings +from blender_kitsu.shot_builder.connectors.connector import Connector import requests +from blender_kitsu import cache +from blender_kitsu.gazu.asset import all_assets_for_shot +from blender_kitsu.gazu.shot import all_shots_for_project, all_sequences_for_project import typing import logging @@ -131,47 +134,9 @@ class KitsuConnector(Connector): def __init__(self, **kwargs): super().__init__(**kwargs) - self.__jwt_access_token = "" - self.__validate() - self.__authorize() - - def __validate(self) -> None: - self._preferences.kitsu._validate() - if not self._production.config.get('KITSU_PROJECT_ID'): - raise KitsuException( - "KITSU_PROJECT_ID is not configured in config.py") - - def __authorize(self) -> None: - kitsu_pref = self._preferences.kitsu - backend = kitsu_pref.backend - username = kitsu_pref.username - password = kitsu_pref.password - - logger.info(f"authorize {username} against {backend}") - response = requests.post( - url=f"{backend}/auth/login", data={'email': username, 'password': password}) - if response.status_code != 200: - self.__jwt_access_token = "" - raise KitsuException( - f"unable to authorize (status code={response.status_code})") - json_response = response.json() - self.__jwt_access_token = json_response['access_token'] - - def __api_get(self, api: str) -> typing.Any: - kitsu_pref = self._preferences.kitsu - backend = kitsu_pref.backend - - response = requests.get(url=f"{backend}{api}", headers={ - "Authorization": f"Bearer {self.__jwt_access_token}" - }) - if response.status_code != 200: - raise KitsuException( - f"unable to call kitsu (api={api}, status code={response.status_code})") - return response.json() def __get_production_data(self) -> KitsuProject: - project_id = self._production.config['KITSU_PROJECT_ID'] - production = self.__api_get(f"data/projects/{project_id}") + production = cache.project_active_get() project = KitsuProject(typing.cast( typing.Dict[str, typing.Any], production)) return project @@ -181,22 +146,23 @@ class KitsuConnector(Connector): return production.get_name() def get_task_types(self) -> typing.List[TaskType]: - task_types = self.__api_get(f"data/task_types/") + 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_id = self._production.config['KITSU_PROJECT_ID'] - kitsu_sequences = self.__api_get(f"data/projects/{project_id}/sequences") + project = cache.project_active_get() + kitsu_sequences = 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 = self.__api_get(f"data/projects/{project_id}/shots") - + kitsu_shots = all_shots_for_project(project.id) shots: typing.List[ShotRef] = [] for shot_data in kitsu_shots: @@ -232,8 +198,8 @@ class KitsuConnector(Connector): return shots def get_assets_for_shot(self, shot: Shot) -> typing.List[AssetRef]: - kitsu_assets = self.__api_get( - f"data/shots/{shot.kitsu_id}/assets") + kitsu_assets = all_assets_for_shot(shot.kitsu_id) + return [AssetRef(name=asset_data['name'], code=asset_data['code']) for asset_data in kitsu_assets] @@ -241,7 +207,5 @@ class KitsuConnector(Connector): """ Retrieve the render settings for the given shot. """ - kitsu_project = self.__get_production_data() - resolution = kitsu_project.get_resolution() - frames_per_second = shot.frames_per_second - return RenderSettings(width=resolution[0], height=resolution[1], frames_per_second=frames_per_second) + 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/shot_builder/README.md b/blender_kitsu/shot_builder/docs/README.md similarity index 96% rename from shot_builder/README.md rename to blender_kitsu/shot_builder/docs/README.md index f5b67716..a438b3ee 100644 --- a/shot_builder/README.md +++ b/blender_kitsu/shot_builder/docs/README.md @@ -1,233 +1,233 @@ -# 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 `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. - -## 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. +# 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 `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. + +## 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/shot_builder/docs/examples/shot-builder/README.md b/blender_kitsu/shot_builder/docs/examples/README.md similarity index 96% rename from shot_builder/docs/examples/shot-builder/README.md rename to blender_kitsu/shot_builder/docs/examples/README.md index 7b192c94..601eba80 100644 --- a/shot_builder/docs/examples/shot-builder/README.md +++ b/blender_kitsu/shot_builder/docs/examples/README.md @@ -1,4 +1,4 @@ -# 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. +# 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/shot_builder/docs/examples/shot-builder/assets.py b/blender_kitsu/shot_builder/docs/examples/assets.py similarity index 91% rename from shot_builder/docs/examples/shot-builder/assets.py rename to blender_kitsu/shot_builder/docs/examples/assets.py index 8b81843b..eb40b648 100644 --- a/shot_builder/docs/examples/shot-builder/assets.py +++ b/blender_kitsu/shot_builder/docs/examples/assets.py @@ -1,83 +1,83 @@ -from shot_builder.asset import Asset - - -class SpriteFrightAsset(Asset): - path = "{production.path}/lib/{asset.asset_type}/{asset.code}/{asset.code}.blend" - - -class Character(SpriteFrightAsset): - asset_type = "char" - collection = "CH-{asset.code}" - - -class Ellie(Character): - name = "Ellie" - code = "ellie" - - -class Victoria(Character): - name = "Victoria" - code = "victoria" - - -class Phil(Character): - name = "Phil" - code = "phil" - - -class Rex(Character): - name = "Rex" - code = "rex" - - -class Jay(Character): - name = "Jay" - code = "jay" - -# TODO: Bird character has no asset file yet. -# class Bird(Character): -# name = "Bird" -# code = "bird" - - -class Prop(SpriteFrightAsset): - asset_type = "props" - collection = "PR-{asset.code}" - - -class Boombox(Prop): - name = "Boombox" - code = "boombox" - - -class BBQGrill(Prop): - name = "BBQ Grill" - code = "bbq_grill" - - -# NOTE: NotepadAndPencil is a combined asset. In Kitsu it is defined as a single asset. In the production -# reportitory it is stored as 2 collections in a single file. See `hooks.link_char_prop_for_anim` -# where this is handled. -class NotepadAndPencil(Prop): - name = "Notepad and pencil" - code = "notepad_pencil" - - -class Binoculars(Prop): - name = "Binoculars (Ellie)" - code = "binoculars" - - -class Backpack(Prop): - name = "Backpack (Phil)" - code = "backpack" - - -class Set(SpriteFrightAsset): - asset_type = "sets" - collection = "SE-{asset.code}" - - -class MushroomGrove(Set): - name = "Mushroom grove" - code = "mushroom_grove" +from blender_kitsu.shot_builder.asset import Asset + + +class SpriteFrightAsset(Asset): + path = "{production.path}/lib/{asset.asset_type}/{asset.code}/{asset.code}.blend" + + +class Character(SpriteFrightAsset): + asset_type = "char" + collection = "CH-{asset.code}" + + +class Ellie(Character): + name = "Ellie" + code = "ellie" + + +class Victoria(Character): + name = "Victoria" + code = "victoria" + + +class Phil(Character): + name = "Phil" + code = "phil" + + +class Rex(Character): + name = "Rex" + code = "rex" + + +class Jay(Character): + name = "Jay" + code = "jay" + +# TODO: Bird character has no asset file yet. +# class Bird(Character): +# name = "Bird" +# code = "bird" + + +class Prop(SpriteFrightAsset): + asset_type = "props" + collection = "PR-{asset.code}" + + +class Boombox(Prop): + name = "Boombox" + code = "boombox" + + +class BBQGrill(Prop): + name = "BBQ Grill" + code = "bbq_grill" + + +# NOTE: NotepadAndPencil is a combined asset. In Kitsu it is defined as a single asset. In the production +# reportitory it is stored as 2 collections in a single file. See `hooks.link_char_prop_for_anim` +# where this is handled. +class NotepadAndPencil(Prop): + name = "Notepad and pencil" + code = "notepad_pencil" + + +class Binoculars(Prop): + name = "Binoculars (Ellie)" + code = "binoculars" + + +class Backpack(Prop): + name = "Backpack (Phil)" + code = "backpack" + + +class Set(SpriteFrightAsset): + asset_type = "sets" + collection = "SE-{asset.code}" + + +class MushroomGrove(Set): + name = "Mushroom grove" + code = "mushroom_grove" diff --git a/shot_builder/docs/examples/shot-builder/config.py b/blender_kitsu/shot_builder/docs/examples/config.py similarity index 80% rename from shot_builder/docs/examples/shot-builder/config.py rename to blender_kitsu/shot_builder/docs/examples/config.py index 3d3a8dde..bde157a3 100644 --- a/shot_builder/docs/examples/shot-builder/config.py +++ b/blender_kitsu/shot_builder/docs/examples/config.py @@ -1,14 +1,14 @@ -from shot_builder.connectors.kitsu import KitsuConnector - -PRODUCTION_NAME = KitsuConnector -SHOTS = KitsuConnector -ASSETS = KitsuConnector -RENDER_SETTINGS = KitsuConnector - -KITSU_PROJECT_ID = "fc77c0b9-bb76-41c3-b843-c9b156f9b3ec" - -# Formatting rules -# ---------------- - -# The name of the scene in blender where the shot is build in. -# SCENE_NAME_FORMAT = "{shot.sequence_code}_{shot.code}.{task_type}" +from blender_kitsu.shot_builder.connectors.kitsu import KitsuConnector + +PRODUCTION_NAME = KitsuConnector +SHOTS = KitsuConnector +ASSETS = KitsuConnector +RENDER_SETTINGS = KitsuConnector + +KITSU_PROJECT_ID = "fc77c0b9-bb76-41c3-b843-c9b156f9b3ec" + +# Formatting rules +# ---------------- + +# The name of the scene in blender where the shot is build in. +# SCENE_NAME_FORMAT = "{shot.sequence_code}_{shot.code}.{task_type}" diff --git a/shot_builder/docs/examples/shot-builder/hooks.py b/blender_kitsu/shot_builder/docs/examples/hooks.py similarity index 92% rename from shot_builder/docs/examples/shot-builder/hooks.py rename to blender_kitsu/shot_builder/docs/examples/hooks.py index d588dad3..b8af3a91 100644 --- a/shot_builder/docs/examples/shot-builder/hooks.py +++ b/blender_kitsu/shot_builder/docs/examples/hooks.py @@ -1,151 +1,151 @@ -import bpy -from shot_builder.hooks import hook, Wildcard -from shot_builder.asset import Asset -from shot_builder.shot import Shot -from shot_builder.project import Production - -import logging - -logger = logging.getLogger(__name__) - -# ---------- Global Hook ---------- - - -@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}/lib/cam/camera_rig.blend" - 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['CAM-camera'] - 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 - - _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=['char', '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, - ) +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 + +import logging + +logger = logging.getLogger(__name__) + +# ---------- Global Hook ---------- + + +@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}/lib/cam/camera_rig.blend" + 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['CAM-camera'] + 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 + + _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=['char', '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/blender_kitsu/shot_builder/docs/examples/shot-builder/README.md b/blender_kitsu/shot_builder/docs/examples/shot-builder/README.md new file mode 100644 index 00000000..0306e897 --- /dev/null +++ b/blender_kitsu/shot_builder/docs/examples/shot-builder/README.md @@ -0,0 +1,6 @@ +# 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/shot_builder/docs/examples/shot-builder/shots.py b/blender_kitsu/shot_builder/docs/examples/shots.py similarity index 84% rename from shot_builder/docs/examples/shot-builder/shots.py rename to blender_kitsu/shot_builder/docs/examples/shots.py index 898ed223..bc437772 100644 --- a/shot_builder/docs/examples/shot-builder/shots.py +++ b/blender_kitsu/shot_builder/docs/examples/shots.py @@ -1,29 +1,29 @@ -from shot_builder.shot import Shot -from shot_builder.project import Production - - -class SpriteFrightShot(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_output_collection_name(self, shot: Shot, task_type: str) -> str: - """ - Get the collection name where the output is stored. - """ - return f"{shot.sequence_code}_{shot.code}.{task_type}.output" - - -class Sequence_0002(SpriteFrightShot): - sequence_code = "0002" - - -class Shot_0001_0001_A(Sequence_0002): - name = "001" - code = "0001" +from blender_kitsu.shot_builder.shot import Shot +from blender_kitsu.shot_builder.project import Production + + +class SpriteFrightShot(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_output_collection_name(self, shot: Shot, task_type: str) -> str: + """ + Get the collection name where the output is stored. + """ + return f"{shot.sequence_code}_{shot.code}.{task_type}.output" + + +class Sequence_0002(SpriteFrightShot): + sequence_code = "0002" + + +class Shot_0001_0001_A(Sequence_0002): + name = "001" + code = "0001" diff --git a/shot_builder/hooks.py b/blender_kitsu/shot_builder/hooks.py similarity index 98% rename from shot_builder/hooks.py rename to blender_kitsu/shot_builder/hooks.py index b07b8df7..6b8188b0 100644 --- a/shot_builder/hooks.py +++ b/blender_kitsu/shot_builder/hooks.py @@ -78,7 +78,7 @@ class Hooks: def _register_hook(func: types.FunctionType) -> None: - from shot_builder.project import get_active_production + from blender_kitsu.shot_builder.project import get_active_production production = get_active_production() production.hooks.register(func) diff --git a/shot_builder/operators.py b/blender_kitsu/shot_builder/operators.py similarity index 79% rename from shot_builder/operators.py rename to blender_kitsu/shot_builder/operators.py index c8f83a97..544684c5 100644 --- a/shot_builder/operators.py +++ b/blender_kitsu/shot_builder/operators.py @@ -19,10 +19,11 @@ # from typing import * import bpy -from shot_builder.shot import ShotRef -from shot_builder.project import * -from shot_builder.builder import ShotBuilder -from shot_builder.task_type import TaskType +from blender_kitsu.shot_builder.shot import ShotRef +from blender_kitsu.shot_builder.project import ensure_loaded_production, get_active_production +from blender_kitsu.shot_builder.builder import ShotBuilder +from blender_kitsu.shot_builder.task_type import TaskType +from blender_kitsu import prefs, cache _production_task_type_items: List[Tuple[str, str, str]] = [] @@ -103,18 +104,34 @@ class SHOTBUILDER_OT_NewShotFile(bpy.types.Operator): ) 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() - production_root = get_production_root(context) - if production_root is None: + if addon_prefs.session.is_auth() is False: self.report( - {'WARNING'}, "Operator is cancelled due to inability to determine the production path. Make sure the a default path in configured in the preferences.") + {'ERROR'}, "Must be logged into Kitsu to continue. Check 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. Check 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. Check 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 + ensure_loaded_production(context) production = get_active_production() - self.production_root = str(production.path) - self.production_name = production.get_name(context=context) + + self.production_root = addon_prefs.project_root_dir + self.production_name = project.name global _production_task_type_items _production_task_type_items = production.get_task_type_items( diff --git a/shot_builder/project.py b/blender_kitsu/shot_builder/project.py similarity index 95% rename from shot_builder/project.py rename to blender_kitsu/shot_builder/project.py index 93906a14..d6de5442 100644 --- a/shot_builder/project.py +++ b/blender_kitsu/shot_builder/project.py @@ -23,15 +23,18 @@ from collections import defaultdict import bpy -from shot_builder.task_type import * -from shot_builder.shot import Shot, ShotRef -from shot_builder.render_settings import RenderSettings -from shot_builder.asset import Asset, AssetRef -from shot_builder.sys_utils import * -from shot_builder.hooks import Hooks, register_hooks +from blender_kitsu.shot_builder.task_type import * +from blender_kitsu.shot_builder.shot import Shot, ShotRef +from blender_kitsu.shot_builder.render_settings import RenderSettings +from blender_kitsu.shot_builder.asset import Asset, AssetRef +from blender_kitsu.shot_builder.sys_utils import * +from blender_kitsu.shot_builder.hooks import Hooks, register_hooks -from shot_builder.connectors.default import DefaultConnector -from shot_builder.connectors.connector import Connector +from blender_kitsu.shot_builder.connectors.default import DefaultConnector +from blender_kitsu.shot_builder.connectors.connector import Connector + +from blender_kitsu import prefs +from pathlib import Path from typing import * import types @@ -77,7 +80,7 @@ class Production: connector_cls: Type[Connector], context: bpy.types.Context) -> Connector: # TODO: Cache connector - preferences = context.preferences.addons[__package__].preferences + preferences = context.preferences.addons["blender_kitsu"].preferences return connector_cls(production=self, preferences=preferences) def __format_shot_name(self, shot: Shot) -> str: @@ -382,8 +385,9 @@ def get_production_root(context: bpy.types.Context) -> Optional[pathlib.Path]: production_root = _find_production_root(current_file) if production_root: return production_root - production_root = pathlib.Path( - context.preferences.addons[__package__].preferences.production_path) + + 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 diff --git a/shot_builder/render_settings.py b/blender_kitsu/shot_builder/render_settings.py similarity index 95% rename from shot_builder/render_settings.py rename to blender_kitsu/shot_builder/render_settings.py index f2c0b550..5b79a4a8 100644 --- a/shot_builder/render_settings.py +++ b/blender_kitsu/shot_builder/render_settings.py @@ -18,7 +18,7 @@ # -from shot_builder.asset import Asset +from blender_kitsu.shot_builder.asset import Asset from typing import * diff --git a/shot_builder/shot.py b/blender_kitsu/shot_builder/shot.py similarity index 100% rename from shot_builder/shot.py rename to blender_kitsu/shot_builder/shot.py diff --git a/shot_builder/sys_utils.py b/blender_kitsu/shot_builder/sys_utils.py similarity index 100% rename from shot_builder/sys_utils.py rename to blender_kitsu/shot_builder/sys_utils.py diff --git a/shot_builder/task_type.py b/blender_kitsu/shot_builder/task_type.py similarity index 100% rename from shot_builder/task_type.py rename to blender_kitsu/shot_builder/task_type.py diff --git a/shot_builder/ui.py b/blender_kitsu/shot_builder/ui.py similarity index 95% rename from shot_builder/ui.py rename to blender_kitsu/shot_builder/ui.py index 5d44b5e1..bfb62552 100644 --- a/shot_builder/ui.py +++ b/blender_kitsu/shot_builder/ui.py @@ -19,7 +19,7 @@ # import bpy from typing import * -from shot_builder.operators import * +from blender_kitsu.shot_builder.operators import * def topbar_file_new_draw_handler(self: Any, context: bpy.types.Context) -> None: diff --git a/shot_builder/vars.py b/blender_kitsu/shot_builder/vars.py similarity index 100% rename from shot_builder/vars.py rename to blender_kitsu/shot_builder/vars.py diff --git a/shot_builder/properties.py b/shot_builder/properties.py deleted file mode 100644 index ad822df4..00000000 --- a/shot_builder/properties.py +++ /dev/null @@ -1,57 +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 - -import pathlib - -from shot_builder.project import is_valid_production_root -from shot_builder.connectors.kitsu import KitsuPreferences - - -class ShotBuilderPreferences(bpy.types.AddonPreferences): - bl_idname = __package__ - - production_path: bpy.props.StringProperty( # type: ignore - name="Production Root", - description="The location to load configuration files from when " - "they couldn't be found in any parent folder of the current " - "file. Folder must contain a sub-folder named `shot-builder` " - "that holds the configuration files", - subtype='DIR_PATH', - ) - - kitsu: bpy.props.PointerProperty( # type: ignore - name="Kitsu Preferences", - type=KitsuPreferences - ) - - def draw(self, context: bpy.types.Context) -> None: - layout = self.layout - - is_valid = is_valid_production_root(pathlib.Path(self.production_path)) - layout.prop(self, "production_path", - icon='NONE' if is_valid else 'ERROR') - if not is_valid: - layout.label(text="Folder must contain a sub-folder named " - "`shot-builder` that holds the configuration " - "files.", - icon="ERROR") - sublayout = layout.box() - self.kitsu.draw(sublayout, context)