Skip to content
This repository has been archived by the owner on Sep 20, 2024. It is now read-only.

Workfiles: Support more workfile templates #1966

Merged
merged 19 commits into from
Aug 31, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions openpype/lib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@
get_linked_assets,
get_latest_version,

get_workfile_template_key,
get_workfile_template_key_from_context,
get_workdir_data,
get_workdir,
get_workdir_with_workdir_data,
Expand Down Expand Up @@ -189,6 +191,8 @@
"get_linked_assets",
"get_latest_version",

"get_workfile_template_key",
"get_workfile_template_key_from_context",
"get_workdir_data",
"get_workdir",
"get_workdir_with_workdir_data",
Expand Down
15 changes: 13 additions & 2 deletions openpype/lib/applications.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
from .local_settings import get_openpype_username
from .avalon_context import (
get_workdir_data,
get_workdir_with_workdir_data
get_workdir_with_workdir_data,
get_workfile_template_key_from_context
)

from .python_module_tools import (
Expand Down Expand Up @@ -1236,8 +1237,18 @@ def prepare_context_environments(data):

anatomy = data["anatomy"]

template_key = get_workfile_template_key_from_context(
asset_doc["name"],
task_name,
app.host_name,
project_name=project_name,
dbcon=data["dbcon"]
)

try:
workdir = get_workdir_with_workdir_data(workdir_data, anatomy)
workdir = get_workdir_with_workdir_data(
workdir_data, anatomy, template_key=template_key
)

except Exception as exc:
raise ApplicationLaunchFailed(
Expand Down
146 changes: 140 additions & 6 deletions openpype/lib/avalon_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,127 @@ def get_latest_version(asset_name, subset_name, dbcon=None, project_name=None):
return version_doc


def get_workfile_template_key_from_context(
asset_name, task_name, host_name, project_name=None,
dbcon=None, project_settings=None
):
"""Helper function to get template key for workfile template.

Do the same as `get_workfile_template_key` but returns value for "session
context".

It is required to pass one of 'dbcon' with already set project name or
'project_name' arguments.

Args:
asset_name(str): Name of asset document.
task_name(str): Task name for which is template key retrieved.
Must be available on asset document under `data.tasks`.
host_name(str): Name of host implementation for which is workfile
used.
project_name(str): Project name where asset and task is. Not required
when 'dbcon' is passed.
dbcon(AvalonMongoDB): Connection to mongo with already set project
under `AVALON_PROJECT`. Not required when 'project_name' is passed.
project_settings(dict): Project settings for passed 'project_name'.
Not required at all but makes function faster.
Raises:
ValueError: When both 'dbcon' and 'project_name' were not
passed.
"""
if not dbcon:
if not project_name:
raise ValueError((
"`get_workfile_template_key_from_context` requires to pass"
" one of 'dbcon' or 'project_name' arguments."
))
from avalon.api import AvalonMongoDB

dbcon = AvalonMongoDB()
dbcon.Session["AVALON_PROJECT"] = project_name

elif not project_name:
project_name = dbcon.Session["AVALON_PROJECT"]

asset_doc = dbcon.find_one(
{
"type": "asset",
"name": asset_name
},
{
"data.tasks": 1
}
)
asset_tasks = asset_doc.get("data", {}).get("tasks") or {}
task_info = asset_tasks.get(task_name) or {}
task_type = task_info.get("type")

return get_workfile_template_key(
task_type, host_name, project_name, project_settings
)


def get_workfile_template_key(
task_type, host_name, project_name=None, project_settings=None
):
"""Workfile template key which should be used to get workfile template.

Function is using profiles from project settings to return right template
for passet task type and host name.

One of 'project_name' or 'project_settings' must be passed it is preffered
to pass settings if are already available.

Args:
task_type(str): Name of task type.
host_name(str): Name of host implementation (e.g. "maya", "nuke", ...)
project_name(str): Name of project in which context should look for
settings. Not required if `project_settings` are passed.
project_settings(dict): Prepare project settings for project name.
Not needed if `project_name` is passed.

Raises:
ValueError: When both 'project_name' and 'project_settings' were not
passed.
"""
default = "work"
if not task_type or not host_name:
return default

if not project_settings:
if not project_name:
raise ValueError((
"`get_workfile_template_key` requires to pass"
" one of 'project_name' or 'project_settings' arguments."
))
project_settings = get_project_settings(project_name)

try:
profiles = (
project_settings
["global"]
["tools"]
["Workfiles"]
["workfile_template_profiles"]
)
except Exception:
profiles = []

if not profiles:
return default

from .profiles_filtering import filter_profiles

profile_filter = {
"task_types": task_type,
"hosts": host_name
}
profile = filter_profiles(profiles, profile_filter)
if profile:
return profile["workfile_template"] or default
return default


def get_workdir_data(project_doc, asset_doc, task_name, host_name):
"""Prepare data for workdir template filling from entered information.

Expand Down Expand Up @@ -373,7 +494,8 @@ def get_workdir_data(project_doc, asset_doc, task_name, host_name):


def get_workdir_with_workdir_data(
workdir_data, anatomy=None, project_name=None, template_key=None
workdir_data, anatomy=None, project_name=None,
template_key=None, dbcon=None
):
"""Fill workdir path from entered data and project's anatomy.

Expand All @@ -387,8 +509,10 @@ def get_workdir_with_workdir_data(
`project_name` is entered.
project_name (str): Project's name. Optional if `anatomy` is entered
otherwise Anatomy object is created with using the project name.
template_key (str): Key of work templates in anatomy templates. By
default is seto to `"work"`.
template_key (str): Key of work templates in anatomy templates. If not
passed `get_workfile_template_key_from_context` is used to get it.
dbcon(AvalonMongoDB): Mongo connection. Required only if 'template_key'
and 'project_name' are not passed.

Returns:
TemplateResult: Workdir path.
Expand All @@ -406,7 +530,13 @@ def get_workdir_with_workdir_data(
anatomy = Anatomy(project_name)

if not template_key:
template_key = "work"
template_key = get_workfile_template_key_from_context(
workdir_data["asset"],
workdir_data["task"],
workdir_data["app"],
project_name=workdir_data["project"]["name"],
dbcon=dbcon
)

anatomy_filled = anatomy.format(workdir_data)
# Output is TemplateResult object which contain usefull data
Expand Down Expand Up @@ -447,7 +577,9 @@ def get_workdir(
project_doc, asset_doc, task_name, host_name
)
# Output is TemplateResult object which contain usefull data
return get_workdir_with_workdir_data(workdir_data, anatomy, template_key)
return get_workdir_with_workdir_data(
workdir_data, anatomy, template_key=template_key
)


@with_avalon
Expand Down Expand Up @@ -516,7 +648,9 @@ def create_workfile_doc(asset_doc, task_name, filename, workdir, dbcon=None):
# Prepare anatomy
anatomy = Anatomy(project_doc["name"])
# Get workdir path (result is anatomy.TemplateResult)
template_workdir = get_workdir_with_workdir_data(workdir_data, anatomy)
template_workdir = get_workdir_with_workdir_data(
workdir_data, anatomy, dbcon=dbcon
)
template_workdir_path = str(template_workdir).replace("\\", "/")

# Replace slashses in workdir path where workfile is located
Expand Down
7 changes: 7 additions & 0 deletions openpype/settings/defaults/project_settings/global.json
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,13 @@
]
},
"Workfiles": {
"workfile_template_profiles": [
{
"task_types": [],
"hosts": [],
"workfile_template": "work"
}
],
"last_workfile_on_startup": [
{
"hosts": [],
Expand Down
4 changes: 3 additions & 1 deletion openpype/settings/entities/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@
ToolsEnumEntity,
TaskTypeEnumEntity,
ProvidersEnum,
DeadlineUrlEnumEntity
DeadlineUrlEnumEntity,
AnatomyTemplatesEnumEntity
)

from .list_entity import ListEntity
Expand Down Expand Up @@ -162,6 +163,7 @@
"TaskTypeEnumEntity",
"ProvidersEnum",
"DeadlineUrlEnumEntity",
"AnatomyTemplatesEnumEntity",

"ListEntity",

Expand Down
66 changes: 66 additions & 0 deletions openpype/settings/entities/enum_entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -494,3 +494,69 @@ def set_override_state(self, *args, **kwargs):

elif self._current_value not in self.valid_keys:
self._current_value = tuple(self.valid_keys)[0]


class AnatomyTemplatesEnumEntity(BaseEnumEntity):
schema_types = ["anatomy-templates-enum"]

def _item_initalization(self):
self.multiselection = False

self.enum_items = []
self.valid_keys = set()

enum_default = self.schema_data.get("default") or "work"

self.value_on_not_set = enum_default
self.valid_value_types = (STRING_TYPE,)

# GUI attribute
self.placeholder = self.schema_data.get("placeholder")

def _get_enum_values(self):
templates_entity = self.get_entity_from_path(
"project_anatomy/templates"
)

valid_keys = set()
enum_items_list = []

others_entity = None
for key, entity in templates_entity.items():
# Skip defaults key
if key == "defaults":
continue

if key == "others":
others_entity = entity
continue

label = key
if hasattr(entity, "label"):
label = entity.label or label

enum_items_list.append({key: label})
valid_keys.add(key)

if others_entity is not None:
get_child_label_func = getattr(
others_entity, "get_child_label", None
)
for key, child_entity in others_entity.items():
label = key
if callable(get_child_label_func):
label = get_child_label_func(child_entity) or label

enum_items_list.append({key: label})
valid_keys.add(key)

return enum_items_list, valid_keys

def set_override_state(self, *args, **kwargs):
super(AnatomyTemplatesEnumEntity, self).set_override_state(
*args, **kwargs
)

self.enum_items, self.valid_keys = self._get_enum_values()
if self._current_value not in self.valid_keys:
self._current_value = self.value_on_not_set
14 changes: 14 additions & 0 deletions openpype/settings/entities/schemas/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,20 @@ How output of the schema could look like on save:
}
```

### anatomy-templates-enum
- enumeration of all available anatomy template keys
- have only single selection mode
- it is possible to define default value `default`
- `"work"` is used if default value is not specified
```
{
"key": "host",
"label": "Host name",
"type": "anatomy-templates-enum",
"default": "publish"
}
```

### hosts-enum
- enumeration of available hosts
- multiselection can be allowed with setting key `"multiselection"` to `True` (Default: `False`)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,37 @@
"key": "Workfiles",
"label": "Workfiles",
"children": [
{
"type": "list",
"key": "workfile_template_profiles",
"label": "Workfile template profiles",
"use_label_wrap": true,
"object_type": {
"type": "dict",
"children": [
{
"key": "task_types",
"label": "Task types",
"type": "task-types-enum"
},
{
"type": "hosts-enum",
"key": "hosts",
"label": "Hosts",
"multiselection": true
},
{
"type": "splitter"
},
{
"key": "workfile_template",
"label": "Workfile template",
"type": "anatomy-templates-enum",
"multiselection": false
}
]
}
},
{
"type": "list",
"key": "last_workfile_on_startup",
Expand Down
Loading