diff --git a/openpype/tools/new_publisher/publish_log_viewer/__init__.py b/openpype/tools/new_publisher/publish_log_viewer/__init__.py index 156bd05305..eef88102cc 100644 --- a/openpype/tools/new_publisher/publish_log_viewer/__init__.py +++ b/openpype/tools/new_publisher/publish_log_viewer/__init__.py @@ -1,4 +1,4 @@ -from .widgets import ( +from .window import ( PublishLogViewerWindow ) diff --git a/openpype/tools/new_publisher/publish_log_viewer/constants.py b/openpype/tools/new_publisher/publish_log_viewer/constants.py new file mode 100644 index 0000000000..42a4d7be74 --- /dev/null +++ b/openpype/tools/new_publisher/publish_log_viewer/constants.py @@ -0,0 +1,19 @@ +from Qt import QtCore + + +ITEM_ID_ROLE = QtCore.Qt.UserRole + 1 +ITEM_IS_GROUP_ROLE = QtCore.Qt.UserRole + 2 +ITEM_LABEL_ROLE = QtCore.Qt.UserRole + 3 +PLUGIN_SKIPPED_ROLE = QtCore.Qt.UserRole + 4 +PLUGIN_ERRORED_ROLE = QtCore.Qt.UserRole + 5 +INSTANCE_REMOVED_ROLE = QtCore.Qt.UserRole + 6 + + +__all__ = ( + "ITEM_ID_ROLE", + "ITEM_IS_GROUP_ROLE", + "ITEM_LABEL_ROLE", + "PLUGIN_SKIPPED_ROLE", + "PLUGIN_ERRORED_ROLE", + "INSTANCE_REMOVED_ROLE" +) diff --git a/openpype/tools/new_publisher/publish_log_viewer/delegates.py b/openpype/tools/new_publisher/publish_log_viewer/delegates.py new file mode 100644 index 0000000000..ec4555e4b3 --- /dev/null +++ b/openpype/tools/new_publisher/publish_log_viewer/delegates.py @@ -0,0 +1,188 @@ +import platform +from Qt import QtWidgets, QtCore, QtGui +from constants import ( + ITEM_ID_ROLE, + ITEM_IS_GROUP_ROLE, + ITEM_LABEL_ROLE, + PLUGIN_SKIPPED_ROLE, + PLUGIN_ERRORED_ROLE, + INSTANCE_REMOVED_ROLE +) + +colors = { + "error": QtGui.QColor("#ff4a4a"), + "warning": QtGui.QColor("#ff9900"), + "ok": QtGui.QColor("#77AE24"), + "active": QtGui.QColor("#99CEEE"), + "idle": QtCore.Qt.white, + "inactive": QtGui.QColor("#888"), + "hover": QtGui.QColor(255, 255, 255, 5), + "selected": QtGui.QColor(255, 255, 255, 10), + "outline": QtGui.QColor("#333"), + "group": QtGui.QColor("#21252B"), + "group-hover": QtGui.QColor("#3c3c3c"), + "group-selected-hover": QtGui.QColor("#555555") +} + + +class ItemDelegate(QtWidgets.QStyledItemDelegate): + pass + + +class GroupItemDelegate(QtWidgets.QStyledItemDelegate): + """Generic delegate for instance header""" + + def __init__(self, parent): + super(GroupItemDelegate, self).__init__(parent) + self.item_delegate = ItemDelegate(parent) + + self._minus_pixmaps = {} + self._plus_pixmaps = {} + self._pix_offset_ratio = 1 / 3 + self._pix_stroke_size_ratio = 1 / 7 + + path_stroker = QtGui.QPainterPathStroker() + path_stroker.setCapStyle(QtCore.Qt.RoundCap) + path_stroker.setJoinStyle(QtCore.Qt.RoundJoin) + + self._path_stroker = path_stroker + + def _get_plus_pixmap(self, size): + pix = self._minus_pixmaps.get(size) + if pix is not None: + return pix + + pix = QtGui.QPixmap(size, size) + pix.fill(QtCore.Qt.transparent) + + offset = int(size * self._pix_offset_ratio) + pnt_1 = QtCore.QPoint(offset, int(size / 2)) + pnt_2 = QtCore.QPoint(size - offset, int(size / 2)) + pnt_3 = QtCore.QPoint(int(size / 2), offset) + pnt_4 = QtCore.QPoint(int(size / 2), size - offset) + path_1 = QtGui.QPainterPath(pnt_1) + path_1.lineTo(pnt_2) + path_2 = QtGui.QPainterPath(pnt_3) + path_2.lineTo(pnt_4) + + self._path_stroker.setWidth(size * self._pix_stroke_size_ratio) + stroked_path_1 = self._path_stroker.createStroke(path_1) + stroked_path_2 = self._path_stroker.createStroke(path_2) + + pix = QtGui.QPixmap(size, size) + pix.fill(QtCore.Qt.transparent) + + painter = QtGui.QPainter(pix) + painter.setRenderHint(QtGui.QPainter.Antialiasing) + painter.setPen(QtCore.Qt.transparent) + painter.setBrush(QtCore.Qt.white) + painter.drawPath(stroked_path_1) + painter.drawPath(stroked_path_2) + painter.end() + + self._minus_pixmaps[size] = pix + + return pix + + def _get_minus_pixmap(self, size): + pix = self._plus_pixmaps.get(size) + if pix is not None: + return pix + + offset = int(size / self._pix_offset_ratio) + pnt_1 = QtCore.QPoint(offset, int(size / 2)) + pnt_2 = QtCore.QPoint(size - offset, int(size / 2)) + path = QtGui.QPainterPath(pnt_1) + path.lineTo(pnt_2) + self._path_stroker.setWidth(size / self._pix_stroke_size_ratio) + stroked_path = self._path_stroker.createStroke(path) + + pix = QtGui.QPixmap(size, size) + pix.fill(QtCore.Qt.transparent) + + painter = QtGui.QPainter(pix) + painter.setRenderHint(QtGui.QPainter.Antialiasing) + painter.setPen(QtCore.Qt.transparent) + painter.setBrush(QtCore.Qt.white) + painter.drawPath(stroked_path) + painter.end() + + self._plus_pixmaps[size] = pix + + return pix + + def paint(self, painter, option, index): + if index.data(ITEM_IS_GROUP_ROLE): + self.group_item_paint(painter, option, index) + else: + self.item_delegate.paint(painter, option, index) + + def group_item_paint(self, painter, option, index): + """Paint text + _ + My label + """ + self.initStyleOption(option, index) + + widget = option.widget + if widget: + style = widget.style() + else: + style = QtWidgets.QApplicaion.style() + _rect = style.proxy().subElementRect( + style.SE_ItemViewItemText, option, widget + ) + + bg_rect = QtCore.QRectF(option.rect) + bg_rect.setY(_rect.y()) + bg_rect.setHeight(_rect.height()) + + expander_rect = QtCore.QRectF(bg_rect) + expander_rect.setWidth(expander_rect.height() + 5) + + label_rect = QtCore.QRectF( + expander_rect.x() + expander_rect.width(), + expander_rect.y(), + bg_rect.width() - expander_rect.width(), + expander_rect.height() + ) + + bg_path = QtGui.QPainterPath() + radius = (bg_rect.height() / 2) - 0.01 + bg_path.addRoundedRect(bg_rect, radius, radius) + + painter.fillPath(bg_path, colors["group"]) + + selected = option.state & QtWidgets.QStyle.State_Selected + hovered = option.state & QtWidgets.QStyle.State_MouseOver + + if selected and hovered: + painter.fillPath(bg_path, colors["selected"]) + elif hovered: + painter.fillPath(bg_path, colors["hover"]) + + expanded = self.parent().isExpanded(index) + if expanded: + expander_icon = self._get_minus_pixmap(expander_rect.height()) + else: + expander_icon = self._get_plus_pixmap(expander_rect.height()) + + label = index.data(QtCore.Qt.DisplayRole) + label = option.fontMetrics.elidedText( + label, QtCore.Qt.ElideRight, label_rect.width() + ) + + # Maintain reference to state, so we can restore it once we're done + painter.save() + pix_point = QtCore.QPoint( + expander_rect.center().x() - int(expander_icon.width() / 2), + expander_rect.top() + ) + painter.drawPixmap(pix_point, expander_icon) + + # Draw label + painter.setFont(option.font) + painter.drawText(label_rect, QtCore.Qt.AlignVCenter, label) + + # Ok, we're done, tidy up. + painter.restore() diff --git a/openpype/tools/new_publisher/publish_log_viewer/model.py b/openpype/tools/new_publisher/publish_log_viewer/model.py new file mode 100644 index 0000000000..ccc6cb4946 --- /dev/null +++ b/openpype/tools/new_publisher/publish_log_viewer/model.py @@ -0,0 +1,197 @@ +import uuid +from Qt import QtCore, QtGui + +import pyblish.api + +from .constants import ( + ITEM_ID_ROLE, + ITEM_IS_GROUP_ROLE, + ITEM_LABEL_ROLE, + PLUGIN_SKIPPED_ROLE, + PLUGIN_ERRORED_ROLE, + INSTANCE_REMOVED_ROLE +) + + +class InstancesModel(QtGui.QStandardItemModel): + def __init__(self, *args, **kwargs): + super(InstancesModel, self).__init__(*args, **kwargs) + + self._items_by_id = {} + self._plugin_items_by_id = {} + + def get_items_by_id(self): + return self._items_by_id + + def set_report(self, report_item): + self.clear() + self._items_by_id.clear() + self._plugin_items_by_id.clear() + + root_item = self.invisibleRootItem() + + families = set(report_item.instance_items_by_family.keys()) + families.remove(None) + all_families = list(sorted(families)) + all_families.insert(0, None) + + family_items = [] + for family in all_families: + items = [] + instance_items = report_item.instance_items_by_family[family] + for instance_item in instance_items: + item = QtGui.QStandardItem(instance_item.label) + item.setData(instance_item.label, ITEM_LABEL_ROLE) + item.setData(instance_item.id, ITEM_ID_ROLE) + item.setData(instance_item.removed, INSTANCE_REMOVED_ROLE) + item.setData(False, ITEM_IS_GROUP_ROLE) + items.append(item) + self._items_by_id[instance_item.id] = item + self._plugin_items_by_id[instance_item.id] = item + + if family is None: + family_items.extend(items) + continue + + family_item = QtGui.QStandardItem(family) + family_item.setData(family, ITEM_LABEL_ROLE) + family_item.setFlags(QtCore.Qt.ItemIsEnabled) + family_id = uuid.uuid4() + family_item.setData(family_id, ITEM_ID_ROLE) + family_item.setData(True, ITEM_IS_GROUP_ROLE) + family_item.appendRows(items) + family_items.append(family_item) + self._items_by_id[family_id] = family_item + + root_item.appendRows(family_items) + + +class InstanceProxyModel(QtCore.QSortFilterProxyModel): + def __init__(self, *args, **kwargs): + super(InstanceProxyModel, self).__init__(*args, **kwargs) + + self._ignore_removed = True + + @property + def ignore_removed(self): + return self._ignore_removed + + def set_ignore_removed(self, value): + if value == self._ignore_removed: + return + self._ignore_removed = value + + if self.sourceModel(): + self.invalidateFilter() + + def filterAcceptsRow(self, row, parent): + model = self.sourceModel() + source_index = model.index(row, 0, parent) + if source_index.data(ITEM_IS_GROUP_ROLE): + return model.rowCount(source_index) > 0 + + if self._ignore_removed and source_index.data(PLUGIN_SKIPPED_ROLE): + return False + return True + + +class PluginsModel(QtGui.QStandardItemModel): + order_label_mapping = ( + (pyblish.api.CollectorOrder + 0.5, "Collect"), + (pyblish.api.ValidatorOrder + 0.5, "Validate"), + (pyblish.api.ExtractorOrder + 0.5, "Extract"), + (pyblish.api.IntegratorOrder + 0.5, "Integrate"), + (None, "Other") + ) + + def __init__(self, *args, **kwargs): + super(PluginsModel, self).__init__(*args, **kwargs) + + self._items_by_id = {} + self._plugin_items_by_id = {} + + def get_items_by_id(self): + return self._items_by_id + + def set_report(self, report_item): + self.clear() + self._items_by_id.clear() + self._plugin_items_by_id.clear() + + root_item = self.invisibleRootItem() + + labels_iter = iter(self.order_label_mapping) + cur_order, cur_label = next(labels_iter) + cur_plugin_items = [] + + plugin_items_by_group_labels = [] + plugin_items_by_group_labels.append((cur_label, cur_plugin_items)) + for plugin_id in report_item.plugins_id_order: + plugin_item = report_item.plugins_items_by_id[plugin_id] + if cur_order is not None and plugin_item.order >= cur_order: + cur_order, cur_label = next(labels_iter) + cur_plugin_items = [] + plugin_items_by_group_labels.append( + (cur_label, cur_plugin_items) + ) + + cur_plugin_items.append(plugin_item) + + group_items = [] + for group_label, plugin_items in plugin_items_by_group_labels: + group_id = uuid.uuid4() + group_item = QtGui.QStandardItem(group_label) + group_item.setData(group_label, ITEM_LABEL_ROLE) + group_item.setData(group_id, ITEM_ID_ROLE) + group_item.setData(True, ITEM_IS_GROUP_ROLE) + group_item.setFlags(QtCore.Qt.ItemIsEnabled) + group_items.append(group_item) + + self._items_by_id[group_id] = group_item + + if not plugin_items: + continue + + items = [] + for plugin_item in plugin_items: + item = QtGui.QStandardItem(plugin_item.label) + item.setData(False, ITEM_IS_GROUP_ROLE) + item.setData(plugin_item.label, ITEM_LABEL_ROLE) + item.setData(plugin_item.id, ITEM_ID_ROLE) + item.setData(plugin_item.skipped, PLUGIN_SKIPPED_ROLE) + item.setData(plugin_item.errored, PLUGIN_ERRORED_ROLE) + items.append(item) + self._items_by_id[plugin_item.id] = item + self._plugin_items_by_id[plugin_item.id] = item + group_item.appendRows(items) + + root_item.appendRows(group_items) + + +class PluginProxyModel(QtCore.QSortFilterProxyModel): + def __init__(self, *args, **kwargs): + super(PluginProxyModel, self).__init__(*args, **kwargs) + + self._ignore_skipped = True + + @property + def ignore_skipped(self): + return self._ignore_skipped + + def set_ignore_skipped(self, value): + if value == self._ignore_skipped: + return + self._ignore_skipped = value + + if self.sourceModel(): + self.invalidateFilter() + + def filterAcceptsRow(self, row, parent): + model = self.sourceModel() + source_index = model.index(row, 0, parent) + if source_index.data(ITEM_IS_GROUP_ROLE): + return model.rowCount(source_index) > 0 + + if self._ignore_skipped and source_index.data(PLUGIN_SKIPPED_ROLE): + return False + return True diff --git a/openpype/tools/new_publisher/publish_log_viewer/widgets.py b/openpype/tools/new_publisher/publish_log_viewer/widgets.py index 6043344bda..d45f88dfb6 100644 --- a/openpype/tools/new_publisher/publish_log_viewer/widgets.py +++ b/openpype/tools/new_publisher/publish_log_viewer/widgets.py @@ -5,11 +5,16 @@ from Qt import QtWidgets, QtCore, QtGui import pyblish.api -ITEM_ID_ROLE = QtCore.Qt.UserRole + 1 -ITEM_IS_GROUP_ROLE = QtCore.Qt.UserRole + 2 -PLUGIN_SKIPPED_ROLE = QtCore.Qt.UserRole + 3 -PLUGIN_ERRORED_ROLE = QtCore.Qt.UserRole + 4 -INSTANCE_REMOVED_ROLE = QtCore.Qt.UserRole + 5 +from .constants import ( + ITEM_ID_ROLE +) +from .delegates import GroupItemDelegate +from .model import ( + InstancesModel, + InstanceProxyModel, + PluginsModel, + PluginProxyModel +) class PluginItem: @@ -96,154 +101,6 @@ class PublishReport: self.logs = all_logs -class InstancesModel(QtGui.QStandardItemModel): - def set_report(self, report_item): - self.clear() - - root_item = self.invisibleRootItem() - - families = set(report_item.instance_items_by_family.keys()) - families.remove(None) - all_families = list(sorted(families)) - all_families.insert(0, None) - - family_items = [] - for family in all_families: - items = [] - instance_items = report_item.instance_items_by_family[family] - for instance_item in instance_items: - item = QtGui.QStandardItem(instance_item.label) - item.setData(instance_item.id, ITEM_ID_ROLE) - item.setData(instance_item.removed, INSTANCE_REMOVED_ROLE) - item.setData(False, ITEM_IS_GROUP_ROLE) - items.append(item) - - if family is None: - family_items.extend(items) - continue - - family_item = QtGui.QStandardItem(family) - family_item.setFlags(QtCore.Qt.ItemIsEnabled) - family_item.setData(True, ITEM_IS_GROUP_ROLE) - family_item.appendRows(items) - family_items.append(family_item) - - root_item.appendRows(family_items) - - -class InstanceProxyModel(QtCore.QSortFilterProxyModel): - def __init__(self, *args, **kwargs): - super(InstanceProxyModel, self).__init__(*args, **kwargs) - - self._ignore_removed = True - - @property - def ignore_removed(self): - return self._ignore_removed - - def set_ignore_removed(self, value): - if value == self._ignore_removed: - return - self._ignore_removed = value - - if self.sourceModel(): - self.invalidateFilter() - - def filterAcceptsRow(self, row, parent): - model = self.sourceModel() - source_index = model.index(row, 0, parent) - if source_index.data(ITEM_IS_GROUP_ROLE): - return model.rowCount(source_index) > 0 - - if self._ignore_removed and source_index.data(PLUGIN_SKIPPED_ROLE): - return False - return True - - -class PluginsModel(QtGui.QStandardItemModel): - order_label_mapping = ( - (pyblish.api.CollectorOrder + 0.5, "Collect"), - (pyblish.api.ValidatorOrder + 0.5, "Validate"), - (pyblish.api.ExtractorOrder + 0.5, "Extract"), - (pyblish.api.IntegratorOrder + 0.5, "Integrate"), - (None, "Other") - ) - - def set_report(self, report_item): - self.clear() - - root_item = self.invisibleRootItem() - - labels_iter = iter(self.order_label_mapping) - cur_order, cur_label = next(labels_iter) - cur_plugin_items = [] - - plugin_items_by_group_labels = [] - plugin_items_by_group_labels.append((cur_label, cur_plugin_items)) - for plugin_id in report_item.plugins_id_order: - plugin_item = report_item.plugins_items_by_id[plugin_id] - if cur_order is not None and plugin_item.order >= cur_order: - cur_order, cur_label = next(labels_iter) - cur_plugin_items = [] - plugin_items_by_group_labels.append( - (cur_label, cur_plugin_items) - ) - - cur_plugin_items.append(plugin_item) - - group_items = [] - for group_label, plugin_items in plugin_items_by_group_labels: - group_item = QtGui.QStandardItem(group_label) - group_item.setData(True, ITEM_IS_GROUP_ROLE) - group_item.setFlags(QtCore.Qt.ItemIsEnabled) - group_items.append(group_item) - - if not plugin_items: - continue - - items = [] - for plugin_item in plugin_items: - item = QtGui.QStandardItem(plugin_item.label) - item.setData(False, ITEM_IS_GROUP_ROLE) - item.setData(False, ITEM_IS_GROUP_ROLE) - item.setData(plugin_item.id, ITEM_ID_ROLE) - item.setData(plugin_item.skipped, PLUGIN_SKIPPED_ROLE) - item.setData(plugin_item.errored, PLUGIN_ERRORED_ROLE) - items.append(item) - group_item.appendRows(items) - - root_item.appendRows(group_items) - - -class PluginProxyModel(QtCore.QSortFilterProxyModel): - def __init__(self, *args, **kwargs): - super(PluginProxyModel, self).__init__(*args, **kwargs) - - self._ignore_skipped = True - - @property - def ignore_skipped(self): - return self._ignore_skipped - - def set_ignore_skipped(self, value): - if value == self._ignore_skipped: - return - self._ignore_skipped = value - - if self.sourceModel(): - self.invalidateFilter() - - def filterAcceptsRow(self, row, parent): - model = self.sourceModel() - source_index = model.index(row, 0, parent) - if source_index.data(ITEM_IS_GROUP_ROLE): - return model.rowCount(source_index) > 0 - - if self._ignore_skipped and source_index.data(PLUGIN_SKIPPED_ROLE): - return False - return True - - class DetailsWidget(QtWidgets.QWidget): def __init__(self, parent): super(DetailsWidget, self).__init__(parent) @@ -297,28 +154,38 @@ class PublishLogViewerWidget(QtWidgets.QWidget): removed_instances_check.setChecked(instances_proxy.ignore_removed) instances_view = QtWidgets.QTreeView(self) + instances_view.setObjectName("PublishDetailViews") instances_view.setModel(instances_proxy) - # instances_view.setIndentation(0) + instances_view.setIndentation(0) instances_view.setHeaderHidden(True) instances_view.setEditTriggers(QtWidgets.QTreeView.NoEditTriggers) + instances_delegate = GroupItemDelegate(instances_view) + instances_view.setItemDelegate(instances_delegate) + skipped_plugins_check = QtWidgets.QCheckBox( "Hide skipped plugins", self ) skipped_plugins_check.setChecked(plugins_proxy.ignore_skipped) plugins_view = QtWidgets.QTreeView(self) + plugins_view.setObjectName("PublishDetailViews") plugins_view.setModel(plugins_proxy) - # plugins_view.setIndentation(0) + plugins_view.setIndentation(0) plugins_view.setHeaderHidden(True) plugins_view.setEditTriggers(QtWidgets.QTreeView.NoEditTriggers) + plugins_delegate = GroupItemDelegate(plugins_view) + plugins_view.setItemDelegate(plugins_delegate) + details_widget = DetailsWidget(self) layout = QtWidgets.QGridLayout(self) + # Row 1 layout.addWidget(removed_instances_check, 0, 0) - layout.addWidget(instances_view, 1, 0) layout.addWidget(skipped_plugins_check, 0, 1) + # Row 2 + layout.addWidget(instances_view, 1, 0) layout.addWidget(plugins_view, 1, 1) layout.addWidget(details_widget, 1, 2) @@ -345,6 +212,9 @@ class PublishLogViewerWidget(QtWidgets.QWidget): self._instances_model = instances_model self._instances_proxy = instances_proxy + self._instances_delegate = instances_delegate + self._plugins_delegate = plugins_delegate + self._skipped_plugins_check = skipped_plugins_check self._plugins_view = plugins_view self._plugins_model = plugins_model @@ -358,7 +228,6 @@ class PublishLogViewerWidget(QtWidgets.QWidget): self._instances_model.set_report(report_item) self._plugins_model.set_report(report_item) - self._details_widget.set_logs(report_item.logs) self._ignore_selection_changes = False diff --git a/openpype/tools/new_publisher/publish_log_viewer/window.py b/openpype/tools/new_publisher/publish_log_viewer/window.py index 517b0aadee..1bfca3b86b 100644 --- a/openpype/tools/new_publisher/publish_log_viewer/window.py +++ b/openpype/tools/new_publisher/publish_log_viewer/window.py @@ -1,27 +1,4 @@ -import os -import sys -import json -import copy -import uuid -import collections - -openpype_dir = r"C:\Users\jakub.trllo\Desktop\pype\pype3" -mongo_url = "mongodb://localhost:2707" - -os.environ["OPENPYPE_MONGO"] = mongo_url -os.environ["AVALON_MONGO"] = mongo_url -os.environ["OPENPYPE_DATABASE_NAME"] = "openpype" -os.environ["AVALON_CONFIG"] = "openpype" -os.environ["AVALON_TIMEOUT"] = "1000" -os.environ["AVALON_DB"] = "avalon" -for path in [ - openpype_dir, - r"{}\repos\avalon-core".format(openpype_dir), - r"{}\.venv\Lib\site-packages".format(openpype_dir) -]: - sys.path.append(path) - -from Qt import QtWidgets, QtCore, QtGui +from Qt import QtWidgets from openpype import style if __package__: @@ -50,22 +27,3 @@ class PublishLogViewerWindow(QtWidgets.QWidget): def set_report(self, report_data): self._main_widget.set_report(report_data) - - -def main(): - """Main function for testing purposes.""" - app = QtWidgets.QApplication([]) - window = PublishLogViewerWindow() - - log_path = os.path.join(os.path.dirname(__file__), "logs.json") - with open(log_path, "r") as file_stream: - report_data = json.load(file_stream) - - window.set_report(report_data) - - window.show() - app.exec_() - - -if __name__ == "__main__": - main()