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
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,46 @@ ComfyUI has different data types that serve different purposes:
- Supports built-in ComfyUI iteration over each item
- Best for:
- Working directly with multiple items in parallel
- Processing each item in a collection separately
- When you need ComfyUI's automatic iteration functionality

### 2. LIST
- A Python list passed as a single ComfyUI variable
- Must be processed as a complete unit by compatible nodes
- Operations apply to the entire LIST at once
- Best for:
- Storing and manipulating structured data as a single unit
- When you need to preserve ordered collections
- Passing complex data structures between nodes

### 3. SET
- A Python set passed as a single ComfyUI variable
- Unordered collection of unique items
- Useful for membership testing, removing duplicates, and set operations
- Best for:
- When you need to ensure uniqueness of items
- Performing mathematical set operations (union, intersection, difference)
- Efficient membership testing (contains operation)
- When item order doesn't matter

## Control Flow Nodes

Control flow nodes provide mechanisms to direct the flow of execution in your ComfyUI workflows, allowing for conditional processing and dynamic execution paths.

### Available Control Flow Nodes:

#### Conditional Processing
- **if/else** - Routes execution based on a boolean condition
- **if/elif/.../else** - Supports multiple conditional branches
- **switch/case** - Selects from multiple options based on an index

#### Execution Management
- **disable flow** - Conditionally enables or disables a flow
- **flow select** - Directs output to either "true" or "false" path
- **force calculation** - Prevents caching and forces recalculation
- **force execution order** - Controls the sequence of node execution

These control flow nodes enable building more complex, dynamic workflows with decision-making capabilities based on runtime conditions.
- Batch processing scenarios
- When you need to apply the same operation to multiple inputs
- When your operation needs to work with individual items separately
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "basic_data_handling"
version = "0.4.0"
version = "0.4.1"
description = """Basic Python functions for manipulating data that every programmer is used to.
Comprehensive node collection for data manipulation in ComfyUI workflows.

Expand Down
85 changes: 76 additions & 9 deletions src/basic_data_handling/control_flow_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def check_lazy_status(self, **kwargs) -> list[str]:
# Check if condition
if kwargs.get("if", False) and kwargs.get("then") is None:
needed.append("then")
return needed # If main condition is true, we only need "then"
return needed # If the main condition is true, we only need "then"

# Check each elif condition
elif_index = 0
Expand Down Expand Up @@ -153,7 +153,7 @@ class SwitchCase(ComfyNodeABC):
def INPUT_TYPES(cls):
return {
"required": ContainsDynamicDict({
"selector": (IO.INT, {"default": 0, "min": 0}),
"select": (IO.INT, {"default": 0, "min": 0}),
"case_0": (IO.ANY, {"lazy": True, "_dynamic": "number"}),
}),
"optional": {
Expand All @@ -167,29 +167,29 @@ def INPUT_TYPES(cls):
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "execute"

def check_lazy_status(self, selector: int, **kwargs) -> list[str]:
def check_lazy_status(self, select: int, **kwargs) -> list[str]:
needed = []

# Check for needed case inputs based on selector
# Check for necessary case inputs based on select
case_count = 0
for key, value in kwargs.items():
if key.startswith("case_"):
try:
case_index = int(key.split("_")[1])
case_count = max(case_count, case_index + 1)
if value is None and selector == case_index:
if value is None and select == case_index:
needed.append(key)
except ValueError:
pass # Not a numeric case key

# Check if default is needed when selector is out of range
if "default" in kwargs and kwargs["default"] is None and not 0 <= selector < case_count:
# Check if default is needed when select is out of range
if "default" in kwargs and kwargs["default"] is None and not 0 <= select < case_count:
needed.append("default")

return needed

def execute(self, selector: int, **kwargs) -> tuple[Any]:
# Build cases array from all case_X inputs
# Build a case array from all case_X inputs
cases = []
for i in range(len(kwargs)):
case_key = f"case_{i}"
Expand All @@ -202,10 +202,44 @@ def execute(self, selector: int, **kwargs) -> tuple[Any]:
if 0 <= selector < len(cases) and cases[selector] is not None:
return (cases[selector],)

# If selector is out of range or the selected case is None, return default
# If select is out of range or the selected case is None, return default
return (kwargs.get("default"),)


class DisableFlow(ComfyNodeABC):
"""
Conditionally enable or disable a flow.

This node takes a value and either passes it through or blocks execution
based on the 'select' parameter. When 'select' is True, the value passes through;
when False, execution is blocked.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"value": (IO.ANY, {}),
"select": (IO.BOOLEAN, {"default": True}),
}
}

RETURN_TYPES = (IO.ANY,)
RETURN_NAMES = ("value",)
CATEGORY = "Basic/flow control"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "execute"

@classmethod
def IS_CHANGED(s, value: Any):
return float("NaN") # not equal to anything -> trigger recalculation

def execute(self, value: Any, select: bool = True) -> tuple[Any]:
if select:
return (value,)
else:
return (ExecutionBlocker(None),)


class FlowSelect(ComfyNodeABC):
"""
Select the direction of the flow.
Expand Down Expand Up @@ -237,6 +271,35 @@ def select(self, value, select = True) -> tuple[Any, Any]:
return ExecutionBlocker(None), value


class ForceCalculation(ComfyNodeABC):
"""
Forces recalculation of the connected nodes.

This node passes the input directly to the output but prevents caching
by marking itself as an output node and also indicates the out has changed.
Use this when you need to ensure nodes are always recalculated.
"""

OUTPUT_NODE = True # Marks as an output node to force calculation

@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"value": (IO.ANY, {}),
}
}

RETURN_TYPES = (IO.ANY,)
RETURN_NAMES = ("value",)
CATEGORY = "Basic/flow control"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "execute"

def execute(self, value: Any) -> tuple[Any, int]:
return (value,)


class ExecutionOrder(ComfyNodeABC):
"""
Force execution order in the workflow.
Expand Down Expand Up @@ -268,14 +331,18 @@ def execute(self, **kwargs) -> tuple[Any]:
"Basic data handling: IfElse": IfElse,
"Basic data handling: IfElifElse": IfElifElse,
"Basic data handling: SwitchCase": SwitchCase,
"Basic data handling: DisableFlow": DisableFlow,
"Basic data handling: FlowSelect": FlowSelect,
"Basic data handling: ForceCalculation": ForceCalculation,
"Basic data handling: ExecutionOrder": ExecutionOrder,
}

NODE_DISPLAY_NAME_MAPPINGS = {
"Basic data handling: IfElse": "if/else",
"Basic data handling: IfElifElse": "if/elif/.../else",
"Basic data handling: SwitchCase": "switch/case",
"Basic data handling: DisableFlow": "disable flow",
"Basic data handling: FlowSelect": "flow select",
"Basic data handling: ForceCalculation": "force calculation",
"Basic data handling: ExecutionOrder": "force execution order",
}
Loading