diff --git a/README.md b/README.md index 77c7217..1fc8327 100644 --- a/README.md +++ b/README.md @@ -1,67 +1,59 @@ -# ComfyUI Simple Variables +# SimpleVariables -A lightweight custom node pack for ComfyUI that provides **Set/Get variable nodes** for storing and retrieving any data by name across your workflow. +A lightweight ComfyUI node pack for storing and retrieving any data by name across your workflow. ## Installation ```bash cd ComfyUI/custom_nodes -git clone https://github.com/jessesep/ComfyUI-SimpleVariables.git +git clone https://github.com/jessesep/SimpleVariables.git ``` -Restart ComfyUI and search for **"variable"** in the node menu. +Restart ComfyUI and search for **"variable"**. ## Nodes -| Node | Description | Category | -|------|-------------|----------| -| **Set Variable** | Store any data with a custom name | `variables` | -| **Get Variable** | Retrieve stored data by name | `variables` | -| **List Variables** | Show all stored variables | `variables` | +| Node | Description | +|------|-------------| +| **Set Variable** | Store any data with a name, outputs preview | +| **Get Variable** | Retrieve data by name, outputs preview | +| **List Variables** | Show all stored variables | +| **Clear Variables** | Clear all stored variables | ## Usage -### Basic Example - ``` -[Load Image] → [Set Variable] (name: "reference") +[Load Image] → [Set Variable] (name: "ref") ↓ - (passes through) + value (passthrough) + preview → [Show Text] → "[ref] = Tensor [1, 512, 512, 3]" -... elsewhere in workflow ... +... elsewhere ... -[Get Variable] (name: "reference") → [Any Node] +[Get Variable] (name: "ref") → value → [Any Node] + → preview → "[ref] = Tensor [1, 512, 512, 3]" ``` -### How It Works +## Features -1. **Set Variable** stores any input data with your chosen variable name -2. Data passes through, so you can continue your workflow normally -3. **Get Variable** retrieves stored data anywhere by name -4. Works with ALL ComfyUI data types: Image, Mask, Latent, Model, CLIP, VAE, Conditioning, String, Int, Float, and custom types +- **Universal** - Works with all data types (Image, Mask, Latent, Model, etc.) +- **Preview output** - See what's stored (type + shape/value) +- **Passthrough** - Set Variable passes data through +- **Debug** - List Variables shows all stored data -### Tips +## Preview Examples -- Use descriptive names like `"upscaled_image"` or `"final_mask"` -- **List Variables** helps debug what's currently stored -- Variables persist for the ComfyUI session (cleared on restart) -- If a variable doesn't exist, you'll see available variable names in the error - -## Why Use This? - -- **Reduce wire clutter** - "Wireless" data transfer across your workflow -- **Reuse outputs** - Store once, use multiple times -- **Simplify complex workflows** - No more spaghetti connections -- **Universal** - One node handles all data types - -## Deprecated Nodes - -Type-specific nodes (Set Image Variable, Set Mask Variable, etc.) are available under `variables/deprecated` for backward compatibility, but **Set/Get Variable handles all types** so they're no longer needed. +| Data Type | Preview Output | +|-----------|----------------| +| Image | `[my_image] = Tensor [1, 512, 512, 3]` | +| String | `[prompt] = "a beautiful sunset..."` | +| Int | `[steps] = 20` | +| Model | `[my_model] = BaseModel` | ## License -MIT License - Free to use and modify. +MIT License ## Credits -Created by jessesep with assistance from Claude (Anthropic). +Created by jessesep with Claude (Anthropic). diff --git a/__init__.py b/__init__.py index db9555e..df55d1a 100644 --- a/__init__.py +++ b/__init__.py @@ -1,12 +1,11 @@ """ -ComfyUI Simple Variables - Set and Get nodes for storing/retrieving any data by name +SimpleVariables - Set and Get nodes for storing/retrieving any data by name in ComfyUI """ # Global storage for variables _variable_storage = {} -# ============== MAIN NODES ============== class SetVariable: """Store any data with a custom variable name""" @classmethod @@ -17,16 +16,34 @@ class SetVariable: }, "optional": { "value": ("*",), + }, + "hidden": { + "unique_id": "UNIQUE_ID", } } - RETURN_TYPES = ("*",) - RETURN_NAMES = ("value",) + RETURN_TYPES = ("*", "STRING",) + RETURN_NAMES = ("value", "preview",) FUNCTION = "set_var" CATEGORY = "variables" + OUTPUT_NODE = True - def set_var(self, variable_name, value=None): + def set_var(self, variable_name, unique_id=None, value=None): _variable_storage[variable_name] = value - return (value,) + + # Create preview string + if value is None: + preview = f"[{variable_name}] = None" + else: + type_name = type(value).__name__ + if hasattr(value, 'shape'): + preview = f"[{variable_name}] = {type_name} {list(value.shape)}" + elif isinstance(value, (str, int, float, bool)): + val_str = str(value)[:50] + "..." if len(str(value)) > 50 else str(value) + preview = f"[{variable_name}] = {val_str}" + else: + preview = f"[{variable_name}] = {type_name}" + + return (value, preview,) class GetVariable: @@ -38,15 +55,32 @@ class GetVariable: "variable_name": ("STRING", {"default": "my_var"}), } } - RETURN_TYPES = ("*",) - RETURN_NAMES = ("value",) + RETURN_TYPES = ("*", "STRING",) + RETURN_NAMES = ("value", "preview",) FUNCTION = "get_var" CATEGORY = "variables" def get_var(self, variable_name): - if variable_name in _variable_storage: - return (_variable_storage[variable_name],) - raise ValueError(f"Variable '{variable_name}' not found. Available: {list(_variable_storage.keys())}") + if variable_name not in _variable_storage: + available = list(_variable_storage.keys()) + raise ValueError(f"Variable '{variable_name}' not found. Available: {available}") + + value = _variable_storage[variable_name] + + # Create preview string + if value is None: + preview = f"[{variable_name}] = None" + else: + type_name = type(value).__name__ + if hasattr(value, 'shape'): + preview = f"[{variable_name}] = {type_name} {list(value.shape)}" + elif isinstance(value, (str, int, float, bool)): + val_str = str(value)[:50] + "..." if len(str(value)) > 50 else str(value) + preview = f"[{variable_name}] = {val_str}" + else: + preview = f"[{variable_name}] = {type_name}" + + return (value, preview,) class ListVariables: @@ -60,287 +94,56 @@ class ListVariables: CATEGORY = "variables" def list_vars(self): - var_list = "\n".join([f"{k}: {type(v).__name__}" for k, v in _variable_storage.items()]) - return (var_list if var_list else "(no variables set)",) + if not _variable_storage: + return ("(no variables set)",) + + lines = [] + for name, value in _variable_storage.items(): + if value is None: + lines.append(f"{name}: None") + elif hasattr(value, 'shape'): + lines.append(f"{name}: {type(value).__name__} {list(value.shape)}") + elif isinstance(value, (str, int, float, bool)): + val_str = str(value)[:30] + "..." if len(str(value)) > 30 else str(value) + lines.append(f"{name}: {val_str}") + else: + lines.append(f"{name}: {type(value).__name__}") + + return ("\n".join(lines),) -# ============== DEPRECATED TYPE-SPECIFIC NODES ============== -# These still work but Set/Get Variable handles all types - -class SetImageVariable: +class ClearVariables: + """Clear all stored variables""" @classmethod def INPUT_TYPES(cls): - return {"required": {"image": ("IMAGE",), "variable_name": ("STRING", {"default": "my_image"})}} - RETURN_TYPES = ("IMAGE",) - FUNCTION = "set_var" - CATEGORY = "variables/deprecated" - def set_var(self, image, variable_name): - _variable_storage[variable_name] = image - return (image,) - -class GetImageVariable: - @classmethod - def INPUT_TYPES(cls): - return {"required": {"variable_name": ("STRING", {"default": "my_image"})}} - RETURN_TYPES = ("IMAGE",) - FUNCTION = "get_var" - CATEGORY = "variables/deprecated" - def get_var(self, variable_name): - if variable_name in _variable_storage: return (_variable_storage[variable_name],) - raise ValueError(f"Variable '{variable_name}' not found") - -class SetMaskVariable: - @classmethod - def INPUT_TYPES(cls): - return {"required": {"mask": ("MASK",), "variable_name": ("STRING", {"default": "my_mask"})}} - RETURN_TYPES = ("MASK",) - FUNCTION = "set_var" - CATEGORY = "variables/deprecated" - def set_var(self, mask, variable_name): - _variable_storage[variable_name] = mask - return (mask,) - -class GetMaskVariable: - @classmethod - def INPUT_TYPES(cls): - return {"required": {"variable_name": ("STRING", {"default": "my_mask"})}} - RETURN_TYPES = ("MASK",) - FUNCTION = "get_var" - CATEGORY = "variables/deprecated" - def get_var(self, variable_name): - if variable_name in _variable_storage: return (_variable_storage[variable_name],) - raise ValueError(f"Variable '{variable_name}' not found") - -class SetLatentVariable: - @classmethod - def INPUT_TYPES(cls): - return {"required": {"latent": ("LATENT",), "variable_name": ("STRING", {"default": "my_latent"})}} - RETURN_TYPES = ("LATENT",) - FUNCTION = "set_var" - CATEGORY = "variables/deprecated" - def set_var(self, latent, variable_name): - _variable_storage[variable_name] = latent - return (latent,) - -class GetLatentVariable: - @classmethod - def INPUT_TYPES(cls): - return {"required": {"variable_name": ("STRING", {"default": "my_latent"})}} - RETURN_TYPES = ("LATENT",) - FUNCTION = "get_var" - CATEGORY = "variables/deprecated" - def get_var(self, variable_name): - if variable_name in _variable_storage: return (_variable_storage[variable_name],) - raise ValueError(f"Variable '{variable_name}' not found") - -class SetConditioningVariable: - @classmethod - def INPUT_TYPES(cls): - return {"required": {"conditioning": ("CONDITIONING",), "variable_name": ("STRING", {"default": "my_cond"})}} - RETURN_TYPES = ("CONDITIONING",) - FUNCTION = "set_var" - CATEGORY = "variables/deprecated" - def set_var(self, conditioning, variable_name): - _variable_storage[variable_name] = conditioning - return (conditioning,) - -class GetConditioningVariable: - @classmethod - def INPUT_TYPES(cls): - return {"required": {"variable_name": ("STRING", {"default": "my_cond"})}} - RETURN_TYPES = ("CONDITIONING",) - FUNCTION = "get_var" - CATEGORY = "variables/deprecated" - def get_var(self, variable_name): - if variable_name in _variable_storage: return (_variable_storage[variable_name],) - raise ValueError(f"Variable '{variable_name}' not found") - -class SetModelVariable: - @classmethod - def INPUT_TYPES(cls): - return {"required": {"model": ("MODEL",), "variable_name": ("STRING", {"default": "my_model"})}} - RETURN_TYPES = ("MODEL",) - FUNCTION = "set_var" - CATEGORY = "variables/deprecated" - def set_var(self, model, variable_name): - _variable_storage[variable_name] = model - return (model,) - -class GetModelVariable: - @classmethod - def INPUT_TYPES(cls): - return {"required": {"variable_name": ("STRING", {"default": "my_model"})}} - RETURN_TYPES = ("MODEL",) - FUNCTION = "get_var" - CATEGORY = "variables/deprecated" - def get_var(self, variable_name): - if variable_name in _variable_storage: return (_variable_storage[variable_name],) - raise ValueError(f"Variable '{variable_name}' not found") - -class SetClipVariable: - @classmethod - def INPUT_TYPES(cls): - return {"required": {"clip": ("CLIP",), "variable_name": ("STRING", {"default": "my_clip"})}} - RETURN_TYPES = ("CLIP",) - FUNCTION = "set_var" - CATEGORY = "variables/deprecated" - def set_var(self, clip, variable_name): - _variable_storage[variable_name] = clip - return (clip,) - -class GetClipVariable: - @classmethod - def INPUT_TYPES(cls): - return {"required": {"variable_name": ("STRING", {"default": "my_clip"})}} - RETURN_TYPES = ("CLIP",) - FUNCTION = "get_var" - CATEGORY = "variables/deprecated" - def get_var(self, variable_name): - if variable_name in _variable_storage: return (_variable_storage[variable_name],) - raise ValueError(f"Variable '{variable_name}' not found") - -class SetVaeVariable: - @classmethod - def INPUT_TYPES(cls): - return {"required": {"vae": ("VAE",), "variable_name": ("STRING", {"default": "my_vae"})}} - RETURN_TYPES = ("VAE",) - FUNCTION = "set_var" - CATEGORY = "variables/deprecated" - def set_var(self, vae, variable_name): - _variable_storage[variable_name] = vae - return (vae,) - -class GetVaeVariable: - @classmethod - def INPUT_TYPES(cls): - return {"required": {"variable_name": ("STRING", {"default": "my_vae"})}} - RETURN_TYPES = ("VAE",) - FUNCTION = "get_var" - CATEGORY = "variables/deprecated" - def get_var(self, variable_name): - if variable_name in _variable_storage: return (_variable_storage[variable_name],) - raise ValueError(f"Variable '{variable_name}' not found") - -class SetStringVariable: - @classmethod - def INPUT_TYPES(cls): - return {"required": {"string": ("STRING", {"forceInput": True}), "variable_name": ("STRING", {"default": "my_string"})}} + return { + "required": { + "clear": ("BOOLEAN", {"default": False}), + } + } RETURN_TYPES = ("STRING",) - FUNCTION = "set_var" - CATEGORY = "variables/deprecated" - def set_var(self, string, variable_name): - _variable_storage[variable_name] = string - return (string,) + RETURN_NAMES = ("status",) + FUNCTION = "clear_vars" + CATEGORY = "variables" -class GetStringVariable: - @classmethod - def INPUT_TYPES(cls): - return {"required": {"variable_name": ("STRING", {"default": "my_string"})}} - RETURN_TYPES = ("STRING",) - FUNCTION = "get_var" - CATEGORY = "variables/deprecated" - def get_var(self, variable_name): - if variable_name in _variable_storage: return (_variable_storage[variable_name],) - raise ValueError(f"Variable '{variable_name}' not found") - -class SetIntVariable: - @classmethod - def INPUT_TYPES(cls): - return {"required": {"value": ("INT", {"forceInput": True}), "variable_name": ("STRING", {"default": "my_int"})}} - RETURN_TYPES = ("INT",) - FUNCTION = "set_var" - CATEGORY = "variables/deprecated" - def set_var(self, value, variable_name): - _variable_storage[variable_name] = value - return (value,) - -class GetIntVariable: - @classmethod - def INPUT_TYPES(cls): - return {"required": {"variable_name": ("STRING", {"default": "my_int"})}} - RETURN_TYPES = ("INT",) - FUNCTION = "get_var" - CATEGORY = "variables/deprecated" - def get_var(self, variable_name): - if variable_name in _variable_storage: return (_variable_storage[variable_name],) - raise ValueError(f"Variable '{variable_name}' not found") - -class SetFloatVariable: - @classmethod - def INPUT_TYPES(cls): - return {"required": {"value": ("FLOAT", {"forceInput": True}), "variable_name": ("STRING", {"default": "my_float"})}} - RETURN_TYPES = ("FLOAT",) - FUNCTION = "set_var" - CATEGORY = "variables/deprecated" - def set_var(self, value, variable_name): - _variable_storage[variable_name] = value - return (value,) - -class GetFloatVariable: - @classmethod - def INPUT_TYPES(cls): - return {"required": {"variable_name": ("STRING", {"default": "my_float"})}} - RETURN_TYPES = ("FLOAT",) - FUNCTION = "get_var" - CATEGORY = "variables/deprecated" - def get_var(self, variable_name): - if variable_name in _variable_storage: return (_variable_storage[variable_name],) - raise ValueError(f"Variable '{variable_name}' not found") + def clear_vars(self, clear): + if clear: + count = len(_variable_storage) + _variable_storage.clear() + return (f"Cleared {count} variable(s)",) + return ("Not cleared (set clear=True)",) -# ============== NODE MAPPINGS ============== NODE_CLASS_MAPPINGS = { - # Main nodes "SetVariable": SetVariable, "GetVariable": GetVariable, "ListVariables": ListVariables, - # Deprecated type-specific nodes - "SetImageVariable": SetImageVariable, - "GetImageVariable": GetImageVariable, - "SetMaskVariable": SetMaskVariable, - "GetMaskVariable": GetMaskVariable, - "SetLatentVariable": SetLatentVariable, - "GetLatentVariable": GetLatentVariable, - "SetConditioningVariable": SetConditioningVariable, - "GetConditioningVariable": GetConditioningVariable, - "SetModelVariable": SetModelVariable, - "GetModelVariable": GetModelVariable, - "SetClipVariable": SetClipVariable, - "GetClipVariable": GetClipVariable, - "SetVaeVariable": SetVaeVariable, - "GetVaeVariable": GetVaeVariable, - "SetStringVariable": SetStringVariable, - "GetStringVariable": GetStringVariable, - "SetIntVariable": SetIntVariable, - "GetIntVariable": GetIntVariable, - "SetFloatVariable": SetFloatVariable, - "GetFloatVariable": GetFloatVariable, + "ClearVariables": ClearVariables, } NODE_DISPLAY_NAME_MAPPINGS = { - # Main nodes "SetVariable": "Set Variable", "GetVariable": "Get Variable", "ListVariables": "List Variables", - # Deprecated - "SetImageVariable": "Set Image Variable (deprecated)", - "GetImageVariable": "Get Image Variable (deprecated)", - "SetMaskVariable": "Set Mask Variable (deprecated)", - "GetMaskVariable": "Get Mask Variable (deprecated)", - "SetLatentVariable": "Set Latent Variable (deprecated)", - "GetLatentVariable": "Get Latent Variable (deprecated)", - "SetConditioningVariable": "Set Conditioning Variable (deprecated)", - "GetConditioningVariable": "Get Conditioning Variable (deprecated)", - "SetModelVariable": "Set Model Variable (deprecated)", - "GetModelVariable": "Get Model Variable (deprecated)", - "SetClipVariable": "Set CLIP Variable (deprecated)", - "GetClipVariable": "Get CLIP Variable (deprecated)", - "SetVaeVariable": "Set VAE Variable (deprecated)", - "GetVaeVariable": "Get VAE Variable (deprecated)", - "SetStringVariable": "Set String Variable (deprecated)", - "GetStringVariable": "Get String Variable (deprecated)", - "SetIntVariable": "Set Int Variable (deprecated)", - "GetIntVariable": "Get Int Variable (deprecated)", - "SetFloatVariable": "Set Float Variable (deprecated)", - "GetFloatVariable": "Get Float Variable (deprecated)", + "ClearVariables": "Clear Variables", }