This repository has been archived by the owner on Sep 20, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 129
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1053 from pypeclub/2.x/maya-speedup-camera-collec…
…tion Maya: speedup renderable camera collection
- Loading branch information
Showing
2 changed files
with
336 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters