Initial commit

This commit is contained in:
Aodhan Collins
2025-12-29 20:50:25 +00:00
commit 2417dcc090
50 changed files with 1596 additions and 0 deletions

83
README.md Normal file
View File

@@ -0,0 +1,83 @@
# AODH Pack
A set of custom ComfyUI nodes designed to streamline the creation of images for anime and video game characters using structured JSON data.
## Nodes
### Character JSON Reader
The **Character JSON Reader** node allows you to load character definitions from JSON files stored in the `characters` directory. It parses the JSON and outputs individual strings for various character attributes, making it easy to build complex prompts.
#### Features:
- **Automatic File Discovery**: Scans the `nodes/character_reader/characters/` folder for `.json` files.
- **Selection Modes**:
- `manual`: Select a specific file from the dropdown.
- `sequential`: Cycles through files based on the `index` input.
- `random`: Picks a random file using the `index` as a seed.
- **Structured Outputs**: Provides 21 distinct output pins covering identity, wardrobe, and style.
- **Prompt-Ready Formatting**: Automatically appends a comma to every non-empty output string.
- **Robustness**: Ensures all outputs are valid strings, even if fields are missing from the JSON.
### Resolution Reader
The **Resolution Reader** node reads resolution configurations from text files. Each line in the text file should follow the format: `width, height, upscale`.
#### Features:
- **Selection Modes**:
- `manual`: Uses the first line of the selected file.
- `sequential`: Cycles through lines in the file based on the `index` input.
- `random`: Picks a random line from the file using the `index` as a seed.
- **Outputs**: Provides `width` (INT), `height` (INT), and `upscale` (FLOAT).
#### JSON Structure:
Place your character JSON files in the `nodes/character_reader/characters/` directory. The expected format is:
```json
{
"character_id": "unique_id",
"identity": {
"base_specs": "...",
"hair": "...",
"eyes": "...",
"expression": "...",
"hands": "...",
"arms": "...",
"torso": "...",
"pelvis": "...",
"legs": "...",
"feet": "...",
"distinguishing_marks": "..."
},
"wardrobe": {
"inner_layer": "...",
"outer_layer": "...",
"lower_body": "...",
"footwear": "...",
"gloves": "...",
"accessories": "..."
},
"styles": {
"aesthetic": "...",
"primary_color": "...",
"secondary_color": "...",
"tertiary_color": "..."
},
"lora":{
"lora_name": "...",
"lora_Weight": "...",
"lora_triggers": "..."
}
}
```
## Installation
1. Clone this repository into your `ComfyUI/custom_nodes/` directory.
2. Add your character JSON files to the `nodes/character_reader/characters/` folder.
3. Add your resolution text files to the `nodes/resolution_reader/resolutions/` folder.
4. Restart ComfyUI.
## Usage
Find the node under the `AODH Pack` category in the ComfyUI node menu. Select your character file from the dropdown, and connect the output pins to your prompt encoding nodes.

3
__init__.py Normal file
View File

@@ -0,0 +1,3 @@
from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']

20
deploy.sh Normal file
View File

@@ -0,0 +1,20 @@
#!/bin/bash
# Deployment script for AODH Pack
TARGET_DIR="/home/aodhan/gitclones/ComfyUI/custom_nodes/aodh-pack"
SOURCE_DIR="$(pwd)"
echo "Deploying AODH Pack to $TARGET_DIR..."
# Create target directory if it doesn't exist
mkdir -p "$TARGET_DIR"
# Sync files, excluding git and other unnecessary files
# Using cp -r for simplicity, but rsync would be better if available
cp -r "$SOURCE_DIR/__init__.py" "$TARGET_DIR/"
cp -r "$SOURCE_DIR/nodes" "$TARGET_DIR/"
cp -r "$SOURCE_DIR/README.md" "$TARGET_DIR/"
cp -r "$SOURCE_DIR/requirements.txt" "$TARGET_DIR/"
echo "Deployment complete!"

20
nodes/__init__.py Normal file
View File

@@ -0,0 +1,20 @@
from .character_reader import NODE_CLASS_MAPPINGS as CR_CLASS, NODE_DISPLAY_NAME_MAPPINGS as CR_DISPLAY
from .resolution_reader import NODE_CLASS_MAPPINGS as RR_CLASS, NODE_DISPLAY_NAME_MAPPINGS as RR_DISPLAY
from .reenforcer import NODE_CLASS_MAPPINGS as RE_CLASS, NODE_DISPLAY_NAME_MAPPINGS as RE_DISPLAY
from .lora_from_string import NODE_CLASS_MAPPINGS as LFS_CLASS, NODE_DISPLAY_NAME_MAPPINGS as LFS_DISPLAY
NODE_CLASS_MAPPINGS = {
**CR_CLASS,
**RR_CLASS,
**RE_CLASS,
**LFS_CLASS
}
NODE_DISPLAY_NAME_MAPPINGS = {
**CR_DISPLAY,
**RR_DISPLAY,
**RE_DISPLAY,
**LFS_DISPLAY
}
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']

Binary file not shown.

View File

@@ -0,0 +1,9 @@
from .character_reader import CharacterJsonReader
NODE_CLASS_MAPPINGS = {
"CharacterJsonReader": CharacterJsonReader
}
NODE_DISPLAY_NAME_MAPPINGS = {
"CharacterJsonReader": "Character JSON Reader"
}

View File

