#!/usr/bin/env python3 """Generates a blank, correctly-sized, labeled PNG for every game-art texture slot that hasn't been staged for an artist yet - see reimport_textures.py for bringing finished art back in. Only covers actual hand-drawn game art (tile layers, items, body parts) - not the auto-generated UI font/panel textures (engine/ui_assets.py), which regenerate from code and aren't meant to be hand-painted. Existing files in the output directory are left untouched, so re-running this after an artist has started replacing some of them won't blank out their work - it only fills in the gaps, the same "only write if missing" idiom engine/placeholder_textures.py already uses. Usage: python scripts/export_blank_textures.py [--out art_wip] """ from __future__ import annotations import argparse from pathlib import Path from typing import Iterator from PIL import Image, ImageDraw, ImageFont from engine.defs import DefRegistry from engine.placeholder_textures import TEXTURE_SIZE ROOT_DIR = Path(__file__).resolve().parent.parent DEFS_DIR = ROOT_DIR / "resources" / "defs" DEFAULT_OUT_DIR = ROOT_DIR / "art_wip" LABEL_FONT_SIZE = 7 LABEL_COLOR = (130, 130, 130, 220) _label_font = None def _font(): global _label_font if _label_font is None: _label_font = ImageFont.load_default(size=LABEL_FONT_SIZE) return _label_font def _blank_labeled_image(label: str, size: int) -> Image.Image: """A fully transparent canvas with just an identifying label drawn on it - a tile artist filling the whole square in opaque paint will naturally cover it; an icon artist working on a transparent background will still see it as a guide (and can paint over/around it). """ img = Image.new("RGBA", (size, size), (0, 0, 0, 0)) ImageDraw.Draw(img).text((1, 1), label, font=_font(), fill=LABEL_COLOR) return img def _texture_slots(registry: DefRegistry) -> Iterator[tuple[str, str, int]]: """Yields (relative_path, label, size) for every def with a texture path - the same set engine/placeholder_textures.py::generate_placeholder_textures covers. """ for layer_def in registry.tile_layer_defs.values(): if layer_def.texture: yield layer_def.texture, layer_def.id, TEXTURE_SIZE for item_def in registry.item_defs.values(): if item_def.texture: yield item_def.texture, item_def.id, TEXTURE_SIZE for part_def in registry.body_part_defs.values(): if part_def.texture: yield part_def.texture, part_def.id, TEXTURE_SIZE def export_blank_textures(registry: DefRegistry, out_dir: Path) -> tuple[int, int]: """Writes a blank labeled PNG for every texture slot not already present in out_dir. Returns (written_count, skipped_count). """ written = skipped = 0 for relative_path, label, size in _texture_slots(registry): path = out_dir / relative_path if path.exists(): skipped += 1 continue path.parent.mkdir(parents=True, exist_ok=True) _blank_labeled_image(label, size).save(path) written += 1 return written, skipped def main() -> None: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( "--out", type=Path, default=DEFAULT_OUT_DIR, help=f"staging directory for artists (default: {DEFAULT_OUT_DIR})" ) args = parser.parse_args() registry = DefRegistry.load(DEFS_DIR) written, skipped = export_blank_textures(registry, args.out) print(f"Wrote {written} blank texture template(s) to {args.out}") if skipped: print(f"Skipped {skipped} file(s) already staged there (not overwritten).") if __name__ == "__main__": main()