mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge branch 'develop' into feature/909-define-basic-trait-type-using-dataclasses
This commit is contained in:
commit
2b05c5209c
11 changed files with 453 additions and 162 deletions
16
.github/workflows/upload_to_ynput_cloud.yml
vendored
Normal file
16
.github/workflows/upload_to_ynput_cloud.yml
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
name: 📤 Upload to Ynput Cloud
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
call-upload-to-ynput-cloud:
|
||||
uses: ynput/ops-repo-automation/.github/workflows/upload_to_ynput_cloud.yml@main
|
||||
secrets:
|
||||
CI_EMAIL: ${{ secrets.CI_EMAIL }}
|
||||
CI_USER: ${{ secrets.CI_USER }}
|
||||
YNPUT_BOT_TOKEN: ${{ secrets.YNPUT_BOT_TOKEN }}
|
||||
YNPUT_CLOUD_URL: ${{ secrets.YNPUT_CLOUD_URL }}
|
||||
YNPUT_CLOUD_TOKEN: ${{ secrets.YNPUT_CLOUD_TOKEN }}
|
||||
|
|
@ -327,8 +327,8 @@ class UISeparatorDef(UIDef):
|
|||
class UILabelDef(UIDef):
|
||||
type = "label"
|
||||
|
||||
def __init__(self, label, key=None):
|
||||
super().__init__(label=label, key=key)
|
||||
def __init__(self, label, key=None, *args, **kwargs):
|
||||
super().__init__(label=label, key=key, *args, **kwargs)
|
||||
|
||||
def _def_type_compare(self, other: "UILabelDef") -> bool:
|
||||
return self.label == other.label
|
||||
|
|
@ -523,7 +523,10 @@ class TextDef(AbstractAttrDef):
|
|||
|
||||
def serialize(self):
|
||||
data = super().serialize()
|
||||
data["regex"] = self.regex.pattern
|
||||
regex = None
|
||||
if self.regex is not None:
|
||||
regex = self.regex.pattern
|
||||
data["regex"] = regex
|
||||
data["multiline"] = self.multiline
|
||||
data["placeholder"] = self.placeholder
|
||||
return data
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import os
|
||||
import sys
|
||||
import uuid
|
||||
import getpass
|
||||
import logging
|
||||
import platform
|
||||
|
|
@ -11,12 +10,12 @@ import copy
|
|||
|
||||
from . import Terminal
|
||||
|
||||
# Check for `unicode` in builtins
|
||||
USE_UNICODE = hasattr(__builtins__, "unicode")
|
||||
|
||||
|
||||
class LogStreamHandler(logging.StreamHandler):
|
||||
""" StreamHandler class designed to handle utf errors in python 2.x hosts.
|
||||
"""StreamHandler class.
|
||||
|
||||
This was originally designed to handle UTF errors in python 2.x hosts,
|
||||
however currently solely remains for backwards compatibility.
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -25,49 +24,27 @@ class LogStreamHandler(logging.StreamHandler):
|
|||
self.enabled = True
|
||||
|
||||
def enable(self):
|
||||
""" Enable StreamHandler
|
||||
"""Enable StreamHandler
|
||||
|
||||
Used to silence output
|
||||
Make StreamHandler output again
|
||||
"""
|
||||
self.enabled = True
|
||||
|
||||
def disable(self):
|
||||
""" Disable StreamHandler
|
||||
"""Disable StreamHandler
|
||||
|
||||
Make StreamHandler output again
|
||||
Used to silence output
|
||||
"""
|
||||
self.enabled = False
|
||||
|
||||
def emit(self, record):
|
||||
if not self.enable:
|
||||
if not self.enabled or self.stream is None:
|
||||
return
|
||||
try:
|
||||
msg = self.format(record)
|
||||
msg = Terminal.log(msg)
|
||||
stream = self.stream
|
||||
if stream is None:
|
||||
return
|
||||
fs = "%s\n"
|
||||
# if no unicode support...
|
||||
if not USE_UNICODE:
|
||||
stream.write(fs % msg)
|
||||
else:
|
||||
try:
|
||||
if (isinstance(msg, unicode) and # noqa: F821
|
||||
getattr(stream, 'encoding', None)):
|
||||
ufs = u'%s\n'
|
||||
try:
|
||||
stream.write(ufs % msg)
|
||||
except UnicodeEncodeError:
|
||||
stream.write((ufs % msg).encode(stream.encoding))
|
||||
else:
|
||||
if (getattr(stream, 'encoding', 'utf-8')):
|
||||
ufs = u'%s\n'
|
||||
stream.write(ufs % unicode(msg)) # noqa: F821
|
||||
else:
|
||||
stream.write(fs % msg)
|
||||
except UnicodeError:
|
||||
stream.write(fs % msg.encode("UTF-8"))
|
||||
stream.write(f"{msg}\n")
|
||||
self.flush()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
|
|
@ -141,8 +118,6 @@ class Logger:
|
|||
process_data = None
|
||||
# Cached process name or ability to set different process name
|
||||
_process_name = None
|
||||
# TODO Remove 'mongo_process_id' in 1.x.x
|
||||
mongo_process_id = uuid.uuid4().hex
|
||||
|
||||
@classmethod
|
||||
def get_logger(cls, name=None):
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import os
|
||||
import re
|
||||
import logging
|
||||
import platform
|
||||
|
||||
import clique
|
||||
|
||||
|
|
@ -38,31 +37,7 @@ def create_hard_link(src_path, dst_path):
|
|||
dst_path(str): Full path to a file where a link of source will be
|
||||
added.
|
||||
"""
|
||||
# Use `os.link` if is available
|
||||
# - should be for all platforms with newer python versions
|
||||
if hasattr(os, "link"):
|
||||
os.link(src_path, dst_path)
|
||||
return
|
||||
|
||||
# Windows implementation of hardlinks
|
||||
# - used in Python 2
|
||||
if platform.system().lower() == "windows":
|
||||
import ctypes
|
||||
from ctypes.wintypes import BOOL
|
||||
CreateHardLink = ctypes.windll.kernel32.CreateHardLinkW
|
||||
CreateHardLink.argtypes = [
|
||||
ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_void_p
|
||||
]
|
||||
CreateHardLink.restype = BOOL
|
||||
|
||||
res = CreateHardLink(dst_path, src_path, None)
|
||||
if res == 0:
|
||||
raise ctypes.WinError()
|
||||
return
|
||||
# Raises not implemented error if gets here
|
||||
raise NotImplementedError(
|
||||
"Implementation of hardlink for current environment is missing."
|
||||
)
|
||||
os.link(src_path, dst_path)
|
||||
|
||||
|
||||
def collect_frames(files):
|
||||
|
|
@ -210,7 +185,7 @@ def get_last_version_from_path(path_dir, filter):
|
|||
assert isinstance(filter, list) and (
|
||||
len(filter) != 0), "`filter` argument needs to be list and not empty"
|
||||
|
||||
filtred_files = list()
|
||||
filtered_files = list()
|
||||
|
||||
# form regex for filtering
|
||||
pattern = r".*".join(filter)
|
||||
|
|
@ -218,10 +193,10 @@ def get_last_version_from_path(path_dir, filter):
|
|||
for file in os.listdir(path_dir):
|
||||
if not re.findall(pattern, file):
|
||||
continue
|
||||
filtred_files.append(file)
|
||||
filtered_files.append(file)
|
||||
|
||||
if filtred_files:
|
||||
sorted(filtred_files)
|
||||
return filtred_files[-1]
|
||||
if filtered_files:
|
||||
filtered_files.sort()
|
||||
return filtered_files[-1]
|
||||
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ from typing import (
|
|||
Iterable,
|
||||
Tuple,
|
||||
List,
|
||||
Set,
|
||||
Dict,
|
||||
Any,
|
||||
Callable,
|
||||
|
|
@ -252,8 +253,10 @@ class CreateContext:
|
|||
# Shared data across creators during collection phase
|
||||
self._collection_shared_data = None
|
||||
|
||||
# Context validation cache
|
||||
self._folder_id_by_folder_path = {}
|
||||
# Entities cache
|
||||
self._folder_entities_by_path = {}
|
||||
self._task_entities_by_id = {}
|
||||
self._task_ids_by_folder_path = {}
|
||||
self._task_names_by_folder_path = {}
|
||||
|
||||
self.thumbnail_paths_by_instance_id = {}
|
||||
|
|
@ -356,12 +359,12 @@ class CreateContext:
|
|||
return self._host_is_valid
|
||||
|
||||
@property
|
||||
def host_name(self):
|
||||
def host_name(self) -> str:
|
||||
if hasattr(self.host, "name"):
|
||||
return self.host.name
|
||||
return os.environ["AYON_HOST_NAME"]
|
||||
|
||||
def get_current_project_name(self):
|
||||
def get_current_project_name(self) -> Optional[str]:
|
||||
"""Project name which was used as current context on context reset.
|
||||
|
||||
Returns:
|
||||
|
|
@ -370,7 +373,7 @@ class CreateContext:
|
|||
|
||||
return self._current_project_name
|
||||
|
||||
def get_current_folder_path(self):
|
||||
def get_current_folder_path(self) -> Optional[str]:
|
||||
"""Folder path which was used as current context on context reset.
|
||||
|
||||
Returns:
|
||||
|
|
@ -379,7 +382,7 @@ class CreateContext:
|
|||
|
||||
return self._current_folder_path
|
||||
|
||||
def get_current_task_name(self):
|
||||
def get_current_task_name(self) -> Optional[str]:
|
||||
"""Task name which was used as current context on context reset.
|
||||
|
||||
Returns:
|
||||
|
|
@ -388,7 +391,7 @@ class CreateContext:
|
|||
|
||||
return self._current_task_name
|
||||
|
||||
def get_current_task_type(self):
|
||||
def get_current_task_type(self) -> Optional[str]:
|
||||
"""Task type which was used as current context on context reset.
|
||||
|
||||
Returns:
|
||||
|
|
@ -403,7 +406,7 @@ class CreateContext:
|
|||
self._current_task_type = task_type
|
||||
return self._current_task_type
|
||||
|
||||
def get_current_project_entity(self):
|
||||
def get_current_project_entity(self) -> Optional[Dict[str, Any]]:
|
||||
"""Project entity for current context project.
|
||||
|
||||
Returns:
|
||||
|
|
@ -419,26 +422,21 @@ class CreateContext:
|
|||
self._current_project_entity = project_entity
|
||||
return copy.deepcopy(self._current_project_entity)
|
||||
|
||||
def get_current_folder_entity(self):
|
||||
def get_current_folder_entity(self) -> Optional[Dict[str, Any]]:
|
||||
"""Folder entity for current context folder.
|
||||
|
||||
Returns:
|
||||
Union[dict[str, Any], None]: Folder entity.
|
||||
Optional[dict[str, Any]]: Folder entity.
|
||||
|
||||
"""
|
||||
if self._current_folder_entity is not _NOT_SET:
|
||||
return copy.deepcopy(self._current_folder_entity)
|
||||
folder_entity = None
|
||||
|
||||
folder_path = self.get_current_folder_path()
|
||||
if folder_path:
|
||||
project_name = self.get_current_project_name()
|
||||
folder_entity = ayon_api.get_folder_by_path(
|
||||
project_name, folder_path
|
||||
)
|
||||
self._current_folder_entity = folder_entity
|
||||
self._current_folder_entity = self.get_folder_entity(folder_path)
|
||||
return copy.deepcopy(self._current_folder_entity)
|
||||
|
||||
def get_current_task_entity(self):
|
||||
def get_current_task_entity(self) -> Optional[Dict[str, Any]]:
|
||||
"""Task entity for current context task.
|
||||
|
||||
Returns:
|
||||
|
|
@ -447,18 +445,12 @@ class CreateContext:
|
|||
"""
|
||||
if self._current_task_entity is not _NOT_SET:
|
||||
return copy.deepcopy(self._current_task_entity)
|
||||
task_entity = None
|
||||
|
||||
folder_path = self.get_current_folder_path()
|
||||
task_name = self.get_current_task_name()
|
||||
if task_name:
|
||||
folder_entity = self.get_current_folder_entity()
|
||||
if folder_entity:
|
||||
project_name = self.get_current_project_name()
|
||||
task_entity = ayon_api.get_task_by_name(
|
||||
project_name,
|
||||
folder_id=folder_entity["id"],
|
||||
task_name=task_name
|
||||
)
|
||||
self._current_task_entity = task_entity
|
||||
self._current_task_entity = self.get_task_entity(
|
||||
folder_path, task_name
|
||||
)
|
||||
return copy.deepcopy(self._current_task_entity)
|
||||
|
||||
def get_current_workfile_path(self):
|
||||
|
|
@ -566,8 +558,13 @@ class CreateContext:
|
|||
|
||||
# Give ability to store shared data for collection phase
|
||||
self._collection_shared_data = {}
|
||||
self._folder_id_by_folder_path = {}
|
||||
|
||||
self._folder_entities_by_path = {}
|
||||
self._task_entities_by_id = {}
|
||||
|
||||
self._task_ids_by_folder_path = {}
|
||||
self._task_names_by_folder_path = {}
|
||||
|
||||
self._event_hub.clear_callbacks()
|
||||
|
||||
def reset_finalization(self):
|
||||
|
|
@ -1468,6 +1465,260 @@ class CreateContext:
|
|||
if failed_info:
|
||||
raise CreatorsCreateFailed(failed_info)
|
||||
|
||||
def get_folder_entities(self, folder_paths: Iterable[str]):
|
||||
"""Get folder entities by paths.
|
||||
|
||||
Args:
|
||||
folder_paths (Iterable[str]): Folder paths.
|
||||
|
||||
Returns:
|
||||
Dict[str, Optional[Dict[str, Any]]]: Folder entities by path.
|
||||
|
||||
"""
|
||||
output = {
|
||||
folder_path: None
|
||||
for folder_path in folder_paths
|
||||
}
|
||||
remainder_paths = set()
|
||||
for folder_path in output:
|
||||
# Skip invalid folder paths (folder name or empty path)
|
||||
if not folder_path or "/" not in folder_path:
|
||||
continue
|
||||
|
||||
if folder_path not in self._folder_entities_by_path:
|
||||
remainder_paths.add(folder_path)
|
||||
continue
|
||||
|
||||
output[folder_path] = self._folder_entities_by_path[folder_path]
|
||||
|
||||
if not remainder_paths:
|
||||
return output
|
||||
|
||||
found_paths = set()
|
||||
for folder_entity in ayon_api.get_folders(
|
||||
self.project_name,
|
||||
folder_paths=remainder_paths,
|
||||
):
|
||||
folder_path = folder_entity["path"]
|
||||
found_paths.add(folder_path)
|
||||
output[folder_path] = folder_entity
|
||||
self._folder_entities_by_path[folder_path] = folder_entity
|
||||
|
||||
# Cache empty folder entities
|
||||
for path in remainder_paths - found_paths:
|
||||
self._folder_entities_by_path[path] = None
|
||||
|
||||
return output
|
||||
|
||||
def get_task_entities(
|
||||
self,
|
||||
task_names_by_folder_paths: Dict[str, Set[str]]
|
||||
) -> Dict[str, Dict[str, Optional[Dict[str, Any]]]]:
|
||||
"""Get task entities by folder path and task name.
|
||||
|
||||
Entities are cached until reset.
|
||||
|
||||
Args:
|
||||
task_names_by_folder_paths (Dict[str, Set[str]]): Task names by
|
||||
folder path.
|
||||
|
||||
Returns:
|
||||
Dict[str, Dict[str, Dict[str, Any]]]: Task entities by folder path
|
||||
and task name.
|
||||
|
||||
"""
|
||||
output = {}
|
||||
for folder_path, task_names in task_names_by_folder_paths.items():
|
||||
if folder_path is None:
|
||||
continue
|
||||
output[folder_path] = {
|
||||
task_name: None
|
||||
for task_name in task_names
|
||||
if task_name is not None
|
||||
}
|
||||
|
||||
missing_folder_paths = set()
|
||||
for folder_path, output_task_entities_by_name in output.items():
|
||||
if not output_task_entities_by_name:
|
||||
continue
|
||||
|
||||
if folder_path not in self._task_ids_by_folder_path:
|
||||
missing_folder_paths.add(folder_path)
|
||||
continue
|
||||
|
||||
all_tasks_filled = True
|
||||
task_ids = self._task_ids_by_folder_path[folder_path]
|
||||
task_entities_by_name = {}
|
||||
for task_id in task_ids:
|
||||
task_entity = self._task_entities_by_id.get(task_id)
|
||||
if task_entity is None:
|
||||
all_tasks_filled = False
|
||||
continue
|
||||
task_entities_by_name[task_entity["name"]] = task_entity
|
||||
|
||||
any_missing = False
|
||||
for task_name in set(output_task_entities_by_name):
|
||||
task_entity = task_entities_by_name.get(task_name)
|
||||
if task_entity is None:
|
||||
any_missing = True
|
||||
continue
|
||||
|
||||
output_task_entities_by_name[task_name] = task_entity
|
||||
|
||||
if any_missing and not all_tasks_filled:
|
||||
missing_folder_paths.add(folder_path)
|
||||
|
||||
if not missing_folder_paths:
|
||||
return output
|
||||
|
||||
folder_entities_by_path = self.get_folder_entities(
|
||||
missing_folder_paths
|
||||
)
|
||||
folder_path_by_id = {}
|
||||
for folder_path, folder_entity in folder_entities_by_path.items():
|
||||
if folder_entity is not None:
|
||||
folder_path_by_id[folder_entity["id"]] = folder_path
|
||||
|
||||
if not folder_path_by_id:
|
||||
return output
|
||||
|
||||
task_entities_by_parent_id = collections.defaultdict(list)
|
||||
for task_entity in ayon_api.get_tasks(
|
||||
self.project_name,
|
||||
folder_ids=folder_path_by_id.keys()
|
||||
):
|
||||
folder_id = task_entity["folderId"]
|
||||
task_entities_by_parent_id[folder_id].append(task_entity)
|
||||
|
||||
for folder_id, task_entities in task_entities_by_parent_id.items():
|
||||
folder_path = folder_path_by_id[folder_id]
|
||||
task_ids = set()
|
||||
task_names = set()
|
||||
for task_entity in task_entities:
|
||||
task_id = task_entity["id"]
|
||||
task_name = task_entity["name"]
|
||||
task_ids.add(task_id)
|
||||
task_names.add(task_name)
|
||||
self._task_entities_by_id[task_id] = task_entity
|
||||
|
||||
output[folder_path][task_name] = task_entity
|
||||
self._task_ids_by_folder_path[folder_path] = task_ids
|
||||
self._task_names_by_folder_path[folder_path] = task_names
|
||||
|
||||
return output
|
||||
|
||||
def get_folder_entity(
|
||||
self,
|
||||
folder_path: Optional[str],
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""Get folder entity by path.
|
||||
|
||||
Entities are cached until reset.
|
||||
|
||||
Args:
|
||||
folder_path (Optional[str]): Folder path.
|
||||
|
||||
Returns:
|
||||
Optional[Dict[str, Any]]: Folder entity.
|
||||
|
||||
"""
|
||||
if not folder_path:
|
||||
return None
|
||||
return self.get_folder_entities([folder_path]).get(folder_path)
|
||||
|
||||
def get_task_entity(
|
||||
self,
|
||||
folder_path: Optional[str],
|
||||
task_name: Optional[str],
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""Get task entity by name and folder path.
|
||||
|
||||
Entities are cached until reset.
|
||||
|
||||
Args:
|
||||
folder_path (Optional[str]): Folder path.
|
||||
task_name (Optional[str]): Task name.
|
||||
|
||||
Returns:
|
||||
Optional[Dict[str, Any]]: Task entity.
|
||||
|
||||
"""
|
||||
if not folder_path or not task_name:
|
||||
return None
|
||||
|
||||
output = self.get_task_entities({folder_path: {task_name}})
|
||||
return output.get(folder_path, {}).get(task_name)
|
||||
|
||||
def get_instances_folder_entities(
|
||||
self, instances: Optional[Iterable["CreatedInstance"]] = None
|
||||
) -> Dict[str, Optional[Dict[str, Any]]]:
|
||||
if instances is None:
|
||||
instances = self._instances_by_id.values()
|
||||
instances = list(instances)
|
||||
output = {
|
||||
instance.id: None
|
||||
for instance in instances
|
||||
}
|
||||
if not instances:
|
||||
return output
|
||||
|
||||
folder_paths = {
|
||||
instance.get("folderPath")
|
||||
for instance in instances
|
||||
}
|
||||
folder_paths.discard(None)
|
||||
folder_entities_by_path = self.get_folder_entities(folder_paths)
|
||||
for instance in instances:
|
||||
folder_path = instance.get("folderPath")
|
||||
output[instance.id] = folder_entities_by_path.get(folder_path)
|
||||
return output
|
||||
|
||||
def get_instances_task_entities(
|
||||
self, instances: Optional[Iterable["CreatedInstance"]] = None
|
||||
):
|
||||
"""Get task entities for instances.
|
||||
|
||||
Args:
|
||||
instances (Optional[Iterable[CreatedInstance]]): Instances to
|
||||
get task entities. If not provided all instances are used.
|
||||
|
||||
Returns:
|
||||
Dict[str, Optional[Dict[str, Any]]]: Task entity by instance id.
|
||||
|
||||
"""
|
||||
if instances is None:
|
||||
instances = self._instances_by_id.values()
|
||||
instances = list(instances)
|
||||
|
||||
output = {
|
||||
instance.id: None
|
||||
for instance in instances
|
||||
}
|
||||
if not instances:
|
||||
return output
|
||||
|
||||
filtered_instances = []
|
||||
task_names_by_folder_path = collections.defaultdict(set)
|
||||
for instance in instances:
|
||||
folder_path = instance.get("folderPath")
|
||||
task_name = instance.get("task")
|
||||
if not folder_path or not task_name:
|
||||
continue
|
||||
filtered_instances.append(instance)
|
||||
task_names_by_folder_path[folder_path].add(task_name)
|
||||
|
||||
task_entities_by_folder_path = self.get_task_entities(
|
||||
task_names_by_folder_path
|
||||
)
|
||||
for instance in filtered_instances:
|
||||
folder_path = instance["folderPath"]
|
||||
task_name = instance["task"]
|
||||
output[instance.id] = (
|
||||
task_entities_by_folder_path[folder_path][task_name]
|
||||
)
|
||||
|
||||
return output
|
||||
|
||||
def get_instances_context_info(
|
||||
self, instances: Optional[Iterable["CreatedInstance"]] = None
|
||||
) -> Dict[str, InstanceContextInfo]:
|
||||
|
|
@ -1508,15 +1759,16 @@ class CreateContext:
|
|||
if instance.has_promised_context:
|
||||
context_info.folder_is_valid = True
|
||||
context_info.task_is_valid = True
|
||||
# NOTE missing task type
|
||||
continue
|
||||
# TODO allow context promise
|
||||
folder_path = context_info.folder_path
|
||||
if not folder_path:
|
||||
continue
|
||||
|
||||
if folder_path in self._folder_id_by_folder_path:
|
||||
folder_id = self._folder_id_by_folder_path[folder_path]
|
||||
if folder_id is None:
|
||||
if folder_path in self._folder_entities_by_path:
|
||||
folder_entity = self._folder_entities_by_path[folder_path]
|
||||
if folder_entity is None:
|
||||
continue
|
||||
context_info.folder_is_valid = True
|
||||
|
||||
|
|
@ -1535,72 +1787,78 @@ class CreateContext:
|
|||
|
||||
# Backwards compatibility for cases where folder name is set instead
|
||||
# of folder path
|
||||
folder_names = set()
|
||||
folder_paths = set()
|
||||
for folder_path in task_names_by_folder_path.keys():
|
||||
task_names_by_folder_name = {}
|
||||
task_names_by_folder_path_clean = {}
|
||||
for folder_path, task_names in task_names_by_folder_path.items():
|
||||
if folder_path is None:
|
||||
pass
|
||||
elif "/" in folder_path:
|
||||
folder_paths.add(folder_path)
|
||||
else:
|
||||
folder_names.add(folder_path)
|
||||
continue
|
||||
|
||||
folder_paths_by_id = {}
|
||||
if folder_paths:
|
||||
clean_task_names = {
|
||||
task_name
|
||||
for task_name in task_names
|
||||
if task_name
|
||||
}
|
||||
|
||||
if "/" not in folder_path:
|
||||
task_names_by_folder_name[folder_path] = clean_task_names
|
||||
continue
|
||||
|
||||
folder_paths.add(folder_path)
|
||||
if not clean_task_names:
|
||||
continue
|
||||
|
||||
task_names_by_folder_path_clean[folder_path] = clean_task_names
|
||||
|
||||
folder_paths_by_name = collections.defaultdict(list)
|
||||
if task_names_by_folder_name:
|
||||
for folder_entity in ayon_api.get_folders(
|
||||
project_name,
|
||||
folder_paths=folder_paths,
|
||||
fields={"id", "path"}
|
||||
folder_names=task_names_by_folder_name.keys(),
|
||||
fields={"name", "path"}
|
||||
):
|
||||
folder_id = folder_entity["id"]
|
||||
folder_path = folder_entity["path"]
|
||||
folder_paths_by_id[folder_id] = folder_path
|
||||
self._folder_id_by_folder_path[folder_path] = folder_id
|
||||
|
||||
folder_entities_by_name = collections.defaultdict(list)
|
||||
if folder_names:
|
||||
for folder_entity in ayon_api.get_folders(
|
||||
project_name,
|
||||
folder_names=folder_names,
|
||||
fields={"id", "name", "path"}
|
||||
):
|
||||
folder_id = folder_entity["id"]
|
||||
folder_name = folder_entity["name"]
|
||||
folder_path = folder_entity["path"]
|
||||
folder_paths_by_id[folder_id] = folder_path
|
||||
folder_entities_by_name[folder_name].append(folder_entity)
|
||||
self._folder_id_by_folder_path[folder_path] = folder_id
|
||||
folder_paths_by_name[folder_name].append(folder_path)
|
||||
|
||||
tasks_entities = ayon_api.get_tasks(
|
||||
project_name,
|
||||
folder_ids=folder_paths_by_id.keys(),
|
||||
fields={"name", "folderId"}
|
||||
folder_path_by_name = {}
|
||||
for folder_name, paths in folder_paths_by_name.items():
|
||||
if len(paths) != 1:
|
||||
continue
|
||||
path = paths[0]
|
||||
folder_path_by_name[folder_name] = path
|
||||
folder_paths.add(path)
|
||||
clean_task_names = task_names_by_folder_name[folder_name]
|
||||
if not clean_task_names:
|
||||
continue
|
||||
folder_task_names = task_names_by_folder_path_clean.setdefault(
|
||||
path, set()
|
||||
)
|
||||
folder_task_names |= clean_task_names
|
||||
|
||||
folder_entities_by_path = self.get_folder_entities(folder_paths)
|
||||
task_entities_by_folder_path = self.get_task_entities(
|
||||
task_names_by_folder_path_clean
|
||||
)
|
||||
|
||||
task_names_by_folder_path = collections.defaultdict(set)
|
||||
for task_entity in tasks_entities:
|
||||
folder_id = task_entity["folderId"]
|
||||
folder_path = folder_paths_by_id[folder_id]
|
||||
task_names_by_folder_path[folder_path].add(task_entity["name"])
|
||||
self._task_names_by_folder_path.update(task_names_by_folder_path)
|
||||
|
||||
for instance in to_validate:
|
||||
folder_path = instance["folderPath"]
|
||||
task_name = instance.get("task")
|
||||
if folder_path and "/" not in folder_path:
|
||||
folder_entities = folder_entities_by_name.get(folder_path)
|
||||
if len(folder_entities) == 1:
|
||||
folder_path = folder_entities[0]["path"]
|
||||
instance["folderPath"] = folder_path
|
||||
new_folder_path = folder_path_by_name.get(folder_path)
|
||||
if new_folder_path:
|
||||
folder_path = new_folder_path
|
||||
instance["folderPath"] = new_folder_path
|
||||
|
||||
if folder_path not in task_names_by_folder_path:
|
||||
folder_entity = folder_entities_by_path.get(folder_path)
|
||||
if not folder_entity:
|
||||
continue
|
||||
context_info = info_by_instance_id[instance.id]
|
||||
context_info.folder_is_valid = True
|
||||
|
||||
if (
|
||||
not task_name
|
||||
or task_name in task_names_by_folder_path[folder_path]
|
||||
or task_name in task_entities_by_folder_path[folder_path]
|
||||
):
|
||||
context_info.task_is_valid = True
|
||||
return info_by_instance_id
|
||||
|
|
|
|||
|
|
@ -383,6 +383,13 @@ def get_representations_delivery_template_data(
|
|||
continue
|
||||
|
||||
template_data = repre_entity["context"]
|
||||
# Bug in 'ayon_api', 'get_representations_hierarchy' did not fully
|
||||
# convert representation entity. Fixed in 'ayon_api' 1.0.10.
|
||||
if isinstance(template_data, str):
|
||||
con = ayon_api.get_server_api_connection()
|
||||
repre_entity = con._representation_conversion(repre_entity)
|
||||
template_data = repre_entity["context"]
|
||||
|
||||
template_data.update(copy.deepcopy(general_template_data))
|
||||
template_data.update(get_folder_template_data(
|
||||
repre_hierarchy.folder, project_name
|
||||
|
|
@ -402,5 +409,9 @@ def get_representations_delivery_template_data(
|
|||
"version": version_entity["version"],
|
||||
})
|
||||
_merge_data(template_data, repre_entity["context"])
|
||||
|
||||
# Remove roots from template data to auto-fill them with anatomy data
|
||||
template_data.pop("root", None)
|
||||
|
||||
output[repre_id] = template_data
|
||||
return output
|
||||
|
|
|
|||
|
|
@ -154,7 +154,9 @@ class ExtractHierarchyToAYON(pyblish.api.ContextPlugin):
|
|||
# TODO check if existing entity have 'task' type
|
||||
if task_entity is None:
|
||||
task_entity = entity_hub.add_new_task(
|
||||
task_info["type"],
|
||||
task_type=task_info["type"],
|
||||
# TODO change 'parent_id' to 'folder_id' when ayon api
|
||||
# is updated
|
||||
parent_id=entity.id,
|
||||
name=task_name
|
||||
)
|
||||
|
|
@ -182,7 +184,7 @@ class ExtractHierarchyToAYON(pyblish.api.ContextPlugin):
|
|||
folder_type = "Folder"
|
||||
|
||||
child_entity = entity_hub.add_new_folder(
|
||||
folder_type,
|
||||
folder_type=folder_type,
|
||||
parent_id=entity.id,
|
||||
name=child_name
|
||||
)
|
||||
|
|
|
|||
|
|
@ -458,7 +458,18 @@ class CollectUSDLayerContributions(pyblish.api.InstancePlugin,
|
|||
return new_instance
|
||||
|
||||
@classmethod
|
||||
def get_attribute_defs(cls):
|
||||
def get_attr_defs_for_instance(cls, create_context, instance):
|
||||
# Filtering of instance, if needed, can be customized
|
||||
if not cls.instance_matches_plugin_families(instance):
|
||||
return []
|
||||
|
||||
# Attributes logic
|
||||
publish_attributes = instance["publish_attributes"].get(
|
||||
cls.__name__, {})
|
||||
|
||||
visible = publish_attributes.get("contribution_enabled", True)
|
||||
variant_visible = visible and publish_attributes.get(
|
||||
"contribution_apply_as_variant", True)
|
||||
|
||||
return [
|
||||
UISeparatorDef("usd_container_settings1"),
|
||||
|
|
@ -484,7 +495,8 @@ class CollectUSDLayerContributions(pyblish.api.InstancePlugin,
|
|||
"the contribution itself will be added to the "
|
||||
"department layer."
|
||||
),
|
||||
default="usdAsset"),
|
||||
default="usdAsset",
|
||||
visible=visible),
|
||||
EnumDef("contribution_target_product_init",
|
||||
label="Initialize as",
|
||||
tooltip=(
|
||||
|
|
@ -495,7 +507,8 @@ class CollectUSDLayerContributions(pyblish.api.InstancePlugin,
|
|||
"setting will do nothing."
|
||||
),
|
||||
items=["asset", "shot"],
|
||||
default="asset"),
|
||||
default="asset",
|
||||
visible=visible),
|
||||
|
||||
# Asset layer, e.g. model.usd, look.usd, rig.usd
|
||||
EnumDef("contribution_layer",
|
||||
|
|
@ -507,7 +520,8 @@ class CollectUSDLayerContributions(pyblish.api.InstancePlugin,
|
|||
"the list) will contribute as a stronger opinion."
|
||||
),
|
||||
items=list(cls.contribution_layers.keys()),
|
||||
default="model"),
|
||||
default="model",
|
||||
visible=visible),
|
||||
BoolDef("contribution_apply_as_variant",
|
||||
label="Add as variant",
|
||||
tooltip=(
|
||||
|
|
@ -518,13 +532,16 @@ class CollectUSDLayerContributions(pyblish.api.InstancePlugin,
|
|||
"appended to as a sublayer to the department layer "
|
||||
"instead."
|
||||
),
|
||||
default=True),
|
||||
default=True,
|
||||
visible=visible),
|
||||
TextDef("contribution_variant_set_name",
|
||||
label="Variant Set Name",
|
||||
default="{layer}"),
|
||||
default="{layer}",
|
||||
visible=variant_visible),
|
||||
TextDef("contribution_variant",
|
||||
label="Variant Name",
|
||||
default="{variant}"),
|
||||
default="{variant}",
|
||||
visible=variant_visible),
|
||||
BoolDef("contribution_variant_is_default",
|
||||
label="Set as default variant selection",
|
||||
tooltip=(
|
||||
|
|
@ -535,10 +552,41 @@ class CollectUSDLayerContributions(pyblish.api.InstancePlugin,
|
|||
"The behavior is unpredictable if multiple instances "
|
||||
"for the same variant set have this enabled."
|
||||
),
|
||||
default=False),
|
||||
default=False,
|
||||
visible=variant_visible),
|
||||
UISeparatorDef("usd_container_settings3"),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def register_create_context_callbacks(cls, create_context):
|
||||
create_context.add_value_changed_callback(cls.on_values_changed)
|
||||
|
||||
@classmethod
|
||||
def on_values_changed(cls, event):
|
||||
"""Update instance attribute definitions on attribute changes."""
|
||||
|
||||
# Update attributes if any of the following plug-in attributes
|
||||
# change:
|
||||
keys = ["contribution_enabled", "contribution_apply_as_variant"]
|
||||
|
||||
for instance_change in event["changes"]:
|
||||
instance = instance_change["instance"]
|
||||
if not cls.instance_matches_plugin_families(instance):
|
||||
continue
|
||||
value_changes = instance_change["changes"]
|
||||
plugin_attribute_changes = (
|
||||
value_changes.get("publish_attributes", {})
|
||||
.get(cls.__name__, {}))
|
||||
|
||||
if not any(key in plugin_attribute_changes for key in keys):
|
||||
continue
|
||||
|
||||
# Update the attribute definitions
|
||||
new_attrs = cls.get_attr_defs_for_instance(
|
||||
event["create_context"], instance
|
||||
)
|
||||
instance.set_publish_plugin_attr_defs(cls.__name__, new_attrs)
|
||||
|
||||
|
||||
class CollectUSDLayerContributionsHoudiniLook(CollectUSDLayerContributions):
|
||||
"""
|
||||
|
|
@ -551,9 +599,12 @@ class CollectUSDLayerContributionsHoudiniLook(CollectUSDLayerContributions):
|
|||
label = CollectUSDLayerContributions.label + " (Look)"
|
||||
|
||||
@classmethod
|
||||
def get_attribute_defs(cls):
|
||||
defs = super(CollectUSDLayerContributionsHoudiniLook,
|
||||
cls).get_attribute_defs()
|
||||
def get_attr_defs_for_instance(cls, create_context, instance):
|
||||
# Filtering of instance, if needed, can be customized
|
||||
if not cls.instance_matches_plugin_families(instance):
|
||||
return []
|
||||
|
||||
defs = super().get_attr_defs_for_instance(create_context, instance)
|
||||
|
||||
# Update default for department layer to look
|
||||
layer_def = next(d for d in defs if d.key == "contribution_layer")
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Package declaring AYON addon 'core' version."""
|
||||
__version__ = "1.0.4+dev"
|
||||
__version__ = "1.0.6+dev"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
name = "core"
|
||||
title = "Core"
|
||||
version = "1.0.4+dev"
|
||||
version = "1.0.6+dev"
|
||||
|
||||
client_dir = "ayon_core"
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
[tool.poetry]
|
||||
name = "ayon-core"
|
||||
version = "1.0.4+dev"
|
||||
version = "1.0.6+dev"
|
||||
description = ""
|
||||
authors = ["Ynput Team <team@ynput.io>"]
|
||||
readme = "README.md"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue