+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "basic_data_handling"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
description = """NOTE: Still in development! Expect breaking changes!
|
||||
Basic Python functions for manipulating data that every programmer is used to.
|
||||
Currently supported ComfyUI data types: BOOLEAN, FLOAT, INT, STRING and data lists.
|
||||
|
||||
@@ -25,77 +25,6 @@ class BooleanAnd(ComfyNodeABC):
|
||||
return (input1 and input2,)
|
||||
|
||||
|
||||
class BooleanOr(ComfyNodeABC):
|
||||
"""
|
||||
Returns the logical OR result of two boolean values.
|
||||
|
||||
This node takes two boolean inputs and returns their logical OR result.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"input1": (IO.BOOLEAN, {"default": False, "forceInput": True}),
|
||||
"input2": (IO.BOOLEAN, {"default": False, "forceInput": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.BOOLEAN,)
|
||||
CATEGORY = "Basic/BOOLEAN"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "or_operation"
|
||||
|
||||
def or_operation(self, input1: bool, input2: bool) -> tuple[bool]:
|
||||
return (input1 or input2,)
|
||||
|
||||
|
||||
class BooleanNot(ComfyNodeABC):
|
||||
"""
|
||||
Returns the logical NOT result of a boolean value.
|
||||
|
||||
This node takes one boolean input and returns its logical NOT result.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"input": (IO.BOOLEAN, {"default": False, "forceInput": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.BOOLEAN,)
|
||||
CATEGORY = "Basic/BOOLEAN"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "not_operation"
|
||||
|
||||
def not_operation(self, input: bool) -> tuple[bool]:
|
||||
return (not input,)
|
||||
|
||||
|
||||
class BooleanXor(ComfyNodeABC):
|
||||
"""
|
||||
Returns the logical XOR result of two boolean values.
|
||||
|
||||
This node takes two boolean inputs and returns their logical XOR result.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"input1": (IO.BOOLEAN, {"default": False, "forceInput": True}),
|
||||
"input2": (IO.BOOLEAN, {"default": False, "forceInput": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.BOOLEAN,)
|
||||
CATEGORY = "Basic/BOOLEAN"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "xor_operation"
|
||||
|
||||
def xor_operation(self, input1: bool, input2: bool) -> tuple[bool]:
|
||||
return (input1 != input2,)
|
||||
|
||||
|
||||
class BooleanNand(ComfyNodeABC):
|
||||
"""
|
||||
Returns the logical NAND result of two boolean values.
|
||||
@@ -144,20 +73,91 @@ class BooleanNor(ComfyNodeABC):
|
||||
return (not (input1 or input2),)
|
||||
|
||||
|
||||
class BooleanNot(ComfyNodeABC):
|
||||
"""
|
||||
Returns the logical NOT result of a boolean value.
|
||||
|
||||
This node takes one boolean input and returns its logical NOT result.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"input": (IO.BOOLEAN, {"default": False, "forceInput": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.BOOLEAN,)
|
||||
CATEGORY = "Basic/BOOLEAN"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "not_operation"
|
||||
|
||||
def not_operation(self, input: bool) -> tuple[bool]:
|
||||
return (not input,)
|
||||
|
||||
|
||||
class BooleanOr(ComfyNodeABC):
|
||||
"""
|
||||
Returns the logical OR result of two boolean values.
|
||||
|
||||
This node takes two boolean inputs and returns their logical OR result.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"input1": (IO.BOOLEAN, {"default": False, "forceInput": True}),
|
||||
"input2": (IO.BOOLEAN, {"default": False, "forceInput": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.BOOLEAN,)
|
||||
CATEGORY = "Basic/BOOLEAN"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "or_operation"
|
||||
|
||||
def or_operation(self, input1: bool, input2: bool) -> tuple[bool]:
|
||||
return (input1 or input2,)
|
||||
|
||||
|
||||
class BooleanXor(ComfyNodeABC):
|
||||
"""
|
||||
Returns the logical XOR result of two boolean values.
|
||||
|
||||
This node takes two boolean inputs and returns their logical XOR result.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"input1": (IO.BOOLEAN, {"default": False, "forceInput": True}),
|
||||
"input2": (IO.BOOLEAN, {"default": False, "forceInput": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.BOOLEAN,)
|
||||
CATEGORY = "Basic/BOOLEAN"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "xor_operation"
|
||||
|
||||
def xor_operation(self, input1: bool, input2: bool) -> tuple[bool]:
|
||||
return (input1 != input2,)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"Basic data handling: BooleanAnd": BooleanAnd,
|
||||
"Basic data handling: BooleanOr": BooleanOr,
|
||||
"Basic data handling: BooleanNot": BooleanNot,
|
||||
"Basic data handling: BooleanXor": BooleanXor,
|
||||
"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,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"Basic data handling: BooleanAnd": "and",
|
||||
"Basic data handling: BooleanOr": "or",
|
||||
"Basic data handling: BooleanNot": "not",
|
||||
"Basic data handling: BooleanXor": "xor",
|
||||
"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",
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ from typing import Any
|
||||
from inspect import cleandoc
|
||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
||||
|
||||
class CastToString(ComfyNodeABC):
|
||||
class CastToBoolean(ComfyNodeABC):
|
||||
"""
|
||||
Converts any input to a STRING. Non-string values are converted using str().
|
||||
Converts any input to a BOOLEAN. Follows standard Python truthy/falsy rules.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -14,18 +14,18 @@ class CastToString(ComfyNodeABC):
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
RETURN_TYPES = ("BOOLEAN",)
|
||||
CATEGORY = "Basic/cast"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "convert_to_string"
|
||||
FUNCTION = "convert_to_boolean"
|
||||
|
||||
def convert_to_string(self, input: Any) -> tuple[str]:
|
||||
return (str(input),)
|
||||
def convert_to_boolean(self, input: Any) -> tuple[bool]:
|
||||
return (bool(input),)
|
||||
|
||||
|
||||
class CastToInt(ComfyNodeABC):
|
||||
class CastToDict(ComfyNodeABC):
|
||||
"""
|
||||
Converts any numeric input to an INT. Non-numeric or invalid inputs raise a ValueError.
|
||||
Converts compatible inputs to a DICT. Input must be a mapping or a list of key-value pairs.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -35,16 +35,16 @@ class CastToInt(ComfyNodeABC):
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.INT,)
|
||||
RETURN_TYPES = ("DICT",)
|
||||
CATEGORY = "Basic/cast"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "convert_to_int"
|
||||
FUNCTION = "convert_to_dict"
|
||||
|
||||
def convert_to_int(self, input: Any) -> tuple[int]:
|
||||
def convert_to_dict(self, input: Any) -> tuple[dict]:
|
||||
try:
|
||||
return (int(input),)
|
||||
return (dict(input),)
|
||||
except (ValueError, TypeError):
|
||||
raise ValueError(f"Cannot convert {input} to an INT.")
|
||||
raise ValueError(f"Cannot convert {input} to a DICT. Ensure it is a mapping or list of key-value pairs.")
|
||||
|
||||
|
||||
class CastToFloat(ComfyNodeABC):
|
||||
@@ -71,9 +71,9 @@ class CastToFloat(ComfyNodeABC):
|
||||
raise ValueError(f"Cannot convert {input} to a FLOAT.")
|
||||
|
||||
|
||||
class CastToBoolean(ComfyNodeABC):
|
||||
class CastToInt(ComfyNodeABC):
|
||||
"""
|
||||
Converts any input to a BOOLEAN. Follows standard Python truthy/falsy rules.
|
||||
Converts any numeric input to an INT. Non-numeric or invalid inputs raise a ValueError.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -83,13 +83,16 @@ class CastToBoolean(ComfyNodeABC):
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("BOOLEAN",)
|
||||
RETURN_TYPES = (IO.INT,)
|
||||
CATEGORY = "Basic/cast"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "convert_to_boolean"
|
||||
FUNCTION = "convert_to_int"
|
||||
|
||||
def convert_to_boolean(self, input: Any) -> tuple[bool]:
|
||||
return (bool(input),)
|
||||
def convert_to_int(self, input: Any) -> tuple[int]:
|
||||
try:
|
||||
return (int(input),)
|
||||
except (ValueError, TypeError):
|
||||
raise ValueError(f"Cannot convert {input} to an INT.")
|
||||
|
||||
|
||||
class CastToList(ComfyNodeABC):
|
||||
@@ -140,9 +143,9 @@ class CastToSet(ComfyNodeABC):
|
||||
return ({input,} if not isinstance(input, list) else set(input),)
|
||||
|
||||
|
||||
class CastToDict(ComfyNodeABC):
|
||||
class CastToString(ComfyNodeABC):
|
||||
"""
|
||||
Converts compatible inputs to a DICT. Input must be a mapping or a list of key-value pairs.
|
||||
Converts any input to a STRING. Non-string values are converted using str().
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -152,34 +155,31 @@ class CastToDict(ComfyNodeABC):
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("DICT",)
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
CATEGORY = "Basic/cast"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "convert_to_dict"
|
||||
FUNCTION = "convert_to_string"
|
||||
|
||||
def convert_to_dict(self, input: Any) -> tuple[dict]:
|
||||
try:
|
||||
return (dict(input),)
|
||||
except (ValueError, TypeError):
|
||||
raise ValueError(f"Cannot convert {input} to a DICT. Ensure it is a mapping or list of key-value pairs.")
|
||||
def convert_to_string(self, input: Any) -> tuple[str]:
|
||||
return (str(input),)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"Basic data handling: CastToString": CastToString,
|
||||
"Basic data handling: CastToInt": CastToInt,
|
||||
"Basic data handling: CastToFloat": CastToFloat,
|
||||
"Basic data handling: CastToBoolean": CastToBoolean,
|
||||
"Basic data handling: CastToDict": CastToDict,
|
||||
"Basic data handling: CastToFloat": CastToFloat,
|
||||
"Basic data handling: CastToInt": CastToInt,
|
||||
"Basic data handling: CastToList": CastToList,
|
||||
"Basic data handling: CastToSet": CastToSet,
|
||||
"Basic data handling: CastToDict": CastToDict,
|
||||
"Basic data handling: CastToString": CastToString,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"Basic data handling: CastToString": "to STRING",
|
||||
"Basic data handling: CastToInt": "to INT",
|
||||
"Basic data handling: CastToFloat": "to FLOAT",
|
||||
"Basic data handling: CastToBoolean": "to BOOLEAN",
|
||||
"Basic data handling: CastToDict": "to DICT",
|
||||
"Basic data handling: CastToFloat": "to FLOAT",
|
||||
"Basic data handling: CastToInt": "to INT",
|
||||
"Basic data handling: CastToList": "to LIST",
|
||||
"Basic data handling: CastToSet": "to SET",
|
||||
"Basic data handling: CastToDict": "to DICT",
|
||||
"Basic data handling: CastToString": "to STRING",
|
||||
}
|
||||
|
||||
@@ -58,32 +58,6 @@ class NotEqual(ComfyNodeABC):
|
||||
return (value1 != value2,)
|
||||
|
||||
|
||||
class GreaterThan:
|
||||
"""
|
||||
Checks if the first value is greater than the second.
|
||||
|
||||
This node takes two numerical inputs and returns True if the first value
|
||||
is greater than the second value, and False otherwise.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value1": (IO.NUMBER, {"widgetType": "FLOAT"}),
|
||||
"value2": (IO.NUMBER, {"widgetType": "FLOAT"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.BOOLEAN,)
|
||||
RETURN_NAMES = ("result",)
|
||||
CATEGORY = "Basic/comparison"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "compare"
|
||||
|
||||
def compare(self, value1: float, value2: float) -> tuple[bool]:
|
||||
return (value1 > value2,)
|
||||
|
||||
|
||||
class LessThan:
|
||||
"""
|
||||
Checks if the first value is less than the second.
|
||||
@@ -110,32 +84,6 @@ class LessThan:
|
||||
return (value1 < value2,)
|
||||
|
||||
|
||||
class GreaterThanOrEqual:
|
||||
"""
|
||||
Checks if the first value is greater than or equal to the second.
|
||||
|
||||
This node takes two numerical inputs and returns True if the first value
|
||||
is greater than or equal to the second value, and False otherwise.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value1": (IO.NUMBER, {"widgetType": "FLOAT"}),
|
||||
"value2": (IO.NUMBER, {"widgetType": "FLOAT"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.BOOLEAN,)
|
||||
RETURN_NAMES = ("result",)
|
||||
CATEGORY = "Basic/comparison"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "compare"
|
||||
|
||||
def compare(self, value1: float, value2: float) -> tuple[bool]:
|
||||
return (value1 >= value2,)
|
||||
|
||||
|
||||
class LessThanOrEqual:
|
||||
"""
|
||||
Checks if the first value is less than or equal to the second.
|
||||
@@ -162,21 +110,19 @@ class LessThanOrEqual:
|
||||
return (value1 <= value2,)
|
||||
|
||||
|
||||
class StringComparison:
|
||||
class GreaterThan:
|
||||
"""
|
||||
Compares two strings using a selected comparison operator.
|
||||
Checks if the first value is greater than the second.
|
||||
|
||||
This node takes two string inputs and a comparison operator, and returns
|
||||
a boolean result based on the selected comparison.
|
||||
This node takes two numerical inputs and returns True if the first value
|
||||
is greater than the second value, and False otherwise.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"string1": ("STRING", {"default": ""}),
|
||||
"string2": ("STRING", {"default": ""}),
|
||||
"operator": (["==", "!=", ">", "<", ">=", "<="], {"default": "=="}),
|
||||
"case_sensitive": (IO.BOOLEAN, {"default": True}),
|
||||
"value1": (IO.NUMBER, {"widgetType": "FLOAT"}),
|
||||
"value2": (IO.NUMBER, {"widgetType": "FLOAT"}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,25 +132,59 @@ class StringComparison:
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "compare"
|
||||
|
||||
def compare(self, string1: str, string2: str, operator: str, case_sensitive: str) -> tuple[bool]:
|
||||
if case_sensitive == "False":
|
||||
string1 = string1.lower()
|
||||
string2 = string2.lower()
|
||||
def compare(self, value1: float, value2: float) -> tuple[bool]:
|
||||
return (value1 > value2,)
|
||||
|
||||
if operator == "==":
|
||||
return (string1 == string2,)
|
||||
elif operator == "!=":
|
||||
return (string1 != string2,)
|
||||
elif operator == ">":
|
||||
return (string1 > string2,)
|
||||
elif operator == "<":
|
||||
return (string1 < string2,)
|
||||
elif operator == ">=":
|
||||
return (string1 >= string2,)
|
||||
elif operator == "<=":
|
||||
return (string1 <= string2,)
|
||||
else:
|
||||
raise ValueError(f"Unknown operator: {operator}")
|
||||
|
||||
class GreaterThanOrEqual:
|
||||
"""
|
||||
Checks if the first value is greater than or equal to the second.
|
||||
|
||||
This node takes two numerical inputs and returns True if the first value
|
||||
is greater than or equal to the second value, and False otherwise.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value1": (IO.NUMBER, {"widgetType": "FLOAT"}),
|
||||
"value2": (IO.NUMBER, {"widgetType": "FLOAT"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.BOOLEAN,)
|
||||
RETURN_NAMES = ("result",)
|
||||
CATEGORY = "Basic/comparison"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "compare"
|
||||
|
||||
def compare(self, value1: float, value2: float) -> tuple[bool]:
|
||||
return (value1 >= value2,)
|
||||
|
||||
|
||||
class IsNull:
|
||||
"""
|
||||
Checks if a value is None/null.
|
||||
|
||||
This node takes any input value and returns True if the value is None,
|
||||
and False otherwise.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value": (IO.ANY, {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.BOOLEAN,)
|
||||
RETURN_NAMES = ("is_null",)
|
||||
CATEGORY = "Basic/comparison"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "check_null"
|
||||
|
||||
def check_null(self, value: Any) -> tuple[bool]:
|
||||
return (value is None,)
|
||||
|
||||
|
||||
class NumberInRange:
|
||||
@@ -236,38 +216,13 @@ class NumberInRange:
|
||||
FUNCTION = "check_range"
|
||||
|
||||
def check_range(self, value: float, min_value: float, max_value: float,
|
||||
include_min: str = "True", include_max: str = "True") -> tuple[bool]:
|
||||
include_min: str = "True", include_max: str = "True") -> tuple[bool]:
|
||||
min_check = value >= min_value if include_min == "True" else value > min_value
|
||||
max_check = value <= max_value if include_max == "True" else value < max_value
|
||||
|
||||
return (min_check and max_check,)
|
||||
|
||||
|
||||
class IsNull:
|
||||
"""
|
||||
Checks if a value is None/null.
|
||||
|
||||
This node takes any input value and returns True if the value is None,
|
||||
and False otherwise.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value": (IO.ANY, {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.BOOLEAN,)
|
||||
RETURN_NAMES = ("is_null",)
|
||||
CATEGORY = "Basic/comparison"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "check_null"
|
||||
|
||||
def check_null(self, value: Any) -> tuple[bool]:
|
||||
return (value is None,)
|
||||
|
||||
|
||||
class CompareLength:
|
||||
"""
|
||||
Compares the length of a container (string, list, etc) with a value.
|
||||
@@ -314,28 +269,73 @@ class CompareLength:
|
||||
raise ValueError(f"Unknown operator: {operator}")
|
||||
|
||||
|
||||
class StringComparison:
|
||||
"""
|
||||
Compares two strings using a selected comparison operator.
|
||||
|
||||
This node takes two string inputs and a comparison operator, and returns
|
||||
a boolean result based on the selected comparison.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"string1": ("STRING", {"default": ""}),
|
||||
"string2": ("STRING", {"default": ""}),
|
||||
"operator": (["==", "!=", ">", "<", ">=", "<="], {"default": "=="}),
|
||||
"case_sensitive": (IO.BOOLEAN, {"default": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.BOOLEAN,)
|
||||
RETURN_NAMES = ("result",)
|
||||
CATEGORY = "Basic/comparison"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "compare"
|
||||
|
||||
def compare(self, string1: str, string2: str, operator: str, case_sensitive: str) -> tuple[bool]:
|
||||
if case_sensitive == "False":
|
||||
string1 = string1.lower()
|
||||
string2 = string2.lower()
|
||||
|
||||
if operator == "==":
|
||||
return (string1 == string2,)
|
||||
elif operator == "!=":
|
||||
return (string1 != string2,)
|
||||
elif operator == ">":
|
||||
return (string1 > string2,)
|
||||
elif operator == "<":
|
||||
return (string1 < string2,)
|
||||
elif operator == ">=":
|
||||
return (string1 >= string2,)
|
||||
elif operator == "<=":
|
||||
return (string1 <= string2,)
|
||||
else:
|
||||
raise ValueError(f"Unknown operator: {operator}")
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"Basic data handling: Equal": Equal,
|
||||
"Basic data handling: NotEqual": NotEqual,
|
||||
"Basic data handling: GreaterThan": GreaterThan,
|
||||
"Basic data handling: LessThan": LessThan,
|
||||
"Basic data handling: GreaterThanOrEqual": GreaterThanOrEqual,
|
||||
"Basic data handling: LessThanOrEqual": LessThanOrEqual,
|
||||
"Basic data handling: StringComparison": StringComparison,
|
||||
"Basic data handling: NumberInRange": NumberInRange,
|
||||
"Basic data handling: GreaterThan": GreaterThan,
|
||||
"Basic data handling: GreaterThanOrEqual": GreaterThanOrEqual,
|
||||
"Basic data handling: IsNull": IsNull,
|
||||
"Basic data handling: NumberInRange": NumberInRange,
|
||||
"Basic data handling: CompareLength": CompareLength,
|
||||
"Basic data handling: StringComparison": StringComparison,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"Basic data handling: Equal": "==",
|
||||
"Basic data handling: NotEqual": "!=",
|
||||
"Basic data handling: GreaterThan": ">",
|
||||
"Basic data handling: LessThan": "<",
|
||||
"Basic data handling: GreaterThanOrEqual": ">=",
|
||||
"Basic data handling: LessThanOrEqual": "<=",
|
||||
"Basic data handling: StringComparison": "string compare",
|
||||
"Basic data handling: NumberInRange": "in range",
|
||||
"Basic data handling: GreaterThan": ">",
|
||||
"Basic data handling: GreaterThanOrEqual": ">=",
|
||||
"Basic data handling: IsNull": "is null",
|
||||
"Basic data handling: NumberInRange": "in range",
|
||||
"Basic data handling: CompareLength": "compare length",
|
||||
"Basic data handling: StringComparison": "string compare",
|
||||
}
|
||||
|
||||
@@ -36,6 +36,85 @@ class DataListAppend(ComfyNodeABC):
|
||||
return (result,)
|
||||
|
||||
|
||||
class DataListContains(ComfyNodeABC):
|
||||
"""
|
||||
Checks if a list contains a specified value.
|
||||
|
||||
This node takes a list and a value as inputs, then returns True if the value
|
||||
is present in the list, and False otherwise.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": (IO.ANY,),
|
||||
"value": (IO.ANY,),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.BOOLEAN,)
|
||||
RETURN_NAMES = ("contains",)
|
||||
CATEGORY = "Basic/data list"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "contains"
|
||||
INPUT_IS_LIST = True
|
||||
|
||||
def contains(self, **kwargs: list[Any]) -> tuple[bool]:
|
||||
value = kwargs.get('value', [])
|
||||
if len(value) == 0:
|
||||
return (False,)
|
||||
return (value[0] in kwargs.get('list', []),)
|
||||
|
||||
|
||||
class DataListCount(ComfyNodeABC):
|
||||
"""
|
||||
Counts the number of occurrences of a value in a list.
|
||||
|
||||
This node takes a list and a value as inputs, then returns the number of times
|
||||
the value appears in the list.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": (IO.ANY,),
|
||||
"value": (IO.ANY,),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.INT,)
|
||||
RETURN_NAMES = ("count",)
|
||||
CATEGORY = "Basic/data list"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "count"
|
||||
INPUT_IS_LIST = True
|
||||
|
||||
def count(self, **kwargs: list[Any]) -> tuple[int]:
|
||||
value = kwargs.get('value', [None])[0]
|
||||
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.
|
||||
@@ -64,12 +143,51 @@ class DataListExtend(ComfyNodeABC):
|
||||
return (kwargs.get('list_a', []) + kwargs.get('list_b', []),)
|
||||
|
||||
|
||||
class DataListInsert(ComfyNodeABC):
|
||||
class DataListFilter(ComfyNodeABC):
|
||||
"""
|
||||
Inserts an item at a specified position in a list.
|
||||
Filters a data list using boolean values.
|
||||
|
||||
This node takes a list, an index, and any item as inputs, then returns a new
|
||||
list with the item inserted at the specified index.
|
||||
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
|
||||
till the lengths are matching.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value": (IO.ANY, {}),
|
||||
"filter": (IO.BOOLEAN, {"forceInput": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.ANY,)
|
||||
RETURN_NAMES = ("filtered_list",)
|
||||
CATEGORY = "Basic/data list"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "filter_data"
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
def filter_data(self, **kwargs: list[Any]) -> tuple[list[Any]]:
|
||||
values = kwargs.get('value', [])
|
||||
filters = kwargs.get('filter', [])
|
||||
|
||||
# Create a new list with only items where the filter is False
|
||||
result = [_val for _val, _filter in zip(values, filters) if not _filter]
|
||||
|
||||
return (result,)
|
||||
|
||||
|
||||
class DataListGetItem(ComfyNodeABC):
|
||||
"""
|
||||
Retrieves an item at a specified position in a list.
|
||||
|
||||
This node takes a list and an index as inputs, then returns the item at the specified index.
|
||||
Negative indices count from the end of the list.
|
||||
Out of range indices return None.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -77,94 +195,22 @@ class DataListInsert(ComfyNodeABC):
|
||||
"required": {
|
||||
"list": (IO.ANY,),
|
||||
"index": (IO.INT, {"default": 0}),
|
||||
"item": (IO.ANY,),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.ANY,)
|
||||
RETURN_NAMES = ("list",)
|
||||
RETURN_NAMES = ("item",)
|
||||
CATEGORY = "Basic/data list"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "insert"
|
||||
FUNCTION = "get_item"
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
def insert(self, **kwargs: list[Any]) -> tuple[list[Any]]:
|
||||
result = kwargs.get('list', []).copy()
|
||||
result.insert(kwargs.get('index', [0])[0], kwargs.get('item', [None])[0])
|
||||
return (result,)
|
||||
|
||||
|
||||
class DataListRemove(ComfyNodeABC):
|
||||
"""
|
||||
Removes the first occurrence of a specified value from a list.
|
||||
|
||||
This node takes a list and a value as inputs, then returns a new list with
|
||||
the first occurrence of the value removed. Raises a ValueError if the value is not present.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": (IO.ANY,),
|
||||
"value": (IO.ANY,),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.ANY, IO.BOOLEAN,)
|
||||
RETURN_NAMES = ("list", "success",)
|
||||
CATEGORY = "Basic/data list"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "remove"
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True, False,)
|
||||
|
||||
def remove(self, **kwargs: list[Any]) -> tuple[list[Any], bool]:
|
||||
result = kwargs.get('list', []).copy()
|
||||
value = kwargs.get('value', [])
|
||||
def get_item(self, **kwargs: list[Any]) -> tuple[Any]:
|
||||
index = kwargs.get('index', [0])[0]
|
||||
try:
|
||||
result.remove(value[0])
|
||||
return result, True
|
||||
except ValueError:
|
||||
return result, False
|
||||
|
||||
|
||||
class DataListPop(ComfyNodeABC):
|
||||
"""
|
||||
Removes and returns an item at a specified position in a list.
|
||||
|
||||
This node takes a list and an index as inputs, then returns both the new list
|
||||
with the item removed and the removed item. If no index is specified,
|
||||
removes and returns the last item.
|
||||
When the list is empty, the item is None.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": (IO.ANY,),
|
||||
},
|
||||
"optional": {
|
||||
"index": (IO.INT, {"default": -1}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.ANY, IO.ANY)
|
||||
RETURN_NAMES = ("list", "item")
|
||||
CATEGORY = "Basic/data list"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "pop"
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True, False)
|
||||
|
||||
def pop(self, **kwargs: list[Any]) -> tuple[list[Any], Any]:
|
||||
result = kwargs.get('list', []).copy()
|
||||
index = kwargs.get('index', [-1])[0]
|
||||
try:
|
||||
item = result.pop(index)
|
||||
return result, item
|
||||
return (kwargs.get('list', [])[index],)
|
||||
except IndexError:
|
||||
return result, None
|
||||
return (None,)
|
||||
|
||||
|
||||
class DataListIndex(ComfyNodeABC):
|
||||
@@ -209,12 +255,178 @@ class DataListIndex(ComfyNodeABC):
|
||||
return (-1,)
|
||||
|
||||
|
||||
class DataListCount(ComfyNodeABC):
|
||||
class DataListInsert(ComfyNodeABC):
|
||||
"""
|
||||
Counts the number of occurrences of a value in a list.
|
||||
Inserts an item at a specified position in a list.
|
||||
|
||||
This node takes a list and a value as inputs, then returns the number of times
|
||||
the value appears in the list.
|
||||
This node takes a list, an index, and any item as inputs, then returns a new
|
||||
list with the item inserted at the specified index.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": (IO.ANY,),
|
||||
"index": (IO.INT, {"default": 0}),
|
||||
"item": (IO.ANY,),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.ANY,)
|
||||
RETURN_NAMES = ("list",)
|
||||
CATEGORY = "Basic/data list"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "insert"
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
def insert(self, **kwargs: list[Any]) -> tuple[list[Any]]:
|
||||
result = kwargs.get('list', []).copy()
|
||||
result.insert(kwargs.get('index', [0])[0], kwargs.get('item', [None])[0])
|
||||
return (result,)
|
||||
|
||||
|
||||
class DataListLength(ComfyNodeABC):
|
||||
"""
|
||||
Counts the number of items in a list.
|
||||
|
||||
This node takes a list as input and returns its length as an integer.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": (IO.ANY,),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.INT,)
|
||||
RETURN_NAMES = ("length",)
|
||||
CATEGORY = "Basic/data list"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "length"
|
||||
INPUT_IS_LIST = True
|
||||
|
||||
def length(self, **kwargs: list[Any]) -> tuple[int]:
|
||||
return (len(kwargs.get('list', [])),)
|
||||
|
||||
|
||||
class DataListMax(ComfyNodeABC):
|
||||
"""
|
||||
Finds the maximum value in a list of numbers.
|
||||
|
||||
This node takes a list of numbers (either FLOAT or INT) and returns
|
||||
the maximum value. Returns None if the list is empty or if it contains
|
||||
non-numeric values.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": (IO.NUMBER, {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.NUMBER,)
|
||||
RETURN_NAMES = ("max",)
|
||||
CATEGORY = "Basic/data list"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "find_max"
|
||||
INPUT_IS_LIST = True
|
||||
|
||||
def find_max(self, **kwargs: list[Any]) -> tuple[Any]:
|
||||
values = kwargs.get('list', [])
|
||||
if not values:
|
||||
return (None,)
|
||||
|
||||
try:
|
||||
# Return the same type as found in the maximum value
|
||||
return (max(values),)
|
||||
except (TypeError, ValueError):
|
||||
# Handle case where list contains non-comparable elements
|
||||
return (None,)
|
||||
|
||||
|
||||
class DataListMin(ComfyNodeABC):
|
||||
"""
|
||||
Finds the minimum value in a list of numbers.
|
||||
|
||||
This node takes a list of numbers (either FLOAT or INT) and returns
|
||||
the minimum value. Returns None if the list is empty or if it contains
|
||||
non-numeric values.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": (IO.NUMBER, {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.NUMBER,)
|
||||
RETURN_NAMES = ("min",)
|
||||
CATEGORY = "Basic/data list"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "find_min"
|
||||
INPUT_IS_LIST = True
|
||||
|
||||
def find_min(self, **kwargs: list[Any]) -> tuple[Any]:
|
||||
values = kwargs.get('list', [])
|
||||
if not values:
|
||||
return (None,)
|
||||
|
||||
try:
|
||||
# Return the same type as found in the minimum value
|
||||
return (min(values),)
|
||||
except (TypeError, ValueError):
|
||||
# Handle case where list contains non-comparable elements
|
||||
return (None,)
|
||||
|
||||
|
||||
class DataListPop(ComfyNodeABC):
|
||||
"""
|
||||
Removes and returns an item at a specified position in a list.
|
||||
|
||||
This node takes a list and an index as inputs, then returns both the new list
|
||||
with the item removed and the removed item. If no index is specified,
|
||||
removes and returns the last item.
|
||||
When the list is empty, the item is None.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": (IO.ANY,),
|
||||
},
|
||||
"optional": {
|
||||
"index": (IO.INT, {"default": -1}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.ANY, IO.ANY)
|
||||
RETURN_NAMES = ("list", "item")
|
||||
CATEGORY = "Basic/data list"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "pop"
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True, False)
|
||||
|
||||
def pop(self, **kwargs: list[Any]) -> tuple[list[Any], Any]:
|
||||
result = kwargs.get('list', []).copy()
|
||||
index = kwargs.get('index', [-1])[0]
|
||||
try:
|
||||
item = result.pop(index)
|
||||
return result, item
|
||||
except IndexError:
|
||||
return result, None
|
||||
|
||||
|
||||
class DataListRemove(ComfyNodeABC):
|
||||
"""
|
||||
Removes the first occurrence of a specified value from a list.
|
||||
|
||||
This node takes a list and a value as inputs, then returns a new list with
|
||||
the first occurrence of the value removed. Raises a ValueError if the value is not present.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -225,50 +437,22 @@ class DataListCount(ComfyNodeABC):
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.INT,)
|
||||
RETURN_NAMES = ("count",)
|
||||
RETURN_TYPES = (IO.ANY, IO.BOOLEAN,)
|
||||
RETURN_NAMES = ("list", "success",)
|
||||
CATEGORY = "Basic/data list"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "count"
|
||||
FUNCTION = "remove"
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True, False,)
|
||||
|
||||
def count(self, **kwargs: list[Any]) -> tuple[int]:
|
||||
value = kwargs.get('value', [None])[0]
|
||||
return (kwargs.get('list', []).count(value),)
|
||||
|
||||
|
||||
class DataListSort(ComfyNodeABC):
|
||||
"""
|
||||
Sorts the items in a list.
|
||||
|
||||
This node takes a list as input and returns a new sorted list.
|
||||
Options include sorting in reverse order and using a key function.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": (IO.ANY,),
|
||||
},
|
||||
"optional": {
|
||||
"reverse": (["False", "True"], {"default": "False"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.ANY,)
|
||||
RETURN_NAMES = ("list",)
|
||||
CATEGORY = "Basic/data list"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "sort"
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
def sort(self, **kwargs: list[Any]) -> tuple[list[Any]]:
|
||||
# Convert string to boolean
|
||||
reverse = kwargs.get('reverse', ["False"])[0] == "True"
|
||||
|
||||
result = sorted(kwargs.get('list', []), reverse=reverse)
|
||||
return (result,)
|
||||
def remove(self, **kwargs: list[Any]) -> tuple[list[Any], bool]:
|
||||
result = kwargs.get('list', []).copy()
|
||||
value = kwargs.get('value', [])
|
||||
try:
|
||||
result.remove(value[0])
|
||||
return result, True
|
||||
except ValueError:
|
||||
return result, False
|
||||
|
||||
|
||||
class DataListReverse(ComfyNodeABC):
|
||||
@@ -299,29 +483,41 @@ class DataListReverse(ComfyNodeABC):
|
||||
return (result,)
|
||||
|
||||
|
||||
class DataListLength(ComfyNodeABC):
|
||||
class DataListSetItem(ComfyNodeABC):
|
||||
"""
|
||||
Counts the number of items in a list.
|
||||
Sets an item at a specified position in a list.
|
||||
|
||||
This node takes a list as input and returns its length as an integer.
|
||||
This node takes a list, an index, and a value, then returns a new list with
|
||||
the item at the specified index replaced by the value.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": (IO.ANY,),
|
||||
"index": (IO.INT, {"default": 0}),
|
||||
"value": (IO.ANY,),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.INT,)
|
||||
RETURN_NAMES = ("length",)
|
||||
RETURN_TYPES = (IO.ANY,)
|
||||
RETURN_NAMES = ("list",)
|
||||
CATEGORY = "Basic/data list"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "length"
|
||||
FUNCTION = "set_item"
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
def length(self, **kwargs: list[Any]) -> tuple[int]:
|
||||
return (len(kwargs.get('list', [])),)
|
||||
def set_item(self, **kwargs: list[Any]) -> tuple[Any]:
|
||||
input_list = kwargs.get('list', [])
|
||||
index = kwargs.get('index', [0])[0]
|
||||
value = kwargs.get('value', [None])[0]
|
||||
try:
|
||||
result = input_list.copy()
|
||||
result[index] = value
|
||||
return (result,)
|
||||
except IndexError:
|
||||
raise IndexError(f"Index {index} out of range for list of length {len(input_list)}")
|
||||
|
||||
|
||||
class DataListSlice(ComfyNodeABC):
|
||||
@@ -362,52 +558,21 @@ class DataListSlice(ComfyNodeABC):
|
||||
return (input_list[start:stop:step],)
|
||||
|
||||
|
||||
class DataListGetItem(ComfyNodeABC):
|
||||
class DataListSort(ComfyNodeABC):
|
||||
"""
|
||||
Retrieves an item at a specified position in a list.
|
||||
Sorts the items in a list.
|
||||
|
||||
This node takes a list and an index as inputs, then returns the item at the specified index.
|
||||
Negative indices count from the end of the list.
|
||||
Out of range indices return None.
|
||||
This node takes a list as input and returns a new sorted list.
|
||||
Options include sorting in reverse order and using a key function.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": (IO.ANY,),
|
||||
"index": (IO.INT, {"default": 0}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.ANY,)
|
||||
RETURN_NAMES = ("item",)
|
||||
CATEGORY = "Basic/data list"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "get_item"
|
||||
INPUT_IS_LIST = True
|
||||
|
||||
def get_item(self, **kwargs: list[Any]) -> tuple[Any]:
|
||||
index = kwargs.get('index', [0])[0]
|
||||
try:
|
||||
return (kwargs.get('list', [])[index],)
|
||||
except IndexError:
|
||||
return (None,)
|
||||
|
||||
|
||||
class DataListSetItem(ComfyNodeABC):
|
||||
"""
|
||||
Sets an item at a specified position in a list.
|
||||
|
||||
This node takes a list, an index, and a value, then returns a new list with
|
||||
the item at the specified index replaced by the value.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": (IO.ANY,),
|
||||
"index": (IO.INT, {"default": 0}),
|
||||
"value": (IO.ANY,),
|
||||
},
|
||||
"optional": {
|
||||
"reverse": (["False", "True"], {"default": "False"}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -415,71 +580,16 @@ class DataListSetItem(ComfyNodeABC):
|
||||
RETURN_NAMES = ("list",)
|
||||
CATEGORY = "Basic/data list"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "set_item"
|
||||
FUNCTION = "sort"
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
def set_item(self, **kwargs: list[Any]) -> tuple[Any]:
|
||||
input_list = kwargs.get('list', [])
|
||||
index = kwargs.get('index', [0])[0]
|
||||
value = kwargs.get('value', [None])[0]
|
||||
try:
|
||||
result = input_list.copy()
|
||||
result[index] = value
|
||||
return (result,)
|
||||
except IndexError:
|
||||
raise IndexError(f"Index {index} out of range for list of length {len(input_list)}")
|
||||
def sort(self, **kwargs: list[Any]) -> tuple[list[Any]]:
|
||||
# Convert string to boolean
|
||||
reverse = kwargs.get('reverse', ["False"])[0] == "True"
|
||||
|
||||
|
||||
class DataListContains(ComfyNodeABC):
|
||||
"""
|
||||
Checks if a list contains a specified value.
|
||||
|
||||
This node takes a list and a value as inputs, then returns True if the value
|
||||
is present in the list, and False otherwise.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": (IO.ANY,),
|
||||
"value": (IO.ANY,),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.BOOLEAN,)
|
||||
RETURN_NAMES = ("contains",)
|
||||
CATEGORY = "Basic/data list"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "contains"
|
||||
INPUT_IS_LIST = True
|
||||
|
||||
def contains(self, **kwargs: list[Any]) -> tuple[bool]:
|
||||
value = kwargs.get('value', [])
|
||||
if len(value) == 0:
|
||||
return (False,)
|
||||
return (value[0] in kwargs.get('list', []),)
|
||||
|
||||
|
||||
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 ([],)
|
||||
result = sorted(kwargs.get('list', []), reverse=reverse)
|
||||
return (result,)
|
||||
|
||||
|
||||
class DataListZip(ComfyNodeABC):
|
||||
@@ -525,116 +635,6 @@ class DataListZip(ComfyNodeABC):
|
||||
return (result,)
|
||||
|
||||
|
||||
class DataListFilter(ComfyNodeABC):
|
||||
"""
|
||||
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
|
||||
corresponding element in the filter list is False.
|
||||
|
||||
If the lists have different lengths, the last element of the shorter list is repeated
|
||||
till the lengths are matching.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value": (IO.ANY, {}),
|
||||
"filter": (IO.BOOLEAN, {"forceInput": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.ANY,)
|
||||
RETURN_NAMES = ("filtered_list",)
|
||||
CATEGORY = "Basic/data list"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "filter_data"
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
def filter_data(self, **kwargs: list[Any]) -> tuple[list[Any]]:
|
||||
values = kwargs.get('value', [])
|
||||
filters = kwargs.get('filter', [])
|
||||
|
||||
# Create a new list with only items where the filter is False
|
||||
result = [_val for _val, _filter in zip(values, filters) if not _filter]
|
||||
|
||||
return (result,)
|
||||
|
||||
|
||||
class DataListMin(ComfyNodeABC):
|
||||
"""
|
||||
Finds the minimum value in a list of numbers.
|
||||
|
||||
This node takes a list of numbers (either FLOAT or INT) and returns
|
||||
the minimum value. Returns None if the list is empty or if it contains
|
||||
non-numeric values.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": (IO.NUMBER, {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.NUMBER,)
|
||||
RETURN_NAMES = ("min",)
|
||||
CATEGORY = "Basic/data list"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "find_min"
|
||||
INPUT_IS_LIST = True
|
||||
|
||||
def find_min(self, **kwargs: list[Any]) -> tuple[Any]:
|
||||
values = kwargs.get('list', [])
|
||||
if not values:
|
||||
return (None,)
|
||||
|
||||
try:
|
||||
# Return the same type as found in the minimum value
|
||||
return (min(values),)
|
||||
except (TypeError, ValueError):
|
||||
# Handle case where list contains non-comparable elements
|
||||
return (None,)
|
||||
|
||||
|
||||
class DataListMax(ComfyNodeABC):
|
||||
"""
|
||||
Finds the maximum value in a list of numbers.
|
||||
|
||||
This node takes a list of numbers (either FLOAT or INT) and returns
|
||||
the maximum value. Returns None if the list is empty or if it contains
|
||||
non-numeric values.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": (IO.NUMBER, {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.NUMBER,)
|
||||
RETURN_NAMES = ("max",)
|
||||
CATEGORY = "Basic/data list"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "find_max"
|
||||
INPUT_IS_LIST = True
|
||||
|
||||
def find_max(self, **kwargs: list[Any]) -> tuple[Any]:
|
||||
values = kwargs.get('list', [])
|
||||
if not values:
|
||||
return (None,)
|
||||
|
||||
try:
|
||||
# Return the same type as found in the maximum value
|
||||
return (max(values),)
|
||||
except (TypeError, ValueError):
|
||||
# Handle case where list contains non-comparable elements
|
||||
return (None,)
|
||||
|
||||
|
||||
class DataListToList(ComfyNodeABC):
|
||||
"""
|
||||
Converts a ComfyUI data list into a LIST object.
|
||||
@@ -687,48 +687,48 @@ class DataListToSet(ComfyNodeABC):
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"Basic data handling: DataListAppend": DataListAppend,
|
||||
"Basic data handling: DataListExtend": DataListExtend,
|
||||
"Basic data handling: DataListInsert": DataListInsert,
|
||||
"Basic data handling: DataListRemove": DataListRemove,
|
||||
"Basic data handling: DataListPop": DataListPop,
|
||||
"Basic data handling: DataListIndex": DataListIndex,
|
||||
"Basic data handling: DataListCount": DataListCount,
|
||||
"Basic data handling: DataListSort": DataListSort,
|
||||
"Basic data handling: DataListReverse": DataListReverse,
|
||||
"Basic data handling: DataListLength": DataListLength,
|
||||
"Basic data handling: DataListSlice": DataListSlice,
|
||||
"Basic data handling: DataListGetItem": DataListGetItem,
|
||||
"Basic data handling: DataListSetItem": DataListSetItem,
|
||||
"Basic data handling: DataListContains": DataListContains,
|
||||
"Basic data handling: DataListCount": DataListCount,
|
||||
"Basic data handling: DataListCreateEmpty": DataListCreateEmpty,
|
||||
"Basic data handling: DataListZip": DataListZip,
|
||||
"Basic data handling: DataListExtend": DataListExtend,
|
||||
"Basic data handling: DataListFilter": DataListFilter,
|
||||
"Basic data handling: DataListMin": DataListMin,
|
||||
"Basic data handling: DataListGetItem": DataListGetItem,
|
||||
"Basic data handling: DataListIndex": DataListIndex,
|
||||
"Basic data handling: DataListInsert": DataListInsert,
|
||||
"Basic data handling: DataListLength": DataListLength,
|
||||
"Basic data handling: DataListMax": DataListMax,
|
||||
"Basic data handling: DataListMin": DataListMin,
|
||||
"Basic data handling: DataListPop": DataListPop,
|
||||
"Basic data handling: DataListRemove": DataListRemove,
|
||||
"Basic data handling: DataListReverse": DataListReverse,
|
||||
"Basic data handling: DataListSetItem": DataListSetItem,
|
||||
"Basic data handling: DataListSlice": DataListSlice,
|
||||
"Basic data handling: DataListSort": DataListSort,
|
||||
"Basic data handling: DataListZip": DataListZip,
|
||||
"Basic data handling: DataListToList": DataListToList,
|
||||
"Basic data handling: DataListToSet": DataListToSet,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"Basic data handling: DataListAppend": "append",
|
||||
"Basic data handling: DataListExtend": "extend",
|
||||
"Basic data handling: DataListInsert": "insert",
|
||||
"Basic data handling: DataListRemove": "remove",
|
||||
"Basic data handling: DataListPop": "pop",
|
||||
"Basic data handling: DataListIndex": "index",
|
||||
"Basic data handling: DataListCount": "count",
|
||||
"Basic data handling: DataListSort": "sort",
|
||||
"Basic data handling: DataListReverse": "reverse",
|
||||
"Basic data handling: DataListLength": "length",
|
||||
"Basic data handling: DataListSlice": "slice",
|
||||
"Basic data handling: DataListGetItem": "get item",
|
||||
"Basic data handling: DataListSetItem": "set item",
|
||||
"Basic data handling: DataListContains": "contains",
|
||||
"Basic data handling: DataListCount": "count",
|
||||
"Basic data handling: DataListCreateEmpty": "create empty",
|
||||
"Basic data handling: DataListZip": "zip",
|
||||
"Basic data handling: DataListExtend": "extend",
|
||||
"Basic data handling: DataListFilter": "filter",
|
||||
"Basic data handling: DataListMin": "min",
|
||||
"Basic data handling: DataListGetItem": "get item",
|
||||
"Basic data handling: DataListIndex": "index",
|
||||
"Basic data handling: DataListInsert": "insert",
|
||||
"Basic data handling: DataListLength": "length",
|
||||
"Basic data handling: DataListMax": "max",
|
||||
"Basic data handling: DataListMin": "min",
|
||||
"Basic data handling: DataListPop": "pop",
|
||||
"Basic data handling: DataListRemove": "remove",
|
||||
"Basic data handling: DataListReverse": "reverse",
|
||||
"Basic data handling: DataListSetItem": "set item",
|
||||
"Basic data handling: DataListSlice": "slice",
|
||||
"Basic data handling: DataListSort": "sort",
|
||||
"Basic data handling: DataListZip": "zip",
|
||||
"Basic data handling: DataListToList": "convert to LIST",
|
||||
"Basic data handling: DataListToSet": "convert to SET",
|
||||
}
|
||||
|
||||
@@ -21,38 +21,6 @@ class DictCreate(ComfyNodeABC):
|
||||
return ({},)
|
||||
|
||||
|
||||
class DictCreateFromItemsList(ComfyNodeABC):
|
||||
"""
|
||||
Creates a dictionary from a list of key-value pairs.
|
||||
|
||||
This node takes a list of key-value pairs (tuples) and builds a dictionary from them.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"items": ("LIST", {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("DICT",)
|
||||
CATEGORY = "Basic/DICT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "create_from_items"
|
||||
|
||||
def create_from_items(self, items: list) -> tuple[dict]:
|
||||
try:
|
||||
# Check if items are valid (key-value pairs)
|
||||
for item in items:
|
||||
print(f'item: "{item}", len(item): {len(item)}, items: "{items}", len(items): {len(items)}')
|
||||
if not isinstance(item, tuple) and len(item) != 2:
|
||||
raise ValueError("Each item must be a (key, value) pair")
|
||||
|
||||
return (dict(items),)
|
||||
except Exception as e:
|
||||
raise ValueError(f"Error creating dictionary from items: {str(e)}")
|
||||
|
||||
|
||||
class DictCreateFromItemsDataList(ComfyNodeABC):
|
||||
"""
|
||||
Creates a dictionary from a list of key-value pairs.
|
||||
@@ -87,6 +55,211 @@ class DictCreateFromItemsDataList(ComfyNodeABC):
|
||||
raise ValueError(f"Error creating dictionary from items: {str(e)}")
|
||||
|
||||
|
||||
class DictCreateFromItemsList(ComfyNodeABC):
|
||||
"""
|
||||
Creates a dictionary from a list of key-value pairs.
|
||||
|
||||
This node takes a list of key-value pairs (tuples) and builds a dictionary from them.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"items": ("LIST", {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("DICT",)
|
||||
CATEGORY = "Basic/DICT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "create_from_items"
|
||||
|
||||
def create_from_items(self, items: list) -> tuple[dict]:
|
||||
try:
|
||||
# Check if items are valid (key-value pairs)
|
||||
for item in items:
|
||||
print(f'item: "{item}", len(item): {len(item)}, items: "{items}", len(items): {len(items)}')
|
||||
if not isinstance(item, tuple) and len(item) != 2:
|
||||
raise ValueError("Each item must be a (key, value) pair")
|
||||
|
||||
return (dict(items),)
|
||||
except Exception as e:
|
||||
raise ValueError(f"Error creating dictionary from items: {str(e)}")
|
||||
|
||||
|
||||
class DictCreateFromLists(ComfyNodeABC):
|
||||
"""
|
||||
Creates a dictionary from separate lists of keys and values.
|
||||
|
||||
This node takes a list of keys and a list of values, then creates a dictionary
|
||||
by pairing corresponding elements from each list. If the lists are of different
|
||||
lengths, only pairs up to the length of the shorter list are used.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"keys": ("LIST", {}),
|
||||
"values": ("LIST", {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("DICT",)
|
||||
CATEGORY = "Basic/DICT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "create_from_lists"
|
||||
|
||||
def create_from_lists(self, keys: list, values: list) -> tuple[dict]:
|
||||
# Pair keys with values up to the length of the shorter list
|
||||
result = dict(zip(keys, values))
|
||||
return (result,)
|
||||
|
||||
|
||||
class DictCompare(ComfyNodeABC):
|
||||
"""
|
||||
Compares two dictionaries and reports differences.
|
||||
|
||||
This node takes two dictionaries and compares them, returning information about
|
||||
their equality, any keys that exist in only one dictionary, and any keys with
|
||||
different values.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"dict1": ("DICT", {}),
|
||||
"dict2": ("DICT", {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.BOOLEAN, "LIST", "LIST", "LIST")
|
||||
RETURN_NAMES = ("are_equal", "only_in_dict1", "only_in_dict2", "different_values")
|
||||
CATEGORY = "Basic/DICT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "compare"
|
||||
|
||||
def compare(self, dict1: dict, dict2: dict) -> tuple[bool, list, list, list]:
|
||||
are_equal = (dict1 == dict2)
|
||||
|
||||
keys1 = set(dict1.keys())
|
||||
keys2 = set(dict2.keys())
|
||||
|
||||
only_in_dict1 = list(keys1 - keys2)
|
||||
only_in_dict2 = list(keys2 - keys1)
|
||||
|
||||
different_values = []
|
||||
for key in keys1 & keys2:
|
||||
if dict1[key] != dict2[key]:
|
||||
different_values.append(key)
|
||||
|
||||
return are_equal, only_in_dict1, only_in_dict2, different_values
|
||||
|
||||
|
||||
class DictContainsKey(ComfyNodeABC):
|
||||
"""
|
||||
Checks if a key exists in a dictionary.
|
||||
|
||||
This node takes a dictionary and a key as inputs, then returns True if the key
|
||||
exists in the dictionary, and False otherwise.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"input_dict": ("DICT", {}),
|
||||
"key": (IO.STRING, {"default": ""}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.BOOLEAN,)
|
||||
CATEGORY = "Basic/DICT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "contains_key"
|
||||
|
||||
def contains_key(self, input_dict: dict, key: str) -> tuple[bool]:
|
||||
return (key in input_dict,)
|
||||
|
||||
|
||||
class DictExcludeKeys(ComfyNodeABC):
|
||||
"""
|
||||
Creates a new dictionary excluding the specified keys.
|
||||
|
||||
This node takes a dictionary and a list of keys, then returns a new dictionary
|
||||
containing all key-value pairs except those with keys in the provided list.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"input_dict": ("DICT", {}),
|
||||
"keys_to_exclude": ("LIST", {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("DICT",)
|
||||
CATEGORY = "Basic/DICT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "exclude_keys"
|
||||
|
||||
def exclude_keys(self, input_dict: dict, keys_to_exclude: list) -> tuple[dict]:
|
||||
result = {k: v for k, v in input_dict.items() if k not in keys_to_exclude}
|
||||
return (result,)
|
||||
|
||||
|
||||
class DictFilterByKeys(ComfyNodeABC):
|
||||
"""
|
||||
Creates a new dictionary with only the specified keys.
|
||||
|
||||
This node takes a dictionary and a list of keys, then returns a new dictionary
|
||||
containing only the key-value pairs for the keys that exist in the original dictionary.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"input_dict": ("DICT", {}),
|
||||
"keys": ("LIST", {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("DICT",)
|
||||
CATEGORY = "Basic/DICT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "filter_by_keys"
|
||||
|
||||
def filter_by_keys(self, input_dict: dict, keys: list) -> tuple[dict]:
|
||||
result = {k: input_dict[k] for k in keys if k in input_dict}
|
||||
return (result,)
|
||||
|
||||
|
||||
class DictFromKeys(ComfyNodeABC):
|
||||
"""
|
||||
Creates a dictionary from a list of keys and a default value.
|
||||
|
||||
This node takes a list of keys and an optional value, then creates a new
|
||||
dictionary where each key is associated with the value. If no value is
|
||||
provided, None is used.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"keys": ("LIST", {}),
|
||||
},
|
||||
"optional": {
|
||||
"value": (IO.ANY, {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("DICT",)
|
||||
CATEGORY = "Basic/DICT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "from_keys"
|
||||
|
||||
def from_keys(self, keys: list, value=None) -> tuple[dict]:
|
||||
return (dict.fromkeys(keys, value),)
|
||||
|
||||
|
||||
class DictGet(ComfyNodeABC):
|
||||
"""
|
||||
Retrieves a value from a dictionary using the specified key.
|
||||
@@ -117,62 +290,71 @@ class DictGet(ComfyNodeABC):
|
||||
return (input_dict.get(key, default),)
|
||||
|
||||
|
||||
class DictSet(ComfyNodeABC):
|
||||
class DictGetKeysValues(ComfyNodeABC):
|
||||
"""
|
||||
Adds or updates a key-value pair in a dictionary.
|
||||
Returns keys and values as separate lists.
|
||||
|
||||
This node takes a dictionary, key, and value as inputs, then returns
|
||||
a modified dictionary with the new key-value pair.
|
||||
This node takes a dictionary and returns two lists: one containing
|
||||
all keys and another containing all corresponding values.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"input_dict": ("DICT", {}),
|
||||
"key": (IO.STRING, {"default": ""}),
|
||||
"value": (IO.ANY, {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("DICT",)
|
||||
RETURN_TYPES = ("LIST", "LIST")
|
||||
RETURN_NAMES = ("keys", "values")
|
||||
CATEGORY = "Basic/DICT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "set"
|
||||
FUNCTION = "get_keys_values"
|
||||
|
||||
def set(self, input_dict: dict, key: str, value: Any) -> tuple[dict]:
|
||||
result = input_dict.copy()
|
||||
result[key] = value
|
||||
return (result,)
|
||||
def get_keys_values(self, input_dict: dict) -> tuple[list, list]:
|
||||
keys = list(input_dict.keys())
|
||||
values = list(input_dict.values())
|
||||
return keys, values
|
||||
|
||||
|
||||
class DictKeys(ComfyNodeABC):
|
||||
class DictGetMultiple(ComfyNodeABC):
|
||||
"""
|
||||
Returns all keys in a dictionary.
|
||||
Retrieves multiple values from a dictionary using a list of keys.
|
||||
|
||||
This node takes a dictionary and returns a list containing all of its keys.
|
||||
This node takes a dictionary and a list of keys, then returns a list
|
||||
containing the corresponding values. If a key is not found, the default
|
||||
value is used for that position.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"input_dict": ("DICT", {}),
|
||||
"keys": ("LIST", {}),
|
||||
},
|
||||
"optional": {
|
||||
"default": (IO.ANY, {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LIST",)
|
||||
RETURN_NAMES = ("values",)
|
||||
CATEGORY = "Basic/DICT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "keys"
|
||||
FUNCTION = "get_multiple"
|
||||
|
||||
def keys(self, input_dict: dict) -> tuple[list]:
|
||||
return (list(input_dict.keys()),)
|
||||
def get_multiple(self, input_dict: dict, keys: list, default=None) -> tuple[list]:
|
||||
values = [input_dict.get(key, default) for key in keys]
|
||||
return (values,)
|
||||
|
||||
|
||||
class DictValues(ComfyNodeABC):
|
||||
class DictInvert(ComfyNodeABC):
|
||||
"""
|
||||
Returns all values in a dictionary.
|
||||
Creates a new dictionary with keys and values swapped.
|
||||
|
||||
This node takes a dictionary and returns a list containing all of its values.
|
||||
This node takes a dictionary as input and returns a new dictionary where
|
||||
the keys become values and values become keys. Note that values must be
|
||||
hashable to be used as keys in the new dictionary.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -182,13 +364,19 @@ class DictValues(ComfyNodeABC):
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LIST",)
|
||||
RETURN_TYPES = ("DICT", IO.BOOLEAN)
|
||||
RETURN_NAMES = ("inverted_dict", "success")
|
||||
CATEGORY = "Basic/DICT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "values"
|
||||
FUNCTION = "invert"
|
||||
|
||||
def values(self, input_dict: dict) -> tuple[list]:
|
||||
return (list(input_dict.values()),)
|
||||
def invert(self, input_dict: dict) -> tuple[dict, bool]:
|
||||
try:
|
||||
inverted = {v: k for k, v in input_dict.items()}
|
||||
return inverted, True
|
||||
except Exception:
|
||||
# Return original dictionary if inversion fails (e.g., unhashable values)
|
||||
return input_dict, False
|
||||
|
||||
|
||||
class DictItems(ComfyNodeABC):
|
||||
@@ -215,57 +403,91 @@ class DictItems(ComfyNodeABC):
|
||||
return (list(input_dict.items()),)
|
||||
|
||||
|
||||
class DictContainsKey(ComfyNodeABC):
|
||||
class DictKeys(ComfyNodeABC):
|
||||
"""
|
||||
Checks if a key exists in a dictionary.
|
||||
Returns all keys in a dictionary.
|
||||
|
||||
This node takes a dictionary and a key as inputs, then returns True if the key
|
||||
exists in the dictionary, and False otherwise.
|
||||
This node takes a dictionary and returns a list containing all of its keys.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"input_dict": ("DICT", {}),
|
||||
"key": (IO.STRING, {"default": ""}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.BOOLEAN,)
|
||||
RETURN_TYPES = ("LIST",)
|
||||
CATEGORY = "Basic/DICT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "contains_key"
|
||||
FUNCTION = "keys"
|
||||
|
||||
def contains_key(self, input_dict: dict, key: str) -> tuple[bool]:
|
||||
return (key in input_dict,)
|
||||
def keys(self, input_dict: dict) -> tuple[list]:
|
||||
return (list(input_dict.keys()),)
|
||||
|
||||
|
||||
class DictFromKeys(ComfyNodeABC):
|
||||
class DictLength(ComfyNodeABC):
|
||||
"""
|
||||
Creates a dictionary from a list of keys and a default value.
|
||||
Returns the number of key-value pairs in a dictionary.
|
||||
|
||||
This node takes a list of keys and an optional value, then creates a new
|
||||
dictionary where each key is associated with the value. If no value is
|
||||
provided, None is used.
|
||||
This node takes a dictionary as input and returns its length (number of items).
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"keys": ("LIST", {}),
|
||||
"input_dict": ("DICT", {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INT",)
|
||||
RETURN_NAMES = ("length",)
|
||||
CATEGORY = "Basic/DICT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "length"
|
||||
|
||||
def length(self, input_dict: dict) -> tuple[int]:
|
||||
return (len(input_dict),)
|
||||
|
||||
|
||||
class DictMerge(ComfyNodeABC):
|
||||
"""
|
||||
Merges multiple dictionaries into a single dictionary.
|
||||
|
||||
This node takes multiple dictionaries as input and combines them into a single
|
||||
dictionary. If there are duplicate keys, values from later dictionaries take precedence.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"dict1": ("DICT", {}),
|
||||
},
|
||||
"optional": {
|
||||
"value": (IO.ANY, {}),
|
||||
"dict2": ("DICT", {}),
|
||||
"dict3": ("DICT", {}),
|
||||
"dict4": ("DICT", {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("DICT",)
|
||||
CATEGORY = "Basic/DICT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "from_keys"
|
||||
FUNCTION = "merge"
|
||||
|
||||
def from_keys(self, keys: list, value=None) -> tuple[dict]:
|
||||
return (dict.fromkeys(keys, value),)
|
||||
def merge(self, dict1: dict, dict2=None, dict3=None, dict4=None) -> tuple[dict]:
|
||||
result = dict1.copy()
|
||||
|
||||
if dict2 is not None:
|
||||
result.update(dict2)
|
||||
|
||||
if dict3 is not None:
|
||||
result.update(dict3)
|
||||
|
||||
if dict4 is not None:
|
||||
result.update(dict4)
|
||||
|
||||
return (result,)
|
||||
|
||||
|
||||
class DictPop(ComfyNodeABC):
|
||||
@@ -342,6 +564,65 @@ class DictPopItem(ComfyNodeABC):
|
||||
return result, "", None, False
|
||||
|
||||
|
||||
class DictRemove(ComfyNodeABC):
|
||||
"""
|
||||
Removes a key-value pair from a dictionary.
|
||||
|
||||
This node takes a dictionary and a key as inputs, then returns a new
|
||||
dictionary with the specified key removed. If the key doesn't exist,
|
||||
the dictionary remains unchanged.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"input_dict": ("DICT", {}),
|
||||
"key": (IO.STRING, {"default": ""}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("DICT", IO.BOOLEAN)
|
||||
RETURN_NAMES = ("dict", "key_removed")
|
||||
CATEGORY = "Basic/DICT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "remove"
|
||||
|
||||
def remove(self, input_dict: dict, key: str) -> tuple[dict, bool]:
|
||||
result = input_dict.copy()
|
||||
if key in result:
|
||||
del result[key]
|
||||
return result, True
|
||||
return result, False
|
||||
|
||||
|
||||
class DictSet(ComfyNodeABC):
|
||||
"""
|
||||
Adds or updates a key-value pair in a dictionary.
|
||||
|
||||
This node takes a dictionary, key, and value as inputs, then returns
|
||||
a modified dictionary with the new key-value pair.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"input_dict": ("DICT", {}),
|
||||
"key": (IO.STRING, {"default": ""}),
|
||||
"value": (IO.ANY, {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("DICT",)
|
||||
CATEGORY = "Basic/DICT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "set"
|
||||
|
||||
def set(self, input_dict: dict, key: str, value: Any) -> tuple[dict]:
|
||||
result = input_dict.copy()
|
||||
result[key] = value
|
||||
return (result,)
|
||||
|
||||
|
||||
class DictSetDefault(ComfyNodeABC):
|
||||
"""
|
||||
Returns the value for a key, setting a default if the key doesn't exist.
|
||||
@@ -400,11 +681,11 @@ class DictUpdate(ComfyNodeABC):
|
||||
return (result,)
|
||||
|
||||
|
||||
class DictLength(ComfyNodeABC):
|
||||
class DictValues(ComfyNodeABC):
|
||||
"""
|
||||
Returns the number of key-value pairs in a dictionary.
|
||||
Returns all values in a dictionary.
|
||||
|
||||
This node takes a dictionary as input and returns its length (number of items).
|
||||
This node takes a dictionary and returns a list containing all of its values.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -414,346 +695,65 @@ class DictLength(ComfyNodeABC):
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INT",)
|
||||
RETURN_NAMES = ("length",)
|
||||
CATEGORY = "Basic/DICT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "length"
|
||||
|
||||
def length(self, input_dict: dict) -> tuple[int]:
|
||||
return (len(input_dict),)
|
||||
|
||||
|
||||
class DictMerge(ComfyNodeABC):
|
||||
"""
|
||||
Merges multiple dictionaries into a single dictionary.
|
||||
|
||||
This node takes multiple dictionaries as input and combines them into a single
|
||||
dictionary. If there are duplicate keys, values from later dictionaries take precedence.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"dict1": ("DICT", {}),
|
||||
},
|
||||
"optional": {
|
||||
"dict2": ("DICT", {}),
|
||||
"dict3": ("DICT", {}),
|
||||
"dict4": ("DICT", {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("DICT",)
|
||||
CATEGORY = "Basic/DICT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "merge"
|
||||
|
||||
def merge(self, dict1: dict, dict2=None, dict3=None, dict4=None) -> tuple[dict]:
|
||||
result = dict1.copy()
|
||||
|
||||
if dict2 is not None:
|
||||
result.update(dict2)
|
||||
|
||||
if dict3 is not None:
|
||||
result.update(dict3)
|
||||
|
||||
if dict4 is not None:
|
||||
result.update(dict4)
|
||||
|
||||
return (result,)
|
||||
|
||||
|
||||
class DictGetKeysValues(ComfyNodeABC):
|
||||
"""
|
||||
Returns keys and values as separate lists.
|
||||
|
||||
This node takes a dictionary and returns two lists: one containing
|
||||
all keys and another containing all corresponding values.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"input_dict": ("DICT", {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LIST", "LIST")
|
||||
RETURN_NAMES = ("keys", "values")
|
||||
CATEGORY = "Basic/DICT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "get_keys_values"
|
||||
|
||||
def get_keys_values(self, input_dict: dict) -> tuple[list, list]:
|
||||
keys = list(input_dict.keys())
|
||||
values = list(input_dict.values())
|
||||
return keys, values
|
||||
|
||||
|
||||
class DictRemove(ComfyNodeABC):
|
||||
"""
|
||||
Removes a key-value pair from a dictionary.
|
||||
|
||||
This node takes a dictionary and a key as inputs, then returns a new
|
||||
dictionary with the specified key removed. If the key doesn't exist,
|
||||
the dictionary remains unchanged.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"input_dict": ("DICT", {}),
|
||||
"key": (IO.STRING, {"default": ""}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("DICT", IO.BOOLEAN)
|
||||
RETURN_NAMES = ("dict", "key_removed")
|
||||
CATEGORY = "Basic/DICT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "remove"
|
||||
|
||||
def remove(self, input_dict: dict, key: str) -> tuple[dict, bool]:
|
||||
result = input_dict.copy()
|
||||
if key in result:
|
||||
del result[key]
|
||||
return result, True
|
||||
return result, False
|
||||
|
||||
|
||||
class DictFilterByKeys(ComfyNodeABC):
|
||||
"""
|
||||
Creates a new dictionary with only the specified keys.
|
||||
|
||||
This node takes a dictionary and a list of keys, then returns a new dictionary
|
||||
containing only the key-value pairs for the keys that exist in the original dictionary.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"input_dict": ("DICT", {}),
|
||||
"keys": ("LIST", {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("DICT",)
|
||||
CATEGORY = "Basic/DICT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "filter_by_keys"
|
||||
|
||||
def filter_by_keys(self, input_dict: dict, keys: list) -> tuple[dict]:
|
||||
result = {k: input_dict[k] for k in keys if k in input_dict}
|
||||
return (result,)
|
||||
|
||||
|
||||
class DictExcludeKeys(ComfyNodeABC):
|
||||
"""
|
||||
Creates a new dictionary excluding the specified keys.
|
||||
|
||||
This node takes a dictionary and a list of keys, then returns a new dictionary
|
||||
containing all key-value pairs except those with keys in the provided list.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"input_dict": ("DICT", {}),
|
||||
"keys_to_exclude": ("LIST", {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("DICT",)
|
||||
CATEGORY = "Basic/DICT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "exclude_keys"
|
||||
|
||||
def exclude_keys(self, input_dict: dict, keys_to_exclude: list) -> tuple[dict]:
|
||||
result = {k: v for k, v in input_dict.items() if k not in keys_to_exclude}
|
||||
return (result,)
|
||||
|
||||
|
||||
class DictGetMultiple(ComfyNodeABC):
|
||||
"""
|
||||
Retrieves multiple values from a dictionary using a list of keys.
|
||||
|
||||
This node takes a dictionary and a list of keys, then returns a list
|
||||
containing the corresponding values. If a key is not found, the default
|
||||
value is used for that position.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"input_dict": ("DICT", {}),
|
||||
"keys": ("LIST", {}),
|
||||
},
|
||||
"optional": {
|
||||
"default": (IO.ANY, {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LIST",)
|
||||
RETURN_NAMES = ("values",)
|
||||
CATEGORY = "Basic/DICT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "get_multiple"
|
||||
FUNCTION = "values"
|
||||
|
||||
def get_multiple(self, input_dict: dict, keys: list, default=None) -> tuple[list]:
|
||||
values = [input_dict.get(key, default) for key in keys]
|
||||
return (values,)
|
||||
|
||||
|
||||
class DictInvert(ComfyNodeABC):
|
||||
"""
|
||||
Creates a new dictionary with keys and values swapped.
|
||||
|
||||
This node takes a dictionary as input and returns a new dictionary where
|
||||
the keys become values and values become keys. Note that values must be
|
||||
hashable to be used as keys in the new dictionary.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"input_dict": ("DICT", {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("DICT", IO.BOOLEAN)
|
||||
RETURN_NAMES = ("inverted_dict", "success")
|
||||
CATEGORY = "Basic/DICT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "invert"
|
||||
|
||||
def invert(self, input_dict: dict) -> tuple[dict, bool]:
|
||||
try:
|
||||
inverted = {v: k for k, v in input_dict.items()}
|
||||
return inverted, True
|
||||
except Exception:
|
||||
# Return original dictionary if inversion fails (e.g., unhashable values)
|
||||
return input_dict, False
|
||||
|
||||
|
||||
class DictCreateFromLists(ComfyNodeABC):
|
||||
"""
|
||||
Creates a dictionary from separate lists of keys and values.
|
||||
|
||||
This node takes a list of keys and a list of values, then creates a dictionary
|
||||
by pairing corresponding elements from each list. If the lists are of different
|
||||
lengths, only pairs up to the length of the shorter list are used.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"keys": ("LIST", {}),
|
||||
"values": ("LIST", {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("DICT",)
|
||||
CATEGORY = "Basic/DICT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "create_from_lists"
|
||||
|
||||
def create_from_lists(self, keys: list, values: list) -> tuple[dict]:
|
||||
# Pair keys with values up to the length of the shorter list
|
||||
result = dict(zip(keys, values))
|
||||
return (result,)
|
||||
|
||||
|
||||
class DictCompare(ComfyNodeABC):
|
||||
"""
|
||||
Compares two dictionaries and reports differences.
|
||||
|
||||
This node takes two dictionaries and compares them, returning information about
|
||||
their equality, any keys that exist in only one dictionary, and any keys with
|
||||
different values.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"dict1": ("DICT", {}),
|
||||
"dict2": ("DICT", {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.BOOLEAN, "LIST", "LIST", "LIST")
|
||||
RETURN_NAMES = ("are_equal", "only_in_dict1", "only_in_dict2", "different_values")
|
||||
CATEGORY = "Basic/DICT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "compare"
|
||||
|
||||
def compare(self, dict1: dict, dict2: dict) -> tuple[bool, list, list, list]:
|
||||
are_equal = (dict1 == dict2)
|
||||
|
||||
keys1 = set(dict1.keys())
|
||||
keys2 = set(dict2.keys())
|
||||
|
||||
only_in_dict1 = list(keys1 - keys2)
|
||||
only_in_dict2 = list(keys2 - keys1)
|
||||
|
||||
different_values = []
|
||||
for key in keys1 & keys2:
|
||||
if dict1[key] != dict2[key]:
|
||||
different_values.append(key)
|
||||
|
||||
return are_equal, only_in_dict1, only_in_dict2, different_values
|
||||
def values(self, input_dict: dict) -> tuple[list]:
|
||||
return (list(input_dict.values()),)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"Basic data handling: DictCreate": DictCreate,
|
||||
"Basic data handling: DictCreateFromItemsList": DictCreateFromItemsList,
|
||||
"Basic data handling: DictCreateFromItemsDataList": DictCreateFromItemsDataList,
|
||||
"Basic data handling: DictGet": DictGet,
|
||||
"Basic data handling: DictSet": DictSet,
|
||||
"Basic data handling: DictKeys": DictKeys,
|
||||
"Basic data handling: DictValues": DictValues,
|
||||
"Basic data handling: DictItems": DictItems,
|
||||
"Basic data handling: DictContainsKey": DictContainsKey,
|
||||
"Basic data handling: DictFromKeys": DictFromKeys,
|
||||
"Basic data handling: DictPop": DictPop,
|
||||
"Basic data handling: DictPopItem": DictPopItem,
|
||||
"Basic data handling: DictSetDefault": DictSetDefault,
|
||||
"Basic data handling: DictUpdate": DictUpdate,
|
||||
"Basic data handling: DictLength": DictLength,
|
||||
"Basic data handling: DictMerge": DictMerge,
|
||||
"Basic data handling: DictGetKeysValues": DictGetKeysValues,
|
||||
"Basic data handling: DictRemove": DictRemove,
|
||||
"Basic data handling: DictFilterByKeys": DictFilterByKeys,
|
||||
"Basic data handling: DictExcludeKeys": DictExcludeKeys,
|
||||
"Basic data handling: DictGetMultiple": DictGetMultiple,
|
||||
"Basic data handling: DictInvert": DictInvert,
|
||||
"Basic data handling: DictCreateFromItemsList": DictCreateFromItemsList,
|
||||
"Basic data handling: DictCreateFromLists": DictCreateFromLists,
|
||||
"Basic data handling: DictCompare": DictCompare,
|
||||
"Basic data handling: DictContainsKey": DictContainsKey,
|
||||
"Basic data handling: DictExcludeKeys": DictExcludeKeys,
|
||||
"Basic data handling: DictFilterByKeys": DictFilterByKeys,
|
||||
"Basic data handling: DictFromKeys": DictFromKeys,
|
||||
"Basic data handling: DictGet": DictGet,
|
||||
"Basic data handling: DictGetKeysValues": DictGetKeysValues,
|
||||
"Basic data handling: DictGetMultiple": DictGetMultiple,
|
||||
"Basic data handling: DictInvert": DictInvert,
|
||||
"Basic data handling: DictItems": DictItems,
|
||||
"Basic data handling: DictKeys": DictKeys,
|
||||
"Basic data handling: DictLength": DictLength,
|
||||
"Basic data handling: DictMerge": DictMerge,
|
||||
"Basic data handling: DictPop": DictPop,
|
||||
"Basic data handling: DictPopItem": DictPopItem,
|
||||
"Basic data handling: DictRemove": DictRemove,
|
||||
"Basic data handling: DictSet": DictSet,
|
||||
"Basic data handling: DictSetDefault": DictSetDefault,
|
||||
"Basic data handling: DictUpdate": DictUpdate,
|
||||
"Basic data handling: DictValues": DictValues,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"Basic data handling: DictCreate": "create",
|
||||
"Basic data handling: DictCreateFromItemsList": "from items (LIST)",
|
||||
"Basic data handling: DictCreateFromItemsDataList": "from items (data list)",
|
||||
"Basic data handling: DictGet": "get",
|
||||
"Basic data handling: DictSet": "set",
|
||||
"Basic data handling: DictKeys": "keys",
|
||||
"Basic data handling: DictValues": "values",
|
||||
"Basic data handling: DictItems": "items",
|
||||
"Basic data handling: DictContainsKey": "contains_key",
|
||||
"Basic data handling: DictCreate": "create empty",
|
||||
"Basic data handling: DictCreateFromItemsDataList": "create from items (data list)",
|
||||
"Basic data handling: DictCreateFromItemsList": "create from items (LIST)",
|
||||
"Basic data handling: DictCreateFromLists": "create from LISTs",
|
||||
"Basic data handling: DictCompare": "compare",
|
||||
"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: DictPop": "pop",
|
||||
"Basic data handling: DictPopItem": "popitem",
|
||||
"Basic data handling: DictSetDefault": "setdefault",
|
||||
"Basic data handling: DictUpdate": "update",
|
||||
"Basic data handling: DictGet": "get",
|
||||
"Basic data handling: DictGetKeysValues": "get keys values",
|
||||
"Basic data handling: DictGetMultiple": "get multiple",
|
||||
"Basic data handling: DictInvert": "invert",
|
||||
"Basic data handling: DictItems": "items",
|
||||
"Basic data handling: DictKeys": "keys",
|
||||
"Basic data handling: DictLength": "length",
|
||||
"Basic data handling: DictMerge": "merge",
|
||||
"Basic data handling: DictGetKeysValues": "get_keys_values",
|
||||
"Basic data handling: DictPop": "pop",
|
||||
"Basic data handling: DictPopItem": "popitem",
|
||||
"Basic data handling: DictRemove": "remove",
|
||||
"Basic data handling: DictFilterByKeys": "filter_by_keys",
|
||||
"Basic data handling: DictExcludeKeys": "exclude_keys",
|
||||
"Basic data handling: DictGetMultiple": "get_multiple",
|
||||
"Basic data handling: DictInvert": "invert",
|
||||
"Basic data handling: DictCreateFromLists": "from_lists",
|
||||
"Basic data handling: DictCompare": "compare",
|
||||
"Basic data handling: DictSet": "set",
|
||||
"Basic data handling: DictSetDefault": "setdefault",
|
||||
"Basic data handling: DictUpdate": "update",
|
||||
"Basic data handling: DictValues": "values",
|
||||
}
|
||||
|
||||
@@ -128,6 +128,103 @@ class FloatDivideSafe(ComfyNodeABC):
|
||||
return (float1 / float2,)
|
||||
|
||||
|
||||
class FloatAsIntegerRatio(ComfyNodeABC):
|
||||
"""
|
||||
Returns the integer ratio of a floating-point number.
|
||||
|
||||
This node takes a floating-point number and returns two integers,
|
||||
which represent the ratio as numerator and denominator.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"float_value": (IO.FLOAT, {"default": 0.0}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.INT, IO.INT)
|
||||
RETURN_NAMES = ("numerator", "denominator")
|
||||
CATEGORY = "Basic/FLOAT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "as_integer_ratio"
|
||||
|
||||
def as_integer_ratio(self, float_value: float) -> tuple[int, int]:
|
||||
# Decompose the float into numerator and denominator
|
||||
numerator, denominator = float_value.as_integer_ratio()
|
||||
return numerator, denominator
|
||||
|
||||
|
||||
class FloatFromHex(ComfyNodeABC):
|
||||
"""
|
||||
Converts a hexadecimal string to its corresponding floating-point number.
|
||||
|
||||
This node takes a hexadecimal float string as input and returns the float.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"hex_value": (IO.STRING, {"default": "0x0.0p+0"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.FLOAT,)
|
||||
CATEGORY = "Basic/FLOAT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "from_hex"
|
||||
|
||||
def from_hex(self, hex_value: str) -> tuple[float]:
|
||||
return (float.fromhex(hex_value),)
|
||||
|
||||
|
||||
class FloatHex(ComfyNodeABC):
|
||||
"""
|
||||
Converts a floating-point number to its hexadecimal representation.
|
||||
|
||||
This node takes a float as input and returns its hexadecimal string representation.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"float_value": (IO.FLOAT, {"default": 0.0}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
CATEGORY = "Basic/FLOAT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "to_hex"
|
||||
|
||||
def to_hex(self, float_value: float) -> tuple[str]:
|
||||
return (float_value.hex(),)
|
||||
|
||||
|
||||
class FloatIsInteger(ComfyNodeABC):
|
||||
"""
|
||||
Checks if a floating-point number is an integer.
|
||||
|
||||
This node takes a floating-point number as input and returns True if the number
|
||||
is an integer, otherwise False.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"float_value": (IO.FLOAT, {"default": 0.0}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("BOOLEAN",)
|
||||
CATEGORY = "Basic/FLOAT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "is_integer"
|
||||
|
||||
def is_integer(self, float_value: float) -> tuple[bool]:
|
||||
return (float_value.is_integer(),)
|
||||
|
||||
|
||||
class FloatPower(ComfyNodeABC):
|
||||
"""
|
||||
Raises one floating-point number to the power of another.
|
||||
@@ -178,115 +275,18 @@ class FloatRound(ComfyNodeABC):
|
||||
return (round(float_value, decimal_places),)
|
||||
|
||||
|
||||
class FloatIsInteger(ComfyNodeABC):
|
||||
"""
|
||||
Checks if a floating-point number is an integer.
|
||||
|
||||
This node takes a floating-point number as input and returns True if the number
|
||||
is an integer, otherwise False.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"float_value": (IO.FLOAT, {"default": 0.0}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("BOOLEAN",)
|
||||
CATEGORY = "Basic/FLOAT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "is_integer"
|
||||
|
||||
def is_integer(self, float_value: float) -> tuple[bool]:
|
||||
return (float_value.is_integer(),)
|
||||
|
||||
|
||||
class FloatAsIntegerRatio(ComfyNodeABC):
|
||||
"""
|
||||
Returns the integer ratio of a floating-point number.
|
||||
|
||||
This node takes a floating-point number and returns two integers,
|
||||
which represent the ratio as numerator and denominator.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"float_value": (IO.FLOAT, {"default": 0.0}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.INT, IO.INT)
|
||||
RETURN_NAMES = ("numerator", "denominator")
|
||||
CATEGORY = "Basic/FLOAT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "as_integer_ratio"
|
||||
|
||||
def as_integer_ratio(self, float_value: float) -> tuple[int, int]:
|
||||
# Decompose the float into numerator and denominator
|
||||
numerator, denominator = float_value.as_integer_ratio()
|
||||
return numerator, denominator
|
||||
|
||||
|
||||
class FloatHex(ComfyNodeABC):
|
||||
"""
|
||||
Converts a floating-point number to its hexadecimal representation.
|
||||
|
||||
This node takes a float as input and returns its hexadecimal string representation.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"float_value": (IO.FLOAT, {"default": 0.0}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
CATEGORY = "Basic/FLOAT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "to_hex"
|
||||
|
||||
def to_hex(self, float_value: float) -> tuple[str]:
|
||||
return (float_value.hex(),)
|
||||
|
||||
|
||||
class FloatFromHex(ComfyNodeABC):
|
||||
"""
|
||||
Converts a hexadecimal string to its corresponding floating-point number.
|
||||
|
||||
This node takes a hexadecimal float string as input and returns the float.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"hex_value": (IO.STRING, {"default": "0x0.0p+0"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.FLOAT,)
|
||||
CATEGORY = "Basic/FLOAT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "from_hex"
|
||||
|
||||
def from_hex(self, hex_value: str) -> tuple[float]:
|
||||
return (float.fromhex(hex_value),)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"Basic data handling: FloatAdd": FloatAdd,
|
||||
"Basic data handling: FloatSubtract": FloatSubtract,
|
||||
"Basic data handling: FloatMultiply": FloatMultiply,
|
||||
"Basic data handling: FloatDivide": FloatDivide,
|
||||
"Basic data handling: FloatDivideSafe": FloatDivideSafe,
|
||||
"Basic data handling: FloatAsIntegerRatio": FloatAsIntegerRatio,
|
||||
"Basic data handling: FloatFromHex": FloatFromHex,
|
||||
"Basic data handling: FloatHex": FloatHex,
|
||||
"Basic data handling: FloatIsInteger": FloatIsInteger,
|
||||
"Basic data handling: FloatPower": FloatPower,
|
||||
"Basic data handling: FloatRound": FloatRound,
|
||||
"Basic data handling: FloatIsInteger": FloatIsInteger,
|
||||
"Basic data handling: FloatAsIntegerRatio": FloatAsIntegerRatio,
|
||||
"Basic data handling: FloatHex": FloatHex,
|
||||
"Basic data handling: FloatFromHex": FloatFromHex,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
@@ -295,10 +295,10 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"Basic data handling: FloatMultiply": "multiply",
|
||||
"Basic data handling: FloatDivide": "divide",
|
||||
"Basic data handling: FloatDivideSafe": "divide (division by zero safe)",
|
||||
"Basic data handling: FloatAsIntegerRatio": "integer ratio",
|
||||
"Basic data handling: FloatFromHex": "from hex",
|
||||
"Basic data handling: FloatHex": "to hex",
|
||||
"Basic data handling: FloatIsInteger": "is integer",
|
||||
"Basic data handling: FloatPower": "power",
|
||||
"Basic data handling: FloatRound": "round",
|
||||
"Basic data handling: FloatIsInteger": "is_integer",
|
||||
"Basic data handling: FloatAsIntegerRatio": "as_integer_ratio",
|
||||
"Basic data handling: FloatHex": "to_hex",
|
||||
"Basic data handling: FloatFromHex": "from_hex",
|
||||
}
|
||||
|
||||
@@ -130,6 +130,81 @@ class IntDivideSafe(ComfyNodeABC):
|
||||
return (int1 // int2,)
|
||||
|
||||
|
||||
class IntBitCount(ComfyNodeABC):
|
||||
"""
|
||||
Returns the number of 1 bits in the binary representation of an integer.
|
||||
|
||||
This node takes an integer as input and returns the count of set bits.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"int_value": (IO.INT, {"default": 0}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.INT,)
|
||||
CATEGORY = "Basic/INT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "bit_count"
|
||||
|
||||
def bit_count(self, int_value: int) -> tuple[int]:
|
||||
# Introduced in Python 3.10
|
||||
return (int_value.bit_count(),)
|
||||
|
||||
|
||||
class IntBitLength(ComfyNodeABC):
|
||||
"""
|
||||
Returns the number of bits required to represent an integer in binary.
|
||||
|
||||
This node takes an integer as input and returns the number of bits needed
|
||||
to represent it, excluding the sign and leading zeros.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"int_value": (IO.INT, {"default": 0}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.INT,)
|
||||
CATEGORY = "Basic/INT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "bit_length"
|
||||
|
||||
def bit_length(self, int_value: int) -> tuple[int]:
|
||||
return (int_value.bit_length(),)
|
||||
|
||||
|
||||
class IntFromBytes(ComfyNodeABC):
|
||||
"""
|
||||
Converts a bytes object to an integer.
|
||||
|
||||
This class method takes bytes, byte order, and signed flag as inputs and
|
||||
returns an integer.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"bytes_value": ("BYTES", {}),
|
||||
"byteorder": (["big", "little"], {"default": "big"}),
|
||||
"signed": (["True", "False"], {"default": "False"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.INT,)
|
||||
CATEGORY = "Basic/INT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "from_bytes"
|
||||
|
||||
def from_bytes(self, bytes_value, byteorder: Literal["big", "little"], signed: Literal["True", "False"]) -> tuple[int]:
|
||||
signed_bool = (signed == "True")
|
||||
return (int.from_bytes(bytes_value, byteorder=byteorder, signed=signed_bool),)
|
||||
|
||||
|
||||
class IntModulus(ComfyNodeABC):
|
||||
"""
|
||||
Returns the modulus of two integers.
|
||||
@@ -182,30 +257,6 @@ class IntPower(ComfyNodeABC):
|
||||
return (base**exponent,)
|
||||
|
||||
|
||||
class IntBitLength(ComfyNodeABC):
|
||||
"""
|
||||
Returns the number of bits required to represent an integer in binary.
|
||||
|
||||
This node takes an integer as input and returns the number of bits needed
|
||||
to represent it, excluding the sign and leading zeros.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"int_value": (IO.INT, {"default": 0}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.INT,)
|
||||
CATEGORY = "Basic/INT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "bit_length"
|
||||
|
||||
def bit_length(self, int_value: int) -> tuple[int]:
|
||||
return (int_value.bit_length(),)
|
||||
|
||||
|
||||
class IntToBytes(ComfyNodeABC):
|
||||
"""
|
||||
Converts an integer to its byte representation.
|
||||
@@ -234,69 +285,18 @@ class IntToBytes(ComfyNodeABC):
|
||||
return (int_value.to_bytes(length, byteorder=byteorder, signed=signed_bool),)
|
||||
|
||||
|
||||
class IntFromBytes(ComfyNodeABC):
|
||||
"""
|
||||
Converts a bytes object to an integer.
|
||||
|
||||
This class method takes bytes, byte order, and signed flag as inputs and
|
||||
returns an integer.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"bytes_value": ("BYTES", {}),
|
||||
"byteorder": (["big", "little"], {"default": "big"}),
|
||||
"signed": (["True", "False"], {"default": "False"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.INT,)
|
||||
CATEGORY = "Basic/INT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "from_bytes"
|
||||
|
||||
def from_bytes(self, bytes_value, byteorder: Literal["big", "little"], signed: Literal["True", "False"]) -> tuple[int]:
|
||||
signed_bool = (signed == "True")
|
||||
return (int.from_bytes(bytes_value, byteorder=byteorder, signed=signed_bool),)
|
||||
|
||||
|
||||
class IntBitCount(ComfyNodeABC):
|
||||
"""
|
||||
Returns the number of 1 bits in the binary representation of an integer.
|
||||
|
||||
This node takes an integer as input and returns the count of set bits.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"int_value": (IO.INT, {"default": 0}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.INT,)
|
||||
CATEGORY = "Basic/INT"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "bit_count"
|
||||
|
||||
def bit_count(self, int_value: int) -> tuple[int]:
|
||||
# Introduced in Python 3.10
|
||||
return (int_value.bit_count(),)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"Basic data handling: IntAdd": IntAdd,
|
||||
"Basic data handling: IntSubtract": IntSubtract,
|
||||
"Basic data handling: IntMultiply": IntMultiply,
|
||||
"Basic data handling: IntDivide": IntDivide,
|
||||
"Basic data handling: IntDivideSafe": IntDivideSafe,
|
||||
"Basic data handling: IntBitCount": IntBitCount,
|
||||
"Basic data handling: IntBitLength": IntBitLength,
|
||||
"Basic data handling: IntFromBytes": IntFromBytes,
|
||||
"Basic data handling: IntModulus": IntModulus,
|
||||
"Basic data handling: IntPower": IntPower,
|
||||
"Basic data handling: IntBitLength": IntBitLength,
|
||||
"Basic data handling: IntToBytes": IntToBytes,
|
||||
"Basic data handling: IntFromBytes": IntFromBytes,
|
||||
"Basic data handling: IntBitCount": IntBitCount,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
@@ -305,11 +305,11 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"Basic data handling: IntMultiply": "multiply",
|
||||
"Basic data handling: IntDivide": "divide",
|
||||
"Basic data handling: IntDivideSafe": "divide (division by zero safe)",
|
||||
"Basic data handling: IntBitCount": "bit count",
|
||||
"Basic data handling: IntBitLength": "bit length",
|
||||
"Basic data handling: IntFromBytes": "from bytes",
|
||||
"Basic data handling: IntModulus": "modulus",
|
||||
"Basic data handling: IntPower": "power",
|
||||
"Basic data handling: IntBitLength": "bit_length",
|
||||
"Basic data handling: IntToBytes": "to_bytes",
|
||||
"Basic data handling: IntFromBytes": "from_bytes",
|
||||
"Basic data handling: IntBitCount": "bit_count",
|
||||
"Basic data handling: IntToBytes": "to bytes",
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,25 @@ 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):
|
||||
"""
|
||||
Create an empty LIST.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LIST",)
|
||||
CATEGORY = "Basic/LIST"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "create"
|
||||
|
||||
def create(self) -> tuple[list[Any]]:
|
||||
return ([],)
|
||||
|
||||
|
||||
class ListAppend(ComfyNodeABC):
|
||||
"""
|
||||
Adds an item to the end of a LIST.
|
||||
@@ -31,23 +50,56 @@ class ListAppend(ComfyNodeABC):
|
||||
return (result,)
|
||||
|
||||
|
||||
class ListEmpty(ComfyNodeABC):
|
||||
class ListContains(ComfyNodeABC):
|
||||
"""
|
||||
Create an empty LIST.
|
||||
Checks if a LIST contains a specified value.
|
||||
|
||||
This node takes a LIST and a value as inputs, then returns True if the value
|
||||
is present in the LIST, and False otherwise.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {}
|
||||
"required": {
|
||||
"list": ("LIST", {}),
|
||||
"value": (IO.ANY, {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LIST",)
|
||||
RETURN_TYPES = ("BOOLEAN",)
|
||||
RETURN_NAMES = ("contains",)
|
||||
CATEGORY = "Basic/LIST"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "create"
|
||||
FUNCTION = "contains"
|
||||
|
||||
def create(self) -> tuple[list[Any]]:
|
||||
return ([],)
|
||||
def contains(self, list: list[Any], value: Any) -> tuple[bool]:
|
||||
return (value in list,)
|
||||
|
||||
|
||||
class ListCount(ComfyNodeABC):
|
||||
"""
|
||||
Counts the number of occurrences of a value in a LIST.
|
||||
|
||||
This node takes a LIST and a value as inputs, then returns the number of times
|
||||
the value appears in the LIST.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": ("LIST", {}),
|
||||
"value": (IO.ANY, {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INT",)
|
||||
RETURN_NAMES = ("count",)
|
||||
CATEGORY = "Basic/LIST"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "count"
|
||||
|
||||
def count(self, list: list[Any], value: Any) -> tuple[int]:
|
||||
return (list.count(value),)
|
||||
|
||||
|
||||
class ListExtend(ComfyNodeABC):
|
||||
@@ -77,12 +129,13 @@ class ListExtend(ComfyNodeABC):
|
||||
return (result,)
|
||||
|
||||
|
||||
class ListInsert(ComfyNodeABC):
|
||||
class ListGetItem(ComfyNodeABC):
|
||||
"""
|
||||
Inserts an item at a specified position in a LIST.
|
||||
Retrieves an item at a specified position in a LIST.
|
||||
|
||||
This node takes a LIST, an index, and any item as inputs, then returns a new
|
||||
LIST with the item inserted at the specified index.
|
||||
This node takes a LIST and an index as inputs, then returns the item at the specified index.
|
||||
Negative indices count from the end of the LIST.
|
||||
Out of range indices return None.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -90,86 +143,20 @@ class ListInsert(ComfyNodeABC):
|
||||
"required": {
|
||||
"list": ("LIST", {}),
|
||||
"index": ("INT", {"default": 0}),
|
||||
"item": (IO.ANY, {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LIST",)
|
||||
RETURN_TYPES = (IO.ANY,)
|
||||
RETURN_NAMES = ("item",)
|
||||
CATEGORY = "Basic/LIST"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "insert"
|
||||
FUNCTION = "get_item"
|
||||
|
||||
def insert(self, list: list[Any], index: int, item: Any) -> tuple[list[Any]]:
|
||||
result = list.copy()
|
||||
result.insert(index, item)
|
||||
return (result,)
|
||||
|
||||
|
||||
class ListRemove(ComfyNodeABC):
|
||||
"""
|
||||
Removes the first occurrence of a specified value from a LIST.
|
||||
|
||||
This node takes a LIST and a value as inputs, then returns a new LIST with
|
||||
the first occurrence of the value removed and a success indicator. If the value
|
||||
is not present, the original LIST is returned with success set to False.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": ("LIST", {}),
|
||||
"value": (IO.ANY, {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LIST", "BOOLEAN")
|
||||
RETURN_NAMES = ("list", "success")
|
||||
CATEGORY = "Basic/LIST"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "remove"
|
||||
|
||||
def remove(self, list: list[Any], value: Any) -> tuple[list[Any], bool]:
|
||||
result = list.copy()
|
||||
def get_item(self, list: list[Any], index: int) -> tuple[Any]:
|
||||
try:
|
||||
result.remove(value)
|
||||
return result, True
|
||||
except ValueError:
|
||||
return result, False
|
||||
|
||||
|
||||
class ListPop(ComfyNodeABC):
|
||||
"""
|
||||
Removes and returns an item at a specified position in a LIST.
|
||||
|
||||
This node takes a LIST and an index as inputs, then returns both the new LIST
|
||||
with the item removed and the removed item. If no index is specified,
|
||||
removes and returns the last item.
|
||||
When the LIST is empty, the item is None.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": ("LIST", {}),
|
||||
},
|
||||
"optional": {
|
||||
"index": ("INT", {"default": -1}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LIST", IO.ANY)
|
||||
RETURN_NAMES = ("list", "item")
|
||||
CATEGORY = "Basic/LIST"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "pop"
|
||||
|
||||
def pop(self, list: list[Any], index: int = -1) -> tuple[list[Any], Any]:
|
||||
result = list.copy()
|
||||
try:
|
||||
item = result.pop(index)
|
||||
return result, item
|
||||
return (list[index],)
|
||||
except IndexError:
|
||||
return result, None
|
||||
return (None,)
|
||||
|
||||
|
||||
class ListIndex(ComfyNodeABC):
|
||||
@@ -209,12 +196,164 @@ class ListIndex(ComfyNodeABC):
|
||||
return (-1,)
|
||||
|
||||
|
||||
class ListCount(ComfyNodeABC):
|
||||
class ListInsert(ComfyNodeABC):
|
||||
"""
|
||||
Counts the number of occurrences of a value in a LIST.
|
||||
Inserts an item at a specified position in a LIST.
|
||||
|
||||
This node takes a LIST and a value as inputs, then returns the number of times
|
||||
the value appears in the LIST.
|
||||
This node takes a LIST, an index, and any item as inputs, then returns a new
|
||||
LIST with the item inserted at the specified index.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": ("LIST", {}),
|
||||
"index": ("INT", {"default": 0}),
|
||||
"item": (IO.ANY, {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LIST",)
|
||||
CATEGORY = "Basic/LIST"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "insert"
|
||||
|
||||
def insert(self, list: list[Any], index: int, item: Any) -> tuple[list[Any]]:
|
||||
result = list.copy()
|
||||
result.insert(index, item)
|
||||
return (result,)
|
||||
|
||||
|
||||
class ListLength(ComfyNodeABC):
|
||||
"""
|
||||
Returns the number of items in a LIST.
|
||||
|
||||
This node takes a LIST as input and returns its length as an integer.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": ("LIST", {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INT",)
|
||||
RETURN_NAMES = ("length",)
|
||||
CATEGORY = "Basic/LIST"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "length"
|
||||
|
||||
def length(self, list: list[Any]) -> tuple[int]:
|
||||
return (len(list),)
|
||||
|
||||
|
||||
class ListMax(ComfyNodeABC):
|
||||
"""
|
||||
Returns the maximum value in a LIST.
|
||||
|
||||
This node takes a LIST of comparable items and returns the maximum value.
|
||||
Returns None if the LIST is empty or if items are not comparable.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": ("LIST", {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.ANY,)
|
||||
RETURN_NAMES = ("max_value",)
|
||||
CATEGORY = "Basic/LIST"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "find_max"
|
||||
|
||||
def find_max(self, list: list[Any]) -> tuple[Any]:
|
||||
if not list:
|
||||
return (None,)
|
||||
|
||||
try:
|
||||
return (max(list),)
|
||||
except (TypeError, ValueError):
|
||||
# Handle case where list contains non-comparable elements
|
||||
return (None,)
|
||||
|
||||
|
||||
class ListMin(ComfyNodeABC):
|
||||
"""
|
||||
Returns the minimum value in a LIST.
|
||||
|
||||
This node takes a LIST of comparable items and returns the minimum value.
|
||||
Returns None if the LIST is empty or if items are not comparable.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": ("LIST", {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.ANY,)
|
||||
RETURN_NAMES = ("min_value",)
|
||||
CATEGORY = "Basic/LIST"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "find_min"
|
||||
|
||||
def find_min(self, list: list[Any]) -> tuple[Any]:
|
||||
if not list:
|
||||
return (None,)
|
||||
|
||||
try:
|
||||
return (min(list),)
|
||||
except (TypeError, ValueError):
|
||||
# Handle case where list contains non-comparable elements
|
||||
return (None,)
|
||||
|
||||
|
||||
class ListPop(ComfyNodeABC):
|
||||
"""
|
||||
Removes and returns an item at a specified position in a LIST.
|
||||
|
||||
This node takes a LIST and an index as inputs, then returns both the new LIST
|
||||
with the item removed and the removed item. If no index is specified,
|
||||
removes and returns the last item.
|
||||
When the LIST is empty, the item is None.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": ("LIST", {}),
|
||||
},
|
||||
"optional": {
|
||||
"index": ("INT", {"default": -1}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LIST", IO.ANY)
|
||||
RETURN_NAMES = ("list", "item")
|
||||
CATEGORY = "Basic/LIST"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "pop"
|
||||
|
||||
def pop(self, list: list[Any], index: int = -1) -> tuple[list[Any], Any]:
|
||||
result = list.copy()
|
||||
try:
|
||||
item = result.pop(index)
|
||||
return result, item
|
||||
except IndexError:
|
||||
return result, None
|
||||
|
||||
|
||||
class ListRemove(ComfyNodeABC):
|
||||
"""
|
||||
Removes the first occurrence of a specified value from a LIST.
|
||||
|
||||
This node takes a LIST and a value as inputs, then returns a new LIST with
|
||||
the first occurrence of the value removed and a success indicator. If the value
|
||||
is not present, the original LIST is returned with success set to False.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -225,14 +364,105 @@ class ListCount(ComfyNodeABC):
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INT",)
|
||||
RETURN_NAMES = ("count",)
|
||||
RETURN_TYPES = ("LIST", "BOOLEAN")
|
||||
RETURN_NAMES = ("list", "success")
|
||||
CATEGORY = "Basic/LIST"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "count"
|
||||
FUNCTION = "remove"
|
||||
|
||||
def count(self, list: list[Any], value: Any) -> tuple[int]:
|
||||
return (list.count(value),)
|
||||
def remove(self, list: list[Any], value: Any) -> tuple[list[Any], bool]:
|
||||
result = list.copy()
|
||||
try:
|
||||
result.remove(value)
|
||||
return result, True
|
||||
except ValueError:
|
||||
return result, False
|
||||
|
||||
|
||||
class ListReverse(ComfyNodeABC):
|
||||
"""
|
||||
Reverses the order of items in a LIST.
|
||||
|
||||
This node takes a LIST as input and returns a new LIST with the items in reversed order.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": ("LIST", {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LIST",)
|
||||
CATEGORY = "Basic/LIST"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "reverse"
|
||||
|
||||
def reverse(self, list: list[Any]) -> tuple[list[Any]]:
|
||||
result = list.copy()
|
||||
result.reverse()
|
||||
return (result,)
|
||||
|
||||
|
||||
class ListSetItem(ComfyNodeABC):
|
||||
"""
|
||||
Sets an item at a specified position in a LIST.
|
||||
|
||||
This node takes a LIST, an index, and a value, then returns a new LIST with
|
||||
the item at the specified index replaced by the value.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": ("LIST", {}),
|
||||
"index": ("INT", {"default": 0}),
|
||||
"value": (IO.ANY, {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LIST",)
|
||||
CATEGORY = "Basic/LIST"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "set_item"
|
||||
|
||||
def set_item(self, list: list[Any], index: int, value: Any) -> tuple[list[Any]]:
|
||||
result = list.copy()
|
||||
try:
|
||||
result[index] = value
|
||||
return (result,)
|
||||
except IndexError:
|
||||
raise IndexError(f"Index {index} out of range for LIST of length {len(list)}")
|
||||
|
||||
|
||||
class ListSlice(ComfyNodeABC):
|
||||
"""
|
||||
Creates a slice of a LIST.
|
||||
|
||||
This node takes a LIST and start/stop/step parameters, and returns a new LIST
|
||||
containing the specified slice of the original LIST.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": ("LIST", {}),
|
||||
},
|
||||
"optional": {
|
||||
"start": ("INT", {"default": 0}),
|
||||
"stop": ("INT", {"default": INT_MAX}),
|
||||
"step": ("INT", {"default": 1}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LIST",)
|
||||
CATEGORY = "Basic/LIST"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "slice"
|
||||
|
||||
def slice(self, list: list[Any], start: int = 0, stop: int = INT_MAX,
|
||||
step: int = 1) -> tuple[list[Any]]:
|
||||
return (list[start:stop:step],)
|
||||
|
||||
|
||||
class ListSort(ComfyNodeABC):
|
||||
@@ -271,172 +501,6 @@ class ListSort(ComfyNodeABC):
|
||||
return (list.copy(),)
|
||||
|
||||
|
||||
class ListReverse(ComfyNodeABC):
|
||||
"""
|
||||
Reverses the order of items in a LIST.
|
||||
|
||||
This node takes a LIST as input and returns a new LIST with the items in reversed order.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": ("LIST", {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LIST",)
|
||||
CATEGORY = "Basic/LIST"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "reverse"
|
||||
|
||||
def reverse(self, list: list[Any]) -> tuple[list[Any]]:
|
||||
result = list.copy()
|
||||
result.reverse()
|
||||
return (result,)
|
||||
|
||||
|
||||
class ListLength(ComfyNodeABC):
|
||||
"""
|
||||
Returns the number of items in a LIST.
|
||||
|
||||
This node takes a LIST as input and returns its length as an integer.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": ("LIST", {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INT",)
|
||||
RETURN_NAMES = ("length",)
|
||||
CATEGORY = "Basic/LIST"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "length"
|
||||
|
||||
def length(self, list: list[Any]) -> tuple[int]:
|
||||
return (len(list),)
|
||||
|
||||
|
||||
class ListSlice(ComfyNodeABC):
|
||||
"""
|
||||
Creates a slice of a LIST.
|
||||
|
||||
This node takes a LIST and start/stop/step parameters, and returns a new LIST
|
||||
containing the specified slice of the original LIST.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": ("LIST", {}),
|
||||
},
|
||||
"optional": {
|
||||
"start": ("INT", {"default": 0}),
|
||||
"stop": ("INT", {"default": INT_MAX}),
|
||||
"step": ("INT", {"default": 1}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LIST",)
|
||||
CATEGORY = "Basic/LIST"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "slice"
|
||||
|
||||
def slice(self, list: list[Any], start: int = 0, stop: int = INT_MAX,
|
||||
step: int = 1) -> tuple[list[Any]]:
|
||||
return (list[start:stop:step],)
|
||||
|
||||
|
||||
class ListGetItem(ComfyNodeABC):
|
||||
"""
|
||||
Retrieves an item at a specified position in a LIST.
|
||||
|
||||
This node takes a LIST and an index as inputs, then returns the item at the specified index.
|
||||
Negative indices count from the end of the LIST.
|
||||
Out of range indices return None.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": ("LIST", {}),
|
||||
"index": ("INT", {"default": 0}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.ANY,)
|
||||
RETURN_NAMES = ("item",)
|
||||
CATEGORY = "Basic/LIST"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "get_item"
|
||||
|
||||
def get_item(self, list: list[Any], index: int) -> tuple[Any]:
|
||||
try:
|
||||
return (list[index],)
|
||||
except IndexError:
|
||||
return (None,)
|
||||
|
||||
|
||||
class ListSetItem(ComfyNodeABC):
|
||||
"""
|
||||
Sets an item at a specified position in a LIST.
|
||||
|
||||
This node takes a LIST, an index, and a value, then returns a new LIST with
|
||||
the item at the specified index replaced by the value.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": ("LIST", {}),
|
||||
"index": ("INT", {"default": 0}),
|
||||
"value": (IO.ANY, {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LIST",)
|
||||
CATEGORY = "Basic/LIST"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "set_item"
|
||||
|
||||
def set_item(self, list: list[Any], index: int, value: Any) -> tuple[list[Any]]:
|
||||
result = list.copy()
|
||||
try:
|
||||
result[index] = value
|
||||
return (result,)
|
||||
except IndexError:
|
||||
raise IndexError(f"Index {index} out of range for LIST of length {len(list)}")
|
||||
|
||||
|
||||
class ListContains(ComfyNodeABC):
|
||||
"""
|
||||
Checks if a LIST contains a specified value.
|
||||
|
||||
This node takes a LIST and a value as inputs, then returns True if the value
|
||||
is present in the LIST, and False otherwise.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": ("LIST", {}),
|
||||
"value": (IO.ANY, {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("BOOLEAN",)
|
||||
RETURN_NAMES = ("contains",)
|
||||
CATEGORY = "Basic/LIST"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "contains"
|
||||
|
||||
def contains(self, list: list[Any], value: Any) -> tuple[bool]:
|
||||
return (value in list,)
|
||||
|
||||
|
||||
class ListToDataList(ComfyNodeABC):
|
||||
"""
|
||||
Converts a LIST object into a ComfyUI data list.
|
||||
@@ -487,110 +551,46 @@ class ListToSet(ComfyNodeABC):
|
||||
return (set(list),)
|
||||
|
||||
|
||||
class ListMin(ComfyNodeABC):
|
||||
"""
|
||||
Returns the minimum value in a LIST.
|
||||
|
||||
This node takes a LIST of comparable items and returns the minimum value.
|
||||
Returns None if the LIST is empty or if items are not comparable.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": ("LIST", {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.ANY,)
|
||||
RETURN_NAMES = ("min_value",)
|
||||
CATEGORY = "Basic/LIST"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "find_min"
|
||||
|
||||
def find_min(self, list: list[Any]) -> tuple[Any]:
|
||||
if not list:
|
||||
return (None,)
|
||||
|
||||
try:
|
||||
return (min(list),)
|
||||
except (TypeError, ValueError):
|
||||
# Handle case where list contains non-comparable elements
|
||||
return (None,)
|
||||
|
||||
|
||||
class ListMax(ComfyNodeABC):
|
||||
"""
|
||||
Returns the maximum value in a LIST.
|
||||
|
||||
This node takes a LIST of comparable items and returns the maximum value.
|
||||
Returns None if the LIST is empty or if items are not comparable.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": ("LIST", {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.ANY,)
|
||||
RETURN_NAMES = ("max_value",)
|
||||
CATEGORY = "Basic/LIST"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "find_max"
|
||||
|
||||
def find_max(self, list: list[Any]) -> tuple[Any]:
|
||||
if not list:
|
||||
return (None,)
|
||||
|
||||
try:
|
||||
return (max(list),)
|
||||
except (TypeError, ValueError):
|
||||
# Handle case where list contains non-comparable elements
|
||||
return (None,)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"Basic data handling: ListAppend": ListAppend,
|
||||
"Basic data handling: ListEmpty": ListEmpty,
|
||||
"Basic data handling: ListExtend": ListExtend,
|
||||
"Basic data handling: ListInsert": ListInsert,
|
||||
"Basic data handling: ListRemove": ListRemove,
|
||||
"Basic data handling: ListPop": ListPop,
|
||||
"Basic data handling: ListIndex": ListIndex,
|
||||
"Basic data handling: ListCount": ListCount,
|
||||
"Basic data handling: ListSort": ListSort,
|
||||
"Basic data handling: ListReverse": ListReverse,
|
||||
"Basic data handling: ListLength": ListLength,
|
||||
"Basic data handling: ListSlice": ListSlice,
|
||||
"Basic data handling: ListGetItem": ListGetItem,
|
||||
"Basic data handling: ListSetItem": ListSetItem,
|
||||
"Basic data handling: ListAppend": ListAppend,
|
||||
"Basic data handling: ListContains": ListContains,
|
||||
"Basic data handling: ListCount": ListCount,
|
||||
"Basic data handling: ListExtend": ListExtend,
|
||||
"Basic data handling: ListGetItem": ListGetItem,
|
||||
"Basic data handling: ListIndex": ListIndex,
|
||||
"Basic data handling: ListInsert": ListInsert,
|
||||
"Basic data handling: ListLength": ListLength,
|
||||
"Basic data handling: ListMax": ListMax,
|
||||
"Basic data handling: ListMin": ListMin,
|
||||
"Basic data handling: ListPop": ListPop,
|
||||
"Basic data handling: ListRemove": ListRemove,
|
||||
"Basic data handling: ListReverse": ListReverse,
|
||||
"Basic data handling: ListSetItem": ListSetItem,
|
||||
"Basic data handling: ListSlice": ListSlice,
|
||||
"Basic data handling: ListSort": ListSort,
|
||||
"Basic data handling: ListToDataList": ListToDataList,
|
||||
"Basic data handling: ListToSet": ListToSet,
|
||||
"Basic data handling: ListMin": ListMin,
|
||||
"Basic data handling: ListMax": ListMax,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"Basic data handling: ListEmpty": "create empty",
|
||||
"Basic data handling: ListAppend": "append",
|
||||
"Basic data handling: ListEmpty": "empty",
|
||||
"Basic data handling: ListExtend": "extend",
|
||||
"Basic data handling: ListInsert": "insert",
|
||||
"Basic data handling: ListRemove": "remove",
|
||||
"Basic data handling: ListPop": "pop",
|
||||
"Basic data handling: ListIndex": "index",
|
||||
"Basic data handling: ListCount": "count",
|
||||
"Basic data handling: ListSort": "sort",
|
||||
"Basic data handling: ListReverse": "reverse",
|
||||
"Basic data handling: ListLength": "length",
|
||||
"Basic data handling: ListSlice": "slice",
|
||||
"Basic data handling: ListGetItem": "get item",
|
||||
"Basic data handling: ListSetItem": "set item",
|
||||
"Basic data handling: ListContains": "contains",
|
||||
"Basic data handling: ListCount": "count",
|
||||
"Basic data handling: ListExtend": "extend",
|
||||
"Basic data handling: ListGetItem": "get item",
|
||||
"Basic data handling: ListIndex": "index",
|
||||
"Basic data handling: ListInsert": "insert",
|
||||
"Basic data handling: ListLength": "length",
|
||||
"Basic data handling: ListMax": "max",
|
||||
"Basic data handling: ListMin": "min",
|
||||
"Basic data handling: ListPop": "pop",
|
||||
"Basic data handling: ListRemove": "remove",
|
||||
"Basic data handling: ListReverse": "reverse",
|
||||
"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: ListToSet": "convert to SET",
|
||||
"Basic data handling: ListMin": "min",
|
||||
"Basic data handling: ListMax": "max",
|
||||
}
|
||||
|
||||
@@ -3,111 +3,17 @@ from inspect import cleandoc
|
||||
from typing import Literal, Union
|
||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
||||
|
||||
class MathSin(ComfyNodeABC):
|
||||
class MathAbs(ComfyNodeABC):
|
||||
"""
|
||||
Calculates the sine of an angle in radians or degrees.
|
||||
Returns the absolute value of a number.
|
||||
|
||||
This node takes an angle value and unit type, and returns the sine of that angle.
|
||||
The sine of an angle is the ratio of the length of the opposite side to the length
|
||||
of the hypotenuse in a right-angled triangle.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"angle": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
|
||||
"unit": (["radians", "degrees"], {"default": "degrees"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.FLOAT,)
|
||||
CATEGORY = "Basic/maths"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "calculate"
|
||||
|
||||
def calculate(self, angle: float, unit: Literal["radians", "degrees"]) -> tuple[float]:
|
||||
if unit == "degrees":
|
||||
# Convert degrees to radians
|
||||
angle = math.radians(angle)
|
||||
return (math.sin(angle),)
|
||||
|
||||
|
||||
class MathCos(ComfyNodeABC):
|
||||
"""
|
||||
Calculates the cosine of an angle in radians or degrees.
|
||||
|
||||
This node takes an angle value and unit type, and returns the cosine of that angle.
|
||||
The cosine of an angle is the ratio of the length of the adjacent side to the length
|
||||
of the hypotenuse in a right-angled triangle.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"angle": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
|
||||
"unit": (["radians", "degrees"], {"default": "degrees"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.FLOAT,)
|
||||
CATEGORY = "Basic/maths"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "calculate"
|
||||
|
||||
def calculate(self, angle: float, unit: Literal["radians", "degrees"]) -> tuple[float]:
|
||||
if unit == "degrees":
|
||||
# Convert degrees to radians
|
||||
angle = math.radians(angle)
|
||||
return (math.cos(angle),)
|
||||
|
||||
|
||||
class MathTan(ComfyNodeABC):
|
||||
"""
|
||||
Calculates the tangent of an angle in radians or degrees.
|
||||
|
||||
This node takes an angle value and unit type, and returns the tangent of that angle.
|
||||
The tangent of an angle is the ratio of the length of the opposite side to the length
|
||||
of the adjacent side in a right-angled triangle.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"angle": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
|
||||
"unit": (["radians", "degrees"], {"default": "degrees"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.FLOAT,)
|
||||
CATEGORY = "Basic/maths"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "calculate"
|
||||
|
||||
def calculate(self, angle: float, unit: Literal["radians", "degrees"]) -> tuple[float]:
|
||||
if unit == "degrees":
|
||||
# Convert degrees to radians
|
||||
angle = math.radians(angle)
|
||||
|
||||
# Handle specific angles that would result in division by zero
|
||||
if abs(math.cos(angle)) < 1e-10:
|
||||
raise ValueError("Tangent is undefined at this angle (division by zero)")
|
||||
|
||||
return (math.tan(angle),)
|
||||
|
||||
|
||||
class MathAsin(ComfyNodeABC):
|
||||
"""
|
||||
Calculates the arc sine (inverse sine) of a value.
|
||||
|
||||
This node takes a value between -1 and 1, and returns the arc sine in radians or degrees.
|
||||
The arc sine is the inverse operation of sine, returning the angle whose sine is the input value.
|
||||
This node takes a number and returns its absolute value (magnitude without sign).
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
|
||||
"unit": (["radians", "degrees"], {"default": "degrees"}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,11 +22,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)
|
||||
if unit == "degrees":
|
||||
result = math.degrees(result)
|
||||
return (result,)
|
||||
def calculate(self, value: Union[float, int]) -> tuple[Union[float, int]]:
|
||||
return (abs(value),)
|
||||
|
||||
|
||||
class MathAcos(ComfyNodeABC):
|
||||
@@ -151,6 +54,34 @@ class MathAcos(ComfyNodeABC):
|
||||
return (result,)
|
||||
|
||||
|
||||
class MathAsin(ComfyNodeABC):
|
||||
"""
|
||||
Calculates the arc sine (inverse sine) of a value.
|
||||
|
||||
This node takes a value between -1 and 1, and returns the arc sine in radians or degrees.
|
||||
The arc sine is the inverse operation of sine, returning the angle whose sine is the input value.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
|
||||
"unit": (["radians", "degrees"], {"default": "degrees"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.FLOAT,)
|
||||
CATEGORY = "Basic/maths"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "calculate"
|
||||
|
||||
def calculate(self, value: float, unit: Literal["radians", "degrees"]) -> tuple[float]:
|
||||
result = math.asin(value)
|
||||
if unit == "degrees":
|
||||
result = math.degrees(result)
|
||||
return (result,)
|
||||
|
||||
|
||||
class MathAtan(ComfyNodeABC):
|
||||
"""
|
||||
Calculates the arc tangent (inverse tangent) of a value.
|
||||
@@ -209,17 +140,43 @@ class MathAtan2(ComfyNodeABC):
|
||||
return (result,)
|
||||
|
||||
|
||||
class MathSqrt(ComfyNodeABC):
|
||||
class MathCeil(ComfyNodeABC):
|
||||
"""
|
||||
Calculates the square root of a non-negative number.
|
||||
Returns the ceiling of a number.
|
||||
|
||||
This node takes a non-negative number and returns its square root.
|
||||
This node takes a number and returns the smallest integer greater than or equal to the input value.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value": (IO.NUMBER, {"default": 0.0, "min": 0.0, "widgetType": "FLOAT"}),
|
||||
"value": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.INT,)
|
||||
CATEGORY = "Basic/maths"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "calculate"
|
||||
|
||||
def calculate(self, value: float) -> tuple[int]:
|
||||
return (math.ceil(value),)
|
||||
|
||||
|
||||
class MathCos(ComfyNodeABC):
|
||||
"""
|
||||
Calculates the cosine of an angle in radians or degrees.
|
||||
|
||||
This node takes an angle value and unit type, and returns the cosine of that angle.
|
||||
The cosine of an angle is the ratio of the length of the adjacent side to the length
|
||||
of the hypotenuse in a right-angled triangle.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"angle": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
|
||||
"unit": (["radians", "degrees"], {"default": "degrees"}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,8 +185,53 @@ class MathSqrt(ComfyNodeABC):
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "calculate"
|
||||
|
||||
def calculate(self, value: float) -> tuple[float]:
|
||||
return (math.sqrt(value),)
|
||||
def calculate(self, angle: float, unit: Literal["radians", "degrees"]) -> tuple[float]:
|
||||
if unit == "degrees":
|
||||
# Convert degrees to radians
|
||||
angle = math.radians(angle)
|
||||
return (math.cos(angle),)
|
||||
|
||||
|
||||
class MathDegrees(ComfyNodeABC):
|
||||
"""
|
||||
Converts an angle from radians to degrees.
|
||||
|
||||
This node takes an angle in radians and returns the equivalent angle in degrees.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"radians": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.FLOAT,)
|
||||
CATEGORY = "Basic/maths"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "calculate"
|
||||
|
||||
def calculate(self, radians: float) -> tuple[float]:
|
||||
return (math.degrees(radians),)
|
||||
|
||||
|
||||
class MathE(ComfyNodeABC):
|
||||
"""
|
||||
Returns the mathematical constant e.
|
||||
|
||||
This node returns the value of e (Euler's number), which is approximately 2.71828.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {"required": {}}
|
||||
|
||||
RETURN_TYPES = (IO.FLOAT,)
|
||||
CATEGORY = "Basic/maths"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "calculate"
|
||||
|
||||
def calculate(self) -> tuple[float]:
|
||||
return (math.e,)
|
||||
|
||||
|
||||
class MathExp(ComfyNodeABC):
|
||||
@@ -255,6 +257,29 @@ class MathExp(ComfyNodeABC):
|
||||
return (math.exp(value),)
|
||||
|
||||
|
||||
class MathFloor(ComfyNodeABC):
|
||||
"""
|
||||
Returns the floor of a number.
|
||||
|
||||
This node takes a number and returns the largest integer less than or equal to the input value.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.INT,)
|
||||
CATEGORY = "Basic/maths"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "calculate"
|
||||
|
||||
def calculate(self, value: float) -> tuple[int]:
|
||||
return (math.floor(value),)
|
||||
|
||||
|
||||
class MathLog(ComfyNodeABC):
|
||||
"""
|
||||
Calculates the natural logarithm (base e) of a number.
|
||||
@@ -308,157 +333,29 @@ class MathLog10(ComfyNodeABC):
|
||||
return (math.log10(value),)
|
||||
|
||||
|
||||
class MathDegrees(ComfyNodeABC):
|
||||
class MathMax(ComfyNodeABC):
|
||||
"""
|
||||
Converts an angle from radians to degrees.
|
||||
Returns the maximum value among the inputs.
|
||||
|
||||
This node takes an angle in radians and returns the equivalent angle in degrees.
|
||||
This node takes two or more values and returns the maximum value among them.
|
||||
The function works with both FLOAT and INT types.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"radians": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
|
||||
"value1": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
|
||||
"value2": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.FLOAT,)
|
||||
RETURN_TYPES = ("*",)
|
||||
CATEGORY = "Basic/maths"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "calculate"
|
||||
|
||||
def calculate(self, radians: float) -> tuple[float]:
|
||||
return (math.degrees(radians),)
|
||||
|
||||
|
||||
class MathRadians(ComfyNodeABC):
|
||||
"""
|
||||
Converts an angle from degrees to radians.
|
||||
|
||||
This node takes an angle in degrees and returns the equivalent angle in radians.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"degrees": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.FLOAT,)
|
||||
CATEGORY = "Basic/maths"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "calculate"
|
||||
|
||||
def calculate(self, degrees: float) -> tuple[float]:
|
||||
return (math.radians(degrees),)
|
||||
|
||||
|
||||
class MathFloor(ComfyNodeABC):
|
||||
"""
|
||||
Returns the floor of a number.
|
||||
|
||||
This node takes a number and returns the largest integer less than or equal to the input value.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.INT,)
|
||||
CATEGORY = "Basic/maths"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "calculate"
|
||||
|
||||
def calculate(self, value: float) -> tuple[int]:
|
||||
return (math.floor(value),)
|
||||
|
||||
|
||||
class MathCeil(ComfyNodeABC):
|
||||
"""
|
||||
Returns the ceiling of a number.
|
||||
|
||||
This node takes a number and returns the smallest integer greater than or equal to the input value.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.INT,)
|
||||
CATEGORY = "Basic/maths"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "calculate"
|
||||
|
||||
def calculate(self, value: float) -> tuple[int]:
|
||||
return (math.ceil(value),)
|
||||
|
||||
|
||||
class MathAbs(ComfyNodeABC):
|
||||
"""
|
||||
Returns the absolute value of a number.
|
||||
|
||||
This node takes a number and returns its absolute value (magnitude without sign).
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.FLOAT,)
|
||||
CATEGORY = "Basic/maths"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "calculate"
|
||||
|
||||
def calculate(self, value: Union[float, int]) -> tuple[Union[float, int]]:
|
||||
return (abs(value),)
|
||||
|
||||
|
||||
class MathPi(ComfyNodeABC):
|
||||
"""
|
||||
Returns the mathematical constant π (pi).
|
||||
|
||||
This node returns the value of π, which is approximately 3.14159.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {"required": {}}
|
||||
|
||||
RETURN_TYPES = (IO.FLOAT,)
|
||||
CATEGORY = "Basic/maths"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "calculate"
|
||||
|
||||
def calculate(self) -> tuple[float]:
|
||||
return (math.pi,)
|
||||
|
||||
|
||||
class MathE(ComfyNodeABC):
|
||||
"""
|
||||
Returns the mathematical constant e.
|
||||
|
||||
This node returns the value of e (Euler's number), which is approximately 2.71828.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {"required": {}}
|
||||
|
||||
RETURN_TYPES = (IO.FLOAT,)
|
||||
CATEGORY = "Basic/maths"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "calculate"
|
||||
|
||||
def calculate(self) -> tuple[float]:
|
||||
return (math.e,)
|
||||
def calculate(self, value1: Union[float, int], value2: Union[float, int]) -> tuple[Union[float, int]]:
|
||||
return (max(value1, value2),)
|
||||
|
||||
|
||||
class MathMin(ComfyNodeABC):
|
||||
@@ -486,73 +383,176 @@ class MathMin(ComfyNodeABC):
|
||||
return (min(value1, value2),)
|
||||
|
||||
|
||||
class MathMax(ComfyNodeABC):
|
||||
class MathPi(ComfyNodeABC):
|
||||
"""
|
||||
Returns the maximum value among the inputs.
|
||||
Returns the mathematical constant π (pi).
|
||||
|
||||
This node takes two or more values and returns the maximum value among them.
|
||||
The function works with both FLOAT and INT types.
|
||||
This node returns the value of π, which is approximately 3.14159.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {"required": {}}
|
||||
|
||||
RETURN_TYPES = (IO.FLOAT,)
|
||||
CATEGORY = "Basic/maths"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "calculate"
|
||||
|
||||
def calculate(self) -> tuple[float]:
|
||||
return (math.pi,)
|
||||
|
||||
|
||||
class MathRadians(ComfyNodeABC):
|
||||
"""
|
||||
Converts an angle from degrees to radians.
|
||||
|
||||
This node takes an angle in degrees and returns the equivalent angle in radians.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value1": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
|
||||
"value2": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
|
||||
"degrees": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("*",)
|
||||
RETURN_TYPES = (IO.FLOAT,)
|
||||
CATEGORY = "Basic/maths"
|
||||
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, degrees: float) -> tuple[float]:
|
||||
return (math.radians(degrees),)
|
||||
|
||||
|
||||
class MathSin(ComfyNodeABC):
|
||||
"""
|
||||
Calculates the sine of an angle in radians or degrees.
|
||||
|
||||
This node takes an angle value and unit type, and returns the sine of that angle.
|
||||
The sine of an angle is the ratio of the length of the opposite side to the length
|
||||
of the hypotenuse in a right-angled triangle.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"angle": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
|
||||
"unit": (["radians", "degrees"], {"default": "degrees"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.FLOAT,)
|
||||
CATEGORY = "Basic/maths"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "calculate"
|
||||
|
||||
def calculate(self, angle: float, unit: Literal["radians", "degrees"]) -> tuple[float]:
|
||||
if unit == "degrees":
|
||||
# Convert degrees to radians
|
||||
angle = math.radians(angle)
|
||||
return (math.sin(angle),)
|
||||
|
||||
|
||||
class MathSqrt(ComfyNodeABC):
|
||||
"""
|
||||
Calculates the square root of a non-negative number.
|
||||
|
||||
This node takes a non-negative number and returns its square root.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value": (IO.NUMBER, {"default": 0.0, "min": 0.0, "widgetType": "FLOAT"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.FLOAT,)
|
||||
CATEGORY = "Basic/maths"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "calculate"
|
||||
|
||||
def calculate(self, value: float) -> tuple[float]:
|
||||
return (math.sqrt(value),)
|
||||
|
||||
|
||||
class MathTan(ComfyNodeABC):
|
||||
"""
|
||||
Calculates the tangent of an angle in radians or degrees.
|
||||
|
||||
This node takes an angle value and unit type, and returns the tangent of that angle.
|
||||
The tangent of an angle is the ratio of the length of the opposite side to the length
|
||||
of the adjacent side in a right-angled triangle.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"angle": (IO.NUMBER, {"default": 0.0, "widgetType": "FLOAT"}),
|
||||
"unit": (["radians", "degrees"], {"default": "degrees"}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.FLOAT,)
|
||||
CATEGORY = "Basic/maths"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "calculate"
|
||||
|
||||
def calculate(self, angle: float, unit: Literal["radians", "degrees"]) -> tuple[float]:
|
||||
if unit == "degrees":
|
||||
# Convert degrees to radians
|
||||
angle = math.radians(angle)
|
||||
|
||||
# Handle specific angles that would result in division by zero
|
||||
if abs(math.cos(angle)) < 1e-10:
|
||||
raise ValueError("Tangent is undefined at this angle (division by zero)")
|
||||
|
||||
return (math.tan(angle),)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"Basic data handling: MathSin": MathSin,
|
||||
"Basic data handling: MathCos": MathCos,
|
||||
"Basic data handling: MathTan": MathTan,
|
||||
"Basic data handling: MathAsin": MathAsin,
|
||||
"Basic data handling: MathAbs": MathAbs,
|
||||
"Basic data handling: MathAcos": MathAcos,
|
||||
"Basic data handling: MathAsin": MathAsin,
|
||||
"Basic data handling: MathAtan": MathAtan,
|
||||
"Basic data handling: MathAtan2": MathAtan2,
|
||||
"Basic data handling: MathSqrt": MathSqrt,
|
||||
"Basic data handling: MathCeil": MathCeil,
|
||||
"Basic data handling: MathCos": MathCos,
|
||||
"Basic data handling: MathDegrees": MathDegrees,
|
||||
"Basic data handling: MathE": MathE,
|
||||
"Basic data handling: MathExp": MathExp,
|
||||
"Basic data handling: MathFloor": MathFloor,
|
||||
"Basic data handling: MathLog": MathLog,
|
||||
"Basic data handling: MathLog10": MathLog10,
|
||||
"Basic data handling: MathDegrees": MathDegrees,
|
||||
"Basic data handling: MathRadians": MathRadians,
|
||||
"Basic data handling: MathFloor": MathFloor,
|
||||
"Basic data handling: MathCeil": MathCeil,
|
||||
"Basic data handling: MathAbs": MathAbs,
|
||||
"Basic data handling: MathPi": MathPi,
|
||||
"Basic data handling: MathE": MathE,
|
||||
"Basic data handling: MathMin": MathMin,
|
||||
"Basic data handling: MathMax": MathMax,
|
||||
"Basic data handling: MathMin": MathMin,
|
||||
"Basic data handling: MathPi": MathPi,
|
||||
"Basic data handling: MathRadians": MathRadians,
|
||||
"Basic data handling: MathSin": MathSin,
|
||||
"Basic data handling: MathSqrt": MathSqrt,
|
||||
"Basic data handling: MathTan": MathTan,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"Basic data handling: MathSin": "sin",
|
||||
"Basic data handling: MathCos": "cos",
|
||||
"Basic data handling: MathTan": "tan",
|
||||
"Basic data handling: MathAsin": "asin",
|
||||
"Basic data handling: MathAbs": "abs",
|
||||
"Basic data handling: MathAcos": "acos",
|
||||
"Basic data handling: MathAsin": "asin",
|
||||
"Basic data handling: MathAtan": "atan",
|
||||
"Basic data handling: MathAtan2": "atan2",
|
||||
"Basic data handling: MathSqrt": "sqrt",
|
||||
"Basic data handling: MathCeil": "ceil",
|
||||
"Basic data handling: MathCos": "cos",
|
||||
"Basic data handling: MathDegrees": "degrees",
|
||||
"Basic data handling: MathE": "e (2.71828)",
|
||||
"Basic data handling: MathExp": "exp",
|
||||
"Basic data handling: MathFloor": "floor",
|
||||
"Basic data handling: MathLog": "log",
|
||||
"Basic data handling: MathLog10": "log10",
|
||||
"Basic data handling: MathDegrees": "degrees",
|
||||
"Basic data handling: MathRadians": "radians",
|
||||
"Basic data handling: MathFloor": "floor",
|
||||
"Basic data handling: MathCeil": "ceil",
|
||||
"Basic data handling: MathAbs": "abs",
|
||||
"Basic data handling: MathPi": "pi",
|
||||
"Basic data handling: MathE": "e",
|
||||
"Basic data handling: MathMin": "min",
|
||||
"Basic data handling: MathMax": "max",
|
||||
"Basic data handling: MathMin": "min",
|
||||
"Basic data handling: MathPi": "pi (3.14159)",
|
||||
"Basic data handling: MathRadians": "radians",
|
||||
"Basic data handling: MathSin": "sin",
|
||||
"Basic data handling: MathSqrt": "sqrt",
|
||||
"Basic data handling: MathTan": "tan",
|
||||
}
|
||||
|
||||
@@ -4,6 +4,341 @@ import glob
|
||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
||||
|
||||
|
||||
class PathAbspath(ComfyNodeABC):
|
||||
"""
|
||||
Returns the absolute path of a file or directory.
|
||||
|
||||
This node takes a path and returns its absolute (full) path
|
||||
by resolving any relative path components and symbolic links.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"path": (IO.STRING, {"default": ""}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
RETURN_NAMES = ("absolute_path",)
|
||||
CATEGORY = "Basic/path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "get_abspath"
|
||||
|
||||
def get_abspath(self, path: str) -> tuple[str]:
|
||||
return (os.path.abspath(path),)
|
||||
|
||||
|
||||
class PathBasename(ComfyNodeABC):
|
||||
"""
|
||||
Returns the base name of a path.
|
||||
|
||||
This node extracts the filename component from a path,
|
||||
removing any directory information.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"path": (IO.STRING, {"default": ""}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
RETURN_NAMES = ("basename",)
|
||||
CATEGORY = "Basic/path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "get_basename"
|
||||
|
||||
def get_basename(self, path: str) -> tuple[str]:
|
||||
return (os.path.basename(path),)
|
||||
|
||||
|
||||
class PathCommonPrefix(ComfyNodeABC):
|
||||
"""
|
||||
Finds the common prefix of multiple paths.
|
||||
|
||||
This node returns the longest common leading component of the given paths.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"path1": (IO.STRING, {"default": ""}),
|
||||
},
|
||||
"optional": {
|
||||
"path2": (IO.STRING, {"default": ""}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
RETURN_NAMES = ("common_prefix",)
|
||||
CATEGORY = "Basic/path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "get_common_prefix"
|
||||
|
||||
def get_common_prefix(self, path1: str, path2: str = "") -> tuple[str]:
|
||||
paths = [p for p in [path1, path2] if p]
|
||||
return (os.path.commonprefix(paths),)
|
||||
|
||||
|
||||
class PathDirname(ComfyNodeABC):
|
||||
"""
|
||||
Returns the directory name of a path.
|
||||
|
||||
This node extracts the directory component from a path,
|
||||
removing the filename.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"path": (IO.STRING, {"default": ""}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
RETURN_NAMES = ("dirname",)
|
||||
CATEGORY = "Basic/path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "get_dirname"
|
||||
|
||||
def get_dirname(self, path: str) -> tuple[str]:
|
||||
return (os.path.dirname(path),)
|
||||
|
||||
|
||||
class PathExists(ComfyNodeABC):
|
||||
"""
|
||||
Checks if a path exists in the filesystem.
|
||||
|
||||
This node returns True if the path exists (either as a file or a directory),
|
||||
and False otherwise.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"path": (IO.STRING, {"default": ""}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.BOOLEAN,)
|
||||
RETURN_NAMES = ("exists",)
|
||||
CATEGORY = "Basic/path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "check_exists"
|
||||
|
||||
def check_exists(self, path: str) -> tuple[bool]:
|
||||
return (os.path.exists(path),)
|
||||
|
||||
|
||||
class PathExpandVars(ComfyNodeABC):
|
||||
"""
|
||||
Expands environment variables in a path.
|
||||
|
||||
This node replaces environment variables in a path with their values.
|
||||
For example, $HOME or ${HOME} on Unix, or %USERPROFILE% on Windows.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"path": (IO.STRING, {"default": ""}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
RETURN_NAMES = ("expanded_path",)
|
||||
CATEGORY = "Basic/path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "expand_vars"
|
||||
|
||||
def expand_vars(self, path: str) -> tuple[str]:
|
||||
return (os.path.expandvars(path),)
|
||||
|
||||
|
||||
class PathGetCwd(ComfyNodeABC):
|
||||
"""
|
||||
Returns the current working directory.
|
||||
|
||||
This node returns the current working directory as an absolute path.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {"required": {}}
|
||||
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
RETURN_NAMES = ("current_directory",)
|
||||
CATEGORY = "Basic/path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "get_cwd"
|
||||
|
||||
def get_cwd(self) -> tuple[str]:
|
||||
return (os.getcwd(),)
|
||||
|
||||
|
||||
class PathGetExtension(ComfyNodeABC):
|
||||
"""
|
||||
Returns the extension of a file.
|
||||
|
||||
This node extracts the file extension from a path,
|
||||
including the dot (e.g., '.txt').
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"path": (IO.STRING, {"default": ""}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
RETURN_NAMES = ("extension",)
|
||||
CATEGORY = "Basic/path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "get_extension"
|
||||
|
||||
def get_extension(self, path: str) -> tuple[str]:
|
||||
return (os.path.splitext(path)[1],)
|
||||
|
||||
|
||||
class PathGetSize(ComfyNodeABC):
|
||||
"""
|
||||
Returns the size of a file in bytes.
|
||||
|
||||
This node returns the size in bytes of the file at the given path.
|
||||
Raises an error if the path doesn't exist or isn't a file.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"path": (IO.STRING, {"default": ""}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.INT,)
|
||||
RETURN_NAMES = ("size_bytes",)
|
||||
CATEGORY = "Basic/path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "get_size"
|
||||
|
||||
def get_size(self, path: str) -> tuple[int]:
|
||||
if not os.path.exists(path):
|
||||
raise FileNotFoundError(f"Basic data handling: Path does not exist: {path}")
|
||||
if not os.path.isfile(path):
|
||||
raise ValueError(f"Basic data handling: Path is not a file: {path}")
|
||||
return (os.path.getsize(path),)
|
||||
|
||||
|
||||
class PathGlob(ComfyNodeABC):
|
||||
"""
|
||||
Finds paths matching a pattern.
|
||||
|
||||
This node returns a list of paths matching the given pattern.
|
||||
The pattern follows shell-style wildcard rules:
|
||||
* - matches any number of characters
|
||||
? - matches a single character
|
||||
[seq] - matches any character in seq
|
||||
[!seq] - matches any character not in seq
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"pattern": (IO.STRING, {"default": "*.txt"}),
|
||||
},
|
||||
"optional": {
|
||||
"recursive": (IO.BOOLEAN, {"default": False}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
RETURN_NAMES = ("matching_paths",)
|
||||
CATEGORY = "Basic/path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "glob_paths"
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
def glob_paths(self, pattern: str, recursive: bool = False) -> tuple[list[str]]:
|
||||
return (glob.glob(pattern, recursive=recursive),)
|
||||
|
||||
|
||||
class PathIsAbsolute(ComfyNodeABC):
|
||||
"""
|
||||
Checks if a path is absolute.
|
||||
|
||||
This node returns True if the path is absolute (begins at the root directory),
|
||||
and False if it's relative.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"path": (IO.STRING, {"default": ""}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.BOOLEAN,)
|
||||
RETURN_NAMES = ("is_absolute",)
|
||||
CATEGORY = "Basic/path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "check_is_absolute"
|
||||
|
||||
def check_is_absolute(self, path: str) -> tuple[bool]:
|
||||
return (os.path.isabs(path),)
|
||||
|
||||
|
||||
class PathIsDir(ComfyNodeABC):
|
||||
"""
|
||||
Checks if a path points to a directory.
|
||||
|
||||
This node returns True if the path exists and is a directory,
|
||||
and False otherwise.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"path": (IO.STRING, {"default": ""}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.BOOLEAN,)
|
||||
RETURN_NAMES = ("is_dir",)
|
||||
CATEGORY = "Basic/path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "check_is_dir"
|
||||
|
||||
def check_is_dir(self, path: str) -> tuple[bool]:
|
||||
return (os.path.isdir(path),)
|
||||
|
||||
|
||||
class PathIsFile(ComfyNodeABC):
|
||||
"""
|
||||
Checks if a path points to a file.
|
||||
|
||||
This node returns True if the path exists and is a regular file,
|
||||
and False otherwise.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"path": (IO.STRING, {"default": ""}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.BOOLEAN,)
|
||||
RETURN_NAMES = ("is_file",)
|
||||
CATEGORY = "Basic/path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "check_is_file"
|
||||
|
||||
def check_is_file(self, path: str) -> tuple[bool]:
|
||||
return (os.path.isfile(path),)
|
||||
|
||||
|
||||
class PathJoin(ComfyNodeABC):
|
||||
"""
|
||||
Joins multiple path components into a single path.
|
||||
@@ -34,258 +369,51 @@ class PathJoin(ComfyNodeABC):
|
||||
return (str(os.path.join(*paths)),)
|
||||
|
||||
|
||||
class PathAbspath(ComfyNodeABC):
|
||||
class PathListDir(ComfyNodeABC):
|
||||
"""
|
||||
Returns the absolute path of a file or directory.
|
||||
Lists the contents of a directory.
|
||||
|
||||
This node takes a path and returns its absolute (full) path
|
||||
by resolving any relative path components and symbolic links.
|
||||
This node returns a list of files and directories in the specified path.
|
||||
If 'files_only' is True, it only returns files.
|
||||
If 'dirs_only' is True, it only returns directories.
|
||||
If both are False, it returns all contents.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"path": (IO.STRING, {"default": ""}),
|
||||
},
|
||||
"optional": {
|
||||
"files_only": (IO.BOOLEAN, {"default": False}),
|
||||
"dirs_only": (IO.BOOLEAN, {"default": False}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
RETURN_NAMES = ("absolute_path",)
|
||||
RETURN_NAMES = ("entries",)
|
||||
CATEGORY = "Basic/path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "get_abspath"
|
||||
FUNCTION = "list_directory"
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
def get_abspath(self, path: str) -> tuple[str]:
|
||||
return (os.path.abspath(path),)
|
||||
def list_directory(self, path: str, files_only: str = False, dirs_only: str = False) -> tuple[list[str]]:
|
||||
if not path:
|
||||
path = os.getcwd()
|
||||
|
||||
|
||||
class PathExists(ComfyNodeABC):
|
||||
"""
|
||||
Checks if a path exists in the filesystem.
|
||||
|
||||
This node returns True if the path exists (either as a file or a directory),
|
||||
and False otherwise.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"path": (IO.STRING, {"default": ""}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.BOOLEAN,)
|
||||
RETURN_NAMES = ("exists",)
|
||||
CATEGORY = "Basic/path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "check_exists"
|
||||
|
||||
def check_exists(self, path: str) -> tuple[bool]:
|
||||
return (os.path.exists(path),)
|
||||
|
||||
|
||||
class PathIsFile(ComfyNodeABC):
|
||||
"""
|
||||
Checks if a path points to a file.
|
||||
|
||||
This node returns True if the path exists and is a regular file,
|
||||
and False otherwise.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"path": (IO.STRING, {"default": ""}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.BOOLEAN,)
|
||||
RETURN_NAMES = ("is_file",)
|
||||
CATEGORY = "Basic/path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "check_is_file"
|
||||
|
||||
def check_is_file(self, path: str) -> tuple[bool]:
|
||||
return (os.path.isfile(path),)
|
||||
|
||||
|
||||
class PathIsDir(ComfyNodeABC):
|
||||
"""
|
||||
Checks if a path points to a directory.
|
||||
|
||||
This node returns True if the path exists and is a directory,
|
||||
and False otherwise.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"path": (IO.STRING, {"default": ""}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.BOOLEAN,)
|
||||
RETURN_NAMES = ("is_dir",)
|
||||
CATEGORY = "Basic/path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "check_is_dir"
|
||||
|
||||
def check_is_dir(self, path: str) -> tuple[bool]:
|
||||
return (os.path.isdir(path),)
|
||||
|
||||
|
||||
class PathGetSize(ComfyNodeABC):
|
||||
"""
|
||||
Returns the size of a file in bytes.
|
||||
|
||||
This node returns the size in bytes of the file at the given path.
|
||||
Raises an error if the path doesn't exist or isn't a file.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"path": (IO.STRING, {"default": ""}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.INT,)
|
||||
RETURN_NAMES = ("size_bytes",)
|
||||
CATEGORY = "Basic/path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "get_size"
|
||||
|
||||
def get_size(self, path: str) -> tuple[int]:
|
||||
if not os.path.exists(path):
|
||||
raise FileNotFoundError(f"Basic data handling: Path does not exist: {path}")
|
||||
if not os.path.isfile(path):
|
||||
raise ValueError(f"Basic data handling: Path is not a file: {path}")
|
||||
return (os.path.getsize(path),)
|
||||
raise FileNotFoundError(f"Directory does not exist: {path}")
|
||||
if not os.path.isdir(path):
|
||||
raise NotADirectoryError(f"Basic data handling: Path is not a directory: {path}")
|
||||
|
||||
entries = os.listdir(path)
|
||||
|
||||
class PathSplit(ComfyNodeABC):
|
||||
"""
|
||||
Splits a path into directory and filename components.
|
||||
if files_only:
|
||||
entries = [e for e in entries if os.path.isfile(os.path.join(path, e))]
|
||||
elif dirs_only:
|
||||
entries = [e for e in entries if os.path.isdir(os.path.join(path, e))]
|
||||
|
||||
This node takes a path and returns a tuple containing the directory path
|
||||
and the filename.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"path": (IO.STRING, {"default": ""}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.STRING, IO.STRING)
|
||||
RETURN_NAMES = ("directory", "filename")
|
||||
CATEGORY = "Basic/path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "split_path"
|
||||
|
||||
def split_path(self, path: str) -> tuple[str, str]:
|
||||
return os.path.split(path)
|
||||
|
||||
|
||||
class PathSplitExt(ComfyNodeABC):
|
||||
"""
|
||||
Splits a path into name and extension components.
|
||||
|
||||
This node takes a path and returns a tuple containing the path without
|
||||
the extension and the extension (including the dot).
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"path": (IO.STRING, {"default": ""}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.STRING, IO.STRING)
|
||||
RETURN_NAMES = ("path_without_ext", "extension")
|
||||
CATEGORY = "Basic/path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "split_ext"
|
||||
|
||||
def split_ext(self, path: str) -> tuple[str, str]:
|
||||
return os.path.splitext(path)
|
||||
|
||||
|
||||
class PathBasename(ComfyNodeABC):
|
||||
"""
|
||||
Returns the base name of a path.
|
||||
|
||||
This node extracts the filename component from a path,
|
||||
removing any directory information.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"path": (IO.STRING, {"default": ""}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
RETURN_NAMES = ("basename",)
|
||||
CATEGORY = "Basic/path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "get_basename"
|
||||
|
||||
def get_basename(self, path: str) -> tuple[str]:
|
||||
return (os.path.basename(path),)
|
||||
|
||||
|
||||
class PathDirname(ComfyNodeABC):
|
||||
"""
|
||||
Returns the directory name of a path.
|
||||
|
||||
This node extracts the directory component from a path,
|
||||
removing the filename.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"path": (IO.STRING, {"default": ""}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
RETURN_NAMES = ("dirname",)
|
||||
CATEGORY = "Basic/path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "get_dirname"
|
||||
|
||||
def get_dirname(self, path: str) -> tuple[str]:
|
||||
return (os.path.dirname(path),)
|
||||
|
||||
|
||||
class PathGetExtension(ComfyNodeABC):
|
||||
"""
|
||||
Returns the extension of a file.
|
||||
|
||||
This node extracts the file extension from a path,
|
||||
including the dot (e.g., '.txt').
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"path": (IO.STRING, {"default": ""}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
RETURN_NAMES = ("extension",)
|
||||
CATEGORY = "Basic/path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "get_extension"
|
||||
|
||||
def get_extension(self, path: str) -> tuple[str]:
|
||||
return (os.path.splitext(path)[1],)
|
||||
return (entries,)
|
||||
|
||||
|
||||
class PathNormalize(ComfyNodeABC):
|
||||
@@ -344,45 +472,12 @@ class PathRelative(ComfyNodeABC):
|
||||
return (os.path.relpath(path, start),)
|
||||
|
||||
|
||||
class PathGlob(ComfyNodeABC):
|
||||
class PathSplit(ComfyNodeABC):
|
||||
"""
|
||||
Finds paths matching a pattern.
|
||||
Splits a path into directory and filename components.
|
||||
|
||||
This node returns a list of paths matching the given pattern.
|
||||
The pattern follows shell-style wildcard rules:
|
||||
* - matches any number of characters
|
||||
? - matches a single character
|
||||
[seq] - matches any character in seq
|
||||
[!seq] - matches any character not in seq
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"pattern": (IO.STRING, {"default": "*.txt"}),
|
||||
},
|
||||
"optional": {
|
||||
"recursive": (IO.BOOLEAN, {"default": False}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
RETURN_NAMES = ("matching_paths",)
|
||||
CATEGORY = "Basic/path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "glob_paths"
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
def glob_paths(self, pattern: str, recursive: bool = False) -> tuple[list[str]]:
|
||||
return (glob.glob(pattern, recursive=recursive),)
|
||||
|
||||
|
||||
class PathExpandVars(ComfyNodeABC):
|
||||
"""
|
||||
Expands environment variables in a path.
|
||||
|
||||
This node replaces environment variables in a path with their values.
|
||||
For example, $HOME or ${HOME} on Unix, or %USERPROFILE% on Windows.
|
||||
This node takes a path and returns a tuple containing the directory path
|
||||
and the filename.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -392,89 +487,22 @@ class PathExpandVars(ComfyNodeABC):
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
RETURN_NAMES = ("expanded_path",)
|
||||
RETURN_TYPES = (IO.STRING, IO.STRING)
|
||||
RETURN_NAMES = ("directory", "filename")
|
||||
CATEGORY = "Basic/path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "expand_vars"
|
||||
FUNCTION = "split_path"
|
||||
|
||||
def expand_vars(self, path: str) -> tuple[str]:
|
||||
return (os.path.expandvars(path),)
|
||||
def split_path(self, path: str) -> tuple[str, str]:
|
||||
return os.path.split(path)
|
||||
|
||||
|
||||
class PathGetCwd(ComfyNodeABC):
|
||||
class PathSplitExt(ComfyNodeABC):
|
||||
"""
|
||||
Returns the current working directory.
|
||||
Splits a path into name and extension components.
|
||||
|
||||
This node returns the current working directory as an absolute path.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {"required": {}}
|
||||
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
RETURN_NAMES = ("current_directory",)
|
||||
CATEGORY = "Basic/path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "get_cwd"
|
||||
|
||||
def get_cwd(self) -> tuple[str]:
|
||||
return (os.getcwd(),)
|
||||
|
||||
|
||||
class PathListDir(ComfyNodeABC):
|
||||
"""
|
||||
Lists the contents of a directory.
|
||||
|
||||
This node returns a list of files and directories in the specified path.
|
||||
If 'files_only' is True, it only returns files.
|
||||
If 'dirs_only' is True, it only returns directories.
|
||||
If both are False, it returns all contents.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"path": (IO.STRING, {"default": ""}),
|
||||
},
|
||||
"optional": {
|
||||
"files_only": (IO.BOOLEAN, {"default": False}),
|
||||
"dirs_only": (IO.BOOLEAN, {"default": False}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
RETURN_NAMES = ("entries",)
|
||||
CATEGORY = "Basic/path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "list_directory"
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
def list_directory(self, path: str, files_only: str = False, dirs_only: str = False) -> tuple[list[str]]:
|
||||
if not path:
|
||||
path = os.getcwd()
|
||||
|
||||
if not os.path.exists(path):
|
||||
raise FileNotFoundError(f"Directory does not exist: {path}")
|
||||
if not os.path.isdir(path):
|
||||
raise NotADirectoryError(f"Basic data handling: Path is not a directory: {path}")
|
||||
|
||||
entries = os.listdir(path)
|
||||
|
||||
if files_only:
|
||||
entries = [e for e in entries if os.path.isfile(os.path.join(path, e))]
|
||||
elif dirs_only:
|
||||
entries = [e for e in entries if os.path.isdir(os.path.join(path, e))]
|
||||
|
||||
return (entries,)
|
||||
|
||||
|
||||
class PathIsAbsolute(ComfyNodeABC):
|
||||
"""
|
||||
Checks if a path is absolute.
|
||||
|
||||
This node returns True if the path is absolute (begins at the root directory),
|
||||
and False if it's relative.
|
||||
This node takes a path and returns a tuple containing the path without
|
||||
the extension and the extension (including the dot).
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -484,84 +512,56 @@ class PathIsAbsolute(ComfyNodeABC):
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.BOOLEAN,)
|
||||
RETURN_NAMES = ("is_absolute",)
|
||||
RETURN_TYPES = (IO.STRING, IO.STRING)
|
||||
RETURN_NAMES = ("path_without_ext", "extension")
|
||||
CATEGORY = "Basic/path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "check_is_absolute"
|
||||
FUNCTION = "split_ext"
|
||||
|
||||
def check_is_absolute(self, path: str) -> tuple[bool]:
|
||||
return (os.path.isabs(path),)
|
||||
|
||||
|
||||
class PathCommonPrefix(ComfyNodeABC):
|
||||
"""
|
||||
Finds the common prefix of multiple paths.
|
||||
|
||||
This node returns the longest common leading component of the given paths.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"path1": (IO.STRING, {"default": ""}),
|
||||
},
|
||||
"optional": {
|
||||
"path2": (IO.STRING, {"default": ""}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
RETURN_NAMES = ("common_prefix",)
|
||||
CATEGORY = "Basic/path"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "get_common_prefix"
|
||||
|
||||
def get_common_prefix(self, path1: str, path2: str = "") -> tuple[str]:
|
||||
paths = [p for p in [path1, path2] if p]
|
||||
return (os.path.commonprefix(paths),)
|
||||
def split_ext(self, path: str) -> tuple[str, str]:
|
||||
return os.path.splitext(path)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"Basic data handling: PathJoin": PathJoin,
|
||||
"Basic data handling: PathAbspath": PathAbspath,
|
||||
"Basic data handling: PathExists": PathExists,
|
||||
"Basic data handling: PathIsFile": PathIsFile,
|
||||
"Basic data handling: PathIsDir": PathIsDir,
|
||||
"Basic data handling: PathGetSize": PathGetSize,
|
||||
"Basic data handling: PathSplit": PathSplit,
|
||||
"Basic data handling: PathSplitExt": PathSplitExt,
|
||||
"Basic data handling: PathBasename": PathBasename,
|
||||
"Basic data handling: PathCommonPrefix": PathCommonPrefix,
|
||||
"Basic data handling: PathDirname": PathDirname,
|
||||
"Basic data handling: PathGetExtension": PathGetExtension,
|
||||
"Basic data handling: PathNormalize": PathNormalize,
|
||||
"Basic data handling: PathRelative": PathRelative,
|
||||
"Basic data handling: PathGlob": PathGlob,
|
||||
"Basic data handling: PathExists": PathExists,
|
||||
"Basic data handling: PathExpandVars": PathExpandVars,
|
||||
"Basic data handling: PathGetCwd": PathGetCwd,
|
||||
"Basic data handling: PathListDir": PathListDir,
|
||||
"Basic data handling: PathGetExtension": PathGetExtension,
|
||||
"Basic data handling: PathGetSize": PathGetSize,
|
||||
"Basic data handling: PathGlob": PathGlob,
|
||||
"Basic data handling: PathIsAbsolute": PathIsAbsolute,
|
||||
"Basic data handling: PathCommonPrefix": PathCommonPrefix,
|
||||
"Basic data handling: PathIsDir": PathIsDir,
|
||||
"Basic data handling: PathIsFile": PathIsFile,
|
||||
"Basic data handling: PathJoin": PathJoin,
|
||||
"Basic data handling: PathListDir": PathListDir,
|
||||
"Basic data handling: PathNormalize": PathNormalize,
|
||||
"Basic data handling: PathRelative": PathRelative,
|
||||
"Basic data handling: PathSplit": PathSplit,
|
||||
"Basic data handling: PathSplitExt": PathSplitExt,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"Basic data handling: PathJoin": "join",
|
||||
"Basic data handling: PathAbspath": "abspath",
|
||||
"Basic data handling: PathExists": "exists",
|
||||
"Basic data handling: PathIsFile": "is_file",
|
||||
"Basic data handling: PathIsDir": "is_dir",
|
||||
"Basic data handling: PathGetSize": "get_size",
|
||||
"Basic data handling: PathSplit": "split",
|
||||
"Basic data handling: PathSplitExt": "splitext",
|
||||
"Basic data handling: PathBasename": "basename",
|
||||
"Basic data handling: PathCommonPrefix": "common prefix",
|
||||
"Basic data handling: PathDirname": "dirname",
|
||||
"Basic data handling: PathGetExtension": "get_extension",
|
||||
"Basic data handling: PathExists": "exists",
|
||||
"Basic data handling: PathExpandVars": "expand vars",
|
||||
"Basic data handling: PathGetCwd": "get current working directory",
|
||||
"Basic data handling: PathGetExtension": "get extension",
|
||||
"Basic data handling: PathGetSize": "get size",
|
||||
"Basic data handling: PathGlob": "glob",
|
||||
"Basic data handling: PathIsAbsolute": "is absolute",
|
||||
"Basic data handling: PathIsDir": "is dir",
|
||||
"Basic data handling: PathIsFile": "is file",
|
||||
"Basic data handling: PathJoin": "join",
|
||||
"Basic data handling: PathListDir": "list dir",
|
||||
"Basic data handling: PathNormalize": "normalize",
|
||||
"Basic data handling: PathRelative": "relative",
|
||||
"Basic data handling: PathGlob": "glob",
|
||||
"Basic data handling: PathExpandVars": "expand_vars",
|
||||
"Basic data handling: PathGetCwd": "get_cwd",
|
||||
"Basic data handling: PathListDir": "list_dir",
|
||||
"Basic data handling: PathIsAbsolute": "is_absolute",
|
||||
"Basic data handling: PathCommonPrefix": "common_prefix",
|
||||
"Basic data handling: PathSplit": "split",
|
||||
"Basic data handling: PathSplitExt": "splitext",
|
||||
}
|
||||
|
||||
@@ -2,6 +2,77 @@ from inspect import cleandoc
|
||||
import re
|
||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
||||
|
||||
class RegexFindallDataList(ComfyNodeABC):
|
||||
"""
|
||||
Returns all non-overlapping matches of a pattern in the string as a list of strings.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"string": (IO.STRING, {}),
|
||||
"pattern": (IO.STRING, {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
CATEGORY = "Basic/STRING/regex"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "findall"
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
def findall(self, pattern: str, string: str) -> tuple[list[str]]:
|
||||
return (re.findall(pattern, string),)
|
||||
|
||||
|
||||
class RegexFindallList(ComfyNodeABC):
|
||||
"""
|
||||
Returns all non-overlapping matches of a pattern in the string as a list of strings.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"string": (IO.STRING, {}),
|
||||
"pattern": (IO.STRING, {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LIST",)
|
||||
CATEGORY = "Basic/STRING/regex"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "findall"
|
||||
|
||||
def findall(self, pattern: str, string: str) -> tuple[list[str]]:
|
||||
return (re.findall(pattern, string),)
|
||||
|
||||
|
||||
class RegexGroupDict(ComfyNodeABC):
|
||||
"""
|
||||
Searches the string with the given pattern and returns a DICT of named groups.
|
||||
If no match is found, it returns an empty DICT.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"string": (IO.STRING, {}),
|
||||
"pattern": (IO.STRING, {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("DICT",)
|
||||
CATEGORY = "Basic/STRING/regex"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "groupdict"
|
||||
|
||||
def groupdict(self, pattern: str, string: str) -> tuple[dict]:
|
||||
match = re.search(pattern, string)
|
||||
if match:
|
||||
return (match.groupdict(),)
|
||||
return ({},)
|
||||
|
||||
|
||||
class RegexSearchGroupsDataList(ComfyNodeABC):
|
||||
"""
|
||||
Searches the string for a match to the pattern and returns a LIST of match groups.
|
||||
@@ -55,77 +126,6 @@ class RegexSearchGroupsList(ComfyNodeABC):
|
||||
return ([],)
|
||||
|
||||
|
||||
class RegexGroupDict(ComfyNodeABC):
|
||||
"""
|
||||
Searches the string with the given pattern and returns a DICT of named groups.
|
||||
If no match is found, it returns an empty DICT.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"string": (IO.STRING, {}),
|
||||
"pattern": (IO.STRING, {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("DICT",)
|
||||
CATEGORY = "Basic/STRING/regex"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "groupdict"
|
||||
|
||||
def groupdict(self, pattern: str, string: str) -> tuple[dict]:
|
||||
match = re.search(pattern, string)
|
||||
if match:
|
||||
return (match.groupdict(),)
|
||||
return ({},)
|
||||
|
||||
|
||||
class RegexFindallDataList(ComfyNodeABC):
|
||||
"""
|
||||
Returns all non-overlapping matches of a pattern in the string as a list of strings.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"string": (IO.STRING, {}),
|
||||
"pattern": (IO.STRING, {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
CATEGORY = "Basic/STRING/regex"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "findall"
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
def findall(self, pattern: str, string: str) -> tuple[list[str]]:
|
||||
return (re.findall(pattern, string),)
|
||||
|
||||
|
||||
class RegexFindallList(ComfyNodeABC):
|
||||
"""
|
||||
Returns all non-overlapping matches of a pattern in the string as a list of strings.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"string": (IO.STRING, {}),
|
||||
"pattern": (IO.STRING, {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LIST",)
|
||||
CATEGORY = "Basic/STRING/regex"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "findall"
|
||||
|
||||
def findall(self, pattern: str, string: str) -> tuple[list[str]]:
|
||||
return (re.findall(pattern, string),)
|
||||
|
||||
|
||||
class RegexSplitDataList(ComfyNodeABC):
|
||||
"""
|
||||
Splits the string at each match of the pattern and returns a list of substrings.
|
||||
@@ -220,11 +220,11 @@ class RegexTest(ComfyNodeABC):
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"Basic data handling: RegexSearchGroupsDataList": RegexSearchGroupsDataList,
|
||||
"Basic data handling: RegexSearchGroupsList": RegexSearchGroupsList,
|
||||
"Basic data handling: RegexGroupDict": RegexGroupDict,
|
||||
"Basic data handling: RegexFindallDataList": RegexFindallDataList,
|
||||
"Basic data handling: RegexFindallList": RegexFindallList,
|
||||
"Basic data handling: RegexGroupDict": RegexGroupDict,
|
||||
"Basic data handling: RegexSearchGroupsDataList": RegexSearchGroupsDataList,
|
||||
"Basic data handling: RegexSearchGroupsList": RegexSearchGroupsList,
|
||||
"Basic data handling: RegexSplitDataList": RegexSplitDataList,
|
||||
"Basic data handling: RegexSplitList": RegexSplitList,
|
||||
"Basic data handling: RegexSub": RegexSub,
|
||||
@@ -232,11 +232,11 @@ NODE_CLASS_MAPPINGS = {
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"Basic data handling: RegexSearchGroupsDataList": "search groups (data list)",
|
||||
"Basic data handling: RegexSearchGroupsList": "search groups (LIST)",
|
||||
"Basic data handling: RegexGroupDict": "search named groups",
|
||||
"Basic data handling: RegexFindallDataList": "find all (data list)",
|
||||
"Basic data handling: RegexFindallList": "find all (LIST)",
|
||||
"Basic data handling: RegexGroupDict": "search named groups",
|
||||
"Basic data handling: RegexSearchGroupsDataList": "search groups (data list)",
|
||||
"Basic data handling: RegexSearchGroupsList": "search groups (LIST)",
|
||||
"Basic data handling: RegexSplitDataList": "split (data list)",
|
||||
"Basic data handling: RegexSplitList": "split (LIST)",
|
||||
"Basic data handling: RegexSub": "substitute",
|
||||
|
||||
@@ -29,36 +29,57 @@ class SetAdd(ComfyNodeABC):
|
||||
return (result,)
|
||||
|
||||
|
||||
class SetRemove(ComfyNodeABC):
|
||||
class SetContains(ComfyNodeABC):
|
||||
"""
|
||||
Removes an item from a SET.
|
||||
Checks if a SET contains a specified value.
|
||||
|
||||
This node takes a SET and any item as inputs, then returns a new SET
|
||||
with the item removed and a success indicator. If the item is not present,
|
||||
the original SET is returned with success set to False.
|
||||
This node takes a SET and a value as inputs, then returns True if the value
|
||||
is present in the SET, and False otherwise.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"set": ("SET", {}),
|
||||
"item": (IO.ANY, {}),
|
||||
"value": (IO.ANY, {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("SET", IO.BOOLEAN)
|
||||
RETURN_NAMES = ("set", "success")
|
||||
RETURN_TYPES = (IO.BOOLEAN,)
|
||||
RETURN_NAMES = ("contains",)
|
||||
CATEGORY = "Basic/SET"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "remove"
|
||||
FUNCTION = "contains"
|
||||
|
||||
def remove(self, set: set[Any], item: Any) -> tuple[set[Any], bool]:
|
||||
result = set.copy()
|
||||
try:
|
||||
result.remove(item)
|
||||
return result, True
|
||||
except KeyError:
|
||||
return result, False
|
||||
def contains(self, set: set[Any], value: Any) -> tuple[bool]:
|
||||
return (value in set,)
|
||||
|
||||
|
||||
class SetDifference(ComfyNodeABC):
|
||||
"""
|
||||
Returns the difference between two SETs.
|
||||
|
||||
This node takes two SETs as input and returns a new SET containing
|
||||
elements in the first SET but not in the second SET.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"set1": ("SET", {}),
|
||||
"set2": ("SET", {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("SET",)
|
||||
CATEGORY = "Basic/SET"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "difference"
|
||||
|
||||
def difference(self, set1: set[Any], set2: set[Any]) -> tuple[set[Any]]:
|
||||
result = set1.copy()
|
||||
result.difference_update(set2)
|
||||
return (result,)
|
||||
|
||||
|
||||
class SetDiscard(ComfyNodeABC):
|
||||
@@ -88,75 +109,6 @@ class SetDiscard(ComfyNodeABC):
|
||||
return (result,)
|
||||
|
||||
|
||||
class SetPop(ComfyNodeABC):
|
||||
"""
|
||||
Removes and returns an arbitrary item from a SET.
|
||||
|
||||
This node takes a SET as input and returns both the new SET
|
||||
with an arbitrary item removed and the removed item.
|
||||
When the SET is empty, the item is None.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"set": ("SET", {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("SET", IO.ANY)
|
||||
RETURN_NAMES = ("set", "item")
|
||||
CATEGORY = "Basic/SET"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "pop"
|
||||
|
||||
def pop(self, set: set[Any]) -> tuple[set[Any], Any]:
|
||||
result = set.copy()
|
||||
try:
|
||||
item = result.pop()
|
||||
return result, item
|
||||
except KeyError:
|
||||
return result, None
|
||||
|
||||
|
||||
class SetUnion(ComfyNodeABC):
|
||||
"""
|
||||
Returns the union of two or more SETs.
|
||||
|
||||
This node takes multiple SETs as input and returns a new SET containing
|
||||
all elements from all the input SETs.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"set1": ("SET", {}),
|
||||
"set2": ("SET", {}),
|
||||
},
|
||||
"optional": {
|
||||
"set3": ("SET", {}),
|
||||
"set4": ("SET", {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("SET",)
|
||||
CATEGORY = "Basic/SET"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "union"
|
||||
|
||||
def union(self, set1: set[Any], set2: set[Any], set3=None, set4=None) -> tuple[set[Any]]:
|
||||
result = set1.copy()
|
||||
result.update(set2)
|
||||
|
||||
if set3 is not None:
|
||||
result.update(set3)
|
||||
|
||||
if set4 is not None:
|
||||
result.update(set4)
|
||||
|
||||
return (result,)
|
||||
|
||||
|
||||
class SetIntersection(ComfyNodeABC):
|
||||
"""
|
||||
Returns the intersection of two or more SETs.
|
||||
@@ -195,12 +147,11 @@ class SetIntersection(ComfyNodeABC):
|
||||
return (result,)
|
||||
|
||||
|
||||
class SetDifference(ComfyNodeABC):
|
||||
class SetIsDisjoint(ComfyNodeABC):
|
||||
"""
|
||||
Returns the difference between two SETs.
|
||||
Checks if two SETs have no elements in common.
|
||||
|
||||
This node takes two SETs as input and returns a new SET containing
|
||||
elements in the first SET but not in the second SET.
|
||||
This node takes two SETs as input and returns True if they have no elements in common.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -211,42 +162,14 @@ class SetDifference(ComfyNodeABC):
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("SET",)
|
||||
RETURN_TYPES = (IO.BOOLEAN,)
|
||||
RETURN_NAMES = ("is_disjoint",)
|
||||
CATEGORY = "Basic/SET"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "difference"
|
||||
FUNCTION = "is_disjoint"
|
||||
|
||||
def difference(self, set1: set[Any], set2: set[Any]) -> tuple[set[Any]]:
|
||||
result = set1.copy()
|
||||
result.difference_update(set2)
|
||||
return (result,)
|
||||
|
||||
|
||||
class SetSymmetricDifference(ComfyNodeABC):
|
||||
"""
|
||||
Returns the symmetric difference between two SETs.
|
||||
|
||||
This node takes two SETs as input and returns a new SET containing
|
||||
elements in either SET but not in both.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"set1": ("SET", {}),
|
||||
"set2": ("SET", {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("SET",)
|
||||
CATEGORY = "Basic/SET"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "symmetric_difference"
|
||||
|
||||
def symmetric_difference(self, set1: set[Any], set2: set[Any]) -> tuple[set[Any]]:
|
||||
result = set1.copy()
|
||||
result.symmetric_difference_update(set2)
|
||||
return (result,)
|
||||
def is_disjoint(self, set1: set[Any], set2: set[Any]) -> tuple[bool]:
|
||||
return (set1.isdisjoint(set2),)
|
||||
|
||||
|
||||
class SetIsSubset(ComfyNodeABC):
|
||||
@@ -301,57 +224,6 @@ class SetIsSuperset(ComfyNodeABC):
|
||||
return (set1.issuperset(set2),)
|
||||
|
||||
|
||||
class SetIsDisjoint(ComfyNodeABC):
|
||||
"""
|
||||
Checks if two SETs have no elements in common.
|
||||
|
||||
This node takes two SETs as input and returns True if they have no elements in common.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"set1": ("SET", {}),
|
||||
"set2": ("SET", {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.BOOLEAN,)
|
||||
RETURN_NAMES = ("is_disjoint",)
|
||||
CATEGORY = "Basic/SET"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "is_disjoint"
|
||||
|
||||
def is_disjoint(self, set1: set[Any], set2: set[Any]) -> tuple[bool]:
|
||||
return (set1.isdisjoint(set2),)
|
||||
|
||||
|
||||
class SetContains(ComfyNodeABC):
|
||||
"""
|
||||
Checks if a SET contains a specified value.
|
||||
|
||||
This node takes a SET and a value as inputs, then returns True if the value
|
||||
is present in the SET, and False otherwise.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"set": ("SET", {}),
|
||||
"value": (IO.ANY, {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.BOOLEAN,)
|
||||
RETURN_NAMES = ("contains",)
|
||||
CATEGORY = "Basic/SET"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "contains"
|
||||
|
||||
def contains(self, set: set[Any], value: Any) -> tuple[bool]:
|
||||
return (value in set,)
|
||||
|
||||
|
||||
class SetLength(ComfyNodeABC):
|
||||
"""
|
||||
Returns the number of items in a SET.
|
||||
@@ -376,28 +248,132 @@ class SetLength(ComfyNodeABC):
|
||||
return (len(set),)
|
||||
|
||||
|
||||
class ListToSet(ComfyNodeABC):
|
||||
class SetPop(ComfyNodeABC):
|
||||
"""
|
||||
Converts a LIST into a SET.
|
||||
Removes and returns an arbitrary item from a SET.
|
||||
|
||||
This node takes a LIST input and creates a new SET containing all unique elements
|
||||
from the LIST, removing any duplicates.
|
||||
This node takes a SET as input and returns both the new SET
|
||||
with an arbitrary item removed and the removed item.
|
||||
When the SET is empty, the item is None.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": ("LIST", {}),
|
||||
"set": ("SET", {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("SET", IO.ANY)
|
||||
RETURN_NAMES = ("set", "item")
|
||||
CATEGORY = "Basic/SET"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "pop"
|
||||
|
||||
def pop(self, set: set[Any]) -> tuple[set[Any], Any]:
|
||||
result = set.copy()
|
||||
try:
|
||||
item = result.pop()
|
||||
return result, item
|
||||
except KeyError:
|
||||
return result, None
|
||||
|
||||
|
||||
class SetRemove(ComfyNodeABC):
|
||||
"""
|
||||
Removes an item from a SET.
|
||||
|
||||
This node takes a SET and any item as inputs, then returns a new SET
|
||||
with the item removed and a success indicator. If the item is not present,
|
||||
the original SET is returned with success set to False.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"set": ("SET", {}),
|
||||
"item": (IO.ANY, {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("SET", IO.BOOLEAN)
|
||||
RETURN_NAMES = ("set", "success")
|
||||
CATEGORY = "Basic/SET"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "remove"
|
||||
|
||||
def remove(self, set: set[Any], item: Any) -> tuple[set[Any], bool]:
|
||||
result = set.copy()
|
||||
try:
|
||||
result.remove(item)
|
||||
return result, True
|
||||
except KeyError:
|
||||
return result, False
|
||||
|
||||
|
||||
class SetSymmetricDifference(ComfyNodeABC):
|
||||
"""
|
||||
Returns the symmetric difference between two SETs.
|
||||
|
||||
This node takes two SETs as input and returns a new SET containing
|
||||
elements in either SET but not in both.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"set1": ("SET", {}),
|
||||
"set2": ("SET", {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("SET",)
|
||||
CATEGORY = "Basic/SET"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "convert"
|
||||
FUNCTION = "symmetric_difference"
|
||||
|
||||
def convert(self, list: list[Any]) -> tuple[set[Any]]:
|
||||
return (set(list),)
|
||||
def symmetric_difference(self, set1: set[Any], set2: set[Any]) -> tuple[set[Any]]:
|
||||
result = set1.copy()
|
||||
result.symmetric_difference_update(set2)
|
||||
return (result,)
|
||||
|
||||
|
||||
class SetUnion(ComfyNodeABC):
|
||||
"""
|
||||
Returns the union of two or more SETs.
|
||||
|
||||
This node takes multiple SETs as input and returns a new SET containing
|
||||
all elements from all the input SETs.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"set1": ("SET", {}),
|
||||
"set2": ("SET", {}),
|
||||
},
|
||||
"optional": {
|
||||
"set3": ("SET", {}),
|
||||
"set4": ("SET", {}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("SET",)
|
||||
CATEGORY = "Basic/SET"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "union"
|
||||
|
||||
def union(self, set1: set[Any], set2: set[Any], set3=None, set4=None) -> tuple[set[Any]]:
|
||||
result = set1.copy()
|
||||
result.update(set2)
|
||||
|
||||
if set3 is not None:
|
||||
result.update(set3)
|
||||
|
||||
if set4 is not None:
|
||||
result.update(set4)
|
||||
|
||||
return (result,)
|
||||
|
||||
|
||||
class SetToDataList(ComfyNodeABC):
|
||||
@@ -453,36 +429,36 @@ class SetToList(ComfyNodeABC):
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"Basic data handling: SetAdd": SetAdd,
|
||||
"Basic data handling: SetRemove": SetRemove,
|
||||
"Basic data handling: SetDiscard": SetDiscard,
|
||||
"Basic data handling: SetPop": SetPop,
|
||||
"Basic data handling: SetUnion": SetUnion,
|
||||
"Basic data handling: SetIntersection": SetIntersection,
|
||||
"Basic data handling: SetContains": SetContains,
|
||||
"Basic data handling: SetDifference": SetDifference,
|
||||
"Basic data handling: SetSymmetricDifference": SetSymmetricDifference,
|
||||
"Basic data handling: SetDiscard": SetDiscard,
|
||||
"Basic data handling: SetIntersection": SetIntersection,
|
||||
"Basic data handling: SetIsDisjoint": SetIsDisjoint,
|
||||
"Basic data handling: SetIsSubset": SetIsSubset,
|
||||
"Basic data handling: SetIsSuperset": SetIsSuperset,
|
||||
"Basic data handling: SetIsDisjoint": SetIsDisjoint,
|
||||
"Basic data handling: SetContains": SetContains,
|
||||
"Basic data handling: SetLength": SetLength,
|
||||
"Basic data handling: SetPop": SetPop,
|
||||
"Basic data handling: SetRemove": SetRemove,
|
||||
"Basic data handling: SetSymmetricDifference": SetSymmetricDifference,
|
||||
"Basic data handling: SetUnion": SetUnion,
|
||||
"Basic data handling: SetToDataList": SetToDataList,
|
||||
"Basic data handling: SetToList": SetToList,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"Basic data handling: SetAdd": "add",
|
||||
"Basic data handling: SetRemove": "remove",
|
||||
"Basic data handling: SetDiscard": "discard",
|
||||
"Basic data handling: SetPop": "pop",
|
||||
"Basic data handling: SetUnion": "union",
|
||||
"Basic data handling: SetIntersection": "intersection",
|
||||
"Basic data handling: SetDifference": "difference",
|
||||
"Basic data handling: SetSymmetricDifference": "symmetric_difference",
|
||||
"Basic data handling: SetIsSubset": "is_subset",
|
||||
"Basic data handling: SetIsSuperset": "is_superset",
|
||||
"Basic data handling: SetIsDisjoint": "is_disjoint",
|
||||
"Basic data handling: SetContains": "contains",
|
||||
"Basic data handling: SetDifference": "difference",
|
||||
"Basic data handling: SetDiscard": "discard",
|
||||
"Basic data handling: SetIntersection": "intersection",
|
||||
"Basic data handling: SetIsDisjoint": "is disjoint",
|
||||
"Basic data handling: SetIsSubset": "is subset",
|
||||
"Basic data handling: SetIsSuperset": "is superset",
|
||||
"Basic data handling: SetLength": "length",
|
||||
"Basic data handling: SetPop": "pop",
|
||||
"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: SetToList": "convert to LIST",
|
||||
}
|
||||
|
||||
@@ -314,32 +314,6 @@ class StringFind(ComfyNodeABC):
|
||||
return (string.find(substring, start, end),)
|
||||
|
||||
|
||||
class StringIn(ComfyNodeABC):
|
||||
"""
|
||||
Checks if a string contains a specified substring.
|
||||
|
||||
This node implements the 'in' operator for strings. It checks whether the given substring
|
||||
exists within the input string. Returns True if the substring is found,
|
||||
otherwise False.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"string": (IO.STRING, {"default": "", "defaultInput": True}),
|
||||
"substring": (IO.STRING, {"default": ""}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.BOOLEAN,)
|
||||
CATEGORY = "Basic/STRING"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "contains"
|
||||
|
||||
def contains(self, string, substring):
|
||||
return (substring in string,)
|
||||
|
||||
|
||||
class StringFormatMap(ComfyNodeABC):
|
||||
"""
|
||||
Formats a string using values from a dictionary.
|
||||
@@ -373,6 +347,32 @@ class StringFormatMap(ComfyNodeABC):
|
||||
return (f"Formatting error: {str(e)}",)
|
||||
|
||||
|
||||
class StringIn(ComfyNodeABC):
|
||||
"""
|
||||
Checks if a string contains a specified substring.
|
||||
|
||||
This node implements the 'in' operator for strings. It checks whether the given substring
|
||||
exists within the input string. Returns True if the substring is found,
|
||||
otherwise False.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"string": (IO.STRING, {"default": "", "defaultInput": True}),
|
||||
"substring": (IO.STRING, {"default": ""}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.BOOLEAN,)
|
||||
CATEGORY = "Basic/STRING"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "contains"
|
||||
|
||||
def contains(self, string, substring):
|
||||
return (substring in string,)
|
||||
|
||||
|
||||
class StringIsAlnum(ComfyNodeABC):
|
||||
"""
|
||||
Checks if all characters in the string are alphanumeric.
|
||||
@@ -666,6 +666,85 @@ class StringIsUpper(ComfyNodeABC):
|
||||
return (string.isupper(),)
|
||||
|
||||
|
||||
class StringLength(ComfyNodeABC):
|
||||
"""
|
||||
Returns the length of a string.
|
||||
|
||||
This node calculates and returns the number of characters in the input string.
|
||||
It works the same way as Python's built-in len() function for strings.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"string": (IO.STRING, {"default": "", "defaultInput": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.INT,)
|
||||
CATEGORY = "Basic/STRING"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "length"
|
||||
|
||||
def length(self, string):
|
||||
return (len(string),)
|
||||
|
||||
|
||||
class StringDataListJoin(ComfyNodeABC):
|
||||
"""
|
||||
Joins strings from a data list with a specified separator.
|
||||
|
||||
This node takes a data list of strings and concatenates them, with the specified
|
||||
separator string between each element. The separator is inserted between the strings,
|
||||
not at the beginning or end of the result.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"strings": (IO.STRING, {"forceInput": True}),
|
||||
"sep": (IO.STRING, {"default": " "}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
CATEGORY = "Basic/STRING"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "join"
|
||||
INPUT_IS_LIST = True # The "strings" input accepts a data list
|
||||
|
||||
def join(self, sep, strings):
|
||||
separator = sep[0] # everything comes as a list, so sep is list[str]
|
||||
return (separator.join(strings),)
|
||||
|
||||
|
||||
class StringListJoin(ComfyNodeABC):
|
||||
"""
|
||||
Joins strings from a LIST with a specified separator.
|
||||
|
||||
This node takes a data list of strings and concatenates them, with the specified
|
||||
separator string between each element. The separator is inserted between the strings,
|
||||
not at the beginning or end of the result.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"strings": ("LIST", {"forceInput": True}),
|
||||
"sep": (IO.STRING, {"default": " "}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
CATEGORY = "Basic/STRING"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "join"
|
||||
|
||||
def join(self, sep, strings):
|
||||
separator = sep[0] # everything comes as a list, so sep is list[str]
|
||||
return (separator.join(strings),)
|
||||
|
||||
|
||||
class StringLjust(ComfyNodeABC):
|
||||
"""
|
||||
Left-aligns the string within a field of a given width.
|
||||
@@ -697,6 +776,29 @@ class StringLjust(ComfyNodeABC):
|
||||
return (string.ljust(width, fillchar),)
|
||||
|
||||
|
||||
class StringLower(ComfyNodeABC):
|
||||
"""
|
||||
Converts the string to lowercase.
|
||||
|
||||
This node returns a copy of the string with all uppercase characters converted to lowercase.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"string": (IO.STRING, {"default": "", "defaultInput": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
CATEGORY = "Basic/STRING"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "lower"
|
||||
|
||||
def lower(self, string):
|
||||
return (string.lower(),)
|
||||
|
||||
|
||||
class StringLstrip(ComfyNodeABC):
|
||||
"""
|
||||
Removes leading characters from the string.
|
||||
@@ -777,6 +879,101 @@ class StringRemovesuffix(ComfyNodeABC):
|
||||
return (string.removesuffix(suffix),)
|
||||
|
||||
|
||||
class StringReplace(ComfyNodeABC):
|
||||
"""
|
||||
Replaces occurrences of a substring with another substring.
|
||||
|
||||
This node returns a copy of the string with all occurrences of substring
|
||||
old replaced by new. If the optional argument count is given, only the
|
||||
first count occurrences are replaced.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"string": (IO.STRING, {"default": "", "defaultInput": True}),
|
||||
"old": (IO.STRING, {"default": ""}),
|
||||
"new": (IO.STRING, {"default": ""}),
|
||||
},
|
||||
"optional": {
|
||||
"count": (IO.INT, {"default": -1, "min": -1}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
CATEGORY = "Basic/STRING"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "replace"
|
||||
|
||||
def replace(self, string, old, new, count=-1):
|
||||
return (string.replace(old, new, count),)
|
||||
|
||||
|
||||
class StringRfind(ComfyNodeABC):
|
||||
"""
|
||||
Finds the highest index of the substring in the string.
|
||||
|
||||
This node searches for the last occurrence of the specified substring within the input string.
|
||||
Returns the highest index where the substring is found. If the substring is not found, it returns -1.
|
||||
Optional start and end parameters allow you to limit the search to a specific portion of the string.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"string": (IO.STRING, {"default": "", "defaultInput": True}),
|
||||
"substring": (IO.STRING, {"default": ""}),
|
||||
},
|
||||
"optional": {
|
||||
"start": (IO.INT, {"default": 0, "min": 0}),
|
||||
"end": (IO.INT, {"default": 0, "min": 0}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.INT,)
|
||||
CATEGORY = "Basic/STRING"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "rfind"
|
||||
|
||||
def rfind(self, string, substring, start=0, end=0):
|
||||
# If end is 0 or negative, search to the end of the string
|
||||
if end <= 0:
|
||||
end = len(string)
|
||||
|
||||
return (string.rfind(substring, start, end),)
|
||||
|
||||
|
||||
class StringRjust(ComfyNodeABC):
|
||||
"""
|
||||
Right-aligns the string within a field of a given width.
|
||||
|
||||
This node returns a string right-aligned in a field of the specified width.
|
||||
If fillchar is provided, it is used as the padding character instead of a space.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"string": (IO.STRING, {"default": "", "defaultInput": True}),
|
||||
"width": (IO.INT, {"default": 10, "min": 0}),
|
||||
},
|
||||
"optional": {
|
||||
"fillchar": (IO.STRING, {"default": " "}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
CATEGORY = "Basic/STRING"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "rjust"
|
||||
|
||||
def rjust(self, string, width, fillchar=" "):
|
||||
# Ensure fillchar is only one character
|
||||
if len(fillchar) > 0:
|
||||
fillchar = fillchar[0]
|
||||
return (string.rjust(width, fillchar),)
|
||||
|
||||
|
||||
class StringRsplitDataList(ComfyNodeABC):
|
||||
"""
|
||||
Splits the string at the specified separator from the right.
|
||||
@@ -842,179 +1039,6 @@ class StringRsplitList(ComfyNodeABC):
|
||||
return (string.rsplit(sep, maxsplit),)
|
||||
|
||||
|
||||
class StringLower(ComfyNodeABC):
|
||||
"""
|
||||
Converts the string to lowercase.
|
||||
|
||||
This node returns a copy of the string with all uppercase characters converted to lowercase.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"string": (IO.STRING, {"default": "", "defaultInput": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
CATEGORY = "Basic/STRING"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "lower"
|
||||
|
||||
def lower(self, string):
|
||||
return (string.lower(),)
|
||||
|
||||
|
||||
class StringReplace(ComfyNodeABC):
|
||||
"""
|
||||
Replaces occurrences of a substring with another substring.
|
||||
|
||||
This node returns a copy of the string with all occurrences of substring
|
||||
old replaced by new. If the optional argument count is given, only the
|
||||
first count occurrences are replaced.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"string": (IO.STRING, {"default": "", "defaultInput": True}),
|
||||
"old": (IO.STRING, {"default": ""}),
|
||||
"new": (IO.STRING, {"default": ""}),
|
||||
},
|
||||
"optional": {
|
||||
"count": (IO.INT, {"default": -1, "min": -1}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
CATEGORY = "Basic/STRING"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "replace"
|
||||
|
||||
def replace(self, string, old, new, count=-1):
|
||||
return (string.replace(old, new, count),)
|
||||
|
||||
|
||||
class StringRjust(ComfyNodeABC):
|
||||
"""
|
||||
Right-aligns the string within a field of a given width.
|
||||
|
||||
This node returns a string right-aligned in a field of the specified width.
|
||||
If fillchar is provided, it is used as the padding character instead of a space.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"string": (IO.STRING, {"default": "", "defaultInput": True}),
|
||||
"width": (IO.INT, {"default": 10, "min": 0}),
|
||||
},
|
||||
"optional": {
|
||||
"fillchar": (IO.STRING, {"default": " "}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
CATEGORY = "Basic/STRING"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "rjust"
|
||||
|
||||
def rjust(self, string, width, fillchar=" "):
|
||||
# Ensure fillchar is only one character
|
||||
if len(fillchar) > 0:
|
||||
fillchar = fillchar[0]
|
||||
return (string.rjust(width, fillchar),)
|
||||
|
||||
|
||||
class StringDataListJoin(ComfyNodeABC):
|
||||
"""
|
||||
Joins strings from a data list with a specified separator.
|
||||
|
||||
This node takes a data list of strings and concatenates them, with the specified
|
||||
separator string between each element. The separator is inserted between the strings,
|
||||
not at the beginning or end of the result.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"strings": (IO.STRING, {"forceInput": True}),
|
||||
"sep": (IO.STRING, {"default": " "}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
CATEGORY = "Basic/STRING"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "join"
|
||||
INPUT_IS_LIST = True # The "strings" input accepts a data list
|
||||
|
||||
def join(self, sep, strings):
|
||||
separator = sep[0] # everything comes as a list, so sep is list[str]
|
||||
return (separator.join(strings),)
|
||||
|
||||
|
||||
class StringListJoin(ComfyNodeABC):
|
||||
"""
|
||||
Joins strings from a LIST with a specified separator.
|
||||
|
||||
This node takes a data list of strings and concatenates them, with the specified
|
||||
separator string between each element. The separator is inserted between the strings,
|
||||
not at the beginning or end of the result.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"strings": ("LIST", {"forceInput": True}),
|
||||
"sep": (IO.STRING, {"default": " "}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
CATEGORY = "Basic/STRING"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "join"
|
||||
|
||||
def join(self, sep, strings):
|
||||
separator = sep[0] # everything comes as a list, so sep is list[str]
|
||||
return (separator.join(strings),)
|
||||
|
||||
|
||||
class StringRfind(ComfyNodeABC):
|
||||
"""
|
||||
Finds the highest index of the substring in the string.
|
||||
|
||||
This node searches for the last occurrence of the specified substring within the input string.
|
||||
Returns the highest index where the substring is found. If the substring is not found, it returns -1.
|
||||
Optional start and end parameters allow you to limit the search to a specific portion of the string.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"string": (IO.STRING, {"default": "", "defaultInput": True}),
|
||||
"substring": (IO.STRING, {"default": ""}),
|
||||
},
|
||||
"optional": {
|
||||
"start": (IO.INT, {"default": 0, "min": 0}),
|
||||
"end": (IO.INT, {"default": 0, "min": 0}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.INT,)
|
||||
CATEGORY = "Basic/STRING"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "rfind"
|
||||
|
||||
def rfind(self, string, substring, start=0, end=0):
|
||||
# If end is 0 or negative, search to the end of the string
|
||||
if end <= 0:
|
||||
end = len(string)
|
||||
|
||||
return (string.rfind(substring, start, end),)
|
||||
|
||||
|
||||
class StringRstrip(ComfyNodeABC):
|
||||
"""
|
||||
Removes trailing characters from the string.
|
||||
@@ -1340,30 +1364,6 @@ class StringZfill(ComfyNodeABC):
|
||||
return (string.zfill(width),)
|
||||
|
||||
|
||||
class StringLength(ComfyNodeABC):
|
||||
"""
|
||||
Returns the length of a string.
|
||||
|
||||
This node calculates and returns the number of characters in the input string.
|
||||
It works the same way as Python's built-in len() function for strings.
|
||||
"""
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"string": (IO.STRING, {"default": "", "defaultInput": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.INT,)
|
||||
CATEGORY = "Basic/STRING"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "length"
|
||||
|
||||
def length(self, string):
|
||||
return (len(string),)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"Basic data handling: StringCapitalize": StringCapitalize,
|
||||
"Basic data handling: StringCasefold": StringCasefold,
|
||||
@@ -1372,10 +1372,11 @@ NODE_CLASS_MAPPINGS = {
|
||||
"Basic data handling: StringCount": StringCount,
|
||||
"Basic data handling: StringDecode": StringDecode,
|
||||
"Basic data handling: StringEncode": StringEncode,
|
||||
"Basic data handling: StringExpandtabs": StringExpandtabs,
|
||||
"Basic data handling: StringEndswith": StringEndswith,
|
||||
"Basic data handling: StringExpandtabs": StringExpandtabs,
|
||||
"Basic data handling: StringFind": StringFind,
|
||||
"Basic data handling: StringFormatMap": StringFormatMap,
|
||||
"Basic data handling: StringIn": StringIn,
|
||||
"Basic data handling: StringIsAlnum": StringIsAlnum,
|
||||
"Basic data handling: StringIsAlpha": StringIsAlpha,
|
||||
"Basic data handling: StringIsAscii": StringIsAscii,
|
||||
@@ -1388,6 +1389,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"Basic data handling: StringIsSpace": StringIsSpace,
|
||||
"Basic data handling: StringIsTitle": StringIsTitle,
|
||||
"Basic data handling: StringIsUpper": StringIsUpper,
|
||||
"Basic data handling: StringLength": StringLength,
|
||||
"Basic data handling: StringDataListJoin": StringDataListJoin,
|
||||
"Basic data handling: StringListJoin": StringListJoin,
|
||||
"Basic data handling: StringLjust": StringLjust,
|
||||
@@ -1411,7 +1413,6 @@ NODE_CLASS_MAPPINGS = {
|
||||
"Basic data handling: StringTitle": StringTitle,
|
||||
"Basic data handling: StringUpper": StringUpper,
|
||||
"Basic data handling: StringZfill": StringZfill,
|
||||
"Basic data handling: StringLength": StringLength,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
@@ -1422,8 +1423,8 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"Basic data handling: StringCount": "count",
|
||||
"Basic data handling: StringDecode": "decode",
|
||||
"Basic data handling: StringEncode": "encode",
|
||||
"Basic data handling: StringExpandtabs": "expandtabs",
|
||||
"Basic data handling: StringEndswith": "endswith",
|
||||
"Basic data handling: StringExpandtabs": "expandtabs",
|
||||
"Basic data handling: StringFind": "find",
|
||||
"Basic data handling: StringFormatMap": "format_map",
|
||||
"Basic data handling: StringIn": "in (contains)",
|
||||
@@ -1439,6 +1440,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"Basic data handling: StringIsSpace": "isspace",
|
||||
"Basic data handling: StringIsTitle": "istitle",
|
||||
"Basic data handling: StringIsUpper": "isupper",
|
||||
"Basic data handling: StringLength": "length",
|
||||
"Basic data handling: StringDataListJoin": "join (from data list)",
|
||||
"Basic data handling: StringListJoin": "join (from LIST)",
|
||||
"Basic data handling: StringLjust": "ljust",
|
||||
@@ -1462,5 +1464,4 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"Basic data handling: StringTitle": "title",
|
||||
"Basic data handling: StringUpper": "upper",
|
||||
"Basic data handling: StringZfill": "zfill",
|
||||
"Basic data handling: StringLength": "length",
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import pytest
|
||||
from src.basic_data_handling.casting_nodes import (CastToString, CastToInt, CastToFloat, CastToBoolean,
|
||||
CastToList, CastDataListToList, CastToSet, CastDataListToSet,
|
||||
CastToDict)
|
||||
CastToList, CastToSet, CastToDict)
|
||||
|
||||
|
||||
def test_cast_to_string():
|
||||
@@ -49,13 +48,6 @@ def test_cast_to_list():
|
||||
assert node.convert_to_list(None) == ([None],)
|
||||
|
||||
|
||||
def test_cast_data_list_to_list():
|
||||
node = CastDataListToList()
|
||||
assert node.convert(list=[1, 2, 3]) == ([1, 2, 3],)
|
||||
assert node.convert(list=[]) == ([],)
|
||||
assert node.convert(list=["a", "b", "c"]) == (["a", "b", "c"],)
|
||||
|
||||
|
||||
def test_cast_to_set():
|
||||
node = CastToSet()
|
||||
assert node.convert_to_set(123) == ({123},)
|
||||
@@ -64,13 +56,6 @@ def test_cast_to_set():
|
||||
assert node.convert_to_set(None) == ({None},)
|
||||
|
||||
|
||||
def test_cast_data_list_to_set():
|
||||
node = CastDataListToSet()
|
||||
assert node.convert(list=[1, 2, 3, 3]) == ({1, 2, 3},)
|
||||
assert node.convert(list=["a", "b", "b"]) == ({"a", "b"},)
|
||||
assert node.convert(list=[]) == (set(),)
|
||||
|
||||
|
||||
def test_cast_to_dict():
|
||||
node = CastToDict()
|
||||
assert node.convert_to_dict({"key": "value"}) == ({"key": "value"},)
|
||||
|
||||
@@ -5,12 +5,10 @@ from src.basic_data_handling.data_list_nodes import (
|
||||
DataListInsert,
|
||||
DataListRemove,
|
||||
DataListPop,
|
||||
DataListClear,
|
||||
DataListIndex,
|
||||
DataListCount,
|
||||
DataListSort,
|
||||
DataListReverse,
|
||||
DataListCopy,
|
||||
DataListLength,
|
||||
DataListSlice,
|
||||
DataListGetItem,
|
||||
@@ -59,12 +57,6 @@ def test_pop():
|
||||
assert node.pop(list=[], index=[0]) == ([], None) # Pop from empty list
|
||||
|
||||
|
||||
def test_clear():
|
||||
node = DataListClear()
|
||||
assert node.clear(list=[1, 2, 3]) == ([],)
|
||||
assert node.clear(list=[]) == ([],)
|
||||
|
||||
|
||||
def test_index():
|
||||
node = DataListIndex()
|
||||
assert node.list_index(list=["a", "b", "c"], value=["b"]) == (1,)
|
||||
@@ -92,12 +84,6 @@ def test_reverse():
|
||||
assert node.reverse(list=[]) == ([],)
|
||||
|
||||
|
||||
def test_copy():
|
||||
node = DataListCopy()
|
||||
assert node.copy(list=[1, 2, 3]) == ([1, 2, 3],)
|
||||
assert node.copy(list=[]) == ([],)
|
||||
|
||||
|
||||
def test_length():
|
||||
node = DataListLength()
|
||||
assert node.length(list=[1, 2, 3]) == (3,)
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
#import pytest
|
||||
from src.basic_data_handling.dict_nodes import (
|
||||
DictCreate,
|
||||
DictCreateFromItems,
|
||||
DictGet,
|
||||
DictSet,
|
||||
DictKeys,
|
||||
DictValues,
|
||||
DictItems,
|
||||
DictContainsKey,
|
||||
DictClear,
|
||||
DictCopy,
|
||||
DictFromKeys,
|
||||
DictPop,
|
||||
DictPopItem,
|
||||
@@ -25,7 +22,6 @@ from src.basic_data_handling.dict_nodes import (
|
||||
DictInvert,
|
||||
DictCreateFromLists,
|
||||
DictCompare,
|
||||
AnyToDict,
|
||||
)
|
||||
|
||||
def test_dict_create():
|
||||
@@ -33,12 +29,6 @@ def test_dict_create():
|
||||
assert node.create() == ({},) # Creates an empty dictionary
|
||||
|
||||
|
||||
def test_dict_create_from_items():
|
||||
node = DictCreateFromItems()
|
||||
items = [("key1", "value1"), ("key2", "value2")]
|
||||
assert node.create_from_items(items) == ({"key1": "value1", "key2": "value2"},)
|
||||
|
||||
|
||||
def test_dict_get():
|
||||
node = DictGet()
|
||||
my_dict = {"key1": "value1", "key2": "value2"}
|
||||
@@ -77,18 +67,6 @@ def test_dict_contains_key():
|
||||
assert node.contains_key(my_dict, "key2") == (False,)
|
||||
|
||||
|
||||
def test_dict_clear():
|
||||
node = DictClear()
|
||||
my_dict = {"key1": "value1"}
|
||||
assert node.clear(my_dict) == ({},)
|
||||
|
||||
|
||||
def test_dict_copy():
|
||||
node = DictCopy()
|
||||
my_dict = {"key1": "value1"}
|
||||
assert node.copy(my_dict) == ({"key1": "value1"},)
|
||||
|
||||
|
||||
def test_dict_from_keys():
|
||||
node = DictFromKeys()
|
||||
keys = ["key1", "key2"]
|
||||
@@ -185,9 +163,3 @@ def test_dict_compare():
|
||||
dict1 = {"key1": "value1"}
|
||||
dict2 = {"key1": "value1"}
|
||||
assert node.compare(dict1, dict2) == (True, [], [], [])
|
||||
|
||||
|
||||
def test_any_to_dict():
|
||||
node = AnyToDict()
|
||||
my_dict = {"key1": "value1"}
|
||||
assert node.convert(my_dict) == ({"key1": "value1"},)
|
||||
|
||||
@@ -5,7 +5,6 @@ from src.basic_data_handling.list_nodes import (
|
||||
ListInsert,
|
||||
ListRemove,
|
||||
ListPop,
|
||||
ListClear,
|
||||
ListIndex,
|
||||
ListCount,
|
||||
ListSort,
|
||||
@@ -55,12 +54,6 @@ def test_list_pop():
|
||||
assert node.pop([], 0) == ([], None) # Empty list pop
|
||||
|
||||
|
||||
def test_list_clear():
|
||||
node = ListClear()
|
||||
assert node.clear([1, 2, 3]) == ([],)
|
||||
assert node.clear([]) == ([],)
|
||||
|
||||
|
||||
def test_list_index():
|
||||
node = ListIndex()
|
||||
assert node.index([1, 2, 3, 2], 2) == (1,)
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
#import pytest
|
||||
from src.basic_data_handling.regex_nodes import (
|
||||
RegexSearchGroups,
|
||||
RegexSearchGroupsDataList,
|
||||
RegexGroupDict,
|
||||
RegexFindall,
|
||||
RegexSplit,
|
||||
RegexFindallDataList,
|
||||
RegexSplitDataList,
|
||||
RegexSub,
|
||||
RegexTest
|
||||
)
|
||||
|
||||
def test_regex_search_groups():
|
||||
node = RegexSearchGroups()
|
||||
node = RegexSearchGroupsDataList()
|
||||
assert node.search_groups(r"(foo)(bar)", "foobar") == (["foo", "bar"],)
|
||||
assert node.search_groups(r"(test)(\d+)", "test123") == (["test", "123"],)
|
||||
assert node.search_groups(r"(nothing)", "no match here") == ([],) # No match
|
||||
@@ -26,7 +26,7 @@ def test_regex_group_dict():
|
||||
|
||||
|
||||
def test_regex_findall():
|
||||
node = RegexFindall()
|
||||
node = RegexFindallDataList()
|
||||
assert node.findall(r"\d+", "abc 123 def 456") == (["123", "456"],)
|
||||
assert node.findall(r"\b[a-zA-Z]+\b", "Multiple words here") == (["Multiple", "words", "here"],)
|
||||
assert node.findall(r"nonexistent", "no match") == ([],) # No match
|
||||
@@ -34,7 +34,7 @@ def test_regex_findall():
|
||||
|
||||
|
||||
def test_regex_split():
|
||||
node = RegexSplit()
|
||||
node = RegexSplitDataList()
|
||||
pattern = r",\s*"
|
||||
string = "one, two, three"
|
||||
assert node.split(pattern, string) == (["one", "two", "three"],)
|
||||
|
||||
@@ -4,7 +4,6 @@ from src.basic_data_handling.set_nodes import (
|
||||
SetRemove,
|
||||
SetDiscard,
|
||||
SetPop,
|
||||
SetClear,
|
||||
SetUnion,
|
||||
SetIntersection,
|
||||
SetDifference,
|
||||
@@ -14,7 +13,6 @@ from src.basic_data_handling.set_nodes import (
|
||||
SetIsDisjoint,
|
||||
SetContains,
|
||||
SetLength,
|
||||
ListToSet,
|
||||
SetToList
|
||||
)
|
||||
|
||||
@@ -47,12 +45,6 @@ def test_set_pop():
|
||||
assert node.pop(empty_set) == (set(), None) # Handle empty set
|
||||
|
||||
|
||||
def test_set_clear():
|
||||
node = SetClear()
|
||||
assert node.clear({1, 2, 3}) == (set(),)
|
||||
assert node.clear(set()) == (set(),) # Already empty set remains empty
|
||||
|
||||
|
||||
def test_set_union():
|
||||
node = SetUnion()
|
||||
assert node.union({1, 2}, {3, 4}) == ({1, 2, 3, 4},)
|
||||
@@ -111,12 +103,6 @@ def test_set_length():
|
||||
assert node.length(set()) == (0,) # Empty set
|
||||
|
||||
|
||||
def test_list_to_set():
|
||||
node = ListToSet()
|
||||
assert node.convert([1, 2, 3, 2]) == ({1, 2, 3},)
|
||||
assert node.convert([]) == (set(),) # Empty list
|
||||
|
||||
|
||||
def test_set_to_list():
|
||||
node = SetToList()
|
||||
result = node.convert({1, 2, 3})
|
||||
|
||||
+46
-10
@@ -6,13 +6,14 @@ from src.basic_data_handling.string_nodes import (
|
||||
StringCenter,
|
||||
StringConcat,
|
||||
StringCount,
|
||||
StringDataListJoin,
|
||||
StringDecode,
|
||||
StringEncode,
|
||||
StringEndswith,
|
||||
StringExpandtabs,
|
||||
StringFind,
|
||||
StringIn,
|
||||
StringFormatMap,
|
||||
StringIn,
|
||||
StringIsAlnum,
|
||||
StringIsAlpha,
|
||||
StringIsAscii,
|
||||
@@ -25,7 +26,8 @@ from src.basic_data_handling.string_nodes import (
|
||||
StringIsSpace,
|
||||
StringIsTitle,
|
||||
StringIsUpper,
|
||||
StringJoin,
|
||||
StringLength,
|
||||
StringListJoin,
|
||||
StringLjust,
|
||||
StringLower,
|
||||
StringLstrip,
|
||||
@@ -34,10 +36,13 @@ from src.basic_data_handling.string_nodes import (
|
||||
StringReplace,
|
||||
StringRfind,
|
||||
StringRjust,
|
||||
StringRsplit,
|
||||
StringRsplitDataList,
|
||||
StringRsplitList,
|
||||
StringRstrip,
|
||||
StringSplit,
|
||||
StringSplitlines,
|
||||
StringSplitDataList,
|
||||
StringSplitList,
|
||||
StringSplitlinesDataList,
|
||||
StringSplitlinesList,
|
||||
StringStartswith,
|
||||
StringStrip,
|
||||
StringSwapcase,
|
||||
@@ -211,12 +216,18 @@ def test_isupper():
|
||||
assert node.isupper("") == (False,) # Empty string
|
||||
|
||||
def test_join():
|
||||
node = StringJoin()
|
||||
node = StringDataListJoin()
|
||||
# join expects a data list as input, so we need to simulate this
|
||||
assert node.join([", "], ["apple", "banana", "cherry"]) == ("apple, banana, cherry",)
|
||||
assert node.join([""], ["a", "b", "c"]) == ("abc",)
|
||||
assert node.join(["-"], []) == ("",) # Empty list
|
||||
|
||||
# Test ListJoin variant
|
||||
node_list = StringListJoin()
|
||||
assert node_list.join([", "], ["apple", "banana", "cherry"]) == ("apple, banana, cherry",)
|
||||
assert node_list.join([""], ["a", "b", "c"]) == ("abc",)
|
||||
assert node_list.join(["-"], []) == ("",) # Empty list
|
||||
|
||||
def test_ljust():
|
||||
node = StringLjust()
|
||||
assert node.ljust("test", 10) == ("test ",)
|
||||
@@ -269,11 +280,17 @@ def test_rjust():
|
||||
assert node.rjust("test", 2) == ("test",) # Width smaller than string length
|
||||
|
||||
def test_rsplit():
|
||||
node = StringRsplit()
|
||||
node = StringRsplitDataList()
|
||||
assert node.rsplit("apple,banana,cherry", ",") == (["apple", "banana", "cherry"],)
|
||||
assert node.rsplit("apple,banana,cherry", ",", 1) == (["apple,banana", "cherry"],)
|
||||
assert node.rsplit(" words with spaces ") == (["words", "with", "spaces"],) # Default splits on whitespace
|
||||
|
||||
# Test List variant
|
||||
node_list = StringRsplitList()
|
||||
assert node_list.rsplit("apple,banana,cherry", ",") == (["apple", "banana", "cherry"],)
|
||||
assert node_list.rsplit("apple,banana,cherry", ",", 1) == (["apple,banana", "cherry"],)
|
||||
assert node_list.rsplit(" words with spaces ") == (["words", "with", "spaces"],) # Default splits on whitespace
|
||||
|
||||
def test_rstrip():
|
||||
node = StringRstrip()
|
||||
assert node.rstrip("hello ") == ("hello",)
|
||||
@@ -282,18 +299,31 @@ def test_rstrip():
|
||||
assert node.rstrip("") == ("",) # Empty string
|
||||
|
||||
def test_split():
|
||||
node = StringSplit()
|
||||
node = StringSplitDataList()
|
||||
assert node.split("apple,banana,cherry", ",") == (["apple", "banana", "cherry"],)
|
||||
assert node.split("apple,banana,cherry", ",", 1) == (["apple", "banana,cherry"],)
|
||||
assert node.split(" words with spaces ") == (["words", "with", "spaces"],) # Default splits on whitespace
|
||||
|
||||
# Test List variant
|
||||
node_list = StringSplitList()
|
||||
assert node_list.split("apple,banana,cherry", ",") == (["apple", "banana", "cherry"],)
|
||||
assert node_list.split("apple,banana,cherry", ",", 1) == (["apple", "banana,cherry"],)
|
||||
assert node_list.split(" words with spaces ") == (["words", "with", "spaces"],) # Default splits on whitespace
|
||||
|
||||
def test_splitlines():
|
||||
node = StringSplitlines()
|
||||
node = StringSplitlinesDataList()
|
||||
assert node.splitlines("line1\nline2\nline3") == (["line1", "line2", "line3"],)
|
||||
assert node.splitlines("line1\r\nline2\rline3") == (["line1", "line2", "line3"],)
|
||||
assert node.splitlines("line1\nline2\nline3", "True") == (["line1\n", "line2\n", "line3"],) # Keep line endings
|
||||
assert node.splitlines("line1\nline2\nline3", True) == (["line1\n", "line2\n", "line3"],) # Keep line endings
|
||||
assert node.splitlines("no newlines") == (["no newlines"],)
|
||||
|
||||
# Test List variant
|
||||
node_list = StringSplitlinesList()
|
||||
assert node_list.splitlines("line1\nline2\nline3") == (["line1", "line2", "line3"],)
|
||||
assert node_list.splitlines("line1\r\nline2\rline3") == (["line1", "line2", "line3"],)
|
||||
assert node_list.splitlines("line1\nline2\nline3", True) == (["line1\n", "line2\n", "line3"],) # Keep line endings
|
||||
assert node_list.splitlines("no newlines") == (["no newlines"],)
|
||||
|
||||
def test_startswith():
|
||||
node = StringStartswith()
|
||||
assert node.startswith("function startswith", "function") == (True,)
|
||||
@@ -333,3 +363,9 @@ def test_zfill():
|
||||
assert node.zfill("-123", 5) == ("-0123",) # Preserves sign
|
||||
assert node.zfill("123", 2) == ("123",) # Width smaller than string length
|
||||
assert node.zfill("", 3) == ("000",) # Empty string
|
||||
|
||||
def test_length():
|
||||
node = StringLength()
|
||||
assert node.length("hello") == (5,)
|
||||
assert node.length("") == (0,) # Empty string
|
||||
assert node.length("hello world") == (11,) # String with spaces
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
console.log(app);
|
||||
Reference in New Issue
Block a user