Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Maya: Yeti - Implement writing and loading user variables with a yeti cache #287

Merged
merged 4 commits into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions client/ayon_core/hosts/maya/api/yeti.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
from typing import List

from maya import cmds


def get_yeti_user_variables(yeti_shape_node: str) -> List[str]:
"""Get user defined yeti user variables for a `pgYetiMaya` shape node.

Arguments:
yeti_shape_node (str): The `pgYetiMaya` shape node.

Returns:
list: Attribute names (for a vector attribute it only lists the top
parent attribute, not the attribute per axis)
"""

attrs = cmds.listAttr(yeti_shape_node,
userDefined=True,
string=("yetiVariableV_*",
"yetiVariableF_*")) or []
valid_attrs = []
for attr in attrs:
attr_type = cmds.attributeQuery(attr, node=yeti_shape_node,
attributeType=True)
if attr.startswith("yetiVariableV_") and attr_type == "double3":
# vector
valid_attrs.append(attr)
elif attr.startswith("yetiVariableF_") and attr_type == "double":
valid_attrs.append(attr)

return valid_attrs


def create_yeti_variable(yeti_shape_node: str,
attr_name: str,
value=None,
force_value: bool = False) -> bool:
"""Get user defined yeti user variables for a `pgYetiMaya` shape node.

Arguments:
yeti_shape_node (str): The `pgYetiMaya` shape node.
attr_name (str): The fully qualified yeti variable name, e.g.
"yetiVariableF_myfloat" or "yetiVariableV_myvector"
value (object): The value to set (must match the type of the attribute)
When value is None it will ignored and not be set.
force_value (bool): Whether to set the value if the attribute already
exists or not.

Returns:
bool: Whether the attribute value was set or not.

"""
exists = cmds.attributeQuery(attr_name, node=yeti_shape_node, exists=True)
if not exists:
if attr_name.startswith("yetiVariableV_"):
_create_vector_yeti_user_variable(yeti_shape_node, attr_name)
if attr_name.startswith("yetiVariableF_"):
_create_float_yeti_user_variable(yeti_shape_node, attr_name)

if value is not None and (not exists or force_value):
plug = "{}.{}".format(yeti_shape_node, attr_name)
if (
isinstance(value, (list, tuple))
and attr_name.startswith("yetiVariableV_")
):
cmds.setAttr(plug, *value, type="double3")
else:
cmds.setAttr(plug, value)

return True
return False


def _create_vector_yeti_user_variable(yeti_shape_node: str, attr_name: str):
if not attr_name.startswith("yetiVariableV_"):
raise ValueError("Must start with yetiVariableV_")
BigRoy marked this conversation as resolved.
Show resolved Hide resolved
cmds.addAttr(yeti_shape_node,
longName=attr_name,
attributeType="double3",
cachedInternally=True,
keyable=True)
for axis in "XYZ":
cmds.addAttr(yeti_shape_node,
longName="{}{}".format(attr_name, axis),
attributeType="double",
parent=attr_name,
cachedInternally=True,
keyable=True)


def _create_float_yeti_user_variable(yeti_node: str, attr_name: str):
if not attr_name.startswith("yetiVariableF_"):
raise ValueError("Must start with yetiVariableF_")
BigRoy marked this conversation as resolved.
Show resolved Hide resolved

cmds.addAttr(yeti_node,
longName=attr_name,
attributeType="double",
cachedInternally=True,
softMinValue=0,
softMaxValue=100,
keyable=True)
41 changes: 41 additions & 0 deletions client/ayon_core/hosts/maya/plugins/load/load_yeti_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
get_representation_path
)
from ayon_core.hosts.maya.api import lib
from ayon_core.hosts.maya.api.yeti import create_yeti_variable
from ayon_core.hosts.maya.api.pipeline import containerise
from ayon_core.hosts.maya.api.plugin import get_load_color_for_product_type

Expand All @@ -23,8 +24,19 @@
"viewportDensity",
"viewportWidth",
"viewportLength",
"renderDensity",
"renderWidth",
"renderLength",
"increaseRenderBounds"
}

SKIP_ATTR_MESSAGE = (
"Skipping updating %s.%s to %s because it "
"is considered a local overridable attribute. "
"Either set manually or the load the cache "
"anew."
)


def set_attribute(node, attr, value):
"""Wrapper of set attribute which ignores None values"""
Expand Down Expand Up @@ -209,9 +221,31 @@ def update(self, container, context):

for attr, value in node_settings["attrs"].items():
if attr in SKIP_UPDATE_ATTRS:
self.log.info(
SKIP_ATTR_MESSAGE, yeti_node, attr, value
)
continue
set_attribute(attr, value, yeti_node)

# Set up user defined attributes
user_variables = node_settings.get("user_variables", {})
for attr, value in user_variables.items():
was_value_set = create_yeti_variable(
yeti_shape_node=yeti_node,
attr_name=attr,
value=value,
# We do not want to update the
# value if it already exists so
# that any local overrides that
# may have been applied still
# persist
force_value=False
)
if not was_value_set:
self.log.info(
SKIP_ATTR_MESSAGE, yeti_node, attr, value
)

cmds.setAttr("{}.representation".format(container_node),
repre_entity["id"],
typ="string")
Expand Down Expand Up @@ -332,6 +366,13 @@ def create_node(self, namespace, node_settings):
for attr, value in attributes.items():
set_attribute(attr, value, yeti_node)

# Set up user defined attributes
user_variables = node_settings.get("user_variables", {})
for attr, value in user_variables.items():
create_yeti_variable(yeti_shape_node=yeti_node,
attr_name=attr,
value=value)

# Connect to the time node
cmds.connectAttr("time1.outTime", "%s.currentTime" % yeti_node)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import pyblish.api

from ayon_core.hosts.maya.api import lib
from ayon_core.hosts.maya.api.yeti import get_yeti_user_variables


SETTINGS = {
Expand Down Expand Up @@ -34,7 +35,7 @@ class CollectYetiCache(pyblish.api.InstancePlugin):
- "increaseRenderBounds"
- "imageSearchPath"

Other information is the name of the transform and it's Colorbleed ID
Other information is the name of the transform and its `cbId`
"""

order = pyblish.api.CollectorOrder + 0.45
Expand All @@ -54,13 +55,29 @@ def process(self, instance):
# Get specific node attributes
attr_data = {}
for attr in SETTINGS:
# Ignore non-existing attributes with a warning, e.g. cbId
# if they have not been generated yet
if not cmds.attributeQuery(attr, node=shape, exists=True):
self.log.warning(
"Attribute '{}' not found on Yeti node: {}".format(
attr, shape
)
)
continue

current = cmds.getAttr("%s.%s" % (shape, attr))
# change None to empty string as Maya doesn't support
# NoneType in attributes
if current is None:
current = ""
attr_data[attr] = current

# Get user variable attributes
user_variable_attrs = {
attr: lib.get_attribute("{}.{}".format(shape, attr))
for attr in get_yeti_user_variables(shape)
}

# Get transform data
parent = cmds.listRelatives(shape, parent=True)[0]
transform_data = {"name": parent, "cbId": lib.get_id(parent)}
Expand All @@ -70,6 +87,7 @@ def process(self, instance):
"name": shape,
"cbId": lib.get_id(shape),
"attrs": attr_data,
"user_variables": user_variable_attrs
}

settings["nodes"].append(shape_data)
Expand Down
Loading