Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions src/basic_data_handling/boolean_nodes.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from inspect import cleandoc
from typing import Any

try:
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
Expand All @@ -12,6 +13,9 @@ class IO:
ANY = "*"
ComfyNodeABC = object

from ._dynamic_input import ContainsDynamicDict


class BooleanAnd(ComfyNodeABC):
"""
Returns the logical AND result of two boolean values.
Expand Down Expand Up @@ -155,20 +159,80 @@ def xor_operation(self, input1: bool, input2: bool) -> tuple[bool]:
return (input1 != input2,)


class GenericOr(ComfyNodeABC):
"""
Returns the logical N/OR result of one or more values.

This node takes a dynamic number of inputs and returns their logical N/OR result.
Note that values are evaluated according Python's rules. I.e. an empty string is
`false`, an integer 0 is also `false`, etc.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"invert": (IO.BOOLEAN, {"default": False}),
},
"optional": ContainsDynamicDict({
"item_0": (IO.ANY, {"_dynamic": "number", "widgetType": "STRING"}),
})
}

RETURN_TYPES = (IO.BOOLEAN,)
CATEGORY = "Basic/BOOLEAN"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "or_operation"

def or_operation(self, invert: bool, **kwargs: list[Any]) -> tuple[bool]:
return (any(kwargs.values()) ^ invert,)


class GenericAnd(ComfyNodeABC):
"""
Returns the logical N/AND result of one or more values.

This node takes a dynamic number of inputs and returns their logical N/AND result.
Note that values are evaluated according Python's rules. I.e. an empty string is
`false`, an integer 0 is also `false`, etc.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"invert": (IO.BOOLEAN, {"default": False}),
},
"optional": ContainsDynamicDict({
"item_0": (IO.ANY, {"_dynamic": "number", "widgetType": "STRING", "default": "True"}),
})
}

RETURN_TYPES = (IO.BOOLEAN,)
CATEGORY = "Basic/BOOLEAN"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "and_operation"

def and_operation(self, invert: bool, **kwargs: list[Any]) -> tuple[bool]:
return (all(kwargs.values()) ^ invert,)


NODE_CLASS_MAPPINGS = {
"Basic data handling: Boolean And": BooleanAnd,
"Basic data handling: Generic And": GenericAnd,
"Basic data handling: Boolean Nand": BooleanNand,
"Basic data handling: Boolean Nor": BooleanNor,
"Basic data handling: Boolean Not": BooleanNot,
"Basic data handling: Boolean Or": BooleanOr,
"Basic data handling: Generic Or": GenericOr,
"Basic data handling: Boolean Xor": BooleanXor,
}

NODE_DISPLAY_NAME_MAPPINGS = {
"Basic data handling: Boolean And": "and",
"Basic data handling: Generic And": "and (generic)",
"Basic data handling: Boolean Nand": "nand",
"Basic data handling: Boolean Nor": "nor",
"Basic data handling: Boolean Not": "not",
"Basic data handling: Boolean Or": "or",
"Basic data handling: Generic Or": "or (generic)",
"Basic data handling: Boolean Xor": "xor",
}
36 changes: 36 additions & 0 deletions tests/test_boolean_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
BooleanXor,
BooleanNand,
BooleanNor,
GenericAnd,
GenericOr,
)


Expand Down Expand Up @@ -54,3 +56,37 @@ def test_boolean_nor():
assert node.nor_operation(True, False) == (False,)
assert node.nor_operation(False, True) == (False,)
assert node.nor_operation(False, False) == (True,)


def test_generic_or():
node = GenericOr()
# 2xOR
assert node.or_operation(False, item_0=True, item_1=True) == (True,)
assert node.or_operation(False, item_0=True, item_1=False) == (True,)
assert node.or_operation(False, item_0=False, item_1=True) == (True,)
assert node.or_operation(False, item_0=False, item_1=False) == (False,)
# 2xNOR
assert node.or_operation(True, item_0=True, item_1=True) == (False,)
assert node.or_operation(True, item_0=True, item_1=False) == (False,)
assert node.or_operation(True, item_0=False, item_1=True) == (False,)
assert node.or_operation(True, item_0=False, item_1=False) == (True,)
# A couple of 3xOR cases
assert node.or_operation(False, item_0=False, item_1=False, item_2=True) == (True,)
assert node.or_operation(False, item_0=False, item_1=False, item_2=False) == (False,)


def test_generic_and():
node = GenericAnd()
# 2xAND
assert node.and_operation(False, item_0=True, item_1=True) == (True,)
assert node.and_operation(False, item_0=True, item_1=False) == (False,)
assert node.and_operation(False, item_0=False, item_1=True) == (False,)
assert node.and_operation(False, item_0=False, item_1=False) == (False,)
# 2xNAND
assert node.and_operation(True, item_0=True, item_1=True) == (False,)
assert node.and_operation(True, item_0=True, item_1=False) == (True,)
assert node.and_operation(True, item_0=False, item_1=True) == (True,)
assert node.and_operation(True, item_0=False, item_1=False) == (True,)
# A couple of 3xAND cases
assert node.and_operation(False, item_0=True, item_1=True, item_2=True) == (True,)
assert node.and_operation(False, item_0=True, item_1=False, item_2=True) == (False,)