From a3fc00692ac81ae0fdfff9cd38f2b303f217a892 Mon Sep 17 00:00:00 2001 From: DaPaLi Date: Sun, 15 Mar 2026 19:42:36 +0100 Subject: [PATCH] Initial commit: ComfyUI Block Bypass Switch --- __init__.py | 5 ++ block_bypass.py | 52 ++++++++++++ js/group_bypass.js | 64 +++++++++++++++ js/group_bypass_panel.js | 168 +++++++++++++++++++++++++++++++++++++++ pyproject.toml | 12 +++ 5 files changed, 301 insertions(+) create mode 100644 __init__.py create mode 100644 block_bypass.py create mode 100644 js/group_bypass.js create mode 100644 js/group_bypass_panel.js create mode 100644 pyproject.toml diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..7bf4c4c --- /dev/null +++ b/__init__.py @@ -0,0 +1,5 @@ +from .block_bypass import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS + +WEB_DIRECTORY = "./js" + +__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"] diff --git a/block_bypass.py b/block_bypass.py new file mode 100644 index 0000000..902168f --- /dev/null +++ b/block_bypass.py @@ -0,0 +1,52 @@ +try: + from comfy.comfy_types import IO + any_type = IO.ANY +except ImportError: + # Fallback für ältere ComfyUI-Versionen + class AnyType(str): + def __ne__(self, other): + return False + any_type = AnyType("*") + + +class BlockBypassSwitch: + """ + Leitet entweder den direkten Eingang (Block überspringen) + oder den Block-Ausgang weiter — per 1-Klick-Toggle. + + Verkabelung: + • direct_in → Signal VOR dem Block + • block_out → Signal NACH dem Block + • bypass → True = Block überspringen / False = Block aktiv + """ + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "direct_in": (any_type,), + "block_out": (any_type,), + "bypass": ("BOOLEAN", { + "default": False, + "label_on": "BYPASS (Block überspringen)", + "label_off": "BLOCK AKTIV", + }), + } + } + + RETURN_TYPES = (any_type,) + RETURN_NAMES = ("output",) + FUNCTION = "switch" + CATEGORY = "utils/bypass" + + def switch(self, direct_in, block_out, bypass): + return (direct_in if bypass else block_out,) + + +NODE_CLASS_MAPPINGS = { + "BlockBypassSwitch": BlockBypassSwitch, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "BlockBypassSwitch": "⚡ Block Bypass Switch", +} diff --git a/js/group_bypass.js b/js/group_bypass.js new file mode 100644 index 0000000..b31d2cd --- /dev/null +++ b/js/group_bypass.js @@ -0,0 +1,64 @@ +import { app } from "../../scripts/app.js"; + +app.registerExtension({ + name: "Comfy.GroupBypassToggle", + + setup() { + const canvas = app.canvas; + + // Hook in den Gruppen-Kontextmenü + const _getGroupMenuOptions = canvas.getGroupMenuOptions?.bind(canvas); + + canvas.getGroupMenuOptions = function (group) { + const options = _getGroupMenuOptions ? _getGroupMenuOptions(group) : []; + + const nodes = getGroupNodes(group); + if (nodes.length === 0) return options; + + const allBypassed = nodes.every(n => n.mode === 4); + const allMuted = nodes.every(n => n.mode === 2); + + options.push(null); // Trennlinie + + options.push({ + content: allBypassed ? "✅ Gruppe aktivieren" : "⚡ Gruppe bypassen", + callback: () => setGroupMode(group, allBypassed ? 0 : 4), + }); + + options.push({ + content: allMuted ? "✅ Gruppe aktivieren" : "🔇 Gruppe muten", + callback: () => setGroupMode(group, allMuted ? 0 : 2), + }); + + return options; + }; + }, +}); + +// Alle Nodes innerhalb einer Gruppe ermitteln +function getGroupNodes(group) { + // Methode 1: LiteGraph pflegt _nodes direkt auf der Gruppe + if (group._nodes && group._nodes.length > 0) { + return [...group._nodes]; + } + + // Methode 2: Bounding-Box-Fallback + const [gx, gy] = group.pos; + const [gw, gh] = group.size; + + return app.graph._nodes.filter(node => { + const [nx, ny] = node.pos; + const nw = node.size?.[0] ?? 0; + const nh = node.size?.[1] ?? 0; + return nx >= gx && ny >= gy && nx + nw <= gx + gw && ny + nh <= gy + gh; + }); +} + +// Modus auf alle Nodes der Gruppe setzen +function setGroupMode(group, mode) { + const nodes = getGroupNodes(group); + for (const node of nodes) { + node.mode = mode; + } + app.graph.setDirtyCanvas(true, true); +} diff --git a/js/group_bypass_panel.js b/js/group_bypass_panel.js new file mode 100644 index 0000000..7d572ff --- /dev/null +++ b/js/group_bypass_panel.js @@ -0,0 +1,168 @@ +import { app } from "../../scripts/app.js"; + +app.registerExtension({ + name: "Comfy.GroupBypassPanel", + setup() { + buildPanel(); + buildToggleButton(); + }, +}); + +let panel = null; + +// ── Panel aufbauen ──────────────────────────────────────────────────────────── +function buildPanel() { + panel = document.createElement("div"); + panel.id = "group-bypass-panel"; + panel.style.cssText = ` + position: fixed; bottom: 70px; left: 50%; transform: translateX(-50%); + background: #1e1e1e; border: 1px solid #555; + border-radius: 10px; padding: 14px; width: 270px; + z-index: 9999; display: none; color: #fff; + font-family: sans-serif; font-size: 13px; + box-shadow: 0 4px 20px rgba(0,0,0,0.7); + `; + + panel.innerHTML = ` +
+ ⚡ Gruppen Bypass + + +
+ + + +
+ + + +
+ +
+
+ `; + + document.body.appendChild(panel); + + panel.querySelector("#gbp-close").onclick = () => { panel.style.display = "none"; }; + panel.querySelector("#gbp-refresh").onclick = refreshGroups; + panel.querySelector("#gbp-bypass").onclick = () => applyMode(4, "⚡ Bypassed"); + panel.querySelector("#gbp-mute").onclick = () => applyMode(2, "🔇 Gemutet"); + panel.querySelector("#gbp-activate").onclick = () => applyMode(0, "✅ Aktiviert"); +} + +// ── Schwebender Button unten rechts ────────────────────────────────────────── +function buildToggleButton() { + const btn = document.createElement("button"); + btn.textContent = "⚡ Gruppen"; + btn.title = "Gruppen Bypass Panel öffnen"; + btn.style.cssText = ` + position: fixed; bottom: 22px; left: 50%; transform: translateX(-50%); + padding: 8px 16px; background: #b87200; color: #fff; + border: none; border-radius: 7px; cursor: pointer; + z-index: 9998; font-size: 13px; font-family: sans-serif; + box-shadow: 0 2px 10px rgba(0,0,0,0.5); + transition: background 0.15s; + `; + btn.onmouseenter = () => btn.style.background = "#d48c00"; + btn.onmouseleave = () => btn.style.background = "#b87200"; + btn.onclick = togglePanel; + document.body.appendChild(btn); +} + +// ── Panel ein-/ausblenden ───────────────────────────────────────────────────── +function togglePanel() { + if (!panel) return; + if (panel.style.display === "none") { + refreshGroups(); + panel.style.display = "block"; + } else { + panel.style.display = "none"; + } +} + +// ── Dropdown mit Gruppen befüllen ───────────────────────────────────────────── +function refreshGroups() { + const select = panel.querySelector("#gbp-select"); + const groups = app.graph?._groups ?? []; + select.innerHTML = ""; + + if (groups.length === 0) { + select.innerHTML = ``; + setStatus("Keine Gruppen gefunden."); + return; + } + + groups.forEach((group, i) => { + const opt = document.createElement("option"); + opt.value = i; + opt.textContent = group.title?.trim() || `Gruppe ${i + 1}`; + select.appendChild(opt); + }); + + setStatus(`${groups.length} Gruppe(n) geladen.`); +} + +// ── Modus auf ausgewählte Gruppe anwenden ───────────────────────────────────── +function applyMode(mode, label) { + const select = panel.querySelector("#gbp-select"); + const idx = parseInt(select.value); + if (isNaN(idx) || idx < 0) return; + + const group = (app.graph?._groups ?? [])[idx]; + if (!group) return; + + const nodes = getGroupNodes(group); + if (nodes.length === 0) { + setStatus("⚠️ Keine Nodes in dieser Gruppe."); + return; + } + + for (const node of nodes) { + node.mode = mode; + } + app.graph.setDirtyCanvas(true, true); + setStatus(`${label}: "${group.title || "Gruppe"}" (${nodes.length} Nodes)`); +} + +// ── Statuszeile setzen ──────────────────────────────────────────────────────── +function setStatus(msg) { + const el = panel?.querySelector("#gbp-status"); + if (el) el.textContent = msg; +} + +// ── Nodes einer Gruppe ermitteln ────────────────────────────────────────────── +function getGroupNodes(group) { + if (group._nodes?.length > 0) return [...group._nodes]; + + const [gx, gy] = group.pos; + const [gw, gh] = group.size; + return (app.graph?._nodes ?? []).filter(node => { + const [nx, ny] = node.pos; + const nw = node.size?.[0] ?? 0; + const nh = node.size?.[1] ?? 0; + return nx >= gx && ny >= gy && nx + nw <= gx + gw && ny + nh <= gy + gh; + }); +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..ff3dee7 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,12 @@ +[project] +name = "block-bypass-switch" +version = "1.0.0" +description = "1-Klick Block Bypass Switch Node für ComfyUI" + +[project.urls] +Repository = "" + +[tool.comfy] +PublisherId = "" +DisplayName = "Block Bypass Switch" +Icon = ""