Extensions

From Derivative
Jump to: navigation, search


When creating a custom Component, it is often desirable to extend the component's functionality and data. This can be accomplished using Python Extensions. You can add local Python data and functionality, including data that will work with TouchDesigner's procedural system. Extensions are specified as a list of Python objects on the Extensions page of a Component. Each of a Component's extensions can then be accessed by other operators, either directly (when Promoted) or through the ext object.

Creating extensions requires a basic understanding of the Python programming language. Some great learning resources:

Creating Extensions

The easiest and recommended way to create extensions is using the Component Editor. You can also set up an extension directly using your custom Component's Extension Parameters.

Using The Component Editor To Create An Extension

To open the Component Editor, select Customize Component... from the RMB Menu of your custom Component. Open the Extension Code section of the dialog.

Component Editor Extensions

The Extension Code section of the Component Editor assists in creating extensions for your custom Component. To create a new extension, simply enter the name in the textbox and click Add. Once created, you can edit, reinitialize, or delete the extension using the buttons on the right. The + expands advanced features that let you create a custom definition and/or name for your extension, or turn Promotion on or off. For more info, see the Extension Parameters section below.

TIP: It is standard in TouchDesigner to capitalize your extension name and add the suffix Ext.

Extension Parameters

NOTE: using the Component Editor automates this process. If you are using that method, you won't need to work directly with these Parameters.

ExtPars.PNG

To create an extension directly, you must set up your custom Component's Extension Parameters. In most cases, you will also set up an associated DAT containing your extension code.

Each of a Component's four available extensions has three parameters associated with it:

Extension Object

The Extension Object Parameter contains a bit of Python code that must return a Python object when executed. A reference to this object will be stored as the given extension. In the image above, we see the most common usage: a class (ExampleExt) instantiated with the Component (me) as argument. The class is defined in a DAT, also named ExampleExt inside the Component.

Expert Python users will note that the code in this Parameter can return any Python object, which has some creative uses, such as Internal Parameters.

Extension Name

By default, an extension is referenced by the Python object's class name. In the above example, it would be called ExampleExt. The Extension Name parameter lets you give the extension a name of your choosing. So, in the above image, we see that ExampleExt will use the name CustomNameExt.

Promote Extension

Promoted Extensions allow more direct access to their data and functionality. See Promoting Extensions.

Re-Init Extensions

TouchDesigner will automatically re-initialize extensions when they change, but sometimes you will want to do this manually. The Re-Init Extensions Parameter will re-run the code in the Extension Object parameters and replace the Component's extensions with the results.

Writing Extension Code

In most cases, you will write extension code in a DAT within your custom Component. If you use the Component Editor to create your extension, this is set up automatically for you. We will use the default extension that the Component Editor creates as an example in the following sections.

from TDStoreTools import StorageManager # deeply dependable collections/storage
TDF = op.TDModules.mod.TDFunctions # utility functions

class DefaultExt:
	"""
	DefaultExt description.
	"""
	def __init__(self, ownerComp):
		# The component this extension is attached to
		self.ownerComp = ownerComp

		# Attributes:
		self.a = 0
		# Promoted attributes:
		self.B = 1

		# Properties
		TDF.createProperty(self,
				'MyProperty', # name
				value=0, # starting value
				dependable=True, # dependable (True, False or "deep")
				readOnly=True) # write via self._MyProperty.val

		# stored items (persistent across saves and re-initialization):
		storedItems = [
			# one info dictionary per stored item:
			{'name': 'StoredList', # Required. Promoted if capitalized.
			 'default': [], # default value
			 'property': True, # allows access via self.StoredProperty
			 'readOnly': True, # change via ownerComp.stored['StoredProperty']
			 'dependable': True}, # if True, collections are deeply dependable
		]
		self.stored = StorageManager(self, ownerComp, storedItems)

	def myFunction(self, v):
		debug(v)

	def PromotedFunction(self, v):
		"""
		Promoted Function description.
		"""
		debug(v)

Importing Modules

TDF = op.TDModules.mod.TDFunctions # utility functions
from TDStoreTools import StorageManager # deeply dependable collections/storage

