From be0aba486a0191969c647c8a8738dc6bb7aee63e Mon Sep 17 00:00:00 2001 From: rgthree Date: Sat, 26 Aug 2023 15:44:06 -0400 Subject: [PATCH] Rename 'Node Combiner' to 'Node Collector' and port the old nodes over. Add context outputs to context switch so we don't have to go through another context. Offset the reroute by 5 so it lines up with the grid. --- README.md | 4 +- __dev__.py | 7 +- js/base_node_dispatcher.js | 7 +- js/node_collector.js | 95 +++++++++++++++++++++++++++ js/node_combiner.js | 64 ------------------ js/reroute.js | 2 +- js/smartswitch.js | 1 - py/context_switch.py | 23 ++++--- ts/base_node_dispatcher.ts | 8 ++- ts/node_collector.ts | 130 +++++++++++++++++++++++++++++++++++++ ts/node_combiner.ts | 85 ------------------------ ts/reroute.ts | 2 +- ts/utils.ts | 2 +- 13 files changed, 259 insertions(+), 171 deletions(-) create mode 100644 js/node_collector.js delete mode 100644 js/node_combiner.js delete mode 100644 js/smartswitch.js create mode 100644 ts/node_collector.ts delete mode 100644 ts/node_combiner.ts diff --git a/README.md b/README.md index bbd186f..9f17c0a 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ _(In aqua blue above)_ Chooses the first non-null context. _(In purple above)_ Add a collection of all connected nodes allowing a single-spot as a "dashboard" to quickly enable and disable nodes. Two distinct nodes; one for "Muting" connected nodes, and one for "Bypassing" connected nodes. -Also, you can use the **Node Combiner** as UI-only virtual node that allows you to connect any number of nodes as an input, into a single output. As of right now, this is only useful for cleaning up noodles to the Muter node and **any other use will likely not work at all.** +Also, you can use the **Node Collector** as UI-only virtual node that allows you to connect any number of nodes as an input, into a single output. As of right now, this is only useful for cleaning up noodles to the Muter node and **any other use will likely not work at all.**
A powerful combination: Using Context, Context Switch, & Fast Muter @@ -79,6 +79,6 @@ Also, you can use the **Node Combiner** as UI-only virtual node that allows you - The workflow seen here would be a common one where we can generate a handful of base previews cheaply with a random seed, and then choose one to upscale and save to disk. -4. Lastly, and optionally, you can see the `Node Combiner`. Use it to clean up noodles if you want and connect it to the muter. You can connect anything to it, but doing so may break your workflow's execution. +4. Lastly, and optionally, you can see the `Node Collector`. Use it to clean up noodles if you want and connect it to the muter. You can connect anything to it, but doing so may break your workflow's execution.
diff --git a/__dev__.py b/__dev__.py index 9dd1116..ee45c00 100644 --- a/__dev__.py +++ b/__dev__.py @@ -2,11 +2,14 @@ import subprocess import os import shutil -subprocess.run(["tsc"]) - THIS_DIR=os.path.dirname(os.path.abspath(__file__)) DIR_DEV_JS=os.path.abspath(f'{THIS_DIR}/js') DIR_WEB_JS=os.path.abspath(f'{THIS_DIR}/../../web/extensions/rgthree') +shutil.rmtree(DIR_DEV_JS) + +subprocess.run(["tsc"]) + + shutil.rmtree(DIR_WEB_JS) shutil.copytree(DIR_DEV_JS, DIR_WEB_JS, dirs_exist_ok=True) \ No newline at end of file diff --git a/js/base_node_dispatcher.js b/js/base_node_dispatcher.js index 71bb71d..44cf992 100644 --- a/js/base_node_dispatcher.js +++ b/js/base_node_dispatcher.js @@ -13,11 +13,14 @@ export class BaseNodeDispatcher extends LGraphNode { this.connections = []; this.addInput("", "*"); } + isPassThroughType(type) { + return (type === null || type === void 0 ? void 0 : type.includes('Reroute')) || (type === null || type === void 0 ? void 0 : type.includes('Node Combiner')) || (type === null || type === void 0 ? void 0 : type.includes('Node Collector')); + } doChainLookup(startNode = this) { let rootNodes = []; const slotsToRemove = []; const type = startNode.constructor.type; - if (startNode === this || (type === null || type === void 0 ? void 0 : type.includes('Reroute')) || (type === null || type === void 0 ? void 0 : type.includes('Combiner'))) { + if (startNode === this || this.isPassThroughType(type)) { const removeDups = startNode === this; for (const input of startNode.inputs) { const linkId = input.link; @@ -27,7 +30,7 @@ export class BaseNodeDispatcher extends LGraphNode { const link = app.graph.links[linkId]; const originNode = app.graph.getNodeById(link.origin_id); const originNodeType = originNode.constructor.type; - if ((originNodeType === null || originNodeType === void 0 ? void 0 : originNodeType.includes('Reroute')) || (originNodeType === null || originNodeType === void 0 ? void 0 : originNodeType.includes('Combiner'))) { + if (this.isPassThroughType(originNodeType)) { for (const foundNode of this.doChainLookup(originNode)) { if (!rootNodes.includes(foundNode)) { rootNodes.push(foundNode); diff --git a/js/node_collector.js b/js/node_collector.js new file mode 100644 index 0000000..4d83c7e --- /dev/null +++ b/js/node_collector.js @@ -0,0 +1,95 @@ +import { app } from "../../scripts/app.js"; +import { addConnectionLayoutSupport } from "./utils.js"; +class CollectorNode extends LGraphNode { + constructor(title = CollectorNode.title) { + super(title); + this.isVirtualNode = true; + this.properties = this.properties || {}; + this.connections = []; + this.addInput("", "*"); + this.addOutput("Output", "*"); + } + clone() { + const cloned = super.clone(); + return cloned; + } + updateOutputLinks(startNode = this) { + const type = startNode.constructor.type; + if (startNode.onConnectionsChainChange) { + startNode.onConnectionsChainChange(); + } + if (startNode === this || (type === null || type === void 0 ? void 0 : type.includes('Reroute')) || (type === null || type === void 0 ? void 0 : type.includes('Combiner'))) { + for (const output of startNode.outputs) { + if (!output.links || !output.links.length) + continue; + for (const linkId of output.links) { + const link = app.graph.links[linkId]; + const targetNode = app.graph.getNodeById(link.target_id); + targetNode && this.updateOutputLinks(targetNode); + } + } + } + } + onConnectionsChange(_type, _slotIndex, _isConnected, link_info, _ioSlot) { + if (!link_info) + return; + this.stabilizeInputsOutputs(); + this.updateOutputLinks(); + } + stabilizeInputsOutputs() { + var _a, _b; + for (let index = this.inputs.length - 1; index >= 0; index--) { + const input = this.inputs[index]; + if (!input.link) { + this.removeInput(index); + } + } + this.addInput('', '*'); + const outputLength = ((_b = (_a = this.outputs[0]) === null || _a === void 0 ? void 0 : _a.links) === null || _b === void 0 ? void 0 : _b.length) || 0; + if (outputLength > 1) { + this.outputs[0].links.length = 1; + } + } +} +CollectorNode.legacyType = "Node Combiner (rgthree)"; +CollectorNode.title = "Node Collector (rgthree)"; +CollectorNode.category = 'rgthree'; +CollectorNode._category = 'rgthree'; +app.registerExtension({ + name: "rgthree.NodeCollector", + registerCustomNodes() { + console.log('registerCustomNodes'); + addConnectionLayoutSupport(CollectorNode, app, [['Left', 'Right'], ['Right', 'Left']]); + LiteGraph.registerNodeType(CollectorNode.title, CollectorNode); + CollectorNode.category = CollectorNode._category; + }, + async loadedGraphNode(node) { + if (node.type === CollectorNode.legacyType) { + const newNode = new CollectorNode(); + newNode.pos = [...node.pos]; + newNode.size = [...node.size]; + newNode.properties = Object.assign({}, node.properties); + const links = []; + for (const [index, output] of node.outputs.entries()) { + for (const linkId of (output.links || [])) { + const link = app.graph.links[linkId]; + const targetNode = app.graph.getNodeById(link.target_id); + links.push({ node: newNode, slot: index, targetNode, targetSlot: link.target_slot }); + } + } + for (const [index, input] of node.inputs.entries()) { + const linkId = input.link; + if (linkId) { + const link = app.graph.links[linkId]; + const originNode = app.graph.getNodeById(link.origin_id); + links.push({ node: originNode, slot: link.origin_slot, targetNode: newNode, targetSlot: index }); + } + } + app.graph.add(newNode); + app.graph.remove(node); + for (const link of links) { + link.node.connect(link.slot, link.targetNode, link.targetSlot); + } + } + }, +}); diff --git a/js/node_combiner.js b/js/node_combiner.js deleted file mode 100644 index 2e5ef4f..0000000 --- a/js/node_combiner.js +++ /dev/null @@ -1,64 +0,0 @@ -import { app } from "../../scripts/app.js"; -import { addConnectionLayoutSupport } from "./utils.js"; -app.registerExtension({ - name: "rgthree.NodeCombiner", - registerCustomNodes() { - class CombinerNode extends LGraphNode { - constructor(title = CombinerNode.title) { - super(title); - this.isVirtualNode = true; - this.properties = this.properties || {}; - this.connections = []; - this.addInput("", "*"); - this.addOutput("Output", "*"); - } - clone() { - const cloned = super.clone(); - return cloned; - } - updateOutputLinks(startNode = this) { - const type = startNode.constructor.type; - if (startNode.onConnectionsChainChange) { - startNode.onConnectionsChainChange(); - } - if (startNode === this || (type === null || type === void 0 ? void 0 : type.includes('Reroute')) || (type === null || type === void 0 ? void 0 : type.includes('Combiner'))) { - for (const output of startNode.outputs) { - if (!output.links || !output.links.length) - continue; - for (const linkId of output.links) { - const link = app.graph.links[linkId]; - const targetNode = app.graph.getNodeById(link.target_id); - targetNode && this.updateOutputLinks(targetNode); - } - } - } - } - onConnectionsChange(_type, _slotIndex, _isConnected, link_info, _ioSlot) { - if (!link_info) - return; - this.stabilizeInputsOutputs(); - this.updateOutputLinks(); - } - stabilizeInputsOutputs() { - var _a, _b; - for (let index = this.inputs.length - 1; index >= 0; index--) { - const input = this.inputs[index]; - if (!input.link) { - this.removeInput(index); - } - } - this.addInput('', '*'); - const outputLength = ((_b = (_a = this.outputs[0]) === null || _a === void 0 ? void 0 : _a.links) === null || _b === void 0 ? void 0 : _b.length) || 0; - if (outputLength > 1) { - this.outputs[0].links.length = 1; - } - } - } - CombinerNode.title = "Node Combiner (rgthree)"; - CombinerNode.category = 'rgthree'; - CombinerNode._category = 'rgthree'; - addConnectionLayoutSupport(CombinerNode, app, [['Left', 'Right'], ['Right', 'Left']]); - LiteGraph.registerNodeType(CombinerNode.title, CombinerNode); - CombinerNode.category = CombinerNode._category; - }, -}); diff --git a/js/reroute.js b/js/reroute.js index c8412d3..36cc8b4 100644 --- a/js/reroute.js +++ b/js/reroute.js @@ -136,7 +136,7 @@ app.registerExtension({ RerouteNode._category = 'rgthree'; RerouteNode.title_mode = LiteGraph.NO_TITLE; RerouteNode.collapsable = false; - RerouteNode.layout_slot_offset = 0; + RerouteNode.layout_slot_offset = 5; RerouteNode.size = [40, 30]; addConnectionLayoutSupport(RerouteNode, app, [ ["Left", "Right"], diff --git a/js/smartswitch.js b/js/smartswitch.js deleted file mode 100644 index 3918c74..0000000 --- a/js/smartswitch.js +++ /dev/null @@ -1 +0,0 @@ -"use strict"; diff --git a/py/context_switch.py b/py/context_switch.py index 882006f..ebb4f54 100644 --- a/py/context_switch.py +++ b/py/context_switch.py @@ -20,18 +20,21 @@ class RgthreeContextSwitch: }, } - RETURN_TYPES = ("RGTHREE_CONTEXT",) - RETURN_NAMES = ("CONTEXT",) + RETURN_TYPES = ("RGTHREE_CONTEXT", "MODEL", "CLIP", "VAE", "CONDITIONING", "CONDITIONING", "LATENT", "IMAGE", "INT",) + RETURN_NAMES = ("CONTEXT", "MODEL", "CLIP", "VAE", "POSITIVE", "NEGATIVE", "LATENT", "IMAGE", "SEED",) FUNCTION = "switch" def switch(self, ctx_01=None, ctx_02=None, ctx_03=None, ctx_04=None, prompt=None): + ctx=None if ctx_01 != None: - return (ctx_01,) - if ctx_02 != None: - return (ctx_02,) - if ctx_03 != None: - return (ctx_03,) - if ctx_04 != None: - return (ctx_04,) - return (None,) + ctx = ctx_01 + elif ctx_02 != None: + ctx = ctx_02 + elif ctx_03 != None: + ctx = ctx_03 + elif ctx_04 != None: + ctx = ctx_04 + if ctx != None: + return (ctx, ctx['model'], ctx['clip'], ctx['vae'], ctx['positive'], ctx['negative'], ctx['latent'], ctx['images'], ctx['seed'],) + return (None,None,None,None,None,None,None,None,None,) diff --git a/ts/base_node_dispatcher.ts b/ts/base_node_dispatcher.ts index e086c6d..f255d7f 100644 --- a/ts/base_node_dispatcher.ts +++ b/ts/base_node_dispatcher.ts @@ -29,11 +29,15 @@ export class BaseNodeDispatcher extends LGraphNode { this.addInput("", "*"); } + private isPassThroughType(type: string|null) { + return type?.includes('Reroute') || type?.includes('Node Combiner') || type?.includes('Node Collector'); + } + private doChainLookup(startNode: TLGraphNode = this) { let rootNodes: TLGraphNode[] = []; const slotsToRemove = []; const type = (startNode.constructor as typeof TLGraphNode).type; - if (startNode === this || type?.includes('Reroute') || type?.includes('Combiner')) { + if (startNode === this || this.isPassThroughType(type)) { const removeDups = startNode === this; for (const input of startNode.inputs) { const linkId: number | null = input!.link; @@ -43,7 +47,7 @@ export class BaseNodeDispatcher extends LGraphNode { const link: LLink = (app.graph as LGraph).links[linkId]!; const originNode: TLGraphNode = (app.graph as LGraph).getNodeById(link.origin_id)!; const originNodeType = (originNode.constructor as typeof TLGraphNode).type; - if (originNodeType?.includes('Reroute') || originNodeType?.includes('Combiner')) { + if (this.isPassThroughType(originNodeType)) { for (const foundNode of this.doChainLookup(originNode)) { if (!rootNodes.includes(foundNode)) { rootNodes.push(foundNode); diff --git a/ts/node_collector.ts b/ts/node_collector.ts new file mode 100644 index 0000000..c8df183 --- /dev/null +++ b/ts/node_collector.ts @@ -0,0 +1,130 @@ +// / +// @ts-ignore +import { app } from "../../scripts/app.js"; +import type {LLink, LGraph, INodeInputSlot, INodeOutputSlot, LGraphNode as TLGraphNode, LiteGraph as TLiteGraph} from './typings/litegraph.js'; +import { addConnectionLayoutSupport } from "./utils.js"; + +declare const LiteGraph: typeof TLiteGraph; +declare const LGraphNode: typeof TLGraphNode; + +class CollectorNode extends LGraphNode { + + static legacyType = "Node Combiner (rgthree)"; + static override title = "Node Collector (rgthree)"; + // `category` seems to get reset at register, so we'll + // re-reset it after the register call. ¯\_(ツ)_/¯ + static category = 'rgthree'; + static _category = 'rgthree'; + + isVirtualNode = true; + + constructor(title = CollectorNode.title) { + super(title); + this.properties = this.properties || {}; + this.connections = []; + this.addInput("", "*"); + this.addOutput("Output", "*"); + } + + override clone() { + const cloned = super.clone(); + return cloned; + } + + private updateOutputLinks(startNode: TLGraphNode = this) { + const type = (startNode.constructor as typeof TLGraphNode).type; + // @ts-ignore + if (startNode.onConnectionsChainChange) { + // @ts-ignore + startNode.onConnectionsChainChange(); + } + if (startNode === this || type?.includes('Reroute') || type?.includes('Combiner')) { + for (const output of startNode.outputs) { + if (!output.links || !output.links.length) continue; + for (const linkId of output.links) { + const link: LLink = (app.graph as LGraph).links[linkId]!; + const targetNode: TLGraphNode = (app.graph as LGraph).getNodeById(link.target_id)!; + targetNode && this.updateOutputLinks(targetNode) + } + } + } + } + + override onConnectionsChange(_type: number, _slotIndex: number, _isConnected: boolean, link_info: LLink, _ioSlot: (INodeOutputSlot | INodeInputSlot)) { + if (!link_info) return; + this.stabilizeInputsOutputs(); + // Follow outputs to see if we need to trigger an onConnectionChange. + this.updateOutputLinks(); + } + + private stabilizeInputsOutputs() { + for (let index = this.inputs.length - 1; index >= 0; index--) { + const input = this.inputs[index]!; + if (!input.link) { + this.removeInput(index); + } + } + this.addInput('', '*'); + + const outputLength = this.outputs[0]?.links?.length || 0; + if (outputLength > 1) { + this.outputs[0]!.links!.length = 1; + } + } +} + +app.registerExtension({ + name: "rgthree.NodeCollector", + registerCustomNodes() { + console.log('registerCustomNodes') + + // @ts-ignore: Fix incorrect litegraph typings. + addConnectionLayoutSupport(CollectorNode, app, [['Left','Right'],['Right','Left']]); + + LiteGraph.registerNodeType(CollectorNode.title, CollectorNode); + CollectorNode.category = CollectorNode._category; + }, + + /** + * In case anyone has the old Node Combiner (which was a bad name since it doesn't really + * combine, but rather collects), let's port it over to the new Collector. There will be an + * error that shows for these users, since ComfyUI doesn't know we're porting it, but when they + * look for the redbox, they won't see it and a refresh will fix it (since it will then be + * ported). + */ + async loadedGraphNode(node: TLGraphNode) { + if (node.type === CollectorNode.legacyType) { + // Create a new CollectorNode. + const newNode = new CollectorNode(); + // Port the position, size, and properties from the old node. + newNode.pos = [...node.pos]; + newNode.size = [...node.size]; + newNode.properties = {...node.properties}; + // We now collect the links data, inputs and outputs, of the old node since these will be + // lost when we remove it. + const links: any[] = []; + for (const [index, output] of node.outputs.entries()) { + for (const linkId of (output.links || [])) { + const link: LLink = (app.graph as LGraph).links[linkId]!; + const targetNode = app.graph.getNodeById(link.target_id); + links.push({node: newNode, slot: index, targetNode, targetSlot: link.target_slot}); + } + } + for (const [index, input] of node.inputs.entries()) { + const linkId = input.link; + if (linkId) { + const link: LLink = (app.graph as LGraph).links[linkId]!; + const originNode = app.graph.getNodeById(link.origin_id); + links.push({node: originNode, slot: link.origin_slot, targetNode: newNode, targetSlot: index}); + } + } + // Add the new node, remove the old node. + app.graph.add(newNode); + app.graph.remove(node); + // Now go through and connect the other nodes up as they were. + for (const link of links) { + link.node.connect(link.slot, link.targetNode, link.targetSlot); + } + } + }, +}); \ No newline at end of file diff --git a/ts/node_combiner.ts b/ts/node_combiner.ts deleted file mode 100644 index e3bd6b3..0000000 --- a/ts/node_combiner.ts +++ /dev/null @@ -1,85 +0,0 @@ -// / -// @ts-ignore -import { app } from "../../scripts/app.js"; -import type {LLink, LGraph, INodeInputSlot, INodeOutputSlot, LGraphNode as TLGraphNode, LiteGraph as TLiteGraph} from './typings/litegraph.js'; -import { addConnectionLayoutSupport } from "./utils.js"; - -declare const LiteGraph: typeof TLiteGraph; -declare const LGraphNode: typeof TLGraphNode; - -app.registerExtension({ - name: "rgthree.NodeCombiner", - registerCustomNodes() { - class CombinerNode extends LGraphNode { - - static override title = "Node Combiner (rgthree)"; - // `category` seems to get reset at register, so we'll - // re-reset it after the register call. ¯\_(ツ)_/¯ - static category = 'rgthree'; - static _category = 'rgthree'; - - isVirtualNode = true; - - constructor(title = CombinerNode.title) { - super(title); - this.properties = this.properties || {}; - this.connections = []; - this.addInput("", "*"); - this.addOutput("Output", "*"); - } - - override clone() { - const cloned = super.clone(); - return cloned; - } - - private updateOutputLinks(startNode: TLGraphNode = this) { - const type = (startNode.constructor as typeof TLGraphNode).type; - // @ts-ignore - if (startNode.onConnectionsChainChange) { - // @ts-ignore - startNode.onConnectionsChainChange(); - } - if (startNode === this || type?.includes('Reroute') || type?.includes('Combiner')) { - for (const output of startNode.outputs) { - if (!output.links || !output.links.length) continue; - for (const linkId of output.links) { - const link: LLink = (app.graph as LGraph).links[linkId]!; - const targetNode: TLGraphNode = (app.graph as LGraph).getNodeById(link.target_id)!; - targetNode && this.updateOutputLinks(targetNode) - } - } - } - } - - override onConnectionsChange(_type: number, _slotIndex: number, _isConnected: boolean, link_info: LLink, _ioSlot: (INodeOutputSlot | INodeInputSlot)) { - if (!link_info) return; - this.stabilizeInputsOutputs(); - // Follow outputs to see if we need to trigger an onConnectionChange. - this.updateOutputLinks(); - } - - private stabilizeInputsOutputs() { - for (let index = this.inputs.length - 1; index >= 0; index--) { - const input = this.inputs[index]!; - if (!input.link) { - this.removeInput(index); - } - } - this.addInput('', '*'); - - const outputLength = this.outputs[0]?.links?.length || 0; - if (outputLength > 1) { - this.outputs[0]!.links!.length = 1; - } - } - } - - // @ts-ignore: Fix incorrect litegraph typings. - addConnectionLayoutSupport(CombinerNode, app, [['Left','Right'],['Right','Left']]); - - LiteGraph.registerNodeType(CombinerNode.title, CombinerNode); - CombinerNode.category = CombinerNode._category; - }, -}); - diff --git a/ts/reroute.ts b/ts/reroute.ts index 8a578bc..6be2c62 100644 --- a/ts/reroute.ts +++ b/ts/reroute.ts @@ -21,7 +21,7 @@ app.registerExtension({ static _category = 'rgthree'; static readonly title_mode = LiteGraph.NO_TITLE; static collapsable = false; - static layout_slot_offset = 0; + static layout_slot_offset = 5; static size: Vector2 = [40, 30]; // Starting size, read from within litegraph.core readonly isVirtualNode?: boolean; diff --git a/ts/utils.ts b/ts/utils.ts index a0969b8..4f361dc 100644 --- a/ts/utils.ts +++ b/ts/utils.ts @@ -177,7 +177,7 @@ export function getConnectionPosForLayout(node: TLGraphNode, isInput: boolean, s }, 0)); // Set the direction first. This is how the connection line will be drawn. cxn.dir = data[0]; - // If we are only 10px wide or tall, then put it one the end. + // If we are only 10px wide or tall, then put it one the end if (node.size[0] == 10 && ['Left', 'Right'].includes(side) && ['Top', 'Bottom'].includes(otherSide)) { side = otherSide === 'Top' ? 'Bottom' : 'Top'; } else if (node.size[1] == 10 && ['Top', 'Bottom'].includes(side) && ['Left', 'Right'].includes(otherSide)) {