From 23dd8cb1a2ac2cdea8285596725cb866e00b1612 Mon Sep 17 00:00:00 2001 From: BRADSEC <7948876+bradsec@users.noreply.github.com> Date: Sat, 20 Jun 2026 19:30:49 +1000 Subject: [PATCH] feat: add V3 (comfy_api) dual-API wrapper and input tooltips; bump 2.1.4 V3 io.ComfyNode schema delegates to the V1 class so logic is not forked. comfy_entrypoint exported import-guarded; V1 NODE_CLASS_MAPPINGS remain authoritative on the 0.25.0 loader (no double registration). Verified ComfyUI 0.25.0. --- README.md | 4 ++ __init__.py | 8 +++- nodes.py | 102 +++++++++++++++++++++++++++++++++++++++++++++---- pyproject.toml | 2 +- 4 files changed, 106 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 964535a..92da89c 100755 --- a/README.md +++ b/README.md @@ -76,6 +76,10 @@ Resolution Selector Plus (model: All, resolution: "1920x1080 (16:9 Landscape)") --- +## Compatibility + +Dual API: exports the legacy V1 `NODE_CLASS_MAPPINGS` and a V3 `comfy_entrypoint` (`comfy_api`). Verified on ComfyUI 0.25.0. On builds whose loader reads `NODE_CLASS_MAPPINGS` first, the V1 path is used and `comfy_entrypoint` is skipped, so nodes are not registered twice. + ## Support If you find this useful, please consider [starring the repo](https://github.com/bradsec/ComfyUI_ResolutionSelectorPlus). Stars help other people discover these nodes. diff --git a/__init__.py b/__init__.py index 02dd9e2..432bb11 100644 --- a/__init__.py +++ b/__init__.py @@ -2,4 +2,10 @@ from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS WEB_DIRECTORY = "./js" -__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', 'WEB_DIRECTORY'] \ No newline at end of file +__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', 'WEB_DIRECTORY'] + +try: + from .nodes import comfy_entrypoint # noqa: F401 + __all__.append('comfy_entrypoint') +except ImportError: + pass \ No newline at end of file diff --git a/nodes.py b/nodes.py index 97a2884..8984bda 100755 --- a/nodes.py +++ b/nodes.py @@ -247,6 +247,26 @@ def parse_resolution_string(resolution_str): raise ValueError(f"Invalid resolution format: {resolution_str}") +# Tooltips (shared by both API wrappers). +TIP_MODEL = "Image model. Filters the resolution presets to that model's recommended sizes. 'All' shows every preset." +TIP_RESOLUTION = "Preset resolution. The list filters to the selected model." +TIP_MULT = "Multiplies the preset width and height (1x to 4x)." +TIP_BATCH = "Number of latent samples in the preset latent batch." +TIP_CW = "Custom width in pixels. 0 disables the custom outputs. Must satisfy the model's divisibility and bounds." +TIP_CH = "Custom height in pixels. 0 disables the custom outputs. Must satisfy the model's divisibility and bounds." +TIP_CMULT = "Multiplies the custom width and height (1x to 4x)." +TIP_CBATCH = "Number of latent samples in the custom latent batch." + +OUTPUT_TIPS = { + "width": "Preset width in pixels after multiplier.", + "height": "Preset height in pixels after multiplier.", + "latent": "Empty latent for the preset resolution.", + "custom_width": "Custom width after multiplier, or 0 when custom is disabled.", + "custom_height": "Custom height after multiplier, or 0 when custom is disabled.", + "custom_latent": "Empty latent for the custom resolution.", +} + + class ResolutionSelector: """ Enhanced resolution selector supporting multiple image generation models. @@ -276,20 +296,24 @@ class ResolutionSelector: return { "required": { "model": (model_list, { - "default": "SDXL" + "default": "SDXL", + "tooltip": TIP_MODEL, }), "resolution": (all_resolutions, { - "default": "1024x1024 (1:1 Square)" + "default": "1024x1024 (1:1 Square)", + "tooltip": TIP_RESOLUTION, }), "resolution_multiplier": (["1x", "2x", "3x", "4x"], { - "default": "1x" + "default": "1x", + "tooltip": TIP_MULT, }), "batch_size": ("INT", { "default": 1, "min": 1, "max": 64, "step": 1, - "display": "number" + "display": "number", + "tooltip": TIP_BATCH, }), }, "optional": { @@ -298,24 +322,28 @@ class ResolutionSelector: "min": 0, "max": 4096, "step": 8, - "display": "number" + "display": "number", + "tooltip": TIP_CW, }), "custom_height": ("INT", { "default": 0, "min": 0, "max": 4096, "step": 8, - "display": "number" + "display": "number", + "tooltip": TIP_CH, }), "custom_multiplier": (["1x", "2x", "3x", "4x"], { - "default": "1x" + "default": "1x", + "tooltip": TIP_CMULT, }), "custom_batch": ("INT", { "default": 1, "min": 1, "max": 64, "step": 1, - "display": "number" + "display": "number", + "tooltip": TIP_CBATCH, }), } } @@ -463,3 +491,61 @@ NODE_CLASS_MAPPINGS = { NODE_DISPLAY_NAME_MAPPINGS = { "ResolutionSelectorPlus": "Resolution Selector Plus", } + + +# -- V3 node (import-guarded comfy_api schema) --------------------------------- +# Thin adapter: execute delegates to the V1 class so the logic is never forked. + +try: + from comfy_api.v0_0_2 import io, ComfyExtension + + class ResolutionSelectorPlusV3(io.ComfyNode): + @classmethod + def define_schema(cls) -> io.Schema: + model_list = ["All"] + list(MODEL_RESOLUTIONS.keys()) + all_resolutions = get_all_resolutions() + mult = ["1x", "2x", "3x", "4x"] + return io.Schema( + node_id="ResolutionSelectorPlus", + display_name="Resolution Selector Plus", + category="utils", + description="Model-aware resolution presets, custom dimensions, and empty latent output.", + inputs=[ + io.Combo.Input("model", options=model_list, default="SDXL", tooltip=TIP_MODEL), + io.Combo.Input("resolution", options=all_resolutions, + default="1024x1024 (1:1 Square)", tooltip=TIP_RESOLUTION), + io.Combo.Input("resolution_multiplier", options=mult, default="1x", tooltip=TIP_MULT), + io.Int.Input("batch_size", default=1, min=1, max=64, step=1, tooltip=TIP_BATCH), + io.Int.Input("custom_width", default=0, min=0, max=4096, step=8, optional=True, tooltip=TIP_CW), + io.Int.Input("custom_height", default=0, min=0, max=4096, step=8, optional=True, tooltip=TIP_CH), + io.Combo.Input("custom_multiplier", options=mult, default="1x", optional=True, tooltip=TIP_CMULT), + io.Int.Input("custom_batch", default=1, min=1, max=64, step=1, optional=True, tooltip=TIP_CBATCH), + ], + outputs=[ + io.Int.Output(id="width", display_name="width", tooltip=OUTPUT_TIPS["width"]), + io.Int.Output(id="height", display_name="height", tooltip=OUTPUT_TIPS["height"]), + io.Latent.Output(id="latent", display_name="latent", tooltip=OUTPUT_TIPS["latent"]), + io.Int.Output(id="custom_width", display_name="custom_width", tooltip=OUTPUT_TIPS["custom_width"]), + io.Int.Output(id="custom_height", display_name="custom_height", tooltip=OUTPUT_TIPS["custom_height"]), + io.Latent.Output(id="custom_latent", display_name="custom_latent", tooltip=OUTPUT_TIPS["custom_latent"]), + ], + ) + + @classmethod + def execute(cls, model, resolution, resolution_multiplier="1x", batch_size=1, + custom_width=0, custom_height=0, custom_multiplier="1x", custom_batch=1) -> io.NodeOutput: + result = ResolutionSelector().select_resolution( + model, resolution, resolution_multiplier, batch_size, + custom_width, custom_height, custom_multiplier, custom_batch) + return io.NodeOutput(*result) + + class ResolutionSelectorPlusExtension(ComfyExtension): + async def get_node_list(self): + return [ResolutionSelectorPlusV3] + + async def comfy_entrypoint() -> "ResolutionSelectorPlusExtension": + return ResolutionSelectorPlusExtension() + +except ImportError: + # Older ComfyUI without comfy_api: V1 mappings above are the only path. + pass diff --git a/pyproject.toml b/pyproject.toml index 670ea2d..47a2b75 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "comfyui_resolutionselectorplus" description = "Custom node for ComfyUI providing easy resolution and empty latent selection for image generation workflows." -version = "2.1.3" +version = "2.1.4" license = {file = "LICENSE"} [project.urls]