diff --git a/.github/pr-glob-labeler.yml b/.github/pr-glob-labeler.yml new file mode 100644 index 0000000000..286e7768b5 --- /dev/null +++ b/.github/pr-glob-labeler.yml @@ -0,0 +1,102 @@ +# Add type: unittest label if any changes in tests folders +'type: unittest': +- '*/*tests*/**/*' + +# any changes in documentation structure +'type: documentation': +- '*/**/*website*/**/*' +- '*/**/*docs*/**/*' + +# hosts triage +'host: Nuke': +- '*/**/*nuke*' +- '*/**/*nuke*/**/*' + +'host: Photoshop': +- '*/**/*photoshop*' +- '*/**/*photoshop*/**/*' + +'host: Harmony': +- '*/**/*harmony*' +- '*/**/*harmony*/**/*' + +'host: UE': +- '*/**/*unreal*' +- '*/**/*unreal*/**/*' + +'host: Houdini': +- '*/**/*houdini*' +- '*/**/*houdini*/**/*' + +'host: Maya': +- '*/**/*maya*' +- '*/**/*maya*/**/*' + +'host: Resolve': +- '*/**/*resolve*' +- '*/**/*resolve*/**/*' + +'host: Blender': +- '*/**/*blender*' +- '*/**/*blender*/**/*' + +'host: Hiero': +- '*/**/*hiero*' +- '*/**/*hiero*/**/*' + +'host: Fusion': +- '*/**/*fusion*' +- '*/**/*fusion*/**/*' + +'host: Flame': +- '*/**/*flame*' +- '*/**/*flame*/**/*' + +'host: TrayPublisher': +- '*/**/*traypublisher*' +- '*/**/*traypublisher*/**/*' + +'host: 3dsmax': +- '*/**/*max*' +- '*/**/*max*/**/*' + +'host: TV Paint': +- '*/**/*tvpaint*' +- '*/**/*tvpaint*/**/*' + +'host: CelAction': +- '*/**/*celaction*' +- '*/**/*celaction*/**/*' + +'host: After Effects': +- '*/**/*aftereffects*' +- '*/**/*aftereffects*/**/*' + +'host: Substance Painter': +- '*/**/*substancepainter*' +- '*/**/*substancepainter*/**/*' + +# modules triage +'module: Deadline': +- '*/**/*deadline*' +- '*/**/*deadline*/**/*' + +'module: RoyalRender': +- '*/**/*royalrender*' +- '*/**/*royalrender*/**/*' + +'module: Sitesync': +- '*/**/*sync_server*' +- '*/**/*sync_server*/**/*' + +'module: Ftrack': +- '*/**/*ftrack*' +- '*/**/*ftrack*/**/*' + +'module: Shotgrid': +- '*/**/*shotgrid*' +- '*/**/*shotgrid*/**/*' + +'module: Kitsu': +- '*/**/*kitsu*' +- '*/**/*kitsu*/**/*' diff --git a/client/ayon_core/addon/base.py b/client/ayon_core/addon/base.py index a3920c4acb..3b35476aed 100644 --- a/client/ayon_core/addon/base.py +++ b/client/ayon_core/addon/base.py @@ -788,6 +788,7 @@ class AddonsManager: addon_classes.append(modules_item) + aliased_names = [] for addon_cls in addon_classes: name = addon_cls.__name__ if issubclass(addon_cls, OpenPypeModule): @@ -807,6 +808,13 @@ class AddonsManager: self._addons.append(addon) self._addons_by_id[addon.id] = addon self._addons_by_name[addon.name] = addon + # NOTE This will be removed with release 1.0.0 of ayon-core + # please use carefully. + # Gives option to use alias name for addon for cases when + # name in OpenPype was not the same as in AYON. + name_alias = getattr(addon, "openpype_alias", None) + if name_alias: + aliased_names.append((name_alias, addon)) enabled_str = "X" if not addon.enabled: enabled_str = " " @@ -822,6 +830,17 @@ class AddonsManager: exc_info=True ) + for item in aliased_names: + name_alias, addon = item + if name_alias not in self._addons_by_name: + self._addons_by_name[name_alias] = addon + continue + self.log.warning( + "Alias name '{}' of addon '{}' is already assigned.".format( + name_alias, addon.name + ) + ) + if self._report is not None: report[self._report_total_key] = time.time() - time_start self._report["Initialization"] = report diff --git a/client/ayon_core/cli_commands.py b/client/ayon_core/cli_commands.py index c07b72afdf..7e652950eb 100644 --- a/client/ayon_core/cli_commands.py +++ b/client/ayon_core/cli_commands.py @@ -73,6 +73,20 @@ class Commands: import pyblish.api import pyblish.util + # Fix older jobs + for src_key, dst_key in ( + ("AVALON_PROJECT", "AYON_PROJECT_NAME"), + ("AVALON_ASSET", "AYON_FOLDER_PATH"), + ("AVALON_TASK", "AYON_TASK_NAME"), + ("AVALON_WORKDIR", "AYON_WORKDIR"), + ("AVALON_APP_NAME", "AYON_APP_NAME"), + ("AVALON_APP", "AYON_HOST_NAME"), + ): + if src_key in os.environ and dst_key not in os.environ: + os.environ[dst_key] = os.environ[src_key] + # Remove old keys, so we're sure they're not used + os.environ.pop(src_key, None) + log = Logger.get_logger("CLI-publish") install_ayon_plugins() @@ -87,7 +101,7 @@ class Commands: if not any(paths): raise RuntimeError("No publish paths specified") - app_full_name = os.getenv("AVALON_APP_NAME") + app_full_name = os.getenv("AYON_APP_NAME") if app_full_name: context = get_global_context() env = get_app_environments_for_context( diff --git a/client/ayon_core/hooks/pre_create_extra_workdir_folders.py b/client/ayon_core/hooks/pre_create_extra_workdir_folders.py index 6116d5fbd3..72c6bf2f68 100644 --- a/client/ayon_core/hooks/pre_create_extra_workdir_folders.py +++ b/client/ayon_core/hooks/pre_create_extra_workdir_folders.py @@ -21,7 +21,7 @@ class CreateWorkdirExtraFolders(PreLaunchHook): return env = self.data.get("env") or {} - workdir = env.get("AVALON_WORKDIR") + workdir = env.get("AYON_WORKDIR") if not workdir or not os.path.exists(workdir): return diff --git a/client/ayon_core/host/dirmap.py b/client/ayon_core/host/dirmap.py index cecd689a4c..9756657386 100644 --- a/client/ayon_core/host/dirmap.py +++ b/client/ayon_core/host/dirmap.py @@ -92,8 +92,8 @@ class HostDirmap(object): self.on_enable_dirmap() - for k, sp in enumerate(mapping["source-path"]): - dst = mapping["destination-path"][k] + for k, sp in enumerate(mapping["source_path"]): + dst = mapping["destination_path"][k] try: # add trailing slash if missing sp = os.path.join(sp, '') @@ -116,7 +116,7 @@ class HostDirmap(object): continue def get_mappings(self): - """Get translation from source-path to destination-path. + """Get translation from source_path to destination_path. It checks if Site Sync is enabled and user chose to use local site, in that case configuration in Local Settings takes precedence @@ -138,8 +138,8 @@ class HostDirmap(object): if ( not mapping - or not mapping.get("destination-path") - or not mapping.get("source-path") + or not mapping.get("destination_path") + or not mapping.get("source_path") ): return {} self.log.info("Processing directory mapping ...") @@ -154,7 +154,7 @@ class HostDirmap(object): in Local Settings. Returns: - dict : { "source-path": [XXX], "destination-path": [YYYY]} + dict : { "source_path": [XXX], "destination_path": [YYYY]} """ project_name = self.project_name @@ -210,13 +210,13 @@ class HostDirmap(object): continue if os.path.isdir(active_site_dir): - if "destination-path" not in mapping: - mapping["destination-path"] = [] - mapping["destination-path"].append(active_site_dir) + if "destination_path" not in mapping: + mapping["destination_path"] = [] + mapping["destination_path"].append(active_site_dir) - if "source-path" not in mapping: - mapping["source-path"] = [] - mapping["source-path"].append(remote_site_dir) + if "source_path" not in mapping: + mapping["source_path"] = [] + mapping["source_path"].append(remote_site_dir) self.log.debug("local sync mapping:: {}".format(mapping)) return mapping diff --git a/client/ayon_core/host/host.py b/client/ayon_core/host/host.py index 6d129e18d9..51dff9d558 100644 --- a/client/ayon_core/host/host.py +++ b/client/ayon_core/host/host.py @@ -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) @@ -107,7 +106,7 @@ class HostBase(object): Union[str, None]: Current project name. """ - return os.environ.get("AVALON_PROJECT") + return os.environ.get("AYON_PROJECT_NAME") def get_current_asset_name(self): """ @@ -115,7 +114,7 @@ class HostBase(object): Union[str, None]: Current asset name. """ - return os.environ.get("AVALON_ASSET") + return os.environ.get("AYON_FOLDER_PATH") def get_current_task_name(self): """ @@ -123,7 +122,7 @@ class HostBase(object): Union[str, None]: Current task name. """ - return os.environ.get("AVALON_TASK") + return os.environ.get("AYON_TASK_NAME") def get_current_context(self): """Get current context information. @@ -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'. diff --git a/client/ayon_core/host/interfaces.py b/client/ayon_core/host/interfaces.py index 7c6057acf0..7157ad6f7e 100644 --- a/client/ayon_core/host/interfaces.py +++ b/client/ayon_core/host/interfaces.py @@ -234,7 +234,7 @@ class IWorkfileHost: str: Path to new workdir. """ - return session["AVALON_WORKDIR"] + return session["AYON_WORKDIR"] # --- Deprecated method names --- def file_extensions(self): diff --git a/client/ayon_core/hosts/aftereffects/api/launch_logic.py b/client/ayon_core/hosts/aftereffects/api/launch_logic.py index ad521c2f01..0d1a6cf585 100644 --- a/client/ayon_core/hosts/aftereffects/api/launch_logic.py +++ b/client/ayon_core/hosts/aftereffects/api/launch_logic.py @@ -15,9 +15,8 @@ from wsrpc_aiohttp import ( 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.lib import Logger, is_in_tests +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,14 +297,11 @@ 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 + os.environ["AYON_PROJECT_NAME"] = project if asset: - legacy_io.Session["AVALON_ASSET"] = asset - os.environ["AVALON_ASSET"] = asset + os.environ["AYON_FOLDER_PATH"] = asset if task: - legacy_io.Session["AVALON_TASK"] = task - os.environ["AVALON_TASK"] = task + os.environ["AYON_TASK_NAME"] = task async def read(self): log.debug("aftereffects.read client calls server server calls " diff --git a/client/ayon_core/hosts/blender/api/pipeline.py b/client/ayon_core/hosts/blender/api/pipeline.py index 6801b1f71b..a49afeea6b 100644 --- a/client/ayon_core/hosts/blender/api/pipeline.py +++ b/client/ayon_core/hosts/blender/api/pipeline.py @@ -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, @@ -273,7 +272,7 @@ def set_resolution(data): def on_new(): - project = os.environ.get("AVALON_PROJECT") + project = os.environ.get("AYON_PROJECT_NAME") settings = get_project_settings(project).get("blender") set_resolution_startup = settings.get("set_resolution_startup") @@ -294,7 +293,7 @@ def on_new(): def on_open(): - project = os.environ.get("AVALON_PROJECT") + project = os.environ.get("AYON_PROJECT_NAME") settings = get_project_settings(project).get("blender") set_resolution_startup = settings.get("set_resolution_startup") @@ -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("AYON_WORKDIR") log.debug("New working directory: %s", workdir) diff --git a/client/ayon_core/hosts/blender/api/workio.py b/client/ayon_core/hosts/blender/api/workio.py index a8f6193abc..e0f333843a 100644 --- a/client/ayon_core/hosts/blender/api/workio.py +++ b/client/ayon_core/hosts/blender/api/workio.py @@ -82,7 +82,7 @@ def file_extensions() -> List[str]: def work_root(session: dict) -> str: """Return the default root to browse for work files.""" - work_dir = session["AVALON_WORKDIR"] + work_dir = session["AYON_WORKDIR"] scene_dir = session.get("AVALON_SCENEDIR") if scene_dir: return str(Path(work_dir, scene_dir)) diff --git a/client/ayon_core/hosts/blender/plugins/publish/extract_playblast.py b/client/ayon_core/hosts/blender/plugins/publish/extract_playblast.py index 83e6b26fbe..04fc2c5c39 100644 --- a/client/ayon_core/hosts/blender/plugins/publish/extract_playblast.py +++ b/client/ayon_core/hosts/blender/plugins/publish/extract_playblast.py @@ -1,9 +1,11 @@ import os +import json + import clique +import pyblish.api import bpy -import pyblish.api from ayon_core.pipeline import publish from ayon_core.hosts.blender.api import capture from ayon_core.hosts.blender.api.lib import maintained_time @@ -23,6 +25,8 @@ class ExtractPlayblast(publish.Extractor, publish.OptionalPyblishPluginMixin): optional = True order = pyblish.api.ExtractorOrder + 0.01 + presets = "{}" + def process(self, instance): if not self.is_active(instance.data): return @@ -59,8 +63,7 @@ class ExtractPlayblast(publish.Extractor, publish.OptionalPyblishPluginMixin): self.log.debug(f"Outputting images to {path}") - project_settings = instance.context.data["project_settings"]["blender"] - presets = project_settings["publish"]["ExtractPlayblast"]["presets"] + presets = json.loads(self.presets) preset = presets.get("default") preset.update({ "camera": camera, diff --git a/client/ayon_core/hosts/blender/plugins/publish/extract_thumbnail.py b/client/ayon_core/hosts/blender/plugins/publish/extract_thumbnail.py index 7b445a0113..ec701610ce 100644 --- a/client/ayon_core/hosts/blender/plugins/publish/extract_thumbnail.py +++ b/client/ayon_core/hosts/blender/plugins/publish/extract_thumbnail.py @@ -1,5 +1,6 @@ import os import glob +import json import pyblish.api from ayon_core.pipeline import publish @@ -21,7 +22,7 @@ class ExtractThumbnail(publish.Extractor): hosts = ["blender"] families = ["review"] order = pyblish.api.ExtractorOrder + 0.01 - presets = {} + presets = "{}" def process(self, instance): self.log.debug("Extracting capture..") @@ -44,7 +45,8 @@ class ExtractThumbnail(publish.Extractor): family = instance.data.get("family") isolate = instance.data("isolate", None) - preset = self.presets.get(family, {}) + presets = json.loads(self.presets) + preset = presets.get(family, {}) preset.update({ "camera": camera, diff --git a/client/ayon_core/hosts/flame/api/workio.py b/client/ayon_core/hosts/flame/api/workio.py index 0e3cb7f5fd..eef10a4847 100644 --- a/client/ayon_core/hosts/flame/api/workio.py +++ b/client/ayon_core/hosts/flame/api/workio.py @@ -34,4 +34,4 @@ def current_file(): def work_root(session): - return os.path.normpath(session["AVALON_WORKDIR"]).replace("\\", "/") + return os.path.normpath(session["AYON_WORKDIR"]).replace("\\", "/") diff --git a/client/ayon_core/hosts/flame/plugins/load/load_clip.py b/client/ayon_core/hosts/flame/plugins/load/load_clip.py index 6f35196932..47d0331255 100644 --- a/client/ayon_core/hosts/flame/plugins/load/load_clip.py +++ b/client/ayon_core/hosts/flame/plugins/load/load_clip.py @@ -70,7 +70,7 @@ class LoadClip(opfapi.ClipLoader): self.log.info("Loading with colorspace: `{}`".format(colorspace)) # create workfile path - workfile_dir = os.environ["AVALON_WORKDIR"] + workfile_dir = os.environ["AYON_WORKDIR"] openclip_dir = os.path.join( workfile_dir, clip_name ) diff --git a/client/ayon_core/hosts/flame/plugins/load/load_clip_batch.py b/client/ayon_core/hosts/flame/plugins/load/load_clip_batch.py index a66bf53622..cdf96bd459 100644 --- a/client/ayon_core/hosts/flame/plugins/load/load_clip_batch.py +++ b/client/ayon_core/hosts/flame/plugins/load/load_clip_batch.py @@ -50,17 +50,28 @@ class LoadClipBatch(opfapi.ClipLoader): version_name = version.get("name", None) colorspace = self.get_colorspace(context) + # TODO remove '{folder[name]}' and '{product[name]}' replacement + clip_name_template = ( + self.clip_name_template + .replace("{folder[name]}", "{asset}") + .replace("{product[name]}", "{subset}") + ) + layer_rename_template = ( + self.layer_rename_template + .replace("{folder[name]}", "{asset}") + .replace("{product[name]}", "{subset}") + ) # in case output is not in context replace key to representation if not context["representation"]["context"].get("output"): - self.clip_name_template = self.clip_name_template.replace( + clip_name_template = clip_name_template.replace( "output", "representation") - self.layer_rename_template = self.layer_rename_template.replace( + layer_rename_template = layer_rename_template.replace( "output", "representation") formatting_data = deepcopy(context["representation"]["context"]) formatting_data["batch"] = self.batch.name.get_value() - clip_name = StringTemplate(self.clip_name_template).format( + clip_name = StringTemplate(clip_name_template).format( formatting_data) # convert colorspace with ocio to flame mapping @@ -69,7 +80,7 @@ class LoadClipBatch(opfapi.ClipLoader): self.log.info("Loading with colorspace: `{}`".format(colorspace)) # create workfile path - workfile_dir = options.get("workdir") or os.environ["AVALON_WORKDIR"] + workfile_dir = options.get("workdir") or os.environ["AYON_WORKDIR"] openclip_dir = os.path.join( workfile_dir, clip_name ) @@ -86,7 +97,7 @@ class LoadClipBatch(opfapi.ClipLoader): "path": path.replace("\\", "/"), "colorspace": colorspace, "version": "v{:0>3}".format(version_name), - "layer_rename_template": self.layer_rename_template, + "layer_rename_template": layer_rename_template, "layer_rename_patterns": self.layer_rename_patterns, "context_data": formatting_data } diff --git a/client/ayon_core/hosts/flame/plugins/publish/extract_subset_resources.py b/client/ayon_core/hosts/flame/plugins/publish/extract_subset_resources.py index af699fd03a..9e55dbce96 100644 --- a/client/ayon_core/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/client/ayon_core/hosts/flame/plugins/publish/extract_subset_resources.py @@ -1,6 +1,5 @@ import os import re -import tempfile from copy import deepcopy import pyblish.api @@ -15,12 +14,12 @@ from ayon_core.pipeline.editorial import ( import flame -class ExtractSubsetResources(publish.Extractor): +class ExtractProductResources(publish.Extractor): """ Extractor for transcoding files from Flame clip """ - label = "Extract subset resources" + label = "Extract product resources" order = pyblish.api.ExtractorOrder families = ["clip"] hosts = ["flame"] @@ -47,7 +46,7 @@ class ExtractSubsetResources(publish.Extractor): hide_ui_on_process = True # settings - export_presets_mapping = {} + export_presets_mapping = [] def process(self, instance): if not self.keep_original_representation: @@ -146,15 +145,21 @@ class ExtractSubsetResources(publish.Extractor): # append staging dir for later cleanup instance.context.data["cleanupFullPaths"].append(staging_dir) + export_presets_mapping = {} + for preset_mapping in deepcopy(self.export_presets_mapping): + name = preset_mapping.pop("name") + export_presets_mapping[name] = preset_mapping + # add default preset type for thumbnail and reviewable video # update them with settings and override in case the same # are found in there - _preset_keys = [k.split('_')[0] for k in self.export_presets_mapping] + _preset_keys = [k.split('_')[0] for k in export_presets_mapping] export_presets = { - k: v for k, v in deepcopy(self.default_presets).items() + k: v + for k, v in deepcopy(self.default_presets).items() if k not in _preset_keys } - export_presets.update(self.export_presets_mapping) + export_presets.update(export_presets_mapping) if not instance.data.get("versionData"): instance.data["versionData"] = {} diff --git a/client/ayon_core/hosts/fusion/addon.py b/client/ayon_core/hosts/fusion/addon.py index 391ee770c4..54e48ea7bf 100644 --- a/client/ayon_core/hosts/fusion/addon.py +++ b/client/ayon_core/hosts/fusion/addon.py @@ -22,7 +22,7 @@ def get_fusion_version(app_name): The function is triggered by the prelaunch hooks to get the fusion version. `app_name` is obtained by prelaunch hooks from the - `launch_context.env.get("AVALON_APP_NAME")`. + `launch_context.env.get("AYON_APP_NAME")`. To get a correct Fusion version, a version number should be present in the `applications/fusion/variants` key diff --git a/client/ayon_core/hosts/fusion/api/pipeline.py b/client/ayon_core/hosts/fusion/api/pipeline.py index 7c480704a5..0e9e0724c7 100644 --- a/client/ayon_core/hosts/fusion/api/pipeline.py +++ b/client/ayon_core/hosts/fusion/api/pipeline.py @@ -135,7 +135,7 @@ class FusionHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): return current_filepath def work_root(self, session): - work_dir = session["AVALON_WORKDIR"] + work_dir = session["AYON_WORKDIR"] scene_dir = session.get("AVALON_SCENEDIR") if scene_dir: return os.path.join(work_dir, scene_dir) diff --git a/client/ayon_core/hosts/fusion/api/plugin.py b/client/ayon_core/hosts/fusion/api/plugin.py index 12a29d2986..70392c2f5b 100644 --- a/client/ayon_core/hosts/fusion/api/plugin.py +++ b/client/ayon_core/hosts/fusion/api/plugin.py @@ -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("AYON_WORKDIR")) formatting_data.update({ "workdir": workdir, "frame": "0" * frame_padding, @@ -148,7 +147,16 @@ class GenericCreateSaver(Creator): }) # build file path to render - filepath = self.temp_rendering_path_template.format(**formatting_data) + # TODO make sure the keys are available in 'formatting_data' + temp_rendering_path_template = ( + self.temp_rendering_path_template + .replace("{product[name]}", "{subset}") + .replace("{product[type]}", "{family}") + .replace("{folder[name]}", "{asset}") + .replace("{task[name]}", "{task}") + ) + + filepath = temp_rendering_path_template.format(**formatting_data) comp = get_current_comp() tool["Clip"] = comp.ReverseMapPath(os.path.normpath(filepath)) diff --git a/client/ayon_core/hosts/fusion/hooks/pre_fusion_profile_hook.py b/client/ayon_core/hosts/fusion/hooks/pre_fusion_profile_hook.py index f63aaa1eb4..5aa2783129 100644 --- a/client/ayon_core/hosts/fusion/hooks/pre_fusion_profile_hook.py +++ b/client/ayon_core/hosts/fusion/hooks/pre_fusion_profile_hook.py @@ -131,7 +131,7 @@ class FusionCopyPrefsPrelaunch(PreLaunchHook): ) = self.get_copy_fusion_prefs_settings() # Get launched application context and return correct app version - app_name = self.launch_context.env.get("AVALON_APP_NAME") + app_name = self.launch_context.env.get("AYON_APP_NAME") app_version = get_fusion_version(app_name) if app_version is None: version_names = ", ".join(str(x) for x in FUSION_VERSIONS_DICT) diff --git a/client/ayon_core/hosts/fusion/hooks/pre_fusion_setup.py b/client/ayon_core/hosts/fusion/hooks/pre_fusion_setup.py index 7cfa9d0a26..7eaf2ddc02 100644 --- a/client/ayon_core/hosts/fusion/hooks/pre_fusion_setup.py +++ b/client/ayon_core/hosts/fusion/hooks/pre_fusion_setup.py @@ -28,7 +28,7 @@ class FusionPrelaunch(PreLaunchHook): def execute(self): # making sure python 3 is installed at provided path # Py 3.3-3.10 for Fusion 18+ or Py 3.6 for Fu 16-17 - app_data = self.launch_context.env.get("AVALON_APP_NAME") + app_data = self.launch_context.env.get("AYON_APP_NAME") app_version = get_fusion_version(app_data) if not app_version: raise ApplicationLaunchFailed( diff --git a/client/ayon_core/hosts/harmony/api/README.md b/client/ayon_core/hosts/harmony/api/README.md index cdc17b2285..680a88c423 100644 --- a/client/ayon_core/hosts/harmony/api/README.md +++ b/client/ayon_core/hosts/harmony/api/README.md @@ -52,7 +52,7 @@ Because Harmony projects are directories, this integration uses `.zip` as work f ### Show Workfiles on launch -You can show the Workfiles app when Harmony launches by setting environment variable `AVALON_HARMONY_WORKFILES_ON_LAUNCH=1`. +You can show the Workfiles app when Harmony launches by setting environment variable `AYON_HARMONY_WORKFILES_ON_LAUNCH=1`. ## Developing diff --git a/client/ayon_core/hosts/harmony/api/TB_sceneOpened.js b/client/ayon_core/hosts/harmony/api/TB_sceneOpened.js index 1fb0d295e7..cdf60c1aa8 100644 --- a/client/ayon_core/hosts/harmony/api/TB_sceneOpened.js +++ b/client/ayon_core/hosts/harmony/api/TB_sceneOpened.js @@ -349,7 +349,7 @@ function start() { /** hostname or ip of server - should be localhost */ var host = '127.0.0.1'; /** port of the server */ - var port = parseInt(System.getenv('AVALON_HARMONY_PORT')); + var port = parseInt(System.getenv('AYON_HARMONY_PORT')); // Attach the client to the QApplication to preserve. var app = QCoreApplication.instance(); diff --git a/client/ayon_core/hosts/harmony/api/lib.py b/client/ayon_core/hosts/harmony/api/lib.py index 782134c343..bc73e19066 100644 --- a/client/ayon_core/hosts/harmony/api/lib.py +++ b/client/ayon_core/hosts/harmony/api/lib.py @@ -189,14 +189,14 @@ def launch(application_path, *args): install_host(harmony) ProcessContext.port = random.randrange(49152, 65535) - os.environ["AVALON_HARMONY_PORT"] = str(ProcessContext.port) + os.environ["AYON_HARMONY_PORT"] = str(ProcessContext.port) ProcessContext.application_path = application_path # Launch Harmony. setup_startup_scripts() check_libs() - if not os.environ.get("AVALON_HARMONY_WORKFILES_ON_LAUNCH", False): + if not os.environ.get("AYON_HARMONY_WORKFILES_ON_LAUNCH", False): open_empty_workfile() return diff --git a/client/ayon_core/hosts/harmony/api/workio.py b/client/ayon_core/hosts/harmony/api/workio.py index 8df5ede917..1f95148e75 100644 --- a/client/ayon_core/hosts/harmony/api/workio.py +++ b/client/ayon_core/hosts/harmony/api/workio.py @@ -74,4 +74,4 @@ def current_file(): def work_root(session): - return os.path.normpath(session["AVALON_WORKDIR"]).replace("\\", "/") + return os.path.normpath(session["AYON_WORKDIR"]).replace("\\", "/") diff --git a/client/ayon_core/hosts/harmony/plugins/publish/validate_scene_settings.py b/client/ayon_core/hosts/harmony/plugins/publish/validate_scene_settings.py index 0cf96e70b0..6d46fbcd33 100644 --- a/client/ayon_core/hosts/harmony/plugins/publish/validate_scene_settings.py +++ b/client/ayon_core/hosts/harmony/plugins/publish/validate_scene_settings.py @@ -77,7 +77,7 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin): expected_settings.pop("resolutionWidth") expected_settings.pop("resolutionHeight") - if (any(re.search(pattern, os.getenv('AVALON_TASK')) + if (any(re.search(pattern, os.getenv('AYON_TASK_NAME')) for pattern in self.skip_timelines_check)): self.log.info("Skipping frames check because of " "task name and pattern {}".format( diff --git a/client/ayon_core/hosts/hiero/api/workio.py b/client/ayon_core/hosts/hiero/api/workio.py index 14d9439344..4c2416ca38 100644 --- a/client/ayon_core/hosts/hiero/api/workio.py +++ b/client/ayon_core/hosts/hiero/api/workio.py @@ -70,4 +70,4 @@ def current_file(): def work_root(session): - return os.path.normpath(session["AVALON_WORKDIR"]).replace("\\", "/") + return os.path.normpath(session["AYON_WORKDIR"]).replace("\\", "/") diff --git a/client/ayon_core/hosts/houdini/api/usd.py b/client/ayon_core/hosts/houdini/api/usd.py index e900bc5fac..e9c02a0307 100644 --- a/client/ayon_core/hosts/houdini/api/usd.py +++ b/client/ayon_core/hosts/houdini/api/usd.py @@ -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) diff --git a/client/ayon_core/hosts/houdini/hooks/set_paths.py b/client/ayon_core/hosts/houdini/hooks/set_paths.py index 1f24a8dd7d..7eb346cc74 100644 --- a/client/ayon_core/hosts/houdini/hooks/set_paths.py +++ b/client/ayon_core/hosts/houdini/hooks/set_paths.py @@ -10,7 +10,7 @@ class SetPath(PreLaunchHook): launch_types = {LaunchTypes.local} def execute(self): - workdir = self.launch_context.env.get("AVALON_WORKDIR", "") + workdir = self.launch_context.env.get("AYON_WORKDIR", "") if not workdir: self.log.warning("BUG: Workdir is not filled.") return diff --git a/client/ayon_core/hosts/max/api/pipeline.py b/client/ayon_core/hosts/max/api/pipeline.py index ff0267b39a..c26e697429 100644 --- a/client/ayon_core/hosts/max/api/pipeline.py +++ b/client/ayon_core/hosts/max/api/pipeline.py @@ -63,9 +63,8 @@ class MaxHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): rt.callbacks.addScript(rt.Name('postWorkspaceChange'), self._deferred_menu_creation) - def has_unsaved_changes(self): - # TODO: how to get it from 3dsmax? - return True + def workfile_has_unsaved_changes(self): + return rt.getSaveRequired() def get_workfile_extensions(self): return [".max"] diff --git a/client/ayon_core/hosts/max/hooks/set_paths.py b/client/ayon_core/hosts/max/hooks/set_paths.py index c18fd29295..0ee1b0dab7 100644 --- a/client/ayon_core/hosts/max/hooks/set_paths.py +++ b/client/ayon_core/hosts/max/hooks/set_paths.py @@ -10,7 +10,7 @@ class SetPath(PreLaunchHook): launch_types = {LaunchTypes.local} def execute(self): - workdir = self.launch_context.env.get("AVALON_WORKDIR", "") + workdir = self.launch_context.env.get("AYON_WORKDIR", "") if not workdir: self.log.warning("BUG: Workdir is not filled.") return diff --git a/client/ayon_core/hosts/max/plugins/publish/extract_pointcloud.py b/client/ayon_core/hosts/max/plugins/publish/extract_pointcloud.py index 294d63794e..67dde7f0a6 100644 --- a/client/ayon_core/hosts/max/plugins/publish/extract_pointcloud.py +++ b/client/ayon_core/hosts/max/plugins/publish/extract_pointcloud.py @@ -155,7 +155,9 @@ class ExtractPointCloud(publish.Extractor): custom_attr_list = [] attr_settings = self.settings["attribute"] - for key, value in attr_settings.items(): + for attr in attr_settings: + key = attr["name"] + value = attr["value"] custom_attr = "{0}.PRTChannels_{1}=True".format(operator, value) self.log.debug( diff --git a/client/ayon_core/hosts/max/plugins/publish/save_scene.py b/client/ayon_core/hosts/max/plugins/publish/save_scene.py index a40788ab41..1c59335ceb 100644 --- a/client/ayon_core/hosts/max/plugins/publish/save_scene.py +++ b/client/ayon_core/hosts/max/plugins/publish/save_scene.py @@ -1,11 +1,9 @@ import pyblish.api -import os +from ayon_core.pipeline import registered_host class SaveCurrentScene(pyblish.api.ContextPlugin): - """Save current scene - - """ + """Save current scene""" label = "Save current file" order = pyblish.api.ExtractorOrder - 0.49 @@ -13,9 +11,13 @@ class SaveCurrentScene(pyblish.api.ContextPlugin): families = ["maxrender", "workfile"] def process(self, context): - from pymxs import runtime as rt - folder = rt.maxFilePath - file = rt.maxFileName - current = os.path.join(folder, file) - assert context.data["currentFile"] == current - rt.saveMaxFile(current) + host = registered_host() + current_file = host.get_current_workfile() + + assert context.data["currentFile"] == current_file + + if host.workfile_has_unsaved_changes(): + self.log.info(f"Saving current file: {current_file}") + host.save_workfile(current_file) + else: + self.log.debug("No unsaved changes, skipping file save..") \ No newline at end of file diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_attributes.py b/client/ayon_core/hosts/max/plugins/publish/validate_attributes.py index 444a8f0829..354539871f 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_attributes.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_attributes.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- """Validator for Attributes.""" +import json + from pyblish.api import ContextPlugin, ValidatorOrder from pymxs import runtime as rt @@ -61,9 +63,13 @@ class ValidateAttributes(OptionalPyblishPluginMixin, @classmethod def get_invalid(cls, context): - attributes = ( - context.data["project_settings"]["max"]["publish"] - ["ValidateAttributes"]["attributes"] + attributes = json.loads( + context.data + ["project_settings"] + ["max"] + ["publish"] + ["ValidateAttributes"] + ["attributes"] ) if not attributes: return @@ -112,9 +118,13 @@ class ValidateAttributes(OptionalPyblishPluginMixin, @classmethod def repair(cls, context): - attributes = ( - context.data["project_settings"]["max"]["publish"] - ["ValidateAttributes"]["attributes"] + attributes = json.loads( + context.data + ["project_settings"] + ["max"] + ["publish"] + ["ValidateAttributes"] + ["attributes"] ) invalid_attributes = cls.get_invalid(context) for attrs in invalid_attributes: diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_loaded_plugin.py b/client/ayon_core/hosts/max/plugins/publish/validate_loaded_plugin.py index fe6e32b27b..bf5ac26fef 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_loaded_plugin.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_loaded_plugin.py @@ -25,7 +25,7 @@ class ValidateLoadedPlugin(OptionalPyblishPluginMixin, optional = True actions = [RepairAction] - family_plugins_mapping = {} + family_plugins_mapping = [] @classmethod def get_invalid(cls, instance): @@ -34,6 +34,12 @@ class ValidateLoadedPlugin(OptionalPyblishPluginMixin, if not family_plugins_mapping: return + # Backward compatibility - settings did have 'product_types' + if "product_types" in family_plugins_mapping: + family_plugins_mapping["families"] = family_plugins_mapping.pop( + "product_types" + ) + invalid = [] # Find all plug-in requirements for current instance instance_families = {instance.data["family"]} @@ -47,7 +53,9 @@ class ValidateLoadedPlugin(OptionalPyblishPluginMixin, if not mapping: return - match_families = {fam.strip() for fam in mapping["families"]} + match_families = { + fam.strip() for fam in mapping["families"] + } has_match = "*" in match_families or match_families.intersection( instance_families) diff --git a/client/ayon_core/hosts/max/plugins/publish/validate_pointcloud.py b/client/ayon_core/hosts/max/plugins/publish/validate_pointcloud.py index a025ed3992..73b18984ed 100644 --- a/client/ayon_core/hosts/max/plugins/publish/validate_pointcloud.py +++ b/client/ayon_core/hosts/max/plugins/publish/validate_pointcloud.py @@ -59,7 +59,9 @@ class ValidatePointCloud(pyblish.api.InstancePlugin): event_name = sub_anim.name opt = "${0}.{1}.export_particles".format(sel.name, event_name) - for key, value in attr_settings.items(): + for attr in attr_settings: + key = attr["name"] + value = attr["value"] custom_attr = "{0}.PRTChannels_{1}".format(opt, value) try: diff --git a/client/ayon_core/hosts/max/startup/startup.ms b/client/ayon_core/hosts/max/startup/startup.ms index b80ead4b74..4c597901f3 100644 --- a/client/ayon_core/hosts/max/startup/startup.ms +++ b/client/ayon_core/hosts/max/startup/startup.ms @@ -8,5 +8,8 @@ local pythonpath = systemTools.getEnvVariable "MAX_PYTHONPATH" systemTools.setEnvVariable "PYTHONPATH" pythonpath + /*opens the create menu on startup to ensure users are presented with a useful default view.*/ + max create mode + python.ExecuteFile startup ) \ No newline at end of file diff --git a/client/ayon_core/hosts/maya/api/lib.py b/client/ayon_core/hosts/maya/api/lib.py index 7b791c3d51..3a29fe433b 100644 --- a/client/ayon_core/hosts/maya/api/lib.py +++ b/client/ayon_core/hosts/maya/api/lib.py @@ -329,7 +329,7 @@ def generate_capture_preset(instance, camera, path, # Update preset with current panel setting # if override_viewport_options is turned off - if not capture_preset["Viewport Options"]["override_viewport_options"]: + if not capture_preset["ViewportOptions"]["override_viewport_options"]: panel_preset = capture.parse_view(preset["panel"]) panel_preset.pop("camera") preset.update(panel_preset) @@ -2937,14 +2937,15 @@ def load_capture_preset(data): options.update(data["Generic"]) options.update(data["Resolution"]) - camera_options.update(data['Camera Options']) + camera_options.update(data["CameraOptions"]) viewport_options.update(data["Renderer"]) # DISPLAY OPTIONS disp_options = {} - for key, value in data['Display Options'].items(): - if key.startswith('background'): + for key, value in data["DisplayOptions"].items(): + if key.startswith("background"): # Convert background, backgroundTop, backgroundBottom colors + if len(value) == 4: # Ignore alpha + convert RGB to float value = [ @@ -2956,7 +2957,7 @@ def load_capture_preset(data): elif key == "displayGradient": disp_options[key] = value - options['display_options'] = disp_options + options["display_options"] = disp_options # Viewport Options has a mixture of Viewport2 Options and Viewport Options # to pass along to capture. So we'll need to differentiate between the two @@ -2981,7 +2982,7 @@ def load_capture_preset(data): "motionBlurShutterOpenFraction", "lineAAEnable" } - for key, value in data['Viewport Options'].items(): + for key, value in data["ViewportOptions"].items(): # There are some keys we want to ignore if key in {"override_viewport_options", "high_quality"}: @@ -3140,119 +3141,6 @@ def fix_incompatible_containers(): "ReferenceLoader", type="string") -def _null(*args): - pass - - -class shelf(): - '''A simple class to build shelves in maya. Since the build method is empty, - it should be extended by the derived class to build the necessary shelf - elements. By default it creates an empty shelf called "customShelf".''' - - ########################################################################### - '''This is an example shelf.''' - # class customShelf(_shelf): - # def build(self): - # self.addButon(label="button1") - # self.addButon("button2") - # self.addButon("popup") - # p = cmds.popupMenu(b=1) - # self.addMenuItem(p, "popupMenuItem1") - # self.addMenuItem(p, "popupMenuItem2") - # sub = self.addSubMenu(p, "subMenuLevel1") - # self.addMenuItem(sub, "subMenuLevel1Item1") - # sub2 = self.addSubMenu(sub, "subMenuLevel2") - # self.addMenuItem(sub2, "subMenuLevel2Item1") - # self.addMenuItem(sub2, "subMenuLevel2Item2") - # self.addMenuItem(sub, "subMenuLevel1Item2") - # self.addMenuItem(p, "popupMenuItem3") - # self.addButon("button3") - # customShelf() - ########################################################################### - - def __init__(self, name="customShelf", iconPath="", preset={}): - self.name = name - - self.iconPath = iconPath - - self.labelBackground = (0, 0, 0, 0) - self.labelColour = (.9, .9, .9) - - self.preset = preset - - self._cleanOldShelf() - cmds.setParent(self.name) - self.build() - - def build(self): - '''This method should be overwritten in derived classes to actually - build the shelf elements. Otherwise, nothing is added to the shelf.''' - for item in self.preset['items']: - if not item.get('command'): - item['command'] = self._null - if item['type'] == 'button': - self.addButon(item['name'], - command=item['command'], - icon=item['icon']) - if item['type'] == 'menuItem': - self.addMenuItem(item['parent'], - item['name'], - command=item['command'], - icon=item['icon']) - if item['type'] == 'subMenu': - self.addMenuItem(item['parent'], - item['name'], - command=item['command'], - icon=item['icon']) - - def addButon(self, label, icon="commandButton.png", - command=_null, doubleCommand=_null): - ''' - Adds a shelf button with the specified label, command, - double click command and image. - ''' - cmds.setParent(self.name) - if icon: - icon = os.path.join(self.iconPath, icon) - print(icon) - cmds.shelfButton(width=37, height=37, image=icon, label=label, - command=command, dcc=doubleCommand, - imageOverlayLabel=label, olb=self.labelBackground, - olc=self.labelColour) - - def addMenuItem(self, parent, label, command=_null, icon=""): - ''' - Adds a shelf button with the specified label, command, - double click command and image. - ''' - if icon: - icon = os.path.join(self.iconPath, icon) - print(icon) - return cmds.menuItem(p=parent, label=label, c=command, i="") - - def addSubMenu(self, parent, label, icon=None): - ''' - Adds a sub menu item with the specified label and icon to - the specified parent popup menu. - ''' - if icon: - icon = os.path.join(self.iconPath, icon) - print(icon) - return cmds.menuItem(p=parent, label=label, i=icon, subMenu=1) - - def _cleanOldShelf(self): - ''' - Checks if the shelf exists and empties it if it does - or creates it if it does not. - ''' - if cmds.shelfLayout(self.name, ex=1): - if cmds.shelfLayout(self.name, q=1, ca=1): - for each in cmds.shelfLayout(self.name, q=1, ca=1): - cmds.deleteUI(each) - else: - cmds.shelfLayout(self.name, p="ShelfLayout") - - def update_content_on_context_change(): """ This will update scene content to match new asset on context change @@ -4059,10 +3947,10 @@ def get_capture_preset(task_name, task_type, subset, project_settings, log): Args: task_name (str): Task name. - take_type (str): Task type. + task_type (str): Task type. subset (str): Subset name. project_settings (dict): Project settings. - log (object): Logging object. + log (logging.Logger): Logging object. """ capture_preset = None filtering_criteria = { @@ -4091,8 +3979,18 @@ def get_capture_preset(task_name, task_type, subset, project_settings, log): "Falling back to deprecated Extract Playblast capture preset " "because no new style playblast profiles are defined." ) - capture_preset = plugin_settings["capture_preset"] + capture_preset = plugin_settings.get("capture_preset") + if capture_preset: + # Create deepcopy of preset as we'll change the values + capture_preset = copy.deepcopy(capture_preset) + + viewport_options = capture_preset["ViewportOptions"] + # Change 'list' to 'dict' for 'capture.py' + viewport_options["pluginObjects"] = { + item["name"]: item["value"] + for item in viewport_options["pluginObjects"] + } return capture_preset or {} diff --git a/client/ayon_core/hosts/maya/api/lib_rendersettings.py b/client/ayon_core/hosts/maya/api/lib_rendersettings.py index 54ee7888b3..b8a4d04a10 100644 --- a/client/ayon_core/hosts/maya/api/lib_rendersettings.py +++ b/client/ayon_core/hosts/maya/api/lib_rendersettings.py @@ -46,7 +46,7 @@ class RenderSettings(object): project_settings = get_project_settings( get_current_project_name() ) - render_settings = project_settings["maya"]["RenderSettings"] + render_settings = project_settings["maya"]["render_settings"] image_prefixes = { "vray": render_settings["vray_renderer"]["image_prefix"], "arnold": render_settings["arnold_renderer"]["image_prefix"], @@ -82,12 +82,12 @@ class RenderSettings(object): try: aov_separator = self._aov_chars[( self._project_settings["maya"] - ["RenderSettings"] + ["render_settings"] ["aov_separator"] )] except KeyError: aov_separator = "_" - reset_frame = self._project_settings["maya"]["RenderSettings"]["reset_current_frame"] # noqa + reset_frame = self._project_settings["maya"]["render_settings"]["reset_current_frame"] # noqa if reset_frame: start_frame = cmds.getAttr("defaultRenderGlobals.startFrame") @@ -131,7 +131,7 @@ class RenderSettings(object): import maya.mel as mel # noqa: F401 createOptions() - render_settings = self._project_settings["maya"]["RenderSettings"] + render_settings = self._project_settings["maya"]["render_settings"] arnold_render_presets = render_settings["arnold_renderer"] # noqa # Force resetting settings and AOV list to avoid having to deal with # AOV checking logic, for now. @@ -180,7 +180,7 @@ class RenderSettings(object): from maya import cmds # noqa: F401 import maya.mel as mel # noqa: F401 - render_settings = self._project_settings["maya"]["RenderSettings"] + render_settings = self._project_settings["maya"]["render_settings"] redshift_render_presets = render_settings["redshift_renderer"] remove_aovs = render_settings["remove_aovs"] @@ -239,7 +239,7 @@ class RenderSettings(object): rman_render_presets = ( self._project_settings ["maya"] - ["RenderSettings"] + ["render_settings"] ["renderman_renderer"] ) display_filters = rman_render_presets["display_filters"] @@ -304,7 +304,7 @@ class RenderSettings(object): settings = cmds.ls(type="VRaySettingsNode") node = settings[0] if settings else cmds.createNode("VRaySettingsNode") - render_settings = self._project_settings["maya"]["RenderSettings"] + render_settings = self._project_settings["maya"]["render_settings"] vray_render_presets = render_settings["vray_renderer"] # vrayRenderElement remove_aovs = render_settings["remove_aovs"] @@ -390,7 +390,8 @@ class RenderSettings(object): import maya.mel as mel # noqa: F401 for item in additional_attribs: - attribute, value = item + attribute = item["attribute"] + value = item["value"] attribute = str(attribute) # ensure str conversion from settings attribute_type = cmds.getAttr(attribute, type=True) if attribute_type in {"long", "bool"}: diff --git a/client/ayon_core/hosts/maya/api/menu.py b/client/ayon_core/hosts/maya/api/menu.py index 7478739496..70347e91b6 100644 --- a/client/ayon_core/hosts/maya/api/menu.py +++ b/client/ayon_core/hosts/maya/api/menu.py @@ -9,7 +9,8 @@ import maya.cmds as cmds from ayon_core.pipeline import ( get_current_asset_name, - get_current_task_name + get_current_task_name, + registered_host ) from ayon_core.pipeline.workfile import BuildWorkfile from ayon_core.tools.utils import host_tools @@ -21,8 +22,10 @@ from .workfile_template_builder import ( create_placeholder, update_placeholder, build_workfile_template, - update_workfile_template, + update_workfile_template ) +from ayon_core.tools.workfile_template_build import open_template_ui +from .workfile_template_builder import MayaTemplateBuilder log = logging.getLogger(__name__) @@ -167,16 +170,6 @@ def install(project_settings): tearOff=True, parent=MENU_NAME ) - cmds.menuItem( - "Create Placeholder", - parent=builder_menu, - command=create_placeholder - ) - cmds.menuItem( - "Update Placeholder", - parent=builder_menu, - command=update_placeholder - ) cmds.menuItem( "Build Workfile from template", parent=builder_menu, @@ -187,6 +180,27 @@ def install(project_settings): parent=builder_menu, command=update_workfile_template ) + cmds.menuItem( + divider=True, + parent=builder_menu + ) + cmds.menuItem( + "Open Template", + parent=builder_menu, + command=lambda *args: open_template_ui( + MayaTemplateBuilder(registered_host()), get_main_window() + ), + ) + cmds.menuItem( + "Create Placeholder", + parent=builder_menu, + command=create_placeholder + ) + cmds.menuItem( + "Update Placeholder", + parent=builder_menu, + command=update_placeholder + ) cmds.setParent(MENU_NAME, menu=True) diff --git a/client/ayon_core/hosts/maya/api/pipeline.py b/client/ayon_core/hosts/maya/api/pipeline.py index 95617cb90a..e58316030e 100644 --- a/client/ayon_core/hosts/maya/api/pipeline.py +++ b/client/ayon_core/hosts/maya/api/pipeline.py @@ -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("AYON_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("AYON_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("AYON_WORKDIR").replace("\\", "/") expected_work_dir = event.data["workdir_path"].replace("\\", "/") if current_work_dir == expected_work_dir: return diff --git a/client/ayon_core/hosts/maya/api/plugin.py b/client/ayon_core/hosts/maya/api/plugin.py index c5e3f42d10..42c0c7051e 100644 --- a/client/ayon_core/hosts/maya/api/plugin.py +++ b/client/ayon_core/hosts/maya/api/plugin.py @@ -22,6 +22,7 @@ from ayon_core.pipeline import ( LegacyCreator, LoaderPlugin, get_representation_path, + get_current_project_name, ) from ayon_core.pipeline.load import LoadError from ayon_core.client import get_asset_by_name @@ -585,6 +586,39 @@ class RenderlayerCreator(NewCreator, MayaCreatorBase): project_name) +def get_load_color_for_family(family, settings=None): + """Get color for family from settings. + + Args: + family (str): Family name. + settings (Optional[dict]): Settings dictionary. + + Returns: + Union[tuple[float, float, float], None]: RGB color. + + """ + if settings is None: + settings = get_project_settings(get_current_project_name()) + + colors = settings["maya"]["load"]["colors"] + color = colors.get(family) + if not color: + return None + + if len(color) == 3: + red, green, blue = color + elif len(color) == 4: + red, green, blue, _ = color + else: + raise ValueError("Invalid color definition {}".format(str(color))) + + if isinstance(red, int): + red = red / 255.0 + green = green / 255.0 + blue = blue / 255.0 + return red, green, blue + + class Loader(LoaderPlugin): hosts = ["maya"] @@ -611,33 +645,38 @@ class Loader(LoaderPlugin): options["attach_to_root"] = True custom_naming = self.load_settings[loader_key] - if not custom_naming['namespace']: + if not custom_naming["namespace"]: raise LoadError("No namespace specified in " "Maya ReferenceLoader settings") - elif not custom_naming['group_name']: + elif not custom_naming["group_name"]: self.log.debug("No custom group_name, no group will be created.") options["attach_to_root"] = False - asset = context['asset'] - subset = context['subset'] + asset = context["asset"] + subset = context["subset"] + family = ( + subset["data"].get("family") + or subset["data"]["families"][0] + ) formatting_data = { - "asset_name": asset['name'], - "asset_type": asset['type'], + "asset_name": asset["name"], + "asset_type": asset["type"], "folder": { "name": asset["name"], }, - "subset": subset['name'], - "family": ( - subset['data'].get('family') or - subset['data']['families'][0] - ) + "subset": subset["name"], + "product": { + "name": subset["name"], + "type": family, + }, + "family": family } - custom_namespace = custom_naming['namespace'].format( + custom_namespace = custom_naming["namespace"].format( **formatting_data ) - custom_group_name = custom_naming['group_name'].format( + custom_group_name = custom_naming["group_name"].format( **formatting_data ) @@ -937,7 +976,7 @@ class ReferenceLoader(Loader): """ settings = get_project_settings(project_name) use_env_var_as_root = (settings["maya"] - ["maya-dirmap"] + ["maya_dirmap"] ["use_env_var_as_root"]) if use_env_var_as_root: anatomy = Anatomy(project_name) diff --git a/client/ayon_core/hosts/maya/api/shader_definition_editor.py b/client/ayon_core/hosts/maya/api/shader_definition_editor.py index 04e8dded6f..bfa531eb87 100644 --- a/client/ayon_core/hosts/maya/api/shader_definition_editor.py +++ b/client/ayon_core/hosts/maya/api/shader_definition_editor.py @@ -12,7 +12,7 @@ import gridfs DEFINITION_FILENAME = "{}/maya/shader_definition.txt".format( - os.getenv("AVALON_PROJECT")) + os.getenv("AYON_PROJECT_NAME")) class ShaderDefinitionsEditor(QtWidgets.QWidget): diff --git a/client/ayon_core/hosts/maya/api/workio.py b/client/ayon_core/hosts/maya/api/workio.py index 8c31974c73..ff6c11eb4f 100644 --- a/client/ayon_core/hosts/maya/api/workio.py +++ b/client/ayon_core/hosts/maya/api/workio.py @@ -35,7 +35,7 @@ def current_file(): def work_root(session): - work_dir = session["AVALON_WORKDIR"] + work_dir = session["AYON_WORKDIR"] scene_dir = None # Query scene file rule from workspace.mel if it exists in WORKDIR diff --git a/client/ayon_core/hosts/maya/hooks/pre_copy_mel.py b/client/ayon_core/hosts/maya/hooks/pre_copy_mel.py index 7198f98131..03ca8661bd 100644 --- a/client/ayon_core/hosts/maya/hooks/pre_copy_mel.py +++ b/client/ayon_core/hosts/maya/hooks/pre_copy_mel.py @@ -12,7 +12,7 @@ class PreCopyMel(PreLaunchHook): def execute(self): project_doc = self.data["project_doc"] - workdir = self.launch_context.env.get("AVALON_WORKDIR") + workdir = self.launch_context.env.get("AYON_WORKDIR") if not workdir: self.log.warning("BUG: Workdir is not filled.") return diff --git a/client/ayon_core/hosts/maya/plugins/create/create_render.py b/client/ayon_core/hosts/maya/plugins/create/create_render.py index f537f249cd..4481836c89 100644 --- a/client/ayon_core/hosts/maya/plugins/create/create_render.py +++ b/client/ayon_core/hosts/maya/plugins/create/create_render.py @@ -35,7 +35,7 @@ class CreateRenderlayer(plugin.RenderlayerCreator): @classmethod def apply_settings(cls, project_settings): - cls.render_settings = project_settings["maya"]["RenderSettings"] + cls.render_settings = project_settings["maya"]["render_settings"] def create(self, subset_name, instance_data, pre_create_data): # Only allow a single render instance to exist diff --git a/client/ayon_core/hosts/maya/plugins/create/create_review.py b/client/ayon_core/hosts/maya/plugins/create/create_review.py index 6f7c0ca802..3e75b52556 100644 --- a/client/ayon_core/hosts/maya/plugins/create/create_review.py +++ b/client/ayon_core/hosts/maya/plugins/create/create_review.py @@ -75,7 +75,7 @@ class CreateReview(plugin.MayaCreator): "review_width": preset["Resolution"]["width"], "review_height": preset["Resolution"]["height"], "isolate": preset["Generic"]["isolate_view"], - "imagePlane": preset["Viewport Options"]["imagePlane"], + "imagePlane": preset["ViewportOptions"]["imagePlane"], "panZoom": preset["Generic"]["pan_zoom"] } for key, value in mapping.items(): diff --git a/client/ayon_core/hosts/maya/plugins/create/create_vrayscene.py b/client/ayon_core/hosts/maya/plugins/create/create_vrayscene.py index 3642f5f689..bc8110eff1 100644 --- a/client/ayon_core/hosts/maya/plugins/create/create_vrayscene.py +++ b/client/ayon_core/hosts/maya/plugins/create/create_vrayscene.py @@ -23,7 +23,7 @@ class CreateVRayScene(plugin.RenderlayerCreator): @classmethod def apply_settings(cls, project_settings): - cls.render_settings = project_settings["maya"]["RenderSettings"] + cls.render_settings = project_settings["maya"]["render_settings"] def create(self, subset_name, instance_data, pre_create_data): # Only allow a single render instance to exist diff --git a/client/ayon_core/hosts/maya/plugins/load/load_arnold_standin.py b/client/ayon_core/hosts/maya/plugins/load/load_arnold_standin.py index c690d1c205..16ac460cf7 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_arnold_standin.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_arnold_standin.py @@ -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 ( @@ -16,6 +15,7 @@ from ayon_core.hosts.maya.api.lib import ( convert_to_maya_fps ) from ayon_core.hosts.maya.api.pipeline import containerise +from ayon_core.hosts.maya.api.plugin import get_load_color_for_family def is_sequence(files): @@ -26,11 +26,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""" @@ -72,11 +67,12 @@ class ArnoldStandinLoader(load.LoaderPlugin): # Set color. settings = get_project_settings(context["project"]["name"]) - color = settings['maya']['load']['colors'].get('ass') + color = get_load_color_for_family("ass", settings) if color is not None: + red, green, blue = color cmds.setAttr(root + ".useOutlinerColor", True) cmds.setAttr( - root + ".outlinerColor", color[0], color[1], color[2] + root + ".outlinerColor", red, green, blue ) with maintained_selection(): @@ -99,7 +95,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] diff --git a/client/ayon_core/hosts/maya/plugins/load/load_gpucache.py b/client/ayon_core/hosts/maya/plugins/load/load_gpucache.py index 00a76d374b..cdaaeeae6a 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_gpucache.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_gpucache.py @@ -9,6 +9,7 @@ from ayon_core.pipeline import ( get_representation_path ) from ayon_core.settings import get_project_settings +from ayon_core.hosts.maya.api.plugin import get_load_color_for_family class GpuCacheLoader(load.LoaderPlugin): @@ -39,13 +40,12 @@ class GpuCacheLoader(load.LoaderPlugin): project_name = context["project"]["name"] settings = get_project_settings(project_name) - colors = settings['maya']['load']['colors'] - c = colors.get('model') - if c is not None: + color = get_load_color_for_family("model", settings) + if color is not None: + red, green, blue = color cmds.setAttr(root + ".useOutlinerColor", 1) cmds.setAttr( - root + ".outlinerColor", - (float(c[0]) / 255), (float(c[1]) / 255), (float(c[2]) / 255) + root + ".outlinerColor", red, green, blue ) # Create transform with shape diff --git a/client/ayon_core/hosts/maya/plugins/load/load_redshift_proxy.py b/client/ayon_core/hosts/maya/plugins/load/load_redshift_proxy.py index dd378602c9..8910d0fcd0 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_redshift_proxy.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_redshift_proxy.py @@ -16,6 +16,7 @@ from ayon_core.hosts.maya.api.lib import ( unique_namespace ) from ayon_core.hosts.maya.api.pipeline import containerise +from ayon_core.hosts.maya.api.plugin import get_load_color_for_family class RedshiftProxyLoader(load.LoaderPlugin): @@ -59,12 +60,13 @@ class RedshiftProxyLoader(load.LoaderPlugin): # colour the group node project_name = context["project"]["name"] settings = get_project_settings(project_name) - colors = settings['maya']['load']['colors'] - c = colors.get(family) - if c is not None: + color = get_load_color_for_family(family, settings) + if color is not None: + red, green, blue = color cmds.setAttr("{0}.useOutlinerColor".format(group_node), 1) - cmds.setAttr("{0}.outlinerColor".format(group_node), - c[0], c[1], c[2]) + cmds.setAttr( + "{0}.outlinerColor".format(group_node), red, green, blue + ) return containerise( name=name, diff --git a/client/ayon_core/hosts/maya/plugins/load/load_reference.py b/client/ayon_core/hosts/maya/plugins/load/load_reference.py index 36bd2e5969..75f42a9fe6 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_reference.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_reference.py @@ -1,4 +1,3 @@ -import os import difflib import contextlib @@ -6,7 +5,7 @@ from maya import cmds import qargparse from ayon_core.settings import get_project_settings -import ayon_core.hosts.maya.api.plugin +from ayon_core.hosts.maya.api import plugin from ayon_core.hosts.maya.api.lib import ( maintained_selection, get_container_members, @@ -87,7 +86,7 @@ def preserve_modelpanel_cameras(container, log=None): cmds.modelPanel(panel, edit=True, camera=new_camera) -class ReferenceLoader(ayon_core.hosts.maya.api.plugin.ReferenceLoader): +class ReferenceLoader(plugin.ReferenceLoader): """Reference file""" families = ["model", @@ -185,14 +184,16 @@ class ReferenceLoader(ayon_core.hosts.maya.api.plugin.ReferenceLoader): "{}.displayHandle".format(group_name), display_handle ) - colors = settings['maya']['load']['colors'] - c = colors.get(family) - if c is not None: + color = plugin.get_load_color_for_family(family, settings) + if color is not None: + red, green, blue = color cmds.setAttr("{}.useOutlinerColor".format(group_name), 1) - cmds.setAttr("{}.outlinerColor".format(group_name), - (float(c[0]) / 255), - (float(c[1]) / 255), - (float(c[2]) / 255)) + cmds.setAttr( + "{}.outlinerColor".format(group_name), + red, + green, + blue + ) cmds.setAttr( "{}.displayHandle".format(group_name), display_handle diff --git a/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_arnold.py b/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_arnold.py index 98f98330d7..c68fddc60a 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_arnold.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_arnold.py @@ -5,6 +5,7 @@ from ayon_core.pipeline import ( load, get_representation_path ) +from ayon_core.hosts.maya.api.plugin import get_load_color_for_family # TODO aiVolume doesn't automatically set velocity fps correctly, set manual? @@ -50,16 +51,11 @@ class LoadVDBtoArnold(load.LoaderPlugin): project_name = context["project"]["name"] settings = get_project_settings(project_name) - colors = settings['maya']['load']['colors'] - - c = colors.get(family) - if c is not None: + color = get_load_color_for_family(family, settings) + if color is not None: + red, green, blue = color cmds.setAttr(root + ".useOutlinerColor", 1) - cmds.setAttr(root + ".outlinerColor", - (float(c[0]) / 255), - (float(c[1]) / 255), - (float(c[2]) / 255) - ) + cmds.setAttr(root + ".outlinerColor", red, green, blue) # Create VRayVolumeGrid grid_node = cmds.createNode("aiVolume", diff --git a/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_redshift.py b/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_redshift.py index 426e85cf7c..1bc75ae4c6 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_redshift.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_redshift.py @@ -5,6 +5,7 @@ from ayon_core.pipeline import ( load, get_representation_path ) +from ayon_core.hosts.maya.api.plugin import get_load_color_for_family class LoadVDBtoRedShift(load.LoaderPlugin): @@ -69,16 +70,11 @@ class LoadVDBtoRedShift(load.LoaderPlugin): project_name = context["project"]["name"] settings = get_project_settings(project_name) - colors = settings['maya']['load']['colors'] - - c = colors.get(family) - if c is not None: + color = get_load_color_for_family(family, settings) + if color is not None: + red, green, blue = color cmds.setAttr(root + ".useOutlinerColor", 1) - cmds.setAttr(root + ".outlinerColor", - (float(c[0])/255), - (float(c[1])/255), - (float(c[2])/255) - ) + cmds.setAttr(root + ".outlinerColor", red, green, blue) # Create VR volume_node = cmds.createNode("RedshiftVolumeShape", diff --git a/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_vray.py b/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_vray.py index ca0519900b..0c87162629 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_vray.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_vdb_to_vray.py @@ -5,6 +5,7 @@ from ayon_core.pipeline import ( load, get_representation_path ) +from ayon_core.hosts.maya.api.plugin import get_load_color_for_family from maya import cmds @@ -129,15 +130,11 @@ class LoadVDBtoVRay(load.LoaderPlugin): project_name = context["project"]["name"] settings = get_project_settings(project_name) - colors = settings['maya']['load']['colors'] - - c = colors.get(family) - if c is not None: + color = get_load_color_for_family(family, settings) + if color is not None: + red, green, blue = color cmds.setAttr(root + ".useOutlinerColor", 1) - cmds.setAttr(root + ".outlinerColor", - float(c[0]) / 255, - float(c[1]) / 255, - float(c[2]) / 255) + cmds.setAttr(root + ".outlinerColor", red, green, blue) # Create VRayVolumeGrid grid_node = cmds.createNode("VRayVolumeGrid", diff --git a/client/ayon_core/hosts/maya/plugins/load/load_vrayproxy.py b/client/ayon_core/hosts/maya/plugins/load/load_vrayproxy.py index 9b36303b64..50b63f4f11 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_vrayproxy.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_vrayproxy.py @@ -22,6 +22,7 @@ from ayon_core.hosts.maya.api.lib import ( unique_namespace ) from ayon_core.hosts.maya.api.pipeline import containerise +from ayon_core.hosts.maya.api.plugin import get_load_color_for_family class VRayProxyLoader(load.LoaderPlugin): @@ -80,15 +81,12 @@ class VRayProxyLoader(load.LoaderPlugin): # colour the group node project_name = context["project"]["name"] settings = get_project_settings(project_name) - colors = settings['maya']['load']['colors'] - c = colors.get(family) - if c is not None: + color = get_load_color_for_family(family, settings) + if color is not None: + red, green, blue = color cmds.setAttr("{0}.useOutlinerColor".format(group_node), 1) cmds.setAttr( - "{0}.outlinerColor".format(group_node), - (float(c[0]) / 255), - (float(c[1]) / 255), - (float(c[2]) / 255) + "{0}.outlinerColor".format(group_node), red, green, blue ) return containerise( diff --git a/client/ayon_core/hosts/maya/plugins/load/load_vrayscene.py b/client/ayon_core/hosts/maya/plugins/load/load_vrayscene.py index 92d2b32549..7b4edb0567 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_vrayscene.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_vrayscene.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -import os import maya.cmds as cmds # noqa from ayon_core.settings import get_project_settings from ayon_core.pipeline import ( @@ -12,6 +11,7 @@ from ayon_core.hosts.maya.api.lib import ( unique_namespace ) from ayon_core.hosts.maya.api.pipeline import containerise +from ayon_core.hosts.maya.api.plugin import get_load_color_for_family class VRaySceneLoader(load.LoaderPlugin): @@ -58,14 +58,12 @@ class VRaySceneLoader(load.LoaderPlugin): # colour the group node project_name = context["project"]["name"] settings = get_project_settings(project_name) - colors = settings['maya']['load']['colors'] - c = colors.get(family) - if c is not None: + color = get_load_color_for_family(family, settings) + if color is not None: + red, green, blue = color cmds.setAttr("{0}.useOutlinerColor".format(root_node), 1) - cmds.setAttr("{0}.outlinerColor".format(root_node), - (float(c[0])/255), - (float(c[1])/255), - (float(c[2])/255) + cmds.setAttr( + "{0}.outlinerColor".format(root_node), red, green, blue ) return containerise( diff --git a/client/ayon_core/hosts/maya/plugins/load/load_yeti_cache.py b/client/ayon_core/hosts/maya/plugins/load/load_yeti_cache.py index d2fc1c0ab0..afbb632d87 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_yeti_cache.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_yeti_cache.py @@ -13,6 +13,7 @@ from ayon_core.pipeline import ( ) from ayon_core.hosts.maya.api import lib from ayon_core.hosts.maya.api.pipeline import containerise +from ayon_core.hosts.maya.api.plugin import get_load_color_for_family # Do not reset these values on update but only apply on first load @@ -81,16 +82,11 @@ class YetiCacheLoader(load.LoaderPlugin): project_name = context["project"]["name"] settings = get_project_settings(project_name) - colors = settings['maya']['load']['colors'] - - c = colors.get(family) - if c is not None: + color = get_load_color_for_family(family, settings) + if color is not None: + red, green, blue = color cmds.setAttr(group_node + ".useOutlinerColor", 1) - cmds.setAttr(group_node + ".outlinerColor", - (float(c[0])/255), - (float(c[1])/255), - (float(c[2])/255) - ) + cmds.setAttr(group_node + ".outlinerColor", red, green, blue) nodes.append(group_node) diff --git a/client/ayon_core/hosts/maya/plugins/load/load_yeti_rig.py b/client/ayon_core/hosts/maya/plugins/load/load_yeti_rig.py index 2572e550e2..e7178be38b 100644 --- a/client/ayon_core/hosts/maya/plugins/load/load_yeti_rig.py +++ b/client/ayon_core/hosts/maya/plugins/load/load_yeti_rig.py @@ -1,11 +1,10 @@ import maya.cmds as cmds -from ayon_core.settings import get_current_project_settings -import ayon_core.hosts.maya.api.plugin +from ayon_core.hosts.maya.api import plugin from ayon_core.hosts.maya.api import lib -class YetiRigLoader(ayon_core.hosts.maya.api.plugin.ReferenceLoader): +class YetiRigLoader(plugin.ReferenceLoader): """This loader will load Yeti rig.""" families = ["yetiRig"] @@ -41,14 +40,12 @@ class YetiRigLoader(ayon_core.hosts.maya.api.plugin.ReferenceLoader): groupName=group_name ) - settings = get_current_project_settings() - colors = settings["maya"]["load"]["colors"] - c = colors.get("yetiRig") - if c is not None: + color = plugin.get_load_color_for_family("yetiRig") + if color is not None: + red, green, blue = color cmds.setAttr(group_name + ".useOutlinerColor", 1) cmds.setAttr( - group_name + ".outlinerColor", - (float(c[0]) / 255), (float(c[1]) / 255), (float(c[2]) / 255) + group_name + ".outlinerColor", red, green, blue ) self[:] = nodes diff --git a/client/ayon_core/hosts/maya/plugins/publish/collect_render.py b/client/ayon_core/hosts/maya/plugins/publish/collect_render.py index e4221a091c..4ea91ccb0d 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/collect_render.py +++ b/client/ayon_core/hosts/maya/plugins/publish/collect_render.py @@ -8,13 +8,12 @@ publishing on farm. Requires: instance -> families instance -> setMembers + instance -> asset context -> currentFile context -> workspaceDir context -> user - session -> AVALON_ASSET - Optional: Provides: diff --git a/client/ayon_core/hosts/maya/plugins/publish/collect_review.py b/client/ayon_core/hosts/maya/plugins/publish/collect_review.py index 679a21243a..205c871c93 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/collect_review.py +++ b/client/ayon_core/hosts/maya/plugins/publish/collect_review.py @@ -39,7 +39,7 @@ class CollectReview(pyblish.api.InstancePlugin): if display_lights == "project_settings": settings = instance.context.data["project_settings"] settings = settings["maya"]["publish"]["ExtractPlayblast"] - settings = settings["capture_preset"]["Viewport Options"] + settings = settings["capture_preset"]["ViewportOptions"] display_lights = settings["displayLights"] # Collect camera focal length. diff --git a/client/ayon_core/hosts/maya/plugins/publish/collect_vrayscene.py b/client/ayon_core/hosts/maya/plugins/publish/collect_vrayscene.py index 0efefe72c7..db008cc2be 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/collect_vrayscene.py +++ b/client/ayon_core/hosts/maya/plugins/publish/collect_vrayscene.py @@ -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 diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_camera_alembic.py b/client/ayon_core/hosts/maya/plugins/publish/extract_camera_alembic.py index b9561e299e..2be9cfec95 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_camera_alembic.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_camera_alembic.py @@ -1,4 +1,5 @@ import os +import json from maya import cmds @@ -21,7 +22,7 @@ class ExtractCameraAlembic(publish.Extractor, label = "Extract Camera (Alembic)" hosts = ["maya"] families = ["camera", "matchmove"] - bake_attributes = [] + bake_attributes = "[]" def process(self, instance): @@ -95,11 +96,12 @@ class ExtractCameraAlembic(publish.Extractor, job_str += ' -file "{0}"'.format(path) + bake_attributes = json.loads(self.bake_attributes) # bake specified attributes in preset - assert isinstance(self.bake_attributes, (list, tuple)), ( + assert isinstance(bake_attributes, list), ( "Attributes to bake must be specified as a list" ) - for attr in self.bake_attributes: + for attr in bake_attributes: self.log.debug("Adding {} attribute".format(attr)) job_str += " -attr {0}".format(attr) diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_camera_mayaScene.py b/client/ayon_core/hosts/maya/plugins/publish/extract_camera_mayaScene.py index 8ca1fd9d3a..689eed09f8 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_camera_mayaScene.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_camera_mayaScene.py @@ -112,9 +112,11 @@ class ExtractCameraMayaScene(publish.Extractor, def process(self, instance): """Plugin entry point.""" # get settings - ext_mapping = ( - instance.context.data["project_settings"]["maya"]["ext_mapping"] - ) + maya_settings = instance.context.data["project_settings"]["maya"] + ext_mapping = { + item["name"]: item["value"] + for item in maya_settings["ext_mapping"] + } if ext_mapping: self.log.debug("Looking in settings for scene type ...") # use extension mapping for first family found diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_import_reference.py b/client/ayon_core/hosts/maya/plugins/publish/extract_import_reference.py index 2a43a30b8d..3fb84c8d83 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_import_reference.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_import_reference.py @@ -37,9 +37,11 @@ class ExtractImportReference(publish.Extractor, if not self.is_active(instance.data): return - ext_mapping = ( - instance.context.data["project_settings"]["maya"]["ext_mapping"] - ) + maya_settings = instance.context.data["project_settings"]["maya"] + ext_mapping = { + item["name"]: item["value"] + for item in maya_settings["ext_mapping"] + } if ext_mapping: self.log.debug("Looking in settings for scene type ...") # use extension mapping for first family found diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_look.py b/client/ayon_core/hosts/maya/plugins/publish/extract_look.py index 7f97a7bf82..29390e2c7d 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_look.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_look.py @@ -431,9 +431,11 @@ class ExtractLook(publish.Extractor): project settings. """ - ext_mapping = ( - instance.context.data["project_settings"]["maya"]["ext_mapping"] - ) + maya_settings = instance.context.data["project_settings"]["maya"] + ext_mapping = { + item["name"]: item["value"] + for item in maya_settings["ext_mapping"] + } if ext_mapping: self.log.debug("Looking in settings for scene type ...") # use extension mapping for first family found diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_maya_scene_raw.py b/client/ayon_core/hosts/maya/plugins/publish/extract_maya_scene_raw.py index 5045a8d252..cd6f3bab6a 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_maya_scene_raw.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_maya_scene_raw.py @@ -43,9 +43,11 @@ class ExtractMayaSceneRaw(publish.Extractor, AYONPyblishPluginMixin): def process(self, instance): """Plugin entry point.""" - ext_mapping = ( - instance.context.data["project_settings"]["maya"]["ext_mapping"] - ) + maya_settings = instance.context.data["project_settings"]["maya"] + ext_mapping = { + item["name"]: item["value"] + for item in maya_settings["ext_mapping"] + } if ext_mapping: self.log.debug("Looking in settings for scene type ...") # use extension mapping for first family found diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_model.py b/client/ayon_core/hosts/maya/plugins/publish/extract_model.py index b6ae4d537a..543af59e8f 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_model.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_model.py @@ -35,9 +35,11 @@ class ExtractModel(publish.Extractor, if not self.is_active(instance.data): return - ext_mapping = ( - instance.context.data["project_settings"]["maya"]["ext_mapping"] - ) + maya_settings = instance.context.data["project_settings"]["maya"] + ext_mapping = { + item["name"]: item["value"] + for item in maya_settings["ext_mapping"] + } if ext_mapping: self.log.debug("Looking in settings for scene type ...") # use extension mapping for first family found diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_rig.py b/client/ayon_core/hosts/maya/plugins/publish/extract_rig.py index 13e3d7c6b4..305f4698c6 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_rig.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_rig.py @@ -18,9 +18,11 @@ class ExtractRig(publish.Extractor): def process(self, instance): """Plugin entry point.""" - ext_mapping = ( - instance.context.data["project_settings"]["maya"]["ext_mapping"] - ) + maya_settings = instance.context.data["project_settings"]["maya"] + ext_mapping = { + item["name"]: item["value"] + for item in maya_settings["ext_mapping"] + } if ext_mapping: self.log.debug("Looking in settings for scene type ...") # use extension mapping for first family found diff --git a/client/ayon_core/hosts/maya/plugins/publish/extract_yeti_rig.py b/client/ayon_core/hosts/maya/plugins/publish/extract_yeti_rig.py index 7387849736..0b67117ebc 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/extract_yeti_rig.py +++ b/client/ayon_core/hosts/maya/plugins/publish/extract_yeti_rig.py @@ -100,9 +100,11 @@ class ExtractYetiRig(publish.Extractor): def process(self, instance): """Plugin entry point.""" - ext_mapping = ( - instance.context.data["project_settings"]["maya"]["ext_mapping"] - ) + maya_settings = instance.context.data["project_settings"]["maya"] + ext_mapping = { + item["name"]: item["value"] + for item in maya_settings["ext_mapping"] + } if ext_mapping: self.log.debug("Looking in settings for scene type ...") # use extension mapping for first family found diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_attributes.py b/client/ayon_core/hosts/maya/plugins/publish/validate_attributes.py index 3dfe2f4f2d..fc39756bf0 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_attributes.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_attributes.py @@ -1,3 +1,4 @@ +import json from collections import defaultdict import pyblish.api @@ -23,19 +24,19 @@ class ValidateAttributes(pyblish.api.InstancePlugin, """ order = ValidateContentsOrder - label = "Attributes" + label = "Validate Attributes" hosts = ["maya"] actions = [RepairAction] optional = True - attributes = None + attributes = "{}" def process(self, instance): if not self.is_active(instance.data): return # Check for preset existence. - if not self.attributes: + if not self.get_attributes_data(): return invalid = self.get_invalid(instance, compute=True) @@ -44,6 +45,10 @@ class ValidateAttributes(pyblish.api.InstancePlugin, "Found attributes with invalid values: {}".format(invalid) ) + @classmethod + def get_attributes_data(cls): + return json.loads(cls.attributes) + @classmethod def get_invalid(cls, instance, compute=False): if compute: @@ -55,21 +60,22 @@ class ValidateAttributes(pyblish.api.InstancePlugin, def get_invalid_attributes(cls, instance): invalid_attributes = [] + attributes_data = cls.get_attributes_data() # Filter families. families = [instance.data["family"]] families += instance.data.get("families", []) - families = set(families) & set(cls.attributes.keys()) + families = set(families) & set(attributes_data.keys()) if not families: return [] # Get all attributes to validate. attributes = defaultdict(dict) for family in families: - if family not in cls.attributes: + if family not in attributes_data: # No attributes to validate for family continue - for preset_attr, preset_value in cls.attributes[family].items(): + for preset_attr, preset_value in attributes_data[family].items(): node_name, attribute_name = preset_attr.split(".", 1) attributes[node_name][attribute_name] = preset_value diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_frame_range.py b/client/ayon_core/hosts/maya/plugins/publish/validate_frame_range.py index 85cc606b25..d5f99e5563 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_frame_range.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_frame_range.py @@ -39,7 +39,7 @@ class ValidateFrameRange(pyblish.api.InstancePlugin, "yeticache"] optional = True actions = [RepairAction] - exclude_families = [] + exclude_product_types = [] def process(self, instance): if not self.is_active(instance.data): @@ -73,7 +73,9 @@ class ValidateFrameRange(pyblish.api.InstancePlugin, # compare with data on instance errors = [] - if [ef for ef in self.exclude_families + # QUESTION shouldn't this be just: + # 'if instance.data["family"] in self.exclude_product_types:' + if [ef for ef in self.exclude_product_types if instance.data["family"] in ef]: return if (inst_start != frame_start_handle): diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_model_name.py b/client/ayon_core/hosts/maya/plugins/publish/validate_model_name.py index 7812877fd3..cf2bbcd77c 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_model_name.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_model_name.py @@ -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) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_in_database.py b/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_in_database.py index bf12def5e9..de86ffe575 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_in_database.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_node_ids_in_database.py @@ -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) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_plugin_path_attributes.py b/client/ayon_core/hosts/maya/plugins/publish/validate_plugin_path_attributes.py index d672be6fa0..fd71039e30 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_plugin_path_attributes.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_plugin_path_attributes.py @@ -30,14 +30,18 @@ class ValidatePluginPathAttributes(pyblish.api.InstancePlugin): def get_invalid(cls, instance): invalid = list() - file_attrs = cls.attribute + file_attrs = { + item["name"]: item["value"] + for item in cls.attribute + } if not file_attrs: return invalid # Consider only valid node types to avoid "Unknown object type" warning all_node_types = set(cmds.allNodeTypes()) node_types = [ - key for key in file_attrs.keys() + key + for key in file_attrs.keys() if key in all_node_types ] diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_render_image_rule.py b/client/ayon_core/hosts/maya/plugins/publish/validate_render_image_rule.py index 576886072d..384d99df1a 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_render_image_rule.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_render_image_rule.py @@ -55,12 +55,15 @@ class ValidateRenderImageRule(pyblish.api.InstancePlugin): if staging_dir: cls.log.debug( "Staging dir found: \"{}\". Ignoring setting from " - "`project_settings/maya/RenderSettings/" + "`project_settings/maya/render_settings/" "default_render_image_folder`.".format(staging_dir) ) return staging_dir - return instance.context.data.get('project_settings')\ - .get('maya') \ - .get('RenderSettings') \ - .get('default_render_image_folder') + return ( + instance.context.data + ["project_settings"] + ["maya"] + ["render_settings"] + ["default_render_image_folder"] + ) diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_renderlayer_aovs.py b/client/ayon_core/hosts/maya/plugins/publish/validate_renderlayer_aovs.py index 71cd6d7112..b5bfdcc9ec 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_renderlayer_aovs.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_renderlayer_aovs.py @@ -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 diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_rendersettings.py b/client/ayon_core/hosts/maya/plugins/publish/validate_rendersettings.py index ed70d81b63..78a247b3f2 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_rendersettings.py @@ -265,7 +265,7 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): # load validation definitions from settings settings_lights_flag = instance.context.data["project_settings"].get( "maya", {}).get( - "RenderSettings", {}).get( + "render_settings", {}).get( "enable_all_lights", False) instance_lights_flag = instance.data.get("renderSetupIncludeLights") @@ -281,6 +281,8 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): # if so, compare its value from the one required. for data in cls.get_nodes(instance, renderer): for node in data["nodes"]: + # Why is captured 'PublishValidationError'? How it can be + # raised by 'cmds.getAttr(...)'? try: render_value = cmds.getAttr( "{}.{}".format(node, data["attribute"]) @@ -310,11 +312,16 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): @classmethod def get_nodes(cls, instance, renderer): maya_settings = instance.context.data["project_settings"]["maya"] + renderer_key = "{}_render_attributes".format(renderer) validation_settings = ( maya_settings["publish"]["ValidateRenderSettings"].get( - "{}_render_attributes".format(renderer) - ) or [] - ) + renderer_key + ) + ) or [] + validation_settings = [ + (item["type"], item["value"]) + for item in validation_settings + ] result = [] for attr, values in OrderedDict(validation_settings).items(): values = [convert_to_int_or_float(v) for v in values if v] diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_rig_joints_hidden.py b/client/ayon_core/hosts/maya/plugins/publish/validate_rig_joints_hidden.py index c6b9d23574..bb5ec8353e 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_rig_joints_hidden.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_rig_joints_hidden.py @@ -7,6 +7,7 @@ from ayon_core.hosts.maya.api import lib from ayon_core.pipeline.publish import ( RepairAction, ValidateContentsOrder, + PublishValidationError ) @@ -38,7 +39,8 @@ class ValidateRigJointsHidden(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: - raise ValueError("Visible joints found: {0}".format(invalid)) + raise PublishValidationError( + "Visible joints found: {0}".format(invalid)) @classmethod def repair(cls, instance): diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_transform_naming_suffix.py b/client/ayon_core/hosts/maya/plugins/publish/validate_transform_naming_suffix.py index 1f8d6b7470..e565866778 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_transform_naming_suffix.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_transform_naming_suffix.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- """Plugin for validating naming conventions.""" +import json from maya import cmds import pyblish.api @@ -35,29 +36,37 @@ class ValidateTransformNamingSuffix(pyblish.api.InstancePlugin, """ order = ValidateContentsOrder - hosts = ['maya'] - families = ['model'] + hosts = ["maya"] + families = ["model"] optional = True - label = 'Suffix Naming Conventions' + label = "Suffix Naming Conventions" actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction] - SUFFIX_NAMING_TABLE = {"mesh": ["_GEO", "_GES", "_GEP", "_OSD"], - "nurbsCurve": ["_CRV"], - "nurbsSurface": ["_NRB"], - "locator": ["_LOC"], - "group": ["_GRP"]} + SUFFIX_NAMING_TABLE = json.dumps({ + "mesh": ["_GEO", "_GES", "_GEP", "_OSD"], + "nurbsCurve": ["_CRV"], + "nurbsSurface": ["_NRB"], + "locator": ["_LOC"], + "group": ["_GRP"] + }) ALLOW_IF_NOT_IN_SUFFIX_TABLE = True @classmethod def get_table_for_invalid(cls): - ss = [] - for k, v in cls.SUFFIX_NAMING_TABLE.items(): - ss.append(" - {}: {}".format(k, ", ".join(v))) + suffix_naming_table = json.loads(cls.SUFFIX_NAMING_TABLE) + ss = [ + " - {}: {}".format(k, ", ".join(v)) + for k, v in suffix_naming_table.items() + ] return "
".join(ss) @staticmethod - def is_valid_name(node_name, shape_type, - SUFFIX_NAMING_TABLE, ALLOW_IF_NOT_IN_SUFFIX_TABLE): + def is_valid_name( + node_name, + shape_type, + suffix_naming_table, + allow_if_not_in_suffix_table + ): """Return whether node's name is correct. The correctness for a transform's suffix is dependent on what @@ -70,18 +79,18 @@ class ValidateTransformNamingSuffix(pyblish.api.InstancePlugin, Args: node_name (str): Node name. shape_type (str): Type of node. - SUFFIX_NAMING_TABLE (dict): Mapping dict for suffixes. - ALLOW_IF_NOT_IN_SUFFIX_TABLE (dict): Filter dict. + suffix_naming_table (dict): Mapping dict for suffixes. + allow_if_not_in_suffix_table (bool): Default output. """ - if shape_type not in SUFFIX_NAMING_TABLE: - return ALLOW_IF_NOT_IN_SUFFIX_TABLE - else: - suffices = SUFFIX_NAMING_TABLE[shape_type] - for suffix in suffices: - if node_name.endswith(suffix): - return True - return False + if shape_type not in suffix_naming_table: + return allow_if_not_in_suffix_table + + suffices = suffix_naming_table[shape_type] + for suffix in suffices: + if node_name.endswith(suffix): + return True + return False @classmethod def get_invalid(cls, instance): @@ -91,9 +100,10 @@ class ValidateTransformNamingSuffix(pyblish.api.InstancePlugin, instance (:class:`pyblish.api.Instance`): published instance. """ - transforms = cmds.ls(instance, type='transform', long=True) + transforms = cmds.ls(instance, type="transform", long=True) invalid = [] + suffix_naming_table = json.loads(cls.SUFFIX_NAMING_TABLE) for transform in transforms: shapes = cmds.listRelatives(transform, shapes=True, @@ -101,9 +111,12 @@ class ValidateTransformNamingSuffix(pyblish.api.InstancePlugin, noIntermediate=True) shape_type = cmds.nodeType(shapes[0]) if shapes else "group" - if not cls.is_valid_name(transform, shape_type, - cls.SUFFIX_NAMING_TABLE, - cls.ALLOW_IF_NOT_IN_SUFFIX_TABLE): + if not cls.is_valid_name( + transform, + shape_type, + suffix_naming_table, + cls.ALLOW_IF_NOT_IN_SUFFIX_TABLE + ): invalid.append(transform) return invalid diff --git a/client/ayon_core/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py b/client/ayon_core/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py index d43e04da60..21ea827f68 100644 --- a/client/ayon_core/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py +++ b/client/ayon_core/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py @@ -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, diff --git a/client/ayon_core/hosts/maya/startup/userSetup.py b/client/ayon_core/hosts/maya/startup/userSetup.py index 882f2df27c..adbbfe4f44 100644 --- a/client/ayon_core/hosts/maya/startup/userSetup.py +++ b/client/ayon_core/hosts/maya/startup/userSetup.py @@ -38,7 +38,7 @@ if explicit_plugins_loading["enabled"]: key = "AYON_OPEN_WORKFILE_POST_INITIALIZATION" if bool(int(os.environ.get(key, "0"))): def _log_and_open(): - path = os.environ["AVALON_LAST_WORKFILE"] + path = os.environ["AYON_LAST_WORKFILE"] print("Opening \"{}\"".format(path)) cmds.file(path, open=True, force=True) cmds.evalDeferred( @@ -46,24 +46,5 @@ if bool(int(os.environ.get(key, "0"))): lowestPriority=True ) -# Build a shelf. -shelf_preset = settings['maya'].get('project_shelf') -if shelf_preset: - icon_path = os.path.join( - os.environ['OPENPYPE_PROJECT_SCRIPTS'], - project_name, - "icons") - icon_path = os.path.abspath(icon_path) - - for i in shelf_preset['imports']: - import_string = "from {} import {}".format(project_name, i) - print(import_string) - exec(import_string) - - cmds.evalDeferred( - "mlib.shelf(name=shelf_preset['name'], iconPath=icon_path," - " preset=shelf_preset)" - ) - print("Finished OpenPype usersetup.") diff --git a/client/ayon_core/hosts/nuke/api/lib.py b/client/ayon_core/hosts/nuke/api/lib.py index 2ac33de68e..c320df9361 100644 --- a/client/ayon_core/hosts/nuke/api/lib.py +++ b/client/ayon_core/hosts/nuke/api/lib.py @@ -120,7 +120,7 @@ def deprecated(new_destination): class Context: main_window = None context_action_item = None - project_name = os.getenv("AVALON_PROJECT") + project_name = os.getenv("AYON_PROJECT_NAME") # Workfile related code workfiles_launched = False workfiles_tool_timer = None @@ -2605,7 +2605,7 @@ Reopening Nuke should synchronize these paths and resolve any discrepancies. def set_favorites(self): from .utils import set_context_favorites - work_dir = os.getenv("AVALON_WORKDIR") + work_dir = os.getenv("AYON_WORKDIR") asset = get_current_asset_name() favorite_items = OrderedDict() @@ -2953,7 +2953,7 @@ def process_workfile_builder(): create_fv_on = workfile_builder.get("create_first_version") or None builder_on = workfile_builder.get("builder_on_start") or None - last_workfile_path = os.environ.get("AVALON_LAST_WORKFILE") + last_workfile_path = os.environ.get("AYON_LAST_WORKFILE") # generate first version in file not existing and feature is enabled if create_fv_on and not os.path.exists(last_workfile_path): @@ -3203,7 +3203,7 @@ class DirmapCache: @classmethod def project_name(cls): if cls._project_name is None: - cls._project_name = os.getenv("AVALON_PROJECT") + cls._project_name = os.getenv("AYON_PROJECT_NAME") return cls._project_name @classmethod diff --git a/client/ayon_core/hosts/nuke/api/pipeline.py b/client/ayon_core/hosts/nuke/api/pipeline.py index bdba0757b6..735426240e 100644 --- a/client/ayon_core/hosts/nuke/api/pipeline.py +++ b/client/ayon_core/hosts/nuke/api/pipeline.py @@ -21,10 +21,12 @@ from ayon_core.pipeline import ( AVALON_CONTAINER_ID, get_current_asset_name, get_current_task_name, + registered_host, ) from ayon_core.pipeline.workfile import BuildWorkfile from ayon_core.tools.utils import host_tools from ayon_core.hosts.nuke import NUKE_ROOT_DIR +from ayon_core.tools.workfile_template_build import open_template_ui from .command import viewer_update_and_undo_stop from .lib import ( @@ -55,6 +57,7 @@ from .workfile_template_builder import ( build_workfile_template, create_placeholder, update_placeholder, + NukeTemplateBuilder, ) from .workio import ( open_file, @@ -176,7 +179,7 @@ def add_nuke_callbacks(): nuke.addOnScriptLoad(WorkfileSettings().set_context_settings) - if nuke_settings["nuke-dirmap"]["enabled"]: + if nuke_settings["dirmap"]["enabled"]: log.info("Added Nuke's dir-mapping callback ...") # Add dirmap for file paths. nuke.addFilenameFilter(dirmap_file_name_filter) @@ -313,7 +316,7 @@ def _install_menu(): lambda: BuildWorkfile().process() ) - menu_template = menu.addMenu("Template Builder") # creating template menu + menu_template = menu.addMenu("Template Builder") menu_template.addCommand( "Build Workfile from template", lambda: build_workfile_template() @@ -321,6 +324,12 @@ def _install_menu(): if not ASSIST: menu_template.addSeparator() + menu_template.addCommand( + "Open template", + lambda: open_template_ui( + NukeTemplateBuilder(registered_host()), get_main_window() + ) + ) menu_template.addCommand( "Create Place Holder", lambda: create_placeholder() diff --git a/client/ayon_core/hosts/nuke/api/workfile_template_builder.py b/client/ayon_core/hosts/nuke/api/workfile_template_builder.py index 4c15da983e..218ba97dd5 100644 --- a/client/ayon_core/hosts/nuke/api/workfile_template_builder.py +++ b/client/ayon_core/hosts/nuke/api/workfile_template_builder.py @@ -7,7 +7,7 @@ from ayon_core.pipeline.workfile.workfile_template_builder import ( LoadPlaceholderItem, CreatePlaceholderItem, PlaceholderLoadMixin, - PlaceholderCreateMixin + PlaceholderCreateMixin, ) from ayon_core.tools.workfile_template_build import ( WorkfileBuildPlaceholderDialog, diff --git a/client/ayon_core/hosts/nuke/api/workio.py b/client/ayon_core/hosts/nuke/api/workio.py index 98e59eff71..b2445fd3d2 100644 --- a/client/ayon_core/hosts/nuke/api/workio.py +++ b/client/ayon_core/hosts/nuke/api/workio.py @@ -68,7 +68,7 @@ def current_file(): def work_root(session): - work_dir = session["AVALON_WORKDIR"] + work_dir = session["AYON_WORKDIR"] scene_dir = session.get("AVALON_SCENEDIR") if scene_dir: path = os.path.join(work_dir, scene_dir) diff --git a/client/ayon_core/hosts/nuke/startup/custom_write_node.py b/client/ayon_core/hosts/nuke/startup/custom_write_node.py index 01e255d0c0..89dfde297c 100644 --- a/client/ayon_core/hosts/nuke/startup/custom_write_node.py +++ b/client/ayon_core/hosts/nuke/startup/custom_write_node.py @@ -112,7 +112,7 @@ class WriteNodeKnobSettingPanel(nukescripts.PythonPanel): for write_node in write_selected_nodes: # data for mapping the path data = { - "work": os.getenv("AVALON_WORKDIR"), + "work": os.getenv("AYON_WORKDIR"), "subset": write_node["name"].value(), "frame": "#" * frame_padding, "ext": ext diff --git a/client/ayon_core/hosts/photoshop/api/lib.py b/client/ayon_core/hosts/photoshop/api/lib.py index 3111503e40..af14e6d02f 100644 --- a/client/ayon_core/hosts/photoshop/api/lib.py +++ b/client/ayon_core/hosts/photoshop/api/lib.py @@ -3,12 +3,11 @@ import sys import contextlib import traceback -from ayon_core.lib import env_value_to_bool, Logger +from ayon_core.lib import env_value_to_bool, Logger, is_in_tests from ayon_core.addon import AddonsManager from ayon_core.pipeline import install_host from ayon_core.tools.utils import host_tools from ayon_core.tools.utils import get_ayon_qt_app -from ayon_core.tests.lib import is_in_tests from .launch_logic import ProcessLauncher, stub diff --git a/client/ayon_core/hosts/photoshop/api/pipeline.py b/client/ayon_core/hosts/photoshop/api/pipeline.py index 046ec8e6ee..ebde175053 100644 --- a/client/ayon_core/hosts/photoshop/api/pipeline.py +++ b/client/ayon_core/hosts/photoshop/api/pipeline.py @@ -62,7 +62,7 @@ class PhotoshopHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): return None def work_root(self, session): - return os.path.normpath(session["AVALON_WORKDIR"]).replace("\\", "/") + return os.path.normpath(session["AYON_WORKDIR"]).replace("\\", "/") def open_workfile(self, filepath): lib.stub().open(filepath) diff --git a/client/ayon_core/hosts/photoshop/plugins/publish/collect_batch_data.py b/client/ayon_core/hosts/photoshop/plugins/publish/collect_batch_data.py index 5e43a021c3..464b6e3999 100644 --- a/client/ayon_core/hosts/photoshop/plugins/publish/collect_batch_data.py +++ b/client/ayon_core/hosts/photoshop/plugins/publish/collect_batch_data.py @@ -17,12 +17,11 @@ 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 ) -from ayon_core.tests.lib import is_in_tests +from ayon_core.lib import is_in_tests class CollectBatchData(pyblish.api.ContextPlugin): @@ -53,10 +52,10 @@ class CollectBatchData(pyblish.api.ContextPlugin): assert os.path.exists(batch_dir), \ "Folder {} doesn't exist".format(batch_dir) - project_name = os.environ.get("AVALON_PROJECT") + project_name = os.environ.get("AYON_PROJECT_NAME") if project_name is None: raise AssertionError( - "Environment `AVALON_PROJECT` was not found." + "Environment `AYON_PROJECT_NAME` was not found." "Could not set project `root` which may cause issues." ) @@ -69,10 +68,8 @@ class CollectBatchData(pyblish.api.ContextPlugin): batch_data["context"] ) - 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 + os.environ["AYON_FOLDER_PATH"] = asset_name + os.environ["AYON_TASK_NAME"] = task_name context.data["asset"] = asset_name context.data["task"] = task_name diff --git a/client/ayon_core/hosts/photoshop/plugins/publish/collect_color_coded_instances.py b/client/ayon_core/hosts/photoshop/plugins/publish/collect_color_coded_instances.py index e309da62ba..6a09cff3c7 100644 --- a/client/ayon_core/hosts/photoshop/plugins/publish/collect_color_coded_instances.py +++ b/client/ayon_core/hosts/photoshop/plugins/publish/collect_color_coded_instances.py @@ -3,10 +3,9 @@ import re import pyblish.api -from ayon_core.lib import prepare_template_data +from ayon_core.lib import prepare_template_data, is_in_tests from ayon_core.hosts.photoshop import api as photoshop from ayon_core.settings import get_project_settings -from ayon_core.tests.lib import is_in_tests class CollectColorCodedInstances(pyblish.api.ContextPlugin): @@ -29,9 +28,8 @@ class CollectColorCodedInstances(pyblish.api.ContextPlugin): Identifier: id (str): "pyblish.avalon.instance" """ - order = pyblish.api.CollectorOrder + 0.100 - label = "Instances" + label = "Collect Color-coded Instances" order = pyblish.api.CollectorOrder hosts = ["photoshop"] targets = ["automated"] @@ -42,7 +40,7 @@ class CollectColorCodedInstances(pyblish.api.ContextPlugin): # flattened template cannot subset_template_name = "" create_flatten_image = "no" - flatten_subset_template = "" + flatten_product_name_template = "" def process(self, context): self.log.info("CollectColorCodedInstances") @@ -124,12 +122,12 @@ class CollectColorCodedInstances(pyblish.api.ContextPlugin): if self.create_flatten_image != "no" and publishable_layers: self.log.debug("create_flatten_image") - if not self.flatten_subset_template: + if not self.flatten_product_name_template: self.log.warning("No template for flatten image") return fill_pairs.pop("layer") - subset = self.flatten_subset_template.format( + subset = self.flatten_product_name_template.format( **prepare_template_data(fill_pairs)) first_layer = publishable_layers[0] # dummy layer diff --git a/client/ayon_core/hosts/photoshop/plugins/publish/collect_review.py b/client/ayon_core/hosts/photoshop/plugins/publish/collect_review.py index e487760736..1ffbadf022 100644 --- a/client/ayon_core/hosts/photoshop/plugins/publish/collect_review.py +++ b/client/ayon_core/hosts/photoshop/plugins/publish/collect_review.py @@ -6,24 +6,17 @@ Provides: instance -> family ("review") """ -import os - import pyblish.api -from ayon_core.pipeline.create import get_subset_name - class CollectReview(pyblish.api.ContextPlugin): """Adds review to families for instances marked to be reviewable. """ label = "Collect Review" - label = "Review" hosts = ["photoshop"] order = pyblish.api.CollectorOrder + 0.1 - publish = True - def process(self, context): for instance in context: creator_attributes = instance.data["creator_attributes"] diff --git a/client/ayon_core/hosts/resolve/api/workio.py b/client/ayon_core/hosts/resolve/api/workio.py index 5e4865ddc5..b6c2f63432 100644 --- a/client/ayon_core/hosts/resolve/api/workio.py +++ b/client/ayon_core/hosts/resolve/api/workio.py @@ -79,7 +79,7 @@ def open_file(filepath): def current_file(): pm = get_project_manager() file_ext = file_extensions()[0] - workdir_path = os.getenv("AVALON_WORKDIR") + workdir_path = os.getenv("AYON_WORKDIR") project = pm.GetCurrentProject() project_name = project.GetName() file_name = project_name + file_ext @@ -93,4 +93,4 @@ def current_file(): def work_root(session): - return os.path.normpath(session["AVALON_WORKDIR"]).replace("\\", "/") + return os.path.normpath(session["AYON_WORKDIR"]).replace("\\", "/") diff --git a/client/ayon_core/hosts/substancepainter/api/pipeline.py b/client/ayon_core/hosts/substancepainter/api/pipeline.py index 2bbcf2aded..03cb22136c 100644 --- a/client/ayon_core/hosts/substancepainter/api/pipeline.py +++ b/client/ayon_core/hosts/substancepainter/api/pipeline.py @@ -240,33 +240,34 @@ class SubstanceHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): def _install_shelves(self, project_settings): - shelves = project_settings["substancepainter"].get("shelves", {}) + shelves = project_settings["substancepainter"].get("shelves", []) if not shelves: return # Prepare formatting data if we detect any path which might have # template tokens like {asset} in there. formatting_data = {} - has_formatting_entries = any("{" in path for path in shelves.values()) + has_formatting_entries = any("{" in item["value"] for item in shelves) if has_formatting_entries: project_name = self.get_current_project_name() asset_name = self.get_current_asset_name() task_name = self.get_current_asset_name() system_settings = get_system_settings() - formatting_data = get_template_data_with_names(project_name, - asset_name, - task_name, - system_settings) + formatting_data = get_template_data_with_names( + project_name, asset_name, task_name, system_settings + ) anatomy = Anatomy(project_name) formatting_data["root"] = anatomy.roots - for name, path in shelves.items(): - shelf_name = None + for shelve_item in shelves: # Allow formatting with anatomy for the paths + path = shelve_item["value"] if "{" in path: path = StringTemplate.format_template(path, formatting_data) + name = shelve_item["name"] + shelf_name = None try: shelf_name = lib.load_shelf(path, name=name) except ValueError as exc: diff --git a/client/ayon_core/hosts/traypublisher/api/editorial.py b/client/ayon_core/hosts/traypublisher/api/editorial.py index d84a7200c8..6153bc5752 100644 --- a/client/ayon_core/hosts/traypublisher/api/editorial.py +++ b/client/ayon_core/hosts/traypublisher/api/editorial.py @@ -16,25 +16,31 @@ class ShotMetadataSolver: NO_DECOR_PATERN = re.compile(r"\{([a-z]*?)\}") - # presets - clip_name_tokenizer = None - shot_rename = True - shot_hierarchy = None - shot_add_tasks = None + def __init__(self, logger): + self.clip_name_tokenizer = [] + self.shot_rename = { + "enabled": False, + "shot_rename_template": "", + } + self.shot_hierarchy = { + "enabled": False, + "parents": [], + "parents_path": "", + } + self.shot_add_tasks = [] + self.log = logger - def __init__( + def update_data( self, clip_name_tokenizer, shot_rename, shot_hierarchy, - shot_add_tasks, - logger + shot_add_tasks ): self.clip_name_tokenizer = clip_name_tokenizer self.shot_rename = shot_rename self.shot_hierarchy = shot_hierarchy self.shot_add_tasks = shot_add_tasks - self.log = logger def _rename_template(self, data): """Shot renaming function @@ -86,7 +92,9 @@ class ShotMetadataSolver: search_text = parent_name + clip_name - for token_key, pattern in self.clip_name_tokenizer.items(): + for clip_name_item in self.clip_name_tokenizer: + token_key = clip_name_item["name"] + pattern = clip_name_item["regex"] p = re.compile(pattern) match = p.findall(search_text) if not match: @@ -137,11 +145,11 @@ class ShotMetadataSolver: )) _parent_tokens_type = { - parent_token["name"]: parent_token["type"] + parent_token["name"]: parent_token["parent_type"] for parent_token in hierarchy_parents } for _index, _parent in enumerate( - shot_hierarchy["parents_path"].split("/") + shot_hierarchy["parents_path"].split("/") ): # format parent token with value which is formatted try: @@ -262,22 +270,22 @@ class ShotMetadataSolver: """ tasks_to_add = {} - project_tasks = project_doc["config"]["tasks"] - for task_name, task_data in self.shot_add_tasks.items(): - _task_data = deepcopy(task_data) + project_task_types = project_doc["config"]["tasks"] + for task_item in self.shot_add_tasks: + task_name = task_item["name"] + task_type = task_item["task_type"] # check if task type in project task types - if _task_data["type"] in project_tasks.keys(): - tasks_to_add[task_name] = _task_data - else: + if task_type not in project_task_types.keys(): raise KeyError( "Missing task type `{}` for `{}` is not" " existing in `{}``".format( - _task_data["type"], + task_type, task_name, - list(project_tasks.keys()) + list(project_task_types.keys()) ) ) + tasks_to_add[task_name] = {"type": task_type} return tasks_to_add diff --git a/client/ayon_core/hosts/traypublisher/api/pipeline.py b/client/ayon_core/hosts/traypublisher/api/pipeline.py index 87177705c9..f4526ddf4b 100644 --- a/client/ayon_core/hosts/traypublisher/api/pipeline.py +++ b/client/ayon_core/hosts/traypublisher/api/pipeline.py @@ -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 @@ -23,8 +22,7 @@ class TrayPublisherHost(HostBase, IPublishHost): name = "traypublisher" def install(self): - os.environ["AVALON_APP"] = self.name - legacy_io.Session["AVALON_APP"] = self.name + os.environ["AYON_HOST_NAME"] = self.name pyblish.api.register_host("traypublisher") pyblish.api.register_plugin_path(PUBLISH_PATH) @@ -42,9 +40,7 @@ class TrayPublisherHost(HostBase, IPublishHost): def set_project_name(self, project_name): # 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() + os.environ["AYON_PROJECT_NAME"] = project_name HostContext.set_project_name(project_name) diff --git a/client/ayon_core/hosts/traypublisher/api/plugin.py b/client/ayon_core/hosts/traypublisher/api/plugin.py index 77a8f23d2e..d3d7f80e89 100644 --- a/client/ayon_core/hosts/traypublisher/api/plugin.py +++ b/client/ayon_core/hosts/traypublisher/api/plugin.py @@ -311,7 +311,7 @@ class SettingsCreator(TrayPublishCreator): @classmethod def from_settings(cls, item_data): identifier = item_data["identifier"] - family = item_data["family"] + family = item_data["product_type"] if not identifier: identifier = "settings_{}".format(family) return type( diff --git a/client/ayon_core/hosts/traypublisher/plugins/create/create_editorial.py b/client/ayon_core/hosts/traypublisher/plugins/create/create_editorial.py index 51a67a871e..d6501e65a2 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/create/create_editorial.py +++ b/client/ayon_core/hosts/traypublisher/plugins/create/create_editorial.py @@ -174,46 +174,42 @@ Supporting publishing new shots to project or updating already created. Publishing will create OTIO file. """ icon = "fa.file" + product_type_presets = [] - def __init__( - self, project_settings, *args, **kwargs - ): - super(EditorialSimpleCreator, self).__init__( - project_settings, *args, **kwargs - ) + def __init__(self, *args, **kwargs): + self._shot_metadata_solver = ShotMetadataSolver(self.log) + super(EditorialSimpleCreator, self).__init__(*args, **kwargs) + + def apply_settings(self, project_settings): editorial_creators = deepcopy( project_settings["traypublisher"]["editorial_creators"] ) - # get this creator settings by identifier - self._creator_settings = editorial_creators.get(self.identifier) + creator_settings = editorial_creators.get(self.identifier) - clip_name_tokenizer = self._creator_settings["clip_name_tokenizer"] - shot_rename = self._creator_settings["shot_rename"] - shot_hierarchy = self._creator_settings["shot_hierarchy"] - shot_add_tasks = self._creator_settings["shot_add_tasks"] - - self._shot_metadata_solver = ShotMetadataSolver( - clip_name_tokenizer, - shot_rename, - shot_hierarchy, - shot_add_tasks, - self.log + self._shot_metadata_solver.update_data( + creator_settings["clip_name_tokenizer"], + creator_settings["shot_rename"], + creator_settings["shot_hierarchy"], + creator_settings["shot_add_tasks"] ) - - # try to set main attributes from settings - if self._creator_settings.get("default_variants"): - self.default_variants = self._creator_settings["default_variants"] + self.product_type_presets = creator_settings["product_type_presets"] + default_variants = creator_settings.get("default_variants") + if default_variants: + self.default_variants = default_variants def create(self, subset_name, instance_data, pre_create_data): - allowed_family_presets = self._get_allowed_family_presets( + allowed_product_type_presets = self._get_allowed_product_type_presets( pre_create_data) + product_types = { + item["product_type"] + for item in self.product_type_presets + } clip_instance_properties = { - k: v for k, v in pre_create_data.items() + k: v + for k, v in pre_create_data.items() if k != "sequence_filepath_data" - if k not in [ - i["family"] for i in self._creator_settings["family_presets"] - ] + if k not in product_types } asset_name = instance_data["folderPath"] @@ -255,7 +251,7 @@ or updating already created. Publishing will create OTIO file. otio_timeline, media_path, clip_instance_properties, - allowed_family_presets, + allowed_product_type_presets, os.path.basename(seq_path), first_otio_timeline ) @@ -355,7 +351,7 @@ or updating already created. Publishing will create OTIO file. otio_timeline, media_path, instance_data, - family_presets, + product_type_presets, sequence_file_name, first_otio_timeline=None ): @@ -365,7 +361,7 @@ or updating already created. Publishing will create OTIO file. otio_timeline (otio.Timeline): otio timeline object media_path (str): media file path string instance_data (dict): clip instance data - family_presets (list): list of dict settings subset presets + product_type_presets (list): list of dict settings subset presets """ tracks = [ @@ -411,17 +407,17 @@ or updating already created. Publishing will create OTIO file. "instance_id": None } - for _fpreset in family_presets: + for product_type_preset in product_type_presets: # exclude audio family if no audio stream if ( - _fpreset["family"] == "audio" + product_type_preset["product_type"] == "audio" and not media_data.get("audio") ): continue instance = self._make_subset_instance( otio_clip, - _fpreset, + product_type_preset, deepcopy(base_instance_data), parenting_data ) @@ -533,7 +529,7 @@ or updating already created. Publishing will create OTIO file. def _make_subset_instance( self, otio_clip, - preset, + product_type_preset, instance_data, parenting_data ): @@ -541,16 +537,16 @@ or updating already created. Publishing will create OTIO file. Args: otio_clip (otio.Clip): otio clip object - preset (dict): single family preset + product_type_preset (dict): single family preset instance_data (dict): instance data parenting_data (dict): shot instance parent data Returns: CreatedInstance: creator instance object """ - family = preset["family"] + family = product_type_preset["product_type"] label = self._make_subset_naming( - preset, + product_type_preset, instance_data ) instance_data["label"] = label @@ -569,11 +565,11 @@ or updating already created. Publishing will create OTIO file. else: # add review family if defined instance_data.update({ - "outputFileType": preset["output_file_type"], + "outputFileType": product_type_preset["output_file_type"], "parent_instance_id": parenting_data["instance_id"], "creator_attributes": { "parent_instance": parenting_data["instance_label"], - "add_review_family": preset.get("review") + "add_review_family": product_type_preset.get("review") } }) @@ -585,15 +581,11 @@ or updating already created. Publishing will create OTIO file. return c_instance - def _make_subset_naming( - self, - preset, - instance_data - ): + def _make_subset_naming(self, product_type_preset, instance_data): """ Subset name maker Args: - preset (dict): single preset item + product_type_preset (dict): single preset item instance_data (dict): instance data Returns: @@ -602,10 +594,10 @@ or updating already created. Publishing will create OTIO file. asset_name = instance_data["creator_attributes"]["folderPath"] variant_name = instance_data["variant"] - family = preset["family"] + family = product_type_preset["product_type"] # get variant name from preset or from inheritance - _variant_name = preset.get("variant") or variant_name + _variant_name = product_type_preset.get("variant") or variant_name # subset name subset_name = "{}{}".format( @@ -763,7 +755,7 @@ or updating already created. Publishing will create OTIO file. "sourceOut": int(source_out) } - def _get_allowed_family_presets(self, pre_create_data): + def _get_allowed_product_type_presets(self, pre_create_data): """ Filter out allowed family presets. Args: @@ -773,10 +765,11 @@ or updating already created. Publishing will create OTIO file. list: lit of dict with preset items """ return [ - {"family": "shot"}, + {"product_type": "shot"}, *[ - preset for preset in self._creator_settings["family_presets"] - if pre_create_data[preset["family"]] + preset + for preset in self.product_type_presets + if pre_create_data[preset["product_type"]] ] ] @@ -853,8 +846,8 @@ or updating already created. Publishing will create OTIO file. ] # add variants swithers attr_defs.extend( - BoolDef(_var["family"], label=_var["family"]) - for _var in self._creator_settings["family_presets"] + BoolDef(item["product_type"], label=item["product_type"]) + for item in self.product_type_presets ) attr_defs.append(UISeparatorDef()) diff --git a/client/ayon_core/hosts/traypublisher/plugins/create/create_from_settings.py b/client/ayon_core/hosts/traypublisher/plugins/create/create_from_settings.py index 20f8dd792a..cc1429901d 100644 --- a/client/ayon_core/hosts/traypublisher/plugins/create/create_from_settings.py +++ b/client/ayon_core/hosts/traypublisher/plugins/create/create_from_settings.py @@ -8,7 +8,7 @@ log = Logger.get_logger(__name__) def initialize(): from ayon_core.hosts.traypublisher.api.plugin import SettingsCreator - project_name = os.environ["AVALON_PROJECT"] + project_name = os.environ["AYON_PROJECT_NAME"] project_settings = get_project_settings(project_name) simple_creators = project_settings["traypublisher"]["simple_creators"] diff --git a/client/ayon_core/hosts/tvpaint/api/pipeline.py b/client/ayon_core/hosts/tvpaint/api/pipeline.py index 1360b423b3..78a0c5270d 100644 --- a/client/ayon_core/hosts/tvpaint/api/pipeline.py +++ b/client/ayon_core/hosts/tvpaint/api/pipeline.py @@ -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("AYON_WORKDIR") if not os.path.exists(workdir): os.makedirs(workdir) @@ -157,7 +155,7 @@ class TVPaintHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): return execute_george(george_script) def work_root(self, session): - return session["AVALON_WORKDIR"] + return session["AYON_WORKDIR"] def get_current_workfile(self): return execute_george("tv_GetProjectName") @@ -176,7 +174,7 @@ class TVPaintHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost): # Setup project settings if its the template that's launched. # TODO also check for template creation when it's possible to define # templates - last_workfile = os.environ.get("AVALON_LAST_WORKFILE") + last_workfile = os.environ.get("AYON_LAST_WORKFILE") if not last_workfile or os.path.exists(last_workfile): return diff --git a/client/ayon_core/hosts/tvpaint/plugins/publish/collect_workfile_data.py b/client/ayon_core/hosts/tvpaint/plugins/publish/collect_workfile_data.py index 9fbf67863a..a6b6f05dc9 100644 --- a/client/ayon_core/hosts/tvpaint/plugins/publish/collect_workfile_data.py +++ b/client/ayon_core/hosts/tvpaint/plugins/publish/collect_workfile_data.py @@ -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, @@ -86,11 +85,10 @@ class CollectWorkfileData(pyblish.api.ContextPlugin): if workfile_context: # Change current context with context from workfile key_map = ( - ("AVALON_ASSET", "asset_name"), - ("AVALON_TASK", "task_name") + ("AYON_FOLDER_PATH", "asset_name"), + ("AYON_TASK_NAME", "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)) diff --git a/client/ayon_core/hosts/tvpaint/plugins/publish/extract_sequence.py b/client/ayon_core/hosts/tvpaint/plugins/publish/extract_sequence.py index 6d54d8ec32..0ab9fbd038 100644 --- a/client/ayon_core/hosts/tvpaint/plugins/publish/extract_sequence.py +++ b/client/ayon_core/hosts/tvpaint/plugins/publish/extract_sequence.py @@ -31,7 +31,7 @@ class ExtractSequence(pyblish.api.Extractor): families = ["review", "render"] # Modifiable with settings - review_bg = [255, 255, 255, 255] + review_bg = [255, 255, 255, 1.0] def process(self, instance): self.log.info( diff --git a/client/ayon_core/hosts/tvpaint/plugins/publish/validate_workfile_project_name.py b/client/ayon_core/hosts/tvpaint/plugins/publish/validate_workfile_project_name.py index be3259bfd8..5b42842717 100644 --- a/client/ayon_core/hosts/tvpaint/plugins/publish/validate_workfile_project_name.py +++ b/client/ayon_core/hosts/tvpaint/plugins/publish/validate_workfile_project_name.py @@ -6,7 +6,7 @@ class ValidateWorkfileProjectName(pyblish.api.ContextPlugin): """Validate project name stored in workfile metadata. It is not possible to publish from different project than is set in - environment variable "AVALON_PROJECT". + environment variable "AYON_PROJECT_NAME". """ label = "Validate Workfile Project Name" diff --git a/client/ayon_core/hosts/unreal/api/rendering.py b/client/ayon_core/hosts/unreal/api/rendering.py index 8717788732..4a15ceb89a 100644 --- a/client/ayon_core/hosts/unreal/api/rendering.py +++ b/client/ayon_core/hosts/unreal/api/rendering.py @@ -60,7 +60,7 @@ def start_rendering(): inst_data.append(data) try: - project = os.environ.get("AVALON_PROJECT") + project = os.environ.get("AYON_PROJECT_NAME") anatomy = Anatomy(project) root = anatomy.roots['renders'] except Exception as e: diff --git a/client/ayon_core/hosts/unreal/hooks/pre_workfile_preparation.py b/client/ayon_core/hosts/unreal/hooks/pre_workfile_preparation.py index 4317844ca5..0eaa1adb84 100644 --- a/client/ayon_core/hosts/unreal/hooks/pre_workfile_preparation.py +++ b/client/ayon_core/hosts/unreal/hooks/pre_workfile_preparation.py @@ -146,7 +146,7 @@ class UnrealPrelaunchHook(PreLaunchHook): def execute(self): """Hook entry method.""" - workdir = self.launch_context.env["AVALON_WORKDIR"] + workdir = self.launch_context.env["AYON_WORKDIR"] executable = str(self.launch_context.executable) engine_version = self.app_name.split("/")[-1].replace("-", ".") try: diff --git a/client/ayon_core/lib/__init__.py b/client/ayon_core/lib/__init__.py index 12a5535a1c..ab6a604adc 100644 --- a/client/ayon_core/lib/__init__.py +++ b/client/ayon_core/lib/__init__.py @@ -158,6 +158,7 @@ from .ayon_info import ( is_running_from_build, is_staging_enabled, is_dev_mode_enabled, + is_in_tests, ) @@ -229,6 +230,8 @@ __all__ = [ "IniSettingRegistry", "JSONSettingRegistry", + "AYONSecureRegistry", + "AYONSettingsRegistry", "OpenPypeSecureRegistry", "OpenPypeSettingsRegistry", "get_local_site_id", @@ -271,6 +274,7 @@ __all__ = [ "terminal", "get_datetime_data", + "get_timestamp", "get_formatted_current_time", "Logger", @@ -278,6 +282,7 @@ __all__ = [ "is_running_from_build", "is_staging_enabled", "is_dev_mode_enabled", + "is_in_tests", "requests_get", "requests_post" diff --git a/client/ayon_core/lib/applications.py b/client/ayon_core/lib/applications.py index febdaacdd1..60ef2a6708 100644 --- a/client/ayon_core/lib/applications.py +++ b/client/ayon_core/lib/applications.py @@ -230,29 +230,25 @@ class ApplicationGroup: self.manager = manager self._data = data - self.enabled = data.get("enabled", True) - self.label = data.get("label") or None - self.icon = data.get("icon") or None - self._environment = data.get("environment") or {} + self.enabled = data["enabled"] + self.label = data["label"] or None + self.icon = data["icon"] or None + env = {} + try: + env = json.loads(data["environment"]) + except Exception: + pass + self._environment = env - host_name = data.get("host_name", None) + host_name = data["host_name"] or None self.is_host = host_name is not None self.host_name = host_name - variants = data.get("variants") or {} - key_label_mapping = variants.pop(M_DYNAMIC_KEY_LABEL, {}) - for variant_name, variant_data in variants.items(): - if variant_name in METADATA_KEYS: - continue - - if "variant_label" not in variant_data: - variant_label = key_label_mapping.get(variant_name) - if variant_label: - variant_data["variant_label"] = variant_label - - variants[variant_name] = Application( - variant_name, variant_data, self - ) + settings_variants = data["variants"] + variants = {} + for variant_data in settings_variants: + app_variant = Application(variant_data, self) + variants[app_variant.name] = app_variant self.variants = variants @@ -274,62 +270,56 @@ class Application: Object by itself does nothing special. Args: - name (str): Specific version (or variant) of application. - e.g. "maya2020", "nuke11.3", etc. data (dict): Data for the version containing information about executables, variant label or if is enabled. Only required key is `executables`. group (ApplicationGroup): App group object that created the application and under which application belongs. + """ - - def __init__(self, name, data, group): - self.name = name - self.group = group + def __init__(self, data, group): self._data = data - + name = data["name"] + label = data["label"] or name enabled = False if group.enabled: enabled = data.get("enabled", True) - self.enabled = enabled - self.use_python_2 = data.get("use_python_2", False) - - self.label = data.get("variant_label") or name - self.full_name = "/".join((group.name, name)) if group.label: - full_label = " ".join((group.label, self.label)) + full_label = " ".join((group.label, label)) else: - full_label = self.label - self.full_label = full_label - self._environment = data.get("environment") or {} + full_label = label + env = {} + try: + env = json.loads(data["environment"]) + except Exception: + pass - arguments = data.get("arguments") + arguments = data["arguments"] if isinstance(arguments, dict): arguments = arguments.get(platform.system().lower()) if not arguments: arguments = [] + + _executables = data["executables"].get(platform.system().lower(), []) + executables = [ + ApplicationExecutable(executable) + for executable in _executables + ] + + self.group = group + + self.name = name + self.label = label + self.enabled = enabled + self.use_python_2 = data.get("use_python_2", False) + + self.full_name = "/".join((group.name, name)) + self.full_label = full_label self.arguments = arguments - - if "executables" not in data: - self.executables = [ - UndefinedApplicationExecutable() - ] - return - - _executables = data["executables"] - if isinstance(_executables, dict): - _executables = _executables.get(platform.system().lower()) - - if not _executables: - _executables = [] - - executables = [] - for executable in _executables: - executables.append(ApplicationExecutable(executable)) - self.executables = executables + self._environment = env def __repr__(self): return "<{}> - {}".format(self.__class__.__name__, self.full_name) @@ -384,12 +374,12 @@ class ApplicationManager: """Load applications and tools and store them by their full name. Args: - system_settings (dict): Preloaded system settings. When passed manager + studio_settings (dict): Preloaded studio settings. When passed manager will always use these values. Gives ability to create manager using different settings. """ - def __init__(self, system_settings=None): + def __init__(self, studio_settings=None): self.log = Logger.get_logger(self.__class__.__name__) self.app_groups = {} @@ -397,16 +387,16 @@ class ApplicationManager: self.tool_groups = {} self.tools = {} - self._system_settings = system_settings + self._studio_settings = studio_settings self.refresh() - def set_system_settings(self, system_settings): + def set_studio_settings(self, studio_settings): """Ability to change init system settings. This will trigger refresh of manager. """ - self._system_settings = system_settings + self._studio_settings = studio_settings self.refresh() @@ -417,72 +407,37 @@ class ApplicationManager: self.tool_groups.clear() self.tools.clear() - if self._system_settings is not None: - settings = copy.deepcopy(self._system_settings) + if self._studio_settings is not None: + settings = copy.deepcopy(self._studio_settings) else: settings = get_system_settings( clear_metadata=False, exclude_locals=False ) - all_app_defs = {} + applications_addon_settings = settings["applications"] + # Prepare known applications - app_defs = settings["applications"] - additional_apps = {} + app_defs = applications_addon_settings["applications"] + additional_apps = app_defs.pop("additional_apps") + for additional_app in additional_apps: + app_name = additional_app.pop("name") + if app_name in app_defs: + self.log.warning(( + "Additional application '{}' is already" + " in built-in applications." + ).format(app_name)) + app_defs[app_name] = additional_app + for group_name, variant_defs in app_defs.items(): - if group_name in METADATA_KEYS: - continue - - if group_name == "additional_apps": - additional_apps = variant_defs - else: - all_app_defs[group_name] = variant_defs - - # Prepare additional applications - # - First find dynamic keys that can be used as labels of group - dynamic_keys = {} - for group_name, variant_defs in additional_apps.items(): - if group_name == M_DYNAMIC_KEY_LABEL: - dynamic_keys = variant_defs - break - - # Add additional apps to known applications - for group_name, variant_defs in additional_apps.items(): - if group_name in METADATA_KEYS: - continue - - # Determine group label - label = variant_defs.get("label") - if not label: - # Look for label set in dynamic labels - label = dynamic_keys.get(group_name) - if not label: - label = group_name - variant_defs["label"] = label - - all_app_defs[group_name] = variant_defs - - for group_name, variant_defs in all_app_defs.items(): - if group_name in METADATA_KEYS: - continue - group = ApplicationGroup(group_name, variant_defs, self) self.app_groups[group_name] = group for app in group: self.applications[app.full_name] = app - tools_definitions = settings["tools"]["tool_groups"] - tool_label_mapping = tools_definitions.pop(M_DYNAMIC_KEY_LABEL, {}) - for tool_group_name, tool_group_data in tools_definitions.items(): - if not tool_group_name or tool_group_name in METADATA_KEYS: - continue - - tool_group_label = ( - tool_label_mapping.get(tool_group_name) or tool_group_name - ) - group = EnvironmentToolGroup( - tool_group_name, tool_group_label, tool_group_data, self - ) - self.tool_groups[tool_group_name] = group + tools_definitions = applications_addon_settings["tool_groups"] + for tool_group_data in tools_definitions: + group = EnvironmentToolGroup(tool_group_data, self) + self.tool_groups[group.name] = group for tool in group: self.tools[tool.full_name] = tool @@ -571,30 +526,31 @@ class EnvironmentToolGroup: are same. Args: - name (str): Name of the tool group. - data (dict): Group's information with it's variants. + data (dict): Group information with variants. manager (ApplicationManager): Manager that creates the group. """ - def __init__(self, name, label, data, manager): + def __init__(self, data, manager): + name = data["name"] + label = data["label"] + self.name = name self.label = label self._data = data self.manager = manager - self._environment = data["environment"] - variants = data.get("variants") or {} - label_by_key = variants.pop(M_DYNAMIC_KEY_LABEL, {}) + environment = {} + try: + environment = json.loads(data["environment"]) + except Exception: + pass + self._environment = environment + + variants = data.get("variants") or [] variants_by_name = {} - for variant_name, variant_data in variants.items(): - if variant_name in METADATA_KEYS: - continue - - variant_label = label_by_key.get(variant_name) or variant_name - tool = EnvironmentTool( - variant_name, variant_label, variant_data, self - ) - variants_by_name[variant_name] = tool + for variant_data in variants: + tool = EnvironmentTool(variant_data, self) + variants_by_name[tool.name] = tool self.variants = variants_by_name def __repr__(self): @@ -615,23 +571,25 @@ class EnvironmentTool: Structure of tool information. Args: - name (str): Name of the tool. variant_data (dict): Variant data with environments and host and app variant filters. - group (str): Name of group which wraps tool. + group (EnvironmentToolGroup): Name of group which wraps tool. """ - def __init__(self, name, label, variant_data, group): + def __init__(self, variant_data, group): # Backwards compatibility 3.9.1 - 3.9.2 # - 'variant_data' contained only environments but contain also host # and application variant filters - host_names = variant_data.get("host_names", []) - app_variants = variant_data.get("app_variants", []) + name = variant_data["name"] + label = variant_data["label"] + host_names = variant_data["host_names"] + app_variants = variant_data["app_variants"] - if "environment" in variant_data: - environment = variant_data["environment"] - else: - environment = variant_data + environment = {} + try: + environment = json.loads(variant_data["environment"]) + except Exception: + pass self.host_names = host_names self.app_variants = app_variants @@ -1740,15 +1698,15 @@ def prepare_context_environments(data, env_group=None, addons_manager=None): app = data["app"] context_env = { - "AVALON_PROJECT": project_doc["name"], - "AVALON_APP_NAME": app.full_name + "AYON_PROJECT_NAME": project_doc["name"], + "AYON_APP_NAME": app.full_name } if asset_doc: asset_name = get_asset_name_identifier(asset_doc) - context_env["AVALON_ASSET"] = asset_name + context_env["AYON_FOLDER_PATH"] = asset_name if task_name: - context_env["AVALON_TASK"] = task_name + context_env["AYON_TASK_NAME"] = task_name log.debug( "Context environments set:\n{}".format( @@ -1766,7 +1724,7 @@ def prepare_context_environments(data, env_group=None, addons_manager=None): if not app.is_host: return - data["env"]["AVALON_APP"] = app.host_name + data["env"]["AYON_HOST_NAME"] = app.host_name if not asset_doc or not task_name: # QUESTION replace with log.info and skip workfile discovery? @@ -1812,7 +1770,7 @@ def prepare_context_environments(data, env_group=None, addons_manager=None): "Couldn't create workdir because: {}".format(str(exc)) ) - data["env"]["AVALON_WORKDIR"] = workdir + data["env"]["AYON_WORKDIR"] = workdir _prepare_last_workfile(data, workdir, addons_manager) @@ -1929,7 +1887,7 @@ def _prepare_last_workfile(data, workdir, addons_manager): "Setting last workfile path: {}".format(last_workfile_path) ) - data["env"]["AVALON_LAST_WORKFILE"] = last_workfile_path + data["env"]["AYON_LAST_WORKFILE"] = last_workfile_path data["last_workfile_path"] = last_workfile_path diff --git a/client/ayon_core/lib/ayon_info.py b/client/ayon_core/lib/ayon_info.py index 725e10fa0e..97a35adcc6 100644 --- a/client/ayon_core/lib/ayon_info.py +++ b/client/ayon_core/lib/ayon_info.py @@ -38,6 +38,16 @@ def is_staging_enabled(): return os.getenv("AYON_USE_STAGING") == "1" +def is_in_tests(): + """Process is running in automatic tests mode. + + Returns: + bool: True if running in tests. + + """ + return os.environ.get("AYON_IN_TESTS") == "1" + + def is_dev_mode_enabled(): """Dev mode is enabled in AYON. diff --git a/client/ayon_core/lib/log.py b/client/ayon_core/lib/log.py index cbb1e41bae..36c39f9d84 100644 --- a/client/ayon_core/lib/log.py +++ b/client/ayon_core/lib/log.py @@ -257,7 +257,7 @@ class Logger: return cls._process_name # Get process name - process_name = os.environ.get("AVALON_APP_NAME") + process_name = os.environ.get("AYON_APP_NAME") if not process_name: try: import psutil diff --git a/client/ayon_core/modules/clockify/clockify_module.py b/client/ayon_core/modules/clockify/clockify_module.py index adb7eb66af..58407bfe94 100644 --- a/client/ayon_core/modules/clockify/clockify_module.py +++ b/client/ayon_core/modules/clockify/clockify_module.py @@ -2,22 +2,27 @@ import os import threading import time -from ayon_core.modules import OpenPypeModule, ITrayModule, IPluginPaths +from ayon_core.modules import AYONAddon, ITrayModule, IPluginPaths from ayon_core.client import get_asset_by_name from .constants import CLOCKIFY_FTRACK_USER_PATH, CLOCKIFY_FTRACK_SERVER_PATH -class ClockifyModule(OpenPypeModule, ITrayModule, IPluginPaths): +class ClockifyModule(AYONAddon, ITrayModule, IPluginPaths): name = "clockify" - def initialize(self, modules_settings): - clockify_settings = modules_settings[self.name] - self.enabled = clockify_settings["enabled"] - self.workspace_name = clockify_settings["workspace_name"] + def initialize(self, studio_settings): + enabled = self.name in studio_settings + workspace_name = None + if enabled: + clockify_settings = studio_settings[self.name] + workspace_name = clockify_settings["workspace_name"] - if self.enabled and not self.workspace_name: - raise Exception("Clockify Workspace is not set in settings.") + if enabled and workspace_name: + self.log.warning("Clockify Workspace is not set in settings.") + enabled = False + self.enabled = enabled + self.workspace_name = workspace_name self.timer_manager = None self.MessageWidgetClass = None diff --git a/client/ayon_core/modules/clockify/launcher_actions/ClockifyStart.py b/client/ayon_core/modules/clockify/launcher_actions/ClockifyStart.py index 19aa2ef195..f7dd1772b0 100644 --- a/client/ayon_core/modules/clockify/launcher_actions/ClockifyStart.py +++ b/client/ayon_core/modules/clockify/launcher_actions/ClockifyStart.py @@ -12,7 +12,7 @@ class ClockifyStart(LauncherAction): def is_compatible(self, session): """Return whether the action is compatible with the session""" - if "AVALON_TASK" in session: + if "AYON_TASK_NAME" in session: return True return False @@ -20,9 +20,9 @@ class ClockifyStart(LauncherAction): self.clockify_api.set_api() user_id = self.clockify_api.user_id workspace_id = self.clockify_api.workspace_id - project_name = session["AVALON_PROJECT"] - asset_name = session["AVALON_ASSET"] - task_name = session["AVALON_TASK"] + project_name = session["AYON_PROJECT_NAME"] + asset_name = session["AYON_FOLDER_PATH"] + task_name = session["AYON_TASK_NAME"] description = asset_name # fetch asset docs diff --git a/client/ayon_core/modules/clockify/launcher_actions/ClockifySync.py b/client/ayon_core/modules/clockify/launcher_actions/ClockifySync.py index 30f5ae698f..5ef9033ffe 100644 --- a/client/ayon_core/modules/clockify/launcher_actions/ClockifySync.py +++ b/client/ayon_core/modules/clockify/launcher_actions/ClockifySync.py @@ -36,7 +36,7 @@ class ClockifySync(LauncherAction): raise ClockifyPermissionsCheckFailed( "Current CLockify user is missing permissions for this action!" ) - project_name = session.get("AVALON_PROJECT") or "" + project_name = session.get("AYON_PROJECT_NAME") or "" projects_to_sync = [] if project_name.strip(): diff --git a/client/ayon_core/modules/deadline/deadline_module.py b/client/ayon_core/modules/deadline/deadline_module.py index c98d04759e..97d346c287 100644 --- a/client/ayon_core/modules/deadline/deadline_module.py +++ b/client/ayon_core/modules/deadline/deadline_module.py @@ -4,7 +4,7 @@ import six import sys from ayon_core.lib import requests_get, Logger -from ayon_core.modules import OpenPypeModule, IPluginPaths +from ayon_core.modules import AYONAddon, IPluginPaths class DeadlineWebserviceError(Exception): @@ -13,28 +13,28 @@ class DeadlineWebserviceError(Exception): """ -class DeadlineModule(OpenPypeModule, IPluginPaths): +class DeadlineModule(AYONAddon, IPluginPaths): name = "deadline" - def __init__(self, manager, settings): - self.deadline_urls = {} - super(DeadlineModule, self).__init__(manager, settings) - - def initialize(self, modules_settings): + def initialize(self, studio_settings): # This module is always enabled - deadline_settings = modules_settings[self.name] - self.enabled = deadline_settings["enabled"] - deadline_url = deadline_settings.get("DEADLINE_REST_URL") - if deadline_url: - self.deadline_urls = {"default": deadline_url} - else: - self.deadline_urls = deadline_settings.get("deadline_urls") # noqa: E501 + deadline_urls = {} + enabled = self.name in studio_settings + if enabled: + deadline_settings = studio_settings[self.name] + deadline_urls = { + url_item["name"]: url_item["value"] + for url_item in deadline_settings["deadline_urls"] + } - if not self.deadline_urls: - self.enabled = False - self.log.warning(("default Deadline Webservice URL " - "not specified. Disabling module.")) - return + if enabled and not deadline_urls: + enabled = False + self.log.warning(( + "Deadline Webservice URLs are not specified. Disabling addon." + )) + + self.enabled = enabled + self.deadline_urls = deadline_urls def get_plugin_paths(self): """Deadline plugin paths.""" diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py b/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py index 0cfe7c9b39..445971f2b0 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py @@ -47,11 +47,11 @@ class CollectDeadlineServerFromInstance(pyblish.api.InstancePlugin): deadline_settings = ( render_instance.context.data ["system_settings"] - ["modules"] ["deadline"] ) default_server = render_instance.context.data["defaultDeadline"] + # QUESTION How and where is this is set? Should be removed? instance_server = render_instance.data.get("deadlineServers") if not instance_server: self.log.debug("Using default server.") @@ -64,7 +64,10 @@ class CollectDeadlineServerFromInstance(pyblish.api.InstancePlugin): asString=True ) - default_servers = deadline_settings["deadline_urls"] + default_servers = { + url_item["name"]: url_item["value"] + for url_item in deadline_settings["deadline_urls"] + } project_servers = ( render_instance.context.data ["project_settings"] diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_aftereffects_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_aftereffects_deadline.py index f7bc5529fb..3537c3099d 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_aftereffects_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_aftereffects_deadline.py @@ -7,11 +7,10 @@ from datetime import datetime from ayon_core.lib import ( env_value_to_bool, collect_frames, + is_in_tests, ) -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 @attr.s @@ -81,16 +80,20 @@ class AfterEffectsSubmitDeadline( "FTRACK_API_KEY", "FTRACK_API_USER", "FTRACK_SERVER", - "AVALON_PROJECT", - "AVALON_ASSET", - "AVALON_TASK", - "AVALON_APP_NAME", + "AYON_PROJECT_NAME", + "AYON_FOLDER_PATH", + "AYON_TASK_NAME", + "AYON_WORKDIR", + "AYON_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: diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_blender_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_blender_deadline.py index c8b72ca52b..ae19e63a37 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_blender_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_blender_deadline.py @@ -10,11 +10,10 @@ from ayon_core.lib import ( BoolDef, NumberDef, TextDef, + is_in_tests, ) -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 from openpype_modules.deadline import abstract_submit_deadline from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo @@ -103,15 +102,19 @@ class BlenderSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, "FTRACK_API_USER", "FTRACK_SERVER", "OPENPYPE_SG_USER", - "AVALON_PROJECT", - "AVALON_ASSET", - "AVALON_TASK", - "AVALON_APP_NAME", + "AYON_PROJECT_NAME", + "AYON_FOLDER_PATH", + "AYON_TASK_NAME", + "AYON_WORKDIR", + "AYON_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) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py index 77505eb623..9c125db174 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py @@ -6,7 +6,6 @@ import requests import pyblish.api -from ayon_core.pipeline import legacy_io from ayon_core.pipeline.publish import ( AYONPyblishPluginMixin ) @@ -221,17 +220,21 @@ class FusionSubmitDeadline( "FTRACK_API_KEY", "FTRACK_API_USER", "FTRACK_SERVER", - "AVALON_PROJECT", - "AVALON_ASSET", - "AVALON_TASK", - "AVALON_APP_NAME", + "AYON_PROJECT_NAME", + "AYON_FOLDER_PATH", + "AYON_TASK_NAME", + "AYON_WORKDIR", + "AYON_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" diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_harmony_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_harmony_deadline.py index f2f1c90559..beb8afc3a3 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_harmony_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_harmony_deadline.py @@ -10,10 +10,9 @@ 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 +from ayon_core.lib import is_in_tests class _ZipFile(ZipFile): @@ -274,16 +273,20 @@ class HarmonySubmitDeadline( "FTRACK_API_KEY", "FTRACK_API_USER", "FTRACK_SERVER", - "AVALON_PROJECT", - "AVALON_ASSET", - "AVALON_TASK", - "AVALON_APP_NAME", + "AYON_PROJECT_NAME", + "AYON_FOLDER_PATH", + "AYON_TASK_NAME", + "AYON_WORKDIR", + "AYON_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: diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py index eed930e372..94e0947952 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_cache_deadline.py @@ -7,12 +7,11 @@ import pyblish.api from ayon_core.lib import ( TextDef, NumberDef, + is_in_tests, ) from ayon_core.pipeline import ( - legacy_io, 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 @@ -99,15 +98,19 @@ class HoudiniCacheSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline "FTRACK_API_USER", "FTRACK_SERVER", "OPENPYPE_SG_USER", - "AVALON_PROJECT", - "AVALON_ASSET", - "AVALON_TASK", - "AVALON_APP_NAME", + "AYON_PROJECT_NAME", + "AYON_FOLDER_PATH", + "AYON_TASK_NAME", + "AYON_WORKDIR", + "AYON_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) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py index 9988248957..45209d00f6 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_houdini_render_deadline.py @@ -5,11 +5,11 @@ from datetime import datetime import pyblish.api -from ayon_core.pipeline import legacy_io, AYONPyblishPluginMixin -from ayon_core.tests.lib import is_in_tests +from ayon_core.pipeline import AYONPyblishPluginMixin from openpype_modules.deadline import abstract_submit_deadline from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo from ayon_core.lib import ( + is_in_tests, BoolDef, NumberDef ) @@ -204,15 +204,19 @@ class HoudiniSubmitDeadline( "FTRACK_API_USER", "FTRACK_SERVER", "OPENPYPE_SG_USER", - "AVALON_PROJECT", - "AVALON_ASSET", - "AVALON_TASK", - "AVALON_APP_NAME", + "AYON_PROJECT_NAME", + "AYON_FOLDER_PATH", + "AYON_TASK_NAME", + "AYON_WORKDIR", + "AYON_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) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_max_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_max_deadline.py index 0a7c96008e..4eaaedac34 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_max_deadline.py @@ -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 ( @@ -107,15 +106,19 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, "FTRACK_API_USER", "FTRACK_SERVER", "OPENPYPE_SG_USER", - "AVALON_PROJECT", - "AVALON_ASSET", - "AVALON_TASK", - "AVALON_APP_NAME", + "AYON_PROJECT_NAME", + "AYON_FOLDER_PATH", + "AYON_TASK_NAME", + "AYON_WORKDIR", + "AYON_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) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_maya_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_maya_deadline.py index 84e6e93e6a..afa3a3dd14 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -18,6 +18,7 @@ Attributes: from __future__ import print_function import os +import json import getpass import copy import re @@ -29,21 +30,20 @@ from collections import OrderedDict import attr from ayon_core.pipeline import ( - legacy_io, AYONPyblishPluginMixin ) from ayon_core.lib import ( BoolDef, NumberDef, TextDef, - EnumDef + EnumDef, + is_in_tests, ) from ayon_core.hosts.maya.api.lib_rendersettings import RenderSettings from ayon_core.hosts.maya.api.lib import get_attr_in_layer 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 from ayon_core.pipeline.farm.tools import iter_expected_files @@ -131,8 +131,15 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, cls.group = settings.get("group", cls.group) cls.strict_error_checking = settings.get("strict_error_checking", cls.strict_error_checking) - cls.jobInfo = settings.get("jobInfo", cls.jobInfo) - cls.pluginInfo = settings.get("pluginInfo", cls.pluginInfo) + job_info = settings.get("jobInfo") + if job_info: + job_info = json.loads(job_info) + plugin_info = settings.get("pluginInfo") + if plugin_info: + plugin_info = json.loads(plugin_info) + + cls.jobInfo = job_info or cls.jobInfo + cls.pluginInfo = plugin_info or cls.pluginInfo def get_job_info(self): job_info = DeadlineJobInfo(Plugin="MayaBatch") @@ -200,15 +207,19 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, "FTRACK_API_USER", "FTRACK_SERVER", "OPENPYPE_SG_USER", - "AVALON_PROJECT", - "AVALON_ASSET", - "AVALON_TASK", - "AVALON_APP_NAME", + "AYON_PROJECT_NAME", + "AYON_FOLDER_PATH", + "AYON_TASK_NAME", + "AYON_WORKDIR", + "AYON_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) @@ -248,7 +259,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, default_rs_include_lights = ( instance.context.data['project_settings'] ['maya'] - ['RenderSettings'] + ['render_settings'] ['enable_all_lights'] ) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py index 02338c5c32..1042c44c33 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_maya_remote_publish_deadline.py @@ -2,8 +2,8 @@ import os import attr from datetime import datetime -from ayon_core.pipeline import legacy_io, PublishXmlValidationError -from ayon_core.tests.lib import is_in_tests +from ayon_core.pipeline import PublishXmlValidationError +from ayon_core.lib import is_in_tests from openpype_modules.deadline import abstract_submit_deadline from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo @@ -98,14 +98,16 @@ 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"] - environment["AVALON_APP_NAME"] = os.environ.get("AVALON_APP_NAME") + environment["AYON_PROJECT_NAME"] = project_name + environment["AYON_FOLDER_PATH"] = instance.context.data["asset"] + environment["AYON_TASK_NAME"] = instance.context.data["task"] + environment["AYON_APP_NAME"] = os.environ.get("AYON_APP_NAME") environment["OPENPYPE_PUBLISH_SUBSET"] = instance.data["subset"] environment["AYON_LOG_NO_COLORS"] = "1" environment["AYON_USERNAME"] = instance.context.data["user"] diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py index 9fff8edee6..b314232a42 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -7,12 +7,11 @@ from datetime import datetime import requests import pyblish.api -from ayon_core.pipeline import legacy_io from ayon_core.pipeline.publish import ( AYONPyblishPluginMixin ) -from ayon_core.tests.lib import is_in_tests from ayon_core.lib import ( + is_in_tests, BoolDef, NumberDef ) @@ -40,10 +39,10 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, concurrent_tasks = 1 group = "" department = "" - limit_groups = {} + limit_groups = [] use_gpu = False env_allowed_keys = [] - env_search_replace_values = {} + env_search_replace_values = [] workfile_dependency = True use_published_workfile = True @@ -374,10 +373,10 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, keys = [ "PYTHONPATH", "PATH", - "AVALON_PROJECT", - "AVALON_ASSET", - "AVALON_TASK", - "AVALON_APP_NAME", + "AYON_PROJECT_NAME", + "AYON_FOLDER_PATH", + "AYON_TASK_NAME", + "AYON_APP_NAME", "FTRACK_API_KEY", "FTRACK_API_USER", "FTRACK_SERVER", @@ -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" @@ -402,8 +404,10 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, # finally search replace in values of any key if self.env_search_replace_values: for key, value in environment.items(): - for _k, _v in self.env_search_replace_values.items(): - environment[key] = value.replace(_k, _v) + for item in self.env_search_replace_values: + environment[key] = value.replace( + item["name"], item["value"] + ) payload["JobInfo"].update({ "EnvironmentKeyValue%d" % index: "{key}={value}".format( @@ -539,8 +543,10 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, import nuke captured_groups = [] - for lg_name, list_node_class in self.limit_groups.items(): - for node_class in list_node_class: + for limit_group in self.limit_groups: + lg_name = limit_group["name"] + + for node_class in limit_group["value"]: for node in nuke.allNodes(recurseGroups=True): # ignore all nodes not member of defined class if node.Class() not in node_class: diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py index a90397baa2..54d4fb47fc 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py @@ -11,9 +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.tests.lib import is_in_tests +from ayon_core.pipeline import publish +from ayon_core.lib import EnumDef, is_in_tests from ayon_core.pipeline.version_start import get_versioning_start from ayon_core.pipeline.farm.pyblish_functions import ( @@ -68,7 +67,7 @@ class ProcessSubmittedCacheJobOnFarm(pyblish.api.InstancePlugin, "FTRACK_API_USER", "FTRACK_API_KEY", "FTRACK_SERVER", - "AVALON_APP_NAME", + "AYON_APP_NAME", "AYON_USERNAME", "OPENPYPE_SG_USER", "KITSU_LOGIN", @@ -126,9 +125,9 @@ class ProcessSubmittedCacheJobOnFarm(pyblish.api.InstancePlugin, create_metadata_path(instance, anatomy) environment = { - "AVALON_PROJECT": instance.context.data["projectName"], - "AVALON_ASSET": instance.context.data["asset"], - "AVALON_TASK": instance.context.data["task"], + "AYON_PROJECT_NAME": instance.context.data["projectName"], + "AYON_FOLDER_PATH": instance.context.data["asset"], + "AYON_TASK_NAME": instance.context.data["task"], "AYON_USERNAME": instance.context.data["user"], "AYON_LOG_NO_COLORS": "1", "IS_TEST": str(int(is_in_tests())), @@ -370,7 +369,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 } diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py index bd343e103a..d4d04f79f6 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py @@ -12,9 +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.tests.lib import is_in_tests +from ayon_core.pipeline import publish +from ayon_core.lib import EnumDef, is_in_tests from ayon_core.pipeline.version_start import get_versioning_start from ayon_core.pipeline.farm.pyblish_functions import ( @@ -99,18 +98,39 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, "karma_rop", "vray_rop", "redshift_rop"] - aov_filter = {"maya": [r".*([Bb]eauty).*"], - "blender": [r".*([Bb]eauty).*"], - "aftereffects": [r".*"], # for everything from AE - "harmony": [r".*"], # for everything from AE - "celaction": [r".*"], - "max": [r".*"]} + aov_filter = [ + { + "name": "maya", + "value": [r".*([Bb]eauty).*"] + }, + { + "name": "blender", + "value": [r".*([Bb]eauty).*"] + }, + { + # for everything from AE + "name": "aftereffects", + "value": [r".*"] + }, + { + "name": "harmony", + "value": [r".*"] + }, + { + "name": "celaction", + "value": [r".*"] + }, + { + "name": "max", + "value": [r".*"] + }, + ] environ_keys = [ "FTRACK_API_USER", "FTRACK_API_KEY", "FTRACK_SERVER", - "AVALON_APP_NAME", + "AYON_APP_NAME", "AYON_USERNAME", "OPENPYPE_SG_USER", "KITSU_LOGIN", @@ -182,9 +202,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, create_metadata_path(instance, anatomy) environment = { - "AVALON_PROJECT": instance.context.data["projectName"], - "AVALON_ASSET": instance.context.data["asset"], - "AVALON_TASK": instance.context.data["task"], + "AYON_PROJECT_NAME": instance.context.data["projectName"], + "AYON_FOLDER_PATH": instance.context.data["asset"], + "AYON_TASK_NAME": instance.context.data["task"], "AYON_USERNAME": instance.context.data["user"], "AYON_LOG_NO_COLORS": "1", "IS_TEST": str(int(is_in_tests())), @@ -506,17 +526,23 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, self.log.debug("Instance has review explicitly disabled.") do_not_add_review = True + aov_filter = { + item["name"]: item["value"] + for item in self.aov_filter + } if isinstance(instance.data.get("expectedFiles")[0], dict): instances = create_instances_for_aov( instance, instance_skeleton_data, - self.aov_filter, self.skip_integration_repre_list, - do_not_add_review) + aov_filter, + self.skip_integration_repre_list, + do_not_add_review + ) else: representations = prepare_representations( instance_skeleton_data, instance.data.get("expectedFiles"), anatomy, - self.aov_filter, + aov_filter, self.skip_integration_repre_list, do_not_add_review, instance.context, @@ -604,7 +630,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 } diff --git a/client/ayon_core/modules/deadline/repository/custom/plugins/Ayon/Ayon.py b/client/ayon_core/modules/deadline/repository/custom/plugins/Ayon/Ayon.py index a1f752605d..de0a2c6d7a 100644 --- a/client/ayon_core/modules/deadline/repository/custom/plugins/Ayon/Ayon.py +++ b/client/ayon_core/modules/deadline/repository/custom/plugins/Ayon/Ayon.py @@ -15,6 +15,7 @@ import re import os import platform +__version__ = "1.0.0" ###################################################################### # This is the function that Deadline calls to get an instance of the @@ -52,6 +53,9 @@ class AyonDeadlinePlugin(DeadlinePlugin): del self.RenderArgumentCallback def InitializeProcess(self): + self.LogInfo( + "Initializing process with AYON plugin {}".format(__version__) + ) self.PluginType = PluginType.Simple self.StdoutHandling = True diff --git a/client/ayon_core/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py b/client/ayon_core/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py index 81aab00b93..1565b2c496 100644 --- a/client/ayon_core/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py +++ b/client/ayon_core/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py @@ -14,7 +14,7 @@ from Deadline.Scripting import ( DirectoryUtils, ProcessUtils, ) - +__version__ = "1.0.1" VERSION_REGEX = re.compile( r"(?P0|[1-9]\d*)" r"\.(?P0|[1-9]\d*)" @@ -471,12 +471,21 @@ def inject_ayon_environment(deadlinePlugin): ] add_kwargs = { - "project": job.GetJobEnvironmentKeyValue("AVALON_PROJECT"), - "asset": job.GetJobEnvironmentKeyValue("AVALON_ASSET"), - "task": job.GetJobEnvironmentKeyValue("AVALON_TASK"), - "app": job.GetJobEnvironmentKeyValue("AVALON_APP_NAME"), "envgroup": "farm", } + # Support backwards compatible keys + for key, env_keys in ( + ("project", ["AYON_PROJECT_NAME", "AVALON_PROJECT"]), + ("asset", ["AYON_FOLDER_PATH", "AVALON_ASSET"]), + ("task", ["AYON_TASK_NAME", "AVALON_TASK"]), + ("app", ["AYON_APP_NAME", "AVALON_APP_NAME"]), + ): + value = "" + for env_key in env_keys: + value = job.GetJobEnvironmentKeyValue(env_key) + if value: + break + add_kwargs[key] = value if job.GetJobEnvironmentKeyValue("IS_TEST"): args.append("--automatic-tests") @@ -486,8 +495,8 @@ def inject_ayon_environment(deadlinePlugin): args.extend(["--{}".format(key), value]) else: raise RuntimeError(( - "Missing required env vars: AVALON_PROJECT, AVALON_ASSET," - " AVALON_TASK, AVALON_APP_NAME" + "Missing required env vars: AYON_PROJECT_NAME," + " AYON_FOLDER_PATH, AYON_TASK_NAME, AYON_APP_NAME" )) environment = { @@ -593,7 +602,7 @@ def inject_render_job_id(deadlinePlugin): def __main__(deadlinePlugin): - print("*** GlobalJobPreload start ...") + print("*** GlobalJobPreload {} start ...".format(__version__)) print(">>> Getting job ...") job = deadlinePlugin.GetJob() diff --git a/client/ayon_core/modules/royalrender/lib.py b/client/ayon_core/modules/royalrender/lib.py index d985a39d24..5343a5f06f 100644 --- a/client/ayon_core/modules/royalrender/lib.py +++ b/client/ayon_core/modules/royalrender/lib.py @@ -10,7 +10,12 @@ from datetime import datetime import pyblish.api -from ayon_core.lib import BoolDef, NumberDef, is_running_from_build +from ayon_core.lib import ( + BoolDef, + NumberDef, + is_running_from_build, + is_in_tests, +) from ayon_core.lib.execute import run_ayon_launcher_process from ayon_core.modules.royalrender.api import Api as rrApi from ayon_core.modules.royalrender.rr_job import ( @@ -22,7 +27,6 @@ from ayon_core.modules.royalrender.rr_job import ( from ayon_core.pipeline import AYONPyblishPluginMixin from ayon_core.pipeline.publish import KnownPublishError from ayon_core.pipeline.publish.lib import get_published_workfile_instance -from ayon_core.tests.lib import is_in_tests class BaseCreateRoyalRenderJob(pyblish.api.InstancePlugin, @@ -357,8 +361,8 @@ class BaseCreateRoyalRenderJob(pyblish.api.InstancePlugin, if not all(add_kwargs.values()): raise RuntimeError(( - "Missing required env vars: AVALON_PROJECT, AVALON_ASSET," - " AVALON_TASK, AVALON_APP_NAME" + "Missing required env vars: AYON_PROJECT_NAME, AYON_FOLDER_PATH," + " AYON_TASK_NAME, AYON_APP_NAME" )) for key, value in add_kwargs.items(): diff --git a/client/ayon_core/modules/royalrender/plugins/publish/collect_sequences_from_job.py b/client/ayon_core/modules/royalrender/plugins/publish/collect_sequences_from_job.py index a253a1ec5b..cd34ba9bb3 100644 --- a/client/ayon_core/modules/royalrender/plugins/publish/collect_sequences_from_job.py +++ b/client/ayon_core/modules/royalrender/plugins/publish/collect_sequences_from_job.py @@ -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: diff --git a/client/ayon_core/modules/royalrender/plugins/publish/create_publish_royalrender_job.py b/client/ayon_core/modules/royalrender/plugins/publish/create_publish_royalrender_job.py index 680795a329..910abfcb15 100644 --- a/client/ayon_core/modules/royalrender/plugins/publish/create_publish_royalrender_job.py +++ b/client/ayon_core/modules/royalrender/plugins/publish/create_publish_royalrender_job.py @@ -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, @@ -66,7 +63,7 @@ class CreatePublishRoyalRenderJob(pyblish.api.InstancePlugin, "FTRACK_API_USER", "FTRACK_API_KEY", "FTRACK_SERVER", - "AVALON_APP_NAME", + "AYON_APP_NAME", "AYON_USERNAME", "OPENPYPE_SG_USER", ] @@ -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 } @@ -183,9 +179,9 @@ class CreatePublishRoyalRenderJob(pyblish.api.InstancePlugin, anatomy_data = instance.context.data["anatomyData"] environment = RREnvList({ - "AVALON_PROJECT": anatomy_data["project"]["name"], - "AVALON_ASSET": instance.context.data["asset"], - "AVALON_TASK": anatomy_data["task"]["name"], + "AYON_PROJECT_NAME": anatomy_data["project"]["name"], + "AYON_FOLDER_PATH": instance.context.data["asset"], + "AYON_TASK_NAME": anatomy_data["task"]["name"], "AYON_USERNAME": anatomy_data["user"] }) diff --git a/client/ayon_core/modules/royalrender/rr_root/plugins/control_job/perjob/m50__openpype_publish_render.py b/client/ayon_core/modules/royalrender/rr_root/plugins/control_job/perjob/m50__openpype_publish_render.py index 7118c5ebef..778052778f 100644 --- a/client/ayon_core/modules/royalrender/rr_root/plugins/control_job/perjob/m50__openpype_publish_render.py +++ b/client/ayon_core/modules/royalrender/rr_root/plugins/control_job/perjob/m50__openpype_publish_render.py @@ -136,10 +136,10 @@ class OpenPypeContextSelector: def run_publish(self): """Run publish process.""" - env = {"AVALON_PROJECT": str(self.context.get("project")), - "AVALON_ASSET": str(self.context.get("asset")), - "AVALON_TASK": str(self.context.get("task")), - # "AVALON_APP_NAME": str(self.context.get("app_name")) + env = {"AYON_PROJECT_NAME": str(self.context.get("project")), + "AYON_FOLDER_PATH": str(self.context.get("asset")), + "AYON_TASK_NAME": str(self.context.get("task")), + # "AYON_APP_NAME": str(self.context.get("app_name")) } print(">>> setting environment:") @@ -182,10 +182,18 @@ print("running selector") selector = OpenPypeContextSelector() # try to set context from environment -selector.context["project"] = os.getenv("AVALON_PROJECT") -selector.context["asset"] = os.getenv("AVALON_ASSET") -selector.context["task"] = os.getenv("AVALON_TASK") -# selector.context["app_name"] = os.getenv("AVALON_APP_NAME") +for key, env_keys in ( + ("project", ["AYON_PROJECT_NAME", "AVALON_PROJECT"]), + ("asset", ["AYON_FOLDER_PATH", "AVALON_ASSET"]), + ("task", ["AYON_TASK_NAME", "AVALON_TASK"]), + # ("app_name", ["AYON_APP_NAME", "AVALON_APP_NAME"]) +): + value = "" + for env_key in env_keys: + value = os.getenv(env_key) + if value: + break + selector.context[key] = value # if anything inside is None, scratch the whole thing and # ask user for context. diff --git a/client/ayon_core/modules/timers_manager/timers_manager.py b/client/ayon_core/modules/timers_manager/timers_manager.py index daba0cead9..f8f0e4a526 100644 --- a/client/ayon_core/modules/timers_manager/timers_manager.py +++ b/client/ayon_core/modules/timers_manager/timers_manager.py @@ -3,8 +3,8 @@ import platform from ayon_core.client import get_asset_by_name -from ayon_core.modules import ( - OpenPypeModule, +from ayon_core.addon import ( + AYONAddon, ITrayService, IPluginPaths ) @@ -76,7 +76,7 @@ class ExampleTimersManagerConnector: class TimersManager( - OpenPypeModule, + AYONAddon, ITrayService, IPluginPaths ): @@ -99,23 +99,27 @@ class TimersManager( "start_timer" ) - def initialize(self, modules_settings): - timers_settings = modules_settings[self.name] + def initialize(self, studio_settings): + timers_settings = studio_settings.get(self.name) + enabled = timers_settings is not None - self.enabled = timers_settings["enabled"] + auto_stop = False + full_time = 0 + message_time = 0 + if enabled: + # When timer will stop if idle manager is running (minutes) + full_time = int(timers_settings["full_time"] * 60) + # How many minutes before the timer is stopped will popup the message + message_time = int(timers_settings["message_time"] * 60) - # When timer will stop if idle manager is running (minutes) - full_time = int(timers_settings["full_time"] * 60) - # How many minutes before the timer is stopped will popup the message - message_time = int(timers_settings["message_time"] * 60) - - auto_stop = timers_settings["auto_stop"] - platform_name = platform.system().lower() - # Turn of auto stop on MacOs because pynput requires root permissions - # and on linux can cause thread locks on application close - if full_time <= 0 or platform_name in ("darwin", "linux"): - auto_stop = False + auto_stop = timers_settings["auto_stop"] + platform_name = platform.system().lower() + # Turn of auto stop on MacOs because pynput requires root permissions + # and on linux can cause thread locks on application close + if full_time <= 0 or platform_name in ("darwin", "linux"): + auto_stop = False + self.enabled = enabled self.auto_stop = auto_stop self.time_show_message = full_time - message_time self.time_stop_timer = full_time diff --git a/client/ayon_core/pipeline/actions.py b/client/ayon_core/pipeline/actions.py index 1701498d10..8e0ce7e583 100644 --- a/client/ayon_core/pipeline/actions.py +++ b/client/ayon_core/pipeline/actions.py @@ -26,7 +26,7 @@ class LauncherAction(object): Args: session (dict[str, Union[str, None]]): Session data with - AVALON_PROJECT, AVALON_ASSET and AVALON_TASK. + AYON_PROJECT_NAME, AYON_FOLDER_PATH and AYON_TASK_NAME. """ return True diff --git a/client/ayon_core/pipeline/anatomy.py b/client/ayon_core/pipeline/anatomy.py index 86b7d92309..4864822aa1 100644 --- a/client/ayon_core/pipeline/anatomy.py +++ b/client/ayon_core/pipeline/anatomy.py @@ -423,7 +423,7 @@ class Anatomy(BaseAnatomy): def __init__(self, project_name=None, site_name=None): if not project_name: - project_name = os.environ.get("AVALON_PROJECT") + project_name = os.environ.get("AYON_PROJECT_NAME") if not project_name: raise ProjectNotSet(( diff --git a/client/ayon_core/pipeline/colorspace.py b/client/ayon_core/pipeline/colorspace.py index d77f301498..1b795e1c39 100644 --- a/client/ayon_core/pipeline/colorspace.py +++ b/client/ayon_core/pipeline/colorspace.py @@ -254,7 +254,7 @@ def get_imageio_file_rules_colorspace_from_filepath( # match file rule from path colorspace_name = None - for file_rule in file_rules.values(): + for file_rule in file_rules: pattern = file_rule["pattern"] extension = file_rule["ext"] ext_match = re.match( @@ -281,7 +281,7 @@ def get_config_file_rules_colorspace_from_filepath(config_path, filepath): filepath (str): path leading to a file Returns: - Any[str, None]: matching colorspace name + Union[str, None]: matching colorspace name """ if not compatibility_check(): # python environment is not compatible with PyOpenColorIO @@ -918,28 +918,13 @@ def get_imageio_file_rules(project_name, host_name, project_settings=None): Defaults to None. Returns: - dict: file rules data + list[dict[str, Any]]: file rules data """ project_settings = project_settings or get_project_settings(project_name) imageio_global, imageio_host = _get_imageio_settings( project_settings, host_name) - # get file rules from global and host_name - frules_global = imageio_global["file_rules"] - activate_global_rules = ( - frules_global.get("activate_global_file_rules", False) - # TODO: remove this in future - backward compatibility - or frules_global.get("enabled") - ) - global_rules = frules_global["rules"] - - if not activate_global_rules: - log.info( - "Colorspace global file rules are disabled." - ) - global_rules = {} - # host is optional, some might not have any settings frules_host = imageio_host.get("file_rules", {}) @@ -949,8 +934,24 @@ def get_imageio_file_rules(project_name, host_name, project_settings=None): # TODO: remove this in future - backward compatibility activate_host_rules = frules_host.get("enabled", False) - # return host rules if activated or global rules - return frules_host["rules"] if activate_host_rules else global_rules + if activate_host_rules: + return frules_host["rules"] + + # get file rules from global and host_name + frules_global = imageio_global["file_rules"] + activate_global_rules = ( + frules_global.get("activate_global_file_rules", False) + # TODO: remove this in future - backward compatibility + or frules_global.get("enabled") + ) + + if not activate_global_rules: + log.info( + "Colorspace global file rules are disabled." + ) + return [] + + return frules_global["rules"] def get_remapped_colorspace_to_native( diff --git a/client/ayon_core/pipeline/context_tools.py b/client/ayon_core/pipeline/context_tools.py index 197b1eb6e6..730852ff4a 100644 --- a/client/ayon_core/pipeline/context_tools.py +++ b/client/ayon_core/pipeline/context_tools.py @@ -1,7 +1,6 @@ """Core pipeline functionality""" import os -import json import types import logging import platform @@ -20,20 +19,20 @@ from ayon_core.client import ( get_asset_name_identifier, get_ayon_server_api_connection, ) +from ayon_core.lib import is_in_tests from ayon_core.lib.events import emit_event from ayon_core.addon import load_addons, AddonsManager from ayon_core.settings import get_project_settings -from ayon_core.tests.lib import is_in_tests 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("AYON_PROJECT_NAME") + # WARNING: This might be an issue + # - commented out because 'traypublisher' does not have set project + # if not project_name: + # raise ValueError( + # "AYON_PROJECT_NAME 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") + host_name = os.environ.get("AYON_HOST_NAME") # 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): @@ -179,7 +172,7 @@ def install_ayon_plugins(project_name=None, host_name=None): register_inventory_action_path(INVENTORY_PATH) if host_name is None: - host_name = os.environ.get("AVALON_APP") + host_name = os.environ.get("AYON_HOST_NAME") addons_manager = _get_addons_manager() publish_plugin_dirs = addons_manager.collect_publish_plugin_paths( @@ -203,7 +196,7 @@ def install_ayon_plugins(project_name=None, host_name=None): register_inventory_action_path(path) if project_name is None: - project_name = os.environ.get("AVALON_PROJECT") + project_name = os.environ.get("AYON_PROJECT_NAME") # Register studio specific plugins if project_name: @@ -256,8 +249,6 @@ def uninstall_host(): deregister_host() - legacy_io.uninstall() - log.info("Successfully uninstalled Avalon!") @@ -340,7 +331,7 @@ def get_current_host_name(): """Current host name. Function is based on currently registered host integration or environment - variable 'AVALON_APP'. + variable 'AYON_HOST_NAME'. Returns: Union[str, None]: Name of host integration in current process or None. @@ -349,7 +340,7 @@ def get_current_host_name(): host = registered_host() if isinstance(host, HostBase): return host.name - return os.environ.get("AVALON_APP") + return os.environ.get("AYON_HOST_NAME") def get_global_context(): @@ -374,9 +365,9 @@ def get_global_context(): """ return { - "project_name": os.environ.get("AVALON_PROJECT"), - "asset_name": os.environ.get("AVALON_ASSET"), - "task_name": os.environ.get("AVALON_TASK"), + "project_name": os.environ.get("AYON_PROJECT_NAME"), + "asset_name": os.environ.get("AYON_FOLDER_PATH"), + "task_name": os.environ.get("AYON_TASK_NAME"), } @@ -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["AYON_PROJECT_NAME"] + asset_name = session["AYON_FOLDER_PATH"] + task_name = session["AYON_TASK_NAME"] + host_name = session["AYON_HOST_NAME"] + 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["AYON_PROJECT_NAME"] + host_name = session["AYON_HOST_NAME"] + 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["AYON_PROJECT_NAME"] + asset_name = session["AYON_FOLDER_PATH"] + task_name = session["AYON_TASK_NAME"] + host_name = session["AYON_HOST_NAME"] + 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 = { + "AYON_PROJECT_NAME": project_name, + "AYON_FOLDER_PATH": folder_path, + "AYON_TASK_NAME": task_name, + "AYON_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(): diff --git a/client/ayon_core/pipeline/create/context.py b/client/ayon_core/pipeline/create/context.py index 8990d50324..b973c45097 100644 --- a/client/ayon_core/pipeline/create/context.py +++ b/client/ayon_core/pipeline/create/context.py @@ -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 ( @@ -1536,7 +1536,7 @@ class CreateContext: def host_name(self): if hasattr(self.host, "name"): return self.host.name - return os.environ["AVALON_APP"] + return os.environ["AYON_HOST_NAME"] def get_current_project_name(self): """Project name which was used as current context on context reset. @@ -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 diff --git a/client/ayon_core/pipeline/create/legacy_create.py b/client/ayon_core/pipeline/create/legacy_create.py index 08be32eed4..aab6b67e6f 100644 --- a/client/ayon_core/pipeline/create/legacy_create.py +++ b/client/ayon_core/pipeline/create/legacy_create.py @@ -45,7 +45,7 @@ class LegacyCreator(object): def apply_settings(cls, project_settings, system_settings): """Apply OpenPype settings to a plugin class.""" - host_name = os.environ.get("AVALON_APP") + host_name = os.environ.get("AYON_HOST_NAME") plugin_type = "create" plugin_type_settings = ( project_settings diff --git a/client/ayon_core/pipeline/create/subset_name.py b/client/ayon_core/pipeline/create/subset_name.py index 3892971ce8..93a61b8b8b 100644 --- a/client/ayon_core/pipeline/create/subset_name.py +++ b/client/ayon_core/pipeline/create/subset_name.py @@ -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 @@ -129,13 +128,13 @@ def get_subset_name( return "" if not host_name: - host_name = os.environ.get("AVALON_APP") + host_name = os.environ.get("AYON_HOST_NAME") # Use only last part of class family value split by dot (`.`) family = family.rsplit(".", 1)[-1] if project_name is None: - project_name = legacy_io.Session["AVALON_PROJECT"] + project_name = os.environ.get("AYON_PROJECT_NAME") asset_tasks = asset_doc.get("data", {}).get("tasks") or {} task_info = asset_tasks.get(task_name) or {} diff --git a/client/ayon_core/pipeline/farm/pyblish_functions.py b/client/ayon_core/pipeline/farm/pyblish_functions.py index 9423d8501c..389d3d27ed 100644 --- a/client/ayon_core/pipeline/farm/pyblish_functions.py +++ b/client/ayon_core/pipeline/farm/pyblish_functions.py @@ -321,7 +321,7 @@ def prepare_representations(skeleton_data, exp_files, anatomy, aov_filter, """ representations = [] - host_name = os.environ.get("AVALON_APP", "") + host_name = os.environ.get("AYON_HOST_NAME", "") collections, remainders = clique.assemble(exp_files) log = Logger.get_logger("farm_publishing") @@ -541,7 +541,7 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, """ # TODO: this needs to be taking the task from context or instance - task = os.environ["AVALON_TASK"] + task = os.environ["AYON_TASK_NAME"] anatomy = instance.context.data["anatomy"] subset = skeleton["subset"] @@ -611,7 +611,7 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data, log.info("Creating data for: {}".format(subset_name)) - app = os.environ.get("AVALON_APP", "") + app = os.environ.get("AYON_HOST_NAME", "") if isinstance(col, list): render_file_name = os.path.basename(col[0]) diff --git a/client/ayon_core/pipeline/legacy_io.py b/client/ayon_core/pipeline/legacy_io.py index cd09da2917..d5b555845b 100644 --- a/client/ayon_core/pipeline/legacy_io.py +++ b/client/ayon_core/pipeline/legacy_io.py @@ -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() diff --git a/client/ayon_core/pipeline/load/plugins.py b/client/ayon_core/pipeline/load/plugins.py index e13260d296..1d4627689f 100644 --- a/client/ayon_core/pipeline/load/plugins.py +++ b/client/ayon_core/pipeline/load/plugins.py @@ -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, @@ -41,7 +38,7 @@ class LoaderPlugin(list): @classmethod def apply_settings(cls, project_settings, system_settings): - host_name = os.environ.get("AVALON_APP") + host_name = os.environ.get("AYON_HOST_NAME") plugin_type = "load" plugin_type_settings = ( project_settings diff --git a/client/ayon_core/pipeline/publish/lib.py b/client/ayon_core/pipeline/publish/lib.py index 47f4be9e69..a62c2d9c5b 100644 --- a/client/ayon_core/pipeline/publish/lib.py +++ b/client/ayon_core/pipeline/publish/lib.py @@ -437,7 +437,7 @@ def filter_pyblish_plugins(plugins): # - kept becau on farm is probably used host 'shell' which propably # affect how settings are applied there host_name = pyblish.api.current_host() - project_name = os.environ.get("AVALON_PROJECT") + project_name = os.environ.get("AYON_PROJECT_NAME") project_settings = get_project_settings(project_name) system_settings = get_system_settings() diff --git a/client/ayon_core/pipeline/template_data.py b/client/ayon_core/pipeline/template_data.py index a1b944a431..a5ca84c754 100644 --- a/client/ayon_core/pipeline/template_data.py +++ b/client/ayon_core/pipeline/template_data.py @@ -17,12 +17,11 @@ def get_general_template_data(system_settings=None): if not system_settings: system_settings = get_system_settings() - studio_name = system_settings["general"]["studio_name"] - studio_code = system_settings["general"]["studio_code"] + core_settings = system_settings["core"] return { "studio": { - "name": studio_name, - "code": studio_code + "name": core_settings["studio_name"], + "code": core_settings["studio_code"] }, "user": get_ayon_username() } diff --git a/client/ayon_core/pipeline/workfile/build_workfile.py b/client/ayon_core/pipeline/workfile/build_workfile.py index c62facaaa9..3a0d9ba95e 100644 --- a/client/ayon_core/pipeline/workfile/build_workfile.py +++ b/client/ayon_core/pipeline/workfile/build_workfile.py @@ -229,8 +229,8 @@ class BuildWorkfile: def get_build_presets(self, task_name, asset_doc): """ Returns presets to build workfile for task name. - Presets are loaded for current project set in - io.Session["AVALON_PROJECT"], filtered by registered host + Presets are loaded for current project received by + 'get_current_project_name', filtered by registered host and entered task name. Args: @@ -321,7 +321,7 @@ class BuildWorkfile: continue # Check families - profile_families = profile.get("families") + profile_families = profile.get("product_types") if not profile_families: self.log.warning(( "Build profile is missing families configuration: {0}" @@ -338,7 +338,7 @@ class BuildWorkfile: continue # Prepare lowered families and representation names - profile["families_lowered"] = [ + profile["product_types_lowered"] = [ fam.lower() for fam in profile_families ] profile["repre_names_lowered"] = [ @@ -375,11 +375,11 @@ class BuildWorkfile: family_low = family.lower() for profile in profiles: # Skip profile if does not contain family - if family_low not in profile["families_lowered"]: + if family_low not in profile["product_types_lowered"]: continue # Precompile name filters as regexes - profile_regexes = profile.get("subset_name_filters") + profile_regexes = profile.get("product_name_filters") if profile_regexes: _profile_regexes = [] for regex in profile_regexes: @@ -538,7 +538,7 @@ class BuildWorkfile: build_presets += self.build_presets.get("linked_assets", []) subset_ids_ordered = [] for preset in build_presets: - for preset_family in preset["families"]: + for preset_family in preset["product_types"]: for id, subset in subsets_by_id.items(): if preset_family not in subset["data"].get("families", []): continue diff --git a/client/ayon_core/pipeline/workfile/path_resolving.py b/client/ayon_core/pipeline/workfile/path_resolving.py index 95a0a03c60..2062705d3c 100644 --- a/client/ayon_core/pipeline/workfile/path_resolving.py +++ b/client/ayon_core/pipeline/workfile/path_resolving.py @@ -157,7 +157,7 @@ def get_workdir( task_name (str): Task name for which are workdir data preapred. host_name (str): Host which is used to workdir. This is required because workdir template may contain `{app}` key. In `Session` - is stored under `AVALON_APP` key. + is stored under `AYON_HOST_NAME` key. anatomy (Anatomy): Optional argument. Anatomy object is created using project name from `project_doc`. It is preferred to pass this argument as initialization of a new Anatomy object may be time diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index 1afe26813f..3004e55861 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -103,7 +103,7 @@ class AbstractTemplateBuilder(object): if isinstance(host, HostBase): host_name = host.name else: - host_name = os.environ.get("AVALON_APP") + host_name = os.environ.get("AYON_HOST_NAME") self._host = host self._host_name = host_name @@ -129,19 +129,19 @@ class AbstractTemplateBuilder(object): def project_name(self): if isinstance(self._host, HostBase): return self._host.get_current_project_name() - return os.getenv("AVALON_PROJECT") + return os.getenv("AYON_PROJECT_NAME") @property def current_asset_name(self): if isinstance(self._host, HostBase): return self._host.get_current_asset_name() - return os.getenv("AVALON_ASSET") + return os.getenv("AYON_FOLDER_PATH") @property def current_task_name(self): if isinstance(self._host, HostBase): return self._host.get_current_task_name() - return os.getenv("AVALON_TASK") + return os.getenv("AYON_TASK_NAME") def get_current_context(self): if isinstance(self._host, HostBase): @@ -553,6 +553,12 @@ class AbstractTemplateBuilder(object): self.clear_shared_populate_data() + def open_template(self): + """Open template file with registered host.""" + template_preset = self.get_template_preset() + template_path = template_preset["path"] + self.host.open_file(template_path) + @abstractmethod def import_template(self, template_path): """ @@ -579,7 +585,7 @@ class AbstractTemplateBuilder(object): template_path (str): Fullpath for current task and host's template file. """ - last_workfile_path = os.environ.get("AVALON_LAST_WORKFILE") + last_workfile_path = os.environ.get("AYON_LAST_WORKFILE") self.log.info("__ last_workfile_path: {}".format(last_workfile_path)) if os.path.exists(last_workfile_path): # ignore in case workfile existence diff --git a/client/ayon_core/plugins/actions/open_file_explorer.py b/client/ayon_core/plugins/actions/open_file_explorer.py index b29ed30258..fba3c231a5 100644 --- a/client/ayon_core/plugins/actions/open_file_explorer.py +++ b/client/ayon_core/plugins/actions/open_file_explorer.py @@ -22,14 +22,14 @@ class OpenTaskPath(LauncherAction): def is_compatible(self, session): """Return whether the action is compatible with the session""" - return bool(session.get("AVALON_ASSET")) + return bool(session.get("AYON_FOLDER_PATH")) def process(self, session, **kwargs): from qtpy import QtCore, QtWidgets - project_name = session["AVALON_PROJECT"] - asset_name = session["AVALON_ASSET"] - task_name = session.get("AVALON_TASK", None) + project_name = session["AYON_PROJECT_NAME"] + asset_name = session["AYON_FOLDER_PATH"] + task_name = session.get("AYON_TASK_NAME", None) path = self._get_workdir(project_name, asset_name, task_name) if not path: diff --git a/client/ayon_core/plugins/load/delete_old_versions.py b/client/ayon_core/plugins/load/delete_old_versions.py index 6b3263e2b6..956b232e2b 100644 --- a/client/ayon_core/plugins/load/delete_old_versions.py +++ b/client/ayon_core/plugins/load/delete_old_versions.py @@ -359,7 +359,7 @@ # # if mongo_changes_bulk: # dbcon = AvalonMongoDB() -# dbcon.Session["AVALON_PROJECT"] = project_name +# dbcon.Session["AYON_PROJECT_NAME"] = project_name # dbcon.install() # dbcon.bulk_write(mongo_changes_bulk) # dbcon.uninstall() diff --git a/client/ayon_core/plugins/publish/cleanup.py b/client/ayon_core/plugins/publish/cleanup.py index 7bed3269c2..df68af7e57 100644 --- a/client/ayon_core/plugins/publish/cleanup.py +++ b/client/ayon_core/plugins/publish/cleanup.py @@ -5,7 +5,7 @@ import shutil import pyblish.api import re -from ayon_core.tests.lib import is_in_tests +from ayon_core.lib import is_in_tests class CleanUp(pyblish.api.InstancePlugin): diff --git a/client/ayon_core/plugins/publish/collect_anatomy_context_data.py b/client/ayon_core/plugins/publish/collect_anatomy_context_data.py index 978ae5e1e1..fbdf26e76c 100644 --- a/client/ayon_core/plugins/publish/collect_anatomy_context_data.py +++ b/client/ayon_core/plugins/publish/collect_anatomy_context_data.py @@ -4,9 +4,9 @@ Requires: context -> anatomy context -> projectEntity context -> assetEntity + context -> task context -> username context -> datetimeData - session -> AVALON_TASK Provides: context -> anatomyData diff --git a/client/ayon_core/plugins/publish/collect_audio.py b/client/ayon_core/plugins/publish/collect_audio.py index 94477e5578..6da3fd0685 100644 --- a/client/ayon_core/plugins/publish/collect_audio.py +++ b/client/ayon_core/plugins/publish/collect_audio.py @@ -43,7 +43,7 @@ class CollectAudio(pyblish.api.ContextPlugin): "unreal" ] - audio_subset_name = "audioMain" + audio_product_name = "audioMain" def process(self, context): # Fake filtering by family inside context plugin @@ -71,9 +71,9 @@ class CollectAudio(pyblish.api.ContextPlugin): asset_names = set(instances_by_asset_name.keys()) self.log.debug(( - "Searching for audio subset '{subset}' in assets {assets}" + "Searching for audio product '{subset}' in assets {assets}" ).format( - subset=self.audio_subset_name, + subset=self.audio_product_name, assets=", ".join([ '"{}"'.format(asset_name) for asset_name in asset_names @@ -130,11 +130,11 @@ class CollectAudio(pyblish.api.ContextPlugin): } asset_ids = set(asset_id_by_name.values()) - # Query subsets with name define by 'audio_subset_name' attr + # Query subsets with name define by 'audio_product_name' attr # - one or none subsets with the name should be available on an asset subset_docs = get_subsets( project_name, - subset_names=[self.audio_subset_name], + subset_names=[self.audio_product_name], asset_ids=asset_ids, fields=["_id", "parent"] ) diff --git a/client/ayon_core/plugins/publish/collect_context_entities.py b/client/ayon_core/plugins/publish/collect_context_entities.py index 8480435e21..30bb184ef5 100644 --- a/client/ayon_core/plugins/publish/collect_context_entities.py +++ b/client/ayon_core/plugins/publish/collect_context_entities.py @@ -1,7 +1,6 @@ """Collect Anatomy and global anatomy data. Requires: - session -> AVALON_ASSET context -> projectName context -> asset context -> task diff --git a/client/ayon_core/plugins/publish/collect_from_create_context.py b/client/ayon_core/plugins/publish/collect_from_create_context.py index d38138b2e9..66ca5745b2 100644 --- a/client/ayon_core/plugins/publish/collect_from_create_context.py +++ b/client/ayon_core/plugins/publish/collect_from_create_context.py @@ -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 @@ -57,12 +57,14 @@ class CollectFromCreateContext(pyblish.api.ContextPlugin): asset_name = create_context.get_current_asset_name() task_name = create_context.get_current_task_name() for key, value in ( - ("AVALON_PROJECT", project_name), - ("AVALON_ASSET", asset_name), - ("AVALON_TASK", task_name) + ("AYON_PROJECT_NAME", project_name), + ("AYON_FOLDER_PATH", asset_name), + ("AYON_TASK_NAME", task_name) ): - legacy_io.Session[key] = value - os.environ[key] = value + if value is None: + os.environ.pop(key, None) + else: + os.environ[key] = value def create_instance( self, diff --git a/client/ayon_core/plugins/publish/collect_host_name.py b/client/ayon_core/plugins/publish/collect_host_name.py index 89e4e03c1a..e76579bbd2 100644 --- a/client/ayon_core/plugins/publish/collect_host_name.py +++ b/client/ayon_core/plugins/publish/collect_host_name.py @@ -24,13 +24,13 @@ class CollectHostName(pyblish.api.ContextPlugin): if host_name and app_name and app_label: return - # Use AVALON_APP to get host name if available + # Use AYON_HOST_NAME to get host name if available if not host_name: - host_name = os.environ.get("AVALON_APP") + host_name = os.environ.get("AYON_HOST_NAME") - # Use AVALON_APP_NAME to get full app name + # Use AYON_APP_NAME to get full app name if not app_name: - app_name = os.environ.get("AVALON_APP_NAME") + app_name = os.environ.get("AYON_APP_NAME") # Fill missing values based on app full name if (not host_name or not app_label) and app_name: diff --git a/client/ayon_core/plugins/publish/collect_input_representations_to_versions.py b/client/ayon_core/plugins/publish/collect_input_representations_to_versions.py index b5c9872e74..6caee1be6a 100644 --- a/client/ayon_core/plugins/publish/collect_input_representations_to_versions.py +++ b/client/ayon_core/plugins/publish/collect_input_representations_to_versions.py @@ -1,7 +1,5 @@ import pyblish.api -from bson.objectid import ObjectId - from ayon_core.client import get_representations diff --git a/client/ayon_core/plugins/publish/collect_rendered_files.py b/client/ayon_core/plugins/publish/collect_rendered_files.py index 5ffcd669a0..9a316b69a4 100644 --- a/client/ayon_core/plugins/publish/collect_rendered_files.py +++ b/client/ayon_core/plugins/publish/collect_rendered_files.py @@ -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("AYON_WORKDIR") + remapped_workdir = None + if workdir: + remapped_workdir = anatomy.roots_obj.path_remapper( + os.getenv("AYON_WORKDIR") + ) + if remapped_workdir: + os.environ["AYON_WORKDIR"] = remapped_workdir except Exception as e: self.log.error(e, exc_info=True) raise Exception("Error") from e diff --git a/client/ayon_core/plugins/publish/collect_scene_version.py b/client/ayon_core/plugins/publish/collect_scene_version.py index 254d3c913d..b04900c74e 100644 --- a/client/ayon_core/plugins/publish/collect_scene_version.py +++ b/client/ayon_core/plugins/publish/collect_scene_version.py @@ -1,8 +1,7 @@ import os import pyblish.api -from ayon_core.lib import get_version_from_path -from ayon_core.tests.lib import is_in_tests +from ayon_core.lib import get_version_from_path, is_in_tests from ayon_core.pipeline import KnownPublishError diff --git a/client/ayon_core/plugins/publish/extract_review.py b/client/ayon_core/plugins/publish/extract_review.py index 91d39882ae..b0bc94c317 100644 --- a/client/ayon_core/plugins/publish/extract_review.py +++ b/client/ayon_core/plugins/publish/extract_review.py @@ -74,7 +74,7 @@ class ExtractReview(pyblish.api.InstancePlugin): alpha_exts = ["exr", "png", "dpx"] # Preset attributes - profiles = None + profiles = [] def process(self, instance): self.log.debug(str(instance.data["representations"])) @@ -112,7 +112,7 @@ class ExtractReview(pyblish.api.InstancePlugin): self.profiles, { "hosts": host_name, - "families": family, + "product_types": family, }, logger=self.log) if not profile: @@ -719,12 +719,12 @@ class ExtractReview(pyblish.api.InstancePlugin): lut_filters = self.lut_filters(new_repre, instance, ffmpeg_input_args) ffmpeg_video_filters.extend(lut_filters) - bg_alpha = 0 + bg_alpha = 0.0 bg_color = output_def.get("bg_color") if bg_color: bg_red, bg_green, bg_blue, bg_alpha = bg_color - if bg_alpha > 0: + if bg_alpha > 0.0: if not temp_data["input_allow_bg"]: self.log.info(( "Output definition has defined BG color input was" @@ -734,8 +734,7 @@ class ExtractReview(pyblish.api.InstancePlugin): bg_color_hex = "#{0:0>2X}{1:0>2X}{2:0>2X}".format( bg_red, bg_green, bg_blue ) - bg_color_alpha = float(bg_alpha) / 255 - bg_color_str = "{}@{}".format(bg_color_hex, bg_color_alpha) + bg_color_str = "{}@{}".format(bg_color_hex, bg_alpha) self.log.info("Applying BG color {}".format(bg_color_str)) color_args = [ @@ -1079,7 +1078,7 @@ class ExtractReview(pyblish.api.InstancePlugin): fill_color_hex = "{0:0>2X}{1:0>2X}{2:0>2X}".format( f_red, f_green, f_blue ) - fill_color_alpha = float(f_alpha) / 255 + fill_color_alpha = f_alpha line_thickness = letter_box_def["line_thickness"] line_color = letter_box_def["line_color"] @@ -1087,7 +1086,7 @@ class ExtractReview(pyblish.api.InstancePlugin): line_color_hex = "{0:0>2X}{1:0>2X}{2:0>2X}".format( l_red, l_green, l_blue ) - line_color_alpha = float(l_alpha) / 255 + line_color_alpha = l_alpha # test ratios and define if pillar or letter boxes output_ratio = float(output_width) / float(output_height) @@ -1283,8 +1282,12 @@ class ExtractReview(pyblish.api.InstancePlugin): # NOTE Setting only one of `width` or `heigth` is not allowed # - settings value can't have None but has value of 0 - output_width = output_def.get("width") or output_width or None - output_height = output_def.get("height") or output_height or None + output_width = ( + output_def.get("output_width") or output_width or None + ) + output_height = ( + output_def.get("output_height") or output_height or None + ) # Force to use input resolution if output resolution was not defined # in settings. Resolution from instance is not used when # 'use_input_res' is set to 'True'. @@ -1294,7 +1297,12 @@ class ExtractReview(pyblish.api.InstancePlugin): overscan_color_value = "black" overscan_color = output_def.get("overscan_color") if overscan_color: - bg_red, bg_green, bg_blue, _ = overscan_color + if len(overscan_color) == 3: + bg_red, bg_green, bg_blue = overscan_color + else: + # Backwards compatibility + bg_red, bg_green, bg_blue, _ = overscan_color + overscan_color_value = "#{0:0>2X}{1:0>2X}{2:0>2X}".format( bg_red, bg_green, bg_blue ) @@ -1503,14 +1511,16 @@ class ExtractReview(pyblish.api.InstancePlugin): subset_name (str): name of subset Returns: - list: Containg all output definitions matching entered families. + dict[str, Any]: Containing all output definitions matching entered + families. """ - outputs = profile.get("outputs") or {} - if not outputs: - return outputs - filtered_outputs = {} - for filename_suffix, output_def in outputs.items(): + outputs = profile.get("outputs") + if not outputs: + return filtered_outputs + + for output_def in outputs: + filename_suffix = output_def["name"] output_filters = output_def.get("filter") # If no filter on output preset, skip filtering and add output # profile for farther processing @@ -1523,16 +1533,16 @@ class ExtractReview(pyblish.api.InstancePlugin): continue # Subsets name filters - subset_filters = [ - subset_filter - for subset_filter in output_filters.get("subsets", []) + product_name_filters = [ + name_filter + for name_filter in output_filters.get("product_names", []) # Skip empty strings - if subset_filter + if name_filter ] - if subset_name and subset_filters: + if subset_name and product_name_filters: match = False - for subset_filter in subset_filters: - compiled = re.compile(subset_filter) + for product_name_filter in product_name_filters: + compiled = re.compile(product_name_filter) if compiled.search(subset_name): match = True break diff --git a/client/ayon_core/plugins/publish/integrate.py b/client/ayon_core/plugins/publish/integrate.py index a67c837daf..a502031595 100644 --- a/client/ayon_core/plugins/publish/integrate.py +++ b/client/ayon_core/plugins/publish/integrate.py @@ -6,7 +6,6 @@ import datetime import clique import six -from bson.objectid import ObjectId import pyblish.api from ayon_core.client.operations import ( @@ -988,7 +987,6 @@ class IntegrateAsset(pyblish.api.InstancePlugin): """ return { - "_id": ObjectId(), "path": self.get_rootless_path(anatomy, path), "size": os.path.getsize(path), "hash": source_hash(path), diff --git a/client/ayon_core/scripts/non_python_host_launch.py b/client/ayon_core/scripts/non_python_host_launch.py index 97632e98ad..4c18fd0ccc 100644 --- a/client/ayon_core/scripts/non_python_host_launch.py +++ b/client/ayon_core/scripts/non_python_host_launch.py @@ -79,7 +79,7 @@ def main(argv): if after_script_idx is not None: launch_args = sys_args[after_script_idx:] - host_name = os.environ["AVALON_APP"].lower() + host_name = os.environ["AYON_HOST_NAME"].lower() if host_name == "photoshop": # TODO refactor launch logic according to AE from ayon_core.hosts.photoshop.api.lib import main @@ -90,7 +90,7 @@ def main(argv): else: title = "Unknown host name" message = ( - "BUG: Environment variable AVALON_APP contains unknown" + "BUG: Environment variable AYON_HOST_NAME contains unknown" " host name \"{}\"" ).format(host_name) show_error_messagebox(title, message) diff --git a/client/ayon_core/settings/__init__.py b/client/ayon_core/settings/__init__.py index 51019ca570..f0e5c95efe 100644 --- a/client/ayon_core/settings/__init__.py +++ b/client/ayon_core/settings/__init__.py @@ -4,12 +4,12 @@ from .constants import ( ) from .lib import ( get_general_environments, - get_global_settings, get_system_settings, get_project_settings, get_current_project_settings, get_local_settings, ) +from .ayon_settings import get_ayon_settings __all__ = ( @@ -17,9 +17,10 @@ __all__ = ( "PROJECT_SETTINGS_KEY", "get_general_environments", - "get_global_settings", "get_system_settings", "get_project_settings", "get_current_project_settings", "get_local_settings", + + "get_ayon_settings", ) diff --git a/client/ayon_core/settings/ayon_settings.py b/client/ayon_core/settings/ayon_settings.py index ed1199d517..830cbd5e10 100644 --- a/client/ayon_core/settings/ayon_settings.py +++ b/client/ayon_core/settings/ayon_settings.py @@ -52,172 +52,14 @@ def _convert_color(color_value): return color_value -def _convert_host_imageio(host_settings): - if "imageio" not in host_settings: - return - - # --- imageio --- - ayon_imageio = host_settings["imageio"] - # TODO remove when fixed on server - if "ocio_config" in ayon_imageio["ocio_config"]: - ayon_imageio["ocio_config"]["filepath"] = ( - ayon_imageio["ocio_config"].pop("ocio_config") - ) - # Convert file rules - imageio_file_rules = ayon_imageio["file_rules"] - new_rules = {} - for rule in imageio_file_rules["rules"]: - name = rule.pop("name") - new_rules[name] = rule - imageio_file_rules["rules"] = new_rules - - -def _convert_applications_groups(groups, clear_metadata): - environment_key = "environment" - if isinstance(groups, dict): - new_groups = [] - for name, item in groups.items(): - item["name"] = name - new_groups.append(item) - groups = new_groups - - output = {} - group_dynamic_labels = {} - for group in groups: - group_name = group.pop("name") - if "label" in group: - group_dynamic_labels[group_name] = group["label"] - - tool_group_envs = group[environment_key] - if isinstance(tool_group_envs, six.string_types): - group[environment_key] = json.loads(tool_group_envs) - - variants = {} - variant_dynamic_labels = {} - for variant in group.pop("variants"): - variant_name = variant.pop("name") - label = variant.get("label") - if label and label != variant_name: - variant_dynamic_labels[variant_name] = label - variant_envs = variant[environment_key] - if isinstance(variant_envs, six.string_types): - variant[environment_key] = json.loads(variant_envs) - variants[variant_name] = variant - group["variants"] = variants - - if not clear_metadata: - variants["__dynamic_keys_labels__"] = variant_dynamic_labels - output[group_name] = group - - if not clear_metadata: - output["__dynamic_keys_labels__"] = group_dynamic_labels - return output - - -def _convert_applications_system_settings( - ayon_settings, output, clear_metadata -): - # Addon settings - addon_settings = ayon_settings["applications"] - - # Remove project settings - addon_settings.pop("only_available", None) - - # Applications settings - ayon_apps = addon_settings["applications"] - - additional_apps = ayon_apps.pop("additional_apps") - applications = _convert_applications_groups( - ayon_apps, clear_metadata - ) - applications["additional_apps"] = _convert_applications_groups( - additional_apps, clear_metadata - ) - - # Tools settings - tools = _convert_applications_groups( - addon_settings["tool_groups"], clear_metadata - ) - - output["applications"] = applications - output["tools"] = {"tool_groups": tools} - - def _convert_general(ayon_settings, output, default_settings): - # TODO get studio name/code - core_settings = ayon_settings["core"] - environments = core_settings["environments"] - if isinstance(environments, six.string_types): - environments = json.loads(environments) - - general = default_settings["general"] - general.update({ - "log_to_server": False, - "studio_name": core_settings["studio_name"], - "studio_code": core_settings["studio_code"], - "environment": environments - }) - output["general"] = general - - -def _convert_kitsu_system_settings( - ayon_settings, output, addon_versions, default_settings -): - enabled = addon_versions.get("kitsu") is not None - kitsu_settings = default_settings["modules"]["kitsu"] - kitsu_settings["enabled"] = enabled - if enabled: - kitsu_settings["server"] = ayon_settings["kitsu"]["server"] - output["modules"]["kitsu"] = kitsu_settings - - -def _convert_timers_manager_system_settings( - ayon_settings, output, addon_versions, default_settings -): - enabled = addon_versions.get("timers_manager") is not None - manager_settings = default_settings["modules"]["timers_manager"] - manager_settings["enabled"] = enabled - if enabled: - ayon_manager = ayon_settings["timers_manager"] - manager_settings.update({ - key: ayon_manager[key] - for key in { - "auto_stop", - "full_time", - "message_time", - "disregard_publishing" - } - }) - output["modules"]["timers_manager"] = manager_settings - - -def _convert_clockify_system_settings( - ayon_settings, output, addon_versions, default_settings -): - enabled = addon_versions.get("clockify") is not None - clockify_settings = default_settings["modules"]["clockify"] - clockify_settings["enabled"] = enabled - if enabled: - clockify_settings["workspace_name"] = ( - ayon_settings["clockify"]["workspace_name"] - ) - output["modules"]["clockify"] = clockify_settings - - -def _convert_deadline_system_settings( - ayon_settings, output, addon_versions, default_settings -): - enabled = addon_versions.get("deadline") is not None - deadline_settings = default_settings["modules"]["deadline"] - deadline_settings["enabled"] = enabled - if enabled: - ayon_deadline = ayon_settings["deadline"] - deadline_settings["deadline_urls"] = { - item["name"]: item["value"] - for item in ayon_deadline["deadline_urls"] - } - - output["modules"]["deadline"] = deadline_settings + output["core"] = ayon_settings["core"] + version_check_interval = ( + default_settings["general"]["version_check_interval"] + ) + output["general"] = { + "version_check_interval": version_check_interval + } def _convert_royalrender_system_settings( @@ -241,22 +83,24 @@ def _convert_modules_system( # TODO add all modules # TODO add 'enabled' values for func in ( - _convert_kitsu_system_settings, - _convert_timers_manager_system_settings, - _convert_clockify_system_settings, - _convert_deadline_system_settings, _convert_royalrender_system_settings, ): func(ayon_settings, output, addon_versions, default_settings) + for key in { + "timers_manager", + "clockify", + "deadline", + }: + if addon_versions.get(key): + output[key] = ayon_settings + else: + output.pop(key, None) + modules_settings = output["modules"] for module_name in ( "sync_server", - "log_viewer", - "standalonepublish_tool", - "project_manager", "job_queue", - "avalon", "addon_paths", ): settings = default_settings["modules"][module_name] @@ -289,9 +133,6 @@ def convert_system_settings(ayon_settings, default_settings, addon_versions): output = { "modules": {} } - if "applications" in ayon_settings: - _convert_applications_system_settings(ayon_settings, output, False) - if "core" in ayon_settings: _convert_general(ayon_settings, output, default_settings) @@ -313,349 +154,6 @@ def convert_system_settings(ayon_settings, default_settings, addon_versions): # --------- Project settings --------- -def _convert_applications_project_settings(ayon_settings, output): - if "applications" not in ayon_settings: - return - - output["applications"] = { - "only_available": ayon_settings["applications"]["only_available"] - } - - -def _convert_blender_project_settings(ayon_settings, output): - if "blender" not in ayon_settings: - return - ayon_blender = ayon_settings["blender"] - _convert_host_imageio(ayon_blender) - - ayon_publish = ayon_blender["publish"] - - for plugin in ("ExtractThumbnail", "ExtractPlayblast"): - plugin_settings = ayon_publish[plugin] - plugin_settings["presets"] = json.loads(plugin_settings["presets"]) - - output["blender"] = ayon_blender - - -def _convert_celaction_project_settings(ayon_settings, output): - if "celaction" not in ayon_settings: - return - - ayon_celaction = ayon_settings["celaction"] - _convert_host_imageio(ayon_celaction) - - output["celaction"] = ayon_celaction - - -def _convert_flame_project_settings(ayon_settings, output): - if "flame" not in ayon_settings: - return - - ayon_flame = ayon_settings["flame"] - - ayon_publish_flame = ayon_flame["publish"] - # Plugin 'ExtractSubsetResources' renamed to 'ExtractProductResources' - if "ExtractSubsetResources" in ayon_publish_flame: - ayon_product_resources = ayon_publish_flame["ExtractSubsetResources"] - else: - ayon_product_resources = ( - ayon_publish_flame.pop("ExtractProductResources")) - ayon_publish_flame["ExtractSubsetResources"] = ayon_product_resources - - # 'ExtractSubsetResources' changed model of 'export_presets_mapping' - # - some keys were moved under 'other_parameters' - new_subset_resources = {} - for item in ayon_product_resources.pop("export_presets_mapping"): - name = item.pop("name") - if "other_parameters" in item: - other_parameters = item.pop("other_parameters") - item.update(other_parameters) - new_subset_resources[name] = item - - ayon_product_resources["export_presets_mapping"] = new_subset_resources - - # 'imageio' changed model - # - missing subkey 'project' which is in root of 'imageio' model - _convert_host_imageio(ayon_flame) - ayon_imageio_flame = ayon_flame["imageio"] - if "project" not in ayon_imageio_flame: - profile_mapping = ayon_imageio_flame.pop("profilesMapping") - ayon_flame["imageio"] = { - "project": ayon_imageio_flame, - "profilesMapping": profile_mapping - } - - ayon_load_flame = ayon_flame["load"] - for plugin_name in ("LoadClip", "LoadClipBatch"): - plugin_settings = ayon_load_flame[plugin_name] - plugin_settings["families"] = plugin_settings.pop("product_types") - plugin_settings["clip_name_template"] = ( - plugin_settings["clip_name_template"] - .replace("{folder[name]}", "{asset}") - .replace("{product[name]}", "{subset}") - ) - plugin_settings["layer_rename_template"] = ( - plugin_settings["layer_rename_template"] - .replace("{folder[name]}", "{asset}") - .replace("{product[name]}", "{subset}") - ) - - output["flame"] = ayon_flame - - -def _convert_fusion_project_settings(ayon_settings, output): - if "fusion" not in ayon_settings: - return - - ayon_fusion = ayon_settings["fusion"] - _convert_host_imageio(ayon_fusion) - - ayon_imageio_fusion = ayon_fusion["imageio"] - - if "ocioSettings" in ayon_imageio_fusion: - ayon_ocio_setting = ayon_imageio_fusion.pop("ocioSettings") - paths = ayon_ocio_setting.pop("ocioPathModel") - for key, value in tuple(paths.items()): - new_value = [] - if value: - new_value.append(value) - paths[key] = new_value - - ayon_ocio_setting["configFilePath"] = paths - ayon_imageio_fusion["ocio"] = ayon_ocio_setting - elif "ocio" in ayon_imageio_fusion: - paths = ayon_imageio_fusion["ocio"].pop("configFilePath") - for key, value in tuple(paths.items()): - new_value = [] - if value: - new_value.append(value) - paths[key] = new_value - ayon_imageio_fusion["ocio"]["configFilePath"] = paths - - _convert_host_imageio(ayon_imageio_fusion) - - ayon_create_saver = ayon_fusion["create"]["CreateSaver"] - ayon_create_saver["temp_rendering_path_template"] = ( - ayon_create_saver["temp_rendering_path_template"] - .replace("{product[name]}", "{subset}") - .replace("{product[type]}", "{family}") - .replace("{folder[name]}", "{asset}") - .replace("{task[name]}", "{task}") - ) - - output["fusion"] = ayon_fusion - - -def _convert_maya_project_settings(ayon_settings, output): - if "maya" not in ayon_settings: - return - - ayon_maya = ayon_settings["maya"] - - # Change key of render settings - ayon_maya["RenderSettings"] = ayon_maya.pop("render_settings") - - # Convert extensions mapping - ayon_maya["ext_mapping"] = { - item["name"]: item["value"] - for item in ayon_maya["ext_mapping"] - } - - # Maya dirmap - ayon_maya_dirmap = ayon_maya.pop("maya_dirmap") - ayon_maya_dirmap_path = ayon_maya_dirmap["paths"] - ayon_maya_dirmap_path["source-path"] = ( - ayon_maya_dirmap_path.pop("source_path") - ) - ayon_maya_dirmap_path["destination-path"] = ( - ayon_maya_dirmap_path.pop("destination_path") - ) - ayon_maya["maya-dirmap"] = ayon_maya_dirmap - - # Create plugins - ayon_create = ayon_maya["create"] - ayon_create_static_mesh = ayon_create["CreateUnrealStaticMesh"] - if "static_mesh_prefixes" in ayon_create_static_mesh: - ayon_create_static_mesh["static_mesh_prefix"] = ( - ayon_create_static_mesh.pop("static_mesh_prefixes") - ) - - # --- Publish (START) --- - ayon_publish = ayon_maya["publish"] - try: - attributes = json.loads( - ayon_publish["ValidateAttributes"]["attributes"] - ) - except ValueError: - attributes = {} - ayon_publish["ValidateAttributes"]["attributes"] = attributes - - try: - SUFFIX_NAMING_TABLE = json.loads( - ayon_publish - ["ValidateTransformNamingSuffix"] - ["SUFFIX_NAMING_TABLE"] - ) - except ValueError: - SUFFIX_NAMING_TABLE = {} - ayon_publish["ValidateTransformNamingSuffix"]["SUFFIX_NAMING_TABLE"] = ( - SUFFIX_NAMING_TABLE - ) - - validate_frame_range = ayon_publish["ValidateFrameRange"] - if "exclude_product_types" in validate_frame_range: - validate_frame_range["exclude_families"] = ( - validate_frame_range.pop("exclude_product_types")) - - # Extract playblast capture settings - validate_rendern_settings = ayon_publish["ValidateRenderSettings"] - for key in ( - "arnold_render_attributes", - "vray_render_attributes", - "redshift_render_attributes", - "renderman_render_attributes", - ): - if key not in validate_rendern_settings: - continue - validate_rendern_settings[key] = [ - [item["type"], item["value"]] - for item in validate_rendern_settings[key] - ] - - plugin_path_attributes = ayon_publish["ValidatePluginPathAttributes"] - plugin_path_attributes["attribute"] = { - item["name"]: item["value"] - for item in plugin_path_attributes["attribute"] - } - - ayon_capture_preset = ayon_publish["ExtractPlayblast"]["capture_preset"] - display_options = ayon_capture_preset["DisplayOptions"] - for key in ("background", "backgroundBottom", "backgroundTop"): - display_options[key] = _convert_color(display_options[key]) - - for src_key, dst_key in ( - ("DisplayOptions", "Display Options"), - ("ViewportOptions", "Viewport Options"), - ("CameraOptions", "Camera Options"), - ): - ayon_capture_preset[dst_key] = ayon_capture_preset.pop(src_key) - - viewport_options = ayon_capture_preset["Viewport Options"] - viewport_options["pluginObjects"] = { - item["name"]: item["value"] - for item in viewport_options["pluginObjects"] - } - - ayon_playblast_settings = ayon_publish["ExtractPlayblast"]["profiles"] - if ayon_playblast_settings: - for setting in ayon_playblast_settings: - capture_preset = setting["capture_preset"] - display_options = capture_preset["DisplayOptions"] - for key in ("background", "backgroundBottom", "backgroundTop"): - display_options[key] = _convert_color(display_options[key]) - - for src_key, dst_key in ( - ("DisplayOptions", "Display Options"), - ("ViewportOptions", "Viewport Options"), - ("CameraOptions", "Camera Options"), - ): - capture_preset[dst_key] = capture_preset.pop(src_key) - - viewport_options = capture_preset["Viewport Options"] - viewport_options["pluginObjects"] = { - item["name"]: item["value"] - for item in viewport_options["pluginObjects"] - } - - # Extract Camera Alembic bake attributes - try: - bake_attributes = json.loads( - ayon_publish["ExtractCameraAlembic"]["bake_attributes"] - ) - except ValueError: - bake_attributes = [] - ayon_publish["ExtractCameraAlembic"]["bake_attributes"] = bake_attributes - - # --- Publish (END) --- - for renderer_settings in ayon_maya["RenderSettings"].values(): - if ( - not isinstance(renderer_settings, dict) - or "additional_options" not in renderer_settings - ): - continue - renderer_settings["additional_options"] = [ - [item["attribute"], item["value"]] - for item in renderer_settings["additional_options"] - ] - - # Workfile build - ayon_workfile_build = ayon_maya["workfile_build"] - for item in ayon_workfile_build["profiles"]: - for key in ("current_context", "linked_assets"): - for subitem in item[key]: - if "families" in subitem: - break - subitem["families"] = subitem.pop("product_types") - subitem["subset_name_filters"] = subitem.pop( - "product_name_filters") - - _convert_host_imageio(ayon_maya) - - ayon_maya_load = ayon_maya["load"] - load_colors = ayon_maya_load["colors"] - for key, color in tuple(load_colors.items()): - load_colors[key] = _convert_color(color) - - reference_loader = ayon_maya_load["reference_loader"] - reference_loader["namespace"] = ( - reference_loader["namespace"] - .replace("{product[name]}", "{subset}") - ) - - if ayon_maya_load.get("import_loader"): - import_loader = ayon_maya_load["import_loader"] - import_loader["namespace"] = ( - import_loader["namespace"] - .replace("{product[name]}", "{subset}") - ) - - output["maya"] = ayon_maya - - -def _convert_3dsmax_project_settings(ayon_settings, output): - if "max" not in ayon_settings: - return - - ayon_max = ayon_settings["max"] - _convert_host_imageio(ayon_max) - if "PointCloud" in ayon_max: - point_cloud_attribute = ayon_max["PointCloud"]["attribute"] - new_point_cloud_attribute = { - item["name"]: item["value"] - for item in point_cloud_attribute - } - ayon_max["PointCloud"]["attribute"] = new_point_cloud_attribute - # --- Publish (START) --- - ayon_publish = ayon_max["publish"] - if "ValidateAttributes" in ayon_publish: - try: - attributes = json.loads( - ayon_publish["ValidateAttributes"]["attributes"] - ) - except ValueError: - attributes = {} - ayon_publish["ValidateAttributes"]["attributes"] = attributes - - if "ValidateLoadedPlugin" in ayon_publish: - loaded_plugin = ( - ayon_publish["ValidateLoadedPlugin"]["family_plugins_mapping"] - ) - for item in loaded_plugin: - item["families"] = item.pop("product_types") - - output["max"] = ayon_max - - def _convert_nuke_knobs(knobs): new_knobs = [] for knob in knobs: @@ -708,15 +206,6 @@ def _convert_nuke_project_settings(ayon_settings, output): ayon_nuke = ayon_settings["nuke"] - # --- Dirmap --- - dirmap = ayon_nuke.pop("dirmap") - for src_key, dst_key in ( - ("source_path", "source-path"), - ("destination_path", "destination-path"), - ): - dirmap["paths"][dst_key] = dirmap["paths"].pop(src_key) - ayon_nuke["nuke-dirmap"] = dirmap - # --- Load --- ayon_load = ayon_nuke["load"] ayon_load["LoadClip"]["_representations"] = ( @@ -808,7 +297,6 @@ def _convert_nuke_project_settings(ayon_settings, output): # --- ImageIO --- # NOTE 'monitorOutLut' is maybe not yet in v3 (ut should be) - _convert_host_imageio(ayon_nuke) ayon_imageio = ayon_nuke["imageio"] # workfile @@ -857,7 +345,6 @@ def _convert_hiero_project_settings(ayon_settings, output): return ayon_hiero = ayon_settings["hiero"] - _convert_host_imageio(ayon_hiero) new_gui_filters = {} for item in ayon_hiero.pop("filters", []): @@ -882,198 +369,6 @@ def _convert_hiero_project_settings(ayon_settings, output): output["hiero"] = ayon_hiero -def _convert_photoshop_project_settings(ayon_settings, output): - if "photoshop" not in ayon_settings: - return - - ayon_photoshop = ayon_settings["photoshop"] - _convert_host_imageio(ayon_photoshop) - - ayon_publish_photoshop = ayon_photoshop["publish"] - - ayon_colorcoded = ayon_publish_photoshop["CollectColorCodedInstances"] - if "flatten_product_type_template" in ayon_colorcoded: - ayon_colorcoded["flatten_subset_template"] = ( - ayon_colorcoded.pop("flatten_product_type_template")) - - collect_review = ayon_publish_photoshop["CollectReview"] - if "active" in collect_review: - collect_review["publish"] = collect_review.pop("active") - - output["photoshop"] = ayon_photoshop - - -def _convert_substancepainter_project_settings(ayon_settings, output): - if "substancepainter" not in ayon_settings: - return - - ayon_substance_painter = ayon_settings["substancepainter"] - _convert_host_imageio(ayon_substance_painter) - if "shelves" in ayon_substance_painter: - shelves_items = ayon_substance_painter["shelves"] - new_shelves_items = { - item["name"]: item["value"] - for item in shelves_items - } - ayon_substance_painter["shelves"] = new_shelves_items - - output["substancepainter"] = ayon_substance_painter - - -def _convert_tvpaint_project_settings(ayon_settings, output): - if "tvpaint" not in ayon_settings: - return - ayon_tvpaint = ayon_settings["tvpaint"] - - _convert_host_imageio(ayon_tvpaint) - - ayon_publish_settings = ayon_tvpaint["publish"] - for plugin_name in ( - "ValidateProjectSettings", - "ValidateMarks", - "ValidateStartFrame", - "ValidateAssetName", - ): - ayon_value = ayon_publish_settings[plugin_name] - for src_key, dst_key in ( - ("action_enabled", "optional"), - ("action_enable", "active"), - ): - if src_key in ayon_value: - ayon_value[dst_key] = ayon_value.pop(src_key) - - extract_sequence_setting = ayon_publish_settings["ExtractSequence"] - extract_sequence_setting["review_bg"] = _convert_color( - extract_sequence_setting["review_bg"] - ) - - output["tvpaint"] = ayon_tvpaint - - -def _convert_traypublisher_project_settings(ayon_settings, output): - if "traypublisher" not in ayon_settings: - return - - ayon_traypublisher = ayon_settings["traypublisher"] - - _convert_host_imageio(ayon_traypublisher) - - ayon_editorial_simple = ( - ayon_traypublisher["editorial_creators"]["editorial_simple"] - ) - # Subset -> Product type conversion - if "product_type_presets" in ayon_editorial_simple: - family_presets = ayon_editorial_simple.pop("product_type_presets") - for item in family_presets: - item["family"] = item.pop("product_type") - ayon_editorial_simple["family_presets"] = family_presets - - if "shot_metadata_creator" in ayon_editorial_simple: - shot_metadata_creator = ayon_editorial_simple.pop( - "shot_metadata_creator" - ) - if isinstance(shot_metadata_creator["clip_name_tokenizer"], dict): - shot_metadata_creator["clip_name_tokenizer"] = [ - {"name": "_sequence_", "regex": "(sc\\d{3})"}, - {"name": "_shot_", "regex": "(sh\\d{3})"}, - ] - ayon_editorial_simple.update(shot_metadata_creator) - - ayon_editorial_simple["clip_name_tokenizer"] = { - item["name"]: item["regex"] - for item in ayon_editorial_simple["clip_name_tokenizer"] - } - - if "shot_subset_creator" in ayon_editorial_simple: - ayon_editorial_simple.update( - ayon_editorial_simple.pop("shot_subset_creator")) - for item in ayon_editorial_simple["shot_hierarchy"]["parents"]: - item["type"] = item.pop("parent_type") - - # Simple creators - ayon_simple_creators = ayon_traypublisher["simple_creators"] - for item in ayon_simple_creators: - if "product_type" not in item: - break - item["family"] = item.pop("product_type") - - shot_add_tasks = ayon_editorial_simple["shot_add_tasks"] - - # TODO: backward compatibility and remove in future - if isinstance(shot_add_tasks, dict): - shot_add_tasks = [] - - # aggregate shot_add_tasks items - new_shot_add_tasks = { - item["name"]: {"type": item["task_type"]} - for item in shot_add_tasks - } - ayon_editorial_simple["shot_add_tasks"] = new_shot_add_tasks - - output["traypublisher"] = ayon_traypublisher - - -def _convert_webpublisher_project_settings(ayon_settings, output): - if "webpublisher" not in ayon_settings: - return - - ayon_webpublisher = ayon_settings["webpublisher"] - _convert_host_imageio(ayon_webpublisher) - - ayon_publish = ayon_webpublisher["publish"] - - ayon_collect_files = ayon_publish["CollectPublishedFiles"] - ayon_collect_files["task_type_to_family"] = { - item["name"]: item["value"] - for item in ayon_collect_files["task_type_to_family"] - } - - output["webpublisher"] = ayon_webpublisher - - -def _convert_deadline_project_settings(ayon_settings, output): - if "deadline" not in ayon_settings: - return - - ayon_deadline = ayon_settings["deadline"] - - for key in ("deadline_urls",): - ayon_deadline.pop(key) - - ayon_deadline_publish = ayon_deadline["publish"] - limit_groups = { - item["name"]: item["value"] - for item in ayon_deadline_publish["NukeSubmitDeadline"]["limit_groups"] - } - ayon_deadline_publish["NukeSubmitDeadline"]["limit_groups"] = limit_groups - - maya_submit = ayon_deadline_publish["MayaSubmitDeadline"] - for json_key in ("jobInfo", "pluginInfo"): - src_text = maya_submit.pop(json_key) - try: - value = json.loads(src_text) - except ValueError: - value = {} - maya_submit[json_key] = value - - nuke_submit = ayon_deadline_publish["NukeSubmitDeadline"] - nuke_submit["env_search_replace_values"] = { - item["name"]: item["value"] - for item in nuke_submit.pop("env_search_replace_values") - } - nuke_submit["limit_groups"] = { - item["name"]: item["value"] for item in nuke_submit.pop("limit_groups") - } - - process_subsetted_job = ayon_deadline_publish["ProcessSubmittedJobOnFarm"] - process_subsetted_job["aov_filter"] = { - item["name"]: item["value"] - for item in process_subsetted_job.pop("aov_filter") - } - - output["deadline"] = ayon_deadline - - def _convert_royalrender_project_settings(ayon_settings, output): if "royalrender" not in ayon_settings: return @@ -1086,113 +381,15 @@ def _convert_royalrender_project_settings(ayon_settings, output): } -def _convert_kitsu_project_settings(ayon_settings, output): - if "kitsu" not in ayon_settings: - return - - ayon_kitsu_settings = ayon_settings["kitsu"] - ayon_kitsu_settings.pop("server") - - integrate_note = ayon_kitsu_settings["publish"]["IntegrateKitsuNote"] - status_change_conditions = integrate_note["status_change_conditions"] - if "product_type_requirements" in status_change_conditions: - status_change_conditions["family_requirements"] = ( - status_change_conditions.pop("product_type_requirements")) - - output["kitsu"] = ayon_kitsu_settings - - -def _convert_shotgrid_project_settings(ayon_settings, output): - if "shotgrid" not in ayon_settings: - return - - ayon_shotgrid = ayon_settings["shotgrid"] - # This means that a different variant of addon is used - if "leecher_backend_url" not in ayon_shotgrid: - return - - for key in { - "leecher_backend_url", - "filter_projects_by_login", - "shotgrid_settings", - "leecher_manager_url", - }: - ayon_shotgrid.pop(key) - - asset_field = ayon_shotgrid["fields"]["asset"] - asset_field["type"] = asset_field.pop("asset_type") - - task_field = ayon_shotgrid["fields"]["task"] - if "task" in task_field: - task_field["step"] = task_field.pop("task") - - output["shotgrid"] = ayon_settings["shotgrid"] - - -def _convert_slack_project_settings(ayon_settings, output): - if "slack" not in ayon_settings: - return - - ayon_slack = ayon_settings["slack"] - ayon_slack.pop("enabled", None) - for profile in ayon_slack["publish"]["CollectSlackFamilies"]["profiles"]: - profile["tasks"] = profile.pop("task_names") - profile["subsets"] = profile.pop("subset_names") - - output["slack"] = ayon_slack - - def _convert_global_project_settings(ayon_settings, output, default_settings): if "core" not in ayon_settings: return ayon_core = ayon_settings["core"] - _convert_host_imageio(ayon_core) - - for key in ( - "environments", - "studio_name", - "studio_code", - ): - ayon_core.pop(key, None) - # Publish conversion ayon_publish = ayon_core["publish"] - ayon_collect_audio = ayon_publish["CollectAudio"] - if "audio_product_name" in ayon_collect_audio: - ayon_collect_audio["audio_subset_name"] = ( - ayon_collect_audio.pop("audio_product_name")) - - for profile in ayon_publish["ExtractReview"]["profiles"]: - if "product_types" in profile: - profile["families"] = profile.pop("product_types") - new_outputs = {} - for output_def in profile.pop("outputs"): - name = output_def.pop("name") - new_outputs[name] = output_def - - output_def_filter = output_def["filter"] - if "product_names" in output_def_filter: - output_def_filter["subsets"] = ( - output_def_filter.pop("product_names")) - - for color_key in ("overscan_color", "bg_color"): - output_def[color_key] = _convert_color(output_def[color_key]) - - letter_box = output_def["letter_box"] - for color_key in ("fill_color", "line_color"): - letter_box[color_key] = _convert_color(letter_box[color_key]) - - if "output_width" in output_def: - output_def["width"] = output_def.pop("output_width") - - if "output_height" in output_def: - output_def["height"] = output_def.pop("output_height") - - profile["outputs"] = new_outputs - # ExtractThumbnail plugin ayon_extract_thumbnail = ayon_publish["ExtractThumbnail"] # fix display and view at oiio defaults @@ -1357,42 +554,13 @@ def _convert_global_project_settings(ayon_settings, output, default_settings): def convert_project_settings(ayon_settings, default_settings): - # Missing settings - # - standalonepublisher default_settings = copy.deepcopy(default_settings) output = {} - exact_match = { - "aftereffects", - "harmony", - "houdini", - "resolve", - "unreal", - } - for key in exact_match: - if key in ayon_settings: - output[key] = ayon_settings[key] - _convert_host_imageio(output[key]) - _convert_applications_project_settings(ayon_settings, output) - _convert_blender_project_settings(ayon_settings, output) - _convert_celaction_project_settings(ayon_settings, output) - _convert_flame_project_settings(ayon_settings, output) - _convert_fusion_project_settings(ayon_settings, output) - _convert_maya_project_settings(ayon_settings, output) - _convert_3dsmax_project_settings(ayon_settings, output) _convert_nuke_project_settings(ayon_settings, output) _convert_hiero_project_settings(ayon_settings, output) - _convert_photoshop_project_settings(ayon_settings, output) - _convert_substancepainter_project_settings(ayon_settings, output) - _convert_tvpaint_project_settings(ayon_settings, output) - _convert_traypublisher_project_settings(ayon_settings, output) - _convert_webpublisher_project_settings(ayon_settings, output) - _convert_deadline_project_settings(ayon_settings, output) _convert_royalrender_project_settings(ayon_settings, output) - _convert_kitsu_project_settings(ayon_settings, output) - _convert_shotgrid_project_settings(ayon_settings, output) - _convert_slack_project_settings(ayon_settings, output) _convert_global_project_settings(ayon_settings, output, default_settings) diff --git a/client/ayon_core/settings/defaults/system_settings/applications.json b/client/ayon_core/settings/defaults/system_settings/applications.json index a5283751e9..2610c15315 100644 --- a/client/ayon_core/settings/defaults/system_settings/applications.json +++ b/client/ayon_core/settings/defaults/system_settings/applications.json @@ -1271,7 +1271,7 @@ "icon": "{}/app_icons/harmony.png", "host_name": "harmony", "environment": { - "AVALON_HARMONY_WORKFILES_ON_LAUNCH": "1" + "AYON_HARMONY_WORKFILES_ON_LAUNCH": "1" }, "variants": { "21": { diff --git a/client/ayon_core/settings/lib.py b/client/ayon_core/settings/lib.py index beae376b7c..4dff7768f7 100644 --- a/client/ayon_core/settings/lib.py +++ b/client/ayon_core/settings/lib.py @@ -15,7 +15,8 @@ from .constants import ( from .ayon_settings import ( get_ayon_project_settings, - get_ayon_system_settings + get_ayon_system_settings, + get_ayon_settings, ) log = logging.getLogger(__name__) @@ -240,27 +241,22 @@ def get_site_local_overrides(project_name, site_name, local_settings=None): def get_current_project_settings(): """Project settings for current context project. - Project name should be stored in environment variable `AVALON_PROJECT`. + Project name should be stored in environment variable `AYON_PROJECT_NAME`. This function should be used only in host context where environment variable must be set and should not happen that any part of process will change the value of the enviornment variable. """ - project_name = os.environ.get("AVALON_PROJECT") + project_name = os.environ.get("AYON_PROJECT_NAME") if not project_name: raise ValueError( - "Missing context project in environemt variable `AVALON_PROJECT`." + "Missing context project in environemt variable `AYON_PROJECT_NAME`." ) return get_project_settings(project_name) -def get_global_settings(): - default_settings = load_openpype_default_settings() - return default_settings["system_settings"]["general"] - - def get_general_environments(): - value = get_system_settings() - return value["general"]["environment"] + settings = get_ayon_settings() + return json.loads(settings["core"]["environments"]) def get_system_settings(*args, **kwargs): diff --git a/client/ayon_core/tests/README.md b/client/ayon_core/tests/README.md deleted file mode 100644 index c05166767c..0000000000 --- a/client/ayon_core/tests/README.md +++ /dev/null @@ -1,4 +0,0 @@ -Tests for Pype --------------- -Trigger by: - `pype test --pype` \ No newline at end of file diff --git a/client/ayon_core/tests/__init__.py b/client/ayon_core/tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/client/ayon_core/tests/lib.py b/client/ayon_core/tests/lib.py deleted file mode 100644 index c7d4423aba..0000000000 --- a/client/ayon_core/tests/lib.py +++ /dev/null @@ -1,88 +0,0 @@ -import os -import sys -import shutil -import tempfile -import contextlib - -import pyblish -import pyblish.plugin -from pyblish.vendor import six - - -# Setup -HOST = 'python' -FAMILY = 'test.family' - -REGISTERED = pyblish.plugin.registered_paths() -PACKAGEPATH = pyblish.lib.main_package_path() -ENVIRONMENT = os.environ.get("PYBLISHPLUGINPATH", "") -PLUGINPATH = os.path.join(PACKAGEPATH, '..', 'tests', 'plugins') - - -def setup(): - pyblish.plugin.deregister_all_paths() - - -def setup_empty(): - """Disable all plug-ins""" - setup() - pyblish.plugin.deregister_all_plugins() - pyblish.plugin.deregister_all_paths() - pyblish.plugin.deregister_all_hosts() - pyblish.plugin.deregister_all_callbacks() - pyblish.plugin.deregister_all_targets() - pyblish.api.deregister_all_discovery_filters() - - -def teardown(): - """Restore previously REGISTERED paths""" - - pyblish.plugin.deregister_all_paths() - for path in REGISTERED: - pyblish.plugin.register_plugin_path(path) - - os.environ["PYBLISHPLUGINPATH"] = ENVIRONMENT - pyblish.api.deregister_all_plugins() - pyblish.api.deregister_all_hosts() - pyblish.api.deregister_all_discovery_filters() - pyblish.api.deregister_test() - pyblish.api.__init__() - - -@contextlib.contextmanager -def captured_stdout(): - """Temporarily reassign stdout to a local variable""" - try: - sys.stdout = six.StringIO() - yield sys.stdout - finally: - sys.stdout = sys.__stdout__ - - -@contextlib.contextmanager -def captured_stderr(): - """Temporarily reassign stderr to a local variable""" - try: - sys.stderr = six.StringIO() - yield sys.stderr - finally: - sys.stderr = sys.__stderr__ - - -@contextlib.contextmanager -def tempdir(): - """Provide path to temporary directory""" - try: - tempdir = tempfile.mkdtemp() - yield tempdir - finally: - shutil.rmtree(tempdir) - - -def is_in_tests(): - """Returns if process is running in automatic tests mode. - - In tests mode different source DB is used, some plugins might be disabled - etc. - """ - return os.environ.get("IS_TEST") == '1' diff --git a/client/ayon_core/tests/mongo_performance.py b/client/ayon_core/tests/mongo_performance.py deleted file mode 100644 index 2df3363f4b..0000000000 --- a/client/ayon_core/tests/mongo_performance.py +++ /dev/null @@ -1,288 +0,0 @@ -import pymongo -import bson -import random -from datetime import datetime -import os - - -class TestPerformance(): - ''' - Class for testing performance of representation and their 'files' - parts. - Discussion is if embedded array: - 'files' : [ {'_id': '1111', 'path':'....}, - {'_id'...}] - OR documents: - 'files' : { - '1111': {'path':'....'}, - '2222': {'path':'...'} - } - is faster. - - Current results: - without additional partial index documents is 3x faster - With index is array 50x faster then document - - Partial index something like: - db.getCollection('performance_test').createIndex - ({'files._id': 1}, - {partialFilterExpresion: {'files': {'$exists': true}}}) - !DIDNT work for me, had to create manually in Compass - - ''' - - MONGO_URL = 'mongodb://localhost:27017' - MONGO_DB = 'performance_test' - MONGO_COLLECTION = 'performance_test' - - MAX_FILE_SIZE_B = 5000 - MAX_NUMBER_OF_SITES = 50 - ROOT_DIR = "C:/projects" - - inserted_ids = [] - - def __init__(self, version='array'): - ''' - It creates and fills collection, based on value of 'version'. - - :param version: 'array' - files as embedded array, - 'doc' - as document - ''' - self.client = pymongo.MongoClient(self.MONGO_URL) - self.db = self.client[self.MONGO_DB] - self.collection_name = self.MONGO_COLLECTION - - self.version = version - - if self.version != 'array': - self.collection_name = self.MONGO_COLLECTION + '_doc' - - self.collection = self.db[self.collection_name] - - self.ids = [] # for testing - self.inserted_ids = [] - - def prepare(self, no_of_records=100000, create_files=False): - ''' - Produce 'no_of_records' of representations with 'files' segment. - It depends on 'version' value in constructor, 'arrray' or 'doc' - :return: - ''' - print('Purging {} collection'.format(self.collection_name)) - self.collection.delete_many({}) - - id = bson.objectid.ObjectId() - - insert_recs = [] - for i in range(no_of_records): - file_id = bson.objectid.ObjectId() - file_id2 = bson.objectid.ObjectId() - file_id3 = bson.objectid.ObjectId() - - self.inserted_ids.extend([file_id, file_id2, file_id3]) - version_str = "v{:03d}".format(i + 1) - file_name = "test_Cylinder_workfileLookdev_{}.mb".\ - format(version_str) - - document = {"files": self.get_files(self.version, i + 1, - file_id, file_id2, file_id3, - create_files) - , - "context": { - "subset": "workfileLookdev", - "username": "petrk", - "task": "lookdev", - "family": "workfile", - "hierarchy": "Assets", - "project": {"code": "test", "name": "Test"}, - "version": i + 1, - "asset": "Cylinder", - "representation": "mb", - "root": self.ROOT_DIR - }, - "dependencies": [], - "name": "mb", - "parent": {"oid": '{}'.format(id)}, - "data": { - "path": "C:\\projects\\test_performance\\Assets\\Cylinder\\publish\\workfile\\workfileLookdev\\{}\\{}".format(version_str, file_name), # noqa: E501 - "template": "{root[work]}\\{project[name]}\\{hierarchy}\\{asset}\\publish\\{family}\\{subset}\\v{version:0>3}\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}>.{representation}" # noqa: E501 - }, - "type": "representation", - "schema": "openpype:representation-2.0" - } - - insert_recs.append(document) - - print('Prepared {} records in {} collection'. - format(no_of_records, self.collection_name)) - - self.collection.insert_many(insert_recs) - # TODO refactore to produce real array and not needeing ugly regex - self.collection.insert_one({"inserted_id": self.inserted_ids}) - print('-' * 50) - - def run(self, queries=1000, loops=3): - ''' - Run X'queries' that are searching collection Y'loops' times - :param queries: how many times do ..find(...) - :param loops: loop of testing X queries - :return: None - ''' - print('Testing version {} on {}'.format(self.version, - self.collection_name)) - print('Queries rung {} in {} loops'.format(queries, loops)) - - inserted_ids = list(self.collection. - find({"inserted_id": {"$exists": True}})) - import re - self.ids = re.findall("'[0-9a-z]*'", str(inserted_ids)) - - import time - - found_cnt = 0 - for _ in range(loops): - print('Starting loop {}'.format(_)) - start = time.time() - for _ in range(queries): - # val = random.choice(self.ids) - # val = val.replace("'", '') - val = random.randint(0, 50) - print(val) - - if (self.version == 'array'): - # prepared for partial index, without 'files': exists - # wont engage - found = self.collection.\ - find({'files': {"$exists": True}, - 'files.sites.name': "local_{}".format(val)}).\ - count() - else: - key = "files.{}".format(val) - found = self.collection.find_one({key: {"$exists": True}}) - print("found {} records".format(found)) - # if found: - # found_cnt += len(list(found)) - - end = time.time() - print('duration per loop {}'.format(end - start)) - print("found_cnt {}".format(found_cnt)) - - def get_files(self, mode, i, file_id, file_id2, file_id3, - create_files=False): - ''' - Wrapper to decide if 'array' or document version should be used - :param mode: 'array'|'doc' - :param i: step number - :param file_id: ObjectId of first dummy file - :param file_id2: .. - :param file_id3: .. - :return: - ''' - if mode == 'array': - return self.get_files_array(i, file_id, file_id2, file_id3, - create_files) - else: - return self.get_files_doc(i, file_id, file_id2, file_id3) - - def get_files_array(self, i, file_id, file_id2, file_id3, - create_files=False): - ret = [ - { - "path": "{root[work]}" + "{root[work]}/test_performance/Assets/Cylinder/publish/workfile/workfileLookdev/v{:03d}/test_Cylinder_A_workfileLookdev_v{:03d}.dat".format(i, i), # noqa: E501 - "_id": '{}'.format(file_id), - "hash": "temphash", - "sites": self.get_sites(self.MAX_NUMBER_OF_SITES), - "size": random.randint(0, self.MAX_FILE_SIZE_B) - }, - { - "path": "{root[work]}" + "/test_performance/Assets/Cylinder/publish/workfile/workfileLookdev/v{:03d}/test_Cylinder_B_workfileLookdev_v{:03d}.dat".format(i, i), # noqa: E501 - "_id": '{}'.format(file_id2), - "hash": "temphash", - "sites": self.get_sites(self.MAX_NUMBER_OF_SITES), - "size": random.randint(0, self.MAX_FILE_SIZE_B) - }, - { - "path": "{root[work]}" + "/test_performance/Assets/Cylinder/publish/workfile/workfileLookdev/v{:03d}/test_Cylinder_C_workfileLookdev_v{:03d}.dat".format(i, i), # noqa: E501 - "_id": '{}'.format(file_id3), - "hash": "temphash", - "sites": self.get_sites(self.MAX_NUMBER_OF_SITES), - "size": random.randint(0, self.MAX_FILE_SIZE_B) - } - - ] - if create_files: - for f in ret: - path = f.get("path").replace("{root[work]}", self.ROOT_DIR) - os.makedirs(os.path.dirname(path), exist_ok=True) - with open(path, 'wb') as fp: - fp.write(os.urandom(f.get("size"))) - - return ret - - def get_files_doc(self, i, file_id, file_id2, file_id3): - ret = {} - ret['{}'.format(file_id)] = { - "path": "{root[work]}" + - "/test_performance/Assets/Cylinder/publish/workfile/workfileLookdev/" # noqa: E501 - "v{:03d}/test_CylinderA_workfileLookdev_v{:03d}.mb".format(i, i), # noqa: E501 - "hash": "temphash", - "sites": ["studio"], - "size": 87236 - } - - ret['{}'.format(file_id2)] = { - "path": "{root[work]}" + - "/test_performance/Assets/Cylinder/publish/workfile/workfileLookdev/" # noqa: E501 - "v{:03d}/test_CylinderB_workfileLookdev_v{:03d}.mb".format(i, i), # noqa: E501 - "hash": "temphash", - "sites": ["studio"], - "size": 87236 - } - ret['{}'.format(file_id3)] = { - "path": "{root[work]}" + - "/test_performance/Assets/Cylinder/publish/workfile/workfileLookdev/" # noqa: E501 - "v{:03d}/test_CylinderC_workfileLookdev_v{:03d}.mb".format(i, i), # noqa: E501 - "hash": "temphash", - "sites": ["studio"], - "size": 87236 - } - - return ret - - def get_sites(self, number_of_sites=50): - """ - Return array of sites declaration. - Currently on 1st site has "created_dt" fillled, which should - trigger upload to 'gdrive' site. - 'gdrive' site is appended, its destination for syncing for - Sync Server - Args: - number_of_sites: - - Returns: - - """ - sites = [] - for i in range(number_of_sites): - site = {'name': "local_{}".format(i)} - # do not create null 'created_dt' field, Mongo doesnt like it - if i == 0: - site['created_dt'] = datetime.now() - - sites.append(site) - - sites.append({'name': "gdrive"}) - - return sites - - -if __name__ == '__main__': - tp = TestPerformance('array') - tp.prepare(no_of_records=10000, create_files=True) - # tp.run(10, 3) - - # print('-'*50) - # - # tp = TestPerformance('doc') - # tp.prepare() # enable to prepare data - # tp.run(1000, 3) diff --git a/client/ayon_core/tests/test_avalon_plugin_presets.py b/client/ayon_core/tests/test_avalon_plugin_presets.py deleted file mode 100644 index 4926286ca3..0000000000 --- a/client/ayon_core/tests/test_avalon_plugin_presets.py +++ /dev/null @@ -1,43 +0,0 @@ -from ayon_core.pipeline import ( - install_host, - LegacyCreator, - register_creator_plugin, - discover_creator_plugins, -) - - -class MyTestCreator(LegacyCreator): - - my_test_property = "A" - - def __init__(self, name, asset, options=None, data=None): - super(MyTestCreator, self).__init__(self, name, asset, - options=None, data=None) - - -# this is hack like no other - we need to inject our own avalon host -# and bypass all its validation. Avalon hosts are modules that needs -# `ls` callable as attribute. Voila: -class Test: - __name__ = "test" - ls = len - - @staticmethod - def install(): - register_creator_plugin(MyTestCreator) - - -def test_avalon_plugin_presets(monkeypatch, printer): - install_host(Test) - - plugins = discover_creator_plugins() - printer("Test if we got our test plugin") - assert MyTestCreator in plugins - for p in plugins: - if p.__name__ == "MyTestCreator": - printer("Test if we have overridden existing property") - assert p.my_test_property == "B" - printer("Test if we have overridden superclass property") - assert p.active is False - printer("Test if we have added new property") - assert p.new_property == "new" diff --git a/client/ayon_core/tests/test_lib_restructuralization.py b/client/ayon_core/tests/test_lib_restructuralization.py deleted file mode 100644 index ffbd62b045..0000000000 --- a/client/ayon_core/tests/test_lib_restructuralization.py +++ /dev/null @@ -1,25 +0,0 @@ -# Test for backward compatibility of restructure of lib.py into lib library -# Contains simple imports that should still work - - -def test_backward_compatibility(printer): - printer("Test if imports still work") - try: - from ayon_core.lib import execute_hook - from ayon_core.lib import PypeHook - - from ayon_core.lib import ApplicationLaunchFailed - - from ayon_core.lib import get_ffmpeg_tool_path - from ayon_core.lib import get_last_version_from_path - from ayon_core.lib import get_paths_from_environ - from ayon_core.lib import get_version_from_path - from ayon_core.lib import version_up - - from ayon_core.lib import get_ffprobe_streams - - from ayon_core.lib import source_hash - from ayon_core.lib import run_subprocess - - except ImportError as e: - raise diff --git a/client/ayon_core/tests/test_pyblish_filter.py b/client/ayon_core/tests/test_pyblish_filter.py deleted file mode 100644 index bc20f863c9..0000000000 --- a/client/ayon_core/tests/test_pyblish_filter.py +++ /dev/null @@ -1,60 +0,0 @@ -import os -import pyblish.api -import pyblish.util -import pyblish.plugin -from ayon_core.pipeline.publish.lib import filter_pyblish_plugins -from . import lib - - -def test_pyblish_plugin_filter_modifier(printer, monkeypatch): - """ - Test if pyblish filter can filter and modify plugins on-the-fly. - """ - - lib.setup_empty() - monkeypatch.setitem(os.environ, 'PYBLISHPLUGINPATH', '') - plugins = pyblish.api.registered_plugins() - printer("Test if we have no registered plugins") - assert len(plugins) == 0 - paths = pyblish.api.registered_paths() - printer("Test if we have no registered plugin paths") - assert len(paths) == 0 - - class MyTestPlugin(pyblish.api.InstancePlugin): - my_test_property = 1 - label = "Collect Renderable Camera(s)" - hosts = ["test"] - families = ["default"] - - pyblish.api.register_host("test") - pyblish.api.register_plugin(MyTestPlugin) - pyblish.api.register_discovery_filter(filter_pyblish_plugins) - plugins = pyblish.api.discover() - - printer("Test if only one plugin was discovered") - assert len(plugins) == 1 - printer("Test if properties are modified correctly") - assert plugins[0].label == "loaded from preset" - assert plugins[0].families == ["changed", "by", "preset"] - assert plugins[0].optional is True - - lib.teardown() - - -def test_pyblish_plugin_filter_removal(monkeypatch): - """ Test that plugin can be removed by filter """ - lib.setup_empty() - monkeypatch.setitem(os.environ, 'PYBLISHPLUGINPATH', '') - plugins = pyblish.api.registered_plugins() - - class MyTestRemovedPlugin(pyblish.api.InstancePlugin): - my_test_property = 1 - label = "Collect Renderable Camera(s)" - hosts = ["test"] - families = ["default"] - - pyblish.api.register_host("test") - pyblish.api.register_plugin(MyTestRemovedPlugin) - pyblish.api.register_discovery_filter(filter_pyblish_plugins) - plugins = pyblish.api.discover() - assert len(plugins) == 0 diff --git a/client/ayon_core/tools/ayon_utils/widgets/__init__.py b/client/ayon_core/tools/ayon_utils/widgets/__init__.py index f58de17c4a..b1b7dd7527 100644 --- a/client/ayon_core/tools/ayon_utils/widgets/__init__.py +++ b/client/ayon_core/tools/ayon_utils/widgets/__init__.py @@ -9,6 +9,7 @@ from .folders_widget import ( FoldersWidget, FoldersQtModel, FOLDERS_MODEL_SENDER_NAME, + SimpleFoldersWidget, ) from .tasks_widget import ( @@ -31,6 +32,7 @@ __all__ = ( "FoldersWidget", "FoldersQtModel", "FOLDERS_MODEL_SENDER_NAME", + "SimpleFoldersWidget", "TasksWidget", "TasksQtModel", diff --git a/client/ayon_core/tools/ayon_utils/widgets/folders_widget.py b/client/ayon_core/tools/ayon_utils/widgets/folders_widget.py index 1e395b0368..cf81d1c8ff 100644 --- a/client/ayon_core/tools/ayon_utils/widgets/folders_widget.py +++ b/client/ayon_core/tools/ayon_utils/widgets/folders_widget.py @@ -2,6 +2,11 @@ import collections from qtpy import QtWidgets, QtGui, QtCore +from ayon_core.lib.events import QueuedEventSystem +from ayon_core.tools.ayon_utils.models import ( + HierarchyModel, + HierarchyExpectedSelection, +) from ayon_core.tools.utils import ( RecursiveSortFilterProxyModel, TreeView, @@ -390,6 +395,15 @@ class FoldersWidget(QtWidgets.QWidget): return self._get_selected_item_id() + def get_selected_folder_path(self): + """Get selected folder id. + + Returns: + Union[str, None]: Folder path which is selected. + """ + + return self._get_selected_item_value(FOLDER_PATH_ROLE) + def get_selected_folder_label(self): """Selected folder label. @@ -473,9 +487,12 @@ class FoldersWidget(QtWidgets.QWidget): self.refreshed.emit() def _get_selected_item_id(self): + return self._get_selected_item_value(FOLDER_ID_ROLE) + + def _get_selected_item_value(self, role): selection_model = self._folders_view.selectionModel() for index in selection_model.selectedIndexes(): - item_id = index.data(FOLDER_ID_ROLE) + item_id = index.data(role) if item_id is not None: return item_id return None @@ -514,3 +531,110 @@ class FoldersWidget(QtWidgets.QWidget): if folder_id is not None: self.set_selected_folder(folder_id) self._controller.expected_folder_selected(folder_id) + + +class SimpleSelectionModel(object): + """Model handling selection changes. + + Triggering events: + - "selection.project.changed" + - "selection.folder.changed" + """ + + event_source = "selection.model" + + def __init__(self, controller): + self._controller = controller + + self._project_name = None + self._folder_id = None + self._task_id = None + self._task_name = None + + def get_selected_project_name(self): + return self._project_name + + def set_selected_project(self, project_name): + self._project_name = project_name + self._controller.emit_event( + "selection.project.changed", + {"project_name": project_name}, + self.event_source + ) + + def get_selected_folder_id(self): + return self._folder_id + + def set_selected_folder(self, folder_id): + if folder_id == self._folder_id: + return + self._folder_id = folder_id + self._controller.emit_event( + "selection.folder.changed", + { + "project_name": self._project_name, + "folder_id": folder_id, + }, + self.event_source + ) + + +class SimpleFoldersController(object): + def __init__(self): + self._event_system = self._create_event_system() + self._hierarchy_model = HierarchyModel(self) + self._selection_model = SimpleSelectionModel(self) + self._expected_selection = HierarchyExpectedSelection( + self, handle_project=False, handle_folder=True, handle_task=False + ) + + def emit_event(self, topic, data=None, source=None): + """Use implemented event system to trigger event.""" + + if data is None: + data = {} + self._event_system.emit(topic, data, source) + + def register_event_callback(self, topic, callback): + self._event_system.add_callback(topic, callback) + + # Model functions + def get_folder_items(self, project_name, sender=None): + return self._hierarchy_model.get_folder_items(project_name, sender) + + def set_selected_project(self, project_name): + self._selection_model.set_selected_project(project_name) + + def set_selected_folder(self, folder_id): + self._selection_model.set_selected_folder(folder_id) + + def get_expected_selection_data(self): + self._expected_selection.get_expected_selection_data() + + def expected_folder_selected(self, folder_id): + self._expected_selection.expected_folder_selected(folder_id) + + def _create_event_system(self): + return QueuedEventSystem() + + +class SimpleFoldersWidget(FoldersWidget): + def __init__(self, controller=None, *args, **kwargs): + if controller is None: + controller = SimpleFoldersController() + super(SimpleFoldersWidget, self).__init__(controller, *args, **kwargs) + + def set_project_name(self, project_name): + self._controller.set_selected_project(project_name) + super(SimpleFoldersWidget, self).set_project_name(project_name) + + def _on_project_selection_change(self, event): + """Ignore project selection change from controller. + + Only who can trigger project change is this widget with + 'set_project_name' which already cares about project change. + + Args: + event (Event): Triggered event. + """ + pass diff --git a/client/ayon_core/tools/experimental_tools/tools_def.py b/client/ayon_core/tools/experimental_tools/tools_def.py index 568c7032d0..716a45c3a0 100644 --- a/client/ayon_core/tools/experimental_tools/tools_def.py +++ b/client/ayon_core/tools/experimental_tools/tools_def.py @@ -89,9 +89,13 @@ class ExperimentalTools: "New publisher", "Combined creation and publishing into one tool.", self._show_publisher, - hosts_filter=["blender", "maya", "nuke", "celaction", "flame", - "fusion", "harmony", "hiero", "resolve", - "tvpaint", "unreal"] + hosts_filter=[ + "celaction", + "flame", + "harmony", + "hiero", + "resolve", + ] ) ] @@ -139,7 +143,7 @@ class ExperimentalTools: def get_tools_for_host(self, host_name=None): if not host_name: - host_name = os.environ.get("AVALON_APP") + host_name = os.environ.get("AYON_HOST_NAME") tools = [] for tool in self.tools: if ( diff --git a/client/ayon_core/tools/launcher/models/actions.py b/client/ayon_core/tools/launcher/models/actions.py index 37024b5810..53d2b78dc3 100644 --- a/client/ayon_core/tools/launcher/models/actions.py +++ b/client/ayon_core/tools/launcher/models/actions.py @@ -69,9 +69,9 @@ class ApplicationAction(LauncherAction): _log = None required_session_keys = ( - "AVALON_PROJECT", - "AVALON_ASSET", - "AVALON_TASK" + "AYON_PROJECT_NAME", + "AYON_FOLDER_PATH", + "AYON_TASK_NAME" ) @property @@ -85,7 +85,7 @@ class ApplicationAction(LauncherAction): if not session.get(key): return False - project_name = session["AVALON_PROJECT"] + project_name = session["AYON_PROJECT_NAME"] project_entity = self.project_entities[project_name] apps = project_entity["attrib"].get("applications") if not apps or self.application.full_name not in apps: @@ -119,9 +119,9 @@ class ApplicationAction(LauncherAction): ApplicationLaunchFailed, ) - project_name = session["AVALON_PROJECT"] - asset_name = session["AVALON_ASSET"] - task_name = session["AVALON_TASK"] + project_name = session["AYON_PROJECT_NAME"] + asset_name = session["AYON_FOLDER_PATH"] + task_name = session["AYON_TASK_NAME"] try: self.application.launch( project_name=project_name, @@ -416,6 +416,10 @@ class ActionsModel: task_name = task["name"] return { + "AYON_PROJECT_NAME": project_name, + "AYON_FOLDER_PATH": folder_path, + "AYON_TASK_NAME": task_name, + # Deprecated - kept for backwards compatibility "AVALON_PROJECT": project_name, "AVALON_ASSET": folder_path, "AVALON_TASK": task_name, diff --git a/client/ayon_core/tools/publisher/control.py b/client/ayon_core/tools/publisher/control.py index 988362fee4..5ccd428551 100644 --- a/client/ayon_core/tools/publisher/control.py +++ b/client/ayon_core/tools/publisher/control.py @@ -1807,9 +1807,9 @@ class PublisherController(BasePublisherController): context_title = self._host.get_context_title() if context_title is None: - context_title = os.environ.get("AVALON_APP_NAME") + context_title = os.environ.get("AYON_APP_NAME") if context_title is None: - context_title = os.environ.get("AVALON_APP") + context_title = os.environ.get("AYON_HOST_NAME") return context_title diff --git a/client/ayon_core/tools/publisher/widgets/assets_widget.py b/client/ayon_core/tools/publisher/widgets/assets_widget.py index 8a72c03e8b..1c5016de99 100644 --- a/client/ayon_core/tools/publisher/widgets/assets_widget.py +++ b/client/ayon_core/tools/publisher/widgets/assets_widget.py @@ -5,13 +5,13 @@ from qtpy import QtWidgets, QtCore, QtGui from ayon_core.tools.utils import ( PlaceholderLineEdit, RecursiveSortFilterProxyModel, - get_asset_icon, ) from ayon_core.tools.utils.assets_widget import ( SingleSelectAssetsWidget, ASSET_ID_ROLE, ASSET_NAME_ROLE, ASSET_PATH_ROLE, + get_asset_icon, ) @@ -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): diff --git a/client/ayon_core/tools/publisher/widgets/create_widget.py b/client/ayon_core/tools/publisher/widgets/create_widget.py index 12135c6891..8eae205882 100644 --- a/client/ayon_core/tools/publisher/widgets/create_widget.py +++ b/client/ayon_core/tools/publisher/widgets/create_widget.py @@ -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) diff --git a/client/ayon_core/tools/publisher/widgets/tasks_widget.py b/client/ayon_core/tools/publisher/widgets/tasks_widget.py index 44e290408a..9a1b22b9a5 100644 --- a/client/ayon_core/tools/publisher/widgets/tasks_widget.py +++ b/client/ayon_core/tools/publisher/widgets/tasks_widget.py @@ -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() diff --git a/client/ayon_core/tools/push_to_project/models/integrate.py b/client/ayon_core/tools/push_to_project/models/integrate.py index 175716cf10..3de1beb2d2 100644 --- a/client/ayon_core/tools/push_to_project/models/integrate.py +++ b/client/ayon_core/tools/push_to_project/models/integrate.py @@ -8,8 +8,6 @@ import sys import traceback import uuid -from bson.objectid import ObjectId - from ayon_core.client import ( get_project, get_assets, @@ -1080,7 +1078,6 @@ class ProjectPushItemProcess: new_repre_files = [] for (path, rootless_path) in repre_filepaths: new_repre_files.append({ - "_id": ObjectId(), "path": rootless_path, "size": os.path.getsize(path), "hash": source_hash(path), diff --git a/client/ayon_core/tools/sceneinventory/control.py b/client/ayon_core/tools/sceneinventory/control.py index 409f92b506..54e4e9941e 100644 --- a/client/ayon_core/tools/sceneinventory/control.py +++ b/client/ayon_core/tools/sceneinventory/control.py @@ -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. """ diff --git a/client/ayon_core/tools/texture_copy/app.py b/client/ayon_core/tools/texture_copy/app.py index 064f4e5577..eef648eaf9 100644 --- a/client/ayon_core/tools/texture_copy/app.py +++ b/client/ayon_core/tools/texture_copy/app.py @@ -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): @@ -137,8 +132,8 @@ class TextureCopy: def texture_copy(asset, project, path): t.echo("*** Running Texture tool ***") t.echo(">>> Initializing avalon session ...") - os.environ["AVALON_PROJECT"] = project - os.environ["AVALON_ASSET"] = asset + os.environ["AYON_PROJECT_NAME"] = project + os.environ["AYON_FOLDER_PATH"] = asset TextureCopy().process(asset, project, path) diff --git a/client/ayon_core/tools/utils/__init__.py b/client/ayon_core/tools/utils/__init__.py index 7be0ea5e9f..445b4d9b97 100644 --- a/client/ayon_core/tools/utils/__init__.py +++ b/client/ayon_core/tools/utils/__init__.py @@ -37,10 +37,6 @@ from .lib import ( get_qt_app, get_ayon_qt_app, get_openpype_qt_app, - get_asset_icon, - get_asset_icon_by_name, - get_asset_icon_name_from_doc, - get_asset_icon_color_from_doc, ) from .models import ( @@ -100,10 +96,6 @@ __all__ = ( "get_qt_app", "get_ayon_qt_app", "get_openpype_qt_app", - "get_asset_icon", - "get_asset_icon_by_name", - "get_asset_icon_name_from_doc", - "get_asset_icon_color_from_doc", "RecursiveSortFilterProxyModel", diff --git a/client/ayon_core/tools/utils/assets_widget.py b/client/ayon_core/tools/utils/assets_widget.py index c05f3de850..7c3fd8d97c 100644 --- a/client/ayon_core/tools/utils/assets_widget.py +++ b/client/ayon_core/tools/utils/assets_widget.py @@ -10,6 +10,7 @@ from ayon_core.client import ( ) from ayon_core.style import ( get_default_tools_icon_color, + get_default_entity_icon_color, ) from ayon_core.tools.flickcharm import FlickCharm @@ -21,7 +22,7 @@ from .widgets import PlaceholderLineEdit from .models import RecursiveSortFilterProxyModel from .lib import ( DynamicQThread, - get_asset_icon + get_qta_icon_by_name_and_color ) ASSET_ID_ROLE = QtCore.Qt.UserRole + 1 @@ -31,6 +32,59 @@ ASSET_UNDERLINE_COLORS_ROLE = QtCore.Qt.UserRole + 4 ASSET_PATH_ROLE = QtCore.Qt.UserRole + 5 +def _get_default_asset_icon_name(has_children): + if has_children: + return "fa.folder" + return "fa.folder-o" + + +def _get_asset_icon_color_from_doc(asset_doc): + if asset_doc: + return asset_doc["data"].get("color") + return None + + +def _get_asset_icon_name_from_doc(asset_doc): + if asset_doc: + return asset_doc["data"].get("icon") + return None + + +def _get_asset_icon_color(asset_doc): + icon_color = _get_asset_icon_color_from_doc(asset_doc) + if icon_color: + return icon_color + return get_default_entity_icon_color() + + +def _get_asset_icon_name(asset_doc, has_children=True): + icon_name = _get_asset_icon_name_from_doc(asset_doc) + if icon_name: + return icon_name + return _get_default_asset_icon_name(has_children) + + +def get_asset_icon(asset_doc, has_children=False): + """Get asset icon. + + Deprecated: + This function will be removed in future releases. Use on your own + risk. + + Args: + asset_doc (dict): Asset document. + has_children (Optional[bool]): Asset has children assets. + + Returns: + QIcon: Asset icon. + + """ + icon_name = _get_asset_icon_name(asset_doc, has_children) + icon_color = _get_asset_icon_color(asset_doc) + + return get_qta_icon_by_name_and_color(icon_name, icon_color) + + class _AssetsView(TreeViewSpinner, DeselectableTreeView): """Asset items view. @@ -111,7 +165,6 @@ class _AssetModel(QtGui.QStandardItemModel): 'refreshed' signal. Args: - dbcon (AvalonMongoDB): Ready to use connection to mongo with. parent (QObject): Parent Qt object. """ @@ -128,9 +181,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 +194,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 +238,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 +260,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 +279,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 +403,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 +438,6 @@ class _AssetsWidget(QtWidgets.QWidget): inheritance changes. Args: - dbcon (AvalonMongoDB): Connection to avalon mongo db. parent (QWidget): Parent Qt widget. """ @@ -404,11 +449,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 +520,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 +562,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 +571,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) diff --git a/client/ayon_core/tools/utils/lib.py b/client/ayon_core/tools/utils/lib.py index b7edd6be71..e785cec390 100644 --- a/client/ayon_core/tools/utils/lib.py +++ b/client/ayon_core/tools/utils/lib.py @@ -234,62 +234,6 @@ def get_qta_icon_by_name_and_color(icon_name, icon_color): return icon -def get_asset_icon_name(asset_doc, has_children=True): - icon_name = get_asset_icon_name_from_doc(asset_doc) - if icon_name: - return icon_name - return get_default_asset_icon_name(has_children) - - -def get_asset_icon_color(asset_doc): - icon_color = get_asset_icon_color_from_doc(asset_doc) - if icon_color: - return icon_color - return get_default_entity_icon_color() - - -def get_default_asset_icon_name(has_children): - if has_children: - return "fa.folder" - return "fa.folder-o" - - -def get_asset_icon_name_from_doc(asset_doc): - if asset_doc: - return asset_doc["data"].get("icon") - return None - - -def get_asset_icon_color_from_doc(asset_doc): - if asset_doc: - return asset_doc["data"].get("color") - return None - - -def get_asset_icon_by_name(icon_name, icon_color, has_children=False): - if not icon_name: - icon_name = get_default_asset_icon_name(has_children) - - if icon_color: - icon_color = QtGui.QColor(icon_color) - else: - icon_color = get_default_entity_icon_color() - icon = get_qta_icon_by_name_and_color(icon_name, icon_color) - if icon is not None: - return icon - return get_qta_icon_by_name_and_color( - get_default_asset_icon_name(has_children), - icon_color - ) - - -def get_asset_icon(asset_doc, has_children=False): - icon_name = get_asset_icon_name(asset_doc, has_children) - icon_color = get_asset_icon_color(asset_doc) - - return get_qta_icon_by_name_and_color(icon_name, icon_color) - - def get_default_task_icon(color=None): if color is None: color = get_default_entity_icon_color() diff --git a/client/ayon_core/tools/utils/tasks_widget.py b/client/ayon_core/tools/utils/tasks_widget.py deleted file mode 100644 index 12e074f910..0000000000 --- a/client/ayon_core/tools/utils/tasks_widget.py +++ /dev/null @@ -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() diff --git a/client/ayon_core/tools/workfile_template_build/__init__.py b/client/ayon_core/tools/workfile_template_build/__init__.py index 82a22aea50..ad94ebcf79 100644 --- a/client/ayon_core/tools/workfile_template_build/__init__.py +++ b/client/ayon_core/tools/workfile_template_build/__init__.py @@ -1,5 +1,8 @@ from .window import WorkfileBuildPlaceholderDialog +from .lib import open_template_ui __all__ = ( "WorkfileBuildPlaceholderDialog", + + "open_template_ui" ) diff --git a/client/ayon_core/tools/workfile_template_build/lib.py b/client/ayon_core/tools/workfile_template_build/lib.py new file mode 100644 index 0000000000..de3a0d0084 --- /dev/null +++ b/client/ayon_core/tools/workfile_template_build/lib.py @@ -0,0 +1,28 @@ +import traceback + +from qtpy import QtWidgets + +from ayon_core.tools.utils.dialogs import show_message_dialog + + +def open_template_ui(builder, main_window): + """Open template from `builder` + + Asks user about overwriting current scene and feedsback exceptions. + """ + result = QtWidgets.QMessageBox.question( + main_window, + "Opening template", + "Caution! You will loose unsaved changes.\nDo you want to continue?", + QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No + ) + if result == QtWidgets.QMessageBox.Yes: + try: + builder.open_template() + except Exception: + show_message_dialog( + title="Template Load Failed", + message="".join(traceback.format_exc()), + parent=main_window, + level="critical" + ) diff --git a/client/ayon_core/tools/workfile_template_build/window.py b/client/ayon_core/tools/workfile_template_build/window.py index 7f95bac60a..feb11c5e75 100644 --- a/client/ayon_core/tools/workfile_template_build/window.py +++ b/client/ayon_core/tools/workfile_template_build/window.py @@ -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("AYON_HOST_NAME") or "NA" self._host_name = host_name plugins_combo = QtWidgets.QComboBox(self) diff --git a/client/ayon_core/version.py b/client/ayon_core/version.py index 914e415b8c..f3ad9713d5 100644 --- a/client/ayon_core/version.py +++ b/client/ayon_core/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring AYON core addon version.""" -__version__ = "0.2.1-dev.1" +__version__ = "0.3.0-dev.1" diff --git a/client/pyproject.toml b/client/pyproject.toml index c21ca305a7..7b4329a31a 100644 --- a/client/pyproject.toml +++ b/client/pyproject.toml @@ -10,8 +10,6 @@ wsrpc_aiohttp = "^3.1.1" # websocket server Click = "^8" clique = "1.6.*" jsonschema = "^2.6.0" -pymongo = "^3.11.2" -log4mongo = "^1.7" pyblish-base = "^1.8.11" pynput = "^1.7.2" # Timers manager - TODO remove speedcopy = "^2.1" diff --git a/package.py b/package.py index d7266d852b..470bbf256b 100644 --- a/package.py +++ b/package.py @@ -1,8 +1,11 @@ name = "core" title = "Core" -version = "0.2.1-dev.1" +version = "0.3.0-dev.1" client_dir = "ayon_core" plugin_for = ["ayon_server"] -ayon_version = ">=1.0.3,<2.0.0" +requires = [ + "~ayon_server-1.0.3+<2.0.0", +] + diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index 8506801e7e..9b5f3ae571 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -9,7 +9,7 @@ from ayon_server.settings import ( task_types_enum, ) -from ayon_server.types import ColorRGBA_uint8 +from ayon_server.types import ColorRGB_uint8, ColorRGBA_uint8 class ValidateBaseModel(BaseSettingsModel): @@ -395,6 +395,14 @@ class ExtractReviewOutputDefModel(BaseSettingsModel): " same as output aspect ratio." ) ) + # overscan_color: ColorRGB_uint8 = SettingsField( + # (0, 0, 0), + # title="Overscan color", + # description=( + # "Overscan color is used when input aspect ratio is not" + # " same as output aspect ratio." + # ) + # ) width: int = SettingsField( 0, ge=0, @@ -901,7 +909,8 @@ DEFAULT_PUBLISH_VALUES = { "single_frame_filter": "single_frame" }, "overscan_crop": "", - "overscan_color": [0, 0, 0, 1.0], + # "overscan_color": [0, 0, 0], + "overscan_color": [0, 0, 0, 0.0], "width": 1920, "height": 1080, "scale_pixel_aspect": True, @@ -946,7 +955,8 @@ DEFAULT_PUBLISH_VALUES = { "single_frame_filter": "multi_frame" }, "overscan_crop": "", - "overscan_color": [0, 0, 0, 1.0], + # "overscan_color": [0, 0, 0], + "overscan_color": [0, 0, 0, 0.0], "width": 0, "height": 0, "scale_pixel_aspect": True, diff --git a/server_addon/applications/server/__init__.py b/server_addon/applications/server/__init__.py index e782e8a591..d5c2de3df3 100644 --- a/server_addon/applications/server/__init__.py +++ b/server_addon/applications/server/__init__.py @@ -92,8 +92,9 @@ class ApplicationsAddon(BaseServerAddon): settings_model = ApplicationsAddonSettings async def get_default_settings(self): - applications_path = os.path.join(self.addon_dir, "applications.json") - tools_path = os.path.join(self.addon_dir, "tools.json") + server_dir = os.path.join(self.addon_dir, "server") + applications_path = os.path.join(server_dir, "applications.json") + tools_path = os.path.join(server_dir, "tools.json") default_values = copy.deepcopy(DEFAULT_VALUES) with open(applications_path, "r") as stream: default_values.update(json.load(stream)) diff --git a/server_addon/create_ayon_addons.py b/server_addon/create_ayon_addons.py index 08443d6588..9553980f5d 100644 --- a/server_addon/create_ayon_addons.py +++ b/server_addon/create_ayon_addons.py @@ -40,6 +40,11 @@ IGNORED_HOSTS = [ IGNORED_MODULES = [] +PACKAGE_PY_TEMPLATE = """name = "{addon_name}" +version = "{addon_version}" +plugin_for = ["ayon_server"] +""" + class ZipFileLongPaths(zipfile.ZipFile): """Allows longer paths in zip files. @@ -144,18 +149,12 @@ def create_addon_zip( output_dir: Path, addon_name: str, addon_version: str, - keep_source: bool + keep_source: bool, ): zip_filepath = output_dir / f"{addon_name}-{addon_version}.zip" + addon_output_dir = output_dir / addon_name / addon_version with ZipFileLongPaths(zip_filepath, "w", zipfile.ZIP_DEFLATED) as zipf: - zipf.writestr( - "manifest.json", - json.dumps({ - "addon_name": addon_name, - "addon_version": addon_version - }) - ) # Add client code content to zip src_root = os.path.normpath(str(addon_output_dir.absolute())) src_root_offset = len(src_root) + 1 @@ -167,9 +166,10 @@ def create_addon_zip( for filename in filenames: src_path = os.path.join(root, filename) if rel_root: - dst_path = os.path.join("addon", rel_root, filename) + dst_path = os.path.join(rel_root, filename) else: - dst_path = os.path.join("addon", filename) + dst_path = filename + zipf.write(src_path, dst_path) if not keep_source: @@ -180,9 +180,8 @@ def create_addon_package( addon_dir: Path, output_dir: Path, create_zip: bool, - keep_source: bool + keep_source: bool, ): - server_dir = addon_dir / "server" addon_version = get_addon_version(addon_dir) addon_output_dir = output_dir / addon_dir.name / addon_version @@ -191,18 +190,21 @@ def create_addon_package( addon_output_dir.mkdir(parents=True) # Copy server content - src_root = os.path.normpath(str(server_dir.absolute())) - src_root_offset = len(src_root) + 1 - for root, _, filenames in os.walk(str(server_dir)): - dst_root = addon_output_dir - if root != src_root: - rel_root = root[src_root_offset:] - dst_root = dst_root / rel_root + package_py = addon_output_dir / "package.py" + addon_name = addon_dir.name + if addon_name == "royal_render": + addon_name = "royalrender" + package_py_content = PACKAGE_PY_TEMPLATE.format( + addon_name=addon_name, addon_version=addon_version + ) - dst_root.mkdir(parents=True, exist_ok=True) - for filename in filenames: - src_path = os.path.join(root, filename) - shutil.copy(src_path, str(dst_root)) + with open(package_py, "w+") as pkg_py: + pkg_py.write(package_py_content) + + server_dir = addon_dir / "server" + shutil.copytree( + server_dir, addon_output_dir / "server", dirs_exist_ok=True + ) if create_zip: create_addon_zip( diff --git a/server_addon/max/server/settings/publishers.py b/server_addon/max/server/settings/publishers.py index da782cb494..e8a48ec3d1 100644 --- a/server_addon/max/server/settings/publishers.py +++ b/server_addon/max/server/settings/publishers.py @@ -28,9 +28,9 @@ class ValidateAttributesModel(BaseSettingsModel): class FamilyMappingItemModel(BaseSettingsModel): - product_types: list[str] = SettingsField( + families: list[str] = SettingsField( default_factory=list, - title="Product Types" + title="Families" ) plugins: list[str] = SettingsField( default_factory=list, diff --git a/server_addon/max/server/version.py b/server_addon/max/server/version.py index bbab0242f6..1276d0254f 100644 --- a/server_addon/max/server/version.py +++ b/server_addon/max/server/version.py @@ -1 +1 @@ -__version__ = "0.1.4" +__version__ = "0.1.5" diff --git a/server_addon/maya/server/settings/loaders.py b/server_addon/maya/server/settings/loaders.py index 15d4275b80..418a7046ae 100644 --- a/server_addon/maya/server/settings/loaders.py +++ b/server_addon/maya/server/settings/loaders.py @@ -1,5 +1,5 @@ from ayon_server.settings import BaseSettingsModel, SettingsField -from ayon_server.types import ColorRGBA_uint8 +from ayon_server.types import ColorRGB_float, ColorRGBA_uint8 class ColorsSetting(BaseSettingsModel): @@ -35,6 +35,54 @@ class ColorsSetting(BaseSettingsModel): (99, 206, 220, 1.0), title="Yeti Cache:") yetiRig: ColorRGBA_uint8 = SettingsField( (0, 205, 125, 1.0), title="Yeti Rig:") + # model: ColorRGB_float = SettingsField( + # (0.82, 0.52, 0.12), title="Model:" + # ) + # rig: ColorRGB_float = SettingsField( + # (0.23, 0.89, 0.92), title="Rig:" + # ) + # pointcache: ColorRGB_float = SettingsField( + # (0.37, 0.82, 0.12), title="Pointcache:" + # ) + # animation: ColorRGB_float = SettingsField( + # (0.37, 0.82, 0.12), title="Animation:" + # ) + # ass: ColorRGB_float = SettingsField( + # (0.98, 0.53, 0.21), title="Arnold StandIn:" + # ) + # camera: ColorRGB_float = SettingsField( + # (0.53, 0.45, 0.96), title="Camera:" + # ) + # fbx: ColorRGB_float = SettingsField( + # (0.84, 0.65, 1.0), title="FBX:" + # ) + # mayaAscii: ColorRGB_float = SettingsField( + # (0.26, 0.68, 1.0), title="Maya Ascii:" + # ) + # mayaScene: ColorRGB_float = SettingsField( + # (0.26, 0.68, 1.0), title="Maya Scene:" + # ) + # setdress: ColorRGB_float = SettingsField( + # (1.0, 0.98, 0.35), title="Set Dress:" + # ) + # layout: ColorRGB_float = SettingsField( + # (1.0, 0.98, 0.35), title="Layout:" + # ) + # vdbcache: ColorRGB_float = SettingsField( + # (0.98, 0.21, 0.0), title="VDB Cache:" + # ) + # vrayproxy: ColorRGB_float = SettingsField( + # (1.0, 0.59, 0.05), title="VRay Proxy:" + # ) + # vrayscene_layer: ColorRGB_float = SettingsField( + # (1.0, 0.59, 0.05), title="VRay Scene:" + # ) + # yeticache: ColorRGB_float = SettingsField( + # (0.39, 0.81, 0.86), title="Yeti Cache:" + # ) + # yetiRig: ColorRGB_float = SettingsField( + # (0.0, 0.80, 0.49), title="Yeti Rig:" + # ) class ReferenceLoaderModel(BaseSettingsModel): @@ -67,54 +115,38 @@ class LoadersModel(BaseSettingsModel): DEFAULT_LOADERS_SETTING = { "colors": { - "model": [ - 209, 132, 30, 1.0 - ], - "rig": [ - 59, 226, 235, 1.0 - ], - "pointcache": [ - 94, 209, 30, 1.0 - ], - "animation": [ - 94, 209, 30, 1.0 - ], - "ass": [ - 249, 135, 53, 1.0 - ], - "camera": [ - 136, 114, 244, 1.0 - ], - "fbx": [ - 215, 166, 255, 1.0 - ], - "mayaAscii": [ - 67, 174, 255, 1.0 - ], - "mayaScene": [ - 67, 174, 255, 1.0 - ], - "setdress": [ - 255, 250, 90, 1.0 - ], - "layout": [ - 255, 250, 90, 1.0 - ], - "vdbcache": [ - 249, 54, 0, 1.0 - ], - "vrayproxy": [ - 255, 150, 12, 1.0 - ], - "vrayscene_layer": [ - 255, 150, 12, 1.0 - ], - "yeticache": [ - 99, 206, 220, 1.0 - ], - "yetiRig": [ - 0, 205, 125, 1.0 - ] + "model": [209, 132, 30, 1.0], + "rig": [59, 226, 235, 1.0], + "pointcache": [94, 209, 30, 1.0], + "animation": [94, 209, 30, 1.0], + "ass": [249, 135, 53, 1.0], + "camera": [136, 114, 244, 1.0], + "fbx": [215, 166, 255, 1.0], + "mayaAscii": [67, 174, 255, 1.0], + "mayaScene": [67, 174, 255, 1.0], + "setdress": [255, 250, 90, 1.0], + "layout": [255, 250, 90, 1.0], + "vdbcache": [249, 54, 0, 1.0], + "vrayproxy": [255, 150, 12, 1.0], + "vrayscene_layer": [255, 150, 12, 1.0], + "yeticache": [99, 206, 220, 1.0], + "yetiRig": [0, 205, 125, 1.0] + # "model": [0.82, 0.52, 0.12], + # "rig": [0.23, 0.89, 0.92], + # "pointcache": [0.37, 0.82, 0.12], + # "animation": [0.37, 0.82, 0.12], + # "ass": [0.98, 0.53, 0.21], + # "camera":[0.53, 0.45, 0.96], + # "fbx": [0.84, 0.65, 1.0], + # "mayaAscii": [0.26, 0.68, 1.0], + # "mayaScene": [0.26, 0.68, 1.0], + # "setdress": [1.0, 0.98, 0.35], + # "layout": [1.0, 0.98, 0.35], + # "vdbcache": [0.98, 0.21, 0.0], + # "vrayproxy": [1.0, 0.59, 0.05], + # "vrayscene_layer": [1.0, 0.59, 0.05], + # "yeticache": [0.39, 0.81, 0.86], + # "yetiRig": [0.0, 0.80, 0.49], }, "reference_loader": { "namespace": "{folder[name]}_{product[name]}_##_", diff --git a/server_addon/maya/server/settings/publish_playblast.py b/server_addon/maya/server/settings/publish_playblast.py index 0461a18cc8..39f48bacbe 100644 --- a/server_addon/maya/server/settings/publish_playblast.py +++ b/server_addon/maya/server/settings/publish_playblast.py @@ -6,7 +6,7 @@ from ayon_server.settings import ( ensure_unique_names, task_types_enum, ) -from ayon_server.types import ColorRGBA_uint8 +from ayon_server.types import ColorRGBA_uint8, ColorRGB_float def hardware_falloff_enum(): @@ -57,6 +57,9 @@ class DisplayOptionsSetting(BaseSettingsModel): background: ColorRGBA_uint8 = SettingsField( (125, 125, 125, 1.0), title="Background Color" ) + # background: ColorRGB_float = SettingsField( + # (0.5, 0.5, 0.5), title="Background Color" + # ) displayGradient: bool = SettingsField( True, title="Display background gradient" ) @@ -66,6 +69,12 @@ class DisplayOptionsSetting(BaseSettingsModel): backgroundBottom: ColorRGBA_uint8 = SettingsField( (125, 125, 125, 1.0), title="Background Bottom" ) + # backgroundTop: ColorRGB_float = SettingsField( + # (0.5, 0.5, 0.5), title="Background Top" + # ) + # backgroundBottom: ColorRGB_float = SettingsField( + # (0.5, 0.5, 0.5), title="Background Bottom" + # ) class GenericSetting(BaseSettingsModel): @@ -282,24 +291,12 @@ DEFAULT_PLAYBLAST_SETTING = { }, "DisplayOptions": { "override_display": True, - "background": [ - 125, - 125, - 125, - 1.0 - ], - "backgroundBottom": [ - 125, - 125, - 125, - 1.0 - ], - "backgroundTop": [ - 125, - 125, - 125, - 1.0 - ], + "background": [125, 125, 125, 1.0], + "backgroundBottom": [125, 125, 125, 1.0], + "backgroundTop": [125, 125, 125, 1.0], + # "background": [0.5, 0.5, 0.5], + # "backgroundBottom": [0.5, 0.5, 0.5], + # "backgroundTop": [0.5, 0.5, 0.5], "displayGradient": True }, "Generic": { diff --git a/server_addon/maya/server/version.py b/server_addon/maya/server/version.py index 684d830189..8202425a2d 100644 --- a/server_addon/maya/server/version.py +++ b/server_addon/maya/server/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring addon version.""" -__version__ = "0.1.8" +__version__ = "0.1.9" diff --git a/server_addon/photoshop/server/settings/publish_plugins.py b/server_addon/photoshop/server/settings/publish_plugins.py index c4a392d490..d04faaf53a 100644 --- a/server_addon/photoshop/server/settings/publish_plugins.py +++ b/server_addon/photoshop/server/settings/publish_plugins.py @@ -62,9 +62,9 @@ class CollectColorCodedInstancesPlugin(BaseSettingsModel): enum_resolver=lambda: create_flatten_image_enum, ) - flatten_product_type_template: str = SettingsField( + flatten_product_name_template: str = SettingsField( "", - title="Subset template for flatten image" + title="Product name template for flatten image" ) color_code_mapping: list[ColorCodeMappings] = SettingsField( @@ -178,7 +178,7 @@ class PhotoshopPublishPlugins(BaseSettingsModel): DEFAULT_PUBLISH_SETTINGS = { "CollectColorCodedInstances": { "create_flatten_image": "no", - "flatten_product_type_template": "", + "flatten_product_name_template": "", "color_code_mapping": [] }, "CollectReview": { diff --git a/server_addon/photoshop/server/version.py b/server_addon/photoshop/server/version.py index a242f0e757..df0c92f1e2 100644 --- a/server_addon/photoshop/server/version.py +++ b/server_addon/photoshop/server/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring addon version.""" -__version__ = "0.1.1" +__version__ = "0.1.2" diff --git a/server_addon/tvpaint/server/settings/publish_plugins.py b/server_addon/tvpaint/server/settings/publish_plugins.py index 0623524c92..0d978e5714 100644 --- a/server_addon/tvpaint/server/settings/publish_plugins.py +++ b/server_addon/tvpaint/server/settings/publish_plugins.py @@ -1,5 +1,5 @@ from ayon_server.settings import BaseSettingsModel, SettingsField -from ayon_server.types import ColorRGBA_uint8 +from ayon_server.types import ColorRGBA_uint8, ColorRGB_uint8 class CollectRenderInstancesModel(BaseSettingsModel): @@ -10,10 +10,12 @@ class CollectRenderInstancesModel(BaseSettingsModel): class ExtractSequenceModel(BaseSettingsModel): """Review BG color is used for whole scene review and for thumbnails.""" - # TODO Use alpha color review_bg: ColorRGBA_uint8 = SettingsField( (255, 255, 255, 1.0), title="Review BG color") + # review_bg: ColorRGB_uint8 = SettingsField( + # (255, 255, 255), + # title="Review BG color") class ValidatePluginModel(BaseSettingsModel): @@ -100,6 +102,7 @@ DEFAULT_PUBLISH_SETTINGS = { "ignore_render_pass_transparency": False }, "ExtractSequence": { + # "review_bg": [255, 255, 255] "review_bg": [255, 255, 255, 1.0] }, "ValidateProjectSettings": { diff --git a/server_addon/tvpaint/server/version.py b/server_addon/tvpaint/server/version.py index 485f44ac21..b3f4756216 100644 --- a/server_addon/tvpaint/server/version.py +++ b/server_addon/tvpaint/server/version.py @@ -1 +1 @@ -__version__ = "0.1.1" +__version__ = "0.1.2"