Introduction to Python Tutorial

From TouchDesigner 099 Wiki

This tutorial is a general introduction to using Python in TouchDesigner. For an online tutorial for Python in general see: [1]

Inputting Python

Python in the Textport

The simplest method to input Python scripts is through the textport. The textport, like all scripting in TouchDesigner, allows scripts to be specified in either Python or Tscript.

After opening the textport, make sure it is set to Python language. This is controlled by the small toggle button in the upper left side of the textport.

When it is set to Py, all input is interpreted as Python. When set to T it is interpreted as tscript.

In addition the textport prompt and text color have different values for each language, making it easier to identify which state the textport is in:

TextportPython.png TextportTscript.png


As a simple test type the following into the textport:

help(op)

This will output all help related to the op() method found in the td Module.

The td Module is the main module containing all TouchDesigner related classes and objects. It is imported by default when the application begins.
Another useful module is the tdu Module. This module contains some specific TouchDesigner utility functions useful during scripting.

Python in DAT Scripts

Though multiple line scripts can be entered into the textport, they are generally stored in a DAT, such as a text DAT.

Begin by placing down a new Text DAT. Make sure the operator's parameter language mode is set to Python. This is done by clicking the Operator Language button, located in the upper right corner of the operator dialog.

Python Mode Tscript Mode


This mode controls the language of both the DAT contents, and its parameter expressions. In cases where the two use different languages are used, modify the Language parameter on the common page accordingly.

Click the Viewer Active Flag to edit its contents.


Enter the following two line script:

for i in range(1,5):
    print(i)


Note the first line should not include any leading spaces in this example, while the second line is indented. It should either be indented with a single tab or 4 spaces.

A single script can not mix indentation methods.

To run the script, right-click on the node and select Run Script from the popup menu.

RunScript.JPG


The output will be echoed to the textport. Open the textport to see the digits 1 through 4 printed.

Importing Modules

Part of the great power of Python is its access to the countless number of modules that have been created. The default installation includes all standard modules.
For example, open the textport and type:

import math
print(math.sin(4))

This will import the default math module from the installation folder, making its methods available.

Modules not included by default may also be imported. Examples of other useful Python modules are here. Unofficial precompiled modules found on Christoph Gohlke's website.

This is done most easily through the following steps:

  • Install a parallel copy of the same version of Python to the hard disk.

The current version of Python shipped with TouchDesigner is 3.5. It can also be found here.. Python generally installs to location: C:/Python35

  • Ensure the module you wish to use, is compatible with Python 3.5. Also ensure it is compatible with the specific version of TouchDesigner you are running (32 or 64 bit).
  • Install the module to the parallel python installation (eg C:/Python35).
  • Launch C:/Python35/python.exe and import the module manually to make sure there are no errors.

Once the module is successfully installed, it will automatically be visible by TouchDesigner by default. This option is found under the Edit->Preferences menu as "Add External Python to Search Path". Alternatively you can add the search path by modifying the Preference labelled "Python 32/64 bit Module Path". Multiple paths are separated by semicolons (;). Finally you can modify the search path directly by either modifying the system environment variable PYTHONPATH or be executing a script which appends the path to sys.path as in the example below.

import sys
mypath = "C:/Python35/Lib/site-packages/mymodule"
if mypath not in sys.path:
    sys.path.append(mypath)

Overriding built in modules

TouchDesigner may come with specific modules pre-installed. If you require a different version, find the TouchDesigner folder where the original is included and remove it from the search path before importing.

Example:

import sys
sys.path.remove('C:\\Program Files\\Derivative\\TouchDesigner099\\bin\\lib\\site-packages')

Creating Internal Modules from DATs

Python allows scripts to be organized as modules. This is done through the standard import statement. In TouchDesigner the module can reside on disk like regular Python or they can also reside inside other DATs.

Place down a text DAT and rename it to my_utils. Ensure its parameter language is set to Python, activate its viewer, and enter the following function:

def my_adder(x,y):
    return 10*x+y

In the same component, add another text DAT. Rename it to test_import and ensure its parameter language is set to Python as well. In this DAT enter the following:

import my_utils
a = my_utils.my_adder(1,3)
print(a)

After running this script, the value of 13 will be printed in the textport. The my_utils DAT has been treated as a module, through the import statement above.

Component Modules

Just as TouchDesigner allows for Component Variables, and Component Time, so too can one create Component Modules. These are simply DATs placed in a specific child of the component, namely its local/modules component.

Module Search Order

When attempting to import a module, TouchDesigner first searches in the current component for a DAT with that module name.
If it is not found, it then searches its component modules.
If still not found, it then searches the component modules of each parent until it reaches the root component.
If still not found, it then searches the component modules of /sys component.
If still not found, it then searches the regular Python disk search.

In this manner, one can place component modules at a specific parent location and know they will be found by all children and sub-children of that component. For example, any DATs placed in /local/modules will be accessible to all scripts and expressions wherever they are located.


Note: When you attempt to import an external module, it will first look for DATs of that name in the same folder , so be sure to avoid name conflicts keeping the above search order in mind.

Module On Demand

The import statement, though useful contains two disadvantages:

  • Module names must be single words, so relative DAT paths cannot be used.
  • They are unsuitable for use in a parameter expression.

To avoid this, use the Module On Demand or mod object.

In the DAT test_import, replace the contents with the following code:

a = mod.my_utils.my_adder(1,3)
print(a)

Notice how no import statement is needed, making this evaluation suitable for parameters. The same search rules apply when using the module on demand object.

Finally, string paths, including relative paths may be used. Simply pass in the string as a parameter to the mod object:

a = mod('my_utils').my_adder(1,3)
print(a)

Both absolute and relative paths may be used:

a = mod('/project1/my_utils').my_adder(1,3)
print(a)

All versions will result in the same output.

Working with Operators

Basic access

The main class type describing any Operator is the base OP Class. You will need a reference to one of these to do anything. There are two global operator objects are always available (except for in the Textport):

  • me refers to the operator that is currently being evaluated or executed. For example, when executing a script, me refers to the containing DAT. When evaluating an expression, me refers to the containing operator.
  • root refers to the top level component /.

To get references to other OPs (for example, a node named 'wave1' sitting next to the node 'constant1') the most common functions to use are: op() and ops(), for example op('wave1').

op() returns a single OP object, while ops returns a (possibly empty) list of OPs. They are described in td Module.

These functions search for operators from the current component, so both relative and absolute paths are supported. The current component is defined as: The OP that me is inside.

Note that the OP Class itself, also contains an op() and ops() method. In this case, nodes are searched from the OP.

For example: me.op('..') will always return its own parent, while op('..') will return the parent of the current component.

If you are typing a python expression in a parameter of a node 'constant1', and you wish to get a reference to 'wave1', you would type

 op('wave1')

If you are in a script you can assign this reference to a variable for easier repeated access.

 n = op('wave1')

In this case op() will search relative to the DAT that is executing the script.

An OP also has a parent() method that can be use to get the parent COMP of it.

 me.parent()

If you are putting a Python statement in a parameter of a COMP and want to refer to a child of that COMP, you can use the op() method for the OP, which is available as me in the parameters.

 me.op('achildnode')

TIP: To find out quickly what members and methods you have access to for any node, select that node and on its parameter dialog, click the Python Help icon. You will go the wiki for the python classes for that node. There you can find out what info you can get about a node, and what methods are available for it. The documentation can also be arrived at by right clicking on the node and selecting "Python Help..." from the menu.


Examples

Place down a new Text DAT, ensure its parameter flag is set to Python, and enter the following script:

n = op('/project1')
m = n.ops('text*')
for a in m:
    print(a.name)

After running the script, n is assigned the results of the global function, while m is assigned results relative to n.

Some useful members and methods of an OP object, are:

  • name
  • path
  • children
  • parent()

These are described in OP Class. Notice the last attribute, parent() is a function. It takes an optional argument specifying how far up the parent chain to climb. To see how they are used in practice, put down a new Text DAT, ensure its Parameter Language parameter is set to Python and enter the following code:

