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