Merge branch 'develop' of https://github.com/ynput/ayon-core into develop

This commit is contained in:
Jakub Jezek 2024-02-12 23:03:02 +01:00
commit f295db4289
No known key found for this signature in database
GPG key ID: 730D7C02726179A7
85 changed files with 638 additions and 951 deletions

View file

@ -7,3 +7,6 @@ AYON_CORE_ROOT = os.path.dirname(os.path.abspath(__file__))
PACKAGE_DIR = AYON_CORE_ROOT
PLUGINS_DIR = os.path.join(AYON_CORE_ROOT, "plugins")
AYON_SERVER_ENABLED = True
# Indicate if AYON entities should be used instead of OpenPype entities
USE_AYON_ENTITIES = False

View file

@ -31,7 +31,7 @@ AYON addons should contain separated logic of specific kind of implementation, s
- addon must implement `get_plugin_paths` which must return dictionary with possible keys `"publish"`, `"load"`, `"create"` or `"actions"`
- each key may contain list or string with a path to directory with plugins
## ITrayModule
## ITrayAddon
- addon has more logic when used in a tray
- it is possible that addon can be used only in the tray
- abstract methods
@ -46,7 +46,7 @@ AYON addons should contain separated logic of specific kind of implementation, s
- if addon has logic only in tray or for both then should be checking for `tray_initialized` attribute to decide how should handle situations
### ITrayService
- inherits from `ITrayModule` and implements `tray_menu` method for you
- inherits from `ITrayAddon` and implements `tray_menu` method for you
- adds action to submenu "Services" in tray widget menu with icon and label
- abstract attribute `label`
- label shown in menu
@ -57,7 +57,7 @@ AYON addons should contain separated logic of specific kind of implementation, s
- these states must be set by addon itself `set_service_running` is default state on initialization
### ITrayAction
- inherits from `ITrayModule` and implements `tray_menu` method for you
- inherits from `ITrayAddon` and implements `tray_menu` method for you
- adds action to tray widget menu with label
- abstract attribute `label`
- label shown in menu
@ -89,4 +89,4 @@ AYON addons should contain separated logic of specific kind of implementation, s
### TrayAddonsManager
- inherits from `AddonsManager`
- has specific implementation for Pype Tray tool and handle `ITrayModule` methods
- has specific implementation for Pype Tray tool and handle `ITrayAddon` methods

View file

