TouchDesigner Intermediate Article 8

Building Reusable Components in TouchDesigner

Design self-contained, shareable TouchDesigner components with custom parameters, internal scripting patterns, and .tox file packaging for cross-project reuse.

⏱ 20 min components tox architecture encapsulation

As your TouchDesigner projects grow, you will build the same patterns repeatedly — an audio visualiser here, a colour mapper there. Reusable components solve this by encapsulating a network inside a Container COMP (or Base COMP) with a clean external interface of custom parameters and standardised inputs and outputs. Once built, a component drops into any project as a black box, with no knowledge of its internals required.

Why Components

A well-designed component:

  • Hides implementation complexity behind a simple parameter surface
  • Can be updated in one place and redistributed as a new .tox
  • Enables non-technical collaborators to use it without opening the network
  • Makes projects easier to reason about because each box has a defined contract

The analogues in other software are VST plugins, Unreal Blueprints, or React components: encapsulated, parameterised, composable.

Creating Custom Parameters

Select a Base COMP or Container COMP. Go to Edit > Custom Parameters (or press Ctrl+P with the component selected in the network). Click the + button to add a parameter.

Parameter types and their uses:

Type TD Type String Use
Float Float Continuous values (frequency, amplitude, size)
Integer Int Discrete counts, mode selectors
Toggle Toggle On/Off switches
Menu Menu Dropdown with named options
String Str File paths, label text
Operator Reference OP A reference to another operator
XY, XYZ, XYZW XY etc. Grouped multi-component values
RGB, RGBA RGB etc. Colour pickers

After adding a Float parameter named Speed, it appears on the component’s parameter panel as a standard slider. Users do not need to enter the component to change it.

Accessing Custom Parameters Inside the Component

Inside the component (press i to enter), me refers to the component itself. Its custom parameters are on me.par:

# Inside a script inside the component
speed = me.par.Speed.val
amplitude = me.par.Amplitude.val
colour_r = me.par.Colour.r  # for an RGB parameter
colour_g = me.par.Colour.g

Use these values to drive internal operator parameters through expressions or Python scripts:

# In a Parameter expression on an internal Noise TOP's Amplitude
me.parent().par.Amplitude

me.parent() from inside a sub-operator accesses the component, so me.parent().par.Amplitude reads the component’s custom Amplitude parameter from anywhere inside it.

Promoting Internal Parameters

Sometimes you want to expose a deeply nested parameter without writing Python. Use a Parameter CHOP inside the component to read an internal parameter value, then export that CHOP’s channel to the custom parameter — but the cleaner direction is the reverse: drive the internal parameter from the custom parameter using an expression.

For complex mapping, a Parameter Execute DAT inside the component listens for changes to the component’s own custom parameters:

# Parameter Execute DAT — fires when any custom parameter changes
def onValueChange(par, val, prev):
    if par.name == 'Sensitivity':
        # Remap to internal gain
        internal_gain = val ** 2.0  # quadratic response
        op('audio_gain').par.gain = internal_gain
    
    elif par.name == 'Mode':
        # Menu parameter returns string
        if val == 'Organic':
            op('noise_type').par.type = 'Simplex'
        elif val == 'Sharp':
            op('noise_type').par.type = 'Sparse'
    
    return

The __init__.py Pattern

A Base COMP can contain a DAT named __init__ (a Text DAT). TD automatically runs this DAT’s contents when the component is first loaded into a project. Use it to set defaults, validate the environment, or register callbacks:

# __init__ Text DAT — runs once on component load
me.par.Version = '1.3.0'

# Check dependencies
if not op('kinectazure1'):
    debug(f'{me.name}: Warning — no Kinect device found, body tracking disabled')

# Register a global handler
op('oscin1').par.callbacks = me.op('osc_callbacks')

Designing the Interface

A good component interface follows these conventions:

  • Inputs: follow TouchDesigner convention — TOP inputs on left, CHOP inputs via explicit paths or OP reference parameters
  • Outputs: one or more clearly named output TOPs/CHOPs on the right side
  • Custom parameters: group them with separator parameters (type: Header) for organisation
  • A Help parameter (type: String, read-only) containing one-sentence documentation
  • Version parameter: a read-only String showing the current .tox version

Group related parameters on named pages (add pages in the Custom Parameters editor). For example, an Audio Visualiser component might have pages: “Appearance”, “Audio”, and “About”.

Saving as .tox

When the component is complete, right-click it in the network and choose Save Component .tox. This saves the component’s entire contents — all internal operators, scripts, and parameter definitions — into a single file.

Convention: name the file after the component with the version, e.g. AudioVisualiser_v1_3.tox.

Save external: For components you actively develop across projects, use Save External instead. This keeps the .tox file hot-linked to the project: when you modify the .tox, you can reload it into any project.

Loading a .tox

Drag the .tox file from the file browser into any TouchDesigner network. It appears as a component with its parameter interface intact. No installation step — the entire component is self-contained.

To update a .tox across projects, right-click the component → Re-initialize Component (if using external), or replace the .tox file and re-drag.

Full Example: Self-Contained Audio Visualiser Component

External interface:

  • Custom parameters: Colour (RGB), Sensitivity (Float 0–2), Shape (Menu: Bar/Circle/Wave)
  • TOP Output: out1 — the rendered visualisation at 1920×1080

Internal network (abbreviated):

audio_in (Audio Device In CHOP)
  └─→ spectrum (Audio Spectrum CHOP, 64 bands)
        └─→ lag (Lag CHOP, 0.05s)
              └─→ math_gain (Math CHOP, multiply by me.parent().par.Sensitivity)
                    └─→ chop_to_top (CHOP to TOP, 64×1)
                          └─→ lookup (Lookup TOP, maps level to colour from gradient_top)
                                └─→ comp (Composite TOP, composites bars)
                                      └─→ out1 (Out TOP)

The gradient_top (Ramp TOP) is driven by the Colour custom parameter — a Python expression on its first colour stop sets:

# Ramp TOP colour[0] Red parameter expression
op('/').op(me.parent().name).par.Colour.r

Or more cleanly from inside the component:

# In a Parameter Execute DAT
def onValueChange(par, val, prev):
    if par.name in ('Colourr', 'Colourg', 'Colourb'):
        r = me.par.Colour.r
        g = me.par.Colour.g
        b = me.par.Colour.b
        op('gradient_ramp').par.color0r = r
        op('gradient_ramp').par.color0g = g
        op('gradient_ramp').par.color0b = b
    return

The Shape menu parameter switches which branch of the internal network flows to out1:

def onValueChange(par, val, prev):
    if par.name == 'Shape':
        op('out1').par.top = {
            'Bar':    'comp_bars',
            'Circle': 'comp_circle',
            'Wave':   'comp_wave',
        }.get(val, 'comp_bars')
    return

The entire component is saved as AudioVisualiser_v1_0.tox and dropped into any project. Users interact only with three parameters — the internals are invisible.