---
name: comfyui_generate_character_set_oneshot
kind: pipeline
lang: py
domain: pipelines
version: "1.0.0"
purity: impure
signature: "def comfyui_generate_character_set_oneshot(character: str, *, style: str = \"game character, full body, clean background\", checkpoint: str = \"IMG_dreamshaper_8.safetensors\", base_kind: str = \"enemy_creature\", directions: int = 8, make_directional: bool = True, make_3d: bool = True, directional_model: str = \"sv3d\", elevation: float = 15.0, seed: int = 0, size: int = 512, directional_size: int | None = None, flatten_color: tuple = (255, 255, 255), variant_3d: str = \"mini\", lora: str | None = None, lora_strength: float = 1.0, server: str = \"http://127.0.0.1:8188\", export_godot: str | None = None, out_dir: str | None = None, wait_timeout: float = 600.0, free_vram: bool = True, godot_bin: str | None = None) -> dict"
description: "Culminacion del grupo gamedev-2d: genera el set COMPLETO y coherente de UN personaje de juego de un solo tiro — (1) imagen base 2D recortada a alpha, (2) sprite direccional N-way (vistas 3D consistentes SV3D/Zero123) y (3) malla 3D .glb (Hunyuan3D-2). La CLAVE es la coherencia cross-frontera: la vista direccional y la malla 3D parten de la MISMA imagen base 2D aplanada, no de tres generaciones independientes, asi que las tres representaciones son del MISMO personaje con el mismo estilo (no tres personajes distintos). Promueve a UNA llamada la secuencia que hoy exige 4 funciones a mano (issue 0087). Compone: un builder de personaje (enemy_creature/portrait_avatar/topdown_sprite) + comfyui_flatten_alpha_on_color + comfyui_image_to_3d_oneshot + comfyui_build_directional_sprite_workflow + submit/wait/fetch + comfyui_export_asset_to_godot. Secuencial liberando VRAM (POST /free) entre los pasos pesados para caber en 8 GB; el 3D va antes que el direccional (SV3D es el de mayor pico). Un fallo aislado (p.ej. OOM en el 3D) NO aborta el resto: deja el set PARCIAL. Devuelve {ok, character, style, checkpoint, base_kind, seed, coherence_note, base_image, base_flat, base_prompt_id, directional, mesh, exported, steps, error}. Impuro: HTTP a ComfyUI + disco + (export) subprocess."
tags: [gamedev-2d, pipelines, comfyui, ml, godot]
uses_functions: [comfyui_build_enemy_creature_workflow_py_ml, comfyui_build_portrait_avatar_workflow_py_ml, comfyui_build_topdown_sprite_workflow_py_ml, comfyui_build_directional_sprite_workflow_py_ml, comfyui_flatten_alpha_on_color_py_ml, comfyui_submit_workflow_py_ml, comfyui_wait_result_py_ml, comfyui_fetch_output_image_py_ml, comfyui_image_to_3d_oneshot_py_pipelines, comfyui_export_asset_to_godot_py_pipelines]
uses_types: []
returns: []
returns_optional: false
error_type: "error_py_core"
imports: [comfyui_build_enemy_creature_workflow_py_ml, comfyui_build_directional_sprite_workflow_py_ml, comfyui_flatten_alpha_on_color_py_ml, comfyui_image_to_3d_oneshot_py_pipelines, comfyui_submit_workflow_py_ml, comfyui_wait_result_py_ml, comfyui_fetch_output_image_py_ml, comfyui_export_asset_to_godot_py_pipelines]
tested: true
test_file_path: "python/functions/pipelines/comfyui_generate_character_set_oneshot_test.py"
tests: [test_empty_character_fails, test_unknown_base_kind_fails_without_network, test_nothing_to_generate_fails, test_base_builder_introspection_injects_coherence, test_view_order_labels_8way, test_coherence_same_base_feeds_3d_and_directional, test_directional_downloads_all_frames, test_isolated_3d_failure_leaves_partial_set, test_export_godot_called_for_base_and_mesh]
file_path: "python/functions/pipelines/comfyui_generate_character_set_oneshot.py"
params:
  - name: character
    desc: "descripcion del personaje ('armored paladin', 'goblin warrior', 'fire mage'). Se pasa como primer posicional al builder de la base 2D. No puede estar vacio."
  - name: style
    desc: "estilo comun de TODO el set (la firma visual compartida); el builder base lo usa/concatena segun su firma. keyword-only."
  - name: checkpoint
    desc: "modelo base de la generacion 2D, compartido por base y direccional. keyword-only."
  - name: base_kind
    desc: "builder de personaje para la base 2D — uno de supported_base_kinds(): 'enemy_creature' (default, cuerpo entero recortable, ideal para turntable 3D), 'portrait_avatar' (busto), 'topdown_sprite' (cenital). keyword-only."
  - name: directions
    desc: "numero de direcciones del sprite direccional. 8 = 8-way N/NE/E/SE/S/SW/W/NW; 4 = N/E/S/W (RPG clasico). keyword-only."
  - name: make_directional
    desc: "si True genera el sprite direccional N-way. keyword-only."
  - name: make_3d
    desc: "si True genera la malla 3D .glb. keyword-only."
  - name: directional_model
    desc: "'sv3d' (orbit turntable, mejor consistencia rotacional) o 'zero123' (batch por azimuth, menor VRAM). keyword-only."
  - name: elevation
    desc: "picado de camara del orbit direccional en grados (~15-30 para top-down/iso; 0 = ecuador lateral puro). keyword-only."
  - name: seed
    desc: "semilla compartida de la generacion (base 2D + KSampler direccional) para reproducibilidad. keyword-only."
  - name: size
    desc: "lado en px de la base 2D cuadrada (512 con SD1.5 = poca VRAM). keyword-only."
  - name: directional_size
    desc: "lado de cada vista direccional; None = nativo del modelo (576 sv3d / 256 zero123). La base se aplana a este tamano para alimentar el modelo 3D direccional. keyword-only."
  - name: flatten_color
    desc: "color RGB de fondo sobre el que se aplana la base recortada antes de los pasos 3D/direccional (blanco (255,255,255) por defecto; los modelos 3D esperan fondo opaco). keyword-only."
  - name: variant_3d
    desc: "variante Hunyuan3D-2 para la malla: 'mini' (default, ~4.9 GB), 'standard', 'mv'. keyword-only."
  - name: lora
    desc: "LoRA de estilo compartido; solo se aplica si el builder base tiene un param 'lora' generico. keyword-only."
  - name: lora_strength
    desc: "fuerza del LoRA comun. keyword-only."
  - name: server
    desc: "host:port del servidor ComfyUI; se acepta con o sin esquema (http://). keyword-only."
  - name: export_godot
    desc: "ruta de un proyecto Godot 4; si se da, la base 2D (sprite) y la malla (model) se exportan a res://assets/... con su .import. None = no exportar. keyword-only."
  - name: out_dir
    desc: "directorio local donde descargar los artefactos; None = un dir temporal por personaje (character_set_<nombre>_seed<seed> en tempdir). keyword-only."
  - name: wait_timeout
    desc: "segundos maximos esperando cada trabajo en ComfyUI. keyword-only."
  - name: free_vram
    desc: "si True hace POST /free entre el paso 3D y el direccional para liberar VRAM y caber en 8 GB. keyword-only."
  - name: godot_bin
    desc: "binario de Godot para el reimport headless; None autodetecta. keyword-only."
