feat(gamedev): comfyui_build_ui_hud_workflow — elementos de UI/HUD (botones/marcos/barras)
Builder puro hermano de comfyui_build_item_icon_workflow: construye el dict (API format) del workflow de UN elemento de interfaz/HUD de juego (botón, marco/panel, barra de vida/maná/XP, icono de UI, cursor, viñeta de menú). Pieza única centrada, fondo limpio recortable a alpha. Compone comfyui_build_txt2img_workflow + comfyui_inject_lora + Image Rembg. Tests offline 7/7 verdes (golden + 4 edge + error + determinismo). Generación real verificada en GPU (8GB lowvram): ornate health bar frame -> PNG 512x512 RGBA con alpha recortado (reports/0152). Fila añadida en docs/capabilities/gamedev-2d.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -40,6 +40,7 @@ VFX (ver `reports/0143`).
|
|||||||
| `comfyui_build_emote_workflow_py_ml` | `(character, expression, *, ref_face=None, style="character portrait", checkpoint="dreamshaper_8…", size=512, facedetailer=True, lora=None, …) -> dict` | UN emote/expresión facial del MISMO personaje (alegre/triste/enfadado/sorprendido/neutral…) para diálogo, retratos reactivos o emotes de chat: txt2img + prompt scaffold de emote (`portrait of {character}, {expression} expression, emote, clean background`) + FaceDetailer (conserva la expresión); `ref_face` → IPAdapter-FaceID para que varíe SOLO la expresión y el rostro sea el mismo. UNA expresión por llamada; set = mismas claves variando `expression` → `comfyui_build_grid`. Probado e2e en GPU (`reports/0151`). SD1.5. |
|
| `comfyui_build_emote_workflow_py_ml` | `(character, expression, *, ref_face=None, style="character portrait", checkpoint="dreamshaper_8…", size=512, facedetailer=True, lora=None, …) -> dict` | UN emote/expresión facial del MISMO personaje (alegre/triste/enfadado/sorprendido/neutral…) para diálogo, retratos reactivos o emotes de chat: txt2img + prompt scaffold de emote (`portrait of {character}, {expression} expression, emote, clean background`) + FaceDetailer (conserva la expresión); `ref_face` → IPAdapter-FaceID para que varíe SOLO la expresión y el rostro sea el mismo. UNA expresión por llamada; set = mismas claves variando `expression` → `comfyui_build_grid`. Probado e2e en GPU (`reports/0151`). SD1.5. |
|
||||||
| `comfyui_build_parallax_background_workflow_py_ml` | `(scene, *, style="game background, side-scroller…", layers=3, checkpoint="dreamshaper_8…", depth_node="DepthAnythingV2Preprocessor", width=1024, height=512, …) -> dict` | Fondo en capas para parallax 2.5D: genera el fondo apaisado (txt2img) + su depth map (`DepthAnythingV2Preprocessor` sobre el VAEDecode), dos SaveImage. El split en N bandas por profundidad es post (GAP: `split_parallax_layers`, aún no creada). Probado e2e en GPU (`reports/0149`). SD1.5. |
|
| `comfyui_build_parallax_background_workflow_py_ml` | `(scene, *, style="game background, side-scroller…", layers=3, checkpoint="dreamshaper_8…", depth_node="DepthAnythingV2Preprocessor", width=1024, height=512, …) -> dict` | Fondo en capas para parallax 2.5D: genera el fondo apaisado (txt2img) + su depth map (`DepthAnythingV2Preprocessor` sobre el VAEDecode), dos SaveImage. El split en N bandas por profundidad es post (GAP: `split_parallax_layers`, aún no creada). Probado e2e en GPU (`reports/0149`). SD1.5. |
|
||||||
| `comfyui_build_normal_map_workflow_py_ml` | `(image, *, method="normal", strength=1.0, resolution=512, bg_threshold=0.1, filename_prefix="normal_map") -> dict` | Normal/depth map de un sprite existente para iluminación dinámica 2.5D (Godot CanvasItem `normal_map`, Unity sprite normal). `LoadImage → preprocesador controlnet_aux → SaveImage`. `method`: `normal` (default, `BAE-NormalMapPreprocessor`, normal canónico **azul/violeta** usable directo en motor), `normal_midas` (MiDaS, único con `strength`→`a`, paleta no canónica), `normal_dsine` (DSINE), `depth` (`DepthAnythingV2`, height en gris). `image` debe estar en `input/` de ComfyUI. Coste VRAM ≈0. Probado e2e en GPU (`reports/0150`). |
|
| `comfyui_build_normal_map_workflow_py_ml` | `(image, *, method="normal", strength=1.0, resolution=512, bg_threshold=0.1, filename_prefix="normal_map") -> dict` | Normal/depth map de un sprite existente para iluminación dinámica 2.5D (Godot CanvasItem `normal_map`, Unity sprite normal). `LoadImage → preprocesador controlnet_aux → SaveImage`. `method`: `normal` (default, `BAE-NormalMapPreprocessor`, normal canónico **azul/violeta** usable directo en motor), `normal_midas` (MiDaS, único con `strength`→`a`, paleta no canónica), `normal_dsine` (DSINE), `depth` (`DepthAnythingV2`, height en gris). `image` debe estar en `input/` de ComfyUI. Coste VRAM ≈0. Probado e2e en GPU (`reports/0150`). |
|
||||||
|
| `comfyui_build_ui_hud_workflow_py_ml` | `(element, *, ui_style="fantasy game UI", checkpoint="dreamshaper_8…", size=512, transparent=True, lora=None, …) -> dict` | UN elemento de interfaz/HUD de juego (botón, marco/panel, barra de vida/maná/XP, icono de UI, cursor, viñeta de menú): txt2img cuadrado + prompt scaffold de UI (`{element}, {ui_style}, game UI element, centered, clean, plain background…`) + LoRA estilo opcional + Rembg (alpha). HUD coherente = mismo `ui_style`/`checkpoint`/`lora` por pieza, varía solo `element`. El texto/label lo pone el motor (negativo empuja a `no text`). Probado e2e en GPU (`reports/0152`). SD1.5. |
|
||||||
|
|
||||||
## Funciones de post-proceso y puente (`gamedev`, CPU)
|
## Funciones de post-proceso y puente (`gamedev`, CPU)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
---
|
||||||
|
name: comfyui_build_ui_hud_workflow
|
||||||
|
kind: function
|
||||||
|
lang: py
|
||||||
|
domain: ml
|
||||||
|
version: "1.0.0"
|
||||||
|
purity: pure
|
||||||
|
signature: "def comfyui_build_ui_hud_workflow(element: str, *, ui_style: str = \"fantasy game UI\", checkpoint: str = \"dreamshaper_8.safetensors\", size: int = 512, transparent: bool = True, seed: int = 0, lora: str | None = None, lora_strength: float = 1.0, rembg_model: str = \"u2net\", negative: str | None = None, steps: int = 28, cfg: float = 7.0, sampler_name: str = \"dpmpp_2m\", scheduler: str = \"karras\", filename_prefix: str = \"ui_hud\") -> dict"
|
||||||
|
description: "Construye el dict (API format) del workflow de UN elemento de interfaz de juego (UI/HUD) 2D: botones, marcos/paneles, barras de vida/mana/XP, iconos de UI, cursores, vinhetas de menu. Pieza unica centrada, fondo limpio uniforme, estilo consistente entre elementos del set, recortable a alpha. Compone comfyui_build_txt2img_workflow + comfyui_inject_lora (estilo opcional) + Image Rembg (fondo transparente si transparent). Hermano de comfyui_build_item_icon/pixelart/sprite_sheet_workflow. Pura, sin red ni I/O. class_types verificados contra /object_info."
|
||||||
|
tags: [comfyui, ml, gamedev, gamedev-2d, ui, hud, button, frame, bar, workflow]
|
||||||
|
uses_functions: [comfyui_build_txt2img_workflow_py_ml, comfyui_inject_lora_py_ml]
|
||||||
|
uses_types: []
|
||||||
|
returns: []
|
||||||
|
returns_optional: false
|
||||||
|
error_type: ""
|
||||||
|
imports: []
|
||||||
|
params:
|
||||||
|
- name: element
|
||||||
|
desc: "Nombre del elemento de interfaz (ej. 'health bar', 'wooden button', 'ornate frame', 'magic icon', 'menu cursor', 'mana orb'). Se inserta en un prompt scaffold de UI. No puede estar vacio."
|
||||||
|
- name: ui_style
|
||||||
|
desc: "Descriptor de estilo de la interfaz que mantiene consistentes las piezas de un set (ej. 'fantasy game UI', 'sci-fi HUD, neon glow', 'pixel art UI', 'minimal flat UI'). Pasa el MISMO ui_style + checkpoint + lora a todos los elementos del HUD para coherencia visual. keyword-only."
|
||||||
|
- name: checkpoint
|
||||||
|
desc: "Checkpoint del servidor. 'dreamshaper_8.safetensors' (SD1.5, holgado en 8GB lowvram) por defecto; 'juggernaut_xl_v11.safetensors' para SDXL (mas VRAM, subir size). keyword-only."
|
||||||
|
- name: size
|
||||||
|
desc: "Lado del cuadrado en px (width = height = size). 512 SD1.5 por defecto. keyword-only."
|
||||||
|
- name: transparent
|
||||||
|
desc: "Si True inyecta Image Rembg y el PNG sale con alpha (fondo recortado). False = elemento opaco sobre fondo plano, recortable luego por el caller. keyword-only."
|
||||||
|
- name: seed
|
||||||
|
desc: "Semilla del KSampler. keyword-only."
|
||||||
|
- name: lora
|
||||||
|
desc: "LoRA de estilo opcional en models/loras (ej. 'detail_tweaker_sd15.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: rembg_model
|
||||||
|
desc: "Modelo Rembg ('u2net' general, 'isnet-anime' para anime). Solo se usa si transparent=True. keyword-only."
|
||||||
|
- name: negative
|
||||||
|
desc: "Prompt negativo. None usa el negativo por defecto pensado para UI (una pieza, fondo limpio, sin personajes/escena/texto). keyword-only."
|
||||||
|
- name: steps
|
||||||
|
desc: "Pasos del KSampler. keyword-only."
|
||||||
|
- name: cfg
|
||||||
|
desc: "CFG del KSampler. keyword-only."
|
||||||
|
- name: sampler_name
|
||||||
|
desc: "Sampler del KSampler. keyword-only."
|
||||||
|
- name: scheduler
|
||||||
|
desc: "Scheduler del KSampler. 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 cuadrada con prompt scaffold de UI ('{element}, {ui_style}, game UI element, centered, clean, plain background, high detail') + LoRA de estilo opcional + Image Rembg (si transparent). UN elemento; HUD completo -> llamar por element con mismo ui_style/checkpoint/lora."
|
||||||
|
tested: true
|
||||||
|
tests: ["golden transparent: clases CheckpointLoaderSimple/KSampler/VAEDecode/SaveImage/Image Rembg; element + 'game UI element' + 'centered' + 'plain background' en prompt; SaveImage <- Rembg; transparency True", "edge transparent=False: sin Rembg, SaveImage <- VAEDecode", "edge size: width==height==768 (cuadrado)", "edge ui_style en prompt", "edge lora: LoraLoader presente con strength", "error element vacio -> ValueError", "determinismo"]
|
||||||
|
test_file_path: "python/functions/ml/comfyui_build_ui_hud_workflow_test.py"
|
||||||
|
file_path: "python/functions/ml/comfyui_build_ui_hud_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_ui_hud_workflow import comfyui_build_ui_hud_workflow
|
||||||
|
|
||||||
|
# Un marco de barra de vida ornamentado con fondo transparente (alpha), listo para submit.
|
||||||
|
wf = comfyui_build_ui_hud_workflow(
|
||||||
|
"ornate health bar frame",
|
||||||
|
ui_style="fantasy game UI, gold filigree",
|
||||||
|
transparent=True,
|
||||||
|
seed=42,
|
||||||
|
)
|
||||||
|
# HUD coherente: misma firma de estilo para cada pieza de la interfaz.
|
||||||
|
# for el in ["wooden button", "ornate frame", "mana orb", "menu cursor"]:
|
||||||
|
# wf = comfyui_build_ui_hud_workflow(el, ui_style="fantasy game UI",
|
||||||
|
# lora="detail_tweaker_sd15.safetensors", seed=42)
|
||||||
|
# comfyui_submit_workflow(wf) # -> comfyui_wait_result -> comfyui_fetch_output_image
|
||||||
|
# Atlas del HUD: montar los PNG resultantes con comfyui_build_grid.
|
||||||
|
```
|
||||||
|
|
||||||
|
O lanzable directo con: `./fn run comfyui_build_ui_hud_workflow` (imprime nodos + class_types del ejemplo).
|
||||||
|
|
||||||
|
## Cuando usarla
|
||||||
|
|
||||||
|
Cuando necesites elementos de interfaz/HUD para un juego (RPG, plataformas,
|
||||||
|
roguelike) con look consistente: botones, marcos/paneles, barras de vida/mana/XP,
|
||||||
|
iconos de UI, cursores, vinhetas de menu. Pasa el MISMO `ui_style` + `checkpoint` +
|
||||||
|
(`lora`) a todas las piezas para que combinen visualmente; varia solo `element`.
|
||||||
|
`transparent` recorta el fondo (alpha) listo para el motor. Para un atlas/contact-sheet,
|
||||||
|
genera cada elemento y monta los PNG con `comfyui_build_grid`.
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- **El recorte usa Rembg, NO luma-to-alpha**: un elemento de UI es una pieza solida
|
||||||
|
con silueta definida (boton, marco, barra), rembg lo recorta limpio.
|
||||||
|
`comfyui_matting_luma_to_alpha` es para translucidos sobre negro (humo/fuego/magia)
|
||||||
|
y aplanaria la pieza — no la uses para UI.
|
||||||
|
- **Coherencia del HUD = mismos parametros**: si cambias `ui_style`/`checkpoint`/
|
||||||
|
`lora`/`seed` entre piezas, el set deja de combinar. Fija esos y varia solo `element`.
|
||||||
|
- **El texto/label lo pone el motor, no la imagen**: el negativo por defecto empuja
|
||||||
|
a "no text / no label" para que la silueta quede limpia; el numero de la barra o el
|
||||||
|
texto del boton se renderizan en el juego sobre la pieza. Si quieres texto horneado,
|
||||||
|
pasa un `negative` propio sin "text, label".
|
||||||
|
- **SDXL pide mas VRAM y resolucion**: con `checkpoint="juggernaut_xl_v11.safetensors"`
|
||||||
|
sube `size` a 768/1024; con dreamshaper_8 (SD1.5) deja 512 (holgado en 8GB lowvram).
|
||||||
|
- Si el modelo mete personajes o escena, el negativo por defecto ya empuja a "single
|
||||||
|
element / plain background / no character"; refuerza `ui_style` con "isolated UI
|
||||||
|
element" si insiste.
|
||||||
|
- `transparent=False` deja la pieza opaca sobre fondo plano: util si prefieres
|
||||||
|
recortar fuera del workflow o el motor compone sobre un slot solido.
|
||||||
|
- Es una funcion **pura**: solo arma el dict. La generacion real (GPU) la hacen
|
||||||
|
`comfyui_submit_workflow` + `comfyui_wait_result` + `comfyui_fetch_output_image`.
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
"""Construye el workflow ComfyUI de UN elemento de interfaz de juego (UI/HUD) en API format.
|
||||||
|
|
||||||
|
Elementos de UI/HUD de juego (botones, marcos/paneles, barras de vida/mana/XP,
|
||||||
|
iconos de UI, cursores, vinhetas de menu): pieza unica centrada, fondo limpio y
|
||||||
|
uniforme, estilo consistente entre elementos del mismo set, recortable a alpha. Es
|
||||||
|
el builder hermano de comfyui_build_item_icon_workflow / comfyui_build_pixelart_workflow
|
||||||
|
/ comfyui_build_seamless_tile_workflow: mismo patron (PURO, dict API format) que
|
||||||
|
compone funciones existentes del registry, no reescribe el grafo.
|
||||||
|
|
||||||
|
Cableado:
|
||||||
|
|
||||||
|
CheckpointLoaderSimple -> [LoraLoader opcional de estilo] -> KSampler
|
||||||
|
-> CLIPTextEncode (prompt scaffold de UI) ...
|
||||||
|
-> VAEDecode -> [Image Rembg opcional] -> SaveImage
|
||||||
|
|
||||||
|
Compone:
|
||||||
|
- comfyui_build_txt2img_workflow -> base txt2img cuadrada
|
||||||
|
- comfyui_inject_lora -> LoRA de estilo opcional (consistencia)
|
||||||
|
- 'Image Rembg (Remove Background)' (helper local) -> fondo transparente
|
||||||
|
|
||||||
|
Por que Rembg y NO comfyui_matting_luma_to_alpha: un elemento de UI es una pieza
|
||||||
|
SOLIDA con silueta definida (un boton, un marco, una barra); rembg recorta limpio
|
||||||
|
la silueta dejando alpha. La luma-to-alpha es para translucidos sobre negro
|
||||||
|
(humo/fuego/magia) y aplanaria el elemento. Si el caller prefiere recortar fuera
|
||||||
|
del workflow (transparent=False) deja la imagen opaca sobre fondo plano, recortable
|
||||||
|
luego por el pipeline o el caller.
|
||||||
|
|
||||||
|
El mismo ui_style + checkpoint + (lora) en todas las piezas de un set (botones,
|
||||||
|
barras, marcos) hace que combinen visualmente: es la clave de un HUD coherente,
|
||||||
|
igual que en los iconos de inventario.
|
||||||
|
|
||||||
|
class_types/inputs verificados contra /object_info del servidor (8GB lowvram):
|
||||||
|
CheckpointLoaderSimple, CLIPTextEncode, EmptyLatentImage, KSampler, VAEDecode,
|
||||||
|
SaveImage, LoraLoader, 'Image Rembg (Remove Background)' (transparency BOOLEAN).
|
||||||
|
|
||||||
|
Funcion pura: sin red, sin I/O. No muta dicts de entrada (copia profunda en el
|
||||||
|
helper de rembg). Determinista para los mismos argumentos.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import copy
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
||||||
|
|
||||||
|
# Negativo por defecto pensado para UI/HUD: una sola pieza limpia, sin personajes
|
||||||
|
# ni escenas ni texto/marcas que ensucien la silueta centrada y recortable.
|
||||||
|
_UI_NEGATIVE = (
|
||||||
|
"blurry, lowres, character, person, face, landscape, scene, "
|
||||||
|
"multiple elements, cluttered, busy background, text, label, watermark, "
|
||||||
|
"signature, photo, photorealistic, jpeg artifacts, cropped, out of frame, "
|
||||||
|
"deformed"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _inject_rembg(workflow: dict, model: str) -> dict:
|
||||||
|
"""Inserta 'Image Rembg (Remove Background)' (transparency=True) entre VAEDecode y SaveImage.
|
||||||
|
|
||||||
|
Mismo helper que usan comfyui_build_item_icon_workflow / comfyui_build_sprite_sheet_workflow:
|
||||||
|
el nodo recorta la silueta del elemento de UI dejando alpha. Repunta
|
||||||
|
SaveImage.images a la salida del Rembg.
|
||||||
|
"""
|
||||||
|
wf = copy.deepcopy(workflow)
|
||||||
|
vaedecode_id = next(
|
||||||
|
(nid for nid, n in wf.items() if n.get("class_type") == "VAEDecode"), None
|
||||||
|
)
|
||||||
|
save_id = next((nid for nid, n in wf.items() if n.get("class_type") == "SaveImage"), None)
|
||||||
|
if vaedecode_id is None or save_id is None:
|
||||||
|
raise ValueError(
|
||||||
|
"comfyui_build_ui_hud_workflow: no se encontro VAEDecode/SaveImage para Rembg"
|
||||||
|
)
|
||||||
|
numeric = [int(k) for k in wf.keys() if str(k).isdigit()]
|
||||||
|
rembg_id = str((max(numeric) + 1) if numeric else len(wf) + 1)
|
||||||
|
wf[rembg_id] = {
|
||||||
|
"class_type": "Image Rembg (Remove Background)",
|
||||||
|
"inputs": {
|
||||||
|
"images": [vaedecode_id, 0],
|
||||||
|
"transparency": True,
|
||||||
|
"model": model,
|
||||||
|
"post_processing": False,
|
||||||
|
"only_mask": False,
|
||||||
|
"alpha_matting": False,
|
||||||
|
"alpha_matting_foreground_threshold": 240,
|
||||||
|
"alpha_matting_background_threshold": 10,
|
||||||
|
"alpha_matting_erode_size": 10,
|
||||||
|
"background_color": "none",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
wf[save_id]["inputs"]["images"] = [rembg_id, 0]
|
||||||
|
return wf
|
||||||
|
|
||||||
|
|
||||||
|
def comfyui_build_ui_hud_workflow(
|
||||||
|
element: str,
|
||||||
|
*,
|
||||||
|
ui_style: str = "fantasy game UI",
|
||||||
|
checkpoint: str = "dreamshaper_8.safetensors",
|
||||||
|
size: int = 512,
|
||||||
|
transparent: bool = True,
|
||||||
|
seed: int = 0,
|
||||||
|
lora: str | None = None,
|
||||||
|
lora_strength: float = 1.0,
|
||||||
|
rembg_model: str = "u2net",
|
||||||
|
negative: str | None = None,
|
||||||
|
steps: int = 28,
|
||||||
|
cfg: float = 7.0,
|
||||||
|
sampler_name: str = "dpmpp_2m",
|
||||||
|
scheduler: str = "karras",
|
||||||
|
filename_prefix: str = "ui_hud",
|
||||||
|
) -> dict:
|
||||||
|
"""Construye el dict (API format) del workflow de un elemento de UI/HUD de juego.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
element: nombre del elemento de interfaz (ej. "health bar", "wooden button",
|
||||||
|
"ornate frame", "magic icon", "menu cursor", "mana orb"). Se inserta en
|
||||||
|
un prompt scaffold de UI. No puede estar vacio.
|
||||||
|
ui_style: descriptor de estilo de la interfaz que mantiene consistentes las
|
||||||
|
piezas de un set (ej. "fantasy game UI", "sci-fi HUD, neon glow",
|
||||||
|
"pixel art UI", "minimal flat UI"). Pasa el MISMO ui_style + checkpoint +
|
||||||
|
(lora) a todos los elementos del HUD para coherencia visual. keyword-only.
|
||||||
|
checkpoint: checkpoint del servidor. 'dreamshaper_8.safetensors' (SD1.5,
|
||||||
|
holgado en 8GB lowvram) por defecto; 'juggernaut_xl_v11.safetensors'
|
||||||
|
para SDXL (mas VRAM, subir size a 768/1024). keyword-only.
|
||||||
|
size: lado del cuadrado en px (width = height = size). 512 SD1.5 por
|
||||||
|
defecto. keyword-only.
|
||||||
|
transparent: si True inyecta Rembg y el PNG sale con alpha (fondo
|
||||||
|
recortado). Si False deja el elemento opaco sobre fondo plano,
|
||||||
|
recortable luego por el caller/pipeline. keyword-only.
|
||||||
|
seed: semilla del KSampler. keyword-only.
|
||||||
|
lora: LoRA de estilo opcional en models/loras (ej.
|
||||||
|
'detail_tweaker_sd15.safetensors'). None = sin LoRA. keyword-only.
|
||||||
|
lora_strength: fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0].
|
||||||
|
keyword-only.
|
||||||
|
rembg_model: modelo Rembg ('u2net' general, 'isnet-anime' para anime).
|
||||||
|
Solo se usa si transparent=True. keyword-only.
|
||||||
|
negative: prompt negativo. None usa el negativo por defecto pensado para
|
||||||
|
UI (una pieza, fondo limpio, sin personajes/escena/texto). keyword-only.
|
||||||
|
steps, cfg, sampler_name, scheduler, filename_prefix: parametros de
|
||||||
|
generacion. keyword-only.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict en API format listo para comfyui_submit_workflow: txt2img base
|
||||||
|
cuadrada con prompt scaffold de UI ('{element}, {ui_style}, game UI element,
|
||||||
|
centered, clean, plain background, high detail') + LoRA de estilo opcional +
|
||||||
|
Rembg (si transparent). Es UN elemento; un HUD completo -> llamar por element
|
||||||
|
con el mismo ui_style/checkpoint/lora y montar con comfyui_build_grid si se
|
||||||
|
quiere un atlas.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: si element esta vacio, o si la base no tiene VAEDecode/SaveImage
|
||||||
|
donde inyectar el Rembg (propagado por el helper).
|
||||||
|
"""
|
||||||
|
from ml.comfyui_build_txt2img_workflow import comfyui_build_txt2img_workflow
|
||||||
|
|
||||||
|
if not element or not element.strip():
|
||||||
|
raise ValueError("comfyui_build_ui_hud_workflow: 'element' no puede estar vacio")
|
||||||
|
|
||||||
|
lora_strength = max(0.0, min(2.0, float(lora_strength)))
|
||||||
|
neg = _UI_NEGATIVE if negative is None else negative
|
||||||
|
|
||||||
|
# Prompt scaffold de UI: pieza unica, centrada, fondo plano, recortable.
|
||||||
|
positive = (
|
||||||
|
f"{element.strip()}, {ui_style}, game UI element, centered, clean, "
|
||||||
|
"plain background, high detail"
|
||||||
|
)
|
||||||
|
|
||||||
|
wf = comfyui_build_txt2img_workflow(
|
||||||
|
checkpoint,
|
||||||
|
positive,
|
||||||
|
neg,
|
||||||
|
steps=steps,
|
||||||
|
cfg=cfg,
|
||||||
|
width=size,
|
||||||
|
height=size,
|
||||||
|
seed=seed,
|
||||||
|
sampler_name=sampler_name,
|
||||||
|
scheduler=scheduler,
|
||||||
|
filename_prefix=filename_prefix,
|
||||||
|
)
|
||||||
|
|
||||||
|
if lora:
|
||||||
|
from ml.comfyui_inject_lora import comfyui_inject_lora
|
||||||
|
|
||||||
|
wf = comfyui_inject_lora(
|
||||||
|
wf, lora, strength_model=lora_strength, strength_clip=lora_strength
|
||||||
|
)
|
||||||
|
|
||||||
|
if transparent:
|
||||||
|
wf = _inject_rembg(wf, rembg_model)
|
||||||
|
|
||||||
|
return wf
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import json
|
||||||
|
|
||||||
|
wf = comfyui_build_ui_hud_workflow(
|
||||||
|
"ornate health bar frame",
|
||||||
|
ui_style="fantasy game UI, gold filigree",
|
||||||
|
transparent=True,
|
||||||
|
seed=42,
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"nodes": list(wf),
|
||||||
|
"classes": sorted({n["class_type"] for n in wf.values()}),
|
||||||
|
},
|
||||||
|
indent=2,
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
"""Tests offline de comfyui_build_ui_hud_workflow (estructura del dict, sin GPU)."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
from ml.comfyui_build_ui_hud_workflow import ( # noqa: E402
|
||||||
|
comfyui_build_ui_hud_workflow,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _classes(wf):
|
||||||
|
return sorted({n["class_type"] for n in wf.values()})
|
||||||
|
|
||||||
|
|
||||||
|
def _by_class(wf, cls):
|
||||||
|
return [n for n in wf.values() if n["class_type"] == cls]
|
||||||
|
|
||||||
|
|
||||||
|
def _id_of(wf, cls):
|
||||||
|
return next(nid for nid, n in wf.items() if n["class_type"] == cls)
|
||||||
|
|
||||||
|
|
||||||
|
def test_golden_transparent_recipe():
|
||||||
|
wf = comfyui_build_ui_hud_workflow("health bar", transparent=True, seed=42)
|
||||||
|
cls = _classes(wf)
|
||||||
|
# Cadena base txt2img + Rembg para alpha.
|
||||||
|
assert "CheckpointLoaderSimple" in cls
|
||||||
|
assert "KSampler" in cls
|
||||||
|
assert "VAEDecode" in cls
|
||||||
|
assert "SaveImage" in cls
|
||||||
|
assert "Image Rembg (Remove Background)" in cls
|
||||||
|
# El element aparece en el prompt positivo, con el scaffold de UI.
|
||||||
|
pos = next(
|
||||||
|
n for n in wf.values()
|
||||||
|
if n["class_type"] == "CLIPTextEncode" and "health bar" in n["inputs"]["text"]
|
||||||
|
)
|
||||||
|
assert "game UI element" in pos["inputs"]["text"]
|
||||||
|
assert "centered" in pos["inputs"]["text"]
|
||||||
|
assert "plain background" in pos["inputs"]["text"]
|
||||||
|
# SaveImage toma la imagen del Rembg (no del VAEDecode).
|
||||||
|
rembg_id = _id_of(wf, "Image Rembg (Remove Background)")
|
||||||
|
save = next(n for n in wf.values() if n["class_type"] == "SaveImage")
|
||||||
|
assert save["inputs"]["images"] == [rembg_id, 0]
|
||||||
|
assert _by_class(wf, "Image Rembg (Remove Background)")[0]["inputs"]["transparency"] is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_edge_opaque_no_rembg():
|
||||||
|
wf = comfyui_build_ui_hud_workflow("wooden button", transparent=False)
|
||||||
|
assert "Image Rembg (Remove Background)" not in _classes(wf)
|
||||||
|
# SaveImage toma del VAEDecode directamente.
|
||||||
|
vd_id = _id_of(wf, "VAEDecode")
|
||||||
|
save = next(n for n in wf.values() if n["class_type"] == "SaveImage")
|
||||||
|
assert save["inputs"]["images"] == [vd_id, 0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_edge_size_reflected():
|
||||||
|
wf = comfyui_build_ui_hud_workflow("mana orb", size=768)
|
||||||
|
latent = _by_class(wf, "EmptyLatentImage")[0]["inputs"]
|
||||||
|
assert latent["width"] == 768
|
||||||
|
assert latent["height"] == 768 # cuadrado
|
||||||
|
|
||||||
|
|
||||||
|
def test_edge_ui_style_in_prompt():
|
||||||
|
wf = comfyui_build_ui_hud_workflow(
|
||||||
|
"ornate frame", ui_style="sci-fi HUD, neon glow", transparent=False
|
||||||
|
)
|
||||||
|
pos = next(
|
||||||
|
n for n in wf.values()
|
||||||
|
if n["class_type"] == "CLIPTextEncode" and "ornate frame" in n["inputs"]["text"]
|
||||||
|
)
|
||||||
|
assert "sci-fi HUD, neon glow" in pos["inputs"]["text"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_edge_lora_reflected():
|
||||||
|
wf = comfyui_build_ui_hud_workflow(
|
||||||
|
"magic icon", lora="detail_tweaker_sd15.safetensors", lora_strength=0.9
|
||||||
|
)
|
||||||
|
loras = _by_class(wf, "LoraLoader")
|
||||||
|
assert len(loras) == 1
|
||||||
|
assert loras[0]["inputs"]["lora_name"] == "detail_tweaker_sd15.safetensors"
|
||||||
|
assert loras[0]["inputs"]["strength_model"] == 0.9
|
||||||
|
|
||||||
|
|
||||||
|
def test_error_empty_element():
|
||||||
|
try:
|
||||||
|
comfyui_build_ui_hud_workflow(" ")
|
||||||
|
assert False
|
||||||
|
except ValueError as e:
|
||||||
|
assert "element" in str(e)
|
||||||
|
|
||||||
|
|
||||||
|
def test_determinism():
|
||||||
|
a = comfyui_build_ui_hud_workflow("menu cursor", lora="detail_tweaker_sd15.safetensors", seed=7)
|
||||||
|
b = comfyui_build_ui_hud_workflow("menu cursor", lora="detail_tweaker_sd15.safetensors", seed=7)
|
||||||
|
assert a == b
|
||||||
Reference in New Issue
Block a user