diff --git a/openpype/__init__.py b/openpype/__init__.py
index 7fc7e63e61..810664707a 100644
--- a/openpype/__init__.py
+++ b/openpype/__init__.py
@@ -1,102 +1,5 @@
-# -*- coding: utf-8 -*-
-"""Pype module."""
import os
-import platform
-import logging
-
-from .settings import get_project_settings
-from .lib import (
- Anatomy,
- filter_pyblish_plugins,
- change_timer_to_current_context,
- register_event_callback,
-)
-
-log = logging.getLogger(__name__)
PACKAGE_DIR = os.path.dirname(os.path.abspath(__file__))
PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins")
-
-# Global plugin paths
-PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish")
-LOAD_PATH = os.path.join(PLUGINS_DIR, "load")
-
-
-def install():
- """Install OpenPype to Avalon."""
- import avalon.api
- import pyblish.api
- from pyblish.lib import MessageHandler
- from openpype.modules import load_modules
- from openpype.pipeline import (
- register_loader_plugin_path,
- register_inventory_action,
- register_creator_plugin_path,
- )
-
- # Make sure modules are loaded
- load_modules()
-
- def modified_emit(obj, record):
- """Method replacing `emit` in Pyblish's MessageHandler."""
- record.msg = record.getMessage()
- obj.records.append(record)
-
- MessageHandler.emit = modified_emit
-
- log.info("Registering global plug-ins..")
- pyblish.api.register_plugin_path(PUBLISH_PATH)
- pyblish.api.register_discovery_filter(filter_pyblish_plugins)
- register_loader_plugin_path(LOAD_PATH)
-
- project_name = os.environ.get("AVALON_PROJECT")
-
- # Register studio specific plugins
- if project_name:
- anatomy = Anatomy(project_name)
- anatomy.set_root_environments()
- avalon.api.register_root(anatomy.roots)
-
- project_settings = get_project_settings(project_name)
- platform_name = platform.system().lower()
- project_plugins = (
- project_settings
- .get("global", {})
- .get("project_plugins", {})
- .get(platform_name)
- ) or []
- for path in project_plugins:
- try:
- path = str(path.format(**os.environ))
- except KeyError:
- pass
-
- if not path or not os.path.exists(path):
- continue
-
- pyblish.api.register_plugin_path(path)
- register_loader_plugin_path(path)
- register_creator_plugin_path(path)
- register_inventory_action(path)
-
- # apply monkey patched discover to original one
- log.info("Patching discovery")
-
- register_event_callback("taskChanged", _on_task_change)
-
-
-def _on_task_change():
- change_timer_to_current_context()
-
-
-def uninstall():
- """Uninstall Pype from Avalon."""
- import pyblish.api
- from openpype.pipeline import deregister_loader_plugin_path
-
- log.info("Deregistering global plug-ins..")
- pyblish.api.deregister_plugin_path(PUBLISH_PATH)
- pyblish.api.deregister_discovery_filter(filter_pyblish_plugins)
- deregister_loader_plugin_path(LOAD_PATH)
- log.info("Global plug-ins unregistred")
diff --git a/openpype/hosts/aftereffects/api/lib.py b/openpype/hosts/aftereffects/api/lib.py
index dac6b5d28f..ce4cbf09af 100644
--- a/openpype/hosts/aftereffects/api/lib.py
+++ b/openpype/hosts/aftereffects/api/lib.py
@@ -6,6 +6,7 @@ import logging
from Qt import QtWidgets
+from openpype.pipeline import install_host
from openpype.lib.remote_publish import headless_publish
from openpype.tools.utils import host_tools
@@ -22,10 +23,9 @@ def safe_excepthook(*args):
def main(*subprocess_args):
sys.excepthook = safe_excepthook
- import avalon.api
from openpype.hosts.aftereffects import api
- avalon.api.install(api)
+ install_host(api)
os.environ["OPENPYPE_LOG_NO_COLORS"] = "False"
app = QtWidgets.QApplication([])
diff --git a/openpype/hosts/aftereffects/api/pipeline.py b/openpype/hosts/aftereffects/api/pipeline.py
index 94bc369856..3ed2de0e9d 100644
--- a/openpype/hosts/aftereffects/api/pipeline.py
+++ b/openpype/hosts/aftereffects/api/pipeline.py
@@ -15,6 +15,7 @@ from openpype.pipeline import (
deregister_loader_plugin_path,
deregister_creator_plugin_path,
AVALON_CONTAINER_ID,
+ registered_host,
)
import openpype.hosts.aftereffects
from openpype.lib import register_event_callback
@@ -37,24 +38,9 @@ def check_inventory():
if not lib.any_outdated():
return
- host = pyblish.api.registered_host()
- outdated_containers = []
- for container in host.ls():
- representation = container['representation']
- representation_doc = io.find_one(
- {
- "_id": ObjectId(representation),
- "type": "representation"
- },
- projection={"parent": True}
- )
- if representation_doc and not lib.is_latest(representation_doc):
- outdated_containers.append(container)
-
# Warn about outdated containers.
print("Starting new QApplication..")
app = QtWidgets.QApplication(sys.argv)
-
message_box = QtWidgets.QMessageBox()
message_box.setIcon(QtWidgets.QMessageBox.Warning)
msg = "There are outdated containers in the scene."
diff --git a/openpype/hosts/blender/api/pipeline.py b/openpype/hosts/blender/api/pipeline.py
index b9ec2cfea4..0ea579970e 100644
--- a/openpype/hosts/blender/api/pipeline.py
+++ b/openpype/hosts/blender/api/pipeline.py
@@ -19,6 +19,7 @@ from openpype.pipeline import (
deregister_loader_plugin_path,
deregister_creator_plugin_path,
AVALON_CONTAINER_ID,
+ uninstall_host,
)
from openpype.api import Logger
from openpype.lib import (
@@ -209,11 +210,10 @@ def reload_pipeline(*args):
"""
- avalon.api.uninstall()
+ uninstall_host()
for module in (
"avalon.io",
- "avalon.lib",
"avalon.pipeline",
"avalon.api",
):
diff --git a/openpype/hosts/blender/blender_addon/startup/init.py b/openpype/hosts/blender/blender_addon/startup/init.py
index e43373bc6c..13a4b8a7a1 100644
--- a/openpype/hosts/blender/blender_addon/startup/init.py
+++ b/openpype/hosts/blender/blender_addon/startup/init.py
@@ -1,4 +1,4 @@
-from avalon import pipeline
+from openpype.pipeline import install_host
from openpype.hosts.blender import api
-pipeline.install(api)
+install_host(api)
diff --git a/openpype/hosts/celaction/api/cli.py b/openpype/hosts/celaction/api/cli.py
index bc1e3eaf89..8c7b3a2e74 100644
--- a/openpype/hosts/celaction/api/cli.py
+++ b/openpype/hosts/celaction/api/cli.py
@@ -3,8 +3,6 @@ import sys
import copy
import argparse
-from avalon import io
-
import pyblish.api
import pyblish.util
@@ -13,6 +11,8 @@ import openpype
import openpype.hosts.celaction
from openpype.hosts.celaction import api as celaction
from openpype.tools.utils import host_tools
+from openpype.pipeline import install_openpype_plugins
+
log = Logger().get_logger("Celaction_cli_publisher")
@@ -21,9 +21,6 @@ publish_host = "celaction"
HOST_DIR = os.path.dirname(os.path.abspath(openpype.hosts.celaction.__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")
-INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory")
def cli():
@@ -74,7 +71,7 @@ def main():
_prepare_publish_environments()
# Registers pype's Global pyblish plugins
- openpype.install()
+ install_openpype_plugins()
if os.path.exists(PUBLISH_PATH):
log.info(f"Registering path: {PUBLISH_PATH}")
diff --git a/openpype/hosts/flame/startup/openpype_in_flame.py b/openpype/hosts/flame/startup/openpype_in_flame.py
index 931c5a1b79..f2ac23b19e 100644
--- a/openpype/hosts/flame/startup/openpype_in_flame.py
+++ b/openpype/hosts/flame/startup/openpype_in_flame.py
@@ -3,18 +3,19 @@ import sys
from Qt import QtWidgets
from pprint import pformat
import atexit
-import openpype
-import avalon
+
import openpype.hosts.flame.api as opfapi
+from openpype.pipeline import (
+ install_host,
+ registered_host,
+)
def openpype_install():
"""Registering OpenPype in context
"""
- openpype.install()
- avalon.api.install(opfapi)
- print("Avalon registered hosts: {}".format(
- avalon.api.registered_host()))
+ install_host(opfapi)
+ print("Registered host: {}".format(registered_host()))
# Exception handler
diff --git a/openpype/hosts/fusion/scripts/fusion_switch_shot.py b/openpype/hosts/fusion/scripts/fusion_switch_shot.py
index ca7efb9136..ca8e5c9e37 100644
--- a/openpype/hosts/fusion/scripts/fusion_switch_shot.py
+++ b/openpype/hosts/fusion/scripts/fusion_switch_shot.py
@@ -7,6 +7,10 @@ import logging
import avalon.api
from avalon import io
+from openpype.pipeline import (
+ install_host,
+ registered_host,
+)
from openpype.lib import version_up
from openpype.hosts.fusion import api
from openpype.hosts.fusion.api import lib
@@ -218,7 +222,7 @@ def switch(asset_name, filepath=None, new=True):
assert current_comp is not None, (
"Fusion could not load '{}'").format(filepath)
- host = avalon.api.registered_host()
+ host = registered_host()
containers = list(host.ls())
assert containers, "Nothing to update"
@@ -279,7 +283,7 @@ if __name__ == '__main__':
args, unknown = parser.parse_args()
- avalon.api.install(api)
+ install_host(api)
switch(args.asset_name, args.file_path)
sys.exit(0)
diff --git a/openpype/hosts/fusion/utility_scripts/__OpenPype_Menu__.py b/openpype/hosts/fusion/utility_scripts/__OpenPype_Menu__.py
index 4b5e8f91a0..de8fc4b3b4 100644
--- a/openpype/hosts/fusion/utility_scripts/__OpenPype_Menu__.py
+++ b/openpype/hosts/fusion/utility_scripts/__OpenPype_Menu__.py
@@ -1,24 +1,23 @@
import os
import sys
-import openpype
from openpype.api import Logger
+from openpype.pipeline import (
+ install_host,
+ registered_host,
+)
log = Logger().get_logger(__name__)
def main(env):
- import avalon.api
from openpype.hosts.fusion import api
from openpype.hosts.fusion.api import menu
- # Registers pype's Global pyblish plugins
- openpype.install()
-
# activate resolve from pype
- avalon.api.install(api)
+ install_host(api)
- log.info(f"Avalon registered hosts: {avalon.api.registered_host()}")
+ log.info(f"Registered host: {registered_host()}")
menu.launch_openpype_menu()
diff --git a/openpype/hosts/fusion/utility_scripts/switch_ui.py b/openpype/hosts/fusion/utility_scripts/switch_ui.py
index d9eeae25ea..37306c7a2a 100644
--- a/openpype/hosts/fusion/utility_scripts/switch_ui.py
+++ b/openpype/hosts/fusion/utility_scripts/switch_ui.py
@@ -1,14 +1,15 @@
import os
+import sys
import glob
import logging
from Qt import QtWidgets, QtCore
-import avalon.api
from avalon import io
import qtawesome as qta
from openpype import style
+from openpype.pipeline import install_host
from openpype.hosts.fusion import api
from openpype.lib.avalon_context import get_workdir_from_session
@@ -181,8 +182,7 @@ class App(QtWidgets.QWidget):
if __name__ == '__main__':
- import sys
- avalon.api.install(api)
+ install_host(api)
app = QtWidgets.QApplication(sys.argv)
window = App()
diff --git a/openpype/hosts/harmony/api/lib.py b/openpype/hosts/harmony/api/lib.py
index 66eeac1e3a..53fd0f07dd 100644
--- a/openpype/hosts/harmony/api/lib.py
+++ b/openpype/hosts/harmony/api/lib.py
@@ -183,10 +183,10 @@ def launch(application_path, *args):
application_path (str): Path to Harmony.
"""
- from avalon import api
+ from openpype.pipeline import install_host
from openpype.hosts.harmony import api as harmony
- api.install(harmony)
+ install_host(harmony)
ProcessContext.port = random.randrange(49152, 65535)
os.environ["AVALON_HARMONY_PORT"] = str(ProcessContext.port)
diff --git a/openpype/hosts/hiero/api/pipeline.py b/openpype/hosts/hiero/api/pipeline.py
index b334102129..616ff53fd8 100644
--- a/openpype/hosts/hiero/api/pipeline.py
+++ b/openpype/hosts/hiero/api/pipeline.py
@@ -34,14 +34,7 @@ AVALON_CONTAINERS = ":AVALON_CONTAINERS"
def install():
- """
- Installing Hiero integration for avalon
-
- Args:
- config (obj): avalon config module `pype` in our case, it is not
- used but required by avalon.api.install()
-
- """
+ """Installing Hiero integration."""
# adding all events
events.register_events()
diff --git a/openpype/hosts/hiero/api/startup/Python/Startup/Startup.py b/openpype/hosts/hiero/api/startup/Python/Startup/Startup.py
index 21c21cd7c3..2e638c2088 100644
--- a/openpype/hosts/hiero/api/startup/Python/Startup/Startup.py
+++ b/openpype/hosts/hiero/api/startup/Python/Startup/Startup.py
@@ -1,9 +1,9 @@
import traceback
# activate hiero from pype
-import avalon.api
+from openpype.pipeline import install_host
import openpype.hosts.hiero.api as phiero
-avalon.api.install(phiero)
+install_host(phiero)
try:
__import__("openpype.hosts.hiero.api")
diff --git a/openpype/hosts/houdini/api/pipeline.py b/openpype/hosts/houdini/api/pipeline.py
index 6051f4eced..7048accceb 100644
--- a/openpype/hosts/houdini/api/pipeline.py
+++ b/openpype/hosts/houdini/api/pipeline.py
@@ -6,8 +6,6 @@ import contextlib
import hou
import pyblish.api
-import avalon.api
-from avalon.lib import find_submodule
from openpype.pipeline import (
register_creator_plugin_path,
@@ -214,24 +212,12 @@ def ls():
"pyblish.mindbender.container"):
containers += lib.lsattr("id", identifier)
- has_metadata_collector = False
- config_host = find_submodule(avalon.api.registered_config(), "houdini")
- if hasattr(config_host, "collect_container_metadata"):
- has_metadata_collector = True
-
for container in sorted(containers,
# Hou 19+ Python 3 hou.ObjNode are not
# sortable due to not supporting greater
# than comparisons
key=lambda node: node.path()):
- data = parse_container(container)
-
- # Collect custom data if attribute is present
- if has_metadata_collector:
- metadata = config_host.collect_container_metadata(container)
- data.update(metadata)
-
- yield data
+ yield parse_container(container)
def before_save():
diff --git a/openpype/hosts/houdini/plugins/publish/collect_inputs.py b/openpype/hosts/houdini/plugins/publish/collect_inputs.py
index 39e2737e8c..8c7098c710 100644
--- a/openpype/hosts/houdini/plugins/publish/collect_inputs.py
+++ b/openpype/hosts/houdini/plugins/publish/collect_inputs.py
@@ -1,6 +1,7 @@
-import avalon.api as api
import pyblish.api
+from openpype.pipeline import registered_host
+
def collect_input_containers(nodes):
"""Collect containers that contain any of the node in `nodes`.
@@ -18,7 +19,7 @@ def collect_input_containers(nodes):
lookup = frozenset(nodes)
containers = []
- host = api.registered_host()
+ host = registered_host()
for container in host.ls():
node = container["node"]
diff --git a/openpype/hosts/houdini/plugins/publish/increment_current_file.py b/openpype/hosts/houdini/plugins/publish/increment_current_file.py
index 31c2954ee7..c5cacd1880 100644
--- a/openpype/hosts/houdini/plugins/publish/increment_current_file.py
+++ b/openpype/hosts/houdini/plugins/publish/increment_current_file.py
@@ -1,8 +1,8 @@
import pyblish.api
-import avalon.api
from openpype.api import version_up
from openpype.action import get_errored_plugins_from_data
+from openpype.pipeline import registered_host
class IncrementCurrentFile(pyblish.api.InstancePlugin):
@@ -41,7 +41,7 @@ class IncrementCurrentFile(pyblish.api.InstancePlugin):
)
# Filename must not have changed since collecting
- host = avalon.api.registered_host()
+ host = registered_host()
current_file = host.current_file()
assert (
context.data["currentFile"] == current_file
diff --git a/openpype/hosts/houdini/plugins/publish/save_scene.py b/openpype/hosts/houdini/plugins/publish/save_scene.py
index fe5962fbd3..6128c7af77 100644
--- a/openpype/hosts/houdini/plugins/publish/save_scene.py
+++ b/openpype/hosts/houdini/plugins/publish/save_scene.py
@@ -1,5 +1,6 @@
import pyblish.api
-import avalon.api
+
+from openpype.pipeline import registered_host
class SaveCurrentScene(pyblish.api.ContextPlugin):
@@ -12,7 +13,7 @@ class SaveCurrentScene(pyblish.api.ContextPlugin):
def process(self, context):
# Filename must not have changed since collecting
- host = avalon.api.registered_host()
+ host = registered_host()
current_file = host.current_file()
assert context.data['currentFile'] == current_file, (
"Collected filename from current scene name."
diff --git a/openpype/hosts/houdini/startup/python2.7libs/pythonrc.py b/openpype/hosts/houdini/startup/python2.7libs/pythonrc.py
index eb33b49759..afadbffd3e 100644
--- a/openpype/hosts/houdini/startup/python2.7libs/pythonrc.py
+++ b/openpype/hosts/houdini/startup/python2.7libs/pythonrc.py
@@ -1,10 +1,10 @@
-import avalon.api
+from openpype.pipeline import install_host
from openpype.hosts.houdini import api
def main():
print("Installing OpenPype ...")
- avalon.api.install(api)
+ install_host(api)
main()
diff --git a/openpype/hosts/houdini/startup/python3.7libs/pythonrc.py b/openpype/hosts/houdini/startup/python3.7libs/pythonrc.py
index eb33b49759..afadbffd3e 100644
--- a/openpype/hosts/houdini/startup/python3.7libs/pythonrc.py
+++ b/openpype/hosts/houdini/startup/python3.7libs/pythonrc.py
@@ -1,10 +1,10 @@
-import avalon.api
+from openpype.pipeline import install_host
from openpype.hosts.houdini import api
def main():
print("Installing OpenPype ...")
- avalon.api.install(api)
+ install_host(api)
main()
diff --git a/openpype/hosts/houdini/vendor/husdoutputprocessors/avalon_uri_processor.py b/openpype/hosts/houdini/vendor/husdoutputprocessors/avalon_uri_processor.py
index 499b733570..8cd51e6641 100644
--- a/openpype/hosts/houdini/vendor/husdoutputprocessors/avalon_uri_processor.py
+++ b/openpype/hosts/houdini/vendor/husdoutputprocessors/avalon_uri_processor.py
@@ -134,6 +134,7 @@ class AvalonURIOutputProcessor(base.OutputProcessorBase):
"""
from avalon import api, io
+ from openpype.pipeline import registered_root
PROJECT = api.Session["AVALON_PROJECT"]
asset_doc = io.find_one({"name": asset,
@@ -141,7 +142,7 @@ class AvalonURIOutputProcessor(base.OutputProcessorBase):
if not asset_doc:
raise RuntimeError("Invalid asset name: '%s'" % asset)
- root = api.registered_root()
+ root = registered_root()
path = self._template.format(**{
"root": root,
"project": PROJECT,
diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py
index 90688423e0..9e99b96477 100644
--- a/openpype/hosts/maya/api/lib.py
+++ b/openpype/hosts/maya/api/lib.py
@@ -26,6 +26,7 @@ from openpype.pipeline import (
loaders_from_representation,
get_representation_path,
load_container,
+ registered_host,
)
from .commands import reset_frame_range
@@ -1574,7 +1575,7 @@ def assign_look_by_version(nodes, version_id):
"name": "json"})
# See if representation is already loaded, if so reuse it.
- host = api.registered_host()
+ host = registered_host()
representation_id = str(look_representation['_id'])
for container in host.ls():
if (container['loader'] == "LookLoader" and
@@ -2612,7 +2613,7 @@ def get_attr_in_layer(attr, layer):
def fix_incompatible_containers():
"""Backwards compatibility: old containers to use new ReferenceLoader"""
- host = api.registered_host()
+ host = registered_host()
for container in host.ls():
loader = container['loader']
diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py
index c229ca226f..14b9157005 100644
--- a/openpype/hosts/maya/plugins/publish/collect_render.py
+++ b/openpype/hosts/maya/plugins/publish/collect_render.py
@@ -194,11 +194,13 @@ class CollectMayaRender(pyblish.api.ContextPlugin):
assert render_products, "no render products generated"
exp_files = []
multipart = False
+ render_cameras = []
for product in render_products:
if product.multipart:
multipart = True
product_name = product.productName
if product.camera and layer_render_products.has_camera_token():
+ render_cameras.append(product.camera)
product_name = "{}{}".format(
product.camera,
"_" + product_name if product_name else "")
@@ -208,6 +210,8 @@ class CollectMayaRender(pyblish.api.ContextPlugin):
product)
})
+ assert render_cameras, "No render cameras found."
+
self.log.info("multipart: {}".format(
multipart))
assert exp_files, "no file names were generated, this is bug"
diff --git a/openpype/hosts/maya/startup/userSetup.py b/openpype/hosts/maya/startup/userSetup.py
index b89244817a..a3ab483add 100644
--- a/openpype/hosts/maya/startup/userSetup.py
+++ b/openpype/hosts/maya/startup/userSetup.py
@@ -1,11 +1,10 @@
import os
-import avalon.api
from openpype.api import get_project_settings
+from openpype.pipeline import install_host
from openpype.hosts.maya import api
-import openpype.hosts.maya.api.lib as mlib
from maya import cmds
-avalon.api.install(api)
+install_host(api)
print("starting OpenPype usersetup")
diff --git a/openpype/hosts/nuke/startup/menu.py b/openpype/hosts/nuke/startup/menu.py
index 2cac6d09e7..9ed43b2110 100644
--- a/openpype/hosts/nuke/startup/menu.py
+++ b/openpype/hosts/nuke/startup/menu.py
@@ -1,7 +1,7 @@
import nuke
-import avalon.api
from openpype.api import Logger
+from openpype.pipeline import install_host
from openpype.hosts.nuke import api
from openpype.hosts.nuke.api.lib import (
on_script_load,
@@ -13,7 +13,7 @@ from openpype.hosts.nuke.api.lib import (
log = Logger.get_logger(__name__)
-avalon.api.install(api)
+install_host(api)
# fix ffmpeg settings on script
nuke.addOnScriptLoad(on_script_load)
diff --git a/openpype/hosts/photoshop/api/lib.py b/openpype/hosts/photoshop/api/lib.py
index 6d2a493a94..2f57d64464 100644
--- a/openpype/hosts/photoshop/api/lib.py
+++ b/openpype/hosts/photoshop/api/lib.py
@@ -5,9 +5,8 @@ import traceback
from Qt import QtWidgets
-import avalon.api
-
from openpype.api import Logger
+from openpype.pipeline import install_host
from openpype.tools.utils import host_tools
from openpype.lib.remote_publish import headless_publish
from openpype.lib import env_value_to_bool
@@ -24,7 +23,7 @@ def safe_excepthook(*args):
def main(*subprocess_args):
from openpype.hosts.photoshop import api
- avalon.api.install(api)
+ install_host(api)
sys.excepthook = safe_excepthook
# coloring in StdOutBroker
diff --git a/openpype/hosts/photoshop/api/pipeline.py b/openpype/hosts/photoshop/api/pipeline.py
index 7fdaa61b40..1f069c2636 100644
--- a/openpype/hosts/photoshop/api/pipeline.py
+++ b/openpype/hosts/photoshop/api/pipeline.py
@@ -3,7 +3,6 @@ from Qt import QtWidgets
from bson.objectid import ObjectId
import pyblish.api
-import avalon.api
from avalon import io
from openpype.api import Logger
@@ -14,6 +13,7 @@ from openpype.pipeline import (
deregister_loader_plugin_path,
deregister_creator_plugin_path,
AVALON_CONTAINER_ID,
+ registered_host,
)
import openpype.hosts.photoshop
@@ -33,7 +33,7 @@ def check_inventory():
if not lib.any_outdated():
return
- host = avalon.api.registered_host()
+ host = registered_host()
outdated_containers = []
for container in host.ls():
representation = container['representation']
diff --git a/openpype/hosts/resolve/utility_scripts/OpenPype_sync_util_scripts.py b/openpype/hosts/resolve/utility_scripts/OpenPype_sync_util_scripts.py
index ac66916b91..3a16b9c966 100644
--- a/openpype/hosts/resolve/utility_scripts/OpenPype_sync_util_scripts.py
+++ b/openpype/hosts/resolve/utility_scripts/OpenPype_sync_util_scripts.py
@@ -1,13 +1,14 @@
#!/usr/bin/env python
import os
import sys
-import openpype
+
+from openpype.pipeline import install_host
def main(env):
import openpype.hosts.resolve as bmdvr
# Registers openpype's Global pyblish plugins
- openpype.install()
+ install_host(bmdvr)
bmdvr.setup(env)
diff --git a/openpype/hosts/resolve/utility_scripts/__OpenPype__Menu__.py b/openpype/hosts/resolve/utility_scripts/__OpenPype__Menu__.py
index b0cef1838a..89ade9238b 100644
--- a/openpype/hosts/resolve/utility_scripts/__OpenPype__Menu__.py
+++ b/openpype/hosts/resolve/utility_scripts/__OpenPype__Menu__.py
@@ -1,8 +1,7 @@
import os
import sys
-import avalon.api as avalon
-import openpype
+from openpype.pipeline import install_host
from openpype.api import Logger
log = Logger().get_logger(__name__)
@@ -10,13 +9,9 @@ log = Logger().get_logger(__name__)
def main(env):
import openpype.hosts.resolve as bmdvr
- # Registers openpype's Global pyblish plugins
- openpype.install()
# activate resolve from openpype
- avalon.install(bmdvr)
-
- log.info(f"Avalon registered hosts: {avalon.registered_host()}")
+ install_host(bmdvr)
bmdvr.launch_pype_menu()
diff --git a/openpype/hosts/resolve/utility_scripts/tests/test_otio_as_edl.py b/openpype/hosts/resolve/utility_scripts/tests/test_otio_as_edl.py
index 5430ad32df..8433bd9172 100644
--- a/openpype/hosts/resolve/utility_scripts/tests/test_otio_as_edl.py
+++ b/openpype/hosts/resolve/utility_scripts/tests/test_otio_as_edl.py
@@ -1,9 +1,11 @@
#! python3
import os
import sys
-import avalon.api as avalon
-import openpype
+
import opentimelineio as otio
+
+from openpype.pipeline import install_host
+
from openpype.hosts.resolve import TestGUI
import openpype.hosts.resolve as bmdvr
from openpype.hosts.resolve.otio import davinci_export as otio_export
@@ -14,10 +16,8 @@ class ThisTestGUI(TestGUI):
def __init__(self):
super(ThisTestGUI, self).__init__()
- # Registers openpype's Global pyblish plugins
- openpype.install()
# activate resolve from openpype
- avalon.install(bmdvr)
+ install_host(bmdvr)
def _open_dir_button_pressed(self, event):
# selected_path = self.fu.RequestFile(os.path.expanduser("~"))
diff --git a/openpype/hosts/resolve/utility_scripts/tests/testing_create_timeline_item_from_path.py b/openpype/hosts/resolve/utility_scripts/tests/testing_create_timeline_item_from_path.py
index afa311e0b8..477955d527 100644
--- a/openpype/hosts/resolve/utility_scripts/tests/testing_create_timeline_item_from_path.py
+++ b/openpype/hosts/resolve/utility_scripts/tests/testing_create_timeline_item_from_path.py
@@ -1,8 +1,8 @@
#! python3
import os
import sys
-import avalon.api as avalon
-import openpype
+
+from openpype.pipeline import install_host
from openpype.hosts.resolve import TestGUI
import openpype.hosts.resolve as bmdvr
import clique
@@ -13,10 +13,8 @@ class ThisTestGUI(TestGUI):
def __init__(self):
super(ThisTestGUI, self).__init__()
- # Registers openpype's Global pyblish plugins
- openpype.install()
# activate resolve from openpype
- avalon.install(bmdvr)
+ install_host(bmdvr)
def _open_dir_button_pressed(self, event):
# selected_path = self.fu.RequestFile(os.path.expanduser("~"))
diff --git a/openpype/hosts/resolve/utility_scripts/tests/testing_load_media_pool_item.py b/openpype/hosts/resolve/utility_scripts/tests/testing_load_media_pool_item.py
index cfdbe890e5..872d620162 100644
--- a/openpype/hosts/resolve/utility_scripts/tests/testing_load_media_pool_item.py
+++ b/openpype/hosts/resolve/utility_scripts/tests/testing_load_media_pool_item.py
@@ -1,6 +1,5 @@
#! python3
-import avalon.api as avalon
-import openpype
+from openpype.pipeline import install_host
import openpype.hosts.resolve as bmdvr
@@ -15,8 +14,7 @@ def file_processing(fpath):
if __name__ == "__main__":
path = "C:/CODE/__openpype_projects/jtest03dev/shots/sq01/mainsq01sh030/publish/plate/plateMain/v006/jt3d_mainsq01sh030_plateMain_v006.0996.exr"
- openpype.install()
# activate resolve from openpype
- avalon.install(bmdvr)
+ install_host(bmdvr)
- file_processing(path)
\ No newline at end of file
+ file_processing(path)
diff --git a/openpype/hosts/testhost/run_publish.py b/openpype/hosts/testhost/run_publish.py
index 44860a30e4..cc80bdc604 100644
--- a/openpype/hosts/testhost/run_publish.py
+++ b/openpype/hosts/testhost/run_publish.py
@@ -48,8 +48,8 @@ from openpype.tools.publisher.window import PublisherWindow
def main():
"""Main function for testing purposes."""
- import avalon.api
import pyblish.api
+ from openpype.pipeline import install_host
from openpype.modules import ModulesManager
from openpype.hosts.testhost import api as testhost
@@ -57,7 +57,7 @@ def main():
for plugin_path in manager.collect_plugin_paths()["publish"]:
pyblish.api.register_plugin_path(plugin_path)
- avalon.api.install(testhost)
+ install_host(testhost)
QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)
app = QtWidgets.QApplication([])
diff --git a/openpype/hosts/tvpaint/api/launch_script.py b/openpype/hosts/tvpaint/api/launch_script.py
index e66bf61df6..0b25027fc6 100644
--- a/openpype/hosts/tvpaint/api/launch_script.py
+++ b/openpype/hosts/tvpaint/api/launch_script.py
@@ -8,8 +8,8 @@ import logging
from Qt import QtWidgets, QtCore, QtGui
-from avalon import api
from openpype import style
+from openpype.pipeline import install_host
from openpype.hosts.tvpaint.api.communication_server import (
CommunicationWrapper
)
@@ -31,7 +31,7 @@ def main(launch_args):
qt_app = QtWidgets.QApplication([])
# Execute pipeline installation
- api.install(tvpaint_host)
+ install_host(tvpaint_host)
# Create Communicator object and trigger launch
# - this must be done before anything is processed
diff --git a/openpype/hosts/tvpaint/api/pipeline.py b/openpype/hosts/tvpaint/api/pipeline.py
index cafdf0701d..78c10c3dae 100644
--- a/openpype/hosts/tvpaint/api/pipeline.py
+++ b/openpype/hosts/tvpaint/api/pipeline.py
@@ -67,11 +67,8 @@ instances=2
def install():
- """Install Maya-specific functionality of avalon-core.
+ """Install TVPaint-specific functionality."""
- This function is called automatically on calling `api.install(maya)`.
-
- """
log.info("OpenPype - Installing TVPaint integration")
io.install()
@@ -96,11 +93,11 @@ def install():
def uninstall():
- """Uninstall TVPaint-specific functionality of avalon-core.
-
- This function is called automatically on calling `api.uninstall()`.
+ """Uninstall TVPaint-specific functionality.
+ This function is called automatically on calling `uninstall_host()`.
"""
+
log.info("OpenPype - Uninstalling TVPaint integration")
pyblish.api.deregister_host("tvpaint")
pyblish.api.deregister_plugin_path(PUBLISH_PATH)
diff --git a/openpype/hosts/tvpaint/plugins/load/load_workfile.py b/openpype/hosts/tvpaint/plugins/load/load_workfile.py
index d224cfc390..1ce5449065 100644
--- a/openpype/hosts/tvpaint/plugins/load/load_workfile.py
+++ b/openpype/hosts/tvpaint/plugins/load/load_workfile.py
@@ -1,12 +1,13 @@
import os
-from avalon import api, io
+from avalon import io
from openpype.lib import (
StringTemplate,
get_workfile_template_key_from_context,
get_workdir_data,
get_last_workfile_with_version,
)
+from openpype.pipeline import registered_host
from openpype.api import Anatomy
from openpype.hosts.tvpaint.api import lib, pipeline, plugin
@@ -22,7 +23,7 @@ class LoadWorkfile(plugin.Loader):
def load(self, context, name, namespace, options):
# Load context of current workfile as first thing
# - which context and extension has
- host = api.registered_host()
+ host = registered_host()
current_file = host.current_file()
context = pipeline.get_current_workfile_context()
diff --git a/openpype/hosts/unreal/api/plugin.py b/openpype/hosts/unreal/api/plugin.py
index b24bab831d..d8d2f2420d 100644
--- a/openpype/hosts/unreal/api/plugin.py
+++ b/openpype/hosts/unreal/api/plugin.py
@@ -10,6 +10,7 @@ from openpype.pipeline import (
class Creator(LegacyCreator):
"""This serves as skeleton for future OpenPype specific functionality"""
defaults = ['Main']
+ maintain_selection = False
class Loader(LoaderPlugin, ABC):
diff --git a/openpype/hosts/unreal/integration/Content/Python/init_unreal.py b/openpype/hosts/unreal/integration/Content/Python/init_unreal.py
index 2ecd301c25..4bb03b07ed 100644
--- a/openpype/hosts/unreal/integration/Content/Python/init_unreal.py
+++ b/openpype/hosts/unreal/integration/Content/Python/init_unreal.py
@@ -2,13 +2,7 @@ import unreal
openpype_detected = True
try:
- from avalon import api
-except ImportError as exc:
- api = None
- openpype_detected = False
- unreal.log_error("Avalon: cannot load Avalon [ {} ]".format(exc))
-
-try:
+ from openpype.pipeline import install_host
from openpype.hosts.unreal import api as openpype_host
except ImportError as exc:
openpype_host = None
@@ -16,7 +10,7 @@ except ImportError as exc:
unreal.log_error("OpenPype: cannot load OpenPype [ {} ]".format(exc))
if openpype_detected:
- api.install(openpype_host)
+ install_host(openpype_host)
@unreal.uclass()
diff --git a/openpype/hosts/webpublisher/api/__init__.py b/openpype/hosts/webpublisher/api/__init__.py
index dbeb628073..72bbffd099 100644
--- a/openpype/hosts/webpublisher/api/__init__.py
+++ b/openpype/hosts/webpublisher/api/__init__.py
@@ -1,7 +1,6 @@
import os
import logging
-from avalon import api as avalon
from avalon import io
from pyblish import api as pyblish
import openpype.hosts.webpublisher
diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py
index 0348d88be2..e82dcc558f 100644
--- a/openpype/lib/avalon_context.py
+++ b/openpype/lib/avalon_context.py
@@ -161,9 +161,10 @@ def is_latest(representation):
@with_avalon
def any_outdated():
"""Return whether the current scene has any outdated content"""
+ from openpype.pipeline import registered_host
checked = set()
- host = avalon.api.registered_host()
+ host = registered_host()
for container in host.ls():
representation = container['representation']
if representation in checked:
diff --git a/openpype/lib/remote_publish.py b/openpype/lib/remote_publish.py
index 9d97671a61..8a42daf4e9 100644
--- a/openpype/lib/remote_publish.py
+++ b/openpype/lib/remote_publish.py
@@ -1,13 +1,12 @@
import os
from datetime import datetime
-import sys
-from bson.objectid import ObjectId
import collections
+from bson.objectid import ObjectId
+
import pyblish.util
import pyblish.api
-from openpype import uninstall
from openpype.lib.mongo import OpenPypeMongoConnection
from openpype.lib.plugin_tools import parse_json
@@ -81,7 +80,6 @@ def publish(log, close_plugin_name=None):
if result["error"]:
log.error(error_format.format(**result))
- uninstall()
if close_plugin: # close host app explicitly after error
context = pyblish.api.Context()
close_plugin().process(context)
@@ -118,7 +116,6 @@ def publish_and_log(dbcon, _id, log, close_plugin_name=None, batch_id=None):
if result["error"]:
log.error(error_format.format(**result))
- uninstall()
log_lines = [error_format.format(**result)] + log_lines
dbcon.update_one(
{"_id": _id},
diff --git a/openpype/lib/usdlib.py b/openpype/lib/usdlib.py
index 89021156b4..7b3b7112de 100644
--- a/openpype/lib/usdlib.py
+++ b/openpype/lib/usdlib.py
@@ -9,6 +9,7 @@ except ImportError:
from mvpxr import Usd, UsdGeom, Sdf, Kind
from avalon import io, api
+from openpype.pipeline import registered_root
log = logging.getLogger(__name__)
@@ -323,7 +324,7 @@ def get_usd_master_path(asset, subset, representation):
path = template.format(
**{
- "root": api.registered_root(),
+ "root": registered_root(),
"project": api.Session["AVALON_PROJECT"],
"asset": asset_doc["name"],
"subset": subset,
diff --git a/openpype/modules/avalon_apps/avalon_app.py b/openpype/modules/avalon_apps/avalon_app.py
index 51a22323f1..1d21de129b 100644
--- a/openpype/modules/avalon_apps/avalon_app.py
+++ b/openpype/modules/avalon_apps/avalon_app.py
@@ -1,5 +1,5 @@
import os
-import openpype
+
from openpype.modules import OpenPypeModule
from openpype_interfaces import ITrayModule
@@ -26,7 +26,8 @@ class AvalonModule(OpenPypeModule, ITrayModule):
self.avalon_mongo_timeout = avalon_mongo_timeout
# Tray attributes
- self.libraryloader = None
+ self._library_loader_imported = None
+ self._library_loader_window = None
self.rest_api_obj = None
def get_global_environments(self):
@@ -41,21 +42,11 @@ class AvalonModule(OpenPypeModule, ITrayModule):
def tray_init(self):
# Add library tool
+ self._library_loader_imported = False
try:
- from Qt import QtCore
from openpype.tools.libraryloader import LibraryLoaderWindow
- libraryloader = LibraryLoaderWindow(
- show_projects=True,
- show_libraries=True
- )
- # Remove always on top flag for tray
- window_flags = libraryloader.windowFlags()
- if window_flags | QtCore.Qt.WindowStaysOnTopHint:
- window_flags ^= QtCore.Qt.WindowStaysOnTopHint
- libraryloader.setWindowFlags(window_flags)
- self.libraryloader = libraryloader
-
+ self._library_loader_imported = True
except Exception:
self.log.warning(
"Couldn't load Library loader tool for tray.",
@@ -64,7 +55,7 @@ class AvalonModule(OpenPypeModule, ITrayModule):
# Definition of Tray menu
def tray_menu(self, tray_menu):
- if self.libraryloader is None:
+ if not self._library_loader_imported:
return
from Qt import QtWidgets
@@ -84,17 +75,31 @@ class AvalonModule(OpenPypeModule, ITrayModule):
return
def show_library_loader(self):
- if self.libraryloader is None:
- return
+ if self._library_loader_window is None:
+ from Qt import QtCore
+ from openpype.tools.libraryloader import LibraryLoaderWindow
+ from openpype.pipeline import install_openpype_plugins
- self.libraryloader.show()
+ libraryloader = LibraryLoaderWindow(
+ show_projects=True,
+ show_libraries=True
+ )
+ # Remove always on top flag for tray
+ window_flags = libraryloader.windowFlags()
+ if window_flags | QtCore.Qt.WindowStaysOnTopHint:
+ window_flags ^= QtCore.Qt.WindowStaysOnTopHint
+ libraryloader.setWindowFlags(window_flags)
+ self._library_loader_window = libraryloader
+
+ install_openpype_plugins()
+
+ self._library_loader_window.show()
# Raise and activate the window
# for MacOS
- self.libraryloader.raise_()
+ self._library_loader_window.raise_()
# for Windows
- self.libraryloader.activateWindow()
- self.libraryloader.refresh()
+ self._library_loader_window.activateWindow()
# Webserver module implementation
def webserver_initialization(self, server_manager):
diff --git a/openpype/modules/clockify/launcher_actions/ClockifyStart.py b/openpype/modules/clockify/launcher_actions/ClockifyStart.py
index db51964eb7..6428d5e7aa 100644
--- a/openpype/modules/clockify/launcher_actions/ClockifyStart.py
+++ b/openpype/modules/clockify/launcher_actions/ClockifyStart.py
@@ -1,12 +1,14 @@
-from avalon import api, io
+from avalon import io
+
from openpype.api import Logger
+from openpype.pipeline import LauncherAction
from openpype_modules.clockify.clockify_api import ClockifyAPI
-log = Logger().get_logger(__name__)
+log = Logger.get_logger(__name__)
-class ClockifyStart(api.Action):
+class ClockifyStart(LauncherAction):
name = "clockify_start_timer"
label = "Clockify - Start Timer"
diff --git a/openpype/modules/clockify/launcher_actions/ClockifySync.py b/openpype/modules/clockify/launcher_actions/ClockifySync.py
index 02982d373a..3c81e2766c 100644
--- a/openpype/modules/clockify/launcher_actions/ClockifySync.py
+++ b/openpype/modules/clockify/launcher_actions/ClockifySync.py
@@ -1,10 +1,13 @@
-from avalon import api, io
+from avalon import io
+
from openpype_modules.clockify.clockify_api import ClockifyAPI
from openpype.api import Logger
-log = Logger().get_logger(__name__)
+from openpype.pipeline import LauncherAction
+
+log = Logger.get_logger(__name__)
-class ClockifySync(api.Action):
+class ClockifySync(LauncherAction):
name = "sync_to_clockify"
label = "Sync to Clockify"
diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py
index 2c27571f9f..3744a21b43 100644
--- a/openpype/modules/sync_server/sync_server_module.py
+++ b/openpype/modules/sync_server/sync_server_module.py
@@ -4,7 +4,7 @@ from datetime import datetime
import threading
import platform
import copy
-from collections import deque
+from collections import deque, defaultdict
from avalon.api import AvalonMongoDB
@@ -157,7 +157,6 @@ class SyncServerModule(OpenPypeModule, ITrayModule):
representation_id,
site_name=site_name, force=force)
- # public facing API
def remove_site(self, collection, representation_id, site_name,
remove_local_files=False):
"""
@@ -184,6 +183,151 @@ class SyncServerModule(OpenPypeModule, ITrayModule):
if remove_local_files:
self._remove_local_file(collection, representation_id, site_name)
+ def compute_resource_sync_sites(self, project_name):
+ """Get available resource sync sites state for publish process.
+
+ Returns dict with prepared state of sync sites for 'project_name'.
+ It checks if Site Sync is enabled, handles alternative sites.
+ Publish process stores this dictionary as a part of representation
+ document in DB.
+
+ Example:
+ [
+ {
+ 'name': '42abbc09-d62a-44a4-815c-a12cd679d2d7',
+ 'created_dt': datetime.datetime(2022, 3, 30, 12, 16, 9, 778637)
+ },
+ {'name': 'studio'},
+ {'name': 'SFTP'}
+ ] -- representation is published locally, artist or Settings have set
+ remote site as 'studio'. 'SFTP' is alternate site to 'studio'. Eg.
+ whenever file is on 'studio', it is also on 'SFTP'.
+ """
+
+ def create_metadata(name, created=True):
+ """Create sync site metadata for site with `name`"""
+ metadata = {"name": name}
+ if created:
+ metadata["created_dt"] = datetime.now()
+ return metadata
+
+ if (
+ not self.sync_system_settings["enabled"] or
+ not self.sync_project_settings[project_name]["enabled"]):
+ return [create_metadata(self.DEFAULT_SITE)]
+
+ local_site = self.get_active_site(project_name)
+ remote_site = self.get_remote_site(project_name)
+
+ # Attached sites metadata by site name
+ # That is the local site, remote site, the always accesible sites
+ # and their alternate sites (alias of sites with different protocol)
+ attached_sites = dict()
+ attached_sites[local_site] = create_metadata(local_site)
+
+ if remote_site and remote_site not in attached_sites:
+ attached_sites[remote_site] = create_metadata(remote_site,
+ created=False)
+
+ attached_sites = self._add_alternative_sites(attached_sites)
+ # add skeleton for sites where it should be always synced to
+ # usually it would be a backup site which is handled by separate
+ # background process
+ for site in self._get_always_accessible_sites(project_name):
+ if site not in attached_sites:
+ attached_sites[site] = create_metadata(site, created=False)
+
+ return list(attached_sites.values())
+
+ def _get_always_accessible_sites(self, project_name):
+ """Sites that synced to as a part of background process.
+
+ Artist machine doesn't handle those, explicit Tray with that site name
+ as a local id must be running.
+ Example is dropbox site serving as a backup solution
+ """
+ always_accessible_sites = (
+ self.get_sync_project_setting(project_name)["config"].
+ get("always_accessible_on", [])
+ )
+ return [site.strip() for site in always_accessible_sites]
+
+ def _add_alternative_sites(self, attached_sites):
+ """Add skeleton document for alternative sites
+
+ Each new configured site in System Setting could serve as a alternative
+ site, it's a kind of alias. It means that files on 'a site' are
+ physically accessible also on 'a alternative' site.
+ Example is sftp site serving studio files via sftp protocol, physically
+ file is only in studio, sftp server has this location mounted.
+ """
+ additional_sites = self.sync_system_settings.get("sites", {})
+
+ alt_site_pairs = self._get_alt_site_pairs(additional_sites)
+
+ for site_name in additional_sites.keys():
+ # Get alternate sites (stripped names) for this site name
+ alt_sites = alt_site_pairs.get(site_name)
+ alt_sites = [site.strip() for site in alt_sites]
+ alt_sites = set(alt_sites)
+
+ # If no alternative sites we don't need to add
+ if not alt_sites:
+ continue
+
+ # Take a copy of data of the first alternate site that is already
+ # defined as an attached site to match the same state.
+ match_meta = next((attached_sites[site] for site in alt_sites
+ if site in attached_sites), None)
+ if not match_meta:
+ continue
+
+ alt_site_meta = copy.deepcopy(match_meta)
+ alt_site_meta["name"] = site_name
+
+ # Note: We change mutable `attached_site` dict in-place
+ attached_sites[site_name] = alt_site_meta
+
+ return attached_sites
+
+ def _get_alt_site_pairs(self, conf_sites):
+ """Returns dict of site and its alternative sites.
+
+ If `site` has alternative site, it means that alt_site has 'site' as
+ alternative site
+ Args:
+ conf_sites (dict)
+ Returns:
+ (dict): {'site': [alternative sites]...}
+ """
+ alt_site_pairs = defaultdict(set)
+ for site_name, site_info in conf_sites.items():
+ alt_sites = set(site_info.get("alternative_sites", []))
+ alt_site_pairs[site_name].update(alt_sites)
+
+ for alt_site in alt_sites:
+ alt_site_pairs[alt_site].add(site_name)
+
+ for site_name, alt_sites in alt_site_pairs.items():
+ sites_queue = deque(alt_sites)
+ while sites_queue:
+ alt_site = sites_queue.popleft()
+
+ # safety against wrong config
+ # {"SFTP": {"alternative_site": "SFTP"}
+ if alt_site == site_name or alt_site not in alt_site_pairs:
+ continue
+
+ for alt_alt_site in alt_site_pairs[alt_site]:
+ if (
+ alt_alt_site != site_name
+ and alt_alt_site not in alt_sites
+ ):
+ alt_sites.add(alt_alt_site)
+ sites_queue.append(alt_alt_site)
+
+ return alt_site_pairs
+
def clear_project(self, collection, site_name):
"""
Clear 'collection' of 'site_name' and its local files
diff --git a/openpype/pipeline/__init__.py b/openpype/pipeline/__init__.py
index 883713b078..308be6da64 100644
--- a/openpype/pipeline/__init__.py
+++ b/openpype/pipeline/__init__.py
@@ -69,6 +69,22 @@ from .actions import (
deregister_inventory_action_path,
)
+from .context_tools import (
+ install_openpype_plugins,
+ install_host,
+ uninstall_host,
+ is_installed,
+
+ register_root,
+ registered_root,
+
+ register_host,
+ registered_host,
+ deregister_host,
+)
+install = install_host
+uninstall = uninstall_host
+
__all__ = (
"AVALON_CONTAINER_ID",
@@ -137,4 +153,21 @@ __all__ = (
"register_inventory_action_path",
"deregister_inventory_action",
"deregister_inventory_action_path",
+
+ # --- Process context ---
+ "install_openpype_plugins",
+ "install_host",
+ "uninstall_host",
+ "is_installed",
+
+ "register_root",
+ "registered_root",
+
+ "register_host",
+ "registered_host",
+ "deregister_host",
+
+ # Backwards compatible function names
+ "install",
+ "uninstall",
)
diff --git a/openpype/pipeline/context_tools.py b/openpype/pipeline/context_tools.py
new file mode 100644
index 0000000000..1bef260ec9
--- /dev/null
+++ b/openpype/pipeline/context_tools.py
@@ -0,0 +1,335 @@
+"""Core pipeline functionality"""
+
+import os
+import sys
+import json
+import types
+import logging
+import inspect
+import platform
+
+import pyblish.api
+from pyblish.lib import MessageHandler
+
+from avalon import io, Session
+
+import openpype
+from openpype.modules import load_modules
+from openpype.settings import get_project_settings
+from openpype.lib import (
+ Anatomy,
+ register_event_callback,
+ filter_pyblish_plugins,
+ change_timer_to_current_context,
+)
+
+from . import (
+ register_loader_plugin_path,
+ register_inventory_action,
+ register_creator_plugin_path,
+ deregister_loader_plugin_path,
+)
+
+
+_is_installed = False
+_registered_root = {"_": ""}
+_registered_host = {"_": None}
+
+log = logging.getLogger(__name__)
+
+PACKAGE_DIR = os.path.dirname(os.path.abspath(openpype.__file__))
+PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins")
+
+# Global plugin paths
+PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish")
+LOAD_PATH = os.path.join(PLUGINS_DIR, "load")
+
+
+def register_root(path):
+ """Register currently active root"""
+ log.info("Registering root: %s" % path)
+ _registered_root["_"] = path
+
+
+def registered_root():
+ """Return currently registered root"""
+ root = _registered_root["_"]
+ if root:
+ return root
+
+ root = Session.get("AVALON_PROJECTS")
+ if root:
+ return os.path.normpath(root)
+ return ""
+
+
+def install_host(host):
+ """Install `host` into the running Python session.
+
+ Args:
+ host (module): A Python module containing the Avalon
+ avalon host-interface.
+ """
+ global _is_installed
+
+ _is_installed = True
+
+ io.install()
+
+ missing = list()
+ for key in ("AVALON_PROJECT", "AVALON_ASSET"):
+ if key not in Session:
+ missing.append(key)
+
+ assert not missing, (
+ "%s missing from environment, %s" % (
+ ", ".join(missing),
+ json.dumps(Session, indent=4, sort_keys=True)
+ ))
+
+ project_name = Session["AVALON_PROJECT"]
+ log.info("Activating %s.." % project_name)
+
+ # Optional host install function
+ if hasattr(host, "install"):
+ host.install()
+
+ register_host(host)
+
+ register_event_callback("taskChanged", _on_task_change)
+
+ def modified_emit(obj, record):
+ """Method replacing `emit` in Pyblish's MessageHandler."""
+ record.msg = record.getMessage()
+ obj.records.append(record)
+
+ MessageHandler.emit = modified_emit
+
+ install_openpype_plugins()
+
+
+def install_openpype_plugins(project_name=None):
+ # Make sure modules are loaded
+ load_modules()
+
+ log.info("Registering global plug-ins..")
+ pyblish.api.register_plugin_path(PUBLISH_PATH)
+ pyblish.api.register_discovery_filter(filter_pyblish_plugins)
+ register_loader_plugin_path(LOAD_PATH)
+
+ if project_name is None:
+ project_name = os.environ.get("AVALON_PROJECT")
+
+ # Register studio specific plugins
+ if project_name:
+ anatomy = Anatomy(project_name)
+ anatomy.set_root_environments()
+ register_root(anatomy.roots)
+
+ project_settings = get_project_settings(project_name)
+ platform_name = platform.system().lower()
+ project_plugins = (
+ project_settings
+ .get("global", {})
+ .get("project_plugins", {})
+ .get(platform_name)
+ ) or []
+ for path in project_plugins:
+ try:
+ path = str(path.format(**os.environ))
+ except KeyError:
+ pass
+
+ if not path or not os.path.exists(path):
+ continue
+
+ pyblish.api.register_plugin_path(path)
+ register_loader_plugin_path(path)
+ register_creator_plugin_path(path)
+ register_inventory_action(path)
+
+
+def _on_task_change():
+ change_timer_to_current_context()
+
+
+def uninstall_host():
+ """Undo all of what `install()` did"""
+ host = registered_host()
+
+ try:
+ host.uninstall()
+ except AttributeError:
+ pass
+
+ log.info("Deregistering global plug-ins..")
+ pyblish.api.deregister_plugin_path(PUBLISH_PATH)
+ pyblish.api.deregister_discovery_filter(filter_pyblish_plugins)
+ deregister_loader_plugin_path(LOAD_PATH)
+ log.info("Global plug-ins unregistred")
+
+ deregister_host()
+
+ io.uninstall()
+
+ log.info("Successfully uninstalled Avalon!")
+
+
+def is_installed():
+ """Return state of installation
+
+ Returns:
+ True if installed, False otherwise
+
+ """
+
+ return _is_installed
+
+
+def register_host(host):
+ """Register a new host for the current process
+
+ Arguments:
+ host (ModuleType): A module implementing the
+ Host API interface. See the Host API
+ documentation for details on what is
+ required, or browse the source code.
+
+ """
+ signatures = {
+ "ls": []
+ }
+
+ _validate_signature(host, signatures)
+ _registered_host["_"] = host
+
+
+def _validate_signature(module, signatures):
+ # Required signatures for each member
+
+ missing = list()
+ invalid = list()
+ success = True
+
+ for member in signatures:
+ if not hasattr(module, member):
+ missing.append(member)
+ success = False
+
+ else:
+ attr = getattr(module, member)
+ if sys.version_info.major >= 3:
+ signature = inspect.getfullargspec(attr)[0]
+ else:
+ signature = inspect.getargspec(attr)[0]
+ required_signature = signatures[member]
+
+ assert isinstance(signature, list)
+ assert isinstance(required_signature, list)
+
+ if not all(member in signature
+ for member in required_signature):
+ invalid.append({
+ "member": member,
+ "signature": ", ".join(signature),
+ "required": ", ".join(required_signature)
+ })
+ success = False
+
+ if not success:
+ report = list()
+
+ if missing:
+ report.append(
+ "Incomplete interface for module: '%s'\n"
+ "Missing: %s" % (module, ", ".join(
+ "'%s'" % member for member in missing))
+ )
+
+ if invalid:
+ report.append(
+ "'%s': One or more members were found, but didn't "
+ "have the right argument signature." % module.__name__
+ )
+
+ for member in invalid:
+ report.append(
+ " Found: {member}({signature})".format(**member)
+ )
+ report.append(
+ " Expected: {member}({required})".format(**member)
+ )
+
+ raise ValueError("\n".join(report))
+
+
+def registered_host():
+ """Return currently registered host"""
+ return _registered_host["_"]
+
+
+def deregister_host():
+ _registered_host["_"] = default_host()
+
+
+def default_host():
+ """A default host, in place of anything better
+
+ This may be considered as reference for the
+ interface a host must implement. It also ensures
+ that the system runs, even when nothing is there
+ to support it.
+
+ """
+
+ host = types.ModuleType("defaultHost")
+
+ def ls():
+ return list()
+
+ host.__dict__.update({
+ "ls": ls
+ })
+
+ return host
+
+
+def debug_host():
+ """A debug host, useful to debugging features that depend on a host"""
+
+ host = types.ModuleType("debugHost")
+
+ def ls():
+ containers = [
+ {
+ "representation": "ee-ft-a-uuid1",
+ "schema": "openpype:container-1.0",
+ "name": "Bruce01",
+ "objectName": "Bruce01_node",
+ "namespace": "_bruce01_",
+ "version": 3,
+ },
+ {
+ "representation": "aa-bc-s-uuid2",
+ "schema": "openpype:container-1.0",
+ "name": "Bruce02",
+ "objectName": "Bruce01_node",
+ "namespace": "_bruce02_",
+ "version": 2,
+ }
+ ]
+
+ for container in containers:
+ yield container
+
+ host.__dict__.update({
+ "ls": ls,
+ "open_file": lambda fname: None,
+ "save_file": lambda fname: None,
+ "current_file": lambda: os.path.expanduser("~/temp.txt"),
+ "has_unsaved_changes": lambda: False,
+ "work_root": lambda: os.path.expanduser("~/temp"),
+ "file_extensions": lambda: ["txt"],
+ })
+
+ return host
diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py
index 3efdb0e5c3..0cc2819172 100644
--- a/openpype/pipeline/create/context.py
+++ b/openpype/pipeline/create/context.py
@@ -356,7 +356,7 @@ class CreatedInstance:
already existing instance.
creator(BaseCreator): Creator responsible for instance.
host(ModuleType): Host implementation loaded with
- `avalon.api.registered_host`.
+ `openpype.pipeline.registered_host`.
new(bool): Is instance new.
"""
# Keys that can't be changed or removed from data after loading using
diff --git a/openpype/pipeline/create/legacy_create.py b/openpype/pipeline/create/legacy_create.py
index cf6629047e..46e0e3d663 100644
--- a/openpype/pipeline/create/legacy_create.py
+++ b/openpype/pipeline/create/legacy_create.py
@@ -142,7 +142,8 @@ def legacy_create(Creator, name, asset, options=None, data=None):
Name of instance
"""
- from avalon.api import registered_host
+ from openpype.pipeline import registered_host
+
host = registered_host()
plugin = Creator(name, asset, options, data)
diff --git a/openpype/pipeline/load/utils.py b/openpype/pipeline/load/utils.py
index 53ac6b626d..cb7c76f133 100644
--- a/openpype/pipeline/load/utils.py
+++ b/openpype/pipeline/load/utils.py
@@ -10,7 +10,7 @@ import six
from bson.objectid import ObjectId
from avalon import io, schema
-from avalon.api import Session, registered_root
+from avalon.api import Session
from openpype.lib import Anatomy
@@ -532,6 +532,8 @@ def get_representation_path(representation, root=None, dbcon=None):
dbcon = io
if root is None:
+ from openpype.pipeline import registered_root
+
root = registered_root()
def path_from_represenation():
diff --git a/openpype/plugins/publish/collect_scene_loaded_versions.py b/openpype/plugins/publish/collect_scene_loaded_versions.py
index 6746757e5f..e54592abb8 100644
--- a/openpype/plugins/publish/collect_scene_loaded_versions.py
+++ b/openpype/plugins/publish/collect_scene_loaded_versions.py
@@ -1,7 +1,8 @@
from bson.objectid import ObjectId
import pyblish.api
-from avalon import api, io
+from avalon import io
+from openpype.pipeline import registered_host
class CollectSceneLoadedVersions(pyblish.api.ContextPlugin):
@@ -24,7 +25,7 @@ class CollectSceneLoadedVersions(pyblish.api.ContextPlugin):
]
def process(self, context):
- host = api.registered_host()
+ host = registered_host()
if host is None:
self.log.warn("No registered host.")
return
diff --git a/openpype/pype_commands.py b/openpype/pype_commands.py
index c05eece2be..e0c8847040 100644
--- a/openpype/pype_commands.py
+++ b/openpype/pype_commands.py
@@ -101,7 +101,8 @@ class PypeCommands:
RuntimeError: When there is no path to process.
"""
from openpype.modules import ModulesManager
- from openpype import install, uninstall
+ from openpype.pipeline import install_openpype_plugins
+
from openpype.api import Logger
from openpype.tools.utils.host_tools import show_publish
from openpype.tools.utils.lib import qt_app_context
@@ -112,7 +113,7 @@ class PypeCommands:
log = Logger.get_logger()
- install()
+ install_openpype_plugins()
manager = ModulesManager()
@@ -294,7 +295,8 @@ class PypeCommands:
# Register target and host
import pyblish.api
import pyblish.util
- import avalon.api
+
+ from openpype.pipeline import install_host
from openpype.hosts.webpublisher import api as webpublisher
log = PypeLogger.get_logger()
@@ -315,7 +317,7 @@ class PypeCommands:
for target in targets:
pyblish.api.register_target(target)
- avalon.api.install(webpublisher)
+ install_host(webpublisher)
log.info("Running publish ...")
diff --git a/openpype/scripts/fusion_switch_shot.py b/openpype/scripts/fusion_switch_shot.py
index 6db8ff36a8..3ba150902e 100644
--- a/openpype/scripts/fusion_switch_shot.py
+++ b/openpype/scripts/fusion_switch_shot.py
@@ -4,12 +4,16 @@ import sys
import logging
# Pipeline imports
-from avalon import api, io
-import avalon.fusion
+from avalon import io
+from openpype.hosts.fusion import api
+import openpype.hosts.fusion.api.lib as fusion_lib
# Config imports
-import openpype.lib as pype
-import openpype.hosts.fusion.lib as fusion_lib
+from openpype.lib import version_up
+from openpype.pipeline import (
+ install_host,
+ registered_host,
+)
from openpype.lib.avalon_context import get_workdir_from_session
@@ -79,7 +83,7 @@ def _format_filepath(session):
# Create new unqiue filepath
if os.path.exists(new_filepath):
- new_filepath = pype.version_up(new_filepath)
+ new_filepath = version_up(new_filepath)
return new_filepath
@@ -102,7 +106,7 @@ def _update_savers(comp, session):
comp.Print("New renders to: %s\n" % renders)
- with avalon.fusion.comp_lock_and_undo_chunk(comp):
+ with api.comp_lock_and_undo_chunk(comp):
savers = comp.GetToolList(False, "Saver").values()
for saver in savers:
filepath = saver.GetAttrs("TOOLST_Clip_Name")[1.0]
@@ -164,19 +168,19 @@ def switch(asset_name, filepath=None, new=True):
# Get current project
self._project = io.find_one({
"type": "project",
- "name": api.Session["AVALON_PROJECT"]
+ "name": io.Session["AVALON_PROJECT"]
})
# Go to comp
if not filepath:
- current_comp = avalon.fusion.get_current_comp()
+ current_comp = api.get_current_comp()
assert current_comp is not None, "Could not find current comp"
else:
fusion = _get_fusion_instance()
current_comp = fusion.LoadComp(filepath, quiet=True)
assert current_comp is not None, "Fusion could not load '%s'" % filepath
- host = api.registered_host()
+ host = registered_host()
containers = list(host.ls())
assert containers, "Nothing to update"
@@ -194,7 +198,7 @@ def switch(asset_name, filepath=None, new=True):
current_comp.Print(message)
# Build the session to switch to
- switch_to_session = api.Session.copy()
+ switch_to_session = io.Session.copy()
switch_to_session["AVALON_ASSET"] = asset['name']
if new:
@@ -203,7 +207,7 @@ def switch(asset_name, filepath=None, new=True):
# Update savers output based on new session
_update_savers(current_comp, switch_to_session)
else:
- comp_path = pype.version_up(filepath)
+ comp_path = version_up(filepath)
current_comp.Print(comp_path)
@@ -234,7 +238,7 @@ if __name__ == '__main__':
args, unknown = parser.parse_args()
- api.install(avalon.fusion)
+ install_host(api)
switch(args.asset_name, args.file_path)
sys.exit(0)
diff --git a/openpype/scripts/non_python_host_launch.py b/openpype/scripts/non_python_host_launch.py
index 43921f0483..f795af7bb3 100644
--- a/openpype/scripts/non_python_host_launch.py
+++ b/openpype/scripts/non_python_host_launch.py
@@ -15,7 +15,7 @@ CURRENT_FILE = os.path.abspath(__file__)
def show_error_messagebox(title, message, detail_message=None):
"""Function will show message and process ends after closing it."""
from Qt import QtWidgets, QtCore
- from avalon import style
+ from openpype import style
app = QtWidgets.QApplication([])
app.setStyleSheet(style.load_stylesheet())
diff --git a/openpype/tests/test_avalon_plugin_presets.py b/openpype/tests/test_avalon_plugin_presets.py
index c491be1c05..464c216d6f 100644
--- a/openpype/tests/test_avalon_plugin_presets.py
+++ b/openpype/tests/test_avalon_plugin_presets.py
@@ -1,6 +1,5 @@
-import avalon.api as api
-import openpype
from openpype.pipeline import (
+ install_host,
LegacyCreator,
register_creator_plugin,
discover_creator_plugins,
@@ -23,15 +22,14 @@ class Test:
__name__ = "test"
ls = len
- def __call__(self):
- pass
+ @staticmethod
+ def install():
+ register_creator_plugin(MyTestCreator)
def test_avalon_plugin_presets(monkeypatch, printer):
+ install_host(Test)
- openpype.install()
- api.register_host(Test())
- register_creator_plugin(MyTestCreator)
plugins = discover_creator_plugins()
printer("Test if we got our test plugin")
assert MyTestCreator in plugins
diff --git a/openpype/tools/libraryloader/app.py b/openpype/tools/libraryloader/app.py
index b73b415128..328e16205c 100644
--- a/openpype/tools/libraryloader/app.py
+++ b/openpype/tools/libraryloader/app.py
@@ -16,8 +16,6 @@ from openpype.tools.utils.assets_widget import MultiSelectAssetsWidget
from openpype.modules import ModulesManager
-from . import lib
-
module = sys.modules[__name__]
module.window = None
@@ -260,14 +258,6 @@ class LibraryLoaderWindow(QtWidgets.QDialog):
self.dbcon.Session["AVALON_PROJECT"] = project_name
- _config = lib.find_config()
- if hasattr(_config, "install"):
- _config.install()
- else:
- print(
- "Config `%s` has no function `install`" % _config.__name__
- )
-
self._subsets_widget.on_project_change(project_name)
if self._repres_widget:
self._repres_widget.on_project_change(project_name)
diff --git a/openpype/tools/libraryloader/lib.py b/openpype/tools/libraryloader/lib.py
deleted file mode 100644
index 182b48893a..0000000000
--- a/openpype/tools/libraryloader/lib.py
+++ /dev/null
@@ -1,21 +0,0 @@
-import os
-import importlib
-import logging
-
-log = logging.getLogger(__name__)
-
-
-# `find_config` from `pipeline`
-def find_config():
- log.info("Finding configuration for project..")
-
- config = os.environ["AVALON_CONFIG"]
-
- if not config:
- raise EnvironmentError(
- "No configuration found in "
- "the project nor environment"
- )
-
- log.info("Found %s, loading.." % config)
- return importlib.import_module(config)
diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py
index 923a1fabdb..fad284d82b 100644
--- a/openpype/tools/loader/app.py
+++ b/openpype/tools/loader/app.py
@@ -5,6 +5,7 @@ from avalon import api, io
from openpype import style
from openpype.lib import register_event_callback
+from openpype.pipeline import install_openpype_plugins
from openpype.tools.utils import (
lib,
PlaceholderLineEdit
@@ -608,14 +609,6 @@ def cli(args):
# Store settings
api.Session["AVALON_PROJECT"] = project
- from avalon import pipeline
-
- # Find the set config
- _config = pipeline.find_config()
- if hasattr(_config, "install"):
- _config.install()
- else:
- print("Config `%s` has no function `install`" %
- _config.__name__)
+ install_openpype_plugins(project)
show()
diff --git a/openpype/tools/mayalookassigner/commands.py b/openpype/tools/mayalookassigner/commands.py
index 78fd51c7a3..8fd592d347 100644
--- a/openpype/tools/mayalookassigner/commands.py
+++ b/openpype/tools/mayalookassigner/commands.py
@@ -5,9 +5,12 @@ import os
from bson.objectid import ObjectId
import maya.cmds as cmds
-from avalon import io, api
+from avalon import io
-from openpype.pipeline import remove_container
+from openpype.pipeline import (
+ remove_container,
+ registered_host,
+)
from openpype.hosts.maya.api import lib
from .vray_proxies import get_alembic_ids_cache
@@ -79,7 +82,7 @@ def get_all_asset_nodes():
list: list of dictionaries
"""
- host = api.registered_host()
+ host = registered_host()
nodes = []
for container in host.ls():
@@ -192,7 +195,7 @@ def remove_unused_looks():
"""
- host = api.registered_host()
+ host = registered_host()
unused = []
for container in host.ls():
diff --git a/openpype/tools/mayalookassigner/vray_proxies.py b/openpype/tools/mayalookassigner/vray_proxies.py
index 25621fc652..c97664f3cb 100644
--- a/openpype/tools/mayalookassigner/vray_proxies.py
+++ b/openpype/tools/mayalookassigner/vray_proxies.py
@@ -11,13 +11,14 @@ from bson.objectid import ObjectId
import alembic.Abc
from maya import cmds
-from avalon import io, api
+from avalon import io
from openpype.pipeline import (
load_container,
loaders_from_representation,
discover_loader_plugins,
get_representation_path,
+ registered_host,
)
from openpype.hosts.maya.api import lib
@@ -188,7 +189,7 @@ def load_look(version_id):
"name": "ma"})
# See if representation is already loaded, if so reuse it.
- host = api.registered_host()
+ host = registered_host()
representation_id = str(look_representation['_id'])
for container in host.ls():
if (container['loader'] == "LookLoader" and
diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py
index 6707feac9c..2973d6a5bb 100644
--- a/openpype/tools/publisher/control.py
+++ b/openpype/tools/publisher/control.py
@@ -11,10 +11,12 @@ try:
except Exception:
from openpype.lib.python_2_comp import WeakMethod
-import avalon.api
import pyblish.api
-from openpype.pipeline import PublishValidationError
+from openpype.pipeline import (
+ PublishValidationError,
+ registered_host,
+)
from openpype.pipeline.create import CreateContext
from Qt import QtCore
@@ -353,7 +355,7 @@ class PublisherController:
"""
def __init__(self, dbcon=None, headless=False):
self.log = logging.getLogger("PublisherController")
- self.host = avalon.api.registered_host()
+ self.host = registered_host()
self.headless = headless
self.create_context = CreateContext(
diff --git a/openpype/tools/sceneinventory/model.py b/openpype/tools/sceneinventory/model.py
index 091d6ca925..f8fd8a911a 100644
--- a/openpype/tools/sceneinventory/model.py
+++ b/openpype/tools/sceneinventory/model.py
@@ -7,8 +7,11 @@ from Qt import QtCore, QtGui
import qtawesome
from bson.objectid import ObjectId
-from avalon import api, io, schema
-from openpype.pipeline import HeroVersionType
+from avalon import io, schema
+from openpype.pipeline import (
+ HeroVersionType,
+ registered_host,
+)
from openpype.style import get_default_entity_icon_color
from openpype.tools.utils.models import TreeModel, Item
from openpype.modules import ModulesManager
@@ -181,7 +184,7 @@ class InventoryModel(TreeModel):
def refresh(self, selected=None, items=None):
"""Refresh the model"""
- host = api.registered_host()
+ host = registered_host()
if not items: # for debugging or testing, injecting items from outside
items = host.ls()
diff --git a/openpype/tools/stdout_broker/window.py b/openpype/tools/stdout_broker/window.py
index a2190e0491..f5720ca05b 100644
--- a/openpype/tools/stdout_broker/window.py
+++ b/openpype/tools/stdout_broker/window.py
@@ -1,7 +1,9 @@
-from avalon import style
-from Qt import QtWidgets, QtCore
-import collections
import re
+import collections
+
+from Qt import QtWidgets
+
+from openpype import style
class ConsoleDialog(QtWidgets.QDialog):
diff --git a/openpype/tools/subsetmanager/model.py b/openpype/tools/subsetmanager/model.py
index b76c3c2343..760a167b42 100644
--- a/openpype/tools/subsetmanager/model.py
+++ b/openpype/tools/subsetmanager/model.py
@@ -2,7 +2,7 @@ import uuid
from Qt import QtCore, QtGui
-from avalon import api
+from openpype.pipeline import registered_host
ITEM_ID_ROLE = QtCore.Qt.UserRole + 1
@@ -21,7 +21,7 @@ class InstanceModel(QtGui.QStandardItemModel):
self._instances_by_item_id = {}
instances = None
- host = api.registered_host()
+ host = registered_host()
list_instances = getattr(host, "list_instances", None)
if list_instances:
instances = list_instances()
diff --git a/openpype/tools/subsetmanager/window.py b/openpype/tools/subsetmanager/window.py
index a53af52174..6314e67015 100644
--- a/openpype/tools/subsetmanager/window.py
+++ b/openpype/tools/subsetmanager/window.py
@@ -4,9 +4,8 @@ import sys
from Qt import QtWidgets, QtCore
import qtawesome
-from avalon import api
-
from openpype import style
+from openpype.pipeline import registered_host
from openpype.tools.utils import PlaceholderLineEdit
from openpype.tools.utils.lib import (
iter_model_rows,
@@ -106,7 +105,7 @@ class SubsetManagerWindow(QtWidgets.QDialog):
self._details_widget.set_details(container, item_id)
def _on_save(self):
- host = api.registered_host()
+ host = registered_host()
if not hasattr(host, "save_instances"):
print("BUG: Host does not have \"save_instances\" method")
return
@@ -141,7 +140,7 @@ class SubsetManagerWindow(QtWidgets.QDialog):
# Prepare menu
menu = QtWidgets.QMenu(self)
actions = []
- host = api.registered_host()
+ host = registered_host()
if hasattr(host, "remove_instance"):
action = QtWidgets.QAction("Remove instance", menu)
action.setData(host.remove_instance)
@@ -176,7 +175,7 @@ class SubsetManagerWindow(QtWidgets.QDialog):
self._details_widget.set_details(None, None)
self._model.refresh()
- host = api.registered_host()
+ host = registered_host()
dev_mode = os.environ.get("AVALON_DEVELOP_MODE") or ""
editable = False
if dev_mode.lower() in ("1", "yes", "true", "on"):
diff --git a/openpype/tools/traypublisher/window.py b/openpype/tools/traypublisher/window.py
index d0453c4f23..a550c88ead 100644
--- a/openpype/tools/traypublisher/window.py
+++ b/openpype/tools/traypublisher/window.py
@@ -8,8 +8,8 @@ publishing plugins.
from Qt import QtWidgets, QtCore
-import avalon.api
from avalon.api import AvalonMongoDB
+from openpype.pipeline import install_host
from openpype.hosts.traypublisher import (
api as traypublisher
)
@@ -163,7 +163,7 @@ class TrayPublishWindow(PublisherWindow):
def main():
- avalon.api.install(traypublisher)
+ install_host(traypublisher)
app = QtWidgets.QApplication([])
window = TrayPublishWindow()
window.show()
diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py
index 2d9733ec94..b0c30f6dfb 100644
--- a/openpype/tools/utils/host_tools.py
+++ b/openpype/tools/utils/host_tools.py
@@ -6,6 +6,7 @@ use singleton approach with global functions (using helper anyway).
import os
import avalon.api
import pyblish.api
+from openpype.pipeline import registered_host
from .lib import qt_app_context
@@ -47,7 +48,7 @@ class HostToolsHelper:
Window, validate_host_requirements
)
# Host validation
- host = avalon.api.registered_host()
+ host = registered_host()
validate_host_requirements(host)
workfiles_window = Window(parent=parent)
diff --git a/openpype/tools/utils/lib.py b/openpype/tools/utils/lib.py
index 5abbe01144..8e2044482a 100644
--- a/openpype/tools/utils/lib.py
+++ b/openpype/tools/utils/lib.py
@@ -6,16 +6,17 @@ import collections
from Qt import QtWidgets, QtCore, QtGui
import qtawesome
-import avalon.api
-
-from openpype.style import get_default_entity_icon_color
+from openpype.style import (
+ get_default_entity_icon_color,
+ get_objected_colors,
+)
+from openpype.resources import get_image_path
+from openpype.lib import filter_profiles
from openpype.api import (
get_project_settings,
Logger
)
-from openpype.lib import filter_profiles
-from openpype.style import get_objected_colors
-from openpype.resources import get_image_path
+from openpype.pipeline import registered_host
log = Logger.get_logger(__name__)
@@ -402,7 +403,7 @@ class FamilyConfigCache:
self.family_configs.clear()
# Skip if we're not in host context
- if not avalon.api.registered_host():
+ if not registered_host():
return
# Update the icons from the project configuration
diff --git a/openpype/tools/workfiles/app.py b/openpype/tools/workfiles/app.py
index f0e7900cf5..38e1911060 100644
--- a/openpype/tools/workfiles/app.py
+++ b/openpype/tools/workfiles/app.py
@@ -3,6 +3,7 @@ import logging
from avalon import api
+from openpype.pipeline import registered_host
from openpype.tools.utils import qt_app_context
from .window import Window
@@ -47,7 +48,7 @@ def show(root=None, debug=False, parent=None, use_context=True, save=True):
except (AttributeError, RuntimeError):
pass
- host = api.registered_host()
+ host = registered_host()
validate_host_requirements(host)
if debug:
diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py
index 56af7752da..bb2ded3b94 100644
--- a/openpype/tools/workfiles/files_widget.py
+++ b/openpype/tools/workfiles/files_widget.py
@@ -18,6 +18,7 @@ from openpype.lib.avalon_context import (
update_current_task,
compute_session_changes
)
+from openpype.pipeline import registered_host
from .model import (
WorkAreaFilesModel,
PublishFilesModel,
@@ -93,7 +94,7 @@ class FilesWidget(QtWidgets.QWidget):
# This is not root but workfile directory
self._workfiles_root = None
self._workdir_path = None
- self.host = api.registered_host()
+ self.host = registered_host()
# Whether to automatically select the latest modified
# file on a refresh of the files model.
diff --git a/openpype/tools/workfiles/save_as_dialog.py b/openpype/tools/workfiles/save_as_dialog.py
index f5ae393d0f..0a7c7821ba 100644
--- a/openpype/tools/workfiles/save_as_dialog.py
+++ b/openpype/tools/workfiles/save_as_dialog.py
@@ -11,6 +11,7 @@ from openpype.lib import (
get_last_workfile_with_version,
get_workdir_data,
)
+from openpype.pipeline import registered_host
from openpype.tools.utils import PlaceholderLineEdit
log = logging.getLogger(__name__)
@@ -65,7 +66,7 @@ class CommentMatcher(object):
return
# Create a regex group for extensions
- extensions = api.registered_host().file_extensions()
+ extensions = registered_host().file_extensions()
any_extension = "(?:{})".format(
"|".join(re.escape(ext[1:]) for ext in extensions)
)
@@ -200,7 +201,7 @@ class SaveAsDialog(QtWidgets.QDialog):
self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint)
self.result = None
- self.host = api.registered_host()
+ self.host = registered_host()
self.root = root
self.work_file = None
self._extensions = extensions
diff --git a/tests/integration/conftest.py b/tests/conftest.py
similarity index 100%
rename from tests/integration/conftest.py
rename to tests/conftest.py
diff --git a/tests/unit/openpype/modules/sync_server/test_module_api.py b/tests/unit/openpype/modules/sync_server/test_module_api.py
new file mode 100644
index 0000000000..a484977758
--- /dev/null
+++ b/tests/unit/openpype/modules/sync_server/test_module_api.py
@@ -0,0 +1,64 @@
+"""Test file for Sync Server, tests API methods, currently for integrate_new
+
+ File:
+ creates temporary directory and downloads .zip file from GDrive
+ unzips .zip file
+ uses content of .zip file (MongoDB's dumps) to import to new databases
+ with use of 'monkeypatch_session' modifies required env vars
+ temporarily
+ runs battery of tests checking that site operation for Sync Server
+ module are working
+ removes temporary folder
+ removes temporary databases (?)
+"""
+import pytest
+
+from tests.lib.testing_classes import ModuleUnitTest
+
+
+class TestModuleApi(ModuleUnitTest):
+
+ REPRESENTATION_ID = "60e578d0c987036c6a7b741d"
+
+ TEST_FILES = [("1eCwPljuJeOI8A3aisfOIBKKjcmIycTEt",
+ "test_site_operations.zip", '')]
+
+ @pytest.fixture(scope="module")
+ def setup_sync_server_module(self, dbcon):
+ """Get sync_server_module from ModulesManager"""
+ from openpype.modules import ModulesManager
+
+ manager = ModulesManager()
+ sync_server = manager.modules_by_name["sync_server"]
+ yield sync_server
+
+ def test_get_alt_site_pairs(self, setup_sync_server_module):
+ conf_sites = {"SFTP": {"alternative_sites": ["studio"]},
+ "studio2": {"alternative_sites": ["studio"]}}
+
+ ret = setup_sync_server_module._get_alt_site_pairs(conf_sites)
+ expected = {"SFTP": {"studio", "studio2"},
+ "studio": {"SFTP", "studio2"},
+ "studio2": {"studio", "SFTP"}}
+ assert ret == expected, "Not matching result"
+
+ def test_get_alt_site_pairs_deep(self, setup_sync_server_module):
+ conf_sites = {"A": {"alternative_sites": ["C"]},
+ "B": {"alternative_sites": ["C"]},
+ "C": {"alternative_sites": ["D"]},
+ "D": {"alternative_sites": ["A"]},
+ "F": {"alternative_sites": ["G"]},
+ "G": {"alternative_sites": ["F"]},
+ }
+
+ ret = setup_sync_server_module._get_alt_site_pairs(conf_sites)
+ expected = {"A": {"B", "C", "D"},
+ "B": {"A", "C", "D"},
+ "C": {"A", "B", "D"},
+ "D": {"A", "B", "C"},
+ "F": {"G"},
+ "G": {"F"}}
+ assert ret == expected, "Not matching result"
+
+
+test_case = TestModuleApi()
diff --git a/website/docs/artist_hosts_hiero.md b/website/docs/artist_hosts_hiero.md
index f516c3a6e0..dc6f1696e7 100644
--- a/website/docs/artist_hosts_hiero.md
+++ b/website/docs/artist_hosts_hiero.md
@@ -94,6 +94,8 @@ This tool will set any defined colorspace definition from OpenPype `Settings / P
With OpenPype, you can use Hiero/NKS as a starting point for creating a project's **shots** as *assets* from timeline clips with its *hierarchycal parents* like **episodes**, **sequences**, **folders**, and its child **tasks**. Most importantly it will create **versions** of plate *subsets*, with or without **reference video**. Publishig is naturally creating clip's **thumbnails** and assigns it to shot *asset*. Hiero is also publishing **audio** *subset* and various **soft-effects** either as retiming component as part of published plates or **color-tranformations**, that will be evailable later on for compositor artists to use either as *viewport input-process* or *loaded nodes* in graph editor.
+
+
### Preparing timeline for conversion to instances
Because we don't support on-fly data conversion so in case of working with raw camera sources or some other formats which need to be converted for 2D/3D work. We suggest to convert those before and reconform the timeline. Before any clips in timeline could be converted to publishable instances we recommend following.
1. Merge all tracks which supposed to be one and they are multiply only because of editor's style
@@ -191,3 +193,12 @@ If you wish to change any individual properties of the shot then you are able to
+
+### Publishing Effects from Hiero to Nuke
+This video shows a way to publish shot look as effect from Hiero to Nuke.
+
+
+
+### Assembling edit from published shot versions
+
+
diff --git a/website/docs/artist_hosts_nuke_tut.md b/website/docs/artist_hosts_nuke_tut.md
index eefb213dd2..296fdf44d5 100644
--- a/website/docs/artist_hosts_nuke_tut.md
+++ b/website/docs/artist_hosts_nuke_tut.md
@@ -89,6 +89,8 @@ This menu item will set correct Colorspace definitions for you. All has to be co
- set preview LUT to your viewers
- set correct colorspace to all discovered Read nodes (following expression set in settings)
+See [Nuke Color Management](artist_hosts_nuke_tut.md#nuke-color-management)
+