output: "dict con ok (bool, True si la base salio y todos los pasos solicitados tuvieron exito), character/style/checkpoint/base_kind/seed (eco), coherence_note (str), base_image (PNG RGBA recortado = deliverable 2D), base_flat (PNG aplanado = fuente comun de 3D+direccional), base_prompt_id (str), directional ({ok, model, directions, view_order, views:[{direction, path}], prompt_id, error} o None), mesh ({ok, mesh_glb, faces, prompt_id, error} o None), exported (list de resultados de export a Godot), steps (list de {step, ok, detail} del log secuencial), error (str). Un paso solicitado que falla deja el set PARCIAL sin abortar los demas."
---

Pipeline que genera el set COMPLETO de un personaje de juego de un solo tiro y, sobre
todo, COHERENTE: la imagen base 2D, el sprite direccional 8-way y la malla 3D son del
MISMO personaje porque las dos ultimas derivan de la primera. Es la culminacion del
grupo `gamedev-2d` — junta sus fronteras de generacion 2D, 2.5D direccional (SV3D) y
3D-mesh (Hunyuan3D) en una capa de orquestacion que garantiza la identidad compartida.

## Ejemplo

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

# Set completo de UN personaje (SD1.5 512 base, SV3D 576 direccional, Hunyuan3D mini):
res = comfyui_generate_character_set_oneshot(
    "armored paladin",
    style="dark fantasy, hand-painted, full body, clean background",
    base_kind="enemy_creature",   # cuerpo entero recortable -> mejor turntable 3D
    directions=8,                  # sprite 8-way
    make_3d=True,
    make_directional=True,
    seed=7,
    size=512,
    out_dir="/tmp/character_set_paladin",
)
# res["base_image"]              -> PNG RGBA recortado (el sprite 2D)
# res["base_flat"]               -> PNG aplanado sobre blanco (la FUENTE comun)
# res["mesh"]["mesh_glb"]        -> .glb de la malla 3D
# res["directional"]["views"]    -> [{"direction":"S","path":...}, {"direction":"SE",...}, ...]
# res["coherence_note"]          -> explica que 3D y direccional parten de la MISMA base
# res["ok"]                      -> True si base + 3D + direccional salieron todos

# Solo el sprite direccional, sin malla 3D (mas rapido):
res = comfyui_generate_character_set_oneshot(
    "goblin warrior", make_3d=False, directions=8, seed=7, size=512,
)