@@ -0,0 +1,117 @@
import json
import os
import random
class CharacterJsonReader:
@classmethod
def INPUT_TYPES(s):
base_path = os.path.dirname(os.path.realpath(__file__))
char_dir = os.path.join(base_path, "characters")
characters = []
if os.path.exists(char_dir):
characters = sorted([f for f in os.listdir(char_dir) if f.endswith('.json')])
return {
"required": {
"character_file": (characters, ),
"selection_mode": (["manual", "sequential", "random"], {"default": "manual"}),
"repeat_count": ("INT", {"default": 1, "min": 1, "max": 100, "step": 1}),
}
}
@classmethod
def IS_CHANGED(s, character_file, selection_mode, repeat_count):
if selection_mode == "random" or selection_mode == "sequential":
return float("nan")
return character_file
RETURN_TYPES = (
"STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING",
"STRING", "STRING", "STRING", "STRING", "STRING", "STRING",
"STRING", "STRING", "STRING", "STRING",
"STRING", "FLOAT", "STRING"
)
RETURN_NAMES = (
"name", "base_specs", "hair", "eyes", "expression", "hands", "arms", "torso", "pelvis", "legs", "feet", "distinguishing_marks",
"inner_layer", "outer_layer", "lower_body", "footwear", "gloves", "accessories",
"aesthetic", "primary_color", "secondary_color", "tertiary_color",
"lora_name", "lora_weight", "lora_triggers"
)
FUNCTION = "read_character"
CATEGORY = "AODH Pack"
_current_index = 0
_current_count = 0
_last_selection = None
def read_character(self, character_file, selection_mode, repeat_count):
base_path = os.path.dirname(os.path.realpath(__file__))
char_dir = os.path.join(base_path, "characters")
characters = sorted([f for f in os.listdir(char_dir) if f.endswith('.json')])
if not characters:
return ("",) * 22 + ("", 0.0, "")
if selection_mode == "manual":
selected_file = character_file
elif selection_mode == "sequential":
if self.__class__._current_count >= repeat_count:
self.__class__._current_index += 1
self.__class__._current_count = 0
selected_file = characters[self.__class__._current_index % len(characters)]
self.__class__._current_count += 1
elif selection_mode == "random":
if self.__class__._current_count >= repeat_count or self.__class__._last_selection is None or self.__class__._last_selection not in characters:
self.__class__._last_selection = random.choice(characters)
self.__class__._current_count = 0
selected_file = self.__class__._last_selection
self.__class__._current_count += 1
else:
selected_file = character_file
file_path = os.path.join(char_dir, selected_file)
with open(file_path, 'r') as f:
data = json.load(f)
identity = data.get("identity", {})
wardrobe = data.get("wardrobe", {})
styles = data.get("styles", {})
lora = data.get("lora", {})
def get_val(obj, key, prepend_comma=True):
val = obj.get(key, "")
if val and prepend_comma and not val.startswith(","):
val = ", " + val
return val
return (
get_val(data, "character_id", prepend_comma=False),
get_val(identity, "base_specs"),
get_val(identity, "hair"),
get_val(identity, "eyes"),
get_val(identity, "expression"),
get_val(identity, "hands"),
get_val(identity, "arms"),
get_val(identity, "torso"),
get_val(identity, "pelvis"),
get_val(identity, "legs"),
get_val(identity, "feet"),
get_val(identity, "distinguishing_marks"),
get_val(wardrobe, "inner_layer"),
get_val(wardrobe, "outer_layer"),
get_val(wardrobe, "lower_body"),
get_val(wardrobe, "footwear"),
get_val(wardrobe, "gloves"),
get_val(wardrobe, "accessories"),
get_val(styles, "aesthetic"),
get_val(styles, "primary_color"),
get_val(styles, "secondary_color"),
get_val(styles, "tertiary_color"),
get_val(lora, "lora_name", prepend_comma=False),
float(lora.get("lora_weight", 0.0) if lora.get("lora_weight") else 0.0),
get_val(lora, "lora_triggers"),
)

View File

@@ -0,0 +1,35 @@
{
"character_id": "aerith_gainsborough",
"identity": {
"base_specs": "1girl, slender build, fair skin",
"hair": "long brown hair, braided, pink ribbon",
"eyes": "green eyes",
"expression": "cheerful expression",
"hands": "pink nails",
"arms": "",
"torso": "small breasts",
"pelvis": "",
"legs": "",
"feet": "",
"distinguishing_marks": ""
},
"wardrobe": {
"inner_layer": "",
"outer_layer": "pink dress, red bolero jacket",
"lower_body": "long skirt",
"footwear": "brown boots",
"gloves": "",
"accessories": "gold bracelets, flower basket"
},
"styles": {
"aesthetic": "floral, gentle, final fantasy style",
"primary_color": "pink",
"secondary_color": "red",
"tertiary_color": "brown"
},
"lora": {
"lora_name": "",
"lora_weight": 1.0,
"lora_triggers": ""
}
}

View File

@@ -0,0 +1,35 @@
{
"character_id": "android_18",
"identity": {
"base_specs": "1girl, slender build, fair skin",
"hair": "shoulder-length blonde hair, tucked behind one ear",
"eyes": "blue eyes",
"expression": "cool, indifferent expression",
"hands": "blue nails",
"arms": "",
"torso": "medium breasts",
"pelvis": "",
"legs": "",
"feet": "",
"distinguishing_marks": "gold hoop earrings"
},
"wardrobe": {
"inner_layer": "black short-sleeved shirt",
"outer_layer": "blue denim vest, Red Ribbon logo on back",
"lower_body": "blue denim skirt, black leggings",
"footwear": "brown boots",
"gloves": "",
"accessories": ""
},
"styles": {
"aesthetic": "90s casual, anime, dragon ball style",
"primary_color": "blue",
"secondary_color": "black",
"tertiary_color": "white"
},
"lora": {
"lora_name": "",
"lora_weight": 1.0,
"lora_triggers": ""
}
}

View File

@@ -0,0 +1,35 @@
{
"character_id": "anya_forger",
"identity": {
"base_specs": "1girl, small build, fair skin",
"hair": "short pink hair, two small horns (hair ornaments)",
"eyes": "green eyes",
"expression": "smirk",
"hands": "pink nails",
"arms": "",
"torso": "flat chest",
"pelvis": "",
"legs": "",
"feet": "",
"distinguishing_marks": ""
},
"wardrobe": {
"inner_layer": "",
"outer_layer": "black Eden Academy uniform, gold trim",
"lower_body": "uniform skirt",
"footwear": "black shoes, white socks",
"gloves": "",
"accessories": "black and gold hair cones"
},
"styles": {
"aesthetic": "cute, academic, spy x family style",
"primary_color": "pink",
"secondary_color": "black",
"tertiary_color": "gold"
},
"lora": {
"lora_name": "",
"lora_weight": 1.0,
"lora_triggers": ""
}
}

