From 8c3cc01a714cee8452713d2fe978281ea05bc3a4 Mon Sep 17 00:00:00 2001 From: exectails Date: Sun, 24 Nov 2024 13:09:29 +0100 Subject: [PATCH] Moved classes around --- __init__.py | 20 +++- node.py | 221 --------------------------------------------- nodes/converter.py | 44 +++++++++ nodes/formatter.py | 105 +++++++++++++++++++++ nodes/scripting.py | 50 ++++++++++ 5 files changed, 217 insertions(+), 223 deletions(-) delete mode 100644 node.py create mode 100644 nodes/converter.py create mode 100644 nodes/formatter.py create mode 100644 nodes/scripting.py diff --git a/__init__.py b/__init__.py index 3d4b3d4..bb2aa7a 100644 --- a/__init__.py +++ b/__init__.py @@ -1,4 +1,20 @@ -from .node import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS +from .nodes.converter import ETATOI, ETITOA +from .nodes.formatter import ETTextFormatter2Node, ETTextFormatter5Node, ETTextFormatter10Node +from .nodes.scripting import ETPythonTextScript3Node -WEB_DIRECTORY = "./js" +et_nodes = { + ("ATOI", ETATOI), + ("ITOA", ETITOA), + + ("Text Formatter (2 Arguments)", ETTextFormatter2Node), + ("Text Formatter (5 Arguments)", ETTextFormatter5Node), + ("Text Formatter (10 Arguments)", ETTextFormatter10Node), + + ("Python Text Script (3 Arguments)", ETPythonTextScript3Node), +} + +NODE_CLASS_MAPPINGS = {cls.__name__: cls for display_name, cls in et_nodes} +NODE_DISPLAY_NAME_MAPPINGS = {cls.__name__: display_name for display_name, cls in et_nodes} + +#WEB_DIRECTORY = "./js" __all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"] diff --git a/node.py b/node.py deleted file mode 100644 index 19981e8..0000000 --- a/node.py +++ /dev/null @@ -1,221 +0,0 @@ -from typing import Any - -CATEGORY_NAME = "exectails" - - -class AnyType(str): - def __ne__(self, __value: object) -> bool: - return False - - -anyType = AnyType("*") - - -class ETTextFormatterNode: - """ - A node that takes a number of text inputs and concatenates them into - a single string. - """ - - INPUT_IS_LIST = True - - RETURN_TYPES = ("STRING",) - RETURN_NAMES = ("text",) - - CATEGORY = CATEGORY_NAME - FUNCTION = "process" - - def process(self, format, arg0="", arg1="", arg2="", arg3="", arg4="", arg5="", arg6="", arg7="", arg8="", arg9="") -> tuple: - # This looks pretty stupid, but it makes sense. I promise. - # Technically, we just want to accept strings that we can - # format, but due to how ComfyUI works, lists of strings - # can be plugged into string inputs as if they were plain - # strings. - # - # What happens then is that the system takes the length of - # the longest list passed in, padding the other ones to the - # same length, and calling the function for each entry. These - # entries then make up the output list. - # - # To avoid this, you can set the input to be a list, process - # will only be called once with all lists, and we can control - # what the output will actually be. The caveat is that we need - # do handle the lists somehow, so we flatten them to one string. - # Easy! - - format = self.flatten(format) - arg0 = self.flatten(arg0) - arg1 = self.flatten(arg1) - arg2 = self.flatten(arg2) - arg3 = self.flatten(arg3) - arg4 = self.flatten(arg4) - arg5 = self.flatten(arg5) - arg6 = self.flatten(arg6) - arg7 = self.flatten(arg7) - arg8 = self.flatten(arg8) - arg9 = self.flatten(arg9) - - result = format.format(arg0=arg0, arg1=arg1, arg2=arg2, arg3=arg3, arg4=arg4, arg5=arg5, arg6=arg6, arg7=arg7, arg8=arg8, arg9=arg9) - - return (result,) - - def flatten(self, value: list) -> str: - return " ".join([str(s) for s in value]) - - -class ETTextFormatter2Node(ETTextFormatterNode): - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "format": ("STRING", {"multiline": True}), - }, - "optional": { - "arg0": ("STRING", {"forceInput": True}), - "arg1": ("STRING", {"forceInput": True}), - } - } - - -class ETTextFormatter5Node(ETTextFormatterNode): - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "format": ("STRING", {"multiline": True}), - }, - "optional": { - "arg0": ("STRING", {"forceInput": True}), - "arg1": ("STRING", {"forceInput": True}), - "arg2": ("STRING", {"forceInput": True}), - "arg3": ("STRING", {"forceInput": True}), - "arg4": ("STRING", {"forceInput": True}), - } - } - - -class ETTextFormatter10Node(ETTextFormatterNode): - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "format": ("STRING", {"multiline": True}), - }, - "optional": { - "arg0": ("STRING", {"forceInput": True}), - "arg1": ("STRING", {"forceInput": True}), - "arg2": ("STRING", {"forceInput": True}), - "arg3": ("STRING", {"forceInput": True}), - "arg4": ("STRING", {"forceInput": True}), - "arg5": ("STRING", {"forceInput": True}), - "arg6": ("STRING", {"forceInput": True}), - "arg7": ("STRING", {"forceInput": True}), - "arg8": ("STRING", {"forceInput": True}), - "arg9": ("STRING", {"forceInput": True}), - } - } - - -class ETATOI: - """ - A node that converts a string to an integer. - """ - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "text": ("STRING", {"default": "", "multiline": True}), - }, - } - - RETURN_TYPES = ("INT",) - RETURN_NAMES = ("integer",) - - CATEGORY = CATEGORY_NAME - FUNCTION = "process" - - def process(self, text: str) -> tuple: - return (int(text),) - - -class ETITOA: - """ - A node that converts an integer to a string. - """ - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "integer": ("INT", {"default": 0}), - }, - } - - RETURN_TYPES = ("STRING",) - RETURN_NAMES = ("text",) - - CATEGORY = CATEGORY_NAME - FUNCTION = "process" - - def process(self, integer: int) -> tuple: - return (str(integer),) - - -class ETPythonTextScript3Node: - """ - A node that takes a number of text inputs and concatenates them into - a single string. - """ - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "script": ("STRING", {"multiline": True, "tooltip": "A python script to execute. Gets access to arg0~2 and may return text via a result variable."}), - }, - "optional": { - "arg0": (anyType, {"forceInput": True, "tooltip": "An argument passed to the script. Value is None if not connected."}), - "arg1": (anyType, {"forceInput": True , "tooltip": "An argument passed to the script. Value is None if not connected."}), - "arg2": (anyType, {"forceInput": True , "tooltip": "An argument passed to the script. Value is None if not connected."}), - }, - } - - RETURN_TYPES = ("STRING",) - RETURN_NAMES = ("text",) - - CATEGORY = CATEGORY_NAME - FUNCTION = "process" - - def process(self, script, arg0=None, arg1=None, arg2=None) -> tuple: - - localVars = {} - result = exec(script, {"arg0": arg0, "arg1": arg1, "arg2": arg2}, localVars) - - result = None - if "result" in localVars: - result = str(localVars["result"]) - - return (result,) - - def flatten(self, value: list) -> str: - return " ".join([str(s) for s in value]) - - -NODE_CLASS_MAPPINGS = { - "ETTextFormatter2Node": ETTextFormatter2Node, - "ETTextFormatter5Node": ETTextFormatter5Node, - "ETTextFormatter10Node": ETTextFormatter10Node, - "ETPythonTextScript3Node": ETPythonTextScript3Node, - "ETATOI": ETATOI, - "ETITOAT": ETITOA, -} - -NODE_DISPLAY_NAME_MAPPINGS = { - "ETTextFormatter2Node": "Text Formatter (2 Arguments)", - "ETTextFormatter5Node": "Text Formatter (5 Arguments)", - "ETTextFormatter10Node": "Text Formatter (10 Arguments)", - "ETPythonTextScript3Node": "Python Text Script", - "ETATOI": "Str2Int", - "ETITOAT": "Int2Str", -} diff --git a/nodes/converter.py b/nodes/converter.py new file mode 100644 index 0000000..c5ec8a8 --- /dev/null +++ b/nodes/converter.py @@ -0,0 +1,44 @@ +class ETATOI: + """ + A node that converts a string to an integer. + """ + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "text": ("STRING", {"default": "", "multiline": True}), + }, + } + + RETURN_TYPES = ("INT",) + RETURN_NAMES = ("integer",) + + CATEGORY = "exectails" + FUNCTION = "process" + + def process(self, text: str) -> tuple: + return (int(text),) + + +class ETITOA: + """ + A node that converts an integer to a string. + """ + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "integer": ("INT", {"default": 0}), + }, + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("text",) + + CATEGORY = "exectails" + FUNCTION = "process" + + def process(self, integer: int) -> tuple: + return (str(integer),) diff --git a/nodes/formatter.py b/nodes/formatter.py new file mode 100644 index 0000000..41553dd --- /dev/null +++ b/nodes/formatter.py @@ -0,0 +1,105 @@ +class ETTextFormatterNode: + """ + Base class for nodes that take a number of inputs and format them + into a single string. + """ + + INPUT_IS_LIST = True + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("text",) + + CATEGORY = "exectails" + FUNCTION = "process" + + def process(self, format, arg0="", arg1="", arg2="", arg3="", arg4="", arg5="", arg6="", arg7="", arg8="", arg9="") -> tuple: + # This looks kinda iffy, but it makes sense. I promise. + # + # Technically, we just want to accept variables that we can + # format and return a single, formatted string. But due to + # how ComfyUI works, lists of strings can be plugged into + # string inputs as if they were plain strings. What happens + # then is that the system takes the length of the longest + # list passed in, padding the other ones to the same length, + # and calling the node's function for each entry. Finally, + # these entries are then again returned as a list with the + # longest length. This is not what we want for a simple + # formatter. + # + # Instead, we set the input to be a list, which makes it so + # process is only called once with all lists, which we can + # handle as we see fit. We can also return whatever we want. + # The caveat is that we need to handle the lists somehow, + # so we flatten them all to one string. Easy! + + format = self.flatten(format) + arg0 = self.flatten(arg0) + arg1 = self.flatten(arg1) + arg2 = self.flatten(arg2) + arg3 = self.flatten(arg3) + arg4 = self.flatten(arg4) + arg5 = self.flatten(arg5) + arg6 = self.flatten(arg6) + arg7 = self.flatten(arg7) + arg8 = self.flatten(arg8) + arg9 = self.flatten(arg9) + + result = format.format(arg0=arg0, arg1=arg1, arg2=arg2, arg3=arg3, arg4=arg4, arg5=arg5, arg6=arg6, arg7=arg7, arg8=arg8, arg9=arg9) + + return (result,) + + def flatten(self, value: list) -> str: + return " ".join([str(s) for s in value]) + + +class ETTextFormatter2Node(ETTextFormatterNode): + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "format": ("STRING", {"multiline": True}), + }, + "optional": { + "arg0": ("STRING", {"forceInput": True}), + "arg1": ("STRING", {"forceInput": True}), + } + } + + +class ETTextFormatter5Node(ETTextFormatterNode): + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "format": ("STRING", {"multiline": True}), + }, + "optional": { + "arg0": ("STRING", {"forceInput": True}), + "arg1": ("STRING", {"forceInput": True}), + "arg2": ("STRING", {"forceInput": True}), + "arg3": ("STRING", {"forceInput": True}), + "arg4": ("STRING", {"forceInput": True}), + } + } + + +class ETTextFormatter10Node(ETTextFormatterNode): + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "format": ("STRING", {"multiline": True}), + }, + "optional": { + "arg0": ("STRING", {"forceInput": True}), + "arg1": ("STRING", {"forceInput": True}), + "arg2": ("STRING", {"forceInput": True}), + "arg3": ("STRING", {"forceInput": True}), + "arg4": ("STRING", {"forceInput": True}), + "arg5": ("STRING", {"forceInput": True}), + "arg6": ("STRING", {"forceInput": True}), + "arg7": ("STRING", {"forceInput": True}), + "arg8": ("STRING", {"forceInput": True}), + "arg9": ("STRING", {"forceInput": True}), + } + } diff --git a/nodes/scripting.py b/nodes/scripting.py new file mode 100644 index 0000000..eb23d3c --- /dev/null +++ b/nodes/scripting.py @@ -0,0 +1,50 @@ +from typing import Any + + +class AnyType(str): + def __ne__(self, __value: object) -> bool: + return False + + +anyType = AnyType("*") + + +class ETPythonTextScript3Node: + """ + A node that takes a number of arguments and a Python script to execute + them with. Intended as a bruteforce method if no general purpose string + utility nodes are available for a certain function. + """ + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "script": ("STRING", {"multiline": True, "tooltip": "A python script to execute. Gets access to arg0~2 and may return text via a result variable."}), + }, + "optional": { + "arg0": (anyType, {"forceInput": True, "tooltip": "An argument passed to the script. Value is None if not connected."}), + "arg1": (anyType, {"forceInput": True, "tooltip": "An argument passed to the script. Value is None if not connected."}), + "arg2": (anyType, {"forceInput": True, "tooltip": "An argument passed to the script. Value is None if not connected."}), + }, + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("text",) + + CATEGORY = "exectails" + FUNCTION = "process" + + def process(self, script, arg0=None, arg1=None, arg2=None) -> tuple: + + localVars = {} + result = exec(script, {"arg0": arg0, "arg1": arg1, "arg2": arg2}, localVars) + + result = None + if "result" in localVars: + result = str(localVars["result"]) + + return (result,) + + def flatten(self, value: list) -> str: + return " ".join([str(s) for s in value])