-
-
Notifications
You must be signed in to change notification settings - Fork 5
04. QuickExt
When adding an extension using CustomParTools
Shift+Alt+LeftClick, the created default Extension will have the following available:
- Simplified default extension code
- Includes
ExtUtilswhich 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->Extensionsection by long-clicking onAddand selectingQuickExt
It is important that
ExtUtilsstays 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.
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 # importCustomParHelper 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!
- 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
-
Import the CustomParHelper class:
CustomParHelper: CustomParHelper = next(d for d in me.docked if 'ExtUtils' in d.tags).mod('CustomParHelper').CustomParHelper # import
-
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)
-
-
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
- 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 is a versatile utility class that centralizes the management of various types of executions and callbacks in TouchDesigner, eliminating the need for dedicated nodes.
- 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
-
Make sure ExtUtils is docked to your extension
-
Import the NoNode class:
NoNode: NoNode = next(d for d in me.docked if 'ExtUtils' in d.tags).mod('NoNode').NoNode # import
-
Initialize the NoNode system in your extension:
NoNode.Init(enable_chopexec=True, enable_datexec=True, enable_keyboard_shortcuts=True)
-
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
- Register a callback for CHOP value changes:
-
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):
- React to table changes in a DAT:
-
Keyboard shortcuts:
- Register a keyboard shortcut:
NoNode.RegisterKeyboardShortcut('ctrl.k', onKeyboardShortcut) # callback signature: def onKeyboardShortcut():
- Register a keyboard shortcut:
-
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')
- Deregister a CHOP execution:
-
Visual indication:
- Operators with registered callbacks are marked with a color for easy identification
- Customize the mark color:
NoNode.SetMarkColor((r, g, b))