#!/usr/bin/env python3 """Migrate character, outfit, and action JSON files to new field groupings. New groups combine identity + wardrobe fields by body region: base - base_specs / full_body → all detailers head - hair+eyes / headwear → face detailer upper_body - arms+torso / top lower_body - pelvis+legs / bottom+legwear hands - hands / hands+gloves → hand detailer feet - feet / footwear → foot detailer additional - extra / accessories """ import json import glob import os DATA_DIR = os.path.join(os.path.dirname(__file__), 'data') def merge(*values): """Concatenate non-empty strings with ', '.""" parts = [v.strip().rstrip(',').strip() for v in values if v and v.strip()] return ', '.join(parts) def migrate_identity(identity): """Convert old identity dict to new grouped format.""" return { 'base': identity.get('base_specs', ''), 'head': merge(identity.get('hair', ''), identity.get('eyes', '')), 'upper_body': merge(identity.get('arms', ''), identity.get('torso', '')), 'lower_body': merge(identity.get('pelvis', ''), identity.get('legs', '')), 'hands': identity.get('hands', ''), 'feet': identity.get('feet', ''), 'additional': identity.get('extra', ''), } def migrate_wardrobe_outfit(outfit): """Convert old wardrobe outfit dict to new grouped format.""" return { 'base': outfit.get('full_body', ''), 'head': outfit.get('headwear', ''), 'upper_body': outfit.get('top', ''), 'lower_body': merge(outfit.get('bottom', ''), outfit.get('legwear', '')), 'hands': merge(outfit.get('hands', ''), outfit.get('gloves', '')), 'feet': outfit.get('footwear', ''), 'additional': outfit.get('accessories', ''), } def migrate_action(action): """Convert old action dict to new grouped format.""" return { 'base': action.get('full_body', ''), 'head': merge(action.get('head', ''), action.get('eyes', '')), 'upper_body': merge(action.get('arms', ''), action.get('torso', '')), 'lower_body': merge(action.get('pelvis', ''), action.get('legs', '')), 'hands': action.get('hands', ''), 'feet': action.get('feet', ''), 'additional': action.get('additional', ''), } def process_file(path, transform_fn, section_key): """Load JSON, apply transform to a section, write back.""" with open(path, 'r') as f: data = json.load(f) section = data.get(section_key) if section is None: print(f" SKIP {path} — no '{section_key}' section") return False # Check if already migrated (new keys present, no old keys) if 'upper_body' in section and 'base' in section and 'full_body' not in section and 'base_specs' not in section: print(f" SKIP {path} — already migrated") return False data[section_key] = transform_fn(section) with open(path, 'w') as f: json.dump(data, f, indent=2, ensure_ascii=False) f.write('\n') return True def process_character(path): """Migrate a character JSON file (identity + all wardrobe outfits).""" with open(path, 'r') as f: data = json.load(f) changed = False # Migrate identity identity = data.get('identity', {}) if identity and 'base_specs' in identity: data['identity'] = migrate_identity(identity) changed = True # Migrate wardrobe (nested format with named outfits) wardrobe = data.get('wardrobe', {}) if wardrobe: new_wardrobe = {} for outfit_name, outfit_data in wardrobe.items(): if isinstance(outfit_data, dict): if 'full_body' in outfit_data or 'headwear' in outfit_data or 'top' in outfit_data: new_wardrobe[outfit_name] = migrate_wardrobe_outfit(outfit_data) changed = True else: new_wardrobe[outfit_name] = outfit_data # already migrated or unknown format else: new_wardrobe[outfit_name] = outfit_data data['wardrobe'] = new_wardrobe if changed: with open(path, 'w') as f: json.dump(data, f, indent=2, ensure_ascii=False) f.write('\n') return changed def main(): stats = {'characters': 0, 'outfits': 0, 'actions': 0, 'skipped': 0} # Characters print("=== Characters ===") for path in sorted(glob.glob(os.path.join(DATA_DIR, 'characters', '*.json'))): name = os.path.basename(path) if process_character(path): print(f" OK {name}") stats['characters'] += 1 else: print(f" SKIP {name}") stats['skipped'] += 1 # Outfits (clothing) print("\n=== Outfits ===") for path in sorted(glob.glob(os.path.join(DATA_DIR, 'clothing', '*.json'))): name = os.path.basename(path) if process_file(path, migrate_wardrobe_outfit, 'wardrobe'): print(f" OK {name}") stats['outfits'] += 1 else: print(f" SKIP {name}") stats['skipped'] += 1 # Actions print("\n=== Actions ===") for path in sorted(glob.glob(os.path.join(DATA_DIR, 'actions', '*.json'))): name = os.path.basename(path) if process_file(path, migrate_action, 'action'): print(f" OK {name}") stats['actions'] += 1 else: print(f" SKIP {name}") stats['skipped'] += 1 print(f"\nDone! Characters: {stats['characters']}, Outfits: {stats['outfits']}, " f"Actions: {stats['actions']}, Skipped: {stats['skipped']}") if __name__ == '__main__': main()