ayon-core/openpype/tools/utils/host_tools.py
Jakub Trllo 0498a4016d
Loader tool: Refactor loader tool (for AYON) (#5729)
* initial commitof ayon loader

* tweaks in ayon utils

* implemented product type filtering

* products have icons and proper style

* fix refresh of products

* added enable grouping checkbox

* added icons and sorting of grouped items

* fix version delegate

* add splitter between context and product type filtering

* fix products filtering by name

* implemented 'filter_repre_contexts_by_loader'

* implemented base of action items

* implemented folder underline colors

* changed version items to dictionary

* use 'product_id' instead of 'subset_id'

* base implementation of info widget

* require less to trigger action

* set selection of version ids in controller

* added representation widget and related logic changes

* implemented actions in representations widget

* handle load error

* use versions for subset loader

* fix representations widget

* implemente "in scene" logic properly

* use ayon loader in host tools

* fix used function to get tasks

* show actions per representation name

* center window

* add window flag to loader window

* added 'ThumbnailPainterWidget' to tool utils

* implemented thumbnails model

* implement thumbnail widget

* fix FolderItem args docstring

* bypass bug in ayon_api

* fix sorting of folders

* added refresh button

* added expected selection and go to current context

* added information if project item is library project

* added more filtering options to projects widget

* added missing information abou is library to model items

* remove select project item on selection change

* filter out non library projects

* set current context project to project combobox

* change window title

* fix hero version queries

* move current project to the top

* fix reset

* change icon for library projects

* added libraries separator to project widget

* show libraries separator in loader

* ise single line expression

* library loader tool is loader tool in AYON mode

* fixes in grouping model

* implemented grouping logic

* use loader in tray action

* better initial sizes

* moved 'ActionItem' to abstract

* filter loaders by tool name based on current context project

* formatting fixes

* separate abstract classes into frontend and backend abstractions

* added docstrings to abstractions

* implemented 'to_data' and 'from_data' for action item options

* added more docstrings

* first filter representation contexts and then create action items

* implemented 'refresh' method

* do not reset controller in '_on_first_show'

Method '_on_show_timer' will take about the reset.

* 'ThumbnailPainterWidget' have more options of bg painting

* do not use checkerboard in loader thumbnail

* fix condition

Co-authored-by: Roy Nieterau <roy_nieterau@hotmail.com>

---------

Co-authored-by: Roy Nieterau <roy_nieterau@hotmail.com>
2023-10-11 18:16:45 +02:00

458 lines
15 KiB
Python

"""Single access point to all tools usable in hosts.
It is possible to create `HostToolsHelper` in host implementation or
use singleton approach with global functions (using helper anyway).
"""
import os
import pyblish.api
from openpype import AYON_SERVER_ENABLED
from openpype.host import IWorkfileHost, ILoadHost
from openpype.lib import Logger
from openpype.pipeline import (
registered_host,
get_current_asset_name,
)
from .lib import qt_app_context
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._publisher_tool = None
self._subset_manager_tool = None
self._scene_inventory_tool = None
self._library_loader_tool = None
self._experimental_tools_dialog = None
@property
def log(self):
if self._log is None:
self._log = Logger.get_logger(self.__class__.__name__)
return self._log
def _init_ayon_workfiles_tool(self, parent):
from openpype.tools.ayon_workfiles.widgets import WorkfilesToolWindow
workfiles_window = WorkfilesToolWindow(parent=parent)
self._workfiles_tool = workfiles_window
def _init_openpype_workfiles_tool(self, parent):
from openpype.tools.workfiles.app import Window
# Host validation
host = registered_host()
IWorkfileHost.validate_workfile_methods(host)
workfiles_window = Window(parent=parent)
self._workfiles_tool = workfiles_window
def get_workfiles_tool(self, parent):
"""Create, cache and return workfiles tool window."""
if self._workfiles_tool is None:
if AYON_SERVER_ENABLED:
self._init_ayon_workfiles_tool(parent)
else:
self._init_openpype_workfiles_tool(parent)
return self._workfiles_tool
def show_workfiles(
self, parent=None, use_context=None, save=None, on_top=None
):
"""Workfiles tool for changing context and saving workfiles."""
with qt_app_context():
workfiles_tool = self.get_workfiles_tool(parent)
workfiles_tool.ensure_visible(use_context, save, on_top)
def get_loader_tool(self, parent):
"""Create, cache and return loader tool window."""
if self._loader_tool is None:
host = registered_host()
ILoadHost.validate_load_methods(host)
if AYON_SERVER_ENABLED:
from openpype.tools.ayon_loader.ui import LoaderWindow
from openpype.tools.ayon_loader import LoaderController
controller = LoaderController(host=host)
loader_window = LoaderWindow(
controller=controller,
parent=parent or self._parent
)
else:
from openpype.tools.loader import LoaderWindow
loader_window = LoaderWindow(parent=parent or self._parent)
self._loader_tool = loader_window
return self._loader_tool
def show_loader(self, parent=None, use_context=None):
"""Loader tool for loading representations."""
with qt_app_context():
loader_tool = self.get_loader_tool(parent)
loader_tool.show()
loader_tool.raise_()
loader_tool.activateWindow()
loader_tool.showNormal()
if use_context is None:
use_context = False
if not AYON_SERVER_ENABLED and use_context:
context = {"asset": get_current_asset_name()}
loader_tool.set_context(context, refresh=True)
else:
loader_tool.refresh()
def get_creator_tool(self, parent):
"""Create, cache and return creator tool window."""
if self._creator_tool is None:
from openpype.tools.creator import CreatorWindow
creator_window = CreatorWindow(parent=parent or self._parent)
self._creator_tool = creator_window
return self._creator_tool
def show_creator(self, parent=None):
"""Show tool to create new instantes for publishing."""
with qt_app_context():
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 openpype.tools.subsetmanager import SubsetManagerWindow
subset_manager_window = SubsetManagerWindow(
parent=parent or self._parent
)
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."""
with qt_app_context():
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 openpype.tools.sceneinventory import SceneInventoryWindow
host = registered_host()
ILoadHost.validate_load_methods(host)
scene_inventory_window = SceneInventoryWindow(
parent=parent or self._parent
)
self._scene_inventory_tool = scene_inventory_window
return self._scene_inventory_tool
def show_scene_inventory(self, parent=None):
"""Show tool maintain loaded containers."""
with qt_app_context():
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()
scene_inventory_tool.showNormal()
def get_library_loader_tool(self, parent):
"""Create, cache and return library loader tool window."""
if AYON_SERVER_ENABLED:
return self.get_loader_tool(parent)
if self._library_loader_tool is None:
from openpype.tools.libraryloader import LibraryLoaderWindow
library_window = LibraryLoaderWindow(
parent=parent or self._parent
)
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."""
if AYON_SERVER_ENABLED:
return self.show_loader(parent)
with qt_app_context():
library_loader_tool = self.get_library_loader_tool(parent)
library_loader_tool.show()
library_loader_tool.raise_()
library_loader_tool.activateWindow()
library_loader_tool.showNormal()
library_loader_tool.refresh()
def show_publish(self, parent=None):
"""Try showing the most desirable publish GUI
This function cycles through the currently registered
graphical user interfaces, if any, and presents it to
the user.
"""
pyblish_show = self._discover_pyblish_gui()
return pyblish_show(parent)
def _discover_pyblish_gui(self):
"""Return the most desirable of the currently registered GUIs"""
# Prefer last registered
guis = list(reversed(pyblish.api.registered_guis()))
for gui in guis:
try:
gui = __import__(gui).show
except (ImportError, AttributeError):
continue
else:
return gui
raise ImportError("No Pyblish GUI found")
def get_experimental_tools_dialog(self, parent=None):
"""Dialog of experimental tools.
For some hosts it is not easy to modify menu of tools. For
those cases was added experimental tools dialog which is Qt based
and can dynamically filled by experimental tools so
host need only single "Experimental tools" button to see them.
Dialog can be also empty with a message that there are not available
experimental tools.
"""
if self._experimental_tools_dialog is None:
from openpype.tools.experimental_tools import (
ExperimentalToolsDialog
)
self._experimental_tools_dialog = ExperimentalToolsDialog(parent)
return self._experimental_tools_dialog
def show_experimental_tools_dialog(self, parent=None):
"""Show dialog with experimental tools."""
with qt_app_context():
dialog = self.get_experimental_tools_dialog(parent)
dialog.show()
dialog.raise_()
dialog.activateWindow()
dialog.showNormal()
def get_publisher_tool(self, parent=None, controller=None):
"""Create, cache and return publisher window."""
if self._publisher_tool is None:
from openpype.tools.publisher.window import PublisherWindow
host = registered_host()
ILoadHost.validate_load_methods(host)
publisher_window = PublisherWindow(
controller=controller, parent=parent or self._parent
)
self._publisher_tool = publisher_window
return self._publisher_tool
def show_publisher_tool(self, parent=None, controller=None, tab=None):
with qt_app_context():
window = self.get_publisher_tool(parent, controller)
if tab:
window.set_current_tab(tab)
window.make_sure_is_visible()
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 == "publish":
self.log.info("Can't return publish tool window.")
# "new" publisher
elif tool_name == "publisher":
return self.get_publisher_tool(parent, *args, **kwargs)
elif tool_name == "experimental_tools":
return self.get_experimental_tools_dialog(parent, *args, **kwargs)
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 == "publish":
self.show_publish(parent, *args, **kwargs)
elif tool_name == "publisher":
self.show_publisher_tool(parent, *args, **kwargs)
elif tool_name == "experimental_tools":
self.show_experimental_tools_dialog(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 access 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(*args, **kwargs):
_SingletonPoint.show_tool_by_name(
"workfiles", *args, **kwargs
)
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_publish(parent=None):
_SingletonPoint.show_tool_by_name("publish", parent)
def show_publisher(parent=None, **kwargs):
_SingletonPoint.show_tool_by_name("publisher", parent, **kwargs)
def show_experimental_tools_dialog(parent=None):
_SingletonPoint.show_tool_by_name("experimental_tools", parent)
def get_pyblish_icon():
pyblish_dir = os.path.abspath(os.path.dirname(pyblish.api.__file__))
icon_path = os.path.join(pyblish_dir, "icons", "logo-32x32.svg")
if os.path.exists(icon_path):
return icon_path
return None