---
name: comfyui_build_normal_map_workflow
kind: function
lang: py
domain: ml
version: "1.0.0"
purity: pure
signature: "def comfyui_build_normal_map_workflow(image: str, *, method: str = \"normal\", strength: float = 1.0, resolution: int = 512, bg_threshold: float = 0.1, filename_prefix: str = \"normal_map\") -> dict  # method='normal' -> BAE (canonico)"
description: "Construye el dict (API format) de un workflow ComfyUI que genera el NORMAL MAP (o DEPTH/HEIGHT) de un sprite para iluminacion dinamica 2.5D (Godot CanvasItem normal_map, Unity sprite normal). Pipeline LoadImage -> preprocesador controlnet_aux -> SaveImage. method elige el nodo: 'normal' (BAE, default, normal canonico azul/violeta), 'normal_midas' (MiDaS, con strength), 'normal_dsine' (DSINE), 'depth' (DepthAnythingV2 height). Nodos de normal NATIVOS, sin necesidad de depth->Sobel post. Pura, sin red ni I/O. class_types verificados contra /object_info."
tags: [comfyui, ml, gamedev-2d, normal-map, depth-map, lighting, sprite, workflow, controlnet-aux]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: []
params:
  - name: image
    desc: "Nombre del archivo de entrada dentro del directorio input/ de ComfyUI (lo que consume LoadImage). No vacio. Si el sprite vive en output/, copialo a input/ antes de enviar (I/O del runner)."
  - name: method
    desc: "Preprocesador: 'normal' (default, BAE-NormalMapPreprocessor, normal canonico azul/violeta usable en motor), 'normal_midas' (MiDaS, con strength, paleta no canonica), 'normal_dsine' (DSINE), 'depth' (DepthAnythingV2, height map en gris). keyword-only."
  - name: strength
    desc: "Intensidad del relieve. Solo efectivo con method='normal_midas' (MiDaS): a = clamp(strength*2pi, 0, 5pi); strength=1.0 -> a=2pi. Debe ser >= 0. Para los demas metodos se ignora. keyword-only."
  - name: resolution
    desc: "Lado en px al que el preprocesador re-escala antes de inferir (64..16384). Conviene matchear el lado del sprite. keyword-only."
  - name: bg_threshold
    desc: "Umbral de fondo de MiDaS (0..1). Solo aplica a method='normal_midas'. keyword-only."
  - name: filename_prefix
    desc: "Prefijo del PNG resultante en output/. keyword-only."
output: "dict en API format con 3 nodos (LoadImage -> preprocesador normal/depth -> SaveImage), listo para comfyui_submit_workflow."
tested: true
tests: ["golden: 3 nodos LoadImage->BAE-NormalMapPreprocessor->SaveImage, image reflejado, sin a", "edge image reflejado en LoadImage", "edge strength escala a linealmente y satura en 5pi (method=normal_midas), default BAE sin a", "edge method='depth' selecciona DepthAnythingV2Preprocessor sin a", "edge method midas/dsine seleccionan su nodo", "edge resolution/filename_prefix reflejados", "error image vacio / method invalido / strength negativo / resolution fuera de rango -> ValueError", "determinismo"]
test_file_path: "python/functions/ml/comfyui_build_normal_map_workflow_test.py"
file_path: "python/functions/ml/comfyui_build_normal_map_workflow.py"
---

## Ejemplo

```python
import sys, os
sys.path.insert(0, os.path.join("python", "functions"))
from ml.comfyui_build_normal_map_workflow import comfyui_build_normal_map_workflow

# Normal map CANONICO azul/violeta (BAE) de un sprite ya copiado a input/ de ComfyUI.
wf = comfyui_build_normal_map_workflow(
    "item_icon_potion_00001_.png",
    method="normal",          # BAE: normal tangent-space usable directo en Godot/Unity
    resolution=512,
    filename_prefix="potion_normal",
)
# wf -> comfyui_submit_workflow(wf) ; luego esperar y leer el PNG de output/.

# Variante con control de intensidad de relieve (MiDaS, paleta no canonica):
wf_midas = comfyui_build_normal_map_workflow("sprite.png", method="normal_midas", strength=1.5)

# Variante depth/height (escala de grises) para parallax/height:
wf_depth = comfyui_build_normal_map_workflow("bg_layer.png", method="depth")
```

## Cuando usarla

- Cuando un sprite/imagen 2D debe reaccionar a **luces dinamicas** del motor (2.5D
  lighting): generas su normal map y lo enchufas en `Godot Sprite2D.material` (normal_map)
  o como secondary texture "Normal map" del sprite en Unity.
- Cuando necesitas un **height/depth map** de un fondo o tile para parallax o
  desplazamiento (usa `method="depth"`).
- Builder hermano de los `comfyui_build_*_workflow` del grupo `gamedev-2d`: produce el
  dict y delega el envio a `comfyui_submit_workflow`.

## Gotchas

- **`image` vive en input/, no en output/.** LoadImage solo ve el directorio `input/` de
  ComfyUI. Para usar un sprite generado (que esta en `output/`), copialo a `input/` antes
  de enviar el workflow. Esta funcion es pura: no copia nada.
- **`strength` solo afecta a `method="normal_midas"` (MiDaS).** El default BAE, DSINE y
  DepthAnything no exponen intensidad — con esos metodos `strength` se ignora (documentado,
  no es bug).
- **MiDaS (`normal_midas`) NO da el azul/violeta canonico**: su paleta domina verde/rosa
  (otra convencion de color). Para un normal map usable directo en motor usa el default
  `method="normal"` (BAE), que da el azul/violeta tangent-space (~(128,128,255) de fondo).
  Usa MiDaS solo cuando necesites variar el relieve via `strength`.
- **`method="depth"` NO produce un normal map**, sino un depth/height en escala de grises.
- **Primera ejecucion descarga pesos** del preprocesador (MiDaS/BAE/DSINE/DepthAnything)
  via controlnet_aux si no estan en cache. Requiere red la 1a vez; despues es local.
- **No se necesita depth->Sobel->normal post.** Hay nodos de normal NATIVOS en el server,
  asi que una `comfyui_depth_to_normal` auxiliar es innecesaria hoy (solo se justificaria
  para derivar el normal de un depth de mas calidad — gap anotado, no creado).
- Coste VRAM ~0 (no hay difusion): apto para el server 8GB lowvram.
