mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 12:54:40 +01:00
Merge branch 'develop' into bugfix/blind-flickering-fix
This commit is contained in:
commit
d64d3acabb
36 changed files with 378 additions and 296 deletions
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
|
@ -35,6 +35,7 @@ body:
|
|||
label: Version
|
||||
description: What version are you running? Look to AYON Tray
|
||||
options:
|
||||
- 1.5.0
|
||||
- 1.4.1
|
||||
- 1.4.0
|
||||
- 1.3.2
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook):
|
|||
"cinema4d",
|
||||
"silhouette",
|
||||
"gaffer",
|
||||
"loki",
|
||||
}
|
||||
launch_types = {LaunchTypes.local}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ class OCIOEnvHook(PreLaunchHook):
|
|||
"cinema4d",
|
||||
"silhouette",
|
||||
"gaffer",
|
||||
"loki",
|
||||
}
|
||||
launch_types = set()
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import warnings
|
|||
from datetime import datetime
|
||||
from abc import ABC, abstractmethod
|
||||
from functools import lru_cache
|
||||
from typing import Optional, Any
|
||||
|
||||
import platformdirs
|
||||
import ayon_api
|
||||
|
|
@ -15,26 +16,31 @@ import ayon_api
|
|||
_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:
|
||||
username = None
|
||||
|
||||
|
||||
def _get_ayon_appdirs(*args):
|
||||
def _get_ayon_appdirs(*args: str) -> str:
|
||||
return os.path.join(
|
||||
platformdirs.user_data_dir("AYON", "Ynput"),
|
||||
*args
|
||||
)
|
||||
|
||||
|
||||
def get_ayon_appdirs(*args):
|
||||
def get_ayon_appdirs(*args: str) -> str:
|
||||
"""Local app data directory of AYON client.
|
||||
|
||||
Deprecated:
|
||||
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 (Iterable[str]): Subdirectories/files in local app data dir.
|
||||
*args (Iterable[str]): Subdirectories/files in the local app data dir.
|
||||
|
||||
Returns:
|
||||
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:
|
||||
"""Get storage directory for launcher.
|
||||
"""Get a storage directory for launcher.
|
||||
|
||||
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:
|
||||
"""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:
|
||||
This function should be called at least once on bootstrap.
|
||||
This function should be called at least once on the bootstrap.
|
||||
|
||||
Args:
|
||||
*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:
|
||||
"""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
|
||||
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:
|
||||
addon_name (str): Addon name.
|
||||
*args (str): Subfolders in resources directory.
|
||||
*args (str): Subfolders in the resources directory.
|
||||
|
||||
Returns:
|
||||
str: Path to resources directory.
|
||||
|
|
@ -134,9 +140,10 @@ class AYONSecureRegistry:
|
|||
identify which data were created by AYON.
|
||||
|
||||
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:
|
||||
import keyring
|
||||
|
||||
|
|
@ -152,13 +159,12 @@ class AYONSecureRegistry:
|
|||
keyring.set_keyring(Windows.WinVaultKeyring())
|
||||
|
||||
# Force "AYON" prefix
|
||||
self._name = "/".join(("AYON", name))
|
||||
self._name = f"AYON/{name}"
|
||||
|
||||
def set_item(self, name, value):
|
||||
# type: (str, str) -> None
|
||||
"""Set sensitive item into system's keyring.
|
||||
def set_item(self, name: str, value: str) -> None:
|
||||
"""Set sensitive item into the 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.
|
||||
|
||||
Args:
|
||||
|
|
@ -172,22 +178,26 @@ class AYONSecureRegistry:
|
|||
import keyring
|
||||
|
||||
keyring.set_password(self._name, name, value)
|
||||
self.get_item.cache_clear()
|
||||
|
||||
@lru_cache(maxsize=32)
|
||||
def get_item(self, name, default=_PLACEHOLDER):
|
||||
"""Get value of sensitive item from system's keyring.
|
||||
def get_item(
|
||||
self, name: str, default: Any = _PLACEHOLDER
|
||||
) -> Optional[str]:
|
||||
"""Get value of sensitive item from the system's keyring.
|
||||
|
||||
See also `Keyring module`_
|
||||
|
||||
Args:
|
||||
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:
|
||||
value (str): Value of the item.
|
||||
|
||||
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:
|
||||
https://github.com/jaraco/keyring
|
||||
|
|
@ -202,14 +212,12 @@ class AYONSecureRegistry:
|
|||
if default is not _PLACEHOLDER:
|
||||
return default
|
||||
|
||||
# NOTE Should raise `KeyError`
|
||||
raise ValueError(
|
||||
"Item {}:{} does not exist in keyring.".format(self._name, name)
|
||||
raise RegistryItemNotFound(
|
||||
f"Item {self._name}:{name} not found in keyring."
|
||||
)
|
||||
|
||||
def delete_item(self, name):
|
||||
# type: (str) -> None
|
||||
"""Delete value stored in system's keyring.
|
||||
def delete_item(self, name: str) -> None:
|
||||
"""Delete value stored in the system's keyring.
|
||||
|
||||
See also `Keyring module`_
|
||||
|
||||
|
|
@ -227,47 +235,38 @@ class AYONSecureRegistry:
|
|||
|
||||
|
||||
class ASettingRegistry(ABC):
|
||||
"""Abstract class defining structure of **SettingRegistry** 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.
|
||||
"""Abstract class to defining structure of registry class.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, name):
|
||||
# type: (str) -> ASettingRegistry
|
||||
super(ASettingRegistry, self).__init__()
|
||||
|
||||
def __init__(self, name: str) -> None:
|
||||
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
|
||||
def _set_item(self, name, value):
|
||||
# type: (str, str) -> None
|
||||
# Implement it
|
||||
pass
|
||||
def _get_item(self, name: str) -> Any:
|
||||
"""Get item value from registry."""
|
||||
|
||||
def __setitem__(self, name, value):
|
||||
self._items[name] = value
|
||||
@abstractmethod
|
||||
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)
|
||||
|
||||
def get_item(self, name):
|
||||
# type: (str) -> str
|
||||
def __delitem__(self, name: str) -> None:
|
||||
self._delete_item(name)
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self._name
|
||||
|
||||
def get_item(self, name: str) -> str:
|
||||
"""Get item from settings registry.
|
||||
|
||||
Args:
|
||||
|
|
@ -277,22 +276,22 @@ class ASettingRegistry(ABC):
|
|||
value (str): Value of the item.
|
||||
|
||||
Raises:
|
||||
ValueError: If item doesn't exist.
|
||||
RegistryItemNotFound: If the item doesn't exist.
|
||||
|
||||
"""
|
||||
return self._get_item(name)
|
||||
|
||||
@abstractmethod
|
||||
def _get_item(self, name):
|
||||
# type: (str) -> str
|
||||
# Implement it
|
||||
pass
|
||||
def set_item(self, name: str, value: str) -> None:
|
||||
"""Set item to settings registry.
|
||||
|
||||
def __getitem__(self, name):
|
||||
return self._get_item(name)
|
||||
Args:
|
||||
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.
|
||||
|
||||
Args:
|
||||
|
|
@ -301,16 +300,6 @@ class ASettingRegistry(ABC):
|
|||
"""
|
||||
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 using :mod:`configparser`.
|
||||
|
|
@ -318,20 +307,17 @@ class IniSettingRegistry(ASettingRegistry):
|
|||
This class is using :mod:`configparser` (ini) files to store items.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, name, path):
|
||||
# type: (str, str) -> IniSettingRegistry
|
||||
super(IniSettingRegistry, self).__init__(name)
|
||||
def __init__(self, name: str, path: str) -> None:
|
||||
super().__init__(name)
|
||||
# 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):
|
||||
with open(self._registry_file, mode="w") as cfg:
|
||||
print("# Settings registry", cfg)
|
||||
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):
|
||||
# type: (str, str, str) -> None
|
||||
def set_item_section(self, section: str, name: str, value: str) -> None:
|
||||
"""Set item to specific section of ini registry.
|
||||
|
||||
If section doesn't exists, it is created.
|
||||
|
|
@ -354,12 +340,10 @@ class IniSettingRegistry(ASettingRegistry):
|
|||
with open(self._registry_file, mode="w") as cfg:
|
||||
config.write(cfg)
|
||||
|
||||
def _set_item(self, name, value):
|
||||
# type: (str, str) -> None
|
||||
def _set_item(self, name: str, value: str) -> None:
|
||||
self.set_item_section("MAIN", name, value)
|
||||
|
||||
def set_item(self, name, value):
|
||||
# type: (str, str) -> None
|
||||
def set_item(self, name: str, value: str) -> None:
|
||||
"""Set item to settings ini file.
|
||||
|
||||
This saves item to ``DEFAULT`` section of ini as each item there
|
||||
|
|
@ -372,10 +356,9 @@ class IniSettingRegistry(ASettingRegistry):
|
|||
"""
|
||||
# this does the some, overridden just for different docstring.
|
||||
# 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):
|
||||
# type: (str) -> str
|
||||
def get_item(self, name: str) -> str:
|
||||
"""Gets item from settings ini file.
|
||||
|
||||
This gets settings from ``DEFAULT`` section of ini file as each item
|
||||
|
|
@ -388,19 +371,18 @@ class IniSettingRegistry(ASettingRegistry):
|
|||
str: Value of item.
|
||||
|
||||
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)
|
||||
def get_item_from_section(self, section, name):
|
||||
# type: (str, str) -> str
|
||||
def get_item_from_section(self, section: str, name: str) -> str:
|
||||
"""Get item from section of ini file.
|
||||
|
||||
This will read ini file and try to get item value from specified
|
||||
section. If that section or item doesn't exist, :exc:`ValueError`
|
||||
is risen.
|
||||
section. If that section or item doesn't exist,
|
||||
:exc:`RegistryItemNotFound` is risen.
|
||||
|
||||
Args:
|
||||
section (str): Name of ini section.
|
||||
|
|
@ -410,7 +392,7 @@ class IniSettingRegistry(ASettingRegistry):
|
|||
str: Item value.
|
||||
|
||||
Raises:
|
||||
ValueError: If value doesn't exist.
|
||||
RegistryItemNotFound: If value doesn't exist.
|
||||
|
||||
"""
|
||||
config = configparser.ConfigParser()
|
||||
|
|
@ -418,16 +400,15 @@ class IniSettingRegistry(ASettingRegistry):
|
|||
try:
|
||||
value = config[section][name]
|
||||
except KeyError:
|
||||
raise ValueError(
|
||||
"Registry doesn't contain value {}:{}".format(section, name))
|
||||
raise RegistryItemNotFound(
|
||||
f"Registry doesn't contain value {section}:{name}"
|
||||
)
|
||||
return value
|
||||
|
||||
def _get_item(self, name):
|
||||
# type: (str) -> str
|
||||
def _get_item(self, name: str) -> str:
|
||||
return self.get_item_from_section("MAIN", name)
|
||||
|
||||
def delete_item_from_section(self, section, name):
|
||||
# type: (str, str) -> None
|
||||
def delete_item_from_section(self, section: str, name: str) -> None:
|
||||
"""Delete item from section in ini file.
|
||||
|
||||
Args:
|
||||
|
|
@ -435,7 +416,7 @@ class IniSettingRegistry(ASettingRegistry):
|
|||
name (str): Name of the item.
|
||||
|
||||
Raises:
|
||||
ValueError: If item doesn't exist.
|
||||
RegistryItemNotFound: If the item doesn't exist.
|
||||
|
||||
"""
|
||||
self.get_item_from_section.cache_clear()
|
||||
|
|
@ -444,8 +425,9 @@ class IniSettingRegistry(ASettingRegistry):
|
|||
try:
|
||||
_ = config[section][name]
|
||||
except KeyError:
|
||||
raise ValueError(
|
||||
"Registry doesn't contain value {}:{}".format(section, name))
|
||||
raise RegistryItemNotFound(
|
||||
f"Registry doesn't contain value {section}:{name}"
|
||||
)
|
||||
config.remove_option(section, name)
|
||||
|
||||
# if section is empty, delete it
|
||||
|
|
@ -461,29 +443,28 @@ class IniSettingRegistry(ASettingRegistry):
|
|||
|
||||
|
||||
class JSONSettingRegistry(ASettingRegistry):
|
||||
"""Class using json file as storage."""
|
||||
"""Class using a json file as storage."""
|
||||
|
||||
def __init__(self, name, path):
|
||||
# type: (str, str) -> JSONSettingRegistry
|
||||
super(JSONSettingRegistry, self).__init__(name)
|
||||
#: str: name of registry file
|
||||
self._registry_file = os.path.join(path, "{}.json".format(name))
|
||||
def __init__(self, name: str, path: str) -> None:
|
||||
super().__init__(name)
|
||||
self._registry_file = os.path.join(path, f"{name}.json")
|
||||
now = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
|
||||
header = {
|
||||
"__metadata__": {"generated": now},
|
||||
"registry": {}
|
||||
}
|
||||
|
||||
if not os.path.exists(os.path.dirname(self._registry_file)):
|
||||
os.makedirs(os.path.dirname(self._registry_file), exist_ok=True)
|
||||
# Use 'os.path.dirname' in case someone uses slashes in 'name'
|
||||
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):
|
||||
with open(self._registry_file, mode="w") as cfg:
|
||||
json.dump(header, cfg, indent=4)
|
||||
|
||||
@lru_cache(maxsize=32)
|
||||
def _get_item(self, name):
|
||||
# type: (str) -> object
|
||||
"""Get item value from registry json.
|
||||
def _get_item(self, name: str) -> str:
|
||||
"""Get item value from the registry.
|
||||
|
||||
Note:
|
||||
See :meth:`ayon_core.lib.JSONSettingRegistry.get_item`
|
||||
|
|
@ -494,29 +475,13 @@ class JSONSettingRegistry(ASettingRegistry):
|
|||
try:
|
||||
value = data["registry"][name]
|
||||
except KeyError:
|
||||
raise ValueError(
|
||||
"Registry doesn't contain value {}".format(name))
|
||||
raise RegistryItemNotFound(
|
||||
f"Registry doesn't contain value {name}"
|
||||
)
|
||||
return value
|
||||
|
||||
def get_item(self, name):
|
||||
# type: (str) -> object
|
||||
"""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.
|
||||
def _set_item(self, name: str, value: str) -> None:
|
||||
"""Set item value to the registry.
|
||||
|
||||
Note:
|
||||
See :meth:`ayon_core.lib.JSONSettingRegistry.set_item`
|
||||
|
|
@ -528,41 +493,39 @@ class JSONSettingRegistry(ASettingRegistry):
|
|||
cfg.truncate(0)
|
||||
cfg.seek(0)
|
||||
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()
|
||||
|
||||
def _delete_item(self, name: str) -> None:
|
||||
with open(self._registry_file, "r+") as cfg:
|
||||
data = json.load(cfg)
|
||||
del data["registry"][name]
|
||||
cfg.truncate(0)
|
||||
cfg.seek(0)
|
||||
json.dump(data, cfg, indent=4)
|
||||
self._get_item.cache_clear()
|
||||
|
||||
|
||||
class AYONSettingsRegistry(JSONSettingRegistry):
|
||||
"""Class handling AYON general settings registry.
|
||||
|
||||
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:
|
||||
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()
|
||||
super(AYONSettingsRegistry, self).__init__(name, path)
|
||||
super().__init__(name, path)
|
||||
|
||||
|
||||
def get_local_site_id():
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"""Core pipeline functionality"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import logging
|
||||
import platform
|
||||
|
|
@ -16,7 +17,6 @@ from ayon_core.host import HostBase
|
|||
from ayon_core.lib import (
|
||||
is_in_tests,
|
||||
initialize_ayon_connection,
|
||||
version_up
|
||||
)
|
||||
from ayon_core.addon import load_addons, AddonsManager
|
||||
from ayon_core.settings import get_project_settings
|
||||
|
|
@ -24,12 +24,7 @@ from ayon_core.settings import get_project_settings
|
|||
from .publish.lib import filter_pyblish_plugins
|
||||
from .anatomy import Anatomy
|
||||
from .template_data import get_template_data_with_names
|
||||
from .workfile import (
|
||||
get_custom_workfile_template_by_string_context,
|
||||
get_workfile_template_key_from_context,
|
||||
get_last_workfile,
|
||||
MissingWorkdirError,
|
||||
)
|
||||
from .workfile import get_custom_workfile_template_by_string_context
|
||||
from . import (
|
||||
register_loader_plugin_path,
|
||||
register_inventory_action_path,
|
||||
|
|
@ -75,7 +70,7 @@ def _get_addons_manager():
|
|||
|
||||
|
||||
def register_root(path):
|
||||
"""Register currently active root"""
|
||||
"""DEPRECATED Register currently active root."""
|
||||
log.info("Registering root: %s" % path)
|
||||
_registered_root["_"] = path
|
||||
|
||||
|
|
@ -94,18 +89,29 @@ def registered_root():
|
|||
|
||||
Returns:
|
||||
dict[str, str]: Root paths.
|
||||
"""
|
||||
|
||||
"""
|
||||
warnings.warn(
|
||||
"Used deprecated function 'registered_root'. Please use 'Anatomy'"
|
||||
" to get roots.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return _registered_root["_"]
|
||||
|
||||
|
||||
def install_host(host):
|
||||
def install_host(host: HostBase) -> None:
|
||||
"""Install `host` into the running Python session.
|
||||
|
||||
Args:
|
||||
host (HostBase): A host interface object.
|
||||
|
||||
"""
|
||||
if not isinstance(host, HostBase):
|
||||
log.error(
|
||||
f"Host must be a subclass of 'HostBase', got '{type(host)}'."
|
||||
)
|
||||
|
||||
global _is_installed
|
||||
|
||||
_is_installed = True
|
||||
|
|
@ -183,7 +189,7 @@ def install_ayon_plugins(project_name=None, host_name=None):
|
|||
register_inventory_action_path(INVENTORY_PATH)
|
||||
|
||||
if host_name is None:
|
||||
host_name = os.environ.get("AYON_HOST_NAME")
|
||||
host_name = get_current_host_name()
|
||||
|
||||
addons_manager = _get_addons_manager()
|
||||
publish_plugin_dirs = addons_manager.collect_publish_plugin_paths(
|
||||
|
|
@ -366,6 +372,24 @@ def get_current_task_name():
|
|||
return get_global_context()["task_name"]
|
||||
|
||||
|
||||
def get_current_project_settings() -> dict[str, Any]:
|
||||
"""Project settings for the current context project.
|
||||
|
||||
Returns:
|
||||
dict[str, Any]: Project settings for the current context project.
|
||||
|
||||
Raises:
|
||||
ValueError: If current project is not set.
|
||||
|
||||
"""
|
||||
project_name = get_current_project_name()
|
||||
if not project_name:
|
||||
raise ValueError(
|
||||
"Current project is not set. Can't get project settings."
|
||||
)
|
||||
return get_project_settings(project_name)
|
||||
|
||||
|
||||
def get_current_project_entity(fields=None):
|
||||
"""Helper function to get project document based on global Session.
|
||||
|
||||
|
|
@ -552,6 +576,7 @@ def change_current_context(
|
|||
" It is not necessary to pass it in anymore."
|
||||
),
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
host = registered_host()
|
||||
|
|
@ -580,53 +605,16 @@ def get_process_id():
|
|||
|
||||
|
||||
def version_up_current_workfile():
|
||||
"""Function to increment and save workfile
|
||||
"""DEPRECATED Function to increment and save workfile.
|
||||
|
||||
Please use 'save_next_version' from 'ayon_core.pipeline.workfile' instead.
|
||||
|
||||
"""
|
||||
host = registered_host()
|
||||
|
||||
project_name = get_current_project_name()
|
||||
folder_path = get_current_folder_path()
|
||||
task_name = get_current_task_name()
|
||||
host_name = get_current_host_name()
|
||||
|
||||
template_key = get_workfile_template_key_from_context(
|
||||
project_name,
|
||||
folder_path,
|
||||
task_name,
|
||||
host_name,
|
||||
warnings.warn(
|
||||
"Used deprecated 'version_up_current_workfile' please use"
|
||||
" 'save_next_version' from 'ayon_core.pipeline.workfile' instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
anatomy = Anatomy(project_name)
|
||||
|
||||
data = get_template_data_with_names(
|
||||
project_name, folder_path, task_name, host_name
|
||||
)
|
||||
data["root"] = anatomy.roots
|
||||
|
||||
work_template = anatomy.get_template_item("work", template_key)
|
||||
|
||||
# Define saving file extension
|
||||
extensions = host.get_workfile_extensions()
|
||||
current_file = host.get_current_workfile()
|
||||
if current_file:
|
||||
extensions = [os.path.splitext(current_file)[-1]]
|
||||
|
||||
work_root = work_template["directory"].format_strict(data)
|
||||
file_template = work_template["file"].template
|
||||
last_workfile_path = get_last_workfile(
|
||||
work_root, file_template, data, extensions, True
|
||||
)
|
||||
# `get_last_workfile` will return the first expected file version
|
||||
# if no files exist yet. In that case, if they do not exist we will
|
||||
# want to save v001
|
||||
new_workfile_path = last_workfile_path
|
||||
if os.path.exists(new_workfile_path):
|
||||
new_workfile_path = version_up(new_workfile_path)
|
||||
|
||||
# Raise an error if the parent folder doesn't exist as `host.save_workfile`
|
||||
# is not supposed/able to create missing folders.
|
||||
parent_folder = os.path.dirname(new_workfile_path)
|
||||
if not os.path.exists(parent_folder):
|
||||
raise MissingWorkdirError(
|
||||
f"Work area directory '{parent_folder}' does not exist.")
|
||||
|
||||
host.save_workfile(new_workfile_path)
|
||||
from ayon_core.pipeline.workfile import save_next_version
|
||||
save_next_version()
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@ import opentimelineio as otio
|
|||
from opentimelineio import opentime as _ot
|
||||
|
||||
|
||||
# https://github.com/AcademySoftwareFoundation/OpenTimelineIO/issues/1822
|
||||
OTIO_EPSILON = 1e-9
|
||||
|
||||
|
||||
def otio_range_to_frame_range(otio_range):
|
||||
start = _ot.to_frames(
|
||||
otio_range.start_time, otio_range.start_time.rate)
|
||||
|
|
|
|||
|
|
@ -720,11 +720,13 @@ def get_representation_path(representation, root=None):
|
|||
str: fullpath of the representation
|
||||
|
||||
"""
|
||||
|
||||
if root is None:
|
||||
from ayon_core.pipeline import registered_root
|
||||
from ayon_core.pipeline import get_current_project_name, Anatomy
|
||||
|
||||
root = registered_root()
|
||||
anatomy = Anatomy(get_current_project_name())
|
||||
return get_representation_path_with_anatomy(
|
||||
representation, anatomy
|
||||
)
|
||||
|
||||
def path_from_representation():
|
||||
try:
|
||||
|
|
@ -772,7 +774,7 @@ def get_representation_path(representation, root=None):
|
|||
|
||||
dir_path, file_name = os.path.split(path)
|
||||
if not os.path.exists(dir_path):
|
||||
return
|
||||
return None
|
||||
|
||||
base_name, ext = os.path.splitext(file_name)
|
||||
file_name_items = None
|
||||
|
|
@ -782,7 +784,7 @@ def get_representation_path(representation, root=None):
|
|||
file_name_items = base_name.split("%")
|
||||
|
||||
if not file_name_items:
|
||||
return
|
||||
return None
|
||||
|
||||
filename_start = file_name_items[0]
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import sys
|
|||
import inspect
|
||||
import copy
|
||||
import warnings
|
||||
import hashlib
|
||||
import xml.etree.ElementTree
|
||||
from typing import TYPE_CHECKING, Optional, Union, List
|
||||
|
||||
|
|
@ -243,32 +244,38 @@ def publish_plugins_discover(
|
|||
|
||||
for path in paths:
|
||||
path = os.path.normpath(path)
|
||||
if not os.path.isdir(path):
|
||||
continue
|
||||
filenames = []
|
||||
if os.path.isdir(path):
|
||||
filenames.extend(
|
||||
name
|
||||
for name in os.listdir(path)
|
||||
if (
|
||||
os.path.isfile(os.path.join(path, name))
|
||||
and not name.startswith("_")
|
||||
)
|
||||
)
|
||||
else:
|
||||
filenames.append(os.path.basename(path))
|
||||
path = os.path.dirname(path)
|
||||
|
||||
for fname in os.listdir(path):
|
||||
if fname.startswith("_"):
|
||||
continue
|
||||
|
||||
abspath = os.path.join(path, fname)
|
||||
|
||||
if not os.path.isfile(abspath):
|
||||
continue
|
||||
|
||||
mod_name, mod_ext = os.path.splitext(fname)
|
||||
|
||||
if mod_ext != ".py":
|
||||
dirpath_hash = hashlib.md5(path.encode("utf-8")).hexdigest()
|
||||
for filename in filenames:
|
||||
basename, ext = os.path.splitext(filename)
|
||||
if ext.lower() != ".py":
|
||||
continue
|
||||
|
||||
filepath = os.path.join(path, filename)
|
||||
module_name = f"{dirpath_hash}.{basename}"
|
||||
try:
|
||||
module = import_filepath(
|
||||
abspath, mod_name, sys_module_name=mod_name)
|
||||
filepath, module_name, sys_module_name=module_name
|
||||
)
|
||||
|
||||
except Exception as err: # noqa: BLE001
|
||||
# we need broad exception to catch all possible errors.
|
||||
result.crashed_file_paths[abspath] = sys.exc_info()
|
||||
result.crashed_file_paths[filepath] = sys.exc_info()
|
||||
|
||||
log.debug('Skipped: "%s" (%s)', mod_name, err)
|
||||
log.debug('Skipped: "%s" (%s)', filepath, err)
|
||||
continue
|
||||
|
||||
for plugin in pyblish.plugin.plugins_from_module(module):
|
||||
|
|
@ -354,12 +361,18 @@ def get_plugin_settings(plugin, project_settings, log, category=None):
|
|||
# Use project settings based on a category name
|
||||
if category:
|
||||
try:
|
||||
return (
|
||||
output = (
|
||||
project_settings
|
||||
[category]
|
||||
["publish"]
|
||||
[plugin.__name__]
|
||||
)
|
||||
warnings.warn(
|
||||
"Please fill 'settings_category'"
|
||||
f" for plugin '{plugin.__name__}'.",
|
||||
DeprecationWarning
|
||||
)
|
||||
return output
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
|
@ -384,12 +397,18 @@ def get_plugin_settings(plugin, project_settings, log, category=None):
|
|||
category_from_file = "core"
|
||||
|
||||
try:
|
||||
return (
|
||||
output = (
|
||||
project_settings
|
||||
[category_from_file]
|
||||
[plugin_kind]
|
||||
[plugin.__name__]
|
||||
)
|
||||
warnings.warn(
|
||||
"Please fill 'settings_category'"
|
||||
f" for plugin '{plugin.__name__}'.",
|
||||
DeprecationWarning
|
||||
)
|
||||
return output
|
||||
except KeyError:
|
||||
pass
|
||||
return {}
|
||||
|
|
|
|||
|
|
@ -22,9 +22,11 @@ from .utils import (
|
|||
should_open_workfiles_tool_on_launch,
|
||||
MissingWorkdirError,
|
||||
|
||||
save_workfile_info,
|
||||
save_current_workfile_to,
|
||||
save_workfile_with_current_context,
|
||||
save_workfile_info,
|
||||
save_next_version,
|
||||
copy_workfile_to_context,
|
||||
find_workfile_rootless_path,
|
||||
)
|
||||
|
||||
|
|
@ -63,9 +65,11 @@ __all__ = (
|
|||
"should_open_workfiles_tool_on_launch",
|
||||
"MissingWorkdirError",
|
||||
|
||||
"save_workfile_info",
|
||||
"save_current_workfile_to",
|
||||
"save_workfile_with_current_context",
|
||||
"save_workfile_info",
|
||||
"save_next_version",
|
||||
"copy_workfile_to_context",
|
||||
|
||||
"BuildWorkfile",
|
||||
|
||||
|
|
|
|||
|
|
@ -417,7 +417,8 @@ def save_next_version(
|
|||
Args:
|
||||
version (Optional[int]): Workfile version that will be used. Last
|
||||
version + 1 is used if is not passed in.
|
||||
comment (optional[str]): Workfile comment.
|
||||
comment (optional[str]): Workfile comment. Pass '""' to clear comment.
|
||||
The current workfile comment is used if it is not passed.
|
||||
description (Optional[str]): Workfile description.
|
||||
prepared_data (Optional[SaveWorkfileOptionalData]): Prepared data
|
||||
for speed enhancements.
|
||||
|
|
@ -427,6 +428,11 @@ def save_next_version(
|
|||
from ayon_core.pipeline.context_tools import registered_host
|
||||
|
||||
host = registered_host()
|
||||
current_path = host.get_current_workfile()
|
||||
if not current_path:
|
||||
current_path = None
|
||||
else:
|
||||
current_path = os.path.normpath(current_path)
|
||||
|
||||
context = host.get_current_context()
|
||||
project_name = context["project_name"]
|
||||
|
|
@ -481,7 +487,8 @@ def save_next_version(
|
|||
)
|
||||
rootless_dir = workdir.rootless
|
||||
last_workfile = None
|
||||
if version is None:
|
||||
current_workfile = None
|
||||
if version is None or comment is None:
|
||||
workfiles = host.list_workfiles(
|
||||
project_name, folder_entity, task_entity,
|
||||
prepared_data=ListWorkfilesOptionalData(
|
||||
|
|
@ -492,29 +499,37 @@ def save_next_version(
|
|||
)
|
||||
)
|
||||
for workfile in workfiles:
|
||||
if current_workfile is None and workfile.filepath == current_path:
|
||||
current_workfile = workfile
|
||||
|
||||
if workfile.version is None:
|
||||
continue
|
||||
|
||||
if (
|
||||
last_workfile is None
|
||||
or last_workfile.version < workfile.version
|
||||
):
|
||||
last_workfile = workfile
|
||||
|
||||
version = None
|
||||
if last_workfile is not None:
|
||||
version = last_workfile.version + 1
|
||||
if version is None and last_workfile is not None:
|
||||
version = last_workfile.version + 1
|
||||
|
||||
if version is None:
|
||||
version = get_versioning_start(
|
||||
project_name,
|
||||
host.name,
|
||||
task_name=task_entity["name"],
|
||||
task_type=task_entity["taskType"],
|
||||
product_type="workfile"
|
||||
)
|
||||
if version is None:
|
||||
version = get_versioning_start(
|
||||
project_name,
|
||||
host.name,
|
||||
task_name=task_entity["name"],
|
||||
task_type=task_entity["taskType"],
|
||||
product_type="workfile"
|
||||
)
|
||||
|
||||
# Re-use comment from the current workfile if is not passed in
|
||||
if comment is None and current_workfile is not None:
|
||||
comment = current_workfile.comment
|
||||
|
||||
template_data["version"] = version
|
||||
template_data["comment"] = comment
|
||||
if comment:
|
||||
template_data["comment"] = comment
|
||||
|
||||
# Resolve extension
|
||||
# - Don't fill any if the host does not have defined any -> e.g. if host
|
||||
|
|
@ -525,13 +540,13 @@ def save_next_version(
|
|||
ext = None
|
||||
workfile_extensions = host.get_workfile_extensions()
|
||||
if workfile_extensions:
|
||||
current_path = host.get_current_workfile()
|
||||
if current_path:
|
||||
ext = os.path.splitext(current_path)[1].lstrip(".")
|
||||
ext = os.path.splitext(current_path)[1]
|
||||
elif last_workfile is not None:
|
||||
ext = os.path.splitext(last_workfile.filepath)[1].lstrip(".")
|
||||
ext = os.path.splitext(last_workfile.filepath)[1]
|
||||
else:
|
||||
ext = next(iter(workfile_extensions), None)
|
||||
ext = next(iter(workfile_extensions))
|
||||
ext = ext.lstrip(".")
|
||||
|
||||
if ext:
|
||||
template_data["ext"] = ext
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ class CleanUp(pyblish.api.InstancePlugin):
|
|||
"webpublisher",
|
||||
"shell"
|
||||
]
|
||||
settings_category = "core"
|
||||
|
||||
exclude_families = ["clip"]
|
||||
optional = True
|
||||
active = True
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ class CleanUpFarm(pyblish.api.ContextPlugin):
|
|||
|
||||
order = pyblish.api.IntegratorOrder + 11
|
||||
label = "Clean Up Farm"
|
||||
|
||||
settings_category = "core"
|
||||
enabled = True
|
||||
|
||||
# Keep "filesequence" for backwards compatibility of older jobs
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin):
|
|||
order = pyblish.api.CollectorOrder + 0.49
|
||||
label = "Collect Anatomy Instance data"
|
||||
|
||||
settings_category = "core"
|
||||
|
||||
follow_workfile_version = False
|
||||
|
||||
def process(self, context):
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ class CollectAudio(pyblish.api.ContextPlugin):
|
|||
"max",
|
||||
"circuit",
|
||||
]
|
||||
settings_category = "core"
|
||||
|
||||
audio_product_name = "audioMain"
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ class CollectFramesFixDef(
|
|||
targets = ["local"]
|
||||
hosts = ["nuke"]
|
||||
families = ["render", "prerender"]
|
||||
settings_category = "core"
|
||||
|
||||
rewrite_version_enable = False
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import ayon_api
|
||||
import ayon_api.utils
|
||||
|
||||
from ayon_core.host import ILoadHost
|
||||
from ayon_core.pipeline import registered_host
|
||||
|
||||
import pyblish.api
|
||||
|
||||
|
||||
|
|
@ -27,16 +29,23 @@ class CollectSceneLoadedVersions(pyblish.api.ContextPlugin):
|
|||
def process(self, context):
|
||||
host = registered_host()
|
||||
if host is None:
|
||||
self.log.warn("No registered host.")
|
||||
self.log.warning("No registered host.")
|
||||
return
|
||||
|
||||
if not hasattr(host, "ls"):
|
||||
host_name = host.__name__
|
||||
self.log.warn("Host %r doesn't have ls() implemented." % host_name)
|
||||
if not isinstance(host, ILoadHost):
|
||||
host_name = host.name
|
||||
self.log.warning(
|
||||
f"Host {host_name} does not implement ILoadHost. "
|
||||
"Skipping querying of loaded versions in scene."
|
||||
)
|
||||
return
|
||||
|
||||
containers = list(host.get_containers())
|
||||
if not containers:
|
||||
# Opt out early if there are no containers
|
||||
self.log.debug("No loaded containers found in scene.")
|
||||
return
|
||||
|
||||
loaded_versions = []
|
||||
containers = list(host.ls())
|
||||
repre_ids = {
|
||||
container["representation"]
|
||||
for container in containers
|
||||
|
|
@ -61,6 +70,7 @@ class CollectSceneLoadedVersions(pyblish.api.ContextPlugin):
|
|||
|
||||
# QUESTION should we add same representation id when loaded multiple
|
||||
# times?
|
||||
loaded_versions = []
|
||||
for con in containers:
|
||||
repre_id = con["representation"]
|
||||
repre_entity = repre_entities_by_id.get(repre_id)
|
||||
|
|
@ -80,4 +90,5 @@ class CollectSceneLoadedVersions(pyblish.api.ContextPlugin):
|
|||
}
|
||||
loaded_versions.append(version)
|
||||
|
||||
self.log.debug(f"Collected {len(loaded_versions)} loaded versions.")
|
||||
context.data["loadedVersions"] = loaded_versions
|
||||
|
|
|
|||
|
|
@ -12,9 +12,10 @@ class CollectSceneVersion(pyblish.api.ContextPlugin):
|
|||
"""
|
||||
|
||||
order = pyblish.api.CollectorOrder
|
||||
label = 'Collect Scene Version'
|
||||
label = "Collect Scene Version"
|
||||
# configurable in Settings
|
||||
hosts = ["*"]
|
||||
settings_category = "core"
|
||||
|
||||
# in some cases of headless publishing (for example webpublisher using PS)
|
||||
# you want to ignore version from name and let integrate use next version
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ class ExtractBurnin(publish.Extractor):
|
|||
"unreal",
|
||||
"circuit",
|
||||
]
|
||||
settings_category = "core"
|
||||
|
||||
optional = True
|
||||
|
||||
|
|
|
|||
|
|
@ -55,6 +55,8 @@ class ExtractOIIOTranscode(publish.Extractor):
|
|||
label = "Transcode color spaces"
|
||||
order = pyblish.api.ExtractorOrder + 0.019
|
||||
|
||||
settings_category = "core"
|
||||
|
||||
optional = True
|
||||
|
||||
# Supported extensions
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ from ayon_core.lib import (
|
|||
get_ffmpeg_tool_args,
|
||||
run_subprocess
|
||||
)
|
||||
from ayon_core.pipeline import editorial
|
||||
|
||||
|
||||
class ExtractOtioAudioTracks(pyblish.api.ContextPlugin):
|
||||
|
|
@ -172,6 +173,14 @@ class ExtractOtioAudioTracks(pyblish.api.ContextPlugin):
|
|||
clip_start = otio_clip.source_range.start_time
|
||||
fps = clip_start.rate
|
||||
conformed_av_start = media_av_start.rescaled_to(fps)
|
||||
|
||||
# Avoid rounding issue on media available range.
|
||||
if clip_start.almost_equal(
|
||||
conformed_av_start,
|
||||
editorial.OTIO_EPSILON
|
||||
):
|
||||
conformed_av_start = clip_start
|
||||
|
||||
# ffmpeg ignores embedded tc
|
||||
start = clip_start - conformed_av_start
|
||||
duration = otio_clip.source_range.duration
|
||||
|
|
|
|||
|
|
@ -23,7 +23,11 @@ from ayon_core.lib import (
|
|||
get_ffmpeg_tool_args,
|
||||
run_subprocess,
|
||||
)
|
||||
from ayon_core.pipeline import publish
|
||||
from ayon_core.pipeline import (
|
||||
KnownPublishError,
|
||||
editorial,
|
||||
publish,
|
||||
)
|
||||
|
||||
|
||||
class ExtractOTIOReview(
|
||||
|
|
@ -97,8 +101,11 @@ class ExtractOTIOReview(
|
|||
|
||||
# skip instance if no reviewable data available
|
||||
if (
|
||||
not isinstance(otio_review_clips[0], otio.schema.Clip)
|
||||
and len(otio_review_clips) == 1
|
||||
len(otio_review_clips) == 1
|
||||
and (
|
||||
not isinstance(otio_review_clips[0], otio.schema.Clip)
|
||||
or otio_review_clips[0].media_reference.is_missing_reference
|
||||
)
|
||||
):
|
||||
self.log.warning(
|
||||
"Instance `{}` has nothing to process".format(instance))
|
||||
|
|
@ -248,7 +255,7 @@ class ExtractOTIOReview(
|
|||
|
||||
# Single video way.
|
||||
# Extraction via FFmpeg.
|
||||
else:
|
||||
elif hasattr(media_ref, "target_url"):
|
||||
path = media_ref.target_url
|
||||
# Set extract range from 0 (FFmpeg ignores
|
||||
# embedded timecode).
|
||||
|
|
@ -370,6 +377,13 @@ class ExtractOTIOReview(
|
|||
|
||||
avl_start = avl_range.start_time
|
||||
|
||||
# Avoid rounding issue on media available range.
|
||||
if start.almost_equal(
|
||||
avl_start,
|
||||
editorial.OTIO_EPSILON
|
||||
):
|
||||
avl_start = start
|
||||
|
||||
# An additional gap is required before the available
|
||||
# range to conform source start point and head handles.
|
||||
if start < avl_start:
|
||||
|
|
@ -388,6 +402,14 @@ class ExtractOTIOReview(
|
|||
# (media duration is shorter then clip requirement).
|
||||
end_point = start + duration
|
||||
avl_end_point = avl_range.end_time_exclusive()
|
||||
|
||||
# Avoid rounding issue on media available range.
|
||||
if end_point.almost_equal(
|
||||
avl_end_point,
|
||||
editorial.OTIO_EPSILON
|
||||
):
|
||||
avl_end_point = end_point
|
||||
|
||||
if end_point > avl_end_point:
|
||||
gap_duration = end_point - avl_end_point
|
||||
duration -= gap_duration
|
||||
|
|
@ -444,7 +466,7 @@ class ExtractOTIOReview(
|
|||
command = get_ffmpeg_tool_args("ffmpeg")
|
||||
|
||||
input_extension = None
|
||||
if sequence:
|
||||
if sequence is not None:
|
||||
input_dir, collection, sequence_fps = sequence
|
||||
in_frame_start = min(collection.indexes)
|
||||
|
||||
|
|
@ -478,7 +500,7 @@ class ExtractOTIOReview(
|
|||
"-i", input_path
|
||||
])
|
||||
|
||||
elif video:
|
||||
elif video is not None:
|
||||
video_path, otio_range = video
|
||||
frame_start = otio_range.start_time.value
|
||||
input_fps = otio_range.start_time.rate
|
||||
|
|
@ -496,7 +518,7 @@ class ExtractOTIOReview(
|
|||
"-i", video_path
|
||||
])
|
||||
|
||||
elif gap:
|
||||
elif gap is not None:
|
||||
sec_duration = frames_to_seconds(gap, self.actual_fps)
|
||||
|
||||
# form command for rendering gap files
|
||||
|
|
@ -510,6 +532,9 @@ class ExtractOTIOReview(
|
|||
"-tune", "stillimage"
|
||||
])
|
||||
|
||||
else:
|
||||
raise KnownPublishError("Sequence, video or gap is required.")
|
||||
|
||||
if video or sequence:
|
||||
command.extend([
|
||||
"-vf", f"scale={self.to_width}:{self.to_height}:flags=lanczos",
|
||||
|
|
|
|||
|
|
@ -165,6 +165,7 @@ class ExtractReview(pyblish.api.InstancePlugin):
|
|||
"photoshop"
|
||||
]
|
||||
|
||||
settings_category = "core"
|
||||
# Supported extensions
|
||||
image_exts = {"exr", "jpg", "jpeg", "png", "dpx", "tga", "tiff", "tif"}
|
||||
video_exts = {"mov", "mp4"}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
|
|||
"houdini",
|
||||
"circuit",
|
||||
]
|
||||
settings_category = "core"
|
||||
enabled = False
|
||||
|
||||
integrate_thumbnail = False
|
||||
|
|
|
|||
|
|
@ -256,6 +256,7 @@ class CollectUSDLayerContributions(pyblish.api.InstancePlugin,
|
|||
label = "Collect USD Layer Contributions (Asset/Shot)"
|
||||
families = ["usd"]
|
||||
enabled = True
|
||||
settings_category = "core"
|
||||
|
||||
# A contribution defines a contribution into a (department) layer which
|
||||
# will get layered into the target product, usually the asset or shot.
|
||||
|
|
@ -633,6 +634,8 @@ class ExtractUSDLayerContribution(publish.Extractor):
|
|||
label = "Extract USD Layer Contributions (Asset/Shot)"
|
||||
order = pyblish.api.ExtractorOrder + 0.45
|
||||
|
||||
settings_category = "core"
|
||||
|
||||
use_ayon_entity_uri = False
|
||||
|
||||
def process(self, instance):
|
||||
|
|
@ -795,6 +798,8 @@ class ExtractUSDAssetContribution(publish.Extractor):
|
|||
label = "Extract USD Asset/Shot Contributions"
|
||||
order = ExtractUSDLayerContribution.order + 0.01
|
||||
|
||||
settings_category = "core"
|
||||
|
||||
use_ayon_entity_uri = False
|
||||
|
||||
def process(self, instance):
|
||||
|
|
|
|||
|
|
@ -61,6 +61,8 @@ class IntegrateHeroVersion(
|
|||
# Must happen after IntegrateNew
|
||||
order = pyblish.api.IntegratorOrder + 0.1
|
||||
|
||||
settings_category = "core"
|
||||
|
||||
optional = True
|
||||
active = True
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ class IntegrateProductGroup(pyblish.api.InstancePlugin):
|
|||
order = pyblish.api.IntegratorOrder - 0.1
|
||||
label = "Product Group"
|
||||
|
||||
settings_category = "core"
|
||||
|
||||
# Attributes set by settings
|
||||
product_grouping_profiles = None
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ class PreIntegrateThumbnails(pyblish.api.InstancePlugin):
|
|||
label = "Override Integrate Thumbnail Representations"
|
||||
order = pyblish.api.IntegratorOrder - 0.1
|
||||
|
||||
settings_category = "core"
|
||||
|
||||
integrate_profiles = []
|
||||
|
||||
def process(self, instance):
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ class ValidateOutdatedContainers(
|
|||
|
||||
label = "Validate Outdated Containers"
|
||||
order = pyblish.api.ValidatorOrder
|
||||
settings_category = "core"
|
||||
|
||||
optional = True
|
||||
actions = [ShowInventory]
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class ValidateCurrentSaveFile(pyblish.api.ContextPlugin):
|
|||
label = "Validate File Saved"
|
||||
order = pyblish.api.ValidatorOrder - 0.1
|
||||
hosts = ["fusion", "houdini", "max", "maya", "nuke", "substancepainter",
|
||||
"cinema4d", "silhouette", "gaffer", "blender"]
|
||||
"cinema4d", "silhouette", "gaffer", "blender", "loki"]
|
||||
actions = [SaveByVersionUpAction, ShowWorkfilesAction]
|
||||
|
||||
def process(self, context):
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ class ValidateIntent(pyblish.api.ContextPlugin):
|
|||
order = pyblish.api.ValidatorOrder
|
||||
|
||||
label = "Validate Intent"
|
||||
settings_category = "core"
|
||||
|
||||
enabled = False
|
||||
|
||||
# Can be modified by settings
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ class ValidateVersion(pyblish.api.InstancePlugin, OptionalPyblishPluginMixin):
|
|||
order = pyblish.api.ValidatorOrder
|
||||
|
||||
label = "Validate Version"
|
||||
settings_category = "core"
|
||||
|
||||
optional = False
|
||||
active = True
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import logging
|
|||
import collections
|
||||
import copy
|
||||
import time
|
||||
import warnings
|
||||
|
||||
import ayon_api
|
||||
|
||||
|
|
@ -175,17 +176,22 @@ def get_project_environments(project_name, project_settings=None):
|
|||
|
||||
|
||||
def get_current_project_settings():
|
||||
"""Project settings for current context project.
|
||||
"""DEPRECATE Project settings for current context project.
|
||||
|
||||
Function requires access to pipeline context which is in
|
||||
'ayon_core.pipeline'.
|
||||
|
||||
Returns:
|
||||
dict[str, Any]: Project settings for current context project.
|
||||
|
||||
Project name should be stored in environment variable `AYON_PROJECT_NAME`.
|
||||
This function should be used only in host context where environment
|
||||
variable must be set and should not happen that any part of process will
|
||||
change the value of the environment variable.
|
||||
"""
|
||||
project_name = os.environ.get("AYON_PROJECT_NAME")
|
||||
if not project_name:
|
||||
raise ValueError(
|
||||
"Missing context project in environment"
|
||||
" variable `AYON_PROJECT_NAME`."
|
||||
)
|
||||
return get_project_settings(project_name)
|
||||
warnings.warn(
|
||||
"Used deprecated function 'get_current_project_settings' in"
|
||||
" 'ayon_core.settings'. The function was moved to"
|
||||
" 'ayon_core.pipeline.context_tools'.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
from ayon_core.pipeline.context_tools import get_current_project_settings
|
||||
|
||||
return get_current_project_settings()
|
||||
|
|
|
|||
|
|
@ -399,7 +399,11 @@ class ActionsModel:
|
|||
return cache.get_data()
|
||||
|
||||
try:
|
||||
response = ayon_api.post("actions/list", **request_data)
|
||||
# 'variant' query is supported since AYON backend 1.10.4
|
||||
query = urlencode({"variant": self._variant})
|
||||
response = ayon_api.post(
|
||||
f"actions/list?{query}", **request_data
|
||||
)
|
||||
response.raise_for_status()
|
||||
except Exception:
|
||||
self.log.warning("Failed to collect webactions.", exc_info=True)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Package declaring AYON addon 'core' version."""
|
||||
__version__ = "1.4.1+dev"
|
||||
__version__ = "1.5.0+dev"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
name = "core"
|
||||
title = "Core"
|
||||
version = "1.4.1+dev"
|
||||
version = "1.5.0+dev"
|
||||
|
||||
client_dir = "ayon_core"
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
[tool.poetry]
|
||||
name = "ayon-core"
|
||||
version = "1.4.1+dev"
|
||||
version = "1.5.0+dev"
|
||||
description = ""
|
||||
authors = ["Ynput Team <team@ynput.io>"]
|
||||
readme = "README.md"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue