Add initial DD Nodes custom nodes package structure

Set up the ComfyUI custom nodes package with text, image, and math
utility nodes including text concatenation/replace/display, image
resize/batch/size, and integer/float math operations with type
conversions. Includes package entry point and updated README.

https://claude.ai/code/session_01PdwEaTFxk45a8FjpqWqZbK
This commit is contained in:
Claude
2026-02-16 02:10:47 +00:00
parent 2501084451
commit 4e5c1184c5
6 changed files with 373 additions and 1 deletions
+36 -1
View File
@@ -1 +1,36 @@
# ComfyUI_DD_Nodes
# ComfyUI DD Nodes
Custom nodes for [ComfyUI](https://github.com/comfyanonymous/ComfyUI) by Digital Divas.
## Nodes
### Text Nodes
- **DD Text Concatenate** - Concatenates two text strings with an optional separator
- **DD Text Replace** - Replaces occurrences of a search string within text
- **DD Show Text** - Displays text output in the UI
### Image Nodes
- **DD Image Resize** - Resizes an image to specified dimensions (nearest, bilinear, bicubic, area)
- **DD Image Batch** - Combines two images into a single batch
- **DD Get Image Size** - Returns the width and height of an image
### Math Nodes
- **DD Integer Math** - Performs math operations on two integers (add, subtract, multiply, divide, modulo, power, min, max)
- **DD Float Math** - Performs math operations on two floats
- **DD Int to Float** - Converts an integer to a float
- **DD Float to Int** - Converts a float to an integer with rounding mode selection
## Installation
Clone this repository into your ComfyUI `custom_nodes` directory:
```bash
cd ComfyUI/custom_nodes
git clone https://github.com/digital-divas-admin/ComfyUI_DD_Nodes.git
```
Restart ComfyUI to load the nodes.
## License
MIT License - see [LICENSE](LICENSE) for details.
+21
View File
@@ -0,0 +1,21 @@
"""ComfyUI DD Nodes - Custom nodes for ComfyUI by Digital Divas."""
from .nodes.text_nodes import (
NODE_CLASS_MAPPINGS as TEXT_NODES,
NODE_DISPLAY_NAME_MAPPINGS as TEXT_NAMES,
)
from .nodes.image_nodes import (
NODE_CLASS_MAPPINGS as IMAGE_NODES,
NODE_DISPLAY_NAME_MAPPINGS as IMAGE_NAMES,
)
from .nodes.math_nodes import (
NODE_CLASS_MAPPINGS as MATH_NODES,
NODE_DISPLAY_NAME_MAPPINGS as MATH_NAMES,
)
NODE_CLASS_MAPPINGS = {**TEXT_NODES, **IMAGE_NODES, **MATH_NODES}
NODE_DISPLAY_NAME_MAPPINGS = {**TEXT_NAMES, **IMAGE_NAMES, **MATH_NAMES}
WEB_DIRECTORY = "./js"
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
View File
+99
View File
@@ -0,0 +1,99 @@
import torch
class DD_ImageResize:
"""Resizes an image to the specified dimensions."""
RESIZE_METHODS = ["nearest", "bilinear", "bicubic", "area"]
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
"width": ("INT", {"default": 512, "min": 1, "max": 8192, "step": 1}),
"height": ("INT", {"default": 512, "min": 1, "max": 8192, "step": 1}),
"method": (cls.RESIZE_METHODS, {"default": "bilinear"}),
},
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("image",)
FUNCTION = "resize"
CATEGORY = "DD Nodes/Image"
def resize(self, image, width, height, method):
# image shape: (batch, height, width, channels)
batch = image.shape[0]
# Convert to (batch, channels, height, width) for interpolation
img = image.permute(0, 3, 1, 2)
resized = torch.nn.functional.interpolate(
img, size=(height, width), mode=method
)
# Convert back to (batch, height, width, channels)
resized = resized.permute(0, 2, 3, 1)
return (resized,)
class DD_ImageBatch:
"""Combines two images into a single batch."""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image_a": ("IMAGE",),
"image_b": ("IMAGE",),
},
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("image",)
FUNCTION = "batch"
CATEGORY = "DD Nodes/Image"
def batch(self, image_a, image_b):
if image_a.shape[1:] != image_b.shape[1:]:
# Resize image_b to match image_a dimensions
img_b = image_b.permute(0, 3, 1, 2)
img_b = torch.nn.functional.interpolate(
img_b,
size=(image_a.shape[1], image_a.shape[2]),
mode="bilinear",
)
image_b = img_b.permute(0, 2, 3, 1)
return (torch.cat([image_a, image_b], dim=0),)
class DD_GetImageSize:
"""Returns the width and height of an image."""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
},
}
RETURN_TYPES = ("INT", "INT")
RETURN_NAMES = ("width", "height")
FUNCTION = "get_size"
CATEGORY = "DD Nodes/Image"
def get_size(self, image):
# image shape: (batch, height, width, channels)
return (image.shape[2], image.shape[1])
NODE_CLASS_MAPPINGS = {
"DD_ImageResize": DD_ImageResize,
"DD_ImageBatch": DD_ImageBatch,
"DD_GetImageSize": DD_GetImageSize,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"DD_ImageResize": "DD Image Resize",
"DD_ImageBatch": "DD Image Batch",
"DD_GetImageSize": "DD Get Image Size",
}
+133
View File
@@ -0,0 +1,133 @@
class DD_IntMath:
"""Performs basic math operations on two integers."""
OPERATIONS = ["add", "subtract", "multiply", "divide", "modulo", "power", "min", "max"]
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"a": ("INT", {"default": 0, "min": -2**31, "max": 2**31 - 1}),
"b": ("INT", {"default": 0, "min": -2**31, "max": 2**31 - 1}),
"operation": (cls.OPERATIONS, {"default": "add"}),
},
}
RETURN_TYPES = ("INT", "FLOAT")
RETURN_NAMES = ("int_result", "float_result")
FUNCTION = "compute"
CATEGORY = "DD Nodes/Math"
def compute(self, a, b, operation):
ops = {
"add": lambda: a + b,
"subtract": lambda: a - b,
"multiply": lambda: a * b,
"divide": lambda: a / b if b != 0 else 0,
"modulo": lambda: a % b if b != 0 else 0,
"power": lambda: a**b,
"min": lambda: min(a, b),
"max": lambda: max(a, b),
}
result = ops[operation]()
return (int(result), float(result))
class DD_FloatMath:
"""Performs basic math operations on two floats."""
OPERATIONS = ["add", "subtract", "multiply", "divide", "power", "min", "max"]
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"a": ("FLOAT", {"default": 0.0, "min": -1e10, "max": 1e10, "step": 0.01}),
"b": ("FLOAT", {"default": 0.0, "min": -1e10, "max": 1e10, "step": 0.01}),
"operation": (cls.OPERATIONS, {"default": "add"}),
},
}
RETURN_TYPES = ("FLOAT", "INT")
RETURN_NAMES = ("float_result", "int_result")
FUNCTION = "compute"
CATEGORY = "DD Nodes/Math"
def compute(self, a, b, operation):
ops = {
"add": lambda: a + b,
"subtract": lambda: a - b,
"multiply": lambda: a * b,
"divide": lambda: a / b if b != 0 else 0.0,
"power": lambda: a**b,
"min": lambda: min(a, b),
"max": lambda: max(a, b),
}
result = ops[operation]()
return (float(result), int(result))
class DD_IntToFloat:
"""Converts an integer to a float."""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"value": ("INT", {"default": 0, "min": -2**31, "max": 2**31 - 1}),
},
}
RETURN_TYPES = ("FLOAT",)
RETURN_NAMES = ("float_value",)
FUNCTION = "convert"
CATEGORY = "DD Nodes/Math"
def convert(self, value):
return (float(value),)
class DD_FloatToInt:
"""Converts a float to an integer."""
ROUND_MODES = ["round", "floor", "ceil", "truncate"]
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"value": ("FLOAT", {"default": 0.0, "min": -1e10, "max": 1e10, "step": 0.01}),
"mode": (cls.ROUND_MODES, {"default": "round"}),
},
}
RETURN_TYPES = ("INT",)
RETURN_NAMES = ("int_value",)
FUNCTION = "convert"
CATEGORY = "DD Nodes/Math"
def convert(self, value, mode):
import math
modes = {
"round": lambda: round(value),
"floor": lambda: math.floor(value),
"ceil": lambda: math.ceil(value),
"truncate": lambda: int(value),
}
return (modes[mode](),)
NODE_CLASS_MAPPINGS = {
"DD_IntMath": DD_IntMath,
"DD_FloatMath": DD_FloatMath,
"DD_IntToFloat": DD_IntToFloat,
"DD_FloatToInt": DD_FloatToInt,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"DD_IntMath": "DD Integer Math",
"DD_FloatMath": "DD Float Math",
"DD_IntToFloat": "DD Int to Float",
"DD_FloatToInt": "DD Float to Int",
}
+84
View File
@@ -0,0 +1,84 @@
class DD_TextConcatenate:
"""Concatenates two text strings with an optional separator."""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"text_a": ("STRING", {"default": "", "multiline": True}),
"text_b": ("STRING", {"default": "", "multiline": True}),
},
"optional": {
"separator": ("STRING", {"default": ", "}),
},
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("text",)
FUNCTION = "concatenate"
CATEGORY = "DD Nodes/Text"
def concatenate(self, text_a, text_b, separator=", "):
if not text_a and not text_b:
return ("",)
if not text_a:
return (text_b,)
if not text_b:
return (text_a,)
return (f"{text_a}{separator}{text_b}",)
class DD_TextReplace:
"""Replaces occurrences of a search string within text."""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"text": ("STRING", {"default": "", "multiline": True}),
"find": ("STRING", {"default": ""}),
"replace": ("STRING", {"default": ""}),
},
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("text",)
FUNCTION = "replace"
CATEGORY = "DD Nodes/Text"
def replace(self, text, find, replace):
return (text.replace(find, replace),)
class DD_ShowText:
"""Displays text output in the UI."""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"text": ("STRING", {"forceInput": True}),
},
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("text",)
FUNCTION = "show"
CATEGORY = "DD Nodes/Text"
OUTPUT_NODE = True
def show(self, text):
return {"ui": {"text": [text]}, "result": (text,)}
NODE_CLASS_MAPPINGS = {
"DD_TextConcatenate": DD_TextConcatenate,
"DD_TextReplace": DD_TextReplace,
"DD_ShowText": DD_ShowText,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"DD_TextConcatenate": "DD Text Concatenate",
"DD_TextReplace": "DD Text Replace",
"DD_ShowText": "DD Show Text",
}