Merge shot_builder
into blender kitsu
#3
@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
from blender_kitsu import (
|
from blender_kitsu import (
|
||||||
|
shot_builder,
|
||||||
lookdev,
|
lookdev,
|
||||||
bkglobals,
|
bkglobals,
|
||||||
types,
|
types,
|
||||||
@ -92,6 +93,7 @@ def register():
|
|||||||
# tasks.register()
|
# tasks.register()
|
||||||
playblast.register()
|
playblast.register()
|
||||||
anim.register()
|
anim.register()
|
||||||
|
shot_builder.register()
|
||||||
|
|
||||||
LoggerLevelManager.configure_levels()
|
LoggerLevelManager.configure_levels()
|
||||||
logger.info("Registered blender-kitsu")
|
logger.info("Registered blender-kitsu")
|
||||||
@ -109,6 +111,7 @@ def unregister():
|
|||||||
prefs.unregister()
|
prefs.unregister()
|
||||||
lookdev.unregister()
|
lookdev.unregister()
|
||||||
playblast.unregister()
|
playblast.unregister()
|
||||||
|
shot_builder.unregister()
|
||||||
|
|
||||||
LoggerLevelManager.restore_levels()
|
LoggerLevelManager.restore_levels()
|
||||||
|
|
||||||
|
@ -285,6 +285,15 @@ class KITSU_addon_preferences(bpy.types.AddonPreferences):
|
|||||||
type=KITSU_media_update_search_paths
|
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()
|
session: Session = Session()
|
||||||
|
|
||||||
tasks: bpy.props.CollectionProperty(type=KITSU_task)
|
tasks: bpy.props.CollectionProperty(type=KITSU_task)
|
||||||
|
@ -1,58 +1,56 @@
|
|||||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or
|
# This program is free software; you can redistribute it and/or
|
||||||
# modify it under the terms of the GNU General Public License
|
# modify it under the terms of the GNU General Public License
|
||||||
# as published by the Free Software Foundation; either version 2
|
# as published by the Free Software Foundation; either version 2
|
||||||
# of the License, or (at your option) any later version.
|
# of the License, or (at your option) any later version.
|
||||||
#
|
#
|
||||||
# This program is distributed in the hope that it will be useful,
|
# This program is distributed in the hope that it will be useful,
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
# GNU General Public License for more details.
|
# GNU General Public License for more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program; if not, write to the Free Software Foundation,
|
# along with this program; if not, write to the Free Software Foundation,
|
||||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
#
|
#
|
||||||
# ##### END GPL LICENSE BLOCK #####
|
# ##### END GPL LICENSE BLOCK #####
|
||||||
|
|
||||||
# <pep8 compliant>
|
# <pep8 compliant>
|
||||||
|
|
||||||
from shot_builder.ui import *
|
from blender_kitsu.shot_builder.ui import *
|
||||||
from shot_builder.connectors.kitsu import *
|
from blender_kitsu.shot_builder.connectors.kitsu import *
|
||||||
from shot_builder.operators import *
|
from blender_kitsu.shot_builder.operators import *
|
||||||
from shot_builder.properties import *
|
import bpy
|
||||||
import bpy
|
|
||||||
|
# import logging
|
||||||
# import logging
|
# logging.basicConfig(level=logging.DEBUG)
|
||||||
# logging.basicConfig(level=logging.DEBUG)
|
|
||||||
|
|
||||||
|
# bl_info = {
|
||||||
bl_info = {
|
# 'name': 'Shot Builder',
|
||||||
'name': 'Shot Builder',
|
# "author": "Jeroen Bakker",
|
||||||
"author": "Jeroen Bakker",
|
# 'version': (0, 1),
|
||||||
'version': (0, 1),
|
# 'blender': (2, 90, 0),
|
||||||
'blender': (2, 90, 0),
|
# 'location': 'Addon Preferences panel and file new menu',
|
||||||
'location': 'Addon Preferences panel and file new menu',
|
# 'description': 'Shot builder production tool.',
|
||||||
'description': 'Shot builder production tool.',
|
# 'category': 'Studio',
|
||||||
'category': 'Studio',
|
# }
|
||||||
}
|
|
||||||
|
|
||||||
|
classes = (
|
||||||
classes = (
|
KitsuPreferences,
|
||||||
KitsuPreferences,
|
SHOTBUILDER_OT_NewShotFile,
|
||||||
ShotBuilderPreferences,
|
)
|
||||||
SHOTBUILDER_OT_NewShotFile,
|
|
||||||
)
|
|
||||||
|
def register():
|
||||||
|
for cls in classes:
|
||||||
def register():
|
bpy.utils.register_class(cls)
|
||||||
for cls in classes:
|
bpy.types.TOPBAR_MT_file_new.append(topbar_file_new_draw_handler)
|
||||||
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)
|
||||||
def unregister():
|
for cls in classes:
|
||||||
bpy.types.TOPBAR_MT_file_new.remove(topbar_file_new_draw_handler)
|
bpy.utils.unregister_class(cls)
|
||||||
for cls in classes:
|
|
||||||
bpy.utils.unregister_class(cls)
|
|
@ -1,13 +1,13 @@
|
|||||||
from shot_builder.project import Production
|
from blender_kitsu.shot_builder.project import Production
|
||||||
from shot_builder.task_type import TaskType
|
from blender_kitsu.shot_builder.task_type import TaskType
|
||||||
from shot_builder.asset import Asset, AssetRef
|
from blender_kitsu.shot_builder.asset import Asset, AssetRef
|
||||||
from shot_builder.builder.build_step import BuildStep, BuildContext
|
from blender_kitsu.shot_builder.builder.build_step import BuildStep, BuildContext
|
||||||
from shot_builder.builder.init_asset import InitAssetStep
|
from blender_kitsu.shot_builder.builder.init_asset import InitAssetStep
|
||||||
from shot_builder.builder.init_shot import InitShotStep
|
from blender_kitsu.shot_builder.builder.init_shot import InitShotStep
|
||||||
from shot_builder.builder.set_render_settings import SetRenderSettingsStep
|
from blender_kitsu.shot_builder.builder.set_render_settings import SetRenderSettingsStep
|
||||||
from shot_builder.builder.new_scene import NewSceneStep
|
from blender_kitsu.shot_builder.builder.new_scene import NewSceneStep
|
||||||
from shot_builder.builder.invoke_hook import InvokeHookStep
|
from blender_kitsu.shot_builder.builder.invoke_hook import InvokeHookStep
|
||||||
from shot_builder.builder.save_file import SaveFileStep
|
from blender_kitsu.shot_builder.builder.save_file import SaveFileStep
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
|
@ -1,11 +1,11 @@
|
|||||||
import bpy
|
import bpy
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
from shot_builder.project import Production
|
from blender_kitsu.shot_builder.project import Production
|
||||||
from shot_builder.shot import Shot
|
from blender_kitsu.shot_builder.shot import Shot
|
||||||
from shot_builder.task_type import TaskType
|
from blender_kitsu.shot_builder.task_type import TaskType
|
||||||
from shot_builder.render_settings import RenderSettings
|
from blender_kitsu.shot_builder.render_settings import RenderSettings
|
||||||
from shot_builder.asset import Asset
|
from blender_kitsu.shot_builder.asset import Asset
|
||||||
|
|
||||||
|
|
||||||
class BuildContext:
|
class BuildContext:
|
@ -1,7 +1,7 @@
|
|||||||
from shot_builder.builder.build_step import BuildStep, BuildContext
|
from blender_kitsu.shot_builder.builder.build_step import BuildStep, BuildContext
|
||||||
from shot_builder.asset import *
|
from blender_kitsu.shot_builder.asset import *
|
||||||
from shot_builder.project import *
|
from blender_kitsu.shot_builder.project import *
|
||||||
from shot_builder.shot import *
|
from blender_kitsu.shot_builder.shot import *
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
|
@ -1,7 +1,7 @@
|
|||||||
from shot_builder.builder.build_step import BuildStep, BuildContext
|
from blender_kitsu.shot_builder.builder.build_step import BuildStep, BuildContext
|
||||||
from shot_builder.asset import *
|
from blender_kitsu.shot_builder.asset import *
|
||||||
from shot_builder.project import *
|
from blender_kitsu.shot_builder.project import *
|
||||||
from shot_builder.shot import *
|
from blender_kitsu.shot_builder.shot import *
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
|
@ -1,5 +1,5 @@
|
|||||||
from shot_builder.builder.build_step import BuildStep, BuildContext
|
from blender_kitsu.shot_builder.builder.build_step import BuildStep, BuildContext
|
||||||
from shot_builder.hooks import HookFunction
|
from blender_kitsu.shot_builder.hooks import HookFunction
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
import typing
|
import typing
|
@ -1,5 +1,5 @@
|
|||||||
from shot_builder.builder.build_step import BuildStep, BuildContext
|
from blender_kitsu.shot_builder.builder.build_step import BuildStep, BuildContext
|
||||||
from shot_builder.render_settings import RenderSettings
|
from blender_kitsu.shot_builder.render_settings import RenderSettings
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
import logging
|
import logging
|
@ -1,7 +1,7 @@
|
|||||||
from shot_builder.builder.build_step import BuildStep, BuildContext
|
from blender_kitsu.shot_builder.builder.build_step import BuildStep, BuildContext
|
||||||
from shot_builder.asset import *
|
from blender_kitsu.shot_builder.asset import *
|
||||||
from shot_builder.project import *
|
from blender_kitsu.shot_builder.project import *
|
||||||
from shot_builder.shot import *
|
from blender_kitsu.shot_builder.shot import *
|
||||||
import pathlib
|
import pathlib
|
||||||
|
|
||||||
import bpy
|
import bpy
|
@ -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 bpy
|
||||||
|
|
||||||
import typing
|
import typing
|
@ -21,16 +21,16 @@
|
|||||||
This module contains the Connector class. It is an abstract base class for concrete connectors.
|
This module contains the Connector class. It is an abstract base class for concrete connectors.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from shot_builder.shot import Shot, ShotRef
|
from blender_kitsu.shot_builder.shot import Shot, ShotRef
|
||||||
from shot_builder.asset import Asset, AssetRef
|
from blender_kitsu.shot_builder.asset import Asset, AssetRef
|
||||||
from shot_builder.task_type import TaskType
|
from blender_kitsu.shot_builder.task_type import TaskType
|
||||||
from shot_builder.render_settings import RenderSettings
|
from blender_kitsu.shot_builder.render_settings import RenderSettings
|
||||||
from typing import *
|
from typing import *
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from shot_builder.project import Production
|
from blender_kitsu.shot_builder.project import Production
|
||||||
from shot_builder.properties import ShotBuilderPreferences
|
from blender_kitsu.shot_builder.properties import ShotBuilderPreferences
|
||||||
|
|
||||||
|
|
||||||
class Connector:
|
class Connector:
|
||||||
@ -58,8 +58,8 @@ class Connector:
|
|||||||
|
|
||||||
Example of using predefined connectors in a production config file:
|
Example of using predefined connectors in a production config file:
|
||||||
```shot-builder/config.py
|
```shot-builder/config.py
|
||||||
from shot_builder.connectors.default import DefaultConnector
|
from blender_kitsu.shot_builder.connectors.default import DefaultConnector
|
||||||
from shot_builder.connectors.kitsu import KitsuConnector
|
from blender_kitsu.shot_builder.connectors.kitsu import KitsuConnector
|
||||||
|
|
||||||
PRODUCTION_NAME = DefaultConnector
|
PRODUCTION_NAME = DefaultConnector
|
||||||
TASK_TYPES = KitsuConnector
|
TASK_TYPES = KitsuConnector
|
@ -17,11 +17,11 @@
|
|||||||
# ##### END GPL LICENSE BLOCK #####
|
# ##### END GPL LICENSE BLOCK #####
|
||||||
|
|
||||||
# <pep8 compliant>
|
# <pep8 compliant>
|
||||||
from shot_builder.shot import Shot, ShotRef
|
from blender_kitsu.shot_builder.shot import Shot, ShotRef
|
||||||
from shot_builder.asset import Asset, AssetRef
|
from blender_kitsu.shot_builder.asset import Asset, AssetRef
|
||||||
from shot_builder.task_type import TaskType
|
from blender_kitsu.shot_builder.task_type import TaskType
|
||||||
from shot_builder.render_settings import RenderSettings
|
from blender_kitsu.shot_builder.render_settings import RenderSettings
|
||||||
from shot_builder.connectors.connector import Connector
|
from blender_kitsu.shot_builder.connectors.connector import Connector
|
||||||
from typing import *
|
from typing import *
|
||||||
|
|
||||||
|
|
@ -18,13 +18,16 @@
|
|||||||
|
|
||||||
# <pep8 compliant>
|
# <pep8 compliant>
|
||||||
import bpy
|
import bpy
|
||||||
from shot_builder import vars
|
from blender_kitsu.shot_builder import vars
|
||||||
from shot_builder.shot import Shot, ShotRef
|
from blender_kitsu.shot_builder.shot import Shot, ShotRef
|
||||||
from shot_builder.asset import Asset, AssetRef
|
from blender_kitsu.shot_builder.asset import Asset, AssetRef
|
||||||
from shot_builder.task_type import TaskType
|
from blender_kitsu.shot_builder.task_type import TaskType
|
||||||
from shot_builder.render_settings import RenderSettings
|
from blender_kitsu.shot_builder.render_settings import RenderSettings
|
||||||
from shot_builder.connectors.connector import Connector
|
from blender_kitsu.shot_builder.connectors.connector import Connector
|
||||||
import requests
|
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 typing
|
||||||
import logging
|
import logging
|
||||||
@ -131,47 +134,9 @@ class KitsuConnector(Connector):
|
|||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super().__init__(**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:
|
def __get_production_data(self) -> KitsuProject:
|
||||||
project_id = self._production.config['KITSU_PROJECT_ID']
|
production = cache.project_active_get()
|
||||||
production = self.__api_get(f"data/projects/{project_id}")
|
|
||||||
project = KitsuProject(typing.cast(
|
project = KitsuProject(typing.cast(
|
||||||
typing.Dict[str, typing.Any], production))
|
typing.Dict[str, typing.Any], production))
|
||||||
return project
|
return project
|
||||||
@ -181,22 +146,23 @@ class KitsuConnector(Connector):
|
|||||||
return production.get_name()
|
return production.get_name()
|
||||||
|
|
||||||
def get_task_types(self) -> typing.List[TaskType]:
|
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
|
import pprint
|
||||||
pprint.pprint(task_types)
|
pprint.pprint(task_types)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def get_shots(self) -> typing.List[ShotRef]:
|
def get_shots(self) -> typing.List[ShotRef]:
|
||||||
project_id = self._production.config['KITSU_PROJECT_ID']
|
project = cache.project_active_get()
|
||||||
kitsu_sequences = self.__api_get(f"data/projects/{project_id}/sequences")
|
kitsu_sequences = all_sequences_for_project(project.id)
|
||||||
|
|
||||||
sequence_lookup = {sequence_data['id']: KitsuSequenceRef(
|
sequence_lookup = {sequence_data['id']: KitsuSequenceRef(
|
||||||
kitsu_id=sequence_data['id'],
|
kitsu_id=sequence_data['id'],
|
||||||
name=sequence_data['name'],
|
name=sequence_data['name'],
|
||||||
code=sequence_data['code'],
|
code=sequence_data['code'],
|
||||||
) for sequence_data in kitsu_sequences}
|
) 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] = []
|
shots: typing.List[ShotRef] = []
|
||||||
|
|
||||||
for shot_data in kitsu_shots:
|
for shot_data in kitsu_shots:
|
||||||
@ -232,8 +198,8 @@ class KitsuConnector(Connector):
|
|||||||
return shots
|
return shots
|
||||||
|
|
||||||
def get_assets_for_shot(self, shot: Shot) -> typing.List[AssetRef]:
|
def get_assets_for_shot(self, shot: Shot) -> typing.List[AssetRef]:
|
||||||
kitsu_assets = self.__api_get(
|
kitsu_assets = all_assets_for_shot(shot.kitsu_id)
|
||||||
f"data/shots/{shot.kitsu_id}/assets")
|
|
||||||
return [AssetRef(name=asset_data['name'], code=asset_data['code'])
|
return [AssetRef(name=asset_data['name'], code=asset_data['code'])
|
||||||
for asset_data in kitsu_assets]
|
for asset_data in kitsu_assets]
|
||||||
|
|
||||||
@ -241,7 +207,5 @@ class KitsuConnector(Connector):
|
|||||||
"""
|
"""
|
||||||
Retrieve the render settings for the given shot.
|
Retrieve the render settings for the given shot.
|
||||||
"""
|
"""
|
||||||
kitsu_project = self.__get_production_data()
|
project = cache.project_active_get()
|
||||||
resolution = kitsu_project.get_resolution()
|
return RenderSettings(width=int(project.resolution.split('x')[0]), height=int(project.resolution.split('x')[1]), frames_per_second=project.fps)
|
||||||
frames_per_second = shot.frames_per_second
|
|
||||||
return RenderSettings(width=resolution[0], height=resolution[1], frames_per_second=frames_per_second)
|
|
@ -1,233 +1,233 @@
|
|||||||
# Project Description (DRAFT)
|
# Project Description (DRAFT)
|
||||||
|
|
||||||
Shot Builder is an Add-on that helps studios to work with task specific
|
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
|
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.
|
* Build blend files for a specific task and shot.
|
||||||
* Sync data back from work files to places like kitsu, or `edit.blend`.
|
* Sync data back from work files to places like kitsu, or `edit.blend`.
|
||||||
|
|
||||||
## Design Principles
|
## Design Principles
|
||||||
|
|
||||||
The main design principles are:
|
The main design principles are:
|
||||||
|
|
||||||
* The core-tool can be installed as an add-on, but the (production specific)
|
* The core-tool can be installed as an add-on, but the (production specific)
|
||||||
configuration should be part of the production repository.
|
configuration should be part of the production repository.
|
||||||
* The configuration files are a collection of python files. The API between
|
* 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
|
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 working on the production should be able to work with it.
|
||||||
* TDs/artists should be able to handle issues during building without looking
|
* TDs/artists should be able to handle issues during building without looking
|
||||||
at how the add-on is structured.
|
at how the add-on is structured.
|
||||||
* The tool contains connectors that can be configured to read/write data
|
* 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
|
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.
|
The start and end time of a shot could be stored in an external production tracking application.
|
||||||
|
|
||||||
## Connectors
|
## Connectors
|
||||||
|
|
||||||
Connectors are components that can be used to read or write to files or
|
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
|
systems. The connectors will add flexibility to the add-on so it could be used
|
||||||
in multiple productions or studios.
|
in multiple productions or studios.
|
||||||
|
|
||||||
In the configuration files the TD can setup the connectors that are used for
|
In the configuration files the TD can setup the connectors that are used for
|
||||||
the production. Possible connectors would be:
|
the production. Possible connectors would be:
|
||||||
|
|
||||||
* Connector for text based config files (json/yaml).
|
* Connector for text based config files (json/yaml).
|
||||||
* Connector for kitsu (https://www.cg-wire.com/en/kitsu.html).
|
* Connector for kitsu (https://www.cg-wire.com/en/kitsu.html).
|
||||||
* Connector for blend files.
|
* Connector for blend files.
|
||||||
|
|
||||||
## Layering & Hooks
|
## Layering & Hooks
|
||||||
|
|
||||||
The configuration of the tool is layered. When building a work file for a sequence
|
The configuration of the tool is layered. When building a work file for a sequence
|
||||||
there are multiple ways to change the configuration.
|
there are multiple ways to change the configuration.
|
||||||
|
|
||||||
* Configuration for the production.
|
* Configuration for the production.
|
||||||
* Configuration for the asset that is needed.
|
* Configuration for the asset that is needed.
|
||||||
* Configuration for the asset type of the loaded asset.
|
* Configuration for the asset type of the loaded asset.
|
||||||
* Configuration for the sequence.
|
* Configuration for the sequence.
|
||||||
* Configuration for the shot.
|
* Configuration for the shot.
|
||||||
* Configuration for the task type.
|
* Configuration for the task type.
|
||||||
|
|
||||||
For any combination of these configurations hooks can be defined.
|
For any combination of these configurations hooks can be defined.
|
||||||
|
|
||||||
```
|
```
|
||||||
@shot_tools.hook(match_asset_name='Spring', match_shot_code='02_020A')
|
@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:
|
def hook_Spring_02_020A(asset: shot_tools.Asset, shot: shot_tools.Shot, **kwargs) -> None:
|
||||||
"""
|
"""
|
||||||
Specific overrides when Spring is loaded in 02_020A.
|
Specific overrides when Spring is loaded in 02_020A.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@shot_tools.hook(match_task_type='anim')
|
@shot_tools.hook(match_task_type='anim')
|
||||||
def hook_task_anim(task: shot_tools.Task, shot: shot_tools.Shot, **kwargs) -> None:
|
def hook_task_anim(task: shot_tools.Task, shot: shot_tools.Shot, **kwargs) -> None:
|
||||||
"""
|
"""
|
||||||
Specific overrides for any animation task.
|
Specific overrides for any animation task.
|
||||||
"""
|
"""
|
||||||
```
|
```
|
||||||
|
|
||||||
### Data
|
### Data
|
||||||
|
|
||||||
All hooks must have Python’s `**kwargs` parameter. The `kwargs` contains
|
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
|
the context at the moment the hook is invoked. The context can contain the
|
||||||
following items.
|
following items.
|
||||||
|
|
||||||
* `production`: `shot_tools.Production`: Include the name of the production
|
* `production`: `shot_tools.Production`: Include the name of the production
|
||||||
and the location on the filesystem.
|
and the location on the filesystem.
|
||||||
* `task`: `shot_tools.Task`: The task (combination of task_type and shot)
|
* `task`: `shot_tools.Task`: The task (combination of task_type and shot)
|
||||||
* `task_type`: `shot_tools.TaskType`: Is part of the `task`.
|
* `task_type`: `shot_tools.TaskType`: Is part of the `task`.
|
||||||
* `sequence`: `shot_tools.Sequence`: Is part of `shot`.
|
* `sequence`: `shot_tools.Sequence`: Is part of `shot`.
|
||||||
* `shot`: `shot_tools.Shot` Is part of `task`.
|
* `shot`: `shot_tools.Shot` Is part of `task`.
|
||||||
* `asset`: `shot_tools.Asset`: Only available during asset loading phase.
|
* `asset`: `shot_tools.Asset`: Only available during asset loading phase.
|
||||||
* `asset_type`: `shot_tools.AssetType`: Only available during asset loading phase.
|
* `asset_type`: `shot_tools.AssetType`: Only available during asset loading phase.
|
||||||
|
|
||||||
### Execution Order
|
### Execution Order
|
||||||
|
|
||||||
The add-on will internally create a list containing the hooks that needs to be
|
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
|
executed for the command in a sensible order. It will then execute them in that
|
||||||
order.
|
order.
|
||||||
|
|
||||||
By default the next order will be used:
|
By default the next order will be used:
|
||||||
|
|
||||||
* Production wide hooks
|
* Production wide hooks
|
||||||
* Asset Type hooks
|
* Asset Type hooks
|
||||||
* Asset hooks
|
* Asset hooks
|
||||||
* Sequence hooks
|
* Sequence hooks
|
||||||
* Shot hooks
|
* Shot hooks
|
||||||
* Task type hooks
|
* Task type hooks
|
||||||
|
|
||||||
A hook with a single ‘match’ rule will be run in the corresponding phase. A hook with
|
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
|
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.
|
‘asset’ and ‘task type’ match rules will be run in the ‘task type’ phase.
|
||||||
|
|
||||||
#### Events
|
#### Events
|
||||||
|
|
||||||
Order of execution can be customized by adding the optional `run_before`
|
Order of execution can be customized by adding the optional `run_before`
|
||||||
or `run_after` parameters.
|
or `run_after` parameters.
|
||||||
|
|
||||||
```
|
```
|
||||||
@shot_tools.hook(match_task_type='anim',
|
@shot_tools.hook(match_task_type='anim',
|
||||||
requires={shot_tools.events.AssetsLoaded, hook_task_other_anim},
|
requires={shot_tools.events.AssetsLoaded, hook_task_other_anim},
|
||||||
is_required_by={shot_tools.events.ShotOverrides})
|
is_required_by={shot_tools.events.ShotOverrides})
|
||||||
def hook_task_anim(task: shot_tools.Task, shot: shot_tools.Shot, **kwargs) -> None:
|
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.
|
Specific overrides for any animation task run after all assets have been loaded.
|
||||||
"""
|
"""
|
||||||
```
|
```
|
||||||
|
|
||||||
Events could be:
|
Events could be:
|
||||||
|
|
||||||
* `shot_tools.events.BuildStart`
|
* `shot_tools.events.BuildStart`
|
||||||
* `shot_tools.events.ProductionSettingsLoaded`
|
* `shot_tools.events.ProductionSettingsLoaded`
|
||||||
* `shot_tools.events.AssetsLoaded`
|
* `shot_tools.events.AssetsLoaded`
|
||||||
* `shot_tools.events.AssetTypeOverrides`
|
* `shot_tools.events.AssetTypeOverrides`
|
||||||
* `shot_tools.events.SequenceOverrides`
|
* `shot_tools.events.SequenceOverrides`
|
||||||
* `shot_tools.events.ShotOverrides`
|
* `shot_tools.events.ShotOverrides`
|
||||||
* `shot_tools.events.TaskTypeOverrides`
|
* `shot_tools.events.TaskTypeOverrides`
|
||||||
* `shot_tools.events.BuildFinished`
|
* `shot_tools.events.BuildFinished`
|
||||||
* `shot_tools.events.HookStart`
|
* `shot_tools.events.HookStart`
|
||||||
* `shot_tools.events.HookEnd`
|
* `shot_tools.events.HookEnd`
|
||||||
|
|
||||||
During usage we should see which one of these or other events are needed.
|
During usage we should see which one of these or other events are needed.
|
||||||
|
|
||||||
`shot_tools.events.BuildStart`, `shot_tools.events.ProductionSettingsLoaded`
|
`shot_tools.events.BuildStart`, `shot_tools.events.ProductionSettingsLoaded`
|
||||||
and `shot_tools.events.HookStart` can only be used in the `run_after`
|
and `shot_tools.events.HookStart` can only be used in the `run_after`
|
||||||
parameter. `shot_tools.events.BuildFinished`, `shot_tools.events.HookFinished`
|
parameter. `shot_tools.events.BuildFinished`, `shot_tools.events.HookFinished`
|
||||||
can only be used in the `run_before` parameter.
|
can only be used in the `run_before` parameter.
|
||||||
|
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
The shot builder has an API between the add-on and the configuration files. This
|
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
|
API contains convenience functions and classes to hide complexity and makes
|
||||||
sure that the configuration files are easy to maintain.
|
sure that the configuration files are easy to maintain.
|
||||||
|
|
||||||
```
|
```
|
||||||
register_task_type(task_type="anim")
|
register_task_type(task_type="anim")
|
||||||
register_task_type(task_type="lighting")
|
register_task_type(task_type="lighting")
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
# shot_tool/characters.py
|
# shot_tool/characters.py
|
||||||
class Asset(shot_tool.some_module.Asset):
|
class Asset(shot_tool.some_module.Asset):
|
||||||
asset_file = "/{asset_type}/{name}/{name}.blend"
|
asset_file = "/{asset_type}/{name}/{name}.blend"
|
||||||
collection = “{class_name}”
|
collection = “{class_name}”
|
||||||
name = “{class_name}”
|
name = “{class_name}”
|
||||||
|
|
||||||
class Character(Asset):
|
class Character(Asset):
|
||||||
asset_type = ‘char’
|
asset_type = ‘char’
|
||||||
|
|
||||||
|
|
||||||
class Ellie(Character):
|
class Ellie(Character):
|
||||||
collection = “{class_name}-{variant_name}”
|
collection = “{class_name}-{variant_name}”
|
||||||
variants = {‘default’, ‘short_hair’}
|
variants = {‘default’, ‘short_hair’}
|
||||||
|
|
||||||
class Victoria(Character): pass
|
class Victoria(Character): pass
|
||||||
class Rex(Character): pass
|
class Rex(Character): pass
|
||||||
|
|
||||||
# shot_tool/shots.py
|
# shot_tool/shots.py
|
||||||
class Shot_01_020_A(shot_tool.some_module.Shot):
|
class Shot_01_020_A(shot_tool.some_module.Shot):
|
||||||
shot_id = ‘01_020_A’
|
shot_id = ‘01_020_A’
|
||||||
assets = {
|
assets = {
|
||||||
characters.Ellie(variant=”short_hair”),
|
characters.Ellie(variant=”short_hair”),
|
||||||
characters.Rex,
|
characters.Rex,
|
||||||
sets.LogOverChasm,
|
sets.LogOverChasm,
|
||||||
}
|
}
|
||||||
|
|
||||||
class AllHumansShot(shot_tool.some_module.Shot):
|
class AllHumansShot(shot_tool.some_module.Shot):
|
||||||
assets = {
|
assets = {
|
||||||
characters.Ellie(variant=”short_hair”),
|
characters.Ellie(variant=”short_hair”),
|
||||||
characters.Rex,
|
characters.Rex,
|
||||||
characters.Victoria,
|
characters.Victoria,
|
||||||
}
|
}
|
||||||
|
|
||||||
class Shot_01_035_A(AllHumansShot):
|
class Shot_01_035_A(AllHumansShot):
|
||||||
assets = {
|
assets = {
|
||||||
sets.Camp,
|
sets.Camp,
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
This API is structured/implemented in a way that it keeps track of what
|
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
|
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
|
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
|
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
|
direct the TD into the direction where the actual cause is. And when possible
|
||||||
propose several solutions to fix it.
|
propose several solutions to fix it.
|
||||||
|
|
||||||
## Setting up the tool
|
## Setting up the tool
|
||||||
|
|
||||||
The artist/TD can configure their current local project directory in the add-on preferences.
|
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)
|
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
|
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,
|
configuration file is found. Project-specific settings are not configured/stored in the add-on,
|
||||||
but in this configuration file.
|
but in this configuration file.
|
||||||
|
|
||||||
The add-on will look in the root of the production repository to locate the
|
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
|
main configuration file `shot-builder/config.py`. This file contains general
|
||||||
settings about the production, including:
|
settings about the production, including:
|
||||||
|
|
||||||
* The name of the production for reporting back to the user when needed.
|
* The name of the production for reporting back to the user when needed.
|
||||||
* Naming standards to test against when reporting deviations.
|
* 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.
|
* Location of other configuration (`tasks.py`, `assets.py`) relative to the `shot-builder` directory of the production.
|
||||||
* Configuration of the needed connectors.
|
* Configuration of the needed connectors.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Any artist can open a shot file via the `File` menu. A modal panel appears
|
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
|
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
|
already exists, it will be opened. When the file doesn't exist, the file
|
||||||
will be built.
|
will be built.
|
||||||
|
|
||||||
In the future other use cases will also be accessible, such as:
|
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.
|
* Syncing data back from a work file to the source of the data.
|
||||||
* Report of errors/differences between the shot file and the configuration.
|
* Report of errors/differences between the shot file and the configuration.
|
||||||
|
|
||||||
## Open Issues
|
## Open Issues
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
* Security keys needed by connectors need to be stored somewhere. The easy
|
* 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
|
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
|
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
|
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.
|
keys from an online service authenticated by the blender cloud add-on.
|
||||||
|
|
||||||
We could use `keyring` to access OS key stores.
|
We could use `keyring` to access OS key stores.
|
@ -1,4 +1,4 @@
|
|||||||
# Example configuration files
|
# Example configuration files
|
||||||
|
|
||||||
This folder contains an example shot builder configuration. It shows the part
|
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.
|
that a TD would do to incorporate the shot builder in a production.
|
@ -1,83 +1,83 @@
|
|||||||
from shot_builder.asset import Asset
|
from blender_kitsu.shot_builder.asset import Asset
|
||||||
|
|
||||||
|
|
||||||
class SpriteFrightAsset(Asset):
|
class SpriteFrightAsset(Asset):
|
||||||
path = "{production.path}/lib/{asset.asset_type}/{asset.code}/{asset.code}.blend"
|
path = "{production.path}/lib/{asset.asset_type}/{asset.code}/{asset.code}.blend"
|
||||||
|
|
||||||
|
|
||||||
class Character(SpriteFrightAsset):
|
class Character(SpriteFrightAsset):
|
||||||
asset_type = "char"
|
asset_type = "char"
|
||||||
collection = "CH-{asset.code}"
|
collection = "CH-{asset.code}"
|
||||||
|
|
||||||
|
|
||||||
class Ellie(Character):
|
class Ellie(Character):
|
||||||
name = "Ellie"
|
name = "Ellie"
|
||||||
code = "ellie"
|
code = "ellie"
|
||||||
|
|
||||||
|
|
||||||
class Victoria(Character):
|
class Victoria(Character):
|
||||||
name = "Victoria"
|
name = "Victoria"
|
||||||
code = "victoria"
|
code = "victoria"
|
||||||
|
|
||||||
|
|
||||||
class Phil(Character):
|
class Phil(Character):
|
||||||
name = "Phil"
|
name = "Phil"
|
||||||
code = "phil"
|
code = "phil"
|
||||||
|
|
||||||
|
|
||||||
class Rex(Character):
|
class Rex(Character):
|
||||||
name = "Rex"
|
name = "Rex"
|
||||||
code = "rex"
|
code = "rex"
|
||||||
|
|
||||||
|
|
||||||
class Jay(Character):
|
class Jay(Character):
|
||||||
name = "Jay"
|
name = "Jay"
|
||||||
code = "jay"
|
code = "jay"
|
||||||
|
|
||||||
# TODO: Bird character has no asset file yet.
|
# TODO: Bird character has no asset file yet.
|
||||||
# class Bird(Character):
|
# class Bird(Character):
|
||||||
# name = "Bird"
|
# name = "Bird"
|
||||||
# code = "bird"
|
# code = "bird"
|
||||||
|
|
||||||
|
|
||||||
class Prop(SpriteFrightAsset):
|
class Prop(SpriteFrightAsset):
|
||||||
asset_type = "props"
|
asset_type = "props"
|
||||||
collection = "PR-{asset.code}"
|
collection = "PR-{asset.code}"
|
||||||
|
|
||||||
|
|
||||||
class Boombox(Prop):
|
class Boombox(Prop):
|
||||||
name = "Boombox"
|
name = "Boombox"
|
||||||
code = "boombox"
|
code = "boombox"
|
||||||
|
|
||||||
|
|
||||||
class BBQGrill(Prop):
|
class BBQGrill(Prop):
|
||||||
name = "BBQ Grill"
|
name = "BBQ Grill"
|
||||||
code = "bbq_grill"
|
code = "bbq_grill"
|
||||||
|
|
||||||
|
|
||||||
# NOTE: NotepadAndPencil is a combined asset. In Kitsu it is defined as a single asset. In the production
|
# 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`
|
# reportitory it is stored as 2 collections in a single file. See `hooks.link_char_prop_for_anim`
|
||||||
# where this is handled.
|
# where this is handled.
|
||||||
class NotepadAndPencil(Prop):
|
class NotepadAndPencil(Prop):
|
||||||
name = "Notepad and pencil"
|
name = "Notepad and pencil"
|
||||||
code = "notepad_pencil"
|
code = "notepad_pencil"
|
||||||
|
|
||||||
|
|
||||||
class Binoculars(Prop):
|
class Binoculars(Prop):
|
||||||
name = "Binoculars (Ellie)"
|
name = "Binoculars (Ellie)"
|
||||||
code = "binoculars"
|
code = "binoculars"
|
||||||
|
|
||||||
|
|
||||||
class Backpack(Prop):
|
class Backpack(Prop):
|
||||||
name = "Backpack (Phil)"
|
name = "Backpack (Phil)"
|
||||||
code = "backpack"
|
code = "backpack"
|
||||||
|
|
||||||
|
|
||||||
class Set(SpriteFrightAsset):
|
class Set(SpriteFrightAsset):
|
||||||
asset_type = "sets"
|
asset_type = "sets"
|
||||||
collection = "SE-{asset.code}"
|
collection = "SE-{asset.code}"
|
||||||
|
|
||||||
|
|
||||||
class MushroomGrove(Set):
|
class MushroomGrove(Set):
|
||||||
name = "Mushroom grove"
|
name = "Mushroom grove"
|
||||||
code = "mushroom_grove"
|
code = "mushroom_grove"
|
@ -1,14 +1,14 @@
|
|||||||
from shot_builder.connectors.kitsu import KitsuConnector
|
from blender_kitsu.shot_builder.connectors.kitsu import KitsuConnector
|
||||||
|
|
||||||
PRODUCTION_NAME = KitsuConnector
|
PRODUCTION_NAME = KitsuConnector
|
||||||
SHOTS = KitsuConnector
|
SHOTS = KitsuConnector
|
||||||
ASSETS = KitsuConnector
|
ASSETS = KitsuConnector
|
||||||
RENDER_SETTINGS = KitsuConnector
|
RENDER_SETTINGS = KitsuConnector
|
||||||
|
|
||||||
KITSU_PROJECT_ID = "fc77c0b9-bb76-41c3-b843-c9b156f9b3ec"
|
KITSU_PROJECT_ID = "fc77c0b9-bb76-41c3-b843-c9b156f9b3ec"
|
||||||
|
|
||||||
# Formatting rules
|
# Formatting rules
|
||||||
# ----------------
|
# ----------------
|
||||||
|
|
||||||
# The name of the scene in blender where the shot is build in.
|
# The name of the scene in blender where the shot is build in.
|
||||||
# SCENE_NAME_FORMAT = "{shot.sequence_code}_{shot.code}.{task_type}"
|
# SCENE_NAME_FORMAT = "{shot.sequence_code}_{shot.code}.{task_type}"
|
@ -1,151 +1,151 @@
|
|||||||
import bpy
|
import bpy
|
||||||
from shot_builder.hooks import hook, Wildcard
|
from blender_kitsu.shot_builder.hooks import hook, Wildcard
|
||||||
from shot_builder.asset import Asset
|
from blender_kitsu.shot_builder.asset import Asset
|
||||||
from shot_builder.shot import Shot
|
from blender_kitsu.shot_builder.shot import Shot
|
||||||
from shot_builder.project import Production
|
from blender_kitsu.shot_builder.project import Production
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# ---------- Global Hook ----------
|
# ---------- Global Hook ----------
|
||||||
|
|
||||||
|
|
||||||
@hook()
|
@hook()
|
||||||
def set_cycles_render_engine(scene: bpy.types.Scene, **kwargs):
|
def set_cycles_render_engine(scene: bpy.types.Scene, **kwargs):
|
||||||
"""
|
"""
|
||||||
By default we set Cycles as the renderer.
|
By default we set Cycles as the renderer.
|
||||||
"""
|
"""
|
||||||
scene.render.engine = 'CYCLES'
|
scene.render.engine = 'CYCLES'
|
||||||
|
|
||||||
|
|
||||||
# ---------- Overrides for animation files ----------
|
# ---------- Overrides for animation files ----------
|
||||||
|
|
||||||
@hook(match_task_type='anim')
|
@hook(match_task_type='anim')
|
||||||
def task_type_anim_set_workbench(scene: bpy.types.Scene, **kwargs):
|
def task_type_anim_set_workbench(scene: bpy.types.Scene, **kwargs):
|
||||||
"""
|
"""
|
||||||
Override of the render engine to Workbench when building animation files.
|
Override of the render engine to Workbench when building animation files.
|
||||||
"""
|
"""
|
||||||
scene.render.engine = 'BLENDER_WORKBENCH'
|
scene.render.engine = 'BLENDER_WORKBENCH'
|
||||||
|
|
||||||
# ---------- Create output collection for animation files ----------
|
# ---------- Create output collection for animation files ----------
|
||||||
|
|
||||||
|
|
||||||
def _add_camera_rig(
|
def _add_camera_rig(
|
||||||
scene: bpy.types.Scene,
|
scene: bpy.types.Scene,
|
||||||
production: Production,
|
production: Production,
|
||||||
shot: Shot,
|
shot: Shot,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Function to load the camera rig. The rig will be added to the output 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.
|
of the shot and the camera will be set as active camera.
|
||||||
"""
|
"""
|
||||||
# Load camera rig.
|
# Load camera rig.
|
||||||
path = f"{production.path}/lib/cam/camera_rig.blend"
|
path = f"{production.path}/lib/cam/camera_rig.blend"
|
||||||
collection_name = "CA-camera_rig"
|
collection_name = "CA-camera_rig"
|
||||||
bpy.ops.wm.link(
|
bpy.ops.wm.link(
|
||||||
filepath=path,
|
filepath=path,
|
||||||
directory=path + "/Collection",
|
directory=path + "/Collection",
|
||||||
filename=collection_name,
|
filename=collection_name,
|
||||||
)
|
)
|
||||||
# Keep the active object name as this would also be the name of the collection after enabling library override.
|
# 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
|
active_object_name = bpy.context.active_object.name
|
||||||
|
|
||||||
# Make library override.
|
# Make library override.
|
||||||
bpy.ops.object.make_override_library()
|
bpy.ops.object.make_override_library()
|
||||||
|
|
||||||
# Add camera collection to the output collection
|
# Add camera collection to the output collection
|
||||||
asset_collection = bpy.data.collections[active_object_name]
|
asset_collection = bpy.data.collections[active_object_name]
|
||||||
shot.output_collection.children.link(asset_collection)
|
shot.output_collection.children.link(asset_collection)
|
||||||
|
|
||||||
# Set the camera of the camera rig as active scene camera.
|
# Set the camera of the camera rig as active scene camera.
|
||||||
camera = bpy.data.objects['CAM-camera']
|
camera = bpy.data.objects['CAM-camera']
|
||||||
scene.camera = camera
|
scene.camera = camera
|
||||||
|
|
||||||
|
|
||||||
@hook(match_task_type='anim')
|
@hook(match_task_type='anim')
|
||||||
def task_type_anim_output_collection(scene: bpy.types.Scene, production: Production, shot: Shot, task_type: str, **kwargs):
|
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
|
Animations are stored in an output collection. This collection will be linked
|
||||||
by the lighting file.
|
by the lighting file.
|
||||||
|
|
||||||
Also loads the camera rig.
|
Also loads the camera rig.
|
||||||
"""
|
"""
|
||||||
output_collection = bpy.data.collections.new(
|
output_collection = bpy.data.collections.new(
|
||||||
name=shot.get_output_collection_name(shot=shot, task_type=task_type))
|
name=shot.get_output_collection_name(shot=shot, task_type=task_type))
|
||||||
shot.output_collection = output_collection
|
shot.output_collection = output_collection
|
||||||
output_collection.use_fake_user = True
|
output_collection.use_fake_user = True
|
||||||
|
|
||||||
_add_camera_rig(scene, production, shot)
|
_add_camera_rig(scene, production, shot)
|
||||||
|
|
||||||
|
|
||||||
@hook(match_task_type='lighting')
|
@hook(match_task_type='lighting')
|
||||||
def link_anim_output_collection(scene: bpy.types.Scene, production: Production, shot: Shot, **kwargs):
|
def link_anim_output_collection(scene: bpy.types.Scene, production: Production, shot: Shot, **kwargs):
|
||||||
"""
|
"""
|
||||||
Link in the animation output collection from the animation file.
|
Link in the animation output collection from the animation file.
|
||||||
"""
|
"""
|
||||||
anim_collection = bpy.data.collections.new(name="animation")
|
anim_collection = bpy.data.collections.new(name="animation")
|
||||||
scene.collection.children.link(anim_collection)
|
scene.collection.children.link(anim_collection)
|
||||||
anim_file_path = shot.get_anim_file_path(production, shot)
|
anim_file_path = shot.get_anim_file_path(production, shot)
|
||||||
anim_output_collection_name = shot.get_output_collection_name(
|
anim_output_collection_name = shot.get_output_collection_name(
|
||||||
shot=shot, task_type="anim")
|
shot=shot, task_type="anim")
|
||||||
result = bpy.ops.wm.link(
|
result = bpy.ops.wm.link(
|
||||||
filepath=anim_file_path,
|
filepath=anim_file_path,
|
||||||
directory=anim_file_path + "/Collection",
|
directory=anim_file_path + "/Collection",
|
||||||
filename=anim_output_collection_name,
|
filename=anim_output_collection_name,
|
||||||
)
|
)
|
||||||
assert (result == {'FINISHED'})
|
assert (result == {'FINISHED'})
|
||||||
|
|
||||||
# Move the anim output collection from scene collection to the animation collection.
|
# Move the anim output collection from scene collection to the animation collection.
|
||||||
anim_output_collection = bpy.data.objects[anim_output_collection_name]
|
anim_output_collection = bpy.data.objects[anim_output_collection_name]
|
||||||
anim_collection.objects.link(anim_output_collection)
|
anim_collection.objects.link(anim_output_collection)
|
||||||
scene.collection.objects.unlink(anim_output_collection)
|
scene.collection.objects.unlink(anim_output_collection)
|
||||||
|
|
||||||
# Use animation camera as active scene camera.
|
# Use animation camera as active scene camera.
|
||||||
camera = bpy.data.objects['CAM-camera']
|
camera = bpy.data.objects['CAM-camera']
|
||||||
scene.camera = camera
|
scene.camera = camera
|
||||||
|
|
||||||
|
|
||||||
# ---------- Asset loading and linking ----------
|
# ---------- Asset loading and linking ----------
|
||||||
|
|
||||||
|
|
||||||
@hook(match_task_type='anim', match_asset_type=['char', 'props'])
|
@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):
|
def link_char_prop_for_anim(scene: bpy.types.Scene, shot: Shot, asset: Asset, **kwargs):
|
||||||
"""
|
"""
|
||||||
Loading a character or prop for an animation file.
|
Loading a character or prop for an animation file.
|
||||||
"""
|
"""
|
||||||
collection_names = []
|
collection_names = []
|
||||||
if asset.code == 'notepad_pencil':
|
if asset.code == 'notepad_pencil':
|
||||||
collection_names.append("PR-pencil")
|
collection_names.append("PR-pencil")
|
||||||
collection_names.append("PR-notepad")
|
collection_names.append("PR-notepad")
|
||||||
else:
|
else:
|
||||||
collection_names.append(asset.collection)
|
collection_names.append(asset.collection)
|
||||||
|
|
||||||
for collection_name in collection_names:
|
for collection_name in collection_names:
|
||||||
logger.info("link asset")
|
logger.info("link asset")
|
||||||
bpy.ops.wm.link(
|
bpy.ops.wm.link(
|
||||||
filepath=str(asset.path),
|
filepath=str(asset.path),
|
||||||
directory=str(asset.path) + "/Collection",
|
directory=str(asset.path) + "/Collection",
|
||||||
filename=collection_name,
|
filename=collection_name,
|
||||||
)
|
)
|
||||||
# Keep the active object name as this would also be the name of the collection after enabling library override.
|
# 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
|
active_object_name = bpy.context.active_object.name
|
||||||
|
|
||||||
# Make library override.
|
# Make library override.
|
||||||
bpy.ops.object.make_override_library()
|
bpy.ops.object.make_override_library()
|
||||||
|
|
||||||
# Add overridden collection to the output collection.
|
# Add overridden collection to the output collection.
|
||||||
asset_collection = bpy.data.collections[active_object_name]
|
asset_collection = bpy.data.collections[active_object_name]
|
||||||
shot.output_collection.children.link(asset_collection)
|
shot.output_collection.children.link(asset_collection)
|
||||||
|
|
||||||
|
|
||||||
@hook(match_task_type=Wildcard, match_asset_type='sets')
|
@hook(match_task_type=Wildcard, match_asset_type='sets')
|
||||||
def link_set(asset: Asset, **kwargs):
|
def link_set(asset: Asset, **kwargs):
|
||||||
"""
|
"""
|
||||||
Load the set of the shot.
|
Load the set of the shot.
|
||||||
"""
|
"""
|
||||||
bpy.ops.wm.link(
|
bpy.ops.wm.link(
|
||||||
filepath=str(asset.path),
|
filepath=str(asset.path),
|
||||||
directory=str(asset.path) + "/Collection",
|
directory=str(asset.path) + "/Collection",
|
||||||
filename=asset.collection,
|
filename=asset.collection,
|
||||||
)
|
)
|
@ -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.
|
||||||
|
|
||||||
|
|
@ -1,29 +1,29 @@
|
|||||||
from shot_builder.shot import Shot
|
from blender_kitsu.shot_builder.shot import Shot
|
||||||
from shot_builder.project import Production
|
from blender_kitsu.shot_builder.project import Production
|
||||||
|
|
||||||
|
|
||||||
class SpriteFrightShot(Shot):
|
class SpriteFrightShot(Shot):
|
||||||
def get_anim_file_path(self, production: Production, shot: Shot) -> str:
|
def get_anim_file_path(self, production: Production, shot: Shot) -> str:
|
||||||
"""
|
"""
|
||||||
Get the animation file path for this given shot.
|
Get the animation file path for this given shot.
|
||||||
"""
|
"""
|
||||||
return self.file_path_format.format_map({
|
return self.file_path_format.format_map({
|
||||||
'production': production,
|
'production': production,
|
||||||
'shot': shot,
|
'shot': shot,
|
||||||
'task_type': "anim"
|
'task_type': "anim"
|
||||||
})
|
})
|
||||||
|
|
||||||
def get_output_collection_name(self, shot: Shot, task_type: str) -> str:
|
def get_output_collection_name(self, shot: Shot, task_type: str) -> str:
|
||||||
"""
|
"""
|
||||||
Get the collection name where the output is stored.
|
Get the collection name where the output is stored.
|
||||||
"""
|
"""
|
||||||
return f"{shot.sequence_code}_{shot.code}.{task_type}.output"
|
return f"{shot.sequence_code}_{shot.code}.{task_type}.output"
|
||||||
|
|
||||||
|
|
||||||
class Sequence_0002(SpriteFrightShot):
|
class Sequence_0002(SpriteFrightShot):
|
||||||
sequence_code = "0002"
|
sequence_code = "0002"
|
||||||
|
|
||||||
|
|
||||||
class Shot_0001_0001_A(Sequence_0002):
|
class Shot_0001_0001_A(Sequence_0002):
|
||||||
name = "001"
|
name = "001"
|
||||||
code = "0001"
|
code = "0001"
|
@ -78,7 +78,7 @@ class Hooks:
|
|||||||
|
|
||||||
|
|
||||||
def _register_hook(func: types.FunctionType) -> None:
|
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 = get_active_production()
|
||||||
production.hooks.register(func)
|
production.hooks.register(func)
|
||||||
|
|
@ -19,10 +19,11 @@
|
|||||||
# <pep8 compliant>
|
# <pep8 compliant>
|
||||||
from typing import *
|
from typing import *
|
||||||
import bpy
|
import bpy
|
||||||
from shot_builder.shot import ShotRef
|
from blender_kitsu.shot_builder.shot import ShotRef
|
||||||
from shot_builder.project import *
|
from blender_kitsu.shot_builder.project import ensure_loaded_production, get_active_production
|
||||||
from shot_builder.builder import ShotBuilder
|
from blender_kitsu.shot_builder.builder import ShotBuilder
|
||||||
from shot_builder.task_type import TaskType
|
from blender_kitsu.shot_builder.task_type import TaskType
|
||||||
|
from blender_kitsu import prefs, cache
|
||||||
|
|
||||||
_production_task_type_items: List[Tuple[str, str, str]] = []
|
_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]:
|
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 addon_prefs.session.is_auth() is False:
|
||||||
if production_root is None:
|
|
||||||
self.report(
|
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'}
|
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)
|
ensure_loaded_production(context)
|
||||||
production = get_active_production()
|
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
|
global _production_task_type_items
|
||||||
_production_task_type_items = production.get_task_type_items(
|
_production_task_type_items = production.get_task_type_items(
|
@ -23,15 +23,18 @@ from collections import defaultdict
|
|||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
from shot_builder.task_type import *
|
from blender_kitsu.shot_builder.task_type import *
|
||||||
from shot_builder.shot import Shot, ShotRef
|
from blender_kitsu.shot_builder.shot import Shot, ShotRef
|
||||||
from shot_builder.render_settings import RenderSettings
|
from blender_kitsu.shot_builder.render_settings import RenderSettings
|
||||||
from shot_builder.asset import Asset, AssetRef
|
from blender_kitsu.shot_builder.asset import Asset, AssetRef
|
||||||
from shot_builder.sys_utils import *
|
from blender_kitsu.shot_builder.sys_utils import *
|
||||||
from shot_builder.hooks import Hooks, register_hooks
|
from blender_kitsu.shot_builder.hooks import Hooks, register_hooks
|
||||||
|
|
||||||
from shot_builder.connectors.default import DefaultConnector
|
from blender_kitsu.shot_builder.connectors.default import DefaultConnector
|
||||||
from shot_builder.connectors.connector import Connector
|
from blender_kitsu.shot_builder.connectors.connector import Connector
|
||||||
|
|
||||||
|
from blender_kitsu import prefs
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from typing import *
|
from typing import *
|
||||||
import types
|
import types
|
||||||
@ -77,7 +80,7 @@ class Production:
|
|||||||
connector_cls: Type[Connector],
|
connector_cls: Type[Connector],
|
||||||
context: bpy.types.Context) -> Connector:
|
context: bpy.types.Context) -> Connector:
|
||||||
# TODO: Cache connector
|
# TODO: Cache connector
|
||||||
preferences = context.preferences.addons[__package__].preferences
|
preferences = context.preferences.addons["blender_kitsu"].preferences
|
||||||
return connector_cls(production=self, preferences=preferences)
|
return connector_cls(production=self, preferences=preferences)
|
||||||
|
|
||||||
def __format_shot_name(self, shot: Shot) -> str:
|
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)
|
production_root = _find_production_root(current_file)
|
||||||
if production_root:
|
if production_root:
|
||||||
return 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):
|
if is_valid_production_root(production_root):
|
||||||
return production_root
|
return production_root
|
||||||
return None
|
return None
|
@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
# <pep8 compliant>
|
# <pep8 compliant>
|
||||||
|
|
||||||
from shot_builder.asset import Asset
|
from blender_kitsu.shot_builder.asset import Asset
|
||||||
from typing import *
|
from typing import *
|
||||||
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
|||||||
# <pep8 compliant>
|
# <pep8 compliant>
|
||||||
import bpy
|
import bpy
|
||||||
from typing import *
|
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:
|
def topbar_file_new_draw_handler(self: Any, context: bpy.types.Context) -> None:
|
@ -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 #####
|
|
||||||
|
|
||||||
# <pep8 compliant>
|
|
||||||
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)
|
|
Loading…
Reference in New Issue
Block a user