diff --git a/CHANGELOG.md b/CHANGELOG.md
index f971c33208..7790894b7f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,16 +1,31 @@
# Changelog
-## [3.9.0-nightly.8](https://github.com/pypeclub/OpenPype/tree/HEAD)
+## [3.9.1-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD)
-[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.8.2...HEAD)
+[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.9.0...HEAD)
+
+**🐛 Bug fixes**
+
+- Harmony - fixed creator issue [\#2891](https://github.com/pypeclub/OpenPype/pull/2891)
+- General: Remove forgotten use of avalon Creator [\#2885](https://github.com/pypeclub/OpenPype/pull/2885)
+- General: Avoid circular import [\#2884](https://github.com/pypeclub/OpenPype/pull/2884)
+- Fixes for attaching loaded containers \(\#2837\) [\#2874](https://github.com/pypeclub/OpenPype/pull/2874)
+
+**🔀 Refactored code**
+
+- General: Reduce style usage to OpenPype repository [\#2889](https://github.com/pypeclub/OpenPype/pull/2889)
+
+## [3.9.0](https://github.com/pypeclub/OpenPype/tree/3.9.0) (2022-03-14)
+
+[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.9.0-nightly.9...3.9.0)
**Deprecated:**
- AssetCreator: Remove the tool [\#2845](https://github.com/pypeclub/OpenPype/pull/2845)
-- Houdini: Remove unused code [\#2779](https://github.com/pypeclub/OpenPype/pull/2779)
**🚀 Enhancements**
+- General: Subset name filtering in ExtractReview outpus [\#2872](https://github.com/pypeclub/OpenPype/pull/2872)
- NewPublisher: Descriptions and Icons in creator dialog [\#2867](https://github.com/pypeclub/OpenPype/pull/2867)
- NewPublisher: Changing task on publishing instance [\#2863](https://github.com/pypeclub/OpenPype/pull/2863)
- TrayPublisher: Choose project widget is more clear [\#2859](https://github.com/pypeclub/OpenPype/pull/2859)
@@ -22,11 +37,12 @@
- global: letter box calculated on output as last process [\#2812](https://github.com/pypeclub/OpenPype/pull/2812)
- Nuke: adding Reformat to baking mov plugin [\#2811](https://github.com/pypeclub/OpenPype/pull/2811)
- Manager: Update all to latest button [\#2805](https://github.com/pypeclub/OpenPype/pull/2805)
-- General: Set context environments for non host applications [\#2803](https://github.com/pypeclub/OpenPype/pull/2803)
**🐛 Bug fixes**
+- General: Missing time function [\#2877](https://github.com/pypeclub/OpenPype/pull/2877)
- Deadline: Fix plugin name for tile assemble [\#2868](https://github.com/pypeclub/OpenPype/pull/2868)
+- Nuke: gizmo precollect fix [\#2866](https://github.com/pypeclub/OpenPype/pull/2866)
- General: Fix hardlink for windows [\#2864](https://github.com/pypeclub/OpenPype/pull/2864)
- General: ffmpeg was crashing on slate merge [\#2860](https://github.com/pypeclub/OpenPype/pull/2860)
- WebPublisher: Video file was published with one too many frame [\#2858](https://github.com/pypeclub/OpenPype/pull/2858)
@@ -35,6 +51,7 @@
- Nuke: slate resolution to input video resolution [\#2853](https://github.com/pypeclub/OpenPype/pull/2853)
- WebPublisher: Fix username stored in DB [\#2852](https://github.com/pypeclub/OpenPype/pull/2852)
- WebPublisher: Fix wrong number of frames for video file [\#2851](https://github.com/pypeclub/OpenPype/pull/2851)
+- Nuke: Fix family test in validate\_write\_legacy to work with stillImage [\#2847](https://github.com/pypeclub/OpenPype/pull/2847)
- Nuke: fix multiple baking profile farm publishing [\#2842](https://github.com/pypeclub/OpenPype/pull/2842)
- Blender: Fixed parameters for FBX export of the camera [\#2840](https://github.com/pypeclub/OpenPype/pull/2840)
- Maya: Stop creation of reviews for Cryptomattes [\#2832](https://github.com/pypeclub/OpenPype/pull/2832)
@@ -47,23 +64,18 @@
- Ftrack: Job killer with missing user [\#2819](https://github.com/pypeclub/OpenPype/pull/2819)
- Nuke: Use AVALON\_APP to get value for "app" key [\#2818](https://github.com/pypeclub/OpenPype/pull/2818)
- StandalonePublisher: use dynamic groups in subset names [\#2816](https://github.com/pypeclub/OpenPype/pull/2816)
-- Settings UI: Search case sensitivity [\#2810](https://github.com/pypeclub/OpenPype/pull/2810)
-- Flame Babypublisher optimalization [\#2806](https://github.com/pypeclub/OpenPype/pull/2806)
**🔀 Refactored code**
+- Refactor: move webserver tool to openpype [\#2876](https://github.com/pypeclub/OpenPype/pull/2876)
- General: Move create logic from avalon to OpenPype [\#2854](https://github.com/pypeclub/OpenPype/pull/2854)
- General: Add vendors from avalon [\#2848](https://github.com/pypeclub/OpenPype/pull/2848)
+- General: Basic event system [\#2846](https://github.com/pypeclub/OpenPype/pull/2846)
- General: Move change context functions [\#2839](https://github.com/pypeclub/OpenPype/pull/2839)
- Tools: Don't use avalon tools code [\#2829](https://github.com/pypeclub/OpenPype/pull/2829)
- Move Unreal Implementation to OpenPype [\#2823](https://github.com/pypeclub/OpenPype/pull/2823)
- General: Extract template formatting from anatomy [\#2766](https://github.com/pypeclub/OpenPype/pull/2766)
-**Merged pull requests:**
-
-- Nuke: gizmo precollect fix [\#2866](https://github.com/pypeclub/OpenPype/pull/2866)
-- Nuke: Fix family test in validate\_write\_legacy to work with stillImage [\#2847](https://github.com/pypeclub/OpenPype/pull/2847)
-
## [3.8.2](https://github.com/pypeclub/OpenPype/tree/3.8.2) (2022-02-07)
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.8.2-nightly.3...3.8.2)
diff --git a/openpype/__init__.py b/openpype/__init__.py
index 942112835b..99629a4257 100644
--- a/openpype/__init__.py
+++ b/openpype/__init__.py
@@ -5,13 +5,13 @@ import platform
import functools
import logging
-from openpype.pipeline import LegacyCreator
from .settings import get_project_settings
from .lib import (
Anatomy,
filter_pyblish_plugins,
set_plugin_attributes_from_settings,
- change_timer_to_current_context
+ change_timer_to_current_context,
+ register_event_callback,
)
pyblish = avalon = _original_discover = None
@@ -75,6 +75,10 @@ def install():
"""Install Pype to Avalon."""
from pyblish.lib import MessageHandler
from openpype.modules import load_modules
+ from openpype.pipeline import (
+ LegacyCreator,
+ register_loader_plugin_path,
+ )
from avalon import pipeline
# Make sure modules are loaded
@@ -90,7 +94,7 @@ def install():
log.info("Registering global plug-ins..")
pyblish.register_plugin_path(PUBLISH_PATH)
pyblish.register_discovery_filter(filter_pyblish_plugins)
- avalon.register_plugin_path(avalon.Loader, LOAD_PATH)
+ register_loader_plugin_path(LOAD_PATH)
project_name = os.environ.get("AVALON_PROJECT")
@@ -118,7 +122,7 @@ def install():
continue
pyblish.register_plugin_path(path)
- avalon.register_plugin_path(avalon.Loader, path)
+ register_loader_plugin_path(path)
avalon.register_plugin_path(LegacyCreator, path)
avalon.register_plugin_path(avalon.InventoryAction, path)
@@ -128,20 +132,22 @@ def install():
avalon.discover = patched_discover
pipeline.discover = patched_discover
- avalon.on("taskChanged", _on_task_change)
+ register_event_callback("taskChanged", _on_task_change)
-def _on_task_change(*args):
+def _on_task_change():
change_timer_to_current_context()
@import_wrapper
def uninstall():
"""Uninstall Pype from Avalon."""
+ from openpype.pipeline import deregister_loader_plugin_path
+
log.info("Deregistering global plug-ins..")
pyblish.deregister_plugin_path(PUBLISH_PATH)
pyblish.deregister_discovery_filter(filter_pyblish_plugins)
- avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH)
+ deregister_loader_plugin_path(LOAD_PATH)
log.info("Global plug-ins unregistred")
# restore original discover
diff --git a/openpype/hosts/aftereffects/api/launch_logic.py b/openpype/hosts/aftereffects/api/launch_logic.py
index 97f14c9332..c549268978 100644
--- a/openpype/hosts/aftereffects/api/launch_logic.py
+++ b/openpype/hosts/aftereffects/api/launch_logic.py
@@ -15,7 +15,7 @@ from Qt import QtCore
from openpype.tools.utils import host_tools
from avalon import api
-from avalon.tools.webserver.app import WebServerTool
+from openpype.tools.adobe_webserver.app import WebServerTool
from .ws_stub import AfterEffectsServerStub
diff --git a/openpype/hosts/aftereffects/api/pipeline.py b/openpype/hosts/aftereffects/api/pipeline.py
index ef56e96155..681f1c51a7 100644
--- a/openpype/hosts/aftereffects/api/pipeline.py
+++ b/openpype/hosts/aftereffects/api/pipeline.py
@@ -9,8 +9,13 @@ from avalon import io, pipeline
from openpype import lib
from openpype.api import Logger
-from openpype.pipeline import LegacyCreator
+from openpype.pipeline import (
+ LegacyCreator,
+ register_loader_plugin_path,
+ deregister_loader_plugin_path,
+)
import openpype.hosts.aftereffects
+from openpype.lib import register_event_callback
from .launch_logic import get_stub
@@ -66,7 +71,7 @@ def install():
pyblish.api.register_host("aftereffects")
pyblish.api.register_plugin_path(PUBLISH_PATH)
- avalon.api.register_plugin_path(avalon.api.Loader, LOAD_PATH)
+ register_loader_plugin_path(LOAD_PATH)
avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH)
log.info(PUBLISH_PATH)
@@ -74,12 +79,12 @@ def install():
"instanceToggled", on_pyblish_instance_toggled
)
- avalon.api.on("application.launched", application_launch)
+ register_event_callback("application.launched", application_launch)
def uninstall():
pyblish.api.deregister_plugin_path(PUBLISH_PATH)
- avalon.api.deregister_plugin_path(avalon.api.Loader, LOAD_PATH)
+ deregister_loader_plugin_path(LOAD_PATH)
avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH)
diff --git a/openpype/hosts/aftereffects/api/plugin.py b/openpype/hosts/aftereffects/api/plugin.py
index fbe07663dd..29705cc5be 100644
--- a/openpype/hosts/aftereffects/api/plugin.py
+++ b/openpype/hosts/aftereffects/api/plugin.py
@@ -1,9 +1,8 @@
-import avalon.api
+from openpype.pipeline import LoaderPlugin
from .launch_logic import get_stub
-class AfterEffectsLoader(avalon.api.Loader):
+class AfterEffectsLoader(LoaderPlugin):
@staticmethod
def get_stub():
return get_stub()
-
diff --git a/openpype/hosts/aftereffects/api/ws_stub.py b/openpype/hosts/aftereffects/api/ws_stub.py
index 5a0600e92e..b0893310c1 100644
--- a/openpype/hosts/aftereffects/api/ws_stub.py
+++ b/openpype/hosts/aftereffects/api/ws_stub.py
@@ -8,7 +8,7 @@ import logging
import attr
from wsrpc_aiohttp import WebSocketAsync
-from avalon.tools.webserver.app import WebServerTool
+from openpype.tools.adobe_webserver.app import WebServerTool
@attr.s
diff --git a/openpype/hosts/aftereffects/plugins/load/load_background.py b/openpype/hosts/aftereffects/plugins/load/load_background.py
index 1a2d6fc432..be43cae44e 100644
--- a/openpype/hosts/aftereffects/plugins/load/load_background.py
+++ b/openpype/hosts/aftereffects/plugins/load/load_background.py
@@ -1,11 +1,10 @@
import re
-import avalon.api
-
from openpype.lib import (
get_background_layers,
get_unique_layer_name
)
+from openpype.pipeline import get_representation_path
from openpype.hosts.aftereffects.api import (
AfterEffectsLoader,
containerise
@@ -78,7 +77,7 @@ class BackgroundLoader(AfterEffectsLoader):
else: # switching version - keep same name
comp_name = container["namespace"]
- path = avalon.api.get_representation_path(representation)
+ path = get_representation_path(representation)
layers = get_background_layers(path)
comp = stub.reload_background(container["members"][1],
diff --git a/openpype/hosts/aftereffects/plugins/load/load_file.py b/openpype/hosts/aftereffects/plugins/load/load_file.py
index 9dbbf7aae1..9eb9e80a2c 100644
--- a/openpype/hosts/aftereffects/plugins/load/load_file.py
+++ b/openpype/hosts/aftereffects/plugins/load/load_file.py
@@ -1,8 +1,8 @@
import re
-import avalon.api
from openpype import lib
+from openpype.pipeline import get_representation_path
from openpype.hosts.aftereffects.api import (
AfterEffectsLoader,
containerise
@@ -92,7 +92,7 @@ class FileLoader(AfterEffectsLoader):
"{}_{}".format(context["asset"], context["subset"]))
else: # switching version - keep same name
layer_name = container["namespace"]
- path = avalon.api.get_representation_path(representation)
+ path = get_representation_path(representation)
# with aftereffects.maintained_selection(): # TODO
stub.replace_item(layer.id, path, stub.LOADED_ICON + layer_name)
stub.imprint(
diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py
index 1c9820ff22..07a7509dd7 100644
--- a/openpype/hosts/blender/api/pipeline.py
+++ b/openpype/hosts/blender/api/pipeline.py
@@ -14,8 +14,16 @@ import avalon.api
from avalon import io, schema
from avalon.pipeline import AVALON_CONTAINER_ID
-from openpype.pipeline import LegacyCreator
+from openpype.pipeline import (
+ LegacyCreator,
+ register_loader_plugin_path,
+ deregister_loader_plugin_path,
+)
from openpype.api import Logger
+from openpype.lib import (
+ register_event_callback,
+ emit_event
+)
import openpype.hosts.blender
HOST_DIR = os.path.dirname(os.path.abspath(openpype.hosts.blender.__file__))
@@ -46,13 +54,14 @@ def install():
pyblish.api.register_host("blender")
pyblish.api.register_plugin_path(str(PUBLISH_PATH))
- avalon.api.register_plugin_path(avalon.api.Loader, str(LOAD_PATH))
+ register_loader_plugin_path(str(LOAD_PATH))
avalon.api.register_plugin_path(LegacyCreator, str(CREATE_PATH))
lib.append_user_scripts()
- avalon.api.on("new", on_new)
- avalon.api.on("open", on_open)
+ register_event_callback("new", on_new)
+ register_event_callback("open", on_open)
+
_register_callbacks()
_register_events()
@@ -67,7 +76,7 @@ def uninstall():
pyblish.api.deregister_host("blender")
pyblish.api.deregister_plugin_path(str(PUBLISH_PATH))
- avalon.api.deregister_plugin_path(avalon.api.Loader, str(LOAD_PATH))
+ deregister_loader_plugin_path(str(LOAD_PATH))
avalon.api.deregister_plugin_path(LegacyCreator, str(CREATE_PATH))
if not IS_HEADLESS:
@@ -114,22 +123,22 @@ def set_start_end_frames():
scene.render.resolution_y = resolution_y
-def on_new(arg1, arg2):
+def on_new():
set_start_end_frames()
-def on_open(arg1, arg2):
+def on_open():
set_start_end_frames()
@bpy.app.handlers.persistent
def _on_save_pre(*args):
- avalon.api.emit("before_save", args)
+ emit_event("before.save")
@bpy.app.handlers.persistent
def _on_save_post(*args):
- avalon.api.emit("save", args)
+ emit_event("save")
@bpy.app.handlers.persistent
@@ -137,9 +146,9 @@ def _on_load_post(*args):
# Detect new file or opening an existing file
if bpy.data.filepath:
# Likely this was an open operation since it has a filepath
- avalon.api.emit("open", args)
+ emit_event("open")
else:
- avalon.api.emit("new", args)
+ emit_event("new")
ops.OpenFileCacher.post_load()
@@ -170,7 +179,7 @@ def _register_callbacks():
log.info("Installed event handler _on_load_post...")
-def _on_task_changed(*args):
+def _on_task_changed():
"""Callback for when the task in the context is changed."""
# TODO (jasper): Blender has no concept of projects or workspace.
@@ -187,7 +196,7 @@ def _on_task_changed(*args):
def _register_events():
"""Install callbacks for specific events."""
- avalon.api.on("taskChanged", _on_task_changed)
+ register_event_callback("taskChanged", _on_task_changed)
log.info("Installed event callback for 'taskChanged'...")
diff --git a/openpype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py
index 20d1e4c8db..3207f543b7 100644
--- a/openpype/hosts/blender/api/plugin.py
+++ b/openpype/hosts/blender/api/plugin.py
@@ -5,8 +5,10 @@ from typing import Dict, List, Optional
import bpy
-import avalon.api
-from openpype.pipeline import LegacyCreator
+from openpype.pipeline import (
+ LegacyCreator,
+ LoaderPlugin,
+)
from .pipeline import AVALON_CONTAINERS
from .ops import (
MainThreadItem,
@@ -145,13 +147,13 @@ class Creator(LegacyCreator):
return collection
-class Loader(avalon.api.Loader):
+class Loader(LoaderPlugin):
"""Base class for Loader plug-ins."""
hosts = ["blender"]
-class AssetLoader(avalon.api.Loader):
+class AssetLoader(LoaderPlugin):
"""A basic AssetLoader for Blender
This will implement the basic logic for linking/appending assets
diff --git a/openpype/hosts/blender/plugins/load/load_abc.py b/openpype/hosts/blender/plugins/load/load_abc.py
index 07800521c9..3daaeceffe 100644
--- a/openpype/hosts/blender/plugins/load/load_abc.py
+++ b/openpype/hosts/blender/plugins/load/load_abc.py
@@ -6,7 +6,7 @@ from typing import Dict, List, Optional
import bpy
-from avalon import api
+from openpype.pipeline import get_representation_path
from openpype.hosts.blender.api.pipeline import (
AVALON_CONTAINERS,
AVALON_PROPERTY,
@@ -178,7 +178,7 @@ class CacheModelLoader(plugin.AssetLoader):
"""
object_name = container["objectName"]
asset_group = bpy.data.objects.get(object_name)
- libpath = Path(api.get_representation_path(representation))
+ libpath = Path(get_representation_path(representation))
extension = libpath.suffix.lower()
self.log.info(
diff --git a/openpype/hosts/blender/plugins/load/load_action.py b/openpype/hosts/blender/plugins/load/load_action.py
index a9d8522220..3c8fe988f0 100644
--- a/openpype/hosts/blender/plugins/load/load_action.py
+++ b/openpype/hosts/blender/plugins/load/load_action.py
@@ -5,9 +5,13 @@ from pathlib import Path
from pprint import pformat
from typing import Dict, List, Optional
-from avalon import api, blender
import bpy
+from openpype.pipeline import get_representation_path
import openpype.hosts.blender.api.plugin
+from openpype.hosts.blender.api.pipeline import (
+ containerise_existing,
+ AVALON_PROPERTY,
+)
logger = logging.getLogger("openpype").getChild("blender").getChild("load_action")
@@ -49,7 +53,7 @@ class BlendActionLoader(openpype.hosts.blender.api.plugin.AssetLoader):
container = bpy.data.collections.new(lib_container)
container.name = container_name
- blender.pipeline.containerise_existing(
+ containerise_existing(
container,
name,
namespace,
@@ -57,8 +61,7 @@ class BlendActionLoader(openpype.hosts.blender.api.plugin.AssetLoader):
self.__class__.__name__,
)
- container_metadata = container.get(
- blender.pipeline.AVALON_PROPERTY)
+ container_metadata = container.get(AVALON_PROPERTY)
container_metadata["libpath"] = libpath
container_metadata["lib_container"] = lib_container
@@ -90,16 +93,16 @@ class BlendActionLoader(openpype.hosts.blender.api.plugin.AssetLoader):
anim_data.action.make_local()
- if not obj.get(blender.pipeline.AVALON_PROPERTY):
+ if not obj.get(AVALON_PROPERTY):
- obj[blender.pipeline.AVALON_PROPERTY] = dict()
+ obj[AVALON_PROPERTY] = dict()
- avalon_info = obj[blender.pipeline.AVALON_PROPERTY]
+ avalon_info = obj[AVALON_PROPERTY]
avalon_info.update({"container_name": container_name})
objects_list.append(obj)
- animation_container.pop(blender.pipeline.AVALON_PROPERTY)
+ animation_container.pop(AVALON_PROPERTY)
# Save the list of objects in the metadata container
container_metadata["objects"] = objects_list
@@ -128,7 +131,7 @@ class BlendActionLoader(openpype.hosts.blender.api.plugin.AssetLoader):
container["objectName"]
)
- libpath = Path(api.get_representation_path(representation))
+ libpath = Path(get_representation_path(representation))
extension = libpath.suffix.lower()
logger.info(
@@ -153,8 +156,7 @@ class BlendActionLoader(openpype.hosts.blender.api.plugin.AssetLoader):
f"Unsupported file: {libpath}"
)
- collection_metadata = collection.get(
- blender.pipeline.AVALON_PROPERTY)
+ collection_metadata = collection.get(AVALON_PROPERTY)
collection_libpath = collection_metadata["libpath"]
normalized_collection_libpath = (
@@ -225,16 +227,16 @@ class BlendActionLoader(openpype.hosts.blender.api.plugin.AssetLoader):
strip.action = anim_data.action
strip.action_frame_end = anim_data.action.frame_range[1]
- if not obj.get(blender.pipeline.AVALON_PROPERTY):
+ if not obj.get(AVALON_PROPERTY):
- obj[blender.pipeline.AVALON_PROPERTY] = dict()
+ obj[AVALON_PROPERTY] = dict()
- avalon_info = obj[blender.pipeline.AVALON_PROPERTY]
+ avalon_info = obj[AVALON_PROPERTY]
avalon_info.update({"container_name": collection.name})
objects_list.append(obj)
- anim_container.pop(blender.pipeline.AVALON_PROPERTY)
+ anim_container.pop(AVALON_PROPERTY)
# Save the list of objects in the metadata container
collection_metadata["objects"] = objects_list
@@ -266,8 +268,7 @@ class BlendActionLoader(openpype.hosts.blender.api.plugin.AssetLoader):
"Nested collections are not supported."
)
- collection_metadata = collection.get(
- blender.pipeline.AVALON_PROPERTY)
+ collection_metadata = collection.get(AVALON_PROPERTY)
objects = collection_metadata["objects"]
lib_container = collection_metadata["lib_container"]
diff --git a/openpype/hosts/blender/plugins/load/load_audio.py b/openpype/hosts/blender/plugins/load/load_audio.py
index e065150c15..b95c5db270 100644
--- a/openpype/hosts/blender/plugins/load/load_audio.py
+++ b/openpype/hosts/blender/plugins/load/load_audio.py
@@ -6,7 +6,7 @@ from typing import Dict, List, Optional
import bpy
-from avalon import api
+from openpype.pipeline import get_representation_path
from openpype.hosts.blender.api import plugin
from openpype.hosts.blender.api.pipeline import (
AVALON_CONTAINERS,
@@ -102,7 +102,7 @@ class AudioLoader(plugin.AssetLoader):
"""
object_name = container["objectName"]
asset_group = bpy.data.objects.get(object_name)
- libpath = Path(api.get_representation_path(representation))
+ libpath = Path(get_representation_path(representation))
self.log.info(
"Container: %s\nRepresentation: %s",
diff --git a/openpype/hosts/blender/plugins/load/load_camera_blend.py b/openpype/hosts/blender/plugins/load/load_camera_blend.py
index 61955f124d..6ed2e8a575 100644
--- a/openpype/hosts/blender/plugins/load/load_camera_blend.py
+++ b/openpype/hosts/blender/plugins/load/load_camera_blend.py
@@ -7,7 +7,7 @@ from typing import Dict, List, Optional
import bpy
-from avalon import api
+from openpype.pipeline import get_representation_path
from openpype.hosts.blender.api import plugin
from openpype.hosts.blender.api.pipeline import (
AVALON_CONTAINERS,
@@ -155,7 +155,7 @@ class BlendCameraLoader(plugin.AssetLoader):
"""
object_name = container["objectName"]
asset_group = bpy.data.objects.get(object_name)
- libpath = Path(api.get_representation_path(representation))
+ libpath = Path(get_representation_path(representation))
extension = libpath.suffix.lower()
self.log.info(
diff --git a/openpype/hosts/blender/plugins/load/load_camera_fbx.py b/openpype/hosts/blender/plugins/load/load_camera_fbx.py
index 175ddacf9f..626ed44f08 100644
--- a/openpype/hosts/blender/plugins/load/load_camera_fbx.py
+++ b/openpype/hosts/blender/plugins/load/load_camera_fbx.py
@@ -6,7 +6,7 @@ from typing import Dict, List, Optional
import bpy
-from avalon import api
+from openpype.pipeline import get_representation_path
from openpype.hosts.blender.api import plugin, lib
from openpype.hosts.blender.api.pipeline import (
AVALON_CONTAINERS,
@@ -143,7 +143,7 @@ class FbxCameraLoader(plugin.AssetLoader):
"""
object_name = container["objectName"]
asset_group = bpy.data.objects.get(object_name)
- libpath = Path(api.get_representation_path(representation))
+ libpath = Path(get_representation_path(representation))
extension = libpath.suffix.lower()
self.log.info(
diff --git a/openpype/hosts/blender/plugins/load/load_fbx.py b/openpype/hosts/blender/plugins/load/load_fbx.py
index c6e6af5592..2d249ef647 100644
--- a/openpype/hosts/blender/plugins/load/load_fbx.py
+++ b/openpype/hosts/blender/plugins/load/load_fbx.py
@@ -6,7 +6,7 @@ from typing import Dict, List, Optional
import bpy
-from avalon import api
+from openpype.pipeline import get_representation_path
from openpype.hosts.blender.api import plugin, lib
from openpype.hosts.blender.api.pipeline import (
AVALON_CONTAINERS,
@@ -187,7 +187,7 @@ class FbxModelLoader(plugin.AssetLoader):
"""
object_name = container["objectName"]
asset_group = bpy.data.objects.get(object_name)
- libpath = Path(api.get_representation_path(representation))
+ libpath = Path(get_representation_path(representation))
extension = libpath.suffix.lower()
self.log.info(
diff --git a/openpype/hosts/blender/plugins/load/load_layout_blend.py b/openpype/hosts/blender/plugins/load/load_layout_blend.py
index 7f8ae610c6..d87df3c010 100644
--- a/openpype/hosts/blender/plugins/load/load_layout_blend.py
+++ b/openpype/hosts/blender/plugins/load/load_layout_blend.py
@@ -6,9 +6,11 @@ from typing import Dict, List, Optional
import bpy
-from avalon import api
from openpype import lib
-from openpype.pipeline import legacy_create
+from openpype.pipeline import (
+ legacy_create,
+ get_representation_path,
+)
from openpype.hosts.blender.api import plugin
from openpype.hosts.blender.api.pipeline import (
AVALON_CONTAINERS,
@@ -309,7 +311,7 @@ class BlendLayoutLoader(plugin.AssetLoader):
"""
object_name = container["objectName"]
asset_group = bpy.data.objects.get(object_name)
- libpath = Path(api.get_representation_path(representation))
+ libpath = Path(get_representation_path(representation))
extension = libpath.suffix.lower()
self.log.info(
diff --git a/openpype/hosts/blender/plugins/load/load_layout_json.py b/openpype/hosts/blender/plugins/load/load_layout_json.py
index 5b5f9ab83d..0693937fec 100644
--- a/openpype/hosts/blender/plugins/load/load_layout_json.py
+++ b/openpype/hosts/blender/plugins/load/load_layout_json.py
@@ -7,7 +7,13 @@ from typing import Dict, Optional
import bpy
-from avalon import api
+from openpype.pipeline import (
+ discover_loader_plugins,
+ remove_container,
+ load_container,
+ get_representation_path,
+ loaders_from_representation,
+)
from openpype.hosts.blender.api.pipeline import (
AVALON_INSTANCES,
AVALON_CONTAINERS,
@@ -33,7 +39,7 @@ class JsonLayoutLoader(plugin.AssetLoader):
objects = list(asset_group.children)
for obj in objects:
- api.remove(obj.get(AVALON_PROPERTY))
+ remove_container(obj.get(AVALON_PROPERTY))
def _remove_animation_instances(self, asset_group):
instances = bpy.data.collections.get(AVALON_INSTANCES)
@@ -66,13 +72,13 @@ class JsonLayoutLoader(plugin.AssetLoader):
with open(libpath, "r") as fp:
data = json.load(fp)
- all_loaders = api.discover(api.Loader)
+ all_loaders = discover_loader_plugins()
for element in data:
reference = element.get('reference')
family = element.get('family')
- loaders = api.loaders_from_representation(all_loaders, reference)
+ loaders = loaders_from_representation(all_loaders, reference)
loader = self._get_loader(loaders, family)
if not loader:
@@ -102,7 +108,7 @@ class JsonLayoutLoader(plugin.AssetLoader):
# at this time it will not return anything. The assets will be
# loaded in the next Blender cycle, so we use the options to
# set the transform, parent and assign the action, if there is one.
- api.load(
+ load_container(
loader,
reference,
namespace=instance_name,
@@ -188,7 +194,7 @@ class JsonLayoutLoader(plugin.AssetLoader):
"""
object_name = container["objectName"]
asset_group = bpy.data.objects.get(object_name)
- libpath = Path(api.get_representation_path(representation))
+ libpath = Path(get_representation_path(representation))
extension = libpath.suffix.lower()
self.log.info(
diff --git a/openpype/hosts/blender/plugins/load/load_look.py b/openpype/hosts/blender/plugins/load/load_look.py
index 066ec0101b..70d1b95f02 100644
--- a/openpype/hosts/blender/plugins/load/load_look.py
+++ b/openpype/hosts/blender/plugins/load/load_look.py
@@ -8,7 +8,7 @@ import os
import json
import bpy
-from avalon import api
+from openpype.pipeline import get_representation_path
from openpype.hosts.blender.api import plugin
from openpype.hosts.blender.api.pipeline import (
containerise_existing,
@@ -140,7 +140,7 @@ class BlendLookLoader(plugin.AssetLoader):
def update(self, container: Dict, representation: Dict):
collection = bpy.data.collections.get(container["objectName"])
- libpath = Path(api.get_representation_path(representation))
+ libpath = Path(get_representation_path(representation))
extension = libpath.suffix.lower()
self.log.info(
diff --git a/openpype/hosts/blender/plugins/load/load_model.py b/openpype/hosts/blender/plugins/load/load_model.py
index 04ece0b338..18d01dcb29 100644
--- a/openpype/hosts/blender/plugins/load/load_model.py
+++ b/openpype/hosts/blender/plugins/load/load_model.py
@@ -6,7 +6,7 @@ from typing import Dict, List, Optional
import bpy
-from avalon import api
+from openpype.pipeline import get_representation_path
from openpype.hosts.blender.api import plugin
from openpype.hosts.blender.api.pipeline import (
AVALON_CONTAINERS,
@@ -195,7 +195,7 @@ class BlendModelLoader(plugin.AssetLoader):
"""
object_name = container["objectName"]
asset_group = bpy.data.objects.get(object_name)
- libpath = Path(api.get_representation_path(representation))
+ libpath = Path(get_representation_path(representation))
extension = libpath.suffix.lower()
self.log.info(
diff --git a/openpype/hosts/blender/plugins/load/load_rig.py b/openpype/hosts/blender/plugins/load/load_rig.py
index eacabd3447..cec088076c 100644
--- a/openpype/hosts/blender/plugins/load/load_rig.py
+++ b/openpype/hosts/blender/plugins/load/load_rig.py
@@ -6,11 +6,15 @@ from typing import Dict, List, Optional
import bpy
-from avalon import api
-from avalon.blender import lib as avalon_lib
from openpype import lib
-from openpype.pipeline import legacy_create
-from openpype.hosts.blender.api import plugin
+from openpype.pipeline import (
+ legacy_create,
+ get_representation_path,
+)
+from openpype.hosts.blender.api import (
+ plugin,
+ get_selection,
+)
from openpype.hosts.blender.api.pipeline import (
AVALON_CONTAINERS,
AVALON_PROPERTY,
@@ -263,7 +267,7 @@ class BlendRigLoader(plugin.AssetLoader):
if anim_file:
bpy.ops.import_scene.fbx(filepath=anim_file, anim_offset=0.0)
- imported = avalon_lib.get_selection()
+ imported = get_selection()
armature = [
o for o in asset_group.children if o.type == 'ARMATURE'][0]
@@ -307,7 +311,7 @@ class BlendRigLoader(plugin.AssetLoader):
"""
object_name = container["objectName"]
asset_group = bpy.data.objects.get(object_name)
- libpath = Path(api.get_representation_path(representation))
+ libpath = Path(get_representation_path(representation))
extension = libpath.suffix.lower()
self.log.info(
diff --git a/openpype/hosts/flame/api/pipeline.py b/openpype/hosts/flame/api/pipeline.py
index f802cf160b..930c6abe29 100644
--- a/openpype/hosts/flame/api/pipeline.py
+++ b/openpype/hosts/flame/api/pipeline.py
@@ -7,7 +7,11 @@ from avalon import api as avalon
from avalon.pipeline import AVALON_CONTAINER_ID
from pyblish import api as pyblish
from openpype.api import Logger
-from openpype.pipeline import LegacyCreator
+from openpype.pipeline import (
+ LegacyCreator,
+ register_loader_plugin_path,
+ deregister_loader_plugin_path,
+)
from .lib import (
set_segment_data_marker,
set_publish_attribute,
@@ -33,7 +37,7 @@ def install():
pyblish.register_host("flame")
pyblish.register_plugin_path(PUBLISH_PATH)
- avalon.register_plugin_path(avalon.Loader, LOAD_PATH)
+ register_loader_plugin_path(LOAD_PATH)
avalon.register_plugin_path(LegacyCreator, CREATE_PATH)
avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH)
log.info("OpenPype Flame plug-ins registred ...")
@@ -48,7 +52,7 @@ def uninstall():
log.info("Deregistering Flame plug-ins..")
pyblish.deregister_plugin_path(PUBLISH_PATH)
- avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH)
+ deregister_loader_plugin_path(LOAD_PATH)
avalon.deregister_plugin_path(LegacyCreator, CREATE_PATH)
avalon.deregister_plugin_path(avalon.InventoryAction, INVENTORY_PATH)
diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py
index 5221701a2f..4c9d3c5383 100644
--- a/openpype/hosts/flame/api/plugin.py
+++ b/openpype/hosts/flame/api/plugin.py
@@ -7,9 +7,11 @@ import six
import qargparse
from Qt import QtWidgets, QtCore
import openpype.api as openpype
-from openpype.pipeline import LegacyCreator
+from openpype.pipeline import (
+ LegacyCreator,
+ LoaderPlugin,
+)
from openpype import style
-import avalon.api as avalon
from . import (
lib as flib,
pipeline as fpipeline,
@@ -660,7 +662,7 @@ class PublishableClip:
# Publishing plugin functions
# Loader plugin functions
-class ClipLoader(avalon.Loader):
+class ClipLoader(LoaderPlugin):
"""A basic clip loader for Flame
This will implement the basic behavior for a loader to inherit from that
diff --git a/openpype/hosts/flame/plugins/load/load_clip.py b/openpype/hosts/flame/plugins/load/load_clip.py
index 8ba01d6937..8980f72cb8 100644
--- a/openpype/hosts/flame/plugins/load/load_clip.py
+++ b/openpype/hosts/flame/plugins/load/load_clip.py
@@ -172,7 +172,7 @@ class LoadClip(opfapi.ClipLoader):
# version_name = version.get("name", None)
# colorspace = version_data.get("colorspace", None)
# object_name = "{}_{}".format(name, namespace)
- # file = api.get_representation_path(representation).replace("\\", "/")
+ # file = get_representation_path(representation).replace("\\", "/")
# clip = track_item.source()
# # reconnect media to new path
diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py
index 194557e37a..2e3b84def8 100644
--- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py
+++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py
@@ -226,16 +226,10 @@ class ExtractSubsetResources(openpype.api.Extractor):
# imagesequence as list
if (
# first check if path in files is not mov extension
- next(
- # iter all paths in files
- # return only .mov positive test
- iter([
- f for f in files
- if ".mov" in os.path.splitext(f)[-1]
- ]),
- # if nothing return default
- None
- )
+ [
+ f for f in files
+ if os.path.splitext(f)[-1] == ".mov"
+ ]
# then try if thumbnail is not in unique name
or unique_name == "thumbnail"
):
diff --git a/openpype/hosts/fusion/api/lib.py b/openpype/hosts/fusion/api/lib.py
index 5d97f83032..2bb5ea8aae 100644
--- a/openpype/hosts/fusion/api/lib.py
+++ b/openpype/hosts/fusion/api/lib.py
@@ -5,8 +5,8 @@ import contextlib
from Qt import QtGui
-import avalon.api
from avalon import io
+from openpype.pipeline import switch_container
from .pipeline import get_current_comp, comp_lock_and_undo_chunk
self = sys.modules[__name__]
@@ -142,7 +142,7 @@ def switch_item(container,
assert representation, ("Could not find representation in the database "
"with the name '%s'" % representation_name)
- avalon.api.switch(container, representation)
+ switch_container(container, representation)
return representation
diff --git a/openpype/hosts/fusion/api/pipeline.py b/openpype/hosts/fusion/api/pipeline.py
index 5ac56fcbed..92e54ad6f5 100644
--- a/openpype/hosts/fusion/api/pipeline.py
+++ b/openpype/hosts/fusion/api/pipeline.py
@@ -11,7 +11,11 @@ import avalon.api
from avalon.pipeline import AVALON_CONTAINER_ID
from openpype.api import Logger
-from openpype.pipeline import LegacyCreator
+from openpype.pipeline import (
+ LegacyCreator,
+ register_loader_plugin_path,
+ deregister_loader_plugin_path,
+)
import openpype.hosts.fusion
log = Logger().get_logger(__name__)
@@ -63,7 +67,7 @@ def install():
pyblish.api.register_plugin_path(PUBLISH_PATH)
log.info("Registering Fusion plug-ins..")
- avalon.api.register_plugin_path(avalon.api.Loader, LOAD_PATH)
+ register_loader_plugin_path(LOAD_PATH)
avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH)
avalon.api.register_plugin_path(avalon.api.InventoryAction, INVENTORY_PATH)
@@ -87,7 +91,7 @@ def uninstall():
pyblish.api.deregister_plugin_path(PUBLISH_PATH)
log.info("Deregistering Fusion plug-ins..")
- avalon.api.deregister_plugin_path(avalon.api.Loader, LOAD_PATH)
+ deregister_loader_plugin_path(LOAD_PATH)
avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH)
avalon.api.deregister_plugin_path(
avalon.api.InventoryAction, INVENTORY_PATH
diff --git a/openpype/hosts/fusion/plugins/load/actions.py b/openpype/hosts/fusion/plugins/load/actions.py
index 6af99e4c56..bc59cec77f 100644
--- a/openpype/hosts/fusion/plugins/load/actions.py
+++ b/openpype/hosts/fusion/plugins/load/actions.py
@@ -2,10 +2,10 @@
"""
-from avalon import api
+from openpype.pipeline import load
-class FusionSetFrameRangeLoader(api.Loader):
+class FusionSetFrameRangeLoader(load.LoaderPlugin):
"""Specific loader of Alembic for the avalon.animation family"""
families = ["animation",
@@ -39,7 +39,7 @@ class FusionSetFrameRangeLoader(api.Loader):
lib.update_frame_range(start, end)
-class FusionSetFrameRangeWithHandlesLoader(api.Loader):
+class FusionSetFrameRangeWithHandlesLoader(load.LoaderPlugin):
"""Specific loader of Alembic for the avalon.animation family"""
families = ["animation",
diff --git a/openpype/hosts/fusion/plugins/load/load_sequence.py b/openpype/hosts/fusion/plugins/load/load_sequence.py
index ea118585bf..075820de35 100644
--- a/openpype/hosts/fusion/plugins/load/load_sequence.py
+++ b/openpype/hosts/fusion/plugins/load/load_sequence.py
@@ -1,8 +1,12 @@
import os
import contextlib
-from avalon import api, io
+from avalon import io
+from openpype.pipeline import (
+ load,
+ get_representation_path,
+)
from openpype.hosts.fusion.api import (
imprint_container,
get_current_comp,
@@ -117,7 +121,7 @@ def loader_shift(loader, frame, relative=True):
return int(shift)
-class FusionLoadSequence(api.Loader):
+class FusionLoadSequence(load.LoaderPlugin):
"""Load image sequence into Fusion"""
families = ["imagesequence", "review", "render"]
@@ -204,7 +208,7 @@ class FusionLoadSequence(api.Loader):
assert tool.ID == "Loader", "Must be Loader"
comp = tool.Comp()
- root = os.path.dirname(api.get_representation_path(representation))
+ root = os.path.dirname(get_representation_path(representation))
path = self._get_first_image(root)
# Get start frame from version data
diff --git a/openpype/hosts/harmony/api/README.md b/openpype/hosts/harmony/api/README.md
index a8d182736a..e8d354e1e6 100644
--- a/openpype/hosts/harmony/api/README.md
+++ b/openpype/hosts/harmony/api/README.md
@@ -575,7 +575,7 @@ replace_files = """function %s_replace_files(args)
""" % (signature, signature)
-class ImageSequenceLoader(api.Loader):
+class ImageSequenceLoader(load.LoaderPlugin):
"""Load images
Stores the imported asset in a container named after the asset.
"""
diff --git a/openpype/hosts/harmony/api/TB_sceneOpened.js b/openpype/hosts/harmony/api/TB_sceneOpened.js
index 5a3fe9ce82..6a403fa65e 100644
--- a/openpype/hosts/harmony/api/TB_sceneOpened.js
+++ b/openpype/hosts/harmony/api/TB_sceneOpened.js
@@ -272,8 +272,8 @@ function Client() {
app.avalonClient.send(
{
- 'module': 'avalon.api',
- 'method': 'emit',
+ 'module': 'openpype.lib',
+ 'method': 'emit_event',
'args': ['application.launched']
}, false);
};
diff --git a/openpype/hosts/harmony/api/pipeline.py b/openpype/hosts/harmony/api/pipeline.py
index 6d0f5e9416..f967da15ca 100644
--- a/openpype/hosts/harmony/api/pipeline.py
+++ b/openpype/hosts/harmony/api/pipeline.py
@@ -9,7 +9,12 @@ import avalon.api
from avalon.pipeline import AVALON_CONTAINER_ID
from openpype import lib
-from openpype.pipeline import LegacyCreator
+from openpype.lib import register_event_callback
+from openpype.pipeline import (
+ LegacyCreator,
+ register_loader_plugin_path,
+ deregister_loader_plugin_path,
+)
import openpype.hosts.harmony
import openpype.hosts.harmony.api as harmony
@@ -130,7 +135,7 @@ def check_inventory():
harmony.send({"function": "PypeHarmony.message", "args": msg})
-def application_launch():
+def application_launch(event):
"""Event that is executed after Harmony is launched."""
# FIXME: This is breaking server <-> client communication.
# It is now moved so it it manually called.
@@ -179,7 +184,7 @@ def install():
pyblish.api.register_host("harmony")
pyblish.api.register_plugin_path(PUBLISH_PATH)
- avalon.api.register_plugin_path(avalon.api.Loader, LOAD_PATH)
+ register_loader_plugin_path(LOAD_PATH)
avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH)
log.info(PUBLISH_PATH)
@@ -188,12 +193,12 @@ def install():
"instanceToggled", on_pyblish_instance_toggled
)
- avalon.api.on("application.launched", application_launch)
+ register_event_callback("application.launched", application_launch)
def uninstall():
pyblish.api.deregister_plugin_path(PUBLISH_PATH)
- avalon.api.deregister_plugin_path(avalon.api.Loader, LOAD_PATH)
+ deregister_loader_plugin_path(LOAD_PATH)
avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH)
diff --git a/openpype/hosts/harmony/plugins/load/load_audio.py b/openpype/hosts/harmony/plugins/load/load_audio.py
index 57ea8ae312..e18a6de097 100644
--- a/openpype/hosts/harmony/plugins/load/load_audio.py
+++ b/openpype/hosts/harmony/plugins/load/load_audio.py
@@ -1,4 +1,7 @@
-from avalon import api
+from openpype.pipeline import (
+ load,
+ get_representation_path,
+)
import openpype.hosts.harmony.api as harmony
sig = harmony.signature()
@@ -29,7 +32,7 @@ function %s(args)
""" % (sig, sig)
-class ImportAudioLoader(api.Loader):
+class ImportAudioLoader(load.LoaderPlugin):
"""Import audio."""
families = ["shot", "audio"]
@@ -37,7 +40,7 @@ class ImportAudioLoader(api.Loader):
label = "Import Audio"
def load(self, context, name=None, namespace=None, data=None):
- wav_file = api.get_representation_path(context["representation"])
+ wav_file = get_representation_path(context["representation"])
harmony.send(
{"function": func, "args": [context["subset"]["name"], wav_file]}
)
diff --git a/openpype/hosts/harmony/plugins/load/load_background.py b/openpype/hosts/harmony/plugins/load/load_background.py
index 686d6b5b7b..9c01fe3cd8 100644
--- a/openpype/hosts/harmony/plugins/load/load_background.py
+++ b/openpype/hosts/harmony/plugins/load/load_background.py
@@ -1,7 +1,10 @@
import os
import json
-from avalon import api
+from openpype.pipeline import (
+ load,
+ get_representation_path,
+)
import openpype.hosts.harmony.api as harmony
import openpype.lib
@@ -226,7 +229,7 @@ replace_files
"""
-class BackgroundLoader(api.Loader):
+class BackgroundLoader(load.LoaderPlugin):
"""Load images
Stores the imported asset in a container named after the asset.
"""
@@ -278,7 +281,7 @@ class BackgroundLoader(api.Loader):
def update(self, container, representation):
- path = api.get_representation_path(representation)
+ path = get_representation_path(representation)
with open(path) as json_file:
data = json.load(json_file)
@@ -297,7 +300,7 @@ class BackgroundLoader(api.Loader):
bg_folder = os.path.dirname(path)
- path = api.get_representation_path(representation)
+ path = get_representation_path(representation)
print(container)
diff --git a/openpype/hosts/harmony/plugins/load/load_imagesequence.py b/openpype/hosts/harmony/plugins/load/load_imagesequence.py
index 310f9bdb61..18695438d5 100644
--- a/openpype/hosts/harmony/plugins/load/load_imagesequence.py
+++ b/openpype/hosts/harmony/plugins/load/load_imagesequence.py
@@ -6,12 +6,15 @@ from pathlib import Path
import clique
-from avalon import api
+from openpype.pipeline import (
+ load,
+ get_representation_path,
+)
import openpype.hosts.harmony.api as harmony
import openpype.lib
-class ImageSequenceLoader(api.Loader):
+class ImageSequenceLoader(load.LoaderPlugin):
"""Load image sequences.
Stores the imported asset in a container named after the asset.
@@ -79,7 +82,7 @@ class ImageSequenceLoader(api.Loader):
self_name = self.__class__.__name__
node = container.get("nodes").pop()
- path = api.get_representation_path(representation)
+ path = get_representation_path(representation)
collections, remainder = clique.assemble(
os.listdir(os.path.dirname(path))
)
diff --git a/openpype/hosts/harmony/plugins/load/load_palette.py b/openpype/hosts/harmony/plugins/load/load_palette.py
index 2e0f70d135..1da3e61e1b 100644
--- a/openpype/hosts/harmony/plugins/load/load_palette.py
+++ b/openpype/hosts/harmony/plugins/load/load_palette.py
@@ -1,11 +1,14 @@
import os
import shutil
-from avalon import api
+from openpype.pipeline import (
+ load,
+ get_representation_path,
+)
import openpype.hosts.harmony.api as harmony
-class ImportPaletteLoader(api.Loader):
+class ImportPaletteLoader(load.LoaderPlugin):
"""Import palettes."""
families = ["palette", "harmony.palette"]
@@ -31,7 +34,7 @@ class ImportPaletteLoader(api.Loader):
scene_path = harmony.send(
{"function": "scene.currentProjectPath"}
)["result"]
- src = api.get_representation_path(representation)
+ src = get_representation_path(representation)
dst = os.path.join(
scene_path,
"palette-library",
diff --git a/openpype/hosts/harmony/plugins/load/load_template.py b/openpype/hosts/harmony/plugins/load/load_template.py
index 112e613ae6..c6dc9d913b 100644
--- a/openpype/hosts/harmony/plugins/load/load_template.py
+++ b/openpype/hosts/harmony/plugins/load/load_template.py
@@ -6,12 +6,15 @@ import os
import shutil
import uuid
-from avalon import api
+from openpype.pipeline import (
+ load,
+ get_representation_path,
+)
import openpype.hosts.harmony.api as harmony
import openpype.lib
-class TemplateLoader(api.Loader):
+class TemplateLoader(load.LoaderPlugin):
"""Load Harmony template as container.
.. todo::
@@ -38,7 +41,7 @@ class TemplateLoader(api.Loader):
# Load template.
self_name = self.__class__.__name__
temp_dir = tempfile.mkdtemp()
- zip_file = api.get_representation_path(context["representation"])
+ zip_file = get_representation_path(context["representation"])
template_path = os.path.join(temp_dir, "temp.tpl")
with zipfile.ZipFile(zip_file, "r") as zip_ref:
zip_ref.extractall(template_path)
diff --git a/openpype/hosts/harmony/plugins/load/load_template_workfile.py b/openpype/hosts/harmony/plugins/load/load_template_workfile.py
index c21b8194b1..2b84a43b35 100644
--- a/openpype/hosts/harmony/plugins/load/load_template_workfile.py
+++ b/openpype/hosts/harmony/plugins/load/load_template_workfile.py
@@ -3,11 +3,14 @@ import zipfile
import os
import shutil
-from avalon import api
+from openpype.pipeline import (
+ load,
+ get_representation_path,
+)
import openpype.hosts.harmony.api as harmony
-class ImportTemplateLoader(api.Loader):
+class ImportTemplateLoader(load.LoaderPlugin):
"""Import templates."""
families = ["harmony.template", "workfile"]
@@ -17,7 +20,7 @@ class ImportTemplateLoader(api.Loader):
def load(self, context, name=None, namespace=None, data=None):
# Import template.
temp_dir = tempfile.mkdtemp()
- zip_file = api.get_representation_path(context["representation"])
+ zip_file = get_representation_path(context["representation"])
template_path = os.path.join(temp_dir, "temp.tpl")
with zipfile.ZipFile(zip_file, "r") as zip_ref:
zip_ref.extractall(template_path)
diff --git a/openpype/hosts/harmony/plugins/publish/collect_farm_render.py b/openpype/hosts/harmony/plugins/publish/collect_farm_render.py
index 85237094e4..35b123f97d 100644
--- a/openpype/hosts/harmony/plugins/publish/collect_farm_render.py
+++ b/openpype/hosts/harmony/plugins/publish/collect_farm_render.py
@@ -5,6 +5,7 @@ from pathlib import Path
import attr
from avalon import api
+from openpype.lib import get_formatted_current_time
import openpype.lib.abstract_collect_render
import openpype.hosts.harmony.api as harmony
from openpype.lib.abstract_collect_render import RenderInstance
@@ -138,7 +139,7 @@ class CollectFarmRender(openpype.lib.abstract_collect_render.
render_instance = HarmonyRenderInstance(
version=version,
- time=api.time(),
+ time=get_formatted_current_time(),
source=context.data["currentFile"],
label=node.split("/")[1],
subset=subset_name,
diff --git a/openpype/hosts/hiero/api/events.py b/openpype/hosts/hiero/api/events.py
index 7563503593..9439199933 100644
--- a/openpype/hosts/hiero/api/events.py
+++ b/openpype/hosts/hiero/api/events.py
@@ -1,12 +1,12 @@
import os
import hiero.core.events
-import avalon.api as avalon
from openpype.api import Logger
from .lib import (
sync_avalon_data_to_workfile,
launch_workfiles_app,
selection_changed_timeline,
- before_project_save
+ before_project_save,
+ register_event_callback
)
from .tags import add_tags_to_workfile
from .menu import update_menu_task_label
@@ -126,5 +126,5 @@ def register_events():
"""
# if task changed then change notext of hiero
- avalon.on("taskChanged", update_menu_task_label)
+ register_event_callback("taskChanged", update_menu_task_label)
log.info("Installed event callback for 'taskChanged'..")
diff --git a/openpype/hosts/hiero/api/menu.py b/openpype/hosts/hiero/api/menu.py
index 306bef87ca..de20b86f30 100644
--- a/openpype/hosts/hiero/api/menu.py
+++ b/openpype/hosts/hiero/api/menu.py
@@ -14,7 +14,7 @@ self = sys.modules[__name__]
self._change_context_menu = None
-def update_menu_task_label(*args):
+def update_menu_task_label():
"""Update the task label in Avalon menu to current session"""
object_name = self._change_context_menu
diff --git a/openpype/hosts/hiero/api/pipeline.py b/openpype/hosts/hiero/api/pipeline.py
index 5cb23ea355..eff126c0b6 100644
--- a/openpype/hosts/hiero/api/pipeline.py
+++ b/openpype/hosts/hiero/api/pipeline.py
@@ -9,7 +9,11 @@ from avalon import api as avalon
from avalon import schema
from pyblish import api as pyblish
from openpype.api import Logger
-from openpype.pipeline import LegacyCreator
+from openpype.pipeline import (
+ LegacyCreator,
+ register_loader_plugin_path,
+ deregister_loader_plugin_path,
+)
from openpype.tools.utils import host_tools
from . import lib, menu, events
@@ -45,7 +49,7 @@ def install():
log.info("Registering Hiero plug-ins..")
pyblish.register_host("hiero")
pyblish.register_plugin_path(PUBLISH_PATH)
- avalon.register_plugin_path(avalon.Loader, LOAD_PATH)
+ register_loader_plugin_path(LOAD_PATH)
avalon.register_plugin_path(LegacyCreator, CREATE_PATH)
avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH)
@@ -67,7 +71,7 @@ def uninstall():
log.info("Deregistering Hiero plug-ins..")
pyblish.deregister_host("hiero")
pyblish.deregister_plugin_path(PUBLISH_PATH)
- avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH)
+ deregister_loader_plugin_path(LOAD_PATH)
avalon.deregister_plugin_path(LegacyCreator, CREATE_PATH)
# register callback for switching publishable
diff --git a/openpype/hosts/hiero/api/plugin.py b/openpype/hosts/hiero/api/plugin.py
index 53928aca41..54e66bf99a 100644
--- a/openpype/hosts/hiero/api/plugin.py
+++ b/openpype/hosts/hiero/api/plugin.py
@@ -6,9 +6,9 @@ import hiero
from Qt import QtWidgets, QtCore
import qargparse
-import avalon.api as avalon
+
import openpype.api as openpype
-from openpype.pipeline import LegacyCreator
+from openpype.pipeline import LoaderPlugin, LegacyCreator
from . import lib
log = openpype.Logger().get_logger(__name__)
@@ -306,7 +306,7 @@ def get_reference_node_parents(ref):
return parents
-class SequenceLoader(avalon.Loader):
+class SequenceLoader(LoaderPlugin):
"""A basic SequenceLoader for Resolve
This will implement the basic behavior for a loader to inherit from that
diff --git a/openpype/hosts/hiero/plugins/load/load_clip.py b/openpype/hosts/hiero/plugins/load/load_clip.py
index b905dd4431..d3908695a2 100644
--- a/openpype/hosts/hiero/plugins/load/load_clip.py
+++ b/openpype/hosts/hiero/plugins/load/load_clip.py
@@ -1,4 +1,5 @@
-from avalon import io, api
+from avalon import io
+from openpype.pipeline import get_representation_path
import openpype.hosts.hiero.api as phiero
# from openpype.hosts.hiero.api import plugin, lib
# reload(lib)
@@ -112,7 +113,7 @@ class LoadClip(phiero.SequenceLoader):
version_name = version.get("name", None)
colorspace = version_data.get("colorspace", None)
object_name = "{}_{}".format(name, namespace)
- file = api.get_representation_path(representation).replace("\\", "/")
+ file = get_representation_path(representation).replace("\\", "/")
clip = track_item.source()
# reconnect media to new path
diff --git a/openpype/hosts/houdini/api/pipeline.py b/openpype/hosts/houdini/api/pipeline.py
index 21027dad2e..7d4e58efb7 100644
--- a/openpype/hosts/houdini/api/pipeline.py
+++ b/openpype/hosts/houdini/api/pipeline.py
@@ -11,12 +11,17 @@ import avalon.api
from avalon.pipeline import AVALON_CONTAINER_ID
from avalon.lib import find_submodule
-from openpype.pipeline import LegacyCreator
+from openpype.pipeline import (
+ LegacyCreator,
+ register_loader_plugin_path,
+)
import openpype.hosts.houdini
from openpype.hosts.houdini.api import lib
from openpype.lib import (
- any_outdated
+ register_event_callback,
+ emit_event,
+ any_outdated,
)
from .lib import get_asset_fps
@@ -48,15 +53,15 @@ def install():
pyblish.api.register_host("hpython")
pyblish.api.register_plugin_path(PUBLISH_PATH)
- avalon.api.register_plugin_path(avalon.api.Loader, LOAD_PATH)
+ register_loader_plugin_path(LOAD_PATH)
avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH)
log.info("Installing callbacks ... ")
- # avalon.on("init", on_init)
- avalon.api.before("save", before_save)
- avalon.api.on("save", on_save)
- avalon.api.on("open", on_open)
- avalon.api.on("new", on_new)
+ # register_event_callback("init", on_init)
+ register_event_callback("before.save", before_save)
+ register_event_callback("save", on_save)
+ register_event_callback("open", on_open)
+ register_event_callback("new", on_new)
pyblish.api.register_callback(
"instanceToggled", on_pyblish_instance_toggled
@@ -102,13 +107,13 @@ def _register_callbacks():
def on_file_event_callback(event):
if event == hou.hipFileEventType.AfterLoad:
- avalon.api.emit("open", [event])
+ emit_event("open")
elif event == hou.hipFileEventType.AfterSave:
- avalon.api.emit("save", [event])
+ emit_event("save")
elif event == hou.hipFileEventType.BeforeSave:
- avalon.api.emit("before_save", [event])
+ emit_event("before.save")
elif event == hou.hipFileEventType.AfterClear:
- avalon.api.emit("new", [event])
+ emit_event("new")
def get_main_window():
@@ -230,11 +235,11 @@ def ls():
yield data
-def before_save(*args):
+def before_save():
return lib.validate_fps()
-def on_save(*args):
+def on_save():
log.info("Running callback on save..")
@@ -243,7 +248,7 @@ def on_save(*args):
lib.set_id(node, new_id, overwrite=False)
-def on_open(*args):
+def on_open():
if not hou.isUIAvailable():
log.debug("Batch mode detected, ignoring `on_open` callbacks..")
@@ -280,7 +285,7 @@ def on_open(*args):
dialog.show()
-def on_new(_):
+def on_new():
"""Set project resolution and fps when create a new file"""
if hou.hipFile.isLoadingHipFile():
diff --git a/openpype/hosts/houdini/plugins/load/actions.py b/openpype/hosts/houdini/plugins/load/actions.py
index acdb998c16..63d74c39a5 100644
--- a/openpype/hosts/houdini/plugins/load/actions.py
+++ b/openpype/hosts/houdini/plugins/load/actions.py
@@ -2,10 +2,10 @@
"""
-from avalon import api
+from openpype.pipeline import load
-class SetFrameRangeLoader(api.Loader):
+class SetFrameRangeLoader(load.LoaderPlugin):
"""Set Houdini frame range"""
families = [
@@ -43,7 +43,7 @@ class SetFrameRangeLoader(api.Loader):
hou.playbar.setPlaybackRange(start, end)
-class SetFrameRangeWithHandlesLoader(api.Loader):
+class SetFrameRangeWithHandlesLoader(load.LoaderPlugin):
"""Set Maya frame range including pre- and post-handles"""
families = [
diff --git a/openpype/hosts/houdini/plugins/load/load_alembic.py b/openpype/hosts/houdini/plugins/load/load_alembic.py
index eaab81f396..0214229d5a 100644
--- a/openpype/hosts/houdini/plugins/load/load_alembic.py
+++ b/openpype/hosts/houdini/plugins/load/load_alembic.py
@@ -1,10 +1,12 @@
import os
-from avalon import api
-
+from openpype.pipeline import (
+ load,
+ get_representation_path,
+)
from openpype.hosts.houdini.api import pipeline
-class AbcLoader(api.Loader):
+class AbcLoader(load.LoaderPlugin):
"""Specific loader of Alembic for the avalon.animation family"""
families = ["model", "animation", "pointcache", "gpuCache"]
@@ -90,7 +92,7 @@ class AbcLoader(api.Loader):
return
# Update the file path
- file_path = api.get_representation_path(representation)
+ file_path = get_representation_path(representation)
file_path = file_path.replace("\\", "/")
alembic_node.setParms({"fileName": file_path})
diff --git a/openpype/hosts/houdini/plugins/load/load_ass.py b/openpype/hosts/houdini/plugins/load/load_ass.py
index 8c272044ec..0144bbaefd 100644
--- a/openpype/hosts/houdini/plugins/load/load_ass.py
+++ b/openpype/hosts/houdini/plugins/load/load_ass.py
@@ -1,11 +1,15 @@
import os
-from avalon import api
-from avalon.houdini import pipeline
import clique
+from openpype.pipeline import (
+ load,
+ get_representation_path,
+)
+
+from openpype.hosts.houdini.api import pipeline
-class AssLoader(api.Loader):
+class AssLoader(load.LoaderPlugin):
"""Load .ass with Arnold Procedural"""
families = ["ass"]
@@ -88,7 +92,7 @@ class AssLoader(api.Loader):
def update(self, container, representation):
# Update the file path
- file_path = api.get_representation_path(representation)
+ file_path = get_representation_path(representation)
file_path = file_path.replace("\\", "/")
procedural = container["node"]
diff --git a/openpype/hosts/houdini/plugins/load/load_camera.py b/openpype/hosts/houdini/plugins/load/load_camera.py
index 8916d3b9b7..ef57d115da 100644
--- a/openpype/hosts/houdini/plugins/load/load_camera.py
+++ b/openpype/hosts/houdini/plugins/load/load_camera.py
@@ -1,4 +1,7 @@
-from avalon import api
+from openpype.pipeline import (
+ load,
+ get_representation_path,
+)
from openpype.hosts.houdini.api import pipeline
@@ -74,7 +77,7 @@ def transfer_non_default_values(src, dest, ignore=None):
dest_parm.setFromParm(parm)
-class CameraLoader(api.Loader):
+class CameraLoader(load.LoaderPlugin):
"""Specific loader of Alembic for the avalon.animation family"""
families = ["camera"]
@@ -129,7 +132,7 @@ class CameraLoader(api.Loader):
node = container["node"]
# Update the file path
- file_path = api.get_representation_path(representation)
+ file_path = get_representation_path(representation)
file_path = file_path.replace("\\", "/")
# Update attributes
diff --git a/openpype/hosts/houdini/plugins/load/load_hda.py b/openpype/hosts/houdini/plugins/load/load_hda.py
index f5f2fb7481..2438570c6e 100644
--- a/openpype/hosts/houdini/plugins/load/load_hda.py
+++ b/openpype/hosts/houdini/plugins/load/load_hda.py
@@ -1,10 +1,13 @@
# -*- coding: utf-8 -*-
-from avalon import api
-
+import os
+from openpype.pipeline import (
+ load,
+ get_representation_path,
+)
from openpype.hosts.houdini.api import pipeline
-class HdaLoader(api.Loader):
+class HdaLoader(load.LoaderPlugin):
"""Load Houdini Digital Asset file."""
families = ["hda"]
@@ -15,7 +18,6 @@ class HdaLoader(api.Loader):
color = "orange"
def load(self, context, name=None, namespace=None, data=None):
- import os
import hou
# Format file name, Houdini only wants forward slashes
@@ -49,7 +51,7 @@ class HdaLoader(api.Loader):
import hou
hda_node = container["node"]
- file_path = api.get_representation_path(representation)
+ file_path = get_representation_path(representation)
file_path = file_path.replace("\\", "/")
hou.hda.installFile(file_path)
defs = hda_node.type().allInstalledDefinitions()
diff --git a/openpype/hosts/houdini/plugins/load/load_image.py b/openpype/hosts/houdini/plugins/load/load_image.py
index 39f583677b..bd9ea3eee3 100644
--- a/openpype/hosts/houdini/plugins/load/load_image.py
+++ b/openpype/hosts/houdini/plugins/load/load_image.py
@@ -1,6 +1,9 @@
import os
-from avalon import api
+from openpype.pipeline import (
+ load,
+ get_representation_path,
+)
from openpype.hosts.houdini.api import lib, pipeline
import hou
@@ -37,7 +40,7 @@ def get_image_avalon_container():
return image_container
-class ImageLoader(api.Loader):
+class ImageLoader(load.LoaderPlugin):
"""Specific loader of Alembic for the avalon.animation family"""
families = ["colorbleed.imagesequence"]
@@ -87,7 +90,7 @@ class ImageLoader(api.Loader):
node = container["node"]
# Update the file path
- file_path = api.get_representation_path(representation)
+ file_path = get_representation_path(representation)
file_path = file_path.replace("\\", "/")
file_path = self._get_file_sequence(file_path)
diff --git a/openpype/hosts/houdini/plugins/load/load_usd_layer.py b/openpype/hosts/houdini/plugins/load/load_usd_layer.py
index 0d4378b480..d803e6abfe 100644
--- a/openpype/hosts/houdini/plugins/load/load_usd_layer.py
+++ b/openpype/hosts/houdini/plugins/load/load_usd_layer.py
@@ -1,8 +1,11 @@
-from avalon import api
+from openpype.pipeline import (
+ load,
+ get_representation_path,
+)
from openpype.hosts.houdini.api import lib, pipeline
-class USDSublayerLoader(api.Loader):
+class USDSublayerLoader(load.LoaderPlugin):
"""Sublayer USD file in Solaris"""
families = [
@@ -57,7 +60,7 @@ class USDSublayerLoader(api.Loader):
node = container["node"]
# Update the file path
- file_path = api.get_representation_path(representation)
+ file_path = get_representation_path(representation)
file_path = file_path.replace("\\", "/")
# Update attributes
diff --git a/openpype/hosts/houdini/plugins/load/load_usd_reference.py b/openpype/hosts/houdini/plugins/load/load_usd_reference.py
index 0edd8d9af6..fdb443f4cf 100644
--- a/openpype/hosts/houdini/plugins/load/load_usd_reference.py
+++ b/openpype/hosts/houdini/plugins/load/load_usd_reference.py
@@ -1,8 +1,11 @@
-from avalon import api
+from openpype.pipeline import (
+ load,
+ get_representation_path,
+)
from openpype.hosts.houdini.api import lib, pipeline
-class USDReferenceLoader(api.Loader):
+class USDReferenceLoader(load.LoaderPlugin):
"""Reference USD file in Solaris"""
families = [
@@ -57,7 +60,7 @@ class USDReferenceLoader(api.Loader):
node = container["node"]
# Update the file path
- file_path = api.get_representation_path(representation)
+ file_path = get_representation_path(representation)
file_path = file_path.replace("\\", "/")
# Update attributes
diff --git a/openpype/hosts/houdini/plugins/load/load_vdb.py b/openpype/hosts/houdini/plugins/load/load_vdb.py
index 40aa7a1d18..06bb9e45e4 100644
--- a/openpype/hosts/houdini/plugins/load/load_vdb.py
+++ b/openpype/hosts/houdini/plugins/load/load_vdb.py
@@ -1,11 +1,14 @@
import os
import re
-from avalon import api
+from openpype.pipeline import (
+ load,
+ get_representation_path,
+)
from openpype.hosts.houdini.api import pipeline
-class VdbLoader(api.Loader):
+class VdbLoader(load.LoaderPlugin):
"""Specific loader of Alembic for the avalon.animation family"""
families = ["vdbcache"]
@@ -96,7 +99,7 @@ class VdbLoader(api.Loader):
return
# Update the file path
- file_path = api.get_representation_path(representation)
+ file_path = get_representation_path(representation)
file_path = self.format_path(file_path)
file_node.setParms({"fileName": file_path})
diff --git a/openpype/hosts/houdini/plugins/load/show_usdview.py b/openpype/hosts/houdini/plugins/load/show_usdview.py
index f23974094e..8066615181 100644
--- a/openpype/hosts/houdini/plugins/load/show_usdview.py
+++ b/openpype/hosts/houdini/plugins/load/show_usdview.py
@@ -1,7 +1,7 @@
-from avalon import api
+from openpype.pipeline import load
-class ShowInUsdview(api.Loader):
+class ShowInUsdview(load.LoaderPlugin):
"""Open USD file in usdview"""
families = ["colorbleed.usd"]
diff --git a/openpype/hosts/houdini/plugins/publish/extract_usd_layered.py b/openpype/hosts/houdini/plugins/publish/extract_usd_layered.py
index 645bd05d4b..3e842ae766 100644
--- a/openpype/hosts/houdini/plugins/publish/extract_usd_layered.py
+++ b/openpype/hosts/houdini/plugins/publish/extract_usd_layered.py
@@ -7,6 +7,7 @@ from collections import deque
import pyblish.api
import openpype.api
+from openpype.pipeline import get_representation_path
import openpype.hosts.houdini.api.usd as hou_usdlib
from openpype.hosts.houdini.api.lib import render_rop
@@ -308,7 +309,7 @@ class ExtractUSDLayered(openpype.api.Extractor):
self.log.debug("No existing representation..")
return False
- old_file = api.get_representation_path(representation)
+ old_file = get_representation_path(representation)
if not os.path.exists(old_file):
return False
diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py
index 41c67a6209..9f97eef2f1 100644
--- a/openpype/hosts/maya/api/lib.py
+++ b/openpype/hosts/maya/api/lib.py
@@ -17,10 +17,16 @@ import bson
from maya import cmds, mel
import maya.api.OpenMaya as om
-from avalon import api, io, pipeline
+from avalon import api, io
from openpype import lib
from openpype.api import get_anatomy_settings
+from openpype.pipeline import (
+ discover_loader_plugins,
+ loaders_from_representation,
+ get_representation_path,
+ load_container,
+)
from .commands import reset_frame_range
@@ -1580,21 +1586,21 @@ def assign_look_by_version(nodes, version_id):
log.info("Using look for the first time ..")
# Load file
- loaders = api.loaders_from_representation(api.discover(api.Loader),
- representation_id)
+ _loaders = discover_loader_plugins()
+ loaders = loaders_from_representation(_loaders, representation_id)
Loader = next((i for i in loaders if i.__name__ == "LookLoader"), None)
if Loader is None:
raise RuntimeError("Could not find LookLoader, this is a bug")
# Reference the look file
with maintained_selection():
- container_node = pipeline.load(Loader, look_representation)
+ container_node = load_container(Loader, look_representation)
# Get container members
shader_nodes = get_container_members(container_node)
# Load relationships
- shader_relation = api.get_representation_path(json_representation)
+ shader_relation = get_representation_path(json_representation)
with open(shader_relation, "r") as f:
relationships = json.load(f)
diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py
index 8c3669c5d1..5cdc3ff4fd 100644
--- a/openpype/hosts/maya/api/pipeline.py
+++ b/openpype/hosts/maya/api/pipeline.py
@@ -14,9 +14,17 @@ from avalon.pipeline import AVALON_CONTAINER_ID
import openpype.hosts.maya
from openpype.tools.utils import host_tools
-from openpype.lib import any_outdated
+from openpype.lib import (
+ any_outdated,
+ register_event_callback,
+ emit_event
+)
from openpype.lib.path_tools import HostDirmap
-from openpype.pipeline import LegacyCreator
+from openpype.pipeline import (
+ LegacyCreator,
+ register_loader_plugin_path,
+ deregister_loader_plugin_path,
+)
from openpype.hosts.maya.lib import copy_workspace_mel
from . import menu, lib
@@ -49,13 +57,13 @@ def install():
pyblish.api.register_host("mayapy")
pyblish.api.register_host("maya")
- avalon.api.register_plugin_path(avalon.api.Loader, LOAD_PATH)
+ register_loader_plugin_path(LOAD_PATH)
avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH)
avalon.api.register_plugin_path(avalon.api.InventoryAction, INVENTORY_PATH)
log.info(PUBLISH_PATH)
log.info("Installing callbacks ... ")
- avalon.api.on("init", on_init)
+ register_event_callback("init", on_init)
# Callbacks below are not required for headless mode, the `init` however
# is important to load referenced Alembics correctly at rendertime.
@@ -69,12 +77,12 @@ def install():
menu.install()
- avalon.api.on("save", on_save)
- avalon.api.on("open", on_open)
- avalon.api.on("new", on_new)
- avalon.api.before("save", on_before_save)
- avalon.api.on("taskChanged", on_task_changed)
- avalon.api.on("before.workfile.save", before_workfile_save)
+ register_event_callback("save", on_save)
+ register_event_callback("open", on_open)
+ register_event_callback("new", on_new)
+ register_event_callback("before.save", on_before_save)
+ register_event_callback("taskChanged", on_task_changed)
+ register_event_callback("workfile.save.before", before_workfile_save)
def _set_project():
@@ -137,7 +145,7 @@ def _register_callbacks():
def _on_maya_initialized(*args):
- avalon.api.emit("init", args)
+ emit_event("init")
if cmds.about(batch=True):
log.warning("Running batch mode ...")
@@ -148,15 +156,15 @@ def _on_maya_initialized(*args):
def _on_scene_new(*args):
- avalon.api.emit("new", args)
+ emit_event("new")
def _on_scene_save(*args):
- avalon.api.emit("save", args)
+ emit_event("save")
def _on_scene_open(*args):
- avalon.api.emit("open", args)
+ emit_event("open")
def _before_scene_save(return_code, client_data):
@@ -166,7 +174,10 @@ def _before_scene_save(return_code, client_data):
# in order to block the operation.
OpenMaya.MScriptUtil.setBool(return_code, True)
- avalon.api.emit("before_save", [return_code, client_data])
+ emit_event(
+ "before.save",
+ {"return_code": return_code}
+ )
def uninstall():
@@ -175,7 +186,7 @@ def uninstall():
pyblish.api.deregister_host("mayapy")
pyblish.api.deregister_host("maya")
- avalon.api.deregister_plugin_path(avalon.api.Loader, LOAD_PATH)
+ deregister_loader_plugin_path(LOAD_PATH)
avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH)
avalon.api.deregister_plugin_path(
avalon.api.InventoryAction, INVENTORY_PATH
@@ -343,7 +354,7 @@ def containerise(name,
return container
-def on_init(_):
+def on_init():
log.info("Running callback on init..")
def safe_deferred(fn):
@@ -384,12 +395,12 @@ def on_init(_):
safe_deferred(override_toolbox_ui)
-def on_before_save(return_code, _):
+def on_before_save():
"""Run validation for scene's FPS prior to saving"""
return lib.validate_fps()
-def on_save(_):
+def on_save():
"""Automatically add IDs to new nodes
Any transform of a mesh, without an existing ID, is given one
@@ -407,7 +418,7 @@ def on_save(_):
lib.set_id(node, new_id, overwrite=False)
-def on_open(_):
+def on_open():
"""On scene open let's assume the containers have changed."""
from Qt import QtWidgets
@@ -455,7 +466,7 @@ def on_open(_):
dialog.show()
-def on_new(_):
+def on_new():
"""Set project resolution and fps when create a new file"""
log.info("Running callback on new..")
with lib.suspended_refresh():
@@ -471,7 +482,7 @@ def on_new(_):
lib.set_context_settings()
-def on_task_changed(*args):
+def on_task_changed():
"""Wrapped function of app initialize and maya's on task changed"""
# Run
menu.update_menu_task_label()
@@ -509,7 +520,7 @@ def on_task_changed(*args):
def before_workfile_save(event):
- workdir_path = event.workdir_path
+ workdir_path = event["workdir_path"]
if workdir_path:
copy_workspace_mel(workdir_path)
diff --git a/openpype/hosts/maya/api/plugin.py b/openpype/hosts/maya/api/plugin.py
index e0c21645e4..84379bc145 100644
--- a/openpype/hosts/maya/api/plugin.py
+++ b/openpype/hosts/maya/api/plugin.py
@@ -4,9 +4,12 @@ from maya import cmds
import qargparse
-from avalon import api
from avalon.pipeline import AVALON_CONTAINER_ID
-from openpype.pipeline import LegacyCreator
+from openpype.pipeline import (
+ LegacyCreator,
+ LoaderPlugin,
+ get_representation_path,
+)
from .pipeline import containerise
from . import lib
@@ -95,7 +98,7 @@ class Creator(LegacyCreator):
return instance
-class Loader(api.Loader):
+class Loader(LoaderPlugin):
hosts = ["maya"]
@@ -178,7 +181,7 @@ class ReferenceLoader(Loader):
loader=self.__class__.__name__
)
loaded_containers.append(container)
- self._organize_containers([ref_node], container)
+ self._organize_containers(nodes, container)
c += 1
namespace = None
@@ -194,7 +197,7 @@ class ReferenceLoader(Loader):
node = container["objectName"]
- path = api.get_representation_path(representation)
+ path = get_representation_path(representation)
# Get reference node from container members
members = get_container_members(node)
@@ -247,6 +250,8 @@ class ReferenceLoader(Loader):
self.log.warning("Ignoring file read error:\n%s", exc)
+ self._organize_containers(content, container["objectName"])
+
# Reapply alembic settings.
if representation["name"] == "abc" and alembic_data:
alembic_nodes = cmds.ls(
@@ -284,7 +289,6 @@ class ReferenceLoader(Loader):
to remove from scene.
"""
-
from maya import cmds
node = container["objectName"]
@@ -318,6 +322,7 @@ class ReferenceLoader(Loader):
@staticmethod
def _organize_containers(nodes, container):
# type: (list, str) -> None
+ """Put containers in loaded data to correct hierarchy."""
for node in nodes:
id_attr = "{}.id".format(node)
if not cmds.attributeQuery("id", node=node, exists=True):
diff --git a/openpype/hosts/maya/api/setdress.py b/openpype/hosts/maya/api/setdress.py
index 1a7c3933a1..96a9700b88 100644
--- a/openpype/hosts/maya/api/setdress.py
+++ b/openpype/hosts/maya/api/setdress.py
@@ -8,7 +8,15 @@ import copy
import six
from maya import cmds
-from avalon import api, io
+from avalon import io
+from openpype.pipeline import (
+ discover_loader_plugins,
+ loaders_from_representation,
+ load_container,
+ update_container,
+ remove_container,
+ get_representation_path,
+)
from openpype.hosts.maya.api.lib import (
matrix_equals,
unique_namespace
@@ -120,12 +128,13 @@ def load_package(filepath, name, namespace=None):
root = "{}:{}".format(namespace, name)
containers = []
- all_loaders = api.discover(api.Loader)
+ all_loaders = discover_loader_plugins()
for representation_id, instances in data.items():
# Find the compatible loaders
- loaders = api.loaders_from_representation(all_loaders,
- representation_id)
+ loaders = loaders_from_representation(
+ all_loaders, representation_id
+ )
for instance in instances:
container = _add(instance=instance,
@@ -180,9 +189,11 @@ def _add(instance, representation_id, loaders, namespace, root="|"):
instance['loader'], instance)
raise RuntimeError("Loader is missing.")
- container = api.load(Loader,
- representation_id,
- namespace=instance['namespace'])
+ container = load_container(
+ Loader,
+ representation_id,
+ namespace=instance['namespace']
+ )
# Get the root from the loaded container
loaded_root = get_container_transforms({"objectName": container},
@@ -320,13 +331,13 @@ def update_package(set_container, representation):
"type": "representation"
})
- current_file = api.get_representation_path(current_representation)
+ current_file = get_representation_path(current_representation)
assert current_file.endswith(".json")
with open(current_file, "r") as fp:
current_data = json.load(fp)
# Load the new package data
- new_file = api.get_representation_path(representation)
+ new_file = get_representation_path(representation)
assert new_file.endswith(".json")
with open(new_file, "r") as fp:
new_data = json.load(fp)
@@ -460,12 +471,12 @@ def update_scene(set_container, containers, current_data, new_data, new_file):
# considered as new element and added afterwards.
processed_containers.pop()
processed_namespaces.remove(container_ns)
- api.remove(container)
+ remove_container(container)
continue
# Check whether the conversion can be done by the Loader.
# They *must* use the same asset, subset and Loader for
- # `api.update` to make sense.
+ # `update_container` to make sense.
old = io.find_one({
"_id": io.ObjectId(representation_current)
})
@@ -479,20 +490,21 @@ def update_scene(set_container, containers, current_data, new_data, new_file):
continue
new_version = new["context"]["version"]
- api.update(container, version=new_version)
+ update_container(container, version=new_version)
else:
# Remove this container because it's not in the new data
log.warning("Removing content: %s", container_ns)
- api.remove(container)
+ remove_container(container)
# Add new assets
- all_loaders = api.discover(api.Loader)
+ all_loaders = discover_loader_plugins()
for representation_id, instances in new_data.items():
# Find the compatible loaders
- loaders = api.loaders_from_representation(all_loaders,
- representation_id)
+ loaders = loaders_from_representation(
+ all_loaders, representation_id
+ )
for instance in instances:
# Already processed in update functionality
@@ -517,7 +529,7 @@ def update_scene(set_container, containers, current_data, new_data, new_file):
def compare_representations(old, new):
"""Check if the old representation given can be updated
- Due to limitations of the `api.update` function we cannot allow
+ Due to limitations of the `update_container` function we cannot allow
differences in the following data:
* Representation name (extension)
diff --git a/openpype/hosts/maya/plugins/inventory/import_modelrender.py b/openpype/hosts/maya/plugins/inventory/import_modelrender.py
index 119edccb7a..c5d3d0c8f4 100644
--- a/openpype/hosts/maya/plugins/inventory/import_modelrender.py
+++ b/openpype/hosts/maya/plugins/inventory/import_modelrender.py
@@ -1,5 +1,9 @@
import json
-from avalon import api, io, pipeline
+from avalon import api, io
+from openpype.pipeline import (
+ get_representation_context,
+ get_representation_path_from_context,
+)
from openpype.hosts.maya.api.lib import (
maintained_selection,
apply_shaders
@@ -73,11 +77,11 @@ class ImportModelRender(api.InventoryAction):
"name": self.look_data_type,
})
- context = pipeline.get_representation_context(look_repr["_id"])
- maya_file = pipeline.get_representation_path_from_context(context)
+ context = get_representation_context(look_repr["_id"])
+ maya_file = get_representation_path_from_context(context)
- context = pipeline.get_representation_context(json_repr["_id"])
- json_file = pipeline.get_representation_path_from_context(context)
+ context = get_representation_context(json_repr["_id"])
+ json_file = get_representation_path_from_context(context)
# Import the look file
with maintained_selection():
diff --git a/openpype/hosts/maya/plugins/load/actions.py b/openpype/hosts/maya/plugins/load/actions.py
index 1cb63c8a7a..483ad32402 100644
--- a/openpype/hosts/maya/plugins/load/actions.py
+++ b/openpype/hosts/maya/plugins/load/actions.py
@@ -2,14 +2,14 @@
"""
-from avalon import api
+from openpype.pipeline import load
from openpype.hosts.maya.api.lib import (
maintained_selection,
unique_namespace
)
-class SetFrameRangeLoader(api.Loader):
+class SetFrameRangeLoader(load.LoaderPlugin):
"""Specific loader of Alembic for the avalon.animation family"""
families = ["animation",
@@ -43,7 +43,7 @@ class SetFrameRangeLoader(api.Loader):
animationEndTime=end)
-class SetFrameRangeWithHandlesLoader(api.Loader):
+class SetFrameRangeWithHandlesLoader(load.LoaderPlugin):
"""Specific loader of Alembic for the avalon.animation family"""
families = ["animation",
@@ -81,7 +81,7 @@ class SetFrameRangeWithHandlesLoader(api.Loader):
animationEndTime=end)
-class ImportMayaLoader(api.Loader):
+class ImportMayaLoader(load.LoaderPlugin):
"""Import action for Maya (unmanaged)
Warning:
diff --git a/openpype/hosts/maya/plugins/load/load_ass.py b/openpype/hosts/maya/plugins/load/load_ass.py
index 18b34d2233..18de4df3b1 100644
--- a/openpype/hosts/maya/plugins/load/load_ass.py
+++ b/openpype/hosts/maya/plugins/load/load_ass.py
@@ -1,8 +1,11 @@
import os
import clique
-from avalon import api
from openpype.api import get_project_settings
+from openpype.pipeline import (
+ load,
+ get_representation_path
+)
import openpype.hosts.maya.api.plugin
from openpype.hosts.maya.api.plugin import get_reference_node
from openpype.hosts.maya.api.lib import (
@@ -106,7 +109,7 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
node = container["objectName"]
representation["context"].pop("frame", None)
- path = api.get_representation_path(representation)
+ path = get_representation_path(representation)
print(path)
# path = self.fname
print(self.fname)
@@ -164,7 +167,7 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
type="string")
-class AssStandinLoader(api.Loader):
+class AssStandinLoader(load.LoaderPlugin):
"""Load .ASS file as standin"""
families = ["ass"]
@@ -240,7 +243,7 @@ class AssStandinLoader(api.Loader):
import pymel.core as pm
- path = api.get_representation_path(representation)
+ path = get_representation_path(representation)
files_in_path = os.listdir(os.path.split(path)[0])
sequence = 0
diff --git a/openpype/hosts/maya/plugins/load/load_assembly.py b/openpype/hosts/maya/plugins/load/load_assembly.py
index 0151da7253..902f38695c 100644
--- a/openpype/hosts/maya/plugins/load/load_assembly.py
+++ b/openpype/hosts/maya/plugins/load/load_assembly.py
@@ -1,7 +1,10 @@
-from avalon import api
+from openpype.pipeline import (
+ load,
+ remove_container
+)
-class AssemblyLoader(api.Loader):
+class AssemblyLoader(load.LoaderPlugin):
families = ["assembly"]
representations = ["json"]
@@ -48,13 +51,11 @@ class AssemblyLoader(api.Loader):
def update(self, container, representation):
from openpype import setdress
- return setdress.update_package(container,
- representation)
+ return setdress.update_package(container, representation)
def remove(self, container):
"""Remove all sub containers"""
- from avalon import api
from openpype import setdress
import maya.cmds as cmds
@@ -63,7 +64,7 @@ class AssemblyLoader(api.Loader):
for member_container in member_containers:
self.log.info("Removing container %s",
member_container['objectName'])
- api.remove(member_container)
+ remove_container(member_container)
# Remove alembic hierarchy reference
# TODO: Check whether removing all contained references is safe enough
diff --git a/openpype/hosts/maya/plugins/load/load_audio.py b/openpype/hosts/maya/plugins/load/load_audio.py
index 99f1f7c172..d8844ffea6 100644
--- a/openpype/hosts/maya/plugins/load/load_audio.py
+++ b/openpype/hosts/maya/plugins/load/load_audio.py
@@ -1,10 +1,14 @@
from maya import cmds, mel
-from avalon import api, io
+from avalon import io
+from openpype.pipeline import (
+ load,
+ get_representation_path
+)
from openpype.hosts.maya.api.pipeline import containerise
from openpype.hosts.maya.api.lib import unique_namespace
-class AudioLoader(api.Loader):
+class AudioLoader(load.LoaderPlugin):
"""Specific loader of audio."""
families = ["audio"]
@@ -51,7 +55,7 @@ class AudioLoader(api.Loader):
assert audio_node is not None, "Audio node not found."
- path = api.get_representation_path(representation)
+ path = get_representation_path(representation)
audio_node.filename.set(path)
cmds.setAttr(
container["objectName"] + ".representation",
diff --git a/openpype/hosts/maya/plugins/load/load_gpucache.py b/openpype/hosts/maya/plugins/load/load_gpucache.py
index 2e0b7bb810..591e568e4c 100644
--- a/openpype/hosts/maya/plugins/load/load_gpucache.py
+++ b/openpype/hosts/maya/plugins/load/load_gpucache.py
@@ -1,9 +1,13 @@
import os
-from avalon import api
+
+from openpype.pipeline import (
+ load,
+ get_representation_path
+)
from openpype.api import get_project_settings
-class GpuCacheLoader(api.Loader):
+class GpuCacheLoader(load.LoaderPlugin):
"""Load model Alembic as gpuCache"""
families = ["model"]
@@ -73,7 +77,7 @@ class GpuCacheLoader(api.Loader):
import maya.cmds as cmds
- path = api.get_representation_path(representation)
+ path = get_representation_path(representation)
# Update the cache
members = cmds.sets(container['objectName'], query=True)
diff --git a/openpype/hosts/maya/plugins/load/load_image_plane.py b/openpype/hosts/maya/plugins/load/load_image_plane.py
index 8e33f51389..b250986489 100644
--- a/openpype/hosts/maya/plugins/load/load_image_plane.py
+++ b/openpype/hosts/maya/plugins/load/load_image_plane.py
@@ -1,6 +1,10 @@
from Qt import QtWidgets, QtCore
-from avalon import api, io
+from avalon import io
+from openpype.pipeline import (
+ load,
+ get_representation_path
+)
from openpype.hosts.maya.api.pipeline import containerise
from openpype.hosts.maya.api.lib import unique_namespace
@@ -74,7 +78,7 @@ class CameraWindow(QtWidgets.QDialog):
self.close()
-class ImagePlaneLoader(api.Loader):
+class ImagePlaneLoader(load.LoaderPlugin):
"""Specific loader of plate for image planes on selected camera."""
families = ["image", "plate", "render"]
@@ -203,7 +207,7 @@ class ImagePlaneLoader(api.Loader):
assert image_plane_shape is not None, "Image plane not found."
- path = api.get_representation_path(representation)
+ path = get_representation_path(representation)
image_plane_shape.imageName.set(path)
cmds.setAttr(
container["objectName"] + ".representation",
diff --git a/openpype/hosts/maya/plugins/load/load_look.py b/openpype/hosts/maya/plugins/load/load_look.py
index 96c1ecbb20..8f02ed59b8 100644
--- a/openpype/hosts/maya/plugins/load/load_look.py
+++ b/openpype/hosts/maya/plugins/load/load_look.py
@@ -5,7 +5,8 @@ from collections import defaultdict
from Qt import QtWidgets
-from avalon import api, io
+from avalon import io
+from openpype.pipeline import get_representation_path
import openpype.hosts.maya.api.plugin
from openpype.hosts.maya.api import lib
from openpype.widgets.message_window import ScrollMessageBox
@@ -77,7 +78,7 @@ class LookLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
})
# Load relationships
- shader_relation = api.get_representation_path(json_representation)
+ shader_relation = get_representation_path(json_representation)
with open(shader_relation, "r") as f:
json_data = json.load(f)
diff --git a/openpype/hosts/maya/plugins/load/load_matchmove.py b/openpype/hosts/maya/plugins/load/load_matchmove.py
index abc702cde8..ee3332bd09 100644
--- a/openpype/hosts/maya/plugins/load/load_matchmove.py
+++ b/openpype/hosts/maya/plugins/load/load_matchmove.py
@@ -1,8 +1,8 @@
-from avalon import api
from maya import mel
+from openpype.pipeline import load
-class MatchmoveLoader(api.Loader):
+class MatchmoveLoader(load.LoaderPlugin):
"""
This will run matchmove script to create track in scene.
diff --git a/openpype/hosts/maya/plugins/load/load_redshift_proxy.py b/openpype/hosts/maya/plugins/load/load_redshift_proxy.py
index fd2ae0f1d3..d93a9f02a2 100644
--- a/openpype/hosts/maya/plugins/load/load_redshift_proxy.py
+++ b/openpype/hosts/maya/plugins/load/load_redshift_proxy.py
@@ -5,8 +5,11 @@ import clique
import maya.cmds as cmds
-from avalon import api
from openpype.api import get_project_settings
+from openpype.pipeline import (
+ load,
+ get_representation_path
+)
from openpype.hosts.maya.api.lib import (
namespaced,
maintained_selection,
@@ -15,7 +18,7 @@ from openpype.hosts.maya.api.lib import (
from openpype.hosts.maya.api.pipeline import containerise
-class RedshiftProxyLoader(api.Loader):
+class RedshiftProxyLoader(load.LoaderPlugin):
"""Load Redshift proxy"""
families = ["redshiftproxy"]
@@ -78,7 +81,7 @@ class RedshiftProxyLoader(api.Loader):
rs_meshes = cmds.ls(members, type="RedshiftProxyMesh")
assert rs_meshes, "Cannot find RedshiftProxyMesh in container"
- filename = api.get_representation_path(representation)
+ filename = get_representation_path(representation)
for rs_mesh in rs_meshes:
cmds.setAttr("{}.fileName".format(rs_mesh),
diff --git a/openpype/hosts/maya/plugins/load/load_reference.py b/openpype/hosts/maya/plugins/load/load_reference.py
index 66cf95a643..04a25f6493 100644
--- a/openpype/hosts/maya/plugins/load/load_reference.py
+++ b/openpype/hosts/maya/plugins/load/load_reference.py
@@ -121,18 +121,10 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
if family == "rig":
self._post_process_rig(name, namespace, context, options)
else:
-
if "translate" in options:
cmds.setAttr(group_name + ".t", *options["translate"])
-
return new_nodes
- def load(self, context, name=None, namespace=None, options=None):
- container = super(ReferenceLoader, self).load(
- context, name, namespace, options)
- # clean containers if present to AVALON_CONTAINERS
- self._organize_containers(self[:], container[0])
-
def switch(self, container, representation):
self.update(container, representation)
diff --git a/openpype/hosts/maya/plugins/load/load_rendersetup.py b/openpype/hosts/maya/plugins/load/load_rendersetup.py
index efeff2f193..7a2d8b1002 100644
--- a/openpype/hosts/maya/plugins/load/load_rendersetup.py
+++ b/openpype/hosts/maya/plugins/load/load_rendersetup.py
@@ -7,10 +7,13 @@ instance.
"""
import json
-import six
import sys
+import six
-from avalon import api
+from openpype.pipeline import (
+ load,
+ get_representation_path
+)
from openpype.hosts.maya.api import lib
from openpype.hosts.maya.api.pipeline import containerise
@@ -18,7 +21,7 @@ from maya import cmds
import maya.app.renderSetup.model.renderSetup as renderSetup
-class RenderSetupLoader(api.Loader):
+class RenderSetupLoader(load.LoaderPlugin):
"""Load json preset for RenderSetup overwriting current one."""
families = ["rendersetup"]
@@ -87,7 +90,7 @@ class RenderSetupLoader(api.Loader):
"Render setup setting will be overwritten by new version. All "
"setting specified by user not included in loaded version "
"will be lost.")
- path = api.get_representation_path(representation)
+ path = get_representation_path(representation)
with open(path, "r") as file:
try:
renderSetup.instance().decode(
diff --git a/openpype/hosts/maya/plugins/load/load_vdb_to_redshift.py b/openpype/hosts/maya/plugins/load/load_vdb_to_redshift.py
index 3e1d67ae9a..70bd9d22e2 100644
--- a/openpype/hosts/maya/plugins/load/load_vdb_to_redshift.py
+++ b/openpype/hosts/maya/plugins/load/load_vdb_to_redshift.py
@@ -1,9 +1,10 @@
import os
-from avalon import api
+
from openpype.api import get_project_settings
+from openpype.pipeline import load
-class LoadVDBtoRedShift(api.Loader):
+class LoadVDBtoRedShift(load.LoaderPlugin):
"""Load OpenVDB in a Redshift Volume Shape"""
families = ["vdbcache"]
diff --git a/openpype/hosts/maya/plugins/load/load_vdb_to_vray.py b/openpype/hosts/maya/plugins/load/load_vdb_to_vray.py
index 6d5544103d..4f14235bfb 100644
--- a/openpype/hosts/maya/plugins/load/load_vdb_to_vray.py
+++ b/openpype/hosts/maya/plugins/load/load_vdb_to_vray.py
@@ -1,6 +1,10 @@
import os
-from avalon import api
+
from openpype.api import get_project_settings
+from openpype.pipeline import (
+ load,
+ get_representation_path
+)
from maya import cmds
@@ -69,7 +73,7 @@ def _fix_duplicate_vvg_callbacks():
matched.add(callback)
-class LoadVDBtoVRay(api.Loader):
+class LoadVDBtoVRay(load.LoaderPlugin):
families = ["vdbcache"]
representations = ["vdb"]
@@ -252,7 +256,7 @@ class LoadVDBtoVRay(api.Loader):
def update(self, container, representation):
- path = api.get_representation_path(representation)
+ path = get_representation_path(representation)
# Find VRayVolumeGrid
members = cmds.sets(container['objectName'], query=True)
diff --git a/openpype/hosts/maya/plugins/load/load_vrayproxy.py b/openpype/hosts/maya/plugins/load/load_vrayproxy.py
index ac2fe635b3..5b79b1efb3 100644
--- a/openpype/hosts/maya/plugins/load/load_vrayproxy.py
+++ b/openpype/hosts/maya/plugins/load/load_vrayproxy.py
@@ -9,8 +9,12 @@ import os
import maya.cmds as cmds
-from avalon import api, io
+from avalon import io
from openpype.api import get_project_settings
+from openpype.pipeline import (
+ load,
+ get_representation_path
+)
from openpype.hosts.maya.api.lib import (
maintained_selection,
namespaced,
@@ -19,7 +23,7 @@ from openpype.hosts.maya.api.lib import (
from openpype.hosts.maya.api.pipeline import containerise
-class VRayProxyLoader(api.Loader):
+class VRayProxyLoader(load.LoaderPlugin):
"""Load VRay Proxy with Alembic or VrayMesh."""
families = ["vrayproxy", "model", "pointcache", "animation"]
@@ -100,7 +104,10 @@ class VRayProxyLoader(api.Loader):
assert vraymeshes, "Cannot find VRayMesh in container"
# get all representations for this version
- filename = self._get_abc(representation["parent"]) or api.get_representation_path(representation) # noqa: E501
+ filename = (
+ self._get_abc(representation["parent"])
+ or get_representation_path(representation)
+ )
for vray_mesh in vraymeshes:
cmds.setAttr("{}.fileName".format(vray_mesh),
@@ -185,7 +192,7 @@ class VRayProxyLoader(api.Loader):
if abc_rep:
self.log.debug("Found, we'll link alembic to vray proxy.")
- file_name = api.get_representation_path(abc_rep)
+ file_name = get_representation_path(abc_rep)
self.log.debug("File: {}".format(self.fname))
return file_name
diff --git a/openpype/hosts/maya/plugins/load/load_vrayscene.py b/openpype/hosts/maya/plugins/load/load_vrayscene.py
index dfe2b85edc..61132088cc 100644
--- a/openpype/hosts/maya/plugins/load/load_vrayscene.py
+++ b/openpype/hosts/maya/plugins/load/load_vrayscene.py
@@ -1,8 +1,11 @@
# -*- coding: utf-8 -*-
import os
import maya.cmds as cmds # noqa
-from avalon import api
from openpype.api import get_project_settings
+from openpype.pipeline import (
+ load,
+ get_representation_path
+)
from openpype.hosts.maya.api.lib import (
maintained_selection,
namespaced,
@@ -11,7 +14,7 @@ from openpype.hosts.maya.api.lib import (
from openpype.hosts.maya.api.pipeline import containerise
-class VRaySceneLoader(api.Loader):
+class VRaySceneLoader(load.LoaderPlugin):
"""Load Vray scene"""
families = ["vrayscene_layer"]
@@ -78,7 +81,7 @@ class VRaySceneLoader(api.Loader):
vraymeshes = cmds.ls(members, type="VRayScene")
assert vraymeshes, "Cannot find VRayScene in container"
- filename = api.get_representation_path(representation)
+ filename = get_representation_path(representation)
for vray_mesh in vraymeshes:
cmds.setAttr("{}.FilePath".format(vray_mesh),
diff --git a/openpype/hosts/maya/plugins/load/load_yeti_cache.py b/openpype/hosts/maya/plugins/load/load_yeti_cache.py
index dfe75173ac..c64e1c540b 100644
--- a/openpype/hosts/maya/plugins/load/load_yeti_cache.py
+++ b/openpype/hosts/maya/plugins/load/load_yeti_cache.py
@@ -7,13 +7,17 @@ from pprint import pprint
from maya import cmds
-from avalon import api, io
+from avalon import io
from openpype.api import get_project_settings
+from openpype.pipeline import (
+ load,
+ get_representation_path
+)
from openpype.hosts.maya.api import lib
from openpype.hosts.maya.api.pipeline import containerise
-class YetiCacheLoader(api.Loader):
+class YetiCacheLoader(load.LoaderPlugin):
families = ["yeticache", "yetiRig"]
representations = ["fur"]
@@ -121,8 +125,8 @@ class YetiCacheLoader(api.Loader):
"cannot find fursettings representation"
)
- settings_fname = api.get_representation_path(fur_settings)
- path = api.get_representation_path(representation)
+ settings_fname = get_representation_path(fur_settings)
+ path = get_representation_path(representation)
# Get all node data
with open(settings_fname, "r") as fp:
settings = json.load(fp)
diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py
index d99e81573b..a525b562f3 100644
--- a/openpype/hosts/maya/plugins/publish/collect_render.py
+++ b/openpype/hosts/maya/plugins/publish/collect_render.py
@@ -50,6 +50,7 @@ import maya.app.renderSetup.model.renderSetup as renderSetup
import pyblish.api
from avalon import api
+from openpype.lib import get_formatted_current_time
from openpype.hosts.maya.api.lib_renderproducts import get as get_layer_render_products # noqa: E501
from openpype.hosts.maya.api import lib
@@ -328,7 +329,7 @@ class CollectMayaRender(pyblish.api.ContextPlugin):
"family": "renderlayer",
"families": ["renderlayer"],
"asset": asset,
- "time": api.time(),
+ "time": get_formatted_current_time(),
"author": context.data["user"],
# Add source to allow tracing back to the scene from
# which was submitted originally
diff --git a/openpype/hosts/maya/plugins/publish/collect_vrayscene.py b/openpype/hosts/maya/plugins/publish/collect_vrayscene.py
index c1e5d388af..327fc836dc 100644
--- a/openpype/hosts/maya/plugins/publish/collect_vrayscene.py
+++ b/openpype/hosts/maya/plugins/publish/collect_vrayscene.py
@@ -7,6 +7,7 @@ from maya import cmds
import pyblish.api
from avalon import api
+from openpype.lib import get_formatted_current_time
from openpype.hosts.maya.api import lib
@@ -117,7 +118,7 @@ class CollectVrayScene(pyblish.api.InstancePlugin):
"family": "vrayscene_layer",
"families": ["vrayscene_layer"],
"asset": api.Session["AVALON_ASSET"],
- "time": api.time(),
+ "time": get_formatted_current_time(),
"author": context.data["user"],
# Add source to allow tracing back to the scene from
# which was submitted originally
diff --git a/openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py b/openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py
index 5cc7b52090..389995d30c 100644
--- a/openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py
+++ b/openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py
@@ -58,16 +58,11 @@ class ExtractMayaSceneRaw(openpype.api.Extractor):
else:
members = instance[:]
- loaded_containers = None
- if set(self.add_for_families).intersection(
- set(instance.data.get("families")),
- set(instance.data.get("family").lower())):
- loaded_containers = self._add_loaded_containers(members)
-
selection = members
- if loaded_containers:
- self.log.info(loaded_containers)
- selection += loaded_containers
+ if set(self.add_for_families).intersection(
+ set(instance.data.get("families", []))) or \
+ instance.data.get("family") in self.add_for_families:
+ selection += self._get_loaded_containers(members)
# Perform extraction
self.log.info("Performing extraction ...")
@@ -97,15 +92,15 @@ class ExtractMayaSceneRaw(openpype.api.Extractor):
self.log.info("Extracted instance '%s' to: %s" % (instance.name, path))
@staticmethod
- def _add_loaded_containers(members):
+ def _get_loaded_containers(members):
# type: (list) -> list
- refs_to_include = [
- cmds.referenceQuery(ref, referenceNode=True)
- for ref in members
- if cmds.referenceQuery(ref, isNodeReferenced=True)
- ]
+ refs_to_include = {
+ cmds.referenceQuery(node, referenceNode=True)
+ for node in members
+ if cmds.referenceQuery(node, isNodeReferenced=True)
+ }
- refs_to_include = set(refs_to_include)
+ members_with_refs = refs_to_include.union(members)
obj_sets = cmds.ls("*.id", long=True, type="objectSet", recursive=True,
objectsOnly=True)
@@ -121,7 +116,7 @@ class ExtractMayaSceneRaw(openpype.api.Extractor):
continue
set_content = set(cmds.sets(obj_set, query=True))
- if set_content.intersection(refs_to_include):
+ if set_content.intersection(members_with_refs):
loaded_containers.append(obj_set)
return loaded_containers
diff --git a/openpype/hosts/nuke/api/pipeline.py b/openpype/hosts/nuke/api/pipeline.py
index d98a951491..fd2e16b8d3 100644
--- a/openpype/hosts/nuke/api/pipeline.py
+++ b/openpype/hosts/nuke/api/pipeline.py
@@ -14,7 +14,12 @@ from openpype.api import (
BuildWorkfile,
get_current_project_settings
)
-from openpype.pipeline import LegacyCreator
+from openpype.lib import register_event_callback
+from openpype.pipeline import (
+ LegacyCreator,
+ register_loader_plugin_path,
+ deregister_loader_plugin_path,
+)
from openpype.tools.utils import host_tools
from .command import viewer_update_and_undo_stop
@@ -98,13 +103,13 @@ def install():
log.info("Registering Nuke plug-ins..")
pyblish.api.register_plugin_path(PUBLISH_PATH)
- avalon.api.register_plugin_path(avalon.api.Loader, LOAD_PATH)
+ register_loader_plugin_path(LOAD_PATH)
avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH)
avalon.api.register_plugin_path(avalon.api.InventoryAction, INVENTORY_PATH)
# Register Avalon event for workfiles loading.
- avalon.api.on("workio.open_file", check_inventory_versions)
- avalon.api.on("taskChanged", change_context_label)
+ register_event_callback("workio.open_file", check_inventory_versions)
+ register_event_callback("taskChanged", change_context_label)
pyblish.api.register_callback(
"instanceToggled", on_pyblish_instance_toggled)
@@ -124,7 +129,7 @@ def uninstall():
log.info("Deregistering Nuke plug-ins..")
pyblish.deregister_host("nuke")
pyblish.api.deregister_plugin_path(PUBLISH_PATH)
- avalon.api.deregister_plugin_path(avalon.api.Loader, LOAD_PATH)
+ deregister_loader_plugin_path(LOAD_PATH)
avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH)
pyblish.api.deregister_callback(
@@ -227,7 +232,7 @@ def _uninstall_menu():
menu.removeItem(item.name())
-def change_context_label(*args):
+def change_context_label():
menubar = nuke.menu("Nuke")
menu = menubar.findItem(MENU_LABEL)
diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py
index ff186cd685..d0bb45a05d 100644
--- a/openpype/hosts/nuke/api/plugin.py
+++ b/openpype/hosts/nuke/api/plugin.py
@@ -4,10 +4,11 @@ import string
import nuke
-import avalon.api
-
from openpype.api import get_current_project_settings
-from openpype.pipeline import LegacyCreator
+from openpype.pipeline import (
+ LegacyCreator,
+ LoaderPlugin,
+)
from .lib import (
Knobby,
check_subsetname_exists,
@@ -85,7 +86,7 @@ def get_review_presets_config():
return [str(name) for name, _prop in outputs.items()]
-class NukeLoader(avalon.api.Loader):
+class NukeLoader(LoaderPlugin):
container_id_knob = "containerId"
container_id = None
diff --git a/openpype/hosts/nuke/plugins/inventory/repair_old_loaders.py b/openpype/hosts/nuke/plugins/inventory/repair_old_loaders.py
index 49405fd213..5f834be557 100644
--- a/openpype/hosts/nuke/plugins/inventory/repair_old_loaders.py
+++ b/openpype/hosts/nuke/plugins/inventory/repair_old_loaders.py
@@ -1,4 +1,4 @@
-from avalon import api, style
+from avalon import api
from openpype.api import Logger
from openpype.hosts.nuke.api.lib import set_avalon_knob_data
@@ -7,7 +7,7 @@ class RepairOldLoaders(api.InventoryAction):
label = "Repair Old Loaders"
icon = "gears"
- color = style.colors.alert
+ color = "#cc0000"
log = Logger.get_logger(__name__)
diff --git a/openpype/hosts/nuke/plugins/load/actions.py b/openpype/hosts/nuke/plugins/load/actions.py
index 07dcf2d8e1..81840b3a38 100644
--- a/openpype/hosts/nuke/plugins/load/actions.py
+++ b/openpype/hosts/nuke/plugins/load/actions.py
@@ -2,13 +2,13 @@
"""
-from avalon import api
from openpype.api import Logger
+from openpype.pipeline import load
log = Logger().get_logger(__name__)
-class SetFrameRangeLoader(api.Loader):
+class SetFrameRangeLoader(load.LoaderPlugin):
"""Specific loader of Alembic for the avalon.animation family"""
families = ["animation",
@@ -42,7 +42,7 @@ class SetFrameRangeLoader(api.Loader):
lib.update_frame_range(start, end)
-class SetFrameRangeWithHandlesLoader(api.Loader):
+class SetFrameRangeWithHandlesLoader(load.LoaderPlugin):
"""Specific loader of Alembic for the avalon.animation family"""
families = ["animation",
diff --git a/openpype/hosts/nuke/plugins/load/load_backdrop.py b/openpype/hosts/nuke/plugins/load/load_backdrop.py
index 6619cfb414..36cec6f4c5 100644
--- a/openpype/hosts/nuke/plugins/load/load_backdrop.py
+++ b/openpype/hosts/nuke/plugins/load/load_backdrop.py
@@ -1,7 +1,11 @@
-from avalon import api, style, io
+from avalon import io
import nuke
import nukescripts
+from openpype.pipeline import (
+ load,
+ get_representation_path,
+)
from openpype.hosts.nuke.api.lib import (
find_free_space_to_paste_nodes,
maintained_selection,
@@ -14,7 +18,7 @@ from openpype.hosts.nuke.api.commands import viewer_update_and_undo_stop
from openpype.hosts.nuke.api import containerise, update_container
-class LoadBackdropNodes(api.Loader):
+class LoadBackdropNodes(load.LoaderPlugin):
"""Loading Published Backdrop nodes (workfile, nukenodes)"""
representations = ["nk"]
@@ -23,7 +27,7 @@ class LoadBackdropNodes(api.Loader):
label = "Iport Nuke Nodes"
order = 0
icon = "eye"
- color = style.colors.light
+ color = "white"
node_color = "0x7533c1ff"
def load(self, context, name, namespace, data):
@@ -191,7 +195,7 @@ class LoadBackdropNodes(api.Loader):
# get corresponding node
GN = nuke.toNode(container['objectName'])
- file = api.get_representation_path(representation).replace("\\", "/")
+ file = get_representation_path(representation).replace("\\", "/")
context = representation["context"]
name = container['name']
version_data = version.get("data", {})
diff --git a/openpype/hosts/nuke/plugins/load/load_camera_abc.py b/openpype/hosts/nuke/plugins/load/load_camera_abc.py
index 9610940619..fb5f7f8ede 100644
--- a/openpype/hosts/nuke/plugins/load/load_camera_abc.py
+++ b/openpype/hosts/nuke/plugins/load/load_camera_abc.py
@@ -1,6 +1,10 @@
import nuke
-from avalon import api, io
+from avalon import io
+from openpype.pipeline import (
+ load,
+ get_representation_path,
+)
from openpype.hosts.nuke.api import (
containerise,
update_container,
@@ -11,7 +15,7 @@ from openpype.hosts.nuke.api.lib import (
)
-class AlembicCameraLoader(api.Loader):
+class AlembicCameraLoader(load.LoaderPlugin):
"""
This will load alembic camera into script.
"""
@@ -127,7 +131,7 @@ class AlembicCameraLoader(api.Loader):
data_imprint.update({k: version_data[k]})
# getting file path
- file = api.get_representation_path(representation).replace("\\", "/")
+ file = get_representation_path(representation).replace("\\", "/")
with maintained_selection():
camera_node = nuke.toNode(object_name)
diff --git a/openpype/hosts/nuke/plugins/load/load_clip.py b/openpype/hosts/nuke/plugins/load/load_clip.py
index a253ba4a9d..2b4315a830 100644
--- a/openpype/hosts/nuke/plugins/load/load_clip.py
+++ b/openpype/hosts/nuke/plugins/load/load_clip.py
@@ -1,7 +1,8 @@
import nuke
import qargparse
-from avalon import api, io
+from avalon import io
+from openpype.pipeline import get_representation_path
from openpype.hosts.nuke.api.lib import (
get_imageio_input_colorspace,
maintained_selection
@@ -41,6 +42,9 @@ class LoadClip(plugin.NukeLoader):
icon = "file-video-o"
color = "white"
+ # Loaded from settings
+ _representations = []
+
script_start = int(nuke.root()["first_frame"].value())
# option gui
@@ -186,7 +190,7 @@ class LoadClip(plugin.NukeLoader):
is_sequence = len(representation["files"]) > 1
read_node = nuke.toNode(container['objectName'])
- file = api.get_representation_path(representation).replace("\\", "/")
+ file = get_representation_path(representation).replace("\\", "/")
start_at_workfile = bool("start at" in read_node['frame_mode'].value())
diff --git a/openpype/hosts/nuke/plugins/load/load_effects.py b/openpype/hosts/nuke/plugins/load/load_effects.py
index f636c6b510..68c3952942 100644
--- a/openpype/hosts/nuke/plugins/load/load_effects.py
+++ b/openpype/hosts/nuke/plugins/load/load_effects.py
@@ -1,7 +1,13 @@
import json
from collections import OrderedDict
import nuke
-from avalon import api, style, io
+
+from avalon import io
+
+from openpype.pipeline import (
+ load,
+ get_representation_path,
+)
from openpype.hosts.nuke.api import (
containerise,
update_container,
@@ -9,7 +15,7 @@ from openpype.hosts.nuke.api import (
)
-class LoadEffects(api.Loader):
+class LoadEffects(load.LoaderPlugin):
"""Loading colorspace soft effect exported from nukestudio"""
representations = ["effectJson"]
@@ -18,7 +24,7 @@ class LoadEffects(api.Loader):
label = "Load Effects - nodes"
order = 0
icon = "cc"
- color = style.colors.light
+ color = "white"
ignore_attr = ["useLifetime"]
@@ -149,7 +155,7 @@ class LoadEffects(api.Loader):
# get corresponding node
GN = nuke.toNode(container['objectName'])
- file = api.get_representation_path(representation).replace("\\", "/")
+ file = get_representation_path(representation).replace("\\", "/")
name = container['name']
version_data = version.get("data", {})
vname = version.get("name", None)
diff --git a/openpype/hosts/nuke/plugins/load/load_effects_ip.py b/openpype/hosts/nuke/plugins/load/load_effects_ip.py
index 990bce54f1..9c4fd4c2c6 100644
--- a/openpype/hosts/nuke/plugins/load/load_effects_ip.py
+++ b/openpype/hosts/nuke/plugins/load/load_effects_ip.py
@@ -3,7 +3,12 @@ from collections import OrderedDict
import nuke
-from avalon import api, style, io
+from avalon import io
+
+from openpype.pipeline import (
+ load,
+ get_representation_path,
+)
from openpype.hosts.nuke.api import lib
from openpype.hosts.nuke.api import (
containerise,
@@ -12,7 +17,7 @@ from openpype.hosts.nuke.api import (
)
-class LoadEffectsInputProcess(api.Loader):
+class LoadEffectsInputProcess(load.LoaderPlugin):
"""Loading colorspace soft effect exported from nukestudio"""
representations = ["effectJson"]
@@ -21,7 +26,7 @@ class LoadEffectsInputProcess(api.Loader):
label = "Load Effects - Input Process"
order = 0
icon = "eye"
- color = style.colors.alert
+ color = "#cc0000"
ignore_attr = ["useLifetime"]
def load(self, context, name, namespace, data):
@@ -156,7 +161,7 @@ class LoadEffectsInputProcess(api.Loader):
# get corresponding node
GN = nuke.toNode(container['objectName'])
- file = api.get_representation_path(representation).replace("\\", "/")
+ file = get_representation_path(representation).replace("\\", "/")
name = container['name']
version_data = version.get("data", {})
vname = version.get("name", None)
diff --git a/openpype/hosts/nuke/plugins/load/load_gizmo.py b/openpype/hosts/nuke/plugins/load/load_gizmo.py
index 659977d789..6f2b191be9 100644
--- a/openpype/hosts/nuke/plugins/load/load_gizmo.py
+++ b/openpype/hosts/nuke/plugins/load/load_gizmo.py
@@ -1,5 +1,11 @@
import nuke
-from avalon import api, style, io
+
+from avalon import io
+
+from openpype.pipeline import (
+ load,
+ get_representation_path,
+)
from openpype.hosts.nuke.api.lib import (
maintained_selection,
get_avalon_knob_data,
@@ -12,7 +18,7 @@ from openpype.hosts.nuke.api import (
)
-class LoadGizmo(api.Loader):
+class LoadGizmo(load.LoaderPlugin):
"""Loading nuke Gizmo"""
representations = ["gizmo"]
@@ -21,7 +27,7 @@ class LoadGizmo(api.Loader):
label = "Load Gizmo"
order = 0
icon = "dropbox"
- color = style.colors.light
+ color = "white"
node_color = "0x75338eff"
def load(self, context, name, namespace, data):
@@ -103,7 +109,7 @@ class LoadGizmo(api.Loader):
# get corresponding node
GN = nuke.toNode(container['objectName'])
- file = api.get_representation_path(representation).replace("\\", "/")
+ file = get_representation_path(representation).replace("\\", "/")
name = container['name']
version_data = version.get("data", {})
vname = version.get("name", None)
diff --git a/openpype/hosts/nuke/plugins/load/load_gizmo_ip.py b/openpype/hosts/nuke/plugins/load/load_gizmo_ip.py
index 240bfd467d..87bebce15b 100644
--- a/openpype/hosts/nuke/plugins/load/load_gizmo_ip.py
+++ b/openpype/hosts/nuke/plugins/load/load_gizmo_ip.py
@@ -1,5 +1,11 @@
-from avalon import api, style, io
import nuke
+
+from avalon import io
+
+from openpype.pipeline import (
+ load,
+ get_representation_path,
+)
from openpype.hosts.nuke.api.lib import (
maintained_selection,
create_backdrop,
@@ -13,7 +19,7 @@ from openpype.hosts.nuke.api import (
)
-class LoadGizmoInputProcess(api.Loader):
+class LoadGizmoInputProcess(load.LoaderPlugin):
"""Loading colorspace soft effect exported from nukestudio"""
representations = ["gizmo"]
@@ -22,7 +28,7 @@ class LoadGizmoInputProcess(api.Loader):
label = "Load Gizmo - Input Process"
order = 0
icon = "eye"
- color = style.colors.alert
+ color = "#cc0000"
node_color = "0x7533c1ff"
def load(self, context, name, namespace, data):
@@ -109,7 +115,7 @@ class LoadGizmoInputProcess(api.Loader):
# get corresponding node
GN = nuke.toNode(container['objectName'])
- file = api.get_representation_path(representation).replace("\\", "/")
+ file = get_representation_path(representation).replace("\\", "/")
name = container['name']
version_data = version.get("data", {})
vname = version.get("name", None)
diff --git a/openpype/hosts/nuke/plugins/load/load_image.py b/openpype/hosts/nuke/plugins/load/load_image.py
index 27c634ec57..9a175a0cba 100644
--- a/openpype/hosts/nuke/plugins/load/load_image.py
+++ b/openpype/hosts/nuke/plugins/load/load_image.py
@@ -1,8 +1,12 @@
import nuke
import qargparse
-from avalon import api, io
+from avalon import io
+from openpype.pipeline import (
+ load,
+ get_representation_path,
+)
from openpype.hosts.nuke.api.lib import (
get_imageio_input_colorspace
)
@@ -13,7 +17,7 @@ from openpype.hosts.nuke.api import (
)
-class LoadImage(api.Loader):
+class LoadImage(load.LoaderPlugin):
"""Load still image into Nuke"""
families = [
@@ -32,6 +36,9 @@ class LoadImage(api.Loader):
icon = "image"
color = "white"
+ # Loaded from settings
+ _representations = []
+
node_name_template = "{class_name}_{ext}"
options = [
@@ -161,7 +168,7 @@ class LoadImage(api.Loader):
repr_cont = representation["context"]
- file = api.get_representation_path(representation)
+ file = get_representation_path(representation)
if not file:
repr_id = representation["_id"]
diff --git a/openpype/hosts/nuke/plugins/load/load_matchmove.py b/openpype/hosts/nuke/plugins/load/load_matchmove.py
index 60d5dc026f..f5a90706c7 100644
--- a/openpype/hosts/nuke/plugins/load/load_matchmove.py
+++ b/openpype/hosts/nuke/plugins/load/load_matchmove.py
@@ -1,8 +1,8 @@
-from avalon import api
import nuke
+from openpype.pipeline import load
-class MatchmoveLoader(api.Loader):
+class MatchmoveLoader(load.LoaderPlugin):
"""
This will run matchmove script to create track in script.
"""
diff --git a/openpype/hosts/nuke/plugins/load/load_model.py b/openpype/hosts/nuke/plugins/load/load_model.py
index 2b52bbf00f..e445beca05 100644
--- a/openpype/hosts/nuke/plugins/load/load_model.py
+++ b/openpype/hosts/nuke/plugins/load/load_model.py
@@ -1,5 +1,9 @@
import nuke
-from avalon import api, io
+from avalon import io
+from openpype.pipeline import (
+ load,
+ get_representation_path,
+)
from openpype.hosts.nuke.api.lib import maintained_selection
from openpype.hosts.nuke.api import (
containerise,
@@ -8,7 +12,7 @@ from openpype.hosts.nuke.api import (
)
-class AlembicModelLoader(api.Loader):
+class AlembicModelLoader(load.LoaderPlugin):
"""
This will load alembic model into script.
"""
@@ -124,7 +128,7 @@ class AlembicModelLoader(api.Loader):
data_imprint.update({k: version_data[k]})
# getting file path
- file = api.get_representation_path(representation).replace("\\", "/")
+ file = get_representation_path(representation).replace("\\", "/")
with maintained_selection():
model_node = nuke.toNode(object_name)
diff --git a/openpype/hosts/nuke/plugins/load/load_script_precomp.py b/openpype/hosts/nuke/plugins/load/load_script_precomp.py
index aa48b631c5..779f101682 100644
--- a/openpype/hosts/nuke/plugins/load/load_script_precomp.py
+++ b/openpype/hosts/nuke/plugins/load/load_script_precomp.py
@@ -1,5 +1,11 @@
import nuke
-from avalon import api, style, io
+
+from avalon import io
+
+from openpype.pipeline import (
+ load,
+ get_representation_path,
+)
from openpype.hosts.nuke.api.lib import get_avalon_knob_data
from openpype.hosts.nuke.api import (
containerise,
@@ -8,7 +14,7 @@ from openpype.hosts.nuke.api import (
)
-class LinkAsGroup(api.Loader):
+class LinkAsGroup(load.LoaderPlugin):
"""Copy the published file to be pasted at the desired location"""
representations = ["nk"]
@@ -17,7 +23,7 @@ class LinkAsGroup(api.Loader):
label = "Load Precomp"
order = 0
icon = "file"
- color = style.colors.alert
+ color = "#cc0000"
def load(self, context, name, namespace, data):
# for k, v in context.items():
@@ -108,7 +114,7 @@ class LinkAsGroup(api.Loader):
"""
node = nuke.toNode(container['objectName'])
- root = api.get_representation_path(representation).replace("\\", "/")
+ root = get_representation_path(representation).replace("\\", "/")
# Get start frame from version data
version = io.find_one({
diff --git a/openpype/hosts/nuke/plugins/publish/precollect_writes.py b/openpype/hosts/nuke/plugins/publish/precollect_writes.py
index 189f28f7c6..85e98db7ed 100644
--- a/openpype/hosts/nuke/plugins/publish/precollect_writes.py
+++ b/openpype/hosts/nuke/plugins/publish/precollect_writes.py
@@ -3,8 +3,9 @@ import re
from pprint import pformat
import nuke
import pyblish.api
+from avalon import io
import openpype.api as pype
-from avalon import io, api
+from openpype.pipeline import get_representation_path
@pyblish.api.log
@@ -182,7 +183,7 @@ class CollectNukeWrites(pyblish.api.InstancePlugin):
if repre_doc:
instance.data["audio"] = [{
"offset": 0,
- "filename": api.get_representation_path(repre_doc)
+ "filename": get_representation_path(repre_doc)
}]
self.log.debug("instance.data: {}".format(pformat(instance.data)))
diff --git a/openpype/hosts/nuke/plugins/publish/validate_read_legacy.py b/openpype/hosts/nuke/plugins/publish/validate_read_legacy.py
index 22a9b3678e..2bf1ff81f8 100644
--- a/openpype/hosts/nuke/plugins/publish/validate_read_legacy.py
+++ b/openpype/hosts/nuke/plugins/publish/validate_read_legacy.py
@@ -1,12 +1,16 @@
import os
-import toml
import nuke
+import toml
import pyblish.api
-from avalon import api
from bson.objectid import ObjectId
+from openpype.pipeline import (
+ discover_loader_plugins,
+ load_container,
+)
+
class RepairReadLegacyAction(pyblish.api.Action):
@@ -49,13 +53,13 @@ class RepairReadLegacyAction(pyblish.api.Action):
loader_name = "LoadMov"
loader_plugin = None
- for Loader in api.discover(api.Loader):
+ for Loader in discover_loader_plugins():
if Loader.__name__ != loader_name:
continue
loader_plugin = Loader
- api.load(
+ load_container(
Loader=loader_plugin,
representation=ObjectId(data["representation"])
)
diff --git a/openpype/hosts/photoshop/api/README.md b/openpype/hosts/photoshop/api/README.md
index b958f53803..80792a4da0 100644
--- a/openpype/hosts/photoshop/api/README.md
+++ b/openpype/hosts/photoshop/api/README.md
@@ -195,11 +195,12 @@ class ExtractImage(openpype.api.Extractor):
#### Loader Plugin
```python
from avalon import api, photoshop
+from openpype.pipeline import load, get_representation_path
stub = photoshop.stub()
-class ImageLoader(api.Loader):
+class ImageLoader(load.LoaderPlugin):
"""Load images
Stores the imported asset in a container named after the asset.
@@ -227,7 +228,7 @@ class ImageLoader(api.Loader):
with photoshop.maintained_selection():
stub.replace_smart_object(
- layer, api.get_representation_path(representation)
+ layer, get_representation_path(representation)
)
stub.imprint(
@@ -245,7 +246,7 @@ https://community.adobe.com/t5/download-install/adobe-extension-debuger-problem/
Add --enable-blink-features=ShadowDOMV0,CustomElementsV0 when starting Chrome
then localhost:8078 (port set in `photoshop\extension\.debug`)
-Or use Visual Studio Code https://medium.com/adobetech/extendscript-debugger-for-visual-studio-code-public-release-a2ff6161fa01
+Or use Visual Studio Code https://medium.com/adobetech/extendscript-debugger-for-visual-studio-code-public-release-a2ff6161fa01
Or install CEF client from https://github.com/Adobe-CEP/CEP-Resources/tree/master/CEP_9.x
## Resources
diff --git a/openpype/hosts/photoshop/api/launch_logic.py b/openpype/hosts/photoshop/api/launch_logic.py
index 112cd8fe3f..0021905cb5 100644
--- a/openpype/hosts/photoshop/api/launch_logic.py
+++ b/openpype/hosts/photoshop/api/launch_logic.py
@@ -14,7 +14,7 @@ from openpype.api import Logger
from openpype.tools.utils import host_tools
from avalon import api
-from avalon.tools.webserver.app import WebServerTool
+from openpype.tools.adobe_webserver.app import WebServerTool
from .ws_stub import PhotoshopServerStub
diff --git a/openpype/hosts/photoshop/api/pipeline.py b/openpype/hosts/photoshop/api/pipeline.py
index 662e9dbebc..e814e1ca4d 100644
--- a/openpype/hosts/photoshop/api/pipeline.py
+++ b/openpype/hosts/photoshop/api/pipeline.py
@@ -6,7 +6,12 @@ import avalon.api
from avalon import pipeline, io
from openpype.api import Logger
-from openpype.pipeline import LegacyCreator
+from openpype.lib import register_event_callback
+from openpype.pipeline import (
+ LegacyCreator,
+ register_loader_plugin_path,
+ deregister_loader_plugin_path,
+)
import openpype.hosts.photoshop
from . import lib
@@ -67,7 +72,7 @@ def install():
pyblish.api.register_host("photoshop")
pyblish.api.register_plugin_path(PUBLISH_PATH)
- avalon.api.register_plugin_path(avalon.api.Loader, LOAD_PATH)
+ register_loader_plugin_path(LOAD_PATH)
avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH)
log.info(PUBLISH_PATH)
@@ -75,12 +80,12 @@ def install():
"instanceToggled", on_pyblish_instance_toggled
)
- avalon.api.on("application.launched", on_application_launch)
+ register_event_callback("application.launched", on_application_launch)
def uninstall():
pyblish.api.deregister_plugin_path(PUBLISH_PATH)
- avalon.api.deregister_plugin_path(avalon.api.Loader, LOAD_PATH)
+ deregister_loader_plugin_path(LOAD_PATH)
avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH)
diff --git a/openpype/hosts/photoshop/api/plugin.py b/openpype/hosts/photoshop/api/plugin.py
index c577c67d82..c80e6bbd06 100644
--- a/openpype/hosts/photoshop/api/plugin.py
+++ b/openpype/hosts/photoshop/api/plugin.py
@@ -1,6 +1,6 @@
import re
-import avalon.api
+from openpype.pipeline import LoaderPlugin
from .launch_logic import stub
@@ -29,7 +29,7 @@ def get_unique_layer_name(layers, asset_name, subset_name):
return "{}_{:0>3d}".format(name, occurrences + 1)
-class PhotoshopLoader(avalon.api.Loader):
+class PhotoshopLoader(LoaderPlugin):
@staticmethod
def get_stub():
return stub()
diff --git a/openpype/hosts/photoshop/api/ws_stub.py b/openpype/hosts/photoshop/api/ws_stub.py
index d4406d17b9..64d89f5420 100644
--- a/openpype/hosts/photoshop/api/ws_stub.py
+++ b/openpype/hosts/photoshop/api/ws_stub.py
@@ -2,12 +2,11 @@
Stub handling connection from server to client.
Used anywhere solution is calling client methods.
"""
-import sys
import json
import attr
from wsrpc_aiohttp import WebSocketAsync
-from avalon.tools.webserver.app import WebServerTool
+from openpype.tools.adobe_webserver.app import WebServerTool
@attr.s
diff --git a/openpype/hosts/photoshop/plugins/load/load_image.py b/openpype/hosts/photoshop/plugins/load/load_image.py
index 3b1cfe9636..0a9421b8f2 100644
--- a/openpype/hosts/photoshop/plugins/load/load_image.py
+++ b/openpype/hosts/photoshop/plugins/load/load_image.py
@@ -1,6 +1,6 @@
import re
-from avalon import api
+from openpype.pipeline import get_representation_path
from openpype.hosts.photoshop import api as photoshop
from openpype.hosts.photoshop.api import get_unique_layer_name
@@ -54,7 +54,7 @@ class ImageLoader(photoshop.PhotoshopLoader):
else: # switching version - keep same name
layer_name = container["namespace"]
- path = api.get_representation_path(representation)
+ path = get_representation_path(representation)
with photoshop.maintained_selection():
stub.replace_smart_object(
layer, path, layer_name
diff --git a/openpype/hosts/photoshop/plugins/load/load_image_from_sequence.py b/openpype/hosts/photoshop/plugins/load/load_image_from_sequence.py
index 12e0503dfc..5f39121ae1 100644
--- a/openpype/hosts/photoshop/plugins/load/load_image_from_sequence.py
+++ b/openpype/hosts/photoshop/plugins/load/load_image_from_sequence.py
@@ -1,8 +1,8 @@
import os
import qargparse
-from avalon.pipeline import get_representation_path_from_context
+from openpype.pipeline import get_representation_path_from_context
from openpype.hosts.photoshop import api as photoshop
from openpype.hosts.photoshop.api import get_unique_layer_name
diff --git a/openpype/hosts/photoshop/plugins/load/load_reference.py b/openpype/hosts/photoshop/plugins/load/load_reference.py
index 60142d4a1f..f5f0545d39 100644
--- a/openpype/hosts/photoshop/plugins/load/load_reference.py
+++ b/openpype/hosts/photoshop/plugins/load/load_reference.py
@@ -1,7 +1,6 @@
import re
-from avalon import api
-
+from openpype.pipeline import get_representation_path
from openpype.hosts.photoshop import api as photoshop
from openpype.hosts.photoshop.api import get_unique_layer_name
@@ -55,7 +54,7 @@ class ReferenceLoader(photoshop.PhotoshopLoader):
else: # switching version - keep same name
layer_name = container["namespace"]
- path = api.get_representation_path(representation)
+ path = get_representation_path(representation)
with photoshop.maintained_selection():
stub.replace_smart_object(
layer, path, layer_name
diff --git a/openpype/hosts/resolve/api/pipeline.py b/openpype/hosts/resolve/api/pipeline.py
index c82545268b..fa309e3503 100644
--- a/openpype/hosts/resolve/api/pipeline.py
+++ b/openpype/hosts/resolve/api/pipeline.py
@@ -9,7 +9,11 @@ from avalon import schema
from avalon.pipeline import AVALON_CONTAINER_ID
from pyblish import api as pyblish
from openpype.api import Logger
-from openpype.pipeline import LegacyCreator
+from openpype.pipeline import (
+ LegacyCreator,
+ register_loader_plugin_path,
+ deregister_loader_plugin_path,
+)
from . import lib
from . import PLUGINS_DIR
from openpype.tools.utils import host_tools
@@ -42,7 +46,7 @@ def install():
pyblish.register_plugin_path(PUBLISH_PATH)
log.info("Registering DaVinci Resovle plug-ins..")
- avalon.register_plugin_path(avalon.Loader, LOAD_PATH)
+ register_loader_plugin_path(LOAD_PATH)
avalon.register_plugin_path(LegacyCreator, CREATE_PATH)
avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH)
@@ -67,7 +71,7 @@ def uninstall():
pyblish.deregister_plugin_path(PUBLISH_PATH)
log.info("Deregistering DaVinci Resovle plug-ins..")
- avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH)
+ deregister_loader_plugin_path(LOAD_PATH)
avalon.deregister_plugin_path(LegacyCreator, CREATE_PATH)
avalon.deregister_plugin_path(avalon.InventoryAction, INVENTORY_PATH)
diff --git a/openpype/hosts/resolve/api/plugin.py b/openpype/hosts/resolve/api/plugin.py
index e7793d6e95..8e1436021c 100644
--- a/openpype/hosts/resolve/api/plugin.py
+++ b/openpype/hosts/resolve/api/plugin.py
@@ -4,14 +4,15 @@ import uuid
import qargparse
from Qt import QtWidgets, QtCore
-from avalon import api
import openpype.api as pype
-from openpype.pipeline import LegacyCreator
+from openpype.pipeline import (
+ LegacyCreator,
+ LoaderPlugin,
+)
from openpype.hosts import resolve
from . import lib
-
class CreatorWidget(QtWidgets.QDialog):
# output items
@@ -292,7 +293,7 @@ class ClipLoader:
""" Initialize object
Arguments:
- cls (avalon.api.Loader): plugin object
+ cls (openpype.pipeline.load.LoaderPlugin): plugin object
context (dict): loader plugin context
options (dict)[optional]: possible keys:
projectBinPath: "path/to/binItem"
@@ -448,7 +449,7 @@ class ClipLoader:
return timeline_item
-class TimelineItemLoader(api.Loader):
+class TimelineItemLoader(LoaderPlugin):
"""A basic SequenceLoader for Resolve
This will implement the basic behavior for a loader to inherit from that
diff --git a/openpype/hosts/resolve/plugins/load/load_clip.py b/openpype/hosts/resolve/plugins/load/load_clip.py
index e20384ee6c..71850d95f6 100644
--- a/openpype/hosts/resolve/plugins/load/load_clip.py
+++ b/openpype/hosts/resolve/plugins/load/load_clip.py
@@ -1,11 +1,14 @@
-from avalon import io, api
-from openpype.hosts import resolve
from copy import deepcopy
from importlib import reload
+
+from avalon import io
+from openpype.hosts import resolve
+from openpype.pipeline import get_representation_path
from openpype.hosts.resolve.api import lib, plugin
reload(plugin)
reload(lib)
+
class LoadClip(resolve.TimelineItemLoader):
"""Load a subset to timeline as clip
@@ -99,7 +102,7 @@ class LoadClip(resolve.TimelineItemLoader):
version_name = version.get("name", None)
colorspace = version_data.get("colorspace", None)
object_name = "{}_{}".format(name, namespace)
- self.fname = api.get_representation_path(representation)
+ self.fname = get_representation_path(representation)
context["version"] = {"data": version_data}
loader = resolve.ClipLoader(self, context)
diff --git a/openpype/hosts/standalonepublisher/plugins/publish/extract_trim_video_audio.py b/openpype/hosts/standalonepublisher/plugins/publish/extract_trim_video_audio.py
index c18de5bc1c..f327895b83 100644
--- a/openpype/hosts/standalonepublisher/plugins/publish/extract_trim_video_audio.py
+++ b/openpype/hosts/standalonepublisher/plugins/publish/extract_trim_video_audio.py
@@ -2,6 +2,9 @@ import os
import pyblish.api
import openpype.api
+from openpype.lib import (
+ get_ffmpeg_tool_path,
+)
from pprint import pformat
@@ -27,7 +30,7 @@ class ExtractTrimVideoAudio(openpype.api.Extractor):
instance.data["representations"] = list()
# get ffmpet path
- ffmpeg_path = openpype.lib.get_ffmpeg_tool_path("ffmpeg")
+ ffmpeg_path = get_ffmpeg_tool_path("ffmpeg")
# get staging dir
staging_dir = self.staging_dir(instance)
@@ -44,7 +47,7 @@ class ExtractTrimVideoAudio(openpype.api.Extractor):
clip_trimed_path = os.path.join(
staging_dir, instance.data["name"] + ext)
# # check video file metadata
- # input_data = plib.ffprobe_streams(video_file_path)[0]
+ # input_data = plib.get_ffprobe_streams(video_file_path)[0]
# self.log.debug(f"__ input_data: `{input_data}`")
start = float(instance.data["clipInH"])
diff --git a/openpype/hosts/tvpaint/api/communication_server.py b/openpype/hosts/tvpaint/api/communication_server.py
index e9c5f4c73e..65cb9aa2f3 100644
--- a/openpype/hosts/tvpaint/api/communication_server.py
+++ b/openpype/hosts/tvpaint/api/communication_server.py
@@ -21,7 +21,7 @@ from aiohttp_json_rpc.protocol import (
)
from aiohttp_json_rpc.exceptions import RpcError
-from avalon import api
+from openpype.lib import emit_event
from openpype.hosts.tvpaint.tvpaint_plugin import get_plugin_files_path
log = logging.getLogger(__name__)
@@ -754,7 +754,7 @@ class BaseCommunicator:
self._on_client_connect()
- api.emit("application.launched")
+ emit_event("application.launched")
def _on_client_connect(self):
self._initial_textfile_write()
@@ -938,5 +938,5 @@ class QtCommunicator(BaseCommunicator):
def _exit(self, *args, **kwargs):
super()._exit(*args, **kwargs)
- api.emit("application.exit")
+ emit_event("application.exit")
self.qt_app.exit(self.exit_code)
diff --git a/openpype/hosts/tvpaint/api/pipeline.py b/openpype/hosts/tvpaint/api/pipeline.py
index f4599047b4..46c9d3a1dd 100644
--- a/openpype/hosts/tvpaint/api/pipeline.py
+++ b/openpype/hosts/tvpaint/api/pipeline.py
@@ -14,7 +14,12 @@ from avalon.pipeline import AVALON_CONTAINER_ID
from openpype.hosts import tvpaint
from openpype.api import get_current_project_settings
-from openpype.pipeline import LegacyCreator
+from openpype.lib import register_event_callback
+from openpype.pipeline import (
+ LegacyCreator,
+ register_loader_plugin_path,
+ deregister_loader_plugin_path,
+)
from .lib import (
execute_george,
@@ -76,7 +81,7 @@ def install():
pyblish.api.register_host("tvpaint")
pyblish.api.register_plugin_path(PUBLISH_PATH)
- avalon.api.register_plugin_path(avalon.api.Loader, LOAD_PATH)
+ register_loader_plugin_path(LOAD_PATH)
avalon.api.register_plugin_path(LegacyCreator, CREATE_PATH)
registered_callbacks = (
@@ -85,8 +90,8 @@ def install():
if on_instance_toggle not in registered_callbacks:
pyblish.api.register_callback("instanceToggled", on_instance_toggle)
- avalon.api.on("application.launched", initial_launch)
- avalon.api.on("application.exit", application_exit)
+ register_event_callback("application.launched", initial_launch)
+ register_event_callback("application.exit", application_exit)
def uninstall():
@@ -98,7 +103,7 @@ def uninstall():
log.info("OpenPype - Uninstalling TVPaint integration")
pyblish.api.deregister_host("tvpaint")
pyblish.api.deregister_plugin_path(PUBLISH_PATH)
- avalon.api.deregister_plugin_path(avalon.api.Loader, LOAD_PATH)
+ deregister_loader_plugin_path(LOAD_PATH)
avalon.api.deregister_plugin_path(LegacyCreator, CREATE_PATH)
diff --git a/openpype/hosts/tvpaint/api/plugin.py b/openpype/hosts/tvpaint/api/plugin.py
index 8510794f06..15ad8905e0 100644
--- a/openpype/hosts/tvpaint/api/plugin.py
+++ b/openpype/hosts/tvpaint/api/plugin.py
@@ -1,9 +1,10 @@
import re
import uuid
-import avalon.api
-
-from openpype.pipeline import LegacyCreator
+from openpype.pipeline import (
+ LegacyCreator,
+ LoaderPlugin,
+)
from openpype.hosts.tvpaint.api import (
pipeline,
lib
@@ -74,7 +75,7 @@ class Creator(LegacyCreator):
self.write_instances(data)
-class Loader(avalon.api.Loader):
+class Loader(LoaderPlugin):
hosts = ["tvpaint"]
@staticmethod
diff --git a/openpype/hosts/unreal/api/pipeline.py b/openpype/hosts/unreal/api/pipeline.py
index 8ab19bd697..9ec11b942d 100644
--- a/openpype/hosts/unreal/api/pipeline.py
+++ b/openpype/hosts/unreal/api/pipeline.py
@@ -7,7 +7,11 @@ import pyblish.api
from avalon.pipeline import AVALON_CONTAINER_ID
from avalon import api
-from openpype.pipeline import LegacyCreator
+from openpype.pipeline import (
+ LegacyCreator,
+ register_loader_plugin_path,
+ deregister_loader_plugin_path,
+)
from openpype.tools.utils import host_tools
import openpype.hosts.unreal
@@ -44,7 +48,7 @@ def install():
print("-=" * 40)
logger.info("installing OpenPype for Unreal")
pyblish.api.register_plugin_path(str(PUBLISH_PATH))
- api.register_plugin_path(api.Loader, str(LOAD_PATH))
+ register_loader_plugin_path(str(LOAD_PATH))
api.register_plugin_path(LegacyCreator, str(CREATE_PATH))
_register_callbacks()
_register_events()
@@ -53,7 +57,7 @@ def install():
def uninstall():
"""Uninstall Unreal configuration for Avalon."""
pyblish.api.deregister_plugin_path(str(PUBLISH_PATH))
- api.deregister_plugin_path(api.Loader, str(LOAD_PATH))
+ deregister_loader_plugin_path(str(LOAD_PATH))
api.deregister_plugin_path(LegacyCreator, str(CREATE_PATH))
diff --git a/openpype/hosts/unreal/api/plugin.py b/openpype/hosts/unreal/api/plugin.py
index dd2e7750f0..b24bab831d 100644
--- a/openpype/hosts/unreal/api/plugin.py
+++ b/openpype/hosts/unreal/api/plugin.py
@@ -1,8 +1,10 @@
# -*- coding: utf-8 -*-
from abc import ABC
-from openpype.pipeline import LegacyCreator
-import avalon.api
+from openpype.pipeline import (
+ LegacyCreator,
+ LoaderPlugin,
+)
class Creator(LegacyCreator):
@@ -10,6 +12,6 @@ class Creator(LegacyCreator):
defaults = ['Main']
-class Loader(avalon.api.Loader, ABC):
+class Loader(LoaderPlugin, ABC):
"""This serves as skeleton for future OpenPype specific functionality"""
pass
diff --git a/openpype/hosts/unreal/plugins/load/load_alembic_geometrycache.py b/openpype/hosts/unreal/plugins/load/load_alembic_geometrycache.py
index 027e9f4cd3..3508fe5ed7 100644
--- a/openpype/hosts/unreal/plugins/load/load_alembic_geometrycache.py
+++ b/openpype/hosts/unreal/plugins/load/load_alembic_geometrycache.py
@@ -2,7 +2,8 @@
"""Loader for published alembics."""
import os
-from avalon import api, pipeline
+from avalon import pipeline
+from openpype.pipeline import get_representation_path
from openpype.hosts.unreal.api import plugin
from openpype.hosts.unreal.api import pipeline as unreal_pipeline
@@ -140,7 +141,7 @@ class PointCacheAlembicLoader(plugin.Loader):
def update(self, container, representation):
name = container["asset_name"]
- source_path = api.get_representation_path(representation)
+ source_path = get_representation_path(representation)
destination_path = container["namespace"]
task = self.get_task(source_path, destination_path, name, True)
diff --git a/openpype/hosts/unreal/plugins/load/load_alembic_skeletalmesh.py b/openpype/hosts/unreal/plugins/load/load_alembic_skeletalmesh.py
index 0236bab138..180942de51 100644
--- a/openpype/hosts/unreal/plugins/load/load_alembic_skeletalmesh.py
+++ b/openpype/hosts/unreal/plugins/load/load_alembic_skeletalmesh.py
@@ -2,7 +2,8 @@
"""Load Skeletal Mesh alembics."""
import os
-from avalon import api, pipeline
+from avalon import pipeline
+from openpype.pipeline import get_representation_path
from openpype.hosts.unreal.api import plugin
from openpype.hosts.unreal.api import pipeline as unreal_pipeline
import unreal # noqa
@@ -104,7 +105,7 @@ class SkeletalMeshAlembicLoader(plugin.Loader):
def update(self, container, representation):
name = container["asset_name"]
- source_path = api.get_representation_path(representation)
+ source_path = get_representation_path(representation)
destination_path = container["namespace"]
task = unreal.AssetImportTask()
diff --git a/openpype/hosts/unreal/plugins/load/load_alembic_staticmesh.py b/openpype/hosts/unreal/plugins/load/load_alembic_staticmesh.py
index 3bcc8b476f..4e00af1d97 100644
--- a/openpype/hosts/unreal/plugins/load/load_alembic_staticmesh.py
+++ b/openpype/hosts/unreal/plugins/load/load_alembic_staticmesh.py
@@ -2,7 +2,8 @@
"""Loader for Static Mesh alembics."""
import os
-from avalon import api, pipeline
+from avalon import pipeline
+from openpype.pipeline import get_representation_path
from openpype.hosts.unreal.api import plugin
from openpype.hosts.unreal.api import pipeline as unreal_pipeline
import unreal # noqa
@@ -123,7 +124,7 @@ class StaticMeshAlembicLoader(plugin.Loader):
def update(self, container, representation):
name = container["asset_name"]
- source_path = api.get_representation_path(representation)
+ source_path = get_representation_path(representation)
destination_path = container["namespace"]
task = self.get_task(source_path, destination_path, name, True)
diff --git a/openpype/hosts/unreal/plugins/load/load_animation.py b/openpype/hosts/unreal/plugins/load/load_animation.py
index 63c734b969..8ef81f7851 100644
--- a/openpype/hosts/unreal/plugins/load/load_animation.py
+++ b/openpype/hosts/unreal/plugins/load/load_animation.py
@@ -3,7 +3,8 @@
import os
import json
-from avalon import api, pipeline
+from avalon import pipeline
+from openpype.pipeline import get_representation_path
from openpype.hosts.unreal.api import plugin
from openpype.hosts.unreal.api import pipeline as unreal_pipeline
import unreal # noqa
@@ -173,7 +174,7 @@ class AnimationFBXLoader(plugin.Loader):
def update(self, container, representation):
name = container["asset_name"]
- source_path = api.get_representation_path(representation)
+ source_path = get_representation_path(representation)
destination_path = container["namespace"]
task = unreal.AssetImportTask()
diff --git a/openpype/hosts/unreal/plugins/load/load_layout.py b/openpype/hosts/unreal/plugins/load/load_layout.py
index b802f5940a..19ee179d20 100644
--- a/openpype/hosts/unreal/plugins/load/load_layout.py
+++ b/openpype/hosts/unreal/plugins/load/load_layout.py
@@ -11,7 +11,13 @@ from unreal import AssetToolsHelpers
from unreal import FBXImportType
from unreal import MathLibrary as umath
-from avalon import api, pipeline
+from avalon.pipeline import AVALON_CONTAINER_ID
+from openpype.pipeline import (
+ discover_loader_plugins,
+ loaders_from_representation,
+ load_container,
+ get_representation_path,
+)
from openpype.hosts.unreal.api import plugin
from openpype.hosts.unreal.api import pipeline as unreal_pipeline
@@ -205,7 +211,7 @@ class LayoutLoader(plugin.Loader):
with open(lib_path, "r") as fp:
data = json.load(fp)
- all_loaders = api.discover(api.Loader)
+ all_loaders = discover_loader_plugins()
if not loaded:
loaded = []
@@ -235,7 +241,7 @@ class LayoutLoader(plugin.Loader):
loaded.append(reference)
family = element.get('family')
- loaders = api.loaders_from_representation(
+ loaders = loaders_from_representation(
all_loaders, reference)
loader = None
@@ -252,7 +258,7 @@ class LayoutLoader(plugin.Loader):
"asset_dir": asset_dir
}
- assets = api.load(
+ assets = load_container(
loader,
reference,
namespace=instance_name,
@@ -387,7 +393,7 @@ class LayoutLoader(plugin.Loader):
data = {
"schema": "openpype:container-2.0",
- "id": pipeline.AVALON_CONTAINER_ID,
+ "id": AVALON_CONTAINER_ID,
"asset": asset,
"namespace": asset_dir,
"container_name": container_name,
@@ -411,9 +417,9 @@ class LayoutLoader(plugin.Loader):
def update(self, container, representation):
ar = unreal.AssetRegistryHelpers.get_asset_registry()
- source_path = api.get_representation_path(representation)
+ source_path = get_representation_path(representation)
destination_path = container["namespace"]
- lib_path = Path(api.get_representation_path(representation))
+ lib_path = Path(get_representation_path(representation))
self._remove_actors(destination_path)
diff --git a/openpype/hosts/unreal/plugins/load/load_rig.py b/openpype/hosts/unreal/plugins/load/load_rig.py
index a7ecb0ef7d..3d5616364c 100644
--- a/openpype/hosts/unreal/plugins/load/load_rig.py
+++ b/openpype/hosts/unreal/plugins/load/load_rig.py
@@ -2,7 +2,8 @@
"""Load Skeletal Meshes form FBX."""
import os
-from avalon import api, pipeline
+from avalon import pipeline
+from openpype.pipeline import get_representation_path
from openpype.hosts.unreal.api import plugin
from openpype.hosts.unreal.api import pipeline as unreal_pipeline
import unreal # noqa
@@ -124,7 +125,7 @@ class SkeletalMeshFBXLoader(plugin.Loader):
def update(self, container, representation):
name = container["asset_name"]
- source_path = api.get_representation_path(representation)
+ source_path = get_representation_path(representation)
destination_path = container["namespace"]
task = unreal.AssetImportTask()
diff --git a/openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py b/openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py
index c8a6964ffb..587fc83a77 100644
--- a/openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py
+++ b/openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py
@@ -2,7 +2,8 @@
"""Load Static meshes form FBX."""
import os
-from avalon import api, pipeline
+from avalon import pipeline
+from openpype.pipeline import get_representation_path
from openpype.hosts.unreal.api import plugin
from openpype.hosts.unreal.api import pipeline as unreal_pipeline
import unreal # noqa
@@ -118,7 +119,7 @@ class StaticMeshFBXLoader(plugin.Loader):
def update(self, container, representation):
name = container["asset_name"]
- source_path = api.get_representation_path(representation)
+ source_path = get_representation_path(representation)
destination_path = container["namespace"]
task = self.get_task(source_path, destination_path, name, True)
diff --git a/openpype/hosts/webpublisher/api/__init__.py b/openpype/hosts/webpublisher/api/__init__.py
index 6ce8a58fc2..dbeb628073 100644
--- a/openpype/hosts/webpublisher/api/__init__.py
+++ b/openpype/hosts/webpublisher/api/__init__.py
@@ -5,7 +5,6 @@ from avalon import api as avalon
from avalon import io
from pyblish import api as pyblish
import openpype.hosts.webpublisher
-from openpype.pipeline import LegacyCreator
log = logging.getLogger("openpype.hosts.webpublisher")
@@ -13,30 +12,19 @@ HOST_DIR = os.path.dirname(os.path.abspath(
openpype.hosts.webpublisher.__file__))
PLUGINS_DIR = os.path.join(HOST_DIR, "plugins")
PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish")
-LOAD_PATH = os.path.join(PLUGINS_DIR, "load")
-CREATE_PATH = os.path.join(PLUGINS_DIR, "create")
-
-
-def application_launch():
- pass
def install():
print("Installing Pype config...")
pyblish.register_plugin_path(PUBLISH_PATH)
- avalon.register_plugin_path(avalon.Loader, LOAD_PATH)
- avalon.register_plugin_path(LegacyCreator, CREATE_PATH)
log.info(PUBLISH_PATH)
io.install()
- avalon.on("application.launched", application_launch)
def uninstall():
pyblish.deregister_plugin_path(PUBLISH_PATH)
- avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH)
- avalon.deregister_plugin_path(LegacyCreator, CREATE_PATH)
# to have required methods for interface
diff --git a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py
index afd6f349db..65cef14703 100644
--- a/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py
+++ b/openpype/hosts/webpublisher/plugins/publish/collect_published_files.py
@@ -14,8 +14,12 @@ import math
from avalon import io
import pyblish.api
-from openpype.lib import prepare_template_data, get_asset, ffprobe_streams
-from openpype.lib.vendor_bin_utils import get_fps
+from openpype.lib import (
+ prepare_template_data,
+ get_asset,
+ get_ffprobe_streams,
+ convert_ffprobe_fps_value,
+)
from openpype.lib.plugin_tools import (
parse_json,
get_subset_name_with_asset_doc
@@ -265,7 +269,7 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin):
def _get_number_of_frames(self, file_url):
"""Return duration in frames"""
try:
- streams = ffprobe_streams(file_url, self.log)
+ streams = get_ffprobe_streams(file_url, self.log)
except Exception as exc:
raise AssertionError((
"FFprobe couldn't read information about input file: \"{}\"."
@@ -288,7 +292,9 @@ class CollectPublishedFiles(pyblish.api.ContextPlugin):
"nb_frames {} not convertible".format(nb_frames))
duration = stream.get("duration")
- frame_rate = get_fps(stream.get("r_frame_rate", '0/0'))
+ frame_rate = convert_ffprobe_fps_value(
+ stream.get("r_frame_rate", '0/0')
+ )
self.log.debug("duration:: {} frame_rate:: {}".format(
duration, frame_rate))
try:
diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py
index 34b217f690..b8502ae718 100644
--- a/openpype/lib/__init__.py
+++ b/openpype/lib/__init__.py
@@ -16,14 +16,19 @@ sys.path.insert(0, python_version_dir)
site.addsitedir(python_version_dir)
+from .events import (
+ emit_event,
+ register_event_callback
+)
+
from .vendor_bin_utils import (
find_executable,
get_vendor_bin_path,
get_oiio_tools_path,
get_ffmpeg_tool_path,
- ffprobe_streams,
is_oiio_supported
)
+
from .env_tools import (
env_value_to_bool,
get_paths_from_environ,
@@ -63,7 +68,10 @@ from .anatomy import (
Anatomy
)
-from .config import get_datetime_data
+from .config import (
+ get_datetime_data,
+ get_formatted_current_time
+)
from .python_module_tools import (
import_filepath,
@@ -81,7 +89,12 @@ from .profiles_filtering import (
from .transcoding import (
get_transcode_temp_directory,
should_convert_for_ffmpeg,
- convert_for_ffmpeg
+ convert_for_ffmpeg,
+ get_ffprobe_data,
+ get_ffprobe_streams,
+ get_ffmpeg_codec_args,
+ get_ffmpeg_format_args,
+ convert_ffprobe_fps_value,
)
from .avalon_context import (
CURRENT_DOC_SCHEMAS,
@@ -194,6 +207,9 @@ from .openpype_version import (
terminal = Terminal
__all__ = [
+ "emit_event",
+ "register_event_callback",
+
"find_executable",
"get_openpype_execute_args",
"get_pype_execute_args",
@@ -213,7 +229,6 @@ __all__ = [
"get_vendor_bin_path",
"get_oiio_tools_path",
"get_ffmpeg_tool_path",
- "ffprobe_streams",
"is_oiio_supported",
"import_filepath",
@@ -225,6 +240,11 @@ __all__ = [
"get_transcode_temp_directory",
"should_convert_for_ffmpeg",
"convert_for_ffmpeg",
+ "get_ffprobe_data",
+ "get_ffprobe_streams",
+ "get_ffmpeg_codec_args",
+ "get_ffmpeg_format_args",
+ "convert_ffprobe_fps_value",
"CURRENT_DOC_SCHEMAS",
"PROJECT_NAME_ALLOWED_SYMBOLS",
@@ -309,6 +329,7 @@ __all__ = [
"Anatomy",
"get_datetime_data",
+ "get_formatted_current_time",
"PypeLogger",
"get_default_components",
diff --git a/openpype/lib/abstract_collect_render.py b/openpype/lib/abstract_collect_render.py
index 3839aad45d..7c768e280c 100644
--- a/openpype/lib/abstract_collect_render.py
+++ b/openpype/lib/abstract_collect_render.py
@@ -26,7 +26,7 @@ class RenderInstance(object):
# metadata
version = attr.ib() # instance version
- time = attr.ib() # time of instance creation (avalon.api.time())
+ time = attr.ib() # time of instance creation (get_formatted_current_time)
source = attr.ib() # path to source scene file
label = attr.ib() # label to show in GUI
subset = attr.ib() # subset name
diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py
index 0bfd3f6de0..26beba41ee 100644
--- a/openpype/lib/avalon_context.py
+++ b/openpype/lib/avalon_context.py
@@ -15,6 +15,7 @@ from openpype.settings import (
)
from .anatomy import Anatomy
from .profiles_filtering import filter_profiles
+from .events import emit_event
# avalon module is not imported at the top
# - may not be in path at the time of pype.lib initialization
@@ -779,7 +780,6 @@ def update_current_task(task=None, asset=None, app=None, template_key=None):
"""
import avalon.api
- from avalon.pipeline import emit
changes = compute_session_changes(
avalon.api.Session,
@@ -799,7 +799,7 @@ def update_current_task(task=None, asset=None, app=None, template_key=None):
os.environ[key] = value
# Emit session change
- emit("taskChanged", changes.copy())
+ emit_event("taskChanged", changes.copy())
return changes
@@ -980,6 +980,8 @@ class BuildWorkfile:
...
}]
"""
+ from openpype.pipeline import discover_loader_plugins
+
# Get current asset name and entity
current_asset_name = avalon.io.Session["AVALON_ASSET"]
current_asset_entity = avalon.io.find_one({
@@ -996,7 +998,7 @@ class BuildWorkfile:
# Prepare available loaders
loaders_by_name = {}
- for loader in avalon.api.discover(avalon.api.Loader):
+ for loader in discover_loader_plugins():
loader_name = loader.__name__
if loader_name in loaders_by_name:
raise KeyError(
@@ -1390,6 +1392,11 @@ class BuildWorkfile:
Returns:
(list) Objects of loaded containers.
"""
+ from openpype.pipeline import (
+ IncompatibleLoaderError,
+ load_container,
+ )
+
loaded_containers = []
# Get subset id order from build presets.
@@ -1451,7 +1458,7 @@ class BuildWorkfile:
if not loader:
continue
try:
- container = avalon.api.load(
+ container = load_container(
loader,
repre["_id"],
name=subset_name
@@ -1460,7 +1467,7 @@ class BuildWorkfile:
is_loaded = True
except Exception as exc:
- if exc == avalon.pipeline.IncompatibleLoaderError:
+ if exc == IncompatibleLoaderError:
self.log.info((
"Loader `{}` is not compatible with"
" representation `{}`"
@@ -1594,11 +1601,13 @@ def get_creator_by_name(creator_name, case_sensitive=False):
Returns:
Creator: Return first matching plugin or `None`.
"""
+ from openpype.pipeline import LegacyCreator
+
# Lower input creator name if is not case sensitive
if not case_sensitive:
creator_name = creator_name.lower()
- for creator_plugin in avalon.api.discover(avalon.api.Creator):
+ for creator_plugin in avalon.api.discover(LegacyCreator):
_creator_name = creator_plugin.__name__
# Lower creator plugin name if is not case sensitive
diff --git a/openpype/lib/config.py b/openpype/lib/config.py
index ba394cfd56..57e8efa57d 100644
--- a/openpype/lib/config.py
+++ b/openpype/lib/config.py
@@ -74,3 +74,9 @@ def get_datetime_data(datetime_obj=None):
"S": str(int(seconds)),
"SS": str(seconds),
}
+
+
+def get_formatted_current_time():
+ return datetime.datetime.now().strftime(
+ "%Y%m%dT%H%M%SZ"
+ )
diff --git a/openpype/lib/events.py b/openpype/lib/events.py
new file mode 100644
index 0000000000..7bec6ee30d
--- /dev/null
+++ b/openpype/lib/events.py
@@ -0,0 +1,268 @@
+"""Events holding data about specific event."""
+import os
+import re
+import inspect
+import logging
+import weakref
+from uuid import uuid4
+try:
+ from weakref import WeakMethod
+except Exception:
+ from openpype.lib.python_2_comp import WeakMethod
+
+
+class EventCallback(object):
+ """Callback registered to a topic.
+
+ The callback function is registered to a topic. Topic is a string which
+ may contain '*' that will be handled as "any characters".
+
+ # Examples:
+ - "workfile.save" Callback will be triggered if the event topic is
+ exactly "workfile.save" .
+ - "workfile.*" Callback will be triggered an event topic starts with
+ "workfile." so "workfile.save" and "workfile.open"
+ will trigger the callback.
+ - "*" Callback will listen to all events.
+
+ Callback can be function or method. In both cases it should expect one
+ or none arguments. When 1 argument is expected then the processed 'Event'
+ object is passed in.
+
+ The registered callbacks don't keep function in memory so it is not
+ possible to store lambda function as callback.
+
+ Args:
+ topic(str): Topic which will be listened.
+ func(func): Callback to a topic.
+
+ Raises:
+ TypeError: When passed function is not a callable object.
+ """
+
+ def __init__(self, topic, func):
+ self._log = None
+ self._topic = topic
+ # Replace '*' with any character regex and escape rest of text
+ # - when callback is registered for '*' topic it will receive all
+ # events
+ # - it is possible to register to a partial topis 'my.event.*'
+ # - it will receive all matching event topics
+ # e.g. 'my.event.start' and 'my.event.end'
+ topic_regex_str = "^{}$".format(
+ ".+".join(
+ re.escape(part)
+ for part in topic.split("*")
+ )
+ )
+ topic_regex = re.compile(topic_regex_str)
+ self._topic_regex = topic_regex
+
+ # Convert callback into references
+ # - deleted functions won't cause crashes
+ if inspect.ismethod(func):
+ func_ref = WeakMethod(func)
+ elif callable(func):
+ func_ref = weakref.ref(func)
+ else:
+ raise TypeError((
+ "Registered callback is not callable. \"{}\""
+ ).format(str(func)))
+
+ # Collect additional data about function
+ # - name
+ # - path
+ # - if expect argument or not
+ func_name = func.__name__
+ func_path = os.path.abspath(inspect.getfile(func))
+ if hasattr(inspect, "signature"):
+ sig = inspect.signature(func)
+ expect_args = len(sig.parameters) > 0
+ else:
+ expect_args = len(inspect.getargspec(func)[0]) > 0
+
+ self._func_ref = func_ref
+ self._func_name = func_name
+ self._func_path = func_path
+ self._expect_args = expect_args
+ self._ref_valid = func_ref is not None
+ self._enabled = True
+
+ def __repr__(self):
+ return "< {} - {} > {}".format(
+ self.__class__.__name__, self._func_name, self._func_path
+ )
+
+ @property
+ def log(self):
+ if self._log is None:
+ self._log = logging.getLogger(self.__class__.__name__)
+ return self._log
+
+ @property
+ def is_ref_valid(self):
+ return self._ref_valid
+
+ def validate_ref(self):
+ if not self._ref_valid:
+ return
+
+ callback = self._func_ref()
+ if not callback:
+ self._ref_valid = False
+
+ @property
+ def enabled(self):
+ """Is callback enabled."""
+ return self._enabled
+
+ def set_enabled(self, enabled):
+ """Change if callback is enabled."""
+ self._enabled = enabled
+
+ def deregister(self):
+ """Calling this funcion will cause that callback will be removed."""
+ # Fake reference
+ self._ref_valid = False
+
+ def topic_matches(self, topic):
+ """Check if event topic matches callback's topic."""
+ return self._topic_regex.match(topic)
+
+ def process_event(self, event):
+ """Process event.
+
+ Args:
+ event(Event): Event that was triggered.
+ """
+
+ # Skip if callback is not enabled or has invalid reference
+ if not self._ref_valid or not self._enabled:
+ return
+
+ # Get reference
+ callback = self._func_ref()
+ # Check if reference is valid or callback's topic matches the event
+ if not callback:
+ # Change state if is invalid so the callback is removed
+ self._ref_valid = False
+
+ elif self.topic_matches(event.topic):
+ # Try execute callback
+ try:
+ if self._expect_args:
+ callback(event)
+ else:
+ callback()
+
+ except Exception:
+ self.log.warning(
+ "Failed to execute event callback {}".format(
+ str(repr(self))
+ ),
+ exc_info=True
+ )
+
+
+# Inherit from 'object' for Python 2 hosts
+class Event(object):
+ """Base event object.
+
+ Can be used for any event because is not specific. Only required argument
+ is topic which defines why event is happening and may be used for
+ filtering.
+
+ Arg:
+ topic (str): Identifier of event.
+ data (Any): Data specific for event. Dictionary is recommended.
+ source (str): Identifier of source.
+ """
+ _data = {}
+
+ def __init__(self, topic, data=None, source=None):
+ self._id = str(uuid4())
+ self._topic = topic
+ if data is None:
+ data = {}
+ self._data = data
+ self._source = source
+
+ def __getitem__(self, key):
+ return self._data[key]
+
+ def get(self, key, *args, **kwargs):
+ return self._data.get(key, *args, **kwargs)
+
+ @property
+ def id(self):
+ return self._id
+
+ @property
+ def source(self):
+ return self._source
+
+ @property
+ def data(self):
+ return self._data
+
+ @property
+ def topic(self):
+ return self._topic
+
+ def emit(self):
+ """Emit event and trigger callbacks."""
+ StoredCallbacks.emit_event(self)
+
+
+class StoredCallbacks:
+ _registered_callbacks = []
+
+ @classmethod
+ def add_callback(cls, topic, callback):
+ callback = EventCallback(topic, callback)
+ cls._registered_callbacks.append(callback)
+ return callback
+
+ @classmethod
+ def emit_event(cls, event):
+ invalid_callbacks = []
+ for callback in cls._registered_callbacks:
+ callback.process_event(event)
+ if not callback.is_ref_valid:
+ invalid_callbacks.append(callback)
+
+ for callback in invalid_callbacks:
+ cls._registered_callbacks.remove(callback)
+
+
+def register_event_callback(topic, callback):
+ """Add callback that will be executed on specific topic.
+
+ Args:
+ topic(str): Topic on which will callback be triggered.
+ callback(function): Callback that will be triggered when a topic
+ is triggered. Callback should expect none or 1 argument where
+ `Event` object is passed.
+
+ Returns:
+ EventCallback: Object wrapping the callback. It can be used to
+ enable/disable listening to a topic or remove the callback from
+ the topic completely.
+ """
+ return StoredCallbacks.add_callback(topic, callback)
+
+
+def emit_event(topic, data=None, source=None):
+ """Emit event with topic and data.
+
+ Arg:
+ topic(str): Event's topic.
+ data(dict): Event's additional data. Optional.
+ source(str): Who emitted the topic. Optional.
+
+ Returns:
+ Event: Object of event that was emitted.
+ """
+ event = Event(topic, data, source)
+ event.emit()
+ return event
diff --git a/openpype/lib/path_templates.py b/openpype/lib/path_templates.py
index 62bfdf774a..14e5fe59f8 100644
--- a/openpype/lib/path_templates.py
+++ b/openpype/lib/path_templates.py
@@ -187,6 +187,16 @@ class StringTemplate(object):
result.validate()
return result
+ @classmethod
+ def format_template(cls, template, data):
+ objected_template = cls(template)
+ return objected_template.format(data)
+
+ @classmethod
+ def format_strict_template(cls, template, data):
+ objected_template = cls(template)
+ return objected_template.format_strict(data)
+
@staticmethod
def find_optional_parts(parts):
new_parts = []
diff --git a/openpype/lib/plugin_tools.py b/openpype/lib/plugin_tools.py
index 19765a6f4a..f11ba56865 100644
--- a/openpype/lib/plugin_tools.py
+++ b/openpype/lib/plugin_tools.py
@@ -280,6 +280,7 @@ def set_plugin_attributes_from_settings(
project_name (str): Name of project for which settings will be loaded.
Value from environment `AVALON_PROJECT` is used if not entered.
"""
+ from openpype.pipeline import LegacyCreator, LoaderPlugin
# determine host application to use for finding presets
if host_name is None:
@@ -289,11 +290,11 @@ def set_plugin_attributes_from_settings(
project_name = os.environ.get("AVALON_PROJECT")
# map plugin superclass to preset json. Currently supported is load and
- # create (avalon.api.Loader and avalon.api.Creator)
+ # create (LoaderPlugin and LegacyCreator)
plugin_type = None
- if superclass.__name__.split(".")[-1] in ("Loader", "SubsetLoader"):
+ if superclass is LoaderPlugin or issubclass(superclass, LoaderPlugin):
plugin_type = "load"
- elif superclass.__name__.split(".")[-1] in ("Creator", "LegacyCreator"):
+ elif superclass is LegacyCreator or issubclass(superclass, LegacyCreator):
plugin_type = "create"
if not host_name or not project_name or plugin_type is None:
diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py
index 462745bcda..6bab6a8160 100644
--- a/openpype/lib/transcoding.py
+++ b/openpype/lib/transcoding.py
@@ -1,15 +1,18 @@
import os
import re
import logging
+import json
import collections
import tempfile
+import subprocess
import xml.etree.ElementTree
from .execute import run_subprocess
from .vendor_bin_utils import (
+ get_ffmpeg_tool_path,
get_oiio_tools_path,
- is_oiio_supported
+ is_oiio_supported,
)
# Max length of string that is supported by ffmpeg
@@ -483,3 +486,290 @@ def convert_for_ffmpeg(
logger.debug("Conversion command: {}".format(" ".join(oiio_cmd)))
run_subprocess(oiio_cmd, logger=logger)
+
+
+# FFMPEG functions
+def get_ffprobe_data(path_to_file, logger=None):
+ """Load data about entered filepath via ffprobe.
+
+ Args:
+ path_to_file (str): absolute path
+ logger (logging.Logger): injected logger, if empty new is created
+ """
+ if not logger:
+ logger = logging.getLogger(__name__)
+ logger.info(
+ "Getting information about input \"{}\".".format(path_to_file)
+ )
+ args = [
+ get_ffmpeg_tool_path("ffprobe"),
+ "-hide_banner",
+ "-loglevel", "fatal",
+ "-show_error",
+ "-show_format",
+ "-show_streams",
+ "-show_programs",
+ "-show_chapters",
+ "-show_private_data",
+ "-print_format", "json",
+ path_to_file
+ ]
+
+ logger.debug("FFprobe command: {}".format(
+ subprocess.list2cmdline(args)
+ ))
+ popen = subprocess.Popen(
+ args,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE
+ )
+
+ popen_stdout, popen_stderr = popen.communicate()
+ if popen_stdout:
+ logger.debug("FFprobe stdout:\n{}".format(
+ popen_stdout.decode("utf-8")
+ ))
+
+ if popen_stderr:
+ logger.warning("FFprobe stderr:\n{}".format(
+ popen_stderr.decode("utf-8")
+ ))
+
+ return json.loads(popen_stdout)
+
+
+def get_ffprobe_streams(path_to_file, logger=None):
+ """Load streams from entered filepath via ffprobe.
+
+ Args:
+ path_to_file (str): absolute path
+ logger (logging.Logger): injected logger, if empty new is created
+ """
+ return get_ffprobe_data(path_to_file, logger)["streams"]
+
+
+def get_ffmpeg_format_args(ffprobe_data, source_ffmpeg_cmd=None):
+ """Copy format from input metadata for output.
+
+ Args:
+ ffprobe_data(dict): Data received from ffprobe.
+ source_ffmpeg_cmd(str): Command that created input if available.
+ """
+ input_format = ffprobe_data.get("format") or {}
+ if input_format.get("format_name") == "mxf":
+ return _ffmpeg_mxf_format_args(ffprobe_data, source_ffmpeg_cmd)
+ return []
+
+
+def _ffmpeg_mxf_format_args(ffprobe_data, source_ffmpeg_cmd):
+ input_format = ffprobe_data["format"]
+ format_tags = input_format.get("tags") or {}
+ product_name = format_tags.get("product_name") or ""
+ output = []
+ if "opatom" in product_name.lower():
+ output.extend(["-f", "mxf_opatom"])
+ return output
+
+
+def get_ffmpeg_codec_args(ffprobe_data, source_ffmpeg_cmd=None, logger=None):
+ """Copy codec from input metadata for output.
+
+ Args:
+ ffprobe_data(dict): Data received from ffprobe.
+ source_ffmpeg_cmd(str): Command that created input if available.
+ """
+ if logger is None:
+ logger = logging.getLogger(__name__)
+
+ video_stream = None
+ no_audio_stream = None
+ for stream in ffprobe_data["streams"]:
+ codec_type = stream["codec_type"]
+ if codec_type == "video":
+ video_stream = stream
+ break
+ elif no_audio_stream is None and codec_type != "audio":
+ no_audio_stream = stream
+
+ if video_stream is None:
+ if no_audio_stream is None:
+ logger.warning(
+ "Couldn't find stream that is not an audio file."
+ )
+ return []
+ logger.info(
+ "Didn't find video stream. Using first non audio stream."
+ )
+ video_stream = no_audio_stream
+
+ codec_name = video_stream.get("codec_name")
+ # Codec "prores"
+ if codec_name == "prores":
+ return _ffmpeg_prores_codec_args(video_stream, source_ffmpeg_cmd)
+
+ # Codec "h264"
+ if codec_name == "h264":
+ return _ffmpeg_h264_codec_args(video_stream, source_ffmpeg_cmd)
+
+ # Coded DNxHD
+ if codec_name == "dnxhd":
+ return _ffmpeg_dnxhd_codec_args(video_stream, source_ffmpeg_cmd)
+
+ output = []
+ if codec_name:
+ output.extend(["-codec:v", codec_name])
+
+ bit_rate = video_stream.get("bit_rate")
+ if bit_rate:
+ output.extend(["-b:v", bit_rate])
+
+ pix_fmt = video_stream.get("pix_fmt")
+ if pix_fmt:
+ output.extend(["-pix_fmt", pix_fmt])
+
+ output.extend(["-g", "1"])
+
+ return output
+
+
+def _ffmpeg_prores_codec_args(stream_data, source_ffmpeg_cmd):
+ output = []
+
+ tags = stream_data.get("tags") or {}
+ encoder = tags.get("encoder") or ""
+ if encoder.endswith("prores_ks"):
+ codec_name = "prores_ks"
+
+ elif encoder.endswith("prores_aw"):
+ codec_name = "prores_aw"
+
+ else:
+ codec_name = "prores"
+
+ output.extend(["-codec:v", codec_name])
+
+ pix_fmt = stream_data.get("pix_fmt")
+ if pix_fmt:
+ output.extend(["-pix_fmt", pix_fmt])
+
+ # Rest of arguments is prores_kw specific
+ if codec_name == "prores_ks":
+ codec_tag_to_profile_map = {
+ "apco": "proxy",
+ "apcs": "lt",
+ "apcn": "standard",
+ "apch": "hq",
+ "ap4h": "4444",
+ "ap4x": "4444xq"
+ }
+ codec_tag_str = stream_data.get("codec_tag_string")
+ if codec_tag_str:
+ profile = codec_tag_to_profile_map.get(codec_tag_str)
+ if profile:
+ output.extend(["-profile:v", profile])
+
+ return output
+
+
+def _ffmpeg_h264_codec_args(stream_data, source_ffmpeg_cmd):
+ output = ["-codec:v", "h264"]
+
+ # Use arguments from source if are available source arguments
+ if source_ffmpeg_cmd:
+ copy_args = (
+ "-crf",
+ "-b:v", "-vb",
+ "-minrate", "-minrate:",
+ "-maxrate", "-maxrate:",
+ "-bufsize", "-bufsize:"
+ )
+ args = source_ffmpeg_cmd.split(" ")
+ for idx, arg in enumerate(args):
+ if arg in copy_args:
+ output.extend([arg, args[idx + 1]])
+
+ pix_fmt = stream_data.get("pix_fmt")
+ if pix_fmt:
+ output.extend(["-pix_fmt", pix_fmt])
+
+ output.extend(["-intra"])
+ output.extend(["-g", "1"])
+
+ return output
+
+
+def _ffmpeg_dnxhd_codec_args(stream_data, source_ffmpeg_cmd):
+ output = ["-codec:v", "dnxhd"]
+
+ # Use source profile (profiles in metadata are not usable in args directly)
+ profile = stream_data.get("profile") or ""
+ # Lower profile and replace space with underscore
+ cleaned_profile = profile.lower().replace(" ", "_")
+
+ # TODO validate this statement
+ # Looks like using 'dnxhd' profile must have set bit rate and in that case
+ # should be used bitrate from source.
+ # - related attributes 'bit_rate_defined', 'bit_rate_must_be_defined'
+ bit_rate_must_be_defined = True
+ dnx_profiles = {
+ "dnxhd",
+ "dnxhr_lb",
+ "dnxhr_sq",
+ "dnxhr_hq",
+ "dnxhr_hqx",
+ "dnxhr_444"
+ }
+ if cleaned_profile in dnx_profiles:
+ if cleaned_profile != "dnxhd":
+ bit_rate_must_be_defined = False
+ output.extend(["-profile:v", cleaned_profile])
+
+ pix_fmt = stream_data.get("pix_fmt")
+ if pix_fmt:
+ output.extend(["-pix_fmt", pix_fmt])
+
+ # Use arguments from source if are available source arguments
+ bit_rate_defined = False
+ if source_ffmpeg_cmd:
+ # Define bitrate arguments
+ bit_rate_args = ("-b:v", "-vb",)
+ # Seprate the two variables in case something else should be copied
+ # from source command
+ copy_args = []
+ copy_args.extend(bit_rate_args)
+
+ args = source_ffmpeg_cmd.split(" ")
+ for idx, arg in enumerate(args):
+ if arg in copy_args:
+ if arg in bit_rate_args:
+ bit_rate_defined = True
+ output.extend([arg, args[idx + 1]])
+
+ # Add bitrate if needed
+ if bit_rate_must_be_defined and not bit_rate_defined:
+ src_bit_rate = stream_data.get("bit_rate")
+ if src_bit_rate:
+ output.extend(["-b:v", src_bit_rate])
+
+ output.extend(["-g", "1"])
+ return output
+
+
+def convert_ffprobe_fps_value(str_value):
+ """Returns (str) value of fps from ffprobe frame format (120/1)"""
+ if str_value == "0/0":
+ print("WARNING: Source has \"r_frame_rate\" value set to \"0/0\".")
+ return "Unknown"
+
+ items = str_value.split("/")
+ if len(items) == 1:
+ fps = float(items[0])
+
+ elif len(items) == 2:
+ fps = float(items[0]) / float(items[1])
+
+ # Check if fps is integer or float number
+ if int(fps) == fps:
+ fps = int(fps)
+
+ return str(fps)
diff --git a/openpype/lib/vendor_bin_utils.py b/openpype/lib/vendor_bin_utils.py
index 4b11f1c046..23e28ea304 100644
--- a/openpype/lib/vendor_bin_utils.py
+++ b/openpype/lib/vendor_bin_utils.py
@@ -1,8 +1,6 @@
import os
import logging
-import json
import platform
-import subprocess
log = logging.getLogger("Vendor utils")
@@ -138,56 +136,6 @@ def get_ffmpeg_tool_path(tool="ffmpeg"):
return find_executable(os.path.join(ffmpeg_dir, tool))
-def ffprobe_streams(path_to_file, logger=None):
- """Load streams from entered filepath via ffprobe.
-
- Args:
- path_to_file (str): absolute path
- logger (logging.getLogger): injected logger, if empty new is created
-
- """
- if not logger:
- logger = log
- logger.info(
- "Getting information about input \"{}\".".format(path_to_file)
- )
- args = [
- get_ffmpeg_tool_path("ffprobe"),
- "-hide_banner",
- "-loglevel", "fatal",
- "-show_error",
- "-show_format",
- "-show_streams",
- "-show_programs",
- "-show_chapters",
- "-show_private_data",
- "-print_format", "json",
- path_to_file
- ]
-
- logger.debug("FFprobe command: {}".format(
- subprocess.list2cmdline(args)
- ))
- popen = subprocess.Popen(
- args,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE
- )
-
- popen_stdout, popen_stderr = popen.communicate()
- if popen_stdout:
- logger.debug("FFprobe stdout:\n{}".format(
- popen_stdout.decode("utf-8")
- ))
-
- if popen_stderr:
- logger.warning("FFprobe stderr:\n{}".format(
- popen_stderr.decode("utf-8")
- ))
-
- return json.loads(popen_stdout)["streams"]
-
-
def is_oiio_supported():
"""Checks if oiiotool is configured for this platform.
@@ -204,23 +152,3 @@ def is_oiio_supported():
))
return False
return True
-
-
-def get_fps(str_value):
- """Returns (str) value of fps from ffprobe frame format (120/1)"""
- if str_value == "0/0":
- print("WARNING: Source has \"r_frame_rate\" value set to \"0/0\".")
- return "Unknown"
-
- items = str_value.split("/")
- if len(items) == 1:
- fps = float(items[0])
-
- elif len(items) == 2:
- fps = float(items[0]) / float(items[1])
-
- # Check if fps is integer or float number
- if int(fps) == fps:
- fps = int(fps)
-
- return str(fps)
diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py
index 1de1c37575..19d504b6c9 100644
--- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py
+++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py
@@ -13,6 +13,8 @@ from avalon import api, io
import pyblish.api
+from openpype.pipeline import get_representation_path
+
def get_resources(version, extension=None):
"""Get the files from the specific version."""
@@ -23,7 +25,7 @@ def get_resources(version, extension=None):
representation = io.find_one(query)
assert representation, "This is a bug"
- directory = api.get_representation_path(representation)
+ directory = get_representation_path(representation)
print("Source: ", directory)
resources = sorted(
[
diff --git a/openpype/modules/ftrack/event_handlers_user/action_rv.py b/openpype/modules/ftrack/event_handlers_user/action_rv.py
index 71d790f7e7..bdb0eaf250 100644
--- a/openpype/modules/ftrack/event_handlers_user/action_rv.py
+++ b/openpype/modules/ftrack/event_handlers_user/action_rv.py
@@ -3,9 +3,10 @@ import subprocess
import traceback
import json
-from openpype_modules.ftrack.lib import BaseAction, statics_icon
import ftrack_api
from avalon import io, api
+from openpype.pipeline import get_representation_path
+from openpype_modules.ftrack.lib import BaseAction, statics_icon
class RVAction(BaseAction):
@@ -307,7 +308,7 @@ class RVAction(BaseAction):
"name": "preview"
}
)
- paths.append(api.get_representation_path(representation))
+ paths.append(get_representation_path(representation))
return paths
diff --git a/openpype/pipeline/__init__.py b/openpype/pipeline/__init__.py
index 7147e56dd2..26970e4edc 100644
--- a/openpype/pipeline/__init__.py
+++ b/openpype/pipeline/__init__.py
@@ -12,6 +12,28 @@ from .create import (
legacy_create,
)
+from .load import (
+ HeroVersionType,
+ IncompatibleLoaderError,
+ LoaderPlugin,
+ SubsetLoaderPlugin,
+
+ discover_loader_plugins,
+ register_loader_plugin,
+ deregister_loader_plugin_path,
+ register_loader_plugin_path,
+ deregister_loader_plugin,
+
+ load_container,
+ remove_container,
+ update_container,
+ switch_container,
+
+ loaders_from_representation,
+ get_representation_path,
+ get_repres_contexts,
+)
+
from .publish import (
PublishValidationError,
PublishXmlValidationError,
@@ -23,6 +45,7 @@ from .publish import (
__all__ = (
"attribute_definitions",
+ # --- Create ---
"BaseCreator",
"Creator",
"AutoCreator",
@@ -30,10 +53,32 @@ __all__ = (
"CreatorError",
- # Legacy creation
+ # - legacy creation
"LegacyCreator",
"legacy_create",
+ # --- Load ---
+ "HeroVersionType",
+ "IncompatibleLoaderError",
+ "LoaderPlugin",
+ "SubsetLoaderPlugin",
+
+ "discover_loader_plugins",
+ "register_loader_plugin",
+ "deregister_loader_plugin_path",
+ "register_loader_plugin_path",
+ "deregister_loader_plugin",
+
+ "load_container",
+ "remove_container",
+ "update_container",
+ "switch_container",
+
+ "loaders_from_representation",
+ "get_representation_path",
+ "get_repres_contexts",
+
+ # --- Publish ---
"PublishValidationError",
"PublishXmlValidationError",
"KnownPublishError",
diff --git a/openpype/pipeline/create/legacy_create.py b/openpype/pipeline/create/legacy_create.py
index d05cdff689..cf6629047e 100644
--- a/openpype/pipeline/create/legacy_create.py
+++ b/openpype/pipeline/create/legacy_create.py
@@ -21,6 +21,7 @@ class LegacyCreator(object):
dynamic_subset_keys = []
log = logging.getLogger("LegacyCreator")
+ log.propagate = True
def __init__(self, name, asset, options=None, data=None):
self.name = name # For backwards compatibility
diff --git a/openpype/pipeline/lib/__init__.py b/openpype/pipeline/lib/__init__.py
index ed38889c66..f762c4205d 100644
--- a/openpype/pipeline/lib/__init__.py
+++ b/openpype/pipeline/lib/__init__.py
@@ -1,8 +1,3 @@
-from .events import (
- BaseEvent,
- BeforeWorkfileSave
-)
-
from .attribute_definitions import (
AbtractAttrDef,
@@ -20,9 +15,6 @@ from .attribute_definitions import (
__all__ = (
- "BaseEvent",
- "BeforeWorkfileSave",
-
"AbtractAttrDef",
"UIDef",
diff --git a/openpype/pipeline/lib/events.py b/openpype/pipeline/lib/events.py
deleted file mode 100644
index 05dea20e8c..0000000000
--- a/openpype/pipeline/lib/events.py
+++ /dev/null
@@ -1,51 +0,0 @@
-"""Events holding data about specific event."""
-
-
-# Inherit from 'object' for Python 2 hosts
-class BaseEvent(object):
- """Base event object.
-
- Can be used to anything because data are not much specific. Only required
- argument is topic which defines why event is happening and may be used for
- filtering.
-
- Arg:
- topic (str): Identifier of event.
- data (Any): Data specific for event. Dictionary is recommended.
- """
- _data = {}
-
- def __init__(self, topic, data=None):
- self._topic = topic
- if data is None:
- data = {}
- self._data = data
-
- @property
- def data(self):
- return self._data
-
- @property
- def topic(self):
- return self._topic
-
- @classmethod
- def emit(cls, *args, **kwargs):
- """Create object of event and emit.
-
- Args:
- Same args as '__init__' expects which may be class specific.
- """
- from avalon import pipeline
-
- obj = cls(*args, **kwargs)
- pipeline.emit(obj.topic, [obj])
- return obj
-
-
-class BeforeWorkfileSave(BaseEvent):
- """Before workfile changes event data."""
- def __init__(self, filename, workdir):
- super(BeforeWorkfileSave, self).__init__("before.workfile.save")
- self.filename = filename
- self.workdir_path = workdir
diff --git a/openpype/pipeline/load/__init__.py b/openpype/pipeline/load/__init__.py
new file mode 100644
index 0000000000..6e7612d4c1
--- /dev/null
+++ b/openpype/pipeline/load/__init__.py
@@ -0,0 +1,78 @@
+from .utils import (
+ HeroVersionType,
+ IncompatibleLoaderError,
+
+ get_repres_contexts,
+ get_subset_contexts,
+ get_representation_context,
+
+ load_with_repre_context,
+ load_with_subset_context,
+ load_with_subset_contexts,
+
+ load_container,
+ remove_container,
+ update_container,
+ switch_container,
+
+ get_loader_identifier,
+
+ get_representation_path_from_context,
+ get_representation_path,
+
+ is_compatible_loader,
+
+ loaders_from_repre_context,
+ loaders_from_representation,
+)
+
+from .plugins import (
+ LoaderPlugin,
+ SubsetLoaderPlugin,
+
+ discover_loader_plugins,
+ register_loader_plugin,
+ deregister_loader_plugin_path,
+ register_loader_plugin_path,
+ deregister_loader_plugin,
+)
+
+
+__all__ = (
+ # utils.py
+ "HeroVersionType",
+ "IncompatibleLoaderError",
+
+ "get_repres_contexts",
+ "get_subset_contexts",
+ "get_representation_context",
+
+ "load_with_repre_context",
+ "load_with_subset_context",
+ "load_with_subset_contexts",
+
+ "load_container",
+ "remove_container",
+ "update_container",
+ "switch_container",
+
+ "get_loader_identifier",
+
+ "get_representation_path_from_context",
+ "get_representation_path",
+
+ "is_compatible_loader",
+
+ "loaders_from_repre_context",
+ "loaders_from_representation",
+
+ # plugins.py
+ "LoaderPlugin",
+ "SubsetLoaderPlugin",
+
+ "discover_loader_plugins",
+ "register_loader_plugin",
+ "deregister_loader_plugin_path",
+ "register_loader_plugin_path",
+ "deregister_loader_plugin",
+)
diff --git a/openpype/pipeline/load/plugins.py b/openpype/pipeline/load/plugins.py
new file mode 100644
index 0000000000..601ad3b258
--- /dev/null
+++ b/openpype/pipeline/load/plugins.py
@@ -0,0 +1,130 @@
+import logging
+
+from .utils import get_representation_path_from_context
+
+
+class LoaderPlugin(list):
+ """Load representation into host application
+
+ Arguments:
+ context (dict): avalon-core:context-1.0
+ name (str, optional): Use pre-defined name
+ namespace (str, optional): Use pre-defined namespace
+
+ .. versionadded:: 4.0
+ This class was introduced
+
+ """
+
+ families = list()
+ representations = list()
+ order = 0
+ is_multiple_contexts_compatible = False
+
+ options = []
+
+ log = logging.getLogger("SubsetLoader")
+ log.propagate = True
+
+ def __init__(self, context):
+ self.fname = self.filepath_from_context(context)
+
+ @classmethod
+ def get_representations(cls):
+ return cls.representations
+
+ def filepath_from_context(self, context):
+ return get_representation_path_from_context(context)
+
+ def load(self, context, name=None, namespace=None, options=None):
+ """Load asset via database
+
+ Arguments:
+ context (dict): Full parenthood of representation to load
+ name (str, optional): Use pre-defined name
+ namespace (str, optional): Use pre-defined namespace
+ options (dict, optional): Additional settings dictionary
+
+ """
+ raise NotImplementedError("Loader.load() must be "
+ "implemented by subclass")
+
+ def update(self, container, representation):
+ """Update `container` to `representation`
+
+ Arguments:
+ container (avalon-core:container-1.0): Container to update,
+ from `host.ls()`.
+ representation (dict): Update the container to this representation.
+
+ """
+ raise NotImplementedError("Loader.update() must be "
+ "implemented by subclass")
+
+ def remove(self, container):
+ """Remove a container
+
+ Arguments:
+ container (avalon-core:container-1.0): Container to remove,
+ from `host.ls()`.
+
+ Returns:
+ bool: Whether the container was deleted
+
+ """
+
+ raise NotImplementedError("Loader.remove() must be "
+ "implemented by subclass")
+
+ @classmethod
+ def get_options(cls, contexts):
+ """
+ Returns static (cls) options or could collect from 'contexts'.
+
+ Args:
+ contexts (list): of repre or subset contexts
+ Returns:
+ (list)
+ """
+ return cls.options or []
+
+
+class SubsetLoaderPlugin(LoaderPlugin):
+ """Load subset into host application
+ Arguments:
+ context (dict): avalon-core:context-1.0
+ name (str, optional): Use pre-defined name
+ namespace (str, optional): Use pre-defined namespace
+ """
+
+ def __init__(self, context):
+ pass
+
+
+def discover_loader_plugins():
+ import avalon.api
+
+ return avalon.api.discover(LoaderPlugin)
+
+
+def register_loader_plugin(plugin):
+ import avalon.api
+
+ return avalon.api.register_plugin(LoaderPlugin, plugin)
+
+
+def deregister_loader_plugin_path(path):
+ import avalon.api
+
+ avalon.api.deregister_plugin_path(LoaderPlugin, path)
+
+
+def register_loader_plugin_path(path):
+ import avalon.api
+
+ return avalon.api.register_plugin_path(LoaderPlugin, path)
+
+
+def deregister_loader_plugin(plugin):
+ import avalon.api
+ avalon.api.deregister_plugin(LoaderPlugin, plugin)
diff --git a/openpype/pipeline/load/utils.py b/openpype/pipeline/load/utils.py
new file mode 100644
index 0000000000..ae47cb9ce9
--- /dev/null
+++ b/openpype/pipeline/load/utils.py
@@ -0,0 +1,706 @@
+import os
+import platform
+import copy
+import getpass
+import logging
+import inspect
+import numbers
+
+import six
+
+from avalon import io, schema
+from avalon.api import Session, registered_root
+
+from openpype.lib import Anatomy
+
+log = logging.getLogger(__name__)
+
+
+class HeroVersionType(object):
+ def __init__(self, version):
+ assert isinstance(version, numbers.Integral), (
+ "Version is not an integer. \"{}\" {}".format(
+ version, str(type(version))
+ )
+ )
+ self.version = version
+
+ def __str__(self):
+ return str(self.version)
+
+ def __int__(self):
+ return int(self.version)
+
+ def __format__(self, format_spec):
+ return self.version.__format__(format_spec)
+
+
+class IncompatibleLoaderError(ValueError):
+ """Error when Loader is incompatible with a representation."""
+ pass
+
+
+def get_repres_contexts(representation_ids, dbcon=None):
+ """Return parenthood context for representation.
+
+ Args:
+ representation_ids (list): The representation ids.
+ dbcon (AvalonMongoDB): Mongo connection object. `avalon.io` used when
+ not entered.
+
+ Returns:
+ dict: The full representation context by representation id.
+ keys are repre_id, value is dictionary with full:
+ asset_doc
+ version_doc
+ subset_doc
+ repre_doc
+
+ """
+ if not dbcon:
+ dbcon = io
+
+ contexts = {}
+ if not representation_ids:
+ return contexts
+
+ _representation_ids = []
+ for repre_id in representation_ids:
+ if isinstance(repre_id, six.string_types):
+ repre_id = io.ObjectId(repre_id)
+ _representation_ids.append(repre_id)
+
+ repre_docs = dbcon.find({
+ "type": "representation",
+ "_id": {"$in": _representation_ids}
+ })
+ repre_docs_by_id = {}
+ version_ids = set()
+ for repre_doc in repre_docs:
+ version_ids.add(repre_doc["parent"])
+ repre_docs_by_id[repre_doc["_id"]] = repre_doc
+
+ version_docs = dbcon.find({
+ "type": {"$in": ["version", "hero_version"]},
+ "_id": {"$in": list(version_ids)}
+ })
+
+ version_docs_by_id = {}
+ hero_version_docs = []
+ versions_for_hero = set()
+ subset_ids = set()
+ for version_doc in version_docs:
+ if version_doc["type"] == "hero_version":
+ hero_version_docs.append(version_doc)
+ versions_for_hero.add(version_doc["version_id"])
+ version_docs_by_id[version_doc["_id"]] = version_doc
+ subset_ids.add(version_doc["parent"])
+
+ if versions_for_hero:
+ _version_docs = dbcon.find({
+ "type": "version",
+ "_id": {"$in": list(versions_for_hero)}
+ })
+ _version_data_by_id = {
+ version_doc["_id"]: version_doc["data"]
+ for version_doc in _version_docs
+ }
+
+ for hero_version_doc in hero_version_docs:
+ hero_version_id = hero_version_doc["_id"]
+ version_id = hero_version_doc["version_id"]
+ version_data = copy.deepcopy(_version_data_by_id[version_id])
+ version_docs_by_id[hero_version_id]["data"] = version_data
+
+ subset_docs = dbcon.find({
+ "type": "subset",
+ "_id": {"$in": list(subset_ids)}
+ })
+ subset_docs_by_id = {}
+ asset_ids = set()
+ for subset_doc in subset_docs:
+ subset_docs_by_id[subset_doc["_id"]] = subset_doc
+ asset_ids.add(subset_doc["parent"])
+
+ asset_docs = dbcon.find({
+ "type": "asset",
+ "_id": {"$in": list(asset_ids)}
+ })
+ asset_docs_by_id = {
+ asset_doc["_id"]: asset_doc
+ for asset_doc in asset_docs
+ }
+
+ project_doc = dbcon.find_one({"type": "project"})
+
+ for repre_id, repre_doc in repre_docs_by_id.items():
+ version_doc = version_docs_by_id[repre_doc["parent"]]
+ subset_doc = subset_docs_by_id[version_doc["parent"]]
+ asset_doc = asset_docs_by_id[subset_doc["parent"]]
+ context = {
+ "project": {
+ "name": project_doc["name"],
+ "code": project_doc["data"].get("code")
+ },
+ "asset": asset_doc,
+ "subset": subset_doc,
+ "version": version_doc,
+ "representation": repre_doc,
+ }
+ contexts[repre_id] = context
+
+ return contexts
+
+
+def get_subset_contexts(subset_ids, dbcon=None):
+ """Return parenthood context for subset.
+
+ Provides context on subset granularity - less detail than
+ 'get_repre_contexts'.
+ Args:
+ subset_ids (list): The subset ids.
+ dbcon (AvalonMongoDB): Mongo connection object. `avalon.io` used when
+ not entered.
+ Returns:
+ dict: The full representation context by representation id.
+ """
+ if not dbcon:
+ dbcon = io
+
+ contexts = {}
+ if not subset_ids:
+ return contexts
+
+ _subset_ids = set()
+ for subset_id in subset_ids:
+ if isinstance(subset_id, six.string_types):
+ subset_id = io.ObjectId(subset_id)
+ _subset_ids.add(subset_id)
+
+ subset_docs = dbcon.find({
+ "type": "subset",
+ "_id": {"$in": list(_subset_ids)}
+ })
+ subset_docs_by_id = {}
+ asset_ids = set()
+ for subset_doc in subset_docs:
+ subset_docs_by_id[subset_doc["_id"]] = subset_doc
+ asset_ids.add(subset_doc["parent"])
+
+ asset_docs = dbcon.find({
+ "type": "asset",
+ "_id": {"$in": list(asset_ids)}
+ })
+ asset_docs_by_id = {
+ asset_doc["_id"]: asset_doc
+ for asset_doc in asset_docs
+ }
+
+ project_doc = dbcon.find_one({"type": "project"})
+
+ for subset_id, subset_doc in subset_docs_by_id.items():
+ asset_doc = asset_docs_by_id[subset_doc["parent"]]
+ context = {
+ "project": {
+ "name": project_doc["name"],
+ "code": project_doc["data"].get("code")
+ },
+ "asset": asset_doc,
+ "subset": subset_doc
+ }
+ contexts[subset_id] = context
+
+ return contexts
+
+
+def get_representation_context(representation):
+ """Return parenthood context for representation.
+
+ Args:
+ representation (str or io.ObjectId or dict): The representation id
+ or full representation as returned by the database.
+
+ Returns:
+ dict: The full representation context.
+
+ """
+
+ assert representation is not None, "This is a bug"
+
+ if isinstance(representation, (six.string_types, io.ObjectId)):
+ representation = io.find_one(
+ {"_id": io.ObjectId(str(representation))})
+
+ version, subset, asset, project = io.parenthood(representation)
+
+ assert all([representation, version, subset, asset, project]), (
+ "This is a bug"
+ )
+
+ context = {
+ "project": {
+ "name": project["name"],
+ "code": project["data"].get("code", '')
+ },
+ "asset": asset,
+ "subset": subset,
+ "version": version,
+ "representation": representation,
+ }
+
+ return context
+
+
+def load_with_repre_context(
+ Loader, repre_context, namespace=None, name=None, options=None, **kwargs
+):
+
+ # Ensure the Loader is compatible for the representation
+ if not is_compatible_loader(Loader, repre_context):
+ raise IncompatibleLoaderError(
+ "Loader {} is incompatible with {}".format(
+ Loader.__name__, repre_context["subset"]["name"]
+ )
+ )
+
+ # Ensure options is a dictionary when no explicit options provided
+ if options is None:
+ options = kwargs.get("data", dict()) # "data" for backward compat
+
+ assert isinstance(options, dict), "Options must be a dictionary"
+
+ # Fallback to subset when name is None
+ if name is None:
+ name = repre_context["subset"]["name"]
+
+ log.info(
+ "Running '%s' on '%s'" % (
+ Loader.__name__, repre_context["asset"]["name"]
+ )
+ )
+
+ loader = Loader(repre_context)
+ return loader.load(repre_context, name, namespace, options)
+
+
+def load_with_subset_context(
+ Loader, subset_context, namespace=None, name=None, options=None, **kwargs
+):
+
+ # Ensure options is a dictionary when no explicit options provided
+ if options is None:
+ options = kwargs.get("data", dict()) # "data" for backward compat
+
+ assert isinstance(options, dict), "Options must be a dictionary"
+
+ # Fallback to subset when name is None
+ if name is None:
+ name = subset_context["subset"]["name"]
+
+ log.info(
+ "Running '%s' on '%s'" % (
+ Loader.__name__, subset_context["asset"]["name"]
+ )
+ )
+
+ loader = Loader(subset_context)
+ return loader.load(subset_context, name, namespace, options)
+
+
+def load_with_subset_contexts(
+ Loader, subset_contexts, namespace=None, name=None, options=None, **kwargs
+):
+
+ # Ensure options is a dictionary when no explicit options provided
+ if options is None:
+ options = kwargs.get("data", dict()) # "data" for backward compat
+
+ assert isinstance(options, dict), "Options must be a dictionary"
+
+ # Fallback to subset when name is None
+ joined_subset_names = " | ".join(
+ context["subset"]["name"]
+ for context in subset_contexts
+ )
+ if name is None:
+ name = joined_subset_names
+
+ log.info(
+ "Running '{}' on '{}'".format(Loader.__name__, joined_subset_names)
+ )
+
+ loader = Loader(subset_contexts)
+ return loader.load(subset_contexts, name, namespace, options)
+
+
+def load_container(
+ Loader, representation, namespace=None, name=None, options=None, **kwargs
+):
+ """Use Loader to load a representation.
+
+ Args:
+ Loader (Loader): The loader class to trigger.
+ representation (str or io.ObjectId or dict): The representation id
+ or full representation as returned by the database.
+ namespace (str, Optional): The namespace to assign. Defaults to None.
+ name (str, Optional): The name to assign. Defaults to subset name.
+ options (dict, Optional): Additional options to pass on to the loader.
+
+ Returns:
+ The return of the `loader.load()` method.
+
+ Raises:
+ IncompatibleLoaderError: When the loader is not compatible with
+ the representation.
+
+ """
+
+ context = get_representation_context(representation)
+ return load_with_repre_context(
+ Loader,
+ context,
+ namespace=namespace,
+ name=name,
+ options=options,
+ **kwargs
+ )
+
+
+def get_loader_identifier(loader):
+ """Loader identifier from loader plugin or object.
+
+ Identifier should be stored to container for future management.
+ """
+ if not inspect.isclass(loader):
+ loader = loader.__class__
+ return loader.__name__
+
+
+def _get_container_loader(container):
+ """Return the Loader corresponding to the container"""
+ from .plugins import discover_loader_plugins
+
+ loader = container["loader"]
+ for Plugin in discover_loader_plugins():
+ # TODO: Ensure the loader is valid
+ if get_loader_identifier(Plugin) == loader:
+ return Plugin
+ return None
+
+
+def remove_container(container):
+ """Remove a container"""
+
+ Loader = _get_container_loader(container)
+ if not Loader:
+ raise RuntimeError("Can't remove container. See log for details.")
+
+ loader = Loader(get_representation_context(container["representation"]))
+ return loader.remove(container)
+
+
+def update_container(container, version=-1):
+ """Update a container"""
+
+ # Compute the different version from 'representation'
+ current_representation = io.find_one({
+ "_id": io.ObjectId(container["representation"])
+ })
+
+ assert current_representation is not None, "This is a bug"
+
+ current_version, subset, asset, project = io.parenthood(
+ current_representation)
+
+ if version == -1:
+ new_version = io.find_one({
+ "type": "version",
+ "parent": subset["_id"]
+ }, sort=[("name", -1)])
+ else:
+ if isinstance(version, HeroVersionType):
+ version_query = {
+ "parent": subset["_id"],
+ "type": "hero_version"
+ }
+ else:
+ version_query = {
+ "parent": subset["_id"],
+ "type": "version",
+ "name": version
+ }
+ new_version = io.find_one(version_query)
+
+ assert new_version is not None, "This is a bug"
+
+ new_representation = io.find_one({
+ "type": "representation",
+ "parent": new_version["_id"],
+ "name": current_representation["name"]
+ })
+
+ assert new_representation is not None, "Representation wasn't found"
+
+ path = get_representation_path(new_representation)
+ assert os.path.exists(path), "Path {} doesn't exist".format(path)
+
+ # Run update on the Loader for this container
+ Loader = _get_container_loader(container)
+ if not Loader:
+ raise RuntimeError("Can't update container. See log for details.")
+
+ loader = Loader(get_representation_context(container["representation"]))
+ return loader.update(container, new_representation)
+
+
+def switch_container(container, representation, loader_plugin=None):
+ """Switch a container to representation
+
+ Args:
+ container (dict): container information
+ representation (dict): representation data from document
+
+ Returns:
+ function call
+ """
+
+ # Get the Loader for this container
+ if loader_plugin is None:
+ loader_plugin = _get_container_loader(container)
+
+ if not loader_plugin:
+ raise RuntimeError("Can't switch container. See log for details.")
+
+ if not hasattr(loader_plugin, "switch"):
+ # Backwards compatibility (classes without switch support
+ # might be better to just have "switch" raise NotImplementedError
+ # on the base class of Loader\
+ raise RuntimeError("Loader '{}' does not support 'switch'".format(
+ loader_plugin.label
+ ))
+
+ # Get the new representation to switch to
+ new_representation = io.find_one({
+ "type": "representation",
+ "_id": representation["_id"],
+ })
+
+ new_context = get_representation_context(new_representation)
+ if not is_compatible_loader(loader_plugin, new_context):
+ raise AssertionError("Must be compatible Loader")
+
+ loader = loader_plugin(new_context)
+
+ return loader.switch(container, new_representation)
+
+
+def get_representation_path_from_context(context):
+ """Preparation wrapper using only context as a argument"""
+ representation = context['representation']
+ project_doc = context.get("project")
+ root = None
+ session_project = Session.get("AVALON_PROJECT")
+ if project_doc and project_doc["name"] != session_project:
+ anatomy = Anatomy(project_doc["name"])
+ root = anatomy.roots_obj
+
+ return get_representation_path(representation, root)
+
+
+def get_representation_path(representation, root=None, dbcon=None):
+ """Get filename from representation document
+
+ There are three ways of getting the path from representation which are
+ tried in following sequence until successful.
+ 1. Get template from representation['data']['template'] and data from
+ representation['context']. Then format template with the data.
+ 2. Get template from project['config'] and format it with default data set
+ 3. Get representation['data']['path'] and use it directly
+
+ Args:
+ representation(dict): representation document from the database
+
+ Returns:
+ str: fullpath of the representation
+
+ """
+
+ from openpype.lib import StringTemplate
+
+ if dbcon is None:
+ dbcon = io
+
+ if root is None:
+ root = registered_root()
+
+ def path_from_represenation():
+ try:
+ template = representation["data"]["template"]
+ except KeyError:
+ return None
+
+ try:
+ context = representation["context"]
+ context["root"] = root
+ template_obj = StringTemplate(template)
+ path = str(template_obj.format(context))
+ # Force replacing backslashes with forward slashed if not on
+ # windows
+ if platform.system().lower() != "windows":
+ path = path.replace("\\", "/")
+ except KeyError:
+ # Template references unavailable data
+ return None
+
+ if not path:
+ return path
+
+ normalized_path = os.path.normpath(path)
+ if os.path.exists(normalized_path):
+ return normalized_path
+ return path
+
+ def path_from_config():
+ try:
+ version_, subset, asset, project = dbcon.parenthood(representation)
+ except ValueError:
+ log.debug(
+ "Representation %s wasn't found in database, "
+ "like a bug" % representation["name"]
+ )
+ return None
+
+ try:
+ template = project["config"]["template"]["publish"]
+ except KeyError:
+ log.debug(
+ "No template in project %s, "
+ "likely a bug" % project["name"]
+ )
+ return None
+
+ # default list() in get would not discover missing parents on asset
+ parents = asset.get("data", {}).get("parents")
+ if parents is not None:
+ hierarchy = "/".join(parents)
+
+ # Cannot fail, required members only
+ data = {
+ "root": root,
+ "project": {
+ "name": project["name"],
+ "code": project.get("data", {}).get("code")
+ },
+ "asset": asset["name"],
+ "silo": asset.get("silo"),
+ "hierarchy": hierarchy,
+ "subset": subset["name"],
+ "version": version_["name"],
+ "representation": representation["name"],
+ "family": representation.get("context", {}).get("family"),
+ "user": dbcon.Session.get("AVALON_USER", getpass.getuser()),
+ "app": dbcon.Session.get("AVALON_APP", ""),
+ "task": dbcon.Session.get("AVALON_TASK", "")
+ }
+
+ try:
+ template_obj = StringTemplate(template)
+ path = str(template_obj.format(data))
+ # Force replacing backslashes with forward slashed if not on
+ # windows
+ if platform.system().lower() != "windows":
+ path = path.replace("\\", "/")
+
+ except KeyError as e:
+ log.debug("Template references unavailable data: %s" % e)
+ return None
+
+ normalized_path = os.path.normpath(path)
+ if os.path.exists(normalized_path):
+ return normalized_path
+ return path
+
+ def path_from_data():
+ if "path" not in representation["data"]:
+ return None
+
+ path = representation["data"]["path"]
+ # Force replacing backslashes with forward slashed if not on
+ # windows
+ if platform.system().lower() != "windows":
+ path = path.replace("\\", "/")
+
+ if os.path.exists(path):
+ return os.path.normpath(path)
+
+ dir_path, file_name = os.path.split(path)
+ if not os.path.exists(dir_path):
+ return
+
+ base_name, ext = os.path.splitext(file_name)
+ file_name_items = None
+ if "#" in base_name:
+ file_name_items = [part for part in base_name.split("#") if part]
+ elif "%" in base_name:
+ file_name_items = base_name.split("%")
+
+ if not file_name_items:
+ return
+
+ filename_start = file_name_items[0]
+
+ for _file in os.listdir(dir_path):
+ if _file.startswith(filename_start) and _file.endswith(ext):
+ return os.path.normpath(path)
+
+ return (
+ path_from_represenation() or
+ path_from_config() or
+ path_from_data()
+ )
+
+
+def is_compatible_loader(Loader, context):
+ """Return whether a loader is compatible with a context.
+
+ This checks the version's families and the representation for the given
+ Loader.
+
+ Returns:
+ bool
+
+ """
+ maj_version, _ = schema.get_schema_version(context["subset"]["schema"])
+ if maj_version < 3:
+ families = context["version"]["data"].get("families", [])
+ else:
+ families = context["subset"]["data"]["families"]
+
+ representation = context["representation"]
+ has_family = (
+ "*" in Loader.families or any(
+ family in Loader.families for family in families
+ )
+ )
+ representations = Loader.get_representations()
+ has_representation = (
+ "*" in representations or representation["name"] in representations
+ )
+ return has_family and has_representation
+
+
+def loaders_from_repre_context(loaders, repre_context):
+ """Return compatible loaders for by representaiton's context."""
+
+ return [
+ loader
+ for loader in loaders
+ if is_compatible_loader(loader, repre_context)
+ ]
+
+
+def loaders_from_representation(loaders, representation):
+ """Return all compatible loaders for a representation."""
+
+ context = get_representation_context(representation)
+ return loaders_from_repre_context(loaders, context)
diff --git a/openpype/plugins/load/add_site.py b/openpype/plugins/load/add_site.py
index 09448d553c..95001691e2 100644
--- a/openpype/plugins/load/add_site.py
+++ b/openpype/plugins/load/add_site.py
@@ -1,8 +1,8 @@
-from avalon import api
from openpype.modules import ModulesManager
+from openpype.pipeline import load
-class AddSyncSite(api.Loader):
+class AddSyncSite(load.LoaderPlugin):
"""Add sync site to representation"""
representations = ["*"]
families = ["*"]
diff --git a/openpype/plugins/load/copy_file.py b/openpype/plugins/load/copy_file.py
index eaf5853035..60db094cfb 100644
--- a/openpype/plugins/load/copy_file.py
+++ b/openpype/plugins/load/copy_file.py
@@ -1,7 +1,8 @@
-from avalon import api, style
+from openpype.style import get_default_entity_icon_color
+from openpype.pipeline import load
-class CopyFile(api.Loader):
+class CopyFile(load.LoaderPlugin):
"""Copy the published file to be pasted at the desired location"""
representations = ["*"]
@@ -10,7 +11,7 @@ class CopyFile(api.Loader):
label = "Copy File"
order = 10
icon = "copy"
- color = style.colors.default
+ color = get_default_entity_icon_color()
def load(self, context, name=None, namespace=None, data=None):
self.log.info("Added copy to clipboard: {0}".format(self.fname))
diff --git a/openpype/plugins/load/copy_file_path.py b/openpype/plugins/load/copy_file_path.py
index 2041c79f6d..565d8d1ff1 100644
--- a/openpype/plugins/load/copy_file_path.py
+++ b/openpype/plugins/load/copy_file_path.py
@@ -1,9 +1,9 @@
import os
-from avalon import api
+from openpype.pipeline import load
-class CopyFilePath(api.Loader):
+class CopyFilePath(load.LoaderPlugin):
"""Copy published file path to clipboard"""
representations = ["*"]
families = ["*"]
diff --git a/openpype/plugins/load/delete_old_versions.py b/openpype/plugins/load/delete_old_versions.py
index e8612745fb..692acdec02 100644
--- a/openpype/plugins/load/delete_old_versions.py
+++ b/openpype/plugins/load/delete_old_versions.py
@@ -8,13 +8,14 @@ import ftrack_api
import qargparse
from Qt import QtWidgets, QtCore
-from avalon import api, style
from avalon.api import AvalonMongoDB
-import avalon.pipeline
+from openpype import style
+from openpype.pipeline import load
+from openpype.lib import StringTemplate
from openpype.api import Anatomy
-class DeleteOldVersions(api.SubsetLoader):
+class DeleteOldVersions(load.SubsetLoaderPlugin):
"""Deletes specific number of old version"""
is_multiple_contexts_compatible = True
@@ -89,16 +90,12 @@ class DeleteOldVersions(api.SubsetLoader):
try:
context = representation["context"]
context["root"] = anatomy.roots
- path = avalon.pipeline.format_template_with_optional_keys(
- context, template
- )
+ path = str(StringTemplate.format_template(template, context))
if "frame" in context:
context["frame"] = self.sequence_splitter
- sequence_path = os.path.normpath(
- avalon.pipeline.format_template_with_optional_keys(
- context, template
- )
- )
+ sequence_path = os.path.normpath(str(
+ StringTemplate.format_template(template, context)
+ ))
except KeyError:
# Template references unavailable data
diff --git a/openpype/plugins/load/delivery.py b/openpype/plugins/load/delivery.py
index 1037d6dc16..04080053e3 100644
--- a/openpype/plugins/load/delivery.py
+++ b/openpype/plugins/load/delivery.py
@@ -3,9 +3,9 @@ from collections import defaultdict
from Qt import QtWidgets, QtCore, QtGui
-from avalon import api
from avalon.api import AvalonMongoDB
+from openpype.pipeline import load
from openpype.api import Anatomy, config
from openpype import resources, style
@@ -20,7 +20,7 @@ from openpype.lib.delivery import (
)
-class Delivery(api.SubsetLoader):
+class Delivery(load.SubsetLoaderPlugin):
"""Export selected versions to folder structure from Template"""
is_multiple_contexts_compatible = True
diff --git a/openpype/plugins/load/open_djv.py b/openpype/plugins/load/open_djv.py
index 4b0e8411c8..273c77c93f 100644
--- a/openpype/plugins/load/open_djv.py
+++ b/openpype/plugins/load/open_djv.py
@@ -1,6 +1,6 @@
import os
-from avalon import api
from openpype.api import ApplicationManager
+from openpype.pipeline import load
def existing_djv_path():
@@ -13,7 +13,8 @@ def existing_djv_path():
return djv_list
-class OpenInDJV(api.Loader):
+
+class OpenInDJV(load.LoaderPlugin):
"""Open Image Sequence with system default"""
djv_list = existing_djv_path()
diff --git a/openpype/plugins/load/open_file.py b/openpype/plugins/load/open_file.py
index 4133a64eb3..f21cd07c7f 100644
--- a/openpype/plugins/load/open_file.py
+++ b/openpype/plugins/load/open_file.py
@@ -2,7 +2,7 @@ import sys
import os
import subprocess
-from avalon import api
+from openpype.pipeline import load
def open(filepath):
@@ -15,7 +15,7 @@ def open(filepath):
subprocess.call(('xdg-open', filepath))
-class Openfile(api.Loader):
+class Openfile(load.LoaderPlugin):
"""Open Image Sequence with system default"""
families = ["render2d"]
diff --git a/openpype/plugins/load/remove_site.py b/openpype/plugins/load/remove_site.py
index aedb5d1f2f..adffec9986 100644
--- a/openpype/plugins/load/remove_site.py
+++ b/openpype/plugins/load/remove_site.py
@@ -1,8 +1,8 @@
-from avalon import api
from openpype.modules import ModulesManager
+from openpype.pipeline import load
-class RemoveSyncSite(api.Loader):
+class RemoveSyncSite(load.LoaderPlugin):
"""Remove sync site and its files on representation"""
representations = ["*"]
families = ["*"]
diff --git a/openpype/plugins/publish/collect_time.py b/openpype/plugins/publish/collect_time.py
index e0adc7dfc3..7a005cc9cb 100644
--- a/openpype/plugins/publish/collect_time.py
+++ b/openpype/plugins/publish/collect_time.py
@@ -1,12 +1,12 @@
import pyblish.api
-from avalon import api
+from openpype.lib import get_formatted_current_time
class CollectTime(pyblish.api.ContextPlugin):
"""Store global time at the time of publish"""
label = "Collect Current Time"
- order = pyblish.api.CollectorOrder
+ order = pyblish.api.CollectorOrder - 0.499
def process(self, context):
- context.data["time"] = api.time()
+ context.data["time"] = get_formatted_current_time()
diff --git a/openpype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py
index 0b139a73e4..cbe1924408 100644
--- a/openpype/plugins/publish/extract_review.py
+++ b/openpype/plugins/publish/extract_review.py
@@ -13,7 +13,7 @@ import pyblish.api
import openpype.api
from openpype.lib import (
get_ffmpeg_tool_path,
- ffprobe_streams,
+ get_ffprobe_streams,
path_to_subprocess_arg,
@@ -103,9 +103,10 @@ class ExtractReview(pyblish.api.InstancePlugin):
self.log.debug("Matching profile: \"{}\"".format(json.dumps(profile)))
+ subset_name = instance.data.get("subset")
instance_families = self.families_from_instance(instance)
- filtered_outputs = self.filter_outputs_by_families(
- profile, instance_families
+ filtered_outputs = self.filter_output_defs(
+ profile, subset_name, instance_families
)
# Store `filename_suffix` to save arguments
profile_outputs = []
@@ -1145,7 +1146,7 @@ class ExtractReview(pyblish.api.InstancePlugin):
# NOTE Skipped using instance's resolution
full_input_path_single_file = temp_data["full_input_path_single_file"]
try:
- streams = ffprobe_streams(
+ streams = get_ffprobe_streams(
full_input_path_single_file, self.log
)
except Exception as exc:
@@ -1187,8 +1188,8 @@ 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_width or output_def.get("width") or None
- output_height = output_height or output_def.get("height") or None
+ output_width = output_def.get("width") or output_width or None
+ output_height = output_def.get("height") or output_height or None
# Overscal color
overscan_color_value = "black"
@@ -1651,7 +1652,7 @@ class ExtractReview(pyblish.api.InstancePlugin):
return True
return False
- def filter_outputs_by_families(self, profile, families):
+ def filter_output_defs(self, profile, subset_name, families):
"""Return outputs matching input instance families.
Output definitions without families filter are marked as valid.
@@ -1684,6 +1685,24 @@ class ExtractReview(pyblish.api.InstancePlugin):
if not self.families_filter_validation(families, families_filters):
continue
+ # Subsets name filters
+ subset_filters = [
+ subset_filter
+ for subset_filter in output_filters.get("subsets", [])
+ # Skip empty strings
+ if subset_filter
+ ]
+ if subset_name and subset_filters:
+ match = False
+ for subset_filter in subset_filters:
+ compiled = re.compile(subset_filter)
+ if compiled.search(subset_name):
+ match = True
+ break
+
+ if not match:
+ continue
+
filtered_outputs[filename_suffix] = output_def
return filtered_outputs
diff --git a/openpype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py
index 5442cf2211..505ae75169 100644
--- a/openpype/plugins/publish/extract_review_slate.py
+++ b/openpype/plugins/publish/extract_review_slate.py
@@ -1,7 +1,14 @@
import os
import openpype.api
-import openpype.lib
import pyblish
+from openpype.lib import (
+ path_to_subprocess_arg,
+ get_ffmpeg_tool_path,
+ get_ffprobe_data,
+ get_ffprobe_streams,
+ get_ffmpeg_codec_args,
+ get_ffmpeg_format_args,
+)
class ExtractReviewSlate(openpype.api.Extractor):
@@ -24,9 +31,9 @@ class ExtractReviewSlate(openpype.api.Extractor):
suffix = "_slate"
slate_path = inst_data.get("slateFrame")
- ffmpeg_path = openpype.lib.get_ffmpeg_tool_path("ffmpeg")
+ ffmpeg_path = get_ffmpeg_tool_path("ffmpeg")
- slate_streams = openpype.lib.ffprobe_streams(slate_path, self.log)
+ slate_streams = get_ffprobe_streams(slate_path, self.log)
# Try to find first stream with defined 'width' and 'height'
# - this is to avoid order of streams where audio can be as first
# - there may be a better way (checking `codec_type`?)+
@@ -66,7 +73,7 @@ class ExtractReviewSlate(openpype.api.Extractor):
os.path.normpath(stagingdir), repre["files"])
self.log.debug("__ input_path: {}".format(input_path))
- video_streams = openpype.lib.ffprobe_streams(
+ video_streams = get_ffprobe_streams(
input_path, self.log
)
@@ -143,7 +150,7 @@ class ExtractReviewSlate(openpype.api.Extractor):
else:
input_args.extend(repre["outputDef"].get('input', []))
input_args.append("-loop 1 -i {}".format(
- openpype.lib.path_to_subprocess_arg(slate_path)
+ path_to_subprocess_arg(slate_path)
))
input_args.extend([
"-r {}".format(fps),
@@ -157,7 +164,7 @@ class ExtractReviewSlate(openpype.api.Extractor):
output_args.extend(repre["_profile"].get('output', []))
else:
# Codecs are copied from source for whole input
- codec_args = self.codec_args(repre)
+ codec_args = self._get_codec_args(repre)
output_args.extend(codec_args)
# make sure colors are correct
@@ -216,12 +223,12 @@ class ExtractReviewSlate(openpype.api.Extractor):
slate_v_path = slate_path.replace(".png", ext)
output_args.append(
- openpype.lib.path_to_subprocess_arg(slate_v_path)
+ path_to_subprocess_arg(slate_v_path)
)
_remove_at_end.append(slate_v_path)
slate_args = [
- openpype.lib.path_to_subprocess_arg(ffmpeg_path),
+ path_to_subprocess_arg(ffmpeg_path),
" ".join(input_args),
" ".join(output_args)
]
@@ -331,7 +338,7 @@ class ExtractReviewSlate(openpype.api.Extractor):
return vf_back
- def codec_args(self, repre):
+ def _get_codec_args(self, repre):
"""Detect possible codec arguments from representation."""
codec_args = []
@@ -345,7 +352,7 @@ class ExtractReviewSlate(openpype.api.Extractor):
try:
# Get information about input file via ffprobe tool
- streams = openpype.lib.ffprobe_streams(full_input_path, self.log)
+ ffprobe_data = get_ffprobe_data(full_input_path, self.log)
except Exception:
self.log.warning(
"Could not get codec data from input.",
@@ -353,42 +360,14 @@ class ExtractReviewSlate(openpype.api.Extractor):
)
return codec_args
- # Try to find first stream that is not an audio
- no_audio_stream = None
- for stream in streams:
- if stream.get("codec_type") != "audio":
- no_audio_stream = stream
- break
+ source_ffmpeg_cmd = repre.get("ffmpeg_cmd")
+ codec_args.extend(
+ get_ffmpeg_format_args(ffprobe_data, source_ffmpeg_cmd)
+ )
+ codec_args.extend(
+ get_ffmpeg_codec_args(
+ ffprobe_data, source_ffmpeg_cmd, logger=self.log
+ )
+ )
- if no_audio_stream is None:
- self.log.warning((
- "Couldn't find stream that is not an audio from file \"{}\""
- ).format(full_input_path))
- return codec_args
-
- codec_name = no_audio_stream.get("codec_name")
- if codec_name:
- codec_args.append("-codec:v {}".format(codec_name))
-
- profile_name = no_audio_stream.get("profile")
- if profile_name:
- # Rest of arguments is prores_kw specific
- if codec_name == "prores_ks":
- codec_tag_to_profile_map = {
- "apco": "proxy",
- "apcs": "lt",
- "apcn": "standard",
- "apch": "hq",
- "ap4h": "4444",
- "ap4x": "4444xq"
- }
- codec_tag_str = no_audio_stream.get("codec_tag_string")
- if codec_tag_str:
- profile = codec_tag_to_profile_map.get(codec_tag_str)
- if profile:
- codec_args.extend(["-profile:v", profile])
-
- pix_fmt = no_audio_stream.get("pix_fmt")
- if pix_fmt:
- codec_args.append("-pix_fmt {}".format(pix_fmt))
return codec_args
diff --git a/openpype/resources/images/spinner-200.svg b/openpype/resources/images/spinner-200.svg
new file mode 100644
index 0000000000..73d8ae2890
--- /dev/null
+++ b/openpype/resources/images/spinner-200.svg
@@ -0,0 +1,6 @@
+
+
diff --git a/openpype/scripts/otio_burnin.py b/openpype/scripts/otio_burnin.py
index 874c08064a..1f57891b84 100644
--- a/openpype/scripts/otio_burnin.py
+++ b/openpype/scripts/otio_burnin.py
@@ -5,12 +5,17 @@ import subprocess
import platform
import json
import opentimelineio_contrib.adapters.ffmpeg_burnins as ffmpeg_burnins
-import openpype.lib
-from openpype.lib.vendor_bin_utils import get_fps
+
+from openpype.lib import (
+ get_ffmpeg_tool_path,
+ get_ffmpeg_codec_args,
+ get_ffmpeg_format_args,
+ convert_ffprobe_fps_value,
+)
-ffmpeg_path = openpype.lib.get_ffmpeg_tool_path("ffmpeg")
-ffprobe_path = openpype.lib.get_ffmpeg_tool_path("ffprobe")
+ffmpeg_path = get_ffmpeg_tool_path("ffmpeg")
+ffprobe_path = get_ffmpeg_tool_path("ffprobe")
FFMPEG = (
@@ -51,157 +56,6 @@ def _get_ffprobe_data(source):
return json.loads(out)
-def _prores_codec_args(stream_data, source_ffmpeg_cmd):
- output = []
-
- tags = stream_data.get("tags") or {}
- encoder = tags.get("encoder") or ""
- if encoder.endswith("prores_ks"):
- codec_name = "prores_ks"
-
- elif encoder.endswith("prores_aw"):
- codec_name = "prores_aw"
-
- else:
- codec_name = "prores"
-
- output.extend(["-codec:v", codec_name])
-
- pix_fmt = stream_data.get("pix_fmt")
- if pix_fmt:
- output.extend(["-pix_fmt", pix_fmt])
-
- # Rest of arguments is prores_kw specific
- if codec_name == "prores_ks":
- codec_tag_to_profile_map = {
- "apco": "proxy",
- "apcs": "lt",
- "apcn": "standard",
- "apch": "hq",
- "ap4h": "4444",
- "ap4x": "4444xq"
- }
- codec_tag_str = stream_data.get("codec_tag_string")
- if codec_tag_str:
- profile = codec_tag_to_profile_map.get(codec_tag_str)
- if profile:
- output.extend(["-profile:v", profile])
-
- return output
-
-
-def _h264_codec_args(stream_data, source_ffmpeg_cmd):
- output = ["-codec:v", "h264"]
-
- # Use arguments from source if are available source arguments
- if source_ffmpeg_cmd:
- copy_args = (
- "-crf",
- "-b:v", "-vb",
- "-minrate", "-minrate:",
- "-maxrate", "-maxrate:",
- "-bufsize", "-bufsize:"
- )
- args = source_ffmpeg_cmd.split(" ")
- for idx, arg in enumerate(args):
- if arg in copy_args:
- output.extend([arg, args[idx + 1]])
-
- pix_fmt = stream_data.get("pix_fmt")
- if pix_fmt:
- output.extend(["-pix_fmt", pix_fmt])
-
- output.extend(["-intra"])
- output.extend(["-g", "1"])
-
- return output
-
-
-def _dnxhd_codec_args(stream_data, source_ffmpeg_cmd):
- output = ["-codec:v", "dnxhd"]
-
- # Use source profile (profiles in metadata are not usable in args directly)
- profile = stream_data.get("profile") or ""
- # Lower profile and replace space with underscore
- cleaned_profile = profile.lower().replace(" ", "_")
- dnx_profiles = {
- "dnxhd",
- "dnxhr_lb",
- "dnxhr_sq",
- "dnxhr_hq",
- "dnxhr_hqx",
- "dnxhr_444"
- }
- if cleaned_profile in dnx_profiles:
- output.extend(["-profile:v", cleaned_profile])
-
- pix_fmt = stream_data.get("pix_fmt")
- if pix_fmt:
- output.extend(["-pix_fmt", pix_fmt])
-
- # Use arguments from source if are available source arguments
- if source_ffmpeg_cmd:
- copy_args = (
- "-b:v", "-vb",
- )
- args = source_ffmpeg_cmd.split(" ")
- for idx, arg in enumerate(args):
- if arg in copy_args:
- output.extend([arg, args[idx + 1]])
-
- output.extend(["-g", "1"])
- return output
-
-
-def _mxf_format_args(ffprobe_data, source_ffmpeg_cmd):
- input_format = ffprobe_data["format"]
- format_tags = input_format.get("tags") or {}
- product_name = format_tags.get("product_name") or ""
- output = []
- if "opatom" in product_name.lower():
- output.extend(["-f", "mxf_opatom"])
- return output
-
-
-def get_format_args(ffprobe_data, source_ffmpeg_cmd):
- input_format = ffprobe_data.get("format") or {}
- if input_format.get("format_name") == "mxf":
- return _mxf_format_args(ffprobe_data, source_ffmpeg_cmd)
- return []
-
-
-def get_codec_args(ffprobe_data, source_ffmpeg_cmd):
- stream_data = ffprobe_data["streams"][0]
- codec_name = stream_data.get("codec_name")
- # Codec "prores"
- if codec_name == "prores":
- return _prores_codec_args(stream_data, source_ffmpeg_cmd)
-
- # Codec "h264"
- if codec_name == "h264":
- return _h264_codec_args(stream_data, source_ffmpeg_cmd)
-
- # Coded DNxHD
- if codec_name == "dnxhd":
- return _dnxhd_codec_args(stream_data, source_ffmpeg_cmd)
-
- output = []
- if codec_name:
- output.extend(["-codec:v", codec_name])
-
- bit_rate = stream_data.get("bit_rate")
- if bit_rate:
- output.extend(["-b:v", bit_rate])
-
- pix_fmt = stream_data.get("pix_fmt")
- if pix_fmt:
- output.extend(["-pix_fmt", pix_fmt])
-
- output.extend(["-g", "1"])
-
- return output
-
-
class ModifiedBurnins(ffmpeg_burnins.Burnins):
'''
This is modification of OTIO FFmpeg Burnin adapter.
@@ -592,7 +446,9 @@ def burnins_from_data(
data["resolution_height"] = stream.get("height", MISSING_KEY_VALUE)
if "fps" not in data:
- data["fps"] = get_fps(stream.get("r_frame_rate", "0/0"))
+ data["fps"] = convert_ffprobe_fps_value(
+ stream.get("r_frame_rate", "0/0")
+ )
# Check frame start and add expression if is available
if frame_start is not None:
@@ -703,10 +559,10 @@ def burnins_from_data(
else:
ffmpeg_args.extend(
- get_format_args(burnin.ffprobe_data, source_ffmpeg_cmd)
+ get_ffmpeg_format_args(burnin.ffprobe_data, source_ffmpeg_cmd)
)
ffmpeg_args.extend(
- get_codec_args(burnin.ffprobe_data, source_ffmpeg_cmd)
+ get_ffmpeg_codec_args(burnin.ffprobe_data, source_ffmpeg_cmd)
)
# Use arguments from source if are available source arguments
if source_ffmpeg_cmd:
diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json
index 9c44d9bc86..30a71b044a 100644
--- a/openpype/settings/defaults/project_settings/global.json
+++ b/openpype/settings/defaults/project_settings/global.json
@@ -87,7 +87,8 @@
"render",
"review",
"ftrack"
- ]
+ ],
+ "subsets": []
},
"overscan_crop": "",
"overscan_color": [
diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json
index 3eea7ccb30..12043d4205 100644
--- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json
+++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json
@@ -291,6 +291,15 @@
"label": "Families",
"type": "list",
"object_type": "text"
+ },
+ {
+ "type": "separator"
+ },
+ {
+ "key": "subsets",
+ "label": "Subsets",
+ "type": "list",
+ "object_type": "text"
}
]
},
diff --git a/openpype/style/__init__.py b/openpype/style/__init__.py
index ea88b342ee..b2a1a4ce6c 100644
--- a/openpype/style/__init__.py
+++ b/openpype/style/__init__.py
@@ -7,13 +7,19 @@ from openpype import resources
from .color_defs import parse_color
-
-_STYLESHEET_CACHE = None
-_FONT_IDS = None
-
current_dir = os.path.dirname(os.path.abspath(__file__))
+class _Cache:
+ stylesheet = None
+ font_ids = None
+
+ tools_icon_color = None
+ default_entity_icon_color = None
+ disabled_entity_icon_color = None
+ deprecated_entity_font_color = None
+
+
def get_style_image_path(image_name):
# All filenames are lowered
image_name = image_name.lower()
@@ -125,21 +131,19 @@ def _load_font():
"""Load and register fonts into Qt application."""
from Qt import QtGui
- global _FONT_IDS
-
# Check if font ids are still loaded
- if _FONT_IDS is not None:
- for font_id in tuple(_FONT_IDS):
+ if _Cache.font_ids is not None:
+ for font_id in tuple(_Cache.font_ids):
font_families = QtGui.QFontDatabase.applicationFontFamilies(
font_id
)
# Reset font if font id is not available
if not font_families:
- _FONT_IDS = None
+ _Cache.font_ids = None
break
- if _FONT_IDS is None:
- _FONT_IDS = []
+ if _Cache.font_ids is None:
+ _Cache.font_ids = []
fonts_dirpath = os.path.join(current_dir, "fonts")
font_dirs = []
font_dirs.append(os.path.join(fonts_dirpath, "Noto_Sans"))
@@ -157,7 +161,7 @@ def _load_font():
continue
full_path = os.path.join(font_dir, filename)
font_id = QtGui.QFontDatabase.addApplicationFont(full_path)
- _FONT_IDS.append(font_id)
+ _Cache.font_ids.append(font_id)
font_families = QtGui.QFontDatabase.applicationFontFamilies(
font_id
)
@@ -167,11 +171,11 @@ def _load_font():
def load_stylesheet():
"""Load and return OpenPype Qt stylesheet."""
- global _STYLESHEET_CACHE
- if _STYLESHEET_CACHE is None:
- _STYLESHEET_CACHE = _load_stylesheet()
+
+ if _Cache.stylesheet is None:
+ _Cache.stylesheet = _load_stylesheet()
_load_font()
- return _STYLESHEET_CACHE
+ return _Cache.stylesheet
def get_app_icon_path():
@@ -182,3 +186,63 @@ def get_app_icon_path():
def app_icon_path():
# Backwards compatibility
return get_app_icon_path()
+
+
+def get_default_tools_icon_color():
+ """Default color used in tool icons.
+
+ Color must be possible to parse using QColor.
+
+ Returns:
+ str: Color as a string.
+ """
+ if _Cache.tools_icon_color is None:
+ color_data = get_colors_data()
+ _Cache.tools_icon_color = color_data["icon-tools"]
+ return _Cache.tools_icon_color
+
+
+def get_default_entity_icon_color():
+ """Default color of entities icons.
+
+ Color must be possible to parse using QColor.
+
+ Returns:
+ str: Color as a string.
+ """
+ if _Cache.default_entity_icon_color is None:
+ color_data = get_colors_data()
+ _Cache.default_entity_icon_color = color_data["icon-entity-default"]
+ return _Cache.default_entity_icon_color
+
+
+def get_disabled_entity_icon_color():
+ """Default color of entities icons.
+
+ TODO: Find more suitable function name.
+
+ Color must be possible to parse using QColor.
+
+ Returns:
+ str: Color as a string.
+ """
+ if _Cache.disabled_entity_icon_color is None:
+ color_data = get_colors_data()
+ _Cache.disabled_entity_icon_color = color_data["icon-entity-disabled"]
+ return _Cache.disabled_entity_icon_color
+
+
+def get_deprecated_entity_font_color():
+ """Font color for deprecated entities.
+
+ Color must be possible to parse using QColor.
+
+ Returns:
+ str: Color as a string.
+ """
+ if _Cache.deprecated_entity_font_color is None:
+ color_data = get_colors_data()
+ _Cache.deprecated_entity_font_color = (
+ color_data["font-entity-deprecated"]
+ )
+ return _Cache.deprecated_entity_font_color
diff --git a/openpype/style/data.json b/openpype/style/data.json
index b8ccef8bbd..a76a77015b 100644
--- a/openpype/style/data.json
+++ b/openpype/style/data.json
@@ -56,6 +56,12 @@
"delete-btn-bg": "rgb(201, 54, 54)",
"delete-btn-bg-disabled": "rgba(201, 54, 54, 64)",
+ "icon-tools": "#ffffff",
+ "icon-alert-tools": "#AA5050",
+ "icon-entity-default": "#bfccd6",
+ "icon-entity-disabled": "#808080",
+ "font-entity-deprecated": "#666666",
+
"tab-widget": {
"bg": "#21252B",
"bg-selected": "#434a56",
diff --git a/openpype/tests/test_lib_restructuralization.py b/openpype/tests/test_lib_restructuralization.py
index d0461e55fb..94080e550d 100644
--- a/openpype/tests/test_lib_restructuralization.py
+++ b/openpype/tests/test_lib_restructuralization.py
@@ -24,7 +24,7 @@ def test_backward_compatibility(printer):
from openpype.lib import get_hierarchy
from openpype.lib import get_linked_assets
from openpype.lib import get_latest_version
- from openpype.lib import ffprobe_streams
+ from openpype.lib import get_ffprobe_streams
from openpype.hosts.fusion.lib import switch_item
diff --git a/openpype/tools/adobe_webserver/app.py b/openpype/tools/adobe_webserver/app.py
new file mode 100644
index 0000000000..b79d6c6c60
--- /dev/null
+++ b/openpype/tools/adobe_webserver/app.py
@@ -0,0 +1,237 @@
+"""This Webserver tool is python 3 specific.
+
+Don't import directly to avalon.tools or implementation of Python 2 hosts
+would break.
+"""
+import os
+import logging
+import urllib
+import threading
+import asyncio
+import socket
+
+from aiohttp import web
+
+from wsrpc_aiohttp import (
+ WSRPCClient
+)
+
+from avalon import api
+
+log = logging.getLogger(__name__)
+
+
+class WebServerTool:
+ """
+ Basic POC implementation of asychronic websocket RPC server.
+ Uses class in external_app_1.py to mimic implementation for single
+ external application.
+ 'test_client' folder contains two test implementations of client
+ """
+ _instance = None
+
+ def __init__(self):
+ WebServerTool._instance = self
+
+ self.client = None
+ self.handlers = {}
+ self.on_stop_callbacks = []
+
+ port = None
+ host_name = "localhost"
+ websocket_url = os.getenv("WEBSOCKET_URL")
+ if websocket_url:
+ parsed = urllib.parse.urlparse(websocket_url)
+ port = parsed.port
+ host_name = parsed.netloc.split(":")[0]
+ if not port:
+ port = 8098 # fallback
+
+ self.port = port
+ self.host_name = host_name
+
+ self.app = web.Application()
+
+ # add route with multiple methods for single "external app"
+ self.webserver_thread = WebServerThread(self, self.port)
+
+ def add_route(self, *args, **kwargs):
+ self.app.router.add_route(*args, **kwargs)
+
+ def add_static(self, *args, **kwargs):
+ self.app.router.add_static(*args, **kwargs)
+
+ def start_server(self):
+ if self.webserver_thread and not self.webserver_thread.is_alive():
+ self.webserver_thread.start()
+
+ def stop_server(self):
+ self.stop()
+
+ async def send_context_change(self, host):
+ """
+ Calls running webserver to inform about context change
+
+ Used when new PS/AE should be triggered,
+ but one already running, without
+ this publish would point to old context.
+ """
+ client = WSRPCClient(os.getenv("WEBSOCKET_URL"),
+ loop=asyncio.get_event_loop())
+ await client.connect()
+
+ project = api.Session["AVALON_PROJECT"]
+ asset = api.Session["AVALON_ASSET"]
+ task = api.Session["AVALON_TASK"]
+ log.info("Sending context change to {}-{}-{}".format(project,
+ asset,
+ task))
+
+ await client.call('{}.set_context'.format(host),
+ project=project, asset=asset, task=task)
+ await client.close()
+
+ def port_occupied(self, host_name, port):
+ """
+ Check if 'url' is already occupied.
+
+ This could mean, that app is already running and we are trying open it
+ again. In that case, use existing running webserver.
+ Check here is easier than capturing exception from thread.
+ """
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ result = True
+ try:
+ sock.bind((host_name, port))
+ result = False
+ except:
+ print("Port is in use")
+
+ return result
+
+ def call(self, func):
+ log.debug("websocket.call {}".format(func))
+ future = asyncio.run_coroutine_threadsafe(
+ func,
+ self.webserver_thread.loop
+ )
+ result = future.result()
+ return result
+
+ @staticmethod
+ def get_instance():
+ if WebServerTool._instance is None:
+ WebServerTool()
+ return WebServerTool._instance
+
+ @property
+ def is_running(self):
+ if not self.webserver_thread:
+ return False
+ return self.webserver_thread.is_running
+
+ def stop(self):
+ if not self.is_running:
+ return
+ try:
+ log.debug("Stopping websocket server")
+ self.webserver_thread.is_running = False
+ self.webserver_thread.stop()
+ except Exception:
+ log.warning(
+ "Error has happened during Killing websocket server",
+ exc_info=True
+ )
+
+ def thread_stopped(self):
+ for callback in self.on_stop_callbacks:
+ callback()
+
+
+class WebServerThread(threading.Thread):
+ """ Listener for websocket rpc requests.
+
+ It would be probably better to "attach" this to main thread (as for
+ example Harmony needs to run something on main thread), but currently
+ it creates separate thread and separate asyncio event loop
+ """
+ def __init__(self, module, port):
+ super(WebServerThread, self).__init__()
+
+ self.is_running = False
+ self.port = port
+ self.module = module
+ self.loop = None
+ self.runner = None
+ self.site = None
+ self.tasks = []
+
+ def run(self):
+ self.is_running = True
+
+ try:
+ log.info("Starting web server")
+ self.loop = asyncio.new_event_loop() # create new loop for thread
+ asyncio.set_event_loop(self.loop)
+
+ self.loop.run_until_complete(self.start_server())
+
+ websocket_url = "ws://localhost:{}/ws".format(self.port)
+
+ log.debug(
+ "Running Websocket server on URL: \"{}\"".format(websocket_url)
+ )
+
+ asyncio.ensure_future(self.check_shutdown(), loop=self.loop)
+ self.loop.run_forever()
+ except Exception:
+ self.is_running = False
+ log.warning(
+ "Websocket Server service has failed", exc_info=True
+ )
+ raise
+ finally:
+ self.loop.close() # optional
+
+ self.is_running = False
+ self.module.thread_stopped()
+ log.info("Websocket server stopped")
+
+ async def start_server(self):
+ """ Starts runner and TCPsite """
+ self.runner = web.AppRunner(self.module.app)
+ await self.runner.setup()
+ self.site = web.TCPSite(self.runner, 'localhost', self.port)
+ await self.site.start()
+
+ def stop(self):
+ """Sets is_running flag to false, 'check_shutdown' shuts server down"""
+ self.is_running = False
+
+ async def check_shutdown(self):
+ """ Future that is running and checks if server should be running
+ periodically.
+ """
+ while self.is_running:
+ while self.tasks:
+ task = self.tasks.pop(0)
+ log.debug("waiting for task {}".format(task))
+ await task
+ log.debug("returned value {}".format(task.result))
+
+ await asyncio.sleep(0.5)
+
+ log.debug("Starting shutdown")
+ await self.site.stop()
+ log.debug("Site stopped")
+ await self.runner.cleanup()
+ log.debug("Runner stopped")
+ tasks = [task for task in asyncio.all_tasks() if
+ task is not asyncio.current_task()]
+ list(map(lambda task: task.cancel(), tasks)) # cancel all the tasks
+ results = await asyncio.gather(*tasks, return_exceptions=True)
+ log.debug(f'Finished awaiting cancelled tasks, results: {results}...')
+ await self.loop.shutdown_asyncgens()
+ # to really make sure everything else has time to stop
+ await asyncio.sleep(0.07)
+ self.loop.stop()
diff --git a/openpype/tools/adobe_webserver/readme.txt b/openpype/tools/adobe_webserver/readme.txt
new file mode 100644
index 0000000000..06cf140fc4
--- /dev/null
+++ b/openpype/tools/adobe_webserver/readme.txt
@@ -0,0 +1,12 @@
+Adobe webserver
+---------------
+Aiohttp (Asyncio) based websocket server used for communication with host
+applications, currently only for Adobe (but could be used for any non python
+DCC which has websocket client).
+
+This webserver is started in spawned Python process that opens DCC during
+its launch, waits for connection from DCC and handles communication going
+forward. Server is closed before Python process is killed.
+
+(Different from `openpype/modules/webserver` as that one is running in Tray,
+this one is running in spawn Python process.)
\ No newline at end of file
diff --git a/openpype/tools/launcher/models.py b/openpype/tools/launcher/models.py
index 9036c9cbd5..85d553fca4 100644
--- a/openpype/tools/launcher/models.py
+++ b/openpype/tools/launcher/models.py
@@ -14,7 +14,10 @@ from openpype.lib.applications import (
CUSTOM_LAUNCH_APP_GROUPS,
ApplicationManager
)
-from openpype.tools.utils.lib import DynamicQThread
+from openpype.tools.utils.lib import (
+ DynamicQThread,
+ get_project_icon,
+)
from openpype.tools.utils.assets_widget import (
AssetModel,
ASSET_NAME_ROLE
@@ -400,6 +403,7 @@ class LauncherModel(QtCore.QObject):
self._dbcon = dbcon
# Available project names
self._project_names = set()
+ self._project_docs_by_name = {}
# Context data
self._asset_docs = []
@@ -460,6 +464,9 @@ class LauncherModel(QtCore.QObject):
"""Available project names."""
return self._project_names
+ def get_project_doc(self, project_name):
+ return self._project_docs_by_name.get(project_name)
+
@property
def asset_filter_data_by_id(self):
"""Prepared filter data by asset id."""
@@ -516,9 +523,13 @@ class LauncherModel(QtCore.QObject):
"""Refresh projects."""
current_project = self.project_name
project_names = set()
+ project_docs_by_name = {}
for project_doc in self._dbcon.projects(only_active=True):
- project_names.add(project_doc["name"])
+ project_name = project_doc["name"]
+ project_names.add(project_name)
+ project_docs_by_name[project_name] = project_doc
+ self._project_docs_by_name = project_docs_by_name
self._project_names = project_names
self.projects_refreshed.emit()
if (
@@ -694,6 +705,11 @@ class LauncherTaskModel(TasksModel):
self._launcher_model = launcher_model
super(LauncherTaskModel, self).__init__(*args, **kwargs)
+ def _refresh_project_doc(self):
+ self._project_doc = self._launcher_model.get_project_doc(
+ self._launcher_model.project_name
+ )
+
def set_asset_id(self, asset_id):
asset_doc = None
if self._context_is_valid():
@@ -718,7 +734,6 @@ class AssetRecursiveSortFilterModel(QtCore.QSortFilterProxyModel):
self._assignee_filter = self._launcher_model.assignee_filters
self.invalidateFilter()
- """Filters to the regex if any of the children matches allow parent"""
def filterAcceptsRow(self, row, parent):
if (
not self._name_filter
@@ -818,7 +833,6 @@ class ProjectModel(QtGui.QStandardItemModel):
super(ProjectModel, self).__init__(parent=parent)
self._launcher_model = launcher_model
- self.project_icon = qtawesome.icon("fa.map", color="white")
self._project_names = set()
launcher_model.projects_refreshed.connect(self._on_refresh)
@@ -863,7 +877,11 @@ class ProjectModel(QtGui.QStandardItemModel):
for row in reversed(sorted(row_counts.keys())):
items = []
for project_name in row_counts[row]:
- item = QtGui.QStandardItem(self.project_icon, project_name)
+ project_doc = self._launcher_model.get_project_doc(
+ project_name
+ )
+ icon = get_project_icon(project_doc)
+ item = QtGui.QStandardItem(icon, project_name)
items.append(item)
self.invisibleRootItem().insertRows(row, items)
diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py
index aa743b05fe..d73a977ac6 100644
--- a/openpype/tools/loader/app.py
+++ b/openpype/tools/loader/app.py
@@ -1,9 +1,10 @@
import sys
from Qt import QtWidgets, QtCore
-from avalon import api, io, pipeline
+from avalon import api, io
from openpype import style
+from openpype.lib import register_event_callback
from openpype.tools.utils import (
lib,
PlaceholderLineEdit
@@ -25,17 +26,6 @@ module = sys.modules[__name__]
module.window = None
-# Register callback on task change
-# - callback can't be defined in Window as it is weak reference callback
-# so `WeakSet` will remove it immediately
-def on_context_task_change(*args, **kwargs):
- if module.window:
- module.window.on_context_task_change(*args, **kwargs)
-
-
-pipeline.on("taskChanged", on_context_task_change)
-
-
class LoaderWindow(QtWidgets.QDialog):
"""Asset loader interface"""
@@ -194,6 +184,8 @@ class LoaderWindow(QtWidgets.QDialog):
self._first_show = True
+ register_event_callback("taskChanged", self.on_context_task_change)
+
def resizeEvent(self, event):
super(LoaderWindow, self).resizeEvent(event)
self._overlay_frame.resize(self.size())
diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py
index baee569239..6cc6fae1fb 100644
--- a/openpype/tools/loader/model.py
+++ b/openpype/tools/loader/model.py
@@ -3,15 +3,13 @@ import re
import math
from uuid import uuid4
-from avalon import (
- style,
- schema
-)
from Qt import QtCore, QtGui
import qtawesome
-from avalon.lib import HeroVersionType
+from avalon import schema
+from openpype.pipeline import HeroVersionType
+from openpype.style import get_default_entity_icon_color
from openpype.tools.utils.models import TreeModel, Item
from openpype.tools.utils import lib
@@ -180,7 +178,10 @@ class SubsetsModel(TreeModel, BaseRepresentationModel):
self._sorter = None
self._grouping = grouping
self._icons = {
- "subset": qtawesome.icon("fa.file-o", color=style.colors.default)
+ "subset": qtawesome.icon(
+ "fa.file-o",
+ color=get_default_entity_icon_color()
+ )
}
self._items_by_id = {}
@@ -1066,8 +1067,10 @@ class RepresentationModel(TreeModel, BaseRepresentationModel):
self._docs = {}
self._icons = lib.get_repre_icons()
- self._icons["repre"] = qtawesome.icon("fa.file-o",
- color=style.colors.default)
+ self._icons["repre"] = qtawesome.icon(
+ "fa.file-o",
+ color=get_default_entity_icon_color()
+ )
self._items_by_id = {}
def set_version_ids(self, version_ids):
@@ -1165,7 +1168,7 @@ class RepresentationModel(TreeModel, BaseRepresentationModel):
"remote_site_name": self.remote_site,
"icon": qtawesome.icon(
"fa.folder",
- color=style.colors.default
+ color=get_default_entity_icon_color()
)
})
self._items_by_id[item_id] = group_item
diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py
index f145756cc5..b14bdd0e93 100644
--- a/openpype/tools/loader/widgets.py
+++ b/openpype/tools/loader/widgets.py
@@ -1,6 +1,5 @@
import os
import sys
-import inspect
import datetime
import pprint
import traceback
@@ -9,8 +8,19 @@ import collections
from Qt import QtWidgets, QtCore, QtGui
from avalon import api, pipeline
-from avalon.lib import HeroVersionType
+from openpype.pipeline import HeroVersionType
+from openpype.pipeline.load import (
+ discover_loader_plugins,
+ SubsetLoaderPlugin,
+ loaders_from_repre_context,
+ get_repres_contexts,
+ get_subset_contexts,
+ load_with_repre_context,
+ load_with_subset_context,
+ load_with_subset_contexts,
+ IncompatibleLoaderError,
+)
from openpype.tools.utils import (
ErrorMessageBox,
lib as tools_lib
@@ -425,7 +435,7 @@ class SubsetWidget(QtWidgets.QWidget):
# Get all representation->loader combinations available for the
# index under the cursor, so we can list the user the options.
- available_loaders = api.discover(api.Loader)
+ available_loaders = discover_loader_plugins()
if self.tool_name:
available_loaders = lib.remove_tool_name_from_loaders(
available_loaders, self.tool_name
@@ -435,7 +445,7 @@ class SubsetWidget(QtWidgets.QWidget):
subset_loaders = []
for loader in available_loaders:
# Skip if its a SubsetLoader.
- if api.SubsetLoader in inspect.getmro(loader):
+ if issubclass(loader, SubsetLoaderPlugin):
subset_loaders.append(loader)
else:
repre_loaders.append(loader)
@@ -459,7 +469,7 @@ class SubsetWidget(QtWidgets.QWidget):
repre_docs = repre_docs_by_version_id[version_id]
for repre_doc in repre_docs:
repre_context = repre_context_by_id[repre_doc["_id"]]
- for loader in pipeline.loaders_from_repre_context(
+ for loader in loaders_from_repre_context(
repre_loaders,
repre_context
):
@@ -515,7 +525,7 @@ class SubsetWidget(QtWidgets.QWidget):
action = lib.get_no_loader_action(menu, one_item_selected)
menu.addAction(action)
else:
- repre_contexts = pipeline.get_repres_contexts(
+ repre_contexts = get_repres_contexts(
repre_context_by_id.keys(), self.dbcon)
menu = lib.add_representation_loaders_to_menu(
@@ -532,7 +542,7 @@ class SubsetWidget(QtWidgets.QWidget):
self.load_started.emit()
- if api.SubsetLoader in inspect.getmro(loader):
+ if issubclass(loader, SubsetLoaderPlugin):
subset_ids = []
subset_version_docs = {}
for item in items:
@@ -541,8 +551,7 @@ class SubsetWidget(QtWidgets.QWidget):
subset_version_docs[subset_id] = item["version_document"]
# get contexts only for selected menu option
- subset_contexts_by_id = pipeline.get_subset_contexts(subset_ids,
- self.dbcon)
+ subset_contexts_by_id = get_subset_contexts(subset_ids, self.dbcon)
subset_contexts = list(subset_contexts_by_id.values())
options = lib.get_options(action, loader, self, subset_contexts)
@@ -575,8 +584,7 @@ class SubsetWidget(QtWidgets.QWidget):
repre_ids.append(representation["_id"])
# get contexts only for selected menu option
- repre_contexts = pipeline.get_repres_contexts(repre_ids,
- self.dbcon)
+ repre_contexts = get_repres_contexts(repre_ids, self.dbcon)
options = lib.get_options(action, loader, self,
list(repre_contexts.values()))
@@ -1339,12 +1347,12 @@ class RepresentationWidget(QtWidgets.QWidget):
selected_side = self._get_selected_side(point_index, rows)
# Get all representation->loader combinations available for the
# index under the cursor, so we can list the user the options.
- available_loaders = api.discover(api.Loader)
+ available_loaders = discover_loader_plugins()
filtered_loaders = []
for loader in available_loaders:
# Skip subset loaders
- if api.SubsetLoader in inspect.getmro(loader):
+ if issubclass(loader, SubsetLoaderPlugin):
continue
if (
@@ -1370,7 +1378,7 @@ class RepresentationWidget(QtWidgets.QWidget):
for item in items:
repre_context = repre_context_by_id[item["_id"]]
- for loader in pipeline.loaders_from_repre_context(
+ for loader in loaders_from_repre_context(
filtered_loaders,
repre_context
):
@@ -1426,7 +1434,7 @@ class RepresentationWidget(QtWidgets.QWidget):
action = lib.get_no_loader_action(menu)
menu.addAction(action)
else:
- repre_contexts = pipeline.get_repres_contexts(
+ repre_contexts = get_repres_contexts(
repre_context_by_id.keys(), self.dbcon)
menu = lib.add_representation_loaders_to_menu(loaders, menu,
repre_contexts)
@@ -1472,8 +1480,7 @@ class RepresentationWidget(QtWidgets.QWidget):
repre_ids.append(item.get("_id"))
- repre_contexts = pipeline.get_repres_contexts(repre_ids,
- self.dbcon)
+ repre_contexts = get_repres_contexts(repre_ids, self.dbcon)
options = lib.get_options(action, loader, self,
list(repre_contexts.values()))
@@ -1540,7 +1547,7 @@ def _load_representations_by_loader(loader, repre_contexts,
"""Loops through list of repre_contexts and loads them with one loader
Args:
- loader (cls of api.Loader) - not initialized yet
+ loader (cls of LoaderPlugin) - not initialized yet
repre_contexts (dicts) - full info about selected representations
(containing repre_doc, version_doc, subset_doc, project info)
options (dict) - qargparse arguments to fill OptionDialog
@@ -1558,12 +1565,12 @@ def _load_representations_by_loader(loader, repre_contexts,
_id = repre_context["representation"]["_id"]
data = data_by_repre_id.get(_id)
options.update(data)
- pipeline.load_with_repre_context(
+ load_with_repre_context(
loader,
repre_context,
options=options
)
- except pipeline.IncompatibleLoaderError as exc:
+ except IncompatibleLoaderError as exc:
print(exc)
error_info.append((
"Incompatible Loader",
@@ -1612,7 +1619,7 @@ def _load_subsets_by_loader(loader, subset_contexts, options,
context["version"] = subset_version_docs[context["subset"]["_id"]]
try:
- pipeline.load_with_subset_contexts(
+ load_with_subset_contexts(
loader,
subset_contexts,
options=options
@@ -1638,7 +1645,7 @@ def _load_subsets_by_loader(loader, subset_contexts, options,
version_doc = subset_version_docs[subset_context["subset"]["_id"]]
subset_context["version"] = version_doc
try:
- pipeline.load_with_subset_context(
+ load_with_subset_context(
loader,
subset_context,
options=options
diff --git a/openpype/tools/mayalookassigner/__init__.py b/openpype/tools/mayalookassigner/__init__.py
index 616a3e94d0..5e40777741 100644
--- a/openpype/tools/mayalookassigner/__init__.py
+++ b/openpype/tools/mayalookassigner/__init__.py
@@ -1,9 +1,9 @@
from .app import (
- App,
+ MayaLookAssignerWindow,
show
)
__all__ = [
- "App",
+ "MayaLookAssignerWindow",
"show"]
diff --git a/openpype/tools/mayalookassigner/app.py b/openpype/tools/mayalookassigner/app.py
index 31bb455f95..0e633a21e3 100644
--- a/openpype/tools/mayalookassigner/app.py
+++ b/openpype/tools/mayalookassigner/app.py
@@ -4,10 +4,10 @@ import logging
from Qt import QtWidgets, QtCore
-from openpype.hosts.maya.api.lib import assign_look_by_version
-
-from avalon import style, io
+from avalon import io
+from openpype import style
from openpype.tools.utils.lib import qt_app_context
+from openpype.hosts.maya.api.lib import assign_look_by_version
from maya import cmds
# old api for MFileIO
@@ -28,10 +28,10 @@ module = sys.modules[__name__]
module.window = None
-class App(QtWidgets.QWidget):
+class MayaLookAssignerWindow(QtWidgets.QWidget):
def __init__(self, parent=None):
- QtWidgets.QWidget.__init__(self, parent=parent)
+ super(MayaLookAssignerWindow, self).__init__(parent=parent)
self.log = logging.getLogger(__name__)
@@ -56,30 +56,41 @@ class App(QtWidgets.QWidget):
def setup_ui(self):
"""Build the UI"""
+ main_splitter = QtWidgets.QSplitter(self)
+
# Assets (left)
- asset_outliner = AssetOutliner()
+ asset_outliner = AssetOutliner(main_splitter)
# Looks (right)
- looks_widget = QtWidgets.QWidget()
- looks_layout = QtWidgets.QVBoxLayout(looks_widget)
+ looks_widget = QtWidgets.QWidget(main_splitter)
- look_outliner = LookOutliner() # Database look overview
+ look_outliner = LookOutliner(looks_widget) # Database look overview
- assign_selected = QtWidgets.QCheckBox("Assign to selected only")
+ assign_selected = QtWidgets.QCheckBox(
+ "Assign to selected only", looks_widget
+ )
assign_selected.setToolTip("Whether to assign only to selected nodes "
"or to the full asset")
- remove_unused_btn = QtWidgets.QPushButton("Remove Unused Looks")
+ remove_unused_btn = QtWidgets.QPushButton(
+ "Remove Unused Looks", looks_widget
+ )
+ looks_layout = QtWidgets.QVBoxLayout(looks_widget)
looks_layout.addWidget(look_outliner)
looks_layout.addWidget(assign_selected)
looks_layout.addWidget(remove_unused_btn)
+ main_splitter.addWidget(asset_outliner)
+ main_splitter.addWidget(looks_widget)
+ main_splitter.setSizes([350, 200])
+
# Footer
- status = QtWidgets.QStatusBar()
+ status = QtWidgets.QStatusBar(self)
status.setSizeGripEnabled(False)
status.setFixedHeight(25)
- warn_layer = QtWidgets.QLabel("Current Layer is not "
- "defaultRenderLayer")
+ warn_layer = QtWidgets.QLabel(
+ "Current Layer is not defaultRenderLayer", self
+ )
warn_layer.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
warn_layer.setStyleSheet("color: #DD5555; font-weight: bold;")
warn_layer.setFixedHeight(25)
@@ -92,11 +103,6 @@ class App(QtWidgets.QWidget):
# Build up widgets
main_layout = QtWidgets.QVBoxLayout(self)
main_layout.setSpacing(0)
- main_splitter = QtWidgets.QSplitter()
- main_splitter.setStyleSheet("QSplitter{ border: 0px; }")
- main_splitter.addWidget(asset_outliner)
- main_splitter.addWidget(looks_widget)
- main_splitter.setSizes([350, 200])
main_layout.addWidget(main_splitter)
main_layout.addLayout(footer)
@@ -124,6 +130,8 @@ class App(QtWidgets.QWidget):
self.remove_unused = remove_unused_btn
self.assign_selected = assign_selected
+ self._first_show = True
+
def setup_connections(self):
"""Connect interactive widgets with actions"""
if self._connections_set_up:
@@ -147,11 +155,14 @@ class App(QtWidgets.QWidget):
def showEvent(self, event):
self.setup_connections()
- super(App, self).showEvent(event)
+ super(MayaLookAssignerWindow, self).showEvent(event)
+ if self._first_show:
+ self._first_show = False
+ self.setStyleSheet(style.load_stylesheet())
def closeEvent(self, event):
self.remove_connection()
- super(App, self).closeEvent(event)
+ super(MayaLookAssignerWindow, self).closeEvent(event)
def _on_renderlayer_switch(self, *args):
"""Callback that updates on Maya renderlayer switch"""
@@ -267,8 +278,7 @@ def show():
if widget.objectName() == "MayaWindow")
with qt_app_context():
- window = App(parent=mainwindow)
- window.setStyleSheet(style.load_stylesheet())
+ window = MayaLookAssignerWindow(parent=mainwindow)
window.show()
module.window = window
diff --git a/openpype/tools/mayalookassigner/commands.py b/openpype/tools/mayalookassigner/commands.py
index 96fc28243b..df72e41354 100644
--- a/openpype/tools/mayalookassigner/commands.py
+++ b/openpype/tools/mayalookassigner/commands.py
@@ -4,10 +4,11 @@ import os
import maya.cmds as cmds
-from openpype.hosts.maya.api import lib
-
from avalon import io, api
+from openpype.pipeline import remove_container
+from openpype.hosts.maya.api import lib
+
from .vray_proxies import get_alembic_ids_cache
log = logging.getLogger(__name__)
@@ -206,6 +207,6 @@ def remove_unused_looks():
for container in unused:
log.info("Removing unused look container: %s", container['objectName'])
- api.remove(container)
+ remove_container(container)
log.info("Finished removing unused looks. (see log for details)")
diff --git a/openpype/tools/mayalookassigner/models.py b/openpype/tools/mayalookassigner/models.py
index 386b7d7e1e..77a3c8a590 100644
--- a/openpype/tools/mayalookassigner/models.py
+++ b/openpype/tools/mayalookassigner/models.py
@@ -3,14 +3,19 @@ from collections import defaultdict
from Qt import QtCore
import qtawesome
-from avalon.style import colors
from openpype.tools.utils import models
+from openpype.style import get_default_entity_icon_color
class AssetModel(models.TreeModel):
Columns = ["label"]
+ def __init__(self, *args, **kwargs):
+ super(AssetModel, self).__init__(*args, **kwargs)
+
+ self._icon_color = get_default_entity_icon_color()
+
def add_items(self, items):
"""
Add items to model with needed data
@@ -65,8 +70,10 @@ class AssetModel(models.TreeModel):
node = index.internalPointer()
icon = node.get("icon")
if icon:
- return qtawesome.icon("fa.{0}".format(icon),
- color=colors.default)
+ return qtawesome.icon(
+ "fa.{0}".format(icon),
+ color=self._icon_color
+ )
return super(AssetModel, self).data(index, role)
diff --git a/openpype/tools/mayalookassigner/views.py b/openpype/tools/mayalookassigner/views.py
index 993023bb45..8e676ebc7f 100644
--- a/openpype/tools/mayalookassigner/views.py
+++ b/openpype/tools/mayalookassigner/views.py
@@ -1,9 +1,6 @@
from Qt import QtWidgets, QtCore
-DEFAULT_COLOR = "#fb9c15"
-
-
class View(QtWidgets.QTreeView):
data_changed = QtCore.Signal()
diff --git a/openpype/tools/mayalookassigner/vray_proxies.py b/openpype/tools/mayalookassigner/vray_proxies.py
index b22ec95a4d..6a9347449a 100644
--- a/openpype/tools/mayalookassigner/vray_proxies.py
+++ b/openpype/tools/mayalookassigner/vray_proxies.py
@@ -12,6 +12,12 @@ from maya import cmds
from avalon import io, api
+from openpype.pipeline import (
+ load_container,
+ loaders_from_representation,
+ discover_loader_plugins,
+ get_representation_path,
+)
from openpype.hosts.maya.api import lib
@@ -155,7 +161,7 @@ def get_look_relationships(version_id):
"name": "json"})
# Load relationships
- shader_relation = api.get_representation_path(json_representation)
+ shader_relation = get_representation_path(json_representation)
with open(shader_relation, "r") as f:
relationships = json.load(f)
@@ -193,8 +199,8 @@ def load_look(version_id):
log.info("Using look for the first time ...")
# Load file
- loaders = api.loaders_from_representation(api.discover(api.Loader),
- representation_id)
+ all_loaders = discover_loader_plugins()
+ loaders = loaders_from_representation(all_loaders, representation_id)
loader = next(
(i for i in loaders if i.__name__ == "LookLoader"), None)
if loader is None:
@@ -202,7 +208,7 @@ def load_look(version_id):
# Reference the look file
with lib.maintained_selection():
- container_node = api.load(loader, look_representation)
+ container_node = load_container(loader, look_representation)
# Get container members
shader_nodes = lib.get_container_members(container_node)
diff --git a/openpype/tools/mayalookassigner/widgets.py b/openpype/tools/mayalookassigner/widgets.py
index e546ee705d..10e573342a 100644
--- a/openpype/tools/mayalookassigner/widgets.py
+++ b/openpype/tools/mayalookassigner/widgets.py
@@ -14,7 +14,7 @@ from .models import (
LookModel
)
from . import commands
-from . import views
+from .views import View
from maya import cmds
@@ -24,25 +24,28 @@ class AssetOutliner(QtWidgets.QWidget):
selection_changed = QtCore.Signal()
def __init__(self, parent=None):
- QtWidgets.QWidget.__init__(self, parent)
+ super(AssetOutliner, self).__init__(parent)
- layout = QtWidgets.QVBoxLayout()
-
- title = QtWidgets.QLabel("Assets")
+ title = QtWidgets.QLabel("Assets", self)
title.setAlignment(QtCore.Qt.AlignCenter)
title.setStyleSheet("font-weight: bold; font-size: 12px")
model = AssetModel()
- view = views.View()
+ view = View(self)
view.setModel(model)
view.customContextMenuRequested.connect(self.right_mouse_menu)
view.setSortingEnabled(False)
view.setHeaderHidden(True)
view.setIndentation(10)
- from_all_asset_btn = QtWidgets.QPushButton("Get All Assets")
- from_selection_btn = QtWidgets.QPushButton("Get Assets From Selection")
+ from_all_asset_btn = QtWidgets.QPushButton(
+ "Get All Assets", self
+ )
+ from_selection_btn = QtWidgets.QPushButton(
+ "Get Assets From Selection", self
+ )
+ layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(title)
layout.addWidget(from_all_asset_btn)
layout.addWidget(from_selection_btn)
@@ -58,8 +61,6 @@ class AssetOutliner(QtWidgets.QWidget):
self.view = view
self.model = model
- self.setLayout(layout)
-
self.log = logging.getLogger(__name__)
def clear(self):
@@ -188,15 +189,10 @@ class LookOutliner(QtWidgets.QWidget):
menu_apply_action = QtCore.Signal()
def __init__(self, parent=None):
- QtWidgets.QWidget.__init__(self, parent)
-
- # look manager layout
- layout = QtWidgets.QVBoxLayout(self)
- layout.setContentsMargins(0, 0, 0, 0)
- layout.setSpacing(10)
+ super(LookOutliner, self).__init__(parent)
# Looks from database
- title = QtWidgets.QLabel("Looks")
+ title = QtWidgets.QLabel("Looks", self)
title.setAlignment(QtCore.Qt.AlignCenter)
title.setStyleSheet("font-weight: bold; font-size: 12px")
title.setAlignment(QtCore.Qt.AlignCenter)
@@ -207,13 +203,17 @@ class LookOutliner(QtWidgets.QWidget):
proxy = QtCore.QSortFilterProxyModel()
proxy.setSourceModel(model)
- view = views.View()
+ view = View(self)
view.setModel(proxy)
view.setMinimumHeight(180)
view.setToolTip("Use right mouse button menu for direct actions")
view.customContextMenuRequested.connect(self.right_mouse_menu)
view.sortByColumn(0, QtCore.Qt.AscendingOrder)
+ # look manager layout
+ layout = QtWidgets.QVBoxLayout(self)
+ layout.setContentsMargins(0, 0, 0, 0)
+ layout.setSpacing(10)
layout.addWidget(title)
layout.addWidget(view)
diff --git a/openpype/tools/project_manager/project_manager/multiselection_combobox.py b/openpype/tools/project_manager/project_manager/multiselection_combobox.py
index 890567de6d..f776831298 100644
--- a/openpype/tools/project_manager/project_manager/multiselection_combobox.py
+++ b/openpype/tools/project_manager/project_manager/multiselection_combobox.py
@@ -1,4 +1,4 @@
-from Qt import QtCore, QtGui, QtWidgets
+from Qt import QtCore, QtWidgets
class ComboItemDelegate(QtWidgets.QStyledItemDelegate):
diff --git a/openpype/tools/publisher/widgets/tasks_widget.py b/openpype/tools/publisher/widgets/tasks_widget.py
index 8a913b7114..aa239f6334 100644
--- a/openpype/tools/publisher/widgets/tasks_widget.py
+++ b/openpype/tools/publisher/widgets/tasks_widget.py
@@ -1,7 +1,7 @@
from Qt import QtCore, QtGui
from openpype.tools.utils.tasks_widget import TasksWidget, TASK_NAME_ROLE
-from openpype.tools.utils.lib import get_task_icon
+from openpype.tools.utils.lib import get_default_task_icon
class TasksModel(QtGui.QStandardItemModel):
@@ -120,7 +120,7 @@ class TasksModel(QtGui.QStandardItemModel):
item = QtGui.QStandardItem(task_name)
item.setData(task_name, TASK_NAME_ROLE)
if task_name:
- item.setData(get_task_icon(), QtCore.Qt.DecorationRole)
+ item.setData(get_default_task_icon(), QtCore.Qt.DecorationRole)
self._items_by_name[task_name] = item
new_items.append(item)
diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py
index cba60be355..7173ae751e 100644
--- a/openpype/tools/sceneinventory/model.py
+++ b/openpype/tools/sceneinventory/model.py
@@ -6,9 +6,11 @@ from collections import defaultdict
from Qt import QtCore, QtGui
import qtawesome
-from avalon import api, io, style, schema
-from avalon.lib import HeroVersionType
+from avalon import api, io, schema
+from openpype.pipeline import HeroVersionType
+from openpype.style import get_default_entity_icon_color
from openpype.tools.utils.models import TreeModel, Item
+from openpype.modules import ModulesManager
from .lib import (
get_site_icons,
@@ -16,8 +18,6 @@ from .lib import (
get_progress_for_repre
)
-from openpype.modules import ModulesManager
-
class InventoryModel(TreeModel):
"""The model for the inventory"""
@@ -38,6 +38,8 @@ class InventoryModel(TreeModel):
self._hierarchy_view = False
+ self._default_icon_color = get_default_entity_icon_color()
+
manager = ModulesManager()
sync_server = manager.modules_by_name["sync_server"]
self.sync_enabled = sync_server.enabled
@@ -131,7 +133,7 @@ class InventoryModel(TreeModel):
if role == QtCore.Qt.DecorationRole:
if index.column() == 0:
# Override color
- color = item.get("color", style.colors.default)
+ color = item.get("color", self._default_icon_color)
if item.get("isGroupNode"): # group-item
return qtawesome.icon("fa.folder", color=color)
if item.get("isNotSet"):
diff --git a/openpype/tools/sceneinventory/switch_dialog.py b/openpype/tools/sceneinventory/switch_dialog.py
index 93ea68beb4..0e7b1b759a 100644
--- a/openpype/tools/sceneinventory/switch_dialog.py
+++ b/openpype/tools/sceneinventory/switch_dialog.py
@@ -3,7 +3,12 @@ import logging
from Qt import QtWidgets, QtCore
import qtawesome
-from avalon import io, api, pipeline
+from avalon import io, pipeline
+from openpype.pipeline import (
+ discover_loader_plugins,
+ switch_container,
+ get_repres_contexts,
+)
from .widgets import (
ButtonWithMenu,
@@ -343,13 +348,13 @@ class SwitchAssetDialog(QtWidgets.QDialog):
def _get_loaders(self, repre_ids):
repre_contexts = None
if repre_ids:
- repre_contexts = pipeline.get_repres_contexts(repre_ids)
+ repre_contexts = get_repres_contexts(repre_ids)
if not repre_contexts:
return list()
available_loaders = []
- for loader_plugin in api.discover(api.Loader):
+ for loader_plugin in discover_loader_plugins():
# Skip loaders without switch method
if not hasattr(loader_plugin, "switch"):
continue
@@ -1352,7 +1357,7 @@ class SwitchAssetDialog(QtWidgets.QDialog):
repre_doc = repres_by_name[container_repre_name]
try:
- api.switch(container, repre_doc, loader)
+ switch_container(container, repre_doc, loader)
except Exception:
msg = (
"Couldn't switch asset."
diff --git a/openpype/tools/sceneinventory/view.py b/openpype/tools/sceneinventory/view.py
index 32c1883de6..c38390c614 100644
--- a/openpype/tools/sceneinventory/view.py
+++ b/openpype/tools/sceneinventory/view.py
@@ -5,9 +5,14 @@ from functools import partial
from Qt import QtWidgets, QtCore
import qtawesome
-from avalon import io, api, style
-from avalon.lib import HeroVersionType
+from avalon import io, api
+from openpype import style
+from openpype.pipeline import (
+ HeroVersionType,
+ update_container,
+ remove_container,
+)
from openpype.modules import ModulesManager
from openpype.tools.utils.lib import (
get_progress_for_repre,
@@ -195,7 +200,7 @@ class SceneInventoryView(QtWidgets.QTreeView):
version_name = version_name_by_id.get(version_id)
if version_name is not None:
try:
- api.update(item, version_name)
+ update_container(item, version_name)
except AssertionError:
self._show_version_error_dialog(
version_name, [item]
@@ -223,7 +228,7 @@ class SceneInventoryView(QtWidgets.QTreeView):
def _on_update_to_latest(items):
for item in items:
try:
- api.update(item, -1)
+ update_container(item, -1)
except AssertionError:
self._show_version_error_dialog(None, [item])
log.warning("Update failed", exc_info=True)
@@ -248,7 +253,7 @@ class SceneInventoryView(QtWidgets.QTreeView):
def _on_update_to_hero(items):
for item in items:
try:
- api.update(item, HeroVersionType(-1))
+ update_container(item, HeroVersionType(-1))
except AssertionError:
self._show_version_error_dialog('hero', [item])
log.warning("Update failed", exc_info=True)
@@ -727,7 +732,7 @@ class SceneInventoryView(QtWidgets.QTreeView):
version = versions_by_label[label]
for item in items:
try:
- api.update(item, version)
+ update_container(item, version)
except AssertionError:
self._show_version_error_dialog(version, [item])
log.warning("Update failed", exc_info=True)
@@ -758,7 +763,7 @@ class SceneInventoryView(QtWidgets.QTreeView):
return
for item in items:
- api.remove(item)
+ remove_container(item)
self.data_changed.emit()
def _show_version_error_dialog(self, version, items):
@@ -828,7 +833,7 @@ class SceneInventoryView(QtWidgets.QTreeView):
# Trigger update to latest
for item in outdated_items:
try:
- api.update(item, -1)
+ update_container(item, -1)
except AssertionError:
self._show_version_error_dialog(None, [item])
log.warning("Update failed", exc_info=True)
diff --git a/openpype/tools/sceneinventory/window.py b/openpype/tools/sceneinventory/window.py
index 83e4435015..b40fbb69e4 100644
--- a/openpype/tools/sceneinventory/window.py
+++ b/openpype/tools/sceneinventory/window.py
@@ -61,7 +61,7 @@ class SceneInventoryWindow(QtWidgets.QDialog):
icon = qtawesome.icon("fa.refresh", color="white")
refresh_button = QtWidgets.QPushButton(self)
- update_all_button.setToolTip("Refresh")
+ refresh_button.setToolTip("Refresh")
refresh_button.setIcon(icon)
control_layout = QtWidgets.QHBoxLayout()
diff --git a/openpype/tools/standalonepublish/widgets/model_asset.py b/openpype/tools/standalonepublish/widgets/model_asset.py
index 6d764eff9f..a7316a2aa7 100644
--- a/openpype/tools/standalonepublish/widgets/model_asset.py
+++ b/openpype/tools/standalonepublish/widgets/model_asset.py
@@ -1,10 +1,15 @@
import logging
import collections
+
from Qt import QtCore, QtGui
import qtawesome
-from . import TreeModel, Node
-from avalon import style
+from openpype.style import (
+ get_default_entity_icon_color,
+ get_deprecated_entity_font_color,
+)
+
+from . import TreeModel, Node
log = logging.getLogger(__name__)
@@ -49,6 +54,14 @@ class AssetModel(TreeModel):
def __init__(self, dbcon, parent=None):
super(AssetModel, self).__init__(parent=parent)
self.dbcon = dbcon
+
+ self._default_asset_icon_color = QtGui.QColor(
+ get_default_entity_icon_color()
+ )
+ self._deprecated_asset_font_color = QtGui.QColor(
+ get_deprecated_entity_font_color()
+ )
+
self.refresh()
def _add_hierarchy(self, assets, parent=None, silos=None):
@@ -163,7 +176,7 @@ class AssetModel(TreeModel):
icon = data.get("icon", None)
if icon is None and node.get("type") == "silo":
icon = "database"
- color = data.get("color", style.colors.default)
+ color = data.get("color", self._default_asset_icon_color)
if icon is None:
# Use default icons if no custom one is specified.
@@ -189,7 +202,7 @@ class AssetModel(TreeModel):
if role == QtCore.Qt.ForegroundRole: # font color
if "deprecated" in node.get("tags", []):
- return QtGui.QColor(style.colors.light).darker(250)
+ return QtGui.QColor(self._deprecated_asset_font_color)
if role == self.ObjectIdRole:
return node.get("_id", None)
diff --git a/openpype/tools/standalonepublish/widgets/model_tasks_template.py b/openpype/tools/standalonepublish/widgets/model_tasks_template.py
index 1f36eaa39d..648f7ed479 100644
--- a/openpype/tools/standalonepublish/widgets/model_tasks_template.py
+++ b/openpype/tools/standalonepublish/widgets/model_tasks_template.py
@@ -1,7 +1,9 @@
from Qt import QtCore
import qtawesome
+
+from openpype.style import get_default_entity_icon_color
+
from . import Node, TreeModel
-from avalon import style
class TasksTemplateModel(TreeModel):
@@ -14,7 +16,7 @@ class TasksTemplateModel(TreeModel):
self.selectable = selectable
self.icon = qtawesome.icon(
'fa.calendar-check-o',
- color=style.colors.default
+ color=get_default_entity_icon_color()
)
def set_tasks(self, tasks):
diff --git a/openpype/tools/standalonepublish/widgets/widget_asset.py b/openpype/tools/standalonepublish/widgets/widget_asset.py
index d929f227f9..e6b74f8f82 100644
--- a/openpype/tools/standalonepublish/widgets/widget_asset.py
+++ b/openpype/tools/standalonepublish/widgets/widget_asset.py
@@ -4,7 +4,7 @@ import qtawesome
from openpype.tools.utils import PlaceholderLineEdit
-from avalon import style
+from openpype.style import get_default_tools_icon_color
from . import RecursiveSortFilterProxyModel, AssetModel
from . import TasksTemplateModel, DeselectableTreeView
@@ -165,7 +165,9 @@ class AssetWidget(QtWidgets.QWidget):
# Header
header = QtWidgets.QHBoxLayout()
- icon = qtawesome.icon("fa.refresh", color=style.colors.light)
+ icon = qtawesome.icon(
+ "fa.refresh", color=get_default_tools_icon_color()
+ )
refresh = QtWidgets.QPushButton(icon, "")
refresh.setToolTip("Refresh items")
diff --git a/openpype/tools/utils/__init__.py b/openpype/tools/utils/__init__.py
index 6ab9e75b52..ea1133c442 100644
--- a/openpype/tools/utils/__init__.py
+++ b/openpype/tools/utils/__init__.py
@@ -42,6 +42,7 @@ __all__ = (
"set_style_property",
"DynamicQThread",
"qt_app_context",
+ "get_asset_icon",
"RecursiveSortFilterProxyModel",
)
diff --git a/openpype/tools/utils/assets_widget.py b/openpype/tools/utils/assets_widget.py
index 4c77b81c0e..3d4efcdd4d 100644
--- a/openpype/tools/utils/assets_widget.py
+++ b/openpype/tools/utils/assets_widget.py
@@ -5,9 +5,10 @@ import Qt
from Qt import QtWidgets, QtCore, QtGui
import qtawesome
-from avalon import style
-
-from openpype.style import get_objected_colors
+from openpype.style import (
+ get_objected_colors,
+ get_default_tools_icon_color,
+)
from openpype.tools.flickcharm import FlickCharm
from .views import (
@@ -512,7 +513,7 @@ class AssetModel(QtGui.QStandardItemModel):
item.setData(asset_label, ASSET_LABEL_ROLE)
has_children = item.rowCount() > 0
- icon = get_asset_icon(asset_data, has_children)
+ icon = get_asset_icon(asset_doc, has_children)
item.setData(icon, QtCore.Qt.DecorationRole)
def _threaded_fetch(self):
@@ -589,7 +590,7 @@ class AssetsWidget(QtWidgets.QWidget):
view.setModel(proxy)
current_asset_icon = qtawesome.icon(
- "fa.arrow-down", color=style.colors.light
+ "fa.arrow-down", color=get_default_tools_icon_color()
)
current_asset_btn = QtWidgets.QPushButton(self)
current_asset_btn.setIcon(current_asset_icon)
@@ -597,7 +598,9 @@ class AssetsWidget(QtWidgets.QWidget):
# Hide by default
current_asset_btn.setVisible(False)
- refresh_icon = qtawesome.icon("fa.refresh", color=style.colors.light)
+ refresh_icon = qtawesome.icon(
+ "fa.refresh", color=get_default_tools_icon_color()
+ )
refresh_btn = QtWidgets.QPushButton(self)
refresh_btn.setIcon(refresh_icon)
refresh_btn.setToolTip("Refresh items")
diff --git a/openpype/tools/utils/delegates.py b/openpype/tools/utils/delegates.py
index 4ec6079bb7..d3718b1734 100644
--- a/openpype/tools/utils/delegates.py
+++ b/openpype/tools/utils/delegates.py
@@ -6,7 +6,7 @@ import numbers
import Qt
from Qt import QtWidgets, QtGui, QtCore
-from avalon.lib import HeroVersionType
+from openpype.pipeline import HeroVersionType
from .models import TreeModel
from . import lib
diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py
index 6ce9e818d9..2d9733ec94 100644
--- a/openpype/tools/utils/host_tools.py
+++ b/openpype/tools/utils/host_tools.py
@@ -224,20 +224,18 @@ class HostToolsHelper:
def get_look_assigner_tool(self, parent):
"""Create, cache and return look assigner tool window."""
if self._look_assigner_tool is None:
- import mayalookassigner
+ from openpype.tools.mayalookassigner import MayaLookAssignerWindow
- mayalookassigner_window = mayalookassigner.App(parent)
+ mayalookassigner_window = MayaLookAssignerWindow(parent)
self._look_assigner_tool = mayalookassigner_window
return self._look_assigner_tool
def show_look_assigner(self, parent=None):
"""Look manager is Maya specific tool for look management."""
- from avalon import style
with qt_app_context():
look_assigner_tool = self.get_look_assigner_tool(parent)
look_assigner_tool.show()
- look_assigner_tool.setStyleSheet(style.load_stylesheet())
def get_experimental_tools_dialog(self, parent=None):
"""Dialog of experimental tools.
diff --git a/openpype/tools/utils/lib.py b/openpype/tools/utils/lib.py
index 042ceaab88..93b156bef8 100644
--- a/openpype/tools/utils/lib.py
+++ b/openpype/tools/utils/lib.py
@@ -7,8 +7,8 @@ from Qt import QtWidgets, QtCore, QtGui
import qtawesome
import avalon.api
-from avalon import style
+from openpype.style import get_default_entity_icon_color
from openpype.api import (
get_project_settings,
Logger
@@ -92,10 +92,6 @@ def qt_app_context():
yield app
-# Backwards compatibility
-application = qt_app_context
-
-
class SharedObjects:
jobs = {}
icons = {}
@@ -126,30 +122,74 @@ def get_qta_icon_by_name_and_color(icon_name, icon_color):
return icon
+def get_project_icon(project_doc):
+ if project_doc:
+ icon_name = project_doc.get("data", {}).get("icon")
+ icon = get_qta_icon_by_name_and_color(icon_name, "white")
+ if icon:
+ return icon
+
+ return get_qta_icon_by_name_and_color(
+ "fa.map", get_default_entity_icon_color()
+ )
+
+
+def get_asset_icon_name(asset_doc, has_children=True):
+ icon_name = asset_doc["data"].get("icon")
+ if icon_name:
+ return icon_name
+
+ if has_children:
+ return "folder"
+ return "folder-o"
+
+
+def get_asset_icon_color(asset_doc):
+ icon_color = asset_doc["data"].get("color")
+ if icon_color:
+ return icon_color
+ return get_default_entity_icon_color()
+
+
def get_asset_icon(asset_doc, has_children=False):
- asset_data = asset_doc.get("data") or {}
- icon_color = asset_data.get("color") or style.colors.default
- icon_name = asset_data.get("icon")
- if not icon_name:
- # Use default icons if no custom one is specified.
- # If it has children show a full folder, otherwise
- # show an open folder
- if has_children:
- icon_name = "folder"
- else:
- icon_name = "folder-o"
+ 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_task_icon():
- """Get icon for a task.
+def get_default_task_icon(color=None):
+ if color is None:
+ color = get_default_entity_icon_color()
+ return get_qta_icon_by_name_and_color("fa.male", color)
- TODO: Get task icon based on data in database.
+
+def get_task_icon(project_doc, asset_doc, task_name):
+ """Get icon for a task.
Icon should be defined by task type which is stored on project.
"""
- return get_qta_icon_by_name_and_color("fa.male", style.colors.default)
+
+ color = get_default_entity_icon_color()
+
+ tasks_info = asset_doc.get("data", {}).get("tasks") or {}
+ task_info = tasks_info.get(task_name) or {}
+ task_icon = task_info.get("icon")
+ if task_icon:
+ icon = get_qta_icon_by_name_and_color(task_icon, color)
+ if icon is not None:
+ return icon
+
+ task_type = task_info.get("type")
+ task_types = project_doc["config"]["tasks"]
+
+ task_type_info = task_types.get(task_type) or {}
+ task_type_icon = task_type_info.get("icon")
+ if task_type_icon:
+ icon = get_qta_icon_by_name_and_color(task_icon, color)
+ if icon is not None:
+ return icon
+ return get_default_task_icon(color)
def schedule(func, time, channel="default"):
@@ -412,6 +452,7 @@ class GroupsConfig:
def __init__(self, dbcon):
self.dbcon = dbcon
self.groups = {}
+ self._default_group_color = get_default_entity_icon_color()
@classmethod
def default_group_config(cls):
@@ -419,7 +460,7 @@ class GroupsConfig:
cls._default_group_config = {
"icon": qtawesome.icon(
"fa.object-group",
- color=style.colors.default
+ color=get_default_entity_icon_color()
),
"order": 0
}
@@ -453,7 +494,7 @@ class GroupsConfig:
for config in group_configs:
name = config["name"]
icon = "fa." + config.get("icon", "object-group")
- color = config.get("color", style.colors.default)
+ color = config.get("color", self._default_group_color)
order = float(config.get("order", 0))
self.groups[name] = {
diff --git a/openpype/tools/utils/tasks_widget.py b/openpype/tools/utils/tasks_widget.py
index 7619f59974..eab183d5f3 100644
--- a/openpype/tools/utils/tasks_widget.py
+++ b/openpype/tools/utils/tasks_widget.py
@@ -1,7 +1,8 @@
from Qt import QtWidgets, QtCore, QtGui
import qtawesome
-from avalon import style
+from openpype.style import get_disabled_entity_icon_color
+from openpype.tools.utils.lib import get_task_icon
from .views import DeselectableTreeView
@@ -21,53 +22,35 @@ class TasksModel(QtGui.QStandardItemModel):
self.setHeaderData(
0, QtCore.Qt.Horizontal, "Tasks", QtCore.Qt.DisplayRole
)
- self._default_icon = qtawesome.icon(
- "fa.male",
- color=style.colors.default
- )
+
self._no_tasks_icon = qtawesome.icon(
"fa.exclamation-circle",
- color=style.colors.mid
+ color=get_disabled_entity_icon_color()
)
self._cached_icons = {}
- self._project_task_types = {}
+ 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.dbcon.Session.get("AVALON_PROJECT"):
+ if self._get_current_project():
return True
return False
def refresh(self):
- self._refresh_task_types()
+ self._refresh_project_doc()
self.set_asset_id(self._last_asset_id)
- def _refresh_task_types(self):
+ def _refresh_project_doc(self):
# Get the project configured icons from database
- task_types = {}
+ project_doc = {}
if self._context_is_valid():
- project = self.dbcon.find_one(
- {"type": "project"},
- {"config.tasks"}
- )
- task_types = project["config"].get("tasks") or task_types
- self._project_task_types = task_types
+ project_doc = self.dbcon.find_one({"type": "project"})
- def _try_get_awesome_icon(self, icon_name):
- icon = None
- if icon_name:
- try:
- icon = qtawesome.icon(
- "fa.{}".format(icon_name),
- color=style.colors.default
- )
-
- except Exception:
- pass
- return icon
+ self._loaded_project_name = self._get_current_project()
+ self._project_doc = project_doc
def headerData(self, section, orientation, role=None):
if role is None:
@@ -82,28 +65,8 @@ class TasksModel(QtGui.QStandardItemModel):
return super(TasksModel, self).headerData(section, orientation, role)
- def _get_icon(self, task_icon, task_type_icon):
- if task_icon in self._cached_icons:
- return self._cached_icons[task_icon]
-
- icon = self._try_get_awesome_icon(task_icon)
- if icon is not None:
- self._cached_icons[task_icon] = icon
- return icon
-
- if task_type_icon in self._cached_icons:
- icon = self._cached_icons[task_type_icon]
- self._cached_icons[task_icon] = icon
- return icon
-
- icon = self._try_get_awesome_icon(task_type_icon)
- if icon is None:
- icon = self._default_icon
-
- self._cached_icons[task_icon] = icon
- self._cached_icons[task_type_icon] = icon
-
- return icon
+ def _get_current_project(self):
+ return self.dbcon.Session.get("AVALON_PROJECT")
def set_asset_id(self, asset_id):
asset_doc = None
@@ -128,6 +91,9 @@ class TasksModel(QtGui.QStandardItemModel):
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:
@@ -138,13 +104,11 @@ class TasksModel(QtGui.QStandardItemModel):
root_item.removeRows(0, root_item.rowCount())
items = []
+
for task_name, task_info in asset_tasks.items():
- task_icon = task_info.get("icon")
task_type = task_info.get("type")
task_order = task_info.get("order")
- task_type_info = self._project_task_types.get(task_type) or {}
- task_type_icon = task_type_info.get("icon")
- icon = self._get_icon(task_icon, task_type_icon)
+ icon = get_task_icon(self._project_doc, asset_doc, task_name)
task_assignees = set()
assignees_data = task_info.get("assignees") or []
diff --git a/openpype/tools/utils/views.py b/openpype/tools/utils/views.py
index 97aaf622a4..a2f1f15b95 100644
--- a/openpype/tools/utils/views.py
+++ b/openpype/tools/utils/views.py
@@ -1,5 +1,5 @@
import os
-from avalon import style
+from openpype.resources import get_image_path
from Qt import QtWidgets, QtCore, QtGui, QtSvg
@@ -24,11 +24,8 @@ class TreeViewSpinner(QtWidgets.QTreeView):
def __init__(self, parent=None):
super(TreeViewSpinner, self).__init__(parent=parent)
- loading_image_path = os.path.join(
- os.path.dirname(os.path.abspath(style.__file__)),
- "svg",
- "spinner-200.svg"
- )
+ loading_image_path = get_image_path("spinner-200.svg")
+
self.spinner = QtSvg.QSvgRenderer(loading_image_path)
self.is_loading = False
diff --git a/openpype/tools/workfiles/app.py b/openpype/tools/workfiles/app.py
index aece7bfb4f..63958ac57b 100644
--- a/openpype/tools/workfiles/app.py
+++ b/openpype/tools/workfiles/app.py
@@ -9,10 +9,9 @@ import datetime
import Qt
from Qt import QtWidgets, QtCore
-from avalon import io, api, pipeline
+from avalon import io, api
from openpype import style
-from openpype.pipeline.lib import BeforeWorkfileSave
from openpype.tools.utils.lib import (
qt_app_context
)
@@ -21,6 +20,7 @@ from openpype.tools.utils.assets_widget import SingleSelectAssetsWidget
from openpype.tools.utils.tasks_widget import TasksWidget
from openpype.tools.utils.delegates import PrettyTimeDelegate
from openpype.lib import (
+ emit_event,
Anatomy,
get_workfile_doc,
create_workfile_doc,
@@ -823,7 +823,11 @@ class FilesWidget(QtWidgets.QWidget):
return
# Trigger before save event
- BeforeWorkfileSave.emit(work_filename, self._workdir_path)
+ emit_event(
+ "workfile.save.before",
+ {"filename": work_filename, "workdir_path": self._workdir_path},
+ source="workfiles.tool"
+ )
# Make sure workfiles root is updated
# - this triggers 'workio.work_root(...)' which may change value of
@@ -853,7 +857,11 @@ class FilesWidget(QtWidgets.QWidget):
api.Session["AVALON_PROJECT"]
)
# Trigger after save events
- pipeline.emit("after.workfile.save", [filepath])
+ emit_event(
+ "workfile.save.after",
+ {"filename": work_filename, "workdir_path": self._workdir_path},
+ source="workfiles.tool"
+ )
self.workfile_created.emit(filepath)
# Refresh files model
diff --git a/openpype/tools/workfiles/model.py b/openpype/tools/workfiles/model.py
index b3cf5063e7..e9184842fc 100644
--- a/openpype/tools/workfiles/model.py
+++ b/openpype/tools/workfiles/model.py
@@ -4,7 +4,11 @@ import logging
from Qt import QtCore
import qtawesome
-from avalon import style
+from openpype.style import (
+ get_default_entity_icon_color,
+ get_disabled_entity_icon_color,
+)
+
from openpype.tools.utils.models import TreeModel, Item
log = logging.getLogger(__name__)
@@ -25,7 +29,10 @@ class FilesModel(TreeModel):
self._root = None
self._file_extensions = file_extensions
self._icons = {
- "file": qtawesome.icon("fa.file-o", color=style.colors.default)
+ "file": qtawesome.icon(
+ "fa.file-o",
+ color=get_default_entity_icon_color()
+ )
}
def set_root(self, root):
@@ -64,7 +71,10 @@ class FilesModel(TreeModel):
"date": None,
"filepath": None,
"enabled": False,
- "icon": qtawesome.icon("fa.times", color=style.colors.mid)
+ "icon": qtawesome.icon(
+ "fa.times",
+ color=get_disabled_entity_icon_color()
+ )
})
self.add_child(item)
self.endResetModel()
diff --git a/openpype/version.py b/openpype/version.py
index d4af8d760f..17e514642d 100644
--- a/openpype/version.py
+++ b/openpype/version.py
@@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
"""Package declaring Pype version."""
-__version__ = "3.9.0-nightly.8"
+__version__ = "3.9.1-nightly.1"
diff --git a/pyproject.toml b/pyproject.toml
index fe681266ca..128d1cd615 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "OpenPype"
-version = "3.9.0-nightly.8" # OpenPype
+version = "3.9.1-nightly.1" # OpenPype
description = "Open VFX and Animation pipeline with support."
authors = ["OpenPype Team "]
license = "MIT License"
diff --git a/repos/avalon-core b/repos/avalon-core
index ffe9e910f1..7753d15507 160000
--- a/repos/avalon-core
+++ b/repos/avalon-core
@@ -1 +1 @@
-Subproject commit ffe9e910f1f382e222d457d8e4a8426c41ed43ae
+Subproject commit 7753d15507afadc143b7d49db8fcfaa6a29fed91
diff --git a/website/docs/artist_hosts_aftereffects.md b/website/docs/artist_hosts_aftereffects.md
index f9ef40fe1a..a9660bd13c 100644
--- a/website/docs/artist_hosts_aftereffects.md
+++ b/website/docs/artist_hosts_aftereffects.md
@@ -15,7 +15,7 @@ sidebar_label: AfterEffects
## Setup
-To install the extension, download, install [Anastasyi's Extension Manager](https://install.anastasiy.com/). Open Anastasyi's Extension Manager and select AfterEffects in menu. Then go to `{path to pype}/repos/avalon-core/avalon/aftereffects/extension.zxp`.
+To install the extension, download, install [Anastasyi's Extension Manager](https://install.anastasiy.com/). Open Anastasyi's Extension Manager and select AfterEffects in menu. Then go to `{path to pype}hosts/aftereffects/api/extension.zxp`.
Drag extension.zxp and drop it to Anastasyi's Extension Manager. The extension will install itself.
diff --git a/website/docs/artist_hosts_photoshop.md b/website/docs/artist_hosts_photoshop.md
index 16539bcf79..b2b5fd58da 100644
--- a/website/docs/artist_hosts_photoshop.md
+++ b/website/docs/artist_hosts_photoshop.md
@@ -14,7 +14,7 @@ sidebar_label: Photoshop
## Setup
-To install the extension, download, install [Anastasyi's Extension Manager](https://install.anastasiy.com/). Open Anastasyi's Extension Manager and select Photoshop in menu. Then go to `{path to pype}/repos/avalon-core/avalon/photoshop/extension.zxp`. Drag extension.zxp and drop it to Anastasyi's Extension Manager. The extension will install itself.
+To install the extension, download, install [Anastasyi's Extension Manager](https://install.anastasiy.com/). Open Anastasyi's Extension Manager and select Photoshop in menu. Then go to `{path to pype}hosts/photoshop/api/extension.zxp`. Drag extension.zxp and drop it to Anastasyi's Extension Manager. The extension will install itself.
## Usage