Last active
June 1, 2023 10:12
-
-
Save davidlatwe/e33b942967f18f4b61f085e3effe86c7 to your computer and use it in GitHub Desktop.
Query renderSetup overrided attribute value without switching layer
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
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 |
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
Thanks !!
I found the problem and updated the code !
Here's what I found...
This isn't entirely true, the node was actually called
RenderSettingsCollection
and the node type isrenderSettingsCollection
. The cause of this bug was the selector that connected toRenderSettingsCollection.selector
. And the selector by default is calledRenderSettingsCollectionSelector
, which has an attributestaticSelection
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 ofstaticSelection
from the selector node, which will only get value like"defaultRenderGlobals defaultResolution defaultRenderQuality"
. And the attribute in your example is in the nodedefaultArnoldDriver
, 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 !