feat(gamedev): comfyui_build_vehicle_mount_workflow — vehículos/monturas que el personaje usa o conduce (caballo, dragón-montura, nave, coche, barco, carro, grifo; vehículo completo vista side/iso, SIN jinete, alpha recortable)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-27 01:32:42 +02:00
parent 5662a54fa7
commit 0421bc6d4f
4 changed files with 493 additions and 0 deletions
+1
View File
@@ -45,6 +45,7 @@ VFX (ver `reports/0143`).
| `comfyui_build_card_art_workflow_py_ml` | `(subject, *, card_style="fantasy trading card art", checkpoint="juggernaut_xl_v11…", width=512, height=768, hires=True, seed=0, lora=None, …) -> dict` | LA ilustración central de UNA carta coleccionable (TCG): criatura/personaje/hechizo en formato **vertical** de carta (`width<height`, ~512×768), composición centrada + iluminación dramática (`{subject}, {card_style}, dramatic lighting, detailed illustration, centered composition, full art…`). `hires=True` → 2ª pasada de detalle (`comfyui_build_hires_fix_workflow`); si no, txt2img + LoRA estilo opcional. Genera SOLO la ilustración — el marco/título/stats los pone el motor/post (negativo rechaza `card frame/border/text/stats/UI`). Set coherente = mismo `card_style`/`checkpoint`/`lora`, varía solo `subject`. Probado e2e en GPU con SD1.5 (`reports/0153`); ⚠️ el path `hires=True` falla hoy por bug del builder `comfyui_build_hires_fix_workflow` (nodo `UltimateSDUpscale` pide `batch_size`) — usar `hires=False` hasta el fix. SD1.5/SDXL. |
| `comfyui_build_enemy_creature_workflow_py_ml` | `(creature, *, variant=None, style="game creature, full body", checkpoint="dreamshaper_8…", size=512, transparent=True, seed=0, lora=None, …) -> dict` | UN enemigo/criatura de juego (goblin, esqueleto, slime, dragón, boss, elemental): figura de **cuerpo entero** centrada, fondo limpio recortable a alpha (`{variant} {creature}, {style}, full body, centered, plain background, game asset…`) → txt2img cuadrado + LoRA estilo opcional + Rembg (alpha). `variant` (ice/fire/elite/corrupted…) se antepone a la criatura para generar la familia del MISMO enemigo (misma `creature`/`seed`/`style`, varía solo `variant`); bestiario coherente = mismo `style`/`checkpoint`/`lora`, varía solo `creature`. El negativo empuja a UNA criatura entera sin recorte. Probado e2e en GPU con SD1.5 (`reports/0154`). SD1.5. |
| `comfyui_build_prop_object_workflow_py_ml` | `(prop, *, style="game prop, isometric or side view", checkpoint="dreamshaper_8…", size=512, transparent=True, seed=0, lora=None, …) -> dict` | UN prop/objeto de escenario (barril, cofre, antorcha, planta, mueble, roca, fuente, estatua): objeto inanimado aislado a **escala de escena y perspectiva de juego** (iso/lateral), centrado, fondo limpio recortable a alpha (`{prop}, {style}, game asset, single object, centered, plain background, scene prop, world object…`) → txt2img cuadrado + LoRA estilo opcional + Rembg (alpha). **Objeto de MUNDO**, no icono plano de inventario (≠ `item_icon`, que es para una casilla de UI); este puebla el nivel. Atrezzo coherente = mismo `style`/`checkpoint`/`lora`, varía solo `prop`. El negativo excluye personas/criaturas (objeto inanimado). Probado e2e en GPU con SD1.5 (`reports/0155`). SD1.5. |
| `comfyui_build_vehicle_mount_workflow_py_ml` | `(vehicle, *, view="side", style="game vehicle", checkpoint="dreamshaper_8…", size=512, transparent=True, seed=0, lora=None, …) -> dict` | UN vehículo/montura que el personaje **USA o CONDUCE** (caballo, dragón-montura, nave espacial, coche, barco, carro, grifo, mecha): el vehículo **COMPLETO** en vista lateral o isométrica, centrado, fondo limpio recortable a alpha, **SIN jinete/conductor** (`{vehicle}, {view} view, {style}, full vehicle, centered, plain background, game asset, no rider, empty…`) → txt2img cuadrado + LoRA estilo opcional + Rembg (alpha). Se genera VACÍO (el negativo rechaza `person/rider/driver/passenger`) para que el motor componga al personaje encima. **DISTINTO de `enemy_creature` (sujeto a COMBATIR) y `prop_object` (atrezzo inanimado que decora)**: aquí el objeto se MONTA/USA; una montura viva que se cabalga (caballo, dragón) entra aquí, no en `enemy_creature`. `view` (side/iso) fija la geometría del parque móvil; set coherente = mismo `view`/`style`/`checkpoint`/`lora`, varía solo `vehicle`. Probado e2e en GPU con SD1.5 — `armored war horse with saddle` side 512×512 RGBA, vehículo centrado recortado a alpha (centroide 0.55/0.54, 4 esquinas transparentes, `reports/0169`). SD1.5. |
| `comfyui_build_topdown_sprite_workflow_py_ml` | `(subject, *, direction="south", style="top-down game sprite, RPG", checkpoint="dreamshaper_8…", size=512, transparent=True, seed=0, lora=None, …) -> dict` | UN sprite en **vista CENITAL (top-down)** estilo RPG clásico/roguelike (Zelda, juegos cenitales): personaje/objeto visto **desde arriba**, centrado, fondo limpio recortable a alpha (`{subject}, top-down view, overhead view, {direction} facing, {style}, centered, plain background, game asset…`) → txt2img cuadrado + LoRA estilo opcional + Rembg (alpha). `direction` (south/north/east/west) para el sprite de movimiento: las 4 vistas del MISMO personaje = misma `subject`/`style`/`seed`, varía solo `direction` → montar con `comfyui_build_grid`. **DISTINTO de `sprite_sheet` (vista lateral/frontal de plataformas)**: el negativo por defecto rechaza side/front/3-4/isometric/perspective para forzar la cenital. Con SD1.5 sin LoRA sale picado alto; cenital estricto pide LoRA top-down + cfg alto. Probado e2e en GPU con SD1.5 (`reports/0156`). SD1.5. |
| `comfyui_build_splash_art_workflow_py_ml` | `(scene, *, mood="epic, cinematic", checkpoint="juggernaut_xl_v11…", width=1024, height=576, hires=True, seed=0, lora=None, …) -> dict` | LA ilustración grande de UN splash / pantalla de carga / key art en formato **pantalla apaisado 16:9** (`width>height`, ~1024×576), composición cinematográfica (`{scene}, {mood}, key art, game splash screen, dramatic lighting, cinematic composition, wide shot, epic scale, atmospheric…`). `hires=True` → 2ª pasada de detalle (`comfyui_build_hires_fix_workflow`) para verse a pantalla completa; si no, txt2img + LoRA estilo opcional. Genera SOLO la ilustración — el título/logo/barra de carga los pone el motor/post (negativo rechaza `text/title/logo/UI/frame/watermark`), dejando aire para superponer el título. Set coherente = mismo `mood`/`checkpoint`/`lora`, varía solo `scene`. Probado e2e en GPU con SD1.5 + hires (1024×576 → 1536×864, 54s, `reports/0159`). SD1.5/SDXL. |
| `comfyui_build_world_map_workflow_py_ml` | `(region, *, map_style="fantasy cartography, aged parchment", checkpoint="juggernaut_xl_v11…", width=768, height=768, hires=False, seed=0, lora=None, …) -> dict` | LA ilustración de la **pantalla de mapa** del juego: una lámina cartográfica en **vista cenital** de un continente/región/reino/mazmorra con aspecto de atlas fantasy (`map of {region}, {map_style}, top-down cartographic view, illustrated game world map, labeled regions, decorative border, compass rose, fantasy atlas, no people…`). **Cuadrado por defecto** (768×768; sube `width` para mundo apaisado, `height` para mazmorra en columna), `hires=False` por defecto (ponlo `True` para detalle fino de costas/relieve). Genera SOLO la ilustración — las marcas interactivas, los iconos pinchables, las rutas y el "estás aquí" los pone el motor SOBRE la lámina; la difusión dibuja labels/ornamentos **DECORATIVOS** pero NO garantiza ortografía ni posiciones usables como datos (el negativo rechaza `photo/3d render/perspective/character/person` para mantener la vista cenital plana). Atlas coherente = mismo `map_style`/`checkpoint`/`lora`, varía solo `region`. Probado e2e en GPU con SD1.5 — reino fantasy 768×768, lámina de pergamino con costas/montañas/regiones + borde ornamental + rosa de los vientos (`prompt_id bf4861fc`, `reports/0167`). SD1.5/SDXL. |
@@ -0,0 +1,123 @@
---
name: comfyui_build_vehicle_mount_workflow
kind: function
lang: py
domain: ml
version: "1.0.0"
purity: pure
signature: "def comfyui_build_vehicle_mount_workflow(vehicle: str, *, view: str = \"side\", style: str = \"game vehicle\", 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 = \"vehicle_mount\") -> dict"
description: "Construye el dict (API format) del workflow de UN vehiculo o montura de juego 2D (caballo, dragon-montura, nave espacial, coche, barco, carro, grifo, mecha): el vehiculo COMPLETO en vista lateral o isometrica, centrado, fondo limpio uniforme recortable a alpha, SIN jinete/conductor, para que el motor coloque encima al personaje que lo usa/conduce. Diferenciado de enemy_creature (sujeto a combatir) y prop_object (atrezzo inanimado): aqui el objeto se USA/MONTA, se genera vacio y a escala de vehiculo. Compone comfyui_build_txt2img_workflow + comfyui_inject_lora (estilo opcional) + Image Rembg (fondo transparente si transparent). Hermano de comfyui_build_enemy_creature/prop_object_workflow. Pura, sin red ni I/O. class_types verificados contra /object_info."
tags: [comfyui, ml, gamedev, gamedev-2d, vehicle, mount, transport, 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: vehicle
desc: "Descripcion del vehiculo o montura (ej. 'armored war horse with saddle', 'fantasy dragon mount', 'small spaceship', 'red sports car', 'wooden sailing ship', 'war chariot', 'griffon mount'). Se inserta en un prompt scaffold de vehiculo. No puede estar vacio."
- name: view
desc: "Vista/perspectiva del vehiculo. 'side' (lateral, para side-scrollers y presentacion limpia) por defecto; 'iso'/'isometric' para RPG isometrico; tambien vale texto libre ('three-quarter', 'top-down'). Se inserta como '{view} view' para fijar la geometria del parque movil. keyword-only."
- name: style
desc: "Descriptor de estilo que mantiene consistentes los vehiculos del set (ej. 'game vehicle', 'stylized fantasy mount', 'sci-fi spaceship art', 'cartoon racing car', 'pixel art vehicle'). Pasa el MISMO style + checkpoint + lora + view a todos los vehiculos para coherencia visual. 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 en la escena con el personaje encima). False = vehiculo opaco sobre fondo plano, recortable luego por el caller. keyword-only."
- name: seed
desc: "Semilla del KSampler. Misma seed + mismo vehicle/view/style -> mismo vehiculo. keyword-only."
- name: lora
desc: "LoRA de estilo opcional en models/loras (ej. 'vehicle_concept_sd15.safetensors', 'mecha_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 vehiculos (un vehiculo entero VACIO sin jinete/conductor, 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 vehiculo ('{vehicle}, {view} view, {style}, full vehicle, centered, plain background, game asset, no rider, empty, ...') + LoRA de estilo opcional + Image Rembg (si transparent). UN vehiculo; para un parque movil -> llamar por cada vehicle con mismo view/style/checkpoint/lora; contact-sheet -> montar los PNG con comfyui_build_grid."
tested: true
tests: ["golden transparent: clases CheckpointLoaderSimple/KSampler/VAEDecode/SaveImage/Image Rembg; vehicle + 'side view' + 'full vehicle' + 'centered' + 'game asset' + 'no rider' en prompt; SaveImage <- Rembg; transparency True", "edge transparent=False: sin Rembg, SaveImage <- VAEDecode", "edge size: width==height==768 (cuadrado)", "edge view: 'isometric view' reflejado en prompt", "edge view default 'side view' y vehicle al inicio del scaffold", "edge style en prompt", "edge lora: LoraLoader presente con strength", "error vehicle vacio -> ValueError", "determinismo"]
test_file_path: "python/functions/ml/comfyui_build_vehicle_mount_workflow_test.py"
file_path: "python/functions/ml/comfyui_build_vehicle_mount_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_vehicle_mount_workflow import comfyui_build_vehicle_mount_workflow
# Un vehiculo/montura con fondo transparente (alpha), VACIO, listo para que el
# personaje lo use/conduzca encima en la escena.
wf = comfyui_build_vehicle_mount_workflow(
"armored war horse with saddle",
view="side",
style="stylized fantasy mount",
transparent=True,
seed=7,
)
# Parque movil coherente: variar `vehicle` con el MISMO view/style/checkpoint/(lora).
# for v in ["war horse", "fantasy dragon mount", "small spaceship", "wooden sailing ship"]:
# wf = comfyui_build_vehicle_mount_workflow(v, view="side", style="game vehicle", seed=7)
# comfyui_submit_workflow(wf) # -> comfyui_wait_result -> comfyui_fetch_output_image
# Contact-sheet del parque movil: montar los PNG resultantes con comfyui_build_grid.
```
O lanzable directo con: `./fn run comfyui_build_vehicle_mount_workflow` (imprime nodos + class_types del ejemplo).
## Cuando usarla
Cuando necesites un vehiculo o montura que el personaje USE o CONDUZCA: caballo,
dragon-montura, nave espacial, coche, barco, carro, grifo, moto, mecha. A diferencia
de `comfyui_build_enemy_creature_workflow` (sujeto vivo a COMBATIR) y
`comfyui_build_prop_object_workflow` (atrezzo inanimado que decora el nivel y no se
interactua), aqui el objeto se MONTA/USA: se genera VACIO (sin jinete) y a escala de
vehiculo para que el personaje quepa encima/dentro, y el motor lo compone con el
sprite del personaje. Fija `view` (side/iso) y `style` + `checkpoint` + (`lora`) para
todo el parque movil y varia solo `vehicle` para que combinen. `transparent` recorta
el fondo (alpha) listo para soltar en la escena. Para un atlas/contact-sheet, genera
cada vehiculo y monta los PNG con `comfyui_build_grid`.
## Gotchas
- **Vehiculo/montura (se USA) != criatura (se COMBATE) != prop (decora)**: si el sujeto
es un enemigo a derrotar usa `comfyui_build_enemy_creature_workflow`; si es atrezzo
inanimado de escena usa `comfyui_build_prop_object_workflow`. Una montura viva
(caballo, dragon que se cabalga) entra AQUI, no en enemy_creature, porque el objetivo
es montarla.
- **Se genera VACIO (sin jinete)**: el negativo por defecto rechaza "person, rider,
driver, passenger" para que el vehiculo salga sin nadie encima y el motor componga al
personaje aparte. Si quieres el vehiculo CON jinete pintado, pasa `negative=""` o un
negativo propio y anade el jinete al `vehicle`.
- **El recorte usa Rembg, NO luma-to-alpha**: un vehiculo es un objeto solido con
silueta definida, rembg lo recorta limpio. `comfyui_matting_luma_to_alpha` es para
translucidos sobre negro (humo/fuego/magia). Si el vehiculo es etereo o translucido
(nave de energia, montura espectral), pon `transparent=False` y recorta con
luma-to-alpha en un paso aparte.
- **`view` define la geometria**: side-scroller pide `view="side"`, RPG isometrico pide
`view="iso"`. Mantenla consistente con el resto de assets del juego; si la mezclas, el
parque movil deja de encajar con los tiles.
- **Coherencia del set = mismos parametros**: si cambias `view`/`style`/`checkpoint`/
`lora`/`seed` entre vehiculos, el parque movil deja de combinar. Fija esos y varia solo
`vehicle`.
- **SDXL pide mas VRAM y resolucion**: 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.
- `transparent=False` deja el vehiculo opaco sobre fondo plano: util si prefieres
recortar fuera del workflow o el motor compone sobre un fondo solido.
- Es una funcion **pura**: solo arma el dict. La generacion real (GPU) la hacen
`comfyui_submit_workflow` + `comfyui_wait_result` + `comfyui_fetch_output_image`.
@@ -0,0 +1,245 @@
"""Construye el workflow ComfyUI de UN vehiculo/montura de juego (API format).
Vehiculo o montura de juego (caballo, dragon-montura, nave espacial, coche, barco,
carro, grifo, moto, carruaje...): el vehiculo COMPLETO en vista lateral o isometrica,
centrado, fondo limpio y uniforme recortable a alpha, SIN jinete/conductor, para que
el motor coloque encima al personaje que lo usa o lo conduce. Es el builder hermano
de comfyui_build_enemy_creature_workflow / comfyui_build_prop_object_workflow: mismo
patron (PURO, dict API format) que compone funciones existentes del registry, no
reescribe el grafo.
Diferencia con enemy_creature y prop_object (clave para no duplicar): una *criatura*
es un sujeto vivo en pose de combate (sprite/token hostil); un *prop* es atrezzo
inanimado que decora el nivel y no se interactua. Un *vehiculo/montura* es un objeto
que el personaje USA o MONTA: se genera vacio (sin jinete) y a escala de vehiculo
para que el personaje quepa encima/dentro. Por eso el scaffold empuja "full vehicle,
no rider, no driver, empty" y el negativo rechaza "person, rider, driver,
passenger". Una montura viva (caballo, dragon) que el personaje cabalga entra aqui,
no en enemy_creature, porque el objetivo es montarla, no combatirla.
Cableado:
CheckpointLoaderSimple -> [LoraLoader opcional de estilo] -> KSampler
-> CLIPTextEncode (prompt scaffold de vehiculo) ...
-> 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: un vehiculo es un sujeto SOLIDO
con silueta definida (coche, barco, caballo); rembg recorta limpio la silueta
dejando alpha, listo para soltar en la escena con el personaje encima. La
luma-to-alpha es para translucidos sobre negro (humo/fuego/magia), donde aplanaria
el vehiculo. Si el vehiculo es etereo o translucido (nave de energia, montura
espectral) y se quiere conservar la translucidez, recortar fuera del workflow
(transparent=False) y componer con luma-to-alpha en un paso aparte. Para el parque
movil tipico (caballo, coche, nave, barco) rembg es lo correcto.
Por que `view` (side/iso) y no meterlo en `style`: la vista define la geometria del
vehiculo y debe ser explicita y consistente con el resto de assets del juego — un
side-scroller pide "side view", un RPG isometrico pide "isometric view". Separar
`view` deja fijar la perspectiva del parque movil entero variando solo `vehicle`.
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 vehiculos/monturas: UN vehiculo entero, bien
# formado, VACIO (sin jinete/conductor), fondo limpio, sin texto/marcas ni recortes.
# No filtra ningun tipo de vehiculo (terrestre, aereo, naval, montura viva, mecanico).
_VEHICLE_NEGATIVE = (
"person, people, rider, driver, passenger, pilot, character, crowd, "
"multiple vehicles, blurry, lowres, deformed, disfigured, bad anatomy, "
"mutated, ugly, 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_enemy_creature_workflow / prop_object: el nodo
recorta la silueta del vehiculo 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_vehicle_mount_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_vehicle_mount_workflow(
vehicle: str,
*,
view: str = "side",
style: str = "game vehicle",
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 = "vehicle_mount",
) -> dict:
"""Construye el dict (API format) del workflow de un vehiculo/montura de juego.
Args:
vehicle: descripcion del vehiculo o montura (ej. "armored war horse with
saddle", "fantasy dragon mount", "small spaceship", "red sports car",
"wooden sailing ship", "war chariot", "griffon mount"). Se inserta en un
prompt scaffold de vehiculo. No puede estar vacio.
view: vista/perspectiva del vehiculo. "side" (lateral, para side-scrollers y
presentacion limpia) por defecto; "iso"/"isometric" para RPG isometrico;
tambien vale texto libre ("three-quarter", "top-down"). Se inserta como
"{view} view" en el prompt para fijar la geometria del parque movil.
keyword-only.
style: descriptor de estilo que mantiene consistentes los vehiculos del set
(ej. "game vehicle", "stylized fantasy mount", "sci-fi spaceship art",
"cartoon racing car", "pixel art vehicle"). Pasa el MISMO style +
checkpoint + (lora) + view a todos los vehiculos 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 en la escena con el personaje encima). Si
False deja el vehiculo opaco sobre fondo plano, recortable luego por el
caller/pipeline. keyword-only.
seed: semilla del KSampler. Misma seed + mismo vehicle/view/style -> mismo
vehiculo. keyword-only.
lora: LoRA de estilo opcional en models/loras (ej.
'vehicle_concept_sd15.safetensors', 'mecha_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
vehiculos (un vehiculo entero VACIO sin jinete/conductor, 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 vehiculo ('{vehicle}, {view} view, {style}, full
vehicle, centered, plain background, game asset, no rider, ...') + LoRA de
estilo opcional + Image Rembg (si transparent). Es UN vehiculo; para un parque
movil -> llamar por cada vehicle con el mismo view/style/checkpoint/(lora).
Montar el set con comfyui_build_grid si se quiere un contact-sheet.
Raises:
ValueError: si vehicle 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 vehicle or not vehicle.strip():
raise ValueError(
"comfyui_build_vehicle_mount_workflow: 'vehicle' no puede estar vacio"
)
vehicle = vehicle.strip()
view = (view or "side").strip()
lora_strength = max(0.0, min(2.0, float(lora_strength)))
neg = _VEHICLE_NEGATIVE if negative is None else negative
# Prompt scaffold de vehiculo: el vehiculo entero en su vista, centrado, fondo
# plano, VACIO (sin jinete), listo como asset de juego recortable que el personaje
# usara/conducira encima.
positive = (
f"{vehicle}, {view} view, {style}, full vehicle, centered, plain background, "
"game asset, single vehicle, no rider, empty, 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_vehicle_mount_workflow(
"armored war horse with saddle",
view="side",
style="stylized fantasy mount",
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,124 @@
"""Tests offline de comfyui_build_vehicle_mount_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_vehicle_mount_workflow import ( # noqa: E402
comfyui_build_vehicle_mount_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_vehicle_mount_workflow(
"armored war horse with saddle", view="side", 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
# El vehiculo aparece en el prompt positivo con el scaffold de vehiculo.
pos = _pos_with(wf, "armored war horse with saddle")
txt = pos["inputs"]["text"]
assert "side view" in txt
assert "full vehicle" in txt
assert "centered" in txt
assert "game asset" in txt
assert "no rider" 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_vehicle_mount_workflow("red sports car", 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_vehicle_mount_workflow("small spaceship", size=768)
latent = _by_class(wf, "EmptyLatentImage")[0]["inputs"]
assert latent["width"] == 768
assert latent["height"] == 768 # cuadrado
def test_edge_view_reflected():
# La vista isometrica se refleja en el prompt como "isometric view".
wf = comfyui_build_vehicle_mount_workflow(
"war chariot", view="isometric", transparent=False
)
pos = _pos_with(wf, "war chariot")
assert "isometric view" in pos["inputs"]["text"]
def test_edge_view_default_side():
# Sin view, por defecto "side view".
wf = comfyui_build_vehicle_mount_workflow("wooden sailing ship", transparent=False)
pos = _pos_with(wf, "wooden sailing ship")
txt = pos["inputs"]["text"]
assert txt.startswith("wooden sailing ship")
assert "side view" in txt
def test_edge_style_in_prompt():
wf = comfyui_build_vehicle_mount_workflow(
"fantasy dragon mount", style="stylized fantasy mount", transparent=False
)
pos = _pos_with(wf, "fantasy dragon mount")
assert "stylized fantasy mount" in pos["inputs"]["text"]
def test_edge_lora_reflected():
wf = comfyui_build_vehicle_mount_workflow(
"mecha walker", lora="mecha_xl.safetensors", lora_strength=0.9
)
loras = _by_class(wf, "LoraLoader")
assert len(loras) == 1
assert loras[0]["inputs"]["lora_name"] == "mecha_xl.safetensors"
assert loras[0]["inputs"]["strength_model"] == 0.9
def test_error_empty_vehicle():
try:
comfyui_build_vehicle_mount_workflow(" ")
assert False
except ValueError as e:
assert "vehicle" in str(e)
def test_determinism():
a = comfyui_build_vehicle_mount_workflow(
"griffon mount", view="iso", lora="vehicle_concept_sd15.safetensors", seed=7
)
b = comfyui_build_vehicle_mount_workflow(
"griffon mount", view="iso", lora="vehicle_concept_sd15.safetensors", seed=7
)
assert a == b