Extensions/ComfyUI-MuteBypassSwitch
ComfyUI Extension

ComfyUI-MuteBypassSwitch

Five switch nodes that keep the graph running when an upstream node is muted (Ctrl+M): mute-bypass, A/B, multi-any merge, multi-channel merge and output fanout. Pure reference hand-off - no copying, conversion or device moves.

By Davonez·Created 7 days ago·Updated 2 days ago· 0
Davonez/ComfyUI-MuteBypassSwitch
Nodes—
On cloudLocal install
Stars0
Updated2 days ago
Readme

English | 中文

静音旁路 / 多路汇聚开关 (Mute Bypass Switch)

给 ComfyUI 的一套小开关:上游节点被静音/禁用(Mute,Ctrl+M)之后,数据照样能往下走, 而且交出去的就是原来那个对象 —— 不复制、不转换、不 detach、不搬 CPU,一个字节都不改。

2.0.0 —— UI 默认英文 + 可选翻译。 节点名、tooltip、右键菜单和控制台提示都改成英文了; 同时附带 locales/en + locales/zh,ComfyUI 语言设成中文时界面仍然是中文。 输出分发开关的控件键从中文改成了 outputs_per_input / output_<n>,选项值改成 2/3/4 和 on/off;1.x 存下来的工作流需要把这两个控件重新选一次。

一共五个节点:

| 节点 | 干什么 | |---|---| | 多通道汇聚开关(推荐:一条支路有多个产出时用它) | 每个通道各自挑"活着的哪一路",通道数量自动加(+2 输入 / +1 输出),最多 8 个通道 | | 多路汇聚开关(推荐:只要一路数据时用它) | 想接几路接几路(输入口会自动长出来),输出有值的那一路 | | 输出分发开关(多通道的反向:一对多) | 一个输入配几个输出(2/3/4 可选),每个输出可单独开关,输出口自动增减 | | 静音旁路开关 | 主线 / 旁路两路,主线被静音时自动走旁路 | | 任意 A/B 开关 | 任意类型二选一 |

只想装一个、只想记一个: 一条支路只产出一个东西 → 用「多路汇聚开关」;产出好几个东西(图 + 蒙版 + 条件…)→ 用「多通道汇聚开关」。

多通道汇聚开关的实际样子(通道 1、2 都接满,自动长出通道 3):

多通道汇聚开关

输出分发开关的实际样子(A/B 两路输入 → 4 个输出,out_1、out_3 各自接了一个预览):

输出分发开关

多路汇聚开关的实际样子(4 路接满、自动多出第 5 个空口):

多路汇聚开关

