| name | manage-annotations |
| description | MUST READ before creating, modifying, or querying annotations. Contains parameter names, coordinate model, and API quirks. |
Manage Annotations Workflow
Creating Annotations
create_annotation(
parent_path='/path/to/network',
mode='annotate', # 'annotate' (title bar), 'comment', or 'networkbox'
title='My Group',
text='Description of this group',
x=..., y=..., width=..., height=...,
color=[r, g, b], # Optional, floats 0-1
opacity=0.5 # Optional
)
Always favor annotations over OP comments for documenting operators or groups.
Enclosing Operators
To create an annotation that encloses a group of operators:
- Get positions:
get_network_layout on the parent COMP (includes all operator positions and bounding box)
- Calculate bounding box: Find
min_x, max_x, min_y, max_y (max includes operator width/height: max_x = max(op_x + op_w), max_y = max(op_y + op_h))
- Add padding: 70 units on left/right/bottom, 170 units on top (title bar + body text)
- Set coordinates:
nodeX = min_x - 70
nodeY = min_y - 70 (BELOW operators, not above!)
nodeWidth = max_x - min_x + 140
nodeHeight = max_y - min_y + 240 (70 bottom + 170 top)
Coordinate Model
nodeX/nodeY = bottom-left corner, width/height extend rightward and upward
- Title bar renders at the top of the rectangle
- Common mistake: Setting
nodeY above the operators. nodeY must be BELOW (less than) the lowest operator's Y.
Querying
get_annotations — list all annotations in a COMP with properties and enclosed operators
get_enclosed_ops — get operators enclosed by an annotation, or annotations enclosing an operator
set_annotation — modify text, title, color, opacity, position, or size
Deleting Annotations
Delete via delete_op, never via raw .destroy() in execute_python.
delete_op resolves utility annotations, purges any tracking, and arms an
auto-save checkpoint that re-exports the parent TDN COMP's .tdn without
the annotation -- the deletion is durable. A raw .destroy() leaves the
stale annotations: entry in the parent's .tdn on disk, and the next
reimport of that COMP (import_network, manager Reload, or cold open)
resurrects the annotation with its pre-delete text.
annotateCOMP Quirks
utility is True for every annotation -- TD UI-drawn ones are born
that way, create_annotation sets it, and TDN import applies it on every
annotation it recreates. (A bare Python parent.create('annotateCOMP')
is utility=False -- set ann.utility = True immediately to match; a
non-utility annotation is an ordinary COMP subtree that enumeration
sweeps will walk into.)
utility=True hides the op from op(), parent.op(), AND
.children -- only findChildren(includeUtility=True) sees it, and a
deep findChildren does not even DESCEND into a utility annotate's
subtree unless includeUtility=True is passed (verified live, TD
2025.33070). Paths THROUGH a utility annotate to its interior ops still
resolve via op().
- Every Envoy op-path tool resolves utility annotations (
delete_op,
set_parameter, set_op_position, get_op, ... -- they share one
utility-aware resolver). For DISCOVERY, use get_annotations (always
sees them) or pass include_utility=True to
query_network/find_children -- with the default False, annotations
are invisible in those listings.
- Annotations are never externalized per-op -- they round-trip through
the parent TDN COMP's semantic
annotations: section. externalize_op
refuses them, and tagging sweeps skip them and their internals (the
widget internals are TD-managed stock content cloned from TDAnnotate).
.type returns 'annotate' (not 'annotateCOMP')
findChildren(type=annotateCOMP) requires the class object, not the string
- Cannot be reliably renamed after creation (TD also ignores a name passed
at create time -- rename right after creating instead)