# Con export directo a un proyecto Godot 4 (sprite -> assets/sprites, malla -> assets/models):
res = comfyui_generate_character_set_oneshot(
    "fire mage", seed=7, size=512,
    export_godot=os.path.expanduser("~/gamedev/projects/dungeon"),
)
```

Lanzable tambien por `fn run` (despacha como pipeline Python):

```bash
./fn run comfyui_generate_character_set_oneshot   # corre el __main__ de demo (armored paladin)
```

## Cuando usarla

Usala cuando necesites TODAS las representaciones de UN personaje para un juego y
tengan que ser el mismo personaje: el sprite 2D para el inventario/retrato, el sprite
direccional para moverlo en un top-down/shooter 8-way, y la malla 3D para un prototipo
3D o un asset hibrido. En vez de llamar `enemy_creature` -> recortar -> aplanar ->
`directional_sprite` -> `image_to_3d` a mano (4 llamadas + el riesgo de que el 2D, el
direccional y el 3D acaben siendo personajes ligeramente distintos), esta funcion lo
hace de un tiro garantizando que el direccional y el 3D salen de la MISMA base. Para
UNA sola representacion aislada, usa su builder/pipeline directo
(`comfyui_build_enemy_creature_workflow`, `comfyui_image_to_3d_oneshot`,
`comfyui_build_directional_sprite_workflow`) — esta es para el *set entero*. Para un
set de assets VARIADOS de un juego (iconos + tiles + UI + enemigos) que no son un solo
personaje, usa `comfyui_generate_asset_pack_oneshot`.

## Gotchas

- **Coherencia = una sola base.** El sprite direccional y la malla 3D NO se generan por
  separado: ambos parten de `base_flat`, que es `base_image` aplanada sobre fondo solido.
  Eso es lo que garantiza que sean el mismo personaje. Si quisieras tres personajes
  distintos, llamarias a los builders por separado — no es lo que hace este pipeline.
- **Por que se aplana la base.** `base_image` sale recortada a alpha (transparente). Los
  modelos 3D (SV3D, Hunyuan3D) y el `LoadImage` de ComfyUI hacen `convert("RGB")`, que
  descarta el alpha y deja los RGB crudos bajo la transparencia (basura) -> mala
  reconstruccion. Por eso se aplana sobre `flatten_color` (blanco) antes de los pasos 3D.
- **VRAM / OOM (RTX 3070 8 GB).** SV3D pica ~7145 MiB y Hunyuan3D mini ~4.9 GB. Caben
  secuencialmente pero NO a la vez: el pipeline hace `POST /free` entre el 3D y el
  direccional (`free_vram=True`) y corre el 3D ANTES (SV3D es el de mayor pico, va con la
  GPU recien liberada). Limpia la VRAM tu mismo antes de empezar
  (`POST http://127.0.0.1:8188/free {"unload_models":true,"free_memory":true}`) y cierra
  cualquier juego — el video/3D no convive con un juego en VRAM. Si un paso pesado peta
  por OOM NO se matan procesos: ese sub-dict queda `ok=False` con su `error` y el resto
  del set sobrevive (set PARCIAL).
- **Fallo aislado = set parcial, no aborto.** Si la base 2D falla, se aborta (sin base no
  hay set). Pero si falla SOLO el 3D o SOLO el direccional, el otro y la base 2D se
  conservan; `ok` global es False y `error` describe el paso parcial.
- **`base_kind` para turntable.** El default `enemy_creature` da cuerpo entero centrado y
  recortable, lo ideal para que SV3D rote la figura. `portrait_avatar` (busto) y
  `topdown_sprite` (cenital) son validos pero el turntable 3D de un busto/cenital es
  menos util. El builder se elige por introspeccion: solo se le pasan los kwargs que su
  firma admita (checkpoint/size/seed/transparent/lora/style).
- **`directional` produce N imagenes en un prompt.** SV3D emite los N frames del orbit en
  un unico SaveImage; el pipeline los descarga TODOS y los etiqueta por direccion con
  `directional_sprite_view_order(directions)` (frame i = direccion i). `comfyui_wait_result`
  lanza `TimeoutError` al expirar pero el job suele completar en GPU — subir `wait_timeout`
  si SV3D tarda en lowvram.
- **`server`** se normaliza (acepta con o sin `http://`); internamente es `host:port`.
- **Export a Godot** lleva la base 2D como `sprite` y la malla como `model`; reimport
  headless una sola vez. Si no encuentra el binario de Godot, deja los `.import` y lo
  anota, no falla la generacion.

## Capability growth log

v1.0.0 (2026-06-27) — version inicial. Promueve a un pipeline one-shot la secuencia
"base 2D de personaje -> aplanar -> malla 3D + sprite direccional, todos del mismo
personaje" (issue 0087). Resuelve la coherencia cross-frontera que ninguna pieza suelta
del grupo gamedev-2d garantizaba: el direccional y el 3D derivan de la MISMA base 2D
aplanada. Secuencial liberando VRAM entre pasos pesados para caber en 8 GB.