View File

@@ -0,0 +1,35 @@
{
"character_id": "bulma",
"identity": {
"base_specs": "1girl, slender build, fair skin",
"hair": "turquoise hair, ponytail",
"eyes": "blue eyes",
"expression": "energetic smile",
"hands": "turquoise nails",
"arms": "",
"torso": "medium breasts",
"pelvis": "",
"legs": "",
"feet": "",
"distinguishing_marks": ""
},
"wardrobe": {
"inner_layer": "",
"outer_layer": "pink dress, 'Bulma' text on front",
"lower_body": "dress",
"footwear": "purple sneakers, white socks",
"gloves": "purple gloves",
"accessories": "red hair ribbon, dragon radar"
},
"styles": {
"aesthetic": "retro-futuristic, anime, dragon ball style",
"primary_color": "pink",
"secondary_color": "turquoise",
"tertiary_color": "purple"
},
"lora": {
"lora_name": "",
"lora_weight": 1.0,
"lora_triggers": ""
}
}

View File

@@ -0,0 +1,35 @@
{
"character_id": "camilla",
"identity": {
"base_specs": "1girl, curvaceous build, fair skin",
"hair": "long wavy lavender hair, hair covering one eye",
"eyes": "purple eyes",
"expression": "seductive smile",
"hands": "purple nails",
"arms": "",
"torso": "large breasts",
"pelvis": "",
"legs": "",
"feet": "",
"distinguishing_marks": "black headband with horns"
},
"wardrobe": {
"inner_layer": "",
"outer_layer": "black armor, cleavage",
"lower_body": "black leggings, armored plates",
"footwear": "black armored boots",
"gloves": "",
"accessories": "purple cape, large axe"
},
"styles": {
"aesthetic": "dark fantasy, gothic, fire emblem style",
"primary_color": "black",
"secondary_color": "gold",
"tertiary_color": "purple"
},
"lora": {
"lora_name": "",
"lora_weight": 1.0,
"lora_triggers": ""
}
}

View File

@@ -0,0 +1,35 @@
{
"character_id": "cammy",
"identity": {
"base_specs": "1girl, muscular build, fair skin",
"hair": "long blonde hair, twin braids",
"eyes": "blue eyes",
"expression": "serious look",
"hands": "green nails",
"arms": "",
"torso": "medium breasts",
"pelvis": "",
"legs": "",
"feet": "",
"distinguishing_marks": "scar on left cheek, green camouflage paint on legs"
},
"wardrobe": {
"inner_layer": "",
"outer_layer": "green high-leg leotard",
"lower_body": "bare legs",
"footwear": "black combat boots, green socks",
"gloves": "red gauntlets",
"accessories": "red beret"
},
"styles": {
"aesthetic": "military, athletic, street fighter style",
"primary_color": "green",
"secondary_color": "red",
"tertiary_color": "black"
},
"lora": {
"lora_name": "",
"lora_weight": 1.0,
"lora_triggers": ""
}
}

View File

@@ -0,0 +1,35 @@
{
"character_id": "chun_li",
"identity": {
"base_specs": "1girl, muscular build, fair skin",
"hair": "black hair, hair buns",
"eyes": "brown eyes",
"expression": "determined smile",
"hands": "blue nails",
"arms": "",
"torso": "medium breasts",
"pelvis": "",
"legs": "thick thighs",
"feet": "",
"distinguishing_marks": ""
},
"wardrobe": {
"inner_layer": "",
"outer_layer": "blue qipao, gold embroidery, white accents",
"lower_body": "brown tights",
"footwear": "white combat boots",
"gloves": "",
"accessories": "white hair ribbons, spiked bracelets"
},
"styles": {
"aesthetic": "martial arts, traditional, street fighter style",
"primary_color": "blue",
"secondary_color": "white",
"tertiary_color": "gold"
},
"lora": {
"lora_name": "",
"lora_weight": 1.0,
"lora_triggers": ""
}
}

View File

@@ -0,0 +1,35 @@
{
"character_id": "ciri",
"identity": {
"base_specs": "1girl, athletic build, pale skin",
"hair": "ashen grey hair, messy bun",
"eyes": "emerald green eyes. mascara",
"expression": "determined look",
"hands": "brown nails",
"arms": "",
"torso": "medium breasts",
"pelvis": "",
"legs": "",
"feet": "",
"distinguishing_marks": "scar over eye"
},
"wardrobe": {
"inner_layer": "white blouse",
"outer_layer": "brown leather vest, silver studs",
"lower_body": "brown leather trousers",
"footwear": "brown leather boots",
"gloves": "brown leather gloves",
"accessories": "silver sword on back, witcher medallion"
},
"styles": {
"aesthetic": "gritty, fantasy, witcher style",
"primary_color": "white",
"secondary_color": "brown",
"tertiary_color": "silver"
},
"lora": {
"lora_name": "",
"lora_weight": 1.0,
"lora_triggers": ""
}
}

View File

@@ -0,0 +1,35 @@
{
"character_id": "hatsune_miku",
"identity": {
"base_specs": "1girl, slender build, fair skin",
"hair": "long turquoise hair, twin tails, floor-length",
"eyes": "turquoise eyes",
"expression": "cheerful smile",
"hands": "turquoise nails",
"arms": "01 tattoo on left shoulder",
"torso": "small breasts",
"pelvis": "",
"legs": "",
"feet": "",
"distinguishing_marks": ""
},
"wardrobe": {
"inner_layer": "",
"outer_layer": "grey sleeveless shirt, turquoise tie",
"lower_body": "grey miniskirt, turquoise trim",
"footwear": "black thigh-high boots, turquoise trim",
"gloves": "black arm warmers, turquoise trim",
"accessories": "hair ornament, headset"
},
"styles": {
"aesthetic": "vocaloid, futuristic, anime style",
"primary_color": "teal",
"secondary_color": "grey",
"tertiary_color": "black"
},
"lora": {
"lora_name": "",
"lora_weight": 1.0,
"lora_triggers": ""
}
}

