| """Wardrobe slot extraction and validation.""" |
|
|
| from __future__ import annotations |
|
|
| from .schema import OutfitGraph, WardrobeSlot |
|
|
| SLOT_BLUEPRINTS: list[tuple[str, str, str, str, str]] = [ |
| ("hair_headwear", "sleek hair or sculptural headwear", "hair / metal", "obsidian with neon edge", "identity lock"), |
| ("upper_body", "structured bodice or fitted top", "chantilly_lace", "black lace and mesh", "lace / mesh adapter"), |
| ("outerwear", "long technical coat or jacket", "patent_leather", "jet black", "FLUX.2 material texture"), |
| ("hands", "gloves with interface details", "patent_leather", "black with crimson nodes", "detail LoRA"), |
| ("lower_body", "tailored lower silhouette", "layered_garments", "dark graphite", "garment consistency"), |
| ("footwear", "strong platform boots", "polished_leather", "matte black sole", "boot detail"), |
| ("jewelry", "choker, buckles, and hard points", "crimson_hardware", "crimson metal", "hardware glow"), |
| ("props", "holographic sigils and data tools", "holographic_glass", "cyan and magenta", "NEXUS glyphs"), |
| ("background_context", "rain, code, and location atmosphere", "neon_rain", "obsidian/cyan/crimson", "environmental continuity"), |
| ] |
|
|
|
|
| def _material_token(value: object) -> str: |
| return str(value).strip().lower().replace("-", "_").replace(" ", "_") |
|
|
|
|
| def build_outfit_graph(prompt: str, adult_mode: bool = False, controls: dict | None = None) -> OutfitGraph: |
| """ |
| Constructs an outfit graph with wardrobe slots configured by prompt analysis and optional controls. |
| |
| Parameters: |
| prompt (str): Text to analyze for slot configuration and locking keywords. |
| adult_mode (bool): If True, marks upper_body and lower_body slots as adult-only. Defaults to False. |
| controls (dict | None): Optional configuration dict with keys: locked_slots, locate_focus, outerwear, upper_body, footwear, hardware, palette. |
| |
| Returns: |
| OutfitGraph: Graph containing configured wardrobe slots and a computed score. |
| """ |
| lowered = prompt.lower() |
| controls = controls or {} |
| locked_slots = set(controls.get("locked_slots") or []) |
| locate_focus = set(controls.get("locate_focus") or []) |
| slots: list[WardrobeSlot] = [] |
| for index, (name, description, material, palette, lora_hint) in enumerate(SLOT_BLUEPRINTS): |
| if name == "outerwear" and controls.get("outerwear"): |
| description = str(controls["outerwear"]) |
| if name == "upper_body" and controls.get("upper_body"): |
| material = str(controls["upper_body"]) |
| if name == "footwear" and controls.get("footwear"): |
| description = str(controls["footwear"]) |
| if name == "jewelry" and controls.get("hardware"): |
| material = _material_token(controls["hardware"]) |
| if controls.get("palette"): |
| palette = str(controls["palette"]) |
| locked = any(token in lowered for token in name.split("_")) or material.replace("_", " ") in lowered |
| if name == "outerwear" and "coat" in lowered: |
| locked = True |
| if name == "footwear" and ("boot" in lowered or "platform" in lowered): |
| locked = True |
| if name == "jewelry" and ("crimson" in lowered or "hardware" in lowered): |
| locked = True |
| if name in locked_slots: |
| locked = True |
| slots.append( |
| WardrobeSlot( |
| name=name, |
| description=description, |
| material=material, |
| palette=palette, |
| lora_hint=lora_hint, |
| locked=locked, |
| adult_only=False if not adult_mode else name in {"upper_body", "lower_body"}, |
| locate_region="manual-focus" if name in locate_focus else "auto-map", |
| edit_priority=max(1, 5 - index // 2), |
| ) |
| ) |
| locked_count = sum(1 for slot in slots if slot.locked) |
| score = round(0.68 + min(0.24, locked_count * 0.035), 2) |
| return OutfitGraph(slots=slots, score=score) |
|
|