In this section we’ll give an overview of some fundamentals of writing NUKE plug-ins, as well as an intro to the underlying architecture and call order.
This is not going to get into that much detail - for that refer to 2d-architecture and Architecture - and is aimed mainly at those new to the NDK. Also highly recommended: if you haven’t ever worked within NUKE before, run through some of the basic videos on the Foundry website to get a handle on common terminology, interfaces, and work practices.
NUKE is based around the premise of building a nodegraph (or DAG) of image operators to combine, produce or otherwise operate on image data. Every object in the node graph is represented by a Node.
A Node is a block in the DAG that is visible to the user. Each node has a set of input arrows that connect it to other nodes, and any number of output arrows connecting it to the inputs of other nodes. The actual C++ node class is not visible to a plug-in writers, in order to allow it to be changed without breaking plug ins. Instead there are a number of access functions that retrieve information using methods on Operators.
Operators are the class that a plug-in creator will generally write. They are connected by their inputs to other Operators, not Nodes. Each Node creates at least one operator and superficially you can think of the node and operator as being equivalent. However there are many reasons a node may contain more than one operator:
- Many operators implement themselves by building internal trees of operators.
- An Operator may ask for an input Op to be created at more than one frame number or view, to allow time blending, stereoscopic or shifting effects.
- Cloning will create more than one operator for the “original” node
- Looping (nyi) will create an operator for each iteration
- Readers and writers create a FileHandler based on a filename
Operators are also known as ‘Ops’ in NUKE parlance.
The slightly confusing part of this is NUKE creates an Op and interrogates it to decide on what the node will be like in the DAG. Therefore there is always one Op per Node in the DAG. But there maybe multiple other Ops created as well as described above.
It is very important to remember that as a plug-in writer you are writing an ‘Op’ not a node. What the user sees in the DAG as a set of nodes does not necessarily reflect what Operators NUKE creates to do data processing.
Any node in the DAG needs an ‘Op’ to do processing of data in the DAG. All data processing plug ins in NUKE are derived from the Op class.
- Ops define what knobs will be visible to the user in the NUKE node panel.
- Ops are instanced by NUKE to do data processing a given time.
- Ops define how many inputs the user sees on the node.
- Ops define what inputs are allowed to be connected to this op.
- Ops define how the inputs are accessed in temporally.
- Ops sign their parameters to return a unique hash for caching.
Op should be considered a virtual base class. No NUKE operators use it directly, they subclass one of the more specialised classes such as Iop ( image processing ), GeoOp ( geometry processing ), ParticleOp ( particle processing ), and DeepOp ( deep image processing ). In this intro we shall look at initially Iops to describe how image processing occurs. Other sections will look into the other sub-systems.
Knobs are the NUKE name for parameters that appear on a node.
An Op is queried by NUKE for what Knobs should be available on the node. The Op also tells NUKE about local parameters it has for each knob that can have values stored on them frozen for a given frame ( or context ) for image processing.
The knob is where parameters are stored, key frames, and animation curves. An op itself has no notion of these concepts, it is only instanced a frozen frame ( or context ) for image processing.
For instance a simple Op with one floating point knob in its control panel is defined like this in C++:
Float_Knob( f, &_value, “mygain” );
Here NUKE creates a floating point knob for the node and uses this to track key frames, values and animation. The first argument &_value is a local parameter that NUKE will set to a given value for image processing when required.
For instance the user may set a value and key on this knob to 0 at frame 1 and 10 at frame 10.
When NUKE uses this op for image processing for say frame 10 it will fill the _value parameter with the value 10 before it calls the op to do image processing. Or say if it needs to render frame 1 it will fill _value with 0.
Now we will look at the typical processing events that occur when the user does some actions in NUKE to view an image.
NUKE works on a ‘pull’ system. That is a node at the bottom of the node tree ‘pulls’ data from its inputs, and they in turn ‘pull’ from their inputs until there are no more nodes to pull from, that is either NUKE has hit a generator node such as a Read or other generator such as a CheckerBoard.
For instance the following occurs when a user creates a CheckerBoard node and views its output:
- User -> Create a CheckerBoard
- NUKE -> Loads and creates an instance of the CheckerBoard Op
- NUKE -> Calls the knobs() function on the op to figure out what parameters the node has
- NUKE -> opens the control panel and shows the parameters for the knobs
- User -> At some point the user attaches a Viewer or output node ( Write ) to produce an image
- NUKE -> Looks to see if it has any ops around that match the current node graph at the current context ( frame number ), if not it creates new ones.
- NUKE -> Stuffs the values of the knobs into the ops frozen at a given context ( store )
- NUKE -> The output op asks its inputs for what channels and image sizes it produces ( validate ). Each Op recursively calls its inputs asking for their image sizes and channels until there are no more inputs.
- NUKE -> The output op requests a certain image size and channels from all its inputs ( request ). Each Op recursively calls its inputs requesting a certain image size and channels required to output the image requested until there are no more inputs.
- NUKE -> The output op requests rows from all its inputs ( engine ). Each Op recursively calls its inputs asking for rows until there are no more inputs. Each row as it flows back down the tree to the output is modified by each Op.
The 2d-architecture section will explain each one of these steps in more detail.