mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-06-12 17:27:26 +08:00
Allow mixing INT and FLOAT connections on the same node by switching from MatchType (which forces all inputs to the same type) to MultiType. Output both FLOAT and INT so users can pick the type they need. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
99 lines
2.7 KiB
Python
99 lines
2.7 KiB
Python
"""Math expression node using simpleeval for safe evaluation.
|
|
|
|
Provides a ComfyMathExpression node that evaluates math expressions
|
|
against dynamically-grown numeric inputs.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import math
|
|
|
|
from simpleeval import simple_eval
|
|
from typing_extensions import override
|
|
|
|
from comfy_api.latest import ComfyExtension, io
|
|
|
|
|
|
def _variadic_sum(*args):
|
|
"""Support both sum(values) and sum(a, b, c)."""
|
|
if len(args) == 1 and hasattr(args[0], "__iter__"):
|
|
return sum(args[0])
|
|
return sum(args)
|
|
|
|
|
|
MATH_FUNCTIONS = {
|
|
"sum": _variadic_sum,
|
|
"min": min,
|
|
"max": max,
|
|
"abs": abs,
|
|
"round": round,
|
|
"pow": pow,
|
|
"sqrt": math.sqrt,
|
|
"ceil": math.ceil,
|
|
"floor": math.floor,
|
|
"log": math.log,
|
|
"log2": math.log2,
|
|
"log10": math.log10,
|
|
"sin": math.sin,
|
|
"cos": math.cos,
|
|
"tan": math.tan,
|
|
}
|
|
|
|
|
|
def _positional_alias(index: int) -> str:
|
|
"""Convert 0-based index to single letter: 0->a, 1->b, ..., 25->z."""
|
|
return chr(ord("a") + index)
|
|
|
|
|
|
class MathExpressionNode(io.ComfyNode):
|
|
"""Evaluates a math expression against dynamically-grown inputs."""
|
|
|
|
@classmethod
|
|
def define_schema(cls) -> io.Schema:
|
|
autogrow = io.Autogrow.TemplateNames(
|
|
input=io.MultiType.Input("value", [io.Float, io.Int]),
|
|
names=[_positional_alias(i) for i in range(26)],
|
|
min=1,
|
|
)
|
|
return io.Schema(
|
|
node_id="ComfyMathExpression",
|
|
display_name="Math Expression",
|
|
category="math",
|
|
search_aliases=[
|
|
"expression", "formula", "calculate", "eval", "math"
|
|
],
|
|
inputs=[
|
|
io.String.Input("expression", default="a + b"),
|
|
io.Autogrow.Input("values", template=autogrow),
|
|
],
|
|
outputs=[
|
|
io.Float.Output(display_name="FLOAT"),
|
|
io.Int.Output(display_name="INT"),
|
|
],
|
|
)
|
|
|
|
@classmethod
|
|
def execute(
|
|
cls, expression: str, values: io.Autogrow.Type
|
|
) -> io.NodeOutput:
|
|
context: dict = dict(values)
|
|
context["values"] = list(values.values())
|
|
|
|
result = simple_eval(expression, names=context, functions=MATH_FUNCTIONS)
|
|
if isinstance(result, bool) or not isinstance(result, (int, float)):
|
|
raise ValueError(
|
|
f"Math Expression '{expression}' must evaluate to a numeric result, "
|
|
f"got {type(result).__name__}: {result!r}"
|
|
)
|
|
return io.NodeOutput(float(result), int(result))
|
|
|
|
|
|
class MathExtension(ComfyExtension):
|
|
@override
|
|
async def get_node_list(self) -> list[type[io.ComfyNode]]:
|
|
return [MathExpressionNode]
|
|
|
|
|
|
async def comfy_entrypoint() -> MathExtension:
|
|
return MathExtension()
|