feat(gamedev): comfyui_build_parallax_background_workflow — fondo en capas para parallax 2.5D
Builder puro (dict API format) del grupo gamedev-2d: genera el fondo apaisado (txt2img) y su mapa de profundidad (DepthAnythingV2Preprocessor sobre el VAEDecode), guardando ambos como PNG. El corte en N bandas por rango de profundidad queda como post-proceso documentado (gap split_parallax_layers). Compone comfyui_build_txt2img_workflow. 8 tests offline verdes; probado e2e en GPU (RTX 3070 8GB lowvram): fondo de bosque + depth map, prompt_id 11763613-33cf-4f63-8405-34f75c1c89be. class_types verificados contra /object_info. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,7 +17,8 @@ Filtro: `mcp__registry__fn_search query="" tag="gamedev-2d"`.
|
|||||||
Documento hermano del grupo `comfyui` (generación genérica de imágenes/video/3D).
|
Documento hermano del grupo `comfyui` (generación genérica de imágenes/video/3D).
|
||||||
Diseño del puente: `docs/comfyui-godot-integration.md`. Planes origen: `reports/0135`
|
Diseño del puente: `docs/comfyui-godot-integration.md`. Planes origen: `reports/0135`
|
||||||
(pixelart), `reports/0139` (entornos/tiles/iso), `reports/0137` (personajes/sprites),
|
(pixelart), `reports/0139` (entornos/tiles/iso), `reports/0137` (personajes/sprites),
|
||||||
`reports/0140` (VFX), `reports/0143` (ronda 2b: builders), `reports/0147` (item icons).
|
`reports/0140` (VFX), `reports/0143` (ronda 2b: builders), `reports/0147` (item icons),
|
||||||
|
`reports/0149` (parallax background).
|
||||||
|
|
||||||
## Builders de workflow 2D (`gamedev-2d`, puros — generación)
|
## Builders de workflow 2D (`gamedev-2d`, puros — generación)
|
||||||
|
|
||||||
@@ -36,6 +37,7 @@ VFX (ver `reports/0143`).
|
|||||||
| `comfyui_build_vfx_spritesheet_workflow_py_ml` | `(prompt, *, motion_model="mm_sd_v15_v2.ckpt", num_frames=16, closed_loop=True, lora=None, …) -> dict` | N frames AnimateDiff loop sobre negro (insumo de luma→alpha). 8GB: 16f@512² revienta, usar ≤8f@512² o bajar resolución. |
|
| `comfyui_build_vfx_spritesheet_workflow_py_ml` | `(prompt, *, motion_model="mm_sd_v15_v2.ckpt", num_frames=16, closed_loop=True, lora=None, …) -> dict` | N frames AnimateDiff loop sobre negro (insumo de luma→alpha). 8GB: 16f@512² revienta, usar ≤8f@512² o bajar resolución. |
|
||||||
| `comfyui_build_item_icon_workflow_py_ml` | `(item, *, style="game icon, clean, centered", checkpoint="dreamshaper_8…", size=512, transparent=True, lora=None, …) -> dict` | UN icono de item de inventario (espada/poción/anillo/libro/escudo): txt2img cuadrado + prompt scaffold de icono + LoRA estilo opcional + Rembg (alpha). Set coherente = mismo style/checkpoint/lora por item. SD1.5. |
|
| `comfyui_build_item_icon_workflow_py_ml` | `(item, *, style="game icon, clean, centered", checkpoint="dreamshaper_8…", size=512, transparent=True, lora=None, …) -> dict` | UN icono de item de inventario (espada/poción/anillo/libro/escudo): txt2img cuadrado + prompt scaffold de icono + LoRA estilo opcional + Rembg (alpha). Set coherente = mismo style/checkpoint/lora por item. SD1.5. |
|
||||||
| `comfyui_build_portrait_avatar_workflow_py_ml` | `(character, *, style="character portrait", ref_face=None, checkpoint="dreamshaper_8…", size=512, facedetailer=True, lora=None, …) -> dict` | UN retrato/avatar de personaje (busto centrado, cara al espectador, fondo simple): txt2img + prompt scaffold de retrato + FaceDetailer (cara nítida) + LoRA estilo opcional; `ref_face` → IPAdapter-FaceID para rostro consistente entre retratos. Diálogo/perfil/selección. SD1.5. |
|
| `comfyui_build_portrait_avatar_workflow_py_ml` | `(character, *, style="character portrait", ref_face=None, checkpoint="dreamshaper_8…", size=512, facedetailer=True, lora=None, …) -> dict` | UN retrato/avatar de personaje (busto centrado, cara al espectador, fondo simple): txt2img + prompt scaffold de retrato + FaceDetailer (cara nítida) + LoRA estilo opcional; `ref_face` → IPAdapter-FaceID para rostro consistente entre retratos. Diálogo/perfil/selección. SD1.5. |
|
||||||
|
| `comfyui_build_parallax_background_workflow_py_ml` | `(scene, *, style="game background, side-scroller…", layers=3, checkpoint="dreamshaper_8…", depth_node="DepthAnythingV2Preprocessor", width=1024, height=512, …) -> dict` | Fondo en capas para parallax 2.5D: genera el fondo apaisado (txt2img) + su depth map (`DepthAnythingV2Preprocessor` sobre el VAEDecode), dos SaveImage. El split en N bandas por profundidad es post (GAP: `split_parallax_layers`, aún no creada). Probado e2e en GPU (`reports/0149`). SD1.5. |
|
||||||
|
|
||||||
## Funciones de post-proceso y puente (`gamedev`, CPU)
|
## Funciones de post-proceso y puente (`gamedev`, CPU)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
---
|
||||||
|
name: comfyui_build_parallax_background_workflow
|
||||||
|
kind: function
|
||||||
|
lang: py
|
||||||
|
domain: ml
|
||||||
|
version: "1.0.0"
|
||||||
|
purity: pure
|
||||||
|
signature: "def comfyui_build_parallax_background_workflow(scene: str, *, style: str = \"game background, side-scroller, parallax layers, detailed, no characters\", layers: int = 3, checkpoint: str = \"dreamshaper_8.safetensors\", negative: str = \"character, person, foreground object, text, watermark, blurry, low quality\", depth_node: str = \"DepthAnythingV2Preprocessor\", depth_model: str = \"depth_anything_v2_vitl.pth\", depth_resolution: int = 512, width: int = 1024, height: int = 512, seed: int = 0, steps: int = 28, cfg: float = 7.0, sampler_name: str = \"dpmpp_2m\", scheduler: str = \"karras\", filename_prefix: str = \"parallax_bg\") -> dict"
|
||||||
|
description: "Construye el dict (API format) de un workflow ComfyUI para un FONDO DE JUEGO EN CAPAS de parallax 2.5D (side-scroller/plataformas): genera un fondo apaisado con txt2img y estima su MAPA DE PROFUNDIDAD con DepthAnythingV2Preprocessor, guardando ambos PNG (fondo + depth). La separacion en N bandas por rango de profundidad es post-proceso (gap: split_parallax_layers). Compone comfyui_build_txt2img_workflow. Pura, sin red ni I/O. class_types verificados contra /object_info."
|
||||||
|
tags: [comfyui, ml, gamedev, gamedev-2d, parallax, background, depth, side-scroller, workflow, stable-diffusion]
|
||||||
|
uses_functions: [comfyui_build_txt2img_workflow_py_ml]
|
||||||
|
uses_types: []
|
||||||
|
returns: []
|
||||||
|
returns_optional: false
|
||||||
|
error_type: ""
|
||||||
|
imports: []
|
||||||
|
params:
|
||||||
|
- name: scene
|
||||||
|
desc: "Descripcion de la escena del fondo (ej. 'forest at dusk, fantasy', 'ruined city skyline'). No puede estar vacio."
|
||||||
|
- name: style
|
||||||
|
desc: "Estilo/encuadre que se concatena a la escena. Por defecto fuerza look de fondo apaisado side-scroller sin personajes. keyword-only."
|
||||||
|
- name: layers
|
||||||
|
desc: "Numero de capas de profundidad a derivar del depth map en post-proceso (cielo/lejano/medio/frente). Debe ser >= 2. NO genera nodos: se refleja en el filename del depth map para trazabilidad. keyword-only."
|
||||||
|
- name: checkpoint
|
||||||
|
desc: "Checkpoint base. Default 'dreamshaper_8.safetensors' (SD1.5 holgado en 8GB). keyword-only."
|
||||||
|
- name: negative
|
||||||
|
desc: "Prompt negativo. Por defecto evita personajes/props en primer plano que rompen un fondo limpio por capas. keyword-only."
|
||||||
|
- name: depth_node
|
||||||
|
desc: "class_type del preprocessor de profundidad. Default 'DepthAnythingV2Preprocessor'. Alternativa con misma firma: 'DepthAnythingPreprocessor'. Para MiDaS/Zoe (sin ckpt_name) el builder omite ckpt_name. keyword-only."
|
||||||
|
- name: depth_model
|
||||||
|
desc: "Peso del depth_node (enum depth_anything_v2_*). Default vitl (buena calidad, cabe en 8GB). Solo aplica a DepthAnything*. keyword-only."
|
||||||
|
- name: depth_resolution
|
||||||
|
desc: "Resolucion interna del preprocessor de profundidad. keyword-only."
|
||||||
|
- name: width
|
||||||
|
desc: "Ancho en px (multiplo de 8). Default 1024 (formato apaisado de parallax). keyword-only."
|
||||||
|
- name: height
|
||||||
|
desc: "Alto en px (multiplo de 8). Default 512. keyword-only."
|
||||||
|
- name: seed
|
||||||
|
desc: "Semilla del KSampler. 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 del fondo en output/. El depth map usa '<filename_prefix>_depth_<layers>L'. keyword-only."
|
||||||
|
output: "dict en API format listo para comfyui_submit_workflow: txt2img base (fondo apaisado) + un SaveImage para el fondo, mas un nodo de profundidad (depth_node) que toma el IMAGE del VAEDecode y un segundo SaveImage para el depth map. Dos imagenes de salida: el fondo y su mapa de profundidad."
|
||||||
|
example: "wf = comfyui_build_parallax_background_workflow('forest at dusk, fantasy', layers=4, seed=7)"
|
||||||
|
file_path: python/functions/ml/comfyui_build_parallax_background_workflow.py
|
||||||
|
tested: true
|
||||||
|
tests: ["test_golden_background_plus_depth", "test_edge_scene_style_reflected_in_prompt", "test_edge_layers_reflected_in_depth_filename", "test_edge_dims_reflected_in_latent", "test_edge_alt_depth_node_no_ckpt", "test_error_empty_scene", "test_error_layers_below_two", "test_purity_deterministic_and_no_global_state"]
|
||||||
|
test_file_path: python/functions/ml/comfyui_build_parallax_background_workflow_test.py
|
||||||
|
---
|
||||||
|
|
||||||
|
Construye el dict (API format) de un workflow ComfyUI para un **fondo de juego en
|
||||||
|
capas** orientado a parallax 2.5D (side-scroller / plataformas). En lugar de
|
||||||
|
intentar separar las capas dentro del grafo (caro y fragil), produce las dos
|
||||||
|
piezas que el motor necesita para el parallax:
|
||||||
|
|
||||||
|
1. **El fondo apaisado** (txt2img) — la escena completa, formato ancho típico de un
|
||||||
|
nivel de plataformas.
|
||||||
|
2. **Su mapa de profundidad** (`DepthAnythingV2Preprocessor` sobre el IMAGE del
|
||||||
|
`VAEDecode`) — escala de grises donde el brillo codifica la distancia.
|
||||||
|
|
||||||
|
Con el fondo + el depth map, derivar N capas por rango de profundidad
|
||||||
|
(cielo / fondo lejano / plano medio / frente) es un post-proceso de numpy: umbralar
|
||||||
|
el depth en `layers` bandas y recortar el fondo con cada máscara a un PNG RGBA. Ese
|
||||||
|
split es un **gap documentado** (`split_parallax_layers`, aún no creada), no este
|
||||||
|
builder.
|
||||||
|
|
||||||
|
Cableado (verificado contra `/object_info` de ComfyUI 0.26.0):
|
||||||
|
|
||||||
|
```
|
||||||
|
CheckpointLoaderSimple -> ... -> KSampler -> VAEDecode --IMAGE--+-> SaveImage (fondo)
|
||||||
|
`-> DepthAnythingV2Preprocessor -> SaveImage (depth)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cuando usarla
|
||||||
|
|
||||||
|
Cuando necesites el fondo de un nivel 2D con scroll parallax y quieras las capas
|
||||||
|
por profundidad. Genera el fondo + el depth en una sola pasada, y deja que el
|
||||||
|
post-proceso corte las bandas. Sube `layers` para más planos de profundidad
|
||||||
|
(3-5 es lo habitual). Para texturas tileables del propio fondo (suelos, muros que
|
||||||
|
se repiten) usa `comfyui_build_seamless_tile_workflow`; para props/edificios iso,
|
||||||
|
`comfyui_build_isometric_workflow`.
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- **Función pura: no valida contra el server.** Si `depth_node` o su peso no están
|
||||||
|
instalados, ComfyUI devuelve HTTP 400 al enviar. Verifica con
|
||||||
|
`comfyui_object_info` (o `curl /object_info`) que `DepthAnythingV2Preprocessor`
|
||||||
|
existe (lo está). Alternativa con misma firma: `DepthAnythingPreprocessor`. Para
|
||||||
|
`MiDaS-DepthMapPreprocessor` / `Zoe-DepthMapPreprocessor` (sin `ckpt_name`) pásalo
|
||||||
|
como `depth_node` y el builder omite `ckpt_name` automáticamente.
|
||||||
|
- **El peso de DepthAnything se descarga on-demand** al primer uso (controlnet_aux
|
||||||
|
baja `depth_anything_v2_vitl.pth` ~335 MB desde HF). Requiere red la primera vez;
|
||||||
|
luego queda cacheado. Para arranque más ligero usa `depth_model="depth_anything_v2_vits.pth"`.
|
||||||
|
- **`layers` NO genera nodos.** Solo se refleja en el filename del depth map
|
||||||
|
(`<prefix>_depth_<N>L`). El número real de capas se materializa en el post-proceso
|
||||||
|
del split, no en este grafo.
|
||||||
|
- **SD1.5 a 1024x512** (apaisado) puede repetir elementos; si aparecen, baja a
|
||||||
|
768x384 o usa un checkpoint SDXL. Para 8GB lowvram, 1024x512 SD1.5 + DepthAnything
|
||||||
|
vitl caben sin OOM (se ejecutan secuencialmente, no a la vez).
|
||||||
|
- **GAP: `split_parallax_layers`** (numpy) aún no existe — segmentaría el depth en
|
||||||
|
`layers` bandas y recortaría el fondo a N PNG RGBA. Hasta entonces el split es
|
||||||
|
manual o se hace en el motor.
|
||||||
|
|
||||||
|
## Capability growth log
|
||||||
|
|
||||||
|
(sin cambios — v1.0.0)
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
"""Construye un workflow ComfyUI de FONDO DE JUEGO EN CAPAS para parallax 2.5D.
|
||||||
|
|
||||||
|
Un fondo de parallax (side-scroller / plataformas) se compone de varias capas a
|
||||||
|
distinta profundidad (cielo, fondo lejano, plano medio, frente) que se desplazan a
|
||||||
|
velocidades distintas para dar sensacion de profundidad. Generar esas capas en un
|
||||||
|
solo grafo ComfyUI nativo no es trivial: el camino robusto y barato es
|
||||||
|
1. generar UN fondo apaisado con txt2img, y
|
||||||
|
2. estimar su MAPA DE PROFUNDIDAD con un preprocessor (DepthAnything V2),
|
||||||
|
y dejar la separacion en N bandas por rango de profundidad como POST-proceso
|
||||||
|
(numpy) fuera del grafo. Este builder produce exactamente eso: el fondo + su depth
|
||||||
|
map, ambos guardados como PNG. El split en capas es una funcion aparte (gap
|
||||||
|
documentado: `split_parallax_layers`, aun no creada).
|
||||||
|
|
||||||
|
Cableado (verificado contra /object_info de ComfyUI 0.26.0):
|
||||||
|
|
||||||
|
CheckpointLoaderSimple ─► ... ─► KSampler ─► VAEDecode ─IMAGE─┬─► SaveImage (fondo)
|
||||||
|
└─► DepthAnythingV2Preprocessor ─► SaveImage (depth)
|
||||||
|
|
||||||
|
class_types reales:
|
||||||
|
- DepthAnythingV2Preprocessor: inputs image(IMAGE) + ckpt_name(enum de pesos
|
||||||
|
depth_anything_v2_*) + resolution(INT). RETURN (IMAGE,). Es el preprocessor de
|
||||||
|
controlnet_aux; descarga el peso elegido on-demand al primer uso.
|
||||||
|
- SaveImage estandar para fondo y para depth (dos nodos, prefijos distintos).
|
||||||
|
|
||||||
|
El parametro `layers` NO genera nodos (el split es post-proceso): se refleja en el
|
||||||
|
filename_prefix del SaveImage del depth map (`<prefix>_depth_<N>L`) para que, al
|
||||||
|
bajar los archivos, el nombre conserve cuantas bandas se queria derivar.
|
||||||
|
|
||||||
|
Compone comfyui_build_txt2img_workflow. Funcion pura: sin red, sin I/O. No muta el
|
||||||
|
dict de entrada (copia profunda).
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import copy
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
||||||
|
|
||||||
|
|
||||||
|
def _is_link(v) -> bool:
|
||||||
|
return isinstance(v, list) and len(v) == 2 and isinstance(v[0], str) and isinstance(v[1], int)
|
||||||
|
|
||||||
|
|
||||||
|
def comfyui_build_parallax_background_workflow(
|
||||||
|
scene: str,
|
||||||
|
*,
|
||||||
|
style: str = "game background, side-scroller, parallax layers, detailed, no characters",
|
||||||
|
layers: int = 3,
|
||||||
|
checkpoint: str = "dreamshaper_8.safetensors",
|
||||||
|
negative: str = "character, person, foreground object, text, watermark, blurry, low quality",
|
||||||
|
depth_node: str = "DepthAnythingV2Preprocessor",
|
||||||
|
depth_model: str = "depth_anything_v2_vitl.pth",
|
||||||
|
depth_resolution: int = 512,
|
||||||
|
width: int = 1024,
|
||||||
|
height: int = 512,
|
||||||
|
seed: int = 0,
|
||||||
|
steps: int = 28,
|
||||||
|
cfg: float = 7.0,
|
||||||
|
sampler_name: str = "dpmpp_2m",
|
||||||
|
scheduler: str = "karras",
|
||||||
|
filename_prefix: str = "parallax_bg",
|
||||||
|
) -> dict:
|
||||||
|
"""Construye el dict (API format) del workflow de un fondo de parallax + su depth map.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
scene: descripcion de la escena del fondo (ej. "forest at dusk, fantasy",
|
||||||
|
"ruined city skyline", "underwater cavern"). No puede estar vacio.
|
||||||
|
style: estilo/encuadre que se concatena a la escena. Por defecto fuerza el
|
||||||
|
look de fondo apaisado de side-scroller sin personajes. keyword-only.
|
||||||
|
layers: numero de capas de profundidad que se derivaran del depth map en
|
||||||
|
post-proceso (cielo/lejano/medio/frente...). Debe ser >= 2 (un parallax
|
||||||
|
necesita al menos dos planos). NO genera nodos en este grafo: se refleja
|
||||||
|
en el filename del depth map para trazabilidad. keyword-only.
|
||||||
|
checkpoint: checkpoint base. Default 'dreamshaper_8.safetensors' (SD1.5,
|
||||||
|
holgado en 8GB). keyword-only.
|
||||||
|
negative: prompt negativo. Por defecto evita personajes/props en primer
|
||||||
|
plano, que rompen un fondo limpio por capas. keyword-only.
|
||||||
|
depth_node: class_type del preprocessor de profundidad. Default
|
||||||
|
'DepthAnythingV2Preprocessor'. Alternativas instaladas con la misma
|
||||||
|
firma (image+ckpt_name+resolution): 'DepthAnythingPreprocessor'. Para
|
||||||
|
MiDaS/Zoe (que NO tienen ckpt_name) edita el nodo a mano. keyword-only.
|
||||||
|
depth_model: peso del depth_node (enum depth_anything_v2_*). Default vitl
|
||||||
|
(buena calidad, cabe en 8GB). Solo aplica a los nodos DepthAnything*.
|
||||||
|
keyword-only.
|
||||||
|
depth_resolution: resolucion interna del preprocessor de profundidad.
|
||||||
|
keyword-only.
|
||||||
|
width: ancho en px (multiplo de 8). Default 1024 (formato apaisado tipico de
|
||||||
|
parallax). keyword-only.
|
||||||
|
height: alto en px (multiplo de 8). Default 512. keyword-only.
|
||||||
|
seed, steps, cfg, sampler_name, scheduler: parametros del KSampler.
|
||||||
|
keyword-only.
|
||||||
|
filename_prefix: prefijo del PNG del fondo en output/. El depth map usa
|
||||||
|
'<filename_prefix>_depth_<layers>L'. keyword-only.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict en API format listo para comfyui_submit_workflow: txt2img base (fondo
|
||||||
|
apaisado) + un SaveImage para el fondo, mas un nodo de profundidad
|
||||||
|
(depth_node) que toma el IMAGE del VAEDecode y un segundo SaveImage para el
|
||||||
|
depth map. Dos imagenes de salida: el fondo y su mapa de profundidad.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: si scene esta vacio, layers < 2, o la base no tiene VAEDecode.
|
||||||
|
"""
|
||||||
|
from ml.comfyui_build_txt2img_workflow import comfyui_build_txt2img_workflow
|
||||||
|
|
||||||
|
if not scene or not scene.strip():
|
||||||
|
raise ValueError(
|
||||||
|
"comfyui_build_parallax_background_workflow: 'scene' no puede estar vacio"
|
||||||
|
)
|
||||||
|
if not isinstance(layers, int) or layers < 2:
|
||||||
|
raise ValueError(
|
||||||
|
"comfyui_build_parallax_background_workflow: 'layers' debe ser un int >= 2 "
|
||||||
|
f"(un parallax necesita >=2 planos), no {layers!r}"
|
||||||
|
)
|
||||||
|
|
||||||
|
positive = f"{scene}, {style}".strip().rstrip(",") if style and style.strip() else scene
|
||||||
|
|
||||||
|
wf = comfyui_build_txt2img_workflow(
|
||||||
|
checkpoint,
|
||||||
|
positive,
|
||||||
|
negative,
|
||||||
|
steps=steps,
|
||||||
|
cfg=cfg,
|
||||||
|
width=width,
|
||||||
|
height=height,
|
||||||
|
seed=seed,
|
||||||
|
sampler_name=sampler_name,
|
||||||
|
scheduler=scheduler,
|
||||||
|
filename_prefix=filename_prefix,
|
||||||
|
)
|
||||||
|
wf = copy.deepcopy(wf)
|
||||||
|
|
||||||
|
vaedecode_id = next(
|
||||||
|
(nid for nid, n in wf.items() if n.get("class_type") == "VAEDecode"), None
|
||||||
|
)
|
||||||
|
if vaedecode_id is None:
|
||||||
|
raise ValueError(
|
||||||
|
"comfyui_build_parallax_background_workflow: no se encontro VAEDecode en la base"
|
||||||
|
)
|
||||||
|
if not _is_link(wf[vaedecode_id]["inputs"].get("samples")):
|
||||||
|
raise ValueError(
|
||||||
|
"comfyui_build_parallax_background_workflow: el VAEDecode no tiene un IMAGE valido"
|
||||||
|
)
|
||||||
|
|
||||||
|
numeric = [int(k) for k in wf.keys() if str(k).isdigit()]
|
||||||
|
next_id = (max(numeric) + 1) if numeric else len(wf) + 1
|
||||||
|
depth_id = str(next_id)
|
||||||
|
save_depth_id = str(next_id + 1)
|
||||||
|
|
||||||
|
# Nodo de profundidad sobre el IMAGE del fondo (VAEDecode output 0).
|
||||||
|
depth_inputs = {"image": [vaedecode_id, 0], "resolution": depth_resolution}
|
||||||
|
# ckpt_name solo lo aceptan los DepthAnything*; para MiDaS/Zoe no se incluye.
|
||||||
|
if depth_node.startswith("DepthAnything"):
|
||||||
|
depth_inputs["ckpt_name"] = depth_model
|
||||||
|
wf[depth_id] = {"class_type": depth_node, "inputs": depth_inputs}
|
||||||
|
|
||||||
|
# SaveImage del depth map: filename refleja el numero de capas a derivar.
|
||||||
|
wf[save_depth_id] = {
|
||||||
|
"class_type": "SaveImage",
|
||||||
|
"inputs": {
|
||||||
|
"filename_prefix": f"{filename_prefix}_depth_{layers}L",
|
||||||
|
"images": [depth_id, 0],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return wf
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import json
|
||||||
|
|
||||||
|
wf = comfyui_build_parallax_background_workflow(
|
||||||
|
"forest at dusk, fantasy, atmospheric",
|
||||||
|
layers=4,
|
||||||
|
seed=7,
|
||||||
|
)
|
||||||
|
print(json.dumps(wf, indent=2))
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
"""Tests offline de comfyui_build_parallax_background_workflow (estructura del dict, sin GPU)."""
|
||||||
|
|
||||||
|
import copy
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
from ml.comfyui_build_parallax_background_workflow import ( # noqa: E402
|
||||||
|
comfyui_build_parallax_background_workflow,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _by_class(wf, cls):
|
||||||
|
return [n for n in wf.values() if n["class_type"] == cls]
|
||||||
|
|
||||||
|
|
||||||
|
def _id_of(wf, cls):
|
||||||
|
return next(nid for nid, n in wf.items() if n["class_type"] == cls)
|
||||||
|
|
||||||
|
|
||||||
|
def test_golden_background_plus_depth():
|
||||||
|
wf = comfyui_build_parallax_background_workflow("forest at dusk, fantasy")
|
||||||
|
# Base txt2img presente.
|
||||||
|
assert len(_by_class(wf, "CheckpointLoaderSimple")) == 1
|
||||||
|
assert len(_by_class(wf, "KSampler")) == 1
|
||||||
|
assert len(_by_class(wf, "VAEDecode")) == 1
|
||||||
|
# Nodo de profundidad sobre el IMAGE del VAEDecode.
|
||||||
|
depths = _by_class(wf, "DepthAnythingV2Preprocessor")
|
||||||
|
assert len(depths) == 1
|
||||||
|
vae_id = _id_of(wf, "VAEDecode")
|
||||||
|
assert depths[0]["inputs"]["image"] == [vae_id, 0]
|
||||||
|
assert depths[0]["inputs"]["ckpt_name"] == "depth_anything_v2_vitl.pth"
|
||||||
|
# Dos SaveImage: uno para el fondo, otro para el depth map.
|
||||||
|
saves = _by_class(wf, "SaveImage")
|
||||||
|
assert len(saves) == 2
|
||||||
|
depth_id = _id_of(wf, "DepthAnythingV2Preprocessor")
|
||||||
|
save_depth = next(s for s in saves if s["inputs"]["images"] == [depth_id, 0])
|
||||||
|
assert "depth" in save_depth["inputs"]["filename_prefix"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_edge_scene_style_reflected_in_prompt():
|
||||||
|
wf = comfyui_build_parallax_background_workflow(
|
||||||
|
"underwater cavern", style="painterly background, no characters"
|
||||||
|
)
|
||||||
|
texts = [n["inputs"]["text"] for n in _by_class(wf, "CLIPTextEncode")]
|
||||||
|
positive = next(t for t in texts if "underwater cavern" in t)
|
||||||
|
assert "underwater cavern" in positive
|
||||||
|
assert "painterly background" in positive
|
||||||
|
|
||||||
|
|
||||||
|
def test_edge_layers_reflected_in_depth_filename():
|
||||||
|
wf = comfyui_build_parallax_background_workflow("desert canyon", layers=5)
|
||||||
|
saves = _by_class(wf, "SaveImage")
|
||||||
|
depth_save = next(s for s in saves if "depth" in s["inputs"]["filename_prefix"])
|
||||||
|
assert "5L" in depth_save["inputs"]["filename_prefix"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_edge_dims_reflected_in_latent():
|
||||||
|
wf = comfyui_build_parallax_background_workflow(
|
||||||
|
"city skyline", width=1280, height=512
|
||||||
|
)
|
||||||
|
lat = _by_class(wf, "EmptyLatentImage")[0]
|
||||||
|
assert lat["inputs"]["width"] == 1280
|
||||||
|
assert lat["inputs"]["height"] == 512
|
||||||
|
|
||||||
|
|
||||||
|
def test_edge_alt_depth_node_no_ckpt():
|
||||||
|
# MiDaS no tiene ckpt_name: el builder no debe inyectarlo.
|
||||||
|
wf = comfyui_build_parallax_background_workflow(
|
||||||
|
"snowy mountains", depth_node="MiDaS-DepthMapPreprocessor"
|
||||||
|
)
|
||||||
|
midas = _by_class(wf, "MiDaS-DepthMapPreprocessor")
|
||||||
|
assert len(midas) == 1
|
||||||
|
assert "ckpt_name" not in midas[0]["inputs"]
|
||||||
|
assert "resolution" in midas[0]["inputs"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_error_empty_scene():
|
||||||
|
try:
|
||||||
|
comfyui_build_parallax_background_workflow("")
|
||||||
|
assert False
|
||||||
|
except ValueError as e:
|
||||||
|
assert "scene" in str(e)
|
||||||
|
|
||||||
|
|
||||||
|
def test_error_layers_below_two():
|
||||||
|
try:
|
||||||
|
comfyui_build_parallax_background_workflow("forest", layers=1)
|
||||||
|
assert False
|
||||||
|
except ValueError as e:
|
||||||
|
assert "layers" in str(e)
|
||||||
|
|
||||||
|
|
||||||
|
def test_purity_deterministic_and_no_global_state():
|
||||||
|
a = comfyui_build_parallax_background_workflow("forest", seed=1)
|
||||||
|
b = comfyui_build_parallax_background_workflow("forest", seed=1)
|
||||||
|
assert a == b
|
||||||
|
# Modificar el resultado no afecta a llamadas posteriores.
|
||||||
|
snapshot = copy.deepcopy(a)
|
||||||
|
a["3"]["inputs"]["seed"] = 999
|
||||||
|
c = comfyui_build_parallax_background_workflow("forest", seed=1)
|
||||||
|
assert c == snapshot
|
||||||
Reference in New Issue
Block a user