feat(gamedev): comfyui_build_directional_sprite_workflow — sprite multi-direccional 2.5D (SV3D turntable / Zero123)
Builder puro (dict API format) que a partir del sprite frontal de un personaje construye el workflow ComfyUI de N vistas direccionales consistentes (8-way N/NE/E/SE/S/SW/W/NW o 4-way) rotando la figura en 3D. SV3D (orbit turntable) por defecto, Stable Zero123 (batch por azimuth) como fallback de menor VRAM. Es el puente 2.5D del catalogo gamedev-2d: consistencia rotacional real (el mismo modelo rotado) frente a sprite_sheet (OpenPose 2D re-poza, identidad inconsistente). Helper directional_sprite_view_order(directions) mapea frame i -> direccion i. Funcion pura: solo construye el grafo; coste GPU al enviar con comfyui_submit_workflow. Probado e2e en GPU: goblin enemy_creature_00001_ -> SV3D 8 direcciones elevation 15, 8 frames 576x576 en 75s, pico 7145/8192 MiB (prompt_id 8b9f75de). Consistencia rotacional medida: MAE adyacentes 27 < frente-espalda 29.6, spread de paleta 3.83. Report: reports/0187. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
---
|
||||
name: comfyui_build_directional_sprite_workflow
|
||||
kind: function
|
||||
lang: py
|
||||
domain: ml
|
||||
version: "1.0.0"
|
||||
purity: pure
|
||||
signature: "def comfyui_build_directional_sprite_workflow(input_image: str, *, directions: int = 8, model: str = \"sv3d\", elevation: float = 0.0, size: int | None = None, orbit_frames: int | None = None, seed: int = 0, ckpt: str | None = None, steps: int = 20, cfg: float | None = None, min_cfg: float = 1.0, sampler_name: str = \"euler\", scheduler: str = \"karras\", filename_prefix: str = \"directional_sprite\") -> dict"
|
||||
description: "Construye el dict (API format) del workflow ComfyUI de un sprite MULTI-DIRECCIONAL: a partir de UNA imagen frontal de un personaje (fondo limpio) genera N vistas direccionales CONSISTENTES (el mismo personaje rotado en 3D, no re-dibujado) para un juego top-down / isométrico / shooter 8-way. Dos modelos 3D nativos del server: SV3D (orbit turntable en una pasada, mejor consistencia, sv3d_p.safetensors) o Stable Zero123 (batch de vistas por azimuth, fallback de menor VRAM, stable_zero123.ckpt). Es el puente 2.5D que faltaba en gamedev-2d: a diferencia de sprite_sheet (re-poza con OpenPose 2D, identidad inconsistente entre angulos), aqui la difusion 3D ROTA la figura sobre su eje, asi casco/arma/paleta son los mismos en cada direccion. Funcion pura: solo construye el grafo (class_types/inputs verificados contra /object_info); el coste GPU esta al enviar con comfyui_submit_workflow. La imagen frontal debe existir ya en el input/ del servidor."
|
||||
tags: [comfyui, ml, gamedev-2d, sprite, directional, sv3d, zero123, turntable, multiview, stable-diffusion, workflow]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: ""
|
||||
imports: []
|
||||
params:
|
||||
- name: input_image
|
||||
desc: "Nombre del archivo de la imagen frontal del personaje dentro del input/ del servidor ComfyUI (lo carga LoadImage). Debe existir ya (subir con POST /upload/image o copiar a ~/ComfyUI/input/). Idealmente personaje centrado, encuadrado entero, mirando a camara, sobre fondo limpio (SV3D/Zero123 rinden mucho mejor sin fondo). No puede estar vacio."
|
||||
- name: directions
|
||||
desc: "Numero de direcciones a generar. 8 (default) = 8-way N/NE/E/SE/S/SW/W/NW (shooter 8-direcciones, top-down/iso); 4 = N/E/S/W (RPG clasico). Cualquier N>=1 vale (vistas equiespaciadas a 360/N grados); 4 y 8 reciben nombres cardinales via directional_sprite_view_order. keyword-only."
|
||||
- name: model
|
||||
desc: "'sv3d' (default, orbita turntable en una pasada, mejor consistencia angular, checkpoint sv3d_p.safetensors) o 'zero123' (batch de vistas con control de azimuth, fallback de menor VRAM, checkpoint stable_zero123.ckpt, nativo 256px). keyword-only."
|
||||
- name: elevation
|
||||
desc: "Elevacion de camara en grados. 0 = orbita en el ecuador (vista lateral pura). Top-down/isometrico suele querer PICADO (mirar al personaje algo desde arriba): subir a ~15-30. keyword-only."
|
||||
- name: size
|
||||
desc: "Lado en px de cada vista. None (default) usa la resolucion nativa del modelo (576 sv3d / 256 zero123). Bajarlo reduce VRAM a costa de detalle (p.ej. 320 para SV3D si OOM). keyword-only."
|
||||
- name: orbit_frames
|
||||
desc: "SOLO sv3d. Numero de frames del orbit que sintetiza SV3D_Conditioning. None (default) = directions (una imagen por direccion, salida directa). Subirlo (p.ej. 21, el nativo de SV3D) da una orbita mas densa y consistente de la que el caller submuestrea las direcciones (frame mas cercano por azimuth); a mas frames, mas VRAM. Ignorado por zero123. keyword-only."
|
||||
- name: seed
|
||||
desc: "Semilla del KSampler (0 = determinista; cambiar para variar la orbita). keyword-only."
|
||||
- name: ckpt
|
||||
desc: "Nombre del checkpoint en el servidor. None (default) usa el del modelo (sv3d_p.safetensors / stable_zero123.ckpt). Pasar 'sv3d_u.safetensors' para la orbita uniforme en el ecuador. keyword-only."
|
||||
- name: steps
|
||||
desc: "Pasos de sampling del KSampler. keyword-only."
|
||||
- name: cfg
|
||||
desc: "Guidance scale. None (default) usa el del modelo (2.5 sv3d / 4.0 zero123). keyword-only."
|
||||
- name: min_cfg
|
||||
desc: "SOLO sv3d. CFG del primer frame para VideoLinearCFGGuidance (interpola de min_cfg a cfg a lo largo del orbit). keyword-only."
|
||||
- name: sampler_name
|
||||
desc: "Algoritmo del KSampler. Default 'euler'. keyword-only."
|
||||
- name: scheduler
|
||||
desc: "Scheduler del KSampler. Default 'karras'. keyword-only."
|
||||
- name: filename_prefix
|
||||
desc: "Prefijo del archivo de salida del SaveImage. keyword-only."
|
||||
output: "dict en API format listo para comfyui_submit_workflow (claves = node_ids string, valores = class_type + inputs). SV3D: ImageOnlyCheckpointLoader + LoadImage + SV3D_Conditioning + VideoLinearCFGGuidance + KSampler + VAEDecode + SaveImage (los N frames del orbit). Zero123: ImageOnlyCheckpointLoader + LoadImage + StableZero123_Conditioning_Batched + KSampler + VAEDecode + SaveImage (un batch de directions vistas). El frame i (i-esima imagen del SaveImage, azimuth creciente desde la frontal) = direccion i de directional_sprite_view_order(directions). El modulo expone ademas directional_sprite_view_order(directions) -> lista de nombres de direccion alineada por indice con los frames."
|
||||
tested: false
|
||||
file_path: "python/functions/ml/comfyui_build_directional_sprite_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_directional_sprite_workflow import (
|
||||
comfyui_build_directional_sprite_workflow,
|
||||
directional_sprite_view_order,
|
||||
)
|
||||
from ml.comfyui_submit_workflow import comfyui_submit_workflow
|
||||
|
||||
# 'goblin_front.png' = sprite frontal del personaje, fondo limpio, ya en input/ del server.
|
||||
wf = comfyui_build_directional_sprite_workflow(
|
||||
"goblin_front.png",
|
||||
directions=8, # 8-way N/NE/E/SE/S/SW/W/NW
|
||||
model="sv3d", # turntable consistente (default)
|
||||
elevation=15.0, # algo de picado (top-down/iso)
|
||||
size=576,
|
||||
seed=7,
|
||||
)
|
||||
# wf["3"]["class_type"] == "SV3D_Conditioning"; wf["7"] == SaveImage (los 8 frames del orbit)
|
||||
pid = comfyui_submit_workflow(wf)["prompt_id"]
|
||||
# sondear /history/{pid} -> comfyui_fetch_output_image por frame.
|
||||
# El frame i corresponde a la direccion i:
|
||||
print(directional_sprite_view_order(8)) # ['S','SE','E','NE','N','NW','W','SW']
|
||||
```
|
||||
|
||||
Probado end-to-end (27/06/2026): goblin `enemy_creature_00001_.png` (compuesto sobre blanco
|
||||
576×576) → SV3D `sv3d_p.safetensors` 8 direcciones elevation 15 seed 7. `prompt_id
|
||||
8b9f75de-9dcb-41f6-ae4c-7d52d34ed238`, 8 frames `dir_sprite_goblin_0000{1..8}_.png`, 75 s,
|
||||
VRAM pico 7145/8192 MiB. Consistencia rotacional medida: MAE adyacentes 27 (rotación
|
||||
gradual) < frente↔espalda 29.6 (la espalda difiere más = rotación 3D real), spread de paleta
|
||||
entre frames 3.83 (identidad de color consistente — el mismo goblin en las 8 vistas).
|
||||
|
||||
## Cuando usarla
|
||||
|
||||
- Cuando necesites un personaje visto desde **varias direcciones de movimiento** con
|
||||
**identidad consistente**: enemigos de un 8-way shooter, NPCs de un top-down, unidades de
|
||||
un RTS isométrico, sprite del jugador en un roguelike cenital.
|
||||
- Tienes **UN sprite frontal** (lo generaste con `enemy_creature`/`topdown_sprite`, o lo
|
||||
dibujaste) y quieres las otras direcciones SIN re-dibujarlas a mano.
|
||||
- Elige `directions`: 8 para movimiento diagonal completo, 4 para RPG clásico.
|
||||
- Sube algo de `elevation` (~15-30) para juegos con cámara picada (iso/top-down); déjalo en
|
||||
0 para un turntable lateral puro.
|
||||
- **No** la uses si quieres re-pozar al personaje (correr, atacar, saltar) en una sola vista
|
||||
— eso es `comfyui_build_sprite_sheet_workflow` (OpenPose). Esto **rota**, no anima la pose.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- **La imagen frontal debe existir ya en `input/`** del servidor (subir con
|
||||
`POST /upload/image` o copiar a `~/ComfyUI/input/`). La función es pura y NO valida que
|
||||
exista ni que el checkpoint esté instalado; valida con `comfyui_validate_workflow` antes
|
||||
de enviar a un server desconocido.
|
||||
- **Fondo limpio obligatorio para buen resultado.** SV3D/Zero123 rinden mucho mejor con el
|
||||
personaje aislado sobre fondo plano (blanco o transparente compuesto sobre blanco). Un
|
||||
fondo con escena confunde la rotación 3D. Recorta/compón el sprite antes (Rembg o
|
||||
`Image.alpha_composite` sobre blanco).
|
||||
- **VRAM (RTX 3070 8GB):** SV3D es un modelo de vídeo; aún en lowvram pesa. Medido: 8 frames
|
||||
a 576² → pico **7145 MiB**, margen estrecho. Limpia la GPU antes
|
||||
(`POST /free {"unload_models":true,"free_memory":true}`) — no convive con un juego AAA
|
||||
abierto (~2.7 GB). Si **OOM**: baja `size` (576→320), baja `directions` (8→4), o cae a
|
||||
`model="zero123"` (vistas independientes, batch más barato). NO hace falta matar procesos.
|
||||
- **`comfyui_wait_result` lanza `TimeoutError`** si el orbit no termina dentro del timeout,
|
||||
pero el job sigue en GPU y completa — recupera los frames sondeando `/history/{prompt_id}`
|
||||
por su cuenta (envolver el wait en try/except o sondear directamente). El golden tardó 75 s,
|
||||
pero con `orbit_frames=21` o `size` alto puede irse a minutos.
|
||||
- **SV3D entrena a 21 frames.** Con `orbit_frames` distinto de 21 (p.ej. el default
|
||||
`=directions=8`) el orbit es más espaciado pero funciona (probado a 8). Para máxima
|
||||
fidelidad angular usa `orbit_frames=21` y submuestrea las `directions` vistas (frame más
|
||||
cercano por azimuth) — a costa de más VRAM y tiempo.
|
||||
- **Mapeo dirección↔frame:** el frame 0 es la vista frontal (personaje de frente); el frame
|
||||
i es el azimuth i·360/N. `directional_sprite_view_order(directions)` da la lista de nombres
|
||||
alineada por índice con los frames. La etiqueta cardinal ("S" = front/mirando abajo) es
|
||||
convención gamedev top-down; ajústala a tu motor si difiere.
|
||||
- **Distinto de `comfyui_generate_views_from_image`** (impura): esa orquesta
|
||||
`/object_info` + submit + wait + fetch para reconstrucción 3D multi-vista (4 cardinales
|
||||
front/right/back/left). Este builder es el equivalente **puro** orientado a sprite
|
||||
direccional gamedev, hermano de `comfyui_build_img2vid_workflow` (builder puro frente al
|
||||
orquestador). Para el flujo completo build→submit→wait→fetch, compón este builder con esas
|
||||
funciones del registry.
|
||||
|
||||
## Capability growth log
|
||||
|
||||
(sin cambios desde v1.0.0)
|
||||
@@ -0,0 +1,310 @@
|
||||
"""Construye el workflow ComfyUI de un sprite MULTI-DIRECCIONAL (vistas 3D consistentes).
|
||||
|
||||
A partir de UNA imagen frontal de un personaje (fondo limpio), construye el dict (API
|
||||
format) de un workflow que genera N vistas direccionales del MISMO personaje rotando en
|
||||
3D: las 8 direcciones N/NE/E/SE/S/SW/W/NW de un shooter 8-way / juego top-down /
|
||||
isométrico, o las 4 direcciones N/E/S/W de un RPG clásico. El valor de este builder es la
|
||||
CONSISTENCIA ROTACIONAL: es el mismo personaje girado en 3D, no re-dibujado por dirección.
|
||||
|
||||
Es el puente 2.5D que faltaba en el catálogo `gamedev-2d`. Los builders 2D de ese grupo
|
||||
(`enemy_creature`, `topdown_sprite`, `sprite_sheet`) producen UNA vista; `sprite_sheet`
|
||||
re-poza con OpenPose 2D (re-dibuja la silueta para cada pose, así que la identidad del
|
||||
personaje deriva entre ángulos). Aquí, en cambio, un modelo de difusión 3D (SV3D turntable
|
||||
u Stable Zero123 órbita) rota la figura alrededor de su eje vertical, de modo que el casco,
|
||||
el arma y la paleta son los mismos en cada dirección. Es la diferencia entre "dibujar al
|
||||
goblin 8 veces" (inconsistente) y "fotografiar al goblin desde 8 ángulos" (consistente).
|
||||
|
||||
Dos modelos, ambos confirmados en /object_info del server 8GB:
|
||||
|
||||
- SV3D (model="sv3d", default): el nodo `SV3D_Conditioning` produce una ÓRBITA de
|
||||
`video_frames` vistas equiespaciadas en 360° en una sola pasada (reutiliza la
|
||||
maquinaria img2vid de Stable Video Diffusion: `ImageOnlyCheckpointLoader` →
|
||||
`SV3D_Conditioning` → `VideoLinearCFGGuidance` → `KSampler` → `VAEDecode` → `SaveImage`,
|
||||
los N frames). Mejor consistencia angular; checkpoint `sv3d_p.safetensors` (~9.4 GB en
|
||||
disco, ~2-4 GB en VRAM porque es un modelo de vídeo en lowvram). Frame 0 = la vista de
|
||||
entrada; el frame i corresponde al azimuth i·360/N.
|
||||
- Stable Zero123 (model="zero123"): el nodo `StableZero123_Conditioning_Batched` genera
|
||||
un BATCH de N vistas a azimuths equiespaciados (control directo del ángulo por vista).
|
||||
Útil como fallback si SV3D no cabe en VRAM: cada vista es una imagen independiente, así
|
||||
que se puede bajar `directions` o `size` con más holgura. Checkpoint
|
||||
`stable_zero123.ckpt`. width/height nativos 256.
|
||||
|
||||
Función PURA: sin red, sin I/O. Solo construye el dict del grafo (los class_types y sus
|
||||
inputs verificados contra /object_info). El coste GPU está al enviar con
|
||||
`comfyui_submit_workflow`. La imagen de entrada debe existir ya en el `input/` del servidor
|
||||
(subir antes con POST /upload/image). Hermana impura: `comfyui_generate_views_from_image`
|
||||
(orquesta /object_info + submit + wait + fetch para reconstrucción 3D multi-vista, 4
|
||||
cardinales); este builder es el equivalente PURO orientado a sprite direccional gamedev,
|
||||
igual que `comfyui_build_img2vid_workflow` es el builder puro frente al orquestador.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
# Modelos soportados -> checkpoint por defecto que el nodo carga vía ImageOnlyCheckpointLoader.
|
||||
_MODEL_CKPT = {
|
||||
"sv3d": "sv3d_p.safetensors",
|
||||
"zero123": "stable_zero123.ckpt",
|
||||
}
|
||||
|
||||
# Resolución nativa por modelo cuando el caller no fija `size`. SV3D entrena a 576²;
|
||||
# Stable Zero123 a 256². Usar la nativa evita degradación y picos de VRAM innecesarios.
|
||||
_MODEL_NATIVE_SIZE = {"sv3d": 576, "zero123": 256}
|
||||
|
||||
# CFG por defecto por modelo cuando el caller no lo fija. SV3D usa guía de vídeo baja
|
||||
# interpolada (VideoLinearCFGGuidance de min_cfg a cfg); Zero123 usa una CFG algo mayor.
|
||||
_MODEL_DEFAULT_CFG = {"sv3d": 2.5, "zero123": 4.0}
|
||||
|
||||
# Nombres de dirección por nº de direcciones, en el orden del orbit empezando por la vista
|
||||
# frontal (frame 0 = personaje de frente, mirando a cámara). Convención gamedev top-down:
|
||||
# "front" = el personaje mirando hacia ABAJO/al jugador (south). Cada paso avanza el azimuth
|
||||
# 360/N grados. El orden es solo una etiqueta de conveniencia para el caller al ensamblar el
|
||||
# grid direccional; el grafo no depende de él.
|
||||
_DIRECTION_NAMES = {
|
||||
4: ["S", "E", "N", "W"],
|
||||
8: ["S", "SE", "E", "NE", "N", "NW", "W", "SW"],
|
||||
}
|
||||
|
||||
|
||||
def directional_sprite_view_order(directions: int) -> list:
|
||||
"""Lista ordenada de nombres de dirección que produce el orbit, frame 0..N-1.
|
||||
|
||||
El frame i del workflow (i-ésima imagen del SaveImage, en orden de azimuth creciente
|
||||
desde la vista frontal) corresponde a la dirección devuelta en la posición i. Para
|
||||
4 u 8 direcciones devuelve los nombres cardinales canónicos; para cualquier otro N
|
||||
devuelve etiquetas genéricas por azimuth (``az0``, ``az45``, ...). Pura.
|
||||
|
||||
Args:
|
||||
directions: número de direcciones del orbit (>= 1).
|
||||
|
||||
Returns:
|
||||
lista de `directions` nombres de dirección, alineada por índice con los frames.
|
||||
"""
|
||||
directions = int(directions)
|
||||
if directions in _DIRECTION_NAMES:
|
||||
return list(_DIRECTION_NAMES[directions])
|
||||
return [f"az{round(i * 360 / directions)}" for i in range(directions)]
|
||||
|
||||
|
||||
def comfyui_build_directional_sprite_workflow(
|
||||
input_image: str,
|
||||
*,
|
||||
directions: int = 8,
|
||||
model: str = "sv3d",
|
||||
elevation: float = 0.0,
|
||||
size: int | None = None,
|
||||
orbit_frames: int | None = None,
|
||||
seed: int = 0,
|
||||
ckpt: str | None = None,
|
||||
steps: int = 20,
|
||||
cfg: float | None = None,
|
||||
min_cfg: float = 1.0,
|
||||
sampler_name: str = "euler",
|
||||
scheduler: str = "karras",
|
||||
filename_prefix: str = "directional_sprite",
|
||||
) -> dict:
|
||||
"""Construye el dict (API format) del workflow de sprite multi-direccional.
|
||||
|
||||
Recibe el sprite frontal de UN personaje (imagen en el `input/` del servidor, fondo
|
||||
limpio) y construye un workflow que produce N vistas direccionales CONSISTENTES (el
|
||||
mismo personaje rotado en 3D), una por dirección de movimiento del juego.
|
||||
|
||||
Args:
|
||||
input_image: nombre del archivo de imagen frontal del personaje dentro del `input/`
|
||||
del servidor ComfyUI (lo carga el nodo LoadImage). Debe existir ya (subir con
|
||||
POST /upload/image o copiar a ~/ComfyUI/input/). Idealmente con fondo limpio y el
|
||||
personaje centrado y encuadrado entero, mirando a cámara. No puede estar vacío.
|
||||
directions: número de direcciones a generar. 8 (default) = 8-way N/NE/E/SE/S/SW/W/NW
|
||||
(shooter 8-direcciones, top-down/iso); 4 = N/E/S/W (RPG clásico). Cualquier N>=1
|
||||
es válido (vistas equiespaciadas a 360/N grados); 4 y 8 reciben nombres cardinales
|
||||
(ver `directional_sprite_view_order`). keyword-only.
|
||||
model: "sv3d" (default, órbita turntable, mejor consistencia angular) o "zero123"
|
||||
(batch de vistas con control de azimuth, fallback de menor VRAM). keyword-only.
|
||||
elevation: elevación de cámara en grados. 0 = órbita en el ecuador (vista lateral
|
||||
pura). Un juego top-down/isométrico suele querer algo de PICADO (mirar al
|
||||
personaje desde arriba): subir a ~15-30. keyword-only.
|
||||
size: lado en px de cada vista. None (default) usa la resolución nativa del modelo
|
||||
(576 para sv3d, 256 para zero123). Bajarlo reduce VRAM a costa de detalle.
|
||||
keyword-only.
|
||||
orbit_frames: SOLO sv3d. Número de frames del orbit que sintetiza SV3D_Conditioning.
|
||||
None (default) = `directions` (una imagen por dirección, salida directa). Subirlo
|
||||
(p. ej. 21, el nativo de SV3D) da una órbita más densa y consistente de la que el
|
||||
caller submuestrea las `directions` vistas (frame más cercano por azimuth); a más
|
||||
frames, más VRAM. Ignorado por zero123 (su batch es exactamente `directions`).
|
||||
keyword-only.
|
||||
seed: semilla del KSampler (0 = determinista; cambiar para variar la órbita).
|
||||
keyword-only.
|
||||
ckpt: nombre del checkpoint en el servidor. None (default) usa el del modelo
|
||||
(`sv3d_p.safetensors` / `stable_zero123.ckpt`). keyword-only.
|
||||
steps: pasos de sampling del KSampler. keyword-only.
|
||||
cfg: guidance scale. None (default) usa el del modelo (2.5 sv3d / 4.0 zero123).
|
||||
keyword-only.
|
||||
min_cfg: SOLO sv3d. CFG del primer frame para VideoLinearCFGGuidance (interpola de
|
||||
min_cfg a cfg a lo largo del orbit). keyword-only.
|
||||
sampler_name: algoritmo del KSampler. Default "euler". keyword-only.
|
||||
scheduler: scheduler del KSampler. Default "karras". keyword-only.
|
||||
filename_prefix: prefijo del archivo de salida del SaveImage. keyword-only.
|
||||
|
||||
Returns:
|
||||
dict en API format listo para `comfyui_submit_workflow`. Las claves son node_ids
|
||||
(string) y cada valor tiene class_type + inputs. SV3D devuelve 6 nodos
|
||||
(ImageOnlyCheckpointLoader, LoadImage, SV3D_Conditioning, VideoLinearCFGGuidance,
|
||||
KSampler, VAEDecode, SaveImage → los N frames del orbit). Zero123 devuelve
|
||||
ImageOnlyCheckpointLoader + LoadImage + StableZero123_Conditioning_Batched + KSampler
|
||||
+ VAEDecode + SaveImage (un batch de `directions` vistas). El frame i (i-ésima imagen
|
||||
del SaveImage, azimuth creciente desde la frontal) = dirección i de
|
||||
`directional_sprite_view_order(directions)`.
|
||||
|
||||
Raises:
|
||||
ValueError: si input_image está vacío, model no es "sv3d"/"zero123", o directions < 1.
|
||||
"""
|
||||
if not input_image or not input_image.strip():
|
||||
raise ValueError(
|
||||
"comfyui_build_directional_sprite_workflow: 'input_image' no puede estar vacío "
|
||||
"(el modelo 3D necesita la vista frontal en input/)."
|
||||
)
|
||||
if model not in _MODEL_CKPT:
|
||||
raise ValueError(
|
||||
"comfyui_build_directional_sprite_workflow: model "
|
||||
f"'{model}' inválido; usa uno de {sorted(_MODEL_CKPT)}."
|
||||
)
|
||||
directions = int(directions)
|
||||
if directions < 1:
|
||||
raise ValueError(
|
||||
"comfyui_build_directional_sprite_workflow: 'directions' debe ser >= 1 "
|
||||
f"(recibido {directions}); 4 u 8 son los valores canónicos de gamedev."
|
||||
)
|
||||
|
||||
input_image = input_image.strip()
|
||||
ckpt_name = ckpt or _MODEL_CKPT[model]
|
||||
px = int(size) if size is not None else _MODEL_NATIVE_SIZE[model]
|
||||
guidance = float(cfg) if cfg is not None else _MODEL_DEFAULT_CFG[model]
|
||||
|
||||
if model == "sv3d":
|
||||
return _build_sv3d(
|
||||
input_image, ckpt_name, directions, elevation, px, orbit_frames, seed,
|
||||
steps, guidance, min_cfg, sampler_name, scheduler, filename_prefix,
|
||||
)
|
||||
return _build_zero123(
|
||||
input_image, ckpt_name, directions, elevation, px, seed,
|
||||
steps, guidance, sampler_name, scheduler, filename_prefix,
|
||||
)
|
||||
|
||||
|
||||
def _build_sv3d(input_image, ckpt_name, directions, elevation, size, orbit_frames,
|
||||
seed, steps, cfg, min_cfg, sampler_name, scheduler, filename_prefix) -> dict:
|
||||
"""Workflow SV3D: 1 imagen frontal -> orbit de N vistas en una pasada.
|
||||
|
||||
SV3D reutiliza la cadena img2vid de Stable Video Diffusion: el checkpoint sv3d_p se
|
||||
carga con ImageOnlyCheckpointLoader (MODEL + CLIP_VISION + VAE), SV3D_Conditioning
|
||||
produce el conditioning del orbit y la latente, se muestrea como vídeo con guía CFG
|
||||
lineal y el SaveImage emite los `video_frames` frames del turntable.
|
||||
"""
|
||||
video_frames = int(orbit_frames) if orbit_frames else directions
|
||||
return {
|
||||
"1": {"class_type": "LoadImage", "inputs": {"image": input_image}},
|
||||
"2": {"class_type": "ImageOnlyCheckpointLoader", "inputs": {"ckpt_name": ckpt_name}},
|
||||
"3": {
|
||||
"class_type": "SV3D_Conditioning",
|
||||
"inputs": {
|
||||
"clip_vision": ["2", 1],
|
||||
"init_image": ["1", 0],
|
||||
"vae": ["2", 2],
|
||||
"width": size,
|
||||
"height": size,
|
||||
"video_frames": video_frames,
|
||||
"elevation": float(elevation),
|
||||
},
|
||||
},
|
||||
"4": {
|
||||
"class_type": "VideoLinearCFGGuidance",
|
||||
"inputs": {"model": ["2", 0], "min_cfg": float(min_cfg)},
|
||||
},
|
||||
"5": {
|
||||
"class_type": "KSampler",
|
||||
"inputs": {
|
||||
"seed": int(seed),
|
||||
"steps": int(steps),
|
||||
"cfg": float(cfg),
|
||||
"sampler_name": sampler_name,
|
||||
"scheduler": scheduler,
|
||||
"denoise": 1.0,
|
||||
"model": ["4", 0],
|
||||
"positive": ["3", 0],
|
||||
"negative": ["3", 1],
|
||||
"latent_image": ["3", 2],
|
||||
},
|
||||
},
|
||||
"6": {"class_type": "VAEDecode", "inputs": {"samples": ["5", 0], "vae": ["2", 2]}},
|
||||
"7": {
|
||||
"class_type": "SaveImage",
|
||||
"inputs": {"images": ["6", 0], "filename_prefix": filename_prefix},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _build_zero123(input_image, ckpt_name, directions, elevation, size, seed,
|
||||
steps, cfg, sampler_name, scheduler, filename_prefix) -> dict:
|
||||
"""Workflow Stable Zero123: batch de N vistas a azimuths equiespaciados.
|
||||
|
||||
StableZero123_Conditioning_Batched genera `directions` vistas en una pasada, empezando
|
||||
en azimuth 0 (la frontal) y avanzando 360/N grados por vista. El KSampler las muestrea
|
||||
como un batch y el SaveImage las emite en orden de azimuth creciente.
|
||||
"""
|
||||
increment = 360.0 / directions
|
||||
return {
|
||||
"1": {"class_type": "LoadImage", "inputs": {"image": input_image}},
|
||||
"2": {"class_type": "ImageOnlyCheckpointLoader", "inputs": {"ckpt_name": ckpt_name}},
|
||||
"3": {
|
||||
"class_type": "StableZero123_Conditioning_Batched",
|
||||
"inputs": {
|
||||
"clip_vision": ["2", 1],
|
||||
"init_image": ["1", 0],
|
||||
"vae": ["2", 2],
|
||||
"width": size,
|
||||
"height": size,
|
||||
"batch_size": directions,
|
||||
"elevation": float(elevation),
|
||||
"azimuth": 0.0,
|
||||
"elevation_batch_increment": 0.0,
|
||||
"azimuth_batch_increment": increment,
|
||||
},
|
||||
},
|
||||
"4": {
|
||||
"class_type": "KSampler",
|
||||
"inputs": {
|
||||
"seed": int(seed),
|
||||
"steps": int(steps),
|
||||
"cfg": float(cfg),
|
||||
"sampler_name": sampler_name,
|
||||
"scheduler": scheduler,
|
||||
"denoise": 1.0,
|
||||
"model": ["2", 0],
|
||||
"positive": ["3", 0],
|
||||
"negative": ["3", 1],
|
||||
"latent_image": ["3", 2],
|
||||
},
|
||||
},
|
||||
"5": {"class_type": "VAEDecode", "inputs": {"samples": ["4", 0], "vae": ["2", 2]}},
|
||||
"6": {
|
||||
"class_type": "SaveImage",
|
||||
"inputs": {"images": ["5", 0], "filename_prefix": filename_prefix},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import json
|
||||
|
||||
wf = comfyui_build_directional_sprite_workflow(
|
||||
"goblin_front.png",
|
||||
directions=8,
|
||||
model="sv3d",
|
||||
elevation=15.0,
|
||||
seed=7,
|
||||
)
|
||||
print(json.dumps({
|
||||
"nodes": list(wf),
|
||||
"classes": sorted({n["class_type"] for n in wf.values()}),
|
||||
"video_frames": wf["3"]["inputs"]["video_frames"],
|
||||
"size": wf["3"]["inputs"]["width"],
|
||||
"view_order": directional_sprite_view_order(8),
|
||||
}, indent=2))
|
||||
Reference in New Issue
Block a user