---
name: comfyui_build_emote_workflow
kind: function
lang: py
domain: ml
version: "1.0.0"
purity: pure
signature: "def comfyui_build_emote_workflow(character: str, expression: str, *, ref_face: str | None = None, style: str = \"character portrait\", checkpoint: str = \"IMG_dreamshaper_8.safetensors\", size: int = 512, facedetailer: bool = True, seed: int = 0, lora: str | None = None, lora_strength: float = 1.0, weight: float = 0.85, negative: str | None = None, steps: int = 28, cfg: float = 7.0, sampler_name: str = \"dpmpp_2m\", scheduler: str = \"karras\", fd_denoise: float = 0.45, bbox_model: str = \"face_yolov8m.pt\", filename_prefix: str = \"emote\") -> dict"
description: "Construye el dict (API format) del workflow de UN emote/expresion facial de personaje 2D (alegre, triste, enfadado, sorprendido, neutral...) para sistema de dialogo, retratos reactivos o emotes de chat. La clave es la consistencia del personaje entre expresiones: con ref_face encadena IPAdapter-FaceID para que el rostro sea el mismo y varie SOLO la expresion; con facedetailer regenera la cara con detalle conservando la expresion (Impact-Pack). Compone comfyui_build_ipadapter_workflow / comfyui_build_txt2img_workflow + comfyui_inject_lora + comfyui_build_facedetailer_workflow. Hermano de comfyui_build_portrait_avatar/sprite_sheet_workflow. Pura, sin red ni I/O. class_types verificados contra /object_info (8GB lowvram)."
tags: [comfyui, ml, gamedev-2d, emote, expression, character, dialogue, faceid, ipadapter, facedetailer, workflow]
uses_functions: [comfyui_build_txt2img_workflow_py_ml, comfyui_build_ipadapter_workflow_py_ml, comfyui_inject_lora_py_ml, comfyui_build_facedetailer_workflow_py_ml]
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: []
params:
  - name: character
    desc: "Descripcion del personaje (ej. 'a knight woman with red hair', 'an old wizard with a long white beard'). Se inserta en un prompt scaffold de emote. No puede estar vacio. Manten el MISMO character en todas las expresiones de un set para coherencia de personaje."
  - name: expression
    desc: "Expresion facial a representar (ej. 'happy, smiling', 'sad', 'angry', 'surprised', 'neutral'). Es lo unico que deberia cambiar entre los emotes de un mismo personaje. No puede estar vacio."
  - name: ref_face
    desc: "Nombre de una imagen de rostro de referencia del personaje en input/ del servidor. Si se pasa, encadena IPAdapter-FaceID para que todos los emotes tengan el MISMO rostro (identidad consistente entre expresiones). None = identidad solo por prompt + seed. keyword-only."
  - name: style
    desc: "Descriptor de estilo que mantiene consistentes los emotes de un set (ej. 'character portrait', 'anime portrait', 'realistic RPG portrait'). Pasa el MISMO style + checkpoint + lora a todas las expresiones para coherencia visual. keyword-only."
  - name: checkpoint
    desc: "Checkpoint del servidor. 'IMG_dreamshaper_8.safetensors' (SD1.5, holgado en 8GB lowvram) por defecto. FaceID solo instalado para SD1.5: con checkpoint SDXL deja ref_face=None. keyword-only."
  - name: size
    desc: "Lado del cuadrado en px (width = height = size). 512 SD1.5 por defecto, encuadre tipico de retrato/emote. keyword-only."
  - name: facedetailer
    desc: "Si True encadena FaceDetailer (Impact-Pack) para regenerar la cara con detalle (donde se lee la expresion) y descarta el SaveImage base (el unico PNG guardado es el refinado). False deja la imagen tal cual sale del VAEDecode. keyword-only."
  - name: seed
    desc: "Semilla del KSampler (y del sampler del FaceDetailer). Misma seed + mismo character/ref_face -> mismo personaje; variar solo expression mantiene la identidad y cambia la cara. keyword-only."
  - name: lora
    desc: "LoRA de estilo opcional en models/loras (ej. 'SD15_anime_lineart.safetensors'). None = sin LoRA. keyword-only."
  - name: lora_strength
    desc: "Fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0]. keyword-only."
  - name: weight
    desc: "Peso del IPAdapter-FaceID (solo si ref_face). 0.85 = parecido alto; baja para mas libertad expresiva, sube para mas parecido. keyword-only."
  - name: negative
    desc: "Prompt negativo. None usa el negativo por defecto pensado para emotes (una cara bien formada, sin texto/recorte; NO filtra ninguna expresion). keyword-only."
  - name: steps
    desc: "Pasos del KSampler. keyword-only."
  - name: cfg
    desc: "CFG del KSampler (y del sampler del FaceDetailer). keyword-only."
  - name: sampler_name
    desc: "Sampler del KSampler. keyword-only."
  - name: scheduler
    desc: "Scheduler del KSampler. keyword-only."
  - name: fd_denoise
    desc: "Fuerza de re-difusion de la cara en el FaceDetailer (0.45: refina sin perder la identidad de FaceID ni la expresion). Solo si facedetailer=True. keyword-only."
  - name: bbox_model
    desc: "Modelo de deteccion de caras de Ultralytics para el FaceDetailer ('face_yolov8m.pt'). Solo si facedetailer=True. keyword-only."
  - name: filename_prefix
    desc: "Prefijo del PNG en output/. keyword-only."
output: "dict en API format listo para comfyui_submit_workflow: base txt2img (o IPAdapter-FaceID si ref_face) con prompt scaffold de emote ('portrait of {character}, {expression} expression, emote, {style}, expressive face, ..., clean background') + LoRA de estilo opcional + FaceDetailer (si facedetailer, con SaveImage base descartado y la expresion conservada en el prompt del detailer). UN emote; un set coherente -> llamar por expression con mismo character/style/checkpoint/(lora) y, para el mismo personaje, el mismo ref_face/seed."
tested: true
tests: ["golden facedetailer: clases CheckpointLoaderSimple/KSampler/VAEDecode/FaceDetailer/UltralyticsDetectorProvider; 'portrait of'+'{expression} expression'+'emote'+'clean background' en prompt; un solo SaveImage = fd_save <- FaceDetailer; FaceDetailer.image <- VAEDecode; sin IPAdapterFaceID si no ref_face", "golden detailer conserva expresion: >=2 CLIPTextEncode con la expresion (base + fd_pos), fd_pos enfoca 'detailed face'", "edge facedetailer=False: sin FaceDetailer, SaveImage base <- VAEDecode", "edge ref_face: IPAdapterUnifiedLoaderFaceID/IPAdapterFaceID presentes, LoadImage con ref_face, weight reflejado, KSampler.model <- rama FaceID (consistencia)", "edge expression en prompt", "edge style en prompt", "edge size: width==height==768 (cuadrado)", "edge lora: LoraLoader con strength", "edge seed reflejado en KSampler", "error character vacio -> ValueError", "error expression vacio -> ValueError", "determinismo"]
test_file_path: "python/functions/ml/comfyui_build_emote_workflow_test.py"
file_path: "python/functions/ml/comfyui_build_emote_workflow.py"
---

## Ejemplo

```python
import sys, os
sys.path.insert(0, os.path.join(os.environ["HOME"], "fn_registry", "python", "functions"))
from ml.comfyui_build_emote_workflow import comfyui_build_emote_workflow

# UN emote con cara nitida (FaceDetailer), identidad por prompt + seed.
wf = comfyui_build_emote_workflow(
    "a knight woman with red hair",
    "happy, smiling",
    style="realistic RPG portrait",
    facedetailer=True,
    seed=7,
)
# -> comfyui_submit_workflow(wf) -> comfyui_wait_result -> comfyui_fetch_output_image

# Set de emotes del MISMO personaje: sube una cara de referencia a input/ del
# servidor y pasala como ref_face (IPAdapter-FaceID). Mismo character/ref_face/seed,
# varia SOLO expression -> mismo rostro, distinta expresion.
#   for expr in ["happy, smiling", "sad", "angry", "surprised", "neutral"]:
#       wf = comfyui_build_emote_workflow(
#           "a knight woman with red hair", expr,
#           ref_face="hero_face.png", style="realistic RPG portrait", seed=7)
#       # submit + wait + fetch cada uno -> luego comfyui_build_grid para una hoja.
```

O lanzable directo con: `./fn run comfyui_build_emote_workflow` (imprime nodos + class_types del ejemplo).

## Cuando usarla

Cuando necesites el set de expresiones/emotes de un personaje para un sistema de
dialogo, retratos reactivos (la cara del NPC cambia segun el tono de la
conversacion) o emotes de chat: alegre, triste, enfadado, sorprendido, neutral...
Es UNA expresion por llamada. Para que sea el MISMO personaje en todas, sube una
foto de su cara a `input/` y pasala como `ref_face` -> IPAdapter-FaceID fija el
rostro y solo cambia la expresion entre llamadas. Mantén `character` + `style` +
`checkpoint` + (`lora`) + `seed` constantes y varia solo `expression`. Un set
completo se monta en una hoja de emotes con `comfyui_build_grid` sobre los PNG.
Para un busto generico (sin expresion concreta) usa el hermano
`comfyui_build_portrait_avatar_workflow`.

## Gotchas

- **La expresion es lo unico que debe variar**: para coherencia de personaje en
  un set, cambia SOLO `expression` y deja `character`/`style`/`checkpoint`/`lora`/
  `seed`/`ref_face` igual. Cambiar otros parametros rompe la identidad entre
  emotes.
- **FaceID solo SD1.5**: `ref_face` requiere los modelos IPAdapter-FaceID, que en
  este servidor solo estan para SD1.5 (IMG_dreamshaper_8). Con un checkpoint SDXL deja
  `ref_face=None` (identidad por prompt + seed) o usa un checkpoint SD1.5.
- **El detailer conserva la expresion**: el prompt del FaceDetailer incluye
  `{expression} expression` para no perder el emote al refinar la cara. Reutiliza
  el `CheckpointLoaderSimple` crudo (no la rama IPAdapter); por eso `fd_denoise`
  es bajo (0.45): refina ojos/boca/cejas preservando la identidad de FaceID. Si la
  expresion se diluye al refinar, sube `fd_denoise` ligeramente; si se aleja el
  parecido, bajalo (0.3-0.4) o sube `weight`.
- **SaveImage unico con facedetailer**: cuando `facedetailer=True` se descarta el
  SaveImage base y solo se guarda el PNG del detailer (`fd_save`) — no hay doble
  guardado. Con `facedetailer=False` el SaveImage base toma del VAEDecode.
- **Requiere custom nodes instalados**: IPAdapter_plus (cubiq) para FaceID e
  Impact-Pack para FaceDetailer/UltralyticsDetectorProvider. Si un nodo falta, el
  submit fallara; consulta `/object_info` del servidor para confirmar.
- **El negativo por defecto no filtra expresiones**: 'neutral', 'sad', etc. son
  expresiones validas, por eso el negativo solo cubre defectos de cara/anatomia,
  no estados de animo. Si pasas un `negative` propio, no incluyas terminos que
  contradigan la `expression` pedida.
- **8GB lowvram**: `size=512` con IMG_dreamshaper_8 va holgado; SDXL pide mas VRAM y
  resolucion mayor. Si hay OOM, baja `size` o desactiva `facedetailer`.
- Es una funcion **pura**: solo arma el dict. La generacion real (GPU) la hacen
  `comfyui_submit_workflow` + `comfyui_wait_result` + `comfyui_fetch_output_image`.
