diff --git a/README.md b/README.md index c69b527..b250e58 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ A local web-based GUI for managing character profiles (JSON) and generating cons ## Features - **Character Gallery**: Automatically scans your `characters/` folder and builds a searchable, sortable database. +- **Outfit Gallery**: Manage reusable outfit presets that can be applied to any character. +- **AI-Powered Creation**: Create new characters and outfits using AI to generate profiles from descriptions, or manually create blank templates. - **Granular Prompt Control**: Every field in your character JSON (Identity, Wardrobe, Styles) has a checkbox. You decide exactly what is sent to the AI. - **ComfyUI Integration**: - **SDXL Optimized**: Designed for high-quality SDXL workflows. @@ -45,6 +47,11 @@ A local web-based GUI for managing character profiles (JSON) and generating cons ## Usage +### Creating Characters & Outfits +- **AI Generation**: Toggle "Use AI to generate profile from description" on, then describe your character or outfit. The AI will generate a complete profile with appropriate tags. +- **Manual Creation**: Toggle AI generation off to create a blank template you can edit yourself. +- **Auto-naming**: Leave the filename field empty to auto-generate one from the name. If a file already exists, a number will be appended automatically. + ### Gallery Management - **Rescan**: Use the "Rescan Character Files" button if you've added new JSON files or manually edited them. - **Save Defaults**: On a character page, select your favorite prompt combination and click "Save as Default Selection" to remember it for future quick generations. @@ -57,9 +64,85 @@ A local web-based GUI for managing character profiles (JSON) and generating cons ./launch.sh --clean ``` +## JSON Structure + +### Character Profile +```json +{ + "character_id": "example_character", + "character_name": "Example Character", + "identity": { + "base_specs": "1girl, slender build, fair skin", + "hair": "long blue hair", + "eyes": "blue eyes", + "hands": "", + "arms": "", + "torso": "", + "pelvis": "", + "legs": "", + "feet": "", + "extra": "" + }, + "defaults": { + "expression": "smile", + "pose": "standing", + "scene": "simple background" + }, + "wardrobe": { + "default": { + "full_body": "", + "headwear": "", + "top": "white blouse", + "bottom": "blue skirt", + "legwear": "black thighhighs", + "footwear": "black shoes", + "hands": "", + "accessories": "ribbon" + } + }, + "styles": { + "aesthetic": "anime style", + "primary_color": "blue", + "secondary_color": "white", + "tertiary_color": "" + }, + "lora": { + "lora_name": "", + "lora_weight": 1.0, + "lora_triggers": "" + }, + "tags": ["tag1", "tag2"] +} +``` + +### Outfit Profile +```json +{ + "outfit_id": "school_uniform_01", + "outfit_name": "School Uniform", + "wardrobe": { + "full_body": "", + "headwear": "", + "top": "white blouse, sailor collar", + "bottom": "pleated skirt", + "legwear": "knee socks", + "footwear": "loafers", + "hands": "", + "accessories": "ribbon tie" + }, + "lora": { + "lora_name": "", + "lora_weight": 0.8, + "lora_triggers": "" + }, + "tags": ["school uniform", "uniform"] +} +``` + ## File Structure -- `/characters`: Your character JSON files. +- `/data/characters`: Your character JSON files. +- `/data/clothing`: Outfit preset JSON files. - `/static/uploads`: Generated images (organized by character subfolders). - `/templates`: HTML UI using Bootstrap 5. - `app.py`: Flask backend and prompt-building logic. diff --git a/app.py b/app.py index 5e16503..82c1da6 100644 --- a/app.py +++ b/app.py @@ -6,14 +6,15 @@ import requests import random from flask import Flask, render_template, request, redirect, url_for, flash, session from werkzeug.utils import secure_filename -from models import db, Character, Settings +from models import db, Character, Settings, Outfit app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///database.db' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config['UPLOAD_FOLDER'] = 'static/uploads' app.config['SECRET_KEY'] = 'dev-key-123' -app.config['CHARACTERS_DIR'] = 'characters' +app.config['CHARACTERS_DIR'] = 'data/characters' +app.config['CLOTHING_DIR'] = 'data/clothing' app.config['COMFYUI_URL'] = 'http://127.0.0.1:8188' app.config['ILLUSTRIOUS_MODELS_DIR'] = '/mnt/alexander/AITools/Image Models/Stable-diffusion/Illustrious/' app.config['NOOB_MODELS_DIR'] = '/mnt/alexander/AITools/Image Models/Stable-diffusion/Noob/' @@ -32,6 +33,16 @@ def get_available_loras(): loras.append(f"Illustrious/Looks/{f}") return sorted(loras) +def get_available_clothing_loras(): + """Get LoRAs from the Clothing directory for outfit LoRAs.""" + clothing_lora_dir = '/mnt/alexander/AITools/Image Models/lora/Illustrious/Clothing/' + loras = [] + if os.path.exists(clothing_lora_dir): + for f in os.listdir(clothing_lora_dir): + if f.endswith('.safetensors'): + loras.append(f"Illustrious/Clothing/{f}") + return sorted(loras) + def get_available_checkpoints(): checkpoints = [] @@ -78,9 +89,12 @@ def build_prompt(data, selected_fields=None, default_fields=None, active_outfit= defaults = data.get('defaults', {}) # Pre-calculate Hand/Glove priority + # Priority: wardrobe gloves > wardrobe hands (outfit) > identity hands (character) hand_val = "" if wardrobe.get('gloves') and is_selected('wardrobe', 'gloves'): hand_val = wardrobe.get('gloves') + elif wardrobe.get('hands') and is_selected('wardrobe', 'hands'): + hand_val = wardrobe.get('hands') elif identity.get('hands') and is_selected('identity', 'hands'): hand_val = identity.get('hands') @@ -123,11 +137,12 @@ def build_prompt(data, selected_fields=None, default_fields=None, active_outfit= if lora.get('lora_triggers') and is_selected('lora', 'lora_triggers'): parts.append(lora.get('lora_triggers')) - # 2. Face Prompt: Tag, Eyes, Expression + # 2. Face Prompt: Tag, Eyes, Expression, Headwear face_parts = [] if char_tag and is_selected('special', 'name'): face_parts.append(char_tag) if identity.get('eyes') and is_selected('identity', 'eyes'): face_parts.append(identity.get('eyes')) if defaults.get('expression') and is_selected('defaults', 'expression'): face_parts.append(defaults.get('expression')) + if wardrobe.get('headwear') and is_selected('wardrobe', 'headwear'): face_parts.append(wardrobe.get('headwear')) # 3. Hand Prompt: Hand value (Gloves or Hands) hand_parts = [hand_val] if hand_val else [] @@ -217,6 +232,64 @@ def sync_characters(): db.session.commit() +def sync_outfits(): + if not os.path.exists(app.config['CLOTHING_DIR']): + return + + current_ids = [] + + for filename in os.listdir(app.config['CLOTHING_DIR']): + if filename.endswith('.json'): + file_path = os.path.join(app.config['CLOTHING_DIR'], filename) + try: + with open(file_path, 'r') as f: + data = json.load(f) + outfit_id = data.get('outfit_id') or filename.replace('.json', '') + + current_ids.append(outfit_id) + + # Generate URL-safe slug: remove special characters from outfit_id + slug = re.sub(r'[^a-zA-Z0-9_]', '', outfit_id) + + # Check if outfit already exists + outfit = Outfit.query.filter_by(outfit_id=outfit_id).first() + name = data.get('outfit_name', outfit_id.replace('_', ' ').title()) + + if outfit: + outfit.data = data + outfit.name = name + outfit.slug = slug + outfit.filename = filename + + # Check if cover image still exists + if outfit.image_path: + full_img_path = os.path.join(app.config['UPLOAD_FOLDER'], outfit.image_path) + if not os.path.exists(full_img_path): + print(f"Image missing for {outfit.name}, clearing path.") + outfit.image_path = None + + # Explicitly tell SQLAlchemy the JSON field was modified + flag_modified(outfit, "data") + else: + new_outfit = Outfit( + outfit_id=outfit_id, + slug=slug, + filename=filename, + name=name, + data=data + ) + db.session.add(new_outfit) + except Exception as e: + print(f"Error importing outfit {filename}: {e}") + + # Remove outfits that are no longer in the folder + all_outfits = Outfit.query.all() + for outfit in all_outfits: + if outfit.outfit_id not in current_ids: + db.session.delete(outfit) + + db.session.commit() + def call_llm(prompt, system_prompt="You are a creative assistant."): settings = Settings.query.first() if not settings or not settings.openrouter_api_key: @@ -337,14 +410,14 @@ def generator(): image_info = outputs[node_id]['images'][0] image_data = get_image(image_info['filename'], image_info['subfolder'], image_info['type']) - char_folder = os.path.join(app.config['UPLOAD_FOLDER'], character.slug) + char_folder = os.path.join(app.config['UPLOAD_FOLDER'], f"characters/{character.slug}") os.makedirs(char_folder, exist_ok=True) filename = f"gen_{int(time.time())}.png" file_path = os.path.join(char_folder, filename) with open(file_path, 'wb') as f: f.write(image_data) - relative_path = f"{character.slug}/{filename}" + relative_path = f"characters/{character.slug}/{filename}" return render_template('generator.html', characters=characters, checkpoints=checkpoints, generated_image=relative_path, selected_char=char_slug, selected_ckpt=checkpoint) time.sleep(2) @@ -370,77 +443,142 @@ def detail(slug): def create_character(): if request.method == 'POST': name = request.form.get('name') - slug = request.form.get('filename') - prompt = request.form.get('prompt') + slug = request.form.get('filename', '').strip() + prompt = request.form.get('prompt', '') + use_llm = request.form.get('use_llm') == 'on' + + # Auto-generate slug from name if not provided + if not slug: + slug = re.sub(r'[^a-zA-Z0-9]+', '_', name.lower()).strip('_') # Validate slug safe_slug = re.sub(r'[^a-zA-Z0-9_]', '', slug) if not safe_slug: - flash("Invalid filename.") - return redirect(request.url) + safe_slug = 'character' + + # Find available filename (increment if exists) + base_slug = safe_slug + counter = 1 + while os.path.exists(os.path.join(app.config['CHARACTERS_DIR'], f"{safe_slug}.json")): + safe_slug = f"{base_slug}_{counter}" + counter += 1 + + # Check if LLM generation is requested + if use_llm: + if not prompt: + flash("Description is required when AI generation is enabled.") + return redirect(request.url) - # Check if exists - if os.path.exists(os.path.join(app.config['CHARACTERS_DIR'], f"{safe_slug}.json")): - flash("Character with this filename already exists.") - return redirect(request.url) - - # Generate JSON with LLM - system_prompt = """You are a JSON generator. output ONLY valid JSON matching this exact structure. Do not wrap in markdown blocks. - Structure: - { - "character_id": "WILL_BE_REPLACED", - "character_name": "WILL_BE_REPLACED", - "identity": { - "base_specs": "string (e.g. 1girl, build, skin)", - "hair": "string", - "eyes": "string", - "hands": "string", - "arms": "string", - "torso": "string", - "pelvis": "string", - "legs": "string", - "feet": "string", - "extra": "string" - }, - "defaults": { - "expression": "", - "pose": "", - "scene": "" - }, - "wardrobe": { - "headwear": "string", - "top": "string", - "legwear": "string", - "footwear": "string", - "hands": "string", - "accessories": "string" - }, - "styles": { - "aesthetic": "string", - "primary_color": "string", - "secondary_color": "string", - "tertiary_color": "string" - }, - "lora": { - "lora_name": "", - "lora_weight": 1.0, - "lora_triggers": "" - }, - "tags": ["string", "string"] - } - Fill the fields based on the user's description. Use Danbooru-style tags for the values (e.g. 'long hair', 'blue eyes'). Keep values concise. Leave defaults fields empty.""" + # Generate JSON with LLM + system_prompt = """You are a JSON generator. output ONLY valid JSON matching this exact structure. Do not wrap in markdown blocks. + Structure: + { + "character_id": "WILL_BE_REPLACED", + "character_name": "WILL_BE_REPLACED", + "identity": { + "base_specs": "string (e.g. 1girl, build, skin)", + "hair": "string", + "eyes": "string", + "hands": "string", + "arms": "string", + "torso": "string", + "pelvis": "string", + "legs": "string", + "feet": "string", + "extra": "string" + }, + "defaults": { + "expression": "", + "pose": "", + "scene": "" + }, + "wardrobe": { + "full_body": "string (e.g. bodysuit, dress, full outfit description)", + "headwear": "string", + "top": "string", + "bottom": "string", + "legwear": "string", + "footwear": "string", + "hands": "string", + "accessories": "string" + }, + "styles": { + "aesthetic": "string", + "primary_color": "string", + "secondary_color": "string", + "tertiary_color": "string" + }, + "lora": { + "lora_name": "", + "lora_weight": 1.0, + "lora_triggers": "" + }, + "tags": ["string", "string"] + } + Fill the fields based on the user's description. Use Danbooru-style tags for the values (e.g. 'long hair', 'blue eyes'). Keep values concise. Use empty strings "" for fields that are not applicable or unknown - never use words like "none" or "n/a". Leave defaults fields empty.""" + + try: + llm_response = call_llm(f"Create a character profile for '{name}' based on this description: {prompt}", system_prompt) + + # Clean response (remove markdown if present) + clean_json = llm_response.replace('```json', '').replace('```', '').strip() + char_data = json.loads(clean_json) + + # Enforce IDs + char_data['character_id'] = safe_slug + char_data['character_name'] = name + + except Exception as e: + print(f"LLM error: {e}") + flash(f"Failed to generate character profile: {e}") + return redirect(request.url) + else: + # Create blank character template + char_data = { + "character_id": safe_slug, + "character_name": name, + "identity": { + "base_specs": "", + "hair": "", + "eyes": "", + "hands": "", + "arms": "", + "torso": "", + "pelvis": "", + "legs": "", + "feet": "", + "extra": "" + }, + "defaults": { + "expression": "", + "pose": "", + "scene": "" + }, + "wardrobe": { + "full_body": "", + "headwear": "", + "top": "", + "bottom": "", + "legwear": "", + "footwear": "", + "hands": "", + "accessories": "" + }, + "styles": { + "aesthetic": "", + "primary_color": "", + "secondary_color": "", + "tertiary_color": "" + }, + "lora": { + "lora_name": "", + "lora_weight": 1.0, + "lora_triggers": "" + }, + "tags": [] + } try: - llm_response = call_llm(f"Create a character profile for '{name}' based on this description: {prompt}", system_prompt) - - # Clean response (remove markdown if present) - clean_json = llm_response.replace('```json', '').replace('```', '').strip() - char_data = json.loads(clean_json) - - # Enforce IDs - char_data['character_id'] = safe_slug - char_data['character_name'] = name - # Save file file_path = os.path.join(app.config['CHARACTERS_DIR'], f"{safe_slug}.json") with open(file_path, 'w') as f: @@ -461,7 +599,7 @@ def create_character(): return redirect(url_for('detail', slug=safe_slug)) except Exception as e: - print(f"LLM/Save error: {e}") + print(f"Save error: {e}") flash(f"Failed to create character: {e}") return redirect(request.url) @@ -698,7 +836,7 @@ def upload_image(slug): if file and allowed_file(file.filename): # Create character subfolder - char_folder = os.path.join(app.config['UPLOAD_FOLDER'], slug) + char_folder = os.path.join(app.config['UPLOAD_FOLDER'], f"characters/{slug}") os.makedirs(char_folder, exist_ok=True) filename = secure_filename(file.filename) @@ -706,7 +844,7 @@ def upload_image(slug): file.save(file_path) # Store relative path in DB - character.image_path = f"{slug}/{filename}" + character.image_path = f"characters/{slug}/{filename}" db.session.commit() flash('Image uploaded successfully!') @@ -729,7 +867,7 @@ def finalize_generation(slug, prompt_id): image_data = get_image(image_info['filename'], image_info['subfolder'], image_info['type']) # Create character subfolder - char_folder = os.path.join(app.config['UPLOAD_FOLDER'], slug) + char_folder = os.path.join(app.config['UPLOAD_FOLDER'], f"characters/{slug}") os.makedirs(char_folder, exist_ok=True) filename = f"gen_{int(time.time())}.png" @@ -739,16 +877,14 @@ def finalize_generation(slug, prompt_id): print(f"Image saved to: {os.path.abspath(file_path)}") - # Handle actions - relative_path = f"{slug}/{filename}" + # Handle actions - always save as preview + relative_path = f"characters/{slug}/{filename}" + session[f'preview_{slug}'] = relative_path + # If action is 'replace', also update the character's cover image immediately if action == 'replace': character.image_path = relative_path db.session.commit() - flash('Cover image updated!') - else: - # Preview mode - session[f'preview_{slug}'] = relative_path return {'success': True, 'image_url': url_for('static', filename=f'uploads/{relative_path}')} @@ -757,7 +893,21 @@ def finalize_generation(slug, prompt_id): print(f"Finalize error: {e}") return {'error': str(e)}, 500 -def _prepare_workflow(workflow, character, prompts, checkpoint=None, custom_negative=None): +@app.route('/character//replace_cover_from_preview', methods=['POST']) +def replace_cover_from_preview(slug): + character = Character.query.filter_by(slug=slug).first_or_404() + preview_path = session.get(f'preview_{slug}') + + if preview_path: + character.image_path = preview_path + db.session.commit() + flash('Cover image updated from preview!') + else: + flash('No preview image available', 'error') + + return redirect(url_for('detail', slug=slug)) + +def _prepare_workflow(workflow, character, prompts, checkpoint=None, custom_negative=None, outfit=None): # 1. Update prompts using replacement to preserve embeddings workflow["6"]["inputs"]["text"] = workflow["6"]["inputs"]["text"].replace("{{POSITIVE_PROMPT}}", prompts["main"]) @@ -782,19 +932,43 @@ def _prepare_workflow(workflow, character, prompts, checkpoint=None, custom_nega if checkpoint: workflow["4"]["inputs"]["ckpt_name"] = checkpoint - # 3. Handle LoRA - lora_data = character.data.get('lora', {}) - lora_name = lora_data.get('lora_name') - + # 3. Handle LoRAs - Node 16 for character, Node 17 for outfit + # Start with direct checkpoint connections model_source = ["4", 0] clip_source = ["4", 1] - if lora_name and "16" in workflow: - workflow["16"]["inputs"]["lora_name"] = lora_name - workflow["16"]["inputs"]["strength_model"] = lora_data.get('lora_weight', 1.0) - workflow["16"]["inputs"]["strength_clip"] = lora_data.get('lora_weight', 1.0) + # Character LoRA (Node 16) + char_lora_data = character.data.get('lora', {}) if character else {} + char_lora_name = char_lora_data.get('lora_name') + + if char_lora_name and "16" in workflow: + workflow["16"]["inputs"]["lora_name"] = char_lora_name + workflow["16"]["inputs"]["strength_model"] = char_lora_data.get('lora_weight', 1.0) + workflow["16"]["inputs"]["strength_clip"] = char_lora_data.get('lora_weight', 1.0) + workflow["16"]["inputs"]["model"] = ["4", 0] # From checkpoint + workflow["16"]["inputs"]["clip"] = ["4", 1] # From checkpoint model_source = ["16", 0] clip_source = ["16", 1] + print(f"Character LoRA: {char_lora_name} @ {char_lora_data.get('lora_weight', 1.0)}") + + # Outfit LoRA (Node 17) - chains from character LoRA or checkpoint + outfit_lora_data = outfit.data.get('lora', {}) if outfit else {} + outfit_lora_name = outfit_lora_data.get('lora_name') + + if outfit_lora_name and "17" in workflow: + workflow["17"]["inputs"]["lora_name"] = outfit_lora_name + workflow["17"]["inputs"]["strength_model"] = outfit_lora_data.get('lora_weight', 0.8) + workflow["17"]["inputs"]["strength_clip"] = outfit_lora_data.get('lora_weight', 0.8) + # Chain from character LoRA (node 16) or checkpoint (node 4) + if char_lora_name and "16" in workflow: + workflow["17"]["inputs"]["model"] = ["16", 0] + workflow["17"]["inputs"]["clip"] = ["16", 1] + else: + workflow["17"]["inputs"]["model"] = ["4", 0] + workflow["17"]["inputs"]["clip"] = ["4", 1] + model_source = ["17", 0] + clip_source = ["17", 1] + print(f"Outfit LoRA: {outfit_lora_name} @ {outfit_lora_data.get('lora_weight', 0.8)}") # Apply connections to all model/clip consumers workflow["3"]["inputs"]["model"] = model_source @@ -843,15 +1017,33 @@ def clear_all_covers(): @app.route('/generate_missing', methods=['POST']) def generate_missing(): - missing = Character.query.filter((Character.image_path == None) | (Character.image_path == '')).all() - if not missing: + # Query fresh from database for each check to avoid stale session issues + def get_missing_count(): + return Character.query.filter((Character.image_path == None) | (Character.image_path == '')).count() + + if get_missing_count() == 0: flash("No characters missing cover images.") return redirect(url_for('index')) success_count = 0 - for character in missing: + processed = 0 + + # Keep generating until no more missing + while get_missing_count() > 0: + # Get the next character in alphabetical order + character = Character.query.filter( + (Character.image_path == None) | (Character.image_path == '') + ).order_by(Character.name).first() + + if not character: + break + + character_slug = character.slug + character_name = character.name + + processed += 1 try: - print(f"Batch generating for: {character.name}") + print(f"Batch generating for: {character_name}") prompt_response = _queue_generation(character, action='replace') prompt_id = prompt_response['prompt_id'] @@ -866,22 +1058,27 @@ def generate_missing(): image_info = outputs[node_id]['images'][0] image_data = get_image(image_info['filename'], image_info['subfolder'], image_info['type']) - char_folder = os.path.join(app.config['UPLOAD_FOLDER'], character.slug) + char_folder = os.path.join(app.config['UPLOAD_FOLDER'], f"characters/{character_slug}") os.makedirs(char_folder, exist_ok=True) filename = f"gen_{int(time.time())}.png" file_path = os.path.join(char_folder, filename) with open(file_path, 'wb') as f: f.write(image_data) - character.image_path = f"{character.slug}/{filename}" - db.session.commit() - success_count += 1 + # Re-query the character to ensure it's attached to the session + character_to_update = Character.query.filter_by(slug=character_slug).first() + if character_to_update: + character_to_update.image_path = f"characters/{character_slug}/{filename}" + db.session.commit() + print(f"Saved cover for {character_name}: {character_to_update.image_path}") + success_count += 1 break break time.sleep(2) max_retries -= 1 except Exception as e: - print(f"Error generating for {character.name}: {e}") + print(f"Error generating for {character_name}: {e}") + db.session.rollback() # Rollback on error to ensure clean state flash(f"Batch generation complete. Generated {success_count} images.") return redirect(url_for('index')) @@ -941,6 +1138,487 @@ def save_defaults(slug): flash('Default prompt selection saved for this character!') return redirect(url_for('detail', slug=slug)) +# ============ OUTFIT ROUTES ============ + +@app.route('/outfits') +def outfits_index(): + outfits = Outfit.query.order_by(Outfit.name).all() + return render_template('outfits/index.html', outfits=outfits) + +@app.route('/outfits/rescan', methods=['POST']) +def rescan_outfits(): + sync_outfits() + flash('Database synced with outfit files.') + return redirect(url_for('outfits_index')) + +@app.route('/outfit/') +def outfit_detail(slug): + outfit = Outfit.query.filter_by(slug=slug).first_or_404() + characters = Character.query.order_by(Character.name).all() + + # Load state from session + preferences = session.get(f'prefs_outfit_{slug}') + preview_image = session.get(f'preview_outfit_{slug}') + selected_character = session.get(f'char_outfit_{slug}') + + return render_template('outfits/detail.html', outfit=outfit, characters=characters, + preferences=preferences, preview_image=preview_image, + selected_character=selected_character) + +@app.route('/outfit//edit', methods=['GET', 'POST']) +def edit_outfit(slug): + outfit = Outfit.query.filter_by(slug=slug).first_or_404() + loras = get_available_clothing_loras() # Use clothing LoRAs for outfits + + if request.method == 'POST': + try: + # 1. Update basic fields + outfit.name = request.form.get('outfit_name') + + # 2. Rebuild the data dictionary + new_data = outfit.data.copy() + new_data['outfit_name'] = outfit.name + + # Update outfit_id if provided + new_outfit_id = request.form.get('outfit_id', outfit.outfit_id) + new_data['outfit_id'] = new_outfit_id + + # Update wardrobe section + if 'wardrobe' in new_data: + for key in new_data['wardrobe'].keys(): + form_key = f"wardrobe_{key}" + if form_key in request.form: + new_data['wardrobe'][key] = request.form.get(form_key) + + # Update lora section + if 'lora' in new_data: + for key in new_data['lora'].keys(): + form_key = f"lora_{key}" + if form_key in request.form: + val = request.form.get(form_key) + if key == 'lora_weight': + try: val = float(val) + except: val = 0.8 + new_data['lora'][key] = val + + # Update Tags (comma separated string to list) + tags_raw = request.form.get('tags', '') + new_data['tags'] = [t.strip() for f in tags_raw.split(',') for t in [f.strip()] if t] + + outfit.data = new_data + flag_modified(outfit, "data") + + # 3. Write back to JSON file + outfit_file = outfit.filename or f"{re.sub(r'[^a-zA-Z0-9_]', '', outfit.outfit_id)}.json" + file_path = os.path.join(app.config['CLOTHING_DIR'], outfit_file) + + with open(file_path, 'w') as f: + json.dump(new_data, f, indent=2) + + db.session.commit() + flash('Outfit profile updated successfully!') + return redirect(url_for('outfit_detail', slug=slug)) + + except Exception as e: + print(f"Edit error: {e}") + flash(f"Error saving changes: {str(e)}") + + return render_template('outfits/edit.html', outfit=outfit, loras=loras) + +@app.route('/outfit//upload', methods=['POST']) +def upload_outfit_image(slug): + outfit = Outfit.query.filter_by(slug=slug).first_or_404() + + if 'image' not in request.files: + flash('No file part') + return redirect(request.url) + + file = request.files['image'] + if file.filename == '': + flash('No selected file') + return redirect(request.url) + + if file and allowed_file(file.filename): + # Create outfit subfolder + outfit_folder = os.path.join(app.config['UPLOAD_FOLDER'], f"outfits/{slug}") + os.makedirs(outfit_folder, exist_ok=True) + + filename = secure_filename(file.filename) + file_path = os.path.join(outfit_folder, filename) + file.save(file_path) + + # Store relative path in DB + outfit.image_path = f"outfits/{slug}/{filename}" + db.session.commit() + flash('Image uploaded successfully!') + + return redirect(url_for('outfit_detail', slug=slug)) + +@app.route('/outfit//generate', methods=['POST']) +def generate_outfit_image(slug): + outfit = Outfit.query.filter_by(slug=slug).first_or_404() + + try: + # Get action type + action = request.form.get('action', 'preview') + client_id = request.form.get('client_id') + + # Get selected fields + selected_fields = request.form.getlist('include_field') + + # Get selected character (if any) + character_slug = request.form.get('character_slug', '') + character = None + + # Handle random character selection + if character_slug == '__random__': + all_characters = Character.query.all() + if all_characters: + character = random.choice(all_characters) + character_slug = character.slug + elif character_slug: + character = Character.query.filter_by(slug=character_slug).first() + + # Save preferences + session[f'prefs_outfit_{slug}'] = selected_fields + session[f'char_outfit_{slug}'] = character_slug + + # Build combined data for prompt building + if character: + # Combine character identity/defaults with outfit wardrobe + combined_data = { + 'character_id': character.character_id, + 'identity': character.data.get('identity', {}), + 'defaults': character.data.get('defaults', {}), + 'wardrobe': outfit.data.get('wardrobe', {}), # Use outfit's wardrobe + 'styles': character.data.get('styles', {}), # Use character's styles + 'lora': outfit.data.get('lora', {}), # Use outfit's lora + 'tags': outfit.data.get('tags', []) + } + + # When character is selected, merge character identity fields into selected_fields + # so they are included in the prompt + if selected_fields: + # Add character identity fields to selection if not already present + for key in ['base_specs', 'hair', 'eyes', 'hands', 'arms', 'torso', 'pelvis', 'legs', 'feet', 'extra']: + if character.data.get('identity', {}).get(key): + field_key = f'identity::{key}' + if field_key not in selected_fields: + selected_fields.append(field_key) + # Add expression and pose, but NOT scene (outfit previews use simple background) + for key in ['expression', 'pose']: + if character.data.get('defaults', {}).get(key): + field_key = f'defaults::{key}' + if field_key not in selected_fields: + selected_fields.append(field_key) + # Always include character name + if 'special::name' not in selected_fields: + selected_fields.append('special::name') + + default_fields = character.default_fields + else: + # Outfit only - no character + combined_data = { + 'character_id': outfit.outfit_id, + 'wardrobe': outfit.data.get('wardrobe', {}), + 'lora': outfit.data.get('lora', {}), + 'tags': outfit.data.get('tags', []) + } + default_fields = outfit.default_fields + + # Queue generation + with open('comfy_workflow.json', 'r') as f: + workflow = json.load(f) + + # Build prompts for combined data + prompts = build_prompt(combined_data, selected_fields, default_fields) + + # Add colored simple background to the main prompt for outfit previews + # Use character's primary_color if available + if character: + primary_color = character.data.get('styles', {}).get('primary_color', '') + if primary_color: + prompts["main"] = f"{prompts['main']}, {primary_color} simple background" + else: + prompts["main"] = f"{prompts['main']}, simple background" + else: + prompts["main"] = f"{prompts['main']}, simple background" + + # Prepare workflow - pass both character and outfit for dual LoRA support + workflow = _prepare_workflow(workflow, character, prompts, outfit=outfit) + + prompt_response = queue_prompt(workflow, client_id=client_id) + + if 'prompt_id' not in prompt_response: + raise Exception(f"ComfyUI failed: {prompt_response.get('error', 'Unknown error')}") + + prompt_id = prompt_response['prompt_id'] + + # Return JSON if AJAX request + if request.headers.get('X-Requested-With') == 'XMLHttpRequest': + return {'status': 'queued', 'prompt_id': prompt_id} + + return redirect(url_for('outfit_detail', slug=slug)) + + except Exception as e: + print(f"Generation error: {e}") + if request.headers.get('X-Requested-With') == 'XMLHttpRequest': + return {'error': str(e)}, 500 + flash(f"Error during generation: {str(e)}") + return redirect(url_for('outfit_detail', slug=slug)) + +@app.route('/outfit//finalize_generation/', methods=['POST']) +def finalize_outfit_generation(slug, prompt_id): + outfit = Outfit.query.filter_by(slug=slug).first_or_404() + action = request.form.get('action', 'preview') + + try: + history = get_history(prompt_id) + if prompt_id not in history: + return {'error': 'History not found'}, 404 + + outputs = history[prompt_id]['outputs'] + for node_id in outputs: + if 'images' in outputs[node_id]: + image_info = outputs[node_id]['images'][0] + image_data = get_image(image_info['filename'], image_info['subfolder'], image_info['type']) + + # Create outfit subfolder + outfit_folder = os.path.join(app.config['UPLOAD_FOLDER'], f"outfits/{slug}") + os.makedirs(outfit_folder, exist_ok=True) + + filename = f"gen_{int(time.time())}.png" + file_path = os.path.join(outfit_folder, filename) + with open(file_path, 'wb') as f: + f.write(image_data) + + print(f"Image saved to: {os.path.abspath(file_path)}") + + # Always save as preview + relative_path = f"outfits/{slug}/{filename}" + session[f'preview_outfit_{slug}'] = relative_path + + return {'success': True, 'image_url': url_for('static', filename=f'uploads/{relative_path}')} + + return {'error': 'No image found in output'}, 404 + except Exception as e: + print(f"Finalize error: {e}") + return {'error': str(e)}, 500 + +@app.route('/outfit//replace_cover_from_preview', methods=['POST']) +def replace_outfit_cover_from_preview(slug): + outfit = Outfit.query.filter_by(slug=slug).first_or_404() + preview_path = session.get(f'preview_outfit_{slug}') + + if preview_path: + outfit.image_path = preview_path + db.session.commit() + flash('Cover image updated from preview!') + else: + flash('No preview image available', 'error') + + return redirect(url_for('outfit_detail', slug=slug)) + +@app.route('/outfit/create', methods=['GET', 'POST']) +def create_outfit(): + if request.method == 'POST': + name = request.form.get('name') + slug = request.form.get('filename', '').strip() + prompt = request.form.get('prompt', '') + use_llm = request.form.get('use_llm') == 'on' + + # Auto-generate slug from name if not provided + if not slug: + slug = re.sub(r'[^a-zA-Z0-9]+', '_', name.lower()).strip('_') + + # Validate slug + safe_slug = re.sub(r'[^a-zA-Z0-9_]', '', slug) + if not safe_slug: + safe_slug = 'outfit' + + # Find available filename (increment if exists) + base_slug = safe_slug + counter = 1 + while os.path.exists(os.path.join(app.config['CLOTHING_DIR'], f"{safe_slug}.json")): + safe_slug = f"{base_slug}_{counter}" + counter += 1 + + # Check if LLM generation is requested + if use_llm: + if not prompt: + flash("Description is required when AI generation is enabled.") + return redirect(request.url) + + # Generate JSON with LLM + system_prompt = """You are a JSON generator. output ONLY valid JSON matching this exact structure. Do not wrap in markdown blocks. + Structure: + { + "outfit_id": "WILL_BE_REPLACED", + "outfit_name": "WILL_BE_REPLACED", + "wardrobe": { + "full_body": "string (e.g. bodysuit, dress, full outfit description)", + "headwear": "string (e.g. hairband, cap)", + "top": "string (e.g. blouse, corset, jacket)", + "bottom": "string (e.g. skirt, pants, shorts)", + "legwear": "string (e.g. stockings, tights, socks)", + "footwear": "string (e.g. heels, boots, sneakers)", + "hands": "string (e.g. gloves, sleeves)", + "accessories": "string (e.g. necklace, belt, apron)" + }, + "lora": { + "lora_name": "", + "lora_weight": 0.8, + "lora_triggers": "" + }, + "tags": ["string", "string"] + } + Fill the fields based on the user's description. Use Danbooru-style tags for the values (e.g. 'frilled skirt', 'lace stockings'). Keep values concise. Use empty strings "" for fields that are not applicable or unknown - never use words like "none" or "n/a". Leave lora fields empty - they can be configured later.""" + + try: + llm_response = call_llm(f"Create an outfit profile for '{name}' based on this description: {prompt}", system_prompt) + + # Clean response (remove markdown if present) + clean_json = llm_response.replace('```json', '').replace('```', '').strip() + outfit_data = json.loads(clean_json) + + # Enforce IDs + outfit_data['outfit_id'] = safe_slug + outfit_data['outfit_name'] = name + + # Ensure required fields exist + if 'wardrobe' not in outfit_data: + outfit_data['wardrobe'] = { + "full_body": "", + "headwear": "", + "top": "", + "bottom": "", + "legwear": "", + "footwear": "", + "hands": "", + "accessories": "" + } + if 'lora' not in outfit_data: + outfit_data['lora'] = { + "lora_name": "", + "lora_weight": 0.8, + "lora_triggers": "" + } + if 'tags' not in outfit_data: + outfit_data['tags'] = [] + + except Exception as e: + print(f"LLM error: {e}") + flash(f"Failed to generate outfit profile: {e}") + return redirect(request.url) + else: + # Create blank outfit template + outfit_data = { + "outfit_id": safe_slug, + "outfit_name": name, + "wardrobe": { + "full_body": "", + "headwear": "", + "top": "", + "bottom": "", + "legwear": "", + "footwear": "", + "hands": "", + "accessories": "" + }, + "lora": { + "lora_name": "", + "lora_weight": 0.8, + "lora_triggers": "" + }, + "tags": [] + } + + try: + # Save file + file_path = os.path.join(app.config['CLOTHING_DIR'], f"{safe_slug}.json") + with open(file_path, 'w') as f: + json.dump(outfit_data, f, indent=2) + + # Add to DB + new_outfit = Outfit( + outfit_id=safe_slug, + slug=safe_slug, + filename=f"{safe_slug}.json", + name=name, + data=outfit_data + ) + db.session.add(new_outfit) + db.session.commit() + + flash('Outfit created successfully!') + return redirect(url_for('outfit_detail', slug=safe_slug)) + + except Exception as e: + print(f"Save error: {e}") + flash(f"Failed to create outfit: {e}") + return redirect(request.url) + + return render_template('outfits/create.html') + +@app.route('/outfit//save_defaults', methods=['POST']) +def save_outfit_defaults(slug): + outfit = Outfit.query.filter_by(slug=slug).first_or_404() + selected_fields = request.form.getlist('include_field') + outfit.default_fields = selected_fields + db.session.commit() + flash('Default prompt selection saved for this outfit!') + return redirect(url_for('outfit_detail', slug=slug)) + +@app.route('/outfit//clone', methods=['POST']) +def clone_outfit(slug): + outfit = Outfit.query.filter_by(slug=slug).first_or_404() + + # Find the next available number for the clone + base_id = outfit.outfit_id + # Extract base name without number suffix + import re + match = re.match(r'^(.+?)_(\d+)$', base_id) + if match: + base_name = match.group(1) + current_num = int(match.group(2)) + else: + base_name = base_id + current_num = 1 + + # Find next available number + next_num = current_num + 1 + while True: + new_id = f"{base_name}_{next_num:02d}" + new_filename = f"{new_id}.json" + new_path = os.path.join(app.config['CLOTHING_DIR'], new_filename) + if not os.path.exists(new_path): + break + next_num += 1 + + # Create new outfit data (copy of original) + new_data = outfit.data.copy() + new_data['outfit_id'] = new_id + new_data['outfit_name'] = f"{outfit.name} (Copy)" + + # Save the new JSON file + with open(new_path, 'w') as f: + json.dump(new_data, f, indent=2) + + # Create new outfit in database + new_slug = re.sub(r'[^a-zA-Z0-9_]', '', new_id) + new_outfit = Outfit( + outfit_id=new_id, + slug=new_slug, + filename=new_filename, + name=new_data['outfit_name'], + data=new_data + ) + db.session.add(new_outfit) + db.session.commit() + + flash(f'Outfit cloned as "{new_id}"!') + return redirect(url_for('outfit_detail', slug=new_slug)) + if __name__ == '__main__': with app.app_context(): os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) @@ -959,4 +1637,5 @@ if __name__ == '__main__': print(f"Migration note: {e}") sync_characters() + sync_outfits() app.run(debug=True, port=5000) diff --git a/characters/camilla.json b/characters/camilla.json deleted file mode 100644 index a8c41a1..0000000 --- a/characters/camilla.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "character_id": "camilla_(fire_emblem)", - "character_name": "Camilla Nohr", - "identity": { - "base_specs": "1girl, curvaceous build, fair skin", - "hair": "long wavy lavender hair, hair covering one eye", - "eyes": "purple eyes", - "hands": "purple nails", - "arms": "", - "torso": "large breasts", - "pelvis": "", - "legs": "", - "feet": "", - "extra": "black headband with horns" - }, - "defaults": { - "expression": "", - "pose": "", - "scene": "" - }, - "wardrobe": { - "default": { - "headwear": "", - "top": "black armor, cleavage", - "legwear": "black leggings, armored plates", - "footwear": "black armored boots", - "hands": "", - "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": "" - }, - "tags": [ - "Fire Emblem" - ] -} \ No newline at end of file diff --git a/comfy_workflow.json b/comfy_workflow.json index 1a3b426..ac70545 100644 --- a/comfy_workflow.json +++ b/comfy_workflow.json @@ -169,5 +169,15 @@ "clip": ["4", 1] }, "class_type": "LoraLoader" + }, + "17": { + "inputs": { + "lora_name": "", + "strength_model": 0.8, + "strength_clip": 0.8, + "model": ["16", 0], + "clip": ["16", 1] + }, + "class_type": "LoraLoader" } } diff --git a/data/actions/belly_dancing.json b/data/actions/belly_dancing.json new file mode 100644 index 0000000..76f5302 --- /dev/null +++ b/data/actions/belly_dancing.json @@ -0,0 +1,16 @@ +{ + "action_id": "belly_dancing", + "action_name": "Belly Dancing", + "action": { + "full_body": "belly dancing", + "head": "", + "eyes": "", + "arms": "hands above head", + "hands": "hands together", + "torso": "", + "pelvis": "shaking hips", + "legs": "", + "feet": "", + "additional": "" + } +} \ No newline at end of file diff --git a/characters/aerith_gainsborough.json b/data/characters/aerith_gainsborough.json similarity index 68% rename from characters/aerith_gainsborough.json rename to data/characters/aerith_gainsborough.json index 1494207..b55a391 100644 --- a/characters/aerith_gainsborough.json +++ b/data/characters/aerith_gainsborough.json @@ -13,36 +13,40 @@ "extra": "pink hair ribbon" }, "defaults": { - "expression": "", - "pose": "", - "scene": "" + "expression": "gentle smile, looking at viewer", + "pose": "handing flower to viewer", + "scene": "city street, night" }, "wardrobe": { "default": { + "full_body": "long pink dress", "headwear": "", - "top": "pink dress, red bolero jacket", - "legwear": "long pink dress", + "top": "red bolero jacket", + "bottom": "", + "legwear": "", "footwear": "brown boots", "hands": "", "accessories": "gold bracelets, flower basket" }, "red_dress": { + "full_body": "long dress, frilled dress, red dress", "headwear": "red hair ribbons", - "top": "long dress, frilled dress, red dress", - "legwear": "long dress, frilled dress, red dress", + "top": "", + "bottom": "", + "legwear": "", "footwear": "white high heels", "hands": "red nails", "accessories": "gold bracelets" } }, "styles": { - "aesthetic": "floral, gentle, final fantasy style", + "aesthetic": "floral, final fantasy vii style", "primary_color": "pink", "secondary_color": "red", "tertiary_color": "brown" }, "lora": { - "lora_name": "Illustrious/Looks/Aerith.safetensors", + "lora_name": "", "lora_weight": 1.0, "lora_triggers": "" }, diff --git a/characters/android_18.json b/data/characters/android_18.json similarity index 69% rename from characters/android_18.json rename to data/characters/android_18.json index 24b1456..962e634 100644 --- a/characters/android_18.json +++ b/data/characters/android_18.json @@ -14,22 +14,24 @@ "extra": "" }, "defaults": { - "expression": "", - "pose": "", - "scene": "" + "expression": "neutral", + "pose": "tucking hair behind ear", + "scene": "wasteland, mountains, " }, "wardrobe": { "default": { - "headwear": "black long sleeved shirt, striped sleeves", - "top": "blue denim vest,", - "legwear": "blue denim skirt, black stockings", + "full_body": "", + "headwear": "", + "top": "blue denim vest,black long sleeved shirt, striped sleeves", + "bottom": "blue denim skirt", + "legwear": "black stockings", "footwear": "brown boots", "hands": "", "accessories": "gold hoop earrings" } }, "styles": { - "aesthetic": "wasteland, mountains, anime, dragon ball style", + "aesthetic": "anime, dragon ball style", "primary_color": "blue", "secondary_color": "black", "tertiary_color": "white" diff --git a/characters/anya_forger.json b/data/characters/anya_forger.json similarity index 88% rename from characters/anya_forger.json rename to data/characters/anya_forger.json index e821ffc..70d58ed 100644 --- a/characters/anya_forger.json +++ b/data/characters/anya_forger.json @@ -20,9 +20,11 @@ }, "wardrobe": { "default": { + "full_body": "black Eden Academy uniform, gold trim", "headwear": "", - "top": "black Eden Academy uniform, gold trim", - "legwear": "uniform skirt", + "top": "", + "bottom": "", + "legwear": "", "footwear": "black shoes, white socks", "hands": "", "accessories": "black and gold hair cones" diff --git a/characters/biwa_hayahide.json b/data/characters/biwa_hayahide.json similarity index 75% rename from characters/biwa_hayahide.json rename to data/characters/biwa_hayahide.json index 0575414..4499407 100644 --- a/characters/biwa_hayahide.json +++ b/data/characters/biwa_hayahide.json @@ -14,22 +14,24 @@ "extra": "" }, "defaults": { - "expression": "", - "pose": "", - "scene": "" + "expression": "thinking", + "pose": "reading", + "scene": "library, sun beam" }, "wardrobe": { "default": { - "headwear": "white shirt", - "top": "tracen school uniform", - "legwear": "pleated skirt", + "full_body": "tracen school uniform", + "headwear": "", + "top": "", + "bottom": "", + "legwear": "", "footwear": "heeled shoes", "hands": "", "accessories": "" } }, "styles": { - "aesthetic": "library,intellectual,", + "aesthetic": "anime,umasumame", "primary_color": "maroon", "secondary_color": "white", "tertiary_color": "grey" diff --git a/characters/bulma.json b/data/characters/bulma.json similarity index 92% rename from characters/bulma.json rename to data/characters/bulma.json index 69e6107..087f5d6 100644 --- a/characters/bulma.json +++ b/data/characters/bulma.json @@ -20,8 +20,10 @@ }, "wardrobe": { "default": { + "full_body": "black playboy bunny", "headwear": "", - "top": "black playboy bunny", + "top": "", + "bottom": "", "legwear": "pantyhose", "footwear": "red high heels", "hands": "detatched cuffs", diff --git a/characters/camilla_(fire_emblem).json b/data/characters/camilla_(fire_emblem).json similarity index 78% rename from characters/camilla_(fire_emblem).json rename to data/characters/camilla_(fire_emblem).json index b78f1b4..4ebc38b 100644 --- a/characters/camilla_(fire_emblem).json +++ b/data/characters/camilla_(fire_emblem).json @@ -20,10 +20,12 @@ }, "wardrobe": { "default": { - "headwear": "belt between breasts", - "top": "black armor, gold trim, cleavage", - "legwear": "purple sash, pelvic curtain, black panties", - "footwear": "black armored thigh boots", + "full_body": "black armor, gold trim", + "headwear": "", + "top": "belt between breasts, cleavage", + "bottom": "purple sash, pelvic curtain, black panties", + "legwear": "black armored thigh boots", + "footwear": "gold heels", "hands": "purple velvet gloves", "accessories": "purple cape, large axe" } diff --git a/characters/cammy.json b/data/characters/cammy.json similarity index 92% rename from characters/cammy.json rename to data/characters/cammy.json index b6476db..9fe9a7b 100644 --- a/characters/cammy.json +++ b/data/characters/cammy.json @@ -20,8 +20,10 @@ }, "wardrobe": { "default": { + "full_body": "green high-leg leotard", "headwear": "", - "top": "green high-leg leotard", + "top": "", + "bottom": "", "legwear": "bare legs", "footwear": "black combat boots, green socks", "hands": "red gauntlets", diff --git a/characters/chun_li.json b/data/characters/chun_li.json similarity index 74% rename from characters/chun_li.json rename to data/characters/chun_li.json index d98eec3..6c26552 100644 --- a/characters/chun_li.json +++ b/data/characters/chun_li.json @@ -14,22 +14,24 @@ "extra": "" }, "defaults": { - "expression": "", - "pose": "", - "scene": "" + "expression": "confident", + "pose": "fighting stance", + "scene": "market, daytime" }, "wardrobe": { "default": { + "full_body": "blue qipao, gold embroidery, white accents", "headwear": "", - "top": "blue qipao, gold embroidery, white accents, puffy shoulders", - "legwear": "brown tights", + "top": " puffy shoulders", + "bottom": "brown tights", + "legwear": "", "footwear": "white lace-up boots", "hands": "", "accessories": "white hair ribbons, spiked bracelets" } }, "styles": { - "aesthetic": "chinese style, market,", + "aesthetic": "chinese style, ", "primary_color": "blue", "secondary_color": "white", "tertiary_color": "gold" diff --git a/characters/ciri.json b/data/characters/ciri.json similarity index 76% rename from characters/ciri.json rename to data/characters/ciri.json index ab004c5..f1a02a8 100644 --- a/characters/ciri.json +++ b/data/characters/ciri.json @@ -14,22 +14,24 @@ "extra": "scar over eye" }, "defaults": { - "expression": "", - "pose": "", + "expression": "serious", + "pose": "fighting stance, holding sword", "scene": "" }, "wardrobe": { "default": { - "headwear": "white blouse", - "top": "", - "legwear": "brown leather trousers", + "full_body": "", + "headwear": "", + "top": "white blouse", + "bottom": "brown leather trousers", + "legwear": "", "footwear": "brown leather boots", "hands": "brown leather gloves", "accessories": "silver sword on back, witcher medallion" } }, "styles": { - "aesthetic": "gritty, fantasy, witcher style", + "aesthetic": "fantasy, witcher style", "primary_color": "white", "secondary_color": "brown", "tertiary_color": "silver" diff --git a/characters/delinquent_mother_flim13.json b/data/characters/delinquent_mother_flim13.json similarity index 70% rename from characters/delinquent_mother_flim13.json rename to data/characters/delinquent_mother_flim13.json index 56db610..9742711 100644 --- a/characters/delinquent_mother_flim13.json +++ b/data/characters/delinquent_mother_flim13.json @@ -5,7 +5,7 @@ "base_specs": "1girl, milf, gyaru, tall", "hair": "blonde hair, long hair", "eyes": "sharp eyes, black eyes, white pupil,", - "hands": "painted nails", + "hands": "red nails", "arms": "", "torso": "very large breasts", "pelvis": "wide hips", @@ -14,18 +14,20 @@ "extra": "" }, "defaults": { - "expression": "", - "pose": "", - "scene": "" + "expression": "naughty face", + "pose": "spread legs, leopard print panties", + "scene": "sitting on couch, from below" }, "wardrobe": { "default": { - "headwear": "cleavage", - "top": "light brown sweater, ", - "legwear": "black skirt", + "full_body": "", + "headwear": "", + "top": "light brown sweater, cleavage", + "bottom": "leopard print skirt", + "legwear": "", "footwear": "red high heels", "hands": "", - "accessories": "necklace, rings" + "accessories": "necklace, rings," } }, "styles": { diff --git a/characters/gold_city.json b/data/characters/gold_city.json similarity index 74% rename from characters/gold_city.json rename to data/characters/gold_city.json index 94661fc..dcc6a81 100644 --- a/characters/gold_city.json +++ b/data/characters/gold_city.json @@ -14,22 +14,24 @@ "extra": "" }, "defaults": { - "expression": "", - "pose": "", - "scene": "" + "expression": "bored", + "pose": "looking at phone", + "scene": "sitting on bench" }, "wardrobe": { "default": { - "headwear": "white shirt", - "top": "tracen school uniform", - "legwear": "pleated skirt", + "full_body": "tracen school uniform", + "headwear": "", + "top": "", + "bottom": "", + "legwear": "", "footwear": "heeled shoes", "hands": "", "accessories": "choker, earrings" } }, "styles": { - "aesthetic": "shopping,modeling,school yard", + "aesthetic": "modeling,school yard", "primary_color": "gold", "secondary_color": "white", "tertiary_color": "black" diff --git a/characters/gold_ship.json b/data/characters/gold_ship.json similarity index 77% rename from characters/gold_ship.json rename to data/characters/gold_ship.json index acf46d4..332480e 100644 --- a/characters/gold_ship.json +++ b/data/characters/gold_ship.json @@ -14,15 +14,17 @@ "extra": "" }, "defaults": { - "expression": "", - "pose": "", - "scene": "" + "expression": "smile", + "pose": "running toward viewer", + "scene": "horse race track, sunshine" }, "wardrobe": { "default": { - "headwear": "white shirt", - "top": "tracen school uniform", - "legwear": "pleated skirt", + "full_body": "tracen school uniform", + "headwear": "", + "top": "", + "bottom": "", + "legwear": "", "footwear": "heeled shoes", "hands": "", "accessories": "ear covers, hat" diff --git a/characters/hatsune_miku.json b/data/characters/hatsune_miku.json similarity index 96% rename from characters/hatsune_miku.json rename to data/characters/hatsune_miku.json index b1ff92d..248172b 100644 --- a/characters/hatsune_miku.json +++ b/data/characters/hatsune_miku.json @@ -20,8 +20,10 @@ }, "wardrobe": { "default": { + "full_body": "", "headwear": "", "top": "grey sleeveless shirt, turquoise tie", + "bottom": "", "legwear": "grey miniskirt, turquoise trim", "footwear": "black thigh-high boots, turquoise trim", "hands": "black arm warmers, turquoise trim", diff --git a/characters/jasmine_disney.json b/data/characters/jasmine_disney.json similarity index 72% rename from characters/jasmine_disney.json rename to data/characters/jasmine_disney.json index 5253caa..7f9028c 100644 --- a/characters/jasmine_disney.json +++ b/data/characters/jasmine_disney.json @@ -14,22 +14,24 @@ "extra": "heavy eyeliner, winged eyeliner" }, "defaults": { - "expression": "", - "pose": "", - "scene": "" + "expression": "gentle smile", + "pose": "hand in water", + "scene": "sitting beside fountain, sunshine" }, "wardrobe": { "default": { - "headwear": "teal crop top, tube top, off-shoulder, cleavage", - "top": "", - "legwear": "teal harem pants, baggy pants, sheer fabric", + "full_body": "", + "headwear": "", + "top": "teal crop top, tube top, off-shoulder, cleavage", + "bottom": "teal harem pants", + "legwear": " baggy pants, sheer fabric", "footwear": "gold shoes, curling toes, pointed shoes", "hands": "", "accessories": "gold hoop earrings, large gold necklace, blue headband, jewel on headband" } }, "styles": { - "aesthetic": "desert palace, large fountain, arabian, disney, cartoon, vibrant, ferns", + "aesthetic": "desert arabian, disney, cartoon, ", "primary_color": "teal", "secondary_color": "gold", "tertiary_color": "black" diff --git a/characters/jessica_rabbit.json b/data/characters/jessica_rabbit.json similarity index 87% rename from characters/jessica_rabbit.json rename to data/characters/jessica_rabbit.json index 42a1914..3dd588a 100644 --- a/characters/jessica_rabbit.json +++ b/data/characters/jessica_rabbit.json @@ -20,9 +20,11 @@ }, "wardrobe": { "default": { + "full_body": "red sequin evening gown", "headwear": "", - "top": "red sequin dress, strapless, high slit, backless", - "legwear": "side slit,", + "top": "strapless, backless", + "bottom": "high slit", + "legwear": "side slit", "footwear": "red high heels", "hands": "purple opera gloves", "accessories": "gold earrings, glitter" diff --git a/characters/jessie.json b/data/characters/jessie.json similarity index 82% rename from characters/jessie.json rename to data/characters/jessie.json index b3ab346..09580a6 100644 --- a/characters/jessie.json +++ b/data/characters/jessie.json @@ -20,9 +20,11 @@ }, "wardrobe": { "default": { - "headwear": "black crop top", - "top": "white Team Rocket uniform jacket, bare stomach, red R logo", - "legwear": "white miniskirt", + "full_body": "", + "headwear": "", + "top": "black crop top,white Team Rocket uniform jacket, red R logo", + "bottom": "midriff, white miniskirt", + "legwear": "", "footwear": "black thigh-high boots", "hands": "black elbow gloves", "accessories": "green earrings" diff --git a/characters/jinx.json b/data/characters/jinx.json similarity index 91% rename from characters/jinx.json rename to data/characters/jinx.json index 44463b3..d3612a6 100644 --- a/characters/jinx.json +++ b/data/characters/jinx.json @@ -20,9 +20,11 @@ }, "wardrobe": { "default": { + "full_body": "", "headwear": "", "top": "pink and black bikini, asymmetrical_bikini ", - "legwear": "pink shorts, single pink stocking", + "bottom": "pink shorts", + "legwear": "single pink stocking", "footwear": "combat boots", "hands": "black fingerless gloves, fishnet elbow gloves,", "accessories": "ammo belts, choker, bullet necklace," diff --git a/characters/kagamine_rin.json b/data/characters/kagamine_rin.json similarity index 83% rename from characters/kagamine_rin.json rename to data/characters/kagamine_rin.json index 225eefe..d2e26d5 100644 --- a/characters/kagamine_rin.json +++ b/data/characters/kagamine_rin.json @@ -20,9 +20,11 @@ }, "wardrobe": { "default": { - "headwear": "white shirt, sailor collar", - "top": "", - "legwear": "black shorts, yellow belt", + "full_body": "", + "headwear": "", + "top": "white shirt, sailor collar", + "bottom": "black shorts, yellow belt", + "legwear": "knee-high socks", "footwear": "white shoes", "hands": "", "accessories": "headset, hair bow" diff --git a/characters/kagari_atsuko.json b/data/characters/kagari_atsuko.json similarity index 79% rename from characters/kagari_atsuko.json rename to data/characters/kagari_atsuko.json index 941fd96..c970673 100644 --- a/characters/kagari_atsuko.json +++ b/data/characters/kagari_atsuko.json @@ -20,10 +20,12 @@ }, "wardrobe": { "default": { - "headwear": "white shirt", - "top": "dark blue witch robes", - "legwear": "dark blue skirt", - "footwear": "brown boots, white socks", + "full_body": "luna nova school uniform", + "headwear": "", + "top": "white shirt,dark blue witch robes", + "bottom": "dark blue skirt", + "legwear": "white socks", + "footwear": "brown boots", "hands": "", "accessories": "pointed witch hat, brown belt, magic wand" } diff --git a/characters/kda_all_out_ahri.json b/data/characters/kda_all_out_ahri.json similarity index 86% rename from characters/kda_all_out_ahri.json rename to data/characters/kda_all_out_ahri.json index f494ca8..848f970 100644 --- a/characters/kda_all_out_ahri.json +++ b/data/characters/kda_all_out_ahri.json @@ -20,9 +20,11 @@ }, "wardrobe": { "default": { - "headwear": "silver crop top", - "top": "white and silver jacket", - "legwear": "black leather shorts", + "full_body": "", + "headwear": "", + "top": "silver crop top, white and silver jacket", + "bottom": "black leather shorts", + "legwear": "", "footwear": "black thigh-high boots", "hands": "", "accessories": "crystal heart, silver jewelry" diff --git a/characters/kda_all_out_akali.json b/data/characters/kda_all_out_akali.json similarity index 85% rename from characters/kda_all_out_akali.json rename to data/characters/kda_all_out_akali.json index 45efdeb..f224ace 100644 --- a/characters/kda_all_out_akali.json +++ b/data/characters/kda_all_out_akali.json @@ -20,9 +20,11 @@ }, "wardrobe": { "default": { - "headwear": "black crop top", - "top": "blue and silver motorcycle jacket", - "legwear": "black leather pants", + "full_body": "", + "headwear": "", + "top": "black crop top, blue and silver motorcycle jacket", + "bottom": "black leather pants", + "legwear": "", "footwear": "blue sneakers", "hands": "black fingerless gloves", "accessories": "kama and kunai" diff --git a/characters/kda_all_out_evelynn.json b/data/characters/kda_all_out_evelynn.json similarity index 85% rename from characters/kda_all_out_evelynn.json rename to data/characters/kda_all_out_evelynn.json index eca3f41..68922c1 100644 --- a/characters/kda_all_out_evelynn.json +++ b/data/characters/kda_all_out_evelynn.json @@ -20,9 +20,11 @@ }, "wardrobe": { "default": { - "headwear": "black leather bra", - "top": "iridescent blue jacket, fur collar", - "legwear": "black leather skirt", + "full_body": "", + "headwear": "", + "top": "black leather bra, iridescent blue jacket, fur collar", + "bottom": "black leather skirt", + "legwear": "", "footwear": "black high-heeled boots", "hands": "", "accessories": "diamond earrings" diff --git a/characters/kda_all_out_kaisa.json b/data/characters/kda_all_out_kaisa.json similarity index 93% rename from characters/kda_all_out_kaisa.json rename to data/characters/kda_all_out_kaisa.json index 3f6372f..2dcfbc6 100644 --- a/characters/kda_all_out_kaisa.json +++ b/data/characters/kda_all_out_kaisa.json @@ -20,8 +20,10 @@ }, "wardrobe": { "default": { - "headwear": "silver bodysuit", + "full_body": "silver bodysuit", + "headwear": "", "top": "white and silver jacket", + "bottom": "", "legwear": "silver leggings", "footwear": "silver high-heeled boots", "hands": "", diff --git a/characters/komi_shouko.json b/data/characters/komi_shouko.json similarity index 75% rename from characters/komi_shouko.json rename to data/characters/komi_shouko.json index 41eba7d..8e88b64 100644 --- a/characters/komi_shouko.json +++ b/data/characters/komi_shouko.json @@ -14,15 +14,17 @@ "extra": "" }, "defaults": { - "expression": "", - "pose": "", - "scene": "" + "expression": "shy,", + "pose": "holding notebook", + "scene": "classroom" }, "wardrobe": { "default": { - "headwear": "white shirt", - "top": "itan private high school uniform, blazer, striped bow tie", - "legwear": "plaid skirt", + "full_body": "itan private high school uniform", + "headwear": "", + "top": "blazer, striped bow tie, white shirt", + "bottom": "plaid skirt", + "legwear": "black pantyhose", "footwear": "loafers", "hands": "", "accessories": "" diff --git a/characters/lara_croft_classic.json b/data/characters/lara_croft_classic.json similarity index 92% rename from characters/lara_croft_classic.json rename to data/characters/lara_croft_classic.json index 2010af6..7dea1f7 100644 --- a/characters/lara_croft_classic.json +++ b/data/characters/lara_croft_classic.json @@ -20,9 +20,11 @@ }, "wardrobe": { "default": { + "full_body": "", "headwear": "", "top": "teal tank top,", - "legwear": "brown shorts", + "bottom": "brown shorts", + "legwear": "thigh holsters", "footwear": "brown combat boots, red laces", "hands": "black fingerless gloves", "accessories": "dual thigh pistol holsters, brown leatherbackpack, red round sunglasses" diff --git a/characters/lisa_minci.json b/data/characters/lisa_minci.json similarity index 93% rename from characters/lisa_minci.json rename to data/characters/lisa_minci.json index a28c383..003f309 100644 --- a/characters/lisa_minci.json +++ b/data/characters/lisa_minci.json @@ -20,9 +20,11 @@ }, "wardrobe": { "default": { + "full_body": "", "headwear": "purple dress, corset", "top": "purple shawl", - "legwear": "slit skirt", + "bottom": "slit skirt", + "legwear": "", "footwear": "black heels", "hands": "purple gloves", "accessories": "witch hat, rose, necklace" diff --git a/characters/lulu.json b/data/characters/lulu.json similarity index 83% rename from characters/lulu.json rename to data/characters/lulu.json index 1551eb4..71dc1a4 100644 --- a/characters/lulu.json +++ b/data/characters/lulu.json @@ -20,9 +20,11 @@ }, "wardrobe": { "default": { - "headwear": "black corset", - "top": "black fur-trimmed dress, many belts on front", - "legwear": "long skirt made of belts", + "full_body": "", + "headwear": "", + "top": "black fur-trimmed dress, many belts on front, black corset", + "bottom": "long skirt made of belts", + "legwear": "", "footwear": "black boots", "hands": "", "accessories": "moogle doll, silver jewelry" diff --git a/characters/majin_android_21.json b/data/characters/majin_android_21.json similarity index 87% rename from characters/majin_android_21.json rename to data/characters/majin_android_21.json index cb00bed..2267bc1 100644 --- a/characters/majin_android_21.json +++ b/data/characters/majin_android_21.json @@ -20,9 +20,11 @@ }, "wardrobe": { "default": { - "headwear": "black tube top", - "top": "", - "legwear": "white harem pants", + "full_body": "", + "headwear": "", + "top": "black tube top", + "bottom": "white harem pants", + "legwear": "baggy pants", "footwear": "black and yellow boots", "hands": "black sleeves", "accessories": "gold bracelets, gold neck ring, hoop earrings,pink donut" diff --git a/characters/marin_kitagawa.json b/data/characters/marin_kitagawa.json similarity index 67% rename from characters/marin_kitagawa.json rename to data/characters/marin_kitagawa.json index 5909f7c..2da9f03 100644 --- a/characters/marin_kitagawa.json +++ b/data/characters/marin_kitagawa.json @@ -14,22 +14,34 @@ "extra": "piercings" }, "defaults": { - "expression": "", - "pose": "", - "scene": "" + "expression": "happy", + "pose": "v", + "scene": "sewing room" }, "wardrobe": { "default": { - "headwear": "black bikini with yellow flower print", + "full_body": "", + "headwear": "", "top": "white school shirt, loosely tied blue tie", + "bottom": "", "legwear": "blue plaid miniskirt", "footwear": "black loafers, black socks", "hands": "", "accessories": "choker, colored bracelets" + }, + "bikini": { + "full_body": "", + "headwear": "", + "top": "black bikini with yellow flower print", + "bottom": "", + "legwear": "", + "footwear": "barefoot", + "hands": "", + "accessories": "choker, colored bracelets" } }, "styles": { - "aesthetic": "gyaru, modern, anime style, sewing machine", + "aesthetic": "gyaru, modern, anime style,", "primary_color": "white", "secondary_color": "blue", "tertiary_color": "pink" diff --git a/characters/megurine_luka.json b/data/characters/megurine_luka.json similarity index 91% rename from characters/megurine_luka.json rename to data/characters/megurine_luka.json index 9df0142..fbb9f0b 100644 --- a/characters/megurine_luka.json +++ b/data/characters/megurine_luka.json @@ -20,9 +20,11 @@ }, "wardrobe": { "default": { + "full_body": "", "headwear": "", "top": "crop top, detached sleeves, gold trim", - "legwear": "side slit, lace-up skirt", + "bottom": "lace-up skirt", + "legwear": "side slit", "footwear": "thinghighs, lace-up boots, gold boots, gold armlet", "hands": "", "accessories": "headset" diff --git a/characters/meiko.json b/data/characters/meiko.json similarity index 90% rename from characters/meiko.json rename to data/characters/meiko.json index 023a733..0f18c02 100644 --- a/characters/meiko.json +++ b/data/characters/meiko.json @@ -20,9 +20,11 @@ }, "wardrobe": { "default": { + "full_body": "", "headwear": "red crop top, sleeveless", "top": "", - "legwear": "red skirt, mini skirt", + "bottom": "red skirt, mini skirt", + "legwear": "", "footwear": "brown boots", "hands": "", "accessories": "choker" diff --git a/characters/nessa.json b/data/characters/nessa.json similarity index 83% rename from characters/nessa.json rename to data/characters/nessa.json index f7b6fa4..687fc37 100644 --- a/characters/nessa.json +++ b/data/characters/nessa.json @@ -20,9 +20,11 @@ }, "wardrobe": { "default": { - "headwear": "white crop top, blue trim", - "top": "gym uniform, number '049'", - "legwear": "midriff, white and blue shorts, black trim", + "full_body": "blue trim", + "headwear": "", + "top": "white crop top, gym uniform, number '049'", + "bottom": "midriff,white and blue shorts, black trim", + "legwear": "", "footwear": "white and blue sandals, orange trim", "hands": "fingerless gloves", "accessories": "wristband, small life buoy, pokeball, gold hoop earrings" diff --git a/characters/olivier_mira_armstrong.json b/data/characters/olivier_mira_armstrong.json similarity index 85% rename from characters/olivier_mira_armstrong.json rename to data/characters/olivier_mira_armstrong.json index 6092da6..e0281c8 100644 --- a/characters/olivier_mira_armstrong.json +++ b/data/characters/olivier_mira_armstrong.json @@ -20,9 +20,11 @@ }, "wardrobe": { "default": { - "headwear": "black shirt", - "top": "blue military coat, fur collar", - "legwear": "black pants", + "full_body": "", + "headwear": "", + "top": "blue military coat, fur collar, black shirt", + "bottom": "black pants", + "legwear": "", "footwear": "black boots", "hands": "black gloves", "accessories": "sword" diff --git a/characters/princess_peach.json b/data/characters/princess_peach.json similarity index 78% rename from characters/princess_peach.json rename to data/characters/princess_peach.json index 7b079f7..0938744 100644 --- a/characters/princess_peach.json +++ b/data/characters/princess_peach.json @@ -14,15 +14,17 @@ "extra": "pink lips, blue earrings" }, "defaults": { - "expression": "", - "pose": "", - "scene": "" + "expression": "smile", + "pose": "hands together", + "scene": "throne room" }, "wardrobe": { "default": { - "headwear": "white petticoat", - "top": "pink floor-length ball gown, puffy sleeves, dark pink panniers", - "legwear": "long skirt", + "full_body": "pink ball gown", + "headwear": "gold crown", + "top": "white petticoat, puffy sleeves, dark pink panniers", + "bottom": "", + "legwear": "floor length skirt", "footwear": "red high heels", "hands": "white opera gloves", "accessories": "gold crown with red and blue jewels, blue brooch" diff --git a/characters/princess_zelda_botw.json b/data/characters/princess_zelda_botw.json similarity index 86% rename from characters/princess_zelda_botw.json rename to data/characters/princess_zelda_botw.json index df7cce3..b129762 100644 --- a/characters/princess_zelda_botw.json +++ b/data/characters/princess_zelda_botw.json @@ -20,9 +20,11 @@ }, "wardrobe": { "default": { - "headwear": "blue tunic", - "top": "blue champion's tunic, brown leather belts", - "legwear": "tan trousers", + "full_body": "", + "headwear": "", + "top": "blue champion's tunic, ", + "bottom": "brown leather belts, tan trousers", + "legwear": "", "footwear": "brown leather boots", "hands": "brown fingerless gloves", "accessories": "sheikah slate, gold jewelry" diff --git a/characters/rice_shower.json b/data/characters/rice_shower.json similarity index 80% rename from characters/rice_shower.json rename to data/characters/rice_shower.json index 9ff161b..1e81693 100644 --- a/characters/rice_shower.json +++ b/data/characters/rice_shower.json @@ -20,12 +20,14 @@ }, "wardrobe": { "default": { - "headwear": "white shirt", - "top": "tracen school uniform", - "legwear": "pleated skirt", + "full_body": "tracen school uniform", + "headwear": "hair flower, small hat", + "top": "white shirt", + "bottom": "pleated skirt", + "legwear": "", "footwear": "heeled shoes", "hands": "", - "accessories": "blue rose, hair flower, small hat," + "accessories": "blue rose" } }, "styles": { diff --git a/characters/riju.json b/data/characters/riju.json similarity index 91% rename from characters/riju.json rename to data/characters/riju.json index a969e20..8cd1766 100644 --- a/characters/riju.json +++ b/data/characters/riju.json @@ -20,9 +20,11 @@ }, "wardrobe": { "default": { + "full_body": "", "headwear": "", "top": "black top, gold trim", - "legwear": "black sarong, pelvic curtain,", + "bottom": "black sarong, pelvic curtain", + "legwear": "", "footwear": "black high heels, gold trim", "hands": "", "accessories": "gold jewelry, earrings," diff --git a/characters/rosalina.json b/data/characters/rosalina.json similarity index 88% rename from characters/rosalina.json rename to data/characters/rosalina.json index b8a501a..a0d688d 100644 --- a/characters/rosalina.json +++ b/data/characters/rosalina.json @@ -20,8 +20,10 @@ }, "wardrobe": { "default": { - "headwear": "", - "top": "turquoise off-the-shoulder gown, silver trim", + "full_body": "turquoise gown, silver trim", + "headwear": "silver crown", + "top": "bare shoulders", + "bottom": "", "legwear": "long skirt", "footwear": "silver high heels", "hands": "", diff --git a/characters/rouge_the_bat.json b/data/characters/rouge_the_bat.json similarity index 80% rename from characters/rouge_the_bat.json rename to data/characters/rouge_the_bat.json index c964b8e..dca9efa 100644 --- a/characters/rouge_the_bat.json +++ b/data/characters/rouge_the_bat.json @@ -4,7 +4,7 @@ "identity": { "base_specs": "1girl, anthro, bat girl, white fur", "hair": "short white hair", - "eyes": "teal eyes", + "eyes": "teal eyes, blue eyeshadow", "hands": "white gloves", "arms": "", "torso": "large breasts", @@ -20,12 +20,14 @@ }, "wardrobe": { "default": { + "full_body": "black skin-tight jumpsuit", "headwear": "", - "top": "black skin-tight jumpsuit, pink heart-shaped chest plate, bare shoulders, cleavage", - "legwear": "jumpsuit", + "top": "pink heart-shaped chest plate, bare shoulders, cleavage", + "bottom": "", + "legwear": "", "footwear": "white boots, pink heart motifs", "hands": "white gloves, pink cuffs", - "accessories": "blue eyeshadow" + "accessories": "" } }, "styles": { diff --git a/characters/ryouko_hakubi.json b/data/characters/ryouko_hakubi.json similarity index 78% rename from characters/ryouko_hakubi.json rename to data/characters/ryouko_hakubi.json index e6d02b5..2b0dcac 100644 --- a/characters/ryouko_hakubi.json +++ b/data/characters/ryouko_hakubi.json @@ -20,12 +20,14 @@ }, "wardrobe": { "default": { - "headwear": "long white dress, plunging neckline, black belt", - "top": "black and orange long sleeve jacket with purple trim,", - "legwear": "side_slit,, red trousers", + "full_body": "long white dress", + "headwear": "", + "top": "black and orange long sleeve jacket with purple trim, plunging neckline", + "bottom": "black belt, red trousers", + "legwear": "side slit", "footwear": "", - "hands": "red gloves", - "accessories": "red gems, wristbands" + "hands": "red gloves, red gem on back of hand", + "accessories": "wristbands" } }, "styles": { diff --git a/characters/sam_totally_spies.json b/data/characters/sam_totally_spies.json similarity index 91% rename from characters/sam_totally_spies.json rename to data/characters/sam_totally_spies.json index 99f464e..29ef0c7 100644 --- a/characters/sam_totally_spies.json +++ b/data/characters/sam_totally_spies.json @@ -20,8 +20,10 @@ }, "wardrobe": { "default": { - "headwear": "green bodysuit, catsuit, skin tight", + "full_body": "green bodysuit, catsuit, skin tight", + "headwear": "", "top": "", + "bottom": "", "legwear": "", "footwear": "heels ", "hands": "green bodysuit", diff --git a/characters/samus_aran_zero_suit.json b/data/characters/samus_aran_zero_suit.json similarity index 82% rename from characters/samus_aran_zero_suit.json rename to data/characters/samus_aran_zero_suit.json index fccdb7e..28d3409 100644 --- a/characters/samus_aran_zero_suit.json +++ b/data/characters/samus_aran_zero_suit.json @@ -20,10 +20,12 @@ }, "wardrobe": { "default": { + "full_body": "blue skin-tight bodysuit, zero suit, pink lines", "headwear": "", - "top": "blue skin-tight bodysuit, pink symbols", - "legwear": "bodysuit", - "footwear": "blue high-heeled boots", + "top": "", + "bottom": "", + "legwear": "", + "footwear": "metal high-heeled boots, yellow glow", "hands": "zero suit", "accessories": "paralyzer pistol" } diff --git a/characters/sarah_miller.json b/data/characters/sarah_miller.json similarity index 64% rename from characters/sarah_miller.json rename to data/characters/sarah_miller.json index a96ae24..043d573 100644 --- a/characters/sarah_miller.json +++ b/data/characters/sarah_miller.json @@ -14,29 +14,31 @@ "extra": "" }, "defaults": { - "expression": "", - "pose": "", - "scene": "" + "expression": "smile", + "pose": "leaning back", + "scene": "living room, couch, low lighting" }, "wardrobe": { "default": { - "headwear": "grey t-shirt, white shirt", + "full_body": "", + "headwear": "grey t-shirt,", "top": "", - "legwear": "blue jeans", - "footwear": "sneakers", + "bottom": "", + "legwear": "pajama pants", + "footwear": "barefoot", "hands": "", "accessories": "wristwatch" } }, "styles": { - "aesthetic": "casual, 2013 fashion, living room", + "aesthetic": "casual, 2013 fashion,", "primary_color": "grey", "secondary_color": "blue", "tertiary_color": "white" }, "lora": { - "lora_name": "Illustrious/Looks/Sarah_Miller_Illustrious.safetensors", - "lora_weight": 0.8, + "lora_name": "Illustrious/Looks/SarahMillerOGILf_1328931.safetensors", + "lora_weight": 0.6, "lora_triggers": "" }, "tags": [ diff --git a/characters/scarlet_ff7.json b/data/characters/scarlet_ff7.json similarity index 79% rename from characters/scarlet_ff7.json rename to data/characters/scarlet_ff7.json index c706488..b14d75f 100644 --- a/characters/scarlet_ff7.json +++ b/data/characters/scarlet_ff7.json @@ -14,15 +14,17 @@ "extra": "red lipstick, heavy makeup" }, "defaults": { - "expression": "", - "pose": "", - "scene": "" + "expression": "smirk", + "pose": "stepping on viewer, from below", + "scene": "penthouse office, night" }, "wardrobe": { "default": { + "full_body": "red dress, formal dress, pencil dress", "headwear": "", - "top": "red dress, formal dress, pencil dress, sleeveless, chest cutout", - "legwear": "long skirt, high slit, side slit,black stockings", + "top": "sleeveless, chest cutout", + "bottom": "high slit", + "legwear": "side slit, black stockings", "footwear": "high heels, red heels, stiletto heels", "hands": "", "accessories": "jewelry, gold earrings, necklace" diff --git a/characters/shantae.json b/data/characters/shantae.json similarity index 80% rename from characters/shantae.json rename to data/characters/shantae.json index 5e9c0e6..bf99338 100644 --- a/characters/shantae.json +++ b/data/characters/shantae.json @@ -14,14 +14,16 @@ "extra": "" }, "defaults": { - "expression": "", - "pose": "", - "scene": "" + "expression": "smile", + "pose": "belly dancing, hands above head", + "scene": "desert town, oasis" }, "wardrobe": { "default": { + "full_body": "gold trim", "headwear": "", - "top": "red bikini top, red harem pants, gold trim", + "top": "red bikini top", + "bottom": "red harem pants", "legwear": "", "footwear": "gold shoes", "hands": "", diff --git a/characters/sorceress_dragons_crown.json b/data/characters/sorceress_dragons_crown.json similarity index 81% rename from characters/sorceress_dragons_crown.json rename to data/characters/sorceress_dragons_crown.json index f5daaa0..76650cf 100644 --- a/characters/sorceress_dragons_crown.json +++ b/data/characters/sorceress_dragons_crown.json @@ -20,10 +20,12 @@ }, "wardrobe": { "default": { - "headwear": "white top", - "top": "black corset,, clothing cutout, low cut, witch hat", - "legwear": "skirt, high slit, side slit", - "footwear": "boots, ", + "full_body": "", + "headwear": "witch hat", + "top": "black corset, white top, clothing cutout, low cut", + "bottom": "skirt, high slit, black skirt", + "legwear": "side slit", + "footwear": "boots", "hands": "", "accessories": "staff, necklace, bracelets, jewelry, wooden staff" } diff --git a/characters/sucy_manbavaran.json b/data/characters/sucy_manbavaran.json similarity index 68% rename from characters/sucy_manbavaran.json rename to data/characters/sucy_manbavaran.json index b3cec1b..2ad5a83 100644 --- a/characters/sucy_manbavaran.json +++ b/data/characters/sucy_manbavaran.json @@ -14,22 +14,24 @@ "extra": "dark circles under eyes" }, "defaults": { - "expression": "", - "pose": "", - "scene": "" + "expression": "neutral expression", + "pose": "looking at mushroom", + "scene": "dark forest" }, "wardrobe": { "default": { - "headwear": "", - "top": "dark purple witch robes", + "full_body": "luna nova school uniform, dark purple witch robes", + "headwear": "pointed witch hat", + "top": "oversized sleeves", + "bottom": "", "legwear": "long skirt with frayed edges", "footwear": "brown boots", "hands": "", - "accessories": "pointed witch hat, potion bottle" + "accessories": "potion bottle, mushroom" } }, "styles": { - "aesthetic": "mushroom, gothic, whimsical, little witch academia style", + "aesthetic": "gothic, magic, little witch academia style", "primary_color": "purple", "secondary_color": "mauve", "tertiary_color": "green" diff --git a/characters/tifa_lockhart.json b/data/characters/tifa_lockhart.json similarity index 70% rename from characters/tifa_lockhart.json rename to data/characters/tifa_lockhart.json index 5e3283d..c7d5082 100644 --- a/characters/tifa_lockhart.json +++ b/data/characters/tifa_lockhart.json @@ -14,16 +14,18 @@ "extra": "" }, "defaults": { - "expression": "", - "pose": "", - "scene": "" + "expression": "smile", + "pose": "leaning on counter, looking at viewer", + "scene": "tavern, bartending" }, "wardrobe": { "default": { - "headwear": "black sports bra", - "top": "white tank top, black suspenders", - "legwear": "black miniskirt", - "footwear": "red boots, thigh high black socks", + "full_body": "", + "headwear": "", + "top": "white tank top, black sports bra,black suspenders", + "bottom": "black miniskirt", + "legwear": "thigh high black socks", + "footwear": "red boots", "hands": "red fingerless gloves", "accessories": "silver earrings" } diff --git a/characters/tracer.json b/data/characters/tracer.json similarity index 78% rename from characters/tracer.json rename to data/characters/tracer.json index 93eaabf..3c5b2a8 100644 --- a/characters/tracer.json +++ b/data/characters/tracer.json @@ -14,17 +14,19 @@ "extra": "freckles" }, "defaults": { - "expression": "", - "pose": "", - "scene": "" + "expression": "smile", + "pose": "dashing, blue afterimage", + "scene": "futuristic city" }, "wardrobe": { "default": { - "headwear": "orange leggings", + "full_body": "", + "headwear": "", "top": "brown flight jacket, yellow vest", - "legwear": "orange leggings", + "bottom": "orange leggings", + "legwear": "", "footwear": "white and orange sneakers", - "hands": "", + "hands": "fingerless gloves", "accessories": "chronal accelerator, yellow goggles" } }, diff --git a/characters/urbosa.json b/data/characters/urbosa.json similarity index 96% rename from characters/urbosa.json rename to data/characters/urbosa.json index a267f4a..3d66510 100644 --- a/characters/urbosa.json +++ b/data/characters/urbosa.json @@ -20,8 +20,10 @@ }, "wardrobe": { "default": { + "full_body": "", "headwear": "", "top": "blue top, green sash, green shoulder guards,", + "bottom": "", "legwear": "blue sarong", "footwear": "anklet, gold heels", "hands": "", diff --git a/characters/widowmaker.json b/data/characters/widowmaker.json similarity index 79% rename from characters/widowmaker.json rename to data/characters/widowmaker.json index 1162ee2..9e3ad45 100644 --- a/characters/widowmaker.json +++ b/data/characters/widowmaker.json @@ -14,15 +14,17 @@ "extra": "" }, "defaults": { - "expression": "", - "pose": "", - "scene": "" + "expression": "light smile", + "pose": "sniping, prone", + "scene": "rooftop, night" }, "wardrobe": { "default": { + "full_body": "purple bodysuit, skintight", "headwear": "", - "top": "purple bodysuit, plunging neckline", - "legwear": "bodysuit", + "top": " plunging neckline", + "bottom": "", + "legwear": "", "footwear": "purple high-heeled boots", "hands": "purple gauntlets", "accessories": "sniper rifle, visor" diff --git a/characters/yor_briar.json b/data/characters/yor_briar.json similarity index 75% rename from characters/yor_briar.json rename to data/characters/yor_briar.json index ea6190e..4b9a017 100644 --- a/characters/yor_briar.json +++ b/data/characters/yor_briar.json @@ -14,14 +14,16 @@ "extra": "" }, "defaults": { - "expression": "", - "pose": "", - "scene": "" + "expression": "serious", + "pose": "leaping", + "scene": "rooftop, night, red sky, blood splatter" }, "wardrobe": { "default": { + "full_body": "black halter dress", "headwear": "", - "top": "black backless halter dress, red rose pattern inside", + "top": "backless, pattern inside", + "bottom": "", "legwear": "black thigh-high boots", "footwear": "black boots", "hands": "black fingerless gloves", @@ -29,7 +31,7 @@ } }, "styles": { - "aesthetic": "elegant, assassin, spy x family style", + "aesthetic": "elegant, assassin,red rose, spy x family style", "primary_color": "black", "secondary_color": "red", "tertiary_color": "gold" diff --git a/characters/yshtola_rhul.json b/data/characters/yshtola_rhul.json similarity index 88% rename from characters/yshtola_rhul.json rename to data/characters/yshtola_rhul.json index 79e8d27..054fe4d 100644 --- a/characters/yshtola_rhul.json +++ b/data/characters/yshtola_rhul.json @@ -20,9 +20,11 @@ }, "wardrobe": { "default": { + "full_body": "black sorceress robes, ", "headwear": "", - "top": "black sorceress robes, fur trim", - "legwear": "long skirt", + "top": "bare shoulders, fur trim", + "bottom": "", + "legwear": "", "footwear": "black boots", "hands": "", "accessories": "wooden staff" diff --git a/characters/yuffie_kisaragi.json b/data/characters/yuffie_kisaragi.json similarity index 74% rename from characters/yuffie_kisaragi.json rename to data/characters/yuffie_kisaragi.json index f29de81..9fb3f91 100644 --- a/characters/yuffie_kisaragi.json +++ b/data/characters/yuffie_kisaragi.json @@ -14,22 +14,24 @@ "extra": "headband" }, "defaults": { - "expression": "", - "pose": "", - "scene": "" + "expression": "cheeky smile", + "pose": "holding glass orb, materia", + "scene": "forest, sunlight" }, "wardrobe": { "default": { + "full_body": "", "headwear": "", "top": "green turtleneck sweater vest, midriff", - "legwear": "beige shorts", - "footwear": "boots, socks", + "bottom": "beige shorts", + "legwear": "single kneehigh sock", + "footwear": "boots, ", "hands": "fingerless glove on one hand, large gauntlet on one arm", "accessories": "shuriken" } }, "styles": { - "aesthetic": "ninja, adventurer, final fantasy style", + "aesthetic": "ninja, japanese, final fantasy style", "primary_color": "green", "secondary_color": "beige", "tertiary_color": "black" diff --git a/characters/yuna_ffx.json b/data/characters/yuna_ffx.json similarity index 63% rename from characters/yuna_ffx.json rename to data/characters/yuna_ffx.json index 1e35e6c..9de587c 100644 --- a/characters/yuna_ffx.json +++ b/data/characters/yuna_ffx.json @@ -14,22 +14,24 @@ "extra": "" }, "defaults": { - "expression": "", - "pose": "", - "scene": "" + "expression": "serene", + "pose": "dancing", + "scene": "standing on water, sunset, pink sky, " }, "wardrobe": { "default": { - "headwear": "white kimono top, yellow obi", - "top": "", - "legwear": "long blue skirt, floral pattern", - "footwear": "boots", - "hands": "detached sleeves", + "full_body": "", + "headwear": "", + "top": "white kimono top, yellow obi, detached sleeves", + "bottom": "long blue skirt, floral pattern", + "legwear": "", + "footwear": "black boots", + "hands": "", "accessories": "summoner staff, necklace" } }, "styles": { - "aesthetic": "sunset, pink sky, shrine maiden,fantasy, final fantasy x style", + "aesthetic": "shrine maiden,fantasy, final fantasy x style", "primary_color": "white", "secondary_color": "blue", "tertiary_color": "yellow" diff --git a/data/clothing/bikini_01.json b/data/clothing/bikini_01.json new file mode 100644 index 0000000..14fe6f7 --- /dev/null +++ b/data/clothing/bikini_01.json @@ -0,0 +1,26 @@ +{ + "outfit_id": "bikini_01", + "outfit_name": "Bikini", + "wardrobe": { + "full_body": "", + "headwear": "", + "top": "bikini top", + "bottom": "bikini bottom", + "legwear": "", + "footwear": "barefoot", + "hands": "", + "accessories": "" + }, + "lora": { + "lora_name": "", + "lora_weight": 0.8, + "lora_triggers": "" + }, + "tags": [ + "bikini", + "swimsuit", + "navel", + "cleavage", + "summer" + ] +} \ No newline at end of file diff --git a/data/clothing/bikini_02.json b/data/clothing/bikini_02.json new file mode 100644 index 0000000..f975d20 --- /dev/null +++ b/data/clothing/bikini_02.json @@ -0,0 +1,26 @@ +{ + "outfit_id": "bikini_02", + "outfit_name": "Bikini (Slingshot)", + "wardrobe": { + "full_body": "", + "headwear": "", + "top": "slingshot swimsuit", + "bottom": "", + "legwear": "", + "footwear": "", + "hands": "", + "accessories": "" + }, + "lora": { + "lora_name": "", + "lora_weight": 0.8, + "lora_triggers": "" + }, + "tags": [ + "slingshot swimsuit", + "swimsuit", + "highleg", + "navel", + "revealing clothes" + ] +} \ No newline at end of file diff --git a/data/clothing/cat_cosplay.json b/data/clothing/cat_cosplay.json new file mode 100644 index 0000000..ab36036 --- /dev/null +++ b/data/clothing/cat_cosplay.json @@ -0,0 +1,27 @@ +{ + "outfit_id": "cat_cosplay", + "outfit_name": "Cat Cosplay", + "wardrobe": { + "full_body": "", + "headwear": "cat ears, nekomimi, hairband", + "top": "bikini top, string bikini", + "bottom": "bikini bottom, panties", + "legwear": "thighhighs", + "footwear": "high heels", + "hands": "paw gloves, animal hands", + "accessories": "collar, bell choker, cat tail" + }, + "lora": { + "lora_name": "", + "lora_weight": 0.8, + "lora_triggers": "" + }, + "tags": [ + "cat girl", + "nekomimi", + "bikini", + "cosplay", + "animal ears", + "tail" + ] +} \ No newline at end of file diff --git a/data/clothing/evening_gown.json b/data/clothing/evening_gown.json new file mode 100644 index 0000000..2410bf6 --- /dev/null +++ b/data/clothing/evening_gown.json @@ -0,0 +1,25 @@ +{ + "outfit_id": "evening_gown", + "outfit_name": "Evening Gown", + "wardrobe": { + "full_body": "", + "headwear": "", + "top": "evening gown", + "bottom": "", + "legwear": "", + "footwear": "high heels", + "hands": "", + "accessories": "" + }, + "lora": { + "lora_name": "", + "lora_weight": 0.8, + "lora_triggers": "" + }, + "tags": [ + "formal", + "elegant", + "long dress", + "flowing" + ] +} \ No newline at end of file diff --git a/data/clothing/french_maid_01.json b/data/clothing/french_maid_01.json new file mode 100644 index 0000000..a97acc6 --- /dev/null +++ b/data/clothing/french_maid_01.json @@ -0,0 +1,22 @@ +{ + "outfit_id": "french_maid_01", + "outfit_name": "French Maid", + "wardrobe": { + "full_body": "", + "headwear": "hairband", + "top": "corset, low cut top", + "bottom": "frilled skirt", + "legwear": "lace stockings", + "footwear": "heels", + "hands": "frilled sleeves", + "accessories": "apron" + }, + "lora": { + "lora_name": "Illustrious/Clothing/V2_Latex_Maid_Illustrious.safetensors", + "lora_weight": 0.8, + "lora_triggers": "" + }, + "tags": [ + "French Maid" + ] +} \ No newline at end of file diff --git a/data/clothing/french_maid_02.json b/data/clothing/french_maid_02.json new file mode 100644 index 0000000..ec4b2dc --- /dev/null +++ b/data/clothing/french_maid_02.json @@ -0,0 +1,23 @@ +{ + "outfit_id": "french_maid_02", + "outfit_name": "French Maid (Latex)", + "wardrobe": { + "full_body": "", + "headwear": "hairband", + "top": "corset, low cut top", + "bottom": "frilled skirt", + "legwear": "lace stockings", + "footwear": "heels", + "hands": "frilled sleeves", + "accessories": "apron" + }, + "lora": { + "lora_name": "Illustrious/Clothing/V2_Latex_Maid_Illustrious.safetensors", + "lora_weight": 0.8, + "lora_triggers": "" + }, + "tags": [ + "French Maid", + "latex" + ] +} \ No newline at end of file diff --git a/data/clothing/latex_outfit.json b/data/clothing/latex_outfit.json new file mode 100644 index 0000000..7643d38 --- /dev/null +++ b/data/clothing/latex_outfit.json @@ -0,0 +1,26 @@ +{ + "outfit_id": "latex_outfit", + "outfit_name": "Latex Outfit", + "wardrobe": { + "full_body": "", + "headwear": "latex cat ears", + "top": "latex bodysuit, breast cutout, zipper, sleeveless, highleg", + "bottom": "", + "legwear": "latex thighhighs, garter belt", + "footwear": "thigh boots, high heels, latex boots", + "hands": "elbow gloves, latex gloves", + "accessories": "choker, collar" + }, + "lora": { + "lora_name": "Illustrious/Clothing/latex_clothing.safetensors", + "lora_weight": 0.8, + "lora_triggers": "" + }, + "tags": [ + "black latex", + "shiny", + "fetish", + "tight clothing", + "erotic" + ] +} \ No newline at end of file diff --git a/data/clothing/lingerie.json b/data/clothing/lingerie.json new file mode 100644 index 0000000..b03b5a2 --- /dev/null +++ b/data/clothing/lingerie.json @@ -0,0 +1,26 @@ +{ + "outfit_id": "lingerie", + "outfit_name": "Lingerie", + "wardrobe": { + "full_body": "", + "headwear": "", + "top": "lace bra", + "bottom": "lace panties", + "legwear": "lace stockings", + "footwear": "", + "hands": "", + "accessories": "" + }, + "lora": { + "lora_name": "", + "lora_weight": 0.8, + "lora_triggers": "" + }, + "tags": [ + "lingerie", + "lace", + "lace trim", + "underwear", + "matching set" + ] +} \ No newline at end of file diff --git a/data/clothing/lingerie_02.json b/data/clothing/lingerie_02.json new file mode 100644 index 0000000..a23a70a --- /dev/null +++ b/data/clothing/lingerie_02.json @@ -0,0 +1,26 @@ +{ + "outfit_id": "lingerie_02", + "outfit_name": "Lingerie (Latex)", + "wardrobe": { + "full_body": "", + "headwear": "", + "top": "latex bra, latex choker", + "bottom": "latex panties", + "legwear": "latex stockings", + "footwear": "", + "hands": "latex elbow gloves", + "accessories": "" + }, + "lora": { + "lora_name": "", + "lora_weight": 0.8, + "lora_triggers": "" + }, + "tags": [ + "lingerie", + "lace", + "lace trim", + "underwear", + "matching set" + ] +} \ No newline at end of file diff --git a/data/clothing/mother.json b/data/clothing/mother.json new file mode 100644 index 0000000..0bbb626 --- /dev/null +++ b/data/clothing/mother.json @@ -0,0 +1,27 @@ +{ + "outfit_id": "mother", + "outfit_name": "Mother", + "wardrobe": { + "full_body": "", + "headwear": "", + "top": "knit sweater, turtleneck, sweater, long sleeves", + "bottom": "long skirt, maxi skirt", + "legwear": "pantyhose", + "footwear": "flat shoes, slippers", + "hands": "wedding ring", + "accessories": "apron, necklace" + }, + "lora": { + "lora_name": "", + "lora_weight": 0.8, + "lora_triggers": "" + }, + "tags": [ + "milf", + "mother", + "mature female", + "casual", + "domestic", + "knitwear" + ] +} \ No newline at end of file diff --git a/data/clothing/nurse_01.json b/data/clothing/nurse_01.json new file mode 100644 index 0000000..41a50e3 --- /dev/null +++ b/data/clothing/nurse_01.json @@ -0,0 +1,22 @@ +{ + "outfit_id": "nurse_01", + "outfit_name": "Nurse", + "wardrobe": { + "full_body": "", + "headwear": "nurse cap", + "top": "nurse outfit", + "bottom": "short skirt", + "legwear": "stockings", + "footwear": "high heels", + "hands": "", + "accessories": "" + }, + "lora": { + "lora_name": "", + "lora_weight": 0.8, + "lora_triggers": "" + }, + "tags": [ + "nurse" + ] +} \ No newline at end of file diff --git a/data/clothing/nurse_02.json b/data/clothing/nurse_02.json new file mode 100644 index 0000000..eaa8345 --- /dev/null +++ b/data/clothing/nurse_02.json @@ -0,0 +1,23 @@ +{ + "outfit_id": "nurse_02", + "outfit_name": "Nurse (Latex)", + "wardrobe": { + "full_body": "", + "headwear": "nurse cap", + "top": "latex nurse outfit", + "bottom": "short skirt", + "legwear": "stockings", + "footwear": "high heels", + "hands": "", + "accessories": "" + }, + "lora": { + "lora_name": "", + "lora_weight": 0.8, + "lora_triggers": "" + }, + "tags": [ + "nurse", + "latex" + ] +} \ No newline at end of file diff --git a/data/clothing/pasties_01.json b/data/clothing/pasties_01.json new file mode 100644 index 0000000..df09538 --- /dev/null +++ b/data/clothing/pasties_01.json @@ -0,0 +1,20 @@ +{ + "outfit_id": "pasties_01", + "outfit_name": "Pasties", + "wardrobe": { + "full_body": "", + "headwear": "", + "top": "nipple pasties", + "bottom": "crotch pasties", + "legwear": "", + "footwear": "", + "hands": "", + "accessories": "" + }, + "lora": { + "lora_name": "", + "lora_weight": 0.8, + "lora_triggers": "" + }, + "tags": [] +} \ No newline at end of file diff --git a/data/clothing/playboy_bunny.json b/data/clothing/playboy_bunny.json new file mode 100644 index 0000000..590384a --- /dev/null +++ b/data/clothing/playboy_bunny.json @@ -0,0 +1,32 @@ +{ + "outfit_id": "playboy_bunny", + "outfit_name": "Playboy Bunny", + "wardrobe": { + "full_body": "playboy bunny, leotard, strapless leotard", + "headwear": "bunny ears, animal ears, headband", + "top": "", + "bottom": "", + "legwear": "pantyhose, fishnets, black pantyhose", + "footwear": "high heels", + "hands": "cuffs, wrist cuffs, white cuffs", + "accessories": "collar, bowtie, bunny tail" + }, + "lora": { + "lora_name": "", + "lora_weight": 0.8, + "lora_triggers": "" + }, + "tags": [ + "playboy bunny", + "bunny ears", + "leotard", + "fishnets", + "collar", + "bowtie", + "cuffs", + "bunny tail", + "pantyhose", + "high heels", + "animal ears" + ] +} \ No newline at end of file diff --git a/data/clothing/school_uniform_01.json b/data/clothing/school_uniform_01.json new file mode 100644 index 0000000..b49a045 --- /dev/null +++ b/data/clothing/school_uniform_01.json @@ -0,0 +1,20 @@ +{ + "outfit_id": "school_uniform_01", + "outfit_name": "School Uniform (Western)", + "wardrobe": { + "full_body": "", + "headwear": "hairband", + "top": "white shirt, tie, blazer", + "bottom": "skirt", + "legwear": "thigh high socks", + "footwear": "black shoes", + "hands": "", + "accessories": "" + }, + "lora": { + "lora_name": "", + "lora_weight": 0.8, + "lora_triggers": "" + }, + "tags": [] +} \ No newline at end of file diff --git a/data/clothing/school_uniform_02.json b/data/clothing/school_uniform_02.json new file mode 100644 index 0000000..b91d17f --- /dev/null +++ b/data/clothing/school_uniform_02.json @@ -0,0 +1,23 @@ +{ + "outfit_id": "school_uniform_02", + "outfit_name": "School Uniform (Sailor)", + "wardrobe": { + "full_body": "", + "headwear": "hairband", + "top": "white shirt, sailor scarf", + "bottom": "pleated skirt", + "legwear": "knee high socks", + "footwear": "black shoes", + "hands": "", + "accessories": "" + }, + "lora": { + "lora_name": "", + "lora_weight": 0.8, + "lora_triggers": "" + }, + "tags": [ + "school uniform", + "sailor" + ] +} \ No newline at end of file diff --git a/data/clothing/school_uniform_03.json b/data/clothing/school_uniform_03.json new file mode 100644 index 0000000..3434f6d --- /dev/null +++ b/data/clothing/school_uniform_03.json @@ -0,0 +1,27 @@ +{ + "outfit_id": "school_uniform_03", + "outfit_name": "School Uniform (Latex Sailor)", + "wardrobe": { + "full_body": "", + "headwear": "", + "top": "latex serafuku, sailor collar, cropped shirt, midriff, short sleeves", + "bottom": "pleated skirt, miniskirt, latex skirt", + "legwear": "latex thighhighs", + "footwear": "high heels", + "hands": "", + "accessories": "neckerchief" + }, + "lora": { + "lora_name": "", + "lora_weight": 0.8, + "lora_triggers": "" + }, + "tags": [ + "latex", + "school uniform", + "serafuku", + "shiny", + "midriff", + "navel" + ] +} \ No newline at end of file diff --git a/data/clothing/school_uniform_04.json b/data/clothing/school_uniform_04.json new file mode 100644 index 0000000..72a1c06 --- /dev/null +++ b/data/clothing/school_uniform_04.json @@ -0,0 +1,20 @@ +{ + "outfit_id": "school_uniform_04", + "outfit_name": "School Uniform (Erotic)", + "wardrobe": { + "full_body": "", + "headwear": "", + "top": "topless", + "bottom": "latex skirt, micro-skirt", + "legwear": "latex thigh highs", + "footwear": "high heels", + "hands": "latex elbow gloves", + "accessories": "" + }, + "lora": { + "lora_name": "", + "lora_weight": 0.8, + "lora_triggers": "" + }, + "tags": [] +} \ No newline at end of file diff --git a/models.py b/models.py index a7b8de4..c6c27e3 100644 --- a/models.py +++ b/models.py @@ -34,6 +34,19 @@ class Character(db.Model): def __repr__(self): return f'' +class Outfit(db.Model): + id = db.Column(db.Integer, primary_key=True) + outfit_id = db.Column(db.String(100), unique=True, nullable=False) + slug = db.Column(db.String(100), unique=True, nullable=False) + filename = db.Column(db.String(255), nullable=True) + name = db.Column(db.String(100), nullable=False) + data = db.Column(db.JSON, nullable=False) + default_fields = db.Column(db.JSON, nullable=True) + image_path = db.Column(db.String(255), nullable=True) + + def __repr__(self): + return f'' + class Settings(db.Model): id = db.Column(db.Integer, primary_key=True) openrouter_api_key = db.Column(db.String(255), nullable=True) diff --git a/templates/create.html b/templates/create.html index e077953..99989cc 100644 --- a/templates/create.html +++ b/templates/create.html @@ -14,22 +14,32 @@
- - -
Used for the JSON file and URL. No spaces or special characters.
+ + +
Used for the JSON file and URL. No spaces or special characters. Auto-generated from name if left empty.
-
+
+ + +
+ +
- + +
Required when AI generation is enabled.
-
- Once created, the system will automatically attempt to generate a cover image using the new profile. +
+ The AI will generate a complete character profile based on your description. +
+ +
+ A blank character profile will be created. You can edit it afterwards to add details.
- +
@@ -37,4 +47,28 @@
+ + {% endblock %} diff --git a/templates/detail.html b/templates/detail.html index 2070c36..a696d1d 100644 --- a/templates/detail.html +++ b/templates/detail.html @@ -1,10 +1,21 @@ {% extends "layout.html" %} {% block content %} + + +
-
+
{% if character.image_path %} {{ character.name }} {% else %} @@ -21,7 +32,6 @@
-
@@ -36,18 +46,28 @@ {% if preview_image %}
-
Latest Preview
+
+ Latest Preview +
+ +
+
-
+
Preview
{% else %}
-
Latest Preview
+
+ Latest Preview +
+ +
+
-
+
Preview
@@ -265,7 +285,7 @@ form.addEventListener('submit', async (e) => { // Only intercept generate actions const submitter = e.submitter; - if (!submitter || (submitter.value !== 'preview' && submitter.value !== 'replace')) { + if (!submitter || submitter.value !== 'preview') { return; } @@ -320,7 +340,7 @@ progressLabel.textContent = 'Saving image...'; const url = `/character/{{ character.slug }}/finalize_generation/${promptId}`; const formData = new FormData(); - formData.append('action', action); + formData.append('action', 'preview'); // Always save as preview try { const response = await fetch(url, { @@ -330,12 +350,19 @@ const data = await response.json(); if (data.success) { - if (action === 'preview') { - previewImg.src = data.image_url; - if (previewCard) previewCard.classList.remove('d-none'); - } else { - // Reload for cover update - window.location.reload(); + // Update preview image + previewImg.src = data.image_url; + if (previewCard) previewCard.classList.remove('d-none'); + + // Enable the replace cover button if it exists + const replaceBtn = document.getElementById('replace-cover-btn'); + if (replaceBtn) { + replaceBtn.disabled = false; + // Check if there's a form to update + const form = replaceBtn.closest('form'); + if (form) { + form.action = `/character/{{ character.slug }}/replace_cover_from_preview`; + } } } else { alert('Save failed: ' + data.error); @@ -348,5 +375,10 @@ } } }); + + // Image modal function + function showImage(src) { + document.getElementById('modalImage').src = src; + } {% endblock %} diff --git a/templates/index.html b/templates/index.html index c2ad2a7..bf1fb01 100644 --- a/templates/index.html +++ b/templates/index.html @@ -40,6 +40,12 @@
{{ char.name }}

{{ char.data.tags | join(', ') }}

+ {% if char.data.lora.lora_name %} + {% set lora_name = char.data.lora.lora_name.split('/')[-1].replace('.safetensors', '') %} + + {% endif %}
{% endfor %} diff --git a/templates/layout.html b/templates/layout.html index b5f4544..1fb99f5 100644 --- a/templates/layout.html +++ b/templates/layout.html @@ -18,6 +18,8 @@
Character Browser
+ Characters + Outfits Create Character Generator Settings diff --git a/templates/outfits/create.html b/templates/outfits/create.html new file mode 100644 index 0000000..24cc456 --- /dev/null +++ b/templates/outfits/create.html @@ -0,0 +1,74 @@ +{% extends "layout.html" %} + +{% block content %} +
+
+
+
+
Create New Outfit
+
+
+
+ + +
+ +
+ + +
Used for the JSON file and URL. No spaces or special characters. Auto-generated from name if left empty.
+
+ +
+ + +
+ +
+ + +
Required when AI generation is enabled.
+
+ +
+ The AI will generate a complete outfit profile with wardrobe items and tags based on your description. +
+ +
+ A blank outfit profile will be created. You can edit it afterwards to add details. +
+ +
+ +
+
+
+
+
+
+
+ + +{% endblock %} diff --git a/templates/outfits/detail.html b/templates/outfits/detail.html new file mode 100644 index 0000000..b6b1cee --- /dev/null +++ b/templates/outfits/detail.html @@ -0,0 +1,353 @@ +{% extends "layout.html" %} + +{% block content %} + + + +
+
+
+
+ {% if outfit.image_path %} + {{ outfit.name }} + {% else %} + No Image Attached + {% endif %} +
+
+
+
+ + +
+ +
+ + {# Character Selector #} +
+ + +
Select a character to preview this outfit on their model.
+
+ +
+ + +
+
+
+ +
+ +
+
0%
+
+
+ + {% if preview_image %} +
+
+ Latest Preview +
+ +
+
+
+
+ Preview +
+
+
+ {% else %} +
+
+ Latest Preview +
+ +
+
+
+
+ Preview +
+
+
+ {% endif %} + +
+
+ Tags +
+ + +
+
+
+ {% for tag in outfit.data.tags %} + {{ tag }} + {% else %} + No tags + {% endfor %} +
+
+
+ +
+
+
+

{{ outfit.name }}

+ Edit Profile +
+ +
+
+ Back to Gallery +
+ +
+ {# Wardrobe section #} + {% set wardrobe = outfit.data.get('wardrobe', {}) %} +
+
+ Wardrobe +
+
+
+ {% for key, value in wardrobe.items() %} +
+ + {{ key.replace('_', ' ') }} +
+
{{ value if value else '--' }}
+ {% endfor %} +
+
+
+ + {# LoRA section #} + {% set lora = outfit.data.get('lora', {}) %} + {% if lora %} +
+
LoRA
+
+
+ {% for key, value in lora.items() %} +
+ + {{ key.replace('_', ' ') }} +
+
{{ value if value else '--' }}
+ {% endfor %} +
+
+
+ {% endif %} +
+
+
+{% endblock %} + +{% block scripts %} + +{% endblock %} \ No newline at end of file diff --git a/templates/outfits/edit.html b/templates/outfits/edit.html new file mode 100644 index 0000000..bcfc8c1 --- /dev/null +++ b/templates/outfits/edit.html @@ -0,0 +1,80 @@ +{% extends "layout.html" %} + +{% block content %} +
+
+

Edit Outfit: {{ outfit.name }}

+ Cancel +
+ +
+
+
+ +
+
Basic Information
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
LoRA Settings
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+
+ + + {% set wardrobe = outfit.data.get('wardrobe', {}) %} +
+
Wardrobe
+
+ {% for key, value in wardrobe.items() %} +
+ + +
+ {% endfor %} +
+
+ +
+ Cancel + +
+
+
+
+
+{% endblock %} diff --git a/templates/outfits/index.html b/templates/outfits/index.html new file mode 100644 index 0000000..cf6af96 --- /dev/null +++ b/templates/outfits/index.html @@ -0,0 +1,41 @@ +{% extends "layout.html" %} + +{% block content %} +
+

Outfit Gallery

+
+ Create New Outfit +
+ +
+
+
+ +
+ {% for outfit in outfits %} +
+
+
+ {% if outfit.image_path %} + {{ outfit.name }} + No Image + {% else %} + {{ outfit.name }} + No Image + {% endif %} +
+
+
{{ outfit.name }}
+

{{ outfit.data.tags | join(', ') }}

+
+ {% if outfit.data.lora.lora_name %} + {% set lora_name = outfit.data.lora.lora_name.split('/')[-1].replace('.safetensors', '') %} + + {% endif %} +
+
+ {% endfor %} +
+{% endblock %}