Skip to content

Instantly share code, notes, and snippets.

@BigRoy
Last active August 1, 2023 04:14
Show Gist options
  • Save BigRoy/7784b266da449a5b5db7ed633302ebad to your computer and use it in GitHub Desktop.
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
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