first upload

text switch(with add)
clip switch
condition switch
image switch
model switch
This commit is contained in:
younger15
2026-04-22 22:56:25 +08:00
commit 25399acf49
12 changed files with 380 additions and 0 deletions
+57
View File
@@ -0,0 +1,57 @@
import torch
import gc
class Iter_ConditioningSwitch:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"output_index": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"input_count": ("INT", {"default": 2, "min": 1, "max": 64}),
"use_last_input": ("BOOLEAN", {"default": True}),
},
}
RETURN_TYPES = ("CONDITIONING",)
FUNCTION = "switch_conditioning"
CATEGORY = "CustomNodes/Iterators"
def switch_conditioning(self, output_index, input_count, use_last_input, **kwargs):
target_idx = output_index % input_count
res = None
for i in range(target_idx, -1, -1):
val = kwargs.get(f"cond_{i}")
if val is not None or not use_last_input:
res = val
break
#gc.collect()
#torch.cuda.empty_cache()
return (res,)
class Iter_CLIPSwitch:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"output_index": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"input_count": ("INT", {"default": 2, "min": 1, "max": 64}),
"use_last_input": ("BOOLEAN", {"default": True}),
},
}
RETURN_TYPES = ("CLIP",)
FUNCTION = "switch_clip"
CATEGORY = "CustomNodes/Iterators"
def switch_clip(self, output_index, input_count, use_last_input, **kwargs):
target_idx = output_index % input_count
res = None
for i in range(target_idx, -1, -1):
val = kwargs.get(f"clip_{i}")
if val is not None or not use_last_input:
res = val
break
#gc.collect()
#torch.cuda.empty_cache()
return (res,)
+29
View File
@@ -0,0 +1,29 @@
class Iter_FloatSwitch:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"output_index": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"input_count": ("INT", {"default": 2, "min": 1, "max": 128, "step": 1}),
"use_last_input": ("BOOLEAN", {"default": True}),
},
}
RETURN_TYPES = ("FLOAT",)
FUNCTION = "switch_float"
CATEGORY = "CustomNodes/Iterators"
def switch_float(self, output_index, input_count, use_last_input, **kwargs):
# 1. 計算目標索引
target_idx = output_index % input_count
# 2. 搜尋有效浮點數
res_float = None
for i in range(target_idx, -1, -1):
val = kwargs.get(f"float_{i}")
# 注意:在 Python 中 0.0 是 False,所以要檢查是否為 None
if val is not None or not use_last_input:
res_float = val
break
return (float(res_float),)
+35
View File
@@ -0,0 +1,35 @@
import torch
import gc
class Iter_ImageSwitch:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"output_index": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"input_count": ("INT", {"default": 2, "min": 1, "max": 64, "step": 1}),
"use_last_input": ("BOOLEAN", {"default": False}),
},
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "switch_image"
CATEGORY = "CustomNodes/Iterators"
def switch_image(self, output_index, input_count, use_last_input, **kwargs):
# 1. 取得目標索引(對 input_count 取模,確保不溢出)
# 這樣 index 0~n 就會嚴格對應到 image_0 ~ image_n
target_idx = output_index % input_count
# 2. 從目標索引開始「往回」搜尋有效接點
res_image = None
for i in range(target_idx, -1, -1):
val = kwargs.get(f"image_{i}")
if val is not None or not use_last_input:
res_image = val
break
gc.collect()
torch.cuda.empty_cache()
return (res_image,)
+40
View File
@@ -0,0 +1,40 @@
import torch
import gc
class Iter_ModelSwitch:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"output_index": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"input_count": ("INT", {"default": 2, "min": 1, "max": 64, "step": 1}),
"use_last_input": ("BOOLEAN", {"default": True}),
},
}
RETURN_TYPES = ("MODEL",)
FUNCTION = "switch_model"
CATEGORY = "CustomNodes/Iterators"
def switch_model(self, output_index, input_count, use_last_input, **kwargs):
# 1. 確定目標索引 (嚴格對應編號)
target_idx = output_index % input_count
# 2. 往回搜尋有效模型
res_model = None
for i in range(target_idx, -1, -1):
val = kwargs.get(f"model_{i}")
if val is not None or not use_last_input:
res_model = val
break
# 4. VRAM 管理
#if force_gc == "enable":
#gc.collect()
#torch.cuda.empty_cache()
return (res_model,)
+68
View File
@@ -0,0 +1,68 @@
import torch
import gc
class Iter_TextSwitch:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {
"required": {
# 預設為空字串,允許不接線
"add_text": ("STRING", {"forceInput": True, "default": ""}),
"output_index": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
"input_count": ("INT", {"default": 2, "min": 1, "max": 128, "step": 1}),
"use_last_input": ("BOOLEAN", {"default": False}),
},
"optional": {
# 這裡不需要寫死 text_0,因為 JS 會動態產生所有輸入
}
}
RETURN_TYPES = ("STRING",)
FUNCTION = "switch_and_combine"
CATEGORY = "CustomNodes/Logic"
def switch_and_combine(self, add_text="", output_index=0, input_count=1, use_last_input=False, **kwargs):
"""
# 1. 蒐集所有動態傳入的字串
texts = []
for i in range(input_count):
val = kwargs.get(f"text_{i}")
# 只有當該接點有連線且有值時才加入(避免計算空的接點)
if val is not None:
texts.append(val)
# 2. 安全性檢查:如果完全沒有輸入接點有連線
if not texts:
# 如果連 add_text 都沒接,就回傳純空字串
return (add_text if add_text is not None else "",)
# 3. 根據索引選取文本(防止除以零)
target_index = output_index % len(texts)
selected_text = texts[target_index]
# 4. 串接並回傳
# 確保兩者皆為字串,避免 NoneType 相加報錯
final_header = add_text if add_text is not None else ""
final_selected = selected_text if selected_text is not None else ""
gc.collect()
torch.cuda.empty_cache()
return (final_header + final_selected,)
"""
# 1. 取得目標索引(對 input_count 取模,確保不溢出)
# 這樣 index 0~n 就會嚴格對應到 image_0 ~ image_n
target_idx = output_index % input_count
# 2. 從目標索引開始「往回」搜尋有效接點
res_txt = None
for i in range(target_idx, -1, -1):
val = kwargs.get(f"text_{i}")
if val is not None or not use_last_input:
res_txt = val
break
#gc.collect()
#torch.cuda.empty_cache()
return (add_text + res_txt,)
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 ChengJhong (younger15)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+40
View File
@@ -0,0 +1,40 @@
# ComfyUI-IterSwitch
A set of dynamic switch nodes for ComfyUI, designed to simplify complex workflows by allowing flexible, iterative input selection. These nodes provide dynamic connection management and intelligent fallback logic.
## Key Features
Dynamic Inputs: Automatically resizes the number of input connections based on the input_count widget.
## Smart Fallback:
With the use_last_input option enabled, the node can search backwards from the current index to find the nearest valid (connected) input, ensuring a result is always returned.
## Type Support:
Includes switches for Text, Conditioning, CLIP, Image, Model, and Float types.
### Example Usage: Iter_TextSwitch
The Iter_TextSwitch node allows you to combine a base string with one of many dynamically connected inputs.
1. Basic Connection
The node takes an add_text input (base string) and allows you to dynamically define multiple text inputs. You can select the specific input using output_index.
![text switch sample](assets/example_iterTextSwitch01.png)
*Example using [waiIllustrious v16](https://civitai.com/models/827184?modelVersionId=2514310)*
2. Fallback Logic
When use_last_input is enabled, the node becomes more robust. If your current output_index points to a disconnected or empty input, the node will automatically search backwards through your inputs until it finds the most recent valid (connected) string.
![will use last valid input if use_last_input checked](assets/example_iterTextSwitch02.png)
*Example using [waiIllustrious v16](https://civitai.com/models/827184?modelVersionId=2514310)*
3. Iterative Workflow
By using the output_index, you can easily integrate this node into iterative loops, ensuring that your text prompts are dynamically selected or concatenated based on your workflow state.
![text switch sample2](assets/example_iterTextSwitch03.png)
*Example using [waiIllustrious v16](https://civitai.com/models/827184?modelVersionId=2514310)*
## Installation
Navigate to your ComfyUI custom_nodes directory.
Run:
Bash
`git clone https://github.com/YOUR_USERNAME/ComfyUI-SwitchForIteration.git`
Restart ComfyUI.
+28
View File
@@ -0,0 +1,28 @@
from .Iter_TextSwitch import Iter_TextSwitch
from .Iter_CondClipSwitch import Iter_ConditioningSwitch, Iter_CLIPSwitch
from .Iter_ImageSwitch import Iter_ImageSwitch
from .Iter_ModelSwitch import Iter_ModelSwitch
from .Iter_FloatSwitch import Iter_FloatSwitch
NODE_CLASS_MAPPINGS = {
"Iter_TextSwitch": Iter_TextSwitch,
"Iter_ConditioningSwitch": Iter_ConditioningSwitch,
"Iter_CLIPSwitch": Iter_CLIPSwitch,
"Iter_ImageSwitch": Iter_ImageSwitch,
"Iter_ModelSwitch": Iter_ModelSwitch,
"Iter_FloatSwitch": Iter_FloatSwitch
}
NODE_DISPLAY_NAME_MAPPINGS = {
"Iter_TextSwitch": "Iter_TextSwitch",
"Iter_ConditioningSwitch": "Iter_ConditioningSwitch",
"Iter_CLIPSwitch": "Iter_CLIPSwitch",
"Iter_ImageSwitch": "Iter_ImageSwitch",
"Iter_ModelSwitch": "Iter_ModelSwitch",
"Iter_FloatSwitch": "Iter_FloatSwitch"
}
# 確保 JS 資料夾被載入
WEB_DIRECTORY = "./web"
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
Binary file not shown.

After

Width:  |  Height:  |  Size: 286 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 KiB

+62
View File
@@ -0,0 +1,62 @@
import { app } from "../../../scripts/app.js";
// 通用的動態接點處理函數
const setupSmartDynamicInputs = (nodeType, prefix, type) => {
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
onNodeCreated?.apply(this, arguments);
const inputCountWidget = this.widgets.find(w => w.name === "input_count");
inputCountWidget.callback = () => {
const targetCount = inputCountWidget.value;
// 1. 找出目前所有屬於該 prefix 的輸入點
const currentInputs = this.inputs.filter(i => i.name.startsWith(`${prefix}_`));
const currentCount = currentInputs.length;
if (targetCount > currentCount) {
// 2. 如果目標數量較多:只新增缺少的接點,原有的接點不受影響
for (let i = currentCount; i < targetCount; i++) {
this.addInput(`${prefix}_${i}`, type);
}
} else if (targetCount < currentCount) {
// 3. 如果目標數量較少:從最後面開始移除,保留前面的連線
for (let i = currentCount - 1; i >= targetCount; i--) {
// 找到該名稱對應的索引並移除
const inputIdx = this.inputs.findIndex(input => input.name === `${prefix}_${i}`);
if (inputIdx !== -1) {
this.removeInput(inputIdx);
}
}
}
// 重新整理 UI 配置
this.setSize(this.computeSize());
};
// 延遲初始化,確保節點建立時 UI 正確
setTimeout(() => {
if (inputCountWidget.callback) inputCountWidget.callback();
}, 50);
};
};
app.registerExtension({
name: "CustomNodes.IterSwitches",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
// 根據節點名稱套用不同的前綴與類型
if (nodeData.name === "Iter_TextSwitch") {
setupSmartDynamicInputs(nodeType, "text", "STRING");
} else if (nodeData.name === "Iter_ConditioningSwitch") {
setupSmartDynamicInputs(nodeType, "cond", "CONDITIONING");
} else if (nodeData.name === "Iter_CLIPSwitch") {
setupSmartDynamicInputs(nodeType, "clip", "CLIP");
} else if (nodeData.name === "Iter_ImageSwitch") {
setupSmartDynamicInputs(nodeType, "image", "IMAGE");
} else if (nodeData.name === "Iter_ModelSwitch") {
setupSmartDynamicInputs(nodeType, "model", "MODEL");
} else if (nodeData.name === "Iter_FloatSwitch") {
setupSmartDynamicInputs(nodeType, "float", "FLOAT");
}
}
});