9 Commits
23 changed files with 2113 additions and 127 deletions
+3 -1
View File
@@ -1,3 +1,5 @@
{ {
"printWidth": 100 "printWidth": 100,
"bracketSpacing": false,
"bracketSameLine": true
} }
+4
View File
@@ -27,12 +27,16 @@ from .py.power_prompt import RgthreePowerPrompt
from .py.power_prompt_simple import RgthreePowerPromptSimple from .py.power_prompt_simple import RgthreePowerPromptSimple
from .py.image_inset_crop import RgthreeImageInsetCrop from .py.image_inset_crop import RgthreeImageInsetCrop
from .py.context_big import RgthreeBigContext from .py.context_big import RgthreeBigContext
from .py.context_dynamic import RgthreeDynamicContext
from .py.context_dynamic_switch import RgthreeContextDynamicSwitch
from .py.ksampler_config import RgthreeKSamplerConfig from .py.ksampler_config import RgthreeKSamplerConfig
from .py.sdxl_power_prompt_postive import RgthreeSDXLPowerPromptPositive from .py.sdxl_power_prompt_postive import RgthreeSDXLPowerPromptPositive
from .py.sdxl_power_prompt_simple import RgthreeSDXLPowerPromptSimple from .py.sdxl_power_prompt_simple import RgthreeSDXLPowerPromptSimple
from .py.any_switch import RgthreeAnySwitch from .py.any_switch import RgthreeAnySwitch
NODE_CLASS_MAPPINGS = { NODE_CLASS_MAPPINGS = {
RgthreeDynamicContext.NAME: RgthreeDynamicContext,
RgthreeContextDynamicSwitch.NAME: RgthreeContextDynamicSwitch,
RgthreeBigContext.NAME: RgthreeBigContext, RgthreeBigContext.NAME: RgthreeBigContext,
RgthreeContext.NAME: RgthreeContext, RgthreeContext.NAME: RgthreeContext,
RgthreeContextSwitch.NAME: RgthreeContextSwitch, RgthreeContextSwitch.NAME: RgthreeContextSwitch,
+47
View File
@@ -0,0 +1,47 @@
"""The Dynamic Context node."""
from .constants import get_category, get_name
from .utils import ByPassTypeTuple
class RgthreeDynamicContext:
"""The Dynamic Context node.
Similar to the static Context and Context Big nodes, this allows users to add any number and
variety of inputs to a Dynamic Context node, and return the outputs by key name.
"""
NAME = get_name("Dynamic Context")
CATEGORY = get_category()
@classmethod
def INPUT_TYPES(cls): # pylint: disable = invalid-name,missing-function-docstring
return {
"required": {},
"optional": {
"base_ctx": ("DYNAMIC_CONTEXT",),
"output_keys": ("DYNAMIC_CONTEXT_OUTPUTS",), # This is a hidden widget of the output keys
},
"hidden": {},
}
RETURN_TYPES = ByPassTypeTuple(("DYNAMIC_CONTEXT",))
RETURN_NAMES = ByPassTypeTuple(("CONTEXT",))
FUNCTION = "main"
def main(self, base_ctx=None, output_keys=None, **kwargs):
"""Creates a new context from the provided data, with an optional base ctx to start."""
new_ctx = base_ctx.copy() if base_ctx is not None else {}
for key_raw, value in kwargs.items():
key = key_raw.upper()
if key.startswith('+ '):
key = key[2:]
if key == "base_ctx":
continue
new_ctx[key] = value
res = [new_ctx]
output_keys = output_keys.split(',') if output_keys is not None else []
for key in output_keys:
res.append(new_ctx[key] if key in new_ctx else None)
return tuple(res)
+59
View File
@@ -0,0 +1,59 @@
"""The original Context Switch."""
from .constants import get_category, get_name
from .context_utils import is_context_empty
from .utils import ByPassTypeTuple
class RgthreeContextDynamicSwitch:
"""The initial Context Switch node.
For now, this will remain as-is but is otherwise backwards compatible with other Context nodes
outputs.
"""
NAME = get_name("Dynamic Context Switch")
CATEGORY = get_category()
@classmethod
def INPUT_TYPES(cls): # pylint: disable = invalid-name, missing-function-docstring
return {
"required": {},
"optional": {
"output_keys": ("DYNAMIC_CONTEXT_OUTPUTS",), # This is a hidden widget of the output keys
"ctx_01": ("DYNAMIC_CONTEXT",),
"ctx_02": ("DYNAMIC_CONTEXT",),
"ctx_03": ("DYNAMIC_CONTEXT",),
"ctx_04": ("DYNAMIC_CONTEXT",),
"ctx_05": ("DYNAMIC_CONTEXT",),
},
}
RETURN_TYPES = ByPassTypeTuple(("DYNAMIC_CONTEXT", ))
RETURN_NAMES = ByPassTypeTuple(("CONTEXT", ))
FUNCTION = "switch"
def switch(self, output_keys=None, ctx_01=None, ctx_02=None, ctx_03=None, ctx_04=None, ctx_05=None):
"""Chooses the first non-empty Context to output.
"""
base_ctx = None
if not is_context_empty(ctx_01):
base_ctx = ctx_01
elif not is_context_empty(ctx_02):
base_ctx = ctx_02
elif not is_context_empty(ctx_03):
base_ctx = ctx_03
elif not is_context_empty(ctx_04):
base_ctx = ctx_04
elif not is_context_empty(ctx_05):
base_ctx = ctx_05
new_ctx = base_ctx.copy() if not is_context_empty(base_ctx) else None
res = [new_ctx]
output_keys = output_keys.split(',') if output_keys is not None else []
for key in output_keys:
if new_ctx is None:
res.append(None)
else:
res.append(new_ctx[key] if key in new_ctx else None)
return tuple(res)
+3 -2
View File
@@ -81,8 +81,9 @@ class RgthreePowerPrompt:
log_node_success(NODE_NAME, f'Loaded "{lora["lora"]}" from prompt') log_node_success(NODE_NAME, f'Loaded "{lora["lora"]}" from prompt')
log_node_info(NODE_NAME, f'{len(loras)} Loras processed; stripping tags for TEXT output.') log_node_info(NODE_NAME, f'{len(loras)} Loras processed; stripping tags for TEXT output.')
elif '<lora:' in prompt: elif '<lora:' in prompt:
_prompt_stripped, loras = get_and_strip_loras(prompt, log_node=NODE_NAME, silent=True) _prompt_stripped, loras, skipped, unfound = get_and_strip_loras(prompt, log_node=NODE_NAME, silent=True)
if len(loras): total_loras = len(loras) + len(skipped) + len(unfound)
if total_loras:
log_node_warn( log_node_warn(
NODE_NAME, f'Found {len(loras)} lora tags in prompt but model & clip were not supplied!') NODE_NAME, f'Found {len(loras)} lora tags in prompt but model & clip were not supplied!')
log_node_info(NODE_NAME, 'Loras not processed, keeping for TEXT output.') log_node_info(NODE_NAME, 'Loras not processed, keeping for TEXT output.')
+8 -3
View File
@@ -16,14 +16,18 @@ def get_and_strip_loras(prompt, silent=False, log_node="Power Prompt"):
matches = re.findall(pattern, prompt) matches = re.findall(pattern, prompt)
loras = [] loras = []
unfound_loras = []
skipped_loras = []
for match in matches: for match in matches:
tag_path = match[0] tag_path = match[0]
tag_path_no_ext = os.path.splitext(match[0])[0] tag_path_no_ext = os.path.splitext(match[0])[0]
tag_filename_no_ext = os.path.splitext(os.path.basename(match[0]))[0] tag_filename_no_ext = os.path.splitext(os.path.basename(match[0]))[0]
strength = float(match[1] if len(match) > 1 and len(match[1]) else 1.0) strength = float(match[1] if len(match) > 1 and len(match[1]) else 1.0)
if strength == 0 and not silent: if strength == 0:
log_node_info(log_node, f'Skipping "{tag_path}" with strength of zero') if not silent:
log_node_info(log_node, f'Skipping "{tag_path}" with strength of zero')
skipped_loras.append({'lora': tag_path, 'strength': strength})
continue continue
if tag_path not in lora_paths: if tag_path not in lora_paths:
@@ -49,8 +53,9 @@ def get_and_strip_loras(prompt, silent=False, log_node="Power Prompt"):
else: else:
if not silent: if not silent:
log_node_warn(log_node, f'Lora "{tag_path}" not found, skipping.') log_node_warn(log_node, f'Lora "{tag_path}" not found, skipping.')
unfound_loras.append({'lora': tag_path, 'strength': strength})
continue continue
loras.append({'lora': tag_path, 'strength': strength}) loras.append({'lora': tag_path, 'strength': strength})
return (re.sub(pattern, '', prompt), loras) return (re.sub(pattern, '', prompt), loras, skipped_loras, unfound_loras)
+14 -11
View File
@@ -105,15 +105,15 @@ class RgthreeSDXLPowerPromptPositive:
values_insert_saved=None): values_insert_saved=None):
if insert_lora == 'DISABLE LORAS': if insert_lora == 'DISABLE LORAS':
prompt_g, loras_g = get_and_strip_loras(prompt_g, True) prompt_g, loras_g, skipped_g, unfound_g = get_and_strip_loras(prompt_g, True)
prompt_l, loras_l = get_and_strip_loras(prompt_l, True) prompt_l, loras_l, skipped_l, unfound_l = get_and_strip_loras(prompt_l, True)
loras = loras_g + loras_l loras = loras_g + loras_l
log_node_info( log_node_info(
NODE_NAME, NODE_NAME,
f'Disabling all found loras ({len(loras)}) and stripping lora tags for TEXT output.') f'Disabling all found loras ({len(loras)}) and stripping lora tags for TEXT output.')
elif opt_model != None and opt_clip != None: elif opt_model != None and opt_clip != None:
prompt_g, loras_g = get_and_strip_loras(prompt_g) prompt_g, loras_g, skipped_g, unfound_g = get_and_strip_loras(prompt_g)
prompt_l, loras_l = get_and_strip_loras(prompt_l) prompt_l, loras_l, skipped_l, unfound_l = get_and_strip_loras(prompt_l)
loras = loras_g + loras_l loras = loras_g + loras_l
if len(loras): if len(loras):
for lora in loras: for lora in loras:
@@ -122,13 +122,16 @@ class RgthreeSDXLPowerPromptPositive:
log_node_success(NODE_NAME, f'Loaded "{lora["lora"]}" from prompt') log_node_success(NODE_NAME, f'Loaded "{lora["lora"]}" from prompt')
log_node_info(NODE_NAME, f'{len(loras)} Loras processed; stripping tags for TEXT output.') log_node_info(NODE_NAME, f'{len(loras)} Loras processed; stripping tags for TEXT output.')
elif '<lora:' in prompt_g or '<lora:' in prompt_l: elif '<lora:' in prompt_g or '<lora:' in prompt_l:
_prompt_stripped_g, loras_g = get_and_strip_loras(prompt_g, True) _prompt_stripped_g, loras_g, skipped_g, unfound_g = get_and_strip_loras(prompt_g, True)
_prompt_stripped_l, loras_l = get_and_strip_loras(prompt_l, True) _prompt_stripped_l, loras_l, skipped_l, unfound_l = get_and_strip_loras(prompt_l, True)
loras = loras_g + loras_l loras = len(loras_g) + len(loras_l)
if len(loras): unfound_loras = len(unfound_g) + len(unfound_l)
log_node_warn( total_loras = loras + unfound_loras + len(skipped_g) + len(skipped_l)
NODE_NAME, f'Found {len(loras)} lora tags in prompt but model & clip were not supplied!') if total_loras:
log_node_info(NODE_NAME, 'Loras not processed, keeping for TEXT output.') log_node_warn(NODE_NAME, f'Found {total_loras} lora tags in prompt but model & clip were not supplied!')
if unfound_loras:
log_node_warn(NODE_NAME, f'Note: {unfound_loras} lora tags were not found!')
log_node_info(NODE_NAME, 'Loras not processed but NOT stripped. Keeping in case used for TEXT output.')
conditioning = self.get_conditioning(prompt_g, prompt_l, opt_clip, opt_clip_width, conditioning = self.get_conditioning(prompt_g, prompt_l, opt_clip, opt_clip_width,
opt_clip_height, target_width, target_height, crop_width, opt_clip_height, target_width, target_height, crop_width,
+11 -1
View File
@@ -1,4 +1,3 @@
class AnyType(str): class AnyType(str):
"""A special class that is always equal in not equal comparisons. Credit to pythongosssss""" """A special class that is always equal in not equal comparisons. Credit to pythongosssss"""
@@ -7,3 +6,14 @@ class AnyType(str):
any_type = AnyType("*") any_type = AnyType("*")
class ByPassTypeTuple(tuple):
"""A special class that will return additional "AnyType" strings beyond defined values.
Credit to Trung0246
"""
def __getitem__(self, index):
if index > len(self) - 1:
return AnyType("*")
return super().__getitem__(index)
+1 -1
View File
@@ -79,7 +79,7 @@ class AnySwitchforMixin extends RgthreeBaseNode {
output.label = output.label =
output.type === 'RGTHREE_CONTEXT' ? 'CONTEXT' : output.type === 'RGTHREE_CONTEXT' ? 'CONTEXT' :
Array.isArray(this.nodeType) || this.nodeType.includes(",") Array.isArray(this.nodeType) || this.nodeType.includes(",")
? connectedType?.label || String(this.nodeType) ? connectedType?.label || connectedType?.name || String(this.nodeType)
: String(this.nodeType); : String(this.nodeType);
} }
} }
+4
View File
@@ -34,6 +34,8 @@ export class RgthreeBaseNode extends LGraphNode {
/** Private Mode member so we can override the setter/getter and call an `onModeChange`. */ /** Private Mode member so we can override the setter/getter and call an `onModeChange`. */
private mode_: NodeMode; private mode_: NodeMode;
configuring = false;
isVirtualNode = false; isVirtualNode = false;
constructor(title = RgthreeBaseNode.title) { constructor(title = RgthreeBaseNode.title) {
@@ -45,12 +47,14 @@ export class RgthreeBaseNode extends LGraphNode {
} }
override configure(info: SerializedLGraphNode<TLGraphNode>): void { override configure(info: SerializedLGraphNode<TLGraphNode>): void {
this.configuring = true;
super.configure(info); super.configure(info);
// Fix https://github.com/comfyanonymous/ComfyUI/issues/1448 locally. // Fix https://github.com/comfyanonymous/ComfyUI/issues/1448 locally.
// Can removed when fixed and adopted. // Can removed when fixed and adopted.
for (const w of (this.widgets || [])) { for (const w of (this.widgets || [])) {
w.last_y = w.last_y || 0; w.last_y = w.last_y || 0;
} }
this.configuring = false;
} }
+34 -12
View File
@@ -2,8 +2,6 @@
import type { import type {
INodeInputSlot, INodeInputSlot,
INodeOutputSlot, INodeOutputSlot,
LGraph,
LLink,
LiteGraph as TLiteGraph, LiteGraph as TLiteGraph,
LGraphNode as TLGraphNode, LGraphNode as TLGraphNode,
} from "./typings/litegraph.js"; } from "./typings/litegraph.js";
@@ -14,7 +12,6 @@ import {
IoDirection, IoDirection,
addConnectionLayoutSupport, addConnectionLayoutSupport,
addMenuItem, addMenuItem,
applyMixins,
matchLocalSlotsToServer, matchLocalSlotsToServer,
replaceNode, replaceNode,
} from "./utils.js"; } from "./utils.js";
@@ -36,10 +33,12 @@ function findMatchingIndexByTypeOrName(otherNode: TLGraphNode, otherSlot: INodeI
otherSlotType = 'COMBO'; otherSlotType = 'COMBO';
} }
const otherSlotName = otherSlot.name.toUpperCase().replace('OPT_', '').replace('_NAME', ''); const otherSlotName = otherSlot.name.toUpperCase().replace('OPT_', '').replace('_NAME', '');
const otherSlotLabel = (otherSlot.label || otherSlotName).toUpperCase().replace('OPT_', '').replace('_NAME', '');
let ctxSlotIndex = -1; let ctxSlotIndex = -1;
if (["CONDITIONING", "INT", "STRING", "FLOAT", "COMBO"].includes(otherSlotType)) { if (["CONDITIONING", "INT", "STRING", "FLOAT", "COMBO"].includes(otherSlotType)) {
ctxSlotIndex = ctxSlots.findIndex((ctxSlot) => { ctxSlotIndex = ctxSlots.findIndex((ctxSlot) => {
const ctxSlotName = ctxSlot.name.toUpperCase().replace('OPT_', '').replace('_NAME', ''); const ctxSlotName = ctxSlot.name.toUpperCase().replace('OPT_', '').replace('_NAME', '');
const ctxSlotLabel = (ctxSlot.label || ctxSlotName).toUpperCase().replace('OPT_', '').replace('_NAME', '');
let ctxSlotType = ctxSlot.type as string; let ctxSlotType = ctxSlot.type as string;
if (Array.isArray(ctxSlotType) || ctxSlotType.includes(',')) { if (Array.isArray(ctxSlotType) || ctxSlotType.includes(',')) {
ctxSlotType = 'COMBO'; ctxSlotType = 'COMBO';
@@ -49,6 +48,7 @@ function findMatchingIndexByTypeOrName(otherNode: TLGraphNode, otherSlot: INodeI
} }
// Straightforward matches. // Straightforward matches.
if(ctxSlotName === otherSlotName if(ctxSlotName === otherSlotName
|| (ctxSlotLabel && otherSlotLabel && ctxSlotLabel == otherSlotLabel)
|| (ctxSlotName === "SEED" && otherSlotName.includes("SEED")) || (ctxSlotName === "SEED" && otherSlotName.includes("SEED"))
|| (ctxSlotName === "STEP_REFINER" && otherSlotName.includes("AT_STEP")) || (ctxSlotName === "STEP_REFINER" && otherSlotName.includes("AT_STEP"))
|| (ctxSlotName === "STEP_REFINER" && otherSlotName.includes("REFINER_STEP"))) { || (ctxSlotName === "STEP_REFINER" && otherSlotName.includes("REFINER_STEP"))) {
@@ -85,7 +85,7 @@ function findMatchingIndexByTypeOrName(otherNode: TLGraphNode, otherSlot: INodeI
/** /**
* A Base Context node for other context based nodes to extend. * A Base Context node for other context based nodes to extend.
*/ */
class BaseContextNode extends RgthreeBaseServerNode { export class BaseContextNode extends RgthreeBaseServerNode {
constructor(title: string) { constructor(title: string) {
super(title); super(title);
} }
@@ -261,6 +261,24 @@ class ContextSwitchBigNode extends BaseContextNode {
} }
} }
function addWidgetForDynamicContextOutputs(node: TLGraphNode, inputName: string) {
node.addCustomWidget({
name: inputName,
value: '',
draw(ctx, node, width, posY, height) {
return;
},
computeSize(width) {
return [0,0];
},
serializeValue() {
const value = (node.outputs || []).map((o, i) => i > 0 && o.name).filter(n => n !== false).join(',');
return value;
}
});
}
const contextNodes = [ContextNode, ContextBigNode, ContextSwitchNode, ContextSwitchBigNode]; const contextNodes = [ContextNode, ContextBigNode, ContextSwitchNode, ContextSwitchBigNode];
const contextTypeToServerDef: { [type: string]: ComfyObjectInfo } = {}; const contextTypeToServerDef: { [type: string]: ComfyObjectInfo } = {};
@@ -296,10 +314,12 @@ app.registerExtension({
const serverDef = type && contextTypeToServerDef[type]; const serverDef = type && contextTypeToServerDef[type];
if (serverDef) { if (serverDef) {
fixBadConfigs(node as ContextNode); fixBadConfigs(node as ContextNode);
matchLocalSlotsToServer(node, IoDirection.OUTPUT, serverDef); if (!type!.includes('Dynamic')) {
// Switches don't need to change inputs, only context outputs matchLocalSlotsToServer(node, IoDirection.OUTPUT, serverDef);
if (!type!.includes("Switch")) { // Switches don't need to change inputs, only context outputs
matchLocalSlotsToServer(node, IoDirection.INPUT, serverDef); if (!type!.includes("Switch")) {
matchLocalSlotsToServer(node, IoDirection.INPUT, serverDef);
}
} }
// }, 100); // }, 100);
} }
@@ -314,10 +334,12 @@ app.registerExtension({
const serverDef = type && contextTypeToServerDef[type]; const serverDef = type && contextTypeToServerDef[type];
if (serverDef) { if (serverDef) {
fixBadConfigs(node as ContextNode); fixBadConfigs(node as ContextNode);
matchLocalSlotsToServer(node, IoDirection.OUTPUT, serverDef); if (!type!.includes('Dynamic')) {
// Switches don't need to change inputs, only context outputs matchLocalSlotsToServer(node, IoDirection.OUTPUT, serverDef);
if (!type!.includes("Switch")) { // Switches don't need to change inputs, only context outputs
matchLocalSlotsToServer(node, IoDirection.INPUT, serverDef); if (!type!.includes("Switch")) {
matchLocalSlotsToServer(node, IoDirection.INPUT, serverDef);
}
} }
} }
}, },
File diff suppressed because it is too large Load Diff
+30 -20
View File
@@ -21,6 +21,7 @@ import {
LAYOUT_LABEL_TO_DATA, LAYOUT_LABEL_TO_DATA,
addConnectionLayoutSupport, addConnectionLayoutSupport,
addMenuItem, addMenuItem,
getConnectedOutputNodesAndFilterPassThroughs,
getSlotLinks, getSlotLinks,
isValidConnection, isValidConnection,
} from "./utils.js"; } from "./utils.js";
@@ -71,7 +72,6 @@ app.registerExtension({
readonly hideSlotLabels: boolean; readonly hideSlotLabels: boolean;
private configuring = true; private configuring = true;
private schedulePromise: Promise<void> | null = null;
defaultConnectionsLayout = configLayout; defaultConnectionsLayout = configLayout;
@@ -137,7 +137,26 @@ app.registerExtension({
} }
} }
} }
this.scheduleStabilize(); if (this.configuring) {
return;
}
this.stabilize();
if (type === LiteGraph.INPUT) {
this.updateDownstream(connected ? 'connect' : 'disconnect', {index: 0, name: this.inputs[0]!.name});
}
}
/**
* Updates connected nodes of a change (checking and calling updateFromUpstream). Used for Dynamic Context.
*/
private updateDownstream(
update: "connect" | "disconnect" | "move" | "update",
updatedIndexes: {index: number; name: string; from?: number},
) {
const nodes = getConnectedOutputNodesAndFilterPassThroughs(this, this, 0);
for (const node of nodes) {
(node as any)?.updateFromUpstream?.(update, this, updatedIndexes);
}
} }
override onDrawForeground(ctx: CanvasRenderingContext2D, canvas: TLGraphCanvas): void { override onDrawForeground(ctx: CanvasRenderingContext2D, canvas: TLGraphCanvas): void {
@@ -161,20 +180,6 @@ app.registerExtension({
return super.disconnectOutput(slot, targetNode); return super.disconnectOutput(slot, targetNode);
} }
scheduleStabilize(ms = 64) {
if (!this.schedulePromise) {
this.schedulePromise = new Promise((resolve) => {
setTimeout(() => {
this.schedulePromise = null
this.stabilize();
resolve();
}, ms);
});
}
return this.schedulePromise;
}
stabilize() { stabilize() {
// If we are currently "configuring" then skip this stabilization. The connected nodes may // If we are currently "configuring" then skip this stabilization. The connected nodes may
// not yet be configured. // not yet be configured.
@@ -229,7 +234,7 @@ app.registerExtension({
// Find all outputs // Find all outputs
const nodes: TLGraphNode[] = [this]; const nodes: TLGraphNode[] = [this];
let outputNode = null; let outputNodes: TLGraphNode[] = [];
let outputType = null; let outputType = null;
while (nodes.length) { while (nodes.length) {
currentNode = nodes.pop()!; currentNode = nodes.pop()!;
@@ -266,8 +271,11 @@ app.registerExtension({
console.warn(`[rgthree] Reroute - Disconnecting connected node's input (${node.id}.${link.target_slot}) (${node.type}) because its type (${String(nodeOutType)}) does not match the reroute type (${String(inputType)})`); console.warn(`[rgthree] Reroute - Disconnecting connected node's input (${node.id}.${link.target_slot}) (${node.type}) because its type (${String(nodeOutType)}) does not match the reroute type (${String(inputType)})`);
node.disconnectInput(link.target_slot); node.disconnectInput(link.target_slot);
} else { } else {
if (outputType != null && outputType !== nodeOutType) {
console.warn(`[rgthree] Reroute - Mismatching output types..`);
}
outputType = nodeOutType; outputType = nodeOutType;
outputNode = node; outputNodes.push(node);
} }
} }
} }
@@ -306,8 +314,10 @@ app.registerExtension({
} }
} }
} }
(inputNode as any)?.onConnectionsChainChange?.(); // (inputNode as any)?.onConnectionsChainChange?.(this);
(outputNode as any)?.onConnectionsChainChange?.(); // for (const outputNode of outputNodes) {
// (outputNode as any)?.onConnectionsChainChange?.(this);
// }
app.graph.setDirtyCanvas(true, true); app.graph.setDirtyCanvas(true, true);
} }
+3 -3
View File
@@ -77,7 +77,7 @@ class LogSession {
export type RgthreeUiMessage = { export type RgthreeUiMessage = {
id: string; id: string;
message: string; message: string;
type?: 'warn'|null; type?: 'warn'|'info'|null;
timeout?: number; timeout?: number;
// closeable?: boolean; // TODO // closeable?: boolean; // TODO
actions?: Array<{ actions?: Array<{
@@ -305,8 +305,8 @@ class Rgthree {
fireEvent(event: string, data: any) { fireEvent(event: string, data: any) {
if (this.eventsToFns.has(event)) { if (this.eventsToFns.has(event)) {
for (let fn of this.eventsToFns.get(event)!) { for (let fn of this.eventsToFns.get(event)!) {
const event = new Event(data); const e = new CustomEvent(event, {detail:data});
fn(event); fn(e);
} }
} }
} }
+4 -1
View File
@@ -38,6 +38,8 @@ export interface INodeSlot {
hidden?: boolean; hidden?: boolean;
// @rgthree // @rgthree
disabled?: boolean; disabled?: boolean;
// @rgthree
removable?: boolean;
} }
export interface INodeInputSlot extends INodeSlot { export interface INodeInputSlot extends INodeSlot {
@@ -1087,7 +1089,8 @@ export declare class LGraphNode {
/** Called by `LGraphCanvas.processContextMenu` */ /** Called by `LGraphCanvas.processContextMenu` */
getMenuOptions?(graphCanvas: LGraphCanvas): ContextMenuItem[]; getMenuOptions?(graphCanvas: LGraphCanvas): ContextMenuItem[];
getSlotMenuOptions?(slot: INodeSlot): ContextMenuItem[]; // rgthree
getSlotMenuOptions?(slotItem: {slot: number, input?: INodeInputSlot, output?: INodeOutputSlot, link_pos: Vector2}): ContextMenuItem[];
getExtraMenuOptions?(canvas: LGraphCanvas, options: ContextMenuItem[]): void; getExtraMenuOptions?(canvas: LGraphCanvas, options: ContextMenuItem[]): void;
} }
+34 -22
View File
@@ -400,7 +400,7 @@ export function getConnectedInputNodes(
slot?: number, slot?: number,
passThroughFollowing = PassThroughFollowing.ALL, passThroughFollowing = PassThroughFollowing.ALL,
) : TLGraphNode[] { ) : TLGraphNode[] {
return getConnectedNodes(startNode, IoDirection.INPUT, currentNode, slot, passThroughFollowing).map(n => n.node); return getConnectedNodesInfo(startNode, IoDirection.INPUT, currentNode, slot, passThroughFollowing).map(n => n.node);
} }
export function getConnectedInputNodesAndFilterPassThroughs( export function getConnectedInputNodesAndFilterPassThroughs(
startNode: TLGraphNode, startNode: TLGraphNode,
@@ -413,14 +413,16 @@ export function getConnectedInputNodesAndFilterPassThroughs(
passThroughFollowing, passThroughFollowing,
); );
} }
export function getConnectedOutputNodes( export function getConnectedOutputNodes(
startNode: TLGraphNode, startNode: TLGraphNode,
currentNode?: TLGraphNode, currentNode?: TLGraphNode,
slot?: number, slot?: number,
passThroughFollowing = PassThroughFollowing.ALL, passThroughFollowing = PassThroughFollowing.ALL,
) : TLGraphNode[] { ) : TLGraphNode[] {
return getConnectedNodes(startNode, IoDirection.OUTPUT, currentNode, slot, passThroughFollowing).map(n => n.node); return getConnectedNodesInfo(startNode, IoDirection.OUTPUT, currentNode, slot, passThroughFollowing).map(n => n.node);
} }
export function getConnectedOutputNodesAndFilterPassThroughs( export function getConnectedOutputNodesAndFilterPassThroughs(
startNode: TLGraphNode, startNode: TLGraphNode,
currentNode?: TLGraphNode, currentNode?: TLGraphNode,
@@ -434,39 +436,48 @@ export function getConnectedOutputNodesAndFilterPassThroughs(
} }
export function getConnectedNodes( export type ConnectedNodeInfo = {node: TLGraphNode, travelFromSlot: number, travelToSlot: number, originTravelFromSlot: number};
export function getConnectedNodesInfo(
startNode: TLGraphNode, startNode: TLGraphNode,
dir = IoDirection.INPUT, dir = IoDirection.INPUT,
currentNode?: TLGraphNode, currentNode?: TLGraphNode,
slot?: number, slot?: number,
passThroughFollowing = PassThroughFollowing.ALL, passThroughFollowing = PassThroughFollowing.ALL,
) : {node:TLGraphNode, slot: number}[] { originTravelFromSlot?: number
) : ConnectedNodeInfo[] {
currentNode = currentNode || startNode; currentNode = currentNode || startNode;
let rootNodes: {node:TLGraphNode, slot: number}[] = []; let rootNodes: ConnectedNodeInfo[] = [];
const slotsToRemove = []; const slotsToRemove = [];
if (startNode === currentNode || shouldPassThrough(currentNode, passThroughFollowing)) { if (startNode === currentNode || shouldPassThrough(currentNode, passThroughFollowing)) {
// const removeDups = startNode === currentNode;
let linkIds: Array<number | null>; let linkIds: Array<number | null>;
if (dir == IoDirection.OUTPUT) { if (dir == IoDirection.OUTPUT) {
linkIds = currentNode.outputs?.flatMap((i) => i.links) || []; if (slot != null && slot > -1) {
} else { linkIds = currentNode.outputs[slot]?.links ?? [];
linkIds = currentNode.inputs?.map((i) => i.link) || [];
}
if (typeof slot == "number" && slot > -1) {
if (linkIds[slot]) {
linkIds = [linkIds[slot]!];
} else { } else {
return []; linkIds = currentNode.outputs?.flatMap((i) => i.links) || [];
}
} else {
if (slot != null && slot > -1) {
linkIds = [currentNode.inputs[slot]?.link ?? -1].filter(i => i > -1);
} else {
linkIds = currentNode.inputs?.map((i) => i.link) || [];
} }
} }
let graph = app.graph as LGraph; let graph = app.graph as LGraph;
for (const linkId of linkIds) { for (const linkId of linkIds) {
const link: LLink = (linkId != null && graph.links[linkId]) as LLink; let link: LLink | null = null;
if (typeof linkId == "number") {
link = graph.links[linkId] as LLink;
}
if (!link) { if (!link) {
continue; continue;
} }
const travelFromSlot = dir == IoDirection.OUTPUT ? link.origin_slot : link.target_slot;
const connectedId = dir == IoDirection.OUTPUT ? link.target_id : link.origin_id; const connectedId = dir == IoDirection.OUTPUT ? link.target_id : link.origin_id;
const originSlot = dir == IoDirection.OUTPUT ? link.target_slot : link.origin_slot; const travelToSlot = dir == IoDirection.OUTPUT ? link.target_slot : link.origin_slot;
originTravelFromSlot = originTravelFromSlot != null ? originTravelFromSlot : travelFromSlot;
const originNode: TLGraphNode = graph.getNodeById(connectedId)!; const originNode: TLGraphNode = graph.getNodeById(connectedId)!;
if (!link) { if (!link) {
console.error("No connected node found... weird"); console.error("No connected node found... weird");
@@ -480,10 +491,10 @@ export function getConnectedNodes(
); );
} else { } else {
// Add the node and, if it's a pass through, let's collect all its nodes as well. // Add the node and, if it's a pass through, let's collect all its nodes as well.
rootNodes.push({node: originNode, slot: originSlot}); rootNodes.push({node: originNode, travelFromSlot, travelToSlot, originTravelFromSlot});
if (shouldPassThrough(originNode, passThroughFollowing)) { if (shouldPassThrough(originNode, passThroughFollowing)) {
for (const foundNode of getConnectedNodes(startNode, dir, originNode)) { for (const foundNode of getConnectedNodesInfo(startNode, dir, originNode, undefined, undefined, originTravelFromSlot)) {
if (!rootNodes.includes(foundNode)) { if (!rootNodes.map(n => n.node).includes(foundNode.node)) {
rootNodes.push(foundNode); rootNodes.push(foundNode);
} }
} }
@@ -494,7 +505,7 @@ export function getConnectedNodes(
return rootNodes; return rootNodes;
} }
type ConnectionType = { type: string | string[]; label: string | undefined }; export type ConnectionType = { type: string | string[]; name: string | undefined; label: string | undefined; };
/** /**
* Follows a connection until we find a type associated with a slot. * Follows a connection until we find a type associated with a slot.
@@ -539,7 +550,7 @@ function getTypeFromSlot(
let graph = app.graph as LGraph; let graph = app.graph as LGraph;
let type = slot?.type; let type = slot?.type;
if (!skipSelf && type != null && type != "*") { if (!skipSelf && type != null && type != "*") {
return { type: type as string, label: slot?.label || slot?.name }; return { type: type as string, label: slot?.label, name: slot?.name};
} }
const links = getSlotLinks(slot); const links = getSlotLinks(slot);
for (const link of links) { for (const link of links) {
@@ -554,7 +565,8 @@ function getTypeFromSlot(
if (connectedSlot?.type != null && connectedSlot?.type != "*") { if (connectedSlot?.type != null && connectedSlot?.type != "*") {
return { return {
type: connectedSlot.type as string, type: connectedSlot.type as string,
label: connectedSlot?.label || connectedSlot?.name, label: connectedSlot?.label,
name: connectedSlot?.name
}; };
} else if (connectedSlot?.type == "*") { } else if (connectedSlot?.type == "*") {
return followConnectionUntilType(connectedNode, dir); return followConnectionUntilType(connectedNode, dir);
+1 -1
View File
@@ -42,7 +42,7 @@ class AnySwitchforMixin extends RgthreeBaseNode {
output.label = output.label =
output.type === 'RGTHREE_CONTEXT' ? 'CONTEXT' : output.type === 'RGTHREE_CONTEXT' ? 'CONTEXT' :
Array.isArray(this.nodeType) || this.nodeType.includes(",") Array.isArray(this.nodeType) || this.nodeType.includes(",")
? (connectedType === null || connectedType === void 0 ? void 0 : connectedType.label) || String(this.nodeType) ? (connectedType === null || connectedType === void 0 ? void 0 : connectedType.label) || (connectedType === null || connectedType === void 0 ? void 0 : connectedType.name) || String(this.nodeType)
: String(this.nodeType); : String(this.nodeType);
} }
} }
+3
View File
@@ -5,6 +5,7 @@ export class RgthreeBaseNode extends LGraphNode {
constructor(title = RgthreeBaseNode.title) { constructor(title = RgthreeBaseNode.title) {
super(title); super(title);
this._tempWidth = 0; this._tempWidth = 0;
this.configuring = false;
this.isVirtualNode = false; this.isVirtualNode = false;
if (title == '__NEED_NAME__') { if (title == '__NEED_NAME__') {
throw new Error('RgthreeBaseNode needs overrides.'); throw new Error('RgthreeBaseNode needs overrides.');
@@ -12,10 +13,12 @@ export class RgthreeBaseNode extends LGraphNode {
this.properties = this.properties || {}; this.properties = this.properties || {};
} }
configure(info) { configure(info) {
this.configuring = true;
super.configure(info); super.configure(info);
for (const w of (this.widgets || [])) { for (const w of (this.widgets || [])) {
w.last_y = w.last_y || 0; w.last_y = w.last_y || 0;
} }
this.configuring = false;
} }
set mode(mode) { set mode(mode) {
if (this.mode_ != mode) { if (this.mode_ != mode) {
+30 -7
View File
@@ -10,10 +10,12 @@ function findMatchingIndexByTypeOrName(otherNode, otherSlot, ctxSlots) {
otherSlotType = 'COMBO'; otherSlotType = 'COMBO';
} }
const otherSlotName = otherSlot.name.toUpperCase().replace('OPT_', '').replace('_NAME', ''); const otherSlotName = otherSlot.name.toUpperCase().replace('OPT_', '').replace('_NAME', '');
const otherSlotLabel = (otherSlot.label || otherSlotName).toUpperCase().replace('OPT_', '').replace('_NAME', '');
let ctxSlotIndex = -1; let ctxSlotIndex = -1;
if (["CONDITIONING", "INT", "STRING", "FLOAT", "COMBO"].includes(otherSlotType)) { if (["CONDITIONING", "INT", "STRING", "FLOAT", "COMBO"].includes(otherSlotType)) {
ctxSlotIndex = ctxSlots.findIndex((ctxSlot) => { ctxSlotIndex = ctxSlots.findIndex((ctxSlot) => {
const ctxSlotName = ctxSlot.name.toUpperCase().replace('OPT_', '').replace('_NAME', ''); const ctxSlotName = ctxSlot.name.toUpperCase().replace('OPT_', '').replace('_NAME', '');
const ctxSlotLabel = (ctxSlot.label || ctxSlotName).toUpperCase().replace('OPT_', '').replace('_NAME', '');
let ctxSlotType = ctxSlot.type; let ctxSlotType = ctxSlot.type;
if (Array.isArray(ctxSlotType) || ctxSlotType.includes(',')) { if (Array.isArray(ctxSlotType) || ctxSlotType.includes(',')) {
ctxSlotType = 'COMBO'; ctxSlotType = 'COMBO';
@@ -22,6 +24,7 @@ function findMatchingIndexByTypeOrName(otherNode, otherSlot, ctxSlots) {
return false; return false;
} }
if (ctxSlotName === otherSlotName if (ctxSlotName === otherSlotName
|| (ctxSlotLabel && otherSlotLabel && ctxSlotLabel == otherSlotLabel)
|| (ctxSlotName === "SEED" && otherSlotName.includes("SEED")) || (ctxSlotName === "SEED" && otherSlotName.includes("SEED"))
|| (ctxSlotName === "STEP_REFINER" && otherSlotName.includes("AT_STEP")) || (ctxSlotName === "STEP_REFINER" && otherSlotName.includes("AT_STEP"))
|| (ctxSlotName === "STEP_REFINER" && otherSlotName.includes("REFINER_STEP"))) { || (ctxSlotName === "STEP_REFINER" && otherSlotName.includes("REFINER_STEP"))) {
@@ -47,7 +50,7 @@ function findMatchingIndexByTypeOrName(otherNode, otherSlot, ctxSlots) {
} }
return ctxSlotIndex; return ctxSlotIndex;
} }
class BaseContextNode extends RgthreeBaseServerNode { export class BaseContextNode extends RgthreeBaseServerNode {
constructor(title) { constructor(title) {
super(title); super(title);
} }
@@ -171,6 +174,22 @@ class ContextSwitchBigNode extends BaseContextNode {
ContextSwitchBigNode.title = "Context Switch Big (rgthree)"; ContextSwitchBigNode.title = "Context Switch Big (rgthree)";
ContextSwitchBigNode.type = "Context Switch Big (rgthree)"; ContextSwitchBigNode.type = "Context Switch Big (rgthree)";
ContextSwitchBigNode.comfyClass = "Context Switch Big (rgthree)"; ContextSwitchBigNode.comfyClass = "Context Switch Big (rgthree)";
function addWidgetForDynamicContextOutputs(node, inputName) {
node.addCustomWidget({
name: inputName,
value: '',
draw(ctx, node, width, posY, height) {
return;
},
computeSize(width) {
return [0, 0];
},
serializeValue() {
const value = (node.outputs || []).map((o, i) => i > 0 && o.name).filter(n => n !== false).join(',');
return value;
}
});
}
const contextNodes = [ContextNode, ContextBigNode, ContextSwitchNode, ContextSwitchBigNode]; const contextNodes = [ContextNode, ContextBigNode, ContextSwitchNode, ContextSwitchBigNode];
const contextTypeToServerDef = {}; const contextTypeToServerDef = {};
function fixBadConfigs(node) { function fixBadConfigs(node) {
@@ -199,9 +218,11 @@ app.registerExtension({
const serverDef = type && contextTypeToServerDef[type]; const serverDef = type && contextTypeToServerDef[type];
if (serverDef) { if (serverDef) {
fixBadConfigs(node); fixBadConfigs(node);
matchLocalSlotsToServer(node, IoDirection.OUTPUT, serverDef); if (!type.includes('Dynamic')) {
if (!type.includes("Switch")) { matchLocalSlotsToServer(node, IoDirection.OUTPUT, serverDef);
matchLocalSlotsToServer(node, IoDirection.INPUT, serverDef); if (!type.includes("Switch")) {
matchLocalSlotsToServer(node, IoDirection.INPUT, serverDef);
}
} }
} }
}, },
@@ -210,9 +231,11 @@ app.registerExtension({
const serverDef = type && contextTypeToServerDef[type]; const serverDef = type && contextTypeToServerDef[type];
if (serverDef) { if (serverDef) {
fixBadConfigs(node); fixBadConfigs(node);
matchLocalSlotsToServer(node, IoDirection.OUTPUT, serverDef); if (!type.includes('Dynamic')) {
if (!type.includes("Switch")) { matchLocalSlotsToServer(node, IoDirection.OUTPUT, serverDef);
matchLocalSlotsToServer(node, IoDirection.INPUT, serverDef); if (!type.includes("Switch")) {
matchLocalSlotsToServer(node, IoDirection.INPUT, serverDef);
}
} }
} }
}, },
+745
View File
@@ -0,0 +1,745 @@
import { app } from "../../scripts/app.js";
import { IoDirection, PassThroughFollowing, followConnectionUntilType, getConnectedInputNodes, getConnectedInputNodesAndFilterPassThroughs, getConnectedNodesInfo, getConnectedOutputNodesAndFilterPassThroughs, shouldPassThrough, } from "./utils.js";
import { rgthree } from "./rgthree.js";
import { BaseContextNode } from "./context.js";
class ContextDynamicNodeBase extends BaseContextNode {
constructor() {
super(...arguments);
this.hasShadowInputs = false;
}
getContextInputsList() {
return this.inputs;
}
onConnectionsChainChange(arg) {
console.log("ContextDynamicNodeBase: onConnectionsChainChange", this.id, arg);
}
onNodeCreated() {
const inputs = this.getContextInputsList();
if (inputs[inputs.length - 1].type === "*") {
this.removeOutput(inputs.length - 1);
}
else {
this.addInput("+", "*");
}
}
getWidgets() {
return Object.assign({}, super.getWidgets(), {
DYNAMIC_CONTEXT_OUTPUTS: (node, inputName, inputData, app) => {
node.addCustomWidget({
name: inputName,
value: "",
draw(ctx, node, width, posY, height) {
return;
},
computeSize(width) {
return [0, 0];
},
serializeValue() {
const value = (node.outputs || [])
.map((o, i) => i > 0 && o.name)
.filter((n) => n !== false)
.join(",");
return value;
},
});
},
});
}
stripOwnedPrefix(name) {
return name.replace(/^\+\s*/, "");
}
addOwnedPrefix(name) {
return `+ ${this.stripOwnedPrefix(name)}`;
}
isOwnedInput(inputOrName) {
const name = typeof inputOrName == "string" ? inputOrName : (inputOrName === null || inputOrName === void 0 ? void 0 : inputOrName.name) || "";
return name.startsWith("+ ") || name === "+";
}
getNextUniqueNameForThisNode(desiredName) {
const inputs = this.getContextInputsList();
const allExistingKeys = inputs.map((i) => this.stripOwnedPrefix(i.name).toLocaleUpperCase());
desiredName = this.stripOwnedPrefix(desiredName);
let newName = desiredName;
let n = 0;
while (allExistingKeys.includes(newName.toLocaleUpperCase())) {
newName = `${desiredName}.${++n}`;
}
return newName;
}
onConnectionsChange(type, slotIndex, isConnected, linkInfo, ioSlot) {
var _a;
(_a = super.onConnectionsChange) === null || _a === void 0 ? void 0 : _a.call(this, type, slotIndex, isConnected, linkInfo, ioSlot);
if (this.configuring) {
return;
}
if (type === LiteGraph.INPUT) {
if (isConnected) {
this.handleInputConnected(slotIndex);
}
else {
this.handleInputDisconnected(slotIndex);
}
}
}
handleInputConnected(slotIndex) {
}
handleInputDisconnected(slotIndex) {
}
updateFromUpstream(update, node, updatedSlotData) {
}
provideInputsData() {
const inputs = this.getContextInputsList();
return inputs
.map((input, index) => ({
name: this.stripOwnedPrefix(input.name),
type: String(input.type),
index,
}))
.filter((i) => i.type !== "*");
}
updateDownstream(update, updatedSlotData) {
var _a;
const nodes = getConnectedOutputNodesAndFilterPassThroughs(this, this, 0);
for (const node of nodes) {
(_a = node === null || node === void 0 ? void 0 : node.updateFromUpstream) === null || _a === void 0 ? void 0 : _a.call(node, update, this, updatedSlotData);
}
}
addContextInput(name, type, slot = -1) {
const inputs = this.getContextInputsList();
if (this.hasShadowInputs) {
inputs.push({ name, type });
}
else {
this.addInput(name, type);
}
if (slot > -1) {
inputs.splice(slot, 0, inputs.splice(inputs.length - 1, 1)[0]);
}
else {
slot = inputs.length - 1;
}
if (type !== "*") {
const output = this.addOutput(name.toUpperCase(), type);
if (type === "COMBO" || String(type).includes(",") || Array.isArray(type)) {
output.widget = true;
}
if (slot > -1) {
this.outputs.splice(slot, 0, this.outputs.splice(this.outputs.length - 1, 1)[0]);
}
this.fixInputsOutputsLinkSlots();
this.updateDownstream("connect", { index: slot, name });
}
}
removeContextInput(slot) {
if (this.hasShadowInputs) {
const inputs = this.getContextInputsList();
const input = inputs.splice(slot, 1)[0];
if (this.outputs[slot]) {
this.removeOutput(slot);
}
this.updateDownstream("disconnect", { index: slot, name: input.name });
this.fixInputsOutputsLinkSlots();
}
else {
this.removeInput(slot);
}
}
moveContextInput(slotFrom, slotTo) {
const inputs = this.getContextInputsList();
if (slotTo === 'bottom') {
slotTo = inputs.length - 1;
}
if (slotFrom === slotTo) {
return;
}
let newIndex = slotTo + (slotFrom < slotTo ? -1 : 0);
const input = inputs.splice(slotFrom, 1)[0];
inputs.splice(newIndex, 0, input);
this.outputs.splice(newIndex, 0, ...this.outputs.splice(slotFrom, 1));
this.fixInputsOutputsLinkSlots();
this.updateDownstream("move", { index: slotTo, from: slotFrom, name: input.name });
}
renameContextInput(index, newName, forceOwnBool = null) {
const inputs = this.getContextInputsList();
const input = inputs[index];
const oldName = input.name;
newName = this.stripOwnedPrefix(newName.trim() || this.getSlotDefaultInputLabel(index));
if (forceOwnBool === true || (this.isOwnedInput(oldName) && forceOwnBool !== false)) {
newName = this.addOwnedPrefix(newName);
}
input.name = newName;
this.outputs[index].name = this.stripOwnedPrefix(inputs[index].name).toUpperCase();
this.updateDownstream("update", { index, name: newName });
}
fixInputsOutputsLinkSlots() {
if (!this.hasShadowInputs) {
const inputs = this.inputs;
for (let index = inputs.length - 1; index > 0; index--) {
const input = inputs[index];
if ((input === null || input === void 0 ? void 0 : input.link) != null) {
app.graph.links[input.link].target_slot = index;
}
}
}
const outputs = this.outputs;
for (let index = outputs.length - 1; index > 0; index--) {
const output = outputs[index];
for (const link of (output === null || output === void 0 ? void 0 : output.links) || []) {
app.graph.links[link].origin_slot = index;
}
}
}
getSlotDefaultInputLabel(slot) {
const inputs = this.getContextInputsList();
const input = inputs[slot];
let defaultLabel = this.stripOwnedPrefix(input.name).toLowerCase();
return defaultLabel.toLocaleLowerCase();
}
}
ContextDynamicNodeBase.logger = rgthree.newLogSession("[Dynamic Context]");
class ContextDynamicNode extends ContextDynamicNodeBase {
static setUp(comfyClass) {
BaseContextNode.setUp(comfyClass, ContextDynamicNode);
}
constructor(title = ContextDynamicNode.title) {
super(title);
}
clone() {
const cloned = super.clone();
while (cloned.inputs.length > 1) {
cloned.removeInput(cloned.inputs.length - 1);
}
cloned.addInput("+", "*");
return cloned;
}
removeInput(slot) {
const input = this.inputs[slot];
super.removeInput(slot);
if (this.outputs[slot]) {
this.removeOutput(slot);
}
this.fixInputsOutputsLinkSlots();
this.updateDownstream("disconnect", { index: slot, name: input.name });
this.stabilizeNames();
}
handleInputConnected(slotIndex) {
const inputs = this.getContextInputsList();
const ioSlot = inputs[slotIndex];
if (slotIndex === 0) {
const baseNodes = getConnectedInputNodesAndFilterPassThroughs(this, this, 0);
const baseNodesDynamicCtx = baseNodes[0];
if (baseNodesDynamicCtx === null || baseNodesDynamicCtx === void 0 ? void 0 : baseNodesDynamicCtx.provideInputsData) {
for (const input of baseNodesDynamicCtx.provideInputsData()) {
const inputs = this.getContextInputsList();
if (input.name === "base_ctx" || input.name === "+") {
continue;
}
const foundIndex = inputs.findIndex(i => this.stripOwnedPrefix(i.name) === input.name);
if (foundIndex > -1) {
this.moveContextInput(foundIndex, input.index);
this.renameContextInput(input.index, input.name, false);
}
else {
this.addContextInput(input.name, input.type, input.index);
this.stabilizeNames();
}
}
}
}
else if (ioSlot.type === "*") {
let cxn = null;
if (ioSlot.link) {
cxn = followConnectionUntilType(this, IoDirection.INPUT, slotIndex, true);
}
if (cxn === null || cxn === void 0 ? void 0 : cxn.type) {
let name = cxn.name;
if (name.match(/^(\+\s*)?[A-Z_]+$/)) {
name = name.toLowerCase();
}
name = this.getNextUniqueNameForThisNode(name);
if (!this.outputs[slotIndex]) {
this.addOutput("*", "*");
}
inputs[slotIndex].type = cxn.type;
inputs[slotIndex].removable = true;
this.outputs[slotIndex].type = cxn.type;
this.updateDownstream("connect", { index: slotIndex, name: this.stripOwnedPrefix(name) });
this.renameContextInput(slotIndex, name, true);
if (cxn.type === "COMBO" || cxn.type.includes(",") || Array.isArray(cxn.type)) {
this.outputs[slotIndex].widget = true;
}
this.addInput("+", "*");
}
}
}
handleInputDisconnected(slotIndex) {
var _a, _b;
const inputs = this.getContextInputsList();
if (slotIndex === 0) {
for (let index = inputs.length - 1; index > 0; index--) {
if (index === 0 || index === inputs.length - 1) {
continue;
}
const input = inputs[index];
if (!this.isOwnedInput(input.name)) {
if (input.link || ((_b = (_a = this.outputs[index]) === null || _a === void 0 ? void 0 : _a.links) === null || _b === void 0 ? void 0 : _b.length)) {
this.renameContextInput(index, input.name, true);
}
else {
this.removeContextInput(index);
}
}
}
this.setSize(this.computeSize());
this.setDirtyCanvas(true, true);
}
}
updateFromUpstream(update, node, updatedSlotData) {
var _a, _b;
console.log("----- ContextDynamicNode :: updateFromUpstream", arguments);
const inputs = this.getContextInputsList();
if (update == "connect") {
const baseInputsData = node.provideInputsData();
const baseIndex = updatedSlotData.index;
const baseInputData = baseInputsData[baseIndex];
const name = this.getNextUniqueNameForThisNode(baseInputData.name);
const foundIndex = inputs.findIndex(i => this.stripOwnedPrefix(i.name) === baseInputData.name);
if (foundIndex > -1) {
this.moveContextInput(foundIndex, baseIndex);
this.renameContextInput(baseIndex, baseInputData.name, false);
}
else {
this.addContextInput(baseInputData.name, baseInputData.type, baseInputData.index);
this.stabilizeNames();
}
}
else if (update == "disconnect") {
if ((_b = (_a = this.outputs[updatedSlotData.index]) === null || _a === void 0 ? void 0 : _a.links) === null || _b === void 0 ? void 0 : _b.length) {
this.renameContextInput(updatedSlotData.index, updatedSlotData.name, true);
this.moveContextInput(updatedSlotData.index, 'bottom');
}
else {
this.removeContextInput(updatedSlotData.index);
}
}
else if (update === "move") {
this.moveContextInput(updatedSlotData.from, updatedSlotData.index);
}
else if (update == "update") {
const baseInputsData = node.provideInputsData();
const baseIndex = updatedSlotData.index;
const baseInput = baseInputsData[baseIndex];
this.renameContextInput(baseIndex, baseInput.name);
this.stabilizeNames();
}
this.setSize(this.computeSize());
this.setDirtyCanvas(true, true);
}
stabilizeNames() {
const inputs = this.getContextInputsList();
const names = [];
for (const [index, input] of inputs.entries()) {
if (index === 0 || index === inputs.length - 1) {
continue;
}
input.label = undefined;
this.outputs[index].label = undefined;
let origName = this.stripOwnedPrefix(input.name).replace(/\.\d+$/, "");
let name = input.name;
if (!this.isOwnedInput(name)) {
names.push(name.toLocaleUpperCase());
}
else {
let n = 0;
name = this.addOwnedPrefix(origName);
while (names.includes(this.stripOwnedPrefix(name).toLocaleUpperCase())) {
name = `${this.addOwnedPrefix(origName)}.${++n}`;
}
names.push(this.stripOwnedPrefix(name).toLocaleUpperCase());
if (input.name !== name) {
this.renameContextInput(index, name);
}
}
}
}
onConnectInput(inputIdx, outputType, outputSlot, outputNode, outputIndex) {
let canConnect = true;
if (super.onConnectInput) {
canConnect = super.onConnectInput(inputIdx, outputType, outputSlot, outputNode, outputIndex);
}
if (canConnect && outputNode instanceof ContextDynamicNode && outputIndex === 0 && inputIdx !== 0) {
ContextDynamicNodeBase.logger.error("Currently, you can only connect a context node in the first slot.");
canConnect = false;
}
return canConnect;
}
getSlotMenuOptions(info) {
const opts = [];
if (info.input) {
if (this.isOwnedInput(info.input.name) && info.input.type !== '*') {
opts.push({
content: "✏️ Rename Input",
callback: () => {
var dialog = app.canvas.createDialog("<span class='name'>Name</span><input autofocus type='text'/><button>OK</button>", {});
var dialogInput = dialog.querySelector("input");
if (dialogInput) {
dialogInput.value = this.stripOwnedPrefix(info.input.name || "");
}
var inner = () => {
app.graph.beforeChange();
this.renameContextInput(info.slot, dialogInput.value);
this.stabilizeNames();
this.setDirtyCanvas(true, true);
dialog.close();
app.graph.afterChange();
};
dialog.querySelector("button").addEventListener("click", inner);
dialogInput.addEventListener("keydown", (e) => {
var _a;
dialog.is_modified = true;
if (e.keyCode == 27) {
dialog.close();
}
else if (e.keyCode == 13) {
inner();
}
else if (e.keyCode != 13 && ((_a = e.target) === null || _a === void 0 ? void 0 : _a.localName) != "textarea") {
return;
}
e.preventDefault();
e.stopPropagation();
});
dialogInput.focus();
},
});
opts.push({
content: "🗑️ Delete Input",
callback: () => {
this.removeInput(info.slot);
},
});
}
}
return opts;
}
}
ContextDynamicNode.title = "Dynamic Context (rgthree)";
ContextDynamicNode.type = "Dynamic Context (rgthree)";
ContextDynamicNode.comfyClass = "Dynamic Context (rgthree)";
class ContextDynamicSwitchNode extends ContextDynamicNodeBase {
constructor(title = ContextDynamicSwitchNode.title) {
super(title);
this.hasShadowInputs = true;
this.lastInputsList = [];
this.shadowInputs = [
{ name: "base_ctx", type: "DYNAMIC_CONTEXT" },
{ name: "+", type: "*" },
];
let alerted = false;
setInterval(() => {
const plusIndex = this.shadowInputs.findIndex((i) => i.name === "+");
if (plusIndex === -1) {
!alerted && console.error("ERROR, no plus in shadow inputs", [...this.shadowInputs]);
alerted = true;
}
else if (plusIndex !== this.shadowInputs.length - 1) {
!alerted &&
console.error("ERROR, plus is not last in shadow inputs", [...this.shadowInputs]);
alerted = true;
}
else {
alerted && console.error("BACK TO NOREMAL", [...this.shadowInputs]);
alerted = false;
}
});
}
static setUp(comfyClass) {
BaseContextNode.setUp(comfyClass, ContextDynamicSwitchNode);
}
clone() {
const cloned = super.clone();
while (cloned.outputs.length > 1) {
cloned.removeOutput(cloned.outputs.length - 1);
}
return cloned;
}
getContextInputsList() {
return this.shadowInputs;
}
onNodeCreated() {
}
onConnectInput(inputIndex, outputType, outputSlot, outputNode, outputIndex) {
let canConnect = true;
if (super.onConnectInput) {
canConnect = super.onConnectInput(inputIndex, outputType, outputSlot, outputNode, outputIndex);
}
const allConnectedNodes = getConnectedInputNodes(this);
if (canConnect && allConnectedNodes.includes(outputNode)) {
rgthree.showMessage({
id: "dynamic-context-looped",
type: "warn",
message: "You may not connect the same context node to a switch.",
timeout: 5000,
});
canConnect = false;
}
if (canConnect && shouldPassThrough(outputNode, PassThroughFollowing.REROUTE_ONLY)) {
const connectedNodes = getConnectedInputNodesAndFilterPassThroughs(outputNode, undefined, undefined, PassThroughFollowing.REROUTE_ONLY);
if (connectedNodes.length && allConnectedNodes.find((n) => connectedNodes.includes(n))) {
rgthree.showMessage({
id: "dynamic-context-looped",
type: "warn",
message: "You may not connect the same context node to a switch, even through a reroute.",
timeout: 5000,
});
canConnect = false;
}
}
return canConnect;
}
configure(info) {
super.configure(info);
setTimeout(() => {
this.shadowInputs = this.getAllShadowInputs();
this.shadowInputs.push({ name: "+", type: "*" });
this.updateLastInputsList();
console.log(this.shadowInputs);
}, 100);
}
moveContextInput(slotFrom, slotTo) {
super.moveContextInput(slotFrom, slotTo);
this.updateLastInputsList();
}
removeContextInput(slot) {
super.removeContextInput(slot);
this.updateLastInputsList();
}
addContextInput(name, type, slot = -1) {
super.addContextInput(name, type, slot);
this.updateLastInputsList();
}
updateLastInputsList() {
this.lastInputsList = this.getAllInputsList();
}
connectSlotFromUpdateOrInput(data) {
console.log(`connectSlotFromUpdateOrInput: ${data.name}`, data);
if (data.duplicatesBefore.length) {
console.log(`[Do Nothing] It has duplicatesBefore (${data.duplicatesBefore.join(",")}).`);
this.updateLastInputsList();
}
else if (data.duplicatesAfter.length) {
const from = this.shadowInputs.findIndex((i) => i.name.toLocaleUpperCase() === data.key);
console.log(`[Move] Has duplicates after. ${from} -> ${data.shadowIndex}`);
this.moveContextInput(from, data.shadowIndex);
}
else {
console.log(`[Add] No dupes, so we can add it at ${data.shadowIndex}.`);
this.addContextInput(data.name, data.type, data.shadowIndex);
}
}
handleInputConnected(slotIndex) {
var _a;
console.log("--- handleInputConnected", slotIndex);
const postInputsList = [...this.getAllInputsList()];
const node = (_a = postInputsList.find((i) => i.slot === slotIndex)) === null || _a === void 0 ? void 0 : _a.node;
if (!node) {
console.error("hmmm... no node foun to handle connect.");
return;
}
const inputsDataLists = postInputsList.filter((d) => d.slot == slotIndex && d.nodeIndex > 0 && d.type !== '*');
for (const data of inputsDataLists) {
this.connectSlotFromUpdateOrInput(data);
}
}
handleInputDisconnected(slotIndex) {
var _a;
console.log("--- handleInputDisconnected", slotIndex);
const preInputsList = [...this.lastInputsList];
const node = (_a = preInputsList.find((i) => i.slot === slotIndex)) === null || _a === void 0 ? void 0 : _a.node;
if (!node) {
console.error("hmmmm... no node found to handle disconnect.");
return;
}
const postInputsList = [...this.getAllInputsList()];
const inputs = [...this.shadowInputs];
console.log("postInputsList", postInputsList);
let lastIndex = 0;
for (let [index, data] of postInputsList.entries()) {
data = this.getAllInputsList()[index];
if (data.shadowIndex === -1 || data.nodeIndex === 0) {
continue;
}
lastIndex++;
const foundIndex = this.shadowInputs.findIndex((i) => i.name.toLocaleUpperCase() === data.key);
console.log(data.name, foundIndex, data.shadowIndex);
if (foundIndex !== data.shadowIndex) {
this.moveContextInput(foundIndex, data.shadowIndex);
}
}
for (let index = inputs.length - 1; index > lastIndex; index--) {
const input = this.shadowInputs[index];
if (input.type !== '*') {
this.removeContextInput(index);
}
}
console.log([...this.shadowInputs]);
}
updateFromUpstream(update, node, updatedSlotData) {
var _a;
console.log("----- ContextDynamicSwitchNode :: updateFromUpstream", update, updatedSlotData);
const preInputsList = [...this.lastInputsList];
const postInputsList = [...this.getAllInputsList()];
if (shouldPassThrough(node)) {
const connectedNodes = getConnectedNodesInfo(this, IoDirection.INPUT);
const foundRerouteInfo = connectedNodes.find((n) => n.node === node);
if (update == "connect") {
this.handleInputConnected(foundRerouteInfo.originTravelFromSlot);
}
else if (update == "disconnect") {
this.handleInputDisconnected(foundRerouteInfo.originTravelFromSlot);
}
else {
throw new Error("Unexpected update type from pass through node: " + update);
}
return;
}
switch (update) {
case "connect": {
const data = postInputsList.find((d) => {
return d.node == node && d.nodeIndex === updatedSlotData.index;
});
if (!data) {
throw new Error("Hmmm.. unfound input slot when connecting upstream.");
}
this.connectSlotFromUpdateOrInput(data);
break;
}
case "disconnect":
const preInputData = preInputsList.find((i) => {
return i.node === node && i.nodeIndex == updatedSlotData.index;
});
if (!preInputData) {
throw new Error("Hmmm... no matching input found in existing input list for disconnect.");
}
if (preInputData.duplicatesBefore.length) {
console.log(`[Do Nothing] It was already duplicated before.`);
this.updateLastInputsList();
}
else if (((_a = preInputData === null || preInputData === void 0 ? void 0 : preInputData.duplicatesAfter) === null || _a === void 0 ? void 0 : _a[0]) != null) {
console.log(`[Move after] Not duplicated before, but is after.`);
this.moveContextInput(preInputData.shadowIndex, preInputData.duplicatesAfter[0]);
}
else {
console.log(`[Remove] ${preInputData.shadowIndex}.`, preInputData);
this.removeContextInput(preInputData.shadowIndex);
}
break;
case "move":
break;
case "update":
const index = postInputsList.findIndex((d) => {
return d.node == node && d.nodeIndex === updatedSlotData.index;
});
const pre = preInputsList[index];
const post = postInputsList[index];
console.log("preData", { ...pre });
console.log("postData", { ...post });
if (pre.shadowIndex == -1 && post.shadowIndex !== -1) {
console.log(`[Add] Old name wasn't shown, but new is.`, post.name, post.shadowIndex);
this.addContextInput(post.name, post.type, post.shadowIndex);
}
else if (pre.shadowIndex !== -1 && post.shadowIndex === -1) {
console.log(`[Remove] Old name was shown, but new isn;t.`, post.name, post.shadowIndex);
this.removeContextInput(pre.shadowIndex);
}
else if (post.shadowIndex > -1) {
console.log(`[Rename] It's shown and has a new name.`, post.name, post.shadowIndex);
this.renameContextInput(post.shadowIndex, post.name);
}
else {
console.log(`[Do Nothing] It's shown and has a new name.`, post.name, post.shadowIndex);
this.updateLastInputsList();
}
break;
}
console.log(this.shadowInputs);
}
getAllInputsList(indexToNodeOverride = {}) {
var _a, _b, _c;
const allConnectedInputsDataByName = {};
const allConnectedInputsData = [];
let currentShadowIndex = 0;
for (const [slot, input] of (this.inputs || []).entries()) {
const connectedNode = (_a = indexToNodeOverride[slot]) !== null && _a !== void 0 ? _a : (_b = getConnectedInputNodesAndFilterPassThroughs(this, this, slot)) === null || _b === void 0 ? void 0 : _b[0];
if (connectedNode) {
for (const inputData of connectedNode.provideInputsData()) {
const key = inputData.name.toLocaleUpperCase();
allConnectedInputsDataByName[key] = allConnectedInputsDataByName[key] || [];
const existings = allConnectedInputsDataByName[key];
let data = {
node: connectedNode,
slot,
shadowIndexFull: allConnectedInputsData.length,
shadowIndex: !existings.length ? currentShadowIndex : -1,
shadowIndexIfShownSingularly: currentShadowIndex,
nodeIndex: inputData.index,
type: inputData.type,
name: inputData.name,
key,
duplicatesBefore: allConnectedInputsDataByName[key].map((d) => d.shadowIndexFull),
duplicatesAfter: [],
};
if (data.shadowIndex > -1) {
currentShadowIndex++;
}
for (const existing of existings) {
existing.duplicatesAfter.push(data.shadowIndexFull);
}
allConnectedInputsData.push(data);
(_c = allConnectedInputsDataByName[key]) === null || _c === void 0 ? void 0 : _c.push(data);
}
}
}
return allConnectedInputsData;
}
getAllShadowInputs() {
const inputsDataMap = {
BASE_CTX: { name: "base_ctx", type: "DYNAMIC_CONTEXT", index: 0 },
};
const baseNodes = getConnectedInputNodesAndFilterPassThroughs(this, this);
for (const inputNode of baseNodes) {
for (const inputData of inputNode.provideInputsData()) {
const dataKey = inputData.name.toLocaleUpperCase();
const existingData = inputsDataMap[dataKey];
if (!existingData) {
inputsDataMap[dataKey] = inputData;
}
else if (existingData.name !== inputData.name) {
throw new Error(`Conflicting data for ${dataKey}. ${existingData.name} !== ${inputData.name}`);
}
else if (existingData.type !== inputData.type) {
throw new Error(`Conflicting data for ${dataKey}. ${existingData.type} !== ${inputData.type}`);
}
}
}
return Object.values(inputsDataMap).map((v, index) => Object.assign({ ...v }, { index }));
}
}
ContextDynamicSwitchNode.title = "Dynamic Context Switch (rgthree)";
ContextDynamicSwitchNode.type = "Dynamic Context Switch (rgthree)";
ContextDynamicSwitchNode.comfyClass = "Dynamic Context Switch (rgthree)";
const contextDynamicNodes = [ContextDynamicNode, ContextDynamicSwitchNode];
app.registerExtension({
name: "rgthree.ContextDynamic",
async beforeRegisterNodeDef(nodeType, nodeData) {
for (const ctxClass of contextDynamicNodes) {
if (nodeData.name === ctxClass.type) {
console.log(nodeData.name);
ctxClass.nodeData = nodeData;
ctxClass.nodeType = nodeType;
ctxClass.setUp(nodeType);
break;
}
}
},
});
+21 -20
View File
@@ -2,7 +2,7 @@ var _a;
import { app } from "../../scripts/app.js"; import { app } from "../../scripts/app.js";
import { rgthreeConfig } from "./rgthree_config.js"; import { rgthreeConfig } from "./rgthree_config.js";
import { rgthree } from "./rgthree.js"; import { rgthree } from "./rgthree.js";
import { LAYOUT_CLOCKWISE, LAYOUT_LABEL_OPPOSITES, LAYOUT_LABEL_TO_DATA, addConnectionLayoutSupport, addMenuItem, getSlotLinks, isValidConnection, } from "./utils.js"; import { LAYOUT_CLOCKWISE, LAYOUT_LABEL_OPPOSITES, LAYOUT_LABEL_TO_DATA, addConnectionLayoutSupport, addMenuItem, getConnectedOutputNodesAndFilterPassThroughs, getSlotLinks, isValidConnection, } from "./utils.js";
import { wait } from "./shared_utils.js"; import { wait } from "./shared_utils.js";
const rerouteConfig = ((_a = rgthreeConfig === null || rgthreeConfig === void 0 ? void 0 : rgthreeConfig['nodes']) === null || _a === void 0 ? void 0 : _a['reroute']) || {}; const rerouteConfig = ((_a = rgthreeConfig === null || rgthreeConfig === void 0 ? void 0 : rgthreeConfig['nodes']) === null || _a === void 0 ? void 0 : _a['reroute']) || {};
let configWidth = Math.max(Math.round((Number(rerouteConfig['default_width']) || 40) / 10) * 10, 10); let configWidth = Math.max(Math.round((Number(rerouteConfig['default_width']) || 40) / 10) * 10, 10);
@@ -31,7 +31,6 @@ app.registerExtension({
var _a; var _a;
super(title); super(title);
this.configuring = true; this.configuring = true;
this.schedulePromise = null;
this.defaultConnectionsLayout = configLayout; this.defaultConnectionsLayout = configLayout;
this.isVirtualNode = true; this.isVirtualNode = true;
this.hideSlotLabels = true; this.hideSlotLabels = true;
@@ -75,7 +74,20 @@ app.registerExtension({
} }
} }
} }
this.scheduleStabilize(); if (this.configuring) {
return;
}
this.stabilize();
if (type === LiteGraph.INPUT) {
this.updateDownstream(connected ? 'connect' : 'disconnect', { index: 0, name: this.inputs[0].name });
}
}
updateDownstream(update, updatedIndexes) {
var _a;
const nodes = getConnectedOutputNodesAndFilterPassThroughs(this, this, 0);
for (const node of nodes) {
(_a = node === null || node === void 0 ? void 0 : node.updateFromUpstream) === null || _a === void 0 ? void 0 : _a.call(node, update, this, updatedIndexes);
}
} }
onDrawForeground(ctx, canvas) { onDrawForeground(ctx, canvas) {
var _a, _b, _c; var _a, _b, _c;
@@ -97,20 +109,8 @@ app.registerExtension({
disconnectOutput(slot, targetNode) { disconnectOutput(slot, targetNode) {
return super.disconnectOutput(slot, targetNode); return super.disconnectOutput(slot, targetNode);
} }
scheduleStabilize(ms = 64) {
if (!this.schedulePromise) {
this.schedulePromise = new Promise((resolve) => {
setTimeout(() => {
this.schedulePromise = null;
this.stabilize();
resolve();
}, ms);
});
}
return this.schedulePromise;
}
stabilize() { stabilize() {
var _a, _b, _c, _d, _e, _f, _g, _h; var _a, _b, _c, _d, _e, _f;
if (this.configuring) { if (this.configuring) {
return; return;
} }
@@ -155,7 +155,7 @@ app.registerExtension({
} }
} }
const nodes = [this]; const nodes = [this];
let outputNode = null; let outputNodes = [];
let outputType = null; let outputType = null;
while (nodes.length) { while (nodes.length) {
currentNode = nodes.pop(); currentNode = nodes.pop();
@@ -187,8 +187,11 @@ app.registerExtension({
node.disconnectInput(link.target_slot); node.disconnectInput(link.target_slot);
} }
else { else {
if (outputType != null && outputType !== nodeOutType) {
console.warn(`[rgthree] Reroute - Mismatching output types..`);
}
outputType = nodeOutType; outputType = nodeOutType;
outputNode = node; outputNodes.push(node);
} }
} }
} }
@@ -220,8 +223,6 @@ app.registerExtension({
} }
} }
} }
(_g = inputNode === null || inputNode === void 0 ? void 0 : inputNode.onConnectionsChainChange) === null || _g === void 0 ? void 0 : _g.call(inputNode);
(_h = outputNode === null || outputNode === void 0 ? void 0 : outputNode.onConnectionsChainChange) === null || _h === void 0 ? void 0 : _h.call(outputNode);
app.graph.setDirtyCanvas(true, true); app.graph.setDirtyCanvas(true, true);
} }
computeSize(out) { computeSize(out) {
+2 -2
View File
@@ -245,8 +245,8 @@ class Rgthree {
fireEvent(event, data) { fireEvent(event, data) {
if (this.eventsToFns.has(event)) { if (this.eventsToFns.has(event)) {
for (let fn of this.eventsToFns.get(event)) { for (let fn of this.eventsToFns.get(event)) {
const event = new Event(data); const e = new CustomEvent(event, { detail: data });
fn(event); fn(e);
} }
} }
} }
+28 -20
View File
@@ -275,46 +275,53 @@ export function filterOutPassthroughNodes(nodes, passThroughFollowing = PassThro
return nodes.filter((n) => !shouldPassThrough(n, passThroughFollowing)); return nodes.filter((n) => !shouldPassThrough(n, passThroughFollowing));
} }
export function getConnectedInputNodes(startNode, currentNode, slot, passThroughFollowing = PassThroughFollowing.ALL) { export function getConnectedInputNodes(startNode, currentNode, slot, passThroughFollowing = PassThroughFollowing.ALL) {
return getConnectedNodes(startNode, IoDirection.INPUT, currentNode, slot, passThroughFollowing).map(n => n.node); return getConnectedNodesInfo(startNode, IoDirection.INPUT, currentNode, slot, passThroughFollowing).map(n => n.node);
} }
export function getConnectedInputNodesAndFilterPassThroughs(startNode, currentNode, slot, passThroughFollowing = PassThroughFollowing.ALL) { export function getConnectedInputNodesAndFilterPassThroughs(startNode, currentNode, slot, passThroughFollowing = PassThroughFollowing.ALL) {
return filterOutPassthroughNodes(getConnectedInputNodes(startNode, currentNode, slot, passThroughFollowing), passThroughFollowing); return filterOutPassthroughNodes(getConnectedInputNodes(startNode, currentNode, slot, passThroughFollowing), passThroughFollowing);
} }
export function getConnectedOutputNodes(startNode, currentNode, slot, passThroughFollowing = PassThroughFollowing.ALL) { export function getConnectedOutputNodes(startNode, currentNode, slot, passThroughFollowing = PassThroughFollowing.ALL) {
return getConnectedNodes(startNode, IoDirection.OUTPUT, currentNode, slot, passThroughFollowing).map(n => n.node); return getConnectedNodesInfo(startNode, IoDirection.OUTPUT, currentNode, slot, passThroughFollowing).map(n => n.node);
} }
export function getConnectedOutputNodesAndFilterPassThroughs(startNode, currentNode, slot, passThroughFollowing = PassThroughFollowing.ALL) { export function getConnectedOutputNodesAndFilterPassThroughs(startNode, currentNode, slot, passThroughFollowing = PassThroughFollowing.ALL) {
return filterOutPassthroughNodes(getConnectedOutputNodes(startNode, currentNode, slot, passThroughFollowing), passThroughFollowing); return filterOutPassthroughNodes(getConnectedOutputNodes(startNode, currentNode, slot, passThroughFollowing), passThroughFollowing);
} }
export function getConnectedNodes(startNode, dir = IoDirection.INPUT, currentNode, slot, passThroughFollowing = PassThroughFollowing.ALL) { export function getConnectedNodesInfo(startNode, dir = IoDirection.INPUT, currentNode, slot, passThroughFollowing = PassThroughFollowing.ALL, originTravelFromSlot) {
var _a, _b; var _a, _b, _c, _d, _e, _f;
currentNode = currentNode || startNode; currentNode = currentNode || startNode;
let rootNodes = []; let rootNodes = [];
const slotsToRemove = []; const slotsToRemove = [];
if (startNode === currentNode || shouldPassThrough(currentNode, passThroughFollowing)) { if (startNode === currentNode || shouldPassThrough(currentNode, passThroughFollowing)) {
let linkIds; let linkIds;
if (dir == IoDirection.OUTPUT) { if (dir == IoDirection.OUTPUT) {
linkIds = ((_a = currentNode.outputs) === null || _a === void 0 ? void 0 : _a.flatMap((i) => i.links)) || []; if (slot != null && slot > -1) {
} linkIds = (_b = (_a = currentNode.outputs[slot]) === null || _a === void 0 ? void 0 : _a.links) !== null && _b !== void 0 ? _b : [];
else {
linkIds = ((_b = currentNode.inputs) === null || _b === void 0 ? void 0 : _b.map((i) => i.link)) || [];
}
if (typeof slot == "number" && slot > -1) {
if (linkIds[slot]) {
linkIds = [linkIds[slot]];
} }
else { else {
return []; linkIds = ((_c = currentNode.outputs) === null || _c === void 0 ? void 0 : _c.flatMap((i) => i.links)) || [];
}
}
else {
if (slot != null && slot > -1) {
linkIds = [(_e = (_d = currentNode.inputs[slot]) === null || _d === void 0 ? void 0 : _d.link) !== null && _e !== void 0 ? _e : -1].filter(i => i > -1);
}
else {
linkIds = ((_f = currentNode.inputs) === null || _f === void 0 ? void 0 : _f.map((i) => i.link)) || [];
} }
} }
let graph = app.graph; let graph = app.graph;
for (const linkId of linkIds) { for (const linkId of linkIds) {
const link = (linkId != null && graph.links[linkId]); let link = null;
if (typeof linkId == "number") {
link = graph.links[linkId];
}
if (!link) { if (!link) {
continue; continue;
} }
const travelFromSlot = dir == IoDirection.OUTPUT ? link.origin_slot : link.target_slot;
const connectedId = dir == IoDirection.OUTPUT ? link.target_id : link.origin_id; const connectedId = dir == IoDirection.OUTPUT ? link.target_id : link.origin_id;
const originSlot = dir == IoDirection.OUTPUT ? link.target_slot : link.origin_slot; const travelToSlot = dir == IoDirection.OUTPUT ? link.target_slot : link.origin_slot;
originTravelFromSlot = originTravelFromSlot != null ? originTravelFromSlot : travelFromSlot;
const originNode = graph.getNodeById(connectedId); const originNode = graph.getNodeById(connectedId);
if (!link) { if (!link) {
console.error("No connected node found... weird"); console.error("No connected node found... weird");
@@ -324,10 +331,10 @@ export function getConnectedNodes(startNode, dir = IoDirection.INPUT, currentNod
console.log(`${startNode.title} (${startNode.id}) seems to have two links to ${originNode.title} (${originNode.id}). One may be stale: ${linkIds.join(", ")}`); console.log(`${startNode.title} (${startNode.id}) seems to have two links to ${originNode.title} (${originNode.id}). One may be stale: ${linkIds.join(", ")}`);
} }
else { else {
rootNodes.push({ node: originNode, slot: originSlot }); rootNodes.push({ node: originNode, travelFromSlot, travelToSlot, originTravelFromSlot });
if (shouldPassThrough(originNode, passThroughFollowing)) { if (shouldPassThrough(originNode, passThroughFollowing)) {
for (const foundNode of getConnectedNodes(startNode, dir, originNode)) { for (const foundNode of getConnectedNodesInfo(startNode, dir, originNode, undefined, undefined, originTravelFromSlot)) {
if (!rootNodes.includes(foundNode)) { if (!rootNodes.map(n => n.node).includes(foundNode.node)) {
rootNodes.push(foundNode); rootNodes.push(foundNode);
} }
} }
@@ -363,7 +370,7 @@ function getTypeFromSlot(slot, dir, skipSelf = false) {
let graph = app.graph; let graph = app.graph;
let type = slot === null || slot === void 0 ? void 0 : slot.type; let type = slot === null || slot === void 0 ? void 0 : slot.type;
if (!skipSelf && type != null && type != "*") { if (!skipSelf && type != null && type != "*") {
return { type: type, label: (slot === null || slot === void 0 ? void 0 : slot.label) || (slot === null || slot === void 0 ? void 0 : slot.name) }; return { type: type, label: slot === null || slot === void 0 ? void 0 : slot.label, name: slot === null || slot === void 0 ? void 0 : slot.name };
} }
const links = getSlotLinks(slot); const links = getSlotLinks(slot);
for (const link of links) { for (const link of links) {
@@ -375,7 +382,8 @@ function getTypeFromSlot(slot, dir, skipSelf = false) {
if ((connectedSlot === null || connectedSlot === void 0 ? void 0 : connectedSlot.type) != null && (connectedSlot === null || connectedSlot === void 0 ? void 0 : connectedSlot.type) != "*") { if ((connectedSlot === null || connectedSlot === void 0 ? void 0 : connectedSlot.type) != null && (connectedSlot === null || connectedSlot === void 0 ? void 0 : connectedSlot.type) != "*") {
return { return {
type: connectedSlot.type, type: connectedSlot.type,
label: (connectedSlot === null || connectedSlot === void 0 ? void 0 : connectedSlot.label) || (connectedSlot === null || connectedSlot === void 0 ? void 0 : connectedSlot.name), label: connectedSlot === null || connectedSlot === void 0 ? void 0 : connectedSlot.label,
name: connectedSlot === null || connectedSlot === void 0 ? void 0 : connectedSlot.name
}; };
} }
else if ((connectedSlot === null || connectedSlot === void 0 ? void 0 : connectedSlot.type) == "*") { else if ((connectedSlot === null || connectedSlot === void 0 ? void 0 : connectedSlot.type) == "*") {