Skip to content

Instantly share code, notes, and snippets.

@davidlatwe
Last active June 1, 2023 10:12
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save davidlatwe/e33b942967f18f4b61f085e3effe86c7 to your computer and use it in GitHub Desktop.
Save davidlatwe/e33b942967f18f4b61f085e3effe86c7 to your computer and use it in GitHub Desktop.
Query renderSetup overrided attribute value without switching layer
import re
import itertools
from maya import cmds
from maya.app.renderSetup.model import selector as rs_selector
from maya.app.renderSetup.model import renderSettings as rs_render_settings
if float(cmds.about(version=True)) >= 2020.0:
_attr_highest_col = "containerHighest"
else:
_attr_highest_col = "collectionHighest"
def query_by_setuplayer(node, attr, layer):
"""Query attribute without switching renderSetupLayer
Caveat: The `node` MUST be long name, or the result will be incorrect,
since we are matching collection's selection in long name.
Arguments:
node (str): node long name
attr (str): node attribute name
layer (str): renderLayer name (NOT renderSetupLayer)
"""
node_attr = node + "." + attr
def original_value(attr_=None):
attr_ = attr_ or attr
node_attr_ = node + "." + attr_
appliers = cmds.ls(cmds.listHistory(node_attr_, pruneDagObjects=True),
type="applyOverride")
if appliers:
return cmds.getAttr(appliers[-1] + ".original", asString=True)
else:
return cmds.getAttr(node + "." + attr_, asString=True)
current_layer = cmds.editRenderLayerGlobals(query=True,
currentRenderLayer=True)
if layer == current_layer:
# At current layer, simple get
return cmds.getAttr(node_attr, asString=True)
if layer == "defaultRenderLayer":
# Querying masterLayer, get original value
return original_value()
# Handling compound attribute
parent_attr = cmds.attributeQuery(attr, node=node, listParent=True) or []
child_attrs = cmds.attributeQuery(attr, node=node, listChildren=True) or []
attrs = {attr}
attrs.update(parent_attr + child_attrs)
enabled_overrides = set()
for at in attrs:
enabled = [n for n in cmds.ls(type="override") if
cmds.getAttr(n + ".attribute") == at and
cmds.getAttr(n + ".enabled")]
enabled_overrides.update(enabled)
if not enabled_overrides:
# No Override enabled in every layer
return cmds.getAttr(node_attr, asString=True)
setup_layer = cmds.listConnections(layer + ".message",
type="renderSetupLayer")[0]
# (NOTE) We starting from the highest collection because if the node
# being collected in multiple collections, only the overrides
# in higher collection has effect.
#
highest_col = cmds.listConnections(setup_layer + "." + _attr_highest_col)
if highest_col is None:
# Empty layer, get original value
return original_value()
# The hunt begins...
def _node_in_(selection):
"""Is `node` being selected or a child of selected
If "|cam1" is being selected, "|cam1|camShape1" is also being selected
but not "|cam1x|cam1xShape".
"""
return any(node == selected or node.startswith(selected + "|")
for selected in selection)
def _is_selected_by_patterns(patterns, types=None):
"""Customized version of `maya.app.renderSetup.model.selector.ls()`
"""
if types is None:
types = []
included = [t for t in types if not t.startswith("-")]
def includer(selection, pattern):
update = set.update
if pattern.startswith(r"-"):
pattern = pattern[1:]
update = set.difference_update
if pattern == "*":
# Since we only need to know about whether this one node
# been selected, so instead of listing everything ("*"),
# listing this one node is all we need here.
#
# This saved a lot of processing time.
#
update(selection, cmds.ls(node, type=included, long=True))
else:
update(selection, cmds.ls(pattern, type=included, long=True))
return selection
selection = reduce(includer, patterns, set())
if _node_in_(selection):
excluded = [t for t in types if t.startswith("-")]
excluded.extend(rs_selector.getRSExcludes())
filter = rs_selector.createTypeFilter(excluded)
selection = itertools.ifilter(filter, selection)
return _node_in_(set(selection))
return False
def is_selected_by(selector, collection):
"""Did the collection selector select this node ?"""
# Special selector
if cmds.nodeType(selector) == "arnoldAOVChildSelector":
selected = cmds.getAttr(selector + ".arnoldAOVNodeName")
return node == selected
# Static selection
if cmds.nodeType(collection) == "renderSettingsCollection":
static = rs_render_settings.getDefaultNodes()
else:
static = cmds.getAttr(selector + ".staticSelection").split()
if _node_in_(static):
return True
# Pattern selection
pattern = cmds.getAttr(selector + ".pattern")
patterns = re.split("\s*[;\s]\s*", pattern)
ftype = cmds.getAttr(selector + ".typeFilter")
return _is_selected_by_patterns(patterns,
rs_selector.Filters.filterTypes(ftype))
def walk_siblings(item):
"""Walk renderSetup nodes from highest(bottom) to lowest(top)"""
yield item
previous = cmds.listConnections(item + ".previous")
if previous is not None:
for sibling in walk_siblings(previous[0]):
yield sibling
def walk_hierarchy(item):
"""Walk renderSetup nodes with depth prior"""
for sibling in walk_siblings(item):
yield sibling
try:
children = cmds.listConnections(sibling + ".listItems")
overrides = set(cmds.ls(children, type="override"))
no_others = len(children) == len(overrides)
if no_others and not enabled_overrides.intersection(overrides):
continue
highest = cmds.listConnections(sibling + ".childHighest")[0]
except (ValueError, TypeError):
continue
else:
for child in walk_hierarchy(highest):
yield child
def is_override_by(item):
if item in enabled_overrides:
attr_ = cmds.getAttr(item + ".attribute")
return cmds.objExists(node + "." + attr_)
return False
# Locate leaf collection and get overrides from there
in_selection = None
overrides = []
for item in walk_hierarchy(highest_col[0]):
try:
selector = cmds.listConnections(item + ".selector")[0]
except ValueError:
# Is an override
if in_selection and is_override_by(item):
overrides.append(item)
else:
# Is a collection
if is_selected_by(selector, item):
if not cmds.getAttr(item + ".selfEnabled"):
# Collection not enabled, not a member
return original_value()
in_selection = True
else:
break
if not overrides:
return original_value()
# Compound value filter
if parent_attr:
index = cmds.attributeQuery(parent_attr[0],
node=node,
listChildren=True).index(attr)
filter = (lambda compound, _: compound[0][index])
elif child_attrs:
default = cmds.attributeQuery(attr, node=node, listDefault=True)
filter = (lambda val, ind: [val if i == ind else v
for i, v in enumerate(default)])
else:
filter = None
def value_filter(value, attr_):
if attr_ in parent_attr:
return filter(value, None)
elif attr_ in child_attrs:
return filter(value, child_attrs.index(attr_))
else:
return value
# Collect values from overrides
root = None
relatives = []
for override in overrides:
attr_ = cmds.getAttr(override + ".attribute")
try:
# Absolute override
root = cmds.getAttr(override + ".attrValue", asString=True)
root = value_filter(root, attr_)
if attr_ in child_attrs:
for i, ca in enumerate(child_attrs):
if not ca == attr_:
root[i] = original_value(ca)
if not len(relatives):
return root
else:
break
except ValueError:
# Relative override
multiply = cmds.getAttr(override + ".multiply")
multiply = value_filter(multiply, attr_)
offset = cmds.getAttr(override + ".offset")
offset = value_filter(offset, attr_)
relatives.insert(0, (multiply, offset))
else:
root = original_value()
# Compute value
if child_attrs:
# compound value
for m, o in relatives:
for r_arr, m_arr, o_arr in zip(root, m, o):
new = list()
for R, M, O in zip(r_arr, m_arr, o_arr):
new.append(R * M + O)
root = new
return root
for m, o in relatives:
root = root * m + o
return root
@davidlatwe
Copy link
Author

