165 lines
5.7 KiB
Python
165 lines
5.7 KiB
Python
#!/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()
|