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,89 @@
---
name: comfyui_image_to_3d_oneshot
kind: pipeline
lang: py
domain: pipelines
purity: impure
version: "1.0.0"
signature: "def comfyui_image_to_3d_oneshot(image_path: str, *, server: str = \"127.0.0.1:8188\", variant: str = \"mini\", dest: str | None = None, wait_timeout: float = 600.0, **gen) -> dict"
description: "Pipeline imagen en disco -> malla 3D GLB en una sola llamada. Sube la imagen al input/ de ComfyUI (POST /upload/image), construye el workflow Hunyuan3D-2 nativo de 9 nodos, lo encola, espera y descarga la malla. Compone comfyui_build_image_to_3d_workflow + comfyui_submit_workflow + comfyui_wait_result + comfyui_fetch_output_mesh. Promocion de secuencia (issue 0087). Impuro: HTTP + disco."
tags: [comfyui, ml, img-to-3d, hunyuan3d, mesh, pipeline]
uses_functions:
- comfyui_build_image_to_3d_workflow_py_ml
- comfyui_submit_workflow_py_ml
- comfyui_wait_result_py_ml
- comfyui_fetch_output_mesh_py_ml
uses_types: []
returns: []
returns_optional: false
error_type: error_go_core
imports: []
tested: false
tests: []
test_file_path: ""
file_path: "python/functions/pipelines/comfyui_image_to_3d_oneshot.py"
params:
- name: image_path
desc: "Ruta local de la imagen de entrada (PNG/JPG). Se sube al input/ del servidor."
- name: server
desc: "host:port del servidor ComfyUI sin esquema. keyword-only."
- name: variant
desc: "'mini' (default), 'standard' o 'mv'; elige el checkpoint Hunyuan3D-2. El modelo debe estar ya instalado (comfyui_install_3d_model). keyword-only."
- name: dest
desc: "Ruta destino de la malla (None = cwd; dir = dentro; archivo = ahi). keyword-only."
- name: wait_timeout
desc: "Segundos maximos esperando a que el server termine la reconstruccion 3D (carga del modelo + difusion + decode). keyword-only."
- name: gen
desc: "Parametros de generacion pasados a comfyui_build_image_to_3d_workflow (resolution, steps, cfg, seed, octree_resolution, num_chunks, threshold, sampler_name, scheduler, filename_prefix). **kwargs."
output: "dict {ok, mesh_path, faces, prompt_id, error}. mesh_path = ruta local de la malla descargada; faces = numero de caras si trimesh esta disponible (si no, None); prompt_id = id del trabajo en ComfyUI. Si falla, ok=False y error explica en que paso."
---
## Ejemplo
```python
import sys, os
sys.path.insert(0, os.path.join(os.environ["HOME"], "fn_registry", "python", "functions"))
from pipelines.comfyui_image_to_3d_oneshot import comfyui_image_to_3d_oneshot
# Una imagen en disco -> un .glb en /tmp/meshes, en una sola llamada.
res = comfyui_image_to_3d_oneshot(
os.path.expanduser("~/ComfyUI/input/3d_src_robot_00001_.png"),
dest="/tmp/meshes",
variant="mini",
seed=42,
)
# res == {"ok": True, "mesh_path": "/tmp/meshes/3d_mesh_00001_.glb",
# "faces": 1668040, "prompt_id": "....", "error": ""}
```
CLI directo (el modelo mini debe estar instalado):
```bash
$HOME/fn_registry/python/.venv/bin/python3 \
python/functions/pipelines/comfyui_image_to_3d_oneshot.py \
~/ComfyUI/input/3d_src_robot_00001_.png
```
## Cuando usarla
Cuando tengas una imagen de un objeto (idealmente aislado sobre fondo plano) y
quieras su malla 3D GLB sin montar el grafo de nodos ni encadenar submit/wait/fetch
a mano. Es la operacion de un solo paso del flujo imagen->3D nativo de ComfyUI;
ideal para scripts y agentes. Para tunear el workflow nodo a nodo, usa las
funciones sueltas (`comfyui_build_image_to_3d_workflow` + submit/wait/fetch).
## Gotchas
- Impuro: sube la imagen (HTTP), dispara la GPU del servidor y escribe la malla en
disco. Requiere el server ComfyUI vivo en `server` y el checkpoint de la variante
ya instalado (`comfyui_install_3d_model` antes; este pipeline NO lo instala).
- La reconstruccion 3D tarda ~60 s en una RTX 3070 (carga del modelo entero a GPU +
difusion + decode de voxel). Sube `wait_timeout` si el server arranca en frio o si
usas `resolution`/`octree_resolution` altos.
- Si el server esta ocupado con otro trabajo, el wait puede agotar el timeout:
reintenta o sube `wait_timeout`. No reinicia ni desencola nada.
- `faces` es best-effort: solo se calcula si `trimesh` esta en el venv. Si no, sale
`None` (la malla igual se descarga bien; comprueba `mesh_path`/`bytes`).
- Salida **shape-only** (sin color/textura): es el camino nativo de Hunyuan3D-2.
- El upload usa POST /upload/image con `overwrite=true`: si subes dos imagenes con
el mismo basename, la segunda pisa a la primera en el input/ del servidor.
@@ -0,0 +1,181 @@
"""comfyui_image_to_3d_oneshot — imagen en disco -> malla 3D GLB en una sola llamada.
Promocion de la secuencia repetida (issue 0087): subir imagen al input/ de ComfyUI
-> construir el workflow Hunyuan3D-2 nativo -> encolar -> esperar -> descargar la
malla. Compone funciones del registry del grupo `comfyui`:
comfyui_build_image_to_3d_workflow_py_ml (workflow de 9 nodos)
comfyui_submit_workflow_py_ml (POST /prompt)
comfyui_wait_result_py_ml (poll /history)
comfyui_fetch_output_mesh_py_ml (GET /view -> disco)
El upload de la imagen al `input/` del servidor (POST /upload/image) es logica
especifica de este pipeline y va inline (no es una funcion reutilizable del
registry todavia).
Pipeline impuro: red (HTTP) + escritura en disco.
"""
from __future__ import annotations
import json
import mimetypes
import os
import sys
import urllib.error
import urllib.request
import uuid
# Importa las funciones del registry (mismo arbol python/functions).
_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 ml.comfyui_build_image_to_3d_workflow import comfyui_build_image_to_3d_workflow
from ml.comfyui_fetch_output_mesh import comfyui_fetch_output_mesh
from ml.comfyui_submit_workflow import comfyui_submit_workflow
from ml.comfyui_wait_result import comfyui_wait_result
# variant -> nombre del checkpoint que espera ImageOnlyCheckpointLoader.
_VARIANT_CKPT = {
"mini": "hunyuan3d-dit-v2-mini.safetensors",
"standard": "hunyuan3d-dit-v2-0.safetensors",
"mv": "hunyuan3d-dit-v2-mv.safetensors",
}
def _upload_image(image_path: str, server: str, timeout: float = 60.0) -> str:
"""Sube una imagen local al input/ de ComfyUI via POST /upload/image.
Construye un multipart/form-data a mano (solo stdlib). Devuelve el nombre del
archivo tal como queda en el servidor (para el nodo LoadImage). Lanza
RuntimeError si falla.
"""
with open(image_path, "rb") as fh:
content = fh.read()
name = os.path.basename(image_path)
ctype = mimetypes.guess_type(name)[0] or "application/octet-stream"
boundary = f"----fnRegistry{uuid.uuid4().hex}"
crlf = "\r\n"
pre = (
f"--{boundary}{crlf}"
f'Content-Disposition: form-data; name="image"; filename="{name}"{crlf}'
f"Content-Type: {ctype}{crlf}{crlf}"
).encode()
mid = (
f"{crlf}--{boundary}{crlf}"
f'Content-Disposition: form-data; name="overwrite"{crlf}{crlf}true'
f"{crlf}--{boundary}{crlf}"
f'Content-Disposition: form-data; name="type"{crlf}{crlf}input'
f"{crlf}--{boundary}--{crlf}"
).encode()
body = pre + content + mid
url = f"http://{server}/upload/image"
req = urllib.request.Request(
url, data=body,
headers={"Content-Type": f"multipart/form-data; boundary={boundary}"},
)
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
out = json.loads(resp.read())
except urllib.error.HTTPError as exc:
detail = exc.read().decode(errors="replace")[:200]
raise RuntimeError(f"upload HTTP {exc.code} en {url}: {detail}") from exc
except urllib.error.URLError as exc:
raise RuntimeError(f"no se pudo conectar a {url}: {exc.reason}") from exc
server_name = out.get("name", name)
subfolder = out.get("subfolder", "")
return f"{subfolder}/{server_name}" if subfolder else server_name
def comfyui_image_to_3d_oneshot(
image_path: str,
*,
server: str = "127.0.0.1:8188",
variant: str = "mini",
dest: str | None = None,
wait_timeout: float = 600.0,
**gen,
) -> dict:
"""Reconstruye una malla 3D desde una imagen en disco, end-to-end.
Args:
image_path: ruta local de la imagen de entrada (PNG/JPG). Se sube al
input/ del servidor.
server: host:port del servidor ComfyUI (sin esquema). keyword-only.
variant: "mini" (default), "standard" o "mv"; elige el checkpoint
Hunyuan3D-2. El modelo debe estar ya instalado (comfyui_install_3d_model).
keyword-only.
dest: ruta destino de la malla (None = cwd; dir = dentro; archivo = ahi).
keyword-only.
wait_timeout: segundos maximos esperando a que el server termine la
reconstruccion 3D (carga del modelo + difusion + decode). keyword-only.
**gen: parametros de generacion pasados a comfyui_build_image_to_3d_workflow
(resolution, steps, cfg, seed, octree_resolution, num_chunks, threshold,
sampler_name, scheduler, filename_prefix).
Returns:
dict {ok, mesh_path, faces, prompt_id, error}. mesh_path = ruta local de la
malla descargada; faces = numero de caras si trimesh esta disponible (si no,
None); prompt_id = id del trabajo en ComfyUI. Si falla, ok=False y error
explica en que paso.
"""
if variant not in _VARIANT_CKPT:
return {"ok": False, "mesh_path": "", "faces": None, "prompt_id": "",
"error": f"variant {variant!r} no valida; usa {sorted(_VARIANT_CKPT)}"}
if not os.path.isfile(os.path.expanduser(image_path)):
return {"ok": False, "mesh_path": "", "faces": None, "prompt_id": "",
"error": f"imagen no existe: {image_path}"}
image_path = os.path.expanduser(image_path)
ckpt_name = _VARIANT_CKPT[variant]
# 1. Subir la imagen al input/ del servidor.
try:
image_name = _upload_image(image_path, server)
except RuntimeError as exc:
return {"ok": False, "mesh_path": "", "faces": None, "prompt_id": "",
"error": f"upload de imagen fallo: {exc}"}
# 2. Construir el workflow imagen->3D (funcion pura del registry).
workflow = comfyui_build_image_to_3d_workflow(image_name, ckpt_name, **gen)
# 3. Encolar.
try:
sub = comfyui_submit_workflow(workflow, server=server)
prompt_id = sub["prompt_id"]
except (RuntimeError, KeyError) as exc:
return {"ok": False, "mesh_path": "", "faces": None, "prompt_id": "",
"error": f"submit fallo: {exc}"}
# 4. Esperar a que termine.
try:
comfyui_wait_result(prompt_id, server=server, timeout=wait_timeout)
except (TimeoutError, RuntimeError) as exc:
return {"ok": False, "mesh_path": "", "faces": None, "prompt_id": prompt_id,
"error": f"wait fallo: {exc}"}
# 5. Descargar la malla.
fetched = comfyui_fetch_output_mesh(prompt_id, server=server, dest=dest)
if not fetched.get("ok"):
return {"ok": False, "mesh_path": "", "faces": None, "prompt_id": prompt_id,
"error": f"fetch de malla fallo: {fetched.get('error')}"}
mesh_path = fetched["path"]
# 6. Caras (best-effort; trimesh puede no estar en el venv).
faces = None
try:
import trimesh # type: ignore
mesh = trimesh.load(mesh_path, force="mesh")
faces = int(len(mesh.faces))
except Exception: # noqa: BLE001 — trimesh ausente o malla ilegible: faces=None.
faces = None
return {"ok": True, "mesh_path": mesh_path, "faces": faces,
"prompt_id": prompt_id, "error": ""}
if __name__ == "__main__":
img = sys.argv[1] if len(sys.argv) > 1 else os.path.expanduser(
"~/ComfyUI/input/3d_src_robot_00001_.png")
res = comfyui_image_to_3d_oneshot(img, dest="/tmp/comfy_oneshot")
print(json.dumps(res, indent=2))