Channel operators
Channel operators are the biggest thing in nod
They allow writing quick most often single line formulas that readily describe the node network. Writing several lines of code to create utility nodes, remembering what their input and output attributes are called (as they differ from node to node), connecting inputs and outputs, adding several comments throughout the code will be a thing of a past.
Can you figure out what the code below is supposed to do?
joint1_tx_pow = mc.createNode('multiplyDivide', n='joint1_tx_pow')
mc.setAttr(joint1_tx_pow + '.op', 3)
mc.setAttr(joint1_tx_pow + '.i2', (2, 1, 1))
joint1_tx_abs = mc.createNode('multiplyDivide', n='joint1_tx_abs')
mc.setAttr(joint1_tx_abs + '.op', 3)
mc.setAttr(joint1_tx_abs + '.i2', (0.5, 1, 1))
joint1_tx_mul = mc.createNode('multiplyDivide', n='joint1_tx_mul')
mc.setAttr(joint1_tx_mul + '.i2', (10, 1, 1))
joint2_tx_sub = mc.createNode('plusMinusAverage', n='joint2_tx_sub')
mc.setAttr(joint2_tx_sub + '.op', 2)
mc.setAttr(joint2_tx_sub + ".i1[1]", 11)
joint2_tx_sub_rev = mc.createNode('reverse', n='joint2_tx_sub_rev')
joint1_tx_add = mc.createNode('plusMinusAverage', n='joint1_tx_add')
joint_1_tx_output_clamp = mc.createNode('clamp', n='joint_1_tx_output_clamp')
mc.setAttr(joint_1_tx_output_clamp + '.mn', (10, 0, 0))
mc.setAttr(joint_1_tx_output_clamp + '.mx', (20, 0, 0))
connectAttr(joint_1_tx_output_clamp + ".opr", joint3 + ".tx")
connectAttr(joint_1_tx_output_clamp + ".opr", joint3 + ".ty")
connectAttr(joint1 + ".tx", joint1_tx_pow + ".i1x")
connectAttr(joint1_tx_pow + ".ox", joint1_tx_abs + ".i1x")
connectAttr(joint1_tx_abs + ".ox", joint1_tx_mul + ".i1x")
connectAttr(joint2 + ".tx", joint2_tx_sub + ".i1[0]")
connectAttr(joint2_tx_sub + ".o1", joint2_tx_sub_rev + ".ix")
connectAttr(joint1_tx_mul + ".ox", joint1_tx_add + ".i1[0]")
connectAttr(joint2_tx_sub_rev + ".ox", joint1_tx_add + ".i1[1]")
connectAttr(joint1_tx_add + ".o1", joint_1_tx_output_clamp + ".ipr")
Wouldn’t it be easier if we could write this in a single readable line?
op.clamp((op.abs(j1.tx) * 10) + op.reverse(j2.tx - 11), min=10, max=20) << [j3.tx, j3.ty]
How much easier is to now modify this code further when developing a rig, by simply changing the formula. Imagine having to now change the order of operations in the maya.cmds
example. It almost requires a rewrite. In contrary you can freely adjust the nod statement in a matter of seconds.
op.clamp(j1.tx + op.reverse(j2.tx - ctrl.offset), min=ctrl.min, max=ctrl.max) >> j3.tx
Math operators
All basic math operations are supported
+
: Add
-
: Subtract
*
: Multiply
/
: Divide
**
: Power
Just like we can add a constant value to a channel and drive another node:
joint.tx + 10 >> group.tx
We can also add another channel instead.
joint.tx + joint.ty >> group.tz
In any operation order
1 / joint.sx >> group.ratio
Example of a sqrt operation
(curve.length) ** 0.5 >> group.lengthSquareRoot
Execution order
Like any math formula in python the execution order will be respected when using brackets (
)
joint1.tx * 0.25 >> joint2.ty
(joint1.tx + joint2.tx) * 4 >> joint3.ty
Additional operators
In addition to the math there are multiple channel operators implemented inside the nod.op
module
Abs nod.op.abs()
Get an absolute value from the channel
op.abs(control.spin) >> geometry.sx
Blend nod.op.blend()
Blend given channels via blendTwoAttr node
op.blend(jointA.rx, jointB.rx, driver=control.twist)
Clamp nod.op.clamp()
Clamp given channel value via clamp node
clamp(joint1.tx + joint2.tx + joint3.tx, min=0, max=100)
As expected inputs of any of the operator functions can also be parent attributes. e.g.:
clamp(joint1.t, min=(0, 0, 0), max=(10, 100, 1000))
Condition nod.op.condition()
Condition operation where inputs and outputs can be either constant values or other channels
op.condition(control.squash, '<', 1, ifFalse=1, ifTrue=joint.tx) >> joint2.tx
Remap nod.op.remap()
Remap given channel value via remapValue node. Refer to docs for more info.
op.remap(control.stretch, inputRange=(0, 10), outputRange=(0, 1)) >> ikHandle.stretchFactor
remap = op.remap(control.tx, inputRange=(0, 10), outputRange=(0, 20), positions=(0.1, 0.2, 0.3, 0.4), values=(0.1, 0.3, 0.6, 1), interpolations=(3, 3, 3, 3))
remap >> joint.tx
This is far more readable than setting attributes on the remapValue via maya.cmds.setAttr
# (...)
mc.setAttr('remapValue1.value[0].value_Position', 0.3)
mc.setAttr('remapValue1.value[1].value_Position', 0.4)
mc.setAttr('remapValue1.value[1].value_FloatValue', 0.44)
# (...)
# and so on...
Reverse nod.op.reverse()
Reverse given channel.
op.reverse(geo.v) >> joint.v