Identity Forge Expression Sheet
Identity Forge's Expression Sheet node
- prompt
- view_label
The name makes you expect a grid image. It isn't one. This node produces no pixels at all - it emits a list of prompts, and ComfyUI's list semantics do the rest: wire it into a CLIP Text Encode and your queue runs once per expression, all of the same described person.
That's the whole trick, and it's a good one. The usual way to get six expressions out of a randomizer is to leave the upstream node on randomize and click queue six times, which is how you end up with six different people pulling six faces. This node takes a resolved character and re-renders it, so the person and outfit read identically across views and only the emotion changes.
What it is, in the wider workflow
Identity Forge is a prompt-side character generator: dropdowns for age, face, hair, outfit, pose, location and light, a constraint engine that stops them contradicting each other, prose out instead of tags. The main node hands you the character as JSON (prompt_json), and two siblings consume it for sheet work - IdentityForgeTurnaround owns the camera, this one owns the face.
Why bother: "same person, different emotion" is the expensive step in any character pipeline, and text is the cheapest pass at it - a storyboard, an acting reference, a batch of one character to seed a LoRA. Prompt-only consistency has limits, but this is the honest first pass.
How it works
character_json takes Identity Forge's prompt_json - the self-describing document with every field resolved to a concrete value. The node parses it, re-runs the engine over it once per view with the engine's schema defaults underneath and the seed pinned to a constant, and overrides exactly three fields per view: expression, mood and smile_type.
Those triples are hardcoded, not re-rolled: neutral is ("neutral", "tranquil", "closed mouth"), laughing is ("laughing", "joyful", "toothy grin"). No two views can land on the same face by accident, and every pairing is one the constraint engine already allows - "laughing" can't drag a "sorrowful" mood along. Your own locks on those fields get overridden; steer the emotion upstream if you care. No RNG, no auto-advancing widget, so it doesn't re-roll between queues the way an index-widget sheet does.
The four widgets that matter
character_json- Identity Forge'sprompt_json. Force-input socket: you have to wire it, you can't type a character in.sheet-Basic (6)gives neutral, smile, laughing, surprised, serious, sad.Full (10)adds angry, smirk, thoughtful, worried.Smiles (3)is smile, laughing, smirk. This is also how many renders one queue costs.framing- overrides the character's own framing, defaulting toHead and shoulders; a face you can't read in a wide shot is a wasted render. Options run Full body, Cowboy, Waist up, Chest up -Unspecifiedleaves the character's framing alone.gaze-Keep the character's gazeby default. Point all ten views at "looking directly into the camera" and it reads as a reference sheet instead of ten candids.
Two outputs, both lists, matched by index: prompt to CLIP Text Encode, view_label (1-neutral, 2-smile, …) to Save Image's filename_prefix so the folder sorts itself. Shorter inputs - negative prompt, model, latent - broadcast off their last element, so a normal graph needs no rewiring.
Install
cd ComfyUI/custom_nodes
git clone https://github.com/EnragedAntelope/comfyui-identity-forge
# restart ComfyUI
Or search Identity Forge in ComfyUI Manager. No Python dependencies at all - requirements.txt says exactly that - but the pack uses the V3 node API and wants ComfyUI 0.25.0 or newer, so a stale install is the one place this silently doesn't appear.
Where people get burned
Ten views is ten renders per queue. On a 20-step checkpoint that's a coffee break - drop to Smiles (3) while you're iterating.
The person still changes between queues if the upstream seed is on randomize. The sheet is stable within a queue, guaranteed. Fix Identity Forge's seed, or save the character to the pack's Vault, or the next queue is a stranger.
A masked character gives you one view. Mascot heads and full helmets have no visible expression, so you get one prompt labelled 1-masked and a log warning. Not a bug - just one image where you expected six.
Long prose on a tag-trained model. The output is sentences, and sentences suit LLM-encoder models - Flux, Qwen, Krea - better than an SDXL anime checkpoint expecting Danbooru tags. If renders drift, set Identity Forge's location and lighting to None.
A NoneType error or blank widget after an update. Fields are built at creation, so a saved graph can hold a stale option. Right-click → Fix node (recreate); values and links survive. Use the pack's own entry - Manager's can fail on the current frontend.
Do you need it
If you've been clicking queue six times and cursing, yes - this deletes a whole category of "why is the face different again" misery. If you need the same pixels across expressions, it can't help; that wants an edit model. Making the words identical is the smaller, more honest half of the problem.
Inputs (4)
| Name | Type | Default | Description |
|---|---|---|---|
| character_json | STRING | Connect Identity Forge's 'prompt_json' output. It is a fully resolved character, so every view is the same person; only the face's emotion changes. | |
| sheet | COMBO | Basic (6) | Which expressions to emit, and so how many images one queue makes. Basic (6) = neutral, smile, laughing, surprised, serious, sad; Full (10) adds angry, smirk, thoughtful, worried. A face-covering character (mascot head, full helmet) emits one view. |
| framing | COMBO | Head and shoulders | How much of the subject each view frames, replacing the character's own framing. 'Head and shoulders' keeps the face large enough to read. 'Unspecified' keeps the character's own framing. |
| gaze | COMBO | Keep the character's gaze | Where every view looks. 'Looking directly into the camera' makes a classic reference sheet; the default keeps the character's own gaze, and 'None' leaves it unstated. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| prompt | STRING | One prompt per expression, as a list. Wire into CLIPTextEncode. |
| view_label | STRING | Matching list of sortable names ('1-neutral', '2-smile', ...). Wire into Save Image's filename_prefix. |