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

Commit

Permalink
Merge pull request #1053 from pypeclub/2.x/maya-speedup-camera-collec…
Browse files Browse the repository at this point in the history
…tion

Maya: speedup renderable camera collection
  • Loading branch information
mkolar authored Feb 24, 2021
2 parents 651d760 + 6d0b270 commit a9c7663
Show file tree
Hide file tree
Showing 2 changed files with 336 additions and 2 deletions.
334 changes: 334 additions & 0 deletions pype/hosts/maya/attributes.py
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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

0 comments on commit a9c7663

Please sign in to comment.