3281 lines
100 KiB
Python
3281 lines
100 KiB
Python
"""
|
|
@author: Trung0246
|
|
@title: ComfyUI-0246
|
|
@nickname: ComfyUI-0246
|
|
@description: Random nodes for ComfyUI I made to solve my struggle with ComfyUI (ex: pipe, process). Have varying quality.
|
|
"""
|
|
|
|
# Built-in
|
|
import sys
|
|
import ast
|
|
import random
|
|
import json
|
|
import copy
|
|
import functools
|
|
import itertools
|
|
import copy
|
|
import uuid
|
|
import unicodedata
|
|
import struct
|
|
import inspect
|
|
|
|
builtins = __import__("builtins")
|
|
re = __import__("re")
|
|
|
|
# Self Code
|
|
from . import utils as lib0246
|
|
|
|
# 3rd Party
|
|
import aiohttp.web
|
|
import natsort
|
|
import regex
|
|
import torch
|
|
|
|
# ComfyUI
|
|
import server
|
|
import execution
|
|
import nodes
|
|
import comfy.sd1_clip
|
|
import comfy.samplers
|
|
|
|
comfy_graph = None
|
|
comfy_graph_utils = None
|
|
wat = None
|
|
|
|
try:
|
|
wat = __import__("wat")
|
|
comfy_graph = __import__("comfy_execution.graph").graph
|
|
comfy_graph_utils = __import__("comfy_execution.graph_utils").graph_utils
|
|
print("\033[95m" + f"{lib0246.HEAD_LOG}Topological Execution is detected." + "\033[0m")
|
|
except ModuleNotFoundError:
|
|
pass
|
|
|
|
NODE_CLASS_MAPPINGS = {}
|
|
NODE_DISPLAY_NAME_MAPPINGS = {}
|
|
|
|
######################################################################################
|
|
######################################## IMPL ########################################
|
|
######################################################################################
|
|
|
|
def highway_impl(_prompt, _id, _workflow, _way_in, flag, kwargs):
|
|
if isinstance(_prompt, list):
|
|
_prompt = _prompt[0]
|
|
if isinstance(_id, list):
|
|
_id = _id[0]
|
|
if isinstance(_workflow, list):
|
|
_workflow = _workflow[0]
|
|
|
|
if isinstance(_way_in, list):
|
|
_way_in = _way_in[0]
|
|
|
|
if _way_in is None:
|
|
_way_in = lib0246.RevisionDict()
|
|
else:
|
|
_way_in = lib0246.RevisionDict(_way_in)
|
|
|
|
# _way_in._id = _id
|
|
# _way_in.purge(_way_in.find(lambda item: item.id == _id))
|
|
|
|
# Time to let the magic play out
|
|
|
|
curr_node = next(_ for _ in _workflow["workflow"]["nodes"] if str(_["id"]) == _id)
|
|
|
|
for i, curr_input in enumerate(curr_node["inputs"]):
|
|
if curr_input["name"] in kwargs:
|
|
name = _workflow["workflow"]["extra"]["0246.__NAME__"][_id]["inputs"][str(i)]["name"][1:]
|
|
if flag:
|
|
_way_in[("data", name)] = lib0246.RevisionBatch(*kwargs[curr_input["name"]])
|
|
else:
|
|
_way_in[("data", name)] = kwargs[curr_input["name"]]
|
|
_way_in[("type", name)] = curr_input.get("type", "*") # Sometimes this does not exist. Weird.
|
|
|
|
res = []
|
|
|
|
for i, curr_output in enumerate(curr_node["outputs"]):
|
|
if curr_output["name"] not in lib0246.BLACKLIST:
|
|
name = _workflow["workflow"]["extra"]["0246.__NAME__"][_id]["outputs"][str(i)]["name"][1:]
|
|
if ("data", name) in _way_in:
|
|
if curr_output.get("links") is None:
|
|
res.append([None])
|
|
elif curr_output["type"] == "*" or _way_in[("type", name)] == "*" or curr_output["type"] == _way_in[("type", name)]:
|
|
res.append(_way_in[("data", name)])
|
|
else:
|
|
raise Exception(f"Output \"{name}\" is not defined or is not of type \"{curr_output['type']}\". Expected \"{_way_in[('type', name)]}\".")
|
|
|
|
_way_in[("kind")] = "highway"
|
|
_way_in[("id")] = _id
|
|
|
|
return (_way_in, ) + tuple(res)
|
|
|
|
def gather_highway_impl(_dict_list, _id):
|
|
new_dict = lib0246.RevisionDict()
|
|
|
|
if _dict_list is None:
|
|
return new_dict
|
|
|
|
if isinstance(_id, list):
|
|
_id = _id[0]
|
|
|
|
for elem in _dict_list:
|
|
iter_inst = elem.path_iter(("data", ))
|
|
for key in iter_inst:
|
|
if ("type", key[1]) not in new_dict:
|
|
new_dict[("type", key[1])] = elem[("type", key[1])]
|
|
if ("data", key[1]) not in new_dict:
|
|
new_dict[("data", key[1])] = lib0246.RevisionBatch()
|
|
if isinstance(elem[("data", key[1])], lib0246.RevisionBatch):
|
|
new_dict[("data", key[1])].extend(elem[("data", key[1])])
|
|
else:
|
|
new_dict[("data", key[1])].append(elem[("data", key[1])])
|
|
|
|
new_dict[("kind")] = "highway"
|
|
new_dict[("id")] = _id
|
|
|
|
return new_dict
|
|
|
|
def junction_impl(self, _id, _prompt, _workflow, _junc_in, _offset = None, _in_mode = False, _out_mode = False, _offset_mode = False, **kwargs):
|
|
if isinstance(_prompt, list):
|
|
_prompt = _prompt[0]
|
|
if isinstance(_id, list):
|
|
_id = _id[0]
|
|
if isinstance(_offset, list):
|
|
_offset = _offset[0]
|
|
if isinstance(_workflow, list):
|
|
_workflow = _workflow[0]
|
|
|
|
if _junc_in is None:
|
|
_junc_in = lib0246.RevisionDict()
|
|
else:
|
|
_junc_in = lib0246.RevisionDict(_junc_in)
|
|
|
|
# _junc_in._id = _id
|
|
# _junc_in.purge(_junc_in.find(lambda item: item.id == _id))
|
|
|
|
# Pack all data from _junc_in and kwargs together with a specific format
|
|
|
|
curr_node = next(_ for _ in _workflow["workflow"]["nodes"] if str(_["id"]) == _id)
|
|
|
|
if _in_mode:
|
|
flat_iter = lib0246.FlatIter(kwargs)
|
|
for param, (key, value) in lib0246.flat_zip(list(filter(lambda _: _["name"] not in lib0246.BLACKLIST, curr_node["inputs"])), flat_iter):
|
|
junction_pack_loop(_junc_in, param["type"], value)
|
|
else:
|
|
for param, key in zip(list(filter(lambda _: _["name"] not in lib0246.BLACKLIST, curr_node["inputs"])), list(kwargs)):
|
|
junction_pack_loop(_junc_in, param["type"], kwargs[key])
|
|
|
|
# Parse the offset string
|
|
|
|
if hasattr(self, "_prev_offset") and hasattr(self, "_parsed_offset") and _offset is not None:
|
|
if type(_offset) is str:
|
|
_offset = ast.literal_eval(_offset)
|
|
if _offset["data"] != self._prev_offset:
|
|
parsed_offset, err = lib0246.parse_offset(_offset["data"])
|
|
if err:
|
|
raise Exception(err)
|
|
self._prev_offset = _offset["data"]
|
|
self._parsed_offset = parsed_offset
|
|
|
|
# Apply the offset to the junction input
|
|
|
|
if hasattr(self, "_parsed_offset"):
|
|
if self._parsed_offset is None:
|
|
raise Exception("Offset is not parsed.")
|
|
|
|
for elem in self._parsed_offset:
|
|
total = _junc_in.path_count(("data", elem[0]))
|
|
if total == 0:
|
|
raise Exception(f"Type \"{elem[0]}\" in offset string does not available in junction.")
|
|
|
|
# Check for ops char
|
|
|
|
if elem[1][0] == '+':
|
|
_junc_in[("index", elem[0])] += int(elem[1][1:])
|
|
elif elem[1][0] == '-':
|
|
_junc_in[("index", elem[0])] -= int(elem[1][1:])
|
|
else:
|
|
_junc_in[("index", elem[0])] = int(elem[1])
|
|
|
|
temp = _junc_in[("index", elem[0])]
|
|
if temp >= total:
|
|
raise Exception(f"Offset \"{elem[1]}\" (total: \"{temp}\") is too large (count: \"{total}\").")
|
|
elif temp < 0:
|
|
raise Exception(f"Offset \"{elem[1]}\" (total: \"{temp}\") is too small (count: \"{total}\").")
|
|
|
|
res = []
|
|
track = {}
|
|
db = {}
|
|
|
|
if _out_mode:
|
|
done_type = {}
|
|
for elem in curr_node["outputs"]:
|
|
if elem["name"] in lib0246.BLACKLIST:
|
|
continue
|
|
|
|
if elem["type"] in done_type:
|
|
# Rotate the list from [11, 22, 33] to [22, 33, 11]
|
|
if elem["type"] not in db:
|
|
db[elem["type"]] = done_type[elem["type"]]
|
|
db[elem["type"]] = (db[elem["type"]][1:] + db[elem["type"]][:1])
|
|
res.append(db[elem["type"]])
|
|
continue
|
|
|
|
total = _junc_in.path_count(("data", elem["type"]))
|
|
if total == 0:
|
|
raise Exception(f"Type \"{elem['type']}\" of output \"{elem['name']}\" does not available in junction.")
|
|
|
|
offset = _junc_in[("index", elem["type"])]
|
|
|
|
if offset >= total:
|
|
raise Exception(f"Too much type \"{elem['type']}\" being taken or offset \"{offset}\" is too large (count: \"{total}\").")
|
|
|
|
temp = []
|
|
res.append(temp)
|
|
|
|
for i in range(offset, total):
|
|
temp.append(_junc_in[("data", elem["type"], i)])
|
|
|
|
done_type[elem["type"]] = temp
|
|
|
|
# Check if every single array in done_type have same length
|
|
base_len = -1
|
|
base_type = None
|
|
for key in done_type:
|
|
curr_len = len(done_type[key])
|
|
if base_len == -1:
|
|
base_len = curr_len
|
|
base_type = key
|
|
elif curr_len != base_len:
|
|
print("\033[93m" + f"{lib0246.HEAD_LOG}WARNING: Type \"{key}\" has different amount (node {_id}, got {curr_len}, want {base_len} from first type \"{base_type}\")." + "\033[0m")
|
|
else:
|
|
for key in _junc_in.path_iter(("type", )):
|
|
track[key[1]] = 0
|
|
|
|
for elem in curr_node["outputs"]:
|
|
if elem["name"] in lib0246.BLACKLIST:
|
|
continue
|
|
|
|
total = _junc_in.path_count(("data", elem["type"]))
|
|
if total == 0:
|
|
raise Exception(f"Type \"{elem['type']}\" of output \"{elem['name']}\" does not available in junction.")
|
|
|
|
offset = _junc_in[("index", elem["type"])]
|
|
real_index = track[elem["type"]] + offset
|
|
|
|
if real_index >= total:
|
|
raise Exception(f"Too much type \"{elem['type']}\" being taken or offset \"{offset}\" is too large (count: \"{total}\").")
|
|
|
|
res.append(_junc_in[("data", elem["type"], real_index)])
|
|
track[elem["type"]] += 1
|
|
|
|
_junc_in[("kind")] = "junction"
|
|
_junc_in[("id")] = _id
|
|
|
|
return (_junc_in, ) + tuple(res)
|
|
|
|
def gather_junction_impl(_dict_list, _id):
|
|
new_dict = lib0246.RevisionDict()
|
|
|
|
if _dict_list is None:
|
|
return new_dict
|
|
|
|
if isinstance(_id, list):
|
|
_id = _id[0]
|
|
|
|
for _dict in _dict_list:
|
|
for tuple_key in _dict.path_iter(("data", )):
|
|
junction_pack_loop(new_dict, tuple_key[1], _dict[tuple_key])
|
|
|
|
new_dict[("kind")] = "junction"
|
|
new_dict[("id")] = _id
|
|
|
|
return new_dict
|
|
|
|
def junction_unpack_raw(
|
|
data_dict, param_dict, key_list, regex_inst,
|
|
base_dict = {}, type_dict = {}, key_special=("default", "data", "index"),
|
|
pack_func=lambda _: _, type_func=lambda _: _,
|
|
fill_func=lambda d, k, v: d.setdefault(k, v),
|
|
stub_flag=False,
|
|
block=1,
|
|
):
|
|
for key in data_dict:
|
|
if key[0] == key_special[2]:
|
|
type_dict[type_func(key[1])] = data_dict[key]
|
|
|
|
for param_key in lib0246.dict_iter(param_dict):
|
|
type_dict.setdefault(type_func(lib0246.dict_get(param_dict, param_key)[0]), 0)
|
|
|
|
block_count = 0
|
|
kill_flag = False
|
|
def block_evt():
|
|
nonlocal block_count
|
|
nonlocal kill_flag
|
|
kill_flag = True
|
|
block_count += 1
|
|
return block_count >= block
|
|
|
|
param_iter = lib0246.cycle_iter(
|
|
block_evt,
|
|
filter(
|
|
lambda _: _[0] in key_list,
|
|
lib0246.dict_iter(param_dict)
|
|
),
|
|
)
|
|
|
|
while block_count < block or block == sys.maxsize:
|
|
try:
|
|
param_key = next(param_iter)
|
|
param_tuple = lib0246.dict_get(param_dict, param_key)
|
|
defaults = {} if len(param_tuple) == 1 else param_tuple[1]
|
|
|
|
regex_res = regex_inst.match(param_key[-1])
|
|
if regex_res is not None and regex_res.lastgroup == "_":
|
|
continue
|
|
|
|
data_key = (key_special[1], type_func(param_tuple[0]), type_dict.get(type_func(param_tuple[0]), 0))
|
|
value = None
|
|
if data_key in data_dict:
|
|
value = data_dict[data_key]
|
|
kill_flag = False
|
|
elif key_special[0] in defaults and (block < sys.maxsize or stub_flag):
|
|
if param_key[-1] in base_dict:
|
|
break
|
|
value = defaults[key_special[0]]
|
|
kill_flag = False
|
|
else:
|
|
break
|
|
fill_func(base_dict, param_key[-1], pack_func(value))
|
|
type_dict[type_func(param_tuple[0])] += 1
|
|
except StopIteration:
|
|
break
|
|
if kill_flag:
|
|
break
|
|
|
|
return base_dict
|
|
|
|
def junction_pack_loop(_junc_in, name, value):
|
|
_junc_in[("type", name)] = type(value).__name__
|
|
count = _junc_in.path_count(("data", name))
|
|
_junc_in[("data", name, count)] = value
|
|
if count == 0:
|
|
_junc_in[("index", name)] = 0
|
|
|
|
def trace_node_func(id_stk, linked_node_id):
|
|
id_stk.append(str(linked_node_id))
|
|
|
|
def trace_node(_prompt, _id, _workflow, _input = False, _func = trace_node_func):
|
|
id_stk = []
|
|
if _input:
|
|
for key in _prompt[_id]["inputs"]:
|
|
if isinstance(_prompt[_id]["inputs"][key], list) and key != "_event":
|
|
id_stk.append(_prompt[_id]["inputs"][key][0])
|
|
else:
|
|
id_stk.append(_id)
|
|
|
|
id_res = set()
|
|
|
|
while len(id_stk) > 0:
|
|
curr_id = id_stk.pop(0)
|
|
if curr_id not in id_res:
|
|
id_res.add(curr_id)
|
|
for node in _workflow["workflow"]["nodes"]:
|
|
if node["id"] == int(curr_id):
|
|
if node.get("outputs"):
|
|
for output in node["outputs"]:
|
|
if output.get("links"):
|
|
for link in output["links"]:
|
|
linked_node_id = find_input_node(_workflow["workflow"]["nodes"], link)
|
|
if linked_node_id is not None:
|
|
_func(id_stk, linked_node_id)
|
|
|
|
return id_res
|
|
|
|
def trace_node_back(node_id, dynprompt, upstream):
|
|
stack = [node_id]
|
|
while len(stack) > 0:
|
|
node_id = stack.pop()
|
|
node_info = dynprompt.get_node(node_id)
|
|
if "inputs" not in node_info:
|
|
continue
|
|
for k, v in node_info["inputs"].items():
|
|
if comfy_graph_utils.is_link(v):
|
|
parent_id = v[0]
|
|
if parent_id not in upstream:
|
|
upstream[parent_id] = []
|
|
stack.append(parent_id)
|
|
upstream[parent_id].append(node_id)
|
|
|
|
def trace_node_front(node_id, upstream, contained):
|
|
stack = [node_id]
|
|
while len(stack) > 0:
|
|
node_id = stack.pop()
|
|
if node_id not in upstream:
|
|
continue
|
|
for child_id in upstream[node_id]:
|
|
if child_id not in contained:
|
|
contained[child_id] = True
|
|
stack.append(child_id)
|
|
|
|
def find_input_node(nodes, link):
|
|
for node in nodes:
|
|
if node.get("inputs"):
|
|
for input in node["inputs"]:
|
|
if input["link"] == link:
|
|
return node["id"]
|
|
return None
|
|
|
|
class ScriptData(dict):
|
|
def __init__(self, data):
|
|
super().__init__(data)
|
|
|
|
def script_node_exec(node_inst, node_name, node_class, node_input, node_args, node_args_base, node_args_hide, node_args_hide_full, **kwargs):
|
|
if "pin" in kwargs:
|
|
func = getattr(node_inst, getattr(node_class, "FUNCTION"))
|
|
real_pin = {k: v for k, v in kwargs["pin"].items() if k in node_args_base}
|
|
flag = hasattr(node_class, "INPUT_IS_LIST") and node_class.INPUT_IS_LIST
|
|
for key, kind in node_args_hide_full:
|
|
match kind:
|
|
case "PROMPT":
|
|
real_pin[key] = [kwargs["inst"]["prompt"]] if flag else kwargs["inst"]["prompt"]
|
|
case "UNIQUE_ID":
|
|
real_pin[key] = [kwargs["inst"]["id"][-1]] if flag else kwargs["inst"]["id"][-1]
|
|
case "EXTRA_PNGINFO":
|
|
real_pin[key] = [kwargs["inst"]["workflow"]] if flag else kwargs["inst"]["workflow"]
|
|
if flag:
|
|
return lib0246.transpose(func(**real_pin), tuple)
|
|
else:
|
|
return func(**real_pin)
|
|
else:
|
|
return (node_class, node_inst, node_name, node_input, node_args, node_args_base, node_args_hide)
|
|
|
|
def script_rule_slice(func, res, pin, **kwargs):
|
|
return res.extend(func(pin=curr_pin[0]) for curr_pin in lib0246.dict_slice(pin))
|
|
|
|
def script_rule_product(func, res, pin, **kwargs):
|
|
return res.extend(func(pin=curr_pin[0]) for curr_pin in lib0246.dict_product(pin))
|
|
|
|
def script_rule_direct(func, res, pin, **kwargs):
|
|
return res.extend(func(pin=pin))
|
|
|
|
def highway_unpack(pipe_in):
|
|
def temp_func(pin, res, **kwargs):
|
|
if res is None:
|
|
iter_inst = pipe_in.path_iter(("data", ))
|
|
for key in iter_inst:
|
|
if isinstance(pipe_in[key], lib0246.RevisionBatch):
|
|
if key[1] in pin:
|
|
pin[key[1]].extend(pipe_in[key])
|
|
else:
|
|
pin[key[1]] = pipe_in[key]
|
|
else:
|
|
if key[1] in pin:
|
|
pin[key[1]].append(pipe_in[key])
|
|
else:
|
|
pin[key[1]] = [pipe_in[key]]
|
|
return True
|
|
return False
|
|
return temp_func
|
|
|
|
def junction_unpack(pipe_in, input_type, regex_inst):
|
|
def temp_func(pin, res, **kwargs):
|
|
if res is None:
|
|
junction_unpack_raw(
|
|
pipe_in, input_type,
|
|
list(filter(lambda x: x != "hidden", input_type.keys())),
|
|
base_dict=pin,
|
|
pack_func=lambda _: [_],
|
|
type_func=lambda _: "STRING" if isinstance(_, list) else _,
|
|
fill_func=lambda d, k, v: d.setdefault(k, []).extend(v),
|
|
stub_flag=True,
|
|
regex_inst=regex_inst,
|
|
block=sys.maxsize,
|
|
)
|
|
return True
|
|
return False
|
|
return temp_func
|
|
|
|
class EventBoolStr(str):
|
|
def __ne__(self, other):
|
|
return other != "EVENT_TYPE" and other != "BOOL" and other != "BOOLEAN" and other != "toggle"
|
|
|
|
CLOUD_METHOD = {
|
|
"text": None, # Cannot be used as function if None
|
|
"pin": None,
|
|
"weight": {
|
|
"bind": False, # Can affect other clouds if itself is affected
|
|
# "many": False, # Can output multiple clouds
|
|
"sole": True, # Can only exist once for same kind within a group
|
|
},
|
|
"rand": {
|
|
"bind": True,
|
|
# "many": True,
|
|
"sole": True,
|
|
},
|
|
"cycle": {
|
|
"bind": True,
|
|
# "many": True,
|
|
"sole": True,
|
|
},
|
|
"merge": {
|
|
"bind": True,
|
|
# "many": False,
|
|
"sole": True,
|
|
},
|
|
}
|
|
|
|
STR_BRACKET = r"([()])"
|
|
STR_REPLACE = r"\\\1"
|
|
|
|
def group_query_inst(group_dict, group_id, group_list = None, inst_curr = None):
|
|
inst_curr = set() if inst_curr is None else inst_curr
|
|
group_list = list(group_dict.keys()) if group_list is None else group_list
|
|
stack = [group_id]
|
|
seen = set()
|
|
while len(stack) > 0:
|
|
track_group = stack.pop()
|
|
if track_group in seen:
|
|
continue
|
|
seen.add(track_group)
|
|
if "group" in group_dict[track_group]:
|
|
stack.extend(group_dict[track_group]["group"])
|
|
if "inst" in group_dict[track_group]:
|
|
for track_inst in group_dict[track_group]["inst"]:
|
|
inst_curr.add(track_inst)
|
|
return inst_curr
|
|
|
|
class CloudFunc:
|
|
def __init__(self, kind):
|
|
self.func = getattr(CloudFunc, f"func_{kind}")
|
|
|
|
@classmethod
|
|
def func_rand(cls, obj, hold, state):
|
|
res = []
|
|
inst_id = obj.inst[state["index"]]["id"]
|
|
if inst_id not in state["data"]:
|
|
state["data"][inst_id] = {
|
|
"rand": random.Random(),
|
|
"seed_mode": [],
|
|
"seed_data": [],
|
|
"seed_count": 0
|
|
}
|
|
|
|
curr_state = state["data"][inst_id]
|
|
|
|
curr_state["seed_count"] = len(obj.inst[state["index"]]["widgets_values"][0])
|
|
curr_seed_len = len(curr_state["seed_data"])
|
|
if curr_state["seed_count"] > curr_seed_len:
|
|
curr_state["seed_data"].extend([None] * (curr_state["seed_count"] - curr_seed_len))
|
|
curr_state["seed_mode"].extend([None] * (curr_state["seed_count"] - curr_seed_len))
|
|
elif curr_state["seed_count"] < curr_seed_len:
|
|
curr_state["seed_data"] = curr_state["seed_data"][:curr_state["seed_count"]]
|
|
curr_state["seed_mode"] = curr_state["seed_mode"][:curr_state["seed_count"]]
|
|
|
|
for i, curr_seed, curr_count, curr_order, curr_mode in zip(itertools.count(start=0, step=1), *obj.inst[state["index"]]["widgets_values"]):
|
|
if curr_state["seed_data"][i] is None or \
|
|
curr_state["seed_mode"][i] != curr_mode or \
|
|
state["change"]:
|
|
curr_state["seed_data"][i] = curr_seed
|
|
curr_state["seed_mode"][i] = curr_mode
|
|
curr_state["rand"].seed(curr_seed)
|
|
if curr_mode != "fix":
|
|
PROMPT_UPDATE.add(state["id"])
|
|
else:
|
|
match curr_mode:
|
|
case "fix":
|
|
curr_state["rand"].seed(curr_seed)
|
|
case "add":
|
|
curr_state["seed_data"][i] += 1
|
|
curr_state["rand"].seed(curr_state["seed_data"][i])
|
|
case "sub":
|
|
curr_state["seed_data"][i] -= 1
|
|
curr_state["rand"].seed(curr_state["seed_data"][i])
|
|
case _:
|
|
# Default to "rand" mode
|
|
pass
|
|
|
|
if curr_mode != "fix":
|
|
PROMPT_UPDATE.add(state["id"])
|
|
|
|
if curr_order:
|
|
lib0246.sort_dict_of_list(hold, "index")
|
|
res.extend(lib0246.random_order(hold["data"], min(len(hold["data"]), curr_count), curr_state["rand"]))
|
|
else:
|
|
choice_list: list = copy.copy(hold["data"])
|
|
for i in range(curr_count):
|
|
curr_choice_idx = curr_state["rand"].randint(0, len(choice_list) - 1)
|
|
res.append(choice_list[curr_choice_idx])
|
|
choice_list.pop(curr_choice_idx)
|
|
|
|
for i in range(len(hold["index"])):
|
|
hold["index"][i] = None
|
|
|
|
return res
|
|
|
|
@classmethod
|
|
def func_cycle(cls, obj, hold, state):
|
|
res = []
|
|
inst_id = obj.inst[state["index"]]["id"]
|
|
if inst_id not in state["data"]:
|
|
state["data"][inst_id] = {
|
|
"track_step": [],
|
|
"track_data": [],
|
|
"track_count": 0
|
|
}
|
|
|
|
curr_state = state["data"][inst_id]
|
|
|
|
curr_state["track_count"] = len(obj.inst[state["index"]]["widgets_values"][0])
|
|
curr_track_len = len(curr_state["track_data"])
|
|
if curr_state["track_count"] > curr_track_len:
|
|
curr_state["track_data"].extend([None] * (curr_state["track_count"] - curr_track_len))
|
|
curr_state["track_step"].extend([None] * (curr_state["track_count"] - curr_track_len))
|
|
elif curr_state["track_count"] < curr_track_len:
|
|
curr_state["track_data"] = curr_state["track_data"][:curr_state["track_count"]]
|
|
curr_state["track_step"] = curr_state["track_step"][:curr_state["track_count"]]
|
|
|
|
for i, curr_offset, curr_step, curr_space, curr_count in zip(itertools.count(start=0, step=1), *obj.inst[state["index"]]["widgets_values"]):
|
|
if curr_state["track_data"][i] is None or \
|
|
curr_state["track_step"][i] != curr_step or \
|
|
state["change"]:
|
|
curr_state["track_data"][i] = curr_offset
|
|
curr_state["track_step"][i] = curr_step
|
|
if curr_step != 0:
|
|
PROMPT_UPDATE.add(state["id"])
|
|
else:
|
|
curr_state["track_data"][i] += curr_step
|
|
|
|
if curr_step != 0:
|
|
PROMPT_UPDATE.add(state["id"])
|
|
|
|
lib0246.sort_dict_of_list(hold, "index")
|
|
for i, track in zip(itertools.count(start=curr_state["track_data"][i], step=curr_space), range(curr_count)):
|
|
res.append(hold["data"][i % len(hold["data"])])
|
|
|
|
for i in range(len(hold["index"])):
|
|
hold["index"][i] = None
|
|
|
|
return res
|
|
|
|
@classmethod
|
|
def func_text(cls, obj, hold, state):
|
|
return obj.inst[state["index"]]["widgets_values"][0]
|
|
|
|
@classmethod
|
|
def func_weight(cls, obj, hold, state):
|
|
hold["data"] = list(map(lambda _: f"({re.sub(STR_BRACKET, STR_REPLACE, _[0])}: {lib0246.snap_place(_[1], round, 2)})", itertools.product(hold["data"], obj.inst[state["index"]]["widgets_values"][0])))
|
|
state["index"] = None
|
|
return []
|
|
|
|
@classmethod
|
|
def func_merge(cls, obj, hold, state):
|
|
res = []
|
|
|
|
lib0246.sort_dict_of_list(hold, "index")
|
|
for i, curr_delim in zip(itertools.count(start=0, step=1), *obj.inst[state["index"]]["widgets_values"]):
|
|
res.append(curr_delim.join(hold["data"]))
|
|
|
|
for i in range(len(hold["index"])):
|
|
hold["index"][i] = None
|
|
|
|
return res
|
|
|
|
class CloudData:
|
|
def __init__(self):
|
|
self.inst = []
|
|
self.group = {}
|
|
self.db = {}
|
|
|
|
self.state = {}
|
|
self.func = {}
|
|
self.track = 0
|
|
self.order = None
|
|
self.id = None
|
|
|
|
@classmethod
|
|
def full_dict_to_data(cls, curr_id, inst_list, group_dict, db_dict = None, kwargs = None):
|
|
dict_dupe = dict(filter(lambda item: item[0].split(":")[0].isnumeric(), kwargs.items()))
|
|
if len(dict_dupe) == 0:
|
|
return [
|
|
CloudData().dict_to_data(curr_id, inst_list, group_dict, db_dict, dict_dupe)
|
|
]
|
|
return [
|
|
CloudData().dict_to_data(curr_id, inst_list, group_dict, db_dict, dupe[0]) for dupe in
|
|
lib0246.dict_product(dict_dupe)
|
|
]
|
|
|
|
def dict_to_data(self, curr_id, inst_list, group_dict, db_dict = None, kwargs = None):
|
|
if kwargs is not None:
|
|
self.track = PROMPT_COUNT
|
|
self.id = curr_id
|
|
if db_dict is not None:
|
|
self.db.update(db_dict)
|
|
self.db[curr_id] = self.db.get(curr_id, {})
|
|
|
|
for inst in inst_list:
|
|
old_id = inst["id"]
|
|
new_id = str(uuid.uuid4())
|
|
match inst["kind"]:
|
|
case "pin" if kwargs is not None:
|
|
curr_value = None
|
|
for key in kwargs:
|
|
curr_int = key.split(":")[0]
|
|
if curr_int.isnumeric() and int(curr_int) == inst["widgets_values"][0]:
|
|
curr_value = kwargs[key]
|
|
break
|
|
match curr_value:
|
|
case CloudData():
|
|
self.dict_to_data(curr_value.id, curr_value.inst, curr_value.group, curr_value.db, None)
|
|
for group_id in group_dict:
|
|
if "inst" in group_dict[group_id]:
|
|
try:
|
|
curr_index = group_dict[group_id]["inst"].index(old_id)
|
|
group_dict[group_id]["inst"][curr_index:curr_index + 1] = map(lambda _: _["id"], curr_value.inst)
|
|
except ValueError:
|
|
pass
|
|
continue
|
|
case str() | int() | float():
|
|
self.inst.append({
|
|
"id": new_id,
|
|
"kind": "text",
|
|
"widgets_values": [[*map(str, kwargs[key])]],
|
|
"widgets_names": [f"cloud:_:{new_id}:text:text_input"]
|
|
})
|
|
case _:
|
|
self.inst.append(inst)
|
|
if old_id.isnumeric():
|
|
inst["id"] = new_id
|
|
self.db[curr_id][new_id] = old_id
|
|
|
|
if kwargs is not None:
|
|
for param in kwargs:
|
|
if param.startswith("cloud:"):
|
|
for inst in self.inst:
|
|
for i in range(len(inst["widgets_values"])):
|
|
if inst["widgets_names"][i] == param:
|
|
inst["widgets_values"][i] = kwargs[param]
|
|
break
|
|
|
|
for old_id, group_data in group_dict.items():
|
|
if "inst" in group_data:
|
|
new_list = []
|
|
for i, inst_id in enumerate(group_data["inst"]):
|
|
if not inst_id.isnumeric():
|
|
new_list.append(inst_id)
|
|
else:
|
|
for curr_inst_id in self.db[curr_id].keys():
|
|
if self.db[curr_id][curr_inst_id] == inst_id:
|
|
new_list.append(curr_inst_id)
|
|
break
|
|
if len(new_list) > 0:
|
|
group_data["inst"] = new_list
|
|
else:
|
|
del group_data["inst"]
|
|
if "group" in group_data:
|
|
for i, inner_old_id in enumerate(group_data["group"]):
|
|
new_id = str(uuid.uuid4())
|
|
if not inner_old_id.split(":")[-1].isnumeric():
|
|
group_data["group"][i] = inner_old_id
|
|
else:
|
|
for curr_group_id in self.db[curr_id].keys():
|
|
if self.db[curr_id][curr_group_id] == inner_old_id:
|
|
group_data["group"][i] = curr_group_id
|
|
break
|
|
new_id = str(uuid.uuid4())
|
|
if not old_id.split(":")[-1].isnumeric():
|
|
self.group[old_id] = group_data
|
|
else:
|
|
for curr_group_id in self.db[curr_id].keys():
|
|
if self.db[curr_id][curr_group_id] == old_id:
|
|
self.group[curr_group_id] = group_data
|
|
break
|
|
else:
|
|
self.group[new_id] = group_data
|
|
for curr_group_id in self.db[curr_id].keys():
|
|
if self.db[curr_id][curr_group_id] == old_id:
|
|
break
|
|
else:
|
|
self.db[curr_id][new_id] = old_id
|
|
|
|
return self
|
|
|
|
@classmethod
|
|
def text_to_dict(cls, text):
|
|
old_func = comfy.sd1_clip.parse_parentheses
|
|
comfy.sd1_clip.parse_parentheses = lib0246.parse_parentheses
|
|
res = list(map(lambda _: (comfy.sd1_clip.unescape_important(_[0]), _[1]), comfy.sd1_clip.token_weights(comfy.sd1_clip.escape_important(text), 1.0)))
|
|
comfy.sd1_clip.parse_parentheses = old_func
|
|
return res
|
|
|
|
def text_to_data(self, text):
|
|
pass
|
|
|
|
def data_eval(self, node_id, prompt, workflow):
|
|
"""
|
|
{
|
|
eval # Evaluated data
|
|
data # Persistent data
|
|
order # Current instance order
|
|
index # Current instance result index
|
|
change # Whether whole cloud changed
|
|
}
|
|
"""
|
|
|
|
self.state["eval"] = {}
|
|
self.state["change"] = self.track == PROMPT_COUNT
|
|
self.state["id"] = node_id
|
|
self.state["prompt"] = prompt
|
|
self.state["workflow"] = workflow
|
|
|
|
if self.state["change"] or self.order is None:
|
|
self.order = self.sort()
|
|
self.func = {}
|
|
self.state["data"] = {}
|
|
self.track = PROMPT_COUNT
|
|
for inst in self.inst:
|
|
self.func[inst["id"]] = CloudFunc(inst["kind"]) if \
|
|
isinstance(inst["kind"], str) else \
|
|
inst["kind"]
|
|
|
|
for i, curr_id in enumerate(self.order["idx"]):
|
|
self.state["order"] = i
|
|
self.state["index"] = next(i for i, _ in enumerate(self.inst) if _["id"] == curr_id)
|
|
hold_curr = {
|
|
"data": [],
|
|
"index": []
|
|
}
|
|
for inst_id_dep in self.order["dep"][curr_id]:
|
|
hold_curr["data"].extend(self.state["eval"][inst_id_dep]["data"])
|
|
hold_curr["index"].extend([self.state["eval"][inst_id_dep]["index"]] * len(self.state["eval"][inst_id_dep]["data"]))
|
|
self.state["eval"][curr_id] = {
|
|
"data": self.func[curr_id].func(
|
|
obj=self,
|
|
hold=hold_curr,
|
|
state=self.state
|
|
),
|
|
"index": self.state["index"]
|
|
}
|
|
i = 0
|
|
for inst_id_dep in self.order["dep"][curr_id]:
|
|
self.state["eval"][inst_id_dep]["index"] = hold_curr["index"][i]
|
|
curr_len = len(self.state["eval"][inst_id_dep]["data"])
|
|
self.state["eval"][inst_id_dep]["data"] = hold_curr["data"][i:i + curr_len]
|
|
i += curr_len
|
|
|
|
temp_res = []
|
|
for curr_id in self.state["eval"]:
|
|
curr_eval = self.state["eval"][curr_id]
|
|
if curr_eval["index"] is not None:
|
|
curr_eval["id"] = curr_id
|
|
temp_res.append(curr_eval)
|
|
temp_res.sort(key=lambda _: _["index"])
|
|
|
|
return sum((curr_eval["data"] for curr_eval in temp_res), [])
|
|
|
|
def sort(self):
|
|
dep = {}
|
|
|
|
for group_id in self.group:
|
|
inst_list = group_query_inst(self.group, group_id)
|
|
inst_dep_list_prim = []
|
|
inst_dep_list_func = []
|
|
inst_dep_list_bind = []
|
|
inst_kind_set = set()
|
|
|
|
for inst_id in inst_list:
|
|
if inst_id not in dep:
|
|
dep[inst_id] = []
|
|
inst_curr_kind = next(filter(lambda _: _["id"] == inst_id, self.inst))["kind"]
|
|
if CLOUD_METHOD[inst_curr_kind] is None:
|
|
inst_dep_list_prim.append(inst_id)
|
|
else:
|
|
if inst_curr_kind in inst_kind_set and CLOUD_METHOD[inst_curr_kind]["sole"]:
|
|
raise Exception(f"Cannot have multiple {inst_curr_kind} cloud to same group {group_id}.")
|
|
(inst_dep_list_bind if CLOUD_METHOD[inst_curr_kind]["bind"] else inst_dep_list_func).append(inst_id)
|
|
if isinstance(inst_curr_kind, str):
|
|
inst_kind_set.add(inst_curr_kind)
|
|
|
|
for inst_id in inst_dep_list_func:
|
|
dep[inst_id].extend(inst_dep_list_prim)
|
|
dep[inst_id].extend(inst_dep_list_bind)
|
|
|
|
idx_db = {}
|
|
for inst_id in inst_dep_list_bind:
|
|
dep[inst_id].extend(inst_dep_list_prim)
|
|
# dep[inst_id].extend(inst_dep_list_func)
|
|
idx_db[inst_id] = self.inst.index(next(filter(lambda _: _["id"] == inst_id, self.inst)))
|
|
|
|
for a_inst_id in inst_dep_list_bind:
|
|
for b_inst_id in inst_dep_list_bind:
|
|
if idx_db[a_inst_id] > idx_db[b_inst_id] and idx_db[a_inst_id] != idx_db[b_inst_id]:
|
|
dep[a_inst_id].append(b_inst_id)
|
|
|
|
for inst_id in self.inst:
|
|
if inst_id["id"] not in dep:
|
|
dep[inst_id["id"]] = []
|
|
|
|
return {
|
|
"idx": list(map(lambda _: _[0], lib0246.flat_iter(lib0246.toposort(dep, key_func=lambda _: next(i for i, c in enumerate(self.inst) if c["id"] == _)), layer=1))),
|
|
"dep": dep
|
|
}
|
|
|
|
def __str__(self):
|
|
return f"CloudData({self.inst}, {self.group}, {self.db})"
|
|
|
|
def __repr__(self):
|
|
return f"CloudData({json.dumps(self.inst, indent=2)}, {json.dumps(self.group, indent=2)}, {json.dumps(self.db, indent=2)})"
|
|
|
|
########################################################################################
|
|
######################################## HIJACK ########################################
|
|
########################################################################################
|
|
|
|
BASE_EXECUTOR = None
|
|
|
|
def init_executor_param_handle(*args, **kwargs):
|
|
return None, tuple(), {}
|
|
|
|
def init_executor_res_handle(result, *args, **kwargs):
|
|
global BASE_EXECUTOR
|
|
if BASE_EXECUTOR is None:
|
|
BASE_EXECUTOR = args[0]
|
|
return result
|
|
|
|
lib0246.hijack(execution.PromptExecutor, "__init__", init_executor_param_handle, init_executor_res_handle)
|
|
|
|
PROMPT_COUNT = 0
|
|
PROMPT_DATA = None
|
|
PROMPT_ID = None
|
|
PROMPT_EXTRA = None
|
|
|
|
PROMPT_UPDATE = set()
|
|
PROMPT_IGNORE = set()
|
|
PROMPT_IGNORE_FLAG = False
|
|
PROMPT_NODE_ID = None
|
|
|
|
PROMPT_HIJACK = set()
|
|
|
|
def is_changed_res_handle(result, *args, **kwargs):
|
|
global PROMPT_NODE_ID
|
|
if PROMPT_NODE_ID in PROMPT_IGNORE:
|
|
PROMPT_NODE_ID = None
|
|
return False
|
|
if PROMPT_NODE_ID in PROMPT_UPDATE:
|
|
PROMPT_UPDATE.remove(PROMPT_NODE_ID)
|
|
PROMPT_NODE_ID = None
|
|
return float("NaN")
|
|
return result
|
|
|
|
def execute_param_handle(*args, **kwargs):
|
|
global PROMPT_ID
|
|
global PROMPT_COUNT
|
|
global PROMPT_DATA
|
|
global PROMPT_EXTRA
|
|
if PROMPT_ID is None or PROMPT_ID != args[2]:
|
|
PROMPT_COUNT += 1
|
|
PROMPT_DATA = args[1]
|
|
PROMPT_ID = args[2]
|
|
PROMPT_EXTRA = args[3]
|
|
|
|
return None, tuple(), {}
|
|
|
|
def executor_res_handle(result, *args, **kwargs):
|
|
global PROMPT_UPDATE
|
|
global PROMPT_IGNORE
|
|
global PROMPT_IGNORE_FLAG
|
|
global PROMPT_HIJACK
|
|
|
|
for node_id in PROMPT_UPDATE:
|
|
if hasattr(BASE_EXECUTOR, "outputs") and node_id in BASE_EXECUTOR.outputs:
|
|
del BASE_EXECUTOR.outputs[node_id]
|
|
else:
|
|
curr_class_type = args[1][node_id]["class_type"]
|
|
if curr_class_type not in PROMPT_HIJACK:
|
|
PROMPT_HIJACK.add(curr_class_type)
|
|
curr_class = nodes.NODE_CLASS_MAPPINGS[curr_class_type]
|
|
|
|
if hasattr(curr_class, "IS_CHANGED"):
|
|
lib0246.hijack(curr_class, "IS_CHANGED", res_func=is_changed_res_handle)
|
|
else:
|
|
curr_class.IS_CHANGED = functools.partial(is_changed_res_handle, None)
|
|
|
|
if hasattr(BASE_EXECUTOR, "outputs"):
|
|
PROMPT_UPDATE.clear()
|
|
PROMPT_IGNORE.clear()
|
|
PROMPT_IGNORE_FLAG = False
|
|
return result
|
|
|
|
lib0246.hijack(execution.PromptExecutor, "execute", execute_param_handle, executor_res_handle)
|
|
|
|
def get_input_data_param_handle(*args, **kwargs):
|
|
global PROMPT_IGNORE
|
|
global PROMPT_IGNORE_FLAG
|
|
global PROMPT_NODE_ID
|
|
PROMPT_NODE_ID = args[2]
|
|
if args[2] in PROMPT_IGNORE:
|
|
PROMPT_IGNORE_FLAG = True
|
|
return None, tuple(), {}
|
|
|
|
lib0246.hijack(execution, "get_input_data", get_input_data_param_handle)
|
|
|
|
def map_node_over_list_func_handle(func, *args, **kwargs):
|
|
return [[[None]]]
|
|
|
|
def map_node_over_list_param_handle(*args, **kwargs):
|
|
global PROMPT_IGNORE_FLAG
|
|
if PROMPT_IGNORE_FLAG:
|
|
PROMPT_IGNORE_FLAG = False
|
|
return map_node_over_list_func_handle, tuple(), {}
|
|
return None, tuple(), {}
|
|
|
|
def map_node_over_list_res_handle(result, *args, **kwargs):
|
|
if (hasattr(args[0], "FUNCTION") and args[0].FUNCTION == args[2]):
|
|
input_iter = None
|
|
if hasattr(args[0], "WRAPPER"):
|
|
return getattr(args[0], "WRAPPER")(result, args, kwargs)
|
|
elif hasattr(args[0], "INPUT_IS_LIST") and args[0].INPUT_IS_LIST:
|
|
try:
|
|
input_iter = zip([({"_": next(filter(
|
|
lambda _: isinstance(_, lib0246.Wrapper),
|
|
itertools.chain.from_iterable(args[1][_] for _ in args[1])
|
|
))}, None)], [0])
|
|
except StopIteration:
|
|
return result
|
|
else:
|
|
input_iter = zip(lib0246.dict_slice(args[1]), range(len(result)))
|
|
|
|
for (curr, index_info), i in input_iter:
|
|
for key in curr:
|
|
if isinstance(curr[key], lib0246.Wrapper):
|
|
if isinstance(result[i], dict):
|
|
if "result" in result[i]:
|
|
result[i]["result"] = tuple(lib0246.Wrapper(_, curr[key]._0246) for _ in result[i]["result"])
|
|
else:
|
|
result[i] = tuple(lib0246.Wrapper(_, curr[key]._0246) for _ in result[i])
|
|
break
|
|
return result
|
|
|
|
lib0246.hijack(execution, "map_node_over_list", map_node_over_list_param_handle, map_node_over_list_res_handle)
|
|
# Honestly not many option beside hijacking _map_node_over_list
|
|
lib0246.hijack(execution, "_map_node_over_list", map_node_over_list_param_handle, map_node_over_list_res_handle)
|
|
|
|
CLASS_LIST = None
|
|
|
|
if not hasattr(execution, "recursive_execute") or \
|
|
(hasattr(execution, "EXPERIMENTAL_EXECUTION") and execution.EXPERIMENTAL_EXECUTION):
|
|
temp_info = None
|
|
|
|
def get_input_info_func_handle(func, *args, **kwargs):
|
|
temp_res = func(*args, **kwargs)
|
|
if temp_res[2] is None:
|
|
return (lib0246.TautologyStr("*"), "optional", temp_info)
|
|
return temp_res
|
|
|
|
def get_input_info_param_handle(*args, **kwargs):
|
|
global CLASS_LIST
|
|
global NODE_CLASS_MAPPINGS
|
|
global temp_info
|
|
if CLASS_LIST is None:
|
|
CLASS_LIST = set(obj for name, obj in inspect.getmembers(sys.modules[__name__]) if inspect.isclass(obj))
|
|
if args[0] == Loop:
|
|
temp_info = {"rawLink": True}
|
|
return get_input_info_func_handle, tuple(), {}
|
|
elif args[0] == Switch:
|
|
temp_info = {"lazy": True}
|
|
return get_input_info_func_handle, tuple(), {}
|
|
elif args[0] in CLASS_LIST:
|
|
temp_info = {}
|
|
return get_input_info_func_handle, tuple(), {}
|
|
return None, tuple(), {}
|
|
|
|
def get_input_info_res_handle(result, *args, **kwargs):
|
|
return result
|
|
|
|
lib0246.hijack(comfy_graph, "get_input_info", get_input_info_param_handle, get_input_info_res_handle)
|
|
lib0246.hijack(execution, "get_input_info", get_input_info_param_handle, get_input_info_res_handle)
|
|
|
|
def merge_result_data_func_handle(func, *args, **kwargs):
|
|
old_output_is_list = args[1].OUTPUT_IS_LIST
|
|
args[1].OUTPUT_IS_LIST = itertools.islice(args[1].OUTPUT_IS_LIST.iter(), len(args[0][0]))
|
|
res = func(*args, **kwargs)
|
|
args[1].OUTPUT_IS_LIST = old_output_is_list
|
|
return res
|
|
|
|
def merge_result_data_param_handle(*args, **kwargs):
|
|
if hasattr(args[1], "OUTPUT_IS_LIST") and isinstance(args[1].OUTPUT_IS_LIST, lib0246.OutputHandle):
|
|
return merge_result_data_func_handle, tuple(), {}
|
|
return None, tuple(), {}
|
|
|
|
lib0246.hijack(execution, "merge_result_data", merge_result_data_param_handle)
|
|
|
|
DEFAULT_JSON_ENCODER = json.JSONEncoder.default
|
|
def wrapper_encoder_func_handle(self, obj):
|
|
if isinstance(obj, lib0246.Wrapper):
|
|
return obj.__wrapped__
|
|
return DEFAULT_JSON_ENCODER(self, obj)
|
|
|
|
json.JSONEncoder.default = wrapper_encoder_func_handle
|
|
|
|
#####################################################################################
|
|
######################################## API ########################################
|
|
#####################################################################################
|
|
|
|
@server.PromptServer.instance.routes.post('/0246-parse-highway')
|
|
async def parse_highway_handler(request):
|
|
data = await request.json()
|
|
|
|
# Validate json
|
|
if data.get("input") is None:
|
|
return aiohttp.web.json_response({
|
|
"error": ["No input provided"]
|
|
})
|
|
|
|
# Parse the input string
|
|
expr_res, order, errors = lib0246.parse_query(data["input"], lib0246.HIGHWAY_OPS)
|
|
|
|
lib0246.highway_check(expr_res, errors)
|
|
|
|
# Return a JSON response with the processed data
|
|
return aiohttp.web.json_response({
|
|
"expr": expr_res,
|
|
"order": order,
|
|
"error": errors
|
|
})
|
|
|
|
@server.PromptServer.instance.routes.post('/0246-parse-prompt')
|
|
async def parse_prompt_handler(request):
|
|
data = await request.json()
|
|
|
|
# Validate json
|
|
if data.get("prompt") is None:
|
|
return aiohttp.web.json_response({
|
|
"error": ["No prompt provided"]
|
|
})
|
|
|
|
return aiohttp.web.json_response({
|
|
"res": CloudData.text_to_dict(data["prompt"])
|
|
})
|
|
|
|
@server.PromptServer.instance.routes.post('/0246-clear')
|
|
async def clear_handler(request):
|
|
global BASE_EXECUTOR
|
|
if hasattr(BASE_EXECUTOR, "outputs"):
|
|
BASE_EXECUTOR.outputs.clear()
|
|
BASE_EXECUTOR.outputs_ui.clear()
|
|
BASE_EXECUTOR.object_storage.clear()
|
|
BASE_EXECUTOR.server.last_prompt_id = None
|
|
RandomInt.RANDOM_DB.clear()
|
|
Hold.HOLD_DB.clear()
|
|
Loop.LOOP_DB.clear()
|
|
|
|
return aiohttp.web.json_response({})
|
|
|
|
@server.PromptServer.instance.routes.post('/0246-terminate')
|
|
async def terminate_handler(request):
|
|
# This requires modifying source code of ComfyUI, which is required
|
|
# For personal purpose only
|
|
setattr(server.PromptServer.instance, "terminate", True)
|
|
|
|
return aiohttp.web.json_response({})
|
|
|
|
######################################################################################
|
|
######################################## NODE ########################################
|
|
######################################################################################
|
|
|
|
class Highway:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"_query": ("STRING", {
|
|
"default": ">data; <data",
|
|
"multiline": False
|
|
}),
|
|
},
|
|
"optional": {
|
|
"_way_in": ("HIGHWAY_PIPE", ),
|
|
},
|
|
"hidden": {
|
|
"_prompt": "PROMPT",
|
|
"_id": "UNIQUE_ID",
|
|
"_workflow": "EXTRA_PNGINFO" # Unfortunately EXTRA_PNGINFO does not get exposed during IS_CHANGED
|
|
}
|
|
}
|
|
|
|
# Amogus moment ඞ
|
|
RETURN_TYPES = lib0246.ByPassTypeTuple(("HIGHWAY_PIPE", ))
|
|
RETURN_NAMES = lib0246.ByPassTypeTuple(("_way_out", ))
|
|
FUNCTION = "execute"
|
|
CATEGORY = "0246"
|
|
|
|
# [TODO] Potential recursion error when attempting to hook the inout in not a very specific way
|
|
# => May have to keep a unique identifier for each class and each node instance
|
|
# Therefore if already exist then throw error
|
|
# => Cyclic detection in JS instead of python
|
|
|
|
# Do not remove the "useless" _query parameter, since data need to be consumed for expanding
|
|
def execute(self, _id = None, _prompt = None, _workflow = None, _way_in = None, _query = None, **kwargs):
|
|
return highway_impl(_prompt, _id, _workflow, _way_in, False, kwargs)
|
|
|
|
@classmethod
|
|
def IS_CHANGED(cls, *args, **kwargs):
|
|
return lib0246.check_update(kwargs["_query"])
|
|
|
|
######################################################################################
|
|
|
|
class HighwayBatch:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"_query": ("STRING", {
|
|
"default": ">data; <data",
|
|
"multiline": False
|
|
}),
|
|
},
|
|
"optional": {
|
|
"_way_in": ("HIGHWAY_PIPE", ),
|
|
},
|
|
"hidden": {
|
|
"_prompt": "PROMPT",
|
|
"_id": "UNIQUE_ID",
|
|
"_workflow": "EXTRA_PNGINFO"
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = lib0246.ByPassTypeTuple(("HIGHWAY_PIPE", ))
|
|
RETURN_NAMES = lib0246.ByPassTypeTuple(("_way_out", ))
|
|
INPUT_IS_LIST = True
|
|
OUTPUT_IS_LIST = lib0246.TautologyRest
|
|
FUNCTION = "execute"
|
|
CATEGORY = "0246"
|
|
|
|
def execute(self, _id = None, _prompt = None, _workflow = None, _way_in = None, _query = None, **kwargs):
|
|
return highway_impl(_prompt, _id, _workflow, gather_highway_impl(_way_in, _id), True, kwargs)
|
|
|
|
@classmethod
|
|
def IS_CHANGED(cls, *args, **kwargs):
|
|
return lib0246.check_update(kwargs["_query"])
|
|
|
|
######################################################################################
|
|
|
|
class Junction:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"_offset": ("STRING", {
|
|
"default": ";",
|
|
"multiline": False
|
|
}),
|
|
# "_mode": (["named_type", "internal_type"],),
|
|
},
|
|
"optional": {
|
|
"_junc_in": ("JUNCTION_PIPE", ),
|
|
},
|
|
"hidden": {
|
|
"_prompt": "PROMPT",
|
|
"_id": "UNIQUE_ID",
|
|
"_workflow": "EXTRA_PNGINFO"
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = lib0246.ByPassTypeTuple(("JUNCTION_PIPE", ))
|
|
RETURN_NAMES = lib0246.ByPassTypeTuple(("_junc_out", ))
|
|
FUNCTION = "execute"
|
|
CATEGORY = "0246"
|
|
|
|
def __init__(self):
|
|
self._prev_offset = None
|
|
self._parsed_offset = None
|
|
|
|
def execute(self, _id = None, _prompt = None, _workflow = None, _junc_in = None, _offset = None, **kwargs):
|
|
return junction_impl(self, _id, _prompt, _workflow, _junc_in, _offset, **kwargs)
|
|
|
|
@classmethod
|
|
def IS_CHANGED(cls, *args, **kwargs):
|
|
return lib0246.check_update(kwargs["_offset"])
|
|
|
|
######################################################################################
|
|
|
|
class JunctionBatch:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"_offset": ("STRING", {
|
|
"default": ";",
|
|
"multiline": False
|
|
}),
|
|
"_mode": (["pluck", "batch"], ),
|
|
},
|
|
"optional": {
|
|
"_junc_in": ("JUNCTION_PIPE", ),
|
|
},
|
|
"hidden": {
|
|
"_prompt": "PROMPT",
|
|
"_id": "UNIQUE_ID",
|
|
"_workflow": "EXTRA_PNGINFO"
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = lib0246.ByPassTypeTuple(("JUNCTION_PIPE", ))
|
|
RETURN_NAMES = lib0246.ByPassTypeTuple(("_junc_out", ))
|
|
INPUT_IS_LIST = True
|
|
FUNCTION = "execute"
|
|
CATEGORY = "0246"
|
|
|
|
def __init__(self):
|
|
self._prev_offset = None
|
|
self._parsed_offset = None
|
|
|
|
def execute(self, _id = None, _prompt = None, _workflow = None, _junc_in = None, _offset = None, _mode = None, **kwargs):
|
|
if isinstance(_mode, list):
|
|
_mode = _mode[0]
|
|
|
|
# Why not adding extra combos to manage _in_mode, _out_mode, and _offset_mode?
|
|
# To prevent people being stupid and we force them to use correct combination
|
|
|
|
if _mode == "batch":
|
|
setattr(JunctionBatch, "OUTPUT_IS_LIST", lib0246.TautologyRest)
|
|
return junction_impl(self, _id, _prompt, _workflow, gather_junction_impl(_junc_in, _id), _offset, _in_mode = True, _out_mode = True, _offset_mode = True, **kwargs)
|
|
else:
|
|
try:
|
|
delattr(JunctionBatch, "OUTPUT_IS_LIST")
|
|
except AttributeError:
|
|
pass
|
|
return junction_impl(self, _id, _prompt, _workflow, gather_junction_impl(_junc_in, _id), _offset, _in_mode = True, _out_mode = False, **kwargs)
|
|
|
|
# [TODO] "hoard" (keep batch when in) and "spread" (keep batch when out, else will spread to junction list and behave like "pluck" or "batch")
|
|
|
|
@classmethod
|
|
def IS_CHANGED(cls, *args, **kwargs):
|
|
return lib0246.check_update(kwargs["_offset"])
|
|
|
|
######################################################################################
|
|
|
|
class Count:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"_node": (lib0246.TautologyStr("*"), ),
|
|
"_event": ("STRING", {
|
|
"default": "10",
|
|
"multiline": False
|
|
}),
|
|
},
|
|
"hidden": {
|
|
"_id": "UNIQUE_ID"
|
|
}
|
|
}
|
|
|
|
COUNT_DB = {}
|
|
COUNT_ID = 0
|
|
|
|
RETURN_TYPES = ("INT", "EVENT_TYPE")
|
|
RETURN_NAMES = ("_count_int", "_count_event")
|
|
INPUT_IS_LIST = True
|
|
FUNCTION = "execute"
|
|
CATEGORY = "0246"
|
|
|
|
def execute(self, _id = None, _node = None, _event = None, **kwargs):
|
|
global PROMPT_ID
|
|
if isinstance(_id, list):
|
|
_id = str(_id[0]) if len(_id) > 0 else None
|
|
if isinstance(_id, str):
|
|
_id = _id[_id.rfind(".") + 1:]
|
|
if Count.COUNT_ID != PROMPT_ID:
|
|
Count.COUNT_DB = {}
|
|
Count.COUNT_ID = PROMPT_ID
|
|
if _id not in Count.COUNT_DB:
|
|
Count.COUNT_DB[_id] = 0
|
|
temp = Count.COUNT_DB[_id]
|
|
Count.COUNT_DB[_id] += 1
|
|
|
|
# [TODO] Syntax:
|
|
# @12, 56, 99, ... # Specific number
|
|
# (0, 9] # 0 to 9 (exclusive, inclusive)
|
|
# [0, 9) # 0 to 9 (inclusive, exclusive)
|
|
# [0, 9] # 0 to 9 (inclusive)
|
|
# (0, 9) # 0 to 9 (exclusive)
|
|
# [0, 9, 2] # 0 to 9 (inclusive) with step 2
|
|
# (0, 9, 2) # 0 to 9 (exclusive) with step 2
|
|
# [0, 9, 2) # 0 to 9 (inclusive, exclusive) with step 2
|
|
# (0, 9, 2] # 0 to 9 (exclusive, inclusive) with step 2
|
|
# @[0, 9], (1, 4, -2), 3, ... # Multiple ranges and numbers
|
|
|
|
return {
|
|
"ui": {
|
|
"text": [f"Count: {temp}, Track: {Count.COUNT_ID}"]
|
|
},
|
|
"result": (temp, {
|
|
"id": _id,
|
|
"bool": temp >= int(_event[0]),
|
|
})
|
|
}
|
|
|
|
######################################################################################
|
|
|
|
class RandomInt:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
# Random uint64 int
|
|
return {
|
|
"required": {
|
|
"val": ("STRING", {
|
|
"default": "rand,0",
|
|
"multiline": False
|
|
}),
|
|
"min": ("INT", {
|
|
"default": 0,
|
|
"min": -9007199254740991,
|
|
"max": 9007199254740991
|
|
}),
|
|
"max": ("INT", {
|
|
"default": 9007199254740991,
|
|
"min": -9007199254740991,
|
|
"max": 9007199254740991
|
|
}),
|
|
"batch_size": ("INT", {
|
|
"default": 2,
|
|
"min": 1,
|
|
"max": sys.maxsize
|
|
}),
|
|
"mode": (["usual", "keep", "force"], )
|
|
},
|
|
"optional": {
|
|
"seed": ("INT", {
|
|
"default": 0,
|
|
"min": -1125899906842624,
|
|
# "max": 18446744073709551615
|
|
"max": 1125899906842624
|
|
}),
|
|
},
|
|
"hidden": {
|
|
"_id": "UNIQUE_ID"
|
|
}
|
|
}
|
|
|
|
RANDOM_DB = {}
|
|
|
|
RETURN_TYPES = ("INT", )
|
|
RETURN_NAMES = ("rand_int", )
|
|
INPUT_IS_LIST = True
|
|
OUTPUT_IS_LIST = (True, )
|
|
# NOT_IDEMPOTENT = True
|
|
FUNCTION = "execute"
|
|
CATEGORY = "0246"
|
|
|
|
def execute(self, _id = None, val = None, min = None, max = None, seed = None, batch_size = None, mode = None, **kwargs):
|
|
if min[0] > max[0]:
|
|
raise Exception("Min is greater than max.")
|
|
if isinstance(_id, list):
|
|
_id = str(_id[0]) if len(_id) > 0 else None
|
|
if isinstance(_id, str):
|
|
_id = _id[_id.rfind(".") + 1:]
|
|
|
|
if _id not in RandomInt.RANDOM_DB:
|
|
RandomInt.RANDOM_DB[_id] = {
|
|
"track": None,
|
|
"prev": [],
|
|
"prev_batch_size": 0,
|
|
"inst": random.Random(),
|
|
"seed": seed[0],
|
|
"flag": 0
|
|
}
|
|
|
|
db = RandomInt.RANDOM_DB[_id]
|
|
|
|
if db["track"] != PROMPT_ID:
|
|
db["track"] = PROMPT_ID
|
|
db["flag"] = 0
|
|
if mode[0] == "keep" or db["prev_batch_size"] != batch_size[0]:
|
|
db["prev"].clear()
|
|
db["prev_batch_size"] = 0
|
|
mode[0] = "force"
|
|
|
|
raw: list = val[0].split(",")
|
|
if len(raw) > batch_size[0]:
|
|
raw = raw[:batch_size[0]]
|
|
|
|
for i in range(len(raw)):
|
|
raw[i] = raw[i].strip()
|
|
if raw[i].isnumeric():
|
|
lib0246.append_replace(db["prev"], i, int(raw[i]))
|
|
else:
|
|
if db["prev_batch_size"] != len(raw) or len(raw) <= i or raw[i] == "rand":
|
|
if seed[0] != db["seed"] or mode[0] == "force":
|
|
db["inst"].seed(seed[0])
|
|
db["seed"] = seed[0]
|
|
mode[0] = ""
|
|
lib0246.append_replace(db["prev"], i, db["inst"].randint(min[0], max[0]))
|
|
elif raw[i] == "add":
|
|
if mode[0] != "force" or (db["flag"] < 1 and mode[0] == "force"):
|
|
db["prev"][i] += 1
|
|
elif raw[i] == "sub":
|
|
if mode[0] != "force" or (db["flag"] < 1 and mode[0] == "force"):
|
|
db["prev"][i] -= 1
|
|
else:
|
|
raise Exception(f"Invalid value \"{raw[i]}\".")
|
|
|
|
db["prev_batch_size"] = len(raw)
|
|
db["flag"] += 1
|
|
|
|
msg = f"Value: {{{', '.join([str(x) for x in db['prev']])}}}, " + \
|
|
f"Seed: {db['seed']}, " + \
|
|
f"Track: {PROMPT_ID}"
|
|
|
|
return {
|
|
"ui": {
|
|
"text": [msg]
|
|
},
|
|
"result": [copy.copy(db["prev"])]
|
|
}
|
|
|
|
@classmethod
|
|
def IS_CHANGED(cls, val = None, *args, **kwargs):
|
|
if any(x in map(lambda _: _.strip(), val[0].split(",")) for x in ("rand", "sub", "add")):
|
|
return float("NaN")
|
|
return val[0]
|
|
|
|
######################################################################################
|
|
|
|
class Hold:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"_mode": (["save", "clear", "pin", "share"], ),
|
|
"_key_id": ("STRING", {
|
|
"default": "",
|
|
"multiline": False
|
|
}),
|
|
},
|
|
"optional": {
|
|
"_data_in": (lib0246.TautologyStr("*"), ),
|
|
"_hold": ("HOLD_TYPE", )
|
|
},
|
|
"hidden": {
|
|
"_id": "UNIQUE_ID",
|
|
"_prompt": "PROMPT",
|
|
"_workflow": "EXTRA_PNGINFO"
|
|
}
|
|
}
|
|
|
|
HOLD_DB = {}
|
|
|
|
RETURN_TYPES = lib0246.ByPassTypeTuple(("*", "*"))
|
|
RETURN_NAMES = ("_data_out", "_data_out_all")
|
|
INPUT_IS_LIST = True
|
|
OUTPUT_IS_LIST = (True, True)
|
|
# NOT_IDEMPOTENT = True # Since this node is similar to CheckpointLoaderSimple (to save resources)
|
|
FUNCTION = "execute"
|
|
CATEGORY = "0246"
|
|
|
|
def execute(
|
|
self, _data_in = None,
|
|
_id = None, _prompt = None, _workflow = None,
|
|
_hold = None, _mode = None, _key_id = None,
|
|
**kwargs
|
|
):
|
|
for key in kwargs:
|
|
if key.startswith("_data_in"):
|
|
_data_in = kwargs[key]
|
|
break
|
|
if isinstance(_id, list):
|
|
_id = str(_id[0]) if len(_id) > 0 else None
|
|
if isinstance(_id, str):
|
|
_id = _id[_id.rfind(".") + 1:]
|
|
if isinstance(_prompt, list):
|
|
_prompt = _prompt[0] if len(_prompt) > 0 else None
|
|
if isinstance(_workflow, list):
|
|
_workflow = _workflow[0] if len(_workflow) > 0 else None
|
|
if isinstance(_hold, list):
|
|
_hold = _hold[0] if len(_hold) > 0 else None
|
|
if isinstance(_mode, list):
|
|
_mode = _mode[0] if len(_mode) > 0 else None
|
|
if isinstance(_key_id, list):
|
|
_key_id = str(_key_id[0]) if len(_key_id) > 0 else None
|
|
|
|
param_flag = _key_id is not None and len(_key_id) > 0
|
|
|
|
if _id not in Hold.HOLD_DB:
|
|
Hold.HOLD_DB[_id] = {}
|
|
if param_flag and _key_id not in Hold.HOLD_DB:
|
|
Hold.HOLD_DB[_key_id] = {}
|
|
|
|
ui_text = f"Id: {_id}, "
|
|
result = None
|
|
|
|
param_flag = _key_id is not None and len(_key_id) > 0
|
|
|
|
if _hold:
|
|
if (
|
|
(_mode == "clear" and Hold.HOLD_DB[_id].get("track", "") != PROMPT_ID) or
|
|
Hold.HOLD_DB[_id].get("mode", "") != _mode or
|
|
"data" not in Hold.HOLD_DB[_id]
|
|
):
|
|
Hold.HOLD_DB[_id]["data"] = []
|
|
Hold.HOLD_DB[_id]["mode"] = _mode
|
|
Hold.HOLD_DB[_id]["track"] = PROMPT_ID
|
|
|
|
# match Hold.HOLD_DB[_key_id]["mode"]:
|
|
# _key_id can be invalid key
|
|
match Hold.HOLD_DB.get(_key_id, {}).get("mode", ""):
|
|
case "save":
|
|
for curr in Hold.HOLD_DB[Hold.HOLD_DB[_key_id]["id"]]["data"]:
|
|
Hold.HOLD_DB[_id]["data"].extend(curr)
|
|
# case "pin" if _mode == "pin":
|
|
# Hold.HOLD_DB[_key_id]["data"].clear()
|
|
case _:
|
|
Hold.HOLD_DB[_id]["data"].extend(_data_in)
|
|
|
|
result = [Hold.HOLD_DB[_id]["data"], sum(Hold.HOLD_DB.get(_key_id, {}).get("data", [[None]]), [])]
|
|
|
|
ui_text += f"Passed, Size: {len(result[0])}, "
|
|
elif param_flag:
|
|
mode_flag = _key_id in Hold.HOLD_DB and "mode" in Hold.HOLD_DB[_key_id]
|
|
|
|
if (
|
|
mode_flag and \
|
|
Hold.HOLD_DB[_key_id]["mode"] == "share" and \
|
|
_key_id in _prompt and _prompt[_key_id]["inputs"]["_mode"] == "share"
|
|
):
|
|
result = Hold.HOLD_DB[_key_id]["data"] if \
|
|
"data" in Hold.HOLD_DB[_key_id] and \
|
|
len(Hold.HOLD_DB[_key_id]["data"]) > 0 and \
|
|
Hold.HOLD_DB[_key_id].get("track") != PROMPT_ID else \
|
|
[[None]]
|
|
elif (
|
|
mode_flag and \
|
|
Hold.HOLD_DB[_id].get("mode", "") == "pin"
|
|
):
|
|
result = [[None]] if _data_in is None or len(_data_in) == 0 else [_data_in]
|
|
elif (
|
|
mode_flag and \
|
|
Hold.HOLD_DB[_key_id]["mode"] == "save" and \
|
|
_key_id in _prompt and _prompt[_key_id]["inputs"]["_mode"] == "save"
|
|
) or (
|
|
Hold.HOLD_DB[_id].get("track", "") == PROMPT_ID
|
|
):
|
|
result = [Hold.HOLD_DB[_key_id]["data"][-1]]
|
|
elif _data_in is not None and len(_data_in) > 0:
|
|
result = [_data_in]
|
|
else:
|
|
result = [[None]]
|
|
|
|
Hold.HOLD_DB[_id]["id"] = _key_id
|
|
if Hold.HOLD_DB[_id].get("mode", "") != _mode or Hold.HOLD_DB[_id].get("track", "") != PROMPT_ID:
|
|
Hold.HOLD_DB[_id]["track"] = PROMPT_ID
|
|
Hold.HOLD_DB[_id]["mode"] = _mode
|
|
|
|
if "data" in Hold.HOLD_DB[_key_id]:
|
|
result.append(sum(Hold.HOLD_DB[_key_id]["data"], []))
|
|
else:
|
|
result.append([None])
|
|
|
|
ui_text += f"Key: {_key_id}, Size: {len(result[0])}, "
|
|
else:
|
|
if Hold.HOLD_DB[_id].get("mode", "") != _mode or Hold.HOLD_DB[_id].get("track", "") != PROMPT_ID:
|
|
Hold.HOLD_DB[_id]["track"] = PROMPT_ID
|
|
Hold.HOLD_DB[_id]["data"] = []
|
|
Hold.HOLD_DB[_id]["mode"] = _mode
|
|
|
|
Hold.HOLD_DB[_id]["data"].append(_data_in)
|
|
result = [_data_in, sum(Hold.HOLD_DB[_id]["data"], [])]
|
|
|
|
if _mode == "share":
|
|
Hold.HOLD_DB[_id]["data"] = [_data_in]
|
|
|
|
ui_text += f"Size: {len(result[0])}, "
|
|
|
|
ui_text += f"Track: {Hold.HOLD_DB[_id]['track']}"
|
|
|
|
if hasattr(BASE_EXECUTOR, "caches"):
|
|
BASE_EXECUTOR.caches.outputs.set(_id, result)
|
|
|
|
return {
|
|
"ui": {
|
|
"text": [ui_text]
|
|
},
|
|
"result": result
|
|
}
|
|
|
|
@classmethod
|
|
def IS_CHANGED(cls, *args, **kwargs):
|
|
return float("NaN")
|
|
|
|
######################################################################################
|
|
|
|
class Loop:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"_event": (EventBoolStr("*"), ),
|
|
"_mode": (["sweep"], ), # Reserved
|
|
"_update": ("STRING", {
|
|
"default": "{'update': ''}",
|
|
"multiline": False
|
|
}),
|
|
},
|
|
"hidden": {
|
|
"_prompt": "PROMPT",
|
|
"_id": "UNIQUE_ID",
|
|
"_workflow": "EXTRA_PNGINFO",
|
|
"_dynprompt": "DYNPROMPT",
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("HOLD_TYPE", )
|
|
RETURN_NAMES = ("_hold", )
|
|
INPUT_IS_LIST = True
|
|
FUNCTION = "execute"
|
|
CATEGORY = "0246"
|
|
|
|
LOOP_DB = {}
|
|
|
|
def execute(self, _id = None, _prompt = None, _workflow = None, _event = None, _mode = None, _update = None, **kwargs):
|
|
global BASE_EXECUTOR
|
|
global PROMPT_ID
|
|
result = {
|
|
"result": (True, )
|
|
}
|
|
|
|
if (isinstance(_event[0], dict) and not _event[0]["bool"]) or (isinstance(_event[0], bool) and not _event[0]):
|
|
if hasattr(execution, "recursive_execute"):
|
|
# [TODO] Less shitty way to remove _event from inputs
|
|
try:
|
|
del BASE_EXECUTOR.outputs[_prompt[0][_id[0]]["inputs"]["_event"][0]]
|
|
except KeyError:
|
|
pass
|
|
|
|
if _mode[0] == "sweep":
|
|
if (PROMPT_ID, _id[0]) in Loop.LOOP_DB:
|
|
Loop.LOOP_DB[(PROMPT_ID, _id[0])]["count"] += 1
|
|
for curr_id in Loop.LOOP_DB[(PROMPT_ID, _id[0])]["exec"]:
|
|
if curr_id in BASE_EXECUTOR.outputs:
|
|
del BASE_EXECUTOR.outputs[curr_id]
|
|
else:
|
|
# Not the most efficient. The better way is to find all nodes that are connected to inputs and this loop node
|
|
Loop.LOOP_DB[(PROMPT_ID, _id[0])] = {
|
|
"count": 1,
|
|
"exec": trace_node(_prompt[0], _id[0], _workflow[0], _input = True) # , lambda curr_id: exec.append(curr_id) if curr_id not in exec else None)
|
|
}
|
|
while Loop.LOOP_DB[(PROMPT_ID, _id[0])]["count"] > 0:
|
|
Loop.LOOP_DB[(PROMPT_ID, _id[0])]["count"] -= 1
|
|
for curr_id in Loop.LOOP_DB[(PROMPT_ID, _id[0])]["exec"]:
|
|
if curr_id in BASE_EXECUTOR.outputs:
|
|
del BASE_EXECUTOR.outputs[curr_id]
|
|
success, error, ex = execution.recursive_execute(server.PromptServer.instance, _prompt[0], BASE_EXECUTOR.outputs, _id[0], {"extra_pnginfo": _workflow[0]}, set(), PROMPT_ID, BASE_EXECUTOR.outputs_ui, BASE_EXECUTOR.object_storage)
|
|
if success is not True:
|
|
raise ex
|
|
del Loop.LOOP_DB[(PROMPT_ID, _id[0])]
|
|
else:
|
|
dynprompt = kwargs["_dynprompt"][0]
|
|
del kwargs["_dynprompt"]
|
|
adj_id = []
|
|
contained = {}
|
|
upstream = {}
|
|
trace_node_back(_id[0], dynprompt, upstream)
|
|
if isinstance(_event[0], dict):
|
|
kwargs["_event"] = [[_event[0]["id"]]]
|
|
for curr_key in kwargs:
|
|
node_id = kwargs[curr_key][0][0]
|
|
adj_id.append(node_id)
|
|
trace_node_front(node_id, upstream, contained)
|
|
contained[_id[0]] = True
|
|
contained[node_id] = True
|
|
graph = comfy_graph_utils.GraphBuilder()
|
|
for node_id in contained:
|
|
graph.node(
|
|
dynprompt.get_node(node_id)["class_type"],
|
|
"_" if node_id == _id[0] else node_id
|
|
).set_override_display_id(node_id)
|
|
for node_id in contained:
|
|
node = graph.lookup_node("_" if node_id == _id[0] else node_id)
|
|
for k, v in dynprompt.get_node(node_id)["inputs"].items():
|
|
if comfy_graph_utils.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)
|
|
|
|
result["result"] = [graph.lookup_node("_").out(0)]
|
|
result["expand"] = graph.finalize()
|
|
|
|
return result
|
|
|
|
@classmethod
|
|
def IS_CHANGED(cls, *args, **kwargs):
|
|
return lib0246.check_update(kwargs["_update"])
|
|
|
|
######################################################################################
|
|
|
|
class Merge:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"_mode": (["flat", "deep"], ),
|
|
"_pad": ("STRING", {
|
|
"default": "_",
|
|
"multiline": False
|
|
}, )
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = lib0246.ByPassTypeTuple(("HIGHWAY_PIPE", "JUNCTION_PIPE", "*", ))
|
|
RETURN_NAMES = ("_way_out", "_junc_out", "_batch_out")
|
|
INPUT_IS_LIST = True
|
|
OUTPUT_IS_LIST = (False, False, True)
|
|
FUNCTION = "execute"
|
|
CATEGORY = "0246"
|
|
|
|
def execute(self, _pad = None, _mode = None, **kwargs):
|
|
way = None
|
|
junc = None
|
|
batch = []
|
|
batch_count = 0
|
|
|
|
for key in kwargs:
|
|
for i in range(len(kwargs[key])):
|
|
curr = kwargs[key][i]
|
|
|
|
if isinstance(curr, list):
|
|
if _mode[0] == "deep":
|
|
batch.extend(curr)
|
|
batch_count += len(curr)
|
|
else:
|
|
batch.append(curr)
|
|
batch_count += 1
|
|
elif isinstance(curr, lib0246.RevisionDict):
|
|
if curr[("kind")] == "junction":
|
|
if junc is None:
|
|
junc = lib0246.RevisionDict()
|
|
junc[("kind")] = "junction"
|
|
junc[("id")] = curr[("id")]
|
|
for type_name in curr.path_iter(("type", )):
|
|
total = junc.path_count(("data", type_name[1]))
|
|
for i in range(curr.path_count(("data", type_name[1]))):
|
|
junc[("data", type_name[1], total + i)] = curr[("data", type_name[1], i)]
|
|
junc[("index", type_name[1])] = 0
|
|
junc[("type", type_name[1])] = type(curr[("data", type_name[1], 0)]).__name__
|
|
else:
|
|
if way is None:
|
|
way = lib0246.RevisionDict()
|
|
way[("kind")] = "highway"
|
|
way[("id")] = curr[("id")]
|
|
for key_name in curr.path_iter(("type", )):
|
|
real_key = key_name[1]
|
|
while True:
|
|
if ("data", real_key) not in way:
|
|
# way[("type", real_key)] = type(curr[("data", key_name[1])]).__name__
|
|
way[("type", real_key)] = curr[("type", key_name[1])]
|
|
way[("data", real_key)] = curr[("data", key_name[1])]
|
|
break
|
|
elif _mode[0] == "deep" and curr[("type", key_name[1])] == way[("type", real_key)]:
|
|
if not isinstance(way[("data", real_key)], list):
|
|
way[("data", real_key)] = lib0246.RevisionBatch(*way[("data", real_key)])
|
|
if isinstance(curr[("data", key_name[1])], list):
|
|
way[("data", real_key)].extend(curr[("data", key_name[1])])
|
|
else:
|
|
way[("data", real_key)].append(curr[("data", key_name[1])])
|
|
break
|
|
real_key += _pad[0]
|
|
else:
|
|
batch.append(curr)
|
|
batch_count += 1
|
|
|
|
return (way, junc, batch)
|
|
|
|
######################################################################################
|
|
|
|
class Beautify:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"optional": {
|
|
"data": (lib0246.TautologyStr("*"), ),
|
|
},
|
|
"required": {
|
|
"mode": (["basic", "more", "full", "json", "wat"], ),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ()
|
|
INPUT_IS_LIST = True
|
|
OUTPUT_NODE = True
|
|
FUNCTION = "execute"
|
|
CATEGORY = "0246"
|
|
|
|
def execute(self, data = None, mode = None, **kwargs):
|
|
for key in kwargs:
|
|
if key.startswith("data"):
|
|
data = kwargs[key]
|
|
break
|
|
|
|
raw_mode = 0
|
|
res_str = None
|
|
match mode[0]:
|
|
case "basic":
|
|
raw_mode = 0
|
|
case "more":
|
|
raw_mode = 1
|
|
case "full":
|
|
raw_mode = 2
|
|
case "json":
|
|
res_str = None
|
|
try:
|
|
res_str = json.dumps(data, indent=2)
|
|
except TypeError:
|
|
res_str = "Cannot convert to JSON."
|
|
case "wat":
|
|
res_str = str(wat.str.long.dunder / data)
|
|
|
|
if res_str is None:
|
|
res_str = lib0246.beautify_structure(data, 0, raw_mode)
|
|
|
|
return {
|
|
"ui": {
|
|
"text": [res_str]
|
|
}
|
|
}
|
|
|
|
######################################################################################
|
|
|
|
class Stringify:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"_mode": (["basic", "value", "force"],),
|
|
"_delimiter": ("STRING", {
|
|
"default": ", ",
|
|
"multiline": False
|
|
}),
|
|
},
|
|
"hidden": {
|
|
"_id": "UNIQUE_ID",
|
|
"_prompt": "PROMPT",
|
|
"_workflow": "EXTRA_PNGINFO"
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING", )
|
|
RETURN_NAMES = ("_str", )
|
|
INPUT_IS_LIST = True
|
|
OUTPUT_IS_LIST = (True, )
|
|
FUNCTION = "execute"
|
|
CATEGORY = "0246"
|
|
|
|
def execute(self, _delimiter = None, _mode = None, _id = None, _prompt = None, _workflow = None, **kwargs):
|
|
res = []
|
|
cloud_delim = None
|
|
_delimiter = copy.copy(_delimiter)
|
|
|
|
for value in kwargs.values():
|
|
if isinstance(value, list):
|
|
for item in value:
|
|
if isinstance(item, CloudData) and (_mode[0] == "basic" or _mode[0] == "value"):
|
|
if cloud_delim is None:
|
|
if _delimiter[-1] is None:
|
|
raise Exception("Last delimiter in delimiter batch cannot be None if Cloud is present.")
|
|
cloud_delim = _delimiter.pop()
|
|
item = cloud_delim.join(map(str, item.data_eval(_id[0], _prompt[0], _workflow[0])))
|
|
elif _mode[0] == "basic" and type(item).__str__ is object.__str__:
|
|
continue
|
|
elif _mode[0] == "value":
|
|
if isinstance(item, object) and type(item).__module__ != 'builtins' and type(item).__str__ is object.__str__:
|
|
continue
|
|
try:
|
|
item_str = str(item)
|
|
if item_str:
|
|
res.append(item_str)
|
|
except Exception:
|
|
continue
|
|
|
|
if _delimiter is not None and len(_delimiter) > 0 and all(isinstance(_, str) for _ in _delimiter):
|
|
res = [curr.join(res) for curr in _delimiter]
|
|
return {
|
|
"ui": {
|
|
"text": [res]
|
|
},
|
|
"result": [res]
|
|
}
|
|
|
|
######################################################################################
|
|
|
|
class BoxRange:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": lib0246.WildDict({
|
|
"script_box_regex": ("STRING", {
|
|
"default": r"(?P<x>^x$)|(?P<y>^y$)|(?P<w>^width$)|(?P<h>^height$)|(^@x$)|(^@y$)|(^@w$)|(^@h$)|(^%x$)|(^%y$)|(^%w$)|(^%h$)",
|
|
"multiline": False
|
|
}),
|
|
"script_order": ("STRING", {
|
|
"default": "box",
|
|
"multiline": False
|
|
}),
|
|
"box_range": ("BOX_RANGE", {
|
|
"ratio": {
|
|
"data": {
|
|
"width": 512,
|
|
"height": 512,
|
|
},
|
|
},
|
|
"row_count": 10,
|
|
"col_count": 10,
|
|
"flex": {
|
|
"share": 1,
|
|
"min_h": 50,
|
|
"center": True,
|
|
}
|
|
}),
|
|
}),
|
|
"hidden": {
|
|
"_id": "UNIQUE_ID"
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = lib0246.ByPassTypeTuple(("*", ))
|
|
RETURN_NAMES = lib0246.ByPassTypeTuple(("_data", ))
|
|
INPUT_IS_LIST = True
|
|
OUTPUT_IS_LIST = lib0246.TautologyRest
|
|
FUNCTION = "execute"
|
|
CATEGORY = "0246"
|
|
|
|
FUNC_REGEX = functools.lru_cache(maxsize=16)(re.compile)
|
|
FUNC_KEY_LIST = [
|
|
"W", "H", "S8W", "S8H", "R", "A",
|
|
"x", "y", "w", "h",
|
|
"px", "py", "pw", "ph",
|
|
# "rx", "ry",
|
|
"s8x", "s8y", "s8w", "s8h",
|
|
]
|
|
|
|
@classmethod
|
|
def process_box(
|
|
cls, build_fn, box_range, box_range_ratio, #res_dict = None, res_list = None,
|
|
_W = False, _H = False, _S8W = False, _S8H = False, _R = False, _A = False,
|
|
_x = False, _y = False, _w = False, _h = False,
|
|
_px = False, _py = False, _pw = False, _ph = False,
|
|
# _rx = False, _ry = False,
|
|
_s8x = False, _s8y = False, _s8w = False, _s8h = False,
|
|
):
|
|
if _W:
|
|
build_fn(box_range_ratio["data"]["width"], "W")
|
|
if _H:
|
|
build_fn(box_range_ratio["data"]["height"], "H")
|
|
if _S8W:
|
|
build_fn(lib0246.snap(box_range_ratio["data"]["width"], 8), "S8W")
|
|
if _S8H:
|
|
build_fn(lib0246.snap(box_range_ratio["data"]["height"], 8), "S8H")
|
|
if _R:
|
|
build_fn(box_range_ratio["data"]["ratio"], "R")
|
|
if _A:
|
|
build_fn(box_range_ratio["data"]["width"] * box_range_ratio["data"]["height"], "A")
|
|
|
|
for i in range(len(box_range["data"])):
|
|
if _x:
|
|
build_fn(lib0246.map(
|
|
box_range["data"][i][0],
|
|
box_range["area"][0], box_range["area"][0] + box_range["area"][2],
|
|
0, box_range_ratio["data"]["width"]
|
|
), "x")
|
|
if _y:
|
|
build_fn(lib0246.map(
|
|
box_range["data"][i][1],
|
|
box_range["area"][1], box_range["area"][1] + box_range["area"][3],
|
|
0, box_range_ratio["data"]["height"]
|
|
), "y")
|
|
if _w:
|
|
build_fn(lib0246.map(
|
|
box_range["data"][i][2],
|
|
0, box_range["area"][2],
|
|
0, box_range_ratio["data"]["width"]
|
|
), "w")
|
|
if _h:
|
|
build_fn(lib0246.map(
|
|
box_range["data"][i][3],
|
|
0, box_range["area"][3],
|
|
0, box_range_ratio["data"]["height"]
|
|
), "h")
|
|
if _px:
|
|
build_fn(lib0246.norm(
|
|
box_range["data"][i][0],
|
|
box_range["area"][0], box_range["area"][0] + box_range["area"][2]
|
|
), "px")
|
|
if _py:
|
|
build_fn(lib0246.norm(
|
|
box_range["data"][i][1],
|
|
box_range["area"][1], box_range["area"][1] + box_range["area"][3]
|
|
), "py")
|
|
if _pw:
|
|
build_fn(box_range["data"][i][2] / box_range["area"][2], "pw")
|
|
if _ph:
|
|
build_fn(box_range["data"][i][3] / box_range["area"][3], "ph")
|
|
# if _rx:
|
|
# res["rx"].append(box_range["data"][i][0])
|
|
# if _ry:
|
|
# res["ry"].append(box_range["data"][i][1])
|
|
if _s8x:
|
|
build_fn(lib0246.snap(lib0246.map(
|
|
box_range["data"][i][0],
|
|
box_range["area"][0], box_range["area"][0] + box_range["area"][2],
|
|
0, box_range_ratio["data"]["width"]
|
|
), 8), "s8x")
|
|
if _s8y:
|
|
build_fn(lib0246.snap(lib0246.map(
|
|
box_range["data"][i][1],
|
|
box_range["area"][1], box_range["area"][1] + box_range["area"][3],
|
|
0, box_range_ratio["data"]["height"]
|
|
), 8), "s8y")
|
|
if _s8w:
|
|
build_fn(lib0246.snap(lib0246.map(
|
|
box_range["data"][i][2],
|
|
0, box_range["area"][2],
|
|
0, box_range_ratio["data"]["width"]
|
|
), 8), "s8w")
|
|
if _s8h:
|
|
build_fn(lib0246.snap(lib0246.map(
|
|
box_range["data"][i][3],
|
|
0, box_range["area"][3],
|
|
0, box_range_ratio["data"]["height"]
|
|
), 8), "s8h")
|
|
|
|
@classmethod
|
|
def process_box_batch(cls, batch, data, name):
|
|
curr_index = BoxRange.FUNC_KEY_LIST.index(name)
|
|
if curr_index >= 0:
|
|
batch[curr_index].append(data)
|
|
|
|
def execute(self, _id = None, script_box_regex = None, script_order = None, box_range = None, box_range_ratio = None):
|
|
if isinstance(script_box_regex, list):
|
|
script_box_regex = script_box_regex[0]
|
|
if isinstance(script_order, list):
|
|
script_order = script_order[0]
|
|
if isinstance(box_range, list):
|
|
box_range = box_range[0]
|
|
if isinstance(box_range_ratio, list):
|
|
box_range_ratio = box_range_ratio[0]
|
|
|
|
full_res = [None]
|
|
|
|
script_regex = BoxRange.FUNC_REGEX(script_box_regex)
|
|
|
|
batch_res = []
|
|
for i in range(len(BoxRange.FUNC_KEY_LIST)):
|
|
batch_res.append([])
|
|
|
|
BoxRange.process_box(
|
|
lambda _, name: BoxRange.process_box_batch(batch_res, _, name),
|
|
box_range, box_range_ratio,
|
|
**{("_" + key): script_regex.match("@" + key) is not None for key in BoxRange.FUNC_KEY_LIST}
|
|
)
|
|
|
|
for i in range(len(batch_res)):
|
|
if len(batch_res[i]) > 0:
|
|
full_res.append(batch_res[i])
|
|
|
|
if len(script_regex.groupindex.keys() & BoxRange.FUNC_KEY_LIST) > 0:
|
|
def temp_func(pin, res, **kwargs):
|
|
if res is None:
|
|
result = {key: [] for key in BoxRange.FUNC_KEY_LIST}
|
|
track = {key: 0 for key in BoxRange.FUNC_KEY_LIST}
|
|
for pin_key in pin:
|
|
temp = script_regex.match(pin_key)
|
|
if temp:
|
|
for res_key in result:
|
|
if temp.groupdict().get(res_key, None) is not None:
|
|
pin[pin_key] = result[res_key]
|
|
track[res_key] += 1
|
|
BoxRange.process_box(
|
|
lambda _, name: result[name].append(_),
|
|
box_range, box_range_ratio,
|
|
**{("_" + key): track[key] > 0 for key in BoxRange.FUNC_KEY_LIST}
|
|
)
|
|
return True
|
|
return False
|
|
full_res[0] = ScriptData({
|
|
"id": _id,
|
|
"func": temp_func,
|
|
"order": script_order,
|
|
"kind": "wrap"
|
|
})
|
|
else:
|
|
full_res[0] = {
|
|
"box": box_range,
|
|
"dim": box_range_ratio
|
|
}
|
|
|
|
BoxRange.process_box(
|
|
lambda _, name: full_res.append([_]),
|
|
box_range, box_range_ratio,
|
|
**{("_" + key): script_regex.match("%" + key) is not None for key in BoxRange.FUNC_KEY_LIST}
|
|
)
|
|
|
|
return full_res
|
|
|
|
@classmethod
|
|
def IS_CHANGED(cls, *args, **kwargs):
|
|
return kwargs["box_range"][0] if isinstance(kwargs["box_range"], list) else kwargs["box_range"]
|
|
|
|
######################################################################################
|
|
|
|
class ScriptNode:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"script_node": (list(nodes.NODE_CLASS_MAPPINGS.keys()), ),
|
|
"script_pin_order": ("STRING", {
|
|
"default": "",
|
|
"multiline": False
|
|
}),
|
|
"script_pin_mode": (["pin_highway", "pin_junction", "pin_direct"], ),
|
|
"script_res_order": ("STRING", {
|
|
"default": "",
|
|
"multiline": False
|
|
}),
|
|
"script_res_mode": (["res_junction", "res_highway_batch"], ),
|
|
"script_ignore_regex": ("STRING", {
|
|
"default": r"(?P<_>^$)",
|
|
"multiline": False
|
|
}),
|
|
},
|
|
"optional": {
|
|
"pipe_in": (lib0246.TautologyStr("*"), ),
|
|
},
|
|
"hidden": {
|
|
"_prompt": "PROMPT",
|
|
"_id": "UNIQUE_ID",
|
|
"_workflow": "EXTRA_PNGINFO"
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = (lib0246.TautologyStr("*"), "SCRIPT_DATA", "SCRIPT_DATA", "SCRIPT_DATA")
|
|
RETURN_NAMES = ("pipe_out", "script_pin_data", "script_exec_data", "script_res_data")
|
|
INPUT_IS_LIST = False
|
|
OUTPUT_IS_LIST = lib0246.ContradictAll
|
|
FUNCTION = "execute"
|
|
CATEGORY = "0246"
|
|
|
|
FUNC_REGEX = functools.lru_cache(maxsize=16)(re.compile)
|
|
|
|
def execute(
|
|
self,
|
|
_id = None, _prompt = None, _workflow = None,
|
|
pipe_in = None,
|
|
script_node = None,
|
|
script_pin_order = None, script_pin_mode = None,
|
|
script_res_order = None, script_res_mode = None,
|
|
script_ignore_regex = None,
|
|
**kwargs
|
|
):
|
|
|
|
pin_func = None
|
|
res_func = None
|
|
pipe_flag = pipe_in is not None and isinstance(pipe_in, lib0246.RevisionDict)
|
|
|
|
class_type = nodes.NODE_CLASS_MAPPINGS[script_node]
|
|
|
|
input_type = getattr(class_type, "INPUT_TYPES")()
|
|
if "required" not in input_type or len(input_type["required"]) == 0:
|
|
raise Exception(f"Node class {_prompt[_id]['class_type']} does not have any required input.")
|
|
|
|
output_type = getattr(class_type, "RETURN_TYPES")
|
|
if len(output_type) == 0:
|
|
raise Exception(f"Node class {_prompt[_id]['class_type']} does not have any output.")
|
|
|
|
match script_pin_mode:
|
|
case "pin_highway" if pipe_flag and pipe_in[("kind")] == "highway":
|
|
pin_func = highway_unpack(pipe_in)
|
|
case "pin_junction" if pipe_flag and pipe_in[("kind")] == "junction":
|
|
pin_func = junction_unpack(pipe_in, input_type, ScriptNode.FUNC_REGEX(script_ignore_regex))
|
|
case "pin_direct":
|
|
raise Exception("pin_direct is not supported yet.")
|
|
case _:
|
|
pass
|
|
|
|
match script_res_mode:
|
|
case "res_junction":
|
|
def temp_func(res, **kwargs):
|
|
if res is not None:
|
|
old_res = copy.copy(res)
|
|
res.clear()
|
|
res.append([lib0246.RevisionDict()])
|
|
|
|
for curr_data in old_res:
|
|
for type_name, curr_elem in zip(output_type, curr_data):
|
|
junction_pack_loop(res[0][0], type_name, curr_elem)
|
|
res[0][0][("kind")] = "junction"
|
|
res[0][0][("id")] = _id
|
|
|
|
return True
|
|
return False
|
|
res_func = temp_func
|
|
case "res_highway_batch":
|
|
def temp_func(res, **kwargs):
|
|
if res is not None:
|
|
old_res = copy.copy(res)
|
|
res.clear()
|
|
res.append([lib0246.RevisionDict()])
|
|
|
|
name_list = getattr(class_type, "RETURN_NAMES") if hasattr(class_type, "RETURN_NAMES") else output_type
|
|
for data_curr in old_res:
|
|
for type_curr, name_curr, count in zip(output_type, name_list, range(len(output_type))):
|
|
res[0][0][("type", name_curr)] = type_curr
|
|
if ("data", name_curr) not in res[0][0]:
|
|
res[0][0][("data", name_curr)] = lib0246.RevisionBatch()
|
|
res[0][0][("data", name_curr)].append(data_curr[count])
|
|
|
|
res[0][0][("kind")] = "highway"
|
|
res[0][0][("id")] = _id
|
|
return True
|
|
return False
|
|
res_func = temp_func
|
|
case _:
|
|
pass
|
|
|
|
node_args = set()
|
|
node_args_base = set()
|
|
node_args_hide = set()
|
|
node_args_hide_full = set()
|
|
for tuple_path in lib0246.dict_iter(input_type):
|
|
node_args.add(tuple_path[1])
|
|
if tuple_path[0] == "hidden":
|
|
node_args_hide.add(tuple_path[1])
|
|
temp_type = lib0246.dict_get(input_type, tuple_path)
|
|
if isinstance(temp_type, tuple):
|
|
temp_type = temp_type[0]
|
|
node_args_hide_full.add((tuple_path[1], temp_type))
|
|
else:
|
|
node_args_base.add(tuple_path[1])
|
|
|
|
return (
|
|
pipe_in,
|
|
None if pin_func is None else ScriptData({
|
|
"id": _id,
|
|
"func": pin_func,
|
|
"order": script_pin_order,
|
|
"kind": "wrap"
|
|
}), ScriptData({
|
|
"id": _id,
|
|
"func": functools.partial(
|
|
script_node_exec,
|
|
node_inst=class_type(),
|
|
node_name=script_node,
|
|
node_class=class_type,
|
|
node_input=input_type,
|
|
node_args=node_args,
|
|
node_args_base=node_args_base,
|
|
node_args_hide=node_args_hide,
|
|
node_args_hide_full=node_args_hide_full,
|
|
),
|
|
"kind": "exec"
|
|
}),
|
|
None if res_func is None else ScriptData({
|
|
"id": _id,
|
|
"func": res_func,
|
|
"order": script_res_order,
|
|
"kind": "wrap"
|
|
})
|
|
)
|
|
|
|
######################################################################################
|
|
|
|
class ScriptPile:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"pipe_in": (lib0246.TautologyStr("*"), ),
|
|
"script_rule_loop_mode": (["_", "slice", "cycle"], ),
|
|
"script_rule_regex": ("STRING", {
|
|
"default": r"(?P<m>^(@model|%MODEL)$)|(?P<c>^(@clip|%CLIP)$)|(?P<_>^$)",
|
|
"multiline": False
|
|
}),
|
|
"script_rule_pin_mode": ([
|
|
# {a: [11, 22], b: [33.0, 44.0]} => {..., a: 11, b: 33.0, ...}, {..., a: 22, b: 44.0, ...}
|
|
"pin_highway_batch",
|
|
|
|
# {a: junc(11, 22), b: junc(33.0, 44.0)} => {..., a: 11, b: 33.0, ...}, {..., a: 22, b: 44.0, ...}
|
|
"pin_highway_junction",
|
|
|
|
# junc({a: 11, b: 33.0}, {a: 22, b: 44.0}) => {..., a: 11, b: 33.0, ...}, {..., a: 22, b: 44.0, ...}
|
|
"pin_junction_highway",
|
|
|
|
# junc(11, 22, 33.0, 44.0, 33, 44) => {..., a: 11, b: 33.0, c: 22, ...}, {..., a: 33, b: 44.0, c: 44, ...}
|
|
"pin_junction"
|
|
], ),
|
|
"count": ("INT", {
|
|
"default": 0,
|
|
"min": 0,
|
|
"max": sys.maxsize
|
|
}),
|
|
},
|
|
"hidden": {
|
|
"_prompt": "PROMPT",
|
|
"_id": "UNIQUE_ID",
|
|
"_workflow": "EXTRA_PNGINFO"
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = (lib0246.TautologyStr("*"), "SCRIPT_DATA", )
|
|
RETURN_NAMES = ("pipe_out", "script_rule_data", )
|
|
OUTPUT_IS_LIST = (False, False, )
|
|
FUNCTION = "execute"
|
|
CATEGORY = "0246"
|
|
|
|
FUNC_REGEX = functools.lru_cache(maxsize=16)(re.compile)
|
|
|
|
@classmethod
|
|
@functools.lru_cache(maxsize=16)
|
|
def build_pile(cls, script_name, script_type, pin_key, script_rule_regex):
|
|
pile_data = {
|
|
"data": {},
|
|
"type": {}
|
|
}
|
|
count = 0
|
|
for curr_pin in pin_key:
|
|
curr_match_pin = script_rule_regex.match("@" + curr_pin)
|
|
if curr_match_pin:
|
|
for (i, curr_out), curr_type in zip(enumerate(script_name), script_type):
|
|
curr_match_out = script_rule_regex.match("%" + curr_out)
|
|
if curr_match_out and curr_match_out.lastgroup == curr_match_pin.lastgroup:
|
|
pile_data["data"][(curr_out, curr_type)] = (i, curr_pin, count)
|
|
count += 1
|
|
if curr_type not in pile_data["type"]:
|
|
pile_data["type"][curr_type] = 0
|
|
pile_data["type"][curr_type] += 1
|
|
break
|
|
else:
|
|
raise Exception(f"Missing corresponding output for input \"{curr_pin}\".")
|
|
return pile_data
|
|
|
|
@classmethod
|
|
def process(cls, pile_data, pipe_iter, curr_func, count, script, func, pin, res, **kwargs):
|
|
curr_count = 0
|
|
try:
|
|
pin.update(next(pipe_iter)[0])
|
|
except StopIteration:
|
|
return True
|
|
|
|
while True:
|
|
flag = 0
|
|
|
|
temp_res = func(pin=pin) if \
|
|
curr_func is None else (
|
|
list(func(pin=curr_raw_pin[0]) \
|
|
for curr_raw_pin in curr_func(pin))
|
|
)
|
|
|
|
curr_count += 1
|
|
|
|
try:
|
|
pin.update(next(pipe_iter)[0])
|
|
except StopIteration:
|
|
flag += 1
|
|
|
|
if flag > 0 or (count > 0 and curr_count >= count):
|
|
res.extend(temp_res)
|
|
return True
|
|
|
|
temp_res = list(lib0246.transpose(list(temp_res), list))
|
|
pin.update({
|
|
curr_pin: temp_res[i] for i, curr_pin, _ in pile_data["data"].values()
|
|
})
|
|
|
|
def execute(
|
|
self, _id = None, _prompt = None, _workflow = None,
|
|
script_rule_regex = None, script_rule_loop_mode = None, script_rule_pin_mode = None,
|
|
count = None, pipe_in = None
|
|
):
|
|
script_rule_regex = ScriptPile.FUNC_REGEX(script_rule_regex)
|
|
|
|
curr_func = None
|
|
match script_rule_loop_mode:
|
|
case "cycle":
|
|
curr_func = lib0246.dict_product
|
|
case "slice":
|
|
curr_func = lib0246.dict_slice
|
|
case "_":
|
|
pass
|
|
case other:
|
|
raise Exception(f"Script rule loop mode \"{other}\" is not supported yet.")
|
|
|
|
def temp_func(script, func, pin, res, **kwargs):
|
|
node_data = script[("script", "exec")]()
|
|
pile_data = ScriptPile.build_pile(
|
|
tuple(
|
|
getattr(node_data[0], "RETURN_NAMES") if \
|
|
hasattr(node_data[0], "RETURN_NAMES") else \
|
|
getattr(node_data[0], "RETURN_TYPES")
|
|
),
|
|
tuple(getattr(node_data[0], "RETURN_TYPES")),
|
|
tuple(pin.keys()),
|
|
script_rule_regex
|
|
)
|
|
|
|
match script_rule_pin_mode:
|
|
case "pin_highway_batch":
|
|
return ScriptPile.process(
|
|
pile_data, lib0246.dict_slice({
|
|
(key[1]): pipe_in[key] for key in pipe_in.path_iter(("data", ))
|
|
}, lambda _: [_]),
|
|
curr_func, count,
|
|
script, func, pin, res
|
|
)
|
|
|
|
case "pin_junction":
|
|
pin_key_set = set(pin.keys())
|
|
diff_key_set = (pin_key_set - node_data[5]) | (node_data[5] - pin_key_set)
|
|
|
|
if len(diff_key_set) == 0:
|
|
raise Exception("No input and output to pile up.")
|
|
|
|
input_type = {}
|
|
for input_key in lib0246.dict_iter(node_data[3]):
|
|
if input_key[1] in diff_key_set:
|
|
lib0246.dict_set(input_type, input_key, lib0246.dict_get(node_data[3], input_key))
|
|
|
|
data_dict = junction_unpack_raw(
|
|
pipe_in, input_type,
|
|
list(filter(lambda x: x != "hidden", input_type.keys())),
|
|
base_dict={
|
|
k: [] for k in diff_key_set
|
|
},
|
|
pack_func=lambda _: [_],
|
|
type_func=lambda _: "STRING" if isinstance(_, list) else _,
|
|
fill_func=lambda d, k, v: d.setdefault(k, []).extend(v),
|
|
regex_inst=script_rule_regex,
|
|
block=sys.maxsize
|
|
)
|
|
|
|
return ScriptPile.process(
|
|
pile_data, lib0246.dict_slice(data_dict, lambda _: [_]),
|
|
curr_func, count,
|
|
script, func, pin, res
|
|
)
|
|
|
|
case other:
|
|
raise Exception(f"Script rule pin mode \"{other}\" is not supported yet.")
|
|
|
|
return False
|
|
|
|
return (pipe_in, ScriptData({
|
|
"id": _id,
|
|
"func": temp_func,
|
|
"kind": "rule"
|
|
}), )
|
|
|
|
######################################################################################
|
|
|
|
class ScriptRule:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"script_rule_mode": (["_", "slice", "cycle"], ),
|
|
},
|
|
"hidden": {
|
|
"_id": "UNIQUE_ID",
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("SCRIPT_DATA", )
|
|
RETURN_NAMES = ("script_rule_data", )
|
|
OUTPUT_IS_LIST = (False, )
|
|
FUNCTION = "execute"
|
|
CATEGORY = "0246"
|
|
|
|
def execute(
|
|
self, _id = None, script_rule_mode = None
|
|
):
|
|
rule_data = None
|
|
|
|
if script_rule_mode is not None:
|
|
if script_rule_mode == "_":
|
|
rule_data = ScriptData({
|
|
"id": _id,
|
|
"func": script_rule_direct,
|
|
"kind": "rule"
|
|
})
|
|
else:
|
|
match script_rule_mode:
|
|
case "slice":
|
|
rule_data = ScriptData({
|
|
"id": _id,
|
|
"func": script_rule_slice,
|
|
"kind": "rule"
|
|
})
|
|
case "cycle":
|
|
rule_data = ScriptData({
|
|
"id": _id,
|
|
"func": script_rule_product,
|
|
"kind": "rule"
|
|
})
|
|
case _:
|
|
raise Exception(f"Invalid rule mode \"{script_rule_mode}\".")
|
|
|
|
return (rule_data, )
|
|
|
|
######################################################################################
|
|
|
|
class Script:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"_exec_mode": (["pass", "act"], ),
|
|
"_sort_mode": ("STRING", {
|
|
"default": "INT",
|
|
"multiline": False
|
|
}),
|
|
},
|
|
"optional": {
|
|
"_script_in": ("SCRIPT_PIPE", ),
|
|
},
|
|
"hidden": {
|
|
"_prompt": "PROMPT",
|
|
"_id": "UNIQUE_ID",
|
|
"_workflow": "EXTRA_PNGINFO"
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = lib0246.ByPassTypeTuple(("SCRIPT_PIPE", ))
|
|
RETURN_NAMES = lib0246.ByPassTypeTuple(("_script_out", ))
|
|
INPUT_IS_LIST = True
|
|
OUTPUT_IS_LIST = lib0246.TautologyRest
|
|
FUNCTION = "execute"
|
|
CATEGORY = "0246"
|
|
|
|
def execute(self, _id = None, _prompt = None, _workflow = None, _exec_mode = None, _sort_mode = None, _script_in = None, **kwargs):
|
|
if isinstance(_prompt, list):
|
|
_prompt = _prompt[0]
|
|
if isinstance(_id, list):
|
|
_id = _id[0]
|
|
if isinstance(_workflow, list):
|
|
_workflow = _workflow[0]
|
|
|
|
if isinstance(_exec_mode, list):
|
|
_exec_mode = _exec_mode[0]
|
|
if isinstance(_sort_mode, list):
|
|
_sort_mode = _sort_mode[0]
|
|
|
|
if _script_in is None or len(_script_in) == 0:
|
|
_script_in = lib0246.RevisionDict()
|
|
else:
|
|
_script_in = lib0246.RevisionDict(_script_in[0])
|
|
|
|
script_wrap_count = _script_in.path_count(("script", "wrap"))
|
|
|
|
for key in kwargs:
|
|
for elem in kwargs[key]:
|
|
if isinstance(elem, ScriptData):
|
|
match elem["kind"]:
|
|
case "exec" if "func" in elem:
|
|
_script_in[("script", "exec")] = elem["func"]
|
|
case "rule":
|
|
_script_in[("script", "rule")] = elem["func"]
|
|
case "wrap":
|
|
_script_in[("script", "wrap", script_wrap_count)] = elem["func"]
|
|
_script_in[("script", "order", script_wrap_count)] = elem["order"]
|
|
_script_in[("script", "id", script_wrap_count)] = elem["id"]
|
|
script_wrap_count += 1
|
|
else:
|
|
curr_nodes = _workflow["workflow"]["nodes"]
|
|
node_index = next(i for i, _ in enumerate(curr_nodes) if _["id"] == int(_id))
|
|
_script_in[("script", "data", next(_ for _ in curr_nodes[node_index]["inputs"] if _["name"] == key).get("label", key))] = elem
|
|
|
|
pin = None
|
|
res = []
|
|
|
|
if _exec_mode == "act":
|
|
# INT FLOAT UNSIGNED SIGNED REAL NOEXP NUMAFTER PATH COMPATIBILITYNORMALIZE
|
|
# LOCALE LOCALEALPHA LOCALENUM IGNORECASE LOWERCASEFIRST GROUPLETTERS CAPITALFIRST
|
|
# UNGROUPLETTERS NANLAST PRESORT
|
|
|
|
_script_in.sort(
|
|
("script", "order"), ("script", "wrap"),
|
|
functools.reduce(
|
|
lambda x, y: x | y,
|
|
map(lambda _: getattr(natsort.ns, _.strip().upper()), _sort_mode.split(" "))
|
|
)
|
|
)
|
|
|
|
inst = {
|
|
"id": [_id],
|
|
"prompt": _prompt,
|
|
"workflow": _workflow,
|
|
}
|
|
|
|
pin = {}
|
|
|
|
for key in _script_in.path_iter_arr(("script", "wrap")):
|
|
if _script_in[key](script=_script_in, inst=inst, pin=pin, res=None):
|
|
inst["id"].append(_script_in[("script", "id", key[2])])
|
|
|
|
if ("script", "rule") in _script_in:
|
|
_script_in[("script", "rule")](
|
|
script=_script_in, inst=inst, pin=pin, res=res,
|
|
func=functools.partial(
|
|
_script_in.get(("script", "exec"), lambda *args, **kwargs: None),
|
|
script=_script_in, inst=inst
|
|
)
|
|
)
|
|
else:
|
|
res.extend(_script_in[("script", "exec")](script=_script_in, inst=inst, pin=pin, res=res))
|
|
|
|
inst["id"].clear()
|
|
inst["id"].append(_id)
|
|
|
|
for key in _script_in.path_iter_arr(("script", "wrap")):
|
|
if _script_in[key](script=_script_in, inst=inst, pin=pin, res=res):
|
|
inst["id"].append(_script_in[("script", "id", key[2])])
|
|
|
|
res = lib0246.transpose(res, list)
|
|
|
|
_script_in[("kind")] = "script"
|
|
_script_in[("id")] = _id
|
|
|
|
return {
|
|
"ui": {
|
|
"text": [""]
|
|
},
|
|
"result": [_script_in, *res]
|
|
}
|
|
|
|
######################################################################################
|
|
|
|
class Hub:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": lib0246.WildDict(),
|
|
"hidden": {
|
|
"_prompt": "PROMPT",
|
|
"_id": "UNIQUE_ID",
|
|
"_workflow": "EXTRA_PNGINFO"
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = lib0246.TautologyDictStr()
|
|
INPUT_IS_LIST = True
|
|
OUTPUT_IS_LIST = lib0246.TautologyAll
|
|
FUNCTION = "execute"
|
|
CATEGORY = "0246"
|
|
|
|
SPECIAL = ["__PIPE__", "__BATCH_PRIM__", "__BATCH_COMBO__"]
|
|
|
|
def execute(self, _id = None, _prompt = None, _workflow = None, **kwargs):
|
|
# [TODO] Maybe set OUTPUT_IS_LIST depends on widget?
|
|
# And allow dumping "node:..."?
|
|
|
|
res_data = {}
|
|
temp_data = {}
|
|
type_data = {}
|
|
name_data = {}
|
|
|
|
curr_nodes = _workflow[0]["workflow"]["nodes"]
|
|
self_index = None
|
|
for i, _ in enumerate(curr_nodes):
|
|
if _["id"] == int(_id[0]):
|
|
self_index = i
|
|
break
|
|
curr_extra = _workflow[0]["workflow"]["extra"]
|
|
|
|
for i, pin in enumerate(curr_nodes[self_index]["outputs"]):
|
|
if pin["name"] in kwargs:
|
|
if pin["name"].startswith("sole"):
|
|
curr_type = curr_extra["0246.HUB_DATA"][_id[0]]["sole_type"][pin["name"]][-1]
|
|
curr_index = next(i for i, _ in enumerate(curr_nodes[self_index]["outputs"]) if _["name"] == pin["name"])
|
|
name_data[curr_index] = pin["name"]
|
|
match curr_type:
|
|
case "__BATCH_PRIM__" | "__BATCH_COMBO__" | "__PIPE__":
|
|
temp_data[curr_index] = kwargs[pin["name"]][0]
|
|
case _:
|
|
res_data[curr_index] = kwargs[pin["name"]]
|
|
type_data[curr_index] = curr_type
|
|
else:
|
|
res_data[i] = [None]
|
|
|
|
for index_temp in temp_data:
|
|
match curr_extra["0246.HUB_DATA"][_id[0]]["sole_type"][name_data[index_temp]][-1]:
|
|
case "__BATCH_PRIM__":
|
|
res_data[index_temp] = []
|
|
for index_type in type_data:
|
|
if type_data[index_type] == temp_data[index_temp]:
|
|
res_data[index_temp].extend(res_data[index_type])
|
|
case "__BATCH_COMBO__":
|
|
res_data[index_temp] = []
|
|
for index_type in type_data:
|
|
if name_data[index_type].endswith(
|
|
temp_data[index_temp] if isinstance(temp_data[index_temp], str) else \
|
|
":".join(temp_data[index_temp])
|
|
+ ":COMBO"
|
|
):
|
|
res_data[index_temp].extend(res_data[index_type])
|
|
case "__PIPE__":
|
|
if temp_data[index_temp] == "HIGHWAY_PIPE":
|
|
temp_res_data = lib0246.RevisionDict()
|
|
for index_type in type_data:
|
|
curr_list = curr_extra["0246.HUB_DATA"][_id[0]]["sole_type"][name_data[index_type]]
|
|
name = curr_list[5] if len(curr_list) == 7 else index_type
|
|
temp_res_data[("data", name)] = res_data[index_type]
|
|
temp_res_data[("type", name)] = curr_list[-1]
|
|
temp_res_data[("kind")] = "highway"
|
|
temp_res_data[("id")] = _id[0]
|
|
res_data[index_temp] = [temp_res_data]
|
|
elif temp_data[index_temp] == "JUNCTION_PIPE":
|
|
temp_res_data = lib0246.RevisionDict()
|
|
for index_type in type_data:
|
|
for value in res_data[index_type]:
|
|
junction_pack_loop(
|
|
temp_res_data,
|
|
curr_extra["0246.HUB_DATA"][_id[0]]["sole_type"][name_data[index_type]][-1],
|
|
value,
|
|
)
|
|
temp_res_data[("kind")] = "junction"
|
|
temp_res_data[("id")] = _id[0]
|
|
res_data[index_temp] = [temp_res_data]
|
|
else:
|
|
raise Exception(f"Invalid pipe type \"{temp_data[index_temp]}\".")
|
|
|
|
if len(res_data) == 0:
|
|
raise Exception("No output.")
|
|
|
|
return {
|
|
"ui": {
|
|
"text": [""]
|
|
},
|
|
"result": [res_data[i] for i in range(len(res_data))]
|
|
}
|
|
|
|
######################################################################################
|
|
|
|
class Cloud:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": lib0246.WildDict(),
|
|
"optional": {
|
|
"cloud": ("CLOUD_DATA", )
|
|
},
|
|
"hidden": {
|
|
"_prompt": "PROMPT",
|
|
"_id": "UNIQUE_ID",
|
|
"_workflow": "EXTRA_PNGINFO"
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = lib0246.ByPassTypeTuple(("CLOUD_PIPE", ))
|
|
RETURN_NAMES = lib0246.ByPassTypeTuple(("_cloud_out", ))
|
|
INPUT_IS_LIST = True
|
|
OUTPUT_IS_LIST = lib0246.TautologyAll
|
|
FUNCTION = "execute"
|
|
CATEGORY = "0246"
|
|
|
|
def execute(self, _id = None, _prompt = None, _workflow = None, **kwargs):
|
|
curr_cloud = copy.deepcopy(kwargs["cloud:cloud"][0])
|
|
return {
|
|
"ui": {
|
|
"text": [""]
|
|
},
|
|
"result": [[*CloudData.full_dict_to_data(_id[0], curr_cloud["inst"], curr_cloud["group"], None, kwargs)]]
|
|
}
|
|
|
|
@classmethod
|
|
def IS_CHANGED(cls, *args, **kwargs):
|
|
return kwargs["cloud:cloud"][0]["track"]
|
|
|
|
######################################################################################
|
|
|
|
class Switch:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": lib0246.WildDict(),
|
|
"hidden": {
|
|
"_id": "UNIQUE_ID",
|
|
"_prompt": "PROMPT",
|
|
"_workflow": "EXTRA_PNGINFO"
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = lib0246.TautologyDictStr()
|
|
INPUT_IS_LIST = True
|
|
OUTPUT_IS_LIST = lib0246.TautologyAll
|
|
FUNCTION = "execute"
|
|
CATEGORY = "0246"
|
|
|
|
SWITCH_TRACK = None
|
|
SWITCH_PROMPT = None
|
|
|
|
def check_lazy_status(self, _id = None, _prompt = None, _workflow = None, **kwargs):
|
|
res = []
|
|
for key in kwargs:
|
|
key_data = key.split(":")
|
|
if key_data[0] == "switch":
|
|
temp_index = kwargs[key][0].split(":")[0]
|
|
if temp_index != "_":
|
|
res.append(kwargs[key][0])
|
|
return res
|
|
|
|
def execute(self, _id = None, _prompt = None, _workflow = None, **kwargs):
|
|
if isinstance(_id, list):
|
|
_id = _id[0]
|
|
if isinstance(_prompt, list):
|
|
_prompt = _prompt[0]
|
|
if isinstance(_workflow, list):
|
|
_workflow = _workflow[0]
|
|
|
|
# [TODO] Remember to recheck "switch:..." (optionally calling recursive_output_delete_if_changed)
|
|
|
|
output = _workflow["workflow"]["nodes"][
|
|
next(i for i, _ in enumerate(_workflow["workflow"]["nodes"]) if _["id"] == int(_id))
|
|
]["outputs"]
|
|
res = []
|
|
for key in kwargs:
|
|
key_data = key.split(":")
|
|
if key_data[0] == "switch":
|
|
temp_index = kwargs[key][0].split(":")[0]
|
|
if temp_index == "_":
|
|
res.append([None])
|
|
|
|
output_node_list = []
|
|
for output_link in output[int(key_data[-1])]["links"]:
|
|
output_node_list.append(next(_ for _ in _workflow["workflow"]["links"] if _[0] == output_link)[3])
|
|
|
|
global PROMPT_IGNORE
|
|
for curr_output in output_node_list:
|
|
PROMPT_IGNORE.update(trace_node(
|
|
_prompt, str(curr_output), _workflow, _input = False,
|
|
_func = lambda id_stk, node_id: \
|
|
trace_node_func(id_stk, node_id) if _prompt[str(node_id)]["class_type"] != "0246.Switch" else None
|
|
))
|
|
continue
|
|
for pin in output:
|
|
if pin["name"].split(":")[-1] == key_data[-1]:
|
|
res.append(kwargs[kwargs[key][0]])
|
|
break
|
|
return res
|
|
|
|
@classmethod
|
|
def IS_CHANGED(cls, _id = None, *args, **kwargs):
|
|
global PROMPT_DATA
|
|
global PROMPT_ID
|
|
if Switch.SWITCH_TRACK is None or Switch.SWITCH_TRACK != PROMPT_ID:
|
|
Switch.SWITCH_TRACK = PROMPT_ID
|
|
Switch.SWITCH_PROMPT = copy.deepcopy(
|
|
PROMPT_DATA.__wrapped__ if isinstance(PROMPT_DATA, lib0246.Wrapper)
|
|
else PROMPT_DATA
|
|
)
|
|
|
|
if hasattr(execution, "recursive_execute"):
|
|
valid_input = set()
|
|
for key in kwargs:
|
|
if key.startswith("switch:"):
|
|
temp_index = kwargs[key][0].split(":")[0]
|
|
if temp_index != "_":
|
|
valid_input.add(temp_index)
|
|
|
|
for key in Switch.SWITCH_PROMPT[_id[0]]["inputs"]:
|
|
curr_index = key.split(":")[0]
|
|
if curr_index.isnumeric():
|
|
if curr_index in valid_input:
|
|
PROMPT_DATA[_id[0]]["inputs"][key] = Switch.SWITCH_PROMPT[_id[0]]["inputs"][key]
|
|
else:
|
|
del PROMPT_DATA[_id[0]]["inputs"][key]
|
|
return " ".join(kwargs.keys())
|
|
|
|
######################################################################################
|
|
|
|
class Meta:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": lib0246.WildDict(),
|
|
"optional": {
|
|
"data": (lib0246.TautologyStr("*"), ),
|
|
},
|
|
"hidden": {
|
|
"_prompt": "PROMPT",
|
|
"_id": "UNIQUE_ID",
|
|
"_workflow": "EXTRA_PNGINFO"
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("INT", "INT", "STRING", "STRING", "STRING", "STRING")
|
|
RETURN_NAMES = ("batch_size", "data_size", "key_list", "type_list", "comfy_type", "py_type")
|
|
INPUT_IS_LIST = True
|
|
OUTPUT_IS_LIST = lib0246.TautologyAll
|
|
FUNCTION = "execute"
|
|
CATEGORY = "0246"
|
|
|
|
def execute(self, _id = None, _prompt = None, _workflow = None, data = None, **kwargs):
|
|
for key in kwargs:
|
|
if key.startswith("data"):
|
|
data = kwargs[key]
|
|
break
|
|
|
|
if isinstance(_id, list):
|
|
_id = _id[0]
|
|
if isinstance(_prompt, list):
|
|
_prompt = _prompt[0]
|
|
if isinstance(_workflow, list):
|
|
_workflow = _workflow[0]
|
|
|
|
data_size = []
|
|
key_list = []
|
|
type_list = []
|
|
input_type = None
|
|
if len(data) > 0:
|
|
match data[0]:
|
|
case lib0246.RevisionDict() if data[0]["kind"] == "highway" or data[0]["kind"] == "junction":
|
|
data_size.append(data[0].path_count(("data", )))
|
|
key_list.extend(map(lambda _: _[1], data[0].path_iter(("data", ))))
|
|
type_list.extend(map(lambda _: data[0][("type", _)], key_list))
|
|
if data[0]["kind"] == "highway":
|
|
for key in key_list:
|
|
data_size.append(len(data[0][("data", key)]) if isinstance(data[0][("data", key)], lib0246.RevisionBatch) else 1)
|
|
else:
|
|
unique_key = []
|
|
for key in key_list:
|
|
if key not in unique_key:
|
|
unique_key.append(key)
|
|
for key in unique_key:
|
|
data_size.append(data[0].path_count(("data", key)))
|
|
case lib0246.RevisionDict() if data[0]["kind"] == "script":
|
|
data_size.append(data[0].path_count(("script", "data")))
|
|
key_list.extend(map(lambda _: _[2], data[0].path_iter(("script", "data"))))
|
|
type_list.extend(map(lambda _: type(data[0][("script", "data", _)]).__name__, key_list))
|
|
case CloudData():
|
|
data_size.append(len(data[0].inst))
|
|
key_list.extend(map(lambda _: _["id"], data[0].inst))
|
|
type_list.extend(map(lambda _: _["kind"], data[0].inst))
|
|
case str():
|
|
char_list = regex.findall(r'\X', data[0])
|
|
data_size.append(len(char_list))
|
|
data_size.append(len(data[0]))
|
|
key_list.extend(char_list) # Grapheme cluster
|
|
key_list.append("") # Sentinel using empty string
|
|
key_list.extend(data[0]) # Code point
|
|
type_list.extend(map(lambda _: unicodedata.category(_), data[0]))
|
|
case int():
|
|
data_size.append(sys.getsizeof(data[0]))
|
|
key_list.extend(bin(data[0])[2:])
|
|
case float():
|
|
data_size.append(sys.getsizeof(data[0]))
|
|
key_list.extend(bin(struct.unpack('Q', struct.pack('d', data[0]))[0])[2:])
|
|
|
|
input_node_link = _workflow["workflow"]["nodes"][
|
|
next(i for i, _ in enumerate(_workflow["workflow"]["nodes"]) if _["id"] == int(_id))
|
|
]["inputs"][0]["link"]
|
|
input_node_link_data = next(_ for _ in _workflow["workflow"]["links"] if _[0] == input_node_link)
|
|
input_type = _workflow["workflow"]["nodes"][
|
|
next(i for i, _ in enumerate(_workflow["workflow"]["nodes"]) if _["id"] == input_node_link_data[1])
|
|
]["outputs"][input_node_link_data[2]]["type"]
|
|
|
|
# [OBSOLETE] The problem with this is this only able to scan outputs, not inputs
|
|
# if input_node["type"] in nodes.NODE_CLASS_MAPPINGS:
|
|
# class_input_type = nodes.NODE_CLASS_MAPPINGS[input_node["type"]].INPUT_TYPES()
|
|
# for key in lib0246.dict_iter(class_input_type):
|
|
# if input_node["outputs"][input_node_link_data[2]]["name"] == key[-1]:
|
|
# type_data = lib0246.dict_get(class_input_type, key)
|
|
# if isinstance(type_data[0], list):
|
|
# data_size.extend(len(type_data[0]))
|
|
# break
|
|
|
|
return (
|
|
[len(data)],
|
|
data_size,
|
|
key_list,
|
|
type_list,
|
|
[input_type],
|
|
[type(data[0]).__name__]
|
|
)
|
|
|
|
######################################################################################
|
|
|
|
class Tag:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"data_in": (lib0246.TautologyStr("*"), ),
|
|
"ops_mode": (["apply", "check", "remove", "clear"], ),
|
|
"neg_mode": ("BOOLEAN", ),
|
|
"tag_mode": (["exact", "regex"], ),
|
|
"tag": ("STRING", {
|
|
"default": "tag",
|
|
"multiline": True
|
|
}),
|
|
},
|
|
"hidden": {
|
|
"_prompt": "PROMPT",
|
|
"_id": "UNIQUE_ID",
|
|
"_workflow": "EXTRA_PNGINFO"
|
|
}
|
|
}
|
|
|
|
INPUT_IS_LIST = True
|
|
OUTPUT_IS_LIST = (True, )
|
|
RETURN_TYPES = (lib0246.TautologyStr("*"), )
|
|
RETURN_NAMES = ("data_out", )
|
|
FUNCTION = "execute"
|
|
CATEGORY = "0246"
|
|
|
|
TAG_DB = {}
|
|
|
|
def execute(
|
|
self, _id = None, _prompt = None, _workflow = None,
|
|
data_in = None, ops_mode = None, neg_mode = None, tag = None, tag_mode = None,
|
|
**kwargs
|
|
):
|
|
if isinstance(_id, list):
|
|
_id = _id[0]
|
|
if isinstance(_prompt, list):
|
|
_prompt = _prompt[0]
|
|
if isinstance(_workflow, list):
|
|
_workflow = _workflow[0]
|
|
|
|
if isinstance(ops_mode, list):
|
|
ops_mode = ops_mode[0]
|
|
if isinstance(neg_mode, list):
|
|
neg_mode = neg_mode[0]
|
|
if isinstance(tag, list):
|
|
tag = tag[0]
|
|
if isinstance(tag_mode, list):
|
|
tag_mode = tag_mode[0]
|
|
|
|
if ops_mode == "apply":
|
|
res = []
|
|
for curr in data_in:
|
|
if isinstance(curr, lib0246.Wrapper):
|
|
curr_attr = getattr(curr, "_0246")
|
|
try:
|
|
curr_attr["id"][curr_attr["tag"].index(tag)] = _id
|
|
except ValueError:
|
|
curr_attr = {
|
|
"tag": curr_attr["tag"] + [tag],
|
|
"id": curr_attr["id"] + [_id]
|
|
}
|
|
res.append(lib0246.Wrapper(curr.__wrapped__, curr_attr))
|
|
else:
|
|
res.append(lib0246.Wrapper(curr, {
|
|
"tag": [tag],
|
|
"id": [_id]
|
|
}))
|
|
return (res, )
|
|
|
|
res = []
|
|
hold = []
|
|
for curr in data_in:
|
|
if isinstance(curr, lib0246.Wrapper):
|
|
curr_attr = getattr(curr, "_0246")
|
|
curr_index = -1
|
|
if tag_mode == "exact":
|
|
try:
|
|
curr_index = curr_attr["tag"].index(tag)
|
|
except ValueError:
|
|
pass
|
|
else:
|
|
for i, curr_tag in enumerate(curr_attr["tag"]):
|
|
if regex.search(tag, curr_tag):
|
|
curr_index = i
|
|
break
|
|
if curr_index > -1: # Intentional double ifs
|
|
if ops_mode == "remove":
|
|
if len(curr_attr["tag"]) - 1 == 0:
|
|
res.append(curr.__wrapped__)
|
|
hold.append(curr)
|
|
continue
|
|
res.append(lib0246.Wrapper(curr.__wrapped__, {
|
|
"tag": curr_attr["tag"][:curr_index] + curr_attr["tag"][curr_index + 1:],
|
|
"id": curr_attr["id"][:curr_index] + curr_attr["id"][curr_index + 1:]
|
|
}))
|
|
continue
|
|
elif ops_mode == "clear":
|
|
res.append(curr.__wrapped__)
|
|
hold.append(curr)
|
|
continue
|
|
res.append(curr)
|
|
|
|
if neg_mode:
|
|
return ([
|
|
_.__wrapped__ if
|
|
ops_mode == "clear" and isinstance(_, lib0246.Wrapper) else
|
|
_ for _ in data_in if _ not in hold and _ not in res
|
|
], )
|
|
return (res, )
|
|
|
|
########################################################################################
|
|
######################################## EXPORT ########################################
|
|
########################################################################################
|
|
|
|
# [TODO] "RandomInt" node can have linger seed if batch len is different
|
|
# [TODO] NestedNodeBuilder prematurely replace prompt (./ComfyUI_NestedNodeBuilder/nodeMenu.js:45)
|
|
# [TODO] Another node pack that are spescialized on optimizing simple-purpose nodes (see work.md)
|
|
# [TODO] Cloud node when connected to Script, the Script node will detect is Cloud is allowed to be
|
|
# converted (by having a cloud object "cloud" exist)
|
|
# [TODO] New cloud object for Cloud node
|
|
# dupe: give data to "dupe" cloud object current position using data from same group
|
|
# flip: enable or disable specific cloud
|
|
# [TODO] CastReroute keep disconnecting when loading workflow
|
|
# [TODO] Hub node cannot be copy-pasted between each tabs
|
|
# [TODO] Beautify input pin name got stuck if connected to CastReroute
|
|
# [TODO] Copy-pasting Cloud with "pin" does not retain position
|
|
# [TODO] Find ways to hide control widgets in Hub
|
|
|
|
NODE_CLASS_MAPPINGS.update({
|
|
"0246.Highway": Highway,
|
|
"0246.HighwayBatch": HighwayBatch,
|
|
"0246.Junction": Junction,
|
|
"0246.JunctionBatch": JunctionBatch,
|
|
"0246.RandomInt": RandomInt,
|
|
"0246.Count": Count,
|
|
"0246.Hold": Hold,
|
|
"0246.Loop": Loop,
|
|
"0246.Beautify": Beautify,
|
|
"0246.Stringify": Stringify,
|
|
"0246.Merge": Merge,
|
|
"0246.BoxRange": BoxRange,
|
|
"0246.ScriptNode": ScriptNode,
|
|
"0246.ScriptRule": ScriptRule,
|
|
"0246.ScriptPile": ScriptPile,
|
|
"0246.Script": Script,
|
|
"0246.Hub": Hub,
|
|
"0246.Cloud": Cloud,
|
|
"0246.Switch": Switch,
|
|
"0246.Meta": Meta,
|
|
"0246.Tag": Tag,
|
|
})
|
|
|
|
NODE_DISPLAY_NAME_MAPPINGS.update({
|
|
"0246.Highway": "Highway",
|
|
"0246.HighwayBatch": "Highway Batch",
|
|
"0246.Junction": "Junction",
|
|
"0246.JunctionBatch": "Junction Batch",
|
|
"0246.RandomInt": "Random Int",
|
|
"0246.Count": "Count",
|
|
"0246.Hold": "Hold",
|
|
"0246.Loop": "Loop",
|
|
"0246.Beautify": "Beautify",
|
|
"0246.Stringify": "Stringify",
|
|
"0246.Merge": "Merge",
|
|
"0246.BoxRange": "Box Range",
|
|
"0246.ScriptNode": "Script Node",
|
|
"0246.ScriptRule": "Script Rule",
|
|
"0246.ScriptPile": "Script Pile",
|
|
"0246.Script": "Script",
|
|
"0246.Hub": "Hub",
|
|
"0246.Cloud": "Cloud",
|
|
"0246.Switch": "Switch",
|
|
"0246.Meta": "Meta",
|
|
"0246.Tag": "Tag",
|
|
})
|
|
|
|
print("\033[95m" + lib0246.HEAD_LOG + "Loaded all nodes and apis." + "\033[0m") |