822 lines
24 KiB
Python
822 lines
24 KiB
Python
# Built-in
|
|
import ast
|
|
sys = __import__("sys")
|
|
import random
|
|
import builtins
|
|
import json
|
|
|
|
# Self Code
|
|
from . import utils as utils0246
|
|
|
|
# 3rd Party
|
|
import aiohttp.web
|
|
|
|
# ComfyUI
|
|
from server import PromptServer
|
|
import execution
|
|
import nodes
|
|
|
|
######################################################################################
|
|
######################################## IMPL ########################################
|
|
######################################################################################
|
|
|
|
def 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 junction_impl(self, _id, _prompt, _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]
|
|
|
|
_type = _prompt[_id]["inputs"]["_type"]
|
|
|
|
if _junc_in is None:
|
|
_junc_in = utils0246.RevisionDict()
|
|
|
|
# _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
|
|
|
|
if _in_mode:
|
|
flat_iter = utils0246.FlatIter(kwargs)
|
|
for param, (key, value) in utils0246.flat_zip(_type["in"], flat_iter):
|
|
pack_loop(_junc_in, param["type"], value)
|
|
else:
|
|
for param, key in zip(_type["in"], list(kwargs)):
|
|
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 = utils0246.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 = {}
|
|
|
|
if _out_mode:
|
|
done_type = {}
|
|
for elem in _type["out"]:
|
|
if elem["full_name"] in utils0246.BLACKLIST:
|
|
continue
|
|
|
|
if elem["type"] in done_type:
|
|
# Rotate the list from [11, 22, 33] to [22, 33, 11]
|
|
res.append(done_type[elem["type"]][1:] + done_type[elem["type"]][:1])
|
|
continue
|
|
|
|
total = _junc_in.path_count(("data", elem["type"]))
|
|
if total == 0:
|
|
raise Exception(f"Type \"{elem['type']}\" of output \"{elem['full_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"[Comfy-0246 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 _type["out"]:
|
|
if elem["full_name"] in utils0246.BLACKLIST:
|
|
continue
|
|
|
|
total = _junc_in.path_count(("data", elem["type"]))
|
|
if total == 0:
|
|
raise Exception(f"Type \"{elem['type']}\" of output \"{elem['full_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
|
|
|
|
return (utils0246.RevisionDict(_junc_in), ) + tuple(res)
|
|
|
|
def gather_impl(_dict_list):
|
|
new_dict = utils0246.RevisionDict()
|
|
|
|
if _dict_list is None:
|
|
return new_dict
|
|
|
|
count_track = {}
|
|
|
|
for _dict in _dict_list:
|
|
for type_name in _dict.path_iter(("type", )):
|
|
if type_name[1] not in count_track:
|
|
count_track[type_name[1]] = 0
|
|
new_dict[("type", type_name[1])] = type_name[1]
|
|
total = _dict.path_count(("data", type_name[1]))
|
|
for i in range(total):
|
|
new_dict[("data", type_name[1], i + count_track[type_name[1]])] = _dict[("data", type_name[1], i)]
|
|
new_dict[("index", type_name[1])] = 0
|
|
count_track[type_name[1]] += total
|
|
|
|
return new_dict
|
|
|
|
########################################################################################
|
|
######################################## HIJACK ########################################
|
|
########################################################################################
|
|
|
|
PROMPT_COUNT = 0
|
|
PROMPT_ID = None
|
|
PROMPT_EXTRA = None
|
|
|
|
def execute_param_handle(*args, **kwargs):
|
|
global PROMPT_ID
|
|
global PROMPT_COUNT
|
|
if PROMPT_ID is None or PROMPT_ID != args[2]:
|
|
PROMPT_COUNT += 1
|
|
PROMPT_ID = args[2]
|
|
PROMPT_EXTRA = args[3]
|
|
return tuple(), {}
|
|
|
|
utils0246.hijack(execution.PromptExecutor, "execute", execute_param_handle)
|
|
|
|
def init_executor_param_handle(*args, **kwargs):
|
|
return tuple(), {}
|
|
|
|
BASE_EXECUTOR = None
|
|
|
|
def init_executor_res_handle(result, *args, **kwargs):
|
|
if not hasattr(args[0], utils0246.WRAP_DATA):
|
|
global BASE_EXECUTOR
|
|
if BASE_EXECUTOR is None:
|
|
BASE_EXECUTOR = args[0]
|
|
return result
|
|
|
|
utils0246.hijack(execution.PromptExecutor, "__init__", init_executor_param_handle, init_executor_res_handle)
|
|
|
|
BASE_TO_EXEC = None
|
|
COND_EXEC = [] # List of functions
|
|
|
|
# Just in case if we needs this ever again
|
|
def execute_node_param_handle(*args, **kwargs):
|
|
global BASE_TO_EXEC
|
|
BASE_TO_EXEC = args[0]
|
|
|
|
track = 0
|
|
while track < len(COND_EXEC):
|
|
if COND_EXEC[track](BASE_TO_EXEC):
|
|
COND_EXEC.pop(track)
|
|
else:
|
|
track += 1
|
|
|
|
return tuple(), {}
|
|
|
|
utils0246.hijack(builtins, "sorted", execute_node_param_handle, None, None, execution)
|
|
|
|
#####################################################################################
|
|
######################################## API ########################################
|
|
#####################################################################################
|
|
|
|
@PromptServer.instance.routes.post('/0246-parse')
|
|
async def parse_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 = utils0246.parse_query(data["input"], utils0246.HIGHWAY_OPS)
|
|
|
|
utils0246.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
|
|
})
|
|
|
|
######################################################################################
|
|
######################################## NODE ########################################
|
|
######################################################################################
|
|
|
|
class Highway:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
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 = utils0246.ByPassTypeTuple(("HIGHWAY_PIPE", ))
|
|
RETURN_NAMES = utils0246.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):
|
|
_type = _prompt[_id]["inputs"]["_type"]
|
|
|
|
if _way_in is None:
|
|
_way_in = utils0246.RevisionDict()
|
|
|
|
# _way_in._id = _id
|
|
# _way_in.purge(_way_in.find(lambda item: item.id == _id))
|
|
|
|
# Time to let the magic play out
|
|
|
|
for param, key in zip(_type["in"], list(kwargs)):
|
|
name = param["name"][1:]
|
|
_way_in[("data", name)] = kwargs[key]
|
|
_way_in[("type", name)] = param["type"]
|
|
|
|
res = []
|
|
|
|
for elem in _type["out"]:
|
|
name = elem["name"][1:]
|
|
|
|
if (
|
|
("data", name) in _way_in and
|
|
elem["type"] == _way_in[("type", name)]
|
|
) or elem["type"] == "*":
|
|
res.append(_way_in[("data", name)])
|
|
else:
|
|
raise Exception(f"Output \"{name}\" is not defined or is not of type \"{elem['type']}\". Expected \"{_way_in[('type', name)]}\".")
|
|
|
|
return (utils0246.RevisionDict(_way_in), ) + tuple(res)
|
|
|
|
@classmethod
|
|
def IS_CHANGED(self, _query, _id = None, _prompt = None, _workflow = None, _way_in = None, *args, **kwargs):
|
|
return utils0246.check_update(_query)
|
|
|
|
######################################################################################
|
|
|
|
class Junction:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
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 = utils0246.ByPassTypeTuple(("JUNCTION_PIPE", ))
|
|
RETURN_NAMES = utils0246.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, _junc_in, _offset, **kwargs)
|
|
|
|
@classmethod
|
|
def IS_CHANGED(self, _offset, _id = None, _prompt = None, _workflow = None, _junc_in = None, *args, **kwargs):
|
|
return utils0246.check_update(_offset)
|
|
|
|
######################################################################################
|
|
|
|
class JunctionBatch:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
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 = utils0246.ByPassTypeTuple(("JUNCTION_PIPE", ))
|
|
RETURN_NAMES = utils0246.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", utils0246.TautologyRest())
|
|
return junction_impl(self, _id, _prompt, gather_impl(_junc_in), _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, gather_impl(_junc_in), _offset, _in_mode = True, _out_mode = False, **kwargs)
|
|
|
|
@classmethod
|
|
def IS_CHANGED(self, _id = None, _prompt = None, _workflow = None, _offset = None, _junc_in = None, _mode = None, *args, **kwargs):
|
|
return utils0246.check_update(_offset)
|
|
|
|
######################################################################################
|
|
|
|
class Count:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {
|
|
"required": {
|
|
"_node": utils0246.ByPassTypeTuple(("*", )),
|
|
"_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")
|
|
FUNCTION = "execute"
|
|
CATEGORY = "0246"
|
|
|
|
def execute(self, _id = None, _node = None, _event = None, **kwargs):
|
|
global PROMPT_COUNT
|
|
if Count.COUNT_ID != PROMPT_COUNT:
|
|
Count.COUNT_DB = {}
|
|
Count.COUNT_ID = PROMPT_COUNT
|
|
if _id not in Count.COUNT_DB:
|
|
Count.COUNT_DB[_id] = 0
|
|
temp = Count.COUNT_DB[_id]
|
|
Count.COUNT_DB[_id] += 1
|
|
|
|
return {
|
|
"ui": {
|
|
"text": (f"Track: {Count.COUNT_ID}, Count: {temp}", )
|
|
},
|
|
"result": (temp, {
|
|
"data": {
|
|
"id": _id,
|
|
},
|
|
"bool": temp >= int(_event) - 1,
|
|
})
|
|
}
|
|
|
|
######################################################################################
|
|
|
|
class Random:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
# Random uint64 int
|
|
return {
|
|
"required": {
|
|
"val": ("STRING", {
|
|
"default": "0",
|
|
"multiline": False
|
|
}),
|
|
"min": ("INT", {
|
|
"default": 0,
|
|
"min": 0,
|
|
# "max": 18446744073709551615
|
|
"max": 9007199254740991
|
|
}),
|
|
"max": ("INT", {
|
|
# "default": 18446744073709551615,
|
|
"default": 9007199254740991,
|
|
"min": 0,
|
|
# "max": 18446744073709551615
|
|
"max": 9007199254740991
|
|
}),
|
|
"batch_size": ("INT", {
|
|
"default": 1,
|
|
"min": 1,
|
|
"max": sys.maxsize
|
|
}),
|
|
},
|
|
"hidden": {
|
|
"_id": "UNIQUE_ID"
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("INT", )
|
|
RETURN_NAMES = ("rand_int", )
|
|
OTUPUT_IS_LIST = (True, )
|
|
FUNCTION = "execute"
|
|
CATEGORY = "0246"
|
|
|
|
RANDOM_DB = {}
|
|
RANDOM_ID = 0
|
|
|
|
def execute(self, _id = None, val = None, min = None, max = None, batch_size = None, **kwargs):
|
|
if Random.RANDOM_ID != PROMPT_COUNT:
|
|
Random.RANDOM_DB = {}
|
|
Random.RANDOM_ID = PROMPT_COUNT
|
|
|
|
if min > max:
|
|
raise Exception("min is greater than max.")
|
|
|
|
if _id not in Random.RANDOM_DB:
|
|
Random.RANDOM_DB[_id] = {}
|
|
|
|
# Initialize previous values and batch size if not present
|
|
if "prev" not in Random.RANDOM_DB[_id]:
|
|
Random.RANDOM_DB[_id]["prev"] = []
|
|
if "prev_batch_size" not in Random.RANDOM_DB[_id]:
|
|
Random.RANDOM_DB[_id]["prev_batch_size"] = None
|
|
|
|
# Check if new random values should be generated
|
|
generate_new = len(Random.RANDOM_DB[_id]["prev"]) == 0 or \
|
|
Random.RANDOM_DB[_id]["prev_batch_size"] != batch_size
|
|
|
|
# If val is greater than -1, return it as a single-element list
|
|
if val != "-1":
|
|
res = val.split(",")
|
|
for i in range(len(res)):
|
|
if i >= len(Random.RANDOM_DB[_id]["prev"]):
|
|
Random.RANDOM_DB[_id]["prev"].append(int(res[i]))
|
|
else:
|
|
Random.RANDOM_DB[_id]["prev"][i] = int(res[i])
|
|
return Random.RANDOM_DB[_id]["prev"]
|
|
|
|
# If val is -1 or we need to generate new random values, generate random numbers
|
|
if val == "-1" or generate_new:
|
|
res = [random.randint(min, max) for _ in range(batch_size)]
|
|
Random.RANDOM_DB[_id]["prev_batch_size"] = batch_size
|
|
|
|
# If val is -2 and we don't need new random values, increment previous values
|
|
if val == "-2" and not generate_new:
|
|
res = [x + 1 for x in Random.RANDOM_DB[_id]["prev"]]
|
|
|
|
# If val is -3 and we don't need new random values, decrement previous values
|
|
if val == "-3" and not generate_new:
|
|
res = [x - 1 for x in Random.RANDOM_DB[_id]["prev"]]
|
|
|
|
Random.RANDOM_DB[_id]["prev"] = res
|
|
|
|
return {
|
|
"ui": {
|
|
"text": (", ".join([str(x) for x in res]), )
|
|
},
|
|
"result": res
|
|
}
|
|
|
|
@classmethod
|
|
def IS_CHANGED(self, val = None, min = None, max = None, batch_size = None, *args, **kwargs):
|
|
return float("NaN")
|
|
|
|
######################################################################################
|
|
|
|
class Hold:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {
|
|
"required": {
|
|
"_data_in": utils0246.ByPassTypeTuple(("*", )),
|
|
},
|
|
"optional": {
|
|
"_hold": ("HOLD_TYPE", )
|
|
},
|
|
"hidden": {
|
|
"_id": "UNIQUE_ID"
|
|
}
|
|
}
|
|
|
|
HOLD_DB = {}
|
|
HOLD_ID = 0
|
|
|
|
RETURN_TYPES = utils0246.ByPassTypeTuple(("*", ))
|
|
RETURN_NAMES = ("_data_out", )
|
|
INPUT_IS_LIST = True
|
|
OUTPUT_IS_LIST = utils0246.TautologyAll()
|
|
FUNCTION = "execute"
|
|
CATEGORY = "0246"
|
|
|
|
def execute(self, _data_in = None, _id = None, _mode = None, _hold = None, **kwargs):
|
|
if _hold is not None and _hold[0]:
|
|
return {
|
|
"ui": {
|
|
"text": (f"Track: {Hold.HOLD_ID}, Length: {len(_data_in)}, Passed", )
|
|
},
|
|
"result": (_data_in, )
|
|
}
|
|
global PROMPT_COUNT
|
|
if Hold.HOLD_ID != PROMPT_COUNT:
|
|
Hold.HOLD_DB = {}
|
|
Hold.HOLD_ID = PROMPT_COUNT
|
|
if _id[0] not in Hold.HOLD_DB:
|
|
Hold.HOLD_DB[_id[0]] = []
|
|
Hold.HOLD_DB[_id[0]].extend(_data_in)
|
|
BASE_EXECUTOR.outputs[_id[0]] = Hold.HOLD_DB[_id[0]]
|
|
return {
|
|
"ui": {
|
|
"text": (f"Id: {Hold.HOLD_ID}, Length: {len(Hold.HOLD_DB[_id[0]])}", )
|
|
},
|
|
"result": (Hold.HOLD_DB[_id[0]], )
|
|
}
|
|
|
|
######################################################################################
|
|
|
|
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
|
|
|
|
def trace_node(_prompt, _id, _workflow):
|
|
id_stk = []
|
|
for key in _prompt[_id]["inputs"]:
|
|
if isinstance(_prompt[_id]["inputs"][key], list) and key != "_event":
|
|
id_stk.append(_prompt[_id]["inputs"][key][0])
|
|
|
|
while len(id_stk) > 0:
|
|
curr_id = id_stk.pop(0)
|
|
if curr_id in BASE_EXECUTOR.outputs:
|
|
del BASE_EXECUTOR.outputs[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:
|
|
id_stk.append(str(linked_node_id))
|
|
|
|
class Loop:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {
|
|
"required": {
|
|
"_event": ("EVENT_TYPE", ),
|
|
"_mode": (["sweep"], ), # Reserved
|
|
"_update": ("STRING", {
|
|
"default": "{'update': ''}",
|
|
"multiline": False
|
|
}),
|
|
},
|
|
"hidden": {
|
|
"_prompt": "PROMPT",
|
|
"_id": "UNIQUE_ID",
|
|
"_workflow": "EXTRA_PNGINFO"
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("HOLD_TYPE", )
|
|
RETURN_NAMES = ("_hold", )
|
|
INPUT_IS_LIST = True
|
|
FUNCTION = "execute"
|
|
CATEGORY = "0246"
|
|
|
|
def execute(self, _id = None, _prompt = None, _workflow = None, _event = None, _mode = None, _update = None, **kwargs):
|
|
global BASE_EXECUTOR
|
|
global PROMPT_ID
|
|
if not _event[0]["bool"]:
|
|
exec = [_id[0]]
|
|
|
|
# [TODO] Less shitty way to remove _event from inputs
|
|
try:
|
|
del BASE_EXECUTOR.outputs[_prompt[0][_id[0]]["inputs"]["_event"][0]]
|
|
except KeyError:
|
|
pass
|
|
|
|
# Not the most efficient. The better way is to find all nodes that are connected to inputs and this loop node
|
|
trace_node(_prompt[0], _id[0], _workflow[0]) # , lambda curr_id: exec.append(curr_id) if curr_id not in exec else None)
|
|
|
|
if _mode[0] == "sweep":
|
|
while len(exec) > 0:
|
|
curr_id = exec.pop(0)
|
|
if BASE_EXECUTOR.outputs.get(curr_id) is not None:
|
|
del BASE_EXECUTOR.outputs[curr_id]
|
|
success, error, ex = execution.recursive_execute(PromptServer.instance, _prompt[0], BASE_EXECUTOR.outputs, curr_id, {"extra_pnginfo": _workflow[0]}, set(), PROMPT_ID, BASE_EXECUTOR.outputs_ui, BASE_EXECUTOR.object_storage)
|
|
if success is not True:
|
|
raise ex
|
|
|
|
return (True, )
|
|
|
|
@classmethod
|
|
def IS_CHANGED(self, _update = None, _event = None, _mode = None, _id = None, _prompt = None, _workflow = None, *args, **kwargs):
|
|
return utils0246.check_update(_update)
|
|
|
|
######################################################################################
|
|
|
|
# [WIP]
|
|
class Mimic:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {
|
|
"required": {
|
|
"_node_class": (list(nodes.NODE_CLASS_MAPPINGS.keys()), ),
|
|
"_mode": (["pluck", "batch"], ),
|
|
"_data": ("STRING", {
|
|
"default": ";",
|
|
"multiline": True
|
|
}),
|
|
},
|
|
# Can accept multiple junction and highway
|
|
# But since text box is basically cannot be resized correctly, we need to allows nested access
|
|
"optional": {
|
|
"_junc_in": ("JUNCTION_PIPE", ),
|
|
"_way_in": ("HIGHWAY_PIPE", ),
|
|
},
|
|
"hidden": {
|
|
"_prompt": "PROMPT",
|
|
"_id": "UNIQUE_ID"
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = utils0246.ByPassTypeTuple(("*", ))
|
|
RETURN_NAMES = utils0246.ByPassTypeTuple(("_pipe_out", )) # Accept _junc_in and _way_in only
|
|
INPUT_IS_LIST = True # Prevent auto loop, we wants to manage it by ourselves
|
|
FUNCTION = "execute"
|
|
CATEGORY = "0246"
|
|
|
|
def execute(self, _id = None, _prompt = None, _data = "", **kwargs):
|
|
class_type = nodes.NODE_CLASS_MAPPINGS[_prompt[_id]["class_type"]]
|
|
|
|
# Check if class_type.INPUT_TYPES have at least one input
|
|
# Also check if have at least one output for RETURN_TYPES
|
|
|
|
class_obj = class_type()
|
|
# Remove class attribute MimicCore.RETURN_NAMES
|
|
return getattr(class_obj, getattr(class_type, "FUNCTION"))(**kwargs)
|
|
|
|
######################################################################################
|
|
|
|
class Beautify:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {
|
|
"required": {
|
|
"data": utils0246.ByPassTypeTuple(("*", )),
|
|
"mode": (["basic", "more", "full", "json"], ),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ()
|
|
INPUT_IS_LIST = True
|
|
OUTPUT_NODE = True
|
|
FUNCTION = "execute"
|
|
CATEGORY = "0246"
|
|
|
|
def execute(self, data = None, mode = None, **kwargs):
|
|
|
|
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."
|
|
|
|
if res_str is None:
|
|
res_str = utils0246.beautify_structure(data, 0, raw_mode)
|
|
|
|
return {
|
|
"ui": {
|
|
"text": (res_str, )
|
|
}
|
|
}
|
|
|
|
########################################################################################
|
|
######################################## EXPORT ########################################
|
|
########################################################################################
|
|
|
|
NODE_CLASS_MAPPINGS = {
|
|
"Highway": Highway,
|
|
"Junction": Junction,
|
|
"JunctionBatch": JunctionBatch,
|
|
# "Mimic": Mimic,
|
|
"Random": Random,
|
|
"Count": Count,
|
|
"Hold": Hold,
|
|
"Loop": Loop,
|
|
"Beautify": Beautify
|
|
}
|
|
|
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
|
"Highway": "Highway",
|
|
"Junction": "Junction",
|
|
"JunctionBatch": "Junction Batch",
|
|
# "Mimic": "Mimic",
|
|
"Random": "Random",
|
|
"Count": "Count",
|
|
"Hold": "Hold",
|
|
"Loop": "Loop",
|
|
"Beautify": "Beautify"
|
|
}
|
|
|
|
print("\033[95m" + utils0246.HEAD_LOG + "Loaded all nodes and apis (/0246-parse)." + "\033[0m") |