v2.2.1
🔍
✓ Verified — v2.2.1

Maya Headless Integration

The Maya Headless integration enables offline batch Maya pipeline operations from Vibrante-Node workflows. Unlike the live Houdini Bridge, the Maya integration uses an action-list pattern — a chain of maya_action_* nodes accumulates a list of operation dictionaries, which the maya_headless executor node then dispatches to a single batch Maya session (maya -batch or mayapy).

This design is optimal for render submissions, automated exports, and scene processing pipelines where a live, interactive Maya connection is neither needed nor appropriate. The Vibrante-Node graph specifies what to do; Maya runs headlessly and executes all operations in one session, then exits.

Key distinction from Houdini
The Houdini Bridge is a persistent live connection — every bridge call is an immediate RPC. The Maya integration is deferred and batched — actions accumulate in a list during graph execution, and Maya is only spawned once when maya_headless fires. This makes it ideal for overnight render farms and automation pipelines.

Architecture

The integration follows a two-phase model: a graph-build phase (action accumulation) and a dispatch phase (batch Maya execution).

Vibrante-Node Graph                    Batch Maya Session
───────────────────                    ──────────────────
maya_action_open_scene  ─┐             maya -batch / mayapy
maya_action_render       ├─ actions ─► runner script
maya_action_export_fbx   ┘             processes action list
        │                              sequentially in one session
        ▼
maya_headless ──────────────────────── subprocess.Popen
                                       blocks until exit
                                       non-zero exit → node error

Each maya_action_* node reads actions_in, appends one action dictionary to the list, and passes actions_out to the next node. The list accumulates as execution flows through the chain. The maya_headless executor at the end receives the complete list, generates a temporary Maya Python script, and runs it in a batch Maya process.


The Action-List Pattern

How it works

Each action is a plain Python dictionary with a "type" key that identifies the handler to call inside the Maya runner script, plus any parameters that handler needs:

# Action: open a Maya scene file
{
    "type": "open_scene",
    "scene_path": "/projects/shot_010/scenes/shot_010.ma"
}

# Action: render a frame range
{
    "type": "render",
    "camera": "renderCam",
    "start_frame": 1001,
    "end_frame": 1100,
    "output_dir": "/renders/shot_010/",
    "renderer": "arnold"
}

# Action: export selection as FBX
{
    "type": "export_fbx",
    "node": "|geo_grp",
    "output_path": "/exports/shot_010_geo.fbx"
}

# Action: run arbitrary MEL or Python
{
    "type": "run_script",
    "language": "python",
    "code": "import maya.cmds as cmds; cmds.polyCube()"
}

Actions are processed in list order — the first action that fails halts the runner and the process exits with a non-zero code, which Vibrante-Node treats as a node error.

The accumulation rule

Every action node must follow this exact pattern to avoid mutating upstream data or failing on a None input:

actions = list(inputs.get("actions_in") or [])
# build one action dict and append
actions.append({ "type": "...", ... })
return {"actions_out": actions, "exec_out": True}

list(...) creates a shallow copy so the node never mutates the original list. or [] handles the case where actions_in is None (the first node in the chain has no upstream connection).


Writing a Maya Action Node

from src.nodes.base import BaseNode

class Maya_Action_Open_Scene(BaseNode):
    name = "maya_action_open_scene"

    def __init__(self):
        super().__init__()
        # [AUTO-GENERATED-PORTS-START]
        self.add_input("actions_in", "list")
        self.add_input("scene_path", "string", widget_type="text")
        self.add_output("actions_out", "list")
        # [AUTO-GENERATED-PORTS-END]

    async def execute(self, inputs):
        actions = list(inputs.get("actions_in") or [])
        scene_path = inputs.get("scene_path", "").strip()

        if not scene_path:
            self.log_error("scene_path is required.")
            return {"actions_out": actions, "exec_out": True}

        actions.append({
            "type": "open_scene",
            "scene_path": scene_path
        })
        return {"actions_out": actions, "exec_out": True}

def register_node():
    return Maya_Action_Open_Scene