In the first two lines of the default extension we see two ways of importing things in TouchDesigner. The TDFunctions module is imported via the global OP Shortcut TDModules, where special TouchDesigner Python modules are available, followed by mod access to the TDFunctions module itself. The next import is done with a standard Python import, which allows access to any available Python modules. For more info on this, see Importing Modules.

Python Attributes

Python attributes are created using standard Python. After the class definition, we see the following code creating attributes:

		# The component this extension is attached to
		self.ownerComp = ownerComp

		# Attributes:
		self.a = 0
		# Promoted attributes:
		self.B = 1

The ownerComp attribute is standard and should be used in every extension. It is passed as an argument during extension creation and holds the Component that the extension is attached to. Next, two custom attributes are created, a and B. Because B is capitalized, it can be promoted, which we'll explore below.

Python Properties

Python properties are like attributes but they have accessor functions. If you are not familiar with this concept, there are a number of tutorials online... here is a good one. Properties can be created using standard Python methods, which we won't explore here. In this default extension, we use a utility function from the TDFunctions module called createProperty.

		TDF.createProperty(self,
				'MyProperty', # name
				value=0, # starting value
				dependable=True, # dependable (True, False or "deep")
				readOnly=True) # write via self._MyProperty.val

This code creates a property called MyProperty which has a starting value of zero, is dependable (explained below), and is read-only. You can read the value of this property directly: value = self.MyProperty. Because it is read-only and dependable, you set it like this: self._MyProperty.val = newValue. If it were not dependable, you would set it like this: self._MyProperty = newValue. And if it were not read-only, you would set it directly: self.MyProperty = newValue.

All these features will be useful to advanced Python users, but less experienced programmers will more often use createProperty in a simpler way:

		TDF.createProperty(self, 'MyProperty', value=0)

This creates MyProperty with a starting value of zero, using the defaults of dependable=True and readOnly=False.

Storage Manager

All of the properties above are re-created when the extension initializes, which happens when a file is loaded, the Operator is cut/pasted, when the Re-Init Extensions button is pressed, etc. To store Python values in such a way that they are persistent during these events, use the StorageManager class.

Important: in many cases it is better to use Custom Parameters for persistent values, because that gives users direct access to them on the Component's Parameter page. You might choose to use StorageManager to hide values or to

		storedItems = [
			# one info dictionary per stored item:
			{'name': 'StoredList', # Required. Promoted if capitalized.
			 'default': [], # default value
			 'property': True, # allows access via self.StoredProperty
			 'readOnly': True, # change via ownerComp.stored['StoredProperty']
			 'dependable': True}, # if True, collections are deeply dependable
		]
		self.stored = StorageManager(self, ownerComp, storedItems)

The use of this object is explained in detail in the StorageManager Class wiki.

Extension Functions

Define functions in extensions exactly as you would in any Python class:

	def myFunction(self, v):
		debug(v)

	def PromotedFunction(self, v):
		"""
		Promoted Function description.
		"""
		debug(v)

As with other attributes, capitalized function names will be accessible through promotion. Within extension functions, use self.ownerComp to accessed the Component that the extension is attached to.

Dependable Values

Often you'll want to reference extension properties in Parameter Expressions and have those expression values update automatically when the property value changes. This functionality can be created by using Dependencies. The easiest way to create dependable values is to use the createProperty function or StorageManager class, as explained above.

Python collections (lists, dictionaries, and sets) can be a bit more complicated to make dependable. For a full explanation of how to achieve this, see Deeply Dependable Collections.

Accessing Extensions

There are three ways to access the extensions on your Component. In order of most common usage to least, they are: Promotion, the ext member, and the extensions member.

Promoting Extensions

The simplest way to access attributes of an extension is to Promote it. If an extension is promoted, all its capitalized methods and members are available at the Component level. Using the above examples, and assuming the extension were on an Operator named myCustomComp, all of the following would work:

  • op('myCustomComp').B
  • op('myCustomComp').MyProperty
  • op('myCustomComp').StoredList
  • op('myCustomComp').PromotedFunction('test')

The following would not work, because they are not capitalized:

  • op('myCustomComp').a
  • op('myCustomComp').myFunction('test')

