feat(gamedev): comfyui_build_structure_workflow — edificios/estructuras de escenario (building completo, view iso/lateral, alpha)

Builder gamedev-2d nuevo: edificacion grande y completa (casa, torre, castillo, tienda,
posada, ruina, muralla, puente, templo, faro) para poblar mapas/escenarios. Diferenciado
de comfyui_build_prop_object (edificio completo vs objeto pequeno suelto): el negativo
rechaza small object/single item/prop/furniture y el scaffold empuja full building/
complete structure/single building. view (iso por defecto) fija la perspectiva del mapa.

Pura (dict API format): compone comfyui_build_txt2img_workflow + comfyui_inject_lora
(estilo/iso opcional) + Image Rembg (alpha si transparent). 12 tests offline verdes.
Probado e2e en GPU (8GB lowvram): medieval blacksmith shop iso 512x512 RGBA, edificio
centrado (centroide 0.54/0.53). Fila en docs/capabilities/gamedev-2d.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-27 01:01:15 +02:00
parent 91cf683289
commit dbb040aa12
4 changed files with 523 additions and 0 deletions
@@ -0,0 +1,122 @@
---
name: comfyui_build_structure_workflow
kind: function
lang: py
domain: ml
version: "1.0.0"
purity: pure
signature: "def comfyui_build_structure_workflow(structure: str, *, view: str = \"isometric\", style: str = \"game building\", checkpoint: str = \"dreamshaper_8.safetensors\", size: int = 512, transparent: bool = True, seed: int = 0, lora: str | None = None, lora_strength: float = 1.0, rembg_model: str = \"u2net\", negative: str | None = None, steps: int = 28, cfg: float = 7.0, sampler_name: str = \"dpmpp_2m\", scheduler: str = \"karras\", filename_prefix: str = \"structure\") -> dict"
description: "Construye el dict (API format) del workflow de UN edificio/estructura de escenario de juego 2D (casa, torre, castillo, tienda, posada, ruina, muralla, puente, templo, faro): UN building COMPLETO y centrado a perspectiva de juego (isometrica/lateral via view), fondo limpio uniforme recortable a alpha, estilo consistente para poblar mapas/escenarios. Diferenciado de comfyui_build_prop_object (edificacion grande completa vs objeto pequeno suelto). Compone comfyui_build_txt2img_workflow + comfyui_inject_lora (estilo/iso opcional) + Image Rembg (fondo transparente si transparent). Hermano de comfyui_build_prop_object/isometric_workflow. Pura, sin red ni I/O. class_types verificados contra /object_info."
tags: [comfyui, ml, gamedev, gamedev-2d, structure, building, architecture, scenery, environment, 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: structure
desc: "Descripcion de la edificacion (ej. 'medieval house', 'wizard tower', 'stone castle', 'merchant shop', 'tavern inn', 'ruined temple', 'city wall', 'wooden bridge', 'lighthouse'). Se inserta en un prompt scaffold de building. No puede estar vacio."
- name: view
desc: "Perspectiva del juego con la que se muestra el edificio. Se inserta como '{view} view' en el prompt (ej. 'isometric', 'side', 'front', '3/4', 'top-down'). Por defecto 'isometric' (la mas comun para mapas iso). Vacio/None -> sin clausula de vista. keyword-only."
- name: style
desc: "Descriptor de estilo que mantiene consistentes las estructuras del set (ej. 'game building', 'low poly stylized building', 'pixel art building', 'fantasy RPG building', 'cartoon village building'). Pasa el MISMO view + style + checkpoint + lora a todos los edificios del mapa para coherencia. keyword-only."
- name: checkpoint
desc: "Checkpoint del servidor. 'dreamshaper_8.safetensors' (SD1.5, holgado en 8GB lowvram) por defecto; 'juggernaut_xl_v11.safetensors' para SDXL (mas VRAM, subir size). keyword-only."
- name: size
desc: "Lado del cuadrado en px (width = height = size). 512 SD1.5 por defecto. keyword-only."
- name: transparent
desc: "Si True inyecta Image Rembg y el PNG sale con alpha (fondo recortado, listo para soltar sobre el mapa). False = edificio opaco sobre fondo plano, recortable luego por el caller. keyword-only."
- name: seed
desc: "Semilla del KSampler. Misma seed + misma structure/view/style -> mismo edificio. keyword-only."
- name: lora
desc: "LoRA de estilo/isometrica opcional en models/loras (ej. 'isometric_game_assets_sd15.safetensors', 'stylized_buildings_xl.safetensors'). Para escenarios iso coherentes la LoRA iso fija el angulo 2:1. 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 estructuras (un edificio completo y entero, sin objetos pequenos/props, sin personas, fondo limpio, sin texto/recorte). keyword-only."
- name: steps
desc: "Pasos del KSampler. keyword-only."
- name: cfg
desc: "CFG del KSampler. keyword-only."
- name: sampler_name
desc: "Sampler del KSampler. keyword-only."
- name: scheduler
desc: "Scheduler del KSampler. keyword-only."
- name: filename_prefix
desc: "Prefijo del PNG en output/. keyword-only."
output: "dict en API format listo para comfyui_submit_workflow: base txt2img cuadrada con prompt scaffold de building ('{structure}, {view} view, {style}, full building, complete structure, single building, centered, plain background, game asset, architecture...') + LoRA de estilo opcional + Image Rembg (si transparent). UN edificio; para poblar un mapa -> llamar por cada structure con mismo view/style/checkpoint/lora; contact-sheet de las edificaciones -> montar los PNG con comfyui_build_grid."
tested: true
tests: ["golden transparent: clases CheckpointLoaderSimple/KSampler/VAEDecode/SaveImage/Image Rembg; structure + 'full building' + 'complete structure' + 'centered' + 'game asset' + 'plain background' + 'isometric view' en prompt; SaveImage <- Rembg; transparency True", "edge transparent=False: sin Rembg, SaveImage <- VAEDecode", "edge size: width==height==768 (cuadrado)", "edge structure al inicio del scaffold", "edge view reflejado como '{view} view'", "edge view vacio: sin clausula 'view' colgando", "edge style del set en prompt", "edge edificio completo (full building/single building) y negativo anti objeto-pequeno/prop/persona (diferenciado de prop_object)", "edge lora: LoraLoader presente con strength", "edge transparent default True", "error structure vacio -> ValueError", "determinismo"]
test_file_path: "python/functions/ml/comfyui_build_structure_workflow_test.py"
file_path: "python/functions/ml/comfyui_build_structure_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_structure_workflow import comfyui_build_structure_workflow
# Un edificio de escenario con fondo transparente (alpha), listo para soltar en el mapa.
wf = comfyui_build_structure_workflow(
"medieval blacksmith shop",
view="isometric",
style="game building",
transparent=True,
seed=7,
)
# Poblar un mapa: variar `structure` con el MISMO view/style/checkpoint/(lora) para coherencia.
# for s in ["medieval house", "wizard tower", "stone castle", "tavern inn", "lighthouse"]:
# wf = comfyui_build_structure_workflow(s, view="isometric", style="game building", seed=7)
# comfyui_submit_workflow(wf) # -> comfyui_wait_result -> comfyui_fetch_output_image
# Contact-sheet de las edificaciones: montar los PNG resultantes con comfyui_build_grid.
```
O lanzable directo con: `./fn run comfyui_build_structure_workflow` (imprime nodos + class_types del ejemplo).
## Cuando usarla
Cuando necesites edificios/estructuras completas para poblar mapas o escenarios de un
juego (RPG, estrategia, isométrico, top-down, plataformas): casas, torres, castillos,
tiendas, posadas, ruinas, murallas, puentes, templos, faros. A diferencia de
`comfyui_build_prop_object_workflow` (un objeto PEQUEÑO suelto: barril, cofre, antorcha
que se deja encima de un tile), aquí el asset es una EDIFICACIÓN grande y completa que
ocupa varios tiles y define el escenario (la casa donde entra el jugador, la torre del
mapa, el castillo del boss). Fija `view` (`isometric` por defecto) para la perspectiva
del mapa y pasa el MISMO `view` + `style` + `checkpoint` + (`lora`) a todos los edificios
del set para que combinen; varía solo `structure`. `transparent` recorta el fondo (alpha)
listo para soltar sobre el mapa. Para un atlas/contact-sheet de las edificaciones, genera
cada una y monta los PNG con `comfyui_build_grid`.
## Gotchas
- **Structure (edificio completo) != prop_object (objeto pequeño suelto)**: si lo que
quieres es un objeto de escena pequeño (barril, cofre, planta, roca) que se deja sobre
un tile, usa `comfyui_build_prop_object_workflow`. Este builder genera la EDIFICACIÓN
grande completa (`full building / single building`) que ocupa varios tiles; el negativo
por defecto rechaza `small object / single item / prop / furniture` para no degradar a
un objeto suelto.
- **El recorte usa Rembg, NO luma-to-alpha**: un edificio es una masa sólida con silueta
definida (muros, tejado, torres), rembg lo recorta limpio. `comfyui_matting_luma_to_alpha`
es para translúcidos sobre negro (humo/fuego/magia). Si la estructura tiene partes
etéreas/translúcidas que quieras conservar, pon `transparent=False` y recorta aparte.
- **`view` fija la perspectiva del mapa**: `isometric` (default) para mapas iso 2:1;
`side`/`front` para plataformas; `top-down` para cenital; `3/4` para vista de ¾. Para
iso estricto, añade la LoRA iso (`lora="isometric_game_assets_sd15.safetensors"`), que
fija mejor el ángulo 2:1 que solo el prompt.
- **Coherencia del set = mismos parámetros**: si cambias `view`/`style`/`checkpoint`/`lora`/
`seed` entre edificios, el escenario deja de combinar. Fija esos y varía solo `structure`.
- **SDXL pide más VRAM y resolución**: con `checkpoint="juggernaut_xl_v11.safetensors"`
sube `size` a 768/1024; con dreamshaper_8 (SD1.5) deja 512 (holgado en 8GB lowvram). Si
hay OOM, baja `size` o usa SD1.5.
- Si el modelo mete varios edificios, personas o lo encoge a un objeto pequeño, el
negativo por defecto ya empuja a "single building / no people / no small object";
refuerza `style` con "single isolated building, full structure" si insiste.
- `transparent=False` deja el edificio opaco sobre fondo plano: útil si prefieres recortar
fuera del workflow o el motor compone sobre un fondo sólido.
- Es una función **pura**: solo arma el dict. La generación real (GPU) la hacen
`comfyui_submit_workflow` + `comfyui_wait_result` + `comfyui_fetch_output_image`.
@@ -0,0 +1,244 @@
"""Construye el workflow ComfyUI de UN edificio/estructura de escenario de juego (API format).
Estructura/edificio de mundo (casa, torre, castillo, tienda, posada, ruina, muralla,
puente, templo, almacen, faro...): UN building COMPLETO y centrado, con la perspectiva
del juego (isometrica o lateral), fondo limpio y uniforme recortable a alpha, estilo
consistente para poblar mapas/escenarios. Es el builder hermano de
comfyui_build_prop_object_workflow / comfyui_build_isometric_workflow: mismo patron
(PURO, dict API format) que compone funciones existentes del registry, no reescribe el
grafo.
Diferencia con prop_object (clave para no duplicar): un *prop* es un objeto de escena
PEQUENO y suelto (barril, cofre, antorcha, planta, roca) que se deja encima de un tile.
Una *structure* es una EDIFICACION grande y completa que ocupa varios tiles y define el
escenario (la casa donde entra el jugador, la torre del mapa, el castillo del boss). Por
eso el scaffold empuja "full building, complete structure" y el negativo rechaza
"small object, single item, prop" para forzar el edificio entero, no un objeto suelto.
Cableado:
CheckpointLoaderSimple -> [LoraLoader opcional de estilo/iso] -> KSampler
-> CLIPTextEncode (prompt scaffold de structure) ...
-> VAEDecode -> [Image Rembg opcional] -> SaveImage
Compone:
- comfyui_build_txt2img_workflow -> base txt2img cuadrada
- comfyui_inject_lora -> LoRA de estilo/isometrica opcional (consistencia)
- 'Image Rembg (Remove Background)' (helper local) -> fondo transparente (alpha)
Por que Rembg y NO comfyui_matting_luma_to_alpha: un edificio es una masa SOLIDA con
silueta definida (muros, tejado, torres); rembg recorta limpio la silueta dejando alpha,
listo para soltar sobre el mapa. La luma-to-alpha es para translucidos sobre negro
(humo/fuego/magia), donde aplanaria la estructura. Para una edificacion solida rembg es
lo correcto. Si la estructura es etérea o tiene partes translucidas que quieras conservar,
pon transparent=False y recorta fuera del workflow.
Por que un solo edificio centrado y fondo plano: una structure se inserta como sprite/
objeto grande en el motor; el scaffold empuja a "single building, centered, plain
background, game asset" y el negativo por defecto rechaza "multiple buildings, small
object, prop, person, character, cropped, out of frame" para mantener UN edificio entero
y recortable.
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 edificios/estructuras: UN building completo y entero,
# fondo limpio, sin objetos pequenos/props sueltos (eso es prop_object), sin personas, sin
# texto/marcas ni recortes. No filtra ningun tipo de edificio (casa, torre, castillo,
# muralla, puente... son validos).
_STRUCTURE_NEGATIVE = (
"small object, single item, prop, furniture, icon, "
"multiple buildings, cityscape, person, people, character, creature, "
"cluttered, blurry, lowres, deformed, bad perspective, "
"text, watermark, signature, logo, photo, photorealistic, "
"cropped, cut off, out of frame, jpeg artifacts"
)
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_prop_object_workflow / item_icon: el nodo recorta
la silueta del edificio dejando alpha. Repunta SaveImage.images a la salida del Rembg.
"""
wf = copy.deepcopy(workflow)
vaedecode_id = next(
(nid for nid, n in wf.items() if n.get("class_type") == "VAEDecode"), None
)
save_id = next((nid for nid, n in wf.items() if n.get("class_type") == "SaveImage"), None)
if vaedecode_id is None or save_id is None:
raise ValueError(
"comfyui_build_structure_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_structure_workflow(
structure: str,
*,
view: str = "isometric",
style: str = "game building",
checkpoint: str = "dreamshaper_8.safetensors",
size: int = 512,
transparent: bool = True,
seed: int = 0,
lora: str | None = None,
lora_strength: float = 1.0,
rembg_model: str = "u2net",
negative: str | None = None,
steps: int = 28,
cfg: float = 7.0,
sampler_name: str = "dpmpp_2m",
scheduler: str = "karras",
filename_prefix: str = "structure",
) -> dict:
"""Construye el dict (API format) del workflow de un edificio/estructura de escenario.
Args:
structure: descripcion de la edificacion (ej. "medieval house", "wizard tower",
"stone castle", "merchant shop", "tavern inn", "ruined temple", "city wall",
"wooden bridge", "lighthouse"). Se inserta en un prompt scaffold de building.
No puede estar vacio.
view: perspectiva del juego con la que se muestra el edificio. Se inserta como
"{view} view" en el prompt (ej. "isometric", "side", "front", "3/4",
"top-down"). Por defecto "isometric" (la mas comun para poblar mapas iso).
keyword-only.
style: descriptor de estilo que mantiene consistentes las estructuras del set
(ej. "game building", "low poly stylized building", "pixel art building",
"fantasy RPG building", "cartoon village building"). Pasa el MISMO view +
style + checkpoint + (lora) a todos los edificios del mapa para coherencia
visual. keyword-only.
checkpoint: checkpoint del servidor. 'dreamshaper_8.safetensors' (SD1.5, holgado
en 8GB lowvram) por defecto; 'juggernaut_xl_v11.safetensors' para SDXL (mas
VRAM, subir size a 768/1024). keyword-only.
size: lado del cuadrado en px (width = height = size). 512 SD1.5 por defecto.
keyword-only.
transparent: si True inyecta Image Rembg y el PNG sale con alpha (fondo recortado,
listo para soltar sobre el mapa). Si False deja el edificio opaco sobre fondo
plano, recortable luego por el caller/pipeline. keyword-only.
seed: semilla del KSampler. Misma seed + misma structure/view/style -> mismo
edificio. keyword-only.
lora: LoRA de estilo/isometrica opcional en models/loras (ej.
'isometric_game_assets_sd15.safetensors', 'stylized_buildings_xl.safetensors').
Para escenarios isometricos coherentes, la LoRA iso ayuda a fijar el angulo
2:1. 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
estructuras (un edificio completo y entero, sin objetos pequenos/props, sin
personas, fondo limpio, sin texto/recorte). keyword-only.
steps, cfg, sampler_name, scheduler, filename_prefix: parametros de generacion.
keyword-only.
Returns:
dict en API format listo para comfyui_submit_workflow: txt2img base cuadrada con
prompt scaffold de building ('{structure}, {view} view, {style}, full building,
complete structure, centered, plain background, game asset, ...') + LoRA de estilo
opcional + Image Rembg (si transparent). Es UN edificio; para poblar un mapa ->
llamar por cada structure con el mismo view/style/checkpoint/(lora). Montar el set
con comfyui_build_grid si se quiere un contact-sheet de las edificaciones.
Raises:
ValueError: si structure 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 structure or not structure.strip():
raise ValueError(
"comfyui_build_structure_workflow: 'structure' no puede estar vacio"
)
structure = structure.strip()
view = (view or "").strip()
lora_strength = max(0.0, min(2.0, float(lora_strength)))
neg = _STRUCTURE_NEGATIVE if negative is None else negative
# Prompt scaffold de structure: un edificio COMPLETO (no un objeto suelto), centrado,
# fondo plano, listo como asset de juego grande recortable. El "{view} view" fija la
# perspectiva del escenario.
view_clause = f"{view} view, " if view else ""
positive = (
f"{structure}, {view_clause}{style}, full building, complete structure, "
"single building, centered, plain background, game asset, "
"architecture, exterior, high detail"
)
wf = comfyui_build_txt2img_workflow(
checkpoint,
positive,
neg,
steps=steps,
cfg=cfg,
width=size,
height=size,
seed=seed,
sampler_name=sampler_name,
scheduler=scheduler,
filename_prefix=filename_prefix,
)
if lora:
from ml.comfyui_inject_lora import comfyui_inject_lora
wf = comfyui_inject_lora(
wf, lora, strength_model=lora_strength, strength_clip=lora_strength
)
if transparent:
wf = _inject_rembg(wf, rembg_model)
return wf
if __name__ == "__main__":
import json
wf = comfyui_build_structure_workflow(
"medieval blacksmith shop",
view="isometric",
style="game building",
transparent=True,
seed=7,
)
print(
json.dumps(
{
"nodes": list(wf),
"classes": sorted({n["class_type"] for n in wf.values()}),
},
indent=2,
)
)
@@ -0,0 +1,156 @@
"""Tests offline de comfyui_build_structure_workflow (estructura del dict, sin GPU)."""
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from ml.comfyui_build_structure_workflow import ( # noqa: E402
comfyui_build_structure_workflow,
)
def _classes(wf):
return sorted({n["class_type"] for n in wf.values()})
def _by_class(wf, cls):
return [n for n in wf.values() if n["class_type"] == cls]
def _id_of(wf, cls):
return next(nid for nid, n in wf.items() if n["class_type"] == cls)
def _pos_with(wf, needle):
return next(
n for n in wf.values()
if n["class_type"] == "CLIPTextEncode" and needle in n["inputs"]["text"]
)
def test_golden_transparent_recipe():
wf = comfyui_build_structure_workflow(
"medieval blacksmith shop", view="isometric", transparent=True, seed=7
)
cls = _classes(wf)
# Cadena base txt2img + Rembg para alpha.
assert "CheckpointLoaderSimple" in cls
assert "KSampler" in cls
assert "VAEDecode" in cls
assert "SaveImage" in cls
assert "Image Rembg (Remove Background)" in cls
# La structure aparece en el prompt positivo con el scaffold de edificio completo.
pos = _pos_with(wf, "medieval blacksmith shop")
txt = pos["inputs"]["text"]
assert "full building" in txt
assert "complete structure" in txt
assert "centered" in txt
assert "game asset" in txt
assert "plain background" in txt
# view reflejado como "{view} view".
assert "isometric view" in txt
# SaveImage toma la imagen del Rembg (no del VAEDecode).
rembg_id = _id_of(wf, "Image Rembg (Remove Background)")
save = next(n for n in wf.values() if n["class_type"] == "SaveImage")
assert save["inputs"]["images"] == [rembg_id, 0]
assert _by_class(wf, "Image Rembg (Remove Background)")[0]["inputs"]["transparency"] is True
def test_edge_opaque_no_rembg():
wf = comfyui_build_structure_workflow("stone castle", transparent=False)
assert "Image Rembg (Remove Background)" not in _classes(wf)
# SaveImage toma del VAEDecode directamente.
vd_id = _id_of(wf, "VAEDecode")
save = next(n for n in wf.values() if n["class_type"] == "SaveImage")
assert save["inputs"]["images"] == [vd_id, 0]
def test_edge_size_reflected():
wf = comfyui_build_structure_workflow("wizard tower", size=768)
latent = _by_class(wf, "EmptyLatentImage")[0]["inputs"]
assert latent["width"] == 768
assert latent["height"] == 768 # cuadrado
def test_edge_structure_at_start():
# El scaffold arranca directamente con la structure.
wf = comfyui_build_structure_workflow("tavern inn", transparent=False)
pos = _pos_with(wf, "tavern inn")
assert pos["inputs"]["text"].startswith("tavern inn")
def test_edge_view_reflected():
# view distinto del default reflejado como "{view} view".
wf = comfyui_build_structure_workflow(
"wooden bridge", view="side", transparent=False
)
txt = _pos_with(wf, "wooden bridge")["inputs"]["text"]
assert "side view" in txt
def test_edge_view_empty_no_clause():
# view vacio -> no se inserta clausula "view" colgando.
wf = comfyui_build_structure_workflow("city wall", view="", transparent=False)
txt = _pos_with(wf, "city wall")["inputs"]["text"]
assert " view," not in txt
assert "full building" in txt
def test_edge_style_in_prompt():
# Estilo del set reflejado.
wf = comfyui_build_structure_workflow(
"ruined temple", style="pixel art building", transparent=False
)
pos = _pos_with(wf, "ruined temple")
assert "pixel art building" in pos["inputs"]["text"]
def test_edge_building_not_small_prop():
# Por defecto el scaffold lo trata como edificio COMPLETO, no objeto pequeno suelto.
wf = comfyui_build_structure_workflow("lighthouse", transparent=False)
txt = _pos_with(wf, "lighthouse")["inputs"]["text"]
assert "full building" in txt
assert "single building" in txt
# El negativo por defecto rechaza objeto pequeno/prop (diferenciado de prop_object).
neg = next(
n for n in wf.values()
if n["class_type"] == "CLIPTextEncode" and "small object" in n["inputs"]["text"]
)
assert "prop" in neg["inputs"]["text"]
assert "single item" in neg["inputs"]["text"]
def test_edge_lora_reflected():
wf = comfyui_build_structure_workflow(
"wizard tower",
lora="isometric_game_assets_sd15.safetensors",
lora_strength=0.9,
)
loras = _by_class(wf, "LoraLoader")
assert len(loras) == 1
assert loras[0]["inputs"]["lora_name"] == "isometric_game_assets_sd15.safetensors"
assert loras[0]["inputs"]["strength_model"] == 0.9
def test_edge_transparent_default_true():
# transparent por defecto True -> Rembg presente sin pasar el flag.
wf = comfyui_build_structure_workflow("stone castle")
assert "Image Rembg (Remove Background)" in _classes(wf)
def test_error_empty_structure():
try:
comfyui_build_structure_workflow(" ")
assert False
except ValueError as e:
assert "structure" in str(e)
def test_determinism():
a = comfyui_build_structure_workflow(
"wizard tower", lora="stylized_buildings_xl.safetensors", seed=7
)
b = comfyui_build_structure_workflow(
"wizard tower", lora="stylized_buildings_xl.safetensors", seed=7
)
assert a == b