820 lines
26 KiB
Python
820 lines
26 KiB
Python
from typing import Any
|
|
from inspect import cleandoc
|
|
|
|
try:
|
|
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
|
except:
|
|
class IO:
|
|
BOOLEAN = "BOOLEAN"
|
|
INT = "INT"
|
|
FLOAT = "FLOAT"
|
|
STRING = "STRING"
|
|
NUMBER = "FLOAT,INT"
|
|
ANY = "*"
|
|
ComfyNodeABC = object
|
|
|
|
from ._dynamic_input import ContainsDynamicDict
|
|
|
|
|
|
class SetCreate(ComfyNodeABC):
|
|
"""
|
|
Creates a new SET from items.
|
|
|
|
This node creates and returns a SET. The list of items is dynamically
|
|
extended based on the number of inputs provided.
|
|
"""
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"optional": ContainsDynamicDict({
|
|
"item_0": (IO.ANY, {"_dynamic": "number", "widgetType": "STRING", "tooltip": "One of the items to add to the SET. Connect more values to add more items."}),
|
|
})
|
|
}
|
|
|
|
RETURN_TYPES = ("SET",)
|
|
RETURN_NAMES = ("set",)
|
|
OUTPUT_TOOLTIPS = ("The new SET containing the provided items (duplicates removed).",)
|
|
CATEGORY = "Basic/SET"
|
|
DESCRIPTION = cleandoc(__doc__ or "")
|
|
FUNCTION = "create_set"
|
|
|
|
def create_set(self, **kwargs: list[Any]) -> tuple[set[Any]]:
|
|
values = list(kwargs.values())[:-1]
|
|
return (set(values),)
|
|
|
|
|
|
class SetCreateFromBoolean(ComfyNodeABC):
|
|
"""
|
|
Creates a new SET from items.
|
|
|
|
This node creates and returns a SET. The list of items is dynamically
|
|
extended based on the number of inputs provided.
|
|
"""
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"optional": ContainsDynamicDict({
|
|
"item_0": (IO.BOOLEAN, {"_dynamic": "number", "widgetType": "STRING", "tooltip": "One of the boolean items to add to the SET. Connect more values to add more items."}),
|
|
})
|
|
}
|
|
|
|
RETURN_TYPES = ("SET",)
|
|
RETURN_NAMES = ("set",)
|
|
OUTPUT_TOOLTIPS = ("The new SET containing the provided boolean items (duplicates removed).",)
|
|
CATEGORY = "Basic/SET"
|
|
DESCRIPTION = cleandoc(__doc__ or "")
|
|
FUNCTION = "create_set"
|
|
|
|
def create_set(self, **kwargs: list[Any]) -> tuple[set[Any]]:
|
|
values = [bool(value) for value in list(kwargs.values())[:-1]]
|
|
return (set(values),)
|
|
|
|
|
|
class SetCreateFromFloat(ComfyNodeABC):
|
|
"""
|
|
Creates a new SET from items.
|
|
|
|
This node creates and returns a SET. The list of items is dynamically
|
|
extended based on the number of inputs provided.
|
|
"""
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"optional": ContainsDynamicDict({
|
|
"item_0": (IO.FLOAT, {"_dynamic": "number", "widgetType": "STRING", "tooltip": "One of the float items to add to the SET. Connect more values to add more items."}),
|
|
})
|
|
}
|
|
|
|
RETURN_TYPES = ("SET",)
|
|
RETURN_NAMES = ("set",)
|
|
OUTPUT_TOOLTIPS = ("The new SET containing the provided float items (duplicates removed).",)
|
|
CATEGORY = "Basic/SET"
|
|
DESCRIPTION = cleandoc(__doc__ or "")
|
|
FUNCTION = "create_set"
|
|
|
|
def create_set(self, **kwargs: list[Any]) -> tuple[set[Any]]:
|
|
values = [float(value) for value in list(kwargs.values())[:-1]]
|
|
return (set(values),)
|
|
|
|
|
|
class SetCreateFromInt(ComfyNodeABC):
|
|
"""
|
|
Creates a new SET from items.
|
|
|
|
This node creates and returns a SET. The list of items is dynamically
|
|
extended based on the number of inputs provided.
|
|
"""
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"optional": ContainsDynamicDict({
|
|
"item_0": (IO.INT, {"_dynamic": "number", "widgetType": "STRING", "tooltip": "One of the integer items to add to the SET. Connect more values to add more items."}),
|
|
})
|
|
}
|
|
|
|
RETURN_TYPES = ("SET",)
|
|
RETURN_NAMES = ("set",)
|
|
OUTPUT_TOOLTIPS = ("The new SET containing the provided integer items (duplicates removed).",)
|
|
CATEGORY = "Basic/SET"
|
|
DESCRIPTION = cleandoc(__doc__ or "")
|
|
FUNCTION = "create_set"
|
|
|
|
def create_set(self, **kwargs: list[Any]) -> tuple[set[Any]]:
|
|
values = [int(value) for value in list(kwargs.values())[:-1]]
|
|
return (set(values),)
|
|
|
|
|
|
class SetCreateFromString(ComfyNodeABC):
|
|
"""
|
|
Creates a new SET from items.
|
|
|
|
This node creates and returns a SET. The list of items is dynamically
|
|
extended based on the number of inputs provided.
|
|
"""
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"optional": ContainsDynamicDict({
|
|
"item_0": (IO.STRING, {"_dynamic": "number", "widgetType": "STRING", "tooltip": "One of the string items to add to the SET. Connect more values to add more items."}),
|
|
})
|
|
}
|
|
|
|
RETURN_TYPES = ("SET",)
|
|
RETURN_NAMES = ("set",)
|
|
OUTPUT_TOOLTIPS = ("The new SET containing the provided string items (duplicates removed).",)
|
|
CATEGORY = "Basic/SET"
|
|
DESCRIPTION = cleandoc(__doc__ or "")
|
|
FUNCTION = "create_set"
|
|
|
|
def create_set(self, **kwargs: list[Any]) -> tuple[set[Any]]:
|
|
values = [str(value) for value in list(kwargs.values())[:-1]]
|
|
return (set(values),)
|
|
|
|
|
|
class SetAdd(ComfyNodeABC):
|
|
"""
|
|
Adds an item to a SET.
|
|
|
|
This node takes a SET and any item as inputs, then returns a new SET
|
|
with the item added. If the item is already present, the SET remains unchanged.
|
|
"""
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"set": ("SET", {"tooltip": "The SET to add to."}),
|
|
"item": (IO.ANY, {"tooltip": "The item to add."}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("SET",)
|
|
RETURN_NAMES = ("set",)
|
|
OUTPUT_TOOLTIPS = ("The SET with the item added (unchanged when the item was already present).",)
|
|
CATEGORY = "Basic/SET"
|
|
DESCRIPTION = cleandoc(__doc__ or "")
|
|
FUNCTION = "add"
|
|
|
|
def add(self, set: set[Any], item: Any) -> tuple[set[Any]]:
|
|
result = set.copy()
|
|
result.add(item)
|
|
return (result,)
|
|
|
|
|
|
class SetAll(ComfyNodeABC):
|
|
"""
|
|
Checks if all elements in the SET are true.
|
|
|
|
This node takes a SET as input and returns True if all elements in the SET
|
|
evaluate to True (or if the SET is empty), and False otherwise.
|
|
"""
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"set": ("SET", {"tooltip": "The SET to evaluate."}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = (IO.BOOLEAN,)
|
|
RETURN_NAMES = ("all_true",)
|
|
OUTPUT_TOOLTIPS = ("True when every element is truthy (or the SET is empty).",)
|
|
CATEGORY = "Basic/SET"
|
|
DESCRIPTION = cleandoc(__doc__ or "")
|
|
FUNCTION = "check_all"
|
|
|
|
def check_all(self, set: set[Any]) -> tuple[bool]:
|
|
return (all(set),)
|
|
|
|
|
|
class SetAny(ComfyNodeABC):
|
|
"""
|
|
Checks if any element in the SET is true.
|
|
|
|
This node takes a SET as input and returns True if at least one element
|
|
in the SET evaluates to True, and False otherwise (including if the SET is empty).
|
|
"""
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"set": ("SET", {"tooltip": "The SET to evaluate."}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = (IO.BOOLEAN,)
|
|
RETURN_NAMES = ("any_true",)
|
|
OUTPUT_TOOLTIPS = ("True when at least one element is truthy (False for an empty SET).",)
|
|
CATEGORY = "Basic/SET"
|
|
DESCRIPTION = cleandoc(__doc__ or "")
|
|
FUNCTION = "check_any"
|
|
|
|
def check_any(self, set: set[Any]) -> tuple[bool]:
|
|
return (any(set),)
|
|
|
|
|
|
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", {"tooltip": "The SET to search."}),
|
|
"value": (IO.ANY, {"tooltip": "The value to look for."}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = (IO.BOOLEAN,)
|
|
RETURN_NAMES = ("contains",)
|
|
OUTPUT_TOOLTIPS = ("True when the value is present in the SET.",)
|
|
CATEGORY = "Basic/SET"
|
|
DESCRIPTION = cleandoc(__doc__ or "")
|
|
FUNCTION = "contains"
|
|
|
|
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", {"tooltip": "The SET to subtract from."}),
|
|
"set2": ("SET", {"tooltip": "The SET of elements to remove."}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("SET",)
|
|
RETURN_NAMES = ("set",)
|
|
OUTPUT_TOOLTIPS = ("Elements in set1 that are not in set2.",)
|
|
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):
|
|
"""
|
|
Removes an item from a SET if it is present.
|
|
|
|
This node takes a SET and any item as inputs, then returns a new SET
|
|
with the item removed. Unlike remove, no error is raised if the item is not present.
|
|
"""
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"set": ("SET", {"tooltip": "The SET to remove from."}),
|
|
"item": (IO.ANY, {"tooltip": "The item to remove if present."}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("SET",)
|
|
RETURN_NAMES = ("set",)
|
|
OUTPUT_TOOLTIPS = ("The SET with the item removed (unchanged when it was absent).",)
|
|
CATEGORY = "Basic/SET"
|
|
DESCRIPTION = cleandoc(__doc__ or "")
|
|
FUNCTION = "discard"
|
|
|
|
def discard(self, set: set[Any], item: Any) -> tuple[set[Any]]:
|
|
result = set.copy()
|
|
result.discard(item)
|
|
return (result,)
|
|
|
|
|
|
class SetEnumerate(ComfyNodeABC):
|
|
"""
|
|
Enumerates elements in a SET.
|
|
|
|
This node takes a SET as input and returns a LIST of tuples where each tuple
|
|
contains an index and a value from the SET. The start parameter specifies the
|
|
initial index value (default is 0).
|
|
|
|
Note: Since SETs are unordered, the enumeration order is arbitrary but consistent
|
|
within a single operation.
|
|
"""
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"set": ("SET", {"tooltip": "The SET to enumerate."}),
|
|
},
|
|
"optional": {
|
|
"start": ("INT", {"default": 0, "tooltip": "Index value assigned to the first element."}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("LIST",)
|
|
RETURN_NAMES = ("enumerated",)
|
|
OUTPUT_TOOLTIPS = ("List of (index, value) pairs. Order is arbitrary but stable within one operation.",)
|
|
CATEGORY = "Basic/SET"
|
|
DESCRIPTION = cleandoc(__doc__ or "")
|
|
FUNCTION = "enumerate_set"
|
|
|
|
def enumerate_set(self, set: set[Any], start: int = 0) -> tuple[list]:
|
|
return (list(enumerate(set, start=start)),)
|
|
|
|
|
|
class SetIntersection(ComfyNodeABC):
|
|
"""
|
|
Returns the intersection of two or more SETs.
|
|
|
|
This node takes multiple SETs as input and returns a new SET containing
|
|
only elements present in all input SETs.
|
|
"""
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"set1": ("SET", {"tooltip": "First SET."}),
|
|
"set2": ("SET", {"tooltip": "Second SET."}),
|
|
},
|
|
"optional": {
|
|
"set3": ("SET", {"tooltip": "Optional additional SET."}),
|
|
"set4": ("SET", {"tooltip": "Optional additional SET."}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("SET",)
|
|
RETURN_NAMES = ("set",)
|
|
OUTPUT_TOOLTIPS = ("Elements present in all of the input SETs.",)
|
|
CATEGORY = "Basic/SET"
|
|
DESCRIPTION = cleandoc(__doc__ or "")
|
|
FUNCTION = "intersection"
|
|
|
|
def intersection(self, set1: set[Any], set2: set[Any], set3=None, set4=None) -> tuple[set[Any]]:
|
|
result = set1.copy()
|
|
result.intersection_update(set2)
|
|
|
|
if set3 is not None:
|
|
result.intersection_update(set3)
|
|
|
|
if set4 is not None:
|
|
result.intersection_update(set4)
|
|
|
|
return (result,)
|
|
|
|
|
|
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", {"tooltip": "First SET."}),
|
|
"set2": ("SET", {"tooltip": "Second SET."}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = (IO.BOOLEAN,)
|
|
RETURN_NAMES = ("is_disjoint",)
|
|
OUTPUT_TOOLTIPS = ("True when the two SETs share no elements.",)
|
|
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 SetIsSubset(ComfyNodeABC):
|
|
"""
|
|
Checks if set1 is a subset of set2.
|
|
|
|
This node takes two SETs as input and returns True if set1 is a subset of set2
|
|
(all elements in set1 are also in set2).
|
|
"""
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"set1": ("SET", {"tooltip": "Candidate subset."}),
|
|
"set2": ("SET", {"tooltip": "SET that may contain all of set1's elements."}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = (IO.BOOLEAN,)
|
|
RETURN_NAMES = ("is_subset",)
|
|
OUTPUT_TOOLTIPS = ("True when every element of set1 is also in set2.",)
|
|
CATEGORY = "Basic/SET"
|
|
DESCRIPTION = cleandoc(__doc__ or "")
|
|
FUNCTION = "is_subset"
|
|
|
|
def is_subset(self, set1: set[Any], set2: set[Any]) -> tuple[bool]:
|
|
return (set1.issubset(set2),)
|
|
|
|
|
|
class SetIsSuperset(ComfyNodeABC):
|
|
"""
|
|
Checks if set1 is a superset of set2.
|
|
|
|
This node takes two SETs as input and returns True if set1 is a superset of set2
|
|
(set1 contains all elements in set2).
|
|
"""
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"set1": ("SET", {"tooltip": "Candidate superset."}),
|
|
"set2": ("SET", {"tooltip": "SET whose elements must all be in set1."}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = (IO.BOOLEAN,)
|
|
RETURN_NAMES = ("is_superset",)
|
|
OUTPUT_TOOLTIPS = ("True when set1 contains every element of set2.",)
|
|
CATEGORY = "Basic/SET"
|
|
DESCRIPTION = cleandoc(__doc__ or "")
|
|
FUNCTION = "is_superset"
|
|
|
|
def is_superset(self, set1: set[Any], set2: set[Any]) -> tuple[bool]:
|
|
return (set1.issuperset(set2),)
|
|
|
|
|
|
class SetLength(ComfyNodeABC):
|
|
"""
|
|
Returns the number of items in a SET.
|
|
|
|
This node takes a SET as input and returns its length (number of elements) as an integer.
|
|
"""
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"set": ("SET", {"tooltip": "The SET to measure."}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = (IO.INT,)
|
|
RETURN_NAMES = ("length",)
|
|
OUTPUT_TOOLTIPS = ("The number of elements in the SET.",)
|
|
CATEGORY = "Basic/SET"
|
|
DESCRIPTION = cleandoc(__doc__ or "")
|
|
FUNCTION = "length"
|
|
|
|
def length(self, set: set[Any]) -> tuple[int]:
|
|
return (len(set),)
|
|
|
|
|
|
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", {"tooltip": "The SET to pop an item from."}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("SET", IO.ANY)
|
|
RETURN_NAMES = ("set", "item")
|
|
OUTPUT_TOOLTIPS = ("The SET with an arbitrary item removed.", "The removed item (None when the SET was empty).")
|
|
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 SetPopRandom(ComfyNodeABC):
|
|
"""
|
|
Removes and returns a random element from a SET.
|
|
|
|
This node takes a SET as input and returns the SET with a random element removed
|
|
and the removed element itself. If the SET is empty, it returns None for the element.
|
|
An optional seed can be provided for reproducible selection.
|
|
"""
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"set": ("SET", {"tooltip": "The SET to pop a random element from."}),
|
|
},
|
|
"optional": {
|
|
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "control_after_generate": True, "tooltip": "Seed for reproducible selection. Leave empty to pick randomly each run."}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("SET", IO.ANY)
|
|
RETURN_NAMES = ("set", "item")
|
|
OUTPUT_TOOLTIPS = ("The SET with a random element removed.", "The removed element (None when the SET was empty).")
|
|
CATEGORY = "Basic/SET"
|
|
DESCRIPTION = cleandoc(__doc__ or "")
|
|
FUNCTION = "pop_random_element"
|
|
|
|
@classmethod
|
|
def IS_CHANGED(cls, **kwargs):
|
|
seed = kwargs.get("seed")
|
|
if seed is None:
|
|
return float("NaN") # Not equal to anything -> trigger recalculation
|
|
return seed
|
|
|
|
def pop_random_element(self, set: set[Any], seed=None) -> tuple[set[Any], Any]:
|
|
import random
|
|
rng = random.Random(seed) if seed is not None else random
|
|
result = set.copy()
|
|
if result:
|
|
random_element = rng.choice(list(result))
|
|
result.remove(random_element)
|
|
return result, random_element
|
|
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", {"tooltip": "The SET to remove from."}),
|
|
"item": (IO.ANY, {"tooltip": "The item to remove."}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("SET", IO.BOOLEAN)
|
|
RETURN_NAMES = ("set", "success")
|
|
OUTPUT_TOOLTIPS = ("The SET with the item removed.", "True when the item was present and removed.")
|
|
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 SetSum(ComfyNodeABC):
|
|
"""
|
|
Calculates the sum of all elements in a SET.
|
|
|
|
This node takes a SET as input and returns the sum of all its elements.
|
|
The optional start parameter specifies the initial value (default is 0).
|
|
|
|
Note: This operation requires all elements to be numeric or otherwise
|
|
compatible with addition. If the SET contains mixed or incompatible types,
|
|
it may raise an error.
|
|
"""
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"set": ("SET", {"tooltip": "The SET of numbers to sum."}),
|
|
},
|
|
"optional": {
|
|
"start": ("INT", {"default": 0, "tooltip": "Initial value added to the sum."}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("INT", "FLOAT",)
|
|
RETURN_NAMES = ("sum_int", "sum_float",)
|
|
OUTPUT_TOOLTIPS = ("The total as an integer.", "The total as a float.")
|
|
CATEGORY = "Basic/SET"
|
|
DESCRIPTION = cleandoc(__doc__ or "")
|
|
FUNCTION = "sum_set"
|
|
|
|
def sum_set(self, set: set[Any], start: int = 0) -> tuple[int, float]:
|
|
result = sum(set, start)
|
|
return result, float(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", {"tooltip": "First SET."}),
|
|
"set2": ("SET", {"tooltip": "Second SET."}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("SET",)
|
|
RETURN_NAMES = ("set",)
|
|
OUTPUT_TOOLTIPS = ("Elements present in exactly one of the two SETs.",)
|
|
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,)
|
|
|
|
|
|
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", {"tooltip": "First SET."}),
|
|
"set2": ("SET", {"tooltip": "Second SET."}),
|
|
},
|
|
"optional": {
|
|
"set3": ("SET", {"tooltip": "Optional additional SET."}),
|
|
"set4": ("SET", {"tooltip": "Optional additional SET."}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("SET",)
|
|
RETURN_NAMES = ("set",)
|
|
OUTPUT_TOOLTIPS = ("All elements from every input SET (duplicates removed).",)
|
|
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):
|
|
"""
|
|
Converts a SET object into a ComfyUI data list.
|
|
|
|
This node takes a SET object (Python set as a single variable) and
|
|
converts it to a ComfyUI data list, allowing its items to be processed
|
|
individually by nodes that accept data lists.
|
|
"""
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"set": ("SET", {"tooltip": "The SET to convert."}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = (IO.ANY,)
|
|
RETURN_NAMES = ("items",)
|
|
OUTPUT_TOOLTIPS = ("The SET's elements as a ComfyUI data list.",)
|
|
CATEGORY = "Basic/SET"
|
|
DESCRIPTION = cleandoc(__doc__ or "")
|
|
FUNCTION = "convert"
|
|
OUTPUT_IS_LIST = (True,)
|
|
|
|
def convert(self, set) -> tuple[list[Any]]:
|
|
return (list(set),)
|
|
|
|
|
|
class SetToList(ComfyNodeABC):
|
|
"""
|
|
Converts a SET into a LIST.
|
|
|
|
This node takes a SET input and creates a new LIST containing all elements
|
|
from the SET. Note that the order of elements in the resulting LIST is arbitrary
|
|
since SETs are unordered collections.
|
|
"""
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"set": ("SET", {"tooltip": "The SET to convert."}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("LIST",)
|
|
RETURN_NAMES = ("list",)
|
|
OUTPUT_TOOLTIPS = ("The SET's elements as a Python LIST (order is arbitrary).",)
|
|
CATEGORY = "Basic/SET"
|
|
DESCRIPTION = cleandoc(__doc__ or "")
|
|
FUNCTION = "convert"
|
|
|
|
def convert(self, set: set[Any]) -> tuple[list[Any]]:
|
|
return (list(set),)
|
|
|
|
|
|
NODE_CLASS_MAPPINGS = {
|
|
"Basic data handling: SetCreate": SetCreate,
|
|
"Basic data handling: SetCreateFromBoolean": SetCreateFromBoolean,
|
|
"Basic data handling: SetCreateFromFloat": SetCreateFromFloat,
|
|
"Basic data handling: SetCreateFromInt": SetCreateFromInt,
|
|
"Basic data handling: SetCreateFromString": SetCreateFromString,
|
|
"Basic data handling: SetAdd": SetAdd,
|
|
"Basic data handling: SetAll": SetAll,
|
|
"Basic data handling: SetAny": SetAny,
|
|
"Basic data handling: SetContains": SetContains,
|
|
"Basic data handling: SetDifference": SetDifference,
|
|
"Basic data handling: SetDiscard": SetDiscard,
|
|
"Basic data handling: SetEnumerate": SetEnumerate,
|
|
"Basic data handling: SetIntersection": SetIntersection,
|
|
"Basic data handling: SetIsDisjoint": SetIsDisjoint,
|
|
"Basic data handling: SetIsSubset": SetIsSubset,
|
|
"Basic data handling: SetIsSuperset": SetIsSuperset,
|
|
"Basic data handling: SetLength": SetLength,
|
|
"Basic data handling: SetPop": SetPop,
|
|
"Basic data handling: SetPopRandom": SetPopRandom,
|
|
"Basic data handling: SetRemove": SetRemove,
|
|
"Basic data handling: SetSum": SetSum,
|
|
"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: SetCreate": "create SET",
|
|
"Basic data handling: SetCreateFromBoolean": "create SET from BOOLEANs",
|
|
"Basic data handling: SetCreateFromFloat": "create SET from FLOATs",
|
|
"Basic data handling: SetCreateFromInt": "create SET from INTs",
|
|
"Basic data handling: SetCreateFromString": "create SET from STRINGs",
|
|
"Basic data handling: SetAdd": "add",
|
|
"Basic data handling: SetAll": "all",
|
|
"Basic data handling: SetAny": "any",
|
|
"Basic data handling: SetContains": "contains",
|
|
"Basic data handling: SetDifference": "difference",
|
|
"Basic data handling: SetDiscard": "discard",
|
|
"Basic data handling: SetEnumerate": "enumerate",
|
|
"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: SetPopRandom": "pop random",
|
|
"Basic data handling: SetRemove": "remove",
|
|
"Basic data handling: SetSum": "sum",
|
|
"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",
|
|
}
|