@ -15,7 +15,7 @@ method to convert 'click_wrap' object to 'click' object.
Before
```python
import click
from ayon_core.modules import AYONAddon
from ayon_core.addon import AYONAddon
class ExampleAddon(AYONAddon):
@ -40,7 +40,7 @@ def mycommand(arg1, arg2):
Now
```
from ayon_core import click_wrap
from ayon_core.modules import AYONAddon
from ayon_core.addon import AYONAddon
class ExampleAddon(AYONAddon):
@ -72,7 +72,7 @@ Added small enhancements:
Example:
```python
from ayon_core import click_wrap
from ayon_core.modules import AYONAddon
from ayon_core.addon import AYONAddon
class ExampleAddon(AYONAddon):

View file

@ -49,7 +49,6 @@ class HostBase(object):
Todo:
- move content of 'install_host' as method of this class
- register host object
- install legacy_io
- install global plugin paths
- store registered plugin paths to this object
- handle current context (project, asset, task)
@ -133,8 +132,6 @@ class HostBase(object):
can be opened multiple workfiles at one moment and change of context
can't be caught properly.
Default implementation returns values from 'legacy_io.Session'.
Returns:
Dict[str, Union[str, None]]: Context with 3 keys 'project_name',
'asset_name' and 'task_name'. All of them can be 'None'.

View file

@ -1,13 +1,10 @@
from ayon_core.modules import OpenPypeModule, IHostAddon
from ayon_core.addon import AYONAddon, IHostAddon
class AfterEffectsAddon(OpenPypeModule, IHostAddon):
class AfterEffectsAddon(AYONAddon, IHostAddon):
name = "aftereffects"
host_name = "aftereffects"
def initialize(self, module_settings):
self.enabled = True
def add_implementation_envs(self, env, _app):
"""Modify environments to contain all required for implementation."""
defaults = {

View file

@ -17,7 +17,7 @@ from qtpy import QtCore
from ayon_core.lib import Logger
from ayon_core.tests.lib import is_in_tests
from ayon_core.pipeline import install_host, legacy_io
from ayon_core.pipeline import install_host
from ayon_core.addon import AddonsManager
from ayon_core.tools.utils import host_tools, get_ayon_qt_app
from ayon_core.tools.adobe_webserver.app import WebServerTool
@ -298,13 +298,10 @@ class AfterEffectsRoute(WebSocketRoute):
log.info("Setting context change")
log.info("project {} asset {} ".format(project, asset))
if project:
legacy_io.Session["AVALON_PROJECT"] = project
os.environ["AVALON_PROJECT"] = project
if asset:
legacy_io.Session["AVALON_ASSET"] = asset
os.environ["AVALON_ASSET"] = asset
if task:
legacy_io.Session["AVALON_TASK"] = task
os.environ["AVALON_TASK"] = task
async def read(self):

View file

@ -1,16 +1,13 @@
import os
from ayon_core.modules import OpenPypeModule, IHostAddon
from ayon_core.addon import AYONAddon, IHostAddon
BLENDER_ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
class BlenderAddon(OpenPypeModule, IHostAddon):
class BlenderAddon(AYONAddon, IHostAddon):
name = "blender"
host_name = "blender"
def initialize(self, module_settings):
self.enabled = True
def add_implementation_envs(self, env, _app):
"""Modify environments to contain all required for implementation."""
# Prepare path to implementation script

View file

@ -19,7 +19,6 @@ from ayon_core.host import (
from ayon_core.client import get_asset_by_name
from ayon_core.pipeline import (
schema,
legacy_io,
get_current_project_name,
get_current_asset_name,
register_loader_plugin_path,
@ -380,7 +379,7 @@ def _on_task_changed():
# `directory` attribute, so it opens in that directory (does it?).
# https://docs.blender.org/api/blender2.8/bpy.types.Operator.html#calling-a-file-selector
# https://docs.blender.org/api/blender2.8/bpy.types.WindowManager.html#bpy.types.WindowManager.fileselect_add
workdir = legacy_io.Session["AVALON_WORKDIR"]
workdir = os.getenv("AVALON_WORKDIR")
log.debug("New working directory: %s", workdir)

View file

@ -1,16 +1,13 @@
import os
from ayon_core.modules import OpenPypeModule, IHostAddon
from ayon_core.addon import AYONAddon, IHostAddon
CELACTION_ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
class CelactionAddon(OpenPypeModule, IHostAddon):
class CelactionAddon(AYONAddon, IHostAddon):
name = "celaction"
host_name = "celaction"
def initialize(self, module_settings):
self.enabled = True
def get_launch_hook_paths(self, app):
if app.host_name != self.host_name:
return []

View file

@ -1,16 +1,13 @@
import os
from ayon_core.modules import OpenPypeModule, IHostAddon
from ayon_core.addon import AYONAddon, IHostAddon
HOST_DIR = os.path.dirname(os.path.abspath(__file__))
class FlameAddon(OpenPypeModule, IHostAddon):
class FlameAddon(AYONAddon, IHostAddon):
name = "flame"
host_name = "flame"
def initialize(self, module_settings):
self.enabled = True
def add_implementation_envs(self, env, _app):
# Add requirements to DL_PYTHON_HOOK_PATH
env["DL_PYTHON_HOOK_PATH"] = os.path.join(HOST_DIR, "startup")

View file

@ -1,6 +1,6 @@
import os
import re
from ayon_core.modules import OpenPypeModule, IHostAddon
from ayon_core.addon import AYONAddon, IHostAddon
from ayon_core.lib import Logger
FUSION_HOST_DIR = os.path.dirname(os.path.abspath(__file__))
@ -48,13 +48,10 @@ def get_fusion_version(app_name):
)
class FusionAddon(OpenPypeModule, IHostAddon):
class FusionAddon(AYONAddon, IHostAddon):
name = "fusion"
host_name = "fusion"
def initialize(self, module_settings):
self.enabled = True
def get_launch_hook_paths(self, app):
if app.host_name != self.host_name:
return []

View file

@ -11,7 +11,6 @@ from ayon_core.lib import (
EnumDef,
)
from ayon_core.pipeline import (
legacy_io,
Creator,
CreatedInstance
)
@ -136,7 +135,7 @@ class GenericCreateSaver(Creator):
ext = data["creator_attributes"]["image_format"]
# Subset change detected
workdir = os.path.normpath(legacy_io.Session["AVALON_WORKDIR"])
workdir = os.path.normpath(os.getenv("AVALON_WORKDIR"))
formatting_data.update({
"workdir": workdir,
"frame": "0" * frame_padding,

View file

@ -1,16 +1,13 @@
import os
from ayon_core.modules import OpenPypeModule, IHostAddon
from ayon_core.addon import AYONAddon, IHostAddon
HARMONY_HOST_DIR = os.path.dirname(os.path.abspath(__file__))
class HarmonyAddon(OpenPypeModule, IHostAddon):
class HarmonyAddon(AYONAddon, IHostAddon):
name = "harmony"
host_name = "harmony"
def initialize(self, module_settings):
self.enabled = True
def add_implementation_envs(self, env, _app):
"""Modify environments to contain all required for implementation."""
openharmony_path = os.path.join(

View file

@ -1,17 +1,14 @@
import os
import platform
from ayon_core.modules import OpenPypeModule, IHostAddon
from ayon_core.addon import AYONAddon, IHostAddon
HIERO_ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
class HieroAddon(OpenPypeModule, IHostAddon):
class HieroAddon(AYONAddon, IHostAddon):
name = "hiero"
host_name = "hiero"
def initialize(self, module_settings):
self.enabled = True
def add_implementation_envs(self, env, _app):
# Add requirements to HIERO_PLUGIN_PATH
new_hiero_paths = [

View file

@ -1,16 +1,13 @@
import os
from ayon_core.modules import OpenPypeModule, IHostAddon
from ayon_core.addon import AYONAddon, IHostAddon
HOUDINI_HOST_DIR = os.path.dirname(os.path.abspath(__file__))
class HoudiniAddon(OpenPypeModule, IHostAddon):
class HoudiniAddon(AYONAddon, IHostAddon):
name = "houdini"
host_name = "houdini"
def initialize(self, module_settings):
self.enabled = True
def add_implementation_envs(self, env, _app):
# Add requirements to HOUDINI_PATH and HOUDINI_MENU_PATH
startup_path = os.path.join(HOUDINI_HOST_DIR, "startup")

View file

@ -6,6 +6,7 @@ import re
import uuid
import logging
import json
from contextlib import contextmanager
import six

View file

@ -7,7 +7,7 @@ from qtpy import QtWidgets, QtCore, QtGui
from ayon_core import style
from ayon_core.client import get_asset_by_name
from ayon_core.pipeline import legacy_io, get_current_project_name
from ayon_core.pipeline import get_current_project_name
from ayon_core.tools.utils.assets_widget import SingleSelectAssetsWidget
from pxr import Sdf
@ -27,7 +27,8 @@ class SelectAssetDialog(QtWidgets.QWidget):
self.setWindowTitle("Pick Asset")
self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.Popup)
assets_widget = SingleSelectAssetsWidget(legacy_io, parent=self)
assets_widget = SingleSelectAssetsWidget(self)
assets_widget.set_project_name(get_current_project_name(), False)
layout = QtWidgets.QHBoxLayout(self)
layout.addWidget(assets_widget)

View file

@ -1,17 +1,14 @@
# -*- coding: utf-8 -*-
import os
from ayon_core.modules import OpenPypeModule, IHostAddon
from ayon_core.addon import AYONAddon, IHostAddon
MAX_HOST_DIR = os.path.dirname(os.path.abspath(__file__))
class MaxAddon(OpenPypeModule, IHostAddon):
class MaxAddon(AYONAddon, IHostAddon):
name = "max"
host_name = "max"
def initialize(self, module_settings):
self.enabled = True
def add_implementation_envs(self, env, _app):
# Remove auto screen scale factor for Qt
# - let 3dsmax decide it's value

View file

@ -60,6 +60,9 @@ class MaxHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
rt.callbacks.addScript(rt.Name('filePostOpen'),
lib.check_colorspace)
rt.callbacks.addScript(rt.Name('postWorkspaceChange'),
self._deferred_menu_creation)
def has_unsaved_changes(self):
# TODO: how to get it from 3dsmax?
return True

View file

@ -1,16 +1,13 @@
import os
from ayon_core.modules import OpenPypeModule, IHostAddon
from ayon_core.addon import AYONAddon, IHostAddon
MAYA_ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
class MayaAddon(OpenPypeModule, IHostAddon):
class MayaAddon(AYONAddon, IHostAddon):
name = "maya"
host_name = "maya"
def initialize(self, module_settings):
self.enabled = True
def add_implementation_envs(self, env, _app):
# Add requirements to PYTHONPATH
new_python_paths = [

View file

@ -26,7 +26,6 @@ from ayon_core.lib import (
emit_event
)
from ayon_core.pipeline import (
legacy_io,
get_current_project_name,
register_loader_plugin_path,
register_inventory_action_path,
@ -247,7 +246,7 @@ def _set_project():
None
"""
workdir = legacy_io.Session["AVALON_WORKDIR"]
workdir = os.getenv("AVALON_WORKDIR")
try:
os.makedirs(workdir)
@ -629,7 +628,7 @@ def on_task_changed():
# Run
menu.update_menu_task_label()
workdir = legacy_io.Session["AVALON_WORKDIR"]
workdir = os.getenv("AVALON_WORKDIR")
if os.path.exists(workdir):
log.info("Updating Maya workspace for task change to %s", workdir)
_set_project()
@ -678,7 +677,7 @@ def workfile_save_before_xgen(event):
import xgenm
current_work_dir = legacy_io.Session["AVALON_WORKDIR"].replace("\\", "/")
current_work_dir = os.getenv("AVALON_WORKDIR").replace("\\", "/")
expected_work_dir = event.data["workdir_path"].replace("\\", "/")
if current_work_dir == expected_work_dir:
return

View file

@ -6,7 +6,6 @@ import maya.cmds as cmds
from ayon_core.settings import get_project_settings
from ayon_core.pipeline import (
load,
legacy_io,
get_representation_path
)
from ayon_core.hosts.maya.api.lib import (
@ -26,11 +25,6 @@ def is_sequence(files):
return sequence
def get_current_session_fps():
session_fps = float(legacy_io.Session.get('AVALON_FPS', 25))
return convert_to_maya_fps(session_fps)
class ArnoldStandinLoader(load.LoaderPlugin):
"""Load as Arnold standin"""
@ -99,7 +93,7 @@ class ArnoldStandinLoader(load.LoaderPlugin):
sequence = is_sequence(os.listdir(os.path.dirname(repre_path)))
cmds.setAttr(standin_shape + ".useFrameExtension", sequence)
fps = float(version["data"].get("fps"))or get_current_session_fps()
fps = float(version["data"].get("fps")) or 25
cmds.setAttr(standin_shape + ".abcFPS", fps)
nodes = [root, standin, standin_shape]

View file

@ -1,13 +1,8 @@
# -*- coding: utf-8 -*-
"""Collect Vray Scene and prepare it for extraction and publishing."""
import re
import maya.app.renderSetup.model.renderSetup as renderSetup
from maya import cmds
import pyblish.api
from ayon_core.pipeline import legacy_io
from ayon_core.lib import get_formatted_current_time
from ayon_core.hosts.maya.api import lib

View file

@ -12,7 +12,6 @@ import ayon_core.hosts.maya.api.action
from ayon_core.client.mongo import OpenPypeMongoConnection
from ayon_core.hosts.maya.api.shader_definition_editor import (
DEFINITION_FILENAME)
from ayon_core.pipeline import legacy_io
from ayon_core.pipeline.publish import (
OptionalPyblishPluginMixin, PublishValidationError, ValidateContentsOrder)

View file

@ -3,7 +3,6 @@ import pyblish.api
import ayon_core.hosts.maya.api.action
from ayon_core.client import get_assets
from ayon_core.hosts.maya.api import lib
from ayon_core.pipeline import legacy_io
from ayon_core.pipeline.publish import (
PublishValidationError, ValidatePipelineOrder)

View file

@ -2,7 +2,6 @@ import pyblish.api
import ayon_core.hosts.maya.api.action
from ayon_core.client import get_subset_by_name
from ayon_core.pipeline import legacy_io
from ayon_core.pipeline.publish import PublishValidationError

View file

@ -5,8 +5,6 @@ import re
import pyblish.api
import ayon_core.hosts.maya.api.action
from ayon_core.pipeline import legacy_io
from ayon_core.settings import get_project_settings
from ayon_core.pipeline.publish import (
ValidateContentsOrder,
OptionalPyblishPluginMixin,

View file

@ -1,17 +1,14 @@
import os
import platform
from ayon_core.modules import OpenPypeModule, IHostAddon
from ayon_core.addon import AYONAddon, IHostAddon
NUKE_ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
class NukeAddon(OpenPypeModule, IHostAddon):
class NukeAddon(AYONAddon, IHostAddon):
name = "nuke"
host_name = "nuke"
def initialize(self, module_settings):
self.enabled = True
def add_implementation_envs(self, env, _app):
# Add requirements to NUKE_PATH
new_nuke_paths = [

View file

@ -1,16 +1,13 @@
import os
from ayon_core.modules import OpenPypeModule, IHostAddon
from ayon_core.addon import AYONAddon, IHostAddon
PHOTOSHOP_HOST_DIR = os.path.dirname(os.path.abspath(__file__))
class PhotoshopAddon(OpenPypeModule, IHostAddon):
class PhotoshopAddon(AYONAddon, IHostAddon):
name = "photoshop"
host_name = "photoshop"
def initialize(self, module_settings):
self.enabled = True
def add_implementation_envs(self, env, _app):
"""Modify environments to contain all required for implementation."""
defaults = {

View file

@ -17,7 +17,6 @@ import os
import pyblish.api
from ayon_core.pipeline import legacy_io
from openpype_modules.webpublisher.lib import (
get_batch_asset_task_info,
parse_json
@ -71,8 +70,6 @@ class CollectBatchData(pyblish.api.ContextPlugin):
os.environ["AVALON_ASSET"] = asset_name
os.environ["AVALON_TASK"] = task_name
legacy_io.Session["AVALON_ASSET"] = asset_name
legacy_io.Session["AVALON_TASK"] = task_name
context.data["asset"] = asset_name
context.data["task"] = task_name

View file

@ -1,17 +1,14 @@
import os
from ayon_core.modules import OpenPypeModule, IHostAddon
from ayon_core.addon import AYONAddon, IHostAddon
from .utils import RESOLVE_ROOT_DIR
class ResolveAddon(OpenPypeModule, IHostAddon):
class ResolveAddon(AYONAddon, IHostAddon):
name = "resolve"
host_name = "resolve"
def initialize(self, module_settings):
self.enabled = True
def get_launch_hook_paths(self, app):
if app.host_name != self.host_name:
return []

View file

@ -33,7 +33,7 @@ def ensure_installed_host():
def launch_menu():
print("Launching Resolve OpenPype menu..")
print("Launching Resolve AYON menu..")
ensure_installed_host()
ayon_core.hosts.resolve.api.launch_pype_menu()
@ -54,7 +54,7 @@ def main():
else:
log.info("No last workfile set to open. Skipping..")
# Launch OpenPype menu
# Launch AYON menu
from ayon_core.settings import get_project_settings
from ayon_core.pipeline.context_tools import get_current_project_name
project_name = get_current_project_name()
@ -62,7 +62,7 @@ def main():
settings = get_project_settings(project_name)
if settings.get("resolve", {}).get("launch_openpype_menu_on_start", True):
log.info("Launching OpenPype menu..")
log.info("Launching AYON menu..")
launch_menu()

View file

@ -1,16 +1,13 @@
import os
from ayon_core.modules import OpenPypeModule, IHostAddon
from ayon_core.addon import AYONAddon, IHostAddon
SUBSTANCE_HOST_DIR = os.path.dirname(os.path.abspath(__file__))
class SubstanceAddon(OpenPypeModule, IHostAddon):
class SubstanceAddon(AYONAddon, IHostAddon):
name = "substancepainter"
host_name = "substancepainter"
def initialize(self, module_settings):
self.enabled = True
def add_implementation_envs(self, env, _app):
# Add requirements to SUBSTANCE_PAINTER_PLUGINS_PATH
plugin_path = os.path.join(SUBSTANCE_HOST_DIR, "deploy")

View file

@ -2,9 +2,9 @@ import os
from ayon_core.lib import get_ayon_launcher_args
from ayon_core.lib.execute import run_detached_process
from ayon_core.modules import (
from ayon_core.addon import (
click_wrap,
OpenPypeModule,
AYONAddon,
ITrayAction,
IHostAddon,
)
@ -12,13 +12,12 @@ from ayon_core.modules import (
TRAYPUBLISH_ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
class TrayPublishAddon(OpenPypeModule, IHostAddon, ITrayAction):
class TrayPublishAddon(AYONAddon, IHostAddon, ITrayAction):
label = "Publisher"
name = "traypublisher"
host_name = "traypublisher"
def initialize(self, modules_settings):
self.enabled = True
def initialize(self, settings):
self.publish_paths = [
os.path.join(TRAYPUBLISH_ROOT_DIR, "plugins", "publish")
]
@ -36,7 +35,7 @@ class TrayPublishAddon(OpenPypeModule, IHostAddon, ITrayAction):
def run_traypublisher(self):
args = get_ayon_launcher_args(
"module", self.name, "launch"
"addon", self.name, "launch"
)
run_detached_process(args)

View file

@ -7,7 +7,6 @@ import pyblish.api
from ayon_core.pipeline import (
register_creator_plugin_path,
legacy_io,
)
from ayon_core.host import HostBase, IPublishHost
@ -24,7 +23,6 @@ class TrayPublisherHost(HostBase, IPublishHost):
def install(self):
os.environ["AVALON_APP"] = self.name
legacy_io.Session["AVALON_APP"] = self.name
pyblish.api.register_host("traypublisher")
pyblish.api.register_plugin_path(PUBLISH_PATH)
@ -43,8 +41,6 @@ class TrayPublisherHost(HostBase, IPublishHost):
# TODO Deregister project specific plugins and register new project
# plugins
os.environ["AVALON_PROJECT"] = project_name
legacy_io.Session["AVALON_PROJECT"] = project_name
legacy_io.install()
HostContext.set_project_name(project_name)

View file

@ -1,5 +1,5 @@
import os
from ayon_core.modules import OpenPypeModule, IHostAddon
from ayon_core.addon import AYONAddon, IHostAddon
TVPAINT_ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
@ -12,13 +12,10 @@ def get_launch_script_path():
)
class TVPaintAddon(OpenPypeModule, IHostAddon):
class TVPaintAddon(AYONAddon, IHostAddon):
name = "tvpaint"
host_name = "tvpaint"
def initialize(self, module_settings):
self.enabled = True
def add_implementation_envs(self, env, _app):
"""Modify environments to contain all required for implementation."""

View file

@ -13,7 +13,6 @@ from ayon_core.hosts.tvpaint import TVPAINT_ROOT_DIR
from ayon_core.settings import get_current_project_settings
from ayon_core.lib import register_event_callback
from ayon_core.pipeline import (
legacy_io,
register_loader_plugin_path,
register_creator_plugin_path,
AVALON_CONTAINER_ID,
@ -66,11 +65,10 @@ class TVPaintHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
def install(self):
"""Install TVPaint-specific functionality."""
log.info("OpenPype - Installing TVPaint integration")
legacy_io.install()
log.info("AYON - Installing TVPaint integration")
# Create workdir folder if does not exist yet
workdir = legacy_io.Session["AVALON_WORKDIR"]
workdir = os.getenv("AVALON_WORKDIR")
if not os.path.exists(workdir):
os.makedirs(workdir)

View file

@ -4,7 +4,6 @@ import tempfile
import pyblish.api
from ayon_core.pipeline import legacy_io
from ayon_core.hosts.tvpaint.api.lib import (
execute_george,
execute_george_through_file,
@ -90,7 +89,6 @@ class CollectWorkfileData(pyblish.api.ContextPlugin):
("AVALON_TASK", "task_name")
)
for env_key, key in key_map:
legacy_io.Session[env_key] = workfile_context[key]
os.environ[env_key] = workfile_context[key]
self.log.info("Context changed to: {}".format(workfile_context))

View file

@ -1,17 +1,14 @@
import os
import re
from ayon_core.modules import IHostAddon, OpenPypeModule
from ayon_core.addon import AYONAddon, IHostAddon
UNREAL_ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
class UnrealAddon(OpenPypeModule, IHostAddon):
class UnrealAddon(AYONAddon, IHostAddon):
name = "unreal"
host_name = "unreal"
def initialize(self, module_settings):
self.enabled = True
def get_global_environments(self):
return {
"AYON_UNREAL_ROOT": UNREAL_ROOT_DIR,

View file

@ -38,8 +38,8 @@ class AYONSecureRegistry:
Registry should be used for private data that should be available only for
user.
All passed registry names will have added prefix `OpenPype/` to easier
identify which data were created by OpenPype.
All passed registry names will have added prefix `AYON/` to easier
identify which data were created by AYON.
Args:
name(str): Name of registry used as identifier for data.

View file

@ -8,7 +8,6 @@ from ayon_core.lib import (
env_value_to_bool,
collect_frames,
)
from ayon_core.pipeline import legacy_io
from openpype_modules.deadline import abstract_submit_deadline
from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo
from ayon_core.tests.lib import is_in_tests
@ -84,13 +83,17 @@ class AfterEffectsSubmitDeadline(
"AVALON_PROJECT",
"AVALON_ASSET",
"AVALON_TASK",
"AVALON_WORKDIR",
"AVALON_APP_NAME",
"AYON_LOG_NO_COLORS",
"IS_TEST"
]
environment = dict({key: os.environ[key] for key in keys
if key in os.environ}, **legacy_io.Session)
environment = {
key: os.environ[key]
for key in keys
if key in os.environ
}
for key in keys:
value = environment.get(key)
if value:

View file

@ -11,7 +11,6 @@ from ayon_core.lib import (
NumberDef,
TextDef,
)
from ayon_core.pipeline import legacy_io
from ayon_core.pipeline.publish import AYONPyblishPluginMixin
from ayon_core.pipeline.farm.tools import iter_expected_files
from ayon_core.tests.lib import is_in_tests
@ -106,12 +105,16 @@ class BlenderSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline,
"AVALON_PROJECT",
"AVALON_ASSET",
"AVALON_TASK",
"AVALON_WORKDIR",
"AVALON_APP_NAME",
"IS_TEST"
]
environment = dict({key: os.environ[key] for key in keys
if key in os.environ}, **legacy_io.Session)
environment = {
key: os.environ[key]
for key in keys
if key in os.environ
}
for key in keys:
value = environment.get(key)

View file

@ -6,7 +6,6 @@ import requests
import pyblish.api
from ayon_core.pipeline import legacy_io
from ayon_core.pipeline.publish import (
AYONPyblishPluginMixin
)
@ -224,14 +223,18 @@ class FusionSubmitDeadline(
"AVALON_PROJECT",
"AVALON_ASSET",
"AVALON_TASK",
"AVALON_WORKDIR",
"AVALON_APP_NAME",
"AYON_LOG_NO_COLORS",
"IS_TEST",
"AYON_BUNDLE_NAME",
]
environment = dict({key: os.environ[key] for key in keys
if key in os.environ}, **legacy_io.Session)
environment = {
key: os.environ[key]
for key in keys
if key in os.environ
}
# to recognize render jobs
environment["AYON_RENDER_JOB"] = "1"

View file

@ -10,7 +10,6 @@ from datetime import datetime
import attr
import pyblish.api
from ayon_core.pipeline import legacy_io
from openpype_modules.deadline import abstract_submit_deadline
from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo
from ayon_core.tests.lib import is_in_tests
@ -277,13 +276,17 @@ class HarmonySubmitDeadline(
"AVALON_PROJECT",
"AVALON_ASSET",
"AVALON_TASK",
"AVALON_WORKDIR",
"AVALON_APP_NAME",
"AYON_LOG_NO_COLORS"
"IS_TEST"
]
environment = dict({key: os.environ[key] for key in keys
if key in os.environ}, **legacy_io.Session)
environment = {
key: os.environ[key]
for key in keys
if key in os.environ
}
for key in keys:
value = environment.get(key)
if value:

View file

@ -9,7 +9,6 @@ from ayon_core.lib import (
NumberDef,
)
from ayon_core.pipeline import (
legacy_io,
AYONPyblishPluginMixin
)
from ayon_core.tests.lib import is_in_tests
@ -102,12 +101,16 @@ class HoudiniCacheSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline
"AVALON_PROJECT",
"AVALON_ASSET",
"AVALON_TASK",
"AVALON_WORKDIR",
"AVALON_APP_NAME",
"AYON_LOG_NO_COLORS",
]
environment = dict({key: os.environ[key] for key in keys
if key in os.environ}, **legacy_io.Session)
environment = {
key: os.environ[key]
for key in keys
if key in os.environ
}
for key in keys:
value = environment.get(key)

View file

@ -5,7 +5,7 @@ from datetime import datetime
import pyblish.api
from ayon_core.pipeline import legacy_io, AYONPyblishPluginMixin
from ayon_core.pipeline import AYONPyblishPluginMixin
from ayon_core.tests.lib import is_in_tests
from openpype_modules.deadline import abstract_submit_deadline
from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo
@ -207,12 +207,16 @@ class HoudiniSubmitDeadline(
"AVALON_PROJECT",
"AVALON_ASSET",
"AVALON_TASK",
"AVALON_WORKDIR",
"AVALON_APP_NAME",
"AYON_LOG_NO_COLORS",
]
environment = dict({key: os.environ[key] for key in keys
if key in os.environ}, **legacy_io.Session)
environment = {
key: os.environ[key]
for key in keys
if key in os.environ
}
for key in keys:
value = environment.get(key)

View file

@ -9,7 +9,6 @@ from ayon_core.lib import (
NumberDef,
)
from ayon_core.pipeline import (
legacy_io,
AYONPyblishPluginMixin
)
from ayon_core.pipeline.publish.lib import (
@ -110,12 +109,16 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline,
"AVALON_PROJECT",
"AVALON_ASSET",
"AVALON_TASK",
"AVALON_WORKDIR",
"AVALON_APP_NAME",
"IS_TEST"
]
environment = dict({key: os.environ[key] for key in keys
if key in os.environ}, **legacy_io.Session)
environment = {
key: os.environ[key]
for key in keys
if key in os.environ
}
for key in keys:
value = environment.get(key)

View file

@ -29,7 +29,6 @@ from collections import OrderedDict
import attr
from ayon_core.pipeline import (
legacy_io,
AYONPyblishPluginMixin
)
from ayon_core.lib import (
@ -203,12 +202,16 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline,
"AVALON_PROJECT",
"AVALON_ASSET",
"AVALON_TASK",
"AVALON_WORKDIR",
"AVALON_APP_NAME",
"IS_TEST"
]
environment = dict({key: os.environ[key] for key in keys
if key in os.environ}, **legacy_io.Session)
environment = {
key: os.environ[key]
for key in keys
if key in os.environ
}
for key in keys:
value = environment.get(key)

View file

@ -2,7 +2,7 @@ import os
import attr
from datetime import datetime
from ayon_core.pipeline import legacy_io, PublishXmlValidationError
from ayon_core.pipeline import PublishXmlValidationError
from ayon_core.tests.lib import is_in_tests
from openpype_modules.deadline import abstract_submit_deadline
from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo
@ -98,10 +98,12 @@ class MayaSubmitRemotePublishDeadline(
"FTRACK_SERVER"
]
environment = dict({key: os.environ[key] for key in keys
if key in os.environ}, **legacy_io.Session)
environment = {
key: os.environ[key]
for key in keys
if key in os.environ
}
# TODO replace legacy_io with context.data
environment["AVALON_PROJECT"] = project_name
environment["AVALON_ASSET"] = instance.context.data["asset"]
environment["AVALON_TASK"] = instance.context.data["task"]

View file

@ -7,7 +7,6 @@ from datetime import datetime
import requests
import pyblish.api
from ayon_core.pipeline import legacy_io
from ayon_core.pipeline.publish import (
AYONPyblishPluginMixin
)
@ -393,8 +392,11 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin,
if self.env_allowed_keys:
keys += self.env_allowed_keys
environment = dict({key: os.environ[key] for key in keys
if key in os.environ}, **legacy_io.Session)
environment = {
key: os.environ[key]
for key in keys
if key in os.environ
}
# to recognize render jobs
environment["AYON_RENDER_JOB"] = "1"

View file

@ -11,8 +11,8 @@ import pyblish.api
from ayon_core.client import (
get_last_version_by_subset_name,
)
from ayon_core.pipeline import publish, legacy_io
from ayon_core.lib import EnumDef, is_running_from_build
from ayon_core.pipeline import publish
from ayon_core.lib import EnumDef
from ayon_core.tests.lib import is_in_tests
from ayon_core.pipeline.version_start import get_versioning_start
@ -370,7 +370,6 @@ class ProcessSubmittedCacheJobOnFarm(pyblish.api.InstancePlugin,
"intent": instance.context.data.get("intent"),
"comment": instance.context.data.get("comment"),
"job": render_job or None,
"session": legacy_io.Session.copy(),
"instances": instances
}

View file

@ -12,8 +12,8 @@ import pyblish.api
from ayon_core.client import (
get_last_version_by_subset_name,
)
from ayon_core.pipeline import publish, legacy_io
from ayon_core.lib import EnumDef, is_running_from_build
from ayon_core.pipeline import publish
from ayon_core.lib import EnumDef
from ayon_core.tests.lib import is_in_tests
from ayon_core.pipeline.version_start import get_versioning_start
@ -604,7 +604,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin,
"intent": instance.context.data.get("intent"),
"comment": instance.context.data.get("comment"),
"job": render_job or None,
"session": legacy_io.Session.copy(),
"instances": instances
}

View file

@ -1,4 +1,4 @@
"""Job queue OpenPype module was created for remote execution of commands.
"""Job queue AYON addon was created for remote execution of commands.
## Why is needed
Primarily created for hosts which are not easilly controlled from command line
@ -30,7 +30,7 @@ workstations know where to send or receive jobs.
### start_worker
- start worker which will process jobs
- has required possitional argument which is application name from OpenPype
- has required possitional argument which is application name from AYON
settings e.g. 'tvpaint/11-5' ('tvpaint' is group '11-5' is variant)
- it is possible to specify server url but url from settings is used when not
passed (this is added mainly for developing purposes)

View file

@ -1,19 +1,14 @@
import os
from ayon_core import AYON_CORE_ROOT
from ayon_core.modules import (
OpenPypeModule,
ITrayAction,
)
from ayon_core.addon import AYONAddon, ITrayAction
class LauncherAction(OpenPypeModule, ITrayAction):
class LauncherAction(AYONAddon, ITrayAction):
label = "Launcher"
name = "launcher_tool"
def initialize(self, _modules_settings):
# This module is always enabled
self.enabled = True
def initialize(self, settings):
# Tray attributes
self._window = None

View file

@ -1,67 +0,0 @@
from ayon_core.modules import AYONAddon, ITrayModule
class LibraryLoaderAddon(AYONAddon, ITrayModule):
name = "library_tool"
def initialize(self, modules_settings):
# Tray attributes
self._library_loader_imported = None
self._library_loader_window = None
def tray_init(self):
# Add library tool
self._library_loader_imported = False
try:
from ayon_core.tools.loader.ui import LoaderWindow
self._library_loader_imported = True
except Exception:
self.log.warning(
"Couldn't load Library loader tool for tray.",
exc_info=True
)
# Definition of Tray menu
def tray_menu(self, tray_menu):
if not self._library_loader_imported:
return
from qtpy import QtWidgets
# Actions
action_library_loader = QtWidgets.QAction(
"Loader", tray_menu
)
action_library_loader.triggered.connect(self.show_library_loader)
tray_menu.addAction(action_library_loader)
def tray_start(self, *_a, **_kw):
return
def tray_exit(self, *_a, **_kw):
return
def show_library_loader(self):
if self._library_loader_window is None:
from ayon_core.pipeline import install_ayon_plugins
self._init_library_loader()
install_ayon_plugins()
self._library_loader_window.show()
# Raise and activate the window
# for MacOS
self._library_loader_window.raise_()
# for Windows
self._library_loader_window.activateWindow()
def _init_library_loader(self):
from ayon_core.tools.loader.ui import LoaderWindow
libraryloader = LoaderWindow()
self._library_loader_window = libraryloader

View file

@ -0,0 +1,67 @@
from ayon_core.addon import AYONAddon, ITrayAddon
class LoaderAddon(AYONAddon, ITrayAddon):
name = "loader_tool"
def initialize(self, settings):
# Tray attributes
self._loader_imported = None
self._loader_window = None
def tray_init(self):
# Add library tool
self._loader_imported = False
try:
from ayon_core.tools.loader.ui import LoaderWindow
self._loader_imported = True
except Exception:
self.log.warning(
"Couldn't load Loader tool for tray.",
exc_info=True
)
# Definition of Tray menu
def tray_menu(self, tray_menu):
if not self._loader_imported:
return
from qtpy import QtWidgets
# Actions
action_loader = QtWidgets.QAction(
"Loader", tray_menu
)
action_loader.triggered.connect(self.show_loader)
tray_menu.addAction(action_loader)
def tray_start(self, *_a, **_kw):
return
def tray_exit(self, *_a, **_kw):
return
def show_loader(self):
if self._loader_window is None:
from ayon_core.pipeline import install_ayon_plugins
self._init_loader()
install_ayon_plugins()
self._loader_window.show()
# Raise and activate the window
# for MacOS
self._loader_window.raise_()
# for Windows
self._loader_window.activateWindow()
def _init_loader(self):
from ayon_core.tools.loader.ui import LoaderWindow
libraryloader = LoaderWindow()
self._loader_window = libraryloader

View file

@ -1,4 +1,4 @@
from .module import (
from .addon import (
PythonInterpreterAction
)

View file

@ -1,13 +1,12 @@
from ayon_core.modules import OpenPypeModule, ITrayAction
from ayon_core.addon import AYONAddon, ITrayAction
class PythonInterpreterAction(OpenPypeModule, ITrayAction):
class PythonInterpreterAction(AYONAddon, ITrayAction):
label = "Console"
name = "python_interpreter"
admin_action = True
def initialize(self, modules_settings):
self.enabled = True
def initialize(self, settings):
self._interpreter_window = None
def tray_init(self):
@ -22,7 +21,7 @@ class PythonInterpreterAction(OpenPypeModule, ITrayAction):
if self._interpreter_window:
return
from openpype_modules.python_console_interpreter.window import (
from ayon_core.modules.python_console_interpreter.window import (
PythonInterpreterWidget
)

View file

@ -8,8 +8,6 @@ from pprint import pformat
import pyblish.api
from ayon_core.pipeline import legacy_io
def collect(root,
regex=None,
@ -132,7 +130,6 @@ class CollectSequencesFromJob(pyblish.api.ContextPlugin):
session = metadata.get("session")
if session:
self.log.info("setting session using metadata")
legacy_io.Session.update(session)
os.environ.update(session)
else:

View file

@ -13,9 +13,6 @@ from ayon_core.modules.royalrender.rr_job import (
get_rr_platform
)
from ayon_core.pipeline.publish import KnownPublishError
from ayon_core.pipeline import (
legacy_io,
)
from ayon_core.pipeline.farm.pyblish_functions import (
create_skeleton_instance,
create_instances_for_aov,
@ -145,7 +142,6 @@ class CreatePublishRoyalRenderJob(pyblish.api.InstancePlugin,
"intent": instance.context.data.get("intent"),
"comment": instance.context.data.get("comment"),
"job": attr.asdict(rr_job),
"session": legacy_io.Session.copy(),
"instances": instances
}

View file

@ -9,7 +9,7 @@ class WidgetUserIdle(QtWidgets.QWidget):
def __init__(self, module):
super(WidgetUserIdle, self).__init__()
self.setWindowTitle("OpenPype - Stop timers")
self.setWindowTitle("AYON - Stop timers")
icon = QtGui.QIcon(resources.get_ayon_icon_filepath())
self.setWindowIcon(icon)

View file

@ -1,6 +1,6 @@
"""WebServerAddon spawns aiohttp server in asyncio loop.
Main usage of the module is in OpenPype tray where make sense to add ability
Main usage of the module is in AYON tray where make sense to add ability
of other modules to add theirs routes. Module which would want use that
option must have implemented method `webserver_initialization` which must
expect `WebServerManager` object where is possible to add routes or paths

View file

@ -1,7 +1,6 @@
"""Core pipeline functionality"""
import os
import json
import types
import logging
import platform
@ -29,11 +28,11 @@ from .publish.lib import filter_pyblish_plugins
from .anatomy import Anatomy
from .template_data import get_template_data_with_names
from .workfile import (
get_workdir,
get_workfile_template_key,
get_custom_workfile_template_by_string_context,
)
from . import (
legacy_io,
register_loader_plugin_path,
register_inventory_action_path,
register_creator_plugin_path,
@ -116,22 +115,17 @@ def install_host(host):
# Make sure global AYON connection has set site id and version
get_ayon_server_api_connection()
legacy_io.install()
addons_manager = _get_addons_manager()
missing = list()
for key in ("AVALON_PROJECT", "AVALON_ASSET"):
if key not in legacy_io.Session:
missing.append(key)
project_name = os.getenv("AVALON_PROJECT")
# WARNING: This might be an issue
# - commented out because 'traypublisher' does not have set project
# if not project_name:
# raise ValueError(
# "AVALON_PROJECT is missing in environment variables."
# )
assert not missing, (
"%s missing from environment, %s" % (
", ".join(missing),
json.dumps(legacy_io.Session, indent=4, sort_keys=True)
))
project_name = legacy_io.Session["AVALON_PROJECT"]
log.info("Activating %s.." % project_name)
log.info("Activating {}..".format(project_name))
# Optional host install function
if hasattr(host, "install"):
@ -158,14 +152,13 @@ def install_host(host):
print("Registering pyblish target: automated")
pyblish.api.register_target("automated")
project_name = os.environ.get("AVALON_PROJECT")
host_name = os.environ.get("AVALON_APP")
# Give option to handle host installation
for addon in addons_manager.get_enabled_addons():
addon.on_host_install(host, host_name, project_name)
install_openpype_plugins(project_name, host_name)
install_ayon_plugins(project_name, host_name)
def install_ayon_plugins(project_name=None, host_name=None):
@ -256,8 +249,6 @@ def uninstall_host():
deregister_host()
legacy_io.uninstall()
log.info("Successfully uninstalled Avalon!")
@ -482,13 +473,17 @@ def get_template_data_from_session(session=None, system_settings=None):
Dict[str, Any]: All available data from session.
"""
if session is None:
session = legacy_io.Session
project_name = session["AVALON_PROJECT"]
asset_name = session["AVALON_ASSET"]
task_name = session["AVALON_TASK"]
host_name = session["AVALON_APP"]
if session is not None:
project_name = session["AVALON_PROJECT"]
asset_name = session["AVALON_ASSET"]
task_name = session["AVALON_TASK"]
host_name = session["AVALON_APP"]
else:
context = get_current_context()
project_name = context["project_name"]
asset_name = context["asset_name"]
task_name = context["task_name"]
host_name = get_current_host_name()
return get_template_data_with_names(
project_name, asset_name, task_name, host_name, system_settings
@ -529,10 +524,12 @@ def get_workdir_from_session(session=None, template_key=None):
str: Workdir path.
"""
if session is None:
session = legacy_io.Session
project_name = session["AVALON_PROJECT"]
host_name = session["AVALON_APP"]
if session is not None:
project_name = session["AVALON_PROJECT"]
host_name = session["AVALON_APP"]
else:
project_name = get_current_project_name()
host_name = get_current_host_name()
template_data = get_template_data_from_session(session)
if not template_key:
@ -556,86 +553,39 @@ def get_custom_workfile_template_from_session(
):
"""Filter and fill workfile template profiles by current context.
Current context is defined by `legacy_io.Session`. That's why this
function should be used only inside host where context is set and stable.
This function cab be used only inside host where context is set.
Args:
session (Union[None, Dict[str, str]]): Session from which are taken
session (Optional[Dict[str, str]]): Session from which are taken
data.
project_settings(Dict[str, Any]): Template profiles from settings.
project_settings(Optional[Dict[str, Any]]): Project settings.
Returns:
str: Path to template or None if none of profiles match current
context. (Existence of formatted path is not validated.)
"""
if session is None:
session = legacy_io.Session
if session is not None:
project_name = session["AVALON_PROJECT"]
asset_name = session["AVALON_ASSET"]
task_name = session["AVALON_TASK"]
host_name = session["AVALON_APP"]
else:
context = get_current_context()
project_name = context["project_name"]
asset_name = context["asset_name"]
task_name = context["task_name"]
host_name = get_current_host_name()
return get_custom_workfile_template_by_string_context(
session["AVALON_PROJECT"],
session["AVALON_ASSET"],
session["AVALON_TASK"],
session["AVALON_APP"],
project_name,
asset_name,
task_name,
host_name,
project_settings=project_settings
)
def compute_session_changes(
session, asset_doc, task_name, template_key=None
):
"""Compute the changes for a session object on task under asset.
Function does not change the session object, only returns changes.
Args:
session (Dict[str, str]): The initial session to compute changes to.
This is required for computing the full Work Directory, as that
also depends on the values that haven't changed.
asset_doc (Dict[str, Any]): Asset document to switch to.
task_name (str): Name of task to switch to.
template_key (Union[str, None]): Prepare workfile template key in
anatomy templates.
Returns:
Dict[str, str]: Changes in the Session dictionary.
"""
# Get asset document and asset
if not asset_doc:
task_name = None
asset_name = None
else:
asset_name = get_asset_name_identifier(asset_doc)
# Detect any changes compared session
mapping = {
"AVALON_ASSET": asset_name,
"AVALON_TASK": task_name,
}
changes = {
key: value
for key, value in mapping.items()
if value != session.get(key)
}
if not changes:
return changes
# Compute work directory (with the temporary changed session so far)
changed_session = session.copy()
changed_session.update(changes)
workdir = None
if asset_doc:
workdir = get_workdir_from_session(
changed_session, template_key
)
changes["AVALON_WORKDIR"] = workdir
return changes
def change_current_context(asset_doc, task_name, template_key=None):
"""Update active Session to a new task work area.
@ -651,32 +601,47 @@ def change_current_context(asset_doc, task_name, template_key=None):
Dict[str, str]: The changed key, values in the current Session.
"""
changes = compute_session_changes(
legacy_io.Session,
asset_doc,
task_name,
template_key=template_key
)
project_name = get_current_project_name()
workdir = None
if asset_doc:
project_doc = get_project(project_name)
host_name = get_current_host_name()
workdir = get_workdir(
project_doc,
asset_doc,
task_name,
host_name,
template_key=template_key
)
folder_path = get_asset_name_identifier(asset_doc)
envs = {
"AVALON_PROJECT": project_name,
"AVALON_ASSET": folder_path,
"AVALON_TASK": task_name,
"AVALON_WORKDIR": workdir,
}
# Update the Session and environments. Pop from environments all keys with
# value set to None.
for key, value in changes.items():
legacy_io.Session[key] = value
for key, value in envs.items():
if value is None:
os.environ.pop(key, None)
else:
os.environ[key] = value
data = changes.copy()
data = envs.copy()
# Convert env keys to human readable keys
data["project_name"] = legacy_io.Session["AVALON_PROJECT"]
data["asset_name"] = legacy_io.Session["AVALON_ASSET"]
data["task_name"] = legacy_io.Session["AVALON_TASK"]
data["project_name"] = project_name
data["asset_name"] = get_asset_name_identifier(asset_doc)
data["task_name"] = task_name
data["workdir_path"] = workdir
# Emit session change
emit_event("taskChanged", data)
return changes
return data
def get_process_id():

View file

@ -27,7 +27,7 @@ from ayon_core.lib.attribute_definitions import (
get_default_values,
)
from ayon_core.host import IPublishHost, IWorkfileHost
from ayon_core.pipeline import legacy_io, Anatomy
from ayon_core.pipeline import Anatomy
from ayon_core.pipeline.plugin_discover import DiscoverResult
from .creator_plugins import (
@ -1684,25 +1684,16 @@ class CreateContext:
if isinstance(self.host, IWorkfileHost):
workfile_path = self.host.get_current_workfile()
# --- TODO remove these conditions ---
if not project_name:
project_name = legacy_io.Session.get("AVALON_PROJECT")
if not asset_name:
asset_name = legacy_io.Session.get("AVALON_ASSET")
if not task_name:
task_name = legacy_io.Session.get("AVALON_TASK")
# ---
return project_name, asset_name, task_name, workfile_path
def reset_current_context(self):
"""Refresh current context.
Reset is based on optional host implementation of `get_current_context`
function or using `legacy_io.Session`.
function.
Some hosts have ability to change context file without using workfiles
tool but that change is not propagated to 'legacy_io.Session'
nor 'os.environ'.
tool but that change is not propagated to 'os.environ'.
Todos:
UI: Current context should be also checked on save - compare

View file

@ -2,7 +2,6 @@ import os
from ayon_core.settings import get_project_settings
from ayon_core.lib import filter_profiles, prepare_template_data
from ayon_core.pipeline import legacy_io
from .constants import DEFAULT_SUBSET_TEMPLATE
@ -135,7 +134,7 @@ def get_subset_name(
family = family.rsplit(".", 1)[-1]
if project_name is None:
project_name = legacy_io.Session["AVALON_PROJECT"]
project_name = os.environ.get("AVALON_PROJECT")
asset_tasks = asset_doc.get("data", {}).get("tasks") or {}
task_info = asset_tasks.get(task_name) or {}

View file

@ -11,7 +11,7 @@ def match_aov_pattern(host_name, aov_patterns, render_file_name):
that we have grabbed from `exp_files`.
Args:
app (str): Host name.
host_name (str): Host name.
aov_patterns (dict): AOV patterns from AOV filters.
render_file_name (str): Incoming file name to match against.

View file

@ -1,109 +1,36 @@
"""Wrapper around interactions with the database"""
import os
import sys
import logging
import functools
from . import schema
module = sys.modules[__name__]
from ayon_core.pipeline import get_current_project_name
Session = {}
_is_installed = False
log = logging.getLogger(__name__)
SESSION_CONTEXT_KEYS = (
# Name of current Project
"AVALON_PROJECT",
# Name of current Asset
"AVALON_ASSET",
# Name of current task
"AVALON_TASK",
# Name of current app
"AVALON_APP",
# Path to working directory
"AVALON_WORKDIR",
# Optional path to scenes directory (see Work Files API)
"AVALON_SCENEDIR"
log.warning(
"DEPRECATION WARNING: 'legacy_io' is deprecated and will be removed in"
" future versions of ayon-core addon."
"\nReading from Session won't give you updated information and changing"
" values won't affect global state of a process."
)
def session_data_from_environment(context_keys=False):
session_data = {}
if context_keys:
for key in SESSION_CONTEXT_KEYS:
value = os.environ.get(key)
session_data[key] = value or ""
else:
for key in SESSION_CONTEXT_KEYS:
session_data[key] = None
for key, default_value in (
# Name of Avalon in graphical user interfaces
# Use this to customise the visual appearance of Avalon
# to better integrate with your surrounding pipeline
("AVALON_LABEL", "Avalon"),
# Used during any connections to the outside world
("AVALON_TIMEOUT", "1000"),
# Name of database used in MongoDB
("AVALON_DB", "avalon"),
):
value = os.environ.get(key) or default_value
if value is not None:
session_data[key] = value
return session_data
return {}
def is_installed():
return module._is_installed
return False
def install():
"""Establish a persistent connection to the database"""
if is_installed():
return
session = session_data_from_environment(context_keys=True)
session["schema"] = "openpype:session-4.0"
try:
schema.validate(session)
except schema.ValidationError as e:
# TODO(marcus): Make this mandatory
log.warning(e)
Session.update(session)
module._is_installed = True
pass
def uninstall():
"""Close any connection to the database.
Deprecated:
This function does nothing should be removed.
"""
module._is_installed = False
pass
def requires_install(func):
@functools.wraps(func)
def decorated(*args, **kwargs):
if not is_installed():
install()
return func(*args, **kwargs)
return decorated
@requires_install
def active_project(*args, **kwargs):
return Session["AVALON_PROJECT"]
return get_current_project_name()
def current_project(*args, **kwargs):
return Session.get("AVALON_PROJECT")
return get_current_project_name()

View file

@ -2,10 +2,7 @@ import os
import logging
from ayon_core.settings import get_system_settings, get_project_settings
from ayon_core.pipeline import (
schema,
legacy_io,
)
from ayon_core.pipeline import schema
from ayon_core.pipeline.plugin_discover import (
discover,
register_plugin,

View file

@ -5,7 +5,7 @@ import os
import pyblish.api
from ayon_core.host import IPublishHost
from ayon_core.pipeline import legacy_io, registered_host
from ayon_core.pipeline import registered_host
from ayon_core.pipeline.create import CreateContext
@ -61,7 +61,6 @@ class CollectFromCreateContext(pyblish.api.ContextPlugin):
("AVALON_ASSET", asset_name),
("AVALON_TASK", task_name)
):
legacy_io.Session[key] = value
os.environ[key] = value
def create_instance(

View file

@ -12,7 +12,7 @@ import json
import pyblish.api
from ayon_core.pipeline import legacy_io, KnownPublishError
from ayon_core.pipeline import KnownPublishError
from ayon_core.pipeline.publish.lib import add_repre_files_for_cleanup
@ -72,7 +72,7 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin):
# validate basic necessary data
data_err = "invalid json file - missing data"
required = ["asset", "user", "comment",
"job", "instances", "session", "version"]
"job", "instances", "version"]
assert all(elem in data.keys() for elem in required), data_err
# set context by first json file
@ -144,7 +144,7 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin):
os.environ.get("AYON_PUBLISH_DATA")
or os.environ.get("OPENPYPE_PUBLISH_DATA")
)
if publish_data_paths:
if not publish_data_paths:
raise KnownPublishError("Missing `AYON_PUBLISH_DATA`")
# QUESTION
@ -165,24 +165,28 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin):
path = anatomy.fill_root(path)
data = self._load_json(path)
assert data, "failed to load json file"
if not session_is_set:
session_data = data["session"]
remapped = anatomy.roots_obj.path_remapper(
session_data["AVALON_WORKDIR"]
)
if remapped:
session_data["AVALON_WORKDIR"] = remapped
self.log.debug("Setting session using data from file")
legacy_io.Session.update(session_data)
os.environ.update(session_data)
session_data = data.get("session")
if not session_is_set and session_data:
session_is_set = True
self.log.debug("Setting session using data from file")
os.environ.update(session_data)
staging_dir_persistent = self._process_path(data, anatomy)
if not staging_dir_persistent:
context.data["cleanupFullPaths"].append(path)
context.data["cleanupEmptyDirs"].append(
os.path.dirname(path)
)
# Remap workdir if it's set
workdir = os.getenv("AVALON_WORKDIR")
remapped_workdir = None
if workdir:
remapped_workdir = anatomy.roots_obj.path_remapper(
os.getenv("AVALON_WORKDIR")
)
if remapped_workdir:
os.environ["AVALON_WORKDIR"] = remapped_workdir
except Exception as e:
self.log.error(e, exc_info=True)
raise Exception("Error") from e

View file

@ -2,6 +2,7 @@ import copy
import os
import subprocess
import tempfile
import re
import pyblish.api
from ayon_core.lib import (
@ -35,6 +36,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
"traypublisher",
"substancepainter",
"nuke",
"aftereffects"
]
enabled = False
@ -49,6 +51,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
# attribute presets from settings
oiiotool_defaults = None
ffmpeg_args = None
product_names = []
def process(self, instance):
# run main process
@ -103,6 +106,26 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
self.log.debug("Skipping crypto passes.")
return
# We only want to process the subsets needed from settings.
def validate_string_against_patterns(input_str, patterns):
for pattern in patterns:
if re.match(pattern, input_str):
return True
return False
product_names = self.product_names
if product_names:
result = validate_string_against_patterns(
instance.data["subset"], product_names
)
if not result:
self.log.debug(
"Product name \"{}\" did not match settings filters: {}".format(
instance.data["subset"], product_names
)
)
return
# first check for any explicitly marked representations for thumbnail
explicit_repres = self._get_explicit_repres_for_thumbnail(instance)
if explicit_repres:

View file

@ -70,6 +70,7 @@
},
"ExtractThumbnail": {
"enabled": true,
"subsets": [],
"integrate_thumbnail": false,
"background_color": [
0,

View file

@ -198,7 +198,7 @@ def _load_font():
def load_stylesheet():
"""Load and return OpenPype Qt stylesheet."""
"""Load and return AYON Qt stylesheet."""
if _Cache.stylesheet is None:
_Cache.stylesheet = _load_stylesheet()
@ -207,7 +207,7 @@ def load_stylesheet():
def get_app_icon_path():
"""Path to OpenPype icon."""
"""Path to AYON icon."""
return resources.get_ayon_icon_filepath()

View file

@ -21,7 +21,7 @@ class CreateWidgetAssetsWidget(SingleSelectAssetsWidget):
def __init__(self, controller, parent):
self._controller = controller
super(CreateWidgetAssetsWidget, self).__init__(None, parent)
super(CreateWidgetAssetsWidget, self).__init__(parent)
self.set_refresh_btn_visibility(False)
self.set_current_asset_btn_visibility(False)
@ -31,6 +31,9 @@ class CreateWidgetAssetsWidget(SingleSelectAssetsWidget):
self._last_filter_height = None
def get_project_name(self):
return self._controller.project_name
def get_selected_asset_name(self):
selection_model = self._view.selectionModel()
indexes = selection_model.selectedRows()
@ -79,10 +82,10 @@ class CreateWidgetAssetsWidget(SingleSelectAssetsWidget):
def update_current_asset(self):
# Hide set current asset if there is no one
asset_name = self._get_current_session_asset()
asset_name = self._get_current_asset_name()
self.set_current_asset_btn_visibility(bool(asset_name))
def _get_current_session_asset(self):
def _get_current_asset_name(self):
return self._controller.current_asset_name
def _create_source_model(self):

View file

@ -565,7 +565,7 @@ class CreateWidget(QtWidgets.QWidget):
self._last_thumbnail_path = None
def _on_current_session_context_request(self):
self._assets_widget.set_current_session_asset()
self._assets_widget.select_current_asset()
task_name = self.current_task_name
if task_name:
self._tasks_widget.select_task_name(task_name)

View file

@ -1,8 +1,12 @@
from qtpy import QtCore, QtGui
from qtpy import QtWidgets, QtCore, QtGui
from ayon_core.tools.utils.tasks_widget import TasksWidget, TASK_NAME_ROLE
from ayon_core.tools.utils.views import DeselectableTreeView
from ayon_core.tools.utils.lib import get_default_task_icon
TASK_NAME_ROLE = QtCore.Qt.UserRole + 1
TASK_TYPE_ROLE = QtCore.Qt.UserRole + 2
TASK_ORDER_ROLE = QtCore.Qt.UserRole + 3
class TasksModel(QtGui.QStandardItemModel):
"""Tasks model.
@ -141,15 +145,159 @@ class TasksModel(QtGui.QStandardItemModel):
return super(TasksModel, self).headerData(section, orientation, role)
class CreateWidgetTasksWidget(TasksWidget):
class TasksProxyModel(QtCore.QSortFilterProxyModel):
def lessThan(self, x_index, y_index):
x_order = x_index.data(TASK_ORDER_ROLE)
y_order = y_index.data(TASK_ORDER_ROLE)
if x_order is not None and y_order is not None:
if x_order < y_order:
return True
if x_order > y_order:
return False
elif x_order is None and y_order is not None:
return True
elif y_order is None and x_order is not None:
return False
x_name = x_index.data(QtCore.Qt.DisplayRole)
y_name = y_index.data(QtCore.Qt.DisplayRole)
if x_name == y_name:
return True
if x_name == tuple(sorted((x_name, y_name)))[0]:
return True
return False
class CreateWidgetTasksWidget(QtWidgets.QWidget):
"""Widget showing active Tasks
Deprecated:
This widget will be removed soon. Please do not use it in new code.
"""
task_changed = QtCore.Signal()
def __init__(self, controller, parent):
self._controller = controller
super(CreateWidgetTasksWidget, self).__init__(None, parent)
self._enabled = None
def _create_source_model(self):
return TasksModel(self._controller)
super(CreateWidgetTasksWidget, self).__init__(parent)
tasks_view = DeselectableTreeView(self)
tasks_view.setIndentation(0)
tasks_view.setSortingEnabled(True)
tasks_view.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
header_view = tasks_view.header()
header_view.setSortIndicator(0, QtCore.Qt.AscendingOrder)
tasks_model = TasksModel(self._controller)
tasks_proxy = TasksProxyModel()
tasks_proxy.setSourceModel(tasks_model)
tasks_view.setModel(tasks_proxy)
layout = QtWidgets.QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(tasks_view)
selection_model = tasks_view.selectionModel()
selection_model.selectionChanged.connect(self._on_task_change)
self._tasks_model = tasks_model
self._tasks_proxy = tasks_proxy
self._tasks_view = tasks_view
self._last_selected_task_name = None
def refresh(self):
self._tasks_model.refresh()
def set_asset_id(self, asset_id):
# Try and preserve the last selected task and reselect it
# after switching assets. If there's no currently selected
# asset keep whatever the "last selected" was prior to it.
current = self.get_selected_task_name()
if current:
self._last_selected_task_name = current
self._tasks_model.set_asset_id(asset_id)
if self._last_selected_task_name:
self.select_task_name(self._last_selected_task_name)
# Force a task changed emit.
self.task_changed.emit()
def _clear_selection(self):
selection_model = self._tasks_view.selectionModel()
selection_model.clearSelection()
def select_task_name(self, task_name):
"""Select a task by name.
If the task does not exist in the current model then selection is only
cleared.
Args:
task_name (str): Name of the task to select.
"""
task_view_model = self._tasks_view.model()
if not task_view_model:
return
# Clear selection
selection_model = self._tasks_view.selectionModel()
selection_model.clearSelection()
# Select the task
mode = (
QtCore.QItemSelectionModel.Select
| QtCore.QItemSelectionModel.Rows
)
for row in range(task_view_model.rowCount()):
index = task_view_model.index(row, 0)
name = index.data(TASK_NAME_ROLE)
if name == task_name:
selection_model.select(index, mode)
# Set the currently active index
self._tasks_view.setCurrentIndex(index)
break
last_selected_task_name = self.get_selected_task_name()
if last_selected_task_name:
self._last_selected_task_name = last_selected_task_name
if not self._enabled:
current = self.get_selected_task_name()
if current:
self._last_selected_task_name = current
self._clear_selection()
def get_selected_task_name(self):
"""Return name of task at current index (selected)
Returns:
str: Name of the current task.
"""
index = self._tasks_view.currentIndex()
selection_model = self._tasks_view.selectionModel()
if index.isValid() and selection_model.isSelected(index):
return index.data(TASK_NAME_ROLE)
return None
def get_selected_task_type(self):
index = self._tasks_view.currentIndex()
selection_model = self._tasks_view.selectionModel()
if index.isValid() and selection_model.isSelected(index):
return index.data(TASK_TYPE_ROLE)
return None
def set_asset_name(self, asset_name):
current = self.get_selected_task_name()
@ -163,14 +311,6 @@ class CreateWidgetTasksWidget(TasksWidget):
# Force a task changed emit.
self.task_changed.emit()
def select_task_name(self, task_name):
super(CreateWidgetTasksWidget, self).select_task_name(task_name)
if not self._enabled:
current = self.get_selected_task_name()
if current:
self._last_selected_task_name = current
self._clear_selection()
def set_enabled(self, enabled):
self._enabled = enabled
if not enabled:
@ -181,3 +321,6 @@ class CreateWidgetTasksWidget(TasksWidget):
elif self._last_selected_task_name is not None:
self.select_task_name(self._last_selected_task_name)
def _on_task_change(self):
self.task_changed.emit()

View file

@ -14,8 +14,7 @@ from .models import SiteSyncModel
class SceneInventoryController:
"""This is a temporary controller for AYON.
Goal of this temporary controller is to provide a way to get current
context instead of using 'AvalonMongoDB' object (or 'legacy_io').
Goal of this controller is to provide a way to get current context.
Also provides (hopefully) cleaner api for site sync.
"""

View file

@ -6,7 +6,7 @@ import speedcopy
from ayon_core.client import get_project, get_asset_by_name
from ayon_core.lib import Terminal
from ayon_core.pipeline import legacy_io, Anatomy
from ayon_core.pipeline import Anatomy
t = Terminal()
@ -16,11 +16,6 @@ texture_extensions = ['.tif', '.tiff', '.jpg', '.jpeg', '.tx', '.png', '.tga',
class TextureCopy:
def __init__(self):
if not legacy_io.Session:
legacy_io.install()
def _get_textures(self, path):
textures = []
for dir, subdir, files in os.walk(path):

View file

@ -12,7 +12,7 @@ from qtpy import QtWidgets, QtCore
import qtawesome
import appdirs
from ayon_core.lib import JSONSettingRegistry
from ayon_core.lib import JSONSettingRegistry, is_running_from_build
from ayon_core.pipeline import install_host
from ayon_core.hosts.traypublisher.api import TrayPublisherHost
from ayon_core.tools.publisher.control_qt import QtPublisherController
@ -35,7 +35,7 @@ class TrayPublisherController(QtPublisherController):
class TrayPublisherRegistry(JSONSettingRegistry):
"""Class handling OpenPype general settings registry.
"""Class handling AYON general settings registry.
Attributes:
vendor (str): Name used for path construction.
@ -265,7 +265,7 @@ def main():
app_instance = get_ayon_qt_app()
if platform.system().lower() == "windows":
if not is_running_from_build() and platform.system().lower() == "windows":
import ctypes
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(
u"traypublisher"

View file

@ -111,7 +111,6 @@ class _AssetModel(QtGui.QStandardItemModel):
'refreshed' signal.
Args:
dbcon (AvalonMongoDB): Ready to use connection to mongo with.
parent (QObject): Parent Qt object.
"""
@ -128,9 +127,8 @@ class _AssetModel(QtGui.QStandardItemModel):
"data.color": 1
}
def __init__(self, dbcon, parent=None):
def __init__(self, parent=None):
super(_AssetModel, self).__init__(parent=parent)
self.dbcon = dbcon
self._refreshing = False
self._doc_fetching_thread = None
@ -142,6 +140,7 @@ class _AssetModel(QtGui.QStandardItemModel):
self._item_ids_with_color = set()
self._items_by_asset_id = {}
self._project_name = None
self._last_project_name = None
@property
@ -185,6 +184,16 @@ class _AssetModel(QtGui.QStandardItemModel):
return self.get_indexes_by_asset_ids(asset_ids)
def get_project_name(self):
return self._project_name
def set_project_name(self, project_name, refresh):
if self._project_name == project_name:
return
self._project_name = project_name
if refresh:
self.refresh()
def refresh(self, force=False):
"""Refresh the data for the model.
@ -197,7 +206,7 @@ class _AssetModel(QtGui.QStandardItemModel):
return
self.stop_refresh()
project_name = self.dbcon.Session.get("AVALON_PROJECT")
project_name = self._project_name
clear_model = False
if project_name != self._last_project_name:
clear_model = True
@ -216,23 +225,6 @@ class _AssetModel(QtGui.QStandardItemModel):
def stop_refresh(self):
self._stop_fetch_thread()
def clear_underlines(self):
for asset_id in set(self._item_ids_with_color):
self._item_ids_with_color.remove(asset_id)
item = self._items_by_asset_id.get(asset_id)
if item is not None:
item.setData(None, ASSET_UNDERLINE_COLORS_ROLE)
def set_underline_colors(self, colors_by_asset_id):
self.clear_underlines()
for asset_id, colors in colors_by_asset_id.items():
item = self._items_by_asset_id.get(asset_id)
if item is None:
continue
item.setData(colors, ASSET_UNDERLINE_COLORS_ROLE)
self._item_ids_with_color.add(asset_id)
def _clear_items(self):
root_item = self.invisibleRootItem()
root_item.removeRows(0, root_item.rowCount())
@ -357,7 +349,7 @@ class _AssetModel(QtGui.QStandardItemModel):
self._doc_fetched.emit()
def _fetch_asset_docs(self):
project_name = self.dbcon.current_project()
project_name = self.get_project_name()
if not project_name:
return []
@ -392,7 +384,6 @@ class _AssetsWidget(QtWidgets.QWidget):
inheritance changes.
Args:
dbcon (AvalonMongoDB): Connection to avalon mongo db.
parent (QWidget): Parent Qt widget.
"""
@ -404,11 +395,9 @@ class _AssetsWidget(QtWidgets.QWidget):
# It was double clicked on view
double_clicked = QtCore.Signal()
def __init__(self, dbcon, parent=None):
def __init__(self, parent=None):
super(_AssetsWidget, self).__init__(parent=parent)
self.dbcon = dbcon
# Tree View
model = self._create_source_model()
proxy = self._create_proxy_model(model)
@ -477,18 +466,28 @@ class _AssetsWidget(QtWidgets.QWidget):
self._model = model
self._proxy = proxy
self._view = view
self._last_project_name = None
self._last_btns_height = None
self._current_asset_name = None
self.model_selection = {}
@property
def header_widget(self):
return self._header_widget
def get_project_name(self):
self._model.get_project_name()
def set_project_name(self, project_name, refresh=True):
self._model.set_project_name(project_name, refresh)
def set_current_asset_name(self, asset_name):
self._current_asset_name = asset_name
def _create_source_model(self):
model = _AssetModel(dbcon=self.dbcon, parent=self)
model = _AssetModel(parent=self)
model.refreshed.connect(self._on_model_refresh)
return model
@ -509,8 +508,8 @@ class _AssetsWidget(QtWidgets.QWidget):
def stop_refresh(self):
self._model.stop_refresh()
def _get_current_session_asset(self):
return self.dbcon.Session.get("AVALON_ASSET")
def _get_current_asset_name(self):
return self._current_asset_name
def _on_current_asset_click(self):
"""Trigger change of asset to current context asset.
@ -518,10 +517,10 @@ class _AssetsWidget(QtWidgets.QWidget):
in differnt way.
"""
self.set_current_session_asset()
self.select_current_asset()
def set_current_session_asset(self):
asset_name = self._get_current_session_asset()
def select_current_asset(self):
asset_name = self._get_current_asset_name()
if asset_name:
self.select_asset_by_name(asset_name)

View file

@ -1,303 +0,0 @@
from qtpy import QtWidgets, QtCore, QtGui
import qtawesome
from ayon_core.client import (
get_project,
get_asset_by_id,
)
from ayon_core.style import get_disabled_entity_icon_color
from ayon_core.tools.utils.lib import get_task_icon
from .views import DeselectableTreeView
TASK_NAME_ROLE = QtCore.Qt.UserRole + 1
TASK_TYPE_ROLE = QtCore.Qt.UserRole + 2
TASK_ORDER_ROLE = QtCore.Qt.UserRole + 3
TASK_ASSIGNEE_ROLE = QtCore.Qt.UserRole + 4
class _TasksModel(QtGui.QStandardItemModel):
"""A model listing the tasks combined for a list of assets"""
def __init__(self, dbcon, parent=None):
super(_TasksModel, self).__init__(parent=parent)
self.dbcon = dbcon
self.setHeaderData(
0, QtCore.Qt.Horizontal, "Tasks", QtCore.Qt.DisplayRole
)
self._no_tasks_icon = qtawesome.icon(
"fa.exclamation-circle",
color=get_disabled_entity_icon_color()
)
self._cached_icons = {}
self._project_doc = {}
self._empty_tasks_item = None
self._last_asset_id = None
self._loaded_project_name = None
def _context_is_valid(self):
if self._get_current_project():
return True
return False
def refresh(self):
self._refresh_project_doc()
self.set_asset_id(self._last_asset_id)
def _refresh_project_doc(self):
# Get the project configured icons from database
project_doc = {}
if self._context_is_valid():
project_name = self.dbcon.active_project()
project_doc = get_project(project_name)
self._loaded_project_name = self._get_current_project()
self._project_doc = project_doc
def headerData(self, section, orientation, role=None):
if role is None:
role = QtCore.Qt.EditRole
# Show nice labels in the header
if section == 0:
if (
role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole)
and orientation == QtCore.Qt.Horizontal
):
return "Tasks"
return super(_TasksModel, self).headerData(section, orientation, role)
def _get_current_project(self):
return self.dbcon.Session.get("AVALON_PROJECT")
def set_asset_id(self, asset_id):
asset_doc = None
if asset_id and self._context_is_valid():
project_name = self._get_current_project()
asset_doc = get_asset_by_id(
project_name, asset_id, fields=["data.tasks"]
)
self._set_asset(asset_doc)
def _get_empty_task_item(self):
if self._empty_tasks_item is None:
item = QtGui.QStandardItem("No task")
item.setData(self._no_tasks_icon, QtCore.Qt.DecorationRole)
item.setFlags(QtCore.Qt.NoItemFlags)
self._empty_tasks_item = item
return self._empty_tasks_item
def _set_asset(self, asset_doc):
"""Set assets to track by their database id
Arguments:
asset_doc (dict): Asset document from MongoDB.
"""
if self._loaded_project_name != self._get_current_project():
self._refresh_project_doc()
asset_tasks = {}
self._last_asset_id = None
if asset_doc:
asset_tasks = asset_doc.get("data", {}).get("tasks") or {}
self._last_asset_id = asset_doc["_id"]
root_item = self.invisibleRootItem()
root_item.removeRows(0, root_item.rowCount())
items = []
for task_name, task_info in asset_tasks.items():
task_type = task_info.get("type")
task_order = task_info.get("order")
icon = get_task_icon(self._project_doc, asset_doc, task_name)
task_assignees = set()
assignees_data = task_info.get("assignees") or []
for assignee in assignees_data:
username = assignee.get("username")
if username:
task_assignees.add(username)
label = "{} ({})".format(task_name, task_type or "type N/A")
item = QtGui.QStandardItem(label)
item.setData(task_name, TASK_NAME_ROLE)
item.setData(task_type, TASK_TYPE_ROLE)
item.setData(task_order, TASK_ORDER_ROLE)
item.setData(task_assignees, TASK_ASSIGNEE_ROLE)
item.setData(icon, QtCore.Qt.DecorationRole)
item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable)
items.append(item)
if not items:
item = QtGui.QStandardItem("No task")
item.setData(self._no_tasks_icon, QtCore.Qt.DecorationRole)
item.setFlags(QtCore.Qt.NoItemFlags)
items.append(item)
root_item.appendRows(items)
class _TasksProxyModel(QtCore.QSortFilterProxyModel):
def lessThan(self, x_index, y_index):
x_order = x_index.data(TASK_ORDER_ROLE)
y_order = y_index.data(TASK_ORDER_ROLE)
if x_order is not None and y_order is not None:
if x_order < y_order:
return True
if x_order > y_order:
return False
elif x_order is None and y_order is not None:
return True
elif y_order is None and x_order is not None:
return False
x_name = x_index.data(QtCore.Qt.DisplayRole)
y_name = y_index.data(QtCore.Qt.DisplayRole)
if x_name == y_name:
return True
if x_name == tuple(sorted((x_name, y_name)))[0]:
return True
return False
class TasksWidget(QtWidgets.QWidget):
"""Widget showing active Tasks
Deprecated:
This widget will be removed soon. Please do not use it in new code.
"""
task_changed = QtCore.Signal()
def __init__(self, dbcon, parent=None):
self._dbcon = dbcon
super(TasksWidget, self).__init__(parent)
tasks_view = DeselectableTreeView(self)
tasks_view.setIndentation(0)
tasks_view.setSortingEnabled(True)
tasks_view.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
header_view = tasks_view.header()
header_view.setSortIndicator(0, QtCore.Qt.AscendingOrder)
tasks_model = self._create_source_model()
tasks_proxy = self._create_proxy_model(tasks_model)
tasks_view.setModel(tasks_proxy)
layout = QtWidgets.QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(tasks_view)
selection_model = tasks_view.selectionModel()
selection_model.selectionChanged.connect(self._on_task_change)
self._tasks_model = tasks_model
self._tasks_proxy = tasks_proxy
self._tasks_view = tasks_view
self._last_selected_task_name = None
def _create_source_model(self):
"""Create source model of tasks widget.
Model must have available 'refresh' method and 'set_asset_id' to change
context of asset.
"""
return _TasksModel(self._dbcon)
def _create_proxy_model(self, source_model):
proxy = _TasksProxyModel()
proxy.setSourceModel(source_model)
return proxy
def refresh(self):
self._tasks_model.refresh()
def set_asset_id(self, asset_id):
# Try and preserve the last selected task and reselect it
# after switching assets. If there's no currently selected
# asset keep whatever the "last selected" was prior to it.
current = self.get_selected_task_name()
if current:
self._last_selected_task_name = current
self._tasks_model.set_asset_id(asset_id)
if self._last_selected_task_name:
self.select_task_name(self._last_selected_task_name)
# Force a task changed emit.
self.task_changed.emit()
def _clear_selection(self):
selection_model = self._tasks_view.selectionModel()
selection_model.clearSelection()
def select_task_name(self, task_name):
"""Select a task by name.
If the task does not exist in the current model then selection is only
cleared.
Args:
task (str): Name of the task to select.
"""
task_view_model = self._tasks_view.model()
if not task_view_model:
return
# Clear selection
selection_model = self._tasks_view.selectionModel()
selection_model.clearSelection()
# Select the task
mode = (
QtCore.QItemSelectionModel.Select
| QtCore.QItemSelectionModel.Rows
)
for row in range(task_view_model.rowCount()):
index = task_view_model.index(row, 0)
name = index.data(TASK_NAME_ROLE)
if name == task_name:
selection_model.select(index, mode)
# Set the currently active index
self._tasks_view.setCurrentIndex(index)
break
last_selected_task_name = self.get_selected_task_name()
if last_selected_task_name:
self._last_selected_task_name = last_selected_task_name
def get_selected_task_name(self):
"""Return name of task at current index (selected)
Returns:
str: Name of the current task.
"""
index = self._tasks_view.currentIndex()
selection_model = self._tasks_view.selectionModel()
if index.isValid() and selection_model.isSelected(index):
return index.data(TASK_NAME_ROLE)
return None
def get_selected_task_type(self):
index = self._tasks_view.currentIndex()
selection_model = self._tasks_view.selectionModel()
if index.isValid() and selection_model.isSelected(index):
return index.data(TASK_TYPE_ROLE)
return None
def _on_task_change(self):
self.task_changed.emit()

View file

@ -1,8 +1,9 @@
import os
from qtpy import QtWidgets
from ayon_core import style
from ayon_core.lib import Logger
from ayon_core.pipeline import legacy_io
from ayon_core.tools.attribute_defs import AttributeDefinitionsWidget
@ -26,7 +27,7 @@ class WorkfileBuildPlaceholderDialog(QtWidgets.QDialog):
host_name = getattr(self._host, "name", None)
if not host_name:
host_name = legacy_io.Session.get("AVALON_APP") or "NA"
host_name = os.getenv("AVALON_APP") or "NA"
self._host_name = host_name
plugins_combo = QtWidgets.QComboBox(self)

View file

@ -176,6 +176,10 @@ class ExtractThumbnailOIIODefaultsModel(BaseSettingsModel):
class ExtractThumbnailModel(BaseSettingsModel):
_isGroup = True
enabled: bool = SettingsField(True)
product_names: list[str] = SettingsField(
default_factory=list,
title="Product names"
)
integrate_thumbnail: bool = SettingsField(
True,
title="Integrate Thumbnail Representation"
@ -844,6 +848,7 @@ DEFAULT_PUBLISH_VALUES = {
},
"ExtractThumbnail": {
"enabled": True,
"product_names": [],
"integrate_thumbnail": True,
"target_size": {
"type": "source"

View file

@ -306,36 +306,38 @@ class PublishPluginsModel(BaseSettingsModel):
default_factory=ValidateExpectedFilesModel,
title="Validate Expected Files"
)
MayaSubmitDeadline: MayaSubmitDeadlineModel = SettingsField(
default_factory=MayaSubmitDeadlineModel,
title="Maya Submit to deadline")
MaxSubmitDeadline: MaxSubmitDeadlineModel = SettingsField(
default_factory=MaxSubmitDeadlineModel,
title="Max Submit to deadline")
FusionSubmitDeadline: FusionSubmitDeadlineModel = SettingsField(
default_factory=FusionSubmitDeadlineModel,
title="Fusion submit to Deadline")
NukeSubmitDeadline: NukeSubmitDeadlineModel = SettingsField(
default_factory=NukeSubmitDeadlineModel,
title="Nuke Submit to deadline")
HarmonySubmitDeadline: HarmonySubmitDeadlineModel = SettingsField(
default_factory=HarmonySubmitDeadlineModel,
title="Harmony Submit to deadline")
AfterEffectsSubmitDeadline: AfterEffectsSubmitDeadlineModel = (
SettingsField(
default_factory=AfterEffectsSubmitDeadlineModel,
title="After Effects to deadline"
title="After Effects to deadline",
section="Hosts"
)
)
CelactionSubmitDeadline: CelactionSubmitDeadlineModel = SettingsField(
default_factory=CelactionSubmitDeadlineModel,
title="Celaction Submit Deadline")
BlenderSubmitDeadline: BlenderSubmitDeadlineModel = SettingsField(
default_factory=BlenderSubmitDeadlineModel,
title="Blender Submit Deadline")
CelactionSubmitDeadline: CelactionSubmitDeadlineModel = SettingsField(
default_factory=CelactionSubmitDeadlineModel,
title="Celaction Submit Deadline")
FusionSubmitDeadline: FusionSubmitDeadlineModel = SettingsField(
default_factory=FusionSubmitDeadlineModel,
title="Fusion submit to Deadline")
HarmonySubmitDeadline: HarmonySubmitDeadlineModel = SettingsField(
default_factory=HarmonySubmitDeadlineModel,
title="Harmony Submit to deadline")
MaxSubmitDeadline: MaxSubmitDeadlineModel = SettingsField(
default_factory=MaxSubmitDeadlineModel,
title="Max Submit to deadline")
MayaSubmitDeadline: MayaSubmitDeadlineModel = SettingsField(
default_factory=MayaSubmitDeadlineModel,
title="Maya Submit to deadline")
NukeSubmitDeadline: NukeSubmitDeadlineModel = SettingsField(
default_factory=NukeSubmitDeadlineModel,
title="Nuke Submit to deadline")
ProcessSubmittedCacheJobOnFarm: ProcessCacheJobFarmModel = SettingsField(
default_factory=ProcessCacheJobFarmModel,
title="Process submitted cache Job on farm.")
title="Process submitted cache Job on farm.",
section="Publish Jobs")
ProcessSubmittedJobOnFarm: ProcessSubmittedJobOnFarmModel = SettingsField(
default_factory=ProcessSubmittedJobOnFarmModel,
title="Process submitted job on farm.")
@ -357,6 +359,65 @@ DEFAULT_DEADLINE_PLUGINS_SETTINGS = {
"deadline"
]
},
"AfterEffectsSubmitDeadline": {
"enabled": True,
"optional": False,
"active": True,
"use_published": True,
"priority": 50,
"chunk_size": 10000,
"group": "",
"department": "",
"multiprocess": True
},
"BlenderSubmitDeadline": {
"enabled": True,
"optional": False,
"active": True,
"use_published": True,
"priority": 50,
"chunk_size": 10,
"group": "none",
"job_delay": "00:00:00:00"
},
"CelactionSubmitDeadline": {
"enabled": True,
"deadline_department": "",
"deadline_priority": 50,
"deadline_pool": "",
"deadline_pool_secondary": "",
"deadline_group": "",
"deadline_chunk_size": 10,
"deadline_job_delay": "00:00:00:00"
},
"FusionSubmitDeadline": {
"enabled": True,
"optional": False,
"active": True,
"priority": 50,
"chunk_size": 10,
"concurrent_tasks": 1,
"group": ""
},
"HarmonySubmitDeadline": {
"enabled": True,
"optional": False,
"active": True,
"use_published": True,
"priority": 50,
"chunk_size": 10000,
"group": "",
"department": ""
},
"MaxSubmitDeadline": {
"enabled": True,
"optional": False,
"active": True,
"use_published": True,
"priority": 50,
"chunk_size": 10,
"group": "none"
},
"MayaSubmitDeadline": {
"enabled": True,
"optional": False,
@ -376,24 +437,6 @@ DEFAULT_DEADLINE_PLUGINS_SETTINGS = {
"pluginInfo": "",
"scene_patches": []
},
"MaxSubmitDeadline": {
"enabled": True,
"optional": False,
"active": True,
"use_published": True,
"priority": 50,
"chunk_size": 10,
"group": "none"
},
"FusionSubmitDeadline": {
"enabled": True,
"optional": False,
"active": True,
"priority": 50,
"chunk_size": 10,
"concurrent_tasks": 1,
"group": ""
},
"NukeSubmitDeadline": {
"enabled": True,
"optional": False,
@ -410,47 +453,6 @@ DEFAULT_DEADLINE_PLUGINS_SETTINGS = {
"env_search_replace_values": [],
"limit_groups": []
},
"HarmonySubmitDeadline": {
"enabled": True,
"optional": False,
"active": True,
"use_published": True,
"priority": 50,
"chunk_size": 10000,
"group": "",
"department": ""
},
"AfterEffectsSubmitDeadline": {
"enabled": True,
"optional": False,
"active": True,
"use_published": True,
"priority": 50,
"chunk_size": 10000,
"group": "",
"department": "",
"multiprocess": True
},
"CelactionSubmitDeadline": {
"enabled": True,
"deadline_department": "",
"deadline_priority": 50,
"deadline_pool": "",
"deadline_pool_secondary": "",
"deadline_group": "",
"deadline_chunk_size": 10,
"deadline_job_delay": "00:00:00:00"
},
"BlenderSubmitDeadline": {
"enabled": True,
"optional": False,
"active": True,
"use_published": True,
"priority": 50,
"chunk_size": 10,
"group": "none",
"job_delay": "00:00:00:00"
},
"ProcessSubmittedCacheJobOnFarm": {
"enabled": True,
"deadline_department": "",

View file

@ -1 +1 @@
__version__ = "0.1.8"
__version__ = "0.1.9"