feat(gamedev): comfyui_build_status_effect_icon_workflow — iconos de estado/buff-debuff (símbolo compacto legible a tamaño reducido, size 256, Rembg alpha)
Builder hermano de item_icon/ui_hud, diferenciado por rol: símbolo de estado compacto (veneno/escudo/velocidad...) optimizado para 16-32 px, no objeto de inventario ni chrome de interfaz. Pura (dict API format), 8 tests offline, 1 generación real verificada (poison 256x256 RGBA). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
---
|
||||
name: comfyui_build_status_effect_icon_workflow
|
||||
kind: function
|
||||
lang: py
|
||||
domain: ml
|
||||
version: "1.0.0"
|
||||
purity: pure
|
||||
signature: "def comfyui_build_status_effect_icon_workflow(effect: str, *, ui_style: str = \"game status icon, bold symbol, flat\", checkpoint: str = \"dreamshaper_8.safetensors\", size: int = 256, 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 = \"status_effect_icon\") -> dict"
|
||||
description: "Construye el dict (API format) del workflow de UN icono de estado / buff-debuff 2D (veneno, quemadura, congelacion, escudo, regeneracion, aturdimiento, velocidad, sangrado, maldicion): simbolo unico audaz y LEGIBLE A TAMANO REDUCIDO, centrado, fondo limpio uniforme, estilo de UI consistente entre estados, recortable a alpha. Tamano por defecto menor (256) por ser iconos compactos del HUD. DISTINTO de item_icon (objeto de inventario) y ui_hud (chrome grande de interfaz): es un simbolo de estado compacto. Compone comfyui_build_txt2img_workflow + comfyui_inject_lora (estilo opcional) + Image Rembg (fondo transparente si transparent). Hermano de comfyui_build_item_icon/ui_hud_workflow. Pura, sin red ni I/O. class_types verificados contra /object_info."
|
||||
tags: [comfyui, ml, gamedev, gamedev-2d, ui, status, buff, debuff, icon, rembg, 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: effect
|
||||
desc: "Nombre del efecto de estado (ej. 'poison', 'burning', 'frozen', 'shield', 'regeneration', 'stun', 'speed boost', 'bleed', 'curse'). Se inserta en un prompt scaffold de icono de estado. No puede estar vacio."
|
||||
- name: ui_style
|
||||
desc: "Descriptor de estilo de UI que mantiene consistentes los iconos de un set (ej. 'game status icon, bold symbol, flat', 'sci-fi HUD status, neon glow', 'pixel art status icon', 'minimal flat status icon'). Pasa el MISMO ui_style + checkpoint + lora a todos los iconos de la barra de estados 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). 256 por defecto: son iconos compactos que se muestran a tamano reducido en el HUD. keyword-only."
|
||||
- name: transparent
|
||||
desc: "Si True inyecta Image Rembg y el PNG sale con alpha (fondo recortado). False = simbolo 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 iconos de estado (un simbolo simple legible, fondo limpio, sin escena/personaje/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 icono de estado ('{effect} status effect icon, {ui_style}, simple bold symbol, centered, readable at small size, plain background, ...') + LoRA de estilo opcional + Image Rembg (si transparent). UN icono; barra de estados completa -> llamar por effect con mismo ui_style/checkpoint/lora."
|
||||
tested: true
|
||||
tests: ["golden transparent: clases CheckpointLoaderSimple/KSampler/VAEDecode/SaveImage/Image Rembg; effect + 'status effect icon' + 'centered' + 'readable at small size' en prompt; SaveImage <- Rembg; transparency True", "edge transparent=False: sin Rembg, SaveImage <- VAEDecode", "edge size: width==height (cuadrado); default 256", "edge ui_style en prompt", "edge effect en prompt", "edge lora: LoraLoader presente con strength", "error effect vacio -> ValueError", "determinismo"]
|
||||
test_file_path: "python/functions/ml/comfyui_build_status_effect_icon_workflow_test.py"
|
||||
file_path: "python/functions/ml/comfyui_build_status_effect_icon_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_status_effect_icon_workflow import comfyui_build_status_effect_icon_workflow
|
||||
|
||||
# Un icono de veneno con fondo transparente (alpha), listo para submit.
|
||||
wf = comfyui_build_status_effect_icon_workflow(
|
||||
"poison",
|
||||
ui_style="game status icon, bold symbol, flat, green",
|
||||
transparent=True,
|
||||
seed=42,
|
||||
)
|
||||
# Barra de estados coherente: misma firma de estilo para cada efecto.
|
||||
# for fx in ["burning", "frozen", "shield", "regeneration", "stun", "speed boost"]:
|
||||
# wf = comfyui_build_status_effect_icon_workflow(fx, ui_style="game status icon, bold symbol, flat",
|
||||
# lora="detail_tweaker_sd15.safetensors", seed=42)
|
||||
# comfyui_submit_workflow(wf) # -> comfyui_wait_result -> comfyui_fetch_output_image
|
||||
# Atlas de estados: montar los PNG resultantes con comfyui_build_grid.
|
||||
```
|
||||
|
||||
O lanzable directo con: `./fn run comfyui_build_status_effect_icon_workflow` (imprime nodos + class_types del ejemplo).
|
||||
|
||||
## Cuando usarla
|
||||
|
||||
Cuando necesites iconos de buff/debuff para la barra de estados de un juego (RPG,
|
||||
roguelike, MOBA): veneno, quemadura, congelacion, escudo, regeneracion, aturdimiento,
|
||||
velocidad, sangrado, bendicion, maldicion. El icono se pinta encima del retrato o la
|
||||
barra del personaje para indicar un efecto activo, asi que la prioridad es la
|
||||
LEGIBILIDAD a tamano reducido (16-32 px): un simbolo audaz y simple, no una
|
||||
ilustracion recargada. Pasa el MISMO `ui_style` + `checkpoint` + (`lora`) a todos los
|
||||
estados del set para que combinen visualmente; varia solo `effect`. `transparent`
|
||||
recorta el fondo (alpha) listo para el motor. Para un atlas, genera cada icono y monta
|
||||
los PNG con `comfyui_build_grid`.
|
||||
|
||||
Eligela frente a sus hermanos por el ROL del asset:
|
||||
- **item_icon** -> objeto de inventario (espada, pocion, anillo): ilustracion de un
|
||||
objeto, no un simbolo de estado.
|
||||
- **ui_hud** -> chrome grande de la interfaz (botones, marcos, barras de vida): piezas
|
||||
de layout, no simbolos compactos.
|
||||
- **status_effect_icon (esta)** -> simbolo de estado compacto que se superpone al HUD,
|
||||
optimizado para legibilidad a tamano reducido.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- **Legibilidad > detalle**: el icono se muestra a 16-32 px sobre el personaje; un
|
||||
simbolo recargado se vuelve ilegible. El prompt empuja a "simple bold symbol,
|
||||
readable at small size" y el `size` por defecto es menor (256, no 512 como
|
||||
item_icon/ui_hud). El negativo por defecto rechaza "intricate details / complex /
|
||||
cluttered" justamente por esto. Si el modelo mete una escena, refuerza `ui_style`
|
||||
con "single bold icon, minimal".
|
||||
- **El recorte usa Rembg, NO luma-to-alpha**: un icono de estado es un simbolo solido
|
||||
con silueta definida, rembg lo recorta limpio. `comfyui_matting_luma_to_alpha` es
|
||||
para translucidos sobre negro (humo/fuego/magia) y aplanaria el simbolo — no la uses
|
||||
para estos iconos.
|
||||
- **Coherencia del set = mismos parametros**: si cambias `ui_style`/`checkpoint`/
|
||||
`lora`/`seed` entre estados, la barra deja de combinar. Fija esos y varia solo
|
||||
`effect`. Una convencion util: el color habla del tipo de efecto (verde veneno, rojo
|
||||
quemadura, azul congelacion) sin sacrificar la consistencia del simbolo.
|
||||
- **El texto/numero lo pone el motor, no la imagen**: el negativo por defecto empuja a
|
||||
"no text / no letters" para que la silueta quede limpia; el contador de turnos o el
|
||||
stack del buff se renderiza en el juego sobre el icono. Si quieres texto horneado,
|
||||
pasa un `negative` propio sin "text, letters".
|
||||
- **SDXL pide mas VRAM y resolucion**: con `checkpoint="juggernaut_xl_v11.safetensors"`
|
||||
sube `size` a 512/768; con dreamshaper_8 (SD1.5) deja 256 (holgado en 8GB lowvram).
|
||||
- `transparent=False` deja el simbolo opaco 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,229 @@
|
||||
"""Construye el workflow ComfyUI de UN icono de estado / buff-debuff (API format).
|
||||
|
||||
Iconos de estado de juego (veneno, quemadura, congelacion, escudo, regeneracion,
|
||||
aturdimiento, velocidad, sangrado, bendicion, maldicion): simbolo unico, audaz y
|
||||
LEGIBLE A TAMANO REDUCIDO, centrado, fondo limpio y uniforme, estilo de UI
|
||||
consistente entre estados del mismo set, recortable a alpha. Es el builder hermano
|
||||
de comfyui_build_item_icon_workflow / comfyui_build_ui_hud_workflow: mismo patron
|
||||
(PURO, dict API format) que compone funciones existentes del registry, no reescribe
|
||||
el grafo.
|
||||
|
||||
DISTINTO de item_icon y ui_hud: un icono de estado no es un objeto de inventario
|
||||
(item_icon) ni un elemento de chrome grande de la interfaz (ui_hud, botones/marcos/
|
||||
barras). Es un SIMBOLO compacto que se pinta encima del retrato/barra del personaje
|
||||
para indicar un efecto activo: por eso el prompt empuja a "simple bold symbol,
|
||||
readable at small size" y el tamano por defecto es menor (256), porque el icono se
|
||||
mostrara a 16-32 px en el HUD y un simbolo recargado se vuelve ilegible.
|
||||
|
||||
Cableado:
|
||||
|
||||
CheckpointLoaderSimple -> [LoraLoader opcional de estilo] -> KSampler
|
||||
-> CLIPTextEncode (prompt scaffold de icono de estado) ...
|
||||
-> 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 icono de estado es un simbolo
|
||||
SOLIDO con silueta definida; rembg recorta limpio la silueta dejando alpha. La
|
||||
luma-to-alpha es para translucidos sobre negro (humo/fuego/magia) y aplanaria el
|
||||
simbolo. 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 todos los iconos de un set (veneno,
|
||||
escudo, velocidad...) hace que combinen visualmente: es la clave de una barra de
|
||||
estados coherente, igual que en los iconos de inventario y los elementos del HUD.
|
||||
|
||||
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 iconos de estado: un solo simbolo limpio y
|
||||
# legible, sin escena ni personaje ni texto/marcas que ensucien la silueta
|
||||
# centrada y recortable. Empuja contra el detalle excesivo que arruina la
|
||||
# legibilidad cuando el icono se muestra a 16-32 px en el HUD.
|
||||
_STATUS_NEGATIVE = (
|
||||
"blurry, lowres, intricate details, complex, busy background, "
|
||||
"cluttered, multiple symbols, detailed scene, landscape, character, "
|
||||
"person, face, text, letters, words, watermark, signature, photo, "
|
||||
"photorealistic, realistic, 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_ui_hud_workflow:
|
||||
el nodo recorta la silueta del simbolo de estado 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_status_effect_icon_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_status_effect_icon_workflow(
|
||||
effect: str,
|
||||
*,
|
||||
ui_style: str = "game status icon, bold symbol, flat",
|
||||
checkpoint: str = "dreamshaper_8.safetensors",
|
||||
size: int = 256,
|
||||
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 = "status_effect_icon",
|
||||
) -> dict:
|
||||
"""Construye el dict (API format) del workflow de un icono de estado / buff-debuff.
|
||||
|
||||
Args:
|
||||
effect: nombre del efecto de estado (ej. "poison", "burning", "frozen",
|
||||
"shield", "regeneration", "stun", "speed boost", "bleed", "curse").
|
||||
Se inserta en un prompt scaffold de icono de estado. No puede estar
|
||||
vacio.
|
||||
ui_style: descriptor de estilo de UI que mantiene consistentes los iconos
|
||||
de un set (ej. "game status icon, bold symbol, flat", "sci-fi HUD
|
||||
status, neon glow", "pixel art status icon", "minimal flat status
|
||||
icon"). Pasa el MISMO ui_style + checkpoint + (lora) a todos los iconos
|
||||
de la barra de estados 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). keyword-only.
|
||||
size: lado del cuadrado en px (width = height = size). 256 por defecto:
|
||||
son iconos compactos que se muestran a tamano reducido en el HUD.
|
||||
keyword-only.
|
||||
transparent: si True inyecta Rembg y el PNG sale con alpha (fondo
|
||||
recortado). Si False deja el simbolo 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
|
||||
iconos de estado (un simbolo simple legible, fondo limpio, sin
|
||||
escena/personaje/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 icono de estado ('{effect} status effect
|
||||
icon, {ui_style}, simple bold symbol, centered, readable at small size,
|
||||
plain background, ...') + LoRA de estilo opcional + Rembg (si transparent).
|
||||
Es UN icono; una barra de estados completa -> llamar por effect con el
|
||||
mismo ui_style/checkpoint/lora y montar con comfyui_build_grid si se quiere
|
||||
un atlas.
|
||||
|
||||
Raises:
|
||||
ValueError: si effect 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 effect or not effect.strip():
|
||||
raise ValueError(
|
||||
"comfyui_build_status_effect_icon_workflow: 'effect' no puede estar vacio"
|
||||
)
|
||||
|
||||
lora_strength = max(0.0, min(2.0, float(lora_strength)))
|
||||
neg = _STATUS_NEGATIVE if negative is None else negative
|
||||
|
||||
# Prompt scaffold de icono de estado: simbolo unico, audaz, centrado, legible
|
||||
# a tamano reducido, fondo plano, recortable.
|
||||
positive = (
|
||||
f"{effect.strip()} status effect icon, {ui_style}, simple bold symbol, "
|
||||
"centered, readable at small size, plain background, clean, 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_status_effect_icon_workflow(
|
||||
"poison",
|
||||
ui_style="game status icon, bold symbol, flat, green",
|
||||
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,116 @@
|
||||
"""Tests offline (sin red, sin GPU) de comfyui_build_status_effect_icon_workflow.
|
||||
|
||||
Verifican que el dict en API format se construye correctamente: clases presentes,
|
||||
cableado del Rembg, prompt scaffold de icono de estado, y reflejo de los argumentos
|
||||
(effect, ui_style, size, transparent, lora). No tocan el servidor ComfyUI.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
||||
|
||||
from ml.comfyui_build_status_effect_icon_workflow import ( # noqa: E402
|
||||
comfyui_build_status_effect_icon_workflow,
|
||||
)
|
||||
|
||||
|
||||
def _classes(wf):
|
||||
return {n["class_type"] for n in wf.values()}
|
||||
|
||||
|
||||
def _positive_prompt(wf):
|
||||
"""Texto positivo: el CLIPTextEncode al que apunta KSampler.positive."""
|
||||
ks = next(n for n in wf.values() if n["class_type"] == "KSampler")
|
||||
pos_id = ks["inputs"]["positive"][0]
|
||||
return wf[pos_id]["inputs"]["text"]
|
||||
|
||||
|
||||
def test_golden_transparent():
|
||||
"""Caso feliz: icono transparente -> Rembg cableado, prompt de estado, clases base."""
|
||||
wf = comfyui_build_status_effect_icon_workflow("poison", transparent=True, seed=42)
|
||||
cls = _classes(wf)
|
||||
for expected in {
|
||||
"CheckpointLoaderSimple",
|
||||
"KSampler",
|
||||
"VAEDecode",
|
||||
"SaveImage",
|
||||
"Image Rembg (Remove Background)",
|
||||
}:
|
||||
assert expected in cls, f"falta clase {expected}"
|
||||
|
||||
prompt = _positive_prompt(wf)
|
||||
assert "poison" in prompt
|
||||
assert "status effect icon" in prompt
|
||||
assert "centered" in prompt
|
||||
assert "readable at small size" in prompt
|
||||
|
||||
# SaveImage debe tomar la imagen del Rembg, no del VAEDecode.
|
||||
save = next(n for n in wf.values() if n["class_type"] == "SaveImage")
|
||||
rembg_id = next(
|
||||
nid for nid, n in wf.items() if n["class_type"] == "Image Rembg (Remove Background)"
|
||||
)
|
||||
assert save["inputs"]["images"][0] == rembg_id
|
||||
rembg = wf[rembg_id]
|
||||
assert rembg["inputs"]["transparency"] is True
|
||||
|
||||
|
||||
def test_edge_transparent_false_no_rembg():
|
||||
"""transparent=False -> sin nodo Rembg; SaveImage cuelga del VAEDecode."""
|
||||
wf = comfyui_build_status_effect_icon_workflow("shield", transparent=False)
|
||||
assert "Image Rembg (Remove Background)" not in _classes(wf)
|
||||
save = next(n for n in wf.values() if n["class_type"] == "SaveImage")
|
||||
vae_id = next(nid for nid, n in wf.items() if n["class_type"] == "VAEDecode")
|
||||
assert save["inputs"]["images"][0] == vae_id
|
||||
|
||||
|
||||
def test_edge_size_reflected_square():
|
||||
"""size se refleja como width == height (cuadrado). Default 256 (icono compacto)."""
|
||||
wf = comfyui_build_status_effect_icon_workflow("frozen", size=128)
|
||||
latent = next(n for n in wf.values() if n["class_type"] == "EmptyLatentImage")
|
||||
assert latent["inputs"]["width"] == 128
|
||||
assert latent["inputs"]["height"] == 128
|
||||
|
||||
wf_default = comfyui_build_status_effect_icon_workflow("frozen")
|
||||
latent_d = next(n for n in wf_default.values() if n["class_type"] == "EmptyLatentImage")
|
||||
assert latent_d["inputs"]["width"] == 256
|
||||
assert latent_d["inputs"]["height"] == 256
|
||||
|
||||
|
||||
def test_edge_ui_style_reflected():
|
||||
"""ui_style se inserta en el prompt positivo."""
|
||||
wf = comfyui_build_status_effect_icon_workflow(
|
||||
"burning", ui_style="sci-fi HUD status, neon glow"
|
||||
)
|
||||
assert "sci-fi HUD status, neon glow" in _positive_prompt(wf)
|
||||
|
||||
|
||||
def test_edge_effect_reflected():
|
||||
"""effect se inserta literal en el prompt positivo."""
|
||||
wf = comfyui_build_status_effect_icon_workflow("regeneration")
|
||||
assert "regeneration" in _positive_prompt(wf)
|
||||
|
||||
|
||||
def test_edge_lora_injected():
|
||||
"""lora -> LoraLoader presente con la fuerza dada."""
|
||||
wf = comfyui_build_status_effect_icon_workflow(
|
||||
"speed boost", lora="detail_tweaker_sd15.safetensors", lora_strength=0.8
|
||||
)
|
||||
loras = [n for n in wf.values() if n["class_type"] == "LoraLoader"]
|
||||
assert len(loras) == 1
|
||||
assert loras[0]["inputs"]["lora_name"] == "detail_tweaker_sd15.safetensors"
|
||||
assert loras[0]["inputs"]["strength_model"] == pytest.approx(0.8)
|
||||
|
||||
|
||||
def test_error_empty_effect():
|
||||
"""effect vacio -> ValueError."""
|
||||
with pytest.raises(ValueError):
|
||||
comfyui_build_status_effect_icon_workflow(" ")
|
||||
|
||||
|
||||
def test_determinism():
|
||||
"""Mismos argumentos -> mismo dict (funcion pura)."""
|
||||
a = comfyui_build_status_effect_icon_workflow("stun", seed=7)
|
||||
b = comfyui_build_status_effect_icon_workflow("stun", seed=7)
|
||||
assert a == b
|
||||
Reference in New Issue
Block a user