mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 16:34:53 +01:00
Merge pull request #514 from kalisp/bugfix/237-Updating_a_look_where_the_shader_name_changed_leaves_the_geo_without_a_shader
Maya: Updating a look where the shader name changed, leaves the geo without a shader
This commit is contained in:
commit
3e265c5ba0
2 changed files with 186 additions and 50 deletions
|
|
@ -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,18 +46,33 @@ 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
|
||||
members = cmds.sets(node, query=True, nodesOnly=True)
|
||||
reference_node = self._get_reference_node(members)
|
||||
|
||||
shader_nodes = cmds.ls(members, type='shadingEngine')
|
||||
orig_nodes = set(self._get_nodes_with_shader(shader_nodes))
|
||||
|
||||
file_type = {
|
||||
"ma": "mayaAscii",
|
||||
"mb": "mayaBinary",
|
||||
|
|
@ -66,6 +83,104 @@ class LookLoader(pype.hosts.maya.plugin.ReferenceLoader):
|
|||
|
||||
assert os.path.exists(path), "%s does not exist." % path
|
||||
|
||||
self._load_reference(file_type, node, path, reference_node)
|
||||
|
||||
# Remove any placeHolderList attribute entries from the set that
|
||||
# are remaining from nodes being removed from the referenced file.
|
||||
members = cmds.sets(node, query=True)
|
||||
invalid = [x for x in members if ".placeHolderList" in x]
|
||||
if invalid:
|
||||
cmds.sets(invalid, remove=node)
|
||||
|
||||
# get new applied shaders and nodes from new version
|
||||
shader_nodes = cmds.ls(members, type='shadingEngine')
|
||||
nodes = set(self._get_nodes_with_shader(shader_nodes))
|
||||
|
||||
json_representation = io.find_one({
|
||||
"type": "representation",
|
||||
"parent": representation['parent'],
|
||||
"name": "json"
|
||||
})
|
||||
|
||||
# Load relationships
|
||||
shader_relation = api.get_representation_path(json_representation)
|
||||
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:
|
||||
# clean references - removes failed reference edits
|
||||
cmds.file(cr=reference_node) # cleanReference
|
||||
|
||||
# reapply shading groups from json representation on orig nodes
|
||||
pype.hosts.maya.lib.apply_shaders(relationships,
|
||||
shader_nodes,
|
||||
orig_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
|
||||
cmds.setAttr("{}.representation".format(node),
|
||||
str(representation["_id"]),
|
||||
type="string")
|
||||
|
||||
def _get_nodes_with_shader(self, shader_nodes):
|
||||
"""
|
||||
Returns list of nodes belonging to specific shaders
|
||||
Args:
|
||||
shader_nodes: <list> of Shader groups
|
||||
Returns
|
||||
<list> node names
|
||||
"""
|
||||
import maya.cmds as cmds
|
||||
# Get container members
|
||||
|
||||
nodes_list = []
|
||||
for shader in shader_nodes:
|
||||
connections = cmds.listConnections(cmds.listHistory(shader, f=1),
|
||||
type='mesh')
|
||||
if connections:
|
||||
for connection in connections:
|
||||
nodes_list.extend(cmds.listRelatives(connection,
|
||||
shapes=True))
|
||||
return nodes_list
|
||||
|
||||
def _load_reference(self, file_type, node, path, reference_node):
|
||||
"""
|
||||
Load reference from 'path' on 'reference_node'. Used when change
|
||||
of look (version/update) is triggered.
|
||||
Args:
|
||||
file_type: extension of referenced file
|
||||
node:
|
||||
path: (string) location of referenced file
|
||||
reference_node: (string) - name of node that should be applied
|
||||
on
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
import maya.cmds as cmds
|
||||
try:
|
||||
content = cmds.file(path,
|
||||
loadReference=reference_node,
|
||||
|
|
@ -86,57 +201,10 @@ class LookLoader(pype.hosts.maya.plugin.ReferenceLoader):
|
|||
raise
|
||||
|
||||
self.log.warning("Ignoring file read error:\n%s", exc)
|
||||
|
||||
# Fix PLN-40 for older containers created with Avalon that had the
|
||||
# `.verticesOnlySet` set to True.
|
||||
if cmds.getAttr("{}.verticesOnlySet".format(node)):
|
||||
self.log.info("Setting %s.verticesOnlySet to False", node)
|
||||
cmds.setAttr("{}.verticesOnlySet".format(node), False)
|
||||
|
||||
# Add new nodes of the reference to the container
|
||||
cmds.sets(content, forceElement=node)
|
||||
|
||||
# Remove any placeHolderList attribute entries from the set that
|
||||
# are remaining from nodes being removed from the referenced file.
|
||||
members = cmds.sets(node, query=True)
|
||||
invalid = [x for x in members if ".placeHolderList" in x]
|
||||
if invalid:
|
||||
cmds.sets(invalid, remove=node)
|
||||
|
||||
# Get container members
|
||||
shader_nodes = cmds.ls(members, type='shadingEngine')
|
||||
|
||||
nodes_list = []
|
||||
for shader in shader_nodes:
|
||||
connections = cmds.listConnections(cmds.listHistory(shader, f=1),
|
||||
type='mesh')
|
||||
if connections:
|
||||
for connection in connections:
|
||||
nodes_list.extend(cmds.listRelatives(connection,
|
||||
shapes=True))
|
||||
nodes = set(nodes_list)
|
||||
|
||||
json_representation = io.find_one({
|
||||
"type": "representation",
|
||||
"parent": representation['parent'],
|
||||
"name": "json"
|
||||
})
|
||||
|
||||
# Load relationships
|
||||
shader_relation = api.get_representation_path(json_representation)
|
||||
with open(shader_relation, "r") as f:
|
||||
relationships = json.load(f)
|
||||
|
||||
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
|
||||
cmds.setAttr("{}.representation".format(node),
|
||||
str(representation["_id"]),
|
||||
type="string")
|
||||
|
|
|
|||
|
|
@ -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,60 @@ 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: <QtWidgets.QMessageBox.Icon>
|
||||
title: <string>
|
||||
messages: <list> of messages
|
||||
cancelable: <boolean> - True if Cancel button should be added
|
||||
"""
|
||||
def __init__(self, icon, title, messages, cancelable=False):
|
||||
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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue