Pixellab based system
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Validate the part library in parts/.
|
||||
Validate a part library (default: the pixellab one in parts_pl/; --library classic for parts/).
|
||||
|
||||
Fails on:
|
||||
- a missing file for any part listed in sprite_parts.py
|
||||
@@ -8,9 +8,10 @@ Fails on:
|
||||
- any partial alpha
|
||||
- a missing (or duplicated) required marker for the slot / body type
|
||||
- any legal composite that triggers "canvas expanded": checked exhaustively per body
|
||||
via bounding boxes, plus a random sample of real composites
|
||||
via bounding boxes (only for libraries without a fits() rule), plus a random sample of
|
||||
real composites
|
||||
|
||||
python validate_parts.py [--sample N]
|
||||
python validate_parts.py [--library pixellab|classic] [--sample N] [--verbose]
|
||||
"""
|
||||
import random
|
||||
import sys
|
||||
@@ -21,10 +22,11 @@ import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
import generate_sprite as gs
|
||||
import sprite_parts as cfg
|
||||
from sprite_parts import TYPES
|
||||
|
||||
PARTS = Path("parts")
|
||||
ALLOWED = {tuple(v) for v in gs.KEYS.values()} | {gs.PUPIL, gs.EYE_WHITE} | {tuple(v) for v in gs.MARKERS.values()}
|
||||
lib = None # registry module, set in main
|
||||
PARTS = None
|
||||
ALLOWED = {tuple(v) for v in gs.KEYS.values()} | {gs.PUPIL, gs.EYE_WHITE} | {tuple(v) for v in gs.MARKERS.values()} | gs.FIXED_COLORS
|
||||
MARKER_BY_COLOR = {v: k for k, v in gs.MARKERS.items()}
|
||||
|
||||
|
||||
@@ -87,7 +89,7 @@ def exhaustive_extents():
|
||||
found by taking each side's extreme from a different slot (or one part covering both)."""
|
||||
problems = []
|
||||
worst = {}
|
||||
for body in cfg.BODY:
|
||||
for body in lib.BODY:
|
||||
plan = body["plans"][0]
|
||||
b = bbox_of("body", body["name"])
|
||||
if b is None:
|
||||
@@ -100,7 +102,7 @@ def exhaustive_extents():
|
||||
opt = {}
|
||||
for slot in gs.PICK_ORDER[1:]:
|
||||
lst = []
|
||||
for p in cfg.SLOTS[slot]:
|
||||
for p in lib.SLOTS[slot]:
|
||||
if p["name"] == "none" or plan not in p["plans"] or p["name"] in body["deny"]:
|
||||
continue
|
||||
if p["requires"] and not set(p["requires"]) & tags:
|
||||
@@ -138,10 +140,10 @@ def exhaustive_extents():
|
||||
w, wwhy = worst_span(0, 2, fx0, fx1)
|
||||
h, hwhy = worst_span(1, 3, fy0, fy1)
|
||||
worst[body["name"]] = (w, h, wwhy, hwhy)
|
||||
if w + 2 > gs.CANVAS:
|
||||
problems.append(f"{body['name']}: worst width {w} > {gs.CANVAS - 2} ({wwhy})")
|
||||
if h + gs.GROUND_MARGIN + 1 > gs.CANVAS:
|
||||
problems.append(f"{body['name']}: worst height {h} > {gs.CANVAS - gs.GROUND_MARGIN - 1} ({hwhy})")
|
||||
if w + 2 > lib.CANVAS:
|
||||
problems.append(f"{body['name']}: worst width {w} > {lib.CANVAS - 2} ({wwhy})")
|
||||
if h + lib.GROUND_MARGIN + 1 > lib.CANVAS:
|
||||
problems.append(f"{body['name']}: worst height {h} > {lib.CANVAS - lib.GROUND_MARGIN - 1} ({hwhy})")
|
||||
return problems, worst
|
||||
|
||||
|
||||
@@ -149,37 +151,52 @@ def sample_composites(n):
|
||||
problems = []
|
||||
rng = random.Random(12345)
|
||||
for i in range(n):
|
||||
ptype = rng.choice(cfg.TYPES)
|
||||
ptype = rng.choice(TYPES)
|
||||
prng = random.Random(i)
|
||||
plan, chosen, markings, tags = gs.pick_parts(ptype, prng)
|
||||
plan, chosen, markings, tags = gs.pick_parts(lib, ptype, prng)
|
||||
if any(not (PARTS / s / f"{p['name']}.png").exists() for s, p in chosen.items() if p["name"] != "none"):
|
||||
continue
|
||||
spr = gs.compose(str(PARTS), chosen, markings, prng)
|
||||
if max(spr.width + 2, spr.height + gs.GROUND_MARGIN + 1) > gs.CANVAS:
|
||||
if max(spr.width + 2, spr.height + lib.GROUND_MARGIN + 1) > lib.CANVAS:
|
||||
problems.append(f"sample {ptype} seed {i}: {spr.width}x{spr.height} "
|
||||
f"({', '.join(f'{s}={p['name']}' for s, p in chosen.items())})")
|
||||
return problems
|
||||
|
||||
|
||||
def main(argv):
|
||||
global lib, PARTS
|
||||
n = 2000
|
||||
if "--sample" in argv:
|
||||
n = int(argv[argv.index("--sample") + 1])
|
||||
name = argv[argv.index("--library") + 1] if "--library" in argv else gs.DEFAULT_LIBRARY
|
||||
if name not in gs.LIBRARIES:
|
||||
print(f"unknown library {name!r}, choose from {', '.join(gs.LIBRARIES)}")
|
||||
return 2
|
||||
lib = gs.load_library(name)
|
||||
PARTS = Path(lib.PARTS_DIR)
|
||||
print(f"library {name} ({PARTS}/, canvas {lib.CANVAS})")
|
||||
if not lib.BODY:
|
||||
print("library is empty (no bodies), nothing to validate")
|
||||
return 0
|
||||
errs = []
|
||||
for slot, parts in cfg.SLOTS.items():
|
||||
for slot, parts in lib.SLOTS.items():
|
||||
for p in parts:
|
||||
if p["name"] != "none":
|
||||
errs += check_file(slot, p)
|
||||
size_probs, worst = exhaustive_extents()
|
||||
errs += size_probs
|
||||
if hasattr(lib, "fits"):
|
||||
worst = {}
|
||||
print("exhaustive size check skipped: library has a fits() rule, relying on the random sample")
|
||||
else:
|
||||
size_probs, worst = exhaustive_extents()
|
||||
errs += size_probs
|
||||
errs += sample_composites(n)
|
||||
for e in errs:
|
||||
print("FAIL", e)
|
||||
if "--verbose" in argv:
|
||||
for b, (w, hh, ww, hw) in sorted(worst.items()):
|
||||
print(f"{b:16s} worst {w}x{hh} w:{ww} h:{hw}")
|
||||
total = sum(1 for parts in cfg.SLOTS.values() for p in parts if p["name"] != "none")
|
||||
present = sum(1 for slot, parts in cfg.SLOTS.items() for p in parts
|
||||
total = sum(1 for parts in lib.SLOTS.values() for p in parts if p["name"] != "none")
|
||||
present = sum(1 for slot, parts in lib.SLOTS.items() for p in parts
|
||||
if p["name"] != "none" and (PARTS / slot / f"{p['name']}.png").exists())
|
||||
print(f"{present}/{total} parts present, {len(errs)} problem(s)")
|
||||
return 1 if errs else 0
|
||||
|
||||
Reference in New Issue
Block a user