Day 11 of the stream, where we introduce guides to the components, a space switch to the pedals, and most offer a demo of Maya Callbacks and how they can be introduced and managed.
This was a very long stream, and it’s sort of divided in two parts. Unintentionally, but luckily, it’s split right down the middle of its duration.
Part 1 lasts until the end of the first hour and it’s about adding live guides to components:
We show how to re-purpose some items we already had sitting around to add a guide mode to the two components we had finalized before, as well as introduce a parent switch/blend to the pedals.
Part 2, from the one hour mark on, is about Maya Callbacks:
Maya Callbacks are a native facility of Maya to introduce cross-talk between nodes driven by events that isn’t dependent on the graph, nor affects it.
It’s extremely useful to establish behavior in a graph that would otherwise be cyclic if it was left to live evaluation all the time:
E.G. Object A drives object B, but object B also drives in a similar fashion object A
Towards the end of the stream someone asked if I could post the example code, which seems like a very good idea.
At the end of this post you will find a formatted and cleaned up version of the episode’s script that you can run on a selection of N objects, and will install a callback that matches the translation of any node immediately downstream of the message plug of the node with the callback. If this sounds convoluted, don’t worry, the stream should make it a lot clearer.
Usage wise all you need to do to make it work and demonstrate how it can affect objects reciprocally is to have a couple nodes selected when you run it that are also connected to each other cyclically by their message plug. Again, if it sounds hard to understand, don’t worry, by the end of the video that should also be pretty obvious.
The script posted here isn’t meant to be a commando typing exercise, nor to crash your Maya session, so the callback is expanded with a circuit breaker to compare the effect of the loop, which wasn’t present in the one we worked on during the stream. That part represents what I was talking about when I mentioned you can use callbacks to manage benign cycles by exiting early on a successful comparison.
Last but not least: It also turns out I was probably incorrect in answering about deploying callbacks. They still don’t seem to leave any record in the scene, and therefore need to be managed on scene open by “the pipeline”.
It seems to me that showing a simple callback manager and tracker might be in the future of this season if the programming component shown today for the first time is to people’s liking 🙂
On to the videos, and script will follow at the end of the page:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
from maya.api import OpenMaya as om2 def iterSelection(): """ generator style iterator over current Maya active selection :return: [MObject) an MObject for each item in the selection """ sel = om2.MGlobal.getActiveSelectionList() for i in xrange(sel.length()): yield sel.getDependNode(i) def removeCallbacksFromNode(node_mob): """ :param node_mob: [MObject] the node to remove all node callbacks from :return: [int] number of callbacks removed """ cbs = om2.MMessage.nodeCallbacks(node_mob) for eachCB in cbs: om2.MMessage.removeCallback(eachCB) len(cbs) def translationPlugsFromAnyPlug(plug): """ :param plug: [MPlug] plug on a node to retrieve translation related plugs from :return: [tuple(MPlug)] tuple of compound translate plug, and three axes translate plugs """ node = plug.node() if not node.hasFn(om2.MFn.kTransform): # this should exclude nodes without translate plugs return mfn_dep = om2.MFnDependencyNode(node) pNames = ('translate', 'tx', 'ty', 'tz') return tuple([mfn_dep.findPlug(eachName, False) for eachName in pNames]) def msgConnectedPlugs(plug): """ :param plug: [MPlug] plug on a node owning message plug we wish to retrieve all destination plugs from :return: [tuple(MPlug)] all plugs on other nodes receiving a message connection coming from the one owning the argument plug """ mfn_dep = om2.MFnDependencyNode(plug.node()) msgPlug = mfn_dep.findPlug('message', False) return tuple([om2.MPlug(otherP) for otherP in msgPlug.destinations()]) def almostEqual(a, b, rel_tol=1e-09, abs_tol=0.0): """ Lifted from pre 3.5 isclose() implementation, floating point error tolerant comparison :param a: [float] first number in comparison :param b: [float] second number in comparison :param rel_tol: [float] relative tolerance in comparison :param abs_tol: [float] absolute tolerance in case of relative tolerance issues :return: [bool] args are equal or not """ return abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol) def cb(msg, plug1, plug2, payload): if msg != 2056: #check most common case first and return unless it's return # an attribute edit type of callback srcTranslationPlugs = translationPlugsFromAnyPlug(plug1) if not len(srcTranslationPlugs): return # trim out the first plug, the translate compound, and only work on the triplet xyz values = [p.asFloat() for p in srcTranslationPlugs[1:4]] for eachDestPlug in msgConnectedPlugs(plug1): # all receiving plugs destTranslationPlugs = translationPlugsFromAnyPlug(eachDestPlug)[1:4] for i, p in enumerate(destTranslationPlugs): if almostEqual(p.asFloat(), values[i]): continue p.setFloat(values[i]) for eachMob in iterSelection(): removeCallbacksFromNode(eachMob) om2.MNodeMessage.addAttributeChangedCallback(eachMob, cb)