-
-
Save davidlatwe/e33b942967f18f4b61f085e3effe86c7 to your computer and use it in GitHub Desktop.
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 |
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
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'?
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
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 ?
Still seems off on my end. Here's what I did. Tested in Maya 2020.
- Create a Render Setup layer named
test
- Go into the layer (make it visible)
- Go to Render Settings with Arnold Renderer and Create Override on "Half Precision" (just as an example).
- As override, enable the attribute.
- 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.
@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.
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 !
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.
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
, enableDisplay 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