Migrate tag injector and random number nodes to ComfyExtension API

Migrated TagInjectorSingle, TagInjectorDuo, TagInjector, TagInjectorLarge, RandomFloat, RandomInt, and NextSeed from legacy node implementation to new ComfyExtension API with io.ComfyNode base class. Replaced INPUT_TYPES, RETURN_TYPES, RETURN_NAMES, FUNCTION, and CATEGORY class attributes with define_schema() method using io.Schema. Converted legacy methods to execute() with type-annotated parameters and io.NodeOutput return type
This commit is contained in:
Vince Liem
2025-11-28 04:25:43 +01:00
parent b010ea7c81
commit b2e7c7ffeb
11 changed files with 658 additions and 58 deletions
+14 -34
View File
@@ -16,41 +16,9 @@ from custom_nodes.comfyui_gr85.nodes.random_seed.next_seed import NextSeed
from custom_nodes.comfyui_gr85.nodes.random_numbers.random_float import RandomFloat
from custom_nodes.comfyui_gr85.nodes.random_numbers.random_int import RandomInt
NODE_CLASS_MAPPINGS = {
"GR85_ImageDimensionResizer": ImageDimensionResizer,
"GR85_ImageSizer": ImageSizer,
"GR85_ImageSizerAll": ImageSizerAll,
"GR85_RandomRatio": RandomRatio,
NODE_CLASS_MAPPINGS = {}
"GR85_SeedBasedOutputSelector": SeedBasedOutputSelector,
"GR85_TagInjector": TagInjector,
"GR85_TagInjectorSingle": TagInjectorSingle,
"GR85_TagInjectorDuo": TagInjectorDuo,
"GR85_TagInjectorLarge": TagInjectorLarge,
"GR85_RandomFloat": RandomFloat,
"GR85_RandomInt": RandomInt,
"GR85_NextSeed": NextSeed,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"GR85_ImageDimensionResizer": "Image Dimension Resizer",
"GR85_ImageSizer": "Image Sizer",
"GR85_ImageSizerAll": "Image Sizer All",
"GR85_RandomRatio": "Random Ratio",
"GR85_SeedBasedOutputSelector": "Seed Based Output Selector",
"GR85_TagInjector": "Tag Injector",
"GR85_TagInjectorSingle": "Tag Injector Single",
"GR85_TagInjectorDuo": "Tag Injector Duo",
"GR85_TagInjectorLarge": "Tag Injector Large",
"GR85_RandomFloat": "Random Float",
"GR85_RandomInt": "Random Int",
"GR85_NextSeed": "Next Seed",
}
NODE_DISPLAY_NAME_MAPPINGS = {}
class GR85Extension(ComfyExtension):
@@ -58,6 +26,18 @@ class GR85Extension(ComfyExtension):
async def get_node_list(self) -> list[type[io.ComfyNode]]:
return [
SimpleWildcardPicker,
SeedBasedOutputSelector,
TagInjectorSingle,
TagInjectorDuo,
TagInjector,
TagInjectorLarge,
RandomFloat,
RandomInt,
NextSeed,
ImageDimensionResizer,
ImageSizerAll,
ImageSizer,
RandomRatio,
]
+174 -5
View File
@@ -1,7 +1,8 @@
import re
from comfy_api.latest import io
class TagInjectorSingle:
class TagInjectorSingle(io.ComfyNode):
def __init__(self):
pass
@@ -23,6 +24,49 @@ class TagInjectorSingle:
FUNCTION = "inject_tag"
CATEGORY = "GR85/Prompt/Tags"
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="GR85_TagInjectorSingle",
display_name="Tag Injector Single",
category="GR85/Prompt/Tags",
inputs=[
io.String.Input(
"template",
multiline=True,
default="__elements__",
),
io.String.Input(
"tag_1",
multiline=True,
default="",
),
io.String.Input(
"tag_name_1",
default="elements",
),
],
outputs=[
io.String.Output(),
io.String.Output(),
],
)
@classmethod
def execute(
cls,
template: str,
tag_1: str = "",
tag_name_1: str = "elements",
) -> io.NodeOutput:
instance = cls()
tagged_text, placeholders = instance.inject_tag(
template=template,
tag_1=tag_1,
tag_name_1=tag_name_1,
)
return io.NodeOutput(tagged_text, placeholders)
def inject_tag(self, template, tag_1=None, tag_name_1="elements"):
"""
Inject a single tag into the template based on the given tag name and value.
@@ -44,7 +88,7 @@ class TagInjectorSingle:
return template, placeholders
class TagInjectorDuo:
class TagInjectorDuo(io.ComfyNode):
def __init__(self):
pass
@@ -66,7 +110,63 @@ class TagInjectorDuo:
RETURN_TYPES = ("STRING", "STRING")
RETURN_NAMES = ("tagged_text", "placeholders")
FUNCTION = "inject_tag"
CATEGORY = "GR85/Prompt"
CATEGORY = "GR85/Prompt/Tags"
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="GR85_TagInjectorDuo",
display_name="Tag Injector Duo",
category="GR85/Prompt/Tags",
inputs=[
io.String.Input(
"template",
multiline=True,
default="__elements__",
),
io.String.Input(
"tag_1",
multiline=True,
default="",
),
io.String.Input(
"tag_2",
multiline=True,
default="",
),
io.String.Input(
"tag_name_1",
default="elements",
),
io.String.Input(
"tag_name_2",
default="stuff",
),
],
outputs=[
io.String.Output(),
io.String.Output(),
],
)
@classmethod
def execute(
cls,
template: str,
tag_1: str = "",
tag_2: str = "",
tag_name_1: str = "elements",
tag_name_2: str = "stuff",
) -> io.NodeOutput:
instance = cls()
tagged_text, placeholders = instance.inject_tag(
template=template,
tag_1=tag_1,
tag_2=tag_2,
tag_name_1=tag_name_1,
tag_name_2=tag_name_2,
)
return io.NodeOutput(tagged_text, placeholders)
def inject_tag(self, template, tag_1=None, tag_2=None,
tag_name_1="elements", tag_name_2="stuff"):
@@ -90,7 +190,7 @@ class TagInjectorDuo:
return template, placeholders
class TagInjector:
class TagInjector(io.ComfyNode):
def __init__(self):
pass
@@ -114,7 +214,76 @@ class TagInjector:
RETURN_TYPES = ("STRING", "STRING")
RETURN_NAMES = ("tagged_text", "placeholders")
FUNCTION = "inject_tag"
CATEGORY = "GR85/Prompt"
CATEGORY = "GR85/Prompt/Tags"
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="GR85_TagInjector",
display_name="Tag Injector",
category="GR85/Prompt/Tags",
inputs=[
io.String.Input(
"template",
multiline=True,
default="__elements__",
),
io.String.Input(
"tag_1",
multiline=True,
default="",
),
io.String.Input(
"tag_2",
multiline=True,
default="",
),
io.String.Input(
"tag_3",
multiline=True,
default="",
),
io.String.Input(
"tag_name_1",
default="elements",
),
io.String.Input(
"tag_name_2",
default="stuff",
),
io.String.Input(
"tag_name_3",
default="things",
),
],
outputs=[
io.String.Output(),
io.String.Output(),
],
)
@classmethod
def execute(
cls,
template: str,
tag_1: str = "",
tag_2: str = "",
tag_3: str = "",
tag_name_1: str = "elements",
tag_name_2: str = "stuff",
tag_name_3: str = "things",
) -> io.NodeOutput:
instance = cls()
tagged_text, placeholders = instance.inject_tag(
template=template,
tag_1=tag_1,
tag_2=tag_2,
tag_3=tag_3,
tag_name_1=tag_name_1,
tag_name_2=tag_name_2,
tag_name_3=tag_name_3,
)
return io.NodeOutput(tagged_text, placeholders)
def inject_tag(self, template, tag_1=None, tag_2=None, tag_3=None,
tag_name_1="elements", tag_name_2="stuff", tag_name_3="things"):
+96 -1
View File
@@ -1,7 +1,8 @@
import re
from comfy_api.latest import io
class TagInjectorLarge:
class TagInjectorLarge(io.ComfyNode):
def __init__(self):
pass
@@ -41,6 +42,100 @@ class TagInjectorLarge:
FUNCTION = "inject_tag"
CATEGORY = "GR85/Prompt/Tags"
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="GR85_TagInjectorLarge",
display_name="Tag Injector Large",
category="GR85/Prompt/Tags",
inputs=[
io.String.Input(
"template",
multiline=True,
default=(
"In a __location__ under a __weather__ sky, a __personality__ person shows "
"__emotion__ while wearing __style__. The __time__ is perfect for a __action__ "
"amidst __mood__."
),
),
io.String.Input("tag_1", multiline=True, default=""),
io.String.Input("tag_2", multiline=True, default=""),
io.String.Input("tag_3", multiline=True, default=""),
io.String.Input("tag_4", multiline=True, default=""),
io.String.Input("tag_5", multiline=True, default=""),
io.String.Input("tag_6", multiline=True, default=""),
io.String.Input("tag_7", multiline=True, default=""),
io.String.Input("tag_8", multiline=True, default=""),
io.String.Input("tag_9", multiline=True, default=""),
io.String.Input("tag_10", multiline=True, default=""),
io.String.Input("tag_name_1", default="location"),
io.String.Input("tag_name_2", default="color"),
io.String.Input("tag_name_3", default="object"),
io.String.Input("tag_name_4", default="emotion"),
io.String.Input("tag_name_5", default="weather"),
io.String.Input("tag_name_6", default="personality"),
io.String.Input("tag_name_7", default="time"),
io.String.Input("tag_name_8", default="action"),
io.String.Input("tag_name_9", default="style"),
io.String.Input("tag_name_10", default="mood"),
],
outputs=[
io.String.Output(),
io.String.Output(),
],
)
@classmethod
def execute(
cls,
template: str,
tag_1: str = "",
tag_2: str = "",
tag_3: str = "",
tag_4: str = "",
tag_5: str = "",
tag_6: str = "",
tag_7: str = "",
tag_8: str = "",
tag_9: str = "",
tag_10: str = "",
tag_name_1: str = "location",
tag_name_2: str = "color",
tag_name_3: str = "object",
tag_name_4: str = "emotion",
tag_name_5: str = "weather",
tag_name_6: str = "personality",
tag_name_7: str = "time",
tag_name_8: str = "action",
tag_name_9: str = "style",
tag_name_10: str = "mood",
) -> io.NodeOutput:
instance = cls()
tagged_text, placeholders = instance.inject_tag(
template=template,
tag_1=tag_1,
tag_2=tag_2,
tag_3=tag_3,
tag_4=tag_4,
tag_5=tag_5,
tag_6=tag_6,
tag_7=tag_7,
tag_8=tag_8,
tag_9=tag_9,
tag_10=tag_10,
tag_name_1=tag_name_1,
tag_name_2=tag_name_2,
tag_name_3=tag_name_3,
tag_name_4=tag_name_4,
tag_name_5=tag_name_5,
tag_name_6=tag_name_6,
tag_name_7=tag_name_7,
tag_name_8=tag_name_8,
tag_name_9=tag_name_9,
tag_name_10=tag_name_10,
)
return io.NodeOutput(tagged_text, placeholders)
def inject_tag(self, template, tag_1=None, tag_2=None, tag_3=None, tag_4=None, tag_5=None,
tag_6=None, tag_7=None, tag_8=None, tag_9=None, tag_10=None,
tag_name_1="location", tag_name_2="color", tag_name_3="object", tag_name_4="emotion",
+56 -1
View File
@@ -1,6 +1,7 @@
import random
from comfy_api.latest import io
class RandomFloat:
class RandomFloat(io.ComfyNode):
"""
A ComfyUI node class that generates a random float based on given inputs.
@@ -29,6 +30,60 @@ class RandomFloat:
FUNCTION = "generate_random_float"
CATEGORY = "GR85/Random/Numbers"
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="GR85_RandomFloat",
display_name="Random Float",
category="GR85/Random/Numbers",
inputs=[
io.Int.Input(
"seed",
default=0,
min=0,
),
io.Float.Input(
"min_value",
default=0.0,
min=-1e-10,
max=1e10,
step=0.0001,
),
io.Float.Input(
"max_value",
default=1.0,
min=-1e-10,
max=1e10,
step=0.0001,
),
io.Int.Input(
"decimal_places",
default=10,
min=0,
),
],
outputs=[
io.Float.Output(),
],
)
@classmethod
def execute(
cls,
seed: int,
min_value: float,
max_value: float,
decimal_places: int,
) -> io.NodeOutput:
instance = cls()
(value,) = instance.generate_random_float(
seed=seed,
min_value=min_value,
max_value=max_value,
decimal_places=decimal_places,
)
return io.NodeOutput(value)
def generate_random_float(self, seed: int, min_value: float, max_value: float, decimal_places: int) -> tuple:
"""
Generates a random float based on the given seed, min, max, and decimal places.
+47 -1
View File
@@ -1,6 +1,7 @@
import random
from comfy_api.latest import io
class RandomInt:
class RandomInt(io.ComfyNode):
"""
A ComfyUI node class that generates a random integer based on given inputs.
@@ -28,6 +29,51 @@ class RandomInt:
FUNCTION = "generate_random_int"
CATEGORY = "GR85/Random/Numbers"
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="GR85_RandomInt",
display_name="Random Int",
category="GR85/Random/Numbers",
inputs=[
io.Int.Input(
"seed",
default=0,
min=0,
),
io.Int.Input(
"min_value",
default=0,
min=-10000000000,
max=10000000000,
),
io.Int.Input(
"max_value",
default=100,
min=-10000000000,
max=10000000000,
),
],
outputs=[
io.Int.Output(),
],
)
@classmethod
def execute(
cls,
seed: int,
min_value: int,
max_value: int,
) -> io.NodeOutput:
instance = cls()
(value,) = instance.generate_random_int(
seed=seed,
min_value=min_value,
max_value=max_value,
)
return io.NodeOutput(value)
def generate_random_int(self, seed: int, min_value: int, max_value: int) -> tuple:
"""
Generates a random integer based on the given seed, min, and max values.
+30 -1
View File
@@ -1,7 +1,8 @@
import random
from comfy_api.latest import io
class NextSeed:
class NextSeed(io.ComfyNode):
def __init__(self):
pass
@@ -18,6 +19,34 @@ class NextSeed:
FUNCTION = "next_seed"
CATEGORY = "GR85/Random/Seed"
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="GR85_NextSeed",
display_name="Next Seed",
category="GR85/Random/Seed",
inputs=[
io.Int.Input(
"seed",
default=0,
min=0,
max=0xFFFFFFFFFFFFFFFF,
),
],
outputs=[
io.Int.Output(),
],
)
@classmethod
def execute(
cls,
seed: int,
) -> io.NodeOutput:
instance = cls()
(value,) = instance.next_seed(seed=seed)
return io.NodeOutput(value)
def next_seed(self, seed):
random.seed(seed)
return (random.randint(0, 0xffffffffffffffff),)
+47 -1
View File
@@ -1,7 +1,8 @@
import math
from comfy_api.latest import io
class ImageDimensionResizer:
class ImageDimensionResizer(io.ComfyNode):
def __init__(self):
pass
@@ -36,6 +37,51 @@ class ImageDimensionResizer:
CATEGORY = "GR85/Resolution"
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="GR85_ImageDimensionResizer",
display_name="Image Dimension Resizer",
category="GR85/Resolution",
inputs=[
io.Int.Input(
"original_width",
default=512,
min=1,
max=4096,
),
io.Int.Input(
"original_height",
default=512,
min=1,
max=4096,
),
io.String.Input(
"target_dimensions",
default="512x512",
),
],
outputs=[
io.Int.Output(),
io.Int.Output(),
],
)
@classmethod
def execute(
cls,
original_width: int,
original_height: int,
target_dimensions: str,
) -> io.NodeOutput:
instance = cls()
width, height = instance.resize_dimensions(
original_width=original_width,
original_height=original_height,
target_dimensions=target_dimensions,
)
return io.NodeOutput(width, height)
def resize_dimensions(self, original_width, original_height, target_dimensions):
"""
Calculates new dimensions while maintaining pixel count
+62 -1
View File
@@ -1,6 +1,8 @@
import math
from comfy_api.latest import io
class ImageSizer:
class ImageSizer(io.ComfyNode):
def __init__(self):
pass
@@ -41,6 +43,65 @@ class ImageSizer:
CATEGORY = "GR85/Resolution"
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="GR85_ImageSizer",
display_name="Image Sizer",
category="GR85/Resolution",
inputs=[
io.String.Input(
"original_dimensions",
default="512x512",
),
io.Int.Input(
"width",
default=1,
min=1,
max=4096,
),
io.Int.Input(
"height",
default=1,
min=1,
max=4096,
),
io.String.Input(
"orientation",
default="original",
),
io.Int.Input(
"tolerance",
default=16,
min=1,
max=128,
),
],
outputs=[
io.Int.Output(),
io.Int.Output(),
],
)
@classmethod
def execute(
cls,
original_dimensions: str,
width: int,
height: int,
orientation: str,
tolerance: int,
) -> io.NodeOutput:
instance = cls()
new_width, new_height = instance.resize_dimensions(
original_dimensions=original_dimensions,
width=width,
height=height,
orientation=orientation,
tolerance=tolerance,
)
return io.NodeOutput(new_width, new_height)
def resize_dimensions(self, original_dimensions, width, height, orientation, tolerance):
source_width, source_height = map(int, original_dimensions.split("x"))
+64 -1
View File
@@ -1,6 +1,8 @@
import math
from comfy_api.latest import io
class ImageSizerAll:
class ImageSizerAll(io.ComfyNode):
def __init__(self):
pass
@@ -47,6 +49,67 @@ class ImageSizerAll:
CATEGORY = "GR85/Resolution"
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="GR85_ImageSizerAll",
display_name="Image Sizer All",
category="GR85/Resolution",
inputs=[
io.Int.Input(
"pixel_amount",
default=1024 * 1024,
min=64,
max=0xFFFFFFFFFFFFFFFF,
),
io.Int.Input(
"width",
default=1,
min=1,
max=4096,
),
io.Int.Input(
"height",
default=1,
min=1,
max=4096,
),
io.String.Input(
"orientation",
default="original",
),
io.Int.Input(
"tolerance",
default=16,
min=1,
max=128,
),
],
outputs=[
io.Int.Output(),
io.Int.Output(),
],
)
@classmethod
def execute(
cls,
pixel_amount: int,
width: int,
height: int,
orientation: str,
tolerance: int,
) -> io.NodeOutput:
instance = cls()
new_width, new_height = instance.resize_dimensions_all(
pixel_amount=pixel_amount,
width=width,
height=height,
orientation=orientation,
tolerance=tolerance,
)
return io.NodeOutput(new_width, new_height)
def resize_dimensions_all(self, pixel_amount, width, height, orientation, tolerance):
"""
Calculates new dimensions for an image while maintaining the same pixel count
+65 -1
View File
@@ -1,7 +1,8 @@
import random
from comfy_api.latest import io
class RandomRatio:
class RandomRatio(io.ComfyNode):
def __init__(self):
pass
@@ -49,6 +50,69 @@ class RandomRatio:
CATEGORY = "GR85/Resolution"
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="GR85_RandomRatio",
display_name="Random Ratio",
category="GR85/Resolution",
inputs=[
io.Int.Input(
"seed",
default=0,
min=0,
max=0xFFFFFFFFFFFFFFFF,
),
io.Int.Input(
"first_width",
default=1,
min=1,
max=4096,
),
io.Int.Input(
"first_height",
default=1,
min=1,
max=4096,
),
io.Int.Input(
"second_width",
default=1,
min=1,
max=4096,
),
io.Int.Input(
"second_height",
default=1,
min=1,
max=4096,
),
],
outputs=[
io.Int.Output(),
io.Int.Output(),
],
)
@classmethod
def execute(
cls,
seed: int,
first_width: int,
first_height: int,
second_width: int,
second_height: int,
) -> io.NodeOutput:
instance = cls()
width, height = instance.random_ratio(
seed=seed,
first_width=first_width,
first_height=first_height,
second_width=second_width,
second_height=second_height,
)
return io.NodeOutput(width, height)
def random_ratio(self, seed, first_width, first_height, second_width, second_height):
"""
Calculates a random ratio between a min and max ratio.
+3 -11
View File
@@ -1,11 +1,3 @@
# Image processing library for handling masks and drawing operations
opencv-python>=4.5.5.64
# Graph library for constructing and computing the Minimum Spanning Tree (MST)
networkx>=3.1
# Image processing library for skeletonization
scikit-image>=0.19.3
# Scientific computing library for distance calculations
scipy>=1.10.1
## comfyui_gr85
# This extension relies only on ComfyUI's bundled Python packages.
# No additional pip requirements are needed.