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

Extensions: классы у компонента

Класс Python на компоненте: op('scene').Next() вместо цепочки скриптов. Как подключить, почему имена с большой буквы и что делать с инициализацией.

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

Extension — класс Python, привязанный к компоненту, и набор узлов превращается в объект с методами: op('scene').Next() вместо цепочки скриптов.

Быстрее всего подключать через RMB → Customize Component, раздел Extension Code: вписали имя, нажали Add — и TouchDesigner сам создаёт Text DAT с заготовкой класса и прописывает его. Называют расширение с большой буквы и суффиксом Ext, например SceneExt. Вручную то же делается на странице Extensions: Text DAT SceneExt внутри компонента, в Object строка op('./SceneExt').module.SceneExt(me) и включённый Promote, чтобы методы были видны прямо на компоненте.

Пересоздаётся расширение само, когда меняется код; руками — кнопкой Re-Init Extensions, а Init Extensions On Start делает это при запуске.

class SceneExt:
    def __init__(self, ownerComp):
        self.ownerComp = ownerComp
        self.index = 0
        self.Count = ownerComp.par.Count.eval()

    def Next(self):
        self.index = (self.index + 1) % self.Count
        self.Apply()

    def Goto(self, i):
        self.index = max(0, min(int(i), self.Count - 1))
        self.Apply()

    def Apply(self):
        self.ownerComp.op('switch1').par.index = self.index
op('scene').Next()
op('scene').Goto(3)

Большая буква

Публичное пишут с большой буквы, внутреннее с маленькой, и это не вкус: Promote выносит наружу только имена с заглавной. Без Promote до расширения добираются через .ext — op('scene').ext.SceneExt.Next(). Заодно видно, где интерфейс компонента, а где внутренняя кухня.

    @property
    def Active(self):
        return bool(self.ownerComp.par.Active.eval())

    @Active.setter
    def Active(self, v):
        self.ownerComp.par.Active = 1 if v else 0

Так компонент становится чёрным ящиком: Next() вызывает кто угодно, не заходя внутрь. Логика собрана в одном месте, а не в пяти колбэках, код в Text DAT с внешним .py хранится в git (Файл проекта), и компонент целиком переносится в другой проект.

Когда он создаётся

__init__ вызывается при каждом создании расширения — загрузка файла, вырезка и вставка, Re-Init Extensions, правка кода, — и всё в self при этом пропадает. То, что должно пережить это и сохранение, держат в хранилище через StorageManager — Состояние и хранилище.

Тяжёлого в __init__ не делают и на загруженную сеть не рассчитывают. Для этого есть встроенные методы:

    def onInitTD(self):
        # вызывается в конце кадра, в котором расширение инициализировано
        self.Setup()

    def onDestroyTD(self):
        # вызывается перед тем, как расширение будет уничтожено
        pass

Если параметр обращается к расширению раньше времени, вылезает «Cannot use an extension during its initialization», и выражение защищают проверкой parent().extensionsReady.

Ошибка в классе — тихая

Не скомпилировался класс — у компонента просто нет методов, а вызовы падают с невнятным сообщением. После правки сразу смотрите Textport.

Одно расширение на компонент

«Главный класс проекта» быстро становится файлом, который не разобрать. Пять компонентов с пятью маленькими расширениями проще поддерживать.

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

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