mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-06-12 17:27:26 +08:00
jsonata PyPI package has critical issues: no Python 3.12/3.13 wheels, no ARM/Apple Silicon wheels, abandoned (last commit 2023), C extension. Replace with simpleeval (pure Python, 3.4M downloads/month, MIT, AST-based security). Add math module functions (sqrt, ceil, floor, log, sin, cos, tan) and variadic sum() supporting both sum(values) and sum(a, b, c). Pin version to >=1.0,<2.0. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
103 lines
2.8 KiB
Python
103 lines
2.8 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:
|
|
template = io.MatchType.Template(
|
|
"num", allowed_types=[io.Float, io.Int]
|
|
)
|
|
autogrow = io.Autogrow.TemplateNames(
|
|
input=io.MatchType.Input("value", template=template),
|
|
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.MatchType.Output(
|
|
template=template, display_name="result"
|
|
),
|
|
],
|
|
)
|
|
|
|
@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(result)
|
|
|
|
|
|
class MathExtension(ComfyExtension):
|
|
@override
|
|
async def get_node_list(self) -> list[type[io.ComfyNode]]:
|
|
return [MathExpressionNode]
|
|
|
|
|
|
async def comfy_entrypoint() -> MathExtension:
|
|
return MathExtension()
|