mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 08:24:53 +01:00
Maya: Yeti Validate Rig Input - OP-3454 (#4554)
* Collect input_SET children in instance. * Fix docs. * Only validate yeti if there are nodes in the scene. * Revert code * Remove connection logic from loader * Connection inventory action * Hound * Revert "Collect input_SET children in instance." This reverts commit 052e65ca1befb19049ee9f02f472d20cf78d8dc1. * Update docs * Update openpype/hosts/maya/plugins/inventory/connect_yeti_rig.py Co-authored-by: Roy Nieterau <roy_nieterau@hotmail.com> * Update openpype/hosts/maya/plugins/inventory/connect_yeti_rig.py Co-authored-by: Roy Nieterau <roy_nieterau@hotmail.com> * Update openpype/hosts/maya/plugins/inventory/connect_yeti_rig.py Co-authored-by: Roy Nieterau <roy_nieterau@hotmail.com> * Update website/docs/artist_hosts_maya_yeti.md Co-authored-by: Roy Nieterau <roy_nieterau@hotmail.com> * BigRoy feedback * Hound * Fix typo * Update openpype/hosts/maya/plugins/inventory/connect_yeti_rig.py Co-authored-by: Roy Nieterau <roy_nieterau@hotmail.com> * Update openpype/hosts/maya/plugins/publish/validate_yeti_renderscript_callbacks.py Co-authored-by: Roy Nieterau <roy_nieterau@hotmail.com> * Update openpype/hosts/maya/plugins/inventory/connect_yeti_rig.py Co-authored-by: Roy Nieterau <roy_nieterau@hotmail.com> * Dont use AVALON_PROJECT * Hound * Update openpype/hosts/maya/plugins/publish/validate_yeti_renderscript_callbacks.py Co-authored-by: Roy Nieterau <roy_nieterau@hotmail.com> --------- Co-authored-by: Roy Nieterau <roy_nieterau@hotmail.com>
This commit is contained in:
parent
8d828f4c41
commit
a13f80ef97
10 changed files with 258 additions and 105 deletions
178
openpype/hosts/maya/plugins/inventory/connect_yeti_rig.py
Normal file
178
openpype/hosts/maya/plugins/inventory/connect_yeti_rig.py
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
import os
|
||||
import json
|
||||
from collections import defaultdict
|
||||
|
||||
from maya import cmds
|
||||
|
||||
from openpype.pipeline import (
|
||||
InventoryAction, get_representation_context, get_representation_path
|
||||
)
|
||||
from openpype.hosts.maya.api.lib import get_container_members, get_id
|
||||
|
||||
|
||||
class ConnectYetiRig(InventoryAction):
|
||||
"""Connect Yeti Rig with an animation or pointcache."""
|
||||
|
||||
label = "Connect Yeti Rig"
|
||||
icon = "link"
|
||||
color = "white"
|
||||
|
||||
def process(self, containers):
|
||||
# Validate selection is more than 1.
|
||||
message = (
|
||||
"Only 1 container selected. 2+ containers needed for this action."
|
||||
)
|
||||
if len(containers) == 1:
|
||||
self.display_warning(message)
|
||||
return
|
||||
|
||||
# Categorize containers by family.
|
||||
containers_by_family = defaultdict(list)
|
||||
for container in containers:
|
||||
family = get_representation_context(
|
||||
container["representation"]
|
||||
)["subset"]["data"]["family"]
|
||||
containers_by_family[family].append(container)
|
||||
|
||||
# Validate to only 1 source container.
|
||||
source_containers = containers_by_family.get("animation", [])
|
||||
source_containers += containers_by_family.get("pointcache", [])
|
||||
source_container_namespaces = [
|
||||
x["namespace"] for x in source_containers
|
||||
]
|
||||
message = (
|
||||
"{} animation containers selected:\n\n{}\n\nOnly select 1 of type "
|
||||
"\"animation\" or \"pointcache\".".format(
|
||||
len(source_containers), source_container_namespaces
|
||||
)
|
||||
)
|
||||
if len(source_containers) != 1:
|
||||
self.display_warning(message)
|
||||
return
|
||||
|
||||
source_container = source_containers[0]
|
||||
source_ids = self.nodes_by_id(source_container)
|
||||
|
||||
# Target containers.
|
||||
target_ids = {}
|
||||
inputs = []
|
||||
|
||||
yeti_rig_containers = containers_by_family.get("yetiRig")
|
||||
if not yeti_rig_containers:
|
||||
self.display_warning(
|
||||
"Select at least one yetiRig container"
|
||||
)
|
||||
return
|
||||
|
||||
for container in yeti_rig_containers:
|
||||
target_ids.update(self.nodes_by_id(container))
|
||||
|
||||
maya_file = get_representation_path(
|
||||
get_representation_context(
|
||||
container["representation"]
|
||||
)["representation"]
|
||||
)
|
||||
_, ext = os.path.splitext(maya_file)
|
||||
settings_file = maya_file.replace(ext, ".rigsettings")
|
||||
if not os.path.exists(settings_file):
|
||||
continue
|
||||
|
||||
with open(settings_file) as f:
|
||||
inputs.extend(json.load(f)["inputs"])
|
||||
|
||||
# Compare loaded connections to scene.
|
||||
for input in inputs:
|
||||
source_node = source_ids.get(input["sourceID"])
|
||||
target_node = target_ids.get(input["destinationID"])
|
||||
|
||||
if not source_node or not target_node:
|
||||
self.log.debug(
|
||||
"Could not find nodes for input:\n" +
|
||||
json.dumps(input, indent=4, sort_keys=True)
|
||||
)
|
||||
continue
|
||||
source_attr, target_attr = input["connections"]
|
||||
|
||||
if not cmds.attributeQuery(
|
||||
source_attr, node=source_node, exists=True
|
||||
):
|
||||
self.log.debug(
|
||||
"Could not find attribute {} on node {} for "
|
||||
"input:\n{}".format(
|
||||
source_attr,
|
||||
source_node,
|
||||
json.dumps(input, indent=4, sort_keys=True)
|
||||
)
|
||||
)
|
||||
continue
|
||||
|
||||
if not cmds.attributeQuery(
|
||||
target_attr, node=target_node, exists=True
|
||||
):
|
||||
self.log.debug(
|
||||
"Could not find attribute {} on node {} for "
|
||||
"input:\n{}".format(
|
||||
target_attr,
|
||||
target_node,
|
||||
json.dumps(input, indent=4, sort_keys=True)
|
||||
)
|
||||
)
|
||||
continue
|
||||
|
||||
source_plug = "{}.{}".format(
|
||||
source_node, source_attr
|
||||
)
|
||||
target_plug = "{}.{}".format(
|
||||
target_node, target_attr
|
||||
)
|
||||
if cmds.isConnected(
|
||||
source_plug, target_plug, ignoreUnitConversion=True
|
||||
):
|
||||
self.log.debug(
|
||||
"Connection already exists: {} -> {}".format(
|
||||
source_plug, target_plug
|
||||
)
|
||||
)
|
||||
continue
|
||||
|
||||
cmds.connectAttr(source_plug, target_plug, force=True)
|
||||
self.log.debug(
|
||||
"Connected attributes: {} -> {}".format(
|
||||
source_plug, target_plug
|
||||
)
|
||||
)
|
||||
|
||||
def nodes_by_id(self, container):
|
||||
ids = {}
|
||||
for member in get_container_members(container):
|
||||
id = get_id(member)
|
||||
if not id:
|
||||
continue
|
||||
ids[id] = member
|
||||
|
||||
return ids
|
||||
|
||||
def display_warning(self, message, show_cancel=False):
|
||||
"""Show feedback to user.
|
||||
|
||||
Returns:
|
||||
bool
|
||||
"""
|
||||
|
||||
from qtpy import QtWidgets
|
||||
|
||||
accept = QtWidgets.QMessageBox.Ok
|
||||
if show_cancel:
|
||||
buttons = accept | QtWidgets.QMessageBox.Cancel
|
||||
else:
|
||||
buttons = accept
|
||||
|
||||
state = QtWidgets.QMessageBox.warning(
|
||||
None,
|
||||
"",
|
||||
message,
|
||||
buttons=buttons,
|
||||
defaultButton=accept
|
||||
)
|
||||
|
||||
return state == accept
|
||||
|
|
@ -1,17 +1,12 @@
|
|||
import os
|
||||
from collections import defaultdict
|
||||
import maya.cmds as cmds
|
||||
|
||||
from openpype.settings import get_project_settings
|
||||
from openpype.settings import get_current_project_settings
|
||||
import openpype.hosts.maya.api.plugin
|
||||
from openpype.hosts.maya.api import lib
|
||||
|
||||
|
||||
class YetiRigLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
|
||||
"""
|
||||
This loader will load Yeti rig. You can select something in scene and if it
|
||||
has same ID as mesh published with rig, their shapes will be linked
|
||||
together.
|
||||
"""
|
||||
"""This loader will load Yeti rig."""
|
||||
|
||||
families = ["yetiRig"]
|
||||
representations = ["ma"]
|
||||
|
|
@ -22,72 +17,31 @@ class YetiRigLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
|
|||
color = "orange"
|
||||
|
||||
def process_reference(
|
||||
self, context, name=None, namespace=None, options=None):
|
||||
self, context, name=None, namespace=None, options=None
|
||||
):
|
||||
|
||||
import maya.cmds as cmds
|
||||
|
||||
# get roots of selected hierarchies
|
||||
selected_roots = []
|
||||
for sel in cmds.ls(sl=True, long=True):
|
||||
selected_roots.append(sel.split("|")[1])
|
||||
|
||||
# get all objects under those roots
|
||||
selected_hierarchy = []
|
||||
for root in selected_roots:
|
||||
selected_hierarchy.append(cmds.listRelatives(
|
||||
root,
|
||||
allDescendents=True) or [])
|
||||
|
||||
# flatten the list and filter only shapes
|
||||
shapes_flat = []
|
||||
for root in selected_hierarchy:
|
||||
shapes = cmds.ls(root, long=True, type="mesh") or []
|
||||
for shape in shapes:
|
||||
shapes_flat.append(shape)
|
||||
|
||||
# create dictionary of cbId and shape nodes
|
||||
scene_lookup = defaultdict(list)
|
||||
for node in shapes_flat:
|
||||
cb_id = lib.get_id(node)
|
||||
scene_lookup[cb_id] = node
|
||||
|
||||
# load rig
|
||||
group_name = "{}:{}".format(namespace, name)
|
||||
with lib.maintained_selection():
|
||||
file_url = self.prepare_root_value(self.fname,
|
||||
context["project"]["name"])
|
||||
nodes = cmds.file(file_url,
|
||||
namespace=namespace,
|
||||
reference=True,
|
||||
returnNewNodes=True,
|
||||
groupReference=True,
|
||||
groupName="{}:{}".format(namespace, name))
|
||||
file_url = self.prepare_root_value(
|
||||
self.fname, context["project"]["name"]
|
||||
)
|
||||
nodes = cmds.file(
|
||||
file_url,
|
||||
namespace=namespace,
|
||||
reference=True,
|
||||
returnNewNodes=True,
|
||||
groupReference=True,
|
||||
groupName=group_name
|
||||
)
|
||||
|
||||
# for every shape node we've just loaded find matching shape by its
|
||||
# cbId in selection. If found outMesh of scene shape will connect to
|
||||
# inMesh of loaded shape.
|
||||
for destination_node in nodes:
|
||||
source_node = scene_lookup[lib.get_id(destination_node)]
|
||||
if source_node:
|
||||
self.log.info("found: {}".format(source_node))
|
||||
self.log.info(
|
||||
"creating connection to {}".format(destination_node))
|
||||
|
||||
cmds.connectAttr("{}.outMesh".format(source_node),
|
||||
"{}.inMesh".format(destination_node),
|
||||
force=True)
|
||||
|
||||
groupName = "{}:{}".format(namespace, name)
|
||||
|
||||
settings = get_project_settings(os.environ['AVALON_PROJECT'])
|
||||
colors = settings['maya']['load']['colors']
|
||||
|
||||
c = colors.get('yetiRig')
|
||||
settings = get_current_project_settings()
|
||||
colors = settings["maya"]["load"]["colors"]
|
||||
c = colors.get("yetiRig")
|
||||
if c is not None:
|
||||
cmds.setAttr(groupName + ".useOutlinerColor", 1)
|
||||
cmds.setAttr(groupName + ".outlinerColor",
|
||||
(float(c[0])/255),
|
||||
(float(c[1])/255),
|
||||
(float(c[2])/255)
|
||||
cmds.setAttr(group_name + ".useOutlinerColor", 1)
|
||||
cmds.setAttr(
|
||||
group_name + ".outlinerColor",
|
||||
(float(c[0]) / 255), (float(c[1]) / 255), (float(c[2]) / 255)
|
||||
)
|
||||
self[:] = nodes
|
||||
|
||||
|
|
|
|||
|
|
@ -48,6 +48,18 @@ class ValidateYetiRenderScriptCallbacks(pyblish.api.InstancePlugin):
|
|||
|
||||
yeti_loaded = cmds.pluginInfo("pgYetiMaya", query=True, loaded=True)
|
||||
|
||||
if not yeti_loaded and not cmds.ls(type="pgYetiMaya"):
|
||||
# The yeti plug-in is available and loaded so at
|
||||
# this point we don't really care whether the scene
|
||||
# has any yeti callback set or not since if the callback
|
||||
# is there it wouldn't error and if it weren't then
|
||||
# nothing happens because there are no yeti nodes.
|
||||
cls.log.info(
|
||||
"Yeti is loaded but no yeti nodes were found. "
|
||||
"Callback validation skipped.."
|
||||
)
|
||||
return False
|
||||
|
||||
renderer = instance.data["renderer"]
|
||||
if renderer == "redshift":
|
||||
cls.log.info("Redshift ignores any pre and post render callbacks")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue