158 lines
5.1 KiB
Python
158 lines
5.1 KiB
Python
"""
|
||
XB-BOX 快速开关(参考 rgthree-comfy 的 Fast Muter / Fast Groups Muter / Any Switch)
|
||
=================================================================================
|
||
三个节点:
|
||
|
||
① XB_DisableGroups —— 禁用分组
|
||
· **不接线**:在节点面板/右键菜单里勾选要控制的分组(可以随时增删);
|
||
· 点一行 = 把该分组里的所有节点一次性「静音 / 恢复」;
|
||
· 与 rgthree「禁用框」一致:状态直接读写画布上节点自己的 mode(随工作流保存)。
|
||
|
||
② XB_DisableNodes —— 禁用节点
|
||
· **不接线**:右击勾选要控制的节点(支持按分组整组加入);
|
||
· 点一行 = 把该节点「静音 / 绕过 / 恢复」;
|
||
· 与 rgthree「禁用节点」一致,但不占用输入端口、不需要拖线绑定。
|
||
|
||
③ XB_AnySwitch —— 切换任意
|
||
· 动态任意类型输入(默认 any_01 / any_02,连满自动追加);
|
||
· 与 rgthree「任意切换」完全一致:没有选择控件,永远输出第一个非空输入。
|
||
|
||
⚠️ 前两个节点**只在前端工作**(控制别的节点的 mode),Python 端不参与执行:
|
||
它们没有任何输入输出,ComfyUI 不会把它们放进执行队列。
|
||
"""
|
||
|
||
|
||
class AnyType(str):
|
||
"""一个永远“等于”任何类型的占位类型(credit: pythongosssss / rgthree)"""
|
||
|
||
def __ne__(self, other):
|
||
return False
|
||
|
||
def __eq__(self, other):
|
||
return True
|
||
|
||
def __hash__(self):
|
||
return hash("*")
|
||
|
||
|
||
class FlexibleOptionalInputType(dict):
|
||
"""让 optional 输入可以无限扩展(任何名字都视为存在,类型由构造时指定)。"""
|
||
|
||
def __init__(self, type_, data=None):
|
||
self.type_ = type_
|
||
self.data = data
|
||
if self.data is not None:
|
||
for k, v in self.data.items():
|
||
self[k] = v
|
||
|
||
def __getitem__(self, key):
|
||
if self.data is not None and key in self.data:
|
||
return self.data[key]
|
||
return (self.type_,)
|
||
|
||
def __contains__(self, key):
|
||
return True
|
||
|
||
|
||
any_type = AnyType("*")
|
||
|
||
|
||
class XB_DisableGroups:
|
||
"""禁用分组:不接线,勾选分组后一键静音 / 恢复整组节点。"""
|
||
|
||
DESCRIPTION = (
|
||
"禁用分组(参考 rgthree 禁用框):不接线,在面板或右键菜单里勾选要控制的分组;"
|
||
"点一行即可把该分组内所有节点静音 / 绕过 / 恢复。状态直接写在画布的节点上,随工作流保存。"
|
||
)
|
||
|
||
@classmethod
|
||
def INPUT_TYPES(cls):
|
||
return {"required": {}}
|
||
|
||
RETURN_TYPES = ()
|
||
FUNCTION = "noop"
|
||
CATEGORY = "XB_ToolBox/Utils"
|
||
OUTPUT_NODE = False
|
||
|
||
def noop(self):
|
||
"""纯前端控制节点,执行时什么都不做。"""
|
||
return {}
|
||
|
||
|
||
class XB_DisableNodes:
|
||
"""禁用节点:不接线,勾选节点(可按分组整组加入)后一键静音 / 绕过 / 恢复。"""
|
||
|
||
DESCRIPTION = (
|
||
"禁用节点(参考 rgthree 禁用节点):不接线,在面板或右键菜单里勾选要控制的节点(支持按分组整组加入);"
|
||
"点一行即可静音 / 绕过 / 恢复该节点。状态直接写在画布的节点上,随工作流保存。"
|
||
)
|
||
|
||
@classmethod
|
||
def INPUT_TYPES(cls):
|
||
return {"required": {}}
|
||
|
||
RETURN_TYPES = ()
|
||
FUNCTION = "noop"
|
||
CATEGORY = "XB_ToolBox/Utils"
|
||
OUTPUT_NODE = False
|
||
|
||
def noop(self):
|
||
return {}
|
||
|
||
|
||
class XB_AnySwitch:
|
||
"""切换任意:动态任意类型输入,直接输出第一个非空输入(无选择控件)。"""
|
||
|
||
DESCRIPTION = (
|
||
"切换任意(参考 rgthree 任意切换):把不同来源接到任意类型的输入端口,端口会随连线自动增加;"
|
||
"节点没有选择控件,永远输出从上往下第一个非空的输入。"
|
||
)
|
||
|
||
@classmethod
|
||
def INPUT_TYPES(cls):
|
||
return {
|
||
"required": {},
|
||
"optional": FlexibleOptionalInputType(any_type),
|
||
}
|
||
|
||
RETURN_TYPES = (any_type,)
|
||
RETURN_NAMES = ("*",)
|
||
FUNCTION = "switch"
|
||
CATEGORY = "XB_ToolBox/Utils"
|
||
OUTPUT_NODE = False
|
||
|
||
@staticmethod
|
||
def _items(kwargs):
|
||
items = [(k, v) for k, v in kwargs.items() if isinstance(k, str) and k.startswith("any_")]
|
||
items.sort(key=lambda kv: kv[0])
|
||
return items
|
||
|
||
@staticmethod
|
||
def _is_empty(value):
|
||
if value is None:
|
||
return True
|
||
# 上下文为空时的兜底(与 rgthree 一致)
|
||
if isinstance(value, dict) and "model" in value and "clip" in value:
|
||
return all(v is None for v in value.values())
|
||
return False
|
||
|
||
def switch(self, **kwargs):
|
||
"""取第一个非空输入(与 rgthree Any Switch 同行为)。"""
|
||
for _k, value in self._items(kwargs):
|
||
if not self._is_empty(value):
|
||
return (value,)
|
||
return (None,)
|
||
|
||
|
||
NODE_CLASS_MAPPINGS = {
|
||
"XB_DisableGroups": XB_DisableGroups,
|
||
"XB_DisableNodes": XB_DisableNodes,
|
||
"XB_AnySwitch": XB_AnySwitch,
|
||
}
|
||
|
||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||
"XB_DisableGroups": "XB-BOX - 🚫 禁用分组",
|
||
"XB_DisableNodes": "XB-BOX - 🚫 禁用节点",
|
||
"XB_AnySwitch": "XB-BOX - 🔀 切换任意",
|
||
}
|