feat(gamedev): comfyui_build_title_lettering_workflow — texto/logo de título de juego (lettering estilizado, apaisado, alpha)
Builder puro (dict API format) hermano de ui_hud/splash_art: compone comfyui_build_txt2img_workflow + comfyui_inject_lora + Image Rembg. Renderiza el nombre del juego/una palabra con un tratamiento de lettering (metálico, tallado, neón, fuego...), formato apaisado 1024x512, fondo recortable a alpha. El negativo NO rechaza texto (el lettering es el sujeto). Documentada la limitación clave: la difusión no garantiza la ortografía exacta del texto (letras de más/deformadas; una palabra-objeto como DRAGON se ilustra en vez de escribirse). Mitigaciones: palabras cortas en mayúscula, re-roll de seeds, SDXL > SD1.5, o pintar el texto real en el motor. Tests 9/9 verde (offline). Verificado e2e en GPU (8GB lowvram): DRAGON/fire engraved (SD1.5, prompt_id 6f3920b7) y AETHER/epic fantasy metallic (SDXL, prompt_id 2a7fe8ba, logo metálico dorado + alpha). Fila en docs/capabilities/gamedev-2d.md. Report en reports/0165. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -51,6 +51,7 @@ VFX (ver `reports/0143`).
|
|||||||
| `comfyui_build_projectile_workflow_py_ml` | `(projectile, *, direction="right", glow=False, style="game projectile, side view", checkpoint="dreamshaper_8…", size=512, transparent=True, seed=0, lora=None, …) -> dict` | UN proyectil orientado (flecha, bala, bola de fuego, rayo, misil, hechizo): sprite pequeño con **orientación** (apunta a la derecha por defecto, ángulo 0 — el motor rota el sprite), aislado, listo para instanciar. **`glow` elige el camino a alpha**: `glow=False` (defecto) = proyectil SÓLIDO con silueta → `plain background` + **Rembg** (alpha por recorte, como `item_icon`/`topdown_sprite`); `glow=True` = brillante/mágico → `glowing, on black background` **sin Rembg** (recortaría el halo), insumo de **`comfyui_matting_luma_to_alpha`** que el caller aplica luego (como `vfx_spritesheet`/`decal_overlay`). `glow=True` ignora `transparent`/`rembg_model`; el negativo por defecto NO rechaza "black background". `direction` se inserta como `pointing {direction}` (`""`/None = sin orientación). Set coherente = mismo `style`/`checkpoint`/`lora`, varía solo `projectile`/`seed`. Probado e2e en GPU con SD1.5 — fireball glow sobre negro + luma→alpha RGBA (`reports/0161`). SD1.5. |
|
| `comfyui_build_projectile_workflow_py_ml` | `(projectile, *, direction="right", glow=False, style="game projectile, side view", checkpoint="dreamshaper_8…", size=512, transparent=True, seed=0, lora=None, …) -> dict` | UN proyectil orientado (flecha, bala, bola de fuego, rayo, misil, hechizo): sprite pequeño con **orientación** (apunta a la derecha por defecto, ángulo 0 — el motor rota el sprite), aislado, listo para instanciar. **`glow` elige el camino a alpha**: `glow=False` (defecto) = proyectil SÓLIDO con silueta → `plain background` + **Rembg** (alpha por recorte, como `item_icon`/`topdown_sprite`); `glow=True` = brillante/mágico → `glowing, on black background` **sin Rembg** (recortaría el halo), insumo de **`comfyui_matting_luma_to_alpha`** que el caller aplica luego (como `vfx_spritesheet`/`decal_overlay`). `glow=True` ignora `transparent`/`rembg_model`; el negativo por defecto NO rechaza "black background". `direction` se inserta como `pointing {direction}` (`""`/None = sin orientación). Set coherente = mismo `style`/`checkpoint`/`lora`, varía solo `projectile`/`seed`. Probado e2e en GPU con SD1.5 — fireball glow sobre negro + luma→alpha RGBA (`reports/0161`). SD1.5. |
|
||||||
| `comfyui_build_structure_workflow_py_ml` | `(structure, *, view="isometric", style="game building", checkpoint="dreamshaper_8…", size=512, transparent=True, seed=0, lora=None, …) -> dict` | UN edificio/estructura de escenario (casa, torre, castillo, tienda, posada, ruina, muralla, puente, templo, faro): UN **building COMPLETO** y centrado a perspectiva de juego (`{view} view`, iso por defecto), fondo limpio recortable a alpha (`{structure}, {view} view, {style}, full building, complete structure, single building, centered, plain background, game asset, architecture…`) → txt2img cuadrado + LoRA estilo/iso opcional + Rembg (alpha). **EDIFICACIÓN grande que ocupa varios tiles y define el escenario**, no un objeto pequeño suelto (≠ `prop_object`, que es atrezzo que se deja sobre un tile); el negativo rechaza `small object / single item / prop / furniture`. `view` fija la perspectiva del mapa (iso/side/front/top-down/¾); LoRA iso fija mejor el ángulo 2:1. Set coherente = mismo `view`/`style`/`checkpoint`/`lora`, varía solo `structure`. Probado e2e en GPU con SD1.5 — `medieval blacksmith shop` iso 512×512 RGBA, edificio centrado recortado a alpha (centroide 0.54/0.53, `reports/0164`). SD1.5. |
|
| `comfyui_build_structure_workflow_py_ml` | `(structure, *, view="isometric", style="game building", checkpoint="dreamshaper_8…", size=512, transparent=True, seed=0, lora=None, …) -> dict` | UN edificio/estructura de escenario (casa, torre, castillo, tienda, posada, ruina, muralla, puente, templo, faro): UN **building COMPLETO** y centrado a perspectiva de juego (`{view} view`, iso por defecto), fondo limpio recortable a alpha (`{structure}, {view} view, {style}, full building, complete structure, single building, centered, plain background, game asset, architecture…`) → txt2img cuadrado + LoRA estilo/iso opcional + Rembg (alpha). **EDIFICACIÓN grande que ocupa varios tiles y define el escenario**, no un objeto pequeño suelto (≠ `prop_object`, que es atrezzo que se deja sobre un tile); el negativo rechaza `small object / single item / prop / furniture`. `view` fija la perspectiva del mapa (iso/side/front/top-down/¾); LoRA iso fija mejor el ángulo 2:1. Set coherente = mismo `view`/`style`/`checkpoint`/`lora`, varía solo `structure`. Probado e2e en GPU con SD1.5 — `medieval blacksmith shop` iso 512×512 RGBA, edificio centrado recortado a alpha (centroide 0.54/0.53, `reports/0164`). SD1.5. |
|
||||||
| `comfyui_build_particle_texture_workflow_py_ml` | `(particle, *, soft=True, style="particle texture, soft glow", checkpoint="dreamshaper_8…", size=256, seed=0, lora=None, …) -> dict` | UNA textura de **partícula individual** reutilizable (chispa, humo, polvo, destello/flare, gota, copo, hoja, círculo de energía) — el "ladrillo" que el sistema de partículas del motor (Godot `GPUParticles2D`, Unity VFX Graph) instancia a **miles** y anima (spawn/fade/color over lifetime). Aislada y centrada **sobre fondo NEGRO** (`{particle} particle, {style}, isolated on pure black background, <soft|sharp> edges, single element, for game particle system…`) → txt2img cuadrado + LoRA estilo opcional. **`soft` controla el borde**: `soft=True` (defecto) → `soft glow, feathered edges` (humo/destello/gota); `soft=False` → `crisp sharp edges, high contrast` (chispa/copo/hoja). **NO inyecta Rembg** (rompería el falloff translúcido): insumo de **`comfyui_matting_luma_to_alpha`** (luma=alpha, additive blend en el motor). **`size` por defecto pequeño (256)** porque se replica a miles. **DISTINTO de `vfx_spritesheet`** (ese es la SECUENCIA animada de un efecto; esto es UNA textura estática reutilizable) **y de `decal_overlay`** (ése es una mancha de desgaste estática para superponer; éste es un emisor de partículas). ⚠️ el `style` por defecto trae "soft glow" → si pides `soft=False` para algo nítido, usa un `style` sin connotación suave. Probado e2e en GPU con SD1.5 — `spark` 256×256 sobre negro plano (dark 85%) + luma→alpha RGBA con falloff preservado (`reports/0163`). SD1.5. |
|
| `comfyui_build_particle_texture_workflow_py_ml` | `(particle, *, soft=True, style="particle texture, soft glow", checkpoint="dreamshaper_8…", size=256, seed=0, lora=None, …) -> dict` | UNA textura de **partícula individual** reutilizable (chispa, humo, polvo, destello/flare, gota, copo, hoja, círculo de energía) — el "ladrillo" que el sistema de partículas del motor (Godot `GPUParticles2D`, Unity VFX Graph) instancia a **miles** y anima (spawn/fade/color over lifetime). Aislada y centrada **sobre fondo NEGRO** (`{particle} particle, {style}, isolated on pure black background, <soft|sharp> edges, single element, for game particle system…`) → txt2img cuadrado + LoRA estilo opcional. **`soft` controla el borde**: `soft=True` (defecto) → `soft glow, feathered edges` (humo/destello/gota); `soft=False` → `crisp sharp edges, high contrast` (chispa/copo/hoja). **NO inyecta Rembg** (rompería el falloff translúcido): insumo de **`comfyui_matting_luma_to_alpha`** (luma=alpha, additive blend en el motor). **`size` por defecto pequeño (256)** porque se replica a miles. **DISTINTO de `vfx_spritesheet`** (ese es la SECUENCIA animada de un efecto; esto es UNA textura estática reutilizable) **y de `decal_overlay`** (ése es una mancha de desgaste estática para superponer; éste es un emisor de partículas). ⚠️ el `style` por defecto trae "soft glow" → si pides `soft=False` para algo nítido, usa un `style` sin connotación suave. Probado e2e en GPU con SD1.5 — `spark` 256×256 sobre negro plano (dark 85%) + luma→alpha RGBA con falloff preservado (`reports/0163`). SD1.5. |
|
||||||
|
| `comfyui_build_title_lettering_workflow_py_ml` | `(text, *, letter_style="epic fantasy metallic", checkpoint="juggernaut_xl_v11…", width=1024, height=512, transparent=True, seed=0, lora=None, …) -> dict` | EL texto/logo de **título** de un juego (el nombre del juego o una palabra) renderizado con un **tratamiento de lettering** (metálico, tallado en fuego/piedra/madera, neón, cristal, oro), formato **apaisado** (`width>height`, 1024×512 por defecto), fondo plano recortable a alpha (`the word "{text}" as a game logo, {letter_style} lettering, stylized typography, centered, plain background…`) → txt2img apaisado + LoRA estilo opcional + Rembg (alpha). El **negativo NO rechaza texto** (el lettering es el sujeto) y empuja contra el ruido textual (`extra letters/jumbled text/deformed letters`). El VALOR es el ESTILO del lettering, **NO** la fidelidad tipográfica: ⚠️ la difusión renderiza texto de forma imperfecta — letras de más, deformadas o mal escritas; mitigar con palabras CORTAS en MAYÚSCULA, **re-roll de seeds** (`comfyui_batch_generate`), SDXL > SD1.5 para texto, o pintar el texto real con una fuente en el motor. **Una palabra que es un objeto concreto (DRAGON) → el modelo dibuja el objeto, no las letras** — usar palabras abstractas o reforzar `letter_style`. Marca coherente = mismo `letter_style`/`checkpoint`/`lora`, varía solo `text`. Recorte por **Rembg** (logo sólido), no luma→alpha. Probado e2e en GPU: `DRAGON`/`fire engraved` SD1.5 1024×512 → ilustró dragones rojos (alpha OK, confirma el gotcha de palabra-objeto, `prompt_id 6f3920b7`); `AETHER`/`epic fantasy metallic` SDXL 768×384 → **logo de texto metálico dorado** legible con ortografía imperfecta + alpha (`prompt_id 2a7fe8ba`, `reports/0165`). SD1.5/SDXL. |
|
||||||
|
|
||||||
## Funciones de post-proceso y puente (`gamedev`, CPU)
|
## Funciones de post-proceso y puente (`gamedev`, CPU)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
---
|
||||||
|
name: comfyui_build_title_lettering_workflow
|
||||||
|
kind: function
|
||||||
|
lang: py
|
||||||
|
domain: ml
|
||||||
|
version: "1.0.0"
|
||||||
|
purity: pure
|
||||||
|
signature: "def comfyui_build_title_lettering_workflow(text: str, *, letter_style: str = \"epic fantasy metallic\", checkpoint: str = \"juggernaut_xl_v11.safetensors\", width: int = 1024, height: 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 = \"title_lettering\") -> dict"
|
||||||
|
description: "Construye el dict (API format) del workflow de UN texto/logo de titulo de juego 2D: el nombre del juego o una palabra renderizada con un TRATAMIENTO de lettering (metalico, fantasy tallado, neon, piedra, fuego, cristal, madera...), formato apaisado, fondo plano recortable a alpha, para usar como logo/titulo en menus, splash o cabecera. El valor del builder es el ESTILO del lettering; la difusion NO garantiza la ortografia exacta del texto (limitacion documentada). Compone comfyui_build_txt2img_workflow + comfyui_inject_lora (estilo opcional) + Image Rembg (fondo transparente si transparent). Hermano de comfyui_build_ui_hud/splash_art_workflow. Pura, sin red ni I/O. class_types verificados contra /object_info (8GB lowvram)."
|
||||||
|
tags: [comfyui, ml, gamedev, gamedev-2d, title, logo, lettering, typography, text, 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: text
|
||||||
|
desc: "La palabra o nombre del juego a renderizar como logo (ej. 'DRAGON', 'Mystic Realms', 'VOID'). Se inserta ENTRECOMILLADO en el prompt scaffold para que el modelo lo trate como cadena literal. No puede estar vacio. La difusion no garantiza la ortografia exacta: usa palabras cortas en mayuscula y re-rollea seeds."
|
||||||
|
- name: letter_style
|
||||||
|
desc: "Tratamiento visual del lettering, el VALOR real del builder (ej. 'epic fantasy metallic', 'fire engraved', 'neon sci-fi glow', 'carved stone', 'cracked ice crystal', 'wooden carved', 'golden royal'). Pasa el MISMO letter_style + checkpoint + lora a varios titulos/subtitulos para coherencia de marca. keyword-only."
|
||||||
|
- name: checkpoint
|
||||||
|
desc: "Checkpoint del servidor. 'juggernaut_xl_v11.safetensors' (SDXL, mejor render de texto y detalle) por defecto; en 8GB lowvram con apaisado grande puede ser pesado: usa 'dreamshaper_8.safetensors' (SD1.5) y/o baja la resolucion. keyword-only."
|
||||||
|
- name: width
|
||||||
|
desc: "Ancho del lienzo en px. Logo de titulo -> apaisado (width > height). 1024 por defecto. keyword-only."
|
||||||
|
- name: height
|
||||||
|
desc: "Alto del lienzo en px. 512 por defecto (2:1 con width=1024, encuadre tipico de logotipo en una linea). Para palabras muy cortas un cuadrado tambien vale (bajar width). keyword-only."
|
||||||
|
- name: transparent
|
||||||
|
desc: "Si True inyecta Image Rembg y el PNG sale con alpha (fondo recortado, listo para superponer en el menu). False = lettering opaco sobre fondo plano, recortable luego por el caller. keyword-only."
|
||||||
|
- name: seed
|
||||||
|
desc: "Semilla del KSampler. Re-rollear seeds es la via principal para conseguir que el texto salga legible: misma text/letter_style, varias seeds, elegir el mejor. keyword-only."
|
||||||
|
- name: lora
|
||||||
|
desc: "LoRA de estilo opcional en models/loras (ej. un LoRA de logotipos o de tipografia tematica). 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 lettering (una pieza de texto limpia, fondo plano, sin escena/personaje; NO rechaza texto, porque el texto es el sujeto). 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 apaisada con prompt scaffold de logo de texto ('the word \"{text}\" as a game logo, {letter_style} lettering, stylized typography, centered, plain background, high detail') + LoRA de estilo opcional + Image Rembg (si transparent). UN logo; titulo + subtitulos coherentes -> llamar por text con mismo letter_style/checkpoint/lora."
|
||||||
|
tested: true
|
||||||
|
tests: ["golden transparent: clases CheckpointLoaderSimple/KSampler/VAEDecode/SaveImage/Image Rembg; '\"DRAGON\"' entrecomillado + 'game logo' + 'fire engraved' + 'lettering' + 'stylized typography' en prompt; SaveImage <- Rembg; transparency True", "edge transparent=False: sin Rembg, SaveImage <- VAEDecode", "edge dims: width/height reflejados; default apaisado 1024x512 (width>height)", "edge letter_style en prompt", "edge text entrecomillado en prompt", "edge negative NO rechaza texto como sujeto (tokens text/letters/words ausentes)", "edge lora: LoraLoader presente con strength", "error text vacio -> ValueError", "determinismo"]
|
||||||
|
test_file_path: "python/functions/ml/comfyui_build_title_lettering_workflow_test.py"
|
||||||
|
file_path: "python/functions/ml/comfyui_build_title_lettering_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_title_lettering_workflow import comfyui_build_title_lettering_workflow
|
||||||
|
|
||||||
|
# El titulo "DRAGON" tallado en fuego, fondo transparente (alpha), listo para submit.
|
||||||
|
wf = comfyui_build_title_lettering_workflow(
|
||||||
|
"DRAGON",
|
||||||
|
letter_style="fire engraved",
|
||||||
|
checkpoint="dreamshaper_8.safetensors",
|
||||||
|
transparent=True,
|
||||||
|
seed=42,
|
||||||
|
)
|
||||||
|
# Generacion real (GPU): submit -> wait -> fetch.
|
||||||
|
# from ml.comfyui_submit_workflow import comfyui_submit_workflow
|
||||||
|
# from ml.comfyui_wait_result import comfyui_wait_result
|
||||||
|
# from ml.comfyui_fetch_output_image import comfyui_fetch_output_image
|
||||||
|
# pid = comfyui_submit_workflow(wf)["prompt_id"]
|
||||||
|
# out = comfyui_wait_result(pid) # outputs por nodo
|
||||||
|
# img = out[next(iter(out))]["images"][0] # {filename, subfolder, type}
|
||||||
|
# comfyui_fetch_output_image(img["filename"], subfolder=img["subfolder"], dest_dir="/tmp")
|
||||||
|
#
|
||||||
|
# Re-roll de seeds (la via para que el texto salga legible): misma text/letter_style,
|
||||||
|
# varias seeds, elegir el mejor -> comfyui_batch_generate(wf, seeds=[1,2,3,4]).
|
||||||
|
# Logo + subtitulo coherentes: mismo letter_style/checkpoint en ambos.
|
||||||
|
```
|
||||||
|
|
||||||
|
O lanzable directo con: `./fn run comfyui_build_title_lettering_workflow` (imprime nodos + class_types del ejemplo).
|
||||||
|
|
||||||
|
## Cuando usarla
|
||||||
|
|
||||||
|
Cuando necesites el logo de texto / titulo de un juego (el nombre, una palabra clave,
|
||||||
|
un subtitulo) con un tratamiento visual concreto para menus, pantalla de inicio,
|
||||||
|
splash o cabecera: lettering metalico, tallado en piedra/madera, neon, fuego, cristal,
|
||||||
|
oro. Genera el ESTILO del texto (lo que la difusion reproduce bien) sobre fondo plano
|
||||||
|
recortable a alpha. Pasa el MISMO `letter_style` + `checkpoint` + (`lora`) a varios
|
||||||
|
titulos/subtitulos para que la marca combine; varia solo `text`. Para el texto exacto,
|
||||||
|
re-rollea seeds (`comfyui_batch_generate`) y/o retoca en post.
|
||||||
|
|
||||||
|
Eligela frente a sus hermanos por el ROL del asset:
|
||||||
|
- **splash_art** -> la ilustracion apaisada de portada/loading (sin texto). El titulo
|
||||||
|
se superpone encima; este builder pinta ESE titulo.
|
||||||
|
- **ui_hud** -> chrome de la interfaz (botones, marcos, barras), sin texto como sujeto.
|
||||||
|
- **title_lettering (esta)** -> el TEXTO estilizado en si (el nombre del juego como logo).
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- **La difusion NO garantiza la ortografia exacta del texto (limitacion clave)**: los
|
||||||
|
modelos SD1.5/SDXL renderizan texto de forma imperfecta. Es habitual que salgan
|
||||||
|
letras de mas, letras deformadas o palabras parecidas pero mal escritas — mas cuanto
|
||||||
|
mas larga o rara sea `text`. El VALOR de este builder es el ESTILO del lettering, no
|
||||||
|
la fidelidad tipografica. Mitigaciones: (1) palabras CORTAS y en MAYUSCULA; (2)
|
||||||
|
re-rollear varias seeds (`comfyui_batch_generate`) y quedarte con la mejor; (3) subir
|
||||||
|
a SDXL (juggernaut_xl, default) que escribe algo mejor que SD1.5; (4) retocar/recomponer
|
||||||
|
las letras en post; o (5) dejar que el motor pinte el texto real con una fuente y usar
|
||||||
|
esto solo como textura/tratamiento de fondo del logo.
|
||||||
|
- **El recorte usa Rembg, NO luma-to-alpha**: un logo de texto es una pieza solida con
|
||||||
|
silueta definida (las letras tratadas); rembg recorta limpio el fondo plano dejando
|
||||||
|
alpha. `comfyui_matting_luma_to_alpha` es para translucidos sobre negro (humo/fuego/
|
||||||
|
magia) y se comeria las partes oscuras de un logo metalico/tallado — no la uses aqui.
|
||||||
|
Si el lettering es un texto NEON brillante sobre negro (no solido), considera generarlo
|
||||||
|
con `transparent=False` sobre fondo negro y aplicar luma-to-alpha tu mismo.
|
||||||
|
- **Apaisado por defecto (1024x512, 2:1)**: el titulo es una pieza de texto en una linea,
|
||||||
|
mas ancha que alta. Para una sola palabra corta un cuadrado tambien vale (baja `width`);
|
||||||
|
para un logo con texto en dos lineas, sube `height`.
|
||||||
|
- **Coherencia de marca = mismos parametros**: si cambias `letter_style`/`checkpoint`/
|
||||||
|
`lora`/`seed` entre el titulo y los subtitulos, dejan de combinar. Fija esos y varia
|
||||||
|
solo `text`.
|
||||||
|
- **SDXL pide mas VRAM**: con `checkpoint="juggernaut_xl_v11.safetensors"` (default) y
|
||||||
|
apaisado grande en 8GB lowvram puede ir justo; si hay OOM baja a `dreamshaper_8`
|
||||||
|
(SD1.5) y/o reduce `width`/`height`.
|
||||||
|
- `transparent=False` deja el lettering 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,247 @@
|
|||||||
|
"""Construye el workflow ComfyUI de UN texto/logo de titulo de juego (API format).
|
||||||
|
|
||||||
|
Lettering / texto estilizado de titulo: el nombre del juego o una palabra renderizada
|
||||||
|
con un TRATAMIENTO visual concreto (metalico, fantasy tallado, neon, piedra, fuego,
|
||||||
|
cristal, madera...) sobre fondo plano, recortable a alpha, para usar como logo o
|
||||||
|
titulo en menus, splash screen o cabecera. Es el builder hermano de
|
||||||
|
comfyui_build_ui_hud_workflow / comfyui_build_splash_art_workflow: mismo patron
|
||||||
|
(PURO, dict API format) que compone funciones existentes del registry, no reescribe
|
||||||
|
el grafo.
|
||||||
|
|
||||||
|
IMPORTANTE — limitacion de la difusion con texto: los modelos de difusion (SD1.5/
|
||||||
|
SDXL) renderizan texto de forma IMPERFECTA. No hay garantia de que las letras salgan
|
||||||
|
con la ortografia exacta de `text`: aparecen letras de mas, letras deformadas o
|
||||||
|
palabras parecidas pero mal escritas, sobre todo con palabras largas o poco comunes.
|
||||||
|
El VALOR de este builder es el ESTILO / tratamiento del lettering (el aspecto
|
||||||
|
metalico, tallado, neon, etc.), no la fidelidad tipografica. Para el texto exacto:
|
||||||
|
re-rollear varias seeds y elegir el mejor, usar palabras CORTAS y en mayuscula, y/o
|
||||||
|
retocar/recomponer las letras en post (o dejar que el motor pinte el texto real con
|
||||||
|
una fuente y usar esto solo como textura/tratamiento). Ver Gotchas en el .md.
|
||||||
|
|
||||||
|
Cableado:
|
||||||
|
|
||||||
|
CheckpointLoaderSimple -> [LoraLoader opcional de estilo] -> KSampler
|
||||||
|
-> CLIPTextEncode (prompt scaffold de logo de texto) ...
|
||||||
|
-> VAEDecode -> [Image Rembg opcional] -> SaveImage
|
||||||
|
|
||||||
|
Compone:
|
||||||
|
- comfyui_build_txt2img_workflow -> base txt2img apaisada (logo es mas ancho que alto)
|
||||||
|
- comfyui_inject_lora -> LoRA de estilo opcional (consistencia de marca)
|
||||||
|
- 'Image Rembg (Remove Background)' (helper local) -> fondo transparente
|
||||||
|
|
||||||
|
Por que Rembg y NO comfyui_matting_luma_to_alpha: un logo de texto es una pieza
|
||||||
|
SOLIDA con silueta definida (las letras tratadas), rembg recorta limpio el fondo
|
||||||
|
plano dejando alpha. La luma-to-alpha es para translucidos sobre negro
|
||||||
|
(humo/fuego/magia) y se comeria las partes oscuras de un logo metalico/tallado. 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.
|
||||||
|
|
||||||
|
Por que apaisado (width > height): el titulo de un juego es una pieza de texto en
|
||||||
|
una sola linea (o pocas), mas ancha que alta; 1024x512 (2:1) da el encuadre tipico
|
||||||
|
de un logotipo. Para palabras muy cortas un cuadrado tambien funciona (bajar width).
|
||||||
|
|
||||||
|
El mismo letter_style + checkpoint + (lora) en varios titulos/subtitulos hace que
|
||||||
|
combinen visualmente: es la clave de una identidad de marca coherente, igual que en
|
||||||
|
los iconos de inventario o 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 un logo de TEXTO: una pieza de lettering limpia
|
||||||
|
# y centrada, sin escena ni personaje que ensucien el fondo plano recortable. NO
|
||||||
|
# rechaza "text/letters/words" (al contrario que ui_hud/status_effect): aqui el texto
|
||||||
|
# ES el sujeto. Empuja contra el ruido textual tipico de la difusion (letras de mas,
|
||||||
|
# texto enrevesado, letras deformadas) para mejorar algo la legibilidad.
|
||||||
|
_LETTERING_NEGATIVE = (
|
||||||
|
"blurry, lowres, extra letters, jumbled text, deformed letters, "
|
||||||
|
"misspelled, gibberish, cluttered, busy background, scene, landscape, "
|
||||||
|
"character, person, face, photo, photorealistic, watermark, signature, "
|
||||||
|
"cropped, out of frame, jpeg artifacts, low quality, 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_ui_hud_workflow / comfyui_build_status_effect_icon_workflow:
|
||||||
|
el nodo recorta el fondo plano dejando el lettering sobre 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_title_lettering_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_title_lettering_workflow(
|
||||||
|
text: str,
|
||||||
|
*,
|
||||||
|
letter_style: str = "epic fantasy metallic",
|
||||||
|
checkpoint: str = "juggernaut_xl_v11.safetensors",
|
||||||
|
width: int = 1024,
|
||||||
|
height: 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 = "title_lettering",
|
||||||
|
) -> dict:
|
||||||
|
"""Construye el dict (API format) del workflow de un texto/logo de titulo de juego.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text: la palabra o nombre del juego a renderizar como logo (ej. "DRAGON",
|
||||||
|
"Mystic Realms", "VOID"). Se inserta ENTRECOMILLADO en el prompt scaffold
|
||||||
|
para que el modelo lo trate como texto literal. No puede estar vacio.
|
||||||
|
OJO: la difusion no garantiza la ortografia exacta (ver Gotchas); usa
|
||||||
|
palabras cortas en mayuscula y re-rollea seeds para el mejor resultado.
|
||||||
|
letter_style: tratamiento visual del lettering, el VALOR real de este builder
|
||||||
|
(ej. "epic fantasy metallic", "fire engraved", "neon sci-fi glow",
|
||||||
|
"carved stone", "cracked ice crystal", "wooden carved", "golden royal").
|
||||||
|
Pasa el MISMO letter_style + checkpoint + (lora) a varios titulos/subtitulos
|
||||||
|
para coherencia de marca. keyword-only.
|
||||||
|
checkpoint: checkpoint del servidor. 'juggernaut_xl_v11.safetensors' (SDXL,
|
||||||
|
mejor render de texto y detalle, recomendado para lettering) por defecto;
|
||||||
|
en 8GB lowvram con apaisado grande puede ser pesado: si la GPU se queda
|
||||||
|
corta usa 'dreamshaper_8.safetensors' (SD1.5) y/o baja la resolucion.
|
||||||
|
keyword-only.
|
||||||
|
width: ancho del lienzo en px. Logo de titulo -> apaisado (width > height).
|
||||||
|
1024 por defecto. keyword-only.
|
||||||
|
height: alto del lienzo en px. 512 por defecto (2:1 con width=1024, encuadre
|
||||||
|
tipico de logotipo en una linea). Para palabras muy cortas un cuadrado
|
||||||
|
tambien vale (bajar width). keyword-only.
|
||||||
|
transparent: si True inyecta Rembg y el PNG sale con alpha (fondo recortado,
|
||||||
|
listo para superponer en el menu). Si False deja el lettering opaco sobre
|
||||||
|
fondo plano, recortable luego por el caller/pipeline. keyword-only.
|
||||||
|
seed: semilla del KSampler. Re-rollear seeds es la via principal para conseguir
|
||||||
|
que el texto salga legible: misma text/letter_style, varias seeds, elegir
|
||||||
|
el mejor. keyword-only.
|
||||||
|
lora: LoRA de estilo opcional en models/loras (ej. un LoRA de logotipos o de
|
||||||
|
tipografia tematica). 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
|
||||||
|
lettering (una pieza de texto limpia, fondo plano, sin escena/personaje;
|
||||||
|
NO rechaza 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: base txt2img apaisada
|
||||||
|
con prompt scaffold de logo de texto ('the word "{text}" as a game logo,
|
||||||
|
{letter_style} lettering, stylized typography, plain background, high detail')
|
||||||
|
+ LoRA de estilo opcional + Rembg (si transparent). Es UN logo; un set de
|
||||||
|
titulo + subtitulos -> llamar por text con el mismo letter_style/checkpoint/
|
||||||
|
lora para coherencia de marca.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: si text 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 text or not text.strip():
|
||||||
|
raise ValueError(
|
||||||
|
"comfyui_build_title_lettering_workflow: 'text' no puede estar vacio"
|
||||||
|
)
|
||||||
|
|
||||||
|
lora_strength = max(0.0, min(2.0, float(lora_strength)))
|
||||||
|
neg = _LETTERING_NEGATIVE if negative is None else negative
|
||||||
|
|
||||||
|
# Prompt scaffold de logo de texto: el texto entrecomillado (para que el modelo lo
|
||||||
|
# trate como cadena literal) con el tratamiento de lettering, centrado, fondo plano,
|
||||||
|
# recortable. El estilo es lo que el modelo reproduce con fidelidad; el texto, no.
|
||||||
|
positive = (
|
||||||
|
f'the word "{text.strip()}" as a game logo, {letter_style} lettering, '
|
||||||
|
"stylized typography, centered, plain background, high detail"
|
||||||
|
)
|
||||||
|
|
||||||
|
wf = comfyui_build_txt2img_workflow(
|
||||||
|
checkpoint,
|
||||||
|
positive,
|
||||||
|
neg,
|
||||||
|
steps=steps,
|
||||||
|
cfg=cfg,
|
||||||
|
width=width,
|
||||||
|
height=height,
|
||||||
|
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_title_lettering_workflow(
|
||||||
|
"DRAGON",
|
||||||
|
letter_style="fire engraved",
|
||||||
|
checkpoint="dreamshaper_8.safetensors",
|
||||||
|
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,142 @@
|
|||||||
|
"""Tests offline (sin red, sin GPU) de comfyui_build_title_lettering_workflow.
|
||||||
|
|
||||||
|
Verifican que el dict en API format se construye correctamente: clases presentes,
|
||||||
|
cableado del Rembg, prompt scaffold de logo de texto (text entrecomillado +
|
||||||
|
letter_style), formato apaisado por defecto, y reflejo de los argumentos
|
||||||
|
(text, letter_style, width/height, 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_title_lettering_workflow import ( # noqa: E402
|
||||||
|
comfyui_build_title_lettering_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 _negative_prompt(wf):
|
||||||
|
ks = next(n for n in wf.values() if n["class_type"] == "KSampler")
|
||||||
|
neg_id = ks["inputs"]["negative"][0]
|
||||||
|
return wf[neg_id]["inputs"]["text"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_golden_transparent():
|
||||||
|
"""Caso feliz: logo transparente -> Rembg cableado, prompt de lettering, clases base."""
|
||||||
|
wf = comfyui_build_title_lettering_workflow(
|
||||||
|
"DRAGON", letter_style="fire engraved", 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)
|
||||||
|
# El texto va ENTRECOMILLADO para que el modelo lo trate como cadena literal.
|
||||||
|
assert '"DRAGON"' in prompt
|
||||||
|
assert "game logo" in prompt
|
||||||
|
assert "fire engraved" in prompt
|
||||||
|
assert "lettering" in prompt
|
||||||
|
assert "stylized typography" 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_title_lettering_workflow("VOID", 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_dims_reflected_landscape_default():
|
||||||
|
"""width/height se reflejan; default es apaisado 1024x512 (logo en una linea)."""
|
||||||
|
wf = comfyui_build_title_lettering_workflow("QUEST", width=768, height=384)
|
||||||
|
latent = next(n for n in wf.values() if n["class_type"] == "EmptyLatentImage")
|
||||||
|
assert latent["inputs"]["width"] == 768
|
||||||
|
assert latent["inputs"]["height"] == 384
|
||||||
|
|
||||||
|
wf_default = comfyui_build_title_lettering_workflow("QUEST")
|
||||||
|
latent_d = next(n for n in wf_default.values() if n["class_type"] == "EmptyLatentImage")
|
||||||
|
assert latent_d["inputs"]["width"] == 1024
|
||||||
|
assert latent_d["inputs"]["height"] == 512
|
||||||
|
# Apaisado por defecto: el logo es mas ancho que alto.
|
||||||
|
assert latent_d["inputs"]["width"] > latent_d["inputs"]["height"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_edge_letter_style_reflected():
|
||||||
|
"""letter_style se inserta en el prompt positivo."""
|
||||||
|
wf = comfyui_build_title_lettering_workflow("NEON", letter_style="neon sci-fi glow")
|
||||||
|
assert "neon sci-fi glow" in _positive_prompt(wf)
|
||||||
|
|
||||||
|
|
||||||
|
def test_edge_text_reflected_quoted():
|
||||||
|
"""text se inserta literal y entrecomillado en el prompt positivo."""
|
||||||
|
wf = comfyui_build_title_lettering_workflow("Mystic Realms")
|
||||||
|
assert '"Mystic Realms"' in _positive_prompt(wf)
|
||||||
|
|
||||||
|
|
||||||
|
def test_edge_negative_allows_text():
|
||||||
|
"""El negativo por defecto NO rechaza texto como sujeto (aqui el texto ES el sujeto).
|
||||||
|
|
||||||
|
Los hermanos (ui_hud, status_effect_icon) llevan tokens 'text'/'letters'/'words'
|
||||||
|
en su negativo para limpiar la silueta; este builder NO, porque el lettering es el
|
||||||
|
sujeto. Tokeniza el negativo y comprueba que esos tokens sueltos no estan (si que
|
||||||
|
se permiten frases como 'extra letters'/'jumbled text' que rechazan el ruido).
|
||||||
|
"""
|
||||||
|
neg = _negative_prompt(comfyui_build_title_lettering_workflow("LOGO"))
|
||||||
|
tokens = {t.strip() for t in neg.split(",")}
|
||||||
|
assert "text" not in tokens
|
||||||
|
assert "letters" not in tokens
|
||||||
|
assert "words" not in tokens
|
||||||
|
|
||||||
|
|
||||||
|
def test_edge_lora_injected():
|
||||||
|
"""lora -> LoraLoader presente con la fuerza dada."""
|
||||||
|
wf = comfyui_build_title_lettering_workflow(
|
||||||
|
"EMPIRE", lora="logo_style_sd15.safetensors", lora_strength=0.7
|
||||||
|
)
|
||||||
|
loras = [n for n in wf.values() if n["class_type"] == "LoraLoader"]
|
||||||
|
assert len(loras) == 1
|
||||||
|
assert loras[0]["inputs"]["lora_name"] == "logo_style_sd15.safetensors"
|
||||||
|
assert loras[0]["inputs"]["strength_model"] == pytest.approx(0.7)
|
||||||
|
|
||||||
|
|
||||||
|
def test_error_empty_text():
|
||||||
|
"""text vacio -> ValueError."""
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
comfyui_build_title_lettering_workflow(" ")
|
||||||
|
|
||||||
|
|
||||||
|
def test_determinism():
|
||||||
|
"""Mismos argumentos -> mismo dict (funcion pura)."""
|
||||||
|
a = comfyui_build_title_lettering_workflow("ARENA", seed=7)
|
||||||
|
b = comfyui_build_title_lettering_workflow("ARENA", seed=7)
|
||||||
|
assert a == b
|
||||||
Reference in New Issue
Block a user