From b2e7c7ffeb63dcf23633db320c94fc0e623af63f Mon Sep 17 00:00:00 2001 From: Vince Liem Date: Fri, 28 Nov 2025 04:25:43 +0100 Subject: [PATCH] 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 --- __init__.py | 48 ++---- nodes/prompt_tags/tag_injector.py | 179 +++++++++++++++++++- nodes/prompt_tags/tag_injector_large.py | 97 ++++++++++- nodes/random_numbers/random_float.py | 57 ++++++- nodes/random_numbers/random_int.py | 48 +++++- nodes/random_seed/next_seed.py | 31 +++- nodes/resolution/image_dimension_resizer.py | 48 +++++- nodes/resolution/image_sizer.py | 63 ++++++- nodes/resolution/image_sizer_all.py | 65 ++++++- nodes/resolution/random_ratio.py | 66 +++++++- requirements.txt | 14 +- 11 files changed, 658 insertions(+), 58 deletions(-) diff --git a/__init__.py b/__init__.py index 93d9f74..3207ca0 100644 --- a/__init__.py +++ b/__init__.py @@ -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, ] diff --git a/nodes/prompt_tags/tag_injector.py b/nodes/prompt_tags/tag_injector.py index 7be1a59..4a22400 100644 --- a/nodes/prompt_tags/tag_injector.py +++ b/nodes/prompt_tags/tag_injector.py @@ -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"): diff --git a/nodes/prompt_tags/tag_injector_large.py b/nodes/prompt_tags/tag_injector_large.py index a4808f8..f36676a 100644 --- a/nodes/prompt_tags/tag_injector_large.py +++ b/nodes/prompt_tags/tag_injector_large.py @@ -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", diff --git a/nodes/random_numbers/random_float.py b/nodes/random_numbers/random_float.py index 5f7ac3c..757a65a 100644 --- a/nodes/random_numbers/random_float.py +++ b/nodes/random_numbers/random_float.py @@ -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. diff --git a/nodes/random_numbers/random_int.py b/nodes/random_numbers/random_int.py index 7ee56b1..fc0d3cf 100644 --- a/nodes/random_numbers/random_int.py +++ b/nodes/random_numbers/random_int.py @@ -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. diff --git a/nodes/random_seed/next_seed.py b/nodes/random_seed/next_seed.py index 8e44578..0587ca0 100644 --- a/nodes/random_seed/next_seed.py +++ b/nodes/random_seed/next_seed.py @@ -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),) diff --git a/nodes/resolution/image_dimension_resizer.py b/nodes/resolution/image_dimension_resizer.py index 71dc841..c4c6abf 100644 --- a/nodes/resolution/image_dimension_resizer.py +++ b/nodes/resolution/image_dimension_resizer.py @@ -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 diff --git a/nodes/resolution/image_sizer.py b/nodes/resolution/image_sizer.py index 7912d6a..b1d8fd7 100644 --- a/nodes/resolution/image_sizer.py +++ b/nodes/resolution/image_sizer.py @@ -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")) diff --git a/nodes/resolution/image_sizer_all.py b/nodes/resolution/image_sizer_all.py index 866949e..766ec4d 100644 --- a/nodes/resolution/image_sizer_all.py +++ b/nodes/resolution/image_sizer_all.py @@ -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 diff --git a/nodes/resolution/random_ratio.py b/nodes/resolution/random_ratio.py index 0a2d7a6..401cced 100644 --- a/nodes/resolution/random_ratio.py +++ b/nodes/resolution/random_ratio.py @@ -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. diff --git a/requirements.txt b/requirements.txt index c5fb12e..a428d8f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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.