print('i am ', me)
print('child of ', me.parent())
print('grandchild of ', me.parent(2))

print('root children:')
k = root.children
for r in k:
    print(r)

The resulting details will be found in the textport.


Python - TScript Equivalents

Commands

Python TScript
Creating an OP (Sphere SOP) op('/project1').create(sphereSOP) cc /project1
opadd SOP:sphere
Creating a named OP op('/project1').create(sphereSOP, 'mysphere') cc /project1
opadd SOP:sphere "mysphere"
Copying OPs (Nodes) op('/project1').copy(op('out1'), 'out2') opcp /project1/out1 out2
Deleting an OP op('mysphere').destroy() cc /project1
oprm "mysphere"
Renaming an OP op('mysphere').name = 'thesphere' cc /project1
opname "mysphere" "thesphere"
Changing an OP's type op('mysphere').changeType(boxSOP) cc /project1
opchangetype "mysphere" SOP:box
Changing multiple OPs' types list = ops('*sphere*')
[s.changeType(boxSOP) for s in list]
cc /project1
opchangetype "*sphere*" SOP:box
Setting an OP's comment op('mysphere').comment = 'this is a sphere' cc /project1
opcomment -c "this is a sphere" "mysphere"
Changing an OP's parameter op('mysphere').par.frequency = 10 cc /project1
opparm "mysphere" frequency ( 10 )
Changing an OP's parameter
with more than 1 value
s = op('mysphere')
s.par.tx = 1
s.par.ty = 2
s.par.tz = 3
cc /project1
opparm "mysphere" t( 1 2 3 )
Pulsing a parameter value op('moviein1').par.cue.pulse() opparm -p moviein1 cue (1)
Cooking an OP op('mysphere').cook() cc /project1
opcook "mysphere"
Saving an OP's data to a file op('mysphere').save('sphere.tog') cc /project1
opsave "mysphere" "sphere.tog"
Changing an OP's Render and Display Flags on s = op('mysphere')
s.render = True
s.display = True
cc /project1
opset -r 1 -d 1 "mysphere"
Loading a .tox file into a COMP op('/project1').loadTox('geo1.tox') cc /project1
loadcomponent "geo1.tox"
Wiring operators together Refer to the Connector Class opwire command
Clicking gadgets (panel components) op('slider1').click(.6, .7) click slider1 .6 .7
Timeline Play/Pause me.time.play = True/False playback/playback -s

Expressions

Python TScript
Querying another OP's parameter op('sphere1').par.tx par("sphere1:tx")
Querying a parameter in the same OP me.par.tx par("tx")
Getting Info CHOP channels from an OP
without cooking it
passive(op('moviein1')).width opinfop("moviein1", "resx")
Getting an OP's parent parent() opparent(".", 0)
Getting an OP's grand-parent parent(2) opparent(".", 1)
Getting an OP's name me.name $ON
Getting an OP's parent's name parent().name $OPN
Getting digits of an OP's name in its parameters me.digits $OD
Getting digits of an OP's parent's
name in its parameters
parent().digits $OPD
Getting digits of another OP's name op("moviein1").digits opdigits("moviein1")
Getting an OP's type # returns an op object, not a string
type(op('moviein1'))
optype("moviein1")
getting a unique random number each frame tdu.rand(absTime.frame+.1) rand($AF+.1)
getting a unique random number per numbered operator tdu.rand(me.digits+.17) rand($OD+.1)
Checking for an OP's existence if op('moviein1'): opexists('moviein1')
Getting the number of children of a COMP len(op('geo1').children) opnumchildren("geo1")
Getting the number of inputs of a multi-input OP len(op('switch1').inputs)
Getting Info CHOP channels from an OP, width is a member op('moviein1').width opinfo("moviein1", "resx")
Conditional "if" in one line of a parameter 22 if me.time.frame<100 else 33 if($F<100,22,33)
Conditional "if" alternative [33,22][me.time.frame<100] if($F<100,22,33)

Time

"Absolute Time" is the time since you started your TouchDesigner process, not counting when your power button was off (top bar).

