Skip to content

04. QuickExt

Function Store edited this page Oct 21, 2024 · 8 revisions

When adding an extension using CustomParTools Shift+Alt+LeftClick, the created default Extension will have the following available:

  • Simplified default extension code
  • Includes ExtUtils which contains CustomParHelper and NoNode, which are also initialized in the default extension code

You can also add QuickExt to your Base COMP via the Component Editor->Extension section by long-clicking on Add and selecting QuickExt

It is important that ExtUtils stays docked to your extension code!

By leveraging [NoNode] and [CustomParHelper], TouchDesigner developers can create more efficient, organized, and maintainable extensions, ultimately leading to smoother workflow and improved project scalability.

Importing

The default extension code will contain the following - at first sight complicated looking - import statements for the utility packages. You can just ignore them, but don't remove them!

This might look complicated at first but it's just a fancy import statement to avoid any conflicts when having multiple extension classes with ExtUtils attached.

  CustomParHelper: CustomParHelper = next(d for d in me.docked if 'ExtUtils' in d.tags).mod('CustomParHelper').CustomParHelper # import
  NoNode: NoNode = next(d for d in me.docked if 'ExtUtils' in d.tags).mod('NoNode').NoNode # import

CustomParHelper

CustomParHelper is a helper class that provides easy access to custom parameters of a COMP and simplifies the implementation of custom parameter callbacks in TouchDesigner extensions.

Long story short, if you have a float custom parameter named Mynumber you can define the functions def onParMynumber(self, val):, or def onParMynumber(self, par, val): or def onParMynumber(self, par, val, prev):, and the properties self.parMynumber and self.evalMynumber will be created by default. The usage is highly customizable though, so keep reading!

Features:

  • Access custom parameters as properties
  • Simplified custom parameter callbacks
  • Support for sequence parameters
  • Support for parameter groups (parGroups)
  • Configurable inclusion for properties and callbacks (by default all parameters are included)
  • Configurable exceptions for pages, properties, callbacks, and sequences

Usage in your extension class:

  1. Import the CustomParHelper class:

    CustomParHelper: CustomParHelper = next(d for d in me.docked if 'ExtUtils' in d.tags).mod('CustomParHelper').CustomParHelper # import
  2. Initialize in your extension's init method as follows:

    CustomParHelper.Init(self, ownerComp)

    Full signature and optional parameters:

    CustomParHelper.Init(self, ownerComp, enable_properties: bool = True, enable_callbacks: bool = True, enable_parGroups: bool = True, expose_public: bool = False,
            par_properties: list[str] = ['*'], par_callbacks: list[str] = ['*'], 
            except_properties: list[str] = [], except_sequences: list[str] = [], except_callbacks: list[str] = [], except_pages: list[str] = [], enable_stubs: bool = False)

    Additional options:

    • enable_parGroups: If True, creates properties and methods for parGroups (default: True)
    • expose_public: If True, uses capitalized property and method names (e.g., Par, Eval instead of par, eval)
    • par_properties: List of parameter names to include in property creation, by default all parameters are included
    • par_callbacks: List of parameter names to include in callback handling, by default all parameters are included
    • except_properties: List of parameter names to exclude from property creation
    • except_callbacks: List of parameter names to exclude from callback handling
    • except_pages: List of parameter pages to exclude from property and callback handling
    • except_sequences: List of sequence names to exclude from property and callback handling
    • enable_stubs: If True, automatically creates and updates stubs for the extension (default: False) (thanks to AlphaMoonbase.berlin for Stubser)
  3. Access custom parameters as properties (if enable_properties=True (default)):

    • self.par<ParamName>: Access the parameter object
    • self.eval<ParamName>: Get the evaluated value of the parameter
    • self.parGroup<GroupName>: Access the parameter group object (if enable_parGroups=True (default))
    • self.evalGroup<GroupName>: Get the evaluated value of the parameter group (if enable_parGroups=True (default))

NOTE: to expose public properties, eg. self.Par instead of self.par, set expose_public=True in the Init function

  1. Implement callbacks (if enable_callbacks=True (default)):
    • For regular parameters:

      def onPar<ParamName>(self, _par, _val, _prev):
      # _par and _prev can be omitted if not needed
    • For pulse parameters:

      def onPar<PulseParamName>(self, _par):
      # _par can be omitted if not needed
    • For sequence blocks:

      def onSeq<SeqName>N(self, idx):
    • For sequence parameters:

      def onSeq<SeqName>N<ParName>(self, _par, idx, _val, _prev):
      # _par and _prev can be omitted if not needed
    • For parameter groups if enable_parGroups=True (default):

      def onParGroup<GroupName>(self, _parGroup, _val):
      # _parGroup can be omitted if not needed

NOTE: The reason this class implemented with static methods, is to omit the need to instantiate the class, providing a simpler interface (arguably).

NoNode

NoNode is a versatile utility class that centralizes the management of various types of executions and callbacks in TouchDesigner, eliminating the need for dedicated nodes.

Key Features:

  • Keyboard shortcut handling
  • CHOP executions (value changes, on/off states)
  • DAT executions (table, row, or cell changes)
  • Centralized event management
  • Reduced node clutter
  • Visual indication of watched operators

Usage examples:

  1. Make sure ExtUtils is docked to your extension

  2. Import the NoNode class:

    NoNode: NoNode = next(d for d in me.docked if 'ExtUtils' in d.tags).mod('NoNode').NoNode # import
  3. Initialize the NoNode system in your extension:

    NoNode.Init(enable_chopexec=True, enable_datexec=True, enable_keyboard_shortcuts=True)
  4. CHOP executions:

    • Register a callback for CHOP value changes:
      NoNode.RegisterChopExec(NoNode.ChopExecType.ValueChange, chop_op, channel_name(s), callback_function)
      # callback signature: def on_value_change_function(channel: Channel, sampleIndex: int, val: float, prev: float):
      # can omit parameters from the right side of the signature if not needed
    • Handle CHOP state changes:
      NoNode.RegisterChopExec(NoNode.ChopExecType.OffToOn, chop_op, channel_name(s), on_activate_function)
      # callback signature: def on_activate_function(channel: Channel, sampleIndex: int, val: float, prev: float):
      # can omit parameters from the right side of the signature if not needed
  5. DAT executions:

    • React to table changes in a DAT:
      NoNode.RegisterDatExec(NoNode.DatExecType.TableChange, dat_op, on_table_change_function)
      # callback signature depends on the event type, eg.: def on_table_change_function(dat: DAT):
    • Handle cell value changes:
      NoNode.RegisterDatExec(NoNode.DatExecType.CellChange, dat_op, on_cell_change_function)
      # callback signature depends on the event type, eg.: def on_cell_change_function(dat: DAT, cells: list[Cell], prev: Cell):
  6. Keyboard shortcuts:

    • Register a keyboard shortcut:
      NoNode.RegisterKeyboardShortcut('ctrl.k', onKeyboardShortcut)
      # callback signature: def onKeyboardShortcut():
  7. Deregister callbacks:

    • Deregister a CHOP execution:
      NoNode.DeregisterChopExec(NoNode.ChopExecType.ValueChange, chop_op, channel_name(s))
    • Deregister a DAT execution:
      NoNode.DeregisterDatExec(NoNode.DatExecType.TableChange, dat_op)
    • Unregister a keyboard shortcut:
      NoNode.UnregisterKeyboardShortcut('ctrl.k')
  8. Visual indication:

    • Operators with registered callbacks are marked with a color for easy identification
    • Customize the mark color:
      NoNode.SetMarkColor((r, g, b))

Clone this wiki locally