hermes-agent/skills/creative/touchdesigner-mcp/references/panel-ui.md
SHL0MS c3e3a9c184 feat(skills): add Tier A references — external-data, panel-ui, replicator, dat-scripting, 3d-scene
Five additional reference docs covering common TD use cases that were not yet
documented in any reference (operators.md lists the ops, but no usage patterns).

- external-data.md: webDAT, webclientDAT, webserverDAT, websocketDAT,
  mqttClientDAT, serialDAT, tcpipDAT — auth, polling, push, JSON parsing
- panel-ui.md: custom parameter pages, button/slider/field/list COMPs,
  containerCOMP layouts, panelExecuteDAT callbacks
- replicator.md: replicatorCOMP for data-driven cloning, per-row overrides,
  recreatemissing pattern, replicator vs Python loop
- dat-scripting.md: full Execute DAT family — chopExecuteDAT, datExecuteDAT,
  parameterExecuteDAT, panelExecuteDAT, opExecuteDAT, executeDAT lifecycle
- 3d-scene.md: light types, three-point rigs, shadows, IBL/cubemaps,
  PBR materials with idiom table, multi-camera, DOF

Same conventions as existing refs: code-first, verify param names with
td_get_par_info, no token-budget impact (load on demand).
2026-04-27 19:35:18 -07:00

9.9 KiB

Panel & UI Reference

Interactive control surfaces inside TouchDesigner — buttons, sliders, fields, custom parameter pages, panel callbacks. For HUD overlays (rendered text on visuals) see layout-compositor.md.

Use cases:

  • VJ control rack (master fader, scene buttons, FX toggles)
  • Installation operator console
  • Self-contained TOX components with their own parameter UIs
  • Phone-style touch interfaces displayed on a tablet

Two Layers of UI

Layer What it is Use for
Custom Parameters Params on any COMP, edited like built-in TD params Configurable components, presets, "settings" panels
Panel COMPs Visible widgets (button, slider, field) inside a containerCOMP Interactive control surfaces, real-time UIs

Combine both: build a containerCOMP with panel widgets that read/write custom parameters on a parent component.


Custom Parameters

Add user-editable params to any COMP. Params persist with the COMP, drive expressions, and survive save/reload.

# Add a custom page to a baseCOMP
comp = op('/project1/my_component')
page = comp.appendCustomPage('Controls')

# Add typed params
page.appendFloat('Intensity', label='Intensity')[0]   # returns a Par
page.appendInt('Count', label='Count')[0]
page.appendToggle('Enabled', label='Enabled')[0]
page.appendMenu('Mode', menuNames=['off', 'soft', 'hard'], menuLabels=['Off', 'Soft', 'Hard'])[0]
page.appendStr('Title', label='Title')[0]
page.appendRGB('Color', label='Color')                # returns 3 pars
page.appendXY('Offset', label='Offset')               # returns 2 pars
page.appendPulse('Reset', label='Reset')[0]
page.appendFile('TextureFile', label='Texture')[0]

Read/write from anywhere:

val = op('/project1/my_component').par.Intensity.eval()
op('/project1/my_component').par.Intensity = 0.7

Drive other params via expression:

op('bloom1').par.threshold.mode = ParMode.EXPRESSION
op('bloom1').par.threshold.expr = "op('/project1/my_component').par.Intensity"

Pulse handler (Reset button):

Use a parameterExecuteDAT watching the COMP's pulse params. See dat-scripting.md.


Panel COMPs — The Widgets

Each is a COMP that renders as a clickable/draggable widget inside a containerCOMP.

Type Type Name Use
Button buttonCOMP Click action — momentary or toggle
Slider sliderCOMP Drag to set 0-1 value (1D or 2D)
Field fieldCOMP Text input
Container containerCOMP Layout + visual styling, holds children
Select selectCOMP Reference and display content from another COMP
List listCOMP Scrollable list with row callbacks

Button

btn = root.create(buttonCOMP, 'play_btn')
btn.par.w = 120; btn.par.h = 40
btn.par.buttontype = 'momentary'    # 'momentary' | 'toggleup' | 'togglepress' | 'radio'
btn.par.bgcolorr = 0.1; btn.par.bgcolorg = 0.1; btn.par.bgcolorb = 0.1
btn.par.text = 'Play'

# Read state
state = btn.panel.state          # 1 when active

Slider

sld = root.create(sliderCOMP, 'master_fader')
sld.par.w = 60; sld.par.h = 300
sld.par.style = 'vertical'        # 'vertical' | 'horizontal' | 'xy'
sld.par.value0min = 0.0
sld.par.value0max = 1.0

# Drive a parameter via expression (always-on, no callback needed)
op('/project1/master_level').par.opacity.mode = ParMode.EXPRESSION
op('/project1/master_level').par.opacity.expr = "op('master_fader').panel.u"

panel.u and panel.v give the 0-1 normalized values. For 2D sliders both are populated.

Field (Text Input)

fld = root.create(fieldCOMP, 'scene_name')
fld.par.w = 200; fld.par.h = 30
fld.par.fieldtype = 'string'      # 'string' | 'integer' | 'float'

# Read current text
text = fld.panel.field            # the text content

List

For scrollable lists with selectable rows, use the docked list1_callbacks DAT to handle row interactions. Set up cells via the list_definition table DAT.


Container COMP — Layout & Styling

containerCOMP is the primary parent for grouping widgets and arranging layouts.

panel = root.create(containerCOMP, 'control_panel')
panel.par.w = 400; panel.par.h = 600
panel.par.bgcolorr = 0.05
panel.par.bgcolorg = 0.05
panel.par.bgcolorb = 0.05
panel.par.bgalpha = 1.0

