---
name: comfyui_build_image_to_3d_workflow
kind: function
lang: py
domain: ml
version: "1.1.0"
purity: pure
signature: "def comfyui_build_image_to_3d_workflow(image_name: str, ckpt_name: str = \"3D_hunyuan3d-dit-v2-mini.safetensors\", *, resolution: int = 3072, steps: int = 30, cfg: float = 5.5, seed: int = 0, octree_resolution: int = 256, num_chunks: int = 8000, threshold: float = 0.6, sampler_name: str = \"euler\", scheduler: str = \"normal\", filename_prefix: str = \"3d_mesh\", watertight: bool = False) -> dict"
description: "Construye el dict de un workflow ComfyUI imagen->malla 3D en API format usando los nodos NATIVOS de Hunyuan3D-2 de ComfyUI 0.26.0 (sin custom node). Cadena de 9 nodos: LoadImage -> ImageOnlyCheckpointLoader -> CLIPVisionEncode -> Hunyuan3Dv2Conditioning -> EmptyLatentHunyuan3Dv2 -> KSampler -> VAEDecodeHunyuan3D -> (VoxelToMeshBasic | VoxelToMesh surface-net si watertight=True) -> SaveGLB. El SaveGLB produce un .glb. Pura, sin red ni I/O."
tags: [comfyui, ml, img-to-3d, hunyuan3d, mesh, workflow]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: []
params:
  - name: image_name
    desc: "Nombre del archivo de imagen en el input/ del servidor ComfyUI (ej. '3d_src_robot_00001_.png'). Lo carga el nodo LoadImage; debe existir ya en input/ (subelo antes o usa el pipeline oneshot)."
  - name: ckpt_name
    desc: "Nombre del checkpoint Hunyuan3D-2 tal como lo ve el servidor (ej. '3D_hunyuan3d-dit-v2-mini.safetensors'). Debe estar en la lista de comfyui_object_info para ImageOnlyCheckpointLoader."
  - name: resolution
    desc: "Resolucion del latente 3D (EmptyLatentHunyuan3Dv2). Mayor = mas detalle de forma y mas VRAM. keyword-only."
  - name: steps
    desc: "Pasos de sampling del KSampler de difusion 3D. keyword-only."
  - name: cfg
    desc: "Classifier-free guidance scale del KSampler. keyword-only."
  - name: seed
    desc: "Semilla del KSampler. 0 es determinista; cambiar para variar la malla. keyword-only."
  - name: octree_resolution
    desc: "Resolucion del grid de voxels en VAEDecodeHunyuan3D. Mayor = malla mas densa (mas caras) y mas memoria. keyword-only."
  - name: num_chunks
    desc: "Numero de chunks de decode del VAE 3D; controla el troceado del grid para caber en memoria. keyword-only."
  - name: threshold
    desc: "Umbral de iso-superficie del nodo voxel->malla (marching cubes / surface net sobre el grid de voxels). keyword-only."
  - name: sampler_name
    desc: "Nombre del sampler del KSampler (ej. 'euler'). keyword-only."
  - name: scheduler
    desc: "Scheduler del sampler (ej. 'normal'). keyword-only."
  - name: filename_prefix
    desc: "Prefijo del archivo de malla que SaveGLB escribe en output/ (ej. '3d_mesh' -> '3d_mesh_00001_.glb'). keyword-only."
  - name: watertight
    desc: "Si False (default, retro-compatible) el nodo '8' es VoxelToMeshBasic (malla NO estanca). Si True usa VoxelToMesh con algorithm='surface net', que produce una malla estanca/manifold de raiz sin post-proceso. keyword-only."
output: "dict en API format con node_ids '1'..'9' como claves; cada valor tiene class_type + inputs. Listo para comfyui_submit_workflow. El nodo '9' (SaveGLB) produce el archivo .glb en el output del servidor. El nodo '8' es VoxelToMeshBasic (watertight=False) o VoxelToMesh surface-net (watertight=True)."
tested: true
tests: ["cadena de 9 nodos Hunyuan3D-2 nativos", "imagen, checkpoint, seed reflejados y SaveGLB presente", "watertight=True usa VoxelToMesh surface-net; default conserva VoxelToMeshBasic", "determinismo: misma entrada -> mismo dict (builder puro)"]
test_file_path: "python/functions/ml/tests/test_comfyui_build_image_to_3d_workflow.py"
file_path: "python/functions/ml/comfyui_build_image_to_3d_workflow.py"
---

