diff --git a/node_list.json b/node_list.json index cf5c06b..9db4e1a 100644 --- a/node_list.json +++ b/node_list.json @@ -17,5 +17,6 @@ "0246.ScriptRule": "Specify how Script node should be executed.", "0246.ScriptPile": "Generic stacker execution mechanism for Script node.", "0246.Script": "General execution mechanism.", - "0246.Cloud": "Prompt/Data/etc management made \"easy\"." + "0246.Cloud": "Prompt/Data/etc management made \"easy\".", + "0246.Tag": "Data tagging mechanism." } \ No newline at end of file diff --git a/nodes.py b/nodes.py index cc22601..c699095 100644 --- a/nodes.py +++ b/nodes.py @@ -93,7 +93,7 @@ def highway_impl(_prompt, _id, _workflow, _way_in, flag, kwargs): if curr_output.get("links") and 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["type"] == "*" or curr_output["type"] == _way_in[("type", name)]: + if 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)]}\".") @@ -1007,7 +1007,26 @@ def map_node_over_list_param_handle(*args, **kwargs): return map_node_over_list_func_handle, tuple(), {} return None, tuple(), {} -lib0246.hijack(execution, "map_node_over_list", map_node_over_list_param_handle) +def map_node_over_list_res_handle(result, *args, **kwargs): + if hasattr(args[0], "INPUT_IS_LIST") and args[0].INPUT_IS_LIST: + try: + wrap_obj = next(filter(lambda _: isinstance(_, lib0246.Wrapper), result)) + return [*map(lambda _: lib0246.Wrapper(_, wrap_obj._0246), result)] + except StopIteration: + return result + + for (curr, index_info), i in zip(lib0246.dict_slice(args[1]), range(len(result))): + for key in curr: + if isinstance(curr[key], lib0246.Wrapper): + if isinstance(result[i], dict): + 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) CLASS_LIST = None @@ -1447,8 +1466,10 @@ class RandomInt: } @classmethod - def IS_CHANGED(cls, *args, **kwargs): - return float("NaN") + def IS_CHANGED(cls, val = None, *args, **kwargs): + if ("rand", "sub", "add") in map(lambda _: _.strip(), val[0].split(",")): + return float("NaN") + return val[0] ###################################################################################### @@ -2977,6 +2998,118 @@ class Meta: [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" + + 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 ######################################## ######################################################################################## @@ -2986,6 +3119,12 @@ class Meta: # [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 NODE_CLASS_MAPPINGS.update({ "0246.Highway": Highway, @@ -3010,6 +3149,7 @@ NODE_CLASS_MAPPINGS.update({ "0246.Switch": Switch, "0246.Meta": Meta, # "0246.Pick": Pick, + "0246.Tag": Tag, }) NODE_DISPLAY_NAME_MAPPINGS.update({ @@ -3035,6 +3175,7 @@ NODE_DISPLAY_NAME_MAPPINGS.update({ "0246.Switch": "Switch", "0246.Meta": "Meta", # "0246.Pick": "Pick", + "0246.Tag": "Tag", }) print("\033[95m" + lib0246.HEAD_LOG + "Loaded all nodes and apis." + "\033[0m") \ No newline at end of file diff --git a/utils.py b/utils.py index 95fe3ad..dacfa01 100644 --- a/utils.py +++ b/utils.py @@ -384,12 +384,11 @@ def hijack(scope, name, param_func = stub_param_handle, res_func = None, out_sco return res_value if res_func is None else res_func(res_value, *final_args, **kwargs) setattr(scope if out_scope is None else out_scope, name, new_func) -WRAP_DATA = "_self_0246_data_dict" - class Wrapper(wrapt.ObjectProxy): - def __init__(self, wrapped): + _0246 = None + def __init__(self, wrapped, data): super().__init__(wrapped) - setattr(self, WRAP_DATA, {}) + self._0246 = data def check_update(data): if isinstance(data, list): @@ -441,7 +440,7 @@ def beautify_structure(data, indent=0, mode=0, stop=False): for item in data: res_str += beautify_structure(item, indent + 1, mode) else: - res_str += f"{indent_str}Type: {type(data).__name__}\n" + res_str += f"{indent_str}Type: {(type(data.__wrapped__).__name__ + ' (wrap)') if isinstance(data, Wrapper) else type(data).__name__}\n" # Attempt to loop through each attribute match mode: case 1: @@ -457,11 +456,11 @@ def beautify_structure(data, indent=0, mode=0, stop=False): res_str += beautify_structure(item, indent + 1, mode) except TypeError: # If data is not iterable, just print its repr. - res_str += f"{indent_str} Data: {repr(data)}\n" + res_str += f"{indent_str} Data: {repr(data.__wrapped__) if isinstance(data, Wrapper) else repr(data)}\n" case 2: try: if isinstance(data, (int, float, str, bool, type(None))): - res_str += f"{indent_str} Data: {repr(data)}\n" + res_str += f"{indent_str} Data: {repr(data.__wrapped__) if isinstance(data, Wrapper) else repr(data)}\n" else: for attr in dir(data): attr_value = getattr(data, attr) diff --git a/web/js/nodes.js b/web/js/nodes.js index adecef9..9f6865c 100644 --- a/web/js/nodes.js +++ b/web/js/nodes.js @@ -173,6 +173,10 @@ app.registerExtension({ node.color = "#652069"; node.bgcolor = LGraphCanvas.node_colors.green.bgcolor; } break; + case "0246.Tag": { + node.color = "#652069"; + node.bgcolor = LGraphCanvas.node_colors.yellow.bgcolor; + } break; } // use_everywhere.js messing with colors :( node._color = node.color; diff --git a/web/js/widgets.js b/web/js/widgets.js index d87e109..4bfde8c 100644 --- a/web/js/widgets.js +++ b/web/js/widgets.js @@ -4425,7 +4425,7 @@ export function cloud_menu(name, options) { } )).json(); - cloud_widget.value = {}; + cloud_widget.value = {}; // [TODO] There's "pin" cloud, do do not remove those let prev_text_cloud = null, prev_weight_cloud = null; for (let i = 0; i < parse_data.res.length; ++ i) {