Move Anim_Setup
module into Blender_Kitsu
#5
@ -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()
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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 #####
|
||||
|
||||
# <pep8 compliant>
|
||||
|
||||
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 #####
|
||||
|
||||
# <pep8 compliant>
|
||||
|
||||
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)
|
@ -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
|
||||
|
@ -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:
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -17,11 +17,11 @@
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# <pep8 compliant>
|
||||
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 *
|
||||
|
||||
|
@ -18,13 +18,16 @@
|
||||
|
||||
# <pep8 compliant>
|
||||
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)
|
@ -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.
|
@ -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.
|
@ -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"
|
@ -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}"
|
@ -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,
|
||||
)
|
@ -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 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"
|
@ -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)
|
||||
|
@ -19,10 +19,11 @@
|
||||
# <pep8 compliant>
|
||||
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(
|
@ -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
|
@ -18,7 +18,7 @@
|
||||
|
||||
# <pep8 compliant>
|
||||
|
||||
from shot_builder.asset import Asset
|
||||
from blender_kitsu.shot_builder.asset import Asset
|
||||
from typing import *
|
||||
|
||||
|
@ -19,7 +19,7 @@
|
||||
# <pep8 compliant>
|
||||
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:
|
@ -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