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:
@@ -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
@@ -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"]
|
||||
@@ -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",
|
||||
}
|
||||
@@ -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",
|
||||
}
|
||||
@@ -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",
|
||||
}
|
||||
Reference in New Issue
Block a user