Spaces:
Runtime error
Runtime error
from comfy_execution.graph_utils import GraphBuilder, is_link | |
from .libs.utils import any_typ | |
class FloatRange: | |
def INPUT_TYPES(s): | |
return {"required": { | |
"start": ("FLOAT", {"default": 0.0, "min": -100.0, "max": 100.0, 'step': 0.000000001}), | |
"stop": ("FLOAT", {"default": 1.0, "min": -100.0, "max": 100.0, 'step': 0.000000001}), | |
"step": ("FLOAT", {"default": 0.01, "min": 0.0, "max": 100.0, 'step': 0.000000001}), | |
"limit": ("INT", {"default": 100, "min": 2, "max": 4096, "step": 1}), | |
"ensure_end": ("BOOLEAN", {"default": True, "label_on": "enable", "label_off": "disable"}), | |
} | |
} | |
RETURN_TYPES = ("FLOAT",) | |
OUTPUT_IS_LIST = (True,) | |
FUNCTION = "doit" | |
CATEGORY = "InspirePack/List" | |
def doit(self, start, stop, step, limit, ensure_end): | |
if start == stop or step == 0: | |
return ([start], ) | |
reverse = False | |
if start > stop: | |
reverse = True | |
start, stop = stop, start | |
res = [] | |
x = start | |
last = x | |
while x <= stop and limit > 0: | |
res.append(x) | |
last = x | |
limit -= 1 | |
x += step | |
if ensure_end and last != stop: | |
if len(res) >= limit: | |
res.pop() | |
res.append(stop) | |
if reverse: | |
res.reverse() | |
return (res, ) | |
class WorklistToItemList: | |
def INPUT_TYPES(s): | |
return {"required": { | |
"item": (any_typ, ), | |
} | |
} | |
INPUT_IS_LIST = True | |
RETURN_TYPES = ("ITEM_LIST",) | |
RETURN_NAMES = ("item_list",) | |
FUNCTION = "doit" | |
DESCRIPTION = "The list in ComfyUI allows for repeated execution of a sub-workflow.\nThis groups these repetitions (a.k.a. list) into a single ITEM_LIST output.\nITEM_LIST can then be used in ForeachList." | |
CATEGORY = "InspirePack/List" | |
def doit(self, item): | |
return (item, ) | |
# Loop nodes are implemented based on BadCafeCode's reference loop implementation | |
# https://github.com/BadCafeCode/execution-inversion-demo-comfyui/blob/main/flow_control.py | |
class ForeachListBegin: | |
def INPUT_TYPES(s): | |
return {"required": { | |
"item_list": ("ITEM_LIST", {"tooltip": "ITEM_LIST containing items to be processed iteratively."}), | |
}, | |
"optional": { | |
"initial_input": (any_typ, {"tooltip": "If initial_input is omitted, the first item in item_list is used as the initial value, and the processing starts from the second item in item_list."}), | |
} | |
} | |
RETURN_TYPES = ("FOREACH_LIST_CONTROL", "ITEM_LIST", any_typ, any_typ) | |
RETURN_NAMES = ("flow_control", "remained_list", "item", "intermediate_output") | |
OUTPUT_TOOLTIPS = ( | |
"Pass ForeachListEnd as is to indicate the end of the iteration.", | |
"Output the ITEM_LIST containing the remaining items during the iteration, passing ForeachListEnd as is to indicate the end of the iteration.", | |
"Output the current item during the iteration.", | |
"Output the intermediate results during the iteration.") | |
FUNCTION = "doit" | |
DESCRIPTION = "A starting node for performing iterative tasks by retrieving items one by one from the ITEM_LIST.\nGenerate a new intermediate_output using item and intermediate_output as inputs, then connect it to ForeachListEnd.\nNOTE:If initial_input is omitted, the first item in item_list is used as the initial value, and the processing starts from the second item in item_list." | |
CATEGORY = "InspirePack/List" | |
def doit(self, item_list, initial_input=None): | |
if initial_input is None: | |
initial_input = item_list[0] | |
item_list = item_list[1:] | |
if len(item_list) > 0: | |
return ("stub", item_list[1:], item_list[0], initial_input) | |
return ("stub", [], None, initial_input) | |
class ForeachListEnd: | |
def INPUT_TYPES(s): | |
return {"required": { | |
"flow_control": ("FOREACH_LIST_CONTROL", {"rawLink": True, "tooltip": "Directly connect the output of ForeachListBegin, the starting node of the iteration."}), | |
"remained_list": ("ITEM_LIST", {"tooltip":"Directly connect the output of ForeachListBegin, the starting node of the iteration."}), | |
"intermediate_output": (any_typ, {"tooltip":"Connect the intermediate outputs processed within the iteration here."}), | |
}, | |
"hidden": { | |
"dynprompt": "DYNPROMPT", | |
"unique_id": "UNIQUE_ID", | |
} | |
} | |
RETURN_TYPES = (any_typ,) | |
RETURN_NAMES = ("result",) | |
OUTPUT_TOOLTIPS = ("This is the final output value.",) | |
FUNCTION = "doit" | |
DESCRIPTION = "A end node for performing iterative tasks by retrieving items one by one from the ITEM_LIST.\nNOTE:Directly connect the outputs of ForeachListBegin to 'flow_control' and 'remained_list'." | |
CATEGORY = "InspirePack/List" | |
def explore_dependencies(self, node_id, dynprompt, upstream): | |
node_info = dynprompt.get_node(node_id) | |
if "inputs" not in node_info: | |
return | |
for k, v in node_info["inputs"].items(): | |
if is_link(v): | |
parent_id = v[0] | |
if parent_id not in upstream: | |
upstream[parent_id] = [] | |
self.explore_dependencies(parent_id, dynprompt, upstream) | |
upstream[parent_id].append(node_id) | |
def collect_contained(self, node_id, upstream, contained): | |
if node_id not in upstream: | |
return | |
for child_id in upstream[node_id]: | |
if child_id not in contained: | |
contained[child_id] = True | |
self.collect_contained(child_id, upstream, contained) | |
def doit(self, flow_control, remained_list, intermediate_output, dynprompt, unique_id): | |
if len(remained_list) == 0: | |
return (intermediate_output,) | |
# We want to loop | |
this_node = dynprompt.get_node(unique_id) | |
upstream = {} | |
# Get the list of all nodes between the open and close nodes | |
self.explore_dependencies(unique_id, dynprompt, upstream) | |
contained = {} | |
open_node = flow_control[0] | |
self.collect_contained(open_node, upstream, contained) | |
contained[unique_id] = True | |
contained[open_node] = True | |
# We'll use the default prefix, but to avoid having node names grow exponentially in size, | |
# we'll use "Recurse" for the name of the recursively-generated copy of this node. | |
graph = GraphBuilder() | |
for node_id in contained: | |
original_node = dynprompt.get_node(node_id) | |
node = graph.node(original_node["class_type"], "Recurse" if node_id == unique_id else node_id) | |
node.set_override_display_id(node_id) | |
for node_id in contained: | |
original_node = dynprompt.get_node(node_id) | |
node = graph.lookup_node("Recurse" if node_id == unique_id else node_id) | |
for k, v in original_node["inputs"].items(): | |
if is_link(v) and v[0] in contained: | |
parent = graph.lookup_node(v[0]) | |
node.set_input(k, parent.out(v[1])) | |
else: | |
node.set_input(k, v) | |
new_open = graph.lookup_node(open_node) | |
new_open.set_input("item_list", remained_list) | |
new_open.set_input("initial_input", intermediate_output) | |
my_clone = graph.lookup_node("Recurse" ) | |
result = (my_clone.out(0),) | |
return { | |
"result": result, | |
"expand": graph.finalize(), | |
} | |
NODE_CLASS_MAPPINGS = { | |
"FloatRange //Inspire": FloatRange, | |
"WorklistToItemList //Inspire": WorklistToItemList, | |
"ForeachListBegin //Inspire": ForeachListBegin, | |
"ForeachListEnd //Inspire": ForeachListEnd, | |
} | |
NODE_DISPLAY_NAME_MAPPINGS = { | |
"FloatRange //Inspire": "Float Range (Inspire)", | |
"WorklistToItemList //Inspire": "Worklist To Item List (Inspire)", | |
"ForeachListBegin //Inspire": "▶Foreach List (Inspire)", | |
"ForeachListEnd //Inspire": "Foreach List◀ (Inspire)", | |
} | |