chore: auto-commit (61 archivos)

- docs/capabilities/INDEX.md
- docs/capabilities/comfyui.md
- python/functions/browser/comfyui_export_workflow_ui.md
- python/functions/browser/comfyui_export_workflow_ui.py
- python/functions/browser/comfyui_load_workflow_ui.md
- python/functions/browser/comfyui_load_workflow_ui.py
- python/functions/browser/comfyui_queue_prompt_ui.md
- python/functions/browser/comfyui_queue_prompt_ui.py
- python/functions/browser/comfyui_refresh_nodes_ui.md
- python/functions/browser/comfyui_refresh_nodes_ui.py
- ...

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-24 00:30:30 +02:00
parent 495f545ec1
commit f12272d002
72 changed files with 6049 additions and 0 deletions
@@ -0,0 +1,189 @@
"""Instala un checkpoint Hunyuan3D-2 en la carpeta checkpoints/ de ComfyUI.
ComfyUI 0.26.0 reconstruye mallas 3D con los nodos nativos de Hunyuan3D-2, que
cargan un checkpoint self-contained (DiT de forma + VAE 3D + encoder de imagen en
un solo .safetensors) via ImageOnlyCheckpointLoader. Esta funcion resuelve el repo
de HuggingFace de la variante pedida, REUTILIZA la cache de HF si ya esta bajado
(sin re-descargar), y copia el .safetensors a la carpeta checkpoints/ (la ruta real
que declara extra_model_paths.yaml) con el nombre que espera el loader nativo.
Cascada: (1) si el destino ya existe -> reutiliza; (2) si esta en la cache de HF
-> copia desde la cache; (3) si no -> descarga con huggingface_hub (token de
`pass` si la variante fuera gated).
Impura: lectura de YAML, escritura en disco, posible red (HTTP) y subprocess (pass).
"""
import os
import shutil
import subprocess
# variant -> (repo_id HF, ruta del archivo dentro del repo, nombre destino en checkpoints/)
_VARIANTS = {
"mini": (
"tencent/Hunyuan3D-2mini",
"hunyuan3d-dit-v2-mini/model.fp16.safetensors",
"hunyuan3d-dit-v2-mini.safetensors",
),
"standard": (
"tencent/Hunyuan3D-2",
"hunyuan3d-dit-v2-0/model.fp16.safetensors",
"hunyuan3d-dit-v2-0.safetensors",
),
"mv": (
"tencent/Hunyuan3D-2mv",
"hunyuan3d-dit-v2-mv/model.fp16.safetensors",
"hunyuan3d-dit-v2-mv.safetensors",
),
}
_MIN_BYTES = 1_000_000 # un .safetensors real pesa GBs; descarta restos/HTML.
def _checkpoints_dir(comfyui_dir: str) -> str:
"""Resuelve el directorio real de checkpoints de ComfyUI.
Lee extra_model_paths.yaml (prefiere la seccion con is_default) para devolver
`<base_path>/<checkpoints_subdir>`. Si el YAML no existe o no se puede parsear,
cae a la ruta nativa `<comfyui_dir>/models/checkpoints`.
"""
base = os.path.expanduser(comfyui_dir)
native = os.path.join(base, "models", "checkpoints")
yml = os.path.join(base, "extra_model_paths.yaml")
if not os.path.isfile(yml):
return native
try:
import yaml
with open(yml, encoding="utf-8") as fh:
data = yaml.safe_load(fh) or {}
except Exception: # noqa: BLE001 — YAML/PyYAML no disponible: usar nativa.
return native
if not isinstance(data, dict):
return native
fallback = None
for section in data.values():
if not isinstance(section, dict):
continue
sub = section.get("checkpoints")
if not sub:
continue
bp = os.path.expanduser(str(section.get("base_path", "")))
first_line = str(sub).splitlines()[0].strip()
resolved = os.path.join(bp, first_line)
if section.get("is_default"):
return resolved
if fallback is None:
fallback = resolved
return fallback or native
def _find_in_hf_cache(repo_id: str, repo_filename: str) -> str | None:
"""Busca el archivo en la cache local de HuggingFace, sin red.
Layout: ~/.cache/huggingface/hub/models--<org>--<name>/snapshots/<hash>/...
Resuelve el symlink al blob real y verifica un tamano minimo. Devuelve la ruta
real o None.
"""
org_name = repo_id.replace("/", "--")
hub = os.path.expanduser("~/.cache/huggingface/hub")
cache_root = os.path.join(hub, f"models--{org_name}", "snapshots")
if not os.path.isdir(cache_root):
return None
target = os.path.basename(repo_filename)
for snap in os.listdir(cache_root):
snap_dir = os.path.join(cache_root, snap)
if not os.path.isdir(snap_dir):
continue
for root, _dirs, files in os.walk(snap_dir):
if target in files:
real = os.path.realpath(os.path.join(root, target))
if os.path.isfile(real) and os.path.getsize(real) >= _MIN_BYTES:
return real
return None
def _pass_hf_token() -> str | None:
"""Lee el token de HuggingFace de `pass API_TOKEN_huggingFace`, o None."""
try:
out = subprocess.run(
["pass", "show", "API_TOKEN_huggingFace"],
capture_output=True, text=True, timeout=10,
)
if out.returncode == 0:
tok = out.stdout.splitlines()[0].strip() if out.stdout.strip() else ""
return tok or None
except (OSError, subprocess.SubprocessError):
pass
return None
def comfyui_install_3d_model(
variant: str = "mini",
*,
hf_token: str | None = None,
comfyui_dir: str = "~/ComfyUI",
) -> dict:
"""Instala el checkpoint Hunyuan3D-2 de la variante pedida en checkpoints/.
Args:
variant: "mini" (≈5 GB VRAM, default), "standard" (dit-v2-0, ≈6 GB) o
"mv" (multiview). Determina el repo de HF y el nombre destino.
hf_token: token de HuggingFace si la variante fuera gated. Si None y hace
falta descargar, se intenta leer de `pass show API_TOKEN_huggingFace`.
keyword-only.
comfyui_dir: raiz de la instalacion de ComfyUI (se expande ~). La carpeta
real de checkpoints se resuelve via extra_model_paths.yaml. keyword-only.
Returns:
dict {ok, path, bytes, reused_cache, error}. path = ruta del checkpoint en
checkpoints/; reused_cache=True si ya estaba instalado o se copio de la
cache de HF (sin descarga de red). Si falla, ok=False y error explica.
"""
if variant not in _VARIANTS:
return {"ok": False, "path": "", "bytes": 0, "reused_cache": False,
"error": f"variant {variant!r} no valida; usa {sorted(_VARIANTS)}"}
repo_id, repo_filename, dest_name = _VARIANTS[variant]
ckpt_dir = _checkpoints_dir(comfyui_dir)
dest = os.path.join(ckpt_dir, dest_name)
# 1. Ya instalado.
if os.path.isfile(dest) and os.path.getsize(dest) >= _MIN_BYTES:
return {"ok": True, "path": dest, "bytes": os.path.getsize(dest),
"reused_cache": True, "error": ""}
# 2. En la cache de HF -> copiar (sin red).
cached = _find_in_hf_cache(repo_id, repo_filename)
if cached:
try:
os.makedirs(ckpt_dir, exist_ok=True)
shutil.copy2(cached, dest)
except OSError as exc:
return {"ok": False, "path": "", "bytes": 0, "reused_cache": False,
"error": f"no se pudo copiar de la cache HF a {dest}: {exc}"}
return {"ok": True, "path": dest, "bytes": os.path.getsize(dest),
"reused_cache": True, "error": ""}
# 3. Descargar con huggingface_hub (lazy; usa su propia cache).
token = hf_token or _pass_hf_token()
try:
from huggingface_hub import hf_hub_download
except ImportError:
return {"ok": False, "path": "", "bytes": 0, "reused_cache": False,
"error": ("no esta en la cache de HF y huggingface_hub no esta "
"instalado en este venv. Instala huggingface_hub o baja "
f"el archivo {repo_filename!r} de {repo_id!r} a mano (o con "
"comfyui_download_model usando la URL de resolve de HF).")}
try:
local = hf_hub_download(repo_id=repo_id, filename=repo_filename, token=token)
os.makedirs(ckpt_dir, exist_ok=True)
shutil.copy2(local, dest)
except Exception as exc: # noqa: BLE001 — red/auth/gated/escritura.
return {"ok": False, "path": "", "bytes": 0, "reused_cache": False,
"error": f"fallo descargando {repo_filename} de {repo_id}: {exc}"}
return {"ok": True, "path": dest, "bytes": os.path.getsize(dest),
"reused_cache": False, "error": ""}
if __name__ == "__main__":
import json
print(json.dumps(comfyui_install_3d_model("mini"), ensure_ascii=False, indent=2))