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)) {