feat(ml): auto-commit con 20 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -183,6 +183,11 @@ Para tunear nodo a nodo en vez del oneshot: `build_image_to_3d_workflow(image_na
|
|||||||
`import_workflow_json`/`import_workflow_png`, se resuelven sus dependencias con
|
`import_workflow_json`/`import_workflow_png`, se resuelven sus dependencias con
|
||||||
`resolve_workflow_deps` (instala nodos con `install_custom_node`, descubre modelos con
|
`resolve_workflow_deps` (instala nodos con `install_custom_node`, descubre modelos con
|
||||||
`search_civitai_models`) y se validan con `validate_workflow` antes de encolar.
|
`search_civitai_models`) y se validan con `validate_workflow` antes de encolar.
|
||||||
|
- **Los 9 builders puros tienen tests de estructura** (`python/functions/ml/tests/test_comfyui_build_*.py`
|
||||||
|
+ `test_comfyui_inject_lora.py`): verifican los `class_type` esperados, que los parámetros se reflejan
|
||||||
|
en los nodos, la validez de las conexiones `[node_id, output_index]` y la pureza de `inject_lora`. Son
|
||||||
|
tests offline (no tocan GPU ni server); las funciones impuras del grupo (todo lo que habla con el server,
|
||||||
|
el navegador o Civitai/HuggingFace) no se cubren con unit tests por diseño — se validan con el server vivo.
|
||||||
- **Las funciones `*_ui` requieren la pestaña abierta y el navegador con CDP** (puerto 9222 por
|
- **Las funciones `*_ui` requieren la pestaña abierta y el navegador con CDP** (puerto 9222 por
|
||||||
defecto). Sin target que matchee `server_url_substr`, devuelven `ok=False`. Para automatización
|
defecto). Sin target que matchee `server_url_substr`, devuelven `ok=False`. Para automatización
|
||||||
desatendida sin navegador, usa el camino API (`submit_workflow` + `wait_result`).
|
desatendida sin navegador, usa el camino API (`submit_workflow` + `wait_result`).
|
||||||
|
|||||||
@@ -38,9 +38,9 @@ params:
|
|||||||
- name: height
|
- name: height
|
||||||
desc: "Alto del latente/imagen en px (multiplo de 8). keyword-only."
|
desc: "Alto del latente/imagen en px (multiplo de 8). keyword-only."
|
||||||
output: "dict en API format con node_ids como claves (CheckpointLoaderSimple '4', EmptyLatentImage '5', LoadImage '10', ControlNetLoader '12', CLIPTextEncode '6'/'7', ControlNetApply '13', KSampler '3', VAEDecode '8', SaveImage '9'). Listo para comfyui_submit_workflow."
|
output: "dict en API format con node_ids como claves (CheckpointLoaderSimple '4', EmptyLatentImage '5', LoadImage '10', ControlNetLoader '12', CLIPTextEncode '6'/'7', ControlNetApply '13', KSampler '3', VAEDecode '8', SaveImage '9'). Listo para comfyui_submit_workflow."
|
||||||
tested: false
|
tested: true
|
||||||
tests: []
|
tests: ["usa ControlNetLoader+ControlNetApply", "control_image, modelo cn y strength reflejados"]
|
||||||
test_file_path: ""
|
test_file_path: "python/functions/ml/tests/test_comfyui_build_controlnet_workflow.py"
|
||||||
file_path: "python/functions/ml/comfyui_build_controlnet_workflow.py"
|
file_path: "python/functions/ml/comfyui_build_controlnet_workflow.py"
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -40,9 +40,9 @@ params:
|
|||||||
- name: filename_prefix
|
- name: filename_prefix
|
||||||
desc: "Prefijo del archivo de malla que SaveGLB escribe en output/ (ej. '3d_mesh' -> '3d_mesh_00001_.glb'). keyword-only."
|
desc: "Prefijo del archivo de malla que SaveGLB escribe en output/ (ej. '3d_mesh' -> '3d_mesh_00001_.glb'). 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."
|
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."
|
||||||
tested: false
|
tested: true
|
||||||
tests: []
|
tests: ["cadena de 9 nodos Hunyuan3D-2 nativos", "imagen, checkpoint, seed reflejados y SaveGLB presente"]
|
||||||
test_file_path: ""
|
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"
|
file_path: "python/functions/ml/comfyui_build_image_to_3d_workflow.py"
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -36,9 +36,9 @@ params:
|
|||||||
- name: scheduler
|
- name: scheduler
|
||||||
desc: "Scheduler del sampler (ej. 'normal', 'karras'). keyword-only."
|
desc: "Scheduler del sampler (ej. 'normal', 'karras'). keyword-only."
|
||||||
output: "dict en API format con node_ids como claves (CheckpointLoaderSimple '4', LoadImage '10', VAEEncode '11', CLIPTextEncode '6'/'7', KSampler '3', VAEDecode '8', SaveImage '9'). Listo para comfyui_submit_workflow."
|
output: "dict en API format con node_ids como claves (CheckpointLoaderSimple '4', LoadImage '10', VAEEncode '11', CLIPTextEncode '6'/'7', KSampler '3', VAEDecode '8', SaveImage '9'). Listo para comfyui_submit_workflow."
|
||||||
tested: false
|
tested: true
|
||||||
tests: []
|
tests: ["usa VAEEncode/LoadImage y no EmptyLatentImage", "denoise e init_image reflejados"]
|
||||||
test_file_path: ""
|
test_file_path: "python/functions/ml/tests/test_comfyui_build_img2img_workflow.py"
|
||||||
file_path: "python/functions/ml/comfyui_build_img2img_workflow.py"
|
file_path: "python/functions/ml/comfyui_build_img2img_workflow.py"
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -38,9 +38,9 @@ params:
|
|||||||
- name: scheduler
|
- name: scheduler
|
||||||
desc: "Scheduler del sampler (ej. 'normal', 'karras'). keyword-only."
|
desc: "Scheduler del sampler (ej. 'normal', 'karras'). keyword-only."
|
||||||
output: "dict en API format con node_ids como claves (CheckpointLoaderSimple '4', LoadImage '10', LoadImageMask '12', VAEEncodeForInpaint '11', CLIPTextEncode '6'/'7', KSampler '3', VAEDecode '8', SaveImage '9'). Listo para comfyui_submit_workflow."
|
output: "dict en API format con node_ids como claves (CheckpointLoaderSimple '4', LoadImage '10', LoadImageMask '12', VAEEncodeForInpaint '11', CLIPTextEncode '6'/'7', KSampler '3', VAEDecode '8', SaveImage '9'). Listo para comfyui_submit_workflow."
|
||||||
tested: false
|
tested: true
|
||||||
tests: []
|
tests: ["usa LoadImageMask+VAEEncodeForInpaint", "imagen base, mascara, seed y denoise reflejados"]
|
||||||
test_file_path: ""
|
test_file_path: "python/functions/ml/tests/test_comfyui_build_inpaint_workflow.py"
|
||||||
file_path: "python/functions/ml/comfyui_build_inpaint_workflow.py"
|
file_path: "python/functions/ml/comfyui_build_inpaint_workflow.py"
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -36,9 +36,9 @@ params:
|
|||||||
- name: height
|
- name: height
|
||||||
desc: "Alto del latente/imagen en px (SDXL nativo 1024). keyword-only."
|
desc: "Alto del latente/imagen en px (SDXL nativo 1024). keyword-only."
|
||||||
output: "dict en API format con node_ids como claves (CheckpointLoaderSimple base '4' y refiner '14', EmptyLatentImage '5', CLIPTextEncode base '6'/'7' y refiner '16'/'17', KSamplerAdvanced base '3' y refiner '15', VAEDecode '8', SaveImage '9'). Listo para comfyui_submit_workflow."
|
output: "dict en API format con node_ids como claves (CheckpointLoaderSimple base '4' y refiner '14', EmptyLatentImage '5', CLIPTextEncode base '6'/'7' y refiner '16'/'17', KSamplerAdvanced base '3' y refiner '15', VAEDecode '8', SaveImage '9'). Listo para comfyui_submit_workflow."
|
||||||
tested: false
|
tested: true
|
||||||
tests: []
|
tests: ["dos KSamplerAdvanced encadenados", "base emite ruido sobrante y refiner lo recoge (start/end_at_step compartidos)"]
|
||||||
test_file_path: ""
|
test_file_path: "python/functions/ml/tests/test_comfyui_build_sdxl_refiner_workflow.py"
|
||||||
file_path: "python/functions/ml/comfyui_build_sdxl_refiner_workflow.py"
|
file_path: "python/functions/ml/comfyui_build_sdxl_refiner_workflow.py"
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -38,9 +38,9 @@ params:
|
|||||||
- name: filename_prefix
|
- name: filename_prefix
|
||||||
desc: "Prefijo del PNG que SaveImage escribe en output/. keyword-only."
|
desc: "Prefijo del PNG que SaveImage escribe en output/. keyword-only."
|
||||||
output: "dict en API format con node_ids '3'..'9' como claves; cada valor tiene class_type + inputs. Listo para comfyui_submit_workflow."
|
output: "dict en API format con node_ids '3'..'9' como claves; cada valor tiene class_type + inputs. Listo para comfyui_submit_workflow."
|
||||||
tested: false
|
tested: true
|
||||||
tests: []
|
tests: ["class_types esperados (6 nodos)", "params seed/steps/cfg/width/height reflejados", "filename_prefix en SaveImage"]
|
||||||
test_file_path: ""
|
test_file_path: "python/functions/ml/tests/test_comfyui_build_txt2img_workflow.py"
|
||||||
file_path: "python/functions/ml/comfyui_build_txt2img_workflow.py"
|
file_path: "python/functions/ml/comfyui_build_txt2img_workflow.py"
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ params:
|
|||||||
- name: method
|
- name: method
|
||||||
desc: "'model' (ESRGAN via UpscaleModelLoader + ImageUpscaleWithModel) o 'latent' (reescalado de pixel x2 con ImageScaleBy, sin modelo). keyword-only."
|
desc: "'model' (ESRGAN via UpscaleModelLoader + ImageUpscaleWithModel) o 'latent' (reescalado de pixel x2 con ImageScaleBy, sin modelo). keyword-only."
|
||||||
output: "dict en API format. Con method='model': LoadImage '10' + UpscaleModelLoader '12' + ImageUpscaleWithModel '13' + SaveImage '9'. Con method='latent': LoadImage '10' + ImageScaleBy '13' + SaveImage '9'. Listo para comfyui_submit_workflow."
|
output: "dict en API format. Con method='model': LoadImage '10' + UpscaleModelLoader '12' + ImageUpscaleWithModel '13' + SaveImage '9'. Con method='latent': LoadImage '10' + ImageScaleBy '13' + SaveImage '9'. Listo para comfyui_submit_workflow."
|
||||||
tested: false
|
tested: true
|
||||||
tests: []
|
tests: ["method='model' usa UpscaleModelLoader+ImageUpscaleWithModel", "method='latent' usa ImageScaleBy sin modelo"]
|
||||||
test_file_path: ""
|
test_file_path: "python/functions/ml/tests/test_comfyui_build_upscale_workflow.py"
|
||||||
file_path: "python/functions/ml/comfyui_build_upscale_workflow.py"
|
file_path: "python/functions/ml/comfyui_build_upscale_workflow.py"
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ params:
|
|||||||
- name: height
|
- name: height
|
||||||
desc: "Alto del viewport del nodo en px. keyword-only."
|
desc: "Alto del viewport del nodo en px. keyword-only."
|
||||||
output: "dict en API format con un unico nodo '1'. Con animation=False: class_type 'Load3D', inputs {model_file, image, width, height}. Con animation=True: class_type 'Load3DAdvanced', inputs {model_file, viewport_state, width, height}. Cargable con comfyui_load_workflow_ui (inyecta en la UI del navegador) o POSTeable a /prompt."
|
output: "dict en API format con un unico nodo '1'. Con animation=False: class_type 'Load3D', inputs {model_file, image, width, height}. Con animation=True: class_type 'Load3DAdvanced', inputs {model_file, viewport_state, width, height}. Cargable con comfyui_load_workflow_ui (inyecta en la UI del navegador) o POSTeable a /prompt."
|
||||||
tested: false
|
tested: true
|
||||||
tests: []
|
tests: ["Load3D simple con model_file/width/height", "animation=True usa Load3DAdvanced"]
|
||||||
test_file_path: ""
|
test_file_path: "python/functions/ml/tests/test_comfyui_build_view_3d_workflow.py"
|
||||||
file_path: "python/functions/ml/comfyui_build_view_3d_workflow.py"
|
file_path: "python/functions/ml/comfyui_build_view_3d_workflow.py"
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -28,9 +28,9 @@ params:
|
|||||||
- name: clip_node
|
- name: clip_node
|
||||||
desc: "node_id cuya salida CLIP (slot 1) alimentara el LoRA. Si None, se detecta la fuente que hoy alimenta los CLIPTextEncode.clip. keyword-only."
|
desc: "node_id cuya salida CLIP (slot 1) alimentara el LoRA. Si None, se detecta la fuente que hoy alimenta los CLIPTextEncode.clip. keyword-only."
|
||||||
output: "copia del workflow con un nodo LoraLoader insertado (node_id = max id numerico + 1) y reconectado entre la fuente model/clip y sus consumidores."
|
output: "copia del workflow con un nodo LoraLoader insertado (node_id = max id numerico + 1) y reconectado entre la fuente model/clip y sus consumidores."
|
||||||
tested: false
|
tested: true
|
||||||
tests: []
|
tests: ["no muta el dict de entrada (pureza)", "inserta LoraLoader con strength correcto", "reconecta KSampler.model al LoRA"]
|
||||||
test_file_path: ""
|
test_file_path: "python/functions/ml/tests/test_comfyui_inject_lora.py"
|
||||||
file_path: "python/functions/ml/comfyui_inject_lora.py"
|
file_path: "python/functions/ml/comfyui_inject_lora.py"
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
"""Helper compartido por los tests de los builders de workflow ComfyUI.
|
||||||
|
|
||||||
|
Valida la invariante del "API format": un dict de nodos numerados, cada uno con
|
||||||
|
`class_type` + `inputs`, donde las conexiones entre nodos son listas
|
||||||
|
`[node_id, output_index]` que referencian un node_id existente.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def assert_api_format(wf):
|
||||||
|
"""Comprueba que `wf` es un workflow ComfyUI en API format bien formado.
|
||||||
|
|
||||||
|
- dict no vacio
|
||||||
|
- cada nodo tiene `class_type` (str) e `inputs` (dict)
|
||||||
|
- toda conexion `[node_id, output_index]` apunta a un nodo existente
|
||||||
|
"""
|
||||||
|
assert isinstance(wf, dict) and wf, "workflow debe ser un dict no vacio"
|
||||||
|
ids = set(wf)
|
||||||
|
for nid, node in wf.items():
|
||||||
|
assert isinstance(node, dict), f"nodo {nid} no es dict"
|
||||||
|
assert isinstance(node.get("class_type"), str) and node["class_type"], (
|
||||||
|
f"nodo {nid} sin class_type"
|
||||||
|
)
|
||||||
|
assert isinstance(node.get("inputs"), dict), f"nodo {nid} sin inputs dict"
|
||||||
|
for key, val in node["inputs"].items():
|
||||||
|
if (
|
||||||
|
isinstance(val, list)
|
||||||
|
and len(val) == 2
|
||||||
|
and isinstance(val[0], str)
|
||||||
|
and isinstance(val[1], int)
|
||||||
|
):
|
||||||
|
assert val[0] in ids, (
|
||||||
|
f"nodo {nid}.{key} referencia node_id inexistente {val[0]!r}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def class_types(wf):
|
||||||
|
"""Conjunto de los `class_type` presentes en el workflow."""
|
||||||
|
return {n["class_type"] for n in wf.values()}
|
||||||
|
|
||||||
|
|
||||||
|
def node_by_ct(wf, ct):
|
||||||
|
"""Primer nodo cuyo `class_type` es `ct` (lanza StopIteration si no hay)."""
|
||||||
|
return next(n for n in wf.values() if n["class_type"] == ct)
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
"""Tests de estructura para comfyui_build_controlnet_workflow (funcion pura)."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.dirname(__file__))
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||||
|
|
||||||
|
from ml.comfyui_build_controlnet_workflow import comfyui_build_controlnet_workflow
|
||||||
|
from _comfyui_wf_assert import assert_api_format, class_types, node_by_ct
|
||||||
|
|
||||||
|
|
||||||
|
def test_estructura_controlnet():
|
||||||
|
wf = comfyui_build_controlnet_workflow("ck.safetensors", "ctrl.png", "cn.pth", "POS", "NEG")
|
||||||
|
assert_api_format(wf)
|
||||||
|
cts = class_types(wf)
|
||||||
|
assert "ControlNetLoader" in cts
|
||||||
|
assert "ControlNetApply" in cts
|
||||||
|
|
||||||
|
|
||||||
|
def test_control_image_modelo_y_strength():
|
||||||
|
wf = comfyui_build_controlnet_workflow(
|
||||||
|
"ck.safetensors", "pose.png", "control_openpose.pth", "POS", "NEG", strength=0.65, seed=5
|
||||||
|
)
|
||||||
|
assert node_by_ct(wf, "ControlNetLoader")["inputs"]["control_net_name"] == "control_openpose.pth"
|
||||||
|
# La imagen de control se carga via LoadImage.
|
||||||
|
assert node_by_ct(wf, "LoadImage")["inputs"]["image"] == "pose.png"
|
||||||
|
apply_in = node_by_ct(wf, "ControlNetApply")["inputs"]
|
||||||
|
assert apply_in["strength"] == 0.65
|
||||||
|
assert node_by_ct(wf, "KSampler")["inputs"]["seed"] == 5
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
"""Tests de estructura para comfyui_build_image_to_3d_workflow (funcion pura)."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.dirname(__file__))
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||||
|
|
||||||
|
from ml.comfyui_build_image_to_3d_workflow import comfyui_build_image_to_3d_workflow
|
||||||
|
from _comfyui_wf_assert import assert_api_format, class_types, node_by_ct
|
||||||
|
|
||||||
|
|
||||||
|
def test_cadena_hunyuan3d_nativa():
|
||||||
|
wf = comfyui_build_image_to_3d_workflow("obj.png")
|
||||||
|
assert_api_format(wf)
|
||||||
|
# Los 9 nodos nativos de Hunyuan3D-2 (sin custom node).
|
||||||
|
esperado = {
|
||||||
|
"LoadImage",
|
||||||
|
"ImageOnlyCheckpointLoader",
|
||||||
|
"CLIPVisionEncode",
|
||||||
|
"Hunyuan3Dv2Conditioning",
|
||||||
|
"EmptyLatentHunyuan3Dv2",
|
||||||
|
"KSampler",
|
||||||
|
"VAEDecodeHunyuan3D",
|
||||||
|
"VoxelToMeshBasic",
|
||||||
|
"SaveGLB",
|
||||||
|
}
|
||||||
|
assert class_types(wf) == esperado
|
||||||
|
|
||||||
|
|
||||||
|
def test_imagen_checkpoint_y_salida_glb():
|
||||||
|
wf = comfyui_build_image_to_3d_workflow(
|
||||||
|
"robot.png", ckpt_name="hunyuan3d-dit-v2-mini.safetensors", seed=42
|
||||||
|
)
|
||||||
|
assert node_by_ct(wf, "LoadImage")["inputs"]["image"] == "robot.png"
|
||||||
|
assert (
|
||||||
|
node_by_ct(wf, "ImageOnlyCheckpointLoader")["inputs"]["ckpt_name"]
|
||||||
|
== "hunyuan3d-dit-v2-mini.safetensors"
|
||||||
|
)
|
||||||
|
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)
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
"""Tests de estructura para comfyui_build_img2img_workflow (funcion pura)."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.dirname(__file__))
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||||
|
|
||||||
|
from ml.comfyui_build_img2img_workflow import comfyui_build_img2img_workflow
|
||||||
|
from _comfyui_wf_assert import assert_api_format, class_types, node_by_ct
|
||||||
|
|
||||||
|
|
||||||
|
def test_estructura_y_class_types():
|
||||||
|
wf = comfyui_build_img2img_workflow("ck.safetensors", "init.png", "POS", "NEG")
|
||||||
|
assert_api_format(wf)
|
||||||
|
# img2img usa VAEEncode (no EmptyLatentImage) para partir de la imagen base.
|
||||||
|
cts = class_types(wf)
|
||||||
|
assert "VAEEncode" in cts
|
||||||
|
assert "LoadImage" in cts
|
||||||
|
assert "EmptyLatentImage" not in cts
|
||||||
|
|
||||||
|
|
||||||
|
def test_denoise_y_init_image():
|
||||||
|
wf = comfyui_build_img2img_workflow(
|
||||||
|
"ck.safetensors", "init.png", "POS", "NEG", denoise=0.45, seed=7
|
||||||
|
)
|
||||||
|
ks = node_by_ct(wf, "KSampler")["inputs"]
|
||||||
|
assert ks["denoise"] == 0.45
|
||||||
|
assert ks["seed"] == 7
|
||||||
|
assert node_by_ct(wf, "LoadImage")["inputs"]["image"] == "init.png"
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
"""Tests de estructura para comfyui_build_inpaint_workflow (funcion pura)."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.dirname(__file__))
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||||
|
|
||||||
|
from ml.comfyui_build_inpaint_workflow import comfyui_build_inpaint_workflow
|
||||||
|
from _comfyui_wf_assert import assert_api_format, class_types, node_by_ct
|
||||||
|
|
||||||
|
|
||||||
|
def test_estructura_inpaint():
|
||||||
|
wf = comfyui_build_inpaint_workflow("ck.safetensors", "img.png", "mask.png", "POS", "NEG")
|
||||||
|
assert_api_format(wf)
|
||||||
|
cts = class_types(wf)
|
||||||
|
# Inpaint necesita la mascara y la codificacion para inpaint.
|
||||||
|
assert "LoadImageMask" in cts
|
||||||
|
assert "VAEEncodeForInpaint" in cts
|
||||||
|
|
||||||
|
|
||||||
|
def test_base_y_mascara_se_cargan():
|
||||||
|
wf = comfyui_build_inpaint_workflow(
|
||||||
|
"ck.safetensors", "base.png", "m.png", "POS", "NEG", seed=33, denoise=0.9
|
||||||
|
)
|
||||||
|
img = node_by_ct(wf, "LoadImage")["inputs"]["image"]
|
||||||
|
mask = node_by_ct(wf, "LoadImageMask")["inputs"]["image"]
|
||||||
|
assert img == "base.png"
|
||||||
|
assert mask == "m.png"
|
||||||
|
ks = node_by_ct(wf, "KSampler")["inputs"]
|
||||||
|
assert ks["seed"] == 33 and ks["denoise"] == 0.9
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
"""Tests de estructura para comfyui_build_sdxl_refiner_workflow (funcion pura)."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.dirname(__file__))
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||||
|
|
||||||
|
from ml.comfyui_build_sdxl_refiner_workflow import comfyui_build_sdxl_refiner_workflow
|
||||||
|
from _comfyui_wf_assert import assert_api_format, class_types
|
||||||
|
|
||||||
|
|
||||||
|
def test_dos_ksampler_advanced():
|
||||||
|
wf = comfyui_build_sdxl_refiner_workflow("base.st", "ref.st", "POS", "NEG")
|
||||||
|
assert_api_format(wf)
|
||||||
|
assert "KSamplerAdvanced" in class_types(wf)
|
||||||
|
ks = [n for n in wf.values() if n["class_type"] == "KSamplerAdvanced"]
|
||||||
|
assert len(ks) == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_base_emite_ruido_sobrante_y_refiner_lo_recoge():
|
||||||
|
wf = comfyui_build_sdxl_refiner_workflow(
|
||||||
|
"base.st", "ref.st", "POS", "NEG", base_steps=20, refiner_steps=5, seed=9
|
||||||
|
)
|
||||||
|
ks = [n["inputs"] for n in wf.values() if n["class_type"] == "KSamplerAdvanced"]
|
||||||
|
base = next(k for k in ks if k["add_noise"] == "enable")
|
||||||
|
refiner = next(k for k in ks if k["add_noise"] == "disable")
|
||||||
|
# El base arranca el ruido y deja el sobrante; el refiner lo termina sin add_noise.
|
||||||
|
assert base["return_with_leftover_noise"] == "enable"
|
||||||
|
assert refiner["return_with_leftover_noise"] == "disable"
|
||||||
|
# Comparten el corte de pasos: el base termina donde declara base_steps.
|
||||||
|
assert base["end_at_step"] == 20
|
||||||
|
assert refiner["start_at_step"] == 20
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
"""Tests de estructura para comfyui_build_txt2img_workflow (funcion pura)."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.dirname(__file__))
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||||
|
|
||||||
|
from ml.comfyui_build_txt2img_workflow import comfyui_build_txt2img_workflow
|
||||||
|
from _comfyui_wf_assert import assert_api_format, class_types, node_by_ct
|
||||||
|
|
||||||
|
|
||||||
|
def test_estructura_y_class_types():
|
||||||
|
wf = comfyui_build_txt2img_workflow("ck.safetensors", "POS", "NEG")
|
||||||
|
assert_api_format(wf)
|
||||||
|
assert class_types(wf) == {
|
||||||
|
"CheckpointLoaderSimple",
|
||||||
|
"CLIPTextEncode",
|
||||||
|
"EmptyLatentImage",
|
||||||
|
"KSampler",
|
||||||
|
"VAEDecode",
|
||||||
|
"SaveImage",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_params_se_reflejan_en_los_nodos():
|
||||||
|
wf = comfyui_build_txt2img_workflow(
|
||||||
|
"ck.safetensors", "POS", "NEG", steps=8, cfg=6.0, width=640, height=480, seed=123
|
||||||
|
)
|
||||||
|
ks = node_by_ct(wf, "KSampler")["inputs"]
|
||||||
|
assert ks["seed"] == 123
|
||||||
|
assert ks["steps"] == 8
|
||||||
|
assert ks["cfg"] == 6.0
|
||||||
|
lat = node_by_ct(wf, "EmptyLatentImage")["inputs"]
|
||||||
|
assert lat["width"] == 640 and lat["height"] == 480
|
||||||
|
assert node_by_ct(wf, "CheckpointLoaderSimple")["inputs"]["ckpt_name"] == "ck.safetensors"
|
||||||
|
textos = sorted(n["inputs"]["text"] for n in wf.values() if n["class_type"] == "CLIPTextEncode")
|
||||||
|
assert textos == ["NEG", "POS"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_filename_prefix_en_saveimage():
|
||||||
|
wf = comfyui_build_txt2img_workflow("ck.safetensors", "POS", filename_prefix="demo_run")
|
||||||
|
assert node_by_ct(wf, "SaveImage")["inputs"]["filename_prefix"] == "demo_run"
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
"""Tests de estructura para comfyui_build_upscale_workflow (funcion pura)."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.dirname(__file__))
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||||
|
|
||||||
|
from ml.comfyui_build_upscale_workflow import comfyui_build_upscale_workflow
|
||||||
|
from _comfyui_wf_assert import assert_api_format, class_types, node_by_ct
|
||||||
|
|
||||||
|
|
||||||
|
def test_method_model_usa_esrgan():
|
||||||
|
wf = comfyui_build_upscale_workflow("img.png", model_name="4x-UltraSharp.pth", method="model")
|
||||||
|
assert_api_format(wf)
|
||||||
|
cts = class_types(wf)
|
||||||
|
assert "UpscaleModelLoader" in cts
|
||||||
|
assert "ImageUpscaleWithModel" in cts
|
||||||
|
assert node_by_ct(wf, "UpscaleModelLoader")["inputs"]["model_name"] == "4x-UltraSharp.pth"
|
||||||
|
|
||||||
|
|
||||||
|
def test_method_latent_no_usa_modelo():
|
||||||
|
wf = comfyui_build_upscale_workflow("img.png", method="latent")
|
||||||
|
assert_api_format(wf)
|
||||||
|
cts = class_types(wf)
|
||||||
|
assert "ImageScaleBy" in cts
|
||||||
|
assert "UpscaleModelLoader" not in cts
|
||||||
|
assert node_by_ct(wf, "LoadImage")["inputs"]["image"] == "img.png"
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
"""Tests de estructura para comfyui_build_view_3d_workflow (funcion pura)."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.dirname(__file__))
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||||
|
|
||||||
|
from ml.comfyui_build_view_3d_workflow import comfyui_build_view_3d_workflow
|
||||||
|
from _comfyui_wf_assert import assert_api_format, class_types, node_by_ct
|
||||||
|
|
||||||
|
|
||||||
|
def test_load3d_simple():
|
||||||
|
wf = comfyui_build_view_3d_workflow("mesh.glb", width=800, height=600)
|
||||||
|
assert_api_format(wf)
|
||||||
|
# Visor minimo: un unico nodo Load3D.
|
||||||
|
assert class_types(wf) == {"Load3D"}
|
||||||
|
n = node_by_ct(wf, "Load3D")["inputs"]
|
||||||
|
assert n["model_file"] == "mesh.glb"
|
||||||
|
assert n["width"] == 800 and n["height"] == 600
|
||||||
|
|
||||||
|
|
||||||
|
def test_animation_usa_load3d_advanced():
|
||||||
|
wf = comfyui_build_view_3d_workflow("mesh.glb", animation=True)
|
||||||
|
assert class_types(wf) == {"Load3DAdvanced"}
|
||||||
|
assert node_by_ct(wf, "Load3DAdvanced")["inputs"]["model_file"] == "mesh.glb"
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
"""Tests de estructura y pureza para comfyui_inject_lora (funcion pura)."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.dirname(__file__))
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||||
|
|
||||||
|
from ml.comfyui_build_txt2img_workflow import comfyui_build_txt2img_workflow
|
||||||
|
from ml.comfyui_inject_lora import comfyui_inject_lora
|
||||||
|
from _comfyui_wf_assert import assert_api_format, class_types
|
||||||
|
|
||||||
|
|
||||||
|
def _lora_node_id(wf):
|
||||||
|
return next(nid for nid, n in wf.items() if n["class_type"] == "LoraLoader")
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_muta_la_entrada():
|
||||||
|
base = comfyui_build_txt2img_workflow("ck.safetensors", "POS", "NEG")
|
||||||
|
base_antes = {k: dict(v) for k, v in base.items()}
|
||||||
|
_ = comfyui_inject_lora(base, "style.safetensors")
|
||||||
|
# La copia profunda garantiza que el dict original queda intacto (pureza).
|
||||||
|
assert "LoraLoader" not in class_types(base)
|
||||||
|
assert set(base) == set(base_antes)
|
||||||
|
|
||||||
|
|
||||||
|
def test_inserta_loraloader_y_respeta_strength():
|
||||||
|
base = comfyui_build_txt2img_workflow("ck.safetensors", "POS", "NEG")
|
||||||
|
inj = comfyui_inject_lora(base, "style.safetensors", strength_model=0.7, strength_clip=0.4)
|
||||||
|
assert_api_format(inj)
|
||||||
|
assert "LoraLoader" in class_types(inj)
|
||||||
|
lid = _lora_node_id(inj)
|
||||||
|
lora_in = inj[lid]["inputs"]
|
||||||
|
assert lora_in["lora_name"] == "style.safetensors"
|
||||||
|
assert lora_in["strength_model"] == 0.7
|
||||||
|
assert lora_in["strength_clip"] == 0.4
|
||||||
|
|
||||||
|
|
||||||
|
def test_reconecta_el_ksampler_al_lora():
|
||||||
|
base = comfyui_build_txt2img_workflow("ck.safetensors", "POS", "NEG")
|
||||||
|
inj = comfyui_inject_lora(base, "style.safetensors")
|
||||||
|
lid = _lora_node_id(inj)
|
||||||
|
ks = next(n for n in inj.values() if n["class_type"] == "KSampler")
|
||||||
|
# El KSampler debe tomar el modelo del LoraLoader, no ya del checkpoint.
|
||||||
|
assert ks["inputs"]["model"][0] == lid
|
||||||
Reference in New Issue
Block a user