From fb1f3d26f10e475cd11304a5ff0809c80c5bd92b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 8 Sep 2020 19:18:06 +0200 Subject: [PATCH] Fix #237 - Updating a look where the shader name changed Added cleanup of references with failed reference edits --- pype/plugins/maya/load/load_look.py | 52 +++++++++++++++++++-- pype/widgets/message_window.py | 71 ++++++++++++++++++++++++++++- 2 files changed, 119 insertions(+), 4 deletions(-) diff --git a/pype/plugins/maya/load/load_look.py b/pype/plugins/maya/load/load_look.py index b9c0d81104..d82978b1a1 100644 --- a/pype/plugins/maya/load/load_look.py +++ b/pype/plugins/maya/load/load_look.py @@ -3,6 +3,8 @@ from avalon import api, io import json import pype.hosts.maya.lib from collections import defaultdict +from pype.widgets.message_window import ScrollMessageBox +from Qt import QtWidgets class LookLoader(pype.hosts.maya.plugin.ReferenceLoader): @@ -44,12 +46,24 @@ class LookLoader(pype.hosts.maya.plugin.ReferenceLoader): self.update(container, representation) def update(self, container, representation): + """ + Called by Scene Inventory when look should be updated to current + version. + If any reference edits cannot be applied, eg. shader renamed and + material not present, reference is unloaded and cleaned. + All failed edits are highlighted to the user via message box. + Args: + container: object that has look to be updated + representation: (dict): relationship data to get proper + representation from DB and persisted + data in .json + Returns: + None + """ import os from maya import cmds - node = container["objectName"] - path = api.get_representation_path(representation) # Get reference node from container members @@ -127,13 +141,45 @@ class LookLoader(pype.hosts.maya.plugin.ReferenceLoader): with open(shader_relation, "r") as f: relationships = json.load(f) + # update of reference could result in failed edits - material is not + # present because of renaming etc. + failed_edits = cmds.referenceQuery(reference_node, + editStrings=True, + failedEdits=True, + successfulEdits=False) + + # highlight failed edits to user + if failed_edits: + shader_data = relationships.get("relationships", {}) + for rel in shader_data.values(): + for member in rel["members"]: + nodes.add(member['name']) + + # clean references - removes failed reference edits + cmds.file(unloadReference=reference_node) + cmds.file(cr=reference_node) # cleanReference + cmds.file(loadReference=reference_node) + + # reapply shading groups from json representation + pype.hosts.maya.lib.apply_shaders(relationships, + shader_nodes, + nodes) + + msg = ["During reference update some edits failed.", + "All successful edits were kept intact.\n", + "Failed and removed edits:"] + msg.extend(failed_edits) + msg = ScrollMessageBox(QtWidgets.QMessageBox.Warning, + "Some reference edit failed", + msg) + msg.exec_() + attributes = relationships.get("attributes", []) # region compute lookup nodes_by_id = defaultdict(list) for n in nodes: nodes_by_id[pype.hosts.maya.lib.get_id(n)].append(n) - pype.hosts.maya.lib.apply_attributes(attributes, nodes_by_id) # Update metadata diff --git a/pype/widgets/message_window.py b/pype/widgets/message_window.py index 41c709b933..f909c60710 100644 --- a/pype/widgets/message_window.py +++ b/pype/widgets/message_window.py @@ -1,4 +1,4 @@ -from Qt import QtWidgets +from Qt import QtWidgets, QtCore import sys import logging @@ -49,6 +49,17 @@ class Window(QtWidgets.QWidget): def message(title=None, message=None, level="info", parent=None): + """ + Produces centered dialog with specific level denoting severity + Args: + title: (string) dialog title + message: (string) message + level: (string) info|warning|critical + parent: (QtWidgets.QApplication) + + Returns: + None + """ app = parent if not app: app = QtWidgets.QApplication(sys.argv) @@ -68,3 +79,61 @@ def message(title=None, message=None, level="info", parent=None): # skip all possible issues that may happen feature is not crutial log.warning("Couldn't center message.", exc_info=True) # sys.exit(app.exec_()) + + +class ScrollMessageBox(QtWidgets.QDialog): + """ + Basic version of scrollable QMessageBox. No other existing dialog + implementation is scrollable. + Args: + icon: + title: + messages: of messages + cancelable: - True if Cancel button should be added + """ + def __init__(self, icon, title, messages, cancelable=False, + *args, **kwargs): + super(ScrollMessageBox, self).__init__() + self.setWindowTitle(title) + self.icon = icon + + self.setWindowFlags(QtCore.Qt.WindowTitleHint) + + layout = QtWidgets.QVBoxLayout(self) + + scroll_widget = QtWidgets.QScrollArea(self) + scroll_widget.setWidgetResizable(True) + content_widget = QtWidgets.QWidget(self) + scroll_widget.setWidget(content_widget) + + max_len = 0 + content_layout = QtWidgets.QVBoxLayout(content_widget) + for message in messages: + label_widget = QtWidgets.QLabel(message, content_widget) + content_layout.addWidget(label_widget) + max_len = max(max_len, len(message)) + + # guess size of scrollable area + max_width = QtWidgets.QApplication.desktop().availableGeometry().width + scroll_widget.setMinimumWidth(min(max_width, max_len * 6)) + layout.addWidget(scroll_widget) + + if not cancelable: # if no specific buttons OK only + buttons = QtWidgets.QDialogButtonBox.Ok + else: + buttons = QtWidgets.QDialogButtonBox.Ok | \ + QtWidgets.QDialogButtonBox.Cancel + + btn_box = QtWidgets.QDialogButtonBox(buttons) + btn_box.accepted.connect(self.accept) + + if cancelable: + btn_box.reject.connect(self.reject) + + btn = QtWidgets.QPushButton('Copy to clipboard') + btn.clicked.connect(lambda: QtWidgets.QApplication. + clipboard().setText("\n".join(messages))) + btn_box.addButton(btn, QtWidgets.QDialogButtonBox.NoRole) + + layout.addWidget(btn_box) + self.show()