mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 08:24:53 +01:00
Merge branch 'develop' into chore/AY-4916_Move-Houdini-client-code
This commit is contained in:
commit
c758df4c50
586 changed files with 4505 additions and 3161 deletions
|
|
@ -51,10 +51,17 @@ IGNORED_MODULES_IN_AYON = set()
|
||||||
# - this is used to log the missing addon
|
# - this is used to log the missing addon
|
||||||
MOVED_ADDON_MILESTONE_VERSIONS = {
|
MOVED_ADDON_MILESTONE_VERSIONS = {
|
||||||
"applications": VersionInfo(0, 2, 0),
|
"applications": VersionInfo(0, 2, 0),
|
||||||
|
"celaction": VersionInfo(0, 2, 0),
|
||||||
"clockify": VersionInfo(0, 2, 0),
|
"clockify": VersionInfo(0, 2, 0),
|
||||||
|
"flame": VersionInfo(0, 2, 0),
|
||||||
|
"max": VersionInfo(0, 2, 0),
|
||||||
|
"photoshop": VersionInfo(0, 2, 0),
|
||||||
"traypublisher": VersionInfo(0, 2, 0),
|
"traypublisher": VersionInfo(0, 2, 0),
|
||||||
"tvpaint": VersionInfo(0, 2, 0),
|
"tvpaint": VersionInfo(0, 2, 0),
|
||||||
|
"maya": VersionInfo(0, 2, 0),
|
||||||
"nuke": VersionInfo(0, 2, 0),
|
"nuke": VersionInfo(0, 2, 0),
|
||||||
|
"resolve": VersionInfo(0, 2, 0),
|
||||||
|
"substancepainter": VersionInfo(0, 2, 0),
|
||||||
"houdini": VersionInfo(0, 3, 0),
|
"houdini": VersionInfo(0, 3, 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
from .addon import (
|
|
||||||
HOST_DIR,
|
|
||||||
FlameAddon,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
|
||||||
"HOST_DIR",
|
|
||||||
"FlameAddon",
|
|
||||||
)
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
from .addon import ResolveAddon
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
|
||||||
"ResolveAddon",
|
|
||||||
)
|
|
||||||
|
|
@ -15,11 +15,11 @@ from ayon_core.pipeline.publish.lib import (
|
||||||
replace_with_published_scene_path
|
replace_with_published_scene_path
|
||||||
)
|
)
|
||||||
from ayon_core.pipeline.publish import KnownPublishError
|
from ayon_core.pipeline.publish import KnownPublishError
|
||||||
from ayon_core.hosts.max.api.lib import (
|
from ayon_max.api.lib import (
|
||||||
get_current_renderer,
|
get_current_renderer,
|
||||||
get_multipass_setting
|
get_multipass_setting
|
||||||
)
|
)
|
||||||
from ayon_core.hosts.max.api.lib_rendersettings import RenderSettings
|
from ayon_max.api.lib_rendersettings import RenderSettings
|
||||||
from openpype_modules.deadline import abstract_submit_deadline
|
from openpype_modules.deadline import abstract_submit_deadline
|
||||||
from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo
|
from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo
|
||||||
|
|
||||||
|
|
@ -205,11 +205,11 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline,
|
||||||
|
|
||||||
def _use_published_name(self, data, project_settings):
|
def _use_published_name(self, data, project_settings):
|
||||||
# Not all hosts can import these modules.
|
# Not all hosts can import these modules.
|
||||||
from ayon_core.hosts.max.api.lib import (
|
from ayon_max.api.lib import (
|
||||||
get_current_renderer,
|
get_current_renderer,
|
||||||
get_multipass_setting
|
get_multipass_setting
|
||||||
)
|
)
|
||||||
from ayon_core.hosts.max.api.lib_rendersettings import RenderSettings
|
from ayon_max.api.lib_rendersettings import RenderSettings
|
||||||
|
|
||||||
instance = self._instance
|
instance = self._instance
|
||||||
job_info = copy.deepcopy(self.job_info)
|
job_info = copy.deepcopy(self.job_info)
|
||||||
|
|
|
||||||
|
|
@ -39,8 +39,8 @@ from ayon_core.lib import (
|
||||||
EnumDef,
|
EnumDef,
|
||||||
is_in_tests,
|
is_in_tests,
|
||||||
)
|
)
|
||||||
from ayon_core.hosts.maya.api.lib_rendersettings import RenderSettings
|
from ayon_maya.api.lib_rendersettings import RenderSettings
|
||||||
from ayon_core.hosts.maya.api.lib import get_attr_in_layer
|
from ayon_maya.api.lib import get_attr_in_layer
|
||||||
|
|
||||||
from openpype_modules.deadline import abstract_submit_deadline
|
from openpype_modules.deadline import abstract_submit_deadline
|
||||||
from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo
|
from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
__version__ = "0.1.10"
|
__version__ = "0.1.12"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
import pyblish.api
|
import pyblish.api
|
||||||
|
|
||||||
|
from ayon_core.lib import filter_profiles
|
||||||
from ayon_core.pipeline.publish import (
|
from ayon_core.pipeline.publish import (
|
||||||
PublishValidationError, OptionalPyblishPluginMixin
|
PublishValidationError,
|
||||||
|
OptionalPyblishPluginMixin
|
||||||
)
|
)
|
||||||
|
from ayon_core.pipeline import get_current_host_name
|
||||||
|
|
||||||
|
|
||||||
class ValidateVersion(pyblish.api.InstancePlugin, OptionalPyblishPluginMixin):
|
class ValidateVersion(pyblish.api.InstancePlugin, OptionalPyblishPluginMixin):
|
||||||
|
|
@ -13,12 +17,35 @@ class ValidateVersion(pyblish.api.InstancePlugin, OptionalPyblishPluginMixin):
|
||||||
order = pyblish.api.ValidatorOrder
|
order = pyblish.api.ValidatorOrder
|
||||||
|
|
||||||
label = "Validate Version"
|
label = "Validate Version"
|
||||||
hosts = ["nuke", "maya", "houdini", "blender",
|
|
||||||
"photoshop", "aftereffects"]
|
|
||||||
|
|
||||||
optional = False
|
optional = False
|
||||||
active = True
|
active = True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def apply_settings(cls, settings):
|
||||||
|
# Disable if no profile is found for the current host
|
||||||
|
profiles = (
|
||||||
|
settings
|
||||||
|
["core"]
|
||||||
|
["publish"]
|
||||||
|
["ValidateVersion"]
|
||||||
|
["plugin_state_profiles"]
|
||||||
|
)
|
||||||
|
profile = filter_profiles(
|
||||||
|
profiles, {"host_names": get_current_host_name()}
|
||||||
|
)
|
||||||
|
if not profile:
|
||||||
|
cls.enabled = False
|
||||||
|
return
|
||||||
|
|
||||||
|
# Apply settings from profile
|
||||||
|
for attr_name in {
|
||||||
|
"enabled",
|
||||||
|
"optional",
|
||||||
|
"active",
|
||||||
|
}:
|
||||||
|
setattr(cls, attr_name, profile[attr_name])
|
||||||
|
|
||||||
def process(self, instance):
|
def process(self, instance):
|
||||||
if not self.is_active(instance.data):
|
if not self.is_active(instance.data):
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ from .hierarchy import (
|
||||||
)
|
)
|
||||||
from .thumbnails import ThumbnailsModel
|
from .thumbnails import ThumbnailsModel
|
||||||
from .selection import HierarchyExpectedSelection
|
from .selection import HierarchyExpectedSelection
|
||||||
|
from .users import UsersModel
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
|
|
@ -32,4 +33,6 @@ __all__ = (
|
||||||
"ThumbnailsModel",
|
"ThumbnailsModel",
|
||||||
|
|
||||||
"HierarchyExpectedSelection",
|
"HierarchyExpectedSelection",
|
||||||
|
|
||||||
|
"UsersModel",
|
||||||
)
|
)
|
||||||
|
|
|
||||||
84
client/ayon_core/tools/common_models/users.py
Normal file
84
client/ayon_core/tools/common_models/users.py
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
import ayon_api
|
||||||
|
|
||||||
|
from ayon_core.lib import CacheItem
|
||||||
|
|
||||||
|
|
||||||
|
class UserItem:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
username,
|
||||||
|
full_name,
|
||||||
|
email,
|
||||||
|
avatar_url,
|
||||||
|
active,
|
||||||
|
):
|
||||||
|
self.username = username
|
||||||
|
self.full_name = full_name
|
||||||
|
self.email = email
|
||||||
|
self.avatar_url = avatar_url
|
||||||
|
self.active = active
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_entity_data(cls, user_data):
|
||||||
|
return cls(
|
||||||
|
user_data["name"],
|
||||||
|
user_data["attrib"]["fullName"],
|
||||||
|
user_data["attrib"]["email"],
|
||||||
|
user_data["attrib"]["avatarUrl"],
|
||||||
|
user_data["active"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class UsersModel:
|
||||||
|
def __init__(self, controller):
|
||||||
|
self._controller = controller
|
||||||
|
self._users_cache = CacheItem(default_factory=list)
|
||||||
|
|
||||||
|
def get_user_items(self):
|
||||||
|
"""Get user items.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[UserItem]: List of user items.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self._invalidate_cache()
|
||||||
|
return self._users_cache.get_data()
|
||||||
|
|
||||||
|
def get_user_items_by_name(self):
|
||||||
|
"""Get user items by name.
|
||||||
|
|
||||||
|
Implemented as most of cases using this model will need to find
|
||||||
|
user information by username.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict[str, UserItem]: Dictionary of user items by name.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
user_item.username: user_item
|
||||||
|
for user_item in self.get_user_items()
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_user_item_by_username(self, username):
|
||||||
|
"""Get user item by username.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
username (str): Username.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Union[UserItem, None]: User item or None if not found.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self._invalidate_cache()
|
||||||
|
for user_item in self.get_user_items():
|
||||||
|
if user_item.username == username:
|
||||||
|
return user_item
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _invalidate_cache(self):
|
||||||
|
if self._users_cache.is_valid:
|
||||||
|
return
|
||||||
|
self._users_cache.update_data([
|
||||||
|
UserItem.from_entity_data(user)
|
||||||
|
for user in ayon_api.get_users()
|
||||||
|
])
|
||||||
|
|
@ -6,9 +6,6 @@ from ayon_core.tools.utils.lib import format_version
|
||||||
from .products_model import (
|
from .products_model import (
|
||||||
PRODUCT_ID_ROLE,
|
PRODUCT_ID_ROLE,
|
||||||
VERSION_NAME_EDIT_ROLE,
|
VERSION_NAME_EDIT_ROLE,
|
||||||
VERSION_STATUS_NAME_ROLE,
|
|
||||||
VERSION_STATUS_SHORT_ROLE,
|
|
||||||
VERSION_STATUS_COLOR_ROLE,
|
|
||||||
VERSION_ID_ROLE,
|
VERSION_ID_ROLE,
|
||||||
PRODUCT_IN_SCENE_ROLE,
|
PRODUCT_IN_SCENE_ROLE,
|
||||||
ACTIVE_SITE_ICON_ROLE,
|
ACTIVE_SITE_ICON_ROLE,
|
||||||
|
|
@ -205,57 +202,6 @@ class LoadedInSceneDelegate(QtWidgets.QStyledItemDelegate):
|
||||||
option.palette.setBrush(QtGui.QPalette.Text, color)
|
option.palette.setBrush(QtGui.QPalette.Text, color)
|
||||||
|
|
||||||
|
|
||||||
class StatusDelegate(QtWidgets.QStyledItemDelegate):
|
|
||||||
"""Delegate showing status name and short name."""
|
|
||||||
|
|
||||||
def paint(self, painter, option, index):
|
|
||||||
if option.widget:
|
|
||||||
style = option.widget.style()
|
|
||||||
else:
|
|
||||||
style = QtWidgets.QApplication.style()
|
|
||||||
|
|
||||||
style.drawControl(
|
|
||||||
QtWidgets.QCommonStyle.CE_ItemViewItem,
|
|
||||||
option,
|
|
||||||
painter,
|
|
||||||
option.widget
|
|
||||||
)
|
|
||||||
|
|
||||||
painter.save()
|
|
||||||
|
|
||||||
text_rect = style.subElementRect(
|
|
||||||
QtWidgets.QCommonStyle.SE_ItemViewItemText,
|
|
||||||
option
|
|
||||||
)
|
|
||||||
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)
|
|
||||||
text = index.data(VERSION_STATUS_NAME_ROLE)
|
|
||||||
if padded_text_rect.width() < fm.width(text):
|
|
||||||
text = index.data(VERSION_STATUS_SHORT_ROLE)
|
|
||||||
|
|
||||||
status_color = index.data(VERSION_STATUS_COLOR_ROLE)
|
|
||||||
fg_color = QtGui.QColor(status_color)
|
|
||||||
pen = painter.pen()
|
|
||||||
pen.setColor(fg_color)
|
|
||||||
painter.setPen(pen)
|
|
||||||
|
|
||||||
painter.drawText(
|
|
||||||
padded_text_rect,
|
|
||||||
option.displayAlignment,
|
|
||||||
text
|
|
||||||
)
|
|
||||||
|
|
||||||
painter.restore()
|
|
||||||
|
|
||||||
|
|
||||||
class SiteSyncDelegate(QtWidgets.QStyledItemDelegate):
|
class SiteSyncDelegate(QtWidgets.QStyledItemDelegate):
|
||||||
"""Paints icons and downloaded representation ration for both sites."""
|
"""Paints icons and downloaded representation ration for both sites."""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,18 +25,19 @@ VERSION_PUBLISH_TIME_ROLE = QtCore.Qt.UserRole + 14
|
||||||
VERSION_STATUS_NAME_ROLE = QtCore.Qt.UserRole + 15
|
VERSION_STATUS_NAME_ROLE = QtCore.Qt.UserRole + 15
|
||||||
VERSION_STATUS_SHORT_ROLE = QtCore.Qt.UserRole + 16
|
VERSION_STATUS_SHORT_ROLE = QtCore.Qt.UserRole + 16
|
||||||
VERSION_STATUS_COLOR_ROLE = QtCore.Qt.UserRole + 17
|
VERSION_STATUS_COLOR_ROLE = QtCore.Qt.UserRole + 17
|
||||||
VERSION_AUTHOR_ROLE = QtCore.Qt.UserRole + 18
|
VERSION_STATUS_ICON_ROLE = QtCore.Qt.UserRole + 18
|
||||||
VERSION_FRAME_RANGE_ROLE = QtCore.Qt.UserRole + 19
|
VERSION_AUTHOR_ROLE = QtCore.Qt.UserRole + 19
|
||||||
VERSION_DURATION_ROLE = QtCore.Qt.UserRole + 20
|
VERSION_FRAME_RANGE_ROLE = QtCore.Qt.UserRole + 20
|
||||||
VERSION_HANDLES_ROLE = QtCore.Qt.UserRole + 21
|
VERSION_DURATION_ROLE = QtCore.Qt.UserRole + 21
|
||||||
VERSION_STEP_ROLE = QtCore.Qt.UserRole + 22
|
VERSION_HANDLES_ROLE = QtCore.Qt.UserRole + 22
|
||||||
VERSION_AVAILABLE_ROLE = QtCore.Qt.UserRole + 23
|
VERSION_STEP_ROLE = QtCore.Qt.UserRole + 23
|
||||||
VERSION_THUMBNAIL_ID_ROLE = QtCore.Qt.UserRole + 24
|
VERSION_AVAILABLE_ROLE = QtCore.Qt.UserRole + 24
|
||||||
ACTIVE_SITE_ICON_ROLE = QtCore.Qt.UserRole + 25
|
VERSION_THUMBNAIL_ID_ROLE = QtCore.Qt.UserRole + 25
|
||||||
REMOTE_SITE_ICON_ROLE = QtCore.Qt.UserRole + 26
|
ACTIVE_SITE_ICON_ROLE = QtCore.Qt.UserRole + 26
|
||||||
REPRESENTATIONS_COUNT_ROLE = QtCore.Qt.UserRole + 27
|
REMOTE_SITE_ICON_ROLE = QtCore.Qt.UserRole + 27
|
||||||
SYNC_ACTIVE_SITE_AVAILABILITY = QtCore.Qt.UserRole + 28
|
REPRESENTATIONS_COUNT_ROLE = QtCore.Qt.UserRole + 28
|
||||||
SYNC_REMOTE_SITE_AVAILABILITY = QtCore.Qt.UserRole + 29
|
SYNC_ACTIVE_SITE_AVAILABILITY = QtCore.Qt.UserRole + 29
|
||||||
|
SYNC_REMOTE_SITE_AVAILABILITY = QtCore.Qt.UserRole + 30
|
||||||
|
|
||||||
|
|
||||||
class ProductsModel(QtGui.QStandardItemModel):
|
class ProductsModel(QtGui.QStandardItemModel):
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ from ayon_core.tools.utils import (
|
||||||
RecursiveSortFilterProxyModel,
|
RecursiveSortFilterProxyModel,
|
||||||
DeselectableTreeView,
|
DeselectableTreeView,
|
||||||
)
|
)
|
||||||
from ayon_core.tools.utils.delegates import PrettyTimeDelegate
|
from ayon_core.tools.utils.delegates import PrettyTimeDelegate, StatusDelegate
|
||||||
|
|
||||||
from .products_model import (
|
from .products_model import (
|
||||||
ProductsModel,
|
ProductsModel,
|
||||||
|
|
@ -17,12 +17,15 @@ from .products_model import (
|
||||||
FOLDER_ID_ROLE,
|
FOLDER_ID_ROLE,
|
||||||
PRODUCT_ID_ROLE,
|
PRODUCT_ID_ROLE,
|
||||||
VERSION_ID_ROLE,
|
VERSION_ID_ROLE,
|
||||||
|
VERSION_STATUS_NAME_ROLE,
|
||||||
|
VERSION_STATUS_SHORT_ROLE,
|
||||||
|
VERSION_STATUS_COLOR_ROLE,
|
||||||
|
VERSION_STATUS_ICON_ROLE,
|
||||||
VERSION_THUMBNAIL_ID_ROLE,
|
VERSION_THUMBNAIL_ID_ROLE,
|
||||||
)
|
)
|
||||||
from .products_delegates import (
|
from .products_delegates import (
|
||||||
VersionDelegate,
|
VersionDelegate,
|
||||||
LoadedInSceneDelegate,
|
LoadedInSceneDelegate,
|
||||||
StatusDelegate,
|
|
||||||
SiteSyncDelegate,
|
SiteSyncDelegate,
|
||||||
)
|
)
|
||||||
from .actions_utils import show_actions_menu
|
from .actions_utils import show_actions_menu
|
||||||
|
|
@ -131,7 +134,12 @@ class ProductsWidget(QtWidgets.QWidget):
|
||||||
|
|
||||||
version_delegate = VersionDelegate()
|
version_delegate = VersionDelegate()
|
||||||
time_delegate = PrettyTimeDelegate()
|
time_delegate = PrettyTimeDelegate()
|
||||||
status_delegate = StatusDelegate()
|
status_delegate = StatusDelegate(
|
||||||
|
VERSION_STATUS_NAME_ROLE,
|
||||||
|
VERSION_STATUS_SHORT_ROLE,
|
||||||
|
VERSION_STATUS_COLOR_ROLE,
|
||||||
|
VERSION_STATUS_ICON_ROLE,
|
||||||
|
)
|
||||||
in_scene_delegate = LoadedInSceneDelegate()
|
in_scene_delegate = LoadedInSceneDelegate()
|
||||||
sitesync_delegate = SiteSyncDelegate()
|
sitesync_delegate = SiteSyncDelegate()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import ayon_api
|
import ayon_api
|
||||||
|
|
||||||
from ayon_core.lib.events import QueuedEventSystem
|
from ayon_core.lib.events import QueuedEventSystem
|
||||||
from ayon_core.host import ILoadHost
|
from ayon_core.host import HostBase
|
||||||
from ayon_core.pipeline import (
|
from ayon_core.pipeline import (
|
||||||
registered_host,
|
registered_host,
|
||||||
get_current_context,
|
get_current_context,
|
||||||
)
|
)
|
||||||
from ayon_core.tools.common_models import HierarchyModel
|
from ayon_core.tools.common_models import HierarchyModel, ProjectsModel
|
||||||
|
|
||||||
from .models import SiteSyncModel
|
from .models import SiteSyncModel, ContainersModel
|
||||||
|
|
||||||
|
|
||||||
class SceneInventoryController:
|
class SceneInventoryController:
|
||||||
|
|
@ -28,11 +28,16 @@ class SceneInventoryController:
|
||||||
self._current_folder_id = None
|
self._current_folder_id = None
|
||||||
self._current_folder_set = False
|
self._current_folder_set = False
|
||||||
|
|
||||||
|
self._containers_model = ContainersModel(self)
|
||||||
self._sitesync_model = SiteSyncModel(self)
|
self._sitesync_model = SiteSyncModel(self)
|
||||||
# Switch dialog requirements
|
# Switch dialog requirements
|
||||||
self._hierarchy_model = HierarchyModel(self)
|
self._hierarchy_model = HierarchyModel(self)
|
||||||
|
self._projects_model = ProjectsModel(self)
|
||||||
self._event_system = self._create_event_system()
|
self._event_system = self._create_event_system()
|
||||||
|
|
||||||
|
def get_host(self) -> HostBase:
|
||||||
|
return self._host
|
||||||
|
|
||||||
def emit_event(self, topic, data=None, source=None):
|
def emit_event(self, topic, data=None, source=None):
|
||||||
if data is None:
|
if data is None:
|
||||||
data = {}
|
data = {}
|
||||||
|
|
@ -47,6 +52,7 @@ class SceneInventoryController:
|
||||||
self._current_folder_id = None
|
self._current_folder_id = None
|
||||||
self._current_folder_set = False
|
self._current_folder_set = False
|
||||||
|
|
||||||
|
self._containers_model.reset()
|
||||||
self._sitesync_model.reset()
|
self._sitesync_model.reset()
|
||||||
self._hierarchy_model.reset()
|
self._hierarchy_model.reset()
|
||||||
|
|
||||||
|
|
@ -80,13 +86,32 @@ class SceneInventoryController:
|
||||||
self._current_folder_set = True
|
self._current_folder_set = True
|
||||||
return self._current_folder_id
|
return self._current_folder_id
|
||||||
|
|
||||||
|
def get_project_status_items(self):
|
||||||
|
project_name = self.get_current_project_name()
|
||||||
|
return self._projects_model.get_project_status_items(
|
||||||
|
project_name, None
|
||||||
|
)
|
||||||
|
|
||||||
|
# Containers methods
|
||||||
def get_containers(self):
|
def get_containers(self):
|
||||||
host = self._host
|
return self._containers_model.get_containers()
|
||||||
if isinstance(host, ILoadHost):
|
|
||||||
return list(host.get_containers())
|
def get_containers_by_item_ids(self, item_ids):
|
||||||
elif hasattr(host, "ls"):
|
return self._containers_model.get_containers_by_item_ids(item_ids)
|
||||||
return list(host.ls())
|
|
||||||
return []
|
def get_container_items(self):
|
||||||
|
return self._containers_model.get_container_items()
|
||||||
|
|
||||||
|
def get_container_items_by_id(self, item_ids):
|
||||||
|
return self._containers_model.get_container_items_by_id(item_ids)
|
||||||
|
|
||||||
|
def get_representation_info_items(self, representation_ids):
|
||||||
|
return self._containers_model.get_representation_info_items(
|
||||||
|
representation_ids
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_version_items(self, product_ids):
|
||||||
|
return self._containers_model.get_version_items(product_ids)
|
||||||
|
|
||||||
# Site Sync methods
|
# Site Sync methods
|
||||||
def is_sitesync_enabled(self):
|
def is_sitesync_enabled(self):
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,10 @@
|
||||||
import numbers
|
|
||||||
|
|
||||||
import ayon_api
|
|
||||||
|
|
||||||
from ayon_core.pipeline import HeroVersionType
|
|
||||||
from ayon_core.tools.utils.models import TreeModel
|
|
||||||
from ayon_core.tools.utils.lib import format_version
|
|
||||||
|
|
||||||
from qtpy import QtWidgets, QtCore, QtGui
|
from qtpy import QtWidgets, QtCore, QtGui
|
||||||
|
|
||||||
|
from .model import VERSION_LABEL_ROLE
|
||||||
|
|
||||||
|
|
||||||
class VersionDelegate(QtWidgets.QStyledItemDelegate):
|
class VersionDelegate(QtWidgets.QStyledItemDelegate):
|
||||||
"""A delegate that display version integer formatted as version string."""
|
"""A delegate that display version integer formatted as version string."""
|
||||||
|
|
||||||
version_changed = QtCore.Signal()
|
|
||||||
first_run = False
|
|
||||||
lock = False
|
|
||||||
|
|
||||||
def __init__(self, controller, *args, **kwargs):
|
|
||||||
self._controller = controller
|
|
||||||
super(VersionDelegate, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def get_project_name(self):
|
|
||||||
return self._controller.get_current_project_name()
|
|
||||||
|
|
||||||
def displayText(self, value, locale):
|
|
||||||
if isinstance(value, HeroVersionType):
|
|
||||||
return format_version(value)
|
|
||||||
if not isinstance(value, numbers.Integral):
|
|
||||||
# For cases where no version is resolved like NOT FOUND cases
|
|
||||||
# where a representation might not exist in current database
|
|
||||||
return
|
|
||||||
|
|
||||||
return format_version(value)
|
|
||||||
|
|
||||||
def paint(self, painter, option, index):
|
def paint(self, painter, option, index):
|
||||||
fg_color = index.data(QtCore.Qt.ForegroundRole)
|
fg_color = index.data(QtCore.Qt.ForegroundRole)
|
||||||
if fg_color:
|
if fg_color:
|
||||||
|
|
@ -44,7 +16,7 @@ class VersionDelegate(QtWidgets.QStyledItemDelegate):
|
||||||
fg_color = None
|
fg_color = None
|
||||||
|
|
||||||
if not fg_color:
|
if not fg_color:
|
||||||
return super(VersionDelegate, self).paint(painter, option, index)
|
return super().paint(painter, option, index)
|
||||||
|
|
||||||
if option.widget:
|
if option.widget:
|
||||||
style = option.widget.style()
|
style = option.widget.style()
|
||||||
|
|
@ -60,9 +32,7 @@ class VersionDelegate(QtWidgets.QStyledItemDelegate):
|
||||||
|
|
||||||
painter.save()
|
painter.save()
|
||||||
|
|
||||||
text = self.displayText(
|
text = index.data(VERSION_LABEL_ROLE)
|
||||||
index.data(QtCore.Qt.DisplayRole), option.locale
|
|
||||||
)
|
|
||||||
pen = painter.pen()
|
pen = painter.pen()
|
||||||
pen.setColor(fg_color)
|
pen.setColor(fg_color)
|
||||||
painter.setPen(pen)
|
painter.setPen(pen)
|
||||||
|
|
@ -82,77 +52,3 @@ class VersionDelegate(QtWidgets.QStyledItemDelegate):
|
||||||
)
|
)
|
||||||
|
|
||||||
painter.restore()
|
painter.restore()
|
||||||
|
|
||||||
def createEditor(self, parent, option, index):
|
|
||||||
item = index.data(TreeModel.ItemRole)
|
|
||||||
if item.get("isGroup") or item.get("isMerged"):
|
|
||||||
return
|
|
||||||
|
|
||||||
editor = QtWidgets.QComboBox(parent)
|
|
||||||
|
|
||||||
def commit_data():
|
|
||||||
if not self.first_run:
|
|
||||||
self.commitData.emit(editor) # Update model data
|
|
||||||
self.version_changed.emit() # Display model data
|
|
||||||
editor.currentIndexChanged.connect(commit_data)
|
|
||||||
|
|
||||||
self.first_run = True
|
|
||||||
self.lock = False
|
|
||||||
|
|
||||||
return editor
|
|
||||||
|
|
||||||
def setEditorData(self, editor, index):
|
|
||||||
if self.lock:
|
|
||||||
# Only set editor data once per delegation
|
|
||||||
return
|
|
||||||
|
|
||||||
editor.clear()
|
|
||||||
|
|
||||||
# Current value of the index
|
|
||||||
item = index.data(TreeModel.ItemRole)
|
|
||||||
value = index.data(QtCore.Qt.DisplayRole)
|
|
||||||
|
|
||||||
project_name = self.get_project_name()
|
|
||||||
# Add all available versions to the editor
|
|
||||||
product_id = item["version_entity"]["productId"]
|
|
||||||
version_entities = list(sorted(
|
|
||||||
ayon_api.get_versions(
|
|
||||||
project_name, product_ids={product_id}, active=True
|
|
||||||
),
|
|
||||||
key=lambda item: abs(item["version"])
|
|
||||||
))
|
|
||||||
|
|
||||||
selected = None
|
|
||||||
items = []
|
|
||||||
is_hero_version = value < 0
|
|
||||||
for version_entity in version_entities:
|
|
||||||
version = version_entity["version"]
|
|
||||||
label = format_version(version)
|
|
||||||
item = QtGui.QStandardItem(label)
|
|
||||||
item.setData(version_entity, QtCore.Qt.UserRole)
|
|
||||||
items.append(item)
|
|
||||||
|
|
||||||
if (
|
|
||||||
version == value
|
|
||||||
or is_hero_version and version < 0
|
|
||||||
):
|
|
||||||
selected = item
|
|
||||||
|
|
||||||
# Reverse items so latest versions be upper
|
|
||||||
items.reverse()
|
|
||||||
for item in items:
|
|
||||||
editor.model().appendRow(item)
|
|
||||||
|
|
||||||
index = 0
|
|
||||||
if selected:
|
|
||||||
index = selected.row()
|
|
||||||
|
|
||||||
# Will trigger index-change signal
|
|
||||||
editor.setCurrentIndex(index)
|
|
||||||
self.first_run = False
|
|
||||||
self.lock = True
|
|
||||||
|
|
||||||
def setModelData(self, editor, model, index):
|
|
||||||
"""Apply the integer version back in the model"""
|
|
||||||
version = editor.itemData(editor.currentIndex())
|
|
||||||
model.setData(index, version["name"])
|
|
||||||
|
|
|
||||||
|
|
@ -1,57 +1,113 @@
|
||||||
import re
|
import re
|
||||||
import logging
|
import logging
|
||||||
import uuid
|
|
||||||
|
|
||||||
from collections import defaultdict
|
import collections
|
||||||
|
|
||||||
import ayon_api
|
|
||||||
from qtpy import QtCore, QtGui
|
from qtpy import QtCore, QtGui
|
||||||
import qtawesome
|
import qtawesome
|
||||||
|
|
||||||
from ayon_core.pipeline import (
|
|
||||||
get_current_project_name,
|
|
||||||
HeroVersionType,
|
|
||||||
)
|
|
||||||
from ayon_core.style import get_default_entity_icon_color
|
from ayon_core.style import get_default_entity_icon_color
|
||||||
from ayon_core.tools.utils import get_qt_icon
|
from ayon_core.tools.utils import get_qt_icon
|
||||||
from ayon_core.tools.utils.models import TreeModel, Item
|
from ayon_core.tools.utils.lib import format_version
|
||||||
|
|
||||||
|
ITEM_ID_ROLE = QtCore.Qt.UserRole + 1
|
||||||
|
NAME_COLOR_ROLE = QtCore.Qt.UserRole + 2
|
||||||
|
COUNT_ROLE = QtCore.Qt.UserRole + 3
|
||||||
|
IS_CONTAINER_ITEM_ROLE = QtCore.Qt.UserRole + 4
|
||||||
|
VERSION_IS_LATEST_ROLE = QtCore.Qt.UserRole + 5
|
||||||
|
VERSION_IS_HERO_ROLE = QtCore.Qt.UserRole + 6
|
||||||
|
VERSION_LABEL_ROLE = QtCore.Qt.UserRole + 7
|
||||||
|
VERSION_COLOR_ROLE = QtCore.Qt.UserRole + 8
|
||||||
|
STATUS_NAME_ROLE = QtCore.Qt.UserRole + 9
|
||||||
|
STATUS_COLOR_ROLE = QtCore.Qt.UserRole + 10
|
||||||
|
STATUS_SHORT_ROLE = QtCore.Qt.UserRole + 11
|
||||||
|
STATUS_ICON_ROLE = QtCore.Qt.UserRole + 12
|
||||||
|
PRODUCT_ID_ROLE = QtCore.Qt.UserRole + 13
|
||||||
|
PRODUCT_TYPE_ROLE = QtCore.Qt.UserRole + 14
|
||||||
|
PRODUCT_TYPE_ICON_ROLE = QtCore.Qt.UserRole + 15
|
||||||
|
PRODUCT_GROUP_NAME_ROLE = QtCore.Qt.UserRole + 16
|
||||||
|
PRODUCT_GROUP_ICON_ROLE = QtCore.Qt.UserRole + 17
|
||||||
|
LOADER_NAME_ROLE = QtCore.Qt.UserRole + 18
|
||||||
|
OBJECT_NAME_ROLE = QtCore.Qt.UserRole + 19
|
||||||
|
ACTIVE_SITE_PROGRESS_ROLE = QtCore.Qt.UserRole + 20
|
||||||
|
REMOTE_SITE_PROGRESS_ROLE = QtCore.Qt.UserRole + 21
|
||||||
|
ACTIVE_SITE_ICON_ROLE = QtCore.Qt.UserRole + 22
|
||||||
|
REMOTE_SITE_ICON_ROLE = QtCore.Qt.UserRole + 23
|
||||||
|
# This value hold unique value of container that should be used to identify
|
||||||
|
# containers inbetween refresh.
|
||||||
|
ITEM_UNIQUE_NAME_ROLE = QtCore.Qt.UserRole + 24
|
||||||
|
|
||||||
|
|
||||||
def walk_hierarchy(node):
|
class InventoryModel(QtGui.QStandardItemModel):
|
||||||
"""Recursively yield group node."""
|
|
||||||
for child in node.children():
|
|
||||||
if child.get("isGroupNode"):
|
|
||||||
yield child
|
|
||||||
|
|
||||||
for _child in walk_hierarchy(child):
|
|
||||||
yield _child
|
|
||||||
|
|
||||||
|
|
||||||
class InventoryModel(TreeModel):
|
|
||||||
"""The model for the inventory"""
|
"""The model for the inventory"""
|
||||||
|
|
||||||
Columns = [
|
column_labels = [
|
||||||
"Name",
|
"Name",
|
||||||
"version",
|
"Version",
|
||||||
"count",
|
"Status",
|
||||||
"productType",
|
"Count",
|
||||||
"group",
|
"Product type",
|
||||||
"loader",
|
"Group",
|
||||||
"objectName",
|
"Loader",
|
||||||
"active_site",
|
"Object name",
|
||||||
"remote_site",
|
"Active site",
|
||||||
|
"Remote site",
|
||||||
]
|
]
|
||||||
active_site_col = Columns.index("active_site")
|
name_col = column_labels.index("Name")
|
||||||
remote_site_col = Columns.index("remote_site")
|
version_col = column_labels.index("Version")
|
||||||
|
status_col = column_labels.index("Status")
|
||||||
|
count_col = column_labels.index("Count")
|
||||||
|
product_type_col = column_labels.index("Product type")
|
||||||
|
product_group_col = column_labels.index("Group")
|
||||||
|
loader_col = column_labels.index("Loader")
|
||||||
|
object_name_col = column_labels.index("Object name")
|
||||||
|
active_site_col = column_labels.index("Active site")
|
||||||
|
remote_site_col = column_labels.index("Remote site")
|
||||||
|
display_role_by_column = {
|
||||||
|
name_col: QtCore.Qt.DisplayRole,
|
||||||
|
version_col: VERSION_LABEL_ROLE,
|
||||||
|
status_col: STATUS_NAME_ROLE,
|
||||||
|
count_col: COUNT_ROLE,
|
||||||
|
product_type_col: PRODUCT_TYPE_ROLE,
|
||||||
|
product_group_col: PRODUCT_GROUP_NAME_ROLE,
|
||||||
|
loader_col: LOADER_NAME_ROLE,
|
||||||
|
object_name_col: OBJECT_NAME_ROLE,
|
||||||
|
active_site_col: ACTIVE_SITE_PROGRESS_ROLE,
|
||||||
|
remote_site_col: REMOTE_SITE_PROGRESS_ROLE,
|
||||||
|
}
|
||||||
|
decoration_role_by_column = {
|
||||||
|
name_col: QtCore.Qt.DecorationRole,
|
||||||
|
product_type_col: PRODUCT_TYPE_ICON_ROLE,
|
||||||
|
product_group_col: PRODUCT_GROUP_ICON_ROLE,
|
||||||
|
active_site_col: ACTIVE_SITE_ICON_ROLE,
|
||||||
|
remote_site_col: REMOTE_SITE_ICON_ROLE,
|
||||||
|
}
|
||||||
|
foreground_role_by_column = {
|
||||||
|
name_col: NAME_COLOR_ROLE,
|
||||||
|
version_col: VERSION_COLOR_ROLE,
|
||||||
|
status_col: STATUS_COLOR_ROLE
|
||||||
|
}
|
||||||
|
width_by_column = {
|
||||||
|
name_col: 250,
|
||||||
|
version_col: 55,
|
||||||
|
status_col: 100,
|
||||||
|
count_col: 55,
|
||||||
|
product_type_col: 150,
|
||||||
|
product_group_col: 120,
|
||||||
|
loader_col: 150,
|
||||||
|
}
|
||||||
|
|
||||||
OUTDATED_COLOR = QtGui.QColor(235, 30, 30)
|
OUTDATED_COLOR = QtGui.QColor(235, 30, 30)
|
||||||
CHILD_OUTDATED_COLOR = QtGui.QColor(200, 160, 30)
|
CHILD_OUTDATED_COLOR = QtGui.QColor(200, 160, 30)
|
||||||
GRAYOUT_COLOR = QtGui.QColor(160, 160, 160)
|
GRAYOUT_COLOR = QtGui.QColor(160, 160, 160)
|
||||||
|
|
||||||
UniqueRole = QtCore.Qt.UserRole + 2 # unique label role
|
|
||||||
|
|
||||||
def __init__(self, controller, parent=None):
|
def __init__(self, controller, parent=None):
|
||||||
super(InventoryModel, self).__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
||||||
|
self.setColumnCount(len(self.column_labels))
|
||||||
|
for idx, label in enumerate(self.column_labels):
|
||||||
|
self.setHeaderData(idx, QtCore.Qt.Horizontal, label)
|
||||||
|
|
||||||
self.log = logging.getLogger(self.__class__.__name__)
|
self.log = logging.getLogger(self.__class__.__name__)
|
||||||
|
|
||||||
self._controller = controller
|
self._controller = controller
|
||||||
|
|
@ -60,103 +116,217 @@ class InventoryModel(TreeModel):
|
||||||
|
|
||||||
self._default_icon_color = get_default_entity_icon_color()
|
self._default_icon_color = get_default_entity_icon_color()
|
||||||
|
|
||||||
site_icons = self._controller.get_site_provider_icons()
|
|
||||||
|
|
||||||
self._site_icons = {
|
|
||||||
provider: get_qt_icon(icon_def)
|
|
||||||
for provider, icon_def in site_icons.items()
|
|
||||||
}
|
|
||||||
|
|
||||||
def outdated(self, item):
|
def outdated(self, item):
|
||||||
return item.get("isOutdated", True)
|
return item.get("isOutdated", True)
|
||||||
|
|
||||||
|
def refresh(self, selected=None):
|
||||||
|
"""Refresh the model"""
|
||||||
|
# for debugging or testing, injecting items from outside
|
||||||
|
container_items = self._controller.get_container_items()
|
||||||
|
|
||||||
|
self._clear_items()
|
||||||
|
|
||||||
|
items_by_repre_id = {}
|
||||||
|
for container_item in container_items:
|
||||||
|
# if (
|
||||||
|
# selected is not None
|
||||||
|
# and container_item.item_id not in selected
|
||||||
|
# ):
|
||||||
|
# continue
|
||||||
|
repre_id = container_item.representation_id
|
||||||
|
items = items_by_repre_id.setdefault(repre_id, [])
|
||||||
|
items.append(container_item)
|
||||||
|
|
||||||
|
repre_id = set(items_by_repre_id.keys())
|
||||||
|
repre_info_by_id = self._controller.get_representation_info_items(
|
||||||
|
repre_id
|
||||||
|
)
|
||||||
|
product_ids = {
|
||||||
|
repre_info.product_id
|
||||||
|
for repre_info in repre_info_by_id.values()
|
||||||
|
}
|
||||||
|
version_items_by_product_id = self._controller.get_version_items(
|
||||||
|
product_ids
|
||||||
|
)
|
||||||
|
# SiteSync addon information
|
||||||
|
progress_by_id = self._controller.get_representations_site_progress(
|
||||||
|
repre_id
|
||||||
|
)
|
||||||
|
sites_info = self._controller.get_sites_information()
|
||||||
|
site_icons = {
|
||||||
|
provider: get_qt_icon(icon_def)
|
||||||
|
for provider, icon_def in (
|
||||||
|
self._controller.get_site_provider_icons().items()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
status_items_by_name = {
|
||||||
|
status_item.name: status_item
|
||||||
|
for status_item in self._controller.get_project_status_items()
|
||||||
|
}
|
||||||
|
|
||||||
|
group_item_icon = qtawesome.icon(
|
||||||
|
"fa.folder", color=self._default_icon_color
|
||||||
|
)
|
||||||
|
valid_item_icon = qtawesome.icon(
|
||||||
|
"fa.file-o", color=self._default_icon_color
|
||||||
|
)
|
||||||
|
invalid_item_icon = qtawesome.icon(
|
||||||
|
"fa.exclamation-circle", color=self._default_icon_color
|
||||||
|
)
|
||||||
|
group_icon = qtawesome.icon(
|
||||||
|
"fa.object-group", color=self._default_icon_color
|
||||||
|
)
|
||||||
|
product_type_icon = qtawesome.icon(
|
||||||
|
"fa.folder", color="#0091B2"
|
||||||
|
)
|
||||||
|
group_item_font = QtGui.QFont()
|
||||||
|
group_item_font.setBold(True)
|
||||||
|
|
||||||
|
active_site_icon = site_icons.get(sites_info["active_site_provider"])
|
||||||
|
remote_site_icon = site_icons.get(sites_info["remote_site_provider"])
|
||||||
|
|
||||||
|
root_item = self.invisibleRootItem()
|
||||||
|
|
||||||
|
group_items = []
|
||||||
|
for repre_id, container_items in items_by_repre_id.items():
|
||||||
|
repre_info = repre_info_by_id[repre_id]
|
||||||
|
version_label = "N/A"
|
||||||
|
version_color = None
|
||||||
|
is_latest = False
|
||||||
|
is_hero = False
|
||||||
|
status_name = None
|
||||||
|
status_color = None
|
||||||
|
status_short = None
|
||||||
|
if not repre_info.is_valid:
|
||||||
|
group_name = "< Entity N/A >"
|
||||||
|
item_icon = invalid_item_icon
|
||||||
|
|
||||||
|
else:
|
||||||
|
group_name = "{}_{}: ({})".format(
|
||||||
|
repre_info.folder_path.rsplit("/")[-1],
|
||||||
|
repre_info.product_name,
|
||||||
|
repre_info.representation_name
|
||||||
|
)
|
||||||
|
item_icon = valid_item_icon
|
||||||
|
|
||||||
|
version_items = (
|
||||||
|
version_items_by_product_id[repre_info.product_id]
|
||||||
|
)
|
||||||
|
version_item = version_items[repre_info.version_id]
|
||||||
|
version_label = format_version(version_item.version)
|
||||||
|
is_hero = version_item.version < 0
|
||||||
|
is_latest = version_item.is_latest
|
||||||
|
if not is_latest:
|
||||||
|
version_color = self.OUTDATED_COLOR
|
||||||
|
status_name = version_item.status
|
||||||
|
status_item = status_items_by_name.get(status_name)
|
||||||
|
if status_item:
|
||||||
|
status_short = status_item.short
|
||||||
|
status_color = status_item.color
|
||||||
|
|
||||||
|
container_model_items = []
|
||||||
|
for container_item in container_items:
|
||||||
|
unique_name = (
|
||||||
|
repre_info.representation_name
|
||||||
|
+ container_item.object_name or "<none>"
|
||||||
|
)
|
||||||
|
|
||||||
|
item = QtGui.QStandardItem()
|
||||||
|
item.setColumnCount(root_item.columnCount())
|
||||||
|
item.setData(container_item.namespace, QtCore.Qt.DisplayRole)
|
||||||
|
item.setData(self.GRAYOUT_COLOR, NAME_COLOR_ROLE)
|
||||||
|
item.setData(self.GRAYOUT_COLOR, VERSION_COLOR_ROLE)
|
||||||
|
item.setData(item_icon, QtCore.Qt.DecorationRole)
|
||||||
|
item.setData(repre_info.product_id, PRODUCT_ID_ROLE)
|
||||||
|
item.setData(container_item.item_id, ITEM_ID_ROLE)
|
||||||
|
item.setData(version_label, VERSION_LABEL_ROLE)
|
||||||
|
item.setData(container_item.loader_name, LOADER_NAME_ROLE)
|
||||||
|
item.setData(container_item.object_name, OBJECT_NAME_ROLE)
|
||||||
|
item.setData(True, IS_CONTAINER_ITEM_ROLE)
|
||||||
|
item.setData(unique_name, ITEM_UNIQUE_NAME_ROLE)
|
||||||
|
container_model_items.append(item)
|
||||||
|
|
||||||
|
if not container_model_items:
|
||||||
|
continue
|
||||||
|
|
||||||
|
progress = progress_by_id[repre_id]
|
||||||
|
active_site_progress = "{}%".format(
|
||||||
|
max(progress["active_site"], 0) * 100
|
||||||
|
)
|
||||||
|
remote_site_progress = "{}%".format(
|
||||||
|
max(progress["remote_site"], 0) * 100
|
||||||
|
)
|
||||||
|
|
||||||
|
group_item = QtGui.QStandardItem()
|
||||||
|
group_item.setColumnCount(root_item.columnCount())
|
||||||
|
group_item.setData(group_name, QtCore.Qt.DisplayRole)
|
||||||
|
group_item.setData(group_name, ITEM_UNIQUE_NAME_ROLE)
|
||||||
|
group_item.setData(group_item_icon, QtCore.Qt.DecorationRole)
|
||||||
|
group_item.setData(group_item_font, QtCore.Qt.FontRole)
|
||||||
|
group_item.setData(repre_info.product_id, PRODUCT_ID_ROLE)
|
||||||
|
group_item.setData(repre_info.product_type, PRODUCT_TYPE_ROLE)
|
||||||
|
group_item.setData(product_type_icon, PRODUCT_TYPE_ICON_ROLE)
|
||||||
|
group_item.setData(is_latest, VERSION_IS_LATEST_ROLE)
|
||||||
|
group_item.setData(is_hero, VERSION_IS_HERO_ROLE)
|
||||||
|
group_item.setData(version_label, VERSION_LABEL_ROLE)
|
||||||
|
group_item.setData(len(container_items), COUNT_ROLE)
|
||||||
|
group_item.setData(status_name, STATUS_NAME_ROLE)
|
||||||
|
group_item.setData(status_short, STATUS_SHORT_ROLE)
|
||||||
|
group_item.setData(status_color, STATUS_COLOR_ROLE)
|
||||||
|
|
||||||
|
group_item.setData(
|
||||||
|
active_site_progress, ACTIVE_SITE_PROGRESS_ROLE
|
||||||
|
)
|
||||||
|
group_item.setData(
|
||||||
|
remote_site_progress, REMOTE_SITE_PROGRESS_ROLE
|
||||||
|
)
|
||||||
|
group_item.setData(active_site_icon, ACTIVE_SITE_ICON_ROLE)
|
||||||
|
group_item.setData(remote_site_icon, REMOTE_SITE_ICON_ROLE)
|
||||||
|
group_item.setData(False, IS_CONTAINER_ITEM_ROLE)
|
||||||
|
|
||||||
|
if version_color is not None:
|
||||||
|
group_item.setData(version_color, VERSION_COLOR_ROLE)
|
||||||
|
|
||||||
|
if repre_info.product_group:
|
||||||
|
group_item.setData(
|
||||||
|
repre_info.product_group, PRODUCT_GROUP_NAME_ROLE
|
||||||
|
)
|
||||||
|
group_item.setData(group_icon, PRODUCT_GROUP_ICON_ROLE)
|
||||||
|
|
||||||
|
group_item.appendRows(container_model_items)
|
||||||
|
group_items.append(group_item)
|
||||||
|
|
||||||
|
if group_items:
|
||||||
|
root_item.appendRows(group_items)
|
||||||
|
|
||||||
|
def flags(self, index):
|
||||||
|
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
|
||||||
|
|
||||||
def data(self, index, role):
|
def data(self, index, role):
|
||||||
if not index.isValid():
|
if not index.isValid():
|
||||||
return
|
return
|
||||||
|
|
||||||
item = index.internalPointer()
|
col = index.column()
|
||||||
|
if role == QtCore.Qt.DisplayRole:
|
||||||
|
role = self.display_role_by_column.get(col)
|
||||||
|
if role is None:
|
||||||
|
print(col, role)
|
||||||
|
return None
|
||||||
|
|
||||||
if role == QtCore.Qt.FontRole:
|
elif role == QtCore.Qt.DecorationRole:
|
||||||
# Make top-level entries bold
|
role = self.decoration_role_by_column.get(col)
|
||||||
if item.get("isGroupNode") or item.get("isNotSet"): # group-item
|
if role is None:
|
||||||
font = QtGui.QFont()
|
return None
|
||||||
font.setBold(True)
|
|
||||||
return font
|
|
||||||
|
|
||||||
if role == QtCore.Qt.ForegroundRole:
|
elif role == QtCore.Qt.ForegroundRole:
|
||||||
# Set the text color to the OUTDATED_COLOR when the
|
role = self.foreground_role_by_column.get(col)
|
||||||
# collected version is not the same as the highest version
|
if role is None:
|
||||||
key = self.Columns[index.column()]
|
return None
|
||||||
if key == "version": # version
|
|
||||||
if item.get("isGroupNode"): # group-item
|
|
||||||
if self.outdated(item):
|
|
||||||
return self.OUTDATED_COLOR
|
|
||||||
|
|
||||||
if self._hierarchy_view:
|
if col != 0:
|
||||||
# If current group is not outdated, check if any
|
index = self.index(index.row(), 0, index.parent())
|
||||||
# outdated children.
|
|
||||||
for _node in walk_hierarchy(item):
|
|
||||||
if self.outdated(_node):
|
|
||||||
return self.CHILD_OUTDATED_COLOR
|
|
||||||
else:
|
|
||||||
|
|
||||||
if self._hierarchy_view:
|
return super().data(index, role)
|
||||||
# Although this is not a group item, we still need
|
|
||||||
# to distinguish which one contain outdated child.
|
|
||||||
for _node in walk_hierarchy(item):
|
|
||||||
if self.outdated(_node):
|
|
||||||
return self.CHILD_OUTDATED_COLOR.darker(150)
|
|
||||||
|
|
||||||
return self.GRAYOUT_COLOR
|
|
||||||
|
|
||||||
if key == "Name" and not item.get("isGroupNode"):
|
|
||||||
return self.GRAYOUT_COLOR
|
|
||||||
|
|
||||||
# Add icons
|
|
||||||
if role == QtCore.Qt.DecorationRole:
|
|
||||||
if index.column() == 0:
|
|
||||||
# Override color
|
|
||||||
color = item.get("color", self._default_icon_color)
|
|
||||||
if item.get("isGroupNode"): # group-item
|
|
||||||
return qtawesome.icon("fa.folder", color=color)
|
|
||||||
if item.get("isNotSet"):
|
|
||||||
return qtawesome.icon("fa.exclamation-circle", color=color)
|
|
||||||
|
|
||||||
return qtawesome.icon("fa.file-o", color=color)
|
|
||||||
|
|
||||||
if index.column() == 3:
|
|
||||||
# Product type icon
|
|
||||||
return item.get("productTypeIcon", None)
|
|
||||||
|
|
||||||
column_name = self.Columns[index.column()]
|
|
||||||
|
|
||||||
if column_name == "group" and item.get("group"):
|
|
||||||
return qtawesome.icon("fa.object-group",
|
|
||||||
color=get_default_entity_icon_color())
|
|
||||||
|
|
||||||
if item.get("isGroupNode"):
|
|
||||||
if column_name == "active_site":
|
|
||||||
provider = item.get("active_site_provider")
|
|
||||||
return self._site_icons.get(provider)
|
|
||||||
|
|
||||||
if column_name == "remote_site":
|
|
||||||
provider = item.get("remote_site_provider")
|
|
||||||
return self._site_icons.get(provider)
|
|
||||||
|
|
||||||
if role == QtCore.Qt.DisplayRole and item.get("isGroupNode"):
|
|
||||||
column_name = self.Columns[index.column()]
|
|
||||||
progress = None
|
|
||||||
if column_name == "active_site":
|
|
||||||
progress = item.get("active_site_progress", 0)
|
|
||||||
elif column_name == "remote_site":
|
|
||||||
progress = item.get("remote_site_progress", 0)
|
|
||||||
if progress is not None:
|
|
||||||
return "{}%".format(max(progress, 0) * 100)
|
|
||||||
|
|
||||||
if role == self.UniqueRole:
|
|
||||||
return item["representation"] + item.get("objectName", "<none>")
|
|
||||||
|
|
||||||
return super(InventoryModel, self).data(index, role)
|
|
||||||
|
|
||||||
def set_hierarchy_view(self, state):
|
def set_hierarchy_view(self, state):
|
||||||
"""Set whether to display products in hierarchy view."""
|
"""Set whether to display products in hierarchy view."""
|
||||||
|
|
@ -165,299 +335,34 @@ class InventoryModel(TreeModel):
|
||||||
if state != self._hierarchy_view:
|
if state != self._hierarchy_view:
|
||||||
self._hierarchy_view = state
|
self._hierarchy_view = state
|
||||||
|
|
||||||
def refresh(self, selected=None, containers=None):
|
def get_outdated_item_ids(self, ignore_hero=True):
|
||||||
"""Refresh the model"""
|
outdated_item_ids = []
|
||||||
|
root_item = self.invisibleRootItem()
|
||||||
# for debugging or testing, injecting items from outside
|
for row in range(root_item.rowCount()):
|
||||||
if containers is None:
|
group_item = root_item.child(row)
|
||||||
containers = self._controller.get_containers()
|
if group_item.data(VERSION_IS_LATEST_ROLE):
|
||||||
|
|
||||||
self.clear()
|
|
||||||
if not selected or not self._hierarchy_view:
|
|
||||||
self._add_containers(containers)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Filter by cherry-picked items
|
|
||||||
self._add_containers((
|
|
||||||
container
|
|
||||||
for container in containers
|
|
||||||
if container["objectName"] in selected
|
|
||||||
))
|
|
||||||
|
|
||||||
def _add_containers(self, containers, parent=None):
|
|
||||||
"""Add the items to the model.
|
|
||||||
|
|
||||||
The items should be formatted similar to `api.ls()` returns, an item
|
|
||||||
is then represented as:
|
|
||||||
{"filename_v001.ma": [full/filename/of/loaded/filename_v001.ma,
|
|
||||||
full/filename/of/loaded/filename_v001.ma],
|
|
||||||
"nodetype" : "reference",
|
|
||||||
"node": "referenceNode1"}
|
|
||||||
|
|
||||||
Note: When performing an additional call to `add_items` it will *not*
|
|
||||||
group the new items with previously existing item groups of the
|
|
||||||
same type.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
containers (generator): Container items.
|
|
||||||
parent (Item, optional): Set this item as parent for the added
|
|
||||||
items when provided. Defaults to the root of the model.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
node.Item: root node which has children added based on the data
|
|
||||||
"""
|
|
||||||
|
|
||||||
project_name = get_current_project_name()
|
|
||||||
|
|
||||||
self.beginResetModel()
|
|
||||||
|
|
||||||
# Group by representation
|
|
||||||
grouped = defaultdict(lambda: {"containers": list()})
|
|
||||||
for container in containers:
|
|
||||||
repre_id = container["representation"]
|
|
||||||
grouped[repre_id]["containers"].append(container)
|
|
||||||
|
|
||||||
(
|
|
||||||
repres_by_id,
|
|
||||||
versions_by_id,
|
|
||||||
products_by_id,
|
|
||||||
folders_by_id,
|
|
||||||
) = self._query_entities(project_name, set(grouped.keys()))
|
|
||||||
# Add to model
|
|
||||||
not_found = defaultdict(list)
|
|
||||||
not_found_ids = []
|
|
||||||
for repre_id, group_dict in sorted(grouped.items()):
|
|
||||||
group_containers = group_dict["containers"]
|
|
||||||
representation = repres_by_id.get(repre_id)
|
|
||||||
if not representation:
|
|
||||||
not_found["representation"].extend(group_containers)
|
|
||||||
not_found_ids.append(repre_id)
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
version_entity = versions_by_id.get(representation["versionId"])
|
if ignore_hero and group_item.data(VERSION_IS_HERO_ROLE):
|
||||||
if not version_entity:
|
|
||||||
not_found["version"].extend(group_containers)
|
|
||||||
not_found_ids.append(repre_id)
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
product_entity = products_by_id.get(version_entity["productId"])
|
for idx in range(group_item.rowCount()):
|
||||||
if not product_entity:
|
item = group_item.child(idx)
|
||||||
not_found["product"].extend(group_containers)
|
outdated_item_ids.append(item.data(ITEM_ID_ROLE))
|
||||||
not_found_ids.append(repre_id)
|
return outdated_item_ids
|
||||||
continue
|
|
||||||
|
|
||||||
folder_entity = folders_by_id.get(product_entity["folderId"])
|
def _clear_items(self):
|
||||||
if not folder_entity:
|
root_item = self.invisibleRootItem()
|
||||||
not_found["folder"].extend(group_containers)
|
root_item.removeRows(0, root_item.rowCount())
|
||||||
not_found_ids.append(repre_id)
|
|
||||||
continue
|
|
||||||
|
|
||||||
group_dict.update({
|
|
||||||
"representation": representation,
|
|
||||||
"version": version_entity,
|
|
||||||
"product": product_entity,
|
|
||||||
"folder": folder_entity
|
|
||||||
})
|
|
||||||
|
|
||||||
for _repre_id in not_found_ids:
|
|
||||||
grouped.pop(_repre_id)
|
|
||||||
|
|
||||||
for where, group_containers in not_found.items():
|
|
||||||
# create the group header
|
|
||||||
group_node = Item()
|
|
||||||
name = "< NOT FOUND - {} >".format(where)
|
|
||||||
group_node["Name"] = name
|
|
||||||
group_node["representation"] = name
|
|
||||||
group_node["count"] = len(group_containers)
|
|
||||||
group_node["isGroupNode"] = False
|
|
||||||
group_node["isNotSet"] = True
|
|
||||||
|
|
||||||
self.add_child(group_node, parent=parent)
|
|
||||||
|
|
||||||
for container in group_containers:
|
|
||||||
item_node = Item()
|
|
||||||
item_node.update(container)
|
|
||||||
item_node["Name"] = container.get("objectName", "NO NAME")
|
|
||||||
item_node["isNotFound"] = True
|
|
||||||
self.add_child(item_node, parent=group_node)
|
|
||||||
|
|
||||||
# TODO Use product icons
|
|
||||||
product_type_icon = qtawesome.icon(
|
|
||||||
"fa.folder", color="#0091B2"
|
|
||||||
)
|
|
||||||
# Prepare site sync specific data
|
|
||||||
progress_by_id = self._controller.get_representations_site_progress(
|
|
||||||
set(grouped.keys())
|
|
||||||
)
|
|
||||||
sites_info = self._controller.get_sites_information()
|
|
||||||
|
|
||||||
# Query the highest available version so the model can know
|
|
||||||
# whether current version is currently up-to-date.
|
|
||||||
highest_version_by_product_id = ayon_api.get_last_versions(
|
|
||||||
project_name,
|
|
||||||
product_ids={
|
|
||||||
group["version"]["productId"] for group in grouped.values()
|
|
||||||
},
|
|
||||||
fields={"productId", "version"}
|
|
||||||
)
|
|
||||||
# Map value to `version` key
|
|
||||||
highest_version_by_product_id = {
|
|
||||||
product_id: version["version"]
|
|
||||||
for product_id, version in highest_version_by_product_id.items()
|
|
||||||
}
|
|
||||||
|
|
||||||
for repre_id, group_dict in sorted(grouped.items()):
|
|
||||||
group_containers = group_dict["containers"]
|
|
||||||
repre_entity = group_dict["representation"]
|
|
||||||
version_entity = group_dict["version"]
|
|
||||||
folder_entity = group_dict["folder"]
|
|
||||||
product_entity = group_dict["product"]
|
|
||||||
|
|
||||||
product_type = product_entity["productType"]
|
|
||||||
|
|
||||||
# create the group header
|
|
||||||
group_node = Item()
|
|
||||||
group_node["Name"] = "{}_{}: ({})".format(
|
|
||||||
folder_entity["name"],
|
|
||||||
product_entity["name"],
|
|
||||||
repre_entity["name"]
|
|
||||||
)
|
|
||||||
group_node["representation"] = repre_id
|
|
||||||
|
|
||||||
# Detect hero version type
|
|
||||||
version = version_entity["version"]
|
|
||||||
if version < 0:
|
|
||||||
version = HeroVersionType(version)
|
|
||||||
group_node["version"] = version
|
|
||||||
|
|
||||||
# Check if the version is outdated.
|
|
||||||
# Hero versions are never considered to be outdated.
|
|
||||||
is_outdated = False
|
|
||||||
if not isinstance(version, HeroVersionType):
|
|
||||||
last_version = highest_version_by_product_id.get(
|
|
||||||
version_entity["productId"])
|
|
||||||
if last_version is not None:
|
|
||||||
is_outdated = version_entity["version"] != last_version
|
|
||||||
group_node["isOutdated"] = is_outdated
|
|
||||||
|
|
||||||
group_node["productType"] = product_type or ""
|
|
||||||
group_node["productTypeIcon"] = product_type_icon
|
|
||||||
group_node["count"] = len(group_containers)
|
|
||||||
group_node["isGroupNode"] = True
|
|
||||||
group_node["group"] = product_entity["attrib"].get("productGroup")
|
|
||||||
|
|
||||||
# Site sync specific data
|
|
||||||
progress = progress_by_id[repre_id]
|
|
||||||
group_node.update(sites_info)
|
|
||||||
group_node["active_site_progress"] = progress["active_site"]
|
|
||||||
group_node["remote_site_progress"] = progress["remote_site"]
|
|
||||||
|
|
||||||
self.add_child(group_node, parent=parent)
|
|
||||||
|
|
||||||
for container in group_containers:
|
|
||||||
item_node = Item()
|
|
||||||
item_node.update(container)
|
|
||||||
|
|
||||||
# store the current version on the item
|
|
||||||
item_node["version"] = version_entity["version"]
|
|
||||||
item_node["version_entity"] = version_entity
|
|
||||||
|
|
||||||
# Remapping namespace to item name.
|
|
||||||
# Noted that the name key is capital "N", by doing this, we
|
|
||||||
# can view namespace in GUI without changing container data.
|
|
||||||
item_node["Name"] = container["namespace"]
|
|
||||||
|
|
||||||
self.add_child(item_node, parent=group_node)
|
|
||||||
|
|
||||||
self.endResetModel()
|
|
||||||
|
|
||||||
return self._root_item
|
|
||||||
|
|
||||||
def _query_entities(self, project_name, repre_ids):
|
|
||||||
"""Query entities for representations from containers.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
tuple[dict, dict, dict, dict]: Representation, version, product
|
|
||||||
and folder documents by id.
|
|
||||||
"""
|
|
||||||
|
|
||||||
repres_by_id = {}
|
|
||||||
versions_by_id = {}
|
|
||||||
products_by_id = {}
|
|
||||||
folders_by_id = {}
|
|
||||||
output = (
|
|
||||||
repres_by_id,
|
|
||||||
versions_by_id,
|
|
||||||
products_by_id,
|
|
||||||
folders_by_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
filtered_repre_ids = set()
|
|
||||||
for repre_id in repre_ids:
|
|
||||||
# Filter out invalid representation ids
|
|
||||||
# NOTE: This is added because scenes from OpenPype did contain
|
|
||||||
# ObjectId from mongo.
|
|
||||||
try:
|
|
||||||
uuid.UUID(repre_id)
|
|
||||||
filtered_repre_ids.add(repre_id)
|
|
||||||
except ValueError:
|
|
||||||
continue
|
|
||||||
if not filtered_repre_ids:
|
|
||||||
return output
|
|
||||||
|
|
||||||
repre_entities = ayon_api.get_representations(project_name, repre_ids)
|
|
||||||
repres_by_id.update({
|
|
||||||
repre_entity["id"]: repre_entity
|
|
||||||
for repre_entity in repre_entities
|
|
||||||
})
|
|
||||||
version_ids = {
|
|
||||||
repre_entity["versionId"]
|
|
||||||
for repre_entity in repres_by_id.values()
|
|
||||||
}
|
|
||||||
if not version_ids:
|
|
||||||
return output
|
|
||||||
|
|
||||||
versions_by_id.update({
|
|
||||||
version_entity["id"]: version_entity
|
|
||||||
for version_entity in ayon_api.get_versions(
|
|
||||||
project_name, version_ids=version_ids
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
product_ids = {
|
|
||||||
version_entity["productId"]
|
|
||||||
for version_entity in versions_by_id.values()
|
|
||||||
}
|
|
||||||
if not product_ids:
|
|
||||||
return output
|
|
||||||
|
|
||||||
products_by_id.update({
|
|
||||||
product_entity["id"]: product_entity
|
|
||||||
for product_entity in ayon_api.get_products(
|
|
||||||
project_name, product_ids=product_ids
|
|
||||||
)
|
|
||||||
})
|
|
||||||
folder_ids = {
|
|
||||||
product_entity["folderId"]
|
|
||||||
for product_entity in products_by_id.values()
|
|
||||||
}
|
|
||||||
if not folder_ids:
|
|
||||||
return output
|
|
||||||
|
|
||||||
folders_by_id.update({
|
|
||||||
folder_entity["id"]: folder_entity
|
|
||||||
for folder_entity in ayon_api.get_folders(
|
|
||||||
project_name, folder_ids=folder_ids
|
|
||||||
)
|
|
||||||
})
|
|
||||||
return output
|
|
||||||
|
|
||||||
|
|
||||||
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"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(FilterProxyModel, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
self.setDynamicSortFilter(True)
|
||||||
|
self.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
|
||||||
self._filter_outdated = False
|
self._filter_outdated = False
|
||||||
self._hierarchy_view = False
|
self._hierarchy_view = False
|
||||||
|
|
||||||
|
|
@ -467,28 +372,23 @@ class FilterProxyModel(QtCore.QSortFilterProxyModel):
|
||||||
|
|
||||||
# Always allow bottom entries (individual containers), since their
|
# Always allow bottom entries (individual containers), since their
|
||||||
# parent group hidden if it wouldn't have been validated.
|
# parent group hidden if it wouldn't have been validated.
|
||||||
rows = model.rowCount(source_index)
|
if source_index.data(IS_CONTAINER_ITEM_ROLE):
|
||||||
if not rows:
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Filter by regex
|
|
||||||
if hasattr(self, "filterRegExp"):
|
|
||||||
regex = self.filterRegExp()
|
|
||||||
else:
|
|
||||||
regex = self.filterRegularExpression()
|
|
||||||
pattern = regex.pattern()
|
|
||||||
if pattern:
|
|
||||||
pattern = re.escape(pattern)
|
|
||||||
|
|
||||||
if not self._matches(row, parent, pattern):
|
|
||||||
return False
|
|
||||||
|
|
||||||
if self._filter_outdated:
|
if self._filter_outdated:
|
||||||
# When filtering to outdated we filter the up to date entries
|
# When filtering to outdated we filter the up to date entries
|
||||||
# thus we "allow" them when they are outdated
|
# thus we "allow" them when they are outdated
|
||||||
if not self._is_outdated(row, parent):
|
if source_index.data(VERSION_IS_LATEST_ROLE):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Filter by regex
|
||||||
|
if hasattr(self, "filterRegularExpression"):
|
||||||
|
regex = self.filterRegularExpression()
|
||||||
|
else:
|
||||||
|
regex = self.filterRegExp()
|
||||||
|
|
||||||
|
if not self._matches(row, parent, regex.pattern()):
|
||||||
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def set_filter_outdated(self, state):
|
def set_filter_outdated(self, state):
|
||||||
|
|
@ -505,37 +405,6 @@ class FilterProxyModel(QtCore.QSortFilterProxyModel):
|
||||||
if state != self._hierarchy_view:
|
if state != self._hierarchy_view:
|
||||||
self._hierarchy_view = state
|
self._hierarchy_view = state
|
||||||
|
|
||||||
def _is_outdated(self, row, parent):
|
|
||||||
"""Return whether row is outdated.
|
|
||||||
|
|
||||||
A row is considered outdated if `isOutdated` data is true or not set.
|
|
||||||
|
|
||||||
"""
|
|
||||||
def outdated(node):
|
|
||||||
return node.get("isOutdated", True)
|
|
||||||
|
|
||||||
index = self.sourceModel().index(row, self.filterKeyColumn(), parent)
|
|
||||||
|
|
||||||
# The scene contents are grouped by "representation", e.g. the same
|
|
||||||
# "representation" loaded twice is grouped under the same header.
|
|
||||||
# Since the version check filters these parent groups we skip that
|
|
||||||
# check for the individual children.
|
|
||||||
has_parent = index.parent().isValid()
|
|
||||||
if has_parent and not self._hierarchy_view:
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Filter to those that have the different version numbers
|
|
||||||
node = index.internalPointer()
|
|
||||||
if outdated(node):
|
|
||||||
return True
|
|
||||||
|
|
||||||
if self._hierarchy_view:
|
|
||||||
for _node in walk_hierarchy(node):
|
|
||||||
if outdated(_node):
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _matches(self, row, parent, pattern):
|
def _matches(self, row, parent, pattern):
|
||||||
"""Return whether row matches regex pattern.
|
"""Return whether row matches regex pattern.
|
||||||
|
|
||||||
|
|
@ -548,38 +417,31 @@ class FilterProxyModel(QtCore.QSortFilterProxyModel):
|
||||||
bool
|
bool
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
if not pattern:
|
||||||
|
return True
|
||||||
|
|
||||||
|
flags = 0
|
||||||
|
if self.sortCaseSensitivity() == QtCore.Qt.CaseInsensitive:
|
||||||
|
flags = re.IGNORECASE
|
||||||
|
|
||||||
|
regex = re.compile(re.escape(pattern), flags=flags)
|
||||||
|
|
||||||
model = self.sourceModel()
|
model = self.sourceModel()
|
||||||
column = self.filterKeyColumn()
|
column = self.filterKeyColumn()
|
||||||
role = self.filterRole()
|
role = self.filterRole()
|
||||||
|
|
||||||
def matches(row, parent, pattern):
|
matches_queue = collections.deque()
|
||||||
|
matches_queue.append((row, parent))
|
||||||
|
while matches_queue:
|
||||||
|
queue_item = matches_queue.popleft()
|
||||||
|
row, parent = queue_item
|
||||||
|
|
||||||
index = model.index(row, column, parent)
|
index = model.index(row, column, parent)
|
||||||
key = model.data(index, role)
|
value = model.data(index, role)
|
||||||
if re.search(pattern, key, re.IGNORECASE):
|
if regex.search(value):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if matches(row, parent, pattern):
|
for idx in range(model.rowCount(index)):
|
||||||
return True
|
matches_queue.append((idx, index))
|
||||||
|
|
||||||
# Also allow if any of the children matches
|
return False
|
||||||
source_index = model.index(row, column, parent)
|
|
||||||
rows = model.rowCount(source_index)
|
|
||||||
|
|
||||||
if any(
|
|
||||||
matches(idx, source_index, pattern)
|
|
||||||
for idx in range(rows)
|
|
||||||
):
|
|
||||||
return True
|
|
||||||
|
|
||||||
if not self._hierarchy_view:
|
|
||||||
return False
|
|
||||||
|
|
||||||
for idx in range(rows):
|
|
||||||
child_index = model.index(idx, column, source_index)
|
|
||||||
child_rows = model.rowCount(child_index)
|
|
||||||
return any(
|
|
||||||
self._matches(child_idx, child_index, pattern)
|
|
||||||
for child_idx in range(child_rows)
|
|
||||||
)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
|
from .containers import ContainersModel
|
||||||
from .sitesync import SiteSyncModel
|
from .sitesync import SiteSyncModel
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
|
"ContainersModel",
|
||||||
"SiteSyncModel",
|
"SiteSyncModel",
|
||||||
)
|
)
|
||||||
|
|
|
||||||
343
client/ayon_core/tools/sceneinventory/models/containers.py
Normal file
343
client/ayon_core/tools/sceneinventory/models/containers.py
Normal file
|
|
@ -0,0 +1,343 @@
|
||||||
|
import uuid
|
||||||
|
import collections
|
||||||
|
|
||||||
|
import ayon_api
|
||||||
|
from ayon_api.graphql import GraphQlQuery
|
||||||
|
from ayon_core.host import ILoadHost
|
||||||
|
|
||||||
|
|
||||||
|
# --- Implementation that should be in ayon-python-api ---
|
||||||
|
# The implementation is not available in all versions of ayon-python-api.
|
||||||
|
RepresentationHierarchy = collections.namedtuple(
|
||||||
|
"RepresentationHierarchy",
|
||||||
|
("folder", "product", "version", "representation")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def representations_parent_ids_qraphql_query():
|
||||||
|
query = GraphQlQuery("RepresentationsHierarchyQuery")
|
||||||
|
|
||||||
|
project_name_var = query.add_variable("projectName", "String!")
|
||||||
|
repre_ids_var = query.add_variable("representationIds", "[String!]")
|
||||||
|
|
||||||
|
project_field = query.add_field("project")
|
||||||
|
project_field.set_filter("name", project_name_var)
|
||||||
|
|
||||||
|
repres_field = project_field.add_field_with_edges("representations")
|
||||||
|
repres_field.add_field("id")
|
||||||
|
repres_field.add_field("name")
|
||||||
|
repres_field.set_filter("ids", repre_ids_var)
|
||||||
|
version_field = repres_field.add_field("version")
|
||||||
|
version_field.add_field("id")
|
||||||
|
product_field = version_field.add_field("product")
|
||||||
|
product_field.add_field("id")
|
||||||
|
product_field.add_field("name")
|
||||||
|
product_field.add_field("productType")
|
||||||
|
product_attrib_field = product_field.add_field("attrib")
|
||||||
|
product_attrib_field.add_field("productGroup")
|
||||||
|
folder_field = product_field.add_field("folder")
|
||||||
|
folder_field.add_field("id")
|
||||||
|
folder_field.add_field("path")
|
||||||
|
return query
|
||||||
|
|
||||||
|
|
||||||
|
def get_representations_hierarchy(project_name, representation_ids):
|
||||||
|
"""Find representations parents by representation id.
|
||||||
|
|
||||||
|
Representation parent entities up to project.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
project_name (str): Project where to look for entities.
|
||||||
|
representation_ids (Iterable[str]): Representation ids.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict[str, RepresentationParents]: Parent entities by
|
||||||
|
representation id.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not representation_ids:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
repre_ids = set(representation_ids)
|
||||||
|
output = {
|
||||||
|
repre_id: RepresentationHierarchy(None, None, None, None)
|
||||||
|
for repre_id in representation_ids
|
||||||
|
}
|
||||||
|
|
||||||
|
query = representations_parent_ids_qraphql_query()
|
||||||
|
query.set_variable_value("projectName", project_name)
|
||||||
|
query.set_variable_value("representationIds", list(repre_ids))
|
||||||
|
|
||||||
|
con = ayon_api.get_server_api_connection()
|
||||||
|
parsed_data = query.query(con)
|
||||||
|
for repre in parsed_data["project"]["representations"]:
|
||||||
|
repre_id = repre["id"]
|
||||||
|
version = repre.pop("version")
|
||||||
|
product = version.pop("product")
|
||||||
|
folder = product.pop("folder")
|
||||||
|
|
||||||
|
output[repre_id] = RepresentationHierarchy(
|
||||||
|
folder, product, version, repre
|
||||||
|
)
|
||||||
|
|
||||||
|
return output
|
||||||
|
# --- END of ayon-python-api implementation ---
|
||||||
|
|
||||||
|
|
||||||
|
class ContainerItem:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
representation_id,
|
||||||
|
loader_name,
|
||||||
|
namespace,
|
||||||
|
name,
|
||||||
|
object_name,
|
||||||
|
item_id
|
||||||
|
):
|
||||||
|
self.representation_id = representation_id
|
||||||
|
self.loader_name = loader_name
|
||||||
|
self.object_name = object_name
|
||||||
|
self.namespace = namespace
|
||||||
|
self.name = name
|
||||||
|
self.item_id = item_id
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_container_data(cls, container):
|
||||||
|
return cls(
|
||||||
|
representation_id=container["representation"],
|
||||||
|
loader_name=container["loader"],
|
||||||
|
namespace=container["namespace"],
|
||||||
|
name=container["name"],
|
||||||
|
object_name=container["objectName"],
|
||||||
|
item_id=uuid.uuid4().hex,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class RepresentationInfo:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
folder_id,
|
||||||
|
folder_path,
|
||||||
|
product_id,
|
||||||
|
product_name,
|
||||||
|
product_type,
|
||||||
|
product_group,
|
||||||
|
version_id,
|
||||||
|
representation_name,
|
||||||
|
):
|
||||||
|
self.folder_id = folder_id
|
||||||
|
self.folder_path = folder_path
|
||||||
|
self.product_id = product_id
|
||||||
|
self.product_name = product_name
|
||||||
|
self.product_type = product_type
|
||||||
|
self.product_group = product_group
|
||||||
|
self.version_id = version_id
|
||||||
|
self.representation_name = representation_name
|
||||||
|
self._is_valid = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_valid(self):
|
||||||
|
if self._is_valid is None:
|
||||||
|
self._is_valid = (
|
||||||
|
self.folder_id is not None
|
||||||
|
and self.product_id is not None
|
||||||
|
and self.version_id is not None
|
||||||
|
and self.representation_name is not None
|
||||||
|
)
|
||||||
|
return self._is_valid
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def new_invalid(cls):
|
||||||
|
return cls(None, None, None, None, None, None, None, None)
|
||||||
|
|
||||||
|
|
||||||
|
class VersionItem:
|
||||||
|
def __init__(self, version_id, product_id, version, status, is_latest):
|
||||||
|
self.version = version
|
||||||
|
self.version_id = version_id
|
||||||
|
self.product_id = product_id
|
||||||
|
self.version = version
|
||||||
|
self.status = status
|
||||||
|
self.is_latest = is_latest
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_hero(self):
|
||||||
|
return self.version < 0
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_entity(cls, version_entity, is_latest):
|
||||||
|
return cls(
|
||||||
|
version_id=version_entity["id"],
|
||||||
|
product_id=version_entity["productId"],
|
||||||
|
version=version_entity["version"],
|
||||||
|
status=version_entity["status"],
|
||||||
|
is_latest=is_latest,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ContainersModel:
|
||||||
|
def __init__(self, controller):
|
||||||
|
self._controller = controller
|
||||||
|
self._items_cache = None
|
||||||
|
self._containers_by_id = {}
|
||||||
|
self._container_items_by_id = {}
|
||||||
|
self._version_items_by_product_id = {}
|
||||||
|
self._repre_info_by_id = {}
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
self._items_cache = None
|
||||||
|
self._containers_by_id = {}
|
||||||
|
self._container_items_by_id = {}
|
||||||
|
self._version_items_by_product_id = {}
|
||||||
|
self._repre_info_by_id = {}
|
||||||
|
|
||||||
|
def get_containers(self):
|
||||||
|
self._update_cache()
|
||||||
|
return list(self._containers_by_id.values())
|
||||||
|
|
||||||
|
def get_containers_by_item_ids(self, item_ids):
|
||||||
|
return {
|
||||||
|
item_id: self._containers_by_id.get(item_id)
|
||||||
|
for item_id in item_ids
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_container_items(self):
|
||||||
|
self._update_cache()
|
||||||
|
return list(self._items_cache)
|
||||||
|
|
||||||
|
def get_container_items_by_id(self, item_ids):
|
||||||
|
return {
|
||||||
|
item_id: self._container_items_by_id.get(item_id)
|
||||||
|
for item_id in item_ids
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_representation_info_items(self, representation_ids):
|
||||||
|
output = {}
|
||||||
|
missing_repre_ids = set()
|
||||||
|
for repre_id in representation_ids:
|
||||||
|
try:
|
||||||
|
uuid.UUID(repre_id)
|
||||||
|
except ValueError:
|
||||||
|
output[repre_id] = RepresentationInfo.new_invalid()
|
||||||
|
continue
|
||||||
|
|
||||||
|
repre_info = self._repre_info_by_id.get(repre_id)
|
||||||
|
if repre_info is None:
|
||||||
|
missing_repre_ids.add(repre_id)
|
||||||
|
else:
|
||||||
|
output[repre_id] = repre_info
|
||||||
|
|
||||||
|
if not missing_repre_ids:
|
||||||
|
return output
|
||||||
|
|
||||||
|
project_name = self._controller.get_current_project_name()
|
||||||
|
repre_hierarchy_by_id = get_representations_hierarchy(
|
||||||
|
project_name, missing_repre_ids
|
||||||
|
)
|
||||||
|
for repre_id, repre_hierarchy in repre_hierarchy_by_id.items():
|
||||||
|
kwargs = {
|
||||||
|
"folder_id": None,
|
||||||
|
"folder_path": None,
|
||||||
|
"product_id": None,
|
||||||
|
"product_name": None,
|
||||||
|
"product_type": None,
|
||||||
|
"product_group": None,
|
||||||
|
"version_id": None,
|
||||||
|
"representation_name": None,
|
||||||
|
}
|
||||||
|
folder = repre_hierarchy.folder
|
||||||
|
product = repre_hierarchy.product
|
||||||
|
version = repre_hierarchy.version
|
||||||
|
repre = repre_hierarchy.representation
|
||||||
|
if folder:
|
||||||
|
kwargs["folder_id"] = folder["id"]
|
||||||
|
kwargs["folder_path"] = folder["path"]
|
||||||
|
if product:
|
||||||
|
group = product["attrib"]["productGroup"]
|
||||||
|
kwargs["product_id"] = product["id"]
|
||||||
|
kwargs["product_name"] = product["name"]
|
||||||
|
kwargs["product_type"] = product["productType"]
|
||||||
|
kwargs["product_group"] = group
|
||||||
|
if version:
|
||||||
|
kwargs["version_id"] = version["id"]
|
||||||
|
if repre:
|
||||||
|
kwargs["representation_name"] = repre["name"]
|
||||||
|
|
||||||
|
repre_info = RepresentationInfo(**kwargs)
|
||||||
|
self._repre_info_by_id[repre_id] = repre_info
|
||||||
|
output[repre_id] = repre_info
|
||||||
|
return output
|
||||||
|
|
||||||
|
def get_version_items(self, product_ids):
|
||||||
|
if not product_ids:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
missing_ids = {
|
||||||
|
product_id
|
||||||
|
for product_id in product_ids
|
||||||
|
if product_id not in self._version_items_by_product_id
|
||||||
|
}
|
||||||
|
if missing_ids:
|
||||||
|
def version_sorted(entity):
|
||||||
|
return entity["version"]
|
||||||
|
|
||||||
|
project_name = self._controller.get_current_project_name()
|
||||||
|
version_entities_by_product_id = {
|
||||||
|
product_id: []
|
||||||
|
for product_id in missing_ids
|
||||||
|
}
|
||||||
|
|
||||||
|
version_entities = list(ayon_api.get_versions(
|
||||||
|
project_name,
|
||||||
|
product_ids=missing_ids,
|
||||||
|
fields={"id", "version", "productId", "status"}
|
||||||
|
))
|
||||||
|
version_entities.sort(key=version_sorted)
|
||||||
|
for version_entity in version_entities:
|
||||||
|
product_id = version_entity["productId"]
|
||||||
|
version_entities_by_product_id[product_id].append(
|
||||||
|
version_entity
|
||||||
|
)
|
||||||
|
|
||||||
|
for product_id, version_entities in (
|
||||||
|
version_entities_by_product_id.items()
|
||||||
|
):
|
||||||
|
last_version = abs(version_entities[-1]["version"])
|
||||||
|
version_items_by_id = {
|
||||||
|
entity["id"]: VersionItem.from_entity(
|
||||||
|
entity, abs(entity["version"]) == last_version
|
||||||
|
)
|
||||||
|
for entity in version_entities
|
||||||
|
}
|
||||||
|
self._version_items_by_product_id[product_id] = (
|
||||||
|
version_items_by_id
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
product_id: dict(self._version_items_by_product_id[product_id])
|
||||||
|
for product_id in product_ids
|
||||||
|
}
|
||||||
|
|
||||||
|
def _update_cache(self):
|
||||||
|
if self._items_cache is not None:
|
||||||
|
return
|
||||||
|
|
||||||
|
host = self._controller.get_host()
|
||||||
|
if isinstance(host, ILoadHost):
|
||||||
|
containers = list(host.get_containers())
|
||||||
|
elif hasattr(host, "ls"):
|
||||||
|
containers = list(host.ls())
|
||||||
|
else:
|
||||||
|
containers = []
|
||||||
|
container_items = []
|
||||||
|
containers_by_id = {}
|
||||||
|
container_items_by_id = {}
|
||||||
|
for container in containers:
|
||||||
|
item = ContainerItem.from_container_data(container)
|
||||||
|
containers_by_id[item.item_id] = container
|
||||||
|
container_items_by_id[item.item_id] = item
|
||||||
|
container_items.append(item)
|
||||||
|
|
||||||
|
self._containers_by_id = containers_by_id
|
||||||
|
self._container_items_by_id = container_items_by_id
|
||||||
|
self._items_cache = container_items
|
||||||
216
client/ayon_core/tools/sceneinventory/select_version_dialog.py
Normal file
216
client/ayon_core/tools/sceneinventory/select_version_dialog.py
Normal file
|
|
@ -0,0 +1,216 @@
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from qtpy import QtWidgets, QtCore, QtGui
|
||||||
|
|
||||||
|
from ayon_core.tools.utils.delegates import StatusDelegate
|
||||||
|
|
||||||
|
from .model import (
|
||||||
|
ITEM_ID_ROLE,
|
||||||
|
STATUS_NAME_ROLE,
|
||||||
|
STATUS_SHORT_ROLE,
|
||||||
|
STATUS_COLOR_ROLE,
|
||||||
|
STATUS_ICON_ROLE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class VersionOption:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
version,
|
||||||
|
label,
|
||||||
|
status_name,
|
||||||
|
status_short,
|
||||||
|
status_color
|
||||||
|
):
|
||||||
|
self.version = version
|
||||||
|
self.label = label
|
||||||
|
self.status_name = status_name
|
||||||
|
self.status_short = status_short
|
||||||
|
self.status_color = status_color
|
||||||
|
|
||||||
|
|
||||||
|
class SelectVersionModel(QtGui.QStandardItemModel):
|
||||||
|
def data(self, index, role=None):
|
||||||
|
if role is None:
|
||||||
|
role = QtCore.Qt.DisplayRole
|
||||||
|
|
||||||
|
index = self.index(index.row(), 0, index.parent())
|
||||||
|
return super().data(index, role)
|
||||||
|
|
||||||
|
|
||||||
|
class SelectVersionComboBox(QtWidgets.QComboBox):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
combo_model = SelectVersionModel(0, 2)
|
||||||
|
|
||||||
|
self.setModel(combo_model)
|
||||||
|
|
||||||
|
combo_view = QtWidgets.QTreeView(self)
|
||||||
|
combo_view.setHeaderHidden(True)
|
||||||
|
combo_view.setIndentation(0)
|
||||||
|
|
||||||
|
self.setView(combo_view)
|
||||||
|
|
||||||
|
header = combo_view.header()
|
||||||
|
header.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents)
|
||||||
|
header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
|
||||||
|
|
||||||
|
status_delegate = StatusDelegate(
|
||||||
|
STATUS_NAME_ROLE,
|
||||||
|
STATUS_SHORT_ROLE,
|
||||||
|
STATUS_COLOR_ROLE,
|
||||||
|
STATUS_ICON_ROLE,
|
||||||
|
)
|
||||||
|
combo_view.setItemDelegateForColumn(1, status_delegate)
|
||||||
|
|
||||||
|
self._combo_model = combo_model
|
||||||
|
self._combo_view = combo_view
|
||||||
|
self._status_delegate = status_delegate
|
||||||
|
self._items_by_id = {}
|
||||||
|
|
||||||
|
def paintEvent(self, event):
|
||||||
|
painter = QtWidgets.QStylePainter(self)
|
||||||
|
option = QtWidgets.QStyleOptionComboBox()
|
||||||
|
self.initStyleOption(option)
|
||||||
|
painter.drawComplexControl(QtWidgets.QStyle.CC_ComboBox, option)
|
||||||
|
idx = self.currentIndex()
|
||||||
|
status_name = self.itemData(idx, STATUS_NAME_ROLE)
|
||||||
|
if status_name is None:
|
||||||
|
painter.drawControl(QtWidgets.QStyle.CE_ComboBoxLabel, option)
|
||||||
|
return
|
||||||
|
|
||||||
|
painter.save()
|
||||||
|
text_field_rect = self.style().subControlRect(
|
||||||
|
QtWidgets.QStyle.CC_ComboBox,
|
||||||
|
option,
|
||||||
|
QtWidgets.QStyle.SC_ComboBoxEditField
|
||||||
|
)
|
||||||
|
adj_rect = text_field_rect.adjusted(1, 0, -1, 0)
|
||||||
|
painter.drawText(
|
||||||
|
adj_rect,
|
||||||
|
QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter,
|
||||||
|
option.currentText
|
||||||
|
)
|
||||||
|
metrics = QtGui.QFontMetrics(self.font())
|
||||||
|
text_width = metrics.width(option.currentText)
|
||||||
|
x_offset = text_width + 2
|
||||||
|
diff_width = adj_rect.width() - x_offset
|
||||||
|
if diff_width <= 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
status_rect = adj_rect.adjusted(x_offset + 2, 0, 0, 0)
|
||||||
|
if diff_width < metrics.width(status_name):
|
||||||
|
status_name = self.itemData(idx, STATUS_SHORT_ROLE)
|
||||||
|
|
||||||
|
color = QtGui.QColor(self.itemData(idx, STATUS_COLOR_ROLE))
|
||||||
|
|
||||||
|
pen = painter.pen()
|
||||||
|
pen.setColor(color)
|
||||||
|
painter.setPen(pen)
|
||||||
|
painter.drawText(
|
||||||
|
status_rect,
|
||||||
|
QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter,
|
||||||
|
status_name
|
||||||
|
)
|
||||||
|
|
||||||
|
def set_current_index(self, index):
|
||||||
|
model = self._combo_view.model()
|
||||||
|
if index > model.rowCount():
|
||||||
|
return
|
||||||
|
|
||||||
|
self.setCurrentIndex(index)
|
||||||
|
|
||||||
|
def get_item_by_id(self, item_id):
|
||||||
|
return self._items_by_id[item_id]
|
||||||
|
|
||||||
|
def set_versions(self, version_options):
|
||||||
|
self._items_by_id = {}
|
||||||
|
model = self._combo_model
|
||||||
|
root_item = model.invisibleRootItem()
|
||||||
|
root_item.removeRows(0, root_item.rowCount())
|
||||||
|
|
||||||
|
new_items = []
|
||||||
|
for version_option in version_options:
|
||||||
|
item_id = uuid.uuid4().hex
|
||||||
|
item = QtGui.QStandardItem(version_option.label)
|
||||||
|
item.setColumnCount(root_item.columnCount())
|
||||||
|
item.setData(
|
||||||
|
version_option.status_name, STATUS_NAME_ROLE
|
||||||
|
)
|
||||||
|
item.setData(
|
||||||
|
version_option.status_short, STATUS_SHORT_ROLE
|
||||||
|
)
|
||||||
|
item.setData(
|
||||||
|
version_option.status_color, STATUS_COLOR_ROLE
|
||||||
|
)
|
||||||
|
item.setData(item_id, ITEM_ID_ROLE)
|
||||||
|
|
||||||
|
new_items.append(item)
|
||||||
|
self._items_by_id[item_id] = version_option
|
||||||
|
|
||||||
|
if new_items:
|
||||||
|
root_item.appendRows(new_items)
|
||||||
|
|
||||||
|
|
||||||
|
class SelectVersionDialog(QtWidgets.QDialog):
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent=parent)
|
||||||
|
|
||||||
|
self.setWindowTitle("Select version")
|
||||||
|
|
||||||
|
label_widget = QtWidgets.QLabel("Set version number to", self)
|
||||||
|
versions_combobox = SelectVersionComboBox(self)
|
||||||
|
|
||||||
|
btns_widget = QtWidgets.QWidget(self)
|
||||||
|
|
||||||
|
confirm_btn = QtWidgets.QPushButton("OK", btns_widget)
|
||||||
|
cancel_btn = QtWidgets.QPushButton("Cancel", btns_widget)
|
||||||
|
|
||||||
|
btns_layout = QtWidgets.QHBoxLayout(btns_widget)
|
||||||
|
btns_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
btns_layout.addStretch(1)
|
||||||
|
btns_layout.addWidget(confirm_btn, 0)
|
||||||
|
btns_layout.addWidget(cancel_btn, 0)
|
||||||
|
|
||||||
|
main_layout = QtWidgets.QVBoxLayout(self)
|
||||||
|
main_layout.addWidget(label_widget, 0)
|
||||||
|
main_layout.addWidget(versions_combobox, 0)
|
||||||
|
main_layout.addWidget(btns_widget, 0)
|
||||||
|
|
||||||
|
confirm_btn.clicked.connect(self._on_confirm)
|
||||||
|
cancel_btn.clicked.connect(self._on_cancel)
|
||||||
|
|
||||||
|
self._selected_item = None
|
||||||
|
self._cancelled = False
|
||||||
|
self._versions_combobox = versions_combobox
|
||||||
|
|
||||||
|
def get_selected_item(self):
|
||||||
|
if self._cancelled:
|
||||||
|
return None
|
||||||
|
return self._selected_item
|
||||||
|
|
||||||
|
def set_versions(self, version_options):
|
||||||
|
self._versions_combobox.set_versions(version_options)
|
||||||
|
|
||||||
|
def select_index(self, index):
|
||||||
|
self._versions_combobox.set_current_index(index)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def ask_for_version(cls, version_options, index=None, parent=None):
|
||||||
|
dialog = cls(parent)
|
||||||
|
dialog.set_versions(version_options)
|
||||||
|
if index is not None:
|
||||||
|
dialog.select_index(index)
|
||||||
|
dialog.exec_()
|
||||||
|
return dialog.get_selected_item()
|
||||||
|
|
||||||
|
def _on_confirm(self):
|
||||||
|
self._cancelled = False
|
||||||
|
index = self._versions_combobox.currentIndex()
|
||||||
|
item_id = self._versions_combobox.itemData(index, ITEM_ID_ROLE)
|
||||||
|
self._selected_item = self._versions_combobox.get_item_by_id(item_id)
|
||||||
|
self.accept()
|
||||||
|
|
||||||
|
def _on_cancel(self):
|
||||||
|
self._cancelled = True
|
||||||
|
self.reject()
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -2,17 +2,10 @@ from qtpy import QtWidgets, QtCore, QtGui
|
||||||
import qtawesome
|
import qtawesome
|
||||||
|
|
||||||
from ayon_core import style, resources
|
from ayon_core import style, resources
|
||||||
from ayon_core.tools.utils.lib import (
|
from ayon_core.tools.utils import PlaceholderLineEdit
|
||||||
preserve_expanded_rows,
|
|
||||||
preserve_selection,
|
|
||||||
)
|
|
||||||
from ayon_core.tools.sceneinventory import SceneInventoryController
|
from ayon_core.tools.sceneinventory import SceneInventoryController
|
||||||
|
|
||||||
from .delegates import VersionDelegate
|
|
||||||
from .model import (
|
|
||||||
InventoryModel,
|
|
||||||
FilterProxyModel
|
|
||||||
)
|
|
||||||
from .view import SceneInventoryView
|
from .view import SceneInventoryView
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -20,7 +13,7 @@ class SceneInventoryWindow(QtWidgets.QDialog):
|
||||||
"""Scene Inventory window"""
|
"""Scene Inventory window"""
|
||||||
|
|
||||||
def __init__(self, controller=None, parent=None):
|
def __init__(self, controller=None, parent=None):
|
||||||
super(SceneInventoryWindow, self).__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
||||||
if controller is None:
|
if controller is None:
|
||||||
controller = SceneInventoryController()
|
controller = SceneInventoryController()
|
||||||
|
|
@ -33,10 +26,9 @@ class SceneInventoryWindow(QtWidgets.QDialog):
|
||||||
|
|
||||||
self.resize(1100, 480)
|
self.resize(1100, 480)
|
||||||
|
|
||||||
# region control
|
|
||||||
|
|
||||||
filter_label = QtWidgets.QLabel("Search", self)
|
filter_label = QtWidgets.QLabel("Search", self)
|
||||||
text_filter = QtWidgets.QLineEdit(self)
|
text_filter = PlaceholderLineEdit(self)
|
||||||
|
text_filter.setPlaceholderText("Filter by name...")
|
||||||
|
|
||||||
outdated_only_checkbox = QtWidgets.QCheckBox(
|
outdated_only_checkbox = QtWidgets.QCheckBox(
|
||||||
"Filter to outdated", self
|
"Filter to outdated", self
|
||||||
|
|
@ -44,52 +36,30 @@ class SceneInventoryWindow(QtWidgets.QDialog):
|
||||||
outdated_only_checkbox.setToolTip("Show outdated files only")
|
outdated_only_checkbox.setToolTip("Show outdated files only")
|
||||||
outdated_only_checkbox.setChecked(False)
|
outdated_only_checkbox.setChecked(False)
|
||||||
|
|
||||||
icon = qtawesome.icon("fa.arrow-up", color="white")
|
update_all_icon = qtawesome.icon("fa.arrow-up", color="white")
|
||||||
update_all_button = QtWidgets.QPushButton(self)
|
update_all_button = QtWidgets.QPushButton(self)
|
||||||
update_all_button.setToolTip("Update all outdated to latest version")
|
update_all_button.setToolTip("Update all outdated to latest version")
|
||||||
update_all_button.setIcon(icon)
|
update_all_button.setIcon(update_all_icon)
|
||||||
|
|
||||||
icon = qtawesome.icon("fa.refresh", color="white")
|
refresh_icon = qtawesome.icon("fa.refresh", color="white")
|
||||||
refresh_button = QtWidgets.QPushButton(self)
|
refresh_button = QtWidgets.QPushButton(self)
|
||||||
refresh_button.setToolTip("Refresh")
|
refresh_button.setToolTip("Refresh")
|
||||||
refresh_button.setIcon(icon)
|
refresh_button.setIcon(refresh_icon)
|
||||||
|
|
||||||
control_layout = QtWidgets.QHBoxLayout()
|
headers_widget = QtWidgets.QWidget(self)
|
||||||
control_layout.addWidget(filter_label)
|
headers_layout = QtWidgets.QHBoxLayout(headers_widget)
|
||||||
control_layout.addWidget(text_filter)
|
headers_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
control_layout.addWidget(outdated_only_checkbox)
|
headers_layout.addWidget(filter_label, 0)
|
||||||
control_layout.addWidget(update_all_button)
|
headers_layout.addWidget(text_filter, 1)
|
||||||
control_layout.addWidget(refresh_button)
|
headers_layout.addWidget(outdated_only_checkbox, 0)
|
||||||
|
headers_layout.addWidget(update_all_button, 0)
|
||||||
model = InventoryModel(controller)
|
headers_layout.addWidget(refresh_button, 0)
|
||||||
proxy = FilterProxyModel()
|
|
||||||
proxy.setSourceModel(model)
|
|
||||||
proxy.setDynamicSortFilter(True)
|
|
||||||
proxy.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
|
|
||||||
|
|
||||||
view = SceneInventoryView(controller, self)
|
view = SceneInventoryView(controller, self)
|
||||||
view.setModel(proxy)
|
|
||||||
|
|
||||||
sync_enabled = controller.is_sitesync_enabled()
|
main_layout = QtWidgets.QVBoxLayout(self)
|
||||||
view.setColumnHidden(model.active_site_col, not sync_enabled)
|
main_layout.addWidget(headers_widget, 0)
|
||||||
view.setColumnHidden(model.remote_site_col, not sync_enabled)
|
main_layout.addWidget(view, 1)
|
||||||
|
|
||||||
# set some nice default widths for the view
|
|
||||||
view.setColumnWidth(0, 250) # name
|
|
||||||
view.setColumnWidth(1, 55) # version
|
|
||||||
view.setColumnWidth(2, 55) # count
|
|
||||||
view.setColumnWidth(3, 150) # product type
|
|
||||||
view.setColumnWidth(4, 120) # group
|
|
||||||
view.setColumnWidth(5, 150) # loader
|
|
||||||
|
|
||||||
# apply delegates
|
|
||||||
version_delegate = VersionDelegate(controller, self)
|
|
||||||
column = model.Columns.index("version")
|
|
||||||
view.setItemDelegateForColumn(column, version_delegate)
|
|
||||||
|
|
||||||
layout = QtWidgets.QVBoxLayout(self)
|
|
||||||
layout.addLayout(control_layout)
|
|
||||||
layout.addWidget(view)
|
|
||||||
|
|
||||||
show_timer = QtCore.QTimer()
|
show_timer = QtCore.QTimer()
|
||||||
show_timer.setInterval(0)
|
show_timer.setInterval(0)
|
||||||
|
|
@ -114,12 +84,8 @@ class SceneInventoryWindow(QtWidgets.QDialog):
|
||||||
self._update_all_button = update_all_button
|
self._update_all_button = update_all_button
|
||||||
self._outdated_only_checkbox = outdated_only_checkbox
|
self._outdated_only_checkbox = outdated_only_checkbox
|
||||||
self._view = view
|
self._view = view
|
||||||
self._model = model
|
|
||||||
self._proxy = proxy
|
|
||||||
self._version_delegate = version_delegate
|
|
||||||
|
|
||||||
self._first_show = True
|
self._first_show = True
|
||||||
self._first_refresh = True
|
|
||||||
|
|
||||||
def showEvent(self, event):
|
def showEvent(self, event):
|
||||||
super(SceneInventoryWindow, self).showEvent(event)
|
super(SceneInventoryWindow, self).showEvent(event)
|
||||||
|
|
@ -139,29 +105,16 @@ class SceneInventoryWindow(QtWidgets.QDialog):
|
||||||
whilst trying to name an instance.
|
whilst trying to name an instance.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
def _on_refresh_request(self):
|
def _on_refresh_request(self):
|
||||||
"""Signal callback to trigger 'refresh' without any arguments."""
|
"""Signal callback to trigger 'refresh' without any arguments."""
|
||||||
|
|
||||||
self.refresh()
|
self.refresh()
|
||||||
|
|
||||||
def refresh(self, containers=None):
|
def refresh(self):
|
||||||
self._first_refresh = False
|
|
||||||
self._controller.reset()
|
self._controller.reset()
|
||||||
with preserve_expanded_rows(
|
self._view.refresh()
|
||||||
tree_view=self._view,
|
|
||||||
role=self._model.UniqueRole
|
|
||||||
):
|
|
||||||
with preserve_selection(
|
|
||||||
tree_view=self._view,
|
|
||||||
role=self._model.UniqueRole,
|
|
||||||
current_index=False
|
|
||||||
):
|
|
||||||
kwargs = {"containers": containers}
|
|
||||||
# TODO do not touch view's inner attribute
|
|
||||||
if self._view._hierarchy_view:
|
|
||||||
kwargs["selected"] = self._view._selected
|
|
||||||
self._model.refresh(**kwargs)
|
|
||||||
|
|
||||||
def _on_show_timer(self):
|
def _on_show_timer(self):
|
||||||
if self._show_counter < 3:
|
if self._show_counter < 3:
|
||||||
|
|
@ -171,17 +124,13 @@ class SceneInventoryWindow(QtWidgets.QDialog):
|
||||||
self.refresh()
|
self.refresh()
|
||||||
|
|
||||||
def _on_hierarchy_view_change(self, enabled):
|
def _on_hierarchy_view_change(self, enabled):
|
||||||
self._proxy.set_hierarchy_view(enabled)
|
self._view.set_hierarchy_view(enabled)
|
||||||
self._model.set_hierarchy_view(enabled)
|
|
||||||
|
|
||||||
def _on_text_filter_change(self, text_filter):
|
def _on_text_filter_change(self, text_filter):
|
||||||
if hasattr(self._proxy, "setFilterRegExp"):
|
self._view.set_text_filter(text_filter)
|
||||||
self._proxy.setFilterRegExp(text_filter)
|
|
||||||
else:
|
|
||||||
self._proxy.setFilterRegularExpression(text_filter)
|
|
||||||
|
|
||||||
def _on_outdated_state_change(self):
|
def _on_outdated_state_change(self):
|
||||||
self._proxy.set_filter_outdated(
|
self._view.set_filter_outdated(
|
||||||
self._outdated_only_checkbox.isChecked()
|
self._outdated_only_checkbox.isChecked()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from qtpy import QtWidgets
|
from qtpy import QtWidgets, QtGui
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -106,3 +106,80 @@ class PrettyTimeDelegate(QtWidgets.QStyledItemDelegate):
|
||||||
def displayText(self, value, locale):
|
def displayText(self, value, locale):
|
||||||
if value is not None:
|
if value is not None:
|
||||||
return pretty_timestamp(value)
|
return pretty_timestamp(value)
|
||||||
|
|
||||||
|
|
||||||
|
class StatusDelegate(QtWidgets.QStyledItemDelegate):
|
||||||
|
"""Delegate showing status name and short name."""
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
status_name_role,
|
||||||
|
status_short_name_role,
|
||||||
|
status_color_role,
|
||||||
|
status_icon_role,
|
||||||
|
*args, **kwargs
|
||||||
|
):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.status_name_role = status_name_role
|
||||||
|
self.status_short_name_role = status_short_name_role
|
||||||
|
self.status_color_role = status_color_role
|
||||||
|
self.status_icon_role = status_icon_role
|
||||||
|
|
||||||
|
def paint(self, painter, option, index):
|
||||||
|
if option.widget:
|
||||||
|
style = option.widget.style()
|
||||||
|
else:
|
||||||
|
style = QtWidgets.QApplication.style()
|
||||||
|
|
||||||
|
style.drawControl(
|
||||||
|
QtWidgets.QCommonStyle.CE_ItemViewItem,
|
||||||
|
option,
|
||||||
|
painter,
|
||||||
|
option.widget
|
||||||
|
)
|
||||||
|
|
||||||
|
painter.save()
|
||||||
|
|
||||||
|
text_rect = style.subElementRect(
|
||||||
|
QtWidgets.QCommonStyle.SE_ItemViewItemText,
|
||||||
|
option
|
||||||
|
)
|
||||||
|
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)
|
||||||
|
text = self._get_status_name(index)
|
||||||
|
if padded_text_rect.width() < fm.width(text):
|
||||||
|
text = self._get_status_short_name(index)
|
||||||
|
|
||||||
|
fg_color = self._get_status_color(index)
|
||||||
|
pen = painter.pen()
|
||||||
|
pen.setColor(fg_color)
|
||||||
|
painter.setPen(pen)
|
||||||
|
|
||||||
|
painter.drawText(
|
||||||
|
padded_text_rect,
|
||||||
|
option.displayAlignment,
|
||||||
|
text
|
||||||
|
)
|
||||||
|
|
||||||
|
painter.restore()
|
||||||
|
|
||||||
|
def _get_status_name(self, index):
|
||||||
|
return index.data(self.status_name_role)
|
||||||
|
|
||||||
|
def _get_status_short_name(self, index):
|
||||||
|
return index.data(self.status_short_name_role)
|
||||||
|
|
||||||
|
def _get_status_color(self, index):
|
||||||
|
return QtGui.QColor(index.data(self.status_color_role))
|
||||||
|
|
||||||
|
def _get_status_icon(self, index):
|
||||||
|
if self.status_icon_role is not None:
|
||||||
|
return index.data(self.status_icon_role)
|
||||||
|
return None
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import contextlib
|
import contextlib
|
||||||
|
import collections
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from qtpy import QtWidgets, QtCore, QtGui
|
from qtpy import QtWidgets, QtCore, QtGui
|
||||||
|
|
@ -196,16 +197,16 @@ def get_openpype_qt_app():
|
||||||
return get_ayon_qt_app()
|
return get_ayon_qt_app()
|
||||||
|
|
||||||
|
|
||||||
def iter_model_rows(model, column, include_root=False):
|
def iter_model_rows(model, column=0, include_root=False):
|
||||||
"""Iterate over all row indices in a model"""
|
"""Iterate over all row indices in a model"""
|
||||||
indices = [QtCore.QModelIndex()] # start iteration at root
|
indexes_queue = collections.deque()
|
||||||
|
# start iteration at root
|
||||||
for index in indices:
|
indexes_queue.append(QtCore.QModelIndex())
|
||||||
|
while indexes_queue:
|
||||||
|
index = indexes_queue.popleft()
|
||||||
# Add children to the iterations
|
# Add children to the iterations
|
||||||
child_rows = model.rowCount(index)
|
for child_row in range(model.rowCount(index)):
|
||||||
for child_row in range(child_rows):
|
indexes_queue.append(model.index(child_row, column, index))
|
||||||
child_index = model.index(child_row, column, index)
|
|
||||||
indices.append(child_index)
|
|
||||||
|
|
||||||
if not include_root and not index.isValid():
|
if not include_root and not index.isValid():
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,10 @@ class WorkfileInfo:
|
||||||
task_id (str): Task id.
|
task_id (str): Task id.
|
||||||
filepath (str): Filepath.
|
filepath (str): Filepath.
|
||||||
filesize (int): File size.
|
filesize (int): File size.
|
||||||
creation_time (int): Creation time (timestamp).
|
creation_time (float): Creation time (timestamp).
|
||||||
modification_time (int): Modification time (timestamp).
|
modification_time (float): Modification time (timestamp).
|
||||||
|
created_by (Union[str, none]): User who created the file.
|
||||||
|
updated_by (Union[str, none]): User who last updated the file.
|
||||||
note (str): Note.
|
note (str): Note.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -26,6 +28,8 @@ class WorkfileInfo:
|
||||||
filesize,
|
filesize,
|
||||||
creation_time,
|
creation_time,
|
||||||
modification_time,
|
modification_time,
|
||||||
|
created_by,
|
||||||
|
updated_by,
|
||||||
note,
|
note,
|
||||||
):
|
):
|
||||||
self.folder_id = folder_id
|
self.folder_id = folder_id
|
||||||
|
|
@ -34,6 +38,8 @@ class WorkfileInfo:
|
||||||
self.filesize = filesize
|
self.filesize = filesize
|
||||||
self.creation_time = creation_time
|
self.creation_time = creation_time
|
||||||
self.modification_time = modification_time
|
self.modification_time = modification_time
|
||||||
|
self.created_by = created_by
|
||||||
|
self.updated_by = updated_by
|
||||||
self.note = note
|
self.note = note
|
||||||
|
|
||||||
def to_data(self):
|
def to_data(self):
|
||||||
|
|
@ -50,6 +56,8 @@ class WorkfileInfo:
|
||||||
"filesize": self.filesize,
|
"filesize": self.filesize,
|
||||||
"creation_time": self.creation_time,
|
"creation_time": self.creation_time,
|
||||||
"modification_time": self.modification_time,
|
"modification_time": self.modification_time,
|
||||||
|
"created_by": self.created_by,
|
||||||
|
"updated_by": self.updated_by,
|
||||||
"note": self.note,
|
"note": self.note,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -212,6 +220,7 @@ class FileItem:
|
||||||
dirpath (str): Directory path of file.
|
dirpath (str): Directory path of file.
|
||||||
filename (str): Filename.
|
filename (str): Filename.
|
||||||
modified (float): Modified timestamp.
|
modified (float): Modified timestamp.
|
||||||
|
created_by (Optional[str]): Username.
|
||||||
representation_id (Optional[str]): Representation id of published
|
representation_id (Optional[str]): Representation id of published
|
||||||
workfile.
|
workfile.
|
||||||
filepath (Optional[str]): Prepared filepath.
|
filepath (Optional[str]): Prepared filepath.
|
||||||
|
|
@ -223,6 +232,8 @@ class FileItem:
|
||||||
dirpath,
|
dirpath,
|
||||||
filename,
|
filename,
|
||||||
modified,
|
modified,
|
||||||
|
created_by=None,
|
||||||
|
updated_by=None,
|
||||||
representation_id=None,
|
representation_id=None,
|
||||||
filepath=None,
|
filepath=None,
|
||||||
exists=None
|
exists=None
|
||||||
|
|
@ -230,6 +241,8 @@ class FileItem:
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.dirpath = dirpath
|
self.dirpath = dirpath
|
||||||
self.modified = modified
|
self.modified = modified
|
||||||
|
self.created_by = created_by
|
||||||
|
self.updated_by = updated_by
|
||||||
self.representation_id = representation_id
|
self.representation_id = representation_id
|
||||||
self._filepath = filepath
|
self._filepath = filepath
|
||||||
self._exists = exists
|
self._exists = exists
|
||||||
|
|
@ -269,6 +282,7 @@ class FileItem:
|
||||||
"filename": self.filename,
|
"filename": self.filename,
|
||||||
"dirpath": self.dirpath,
|
"dirpath": self.dirpath,
|
||||||
"modified": self.modified,
|
"modified": self.modified,
|
||||||
|
"created_by": self.created_by,
|
||||||
"representation_id": self.representation_id,
|
"representation_id": self.representation_id,
|
||||||
"filepath": self.filepath,
|
"filepath": self.filepath,
|
||||||
"exists": self.exists,
|
"exists": self.exists,
|
||||||
|
|
@ -522,6 +536,16 @@ class AbstractWorkfilesFrontend(AbstractWorkfilesCommon):
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_user_items_by_name(self):
|
||||||
|
"""Get user items available on AYON server.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict[str, UserItem]: User items by username.
|
||||||
|
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
# Host information
|
# Host information
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_workfile_extensions(self):
|
def get_workfile_extensions(self):
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ from ayon_core.tools.common_models import (
|
||||||
HierarchyModel,
|
HierarchyModel,
|
||||||
HierarchyExpectedSelection,
|
HierarchyExpectedSelection,
|
||||||
ProjectsModel,
|
ProjectsModel,
|
||||||
|
UsersModel,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .abstract import (
|
from .abstract import (
|
||||||
|
|
@ -161,6 +162,7 @@ class BaseWorkfileController(
|
||||||
self._save_is_enabled = True
|
self._save_is_enabled = True
|
||||||
|
|
||||||
# Expected selected folder and task
|
# Expected selected folder and task
|
||||||
|
self._users_model = self._create_users_model()
|
||||||
self._expected_selection = self._create_expected_selection_obj()
|
self._expected_selection = self._create_expected_selection_obj()
|
||||||
self._selection_model = self._create_selection_model()
|
self._selection_model = self._create_selection_model()
|
||||||
self._projects_model = self._create_projects_model()
|
self._projects_model = self._create_projects_model()
|
||||||
|
|
@ -176,6 +178,12 @@ class BaseWorkfileController(
|
||||||
def is_host_valid(self):
|
def is_host_valid(self):
|
||||||
return self._host_is_valid
|
return self._host_is_valid
|
||||||
|
|
||||||
|
def _create_users_model(self):
|
||||||
|
return UsersModel(self)
|
||||||
|
|
||||||
|
def _create_workfiles_model(self):
|
||||||
|
return WorkfilesModel(self)
|
||||||
|
|
||||||
def _create_expected_selection_obj(self):
|
def _create_expected_selection_obj(self):
|
||||||
return WorkfilesToolExpectedSelection(self)
|
return WorkfilesToolExpectedSelection(self)
|
||||||
|
|
||||||
|
|
@ -188,9 +196,6 @@ class BaseWorkfileController(
|
||||||
def _create_hierarchy_model(self):
|
def _create_hierarchy_model(self):
|
||||||
return HierarchyModel(self)
|
return HierarchyModel(self)
|
||||||
|
|
||||||
def _create_workfiles_model(self):
|
|
||||||
return WorkfilesModel(self)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def event_system(self):
|
def event_system(self):
|
||||||
"""Inner event system for workfiles tool controller.
|
"""Inner event system for workfiles tool controller.
|
||||||
|
|
@ -272,6 +277,9 @@ class BaseWorkfileController(
|
||||||
{"enabled": enabled}
|
{"enabled": enabled}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_user_items_by_name(self):
|
||||||
|
return self._users_model.get_user_items_by_name()
|
||||||
|
|
||||||
# Host information
|
# Host information
|
||||||
def get_workfile_extensions(self):
|
def get_workfile_extensions(self):
|
||||||
host = self._host
|
host = self._host
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import arrow
|
||||||
import ayon_api
|
import ayon_api
|
||||||
from ayon_api.operations import OperationsSession
|
from ayon_api.operations import OperationsSession
|
||||||
|
|
||||||
|
from ayon_core.lib import get_ayon_username
|
||||||
from ayon_core.pipeline.template_data import (
|
from ayon_core.pipeline.template_data import (
|
||||||
get_template_data,
|
get_template_data,
|
||||||
get_task_template_data,
|
get_task_template_data,
|
||||||
|
|
@ -23,6 +24,8 @@ from ayon_core.tools.workfiles.abstract import (
|
||||||
WorkfileInfo,
|
WorkfileInfo,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
_NOT_SET = object()
|
||||||
|
|
||||||
|
|
||||||
class CommentMatcher(object):
|
class CommentMatcher(object):
|
||||||
"""Use anatomy and work file data to parse comments from filenames.
|
"""Use anatomy and work file data to parse comments from filenames.
|
||||||
|
|
@ -188,10 +191,17 @@ class WorkareaModel:
|
||||||
if ext not in self._extensions:
|
if ext not in self._extensions:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
modified = os.path.getmtime(filepath)
|
workfile_info = self._controller.get_workfile_info(
|
||||||
items.append(
|
folder_id, task_id, filepath
|
||||||
FileItem(workdir, filename, modified)
|
|
||||||
)
|
)
|
||||||
|
modified = os.path.getmtime(filepath)
|
||||||
|
items.append(FileItem(
|
||||||
|
workdir,
|
||||||
|
filename,
|
||||||
|
modified,
|
||||||
|
workfile_info.created_by,
|
||||||
|
workfile_info.updated_by,
|
||||||
|
))
|
||||||
return items
|
return items
|
||||||
|
|
||||||
def _get_template_key(self, fill_data):
|
def _get_template_key(self, fill_data):
|
||||||
|
|
@ -439,6 +449,7 @@ class WorkfileEntitiesModel:
|
||||||
self._controller = controller
|
self._controller = controller
|
||||||
self._cache = {}
|
self._cache = {}
|
||||||
self._items = {}
|
self._items = {}
|
||||||
|
self._current_username = _NOT_SET
|
||||||
|
|
||||||
def _get_workfile_info_identifier(
|
def _get_workfile_info_identifier(
|
||||||
self, folder_id, task_id, rootless_path
|
self, folder_id, task_id, rootless_path
|
||||||
|
|
@ -459,8 +470,12 @@ class WorkfileEntitiesModel:
|
||||||
self, folder_id, task_id, workfile_info, filepath
|
self, folder_id, task_id, workfile_info, filepath
|
||||||
):
|
):
|
||||||
note = ""
|
note = ""
|
||||||
|
created_by = None
|
||||||
|
updated_by = None
|
||||||
if workfile_info:
|
if workfile_info:
|
||||||
note = workfile_info["attrib"].get("description") or ""
|
note = workfile_info["attrib"].get("description") or ""
|
||||||
|
created_by = workfile_info.get("createdBy")
|
||||||
|
updated_by = workfile_info.get("updatedBy")
|
||||||
|
|
||||||
filestat = os.stat(filepath)
|
filestat = os.stat(filepath)
|
||||||
return WorkfileInfo(
|
return WorkfileInfo(
|
||||||
|
|
@ -470,6 +485,8 @@ class WorkfileEntitiesModel:
|
||||||
filesize=filestat.st_size,
|
filesize=filestat.st_size,
|
||||||
creation_time=filestat.st_ctime,
|
creation_time=filestat.st_ctime,
|
||||||
modification_time=filestat.st_mtime,
|
modification_time=filestat.st_mtime,
|
||||||
|
created_by=created_by,
|
||||||
|
updated_by=updated_by,
|
||||||
note=note
|
note=note
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -481,7 +498,7 @@ class WorkfileEntitiesModel:
|
||||||
for workfile_info in ayon_api.get_workfiles_info(
|
for workfile_info in ayon_api.get_workfiles_info(
|
||||||
self._controller.get_current_project_name(),
|
self._controller.get_current_project_name(),
|
||||||
task_ids=[task_id],
|
task_ids=[task_id],
|
||||||
fields=["id", "path", "attrib"],
|
fields=["id", "path", "attrib", "createdBy", "updatedBy"],
|
||||||
):
|
):
|
||||||
workfile_identifier = self._get_workfile_info_identifier(
|
workfile_identifier = self._get_workfile_info_identifier(
|
||||||
folder_id, task_id, workfile_info["path"]
|
folder_id, task_id, workfile_info["path"]
|
||||||
|
|
@ -525,18 +542,32 @@ class WorkfileEntitiesModel:
|
||||||
self._items.pop(identifier, None)
|
self._items.pop(identifier, None)
|
||||||
return
|
return
|
||||||
|
|
||||||
if note is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
old_note = workfile_info.get("attrib", {}).get("note")
|
old_note = workfile_info.get("attrib", {}).get("note")
|
||||||
|
|
||||||
new_workfile_info = copy.deepcopy(workfile_info)
|
new_workfile_info = copy.deepcopy(workfile_info)
|
||||||
attrib = new_workfile_info.setdefault("attrib", {})
|
update_data = {}
|
||||||
attrib["description"] = note
|
if note is not None and old_note != note:
|
||||||
|
update_data["attrib"] = {"description": note}
|
||||||
|
attrib = new_workfile_info.setdefault("attrib", {})
|
||||||
|
attrib["description"] = note
|
||||||
|
|
||||||
|
username = self._get_current_username()
|
||||||
|
# Automatically fix 'createdBy' and 'updatedBy' fields
|
||||||
|
# NOTE both fields were not automatically filled by server
|
||||||
|
# until 1.1.3 release.
|
||||||
|
if workfile_info.get("createdBy") is None:
|
||||||
|
update_data["createdBy"] = username
|
||||||
|
new_workfile_info["createdBy"] = username
|
||||||
|
|
||||||
|
if workfile_info.get("updatedBy") != username:
|
||||||
|
update_data["updatedBy"] = username
|
||||||
|
new_workfile_info["updatedBy"] = username
|
||||||
|
|
||||||
|
if not update_data:
|
||||||
|
return
|
||||||
|
|
||||||
self._cache[identifier] = new_workfile_info
|
self._cache[identifier] = new_workfile_info
|
||||||
self._items.pop(identifier, None)
|
self._items.pop(identifier, None)
|
||||||
if old_note == note:
|
|
||||||
return
|
|
||||||
|
|
||||||
project_name = self._controller.get_current_project_name()
|
project_name = self._controller.get_current_project_name()
|
||||||
|
|
||||||
|
|
@ -545,7 +576,7 @@ class WorkfileEntitiesModel:
|
||||||
project_name,
|
project_name,
|
||||||
"workfile",
|
"workfile",
|
||||||
workfile_info["id"],
|
workfile_info["id"],
|
||||||
{"attrib": {"description": note}},
|
update_data,
|
||||||
)
|
)
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
|
|
@ -554,13 +585,18 @@ class WorkfileEntitiesModel:
|
||||||
|
|
||||||
project_name = self._controller.get_current_project_name()
|
project_name = self._controller.get_current_project_name()
|
||||||
|
|
||||||
|
username = self._get_current_username()
|
||||||
workfile_info = {
|
workfile_info = {
|
||||||
"path": rootless_path,
|
"path": rootless_path,
|
||||||
"taskId": task_id,
|
"taskId": task_id,
|
||||||
"attrib": {
|
"attrib": {
|
||||||
"extension": extension,
|
"extension": extension,
|
||||||
"description": note
|
"description": note
|
||||||
}
|
},
|
||||||
|
# TODO remove 'createdBy' and 'updatedBy' fields when server is
|
||||||
|
# or above 1.1.3 .
|
||||||
|
"createdBy": username,
|
||||||
|
"updatedBy": username,
|
||||||
}
|
}
|
||||||
|
|
||||||
session = OperationsSession()
|
session = OperationsSession()
|
||||||
|
|
@ -568,6 +604,11 @@ class WorkfileEntitiesModel:
|
||||||
session.commit()
|
session.commit()
|
||||||
return workfile_info
|
return workfile_info
|
||||||
|
|
||||||
|
def _get_current_username(self):
|
||||||
|
if self._current_username is _NOT_SET:
|
||||||
|
self._current_username = get_ayon_username()
|
||||||
|
return self._current_username
|
||||||
|
|
||||||
|
|
||||||
class PublishWorkfilesModel:
|
class PublishWorkfilesModel:
|
||||||
"""Model for handling of published workfiles.
|
"""Model for handling of published workfiles.
|
||||||
|
|
@ -599,7 +640,7 @@ class PublishWorkfilesModel:
|
||||||
return self._cached_repre_extensions
|
return self._cached_repre_extensions
|
||||||
|
|
||||||
def _file_item_from_representation(
|
def _file_item_from_representation(
|
||||||
self, repre_entity, project_anatomy, task_name=None
|
self, repre_entity, project_anatomy, author, task_name=None
|
||||||
):
|
):
|
||||||
if task_name is not None:
|
if task_name is not None:
|
||||||
task_info = repre_entity["context"].get("task")
|
task_info = repre_entity["context"].get("task")
|
||||||
|
|
@ -634,6 +675,8 @@ class PublishWorkfilesModel:
|
||||||
dirpath,
|
dirpath,
|
||||||
filename,
|
filename,
|
||||||
created_at.float_timestamp,
|
created_at.float_timestamp,
|
||||||
|
author,
|
||||||
|
None,
|
||||||
repre_entity["id"]
|
repre_entity["id"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -643,9 +686,9 @@ class PublishWorkfilesModel:
|
||||||
# Get subset docs of folder
|
# Get subset docs of folder
|
||||||
product_entities = ayon_api.get_products(
|
product_entities = ayon_api.get_products(
|
||||||
project_name,
|
project_name,
|
||||||
folder_ids=[folder_id],
|
folder_ids={folder_id},
|
||||||
product_types=["workfile"],
|
product_types={"workfile"},
|
||||||
fields=["id", "name"]
|
fields={"id", "name"}
|
||||||
)
|
)
|
||||||
|
|
||||||
output = []
|
output = []
|
||||||
|
|
@ -657,25 +700,33 @@ class PublishWorkfilesModel:
|
||||||
version_entities = ayon_api.get_versions(
|
version_entities = ayon_api.get_versions(
|
||||||
project_name,
|
project_name,
|
||||||
product_ids=product_ids,
|
product_ids=product_ids,
|
||||||
fields=["id", "productId"]
|
fields={"id", "author"}
|
||||||
)
|
)
|
||||||
version_ids = {version["id"] for version in version_entities}
|
versions_by_id = {
|
||||||
if not version_ids:
|
version["id"]: version
|
||||||
|
for version in version_entities
|
||||||
|
}
|
||||||
|
if not versions_by_id:
|
||||||
return output
|
return output
|
||||||
|
|
||||||
# Query representations of filtered versions and add filter for
|
# Query representations of filtered versions and add filter for
|
||||||
# extension
|
# extension
|
||||||
repre_entities = ayon_api.get_representations(
|
repre_entities = ayon_api.get_representations(
|
||||||
project_name,
|
project_name,
|
||||||
version_ids=version_ids
|
version_ids=set(versions_by_id)
|
||||||
)
|
)
|
||||||
project_anatomy = self._controller.project_anatomy
|
project_anatomy = self._controller.project_anatomy
|
||||||
|
|
||||||
# Filter queried representations by task name if task is set
|
# Filter queried representations by task name if task is set
|
||||||
file_items = []
|
file_items = []
|
||||||
for repre_entity in repre_entities:
|
for repre_entity in repre_entities:
|
||||||
|
version_id = repre_entity["versionId"]
|
||||||
|
version_entity = versions_by_id[version_id]
|
||||||
file_item = self._file_item_from_representation(
|
file_item = self._file_item_from_representation(
|
||||||
repre_entity, project_anatomy, task_name
|
repre_entity,
|
||||||
|
project_anatomy,
|
||||||
|
version_entity["author"],
|
||||||
|
task_name,
|
||||||
)
|
)
|
||||||
if file_item is not None:
|
if file_item is not None:
|
||||||
file_items.append(file_item)
|
file_items.append(file_item)
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,8 @@ 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
|
||||||
DATE_MODIFIED_ROLE = QtCore.Qt.UserRole + 3
|
AUTHOR_ROLE = QtCore.Qt.UserRole + 3
|
||||||
|
DATE_MODIFIED_ROLE = QtCore.Qt.UserRole + 4
|
||||||
|
|
||||||
|
|
||||||
class PublishedFilesModel(QtGui.QStandardItemModel):
|
class PublishedFilesModel(QtGui.QStandardItemModel):
|
||||||
|
|
@ -23,13 +24,19 @@ class PublishedFilesModel(QtGui.QStandardItemModel):
|
||||||
controller (AbstractWorkfilesFrontend): The control object.
|
controller (AbstractWorkfilesFrontend): The control object.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
columns = [
|
||||||
|
"Name",
|
||||||
|
"Author",
|
||||||
|
"Date Modified",
|
||||||
|
]
|
||||||
|
date_modified_col = columns.index("Date Modified")
|
||||||
|
|
||||||
def __init__(self, controller):
|
def __init__(self, controller):
|
||||||
super(PublishedFilesModel, self).__init__()
|
super(PublishedFilesModel, self).__init__()
|
||||||
|
|
||||||
self.setColumnCount(2)
|
self.setColumnCount(len(self.columns))
|
||||||
|
for idx, label in enumerate(self.columns):
|
||||||
self.setHeaderData(0, QtCore.Qt.Horizontal, "Name")
|
self.setHeaderData(idx, QtCore.Qt.Horizontal, label)
|
||||||
self.setHeaderData(1, QtCore.Qt.Horizontal, "Date Modified")
|
|
||||||
|
|
||||||
controller.register_event_callback(
|
controller.register_event_callback(
|
||||||
"selection.task.changed",
|
"selection.task.changed",
|
||||||
|
|
@ -185,6 +192,8 @@ class PublishedFilesModel(QtGui.QStandardItemModel):
|
||||||
self._remove_empty_item()
|
self._remove_empty_item()
|
||||||
self._remove_missing_context_item()
|
self._remove_missing_context_item()
|
||||||
|
|
||||||
|
user_items_by_name = self._controller.get_user_items_by_name()
|
||||||
|
|
||||||
items_to_remove = set(self._items_by_id.keys())
|
items_to_remove = set(self._items_by_id.keys())
|
||||||
new_items = []
|
new_items = []
|
||||||
for file_item in file_items:
|
for file_item in file_items:
|
||||||
|
|
@ -205,8 +214,15 @@ class PublishedFilesModel(QtGui.QStandardItemModel):
|
||||||
else:
|
else:
|
||||||
flags = QtCore.Qt.NoItemFlags
|
flags = QtCore.Qt.NoItemFlags
|
||||||
|
|
||||||
|
author = file_item.created_by
|
||||||
|
user_item = user_items_by_name.get(author)
|
||||||
|
if user_item is not None and user_item.full_name:
|
||||||
|
author = user_item.full_name
|
||||||
|
|
||||||
item.setFlags(flags)
|
item.setFlags(flags)
|
||||||
|
|
||||||
item.setData(file_item.filepath, FILEPATH_ROLE)
|
item.setData(file_item.filepath, FILEPATH_ROLE)
|
||||||
|
item.setData(author, AUTHOR_ROLE)
|
||||||
item.setData(file_item.modified, DATE_MODIFIED_ROLE)
|
item.setData(file_item.modified, DATE_MODIFIED_ROLE)
|
||||||
|
|
||||||
self._items_by_id[repre_id] = item
|
self._items_by_id[repre_id] = item
|
||||||
|
|
@ -225,22 +241,30 @@ class PublishedFilesModel(QtGui.QStandardItemModel):
|
||||||
# Use flags of first column for all columns
|
# Use flags of first column for all columns
|
||||||
if index.column() != 0:
|
if index.column() != 0:
|
||||||
index = self.index(index.row(), 0, index.parent())
|
index = self.index(index.row(), 0, index.parent())
|
||||||
return super(PublishedFilesModel, self).flags(index)
|
return super().flags(index)
|
||||||
|
|
||||||
def data(self, index, role=None):
|
def data(self, index, role=None):
|
||||||
if role is None:
|
if role is None:
|
||||||
role = QtCore.Qt.DisplayRole
|
role = QtCore.Qt.DisplayRole
|
||||||
|
|
||||||
# Handle roles for first column
|
# Handle roles for first column
|
||||||
if index.column() == 1:
|
col = index.column()
|
||||||
if role == QtCore.Qt.DecorationRole:
|
if col != 1:
|
||||||
return None
|
return super().data(index, role)
|
||||||
|
|
||||||
if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole):
|
if role == QtCore.Qt.DecorationRole:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole):
|
||||||
|
if col == 1:
|
||||||
|
role = AUTHOR_ROLE
|
||||||
|
elif col == 2:
|
||||||
role = DATE_MODIFIED_ROLE
|
role = DATE_MODIFIED_ROLE
|
||||||
index = self.index(index.row(), 0, index.parent())
|
else:
|
||||||
|
return None
|
||||||
|
index = self.index(index.row(), 0, index.parent())
|
||||||
|
|
||||||
return super(PublishedFilesModel, self).data(index, role)
|
return super().data(index, role)
|
||||||
|
|
||||||
|
|
||||||
class SelectContextOverlay(BaseOverlayFrame):
|
class SelectContextOverlay(BaseOverlayFrame):
|
||||||
|
|
@ -295,7 +319,7 @@ class PublishedFilesWidget(QtWidgets.QWidget):
|
||||||
view.setModel(proxy_model)
|
view.setModel(proxy_model)
|
||||||
|
|
||||||
time_delegate = PrettyTimeDelegate()
|
time_delegate = PrettyTimeDelegate()
|
||||||
view.setItemDelegateForColumn(1, time_delegate)
|
view.setItemDelegateForColumn(model.date_modified_col, time_delegate)
|
||||||
|
|
||||||
# Default to a wider first filename column it is what we mostly care
|
# Default to a wider first filename column it is what we mostly care
|
||||||
# about and the date modified is relatively small anyway.
|
# about and the date modified is relatively small anyway.
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ from ayon_core.tools.utils.delegates import PrettyTimeDelegate
|
||||||
|
|
||||||
FILENAME_ROLE = QtCore.Qt.UserRole + 1
|
FILENAME_ROLE = QtCore.Qt.UserRole + 1
|
||||||
FILEPATH_ROLE = QtCore.Qt.UserRole + 2
|
FILEPATH_ROLE = QtCore.Qt.UserRole + 2
|
||||||
DATE_MODIFIED_ROLE = QtCore.Qt.UserRole + 3
|
AUTHOR_ROLE = QtCore.Qt.UserRole + 3
|
||||||
|
DATE_MODIFIED_ROLE = QtCore.Qt.UserRole + 4
|
||||||
|
|
||||||
|
|
||||||
class WorkAreaFilesModel(QtGui.QStandardItemModel):
|
class WorkAreaFilesModel(QtGui.QStandardItemModel):
|
||||||
|
|
@ -21,14 +22,20 @@ class WorkAreaFilesModel(QtGui.QStandardItemModel):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
refreshed = QtCore.Signal()
|
refreshed = QtCore.Signal()
|
||||||
|
columns = [
|
||||||
|
"Name",
|
||||||
|
"Author",
|
||||||
|
"Date Modified",
|
||||||
|
]
|
||||||
|
date_modified_col = columns.index("Date Modified")
|
||||||
|
|
||||||
def __init__(self, controller):
|
def __init__(self, controller):
|
||||||
super(WorkAreaFilesModel, self).__init__()
|
super(WorkAreaFilesModel, self).__init__()
|
||||||
|
|
||||||
self.setColumnCount(2)
|
self.setColumnCount(len(self.columns))
|
||||||
|
|
||||||
self.setHeaderData(0, QtCore.Qt.Horizontal, "Name")
|
for idx, label in enumerate(self.columns):
|
||||||
self.setHeaderData(1, QtCore.Qt.Horizontal, "Date Modified")
|
self.setHeaderData(idx, QtCore.Qt.Horizontal, label)
|
||||||
|
|
||||||
controller.register_event_callback(
|
controller.register_event_callback(
|
||||||
"selection.folder.changed",
|
"selection.folder.changed",
|
||||||
|
|
@ -186,6 +193,7 @@ class WorkAreaFilesModel(QtGui.QStandardItemModel):
|
||||||
return
|
return
|
||||||
self._remove_empty_item()
|
self._remove_empty_item()
|
||||||
self._remove_missing_context_item()
|
self._remove_missing_context_item()
|
||||||
|
user_items_by_name = self._controller.get_user_items_by_name()
|
||||||
|
|
||||||
items_to_remove = set(self._items_by_filename.keys())
|
items_to_remove = set(self._items_by_filename.keys())
|
||||||
new_items = []
|
new_items = []
|
||||||
|
|
@ -205,7 +213,13 @@ class WorkAreaFilesModel(QtGui.QStandardItemModel):
|
||||||
item.setData(file_item.filename, QtCore.Qt.DisplayRole)
|
item.setData(file_item.filename, QtCore.Qt.DisplayRole)
|
||||||
item.setData(file_item.filename, FILENAME_ROLE)
|
item.setData(file_item.filename, FILENAME_ROLE)
|
||||||
|
|
||||||
|
updated_by = file_item.updated_by
|
||||||
|
user_item = user_items_by_name.get(updated_by)
|
||||||
|
if user_item is not None and user_item.full_name:
|
||||||
|
updated_by = user_item.full_name
|
||||||
|
|
||||||
item.setData(file_item.filepath, FILEPATH_ROLE)
|
item.setData(file_item.filepath, FILEPATH_ROLE)
|
||||||
|
item.setData(updated_by, AUTHOR_ROLE)
|
||||||
item.setData(file_item.modified, DATE_MODIFIED_ROLE)
|
item.setData(file_item.modified, DATE_MODIFIED_ROLE)
|
||||||
|
|
||||||
self._items_by_filename[file_item.filename] = item
|
self._items_by_filename[file_item.filename] = item
|
||||||
|
|
@ -224,22 +238,30 @@ class WorkAreaFilesModel(QtGui.QStandardItemModel):
|
||||||
# Use flags of first column for all columns
|
# Use flags of first column for all columns
|
||||||
if index.column() != 0:
|
if index.column() != 0:
|
||||||
index = self.index(index.row(), 0, index.parent())
|
index = self.index(index.row(), 0, index.parent())
|
||||||
return super(WorkAreaFilesModel, self).flags(index)
|
return super().flags(index)
|
||||||
|
|
||||||
def data(self, index, role=None):
|
def data(self, index, role=None):
|
||||||
if role is None:
|
if role is None:
|
||||||
role = QtCore.Qt.DisplayRole
|
role = QtCore.Qt.DisplayRole
|
||||||
|
|
||||||
# Handle roles for first column
|
# Handle roles for first column
|
||||||
if index.column() == 1:
|
col = index.column()
|
||||||
if role == QtCore.Qt.DecorationRole:
|
if col == 0:
|
||||||
return None
|
return super().data(index, role)
|
||||||
|
|
||||||
if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole):
|
if role == QtCore.Qt.DecorationRole:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole):
|
||||||
|
if col == 1:
|
||||||
|
role = AUTHOR_ROLE
|
||||||
|
elif col == 2:
|
||||||
role = DATE_MODIFIED_ROLE
|
role = DATE_MODIFIED_ROLE
|
||||||
index = self.index(index.row(), 0, index.parent())
|
else:
|
||||||
|
return None
|
||||||
|
index = self.index(index.row(), 0, index.parent())
|
||||||
|
|
||||||
return super(WorkAreaFilesModel, self).data(index, role)
|
return super().data(index, role)
|
||||||
|
|
||||||
def set_published_mode(self, published_mode):
|
def set_published_mode(self, published_mode):
|
||||||
if self._published_mode == published_mode:
|
if self._published_mode == published_mode:
|
||||||
|
|
@ -279,7 +301,7 @@ class WorkAreaFilesWidget(QtWidgets.QWidget):
|
||||||
view.setModel(proxy_model)
|
view.setModel(proxy_model)
|
||||||
|
|
||||||
time_delegate = PrettyTimeDelegate()
|
time_delegate = PrettyTimeDelegate()
|
||||||
view.setItemDelegateForColumn(1, time_delegate)
|
view.setItemDelegateForColumn(model.date_modified_col, time_delegate)
|
||||||
|
|
||||||
# Default to a wider first filename column it is what we mostly care
|
# Default to a wider first filename column it is what we mostly care
|
||||||
# about and the date modified is relatively small anyway.
|
# about and the date modified is relatively small anyway.
|
||||||
|
|
|
||||||
|
|
@ -147,13 +147,38 @@ class SidePanelWidget(QtWidgets.QWidget):
|
||||||
workfile_info.creation_time)
|
workfile_info.creation_time)
|
||||||
modification_time = datetime.datetime.fromtimestamp(
|
modification_time = datetime.datetime.fromtimestamp(
|
||||||
workfile_info.modification_time)
|
workfile_info.modification_time)
|
||||||
|
|
||||||
|
user_items_by_name = self._controller.get_user_items_by_name()
|
||||||
|
|
||||||
|
def convert_username(username):
|
||||||
|
user_item = user_items_by_name.get(username)
|
||||||
|
if user_item is not None and user_item.full_name:
|
||||||
|
return user_item.full_name
|
||||||
|
return username
|
||||||
|
|
||||||
|
created_lines = [
|
||||||
|
creation_time.strftime(datetime_format)
|
||||||
|
]
|
||||||
|
if workfile_info.created_by:
|
||||||
|
created_lines.insert(
|
||||||
|
0, convert_username(workfile_info.created_by)
|
||||||
|
)
|
||||||
|
|
||||||
|
modified_lines = [
|
||||||
|
modification_time.strftime(datetime_format)
|
||||||
|
]
|
||||||
|
if workfile_info.updated_by:
|
||||||
|
modified_lines.insert(
|
||||||
|
0, convert_username(workfile_info.updated_by)
|
||||||
|
)
|
||||||
|
|
||||||
lines = (
|
lines = (
|
||||||
"<b>Size:</b>",
|
"<b>Size:</b>",
|
||||||
size_value,
|
size_value,
|
||||||
"<b>Created:</b>",
|
"<b>Created:</b>",
|
||||||
creation_time.strftime(datetime_format),
|
"<br/>".join(created_lines),
|
||||||
"<b>Modified:</b>",
|
"<b>Modified:</b>",
|
||||||
modification_time.strftime(datetime_format)
|
"<br/>".join(modified_lines),
|
||||||
)
|
)
|
||||||
self._orig_note = note
|
self._orig_note = note
|
||||||
self._note_input.setPlainText(note)
|
self._note_input.setPlainText(note)
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,7 @@ class WorkfilesToolWindow(QtWidgets.QWidget):
|
||||||
split_widget.addWidget(tasks_widget)
|
split_widget.addWidget(tasks_widget)
|
||||||
split_widget.addWidget(col_3_widget)
|
split_widget.addWidget(col_3_widget)
|
||||||
split_widget.addWidget(side_panel)
|
split_widget.addWidget(side_panel)
|
||||||
split_widget.setSizes([255, 160, 455, 175])
|
split_widget.setSizes([255, 175, 550, 190])
|
||||||
|
|
||||||
body_layout.addWidget(split_widget)
|
body_layout.addWidget(split_widget)
|
||||||
|
|
||||||
|
|
@ -169,7 +169,7 @@ class WorkfilesToolWindow(QtWidgets.QWidget):
|
||||||
# Force focus on the open button by default, required for Houdini.
|
# Force focus on the open button by default, required for Houdini.
|
||||||
self._files_widget.setFocus()
|
self._files_widget.setFocus()
|
||||||
|
|
||||||
self.resize(1200, 600)
|
self.resize(1260, 600)
|
||||||
|
|
||||||
def _create_col_1_widget(self, controller, parent):
|
def _create_col_1_widget(self, controller, parent):
|
||||||
col_widget = QtWidgets.QWidget(parent)
|
col_widget = QtWidgets.QWidget(parent)
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,10 @@ line-ending = "auto"
|
||||||
# Ignore words that are not in the dictionary.
|
# Ignore words that are not in the dictionary.
|
||||||
ignore-words-list = "ayon,ynput,parms,parm,hda,developpement,ue"
|
ignore-words-list = "ayon,ynput,parms,parm,hda,developpement,ue"
|
||||||
|
|
||||||
|
# Ignore lines that contain this regex. This is hack for missing inline ignore.
|
||||||
|
# Remove with next codespell release (>2.2.6)
|
||||||
|
ignore-regex = ".*codespell:ignore.*"
|
||||||
|
|
||||||
skip = "./.*,./package/*,*/vendor/*,*/unreal/integration/*,*/aftereffects/api/extension/js/libs/*"
|
skip = "./.*,./package/*,*/vendor/*,*/unreal/integration/*,*/aftereffects/api/extension/js/libs/*"
|
||||||
count = true
|
count = true
|
||||||
quiet-level = 3
|
quiet-level = 3
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,11 @@ from typing import Any
|
||||||
|
|
||||||
from ayon_server.addons import BaseServerAddon
|
from ayon_server.addons import BaseServerAddon
|
||||||
|
|
||||||
from .settings import CoreSettings, DEFAULT_VALUES
|
from .settings import (
|
||||||
|
CoreSettings,
|
||||||
|
DEFAULT_VALUES,
|
||||||
|
convert_settings_overrides,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CoreAddon(BaseServerAddon):
|
class CoreAddon(BaseServerAddon):
|
||||||
|
|
@ -17,47 +21,8 @@ class CoreAddon(BaseServerAddon):
|
||||||
source_version: str,
|
source_version: str,
|
||||||
overrides: dict[str, Any],
|
overrides: dict[str, Any],
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
self._convert_imagio_configs_0_3_1(overrides)
|
convert_settings_overrides(source_version, overrides)
|
||||||
# Use super conversion
|
# Use super conversion
|
||||||
return await super().convert_settings_overrides(
|
return await super().convert_settings_overrides(
|
||||||
source_version, overrides
|
source_version, overrides
|
||||||
)
|
)
|
||||||
|
|
||||||
def _convert_imagio_configs_0_3_1(self, overrides):
|
|
||||||
"""Imageio config settings did change to profiles since 0.3.1. ."""
|
|
||||||
imageio_overrides = overrides.get("imageio") or {}
|
|
||||||
if (
|
|
||||||
"ocio_config" not in imageio_overrides
|
|
||||||
or "filepath" not in imageio_overrides["ocio_config"]
|
|
||||||
):
|
|
||||||
return
|
|
||||||
|
|
||||||
ocio_config = imageio_overrides.pop("ocio_config")
|
|
||||||
|
|
||||||
filepath = ocio_config["filepath"]
|
|
||||||
if not filepath:
|
|
||||||
return
|
|
||||||
first_filepath = filepath[0]
|
|
||||||
ocio_config_profiles = imageio_overrides.setdefault(
|
|
||||||
"ocio_config_profiles", []
|
|
||||||
)
|
|
||||||
base_value = {
|
|
||||||
"type": "builtin_path",
|
|
||||||
"product_name": "",
|
|
||||||
"host_names": [],
|
|
||||||
"task_names": [],
|
|
||||||
"task_types": [],
|
|
||||||
"custom_path": "",
|
|
||||||
"builtin_path": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio"
|
|
||||||
}
|
|
||||||
if first_filepath in (
|
|
||||||
"{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio",
|
|
||||||
"{BUILTIN_OCIO_ROOT}/nuke-default/config.ocio",
|
|
||||||
):
|
|
||||||
base_value["type"] = "builtin_path"
|
|
||||||
base_value["builtin_path"] = first_filepath
|
|
||||||
else:
|
|
||||||
base_value["type"] = "custom_path"
|
|
||||||
base_value["custom_path"] = first_filepath
|
|
||||||
|
|
||||||
ocio_config_profiles.append(base_value)
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
from .main import CoreSettings, DEFAULT_VALUES
|
from .main import CoreSettings, DEFAULT_VALUES
|
||||||
|
from .conversion import convert_settings_overrides
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
"CoreSettings",
|
"CoreSettings",
|
||||||
"DEFAULT_VALUES",
|
"DEFAULT_VALUES",
|
||||||
|
|
||||||
|
"convert_settings_overrides",
|
||||||
)
|
)
|
||||||
|
|
|
||||||
86
server/settings/conversion.py
Normal file
86
server/settings/conversion.py
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
import copy
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from .publish_plugins import DEFAULT_PUBLISH_VALUES
|
||||||
|
|
||||||
|
|
||||||
|
def _convert_imageio_configs_0_3_1(overrides):
|
||||||
|
"""Imageio config settings did change to profiles since 0.3.1. ."""
|
||||||
|
imageio_overrides = overrides.get("imageio") or {}
|
||||||
|
if (
|
||||||
|
"ocio_config" not in imageio_overrides
|
||||||
|
or "filepath" not in imageio_overrides["ocio_config"]
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
ocio_config = imageio_overrides.pop("ocio_config")
|
||||||
|
|
||||||
|
filepath = ocio_config["filepath"]
|
||||||
|
if not filepath:
|
||||||
|
return
|
||||||
|
first_filepath = filepath[0]
|
||||||
|
ocio_config_profiles = imageio_overrides.setdefault(
|
||||||
|
"ocio_config_profiles", []
|
||||||
|
)
|
||||||
|
base_value = {
|
||||||
|
"type": "builtin_path",
|
||||||
|
"product_name": "",
|
||||||
|
"host_names": [],
|
||||||
|
"task_names": [],
|
||||||
|
"task_types": [],
|
||||||
|
"custom_path": "",
|
||||||
|
"builtin_path": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio"
|
||||||
|
}
|
||||||
|
if first_filepath in (
|
||||||
|
"{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio",
|
||||||
|
"{BUILTIN_OCIO_ROOT}/nuke-default/config.ocio",
|
||||||
|
):
|
||||||
|
base_value["type"] = "builtin_path"
|
||||||
|
base_value["builtin_path"] = first_filepath
|
||||||
|
else:
|
||||||
|
base_value["type"] = "custom_path"
|
||||||
|
base_value["custom_path"] = first_filepath
|
||||||
|
|
||||||
|
ocio_config_profiles.append(base_value)
|
||||||
|
|
||||||
|
|
||||||
|
def _convert_validate_version_0_3_3(publish_overrides):
|
||||||
|
"""ValidateVersion plugin changed in 0.3.3."""
|
||||||
|
if "ValidateVersion" not in publish_overrides:
|
||||||
|
return
|
||||||
|
|
||||||
|
validate_version = publish_overrides["ValidateVersion"]
|
||||||
|
# Already new settings
|
||||||
|
if "plugin_state_profiles" in validate_version:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Use new default profile as base
|
||||||
|
profile = copy.deepcopy(
|
||||||
|
DEFAULT_PUBLISH_VALUES["ValidateVersion"]["plugin_state_profiles"][0]
|
||||||
|
)
|
||||||
|
# Copy values from old overrides to new overrides
|
||||||
|
for key in {
|
||||||
|
"enabled",
|
||||||
|
"optional",
|
||||||
|
"active",
|
||||||
|
}:
|
||||||
|
if key not in validate_version:
|
||||||
|
continue
|
||||||
|
profile[key] = validate_version.pop(key)
|
||||||
|
|
||||||
|
validate_version["plugin_state_profiles"] = [profile]
|
||||||
|
|
||||||
|
|
||||||
|
def _conver_publish_plugins(overrides):
|
||||||
|
if "publish" not in overrides:
|
||||||
|
return
|
||||||
|
_convert_validate_version_0_3_3(overrides["publish"])
|
||||||
|
|
||||||
|
|
||||||
|
def convert_settings_overrides(
|
||||||
|
source_version: str,
|
||||||
|
overrides: dict[str, Any],
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
_convert_imageio_configs_0_3_1(overrides)
|
||||||
|
_conver_publish_plugins(overrides)
|
||||||
|
return overrides
|
||||||
|
|
@ -59,7 +59,7 @@ class CollectFramesFixDefModel(BaseSettingsModel):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ValidateOutdatedContainersProfile(BaseSettingsModel):
|
class PluginStateByHostModelProfile(BaseSettingsModel):
|
||||||
_layout = "expanded"
|
_layout = "expanded"
|
||||||
# Filtering
|
# Filtering
|
||||||
host_names: list[str] = SettingsField(
|
host_names: list[str] = SettingsField(
|
||||||
|
|
@ -72,17 +72,12 @@ class ValidateOutdatedContainersProfile(BaseSettingsModel):
|
||||||
active: bool = SettingsField(True, title="Active")
|
active: bool = SettingsField(True, title="Active")
|
||||||
|
|
||||||
|
|
||||||
class ValidateOutdatedContainersModel(BaseSettingsModel):
|
class PluginStateByHostModel(BaseSettingsModel):
|
||||||
"""Validate if Publishing intent was selected.
|
|
||||||
|
|
||||||
It is possible to disable validation for specific publishing context
|
|
||||||
with profiles.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_isGroup = True
|
_isGroup = True
|
||||||
plugin_state_profiles: list[ValidateOutdatedContainersProfile] = SettingsField(
|
plugin_state_profiles: list[PluginStateByHostModelProfile] = SettingsField(
|
||||||
default_factory=list,
|
default_factory=list,
|
||||||
title="Plugin enable state profiles",
|
title="Plugin enable state profiles",
|
||||||
|
description="Change plugin state based on host name."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -563,7 +558,7 @@ class ExtractBurninProfile(BaseSettingsModel):
|
||||||
_layout = "expanded"
|
_layout = "expanded"
|
||||||
product_types: list[str] = SettingsField(
|
product_types: list[str] = SettingsField(
|
||||||
default_factory=list,
|
default_factory=list,
|
||||||
title="Produt types"
|
title="Product types"
|
||||||
)
|
)
|
||||||
hosts: list[str] = SettingsField(
|
hosts: list[str] = SettingsField(
|
||||||
default_factory=list,
|
default_factory=list,
|
||||||
|
|
@ -793,12 +788,16 @@ class PublishPuginsModel(BaseSettingsModel):
|
||||||
default_factory=ValidateBaseModel,
|
default_factory=ValidateBaseModel,
|
||||||
title="Validate Editorial Asset Name"
|
title="Validate Editorial Asset Name"
|
||||||
)
|
)
|
||||||
ValidateVersion: ValidateBaseModel = SettingsField(
|
ValidateVersion: PluginStateByHostModel = SettingsField(
|
||||||
default_factory=ValidateBaseModel,
|
default_factory=PluginStateByHostModel,
|
||||||
title="Validate Version"
|
title="Validate Version",
|
||||||
|
description=(
|
||||||
|
"Validate that product version to integrate"
|
||||||
|
" is newer than latest version in AYON."
|
||||||
|
)
|
||||||
)
|
)
|
||||||
ValidateOutdatedContainers: ValidateOutdatedContainersModel = SettingsField(
|
ValidateOutdatedContainers: PluginStateByHostModel = SettingsField(
|
||||||
default_factory=ValidateOutdatedContainersModel,
|
default_factory=PluginStateByHostModel,
|
||||||
title="Validate Containers"
|
title="Validate Containers"
|
||||||
)
|
)
|
||||||
ValidateIntent: ValidateIntentModel = SettingsField(
|
ValidateIntent: ValidateIntentModel = SettingsField(
|
||||||
|
|
@ -882,9 +881,21 @@ DEFAULT_PUBLISH_VALUES = {
|
||||||
"active": True
|
"active": True
|
||||||
},
|
},
|
||||||
"ValidateVersion": {
|
"ValidateVersion": {
|
||||||
"enabled": True,
|
"plugin_state_profiles": [
|
||||||
"optional": False,
|
{
|
||||||
"active": True
|
"host_names": [
|
||||||
|
"aftereffects",
|
||||||
|
"blender",
|
||||||
|
"houdini",
|
||||||
|
"maya",
|
||||||
|
"nuke",
|
||||||
|
"photoshop",
|
||||||
|
],
|
||||||
|
"enabled": True,
|
||||||
|
"optional": False,
|
||||||
|
"active": True
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"ValidateOutdatedContainers": {
|
"ValidateOutdatedContainers": {
|
||||||
"plugin_state_profiles": [
|
"plugin_state_profiles": [
|
||||||
|
|
|
||||||
|
|
@ -212,7 +212,13 @@ class ApplicationsAddonSettings(BaseSettingsModel):
|
||||||
scope=["studio"]
|
scope=["studio"]
|
||||||
)
|
)
|
||||||
only_available: bool = SettingsField(
|
only_available: bool = SettingsField(
|
||||||
True, title="Show only available applications")
|
True,
|
||||||
|
title="Show only available applications",
|
||||||
|
description="Enable to show only applications in AYON Launcher"
|
||||||
|
" for which the executable paths are found on the running machine."
|
||||||
|
" This applies as an additional filter to the applications defined in a "
|
||||||
|
" project's anatomy settings to ignore unavailable applications."
|
||||||
|
)
|
||||||
|
|
||||||
@validator("tool_groups")
|
@validator("tool_groups")
|
||||||
def validate_unique_name(cls, value):
|
def validate_unique_name(cls, value):
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
from .version import __version__
|
||||||
from .addon import (
|
from .addon import (
|
||||||
CELACTION_ROOT_DIR,
|
CELACTION_ROOT_DIR,
|
||||||
CelactionAddon,
|
CelactionAddon,
|
||||||
|
|
@ -5,6 +6,8 @@ from .addon import (
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
|
"__version__",
|
||||||
|
|
||||||
"CELACTION_ROOT_DIR",
|
"CELACTION_ROOT_DIR",
|
||||||
"CelactionAddon",
|
"CelactionAddon",
|
||||||
)
|
)
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
import os
|
import os
|
||||||
from ayon_core.addon import AYONAddon, IHostAddon
|
from ayon_core.addon import AYONAddon, IHostAddon
|
||||||
|
|
||||||
|
from .version import __version__
|
||||||
|
|
||||||
CELACTION_ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
|
CELACTION_ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
|
||||||
class CelactionAddon(AYONAddon, IHostAddon):
|
class CelactionAddon(AYONAddon, IHostAddon):
|
||||||
name = "celaction"
|
name = "celaction"
|
||||||
|
version = __version__
|
||||||
host_name = "celaction"
|
host_name = "celaction"
|
||||||
|
|
||||||
def get_launch_hook_paths(self, app):
|
def get_launch_hook_paths(self, app):
|
||||||
|
|
@ -4,13 +4,11 @@ import winreg
|
||||||
import subprocess
|
import subprocess
|
||||||
from ayon_core.lib import get_ayon_launcher_args
|
from ayon_core.lib import get_ayon_launcher_args
|
||||||
from ayon_applications import PreLaunchHook, LaunchTypes
|
from ayon_applications import PreLaunchHook, LaunchTypes
|
||||||
from ayon_core.hosts.celaction import CELACTION_ROOT_DIR
|
from ayon_celaction import CELACTION_ROOT_DIR
|
||||||
|
|
||||||
|
|
||||||
class CelactionPrelaunchHook(PreLaunchHook):
|
class CelactionPrelaunchHook(PreLaunchHook):
|
||||||
"""
|
"""Bootstrap celacion with AYON"""
|
||||||
Bootstrap celacion with pype
|
|
||||||
"""
|
|
||||||
app_groups = {"celaction"}
|
app_groups = {"celaction"}
|
||||||
platforms = {"windows"}
|
platforms = {"windows"}
|
||||||
launch_types = {LaunchTypes.local}
|
launch_types = {LaunchTypes.local}
|
||||||
|
|
@ -39,7 +37,7 @@ class CelactionPrelaunchHook(PreLaunchHook):
|
||||||
CELACTION_ROOT_DIR, "scripts", "publish_cli.py"
|
CELACTION_ROOT_DIR, "scripts", "publish_cli.py"
|
||||||
)
|
)
|
||||||
subprocess_args = get_ayon_launcher_args("run", path_to_cli)
|
subprocess_args = get_ayon_launcher_args("run", path_to_cli)
|
||||||
openpype_executable = subprocess_args.pop(0)
|
executable = subprocess_args.pop(0)
|
||||||
workfile_settings = self.get_workfile_settings()
|
workfile_settings = self.get_workfile_settings()
|
||||||
|
|
||||||
winreg.SetValueEx(
|
winreg.SetValueEx(
|
||||||
|
|
@ -47,7 +45,7 @@ class CelactionPrelaunchHook(PreLaunchHook):
|
||||||
"SubmitAppTitle",
|
"SubmitAppTitle",
|
||||||
0,
|
0,
|
||||||
winreg.REG_SZ,
|
winreg.REG_SZ,
|
||||||
openpype_executable
|
executable
|
||||||
)
|
)
|
||||||
|
|
||||||
# add required arguments for workfile path
|
# add required arguments for workfile path
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import os
|
import os
|
||||||
import pyblish.api
|
|
||||||
import copy
|
import copy
|
||||||
|
import pyblish.api
|
||||||
|
|
||||||
|
|
||||||
class CollectRenderPath(pyblish.api.InstancePlugin):
|
class CollectRenderPath(pyblish.api.InstancePlugin):
|
||||||
|
|
@ -10,6 +10,8 @@ class CollectRenderPath(pyblish.api.InstancePlugin):
|
||||||
order = pyblish.api.CollectorOrder + 0.495
|
order = pyblish.api.CollectorOrder + 0.495
|
||||||
families = ["render.farm"]
|
families = ["render.farm"]
|
||||||
|
|
||||||
|
settings_category = "celaction"
|
||||||
|
|
||||||
# Presets
|
# Presets
|
||||||
output_extension = "png"
|
output_extension = "png"
|
||||||
anatomy_template_key_render_files = None
|
anatomy_template_key_render_files = None
|
||||||
|
|
@ -4,7 +4,7 @@ import sys
|
||||||
import pyblish.api
|
import pyblish.api
|
||||||
import pyblish.util
|
import pyblish.util
|
||||||
|
|
||||||
import ayon_core.hosts.celaction
|
from ayon_celaction import CELACTION_ROOT_DIR
|
||||||
from ayon_core.lib import Logger
|
from ayon_core.lib import Logger
|
||||||
from ayon_core.tools.utils import host_tools
|
from ayon_core.tools.utils import host_tools
|
||||||
from ayon_core.pipeline import install_ayon_plugins
|
from ayon_core.pipeline import install_ayon_plugins
|
||||||
|
|
@ -13,13 +13,12 @@ from ayon_core.pipeline import install_ayon_plugins
|
||||||
log = Logger.get_logger("celaction")
|
log = Logger.get_logger("celaction")
|
||||||
|
|
||||||
PUBLISH_HOST = "celaction"
|
PUBLISH_HOST = "celaction"
|
||||||
HOST_DIR = os.path.dirname(os.path.abspath(ayon_core.hosts.celaction.__file__))
|
PLUGINS_DIR = os.path.join(CELACTION_ROOT_DIR, "plugins")
|
||||||
PLUGINS_DIR = os.path.join(HOST_DIR, "plugins")
|
|
||||||
PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish")
|
PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish")
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
# Registers pype's Global pyblish plugins
|
# Registers global pyblish plugins
|
||||||
install_ayon_plugins()
|
install_ayon_plugins()
|
||||||
|
|
||||||
if os.path.exists(PUBLISH_PATH):
|
if os.path.exists(PUBLISH_PATH):
|
||||||
3
server_addon/celaction/client/ayon_celaction/version.py
Normal file
3
server_addon/celaction/client/ayon_celaction/version.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Package declaring AYON addon 'celaction' version."""
|
||||||
|
__version__ = "0.2.0"
|
||||||
|
|
@ -1,3 +1,12 @@
|
||||||
name = "celaction"
|
name = "celaction"
|
||||||
title = "CelAction"
|
title = "CelAction"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
|
|
||||||
|
client_dir = "ayon_celaction"
|
||||||
|
|
||||||
|
ayon_required_addons = {
|
||||||
|
"core": ">0.3.2",
|
||||||
|
}
|
||||||
|
ayon_compatible_addons = {
|
||||||
|
"applications": ">=0.2.0",
|
||||||
|
}
|
||||||
|
|
|
||||||
13
server_addon/flame/client/ayon_flame/__init__.py
Normal file
13
server_addon/flame/client/ayon_flame/__init__.py
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
from .version import __version__
|
||||||
|
from .addon import (
|
||||||
|
FLAME_ADDON_ROOT,
|
||||||
|
FlameAddon,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
"__version__",
|
||||||
|
|
||||||
|
"FLAME_ADDON_ROOT",
|
||||||
|
"FlameAddon",
|
||||||
|
)
|
||||||
|
|
@ -1,16 +1,19 @@
|
||||||
import os
|
import os
|
||||||
from ayon_core.addon import AYONAddon, IHostAddon
|
from ayon_core.addon import AYONAddon, IHostAddon
|
||||||
|
|
||||||
HOST_DIR = os.path.dirname(os.path.abspath(__file__))
|
from .version import __version__
|
||||||
|
|
||||||
|
FLAME_ADDON_ROOT = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
|
||||||
class FlameAddon(AYONAddon, IHostAddon):
|
class FlameAddon(AYONAddon, IHostAddon):
|
||||||
name = "flame"
|
name = "flame"
|
||||||
|
version = __version__
|
||||||
host_name = "flame"
|
host_name = "flame"
|
||||||
|
|
||||||
def add_implementation_envs(self, env, _app):
|
def add_implementation_envs(self, env, _app):
|
||||||
# Add requirements to DL_PYTHON_HOOK_PATH
|
# Add requirements to DL_PYTHON_HOOK_PATH
|
||||||
env["DL_PYTHON_HOOK_PATH"] = os.path.join(HOST_DIR, "startup")
|
env["DL_PYTHON_HOOK_PATH"] = os.path.join(FLAME_ADDON_ROOT, "startup")
|
||||||
env.pop("QT_AUTO_SCREEN_SCALE_FACTOR", None)
|
env.pop("QT_AUTO_SCREEN_SCALE_FACTOR", None)
|
||||||
|
|
||||||
# Set default values if are not already set via settings
|
# Set default values if are not already set via settings
|
||||||
|
|
@ -25,7 +28,7 @@ class FlameAddon(AYONAddon, IHostAddon):
|
||||||
if app.host_name != self.host_name:
|
if app.host_name != self.host_name:
|
||||||
return []
|
return []
|
||||||
return [
|
return [
|
||||||
os.path.join(HOST_DIR, "hooks")
|
os.path.join(FLAME_ADDON_ROOT, "hooks")
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_workfile_extensions(self):
|
def get_workfile_extensions(self):
|
||||||
|
|
@ -28,7 +28,7 @@ default_flame_export_presets = {
|
||||||
|
|
||||||
|
|
||||||
def callback_selection(selection, function):
|
def callback_selection(selection, function):
|
||||||
import ayon_core.hosts.flame.api as opfapi
|
import ayon_flame.api as opfapi
|
||||||
opfapi.CTX.selection = selection
|
opfapi.CTX.selection = selection
|
||||||
print("Hook Selection: \n\t{}".format(
|
print("Hook Selection: \n\t{}".format(
|
||||||
pformat({
|
pformat({
|
||||||
|
|
@ -13,6 +13,7 @@ from ayon_core.pipeline import (
|
||||||
deregister_creator_plugin_path,
|
deregister_creator_plugin_path,
|
||||||
AVALON_CONTAINER_ID,
|
AVALON_CONTAINER_ID,
|
||||||
)
|
)
|
||||||
|
from ayon_flame import FLAME_ADDON_ROOT
|
||||||
from .lib import (
|
from .lib import (
|
||||||
set_segment_data_marker,
|
set_segment_data_marker,
|
||||||
set_publish_attribute,
|
set_publish_attribute,
|
||||||
|
|
@ -20,10 +21,8 @@ from .lib import (
|
||||||
get_current_sequence,
|
get_current_sequence,
|
||||||
reset_segment_selection
|
reset_segment_selection
|
||||||
)
|
)
|
||||||
from .. import HOST_DIR
|
|
||||||
|
|
||||||
API_DIR = os.path.join(HOST_DIR, "api")
|
PLUGINS_DIR = os.path.join(FLAME_ADDON_ROOT, "plugins")
|
||||||
PLUGINS_DIR = os.path.join(HOST_DIR, "plugins")
|
|
||||||
PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish")
|
PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish")
|
||||||
LOAD_PATH = os.path.join(PLUGINS_DIR, "load")
|
LOAD_PATH = os.path.join(PLUGINS_DIR, "load")
|
||||||
CREATE_PATH = os.path.join(PLUGINS_DIR, "create")
|
CREATE_PATH = os.path.join(PLUGINS_DIR, "create")
|
||||||
|
|
@ -113,10 +112,6 @@ def on_pyblish_instance_toggled(instance, old_value, new_value):
|
||||||
log.info("instance toggle: {}, old_value: {}, new_value:{} ".format(
|
log.info("instance toggle: {}, old_value: {}, new_value:{} ".format(
|
||||||
instance, old_value, new_value))
|
instance, old_value, new_value))
|
||||||
|
|
||||||
# from ayon_core.hosts.resolve import (
|
|
||||||
# set_publish_attribute
|
|
||||||
# )
|
|
||||||
|
|
||||||
# # Whether instances should be passthrough based on new value
|
# # Whether instances should be passthrough based on new value
|
||||||
# timeline_item = instance.data["item"]
|
# timeline_item = instance.data["item"]
|
||||||
# set_publish_attribute(timeline_item, new_value)
|
# set_publish_attribute(timeline_item, new_value)
|
||||||
|
|
@ -5,6 +5,8 @@ Flame utils for syncing scripts
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
from ayon_core.lib import Logger
|
from ayon_core.lib import Logger
|
||||||
|
from ayon_flame import FLAME_ADDON_ROOT
|
||||||
|
|
||||||
log = Logger.get_logger(__name__)
|
log = Logger.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -16,7 +18,6 @@ def _sync_utility_scripts(env=None):
|
||||||
`/opt/Autodesk/shared/python`. This will be always synchronizing those
|
`/opt/Autodesk/shared/python`. This will be always synchronizing those
|
||||||
folders.
|
folders.
|
||||||
"""
|
"""
|
||||||
from .. import HOST_DIR
|
|
||||||
|
|
||||||
env = env or os.environ
|
env = env or os.environ
|
||||||
|
|
||||||
|
|
@ -26,7 +27,7 @@ def _sync_utility_scripts(env=None):
|
||||||
flame_shared_dir = "/opt/Autodesk/shared/python"
|
flame_shared_dir = "/opt/Autodesk/shared/python"
|
||||||
|
|
||||||
fsd_paths = [os.path.join(
|
fsd_paths = [os.path.join(
|
||||||
HOST_DIR,
|
FLAME_ADDON_ROOT,
|
||||||
"api",
|
"api",
|
||||||
"utility_scripts"
|
"utility_scripts"
|
||||||
)]
|
)]
|
||||||
|
|
@ -10,7 +10,7 @@ from ayon_core.lib import (
|
||||||
run_subprocess,
|
run_subprocess,
|
||||||
)
|
)
|
||||||
from ayon_applications import PreLaunchHook, LaunchTypes
|
from ayon_applications import PreLaunchHook, LaunchTypes
|
||||||
from ayon_core.hosts import flame as opflame
|
from ayon_flame import FLAME_ADDON_ROOT
|
||||||
|
|
||||||
|
|
||||||
class FlamePrelaunch(PreLaunchHook):
|
class FlamePrelaunch(PreLaunchHook):
|
||||||
|
|
@ -23,7 +23,8 @@ class FlamePrelaunch(PreLaunchHook):
|
||||||
permissions = 0o777
|
permissions = 0o777
|
||||||
|
|
||||||
wtc_script_path = os.path.join(
|
wtc_script_path = os.path.join(
|
||||||
opflame.HOST_DIR, "api", "scripts", "wiretap_com.py")
|
FLAME_ADDON_ROOT, "api", "scripts", "wiretap_com.py"
|
||||||
|
)
|
||||||
launch_types = {LaunchTypes.local}
|
launch_types = {LaunchTypes.local}
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
@ -275,7 +275,7 @@ def create_otio_reference(clip_data, fps=None):
|
||||||
|
|
||||||
|
|
||||||
def create_otio_clip(clip_data):
|
def create_otio_clip(clip_data):
|
||||||
from ayon_core.hosts.flame.api import MediaInfoFile, TimeEffectMetadata
|
from ayon_flame.api import MediaInfoFile, TimeEffectMetadata
|
||||||
|
|
||||||
segment = clip_data["PySegment"]
|
segment = clip_data["PySegment"]
|
||||||
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
import ayon_core.hosts.flame.api as opfapi
|
import ayon_flame.api as opfapi
|
||||||
|
|
||||||
|
|
||||||
class CreateShotClip(opfapi.Creator):
|
class CreateShotClip(opfapi.Creator):
|
||||||
|
|
@ -2,7 +2,7 @@ from copy import deepcopy
|
||||||
import os
|
import os
|
||||||
import flame
|
import flame
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
import ayon_core.hosts.flame.api as opfapi
|
import ayon_flame.api as opfapi
|
||||||
from ayon_core.lib import StringTemplate
|
from ayon_core.lib import StringTemplate
|
||||||
from ayon_core.lib.transcoding import (
|
from ayon_core.lib.transcoding import (
|
||||||
VIDEO_EXTENSIONS,
|
VIDEO_EXTENSIONS,
|
||||||
|
|
@ -2,7 +2,7 @@ from copy import deepcopy
|
||||||
import os
|
import os
|
||||||
import flame
|
import flame
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
import ayon_core.hosts.flame.api as opfapi
|
import ayon_flame.api as opfapi
|
||||||
from ayon_core.lib import StringTemplate
|
from ayon_core.lib import StringTemplate
|
||||||
from ayon_core.lib.transcoding import (
|
from ayon_core.lib.transcoding import (
|
||||||
VIDEO_EXTENSIONS,
|
VIDEO_EXTENSIONS,
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import os
|
import os
|
||||||
import pyblish.api
|
import pyblish.api
|
||||||
import tempfile
|
import tempfile
|
||||||
import ayon_core.hosts.flame.api as opfapi
|
import ayon_flame.api as opfapi
|
||||||
from ayon_core.hosts.flame.otio import flame_export as otio_export
|
from ayon_flame.otio import flame_export as otio_export
|
||||||
import opentimelineio as otio
|
import opentimelineio as otio
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
reload(otio_export) # noqa
|
reload(otio_export) # noqa
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import re
|
import re
|
||||||
from types import NoneType
|
from types import NoneType
|
||||||
import pyblish
|
import pyblish
|
||||||
import ayon_core.hosts.flame.api as opfapi
|
import ayon_flame.api as opfapi
|
||||||
from ayon_core.hosts.flame.otio import flame_export
|
from ayon_flame.otio import flame_export
|
||||||
from ayon_core.pipeline import AYON_INSTANCE_ID, AVALON_INSTANCE_ID
|
from ayon_core.pipeline import AYON_INSTANCE_ID, AVALON_INSTANCE_ID
|
||||||
from ayon_core.pipeline.editorial import (
|
from ayon_core.pipeline.editorial import (
|
||||||
is_overlapping_otio_ranges,
|
is_overlapping_otio_ranges,
|
||||||
|
|
@ -24,6 +24,8 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin):
|
||||||
label = "Collect timeline Instances"
|
label = "Collect timeline Instances"
|
||||||
hosts = ["flame"]
|
hosts = ["flame"]
|
||||||
|
|
||||||
|
settings_category = "flame"
|
||||||
|
|
||||||
audio_track_items = []
|
audio_track_items = []
|
||||||
|
|
||||||
# settings
|
# settings
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import pyblish.api
|
import pyblish.api
|
||||||
|
|
||||||
import ayon_core.hosts.flame.api as opfapi
|
import ayon_flame.api as opfapi
|
||||||
from ayon_core.hosts.flame.otio import flame_export
|
from ayon_flame.otio import flame_export
|
||||||
from ayon_core.pipeline.create import get_product_name
|
from ayon_core.pipeline.create import get_product_name
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -5,8 +5,8 @@ from copy import deepcopy
|
||||||
import pyblish.api
|
import pyblish.api
|
||||||
|
|
||||||
from ayon_core.pipeline import publish
|
from ayon_core.pipeline import publish
|
||||||
from ayon_core.hosts.flame import api as opfapi
|
from ayon_flame import api as opfapi
|
||||||
from ayon_core.hosts.flame.api import MediaInfoFile
|
from ayon_flame.api import MediaInfoFile
|
||||||
from ayon_core.pipeline.editorial import (
|
from ayon_core.pipeline.editorial import (
|
||||||
get_media_range_with_retimes
|
get_media_range_with_retimes
|
||||||
)
|
)
|
||||||
|
|
@ -24,6 +24,8 @@ class ExtractProductResources(publish.Extractor):
|
||||||
families = ["clip"]
|
families = ["clip"]
|
||||||
hosts = ["flame"]
|
hosts = ["flame"]
|
||||||
|
|
||||||
|
settings_category = "flame"
|
||||||
|
|
||||||
# plugin defaults
|
# plugin defaults
|
||||||
keep_original_representation = False
|
keep_original_representation = False
|
||||||
|
|
||||||
|
|
@ -3,7 +3,7 @@ import copy
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
import pyblish
|
import pyblish
|
||||||
import ayon_core.hosts.flame.api as opfapi
|
import ayon_flame.api as opfapi
|
||||||
import ayon_core.pipeline as op_pipeline
|
import ayon_core.pipeline as op_pipeline
|
||||||
from ayon_core.pipeline.workfile import get_workdir
|
from ayon_core.pipeline.workfile import get_workdir
|
||||||
|
|
||||||
|
|
@ -16,6 +16,8 @@ class IntegrateBatchGroup(pyblish.api.InstancePlugin):
|
||||||
hosts = ["flame"]
|
hosts = ["flame"]
|
||||||
families = ["clip"]
|
families = ["clip"]
|
||||||
|
|
||||||
|
settings_category = "flame"
|
||||||
|
|
||||||
# settings
|
# settings
|
||||||
default_loader = "LoadClip"
|
default_loader = "LoadClip"
|
||||||
|
|
||||||
|
|
@ -4,7 +4,7 @@ from qtpy import QtWidgets
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
import atexit
|
import atexit
|
||||||
|
|
||||||
import ayon_core.hosts.flame.api as opfapi
|
import ayon_flame.api as opfapi
|
||||||
from ayon_core.pipeline import (
|
from ayon_core.pipeline import (
|
||||||
install_host,
|
install_host,
|
||||||
registered_host,
|
registered_host,
|
||||||
3
server_addon/flame/client/ayon_flame/version.py
Normal file
3
server_addon/flame/client/ayon_flame/version.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Package declaring AYON addon 'flame' version."""
|
||||||
|
__version__ = "0.2.0"
|
||||||
|
|
@ -1,3 +1,10 @@
|
||||||
name = "flame"
|
name = "flame"
|
||||||
title = "Flame"
|
title = "Flame"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
|
|
||||||
|
client_dir = "ayon_flame"
|
||||||
|
|
||||||
|
ayon_required_addons = {
|
||||||
|
"core": ">0.3.2",
|
||||||
|
}
|
||||||
|
ayon_compatible_addons = {}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ from pymxs import runtime as rt
|
||||||
|
|
||||||
|
|
||||||
JSON_PREFIX = "JSON::"
|
JSON_PREFIX = "JSON::"
|
||||||
log = logging.getLogger("ayon_core.hosts.max")
|
log = logging.getLogger("ayon_max")
|
||||||
|
|
||||||
|
|
||||||
def get_main_window():
|
def get_main_window():
|
||||||
|
|
@ -6,7 +6,7 @@ import os
|
||||||
|
|
||||||
from pymxs import runtime as rt
|
from pymxs import runtime as rt
|
||||||
|
|
||||||
from ayon_core.hosts.max.api.lib import get_current_renderer
|
from ayon_max.api.lib import get_current_renderer
|
||||||
from ayon_core.pipeline import get_current_project_name
|
from ayon_core.pipeline import get_current_project_name
|
||||||
from ayon_core.settings import get_project_settings
|
from ayon_core.settings import get_project_settings
|
||||||
|
|
||||||
|
|
@ -5,7 +5,7 @@ from ayon_core.settings import get_project_settings
|
||||||
from ayon_core.pipeline import get_current_project_name
|
from ayon_core.pipeline import get_current_project_name
|
||||||
from ayon_core.pipeline.context_tools import get_current_folder_entity
|
from ayon_core.pipeline.context_tools import get_current_folder_entity
|
||||||
|
|
||||||
from ayon_core.hosts.max.api.lib import (
|
from ayon_max.api.lib import (
|
||||||
set_render_frame_range,
|
set_render_frame_range,
|
||||||
get_current_renderer,
|
get_current_renderer,
|
||||||
get_default_render_folder
|
get_default_render_folder
|
||||||
|
|
@ -5,7 +5,7 @@ from qtpy import QtWidgets, QtCore
|
||||||
from pymxs import runtime as rt
|
from pymxs import runtime as rt
|
||||||
|
|
||||||
from ayon_core.tools.utils import host_tools
|
from ayon_core.tools.utils import host_tools
|
||||||
from ayon_core.hosts.max.api import lib
|
from ayon_max.api import lib
|
||||||
|
|
||||||
|
|
||||||
class AYONMenu(object):
|
class AYONMenu(object):
|
||||||
|
|
@ -14,14 +14,14 @@ from ayon_core.pipeline import (
|
||||||
AVALON_CONTAINER_ID,
|
AVALON_CONTAINER_ID,
|
||||||
AYON_CONTAINER_ID,
|
AYON_CONTAINER_ID,
|
||||||
)
|
)
|
||||||
from ayon_core.hosts.max.api.menu import AYONMenu
|
from ayon_max.api.menu import AYONMenu
|
||||||
from ayon_core.hosts.max.api import lib
|
from ayon_max.api import lib
|
||||||
from ayon_core.hosts.max.api.plugin import MS_CUSTOM_ATTRIB
|
from ayon_max.api.plugin import MS_CUSTOM_ATTRIB
|
||||||
from ayon_core.hosts.max import MAX_HOST_DIR
|
from ayon_max import MAX_HOST_DIR
|
||||||
|
|
||||||
from pymxs import runtime as rt # noqa
|
from pymxs import runtime as rt # noqa
|
||||||
|
|
||||||
log = logging.getLogger("ayon_core.hosts.max")
|
log = logging.getLogger("ayon_max")
|
||||||
|
|
||||||
PLUGINS_DIR = os.path.join(MAX_HOST_DIR, "plugins")
|
PLUGINS_DIR = os.path.join(MAX_HOST_DIR, "plugins")
|
||||||
PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish")
|
PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish")
|
||||||
|
|
@ -3,7 +3,7 @@ import contextlib
|
||||||
from pymxs import runtime as rt
|
from pymxs import runtime as rt
|
||||||
from .lib import get_max_version, render_resolution
|
from .lib import get_max_version, render_resolution
|
||||||
|
|
||||||
log = logging.getLogger("ayon_core.hosts.max")
|
log = logging.getLogger("ayon_max")
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""Pre-launch to force 3ds max startup script."""
|
"""Pre-launch to force 3ds max startup script."""
|
||||||
import os
|
import os
|
||||||
from ayon_core.hosts.max import MAX_HOST_DIR
|
from ayon_max import MAX_HOST_DIR
|
||||||
from ayon_applications import PreLaunchHook, LaunchTypes
|
from ayon_applications import PreLaunchHook, LaunchTypes
|
||||||
|
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue