from comfy_api.latest import io from inspect import cleandoc from antlr4 import InputStream, CommonTokenStream from .Parser.MathExprLexer import MathExprLexer from .Parser.MathExprParser import MathExprParser import re from .Stack import MrmthStack import copy import comfy.utils class CLIPMathNode(io.ComfyNode): """ This node enables the use of math expressions on CLIP weights using Autogrow inputs. """ @classmethod def define_schema(cls) -> io.Schema: return io.Schema( node_id="mrmth_ag_CLIPMathNode", display_name="CLIP Math", category="More math", inputs=[ io.Autogrow.Input(id="V",template=io.Autogrow.TemplatePrefix(io.Clip.Input("values"), 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.String.Input(id="Expression", default="I0*(1-F0)+I1*F0", tooltip="Expression to apply on weights"), io.Combo.Input( id="length_mismatch", options=["do nothing","error","tile", "pad"], display_name="on size mismatch", default="error", tooltip="How to handle mismatched layer counts. For models, this usually defaults to broadcast (zero for missing layers)." ), MrmthStack.Input(id="stack", tooltip="Access stack between nodes",optional=True) ], outputs=[ io.Clip.Output(), MrmthStack.Output(), ], ) tooltip = cleandoc(__doc__) @classmethod def check_lazy_status(cls, Expression, V, F, length_mismatch="tile",stack={}): input_stream = InputStream(Expression) lexer = MathExprLexer(input_stream) stream = CommonTokenStream(lexer) stream.fill() # Support aliases aliases_img = {"a": "V0", "b": "V1", "c": "V2", "d": "V3"} aliases_flt = {"w": "F0", "x": "F1", "y": "F2", "z": "F3"} needed = [] needed1 = [] for token in filter(lambda t: t.type == MathExprParser.VARIABLE, stream.tokens): var_name = token.text if re.match(r"[VF][0-9]+", var_name): needed.append(var_name) elif var_name in aliases_img: needed.append(aliases_img[var_name]) elif var_name in aliases_flt: needed.append(aliases_flt[var_name]) for v in needed: if v.startswith("V"): if v not in V or V[v] is None: needed1.append(v) elif v.startswith("F"): if v not in F or F[v] is None: needed1.append(v) return needed1 @classmethod def execute(cls, V, F, Expression, length_mismatch="tile",stack={}) -> io.NodeOutput: # Determine reference CLIP a = V.get("V0") stack = copy.deepcopy(stack) if stack is not None else {} if a is None: for m in V.values(): if m is not None: a = m break if a is None: raise ValueError("At least one input CLIP is required.") # Prepare CLIP patchers patchers_V = {} for k, v in V.items(): if v is not None: patchers_V[k] = v.patcher # Call autogrow patch calculation from .modelLikeCommon import calculate_patches_autogrow layer_count = V.get("V0").model.state_dict().__len__() if hasattr(V.get("V0"), "model") and hasattr(V.get("V0").model, "state_dict") else 0 pbar = comfy.utils.ProgressBar(layer_count) aliases = {"a": "V0", "b": "V1", "c": "V2", "d": "V3", "w": "F0", "x": "F1", "y": "F2", "z": "F3"} patches = calculate_patches_autogrow(Expression, V=patchers_V, F=F,pbar=pbar, mapping=aliases,stack=stack) out_clip = a.clone() if patches: out_clip.add_patches(patches, 1.0, 1.0) return (out_clip,stack)