Knob changed & linking controls

The knob_changed() method

The knob_changed method on an Op is called after any knob without the NO_KNOB_CHANGED flag set (see Knob Flags for more information) has its value changed by a user interaction event. It allows user interaction on one knob to show and hide other knobs, or to set their values. For example, if you have a mode drop down then you may wish to either show and hide or disable and enable knobs related with the particular current mode. If knob_changed returns zero, then NO_KNOB_CHANGED will be set on that knob and no subsequent knob_changed events called as a result of interaction with it.

Note

Many base Op classes implement various knobs which required knob_changed to be called on them to provide their respective functionality. Thus if you do override knob_changed for one of your Ops it is recommended that you chain up to the base classes knob_changed at the end of your override.

The knob_changed method is passed a knob pointer which represents that which was altered in some way. There are a number of methods for checking whether this is the knob you’re checking for, however the most commonly employed is to use the knob->is(<name>) method, passing a char* mapping to the knob name you defined in the related knobs call. You did make that knob name individual right?

For example, the following snippet from the DeepCopy sample code works, but is sub-optimal.

int knob_changed(DD::Image::Knob* k)
{
  knob("znear")->enable(_useZMin);
  knob("zfar")->enable(_useZMax);
  knob("bbox")->enable(_useBBox);
  return 1;
}

This basically enables or disable sliders based on the state of the ‘use’ bool knobs (ie checkboxes) next to them. The problems here are that:

  • knob_changed always returns 1, so NUKE is unable to optimise out knob_changed calls for knobs that are not required.
  • It does not chain the call to DeepOp::knob_changed, from which the Op is inherited. Currently this is not a problem as DeepOp is not providing any knob_changed functionality in use here, however this may change in the future.
  • It does not check which knob caused the call in the first place, so the enable and disable calls are always called regardless, on every change made to the user interface panel.

So we could change this to:

int knob_changed(DD::Image::Knob* k)
{
  if(k->is("use_znear")) {
     knob("znear")->enable(_useZMin);
     return 1;
  }

  if(k->is("use_zfar"))  {
     knob("zfar")->enable(_useZMax);
     return 1;
  }

  if(k->is("use_bbox"))  {
     knob("bbox")->enable(_useBBox);
     return 1;
  }

  return DeepOp::knob_changed(k);
}

This is a definite improvement, as knob_changed is only called once for unrequired knobs, the slider enabling/disabling is only called when required and we’ve future proofed by chaining to DeepOp::knob_changed. The problem now is that if you save a script with one of the use checkboxes switched off (and thus one of the sliders disabled) when the same script is loaded the slider in question is enabled again. This is because the knob_changed method will not get called for changes made in a previous session (instead the value of the changes made is stored via the standard Knobs() call). Near fear though, as there are a number of solutions available. The one I’d recommend is to use the showPanel event that gets results in a knob_changed event. This is a useful little call that gets put through when the user opens the control panel. This is thus done on a node creation (with showPanel set to true as is the norm), or when a user opens a node’s param panel after loading a saved script. As such it’s only really useful for the situations where what is being set is interface related (since it won’t get called on a script terminal render and so if you’re using it to change values, it won’t get a chance to do so). The showPanel event can be checked for either via passing the event name to the k->is(<name>) call, or using the useful showPanel knob static defined in Knob.h. Extending the previous example we get:

int knob_changed(DD::Image::Knob* k)
{
  if(k == &DD::Image::Knob::showPanel) {
     knob("znear")->enable(_useZMin);
     knob("zfar") ->enable(_useZMax);
     knob("bbox") ->enable(_useBBox);
     return 1;
  }

  if(k->is("use_znear")) {
     knob("znear")->enable(_useZMin);
     return 1;
  }

  if(k->is("use_zfar"))  {
     knob("zfar")->enable(_useZMax);
     return 1;
  }

  if(k->is("use_bbox"))  {
     knob("bbox")->enable(_useBBox);
     return 1;
  }

  return DeepOp::knob_changed(k);
}

A little bit of logic duplication, which could easily be cleared up with embedding in some function calls on a bigger example, but working more efficiently than the original.

Note

If knob_changed() returns zero for a particular knob’s event, the NO_KNOB_CHANGED flag will be set automatically on that knob and no subsequent knob_changed() methods will be called for further user interaction events on that particular knob. This is often the cause of problems where it appears knob_changed() is not being called reliably. To solve this, ensure you’re returning non-zero for those events you subsume.

The knob_changed_finished method

A small subset of knobs have started supporting the knob_changed_finished() method. Similar to knob_changed(), this reports after an interface action changed, however in the case of _finished() it only does so once the drag or change event being registered has been completed and the mouse button in question released. This allows processor intensive, slow, operations to only be calculated once the change is complete, and not at every incremental step. If you’re after similar events, check the knob you’d like it on and if it doesn’t get called, file a feature request with The Foundry’s support address. As usual, the more people who ask for something, the more likely it is to happen.

Linking controls

Linking controls, as seen in places such as Exposure’s Gang mode, can be achieved very simply using the knob_changed methods. Simply check for the appropriate knob on the knob pointer and if found, call set_value on the target knob, passing the change in question. Note that both source and target knobs should be accessed via their respective knob(<name>) objects, using set_value, get_value, and similar calls as required, since any data storage variables associated may not have been updated by the time this is called. If one control is simply for output purposes, you may wish to set it as OUTPUT_ONLY via SetFlags. Additionally, if the knob type in question is a form of array knob then you may want to check out the Value provider (output knobs) section.

Buttons

Button types don’t actually have provide their own action implementation, instead they simply call knob_changed when pressed. To provide an implementation, override knob_changed checking for your button implementation (and returning non-zero!) and Robert is your mother’s brother. If you’re looking to create a button implementing the ‘execute’ or ‘render’ functionality seen on Write nodes, on the CurveTool and so forth, then check out the Executable Ops section.

Table Of Contents

Previous topic

Knob Flags, Ranges & Tooltips

Next topic

Dynamic creation of knobs