The ext Member

Another powerful way to access extensions is the ext member. All Operators have an ext member that gives them access to extensions within them (or their parents, see below). Extensions are accessed via their class name, or, if it's not blank, the string in the associated Extension Name parameter. Attributes don't have to be promoted to be accessed through the ext member. Using a couple of the examples above, the following would work:

  • op('myCustomComp').ext.ExampleExt.B
  • op('myCustomComp').ext.ExampleExt.PromotedFunction('test')
  • op('myCustomComp').ext.ExampleExt.a
  • op('myCustomComp').ext.ExampleExt.myFunction('test')

Another important attribute of the ext member is that it exists on all Operators and will search up the network hierarchy for the named extension. This means that every Operator within op('myCustomComp') would be able to use the code: me.ext.ExampleExt.myFunction('test'). Technically, the me is not even required in most cases, so any Operator within op('myCustomComp') could use this in a script: ext.ExampleExt.myFunction('test') or this in a parameter expression: ext.MyProperty.

The extensions Member

In some rare cases, you may want to access a Component's extensions directly through its extensions member. This is a simple Python list containing a Component's four extension objects. Once again, using the above examples, you could access ExampleExt this way:

  • op('myCustomComp').extensions[0].myFunction('test')

Example Extension: ColorExt

This section will describe an example Extension to illustrate many of the concepts on this page. It will be presented as a tutorial so that you can build the extension yourself.

The ColorExt extension creates a system that stores a baseColor to be used as a background in panels inside the custom Component. The baseColor is not meant to be directly changeable, but can be incremented through a list of available base colors.

Note: because Python errors are reported there, it is always worthwhile to keep a Textport open when working with extensions.

Step 1: Create The ColorExt Extension

For starters, we will want an empty Component to customize. Create a Container COMP somewhere in your network. It's always a good idea to name your Operators, and this is especially true of custom Components, so rename it colorExample. Use the Component Editor to add an extension named ColorExt. This creates a default extension in a DAT inside colorExample. It also sets up colorExample's Extension Parameters to use the new extension. Click the "Edit" button in the Component Editor to edit ColorExt.

Step 2: Write The ColorExt Extension

The ColorExt extension will look a lot like the default because we are intentionally using many of the same features. Here is the code for you to enter or cut/paste:

from TDStoreTools import StorageManager  # deeply dependable collections/storage
TDF = op.TDModules.mod.TDFunctions  # utility functions

class ColorExt:
	"""
	ColorExt stores a base color to be used by operators inside it.
	"""

	def __init__(self, ownerComp):
		# The component this extension is attached to
		self.ownerComp = ownerComp

		# Attributes:
		self.baseColorList = [
			# [red, green, blue]
			[1, 0, 0],
			[0, 1, 0],
			[0, 0, 1],
			[1, 1, 1]
		]

		# stored items (persistent across saves and re-initialization):
		storedItems = [
			{'name': 'ColorIndex', 'default': 0, 'readOnly': True},
		]
		self.stored = StorageManager(self, ownerComp, storedItems)

		# Properties
		TDF.createProperty(self, 'BaseColor',
				   value=self.baseColorList[self.ColorIndex],
				   readOnly=True)

	def IncrementBaseColor(self):
		"""
		Promoted Function description.
		"""
		self.stored['ColorIndex'] += 1
		if self.ColorIndex == len(self.baseColorList):
			self.stored['ColorIndex'] = 0
		self._BaseColor.val = self.baseColorList[self.ColorIndex]

ColorExt Attributes

We only need one attribute for ColorExt, which is the list of available base colors. Sensibly, we have named this baseColorList. Notice that it is not promoted because it starts with a lower case letter. Though there is no way to truly protect Python attributes from being changed from the outside, not promoting them generally indicates that they are not meant to be messed with except from within.

ColorExt Stored Items

We will store one item, ColorIndex, our index into baseColorList. This way, whichever base color is chosen won't be reset when the extension is reinitialized. By way of example, we have promoted ColorIndex (by giving it a capitalized name) but we have once again discouraged changing it from the outside by setting it to read-only. We will see how to set it below in The IncrementBaseColor Function.

