""" setup_clothing_metadata.py Usage: python3 tooling/setup_clothing_metadata.py Creates coverage.json and reference_mask.png for all 5 initial clothing items. This is a pure-Python script — no Blender required. Arguments: clothing_dir Root directory for clothing output (e.g. client/assets/characters/clothing/). Each item subdirectory must already exist. Files created per item: coverage.json -- segment hides + torso_variant (compositor input) reference_mask.png -- greyscale recolor mask (all-white v0.2 placeholder: fully tintable) coverage.json format: { "hides": ["torso", "arm_upper_l", ...], "torso_variant": "full" } hides entries use short segment names (without "seg_" prefix). torso_variant: "full" = clothing hides seg_torso (full torso segment) : "upper" = clothing only hides seg_torso_upper (upper chest) reference_mask.png: 64x64 grayscale PNG. White (255) = fully tintable. v0.2 placeholder — all white. Final art will have partial masks (black areas = preserve material texture color). Decisions: D-162 (clothing pre-baked per body type via Surface Deform) """ import sys import os import json import zlib import struct # ------------------------------------------------------------------------- # Clothing catalogue metadata # ------------------------------------------------------------------------- CLOTHING_ITEMS = { "coveralls_basic": { "description": "Full-body work suit", "hides": [ "torso", "arm_upper_l", "arm_upper_r", "arm_lower_l", "arm_lower_r", "hand_l", "hand_r", "leg_upper_l", "leg_upper_r", "leg_lower_l", "leg_lower_r", "foot_l", "foot_r", ], "torso_variant": "full", }, "jacket_utility": { "description": "Upper body outerwear", "hides": [ "torso", "arm_upper_l", "arm_upper_r", "arm_lower_l", "arm_lower_r", ], "torso_variant": "full", }, "pants_cargo": { # Note: pants hide leg segments but NOT foot segments. Boots (boots_work) # hide feet. This distinction matters for the compositor — a character # wearing pants + no boots shows bare feet via seg_foot_l/r. "description": "Lower body cargo trousers", "hides": [ "leg_upper_l", "leg_upper_r", "leg_lower_l", "leg_lower_r", ], "torso_variant": "full", }, "shirt_henley": { "description": "Upper body inner shirt", "hides": [ "torso", "arm_upper_l", "arm_upper_r", ], "torso_variant": "full", }, "boots_work": { "description": "Work boots (foot slot + lower leg shaft)", "hides": [ "foot_l", "foot_r", "leg_lower_l", "leg_lower_r", ], "torso_variant": "full", }, } MASK_WIDTH = 64 MASK_HEIGHT = 64 # ------------------------------------------------------------------------- # Minimal PNG writer (no external dependencies) # ------------------------------------------------------------------------- def _make_png_chunk(chunk_type, data): """Construct a PNG chunk with CRC.""" payload = chunk_type + data crc = zlib.crc32(payload) & 0xFFFFFFFF return struct.pack(">I", len(data)) + payload + struct.pack(">I", crc) def write_white_grayscale_png(path, width=64, height=64): """ Write a minimal all-white grayscale PNG to `path`. Uses only Python built-ins (zlib, struct) — no Pillow required. """ # PNG signature signature = b'\x89PNG\r\n\x1a\n' # IHDR: width, height, bit_depth=8, colortype=0 (grayscale), # compression=0, filter=0, interlace=0 ihdr_data = struct.pack(">IIBBBBB", width, height, 8, 0, 0, 0, 0) ihdr = _make_png_chunk(b'IHDR', ihdr_data) # IDAT: raw scanlines — filter byte 0x00 (None) + width bytes of 0xFF (white) raw_rows = b''.join(b'\x00' + b'\xff' * width for _ in range(height)) compressed = zlib.compress(raw_rows, 9) idat = _make_png_chunk(b'IDAT', compressed) # IEND iend = _make_png_chunk(b'IEND', b'') with open(path, 'wb') as f: f.write(signature + ihdr + idat + iend) # ------------------------------------------------------------------------- # Per-item setup # ------------------------------------------------------------------------- def setup_item(item_id, config, clothing_dir): """ Write coverage.json and reference_mask.png for one clothing item. Returns (coverage_ok, mask_ok). """ item_dir = os.path.join(clothing_dir, item_id) os.makedirs(item_dir, exist_ok=True) # coverage.json coverage = { "hides": config["hides"], "torso_variant": config["torso_variant"], } coverage_path = os.path.join(item_dir, "coverage.json") with open(coverage_path, 'w') as f: json.dump(coverage, f, indent=2) coverage_ok = True # reference_mask.png (all-white placeholder) mask_path = os.path.join(item_dir, "reference_mask.png") write_white_grayscale_png(mask_path, MASK_WIDTH, MASK_HEIGHT) mask_ok = True return coverage_ok, mask_ok # ------------------------------------------------------------------------- # Entry point # ------------------------------------------------------------------------- if __name__ == "__main__": if len(sys.argv) < 2: print("Usage: python3 tooling/setup_clothing_metadata.py ") sys.exit(1) clothing_dir = sys.argv[1] print(f"\nClothing Metadata Setup") print(f" Output dir: {clothing_dir}") print(f" Items: {len(CLOTHING_ITEMS)}") all_ok = True for item_id, config in CLOTHING_ITEMS.items(): coverage_ok, mask_ok = setup_item(item_id, config, clothing_dir) status = "OK" if (coverage_ok and mask_ok) else "FAILED" print(f" {item_id:20s} {status} " f"coverage={'ok' if coverage_ok else 'FAIL'} " f"mask={'ok' if mask_ok else 'FAIL'}") if not (coverage_ok and mask_ok): all_ok = False print(f"\n Done. {'All OK.' if all_ok else 'Some items failed.'}") if not all_ok: sys.exit(1)