cda36408d0
Renombra los 13 checkpoints/diffusion models de ComfyUI prefijando la
categoría al inicio del nombre, para que en el dropdown de carga el usuario
distinga de inmediato imagen/vídeo/3D y no cargue un modelo en el nodo
equivocado. Misma operación que se hizo con los LoRAs (report 0197) pero
sobre los modelos.
Clasificación:
- IMG_: dreamshaper_8, juggernaut_xl_v11, v1-5-pruned-emaonly-fp16,
flux1-dev-fp8-e4m3fn, flux1-schnell-fp8-e4m3fn
- VIDEO_: svd, ltx-video-2b-v0.9.5, wan2.1_t2v_1.3B_fp16
- 3D_: stable_zero123, sv3d_p, hunyuan3d-dit-v2-mini, hunyuan3d-dit-v2-mv,
hy3dgen/hunyuan3d-dit-v2-0-fp16 (mantiene subcarpeta)
A diferencia de los LoRAs aquí solo se PREFIJA la categoría conservando el
nombre completo (versión/arquitectura). Archivos físicos renombrados en
~/ComfyUI/models/checkpoints, /mnt/2tb/comfyui_models/{checkpoints,
diffusion_models} y la subcarpeta hy3dgen/. Mapa de reversión en
~/ComfyUI/models/checkpoints/_ckpt_rename_map.json.
Actualiza todas las refs (ckpt_name/unet_name + defaults + prosa) en los
builders gamedev/vídeo/3D, style presets, pipelines, tests y los workflows
de ComfyUI. Arregla de paso el default roto de comfyui_text_to_3d_oneshot
(apuntaba a v1-5-pruned-emaonly.safetensors inexistente; ahora al real
IMG_v1-5-pruned-emaonly-fp16.safetensors).
No tocados (justificado): repo-paths de HuggingFace en comfyui_install_3d_model
(<repo>/model.fp16.safetensors son rutas de descarga, no nombres de dropdown)
y el mock de stable-diffusion.cpp en test_genconfig_to_sdcpp_args.
Verificado: dropdowns CheckpointLoaderSimple + UNETLoader listan los nombres
con prefijo; 1 generación real con IMG_juggernaut_xl_v11 (node_errors vacío,
pixelart_00003_.png); 327 tests comfyui verdes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
122 lines
5.2 KiB
Python
122 lines
5.2 KiB
Python
"""Sondea GET /history/{prompt_id} hasta que un workflow ComfyUI termina.
|
|
|
|
Funcion impura: hace red (HTTP GET en bucle) y duerme entre sondeos. Solo
|
|
stdlib (urllib, json, time).
|
|
|
|
Usa polling de /history como mecanismo principal (no WebSocket): es mas robusto
|
|
porque no depende de websocket-client, que no esta garantizado en el venv. Para
|
|
saber si el resultado esta listo (no streaming de progreso paso a paso) el
|
|
polling de /history es suficiente y portable.
|
|
"""
|
|
import json
|
|
import time
|
|
import urllib.error
|
|
import urllib.request
|
|
|
|
|
|
def comfyui_wait_result(
|
|
prompt_id: str,
|
|
server: str = "127.0.0.1:8188",
|
|
timeout: float = 600.0,
|
|
poll_interval: float = 1.0,
|
|
) -> dict:
|
|
"""Espera a que ComfyUI termine de ejecutar un prompt y devuelve sus outputs.
|
|
|
|
Sondea GET /history/{prompt_id} cada poll_interval segundos hasta que el
|
|
prompt aparece en /history con un estado terminal de exito (status.completed
|
|
True o status.status_str "success") **y** outputs ya poblados, o hasta que
|
|
falla (status_str "error"), o hasta agotar el timeout.
|
|
|
|
El requisito de outputs no vacios es deliberado: en jobs pesados (video, 3D)
|
|
ComfyUI puede exponer la entry de /history con el estado marcado como
|
|
terminado antes de haber poblado los outputs. Devolver en ese instante daba
|
|
un dict vacio mientras el job seguia en GPU (salida prematura). Por eso, si
|
|
el estado dice "terminado" pero los outputs aun no estan, se sigue sondeando
|
|
hasta que aparezcan o hasta agotar el timeout, que actua como red de
|
|
seguridad.
|
|
|
|
Args:
|
|
prompt_id: id devuelto por comfyui_submit_workflow.
|
|
server: host:port del servidor ComfyUI (sin esquema).
|
|
timeout: maximo de segundos a esperar antes de fallar. El default amplio
|
|
(600s) cubre workflows largos de video/3D; para imagenes cortas el
|
|
retorno sigue siendo inmediato en cuanto los outputs estan listos.
|
|
poll_interval: segundos entre sondeos.
|
|
|
|
Returns:
|
|
dict de outputs {node_id: {"images": [{filename, subfolder, type}, ...]}}
|
|
tal como ComfyUI los expone en history[prompt_id]["outputs"]. Puede
|
|
contener otros tipos de output (gifs, texto, video bajo "images" con
|
|
"animated") segun los nodos del workflow. Siempre no vacio en caso de
|
|
exito (un workflow sin nodo de guardado agotaria el timeout).
|
|
|
|
Raises:
|
|
TimeoutError: si se agota el timeout sin que el prompt complete con
|
|
outputs (incluye el caso de un prompt_id que nunca aparece en
|
|
/history porque sigue en cola, no existe, o el workflow no produce
|
|
outputs).
|
|
RuntimeError: si la ejecucion termina con status_str "error", si no se
|
|
puede conectar, o si la respuesta no es JSON valido.
|
|
"""
|
|
url = f"http://{server}/history/{prompt_id}"
|
|
# Timeout por-request acotado: una conexion colgada no debe bloquear todo el
|
|
# presupuesto global (que ahora puede ser de varios minutos).
|
|
req_timeout = min(timeout, 30.0) if timeout > 0 else 30.0
|
|
deadline = time.time() + timeout
|
|
while time.time() < deadline:
|
|
try:
|
|
with urllib.request.urlopen(url, timeout=req_timeout) as resp:
|
|
hist = json.loads(resp.read())
|
|
except urllib.error.URLError as exc:
|
|
raise RuntimeError(
|
|
f"comfyui_wait_result: no se pudo conectar a {url}: {exc.reason}"
|
|
) from exc
|
|
except json.JSONDecodeError as exc:
|
|
raise RuntimeError(
|
|
f"comfyui_wait_result: respuesta no es JSON valido desde {url}: {exc}"
|
|
) from exc
|
|
|
|
entry = hist.get(prompt_id)
|
|
if entry:
|
|
status = entry.get("status", {})
|
|
status_str = status.get("status_str")
|
|
if status_str == "error":
|
|
raise RuntimeError(
|
|
f"comfyui_wait_result: ejecucion fallo para {prompt_id}: "
|
|
f"{json.dumps(status)[:500]}"
|
|
)
|
|
done = bool(status.get("completed")) or status_str == "success"
|
|
outputs = entry.get("outputs") or {}
|
|
# Solo se considera terminado cuando hay estado de exito Y outputs.
|
|
# "done" con outputs vacios = entry creada pero aun sin resultados:
|
|
# se sigue esperando (no salir prematuro).
|
|
if done and outputs:
|
|
return outputs
|
|
time.sleep(poll_interval)
|
|
|
|
raise TimeoutError(
|
|
f"comfyui_wait_result: timeout de {timeout}s esperando {prompt_id}"
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import sys
|
|
|
|
from comfyui_build_txt2img_workflow import comfyui_build_txt2img_workflow
|
|
from comfyui_submit_workflow import comfyui_submit_workflow
|
|
|
|
wf = comfyui_build_txt2img_workflow(
|
|
ckpt_name="IMG_v1-5-pruned-emaonly-fp16.safetensors",
|
|
positive="a red apple on a wooden table, sharp focus",
|
|
negative="blurry, low quality",
|
|
steps=20,
|
|
seed=42,
|
|
)
|
|
resp = comfyui_submit_workflow(wf)
|
|
pid = resp["prompt_id"]
|
|
print(f"esperando prompt_id={pid} ...", file=sys.stderr)
|
|
outputs = comfyui_wait_result(pid)
|
|
for node_id, out in outputs.items():
|
|
for img in out.get("images", []):
|
|
print(f"OUTPUT node={node_id} filename={img['filename']}")
|