サブグラフを使用すると、ユーザーはノードを再利用可能でネスト可能なコンポーネントとしてグループ化できます。各サブグラフは UUID を持つ独自の LGraph です。ユーザー向けガイドについては、Subgraphs を参照してください。
ノード識別子
ComfyUI では 3 種類の異なるノード識別子タイプを使用します。誤ったものを使用すると、サイレント失敗(エラーなしで失敗すること)の原因となります。
| タイプ | 形式 | 用途 |
|---|
node.id | 42 (数値) | 直下のグラフレベルローカル。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 は削除時だけでなく、サブグラフ変換時にも発生する可能性があります。構造変更間で状態を保持する必要がある場合は、ティアダウンロジックを保護してください。
関連項目