diff --git a/client/ayon_core/style/data.json b/client/ayon_core/style/data.json index 7389387d97..24629ec085 100644 --- a/client/ayon_core/style/data.json +++ b/client/ayon_core/style/data.json @@ -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", diff --git a/client/ayon_core/style/style.css b/client/ayon_core/style/style.css index 3d84d917a4..0d1d4f710e 100644 --- a/client/ayon_core/style/style.css +++ b/client/ayon_core/style/style.css @@ -44,6 +44,10 @@ QLabel { background: transparent; } +QLabel[overriden="1"] { + color: {color:font-overridden}; +} + /* Inputs */ QAbstractSpinBox, QLineEdit, QPlainTextEdit, QTextEdit { border: 1px solid {color:border}; diff --git a/client/ayon_core/tools/publisher/abstract.py b/client/ayon_core/tools/publisher/abstract.py index a6ae93cecd..7fad2b8176 100644 --- a/client/ayon_core/tools/publisher/abstract.py +++ b/client/ayon_core/tools/publisher/abstract.py @@ -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 @@ -383,7 +383,7 @@ class AbstractPublisherFrontend(AbstractPublisherCommon): ) -> List[Tuple[ str, List[AbstractAttrDef], - Dict[str, List[Tuple[str, Any]]] + Dict[str, List[Tuple[str, Any, Any]]] ]]: pass diff --git a/client/ayon_core/tools/publisher/models/create.py b/client/ayon_core/tools/publisher/models/create.py index 9c13d8ae2f..8763d79712 100644 --- a/client/ayon_core/tools/publisher/models/create.py +++ b/client/ayon_core/tools/publisher/models/create.py @@ -769,7 +769,7 @@ class CreateModel: 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,12 +796,23 @@ 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( @@ -835,7 +846,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 +876,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 +904,7 @@ class CreateModel: output.append(( plugin_name, attr_defs_by_plugin_name[plugin_name], - all_plugin_values + all_plugin_values[plugin_name], )) return output diff --git a/client/ayon_core/tools/publisher/widgets/product_attributes.py b/client/ayon_core/tools/publisher/widgets/product_attributes.py index 61d5ca111d..b0b2640640 100644 --- a/client/ayon_core/tools/publisher/widgets/product_attributes.py +++ b/client/ayon_core/tools/publisher/widgets/product_attributes.py @@ -1,6 +1,10 @@ +import typing +from typing import Dict, List, Any + from qtpy import QtWidgets, QtCore -from ayon_core.lib.attribute_definitions import UnknownDef +from ayon_core.lib.attribute_definitions import AbstractAttrDef, UnknownDef +from ayon_core.tools.utils import set_style_property from ayon_core.tools.attribute_defs import create_widget_for_attr_def from ayon_core.tools.publisher.abstract import AbstractPublisherFrontend from ayon_core.tools.publisher.constants import ( @@ -8,6 +12,49 @@ from ayon_core.tools.publisher.constants import ( INPUTS_LAYOUT_VSPACING, ) +if typing.TYPE_CHECKING: + from typing import Union + + +def _set_label_overriden(label: QtWidgets.QLabel, overriden: bool): + set_style_property( + label, + "overriden", + "1" if overriden else "" + ) + + +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[None, QtWidgets.QLabel]", + ): + self.attr_def: AbstractAttrDef = attr_def + self.instance_ids: List["Union[str, None]"] = instance_ids + self.defaults: List[Any] = defaults + self.label_widget: "Union[None, QtWidgets.QLabel]" = 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[None, QtWidgets.QLabel]", + ): + 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[None, QtWidgets.QLabel]" = label_widget + class CreatorAttrsWidget(QtWidgets.QWidget): """Widget showing creator specific attributes for selected instances. @@ -51,8 +98,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 +127,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 +142,19 @@ class CreatorAttrsWidget(QtWidgets.QWidget): content_layout.setVerticalSpacing(INPUTS_LAYOUT_VSPACING) row = 0 - for attr_def, instance_ids, values in result: + for attr_def, info_by_id in result: widget = create_widget_for_attr_def(attr_def, content_widget) + 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,10 @@ 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 + 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,8 +178,14 @@ 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) tooltip = attr_def.tooltip @@ -138,6 +201,8 @@ class CreatorAttrsWidget(QtWidgets.QWidget): ) if not attr_def.is_label_horizontal: row += 1 + attr_def_info.label_widget = label_widget + _set_label_overriden(label_widget, is_overriden) content_layout.addWidget( widget, row, col_num, 1, expand_cols @@ -165,12 +230,19 @@ class CreatorAttrsWidget(QtWidgets.QWidget): 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 + _set_label_overriden(attr_def_info.label_widget, 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 ) @@ -223,9 +295,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 +324,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,9 +343,7 @@ 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 @@ -290,6 +356,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: @@ -324,35 +391,58 @@ class PublishPluginAttrsWidget(QtWidgets.QWidget): widget.value_changed.connect(self._input_value_changed) - 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: + _set_label_overriden(label_widget, 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 + _set_label_overriden(attr_def_info.label_widget, 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_instance_attr_defs_change(self, event):