Setting a constant number of inputs for your Op is pretty simple: just call inputs(int i) where i is the number of inputs you want your Op to have in its constructor. For example, Checkerboard2.cpp defines a zero input (that is, generator) behavior using:
CheckerBoard2(Node* node) : Iop(node)
{
inputs(0);
//...
}
Likewise, Keymix.cpp defines its 3 inputs using:
Keymix(Node* node) : Iop(node)
{
inputs(3);
//...
}
Depending on the client NUKE configuration, the first four inputs display as a splay across the top of the node, labeled according to their short names (see below). The remainder stack on the left hand side of the node. Clients can pull them out in turn incrementally, with their labels appearing as the respective inputs are picked up. Due to this, it’s generally advisable to put commonly connected inputs on low indexes, and less frequently used inputs on incrementally greater indexes.
Note
Bear in mind that different Op sub-classes define different default numbers of inputs (for example, Black defines 0, whilst Iop is 1). Some, such as the source type classes, may not expect any inputs to be defined at all, so if you’re looking to have node inputs in such circumstances it’s likely that you’re picking the wrong base type for your operator.
Setting up optional, or dynamic, inputs requires a little more work, namely defining minimum_inputs() & maximum_inputs() on your Op to return the required numbers respectively. Additionally, if there’s circumstances under which the Op does not require one or more of the inputs, you need to define uses_input(int i) so that NUKE can optimize the tree and draw the Node Graph (DAG) correctly.
Check out the AppendClip.cpp source included in the NDK as an example, the relevant parts of which are reproduced below:
class AppendClip : public Iop
//...
public:
//...
int minimum_inputs() const { return 1; }
int maximum_inputs() const { return 10000; }
//...
};
float AppendClip::uses_input(int i) const
{
if (i == input0 && weight0 > .01f)
return weight0;
if (i == input1 && weight1 > .01f)
return weight1;
return .01f;
}
Check out the AppendClip Op inside of NUKE as well. As you can see, by default the node appears with a single input. Connecting that input results in additional inputs appearing in the stack on the left hand side of the node. If you’re a little masochistic, you can keep adding inputs to satisfy yourself that it does indeed top out at 10000, or you can simply take our word for it.
The uses_input definition is interesting since it returns a float as opposed to the more to be expected bool. The float is used to govern the weight with which the input highlighting is drawn when the Node Graph (DAG) is processing. Additionally, if the weight returned is zero, NUKE may optimize the Op hooked up to that input out of the Node Graph, meaning the pointer to it may be null (or invalid). Treat with care!
Note
As before, sensible ordering (and naming - see below) of inputs is very important. Common usage with low indexes, infrequent usage with high indexes.
In most circumstances, simply numbering the most commonly used inputs with a low index and increasing the index as frequency of use decreases is enough to imply which inputs are optional. It is also possible to create a stack of inputs on the right hand side of the node through the use of the optional_input() call. In nearly all circumstances, this right hand stack is a single input used as a mask, as provided by the mask functionality of the NukeWrapper. However, you can define this as you desire should an alternative behavior be more appropriate. optional_input returns an index, and inputs between this returned index and the maximum number of inputs are drawn on the right hand side of the node.
Note
The naming ‘optional’ can be a little misleading in this circumstance. If you want to define an input as ‘required’ then it may be sensible to report a warning error if the user has not connected sufficient inputs for the Op to do its thing.
Inputs can be labeled by overriding input_label(int i) and returning a char* containing the label for the input indexed by i. The default implementation has a few tricks up its sleeve - single inputs aren’t labeled, two inputs are B for 0 and A for 1 (to match standard NUKE merge conventions) and numbers for beyond that. It’s generally worth following the merge B/A standard for Ops that bring in a range of channels in one way, shape, or form. If it allows more of an arbitrary ‘mix’, then the Shuffle/ShuffleCopy convention of numbering is generally wise. In general, keeping the label to 1 to 3 characters is wise, particularly in the example of a large number of Op inputs, as the Node Graph can become very difficult to read otherwise. With a small number of inputs, you can generally get away with longer names without compromising readability. The below snippet, taken from the UVProject.cpp example, shows an example of such longer naming:
const char* input_label(int input, char* buffer) const
{
switch (input) {
case 0:
return 0;
case 1:
return "axis/cam";
default:
return 0;
}
}
NUKE does provide an input_longlabel method. This is currently unused (in terms of being presented in the UI), but it is recommended that you implement this if you do input_label for future compatibility.
NUKE Ops can inherit from a variety of classes, and in many circumstances you only want certain types of Ops to be plugged into certain inputs. For example, a convolve would have little use for a piece of 3D geometry on one of its inputs. By default, most base classes you inherit from set up what sort of operators can be plugged into them for you. For example, an image operator expects another image operator on its input for the most part, and a source geometry expects a texture map, rather than another geometry Op.
You can override this behavior by reimplementing test_input(int input, Op* op), returning either true or false depending on whether such a connection should be allowed. An excellent example of this included in the NDK is the UVProject.cpp source whose test_input is reproduced below:
bool test_input(int input, Op* op) const
{
if (input == 1)
return dynamic_cast<AxisOp*>(op) != 0;
return GeoOp::test_input(input, op);
}
This basically checks the second input to see if it’s got an Axis node somewhere in its inheritance tree, and if not, disallows connection. All other connections revert to using the default GeoOp provided test_input, which allows connection to 3D geometry as you might expect.
Note
If you override test_input, you probably want to additionally override default_input as discussed below.
Related to the concept of input class testing, setting the default input returned by the Op allows you to override what class is passed to a function when no operator is connected on that input. This allows you to avoid the joy of testing for NULL’s all over the place in your code, but can lead to confusion if you’re expecting this behavior! If you’re overriding the test_input function to allow connection from other Op types, it’s likely you want to override the default_input to return a lowest common denominator type Op of the type you’re expecting (or indeed NULL for that input if you’re happy testing for this elsewhere in your Op). If you’re looking to test for connection, you can also override this to return NULL, but be aware you then have to test for NULL in all places you’re expecting to access the input operator.
For example, the Phong.cpp sample overrides default_input in the following manner, to allow for NULL testing elsewhere in the operator:
Op* default_input(int input) const
{
if (input == 0)
return Material::default_input(input);
return 0;
}
Subsequently, it uses tests similar to the following to figure out whether there’s a connection. If the default_input hadn’t been overriden, then testing for connection would have been a whole lot more convoluted:
if (input(1) != 0 && input(0) == default_input(0))
input1().shade_GL(ctx, geo);
In another example, the UVProject.cpp sample overrides default_input because the test_input has been overriden:
Op* default_input(int input) const
{
if (input == 1)
return 0;
return GeoOp::default_input(input);
}
If it didn’t, then the second input would appear under the hood to be connected to an operator type it theoretically shouldn’t be connected to. Note that in this example, it does return NULL despite it not being strictly a case of the Op wanting to check for connection subsequently. If you check the rest of the sample, you’ll see that in the _validate for example, it tests for connection to a variety of Axis-derived connections to allow for different behaviors depending on this connection type. In most other circumstances, you want to return, for example, a DD::Image::Op::Iop::default_input(input) type instead (depending on whatever Op type you’re allowing connection to).