feat(gamedev): ronda 1 — pixelize + luma→alpha + export-godot (grupo gamedev)
Tres funciones CPU-only del lote gamedev 2D + 2 helpers puros + grupo de capacidad: - comfyui_pixelize_image_py_ml (impure): Fase 2 pixelart — downscale nearest + cuantizacion a N colores / paleta fija (game-boy/pico-8/nes) + re-upscale nearest. - comfyui_matting_luma_to_alpha_py_ml (impure): frame VFX sobre negro -> RGBA por luminancia ponderada (translucidos con additive blend). - comfyui_export_asset_to_godot_py_pipelines (impure): puente ComfyUI -> Godot 4 — copia a res://assets/<dir> por kind + .import por tipo + filtro Nearest si pixelart + reimport headless best-effort. Compone los 2 helpers puros. - godot_map_asset_dir_py_core, godot_clean_asset_name_py_core (pure): nucleos reutilizables del pipeline. - docs/capabilities/gamedev-2d.md + INDEX: grupo nuevo gamedev. Tests 33/33 verdes (offline PIL/numpy). Golden real verificado: asset de ~/ComfyUI/output -> /tmp/godot_test_proj con .import correcto y reimport headless real de Godot 4.7. Sin GPU, sin red, sin tocar proyectos del usuario. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
---
|
||||
name: comfyui_export_asset_to_godot
|
||||
kind: pipeline
|
||||
lang: py
|
||||
domain: pipelines
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "def comfyui_export_asset_to_godot(asset_path: str, kind: str, godot_project: str, *, name: str | None = None, reimport: bool = True, godot_bin: str | None = None) -> dict"
|
||||
description: "Puente ComfyUI -> Godot: copia un asset generado a la subcarpeta correcta de un proyecto Godot 4 (res://assets/{sprites,tilesets,vfx,audio/{sfx,music},models}/ segun kind), escribe el .import adecuado al tipo (textura Lossless+mipmaps off / audio loop on-off / escena glTF), asegura el filtro Nearest del proyecto si es pixelart (default_texture_filter=0 en project.godot), y lanza reimport headless si el binario de Godot esta disponible. Compone godot_map_asset_dir + godot_clean_asset_name. NO toca ningun proyecto salvo el que se le pasa; idempotente (preserva uid existente). Devuelve {ok, dest_res_path, dest_abs_path, import_path, import_written, pixelart_filter_set, reimported, warnings, error}. Impuro: disco + subprocess."
|
||||
tags: [godot, gamedev, comfyui, pipelines, export, import, launcher]
|
||||
uses_functions: [godot_map_asset_dir_py_core, godot_clean_asset_name_py_core]
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_py_core"
|
||||
imports: [godot_map_asset_dir_py_core, godot_clean_asset_name_py_core]
|
||||
params:
|
||||
- name: asset_path
|
||||
desc: "ruta del asset de origen (p.ej. en ~/ComfyUI/output/)."
|
||||
- name: kind
|
||||
desc: "tipo de asset: 'sprite', 'pixelart', 'tileset', 'vfx', 'sfx', 'music' o 'model'."
|
||||
- name: godot_project
|
||||
desc: "ruta raiz del proyecto Godot destino (debe contener project.godot)."
|
||||
- name: name
|
||||
desc: "nombre base deseado para el archivo destino (sin extension); None lo deriva del origen (snake_case sin _NNNNN_). keyword-only."
|
||||
- name: reimport
|
||||
desc: "si True intenta reimport headless con el binario de Godot. keyword-only."
|
||||
- name: godot_bin
|
||||
desc: "ruta del binario de Godot; None autodetecta (PATH y rutas conocidas como ~/godot/Godot_v4.7-stable_linux.x86_64). keyword-only."
|
||||
output: "dict con ok (bool), dest_res_path (res://...), dest_abs_path, import_path, import_written (bool), pixelart_filter_set (bool), reimported (bool), warnings (list[str]), error (str)."
|
||||
tested: true
|
||||
tests: [test_golden_pixelart, test_edge_music_loop_on, test_edge_sfx_loop_off, test_edge_model_glb_scene, test_edge_tileset_warns, test_idempotent_preserves_uid, test_error_missing_asset, test_error_bad_kind, test_error_not_a_godot_project, test_godot_cli_absent_leaves_import]
|
||||
test_file_path: "python/functions/pipelines/comfyui_export_asset_to_godot_test.py"
|
||||
file_path: "python/functions/pipelines/comfyui_export_asset_to_godot.py"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```python
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.join(os.environ["HOME"], "fn_registry", "python", "functions"))
|
||||
from pipelines.comfyui_export_asset_to_godot import comfyui_export_asset_to_godot
|
||||
|
||||
# Pixelart -> sprites/ con Nearest global + reimport headless automatico
|
||||
res = comfyui_export_asset_to_godot(
|
||||
os.path.expanduser("~/ComfyUI/output/hero_00001_.png"),
|
||||
"pixelart",
|
||||
os.path.expanduser("~/gamedev/projects/crossy_road"),
|
||||
)
|
||||
# {'ok': True, 'dest_res_path': 'res://assets/sprites/hero.png',
|
||||
# 'import_written': True, 'pixelart_filter_set': True, 'reimported': True, ...}
|
||||
|
||||
# Musica con loop ON
|
||||
comfyui_export_asset_to_godot("/tmp/theme.ogg", "music", "/tmp/godot_proj")
|
||||
```
|
||||
|
||||
## Cuando usarla
|
||||
|
||||
Para llevar un asset recien generado en ComfyUI a un proyecto Godot sin tocar el
|
||||
import a mano: resuelve carpeta + escribe el `.import` por tipo + arregla el filtro
|
||||
pixelart + reimporta. Es el ultimo paso del flujo gen -> (pixelize / matting) ->
|
||||
export. Si solo quieres el mapeo o el nombre limpio sin copiar, usa los helpers
|
||||
`godot_map_asset_dir` / `godot_clean_asset_name` directamente.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- **Seguridad**: solo escribe en el `godot_project` que se le pasa. Aborta con
|
||||
`ok=False` si ese directorio no tiene `project.godot`. Nunca toca otros proyectos.
|
||||
- **Filtro Nearest (Godot 4)**: NO es un campo del `.import` por defecto; se setea
|
||||
global en `project.godot` (`default_texture_filter=0`). Con `kind="pixelart"` la
|
||||
función lo asegura (idempotente). Para sprites no-pixelart deja el filtro por
|
||||
defecto del proyecto.
|
||||
- **Godot no deriva TileSet ni SpriteFrames**: para `tileset`/`vfx` copia la textura
|
||||
y **avisa** (en `warnings`) que el `.tres` hay que crearlo en editor o por script.
|
||||
- **reimport best-effort**: si no encuentra el binario de Godot, deja el `.import`
|
||||
escrito y lo anota en `warnings` (`reimported=False`) — no falla. Autodetecta en
|
||||
PATH y en `~/godot/Godot_v4.7-stable_linux.x86_64`; override con `godot_bin`.
|
||||
- **Idempotente**: re-exportar preserva el `uid://` de un `.import` ya existente (no
|
||||
rompe las referencias de escenas que ya usan el asset).
|
||||
- **WEBP animado (SVD)**: para usarlo como animación en Godot, descomponer en frames
|
||||
PNG antes; este pipeline lo copia como textura tal cual (no lo descompone).
|
||||
- El `.import` escrito es el mínimo correcto por tipo; Godot completa los campos que
|
||||
falten en el primer reimport.
|
||||
@@ -0,0 +1,311 @@
|
||||
"""comfyui_export_asset_to_godot — lleva un asset generado a un proyecto Godot 4.
|
||||
|
||||
Pipeline del puente ComfyUI -> Godot (docs/comfyui-godot-integration.md): copia un
|
||||
asset salido de `~/ComfyUI/output/` a la subcarpeta correcta de un proyecto Godot
|
||||
(`res://assets/{sprites,tilesets,vfx,audio/{sfx,music},models}/` segun `kind`),
|
||||
escribe el archivo `.import` adecuado al tipo (textura / audio / escena glTF) con
|
||||
los settings clave, asegura el filtro Nearest del proyecto si el asset es pixelart,
|
||||
y lanza un reimport headless si el binario de Godot esta disponible.
|
||||
|
||||
Compone las funciones puras del registry:
|
||||
godot_map_asset_dir_py_core (kind -> subcarpeta)
|
||||
godot_clean_asset_name_py_core (nombre de origen -> snake_case seguro)
|
||||
|
||||
Y orquesta el I/O especifico del puente (copia, .import, project.godot, reimport).
|
||||
|
||||
Seguridad: NO toca ningun proyecto salvo el `godot_project` que se le pasa
|
||||
explicitamente. Aborta si ese directorio no es un proyecto Godot (sin
|
||||
`project.godot`). Idempotente: preserva el `uid://` de un `.import` ya existente
|
||||
(no rompe referencias de escenas en uso). Impuro: lee/escribe disco + subprocess
|
||||
(reimport).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
_FUNCTIONS_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
if _FUNCTIONS_ROOT not in sys.path:
|
||||
sys.path.insert(0, _FUNCTIONS_ROOT)
|
||||
|
||||
from core.godot_clean_asset_name import godot_clean_asset_name
|
||||
from core.godot_map_asset_dir import godot_map_asset_dir
|
||||
|
||||
# Tipos que importan como textura 2D.
|
||||
_TEXTURE_KINDS = {"sprite", "pixelart", "tileset", "vfx"}
|
||||
# Candidatos de binario Godot 4 (autodeteccion).
|
||||
_GODOT_CANDIDATES = [
|
||||
"godot", "godot4",
|
||||
os.path.expanduser("~/godot/Godot_v4.7-stable_linux.x86_64"),
|
||||
]
|
||||
_UID_RE = re.compile(r'^uid="(uid://[^"]+)"', re.MULTILINE)
|
||||
|
||||
|
||||
def _find_godot_bin(godot_bin: str | None) -> str | None:
|
||||
"""Resuelve el binario de Godot: arg explicito -> PATH -> rutas conocidas."""
|
||||
if godot_bin:
|
||||
return godot_bin if (os.path.isfile(godot_bin) or shutil.which(godot_bin)) else None
|
||||
for cand in _GODOT_CANDIDATES:
|
||||
found = cand if os.path.isfile(cand) else shutil.which(cand)
|
||||
if found:
|
||||
return found
|
||||
return None
|
||||
|
||||
|
||||
def _existing_uid(import_path: str) -> str | None:
|
||||
"""Lee el uid:// de un .import ya presente (idempotencia), si lo hay."""
|
||||
if not os.path.isfile(import_path):
|
||||
return None
|
||||
try:
|
||||
with open(import_path, encoding="utf-8") as fh:
|
||||
m = _UID_RE.search(fh.read())
|
||||
return m.group(1) if m else None
|
||||
except OSError:
|
||||
return None
|
||||
|
||||
|
||||
def _texture_import(res_path: str, pixelart: bool, uid: str | None) -> str:
|
||||
"""Genera el .import de una textura 2D (Lossless, mipmaps off)."""
|
||||
uid_line = f'uid="{uid}"\n' if uid else ""
|
||||
return (
|
||||
"[remap]\n\n"
|
||||
'importer="texture"\n'
|
||||
f"{uid_line}"
|
||||
'type="CompressedTexture2D"\n\n'
|
||||
"[deps]\n\n"
|
||||
f'source_file="{res_path}"\n\n'
|
||||
"[params]\n\n"
|
||||
"compress/mode=0\n" # 0 = Lossless (correcto para pixelart/2D)
|
||||
"mipmaps/generate=false\n"
|
||||
"process/fix_alpha_border=true\n"
|
||||
"detect_3d/compress_to=1\n"
|
||||
)
|
||||
|
||||
|
||||
def _audio_import(res_path: str, kind: str, uid: str | None) -> str:
|
||||
"""Genera el .import de audio: wav (loop segun kind) u ogg (loop segun kind)."""
|
||||
loop = kind == "music"
|
||||
ext = os.path.splitext(res_path)[1].lower()
|
||||
uid_line = f'uid="{uid}"\n' if uid else ""
|
||||
if ext == ".ogg":
|
||||
return (
|
||||
"[remap]\n\n"
|
||||
'importer="oggvorbisstr"\n'
|
||||
f"{uid_line}"
|
||||
'type="AudioStreamOggVorbis"\n\n'
|
||||
"[deps]\n\n"
|
||||
f'source_file="{res_path}"\n\n'
|
||||
"[params]\n\n"
|
||||
f"loop={'true' if loop else 'false'}\n"
|
||||
)
|
||||
return (
|
||||
"[remap]\n\n"
|
||||
'importer="wav"\n'
|
||||
f"{uid_line}"
|
||||
'type="AudioStreamWAV"\n\n'
|
||||
"[deps]\n\n"
|
||||
f'source_file="{res_path}"\n\n'
|
||||
"[params]\n\n"
|
||||
f"edit/loop_mode={1 if loop else 0}\n" # 0=Disabled (sfx), 1=Forward (musica)
|
||||
"force/mono=false\n"
|
||||
)
|
||||
|
||||
|
||||
def _scene_import(res_path: str, uid: str | None) -> str:
|
||||
"""Genera el .import de una malla glTF/GLB (escena PackedScene)."""
|
||||
uid_line = f'uid="{uid}"\n' if uid else ""
|
||||
return (
|
||||
"[remap]\n\n"
|
||||
'importer="scene"\n'
|
||||
f"{uid_line}"
|
||||
'importer_version=1\n'
|
||||
'type="PackedScene"\n\n'
|
||||
"[deps]\n\n"
|
||||
f'source_file="{res_path}"\n\n'
|
||||
"[params]\n\n"
|
||||
"nodes/root_type=\"\"\n"
|
||||
)
|
||||
|
||||
|
||||
def _ensure_pixelart_filter(project_godot: str) -> bool:
|
||||
"""Asegura default_texture_filter=0 (Nearest) en project.godot. Idempotente.
|
||||
|
||||
Returns True si el archivo quedo con el filtro Nearest (ya lo tenia o se anadio).
|
||||
"""
|
||||
key = "rendering/textures/canvas_textures/default_texture_filter"
|
||||
try:
|
||||
with open(project_godot, encoding="utf-8") as fh:
|
||||
text = fh.read()
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
if re.search(rf"^{re.escape(key)}=0\s*$", text, re.MULTILINE):
|
||||
return True # ya esta en Nearest
|
||||
# Reemplaza un valor existente distinto de 0, o anade la clave a [rendering].
|
||||
if re.search(rf"^{re.escape(key)}=", text, re.MULTILINE):
|
||||
text = re.sub(rf"^{re.escape(key)}=.*$", f"{key}=0", text, flags=re.MULTILINE)
|
||||
elif re.search(r"^\[rendering\]\s*$", text, re.MULTILINE):
|
||||
text = re.sub(r"^\[rendering\]\s*$", f"[rendering]\n\n{key}=0", text,
|
||||
count=1, flags=re.MULTILINE)
|
||||
else:
|
||||
text = text.rstrip() + f"\n\n[rendering]\n\n{key}=0\n"
|
||||
try:
|
||||
with open(project_godot, "w", encoding="utf-8") as fh:
|
||||
fh.write(text)
|
||||
return True
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
def comfyui_export_asset_to_godot(
|
||||
asset_path: str,
|
||||
kind: str,
|
||||
godot_project: str,
|
||||
*,
|
||||
name: str | None = None,
|
||||
reimport: bool = True,
|
||||
godot_bin: str | None = None,
|
||||
) -> dict:
|
||||
"""Exporta un asset generado a un proyecto Godot con su .import correcto.
|
||||
|
||||
Args:
|
||||
asset_path: ruta del asset de origen (p.ej. en ~/ComfyUI/output/).
|
||||
kind: tipo de asset: "sprite", "pixelart", "tileset", "vfx", "sfx",
|
||||
"music" o "model".
|
||||
godot_project: ruta raiz del proyecto Godot destino (debe contener
|
||||
project.godot).
|
||||
name: nombre base deseado para el archivo destino (sin extension); si
|
||||
None se deriva del origen (snake_case sin sufijo _NNNNN_). keyword-only.
|
||||
reimport: si True intenta un reimport headless con el binario de Godot.
|
||||
keyword-only.
|
||||
godot_bin: ruta del binario de Godot; None autodetecta (PATH y rutas
|
||||
conocidas). keyword-only.
|
||||
|
||||
Returns:
|
||||
dict con:
|
||||
- ok (bool)
|
||||
- dest_res_path (str): ruta "res://assets/.../<n>.<ext>".
|
||||
- dest_abs_path (str): ruta absoluta en disco del asset copiado.
|
||||
- import_path (str): ruta absoluta del .import escrito.
|
||||
- import_written (bool)
|
||||
- pixelart_filter_set (bool): True si se aseguro Nearest en project.godot.
|
||||
- reimported (bool): True si el reimport headless salio con exit 0.
|
||||
- warnings (list[str])
|
||||
- error (str): vacio si OK.
|
||||
"""
|
||||
out = {
|
||||
"ok": False, "dest_res_path": "", "dest_abs_path": "", "import_path": "",
|
||||
"import_written": False, "pixelart_filter_set": False, "reimported": False,
|
||||
"warnings": [], "error": "",
|
||||
}
|
||||
|
||||
if not os.path.isfile(asset_path):
|
||||
out["error"] = f"asset_path no existe: {asset_path!r}"
|
||||
return out
|
||||
|
||||
project_godot = os.path.join(godot_project, "project.godot")
|
||||
if not os.path.isfile(project_godot):
|
||||
out["error"] = f"no es un proyecto Godot (falta project.godot): {godot_project!r}"
|
||||
return out
|
||||
|
||||
try:
|
||||
subdir = godot_map_asset_dir(kind)
|
||||
except ValueError as exc:
|
||||
out["error"] = str(exc)
|
||||
return out
|
||||
|
||||
clean = godot_clean_asset_name(asset_path, override=name)
|
||||
dest_dir_abs = os.path.join(godot_project, "assets", subdir)
|
||||
dest_abs = os.path.join(dest_dir_abs, clean)
|
||||
res_path = f"res://assets/{subdir}/{clean}"
|
||||
import_abs = dest_abs + ".import"
|
||||
|
||||
# 1. Copiar el asset.
|
||||
try:
|
||||
os.makedirs(dest_dir_abs, exist_ok=True)
|
||||
shutil.copy2(asset_path, dest_abs)
|
||||
except OSError as exc:
|
||||
out["error"] = f"no se pudo copiar el asset: {exc}"
|
||||
return out
|
||||
out["dest_abs_path"] = dest_abs
|
||||
out["dest_res_path"] = res_path
|
||||
|
||||
# 2. Escribir el .import segun el tipo (preservando uid existente).
|
||||
kind_l = kind.strip().lower()
|
||||
uid = _existing_uid(import_abs)
|
||||
if kind_l in _TEXTURE_KINDS:
|
||||
content = _texture_import(res_path, pixelart=(kind_l == "pixelart"), uid=uid)
|
||||
if kind_l in ("tileset", "vfx"):
|
||||
out["warnings"].append(
|
||||
f"{kind_l}: textura copiada; Godot no deriva el TileSet/SpriteFrames "
|
||||
"solo (crear el .tres en editor o por script)."
|
||||
)
|
||||
elif kind_l in ("sfx", "music"):
|
||||
content = _audio_import(res_path, kind_l, uid=uid)
|
||||
elif kind_l == "model":
|
||||
content = _scene_import(res_path, uid=uid)
|
||||
else: # no deberia pasar (map_asset_dir ya valido), defensa.
|
||||
out["error"] = f"kind sin importer: {kind!r}"
|
||||
return out
|
||||
|
||||
try:
|
||||
with open(import_abs, "w", encoding="utf-8") as fh:
|
||||
fh.write(content)
|
||||
out["import_written"] = True
|
||||
out["import_path"] = import_abs
|
||||
except OSError as exc:
|
||||
out["error"] = f"no se pudo escribir el .import: {exc}"
|
||||
return out
|
||||
|
||||
# 3. Filtro Nearest del proyecto si es pixelart (mecanismo Godot 4: global).
|
||||
if kind_l == "pixelart":
|
||||
if _ensure_pixelart_filter(project_godot):
|
||||
out["pixelart_filter_set"] = True
|
||||
else:
|
||||
out["warnings"].append(
|
||||
"no se pudo asegurar default_texture_filter=0 en project.godot"
|
||||
)
|
||||
|
||||
# 4. Reimport headless (best-effort). Sin binario -> deja el .import y anota.
|
||||
if reimport:
|
||||
gbin = _find_godot_bin(godot_bin)
|
||||
if gbin is None:
|
||||
out["warnings"].append(
|
||||
"Godot CLI no encontrado: .import escrito, reimport pendiente "
|
||||
"(abre el editor o pasa godot_bin)."
|
||||
)
|
||||
else:
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
[gbin, "--headless", "--path", godot_project, "--import"],
|
||||
capture_output=True, text=True, timeout=180,
|
||||
)
|
||||
out["reimported"] = proc.returncode == 0
|
||||
if proc.returncode != 0:
|
||||
tail = (proc.stderr or proc.stdout or "").strip().splitlines()[-3:]
|
||||
out["warnings"].append(
|
||||
f"reimport exit {proc.returncode}: {' / '.join(tail)}"
|
||||
)
|
||||
except subprocess.TimeoutExpired:
|
||||
out["warnings"].append("reimport headless excedio el timeout (180s)")
|
||||
except OSError as exc:
|
||||
out["warnings"].append(f"reimport headless fallo: {exc}")
|
||||
|
||||
out["ok"] = True
|
||||
return out
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import json
|
||||
|
||||
if len(sys.argv) < 4:
|
||||
print("uso: comfyui_export_asset_to_godot.py <asset> <kind> <godot_project> [name]",
|
||||
file=sys.stderr)
|
||||
sys.exit(2)
|
||||
a, k, p = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||
nm = sys.argv[4] if len(sys.argv) > 4 else None
|
||||
print(json.dumps(comfyui_export_asset_to_godot(a, k, p, name=nm), indent=2))
|
||||
@@ -0,0 +1,129 @@
|
||||
"""Tests de comfyui_export_asset_to_godot (offline; sin Godot real, reimport mockeado)."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from pipelines.comfyui_export_asset_to_godot import comfyui_export_asset_to_godot # noqa: E402
|
||||
|
||||
|
||||
def _godot_proj(tmp_path):
|
||||
"""Crea un proyecto Godot 4 minimo (solo project.godot)."""
|
||||
proj = tmp_path / "godot_proj"
|
||||
proj.mkdir()
|
||||
(proj / "project.godot").write_text(
|
||||
'config_version=5\n\n[application]\n\nconfig/name="Test"\n', encoding="utf-8"
|
||||
)
|
||||
return str(proj)
|
||||
|
||||
|
||||
def _png(tmp_path, name="hero_00001_.png"):
|
||||
p = tmp_path / name
|
||||
Image.fromarray(np.zeros((16, 16, 3), np.uint8), "RGB").save(str(p))
|
||||
return str(p)
|
||||
|
||||
|
||||
def test_golden_pixelart(tmp_path):
|
||||
proj = _godot_proj(tmp_path)
|
||||
asset = _png(tmp_path)
|
||||
res = comfyui_export_asset_to_godot(asset, "pixelart", proj, reimport=False)
|
||||
assert res["ok"] is True, res["error"]
|
||||
# copiado a sprites/ con nombre limpio
|
||||
assert res["dest_res_path"] == "res://assets/sprites/hero.png"
|
||||
assert os.path.isfile(os.path.join(proj, "assets", "sprites", "hero.png"))
|
||||
# .import textura con Lossless + Nearest global del proyecto
|
||||
imp = open(res["import_path"], encoding="utf-8").read()
|
||||
assert 'importer="texture"' in imp
|
||||
assert "compress/mode=0" in imp
|
||||
assert res["pixelart_filter_set"] is True
|
||||
pg = open(os.path.join(proj, "project.godot"), encoding="utf-8").read()
|
||||
assert "rendering/textures/canvas_textures/default_texture_filter=0" in pg
|
||||
|
||||
|
||||
def test_edge_music_loop_on(tmp_path):
|
||||
proj = _godot_proj(tmp_path)
|
||||
wav = tmp_path / "theme.wav"
|
||||
wav.write_bytes(b"RIFF....WAVE") # contenido falso: el .import no decodifica aqui
|
||||
res = comfyui_export_asset_to_godot(str(wav), "music", proj, reimport=False)
|
||||
assert res["ok"] is True
|
||||
assert res["dest_res_path"] == "res://assets/audio/music/theme.wav"
|
||||
imp = open(res["import_path"], encoding="utf-8").read()
|
||||
assert 'importer="wav"' in imp
|
||||
assert "edit/loop_mode=1" in imp # musica -> loop ON
|
||||
|
||||
|
||||
def test_edge_sfx_loop_off(tmp_path):
|
||||
proj = _godot_proj(tmp_path)
|
||||
wav = tmp_path / "step.wav"
|
||||
wav.write_bytes(b"RIFF....WAVE")
|
||||
res = comfyui_export_asset_to_godot(str(wav), "sfx", proj, reimport=False)
|
||||
assert res["ok"] is True
|
||||
imp = open(res["import_path"], encoding="utf-8").read()
|
||||
assert "edit/loop_mode=0" in imp # sfx -> loop OFF
|
||||
|
||||
|
||||
def test_edge_model_glb_scene(tmp_path):
|
||||
proj = _godot_proj(tmp_path)
|
||||
glb = tmp_path / "robot_00001_.glb"
|
||||
glb.write_bytes(b"glTF....")
|
||||
res = comfyui_export_asset_to_godot(str(glb), "model", proj, reimport=False)
|
||||
assert res["ok"] is True
|
||||
assert res["dest_res_path"] == "res://assets/models/robot.glb"
|
||||
imp = open(res["import_path"], encoding="utf-8").read()
|
||||
assert 'importer="scene"' in imp
|
||||
|
||||
|
||||
def test_edge_tileset_warns(tmp_path):
|
||||
proj = _godot_proj(tmp_path)
|
||||
res = comfyui_export_asset_to_godot(_png(tmp_path, "tiles_00001_.png"), "tileset", proj,
|
||||
reimport=False)
|
||||
assert res["ok"] is True
|
||||
assert any("TileSet" in w for w in res["warnings"])
|
||||
|
||||
|
||||
def test_idempotent_preserves_uid(tmp_path):
|
||||
proj = _godot_proj(tmp_path)
|
||||
asset = _png(tmp_path)
|
||||
r1 = comfyui_export_asset_to_godot(asset, "sprite", proj, reimport=False)
|
||||
# inyecta un uid en el .import como si Godot ya lo hubiera asignado
|
||||
imp_path = r1["import_path"]
|
||||
txt = open(imp_path, encoding="utf-8").read().replace(
|
||||
'importer="texture"\n', 'importer="texture"\nuid="uid://abc123xyz"\n'
|
||||
)
|
||||
open(imp_path, "w", encoding="utf-8").write(txt)
|
||||
r2 = comfyui_export_asset_to_godot(asset, "sprite", proj, reimport=False)
|
||||
assert 'uid="uid://abc123xyz"' in open(r2["import_path"], encoding="utf-8").read()
|
||||
|
||||
|
||||
def test_error_missing_asset(tmp_path):
|
||||
proj = _godot_proj(tmp_path)
|
||||
res = comfyui_export_asset_to_godot(str(tmp_path / "nope.png"), "sprite", proj)
|
||||
assert res["ok"] is False
|
||||
assert "no existe" in res["error"]
|
||||
|
||||
|
||||
def test_error_bad_kind(tmp_path):
|
||||
proj = _godot_proj(tmp_path)
|
||||
res = comfyui_export_asset_to_godot(_png(tmp_path), "hologram", proj, reimport=False)
|
||||
assert res["ok"] is False
|
||||
|
||||
|
||||
def test_error_not_a_godot_project(tmp_path):
|
||||
asset = _png(tmp_path)
|
||||
res = comfyui_export_asset_to_godot(asset, "sprite", str(tmp_path / "empty"))
|
||||
assert res["ok"] is False
|
||||
assert "project.godot" in res["error"]
|
||||
|
||||
|
||||
def test_godot_cli_absent_leaves_import(tmp_path):
|
||||
proj = _godot_proj(tmp_path)
|
||||
asset = _png(tmp_path)
|
||||
res = comfyui_export_asset_to_godot(asset, "sprite", proj, reimport=True,
|
||||
godot_bin="/no/such/godot")
|
||||
assert res["ok"] is True
|
||||
assert res["import_written"] is True
|
||||
assert res["reimported"] is False
|
||||
assert any("Godot CLI no encontrado" in w for w in res["warnings"])
|
||||
Reference in New Issue
Block a user