View File

@@ -0,0 +1,35 @@
{
"character_id": "jessie",
"identity": {
"base_specs": "1girl, slender build, fair skin",
"hair": "long magenta hair, curved back",
"eyes": "blue eyes",
"expression": "arrogant smirk",
"hands": "white nails",
"arms": "",
"torso": "medium breasts",
"pelvis": "",
"legs": "",
"feet": "",
"distinguishing_marks": "green earrings"
},
"wardrobe": {
"inner_layer": "black crop top",
"outer_layer": "white Team Rocket uniform jacket, bare stomach, red R logo",
"lower_body": "white miniskirt",
"footwear": "black thigh-high boots",
"gloves": "black gloves",
"accessories": ""
},
"styles": {
"aesthetic": "villainous, anime, pokemon style",
"primary_color": "white",
"secondary_color": "magenta",
"tertiary_color": "black"
},
"lora": {
"lora_name": "",
"lora_weight": 1.0,
"lora_triggers": ""
}
}

View File

@@ -0,0 +1,35 @@
{
"character_id": "k/da_all_out_ahri",
"identity": {
"base_specs": "1girl, slender build, fair skin, fox ears",
"hair": "long blonde hair, flowing",
"eyes": "yellow eyes",
"expression": "charming smile",
"hands": "silver nails",
"arms": "",
"torso": "medium breasts",
"pelvis": "",
"legs": "",
"feet": "",
"distinguishing_marks": "whisker markings on cheeks, crystal tails"
},
"wardrobe": {
"inner_layer": "silver crop top",
"outer_layer": "white and silver jacket",
"lower_body": "black leather shorts",
"footwear": "black thigh-high boots",
"gloves": "",
"accessories": "crystal orb, silver jewelry"
},
"styles": {
"aesthetic": "pop star, mystical, k/da style",
"primary_color": "silver",
"secondary_color": "white",
"tertiary_color": "blue"
},
"lora": {
"lora_name": "Illustrious/Looks/KDA AhriIlluLoRA.safetensors",
"lora_weight": 1.0,
"lora_triggers": ""
}
}

View File

@@ -0,0 +1,35 @@
{
"character_id": "k/da_all_out_akali",
"identity": {
"base_specs": "1girl, athletic build, fair skin",
"hair": "long dark blue hair, blonde streaks, high ponytail",
"eyes": "blue eyes",
"expression": "cool, rebellious look",
"hands": "blue nails",
"arms": "tattoos on arms",
"torso": "small breasts",
"pelvis": "",
"legs": "",
"feet": "",
"distinguishing_marks": ""
},
"wardrobe": {
"inner_layer": "black crop top",
"outer_layer": "blue and silver motorcycle jacket",
"lower_body": "black leather pants",
"footwear": "blue sneakers",
"gloves": "black fingerless gloves",
"accessories": "kama and kunai"
},
"styles": {
"aesthetic": "pop star, street, k/da style",
"primary_color": "blue",
"secondary_color": "purple",
"tertiary_color": "silver"
},
"lora": {
"lora_name": "Illustrious/Looks/KDAAkaliIlluLoRA.safetensors",
"lora_weight": 1.0,
"lora_triggers": ""
}
}

View File

@@ -0,0 +1,35 @@
{
"character_id": "k/da_all_out_evelynn",
"identity": {
"base_specs": "1girl, curvaceous build, fair skin",
"hair": "light blue hair,",
"eyes": "yellow glowing eyes, slit pupils",
"expression": "seductive, confident look",
"hands": "long black claws, blue nails",
"arms": "",
"torso": "medium breasts",
"pelvis": "",
"legs": "",
"feet": "",
"distinguishing_marks": "two long lashers (shadow tendrils)"
},
"wardrobe": {
"inner_layer": "black leather bra",
"outer_layer": "iridescent blue jacket, fur collar",
"lower_body": "black leather skirt",
"footwear": "black high-heeled boots",
"gloves": "",
"accessories": "diamond earrings"
},
"styles": {
"aesthetic": "pop star, glamorous, k/da style",
"primary_color": "blue",
"secondary_color": "purple",
"tertiary_color": "silver"
},
"lora": {
"lora_name": "Illustrious/Looks/KDA EvelynnIlluLoRA.safetensors",
"lora_weight": 1.0,
"lora_triggers": ""
}
}

View File

@@ -0,0 +1,35 @@
{
"character_id": "k/da_all_out_kai'sa",
"identity": {
"base_specs": "1girl, athletic build, fair skin",
"hair": "long hair, purple hair, hair ornament, ponytail, green highlights",
"eyes": "purple eyes",
"expression": "focused expression",
"hands": "silver nails",
"arms": "",
"torso": "medium breasts",
"pelvis": "",
"legs": "",
"feet": "",
"distinguishing_marks": "purple markings under eyes, floating crystal cannons"
},
"wardrobe": {
"inner_layer": "silver bodysuit",
"outer_layer": "white and silver jacket",
"lower_body": "silver leggings",
"footwear": "silver high-heeled boots",
"gloves": "",
"accessories": "crystal shoulder pods"
},
"styles": {
"aesthetic": "pop star, futuristic, k/da style",
"primary_color": "silver",
"secondary_color": "white",
"tertiary_color": "purple"
},
"lora": {
"lora_name": "Illustrious/Looks/KDA KaisaIlluLoRA.safetensors",
"lora_weight": 1.0,
"lora_triggers": ""
}
}

View File

@@ -0,0 +1,35 @@
{
"character_id": "lara_croft_classic",
"identity": {
"base_specs": "1girl, athletic build, tan skin",
"hair": "long brown hair, single braid",
"eyes": "brown eyes",
"expression": "determined",
"hands": "",
"arms": "",
"torso": "",
"pelvis": "",
"legs": "",
"feet": "",
"distinguishing_marks": ""
},
"wardrobe": {
"inner_layer": "",
"outer_layer": "teal tank top, crop top",
"lower_body": "brown shorts",
"footwear": "brown combat boots, red laces",
"gloves": "black fingerless gloves",
"accessories": "dual thigh holsters, backpack, circular sunglasses"
},
"styles": {
"aesthetic": "adventure, retro, 90s style",
"primary_color": "teal",
"secondary_color": "brown",
"tertiary_color": "black"
},
"lora": {
"lora_name": "lara_croft_classic",
"lora_weight": 0.8,
"lora_triggers": "lara croft, classic outfit"
}
}

