CallbacksExt Examples

From TouchDesigner 099 Wiki

This page contains examples of how to set up a component with a CallbackExt Python callback system. They assume a basic knowledge of TouchDesigner and the Component Editor and an intermediate knowledge of Python.

For full documentation of the CallbackExt extension, see: CallbackExt Extension.

Getting Started

All examples on this page will use the same custom component and will build on each other. To begin, create a Base COMP in your network and name it "userComp". Next, follow the instructions for adding CallbackExt to the component found in CallbackExt Extension: Adding CallbackExt to a Component. Promote the extension as described in that section.

Now we will create some Custom Parameters for userComp. First, use the Component Editor to create a custom page called "UserComp". Then, as described in the section linked above, add "Callbackdat" and "Printcallbacks" parameters. Tip: make the parameters look nice by using the labels "Callback DAT" and "Print Callbacks". These two parameters are used automatically by CallbackExt. Next create a few parameters for use in the examples: a Toggle parameter called "Toggle", a Str parameter called "String", and a Pulse parameter called "Pulse". When you are finished, your custom page should look like this:

UserComp Custom Parameter Page

Lastly, we will want a callbacks DAT to experiment with, so create a Text DAT (anywhere you like) and drag it into the Callback DAT parameter on userComp. TIP: because all the examples below use Python, you will probably want a Textport open, or better yet a devoted Textport editor pane (select "Textport and DATs" using Pane Type arrow in top left of any editor pane).

Example 1 - Parameter Callbacks using a Callback DAT

In this example, we will set up userComp to invoke a callback from its Callback DAT whenever one of its parameters is changed.

Setting up the Parameter Execute DAT

To set up responses to parameter changes, go inside userComp and create a Parameter Execute DAT. Change its OP parameter to .. (parent), its Parameter parameter to * (all), and toggle its Built-In parameter to Off. This tells the parameter execute to call the valueChange callback when any custom parameter is changed.

Now we will define that valueChange callback. In the DAT, edit the valueChange function to look like this:

def valueChange(par, val, prev):
	infoDict = {'parameter': par, 'value': val, 'previousValue': prev}
	parent().DoCallback("onParChange", infoDict)

These two lines of code are all that is necessary to use the CallbackExt callback system. The first line defines a dictionary of relevant info to be sent to the user callback. The next line performs the callback itself by calling the DoCallback method with a callback name (onParChange) and the infoDict defined above.

Even though we have no user callbacks defined, we can test this using the Print Callbacks feature. Just toggle On the Print Callbacks custom parameter and you will immediately see in the Textport a report of CallbacksExt attempting a callback:

onParChange NOT FOUND - callbackInfo: {'value': 'on', 'parameter': type:Par name:Printcallbacks owner:/callbackExtExamples/userComp1, 'ownerComp': type:baseCOMP path:/callbackExtExamples/userComp1, 'previousValue': 'off', 'callbackName': 'onParChange'}

This indicates that CallbackExt tried to call the onParChange callback, but it was not found. The dictionary shows all the data that would have been sent to the callback. Notice that the parameter key in the index holds the Printcallbacks parameter, indicating that this callback was invoked by toggling that parameter. Notice also that the ownerComp and callbackName are automatically provided by CallbacksExt.

By changing their values, you can see that similar callbacks will be attempted when any of the other parameters are changed, with the exception of the Pulse parameter, which acts differently than parameters with values. We'll use that one in a later example.

Creating a User Callback

Creating a user callback for the CallbacksExt system is very simple. Edit the callback DAT you created in the Getting Started section to include the following text:

def onParChange(info):
	debug("PAR CHANGE:", info['parameter'].name, info['value'])

A CallbacksExt callback is simply a function with the correct name and a single argument. That argument will be filled with the dictionary of information that you saw in the textport message. For the substance of this callback, we just print a debug message with the parameter and value elements of the info dictionary.

To test the newly created callback, toggle Off the Print Callbacks parameter to keep the messages from becoming overwhelming. Now change the Toggle and String parameters. You will see the callback's debug messages in the Textport.

Example 2 - Pulse Callbacks using an Assigned Callback

In the previous example, we used a callback defined in the Callback DAT. Assigned Callbacks are very similar, but rather than residing in the Callback DAT, the user assigns a python function to receive the callback.

For this example, we'll use a callback for the Pulse parameter. Go back to your Parameter Execute DAT and toggle On the On Pulse parameter. Then go inside and edit the onPulse function to look like this:

def onPulse(par):
	infoDict = {'parameter': par}
	parent().DoCallback("onPulse", infoDict)

Very similar to the valueChange callback in the first example, with just a little less information in the infoDict and a different name for the callback. You can now test that this callback is happening using the Print Callbacks parameter and pressing the Pulse parameter on userComp.

At this point, you could write an onPulse callback into the Callbacks DAT just like the onParChange callback. Instead, we're going to assign a function directly to the callback. To do this, create a Text DAT in your network next to userComp. In that DAT, enter the following text:

def printPulse(info):
	print("PAR PULSE:", info['parameter'].name)
 
op('userComp').SetAssignedCallback('onPulse', printPulse)

This code does two things. It defines the printPulse function that will act as our callback, and it assigns that function as the 'onPulse' callback for userComp. In your network, right-click on the Text DAT and select "Run Script". The callback is now assigned. Clicking userComp's Pulse parameter will now run this new callback.

A couple things worth noting about assigned callbacks:

  • When the CallbackExt extension is re-initialized, all assigned callbacks are cleared.
  • To remove an assigned callback, simply call SetAssignedCallback with None as the callback.

Example 3 - Callbacks With Return Values

In this example, we will show how to return a value from a callback and receive that value.

Receiving Callback Return Values

CallbackExt can accept return values from callbacks. All calls to DoCallback will return the provided info dictionary with a "returnValue" key containing the returned value. Of course, this value will be None if the callback does not return anything. If no callback is found, DoCallback will return None, so this is usually tested first. As an example, change the valueChange function in your Parameter Execute DAT to this:

def valueChange(par, val, prev):
	infoDict = {'parameter': par, 'value': val, 'previousValue': prev}
	if parent().DoCallback("onParChange", infoDict):
		debug('onParChange callback returned', infoDict['returnValue'])

Now, when a parameter value changes, the if clause tests to see if DoCallback returned anything. If this is False, no callback was found so obviously there will be no return value and nothing should be printed. If this is True, we will print out the returned information. Remember that the return value actually contains infoDict, which resolves to True because it is never empty. We already have a reference to infoDict so no need to store it again. Notice that this means infoDict will be changed when passed to DoCallback!

Try this out by changing parameters on userComp and you will see that all callbacks currently return None. If you comment out the onParChange callback in your Callback DAT, you will see that no debug message is produced, because no callback is found.

Returning a Value From a Callback

Returning a value from a callback is just like returning a value from any other function. Change your onParChange function in your CallbackDAT to look like this:

def valueChange(par, val, prev):
def onParChange(info):
	debug("PAR CHANGE:", info['parameter'].name, info['value'])
	if info['parameter'].name == 'String':
		return 'String changed!'

Now a return value is sent when the String parameter is changed. Try it out and see.

Return Value Instructions

It is important to let users know when a return value is expected from a callback, and what format that return value should take. The standard way to do this with CallbackExt is to include instructions in an "about" key in the callback's info dictionary. This is left up to the programmer, as CallbackExt has no way to know what the intent or use of any given callback is.