graph
graph
¶
MCP tools for graph-level intelligence.
Atomic network building with validation, network verification, node documentation cards, and cook profiling — the senior-artist toolset.
Functions:
| Name | Description |
|---|---|
build_network |
Build a whole node network in ONE atomic call — the PREFERRED way |
cook_frame_range |
Cook a node frame by frame and report what changed on each frame. |
find_expensive_nodes |
Profile cooking and rank the most expensive nodes — how a senior |
get_cook_status |
Whether a node has cooked, how often, and whether it is time dependent. |
get_node_card |
Get the authoritative documentation card for a node type, straight |
verify_network |
Inspect every node in a network at once — errors, warnings, flags, |
Functions:¶
build_network
async
¶
build_network(
ctx: Context,
parent_path: str,
nodes: list[dict[str, Any]],
dry_run: bool = False,
layout: bool = True,
) -> dict
Build a whole node network in ONE atomic call — the PREFERRED way to construct anything of 3+ nodes (massively faster than node-by-node calls, and either the whole network builds or nothing does).
Every node type, parameter name, and input reference is validated against the running Houdini BEFORE anything is created; errors come back with did-you-mean suggestions. Use dry_run=True to prove a plan when using unfamiliar node types. The result includes cooked evidence: per-node errors and the display node's geometry counts — read them instead of assuming success.
Each node spec dict supports exactly these keys; an unknown one (in a spec or in an input entry) is a validation error with a did-you-mean, never a silently dropped request: type (required), name, parms (lists set whole parm tuples; a value written {"expr": "ch('../x')"} is set as an expression, with an optional "language": "hscript" | "python", also as one component of a list: "t": [{"expr": "$F"}, 0, 0]; a ramp takes the whole ramp as {"keys", "values", "basis"}), expressions (or its alias exprs: a block of parm name -> expression), inputs (list of source names — earlier spec names, existing children, or absolute paths; or dicts with index or input_name / source / source_output, where input_name is a connector name or label as get_node_card lists them and source_output an output index or name ("v", "P"; names are checked on the source with its parms set, so a Bind VOP with parmname "orient" is wired from "orient"); or {"indirect_input": n} to wire from connector n of the parent subnet itself), flags (display/render/bypass/template), color [r,g,b], comment, override_expression, run_callbacks. Parms are written in the order given.
A string aimed at a numeric parameter is caught during validation and answered with the {"expr": ...} spelling, instead of failing mid-build and rolling the whole graph back.
A literal in parms does not replace an expression the parm already
holds (a Ray SOP ships dir = @N.x): the dry run lists such parms in
expressions_in_the_way, the build reports expressions_kept and a
warning. "override_expression": true on the spec clears them first.
Display: in a network that was empty, the last node gets the display
flag unless a spec sets one. A display flag asked of a node that has
none (a usdrender_rop) goes to its nearest input that has one, listed in
display_set_upstream.
A LOCKED parm (karmarendersettings resolutiony while res_mode is
autoheight) takes nothing; such a spec is refused at validation, nothing
built, with locked_parms naming the menu whose callback sets the lock.
Houdini runs callbacks only from the UI: "run_callbacks": true on the spec
runs each parm's callback after its write, so {"res_mode": "manual",
"resolution": [1920, 1080]} builds. Validation replays a spec on a probe,
its callbacks included, only when it writes a parm a fresh node locks.
There is no "children" key: build the subnet, then call build_network again with the subnet as parent_path.
Nodes built inside a DOP network come with simulation_cache: the
network to pass to reset_simulation before reading a frame it cooked.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
str
|
Network to build inside (e.g. "/obj/geo1"). A missing
parent directly under /obj is created, as the container the
specs' context needs (geo for SOPs, lopnet for LOPs), and
removed again if the build fails; a dry run reports it as
|
required |
|
list[dict[str, Any]]
|
Ordered node specs (see above). |
required |
|
bool
|
Validate the whole spec without creating anything. |
False
|
|
bool
|
Also lay out the parent network afterwards (default True; honoured only when auto-layout is enabled). The nodes this call creates are always positioned, each relative to its inputs, regardless of this flag; nodes that already existed keep their exact positions, so building into a hand-arranged network is safe. |
True
|
cook_frame_range
async
¶
cook_frame_range(
ctx: Context,
node_path: str,
start: float | None = None,
end: float | None = None,
step: float = 1.0,
attribs: list[str] | None = None,
volumes: bool = False,
) -> dict
Cook a node frame by frame and report what changed on each frame.
This is how you advance a sequential solver and how you prove a simulation
is doing something. Frames are cooked in order, so a SOP solver, a DOP
network or an animated chain all accumulate correctly, and per-frame cook
time, errors, counts, bounding box and attribute aggregates come back in
ONE round trip instead of one per frame. static: true means counts and
bounds never changed over the range: the node cooks but does nothing.
Past 25 frames the rows are 25 evenly spaced frames plus every frame with
an error or warning (frames_shown); totals, static and
slowest_frame still cover every frame.
Prefer this over set_frame in a loop, and over stepping by hand: a 100-frame check is one call rather than 100. The frame is left where the cook ended, ready to screenshot.
No deadline: a heavy sim runs under Houdini's progress bar for as long as it
takes; wait for the verdict and call nothing else meanwhile. Stopped from
that bar, it answers interrupted: true with the frames cooked so far. A
node that keeps a cache across frames (Trail, SOP Solver) starts from what it
holds: press_button its reset (Trail: clear) or reset_simulation first.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
str
|
Node to cook; its output is what gets measured. |
required |
|
float | None
|
First frame. Defaults to the playbar start. |
None
|
|
float | None
|
Last frame, inclusive. Defaults to the playbar end. |
None
|
|
float
|
Frame increment. Keep at 1.0 for any solver, since skipping frames gives it a discontinuous time step and invalid results. |
1.0
|
|
list[str] | None
|
Point attributes to aggregate per frame (min/max/mean/sum). |
None
|
|
bool
|
Also report per-volume name, resolution and value range. |
False
|
find_expensive_nodes
async
¶
find_expensive_nodes(
ctx: Context,
root_path: str = "/",
frame: float | None = None,
limit: int = 15,
) -> dict
Profile cooking and rank the most expensive nodes — how a senior artist finds the slow node instead of guessing.
Records a performance-monitor profile while force-cooking the display outputs under root_path. cook_ms is cumulative (parents include their children), so compare siblings to locate the hotspot.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
str
|
Network to profile (a geo container, or "/" broadly). |
'/'
|
|
float | None
|
Optionally jump to this frame before cooking. |
None
|
|
int
|
Max nodes to return. |
15
|
get_cook_status
async
¶
Whether a node has cooked, how often, and whether it is time dependent.
Note the shape of the limitation: every command runs on Houdini's main thread, so a long cook blocks the bridge and cannot be polled while it runs. This answers the after-the-fact question instead -- did it really recook, is it time dependent, did it end in error -- plus whether the hip has unsaved changes. For asynchronous work use a ROP's background execution and get_render_progress.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
str
|
Node to report on. |
'/obj'
|
get_node_card
async
¶
get_node_card(
ctx: Context,
node_type: str,
context: str = "Sop",
parm_filter: str | None = None,
include_help: bool | None = None,
) -> dict
Get the authoritative documentation card for a node type, straight
from the running Houdini: connectors in order (inputs / outputs
with index, name and label — the index of texcoord on mtlximage
lives here), real parameter names/defaults/menus, and the node's own
shipped help text. Connectors are read off a probe node the first time
a type is asked for in a session (no undo entry, creation scripts not
run); connectors_probed: false with connectors_note means they could
not be read, not that the type has none. A menu whose items a script
computes (loadtype on filemerge::2.0) is read off the same probe and
marked menu_source: "generator", with the script in menu_generator.
A radio folder set (switcher1 on an Add SOP: By Pattern / By Group)
is listed as a FolderSet parm whose menu is its folders; a default
that is an expression ($F on timeshift's frame) is in
default_expression; a VOP's outputs carry their data_type, and an
output named after a parm's value carries named_by_parm (a Bind VOP's
parm output is called whatever its parmname is). Parms the type's
OnCreated script adds (Solaris edit-properties nodes: "Render Points As"
on rendergeometrysettings is xn__primvarskarmaobjectpoint_style_zpbfg)
are read off a second probe that runs it, listed with added_on_create
and counted in parms_added_on_create. A VOP with several signatures
lists them all in signatures (token, label, input types), and its
signature menu is those tokens (menu_source: "signatures"), not only
the one a fresh node's menu offers.
Use this BEFORE setting parameters on a node type you have not used in this session — never guess parameter names. Unversioned names resolve to the newest version.
A menu carries menu_labels (same order as menu) whenever its labels
differ from its tokens: "0"/"1"/"2" = Surface Scatter/Keep Input/Volume Scatter.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
str
|
Type name (e.g. "scatter", "rbdbulletsolver"). |
required |
|
str
|
Category — "Sop", "Lop", "Vop" (MaterialX and other shader nodes inside a material network), "Dop", "Cop", "Chop", "Top", "Object", "Driver"; also "Cop2", "Shop", "VopNet". |
'Sop'
|
|
str | None
|
Substring filter for the parameter list. |
None
|
|
bool | None
|
The node's help text (about 4 KB). Default: included for a whole card, left out when parm_filter asks for specific parameters. Pass True or False to decide. |
None
|
verify_network
async
¶
verify_network(
ctx: Context, parent_path: str, force_cook: bool = False
) -> dict
Inspect every node in a network at once — errors, warnings, flags, and the display node's cooked geometry counts.
Call this after building or modifying a network, the way an artist
middle-clicks nodes: if healthy is false or error_nodes is
non-empty, fix those nodes before telling the user anything is done.
Each node lists errors, warnings, bypassed and display only when
it has them; absent means none. For a LOP network the evidence is the
stage (prims, cameras, lights, gprims without a material), for a COP
network the image layer (resolution, channel ranges).
A node's errors are its LAST cook's verdict. A node that nothing has
cooked since its cause was fixed is reported stale and listed in
stale_error_nodes; pass force_cook=True to recook before judging.
The default cooks only the display node, so a heavy scene is left alone.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
str
|
Network to verify (e.g. "/obj/geo1"). |
required |
|
bool
|
Cook the display node with force and recook every node that has errors, so the verdict is about the network as it is now. |
False
|