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: speedup renderable camera collection #1053

Merged
merged 2 commits into from Feb 24, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
334 changes: 334 additions & 0 deletions pype/hosts/maya/attributes.py
@@ -0,0 +1,334 @@
# -*- 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 Pype

"""

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)
4 changes: 2 additions & 2 deletions pype/plugins/maya/publish/collect_renderable_camera.py
Expand Up @@ -2,7 +2,7 @@

from maya import cmds

from pype.hosts.maya import lib
from pype.hosts.maya.attributes import get_attr_in_layer


class CollectRenderableCamera(pyblish.api.InstancePlugin):
Expand All @@ -24,7 +24,7 @@ def process(self, instance):
self.log.info("layer: {}".format(layer))
cameras = cmds.ls(type="camera", long=True)
renderable = [c for c in cameras if
lib.get_attr_in_layer("%s.renderable" % c, layer=layer)]
get_attr_in_layer("%s.renderable" % c, layer)]

self.log.info("Found cameras %s: %s" % (len(renderable), renderable))

Expand Down