Updated generation pages.
This commit is contained in:
164
migrate_field_groups.py
Normal file
164
migrate_field_groups.py
Normal file
@@ -0,0 +1,164 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user