In the vast majority of cases, knob use in NUKE is static to that Op, that is, they’re created up front, maintained for the Op’s lifetime, and then destroyed automatically. Some semblance of dynamism is given by the ability to show and hide knobs dependent on the state of others, but the knob’s themselves are simply hidden - they’re still present. Other approaches for dynamic content involves using knobs with the ability to display arbitrary amounts of data, for example the knobs-table_knob. This is perfectly adequate most of the time, but there may come a point 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 utilizing 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 in two stages:
add_knobs, as the name suggests, simply creates the initial set of dynamic knobs. If no dynamic knobs are required 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 or not.
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 (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 and replace_knobs.
bool getShowDynamic() const { return knob("show_dynamic")->get_value(); } //KNOB_CHANGED_ALWAYS is set, so 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:
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 tricky, we’d recommend starting off your first foray by extending the above example. For this exercise, take the existing DynamicKnobs example, and: