Migrate legacy reroute to litegraph native reroute (#3151)

Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Chenlei Hu
2025-03-22 18:56:35 -04:00
committed by GitHub
parent f852639758
commit 564c4d557f
23 changed files with 1026 additions and 505 deletions

View File

@@ -357,78 +357,6 @@ export class GroupNodeConfig {
output_is_list: []
})
return def
} else if (node.type === 'Reroute') {
// @ts-expect-error fixme ts strict error
const linksTo = this.linksTo[node.index]
// @ts-expect-error fixme ts strict error
if (linksTo && linksFrom && !this.externalFrom[node.index]?.[0]) {
// Being used internally
return null
}
let config = {}
let rerouteType = '*'
if (linksFrom) {
for (const [, , id, slot] of linksFrom['0']) {
const node = this.nodeData.nodes[id]
const input = node.inputs[slot]
if (rerouteType === '*') {
rerouteType = input.type
}
if (input.widget) {
// @ts-expect-error fixme ts strict error
const targetDef = globalDefs[node.type]
const targetWidget =
targetDef.input.required[input.widget.name] ??
targetDef.input.optional[input.widget.name]
const widget = [targetWidget[0], config]
const res = mergeIfValid(
// @ts-expect-error invalid slot type
{
widget
},
targetWidget,
false,
null,
widget
)
config = res?.customConfig ?? config
}
}
} else if (linksTo) {
const [id, slot] = linksTo['0']
rerouteType = this.nodeData.nodes[id].outputs[slot].type
} else {
// Reroute used as a pipe
for (const l of this.nodeData.links) {
if (l[2] === node.index) {
rerouteType = l[5]
break
}
}
if (rerouteType === '*') {
// Check for an external link
// @ts-expect-error fixme ts strict error
const t = this.externalFrom[node.index]?.[0]
if (t) {
rerouteType = t
}
}
}
// @ts-expect-error
config.forceInput = true
return {
input: {
required: {
[rerouteType]: [rerouteType, config]
}
},
output: [rerouteType],
output_name: [],
output_is_list: []
}
}
console.warn(
@@ -880,22 +808,13 @@ export class GroupNodeHandler {
link = { ...link }
const output = this.groupData.newToOldOutputMap[link.origin_slot]
let innerNode = this.innerNodes[output.node.index]
let l
while (innerNode?.type === 'Reroute') {
l = innerNode.getInputLink(0)
innerNode = innerNode.getInputNode(0)
}
if (!innerNode) {
return null
}
if (l && GroupNodeHandler.isGroupNode(innerNode)) {
return innerNode.updateLink(l)
}
link.origin_id = innerNode.id
link.origin_slot = l?.origin_slot ?? output.slot
link.origin_slot = output.slot
return link
}
@@ -1352,23 +1271,6 @@ export class GroupNodeHandler {
}
}
continue
} else if (innerNode.type === 'Reroute') {
const rerouteLinks = this.groupData.linksFrom[old.node.index]
if (rerouteLinks) {
for (const [_, , targetNodeId, targetSlot] of rerouteLinks['0']) {
const node = this.innerNodes[targetNodeId]
const input = node.inputs[targetSlot]
if (input.widget) {
const widget = node.widgets?.find(
// @ts-expect-error fixme ts strict error
(w) => w.name === input.widget.name
)
if (widget) {
widget.value = newValue
}
}
}
}
}
// @ts-expect-error fixme ts strict error
@@ -1411,30 +1313,6 @@ export class GroupNodeHandler {
return true
}
// @ts-expect-error fixme ts strict error
populateReroute(node, nodeId, map) {
if (node.type !== 'Reroute') return
const link = this.groupData.linksFrom[nodeId]?.[0]?.[0]
if (!link) return
const [, , targetNodeId, targetNodeSlot] = link
const targetNode = this.groupData.nodeData.nodes[targetNodeId]
const inputs = targetNode.inputs
const targetWidget = inputs?.[targetNodeSlot]?.widget
if (!targetWidget) return
const offset = inputs.length - (targetNode.widgets_values?.length ?? 0)
const v = targetNode.widgets_values?.[targetNodeSlot - offset]
if (v == null) return
const widgetName = Object.values(map)[0]
// @ts-expect-error fixme ts strict error
const widget = this.node.widgets.find((w) => w.name === widgetName)
if (widget) {
widget.value = v
}
}
populateWidgets() {
if (!this.node.widgets) return
@@ -1448,9 +1326,6 @@ export class GroupNodeHandler {
const widgets = Object.keys(map)
if (!node.widgets_values?.length) {
// special handling for populating values into reroutes
// this allows primitives connect to them to pick up the correct value
this.populateReroute(node, nodeId, map)
continue
}

View File

@@ -10,7 +10,6 @@ import './load3d'
import './maskeditor'
import './nodeTemplates'
import './noteNode'
import './rerouteNode'
import './saveImageExtraOutput'
import './saveMesh'
import './simpleTouchSupport'

View File

@@ -1,319 +0,0 @@
import type { IContextMenuValue } from '@comfyorg/litegraph'
import { LGraphCanvas, LGraphNode, LiteGraph } from '@comfyorg/litegraph'
import { app } from '../../scripts/app'
import { getWidgetConfig, mergeIfValid, setWidgetConfig } from './widgetInputs'
// Node that allows you to redirect connections for cleaner graphs
app.registerExtension({
name: 'Comfy.RerouteNode',
registerCustomNodes(app) {
interface RerouteNode extends LGraphNode {
__outputType?: string
}
class RerouteNode extends LGraphNode {
static category: string | undefined
static defaultVisibility = false
constructor(title?: string) {
// @ts-expect-error fixme ts strict error
super(title)
if (!this.properties) {
this.properties = {}
}
this.properties.showOutputText = RerouteNode.defaultVisibility
this.properties.horizontal = false
this.addInput('', '*')
this.addOutput(this.properties.showOutputText ? '*' : '', '*')
this.onAfterGraphConfigured = function () {
requestAnimationFrame(() => {
// @ts-expect-error fixme ts strict error
this.onConnectionsChange(LiteGraph.INPUT, null, true, null)
})
}
this.onConnectionsChange = (type, _index, connected) => {
if (app.configuringGraph) return
// Prevent multiple connections to different types when we have no input
if (connected && type === LiteGraph.OUTPUT) {
// Ignore wildcard nodes as these will be updated to real types
const types = new Set(
// @ts-expect-error fixme ts strict error
this.outputs[0].links
.map((l) => app.graph.links[l].type)
.filter((t) => t !== '*')
)
if (types.size > 1) {
const linksToDisconnect = []
// @ts-expect-error fixme ts strict error
for (let i = 0; i < this.outputs[0].links.length - 1; i++) {
// @ts-expect-error fixme ts strict error
const linkId = this.outputs[0].links[i]
const link = app.graph.links[linkId]
linksToDisconnect.push(link)
}
for (const link of linksToDisconnect) {
const node = app.graph.getNodeById(link.target_id)
// @ts-expect-error fixme ts strict error
node.disconnectInput(link.target_slot)
}
}
}
// Find root input
let currentNode: LGraphNode | null = this
let updateNodes = []
let inputType = null
let inputNode = null
while (currentNode) {
updateNodes.unshift(currentNode)
const linkId = currentNode.inputs[0].link
if (linkId !== null) {
const link = app.graph.links[linkId]
if (!link) return
const node = app.graph.getNodeById(link.origin_id)
// @ts-expect-error fixme ts strict error
const type = node.constructor.type
if (type === 'Reroute') {
if (node === this) {
// We've found a circle
currentNode.disconnectInput(link.target_slot)
currentNode = null
} else {
// Move the previous node
currentNode = node
}
} else {
// We've found the end
inputNode = currentNode
// @ts-expect-error fixme ts strict error
inputType = node.outputs[link.origin_slot]?.type ?? null
break
}
} else {
// This path has no input node
currentNode = null
break
}
}
// Find all outputs
const nodes: LGraphNode[] = [this]
let outputType = null
while (nodes.length) {
// @ts-expect-error fixme ts strict error
currentNode = nodes.pop()
const outputs =
// @ts-expect-error fixme ts strict error
(currentNode.outputs ? currentNode.outputs[0].links : []) || []
if (outputs.length) {
for (const linkId of outputs) {
const link = app.graph.links[linkId]
// When disconnecting sometimes the link is still registered
if (!link) continue
const node = app.graph.getNodeById(link.target_id)
// @ts-expect-error fixme ts strict error
const type = node.constructor.type
if (type === 'Reroute') {
// Follow reroute nodes
// @ts-expect-error fixme ts strict error
nodes.push(node)
updateNodes.push(node)
} else {
// We've found an output
const nodeOutType =
// @ts-expect-error fixme ts strict error
node.inputs &&
// @ts-expect-error fixme ts strict error
node.inputs[link?.target_slot] &&
// @ts-expect-error fixme ts strict error
node.inputs[link.target_slot].type
? // @ts-expect-error fixme ts strict error
node.inputs[link.target_slot].type
: null
if (
inputType &&
// @ts-expect-error fixme ts strict error
!LiteGraph.isValidConnection(inputType, nodeOutType)
) {
// The output doesnt match our input so disconnect it
// @ts-expect-error fixme ts strict error
node.disconnectInput(link.target_slot)
} else {
outputType = nodeOutType
}
}
}
} else {
// No more outputs for this path
}
}
const displayType = inputType || outputType || '*'
const color = LGraphCanvas.link_type_colors[displayType]
let widgetConfig
let widgetType
// Update the types of each node
for (const node of updateNodes) {
// If we dont have an input type we are always wildcard but we'll show the output type
// This lets you change the output link to a different type and all nodes will update
// @ts-expect-error fixme ts strict error
node.outputs[0].type = inputType || '*'
// @ts-expect-error fixme ts strict error
node.__outputType = displayType
// @ts-expect-error fixme ts strict error
node.outputs[0].name = node.properties.showOutputText
? displayType
: ''
// @ts-expect-error fixme ts strict error
node.setSize(node.computeSize())
// @ts-expect-error fixme ts strict error
for (const l of node.outputs[0].links || []) {
const link = app.graph.links[l]
if (link) {
link.color = color
if (app.configuringGraph) continue
const targetNode = app.graph.getNodeById(link.target_id)
// @ts-expect-error fixme ts strict error
const targetInput = targetNode.inputs?.[link.target_slot]
if (targetInput?.widget) {
const config = getWidgetConfig(targetInput)
if (!widgetConfig) {
widgetConfig = config[1] ?? {}
widgetType = config[0]
}
const merged = mergeIfValid(targetInput, [
config[0],
widgetConfig
])
if (merged.customConfig) {
widgetConfig = merged.customConfig
}
}
}
}
}
for (const node of updateNodes) {
if (widgetConfig && outputType) {
// @ts-expect-error fixme ts strict error
node.inputs[0].widget = { name: 'value' }
// @ts-expect-error fixme ts strict error
setWidgetConfig(node.inputs[0], [
widgetType ?? displayType,
widgetConfig
])
} else {
// @ts-expect-error fixme ts strict error
setWidgetConfig(node.inputs[0], null)
}
}
if (inputNode) {
// @ts-expect-error fixme ts strict error
const link = app.graph.links[inputNode.inputs[0].link]
if (link) {
link.color = color
}
}
}
this.clone = function () {
const cloned = RerouteNode.prototype.clone.apply(this)
// @ts-expect-error fixme ts strict error
cloned.removeOutput(0)
// @ts-expect-error fixme ts strict error
cloned.addOutput(this.properties.showOutputText ? '*' : '', '*')
// @ts-expect-error fixme ts strict error
cloned.setSize(cloned.computeSize())
return cloned
}
// This node is purely frontend and does not impact the resulting prompt so should not be serialized
this.isVirtualNode = true
}
// @ts-expect-error fixme ts strict error
getExtraMenuOptions(_, options): IContextMenuValue[] {
options.unshift(
{
content:
(this.properties.showOutputText ? 'Hide' : 'Show') + ' Type',
callback: () => {
this.properties.showOutputText = !this.properties.showOutputText
if (this.properties.showOutputText) {
this.outputs[0].name =
this.__outputType || (this.outputs[0].type as string)
} else {
this.outputs[0].name = ''
}
this.setSize(this.computeSize())
app.graph.setDirtyCanvas(true, true)
}
},
{
content:
(RerouteNode.defaultVisibility ? 'Hide' : 'Show') +
' Type By Default',
callback: () => {
RerouteNode.setDefaultTextVisibility(
!RerouteNode.defaultVisibility
)
}
}
)
return []
}
computeSize(): [number, number] {
return [
this.properties.showOutputText && this.outputs && this.outputs.length
? Math.max(
75,
LiteGraph.NODE_TEXT_SIZE * this.outputs[0].name.length * 0.6 +
40
)
: 75,
26
]
}
// @ts-expect-error fixme ts strict error
static setDefaultTextVisibility(visible) {
RerouteNode.defaultVisibility = visible
if (visible) {
localStorage['Comfy.RerouteNode.DefaultVisibility'] = 'true'
} else {
delete localStorage['Comfy.RerouteNode.DefaultVisibility']
}
}
}
// Load default visibility
RerouteNode.setDefaultTextVisibility(
!!localStorage['Comfy.RerouteNode.DefaultVisibility']
)
LiteGraph.registerNodeType(
'Reroute',
Object.assign(RerouteNode, {
title_mode: LiteGraph.NO_TITLE,
title: 'Reroute',
collapsable: false
})
)
RerouteNode.category = 'utils'
}
})

View File

@@ -44,7 +44,7 @@ app.registerExtension({
}
if (!(type in this.slot_types_default_out)) {
this.slot_types_default_out[type] = ['Reroute']
this.slot_types_default_out[type] = []
}
if (this.slot_types_default_out[type].includes(nodeId)) continue
this.slot_types_default_out[type].push(nodeId)
@@ -65,7 +65,7 @@ app.registerExtension({
for (const el of outputs) {
const type = el as string
if (!(type in this.slot_types_default_in)) {
this.slot_types_default_in[type] = ['Reroute'] // ["Reroute", "Primitive"]; primitive doesn't always work :'()
this.slot_types_default_in[type] = []
}
if (this.slot_types_default_in[type].includes(nodeId)) continue