diff --git a/__init__.py b/__init__.py
index 5764c46..db1fd8d 100644
--- a/__init__.py
+++ b/__init__.py
@@ -1,3 +1,10 @@
+"""
+@author: rgthree
+@title: Comfy Nodes
+@nickname: rgthree
+@description: A bunch of nodes I created that I also find useful.
+"""
+
import glob
import inspect
import os
@@ -14,6 +21,7 @@ from .py.seed import RgthreeSeed
from .py.sdxl_empty_latent_image import RgthreeSDXLEmptyLatentImage
from .py.power_prompt import RgthreePowerPrompt
from .py.power_prompt_simple import RgthreePowerPromptSimple
+from .py.image_inset_crop import RgthreeImageInsetCrop
NODE_CLASS_MAPPINGS = {
RgthreeContext.NAME: RgthreeContext,
@@ -24,6 +32,7 @@ NODE_CLASS_MAPPINGS = {
RgthreeSDXLEmptyLatentImage.NAME: RgthreeSDXLEmptyLatentImage,
RgthreePowerPrompt.NAME: RgthreePowerPrompt,
RgthreePowerPromptSimple.NAME: RgthreePowerPromptSimple,
+ RgthreeImageInsetCrop.NAME: RgthreeImageInsetCrop
}
THIS_DIR=os.path.dirname(os.path.abspath(__file__))
diff --git a/js/base_node.js b/js/base_node.js
new file mode 100644
index 0000000..eb34ccd
--- /dev/null
+++ b/js/base_node.js
@@ -0,0 +1,24 @@
+export class RgthreeBaseNode extends LGraphNode {
+ constructor(title = RgthreeBaseNode.title) {
+ super(title);
+ this.isVirtualNode = true;
+ if (title == '__NEED_NAME__') {
+ throw new Error('RgthreeBaseNode needs overrides.');
+ }
+ this.properties = this.properties || {};
+ }
+ onModeChange() {
+ }
+ set mode(mode) {
+ if (this.mode_ != mode) {
+ this.mode_ = mode;
+ this.onModeChange();
+ }
+ }
+ get mode() {
+ return this.mode_;
+ }
+}
+RgthreeBaseNode.title = "__NEED_NAME__";
+RgthreeBaseNode.category = 'rgthree';
+RgthreeBaseNode._category = 'rgthree';
diff --git a/js/base_node_collector.js b/js/base_node_collector.js
index 59b5486..0b30221 100644
--- a/js/base_node_collector.js
+++ b/js/base_node_collector.js
@@ -1,13 +1,9 @@
import { app } from "../../scripts/app.js";
-export class BaseCollectorNode extends LGraphNode {
- constructor(title = BaseCollectorNode.title) {
+import { RgthreeBaseNode } from "./base_node.js";
+export class BaseCollectorNode extends RgthreeBaseNode {
+ constructor(title) {
super(title);
this.isVirtualNode = true;
- if (title == '__NEED_NAME__') {
- throw new Error('BaseCollectorNode needs overrides.');
- }
- this.properties = this.properties || {};
- this.connections = [];
this.addInput("", "*");
this.addOutput("Output", "*");
}
@@ -55,6 +51,3 @@ export class BaseCollectorNode extends LGraphNode {
}
}
}
-BaseCollectorNode.title = "__NEED_NAME__";
-BaseCollectorNode.category = 'rgthree';
-BaseCollectorNode._category = 'rgthree';
diff --git a/js/base_node_mode_changer.js b/js/base_node_mode_changer.js
index 3e24ea2..eedc49a 100644
--- a/js/base_node_mode_changer.js
+++ b/js/base_node_mode_changer.js
@@ -1,38 +1,37 @@
import { app } from "../../scripts/app.js";
-import { addConnectionLayoutSupport, addMenuItem, doChainLookup, wait } from "./utils.js";
-export class BaseNodeModeChanger extends LGraphNode {
- constructor(title = BaseNodeModeChanger.title) {
+import { RgthreeBaseNode } from "./base_node.js";
+import { addConnectionLayoutSupport, addMenuItem, getConnectedInputNodes, wait } from "./utils.js";
+export class BaseNodeModeChanger extends RgthreeBaseNode {
+ constructor(title) {
super(title);
+ this.isVirtualNode = true;
this.debouncer = 0;
this.schedulePromise = null;
- this.isVirtualNode = true;
this.modeOn = -1;
this.modeOff = -1;
- if (title == '__NEED_NAME__') {
- throw new Error('BaseNodeModeChanger needs overrides.');
- }
wait(10).then(() => {
if (this.modeOn < 0 || this.modeOff < 0) {
throw new Error('modeOn and modeOff must be overridden.');
}
});
- this.properties = this.properties || {};
- this.connections = [];
this.addInput("", "*");
}
- scheduleRefreshWidgets() {
+ scheduleStabilizeWidgets() {
if (!this.schedulePromise) {
this.schedulePromise = new Promise((resolve) => {
setTimeout(() => {
- resolve(this.refreshWidgets());
+ resolve(this.stabilizeWidgets());
this.schedulePromise = null;
}, 100);
});
}
return this.schedulePromise;
}
- refreshWidgets() {
- const linkedNodes = doChainLookup(app, this, this);
+ stabilizeWidgets() {
+ if (!this.graph) {
+ return;
+ }
+ const linkedNodes = getConnectedInputNodes(app, this);
this.stabilizeInputsOutputs();
for (const [index, node] of linkedNodes.entries()) {
let widget = this.widgets && this.widgets[index];
@@ -47,6 +46,7 @@ export class BaseNodeModeChanger extends LGraphNode {
this.widgets.length = linkedNodes.length;
}
app.graph.setDirtyCanvas(true, true);
+ setTimeout(() => { this.stabilizeWidgets(); }, 500);
}
setWidget(widget, linkedNode) {
const off = linkedNode.mode === this.modeOff;
@@ -60,10 +60,10 @@ export class BaseNodeModeChanger extends LGraphNode {
};
}
onConnectionsChainChange() {
- this.scheduleRefreshWidgets();
+ this.scheduleStabilizeWidgets();
}
onConnectionsChange(_type, _index, _connected, _linkInfo, _ioSlot) {
- this.scheduleRefreshWidgets();
+ this.scheduleStabilizeWidgets();
}
removeInput(slot) {
this._tempWidth = this.size[0];
@@ -107,7 +107,7 @@ export class BaseNodeModeChanger extends LGraphNode {
static setUp(clazz) {
addMenuItem(clazz, app, {
name: 'Refresh',
- callback: (node) => { node.scheduleRefreshWidgets(); }
+ callback: (node) => { node.scheduleStabilizeWidgets(); }
});
addMenuItem(clazz, app, {
name: (node) => { var _a; return (`${((_a = node.properties) === null || _a === void 0 ? void 0 : _a['collapse_connections']) ? 'Show' : 'Collapse'} Connections`); },
@@ -116,11 +116,8 @@ export class BaseNodeModeChanger extends LGraphNode {
callback: (_node) => { app.graph.setDirtyCanvas(true, true); }
});
addConnectionLayoutSupport(clazz, app, [['Left'], ['Right']]);
- LiteGraph.registerNodeType(clazz.title, clazz);
+ LiteGraph.registerNodeType(clazz.type, clazz);
clazz.category = clazz._category;
}
}
-BaseNodeModeChanger.title = "__NEED_NAME__";
-BaseNodeModeChanger.category = 'rgthree';
-BaseNodeModeChanger._category = 'rgthree';
BaseNodeModeChanger.collapsible = false;
diff --git a/js/bypasser.js b/js/bypasser.js
index d076af5..ddbafa1 100644
--- a/js/bypasser.js
+++ b/js/bypasser.js
@@ -1,5 +1,6 @@
import { app } from "../../scripts/app.js";
import { BaseNodeModeChanger } from "./base_node_mode_changer.js";
+import { NodeTypesString } from "./constants.js";
const MODE_BYPASS = 4;
const MODE_ALWAYS = 0;
class BypasserNode extends BaseNodeModeChanger {
@@ -9,7 +10,8 @@ class BypasserNode extends BaseNodeModeChanger {
this.modeOff = MODE_BYPASS;
}
}
-BypasserNode.title = "Fast Bypasser (rgthree)";
+BypasserNode.type = NodeTypesString.FAST_BYPASSER;
+BypasserNode.title = NodeTypesString.FAST_BYPASSER;
app.registerExtension({
name: "rgthree.Bypasser",
registerCustomNodes() {
diff --git a/js/constants.js b/js/constants.js
new file mode 100644
index 0000000..969c30b
--- /dev/null
+++ b/js/constants.js
@@ -0,0 +1,12 @@
+export function addRgthree(str) {
+ return str + ' (rgthree)';
+}
+export function stripRgthree(str) {
+ return str.replace(/\s*\(rgthree\)$/, '');
+}
+export const NodeTypesString = {
+ NODE_MODE_RELAY: addRgthree('Mute / Bypass Relay'),
+ NODE_MODE_REPEATER: addRgthree('Mute / Bypass Repeater'),
+ FAST_MUTER: addRgthree('Fast Muter'),
+ FAST_BYPASSER: addRgthree('Fast Bypasser'),
+};
diff --git a/js/muter.js b/js/muter.js
index 12da42b..ea42bf3 100644
--- a/js/muter.js
+++ b/js/muter.js
@@ -1,5 +1,6 @@
import { app } from "../../scripts/app.js";
import { BaseNodeModeChanger } from "./base_node_mode_changer.js";
+import { NodeTypesString } from "./constants.js";
const MODE_MUTE = 2;
const MODE_ALWAYS = 0;
class MuterNode extends BaseNodeModeChanger {
@@ -9,7 +10,8 @@ class MuterNode extends BaseNodeModeChanger {
this.modeOff = MODE_MUTE;
}
}
-MuterNode.title = "Fast Muter (rgthree)";
+MuterNode.type = NodeTypesString.FAST_MUTER;
+MuterNode.title = NodeTypesString.FAST_MUTER;
app.registerExtension({
name: "rgthree.Muter",
registerCustomNodes() {
diff --git a/js/node_collector.js b/js/node_collector.js
index 299f348..f46d031 100644
--- a/js/node_collector.js
+++ b/js/node_collector.js
@@ -43,7 +43,7 @@ async function updateCombinerToCollector(node) {
}
newNode.pos = [...node.pos];
newNode.size = [...node.size];
- newNode.properties = Object.assign({}, node.properties);
+ newNode.properties = { ...node.properties };
const links = [];
for (const [index, output] of node.outputs.entries()) {
for (const linkId of (output.links || [])) {
diff --git a/js/node_mode_relay.js b/js/node_mode_relay.js
new file mode 100644
index 0000000..ac7b6c6
--- /dev/null
+++ b/js/node_mode_relay.js
@@ -0,0 +1,86 @@
+import { app } from "../../scripts/app.js";
+import { addConnectionLayoutSupport, addHelp, getConnectedInputNodes, getConnectedOutputNodes, wait } from "./utils.js";
+import { BaseCollectorNode } from './base_node_collector.js';
+import { NodeTypesString, stripRgthree } from "./constants.js";
+const MODE_ALWAYS = 0;
+const MODE_MUTE = 2;
+const MODE_BYPASS = 4;
+const MODE_REPEATS = [MODE_MUTE, MODE_BYPASS];
+class NodeModeRelay extends BaseCollectorNode {
+ constructor(title) {
+ super(title);
+ setTimeout(() => { this.stabilize(); }, 500);
+ this.removeOutput(0);
+ this.addOutput('REPEATER', '_NODE_REPEATER_', {
+ color_on: '#Fc0',
+ color_off: '#a80',
+ shape: LiteGraph.ARROW_SHAPE,
+ });
+ }
+ onConnectOutput(outputIndex, inputType, inputSlot, inputNode, inputIndex) {
+ var _a, _b;
+ let canConnect = true;
+ if (super.onConnectOutput) {
+ canConnect = (_a = super.onConnectOutput) === null || _a === void 0 ? void 0 : _a.call(this, outputIndex, inputType, inputSlot, inputNode, inputIndex);
+ }
+ let nextNode = (_b = getConnectedOutputNodes(app, this, inputNode)[0]) !== null && _b !== void 0 ? _b : inputNode;
+ return canConnect && nextNode.type === NodeTypesString.NODE_MODE_REPEATER;
+ }
+ onConnectionsChange(type, slotIndex, isConnected, link_info, ioSlot) {
+ super.onConnectionsChange(type, slotIndex, isConnected, link_info, ioSlot);
+ setTimeout(() => { this.stabilize(); }, 500);
+ }
+ stabilize() {
+ var _a;
+ if (!this.graph || !this.isAnyOutputConnected() || !this.isInputConnected(0)) {
+ return;
+ }
+ const inputNodes = getConnectedInputNodes(app, this);
+ let mode = undefined;
+ for (const inputNode of inputNodes) {
+ if (mode === undefined) {
+ mode = inputNode.mode;
+ }
+ else if (mode === inputNode.mode && MODE_REPEATS.includes(mode)) {
+ continue;
+ }
+ else if (inputNode.mode === MODE_ALWAYS || mode === MODE_ALWAYS) {
+ mode = MODE_ALWAYS;
+ }
+ else {
+ mode = null;
+ }
+ }
+ if (mode != null) {
+ if ((_a = this.outputs) === null || _a === void 0 ? void 0 : _a.length) {
+ const outputNodes = getConnectedOutputNodes(app, this);
+ for (const outputNode of outputNodes) {
+ outputNode.mode = mode;
+ wait(16).then(() => {
+ outputNode.setDirtyCanvas(true, true);
+ });
+ }
+ }
+ }
+ setTimeout(() => { this.stabilize(); }, 500);
+ }
+}
+NodeModeRelay.type = NodeTypesString.NODE_MODE_RELAY;
+NodeModeRelay.title = NodeTypesString.NODE_MODE_RELAY;
+NodeModeRelay.help = [
+ `This node will relay its input nodes' modes (Mute, Bypass, or Active) to a connected`,
+ `${stripRgthree(NodeTypesString.NODE_MODE_REPEATER)} (which would then repeat that mode change to all of its inputs).`,
+ `\n`,
+ `\n- When all connected input nodes are muted, the relay will set a connected repeater to mute.`,
+ `\n- When all connected input nodes are bypassed, the relay will set a connected repeater to bypass.`,
+ `\n- When any connected input nodes are active, the relay will set a connected repeater to active.`,
+].join(' ');
+app.registerExtension({
+ name: "rgthree.NodeModeRepeaterHelper",
+ registerCustomNodes() {
+ addHelp(NodeModeRelay, app);
+ addConnectionLayoutSupport(NodeModeRelay, app, [['Left', 'Right'], ['Right', 'Left']]);
+ LiteGraph.registerNodeType(NodeModeRelay.type, NodeModeRelay);
+ NodeModeRelay.category = NodeModeRelay._category;
+ },
+});
diff --git a/js/node_mode_repeater.js b/js/node_mode_repeater.js
index 0940cc5..0e379c7 100644
--- a/js/node_mode_repeater.js
+++ b/js/node_mode_repeater.js
@@ -1,30 +1,61 @@
import { app } from "../../scripts/app.js";
-import { addConnectionLayoutSupport, addHelp, doChainLookup } from "./utils.js";
import { BaseCollectorNode } from './base_node_collector.js';
+import { NodeTypesString, stripRgthree } from "./constants.js";
+import { addConnectionLayoutSupport, addHelp, getConnectedInputNodes, getConnectedOutputNodes } from "./utils.js";
class NodeModeRepeater extends BaseCollectorNode {
+ constructor(title) {
+ super(title);
+ this.removeOutput(0);
+ this.addOutput('FAST_TOGGLER', '_FAST_TOGGLER_', {
+ color_on: '#Fc0',
+ color_off: '#a80',
+ shape: LiteGraph.ARROW_SHAPE,
+ });
+ }
+ onConnectOutput(outputIndex, inputType, inputSlot, inputNode, inputIndex) {
+ var _a, _b;
+ let canConnect = true;
+ if (super.onConnectOutput) {
+ canConnect = (_a = super.onConnectOutput) === null || _a === void 0 ? void 0 : _a.call(this, outputIndex, inputType, inputSlot, inputNode, inputIndex);
+ }
+ let nextNode = (_b = getConnectedOutputNodes(app, this, inputNode)[0]) !== null && _b !== void 0 ? _b : inputNode;
+ return canConnect && (nextNode.type === NodeTypesString.FAST_MUTER || nextNode.type === NodeTypesString.FAST_BYPASSER);
+ }
+ onConnectionsChange(type, slotIndex, isConnected, linkInfo, ioSlot) {
+ super.onConnectionsChange(type, slotIndex, isConnected, linkInfo, ioSlot);
+ if (type === LiteGraph.INPUT && isConnected) {
+ const connectedNode = this.getInputNode(slotIndex);
+ if ((connectedNode === null || connectedNode === void 0 ? void 0 : connectedNode.type) === NodeTypesString.NODE_MODE_RELAY) {
+ const input = this.inputs[slotIndex];
+ if (input) {
+ input.color_on = '#FC0';
+ input.color_off = '#a80';
+ }
+ }
+ }
+ }
onModeChange() {
- const linkedNodes = doChainLookup(app, this, this);
+ super.onModeChange();
+ const linkedNodes = getConnectedInputNodes(app, this);
for (const node of linkedNodes) {
- node.mode = this.mode;
+ if (node.type !== NodeTypesString.NODE_MODE_RELAY) {
+ node.mode = this.mode;
+ }
}
}
- set mode(mode) {
- if (this.mode_ != mode) {
- this.mode_ = mode;
- this.onModeChange();
- }
- }
- get mode() {
- return this.mode_;
- }
}
-NodeModeRepeater.type = "Node Mode Repeater (rgthree)";
-NodeModeRepeater.title = "Node Mode Repeater (rgthree)";
+NodeModeRepeater.type = NodeTypesString.NODE_MODE_REPEATER;
+NodeModeRepeater.title = NodeTypesString.NODE_MODE_REPEATER;
NodeModeRepeater.help = [
- `Connect other nodes\' outputs to this Node Mode Repeater and all connected nodes`,
- `will update their mode (mute/bypass/active) when this node's mode changes.`,
- `\n\nOptionally, connect this mode's output to a Fast Muter or Fast Bypasser for a single toggle`,
- `to then quickly mute or bypass this node and all its connected nodes.`
+ `When this node's mode (Mute, Bypass, Active) changes, it will "repeat" that mode to all`,
+ `connected input nodes.`,
+ `\n`,
+ `\n- Optionally, connect this mode's output to a ${stripRgthree(NodeTypesString.FAST_MUTER)}`,
+ `or ${stripRgthree(NodeTypesString.FAST_BYPASSER)} for a single toggle to quickly`,
+ `mute/bypass all its connected nodes.`,
+ `\n- Optionally, connect a ${stripRgthree(NodeTypesString.NODE_MODE_RELAY)} to this nodes'`,
+ `inputs to have it automatically toggle its mode. If connected, this will always take`,
+ `precedence`,
].join(' ');
app.registerExtension({
name: "rgthree.NodeModeRepeater",
diff --git a/js/utils.js b/js/utils.js
index 5f451f2..3fc2812 100644
--- a/js/utils.js
+++ b/js/utils.js
@@ -5,6 +5,11 @@ api.getNodeDefs = async function () {
this.dispatchEvent(new CustomEvent('fresh-node-defs', { detail: defs }));
return defs;
};
+var IoDirection;
+(function (IoDirection) {
+ IoDirection[IoDirection["INPUT"] = 0] = "INPUT";
+ IoDirection[IoDirection["OUTPUT"] = 1] = "OUTPUT";
+})(IoDirection || (IoDirection = {}));
const PADDING = 0;
export const LAYOUT_LABEL_TO_DATA = {
'Left': [LiteGraph.LEFT, [0, 0.5], [PADDING, 0]],
@@ -226,7 +231,7 @@ export function addHelp(node, app) {
const help = node.help;
if (help) {
addMenuItem(node, app, {
- name: 'Node Help',
+ name: '🛟 Node Help',
property: 'help',
callback: (_node) => { alert(help); }
});
@@ -239,34 +244,56 @@ export function isPassThroughType(node) {
|| (type === null || type === void 0 ? void 0 : type.includes('Node Combiner'))
|| (type === null || type === void 0 ? void 0 : type.includes('Node Collector'));
}
-export function doChainLookup(app, startNode, currentNode) {
+export function getConnectedInputNodes(app, startNode, currentNode) {
+ return getConnectedNodes(app, startNode, IoDirection.INPUT, currentNode);
+}
+export function getConnectedOutputNodes(app, startNode, currentNode) {
+ return getConnectedNodes(app, startNode, IoDirection.OUTPUT, currentNode);
+}
+function getConnectedNodes(app, startNode, dir = IoDirection.INPUT, currentNode) {
+ var _a, _b;
+ currentNode = currentNode || startNode;
let rootNodes = [];
const slotsToRemove = [];
if (startNode === currentNode || isPassThroughType(currentNode)) {
const removeDups = startNode === currentNode;
- for (const input of currentNode.inputs) {
- const linkId = input.link;
+ let linkIds;
+ if (dir == IoDirection.OUTPUT) {
+ linkIds = (_a = currentNode.outputs) === null || _a === void 0 ? void 0 : _a.flatMap(i => i.links);
+ }
+ else {
+ linkIds = (_b = currentNode.inputs) === null || _b === void 0 ? void 0 : _b.map(i => i.link);
+ }
+ let graph = app.graph;
+ for (const linkId of linkIds) {
if (!linkId) {
continue;
}
- const link = app.graph.links[linkId];
- const originNode = app.graph.getNodeById(link.origin_id);
+ const link = graph.links[linkId];
+ const connectedId = dir == IoDirection.OUTPUT ? link.target_id : link.origin_id;
+ const originNode = graph.getNodeById(connectedId);
if (isPassThroughType(originNode)) {
- for (const foundNode of doChainLookup(app, startNode, originNode)) {
+ for (const foundNode of getConnectedNodes(app, startNode, dir, originNode)) {
if (!rootNodes.includes(foundNode)) {
rootNodes.push(foundNode);
}
}
}
else if (rootNodes.includes(originNode)) {
- removeDups && (slotsToRemove.push(link.target_slot));
+ const connectedSlot = dir == IoDirection.OUTPUT ? link.origin_slot : link.target_slot;
+ removeDups && (slotsToRemove.push(connectedSlot));
}
else {
rootNodes.push(originNode);
}
}
for (const slot of slotsToRemove) {
- startNode.disconnectInput(slot);
+ if (dir == IoDirection.OUTPUT) {
+ startNode.disconnectOutput(slot);
+ }
+ else {
+ startNode.disconnectInput(slot);
+ }
}
}
return rootNodes;
diff --git a/ts/base_node.ts b/ts/base_node.ts
new file mode 100644
index 0000000..cd7f341
--- /dev/null
+++ b/ts/base_node.ts
@@ -0,0 +1,48 @@
+// /
+// @ts-ignore
+import {app} from "../../scripts/app.js";
+import { NodeMode } from "./typings/comfy.js";
+import type {LGraphNode as TLGraphNode, LiteGraph as TLiteGraph} from './typings/litegraph.js';
+
+declare const LiteGraph: typeof TLiteGraph;
+declare const LGraphNode: typeof TLGraphNode;
+
+
+export class RgthreeBaseNode extends LGraphNode {
+
+ static override title = "__NEED_NAME__";
+ // `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 = RgthreeBaseNode.title) {
+ super(title);
+ if (title == '__NEED_NAME__') {
+ throw new Error('RgthreeBaseNode needs overrides.');
+ }
+ this.properties = this.properties || {};
+ }
+
+ mode_: NodeMode;
+
+ /** When a mode change, we want all connected nodes to match. */
+ onModeChange() {
+ // Override
+ }
+
+ // @ts-ignore - Changing the property to an accessor here seems to work, but ts compiler complains.
+ override set mode(mode: NodeMode) {
+ if (this.mode_ != mode) {
+ this.mode_ = mode;
+ this.onModeChange();
+ }
+
+ }
+ override get mode() {
+ return this.mode_;
+ }
+
+}
\ No newline at end of file
diff --git a/ts/base_node_collector.ts b/ts/base_node_collector.ts
index e7f3d8e..899c467 100644
--- a/ts/base_node_collector.ts
+++ b/ts/base_node_collector.ts
@@ -1,29 +1,20 @@
// /
// @ts-ignore
import { app } from "../../scripts/app.js";
-import type {LLink, LGraph, INodeInputSlot, INodeOutputSlot, LGraphNode as TLGraphNode} from './typings/litegraph.js';
// @ts-ignore
import { ComfyWidgets } from "../../scripts/widgets.js";
+import type {LLink, LGraph, INodeInputSlot, INodeOutputSlot, LGraphNode as TLGraphNode} from './typings/litegraph.js';
+import { RgthreeBaseNode } from "./base_node.js";
+
declare const LGraphNode: typeof TLGraphNode;
-export class BaseCollectorNode extends LGraphNode {
+export class BaseCollectorNode extends RgthreeBaseNode {
- static override title = "__NEED_NAME__";
- // `category` seems to get reset at register, so we'll
- // re-reset it after the register call. ¯\_(ツ)_/¯
- static category = 'rgthree';
- static _category = 'rgthree';
+ override isVirtualNode = true;
- isVirtualNode = true;
-
- constructor(title = BaseCollectorNode.title) {
+ constructor(title?: string) {
super(title);
- if (title == '__NEED_NAME__') {
- throw new Error('BaseCollectorNode needs overrides.');
- }
- this.properties = this.properties || {};
- this.connections = [];
this.addInput("", "*");
this.addOutput("Output", "*");
}
diff --git a/ts/base_node_mode_changer.ts b/ts/base_node_mode_changer.ts
index 4694def..75b87a6 100644
--- a/ts/base_node_mode_changer.ts
+++ b/ts/base_node_mode_changer.ts
@@ -1,47 +1,41 @@
// /
// @ts-ignore
import {app} from "../../scripts/app.js";
+import { RgthreeBaseNode } from "./base_node.js";
import type {Vector2, LLink, INodeInputSlot, INodeOutputSlot, LGraphNode as TLGraphNode, LiteGraph as TLiteGraph, IWidget} from './typings/litegraph.js';
-import { addConnectionLayoutSupport, addMenuItem, doChainLookup, wait } from "./utils.js";
+import { addConnectionLayoutSupport, addMenuItem, getConnectedInputNodes, wait } from "./utils.js";
declare const LiteGraph: typeof TLiteGraph;
declare const LGraphNode: typeof TLGraphNode;
-export class BaseNodeModeChanger extends LGraphNode {
+export class BaseNodeModeChanger extends RgthreeBaseNode {
- static override title = "__NEED_NAME__";
- // `category` seems to get reset at register, so we'll
- // re-reset it after the register call. ¯\_(ツ)_/¯
- static category = 'rgthree';
- static _category = 'rgthree';
static collapsible = false;
+
+ override isVirtualNode = true;
debouncer: number = 0;
schedulePromise: Promise | null = null;
- isVirtualNode = true;
+
// These Must be overriden
readonly modeOn: number = -1;
readonly modeOff: number = -1;
- constructor(title = BaseNodeModeChanger.title) {
+ constructor(title?: string) {
super(title);
- if (title == '__NEED_NAME__') {
- throw new Error('BaseNodeModeChanger needs overrides.');
- }
+
wait(10).then(() => {
if (this.modeOn < 0 || this.modeOff < 0) {
throw new Error('modeOn and modeOff must be overridden.');
}
});
- this.properties = this.properties || {};
- this.connections = [];
this.addInput("", "*");
}
- scheduleRefreshWidgets() {
+ scheduleStabilizeWidgets() {
if (!this.schedulePromise) {
this.schedulePromise = new Promise((resolve) => {
setTimeout(() => {
- resolve(this.refreshWidgets());
+ resolve(this.stabilizeWidgets());
this.schedulePromise = null;
}, 100);
});
@@ -49,8 +43,11 @@ export class BaseNodeModeChanger extends LGraphNode {
return this.schedulePromise;
}
- refreshWidgets() {
- const linkedNodes = doChainLookup(app, this, this);
+ stabilizeWidgets() {
+ if (!this.graph) {
+ return;
+ }
+ const linkedNodes = getConnectedInputNodes(app, this);
this.stabilizeInputsOutputs();
for (const [index, node] of linkedNodes.entries()) {
let widget = this.widgets && this.widgets[index];
@@ -69,6 +66,7 @@ export class BaseNodeModeChanger extends LGraphNode {
this.widgets.length = linkedNodes.length
}
app.graph.setDirtyCanvas(true, true);
+ setTimeout(() => { this.stabilizeWidgets(); }, 500);
}
setWidget(widget: IWidget, linkedNode: TLGraphNode) {
@@ -84,13 +82,12 @@ export class BaseNodeModeChanger extends LGraphNode {
}
-
onConnectionsChainChange() {
- this.scheduleRefreshWidgets();
+ this.scheduleStabilizeWidgets();
}
override onConnectionsChange(_type: number, _index: number, _connected: boolean, _linkInfo: LLink, _ioSlot: (INodeOutputSlot | INodeInputSlot)) {
- this.scheduleRefreshWidgets();
+ this.scheduleStabilizeWidgets();
}
override removeInput(slot: number) {
@@ -138,7 +135,7 @@ export class BaseNodeModeChanger extends LGraphNode {
// @ts-ignore: Fix incorrect litegraph typings.
addMenuItem(clazz, app, {
name: 'Refresh',
- callback: (node) => {(node as T).scheduleRefreshWidgets()}
+ callback: (node) => {(node as T).scheduleStabilizeWidgets()}
});
// @ts-ignore: Fix incorrect litegraph typings.
@@ -152,7 +149,7 @@ export class BaseNodeModeChanger extends LGraphNode {
// @ts-ignore: Fix incorrect litegraph typings.
addConnectionLayoutSupport(clazz, app, [['Left'],['Right']]);
- LiteGraph.registerNodeType((clazz as any).title, clazz);
+ LiteGraph.registerNodeType((clazz as any).type, clazz);
(clazz as any).category = (clazz as any)._category;
}
}
diff --git a/ts/bypasser.ts b/ts/bypasser.ts
index 9cd3bc1..4223aea 100644
--- a/ts/bypasser.ts
+++ b/ts/bypasser.ts
@@ -2,6 +2,7 @@
// @ts-ignore
import {app} from "../../scripts/app.js";
import { BaseNodeModeChanger } from "./base_node_mode_changer.js";
+import { NodeTypesString } from "./constants.js";
import type {LGraphNode} from './typings/litegraph.js';
const MODE_BYPASS = 4;
@@ -9,7 +10,8 @@ const MODE_ALWAYS = 0;
class BypasserNode extends BaseNodeModeChanger {
- static override title = "Fast Bypasser (rgthree)";
+ static override type = NodeTypesString.FAST_BYPASSER;
+ static override title = NodeTypesString.FAST_BYPASSER;
override readonly modeOn = MODE_ALWAYS;
override readonly modeOff = MODE_BYPASS;
diff --git a/ts/constants.ts b/ts/constants.ts
new file mode 100644
index 0000000..adfa401
--- /dev/null
+++ b/ts/constants.ts
@@ -0,0 +1,15 @@
+
+export function addRgthree(str: string) {
+ return str + ' (rgthree)';
+}
+
+export function stripRgthree(str: string) {
+ return str.replace(/\s*\(rgthree\)$/, '');
+}
+
+export const NodeTypesString = {
+ NODE_MODE_RELAY: addRgthree('Mute / Bypass Relay'),
+ NODE_MODE_REPEATER: addRgthree('Mute / Bypass Repeater'),
+ FAST_MUTER: addRgthree('Fast Muter'),
+ FAST_BYPASSER: addRgthree('Fast Bypasser'),
+}
\ No newline at end of file
diff --git a/ts/muter.ts b/ts/muter.ts
index edb7f52..955b4ba 100644
--- a/ts/muter.ts
+++ b/ts/muter.ts
@@ -2,6 +2,7 @@
// @ts-ignore
import {app} from "../../scripts/app.js";
import { BaseNodeModeChanger } from "./base_node_mode_changer.js";
+import { NodeTypesString } from "./constants.js";
import type {LGraphNode} from './typings/litegraph.js';
const MODE_MUTE = 2;
@@ -9,7 +10,8 @@ const MODE_ALWAYS = 0;
class MuterNode extends BaseNodeModeChanger {
- static override title = "Fast Muter (rgthree)";
+ static override type = NodeTypesString.FAST_MUTER;
+ static override title = NodeTypesString.FAST_MUTER;
override readonly modeOn = MODE_ALWAYS;
override readonly modeOff = MODE_MUTE;
diff --git a/ts/node_mode_relay.ts b/ts/node_mode_relay.ts
new file mode 100644
index 0000000..a5fe3fc
--- /dev/null
+++ b/ts/node_mode_relay.ts
@@ -0,0 +1,111 @@
+// /
+// @ts-ignore
+import { app } from "../../scripts/app.js";
+import type {INodeInputSlot, INodeOutputSlot, LGraphNode, LLink, LiteGraph as TLiteGraph,} from './typings/litegraph.js';
+import { addConnectionLayoutSupport, addHelp, getConnectedInputNodes, getConnectedOutputNodes, wait} from "./utils.js";
+// @ts-ignore
+import { ComfyWidgets } from "../../scripts/widgets.js";
+// @ts-ignore
+import { BaseCollectorNode } from './base_node_collector.js';
+import { NodeMode } from "./typings/comfy.js";
+import { NodeTypesString, stripRgthree } from "./constants.js";
+
+declare const LiteGraph: typeof TLiteGraph;
+
+const MODE_ALWAYS = 0;
+const MODE_MUTE = 2;
+const MODE_BYPASS = 4;
+const MODE_REPEATS = [MODE_MUTE, MODE_BYPASS];
+
+
+class NodeModeRelay extends BaseCollectorNode {
+
+ static override type = NodeTypesString.NODE_MODE_RELAY;
+ static override title = NodeTypesString.NODE_MODE_RELAY;
+
+ static help = [
+ `This node will relay its input nodes' modes (Mute, Bypass, or Active) to a connected`,
+ `${stripRgthree(NodeTypesString.NODE_MODE_REPEATER)} (which would then repeat that mode change to all of its inputs).`,
+ `\n`,
+ `\n- When all connected input nodes are muted, the relay will set a connected repeater to mute.`,
+ `\n- When all connected input nodes are bypassed, the relay will set a connected repeater to bypass.`,
+ `\n- When any connected input nodes are active, the relay will set a connected repeater to active.`,
+ ].join(' ');
+
+ constructor(title?: string) {
+ super(title);
+
+ setTimeout(() => { this.stabilize(); }, 500);
+ this.removeOutput(0);
+ this.addOutput('REPEATER', '_NODE_REPEATER_', {
+ color_on: '#Fc0',
+ color_off: '#a80',
+ shape: LiteGraph.ARROW_SHAPE,
+ });
+ }
+
+ override onConnectOutput(outputIndex: number, inputType: string | -1, inputSlot: INodeInputSlot, inputNode: LGraphNode, inputIndex: number): boolean {
+ let canConnect = true;
+ if (super.onConnectOutput) {
+ canConnect = super.onConnectOutput?.(outputIndex, inputType, inputSlot, inputNode, inputIndex);
+ }
+ let nextNode = getConnectedOutputNodes(app, this, inputNode)[0] ?? inputNode;
+ return canConnect && nextNode.type === NodeTypesString.NODE_MODE_REPEATER;
+ }
+
+ override onConnectionsChange(type: number, slotIndex: number, isConnected: boolean, link_info: LLink, ioSlot: INodeOutputSlot | INodeInputSlot): void {
+ super.onConnectionsChange(type, slotIndex, isConnected, link_info, ioSlot);
+ setTimeout(() => { this.stabilize(); }, 500);
+ }
+
+ stabilize() {
+ // If we aren't connected to a repeater, then theres no sense in checking. And if we are, but
+ // have no inputs, then we're also not ready.
+ if (!this.graph || !this.isAnyOutputConnected() || !this.isInputConnected(0)) {
+ return;
+ }
+ const inputNodes = getConnectedInputNodes(app, this);
+ let mode: NodeMode|null = undefined;
+ for (const inputNode of inputNodes) {
+ // If we haven't set our mode to be, then let's set it. Otherwise, mode will stick if it
+ // remains constant, otherwise, if we hit an ALWAYS, then we'll unmute all repeaters and
+ // if not then we won't do anything.
+ if (mode === undefined) {
+ mode = inputNode.mode;
+ } else if (mode === inputNode.mode && MODE_REPEATS.includes(mode)) {
+ continue;
+ } else if (inputNode.mode === MODE_ALWAYS || mode === MODE_ALWAYS) {
+ mode = MODE_ALWAYS;
+ } else {
+ mode = null;
+ }
+ }
+
+ if (mode != null) {
+ if (this.outputs?.length) {
+ const outputNodes = getConnectedOutputNodes(app, this);
+ for (const outputNode of outputNodes) {
+ outputNode.mode = mode
+ wait(16).then(() => {
+ outputNode.setDirtyCanvas(true, true);
+ });
+ }
+ }
+ }
+ setTimeout(() => { this.stabilize(); }, 500);
+ }
+
+}
+
+
+app.registerExtension({
+ name: "rgthree.NodeModeRepeaterHelper",
+ registerCustomNodes() {
+
+ addHelp(NodeModeRelay, app);
+ addConnectionLayoutSupport(NodeModeRelay, app, [['Left','Right'],['Right','Left']]);
+
+ LiteGraph.registerNodeType(NodeModeRelay.type, NodeModeRelay);
+ NodeModeRelay.category = NodeModeRelay._category;
+ },
+});
\ No newline at end of file
diff --git a/ts/node_mode_repeater.ts b/ts/node_mode_repeater.ts
index 71afe65..eee3437 100644
--- a/ts/node_mode_repeater.ts
+++ b/ts/node_mode_repeater.ts
@@ -1,50 +1,80 @@
// /
// @ts-ignore
import { app } from "../../scripts/app.js";
-import type {LiteGraph as TLiteGraph,} from './typings/litegraph.js';
-import { addConnectionLayoutSupport, addHelp, doChainLookup} from "./utils.js";
// @ts-ignore
import { ComfyWidgets } from "../../scripts/widgets.js";
// @ts-ignore
import { BaseCollectorNode } from './base_node_collector.js';
-import { NodeMode } from "./typings/comfy.js";
+import { NodeTypesString, stripRgthree } from "./constants.js";
+
+import type {INodeInputSlot, INodeOutputSlot, LGraphNode, LLink, LiteGraph as TLiteGraph,} from './typings/litegraph.js';
+import { addConnectionLayoutSupport, addHelp, getConnectedInputNodes, getConnectedOutputNodes} from "./utils.js";
declare const LiteGraph: typeof TLiteGraph;
-/** Legacy "Combiner" */
+
class NodeModeRepeater extends BaseCollectorNode {
- static override type = "Node Mode Repeater (rgthree)";
- static override title = "Node Mode Repeater (rgthree)";
+ static override type = NodeTypesString.NODE_MODE_REPEATER;
+ static override title = NodeTypesString.NODE_MODE_REPEATER;
static help = [
- `Connect other nodes\' outputs to this Node Mode Repeater and all connected nodes`,
- `will update their mode (mute/bypass/active) when this node's mode changes.`,
- `\n\nOptionally, connect this mode's output to a Fast Muter or Fast Bypasser for a single toggle`,
- `to then quickly mute or bypass this node and all its connected nodes.`
+ `When this node's mode (Mute, Bypass, Active) changes, it will "repeat" that mode to all`,
+ `connected input nodes.`,
+ `\n`,
+ `\n- Optionally, connect this mode's output to a ${stripRgthree(NodeTypesString.FAST_MUTER)}`,
+ `or ${stripRgthree(NodeTypesString.FAST_BYPASSER)} for a single toggle to quickly`,
+ `mute/bypass all its connected nodes.`,
+ `\n- Optionally, connect a ${stripRgthree(NodeTypesString.NODE_MODE_RELAY)} to this nodes'`,
+ `inputs to have it automatically toggle its mode. If connected, this will always take`,
+ `precedence`,
].join(' ');
- mode_: NodeMode;
+ constructor(title?: string) {
+ super(title);
+ this.removeOutput(0);
+ this.addOutput('FAST_TOGGLER', '_FAST_TOGGLER_', {
+ color_on: '#Fc0',
+ color_off: '#a80',
+ shape: LiteGraph.ARROW_SHAPE,
+ });
+ }
- /** When a mode change, we want all connected nodes to match. */
- onModeChange() {
- const linkedNodes = doChainLookup(app, this, this);
+ override onConnectOutput(outputIndex: number, inputType: string | -1, inputSlot: INodeInputSlot, inputNode: LGraphNode, inputIndex: number): boolean {
+ let canConnect = true;
+ if (super.onConnectOutput) {
+ canConnect = super.onConnectOutput?.(outputIndex, inputType, inputSlot, inputNode, inputIndex);
+ }
+ // Output can only connect to a FAST MUTER or FAST BYPASSER
+ let nextNode = getConnectedOutputNodes(app, this, inputNode)[0] ?? inputNode;
+ return canConnect && (nextNode.type === NodeTypesString.FAST_MUTER || nextNode.type === NodeTypesString.FAST_BYPASSER);
+ }
+
+ override onConnectionsChange(type: number, slotIndex: number, isConnected: boolean, linkInfo: LLink, ioSlot: INodeOutputSlot | INodeInputSlot): void {
+ super.onConnectionsChange(type, slotIndex, isConnected, linkInfo, ioSlot);
+ // If we've added an input, let's see if it's a relay and change our shape and color.
+ if (type === LiteGraph.INPUT && isConnected) {
+ const connectedNode = this.getInputNode(slotIndex);
+ if (connectedNode?.type === NodeTypesString.NODE_MODE_RELAY) {
+ const input = this.inputs[slotIndex]
+ if (input) {
+ input.color_on = '#FC0';
+ input.color_off = '#a80';
+ }
+ }
+ }
+ }
+
+ /** When a mode change, we want all connected nodes to match except for connected relays. */
+ override onModeChange() {
+ super.onModeChange();
+ const linkedNodes = getConnectedInputNodes(app, this);
for (const node of linkedNodes) {
- node.mode = this.mode;
+ if (node.type !== NodeTypesString.NODE_MODE_RELAY) {
+ node.mode = this.mode;
+ }
}
}
-
- // @ts-ignore - Changing the property to an accessor here seems to work, but ts compiler complains.
- override set mode(mode: NodeMode) {
- if (this.mode_ != mode) {
- this.mode_ = mode;
- this.onModeChange();
- }
-
- }
- override get mode() {
- return this.mode_;
- }
}
diff --git a/ts/utils.ts b/ts/utils.ts
index 2fdc063..eae1ff2 100644
--- a/ts/utils.ts
+++ b/ts/utils.ts
@@ -18,6 +18,11 @@ api.getNodeDefs = async function() {
declare const LGraphNode: typeof TLGraphNode;
declare const LiteGraph: typeof TLiteGraph;
+enum IoDirection {
+ INPUT,
+ OUTPUT,
+}
+
const PADDING = 0;
type LiteGraphDir = typeof LiteGraph.LEFT | typeof LiteGraph.RIGHT | typeof LiteGraph.UP | typeof LiteGraph.DOWN;
@@ -279,7 +284,7 @@ export function addHelp(node: typeof LGraphNode, app: ComfyApp) {
const help = (node as any).help as string;
if (help) {
addMenuItem(node, app, {
- name: 'Node Help',
+ name: '🛟 Node Help',
property: 'help',
callback: (_node) => { alert(help); }
});
@@ -302,33 +307,55 @@ export function isPassThroughType(node: TLGraphNode|null) {
* Looks through the immediate chain of a node to collect all connected nodes, passing through nodes
* like reroute, etc. Will also disconnect duplicate nodes from a provided node
*/
-export function doChainLookup(app: ComfyApp, startNode: TLGraphNode, currentNode: TLGraphNode) {
+export function getConnectedInputNodes(app: ComfyApp, startNode: TLGraphNode, currentNode?: TLGraphNode) {
+ return getConnectedNodes(app, startNode, IoDirection.INPUT, currentNode);
+}
+
+export function getConnectedOutputNodes(app: ComfyApp, startNode: TLGraphNode, currentNode?: TLGraphNode) {
+ return getConnectedNodes(app, startNode, IoDirection.OUTPUT, currentNode);
+}
+
+
+function getConnectedNodes(app: ComfyApp, startNode: TLGraphNode, dir = IoDirection.INPUT, currentNode?: TLGraphNode) {
+ currentNode = currentNode || startNode;
let rootNodes: TLGraphNode[] = [];
const slotsToRemove = [];
if (startNode === currentNode || isPassThroughType(currentNode)) {
const removeDups = startNode === currentNode;
- for (const input of currentNode.inputs) {
- const linkId: number | null = input!.link;
+ let linkIds: Array;
+ if (dir == IoDirection.OUTPUT) {
+ linkIds = currentNode.outputs?.flatMap(i => i.links);
+ } else {
+ linkIds = currentNode.inputs?.map(i => i.link);
+ }
+ let graph = app.graph as LGraph;
+ for (const linkId of linkIds) {
if (!linkId) {
continue;
}
- const link: LLink = (app.graph as LGraph).links[linkId]!;
- const originNode: TLGraphNode = (app.graph as LGraph).getNodeById(link.origin_id)!;
+ const link: LLink = graph.links[linkId]!;
+ const connectedId = dir == IoDirection.OUTPUT ? link.target_id : link.origin_id;
+ const originNode: TLGraphNode = graph.getNodeById(connectedId)!;
if (isPassThroughType(originNode)) {
- for (const foundNode of doChainLookup(app, startNode, originNode)) {
+ for (const foundNode of getConnectedNodes(app, startNode, dir, originNode)) {
if (!rootNodes.includes(foundNode)) {
rootNodes.push(foundNode);
}
}
} else if (rootNodes.includes(originNode)) {
- removeDups && (slotsToRemove.push(link.target_slot))
+ const connectedSlot = dir == IoDirection.OUTPUT ? link.origin_slot : link.target_slot;
+ removeDups && (slotsToRemove.push(connectedSlot))
} else {
rootNodes.push(originNode);
}
}
for (const slot of slotsToRemove) {
- startNode.disconnectInput(slot);
+ if (dir == IoDirection.OUTPUT) {
+ startNode.disconnectOutput(slot);
+ } else {
+ startNode.disconnectInput(slot);
+ }
}
}
return rootNodes;
-}
\ No newline at end of file
+}
diff --git a/tsconfig.json b/tsconfig.json
index cd84881..5204a57 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,6 +1,6 @@
{
"compilerOptions": {
- "target": "es2017",
+ "target": "es2019",
"module": "ESNext",
"typeRoots": [
"./ts/typings",