mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 08:24:53 +01:00
Merge branch 'develop' into enhancement/874-publisher-editorial-linked-instances-with-grouping-view
This commit is contained in:
commit
39a925986d
16 changed files with 247 additions and 210 deletions
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
|
@ -35,6 +35,8 @@ body:
|
||||||
label: Version
|
label: Version
|
||||||
description: What version are you running? Look to AYON Tray
|
description: What version are you running? Look to AYON Tray
|
||||||
options:
|
options:
|
||||||
|
- 1.5.2
|
||||||
|
- 1.5.1
|
||||||
- 1.5.0
|
- 1.5.0
|
||||||
- 1.4.1
|
- 1.4.1
|
||||||
- 1.4.0
|
- 1.4.0
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import inspect
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
import collections
|
import collections
|
||||||
|
import warnings
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
@ -815,10 +816,26 @@ class AddonsManager:
|
||||||
|
|
||||||
Unknown keys are logged out.
|
Unknown keys are logged out.
|
||||||
|
|
||||||
|
Deprecated:
|
||||||
|
Use targeted methods 'collect_launcher_action_paths',
|
||||||
|
'collect_create_plugin_paths', 'collect_load_plugin_paths',
|
||||||
|
'collect_publish_plugin_paths' and
|
||||||
|
'collect_inventory_action_paths' to collect plugin paths.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: Output is dictionary with keys "publish", "create", "load",
|
dict: Output is dictionary with keys "publish", "create", "load",
|
||||||
"actions" and "inventory" each containing list of paths.
|
"actions" and "inventory" each containing list of paths.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
warnings.warn(
|
||||||
|
"Used deprecated method 'collect_plugin_paths'. Please use"
|
||||||
|
" targeted methods 'collect_launcher_action_paths',"
|
||||||
|
" 'collect_create_plugin_paths', 'collect_load_plugin_paths'"
|
||||||
|
" 'collect_publish_plugin_paths' and"
|
||||||
|
" 'collect_inventory_action_paths'",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2
|
||||||
|
)
|
||||||
# Output structure
|
# Output structure
|
||||||
output = {
|
output = {
|
||||||
"publish": [],
|
"publish": [],
|
||||||
|
|
@ -874,24 +891,28 @@ class AddonsManager:
|
||||||
if not isinstance(addon, IPluginPaths):
|
if not isinstance(addon, IPluginPaths):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
paths = None
|
||||||
method = getattr(addon, method_name)
|
method = getattr(addon, method_name)
|
||||||
try:
|
try:
|
||||||
paths = method(*args, **kwargs)
|
paths = method(*args, **kwargs)
|
||||||
except Exception:
|
except Exception:
|
||||||
self.log.warning(
|
self.log.warning(
|
||||||
(
|
"Failed to get plugin paths from addon"
|
||||||
"Failed to get plugin paths from addon"
|
f" '{addon.name}' using '{method_name}'.",
|
||||||
" '{}' using '{}'."
|
|
||||||
).format(addon.__class__.__name__, method_name),
|
|
||||||
exc_info=True
|
exc_info=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not paths:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if paths:
|
if isinstance(paths, str):
|
||||||
# Convert to list if value is not list
|
paths = [paths]
|
||||||
if not isinstance(paths, (list, tuple, set)):
|
self.log.warning(
|
||||||
paths = [paths]
|
f"Addon '{addon.name}' returned invalid output type"
|
||||||
output.extend(paths)
|
f" from '{method_name}'."
|
||||||
|
f" Got 'str' expected 'list[str]'."
|
||||||
|
)
|
||||||
|
output.extend(paths)
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def collect_launcher_action_paths(self):
|
def collect_launcher_action_paths(self):
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
"""Addon interfaces for AYON."""
|
"""Addon interfaces for AYON."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import warnings
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
from typing import TYPE_CHECKING, Callable, Optional, Type
|
from typing import TYPE_CHECKING, Callable, Optional, Type
|
||||||
|
|
||||||
|
|
@ -39,26 +40,29 @@ class AYONInterface(metaclass=_AYONInterfaceMeta):
|
||||||
|
|
||||||
|
|
||||||
class IPluginPaths(AYONInterface):
|
class IPluginPaths(AYONInterface):
|
||||||
"""Addon has plugin paths to return.
|
"""Addon wants to register plugin paths."""
|
||||||
|
|
||||||
Expected result is dictionary with keys "publish", "create", "load",
|
|
||||||
"actions" or "inventory" and values as list or string.
|
|
||||||
{
|
|
||||||
"publish": ["path/to/publish_plugins"]
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def get_plugin_paths(self) -> dict[str, list[str]]:
|
def get_plugin_paths(self) -> dict[str, list[str]]:
|
||||||
"""Return plugin paths for addon.
|
"""Return plugin paths for addon.
|
||||||
|
|
||||||
|
This method was abstract (required) in the past, so raise the required
|
||||||
|
'core' addon version when 'get_plugin_paths' is removed from
|
||||||
|
addon.
|
||||||
|
|
||||||
|
Deprecated:
|
||||||
|
Please implement specific methods 'get_create_plugin_paths',
|
||||||
|
'get_load_plugin_paths', 'get_inventory_action_paths' and
|
||||||
|
'get_publish_plugin_paths' to return plugin paths.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict[str, list[str]]: Plugin paths for addon.
|
dict[str, list[str]]: Plugin paths for addon.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
return {}
|
||||||
|
|
||||||
def _get_plugin_paths_by_type(
|
def _get_plugin_paths_by_type(
|
||||||
self, plugin_type: str) -> list[str]:
|
self, plugin_type: str
|
||||||
|
) -> list[str]:
|
||||||
"""Get plugin paths by type.
|
"""Get plugin paths by type.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -78,6 +82,24 @@ class IPluginPaths(AYONInterface):
|
||||||
|
|
||||||
if not isinstance(paths, (list, tuple, set)):
|
if not isinstance(paths, (list, tuple, set)):
|
||||||
paths = [paths]
|
paths = [paths]
|
||||||
|
|
||||||
|
new_function_name = "get_launcher_action_paths"
|
||||||
|
if plugin_type == "create":
|
||||||
|
new_function_name = "get_create_plugin_paths"
|
||||||
|
elif plugin_type == "load":
|
||||||
|
new_function_name = "get_load_plugin_paths"
|
||||||
|
elif plugin_type == "publish":
|
||||||
|
new_function_name = "get_publish_plugin_paths"
|
||||||
|
elif plugin_type == "inventory":
|
||||||
|
new_function_name = "get_inventory_action_paths"
|
||||||
|
|
||||||
|
warnings.warn(
|
||||||
|
f"Addon '{self.name}' returns '{plugin_type}' paths using"
|
||||||
|
" 'get_plugin_paths' method. Please implement"
|
||||||
|
f" '{new_function_name}' instead.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2
|
||||||
|
)
|
||||||
return paths
|
return paths
|
||||||
|
|
||||||
def get_launcher_action_paths(self) -> list[str]:
|
def get_launcher_action_paths(self) -> list[str]:
|
||||||
|
|
|
||||||
|
|
@ -944,6 +944,8 @@ class IWorkfileHost:
|
||||||
self._emit_workfile_save_event(event_data, after_save=False)
|
self._emit_workfile_save_event(event_data, after_save=False)
|
||||||
|
|
||||||
workdir = os.path.dirname(filepath)
|
workdir = os.path.dirname(filepath)
|
||||||
|
if not os.path.exists(workdir):
|
||||||
|
os.makedirs(workdir, exist_ok=True)
|
||||||
|
|
||||||
# Set 'AYON_WORKDIR' environment variable
|
# Set 'AYON_WORKDIR' environment variable
|
||||||
os.environ["AYON_WORKDIR"] = workdir
|
os.environ["AYON_WORKDIR"] = workdir
|
||||||
|
|
@ -1072,10 +1074,13 @@ class IWorkfileHost:
|
||||||
prepared_data=prepared_data,
|
prepared_data=prepared_data,
|
||||||
)
|
)
|
||||||
|
|
||||||
workfile_entities_by_path = {
|
workfile_entities_by_path = {}
|
||||||
workfile_entity["path"]: workfile_entity
|
for workfile_entity in list_workfiles_context.workfile_entities:
|
||||||
for workfile_entity in list_workfiles_context.workfile_entities
|
rootless_path = workfile_entity["path"]
|
||||||
}
|
path = os.path.normpath(
|
||||||
|
list_workfiles_context.anatomy.fill_root(rootless_path)
|
||||||
|
)
|
||||||
|
workfile_entities_by_path[path] = workfile_entity
|
||||||
|
|
||||||
workdir_data = get_template_data(
|
workdir_data = get_template_data(
|
||||||
list_workfiles_context.project_entity,
|
list_workfiles_context.project_entity,
|
||||||
|
|
@ -1114,10 +1119,10 @@ class IWorkfileHost:
|
||||||
|
|
||||||
rootless_path = f"{rootless_workdir}/{filename}"
|
rootless_path = f"{rootless_workdir}/{filename}"
|
||||||
workfile_entity = workfile_entities_by_path.pop(
|
workfile_entity = workfile_entities_by_path.pop(
|
||||||
rootless_path, None
|
filepath, None
|
||||||
)
|
)
|
||||||
version = comment = None
|
version = comment = None
|
||||||
if workfile_entity:
|
if workfile_entity is not None:
|
||||||
_data = workfile_entity["data"]
|
_data = workfile_entity["data"]
|
||||||
version = _data.get("version")
|
version = _data.get("version")
|
||||||
comment = _data.get("comment")
|
comment = _data.get("comment")
|
||||||
|
|
@ -1137,7 +1142,7 @@ class IWorkfileHost:
|
||||||
)
|
)
|
||||||
items.append(item)
|
items.append(item)
|
||||||
|
|
||||||
for workfile_entity in workfile_entities_by_path.values():
|
for filepath, workfile_entity in workfile_entities_by_path.items():
|
||||||
# Workfile entity is not in the filesystem
|
# Workfile entity is not in the filesystem
|
||||||
# but it is in the database
|
# but it is in the database
|
||||||
rootless_path = workfile_entity["path"]
|
rootless_path = workfile_entity["path"]
|
||||||
|
|
@ -1154,13 +1159,13 @@ class IWorkfileHost:
|
||||||
version = parsed_data.version
|
version = parsed_data.version
|
||||||
comment = parsed_data.comment
|
comment = parsed_data.comment
|
||||||
|
|
||||||
filepath = list_workfiles_context.anatomy.fill_root(rootless_path)
|
available = os.path.exists(filepath)
|
||||||
items.append(WorkfileInfo.new(
|
items.append(WorkfileInfo.new(
|
||||||
filepath,
|
filepath,
|
||||||
rootless_path,
|
rootless_path,
|
||||||
version=version,
|
version=version,
|
||||||
comment=comment,
|
comment=comment,
|
||||||
available=False,
|
available=available,
|
||||||
workfile_entity=workfile_entity,
|
workfile_entity=workfile_entity,
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import warnings
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
|
from typing import Optional, Any
|
||||||
|
|
||||||
import platformdirs
|
import platformdirs
|
||||||
import ayon_api
|
import ayon_api
|
||||||
|
|
@ -15,26 +16,31 @@ import ayon_api
|
||||||
_PLACEHOLDER = object()
|
_PLACEHOLDER = object()
|
||||||
|
|
||||||
|
|
||||||
|
# TODO should use 'KeyError' or 'Exception' as base
|
||||||
|
class RegistryItemNotFound(ValueError):
|
||||||
|
"""Raised when the item is not found in the keyring."""
|
||||||
|
|
||||||
|
|
||||||
class _Cache:
|
class _Cache:
|
||||||
username = None
|
username = None
|
||||||
|
|
||||||
|
|
||||||
def _get_ayon_appdirs(*args):
|
def _get_ayon_appdirs(*args: str) -> str:
|
||||||
return os.path.join(
|
return os.path.join(
|
||||||
platformdirs.user_data_dir("AYON", "Ynput"),
|
platformdirs.user_data_dir("AYON", "Ynput"),
|
||||||
*args
|
*args
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_ayon_appdirs(*args):
|
def get_ayon_appdirs(*args: str) -> str:
|
||||||
"""Local app data directory of AYON client.
|
"""Local app data directory of AYON client.
|
||||||
|
|
||||||
Deprecated:
|
Deprecated:
|
||||||
Use 'get_launcher_local_dir' or 'get_launcher_storage_dir' based on
|
Use 'get_launcher_local_dir' or 'get_launcher_storage_dir' based on
|
||||||
use-case. Deprecation added 24/08/09 (0.4.4-dev.1).
|
a use-case. Deprecation added 24/08/09 (0.4.4-dev.1).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
*args (Iterable[str]): Subdirectories/files in local app data dir.
|
*args (Iterable[str]): Subdirectories/files in the local app data dir.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: Path to directory/file in local app data dir.
|
str: Path to directory/file in local app data dir.
|
||||||
|
|
@ -52,7 +58,7 @@ def get_ayon_appdirs(*args):
|
||||||
|
|
||||||
|
|
||||||
def get_launcher_storage_dir(*subdirs: str) -> str:
|
def get_launcher_storage_dir(*subdirs: str) -> str:
|
||||||
"""Get storage directory for launcher.
|
"""Get a storage directory for launcher.
|
||||||
|
|
||||||
Storage directory is used for storing shims, addons, dependencies, etc.
|
Storage directory is used for storing shims, addons, dependencies, etc.
|
||||||
|
|
||||||
|
|
@ -77,14 +83,14 @@ def get_launcher_storage_dir(*subdirs: str) -> str:
|
||||||
|
|
||||||
|
|
||||||
def get_launcher_local_dir(*subdirs: str) -> str:
|
def get_launcher_local_dir(*subdirs: str) -> str:
|
||||||
"""Get local directory for launcher.
|
"""Get a local directory for launcher.
|
||||||
|
|
||||||
Local directory is used for storing machine or user specific data.
|
Local directory is used for storing machine or user-specific data.
|
||||||
|
|
||||||
The location is user specific.
|
The location is user-specific.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
This function should be called at least once on bootstrap.
|
This function should be called at least once on the bootstrap.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
*subdirs (str): Subdirectories relative to local dir.
|
*subdirs (str): Subdirectories relative to local dir.
|
||||||
|
|
@ -101,7 +107,7 @@ def get_launcher_local_dir(*subdirs: str) -> str:
|
||||||
|
|
||||||
|
|
||||||
def get_addons_resources_dir(addon_name: str, *args) -> str:
|
def get_addons_resources_dir(addon_name: str, *args) -> str:
|
||||||
"""Get directory for storing resources for addons.
|
"""Get a directory for storing resources for addons.
|
||||||
|
|
||||||
Some addons might need to store ad-hoc resources that are not part of
|
Some addons might need to store ad-hoc resources that are not part of
|
||||||
addon client package (e.g. because of size). Studio might define
|
addon client package (e.g. because of size). Studio might define
|
||||||
|
|
@ -111,7 +117,7 @@ def get_addons_resources_dir(addon_name: str, *args) -> str:
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
addon_name (str): Addon name.
|
addon_name (str): Addon name.
|
||||||
*args (str): Subfolders in resources directory.
|
*args (str): Subfolders in the resources directory.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: Path to resources directory.
|
str: Path to resources directory.
|
||||||
|
|
@ -124,6 +130,10 @@ def get_addons_resources_dir(addon_name: str, *args) -> str:
|
||||||
return os.path.join(addons_resources_dir, addon_name, *args)
|
return os.path.join(addons_resources_dir, addon_name, *args)
|
||||||
|
|
||||||
|
|
||||||
|
class _FakeException(Exception):
|
||||||
|
"""Placeholder exception used if real exception is not available."""
|
||||||
|
|
||||||
|
|
||||||
class AYONSecureRegistry:
|
class AYONSecureRegistry:
|
||||||
"""Store information using keyring.
|
"""Store information using keyring.
|
||||||
|
|
||||||
|
|
@ -134,9 +144,10 @@ class AYONSecureRegistry:
|
||||||
identify which data were created by AYON.
|
identify which data were created by AYON.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name(str): Name of registry used as identifier for data.
|
name(str): Name of registry used as the identifier for data.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, name):
|
def __init__(self, name: str) -> None:
|
||||||
try:
|
try:
|
||||||
import keyring
|
import keyring
|
||||||
|
|
||||||
|
|
@ -152,13 +163,12 @@ class AYONSecureRegistry:
|
||||||
keyring.set_keyring(Windows.WinVaultKeyring())
|
keyring.set_keyring(Windows.WinVaultKeyring())
|
||||||
|
|
||||||
# Force "AYON" prefix
|
# Force "AYON" prefix
|
||||||
self._name = "/".join(("AYON", name))
|
self._name = f"AYON/{name}"
|
||||||
|
|
||||||
def set_item(self, name, value):
|
def set_item(self, name: str, value: str) -> None:
|
||||||
# type: (str, str) -> None
|
"""Set sensitive item into the system's keyring.
|
||||||
"""Set sensitive item into system's keyring.
|
|
||||||
|
|
||||||
This uses `Keyring module`_ to save sensitive stuff into system's
|
This uses `Keyring module`_ to save sensitive stuff into the system's
|
||||||
keyring.
|
keyring.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -172,22 +182,26 @@ class AYONSecureRegistry:
|
||||||
import keyring
|
import keyring
|
||||||
|
|
||||||
keyring.set_password(self._name, name, value)
|
keyring.set_password(self._name, name, value)
|
||||||
|
self.get_item.cache_clear()
|
||||||
|
|
||||||
@lru_cache(maxsize=32)
|
@lru_cache(maxsize=32)
|
||||||
def get_item(self, name, default=_PLACEHOLDER):
|
def get_item(
|
||||||
"""Get value of sensitive item from system's keyring.
|
self, name: str, default: Any = _PLACEHOLDER
|
||||||
|
) -> Optional[str]:
|
||||||
|
"""Get value of sensitive item from the system's keyring.
|
||||||
|
|
||||||
See also `Keyring module`_
|
See also `Keyring module`_
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name (str): Name of the item.
|
name (str): Name of the item.
|
||||||
default (Any): Default value if item is not available.
|
default (Any): Default value if the item is not available.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
value (str): Value of the item.
|
value (str): Value of the item.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: If item doesn't exist and default is not defined.
|
RegistryItemNotFound: If the item doesn't exist and default
|
||||||
|
is not defined.
|
||||||
|
|
||||||
.. _Keyring module:
|
.. _Keyring module:
|
||||||
https://github.com/jaraco/keyring
|
https://github.com/jaraco/keyring
|
||||||
|
|
@ -195,21 +209,29 @@ class AYONSecureRegistry:
|
||||||
"""
|
"""
|
||||||
import keyring
|
import keyring
|
||||||
|
|
||||||
value = keyring.get_password(self._name, name)
|
# Capture 'ItemNotFoundException' exception (on linux)
|
||||||
|
try:
|
||||||
|
from secretstorage.exceptions import ItemNotFoundException
|
||||||
|
except ImportError:
|
||||||
|
ItemNotFoundException = _FakeException
|
||||||
|
|
||||||
|
try:
|
||||||
|
value = keyring.get_password(self._name, name)
|
||||||
|
except ItemNotFoundException:
|
||||||
|
value = None
|
||||||
|
|
||||||
if value is not None:
|
if value is not None:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
if default is not _PLACEHOLDER:
|
if default is not _PLACEHOLDER:
|
||||||
return default
|
return default
|
||||||
|
|
||||||
# NOTE Should raise `KeyError`
|
raise RegistryItemNotFound(
|
||||||
raise ValueError(
|
f"Item {self._name}:{name} not found in keyring."
|
||||||
"Item {}:{} does not exist in keyring.".format(self._name, name)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def delete_item(self, name):
|
def delete_item(self, name: str) -> None:
|
||||||
# type: (str) -> None
|
"""Delete value stored in the system's keyring.
|
||||||
"""Delete value stored in system's keyring.
|
|
||||||
|
|
||||||
See also `Keyring module`_
|
See also `Keyring module`_
|
||||||
|
|
||||||
|
|
@ -227,47 +249,38 @@ class AYONSecureRegistry:
|
||||||
|
|
||||||
|
|
||||||
class ASettingRegistry(ABC):
|
class ASettingRegistry(ABC):
|
||||||
"""Abstract class defining structure of **SettingRegistry** class.
|
"""Abstract class to defining structure of registry class.
|
||||||
|
|
||||||
It is implementing methods to store secure items into keyring, otherwise
|
|
||||||
mechanism for storing common items must be implemented in abstract
|
|
||||||
methods.
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
_name (str): Registry names.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
def __init__(self, name: str) -> None:
|
||||||
def __init__(self, name):
|
|
||||||
# type: (str) -> ASettingRegistry
|
|
||||||
super(ASettingRegistry, self).__init__()
|
|
||||||
|
|
||||||
self._name = name
|
self._name = name
|
||||||
self._items = {}
|
|
||||||
|
|
||||||
def set_item(self, name, value):
|
|
||||||
# type: (str, str) -> None
|
|
||||||
"""Set item to settings registry.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
name (str): Name of the item.
|
|
||||||
value (str): Value of the item.
|
|
||||||
|
|
||||||
"""
|
|
||||||
self._set_item(name, value)
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def _set_item(self, name, value):
|
def _get_item(self, name: str) -> Any:
|
||||||
# type: (str, str) -> None
|
"""Get item value from registry."""
|
||||||
# Implement it
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __setitem__(self, name, value):
|
@abstractmethod
|
||||||
self._items[name] = value
|
def _set_item(self, name: str, value: str) -> None:
|
||||||
|
"""Set item value to registry."""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def _delete_item(self, name: str) -> None:
|
||||||
|
"""Delete item from registry."""
|
||||||
|
|
||||||
|
def __getitem__(self, name: str) -> Any:
|
||||||
|
return self._get_item(name)
|
||||||
|
|
||||||
|
def __setitem__(self, name: str, value: str) -> None:
|
||||||
self._set_item(name, value)
|
self._set_item(name, value)
|
||||||
|
|
||||||
def get_item(self, name):
|
def __delitem__(self, name: str) -> None:
|
||||||
# type: (str) -> str
|
self._delete_item(name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
def get_item(self, name: str) -> str:
|
||||||
"""Get item from settings registry.
|
"""Get item from settings registry.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -277,22 +290,22 @@ class ASettingRegistry(ABC):
|
||||||
value (str): Value of the item.
|
value (str): Value of the item.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: If item doesn't exist.
|
RegistryItemNotFound: If the item doesn't exist.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self._get_item(name)
|
return self._get_item(name)
|
||||||
|
|
||||||
@abstractmethod
|
def set_item(self, name: str, value: str) -> None:
|
||||||
def _get_item(self, name):
|
"""Set item to settings registry.
|
||||||
# type: (str) -> str
|
|
||||||
# Implement it
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __getitem__(self, name):
|
Args:
|
||||||
return self._get_item(name)
|
name (str): Name of the item.
|
||||||
|
value (str): Value of the item.
|
||||||
|
|
||||||
def delete_item(self, name):
|
"""
|
||||||
# type: (str) -> None
|
self._set_item(name, value)
|
||||||
|
|
||||||
|
def delete_item(self, name: str) -> None:
|
||||||
"""Delete item from settings registry.
|
"""Delete item from settings registry.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -301,16 +314,6 @@ class ASettingRegistry(ABC):
|
||||||
"""
|
"""
|
||||||
self._delete_item(name)
|
self._delete_item(name)
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def _delete_item(self, name):
|
|
||||||
# type: (str) -> None
|
|
||||||
"""Delete item from settings."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __delitem__(self, name):
|
|
||||||
del self._items[name]
|
|
||||||
self._delete_item(name)
|
|
||||||
|
|
||||||
|
|
||||||
class IniSettingRegistry(ASettingRegistry):
|
class IniSettingRegistry(ASettingRegistry):
|
||||||
"""Class using :mod:`configparser`.
|
"""Class using :mod:`configparser`.
|
||||||
|
|
@ -318,20 +321,17 @@ class IniSettingRegistry(ASettingRegistry):
|
||||||
This class is using :mod:`configparser` (ini) files to store items.
|
This class is using :mod:`configparser` (ini) files to store items.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
def __init__(self, name: str, path: str) -> None:
|
||||||
def __init__(self, name, path):
|
super().__init__(name)
|
||||||
# type: (str, str) -> IniSettingRegistry
|
|
||||||
super(IniSettingRegistry, self).__init__(name)
|
|
||||||
# get registry file
|
# get registry file
|
||||||
self._registry_file = os.path.join(path, "{}.ini".format(name))
|
self._registry_file = os.path.join(path, f"{name}.ini")
|
||||||
if not os.path.exists(self._registry_file):
|
if not os.path.exists(self._registry_file):
|
||||||
with open(self._registry_file, mode="w") as cfg:
|
with open(self._registry_file, mode="w") as cfg:
|
||||||
print("# Settings registry", cfg)
|
print("# Settings registry", cfg)
|
||||||
now = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
|
now = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
|
||||||
print("# {}".format(now), cfg)
|
print(f"# {now}", cfg)
|
||||||
|
|
||||||
def set_item_section(self, section, name, value):
|
def set_item_section(self, section: str, name: str, value: str) -> None:
|
||||||
# type: (str, str, str) -> None
|
|
||||||
"""Set item to specific section of ini registry.
|
"""Set item to specific section of ini registry.
|
||||||
|
|
||||||
If section doesn't exists, it is created.
|
If section doesn't exists, it is created.
|
||||||
|
|
@ -354,12 +354,10 @@ class IniSettingRegistry(ASettingRegistry):
|
||||||
with open(self._registry_file, mode="w") as cfg:
|
with open(self._registry_file, mode="w") as cfg:
|
||||||
config.write(cfg)
|
config.write(cfg)
|
||||||
|
|
||||||
def _set_item(self, name, value):
|
def _set_item(self, name: str, value: str) -> None:
|
||||||
# type: (str, str) -> None
|
|
||||||
self.set_item_section("MAIN", name, value)
|
self.set_item_section("MAIN", name, value)
|
||||||
|
|
||||||
def set_item(self, name, value):
|
def set_item(self, name: str, value: str) -> None:
|
||||||
# type: (str, str) -> None
|
|
||||||
"""Set item to settings ini file.
|
"""Set item to settings ini file.
|
||||||
|
|
||||||
This saves item to ``DEFAULT`` section of ini as each item there
|
This saves item to ``DEFAULT`` section of ini as each item there
|
||||||
|
|
@ -372,10 +370,9 @@ class IniSettingRegistry(ASettingRegistry):
|
||||||
"""
|
"""
|
||||||
# this does the some, overridden just for different docstring.
|
# this does the some, overridden just for different docstring.
|
||||||
# we cast value to str as ini options values must be strings.
|
# we cast value to str as ini options values must be strings.
|
||||||
super(IniSettingRegistry, self).set_item(name, str(value))
|
super().set_item(name, str(value))
|
||||||
|
|
||||||
def get_item(self, name):
|
def get_item(self, name: str) -> str:
|
||||||
# type: (str) -> str
|
|
||||||
"""Gets item from settings ini file.
|
"""Gets item from settings ini file.
|
||||||
|
|
||||||
This gets settings from ``DEFAULT`` section of ini file as each item
|
This gets settings from ``DEFAULT`` section of ini file as each item
|
||||||
|
|
@ -388,19 +385,18 @@ class IniSettingRegistry(ASettingRegistry):
|
||||||
str: Value of item.
|
str: Value of item.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: If value doesn't exist.
|
RegistryItemNotFound: If value doesn't exist.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return super(IniSettingRegistry, self).get_item(name)
|
return super().get_item(name)
|
||||||
|
|
||||||
@lru_cache(maxsize=32)
|
@lru_cache(maxsize=32)
|
||||||
def get_item_from_section(self, section, name):
|
def get_item_from_section(self, section: str, name: str) -> str:
|
||||||
# type: (str, str) -> str
|
|
||||||
"""Get item from section of ini file.
|
"""Get item from section of ini file.
|
||||||
|
|
||||||
This will read ini file and try to get item value from specified
|
This will read ini file and try to get item value from specified
|
||||||
section. If that section or item doesn't exist, :exc:`ValueError`
|
section. If that section or item doesn't exist,
|
||||||
is risen.
|
:exc:`RegistryItemNotFound` is risen.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
section (str): Name of ini section.
|
section (str): Name of ini section.
|
||||||
|
|
@ -410,7 +406,7 @@ class IniSettingRegistry(ASettingRegistry):
|
||||||
str: Item value.
|
str: Item value.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: If value doesn't exist.
|
RegistryItemNotFound: If value doesn't exist.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
config = configparser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
|
|
@ -418,16 +414,15 @@ class IniSettingRegistry(ASettingRegistry):
|
||||||
try:
|
try:
|
||||||
value = config[section][name]
|
value = config[section][name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise ValueError(
|
raise RegistryItemNotFound(
|
||||||
"Registry doesn't contain value {}:{}".format(section, name))
|
f"Registry doesn't contain value {section}:{name}"
|
||||||
|
)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def _get_item(self, name):
|
def _get_item(self, name: str) -> str:
|
||||||
# type: (str) -> str
|
|
||||||
return self.get_item_from_section("MAIN", name)
|
return self.get_item_from_section("MAIN", name)
|
||||||
|
|
||||||
def delete_item_from_section(self, section, name):
|
def delete_item_from_section(self, section: str, name: str) -> None:
|
||||||
# type: (str, str) -> None
|
|
||||||
"""Delete item from section in ini file.
|
"""Delete item from section in ini file.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -435,7 +430,7 @@ class IniSettingRegistry(ASettingRegistry):
|
||||||
name (str): Name of the item.
|
name (str): Name of the item.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: If item doesn't exist.
|
RegistryItemNotFound: If the item doesn't exist.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.get_item_from_section.cache_clear()
|
self.get_item_from_section.cache_clear()
|
||||||
|
|
@ -444,8 +439,9 @@ class IniSettingRegistry(ASettingRegistry):
|
||||||
try:
|
try:
|
||||||
_ = config[section][name]
|
_ = config[section][name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise ValueError(
|
raise RegistryItemNotFound(
|
||||||
"Registry doesn't contain value {}:{}".format(section, name))
|
f"Registry doesn't contain value {section}:{name}"
|
||||||
|
)
|
||||||
config.remove_option(section, name)
|
config.remove_option(section, name)
|
||||||
|
|
||||||
# if section is empty, delete it
|
# if section is empty, delete it
|
||||||
|
|
@ -461,29 +457,28 @@ class IniSettingRegistry(ASettingRegistry):
|
||||||
|
|
||||||
|
|
||||||
class JSONSettingRegistry(ASettingRegistry):
|
class JSONSettingRegistry(ASettingRegistry):
|
||||||
"""Class using json file as storage."""
|
"""Class using a json file as storage."""
|
||||||
|
|
||||||
def __init__(self, name, path):
|
def __init__(self, name: str, path: str) -> None:
|
||||||
# type: (str, str) -> JSONSettingRegistry
|
super().__init__(name)
|
||||||
super(JSONSettingRegistry, self).__init__(name)
|
self._registry_file = os.path.join(path, f"{name}.json")
|
||||||
#: str: name of registry file
|
|
||||||
self._registry_file = os.path.join(path, "{}.json".format(name))
|
|
||||||
now = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
|
now = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
|
||||||
header = {
|
header = {
|
||||||
"__metadata__": {"generated": now},
|
"__metadata__": {"generated": now},
|
||||||
"registry": {}
|
"registry": {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if not os.path.exists(os.path.dirname(self._registry_file)):
|
# Use 'os.path.dirname' in case someone uses slashes in 'name'
|
||||||
os.makedirs(os.path.dirname(self._registry_file), exist_ok=True)
|
dirpath = os.path.dirname(self._registry_file)
|
||||||
|
if not os.path.exists(dirpath):
|
||||||
|
os.makedirs(dirpath, exist_ok=True)
|
||||||
if not os.path.exists(self._registry_file):
|
if not os.path.exists(self._registry_file):
|
||||||
with open(self._registry_file, mode="w") as cfg:
|
with open(self._registry_file, mode="w") as cfg:
|
||||||
json.dump(header, cfg, indent=4)
|
json.dump(header, cfg, indent=4)
|
||||||
|
|
||||||
@lru_cache(maxsize=32)
|
@lru_cache(maxsize=32)
|
||||||
def _get_item(self, name):
|
def _get_item(self, name: str) -> str:
|
||||||
# type: (str) -> object
|
"""Get item value from the registry.
|
||||||
"""Get item value from registry json.
|
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
See :meth:`ayon_core.lib.JSONSettingRegistry.get_item`
|
See :meth:`ayon_core.lib.JSONSettingRegistry.get_item`
|
||||||
|
|
@ -494,29 +489,13 @@ class JSONSettingRegistry(ASettingRegistry):
|
||||||
try:
|
try:
|
||||||
value = data["registry"][name]
|
value = data["registry"][name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise ValueError(
|
raise RegistryItemNotFound(
|
||||||
"Registry doesn't contain value {}".format(name))
|
f"Registry doesn't contain value {name}"
|
||||||
|
)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def get_item(self, name):
|
def _set_item(self, name: str, value: str) -> None:
|
||||||
# type: (str) -> object
|
"""Set item value to the registry.
|
||||||
"""Get item value from registry json.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
name (str): Name of the item.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
value of the item
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
ValueError: If item is not found in registry file.
|
|
||||||
|
|
||||||
"""
|
|
||||||
return self._get_item(name)
|
|
||||||
|
|
||||||
def _set_item(self, name, value):
|
|
||||||
# type: (str, object) -> None
|
|
||||||
"""Set item value to registry json.
|
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
See :meth:`ayon_core.lib.JSONSettingRegistry.set_item`
|
See :meth:`ayon_core.lib.JSONSettingRegistry.set_item`
|
||||||
|
|
@ -528,41 +507,39 @@ class JSONSettingRegistry(ASettingRegistry):
|
||||||
cfg.truncate(0)
|
cfg.truncate(0)
|
||||||
cfg.seek(0)
|
cfg.seek(0)
|
||||||
json.dump(data, cfg, indent=4)
|
json.dump(data, cfg, indent=4)
|
||||||
|
|
||||||
def set_item(self, name, value):
|
|
||||||
# type: (str, object) -> None
|
|
||||||
"""Set item and its value into json registry file.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
name (str): name of the item.
|
|
||||||
value (Any): value of the item.
|
|
||||||
|
|
||||||
"""
|
|
||||||
self._set_item(name, value)
|
|
||||||
|
|
||||||
def _delete_item(self, name):
|
|
||||||
# type: (str) -> None
|
|
||||||
self._get_item.cache_clear()
|
self._get_item.cache_clear()
|
||||||
|
|
||||||
|
def _delete_item(self, name: str) -> None:
|
||||||
with open(self._registry_file, "r+") as cfg:
|
with open(self._registry_file, "r+") as cfg:
|
||||||
data = json.load(cfg)
|
data = json.load(cfg)
|
||||||
del data["registry"][name]
|
del data["registry"][name]
|
||||||
cfg.truncate(0)
|
cfg.truncate(0)
|
||||||
cfg.seek(0)
|
cfg.seek(0)
|
||||||
json.dump(data, cfg, indent=4)
|
json.dump(data, cfg, indent=4)
|
||||||
|
self._get_item.cache_clear()
|
||||||
|
|
||||||
|
|
||||||
class AYONSettingsRegistry(JSONSettingRegistry):
|
class AYONSettingsRegistry(JSONSettingRegistry):
|
||||||
"""Class handling AYON general settings registry.
|
"""Class handling AYON general settings registry.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name (Optional[str]): Name of the registry.
|
name (Optional[str]): Name of the registry. Using 'None' or not
|
||||||
"""
|
passing name is deprecated.
|
||||||
|
|
||||||
def __init__(self, name=None):
|
"""
|
||||||
|
def __init__(self, name: Optional[str] = None) -> None:
|
||||||
if not name:
|
if not name:
|
||||||
name = "AYON_settings"
|
name = "AYON_settings"
|
||||||
|
warnings.warn(
|
||||||
|
(
|
||||||
|
"Used 'AYONSettingsRegistry' without 'name' argument."
|
||||||
|
" The argument will be required in future versions."
|
||||||
|
),
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
path = get_launcher_storage_dir()
|
path = get_launcher_storage_dir()
|
||||||
super(AYONSettingsRegistry, self).__init__(name, path)
|
super().__init__(name, path)
|
||||||
|
|
||||||
|
|
||||||
def get_local_site_id():
|
def get_local_site_id():
|
||||||
|
|
|
||||||
|
|
@ -1403,7 +1403,12 @@ def _get_display_view_colorspace_name(config_path, display, view):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
config = _get_ocio_config(config_path)
|
config = _get_ocio_config(config_path)
|
||||||
return config.getDisplayViewColorSpaceName(display, view)
|
colorspace = config.getDisplayViewColorSpaceName(display, view)
|
||||||
|
# Special token. See https://opencolorio.readthedocs.io/en/latest/guides/authoring/authoring.html#shared-views # noqa
|
||||||
|
if colorspace == "<USE_DISPLAY_NAME>":
|
||||||
|
colorspace = display
|
||||||
|
|
||||||
|
return colorspace
|
||||||
|
|
||||||
|
|
||||||
def _get_ocio_config_colorspaces(config_path):
|
def _get_ocio_config_colorspaces(config_path):
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ class CollectAudio(pyblish.api.ContextPlugin):
|
||||||
"blender",
|
"blender",
|
||||||
"houdini",
|
"houdini",
|
||||||
"max",
|
"max",
|
||||||
"circuit",
|
"batchdelivery",
|
||||||
]
|
]
|
||||||
settings_category = "core"
|
settings_category = "core"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,7 @@ This module contains a unified plugin that handles:
|
||||||
|
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
|
|
||||||
import opentimelineio as otio
|
|
||||||
import pyblish.api
|
import pyblish.api
|
||||||
from ayon_core.pipeline.editorial import (
|
|
||||||
get_media_range_with_retimes,
|
|
||||||
otio_range_to_frame_range,
|
|
||||||
otio_range_with_handles,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def validate_otio_clip(instance, logger):
|
def validate_otio_clip(instance, logger):
|
||||||
|
|
@ -74,6 +68,8 @@ class CollectOtioRanges(pyblish.api.InstancePlugin):
|
||||||
if not validate_otio_clip(instance, self.log):
|
if not validate_otio_clip(instance, self.log):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
import opentimelineio as otio
|
||||||
|
|
||||||
otio_clip = instance.data["otioClip"]
|
otio_clip = instance.data["otioClip"]
|
||||||
|
|
||||||
# Collect timeline ranges if workfile start frame is available
|
# Collect timeline ranges if workfile start frame is available
|
||||||
|
|
@ -100,6 +96,11 @@ class CollectOtioRanges(pyblish.api.InstancePlugin):
|
||||||
|
|
||||||
def _collect_timeline_ranges(self, instance, otio_clip):
|
def _collect_timeline_ranges(self, instance, otio_clip):
|
||||||
"""Collect basic timeline frame ranges."""
|
"""Collect basic timeline frame ranges."""
|
||||||
|
from ayon_core.pipeline.editorial import (
|
||||||
|
otio_range_to_frame_range,
|
||||||
|
otio_range_with_handles,
|
||||||
|
)
|
||||||
|
|
||||||
workfile_start = instance.data["workfileFrameStart"]
|
workfile_start = instance.data["workfileFrameStart"]
|
||||||
|
|
||||||
# Get timeline ranges
|
# Get timeline ranges
|
||||||
|
|
@ -129,6 +130,8 @@ class CollectOtioRanges(pyblish.api.InstancePlugin):
|
||||||
|
|
||||||
def _collect_source_ranges(self, instance, otio_clip):
|
def _collect_source_ranges(self, instance, otio_clip):
|
||||||
"""Collect source media frame ranges."""
|
"""Collect source media frame ranges."""
|
||||||
|
import opentimelineio as otio
|
||||||
|
|
||||||
# Get source ranges
|
# Get source ranges
|
||||||
otio_src_range = otio_clip.source_range
|
otio_src_range = otio_clip.source_range
|
||||||
otio_available_range = otio_clip.available_range()
|
otio_available_range = otio_clip.available_range()
|
||||||
|
|
@ -178,6 +181,8 @@ class CollectOtioRanges(pyblish.api.InstancePlugin):
|
||||||
|
|
||||||
def _collect_retimed_ranges(self, instance, otio_clip):
|
def _collect_retimed_ranges(self, instance, otio_clip):
|
||||||
"""Handle retimed clip frame ranges."""
|
"""Handle retimed clip frame ranges."""
|
||||||
|
from ayon_core.pipeline.editorial import get_media_range_with_retimes
|
||||||
|
|
||||||
retimed_attributes = get_media_range_with_retimes(otio_clip, 0, 0)
|
retimed_attributes = get_media_range_with_retimes(otio_clip, 0, 0)
|
||||||
self.log.debug(f"Retimed attributes: {retimed_attributes}")
|
self.log.debug(f"Retimed attributes: {retimed_attributes}")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ class ExtractBurnin(publish.Extractor):
|
||||||
"max",
|
"max",
|
||||||
"blender",
|
"blender",
|
||||||
"unreal",
|
"unreal",
|
||||||
"circuit",
|
"batchdelivery",
|
||||||
]
|
]
|
||||||
settings_category = "core"
|
settings_category = "core"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ from ayon_core.lib import (
|
||||||
get_ffmpeg_tool_args,
|
get_ffmpeg_tool_args,
|
||||||
run_subprocess
|
run_subprocess
|
||||||
)
|
)
|
||||||
from ayon_core.pipeline import editorial
|
|
||||||
|
|
||||||
|
|
||||||
class ExtractOtioAudioTracks(pyblish.api.ContextPlugin):
|
class ExtractOtioAudioTracks(pyblish.api.ContextPlugin):
|
||||||
|
|
@ -159,6 +158,7 @@ class ExtractOtioAudioTracks(pyblish.api.ContextPlugin):
|
||||||
"""
|
"""
|
||||||
# Not all hosts can import this module.
|
# Not all hosts can import this module.
|
||||||
import opentimelineio as otio
|
import opentimelineio as otio
|
||||||
|
from ayon_core.pipeline.editorial import OTIO_EPSILON
|
||||||
|
|
||||||
output = []
|
output = []
|
||||||
# go trough all audio tracks
|
# go trough all audio tracks
|
||||||
|
|
@ -177,7 +177,7 @@ class ExtractOtioAudioTracks(pyblish.api.ContextPlugin):
|
||||||
# Avoid rounding issue on media available range.
|
# Avoid rounding issue on media available range.
|
||||||
if clip_start.almost_equal(
|
if clip_start.almost_equal(
|
||||||
conformed_av_start,
|
conformed_av_start,
|
||||||
editorial.OTIO_EPSILON
|
OTIO_EPSILON
|
||||||
):
|
):
|
||||||
conformed_av_start = clip_start
|
conformed_av_start = clip_start
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@ from ayon_core.lib import (
|
||||||
)
|
)
|
||||||
from ayon_core.pipeline import (
|
from ayon_core.pipeline import (
|
||||||
KnownPublishError,
|
KnownPublishError,
|
||||||
editorial,
|
|
||||||
publish,
|
publish,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -359,6 +358,7 @@ class ExtractOTIOReview(
|
||||||
import opentimelineio as otio
|
import opentimelineio as otio
|
||||||
from ayon_core.pipeline.editorial import (
|
from ayon_core.pipeline.editorial import (
|
||||||
trim_media_range,
|
trim_media_range,
|
||||||
|
OTIO_EPSILON,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _round_to_frame(rational_time):
|
def _round_to_frame(rational_time):
|
||||||
|
|
@ -380,7 +380,7 @@ class ExtractOTIOReview(
|
||||||
# Avoid rounding issue on media available range.
|
# Avoid rounding issue on media available range.
|
||||||
if start.almost_equal(
|
if start.almost_equal(
|
||||||
avl_start,
|
avl_start,
|
||||||
editorial.OTIO_EPSILON
|
OTIO_EPSILON
|
||||||
):
|
):
|
||||||
avl_start = start
|
avl_start = start
|
||||||
|
|
||||||
|
|
@ -406,7 +406,7 @@ class ExtractOTIOReview(
|
||||||
# Avoid rounding issue on media available range.
|
# Avoid rounding issue on media available range.
|
||||||
if end_point.almost_equal(
|
if end_point.almost_equal(
|
||||||
avl_end_point,
|
avl_end_point,
|
||||||
editorial.OTIO_EPSILON
|
OTIO_EPSILON
|
||||||
):
|
):
|
||||||
avl_end_point = end_point
|
avl_end_point = end_point
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,7 @@ class ExtractReview(pyblish.api.InstancePlugin):
|
||||||
"aftereffects",
|
"aftereffects",
|
||||||
"flame",
|
"flame",
|
||||||
"unreal",
|
"unreal",
|
||||||
"circuit",
|
"batchdelivery",
|
||||||
"photoshop"
|
"photoshop"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
|
||||||
"photoshop",
|
"photoshop",
|
||||||
"unreal",
|
"unreal",
|
||||||
"houdini",
|
"houdini",
|
||||||
"circuit",
|
"batchdelivery",
|
||||||
]
|
]
|
||||||
settings_category = "core"
|
settings_category = "core"
|
||||||
enabled = False
|
enabled = False
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""Package declaring AYON addon 'core' version."""
|
"""Package declaring AYON addon 'core' version."""
|
||||||
__version__ = "1.5.0+dev"
|
__version__ = "1.5.2+dev"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
name = "core"
|
name = "core"
|
||||||
title = "Core"
|
title = "Core"
|
||||||
version = "1.5.0+dev"
|
version = "1.5.2+dev"
|
||||||
|
|
||||||
client_dir = "ayon_core"
|
client_dir = "ayon_core"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "ayon-core"
|
name = "ayon-core"
|
||||||
version = "1.5.0+dev"
|
version = "1.5.2+dev"
|
||||||
description = ""
|
description = ""
|
||||||
authors = ["Ynput Team <team@ynput.io>"]
|
authors = ["Ynput Team <team@ynput.io>"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue