From c2f5b13db0943cea276ccb9e2d141ecdfd00b092 Mon Sep 17 00:00:00 2001 From: "ae(seth)tics" Date: Wed, 27 Aug 2025 12:25:41 -0700 Subject: [PATCH] launch 0.0.1 --- README.md | 72 +++++++++++++++++++++++- __init__.py | 3 + nodes/__init__.py | 12 ++++ nodes/constants.py | 65 ++++++++++++++++++++++ nodes/conversion.py | 75 +++++++++++++++++++++++++ nodes/data.py | 91 +++++++++++++++++++++++++++++++ nodes/image.py | 67 +++++++++++++++++++++++ nodes/logic.py | 122 +++++++++++++++++++++++++++++++++++++++++ nodes/main.py | 36 ++++++++++++ nodes/math.py | 130 ++++++++++++++++++++++++++++++++++++++++++++ nodes/text.py | 57 +++++++++++++++++++ nodes/workflow.py | 54 ++++++++++++++++++ 12 files changed, 783 insertions(+), 1 deletion(-) create mode 100644 __init__.py create mode 100644 nodes/__init__.py create mode 100644 nodes/constants.py create mode 100644 nodes/conversion.py create mode 100644 nodes/data.py create mode 100644 nodes/image.py create mode 100644 nodes/logic.py create mode 100644 nodes/main.py create mode 100644 nodes/math.py create mode 100644 nodes/text.py create mode 100644 nodes/workflow.py diff --git a/README.md b/README.md index 7496a86..b241d46 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,72 @@ # ComfyUI-Utilitools -ComfyUI Custom Nodes: Utilities and Tools for improving user experience. + +A collection of utility nodes for ComfyUI that I find handy. Nothing fancy, just stuff that makes workflows a bit easier when you need simple operations. + +## What's in here? + +These aren't the most sophisticated nodes out there, but they get the job done for basic operations: + +### 🧮 Math Nodes +- **Add, Subtract, Multiply, Divide** - Basic math with both int and float outputs +- **Calculator** - Safely evaluate simple math expressions like "2 + 2 * 3" + +### 📝 Text Utilities +- **Text Concatenation** - Join two strings with optional separator +- **String Replace** - Find and replace text with optional count limit + +### 🔄 Type Conversions +- **Float to Int / Int to Float** - Convert between number types +- **Whatever To String** - Convert any value (int, float, string) to string + +### 🤔 Logic & Control +- **If Then Else** - Branch workflows based on boolean conditions +- **Switch** - Select from up to 8 inputs by index +- **Boolean Gates** - AND, OR, NOT operations + +### 📋 Data Manipulation +- **Create List** - Make lists from up to 8 items +- **List Index** - Get item from list by index +- **Batch Controller** - Adjust tensor batch sizes + +### ⚙️ Workflow Helpers +- **Passthrough** - For workflow organization (does nothing, just passes data through) +- **Counter** - Increment/decrement with optional reset +- **Constants** - Int, Float, and String constant value nodes + +### 🖼️ Image Utilities +- **Image Dimensions** - Get width, height, and batch size from images +- **Aspect Ratio** - Calculate ratio as decimal and simplified fraction (like "16:9") + +## Installation + +Just drop this folder into your ComfyUI `custom_nodes` directory and restart ComfyUI. That's it. + +``` +ComfyUI/custom_nodes/ComfyUI-Utilitools/ +``` + +## Notes + +- All nodes are organized under "Utilitools" categories in the node browser +- Most math operations work with both int and float inputs +- The calculator node only accepts basic math expressions for safety +- These are utility nodes I made for myself - they're useful but not groundbreaking + +## Structure + +The code is organized by category for easier maintenance: + +``` +nodes/ +├── math.py # Arithmetic operations +├── text.py # Text processing +├── conversion.py # Type conversions +├── logic.py # Conditional logic +├── data.py # Lists and batching +├── workflow.py # Workflow helpers +├── constants.py # Constant values +├── image.py # Image utilities +└── main.py # Central registry +``` + +Feel free to use, modify, or ignore as needed! diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..a7a1653 --- /dev/null +++ b/__init__.py @@ -0,0 +1,3 @@ +from .nodes.main import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS + +__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] diff --git a/nodes/__init__.py b/nodes/__init__.py new file mode 100644 index 0000000..98aa4cc --- /dev/null +++ b/nodes/__init__.py @@ -0,0 +1,12 @@ +""" +ComfyUI Utilitools Nodes + +A collection of utility nodes for ComfyUI to improve workflow efficiency. +""" + +from .main import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS + +__version__ = "1.0.0" +__author__ = "ComfyUI-Utilitools" + +__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"] \ No newline at end of file diff --git a/nodes/constants.py b/nodes/constants.py new file mode 100644 index 0000000..54ff9aa --- /dev/null +++ b/nodes/constants.py @@ -0,0 +1,65 @@ +class UtilConstantInt: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "value": ("INT", {"default": 0, "min": -999999, "max": 999999, "step": 1}), + } + } + + RETURN_TYPES = ("INT",) + RETURN_NAMES = ("value",) + FUNCTION = "get_value" + CATEGORY = "Utilitools/Constants" + + def get_value(self, value): + return (value,) + + +class UtilConstantFloat: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "value": ("FLOAT", {"default": 0.0, "min": -999999.0, "max": 999999.0, "step": 0.001}), + } + } + + RETURN_TYPES = ("FLOAT",) + RETURN_NAMES = ("value",) + FUNCTION = "get_value" + CATEGORY = "Utilitools/Constants" + + def get_value(self, value): + return (value,) + + +class UtilConstantString: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "value": ("STRING", {"default": "", "multiline": True}), + } + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("value",) + FUNCTION = "get_value" + CATEGORY = "Utilitools/Constants" + + def get_value(self, value): + return (value,) + + +NODE_CLASS_MAPPINGS = { + "UtilConstantInt": UtilConstantInt, + "UtilConstantFloat": UtilConstantFloat, + "UtilConstantString": UtilConstantString, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "UtilConstantInt": "Constant Int", + "UtilConstantFloat": "Constant Float", + "UtilConstantString": "Constant String", +} \ No newline at end of file diff --git a/nodes/conversion.py b/nodes/conversion.py new file mode 100644 index 0000000..d795d13 --- /dev/null +++ b/nodes/conversion.py @@ -0,0 +1,75 @@ +class UtilFloatToInt: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "value": ("FLOAT", {"default": 0.0}), + } + } + + RETURN_TYPES = ("INT",) + RETURN_NAMES = ("int_value",) + FUNCTION = "convert" + CATEGORY = "Utilitools/Conversion" + + def convert(self, value): + return (int(value),) + + +class UtilIntToFloat: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "value": ("INT", {"default": 0}), + } + } + + RETURN_TYPES = ("FLOAT",) + RETURN_NAMES = ("float_value",) + FUNCTION = "convert" + CATEGORY = "Utilitools/Conversion" + + def convert(self, value): + return (float(value),) + + +class UtilWhateverToString: + @classmethod + def INPUT_TYPES(cls): + return { + "required": {}, + "optional": { + "int_value": ("INT", {"default": 0}), + "float_value": ("FLOAT", {"default": 0.0}), + "string_value": ("STRING", {"default": ""}), + } + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("string_result",) + FUNCTION = "convert" + CATEGORY = "Utilitools/Conversion" + + def convert(self, int_value=None, float_value=None, string_value=None): + if int_value is not None: + return (str(int_value),) + elif float_value is not None: + return (str(float_value),) + elif string_value is not None: + return (str(string_value),) + else: + return ("",) + + +NODE_CLASS_MAPPINGS = { + "UtilFloatToInt": UtilFloatToInt, + "UtilIntToFloat": UtilIntToFloat, + "UtilWhateverToString": UtilWhateverToString, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "UtilFloatToInt": "Float to Int", + "UtilIntToFloat": "Int to Float", + "UtilWhateverToString": "Whatever To String", +} \ No newline at end of file diff --git a/nodes/data.py b/nodes/data.py new file mode 100644 index 0000000..b775457 --- /dev/null +++ b/nodes/data.py @@ -0,0 +1,91 @@ +class UtilListCreate: + @classmethod + def INPUT_TYPES(cls): + return { + "required": {}, + "optional": { + "item_0": ("*",), + "item_1": ("*",), + "item_2": ("*",), + "item_3": ("*",), + "item_4": ("*",), + "item_5": ("*",), + "item_6": ("*",), + "item_7": ("*",), + } + } + + RETURN_TYPES = ("LIST",) + RETURN_NAMES = ("list",) + FUNCTION = "create_list" + CATEGORY = "Utilitools/Data" + + def create_list(self, **kwargs): + items = [] + for i in range(8): + key = f"item_{i}" + if key in kwargs and kwargs[key] is not None: + items.append(kwargs[key]) + return (items,) + + +class UtilListIndex: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "list": ("LIST",), + "index": ("INT", {"default": 0, "min": 0, "max": 999, "step": 1}), + } + } + + RETURN_TYPES = ("*",) + RETURN_NAMES = ("item",) + FUNCTION = "get_item" + CATEGORY = "Utilitools/Data" + + def get_item(self, list, index): + if index < len(list): + return (list[index],) + return (None,) + + +class UtilBatchController: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input": ("*",), + "batch_size": ("INT", {"default": 1, "min": 1, "max": 64, "step": 1}), + } + } + + RETURN_TYPES = ("*",) + RETURN_NAMES = ("output",) + FUNCTION = "control_batch" + CATEGORY = "Utilitools/Data" + + def control_batch(self, input, batch_size): + if hasattr(input, 'shape') and len(input.shape) > 0: + if input.shape[0] != batch_size: + if input.shape[0] > batch_size: + return (input[:batch_size],) + else: + import torch + repeats = batch_size // input.shape[0] + (1 if batch_size % input.shape[0] > 0 else 0) + repeated = input.repeat(repeats, *([1] * (len(input.shape) - 1))) + return (repeated[:batch_size],) + return (input,) + + +NODE_CLASS_MAPPINGS = { + "UtilListCreate": UtilListCreate, + "UtilListIndex": UtilListIndex, + "UtilBatchController": UtilBatchController, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "UtilListCreate": "Create List", + "UtilListIndex": "List Index", + "UtilBatchController": "Batch Controller", +} \ No newline at end of file diff --git a/nodes/image.py b/nodes/image.py new file mode 100644 index 0000000..e686e3a --- /dev/null +++ b/nodes/image.py @@ -0,0 +1,67 @@ +class UtilImageDimensions: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "image": ("IMAGE",), + } + } + + RETURN_TYPES = ("INT", "INT", "INT") + RETURN_NAMES = ("width", "height", "batch_size") + FUNCTION = "get_dimensions" + CATEGORY = "Utilitools/Image" + + def get_dimensions(self, image): + if len(image.shape) == 4: # [batch, height, width, channels] + batch_size, height, width, channels = image.shape + return (width, height, batch_size) + elif len(image.shape) == 3: # [height, width, channels] + height, width, channels = image.shape + return (width, height, 1) + else: + return (0, 0, 0) + + +class UtilAspectRatio: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "width": ("INT", {"default": 512, "min": 1, "max": 8192, "step": 1}), + "height": ("INT", {"default": 512, "min": 1, "max": 8192, "step": 1}), + } + } + + RETURN_TYPES = ("FLOAT", "STRING") + RETURN_NAMES = ("ratio", "ratio_string") + FUNCTION = "calculate_ratio" + CATEGORY = "Utilitools/Image" + + def calculate_ratio(self, width, height): + import math + + def gcd(a, b): + while b: + a, b = b, a % b + return a + + ratio = width / height + divisor = gcd(width, height) + simplified_width = width // divisor + simplified_height = height // divisor + + ratio_string = f"{simplified_width}:{simplified_height}" + + return (ratio, ratio_string) + + +NODE_CLASS_MAPPINGS = { + "UtilImageDimensions": UtilImageDimensions, + "UtilAspectRatio": UtilAspectRatio, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "UtilImageDimensions": "Image Dimensions", + "UtilAspectRatio": "Aspect Ratio", +} \ No newline at end of file diff --git a/nodes/logic.py b/nodes/logic.py new file mode 100644 index 0000000..996502b --- /dev/null +++ b/nodes/logic.py @@ -0,0 +1,122 @@ +class UtilIfThenElse: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "condition": ("BOOLEAN", {"default": True}), + "if_true": ("*",), + "if_false": ("*",), + } + } + + RETURN_TYPES = ("*",) + RETURN_NAMES = ("result",) + FUNCTION = "execute" + CATEGORY = "Utilitools/Logic" + + def execute(self, condition, if_true, if_false): + return (if_true if condition else if_false,) + + +class UtilSwitch: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "index": ("INT", {"default": 0, "min": 0, "max": 7, "step": 1}), + }, + "optional": { + "input_0": ("*",), + "input_1": ("*",), + "input_2": ("*",), + "input_3": ("*",), + "input_4": ("*",), + "input_5": ("*",), + "input_6": ("*",), + "input_7": ("*",), + } + } + + RETURN_TYPES = ("*",) + RETURN_NAMES = ("selected",) + FUNCTION = "switch" + CATEGORY = "Utilitools/Logic" + + def switch(self, index, **kwargs): + input_key = f"input_{index}" + if input_key in kwargs and kwargs[input_key] is not None: + return (kwargs[input_key],) + return (None,) + + +class UtilBooleanAND: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "a": ("BOOLEAN", {"default": True}), + "b": ("BOOLEAN", {"default": True}), + } + } + + RETURN_TYPES = ("BOOLEAN",) + RETURN_NAMES = ("result",) + FUNCTION = "execute" + CATEGORY = "Utilitools/Logic" + + def execute(self, a, b): + return (a and b,) + + +class UtilBooleanOR: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "a": ("BOOLEAN", {"default": False}), + "b": ("BOOLEAN", {"default": False}), + } + } + + RETURN_TYPES = ("BOOLEAN",) + RETURN_NAMES = ("result",) + FUNCTION = "execute" + CATEGORY = "Utilitools/Logic" + + def execute(self, a, b): + return (a or b,) + + +class UtilBooleanNOT: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "value": ("BOOLEAN", {"default": True}), + } + } + + RETURN_TYPES = ("BOOLEAN",) + RETURN_NAMES = ("result",) + FUNCTION = "execute" + CATEGORY = "Utilitools/Logic" + + def execute(self, value): + return (not value,) + + +NODE_CLASS_MAPPINGS = { + "UtilIfThenElse": UtilIfThenElse, + "UtilSwitch": UtilSwitch, + "UtilBooleanAND": UtilBooleanAND, + "UtilBooleanOR": UtilBooleanOR, + "UtilBooleanNOT": UtilBooleanNOT, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "UtilIfThenElse": "If Then Else", + "UtilSwitch": "Switch", + "UtilBooleanAND": "Boolean AND", + "UtilBooleanOR": "Boolean OR", + "UtilBooleanNOT": "Boolean NOT", +} \ No newline at end of file diff --git a/nodes/main.py b/nodes/main.py new file mode 100644 index 0000000..6ac4921 --- /dev/null +++ b/nodes/main.py @@ -0,0 +1,36 @@ +""" +ComfyUI Utilitools - Main Node Registry + +Centralized registration of all utility nodes organized by category. +""" + +from .math import NODE_CLASS_MAPPINGS as MATH_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as MATH_DISPLAY_MAPPINGS +from .text import NODE_CLASS_MAPPINGS as TEXT_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as TEXT_DISPLAY_MAPPINGS +from .conversion import NODE_CLASS_MAPPINGS as CONVERSION_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as CONVERSION_DISPLAY_MAPPINGS +from .logic import NODE_CLASS_MAPPINGS as LOGIC_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as LOGIC_DISPLAY_MAPPINGS +from .data import NODE_CLASS_MAPPINGS as DATA_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as DATA_DISPLAY_MAPPINGS +from .workflow import NODE_CLASS_MAPPINGS as WORKFLOW_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as WORKFLOW_DISPLAY_MAPPINGS +from .constants import NODE_CLASS_MAPPINGS as CONSTANTS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as CONSTANTS_DISPLAY_MAPPINGS +from .image import NODE_CLASS_MAPPINGS as IMAGE_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as IMAGE_DISPLAY_MAPPINGS + +NODE_CLASS_MAPPINGS = { + **MATH_MAPPINGS, + **TEXT_MAPPINGS, + **CONVERSION_MAPPINGS, + **LOGIC_MAPPINGS, + **DATA_MAPPINGS, + **WORKFLOW_MAPPINGS, + **CONSTANTS_MAPPINGS, + **IMAGE_MAPPINGS, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + **MATH_DISPLAY_MAPPINGS, + **TEXT_DISPLAY_MAPPINGS, + **CONVERSION_DISPLAY_MAPPINGS, + **LOGIC_DISPLAY_MAPPINGS, + **DATA_DISPLAY_MAPPINGS, + **WORKFLOW_DISPLAY_MAPPINGS, + **CONSTANTS_DISPLAY_MAPPINGS, + **IMAGE_DISPLAY_MAPPINGS, +} \ No newline at end of file diff --git a/nodes/math.py b/nodes/math.py new file mode 100644 index 0000000..7cadfab --- /dev/null +++ b/nodes/math.py @@ -0,0 +1,130 @@ +""" +Mathematical utility nodes for ComfyUI. + +Provides basic arithmetic operations with both integer and float outputs. +""" + + +class UtilAdd: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "a": ("FLOAT", {"default": 0.0, "min": -999999.0, "max": 999999.0, "step": 0.001}), + "b": ("FLOAT", {"default": 0.0, "min": -999999.0, "max": 999999.0, "step": 0.001}), + } + } + + RETURN_TYPES = ("INT", "FLOAT") + RETURN_NAMES = ("int_result", "float_result") + FUNCTION = "add" + CATEGORY = "Utilitools/Math" + + def add(self, a, b): + result = a + b + return (int(result), float(result)) + + +class UtilSubtract: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "a": ("FLOAT", {"default": 0.0, "min": -999999.0, "max": 999999.0, "step": 0.001}), + "b": ("FLOAT", {"default": 0.0, "min": -999999.0, "max": 999999.0, "step": 0.001}), + } + } + + RETURN_TYPES = ("INT", "FLOAT") + RETURN_NAMES = ("int_result", "float_result") + FUNCTION = "subtract" + CATEGORY = "Utilitools/Math" + + def subtract(self, a, b): + result = a - b + return (int(result), float(result)) + + +class UtilMultiply: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "a": ("FLOAT", {"default": 1.0, "min": -999999.0, "max": 999999.0, "step": 0.001}), + "b": ("FLOAT", {"default": 1.0, "min": -999999.0, "max": 999999.0, "step": 0.001}), + } + } + + RETURN_TYPES = ("INT", "FLOAT") + RETURN_NAMES = ("int_result", "float_result") + FUNCTION = "multiply" + CATEGORY = "Utilitools/Math" + + def multiply(self, a, b): + result = a * b + return (int(result), float(result)) + + +class UtilDivide: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "a": ("FLOAT", {"default": 1.0, "min": -999999.0, "max": 999999.0, "step": 0.001}), + "b": ("FLOAT", {"default": 1.0, "min": -999999.0, "max": 999999.0, "step": 0.001}), + } + } + + RETURN_TYPES = ("INT", "FLOAT") + RETURN_NAMES = ("int_result", "float_result") + FUNCTION = "divide" + CATEGORY = "Utilitools/Math" + + def divide(self, a, b): + if b == 0: + return (0, 0.0) + result = a / b + return (int(result), float(result)) + + +class UtilCalculator: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "expression": ("STRING", {"default": "2 + 2"}), + } + } + + RETURN_TYPES = ("FLOAT", "INT") + RETURN_NAMES = ("float_result", "int_result") + FUNCTION = "calculate" + CATEGORY = "Utilitools/Math" + + def calculate(self, expression): + try: + import re + allowed_chars = re.compile(r'^[0-9+\-*/().\s]+$') + if not allowed_chars.match(expression): + return (0.0, 0) + result = eval(expression) + return (float(result), int(result)) + except: + return (0.0, 0) + + +NODE_CLASS_MAPPINGS = { + "UtilAdd": UtilAdd, + "UtilSubtract": UtilSubtract, + "UtilMultiply": UtilMultiply, + "UtilDivide": UtilDivide, + "UtilCalculator": UtilCalculator, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "UtilAdd": "Add", + "UtilSubtract": "Subtract", + "UtilMultiply": "Multiply", + "UtilDivide": "Divide", + "UtilCalculator": "Calculator", +} \ No newline at end of file diff --git a/nodes/text.py b/nodes/text.py new file mode 100644 index 0000000..737df67 --- /dev/null +++ b/nodes/text.py @@ -0,0 +1,57 @@ +class UtilTextConcat: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "text_a": ("STRING", {"default": ""}), + "text_b": ("STRING", {"default": ""}), + }, + "optional": { + "separator": ("STRING", {"default": ""}), + } + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("result",) + FUNCTION = "concat" + CATEGORY = "Utilitools/Text" + + def concat(self, text_a, text_b, separator=""): + return (text_a + separator + text_b,) + + +class UtilStringReplace: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "text": ("STRING", {"default": ""}), + "find": ("STRING", {"default": ""}), + "replace": ("STRING", {"default": ""}), + }, + "optional": { + "count": ("INT", {"default": -1, "min": -1, "max": 1000, "step": 1}), + } + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("result",) + FUNCTION = "replace" + CATEGORY = "Utilitools/Text" + + def replace(self, text, find, replace, count=-1): + if count == -1: + return (text.replace(find, replace),) + else: + return (text.replace(find, replace, count),) + + +NODE_CLASS_MAPPINGS = { + "UtilTextConcat": UtilTextConcat, + "UtilStringReplace": UtilStringReplace, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "UtilTextConcat": "Text Concatenation", + "UtilStringReplace": "String Replace", +} \ No newline at end of file diff --git a/nodes/workflow.py b/nodes/workflow.py new file mode 100644 index 0000000..609b654 --- /dev/null +++ b/nodes/workflow.py @@ -0,0 +1,54 @@ +class UtilPassthrough: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input": ("*",), + } + } + + RETURN_TYPES = ("*",) + RETURN_NAMES = ("output",) + FUNCTION = "passthrough" + CATEGORY = "Utilitools/Workflow" + + def passthrough(self, input): + return (input,) + + +class UtilCounter: + def __init__(self): + self.counter = 0 + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "increment": ("INT", {"default": 1, "min": -999, "max": 999, "step": 1}), + }, + "optional": { + "reset": ("BOOLEAN", {"default": False}), + } + } + + RETURN_TYPES = ("INT",) + RETURN_NAMES = ("count",) + FUNCTION = "count" + CATEGORY = "Utilitools/Workflow" + + def count(self, increment, reset=False): + if reset: + self.counter = 0 + self.counter += increment + return (self.counter,) + + +NODE_CLASS_MAPPINGS = { + "UtilPassthrough": UtilPassthrough, + "UtilCounter": UtilCounter, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "UtilPassthrough": "Passthrough", + "UtilCounter": "Counter", +} \ No newline at end of file