View File

@@ -0,0 +1,35 @@
{
"character_id": "lulu (ffx)",
"identity": {
"base_specs": "1girl, curvaceous build, fair skin",
"hair": "long black hair, complex braids, hairpins",
"eyes": "red eyes",
"expression": "stern expression",
"hands": "black nails",
"arms": "",
"torso": "large breasts",
"pelvis": "",
"legs": "",
"feet": "",
"distinguishing_marks": "dark purple lipstick"
},
"wardrobe": {
"inner_layer": "black corset",
"outer_layer": "black fur-trimmed dress, many belts on front",
"lower_body": "long skirt made of belts",
"footwear": "black boots",
"gloves": "",
"accessories": "moogle doll, silver jewelry"
},
"styles": {
"aesthetic": "gothic, ornate, final fantasy x style",
"primary_color": "black",
"secondary_color": "white",
"tertiary_color": "purple"
},
"lora": {
"lora_name": "Illustrious/Looks/Lulu DG illuLoRA_1337272.safetensors",
"lora_weight": 1.0,
"lora_triggers": ""
}
}

View File

@@ -0,0 +1,35 @@
{
"character_id": "majin_android_21",
"identity": {
"base_specs": "1girl, curvaceous build, pink skin",
"hair": "long voluminous white hair",
"eyes": "red eyes, black sclera",
"expression": "playful yet menacing smile",
"hands": "black claws, pink nails",
"arms": "",
"torso": "medium breasts",
"pelvis": "",
"legs": "",
"feet": "",
"distinguishing_marks": "pink skin, long tail, pointed ears"
},
"wardrobe": {
"inner_layer": "black tube top",
"outer_layer": "",
"lower_body": "white baggy pants",
"footwear": "black and yellow boots",
"gloves": "",
"accessories": "gold bracelets, gold neck ring"
},
"styles": {
"aesthetic": "supernatural, anime, dragon ball style",
"primary_color": "pink",
"secondary_color": "white",
"tertiary_color": "gold"
},
"lora": {
"lora_name": "",
"lora_weight": 1.0,
"lora_triggers": ""
}
}

View File

@@ -0,0 +1,35 @@
{
"character_id": "marin_kitagawa",
"identity": {
"base_specs": "1girl, slender build, fair skin",
"hair": "long blonde hair, pink tips",
"eyes": "pink eyes (contacts)",
"expression": "excited smile",
"hands": "long pink nails",
"arms": "",
"torso": "medium breasts",
"pelvis": "",
"legs": "",
"feet": "",
"distinguishing_marks": "piercings"
},
"wardrobe": {
"inner_layer": "",
"outer_layer": "white school shirt, loosely tied blue tie",
"lower_body": "blue plaid miniskirt",
"footwear": "black loafers, black socks",
"gloves": "",
"accessories": "choker, various bracelets"
},
"styles": {
"aesthetic": "gyaru, modern, anime style",
"primary_color": "white",
"secondary_color": "blue",
"tertiary_color": "pink"
},
"lora": {
"lora_name": "",
"lora_weight": 1.0,
"lora_triggers": ""
}
}

View File

@@ -0,0 +1,35 @@
{
"character_id": "nessa",
"identity": {
"base_specs": "1girl, athletic build, dark skin",
"hair": "long hair, light blue highlights",
"eyes": "blue eyes",
"expression": "confident smile",
"hands": "blue nails",
"arms": "",
"torso": "small breasts",
"pelvis": "",
"legs": "",
"feet": "",
"distinguishing_marks": "blue earrings"
},
"wardrobe": {
"inner_layer": "white and blue bikini top",
"outer_layer": "gym uniform, number 049",
"lower_body": "white and blue shorts",
"footwear": "blue and white sandals",
"gloves": "",
"accessories": "wristband, life buoy, pokeball"
},
"styles": {
"aesthetic": "sporty, aquatic, pokemon style",
"primary_color": "blue",
"secondary_color": "white",
"tertiary_color": "orange"
},
"lora": {
"lora_name": "",
"lora_weight": 1.0,
"lora_triggers": ""
}
}

View File

@@ -0,0 +1,35 @@
{
"character_id": "princess_peach",
"identity": {
"base_specs": "1girl, slender build, fair skin",
"hair": "long blonde hair, voluminous, crown",
"eyes": "blue eyes, long eyelashes",
"expression": "gentle smile",
"hands": "pink nails",
"arms": "",
"torso": "medium breasts",
"pelvis": "",
"legs": "",
"feet": "",
"distinguishing_marks": "pink lips, blue earrings"
},
"wardrobe": {
"inner_layer": "white petticoat",
"outer_layer": "pink floor-length ball gown, puffy sleeves, dark pink panniers",
"lower_body": "long skirt",
"footwear": "red high heels",
"gloves": "white opera gloves",
"accessories": "gold crown with red and blue jewels, blue brooch"
},
"styles": {
"aesthetic": "royal, whimsical, nintendo style",
"primary_color": "pink",
"secondary_color": "gold",
"tertiary_color": "blue"
},
"lora": {
"lora_name": "Princess_Peach_Shiny_Style_V4.0_Illustrious_1652958.safetensors",
"lora_weight": 0.8,
"lora_triggers": "princess peach, crown, pink dress, shiny skin, royal elegance"
}
}

View File