Display Render Setup nodes in the Outliner and other editors

Those renderSetup node are hidden by default.
To show them, go to Render Setup Window, on the top menu Options, enable Display Render Setup nodes in editors.
You must also disable Display > DAG Objects Only in the Outliner menu to see them there.

https://knowledge.autodesk.com/support/maya/learn-explore/caas/CloudHelp/cloudhelp/2018/ENU/Maya-Rendering/files/GUID-912309DC-07B9-4918-AE43-F0E7CD3DFF34-htm.html

@davidlatwe
Copy link
Author

Note

layer

  • parentList : GUI parent
  • next : next layer in GUI
  • previous : previous layer in GUI
  • listItems : connected to next level child collections (no order)
  • collectionLowest : the collection on the top of the GUI list
  • collectionHighest : the collection down the bottom of the GUI list
  • legacyRenderLayer : connected to legacy render layer

collection

  • parentList : GUI parent
  • next : next collection sibling in GUI
  • previous : previous collection sibling in GUI
  • listItems : connected to next level child objects (no order)
  • childLowest : the object on the top of the GUI list
  • childHighest : the object down the bottom of the GUI list
  • selector : selector node
  • selfEnabled : this node's enable state
  • parentEnabled : parent node's enable state
  • enabled : overall enable state

override

  • parentList
  • next
  • previous
  • selfEnabled
  • parentEnabled
  • enabled
  • attribute
  • localRender
  • attrValue

