Merge pull request #8 from ynput/enhancement/remove-legacy-io

Don't use legacy io
This commit is contained in:
Jakub Trllo 2024-02-12 15:38:41 +01:00 committed by GitHub
commit 32251734b1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 386 additions and 682 deletions

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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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)