@@ -0,0 +1,35 @@
{
"character_id": "princess_zelda_botw",
"identity": {
"base_specs": "1girl, slender build, fair skin, pointed ears",
"hair": "long blonde hair, braided, gold hair clips",
"eyes": "green eyes",
"expression": "determined look",
"hands": "gold nails",
"arms": "",
"torso": "small breasts",
"pelvis": "",
"legs": "",
"feet": "",
"distinguishing_marks": "tri-force symbol, elf ears"
},
"wardrobe": {
"inner_layer": "blue tunic",
"outer_layer": "blue champion's tunic, brown leather belts",
"lower_body": "tan trousers",
"footwear": "brown leather boots",
"gloves": "brown fingerless gloves",
"accessories": "sheikah slate, gold jewelry"
},
"styles": {
"aesthetic": "fantasy, adventurous, zelda style",
"primary_color": "blue",
"secondary_color": "gold",
"tertiary_color": "brown"
},
"lora": {
"lora_name": "",
"lora_weight": 1.0,
"lora_triggers": ""
}
}

View File

@@ -0,0 +1,35 @@
{
"character_id": "riju",
"identity": {
"base_specs": "1girl, young, dark skin, gerudo",
"hair": "short red hair, braided ponytail, gold hair ornament",
"eyes": "green eyes",
"expression": "serious",
"hands": "",
"arms": "",
"torso": "small breasts",
"pelvis": "",
"legs": "",
"feet": "",
"distinguishing_marks": "blue lipstick,gerudo markings"
},
"wardrobe": {
"inner_layer": "",
"outer_layer": "gerudo top, colorful sash",
"lower_body": "gerudo pants, puffy pants",
"footwear": "sandals",
"gloves": "",
"accessories": "gold jewelry, crown, earrings"
},
"styles": {
"aesthetic": "fantasy, desert, gerudo style",
"primary_color": "gold",
"secondary_color": "teal",
"tertiary_color": "red"
},
"lora": {
"lora_name": "riju_botw",
"lora_weight": 0.8,
"lora_triggers": "riju, gerudo"
}
}

View File

@@ -0,0 +1,35 @@
{
"character_id": "rosalina",
"identity": {
"base_specs": "1girl, tall, slender build, fair skin",
"hair": "long platinum blonde hair, side-swept bangs covering one eye",
"eyes": "light blue eyes",
"expression": "serene expression",
"hands": "turquoise nails",
"arms": "",
"torso": "medium breasts",
"pelvis": "",
"legs": "",
"feet": "",
"distinguishing_marks": "star-shaped earrings"
},
"wardrobe": {
"inner_layer": "",
"outer_layer": "turquoise off-the-shoulder gown, silver trim",
"lower_body": "long skirt",
"footwear": "silver high heels",
"gloves": "",
"accessories": "silver crown with blue jewels, star wand, luma"
},
"styles": {
"aesthetic": "celestial, elegant, nintendo style",
"primary_color": "turquoise",
"secondary_color": "silver",
"tertiary_color": "yellow"
},
"lora": {
"lora_name": "",
"lora_weight": 1.0,
"lora_triggers": ""
}
}

View File

@@ -0,0 +1,35 @@
{
"character_id": "rouge_the_bat",
"identity": {
"base_specs": "1girl, anthro, bat girl, white fur",
"hair": "short white hair",
"eyes": "teal eyes",
"expression": "sly smirk",
"hands": "white gloves",
"arms": "",
"torso": "large breasts",
"pelvis": "",
"legs": "",
"feet": "",
"distinguishing_marks": "bat wings, eyeshadow"
},
"wardrobe": {
"inner_layer": "",
"outer_layer": "black skin-tight jumpsuit, pink heart-shaped chest plate",
"lower_body": "jumpsuit",
"footwear": "white boots, pink heart motifs",
"gloves": "white gloves, pink cuffs",
"accessories": "blue eyeshadow"
},
"styles": {
"aesthetic": "sleek, spy, sonic style",
"primary_color": "white",
"secondary_color": "pink",
"tertiary_color": "black"
},
"lora": {
"lora_name": "",
"lora_weight": 1.0,
"lora_triggers": ""
}
}

View File

@@ -0,0 +1,35 @@
{
"character_id": "samus_aran",
"identity": {
"base_specs": "1girl, athletic build, fair skin",
"hair": "long blonde hair, ponytail",
"eyes": "blue eyes",
"expression": "serious expression",
"hands": "blue nails",
"arms": "",
"torso": "medium breasts",
"pelvis": "",
"legs": "",
"feet": "",
"distinguishing_marks": "beauty mark on chin"
},
"wardrobe": {
"inner_layer": "",
"outer_layer": "blue skin-tight bodysuit, pink symbols",
"lower_body": "bodysuit",
"footwear": "blue high-heeled boots",
"gloves": "",
"accessories": "paralyzer pistol"
},
"styles": {
"aesthetic": "sci-fi, sleek, metroid style",
"primary_color": "blue",
"secondary_color": "pink",
"tertiary_color": "yellow"
},
"lora": {
"lora_name": "",
"lora_weight": 1.0,
"lora_triggers": ""
}
}

View File

@@ -0,0 +1,35 @@
{
"character_id": "sucy_manbavaran",
"identity": {
"base_specs": "1girl, lanky build, pale skin",
"hair": "mauve hair, hair covering one eye",
"eyes": "droopy red eyes",
"expression": "deadpan expression",
"hands": "purple nails",
"arms": "",
"torso": "small breasts",
"pelvis": "",
"legs": "",
"feet": "",
"distinguishing_marks": "dark circles under eyes"
},
"wardrobe": {
"inner_layer": "",
"outer_layer": "dark purple witch robes",
"lower_body": "long skirt",
"footwear": "brown boots",
"gloves": "",
"accessories": "pointed witch hat, potion bottle"
},
"styles": {
"aesthetic": "gothic, whimsical, little witch academia style",
"primary_color": "purple",
"secondary_color": "mauve",
"tertiary_color": "green"
},
"lora": {
"lora_name": "",
"lora_weight": 1.0,
"lora_triggers": ""
}
}

View File

