# Theme Creation Guide This guide documents how to add a new theme to both the **IsulionMegaPromptV3** (single theme) and **Isulion Multiple Prompt Generator** nodes. ## Architecture Overview ``` Core_Nodes/ ├── mega_prompt_V3.py # Node + ThemeRegistry + HANDLER_CLASS_NAMES_MAP ├── mega_prompt_all_themes.py # Multiple Prompt Generator (theme categories) ├── configs/ │ └── _config.json # Data lists (characters, locations, styles, effects...) └── theme_handlers/ ├── __init__.py # Exports all handler classes ├── base_handler.py # BaseThemeHandler (abstract) └── _handler.py # Handler logic ``` ## Step-by-step: Add theme `my_theme` You must touch **4 files** and optionally a **5th** for the Multiple Prompt Generator. --- ### 1. Create the JSON config file **File:** `Core_Nodes/configs/my_theme_config.json` The top-level key **must match the internal name** (snake_case). ```json { "my_theme": { "characters": ["knight", "wizard", "dragon"], "outfits": ["plate armor", "robe", "scales"], "poses": ["battle stance", "casting spell"], "expressions": ["determined", "mysterious"], "locations": ["castle", "enchanted forest"], "styles": ["gothic", "medieval fantasy"], "effects": ["magic aura", "dragon fire"], "lighting": ["dramatic sunset", "torchlight"], "times": ["golden hour", "midnight"], "weather": ["clear sky", "misty"], "moods": ["epic", "whimsical"] } } ``` Any category keys work — the handler reads them with dot notation: `"my_theme.characters"`. --- ### 2. Create the handler class **File:** `Core_Nodes/theme_handlers/my_theme_handler.py` Inherit `BaseThemeHandler` and implement `generate()`. Use `self._get_random_choice("my_theme.")` to pull from your config. ```python from typing import Dict from .base_handler import BaseThemeHandler class MyThemeHandler(BaseThemeHandler): """Handler for My Theme.""" def generate( self, custom_subject: str = "", custom_location: str = "", include_environment: bool = True, include_style: bool = True, include_effects: bool = True, ) -> Dict[str, str]: components = {} character = self._get_random_choice("my_theme.characters") outfit = self._get_random_choice("my_theme.outfits") pose = self._get_random_choice("my_theme.poses") expression = self._get_random_choice("my_theme.expressions") if custom_subject: components["subject"] = ( f"((masterful {character} rendition)) of {custom_subject}, " f"((wearing {outfit})), ((in {pose})), " f"((with {expression} expression))" ) else: components["subject"] = ( f"((masterful {character})) wearing ((detailed {outfit})), " f"((in {pose})), ((with {expression} expression))" ) if include_environment: if custom_location: components["environment"] = ( f"in ((beautiful {custom_location})), " f"((detailed environment))" ) else: location = self._get_random_choice("my_theme.locations") time = self._get_random_choice("my_theme.times") weather = self._get_random_choice("my_theme.weather") components["environment"] = ( f"in ((stunning {location})) during (({time})) " f"with (({weather}))" ) if include_style: style = self._get_random_choice("my_theme.styles") components["style"] = ( f"((masterfully rendered in {style} style)), " f"((professional composition)), 8k resolution" ) if include_effects: effect = self._get_random_choice("my_theme.effects") mood = self._get_random_choice("my_theme.moods") lighting = self._get_random_choice("my_theme.lighting") components["effects"] = ( f"with (({effect})), (({mood} atmosphere)), " f"((beautiful {lighting}))" ) return components ``` **Contract:** `generate()` must return a `Dict[str, str]` with keys `subject`, `environment`, `style`, `effects`. --- ### 3. Register the handler in `theme_handlers/__init__.py` Add an import and include the class name in `__all__`: ```python # Import (group with similar themes) from .my_theme_handler import MyThemeHandler # In __all__ list __all__ = [ # ... 'MyThemeHandler', # ... ] ``` --- ### 4. Register in `mega_prompt_V3.py` — two places **File:** `Core_Nodes/mega_prompt_V3.py` **4a. `HANDLER_CLASS_NAMES_MAP` (line ~84)** — maps internal name → handler class name string: ```python HANDLER_CLASS_NAMES_MAP = { # ... "my_theme": "MyThemeHandler", # ... } ``` **4b. `_init_mappings()` (line ~210)** — maps display name with emoji → internal name: ```python all_emoji_mappings = { # ... "⚔️ My Theme": "my_theme", # ... } ``` The display name (with emoji) is what users see in the ComfyUI dropdown. The internal name links to the handler and config. --- ### 5. (Optional) Add to Isulion Multiple Prompt Generator **File:** `Core_Nodes/mega_prompt_all_themes.py` Add your display name to the appropriate category in `theme_categories`: ```python theme_categories = { # ... "Science Fiction & Fantasy": [ # ... "⚔️ My Theme", # <-- add here # ... ], # ... } ``` The node automatically picks it up from there. If your theme doesn't fit an existing category, you can create a new one. --- ## Summary checklist | # | File | Action | |---|------|--------| | 1 | `Core_Nodes/configs/my_theme_config.json` | Create JSON with data lists | | 2 | `Core_Nodes/theme_handlers/my_theme_handler.py` | Create handler class extending `BaseThemeHandler` | | 3 | `Core_Nodes/theme_handlers/__init__.py` | Add import + `__all__` entry | | 4 | `Core_Nodes/mega_prompt_V3.py` | Add to `HANDLER_CLASS_NAMES_MAP` + `all_emoji_mappings` | | 5 | `Core_Nodes/mega_prompt_all_themes.py` | Add to `theme_categories` category list | After making these changes, **restart ComfyUI**. ## How the config manager loads data `ConfigManager` (`Core_Nodes/configs/config_manager.py`) reads **all** `*.json` files from `Core_Nodes/configs/` on startup and merges them into a single `self.configs` dict. The top-level key of each JSON file becomes the lookup key. Your config file can be named anything — `my_theme_config.json` — as long as its top-level key matches the internal name. A handler calls `self._get_random_choice("my_theme.characters")` which resolves to `self.config.get_config("my_theme.characters")` → `self.configs["my_theme"]["characters"]`. ## Naming conventions | What | Convention | Example | |------|-----------|---------| | Internal name | `snake_case` | `my_theme` | | Handler class | `PascalCase` + `ThemeHandler` suffix | `MyThemeHandler` | | Config file | `snake_case_config.json` | `my_theme_config.json` | | Display name | Emoji + Title Case | `⚔️ My Theme` | | Config JSON key | Same as internal name | `"my_theme": { ... }` | | Handler filename | `snake_case_handler.py` | `my_theme_handler.py` | ## Available helper methods on `BaseThemeHandler` | Method | Purpose | |--------|---------| | `_get_random_choice(key)` | Pick one random item from the config list at `"theme.key"` | | `_get_safe_random_choice(key, default)` | Same but returns `default` if not found | | `_get_random_choices(key, count)` | Pick `count` random items (with repeats if needed) | ## Troubleshooting - **"Handler not found"** → check that `HANDLER_CLASS_NAMES_MAP` value matches the class name exactly, and the class is exported in `theme_handlers/__init__.py` - **"Config key not found"** → verify the top-level key in your JSON matches the internal name, and the JSON is valid (no trailing commas) - **Theme not in dropdown** → check that internal name in `all_emoji_mappings` matches a key in `HANDLER_CLASS_NAMES_MAP` - **Theme not in Multiple Prompt Generator** → verify the display name in `theme_categories` matches the display name in `all_emoji_mappings` exactly