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"