安装:把 ComfyUI-MuteBypassSwitch 整个文件夹放进 ComfyUI\custom_nodes\ (在 custom_nodes 里 git clone https://github.com/Davonez/ComfyUI-MuteBypassSwitch.git 也行), 重启 ComfyUI,然后浏览器按 F5 刷新一次,节点就出现在菜单里 (分类 Switch,搜「汇聚 / Mute / Multi」都能找到)。更细的步骤见 INSTALL.txt。


1. 为什么原方案会被卡死(问题根因)

ComfyUI 的「静音」不是把节点跳过就算完,而是让这个节点整个不进入执行图。 它输出端那根线,在提交任务时也跟着一起消失了。于是下游节点的输入就没人喂了:

  • 如果下游这个输入是必填的 → 引擎直接判定整个任务非法,报错,一个节点都不跑:

    Required input is missing
    
  • 如果下游这个输入是可选的 → 引擎会安静地跳过它,节点照跑(但少了数据,结果可能不对)。

这两条规则就写在引擎源码里(本机版本:ComfyUI/execution.py 的 validate_inputs):

if x not in inputs:
    if input_category == "required":
        ...
        "message": "Required input is missing",   # 必填缺失 -> 整体失败
    continue                                       # 可选缺失 -> 静默跳过

所以「静音一个中间节点 → 下游报错卡住」是引擎的既定行为,不是哪个插件的 bug。 能救的办法只有一条:在下游那个必填输入前面,插一个"永远有值交得出来"的节点。

本插件就是这个节点。它的两个输入都声明成 可选 + 任意类型 *,所以:

  1. 上游被静音 → 本节点不会因为缺输入而报错(可选输入,引擎直接放过);
  2. 主线没值了 → 本节点把旁路那一路上来的数据原样转交给下游。

* 通配符是引擎官方支持的 Any 类型(comfy_execution/validation.py 里 IO.AnyType.io_type == "*" 直接放行),所以 IMAGE / LATENT / CONDITIONING / MODEL / CLIP / VAE / MASK / 字符串 / 数字…… 都能连,不用为每种类型单独装一个开关。


2. 怎么接线(照着你画的那张图)

             ┌────────────────────────────────┐
   上游 ─────►│  某个节点(平时会被你静音的)    │──────┐ main
     │       └────────────────────────────────┘      │
     │                                               ▼
     │                                    ┌─────────────────────┐
     └───────────────────────────────────►│  静音旁路开关        │──────► 下游节点
                  bypass                  │  switch = 旁路 开    │        (必填输入接这里)
                                          └─────────────────────┘

三步:

  1. 上游(比如 Load Image、你的 LoRA 节点、某个后处理节点)的输出,拉两根线: 一根接「被静音节点」的输入,一根接开关的 bypass;
  2. 被静音节点的输出 → 接开关的 main;
  3. 开关的输出 data → 接下游节点原来那个接口。

平时:main 有值,开关原样转交 main,等于线直接连着,行为完全不变。 被你静音时:main 那根线没了,开关自动改走 bypass,把上游的原始数据交给下游,工作流继续跑。


3. 节点参数

静音旁路开关 (Mute Bypass Switch)

| 参数 | 类型 | 说明 | |---|---|---| | main | 输入,可选,任意类型 | 主线:来自可能被你静音的那个节点 | | bypass | 输入,可选,任意类型 | 旁路:主线失效时接管的那一路(一般直接接上游源头) | | switch | 开关,默认 旁路 开 | 开:主线缺失时允许走旁路;关:只走主线(等于把旁路线剪断) | | route | 下拉,默认 auto | auto 主线优先、缺了走旁路 / main 只走主线 / bypass 只走旁路(调试用) | | data | 输出,任意类型 | 原样透传进来的那个对象(同一个引用) |

route 的完整行为表:

| 主线 | 旁路 | switch | route | 输出 | |---|---|---|---|---| | 有值 | 有/无 | 任意 | auto | 主线(原对象) | | 空 | 有值 | 开 | auto | 旁路(原对象)← 这就是"被静音也能过" | | 空 | 有值 | 关 | auto | 空(恢复原样,下游还是拿不到) | | 空 | 空 | 任意 | auto | 空 + 控制台警告 | | 有值 | 有值 | 任意 | main | 主线 | | 空 | 有值 | 任意 | main | 空(尊重"只走主线") | | 有值 | 有值 | 任意 | bypass | 旁路 | | 有值 | 空 | 任意 | bypass | 退回主线(绝不阻断) |

注意 0 / "" / False 这种"假值"仍然算有值,不会被误判成缺失。

任意 A/B 开关 (Any A/B Switch)

顺手给的一个通用二选一:a / b 两路任意类型输入,which 选 A 或 B, 选中的那路原样输出;如果选中的那路没接,自动退回另一路。适合做 A/B 对比、临时旁路。

多路汇聚开关 (Multi Any Switch)

想接几路接几路 —— 多个任意类型的输入收成一路,输出其中有值的那一路。

第 1 路 ──┐
第 2 路 ──┤
第 3 路 ──┼──►[ 多路汇聚开关 ]──► data ──► 下游
第 4 路 ──┤     (自动挑有值的)
第 N 路 ──┘

| 参数 | 类型 | 说明 | |---|---|---| | input_1 / input_2 … | 输入,可选,任意类型 | 第几路就接第几个口。最后一路接上线后会自动多出一个空口,可以一直加(上限 32 路) | | switch | 开关,默认 汇聚 开 | 开:多路里挑有值的那一路;关:只认第 1 路(等于没这个节点) | | pick | 下拉,默认 first | 多路都有值时用哪一路:first 最上面那路 / last 最下面那路 | | force | 数字,默认 0 | 自己指定走哪一路:0 = 自动挑;1~16 = 强制第 N 路(那一路没值就交空,不再自动换路) | | data | 输出,任意类型 | 被选中的那一路,原样透传(同一个对象) |

行为:按插槽顺序,第一个「有值」的胜出。被静音的上游不会报错,直接跳过它往下选。 不想自动挑:把 force 填成 2,就永远走第 2 路(哪怕第 1 路有值也照走第 2 路)。

右键节点有三个手动项(不用先接线也能用):

| 菜单项 | 作用 | |---|---| | + 增加一路输入 | 立刻多一个空口 | | - 删掉最后一路空口 | 只删动态加的、且没连线的口(input_1/input_2 不动) | | 整理空输入口 | 一次把多余空口收掉,只留「已连线的 + 1 个空位」 |

典型接法(用一路兜底):把「被静音的那条支路」接 input_1, 「上游源头 / 便宜替身」接 input_2;平时走 input_1,一被静音就自动落到 input_2。

几个实现上的关键点(也是为什么它敢这么长口):

  • 后端只在 INPUT_TYPES 里声明了 input_1、input_2,第 3 路以后是前端按需加的插槽; 引擎 get_input_data() 对「没声明但被连线连上的输入」照样解析成 kwargs 传进来, 所以开多少路都不会报「未知输入」。
  • 前端扩展只追加插槽、绝不重排。因为工作流存盘时 graph.links 里的 target_slot 是插槽数组下标,而重新加载时前端是按「先建声明的口、再建控件行、最后追加多余的口」 来重建的;一旦重排,下标就和存档对不上,下次打开会接错口。
  • 多余的口在画布上会自动排在 switch / pick 这两行上面,不影响观感。

多通道汇聚开关 (Multi Channel Switch)

一条支路往往不只产出一个东西(图 + 蒙版 + 条件…)。这个节点就是每个通道各自汇聚: 某条支路被静音,只有它负责的那个通道自动换到下一路,其它通道完全不受影响。

通道1:  a ─┐                              ┌─► ch1  (图)
        b ─┴─►[ 通道1 挑有值的 ]──────────┘
通道2:  a ─┐                              ┌─► ch2  (蒙版)
        b ─┴─►[ 通道2 挑有值的 ]──────────┘
通道3:  a …(接满通道2 就自动长出来)

| 参数 | 说明 | |---|---| | ch{k}_a / ch{k}_b | 第 k 个通道的主用口 / 兜底口,任意类型、可选 | | switch | 总开关:关掉就只认每通道的 a 路 | | pick | 同一通道里多路都有值时用哪一路:first / last | | force | 总强制:auto = 不强制(默认)/a、b、c = 所有通道都只看这一路 | | ch{k}_force | 每个通道各自的强制:auto = 跟随总 force/自动挑;a、b、c = 这个通道只看这一路 | | ch1 … ch8 | 每个通道一个输出口,各自独立 |

强制选择就是"我偏要走这条支路",指定的那一路是空的就交空、绝不偷偷替你去挑。

优先级:通道自己的 ch{k}_force > 总 force > 自动挑(switch/pick)

| 场景 | ch1_force=auto、force=auto | ch1_force=b | force=b(通道仍是 auto) | |---|---|---|---| | 只有 a 有值 | ch1 = a | ch1 = 空(不回落) | ch1 = 空(不回落) | | a、b 都有值 | ch1 = a | ch1 = b | ch1 = b | | 只有 b 有值 | ch1 = b | ch1 = b | ch1 = b |

通道的 ch{k}_force 下拉只显示已经长出来的通道:1 个通道就 1 行,长到 3 个通道就有 3 行。 后端虽然静态声明了 8 个,但前端会把多余的从 node.widgets 里摘掉/按需加回来 (实测:运行时 addWidget 出来的控件,值照样进 prompt、也照样存盘)。

输入侧和输出侧都会自动加路:一个通道的 a、b 都接上了 → 自动多出下一个通道的 2 个输入口 和 1 个输出口(最多 8 个通道)。画布上默认只显示 1 个通道,不占地方。

右键菜单还能手动「+ 加一个通道(+2 输入 / +1 输出)」「- 删掉最后一个空通道」。

输出分发开关 (Output Fanout Switch)

多通道汇聚的反向。结构是「一个输入配几个输出」,节点上只有两个控件:

输出分发开关

A 节点 ──► in_1 ──┐                    ┌─► out_1   ┐
                  ├─[ 输出分发开关 ]───┼─► out_2   ┘ 这两个跟 A
B 节点 ──► in_2 ──┘                    ├─► out_3   ┐
                                       └─► out_4   ┘ 这两个跟 B

| 控件 | 说明 | |---|---| | 每个输入配几个输出 | 2 个 / 3 个 / 4 个。选 2:A 出 out_1/out_2、B 出 out_3/out_4;选 3:A 出 out_1~out_3、B 出 out_4~out_6 | | 输出1 输出2 … | 每个输出一行,只有 开 / 关。不想要哪个输出就选「关」 |

就这两样,不用再选「跟哪一路」——配对是自动的: 第 k 个输出跟第 向上取整(k ÷ 每路个数) 路输入。

  • 接上第 1 路输入 → 自动出 每路个数 个输出
  • 再接第 2 路 → 再多出 每路个数 个(跟第 2 路)
  • 最多 8 路输入;输出口会跟着输入口自己长出来

为什么输出要一次声明 32 个:ComfyUI 的 RETURN_TYPES 是静态的,输出口数量不能像输入那样 动态长。所以后端一次声明够用的数量,前端只把用到的露出来(其余 removeOutput 收起来), 生长时再放出来 —— 因为始终只动数组末尾,graph.links 里的下标不会错位,存盘安全。

注意:把某个输出设成「关」以后,接在它下游的节点会拿到空、报错(一个输出口总得有个值)。 要"这次不走这条分支",把那几个下游节点静音就行。


4. 为什么敢说"不改原数据"

FUNCTION 里只有一行 return (value,) —— 把传进来的对象引用直接塞回输出, 中间没有 copy()、没有 torch 运算、没有 list() 打包、没有 .to()、没有 detach()。 离线单测里用"同一个对象 is 判断 + 执行前后字节比对"验过(见 tests/test_offline.py)。

ComfyUI 那边拿到的就是上游节点原来产出的那个 tensor / 对象,颜色、精度、元数据全都不变。


5. 装 / 卸

  • 安装:把整个 ComfyUI-MuteBypassSwitch 文件夹放进 ComfyUI\custom_nodes\,重启 ComfyUI。
  • 卸载:删掉这个文件夹,重启。不会留下任何配置、不会改你的工作流。
  • 不依赖任何第三方包(只用标准库),不需要 pip install。

节点在菜单里的位置:右键 → Add Node → Switch,或者双击画布搜 静音 / mute / switch。


6. 常见问题

Q: 装完菜单里找不到节点? 先确认控制台没有 IMPORT FAILED 报错,再 F5 刷新浏览器(节点列表是页面加载时拉的)。 中文搜「静音」、英文搜 MuteBypassSwitch 都能出来。

Q: 插上开关了,怎么还是报 Required input is missing? 这个报错说的是哪个输入缺,看 details 里的字段名。两种常见情况:

  1. 下游的输入还直接连在"被静音节点"上,没改接到开关的 data 输出;
  2. 缺的是另一个接缝(另一条线也被静音了)。一个开关只能救它自己那一个输入, 一条线一个开关;两个接缝就放两个,或者直接用「多路汇聚开关」把几条分支汇成一路。

Q: 为什么关了开关还是会报错? 关掉开关 = 恢复成"没有这个节点时"的行为:主线没值,交不出东西,下游当然还是拿不到。 这个开关的意义就是默认打开,需要对照原始故障时再关。

Q: 会不会变慢 / 影响出图质量? 不会。它只是一次引用转交,没有张量计算、没有显存拷贝,也不改 workflow 的其余部分。

Q: 被静音的是 Load Image 这种"源头",我旁路接谁? 源头没有上游可接,就接一个便宜的替身节点(比如 EmptyImage 给 IMAGE、 CLIPTextEncode 给 CONDITIONING、Empty Latent Image 给 LATENT), 把它接到 bypass。这样"没图"的时候也能跑通,不会卡死。

Q: 能不能用它做真正的"断路"(让下游彻底不跑)? 不能。ComfyUI 的输出必须有值,"断路"是整组节点一起静音的事(那种场景用 rgthree 的 Fast Groups Bypasser 或直接 Ctrl+M 整组更合适)。本节点只负责"接缝不断"。


7. 验证过什么(不是"应该能用",是真跑过)

在本机这套环境里跑完了三层验证,全部通过:

① 离线单测 139 项(不需要启动 ComfyUI,python tests\test_offline.py)

  • main / bypass 确实在 optional 里,类型是 *,没有任何必填的链路输入;
  • 透传的是同一个对象(out is in 为真),执行前后字节/属性都没变;
  • 0 / "" / False / [] 这类假值不会被误判成"缺失";
  • 12 种开关×路由×输入缺失组合的输出都符合上面那张行为表;
  • 多路汇聚:kwargs 顺序打乱也按插槽号排、input_10 排在 input_2 后面(数字比较)、 pick=last、只有一路有值、全空、关汇聚、无关 kwargs 全部覆盖;
  • 多通道汇聚:通道之间互不影响、ch1_b 兜底、第 3 路候选 c、第 8 通道可用、 第 9 通道越界被忽略、输出恒为 8 个值;
  • 强制选择(force):多路填 2 就无视第 1 路、第 2 路没接就交空不回退、优先于 pick/switch; 多通道 force=b 让所有通道都走 b、某通道 b 没接就只那个通道交空、切回 auto 恢复自动; 分发节点:1 路输入 -> 前 2 个输出有值、2 路输入 -> A A B B、配 3 个 -> A A A B B B、关掉某个输出 -> 它交空、选项写坏也能按 2 个兜住。

② 后端真机 API 26 项(python tests\test_live_api.py,往 8188 真提交任务)

  • A. 不给 main(= 上游被静音)+ bypass=红 → 任务成功,产物 64x64 纯红;
  • B. main=蓝 + bypass=红 → 产物是蓝(主线优先,平时行为不变);
  • C. switch=关 + 无 main → 任务失败(说明开关真的能控制旁路);
  • D. route=bypass + 两路都有 → 产物是红(旁路优先);
  • E. 两路都缺 → 任务失败,但不是 Required input is missing;
  • F. 多路只接第 3、4 路(1、2 路等于被静音)→ 用第 3 路 64x64 蓝;
  • G. 多路 1~4 全接 → 用第 1 路 32x32 红;
  • H. 多路 pick=last + 全接 → 用第 4 路 80x80 白;
  • I. 多路只接第 2 路 → 用第 2 路 48x48 绿;
  • J. 多通道:通道1 的 a 缺失 → 通道1 用 b(48x48 绿),通道2 用 a(64x64 蓝);
  • K. 多通道:通道1 a/b 都有 → 用 a(32x32 红),通道2 只有 b → 用 b(80x80 白);
  • L. 多通道开到第 3 个通道,三路各自取 a(32红 / 64蓝 / 96灰);
  • M. 多通道 force=b:a 有值也强制走 b(48绿 / 80白);
  • N. 多通道 force=a:两块都走 a(32红 / 64蓝);
  • O. 多通道 force=b 但通道1 的 b 没接 → 交空、下游失败(不自动回退到 a);
  • P. 多路 force=2:无视第 1 路,强制用第 2 路(48绿);
  • Q. 多路 force=2 但只接了第 1 路 → 交空、下游失败(不自动回退);
  • U. 分发:1 路输入 + 配 2 个 → out_1/out_2 都拿到它;
  • V. 分发:配 3 个 → out_1..out_3 都是它;
  • W. 分发:2 路输入 + 配 2 个 → 红、红、绿、绿;
  • X. 分发:把「输出2」关掉 → 接在 out_2 上的下游拿不到数据,如预期失败;
  • Y. 分发:配 3 个 + 2 路输入 → 红红红、绿绿绿;
  • Z. 分发:关掉输出2~8,out_1 仍照常输出(32红)。 (F/H 的 input_3、input_4 和 J/K/L 的 ch2_*、ch3_* 在 INPUT_TYPES 里 根本没声明,能跑通就证明动态输入成立。)

③ 前端真机 81 项(无头 Edge + CDP,加载真实 8188 页面)

  • 静音旁路开关 11 项:* 通配插槽双向都能连;把主线上游设成静音(mode=2,等价 Ctrl+M)后, 前端 graphToPrompt() 出来的 prompt 里 main 这根线真的消失了而 bypass 还在; 这种状态下真的点排队:任务 success,产物 64x64(旁路那路),而不是主线的 128x128; 取消静音后 main 又回到 prompt 里。
  • 多路汇聚开关 21 项:初始 2 个口;每接一路自动多一个空口(1→2→3→4→5); prompt 里确实带上没声明的 input_3/input_4;4 路全接时产物 32x32, 静音第 1 路后变 48x48,再静音第 2 路后变 64x64;右键加/删一路; 存盘→加载→再存盘,每根线仍然指向 input_1..input_4(不会被重排弄错)。
  • 多通道汇聚开关 29 项:刚拖进来只露 1 个通道(2 输入 / 1 输出,后端声明的 8 个输出被收起); 通道接满自动 +2 输入 +1 输出(长到 3 个通道);不静音时 ch1=32x32 / ch2=64x64, 静音通道1的 a 后 ch1 变 48x48 而 ch2 不受影响仍是 64x64; 存盘→加载后 3 个通道、6 个输入口、3 个输出口和连线全在;右键加/删通道正常; 画布上把总 force 切到 b → 产物变 48/80(无视 a 有值),切回 auto → 恢复自动挑; 每通道下拉:ch1_force=b 时只有通道1 变 48,通道2 仍 64;两个各自设 a/b 也互不干扰; ch{k}_force 只显示已长出来的通道(1 通道 1 行、3 通道 3 行)。
  • 输出分发开关 20 项:刚拖进来只有「每个输入配几个输出」+ 两行「开/关」,1 输入 2 输出; 接上 A 后输入侧多一个空位;两个输出各接 SaveImage → 都是 A;再接上 B → 输出自动变 4 个 并多出「输出3」「输出4」;把「配几个」改成 3 个 → 输出变 6 个、out_1..3 跟 A、out_4 跟 B; 存盘→加载后口数、下拉、连线都在,再排队仍正确。

环境:ComfyUI-aki-v3(前端 LiteGraph + comfyAPI 新架构,节点总数 3763)、 Python 3.13、Edge 无头 + CDP。


8. 文件结构

ComfyUI-MuteBypassSwitch/
├─ __init__.py                    # 节点注册 + WEB_DIRECTORY
├─ nodes.py                       # 五个节点的全部逻辑,仅用标准库
├─ pyproject.toml                 # 包信息(ComfyUI Registry 用)
├─ LICENSE                        # MIT
├─ INSTALL.txt                    # 给不看 README 的人看的 30 秒安装说明
├─ web/
│  ├─ multi_any_switch.js         # 多路汇聚:自动加输入口 / 右键加减
│  ├─ multi_channel_switch.js     # 多通道汇聚:自动加通道(输入+输出一起长)
│  └─ output_fanout_switch.js     # 输出分发:一对多,每个输出自己选源
├─ preview_multi.png              # 多路汇聚开关的截图
├─ preview_channel.png            # 多通道汇聚开关的截图
├─ preview_fanout.png             # 输出分发开关的截图
├─ README.md                      # 本文件
├─ README.en.md                   # English version
└─ tests/
   ├─ test_offline.py             # 离线单测 139 项,不用启动 ComfyUI
   ├─ test_live_api.py            # 后端真机 26 项,需要 ComfyUI 在 8188 运行
   ├─ test_frontend_cdp.js        # 静音旁路开关 前端真机 11 项
   ├─ test_multi_frontend_cdp.js  # 多路汇聚开关 前端真机 21 项
   ├─ test_channel_frontend_cdp.js# 多通道汇聚开关 前端真机 29 项
   └─ test_fanout_frontend_cdp.js # 输出分发开关 前端真机 20 项

跑测试:

# 离线(任意 3.10+ 都行)
python tests\test_offline.py

# 后端(用整合包自带 python,ComfyUI 要保持运行)
<ComfyUI 整合包目录>\python\python.exe tests\test_live_api.py

# 前端(会自动开一个无头 Edge,用完自己关)
node tests\test_frontend_cdp.js
node tests\test_multi_frontend_cdp.js
node tests\test_channel_frontend_cdp.js
node tests\test_fanout_frontend_cdp.js

License: MIT | 见 LICENSE。