@BigRoy
Copy link

BigRoy commented Jan 21, 2021

Testing this in Maya 2020 it fails on line 67 due to the attribute existing. It's called containerHighest as opposed to collectionHighest.

Also, I tested this on an render settings override. An override that is created without! a collection but directly on the attribute which it fails to detect.

Is this 'production-ready'?

@davidlatwe
Copy link
Author

Hey Roy !

It's called containerHighest as opposed to collectionHighest.

Yeah, I have noticed that a while back, but didn't update the code here. It's seems that the attribute name has been changed after Maya 2020.0 (didn't tested in Maya 2019 and not able to find any related Maya change log).

Also, I tested this on an render settings override. An override that is created without! a collection but directly on the attribute which it fails to detect.

😮 I remember I have tested that in Maya 2018, will have a look soon !

Is this 'production-ready'?

Should be. 🤔 This has been used in our Avalon config for a long while, and rely on our artists to report bug. :P

@davidlatwe
Copy link
Author

Hey @BigRoy, have a test on "overriding attribute directly with right clicking absolute override" and seems to work fine. Was tested on renderSetting attribute and a shader attribute, without creating collection beforehand.

I have updated the script now, maybe you could have another shot ?

@BigRoy
Copy link

BigRoy commented Jan 22, 2021

Still seems off on my end. Here's what I did. Tested in Maya 2020.

  1. Create a Render Setup layer named test
  2. Go into the layer (make it visible)
  3. Go to Render Settings with Arnold Renderer and Create Override on "Half Precision" (just as an example).
  4. As override, enable the attribute.
  5. Go into the defaultRenderLayer (make it visible)

Then run this:

node, attr = "defaultArnoldDriver.halfPrecision".split(".", 1)
print query_by_setuplayer(node, attr, layer="rs_test")
# False

The output should be True. Note that there's no "collection" for this override. But it's an AbsUniqueOverride directly on the plug.

Note that I'm currently working on an alternate approach to getting the overrides, using a bit more of the Render Setup API which queries the overrides correctly. I still only need to compute the right Absolute/Relative overrides like you're doing.

@BigRoy
Copy link

BigRoy commented Jan 22, 2021

@davidlatwe Here's my current prototype code to query the attribute value in a Render Setup layer without switching layers. All that's really needed is to compute the final value using the overrides.

The get_attr_overrides function is completely functional. I've marked with TODO what needs to be implemented. Unfortunately with overrides possibly being on compound attributes (e.g. translate contains XYZ) and some overrides potentially being on its children (e.g. translateY) or querying for translateY but the override being on the parent compound attribute makes that retrieval messy overcomplicated code and I'm thinking about what code flow would be most readable, be optimized and work as expected for the different cases.

@davidlatwe
Copy link
Author

davidlatwe commented Jan 22, 2021

Thanks !!

I found the problem and updated the code !
Here's what I found...

Note that there's no "collection" for this override.

This isn't entirely true, the node was actually called RenderSettingsCollection and the node type is renderSettingsCollection. The cause of this bug was the selector that connected to RenderSettingsCollection.selector. And the selector by default is called RenderSettingsCollectionSelector, which has an attribute staticSelection and contains renderer's default nodes.

The issue is, the value of RenderSettingsCollectionSelector.staticSelection only set once on it's creation, so if Arnold or other renderers gets registered afterward, the render settings' override will not be found by this script. Since the previous script only reads the value of staticSelection from the selector node, which will only get value like "defaultRenderGlobals defaultResolution defaultRenderQuality". And the attribute in your example is in the node defaultArnoldDriver, which isn't in that list, hence the bug.

So in the fix, I use maya.app.renderSetup.model.renderSettings.getDefaultNodes() to get all renderSettings related nodes then do the compute, and problem solved.


I found your gist while I was typing this ! The interface seems much generic, nice :D
Will have a deeper look in it !

@BigRoy
Copy link

BigRoy commented Jan 27, 2021

@davidlatwe

This part of the code actually retrieves any applyOverride nodes to the node and doesn't query specific to that particular attribute.

...
        appliers = cmds.ls(cmds.listHistory(node_attr_, pruneDagObjects=True),
                           type="applyOverride")
...

It seems cmds.listHistory doesn't collect history from the attribute but from the node. Just wanted to let you know for if you'd ever write something similar on other code.
A workaround/fix is added in my alternative version.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment