Skip to content

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
parent_path
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 would_create_parent.

required
nodes
list[dict[str, Any]]

Ordered node specs (see above).

required
dry_run
bool

Validate the whole spec without creating anything.

False
layout
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
node_path
str

Node to cook; its output is what gets measured.

required
start
float | None

First frame. Defaults to the playbar start.

None
end
float | None

Last frame, inclusive. Defaults to the playbar end.

None
step
float

Frame increment. Keep at 1.0 for any solver, since skipping frames gives it a discontinuous time step and invalid results.

1.0
attribs
list[str] | None

Point attributes to aggregate per frame (min/max/mean/sum).

None
volumes
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
root_path
str

Network to profile (a geo container, or "/" broadly).

'/'
frame
float | None

Optionally jump to this frame before cooking.

None
limit
int

Max nodes to return.

15

get_cook_status async

get_cook_status(ctx: Context, node_path: str = '/obj') -> dict

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
node_path
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
node_type
str

Type name (e.g. "scatter", "rbdbulletsolver").

required
context
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'
parm_filter
str | None

Substring filter for the parameter list.

None
include_help
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
parent_path
str

Network to verify (e.g. "/obj/geo1").

required
force_cook
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