From 87c6d5954696de74cab9f19ccc625dc1fd84a16c Mon Sep 17 00:00:00 2001 From: filtered <176114999+webfiltered@users.noreply.github.com> Date: Sat, 9 Nov 2024 03:13:14 +1100 Subject: [PATCH] Keep links connected when deleting nodes (#288) * Allow node delete to keep more links connected * Allow node delete to match every link * Add config for connectInputToOutput --- src/LGraphNode.ts | 83 +++++++++++++++++++++++++++++++++++------------ src/interfaces.ts | 2 ++ src/litegraph.ts | 1 + 3 files changed, 66 insertions(+), 20 deletions(-) diff --git a/src/LGraphNode.ts b/src/LGraphNode.ts index cc74b9454..c2620dd2e 100644 --- a/src/LGraphNode.ts +++ b/src/LGraphNode.ts @@ -121,6 +121,9 @@ export class LGraphNode implements Positionable, IPinnable { static filter?: string static skip_list?: boolean + /** Default setting for {@link LGraphNode.connectInputToOutput}. @see {@link INodeFlags.keepAllLinksOnBypass} */ + static keepAllLinksOnBypass: boolean = false + title: string graph: LGraph | null = null id: NodeId @@ -2390,30 +2393,70 @@ export class LGraphNode implements Positionable, IPinnable { } /** - * Try auto-connect input to output without this node when possible - * (very basic, only takes into account first input-output) + * Attempts to gracefully bypass this node in all of its connections by reconnecting all links. + * + * Each input is checked against each output. This is done on a matching index basis, i.e. input 3 -> output 3. + * If there are any input links remaining, and {@link flags}.{@link INodeFlags.keepAllLinksOnBypass keepAllLinksOnBypass} is `true`, + * each input will check for outputs that match, and take the first one that matches + * `true`: Try the index matching first, then every input to every output. + * `false`: Only matches indexes, e.g. input 3 to output 3. + * + * If {@link flags}.{@link INodeFlags.keepAllLinksOnBypass keepAllLinksOnBypass} is `undefined`, it will fall back to + * the static {@link keepAllLinksOnBypass}. * - * @returns true if connected, false otherwise + * @returns `true` if any new links were established, otherwise `false`. + * @todo Decision: Change API to return array of new links instead? */ connectInputToOutput(): boolean { - if ( - this.inputs?.length && - this.outputs && - this.outputs.length && - LiteGraph.isValidConnection(this.inputs[0].type, this.outputs[0].type) && - this.inputs[0].link && - this.outputs[0].links && - this.outputs[0].links.length - ) { - const input_link = this.graph._links.get(this.inputs[0].link) - const output_link = this.graph._links.get(this.outputs[0].links[0]) - const input_node = this.getInputNode(0) - const output_node = this.getOutputNodes(0)[0] - if (input_node && output_node) { - input_node.connect(input_link.origin_slot, output_node, output_link.target_slot) - return true + const { inputs, outputs, graph } = this + if (!inputs || !outputs) return + + const { _links } = graph + let madeAnyConnections = false + + // First pass: only match exactly index-to-index + for (const [index, input] of inputs.entries()) { + const output = outputs[index] + if (!output || !LiteGraph.isValidConnection(input.type, output.type)) continue + + const inLink = _links.get(input.link) + const inNode = graph.getNodeById(inLink?.origin_id) + if (!inNode) continue + + bypassAllLinks(output, inNode, inLink) + } + // Configured to only use index-to-index matching + if (!(this.flags.keepAllLinksOnBypass ?? LGraphNode.keepAllLinksOnBypass)) return madeAnyConnections + + // Second pass: match any remaining links + for (const input of inputs) { + const inLink = _links.get(input.link) + const inNode = graph.getNodeById(inLink?.origin_id) + if (!inNode) continue + + for (const output of outputs) { + if (!LiteGraph.isValidConnection(input.type, output.type)) continue + + bypassAllLinks(output, inNode, inLink) + break + } + } + return madeAnyConnections + + function bypassAllLinks(output: INodeOutputSlot, inNode: LGraphNode, inLink: LLink) { + const outLinks = output.links + ?.map(x => _links.get(x)) + .filter(x => !!x) + if (!outLinks?.length) return + + for (const outLink of outLinks) { + const outNode = graph.getNodeById(outLink.target_id) + if (!outNode) return + + // TODO: Add 4th param (afterRerouteId: inLink.parentId) when reroutes are merged. + const result = inNode.connect(inLink.origin_slot, outNode, outLink.target_slot) + madeAnyConnections ||= !!result } } - return false } } diff --git a/src/interfaces.ts b/src/interfaces.ts index 59ee75513..b0770c9ab 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -145,6 +145,8 @@ export interface INodeFlags { allow_interaction?: boolean pinned?: boolean collapsed?: boolean + /** Configuration setting for {@link LGraphNode.connectInputToOutput} */ + keepAllLinksOnBypass?: boolean } export interface INodeInputSlot extends INodeSlot { diff --git a/src/litegraph.ts b/src/litegraph.ts index e4a52fd1f..6a66d5d70 100644 --- a/src/litegraph.ts +++ b/src/litegraph.ts @@ -101,6 +101,7 @@ export interface LGraphNodeConstructor { title_mode?: TitleMode title_color?: string title_text_color?: string + keepAllLinksOnBypass: boolean nodeData: any new(): T }