メインコンテンツへスキップ

概要

サブグラフを使用すると、ユーザーはノードを再利用可能でネスト可能なコンポーネントとしてグループ化できます。各サブグラフは UUID を持つ独自の LGraph です。ユーザー向けガイドについては、Subgraphs を参照してください。

ノード識別子

ComfyUI では 3 種類の異なるノード識別子タイプを使用します。誤ったものを使用すると、サイレント失敗(エラーなしで失敗すること)の原因となります。
タイプ形式用途
node.id42 (数値)直下のグラフレベルローカル。graph.getNodeById(id)
実行 ID"1:2:3" (コロン区切り文字列)バックエンドの進捗メッセージ、UNIQUE_ID
ロケーター ID"<uuid>:<localId>" または "<localId>"UI 状態:バッジ、エラー、画像
拡張機能内からノードのロケーター ID を構築するには:
function getLocatorId(node) {
  const graphId = node.graph?.id
  return graphId ? `${graphId}:${node.id}` : String(node.id)
}

ノードのトラバース

現在のレイヤーのみ

for (const node of app.graph.nodes) {
  console.log(node.id, node.type)
}

すべてノードを再帰的に

ネストされたサブグラフに入るには、すべてのノードでコールバックを呼び出す再帰ヘルパーを使用します:
function walkGraph(graph, callback) {
  for (const node of graph.nodes ?? []) {
    callback(node, graph)
    if (node.subgraph) walkGraph(node.subgraph, callback)
  }
}
完全な例:
import { app } from "../../scripts/app.js"

function walkGraph(graph, callback) {
  for (const node of graph.nodes ?? []) {
    callback(node, graph)
    if (node.subgraph) walkGraph(node.subgraph, callback)
  }
}

app.registerExtension({
  name: "MyExtension.SubgraphWalker",
  async afterConfigureGraph() {
    walkGraph(app.graph, (node, graph) => {
      console.log(`[${graph.id ?? "root"}] node ${node.id}: ${node.type}`)
    })
  }
})

ルート vs アクティブグラフ

目的…使用対象
ワークフロー内のすべてのノードを操作するapp.graph (ルート)
表示されているレイヤーのみを操作するapp.canvas?.graph
特定のサブグラフにアクセスするsomeNode.subgraph
// すべてのノード(ネストされたサブグラフを含む)
walkGraph(app.graph, (node) => { /* ... */ })

// ユーザーが現在見ているノードのみ
for (const node of app.canvas?.graph?.nodes ?? []) { /* ... */ }

イベント

サブグレープレベルのイベント

subgraph.events でディスパッチされます:
イベントペイロード発生時期
widget-promoted{ widget, subgraphNode }ウィジェットが親ノードにプロモートされた場合
widget-demoted{ widget, subgraphNode }ウィジェットが親ノードから削除された場合
input-added{ input }入力スロットが追加された場合
removing-input{ input, index }入力スロットが削除されている場合
output-added{ output }出力スロットが追加された場合
removing-output{ output, index }出力スロットが削除されている場合
renaming-input{ input, index, oldName, newName }入力スロットの名前が変更された場合
renaming-output{ output, index, oldName, newName }出力スロットの名前が変更された場合

キャンバスレベルのイベント

app.canvas.canvas (HTML キャンバス要素) でディスパッチされます:
イベントペイロード発生時期
subgraph-opened{ subgraph, closingGraph, fromNode }ユーザーがサブグラフに移動した場合
subgraph-converted{ subgraphNode }選択範囲がサブグラフに変換された場合

リスニングパターン

import { app } from "../../scripts/app.js"

app.registerExtension({
  name: "MyExtension.SubgraphEvents",
  async setup() {
    app.canvas.canvas.addEventListener("subgraph-opened", (e) => {
      const { subgraph, fromNode } = e.detail
      console.log(`Opened subgraph from node ${fromNode.id}`)
    })
  }
})

ウィジェットのプロモート

SubgraphInput がサブグラフ内のウィジェットに接続されると、そのウィジェットのコピーが親サブグラフノードに表示されます。これにより widget-promoted が発生します。接続を削除すると widget-demoted が発生します。
ウィジェットのプロモート動作は依然として進化中であり、将来のリリースで変更される可能性があります。
import { app } from "../../scripts/app.js"

function walkGraph(graph, callback) {
  for (const node of graph.nodes ?? []) {
    callback(node, graph)
    if (node.subgraph) walkGraph(node.subgraph, callback)
  }
}

app.registerExtension({
  name: "MyExtension.WidgetPromotion",
  async afterConfigureGraph() {
    walkGraph(app.graph, (node) => {
      if (!node.subgraph) return
      if (node._promCleanup) node._promCleanup.abort()
      const controller = new AbortController()
      node._promCleanup = controller
      const { signal } = controller

      node.subgraph.events.addEventListener("widget-promoted", (e) => {
        console.log(`Widget "${e.detail.widget.name}" promoted`)
      }, { signal })

      node.subgraph.events.addEventListener("widget-demoted", (e) => {
        console.log(`Widget "${e.detail.widget.name}" demoted`)
      }, { signal })

      const origRemoved = node.onRemoved
      node.onRemoved = function () {
        controller.abort()
        origRemoved?.apply(this, arguments)
      }
    })
  }
})

クリーンアップ

ノードが削除されたときにすべてのイベントリスナーをクリーンアップするには、AbortController を使用します。
import { app } from "../../scripts/app.js"

app.registerExtension({
  name: "MyExtension.Cleanup",
  async nodeCreated(node) {
    if (!node.subgraph) return

    const controller = new AbortController()
    const { signal } = controller

    node.subgraph.events.addEventListener("input-added", (e) => {
      console.log(`Input added: ${e.detail.input.name}`)
    }, { signal })

    node.subgraph.events.addEventListener("removing-input", (e) => {
      console.log(`Input removing: ${e.detail.input.name}`)
    }, { signal })

    const origRemoved = node.onRemoved
    node.onRemoved = function () {
      controller.abort()
      origRemoved?.apply(this, arguments)
    }
  }
})
onRemoved は削除時だけでなく、サブグラフ変換時にも発生する可能性があります。構造変更間で状態を保持する必要がある場合は、ティアダウンロジックを保護してください。

関連項目