Merge branch 'feature/houdini_cleanup_after_publishing' of github.com:ynput/ayon-core into feature/houdini_cleanup_after_publishing
|
|
@ -3,6 +3,7 @@ import warnings
|
||||||
|
|
||||||
import ayon_api
|
import ayon_api
|
||||||
|
|
||||||
|
from ayon_core.settings import get_studio_settings, get_project_settings
|
||||||
from ayon_core.pipeline.plugin_discover import (
|
from ayon_core.pipeline.plugin_discover import (
|
||||||
discover,
|
discover,
|
||||||
register_plugin,
|
register_plugin,
|
||||||
|
|
@ -40,7 +41,8 @@ class LauncherActionSelection:
|
||||||
task_name=None,
|
task_name=None,
|
||||||
project_entity=None,
|
project_entity=None,
|
||||||
folder_entity=None,
|
folder_entity=None,
|
||||||
task_entity=None
|
task_entity=None,
|
||||||
|
project_settings=None,
|
||||||
):
|
):
|
||||||
self._project_name = project_name
|
self._project_name = project_name
|
||||||
self._folder_id = folder_id
|
self._folder_id = folder_id
|
||||||
|
|
@ -53,6 +55,8 @@ class LauncherActionSelection:
|
||||||
self._folder_entity = folder_entity
|
self._folder_entity = folder_entity
|
||||||
self._task_entity = task_entity
|
self._task_entity = task_entity
|
||||||
|
|
||||||
|
self._project_settings = project_settings
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
(
|
(
|
||||||
|
|
@ -255,6 +259,22 @@ class LauncherActionSelection:
|
||||||
)
|
)
|
||||||
return self._task_entity
|
return self._task_entity
|
||||||
|
|
||||||
|
def get_project_settings(self):
|
||||||
|
"""Project settings for the selection.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict[str, Any]: Project settings or studio settings if
|
||||||
|
project is not selected.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if self._project_settings is None:
|
||||||
|
if self._project_name is None:
|
||||||
|
settings = get_studio_settings()
|
||||||
|
else:
|
||||||
|
settings = get_project_settings(self._project_name)
|
||||||
|
self._project_settings = settings
|
||||||
|
return self._project_settings
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_project_selected(self):
|
def is_project_selected(self):
|
||||||
"""Return whether a project is selected.
|
"""Return whether a project is selected.
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 3.9 KiB |
|
|
@ -5,7 +5,6 @@ from abc import ABCMeta, abstractmethod
|
||||||
import ayon_api
|
import ayon_api
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from ayon_core.style import get_default_entity_icon_color
|
|
||||||
from ayon_core.lib import NestedCacheItem
|
from ayon_core.lib import NestedCacheItem
|
||||||
|
|
||||||
HIERARCHY_MODEL_SENDER = "hierarchy.model"
|
HIERARCHY_MODEL_SENDER = "hierarchy.model"
|
||||||
|
|
@ -31,11 +30,10 @@ class FolderItem:
|
||||||
path (str): Folder path.
|
path (str): Folder path.
|
||||||
folder_type (str): Type of folder.
|
folder_type (str): Type of folder.
|
||||||
label (Union[str, None]): Folder label.
|
label (Union[str, None]): Folder label.
|
||||||
icon (Union[dict[str, Any], None]): Icon definition.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, entity_id, parent_id, name, path, folder_type, label, icon
|
self, entity_id, parent_id, name, path, folder_type, label
|
||||||
):
|
):
|
||||||
self.entity_id = entity_id
|
self.entity_id = entity_id
|
||||||
self.parent_id = parent_id
|
self.parent_id = parent_id
|
||||||
|
|
@ -43,13 +41,6 @@ class FolderItem:
|
||||||
self.path = path
|
self.path = path
|
||||||
self.folder_type = folder_type
|
self.folder_type = folder_type
|
||||||
self.label = label or name
|
self.label = label or name
|
||||||
if not icon:
|
|
||||||
icon = {
|
|
||||||
"type": "awesome-font",
|
|
||||||
"name": "fa.folder",
|
|
||||||
"color": get_default_entity_icon_color()
|
|
||||||
}
|
|
||||||
self.icon = icon
|
|
||||||
|
|
||||||
def to_data(self):
|
def to_data(self):
|
||||||
"""Converts folder item to data.
|
"""Converts folder item to data.
|
||||||
|
|
@ -65,7 +56,6 @@ class FolderItem:
|
||||||
"path": self.path,
|
"path": self.path,
|
||||||
"folder_type": self.folder_type,
|
"folder_type": self.folder_type,
|
||||||
"label": self.label,
|
"label": self.label,
|
||||||
"icon": self.icon,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
@ -95,23 +85,15 @@ class TaskItem:
|
||||||
name (str): Name of task.
|
name (str): Name of task.
|
||||||
task_type (str): Type of task.
|
task_type (str): Type of task.
|
||||||
parent_id (str): Parent folder id.
|
parent_id (str): Parent folder id.
|
||||||
icon (Union[dict[str, Any], None]): Icon definitions.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, task_id, name, task_type, parent_id, icon
|
self, task_id, name, task_type, parent_id
|
||||||
):
|
):
|
||||||
self.task_id = task_id
|
self.task_id = task_id
|
||||||
self.name = name
|
self.name = name
|
||||||
self.task_type = task_type
|
self.task_type = task_type
|
||||||
self.parent_id = parent_id
|
self.parent_id = parent_id
|
||||||
if icon is None:
|
|
||||||
icon = {
|
|
||||||
"type": "awesome-font",
|
|
||||||
"name": "fa.male",
|
|
||||||
"color": get_default_entity_icon_color()
|
|
||||||
}
|
|
||||||
self.icon = icon
|
|
||||||
|
|
||||||
self._label = None
|
self._label = None
|
||||||
|
|
||||||
|
|
@ -149,7 +131,6 @@ class TaskItem:
|
||||||
"name": self.name,
|
"name": self.name,
|
||||||
"parent_id": self.parent_id,
|
"parent_id": self.parent_id,
|
||||||
"task_type": self.task_type,
|
"task_type": self.task_type,
|
||||||
"icon": self.icon,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
@ -180,8 +161,7 @@ def _get_task_items_from_tasks(tasks):
|
||||||
task["id"],
|
task["id"],
|
||||||
task["name"],
|
task["name"],
|
||||||
task["type"],
|
task["type"],
|
||||||
folder_id,
|
folder_id
|
||||||
None
|
|
||||||
))
|
))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
@ -197,8 +177,7 @@ def _get_folder_item_from_hierarchy_item(item):
|
||||||
name,
|
name,
|
||||||
path,
|
path,
|
||||||
item["folderType"],
|
item["folderType"],
|
||||||
item["label"],
|
item["label"]
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -210,8 +189,7 @@ def _get_folder_item_from_entity(entity):
|
||||||
name,
|
name,
|
||||||
entity["path"],
|
entity["path"],
|
||||||
entity["folderType"],
|
entity["folderType"],
|
||||||
entity["label"] or name,
|
entity["label"] or name
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,74 @@ class StatusItem:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FolderTypeItem:
|
||||||
|
"""Item representing folder type of project.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): Folder type name ("Shot").
|
||||||
|
short (str): Short folder type name ("sh").
|
||||||
|
icon (str): Icon name in MaterialIcons ("fiber_new").
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(self, name, short, icon):
|
||||||
|
self.name = name
|
||||||
|
self.short = short
|
||||||
|
self.icon = icon
|
||||||
|
|
||||||
|
def to_data(self):
|
||||||
|
return {
|
||||||
|
"name": self.name,
|
||||||
|
"short": self.short,
|
||||||
|
"icon": self.icon,
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_data(cls, data):
|
||||||
|
return cls(**data)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_project_item(cls, folder_type_data):
|
||||||
|
return cls(
|
||||||
|
name=folder_type_data["name"],
|
||||||
|
short=folder_type_data["shortName"],
|
||||||
|
icon=folder_type_data["icon"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TaskTypeItem:
|
||||||
|
"""Item representing task type of project.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): Task type name ("Shot").
|
||||||
|
short (str): Short task type name ("sh").
|
||||||
|
icon (str): Icon name in MaterialIcons ("fiber_new").
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(self, name, short, icon):
|
||||||
|
self.name = name
|
||||||
|
self.short = short
|
||||||
|
self.icon = icon
|
||||||
|
|
||||||
|
def to_data(self):
|
||||||
|
return {
|
||||||
|
"name": self.name,
|
||||||
|
"short": self.short,
|
||||||
|
"icon": self.icon,
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_data(cls, data):
|
||||||
|
return cls(**data)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_project_item(cls, task_type_data):
|
||||||
|
return cls(
|
||||||
|
name=task_type_data["name"],
|
||||||
|
short=task_type_data["shortName"],
|
||||||
|
icon=task_type_data["icon"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ProjectItem:
|
class ProjectItem:
|
||||||
"""Item representing folder entity on a server.
|
"""Item representing folder entity on a server.
|
||||||
|
|
||||||
|
|
@ -147,19 +215,21 @@ def _get_project_items_from_entitiy(projects):
|
||||||
class ProjectsModel(object):
|
class ProjectsModel(object):
|
||||||
def __init__(self, controller):
|
def __init__(self, controller):
|
||||||
self._projects_cache = CacheItem(default_factory=list)
|
self._projects_cache = CacheItem(default_factory=list)
|
||||||
self._project_statuses_cache = NestedCacheItem(
|
|
||||||
levels=1, default_factory=list
|
|
||||||
)
|
|
||||||
self._projects_by_name = NestedCacheItem(
|
self._projects_by_name = NestedCacheItem(
|
||||||
levels=1, default_factory=list
|
levels=1, default_factory=list
|
||||||
)
|
)
|
||||||
|
self._project_statuses_cache = {}
|
||||||
|
self._folder_types_cache = {}
|
||||||
|
self._task_types_cache = {}
|
||||||
|
|
||||||
self._is_refreshing = False
|
self._is_refreshing = False
|
||||||
self._controller = controller
|
self._controller = controller
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
|
self._project_statuses_cache = {}
|
||||||
|
self._folder_types_cache = {}
|
||||||
|
self._task_types_cache = {}
|
||||||
self._projects_cache.reset()
|
self._projects_cache.reset()
|
||||||
self._project_statuses_cache.reset()
|
|
||||||
self._projects_by_name.reset()
|
self._projects_by_name.reset()
|
||||||
|
|
||||||
def refresh(self):
|
def refresh(self):
|
||||||
|
|
@ -217,22 +287,87 @@ class ProjectsModel(object):
|
||||||
list[StatusItem]: Status items for project.
|
list[StatusItem]: Status items for project.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
statuses_cache = self._project_statuses_cache[project_name]
|
if project_name is None:
|
||||||
if not statuses_cache.is_valid:
|
return []
|
||||||
with self._project_statuses_refresh_event_manager(
|
|
||||||
sender, project_name
|
statuses_cache = self._project_statuses_cache.get(project_name)
|
||||||
|
if (
|
||||||
|
statuses_cache is not None
|
||||||
|
and not self._projects_cache.is_valid
|
||||||
|
):
|
||||||
|
statuses_cache = None
|
||||||
|
|
||||||
|
if statuses_cache is None:
|
||||||
|
with self._project_items_refresh_event_manager(
|
||||||
|
sender, project_name, "statuses"
|
||||||
):
|
):
|
||||||
project_entity = None
|
project_entity = self.get_project_entity(project_name)
|
||||||
if project_name:
|
|
||||||
project_entity = self.get_project_entity(project_name)
|
|
||||||
statuses = []
|
statuses = []
|
||||||
if project_entity:
|
if project_entity:
|
||||||
statuses = [
|
statuses = [
|
||||||
StatusItem.from_project_item(status)
|
StatusItem.from_project_item(status)
|
||||||
for status in project_entity["statuses"]
|
for status in project_entity["statuses"]
|
||||||
]
|
]
|
||||||
statuses_cache.update_data(statuses)
|
statuses_cache = statuses
|
||||||
return statuses_cache.get_data()
|
self._project_statuses_cache[project_name] = statuses_cache
|
||||||
|
return list(statuses_cache)
|
||||||
|
|
||||||
|
def get_folder_type_items(self, project_name, sender):
|
||||||
|
"""Get project status items.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
project_name (str): Project name.
|
||||||
|
sender (Union[str, None]): Name of sender who asked for items.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[FolderType]: Folder type items for project.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self._get_project_items(
|
||||||
|
project_name,
|
||||||
|
sender,
|
||||||
|
"folder_types",
|
||||||
|
self._folder_types_cache,
|
||||||
|
self._folder_type_items_getter,
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_task_type_items(self, project_name, sender):
|
||||||
|
"""Get project task type items.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
project_name (str): Project name.
|
||||||
|
sender (Union[str, None]): Name of sender who asked for items.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[TaskTypeItem]: Task type items for project.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self._get_project_items(
|
||||||
|
project_name,
|
||||||
|
sender,
|
||||||
|
"task_types",
|
||||||
|
self._task_types_cache,
|
||||||
|
self._task_type_items_getter,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_project_items(
|
||||||
|
self, project_name, sender, item_type, cache_obj, getter
|
||||||
|
):
|
||||||
|
if (
|
||||||
|
project_name in cache_obj
|
||||||
|
and (
|
||||||
|
project_name is None
|
||||||
|
or self._projects_by_name[project_name].is_valid
|
||||||
|
)
|
||||||
|
):
|
||||||
|
return cache_obj[project_name]
|
||||||
|
|
||||||
|
with self._project_items_refresh_event_manager(
|
||||||
|
sender, project_name, item_type
|
||||||
|
):
|
||||||
|
cache_value = getter(self.get_project_entity(project_name))
|
||||||
|
cache_obj[project_name] = cache_value
|
||||||
|
return cache_value
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def _project_refresh_event_manager(self, sender):
|
def _project_refresh_event_manager(self, sender):
|
||||||
|
|
@ -254,9 +389,11 @@ class ProjectsModel(object):
|
||||||
self._is_refreshing = False
|
self._is_refreshing = False
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def _project_statuses_refresh_event_manager(self, sender, project_name):
|
def _project_items_refresh_event_manager(
|
||||||
|
self, sender, project_name, item_type
|
||||||
|
):
|
||||||
self._controller.emit_event(
|
self._controller.emit_event(
|
||||||
"projects.statuses.refresh.started",
|
f"projects.{item_type}.refresh.started",
|
||||||
{"sender": sender, "project_name": project_name},
|
{"sender": sender, "project_name": project_name},
|
||||||
PROJECTS_MODEL_SENDER
|
PROJECTS_MODEL_SENDER
|
||||||
)
|
)
|
||||||
|
|
@ -265,7 +402,7 @@ class ProjectsModel(object):
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
self._controller.emit_event(
|
self._controller.emit_event(
|
||||||
"projects.statuses.refresh.finished",
|
f"projects.{item_type}.refresh.finished",
|
||||||
{"sender": sender, "project_name": project_name},
|
{"sender": sender, "project_name": project_name},
|
||||||
PROJECTS_MODEL_SENDER
|
PROJECTS_MODEL_SENDER
|
||||||
)
|
)
|
||||||
|
|
@ -282,3 +419,27 @@ class ProjectsModel(object):
|
||||||
def _query_projects(self):
|
def _query_projects(self):
|
||||||
projects = ayon_api.get_projects(fields=["name", "active", "library"])
|
projects = ayon_api.get_projects(fields=["name", "active", "library"])
|
||||||
return _get_project_items_from_entitiy(projects)
|
return _get_project_items_from_entitiy(projects)
|
||||||
|
|
||||||
|
def _status_items_getter(self, project_entity):
|
||||||
|
if not project_entity:
|
||||||
|
return []
|
||||||
|
return [
|
||||||
|
StatusItem.from_project_item(status)
|
||||||
|
for status in project_entity["statuses"]
|
||||||
|
]
|
||||||
|
|
||||||
|
def _folder_type_items_getter(self, project_entity):
|
||||||
|
if not project_entity:
|
||||||
|
return []
|
||||||
|
return [
|
||||||
|
FolderTypeItem.from_project_item(folder_type)
|
||||||
|
for folder_type in project_entity["folderTypes"]
|
||||||
|
]
|
||||||
|
|
||||||
|
def _task_type_items_getter(self, project_entity):
|
||||||
|
if not project_entity:
|
||||||
|
return []
|
||||||
|
return [
|
||||||
|
TaskTypeItem.from_project_item(task_type)
|
||||||
|
for task_type in project_entity["taskTypes"]
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -227,6 +227,16 @@ class ContextDialogController:
|
||||||
def get_project_items(self, sender=None):
|
def get_project_items(self, sender=None):
|
||||||
return self._projects_model.get_project_items(sender)
|
return self._projects_model.get_project_items(sender)
|
||||||
|
|
||||||
|
def get_folder_type_items(self, project_name, sender=None):
|
||||||
|
return self._projects_model.get_folder_type_items(
|
||||||
|
project_name, sender
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_task_type_items(self, project_name, sender=None):
|
||||||
|
return self._projects_model.get_task_type_items(
|
||||||
|
project_name, sender
|
||||||
|
)
|
||||||
|
|
||||||
def get_folder_items(self, project_name, sender=None):
|
def get_folder_items(self, project_name, sender=None):
|
||||||
return self._hierarchy_model.get_folder_items(project_name, sender)
|
return self._hierarchy_model.get_folder_items(project_name, sender)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -104,8 +104,48 @@ class AbstractLauncherFrontEnd(AbstractLauncherCommon):
|
||||||
Returns:
|
Returns:
|
||||||
list[ProjectItem]: Minimum possible information needed
|
list[ProjectItem]: Minimum possible information needed
|
||||||
for visualisation of folder hierarchy.
|
for visualisation of folder hierarchy.
|
||||||
"""
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_folder_type_items(self, project_name, sender=None):
|
||||||
|
"""Folder type items for a project.
|
||||||
|
|
||||||
|
This function may trigger events with topics
|
||||||
|
'projects.folder_types.refresh.started' and
|
||||||
|
'projects.folder_types.refresh.finished' which will contain 'sender'
|
||||||
|
value in data.
|
||||||
|
That may help to avoid re-refresh of items in UI elements.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
project_name (str): Project name.
|
||||||
|
sender (str): Who requested folder type items.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[FolderTypeItem]: Folder type information.
|
||||||
|
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_task_type_items(self, project_name, sender=None):
|
||||||
|
"""Task type items for a project.
|
||||||
|
|
||||||
|
This function may trigger events with topics
|
||||||
|
'projects.task_types.refresh.started' and
|
||||||
|
'projects.task_types.refresh.finished' which will contain 'sender'
|
||||||
|
value in data.
|
||||||
|
That may help to avoid re-refresh of items in UI elements.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
project_name (str): Project name.
|
||||||
|
sender (str): Who requested task type items.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[TaskTypeItem]: Task type information.
|
||||||
|
|
||||||
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
|
|
|
||||||
|
|
@ -59,12 +59,23 @@ class BaseLauncherController(
|
||||||
def get_project_items(self, sender=None):
|
def get_project_items(self, sender=None):
|
||||||
return self._projects_model.get_project_items(sender)
|
return self._projects_model.get_project_items(sender)
|
||||||
|
|
||||||
|
def get_folder_type_items(self, project_name, sender=None):
|
||||||
|
return self._projects_model.get_folder_type_items(
|
||||||
|
project_name, sender
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_task_type_items(self, project_name, sender=None):
|
||||||
|
return self._projects_model.get_task_type_items(
|
||||||
|
project_name, sender
|
||||||
|
)
|
||||||
|
|
||||||
def get_folder_items(self, project_name, sender=None):
|
def get_folder_items(self, project_name, sender=None):
|
||||||
return self._hierarchy_model.get_folder_items(project_name, sender)
|
return self._hierarchy_model.get_folder_items(project_name, sender)
|
||||||
|
|
||||||
def get_task_items(self, project_name, folder_id, sender=None):
|
def get_task_items(self, project_name, folder_id, sender=None):
|
||||||
return self._hierarchy_model.get_task_items(
|
return self._hierarchy_model.get_task_items(
|
||||||
project_name, folder_id, sender)
|
project_name, folder_id, sender
|
||||||
|
)
|
||||||
|
|
||||||
# Project settings for applications actions
|
# Project settings for applications actions
|
||||||
def get_project_settings(self, project_name):
|
def get_project_settings(self, project_name):
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,108 @@ from ayon_core.pipeline.actions import (
|
||||||
)
|
)
|
||||||
from ayon_core.pipeline.workfile import should_use_last_workfile_on_launch
|
from ayon_core.pipeline.workfile import should_use_last_workfile_on_launch
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Available since applications addon 0.2.4
|
||||||
|
from ayon_applications.action import ApplicationAction
|
||||||
|
except ImportError:
|
||||||
|
# Backwards compatibility from 0.3.3 (24/06/10)
|
||||||
|
# TODO: Remove in future releases
|
||||||
|
class ApplicationAction(LauncherAction):
|
||||||
|
"""Action to launch an application.
|
||||||
|
|
||||||
|
Application action based on 'ApplicationManager' system.
|
||||||
|
|
||||||
|
Handling of applications in launcher is not ideal and should be completely
|
||||||
|
redone from scratch. This is just a temporary solution to keep backwards
|
||||||
|
compatibility with AYON launcher.
|
||||||
|
|
||||||
|
Todos:
|
||||||
|
Move handling of errors to frontend.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Application object
|
||||||
|
application = None
|
||||||
|
# Action attributes
|
||||||
|
name = None
|
||||||
|
label = None
|
||||||
|
label_variant = None
|
||||||
|
group = None
|
||||||
|
icon = None
|
||||||
|
color = None
|
||||||
|
order = 0
|
||||||
|
data = {}
|
||||||
|
project_settings = {}
|
||||||
|
project_entities = {}
|
||||||
|
|
||||||
|
_log = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def log(self):
|
||||||
|
if self._log is None:
|
||||||
|
self._log = Logger.get_logger(self.__class__.__name__)
|
||||||
|
return self._log
|
||||||
|
|
||||||
|
def is_compatible(self, selection):
|
||||||
|
if not selection.is_task_selected:
|
||||||
|
return False
|
||||||
|
|
||||||
|
project_entity = self.project_entities[selection.project_name]
|
||||||
|
apps = project_entity["attrib"].get("applications")
|
||||||
|
if not apps or self.application.full_name not in apps:
|
||||||
|
return False
|
||||||
|
|
||||||
|
project_settings = self.project_settings[selection.project_name]
|
||||||
|
only_available = project_settings["applications"]["only_available"]
|
||||||
|
if only_available and not self.application.find_executable():
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _show_message_box(self, title, message, details=None):
|
||||||
|
from qtpy import QtWidgets, QtGui
|
||||||
|
from ayon_core import style
|
||||||
|
|
||||||
|
dialog = QtWidgets.QMessageBox()
|
||||||
|
icon = QtGui.QIcon(resources.get_ayon_icon_filepath())
|
||||||
|
dialog.setWindowIcon(icon)
|
||||||
|
dialog.setStyleSheet(style.load_stylesheet())
|
||||||
|
dialog.setWindowTitle(title)
|
||||||
|
dialog.setText(message)
|
||||||
|
if details:
|
||||||
|
dialog.setDetailedText(details)
|
||||||
|
dialog.exec_()
|
||||||
|
|
||||||
|
def process(self, selection, **kwargs):
|
||||||
|
"""Process the full Application action"""
|
||||||
|
|
||||||
|
from ayon_applications import (
|
||||||
|
ApplicationExecutableNotFound,
|
||||||
|
ApplicationLaunchFailed,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.application.launch(
|
||||||
|
project_name=selection.project_name,
|
||||||
|
folder_path=selection.folder_path,
|
||||||
|
task_name=selection.task_name,
|
||||||
|
**self.data
|
||||||
|
)
|
||||||
|
|
||||||
|
except ApplicationExecutableNotFound as exc:
|
||||||
|
details = exc.details
|
||||||
|
msg = exc.msg
|
||||||
|
log_msg = str(msg)
|
||||||
|
if details:
|
||||||
|
log_msg += "\n" + details
|
||||||
|
self.log.warning(log_msg)
|
||||||
|
self._show_message_box(
|
||||||
|
"Application executable not found", msg, details
|
||||||
|
)
|
||||||
|
|
||||||
|
except ApplicationLaunchFailed as exc:
|
||||||
|
msg = str(exc)
|
||||||
|
self.log.warning(msg, exc_info=True)
|
||||||
|
self._show_message_box("Application launch failed", msg)
|
||||||
|
|
||||||
|
|
||||||
# class Action:
|
# class Action:
|
||||||
# def __init__(self, label, icon=None, identifier=None):
|
# def __init__(self, label, icon=None, identifier=None):
|
||||||
|
|
@ -43,103 +145,6 @@ from ayon_core.pipeline.workfile import should_use_last_workfile_on_launch
|
||||||
# self._actions.append(action)
|
# self._actions.append(action)
|
||||||
|
|
||||||
|
|
||||||
class ApplicationAction(LauncherAction):
|
|
||||||
"""Action to launch an application.
|
|
||||||
|
|
||||||
Application action based on 'ApplicationManager' system.
|
|
||||||
|
|
||||||
Handling of applications in launcher is not ideal and should be completely
|
|
||||||
redone from scratch. This is just a temporary solution to keep backwards
|
|
||||||
compatibility with AYON launcher.
|
|
||||||
|
|
||||||
Todos:
|
|
||||||
Move handling of errors to frontend.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Application object
|
|
||||||
application = None
|
|
||||||
# Action attributes
|
|
||||||
name = None
|
|
||||||
label = None
|
|
||||||
label_variant = None
|
|
||||||
group = None
|
|
||||||
icon = None
|
|
||||||
color = None
|
|
||||||
order = 0
|
|
||||||
data = {}
|
|
||||||
project_settings = {}
|
|
||||||
project_entities = {}
|
|
||||||
|
|
||||||
_log = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def log(self):
|
|
||||||
if self._log is None:
|
|
||||||
self._log = Logger.get_logger(self.__class__.__name__)
|
|
||||||
return self._log
|
|
||||||
|
|
||||||
def is_compatible(self, selection):
|
|
||||||
if not selection.is_task_selected:
|
|
||||||
return False
|
|
||||||
|
|
||||||
project_entity = self.project_entities[selection.project_name]
|
|
||||||
apps = project_entity["attrib"].get("applications")
|
|
||||||
if not apps or self.application.full_name not in apps:
|
|
||||||
return False
|
|
||||||
|
|
||||||
project_settings = self.project_settings[selection.project_name]
|
|
||||||
only_available = project_settings["applications"]["only_available"]
|
|
||||||
if only_available and not self.application.find_executable():
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _show_message_box(self, title, message, details=None):
|
|
||||||
from qtpy import QtWidgets, QtGui
|
|
||||||
from ayon_core import style
|
|
||||||
|
|
||||||
dialog = QtWidgets.QMessageBox()
|
|
||||||
icon = QtGui.QIcon(resources.get_ayon_icon_filepath())
|
|
||||||
dialog.setWindowIcon(icon)
|
|
||||||
dialog.setStyleSheet(style.load_stylesheet())
|
|
||||||
dialog.setWindowTitle(title)
|
|
||||||
dialog.setText(message)
|
|
||||||
if details:
|
|
||||||
dialog.setDetailedText(details)
|
|
||||||
dialog.exec_()
|
|
||||||
|
|
||||||
def process(self, selection, **kwargs):
|
|
||||||
"""Process the full Application action"""
|
|
||||||
|
|
||||||
from ayon_applications import (
|
|
||||||
ApplicationExecutableNotFound,
|
|
||||||
ApplicationLaunchFailed,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.application.launch(
|
|
||||||
project_name=selection.project_name,
|
|
||||||
folder_path=selection.folder_path,
|
|
||||||
task_name=selection.task_name,
|
|
||||||
**self.data
|
|
||||||
)
|
|
||||||
|
|
||||||
except ApplicationExecutableNotFound as exc:
|
|
||||||
details = exc.details
|
|
||||||
msg = exc.msg
|
|
||||||
log_msg = str(msg)
|
|
||||||
if details:
|
|
||||||
log_msg += "\n" + details
|
|
||||||
self.log.warning(log_msg)
|
|
||||||
self._show_message_box(
|
|
||||||
"Application executable not found", msg, details
|
|
||||||
)
|
|
||||||
|
|
||||||
except ApplicationLaunchFailed as exc:
|
|
||||||
msg = str(exc)
|
|
||||||
self.log.warning(msg, exc_info=True)
|
|
||||||
self._show_message_box("Application launch failed", msg)
|
|
||||||
|
|
||||||
|
|
||||||
class ActionItem:
|
class ActionItem:
|
||||||
"""Item representing single action to trigger.
|
"""Item representing single action to trigger.
|
||||||
|
|
||||||
|
|
@ -440,7 +445,17 @@ class ActionsModel:
|
||||||
)
|
)
|
||||||
|
|
||||||
def _prepare_selection(self, project_name, folder_id, task_id):
|
def _prepare_selection(self, project_name, folder_id, task_id):
|
||||||
return LauncherActionSelection(project_name, folder_id, task_id)
|
project_entity = None
|
||||||
|
if project_name:
|
||||||
|
project_entity = self._controller.get_project_entity(project_name)
|
||||||
|
project_settings = self._controller.get_project_settings(project_name)
|
||||||
|
return LauncherActionSelection(
|
||||||
|
project_name,
|
||||||
|
folder_id,
|
||||||
|
task_id,
|
||||||
|
project_entity=project_entity,
|
||||||
|
project_settings=project_settings,
|
||||||
|
)
|
||||||
|
|
||||||
def _get_discovered_action_classes(self):
|
def _get_discovered_action_classes(self):
|
||||||
if self._discovered_actions is None:
|
if self._discovered_actions is None:
|
||||||
|
|
@ -475,7 +490,9 @@ class ActionsModel:
|
||||||
action_items = {}
|
action_items = {}
|
||||||
for identifier, action in self._get_action_objects().items():
|
for identifier, action in self._get_action_objects().items():
|
||||||
is_application = isinstance(action, ApplicationAction)
|
is_application = isinstance(action, ApplicationAction)
|
||||||
if is_application:
|
# Backwards compatibility from 0.3.3 (24/06/10)
|
||||||
|
# TODO: Remove in future releases
|
||||||
|
if is_application and hasattr(action, "project_settings"):
|
||||||
action.project_entities[project_name] = project_entity
|
action.project_entities[project_name] = project_entity
|
||||||
action.project_settings[project_name] = project_settings
|
action.project_settings[project_name] = project_settings
|
||||||
|
|
||||||
|
|
@ -497,10 +514,14 @@ class ActionsModel:
|
||||||
return action_items
|
return action_items
|
||||||
|
|
||||||
def _get_applications_action_classes(self):
|
def _get_applications_action_classes(self):
|
||||||
actions = []
|
|
||||||
|
|
||||||
addons_manager = self._get_addons_manager()
|
addons_manager = self._get_addons_manager()
|
||||||
applications_addon = addons_manager.get_enabled_addon("applications")
|
applications_addon = addons_manager.get_enabled_addon("applications")
|
||||||
|
if hasattr(applications_addon, "get_applications_action_classes"):
|
||||||
|
return applications_addon.get_applications_action_classes()
|
||||||
|
|
||||||
|
# Backwards compatibility from 0.3.3 (24/06/10)
|
||||||
|
# TODO: Remove in future releases
|
||||||
|
actions = []
|
||||||
if applications_addon is None:
|
if applications_addon is None:
|
||||||
return actions
|
return actions
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -510,6 +510,26 @@ class FrontendLoaderController(_BaseLoaderController):
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_folder_type_items(self, project_name, sender=None):
|
||||||
|
"""Folder type items for a project.
|
||||||
|
|
||||||
|
This function may trigger events with topics
|
||||||
|
'projects.folder_types.refresh.started' and
|
||||||
|
'projects.folder_types.refresh.finished' which will contain 'sender'
|
||||||
|
value in data.
|
||||||
|
That may help to avoid re-refresh of items in UI elements.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
project_name (str): Project name.
|
||||||
|
sender (str): Who requested folder type items.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[FolderTypeItem]: Folder type information.
|
||||||
|
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_project_status_items(self, project_name, sender=None):
|
def get_project_status_items(self, project_name, sender=None):
|
||||||
"""Items for all projects available on server.
|
"""Items for all projects available on server.
|
||||||
|
|
|
||||||
|
|
@ -180,6 +180,11 @@ class LoaderController(BackendLoaderController, FrontendLoaderController):
|
||||||
def get_project_items(self, sender=None):
|
def get_project_items(self, sender=None):
|
||||||
return self._projects_model.get_project_items(sender)
|
return self._projects_model.get_project_items(sender)
|
||||||
|
|
||||||
|
def get_folder_type_items(self, project_name, sender=None):
|
||||||
|
return self._projects_model.get_folder_type_items(
|
||||||
|
project_name, sender
|
||||||
|
)
|
||||||
|
|
||||||
def get_project_status_items(self, project_name, sender=None):
|
def get_project_status_items(self, project_name, sender=None):
|
||||||
return self._projects_model.get_project_status_items(
|
return self._projects_model.get_project_status_items(
|
||||||
project_name, sender
|
project_name, sender
|
||||||
|
|
|
||||||
|
|
@ -188,16 +188,6 @@ class LoaderFoldersModel(FoldersQtModel):
|
||||||
|
|
||||||
self._colored_items = set()
|
self._colored_items = set()
|
||||||
|
|
||||||
def _fill_item_data(self, item, folder_item):
|
|
||||||
"""
|
|
||||||
|
|
||||||
Args:
|
|
||||||
item (QtGui.QStandardItem): Item to fill data.
|
|
||||||
folder_item (FolderItem): Folder item.
|
|
||||||
"""
|
|
||||||
|
|
||||||
super(LoaderFoldersModel, self)._fill_item_data(item, folder_item)
|
|
||||||
|
|
||||||
def set_merged_products_selection(self, items):
|
def set_merged_products_selection(self, items):
|
||||||
changes = {
|
changes = {
|
||||||
folder_id: None
|
folder_id: None
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,7 @@ class ProductsModel(QtGui.QStandardItemModel):
|
||||||
self._last_project_name = None
|
self._last_project_name = None
|
||||||
self._last_folder_ids = []
|
self._last_folder_ids = []
|
||||||
self._last_project_statuses = {}
|
self._last_project_statuses = {}
|
||||||
|
self._last_status_icons_by_name = {}
|
||||||
|
|
||||||
def get_product_item_indexes(self):
|
def get_product_item_indexes(self):
|
||||||
return [
|
return [
|
||||||
|
|
@ -181,6 +182,13 @@ class ProductsModel(QtGui.QStandardItemModel):
|
||||||
return status_item.color
|
return status_item.color
|
||||||
|
|
||||||
col = index.column()
|
col = index.column()
|
||||||
|
if col == self.status_col and role == QtCore.Qt.DecorationRole:
|
||||||
|
role = VERSION_STATUS_ICON_ROLE
|
||||||
|
|
||||||
|
if role == VERSION_STATUS_ICON_ROLE:
|
||||||
|
status_name = self.data(index, VERSION_STATUS_NAME_ROLE)
|
||||||
|
return self._get_status_icon(status_name)
|
||||||
|
|
||||||
if col == 0:
|
if col == 0:
|
||||||
return super(ProductsModel, self).data(index, role)
|
return super(ProductsModel, self).data(index, role)
|
||||||
|
|
||||||
|
|
@ -260,6 +268,25 @@ class ProductsModel(QtGui.QStandardItemModel):
|
||||||
break
|
break
|
||||||
yield color
|
yield color
|
||||||
|
|
||||||
|
def _get_status_icon(self, status_name):
|
||||||
|
icon = self._last_status_icons_by_name.get(status_name)
|
||||||
|
if icon is not None:
|
||||||
|
return icon
|
||||||
|
|
||||||
|
status_item = self._last_project_statuses.get(status_name)
|
||||||
|
if status_item is not None:
|
||||||
|
icon = get_qt_icon({
|
||||||
|
"type": "material-symbols",
|
||||||
|
"name": status_item.icon,
|
||||||
|
"color": status_item.color,
|
||||||
|
})
|
||||||
|
|
||||||
|
if icon is None:
|
||||||
|
icon = QtGui.QIcon()
|
||||||
|
|
||||||
|
self._last_status_icons_by_name[status_name] = icon
|
||||||
|
return icon
|
||||||
|
|
||||||
def _clear(self):
|
def _clear(self):
|
||||||
root_item = self.invisibleRootItem()
|
root_item = self.invisibleRootItem()
|
||||||
root_item.removeRows(0, root_item.rowCount())
|
root_item.removeRows(0, root_item.rowCount())
|
||||||
|
|
@ -419,6 +446,7 @@ class ProductsModel(QtGui.QStandardItemModel):
|
||||||
status_item.name: status_item
|
status_item.name: status_item
|
||||||
for status_item in status_items
|
for status_item in status_items
|
||||||
}
|
}
|
||||||
|
self._last_status_icons_by_name = {}
|
||||||
|
|
||||||
active_site_icon_def = self._controller.get_active_site_icon_def(
|
active_site_icon_def = self._controller.get_active_site_icon_def(
|
||||||
project_name
|
project_name
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ from ayon_core.pipeline.create.context import (
|
||||||
ConvertorsOperationFailed,
|
ConvertorsOperationFailed,
|
||||||
)
|
)
|
||||||
from ayon_core.pipeline.publish import get_publish_instance_label
|
from ayon_core.pipeline.publish import get_publish_instance_label
|
||||||
from ayon_core.tools.common_models import HierarchyModel
|
from ayon_core.tools.common_models import ProjectsModel, HierarchyModel
|
||||||
from ayon_core.lib.profiles_filtering import filter_profiles
|
from ayon_core.lib.profiles_filtering import filter_profiles
|
||||||
|
|
||||||
# Define constant for plugin orders offset
|
# Define constant for plugin orders offset
|
||||||
|
|
@ -1632,6 +1632,7 @@ class PublisherController(BasePublisherController):
|
||||||
self._resetting_instances = False
|
self._resetting_instances = False
|
||||||
|
|
||||||
# Cacher of avalon documents
|
# Cacher of avalon documents
|
||||||
|
self._projects_model = ProjectsModel(self)
|
||||||
self._hierarchy_model = HierarchyModel(self)
|
self._hierarchy_model = HierarchyModel(self)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -1697,6 +1698,16 @@ class PublisherController(BasePublisherController):
|
||||||
|
|
||||||
return self._create_context.get_current_project_settings()
|
return self._create_context.get_current_project_settings()
|
||||||
|
|
||||||
|
def get_folder_type_items(self, project_name, sender=None):
|
||||||
|
return self._projects_model.get_folder_type_items(
|
||||||
|
project_name, sender
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_task_type_items(self, project_name, sender=None):
|
||||||
|
return self._projects_model.get_task_type_items(
|
||||||
|
project_name, sender
|
||||||
|
)
|
||||||
|
|
||||||
# Hierarchy model
|
# Hierarchy model
|
||||||
def get_folder_items(self, project_name, sender=None):
|
def get_folder_items(self, project_name, sender=None):
|
||||||
return self._hierarchy_model.get_folder_items(project_name, sender)
|
return self._hierarchy_model.get_folder_items(project_name, sender)
|
||||||
|
|
@ -1725,14 +1736,14 @@ class PublisherController(BasePublisherController):
|
||||||
return folder_item.entity_id
|
return folder_item.entity_id
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_task_names_by_folder_paths(self, folder_paths):
|
def get_task_items_by_folder_paths(self, folder_paths):
|
||||||
if not folder_paths:
|
if not folder_paths:
|
||||||
return {}
|
return {}
|
||||||
folder_items = self._hierarchy_model.get_folder_items_by_paths(
|
folder_items = self._hierarchy_model.get_folder_items_by_paths(
|
||||||
self.project_name, folder_paths
|
self.project_name, folder_paths
|
||||||
)
|
)
|
||||||
output = {
|
output = {
|
||||||
folder_path: set()
|
folder_path: []
|
||||||
for folder_path in folder_paths
|
for folder_path in folder_paths
|
||||||
}
|
}
|
||||||
project_name = self.project_name
|
project_name = self.project_name
|
||||||
|
|
@ -1740,10 +1751,7 @@ class PublisherController(BasePublisherController):
|
||||||
task_items = self._hierarchy_model.get_task_items(
|
task_items = self._hierarchy_model.get_task_items(
|
||||||
project_name, folder_item.entity_id, None
|
project_name, folder_item.entity_id, None
|
||||||
)
|
)
|
||||||
output[folder_item.path] = {
|
output[folder_item.path] = task_items
|
||||||
task_item.name
|
|
||||||
for task_item in task_items
|
|
||||||
}
|
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,16 @@ class CreateHierarchyController:
|
||||||
project_name, folder_id, sender
|
project_name, folder_id, sender
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_folder_type_items(self, project_name, sender=None):
|
||||||
|
return self._controller.get_folder_type_items(
|
||||||
|
project_name, sender
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_task_type_items(self, project_name, sender=None):
|
||||||
|
return self._controller.get_task_type_items(
|
||||||
|
project_name, sender
|
||||||
|
)
|
||||||
|
|
||||||
# Selection model
|
# Selection model
|
||||||
def set_selected_project(self, project_name):
|
def set_selected_project(self, project_name):
|
||||||
self._selection_model.set_selected_project(project_name)
|
self._selection_model.set_selected_project(project_name)
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,11 @@ class FoldersDialogController:
|
||||||
def get_folder_items(self, project_name, sender=None):
|
def get_folder_items(self, project_name, sender=None):
|
||||||
return self._controller.get_folder_items(project_name, sender)
|
return self._controller.get_folder_items(project_name, sender)
|
||||||
|
|
||||||
|
def get_folder_type_items(self, project_name, sender=None):
|
||||||
|
return self._controller.get_folder_type_items(
|
||||||
|
project_name, sender
|
||||||
|
)
|
||||||
|
|
||||||
def set_selected_folder(self, folder_id):
|
def set_selected_folder(self, folder_id):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -99,12 +99,16 @@ class TasksModel(QtGui.QStandardItemModel):
|
||||||
root_item.removeRows(0, self.rowCount())
|
root_item.removeRows(0, self.rowCount())
|
||||||
return
|
return
|
||||||
|
|
||||||
task_names_by_folder_path = (
|
task_items_by_folder_path = (
|
||||||
self._controller.get_task_names_by_folder_paths(
|
self._controller.get_task_items_by_folder_paths(
|
||||||
self._folder_paths
|
self._folder_paths
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
task_names_by_folder_path = {
|
||||||
|
folder_path: {item.name for item in task_items}
|
||||||
|
for folder_path, task_items in task_items_by_folder_path.items()
|
||||||
|
}
|
||||||
self._task_names_by_folder_path = task_names_by_folder_path
|
self._task_names_by_folder_path = task_names_by_folder_path
|
||||||
|
|
||||||
new_task_names = self.get_intersection_of_tasks(
|
new_task_names = self.get_intersection_of_tasks(
|
||||||
|
|
@ -122,22 +126,54 @@ class TasksModel(QtGui.QStandardItemModel):
|
||||||
item = self._items_by_name.pop(task_name)
|
item = self._items_by_name.pop(task_name)
|
||||||
root_item.removeRow(item.row())
|
root_item.removeRow(item.row())
|
||||||
|
|
||||||
icon = get_qt_icon({
|
default_icon = get_qt_icon({
|
||||||
"type": "awesome-font",
|
"type": "awesome-font",
|
||||||
"name": "fa.male",
|
"name": "fa.male",
|
||||||
"color": get_default_entity_icon_color(),
|
"color": get_default_entity_icon_color(),
|
||||||
})
|
})
|
||||||
new_items = []
|
new_items = []
|
||||||
|
task_type_items = {
|
||||||
|
task_type_item.name: task_type_item
|
||||||
|
for task_type_item in self._controller.get_task_type_items(
|
||||||
|
self._controller.project_name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
icon_name_by_task_name = {}
|
||||||
|
for task_items in task_items_by_folder_path.values():
|
||||||
|
for task_item in task_items:
|
||||||
|
task_name = task_item.name
|
||||||
|
if (
|
||||||
|
task_name not in new_task_names
|
||||||
|
or task_name in icon_name_by_task_name
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
task_type_name = task_item.task_type
|
||||||
|
task_type_item = task_type_items.get(task_type_name)
|
||||||
|
if task_type_item:
|
||||||
|
icon_name_by_task_name[task_name] = task_type_item.icon
|
||||||
|
|
||||||
for task_name in new_task_names:
|
for task_name in new_task_names:
|
||||||
if task_name in self._items_by_name:
|
item = self._items_by_name.get(task_name)
|
||||||
|
if item is None:
|
||||||
|
item = QtGui.QStandardItem(task_name)
|
||||||
|
item.setData(task_name, TASK_NAME_ROLE)
|
||||||
|
self._items_by_name[task_name] = item
|
||||||
|
new_items.append(item)
|
||||||
|
|
||||||
|
if not task_name:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
item = QtGui.QStandardItem(task_name)
|
icon_name = icon_name_by_task_name.get(task_name)
|
||||||
item.setData(task_name, TASK_NAME_ROLE)
|
icon = None
|
||||||
if task_name:
|
if icon_name:
|
||||||
item.setData(icon, QtCore.Qt.DecorationRole)
|
icon = get_qt_icon({
|
||||||
self._items_by_name[task_name] = item
|
"type": "material-symbols",
|
||||||
new_items.append(item)
|
"name": icon_name,
|
||||||
|
"color": get_default_entity_icon_color(),
|
||||||
|
})
|
||||||
|
if icon is None:
|
||||||
|
icon = default_icon
|
||||||
|
item.setData(icon, QtCore.Qt.DecorationRole)
|
||||||
|
|
||||||
if new_items:
|
if new_items:
|
||||||
root_item.appendRows(new_items)
|
root_item.appendRows(new_items)
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,9 @@ class InventoryModel(QtGui.QStandardItemModel):
|
||||||
|
|
||||||
self._default_icon_color = get_default_entity_icon_color()
|
self._default_icon_color = get_default_entity_icon_color()
|
||||||
|
|
||||||
|
self._last_project_statuses = {}
|
||||||
|
self._last_status_icons_by_name = {}
|
||||||
|
|
||||||
def outdated(self, item):
|
def outdated(self, item):
|
||||||
return item.get("isOutdated", True)
|
return item.get("isOutdated", True)
|
||||||
|
|
||||||
|
|
@ -159,10 +162,11 @@ class InventoryModel(QtGui.QStandardItemModel):
|
||||||
self._controller.get_site_provider_icons().items()
|
self._controller.get_site_provider_icons().items()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
status_items_by_name = {
|
self._last_project_statuses = {
|
||||||
status_item.name: status_item
|
status_item.name: status_item
|
||||||
for status_item in self._controller.get_project_status_items()
|
for status_item in self._controller.get_project_status_items()
|
||||||
}
|
}
|
||||||
|
self._last_status_icons_by_name = {}
|
||||||
|
|
||||||
group_item_icon = qtawesome.icon(
|
group_item_icon = qtawesome.icon(
|
||||||
"fa.folder", color=self._default_icon_color
|
"fa.folder", color=self._default_icon_color
|
||||||
|
|
@ -186,7 +190,6 @@ class InventoryModel(QtGui.QStandardItemModel):
|
||||||
remote_site_icon = site_icons.get(sites_info["remote_site_provider"])
|
remote_site_icon = site_icons.get(sites_info["remote_site_provider"])
|
||||||
|
|
||||||
root_item = self.invisibleRootItem()
|
root_item = self.invisibleRootItem()
|
||||||
|
|
||||||
group_items = []
|
group_items = []
|
||||||
for repre_id, container_items in items_by_repre_id.items():
|
for repre_id, container_items in items_by_repre_id.items():
|
||||||
repre_info = repre_info_by_id[repre_id]
|
repre_info = repre_info_by_id[repre_id]
|
||||||
|
|
@ -195,8 +198,6 @@ class InventoryModel(QtGui.QStandardItemModel):
|
||||||
is_latest = False
|
is_latest = False
|
||||||
is_hero = False
|
is_hero = False
|
||||||
status_name = None
|
status_name = None
|
||||||
status_color = None
|
|
||||||
status_short = None
|
|
||||||
if not repre_info.is_valid:
|
if not repre_info.is_valid:
|
||||||
group_name = "< Entity N/A >"
|
group_name = "< Entity N/A >"
|
||||||
item_icon = invalid_item_icon
|
item_icon = invalid_item_icon
|
||||||
|
|
@ -219,10 +220,10 @@ class InventoryModel(QtGui.QStandardItemModel):
|
||||||
if not is_latest:
|
if not is_latest:
|
||||||
version_color = self.OUTDATED_COLOR
|
version_color = self.OUTDATED_COLOR
|
||||||
status_name = version_item.status
|
status_name = version_item.status
|
||||||
status_item = status_items_by_name.get(status_name)
|
|
||||||
if status_item:
|
status_color, status_short, status_icon = self._get_status_data(
|
||||||
status_short = status_item.short
|
status_name
|
||||||
status_color = status_item.color
|
)
|
||||||
|
|
||||||
container_model_items = []
|
container_model_items = []
|
||||||
for container_item in container_items:
|
for container_item in container_items:
|
||||||
|
|
@ -273,6 +274,7 @@ class InventoryModel(QtGui.QStandardItemModel):
|
||||||
group_item.setData(status_name, STATUS_NAME_ROLE)
|
group_item.setData(status_name, STATUS_NAME_ROLE)
|
||||||
group_item.setData(status_short, STATUS_SHORT_ROLE)
|
group_item.setData(status_short, STATUS_SHORT_ROLE)
|
||||||
group_item.setData(status_color, STATUS_COLOR_ROLE)
|
group_item.setData(status_color, STATUS_COLOR_ROLE)
|
||||||
|
group_item.setData(status_icon, STATUS_ICON_ROLE)
|
||||||
|
|
||||||
group_item.setData(
|
group_item.setData(
|
||||||
active_site_progress, ACTIVE_SITE_PROGRESS_ROLE
|
active_site_progress, ACTIVE_SITE_PROGRESS_ROLE
|
||||||
|
|
@ -355,6 +357,32 @@ class InventoryModel(QtGui.QStandardItemModel):
|
||||||
root_item = self.invisibleRootItem()
|
root_item = self.invisibleRootItem()
|
||||||
root_item.removeRows(0, root_item.rowCount())
|
root_item.removeRows(0, root_item.rowCount())
|
||||||
|
|
||||||
|
def _get_status_data(self, status_name):
|
||||||
|
status_item = self._last_project_statuses.get(status_name)
|
||||||
|
status_icon = self._get_status_icon(status_name, status_item)
|
||||||
|
status_color = status_short = None
|
||||||
|
if status_item is not None:
|
||||||
|
status_color = status_item.color
|
||||||
|
status_short = status_item.short
|
||||||
|
return status_color, status_short, status_icon
|
||||||
|
|
||||||
|
def _get_status_icon(self, status_name, status_item):
|
||||||
|
icon = self._last_status_icons_by_name.get(status_name)
|
||||||
|
if icon is not None:
|
||||||
|
return icon
|
||||||
|
|
||||||
|
icon = None
|
||||||
|
if status_item is not None:
|
||||||
|
icon = get_qt_icon({
|
||||||
|
"type": "material-symbols",
|
||||||
|
"name": status_item.icon,
|
||||||
|
"color": status_item.color,
|
||||||
|
})
|
||||||
|
if icon is None:
|
||||||
|
icon = QtGui.QIcon()
|
||||||
|
self._last_status_icons_by_name[status_name] = icon
|
||||||
|
return icon
|
||||||
|
|
||||||
|
|
||||||
class FilterProxyModel(QtCore.QSortFilterProxyModel):
|
class FilterProxyModel(QtCore.QSortFilterProxyModel):
|
||||||
"""Filter model to where key column's value is in the filtered tags"""
|
"""Filter model to where key column's value is in the filtered tags"""
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import uuid
|
||||||
|
|
||||||
from qtpy import QtWidgets, QtCore, QtGui
|
from qtpy import QtWidgets, QtCore, QtGui
|
||||||
|
|
||||||
|
from ayon_core.tools.utils import get_qt_icon
|
||||||
from ayon_core.tools.utils.delegates import StatusDelegate
|
from ayon_core.tools.utils.delegates import StatusDelegate
|
||||||
|
|
||||||
from .model import (
|
from .model import (
|
||||||
|
|
@ -20,13 +21,15 @@ class VersionOption:
|
||||||
label,
|
label,
|
||||||
status_name,
|
status_name,
|
||||||
status_short,
|
status_short,
|
||||||
status_color
|
status_color,
|
||||||
|
status_icon,
|
||||||
):
|
):
|
||||||
self.version = version
|
self.version = version
|
||||||
self.label = label
|
self.label = label
|
||||||
self.status_name = status_name
|
self.status_name = status_name
|
||||||
self.status_short = status_short
|
self.status_short = status_short
|
||||||
self.status_color = status_color
|
self.status_color = status_color
|
||||||
|
self.status_icon = status_icon
|
||||||
|
|
||||||
|
|
||||||
class SelectVersionModel(QtGui.QStandardItemModel):
|
class SelectVersionModel(QtGui.QStandardItemModel):
|
||||||
|
|
@ -84,27 +87,52 @@ class SelectVersionComboBox(QtWidgets.QComboBox):
|
||||||
return
|
return
|
||||||
|
|
||||||
painter.save()
|
painter.save()
|
||||||
text_field_rect = self.style().subControlRect(
|
|
||||||
|
status_icon = self.itemData(idx, STATUS_ICON_ROLE)
|
||||||
|
content_field_rect = self.style().subControlRect(
|
||||||
QtWidgets.QStyle.CC_ComboBox,
|
QtWidgets.QStyle.CC_ComboBox,
|
||||||
option,
|
option,
|
||||||
QtWidgets.QStyle.SC_ComboBoxEditField
|
QtWidgets.QStyle.SC_ComboBoxEditField
|
||||||
)
|
).adjusted(1, 0, -1, 0)
|
||||||
adj_rect = text_field_rect.adjusted(1, 0, -1, 0)
|
|
||||||
|
metrics = option.fontMetrics
|
||||||
|
version_text_width = metrics.width(option.currentText) + 2
|
||||||
|
version_text_rect = QtCore.QRect(content_field_rect)
|
||||||
|
version_text_rect.setWidth(version_text_width)
|
||||||
|
|
||||||
painter.drawText(
|
painter.drawText(
|
||||||
adj_rect,
|
version_text_rect,
|
||||||
QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter,
|
QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter,
|
||||||
option.currentText
|
option.currentText
|
||||||
)
|
)
|
||||||
metrics = QtGui.QFontMetrics(self.font())
|
|
||||||
text_width = metrics.width(option.currentText)
|
status_text_rect = QtCore.QRect(content_field_rect)
|
||||||
x_offset = text_width + 2
|
status_text_rect.setLeft(version_text_rect.right() + 2)
|
||||||
diff_width = adj_rect.width() - x_offset
|
if status_icon is not None and not status_icon.isNull():
|
||||||
if diff_width <= 0:
|
icon_rect = QtCore.QRect(status_text_rect)
|
||||||
|
diff = icon_rect.height() - metrics.height()
|
||||||
|
if diff < 0:
|
||||||
|
diff = 0
|
||||||
|
top_offset = diff // 2
|
||||||
|
bottom_offset = diff - top_offset
|
||||||
|
icon_rect.adjust(0, top_offset, 0, -bottom_offset)
|
||||||
|
icon_rect.setWidth(metrics.height())
|
||||||
|
status_icon.paint(
|
||||||
|
painter,
|
||||||
|
icon_rect,
|
||||||
|
QtCore.Qt.AlignCenter,
|
||||||
|
QtGui.QIcon.Normal,
|
||||||
|
QtGui.QIcon.On
|
||||||
|
)
|
||||||
|
status_text_rect.setLeft(icon_rect.right() + 2)
|
||||||
|
|
||||||
|
if status_text_rect.width() <= 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
status_rect = adj_rect.adjusted(x_offset + 2, 0, 0, 0)
|
if status_text_rect.width() < metrics.width(status_name):
|
||||||
if diff_width < metrics.width(status_name):
|
|
||||||
status_name = self.itemData(idx, STATUS_SHORT_ROLE)
|
status_name = self.itemData(idx, STATUS_SHORT_ROLE)
|
||||||
|
if status_text_rect.width() < metrics.width(status_name):
|
||||||
|
status_name = ""
|
||||||
|
|
||||||
color = QtGui.QColor(self.itemData(idx, STATUS_COLOR_ROLE))
|
color = QtGui.QColor(self.itemData(idx, STATUS_COLOR_ROLE))
|
||||||
|
|
||||||
|
|
@ -112,7 +140,7 @@ class SelectVersionComboBox(QtWidgets.QComboBox):
|
||||||
pen.setColor(color)
|
pen.setColor(color)
|
||||||
painter.setPen(pen)
|
painter.setPen(pen)
|
||||||
painter.drawText(
|
painter.drawText(
|
||||||
status_rect,
|
status_text_rect,
|
||||||
QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter,
|
QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter,
|
||||||
status_name
|
status_name
|
||||||
)
|
)
|
||||||
|
|
@ -140,7 +168,17 @@ class SelectVersionComboBox(QtWidgets.QComboBox):
|
||||||
root_item.removeRows(0, root_item.rowCount())
|
root_item.removeRows(0, root_item.rowCount())
|
||||||
|
|
||||||
new_items = []
|
new_items = []
|
||||||
|
icons_by_name = {}
|
||||||
for version_option in version_options:
|
for version_option in version_options:
|
||||||
|
icon = icons_by_name.get(version_option.status_icon)
|
||||||
|
if icon is None:
|
||||||
|
icon = get_qt_icon({
|
||||||
|
"type": "material-symbols",
|
||||||
|
"name": version_option.status_icon,
|
||||||
|
"color": version_option.status_color
|
||||||
|
})
|
||||||
|
icons_by_name[version_option.status_icon] = icon
|
||||||
|
|
||||||
item_id = uuid.uuid4().hex
|
item_id = uuid.uuid4().hex
|
||||||
item = QtGui.QStandardItem(version_option.label)
|
item = QtGui.QStandardItem(version_option.label)
|
||||||
item.setColumnCount(root_item.columnCount())
|
item.setColumnCount(root_item.columnCount())
|
||||||
|
|
@ -153,6 +191,7 @@ class SelectVersionComboBox(QtWidgets.QComboBox):
|
||||||
item.setData(
|
item.setData(
|
||||||
version_option.status_color, STATUS_COLOR_ROLE
|
version_option.status_color, STATUS_COLOR_ROLE
|
||||||
)
|
)
|
||||||
|
item.setData(icon, STATUS_ICON_ROLE)
|
||||||
item.setData(item_id, ITEM_ID_ROLE)
|
item.setData(item_id, ITEM_ID_ROLE)
|
||||||
|
|
||||||
new_items.append(item)
|
new_items.append(item)
|
||||||
|
|
|
||||||
|
|
@ -740,10 +740,12 @@ class SceneInventoryView(QtWidgets.QTreeView):
|
||||||
status_name = version_item.status
|
status_name = version_item.status
|
||||||
status_short = None
|
status_short = None
|
||||||
status_color = None
|
status_color = None
|
||||||
|
status_icon = None
|
||||||
status_item = status_items_by_name.get(status_name)
|
status_item = status_items_by_name.get(status_name)
|
||||||
if status_item:
|
if status_item:
|
||||||
status_short = status_item.short
|
status_short = status_item.short
|
||||||
status_color = status_item.color
|
status_color = status_item.color
|
||||||
|
status_icon = status_item.icon
|
||||||
version_options.append(
|
version_options.append(
|
||||||
VersionOption(
|
VersionOption(
|
||||||
version,
|
version,
|
||||||
|
|
@ -751,6 +753,7 @@ class SceneInventoryView(QtWidgets.QTreeView):
|
||||||
status_name,
|
status_name,
|
||||||
status_short,
|
status_short,
|
||||||
status_color,
|
status_color,
|
||||||
|
status_icon,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from qtpy import QtWidgets, QtGui
|
from qtpy import QtWidgets, QtGui, QtCore
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -130,32 +130,65 @@ class StatusDelegate(QtWidgets.QStyledItemDelegate):
|
||||||
else:
|
else:
|
||||||
style = QtWidgets.QApplication.style()
|
style = QtWidgets.QApplication.style()
|
||||||
|
|
||||||
style.drawControl(
|
self.initStyleOption(option, index)
|
||||||
QtWidgets.QCommonStyle.CE_ItemViewItem,
|
|
||||||
|
mode = QtGui.QIcon.Normal
|
||||||
|
if not (option.state & QtWidgets.QStyle.State_Enabled):
|
||||||
|
mode = QtGui.QIcon.Disabled
|
||||||
|
elif option.state & QtWidgets.QStyle.State_Selected:
|
||||||
|
mode = QtGui.QIcon.Selected
|
||||||
|
state = QtGui.QIcon.Off
|
||||||
|
if option.state & QtWidgets.QStyle.State_Open:
|
||||||
|
state = QtGui.QIcon.On
|
||||||
|
icon = self._get_status_icon(index)
|
||||||
|
option.features |= QtWidgets.QStyleOptionViewItem.HasDecoration
|
||||||
|
option.icon = icon
|
||||||
|
act_size = icon.actualSize(option.decorationSize, mode, state)
|
||||||
|
option.decorationSize = QtCore.QSize(
|
||||||
|
min(option.decorationSize.width(), act_size.width()),
|
||||||
|
min(option.decorationSize.height(), act_size.height())
|
||||||
|
)
|
||||||
|
|
||||||
|
text = self._get_status_name(index)
|
||||||
|
if text:
|
||||||
|
option.features |= QtWidgets.QStyleOptionViewItem.HasDisplay
|
||||||
|
option.text = text
|
||||||
|
|
||||||
|
painter.save()
|
||||||
|
painter.setClipRect(option.rect)
|
||||||
|
|
||||||
|
icon_rect = style.subElementRect(
|
||||||
|
QtWidgets.QCommonStyle.SE_ItemViewItemDecoration,
|
||||||
|
option,
|
||||||
|
option.widget
|
||||||
|
)
|
||||||
|
text_rect = style.subElementRect(
|
||||||
|
QtWidgets.QCommonStyle.SE_ItemViewItemText,
|
||||||
|
option,
|
||||||
|
option.widget
|
||||||
|
)
|
||||||
|
|
||||||
|
# Draw background
|
||||||
|
style.drawPrimitive(
|
||||||
|
QtWidgets.QCommonStyle.PE_PanelItemViewItem,
|
||||||
option,
|
option,
|
||||||
painter,
|
painter,
|
||||||
option.widget
|
option.widget
|
||||||
)
|
)
|
||||||
|
|
||||||
painter.save()
|
# Draw icon
|
||||||
|
option.icon.paint(
|
||||||
text_rect = style.subElementRect(
|
painter,
|
||||||
QtWidgets.QCommonStyle.SE_ItemViewItemText,
|
icon_rect,
|
||||||
option
|
option.decorationAlignment,
|
||||||
|
mode,
|
||||||
|
state
|
||||||
)
|
)
|
||||||
text_margin = style.proxy().pixelMetric(
|
|
||||||
QtWidgets.QCommonStyle.PM_FocusFrameHMargin,
|
|
||||||
option,
|
|
||||||
option.widget
|
|
||||||
) + 1
|
|
||||||
padded_text_rect = text_rect.adjusted(
|
|
||||||
text_margin, 0, - text_margin, 0
|
|
||||||
)
|
|
||||||
|
|
||||||
fm = QtGui.QFontMetrics(option.font)
|
fm = QtGui.QFontMetrics(option.font)
|
||||||
text = self._get_status_name(index)
|
if text_rect.width() < fm.width(text):
|
||||||
if padded_text_rect.width() < fm.width(text):
|
|
||||||
text = self._get_status_short_name(index)
|
text = self._get_status_short_name(index)
|
||||||
|
if text_rect.width() < fm.width(text):
|
||||||
|
text = ""
|
||||||
|
|
||||||
fg_color = self._get_status_color(index)
|
fg_color = self._get_status_color(index)
|
||||||
pen = painter.pen()
|
pen = painter.pen()
|
||||||
|
|
@ -163,11 +196,47 @@ class StatusDelegate(QtWidgets.QStyledItemDelegate):
|
||||||
painter.setPen(pen)
|
painter.setPen(pen)
|
||||||
|
|
||||||
painter.drawText(
|
painter.drawText(
|
||||||
padded_text_rect,
|
text_rect,
|
||||||
option.displayAlignment,
|
option.displayAlignment,
|
||||||
text
|
text
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if option.state & QtWidgets.QStyle.State_HasFocus:
|
||||||
|
focus_opt = QtWidgets.QStyleOptionFocusRect()
|
||||||
|
focus_opt.state = option.state
|
||||||
|
focus_opt.direction = option.direction
|
||||||
|
focus_opt.rect = option.rect
|
||||||
|
focus_opt.fontMetrics = option.fontMetrics
|
||||||
|
focus_opt.palette = option.palette
|
||||||
|
|
||||||
|
focus_opt.rect = style.subElementRect(
|
||||||
|
QtWidgets.QCommonStyle.SE_ItemViewItemFocusRect,
|
||||||
|
option,
|
||||||
|
option.widget
|
||||||
|
)
|
||||||
|
focus_opt.state |= (
|
||||||
|
QtWidgets.QStyle.State_KeyboardFocusChange
|
||||||
|
| QtWidgets.QStyle.State_Item
|
||||||
|
)
|
||||||
|
focus_opt.backgroundColor = option.palette.color(
|
||||||
|
(
|
||||||
|
QtGui.QPalette.Normal
|
||||||
|
if option.state & QtWidgets.QStyle.State_Enabled
|
||||||
|
else QtGui.QPalette.Disabled
|
||||||
|
),
|
||||||
|
(
|
||||||
|
QtGui.QPalette.Highlight
|
||||||
|
if option.state & QtWidgets.QStyle.State_Selected
|
||||||
|
else QtGui.QPalette.Window
|
||||||
|
)
|
||||||
|
)
|
||||||
|
style.drawPrimitive(
|
||||||
|
QtWidgets.QCommonStyle.PE_FrameFocusRect,
|
||||||
|
focus_opt,
|
||||||
|
painter,
|
||||||
|
option.widget
|
||||||
|
)
|
||||||
|
|
||||||
painter.restore()
|
painter.restore()
|
||||||
|
|
||||||
def _get_status_name(self, index):
|
def _get_status_name(self, index):
|
||||||
|
|
@ -180,6 +249,9 @@ class StatusDelegate(QtWidgets.QStyledItemDelegate):
|
||||||
return QtGui.QColor(index.data(self.status_color_role))
|
return QtGui.QColor(index.data(self.status_color_role))
|
||||||
|
|
||||||
def _get_status_icon(self, index):
|
def _get_status_icon(self, index):
|
||||||
|
icon = None
|
||||||
if self.status_icon_role is not None:
|
if self.status_icon_role is not None:
|
||||||
return index.data(self.status_icon_role)
|
icon = index.data(self.status_icon_role)
|
||||||
return None
|
if icon is None:
|
||||||
|
return QtGui.QIcon()
|
||||||
|
return icon
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@ import collections
|
||||||
from qtpy import QtWidgets, QtGui, QtCore
|
from qtpy import QtWidgets, QtGui, QtCore
|
||||||
|
|
||||||
from ayon_core.lib.events import QueuedEventSystem
|
from ayon_core.lib.events import QueuedEventSystem
|
||||||
|
from ayon_core.style import get_default_entity_icon_color
|
||||||
from ayon_core.tools.common_models import (
|
from ayon_core.tools.common_models import (
|
||||||
|
ProjectsModel,
|
||||||
HierarchyModel,
|
HierarchyModel,
|
||||||
HierarchyExpectedSelection,
|
HierarchyExpectedSelection,
|
||||||
)
|
)
|
||||||
|
|
@ -25,8 +27,9 @@ class FoldersQtModel(QtGui.QStandardItemModel):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
controller (AbstractWorkfilesFrontend): The control object.
|
controller (AbstractWorkfilesFrontend): The control object.
|
||||||
"""
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
_default_folder_icon = None
|
||||||
refreshed = QtCore.Signal()
|
refreshed = QtCore.Signal()
|
||||||
|
|
||||||
def __init__(self, controller):
|
def __init__(self, controller):
|
||||||
|
|
@ -71,13 +74,6 @@ class FoldersQtModel(QtGui.QStandardItemModel):
|
||||||
|
|
||||||
self.set_project_name(self._last_project_name)
|
self.set_project_name(self._last_project_name)
|
||||||
|
|
||||||
def _clear_items(self):
|
|
||||||
self._items_by_id = {}
|
|
||||||
self._parent_id_by_id = {}
|
|
||||||
self._has_content = False
|
|
||||||
root_item = self.invisibleRootItem()
|
|
||||||
root_item.removeRows(0, root_item.rowCount())
|
|
||||||
|
|
||||||
def get_index_by_id(self, item_id):
|
def get_index_by_id(self, item_id):
|
||||||
"""Get index by folder id.
|
"""Get index by folder id.
|
||||||
|
|
||||||
|
|
@ -123,7 +119,7 @@ class FoldersQtModel(QtGui.QStandardItemModel):
|
||||||
|
|
||||||
if not project_name:
|
if not project_name:
|
||||||
self._last_project_name = project_name
|
self._last_project_name = project_name
|
||||||
self._fill_items({})
|
self._fill_items({}, {})
|
||||||
self._current_refresh_thread = None
|
self._current_refresh_thread = None
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -140,15 +136,42 @@ class FoldersQtModel(QtGui.QStandardItemModel):
|
||||||
|
|
||||||
thread = RefreshThread(
|
thread = RefreshThread(
|
||||||
project_name,
|
project_name,
|
||||||
self._controller.get_folder_items,
|
self._thread_getter,
|
||||||
project_name,
|
project_name
|
||||||
FOLDERS_MODEL_SENDER_NAME
|
|
||||||
)
|
)
|
||||||
self._current_refresh_thread = thread
|
self._current_refresh_thread = thread
|
||||||
self._refresh_threads[thread.id] = thread
|
self._refresh_threads[thread.id] = thread
|
||||||
thread.refresh_finished.connect(self._on_refresh_thread)
|
thread.refresh_finished.connect(self._on_refresh_thread)
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _get_default_folder_icon(cls):
|
||||||
|
if cls._default_folder_icon is None:
|
||||||
|
cls._default_folder_icon = get_qt_icon({
|
||||||
|
"type": "awesome-font",
|
||||||
|
"name": "fa.folder",
|
||||||
|
"color": get_default_entity_icon_color()
|
||||||
|
})
|
||||||
|
return cls._default_folder_icon
|
||||||
|
|
||||||
|
def _clear_items(self):
|
||||||
|
self._items_by_id = {}
|
||||||
|
self._parent_id_by_id = {}
|
||||||
|
self._has_content = False
|
||||||
|
root_item = self.invisibleRootItem()
|
||||||
|
root_item.removeRows(0, root_item.rowCount())
|
||||||
|
|
||||||
|
def _thread_getter(self, project_name):
|
||||||
|
folder_items = self._controller.get_folder_items(
|
||||||
|
project_name, FOLDERS_MODEL_SENDER_NAME
|
||||||
|
)
|
||||||
|
folder_type_items = {}
|
||||||
|
if hasattr(self._controller, "get_folder_type_items"):
|
||||||
|
folder_type_items = self._controller.get_folder_type_items(
|
||||||
|
project_name, FOLDERS_MODEL_SENDER_NAME
|
||||||
|
)
|
||||||
|
return folder_items, folder_type_items
|
||||||
|
|
||||||
def _on_refresh_thread(self, thread_id):
|
def _on_refresh_thread(self, thread_id):
|
||||||
"""Callback when refresh thread is finished.
|
"""Callback when refresh thread is finished.
|
||||||
|
|
||||||
|
|
@ -169,19 +192,55 @@ class FoldersQtModel(QtGui.QStandardItemModel):
|
||||||
or thread_id != self._current_refresh_thread.id
|
or thread_id != self._current_refresh_thread.id
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
|
folder_items, folder_type_items = thread.get_result()
|
||||||
self._fill_items(thread.get_result())
|
self._fill_items(folder_items, folder_type_items)
|
||||||
self._current_refresh_thread = None
|
self._current_refresh_thread = None
|
||||||
|
|
||||||
def _fill_item_data(self, item, folder_item):
|
def _get_folder_item_icon(
|
||||||
|
self,
|
||||||
|
folder_item,
|
||||||
|
folder_type_item_by_name,
|
||||||
|
folder_type_icon_cache
|
||||||
|
):
|
||||||
|
icon = folder_type_icon_cache.get(folder_item.folder_type)
|
||||||
|
if icon is not None:
|
||||||
|
return icon
|
||||||
|
|
||||||
|
folder_type_item = folder_type_item_by_name.get(
|
||||||
|
folder_item.folder_type
|
||||||
|
)
|
||||||
|
icon = None
|
||||||
|
if folder_type_item is not None:
|
||||||
|
icon = get_qt_icon({
|
||||||
|
"type": "material-symbols",
|
||||||
|
"name": folder_type_item.icon,
|
||||||
|
"color": get_default_entity_icon_color()
|
||||||
|
})
|
||||||
|
|
||||||
|
if icon is None:
|
||||||
|
icon = self._get_default_folder_icon()
|
||||||
|
folder_type_icon_cache[folder_item.folder_type] = icon
|
||||||
|
return icon
|
||||||
|
|
||||||
|
def _fill_item_data(
|
||||||
|
self,
|
||||||
|
item,
|
||||||
|
folder_item,
|
||||||
|
folder_type_item_by_name,
|
||||||
|
folder_type_icon_cache
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
item (QtGui.QStandardItem): Item to fill data.
|
item (QtGui.QStandardItem): Item to fill data.
|
||||||
folder_item (FolderItem): Folder item.
|
folder_item (FolderItem): Folder item.
|
||||||
"""
|
|
||||||
|
|
||||||
icon = get_qt_icon(folder_item.icon)
|
"""
|
||||||
|
icon = self._get_folder_item_icon(
|
||||||
|
folder_item,
|
||||||
|
folder_type_item_by_name,
|
||||||
|
folder_type_icon_cache
|
||||||
|
)
|
||||||
item.setData(folder_item.entity_id, FOLDER_ID_ROLE)
|
item.setData(folder_item.entity_id, FOLDER_ID_ROLE)
|
||||||
item.setData(folder_item.name, FOLDER_NAME_ROLE)
|
item.setData(folder_item.name, FOLDER_NAME_ROLE)
|
||||||
item.setData(folder_item.path, FOLDER_PATH_ROLE)
|
item.setData(folder_item.path, FOLDER_PATH_ROLE)
|
||||||
|
|
@ -189,7 +248,7 @@ class FoldersQtModel(QtGui.QStandardItemModel):
|
||||||
item.setData(folder_item.label, QtCore.Qt.DisplayRole)
|
item.setData(folder_item.label, QtCore.Qt.DisplayRole)
|
||||||
item.setData(icon, QtCore.Qt.DecorationRole)
|
item.setData(icon, QtCore.Qt.DecorationRole)
|
||||||
|
|
||||||
def _fill_items(self, folder_items_by_id):
|
def _fill_items(self, folder_items_by_id, folder_type_items):
|
||||||
if not folder_items_by_id:
|
if not folder_items_by_id:
|
||||||
if folder_items_by_id is not None:
|
if folder_items_by_id is not None:
|
||||||
self._clear_items()
|
self._clear_items()
|
||||||
|
|
@ -197,6 +256,11 @@ class FoldersQtModel(QtGui.QStandardItemModel):
|
||||||
self.refreshed.emit()
|
self.refreshed.emit()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
folder_type_item_by_name = {
|
||||||
|
folder_type.name: folder_type
|
||||||
|
for folder_type in folder_type_items
|
||||||
|
}
|
||||||
|
folder_type_icon_cache = {}
|
||||||
self._has_content = True
|
self._has_content = True
|
||||||
|
|
||||||
folder_ids = set(folder_items_by_id)
|
folder_ids = set(folder_items_by_id)
|
||||||
|
|
@ -242,7 +306,12 @@ class FoldersQtModel(QtGui.QStandardItemModel):
|
||||||
else:
|
else:
|
||||||
is_new = self._parent_id_by_id[item_id] != parent_id
|
is_new = self._parent_id_by_id[item_id] != parent_id
|
||||||
|
|
||||||
self._fill_item_data(item, folder_item)
|
self._fill_item_data(
|
||||||
|
item,
|
||||||
|
folder_item,
|
||||||
|
folder_type_item_by_name,
|
||||||
|
folder_type_icon_cache
|
||||||
|
)
|
||||||
if is_new:
|
if is_new:
|
||||||
new_items.append(item)
|
new_items.append(item)
|
||||||
self._items_by_id[item_id] = item
|
self._items_by_id[item_id] = item
|
||||||
|
|
@ -619,6 +688,7 @@ class SimpleSelectionModel(object):
|
||||||
class SimpleFoldersController(object):
|
class SimpleFoldersController(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._event_system = self._create_event_system()
|
self._event_system = self._create_event_system()
|
||||||
|
self._projects_model = ProjectsModel(self)
|
||||||
self._hierarchy_model = HierarchyModel(self)
|
self._hierarchy_model = HierarchyModel(self)
|
||||||
self._selection_model = SimpleSelectionModel(self)
|
self._selection_model = SimpleSelectionModel(self)
|
||||||
self._expected_selection = HierarchyExpectedSelection(
|
self._expected_selection = HierarchyExpectedSelection(
|
||||||
|
|
@ -639,6 +709,11 @@ class SimpleFoldersController(object):
|
||||||
def get_folder_items(self, project_name, sender=None):
|
def get_folder_items(self, project_name, sender=None):
|
||||||
return self._hierarchy_model.get_folder_items(project_name, sender)
|
return self._hierarchy_model.get_folder_items(project_name, sender)
|
||||||
|
|
||||||
|
def get_folder_type_items(self, project_name, sender=None):
|
||||||
|
return self._projects_model.get_folder_type_items(
|
||||||
|
project_name, sender
|
||||||
|
)
|
||||||
|
|
||||||
def set_selected_project(self, project_name):
|
def set_selected_project(self, project_name):
|
||||||
self._selection_model.set_selected_project(project_name)
|
self._selection_model.set_selected_project(project_name)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ from functools import partial
|
||||||
|
|
||||||
from qtpy import QtWidgets, QtCore, QtGui
|
from qtpy import QtWidgets, QtCore, QtGui
|
||||||
import qtawesome
|
import qtawesome
|
||||||
|
import qtmaterialsymbols
|
||||||
|
|
||||||
from ayon_core.style import (
|
from ayon_core.style import (
|
||||||
get_objected_colors,
|
get_objected_colors,
|
||||||
|
|
@ -468,7 +469,7 @@ class _IconsCache:
|
||||||
if icon_type == "path":
|
if icon_type == "path":
|
||||||
parts = [icon_type, icon_def["path"]]
|
parts = [icon_type, icon_def["path"]]
|
||||||
|
|
||||||
elif icon_type == "awesome-font":
|
elif icon_type in {"awesome-font", "material-symbols"}:
|
||||||
parts = [icon_type, icon_def["name"], icon_def["color"]]
|
parts = [icon_type, icon_def["name"], icon_def["color"]]
|
||||||
return "|".join(parts)
|
return "|".join(parts)
|
||||||
|
|
||||||
|
|
@ -495,6 +496,13 @@ class _IconsCache:
|
||||||
if icon is None:
|
if icon is None:
|
||||||
icon = cls.get_qta_icon_by_name_and_color(
|
icon = cls.get_qta_icon_by_name_and_color(
|
||||||
"fa.{}".format(icon_name), icon_color)
|
"fa.{}".format(icon_name), icon_color)
|
||||||
|
|
||||||
|
elif icon_type == "material-symbols":
|
||||||
|
icon_name = icon_def["name"]
|
||||||
|
icon_color = icon_def["color"]
|
||||||
|
if qtmaterialsymbols.get_icon_name_char(icon_name) is not None:
|
||||||
|
icon = qtmaterialsymbols.get_icon(icon_name, icon_color)
|
||||||
|
|
||||||
if icon is None:
|
if icon is None:
|
||||||
icon = cls.get_default()
|
icon = cls.get_default()
|
||||||
cls._cache[cache_key] = icon
|
cls._cache[cache_key] = icon
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
from qtpy import QtWidgets, QtGui, QtCore
|
from qtpy import QtWidgets, QtGui, QtCore
|
||||||
|
|
||||||
from ayon_core.style import get_disabled_entity_icon_color
|
from ayon_core.style import (
|
||||||
|
get_disabled_entity_icon_color,
|
||||||
|
get_default_entity_icon_color,
|
||||||
|
)
|
||||||
|
|
||||||
from .views import DeselectableTreeView
|
from .views import DeselectableTreeView
|
||||||
from .lib import RefreshThread, get_qt_icon
|
from .lib import RefreshThread, get_qt_icon
|
||||||
|
|
@ -17,8 +20,9 @@ class TasksQtModel(QtGui.QStandardItemModel):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
controller (AbstractWorkfilesFrontend): The control object.
|
controller (AbstractWorkfilesFrontend): The control object.
|
||||||
"""
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
_default_task_icon = None
|
||||||
refreshed = QtCore.Signal()
|
refreshed = QtCore.Signal()
|
||||||
|
|
||||||
def __init__(self, controller):
|
def __init__(self, controller):
|
||||||
|
|
@ -176,7 +180,7 @@ class TasksQtModel(QtGui.QStandardItemModel):
|
||||||
return
|
return
|
||||||
thread = RefreshThread(
|
thread = RefreshThread(
|
||||||
folder_id,
|
folder_id,
|
||||||
self._controller.get_task_items,
|
self._thread_getter,
|
||||||
project_name,
|
project_name,
|
||||||
folder_id
|
folder_id
|
||||||
)
|
)
|
||||||
|
|
@ -185,8 +189,55 @@ class TasksQtModel(QtGui.QStandardItemModel):
|
||||||
thread.refresh_finished.connect(self._on_refresh_thread)
|
thread.refresh_finished.connect(self._on_refresh_thread)
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
|
def _thread_getter(self, project_name, folder_id):
|
||||||
|
task_items = self._controller.get_task_items(
|
||||||
|
project_name, folder_id
|
||||||
|
)
|
||||||
|
task_type_items = {}
|
||||||
|
if hasattr(self._controller, "get_task_type_items"):
|
||||||
|
task_type_items = self._controller.get_task_type_items(
|
||||||
|
project_name
|
||||||
|
)
|
||||||
|
return task_items, task_type_items
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _get_default_task_icon(cls):
|
||||||
|
if cls._default_task_icon is None:
|
||||||
|
cls._default_task_icon = get_qt_icon({
|
||||||
|
"type": "awesome-font",
|
||||||
|
"name": "fa.male",
|
||||||
|
"color": get_default_entity_icon_color()
|
||||||
|
})
|
||||||
|
return cls._default_task_icon
|
||||||
|
|
||||||
|
def _get_task_item_icon(
|
||||||
|
self,
|
||||||
|
task_item,
|
||||||
|
task_type_item_by_name,
|
||||||
|
task_type_icon_cache
|
||||||
|
):
|
||||||
|
icon = task_type_icon_cache.get(task_item.task_type)
|
||||||
|
if icon is not None:
|
||||||
|
return icon
|
||||||
|
|
||||||
|
task_type_item = task_type_item_by_name.get(
|
||||||
|
task_item.task_type
|
||||||
|
)
|
||||||
|
icon = None
|
||||||
|
if task_type_item is not None:
|
||||||
|
icon = get_qt_icon({
|
||||||
|
"type": "material-symbols",
|
||||||
|
"name": task_type_item.icon,
|
||||||
|
"color": get_default_entity_icon_color()
|
||||||
|
})
|
||||||
|
|
||||||
|
if icon is None:
|
||||||
|
icon = self._get_default_task_icon()
|
||||||
|
task_type_icon_cache[task_item.task_type] = icon
|
||||||
|
return icon
|
||||||
|
|
||||||
def _fill_data_from_thread(self, thread):
|
def _fill_data_from_thread(self, thread):
|
||||||
task_items = thread.get_result()
|
task_items, task_type_items = thread.get_result()
|
||||||
# Task items are refreshed
|
# Task items are refreshed
|
||||||
if task_items is None:
|
if task_items is None:
|
||||||
return
|
return
|
||||||
|
|
@ -197,6 +248,11 @@ class TasksQtModel(QtGui.QStandardItemModel):
|
||||||
return
|
return
|
||||||
self._remove_invalid_items()
|
self._remove_invalid_items()
|
||||||
|
|
||||||
|
task_type_item_by_name = {
|
||||||
|
task_type_item.name: task_type_item
|
||||||
|
for task_type_item in task_type_items
|
||||||
|
}
|
||||||
|
task_type_icon_cache = {}
|
||||||
new_items = []
|
new_items = []
|
||||||
new_names = set()
|
new_names = set()
|
||||||
for task_item in task_items:
|
for task_item in task_items:
|
||||||
|
|
@ -209,8 +265,11 @@ class TasksQtModel(QtGui.QStandardItemModel):
|
||||||
new_items.append(item)
|
new_items.append(item)
|
||||||
self._items_by_name[name] = item
|
self._items_by_name[name] = item
|
||||||
|
|
||||||
# TODO cache locally
|
icon = self._get_task_item_icon(
|
||||||
icon = get_qt_icon(task_item.icon)
|
task_item,
|
||||||
|
task_type_item_by_name,
|
||||||
|
task_type_icon_cache
|
||||||
|
)
|
||||||
item.setData(task_item.label, QtCore.Qt.DisplayRole)
|
item.setData(task_item.label, QtCore.Qt.DisplayRole)
|
||||||
item.setData(name, ITEM_NAME_ROLE)
|
item.setData(name, ITEM_NAME_ROLE)
|
||||||
item.setData(task_item.id, ITEM_ID_ROLE)
|
item.setData(task_item.id, ITEM_ID_ROLE)
|
||||||
|
|
|
||||||
|
|
@ -546,6 +546,46 @@ class AbstractWorkfilesFrontend(AbstractWorkfilesCommon):
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_folder_type_items(self, project_name, sender=None):
|
||||||
|
"""Folder type items for a project.
|
||||||
|
|
||||||
|
This function may trigger events with topics
|
||||||
|
'projects.folder_types.refresh.started' and
|
||||||
|
'projects.folder_types.refresh.finished' which will contain 'sender'
|
||||||
|
value in data.
|
||||||
|
That may help to avoid re-refresh of items in UI elements.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
project_name (str): Project name.
|
||||||
|
sender (str): Who requested folder type items.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[FolderTypeItem]: Folder type information.
|
||||||
|
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_task_type_items(self, project_name, sender=None):
|
||||||
|
"""Task type items for a project.
|
||||||
|
|
||||||
|
This function may trigger events with topics
|
||||||
|
'projects.task_types.refresh.started' and
|
||||||
|
'projects.task_types.refresh.finished' which will contain 'sender'
|
||||||
|
value in data.
|
||||||
|
That may help to avoid re-refresh of items in UI elements.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
project_name (str): Project name.
|
||||||
|
sender (str): Who requested task type items.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[TaskTypeItem]: Task type information.
|
||||||
|
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
# Host information
|
# Host information
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_workfile_extensions(self):
|
def get_workfile_extensions(self):
|
||||||
|
|
|
||||||
|
|
@ -231,6 +231,16 @@ class BaseWorkfileController(
|
||||||
return self._projects_model.get_project_entity(
|
return self._projects_model.get_project_entity(
|
||||||
project_name)
|
project_name)
|
||||||
|
|
||||||
|
def get_folder_type_items(self, project_name, sender=None):
|
||||||
|
return self._projects_model.get_folder_type_items(
|
||||||
|
project_name, sender
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_task_type_items(self, project_name, sender=None):
|
||||||
|
return self._projects_model.get_task_type_items(
|
||||||
|
project_name, sender
|
||||||
|
)
|
||||||
|
|
||||||
def get_folder_entity(self, project_name, folder_id):
|
def get_folder_entity(self, project_name, folder_id):
|
||||||
return self._hierarchy_model.get_folder_entity(
|
return self._hierarchy_model.get_folder_entity(
|
||||||
project_name, folder_id)
|
project_name, folder_id)
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ from ayon_core.tools.utils.delegates import PrettyTimeDelegate
|
||||||
|
|
||||||
from .utils import BaseOverlayFrame
|
from .utils import BaseOverlayFrame
|
||||||
|
|
||||||
|
|
||||||
REPRE_ID_ROLE = QtCore.Qt.UserRole + 1
|
REPRE_ID_ROLE = QtCore.Qt.UserRole + 1
|
||||||
FILEPATH_ROLE = QtCore.Qt.UserRole + 2
|
FILEPATH_ROLE = QtCore.Qt.UserRole + 2
|
||||||
AUTHOR_ROLE = QtCore.Qt.UserRole + 3
|
AUTHOR_ROLE = QtCore.Qt.UserRole + 3
|
||||||
|
|
@ -249,7 +248,7 @@ class PublishedFilesModel(QtGui.QStandardItemModel):
|
||||||
|
|
||||||
# Handle roles for first column
|
# Handle roles for first column
|
||||||
col = index.column()
|
col = index.column()
|
||||||
if col != 1:
|
if col == 0:
|
||||||
return super().data(index, role)
|
return super().data(index, role)
|
||||||
|
|
||||||
if role == QtCore.Qt.DecorationRole:
|
if role == QtCore.Qt.DecorationRole:
|
||||||
|
|
|
||||||
18
client/ayon_core/vendor/python/qtmaterialsymbols/__init__.py
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
from .version import __version__
|
||||||
|
from .utils import get_icon_name_char
|
||||||
|
from .iconic_font import (
|
||||||
|
IconicFont,
|
||||||
|
get_instance,
|
||||||
|
get_icon,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
"__version__",
|
||||||
|
|
||||||
|
"get_icon_name_char",
|
||||||
|
|
||||||
|
"IconicFont",
|
||||||
|
"get_instance",
|
||||||
|
"get_icon",
|
||||||
|
)
|
||||||
209
client/ayon_core/vendor/python/qtmaterialsymbols/browser.py
vendored
Normal file
|
|
@ -0,0 +1,209 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from qtpy import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
|
from .iconic_font import get_instance
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Set icon colour and copy code with color kwarg
|
||||||
|
|
||||||
|
VIEW_COLUMNS = 5
|
||||||
|
AUTO_SEARCH_TIMEOUT = 500
|
||||||
|
ALL_COLLECTIONS = 'All'
|
||||||
|
|
||||||
|
|
||||||
|
class IconBrowser(QtWidgets.QMainWindow):
|
||||||
|
"""
|
||||||
|
A small browser window that allows the user to search through all icons from
|
||||||
|
the available version of QtAwesome. You can also copy the name and python
|
||||||
|
code for the currently selected icon.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(IconBrowser, self).__init__()
|
||||||
|
self.setMinimumSize(400, 300)
|
||||||
|
self.setWindowTitle("Material Icon Browser")
|
||||||
|
|
||||||
|
instance = get_instance()
|
||||||
|
fontMaps = instance.get_charmap()
|
||||||
|
|
||||||
|
iconNames = list(fontMaps)
|
||||||
|
|
||||||
|
self._filterTimer = QtCore.QTimer(self)
|
||||||
|
self._filterTimer.setSingleShot(True)
|
||||||
|
self._filterTimer.setInterval(AUTO_SEARCH_TIMEOUT)
|
||||||
|
self._filterTimer.timeout.connect(self._updateFilter)
|
||||||
|
|
||||||
|
model = IconModel(self.palette().color(QtGui.QPalette.Text))
|
||||||
|
model.setStringList(sorted(iconNames))
|
||||||
|
|
||||||
|
self._proxyModel = QtCore.QSortFilterProxyModel()
|
||||||
|
self._proxyModel.setSourceModel(model)
|
||||||
|
self._proxyModel.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
|
||||||
|
|
||||||
|
self._listView = IconListView(self)
|
||||||
|
self._listView.setUniformItemSizes(True)
|
||||||
|
self._listView.setViewMode(QtWidgets.QListView.IconMode)
|
||||||
|
self._listView.setModel(self._proxyModel)
|
||||||
|
self._listView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||||
|
self._listView.doubleClicked.connect(self._copyIconText)
|
||||||
|
|
||||||
|
self._lineEdit = QtWidgets.QLineEdit(self)
|
||||||
|
self._lineEdit.setAlignment(QtCore.Qt.AlignCenter)
|
||||||
|
self._lineEdit.textChanged.connect(self._triggerDelayedUpdate)
|
||||||
|
self._lineEdit.returnPressed.connect(self._triggerImmediateUpdate)
|
||||||
|
|
||||||
|
self._comboBox = QtWidgets.QComboBox(self)
|
||||||
|
self._comboBox.setMinimumWidth(75)
|
||||||
|
self._comboBox.currentIndexChanged.connect(self._triggerImmediateUpdate)
|
||||||
|
self._comboBox.addItems([ALL_COLLECTIONS] + sorted(fontMaps.keys()))
|
||||||
|
|
||||||
|
lyt = QtWidgets.QHBoxLayout()
|
||||||
|
lyt.setContentsMargins(0, 0, 0, 0)
|
||||||
|
lyt.addWidget(self._comboBox)
|
||||||
|
lyt.addWidget(self._lineEdit)
|
||||||
|
|
||||||
|
searchBarFrame = QtWidgets.QFrame(self)
|
||||||
|
searchBarFrame.setLayout(lyt)
|
||||||
|
|
||||||
|
self._copyButton = QtWidgets.QPushButton('Copy Name', self)
|
||||||
|
self._copyButton.clicked.connect(self._copyIconText)
|
||||||
|
|
||||||
|
lyt = QtWidgets.QVBoxLayout()
|
||||||
|
lyt.addWidget(searchBarFrame)
|
||||||
|
lyt.addWidget(self._listView)
|
||||||
|
lyt.addWidget(self._copyButton)
|
||||||
|
|
||||||
|
frame = QtWidgets.QFrame(self)
|
||||||
|
frame.setLayout(lyt)
|
||||||
|
|
||||||
|
self.setCentralWidget(frame)
|
||||||
|
|
||||||
|
QtWidgets.QShortcut(
|
||||||
|
QtGui.QKeySequence(QtCore.Qt.Key_Return),
|
||||||
|
self,
|
||||||
|
self._copyIconText,
|
||||||
|
)
|
||||||
|
|
||||||
|
self._lineEdit.setFocus()
|
||||||
|
|
||||||
|
# geo = self.geometry()
|
||||||
|
# desktop = QtWidgets.QApplication.desktop()
|
||||||
|
# screen = desktop.screenNumber(desktop.cursor().pos())
|
||||||
|
# centerPoint = desktop.screenGeometry(screen).center()
|
||||||
|
# geo.moveCenter(centerPoint)
|
||||||
|
# self.setGeometry(geo)
|
||||||
|
|
||||||
|
def _updateFilter(self):
|
||||||
|
"""
|
||||||
|
Update the string used for filtering in the proxy model with the
|
||||||
|
current text from the line edit.
|
||||||
|
"""
|
||||||
|
reString = ""
|
||||||
|
|
||||||
|
group = self._comboBox.currentText()
|
||||||
|
if group != ALL_COLLECTIONS:
|
||||||
|
reString += r"^%s\." % group
|
||||||
|
|
||||||
|
searchTerm = self._lineEdit.text()
|
||||||
|
if searchTerm:
|
||||||
|
reString += ".*%s.*$" % searchTerm
|
||||||
|
|
||||||
|
self._proxyModel.setFilterRegularExpression(reString)
|
||||||
|
|
||||||
|
def _triggerDelayedUpdate(self):
|
||||||
|
"""
|
||||||
|
Reset the timer used for committing the search term to the proxy model.
|
||||||
|
"""
|
||||||
|
self._filterTimer.stop()
|
||||||
|
self._filterTimer.start()
|
||||||
|
|
||||||
|
def _triggerImmediateUpdate(self):
|
||||||
|
"""
|
||||||
|
Stop the timer used for committing the search term and update the
|
||||||
|
proxy model immediately.
|
||||||
|
"""
|
||||||
|
self._filterTimer.stop()
|
||||||
|
self._updateFilter()
|
||||||
|
|
||||||
|
def _copyIconText(self):
|
||||||
|
"""
|
||||||
|
Copy the name of the currently selected icon to the clipboard.
|
||||||
|
"""
|
||||||
|
indexes = self._listView.selectedIndexes()
|
||||||
|
if not indexes:
|
||||||
|
return
|
||||||
|
|
||||||
|
clipboard = QtWidgets.QApplication.instance().clipboard()
|
||||||
|
clipboard.setText(indexes[0].data())
|
||||||
|
|
||||||
|
|
||||||
|
class IconListView(QtWidgets.QListView):
|
||||||
|
"""
|
||||||
|
A QListView that scales it's grid size to ensure the same number of
|
||||||
|
columns are always drawn.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super(IconListView, self).__init__(parent)
|
||||||
|
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
|
||||||
|
|
||||||
|
def resizeEvent(self, event):
|
||||||
|
"""
|
||||||
|
Re-implemented to re-calculate the grid size to provide scaling icons
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
event : QtCore.QEvent
|
||||||
|
"""
|
||||||
|
width = self.viewport().width() - 30
|
||||||
|
# The minus 30 above ensures we don't end up with an item width that
|
||||||
|
# can't be drawn the expected number of times across the view without
|
||||||
|
# being wrapped. Without this, the view can flicker during resize
|
||||||
|
tileWidth = width / VIEW_COLUMNS
|
||||||
|
iconWidth = int(tileWidth * 0.8)
|
||||||
|
|
||||||
|
self.setGridSize(QtCore.QSize(tileWidth, tileWidth))
|
||||||
|
self.setIconSize(QtCore.QSize(iconWidth, iconWidth))
|
||||||
|
|
||||||
|
return super(IconListView, self).resizeEvent(event)
|
||||||
|
|
||||||
|
|
||||||
|
class IconModel(QtCore.QStringListModel):
|
||||||
|
def __init__(self, iconColor):
|
||||||
|
super(IconModel, self).__init__()
|
||||||
|
self._iconColor = iconColor
|
||||||
|
|
||||||
|
def flags(self, index):
|
||||||
|
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
|
||||||
|
|
||||||
|
def data(self, index, role):
|
||||||
|
"""
|
||||||
|
Re-implemented to return the icon for the current index.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
index : QtCore.QModelIndex
|
||||||
|
role : int
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Any
|
||||||
|
"""
|
||||||
|
if role == QtCore.Qt.DecorationRole:
|
||||||
|
iconString = self.data(index, role=QtCore.Qt.DisplayRole)
|
||||||
|
instance = get_instance()
|
||||||
|
return instance.get_icon(iconString, color=self._iconColor)
|
||||||
|
return super(IconModel, self).data(index, role)
|
||||||
|
|
||||||
|
|
||||||
|
def run():
|
||||||
|
"""
|
||||||
|
Start the IconBrowser and block until the process exits.
|
||||||
|
"""
|
||||||
|
app = QtWidgets.QApplication([])
|
||||||
|
|
||||||
|
browser = IconBrowser()
|
||||||
|
browser.show()
|
||||||
|
|
||||||
|
sys.exit(app.exec_())
|
||||||
8
client/ayon_core/vendor/python/qtmaterialsymbols/exceptions.py
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
class ApplicationNotRunning(Exception):
|
||||||
|
"""Raised when the QApplication is not running."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class FontError(Exception):
|
||||||
|
"""Raised when there is an issue with font."""
|
||||||
|
pass
|
||||||
254
client/ayon_core/vendor/python/qtmaterialsymbols/iconic_font.py
vendored
Normal file
|
|
@ -0,0 +1,254 @@
|
||||||
|
r"""
|
||||||
|
|
||||||
|
Iconic Font
|
||||||
|
===========
|
||||||
|
|
||||||
|
A lightweight module handling iconic fonts.
|
||||||
|
|
||||||
|
It is designed to provide a simple way for creating QIcons from glyphs.
|
||||||
|
|
||||||
|
From a user's viewpoint, the main entry point is the ``IconicFont`` class which
|
||||||
|
contains methods for loading new iconic fonts with their character map and
|
||||||
|
methods returning instances of ``QIcon``.
|
||||||
|
|
||||||
|
"""
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
from typing import Dict, Optional, Union
|
||||||
|
|
||||||
|
from qtpy import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
|
from .structures import IconOptions, Position
|
||||||
|
from .utils import get_char_mapping, get_icon_name_char, _get_font_name
|
||||||
|
|
||||||
|
|
||||||
|
class _Cache:
|
||||||
|
instance = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_instance():
|
||||||
|
if _Cache.instance is None:
|
||||||
|
_Cache.instance = IconicFont()
|
||||||
|
return _Cache.instance
|
||||||
|
|
||||||
|
|
||||||
|
def get_icon(*args, **kwargs):
|
||||||
|
return get_instance().get_icon(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class CharIconPainter:
|
||||||
|
"""Char icon painter."""
|
||||||
|
def paint(self, iconic, painter, rect, mode, state, options):
|
||||||
|
"""Main paint method."""
|
||||||
|
self._paint_icon(iconic, painter, rect, mode, state, options)
|
||||||
|
|
||||||
|
def _paint_icon(self, iconic, painter, rect, mode, state, options):
|
||||||
|
"""Paint a single icon."""
|
||||||
|
painter.save()
|
||||||
|
|
||||||
|
color = options.get_color_for_state(state, mode)
|
||||||
|
char = options.get_char_for_state(state, mode)
|
||||||
|
|
||||||
|
painter.setPen(QtGui.QColor(color))
|
||||||
|
|
||||||
|
# A 16 pixel-high icon yields a font size of 14, which is pixel perfect
|
||||||
|
# for font-awesome. 16 * 0.875 = 14
|
||||||
|
# The reason why the glyph size is smaller than the icon size is to
|
||||||
|
# account for font bearing.
|
||||||
|
# draw_size = round(0.875 * rect.height() * options.scale_factor)
|
||||||
|
|
||||||
|
draw_size = round(rect.height() * options.scale_factor)
|
||||||
|
|
||||||
|
painter.setFont(iconic.get_font(draw_size))
|
||||||
|
if options.offset is not None:
|
||||||
|
rect = QtCore.QRect(rect)
|
||||||
|
rect.translate(
|
||||||
|
round(options.offset.x * rect.width()),
|
||||||
|
round(options.offset.y * rect.height())
|
||||||
|
)
|
||||||
|
|
||||||
|
scale_x = -1 if options.hflip else 1
|
||||||
|
scale_y = -1 if options.vflip else 1
|
||||||
|
|
||||||
|
if options.vflip or options.hflip or options.rotate:
|
||||||
|
x_center = rect.width() * 0.5
|
||||||
|
y_center = rect.height() * 0.5
|
||||||
|
painter.translate(x_center, y_center)
|
||||||
|
|
||||||
|
transfrom = QtGui.QTransform()
|
||||||
|
transfrom.scale(scale_x, scale_y)
|
||||||
|
painter.setTransform(transfrom, True)
|
||||||
|
|
||||||
|
if options.rotate:
|
||||||
|
painter.rotate(options.rotate)
|
||||||
|
painter.translate(-x_center, -y_center)
|
||||||
|
|
||||||
|
painter.setOpacity(options.opacity)
|
||||||
|
|
||||||
|
painter.drawText(rect, QtCore.Qt.AlignCenter, char)
|
||||||
|
painter.restore()
|
||||||
|
|
||||||
|
|
||||||
|
class CharIconEngine(QtGui.QIconEngine):
|
||||||
|
"""Specialization of QIconEngine used to draw font-based icons."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
iconic: "IconicFont",
|
||||||
|
painter: QtGui.QPainter,
|
||||||
|
options: IconOptions
|
||||||
|
):
|
||||||
|
super(CharIconEngine, self).__init__()
|
||||||
|
self._iconic = iconic
|
||||||
|
self._painter = painter
|
||||||
|
self._options = options
|
||||||
|
|
||||||
|
def paint(self, painter, rect, mode, state):
|
||||||
|
self._painter.paint(
|
||||||
|
self._iconic,
|
||||||
|
painter,
|
||||||
|
rect,
|
||||||
|
mode,
|
||||||
|
state,
|
||||||
|
self._options
|
||||||
|
)
|
||||||
|
|
||||||
|
def pixmap(self, size, mode, state):
|
||||||
|
pm = QtGui.QPixmap(size)
|
||||||
|
pm.fill(QtCore.Qt.transparent)
|
||||||
|
painter = QtGui.QPainter(pm)
|
||||||
|
self.paint(
|
||||||
|
painter,
|
||||||
|
QtCore.QRect(QtCore.QPoint(0, 0), size),
|
||||||
|
mode,
|
||||||
|
state
|
||||||
|
)
|
||||||
|
return pm
|
||||||
|
|
||||||
|
|
||||||
|
class IconicFont(QtCore.QObject):
|
||||||
|
"""Main class for managing icons."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self._painter = CharIconPainter()
|
||||||
|
self._icon_cache = {}
|
||||||
|
|
||||||
|
def get_charmap(self) -> Dict[str, int]:
|
||||||
|
return get_char_mapping()
|
||||||
|
|
||||||
|
def get_font(self, size: int) -> QtGui.QFont:
|
||||||
|
"""Return a QFont corresponding to the given size."""
|
||||||
|
font = QtGui.QFont(_get_font_name())
|
||||||
|
font.setPixelSize(round(size))
|
||||||
|
return font
|
||||||
|
|
||||||
|
def get_icon_with_options(self, options: IconOptions) -> QtGui.QIcon:
|
||||||
|
"""Return a QIcon object corresponding to the provided icon name."""
|
||||||
|
if QtWidgets.QApplication.instance() is None:
|
||||||
|
warnings.warn(
|
||||||
|
"You need to have a running QApplication!"
|
||||||
|
)
|
||||||
|
return QtGui.QIcon()
|
||||||
|
|
||||||
|
cache_key = options.identifier
|
||||||
|
if cache_key in self._icon_cache:
|
||||||
|
return self._icon_cache[cache_key]
|
||||||
|
|
||||||
|
output = self._icon_by_painter(self._painter, options)
|
||||||
|
self._icon_cache[cache_key] = output
|
||||||
|
return output
|
||||||
|
|
||||||
|
def get_icon(
|
||||||
|
self,
|
||||||
|
icon_name: Optional[str] = None,
|
||||||
|
color: Optional[Union[QtGui.QColor, str]] = None,
|
||||||
|
opacity: Optional[float] = None,
|
||||||
|
scale_factor: Optional[float] = None,
|
||||||
|
offset: Optional[Position] = None,
|
||||||
|
hflip: Optional[bool] = False,
|
||||||
|
vflip: Optional[bool] = False,
|
||||||
|
rotate: Optional[int] = 0,
|
||||||
|
icon_name_normal: Optional[str] = None,
|
||||||
|
icon_name_active: Optional[str] = None,
|
||||||
|
icon_name_selected: Optional[str] = None,
|
||||||
|
icon_name_disabled: Optional[str] = None,
|
||||||
|
icon_name_on: Optional[str] = None,
|
||||||
|
icon_name_off: Optional[str] = None,
|
||||||
|
icon_name_on_normal: Optional[str] = None,
|
||||||
|
icon_name_off_normal: Optional[str] = None,
|
||||||
|
icon_name_on_active: Optional[str] = None,
|
||||||
|
icon_name_off_active: Optional[str] = None,
|
||||||
|
icon_name_on_selected: Optional[str] = None,
|
||||||
|
icon_name_off_selected: Optional[str] = None,
|
||||||
|
icon_name_on_disabled: Optional[str] = None,
|
||||||
|
icon_name_off_disabled: Optional[str] = None,
|
||||||
|
color_normal: Optional[Union[QtGui.QColor, str]] = None,
|
||||||
|
color_active: Optional[Union[QtGui.QColor, str]] = None,
|
||||||
|
color_selected: Optional[Union[QtGui.QColor, str]] = None,
|
||||||
|
color_disabled: Optional[Union[QtGui.QColor, str]] = None,
|
||||||
|
color_on: Optional[Union[QtGui.QColor, str]] = None,
|
||||||
|
color_off: Optional[Union[QtGui.QColor, str]] = None,
|
||||||
|
color_on_normal: Optional[Union[QtGui.QColor, str]] = None,
|
||||||
|
color_off_normal: Optional[Union[QtGui.QColor, str]] = None,
|
||||||
|
color_on_active: Optional[Union[QtGui.QColor, str]] = None,
|
||||||
|
color_off_active: Optional[Union[QtGui.QColor, str]] = None,
|
||||||
|
color_on_selected: Optional[Union[QtGui.QColor, str]] = None,
|
||||||
|
color_off_selected: Optional[Union[QtGui.QColor, str]] = None,
|
||||||
|
color_on_disabled: Optional[Union[QtGui.QColor, str]] = None,
|
||||||
|
color_off_disabled: Optional[Union[QtGui.QColor, str]] = None,
|
||||||
|
) -> QtGui.QIcon:
|
||||||
|
"""Return a QIcon object corresponding to the provided icon name."""
|
||||||
|
if QtWidgets.QApplication.instance() is None:
|
||||||
|
warnings.warn(
|
||||||
|
"You need to have a running QApplication!"
|
||||||
|
)
|
||||||
|
return QtGui.QIcon()
|
||||||
|
|
||||||
|
options = IconOptions.from_data(
|
||||||
|
icon_name=icon_name,
|
||||||
|
color=color,
|
||||||
|
opacity=opacity,
|
||||||
|
scale_factor=scale_factor,
|
||||||
|
offset=offset,
|
||||||
|
hflip=hflip,
|
||||||
|
vflip=vflip,
|
||||||
|
rotate=rotate,
|
||||||
|
icon_name_normal=icon_name_normal,
|
||||||
|
icon_name_active=icon_name_active,
|
||||||
|
icon_name_selected=icon_name_selected,
|
||||||
|
icon_name_disabled=icon_name_disabled,
|
||||||
|
icon_name_on=icon_name_on,
|
||||||
|
icon_name_off=icon_name_off,
|
||||||
|
icon_name_on_normal=icon_name_on_normal,
|
||||||
|
icon_name_off_normal=icon_name_off_normal,
|
||||||
|
icon_name_on_active=icon_name_on_active,
|
||||||
|
icon_name_off_active=icon_name_off_active,
|
||||||
|
icon_name_on_selected=icon_name_on_selected,
|
||||||
|
icon_name_off_selected=icon_name_off_selected,
|
||||||
|
icon_name_on_disabled=icon_name_on_disabled,
|
||||||
|
icon_name_off_disabled=icon_name_off_disabled,
|
||||||
|
color_normal=color_normal,
|
||||||
|
color_active=color_active,
|
||||||
|
color_selected=color_selected,
|
||||||
|
color_disabled=color_disabled,
|
||||||
|
color_on=color_on,
|
||||||
|
color_off=color_off,
|
||||||
|
color_on_normal=color_on_normal,
|
||||||
|
color_off_normal=color_off_normal,
|
||||||
|
color_on_active=color_on_active,
|
||||||
|
color_off_active=color_off_active,
|
||||||
|
color_on_selected=color_on_selected,
|
||||||
|
color_off_selected=color_off_selected,
|
||||||
|
color_on_disabled=color_on_disabled,
|
||||||
|
color_off_disabled=color_off_disabled,
|
||||||
|
)
|
||||||
|
cache_key = options.identifier
|
||||||
|
if cache_key in self._icon_cache:
|
||||||
|
return self._icon_cache[cache_key]
|
||||||
|
return self.get_icon_with_options(options)
|
||||||
|
|
||||||
|
def _icon_by_painter(self, painter, options):
|
||||||
|
"""Return the icon corresponding to the given painter."""
|
||||||
|
engine = CharIconEngine(self, painter, options)
|
||||||
|
return QtGui.QIcon(engine)
|
||||||
3599
client/ayon_core/vendor/python/qtmaterialsymbols/resources/MaterialSymbolsOutlined.codepoints
vendored
Normal file
3601
client/ayon_core/vendor/python/qtmaterialsymbols/resources/MaterialSymbolsOutlined.json
vendored
Normal file
BIN
client/ayon_core/vendor/python/qtmaterialsymbols/resources/MaterialSymbolsOutlined.ttf
vendored
Normal file
16
client/ayon_core/vendor/python/qtmaterialsymbols/resources/__init__.py
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import os
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
|
||||||
|
def get_font_filepath(
|
||||||
|
font_name: Optional[str] = "MaterialSymbolsOutlined"
|
||||||
|
) -> str:
|
||||||
|
return os.path.join(CURRENT_DIR, f"{font_name}.ttf")
|
||||||
|
|
||||||
|
|
||||||
|
def get_mapping_filepath(
|
||||||
|
font_name: Optional[str] = "MaterialSymbolsOutlined"
|
||||||
|
) -> str:
|
||||||
|
return os.path.join(CURRENT_DIR, f"{font_name}.json")
|
||||||
221
client/ayon_core/vendor/python/qtmaterialsymbols/structures.py
vendored
Normal file
|
|
@ -0,0 +1,221 @@
|
||||||
|
import collections
|
||||||
|
import json
|
||||||
|
from typing import Optional, Union
|
||||||
|
|
||||||
|
from qtpy import QtGui
|
||||||
|
|
||||||
|
from .utils import get_icon_name_char
|
||||||
|
|
||||||
|
Position = collections.namedtuple("Offset", ["x", "y"])
|
||||||
|
|
||||||
|
|
||||||
|
class IconSubOption:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
on_normal,
|
||||||
|
on_disabled=None,
|
||||||
|
on_active=None,
|
||||||
|
on_selected=None,
|
||||||
|
off_normal=None,
|
||||||
|
off_disabled=None,
|
||||||
|
off_active=None,
|
||||||
|
off_selected=None,
|
||||||
|
):
|
||||||
|
if off_normal is None:
|
||||||
|
off_normal = on_normal
|
||||||
|
|
||||||
|
if on_disabled is None:
|
||||||
|
on_disabled = on_normal
|
||||||
|
if off_disabled is None:
|
||||||
|
off_disabled = on_disabled
|
||||||
|
|
||||||
|
if on_active is None:
|
||||||
|
on_active = on_normal
|
||||||
|
if off_active is None:
|
||||||
|
off_active = on_active
|
||||||
|
|
||||||
|
if on_selected is None:
|
||||||
|
on_selected = on_normal
|
||||||
|
if off_selected is None:
|
||||||
|
off_selected = on_selected
|
||||||
|
|
||||||
|
self._identifier = None
|
||||||
|
self.on_normal = on_normal
|
||||||
|
self.on_disabled = on_disabled
|
||||||
|
self.on_active = on_active
|
||||||
|
self.on_selected = on_selected
|
||||||
|
self.off_normal = off_normal
|
||||||
|
self.off_disabled = off_disabled
|
||||||
|
self.off_active = off_active
|
||||||
|
self.off_selected = off_selected
|
||||||
|
|
||||||
|
@property
|
||||||
|
def identifier(self):
|
||||||
|
if self._identifier is None:
|
||||||
|
self._identifier = self._generate_identifier()
|
||||||
|
return self._identifier
|
||||||
|
|
||||||
|
def get_value_for_state(self, state, mode):
|
||||||
|
if state == QtGui.QIcon.On:
|
||||||
|
if mode == QtGui.QIcon.Disabled:
|
||||||
|
return self.on_disabled
|
||||||
|
if mode == QtGui.QIcon.Active:
|
||||||
|
return self.on_active
|
||||||
|
if mode == QtGui.QIcon.Selected:
|
||||||
|
return self.on_selected
|
||||||
|
return self.on_normal
|
||||||
|
|
||||||
|
if mode == QtGui.QIcon.Disabled:
|
||||||
|
return self.off_disabled
|
||||||
|
if mode == QtGui.QIcon.Active:
|
||||||
|
return self.off_active
|
||||||
|
if mode == QtGui.QIcon.Selected:
|
||||||
|
return self.off_selected
|
||||||
|
return self.off_normal
|
||||||
|
|
||||||
|
def _generate_identifier(self):
|
||||||
|
prev_value = None
|
||||||
|
values = []
|
||||||
|
for value in (
|
||||||
|
self.on_normal,
|
||||||
|
self.off_normal,
|
||||||
|
self.on_active,
|
||||||
|
self.off_active,
|
||||||
|
self.on_selected,
|
||||||
|
self.off_selected,
|
||||||
|
self.on_disabled,
|
||||||
|
self.off_disabled,
|
||||||
|
):
|
||||||
|
id_value = ""
|
||||||
|
if value != prev_value:
|
||||||
|
id_value = self._get_value_id(value)
|
||||||
|
values.append(id_value)
|
||||||
|
prev_value = value
|
||||||
|
|
||||||
|
return "|".join(values)
|
||||||
|
|
||||||
|
def _get_value_id(self, value):
|
||||||
|
if isinstance(value, QtGui.QColor):
|
||||||
|
return value.name()
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
|
||||||
|
def _prepare_mapping(option_name):
|
||||||
|
mapping = []
|
||||||
|
for store_key, alternative_keys in (
|
||||||
|
("on_normal", ["normal", "on", ""]),
|
||||||
|
("off_normal", ["off"]),
|
||||||
|
("on_active", ["active"]),
|
||||||
|
("off_active", []),
|
||||||
|
("on_selected", ["selected"]),
|
||||||
|
("off_selected", []),
|
||||||
|
("on_disabled", ["disabled"]),
|
||||||
|
("off_disabled", []),
|
||||||
|
):
|
||||||
|
mapping_keys = [f"{option_name}_{store_key}"]
|
||||||
|
for alt_key in alternative_keys:
|
||||||
|
key = option_name
|
||||||
|
if alt_key:
|
||||||
|
key = f"{option_name}_{alt_key}"
|
||||||
|
mapping_keys.append(key)
|
||||||
|
mapping.append((store_key, mapping_keys))
|
||||||
|
return mapping
|
||||||
|
|
||||||
|
|
||||||
|
class IconOptions:
|
||||||
|
mapping_color_keys = _prepare_mapping("color")
|
||||||
|
mapping_name_keys = _prepare_mapping("icon_name")
|
||||||
|
data_keys = {
|
||||||
|
"opacity",
|
||||||
|
"offset",
|
||||||
|
"scale_factor",
|
||||||
|
"hflip",
|
||||||
|
"vflip",
|
||||||
|
"rotate",
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
char_option: IconSubOption,
|
||||||
|
color_option: IconSubOption,
|
||||||
|
opacity: Optional[float] = None,
|
||||||
|
scale_factor: Optional[float] = None,
|
||||||
|
offset: Optional[Position] = None,
|
||||||
|
hflip: Optional[bool] = False,
|
||||||
|
vflip: Optional[bool] = False,
|
||||||
|
rotate: Optional[int] = 0,
|
||||||
|
):
|
||||||
|
if opacity is None:
|
||||||
|
opacity = 1.0
|
||||||
|
if scale_factor is None:
|
||||||
|
scale_factor = 1.0
|
||||||
|
|
||||||
|
self._identifier = None
|
||||||
|
self.char_option = char_option
|
||||||
|
self.color_option = color_option
|
||||||
|
self.opacity = opacity
|
||||||
|
self.scale_factor = scale_factor
|
||||||
|
self.offset = offset
|
||||||
|
self.hflip = hflip
|
||||||
|
self.vflip = vflip
|
||||||
|
self.rotate = rotate
|
||||||
|
|
||||||
|
@property
|
||||||
|
def identifier(self):
|
||||||
|
if self._identifier is None:
|
||||||
|
self._identifier = self._generate_identifier()
|
||||||
|
return self._identifier
|
||||||
|
|
||||||
|
def get_color_for_state(self, state, mode) -> QtGui.QColor:
|
||||||
|
return self.color_option.get_value_for_state(state, mode)
|
||||||
|
|
||||||
|
def get_char_for_state(self, state, mode) -> str:
|
||||||
|
return self.char_option.get_value_for_state(state, mode)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_data(cls, **kwargs):
|
||||||
|
new_kwargs = {
|
||||||
|
key: value
|
||||||
|
for key, value in kwargs.items()
|
||||||
|
if key in cls.data_keys and value is not None
|
||||||
|
}
|
||||||
|
color_kwargs = cls._prepare_mapping_values(
|
||||||
|
cls.mapping_color_keys, kwargs
|
||||||
|
)
|
||||||
|
name_kwargs = cls._prepare_mapping_values(
|
||||||
|
cls.mapping_name_keys, kwargs
|
||||||
|
)
|
||||||
|
char_kwargs = {
|
||||||
|
key: get_icon_name_char(value)
|
||||||
|
for key, value in name_kwargs.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
new_kwargs["color_option"] = IconSubOption(**color_kwargs)
|
||||||
|
new_kwargs["char_option"] = IconSubOption(**char_kwargs)
|
||||||
|
return cls(**new_kwargs)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _prepare_mapping_values(cls, mapping, kwargs):
|
||||||
|
mapping_values = {}
|
||||||
|
for store_key, mapping_keys in mapping:
|
||||||
|
for key in mapping_keys:
|
||||||
|
value = kwargs.pop(key, None)
|
||||||
|
if value is not None:
|
||||||
|
mapping_values[store_key] = value
|
||||||
|
break
|
||||||
|
return mapping_values
|
||||||
|
|
||||||
|
def _generate_identifier(self):
|
||||||
|
return (
|
||||||
|
str(value)
|
||||||
|
for value in (
|
||||||
|
self.char_option.identifier,
|
||||||
|
self.color_option.identifier,
|
||||||
|
self.opacity,
|
||||||
|
self.scale_factor,
|
||||||
|
self.offset,
|
||||||
|
self.hflip,
|
||||||
|
self.vflip,
|
||||||
|
self.rotate,
|
||||||
|
)
|
||||||
|
)
|
||||||
69
client/ayon_core/vendor/python/qtmaterialsymbols/utils.py
vendored
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
import json
|
||||||
|
import copy
|
||||||
|
from typing import Dict, Union
|
||||||
|
|
||||||
|
from qtpy import QtWidgets, QtGui, QtCore
|
||||||
|
|
||||||
|
from .exceptions import ApplicationNotRunning, FontError
|
||||||
|
from .resources import get_mapping_filepath, get_font_filepath
|
||||||
|
|
||||||
|
|
||||||
|
class _Cache:
|
||||||
|
mapping = None
|
||||||
|
font_id = None
|
||||||
|
font_name = None
|
||||||
|
|
||||||
|
|
||||||
|
def _load_font():
|
||||||
|
if QtWidgets.QApplication.instance() is None:
|
||||||
|
raise ApplicationNotRunning("No QApplication instance found.")
|
||||||
|
|
||||||
|
if _Cache.font_id is not None:
|
||||||
|
loaded_font_families = QtGui.QFontDatabase.applicationFontFamilies(
|
||||||
|
_Cache.font_id
|
||||||
|
)
|
||||||
|
if loaded_font_families:
|
||||||
|
return
|
||||||
|
|
||||||
|
filepath = get_font_filepath()
|
||||||
|
with open(filepath, "rb") as font_data:
|
||||||
|
font_id = QtGui.QFontDatabase.addApplicationFontFromData(
|
||||||
|
QtCore.QByteArray(font_data.read())
|
||||||
|
)
|
||||||
|
|
||||||
|
loaded_font_families = QtGui.QFontDatabase.applicationFontFamilies(
|
||||||
|
font_id
|
||||||
|
)
|
||||||
|
if not loaded_font_families:
|
||||||
|
raise FontError("Failed to load font.")
|
||||||
|
|
||||||
|
_Cache.font_id = font_id
|
||||||
|
_Cache.font_name = loaded_font_families[0]
|
||||||
|
|
||||||
|
|
||||||
|
def _load_mapping():
|
||||||
|
if _Cache.mapping is not None:
|
||||||
|
return
|
||||||
|
|
||||||
|
filepath = get_mapping_filepath()
|
||||||
|
with open(filepath, "r") as stream:
|
||||||
|
mapping = json.load(stream)
|
||||||
|
_Cache.mapping = {
|
||||||
|
key: chr(value)
|
||||||
|
for key, value in mapping.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _get_font_name():
|
||||||
|
_load_font()
|
||||||
|
return _Cache.font_name
|
||||||
|
|
||||||
|
|
||||||
|
def get_icon_name_char(icon_name: str) -> Union[int, None]:
|
||||||
|
_load_mapping()
|
||||||
|
return _Cache.mapping.get(icon_name, None)
|
||||||
|
|
||||||
|
|
||||||
|
def get_char_mapping() -> Dict[str, int]:
|
||||||
|
_load_mapping()
|
||||||
|
return copy.deepcopy(_Cache.mapping)
|
||||||
1
client/ayon_core/vendor/python/qtmaterialsymbols/version.py
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
@ -4,13 +4,28 @@ from ayon_server.settings.validators import ensure_unique_names
|
||||||
|
|
||||||
|
|
||||||
class ImageIOConfigModel(BaseSettingsModel):
|
class ImageIOConfigModel(BaseSettingsModel):
|
||||||
|
"""[DEPRECATED] Addon OCIO config settings. Please set the OCIO config
|
||||||
|
path in the Core addon profiles here
|
||||||
|
(ayon+settings://core/imageio/ocio_config_profiles).
|
||||||
|
"""
|
||||||
|
|
||||||
override_global_config: bool = SettingsField(
|
override_global_config: bool = SettingsField(
|
||||||
False,
|
False,
|
||||||
title="Override global OCIO config"
|
title="Override global OCIO config",
|
||||||
|
description=(
|
||||||
|
"DEPRECATED functionality. Please set the OCIO config path in the "
|
||||||
|
"Core addon profiles here (ayon+settings://core/imageio/"
|
||||||
|
"ocio_config_profiles)."
|
||||||
|
),
|
||||||
)
|
)
|
||||||
filepath: list[str] = SettingsField(
|
filepath: list[str] = SettingsField(
|
||||||
default_factory=list,
|
default_factory=list,
|
||||||
title="Config path"
|
title="Config path",
|
||||||
|
description=(
|
||||||
|
"DEPRECATED functionality. Please set the OCIO config path in the "
|
||||||
|
"Core addon profiles here (ayon+settings://core/imageio/"
|
||||||
|
"ocio_config_profiles)."
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
147
server_addon/applications/client/ayon_applications/action.py
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
import copy
|
||||||
|
|
||||||
|
import ayon_api
|
||||||
|
|
||||||
|
from ayon_core import resources
|
||||||
|
from ayon_core.lib import Logger, NestedCacheItem
|
||||||
|
from ayon_core.settings import get_studio_settings, get_project_settings
|
||||||
|
from ayon_core.pipeline.actions import LauncherAction
|
||||||
|
|
||||||
|
from .exceptions import (
|
||||||
|
ApplicationExecutableNotFound,
|
||||||
|
ApplicationLaunchFailed,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationAction(LauncherAction):
|
||||||
|
"""Action to launch an application.
|
||||||
|
|
||||||
|
Application action based on 'ApplicationManager' system.
|
||||||
|
|
||||||
|
Handling of applications in launcher is not ideal and should be completely
|
||||||
|
redone from scratch. This is just a temporary solution to keep backwards
|
||||||
|
compatibility with AYON launcher.
|
||||||
|
|
||||||
|
Todos:
|
||||||
|
Move handling of errors to frontend.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Application object
|
||||||
|
application = None
|
||||||
|
# Action attributes
|
||||||
|
name = None
|
||||||
|
label = None
|
||||||
|
label_variant = None
|
||||||
|
group = None
|
||||||
|
icon = None
|
||||||
|
color = None
|
||||||
|
order = 0
|
||||||
|
data = {}
|
||||||
|
project_settings = {}
|
||||||
|
project_entities = {}
|
||||||
|
|
||||||
|
_log = None
|
||||||
|
|
||||||
|
# --- For compatibility for combinations of new and old ayon-core ---
|
||||||
|
project_settings_cache = NestedCacheItem(
|
||||||
|
levels=1, default_factory=dict, lifetime=20
|
||||||
|
)
|
||||||
|
project_entities_cache = NestedCacheItem(
|
||||||
|
levels=1, default_factory=dict, lifetime=20
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _app_get_project_settings(cls, selection):
|
||||||
|
project_name = selection.project_name
|
||||||
|
if project_name in ApplicationAction.project_settings:
|
||||||
|
return ApplicationAction.project_settings[project_name]
|
||||||
|
|
||||||
|
if hasattr(selection, "get_project_settings"):
|
||||||
|
return selection.get_project_settings()
|
||||||
|
|
||||||
|
cache = ApplicationAction.project_settings_cache[project_name]
|
||||||
|
if not cache.is_valid:
|
||||||
|
if project_name:
|
||||||
|
settings = get_project_settings(project_name)
|
||||||
|
else:
|
||||||
|
settings = get_studio_settings()
|
||||||
|
cache.update_data(settings)
|
||||||
|
return copy.deepcopy(cache.get_data())
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _app_get_project_entity(cls, selection):
|
||||||
|
project_name = selection.project_name
|
||||||
|
if project_name in ApplicationAction.project_entities:
|
||||||
|
return ApplicationAction.project_entities[project_name]
|
||||||
|
|
||||||
|
if hasattr(selection, "get_project_settings"):
|
||||||
|
return selection.get_project_entity()
|
||||||
|
|
||||||
|
cache = ApplicationAction.project_entities_cache[project_name]
|
||||||
|
if not cache.is_valid:
|
||||||
|
project_entity = None
|
||||||
|
if project_name:
|
||||||
|
project_entity = ayon_api.get_project(project_name)
|
||||||
|
cache.update_data(project_entity)
|
||||||
|
return copy.deepcopy(cache.get_data())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def log(self):
|
||||||
|
if self._log is None:
|
||||||
|
self._log = Logger.get_logger(self.__class__.__name__)
|
||||||
|
return self._log
|
||||||
|
|
||||||
|
def is_compatible(self, selection):
|
||||||
|
if not selection.is_task_selected:
|
||||||
|
return False
|
||||||
|
|
||||||
|
project_entity = self._app_get_project_entity(selection)
|
||||||
|
apps = project_entity["attrib"].get("applications")
|
||||||
|
if not apps or self.application.full_name not in apps:
|
||||||
|
return False
|
||||||
|
|
||||||
|
project_settings = self._app_get_project_settings(selection)
|
||||||
|
only_available = project_settings["applications"]["only_available"]
|
||||||
|
if only_available and not self.application.find_executable():
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _show_message_box(self, title, message, details=None):
|
||||||
|
from qtpy import QtWidgets, QtGui
|
||||||
|
from ayon_core import style
|
||||||
|
|
||||||
|
dialog = QtWidgets.QMessageBox()
|
||||||
|
icon = QtGui.QIcon(resources.get_ayon_icon_filepath())
|
||||||
|
dialog.setWindowIcon(icon)
|
||||||
|
dialog.setStyleSheet(style.load_stylesheet())
|
||||||
|
dialog.setWindowTitle(title)
|
||||||
|
dialog.setText(message)
|
||||||
|
if details:
|
||||||
|
dialog.setDetailedText(details)
|
||||||
|
dialog.exec_()
|
||||||
|
|
||||||
|
def process(self, selection, **kwargs):
|
||||||
|
"""Process the full Application action"""
|
||||||
|
try:
|
||||||
|
self.application.launch(
|
||||||
|
project_name=selection.project_name,
|
||||||
|
folder_path=selection.folder_path,
|
||||||
|
task_name=selection.task_name,
|
||||||
|
**self.data
|
||||||
|
)
|
||||||
|
|
||||||
|
except ApplicationExecutableNotFound as exc:
|
||||||
|
details = exc.details
|
||||||
|
msg = exc.msg
|
||||||
|
log_msg = str(msg)
|
||||||
|
if details:
|
||||||
|
log_msg += "\n" + details
|
||||||
|
self.log.warning(log_msg)
|
||||||
|
self._show_message_box(
|
||||||
|
"Application executable not found", msg, details
|
||||||
|
)
|
||||||
|
|
||||||
|
except ApplicationLaunchFailed as exc:
|
||||||
|
msg = str(exc)
|
||||||
|
self.log.warning(msg, exc_info=True)
|
||||||
|
self._show_message_box("Application launch failed", msg)
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
import ayon_api
|
||||||
|
|
||||||
from ayon_core.addon import AYONAddon, IPluginPaths, click_wrap
|
from ayon_core.addon import AYONAddon, IPluginPaths, click_wrap
|
||||||
|
|
||||||
from .version import __version__
|
from .version import __version__
|
||||||
|
|
@ -112,6 +114,95 @@ class ApplicationsAddon(AYONAddon, IPluginPaths):
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def get_app_icon_path(self, icon_filename):
|
||||||
|
"""Get icon path.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
icon_filename (str): Icon filename.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Union[str, None]: Icon path or None if not found.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not icon_filename:
|
||||||
|
return None
|
||||||
|
icon_name = os.path.basename(icon_filename)
|
||||||
|
path = os.path.join(APPLICATIONS_ADDON_ROOT, "icons", icon_name)
|
||||||
|
if os.path.exists(path):
|
||||||
|
return path
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_app_icon_url(self, icon_filename, server=False):
|
||||||
|
"""Get icon path.
|
||||||
|
|
||||||
|
Method does not validate if icon filename exist on server.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
icon_filename (str): Icon name.
|
||||||
|
server (Optional[bool]): Return url to AYON server.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Union[str, None]: Icon path or None is server url is not
|
||||||
|
available.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not icon_filename:
|
||||||
|
return None
|
||||||
|
icon_name = os.path.basename(icon_filename)
|
||||||
|
if server:
|
||||||
|
base_url = ayon_api.get_base_url()
|
||||||
|
return (
|
||||||
|
f"{base_url}/addons/{self.name}/{self.version}"
|
||||||
|
f"/public/icons/{icon_name}"
|
||||||
|
)
|
||||||
|
server_url = os.getenv("AYON_WEBSERVER_URL")
|
||||||
|
if not server_url:
|
||||||
|
return None
|
||||||
|
return "/".join([
|
||||||
|
server_url, "addons", self.name, self.version, "icons", icon_name
|
||||||
|
])
|
||||||
|
|
||||||
|
def get_applications_action_classes(self):
|
||||||
|
"""Get application action classes for launcher tool.
|
||||||
|
|
||||||
|
This method should be used only by launcher tool. Please do not use it
|
||||||
|
in other places as its implementation is not optimal, and might
|
||||||
|
change or be removed.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[ApplicationAction]: List of application action classes.
|
||||||
|
|
||||||
|
"""
|
||||||
|
from .action import ApplicationAction
|
||||||
|
|
||||||
|
actions = []
|
||||||
|
|
||||||
|
manager = self.get_applications_manager()
|
||||||
|
for full_name, application in manager.applications.items():
|
||||||
|
if not application.enabled:
|
||||||
|
continue
|
||||||
|
|
||||||
|
icon = self.get_app_icon_path(application.icon)
|
||||||
|
|
||||||
|
action = type(
|
||||||
|
"app_{}".format(full_name),
|
||||||
|
(ApplicationAction,),
|
||||||
|
{
|
||||||
|
"identifier": "application.{}".format(full_name),
|
||||||
|
"application": application,
|
||||||
|
"name": application.name,
|
||||||
|
"label": application.group.label,
|
||||||
|
"label_variant": application.label,
|
||||||
|
"group": None,
|
||||||
|
"icon": icon,
|
||||||
|
"color": getattr(application, "color", None),
|
||||||
|
"order": getattr(application, "order", None) or 0,
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
actions.append(action)
|
||||||
|
return actions
|
||||||
|
|
||||||
def launch_application(
|
def launch_application(
|
||||||
self, app_name, project_name, folder_path, task_name
|
self, app_name, project_name, folder_path, task_name
|
||||||
):
|
):
|
||||||
|
|
@ -132,6 +223,18 @@ class ApplicationsAddon(AYONAddon, IPluginPaths):
|
||||||
task_name=task_name,
|
task_name=task_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def webserver_initialization(self, manager):
|
||||||
|
"""Initialize webserver.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
manager (WebServerManager): Webserver manager.
|
||||||
|
|
||||||
|
"""
|
||||||
|
static_prefix = f"/addons/{self.name}/{self.version}/icons"
|
||||||
|
manager.add_static(
|
||||||
|
static_prefix, os.path.join(APPLICATIONS_ADDON_ROOT, "icons")
|
||||||
|
)
|
||||||
|
|
||||||
# --- CLI ---
|
# --- CLI ---
|
||||||
def cli(self, addon_click_group):
|
def cli(self, addon_click_group):
|
||||||
main_group = click_wrap.group(
|
main_group = click_wrap.group(
|
||||||
|
|
|
||||||
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 257 KiB |
|
After Width: | Height: | Size: 120 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 247 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 131 KiB |
BIN
server_addon/applications/client/ayon_applications/icons/ue4.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 1 KiB |
|
After Width: | Height: | Size: 280 KiB |
|
|
@ -156,7 +156,7 @@ class ApplicationManager:
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
app_name (str): Name of application that should be launched.
|
app_name (str): Name of application that should be launched.
|
||||||
**data (dict): Any additional data. Data may be used during
|
**data (Any): Any additional data. Data may be used during
|
||||||
preparation to store objects usable in multiple places.
|
preparation to store objects usable in multiple places.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ from ayon_core import AYON_CORE_ROOT
|
||||||
from ayon_core.settings import get_project_settings
|
from ayon_core.settings import get_project_settings
|
||||||
from ayon_core.lib import Logger, get_ayon_username
|
from ayon_core.lib import Logger, get_ayon_username
|
||||||
from ayon_core.addon import AddonsManager
|
from ayon_core.addon import AddonsManager
|
||||||
from ayon_core.pipeline import HOST_WORKFILE_EXTENSIONS
|
|
||||||
from ayon_core.pipeline.template_data import get_template_data
|
from ayon_core.pipeline.template_data import get_template_data
|
||||||
from ayon_core.pipeline.workfile import (
|
from ayon_core.pipeline.workfile import (
|
||||||
get_workfile_template_key,
|
get_workfile_template_key,
|
||||||
|
|
@ -573,10 +572,9 @@ def _prepare_last_workfile(data, workdir, addons_manager):
|
||||||
last_workfile_path = data.get("last_workfile_path") or ""
|
last_workfile_path = data.get("last_workfile_path") or ""
|
||||||
if not last_workfile_path:
|
if not last_workfile_path:
|
||||||
host_addon = addons_manager.get_host_addon(app.host_name)
|
host_addon = addons_manager.get_host_addon(app.host_name)
|
||||||
|
extensions = None
|
||||||
if host_addon:
|
if host_addon:
|
||||||
extensions = host_addon.get_workfile_extensions()
|
extensions = host_addon.get_workfile_extensions()
|
||||||
else:
|
|
||||||
extensions = HOST_WORKFILE_EXTENSIONS.get(app.host_name)
|
|
||||||
|
|
||||||
if extensions:
|
if extensions:
|
||||||
anatomy = data["anatomy"]
|
anatomy = data["anatomy"]
|
||||||
|
|
|
||||||
BIN
server_addon/applications/public/icons/3de4.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
server_addon/applications/public/icons/3dsmax.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
server_addon/applications/public/icons/aftereffects.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
server_addon/applications/public/icons/blender.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
server_addon/applications/public/icons/celaction.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
server_addon/applications/public/icons/flame.png
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
server_addon/applications/public/icons/fusion.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
server_addon/applications/public/icons/harmony.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
server_addon/applications/public/icons/hiero.png
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
server_addon/applications/public/icons/houdini.png
Normal file
|
After Width: | Height: | Size: 257 KiB |
BIN
server_addon/applications/public/icons/maya.png
Normal file
|
After Width: | Height: | Size: 120 KiB |
BIN
server_addon/applications/public/icons/nuke.png
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
server_addon/applications/public/icons/nukestudio.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
server_addon/applications/public/icons/nukex.png
Normal file
|
After Width: | Height: | Size: 97 KiB |
BIN
server_addon/applications/public/icons/openrv.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
server_addon/applications/public/icons/photoshop.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
server_addon/applications/public/icons/premiere.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
server_addon/applications/public/icons/python.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
server_addon/applications/public/icons/resolve.png
Normal file
|
After Width: | Height: | Size: 247 KiB |
BIN
server_addon/applications/public/icons/shotgrid.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
server_addon/applications/public/icons/storyboardpro.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
server_addon/applications/public/icons/substancepainter.png
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
server_addon/applications/public/icons/tvpaint.png
Normal file
|
After Width: | Height: | Size: 131 KiB |
BIN
server_addon/applications/public/icons/ue4.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
server_addon/applications/public/icons/wrap.png
Normal file
|
After Width: | Height: | Size: 1 KiB |
BIN
server_addon/applications/public/icons/zbrush.png
Normal file
|
After Width: | Height: | Size: 280 KiB |
|
|
@ -4,13 +4,28 @@ from ayon_server.settings.validators import ensure_unique_names
|
||||||
|
|
||||||
|
|
||||||
class ImageIOConfigModel(BaseSettingsModel):
|
class ImageIOConfigModel(BaseSettingsModel):
|
||||||
|
"""[DEPRECATED] Addon OCIO config settings. Please set the OCIO config
|
||||||
|
path in the Core addon profiles here
|
||||||
|
(ayon+settings://core/imageio/ocio_config_profiles).
|
||||||
|
"""
|
||||||
|
|
||||||
override_global_config: bool = SettingsField(
|
override_global_config: bool = SettingsField(
|
||||||
False,
|
False,
|
||||||
title="Override global OCIO config"
|
title="Override global OCIO config",
|
||||||
|
description=(
|
||||||
|
"DEPRECATED functionality. Please set the OCIO config path in the "
|
||||||
|
"Core addon profiles here (ayon+settings://core/imageio/"
|
||||||
|
"ocio_config_profiles)."
|
||||||
|
),
|
||||||
)
|
)
|
||||||
filepath: list[str] = SettingsField(
|
filepath: list[str] = SettingsField(
|
||||||
default_factory=list,
|
default_factory=list,
|
||||||
title="Config path"
|
title="Config path",
|
||||||
|
description=(
|
||||||
|
"DEPRECATED functionality. Please set the OCIO config path in the "
|
||||||
|
"Core addon profiles here (ayon+settings://core/imageio/"
|
||||||
|
"ocio_config_profiles)."
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,28 @@ from ayon_server.settings.validators import ensure_unique_names
|
||||||
|
|
||||||
|
|
||||||
class ImageIOConfigModel(BaseSettingsModel):
|
class ImageIOConfigModel(BaseSettingsModel):
|
||||||
|
"""[DEPRECATED] Addon OCIO config settings. Please set the OCIO config
|
||||||
|
path in the Core addon profiles here
|
||||||
|
(ayon+settings://core/imageio/ocio_config_profiles).
|
||||||
|
"""
|
||||||
|
|
||||||
override_global_config: bool = SettingsField(
|
override_global_config: bool = SettingsField(
|
||||||
False,
|
False,
|
||||||
title="Override global OCIO config"
|
title="Override global OCIO config",
|
||||||
|
description=(
|
||||||
|
"DEPRECATED functionality. Please set the OCIO config path in the "
|
||||||
|
"Core addon profiles here (ayon+settings://core/imageio/"
|
||||||
|
"ocio_config_profiles)."
|
||||||
|
),
|
||||||
)
|
)
|
||||||
filepath: list[str] = SettingsField(
|
filepath: list[str] = SettingsField(
|
||||||
default_factory=list,
|
default_factory=list,
|
||||||
title="Config path"
|
title="Config path",
|
||||||
|
description=(
|
||||||
|
"DEPRECATED functionality. Please set the OCIO config path in the "
|
||||||
|
"Core addon profiles here (ayon+settings://core/imageio/"
|
||||||
|
"ocio_config_profiles)."
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,8 @@ def find_files_in_subdir(
|
||||||
List[Tuple[str, str]]: List of tuples with path to file and parent
|
List[Tuple[str, str]]: List of tuples with path to file and parent
|
||||||
directories relative to 'src_path'.
|
directories relative to 'src_path'.
|
||||||
"""
|
"""
|
||||||
|
if not os.path.exists(src_path):
|
||||||
|
return []
|
||||||
|
|
||||||
if ignore_file_patterns is None:
|
if ignore_file_patterns is None:
|
||||||
ignore_file_patterns = IGNORE_FILE_PATTERNS
|
ignore_file_patterns = IGNORE_FILE_PATTERNS
|
||||||
|
|
@ -210,6 +212,7 @@ def _get_server_mapping(
|
||||||
addon_dir: Path, addon_version: str
|
addon_dir: Path, addon_version: str
|
||||||
) -> List[Tuple[str, str]]:
|
) -> List[Tuple[str, str]]:
|
||||||
server_dir = addon_dir / "server"
|
server_dir = addon_dir / "server"
|
||||||
|
public_dir = addon_dir / "public"
|
||||||
src_package_py = addon_dir / "package.py"
|
src_package_py = addon_dir / "package.py"
|
||||||
pyproject_toml = addon_dir / "client" / "pyproject.toml"
|
pyproject_toml = addon_dir / "client" / "pyproject.toml"
|
||||||
|
|
||||||
|
|
@ -217,6 +220,10 @@ def _get_server_mapping(
|
||||||
(src_path, f"server/{sub_path}")
|
(src_path, f"server/{sub_path}")
|
||||||
for src_path, sub_path in find_files_in_subdir(str(server_dir))
|
for src_path, sub_path in find_files_in_subdir(str(server_dir))
|
||||||
]
|
]
|
||||||
|
mapping.extend([
|
||||||
|
(src_path, f"public/{sub_path}")
|
||||||
|
for src_path, sub_path in find_files_in_subdir(str(public_dir))
|
||||||
|
])
|
||||||
mapping.append((src_package_py.as_posix(), "package.py"))
|
mapping.append((src_package_py.as_posix(), "package.py"))
|
||||||
if pyproject_toml.exists():
|
if pyproject_toml.exists():
|
||||||
mapping.append((pyproject_toml.as_posix(), "private/pyproject.toml"))
|
mapping.append((pyproject_toml.as_posix(), "private/pyproject.toml"))
|
||||||
|
|
|
||||||
|
|
@ -40,13 +40,28 @@ class ImageIORemappingModel(BaseSettingsModel):
|
||||||
|
|
||||||
|
|
||||||
class ImageIOConfigModel(BaseSettingsModel):
|
class ImageIOConfigModel(BaseSettingsModel):
|
||||||
|
"""[DEPRECATED] Addon OCIO config settings. Please set the OCIO config
|
||||||
|
path in the Core addon profiles here
|
||||||
|
(ayon+settings://core/imageio/ocio_config_profiles).
|
||||||
|
"""
|
||||||
|
|
||||||
override_global_config: bool = SettingsField(
|
override_global_config: bool = SettingsField(
|
||||||
False,
|
False,
|
||||||
title="Override global OCIO config"
|
title="Override global OCIO config",
|
||||||
|
description=(
|
||||||
|
"DEPRECATED functionality. Please set the OCIO config path in the "
|
||||||
|
"Core addon profiles here (ayon+settings://core/imageio/"
|
||||||
|
"ocio_config_profiles)."
|
||||||
|
),
|
||||||
)
|
)
|
||||||
filepath: list[str] = SettingsField(
|
filepath: list[str] = SettingsField(
|
||||||
default_factory=list,
|
default_factory=list,
|
||||||
title="Config path"
|
title="Config path",
|
||||||
|
description=(
|
||||||
|
"DEPRECATED functionality. Please set the OCIO config path in the "
|
||||||
|
"Core addon profiles here (ayon+settings://core/imageio/"
|
||||||
|
"ocio_config_profiles)."
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,28 @@ from ayon_server.settings.validators import ensure_unique_names
|
||||||
|
|
||||||
|
|
||||||
class ImageIOConfigModel(BaseSettingsModel):
|
class ImageIOConfigModel(BaseSettingsModel):
|
||||||
|
"""[DEPRECATED] Addon OCIO config settings. Please set the OCIO config
|
||||||
|
path in the Core addon profiles here
|
||||||
|
(ayon+settings://core/imageio/ocio_config_profiles).
|
||||||
|
"""
|
||||||
|
|
||||||
override_global_config: bool = SettingsField(
|
override_global_config: bool = SettingsField(
|
||||||
False,
|
False,
|
||||||
title="Override global OCIO config"
|
title="Override global OCIO config",
|
||||||
|
description=(
|
||||||
|
"DEPRECATED functionality. Please set the OCIO config path in the "
|
||||||
|
"Core addon profiles here (ayon+settings://core/imageio/"
|
||||||
|
"ocio_config_profiles)."
|
||||||
|
),
|
||||||
)
|
)
|
||||||
filepath: list[str] = SettingsField(
|
filepath: list[str] = SettingsField(
|
||||||
default_factory=list,
|
default_factory=list,
|
||||||
title="Config path"
|
title="Config path",
|
||||||
|
description=(
|
||||||
|
"DEPRECATED functionality. Please set the OCIO config path in the "
|
||||||
|
"Core addon profiles here (ayon+settings://core/imageio/"
|
||||||
|
"ocio_config_profiles)."
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,28 @@ from ayon_server.settings.validators import ensure_unique_names
|
||||||
|
|
||||||
|
|
||||||
class ImageIOConfigModel(BaseSettingsModel):
|
class ImageIOConfigModel(BaseSettingsModel):
|
||||||
|
"""[DEPRECATED] Addon OCIO config settings. Please set the OCIO config
|
||||||
|
path in the Core addon profiles here
|
||||||
|
(ayon+settings://core/imageio/ocio_config_profiles).
|
||||||
|
"""
|
||||||
|
|
||||||
override_global_config: bool = SettingsField(
|
override_global_config: bool = SettingsField(
|
||||||
False,
|
False,
|
||||||
title="Override global OCIO config"
|
title="Override global OCIO config",
|
||||||
|
description=(
|
||||||
|
"DEPRECATED functionality. Please set the OCIO config path in the "
|
||||||
|
"Core addon profiles here (ayon+settings://core/imageio/"
|
||||||
|
"ocio_config_profiles)."
|
||||||
|
),
|
||||||
)
|
)
|
||||||
filepath: list[str] = SettingsField(
|
filepath: list[str] = SettingsField(
|
||||||
default_factory=list,
|
default_factory=list,
|
||||||
title="Config path"
|
title="Config path",
|
||||||
|
description=(
|
||||||
|
"DEPRECATED functionality. Please set the OCIO config path in the "
|
||||||
|
"Core addon profiles here (ayon+settings://core/imageio/"
|
||||||
|
"ocio_config_profiles)."
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||