Files
ComfyUl-FlowBranch/nodes.py
T
2026-07-30 06:39:46 +08:00

404 lines
14 KiB
Python

from __future__ import annotations
import json
from comfy_execution.graph_utils import ExecutionBlocker
class AnyType(str):
def __ne__(self, other):
return False
ANY = AnyType("*")
MISSING = object()
CATEGORY = "流程分支"
LEGACY_CATEGORY = "流程分支/旧版"
class FlexibleOptionalInputType(dict):
"""Allow frontend-defined lazy inputs while keeping known internal inputs typed."""
def __init__(self, dynamic_spec, data=None):
super().__init__(data or {})
self.dynamic_spec = dynamic_spec
def __getitem__(self, key):
return super().__getitem__(key) if dict.__contains__(self, key) else self.dynamic_spec
def __contains__(self, key):
return True
def _blocked(message: str):
return (ExecutionBlocker(f"[流程分支] {message}"),)
def _compile_error(error: str):
return _blocked(error) if error else None
class FlowPublish:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"channel": ("STRING", {
"default": "原始图像",
"multiline": False,
"tooltip": "结果名称。同一工作流中每个名称只能有一个发布位置。",
}),
},
"optional": {
"value": (ANY, {"forceInput": True}),
},
}
RETURN_TYPES = (ANY,)
RETURN_NAMES = ("值",)
FUNCTION = "publish"
CATEGORY = CATEGORY
DESCRIPTION = "给任意类型数据一个可读名称;只有被读取时才参与执行。"
@classmethod
def publish(cls, channel: str, value=MISSING):
if value is MISSING:
return _blocked(f"发送结果“{channel.strip() or '(空名称)'}”没有输入数据。")
return (value,)
class FlowGet:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"channel": ("STRING", {
"default": "原始图像",
"multiline": False,
"tooltip": "读取同名发送结果或流程阶段结果。",
}),
},
"optional": {
"fallback": (ANY, {"forceInput": True, "lazy": True}),
"source": (ANY, {"forceInput": True, "lazy": True}),
"compile_error": ("STRING", {"default": "", "multiline": False}),
},
}
RETURN_TYPES = (ANY,)
RETURN_NAMES = ("值",)
FUNCTION = "get"
CATEGORY = CATEGORY
DESCRIPTION = "读取命名结果;找不到时可使用回退输入。"
@classmethod
def check_lazy_status(cls, channel: str, fallback=MISSING, source=MISSING, compile_error: str = ""):
if compile_error:
return []
if source is not MISSING:
return ["source"] if source is None else []
if fallback is not MISSING:
return ["fallback"] if fallback is None else []
return []
@classmethod
def get(cls, channel: str, fallback=MISSING, source=MISSING, compile_error: str = ""):
error = _compile_error(compile_error)
if error:
return error
if source is not MISSING:
return (source,)
if fallback is not MISSING:
return (fallback,)
return _blocked(f"读取结果“{channel.strip() or '(空名称)'}”找不到发布位置,也没有连接回退输入。")
class FlowPipeline:
EMPTY_CONFIG = json.dumps({"version": 2, "stages": []}, ensure_ascii=False, separators=(",", ":"))
@classmethod
def INPUT_TYPES(cls):
known_optional = {
"source": (ANY, {"forceInput": True, "lazy": True}),
"pipeline_result": (ANY, {"forceInput": True, "lazy": True}),
"selected_value": (ANY, {"forceInput": True, "lazy": True}),
"compile_error": ("STRING", {"default": "", "multiline": False}),
"stage_name": ("STRING", {"default": "", "multiline": False}),
"selected_name": ("STRING", {"default": "", "multiline": False}),
"__stage_internal": ("BOOLEAN", {"default": False}),
"__flow_generated": ("BOOLEAN", {"default": False}),
}
return {
"required": {
"input_channel": ("STRING", {
"default": "原始图像",
"multiline": False,
"tooltip": "第一个阶段读取的结果名称。",
}),
"output_channel": ("STRING", {
"default": "最终图像",
"multiline": False,
"tooltip": "整个流程完成后发布的结果名称。",
}),
"pipeline_config": ("STRING", {
"default": cls.EMPTY_CONFIG,
"multiline": False,
}),
},
"optional": FlexibleOptionalInputType(
(ANY, {"forceInput": True, "lazy": True}),
known_optional,
),
}
RETURN_TYPES = (ANY,)
RETURN_NAMES = ("流程结果",)
FUNCTION = "select"
CATEGORY = CATEGORY
DESCRIPTION = "可无限添加阶段和方案的惰性流程编排器。"
@classmethod
def check_lazy_status(cls, input_channel: str, output_channel: str, pipeline_config: str,
source=MISSING, pipeline_result=MISSING, selected_value=MISSING,
compile_error: str = "", **kwargs):
if compile_error:
return []
if kwargs.get("__stage_internal", False):
if selected_value is not MISSING:
return ["selected_value"] if selected_value is None else []
if source is not MISSING:
return ["source"] if source is None else []
return []
if pipeline_result is not MISSING:
return ["pipeline_result"] if pipeline_result is None else []
if source is not MISSING:
return ["source"] if source is None else []
return []
@classmethod
def select(cls, input_channel: str, output_channel: str, pipeline_config: str,
source=MISSING, pipeline_result=MISSING, selected_value=MISSING,
compile_error: str = "", **kwargs):
error = _compile_error(compile_error)
if error:
return error
if kwargs.get("__stage_internal", False):
if selected_value is not MISSING:
return (selected_value,)
if source is not MISSING:
return (source,)
stage_name = kwargs.get("stage_name") or output_channel or "未命名阶段"
return _blocked(f"阶段“{stage_name}”找不到上一阶段结果。")
if pipeline_result is not MISSING:
return (pipeline_result,)
if source is not MISSING:
return (source,)
return _blocked(f"流程找不到起点结果“{input_channel.strip() or '(空名称)'}”。")
class FlowStage:
DEPRECATED = True
CATEGORY = LEGACY_CATEGORY
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input_channel": ("STRING", {
"default": "上一阶段",
"multiline": False,
"tooltip": "上一步成功结果的通道。",
}),
"output_channel": ("STRING", {
"default": "本阶段",
"multiline": False,
"tooltip": "本阶段结果发布到这个通道。",
}),
"enabled": ("BOOLEAN", {
"default": True,
"label_on": "启用",
"label_off": "旁路",
}),
},
"optional": {
"processed": (ANY, {"forceInput": True, "lazy": True}),
"source": (ANY, {"forceInput": True, "lazy": True}),
"compile_error": ("STRING", {"default": "", "multiline": False}),
},
}
RETURN_TYPES = (ANY,)
RETURN_NAMES = ("阶段结果",)
FUNCTION = "select"
CATEGORY = LEGACY_CATEGORY
DESCRIPTION = "启用时采用处理结果,关闭或处理结果未连接时传递上一步,并发布输出通道。"
@classmethod
def check_lazy_status(cls, input_channel: str, output_channel: str, enabled: bool,
processed=MISSING, source=MISSING, compile_error: str = ""):
if compile_error:
return []
if enabled and processed is not MISSING:
return ["processed"] if processed is None else []
if source is not MISSING:
return ["source"] if source is None else []
return []
@classmethod
def select(cls, input_channel: str, output_channel: str, enabled: bool,
processed=MISSING, source=MISSING, compile_error: str = ""):
error = _compile_error(compile_error)
if error:
return error
if enabled and processed is not MISSING:
return (processed,)
if source is not MISSING:
return (source,)
state = "启用但处理结果未连接" if enabled else "处于旁路状态"
return _blocked(
f"阶段“{input_channel}{output_channel}{state},但输入通道“{input_channel}”没有可用数据。"
)
class FlowRoute:
DEPRECATED = True
CATEGORY = LEGACY_CATEGORY
ROUTES = ("旁路", "方案 1", "方案 2", "方案 3")
OPTION_NAMES = {
"方案 1": "option_1",
"方案 2": "option_2",
"方案 3": "option_3",
}
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input_channel": ("STRING", {
"default": "上一阶段",
"multiline": False,
"tooltip": "旁路或方案缺失时使用的上一步通道。",
}),
"output_channel": ("STRING", {
"default": "本阶段",
"multiline": False,
"tooltip": "最终选择结果发布到这个通道。",
}),
"route": (cls.ROUTES, {"default": "旁路"}),
},
"optional": {
"option_1": (ANY, {"forceInput": True, "lazy": True}),
"option_2": (ANY, {"forceInput": True, "lazy": True}),
"option_3": (ANY, {"forceInput": True, "lazy": True}),
"source": (ANY, {"forceInput": True, "lazy": True}),
"compile_error": ("STRING", {"default": "", "multiline": False}),
},
}
RETURN_TYPES = (ANY,)
RETURN_NAMES = ("选择结果",)
FUNCTION = "select"
CATEGORY = LEGACY_CATEGORY
DESCRIPTION = "仅执行选中的方案;旁路或所选方案未连接时返回输入通道,并发布输出通道。"
@classmethod
def check_lazy_status(cls, input_channel: str, output_channel: str, route: str,
option_1=MISSING, option_2=MISSING, option_3=MISSING,
source=MISSING, compile_error: str = ""):
if compile_error:
return []
option_name = cls.OPTION_NAMES.get(route)
options = {
"option_1": option_1,
"option_2": option_2,
"option_3": option_3,
}
option_value = options.get(option_name, MISSING)
if option_name and option_value is not MISSING:
return [option_name] if option_value is None else []
if source is not MISSING:
return ["source"] if source is None else []
return []
@classmethod
def select(cls, input_channel: str, output_channel: str, route: str,
option_1=MISSING, option_2=MISSING, option_3=MISSING,
source=MISSING, compile_error: str = ""):
error = _compile_error(compile_error)
if error:
return error
options = {"方案 1": option_1, "方案 2": option_2, "方案 3": option_3}
selected = options.get(route, MISSING)
if selected is not MISSING:
return (selected,)
if source is not MISSING:
return (source,)
return _blocked(
f"多路方案“{input_channel}{output_channel}”无法使用“{route}”,"
f"且输入通道“{input_channel}”没有可回退的数据。"
)
class FlowIf:
DEPRECATED = True
CATEGORY = LEGACY_CATEGORY
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"condition": ("BOOLEAN", {
"default": True,
"label_on": "真",
"label_off": "假",
}),
},
"optional": {
"on_true": (ANY, {"forceInput": True, "lazy": True}),
"on_false": (ANY, {"forceInput": True, "lazy": True}),
},
}
RETURN_TYPES = (ANY,)
RETURN_NAMES = ("选择结果",)
FUNCTION = "select"
CATEGORY = LEGACY_CATEGORY
DESCRIPTION = "惰性布尔分支;只执行选中分支,选中分支未连接时自动使用另一分支。"
@classmethod
def check_lazy_status(cls, condition: bool, on_true=MISSING, on_false=MISSING):
primary_name, primary = ("on_true", on_true) if condition else ("on_false", on_false)
fallback_name, fallback = ("on_false", on_false) if condition else ("on_true", on_true)
if primary is not MISSING:
return [primary_name] if primary is None else []
if fallback is not MISSING:
return [fallback_name] if fallback is None else []
return []
@classmethod
def select(cls, condition: bool, on_true=MISSING, on_false=MISSING):
primary = on_true if condition else on_false
fallback = on_false if condition else on_true
if primary is not MISSING:
return (primary,)
if fallback is not MISSING:
return (fallback,)
return _blocked("条件选择的两个分支都没有连接。")
NODE_CLASS_MAPPINGS = {
"FlowBranchPublish": FlowPublish,
"FlowBranchGet": FlowGet,
"FlowBranchPipeline": FlowPipeline,
"FlowBranchStage": FlowStage,
"FlowBranchRoute": FlowRoute,
"FlowBranchIf": FlowIf,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"FlowBranchPublish": "发送结果",
"FlowBranchGet": "读取结果",
"FlowBranchPipeline": "流程编排器",
"FlowBranchStage": "[旧版] 阶段开关",
"FlowBranchRoute": "[旧版] 多路方案",
"FlowBranchIf": "[旧版] 条件选择",
}