Note: the positions of stored items and properties are switched in ColorExt because we need to use ColorIndex in our property.

ColorExt Properties

As described above, the purpose of ColorExt is to provide a base color that can be used by panels inside colorExample. The BaseColor property will provide that value. We make it a property so that it can be made dependable, which means that panels that use BaseColor in their expressions will be automatically updated when the value of BaseColor changes. Once again, we set the property to be read-only in order to discourage direct changes from the outside.

The IncrementBaseColor Function

The IncrementBaseColor function provides the main interface for changing colorExample's base color. Because this function is our main intended interface from the outside, the function name is capitalized so that it will be promoted. Calling the function increments our stored ColorIndex and sets our BaseColor property to the appropriate item in baseColorList.

Another thing worth noting in this function is how the read-only values are set. Stored read-only values are accessed through the Component's StorageManager object, stored, as defined in the __init__ method. This object acts like a Python dictionary with stored items keyed by their name:

		self.stored['ColorIndex'] += 1

Properties, by default, store their values in a member with the name prefixed by an underscore. Dependable values, in addition must be accessed through their member's val attribute. Thus, we have:

		self._BaseColor.val = self.baseColorList[self.ColorIndex]

Step 3: Using ColorExt

Once this code is entered, the extension should be initialized automatically. If in doubt, you can always go to the Component's Extensions parameter page and press Re-Init Extensions. We can now use the new features we have added to colorExample.

Using BaseColor

Our original goal was to create a base color that can be used by panels inside colorExample. To see this in action, go inside colorExample and create a Container COMP. Go to the new container's Look parameter page and enter the following into its Background Color expressions: BaseColorPars.PNG

The color should immediately turn to blue, which is item 0 (the ColorIndex default) in baseColorList. Notice that we use the ext method of accessing ColorExt. This will work for every Operator inside colorExample, because ext will search upwards until it finds the named extension.

Using IncrementBaseColor

We have intentionally made it difficult to change the base color of colorExample. Instead, the user is meant to cycle through a list using our extension function, IncrementBaseColor. We will use the promotion system to run this function as if it were a method of the colorExample operator.

In the textport, type the following code: op("<path to colorExample>").IncrementBaseColor(). Replace <path to colorExample> with the appropriate path. Tip: if you drag colorExample into the textport, TouchDesigner will insert its path automatically.

As you can see, this command increments the base color, and our inner container's background is updated automatically. This is so cool that you might want to run IncrementBaseColor a few more times to see the other options.

Wrapping Up

Although this example was an extremely simple illustration of the power of extensions, we have seen two of their most powerful uses.

  1. Using the ext object, you can provide custom data and functionality to every Operator in a custom Component.
  2. Using promoted extensions, you can provide custom data and functionality to the custom Component itself.

The extension system provides effectively limitless Component customization possibilities in TouchDesigner. One thing worth noting is that in many cases Python operates more slowly than Operators. When doing time-critical operations that require extensions, it will sometimes be worthwhile to use Operators in combination with your extensions and allow the Operators to do the heavy computing while extensions manage the system organization.

Some examples of extremely customized Components that make heavy use of the extension system: Lister Custom COMP, PopMenu Custom COMP, PopDialog Custom COMP.

An Operator Family that contains its own Network inside. There are twelve 3D Object Component and eight 2D Panel Component types. See also Network Path.

Any component can be extended with its own Python classes which contain python functions and data.

TOuch Environment file, the file type used by TouchDesigner to save your project.

An Operator Family that manipulates text strings: multi-line text or tables. Multi-line text is often a command Script, but can be any multi-line text. Tables are rows and columns of cells, each containing a text string.

The component types that are used to render 3D scenes: Geometry Component contain the 3D shapes to render, plus Camera, Light, Ambient Light, Null, Bone, Handle and other component types.

Information associated with SOP geometry. Points and primitives (polygons, NURBS, etc.) can have any number of attributes - position (P) is standard, and optional are normals (N), texture coordinates (uv), color (Cd), etc.

Storage is a python dictionary associated with any operator, used to keep user-specified data within the operator.