8.3 KiB
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/
│ └── <theme>_config.json # Data lists (characters, locations, styles, effects...)
└── theme_handlers/
├── __init__.py # Exports all handler classes
├── base_handler.py # BaseThemeHandler (abstract)
└── <theme>_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).
{
"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.<key>") to pull from your config.
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__:
# 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:
HANDLER_CLASS_NAMES_MAP = {
# ...
"my_theme": "MyThemeHandler",
# ...
}
4b. _init_mappings() (line ~210) — maps display name with emoji → internal name:
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:
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_MAPvalue matches the class name exactly, and the class is exported intheme_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_mappingsmatches a key inHANDLER_CLASS_NAMES_MAP - Theme not in Multiple Prompt Generator → verify the display name in
theme_categoriesmatches the display name inall_emoji_mappingsexactly