Files
kantan-kanto-ComfyUI-Script…/__init__.py
T

205 lines
5.6 KiB
Python

# ComfyUI-ScriptFlow
# Copyright (C) 2026 kantan-kanto (https://github.com/kantan-kanto)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
from __future__ import annotations
__version__ = "1.0.1"
import datetime
import math
import random
from typing import Any, Dict
_ALLOWED_BUILTINS = {
"len": len,
"min": min,
"max": max,
"sum": sum,
"abs": abs,
"round": round,
"int": int,
"float": float,
"str": str,
"bool": bool,
"sorted": sorted,
"reversed": reversed,
"enumerate": enumerate,
"range": range,
"zip": zip,
"map": map,
"filter": filter,
"any": any,
"all": all,
"pow": pow,
"divmod": divmod,
"list": list,
"dict": dict,
"tuple": tuple,
}
class AnyType(str):
def __ne__(self, __value: object) -> bool: # allow any connection type
return False
_ANY_TYPE = AnyType("*")
def _build_globals() -> Dict[str, Any]:
return {
"__builtins__": _ALLOWED_BUILTINS,
"random": random,
"datetime": datetime,
"math": math,
}
def _validate_outputs(locals_dict: Dict[str, Any]) -> None:
text_keys = ("ot1", "ot2", "ot3")
value_keys = ("ov1", "ov2", "ov3")
for key in text_keys:
val = locals_dict.get(key)
if val is None:
continue
if not isinstance(val, str):
raise ValueError(f"{key} must be str or None, got {type(val).__name__}")
for key in value_keys:
val = locals_dict.get(key)
if val is None:
continue
if not isinstance(val, (int, float)):
raise ValueError(f"{key} must be int/float or None, got {type(val).__name__}")
def _select_numeric_value(
base_value: Any, override_value: Any, label: str
) -> float | int:
if override_value is None:
return base_value
if not isinstance(override_value, (int, float)):
raise ValueError(f"{label} must be int/float, got {type(override_value).__name__}")
return override_value
def _select_text_value(base_value: Any, override_value: Any, label: str) -> str:
if override_value is None:
return base_value
if not isinstance(override_value, str):
raise ValueError(f"{label} must be str, got {type(override_value).__name__}")
return override_value
class MultiOutputScript:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"code": (
"STRING",
{
"multiline": True,
"default": (
"# UI inputs: in_text_1, in_text_2, in_text_3, in_value_1, in_value_2, in_value_3\n"
"# Script inputs: it1, it2, it3, iv1, iv2, iv3\n"
"# UI outputs: out_text_1, out_text_2, out_text_3, out_value_1, out_value_2, out_value_3\n"
"# Script outputs: ot1, ot2, ot3, ov1, ov2, ov3\n"
"# Example:\n"
"width, height = (512, 384) if iv1 > iv2 else (384, 512)\n"
"ov1, ov2 = width, height\n"
),
},
),
},
"optional": {
"in_text_1": (_ANY_TYPE,),
"in_text_2": (_ANY_TYPE,),
"in_text_3": (_ANY_TYPE,),
"in_value_1": (_ANY_TYPE,),
"in_value_2": (_ANY_TYPE,),
"in_value_3": (_ANY_TYPE,),
},
}
RETURN_TYPES = (
"STRING",
"STRING",
"STRING",
"INT",
"INT",
"INT",
)
RETURN_NAMES = (
"out_text_1",
"out_text_2",
"out_text_3",
"out_value_1",
"out_value_2",
"out_value_3",
)
FUNCTION = "run"
CATEGORY = "utils"
def run(
self,
code: str,
in_text_1: Any = None,
in_text_2: Any = None,
in_text_3: Any = None,
in_value_1: Any = None,
in_value_2: Any = None,
in_value_3: Any = None,
):
it1 = _select_text_value("", in_text_1, "in_text_1")
it2 = _select_text_value("", in_text_2, "in_text_2")
it3 = _select_text_value("", in_text_3, "in_text_3")
iv1 = _select_numeric_value(0.0, in_value_1, "in_value_1")
iv2 = _select_numeric_value(0.0, in_value_2, "in_value_2")
iv3 = _select_numeric_value(0.0, in_value_3, "in_value_3")
locals_dict: Dict[str, Any] = {
"it1": it1,
"it2": it2,
"it3": it3,
"iv1": iv1,
"iv2": iv2,
"iv3": iv3,
"ot1": None,
"ot2": None,
"ot3": None,
"ov1": None,
"ov2": None,
"ov3": None,
}
globals_dict = _build_globals()
exec(code, globals_dict, locals_dict)
_validate_outputs(locals_dict)
return (
locals_dict.get("ot1"),
locals_dict.get("ot2"),
locals_dict.get("ot3"),
None if locals_dict.get("ov1") is None else int(locals_dict.get("ov1")),
None if locals_dict.get("ov2") is None else int(locals_dict.get("ov2")),
None if locals_dict.get("ov3") is None else int(locals_dict.get("ov3")),
)
NODE_CLASS_MAPPINGS = {
"MultiOutputScript": MultiOutputScript,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"MultiOutputScript": "MultiOutputScript",
}