9508fff282
Mapa de ambas estructuras (output/ de ComfyUI + res://assets/ de Godot 4), tabla tipo-de-asset → carpeta destino → import settings clave, y propuesta de pipeline export_asset_to_godot que compone helpers atómicos + reimport headless (gap confirmado: 0 funciones godot en el registry). Documenta el gotcha de Godot 4: el filtro Nearest del pixelart se setea global (default_texture_filter=0) o por override, no por .import por defecto. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
312 lines
19 KiB
Markdown
312 lines
19 KiB
Markdown
# Integración ComfyUI → Godot: puente de assets ordenado y gestionado
|
||
|
||
Diseño del puente entre la generación de assets en **ComfyUI** (`~/ComfyUI/`) y su consumo en
|
||
proyectos **Godot 4** (`~/gamedev/projects/`). El objetivo es que un asset generado (sprite,
|
||
tileset, pixelart, spritesheet VFX, audio, malla 3D GLB) viaje a un proyecto Godot a la carpeta
|
||
correcta, con los *import settings* adecuados a su tipo, sin romper los archivos `.import`
|
||
existentes ni desordenar el proyecto.
|
||
|
||
- **Fecha:** 26/06/2026
|
||
- **Alcance:** mapa de ambas estructuras + convención de carpetas destino + tabla
|
||
tipo-de-asset → carpeta Godot → import settings + propuesta de función(es) del registry para
|
||
automatizar el traslado. Es documento de diseño; la implementación se delega a `fn-constructor`.
|
||
- **Fuera de alcance:** generación (GPU, otro agente), implementación de las funciones, descarga
|
||
de modelos.
|
||
|
||
Documento hermano del catálogo de capacidades de generación: `~/ComfyUI/CAPABILITIES.md` (fuera
|
||
del repo) y `docs/capabilities/comfyui-overview.md` (versionable). Este documento añade la pata
|
||
que faltaba: **qué pasa con el asset una vez generado**.
|
||
|
||
---
|
||
|
||
## 1. Mapa de ComfyUI (origen de los assets)
|
||
|
||
ComfyUI vive en `~/ComfyUI/` (clon del repo, no versionado en `fn_registry`). Las carpetas
|
||
relevantes para el puente son:
|
||
|
||
```
|
||
~/ComfyUI/
|
||
├── output/ # ★ AQUÍ caen TODOS los assets generados
|
||
│ ├── *.png # imágenes (sprites, pixelart, tiles) — ~150 hoy
|
||
│ ├── *.webp # spritesheets animados / vídeo corto (SaveAnimatedWEBP)
|
||
│ ├── *.mp4 # vídeo (SaveVideo)
|
||
│ ├── *.glb / *.obj / *.ply # mallas 3D (SaveGLB)
|
||
│ └── 3D/ # subcarpeta de salidas 3D (texturizadas, multi-vista)
|
||
├── input/ # imágenes de entrada (img2img, image-to-3d)
|
||
├── models/ + /mnt/2tb/comfyui_models/ # checkpoints/loras/vae/... (vía extra_model_paths.yaml)
|
||
├── user/default/workflows/ # grafos UI, agrupados por capacidad (01_txt2img, 02_img2img, …)
|
||
├── skills_library/<slug>/ # recetas de estilo reproducibles (recipe.json + samples)
|
||
└── custom_nodes/ # nodos extra (PixelArt-Detector, IPAdapter, Hunyuan3D, …)
|
||
```
|
||
|
||
- **`output/` es el único punto de origen del puente.** Cada nodo `Save*` escribe ahí con un
|
||
patrón de nombre `<prefijo>_NNNNN_.<ext>` (sufijo numérico de 5 dígitos). Ejemplos reales en
|
||
disco hoy: `bench160_3000_00001_.png`, `svd_motion_hi_00001_.webp`, `3d_robot_mesh_00001_.glb`,
|
||
`output/3D/character_clean_textured_00001_.glb`.
|
||
- **Modelos centralizados** en `/mnt/2tb/comfyui_models/` vía `extra_model_paths.yaml`
|
||
(`is_default: true`). No intervienen en el puente (son insumo de generación, no asset de salida).
|
||
- **Tipos que generamos hoy** (recuento real del `output/`): `png` (mayoría), `glb` (15), `mp4`
|
||
(6), `webp` (2), `obj`/`ply` (formatos 3D crudos). **Audio aún no se genera en ComfyUI** (no hay
|
||
`wav`/`ogg`/`mp3` en `output/`); el plan de audio existe como report aparte. El puente lo
|
||
contempla igualmente porque Godot lo consume y porque el audio puede llegar de otra fuente.
|
||
- Catálogo navegable de qué sabemos generar y con qué función/grafo: `~/ComfyUI/CAPABILITIES.md`.
|
||
|
||
---
|
||
|
||
## 2. Mapa de Godot (destino de los assets)
|
||
|
||
Los proyectos viven en `~/gamedev/projects/` (fuera de `fn_registry`, igual que ComfyUI). Hay una
|
||
**biblioteca maestra** de assets en `~/gamedev/assets/` y, por proyecto, una copia local de los
|
||
assets que usa. Proyectos reales localizados:
|
||
|
||
- `~/gamedev/projects/crossy_road/` (juego "LizardRoad", Godot 4.7, móvil portrait 640×1280)
|
||
- `~/gamedev/projects/risk/` (pilotaje previo)
|
||
|
||
Estructura canónica de un proyecto Godot 4 (tomada de `crossy_road`, que es el patrón real):
|
||
|
||
```
|
||
~/gamedev/projects/crossy_road/
|
||
├── project.godot # config del proyecto (nombre, autoloads, rendering, display)
|
||
├── .godot/ # ★ caché de import (regenerable) — NUNCA se versiona ni se toca a mano
|
||
│ └── imported/ # binarios .ctex/.sample/... generados desde los .import
|
||
├── assets/ # assets del proyecto (copia local de la biblioteca)
|
||
│ ├── biomas/ agua.png + agua.png.import (par obligatorio por cada asset)
|
||
│ ├── kenney/ packs CC0
|
||
│ ├── external/ otros CC0
|
||
│ └── audio/sfx/ step.wav + step.wav.import
|
||
├── scenes/ *.tscn (escenas)
|
||
├── scripts/ *.gd + *.gd.uid
|
||
├── addons/godot_ai/ addon del MCP (control del editor desde Claude)
|
||
└── export_presets.cfg / android/ (build móvil)
|
||
```
|
||
|
||
### Cómo importa Godot 4 cada asset — el archivo `.import`
|
||
|
||
**Regla de oro:** en Godot, **cada asset es un par `<archivo>` + `<archivo>.import`**. El
|
||
`.import` es un INI que declara el `importer`, el `type` de recurso resultante, un `uid://`
|
||
estable y los parámetros de importación. Godot genera el binario importado en `.godot/imported/`
|
||
y lo regenera al reimportar. **Romper o desincronizar el `.import` = el asset no carga o se
|
||
reimporta con settings por defecto.** Por eso el puente debe respetar este par.
|
||
|
||
Ejemplos reales de `.import` por tipo (de `crossy_road`):
|
||
|
||
**Textura** (`importer="texture"`, `type="CompressedTexture2D"`):
|
||
```ini
|
||
[remap]
|
||
importer="texture"
|
||
type="CompressedTexture2D"
|
||
uid="uid://cedkstexk3ciw"
|
||
path="res://.godot/imported/agua.png-<hash>.ctex"
|
||
[params]
|
||
compress/mode=0 ; 0=Lossless (correcto para pixelart), 2=VRAM
|
||
mipmaps/generate=false ; off para 2D/pixelart
|
||
process/fix_alpha_border=true
|
||
detect_3d/compress_to=1
|
||
```
|
||
|
||
**Audio WAV** (`importer="wav"`, `type="AudioStreamWAV"`):
|
||
```ini
|
||
[remap]
|
||
importer="wav"
|
||
type="AudioStreamWAV"
|
||
[params]
|
||
edit/loop_mode=0 ; 0=Disabled (sfx), 1=Forward (música en bucle)
|
||
compress/mode=2
|
||
force/mono=false
|
||
```
|
||
|
||
**Malla 3D GLB** (`importer="scene"`, produce `PackedScene`): el GLB se importa como escena glTF;
|
||
sus opciones (escala raíz, generación de colisión, manejo de materiales/texturas) viven en el
|
||
`.import` de tipo `scene`. No había ninguno en los proyectos actuales (son 2D), pero es el
|
||
importer canónico de Godot para `.glb`/`.gltf`.
|
||
|
||
### El gotcha del filtro de textura (Godot 4 ≠ Godot 3)
|
||
|
||
En **Godot 4** el filtro Nearest/Linear **no es un campo del `.import` por defecto** (en Godot 3
|
||
sí lo era). El filtro se controla de dos formas:
|
||
|
||
1. **Global del proyecto (recomendado, KISS):** Project Settings → Rendering → Textures →
|
||
Canvas Textures → Default Texture Filter → **Nearest**. En `project.godot` es la clave
|
||
`rendering/textures/canvas_textures/default_texture_filter=0` (0 = Nearest). Tras cambiarlo hay
|
||
que **reimportar** las texturas.
|
||
2. **Override por asset:** en el panel Import de una textura concreta, Texture > Filter →
|
||
`Nearest` (override del default). Esto sí escribe la opción en su `.import`.
|
||
|
||
> **Hallazgo (read-only) en `crossy_road`:** su `project.godot` **no** declara
|
||
> `default_texture_filter`, así que usa el default **Linear** → cualquier asset pixelart se ve
|
||
> **borroso** (sus `biomas/*.png` tienen `mipmaps/generate=false` y `compress/mode=0`, correcto,
|
||
> pero les falta el Nearest). Para un proyecto pixelart, setear el global una vez es el primer
|
||
> paso del puente. (No se modificó nada; queda anotado para el constructor.)
|
||
|
||
---
|
||
|
||
## 3. El puente: convención de carpetas destino en Godot
|
||
|
||
Convención propuesta para la copia local de assets dentro de cada proyecto, alineada con lo que
|
||
ya existe (`assets/audio/sfx/`, `assets/kenney/`) y extendida a todos los tipos que generamos:
|
||
|
||
```
|
||
res://assets/
|
||
├── sprites/ # PNG individuales: personajes, props, objetos sueltos
|
||
├── tilesets/ # PNG de tiles + el recurso TileSet .tres derivado
|
||
├── vfx/ # spritesheets / animaciones (WEBP, o PNG en grid) → SpriteFrames/AtlasTexture
|
||
├── audio/
|
||
│ ├── sfx/ # efectos cortos (WAV) — loop OFF
|
||
│ └── music/ # música (OGG/WAV) — loop ON
|
||
├── models/ # mallas 3D GLB (+ sus texturas/materiales)
|
||
└── _generated/ # opcional: zona de aterrizaje de lo recién traído de ComfyUI, antes de clasificar
|
||
```
|
||
|
||
- **`pixelart` no es una carpeta**, es un *atributo transversal*: un sprite, tile o VFX puede ser
|
||
pixelart. Lo que cambia es el import setting (Nearest + Lossless), no la ubicación. El pixelart
|
||
va a `sprites/` / `tilesets/` / `vfx/` según su rol, y el proyecto entero se marca Nearest a
|
||
nivel global cuando es un juego pixelart.
|
||
- **Biblioteca maestra vs copia local:** `~/gamedev/assets/` es la fuente ordenada; cada proyecto
|
||
guarda dentro su copia (`<proyecto>/assets/...`) porque Godot referencia por rutas `res://`
|
||
relativas a la raíz del proyecto. El puente copia de `~/ComfyUI/output/` → bien a la biblioteca
|
||
maestra, bien directo a un proyecto.
|
||
|
||
### Naming y versionado
|
||
|
||
- ComfyUI nombra `<prefijo>_NNNNN_.<ext>` (p. ej. `svd_motion_hi_00001_.webp`). Al exportar,
|
||
**renombrar a snake_case limpio y semántico** quitando el sufijo `_NNNNN_` y los guiones bajos
|
||
de cola: `svd_motion_hi_00001_.webp` → `explosion_loop.webp`.
|
||
- Sin espacios ni mayúsculas en nombres de archivo (consistencia con `res://` y multiplataforma).
|
||
- Versionado: sufijo opcional `_vN` cuando se itera un asset que ya está en uso
|
||
(`hero_idle.png` → `hero_idle_v2.png`), para no pisar el `uid://` del que ya referencian escenas.
|
||
**Nunca** sobrescribir un asset en uso sin querer: cambia su contenido pero conserva su `uid`,
|
||
lo que puede ser deseable (hot-swap) o no (regresión visual). Decisión explícita por asset.
|
||
|
||
---
|
||
|
||
## 4. Tabla: tipo de asset ComfyUI → carpeta Godot → import settings clave
|
||
|
||
| Tipo (ComfyUI) | Ext salida | Carpeta Godot destino | Importer Godot (`type`) | Import settings clave |
|
||
|---|---|---|---|---|
|
||
| **Sprite individual** | `.png` | `res://assets/sprites/` | `texture` → `CompressedTexture2D` | `compress/mode=0` (Lossless); `mipmaps/generate=false`; repeat off; filtro = default del proyecto (Linear si arte vectorial, Nearest si pixelart) |
|
||
| **Pixelart** (sprite/tile) | `.png` | `sprites/` o `tilesets/` (según rol) | `texture` → `CompressedTexture2D` | **CRÍTICO: Nearest** (global `default_texture_filter=0` o override por asset); `compress/mode=0` Lossless; `mipmaps/generate=false`; repeat off |
|
||
| **Tileset** | `.png` | `res://assets/tilesets/` | `texture` + recurso **`TileSet` (.tres)** manual/script | Nearest (suele ser pixelart); además crear `TileSet` con tamaño de celda (region grid) — Godot **no** deriva el TileSet automáticamente del PNG |
|
||
| **VFX spritesheet** | `.webp` / `.png` (grid) | `res://assets/vfx/` | `texture` → **`SpriteFrames`** (AnimatedSprite2D) o **`AtlasTexture`** por frame | Nearest si pixelart; definir `hframes`/`vframes` o regiones por frame; Godot **no** convierte un sheet a `SpriteFrames` solo: se hace en editor o por script de import |
|
||
| **Audio SFX** | `.wav` | `res://assets/audio/sfx/` | `wav` → `AudioStreamWAV` | `edit/loop_mode=0` (Disabled); `compress/mode` según peso |
|
||
| **Audio música** | `.ogg` / `.wav` | `res://assets/audio/music/` | `oggvorbisstr` (OGG) / `wav` | **loop ON** (`edit/loop_mode=1` en WAV; `loop=true` en OGG) |
|
||
| **Malla 3D** | `.glb` (preferido) | `res://assets/models/` | `scene` → `PackedScene` (glTF) | escala raíz coherente; generar colisión si se necesita; si la textura es pixelart, poner el material en Nearest; preferir `.glb` sobre `.obj`/`.ply` (los lleva embebidos) |
|
||
|
||
Notas que la tabla condensa:
|
||
|
||
- **Godot no automatiza dos conversiones clave:** (1) PNG de tiles → recurso `TileSet`, y (2)
|
||
spritesheet → `SpriteFrames`. Ambas requieren un paso manual en editor o un script de import
|
||
(o un addon, p. ej. el TexturePacker importer / aseprite-importers). El puente puede generar el
|
||
`.tres` por script a partir de la geometría conocida del grid.
|
||
- **WEBP animado (SVD):** nuestros `svd_*.webp` son clips de Stable Video Diffusion. Para usarlos
|
||
como animación 2D en Godot, lo robusto es **descomponer en frames** (PNG en grid) y construir
|
||
`SpriteFrames`, no cargar el WEBP animado tal cual.
|
||
- **3D pixelart/low-poly:** el GLB importa como escena; cuidar que el material no aplique filtro
|
||
Linear a una textura pixelart (se setea en el material/import de la malla).
|
||
|
||
---
|
||
|
||
## 5. Propuesta de función(es) del registry (diseño, NO implementación)
|
||
|
||
Búsqueda en el registry: `mcp__registry__fn_search query="godot export asset import"` → **0
|
||
resultados**. Gap limpio. Hoy llevar un asset de ComfyUI a Godot es manual (copiar + abrir editor
|
||
+ tocar import a mano). Alineado con la doctrina del registry (registry-first + crecer por
|
||
composición de helpers atómicos, issue 0087), la propuesta es **un pipeline one-shot que compone
|
||
helpers pequeños**:
|
||
|
||
### Pipeline principal
|
||
|
||
```
|
||
export_asset_to_godot_py_pipelines (impura, kind=pipeline, tag de grupo: godot, comfyui)
|
||
|
||
Firma:
|
||
export_asset_to_godot(
|
||
asset_path: str, # ruta en ~/ComfyUI/output/ (o cualquier archivo)
|
||
kind: str, # "sprite" | "pixelart" | "tileset" | "vfx" | "sfx" | "music" | "model"
|
||
godot_project: str, # ruta raíz del proyecto Godot destino
|
||
dest_name: str = "", # nombre limpio destino (default: snake_case del origen sin _NNNNN_)
|
||
pixelart: bool = False, # fuerza Nearest + Lossless en la textura
|
||
loop: bool = False, # audio en bucle (música)
|
||
reimport: bool = True, # lanza reimport headless al final
|
||
) -> dict # {dest_res_path, import_written, reimported, warnings[]}
|
||
```
|
||
|
||
Comportamiento:
|
||
1. Resolver la carpeta destino por `kind` (tabla §4) dentro de `res://assets/`.
|
||
2. Copiar el archivo con nombre limpio (snake_case, sin sufijo `_NNNNN_`).
|
||
3. Escribir/asegurar el `.import` adecuado al tipo (texture/wav/scene) con los settings clave.
|
||
4. Si `pixelart=True`, además asegurar el global del proyecto
|
||
(`default_texture_filter=0` en `project.godot`) o el override por asset.
|
||
5. Si `reimport=True`, lanzar reimport headless para que Godot regenere `.godot/imported/`.
|
||
6. Devolver el `res://` final + avisos (p. ej. "tileset copiado pero falta crear el TileSet .tres",
|
||
"WEBP animado: descomponer en frames antes de SpriteFrames").
|
||
|
||
### Helpers atómicos que compone (delegar a `fn-constructor` en paralelo)
|
||
|
||
| Helper (id tentativo) | Pureza | Qué hace |
|
||
|---|---|---|
|
||
| `godot_asset_dest_dir_py_core` | pura | mapea `kind` → subdir de `res://assets/` (tabla §4) |
|
||
| `godot_clean_asset_name_py_core` | pura | quita sufijo `_NNNNN_`, normaliza a snake_case, sin espacios |
|
||
| `godot_write_texture_import_py_infra` | impura | escribe `.import` de textura (compress/mipmaps/filter) preservando `uid` si ya existe |
|
||
| `godot_write_audio_import_py_infra` | impura | escribe `.import` de audio (loop_mode) |
|
||
| `godot_ensure_pixelart_project_py_infra` | impura | setea `default_texture_filter=0` en `project.godot` (idempotente) |
|
||
| `godot_reimport_headless_bash_infra` | impura | `godot --headless --path <proj> --import` para regenerar la caché |
|
||
| `godot_build_spriteframes_tres_py_infra` | impura | genera `.tres` de `SpriteFrames`/`AtlasTexture` a partir de un sheet + geometría de grid |
|
||
| `godot_build_tileset_tres_py_infra` | impura | genera `.tres` de `TileSet` a partir de un PNG + tamaño de celda |
|
||
|
||
### DoD esbozado (según `dod_quality.md`)
|
||
|
||
- **Golden:** `export_asset_to_godot("~/ComfyUI/output/hero.png", "pixelart",
|
||
"~/gamedev/projects/crossy_road")` deja `res://assets/sprites/hero.png` + su `.import` con
|
||
Nearest + Lossless, el `project.godot` con `default_texture_filter=0`, y la reimport headless
|
||
sale con exit 0; assert: el `.import` contiene el override Nearest y el binario aparece en
|
||
`.godot/imported/`.
|
||
- **Edge 1 (audio música):** `kind="music", loop=True` → `.import` con `edit/loop_mode=1`.
|
||
- **Edge 2 (GLB 3D):** `kind="model"` copia a `res://assets/models/` y la escena glTF carga
|
||
(reimport sin error).
|
||
- **Edge 3 (tileset):** copia el PNG y **avisa** que falta el `.tres` del TileSet (o lo genera con
|
||
`godot_build_tileset_tres`), sin romper nada.
|
||
- **Error path:** `kind` desconocido → error claro sin copiar nada; `godot_project` sin
|
||
`project.godot` → aborta y no escribe; nunca sobrescribe un asset en uso sin `dest_name`
|
||
explícito.
|
||
- **Idempotencia:** re-exportar el mismo asset preserva el `uid://` existente (no rompe las
|
||
referencias de las escenas que ya lo usan).
|
||
|
||
### ¿Automatizar el import vía MCP godot-ai?
|
||
|
||
El addon `godot_ai` (MCP, server `127.0.0.1:8000/mcp`) está presente en `crossy_road` y `risk`.
|
||
Con el editor abierto, `filesystem_manage` op `reimport` (recibe `paths`) puede forzar reimport
|
||
desde el editor vivo. **Pero** la convención (`CONVENTIONS.md` de gamedev) ya observa que el
|
||
reimport por MCP suele ser **innecesario**: `project_run` recompila desde disco al arrancar, y un
|
||
`godot --headless --import` regenera la caché sin editor abierto. **Recomendación:** el puente usa
|
||
**reimport headless por CLI** (`godot_reimport_headless_bash_infra`) como mecanismo por defecto
|
||
(no requiere editor abierto ni MCP), y deja el MCP como opción cuando el editor ya está vivo y se
|
||
quiere refrescar en caliente.
|
||
|
||
---
|
||
|
||
## 6. Resumen operativo (TL;DR)
|
||
|
||
1. **Origen:** todo asset generado cae en `~/ComfyUI/output/` (PNG/WEBP/MP4/GLB).
|
||
2. **Destino:** `res://assets/{sprites,tilesets,vfx,audio/{sfx,music},models}/` dentro del
|
||
proyecto Godot; cada asset es un par `archivo` + `archivo.import`.
|
||
3. **Import por tipo:** textura (Lossless, mipmaps off, **Nearest si pixelart**), audio (loop
|
||
off=sfx / on=música), GLB (escena glTF). Tabla §4.
|
||
4. **Gotcha Godot 4:** el Nearest pixelart se setea **global** (`default_texture_filter=0`) o por
|
||
override de asset — **no** es un flag por defecto del `.import`. `crossy_road` hoy está en
|
||
Linear (pixelart borroso): anotado.
|
||
5. **Godot no automatiza** PNG→TileSet ni sheet→SpriteFrames: paso manual o script de import.
|
||
6. **Automatización:** pipeline `export_asset_to_godot` (gap confirmado, 0 funciones hoy) que
|
||
compone helpers atómicos + reimport headless. Diseño en §5; implementación se delega a
|
||
`fn-constructor`.
|
||
|
||
## Fuentes
|
||
|
||
- ComfyUI: `~/ComfyUI/CAPABILITIES.md`, `~/ComfyUI/extra_model_paths.yaml`, listado de
|
||
`~/ComfyUI/output/` (read-only).
|
||
- Godot: `~/gamedev/CONVENTIONS.md`, `~/gamedev/README.md`,
|
||
`~/gamedev/projects/crossy_road/project.godot` y sus `*.import` (read-only).
|
||
- Godot 4 pixel art texture filter: [GDQuest — Setting up pixel art graphics in Godot 4](https://www.gdquest.com/library/pixel_art_setup_godot4/),
|
||
[Godot Forum — How to import pixel art in Godot 4](https://forum.godotengine.org/t/how-to-import-pixel-art-in-godot-4/7105).
|
||
- Godot 4 sprite sheets: [Godot docs — 2D sprite animation](https://docs.godotengine.org/en/stable/tutorials/2d/2d_sprite_animation.html),
|
||
[godot-4-aseprite-importers](https://github.com/nklbdev/godot-4-aseprite-importers).
|