К содержанию
fohbookбета/ TouchDesigner

Python в TouchDesigner

Python 3.11 и модуль td: где писать код, как достать узел, параметр и канал, как создать узел, отложить действие и сохранить состояние. И почему Python на каждом кадре роняет кадры.

Ветка вашего пульта:
Быстрый выбор:

Внутри TouchDesigner свой Python — в ветке 2025.30000 это 3.11, в сборке 2025.33230 точнее 3.11.15 — и модуль td со всей сетью. Импортировать его не нужно: op, me, parent, absTime, project доступны в любом скрипте и выражении.

Место Для чего
Textport (Alt+T) Проверить строчку, посмотреть ошибку
Text DAT Модуль с функциями, вызываемыми откуда угодно — mod.имя_dat.функция()
Script DAT / CHOP / SOP / TOP / POP Оператор, содержимое которого считает Python
Колбэки (Execute DAT и другие) Реакция на события — Скрипты и колбэки
Параметр в режиме Expression Однострочные выражения — Ссылки
Extensions Классы, привязанные к компоненту — Extensions

Текст из Text DAT целиком запускают через RMB → Run Script.

op('noise1')                       # оператор или None, если его нет
opex('noise1')                     # то же, но с ошибкой, если его нет
op('noise1').par.period = 4        # параметр
op('noise1').par.period.expr = "absTime.seconds"   # выражение
op('constant1')['chan1']           # канал CHOP
op('table1')[0, 'name']            # ячейка DAT
op('geo1').render = False          # флаг

ops('render*')                     # список по шаблону
parent()                           # родительский компонент
me                                 # сам оператор
project.folder                     # папка проекта
absTime.seconds                    # время с запуска

opex() из ветки 2023.10000 сразу сообщает, что узла нет, вместо того чтобы вернуть None и уронить код строчкой ниже.

n = parent().create(noiseTOP, 'noise_new')
n.nodeX, n.nodeY = 200, 0
n.par.period = 3
op('noise_old').destroy()

Позже и надолго

run("op('a').par.Reset.pulse()", delayFrames=1)
run("op('player').par.play = 1", delayMilliSeconds=500)

run с задержкой выполняет код после текущего кука, через нужное число кадров или миллисекунд, и это исправляет много странных ошибок вроде правки сети посреди кука — Отложенный запуск и потоки.

Состояние держат не в глобальных переменных модуля, а в хранилище оператора:

me.store('counter', 0)        # записать
me.fetch('counter', 0)        # прочитать, с значением по умолчанию

Хранилище сохраняется в .toe и .tox и возвращается при открытии; если на старте нужно начальное значение, его заводят через storeStartupValue(). Подробно — Состояние и хранилище.

Python на каждом кадре

onFrameStart в Execute DAT, где перебирается сеть, — прямой путь к падению кадров. Python работает в главном потоке, и пока он считает, не считается ничего.

Значения передают CHOP и экспортом, на изменения отвечают колбэки, а не опрос, тяжёлое уносят в отдельный поток через Thread Manager или в процесс (Engine COMP), а сеть обходят один раз при старте и кладут результат в таблицу. Derivative советует то же: многие куски Python заменяет маленькая сеть CHOP или DAT, которая на порядок быстрее.

Отладка

print() пишет в Textport, debug() — туда же, но с именем оператора и номером строки. Ошибки видны в Textport и в Error DAT, который удобно вывести на служебную панель площадки, а op('...').errors() и .warnings() дают их программно — на этом держится мониторинг. Время кусков кода меряют time.perf_counter().

Нашли в статье ошибку, устаревшее или непонятное место?

рядом по смыслу