## Ejemplo

```python
import sys, os
sys.path.insert(0, os.path.join(os.environ["HOME"], "fn_registry", "python", "functions"))
from ml.comfyui_build_image_to_3d_workflow import comfyui_build_image_to_3d_workflow

wf = comfyui_build_image_to_3d_workflow(
    image_name="3d_src_robot_00001_.png",
    ckpt_name="3D_hunyuan3d-dit-v2-mini.safetensors",
    seed=42,
)
# wf["2"]["class_type"] == "ImageOnlyCheckpointLoader"
# wf["3"]["inputs"]["clip_vision"] == ["2", 1]   # CLIP_VISION del loader
# wf["7"]["class_type"] == "VAEDecodeHunyuan3D"
# wf["9"]["class_type"] == "SaveGLB"
```

O lanzable directo con: `./fn run comfyui_build_image_to_3d_workflow` (imprime el JSON del workflow de ejemplo).

## Cuando usarla

Antes de enviar una reconstruccion imagen->3D a ComfyUI: construye aqui el dict
del workflow y pasalo a `comfyui_submit_workflow`. Usala siempre que tengas una
imagen ya en el `input/` del servidor y quieras una malla GLB sin escribir el
grafo de 9 nodos a mano. Para hacerlo end-to-end desde una imagen en disco (subir
+ build + submit + wait + fetch en una llamada), usa el pipeline
`comfyui_image_to_3d_oneshot`.

## Gotchas

- Es API format (nodos numerados), NO el formato de la UI de ComfyUI (graph con
  links). No se pega en la UI tal cual; es el formato que acepta POST /prompt.
- Usa nodos NATIVOS de Hunyuan3D-2 de ComfyUI >= 0.26.0. En versiones anteriores
  (sin `ImageOnlyCheckpointLoader`/`VAEDecodeHunyuan3D`/`SaveGLB`) el server
  rechaza el workflow al enviarlo. Esta funcion es pura y no valida contra el
  server: valida con `comfyui_validate_workflow` antes de encolar si dudas.
- `image_name` debe existir en el `input/` del servidor ANTES de enviar. Esta
  funcion solo referencia el nombre; no sube nada (es pura). El pipeline oneshot
  hace el upload.
- `ckpt_name` debe coincidir EXACTAMENTE con un checkpoint visible para el
  servidor (instalalo con `comfyui_install_3d_model`).
- El camino nativo es **shape-only**: la malla sale SIN color/textura. Para color
  por vertice o textura horneada haria falta el wrapper de kijai (compila
  custom_rasterizer) — fuera de alcance.
- Con el default (`watertight=False`) el nodo `VoxelToMeshBasic` produce malla NO
  estanca ("cube-soup"), lo esperable; se arregla a posteriori con
  `comfyui_make_watertight` o el pipeline `comfyui_mesh_cleanup_oneshot`. Para
  malla estanca DE RAÍZ pasa `watertight=True`: usa `VoxelToMesh` con
  `algorithm="surface net"` (manifold cerrado sin reparar, ver report 0088). El
  nodo `VoxelToMesh` es nativo de ComfyUI >= 0.26.0 (`nodes_hunyuan3d.py`); en
  versiones sin él, usar el default + post-proceso.
- `octree_resolution` alto (256) produce mallas muy densas (decenas de MB de GLB,
  >1M caras) sin decimacion. Para web conviene un paso de simplificacion posterior
  (`comfyui_simplify_mesh` / `comfyui_mesh_cleanup_oneshot`).

## Capability growth log

- v1.1.0 (2026-06-24) — añade `watertight=False` (keyword-only, retro-compatible):
  con `True` el nodo voxel→malla usa `VoxelToMesh` (`algorithm="surface net"`) en
  vez de `VoxelToMeshBasic`, para mallas estancas de raíz sin post-proceso. El
  default conserva el comportamiento histórico exacto.
