Files
Meisoftcoltd-comfyui-sequen…/loop.py
T
Meisoftcoltd e87cad300c feat: add explicit input validations to key nodes
- `loop.py`: Added check for `trigger_dependency` in `SequentialLoopTrigger.trigger_next`.
- `image.py`: Added checks for `generated_images` and `current_loop_index` in `SessionImageSender.set_image`.
- `video.py`: Added list-aware checks for `trigger` and `current_loop_index` in `IncrementalVideoStitcher.stitch_incremental` due to `INPUT_IS_LIST = True`.

All validations throw a `ValueError` with clear Spanish instructions when inputs are missing to provide immediate user feedback on the ComfyUI canvas.
2026-03-23 20:49:22 +00:00

117 lines
4.2 KiB
Python

import random
import time
import urllib.request
import json
from . import register_node
global_loop_index = 0
@register_node
class SequentialLoopStart:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"reset_loop": ("BOOLEAN", {"default": False}),
"loop_idx": ("INT", {"default": 0, "min": 0, "max": 10000}), # Inyectado por el Trigger
}
}
RETURN_TYPES = ("INT",)
RETURN_NAMES = ("current_loop_index",)
FUNCTION = "get_index"
CATEGORY = "🔁 Sequential Batcher/Loop"
@classmethod
def IS_CHANGED(cls, **kwargs):
return time.time()
def get_index(self, reset_loop, loop_idx):
global global_loop_index
print(f"\n{'='*50}")
print(f"🚀 [DEBUG] NODO: Loop Start")
print(f" -> Input loop_idx (UI/Trigger): {loop_idx}")
print(f" -> Input reset_loop: {reset_loop}")
is_reset = str(reset_loop).lower() in ['true', '1', 't', 'y']
if is_reset:
global_loop_index = 0
print(" -> 🔄 Bucle reiniciado a 0 manualmente.")
else:
global_loop_index = loop_idx
print(f" -> 📤 OUTPUT current_loop_index: {global_loop_index}")
print(f"{'='*50}\n")
return (global_loop_index, )
@register_node
class SequentialLoopTrigger:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"trigger_dependency": ("*", ),
"target_loops": ("INT", {"default": 4, "min": 1, "max": 1000}),
"port": ("INT", {"default": 8188, "min": 1000, "max": 9999}),
},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"}
}
RETURN_TYPES = ()
RETURN_NAMES = ()
OUTPUT_NODE = True
FUNCTION = "trigger_next"
CATEGORY = "🔁 Sequential Batcher/Loop"
@classmethod
def IS_CHANGED(cls, **kwargs):
return time.time()
def trigger_next(self, trigger_dependency, target_loops, port, prompt=None, extra_pnginfo=None):
if trigger_dependency is None:
raise ValueError("❌ ERROR CRÍTICO: El nodo 'Loop Trigger' no tiene nada conectado en 'trigger_dependency'. Debes conectar la salida del Stitcher para que el bucle pueda continuar.")
global global_loop_index
next_loop = global_loop_index + 1
print(f"\n{'='*50}")
print(f"🎯 [DEBUG] NODO: Loop Trigger")
print(f" -> Ciclo terminado: {global_loop_index} | Target: {target_loops}")
if next_loop < target_loops:
print(f" -> ⚙️ Preparando Ciclo {next_loop}...")
if prompt is not None:
m_seeds = 0
for node_id, node_data in prompt.items():
inputs = node_data.get("inputs", {})
# Mutar Semillas
for key in ["seed", "noise_seed"]:
if key in inputs and isinstance(inputs[key], (int, float)):
inputs[key] = random.randint(1, 0xffffffff)
m_seeds += 1
# 💥 INYECCIÓN ANTI-CACHÉ: Forzar a Loop Start a leer el nuevo índice
if node_data.get("class_type") == "SequentialLoopStart":
inputs["loop_idx"] = next_loop
inputs["reset_loop"] = False
print(f" -> 💉 Inyectado loop_idx={next_loop} en LoopStart (Nodo {node_id})")
print(f" -> 🎲 Semillas mutadas: {m_seeds}")
p = {"prompt": prompt}
if extra_pnginfo:
p["extra_data"] = {"extra_pnginfo": extra_pnginfo}
data = json.dumps(p).encode('utf-8')
req = urllib.request.Request(f"http://127.0.0.1:{port}/prompt", data=data, headers={'Content-Type': 'application/json'})
try:
urllib.request.urlopen(req, timeout=5)
print(f" -> ✅ Ciclo {next_loop} inyectado en la cola.")
except Exception as e:
print(f" -> ❌ Error HTTP: {e}")
else:
print(f" -> 🏁 ¡Generación finalizada! ({target_loops} ciclos)")
global_loop_index = 0
print(f"{'='*50}\n")
return ()