feat(gamedev): comfyui_build_asset_variant_workflow — variantes img2img de un asset existente
Primer builder gamedev-2d de transformacion (img2img) en vez de generacion (txt2img): parte de un asset ya generado y produce una variante coherente (ice/fire/damaged/golden tier) cambiando material/paleta/estado y conservando silueta, pose y composicion via denoise medio (~0.5). Compone comfyui_build_img2img_workflow + comfyui_inject_lora + ImageScale opcional. Probado e2e en GPU SD1.5: variante ice del goblin del demo pack (prompt_id 5e4a5d3d) — silueta conservada (luminance corr 0.63) + paleta a frio (blueness B-R -1.6 -> +1.9). Subseccion nueva en docs/capabilities y report 0181.
This commit is contained in:
@@ -65,6 +65,19 @@ VFX (ver `reports/0143`).
|
|||||||
| `comfyui_build_rune_glyph_workflow_py_ml` | `(glyph, *, glow=True, style="arcane glowing rune", checkpoint="dreamshaper_8…", size=512, seed=0, lora=None, …) -> dict` | UNA **runa / glifo / sigilo mágico** (glifos rúnicos, círculos mágicos, sigilos de invocación, inscripciones brillantes) para hechizos, portales, marcas de conjuro y efectos de magia: símbolo arcano **aislado** sobre fondo uniforme (`{glyph}, {style}, magic symbol, single isolated glyph, centered, glowing on a solid pure black background, occult sigil, arcane inscription, no scenery, game asset…`) → txt2img cuadrado + LoRA estilo opcional. **`glow` elige el camino a alpha**: `glow=True` (defecto) = runa BRILLANTE sobre **NEGRO puro**, **sin Rembg** (recortaría el halo del resplandor), insumo de **`comfyui_matting_luma_to_alpha`** (luma=alpha, **blend aditivo** en el motor — conserva el glow); `glow=False` = runa MATE/grabada sobre fondo plano (el negativo rechaza `glow/neon/bloom`), recorte/inversión por el caller. El negativo rechaza `realistic text/readable words/latin alphabet` (un glifo arcano, **no letras reales**) + fondo texturizado/niebla. **DISTINTO de `status_effect_icon`** (símbolo SÓLIDO de UI, recorte Rembg, legible a 16-32 px en el HUD): la runa es una marca translúcida que **emite luz** e se inscribe en el mundo. Grimorio coherente = mismo `style`/`checkpoint`/`lora`, varía `glyph`/`seed`. ⚠️ luma Rec601 penaliza el rojo → para runas rojas (sigilo demoníaco) pasar `luma_weights` con más peso al rojo + subir `gamma`; runas blancas/azules/doradas van con pesos por defecto. Probado e2e en GPU con SD1.5 — `circular summoning rune` glow seed 11 512×512, círculo de invocación brillante sobre **negro puro** (esquinas luma 0.00, dark 83%, runa 3.4% brillante, max 255) apto luma→alpha (`prompt_id 701d149a`, `reports/0172`). SD1.5. |
|
| `comfyui_build_rune_glyph_workflow_py_ml` | `(glyph, *, glow=True, style="arcane glowing rune", checkpoint="dreamshaper_8…", size=512, seed=0, lora=None, …) -> dict` | UNA **runa / glifo / sigilo mágico** (glifos rúnicos, círculos mágicos, sigilos de invocación, inscripciones brillantes) para hechizos, portales, marcas de conjuro y efectos de magia: símbolo arcano **aislado** sobre fondo uniforme (`{glyph}, {style}, magic symbol, single isolated glyph, centered, glowing on a solid pure black background, occult sigil, arcane inscription, no scenery, game asset…`) → txt2img cuadrado + LoRA estilo opcional. **`glow` elige el camino a alpha**: `glow=True` (defecto) = runa BRILLANTE sobre **NEGRO puro**, **sin Rembg** (recortaría el halo del resplandor), insumo de **`comfyui_matting_luma_to_alpha`** (luma=alpha, **blend aditivo** en el motor — conserva el glow); `glow=False` = runa MATE/grabada sobre fondo plano (el negativo rechaza `glow/neon/bloom`), recorte/inversión por el caller. El negativo rechaza `realistic text/readable words/latin alphabet` (un glifo arcano, **no letras reales**) + fondo texturizado/niebla. **DISTINTO de `status_effect_icon`** (símbolo SÓLIDO de UI, recorte Rembg, legible a 16-32 px en el HUD): la runa es una marca translúcida que **emite luz** e se inscribe en el mundo. Grimorio coherente = mismo `style`/`checkpoint`/`lora`, varía `glyph`/`seed`. ⚠️ luma Rec601 penaliza el rojo → para runas rojas (sigilo demoníaco) pasar `luma_weights` con más peso al rojo + subir `gamma`; runas blancas/azules/doradas van con pesos por defecto. Probado e2e en GPU con SD1.5 — `circular summoning rune` glow seed 11 512×512, círculo de invocación brillante sobre **negro puro** (esquinas luma 0.00, dark 83%, runa 3.4% brillante, max 255) apto luma→alpha (`prompt_id 701d149a`, `reports/0172`). 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. |
|
| `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. |
|
||||||
|
|
||||||
|
## Builders de transformación (`gamedev-2d`, puros — img2img sobre un asset existente)
|
||||||
|
|
||||||
|
A diferencia de los builders de **generación** de arriba (parten de TEXTO, txt2img desde
|
||||||
|
ruido), estos parten de una **IMAGEN que ya existe** y la transforman. El KSampler arranca
|
||||||
|
del latente de la imagen base (LoadImage → VAEEncode), no de ruido, así que con `denoise`
|
||||||
|
medio conserva la estructura del original mientras el prompt reescribe lo pedido. Cubren el
|
||||||
|
eje que el critic de generación (`reports/0178`) no exploró: derivar de un asset, no inventar
|
||||||
|
un tipo nuevo.
|
||||||
|
|
||||||
|
| ID | Firma corta | Qué hace |
|
||||||
|
|---|---|---|
|
||||||
|
| `comfyui_build_asset_variant_workflow_py_ml` | `(input_image, variant, *, checkpoint="dreamshaper_8…", denoise=0.5, style="game asset", size=512, seed=0, lora=None, …) -> dict` | UNA **variante coherente de un asset 2D ya generado** (img2img): parte del sprite/icono que existe en `input_image` y produce su versión de **otro material/paleta/tier/estado** (`ice element`, `fire element`, `battle-damaged`, `golden tier 2`, `corrupted`) manteniendo **silueta, pose y composición** del original. Compone `comfyui_build_img2img_workflow` (LoadImage → VAEEncode → KSampler con `denoise`) + `comfyui_inject_lora` (estilo opcional) + `ImageScale` opcional (`size` normaliza la base a size×size; `size=None` preserva las dimensiones exactas sin deformar). El prompt es `{variant}, {style}, same composition, same pose, same silhouette, …`. **`denoise` es la palanca**: ~0.3 invisible, **0.45-0.6 recomendado** (cambia material/paleta, conserva forma), ~0.8 deriva la pose y se acerca a txt2img. Set de variantes del MISMO asset = mismo `input_image`/`style`/`seed`, varía solo `variant`. **DISTINTO de los builders txt2img** (`enemy_creature`, `item_icon`…): esos generan un tipo desde cero; éste transforma uno concreto. **NO inyecta Rembg** (img2img preserva el fondo/alpha del original según la base). ⚠️ la imagen base debe existir en `input/` del server (subir con `POST /upload/image`); pura, no valida (usar `comfyui_validate_workflow` antes de enviar); asset NO cuadrado + `size` fijo + `crop="disabled"` deforma → `size=None` o `crop="center"`. Probado e2e en GPU con SD1.5 — variante `ice element, frozen` del goblin `enemy_creature_00001_.png` denoise 0.5 seed 7 512×512 (`prompt_id 5e4a5d3d`): silueta conservada (luminance corr 0.63) + paleta a frío (blueness B−R −1.6→+1.9), `reports/0181`. SD1.5. |
|
||||||
|
|
||||||
## Funciones de post-proceso y puente (`gamedev-2d`, CPU)
|
## Funciones de post-proceso y puente (`gamedev-2d`, CPU)
|
||||||
|
|
||||||
| ID | Firma corta | Qué hace |
|
| ID | Firma corta | Qué hace |
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
---
|
||||||
|
name: comfyui_build_asset_variant_workflow
|
||||||
|
kind: function
|
||||||
|
lang: py
|
||||||
|
domain: ml
|
||||||
|
purity: pure
|
||||||
|
version: 1.0.0
|
||||||
|
signature: "def comfyui_build_asset_variant_workflow(input_image: str, variant: str, *, checkpoint: str = \"dreamshaper_8.safetensors\", denoise: float = 0.5, style: str = \"game asset\", size: int | None = 512, seed: int = 0, lora: str | None = None, lora_strength: float = 1.0, upscale_method: str = \"lanczos\", crop: str = \"disabled\", negative: str | None = None, steps: int = 28, cfg: float = 7.0, sampler_name: str = \"dpmpp_2m\", scheduler: str = \"karras\", filename_prefix: str = \"asset_variant\") -> dict"
|
||||||
|
description: "Construye el dict (API format) del workflow de una VARIANTE img2img de un asset 2D ya generado: parte de una IMAGEN existente (un sprite de enemigo, un icono...) y produce una version coherente que cambia material/paleta/tier/estado (ice element, fire element, battle-damaged, golden tier 2, corrupted) manteniendo la composicion, la pose y la silueta del original. A diferencia de los builders gamedev hermanos (enemy_creature, item_icon...), que parten de TEXTO (txt2img desde ruido), este parte de una imagen via img2img con denoise MEDIO (~0.45-0.6): el KSampler arranca del latente de la imagen base, no de ruido. Normaliza el tamano con un ImageScale opcional (size) o preserva las dimensiones del original (size=None). Compone comfyui_build_img2img_workflow + comfyui_inject_lora (estilo opcional). Pura, sin red ni I/O. class_types verificados contra /object_info (8GB lowvram)."
|
||||||
|
tags: [comfyui, ml, gamedev-2d, img2img, variant, asset-transform, stable-diffusion, workflow]
|
||||||
|
uses_functions: [comfyui_build_img2img_workflow_py_ml, comfyui_inject_lora_py_ml]
|
||||||
|
uses_types: []
|
||||||
|
returns: []
|
||||||
|
returns_optional: false
|
||||||
|
error_type: ""
|
||||||
|
params:
|
||||||
|
- name: input_image
|
||||||
|
desc: "Nombre del archivo de la imagen base dentro de la carpeta input/ del servidor ComfyUI (un asset YA generado). Lo carga el nodo LoadImage. Subelo antes con POST /upload/image o copialo a ~/ComfyUI/input/. No puede estar vacio."
|
||||||
|
- name: variant
|
||||||
|
desc: "Descripcion de la variante a producir (ej. 'ice element, frozen', 'fire element, molten', 'battle-damaged, cracked', 'golden tier 2', 'corrupted shadow'). Reescribe material/paleta/estado del asset manteniendo su composicion. No describe el sujeto desde cero: transforma el que ya existe en input_image. No puede estar vacio."
|
||||||
|
- name: checkpoint
|
||||||
|
desc: "Checkpoint del servidor. 'dreamshaper_8.safetensors' (SD1.5, holgado en 8GB lowvram) por defecto. keyword-only."
|
||||||
|
- name: denoise
|
||||||
|
desc: "Fuerza de denoising del KSampler (cuanto se aparta del original). ~0.3 apenas cambia; 0.45-0.6 (recomendado) cambia material/paleta conservando silueta/pose; ~0.8 se aleja y empieza a ser casi txt2img. Se clampa a [0.0, 1.0]. keyword-only."
|
||||||
|
- name: style
|
||||||
|
desc: "Descriptor de estilo que mantiene coherentes las variantes de un set (ej. 'game asset', 'dark fantasy creature', 'pixel art'). Mismo style + checkpoint + (lora) en todas las variantes del mismo asset. keyword-only."
|
||||||
|
- name: size
|
||||||
|
desc: "Lado en px al que se NORMALIZA la imagen base antes de encodearla (inserta un ImageScale a size x size). None = no escala; la variante hereda las dimensiones EXACTAS del original (preserva proporcion sin deformar). 512 por defecto (SD1.5). keyword-only."
|
||||||
|
- name: seed
|
||||||
|
desc: "Semilla del KSampler. keyword-only."
|
||||||
|
- name: lora
|
||||||
|
desc: "LoRA de estilo opcional en models/loras (ej. 'dark_fantasy_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: upscale_method
|
||||||
|
desc: "Metodo del ImageScale ('lanczos', 'bilinear', 'bicubic', 'area', 'nearest-exact'). Solo se usa si size no es None. keyword-only."
|
||||||
|
- name: crop
|
||||||
|
desc: "Modo de recorte del ImageScale ('disabled' conserva todo el contenido, 'center' recorta al centro para encajar el ratio). Solo si size no es None. keyword-only."
|
||||||
|
- name: negative
|
||||||
|
desc: "Prompt negativo. None usa el negativo por defecto pensado para variantes (conservar pose/composicion, una figura, fondo limpio). keyword-only."
|
||||||
|
- name: steps
|
||||||
|
desc: "Pasos de sampling del KSampler. keyword-only."
|
||||||
|
- name: cfg
|
||||||
|
desc: "Classifier-free guidance scale. keyword-only."
|
||||||
|
- name: sampler_name
|
||||||
|
desc: "Nombre del sampler (ej. 'dpmpp_2m', 'euler'). keyword-only."
|
||||||
|
- name: scheduler
|
||||||
|
desc: "Scheduler del sampler (ej. 'karras', 'normal'). keyword-only."
|
||||||
|
- name: filename_prefix
|
||||||
|
desc: "Prefijo del archivo de salida en SaveImage. keyword-only."
|
||||||
|
output: "dict en API format listo para comfyui_submit_workflow: img2img base (parte de input_image) con prompt de variante + ImageScale opcional (normaliza a size) + LoRA opcional. Nodos: CheckpointLoaderSimple '4', LoadImage '10', VAEEncode '11', CLIPTextEncode '6'/'7', KSampler '3' (denoise medio), VAEDecode '8', SaveImage '9', + ImageScale y LoraLoader si aplican."
|
||||||
|
tested: false
|
||||||
|
file_path: python/functions/ml/comfyui_build_asset_variant_workflow.py
|
||||||
|
---
|
||||||
|
|
||||||
|
Construye el dict (API format) del workflow de una **variante de un asset 2D que ya
|
||||||
|
existe** (img2img). Builder gamedev hermano de `comfyui_build_enemy_creature_workflow`
|
||||||
|
e `comfyui_build_item_icon_workflow`, pero con un eje distinto: en vez de generar un
|
||||||
|
TIPO de asset desde texto, **transforma** una imagen concreta (un sprite ya generado)
|
||||||
|
en una variante coherente — la version "de hielo", "de fuego", "dañada" o "tier 2
|
||||||
|
dorada" — conservando silueta, pose y composición del original.
|
||||||
|
|
||||||
|
## Ejemplo
|
||||||
|
|
||||||
|
```python
|
||||||
|
import sys, os
|
||||||
|
sys.path.insert(0, os.path.join(os.environ["HOME"], "fn_registry", "python", "functions"))
|
||||||
|
from ml.comfyui_build_asset_variant_workflow import comfyui_build_asset_variant_workflow
|
||||||
|
|
||||||
|
# Variante "de hielo" de un sprite de goblin ya generado (subido al input/ del server)
|
||||||
|
wf = comfyui_build_asset_variant_workflow(
|
||||||
|
"enemy_creature_00001_.png", # asset existente en el input/ de ComfyUI
|
||||||
|
"ice element, frozen", # la variante a producir
|
||||||
|
style="dark fantasy creature, game asset",
|
||||||
|
denoise=0.5, # medio: cambia material/paleta, conserva silueta
|
||||||
|
seed=7,
|
||||||
|
)
|
||||||
|
# wf parte de una imagen (img2img), NO de ruido:
|
||||||
|
# "VAEEncode" in {n["class_type"] for n in wf.values()} # True
|
||||||
|
# "EmptyLatentImage" not in {n["class_type"] for n in wf.values()} # True (no es txt2img)
|
||||||
|
# wf["10"]["inputs"]["image"] == "enemy_creature_00001_.png"
|
||||||
|
# wf["3"]["inputs"]["denoise"] == 0.5
|
||||||
|
# "ice element, frozen" in wf["6"]["inputs"]["text"]
|
||||||
|
```
|
||||||
|
|
||||||
|
El bloque se lanza con el python del venv (`python/.venv/bin/python3`). Nota: `./fn
|
||||||
|
run` directo no aplica a este builder porque su firma usa `*` (keyword-only) y el
|
||||||
|
generador de runner de `fn run` no lo soporta — igual que `comfyui_build_img2img_workflow`.
|
||||||
|
Usa el import de arriba o un heredoc.
|
||||||
|
|
||||||
|
Set de variantes del MISMO asset (mismo `input_image`/`style`/`seed`, distinto `variant`):
|
||||||
|
|
||||||
|
```python
|
||||||
|
for v in ["ice element, frozen", "fire element, molten", "battle-damaged, cracked", "golden tier 2"]:
|
||||||
|
wf = comfyui_build_asset_variant_workflow("enemy_creature_00001_.png", v,
|
||||||
|
style="dark fantasy creature, game asset",
|
||||||
|
denoise=0.5, seed=7)
|
||||||
|
# enviar con comfyui_submit_workflow -> familia coherente de variantes
|
||||||
|
```
|
||||||
|
|
||||||
|
Para enviar a la GPU: subir la base con `POST /upload/image`, luego
|
||||||
|
`comfyui_submit_workflow(wf)` + `comfyui_wait_result(prompt_id)` +
|
||||||
|
`comfyui_fetch_output_image(filename)`.
|
||||||
|
|
||||||
|
## Cuando usarla
|
||||||
|
|
||||||
|
Cuando ya tienes un asset 2D generado y quieres **derivar variantes coherentes** de
|
||||||
|
él (elemento/material/tier/estado) sin redibujar desde cero: el sprite de hielo del
|
||||||
|
mismo enemigo, la armadura dorada del mismo personaje, la versión dañada del mismo
|
||||||
|
prop. Es img2img con denoise medio que conserva la composición original. Para generar
|
||||||
|
un asset NUEVO desde texto usa los builders txt2img hermanos
|
||||||
|
(`comfyui_build_enemy_creature_workflow`, `comfyui_build_item_icon_workflow`...); para
|
||||||
|
ampliar/refinar resolución usa `comfyui_build_upscale_workflow`; para img2img genérico
|
||||||
|
sin scaffolding de variante usa `comfyui_build_img2img_workflow` directo.
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- Es **img2img**, no txt2img: SIEMPRE parte de una imagen (`input_image`), no de ruido
|
||||||
|
en blanco. Esa imagen debe existir en la carpeta `input/` del servidor ComfyUI
|
||||||
|
(subir con `POST /upload/image` o copiar a `~/ComfyUI/input/`). Es pura: NO valida
|
||||||
|
que exista; si no está, ComfyUI rechaza el workflow con HTTP 400 al enviarlo. Valida
|
||||||
|
antes con `comfyui_validate_workflow`.
|
||||||
|
- `denoise` es la palanca clave: cerca de 0.0 apenas cambia (variante invisible);
|
||||||
|
0.45-0.6 es el rango útil (cambia material/paleta manteniendo silueta); cerca de 0.8
|
||||||
|
se aleja del original y deriva la pose/composición (deja de ser variante coherente y
|
||||||
|
se acerca a un txt2img). Default 0.5.
|
||||||
|
- `size` reescala la imagen base a `size x size` con un ImageScale ANTES de encodear.
|
||||||
|
Con `size=512` y un asset cuadrado 512 es no-op de tamaño; con un asset NO cuadrado y
|
||||||
|
`crop="disabled"` el ImageScale fuerza el ratio cuadrado y puede deformar — pasa
|
||||||
|
`size=None` para preservar las dimensiones/proporción exactas del original, o
|
||||||
|
`crop="center"` para recortar al centro en vez de deformar.
|
||||||
|
- El prompt refuerza "same composition, same pose, same silhouette" además del denoise
|
||||||
|
medio; aun así, denoise alto o un `variant` que implique cambio de forma (ej. "giant
|
||||||
|
version") puede alterar la silueta. Para variantes solo de paleta/material, mantén
|
||||||
|
denoise ≤0.55.
|
||||||
|
- Asume checkpoint con VAE embebido (VAEEncode/VAEDecode usan el VAE del checkpoint).
|
||||||
|
Para un VAE externo hay que reconectar esas entradas a mano.
|
||||||
|
- 8GB lowvram: SD1.5 a 512² va holgado. Si OOM, baja `size` (384) o `denoise`; NO subas
|
||||||
|
a SDXL en 8GB para esto.
|
||||||
@@ -0,0 +1,262 @@
|
|||||||
|
"""Construye el workflow ComfyUI de una VARIANTE de un asset ya generado (img2img).
|
||||||
|
|
||||||
|
A diferencia de los builders gamedev hermanos (enemy_creature, item_icon,
|
||||||
|
ui_hud...), que parten de TEXTO (txt2img desde ruido), este builder parte de una
|
||||||
|
IMAGEN que ya existe y produce una variante COHERENTE: cambia paleta, material,
|
||||||
|
tier o estado del asset manteniendo la composicion, la pose y la silueta del
|
||||||
|
original. Es el caso real de gamedev: tienes el sprite de un enemigo y quieres su
|
||||||
|
version "de hielo", "de fuego", "danada en combate" o "tier 2 dorada" sin redibujar
|
||||||
|
desde cero.
|
||||||
|
|
||||||
|
El mecanismo es img2img con denoise MEDIO: el KSampler parte del latente de la
|
||||||
|
imagen base (LoadImage -> [ImageScale opcional] -> VAEEncode), no de ruido, asi que
|
||||||
|
con denoise ~0.45-0.6 conserva la estructura global (silueta/pose) mientras el
|
||||||
|
prompt de la variante reescribe material y color. Denoise bajo (~0.3) apenas cambia;
|
||||||
|
alto (~0.8) se aleja del original y empieza a ser casi txt2img.
|
||||||
|
|
||||||
|
Cableado:
|
||||||
|
|
||||||
|
CheckpointLoaderSimple -> [LoraLoader opcional de estilo] -> KSampler
|
||||||
|
LoadImage -> [ImageScale opcional a size x size] -> VAEEncode -> KSampler.latent
|
||||||
|
CLIPTextEncode (prompt de variante + "same composition/pose/silhouette")
|
||||||
|
KSampler (denoise medio) -> VAEDecode -> SaveImage
|
||||||
|
|
||||||
|
Compone:
|
||||||
|
- comfyui_build_img2img_workflow -> base img2img (LoadImage/VAEEncode/KSampler con denoise)
|
||||||
|
- comfyui_inject_lora -> LoRA de estilo opcional (consistencia con el set)
|
||||||
|
|
||||||
|
Por que ImageScale opcional y no EmptyLatentImage: en img2img el tamano de salida lo
|
||||||
|
fija la imagen base (no hay EmptyLatentImage). Para poder NORMALIZAR todos los assets
|
||||||
|
del set a una resolucion comun (`size`), se inserta un ImageScale entre LoadImage y
|
||||||
|
VAEEncode que reescala la base antes de encodear. Si size=None, no se escala y la
|
||||||
|
variante hereda las dimensiones exactas del original (preserva proporcion sin
|
||||||
|
deformar). Es la diferencia clave con un txt2img: aqui SIEMPRE hay una imagen de
|
||||||
|
entrada de la que se parte; el prompt no genera en blanco, transforma.
|
||||||
|
|
||||||
|
Por que el prompt empuja "same composition, same pose, same silhouette": el denoise
|
||||||
|
medio ya conserva la estructura, pero reforzarlo en el texto reduce la deriva de
|
||||||
|
pose/encuadre y mantiene la variante alineada con el original (lo que se quiere para
|
||||||
|
un set coherente: misma figura, distinto material/tier).
|
||||||
|
|
||||||
|
class_types/inputs verificados contra /object_info del servidor (8GB lowvram):
|
||||||
|
CheckpointLoaderSimple, LoadImage, ImageScale, VAEEncode, CLIPTextEncode, KSampler,
|
||||||
|
VAEDecode, SaveImage, LoraLoader.
|
||||||
|
|
||||||
|
Funcion pura: sin red, sin I/O. No muta dicts de entrada (copia profunda al insertar
|
||||||
|
ImageScale). NO valida que input_image/checkpoint/lora existan en el servidor (eso
|
||||||
|
es responsabilidad de comfyui_validate_workflow antes de enviar). 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 variantes de asset: conservar UNA figura entera,
|
||||||
|
# bien formada, fondo limpio, SIN cambiar la composicion/pose y sin texto/marcas ni
|
||||||
|
# objetos extra. No filtra material ni paleta (ice/fire/golden/damaged son validos).
|
||||||
|
_VARIANT_NEGATIVE = (
|
||||||
|
"blurry, lowres, deformed, disfigured, bad anatomy, extra limbs, "
|
||||||
|
"different pose, different composition, different framing, extra objects, "
|
||||||
|
"duplicate, multiple subjects, text, watermark, signature, logo, "
|
||||||
|
"cropped, cut off, out of frame, jpeg artifacts"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _inject_image_scale(
|
||||||
|
workflow: dict, *, size: int, upscale_method: str, crop: str
|
||||||
|
) -> dict:
|
||||||
|
"""Inserta un nodo ImageScale entre LoadImage y VAEEncode para normalizar el tamano.
|
||||||
|
|
||||||
|
Reescala la imagen base a size x size ANTES de encodearla al latente, de modo que
|
||||||
|
la variante salga a la resolucion deseada en lugar de heredar la del original.
|
||||||
|
Repunta VAEEncode.pixels a la salida del ImageScale. Pura: trabaja sobre copia.
|
||||||
|
"""
|
||||||
|
wf = copy.deepcopy(workflow)
|
||||||
|
load_id = next(
|
||||||
|
(nid for nid, n in wf.items() if n.get("class_type") == "LoadImage"), None
|
||||||
|
)
|
||||||
|
vaeencode_id = next(
|
||||||
|
(nid for nid, n in wf.items() if n.get("class_type") == "VAEEncode"), None
|
||||||
|
)
|
||||||
|
if load_id is None or vaeencode_id is None:
|
||||||
|
raise ValueError(
|
||||||
|
"comfyui_build_asset_variant_workflow: no se encontro LoadImage/VAEEncode "
|
||||||
|
"para insertar ImageScale"
|
||||||
|
)
|
||||||
|
numeric = [int(k) for k in wf.keys() if str(k).isdigit()]
|
||||||
|
scale_id = str((max(numeric) + 1) if numeric else len(wf) + 1)
|
||||||
|
# La fuente de pixeles que hoy alimenta el VAEEncode (normalmente LoadImage[0]).
|
||||||
|
src = wf[vaeencode_id]["inputs"].get("pixels", [load_id, 0])
|
||||||
|
wf[scale_id] = {
|
||||||
|
"class_type": "ImageScale",
|
||||||
|
"inputs": {
|
||||||
|
"image": list(src),
|
||||||
|
"upscale_method": upscale_method,
|
||||||
|
"width": int(size),
|
||||||
|
"height": int(size),
|
||||||
|
"crop": crop,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
wf[vaeencode_id]["inputs"]["pixels"] = [scale_id, 0]
|
||||||
|
return wf
|
||||||
|
|
||||||
|
|
||||||
|
def comfyui_build_asset_variant_workflow(
|
||||||
|
input_image: str,
|
||||||
|
variant: str,
|
||||||
|
*,
|
||||||
|
checkpoint: str = "dreamshaper_8.safetensors",
|
||||||
|
denoise: float = 0.5,
|
||||||
|
style: str = "game asset",
|
||||||
|
size: int | None = 512,
|
||||||
|
seed: int = 0,
|
||||||
|
lora: str | None = None,
|
||||||
|
lora_strength: float = 1.0,
|
||||||
|
upscale_method: str = "lanczos",
|
||||||
|
crop: str = "disabled",
|
||||||
|
negative: str | None = None,
|
||||||
|
steps: int = 28,
|
||||||
|
cfg: float = 7.0,
|
||||||
|
sampler_name: str = "dpmpp_2m",
|
||||||
|
scheduler: str = "karras",
|
||||||
|
filename_prefix: str = "asset_variant",
|
||||||
|
) -> dict:
|
||||||
|
"""Construye el dict (API format) de una variante img2img de un asset existente.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_image: nombre del archivo de la imagen base dentro de la carpeta
|
||||||
|
input/ del servidor ComfyUI (un asset YA generado: un sprite de enemigo,
|
||||||
|
un icono de objeto...). Lo carga el nodo LoadImage. Subelo antes con
|
||||||
|
POST /upload/image o copialo a ~/ComfyUI/input/. No puede estar vacio.
|
||||||
|
variant: descripcion de la variante a producir (ej. "ice element, frozen",
|
||||||
|
"fire element, molten", "battle-damaged, cracked", "golden tier 2",
|
||||||
|
"corrupted shadow"). Es lo que reescribe material/paleta/estado del asset
|
||||||
|
manteniendo su composicion. No puede estar vacio. Es lo que diferencia
|
||||||
|
este builder de un txt2img: NO describe el sujeto desde cero, transforma
|
||||||
|
uno que ya existe en la imagen base.
|
||||||
|
checkpoint: checkpoint del servidor. 'dreamshaper_8.safetensors' (SD1.5,
|
||||||
|
holgado en 8GB lowvram) por defecto. keyword-only.
|
||||||
|
denoise: fuerza de denoising del KSampler (cuanto se aparta del original).
|
||||||
|
~0.3 apenas cambia; 0.45-0.6 (recomendado) cambia material/paleta
|
||||||
|
conservando silueta/pose; ~0.8 se aleja y empieza a ser casi txt2img. Se
|
||||||
|
clampa a [0.0, 1.0]. keyword-only.
|
||||||
|
style: descriptor de estilo que mantiene coherentes las variantes de un set
|
||||||
|
(ej. "game asset", "dark fantasy creature", "pixel art"). Pasa el MISMO
|
||||||
|
style + checkpoint + (lora) a todas las variantes del mismo asset.
|
||||||
|
keyword-only.
|
||||||
|
size: lado en px al que se NORMALIZA la imagen base antes de encodearla
|
||||||
|
(inserta un ImageScale a size x size). None = no escala, la variante
|
||||||
|
hereda las dimensiones EXACTAS del original (preserva proporcion sin
|
||||||
|
deformar). 512 por defecto (SD1.5). keyword-only.
|
||||||
|
seed: semilla del KSampler. keyword-only.
|
||||||
|
lora: LoRA de estilo opcional en models/loras (ej.
|
||||||
|
'dark_fantasy_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.
|
||||||
|
upscale_method: metodo del ImageScale ('lanczos', 'bilinear', 'bicubic',
|
||||||
|
'area', 'nearest-exact'). Solo se usa si size no es None. keyword-only.
|
||||||
|
crop: modo de recorte del ImageScale ('disabled' conserva todo el contenido,
|
||||||
|
'center' recorta al centro para encajar el ratio). Solo si size no es
|
||||||
|
None. keyword-only.
|
||||||
|
negative: prompt negativo. None usa el negativo por defecto pensado para
|
||||||
|
variantes (conservar pose/composicion, una figura, fondo limpio).
|
||||||
|
keyword-only.
|
||||||
|
steps, cfg, sampler_name, scheduler, filename_prefix: parametros de
|
||||||
|
generacion. keyword-only.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict en API format listo para comfyui_submit_workflow: img2img base (parte de
|
||||||
|
input_image) con prompt de variante ('{variant}, {style}, same composition,
|
||||||
|
same pose, same silhouette, ...') + ImageScale opcional (normaliza a size) +
|
||||||
|
LoRA de estilo opcional. Es UNA variante; un set de variantes del MISMO asset
|
||||||
|
-> llamar por `variant` con el mismo input_image/style/checkpoint/seed.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: si input_image o variant estan vacios, o si la base no tiene
|
||||||
|
LoadImage/VAEEncode donde insertar el ImageScale (propagado por el helper).
|
||||||
|
"""
|
||||||
|
from ml.comfyui_build_img2img_workflow import comfyui_build_img2img_workflow
|
||||||
|
|
||||||
|
if not input_image or not input_image.strip():
|
||||||
|
raise ValueError(
|
||||||
|
"comfyui_build_asset_variant_workflow: 'input_image' no puede estar vacio"
|
||||||
|
)
|
||||||
|
if not variant or not variant.strip():
|
||||||
|
raise ValueError(
|
||||||
|
"comfyui_build_asset_variant_workflow: 'variant' no puede estar vacio"
|
||||||
|
)
|
||||||
|
|
||||||
|
input_image = input_image.strip()
|
||||||
|
variant = variant.strip()
|
||||||
|
denoise = max(0.0, min(1.0, float(denoise)))
|
||||||
|
lora_strength = max(0.0, min(2.0, float(lora_strength)))
|
||||||
|
neg = _VARIANT_NEGATIVE if negative is None else negative
|
||||||
|
|
||||||
|
# Prompt de variante: reescribe material/paleta/estado pero refuerza que la
|
||||||
|
# composicion, pose y silueta del original se conservan (img2img coherente).
|
||||||
|
positive = (
|
||||||
|
f"{variant}, {style}, same composition, same pose, same silhouette, "
|
||||||
|
"consistent design, high detail"
|
||||||
|
)
|
||||||
|
|
||||||
|
wf = comfyui_build_img2img_workflow(
|
||||||
|
checkpoint,
|
||||||
|
input_image,
|
||||||
|
positive,
|
||||||
|
neg,
|
||||||
|
denoise=denoise,
|
||||||
|
steps=steps,
|
||||||
|
cfg=cfg,
|
||||||
|
seed=seed,
|
||||||
|
sampler_name=sampler_name,
|
||||||
|
scheduler=scheduler,
|
||||||
|
)
|
||||||
|
|
||||||
|
# El builder base hardcodea filename_prefix="comfy_img2img"; lo repuntamos.
|
||||||
|
save_id = next(
|
||||||
|
(nid for nid, n in wf.items() if n.get("class_type") == "SaveImage"), None
|
||||||
|
)
|
||||||
|
if save_id is not None:
|
||||||
|
wf[save_id]["inputs"]["filename_prefix"] = filename_prefix
|
||||||
|
|
||||||
|
if size is not None:
|
||||||
|
wf = _inject_image_scale(
|
||||||
|
wf, size=size, upscale_method=upscale_method, crop=crop
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
return wf
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import json
|
||||||
|
|
||||||
|
wf = comfyui_build_asset_variant_workflow(
|
||||||
|
"enemy_creature_00001_.png",
|
||||||
|
"ice element, frozen",
|
||||||
|
style="dark fantasy creature, game asset",
|
||||||
|
denoise=0.5,
|
||||||
|
seed=7,
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"nodes": list(wf),
|
||||||
|
"classes": sorted({n["class_type"] for n in wf.values()}),
|
||||||
|
"denoise": wf["3"]["inputs"]["denoise"],
|
||||||
|
"positive": wf["6"]["inputs"]["text"],
|
||||||
|
"input_image": wf["10"]["inputs"]["image"],
|
||||||
|
},
|
||||||
|
indent=2,
|
||||||
|
)
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user