It is possible to create custom knobs within plugins by inheriting from this class.
This can be necessary if the Op/Iop needs to keep
around state that is not representable with one of the built-in knobs.

To include a custom Knob in your Op, you would for example do the following in your knobs function:

CustomKnob1(DemoKnob, f, &_data, "data");

This would invoke your knob constructor as follows:

new DemoKnob(&f, &_data, "data");

If you do not wish to specify a data pointer, you can use CustomKnob0. If you wish to specify
a label as well as a name, you can use CustomKnob2.

Knobs are written out as part of the saved script (or when copied to the clipboard)
only if the not_default() function on them returns true. Otherwise, they are assumed
to still be at their default value, and not be worth writing out.

A knob’s default value can per-knob rather than per-knob-class. It is entirely up to
the knob how this is handled, except that it should remain invariant for the lifetime of
that knob.

to_script should serialise the data represented by the knob. The default implementation calls
get_text to get the string to write out, and then automatically handles
any quoting necessary.

Both functions take a pointer to an OutputContext. If this is NULL, it is intended
for saving or copying, and therefore should serialise the entire internal state of the knob.

If it is not NULL, then it is being invoked from the value function, or similar, and should
give a serialisation of that knob’s state at the given frame and view. For example, a simple
knob representation an animation might choose to produce key-value pairs in its full serialisation
format. For example, a line between the points (0, 0) could be represented as “0 0 100 1” (0, 0) to
(100, 1). If the functions were to be called with an OutputContext, it should then evaluate that curve
at the appropriate frame and time. For example, it might take the time value as x on the curve.
Therefore, if OutputContext had a frame of 50, it should produce an output of “0.5”.

The “quote” parameter on to_script indicates whether or not the output needs quoting for TCL safety.
When writing to a file, the output from to_script is directly placed after the knob name. For example,
in this:

GainExample {
gain 0.5
name GainExample1
}

the text “0.5” was the output from to_script for that knob. In a more complex example, where the gain
knob has been split, this will look like:

GainExample {
gain {0.1 0.2 0.3 0.4}
name GainExample1
}

The extra quoting is needed as newlines are treated no differently to other whitespace, and otherwise
it would assume 0.2 was the name of the next knob. Therefore, the “quote” parameter is passed as true,
to indicate that the output needs potentially quoting if there are any characters in it which would
disrupt TCL parsing. The DD::Image::Knob::cstring helper class exists to deal with this, a simple implementation
might be:

from_script is passed the serialised value. It should set the knob’s internal state to match. This is a
value that has already stripped of any quoting that was added in to_script because of quote. For example,
if the script file is

GainExample {
gain {0.1 0.2 0.3 0.4}
name GainExample1
}

then from_script will be passed the string “0.1 0.2 0.3 0.4”. You can use the helper class
DD::Image::Knob::Script_List to help parse TCL-formatted lists. For example, constructing a Script_List with
the string “0.1 0.2 0.3 0.4” as a constructor will construct an object with size() = 4, whose elements are
“0.1” “0.2” “0.3” and “0.4”. Script_List can deal with elements themselves being TCL-quoted structures, so
“{0 1} {2 3}” would parse to a two-element list, with elements “0 1” and “2 3”. Script_List can then be
used in turn on these elements, etc.

from_script should return true if the value changed. Also, if the value changed, it should have called
new_undo before the internal state changed, and changed after it does. This is so that NUKE is aware
that the script may need evaluating, and also so that the undo system can be maintained.

Any change made to the internal state of the knob should be surrounded by a call to new_undo before
(at the point of the new_undo a to_script should result in the original value), and changed after.
If the knob is not to be included in the Undo stack then it can be marked with the flag Knob::NO_UNDO,
and calls to new_undo can be omitted, but calls to changed should still be made.

As described in [...], NUKE updates your Op with the new values of knobs by calling knobs and arranging
for the closure to place the new values in those pointed to. It does this by calling the store function
on the knobs. You therefore need to implement the store function.

StoreType is used internally in NUKE for type information. In a custom knob it can reasonably be ignored,
as it will be ‘Custom’. dst here is the pointer that was passed to the CustomKnob1 or CustomKnob2 function as
its third argument. For example, if you included your knob in knobs like so:

CustomKnob1(DemoKnob, f, &_data, "data");

then it would result in store() being called with the dst pointing at &_data.

It is expected that store will evaluate the knob at the frame and time given in context, and copy that cooked
data into the destination pointer. If the knob doesn’t have any animation, this might be a simple memcpy.
store is also an appropriate place to do any proxy-scaling, as the OutputContext contains the proxy scaling
information.

It is important that custom knobs do this, and that in particular that engine functions do not directly access
the same data store, as the internal knob value could be changed _during_ a call to engine, which might result
in the cache being poisoned.

In addition to this, the store function should also add the cooked data it stored to the hash it has been passed in.
This will ensure proper calculation of the Op’s hash.

The previous section touched briefly on writing arbitrary data using the to_ and from_ script methods provided on the Knob class, so go back and read it if you haven’t already. In this section we’ll break down a more complete example from the NDK - namely Serialize.cpp, the interesting parts of which are shown in the snippets below. In the first part, we set up the custom SerializeKnob as a sub-class of Knob, allowing an instance of CallbackHandler supplied to be the constructor. The idea is that we make our plug-in a CallbackHandler, then when NUKE asks our SerializeKnob if it wants to save any data (i.e. by calling its to_script() function), the SerializeKnob will then ask our plug-in to covert any important data into a std::string (i.e. serialize the data) and pass it back to the SerializeKnob to be saved in the script. In the reverse process, when the script contains some saved information about the SerializeKnob, NUKE asks the SerializeKnob to process the data (stored as a string) in the script by calling its from_script() function. This passes the stored data string to our plug-in, and asks it to deserialize the data into something useful.

The following code snippet shows how our plug-in “Serialize” inherits and implements the load and save functions of the CallbackHandler. In this contrived example, we’re simply loading and saving the std::string data found in the important_data_to_keep variable.

As we’re only manipulating string data, the equivalent could be accomplished by a simple Text_knob, however the idea is to show how it could be extended to more complicated scenarios. In practice, you’d store your data in an appropriate structure or class, and then use a robust serialization scheme to handle the object-to-string conversion, such as the one found in the Boost library (http://www.boost.org/). The code for saving to a Boost archive is commented out in the above snippet.

Included in the example directory is a custom node with a custom knob that implements a ‘add’ or ‘gain’ operation.

To compile to this at the command line ensuring the QTDIR is set and pointing to your Qt toolkit, plus your NDKDIR set and pointing to the directory up from the DDImage include directory, then:

make -f Makefile.qt

Once the plugin is compiled ensure it is in your plugin path, fire up NUKE and create the new node ‘AddCustomQt’. You should see a simple node with a dial instead of a slider to control the gain level.

Keep in mind that a knob can have multiple instances of widgets ( eg make_widget can be called more than once ). For example when link knobs are used more than one widget can be created. Make sure you don’t rely on a one to one mapping between widgets and knobs.