Python TScript
Retrieving a node's local frame number me.time.frame $F
Retrieving a node's local time in seconds me.time.seconds $T
Retrieving absolute time in frames absTime.frame $AF
Retrieving absolute time in seconds absTime.seconds $AT

Storage in Python

Storage is the preferred way to work with persistent global data in Python, since it can store anything data type.

Python
Setting a value in storage of a component n n.store('keyname', 0.0)
Getting a value from storage n.fetch('keyname')

Variables

Variables are always text strings.

Python TScript
Setting a value me.var('DESKTOP') $DESKTOP
Setting a Root Variable root.setVar('MEDIA', 'c:/MEDIA') rvar MEDIA="c:/MEDIA"
Setting a Component Variable
at the current component
parent().setVar('MEDIA', 'c:/MEDIA') cvar MEDIA="c:/MEDIA"
Setting a Component Variable
at another component
op('/project1/geo1').setVar('MEDIA', 'c:/MEDIA') cvar -p "/project1/geo1" MEDIA="c:/MEDIA"
Setting a Path Variable Set the Path Variable parameter of any parent component and use me.var('name') in the same way. Set the Path Variable parameter of any parent component.

Print versus Debug

The above code makes uses of the built in print function. The debug statement can be used to obtain more specific information about a python object, and will also append the line number and script in which debug was called.

Simply replace all occurrences in the above example to obtain more detailed information:

debug('i am ', me)
debug('child of ', me.parent())

debug('root children:')
k = root.children
for r in k:
    debug(r)

The debug function can be used anywhere the print function is used.
Alternatively, one could always print identifying attributes specific to the object: me.path etc.

Python in Parameter Expressions

Begin by placing down an Geometry Component. Make sure the Geometry Object is called geo1 and its Language Parameter is set to Python (as described earlier).

Toggling this flag will automatically convert all existing parameter values to the specified language where it can.

In its Translate X parameter (first) field enter:

mod.myUtils.myAdder(1,3)

The resulting expression now evaluates to 13.

Similarly, a parameter can refer to another parameter value from any location, including the same operator. This is done through the Par Class. Its main access is eval(), val and expr.
In the above field, change the expression to:

me.par.ty.eval()

Now the Translate X value will contain whatever is in the Translate Y (second) field. Modify the Y value to confirm.
As Parameter objects are automatically converted to numbers or strings when needed, the above can be shortened to:

me.par.ty

Quotes and String Constants

All python parameter fields are always treated as expressions, so when entering a string constant, it must be contained within single or double quotes, otherwise it will be interpreted as a specific python object, resulting in an error.

For example, in the material parameter of geo1. Setting it to:

phong1

will result in an error, as phong1 cannot be found.
It must be therefore be set to:

'phong1'

to denote a literal string constant.
The interface to the string parameter fields will be changed in the future to avoid this requirement.

Scripting Parameter Values

In addition to manually entering parameter values and expressions, they may also be scripted. Place down a new Text DAT. Ensure its parameter language is set to Python.

In the Text DAT enter the following:

n = op('geo1')
v = n.par.rx.eval()
print(v)
n.par.rx.val = 30

When run, the current value of rx parameter of geo1 will be printed, then changed to 30.
In this case, the eval() call explicitly returns the parameter value, but it can automatically be evaluated as needed in many cases:

#examples of automatic casting
v = float(n.par.rx)
v = n.par.rx + 5          
v = n.par.rx * n.par.ry

Similarly, the same method can be used to directly set the value of a parameter without using the set method:

#equivalent
n.par.rx.val = 5
n.par.rx = 5


This class may also be used to set the parameters expression. In the above example change the last line to:

n.par.rx.expr = 'me.time.frame'

Run this code, and notice the rx parameter is now set to the current frame, by referring to the operator's Time Component

Things to Note Regarding Python and Tscript

Operators can be in Tscript mode or python mode by setting their Operator Language flag on the parameter. Refer to Introduction to Python. Some old files opening in 099 may have their nodes in tscript mode to keep things compatible, but any new nodes created in 099 are in python mode by default. Default Node Language can be changed in preferences.