mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge branch 'develop' of https://github.com/ynput/ayon-core into feature/AY-6789_Render-instance-support-of-Frame-List
This commit is contained in:
commit
4a5583cff0
17 changed files with 609 additions and 249 deletions
16
.github/workflows/upload_to_ynput_cloud.yml
vendored
Normal file
16
.github/workflows/upload_to_ynput_cloud.yml
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
name: 📤 Upload to Ynput Cloud
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
call-upload-to-ynput-cloud:
|
||||
uses: ynput/ops-repo-automation/.github/workflows/upload_to_ynput_cloud.yml@main
|
||||
secrets:
|
||||
CI_EMAIL: ${{ secrets.CI_EMAIL }}
|
||||
CI_USER: ${{ secrets.CI_USER }}
|
||||
YNPUT_BOT_TOKEN: ${{ secrets.YNPUT_BOT_TOKEN }}
|
||||
YNPUT_CLOUD_URL: ${{ secrets.YNPUT_CLOUD_URL }}
|
||||
YNPUT_CLOUD_TOKEN: ${{ secrets.YNPUT_CLOUD_TOKEN }}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
import os
|
||||
import sys
|
||||
import uuid
|
||||
import getpass
|
||||
import logging
|
||||
import platform
|
||||
|
|
@ -11,12 +10,12 @@ import copy
|
|||
|
||||
from . import Terminal
|
||||
|
||||
# Check for `unicode` in builtins
|
||||
USE_UNICODE = hasattr(__builtins__, "unicode")
|
||||
|
||||
|
||||
class LogStreamHandler(logging.StreamHandler):
|
||||
""" StreamHandler class designed to handle utf errors in python 2.x hosts.
|
||||
"""StreamHandler class.
|
||||
|
||||
This was originally designed to handle UTF errors in python 2.x hosts,
|
||||
however currently solely remains for backwards compatibility.
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -25,49 +24,27 @@ class LogStreamHandler(logging.StreamHandler):
|
|||
self.enabled = True
|
||||
|
||||
def enable(self):
|
||||
""" Enable StreamHandler
|
||||
"""Enable StreamHandler
|
||||
|
||||
Used to silence output
|
||||
Make StreamHandler output again
|
||||
"""
|
||||
self.enabled = True
|
||||
|
||||
def disable(self):
|
||||
""" Disable StreamHandler
|
||||
"""Disable StreamHandler
|
||||
|
||||
Make StreamHandler output again
|
||||
Used to silence output
|
||||
"""
|
||||
self.enabled = False
|
||||
|
||||
def emit(self, record):
|
||||
if not self.enable:
|
||||
if not self.enabled or self.stream is None:
|
||||
return
|
||||
try:
|
||||
msg = self.format(record)
|
||||
msg = Terminal.log(msg)
|
||||
stream = self.stream
|
||||
if stream is None:
|
||||
return
|
||||
fs = "%s\n"
|
||||
# if no unicode support...
|
||||
if not USE_UNICODE:
|
||||
stream.write(fs % msg)
|
||||
else:
|
||||
try:
|
||||
if (isinstance(msg, unicode) and # noqa: F821
|
||||
getattr(stream, 'encoding', None)):
|
||||
ufs = u'%s\n'
|
||||
try:
|
||||
stream.write(ufs % msg)
|
||||
except UnicodeEncodeError:
|
||||
stream.write((ufs % msg).encode(stream.encoding))
|
||||
else:
|
||||
if (getattr(stream, 'encoding', 'utf-8')):
|
||||
ufs = u'%s\n'
|
||||
stream.write(ufs % unicode(msg)) # noqa: F821
|
||||
else:
|
||||
stream.write(fs % msg)
|
||||
except UnicodeError:
|
||||
stream.write(fs % msg.encode("UTF-8"))
|
||||
stream.write(f"{msg}\n")
|
||||
self.flush()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
|
|
@ -141,8 +118,6 @@ class Logger:
|
|||
process_data = None
|
||||
# Cached process name or ability to set different process name
|
||||
_process_name = None
|
||||
# TODO Remove 'mongo_process_id' in 1.x.x
|
||||
mongo_process_id = uuid.uuid4().hex
|
||||
|
||||
@classmethod
|
||||
def get_logger(cls, name=None):
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import os
|
||||
import re
|
||||
import logging
|
||||
import platform
|
||||
|
||||
import clique
|
||||
|
||||
|
|
@ -38,31 +37,7 @@ def create_hard_link(src_path, dst_path):
|
|||
dst_path(str): Full path to a file where a link of source will be
|
||||
added.
|
||||
"""
|
||||
# Use `os.link` if is available
|
||||
# - should be for all platforms with newer python versions
|
||||
if hasattr(os, "link"):
|
||||
os.link(src_path, dst_path)
|
||||
return
|
||||
|
||||
# Windows implementation of hardlinks
|
||||
# - used in Python 2
|
||||
if platform.system().lower() == "windows":
|
||||
import ctypes
|
||||
from ctypes.wintypes import BOOL
|
||||
CreateHardLink = ctypes.windll.kernel32.CreateHardLinkW
|
||||
CreateHardLink.argtypes = [
|
||||
ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_void_p
|
||||
]
|
||||
CreateHardLink.restype = BOOL
|
||||
|
||||
res = CreateHardLink(dst_path, src_path, None)
|
||||
if res == 0:
|
||||
raise ctypes.WinError()
|
||||
return
|
||||
# Raises not implemented error if gets here
|
||||
raise NotImplementedError(
|
||||
"Implementation of hardlink for current environment is missing."
|
||||
)
|
||||
os.link(src_path, dst_path)
|
||||
|
||||
|
||||
def collect_frames(files):
|
||||
|
|
@ -210,7 +185,7 @@ def get_last_version_from_path(path_dir, filter):
|
|||
assert isinstance(filter, list) and (
|
||||
len(filter) != 0), "`filter` argument needs to be list and not empty"
|
||||
|
||||
filtred_files = list()
|
||||
filtered_files = list()
|
||||
|
||||
# form regex for filtering
|
||||
pattern = r".*".join(filter)
|
||||
|
|
@ -218,10 +193,10 @@ def get_last_version_from_path(path_dir, filter):
|
|||
for file in os.listdir(path_dir):
|
||||
if not re.findall(pattern, file):
|
||||
continue
|
||||
filtred_files.append(file)
|
||||
filtered_files.append(file)
|
||||
|
||||
if filtred_files:
|
||||
sorted(filtred_files)
|
||||
return filtred_files[-1]
|
||||
if filtered_files:
|
||||
filtered_files.sort()
|
||||
return filtered_files[-1]
|
||||
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -254,9 +254,8 @@ class CreateContext:
|
|||
self._collection_shared_data = None
|
||||
|
||||
# Entities cache
|
||||
self._folder_entities_by_id = {}
|
||||
self._folder_entities_by_path = {}
|
||||
self._task_entities_by_id = {}
|
||||
self._folder_id_by_folder_path = {}
|
||||
self._task_ids_by_folder_path = {}
|
||||
self._task_names_by_folder_path = {}
|
||||
|
||||
|
|
@ -560,10 +559,9 @@ class CreateContext:
|
|||
# Give ability to store shared data for collection phase
|
||||
self._collection_shared_data = {}
|
||||
|
||||
self._folder_entities_by_id = {}
|
||||
self._folder_entities_by_path = {}
|
||||
self._task_entities_by_id = {}
|
||||
|
||||
self._folder_id_by_folder_path = {}
|
||||
self._task_ids_by_folder_path = {}
|
||||
self._task_names_by_folder_path = {}
|
||||
|
||||
|
|
@ -1480,43 +1478,35 @@ class CreateContext:
|
|||
output = {
|
||||
folder_path: None
|
||||
for folder_path in folder_paths
|
||||
if folder_path is not None
|
||||
}
|
||||
remainder_paths = set()
|
||||
for folder_path in output:
|
||||
# Skip empty/invalid folder paths
|
||||
if folder_path is None or "/" not in folder_path:
|
||||
# Skip invalid folder paths (folder name or empty path)
|
||||
if not folder_path or "/" not in folder_path:
|
||||
continue
|
||||
|
||||
if folder_path not in self._folder_id_by_folder_path:
|
||||
if folder_path not in self._folder_entities_by_path:
|
||||
remainder_paths.add(folder_path)
|
||||
continue
|
||||
|
||||
folder_id = self._folder_id_by_folder_path.get(folder_path)
|
||||
if not folder_id:
|
||||
output[folder_path] = None
|
||||
continue
|
||||
|
||||
folder_entity = self._folder_entities_by_id.get(folder_id)
|
||||
if folder_entity:
|
||||
output[folder_path] = folder_entity
|
||||
else:
|
||||
remainder_paths.add(folder_path)
|
||||
output[folder_path] = self._folder_entities_by_path[folder_path]
|
||||
|
||||
if not remainder_paths:
|
||||
return output
|
||||
|
||||
folder_paths_by_id = {}
|
||||
found_paths = set()
|
||||
for folder_entity in ayon_api.get_folders(
|
||||
self.project_name,
|
||||
folder_paths=remainder_paths,
|
||||
):
|
||||
folder_id = folder_entity["id"]
|
||||
folder_path = folder_entity["path"]
|
||||
folder_paths_by_id[folder_id] = folder_path
|
||||
found_paths.add(folder_path)
|
||||
output[folder_path] = folder_entity
|
||||
self._folder_entities_by_id[folder_id] = folder_entity
|
||||
self._folder_id_by_folder_path[folder_path] = folder_id
|
||||
self._folder_entities_by_path[folder_path] = folder_entity
|
||||
|
||||
# Cache empty folder entities
|
||||
for path in remainder_paths - found_paths:
|
||||
self._folder_entities_by_path[path] = None
|
||||
|
||||
return output
|
||||
|
||||
|
|
@ -1676,6 +1666,7 @@ class CreateContext:
|
|||
instance.get("folderPath")
|
||||
for instance in instances
|
||||
}
|
||||
folder_paths.discard(None)
|
||||
folder_entities_by_path = self.get_folder_entities(folder_paths)
|
||||
for instance in instances:
|
||||
folder_path = instance.get("folderPath")
|
||||
|
|
@ -1775,9 +1766,9 @@ class CreateContext:
|
|||
if not folder_path:
|
||||
continue
|
||||
|
||||
if folder_path in self._folder_id_by_folder_path:
|
||||
folder_id = self._folder_id_by_folder_path[folder_path]
|
||||
if folder_id is None:
|
||||
if folder_path in self._folder_entities_by_path:
|
||||
folder_entity = self._folder_entities_by_path[folder_path]
|
||||
if folder_entity is None:
|
||||
continue
|
||||
context_info.folder_is_valid = True
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,11 @@
|
|||
"icon-alert-tools": "#AA5050",
|
||||
"icon-entity-default": "#bfccd6",
|
||||
"icon-entity-disabled": "#808080",
|
||||
|
||||
"font-entity-deprecated": "#666666",
|
||||
|
||||
"font-overridden": "#91CDFC",
|
||||
|
||||
"overlay-messages": {
|
||||
"close-btn": "#D3D8DE",
|
||||
"bg-success": "#458056",
|
||||
|
|
|
|||
|
|
@ -1585,6 +1585,10 @@ CreateNextPageOverlay {
|
|||
}
|
||||
|
||||
/* Attribute Definition widgets */
|
||||
AttributeDefinitionsLabel[overridden="1"] {
|
||||
color: {color:font-overridden};
|
||||
}
|
||||
|
||||
AttributeDefinitionsWidget QAbstractSpinBox, QLineEdit, QPlainTextEdit, QTextEdit {
|
||||
padding: 1px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from .widgets import (
|
||||
create_widget_for_attr_def,
|
||||
AttributeDefinitionsWidget,
|
||||
AttributeDefinitionsLabel,
|
||||
)
|
||||
|
||||
from .dialog import (
|
||||
|
|
@ -11,6 +12,7 @@ from .dialog import (
|
|||
__all__ = (
|
||||
"create_widget_for_attr_def",
|
||||
"AttributeDefinitionsWidget",
|
||||
"AttributeDefinitionsLabel",
|
||||
|
||||
"AttributeDefinitionsDialog",
|
||||
)
|
||||
|
|
|
|||
1
client/ayon_core/tools/attribute_defs/_constants.py
Normal file
1
client/ayon_core/tools/attribute_defs/_constants.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
REVERT_TO_DEFAULT_LABEL = "Revert to default"
|
||||
|
|
@ -17,6 +17,8 @@ from ayon_core.tools.utils import (
|
|||
PixmapLabel
|
||||
)
|
||||
|
||||
from ._constants import REVERT_TO_DEFAULT_LABEL
|
||||
|
||||
ITEM_ID_ROLE = QtCore.Qt.UserRole + 1
|
||||
ITEM_LABEL_ROLE = QtCore.Qt.UserRole + 2
|
||||
ITEM_ICON_ROLE = QtCore.Qt.UserRole + 3
|
||||
|
|
@ -598,7 +600,7 @@ class FilesView(QtWidgets.QListView):
|
|||
"""View showing instances and their groups."""
|
||||
|
||||
remove_requested = QtCore.Signal()
|
||||
context_menu_requested = QtCore.Signal(QtCore.QPoint)
|
||||
context_menu_requested = QtCore.Signal(QtCore.QPoint, bool)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(FilesView, self).__init__(*args, **kwargs)
|
||||
|
|
@ -690,9 +692,8 @@ class FilesView(QtWidgets.QListView):
|
|||
|
||||
def _on_context_menu_request(self, pos):
|
||||
index = self.indexAt(pos)
|
||||
if index.isValid():
|
||||
point = self.viewport().mapToGlobal(pos)
|
||||
self.context_menu_requested.emit(point)
|
||||
point = self.viewport().mapToGlobal(pos)
|
||||
self.context_menu_requested.emit(point, index.isValid())
|
||||
|
||||
def _on_selection_change(self):
|
||||
self._remove_btn.setEnabled(self.has_selected_item_ids())
|
||||
|
|
@ -721,27 +722,34 @@ class FilesView(QtWidgets.QListView):
|
|||
|
||||
class FilesWidget(QtWidgets.QFrame):
|
||||
value_changed = QtCore.Signal()
|
||||
revert_requested = QtCore.Signal()
|
||||
|
||||
def __init__(self, single_item, allow_sequences, extensions_label, parent):
|
||||
super(FilesWidget, self).__init__(parent)
|
||||
super().__init__(parent)
|
||||
self.setAcceptDrops(True)
|
||||
|
||||
wrapper_widget = QtWidgets.QWidget(self)
|
||||
|
||||
empty_widget = DropEmpty(
|
||||
single_item, allow_sequences, extensions_label, self
|
||||
single_item, allow_sequences, extensions_label, wrapper_widget
|
||||
)
|
||||
|
||||
files_model = FilesModel(single_item, allow_sequences)
|
||||
files_proxy_model = FilesProxyModel()
|
||||
files_proxy_model.setSourceModel(files_model)
|
||||
files_view = FilesView(self)
|
||||
files_view = FilesView(wrapper_widget)
|
||||
files_view.setModel(files_proxy_model)
|
||||
|
||||
layout = QtWidgets.QStackedLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setStackingMode(QtWidgets.QStackedLayout.StackAll)
|
||||
layout.addWidget(empty_widget)
|
||||
layout.addWidget(files_view)
|
||||
layout.setCurrentWidget(empty_widget)
|
||||
wrapper_layout = QtWidgets.QStackedLayout(wrapper_widget)
|
||||
wrapper_layout.setContentsMargins(0, 0, 0, 0)
|
||||
wrapper_layout.setStackingMode(QtWidgets.QStackedLayout.StackAll)
|
||||
wrapper_layout.addWidget(empty_widget)
|
||||
wrapper_layout.addWidget(files_view)
|
||||
wrapper_layout.setCurrentWidget(empty_widget)
|
||||
|
||||
main_layout = QtWidgets.QHBoxLayout(self)
|
||||
main_layout.setContentsMargins(0, 0, 0, 0)
|
||||
main_layout.addWidget(wrapper_widget, 1)
|
||||
|
||||
files_proxy_model.rowsInserted.connect(self._on_rows_inserted)
|
||||
files_proxy_model.rowsRemoved.connect(self._on_rows_removed)
|
||||
|
|
@ -761,7 +769,11 @@ class FilesWidget(QtWidgets.QFrame):
|
|||
|
||||
self._widgets_by_id = {}
|
||||
|
||||
self._layout = layout
|
||||
self._wrapper_widget = wrapper_widget
|
||||
self._wrapper_layout = wrapper_layout
|
||||
|
||||
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
self.customContextMenuRequested.connect(self._on_context_menu)
|
||||
|
||||
def _set_multivalue(self, multivalue):
|
||||
if self._multivalue is multivalue:
|
||||
|
|
@ -770,7 +782,7 @@ class FilesWidget(QtWidgets.QFrame):
|
|||
self._files_view.set_multivalue(multivalue)
|
||||
self._files_model.set_multivalue(multivalue)
|
||||
self._files_proxy_model.set_multivalue(multivalue)
|
||||
self.setEnabled(not multivalue)
|
||||
self._wrapper_widget.setEnabled(not multivalue)
|
||||
|
||||
def set_value(self, value, multivalue):
|
||||
self._in_set_value = True
|
||||
|
|
@ -888,22 +900,28 @@ class FilesWidget(QtWidgets.QFrame):
|
|||
if items_to_delete:
|
||||
self._remove_item_by_ids(items_to_delete)
|
||||
|
||||
def _on_context_menu_requested(self, pos):
|
||||
if self._multivalue:
|
||||
return
|
||||
def _on_context_menu(self, pos):
|
||||
self._on_context_menu_requested(pos, False)
|
||||
|
||||
def _on_context_menu_requested(self, pos, valid_index):
|
||||
menu = QtWidgets.QMenu(self._files_view)
|
||||
if valid_index and not self._multivalue:
|
||||
if self._files_view.has_selected_sequence():
|
||||
split_action = QtWidgets.QAction("Split sequence", menu)
|
||||
split_action.triggered.connect(self._on_split_request)
|
||||
menu.addAction(split_action)
|
||||
|
||||
if self._files_view.has_selected_sequence():
|
||||
split_action = QtWidgets.QAction("Split sequence", menu)
|
||||
split_action.triggered.connect(self._on_split_request)
|
||||
menu.addAction(split_action)
|
||||
remove_action = QtWidgets.QAction("Remove", menu)
|
||||
remove_action.triggered.connect(self._on_remove_requested)
|
||||
menu.addAction(remove_action)
|
||||
|
||||
remove_action = QtWidgets.QAction("Remove", menu)
|
||||
remove_action.triggered.connect(self._on_remove_requested)
|
||||
menu.addAction(remove_action)
|
||||
if not valid_index:
|
||||
revert_action = QtWidgets.QAction(REVERT_TO_DEFAULT_LABEL, menu)
|
||||
revert_action.triggered.connect(self.revert_requested)
|
||||
menu.addAction(revert_action)
|
||||
|
||||
menu.popup(pos)
|
||||
if menu.actions():
|
||||
menu.popup(pos)
|
||||
|
||||
def dragEnterEvent(self, event):
|
||||
if self._multivalue:
|
||||
|
|
@ -1011,5 +1029,5 @@ class FilesWidget(QtWidgets.QFrame):
|
|||
current_widget = self._files_view
|
||||
else:
|
||||
current_widget = self._empty_widget
|
||||
self._layout.setCurrentWidget(current_widget)
|
||||
self._wrapper_layout.setCurrentWidget(current_widget)
|
||||
self._files_view.update_remove_btn_visibility()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import copy
|
||||
import typing
|
||||
from typing import Optional
|
||||
|
||||
from qtpy import QtWidgets, QtCore
|
||||
|
||||
|
|
@ -20,14 +22,25 @@ from ayon_core.tools.utils import (
|
|||
FocusSpinBox,
|
||||
FocusDoubleSpinBox,
|
||||
MultiSelectionComboBox,
|
||||
set_style_property,
|
||||
)
|
||||
from ayon_core.tools.utils import NiceCheckbox
|
||||
|
||||
from ._constants import REVERT_TO_DEFAULT_LABEL
|
||||
from .files_widget import FilesWidget
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from typing import Union
|
||||
|
||||
def create_widget_for_attr_def(attr_def, parent=None):
|
||||
widget = _create_widget_for_attr_def(attr_def, parent)
|
||||
|
||||
def create_widget_for_attr_def(
|
||||
attr_def: AbstractAttrDef,
|
||||
parent: Optional[QtWidgets.QWidget] = None,
|
||||
handle_revert_to_default: Optional[bool] = True,
|
||||
):
|
||||
widget = _create_widget_for_attr_def(
|
||||
attr_def, parent, handle_revert_to_default
|
||||
)
|
||||
if not attr_def.visible:
|
||||
widget.setVisible(False)
|
||||
|
||||
|
|
@ -36,42 +49,96 @@ def create_widget_for_attr_def(attr_def, parent=None):
|
|||
return widget
|
||||
|
||||
|
||||
def _create_widget_for_attr_def(attr_def, parent=None):
|
||||
def _create_widget_for_attr_def(
|
||||
attr_def: AbstractAttrDef,
|
||||
parent: "Union[QtWidgets.QWidget, None]",
|
||||
handle_revert_to_default: bool,
|
||||
):
|
||||
if not isinstance(attr_def, AbstractAttrDef):
|
||||
raise TypeError("Unexpected type \"{}\" expected \"{}\"".format(
|
||||
str(type(attr_def)), AbstractAttrDef
|
||||
))
|
||||
|
||||
cls = None
|
||||
if isinstance(attr_def, NumberDef):
|
||||
return NumberAttrWidget(attr_def, parent)
|
||||
cls = NumberAttrWidget
|
||||
|
||||
if isinstance(attr_def, TextDef):
|
||||
return TextAttrWidget(attr_def, parent)
|
||||
elif isinstance(attr_def, TextDef):
|
||||
cls = TextAttrWidget
|
||||
|
||||
if isinstance(attr_def, EnumDef):
|
||||
return EnumAttrWidget(attr_def, parent)
|
||||
elif isinstance(attr_def, EnumDef):
|
||||
cls = EnumAttrWidget
|
||||
|
||||
if isinstance(attr_def, BoolDef):
|
||||
return BoolAttrWidget(attr_def, parent)
|
||||
elif isinstance(attr_def, BoolDef):
|
||||
cls = BoolAttrWidget
|
||||
|
||||
if isinstance(attr_def, UnknownDef):
|
||||
return UnknownAttrWidget(attr_def, parent)
|
||||
elif isinstance(attr_def, UnknownDef):
|
||||
cls = UnknownAttrWidget
|
||||
|
||||
if isinstance(attr_def, HiddenDef):
|
||||
return HiddenAttrWidget(attr_def, parent)
|
||||
elif isinstance(attr_def, HiddenDef):
|
||||
cls = HiddenAttrWidget
|
||||
|
||||
if isinstance(attr_def, FileDef):
|
||||
return FileAttrWidget(attr_def, parent)
|
||||
elif isinstance(attr_def, FileDef):
|
||||
cls = FileAttrWidget
|
||||
|
||||
if isinstance(attr_def, UISeparatorDef):
|
||||
return SeparatorAttrWidget(attr_def, parent)
|
||||
elif isinstance(attr_def, UISeparatorDef):
|
||||
cls = SeparatorAttrWidget
|
||||
|
||||
if isinstance(attr_def, UILabelDef):
|
||||
return LabelAttrWidget(attr_def, parent)
|
||||
elif isinstance(attr_def, UILabelDef):
|
||||
cls = LabelAttrWidget
|
||||
|
||||
raise ValueError("Unknown attribute definition \"{}\"".format(
|
||||
str(type(attr_def))
|
||||
))
|
||||
if cls is None:
|
||||
raise ValueError("Unknown attribute definition \"{}\"".format(
|
||||
str(type(attr_def))
|
||||
))
|
||||
|
||||
return cls(attr_def, parent, handle_revert_to_default)
|
||||
|
||||
|
||||
class AttributeDefinitionsLabel(QtWidgets.QLabel):
|
||||
"""Label related to value attribute definition.
|
||||
|
||||
Label is used to show attribute definition label and to show if value
|
||||
is overridden.
|
||||
|
||||
Label can be right-clicked to revert value to default.
|
||||
"""
|
||||
revert_to_default_requested = QtCore.Signal(str)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
attr_id: str,
|
||||
label: str,
|
||||
parent: QtWidgets.QWidget,
|
||||
):
|
||||
super().__init__(label, parent)
|
||||
|
||||
self._attr_id = attr_id
|
||||
self._overridden = False
|
||||
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
|
||||
self.customContextMenuRequested.connect(self._on_context_menu)
|
||||
|
||||
def set_overridden(self, overridden: bool):
|
||||
if self._overridden == overridden:
|
||||
return
|
||||
self._overridden = overridden
|
||||
set_style_property(
|
||||
self,
|
||||
"overridden",
|
||||
"1" if overridden else ""
|
||||
)
|
||||
|
||||
def _on_context_menu(self, point: QtCore.QPoint):
|
||||
menu = QtWidgets.QMenu(self)
|
||||
action = QtWidgets.QAction(menu)
|
||||
action.setText(REVERT_TO_DEFAULT_LABEL)
|
||||
action.triggered.connect(self._request_revert_to_default)
|
||||
menu.addAction(action)
|
||||
menu.exec_(self.mapToGlobal(point))
|
||||
|
||||
def _request_revert_to_default(self):
|
||||
self.revert_to_default_requested.emit(self._attr_id)
|
||||
|
||||
|
||||
class AttributeDefinitionsWidget(QtWidgets.QWidget):
|
||||
|
|
@ -83,16 +150,18 @@ class AttributeDefinitionsWidget(QtWidgets.QWidget):
|
|||
"""
|
||||
|
||||
def __init__(self, attr_defs=None, parent=None):
|
||||
super(AttributeDefinitionsWidget, self).__init__(parent)
|
||||
super().__init__(parent)
|
||||
|
||||
self._widgets = []
|
||||
self._widgets_by_id = {}
|
||||
self._labels_by_id = {}
|
||||
self._current_keys = set()
|
||||
|
||||
self.set_attr_defs(attr_defs)
|
||||
|
||||
def clear_attr_defs(self):
|
||||
"""Remove all existing widgets and reset layout if needed."""
|
||||
self._widgets = []
|
||||
self._widgets_by_id = {}
|
||||
self._labels_by_id = {}
|
||||
self._current_keys = set()
|
||||
|
||||
layout = self.layout()
|
||||
|
|
@ -134,6 +203,7 @@ class AttributeDefinitionsWidget(QtWidgets.QWidget):
|
|||
self._current_keys.add(attr_def.key)
|
||||
widget = create_widget_for_attr_def(attr_def, self)
|
||||
self._widgets.append(widget)
|
||||
self._widgets_by_id[attr_def.id] = widget
|
||||
|
||||
if not attr_def.visible:
|
||||
continue
|
||||
|
|
@ -145,7 +215,13 @@ class AttributeDefinitionsWidget(QtWidgets.QWidget):
|
|||
col_num = 2 - expand_cols
|
||||
|
||||
if attr_def.is_value_def and attr_def.label:
|
||||
label_widget = QtWidgets.QLabel(attr_def.label, self)
|
||||
label_widget = AttributeDefinitionsLabel(
|
||||
attr_def.id, attr_def.label, self
|
||||
)
|
||||
label_widget.revert_to_default_requested.connect(
|
||||
self._on_revert_request
|
||||
)
|
||||
self._labels_by_id[attr_def.id] = label_widget
|
||||
tooltip = attr_def.tooltip
|
||||
if tooltip:
|
||||
label_widget.setToolTip(tooltip)
|
||||
|
|
@ -160,6 +236,9 @@ class AttributeDefinitionsWidget(QtWidgets.QWidget):
|
|||
if not attr_def.is_label_horizontal:
|
||||
row += 1
|
||||
|
||||
if attr_def.is_value_def:
|
||||
widget.value_changed.connect(self._on_value_change)
|
||||
|
||||
layout.addWidget(
|
||||
widget, row, col_num, 1, expand_cols
|
||||
)
|
||||
|
|
@ -168,7 +247,7 @@ class AttributeDefinitionsWidget(QtWidgets.QWidget):
|
|||
def set_value(self, value):
|
||||
new_value = copy.deepcopy(value)
|
||||
unused_keys = set(new_value.keys())
|
||||
for widget in self._widgets:
|
||||
for widget in self._widgets_by_id.values():
|
||||
attr_def = widget.attr_def
|
||||
if attr_def.key not in new_value:
|
||||
continue
|
||||
|
|
@ -181,22 +260,42 @@ class AttributeDefinitionsWidget(QtWidgets.QWidget):
|
|||
|
||||
def current_value(self):
|
||||
output = {}
|
||||
for widget in self._widgets:
|
||||
for widget in self._widgets_by_id.values():
|
||||
attr_def = widget.attr_def
|
||||
if not isinstance(attr_def, UIDef):
|
||||
output[attr_def.key] = widget.current_value()
|
||||
|
||||
return output
|
||||
|
||||
def _on_revert_request(self, attr_id):
|
||||
widget = self._widgets_by_id.get(attr_id)
|
||||
if widget is not None:
|
||||
widget.set_value(widget.attr_def.default)
|
||||
|
||||
def _on_value_change(self, value, attr_id):
|
||||
widget = self._widgets_by_id.get(attr_id)
|
||||
if widget is None:
|
||||
return
|
||||
label = self._labels_by_id.get(attr_id)
|
||||
if label is not None:
|
||||
label.set_overridden(value != widget.attr_def.default)
|
||||
|
||||
|
||||
class _BaseAttrDefWidget(QtWidgets.QWidget):
|
||||
# Type 'object' may not work with older PySide versions
|
||||
value_changed = QtCore.Signal(object, str)
|
||||
revert_to_default_requested = QtCore.Signal(str)
|
||||
|
||||
def __init__(self, attr_def, parent):
|
||||
super(_BaseAttrDefWidget, self).__init__(parent)
|
||||
def __init__(
|
||||
self,
|
||||
attr_def: AbstractAttrDef,
|
||||
parent: "Union[QtWidgets.QWidget, None]",
|
||||
handle_revert_to_default: Optional[bool] = True,
|
||||
):
|
||||
super().__init__(parent)
|
||||
|
||||
self.attr_def = attr_def
|
||||
self.attr_def: AbstractAttrDef = attr_def
|
||||
self._handle_revert_to_default: bool = handle_revert_to_default
|
||||
|
||||
main_layout = QtWidgets.QHBoxLayout(self)
|
||||
main_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
|
@ -205,6 +304,15 @@ class _BaseAttrDefWidget(QtWidgets.QWidget):
|
|||
|
||||
self._ui_init()
|
||||
|
||||
def revert_to_default_value(self):
|
||||
if not self.attr_def.is_value_def:
|
||||
return
|
||||
|
||||
if self._handle_revert_to_default:
|
||||
self.set_value(self.attr_def.default)
|
||||
else:
|
||||
self.revert_to_default_requested.emit(self.attr_def.id)
|
||||
|
||||
def _ui_init(self):
|
||||
raise NotImplementedError(
|
||||
"Method '_ui_init' is not implemented. {}".format(
|
||||
|
|
@ -255,7 +363,7 @@ class ClickableLineEdit(QtWidgets.QLineEdit):
|
|||
clicked = QtCore.Signal()
|
||||
|
||||
def __init__(self, text, parent):
|
||||
super(ClickableLineEdit, self).__init__(parent)
|
||||
super().__init__(parent)
|
||||
self.setText(text)
|
||||
self.setReadOnly(True)
|
||||
|
||||
|
|
@ -264,7 +372,7 @@ class ClickableLineEdit(QtWidgets.QLineEdit):
|
|||
def mousePressEvent(self, event):
|
||||
if event.button() == QtCore.Qt.LeftButton:
|
||||
self._mouse_pressed = True
|
||||
super(ClickableLineEdit, self).mousePressEvent(event)
|
||||
super().mousePressEvent(event)
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if self._mouse_pressed:
|
||||
|
|
@ -272,7 +380,7 @@ class ClickableLineEdit(QtWidgets.QLineEdit):
|
|||
if self.rect().contains(event.pos()):
|
||||
self.clicked.emit()
|
||||
|
||||
super(ClickableLineEdit, self).mouseReleaseEvent(event)
|
||||
super().mouseReleaseEvent(event)
|
||||
|
||||
|
||||
class NumberAttrWidget(_BaseAttrDefWidget):
|
||||
|
|
@ -284,6 +392,9 @@ class NumberAttrWidget(_BaseAttrDefWidget):
|
|||
else:
|
||||
input_widget = FocusSpinBox(self)
|
||||
|
||||
# Override context menu event to add revert to default action
|
||||
input_widget.contextMenuEvent = self._input_widget_context_event
|
||||
|
||||
if self.attr_def.tooltip:
|
||||
input_widget.setToolTip(self.attr_def.tooltip)
|
||||
|
||||
|
|
@ -321,6 +432,16 @@ class NumberAttrWidget(_BaseAttrDefWidget):
|
|||
self._set_multiselection_visible(True)
|
||||
return False
|
||||
|
||||
def _input_widget_context_event(self, event):
|
||||
line_edit = self._input_widget.lineEdit()
|
||||
menu = line_edit.createStandardContextMenu()
|
||||
menu.setAttribute(QtCore.Qt.WA_DeleteOnClose)
|
||||
action = QtWidgets.QAction(menu)
|
||||
action.setText(REVERT_TO_DEFAULT_LABEL)
|
||||
action.triggered.connect(self.revert_to_default_value)
|
||||
menu.addAction(action)
|
||||
menu.popup(event.globalPos())
|
||||
|
||||
def current_value(self):
|
||||
return self._input_widget.value()
|
||||
|
||||
|
|
@ -386,6 +507,9 @@ class TextAttrWidget(_BaseAttrDefWidget):
|
|||
else:
|
||||
input_widget = QtWidgets.QLineEdit(self)
|
||||
|
||||
# Override context menu event to add revert to default action
|
||||
input_widget.contextMenuEvent = self._input_widget_context_event
|
||||
|
||||
if (
|
||||
self.attr_def.placeholder
|
||||
and hasattr(input_widget, "setPlaceholderText")
|
||||
|
|
@ -407,6 +531,15 @@ class TextAttrWidget(_BaseAttrDefWidget):
|
|||
|
||||
self.main_layout.addWidget(input_widget, 0)
|
||||
|
||||
def _input_widget_context_event(self, event):
|
||||
menu = self._input_widget.createStandardContextMenu()
|
||||
menu.setAttribute(QtCore.Qt.WA_DeleteOnClose)
|
||||
action = QtWidgets.QAction(menu)
|
||||
action.setText(REVERT_TO_DEFAULT_LABEL)
|
||||
action.triggered.connect(self.revert_to_default_value)
|
||||
menu.addAction(action)
|
||||
menu.popup(event.globalPos())
|
||||
|
||||
def _on_value_change(self):
|
||||
if self.multiline:
|
||||
new_value = self._input_widget.toPlainText()
|
||||
|
|
@ -459,6 +592,20 @@ class BoolAttrWidget(_BaseAttrDefWidget):
|
|||
self.main_layout.addWidget(input_widget, 0)
|
||||
self.main_layout.addStretch(1)
|
||||
|
||||
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
self.customContextMenuRequested.connect(self._on_context_menu)
|
||||
|
||||
def _on_context_menu(self, pos):
|
||||
self._menu = QtWidgets.QMenu(self)
|
||||
|
||||
action = QtWidgets.QAction(self._menu)
|
||||
action.setText(REVERT_TO_DEFAULT_LABEL)
|
||||
action.triggered.connect(self.revert_to_default_value)
|
||||
self._menu.addAction(action)
|
||||
|
||||
global_pos = self.mapToGlobal(pos)
|
||||
self._menu.exec_(global_pos)
|
||||
|
||||
def _on_value_change(self):
|
||||
new_value = self._input_widget.isChecked()
|
||||
self.value_changed.emit(new_value, self.attr_def.id)
|
||||
|
|
@ -487,7 +634,7 @@ class BoolAttrWidget(_BaseAttrDefWidget):
|
|||
class EnumAttrWidget(_BaseAttrDefWidget):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._multivalue = False
|
||||
super(EnumAttrWidget, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def multiselection(self):
|
||||
|
|
@ -522,6 +669,20 @@ class EnumAttrWidget(_BaseAttrDefWidget):
|
|||
|
||||
self.main_layout.addWidget(input_widget, 0)
|
||||
|
||||
input_widget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
input_widget.customContextMenuRequested.connect(self._on_context_menu)
|
||||
|
||||
def _on_context_menu(self, pos):
|
||||
menu = QtWidgets.QMenu(self)
|
||||
|
||||
action = QtWidgets.QAction(menu)
|
||||
action.setText(REVERT_TO_DEFAULT_LABEL)
|
||||
action.triggered.connect(self.revert_to_default_value)
|
||||
menu.addAction(action)
|
||||
|
||||
global_pos = self.mapToGlobal(pos)
|
||||
menu.exec_(global_pos)
|
||||
|
||||
def _on_value_change(self):
|
||||
new_value = self.current_value()
|
||||
if self._multivalue:
|
||||
|
|
@ -614,7 +775,7 @@ class HiddenAttrWidget(_BaseAttrDefWidget):
|
|||
def setVisible(self, visible):
|
||||
if visible:
|
||||
visible = False
|
||||
super(HiddenAttrWidget, self).setVisible(visible)
|
||||
super().setVisible(visible)
|
||||
|
||||
def current_value(self):
|
||||
if self._multivalue:
|
||||
|
|
@ -650,10 +811,25 @@ class FileAttrWidget(_BaseAttrDefWidget):
|
|||
|
||||
self.main_layout.addWidget(input_widget, 0)
|
||||
|
||||
input_widget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
input_widget.customContextMenuRequested.connect(self._on_context_menu)
|
||||
input_widget.revert_requested.connect(self.revert_to_default_value)
|
||||
|
||||
def _on_value_change(self):
|
||||
new_value = self.current_value()
|
||||
self.value_changed.emit(new_value, self.attr_def.id)
|
||||
|
||||
def _on_context_menu(self, pos):
|
||||
menu = QtWidgets.QMenu(self)
|
||||
|
||||
action = QtWidgets.QAction(menu)
|
||||
action.setText(REVERT_TO_DEFAULT_LABEL)
|
||||
action.triggered.connect(self.revert_to_default_value)
|
||||
menu.addAction(action)
|
||||
|
||||
global_pos = self.mapToGlobal(pos)
|
||||
menu.exec_(global_pos)
|
||||
|
||||
def current_value(self):
|
||||
return self._input_widget.current_value()
|
||||
|
||||
|
|
|
|||
|
|
@ -366,7 +366,7 @@ class AbstractPublisherFrontend(AbstractPublisherCommon):
|
|||
@abstractmethod
|
||||
def get_creator_attribute_definitions(
|
||||
self, instance_ids: Iterable[str]
|
||||
) -> List[Tuple[AbstractAttrDef, List[str], List[Any]]]:
|
||||
) -> List[Tuple[AbstractAttrDef, Dict[str, Dict[str, Any]]]]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
|
|
@ -375,6 +375,14 @@ class AbstractPublisherFrontend(AbstractPublisherCommon):
|
|||
):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def revert_instances_create_attr_values(
|
||||
self,
|
||||
instance_ids: List["Union[str, None]"],
|
||||
key: str,
|
||||
):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_publish_attribute_definitions(
|
||||
self,
|
||||
|
|
@ -383,7 +391,7 @@ class AbstractPublisherFrontend(AbstractPublisherCommon):
|
|||
) -> List[Tuple[
|
||||
str,
|
||||
List[AbstractAttrDef],
|
||||
Dict[str, List[Tuple[str, Any]]]
|
||||
Dict[str, List[Tuple[str, Any, Any]]]
|
||||
]]:
|
||||
pass
|
||||
|
||||
|
|
@ -397,6 +405,15 @@ class AbstractPublisherFrontend(AbstractPublisherCommon):
|
|||
):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def revert_instances_publish_attr_values(
|
||||
self,
|
||||
instance_ids: List["Union[str, None]"],
|
||||
plugin_name: str,
|
||||
key: str,
|
||||
):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_product_name(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -412,6 +412,11 @@ class PublisherController(
|
|||
instance_ids, key, value
|
||||
)
|
||||
|
||||
def revert_instances_create_attr_values(self, instance_ids, key):
|
||||
self._create_model.revert_instances_create_attr_values(
|
||||
instance_ids, key
|
||||
)
|
||||
|
||||
def get_publish_attribute_definitions(self, instance_ids, include_context):
|
||||
"""Collect publish attribute definitions for passed instances.
|
||||
|
||||
|
|
@ -432,6 +437,13 @@ class PublisherController(
|
|||
instance_ids, plugin_name, key, value
|
||||
)
|
||||
|
||||
def revert_instances_publish_attr_values(
|
||||
self, instance_ids, plugin_name, key
|
||||
):
|
||||
return self._create_model.revert_instances_publish_attr_values(
|
||||
instance_ids, plugin_name, key
|
||||
)
|
||||
|
||||
def get_product_name(
|
||||
self,
|
||||
creator_identifier,
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ from ayon_core.tools.publisher.abstract import (
|
|||
)
|
||||
|
||||
CREATE_EVENT_SOURCE = "publisher.create.model"
|
||||
_DEFAULT_VALUE = object()
|
||||
|
||||
|
||||
class CreatorType:
|
||||
|
|
@ -752,24 +753,16 @@ class CreateModel:
|
|||
self._remove_instances_from_context(instance_ids)
|
||||
|
||||
def set_instances_create_attr_values(self, instance_ids, key, value):
|
||||
with self._create_context.bulk_value_changes(CREATE_EVENT_SOURCE):
|
||||
for instance_id in instance_ids:
|
||||
instance = self._get_instance_by_id(instance_id)
|
||||
creator_attributes = instance["creator_attributes"]
|
||||
attr_def = creator_attributes.get_attr_def(key)
|
||||
if (
|
||||
attr_def is None
|
||||
or not attr_def.is_value_def
|
||||
or not attr_def.visible
|
||||
or not attr_def.enabled
|
||||
or not attr_def.is_value_valid(value)
|
||||
):
|
||||
continue
|
||||
creator_attributes[key] = value
|
||||
self._set_instances_create_attr_values(instance_ids, key, value)
|
||||
|
||||
def revert_instances_create_attr_values(self, instance_ids, key):
|
||||
self._set_instances_create_attr_values(
|
||||
instance_ids, key, _DEFAULT_VALUE
|
||||
)
|
||||
|
||||
def get_creator_attribute_definitions(
|
||||
self, instance_ids: List[str]
|
||||
) -> List[Tuple[AbstractAttrDef, List[str], List[Any]]]:
|
||||
) -> List[Tuple[AbstractAttrDef, Dict[str, Dict[str, Any]]]]:
|
||||
"""Collect creator attribute definitions for multuple instances.
|
||||
|
||||
Args:
|
||||
|
|
@ -796,37 +789,38 @@ class CreateModel:
|
|||
|
||||
if found_idx is None:
|
||||
idx = len(output)
|
||||
output.append((attr_def, [instance_id], [value]))
|
||||
output.append((
|
||||
attr_def,
|
||||
{
|
||||
instance_id: {
|
||||
"value": value,
|
||||
"default": attr_def.default
|
||||
}
|
||||
}
|
||||
))
|
||||
_attr_defs[idx] = attr_def
|
||||
else:
|
||||
_, ids, values = output[found_idx]
|
||||
ids.append(instance_id)
|
||||
values.append(value)
|
||||
_, info_by_id = output[found_idx]
|
||||
info_by_id[instance_id] = {
|
||||
"value": value,
|
||||
"default": attr_def.default
|
||||
}
|
||||
|
||||
return output
|
||||
|
||||
def set_instances_publish_attr_values(
|
||||
self, instance_ids, plugin_name, key, value
|
||||
self, instance_ids, plugin_name, key, value
|
||||
):
|
||||
with self._create_context.bulk_value_changes(CREATE_EVENT_SOURCE):
|
||||
for instance_id in instance_ids:
|
||||
if instance_id is None:
|
||||
instance = self._create_context
|
||||
else:
|
||||
instance = self._get_instance_by_id(instance_id)
|
||||
plugin_val = instance.publish_attributes[plugin_name]
|
||||
attr_def = plugin_val.get_attr_def(key)
|
||||
# Ignore if attribute is not available or enabled/visible
|
||||
# on the instance, or the value is not valid for definition
|
||||
if (
|
||||
attr_def is None
|
||||
or not attr_def.is_value_def
|
||||
or not attr_def.visible
|
||||
or not attr_def.enabled
|
||||
or not attr_def.is_value_valid(value)
|
||||
):
|
||||
continue
|
||||
self._set_instances_publish_attr_values(
|
||||
instance_ids, plugin_name, key, value
|
||||
)
|
||||
|
||||
plugin_val[key] = value
|
||||
def revert_instances_publish_attr_values(
|
||||
self, instance_ids, plugin_name, key
|
||||
):
|
||||
self._set_instances_publish_attr_values(
|
||||
instance_ids, plugin_name, key, _DEFAULT_VALUE
|
||||
)
|
||||
|
||||
def get_publish_attribute_definitions(
|
||||
self,
|
||||
|
|
@ -835,7 +829,7 @@ class CreateModel:
|
|||
) -> List[Tuple[
|
||||
str,
|
||||
List[AbstractAttrDef],
|
||||
Dict[str, List[Tuple[str, Any]]]
|
||||
Dict[str, List[Tuple[str, Any, Any]]]
|
||||
]]:
|
||||
"""Collect publish attribute definitions for passed instances.
|
||||
|
||||
|
|
@ -865,21 +859,21 @@ class CreateModel:
|
|||
attr_defs = attr_val.attr_defs
|
||||
if not attr_defs:
|
||||
continue
|
||||
|
||||
plugin_attr_defs = all_defs_by_plugin_name.setdefault(
|
||||
plugin_name, []
|
||||
)
|
||||
plugin_attr_defs.append(attr_defs)
|
||||
|
||||
plugin_values = all_plugin_values.setdefault(plugin_name, {})
|
||||
|
||||
plugin_attr_defs.append(attr_defs)
|
||||
|
||||
for attr_def in attr_defs:
|
||||
if isinstance(attr_def, UIDef):
|
||||
continue
|
||||
|
||||
attr_values = plugin_values.setdefault(attr_def.key, [])
|
||||
|
||||
value = attr_val[attr_def.key]
|
||||
attr_values.append((item_id, value))
|
||||
attr_values.append(
|
||||
(item_id, attr_val[attr_def.key], attr_def.default)
|
||||
)
|
||||
|
||||
attr_defs_by_plugin_name = {}
|
||||
for plugin_name, attr_defs in all_defs_by_plugin_name.items():
|
||||
|
|
@ -893,7 +887,7 @@ class CreateModel:
|
|||
output.append((
|
||||
plugin_name,
|
||||
attr_defs_by_plugin_name[plugin_name],
|
||||
all_plugin_values
|
||||
all_plugin_values[plugin_name],
|
||||
))
|
||||
return output
|
||||
|
||||
|
|
@ -1053,6 +1047,53 @@ class CreateModel:
|
|||
CreatorItem.from_creator(creator)
|
||||
)
|
||||
|
||||
def _set_instances_create_attr_values(self, instance_ids, key, value):
|
||||
with self._create_context.bulk_value_changes(CREATE_EVENT_SOURCE):
|
||||
for instance_id in instance_ids:
|
||||
instance = self._get_instance_by_id(instance_id)
|
||||
creator_attributes = instance["creator_attributes"]
|
||||
attr_def = creator_attributes.get_attr_def(key)
|
||||
if (
|
||||
attr_def is None
|
||||
or not attr_def.is_value_def
|
||||
or not attr_def.visible
|
||||
or not attr_def.enabled
|
||||
):
|
||||
continue
|
||||
|
||||
if value is _DEFAULT_VALUE:
|
||||
creator_attributes[key] = attr_def.default
|
||||
|
||||
elif attr_def.is_value_valid(value):
|
||||
creator_attributes[key] = value
|
||||
|
||||
def _set_instances_publish_attr_values(
|
||||
self, instance_ids, plugin_name, key, value
|
||||
):
|
||||
with self._create_context.bulk_value_changes(CREATE_EVENT_SOURCE):
|
||||
for instance_id in instance_ids:
|
||||
if instance_id is None:
|
||||
instance = self._create_context
|
||||
else:
|
||||
instance = self._get_instance_by_id(instance_id)
|
||||
plugin_val = instance.publish_attributes[plugin_name]
|
||||
attr_def = plugin_val.get_attr_def(key)
|
||||
# Ignore if attribute is not available or enabled/visible
|
||||
# on the instance, or the value is not valid for definition
|
||||
if (
|
||||
attr_def is None
|
||||
or not attr_def.is_value_def
|
||||
or not attr_def.visible
|
||||
or not attr_def.enabled
|
||||
):
|
||||
continue
|
||||
|
||||
if value is _DEFAULT_VALUE:
|
||||
plugin_val[key] = attr_def.default
|
||||
|
||||
elif attr_def.is_value_valid(value):
|
||||
plugin_val[key] = value
|
||||
|
||||
def _cc_added_instance(self, event):
|
||||
instance_ids = {
|
||||
instance.id
|
||||
|
|
|
|||
|
|
@ -1,13 +1,58 @@
|
|||
import typing
|
||||
from typing import Dict, List, Any
|
||||
|
||||
from qtpy import QtWidgets, QtCore
|
||||
|
||||
from ayon_core.lib.attribute_definitions import UnknownDef
|
||||
from ayon_core.tools.attribute_defs import create_widget_for_attr_def
|
||||
from ayon_core.lib.attribute_definitions import AbstractAttrDef, UnknownDef
|
||||
from ayon_core.tools.attribute_defs import (
|
||||
create_widget_for_attr_def,
|
||||
AttributeDefinitionsLabel,
|
||||
)
|
||||
from ayon_core.tools.publisher.abstract import AbstractPublisherFrontend
|
||||
from ayon_core.tools.publisher.constants import (
|
||||
INPUTS_LAYOUT_HSPACING,
|
||||
INPUTS_LAYOUT_VSPACING,
|
||||
)
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from typing import Union
|
||||
|
||||
|
||||
class _CreateAttrDefInfo:
|
||||
"""Helper class to store information about create attribute definition."""
|
||||
def __init__(
|
||||
self,
|
||||
attr_def: AbstractAttrDef,
|
||||
instance_ids: List["Union[str, None]"],
|
||||
defaults: List[Any],
|
||||
label_widget: "Union[AttributeDefinitionsLabel, None]",
|
||||
):
|
||||
self.attr_def: AbstractAttrDef = attr_def
|
||||
self.instance_ids: List["Union[str, None]"] = instance_ids
|
||||
self.defaults: List[Any] = defaults
|
||||
self.label_widget: "Union[AttributeDefinitionsLabel, None]" = (
|
||||
label_widget
|
||||
)
|
||||
|
||||
|
||||
class _PublishAttrDefInfo:
|
||||
"""Helper class to store information about publish attribute definition."""
|
||||
def __init__(
|
||||
self,
|
||||
attr_def: AbstractAttrDef,
|
||||
plugin_name: str,
|
||||
instance_ids: List["Union[str, None]"],
|
||||
defaults: List[Any],
|
||||
label_widget: "Union[AttributeDefinitionsLabel, None]",
|
||||
):
|
||||
self.attr_def: AbstractAttrDef = attr_def
|
||||
self.plugin_name: str = plugin_name
|
||||
self.instance_ids: List["Union[str, None]"] = instance_ids
|
||||
self.defaults: List[Any] = defaults
|
||||
self.label_widget: "Union[AttributeDefinitionsLabel, None]" = (
|
||||
label_widget
|
||||
)
|
||||
|
||||
|
||||
class CreatorAttrsWidget(QtWidgets.QWidget):
|
||||
"""Widget showing creator specific attributes for selected instances.
|
||||
|
|
@ -51,8 +96,7 @@ class CreatorAttrsWidget(QtWidgets.QWidget):
|
|||
self._controller: AbstractPublisherFrontend = controller
|
||||
self._scroll_area = scroll_area
|
||||
|
||||
self._attr_def_id_to_instances = {}
|
||||
self._attr_def_id_to_attr_def = {}
|
||||
self._attr_def_info_by_id: Dict[str, _CreateAttrDefInfo] = {}
|
||||
self._current_instance_ids = set()
|
||||
|
||||
# To store content of scroll area to prevent garbage collection
|
||||
|
|
@ -81,8 +125,7 @@ class CreatorAttrsWidget(QtWidgets.QWidget):
|
|||
prev_content_widget.deleteLater()
|
||||
|
||||
self._content_widget = None
|
||||
self._attr_def_id_to_instances = {}
|
||||
self._attr_def_id_to_attr_def = {}
|
||||
self._attr_def_info_by_id = {}
|
||||
|
||||
result = self._controller.get_creator_attribute_definitions(
|
||||
self._current_instance_ids
|
||||
|
|
@ -97,9 +140,21 @@ class CreatorAttrsWidget(QtWidgets.QWidget):
|
|||
content_layout.setVerticalSpacing(INPUTS_LAYOUT_VSPACING)
|
||||
|
||||
row = 0
|
||||
for attr_def, instance_ids, values in result:
|
||||
widget = create_widget_for_attr_def(attr_def, content_widget)
|
||||
for attr_def, info_by_id in result:
|
||||
widget = create_widget_for_attr_def(
|
||||
attr_def, content_widget, handle_revert_to_default=False
|
||||
)
|
||||
default_values = []
|
||||
if attr_def.is_value_def:
|
||||
values = []
|
||||
for item in info_by_id.values():
|
||||
values.append(item["value"])
|
||||
# 'set' cannot be used for default values because they can
|
||||
# be unhashable types, e.g. 'list'.
|
||||
default = item["default"]
|
||||
if default not in default_values:
|
||||
default_values.append(default)
|
||||
|
||||
if len(values) == 1:
|
||||
value = values[0]
|
||||
if value is not None:
|
||||
|
|
@ -108,8 +163,13 @@ class CreatorAttrsWidget(QtWidgets.QWidget):
|
|||
widget.set_value(values, True)
|
||||
|
||||
widget.value_changed.connect(self._input_value_changed)
|
||||
self._attr_def_id_to_instances[attr_def.id] = instance_ids
|
||||
self._attr_def_id_to_attr_def[attr_def.id] = attr_def
|
||||
widget.revert_to_default_requested.connect(
|
||||
self._on_request_revert_to_default
|
||||
)
|
||||
attr_def_info = _CreateAttrDefInfo(
|
||||
attr_def, list(info_by_id), default_values, None
|
||||
)
|
||||
self._attr_def_info_by_id[attr_def.id] = attr_def_info
|
||||
|
||||
if not attr_def.visible:
|
||||
continue
|
||||
|
|
@ -121,10 +181,18 @@ class CreatorAttrsWidget(QtWidgets.QWidget):
|
|||
col_num = 2 - expand_cols
|
||||
|
||||
label = None
|
||||
is_overriden = False
|
||||
if attr_def.is_value_def:
|
||||
is_overriden = any(
|
||||
item["value"] != item["default"]
|
||||
for item in info_by_id.values()
|
||||
)
|
||||
label = attr_def.label or attr_def.key
|
||||
|
||||
if label:
|
||||
label_widget = QtWidgets.QLabel(label, self)
|
||||
label_widget = AttributeDefinitionsLabel(
|
||||
attr_def.id, label, self
|
||||
)
|
||||
tooltip = attr_def.tooltip
|
||||
if tooltip:
|
||||
label_widget.setToolTip(tooltip)
|
||||
|
|
@ -138,6 +206,11 @@ class CreatorAttrsWidget(QtWidgets.QWidget):
|
|||
)
|
||||
if not attr_def.is_label_horizontal:
|
||||
row += 1
|
||||
attr_def_info.label_widget = label_widget
|
||||
label_widget.set_overridden(is_overriden)
|
||||
label_widget.revert_to_default_requested.connect(
|
||||
self._on_request_revert_to_default
|
||||
)
|
||||
|
||||
content_layout.addWidget(
|
||||
widget, row, col_num, 1, expand_cols
|
||||
|
|
@ -159,20 +232,37 @@ class CreatorAttrsWidget(QtWidgets.QWidget):
|
|||
for instance_id, changes in event["instance_changes"].items():
|
||||
if (
|
||||
instance_id in self._current_instance_ids
|
||||
and "creator_attributes" not in changes
|
||||
and "creator_attributes" in changes
|
||||
):
|
||||
self._refresh_content()
|
||||
break
|
||||
|
||||
def _input_value_changed(self, value, attr_id):
|
||||
instance_ids = self._attr_def_id_to_instances.get(attr_id)
|
||||
attr_def = self._attr_def_id_to_attr_def.get(attr_id)
|
||||
if not instance_ids or not attr_def:
|
||||
attr_def_info = self._attr_def_info_by_id.get(attr_id)
|
||||
if attr_def_info is None:
|
||||
return
|
||||
|
||||
if attr_def_info.label_widget is not None:
|
||||
defaults = attr_def_info.defaults
|
||||
is_overriden = len(defaults) != 1 or value not in defaults
|
||||
attr_def_info.label_widget.set_overridden(is_overriden)
|
||||
|
||||
self._controller.set_instances_create_attr_values(
|
||||
instance_ids, attr_def.key, value
|
||||
attr_def_info.instance_ids,
|
||||
attr_def_info.attr_def.key,
|
||||
value
|
||||
)
|
||||
|
||||
def _on_request_revert_to_default(self, attr_id):
|
||||
attr_def_info = self._attr_def_info_by_id.get(attr_id)
|
||||
if attr_def_info is None:
|
||||
return
|
||||
self._controller.revert_instances_create_attr_values(
|
||||
attr_def_info.instance_ids,
|
||||
attr_def_info.attr_def.key,
|
||||
)
|
||||
self._refresh_content()
|
||||
|
||||
|
||||
class PublishPluginAttrsWidget(QtWidgets.QWidget):
|
||||
"""Widget showing publish plugin attributes for selected instances.
|
||||
|
|
@ -223,9 +313,7 @@ class PublishPluginAttrsWidget(QtWidgets.QWidget):
|
|||
self._controller: AbstractPublisherFrontend = controller
|
||||
self._scroll_area = scroll_area
|
||||
|
||||
self._attr_def_id_to_instances = {}
|
||||
self._attr_def_id_to_attr_def = {}
|
||||
self._attr_def_id_to_plugin_name = {}
|
||||
self._attr_def_info_by_id: Dict[str, _PublishAttrDefInfo] = {}
|
||||
|
||||
# Store content of scroll area to prevent garbage collection
|
||||
self._content_widget = None
|
||||
|
|
@ -254,9 +342,7 @@ class PublishPluginAttrsWidget(QtWidgets.QWidget):
|
|||
|
||||
self._content_widget = None
|
||||
|
||||
self._attr_def_id_to_instances = {}
|
||||
self._attr_def_id_to_attr_def = {}
|
||||
self._attr_def_id_to_plugin_name = {}
|
||||
self._attr_def_info_by_id = {}
|
||||
|
||||
result = self._controller.get_publish_attribute_definitions(
|
||||
self._current_instance_ids, self._context_selected
|
||||
|
|
@ -275,12 +361,10 @@ class PublishPluginAttrsWidget(QtWidgets.QWidget):
|
|||
content_layout.addStretch(1)
|
||||
|
||||
row = 0
|
||||
for plugin_name, attr_defs, all_plugin_values in result:
|
||||
plugin_values = all_plugin_values[plugin_name]
|
||||
|
||||
for plugin_name, attr_defs, plugin_values in result:
|
||||
for attr_def in attr_defs:
|
||||
widget = create_widget_for_attr_def(
|
||||
attr_def, content_widget
|
||||
attr_def, content_widget, handle_revert_to_default=False
|
||||
)
|
||||
visible_widget = attr_def.visible
|
||||
# Hide unknown values of publish plugins
|
||||
|
|
@ -290,6 +374,7 @@ class PublishPluginAttrsWidget(QtWidgets.QWidget):
|
|||
widget.setVisible(False)
|
||||
visible_widget = False
|
||||
|
||||
label_widget = None
|
||||
if visible_widget:
|
||||
expand_cols = 2
|
||||
if attr_def.is_value_def and attr_def.is_label_horizontal:
|
||||
|
|
@ -300,7 +385,12 @@ class PublishPluginAttrsWidget(QtWidgets.QWidget):
|
|||
if attr_def.is_value_def:
|
||||
label = attr_def.label or attr_def.key
|
||||
if label:
|
||||
label_widget = QtWidgets.QLabel(label, content_widget)
|
||||
label_widget = AttributeDefinitionsLabel(
|
||||
attr_def.id, label, content_widget
|
||||
)
|
||||
label_widget.revert_to_default_requested.connect(
|
||||
self._on_request_revert_to_default
|
||||
)
|
||||
tooltip = attr_def.tooltip
|
||||
if tooltip:
|
||||
label_widget.setToolTip(tooltip)
|
||||
|
|
@ -323,38 +413,76 @@ class PublishPluginAttrsWidget(QtWidgets.QWidget):
|
|||
continue
|
||||
|
||||
widget.value_changed.connect(self._input_value_changed)
|
||||
widget.revert_to_default_requested.connect(
|
||||
self._on_request_revert_to_default
|
||||
)
|
||||
|
||||
attr_values = plugin_values[attr_def.key]
|
||||
multivalue = len(attr_values) > 1
|
||||
instance_ids = []
|
||||
values = []
|
||||
instances = []
|
||||
for instance, value in attr_values:
|
||||
default_values = []
|
||||
is_overriden = False
|
||||
for (instance_id, value, default_value) in (
|
||||
plugin_values.get(attr_def.key, [])
|
||||
):
|
||||
instance_ids.append(instance_id)
|
||||
values.append(value)
|
||||
instances.append(instance)
|
||||
if not is_overriden and value != default_value:
|
||||
is_overriden = True
|
||||
# 'set' cannot be used for default values because they can
|
||||
# be unhashable types, e.g. 'list'.
|
||||
if default_value not in default_values:
|
||||
default_values.append(default_value)
|
||||
|
||||
self._attr_def_id_to_attr_def[attr_def.id] = attr_def
|
||||
self._attr_def_id_to_instances[attr_def.id] = instances
|
||||
self._attr_def_id_to_plugin_name[attr_def.id] = plugin_name
|
||||
multivalue = len(values) > 1
|
||||
|
||||
self._attr_def_info_by_id[attr_def.id] = _PublishAttrDefInfo(
|
||||
attr_def,
|
||||
plugin_name,
|
||||
instance_ids,
|
||||
default_values,
|
||||
label_widget,
|
||||
)
|
||||
|
||||
if multivalue:
|
||||
widget.set_value(values, multivalue)
|
||||
else:
|
||||
widget.set_value(values[0])
|
||||
|
||||
if label_widget is not None:
|
||||
label_widget.set_overridden(is_overriden)
|
||||
|
||||
self._scroll_area.setWidget(content_widget)
|
||||
self._content_widget = content_widget
|
||||
|
||||
def _input_value_changed(self, value, attr_id):
|
||||
instance_ids = self._attr_def_id_to_instances.get(attr_id)
|
||||
attr_def = self._attr_def_id_to_attr_def.get(attr_id)
|
||||
plugin_name = self._attr_def_id_to_plugin_name.get(attr_id)
|
||||
if not instance_ids or not attr_def or not plugin_name:
|
||||
attr_def_info = self._attr_def_info_by_id.get(attr_id)
|
||||
if attr_def_info is None:
|
||||
return
|
||||
|
||||
if attr_def_info.label_widget is not None:
|
||||
defaults = attr_def_info.defaults
|
||||
is_overriden = len(defaults) != 1 or value not in defaults
|
||||
attr_def_info.label_widget.set_overridden(is_overriden)
|
||||
|
||||
self._controller.set_instances_publish_attr_values(
|
||||
instance_ids, plugin_name, attr_def.key, value
|
||||
attr_def_info.instance_ids,
|
||||
attr_def_info.plugin_name,
|
||||
attr_def_info.attr_def.key,
|
||||
value
|
||||
)
|
||||
|
||||
def _on_request_revert_to_default(self, attr_id):
|
||||
attr_def_info = self._attr_def_info_by_id.get(attr_id)
|
||||
if attr_def_info is None:
|
||||
return
|
||||
|
||||
self._controller.revert_instances_publish_attr_values(
|
||||
attr_def_info.instance_ids,
|
||||
attr_def_info.plugin_name,
|
||||
attr_def_info.attr_def.key,
|
||||
)
|
||||
self._refresh_content()
|
||||
|
||||
def _on_instance_attr_defs_change(self, event):
|
||||
for instance_id in event.data:
|
||||
if (
|
||||
|
|
@ -370,7 +498,7 @@ class PublishPluginAttrsWidget(QtWidgets.QWidget):
|
|||
for instance_id, changes in event["instance_changes"].items():
|
||||
if (
|
||||
instance_id in self._current_instance_ids
|
||||
and "publish_attributes" not in changes
|
||||
and "publish_attributes" in changes
|
||||
):
|
||||
self._refresh_content()
|
||||
break
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Package declaring AYON addon 'core' version."""
|
||||
__version__ = "1.0.5+dev"
|
||||
__version__ = "1.0.6+dev"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
name = "core"
|
||||
title = "Core"
|
||||
version = "1.0.5+dev"
|
||||
version = "1.0.6+dev"
|
||||
|
||||
client_dir = "ayon_core"
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
[tool.poetry]
|
||||
name = "ayon-core"
|
||||
version = "1.0.5+dev"
|
||||
version = "1.0.6+dev"
|
||||
description = ""
|
||||
authors = ["Ynput Team <team@ynput.io>"]
|
||||
readme = "README.md"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue