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.

This commit is contained in:
rgthree
2023-08-26 15:44:06 -04:00
parent d420762509
commit be0aba486a
13 changed files with 259 additions and 171 deletions
+2 -2
View File
@@ -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.**
<details>
<summary><big><b>A powerful combination: Using Context, Context Switch, & Fast Muter</b></big></summary>
@@ -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.
</details>
+5 -2
View File
@@ -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)
+5 -2
View File
@@ -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);
+95
View File
@@ -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);
}
}
},
});
-64
View File
@@ -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;
},
});
+1 -1
View File
@@ -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"],
-1
View File
@@ -1 +0,0 @@
"use strict";
+13 -10
View File
@@ -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,)
+6 -2
View File
@@ -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);
+130
View File
@@ -0,0 +1,130 @@
// / <reference path="../node_modules/litegraph.js/src/litegraph.d.ts" />
// @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);
}
}
},
});
-85
View File
@@ -1,85 +0,0 @@
// / <reference path="../node_modules/litegraph.js/src/litegraph.d.ts" />
// @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;
},
});
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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)) {