@@ -0,0 +1,35 @@
{
"character_id": "tifa_lockhart",
"identity": {
"base_specs": "1girl, athletic build, fair skin",
"hair": "long black hair, tied end",
"eyes": "red eyes",
"expression": "kind smile",
"hands": "dark red nails",
"arms": "",
"torso": "large breasts",
"pelvis": "",
"legs": "",
"feet": "",
"distinguishing_marks": ""
},
"wardrobe": {
"inner_layer": "black sports bra",
"outer_layer": "white tank top, black suspenders",
"lower_body": "black miniskirt",
"footwear": "red boots, black socks",
"gloves": "red combat gloves",
"accessories": "silver earrings"
},
"styles": {
"aesthetic": "urban, martial arts, final fantasy style",
"primary_color": "white",
"secondary_color": "black",
"tertiary_color": "red"
},
"lora": {
"lora_name": "",
"lora_weight": 1.0,
"lora_triggers": ""
}
}

View File

@@ -0,0 +1,35 @@
{
"character_id": "urbosa",
"identity": {
"base_specs": "1girl, tall, muscular, dark skin, gerudo",
"hair": "long red hair, wild hair",
"eyes": "green eyes",
"expression": "confident",
"hands": "gold nails",
"arms": "muscular arms",
"torso": "abs",
"pelvis": "wide hips",
"legs": "muscular legs",
"feet": "",
"distinguishing_marks": "blue lipstick, gerudo markings"
},
"wardrobe": {
"inner_layer": "",
"outer_layer": "gold breastplate, blue champion's skirt",
"lower_body": "",
"footwear": "gold heels",
"gloves": "",
"accessories": "gold jewelry, scimitar"
},
"styles": {
"aesthetic": "fantasy, warrior, gerudo style",
"primary_color": "gold",
"secondary_color": "blue",
"tertiary_color": "red"
},
"lora": {
"lora_name": "urbosa_botw",
"lora_weight": 0.8,
"lora_triggers": "urbosa, gerudo"
}
}

View File

@@ -0,0 +1,35 @@
{
"character_id": "yor_briar",
"identity": {
"base_specs": "1girl, slender build, fair skin",
"hair": "long black hair, styled with gold headband",
"eyes": "red eyes",
"expression": "gentle yet mysterious smile",
"hands": "black nails",
"arms": "",
"torso": "medium breasts",
"pelvis": "",
"legs": "",
"feet": "",
"distinguishing_marks": ""
},
"wardrobe": {
"inner_layer": "",
"outer_layer": "black backless halter dress, red rose pattern inside",
"lower_body": "black thigh-high boots",
"footwear": "black boots",
"gloves": "black fingerless gloves",
"accessories": "gold rose-themed headband, gold needle weapons"
},
"styles": {
"aesthetic": "elegant, assassin, spy x family style",
"primary_color": "black",
"secondary_color": "red",
"tertiary_color": "gold"
},
"lora": {
"lora_name": "",
"lora_weight": 1.0,
"lora_triggers": ""
}
}

View File

@@ -0,0 +1,35 @@
{
"character_id": "y'shtola_rhul",
"identity": {
"base_specs": "1girl, miqo'te, slender build, fair skin, cat ears",
"hair": "short white hair, bangs",
"eyes": "blind white eyes",
"expression": "stoic expression",
"hands": "black nails",
"arms": "",
"torso": "small breasts",
"pelvis": "",
"legs": "",
"feet": "",
"distinguishing_marks": "facial markings, cat tail"
},
"wardrobe": {
"inner_layer": "",
"outer_layer": "black sorceress robes, fur trim",
"lower_body": "long skirt",
"footwear": "black boots",
"gloves": "",
"accessories": "wooden staff"
},
"styles": {
"aesthetic": "magical, scholarly, final fantasy xiv style",
"primary_color": "black",
"secondary_color": "white",
"tertiary_color": "purple"
},
"lora": {
"lora_name": "",
"lora_weight": 1.0,
"lora_triggers": ""
}
}

View File

@@ -0,0 +1,35 @@
{
"character_id": "yuna_ffx",
"identity": {
"base_specs": "1girl, slender, fair skin",
"hair": "short brown hair, bob cut",
"eyes": "heterochromia, blue eye, green eye",
"expression": "gentle",
"hands": "",
"arms": "",
"torso": "small breasts",
"pelvis": "",
"legs": "",
"feet": "",
"distinguishing_marks": ""
},
"wardrobe": {
"inner_layer": "white kimono top, yellow obi",
"outer_layer": "",
"lower_body": "long blue skirt, floral pattern",
"footwear": "boots",
"gloves": "detached sleeves",
"accessories": "summoner staff, necklace"
},
"styles": {
"aesthetic": "fantasy, final fantasy x style",
"primary_color": "white",
"secondary_color": "blue",
"tertiary_color": "yellow"
},
"lora": {
"lora_name": "yuna_ffx",
"lora_weight": 0.8,
"lora_triggers": "yuna, summoner outfit"
}
}

View File

@@ -0,0 +1,9 @@
from .lora_from_string import LoraFromString
NODE_CLASS_MAPPINGS = {
"LoraFromString": LoraFromString
}
NODE_DISPLAY_NAME_MAPPINGS = {
"LoraFromString": "Lora From String"
}

View File

@@ -0,0 +1,35 @@
import folder_paths
import comfy.utils
import comfy.sd
class LoraFromString:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"model": ("MODEL",),
"clip": ("CLIP",),
"lora_name": ("STRING", {"multiline": False}),
"strength_model": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
"strength_clip": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
}
}
RETURN_TYPES = ("MODEL", "CLIP")
FUNCTION = "load_lora"
CATEGORY = "AODH Pack"
def load_lora(self, model, clip, lora_name, strength_model, strength_clip):
if strength_model == 0 and strength_clip == 0:
return (model, clip)
lora_path = folder_paths.get_full_path("loras", lora_name)
lora = None
if lora_path:
lora = comfy.utils.load_torch_file(lora_path, safe_load=True)
else:
print(f"Lora not found: {lora_name}")
return (model, clip)
model_lora, clip_lora = comfy.sd.load_lora_for_models(model, clip, lora, strength_model, strength_clip)
return (model_lora, clip_lora)

View File

@@ -0,0 +1,9 @@
from .reenforcer import ReEnforcer
NODE_CLASS_MAPPINGS = {
"ReEnforcer": ReEnforcer
}
NODE_DISPLAY_NAME_MAPPINGS = {
"ReEnforcer": "ReEnforcer"
}

