分割出力を CPU へ転送する BOOLEAN ウィジェットを追加。旧名 Any List Split の二重登録を削除しノード検索の重複表示を解消。 Co-authored-by: Cursor <cursoragent@cursor.com>
188 lines
5.2 KiB
JavaScript
188 lines
5.2 KiB
JavaScript
/** Any Split (misc) */
|
|
|
|
import { app } from "../../scripts/app.js"
|
|
import {
|
|
attachStabilizeHooks,
|
|
IoDirection,
|
|
LENGTH_OUTPUT_NAME,
|
|
countPrimitiveDataOutputs,
|
|
ensureLengthOutput,
|
|
followConnectionUntilType,
|
|
formatDataOutputName,
|
|
pinWildcardSlot,
|
|
scheduleStabilizeRetries,
|
|
slotLabelForLinkType,
|
|
syncNodeSizeToContent,
|
|
} from "./utils.js"
|
|
import { computeDesiredDataOutputCount } from "./logic/any-split-layout.js"
|
|
import { MISC_GRAPH_RESTORE_WINDOW_MS } from "./logic/restore-window.js"
|
|
import { getGraphLink } from "./utils/graph-links.js"
|
|
|
|
const NODE_CLASS = "Any Split (misc)"
|
|
const MIN_DATA_OUTPUTS = 1
|
|
|
|
const lengthTrailing = {
|
|
isMeta: (node) => node.outputs[node.outputs.length - 1]?.name === LENGTH_OUTPUT_NAME,
|
|
countData: countPrimitiveDataOutputs,
|
|
ensureMeta: ensureLengthOutput,
|
|
}
|
|
|
|
function getSplitInput(node) {
|
|
return node.inputs?.find((inp) => inp.name === "*")
|
|
}
|
|
|
|
function addDataOutputBeforeLength(node, num = 1, nodeType = "*") {
|
|
lengthTrailing.ensureMeta(node)
|
|
for (let n = 0; n < num; n++) {
|
|
if (!lengthTrailing.isMeta(node)) {
|
|
lengthTrailing.ensureMeta(node)
|
|
}
|
|
|
|
const indexOutput = node.outputs[node.outputs.length - 1]
|
|
const savedLengthLinkIds = [...(indexOutput?.links || [])]
|
|
node.outputs.pop()
|
|
|
|
const slotIndex = node.outputs.length + 1
|
|
node.addOutput(formatDataOutputName("any", slotIndex), nodeType || "*")
|
|
node.addOutput(LENGTH_OUTPUT_NAME, "INT")
|
|
|
|
const newLengthPos = node.outputs.length - 1
|
|
for (const linkId of savedLengthLinkIds) {
|
|
const link = getGraphLink(app.graph, linkId)
|
|
if (link && link.origin_id === node.id) {
|
|
link.origin_slot = newLengthPos
|
|
}
|
|
}
|
|
node.outputs[newLengthPos].type = "INT"
|
|
}
|
|
}
|
|
|
|
function renumberDataOutputs(node) {
|
|
const end = lengthTrailing.countData(node)
|
|
for (let i = 0; i < end; i++) {
|
|
node.outputs[i].name = formatDataOutputName("any", i + 1)
|
|
}
|
|
}
|
|
|
|
function ensureMinimumDataOutputs(node, minCount, nodeType) {
|
|
lengthTrailing.ensureMeta(node)
|
|
while (lengthTrailing.countData(node) < minCount) {
|
|
addDataOutputBeforeLength(node, 1, nodeType)
|
|
}
|
|
renumberDataOutputs(node)
|
|
}
|
|
|
|
function removeUnusedDataOutputsFromEnd(node, minCount) {
|
|
if (node.removed) {
|
|
return
|
|
}
|
|
lengthTrailing.ensureMeta(node)
|
|
const lengthPos = node.outputs.length - 1
|
|
for (let i = lengthPos - 1; i >= minCount; i--) {
|
|
if (!node.outputs[i]?.links?.length) {
|
|
node.removeOutput(i)
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
renumberDataOutputs(node)
|
|
}
|
|
|
|
function normalizeLengthOutput(node) {
|
|
const lengthOut = node.outputs[node.outputs.length - 1]
|
|
if (!lengthOut) {
|
|
return
|
|
}
|
|
lengthOut.type = "INT"
|
|
lengthOut.name = LENGTH_OUTPUT_NAME
|
|
lengthOut.label = LENGTH_OUTPUT_NAME
|
|
}
|
|
|
|
function resolveElementType(node) {
|
|
const splitInput = getSplitInput(node)
|
|
const inputIndex = splitInput ? node.inputs.indexOf(splitInput) : 0
|
|
const connectedType = followConnectionUntilType(node, IoDirection.INPUT, inputIndex, true)
|
|
return connectedType?.type || "*"
|
|
}
|
|
|
|
function setupAnySplit(nodeType) {
|
|
nodeType.prototype.serialize_widgets = true
|
|
|
|
attachStabilizeHooks(nodeType, {
|
|
configureStabilize: "none",
|
|
afterBind(node) {
|
|
node._miscSplitRestoringUntil = 0
|
|
},
|
|
onConfigure(node) {
|
|
node._miscSplitRestoringUntil = Date.now() + MISC_GRAPH_RESTORE_WINDOW_MS
|
|
node.stabilize()
|
|
scheduleStabilizeRetries(node, [80, 200, 500])
|
|
},
|
|
onConnectionsChange(node, type, _slotIndex, _isConnected, _linkInfo, ioSlot) {
|
|
if (ioSlot && node.outputs?.includes(ioSlot)) {
|
|
const metaIndex = node.outputs.length - 1
|
|
if (node.outputs.indexOf(ioSlot) === metaIndex && ioSlot.name === LENGTH_OUTPUT_NAME) {
|
|
return false
|
|
}
|
|
}
|
|
if (type === IoDirection.OUTPUT && ioSlot?.name === LENGTH_OUTPUT_NAME) {
|
|
return false
|
|
}
|
|
},
|
|
})
|
|
|
|
nodeType.prototype.stabilize = function () {
|
|
if (this.removed) {
|
|
return
|
|
}
|
|
|
|
const splitInput = getSplitInput(this)
|
|
pinWildcardSlot(splitInput)
|
|
|
|
this.nodeType = resolveElementType(this)
|
|
|
|
lengthTrailing.ensureMeta(this)
|
|
|
|
const restoring = Date.now() < (this._miscSplitRestoringUntil || 0)
|
|
if (!restoring) {
|
|
removeUnusedDataOutputsFromEnd(this, MIN_DATA_OUTPUTS)
|
|
}
|
|
|
|
const dataOutputs = this.outputs.slice(0, lengthTrailing.countData(this))
|
|
const desired = computeDesiredDataOutputCount(dataOutputs)
|
|
ensureMinimumDataOutputs(this, desired, this.nodeType)
|
|
|
|
const dataCount = lengthTrailing.countData(this)
|
|
const primitivesLabel = slotLabelForLinkType(this.nodeType)
|
|
for (let i = 0; i < dataCount; i++) {
|
|
const output = this.outputs[i]
|
|
output.type = this.nodeType
|
|
output.label = primitivesLabel ?? output.name
|
|
}
|
|
|
|
normalizeLengthOutput(this)
|
|
syncNodeSizeToContent(this)
|
|
this.graph?.setDirtyCanvas?.(true, false)
|
|
}
|
|
}
|
|
|
|
app.registerExtension({
|
|
name: "comfyui-misc.AnySplit",
|
|
async beforeRegisterNodeDef(nodeType, nodeData) {
|
|
if (nodeData.name !== NODE_CLASS) {
|
|
return
|
|
}
|
|
|
|
nodeData.input = nodeData.input ?? {}
|
|
nodeData.input.required = {
|
|
"*": ["*"],
|
|
...(nodeData.input.required ?? {}),
|
|
}
|
|
|
|
nodeData.output = ["*", "INT"]
|
|
nodeData.output_name = ["any_01", "length"]
|
|
|
|
setupAnySplit(nodeType)
|
|
},
|
|
})
|