Add string node

This commit is contained in:
Daniel Martinek
2026-03-26 11:17:11 +01:00
parent 085cba73ec
commit db94472a27
2 changed files with 112 additions and 1 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ You can also get the node from comfy manager under the name of More math.
- functions and variables in math expressions
- Conversion between INT and FLOAT; INT and BOOLEAN; AUDIO and IMAGE (red - real - strenght of cosine of frequency; blue - imaginary - strenght of sine of frequency; green - log1p of amplitude - just so it looks good to humans)
- Nodes for FLOAT, CONDITIONING, LATENT, IMAGE, MASK, NOISE, AUDIO, VIDEO, MODEL, CLIP, VAE, SIGMAS and GUIDER
- Nodes for FLOAT, STRING, CONDITIONING, LATENT, IMAGE, MASK, NOISE, AUDIO, VIDEO, MODEL, CLIP, VAE, SIGMAS and GUIDER
- Vector Math: Support for List literals `[v1, v2, ...]` and operations between lists/scalars/tensors
- Custom functions `funcname(variable,variable,...)->expression;` they can be used in any later defined custom function or in expression. Shadowing inbuilt functions do not work. **Be careful with recursion. There is no stack limit. Got to 700 000 iterations before I got bored.**
- Custom variables `varname=expression;` They can be used in any later assigment or final expression.
+111
View File
@@ -0,0 +1,111 @@
from .helper_functions import (
generate_dim_variables,
parse_expr,
getIndexTensorAlongDim,
as_tensor,
normalize_to_common_shape,
make_zero_like,
get_v_variable,
get_f_variable,
checkLazyNew
)
from .Parser.UnifiedMathVisitor import UnifiedMathVisitor
from comfy_api.latest import io
import torch
from .Stack import MrmthStack
import copy
from .ParseTree import MrmthParseTree
class StringMathNode(io.ComfyNode):
"""
Enables math expressions on Audio.
Inputs:
I: Autogrow image inputs (I0, I1, ...)
F: Autogrow float inputs (F0, F1, ...)
Image: Expression
"""
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="mrmth_ag_AudioMathNode",
category="More math",
display_name="Audio math",
inputs=[
io.Autogrow.Input(id="V",template=io.Autogrow.TemplatePrefix(io.String.Input("values", optional=True), prefix="V", min=1, max=50)),
io.Autogrow.Input(id="F", template=io.Autogrow.TemplatePrefix(io.Float.Input("float", default=0.0, optional=True, lazy=True, force_input=True), prefix="F", min=1, max=50)),
io.MultiType.Input(
io.String.Input("Expression", default="", multiline=False),
types=[io.String,MrmthParseTree],
tooltip="3D model file or path string",
),
io.Int.Input(id="batching", default=0),
io.Bool.Input(
id="remember_stack",
default=False,
display_name="Remember stack across batch",
tooltip=(
"If enabled, stack is copied at output leading to changes being remembered during batch operations (node runs multiple times in sucession). If disabled each batch gets it's own copy of the stack."
),
),
MrmthStack.Input(id="stack", tooltip="Access stack between nodes",optional=True)
],
outputs=[
io.Audio.Output(is_output_list=True),
MrmthStack.Output(),
],
)
@classmethod
def check_lazy_status(cls, Expression, V, F,batching=0, remember_stack=False,stack={}):
return checkLazyNew(Expression,V,F)
@classmethod
def execute(cls, V, F, Expression, batching=0, remember_stack=False, stack={}):
variables = {
"a": V.get(0,""), "b": V.get(1,""), "c": V.get(2,""), "d": V.get(3,""),
"w": F.get("F0", 0.0) if F.get("F0") is not None else 0.0,
"x": F.get("F1", 0.0) if F.get("F1") is not None else 0.0,
"y": F.get("F2", 0.0) if F.get("F2") is not None else 0.0,
"z": F.get("F3", 0.0) if F.get("F3") is not None else 0.0
}
v_stacked, v_cnt = get_v_variable(V, length_mismatch="do nothing")
if v_stacked is not None:
variables["V"] = v_stacked
variables["Vcnt"] = float(v_cnt)
variables["V_count"] = float(v_cnt)
f_stacked, f_cnt = get_f_variable(F)
if f_stacked is not None:
variables["F"] = f_stacked
variables["Fcnt"] = float(f_cnt)
variables["F_count"] = float(f_cnt)
for k, val in F.items():
variables[k] = val if val is not None else 0.0
tree = None
if isinstance(Expression,str):
tree = parse_expr(Expression)
else:
tree = Expression
visitor = UnifiedMathVisitor(variables, len(V[0]),torch.device("cpu"),state_storage=stack)
result = visitor.visit(tree)
result = str(result)
if batching and batching > 0:
def chunks(s, n):
"""Produce `n`-character chunks from `s`."""
for start in range(0, len(s), n):
yield s[start:start+n]
stack = stack if remember_stack else copy.deepcopy(stack)
return (chunks(result,batching), stack)
else:
stack = stack if remember_stack else copy.deepcopy(stack)
return (result, stack)