# Layout child panels in vertical stack
panel.par.align = 'lefttoright'   # 'lefttoright' | 'toptobottom' | etc.

Children are positioned automatically based on par.align. For absolute positioning use par.align = 'fillresize' and set each child's par.x / par.y.

Layout Strategies

par.align Behavior
lefttoright Children stacked horizontally
toptobottom Children stacked vertically
righttoleft / bottomtotop Reversed stacks
fillresize Children sized to fill, manual positioning
top / bottom / left / right Fixed positioning

For complex grids: nest containers — vertical container holding horizontal containers.


Panel Callbacks — Reacting to Events

panelExecuteDAT watches a panel and fires Python callbacks on user interaction.

pe = root.create(panelExecuteDAT, 'btn_handler')
pe.par.panel = '/project1/play_btn'
pe.par.click = True              # respond to clicks
pe.par.value = True              # respond to value changes

In its docked DAT:

def onOffToOn(panelValue):
    # Click pressed
    op('/project1/scene_timer').par.start.pulse()
    return

def onOnToOff(panelValue):
    # Click released
    return

def onValueChange(panelValue):
    # Slider drag, field change, etc.
    new_val = panelValue.eval()
    op('/project1/master').par.opacity = new_val
    return

For pulse params on custom-parameter pages, use a parameterExecuteDAT instead.


Building a Complete VJ Control Panel

End-to-end pattern:

# 1. Top-level container
panel = root.create(containerCOMP, 'vj_control')
panel.par.w = 800; panel.par.h = 200
panel.par.align = 'lefttoright'

# 2. Master fader column
master_col = panel.create(containerCOMP, 'master')
master_col.par.w = 120; master_col.par.h = 200
master_col.par.align = 'toptobottom'

master_label = master_col.create(textTOP, 'lbl')
master_label.par.text = 'MASTER'

master_sld = master_col.create(sliderCOMP, 'fader')
master_sld.par.w = 60; master_sld.par.h = 150
master_sld.par.style = 'vertical'

# 3. Scene buttons row
scene_col = panel.create(containerCOMP, 'scenes')
scene_col.par.w = 400; scene_col.par.h = 200
scene_col.par.align = 'lefttoright'
for i in range(8):
    b = scene_col.create(buttonCOMP, f'scene_{i+1}')
    b.par.w = 50; b.par.h = 50
    b.par.text = str(i+1)
    b.par.buttontype = 'radio'      # only one active at a time

# 4. FX toggle column
fx_col = panel.create(containerCOMP, 'fx')
fx_col.par.w = 280; fx_col.par.h = 200
fx_col.par.align = 'toptobottom'
for fx in ['Bloom', 'CRT', 'Glitch', 'Strobe']:
    t = fx_col.create(buttonCOMP, fx.lower())
    t.par.w = 220; t.par.h = 35
    t.par.text = fx
    t.par.buttontype = 'toggleup'

# 5. Display in a window
win = root.create(windowCOMP, 'control_win')
win.par.winop = panel.path
win.par.winw = 800; win.par.winh = 200
win.par.borders = True
win.par.winopen.pulse()

Then wire panel values to ops via expressions or panelExecuteDATs.


Showing the Panel — Window or Embedded

Approach When
windowCOMP pointing at panel Standalone control surface, separate display
Render the containerCOMP via renderTOP Composite UI over visuals (HUD-style)
Use a panelCOMP directly inside a network editor pane Designer/dev preview only — panel is fully interactive

For a touch-screen tablet, use a windowCOMP on a second display routed to the tablet's HDMI input.


Pitfalls

  1. Panel won't respond to clicks — likely par.disabled = True or the parent container has par.disableinputs = True. Check the panel hierarchy.
  2. Slider value not updatingpanel.u/v reads the visual position. If you set par.value0 directly, the visual lags. Use par.value0 AS the source of truth and let the slider follow.
  3. Custom param won't appear — must call appendCustomPage first, then append params. Pages with no params don't show.
  4. Custom param disappears on reload — params added via Python at runtime persist only if the COMP is saved AFTER. Use a tox save (comp.save('mycomp.tox')) or commit via td_execute_python then save the project.
  5. Event callback fires twice — both onOffToOn and onValueChange may fire on a single button press. Pick one to handle the action; don't double-trigger.
  6. Pulse params need .pulse() — setting par.X = True on a pulse param does nothing. Always use .pulse().
  7. Field text doesn't commit until Tab/Enter — fields don't fire callbacks while typing. Use par.committemode = 'all' to fire on every keystroke (heavy).
  8. par.text vs panel contentbuttonCOMP.par.text is the LABEL on the button. The button's STATE is panel.state (0/1). Don't confuse them.
  9. Touch input on macOS — multi-touch via direct touch panels works but TD's gesture handling is rudimentary. For complex multi-touch (pinch/rotate), use TouchOSC on a tablet instead.
  10. Layout doesn't update — changing par.align requires the container to re-cook. Touch a child or pulse the container to trigger.

Quick Recipes

Goal Setup
Master fader sliderCOMP (vertical) → expression on level.par.opacity
Scene picker 8 buttonCOMP (radio) → selectCHOP on their state → drive switchTOP.par.index
FX toggle buttonCOMP (toggleup) → expression on bypass of an FX op
Numeric input fieldCOMP (float) → expression on target par
Component settings Custom params on the component COMP, panel widgets inside drive them
Touch tablet UI containerCOMP with widgets → windowCOMP to second display
Status display textTOP rendered into the panel via selectCOMP