The vast majority of the time, knob use in NUKE is static to that Op (ie they’re created up front, maintained for the Op’s lifetime and destroyed automatically). Some semblance of dynamism is given by the ability to show and hide knobs dependent on the state of others, however the knob’s themselves are still present, simply hidden. Other approaches for dynamic content involves using knobs with the ability to display arbitrary amounts of data, for example the knobs-table_knob. The majority of the time, this is perfectly adequate, however there may come the time that you actually need to destroy or create knobs on the fly, for example as the Reader class does to create or destroy knobs dependent on the underlying reader/writer type being used. To do this, NUKE provides replace_knobs functionality.
Now, replace_knobs is a little tricksy in it’s details, so if you can avoid using it (instead utilising the show/hide or dynamic content knobs approaches mentioned before) then we recommend you do so. There are of course circumstances where you need to, so in this section we’ll run through the nitty gritty of the implementation, with reference to the simplest case example included in the NDK as the DynamicKnobs.cpp sample.
Using replace_knobs requires implementation of 3 key areas. Primarily you need to create a static function to be used as the knob creation callback. This’ll be responsible for creating the dynamic knobs, dependent on whatever criteria you define. Secondly you need to call two Op level functions add_knobs and replace_knobs in your Knobs and knob_changed calls respectively. Both are passed your callback as a void*, call your callback passing the required Knob_Callback used to create knobs, and return the number of knobs created. Add_knobs, as the name suggests, simply creates the initial set of dynamic knobs. If there aren’t to be any dynamic knobs by default then it doesn’t need to be called (and indeed, in the DynamicKnobs.cpp sample this is the case). Replace_knobs takes the same arguments as add_knobs, with the addition of an int defining the number of knobs to be replaced. In most normal circumstances this’d be the number of knobs created by the last call to add_knobs or replace_knobs.
Check out the salient parts of DynamicKnobs as reproduced below:
class DynamicKnobs : public NoIop
{
bool _showDynamic; //Storage for our static knob. This controls whether dynamic knobs are shown.
int _numNewKnobs; //Used to track the number of knobs created by the previous pass, so that the same number can be deleted next time.
float _dynamicKnob; //Storage for our dynamic knob. Normally this would be dynamic (ie heap allocated) itself, but shown as local for simplicity.
public:
DynamicKnobs(Node* node) : NoIop(node),
_showDynamic(false),
_numNewKnobs(0),
_dynamicKnob(0.0f)
{
}
virtual void knobs(Knob_Callback);
virtual int knob_changed(Knob*);
static void addDynamicKnobs(void*, Knob_Callback); //Our add knobs callback, used by add_knobs & replace_knobs.
bool getShowDynamic() const { return knob("show_dynamic")->get_value(); } //Because KNOB_CHANGED_ALWAYS set, can't use _showDynamic directly.
float* getDynamicKnobStore() { return &_dynamicKnob; } //Get for dynamic knob store. As above, simplified.
//... snip.
};
void DynamicKnobs::knobs(Knob_Callback f)
{
Bool_knob(f, &_showDynamic, "show_dynamic", "Show Dynamic");
SetFlags(f, Knob::KNOB_CHANGED_ALWAYS);
//If you were creating knobs by default that would be replaced, this would need to
//be called to create those.
//_numNewKnobs = add_knobs(addDynamicKnobs, this->firstOp(), f);
//Call the callback manually this once to ensure script loads have the appropriate knobs to load into.
if(!f.makeKnobs())
DynamicKnobs::addDynamicKnobs(this->firstOp(), f);
}
int DynamicKnobs::knob_changed(Knob* k)
{
if(k==&Knob::showPanel || k->is("show_dynamic")) {
_numNewKnobs = replace_knobs(knob("show_dynamic"), _numNewKnobs, addDynamicKnobs, this->firstOp());
return 1;
}
return NoIop::knob_changed(k);
}
void DynamicKnobs::addDynamicKnobs(void* p, Knob_Callback f) {
if(((DynamicKnobs*)p)->getShowDynamic()) {
Float_knob(f, ((DynamicKnobs*)p)->getDynamicKnobStore(), "dynamic_knob", "Dynamic Knob");
}
}
As you can see, we stash the numbers of knobs created in an Op instance variable _numNewKnobs and pass it into each future call of replace_knobs. This also demonstrates two important facets of using replace_knobs, namely:
The add_knobs and replace_knobs methods also allow a void* pointer to be passed, which can be used for managing a class which stores both data defining knob creation criteria and for managing the data store variables of the replacement knobs (if required). An excellent example of extending this interface to provide a generic mechanism which manages it’s own underlying stores is presented in Jonathan Egstad’s DynamicKnob’s example which can be found on Nukepedia. As you can see in the simplest case example presented, we simply use the Op itself, and use static stores for the dynamic knob stores.
Since utilising replace_knobs can be kinda tricky, I’d recommend starting off your first foray by extending the above example. For this exercise, take the existing DynamicKnobs example and: