first upload
text switch(with add) clip switch condition switch image switch model switch
This commit is contained in:
@@ -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,)
|
||||
|
||||
@@ -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),)
|
||||
@@ -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,)
|
||||
@@ -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,)
|
||||
@@ -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,)
|
||||
@@ -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.
|
||||
@@ -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.
|
||||

|
||||
*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.
|
||||

|
||||
*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.
|
||||

|
||||
*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
@@ -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 |
@@ -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");
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user