mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-25 21:32:15 +01:00
334 lines
12 KiB
Python
334 lines
12 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Code to get attributes from render layer without switching to it.
|
|
|
|
https://github.com/Colorbleed/colorbleed-config/blob/acre/colorbleed/maya/lib_rendersetup.py
|
|
Credits: Roy Nieterau (BigRoy) / Colorbleed
|
|
Modified for use in OpenPype
|
|
|
|
"""
|
|
|
|
from maya import cmds
|
|
import maya.api.OpenMaya as om
|
|
import pymel.core as pm
|
|
|
|
import maya.app.renderSetup.model.utils as utils
|
|
from maya.app.renderSetup.model import renderSetup
|
|
from maya.app.renderSetup.model.override import (
|
|
AbsOverride,
|
|
RelOverride,
|
|
UniqueOverride
|
|
)
|
|
|
|
EXACT_MATCH = 0
|
|
PARENT_MATCH = 1
|
|
CLIENT_MATCH = 2
|
|
|
|
DEFAULT_RENDER_LAYER = "defaultRenderLayer"
|
|
|
|
|
|
def get_rendersetup_layer(layer):
|
|
"""Return render setup layer name.
|
|
|
|
This also converts names from legacy renderLayer node name to render setup
|
|
name.
|
|
|
|
Note: `DEFAULT_RENDER_LAYER` is not a renderSetupLayer node but it is
|
|
however the valid layer name for Render Setup - so we return that as
|
|
is.
|
|
|
|
Example:
|
|
>>> for legacy_layer in cmds.ls(type="renderLayer"):
|
|
>>> layer = get_rendersetup_layer(legacy_layer)
|
|
|
|
Returns:
|
|
str or None: Returns renderSetupLayer node name if `layer` is a valid
|
|
layer name in legacy renderlayers or render setup layers.
|
|
Returns None if the layer can't be found or Render Setup is
|
|
currently disabled.
|
|
"""
|
|
if layer == DEFAULT_RENDER_LAYER:
|
|
# DEFAULT_RENDER_LAYER doesn't have a `renderSetupLayer`
|
|
return layer
|
|
|
|
if not cmds.mayaHasRenderSetup():
|
|
return None
|
|
|
|
if not cmds.objExists(layer):
|
|
return None
|
|
|
|
if cmds.nodeType(layer) == "renderSetupLayer":
|
|
return layer
|
|
|
|
# By default Render Setup renames the legacy renderlayer
|
|
# to `rs_<layername>` but lets not rely on that as the
|
|
# layer node can be renamed manually
|
|
connections = cmds.listConnections(layer + ".message",
|
|
type="renderSetupLayer",
|
|
exactType=True,
|
|
source=False,
|
|
destination=True,
|
|
plugs=True) or []
|
|
return next((conn.split(".", 1)[0] for conn in connections
|
|
if conn.endswith(".legacyRenderLayer")), None)
|
|
|
|
|
|
def get_attr_in_layer(node_attr, layer):
|
|
"""Return attribute value in Render Setup layer.
|
|
|
|
This will only work for attributes which can be
|
|
retrieved with `maya.cmds.getAttr` and for which
|
|
Relative and Absolute overrides are applicable.
|
|
|
|
Examples:
|
|
>>> get_attr_in_layer("defaultResolution.width", layer="layer1")
|
|
>>> get_attr_in_layer("defaultRenderGlobals.startFrame", layer="layer")
|
|
>>> get_attr_in_layer("transform.translate", layer="layer3")
|
|
|
|
Args:
|
|
attr (str): attribute name as 'node.attribute'
|
|
layer (str): layer name
|
|
|
|
Returns:
|
|
object: attribute value in layer
|
|
|
|
"""
|
|
def _layer_needs_update(layer):
|
|
"""Return whether layer needs updating."""
|
|
# Use `getattr` as e.g. DEFAULT_RENDER_LAYER does not have
|
|
# the attribute
|
|
return getattr(layer, "needsMembershipUpdate", False) or \
|
|
getattr(layer, "needsApplyUpdate", False)
|
|
|
|
def get_default_layer_value(node_attr_):
|
|
"""Return attribute value in `DEFAULT_RENDER_LAYER`."""
|
|
inputs = cmds.listConnections(node_attr_,
|
|
source=True,
|
|
destination=False,
|
|
type="applyOverride") or []
|
|
if inputs:
|
|
override = inputs[0]
|
|
history_overrides = cmds.ls(cmds.listHistory(override,
|
|
pruneDagObjects=True),
|
|
type="applyOverride")
|
|
node = history_overrides[-1] if history_overrides else override
|
|
node_attr_ = node + ".original"
|
|
|
|
return pm.getAttr(node_attr_, asString=True)
|
|
|
|
layer = get_rendersetup_layer(layer)
|
|
rs = renderSetup.instance()
|
|
current_layer = rs.getVisibleRenderLayer()
|
|
if current_layer.name() == layer:
|
|
|
|
# Ensure layer is up-to-date
|
|
if _layer_needs_update(current_layer):
|
|
try:
|
|
rs.switchToLayer(current_layer)
|
|
except RuntimeError:
|
|
# Some cases can cause errors on switching
|
|
# the first time with Render Setup layers
|
|
# e.g. different overrides to compounds
|
|
# and its children plugs. So we just force
|
|
# it another time. If it then still fails
|
|
# we will let it error out.
|
|
rs.switchToLayer(current_layer)
|
|
|
|
return pm.getAttr(node_attr, asString=True)
|
|
|
|
overrides = get_attr_overrides(node_attr, layer)
|
|
default_layer_value = get_default_layer_value(node_attr)
|
|
if not overrides:
|
|
return default_layer_value
|
|
|
|
value = default_layer_value
|
|
for match, layer_override, index in overrides:
|
|
if isinstance(layer_override, AbsOverride):
|
|
# Absolute override
|
|
value = pm.getAttr(layer_override.name() + ".attrValue")
|
|
if match == EXACT_MATCH:
|
|
# value = value
|
|
pass
|
|
if match == PARENT_MATCH:
|
|
value = value[index]
|
|
if match == CLIENT_MATCH:
|
|
value[index] = value
|
|
|
|
elif isinstance(layer_override, RelOverride):
|
|
# Relative override
|
|
# Value = Original * Multiply + Offset
|
|
multiply = pm.getAttr(layer_override.name() + ".multiply")
|
|
offset = pm.getAttr(layer_override.name() + ".offset")
|
|
|
|
if match == EXACT_MATCH:
|
|
value = value * multiply + offset
|
|
if match == PARENT_MATCH:
|
|
value = value * multiply[index] + offset[index]
|
|
if match == CLIENT_MATCH:
|
|
value[index] = value[index] * multiply + offset
|
|
|
|
else:
|
|
raise TypeError("Unsupported override: %s" % layer_override)
|
|
|
|
return value
|
|
|
|
|
|
def get_attr_overrides(node_attr, layer,
|
|
skip_disabled=True,
|
|
skip_local_render=True,
|
|
stop_at_absolute_override=True):
|
|
"""Return all Overrides applicable to the attribute.
|
|
|
|
Overrides are returned as a 3-tuple:
|
|
(Match, Override, Index)
|
|
Match:
|
|
This is any of EXACT_MATCH, PARENT_MATCH, CLIENT_MATCH
|
|
and defines whether the override is exactly on the
|
|
plug, on the parent or on a child plug.
|
|
Override:
|
|
This is the RenderSetup Override instance.
|
|
Index:
|
|
This is the Plug index under the parent or for
|
|
the child that matches. The EXACT_MATCH index will
|
|
always be None. For PARENT_MATCH the index is which
|
|
index the plug is under the parent plug. For CLIENT_MATCH
|
|
the index is which child index matches the plug.
|
|
|
|
Args:
|
|
node_attr (str): attribute name as 'node.attribute'
|
|
layer (str): layer name
|
|
skip_disabled (bool): exclude disabled overrides
|
|
skip_local_render (bool): exclude overrides marked
|
|
as local render.
|
|
stop_at_absolute_override: exclude overrides prior
|
|
to the last absolute override as they have
|
|
no influence on the resulting value.
|
|
|
|
Returns:
|
|
list: Ordered Overrides in order of strength
|
|
|
|
"""
|
|
def get_mplug_children(plug):
|
|
"""Return children MPlugs of compound `MPlug`."""
|
|
children = []
|
|
if plug.isCompound:
|
|
for i in range(plug.numChildren()):
|
|
children.append(plug.child(i))
|
|
return children
|
|
|
|
def get_mplug_names(mplug):
|
|
"""Return long and short name of `MPlug`."""
|
|
long_name = mplug.partialName(useLongNames=True)
|
|
short_name = mplug.partialName(useLongNames=False)
|
|
return {long_name, short_name}
|
|
|
|
def iter_override_targets(override):
|
|
try:
|
|
for target in override._targets():
|
|
yield target
|
|
except AssertionError:
|
|
# Workaround: There is a bug where the private `_targets()` method
|
|
# fails on some attribute plugs. For example overrides
|
|
# to the defaultRenderGlobals.endFrame
|
|
# (Tested in Maya 2020.2)
|
|
print("Workaround for %s" % override)
|
|
from maya.app.renderSetup.common.utils import findPlug
|
|
|
|
attr = override.attributeName()
|
|
if isinstance(override, UniqueOverride):
|
|
node = override.targetNodeName()
|
|
yield findPlug(node, attr)
|
|
else:
|
|
nodes = override.parent().selector().nodes()
|
|
for node in nodes:
|
|
if cmds.attributeQuery(attr, node=node, exists=True):
|
|
yield findPlug(node, attr)
|
|
|
|
# Get the MPlug for the node.attr
|
|
sel = om.MSelectionList()
|
|
sel.add(node_attr)
|
|
plug = sel.getPlug(0)
|
|
|
|
layer = get_rendersetup_layer(layer)
|
|
if layer == DEFAULT_RENDER_LAYER:
|
|
# DEFAULT_RENDER_LAYER will never have overrides
|
|
# since it's the default layer
|
|
return []
|
|
|
|
rs_layer = renderSetup.instance().getRenderLayer(layer)
|
|
if rs_layer is None:
|
|
# Renderlayer does not exist
|
|
return
|
|
|
|
# Get any parent or children plugs as we also
|
|
# want to include them in the attribute match
|
|
# for overrides
|
|
parent = plug.parent() if plug.isChild else None
|
|
parent_index = None
|
|
if parent:
|
|
parent_index = get_mplug_children(parent).index(plug)
|
|
|
|
children = get_mplug_children(plug)
|
|
|
|
# Create lookup for the attribute by both long
|
|
# and short names
|
|
attr_names = get_mplug_names(plug)
|
|
for child in children:
|
|
attr_names.update(get_mplug_names(child))
|
|
if parent:
|
|
attr_names.update(get_mplug_names(parent))
|
|
|
|
# Get all overrides of the layer
|
|
# And find those that are relevant to the attribute
|
|
plug_overrides = []
|
|
|
|
# Iterate over the overrides in reverse so we get the last
|
|
# overrides first and can "break" whenever an absolute
|
|
# override is reached
|
|
layer_overrides = list(utils.getOverridesRecursive(rs_layer))
|
|
for layer_override in reversed(layer_overrides):
|
|
|
|
if skip_disabled and not layer_override.isEnabled():
|
|
# Ignore disabled overrides
|
|
continue
|
|
|
|
if skip_local_render and layer_override.isLocalRender():
|
|
continue
|
|
|
|
# The targets list can be very large so we'll do
|
|
# a quick filter by attribute name to detect whether
|
|
# it matches the attribute name, or its parent or child
|
|
if layer_override.attributeName() not in attr_names:
|
|
continue
|
|
|
|
override_match = None
|
|
for override_plug in iter_override_targets(layer_override):
|
|
|
|
override_match = None
|
|
if plug == override_plug:
|
|
override_match = (EXACT_MATCH, layer_override, None)
|
|
|
|
elif parent and override_plug == parent:
|
|
override_match = (PARENT_MATCH, layer_override, parent_index)
|
|
|
|
elif children and override_plug in children:
|
|
child_index = children.index(override_plug)
|
|
override_match = (CLIENT_MATCH, layer_override, child_index)
|
|
|
|
if override_match:
|
|
plug_overrides.append(override_match)
|
|
break
|
|
|
|
if (
|
|
override_match and
|
|
stop_at_absolute_override and
|
|
isinstance(layer_override, AbsOverride) and
|
|
# When the override is only on a child plug then it doesn't
|
|
# override the entire value so we not stop at this override
|
|
not override_match[0] == CLIENT_MATCH
|
|
):
|
|
# If override is absolute override, then BREAK out
|
|
# of parent loop we don't need to look any further as
|
|
# this is the absolute override
|
|
break
|
|
|
|
return reversed(plug_overrides)
|