Last active
August 1, 2023 04:14
-
-
Save BigRoy/7784b266da449a5b5db7ed633302ebad to your computer and use it in GitHub Desktop.
Maya passthrough any a->b->c attribute connections changing it to a direct a->c connection
This file contains hidden or 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
from maya import cmds | |
from collections import defaultdict | |
import contextlib | |
@contextlib.contextmanager | |
def unlocked(plug): | |
"""Unlock attribute during the context""" | |
locked = cmds.getAttr(plug, lock=True) | |
if locked: | |
cmds.setAttr(plug, lock=False) | |
try: | |
yield | |
finally: | |
if locked: | |
cmds.setAttr(plug, lock=True) | |
def disconnect_inputs(plug): | |
"""Disconnect any input sources for the plug, including for locked attributes that can be unlocked""" | |
sources = cmds.listConnections(plug, plugs=True, source=True, destination=True, shapes=True, skipConversionNodes=False) or [] | |
if not sources: | |
return | |
with unlocked(plug): | |
for dest, source in pairwise(sources): | |
if cmds.isConnected(source, dest): | |
cmds.disconnectAttr(source, dest) | |
def get_connections(nodes_or_plugs, skipConversionNodes=True): | |
"""Return 'sources' and 'destinations' per plug for input nodes or plugs. | |
Arguments: | |
nodes_or_plugs (list or str): List or single string of node or node.attr name. | |
Returns: | |
dict: {plug: {"sources": sources, "destinations": destination}} | |
""" | |
sources = cmds.listConnections(nodes_or_plugs, source=True, destination=False, connections=True, plugs=True, shapes=True, skipConversionNodes=skipConversionNodes) or [] | |
destinations = cmds.listConnections(nodes_or_plugs, source=False, destination=True, connections=True, plugs=True, shapes=True, skipConversionNodes=skipConversionNodes) or [] | |
if not sources and not destinations: | |
return {} | |
plugs = set() | |
sources_by_plug = defaultdict(list) | |
for plug, src in pairwise(sources): | |
sources_by_plug[plug].append(src) | |
plugs.add(plug) | |
destinations_by_plug = defaultdict(list) | |
for plug, dest in pairwise(destinations): | |
destinations_by_plug[plug].append(dest) | |
plugs.add(plug) | |
result = {} | |
for plug in plugs: | |
result[plug] = { | |
"sources": sources_by_plug.get(plug, []), | |
"destinations": destinations_by_plug.get(plug, []) | |
} | |
return result | |
# Optimize out all "passthrough" connections which just go straight through an attribute to be the input for another attribute | |
# Also disconnect the original inputs if the plugs were user-defined attributes anyway, and not for | |
for i in range(5): | |
optimized = False | |
print(f"Iteration: {i}") | |
for plug, connections in get_connections(cmds.ls()).items(): | |
sources = connections["sources"] | |
destinations = connections["destinations"] | |
if not sources or not destinations: | |
continue | |
if len(sources) == 1: | |
source = sources[0] | |
for destination in destinations: | |
print(f"Passing through {plug} -- connecting {source} -> {destination}") | |
cmds.connectAttr(source, destination, force=True) | |
optimized = True | |
# If the plug is a user defined attribute then we assume | |
# it's a plug that is not used for computation at all. | |
# And thus we can disconnect the input safely | |
node, attr = plug.split(".", 1) | |
user_defined = set(cmds.listAttr(node, userDefined=True) or []) | |
if attr in user_defined: | |
print(f"Disconnecting input for {attr}") | |
disconnect_inputs(plug) | |
if not optimized: | |
# Nothing more to optimize | |
print(f"Done optimizing in iteration: {i}") | |
break |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment