From b8d2062e75149d2d003d28cfd1c5d453ee6738a9 Mon Sep 17 00:00:00 2001 From: StableLlama Date: Sat, 17 May 2025 23:30:32 +0200 Subject: [PATCH] Better internal names for BOOLEAN functions (breaking change) Rename CATEGORY "Flow Control" to "flow control" and "data list" to "Data List" and "path" to "Path" Add creation nodes for Data List, LIST and SET Add creation nodes for FLOAT and INT, the later also with different bases Optimize math input widgets to prevent rounding --- src/basic_data_handling/boolean_nodes.py | 24 +- src/basic_data_handling/control_flow_nodes.py | 4 +- src/basic_data_handling/data_list_nodes.py | 224 ++++++++++++++---- src/basic_data_handling/dict_nodes.py | 2 +- src/basic_data_handling/float_nodes.py | 29 ++- src/basic_data_handling/int_nodes.py | 61 ++++- src/basic_data_handling/list_nodes.py | 132 ++++++++++- src/basic_data_handling/math_nodes.py | 127 +++++----- src/basic_data_handling/path_nodes.py | 62 ++--- src/basic_data_handling/set_nodes.py | 137 ++++++++++- web/js/dynamicnode.js | 22 +- 11 files changed, 642 insertions(+), 182 deletions(-) diff --git a/src/basic_data_handling/boolean_nodes.py b/src/basic_data_handling/boolean_nodes.py index 8f925f0..b0685f2 100644 --- a/src/basic_data_handling/boolean_nodes.py +++ b/src/basic_data_handling/boolean_nodes.py @@ -145,19 +145,19 @@ class BooleanXor(ComfyNodeABC): NODE_CLASS_MAPPINGS = { - "Basic data handling: BooleanAnd": BooleanAnd, - "Basic data handling: BooleanNand": BooleanNand, - "Basic data handling: BooleanNor": BooleanNor, - "Basic data handling: BooleanNot": BooleanNot, - "Basic data handling: BooleanOr": BooleanOr, - "Basic data handling: BooleanXor": BooleanXor, + "Basic data handling: Boolean And": BooleanAnd, + "Basic data handling: Boolean Nand": BooleanNand, + "Basic data handling: Boolean Nor": BooleanNor, + "Basic data handling: Boolean Not": BooleanNot, + "Basic data handling: Boolean Or": BooleanOr, + "Basic data handling: Boolean Xor": BooleanXor, } NODE_DISPLAY_NAME_MAPPINGS = { - "Basic data handling: BooleanAnd": "and", - "Basic data handling: BooleanNand": "nand", - "Basic data handling: BooleanNor": "nor", - "Basic data handling: BooleanNot": "not", - "Basic data handling: BooleanOr": "or", - "Basic data handling: BooleanXor": "xor", + "Basic data handling: Boolean And": "and", + "Basic data handling: Boolean Nand": "nand", + "Basic data handling: Boolean Nor": "nor", + "Basic data handling: Boolean Not": "not", + "Basic data handling: Boolean Or": "or", + "Basic data handling: Boolean Xor": "xor", } diff --git a/src/basic_data_handling/control_flow_nodes.py b/src/basic_data_handling/control_flow_nodes.py index 602e0f6..89cbd99 100644 --- a/src/basic_data_handling/control_flow_nodes.py +++ b/src/basic_data_handling/control_flow_nodes.py @@ -22,7 +22,7 @@ class IfElse(ComfyNodeABC): RETURN_TYPES = (IO.ANY,) RETURN_NAMES = ("result",) - CATEGORY = "Basic/Flow Control" + CATEGORY = "Basic/flow control" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "execute" @@ -65,7 +65,7 @@ class SwitchCase(ComfyNodeABC): RETURN_TYPES = (IO.ANY,) RETURN_NAMES = ("result",) - CATEGORY = "Basic/Flow Control" + CATEGORY = "Basic/flow control" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "execute" diff --git a/src/basic_data_handling/data_list_nodes.py b/src/basic_data_handling/data_list_nodes.py index f55c084..346ba61 100644 --- a/src/basic_data_handling/data_list_nodes.py +++ b/src/basic_data_handling/data_list_nodes.py @@ -4,6 +4,141 @@ from comfy.comfy_types.node_typing import IO, ComfyNodeABC INT_MAX = 2**15-1 # the computer can do more but be nice to the eyes + +class DataListCreate(ComfyNodeABC): + """ + Creates a new Data List from items. + + This node creates and returns a Data List. The list of items is dynamically + extended based on the number of inputs provided. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "optional": { + "item_0": (IO.ANY, {"_dynamic": "number"}), + } + } + + RETURN_TYPES = (IO.ANY,) + RETURN_NAMES = ("list",) + CATEGORY = "Basic/Data List" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "create_list" + OUTPUT_IS_LIST = (True,) + + def create_list(self, **kwargs: list[Any]) -> tuple[list]: + return (list(kwargs.values()),) + + +class DataListCreateFromBoolean(ComfyNodeABC): + """ + Creates a new Data List from BOOLEAN items. + + This node creates and returns a Data List. The list of items is dynamically + extended based on the number of inputs provided. + """ + EXPERIMENTAL = True + @classmethod + def INPUT_TYPES(cls): + return { + "optional": { + "item_0": (IO.BOOLEAN, {"_dynamic": "number"}), + } + } + + RETURN_TYPES = (IO.BOOLEAN,) + RETURN_NAMES = ("list",) + CATEGORY = "Basic/Data List" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "create_list" + OUTPUT_IS_LIST = (True,) + + def create_list(self, **kwargs: list[Any]) -> tuple[list]: + return ([bool(value) for value in kwargs.values()],) + + +class DataListCreateFromFloat(ComfyNodeABC): + """ + Creates a new Data List from FLOAT items. + + This node creates and returns a Data List. The list of items is dynamically + extended based on the number of inputs provided. + """ + EXPERIMENTAL = True + @classmethod + def INPUT_TYPES(cls): + return { + "optional": { + "item_0": (IO.FLOAT, {"_dynamic": "number"}), + } + } + + RETURN_TYPES = (IO.FLOAT,) + RETURN_NAMES = ("list",) + CATEGORY = "Basic/Data List" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "create_list" + OUTPUT_IS_LIST = (True,) + + def create_list(self, **kwargs: list[Any]) -> tuple[list]: + return ([float(value) for value in kwargs.values()],) + + +class DataListCreateFromInt(ComfyNodeABC): + """ + Creates a new Data List from INT items. + + This node creates and returns a Data List. The list of items is dynamically + extended based on the number of inputs provided. + """ + EXPERIMENTAL = True + @classmethod + def INPUT_TYPES(cls): + return { + "optional": { + "item_0": (IO.INT, {"_dynamic": "number"}), + } + } + + RETURN_TYPES = (IO.INT,) + RETURN_NAMES = ("list",) + CATEGORY = "Basic/Data List" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "create_list" + OUTPUT_IS_LIST = (True,) + + def create_list(self, **kwargs: list[Any]) -> tuple[list]: + return ([int(value) for value in kwargs.values()],) + + +class DataListCreateFromString(ComfyNodeABC): + """ + Creates a new Data List from STRING items. + + This node creates and returns a Data List. The list of items is dynamically + extended based on the number of inputs provided. + """ + EXPERIMENTAL = True + @classmethod + def INPUT_TYPES(cls): + return { + "optional": { + "item_0": (IO.STRING, {"_dynamic": "number"}), + } + } + + RETURN_TYPES = (IO.STRING,) + RETURN_NAMES = ("list",) + CATEGORY = "Basic/Data List" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "create_list" + OUTPUT_IS_LIST = (True,) + + def create_list(self, **kwargs: list[Any]) -> tuple[list[Any]]: + return ([str(value) for value in kwargs.values()],) + + class DataListAppend(ComfyNodeABC): """ Adds an item to the end of a list. @@ -22,7 +157,7 @@ class DataListAppend(ComfyNodeABC): RETURN_TYPES = (IO.ANY,) RETURN_NAMES = ("list",) - CATEGORY = "Basic/data list" + CATEGORY = "Basic/Data List" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "append" INPUT_IS_LIST = True @@ -54,7 +189,7 @@ class DataListContains(ComfyNodeABC): RETURN_TYPES = (IO.BOOLEAN,) RETURN_NAMES = ("contains",) - CATEGORY = "Basic/data list" + CATEGORY = "Basic/Data List" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "contains" INPUT_IS_LIST = True @@ -84,7 +219,7 @@ class DataListCount(ComfyNodeABC): RETURN_TYPES = (IO.INT,) RETURN_NAMES = ("count",) - CATEGORY = "Basic/data list" + CATEGORY = "Basic/Data List" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "count" INPUT_IS_LIST = True @@ -94,27 +229,6 @@ class DataListCount(ComfyNodeABC): return (kwargs.get('list', []).count(value),) -class DataListCreateEmpty(ComfyNodeABC): - """ - Creates a new empty list. - - This node creates and returns an empty list. - """ - @classmethod - def INPUT_TYPES(cls): - return {"required": {}} - - RETURN_TYPES = (IO.ANY,) - RETURN_NAMES = ("list",) - CATEGORY = "Basic/data list" - DESCRIPTION = cleandoc(__doc__ or "") - FUNCTION = "create_empty" - OUTPUT_IS_LIST = (True,) - - def create_empty(self) -> tuple[list]: - return ([],) - - class DataListExtend(ComfyNodeABC): """ Extends a list by appending elements from another list. @@ -133,7 +247,7 @@ class DataListExtend(ComfyNodeABC): RETURN_TYPES = (IO.ANY,) RETURN_NAMES = ("list",) - CATEGORY = "Basic/data list" + CATEGORY = "Basic/Data List" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "extend" INPUT_IS_LIST = True @@ -145,10 +259,10 @@ class DataListExtend(ComfyNodeABC): class DataListFilter(ComfyNodeABC): """ - Filters a data list using boolean values. + Filters a Data List using boolean values. - This node takes a value data list and a filter data list (containing only boolean values). - It returns a new data list containing only the elements from the value list where the + This node takes a value Data List and a filter Data List (containing only boolean values). + It returns a new Data List containing only the elements from the value list where the corresponding element in the filter list is False. If the lists have different lengths, the last element of the shorter list is repeated @@ -165,7 +279,7 @@ class DataListFilter(ComfyNodeABC): RETURN_TYPES = (IO.ANY,) RETURN_NAMES = ("filtered_list",) - CATEGORY = "Basic/data list" + CATEGORY = "Basic/Data List" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "filter_data" INPUT_IS_LIST = True @@ -200,7 +314,7 @@ class DataListGetItem(ComfyNodeABC): RETURN_TYPES = (IO.ANY,) RETURN_NAMES = ("item",) - CATEGORY = "Basic/data list" + CATEGORY = "Basic/Data List" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "get_item" INPUT_IS_LIST = True @@ -236,7 +350,7 @@ class DataListIndex(ComfyNodeABC): RETURN_TYPES = (IO.INT,) RETURN_NAMES = ("index",) - CATEGORY = "Basic/data list" + CATEGORY = "Basic/Data List" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "list_index" INPUT_IS_LIST = True @@ -274,7 +388,7 @@ class DataListInsert(ComfyNodeABC): RETURN_TYPES = (IO.ANY,) RETURN_NAMES = ("list",) - CATEGORY = "Basic/data list" + CATEGORY = "Basic/Data List" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "insert" INPUT_IS_LIST = True @@ -302,7 +416,7 @@ class DataListLength(ComfyNodeABC): RETURN_TYPES = (IO.INT,) RETURN_NAMES = ("length",) - CATEGORY = "Basic/data list" + CATEGORY = "Basic/Data List" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "length" INPUT_IS_LIST = True @@ -329,7 +443,7 @@ class DataListMax(ComfyNodeABC): RETURN_TYPES = (IO.NUMBER,) RETURN_NAMES = ("max",) - CATEGORY = "Basic/data list" + CATEGORY = "Basic/Data List" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "find_max" INPUT_IS_LIST = True @@ -365,7 +479,7 @@ class DataListMin(ComfyNodeABC): RETURN_TYPES = (IO.NUMBER,) RETURN_NAMES = ("min",) - CATEGORY = "Basic/data list" + CATEGORY = "Basic/Data List" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "find_min" INPUT_IS_LIST = True @@ -405,7 +519,7 @@ class DataListPop(ComfyNodeABC): RETURN_TYPES = (IO.ANY, IO.ANY) RETURN_NAMES = ("list", "item") - CATEGORY = "Basic/data list" + CATEGORY = "Basic/Data List" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "pop" INPUT_IS_LIST = True @@ -439,7 +553,7 @@ class DataListRemove(ComfyNodeABC): RETURN_TYPES = (IO.ANY, IO.BOOLEAN,) RETURN_NAMES = ("list", "success",) - CATEGORY = "Basic/data list" + CATEGORY = "Basic/Data List" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "remove" INPUT_IS_LIST = True @@ -471,7 +585,7 @@ class DataListReverse(ComfyNodeABC): RETURN_TYPES = (IO.ANY,) RETURN_NAMES = ("list",) - CATEGORY = "Basic/data list" + CATEGORY = "Basic/Data List" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "reverse" INPUT_IS_LIST = True @@ -502,7 +616,7 @@ class DataListSetItem(ComfyNodeABC): RETURN_TYPES = (IO.ANY,) RETURN_NAMES = ("list",) - CATEGORY = "Basic/data list" + CATEGORY = "Basic/Data List" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "set_item" INPUT_IS_LIST = True @@ -542,7 +656,7 @@ class DataListSlice(ComfyNodeABC): RETURN_TYPES = (IO.ANY,) RETURN_NAMES = ("list",) - CATEGORY = "Basic/data list" + CATEGORY = "Basic/Data List" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "slice" INPUT_IS_LIST = True @@ -578,7 +692,7 @@ class DataListSort(ComfyNodeABC): RETURN_TYPES = (IO.ANY,) RETURN_NAMES = ("list",) - CATEGORY = "Basic/data list" + CATEGORY = "Basic/Data List" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "sort" INPUT_IS_LIST = True @@ -596,7 +710,7 @@ class DataListZip(ComfyNodeABC): """ Combines multiple lists element-wise. - This node takes multiple data lists as input and returns a new data list + This node takes multiple Data Lists as input and returns a new Data List where each item is a list containing the corresponding elements from the input lists. The length of the output list will be equal to the length of the shortest input list. """ @@ -615,7 +729,7 @@ class DataListZip(ComfyNodeABC): RETURN_TYPES = (IO.ANY,) RETURN_NAMES = ("list",) - CATEGORY = "Basic/data list" + CATEGORY = "Basic/Data List" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "zip_lists" INPUT_IS_LIST = True @@ -637,9 +751,9 @@ class DataListZip(ComfyNodeABC): class DataListToList(ComfyNodeABC): """ - Converts a ComfyUI data list into a LIST object. + Converts a ComfyUI Data List into a LIST object. - This node takes a data list input (which is typically a list of items with the same type) + This node takes a Data List input (which is typically a list of items with the same type) and converts it to a LIST object (a Python list as a single variable). """ @classmethod @@ -651,7 +765,7 @@ class DataListToList(ComfyNodeABC): } RETURN_TYPES = ("LIST",) - CATEGORY = "Basic/data list" + CATEGORY = "Basic/Data List" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "convert" INPUT_IS_LIST = True @@ -662,9 +776,9 @@ class DataListToList(ComfyNodeABC): class DataListToSet(ComfyNodeABC): """ - Converts a ComfyUI data list into a LIST object. + Converts a ComfyUI Data List into a LIST object. - This node takes a data list input (which is typically a list of items with the same type) + This node takes a Data List input (which is typically a list of items with the same type) and converts it to a LIST object (a Python list as a single variable). """ @classmethod @@ -676,7 +790,7 @@ class DataListToSet(ComfyNodeABC): } RETURN_TYPES = ("SET",) - CATEGORY = "Basic/data list" + CATEGORY = "Basic/Data List" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "convert" INPUT_IS_LIST = True @@ -686,10 +800,14 @@ class DataListToSet(ComfyNodeABC): NODE_CLASS_MAPPINGS = { + "Basic data handling: DataListCreate": DataListCreate, + "Basic data handling: DataListCreateFromBoolean": DataListCreateFromBoolean, + "Basic data handling: DataListCreateFromFloat": DataListCreateFromFloat, + "Basic data handling: DataListCreateFromInt": DataListCreateFromInt, + "Basic data handling: DataListCreateFromString": DataListCreateFromString, "Basic data handling: DataListAppend": DataListAppend, "Basic data handling: DataListContains": DataListContains, "Basic data handling: DataListCount": DataListCount, - "Basic data handling: DataListCreateEmpty": DataListCreateEmpty, "Basic data handling: DataListExtend": DataListExtend, "Basic data handling: DataListFilter": DataListFilter, "Basic data handling: DataListGetItem": DataListGetItem, @@ -710,10 +828,14 @@ NODE_CLASS_MAPPINGS = { } NODE_DISPLAY_NAME_MAPPINGS = { + "Basic data handling: DataListCreate": "create Data List", + "Basic data handling: DataListCreateFromBoolean": "create Data List from BOOLEANs", + "Basic data handling: DataListCreateFromFloat": "create Data List from FLOATs", + "Basic data handling: DataListCreateFromInt": "create Data List from INTs", + "Basic data handling: DataListCreateFromString": "create Data List from STRINGs", "Basic data handling: DataListAppend": "append", "Basic data handling: DataListContains": "contains", "Basic data handling: DataListCount": "count", - "Basic data handling: DataListCreateEmpty": "create empty", "Basic data handling: DataListExtend": "extend", "Basic data handling: DataListFilter": "filter", "Basic data handling: DataListGetItem": "get item", diff --git a/src/basic_data_handling/dict_nodes.py b/src/basic_data_handling/dict_nodes.py index f9c22f0..edfa95c 100644 --- a/src/basic_data_handling/dict_nodes.py +++ b/src/basic_data_handling/dict_nodes.py @@ -740,7 +740,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "Basic data handling: DictContainsKey": "contains key", "Basic data handling: DictExcludeKeys": "exclude keys", "Basic data handling: DictFilterByKeys": "filter by keys", - "Basic data handling: DictFromKeys": "fromkeys", + "Basic data handling: DictFromKeys": "from keys", "Basic data handling: DictGet": "get", "Basic data handling: DictGetKeysValues": "get keys values", "Basic data handling: DictGetMultiple": "get multiple", diff --git a/src/basic_data_handling/float_nodes.py b/src/basic_data_handling/float_nodes.py index b075b0c..5492ae3 100644 --- a/src/basic_data_handling/float_nodes.py +++ b/src/basic_data_handling/float_nodes.py @@ -1,6 +1,31 @@ from inspect import cleandoc from comfy.comfy_types.node_typing import IO, ComfyNodeABC + +class FloatCreate(ComfyNodeABC): + """ + Create a FLOAT from a STRING widget. + + The input string must be a valid floating-point number and will be + directly converted to a FLOAT without any further processing. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "value": (IO.ANY, {"default": "0.0", "widgetType": "STRING"}), + } + } + + RETURN_TYPES = (IO.FLOAT,) + CATEGORY = "Basic/FLOAT" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "create" + + def create(self, value: str) -> tuple[float]: + return (float(value),) + + class FloatAdd(ComfyNodeABC): """ Adds two floating-point numbers. @@ -276,6 +301,7 @@ class FloatRound(ComfyNodeABC): NODE_CLASS_MAPPINGS = { + "Basic data handling: FloatCreate": FloatCreate, "Basic data handling: FloatAdd": FloatAdd, "Basic data handling: FloatSubtract": FloatSubtract, "Basic data handling: FloatMultiply": FloatMultiply, @@ -290,11 +316,12 @@ NODE_CLASS_MAPPINGS = { } NODE_DISPLAY_NAME_MAPPINGS = { + "Basic data handling: FloatCreate": "create FLOAT", "Basic data handling: FloatAdd": "add", "Basic data handling: FloatSubtract": "subtract", "Basic data handling: FloatMultiply": "multiply", "Basic data handling: FloatDivide": "divide", - "Basic data handling: FloatDivideSafe": "divide (division by zero safe)", + "Basic data handling: FloatDivideSafe": "divide (zero safe)", "Basic data handling: FloatAsIntegerRatio": "integer ratio", "Basic data handling: FloatFromHex": "from hex", "Basic data handling: FloatHex": "to hex", diff --git a/src/basic_data_handling/int_nodes.py b/src/basic_data_handling/int_nodes.py index d044d7b..9a9c52f 100644 --- a/src/basic_data_handling/int_nodes.py +++ b/src/basic_data_handling/int_nodes.py @@ -2,6 +2,61 @@ from inspect import cleandoc from typing import Literal from comfy.comfy_types.node_typing import IO, ComfyNodeABC + +class IntCreate(ComfyNodeABC): + """ + Create an INT from a STRING widget. + + The input string must be a valid integer number and will be + directly converted to an INT without any further processing. + + Strings starting with "0b" are interpreted as binary numbers, + "0o" as octal numbers, and "0x" as hexadecimal numbers. + + Note: This doesn't handle ones' complement as the data size is unknown. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "value": (IO.ANY, {"default": "0", "widgetType": "STRING"}), + } + } + + RETURN_TYPES = (IO.INT,) + CATEGORY = "Basic/INT" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "create" + + def create(self, value: str) -> tuple[int]: + return (int(value, 0),) # Automatically detects base using prefixes + + +class IntCreateWithBase(ComfyNodeABC): + """ + Create an INT from a STRING with a given base. + + The input string must be a valid integer number and will be + directly converted to an INT without any further processing. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "value": (IO.STRING, {"default": "0"}), + "base": (IO.INT, {"default": "10", "min": 2}), + } + } + + RETURN_TYPES = (IO.INT,) + CATEGORY = "Basic/INT" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "create" + + def create(self, value: str, base: int) -> tuple[int]: + return (int(value, base),) + + class IntAdd(ComfyNodeABC): """ Adds two integers. @@ -286,6 +341,8 @@ class IntToBytes(ComfyNodeABC): NODE_CLASS_MAPPINGS = { + "Basic data handling: IntCreate": IntCreate, + "Basic data handling: IntCreateWithBase": IntCreateWithBase, "Basic data handling: IntAdd": IntAdd, "Basic data handling: IntSubtract": IntSubtract, "Basic data handling: IntMultiply": IntMultiply, @@ -300,11 +357,13 @@ NODE_CLASS_MAPPINGS = { } NODE_DISPLAY_NAME_MAPPINGS = { + "Basic data handling: IntCreate": "create INT", + "Basic data handling: IntCreateWithBase": "create INT with base", "Basic data handling: IntAdd": "add", "Basic data handling: IntSubtract": "subtract", "Basic data handling: IntMultiply": "multiply", "Basic data handling: IntDivide": "divide", - "Basic data handling: IntDivideSafe": "divide (division by zero safe)", + "Basic data handling: IntDivideSafe": "divide (zero safe)", "Basic data handling: IntBitCount": "bit count", "Basic data handling: IntBitLength": "bit length", "Basic data handling: IntFromBytes": "from bytes", diff --git a/src/basic_data_handling/list_nodes.py b/src/basic_data_handling/list_nodes.py index b5d1a7f..c645967 100644 --- a/src/basic_data_handling/list_nodes.py +++ b/src/basic_data_handling/list_nodes.py @@ -4,23 +4,129 @@ from comfy.comfy_types.node_typing import IO, ComfyNodeABC INT_MAX = 2**15-1 # the computer can do more but be nice to the eyes -class ListEmpty(ComfyNodeABC): + +class ListCreate(ComfyNodeABC): """ - Create an empty LIST. + Creates a new LIST from items. + + This node creates and returns a LIST. The list of items is dynamically + extended based on the number of inputs provided. """ @classmethod def INPUT_TYPES(cls): return { - "required": {} + "optional": { + "item_0": (IO.ANY, {"_dynamic": "number"}), + } } RETURN_TYPES = ("LIST",) CATEGORY = "Basic/LIST" DESCRIPTION = cleandoc(__doc__ or "") - FUNCTION = "create" + FUNCTION = "create_list" - def create(self) -> tuple[list[Any]]: - return ([],) + def create_list(self, **kwargs: list[Any]) -> tuple[list[Any]]: + return (list(kwargs.values()),) + + +class ListCreateFromBoolean(ComfyNodeABC): + """ + Creates a new LIST from BOOLEAN items. + + This node creates and returns a LIST. The list of items is dynamically + extended based on the number of inputs provided. + """ + EXPERIMENTAL = True + @classmethod + def INPUT_TYPES(cls): + return { + "optional": { + "item_0": (IO.BOOLEAN, {"_dynamic": "number"}), + } + } + + RETURN_TYPES = ("LIST",) + CATEGORY = "Basic/LIST" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "create_list" + + def create_list(self, **kwargs: list[Any]) -> tuple[list[Any]]: + return ([bool(value) for value in kwargs.values()],) + + +class ListCreateFromFloat(ComfyNodeABC): + """ + Creates a new LIST from FLOAT items. + + This node creates and returns a LIST. The list of items is dynamically + extended based on the number of inputs provided. + """ + EXPERIMENTAL = True + @classmethod + def INPUT_TYPES(cls): + return { + "optional": { + "item_0": (IO.FLOAT, {"_dynamic": "number"}), + } + } + + RETURN_TYPES = ("LIST",) + CATEGORY = "Basic/LIST" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "create_list" + + def create_list(self, **kwargs: list[Any]) -> tuple[list[Any]]: + return ([float(value) for value in kwargs.values()],) + + +class ListCreateFromInt(ComfyNodeABC): + """ + Creates a new LIST from INT items. + + This node creates and returns a LIST. The list of items is dynamically + extended based on the number of inputs provided. + """ + EXPERIMENTAL = True + @classmethod + def INPUT_TYPES(cls): + return { + "optional": { + "item_0": (IO.INT, {"_dynamic": "number"}), + } + } + + RETURN_TYPES = ("LIST",) + CATEGORY = "Basic/LIST" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "create_list" + + def create_list(self, **kwargs: list[Any]) -> tuple[list[Any]]: + return ([int(value) for value in kwargs.values()],) + + +class ListCreateFromString(ComfyNodeABC): + """ + Creates a new LIST from STRING items. + + This node creates and returns a LIST. The list of items is dynamically + extended based on the number of inputs provided. + """ + EXPERIMENTAL = True + @classmethod + def INPUT_TYPES(cls): + return { + "optional": { + "item_0": (IO.STRING, {"_dynamic": "number"}), + } + } + + RETURN_TYPES = ("LIST",) + CATEGORY = "Basic/LIST" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "create_list" + + def create_list(self, **kwargs: list[Any]) -> tuple[list[Any]]: + return ([str(value) for value in kwargs.values()],) class ListAppend(ComfyNodeABC): @@ -552,7 +658,11 @@ class ListToSet(ComfyNodeABC): NODE_CLASS_MAPPINGS = { - "Basic data handling: ListEmpty": ListEmpty, + "Basic data handling: ListCreate": ListCreate, + "Basic data handling: ListCreateFromBoolean": ListCreateFromBoolean, + "Basic data handling: ListCreateFromFloat": ListCreateFromFloat, + "Basic data handling: ListCreateFromInt": ListCreateFromInt, + "Basic data handling: ListCreateFromString": ListCreateFromString, "Basic data handling: ListAppend": ListAppend, "Basic data handling: ListContains": ListContains, "Basic data handling: ListCount": ListCount, @@ -574,7 +684,11 @@ NODE_CLASS_MAPPINGS = { } NODE_DISPLAY_NAME_MAPPINGS = { - "Basic data handling: ListEmpty": "create empty", + "Basic data handling: ListCreate": "create LIST", + "Basic data handling: ListCreateFromBoolean": "create LIST from BOOLEANs", + "Basic data handling: ListCreateFromFloat": "create LIST from FLOATs", + "Basic data handling: ListCreateFromInt": "create LIST from INTs", + "Basic data handling: ListCreateFromString": "create LIST from STRINGs", "Basic data handling: ListAppend": "append", "Basic data handling: ListContains": "contains", "Basic data handling: ListCount": "count", @@ -591,6 +705,6 @@ NODE_DISPLAY_NAME_MAPPINGS = { "Basic data handling: ListSetItem": "set item", "Basic data handling: ListSlice": "slice", "Basic data handling: ListSort": "sort", - "Basic data handling: ListToDataList": "convert to data list", + "Basic data handling: ListToDataList": "convert to Data List", "Basic data handling: ListToSet": "convert to SET", } diff --git a/src/basic_data_handling/math_nodes.py b/src/basic_data_handling/math_nodes.py index ca2b502..5c6a299 100644 --- a/src/basic_data_handling/math_nodes.py +++ b/src/basic_data_handling/math_nodes.py @@ -13,7 +13,7 @@ class MathAbs(ComfyNodeABC): def INPUT_TYPES(cls): return { "required": { - "value": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), + "value": (IO.NUMBER, {"default": 0.0, "widgetType": "STRING"}), } } @@ -22,8 +22,8 @@ class MathAbs(ComfyNodeABC): DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" - def calculate(self, value: Union[float, int]) -> tuple[Union[float, int]]: - return (abs(value),) + def calculate(self, value: Union[float, int, str]) -> tuple[Union[float, int]]: + return (abs(float(value)),) class MathAcos(ComfyNodeABC): @@ -37,7 +37,7 @@ class MathAcos(ComfyNodeABC): def INPUT_TYPES(cls): return { "required": { - "value": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), + "value": (IO.NUMBER, {"default": 0.0, "widgetType": "STRING"}), "unit": (["radians", "degrees"], {"default": "degrees"}), } } @@ -47,8 +47,8 @@ class MathAcos(ComfyNodeABC): DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" - def calculate(self, value: float, unit: Literal["radians", "degrees"]) -> tuple[float]: - result = math.acos(value) + def calculate(self, value: Union[float, int, str], unit: Literal["radians", "degrees"]) -> tuple[float]: + result = math.acos(float(value)) if unit == "degrees": result = math.degrees(result) return (result,) @@ -65,7 +65,7 @@ class MathAsin(ComfyNodeABC): def INPUT_TYPES(cls): return { "required": { - "value": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), + "value": (IO.NUMBER, {"default": 0.0, "widgetType": "STRING"}), "unit": (["radians", "degrees"], {"default": "degrees"}), } } @@ -75,8 +75,8 @@ class MathAsin(ComfyNodeABC): DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" - def calculate(self, value: float, unit: Literal["radians", "degrees"]) -> tuple[float]: - result = math.asin(value) + def calculate(self, value: Union[float, int, str], unit: Literal["radians", "degrees"]) -> tuple[float]: + result = math.asin(float(value)) if unit == "degrees": result = math.degrees(result) return (result,) @@ -93,7 +93,7 @@ class MathAtan(ComfyNodeABC): def INPUT_TYPES(cls): return { "required": { - "value": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), + "value": (IO.NUMBER, {"default": 0.0, "widgetType": "STRING"}), "unit": (["radians", "degrees"], {"default": "degrees"}), } } @@ -103,8 +103,8 @@ class MathAtan(ComfyNodeABC): DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" - def calculate(self, value: float, unit: Literal["radians", "degrees"]) -> tuple[float]: - result = math.atan(value) + def calculate(self, value: Union[float, int, str], unit: Literal["radians", "degrees"]) -> tuple[float]: + result = math.atan(float(value)) if unit == "degrees": result = math.degrees(result) return (result,) @@ -122,8 +122,8 @@ class MathAtan2(ComfyNodeABC): def INPUT_TYPES(cls): return { "required": { - "y": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), - "x": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), + "y": (IO.NUMBER, {"default": 0.0, "widgetType": "STRING"}), + "x": (IO.NUMBER, {"default": 0.0, "widgetType": "STRING"}), "unit": (["radians", "degrees"], {"default": "degrees"}), } } @@ -133,8 +133,8 @@ class MathAtan2(ComfyNodeABC): DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" - def calculate(self, y: float, x: float, unit: Literal["radians", "degrees"]) -> tuple[float]: - result = math.atan2(y, x) + def calculate(self, y: Union[float, int, str], x: Union[float, int, str], unit: Literal["radians", "degrees"]) -> tuple[float]: + result = math.atan2(float(y), float(x)) if unit == "degrees": result = math.degrees(result) return (result,) @@ -150,7 +150,7 @@ class MathCeil(ComfyNodeABC): def INPUT_TYPES(cls): return { "required": { - "value": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), + "value": (IO.NUMBER, {"default": 0.0, "widgetType": "STRING"}), } } @@ -159,8 +159,8 @@ class MathCeil(ComfyNodeABC): DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" - def calculate(self, value: float) -> tuple[int]: - return (math.ceil(value),) + def calculate(self, value: Union[float, int, str]) -> tuple[int]: + return (math.ceil(float(value)),) class MathCos(ComfyNodeABC): @@ -175,7 +175,7 @@ class MathCos(ComfyNodeABC): def INPUT_TYPES(cls): return { "required": { - "angle": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), + "angle": (IO.NUMBER, {"default": 0.0, "widgetType": "STRING"}), "unit": (["radians", "degrees"], {"default": "degrees"}), } } @@ -185,11 +185,11 @@ class MathCos(ComfyNodeABC): DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" - def calculate(self, angle: float, unit: Literal["radians", "degrees"]) -> tuple[float]: + def calculate(self, angle: Union[float, int, str], unit: Literal["radians", "degrees"]) -> tuple[float]: if unit == "degrees": # Convert degrees to radians - angle = math.radians(angle) - return (math.cos(angle),) + angle = math.radians(float(angle)) + return (math.cos(float(angle)),) class MathDegrees(ComfyNodeABC): @@ -202,7 +202,7 @@ class MathDegrees(ComfyNodeABC): def INPUT_TYPES(cls): return { "required": { - "radians": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), + "radians": (IO.NUMBER, {"default": 0.0, "widgetType": "STRING"}), } } @@ -211,8 +211,8 @@ class MathDegrees(ComfyNodeABC): DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" - def calculate(self, radians: float) -> tuple[float]: - return (math.degrees(radians),) + def calculate(self, radians: Union[float, int, str]) -> tuple[float]: + return (math.degrees(float(radians)),) class MathE(ComfyNodeABC): @@ -244,7 +244,7 @@ class MathExp(ComfyNodeABC): def INPUT_TYPES(cls): return { "required": { - "value": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), + "value": (IO.NUMBER, {"default": 0.0, "widgetType": "STRING"}), } } @@ -253,8 +253,8 @@ class MathExp(ComfyNodeABC): DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" - def calculate(self, value: float) -> tuple[float]: - return (math.exp(value),) + def calculate(self, value: Union[float, int, str]) -> tuple[float]: + return (math.exp(float(value)),) class MathFloor(ComfyNodeABC): @@ -267,7 +267,7 @@ class MathFloor(ComfyNodeABC): def INPUT_TYPES(cls): return { "required": { - "value": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), + "value": (IO.NUMBER, {"default": 0.0, "widgetType": "STRING"}), } } @@ -276,8 +276,8 @@ class MathFloor(ComfyNodeABC): DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" - def calculate(self, value: float) -> tuple[int]: - return (math.floor(value),) + def calculate(self, value: Union[float, int, str]) -> tuple[int]: + return (math.floor(float(value)),) class MathLog(ComfyNodeABC): @@ -291,10 +291,10 @@ class MathLog(ComfyNodeABC): def INPUT_TYPES(cls): return { "required": { - "value": (IO.NUMBER, {"default": 1.0, "min": 0.0000001, "widgetType": "FLOAT"}), + "value": (IO.NUMBER, {"default": 1.0, "min": 0.0000001, "widgetType": "STRING"}), }, "optional": { - "base": (IO.NUMBER, {"default": math.e, "min": 0.0000001, "widgetType": "FLOAT"}), + "base": (IO.NUMBER, {"default": math.e, "min": 0.0000001, "widgetType": "STRING"}), } } @@ -303,11 +303,8 @@ class MathLog(ComfyNodeABC): DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" - def calculate(self, value: float, base: float = math.e) -> tuple[float]: - if base == math.e: - return (math.log(value),) - else: - return (math.log(value, base),) + def calculate(self, value: Union[float, int, str], base: Union[float, int, str] = math.e) -> tuple[float]: + return (math.log(float(value), float(base)),) class MathLog10(ComfyNodeABC): @@ -320,7 +317,7 @@ class MathLog10(ComfyNodeABC): def INPUT_TYPES(cls): return { "required": { - "value": (IO.NUMBER, {"default": 1.0, "min": 0.0000001, "widgetType": "FLOAT"}), + "value": (IO.NUMBER, {"default": 1.0, "min": 0.0000001, "widgetType": "STRING"}), } } @@ -329,8 +326,8 @@ class MathLog10(ComfyNodeABC): DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" - def calculate(self, value: float) -> tuple[float]: - return (math.log10(value),) + def calculate(self, value: Union[float, int, str]) -> tuple[float]: + return (math.log10(float(value)),) class MathMax(ComfyNodeABC): @@ -344,8 +341,8 @@ class MathMax(ComfyNodeABC): def INPUT_TYPES(cls): return { "required": { - "value1": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), - "value2": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), + "value1": (IO.NUMBER, {"default": 0.0, "widgetType": "STRING"}), + "value2": (IO.NUMBER, {"default": 0.0, "widgetType": "STRING"}), } } @@ -354,8 +351,8 @@ class MathMax(ComfyNodeABC): DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" - def calculate(self, value1: Union[float, int], value2: Union[float, int]) -> tuple[Union[float, int]]: - return (max(value1, value2),) + def calculate(self, value1: Union[float, int, str], value2: Union[float, int, str]) -> tuple[Union[float, int]]: + return (max(float(value1), float(value2)),) class MathMin(ComfyNodeABC): @@ -369,8 +366,8 @@ class MathMin(ComfyNodeABC): def INPUT_TYPES(cls): return { "required": { - "value1": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), - "value2": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), + "value1": (IO.NUMBER, {"default": 0.0, "widgetType": "STRING"}), + "value2": (IO.NUMBER, {"default": 0.0, "widgetType": "STRING"}), } } @@ -379,8 +376,8 @@ class MathMin(ComfyNodeABC): DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" - def calculate(self, value1: Union[float, int], value2: Union[float, int]) -> tuple[Union[float, int]]: - return (min(value1, value2),) + def calculate(self, value1: Union[float, int, str], value2: Union[float, int, str]) -> tuple[Union[float, int]]: + return (min(float(value1), float(value2)),) class MathPi(ComfyNodeABC): @@ -412,7 +409,7 @@ class MathRadians(ComfyNodeABC): def INPUT_TYPES(cls): return { "required": { - "degrees": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), + "degrees": (IO.NUMBER, {"default": 0.0, "widgetType": "STRING"}), } } @@ -421,8 +418,8 @@ class MathRadians(ComfyNodeABC): DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" - def calculate(self, degrees: float) -> tuple[float]: - return (math.radians(degrees),) + def calculate(self, degrees: Union[float, int, str]) -> tuple[float]: + return (math.radians(float(degrees)),) class MathSin(ComfyNodeABC): @@ -437,7 +434,7 @@ class MathSin(ComfyNodeABC): def INPUT_TYPES(cls): return { "required": { - "angle": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), + "angle": (IO.NUMBER, {"default": 0.0, "widgetType": "STRING"}), "unit": (["radians", "degrees"], {"default": "degrees"}), } } @@ -447,11 +444,11 @@ class MathSin(ComfyNodeABC): DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" - def calculate(self, angle: float, unit: Literal["radians", "degrees"]) -> tuple[float]: + def calculate(self, angle: Union[float, int, str], unit: Literal["radians", "degrees"]) -> tuple[float]: if unit == "degrees": # Convert degrees to radians - angle = math.radians(angle) - return (math.sin(angle),) + angle = math.radians(float(angle)) + return (math.sin(float(angle)),) class MathSqrt(ComfyNodeABC): @@ -464,7 +461,7 @@ class MathSqrt(ComfyNodeABC): def INPUT_TYPES(cls): return { "required": { - "value": (IO.NUMBER, {"default": 0.0, "min": 0.0, "widgetType": "FLOAT"}), + "value": (IO.NUMBER, {"default": 0.0, "min": 0.0, "widgetType": "STRING"}), } } @@ -473,8 +470,8 @@ class MathSqrt(ComfyNodeABC): DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" - def calculate(self, value: float) -> tuple[float]: - return (math.sqrt(value),) + def calculate(self, value: Union[float, int, str]) -> tuple[float]: + return (math.sqrt(float(value)),) class MathTan(ComfyNodeABC): @@ -489,7 +486,7 @@ class MathTan(ComfyNodeABC): def INPUT_TYPES(cls): return { "required": { - "angle": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}), + "angle": (IO.NUMBER, {"default": 0.0, "widgetType": "STRING"}), "unit": (["radians", "degrees"], {"default": "degrees"}), } } @@ -499,16 +496,16 @@ class MathTan(ComfyNodeABC): DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "calculate" - def calculate(self, angle: float, unit: Literal["radians", "degrees"]) -> tuple[float]: + def calculate(self, angle: Union[float, int, str], unit: Literal["radians", "degrees"]) -> tuple[float]: if unit == "degrees": # Convert degrees to radians - angle = math.radians(angle) + angle = math.radians(float(angle)) # Handle specific angles that would result in division by zero - if abs(math.cos(angle)) < 1e-10: + if abs(math.cos(float(angle))) < 1e-10: raise ValueError("Tangent is undefined at this angle (division by zero)") - return (math.tan(angle),) + return (math.tan(float(angle)),) NODE_CLASS_MAPPINGS = { diff --git a/src/basic_data_handling/path_nodes.py b/src/basic_data_handling/path_nodes.py index ae7a2a1..f825683 100644 --- a/src/basic_data_handling/path_nodes.py +++ b/src/basic_data_handling/path_nodes.py @@ -20,8 +20,8 @@ class PathAbspath(ComfyNodeABC): } RETURN_TYPES = (IO.STRING,) - RETURN_NAMES = ("absolute_path",) - CATEGORY = "Basic/path" + RETURN_NAMES = ("absolute path",) + CATEGORY = "Basic/Path" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "get_abspath" @@ -46,7 +46,7 @@ class PathBasename(ComfyNodeABC): RETURN_TYPES = (IO.STRING,) RETURN_NAMES = ("basename",) - CATEGORY = "Basic/path" + CATEGORY = "Basic/Path" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "get_basename" @@ -72,8 +72,8 @@ class PathCommonPrefix(ComfyNodeABC): } RETURN_TYPES = (IO.STRING,) - RETURN_NAMES = ("common_prefix",) - CATEGORY = "Basic/path" + RETURN_NAMES = ("common prefix",) + CATEGORY = "Basic/Path" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "get_common_prefix" @@ -99,7 +99,7 @@ class PathDirname(ComfyNodeABC): RETURN_TYPES = (IO.STRING,) RETURN_NAMES = ("dirname",) - CATEGORY = "Basic/path" + CATEGORY = "Basic/Path" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "get_dirname" @@ -124,7 +124,7 @@ class PathExists(ComfyNodeABC): RETURN_TYPES = (IO.BOOLEAN,) RETURN_NAMES = ("exists",) - CATEGORY = "Basic/path" + CATEGORY = "Basic/Path" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "check_exists" @@ -148,8 +148,8 @@ class PathExpandVars(ComfyNodeABC): } RETURN_TYPES = (IO.STRING,) - RETURN_NAMES = ("expanded_path",) - CATEGORY = "Basic/path" + RETURN_NAMES = ("expanded path",) + CATEGORY = "Basic/Path" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "expand_vars" @@ -168,8 +168,8 @@ class PathGetCwd(ComfyNodeABC): return {"required": {}} RETURN_TYPES = (IO.STRING,) - RETURN_NAMES = ("current_directory",) - CATEGORY = "Basic/path" + RETURN_NAMES = ("current directory",) + CATEGORY = "Basic/Path" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "get_cwd" @@ -194,7 +194,7 @@ class PathGetExtension(ComfyNodeABC): RETURN_TYPES = (IO.STRING,) RETURN_NAMES = ("extension",) - CATEGORY = "Basic/path" + CATEGORY = "Basic/Path" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "get_extension" @@ -218,8 +218,8 @@ class PathGetSize(ComfyNodeABC): } RETURN_TYPES = (IO.INT,) - RETURN_NAMES = ("size_bytes",) - CATEGORY = "Basic/path" + RETURN_NAMES = ("size (bytes)",) + CATEGORY = "Basic/Path" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "get_size" @@ -254,8 +254,8 @@ class PathGlob(ComfyNodeABC): } RETURN_TYPES = (IO.STRING,) - RETURN_NAMES = ("matching_paths",) - CATEGORY = "Basic/path" + RETURN_NAMES = ("matching paths",) + CATEGORY = "Basic/Path" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "glob_paths" OUTPUT_IS_LIST = (True,) @@ -280,8 +280,8 @@ class PathIsAbsolute(ComfyNodeABC): } RETURN_TYPES = (IO.BOOLEAN,) - RETURN_NAMES = ("is_absolute",) - CATEGORY = "Basic/path" + RETURN_NAMES = ("is absolute",) + CATEGORY = "Basic/Path" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "check_is_absolute" @@ -305,8 +305,8 @@ class PathIsDir(ComfyNodeABC): } RETURN_TYPES = (IO.BOOLEAN,) - RETURN_NAMES = ("is_dir",) - CATEGORY = "Basic/path" + RETURN_NAMES = ("is dir",) + CATEGORY = "Basic/Path" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "check_is_dir" @@ -330,8 +330,8 @@ class PathIsFile(ComfyNodeABC): } RETURN_TYPES = (IO.BOOLEAN,) - RETURN_NAMES = ("is_file",) - CATEGORY = "Basic/path" + RETURN_NAMES = ("is file",) + CATEGORY = "Basic/Path" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "check_is_file" @@ -360,7 +360,7 @@ class PathJoin(ComfyNodeABC): RETURN_TYPES = (IO.STRING,) RETURN_NAMES = ("path",) - CATEGORY = "Basic/path" + CATEGORY = "Basic/Path" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "join_paths" @@ -392,7 +392,7 @@ class PathListDir(ComfyNodeABC): RETURN_TYPES = (IO.STRING,) RETURN_NAMES = ("entries",) - CATEGORY = "Basic/path" + CATEGORY = "Basic/Path" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "list_directory" OUTPUT_IS_LIST = (True,) @@ -433,8 +433,8 @@ class PathNormalize(ComfyNodeABC): } RETURN_TYPES = (IO.STRING,) - RETURN_NAMES = ("normalized_path",) - CATEGORY = "Basic/path" + RETURN_NAMES = ("normalized path",) + CATEGORY = "Basic/Path" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "normalize_path" @@ -461,8 +461,8 @@ class PathRelative(ComfyNodeABC): } RETURN_TYPES = (IO.STRING,) - RETURN_NAMES = ("relative_path",) - CATEGORY = "Basic/path" + RETURN_NAMES = ("relative path",) + CATEGORY = "Basic/Path" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "get_relative_path" @@ -489,7 +489,7 @@ class PathSplit(ComfyNodeABC): RETURN_TYPES = (IO.STRING, IO.STRING) RETURN_NAMES = ("directory", "filename") - CATEGORY = "Basic/path" + CATEGORY = "Basic/Path" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "split_path" @@ -513,8 +513,8 @@ class PathSplitExt(ComfyNodeABC): } RETURN_TYPES = (IO.STRING, IO.STRING) - RETURN_NAMES = ("path_without_ext", "extension") - CATEGORY = "Basic/path" + RETURN_NAMES = ("path without ext", "extension") + CATEGORY = "Basic/Path" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "split_ext" diff --git a/src/basic_data_handling/set_nodes.py b/src/basic_data_handling/set_nodes.py index 23f10ac..c8dd905 100644 --- a/src/basic_data_handling/set_nodes.py +++ b/src/basic_data_handling/set_nodes.py @@ -2,6 +2,131 @@ from typing import Any from inspect import cleandoc from comfy.comfy_types.node_typing import IO, ComfyNodeABC + +class SetCreate(ComfyNodeABC): + """ + Creates a new SET from items. + + This node creates and returns a SET. The list of items is dynamically + extended based on the number of inputs provided. + """ + @classmethod + def INPUT_TYPES(cls): + return { + "optional": { + "item_0": (IO.ANY, {"_dynamic": "number"}), + } + } + + RETURN_TYPES = ("SET",) + CATEGORY = "Basic/SET" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "create_set" + + def create_set(self, **kwargs: list[Any]) -> tuple[set[Any]]: + return (set(kwargs.values()),) + + +class SetCreateFromBoolean(ComfyNodeABC): + """ + Creates a new SET from items. + + This node creates and returns a SET. The list of items is dynamically + extended based on the number of inputs provided. + """ + EXPERIMENTAL = True + @classmethod + def INPUT_TYPES(cls): + return { + "optional": { + "item_0": (IO.BOOLEAN, {"_dynamic": "number"}), + } + } + + RETURN_TYPES = ("SET",) + CATEGORY = "Basic/SET" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "create_set" + + def create_set(self, **kwargs: list[Any]) -> tuple[set[Any]]: + return (set([bool(value) for value in kwargs.values()]),) + + +class SetCreateFromFloat(ComfyNodeABC): + """ + Creates a new SET from items. + + This node creates and returns a SET. The list of items is dynamically + extended based on the number of inputs provided. + """ + EXPERIMENTAL = True + @classmethod + def INPUT_TYPES(cls): + return { + "optional": { + "item_0": (IO.FLOAT, {"_dynamic": "number"}), + } + } + + RETURN_TYPES = ("SET",) + CATEGORY = "Basic/SET" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "create_set" + + def create_set(self, **kwargs: list[Any]) -> tuple[set[Any]]: + return (set([float(value) for value in kwargs.values()]),) + + +class SetCreateFromInt(ComfyNodeABC): + """ + Creates a new SET from items. + + This node creates and returns a SET. The list of items is dynamically + extended based on the number of inputs provided. + """ + EXPERIMENTAL = True + @classmethod + def INPUT_TYPES(cls): + return { + "optional": { + "item_0": (IO.INT, {"_dynamic": "number"}), + } + } + + RETURN_TYPES = ("SET",) + CATEGORY = "Basic/SET" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "create_set" + + def create_set(self, **kwargs: list[Any]) -> tuple[set[Any]]: + return (set([int(value) for value in kwargs.values()]),) + + +class SetCreateFromString(ComfyNodeABC): + """ + Creates a new SET from items. + + This node creates and returns a SET. The list of items is dynamically + extended based on the number of inputs provided. + """ + EXPERIMENTAL = True + @classmethod + def INPUT_TYPES(cls): + return { + "optional": { + "item_0": (IO.STRING, {"_dynamic": "number"}), + } + } + + RETURN_TYPES = ("SET",) + CATEGORY = "Basic/SET" + DESCRIPTION = cleandoc(__doc__ or "") + FUNCTION = "create_set" + + def create_set(self, **kwargs: list[Any]) -> tuple[set[Any]]: + return (set([string(value) for value in kwargs.values()]),) + + class SetAdd(ComfyNodeABC): """ Adds an item to a SET. @@ -428,6 +553,11 @@ class SetToList(ComfyNodeABC): NODE_CLASS_MAPPINGS = { + "Basic data handling: SetCreate": SetCreate, + "Basic data handling: SetCreateFromBoolean": SetCreateFromBoolean, + "Basic data handling: SetCreateFromFloat": SetCreateFromFloat, + "Basic data handling: SetCreateFromInt": SetCreateFromInt, + "Basic data handling: SetCreateFromString": SetCreateFromString, "Basic data handling: SetAdd": SetAdd, "Basic data handling: SetContains": SetContains, "Basic data handling: SetDifference": SetDifference, @@ -446,6 +576,11 @@ NODE_CLASS_MAPPINGS = { } NODE_DISPLAY_NAME_MAPPINGS = { + "Basic data handling: SetCreate": "create SET", + "Basic data handling: SetCreateFromBoolean": "create SET from BOOLEANs", + "Basic data handling: SetCreateFromFloat": "create SET from FLOATs", + "Basic data handling: SetCreateFromInt": "create SET from INTs", + "Basic data handling: SetCreateFromString": "create SET from STRINGs", "Basic data handling: SetAdd": "add", "Basic data handling: SetContains": "contains", "Basic data handling: SetDifference": "difference", @@ -459,6 +594,6 @@ NODE_DISPLAY_NAME_MAPPINGS = { "Basic data handling: SetRemove": "remove", "Basic data handling: SetSymmetricDifference": "symmetric difference", "Basic data handling: SetUnion": "union", - "Basic data handling: SetToDataList": "convert to data list", + "Basic data handling: SetToDataList": "convert to Data List", "Basic data handling: SetToList": "convert to LIST", } diff --git a/web/js/dynamicnode.js b/web/js/dynamicnode.js index 5149a20..ce8bef5 100644 --- a/web/js/dynamicnode.js +++ b/web/js/dynamicnode.js @@ -41,9 +41,14 @@ app.registerExtension({ return; } + const combinedInputData = { + ...nodeData?.input?.required ?? {}, + ...nodeData?.input?.optional ?? {} + }; + const dynamicInputs = []; - for (const name in nodeData?.input?.required ?? {}) { - const dynamic = nodeData.input.required[name][1]?._dynamic; + for (const name in combinedInputData) { + const dynamic = combinedInputData[name][1]?._dynamic; if (dynamic) { let matcher; @@ -61,7 +66,8 @@ app.registerExtension({ continue; } const baseName = name.match(matcher)?.[1] ?? name; - dynamicInputs.push({name, baseName, matcher, dynamic}); + const dynamicType = combinedInputData[name][0]; + dynamicInputs.push({name, baseName, matcher, dynamic, dynamicType}); } } if (dynamicInputs.length === 0) { @@ -119,6 +125,7 @@ app.registerExtension({ // Add helper method to insert input at a specific position nodeType.prototype.addInputAtPosition = function (name, type, position) { + console.warn("Adding input at position:", name, type, position); this.addInput(name, type); // Add new input const newInput = this.inputs.pop(); // Fetch the newly added input (last item) this.inputs.splice(position, 0, newInput); // Place it at the desired position @@ -143,7 +150,8 @@ app.registerExtension({ try { const baseName = dynamicInputs[0].baseName; - const dynamicType = nodeData.input.required[dynamicInputs[0].name][0]; + //const dynamicType = nodeData.input.required[dynamicInputs[0].name][0]; + const dynamicType = dynamicInputs[0].dynamicType; // Get dynamic input slots const dynamicSlots = this.inputs @@ -189,7 +197,7 @@ app.registerExtension({ let hasEmptyDynamic = false; for (let idx = 0; idx < this.inputs.length; idx++) { const input = this.inputs[idx]; - const isDynamic = dynamicInputs.some(di => di.matcher.test(input.name)); + const isDynamic = isDynamicInput(input.name); if (hasEmptyDynamic && isDynamic) { if (input.link === null) { // last input is empty and this input is empty @@ -251,7 +259,7 @@ app.registerExtension({ for (let i = 0; i < this.inputs.length; i++) { const input = this.inputs[i]; - const isDynamic = dynamicInputs.some(di => di.matcher.test(input.name)); + const isDynamic = isDynamicInput(input.name); if (isDynamic) { dynamicInputInfo.push({ @@ -270,8 +278,6 @@ app.registerExtension({ return a.index - b.index; // Keep order for same connection status }); - this.properties.dynamicInputs = dynamicInputInfo.length; - // Just rename the inputs in place - don't remove/add to keep connections intact for (let i = 0; i < dynamicInputInfo.length; i++) { const info = dynamicInputInfo[i];