Added:
- Data List and LIST: new "filter empty" node that removes every empty entry (`None`, empty STRING, empty LIST/DICT/SET, ...); `0` and `False` are kept. An optional boolean switch also treats a STRING consisting only of whitespace as empty.
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
"""Small helpers shared by the node modules (this is not a ComfyUI node module)."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
def as_bool(value: Any) -> bool:
|
||||
"""Normalize a ComfyUI BOOLEAN widget/input value into a real bool.
|
||||
|
||||
ComfyUI hands over real booleans, but legacy combo widgets ("True" / "False"
|
||||
strings) and nodes with ``INPUT_IS_LIST`` (values wrapped into a list) also
|
||||
occur, so all of them are accepted here.
|
||||
"""
|
||||
if isinstance(value, (list, tuple)):
|
||||
value = value[0] if value else False
|
||||
if isinstance(value, str):
|
||||
return value.strip().lower() in ("true", "1", "yes", "on")
|
||||
return bool(value)
|
||||
|
||||
|
||||
def is_empty_entry(value: Any, treat_whitespace_as_empty: bool = False) -> bool:
|
||||
"""Tell whether an entry of a Data List / LIST has to be regarded as empty.
|
||||
|
||||
Empty are ``None``, an empty STRING and an empty collection (empty LIST,
|
||||
DICT, SET, ...). Numbers and booleans are never empty, so ``0`` and ``False``
|
||||
are kept.
|
||||
|
||||
When ``treat_whitespace_as_empty`` is enabled, a STRING that consists only of
|
||||
whitespace characters (spaces, tabs, line breaks, ...) counts as empty as
|
||||
well. The whitespace rule applies to STRINGs only; objects without a length
|
||||
(e.g. plain numbers or 0-dimensional TENSORs) are never empty.
|
||||
"""
|
||||
if value is None:
|
||||
return True
|
||||
if isinstance(value, str):
|
||||
return not value.strip() if treat_whitespace_as_empty else not value
|
||||
try:
|
||||
return len(value) == 0
|
||||
except TypeError:
|
||||
return False
|
||||
@@ -16,6 +16,7 @@ except:
|
||||
ComfyNodeABC = object
|
||||
|
||||
from ._dynamic_input import ContainsDynamicDict
|
||||
from ._value_utils import as_bool, is_empty_entry
|
||||
|
||||
INT_MAX = 2**15 - 1 # the computer can do more but be nice to the eyes
|
||||
|
||||
@@ -501,6 +502,52 @@ class DataListFilter(ComfyNodeABC):
|
||||
return (result,)
|
||||
|
||||
|
||||
class DataListFilterEmpty(ComfyNodeABC):
|
||||
"""
|
||||
Removes every empty entry of a Data List.
|
||||
|
||||
This node takes a Data List and returns a new Data List without the entries
|
||||
that are empty, i.e. ``None``, an empty STRING and an empty collection
|
||||
(empty LIST, DICT, SET, ...). Numbers and booleans are never empty, so ``0``
|
||||
and ``False`` are kept.
|
||||
|
||||
When ``treat_whitespace_as_empty`` is enabled, a STRING that consists only of
|
||||
whitespace characters (spaces, tabs, line breaks, ...) counts as empty as
|
||||
well and is removed, too.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": (IO.ANY, {"tooltip": "The Data List whose empty entries are removed."}),
|
||||
},
|
||||
"optional": {
|
||||
"treat_whitespace_as_empty": (
|
||||
IO.BOOLEAN,
|
||||
{
|
||||
"default": False,
|
||||
"tooltip": "When enabled, STRINGs that consist only of whitespace (spaces, tabs, line breaks) are removed as well.",
|
||||
},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.ANY,)
|
||||
RETURN_NAMES = ("list",)
|
||||
OUTPUT_TOOLTIPS = ("A new Data List without the empty entries.",)
|
||||
CATEGORY = "Basic/Data List"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "filter_empty"
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
def filter_empty(self, **kwargs: list[Any]) -> tuple[list[Any]]:
|
||||
treat_whitespace_as_empty = as_bool(kwargs.get("treat_whitespace_as_empty"))
|
||||
result = [value for value in (kwargs.get("list") or []) if not is_empty_entry(value, treat_whitespace_as_empty)]
|
||||
return (result,)
|
||||
|
||||
|
||||
class DataListFilterSelect(ComfyNodeABC):
|
||||
"""
|
||||
Filters a Data List using boolean values.
|
||||
@@ -1334,6 +1381,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"Basic data handling: DataListEnumerate": DataListEnumerate,
|
||||
"Basic data handling: DataListExtend": DataListExtend,
|
||||
"Basic data handling: DataListFilter": DataListFilter,
|
||||
"Basic data handling: DataListFilterEmpty": DataListFilterEmpty,
|
||||
"Basic data handling: DataListFilterSelect": DataListFilterSelect,
|
||||
"Basic data handling: DataListFirst": DataListFirst,
|
||||
"Basic data handling: DataListGetItem": DataListGetItem,
|
||||
@@ -1373,6 +1421,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"Basic data handling: DataListEnumerate": "enumerate",
|
||||
"Basic data handling: DataListExtend": "extend",
|
||||
"Basic data handling: DataListFilter": "filter",
|
||||
"Basic data handling: DataListFilterEmpty": "filter empty",
|
||||
"Basic data handling: DataListFilterSelect": "filter select",
|
||||
"Basic data handling: DataListFirst": "first",
|
||||
"Basic data handling: DataListGetItem": "get item",
|
||||
|
||||
@@ -17,6 +17,7 @@ except:
|
||||
ComfyNodeABC = object
|
||||
|
||||
from ._dynamic_input import ContainsDynamicDict
|
||||
from ._value_utils import as_bool, is_empty_entry
|
||||
|
||||
INT_MAX = 2**15 - 1 # the computer can do more but be nice to the eyes
|
||||
|
||||
@@ -404,6 +405,50 @@ class ListExtend(ComfyNodeABC):
|
||||
return (result,)
|
||||
|
||||
|
||||
class ListFilterEmpty(ComfyNodeABC):
|
||||
"""
|
||||
Removes every empty entry of a LIST.
|
||||
|
||||
This node takes a LIST and returns a new LIST without the entries that are
|
||||
empty, i.e. ``None``, an empty STRING and an empty collection (empty LIST,
|
||||
DICT, SET, ...). Numbers and booleans are never empty, so ``0`` and ``False``
|
||||
are kept.
|
||||
|
||||
When ``treat_whitespace_as_empty`` is enabled, a STRING that consists only of
|
||||
whitespace characters (spaces, tabs, line breaks, ...) counts as empty as
|
||||
well and is removed, too.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"list": ("LIST", {"tooltip": "The LIST whose empty entries are removed."}),
|
||||
},
|
||||
"optional": {
|
||||
"treat_whitespace_as_empty": (
|
||||
IO.BOOLEAN,
|
||||
{
|
||||
"default": False,
|
||||
"tooltip": "When enabled, STRINGs that consist only of whitespace (spaces, tabs, line breaks) are removed as well.",
|
||||
},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LIST",)
|
||||
RETURN_NAMES = ("list",)
|
||||
OUTPUT_TOOLTIPS = ("A new LIST without the empty entries.",)
|
||||
CATEGORY = "Basic/LIST"
|
||||
DESCRIPTION = cleandoc(__doc__ or "")
|
||||
FUNCTION = "filter_empty"
|
||||
|
||||
def filter_empty(self, list: list[Any], treat_whitespace_as_empty: bool = False) -> tuple[list[Any]]:
|
||||
list = list if list is not None else []
|
||||
treat = as_bool(treat_whitespace_as_empty)
|
||||
return ([value for value in list if not is_empty_entry(value, treat)],)
|
||||
|
||||
|
||||
class ListFirst(ComfyNodeABC):
|
||||
"""
|
||||
Returns the first element in a LIST.
|
||||
@@ -1135,6 +1180,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"Basic data handling: ListCount": ListCount,
|
||||
"Basic data handling: ListEnumerate": ListEnumerate,
|
||||
"Basic data handling: ListExtend": ListExtend,
|
||||
"Basic data handling: ListFilterEmpty": ListFilterEmpty,
|
||||
"Basic data handling: ListFirst": ListFirst,
|
||||
"Basic data handling: ListGetItem": ListGetItem,
|
||||
"Basic data handling: ListIndex": ListIndex,
|
||||
@@ -1171,6 +1217,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"Basic data handling: ListCount": "count",
|
||||
"Basic data handling: ListEnumerate": "enumerate",
|
||||
"Basic data handling: ListExtend": "extend",
|
||||
"Basic data handling: ListFilterEmpty": "filter empty",
|
||||
"Basic data handling: ListFirst": "first",
|
||||
"Basic data handling: ListGetItem": "get item",
|
||||
"Basic data handling: ListIndex": "index",
|
||||
|
||||
Reference in New Issue
Block a user