Node JSON conventions
  • node_id follows the maya_action_* pattern (e.g. maya_action_render).
  • category must be "Maya".
  • icon_path use "icons/maya.svg".
  • use_exec is true — always include exec_in / exec_out.
  • actions_in / actions_out are mandatory ports for every action node in the chain.
  • The type key in each action dict must exactly match a handler registered in the Maya runner script.

Node Library

Maya nodes live under the Maya category in the Library panel. There are 25 nodes in total:

Node IDDescription
maya_action_open_sceneOpen a .ma or .mb scene file
maya_action_new_sceneCreate a new empty scene
maya_action_save_sceneSave the current scene (in-place or to a new path)
maya_action_import_fileImport a file into the current scene
maya_action_import_referenceAdd a file reference to the current scene
maya_action_update_referenceUpdate an existing reference to a new file path
maya_action_remove_referenceRemove a file reference
maya_action_renderRender a frame range using the scene's render settings
maya_action_set_render_settingsOverride render globals (camera, resolution, renderer, output path)
maya_action_export_fbxExport a node or selection to FBX
maya_action_export_abcExport an Alembic cache for a node or selection
maya_action_export_objExport geometry as OBJ
maya_action_export_usdExport a USD stage from the scene
maya_action_set_attrSet a node attribute value (cmds.setAttr)
maya_action_get_attrRead a node attribute value into the actions list as metadata
maya_action_run_melExecute an arbitrary MEL expression or script file
maya_action_run_pythonExecute an arbitrary Python string inside the batch session
maya_action_selectSelect nodes by name pattern or type
maya_action_apply_shaderAssign a material to a mesh node
maya_action_create_nodeCreate any Maya node type with optional attribute preset
maya_action_delete_nodeDelete nodes matching a name pattern
maya_action_set_frame_rangeSet the timeline start and end frame
maya_action_set_fpsChange the scene frame rate
maya_action_optimize_sceneRun Maya's Optimize Scene Size utility
maya_headlessExecutor — spawns batch Maya and dispatches the action list

Complete Workflow Example

Render pipeline

This example opens a scene, applies render settings, and renders a frame range. Connect the exec chain (exec_out → exec_in) alongside the data chain (actions_out → actions_in):

# Built programmatically from the Scripting Console
scene.clear()

# Node 1: open scene
open_node = scene.add_node_by_name("maya_action_open_scene", (0, 0))
open_node.set_parameter("scene_path", "C:/projects/shot_010/scenes/shot_010.ma")

# Node 2: override render settings
settings_node = scene.add_node_by_name("maya_action_set_render_settings", (300, 0))
settings_node.set_parameter("camera", "renderCam")
settings_node.set_parameter("width", 1920)
settings_node.set_parameter("height", 1080)
settings_node.set_parameter("output_dir", "C:/renders/shot_010/")

# Node 3: render
render_node = scene.add_node_by_name("maya_action_render", (600, 0))
render_node.set_parameter("start_frame", 1001)
render_node.set_parameter("end_frame", 1100)

# Node 4: save scene after render
save_node = scene.add_node_by_name("maya_action_save_scene", (900, 0))

# Node 5: executor
exec_node = scene.add_node_by_name("maya_headless", (1200, 0))

# Wire data chain (action list)
scene.connect_nodes(open_node,     "actions_out", settings_node, "actions_in")
scene.connect_nodes(settings_node, "actions_out", render_node,   "actions_in")
scene.connect_nodes(render_node,   "actions_out", save_node,     "actions_in")
scene.connect_nodes(save_node,     "actions_out", exec_node,     "actions_in")

# Wire exec chain (execution order)
scene.connect_nodes(open_node,     "exec_out", settings_node, "exec_in")
scene.connect_nodes(settings_node, "exec_out", render_node,   "exec_in")
scene.connect_nodes(render_node,   "exec_out", save_node,     "exec_in")
scene.connect_nodes(save_node,     "exec_out", exec_node,     "exec_in")

app.execute_pipeline()

Batch FBX export over a shot list

Combine Vibrante-Node's general data nodes with Maya action nodes to loop over a list of scene paths and export each one:

