Architecture

Op Lookup

NUKE has a symbol table, linking Ops to factory functions. If an attempt is made to create an Op with a name not already in the symbol table, NUKE looks in its plug-in path for a matching plug-in. For example, if trying to create an Op “Example”, it looks for (on Linux) Example.so or Example.tcl. If it finds Example.so, it calls dlopen() or the equivalent on this. NUKE’s plug-ins do not have an entry point per se - they are expected to have a global or static object within them whose constructor is run. This should be an Iop::Description or an Op::Description. The constructors of these add the Op to the symbol table.

If you want for two different Ops to share the same .so, then you should make one of the plug-ins a TCL pointer to the other. For example, if you want ExampleRed to be implemented within ExampleGreen.so, there needs to be an ExampleRed.tcl file containing the line:

load ExampleGreen

The joint plug-in should contain Description objects for all of its classes.

Op Construction

NUKE invokes your Op constructor through the factory helper function that is in the Description. The constructor is passed the pointer to the node. Note that there can be multiple Ops per node: if the node is being accessed at two separate times, or views, for example (through the use of nodes such as FrameBlend or Anaglyph), multiple Ops are created.

At the time that your Op is constructed, knobs do not yet exist and the inputs are not yet wired up. Some Ops want to set defaults according to the size of the input image. The special function “input_format()” works during Op construction and can be used to get the size of what will be connected as the input image.

Knobs

void DD::Image::Knob::knobs(Knob_Callback)

knobs() is invoked by NUKE on your Op soon after creation. An example implementation is as follows:

void knobs(Knob_Callback f)
{
  Float_knob(f, &_flt, "flt", "float value");
  SetRange(f, IRange(0, 10));
  SetFlags(f, Knob::EARLY_STORE);
  XY_Knob(f, &_pos, "pos");
  String_knob(f, &_str, "text");
}

The knobs() function has a dual purpose. The first call to knobs() will be the “make knobs” phase. During this phase, calls to functions such as Float_knob() and XY_Knob() create knobs. These take a number of standard parameters:

  • the Knob_Callback pointer itself

  • a pointer to the data field within the Op

  • the internal name of the knob

  • the label to use for the knob (this parameter is optional, and defaults to the name)

Some knobs take additional parameters, such as the Enumeration_knob, which takes a pointer the list of values. The default value of the knobs is taken from the value presently in the integer addressed by the pointer. Note that knobs at their default value are not written out by NUKE’s save script. This means that if you change the default value of a knob, or make it unstable (by making it depend upon some environment variable, for example), and a script is saved at the old default value, then loading this up into a context where the default value is different results in the new default value. For this reason, it is advisable to avoid changing default values. Use the knobDefault() mechanism if you wish to change the value that a knob on a newly-created node has by default.

This behavior can be overridden by setting the flag Knob::ALWAYS_SAVE on the knob. This then always writes out the value of the knob, even if it is the default. This results in larger file sizes. In particular, it is recommended that if the knob’s default depends upon the input_format(), then you should use this flag, as when it is recreated on a script load the input_format() may be different.

The other purpose of the knobs() function is for NUKE and the knobs to communicate back to your Op to tell it what value the user has selected. In order to do this, rather than store the pointers you passed, it calls knobs() again, with a different object as the callback. This time, rather than creating the knobs functions like Float_knob(), calling knobs() again causes the knob to be evaluated at the proper time, and the value pointed to will be altered.

Because of this dual use, the knobs() function will be called frequently. Code that genuinely only needs to run on the knob-creation call should call Knob_Callback::makeKnobs() on the callback passed on. The calls to the knob function (SetRange, SetFlags, ClearFlags) are No-Ops during the non-makeKnobs phase.

When the knobs() function evaluates, there are two phases and knobs() is called twice. It is called as follows:

  • knobs() is called, storing knobs with the EARLY_STORE flag.

  • split_input, inputContext(), inputUIContext, and uses_input are called.

  • knobs() is called, storing knobs without the EARLY_STORE flag.

Validation

void Op::_validate(bool)

_validate() is called by NUKE on your Op to “validate” it. Validation consists of:

  • Calling, in turn, validate() on all the inputs that the Op is using in its current configuration. (It is possible to omit to validate inputs that are not used. For example, a mask input controlled by a Bool_knob that is off, and is not used at all, need not be validated.)

  • Setting up Op-type-dependent data. For the case of Iops, validate should set the “IopInfo”, which is stored in the info_ member on Iop. For Deep Ops, validate should set the DeepInfo.

See the respective sections on the various Op types for more info on the specifics on their validation implementation.