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 (ie generator) behaviour 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 4 inputs will display as a splay across the top of the node, labelled according to their short names (see below). The remainder will 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 different Op subclasses will define different default numbers of inputs (eg 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 underwhich the op does not require one or more of the inputs, defining uses_input(int i) so that NUKE can optimise the tree and draw the 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 Op inside of NUKE as well. As you can see, by default the node appears with a single input which as you connect will result 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 which happens when the DAG is processing is drawn. Additionally, if the weight returned is zero, NUKE may optimise the op hooked up to that input out of the scenegraph, so 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, high index.
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 more 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 behaviour 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.
Inputs can be labelled 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 labelled, two inputs are B for 0 and A for 1 (to match standard NUKE merge conventions) and number for beyond that. It’s generally worth following the merge B/A standard for ops which 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 DAG 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 in to 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’ll 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 other geometry op.
You can override this behaviour 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, who’s 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 2nd input to see if it’s got an axis node somewhere in its inheritance tree, and if not disallow 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’ll 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 behaviour! If you’re overriding the test_input function to allow connection from other op types, it’s likely you’ll 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’ve 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 behaviours dependant on this connection type. In most other circumstances you’d want to return, for example, a DD::Image::Op::Iop::default_input(input) type instead (dependent on whatever Op type you’re allowing connection to).