diff --git a/openpype/hosts/celaction/api/cli.py b/openpype/hosts/celaction/api/cli.py
index acd9da8229..bc1e3eaf89 100644
--- a/openpype/hosts/celaction/api/cli.py
+++ b/openpype/hosts/celaction/api/cli.py
@@ -4,7 +4,6 @@ import copy
import argparse
from avalon import io
-from avalon.tools import publish
import pyblish.api
import pyblish.util
@@ -13,6 +12,7 @@ from openpype.api import Logger
import openpype
import openpype.hosts.celaction
from openpype.hosts.celaction import api as celaction
+from openpype.tools.utils import host_tools
log = Logger().get_logger("Celaction_cli_publisher")
@@ -82,7 +82,7 @@ def main():
pyblish.api.register_host(publish_host)
- return publish.show()
+ return host_tools.show_publish()
if __name__ == "__main__":
diff --git a/openpype/hosts/fusion/api/__init__.py b/openpype/hosts/fusion/api/__init__.py
index 5581a0a9cb..5aee978c15 100644
--- a/openpype/hosts/fusion/api/__init__.py
+++ b/openpype/hosts/fusion/api/__init__.py
@@ -1,8 +1,6 @@
from .pipeline import (
install,
- uninstall,
- publish,
- launch_workfiles_app
+ uninstall
)
from .utils import (
@@ -22,12 +20,9 @@ __all__ = [
# pipeline
"install",
"uninstall",
- "publish",
- "launch_workfiles_app",
# utils
"setup",
- "get_resolve_module",
# lib
"get_additional_data",
diff --git a/openpype/hosts/fusion/api/menu.py b/openpype/hosts/fusion/api/menu.py
index 9093aa9e5e..5d2efb4911 100644
--- a/openpype/hosts/fusion/api/menu.py
+++ b/openpype/hosts/fusion/api/menu.py
@@ -3,19 +3,7 @@ import sys
from Qt import QtWidgets, QtCore
-from .pipeline import (
- publish,
- launch_workfiles_app
-)
-
-from avalon.tools import (
- creator,
- sceneinventory,
-)
-from openpype.tools import (
- loader,
- libraryloader
-)
+from openpype.tools.utils import host_tools
from openpype.hosts.fusion.scripts import (
set_rendermode,
@@ -36,7 +24,7 @@ def load_stylesheet():
class Spacer(QtWidgets.QWidget):
def __init__(self, height, *args, **kwargs):
- super(self.__class__, self).__init__(*args, **kwargs)
+ super(Spacer, self).__init__(*args, **kwargs)
self.setFixedHeight(height)
@@ -53,7 +41,7 @@ class Spacer(QtWidgets.QWidget):
class OpenPypeMenu(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
- super(self.__class__, self).__init__(*args, **kwargs)
+ super(OpenPypeMenu, self).__init__(*args, **kwargs)
self.setObjectName("OpenPypeMenu")
@@ -117,27 +105,27 @@ class OpenPypeMenu(QtWidgets.QWidget):
def on_workfile_clicked(self):
print("Clicked Workfile")
- launch_workfiles_app()
+ host_tools.show_workfiles()
def on_create_clicked(self):
print("Clicked Create")
- creator.show()
+ host_tools.show_creator()
def on_publish_clicked(self):
print("Clicked Publish")
- publish(None)
+ host_tools.show_publish()
def on_load_clicked(self):
print("Clicked Load")
- loader.show(use_context=True)
+ host_tools.show_loader(use_context=True)
def on_inventory_clicked(self):
print("Clicked Inventory")
- sceneinventory.show()
+ host_tools.show_scene_inventory()
def on_libload_clicked(self):
print("Clicked Library")
- libraryloader.show()
+ host_tools.show_library_loader()
def on_rendernode_clicked(self):
from avalon import style
diff --git a/openpype/hosts/fusion/api/pipeline.py b/openpype/hosts/fusion/api/pipeline.py
index 688e75f6fe..c721146830 100644
--- a/openpype/hosts/fusion/api/pipeline.py
+++ b/openpype/hosts/fusion/api/pipeline.py
@@ -3,7 +3,6 @@ Basic avalon integration
"""
import os
-from openpype.tools import workfiles
from avalon import api as avalon
from pyblish import api as pyblish
from openpype.api import Logger
@@ -98,14 +97,3 @@ def on_pyblish_instance_toggled(instance, new_value, old_value):
current = attrs["TOOLB_PassThrough"]
if current != passthrough:
tool.SetAttrs({"TOOLB_PassThrough": passthrough})
-
-
-def launch_workfiles_app(*args):
- workdir = os.environ["AVALON_WORKDIR"]
- workfiles.show(workdir)
-
-
-def publish(parent):
- """Shorthand to publish from within host"""
- from avalon.tools import publish
- return publish.show(parent)
diff --git a/openpype/hosts/harmony/api/__init__.py b/openpype/hosts/harmony/api/__init__.py
index fd21725bd5..bcf7dffbe7 100644
--- a/openpype/hosts/harmony/api/__init__.py
+++ b/openpype/hosts/harmony/api/__init__.py
@@ -3,17 +3,14 @@
import os
from pathlib import Path
import logging
-import re
from openpype import lib
-from openpype.api import (get_current_project_settings)
import openpype.hosts.harmony
import pyblish.api
from avalon import io, harmony
import avalon.api
-import avalon.tools.sceneinventory
log = logging.getLogger("openpype.hosts.harmony")
diff --git a/openpype/hosts/hiero/api/menu.py b/openpype/hosts/hiero/api/menu.py
index bcd78aa5bb..e3de220777 100644
--- a/openpype/hosts/hiero/api/menu.py
+++ b/openpype/hosts/hiero/api/menu.py
@@ -2,6 +2,7 @@ import os
import sys
import hiero.core
from openpype.api import Logger
+from openpype.tools.utils import host_tools
from avalon.api import Session
from hiero.ui import findMenuAction
@@ -41,8 +42,6 @@ def menu_install():
apply_colorspace_project, apply_colorspace_clips
)
# here is the best place to add menu
- from avalon.tools import creator, sceneinventory
- from openpype.tools import loader
from avalon.vendor.Qt import QtGui
menu_name = os.environ['AVALON_LABEL']
@@ -87,15 +86,15 @@ def menu_install():
creator_action = menu.addAction("Create ...")
creator_action.setIcon(QtGui.QIcon("icons:CopyRectangle.png"))
- creator_action.triggered.connect(creator.show)
+ creator_action.triggered.connect(host_tools.show_creator)
loader_action = menu.addAction("Load ...")
loader_action.setIcon(QtGui.QIcon("icons:CopyRectangle.png"))
- loader_action.triggered.connect(loader.show)
+ loader_action.triggered.connect(host_tools.show_loader)
sceneinventory_action = menu.addAction("Manage ...")
sceneinventory_action.setIcon(QtGui.QIcon("icons:CopyRectangle.png"))
- sceneinventory_action.triggered.connect(sceneinventory.show)
+ sceneinventory_action.triggered.connect(host_tools.show_scene_inventory)
menu.addSeparator()
if os.getenv("OPENPYPE_DEVELOP"):
diff --git a/openpype/hosts/hiero/api/pipeline.py b/openpype/hosts/hiero/api/pipeline.py
index 12f6923de7..6f6588e1be 100644
--- a/openpype/hosts/hiero/api/pipeline.py
+++ b/openpype/hosts/hiero/api/pipeline.py
@@ -4,13 +4,12 @@ Basic avalon integration
import os
import contextlib
from collections import OrderedDict
-from avalon.tools import publish as _publish
-from openpype.tools import workfiles
from avalon.pipeline import AVALON_CONTAINER_ID
from avalon import api as avalon
from avalon import schema
from pyblish import api as pyblish
from openpype.api import Logger
+from openpype.tools.utils import host_tools
from . import lib, menu, events
log = Logger().get_logger(__name__)
@@ -211,15 +210,13 @@ def update_container(track_item, data=None):
def launch_workfiles_app(*args):
''' Wrapping function for workfiles launcher '''
- workdir = os.environ["AVALON_WORKDIR"]
-
# show workfile gui
- workfiles.show(workdir)
+ host_tools.show_workfiles()
def publish(parent):
"""Shorthand to publish from within host"""
- return _publish.show(parent)
+ return host_tools.show_publish(parent)
@contextlib.contextmanager
diff --git a/openpype/hosts/houdini/startup/MainMenuCommon.xml b/openpype/hosts/houdini/startup/MainMenuCommon.xml
index 76585085e2..2b556a2e75 100644
--- a/openpype/hosts/houdini/startup/MainMenuCommon.xml
+++ b/openpype/hosts/houdini/startup/MainMenuCommon.xml
@@ -7,24 +7,30 @@
@@ -32,9 +38,9 @@ cbsceneinventory.show()
@@ -43,9 +49,10 @@ publish.show(parent)
diff --git a/openpype/hosts/maya/api/__init__.py b/openpype/hosts/maya/api/__init__.py
index 13f8a4cb78..d1c13b04d5 100644
--- a/openpype/hosts/maya/api/__init__.py
+++ b/openpype/hosts/maya/api/__init__.py
@@ -8,7 +8,7 @@ from avalon import api as avalon
from avalon import pipeline
from avalon.maya import suspended_refresh
from avalon.maya.pipeline import IS_HEADLESS
-from openpype.tools import workfiles
+from openpype.tools.utils import host_tools
from pyblish import api as pyblish
from openpype.lib import any_outdated
import openpype.hosts.maya
@@ -208,16 +208,12 @@ def on_init(_):
launch_workfiles = os.environ.get("WORKFILES_STARTUP")
if launch_workfiles:
- safe_deferred(launch_workfiles_app)
+ safe_deferred(host_tools.show_workfiles)
if not IS_HEADLESS:
safe_deferred(override_toolbox_ui)
-def launch_workfiles_app():
- workfiles.show(os.environ["AVALON_WORKDIR"])
-
-
def on_before_save(return_code, _):
"""Run validation for scene's FPS prior to saving"""
return lib.validate_fps()
diff --git a/openpype/hosts/maya/api/customize.py b/openpype/hosts/maya/api/customize.py
index a84412963b..8474262626 100644
--- a/openpype/hosts/maya/api/customize.py
+++ b/openpype/hosts/maya/api/customize.py
@@ -1,10 +1,16 @@
"""A set of commands that install overrides to Maya's UI"""
+import os
+import logging
+
+from functools import partial
+
import maya.cmds as mc
import maya.mel as mel
-from functools import partial
-import os
-import logging
+
+from avalon.maya import pipeline
+from openpype.api import resources
+from openpype.tools.utils import host_tools
log = logging.getLogger(__name__)
@@ -69,39 +75,8 @@ def override_component_mask_commands():
def override_toolbox_ui():
"""Add custom buttons in Toolbox as replacement for Maya web help icon."""
- inventory = None
- loader = None
- launch_workfiles_app = None
- mayalookassigner = None
- try:
- import avalon.tools.sceneinventory as inventory
- except Exception:
- log.warning("Could not import SceneInventory tool")
-
- try:
- import openpype.tools.loader as loader
- except Exception:
- log.warning("Could not import Loader tool")
-
- try:
- from avalon.maya.pipeline import launch_workfiles_app
- except Exception:
- log.warning("Could not import Workfiles tool")
-
- try:
- from openpype.tools import mayalookassigner
- except Exception:
- log.warning("Could not import Maya Look assigner tool")
-
- from openpype.api import resources
-
icons = resources.get_resource("icons")
- if not any((
- mayalookassigner, launch_workfiles_app, loader, inventory
- )):
- return
-
# Ensure the maya web icon on toolbox exists
web_button = "ToolBox|MainToolboxLayout|mayaWebButton"
if not mc.iconTextButton(web_button, query=True, exists=True):
@@ -120,14 +95,23 @@ def override_toolbox_ui():
# Create our controls
background_color = (0.267, 0.267, 0.267)
controls = []
- if mayalookassigner:
+ look_assigner = None
+ try:
+ look_assigner = host_tools.get_tool_by_name(
+ "lookassigner",
+ parent=pipeline._parent
+ )
+ except Exception:
+ log.warning("Couldn't create Look assigner window.", exc_info=True)
+
+ if look_assigner is not None:
controls.append(
mc.iconTextButton(
"pype_toolbox_lookmanager",
annotation="Look Manager",
label="Look Manager",
image=os.path.join(icons, "lookmanager.png"),
- command=lambda: mayalookassigner.show(),
+ command=host_tools.show_look_assigner,
bgc=background_color,
width=icon_size,
height=icon_size,
@@ -135,50 +119,53 @@ def override_toolbox_ui():
)
)
- if launch_workfiles_app:
- controls.append(
- mc.iconTextButton(
- "pype_toolbox_workfiles",
- annotation="Work Files",
- label="Work Files",
- image=os.path.join(icons, "workfiles.png"),
- command=lambda: launch_workfiles_app(),
- bgc=background_color,
- width=icon_size,
- height=icon_size,
- parent=parent
- )
+ controls.append(
+ mc.iconTextButton(
+ "pype_toolbox_workfiles",
+ annotation="Work Files",
+ label="Work Files",
+ image=os.path.join(icons, "workfiles.png"),
+ command=lambda: host_tools.show_workfiles(
+ parent=pipeline._parent
+ ),
+ bgc=background_color,
+ width=icon_size,
+ height=icon_size,
+ parent=parent
)
+ )
- if loader:
- controls.append(
- mc.iconTextButton(
- "pype_toolbox_loader",
- annotation="Loader",
- label="Loader",
- image=os.path.join(icons, "loader.png"),
- command=lambda: loader.show(use_context=True),
- bgc=background_color,
- width=icon_size,
- height=icon_size,
- parent=parent
- )
+ controls.append(
+ mc.iconTextButton(
+ "pype_toolbox_loader",
+ annotation="Loader",
+ label="Loader",
+ image=os.path.join(icons, "loader.png"),
+ command=lambda: host_tools.show_loader(
+ parent=pipeline._parent, use_context=True
+ ),
+ bgc=background_color,
+ width=icon_size,
+ height=icon_size,
+ parent=parent
)
+ )
- if inventory:
- controls.append(
- mc.iconTextButton(
- "pype_toolbox_manager",
- annotation="Inventory",
- label="Inventory",
- image=os.path.join(icons, "inventory.png"),
- command=lambda: inventory.show(),
- bgc=background_color,
- width=icon_size,
- height=icon_size,
- parent=parent
- )
+ controls.append(
+ mc.iconTextButton(
+ "pype_toolbox_manager",
+ annotation="Inventory",
+ label="Inventory",
+ image=os.path.join(icons, "inventory.png"),
+ command=lambda: host_tools.show_scene_inventory(
+ parent=pipeline._parent
+ ),
+ bgc=background_color,
+ width=icon_size,
+ height=icon_size,
+ parent=parent
)
+ )
# Add the buttons on the bottom and stack
# them above each other with side padding
diff --git a/openpype/hosts/maya/api/menu.py b/openpype/hosts/maya/api/menu.py
index ad225dcd28..4f0966abfd 100644
--- a/openpype/hosts/maya/api/menu.py
+++ b/openpype/hosts/maya/api/menu.py
@@ -2,13 +2,15 @@ import sys
import os
import logging
-from avalon.vendor.Qt import QtWidgets, QtGui
-from avalon.maya import pipeline
-from openpype.api import BuildWorkfile
-import maya.cmds as cmds
-from openpype.settings import get_project_settings
+from Qt import QtWidgets, QtGui
-self = sys.modules[__name__]
+import maya.cmds as cmds
+
+from avalon.maya import pipeline
+
+from openpype.api import BuildWorkfile
+from openpype.settings import get_project_settings
+from openpype.tools.utils import host_tools
log = logging.getLogger(__name__)
@@ -36,25 +38,15 @@ def deferred():
)
def add_look_assigner_item():
- import mayalookassigner
cmds.menuItem(
"Look assigner",
parent=pipeline._menu,
- command=lambda *args: mayalookassigner.show()
+ command=lambda *args: host_tools.show_look_assigner(
+ pipeline._parent
+ )
)
def modify_workfiles():
- from openpype.tools import workfiles
-
- def launch_workfiles_app(*_args, **_kwargs):
- workfiles.show(
- os.path.join(
- cmds.workspace(query=True, rootDirectory=True),
- cmds.workspace(fileRuleEntry="scene")
- ),
- parent=pipeline._parent
- )
-
# Find the pipeline menu
top_menu = _get_menu()
@@ -75,7 +67,7 @@ def deferred():
cmds.menuItem(
"Work Files",
parent=pipeline._menu,
- command=launch_workfiles_app,
+ command=lambda *args: host_tools.show_workfiles(pipeline._parent),
insertAfter=after_action
)
diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py
index 575cc2456b..d2f277329a 100644
--- a/openpype/hosts/maya/plugins/publish/collect_render.py
+++ b/openpype/hosts/maya/plugins/publish/collect_render.py
@@ -244,17 +244,17 @@ class CollectMayaRender(pyblish.api.ContextPlugin):
# metadata file will be located in top-most common
# directory.
# TODO: use `os.path.commonpath()` after switch to Python 3
+ publish_meta_path = os.path.normpath(publish_meta_path)
common_publish_meta_path = os.path.splitdrive(
publish_meta_path)[0]
if common_publish_meta_path:
common_publish_meta_path += os.path.sep
- for part in publish_meta_path.split("/"):
+ for part in publish_meta_path.replace(
+ common_publish_meta_path, "").split(os.path.sep):
common_publish_meta_path = os.path.join(
common_publish_meta_path, part)
if part == expected_layer_name:
break
- common_publish_meta_path = common_publish_meta_path.replace(
- "\\", "/")
self.log.info(
"Publish meta path: {}".format(common_publish_meta_path))
diff --git a/openpype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py
index f112ba8255..9ee3a4464b 100644
--- a/openpype/hosts/nuke/api/lib.py
+++ b/openpype/hosts/nuke/api/lib.py
@@ -7,7 +7,6 @@ from collections import OrderedDict
from avalon import api, io, lib
-from openpype.tools import workfiles
import avalon.nuke
from avalon.nuke import lib as anlib
from avalon.nuke import (
@@ -24,7 +23,7 @@ from openpype.api import (
get_current_project_settings,
ApplicationManager
)
-
+from openpype.tools.utils import host_tools
import nuke
from .utils import set_context_favorites
@@ -1662,7 +1661,7 @@ def launch_workfiles_app():
if not opnl.workfiles_launched:
opnl.workfiles_launched = True
- workfiles.show(os.environ["AVALON_WORKDIR"])
+ host_tools.show_workfiles()
def process_workfile_builder():
diff --git a/openpype/hosts/nuke/api/menu.py b/openpype/hosts/nuke/api/menu.py
index 021ea04159..87990c5e92 100644
--- a/openpype/hosts/nuke/api/menu.py
+++ b/openpype/hosts/nuke/api/menu.py
@@ -4,7 +4,7 @@ from avalon.api import Session
from .lib import WorkfileSettings
from openpype.api import Logger, BuildWorkfile, get_current_project_settings
-from openpype.tools import workfiles
+from openpype.tools.utils import host_tools
log = Logger().get_logger(__name__)
@@ -25,7 +25,7 @@ def install():
menu.removeItem(rm_item[1].name())
menu.addCommand(
name,
- workfiles.show,
+ host_tools.show_workfiles,
index=2
)
menu.addSeparator(index=3)
diff --git a/openpype/hosts/resolve/api/menu.py b/openpype/hosts/resolve/api/menu.py
index c639fd2db8..262ce739dd 100644
--- a/openpype/hosts/resolve/api/menu.py
+++ b/openpype/hosts/resolve/api/menu.py
@@ -8,15 +8,7 @@ from .pipeline import (
launch_workfiles_app
)
-from avalon.tools import (
- creator,
- sceneinventory,
- subsetmanager
-)
-from openpype.tools import (
- loader,
- libraryloader,
-)
+from openpype.tools.utils import host_tools
def load_stylesheet():
@@ -32,7 +24,7 @@ def load_stylesheet():
class Spacer(QtWidgets.QWidget):
def __init__(self, height, *args, **kwargs):
- super(self.__class__, self).__init__(*args, **kwargs)
+ super(Spacer, self).__init__(*args, **kwargs)
self.setFixedHeight(height)
@@ -49,7 +41,7 @@ class Spacer(QtWidgets.QWidget):
class OpenPypeMenu(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
- super(self.__class__, self).__init__(*args, **kwargs)
+ super(OpenPypeMenu, self).__init__(*args, **kwargs)
self.setObjectName("OpenPypeMenu")
@@ -119,7 +111,7 @@ class OpenPypeMenu(QtWidgets.QWidget):
def on_create_clicked(self):
print("Clicked Create")
- creator.show()
+ host_tools.show_creator()
def on_publish_clicked(self):
print("Clicked Publish")
@@ -127,19 +119,19 @@ class OpenPypeMenu(QtWidgets.QWidget):
def on_load_clicked(self):
print("Clicked Load")
- loader.show(use_context=True)
+ host_tools.show_loader(use_context=True)
def on_inventory_clicked(self):
print("Clicked Inventory")
- sceneinventory.show()
+ host_tools.show_scene_inventory()
def on_subsetm_clicked(self):
print("Clicked Subset Manager")
- subsetmanager.show()
+ host_tools.show_subset_manager()
def on_libload_clicked(self):
print("Clicked Library")
- libraryloader.show()
+ host_tools.show_library_loader()
def on_rename_clicked(self):
print("Clicked Rename")
diff --git a/openpype/hosts/resolve/api/pipeline.py b/openpype/hosts/resolve/api/pipeline.py
index 80249310e8..ce95cfe02a 100644
--- a/openpype/hosts/resolve/api/pipeline.py
+++ b/openpype/hosts/resolve/api/pipeline.py
@@ -4,7 +4,6 @@ Basic avalon integration
import os
import contextlib
from collections import OrderedDict
-from openpype.tools import workfiles
from avalon import api as avalon
from avalon import schema
from avalon.pipeline import AVALON_CONTAINER_ID
@@ -12,6 +11,7 @@ from pyblish import api as pyblish
from openpype.api import Logger
from . import lib
from . import PLUGINS_DIR
+from openpype.tools.utils import host_tools
log = Logger().get_logger(__name__)
PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish")
@@ -212,14 +212,12 @@ def update_container(timeline_item, data=None):
def launch_workfiles_app(*args):
- workdir = os.environ["AVALON_WORKDIR"]
- workfiles.show(workdir)
+ host_tools.show_workfiles()
def publish(parent):
"""Shorthand to publish from within host"""
- from avalon.tools import publish
- return publish.show(parent)
+ return host_tools.show_publish()
@contextlib.contextmanager
diff --git a/openpype/tools/libraryloader/app.py b/openpype/tools/libraryloader/app.py
index 8080c547c9..3f11157418 100644
--- a/openpype/tools/libraryloader/app.py
+++ b/openpype/tools/libraryloader/app.py
@@ -38,7 +38,10 @@ class LibraryLoaderWindow(QtWidgets.QDialog):
# Enable minimize and maximize for app
self.setWindowTitle(self.tool_title)
- self.setWindowFlags(QtCore.Qt.Window)
+ window_flags = QtCore.Qt.Window
+ if not parent:
+ window_flags |= QtCore.Qt.WindowStaysOnTopHint
+ self.setWindowFlags(window_flags)
self.setFocusPolicy(QtCore.Qt.StrongFocus)
if icon is not None:
self.setWindowIcon(icon)
diff --git a/openpype/tools/loader/app.py b/openpype/tools/loader/app.py
index c18b6e798a..bc0eef3bca 100644
--- a/openpype/tools/loader/app.py
+++ b/openpype/tools/loader/app.py
@@ -51,7 +51,10 @@ class LoaderWindow(QtWidgets.QDialog):
self.family_config_cache = lib.FamilyConfigCache(io)
# Enable minimize and maximize for app
- self.setWindowFlags(QtCore.Qt.Window)
+ window_flags = QtCore.Qt.Window
+ if not parent:
+ window_flags |= QtCore.Qt.WindowStaysOnTopHint
+ self.setWindowFlags(window_flags)
self.setFocusPolicy(QtCore.Qt.StrongFocus)
body = QtWidgets.QWidget()
diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py
index 6b94fc6e44..1ccbb5796d 100644
--- a/openpype/tools/loader/widgets.py
+++ b/openpype/tools/loader/widgets.py
@@ -786,7 +786,10 @@ class ThumbnailWidget(QtWidgets.QLabel):
def scale_pixmap(self, pixmap):
return pixmap.scaled(
- self.width(), self.height(), QtCore.Qt.KeepAspectRatio
+ self.width(),
+ self.height(),
+ QtCore.Qt.KeepAspectRatio,
+ QtCore.Qt.SmoothTransformation
)
def set_thumbnail(self, entity=None):
diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py
new file mode 100644
index 0000000000..ee184ccf2d
--- /dev/null
+++ b/openpype/tools/utils/host_tools.py
@@ -0,0 +1,357 @@
+"""Single access point to all tools usable in hosts.
+
+It is possible to create `HostToolsHelper` in host implementaion or
+use singleton approach with global functions (using helper anyway).
+"""
+
+import avalon.api
+
+
+class HostToolsHelper:
+ """Create and cache tool windows in memory.
+
+ Almost all methods expect parent widget but the parent is used only on
+ first tool creation.
+
+ Class may also contain tools that are available only for one or few hosts.
+ """
+ def __init__(self, parent=None):
+ self._log = None
+ # Global parent for all tools (may and may not be set)
+ self._parent = parent
+
+ # Prepare attributes for all tools
+ self._workfiles_tool = None
+ self._loader_tool = None
+ self._creator_tool = None
+ self._subset_manager_tool = None
+ self._scene_inventory_tool = None
+ self._library_loader_tool = None
+ self._look_assigner_tool = None
+
+ @property
+ def log(self):
+ if self._log is None:
+ from openpype.api import Logger
+
+ self._log = Logger.get_logger(self.__class__.__name__)
+ return self._log
+
+ def get_workfiles_tool(self, parent):
+ """Create, cache and return workfiles tool window."""
+ if self._workfiles_tool is None:
+ from avalon import style
+ from openpype.tools.workfiles.app import (
+ Window, validate_host_requirements
+ )
+ # Host validation
+ host = avalon.api.registered_host()
+ validate_host_requirements(host)
+
+ workfiles_window = Window(parent=parent)
+ workfiles_window.setStyleSheet(style.load_stylesheet())
+ self._workfiles_tool = workfiles_window
+
+ return self._workfiles_tool
+
+ def show_workfiles(self, parent=None, use_context=None, save=None):
+ """Workfiles tool for changing context and saving workfiles."""
+ if use_context is None:
+ use_context = True
+
+ if save is None:
+ save = True
+
+ workfiles_tool = self.get_workfiles_tool(parent)
+ if use_context:
+ context = {
+ "asset": avalon.api.Session["AVALON_ASSET"],
+ "silo": avalon.api.Session["AVALON_SILO"],
+ "task": avalon.api.Session["AVALON_TASK"]
+ }
+ workfiles_tool.set_context(context)
+
+ if save:
+ workfiles_tool.set_save_enabled(save)
+
+ workfiles_tool.refresh()
+ workfiles_tool.show()
+ # Pull window to the front.
+ workfiles_tool.raise_()
+ workfiles_tool.activateWindow()
+
+ def get_loader_tool(self, parent):
+ """Create, cache and return loader tool window."""
+ if self._loader_tool is None:
+ from avalon import style
+ from openpype.tools.loader import LoaderWindow
+
+ loader_window = LoaderWindow(parent=parent or self._parent)
+ loader_window.setStyleSheet(style.load_stylesheet())
+ self._loader_tool = loader_window
+
+ return self._loader_tool
+
+ def show_loader(self, parent=None, use_context=None):
+ """Loader tool for loading representations."""
+ if use_context is None:
+ use_context = False
+ loader_tool = self.get_loader_tool(parent)
+
+ if use_context:
+ context = {"asset": avalon.api.Session["AVALON_ASSET"]}
+ loader_tool.set_context(context, refresh=True)
+ else:
+ loader_tool.refresh()
+
+ loader_tool.show()
+ loader_tool.raise_()
+ loader_tool.activateWindow()
+ loader_tool.refresh()
+
+ def get_creator_tool(self, parent):
+ """Create, cache and return creator tool window."""
+ if self._creator_tool is None:
+ from avalon import style
+ from avalon.tools.creator.app import Window
+
+ creator_window = Window(parent=parent or self._parent)
+ creator_window.setStyleSheet(style.load_stylesheet())
+ self._creator_tool = creator_window
+
+ return self._creator_tool
+
+ def show_creator(self, parent=None):
+ """Show tool to create new instantes for publishing."""
+ creator_tool = self.get_creator_tool(parent)
+ creator_tool.refresh()
+ creator_tool.show()
+
+ # Pull window to the front.
+ creator_tool.raise_()
+ creator_tool.activateWindow()
+
+ def get_subset_manager_tool(self, parent):
+ """Create, cache and return subset manager tool window."""
+ if self._subset_manager_tool is None:
+ from avalon import style
+ from avalon.tools.subsetmanager import Window
+
+ subset_manager_window = Window(parent=parent or self._parent)
+ subset_manager_window.setStyleSheet(style.load_stylesheet())
+ self._subset_manager_tool = subset_manager_window
+
+ return self._subset_manager_tool
+
+ def show_subset_manager(self, parent=None):
+ """Show tool display/remove existing created instances."""
+ subset_manager_tool = self.get_subset_manager_tool(parent)
+ subset_manager_tool.show()
+
+ # Pull window to the front.
+ subset_manager_tool.raise_()
+ subset_manager_tool.activateWindow()
+
+ def get_scene_inventory_tool(self, parent):
+ """Create, cache and return scene inventory tool window."""
+ if self._scene_inventory_tool is None:
+ from avalon import style
+ from avalon.tools.sceneinventory.app import Window
+
+ scene_inventory_window = Window(parent=parent or self._parent)
+ scene_inventory_window.setStyleSheet(style.load_stylesheet())
+ self._scene_inventory_tool = scene_inventory_window
+
+ return self._scene_inventory_tool
+
+ def show_scene_inventory(self, parent=None):
+ """Show tool maintain loaded containers."""
+ scene_inventory_tool = self.get_scene_inventory_tool(parent)
+ scene_inventory_tool.show()
+ scene_inventory_tool.refresh()
+
+ # Pull window to the front.
+ scene_inventory_tool.raise_()
+ scene_inventory_tool.activateWindow()
+
+ def get_library_loader_tool(self, parent):
+ """Create, cache and return library loader tool window."""
+ if self._library_loader_tool is None:
+ from avalon import style
+ from openpype.tools.libraryloader import LibraryLoaderWindow
+
+ library_window = LibraryLoaderWindow(
+ parent=parent or self._parent
+ )
+ library_window.setStyleSheet(style.load_stylesheet())
+ self._library_loader_tool = library_window
+
+ return self._library_loader_tool
+
+ def show_library_loader(self, parent=None):
+ """Loader tool for loading representations from library project."""
+ library_loader_tool = self.get_library_loader_tool(parent)
+ library_loader_tool.show()
+ library_loader_tool.raise_()
+ library_loader_tool.activateWindow()
+ library_loader_tool.refresh()
+
+ def show_publish(self, parent=None):
+ """Publish UI."""
+ from avalon.tools import publish
+
+ publish.show(parent)
+
+ def get_look_assigner_tool(self, parent):
+ """Create, cache and return look assigner tool window."""
+ if self._look_assigner_tool is None:
+ from avalon import style
+ import mayalookassigner
+
+ mayalookassigner_window = mayalookassigner.App(parent)
+ mayalookassigner_window.setStyleSheet(style.load_stylesheet())
+ 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."""
+ look_assigner_tool = self.get_look_assigner_tool(parent)
+ look_assigner_tool.show()
+
+ def get_tool_by_name(self, tool_name, parent=None, *args, **kwargs):
+ """Show tool by it's name.
+
+ This is helper for
+ """
+ if tool_name == "workfiles":
+ return self.get_workfiles_tool(parent, *args, **kwargs)
+
+ elif tool_name == "loader":
+ return self.get_loader_tool(parent, *args, **kwargs)
+
+ elif tool_name == "libraryloader":
+ return self.get_library_loader_tool(parent, *args, **kwargs)
+
+ elif tool_name == "creator":
+ return self.get_creator_tool(parent, *args, **kwargs)
+
+ elif tool_name == "subsetmanager":
+ return self.get_subset_manager_tool(parent, *args, **kwargs)
+
+ elif tool_name == "sceneinventory":
+ return self.get_scene_inventory_tool(parent, *args, **kwargs)
+
+ elif tool_name == "lookassigner":
+ return self.get_look_assigner_tool(parent, *args, **kwargs)
+
+ elif tool_name == "publish":
+ self.log.info("Can't return publish tool window.")
+
+ else:
+ self.log.warning(
+ "Can't show unknown tool name: \"{}\"".format(tool_name)
+ )
+
+ def show_tool_by_name(self, tool_name, parent=None, *args, **kwargs):
+ """Show tool by it's name.
+
+ This is helper for
+ """
+ if tool_name == "workfiles":
+ self.show_workfiles(parent, *args, **kwargs)
+
+ elif tool_name == "loader":
+ self.show_loader(parent, *args, **kwargs)
+
+ elif tool_name == "libraryloader":
+ self.show_library_loader(parent, *args, **kwargs)
+
+ elif tool_name == "creator":
+ self.show_creator(parent, *args, **kwargs)
+
+ elif tool_name == "subsetmanager":
+ self.show_subset_manager(parent, *args, **kwargs)
+
+ elif tool_name == "sceneinventory":
+ self.show_scene_inventory(parent, *args, **kwargs)
+
+ elif tool_name == "lookassigner":
+ self.show_look_assigner(parent, *args, **kwargs)
+
+ elif tool_name == "publish":
+ self.show_publish(parent, *args, **kwargs)
+
+ else:
+ self.log.warning(
+ "Can't show unknown tool name: \"{}\"".format(tool_name)
+ )
+
+
+class _SingletonPoint:
+ """Singleton access to host tools.
+
+ Some hosts don't have ability to create 'HostToolsHelper' object anc can
+ only register function callbacks. For those cases is created this singleton
+ point where 'HostToolsHelper' is created "in shared memory".
+ """
+ helper = None
+
+ @classmethod
+ def _create_helper(cls):
+ if cls.helper is None:
+ cls.helper = HostToolsHelper()
+
+ @classmethod
+ def show_tool_by_name(cls, tool_name, parent=None, *args, **kwargs):
+ cls._create_helper()
+ cls.helper.show_tool_by_name(tool_name, parent, *args, **kwargs)
+
+ @classmethod
+ def get_tool_by_name(cls, tool_name, parent=None, *args, **kwargs):
+ cls._create_helper()
+ return cls.helper.get_tool_by_name(tool_name, parent, *args, **kwargs)
+
+
+# Function callbacks using singleton acces point
+def get_tool_by_name(tool_name, parent=None, *args, **kwargs):
+ return _SingletonPoint.get_tool_by_name(tool_name, parent, *args, **kwargs)
+
+
+def show_tool_by_name(tool_name, parent=None, *args, **kwargs):
+ _SingletonPoint.show_tool_by_name(tool_name, parent, *args, **kwargs)
+
+
+def show_workfiles(parent=None, use_context=None, save=None):
+ _SingletonPoint.show_tool_by_name(
+ "workfiles", parent, use_context=use_context, save=save
+ )
+
+
+def show_loader(parent=None, use_context=None):
+ _SingletonPoint.show_tool_by_name(
+ "loader", parent, use_context=use_context
+ )
+
+
+def show_library_loader(parent=None):
+ _SingletonPoint.show_tool_by_name("libraryloader", parent)
+
+
+def show_creator(parent=None):
+ _SingletonPoint.show_tool_by_name("creator", parent)
+
+
+def show_subset_manager(parent=None):
+ _SingletonPoint.show_tool_by_name("subsetmanager", parent)
+
+
+def show_scene_inventory(parent=None):
+ _SingletonPoint.show_tool_by_name("sceneinventory", parent)
+
+
+def show_look_assigner(parent=None):
+ _SingletonPoint.show_tool_by_name("lookassigner", parent)
+
+
+def show_publish(parent=None):
+ _SingletonPoint.show_tool_by_name("publish", parent)
diff --git a/openpype/tools/workfiles/app.py b/openpype/tools/workfiles/app.py
index 6fff0d0278..1679a18241 100644
--- a/openpype/tools/workfiles/app.py
+++ b/openpype/tools/workfiles/app.py
@@ -944,7 +944,10 @@ class Window(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(Window, self).__init__(parent=parent)
self.setWindowTitle(self.title)
- self.setWindowFlags(QtCore.Qt.Window | QtCore.Qt.WindowCloseButtonHint)
+ window_flags = QtCore.Qt.Window | QtCore.Qt.WindowCloseButtonHint
+ if not parent:
+ window_flags |= QtCore.Qt.WindowStaysOnTopHint
+ self.setWindowFlags(window_flags)
# Create pages widget and set it as central widget
pages_widget = QtWidgets.QStackedWidget(self)
@@ -1015,6 +1018,9 @@ class Window(QtWidgets.QMainWindow):
"""
+ def set_save_enabled(self, enabled):
+ self.files_widget.btn_save.setEnabled(enabled)
+
def on_task_changed(self):
# Since we query the disk give it slightly more delay
tools_lib.schedule(self._on_task_changed, 100, channel="mongo")
@@ -1187,7 +1193,7 @@ def show(root=None, debug=False, parent=None, use_context=True, save=True):
}
window.set_context(context)
- window.files_widget.btn_save.setEnabled(save)
+ window.set_save_enabled(save)
window.show()
window.setStyleSheet(style.load_stylesheet())
diff --git a/tools/ci_tools.py b/tools/ci_tools.py
index 337b19a346..e5ca0c2c28 100644
--- a/tools/ci_tools.py
+++ b/tools/ci_tools.py
@@ -27,7 +27,7 @@ def get_release_type_github(Log, github_token):
return "minor"
if any(label in labels for label in patch_labels):
- return "path"
+ return "patch"
return None
diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js
index 3ce1cde060..ddbcfd9ce7 100644
--- a/website/docusaurus.config.js
+++ b/website/docusaurus.config.js
@@ -116,8 +116,8 @@ module.exports = {
// Optional: Algolia search parameters
searchParameters: {},
},
- googleAnalytics: {
- trackingID: 'G-HHJZ9VF0FG',
+ gtag: {
+ trackingID: 'G-DTKXMFENFY',
// Optional fields.
anonymizeIP: false, // Should IPs be anonymized?
},
diff --git a/website/yarn.lock b/website/yarn.lock
index 066d156d97..ae40005384 100644
--- a/website/yarn.lock
+++ b/website/yarn.lock
@@ -2175,11 +2175,11 @@ autoprefixer@^10.0.2, autoprefixer@^10.2.5:
postcss-value-parser "^4.1.0"
axios@^0.21.1:
- version "0.21.1"
- resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
- integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
+ version "0.21.4"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
+ integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==
dependencies:
- follow-redirects "^1.10.0"
+ follow-redirects "^1.14.0"
babel-loader@^8.2.2:
version "8.2.2"
@@ -3982,10 +3982,10 @@ flux@^4.0.1:
fbemitter "^3.0.0"
fbjs "^3.0.0"
-follow-redirects@^1.0.0, follow-redirects@^1.10.0:
- version "1.13.3"
- resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.3.tgz#e5598ad50174c1bc4e872301e82ac2cd97f90267"
- integrity sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==
+follow-redirects@^1.0.0, follow-redirects@^1.14.0:
+ version "1.14.4"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.4.tgz#838fdf48a8bbdd79e52ee51fb1c94e3ed98b9379"
+ integrity sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==
for-in@^1.0.2:
version "1.0.2"