chore: auto-commit (14 archivos)

- docs/capabilities/comfyui.md
- python/functions/ml/comfyui_build_image_to_3d_workflow.md
- python/functions/ml/comfyui_build_image_to_3d_workflow.py
- python/functions/ml/tests/test_comfyui_build_image_to_3d_workflow.py
- python/functions/ml/comfyui_build_facedetailer_workflow.md
- python/functions/ml/comfyui_build_facedetailer_workflow.py
- python/functions/ml/comfyui_build_hires_fix_workflow.md
- python/functions/ml/comfyui_build_hires_fix_workflow.py
- python/functions/ml/tests/test_comfyui_build_facedetailer_workflow.py
- python/functions/ml/tests/test_comfyui_build_hires_fix_workflow.py
- ...

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-24 02:34:10 +02:00
parent 3823a28d1c
commit f686b338d6
14 changed files with 1265 additions and 28 deletions
@@ -0,0 +1,84 @@
"""Tests de comfyui_build_facedetailer_workflow (builder puro, sin red)."""
import os
import sys
import pytest
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from ml.comfyui_build_facedetailer_workflow import comfyui_build_facedetailer_workflow # noqa: E402
from ml.comfyui_build_txt2img_workflow import comfyui_build_txt2img_workflow # noqa: E402
def test_image_mode_builds_detailer_chain():
wf = comfyui_build_facedetailer_workflow(
"portrait_00001_.png",
ckpt_name="dreamshaper_8.safetensors",
positive="detailed face",
seed=42,
)
# Detector + FaceDetailer + SaveImage presentes.
assert wf["fd_det"]["class_type"] == "UltralyticsDetectorProvider"
assert wf["fd_face"]["class_type"] == "FaceDetailer"
assert wf["fd_save"]["class_type"] == "SaveImage"
# La imagen viene del LoadImage propio del modo str.
assert wf["fd_load"]["class_type"] == "LoadImage"
assert wf["fd_face"]["inputs"]["image"] == ["fd_load", 0]
# FaceDetailer conecta el bbox_detector y la conditioning.
assert wf["fd_face"]["inputs"]["bbox_detector"] == ["fd_det", 0]
assert wf["fd_face"]["inputs"]["positive"] == ["fd_pos", 0]
assert wf["fd_face"]["inputs"]["seed"] == 42
def test_workflow_mode_reuses_base_nodes():
base = comfyui_build_txt2img_workflow(
ckpt_name="dreamshaper_8.safetensors",
positive="portrait of a woman",
seed=7,
)
wf = comfyui_build_facedetailer_workflow(
base,
ckpt_name="dreamshaper_8.safetensors",
positive="detailed face",
denoise=0.45,
)
# Los nodos del base se conservan.
assert wf["4"]["class_type"] == "CheckpointLoaderSimple"
assert wf["8"]["class_type"] == "VAEDecode"
# La imagen del detailer viene del VAEDecode del base.
assert wf["fd_face"]["inputs"]["image"] == ["8", 0]
# Reutiliza el checkpoint del base (model/clip/vae del nodo "4").
assert wf["fd_face"]["inputs"]["model"] == ["4", 0]
assert wf["fd_face"]["inputs"]["vae"] == ["4", 2]
assert wf["fd_face"]["inputs"]["denoise"] == 0.45
def test_normalizes_short_bbox_model():
wf = comfyui_build_facedetailer_workflow(
"x.png", ckpt_name="dreamshaper_8.safetensors", positive="face",
bbox_model="face_yolov8m.pt",
)
assert wf["fd_det"]["inputs"]["model_name"] == "bbox/face_yolov8m.pt"
# Un nombre ya prefijado se respeta.
wf2 = comfyui_build_facedetailer_workflow(
"x.png", ckpt_name="dreamshaper_8.safetensors", positive="face",
bbox_model="bbox/face_yolov8m.pt",
)
assert wf2["fd_det"]["inputs"]["model_name"] == "bbox/face_yolov8m.pt"
def test_dict_without_vaedecode_raises():
with pytest.raises(ValueError):
comfyui_build_facedetailer_workflow(
{"1": {"class_type": "LoadImage", "inputs": {"image": "x.png"}}},
ckpt_name="dreamshaper_8.safetensors",
positive="face",
)
def test_invalid_base_type_raises():
with pytest.raises(ValueError):
comfyui_build_facedetailer_workflow(
123, ckpt_name="dreamshaper_8.safetensors", positive="face",
)
@@ -0,0 +1,50 @@
"""Tests de comfyui_build_hires_fix_workflow (builder puro, sin red)."""
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from ml.comfyui_build_hires_fix_workflow import comfyui_build_hires_fix_workflow # noqa: E402
def test_base_ksampler_and_ultimate_upscale_present():
wf = comfyui_build_hires_fix_workflow(
ckpt_name="dreamshaper_8.safetensors",
positive="a fox",
seed=42,
)
# Pasada base = KSampler; pasada de detalle = UltimateSDUpscale.
assert wf["3"]["class_type"] == "KSampler"
assert wf["12"]["class_type"] == "UltimateSDUpscale"
assert wf["9"]["class_type"] == "SaveImage"
# El SaveImage cuelga del UltimateSDUpscale (la imagen final).
assert wf["9"]["inputs"]["images"] == ["12", 0]
def test_second_pass_denoise_is_partial():
wf = comfyui_build_hires_fix_workflow(
ckpt_name="dreamshaper_8.safetensors", positive="x", denoise=0.4,
)
# La base re-genera entera (denoise=1.0); la 2a pasada solo anade detalle (<1).
assert wf["3"]["inputs"]["denoise"] == 1.0
assert wf["12"]["inputs"]["denoise"] == 0.4
assert wf["12"]["inputs"]["denoise"] < 1.0
def test_first_pass_dims_reflected():
wf = comfyui_build_hires_fix_workflow(
ckpt_name="dreamshaper_8.safetensors", positive="x", first_pass=(640, 960),
)
assert wf["5"]["inputs"]["width"] == 640
assert wf["5"]["inputs"]["height"] == 960
def test_upscale_model_wired():
wf = comfyui_build_hires_fix_workflow(
ckpt_name="dreamshaper_8.safetensors", positive="x",
upscale_model="4x_foolhardy_Remacri.pth",
)
assert wf["11"]["class_type"] == "UpscaleModelLoader"
assert wf["11"]["inputs"]["model_name"] == "4x_foolhardy_Remacri.pth"
assert wf["12"]["inputs"]["upscale_model"] == ["11", 0]
@@ -40,3 +40,19 @@ def test_imagen_checkpoint_y_salida_glb():
assert node_by_ct(wf, "KSampler")["inputs"]["seed"] == 42
# SaveGLB es el nodo de salida: produce la malla .glb.
assert "SaveGLB" in class_types(wf)
def test_default_conserva_voxeltomeshbasic():
# Retro-compatibilidad: sin watertight el nodo "8" es VoxelToMeshBasic.
wf = comfyui_build_image_to_3d_workflow("obj.png")
assert wf["8"]["class_type"] == "VoxelToMeshBasic"
assert "algorithm" not in wf["8"]["inputs"]
def test_watertight_usa_voxeltomesh_surface_net():
wf = comfyui_build_image_to_3d_workflow("obj.png", watertight=True)
assert wf["8"]["class_type"] == "VoxelToMesh"
assert wf["8"]["inputs"]["algorithm"] == "surface net"
assert wf["8"]["inputs"]["voxel"] == ["7", 0]
# El resto de la cadena no cambia: SaveGLB sigue colgando del nodo "8".
assert wf["9"]["inputs"]["mesh"] == ["8", 0]