View File

@@ -0,0 +1,18 @@
class ReEnforcer:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"string": ("STRING", {"multiline": True}),
"count": ("INT", {"default": 1, "min": 1, "max": 100}),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("string",)
FUNCTION = "reenforce"
CATEGORY = "AODH Pack"
def reenforce(self, string, count):
result = ", ".join([string] * count)
return (result,)

View File

@@ -0,0 +1,9 @@
from .resolution_reader import ResolutionReader
NODE_CLASS_MAPPINGS = {
"ResolutionReader": ResolutionReader
}
NODE_DISPLAY_NAME_MAPPINGS = {
"ResolutionReader": "Resolution Reader"
}

View File

@@ -0,0 +1,79 @@
import os
import random
class ResolutionReader:
@classmethod
def INPUT_TYPES(s):
base_path = os.path.dirname(os.path.realpath(__file__))
file_path = os.path.join(base_path, "resolutions", "resolutions.txt")
lines = []
if os.path.exists(file_path):
with open(file_path, 'r') as f:
lines = [line.strip() for line in f.readlines() if line.strip()]
if not lines:
lines = ["1024,1024,1.0"]
return {
"required": {
"resolution": (lines, ),
"selection_mode": (["manual", "sequential", "random"], {"default": "manual"}),
"repeat_count": ("INT", {"default": 1, "min": 1, "max": 100, "step": 1}),
}
}
@classmethod
def IS_CHANGED(s, resolution, selection_mode, repeat_count):
if selection_mode == "random" or selection_mode == "sequential":
return float("nan")
return resolution
RETURN_TYPES = ("INT", "INT", "FLOAT")
RETURN_NAMES = ("width", "height", "upscale")
FUNCTION = "read_resolution"
CATEGORY = "AODH Pack"
_current_index = 0
_current_count = 0
_last_selection = None
def read_resolution(self, resolution, selection_mode, repeat_count):
base_path = os.path.dirname(os.path.realpath(__file__))
file_path = os.path.join(base_path, "resolutions", "resolutions.txt")
lines = []
if os.path.exists(file_path):
with open(file_path, 'r') as f:
lines = [line.strip() for line in f.readlines() if line.strip()]
if not lines:
return (0, 0, 0.0)
if selection_mode == "manual":
selected_line = resolution
elif selection_mode == "sequential":
if self.__class__._current_count >= repeat_count:
self.__class__._current_index += 1
self.__class__._current_count = 0
selected_line = lines[self.__class__._current_index % len(lines)]
self.__class__._current_count += 1
elif selection_mode == "random":
if self.__class__._current_count >= repeat_count or self.__class__._last_selection is None:
self.__class__._last_selection = random.choice(lines)
self.__class__._current_count = 0
selected_line = self.__class__._last_selection
self.__class__._current_count += 1
else:
selected_line = resolution
try:
# Format: width, height, upscale
parts = [p.strip() for p in selected_line.split(',')]
width = int(parts[0])
height = int(parts[1])
upscale = float(parts[2])
return (width, height, upscale)
except (ValueError, IndexError):
return (0, 0, 0.0)

View File

@@ -0,0 +1,4 @@
1280,720,3.0
720,1280,3.0
1376,576,2.5
1024,1024,2.0

44
project-goal.md Normal file
View File

@@ -0,0 +1,44 @@
# AODH Pack
Author: Aodhan Collins
## Overview
A set of custom ComfyUI nodes that aid in the creation of images of anime/video game characters.
## Character JSON Reader
Reads a character JSON file and outputs the character's attributes for diverse image generation use cases. The basic format is as follows
```json
{
"character_id": "cyber_huntress_01",
"identity": {
"base_specs": "1woman, athletic build, pale skin",
"hair": "neon pink bob cut, undercut",
"eyes": "glowing violet eyes",
"expression": "stoic, focused",
"hands": "pink nails",
"arms": "cat tattoo on arm",
"torso": "small breasts",
"pelvis": "narrow waist",
"legs": "flower tattoo on leg",
"feet": "pink toenails",
"distinguishing_marks": "cybernetic interface port on neck, facial tattoos"
},
"wardrobe": {
"inner_layer": "black mesh bodysuit",
"outer_layer": "oversized holographic bomber jacket",
"lower_body": "high-waisted tactical shorts",
"footwear": "platform combat boots",
"gloves": "fingerless leather gloves",
"accessories": "cybernetic visor, choker, utility belt"
},
"styles": {
"aesthetic": "cyberpunk, synthwave",
"primary_color": "pink",
"secondary_color": "violet",
"tertiary_color": "cyan"
}
}
```

2
requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
# No external dependencies required for the current nodes.
# Standard library (json, os) is used.

50
update_nails.py Normal file
View File

@@ -0,0 +1,50 @@
import json
import os
characters_dir = 'nodes/character_reader/characters'
print(f"Looking for characters in: {os.path.abspath(characters_dir)}")
if not os.path.exists(characters_dir):
print(f"ERROR: Directory {characters_dir} does not exist.")
exit(1)
files = [f for f in os.listdir(characters_dir) if f.endswith('.json')]
print(f"Found {len(files)} JSON files.")
for filename in files:
filepath = os.path.join(characters_dir, filename)
print(f"Processing {filename}...")
try:
with open(filepath, 'r') as f:
data = json.load(f)
except Exception as e:
print(f" Error reading {filename}: {e}")
continue
primary_color = data.get('styles', {}).get('primary_color')
if primary_color:
if 'identity' not in data:
data['identity'] = {}
hands = data['identity'].get('hands', '')
nail_string = f"{primary_color} nails"
# Check if nails are already mentioned
if "nails" not in hands and "polish" not in hands:
if hands and hands.strip():
new_hands = f"{hands}, {nail_string}"
else:
new_hands = nail_string
data['identity']['hands'] = new_hands
print(f" UPDATING: '{hands}' -> '{new_hands}'")
with open(filepath, 'w') as f:
json.dump(data, f, indent=2)
else:
print(f" Skipping: Nails already present ('{hands}')")
else:
print(f" Skipping: No primary color found.")