feat(gamedev): comfyui_build_foliage_set_workflow — vegetación/foliage de escenario (árbol, arbusto, hierba, flores, helecho, hongo, cactus, juncos; elemento de naturaleza aislado vista side/top, fondo limpio recortable a alpha; diferenciado de prop_object/structure)
Builder puro hermano de prop_object/structure: compone txt2img + inject_lora (estilo opcional) + Image Rembg (alpha). Scaffold '{plant}, {view} view, {style}, single plant element, centered, plain background, game nature asset'. Negativo rechaza manufacturado/edificio/persona/bosque/maceta para mantener UN elemento vegetal orgánico aislado.
13 tests offline verdes + generación real e2e (golden 'a glowing mushroom' seed 7, prompt_id 8fb65a51, RGBA recortable centrado). Dos gotchas reales SD1.5+Rembg documentados (planta grande->paisaje; follaje claro->Rembg come hojas) con evidencia en reports/0170.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -52,6 +52,7 @@ VFX (ver `reports/0143`).
|
|||||||
| `comfyui_build_decal_overlay_workflow_py_ml` | `(decal, *, on_black=True, style="grunge decal, high detail", checkpoint="dreamshaper_8…", size=512, seed=0, lora=None, …) -> dict` | UN decal/overlay con alpha para superponer sobre superficies/paredes/sprites con blend mode del motor (sangre, grietas, suciedad, óxido, quemaduras, salpicaduras, arañazos, musgo): textura **aislada sobre fondo PLANO** (`{decal}, {style}, single isolated decal, centered, on a solid pure black background, flat backdrop, sticker, no scenery, texture overlay, game asset…`) → txt2img cuadrado + LoRA estilo opcional. `on_black=True` (defecto) pensado para extraer alpha con **`comfyui_matting_luma_to_alpha`** (luma=alpha, conserva el falloff de translúcidos — la técnica gamedev correcta, ≠ recorte binario). **NO inyecta Rembg** (el matting es luma→alpha de disco, no un nodo): el SaveImage sale directo del VAEDecode. Set coherente = mismo `style`/`checkpoint`/`lora`, varía solo `decal`/`seed`. ⚠️ "grunge" en `style` arrastra fondo gris en SD1.5 → para fondo negro plano usar un `style` sin connotación de fondo + reroll de `seed`; luma Rec601 penaliza el rojo → para sangre roja pasar `luma_weights` con más peso al rojo. Probado e2e en GPU con SD1.5 (`reports/0160`). SD1.5. |
|
| `comfyui_build_decal_overlay_workflow_py_ml` | `(decal, *, on_black=True, style="grunge decal, high detail", checkpoint="dreamshaper_8…", size=512, seed=0, lora=None, …) -> dict` | UN decal/overlay con alpha para superponer sobre superficies/paredes/sprites con blend mode del motor (sangre, grietas, suciedad, óxido, quemaduras, salpicaduras, arañazos, musgo): textura **aislada sobre fondo PLANO** (`{decal}, {style}, single isolated decal, centered, on a solid pure black background, flat backdrop, sticker, no scenery, texture overlay, game asset…`) → txt2img cuadrado + LoRA estilo opcional. `on_black=True` (defecto) pensado para extraer alpha con **`comfyui_matting_luma_to_alpha`** (luma=alpha, conserva el falloff de translúcidos — la técnica gamedev correcta, ≠ recorte binario). **NO inyecta Rembg** (el matting es luma→alpha de disco, no un nodo): el SaveImage sale directo del VAEDecode. Set coherente = mismo `style`/`checkpoint`/`lora`, varía solo `decal`/`seed`. ⚠️ "grunge" en `style` arrastra fondo gris en SD1.5 → para fondo negro plano usar un `style` sin connotación de fondo + reroll de `seed`; luma Rec601 penaliza el rojo → para sangre roja pasar `luma_weights` con más peso al rojo. Probado e2e en GPU con SD1.5 (`reports/0160`). 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_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_foliage_set_workflow_py_ml` | `(plant, *, view="side", style="game foliage, stylized", checkpoint="dreamshaper_8…", size=512, transparent=True, seed=0, lora=None, …) -> dict` | UN elemento de **vegetación/foliage** de escenario (árbol, arbusto, hierba alta, flores, helecho, hongo, cactus, tronco caído, juncos, hiedra): UN elemento de **naturaleza ORGÁNICA AISLADO** y centrado a perspectiva de juego (`{view} view`, `side` por defecto), fondo limpio recortable a alpha (`{plant}, {view} view, {style}, single plant element, centered, plain background, game nature asset, natural vegetation, organic, isolated plant…`) → txt2img cuadrado + LoRA estilo opcional + Rembg (alpha). **Vegetación que viste el terreno**, distinta del **objeto MANUFACTURADO** suelto (≠ `prop_object`: barril/cofre/mueble) y del **EDIFICIO** (≠ `structure`: casa/torre); el negativo rechaza `building / manmade object / barrel / furniture / person` y `multiple plants / dense forest / jungle / landscape` (UN elemento, no un bosque) + `pot / planter / vase` (planta en maceta = `prop_object`). Recorte por **Rembg** (planta opaca de silueta definida), no luma→alpha. Set coherente = mismo `view`/`style`/`checkpoint`/`lora`, varía solo `plant`. ⚠️ **dos gotchas reales SD1.5+Rembg**: (1) **plantas grandes (árbol) tienden a PAISAJE** (cielo+campo) en lugar de fondo plano → re-roll de seeds buscando fondo uniforme (`comfyui_batch_generate`); (2) **follaje verde claro sobre fondo claro → Rembg se come las hojas** y deja solo tronco/ramas → preferir elementos de **silueta compacta y color saturado** (hongo, arbusto denso) o `transparent=False` + matting manual. Probado e2e en GPU con SD1.5 — golden `a glowing mushroom` seed 7 512×512 RGBA, hongo centrado recortado a alpha limpio (centroide 0.51/0.58, opaco 19%, `prompt_id 8fb65a51`); evidencia del gotcha del roble en `reports/0170`. 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. |
|
| `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. |
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
---
|
||||||
|
name: comfyui_build_foliage_set_workflow
|
||||||
|
kind: function
|
||||||
|
lang: py
|
||||||
|
domain: ml
|
||||||
|
version: "1.0.0"
|
||||||
|
purity: pure
|
||||||
|
signature: "def comfyui_build_foliage_set_workflow(plant: str, *, view: str = \"side\", style: str = \"game foliage, stylized\", 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 = \"foliage_set\") -> dict"
|
||||||
|
description: "Construye el dict (API format) del workflow de UN elemento de vegetacion/foliage de escenario de juego 2D (arbol, arbusto, hierba alta, flores, helecho, hongo, cactus, tronco caido, juncos, hiedra): UN elemento de naturaleza AISLADO y centrado a perspectiva de juego (lateral/cenital via view), fondo limpio uniforme recortable a alpha, estilo consistente para poblar terrenos/escenarios. Diferenciado de comfyui_build_prop_object (vegetacion organica vs objeto manufacturado) y de structure (planta suelta vs edificio). Compone comfyui_build_txt2img_workflow + comfyui_inject_lora (estilo opcional) + Image Rembg (fondo transparente si transparent). Hermano de comfyui_build_prop_object/structure_workflow. Pura, sin red ni I/O. class_types verificados contra /object_info."
|
||||||
|
tags: [comfyui, ml, gamedev, gamedev-2d, foliage, plant, vegetation, nature, 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: plant
|
||||||
|
desc: "Descripcion del elemento de naturaleza (ej. 'a large oak tree', 'a small bush', 'tall grass', 'a cluster of red flowers', 'a fern', 'a glowing mushroom', 'a desert cactus', 'a fallen log', 'reeds', 'a patch of ivy'). Se inserta en un prompt scaffold de foliage. No puede estar vacio."
|
||||||
|
- name: view
|
||||||
|
desc: "Perspectiva del juego con la que se muestra la planta. Se inserta como '{view} view' en el prompt (ej. 'side', 'top-down', 'front', '3/4', 'isometric'). Por defecto 'side' (la mas comun para vegetacion de plataformas/RPG lateral). Vacio/None -> sin clausula de vista. keyword-only."
|
||||||
|
- name: style
|
||||||
|
desc: "Descriptor de estilo que mantiene consistente la vegetacion del set (ej. 'game foliage, stylized', 'low poly stylized plant', 'pixel art foliage', 'painterly fantasy plant', 'cartoon nature asset'). Pasa el MISMO view + style + checkpoint + lora a todas las plantas del nivel 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 terreno). False = planta opaca sobre fondo plano, recortable luego por el caller. keyword-only."
|
||||||
|
- name: seed
|
||||||
|
desc: "Semilla del KSampler. Misma seed + mismo plant/view/style -> misma planta. keyword-only."
|
||||||
|
- name: lora
|
||||||
|
desc: "LoRA de estilo opcional en models/loras (ej. 'stylized_nature_sd15.safetensors', 'painterly_plants_xl.safetensors'). None = sin LoRA. keyword-only."
|
||||||
|
- name: lora_strength
|
||||||
|
desc: "Fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0]. keyword-only."
|
||||||
|
- name: rembg_model
|
||||||
|
desc: "Modelo Rembg ('u2net' general, 'isnet-anime' para anime). Solo se usa si transparent=True. keyword-only."
|
||||||
|
- name: negative
|
||||||
|
desc: "Prompt negativo. None usa el negativo por defecto pensado para foliage (un elemento vegetal entero, organico, sin objetos manufacturados/edificios/personas, sin bosque/paisaje denso, 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 foliage ('{plant}, {view} view, {style}, single plant element, centered, plain background, game nature asset, natural vegetation, organic...') + LoRA de estilo opcional + Image Rembg (si transparent). UN elemento; para poblar un nivel -> llamar por cada plant con mismo view/style/checkpoint/lora; contact-sheet de la vegetacion -> montar los PNG con comfyui_build_grid."
|
||||||
|
tested: true
|
||||||
|
tests: ["golden transparent: clases CheckpointLoaderSimple/KSampler/VAEDecode/SaveImage/Image Rembg; plant + 'single plant element' + 'game nature asset' + 'natural vegetation' + 'centered' + 'plain background' + 'side view' en prompt; SaveImage <- Rembg; transparency True", "edge transparent=False: sin Rembg, SaveImage <- VAEDecode", "edge size: width==height==768 (cuadrado)", "edge plant al inicio del scaffold", "edge view reflejado como '{view} view'", "edge view vacio: sin clausula 'view' colgando", "edge style del set en prompt", "edge negativo anti manufacturado/edificio/persona/bosque (diferenciado de prop_object y structure)", "edge lora: LoraLoader presente con strength", "edge lora_strength clampado a [0,2]", "edge transparent default True", "error plant vacio -> ValueError", "determinismo"]
|
||||||
|
test_file_path: "python/functions/ml/comfyui_build_foliage_set_workflow_test.py"
|
||||||
|
file_path: "python/functions/ml/comfyui_build_foliage_set_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_foliage_set_workflow import comfyui_build_foliage_set_workflow
|
||||||
|
|
||||||
|
# Un elemento de vegetacion con fondo transparente (alpha), listo para plantar en el terreno.
|
||||||
|
wf = comfyui_build_foliage_set_workflow(
|
||||||
|
"a large oak tree",
|
||||||
|
view="side",
|
||||||
|
style="game foliage, stylized",
|
||||||
|
transparent=True,
|
||||||
|
seed=7,
|
||||||
|
)
|
||||||
|
# Poblar un nivel: variar `plant` con el MISMO view/style/checkpoint/(lora) para coherencia.
|
||||||
|
# for p in ["a small bush", "tall grass", "a fern", "a cluster of red flowers", "a glowing mushroom"]:
|
||||||
|
# wf = comfyui_build_foliage_set_workflow(p, view="side", style="game foliage, stylized", seed=7)
|
||||||
|
# comfyui_submit_workflow(wf) # -> comfyui_wait_result -> comfyui_fetch_output_image
|
||||||
|
# Contact-sheet de la vegetacion: montar los PNG resultantes con comfyui_build_grid.
|
||||||
|
```
|
||||||
|
|
||||||
|
O lanzable directo con: `./fn run comfyui_build_foliage_set_workflow` (imprime nodos + class_types del ejemplo).
|
||||||
|
|
||||||
|
## Cuando usarla
|
||||||
|
|
||||||
|
Cuando necesites vegetación/plantas para vestir el terreno de un escenario de juego
|
||||||
|
(RPG, plataformas, top-down, isométrico): árboles, arbustos, hierba alta, flores,
|
||||||
|
helechos, hongos, cactus, troncos caídos, juncos, hiedra. A diferencia de
|
||||||
|
`comfyui_build_prop_object_workflow` (un objeto MANUFACTURADO suelto: barril, cofre,
|
||||||
|
antorcha, mueble) y de `comfyui_build_structure_workflow` (un EDIFICIO completo: casa,
|
||||||
|
torre, castillo), aquí el asset es un elemento de NATURALEZA ORGÁNICA que se planta por
|
||||||
|
el nivel para vestir el suelo. Fija `view` (`side` por defecto) para la perspectiva del
|
||||||
|
escenario y pasa el MISMO `view` + `style` + `checkpoint` + (`lora`) a todas las plantas
|
||||||
|
del set para que combinen; varía solo `plant`. `transparent` recorta el fondo (alpha)
|
||||||
|
listo para soltar sobre el terreno. Para un atlas/contact-sheet de la vegetación, genera
|
||||||
|
cada elemento y monta los PNG con `comfyui_build_grid`.
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- **Foliage (vegetación orgánica) != prop_object (objeto manufacturado) != structure
|
||||||
|
(edificio)**: si lo que quieres es un objeto de escena manufacturado (barril, cofre,
|
||||||
|
mueble), usa `comfyui_build_prop_object_workflow`; si es una edificación grande (casa,
|
||||||
|
torre), usa `comfyui_build_structure_workflow`. Este builder genera UN elemento vegetal
|
||||||
|
aislado; el negativo por defecto rechaza `building / manmade object / barrel /
|
||||||
|
furniture / person` para no degradar a un prop, un edificio ni una figura.
|
||||||
|
- **Un elemento, no un bosque**: el scaffold empuja `single plant element / isolated
|
||||||
|
plant` y el negativo rechaza `multiple plants / dense forest / jungle / landscape`
|
||||||
|
para mantener UN sprite suelto reutilizable, no un paisaje. Si insiste en pintar varios,
|
||||||
|
refuerza `style` con "single isolated plant".
|
||||||
|
- **El recorte usa Rembg, NO luma-to-alpha**: una planta es una masa opaca con silueta
|
||||||
|
vegetal definida (hojas, ramas, tallos), rembg la recorta limpia.
|
||||||
|
`comfyui_matting_luma_to_alpha` es para translúcidos sobre negro (humo/fuego/magia). Si
|
||||||
|
la planta es etérea/bioluminiscente y quieres conservar partes translúcidas, pon
|
||||||
|
`transparent=False` y recorta aparte.
|
||||||
|
- **`view` fija la perspectiva del escenario**: `side` (default) para plataformas/RPG
|
||||||
|
lateral; `top-down` para cenital; `isometric` para mapas iso 2:1; `3/4` para vista de ¾.
|
||||||
|
Para iso estricto, añade una LoRA iso que fije mejor el ángulo que solo el prompt.
|
||||||
|
- **Coherencia del set = mismos parámetros**: si cambias `view`/`style`/`checkpoint`/`lora`/
|
||||||
|
`seed` entre plantas, la vegetación deja de combinar. Fija esos y varía solo `plant`.
|
||||||
|
- **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.
|
||||||
|
- **El negativo rechaza `pot / planter / vase`**: una planta en maceta es atrezzo
|
||||||
|
(`prop_object`), no foliage. Aquí el elemento es la planta en sí. Si quieres una planta
|
||||||
|
en tiesto, usa `comfyui_build_prop_object_workflow` con `prop="a potted plant"`.
|
||||||
|
- `transparent=False` deja la planta opaca 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,256 @@
|
|||||||
|
"""Construye el workflow ComfyUI de UN elemento de vegetacion/foliage de escenario (API format).
|
||||||
|
|
||||||
|
Vegetacion/foliage de mundo (arbol, arbusto, hierba alta, flores, helecho, hongo,
|
||||||
|
cactus, tronco caido, juncos, hiedra, matorral...): UN elemento de naturaleza
|
||||||
|
AISLADO, centrado y con la perspectiva del juego (lateral o cenital), fondo limpio
|
||||||
|
y uniforme recortable a alpha, estilo consistente para poblar escenarios. Es el
|
||||||
|
builder hermano de comfyui_build_prop_object_workflow / comfyui_build_structure_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
|
||||||
|
MANUFACTURADO e inanimado (barril, cofre, antorcha, mueble, fuente) que se deja sobre
|
||||||
|
un tile. Un *foliage* es un elemento de NATURALEZA ORGANICA (planta, arbol, flor,
|
||||||
|
hongo, hierba): se planta en el escenario para vestir el terreno, conserva su silueta
|
||||||
|
vegetal irregular y se reparte a mansalva por el nivel. Por eso el scaffold empuja
|
||||||
|
"single plant element, nature asset, natural vegetation, organic" en vez del
|
||||||
|
"single object, scene prop" de los props, y el negativo rechaza objetos
|
||||||
|
manufacturados/edificios/personas para mantener UN elemento vegetal limpio.
|
||||||
|
|
||||||
|
Cableado:
|
||||||
|
|
||||||
|
CheckpointLoaderSimple -> [LoraLoader opcional de estilo] -> KSampler
|
||||||
|
-> CLIPTextEncode (prompt scaffold de foliage) ...
|
||||||
|
-> VAEDecode -> [Image Rembg opcional] -> SaveImage
|
||||||
|
|
||||||
|
Compone:
|
||||||
|
- comfyui_build_txt2img_workflow -> base txt2img cuadrada
|
||||||
|
- comfyui_inject_lora -> LoRA de estilo opcional (consistencia del set)
|
||||||
|
- 'Image Rembg (Remove Background)' (helper local) -> fondo transparente (alpha)
|
||||||
|
|
||||||
|
Por que Rembg y NO comfyui_matting_luma_to_alpha: una planta es una masa OPACA con
|
||||||
|
silueta definida (hojas, ramas, tallos); rembg recorta limpio la silueta dejando
|
||||||
|
alpha, listo para soltar sobre el terreno. La luma-to-alpha es para translucidos
|
||||||
|
sobre negro (humo/fuego/magia), donde aplanaria el follaje. Para vegetacion solida
|
||||||
|
(arbol, arbusto, flor, hongo) rembg es lo correcto. Si el elemento es etereo o
|
||||||
|
translucido (planta bioluminiscente, espora flotante) y se quiere conservar la
|
||||||
|
translucidez, recortar fuera del workflow (transparent=False) y componer con
|
||||||
|
luma-to-alpha en un paso aparte.
|
||||||
|
|
||||||
|
Por que un solo elemento centrado y fondo plano: una planta se inserta como sprite/
|
||||||
|
objeto suelto en el motor y se repite por el nivel; el scaffold empuja a "single plant
|
||||||
|
element, centered, plain background, game nature asset" y el negativo por defecto
|
||||||
|
rechaza "person, character, building, multiple plants, dense forest, cropped, out of
|
||||||
|
frame" para mantener UN elemento entero, organico y recortable (no un bosque ni un
|
||||||
|
paisaje).
|
||||||
|
|
||||||
|
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 vegetacion/foliage: UN elemento vegetal entero,
|
||||||
|
# organico, fondo limpio, sin objetos manufacturados (eso es prop_object), sin
|
||||||
|
# edificios (eso es structure), sin personas/criaturas, sin convertirlo en un bosque
|
||||||
|
# o paisaje denso, sin texto/marcas ni recortes. No filtra ningun tipo de planta
|
||||||
|
# (arbol, arbusto, flor, hongo, cactus, helecho... son validos).
|
||||||
|
_FOLIAGE_NEGATIVE = (
|
||||||
|
"person, people, character, creature, animal, hands, face, "
|
||||||
|
"building, house, structure, barrel, furniture, manmade object, "
|
||||||
|
"multiple plants, dense forest, jungle, landscape, scenery background, "
|
||||||
|
"pot, planter, vase, "
|
||||||
|
"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 / structure: el nodo
|
||||||
|
recorta la silueta vegetal del elemento 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_foliage_set_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_foliage_set_workflow(
|
||||||
|
plant: str,
|
||||||
|
*,
|
||||||
|
view: str = "side",
|
||||||
|
style: str = "game foliage, stylized",
|
||||||
|
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 = "foliage_set",
|
||||||
|
) -> dict:
|
||||||
|
"""Construye el dict (API format) del workflow de un elemento de vegetacion/foliage.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
plant: descripcion del elemento de naturaleza (ej. "a large oak tree",
|
||||||
|
"a small bush", "tall grass", "a cluster of red flowers", "a fern",
|
||||||
|
"a glowing mushroom", "a desert cactus", "a fallen log", "reeds",
|
||||||
|
"a patch of ivy"). Se inserta en un prompt scaffold de foliage. No puede
|
||||||
|
estar vacio.
|
||||||
|
view: perspectiva del juego con la que se muestra la planta. Se inserta como
|
||||||
|
"{view} view" en el prompt (ej. "side", "top-down", "front", "3/4",
|
||||||
|
"isometric"). Por defecto "side" (la mas comun para vegetacion de
|
||||||
|
plataformas/RPG lateral). Vacio/None -> sin clausula de vista.
|
||||||
|
keyword-only.
|
||||||
|
style: descriptor de estilo que mantiene consistente la vegetacion del set
|
||||||
|
(ej. "game foliage, stylized", "low poly stylized plant", "pixel art
|
||||||
|
foliage", "painterly fantasy plant", "cartoon nature asset"). Pasa el
|
||||||
|
MISMO view + style + checkpoint + (lora) a todas las plantas del nivel
|
||||||
|
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 terreno). Si False deja la planta
|
||||||
|
opaca sobre fondo plano, recortable luego por el caller/pipeline.
|
||||||
|
keyword-only.
|
||||||
|
seed: semilla del KSampler. Misma seed + mismo plant/view/style -> misma
|
||||||
|
planta. keyword-only.
|
||||||
|
lora: LoRA de estilo opcional en models/loras (ej.
|
||||||
|
'stylized_nature_sd15.safetensors', 'painterly_plants_xl.safetensors').
|
||||||
|
None = sin LoRA. keyword-only.
|
||||||
|
lora_strength: fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0].
|
||||||
|
keyword-only.
|
||||||
|
rembg_model: modelo Rembg ('u2net' general, 'isnet-anime' para anime). Solo
|
||||||
|
se usa si transparent=True. keyword-only.
|
||||||
|
negative: prompt negativo. None usa el negativo por defecto pensado para
|
||||||
|
foliage (un elemento vegetal entero, organico, sin objetos
|
||||||
|
manufacturados/edificios/personas, sin bosque/paisaje denso, 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 foliage ('{plant}, {view} view, {style}, single plant
|
||||||
|
element, centered, plain background, game nature asset, ...') + LoRA de estilo
|
||||||
|
opcional + Image Rembg (si transparent). Es UN elemento; para poblar un nivel
|
||||||
|
-> llamar por cada plant con el mismo view/style/checkpoint/(lora). Montar el
|
||||||
|
set con comfyui_build_grid si se quiere un contact-sheet de la vegetacion.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: si plant 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 plant or not plant.strip():
|
||||||
|
raise ValueError(
|
||||||
|
"comfyui_build_foliage_set_workflow: 'plant' no puede estar vacio"
|
||||||
|
)
|
||||||
|
|
||||||
|
plant = plant.strip()
|
||||||
|
view = (view or "").strip()
|
||||||
|
lora_strength = max(0.0, min(2.0, float(lora_strength)))
|
||||||
|
neg = _FOLIAGE_NEGATIVE if negative is None else negative
|
||||||
|
|
||||||
|
# Prompt scaffold de foliage: un elemento vegetal AISLADO (no un bosque), centrado,
|
||||||
|
# fondo plano, listo como asset de naturaleza suelto (sprite/objeto) recortable. El
|
||||||
|
# "{view} view" fija la perspectiva del escenario.
|
||||||
|
view_clause = f"{view} view, " if view else ""
|
||||||
|
positive = (
|
||||||
|
f"{plant}, {view_clause}{style}, single plant element, centered, "
|
||||||
|
"plain background, game nature asset, natural vegetation, organic, "
|
||||||
|
"isolated plant, 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_foliage_set_workflow(
|
||||||
|
"a large oak tree",
|
||||||
|
view="side",
|
||||||
|
style="game foliage, stylized",
|
||||||
|
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,147 @@
|
|||||||
|
"""Tests offline de comfyui_build_foliage_set_workflow (sin red ni GPU).
|
||||||
|
|
||||||
|
Valida que el dict del workflow refleja plant/view/style/transparent y la
|
||||||
|
estructura de nodos, igual que sus hermanos prop_object/structure.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
||||||
|
|
||||||
|
from ml.comfyui_build_foliage_set_workflow import comfyui_build_foliage_set_workflow
|
||||||
|
|
||||||
|
|
||||||
|
def _classes(wf):
|
||||||
|
return {n["class_type"] for n in wf.values()}
|
||||||
|
|
||||||
|
|
||||||
|
def _positive_prompt(wf):
|
||||||
|
"""Devuelve el texto positivo (el primer CLIPTextEncode cableado al KSampler.positive)."""
|
||||||
|
ksampler = next(n for n in wf.values() if n["class_type"] == "KSampler")
|
||||||
|
pos_id = ksampler["inputs"]["positive"][0]
|
||||||
|
return wf[pos_id]["inputs"]["text"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_golden_transparent():
|
||||||
|
"""Caso feliz: arbol con alpha. Clases completas + scaffold + Rembg cableado."""
|
||||||
|
wf = comfyui_build_foliage_set_workflow("a large oak tree", seed=7)
|
||||||
|
cls = _classes(wf)
|
||||||
|
assert "CheckpointLoaderSimple" in cls
|
||||||
|
assert "KSampler" in cls
|
||||||
|
assert "VAEDecode" in cls
|
||||||
|
assert "SaveImage" in cls
|
||||||
|
assert "Image Rembg (Remove Background)" in cls
|
||||||
|
|
||||||
|
prompt = _positive_prompt(wf)
|
||||||
|
assert "a large oak tree" in prompt
|
||||||
|
assert "single plant element" in prompt
|
||||||
|
assert "game nature asset" in prompt
|
||||||
|
assert "natural vegetation" in prompt
|
||||||
|
assert "centered" in prompt
|
||||||
|
assert "plain background" in prompt
|
||||||
|
assert "side view" in prompt # view por defecto
|
||||||
|
|
||||||
|
# SaveImage debe consumir la salida 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 <- VAEDecode."""
|
||||||
|
wf = comfyui_build_foliage_set_workflow("a fern", transparent=False)
|
||||||
|
assert "Image Rembg (Remove Background)" not in _classes(wf)
|
||||||
|
save = next(n for n in wf.values() if n["class_type"] == "SaveImage")
|
||||||
|
vae_id = next(nid for nid, n in wf.items() if n["class_type"] == "VAEDecode")
|
||||||
|
assert save["inputs"]["images"][0] == vae_id
|
||||||
|
|
||||||
|
|
||||||
|
def test_edge_size_square():
|
||||||
|
"""size se aplica como cuadrado width==height."""
|
||||||
|
wf = comfyui_build_foliage_set_workflow("tall grass", size=768)
|
||||||
|
latent = next(n for n in wf.values() if n["class_type"] == "EmptyLatentImage")
|
||||||
|
assert latent["inputs"]["width"] == 768
|
||||||
|
assert latent["inputs"]["height"] == 768
|
||||||
|
|
||||||
|
|
||||||
|
def test_edge_plant_at_start():
|
||||||
|
"""El plant abre el scaffold (no queda enterrado)."""
|
||||||
|
wf = comfyui_build_foliage_set_workflow("a glowing mushroom")
|
||||||
|
assert _positive_prompt(wf).startswith("a glowing mushroom,")
|
||||||
|
|
||||||
|
|
||||||
|
def test_edge_view_reflected():
|
||||||
|
"""view se refleja como '{view} view'."""
|
||||||
|
wf = comfyui_build_foliage_set_workflow("a desert cactus", view="top-down")
|
||||||
|
assert "top-down view" in _positive_prompt(wf)
|
||||||
|
|
||||||
|
|
||||||
|
def test_edge_view_empty_no_clause():
|
||||||
|
"""view vacio -> sin clausula 'view' colgando."""
|
||||||
|
wf = comfyui_build_foliage_set_workflow("reeds", view="")
|
||||||
|
prompt = _positive_prompt(wf)
|
||||||
|
assert "view," not in prompt
|
||||||
|
assert "reeds," in prompt
|
||||||
|
|
||||||
|
|
||||||
|
def test_edge_style_reflected():
|
||||||
|
"""style del set aparece en el prompt."""
|
||||||
|
wf = comfyui_build_foliage_set_workflow(
|
||||||
|
"a small bush", style="pixel art foliage"
|
||||||
|
)
|
||||||
|
assert "pixel art foliage" in _positive_prompt(wf)
|
||||||
|
|
||||||
|
|
||||||
|
def test_edge_negative_rejects_manmade_and_buildings():
|
||||||
|
"""Negativo por defecto diferencia foliage de prop/structure (rechaza manmade/building)."""
|
||||||
|
wf = comfyui_build_foliage_set_workflow("a fern")
|
||||||
|
ksampler = next(n for n in wf.values() if n["class_type"] == "KSampler")
|
||||||
|
neg_id = ksampler["inputs"]["negative"][0]
|
||||||
|
neg = wf[neg_id]["inputs"]["text"]
|
||||||
|
assert "building" in neg
|
||||||
|
assert "manmade object" in neg or "barrel" in neg
|
||||||
|
assert "person" in neg
|
||||||
|
assert "dense forest" in neg # un elemento, no un bosque
|
||||||
|
|
||||||
|
|
||||||
|
def test_edge_lora_present():
|
||||||
|
"""lora -> LoraLoader presente con la fuerza pasada."""
|
||||||
|
wf = comfyui_build_foliage_set_workflow(
|
||||||
|
"a cluster of red flowers", lora="stylized_nature_sd15.safetensors",
|
||||||
|
lora_strength=0.8,
|
||||||
|
)
|
||||||
|
assert "LoraLoader" in _classes(wf)
|
||||||
|
lora_node = next(n for n in wf.values() if n["class_type"] == "LoraLoader")
|
||||||
|
assert lora_node["inputs"]["strength_model"] == pytest.approx(0.8)
|
||||||
|
|
||||||
|
|
||||||
|
def test_edge_lora_strength_clamped():
|
||||||
|
"""lora_strength fuera de [0,2] se clampa."""
|
||||||
|
wf = comfyui_build_foliage_set_workflow(
|
||||||
|
"a fallen log", lora="x.safetensors", lora_strength=9.0
|
||||||
|
)
|
||||||
|
lora_node = next(n for n in wf.values() if n["class_type"] == "LoraLoader")
|
||||||
|
assert lora_node["inputs"]["strength_model"] == pytest.approx(2.0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_edge_transparent_default_true():
|
||||||
|
"""transparent es True por defecto -> Rembg presente."""
|
||||||
|
wf = comfyui_build_foliage_set_workflow("a patch of ivy")
|
||||||
|
assert "Image Rembg (Remove Background)" in _classes(wf)
|
||||||
|
|
||||||
|
|
||||||
|
def test_error_empty_plant():
|
||||||
|
"""plant vacio -> ValueError."""
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
comfyui_build_foliage_set_workflow(" ")
|
||||||
|
|
||||||
|
|
||||||
|
def test_determinism():
|
||||||
|
"""Mismos args -> mismo dict."""
|
||||||
|
a = comfyui_build_foliage_set_workflow("a fern", seed=3)
|
||||||
|
b = comfyui_build_foliage_set_workflow("a fern", seed=3)
|
||||||
|
assert a == b
|
||||||
Reference in New Issue
Block a user