# Pseudocode — in a real workflow use a "for_each" or "list_iterate" node
# to drive this pattern from a CSV or asset database query.

shot_list = [
    "/projects/shot_010/scenes/shot_010.ma",
    "/projects/shot_020/scenes/shot_020.ma",
    "/projects/shot_030/scenes/shot_030.ma",
]

for scene_path in shot_list:
    actions = []
    actions.append({"type": "open_scene",  "scene_path": scene_path})
    actions.append({"type": "export_fbx",  "node": "|geo_grp",
                    "output_path": scene_path.replace(".ma", "_geo.fbx")})
    actions.append({"type": "export_abc",  "node": "|anim_grp",
                    "start_frame": 1001, "end_frame": 1100,
                    "output_path": scene_path.replace(".ma", "_anim.abc")})
    # dispatch to maya_headless for each shot (or accumulate across shots)

Headless scene validation

# Validate scene health without opening Maya interactively.
# Uses maya_action_run_python to query the scene and write results to a temp file.
actions = []
actions.append({"type": "open_scene", "scene_path": "/projects/shot_010.ma"})
actions.append({
    "type": "run_python",
    "code": (
        "import maya.cmds as cmds, json, pathlib\n"
        "report = {\n"
        "    'polycount': cmds.polyEvaluate(f=True),\n"
        "    'cameras':   cmds.ls(type='camera'),\n"
        "    'errors':    cmds.ls(type='unknown'),\n"
        "}\n"
        "pathlib.Path('/tmp/validation.json').write_text(json.dumps(report))"
    )
})
# maya_headless runs this; downstream nodes read /tmp/validation.json

The maya_headless Executor Node

maya_headless is the only node in the Maya category that actually spawns a process. All other nodes are pure data transformers.

Ports

PortDirectionTypeDescription
actions_inInputlistThe fully accumulated action list from the chain
maya_exeInputstringPath to maya or mayapy executable. Defaults to auto-detect from MAYA_LOCATION or PATH.
env_scriptInputstringOptional path to a shell script or Python file to source before launching Maya (e.g. for module setup)
timeoutInputfloatMax seconds to wait for Maya to complete. 0 = no timeout.
successOutputboolTrue if Maya exited with code 0
exit_codeOutputintRaw process exit code
log_outputOutputstringCaptured stdout + stderr from the Maya process
exec_outOutputanyFires when the process exits (regardless of exit code)

Execution behaviour

Blocking execution
maya_headless blocks the Vibrante-Node execution engine for its entire duration. Do not use it for interactive workflows where the UI must remain responsive during Maya's run. For fire-and-forget submissions, consider wrapping the executor in a background thread node or submit to Deadline instead (see the Deadline integration).

Production Use Cases


Tips & Conventions

Always open before operating

The batch Maya session starts empty. Every action chain must begin with maya_action_open_scene or maya_action_new_scene. Attempting to export or render without a scene loaded produces an error from the runner script.

One Maya session per executor

All actions in a single chain share one batch Maya process. If you need to operate on multiple independent scenes, either chain open → work → open (Maya supports re-opening), or build separate chains feeding separate maya_headless nodes and run them in parallel using Vibrante-Node's branching exec flow.

Action type matching

The "type" string in each action dict must exactly match a handler function name in the Maya runner script. If you add a custom handler to the runner script, your action node's type value must use the same string. There is no runtime discovery — mismatches produce a clear KeyError in the batch Maya log, which is captured in the log_output port.

Error handling in action chains

If you need actions to continue after a partial failure (e.g. skip a bad shot and continue to the next), add an "on_error": "continue" key to individual action dicts and handle it in the runner script. By default the runner halts on the first exception.

# Action with explicit continue-on-error flag
actions.append({
    "type": "export_fbx",
    "node": "|geo_grp",
    "output_path": "/exports/shot_010_geo.fbx",
    "on_error": "continue"   # runner skips this action if export fails
})

Accessing environment variables in the runner

Custom variables set via Vibrante-Node's Settings → Environment Variables are propagated to the maya_headless subprocess environment. In the runner script, read them with os.environ.get("MY_VAR") as usual.