Skip to content
This repository has been archived by the owner on Sep 20, 2024. It is now read-only.

Commit

Permalink
Maya: Yeti Validate Rig Input - OP-3454 (#4554)
Browse files Browse the repository at this point in the history
* 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 052e65c.

* 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>
  • Loading branch information
tokejepsen and BigRoy authored Mar 17, 2023
1 parent 8d828f4 commit a13f80e
Show file tree
Hide file tree
Showing 10 changed files with 260 additions and 107 deletions.
178 changes: 178 additions & 0 deletions openpype/hosts/maya/plugins/inventory/connect_yeti_rig.py
Original file line number Diff line number Diff line change
@@ -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
94 changes: 24 additions & 70 deletions openpype/hosts/maya/plugins/load/load_yeti_rig.py
Original file line number Diff line number Diff line change
@@ -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"]
Expand All @@ -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))

# 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']
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
)

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,18 @@ def get_invalid(cls, instance):

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")
Expand Down
Loading

0 comments on commit a13f80e

Please sign in to comment.