Knob Changed and 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 user interaction events. 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 dropdown you may wish to either show and hide or disable and enable knobs related to the particular current mode. If knob_changed returns zero, then NO_KNOB_CHANGED is set on that knob and no subsequent knob_changed events are 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. So, 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 the alteration in some way. There are a number of methods for checking whether this is the knob you’re checking for, but 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 unique 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 (checkboxes) next to them. The problems here are that:

  • knob_changed always returns 1, so NUKE is unable to optimize 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 - 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 use this instead:

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 is not called for changes made in a previous session (instead the value of the changes made is stored in the standard Knobs() call).

Never fear though, as there are a number of solutions available! The one we’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 Properties panel. This is done on a node creation (with showPanel set to true as is the norm), or when a user opens a node’s Properties panel after loading a saved script. As such it’s only really useful for situations where the control 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 by passing the event name to the k->is(<name>) call, or using the 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);
}

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

Note

If knob_changed() returns zero for a particular knob’s event, the NO_KNOB_CHANGED flag is set automatically on that knob and no subsequent knob_changed() methods are 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, but in the case of _finished(), it only does so once the drag or change event being registered has completed and the mouse button in question is 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 through 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 using SetFlags. Additionally, if the knob type in question is a form of array knob, you may want to check out the Value Provider (Output Knobs) section.

Buttons

Button types don’t actually provide their own action implementation - 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 on, then check out the Executable Ops section.