Latent expression evaluator
ZEvalNode turns expressions into edits
- latent_1
- LATENT
You want to blend two latents 70/30, clamp the result, and zero a channel. ComfyUI's answer is five nodes wired end to end - LatentMultiply here, LatentAdd there - and you rewire them every time you change the math. ZEvalNode's answer is one text box: clamp(l1 * 0.7 + l2 * 0.3, -2, 2). It's a small, obscure pack from a single author, and that's honestly the appeal. No API, no key, no model downloads - just a little calculator for latent tensors.
The name matters here. You're not editing pixels; you're working on the compressed latent tensors the diffusion model actually denoises - 4 channels for SD/SDXL, 16 for Flux. Editing before a resample pass is what gives you those seamless image merges you can't get by compositing pixels, and it's the same "you have to work directly with latents" territory that video workflows live in. ZEvalNode makes that direct manipulation scriptable instead of node-per-operation.
How it works
Feed latents in and they're automatically named l1, l2, etc. in order. The node uses the lark parser to turn your expression into an AST and evaluates it against a latent map. One genuinely nice touch: every extra latent is bilinearly resized to match l1's resolution first, so mismatched sizes won't crash you - channel counts still have to match, more on that below.
The operators are arithmetic (+ - * /, parens), assignment (a = l1 * 0.7;), plus noise(), chanmap(), clamp(), roll(), shuffle(), flip(), sigmoid(), relu(), and resize(). Realistic starters:
l1 + noise() * 0.1- nudge a latent with scaled noisechanmap(l1, 0, 1, 2, -)- copy the first three channels, zero the fourtha = l1 * 0.7; b = l2 * 0.3; clamp(a + b, -2.0, 2.0)- blended and tamed (note the trailing semicolons on assignments; the last line is the output)
The inputs and outputs that matter
There are really only two knobs. expression is a multiline string, default "l1" - it's the whole game, and multiline means you can write small scripts. latent_1 takes your first LATENT; the node's front-end JS adds latent_2, latent_3… slots automatically as you plug more wires in. The single LATENT output goes into a KSampler's latent input to resample the edited result, or straight to a VAE Decode if you just want to look at what you made.
Install
ComfyUI Manager, search ComfyUI-latent-eval, install, restart. Or by hand:
cd ComfyUI/custom_nodes
git clone https://github.com/herczy/ComfyUI-latent-eval
cd ComfyUI-latent-eval
pip install -r requirements.txt
The only dependency is lark, the parser - torch is already in your ComfyUI. No models to fetch. One caveat: the library metadata pins Python ≥ 3.13, so if your ComfyUI install runs an older Python (common in portable builds), pip may grumble; the node itself only needs lark and torch.
Where it bites
Be honest about the rough edges, because this is an early-stage pack - one commit as of writing - and the README is optimistic in places.
resize/zoom/flipcrash on the current release. The README documentszoom(expr, scale)andresize(height, width), but the grammar that actually ships only parsesresize(...), and even then the resize/zoom code paths die with an internal assertion error, whileflip's grammar hands the function one argument too many. If you hit "Latent expression parsing failed" or an assertion error on those, it's not you. Stick to arithmetic,chanmap,clamp,noise,roll,shuffle,sigmoid, andrelu- they all work.- Channel counts must match.
l1 + l2broadcasts fine across resolutions (the node resizes for you) but not across channel counts - blending a 4-channel SD latent with a 16-channel Flux one throws a shape error. noise()is pre-scaled by 0.18215, the SD-family latent scaling factor, so it lands in the right magnitude for SD/SDXL latents.- Parse errors are blunt. The node wraps everything in a "parsing failed" RuntimeError; check commas, parens, and the
-inchanmap.
Verdict: if you occasionally blend latents, the built-in nodes are fine and you don't need this. If you find yourself doing latent math constantly and want it editable as a string instead of a tangle of wires, it's a genuinely handy little calculator - just don't lean on the resize path yet.
Inputs (2)
| Name | Type | Default | Description |
|---|---|---|---|
| expression | STRING | l1 | — |
| latent_1 | LATENT | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| LATENT | LATENT | — |