79 lines
4.7 KiB
Python
79 lines
4.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Batch-generate real sprites for every actor: flux_local -> bg_remove_local ->
|
|
trim + downscale (Pillow). Overwrites the placeholder PNGs in sequences/assets.
|
|
Full-res renders kept in assets_src/renders. Prompts appended to assets_src/PROMPTS.md.
|
|
Single facing per actor — the Blender 16-facing turntable pipeline is the Phase D
|
|
upgrade path in MEGAPLAN.md.
|
|
"""
|
|
import os, sys, datetime
|
|
sys.path.insert(0, os.path.dirname(__file__))
|
|
from mbgen import run, upload # noqa: E402
|
|
from PIL import Image # noqa: E402
|
|
|
|
ROOT = os.path.join(os.path.dirname(__file__), '..')
|
|
RENDERS = os.path.join(ROOT, 'assets_src', 'renders')
|
|
ASSETS = os.path.join(ROOT, 'mods', 'vinylgod', 'sequences', 'assets')
|
|
os.makedirs(RENDERS, exist_ok=True)
|
|
|
|
MRP = 'rusty junkyard robot aesthetic, rust metal with hot pink trim, analog dials and speaker parts, '
|
|
STR = 'glossy seamless white tech aesthetic, soft blue ring lights, smooth rounded minimal design, '
|
|
SUF = ', isometric three-quarter view from above, single object centered, video game asset, plain flat grey background, no text'
|
|
|
|
ACTORS = [
|
|
('popupvan', 24, MRP + 'small delivery truck covered in band stickers and a roof-mounted loudspeaker'),
|
|
('popupshop', 64, MRP + 'record store shack built from shipping containers with neon OPEN sign and crane arm'),
|
|
('pressplant', 64, MRP + 'industrial hydraulic vinyl record pressing plant building with steam vents and conveyor of records'),
|
|
('ampstack', 32, MRP + 'wall of stacked guitar amplifiers crackling with pink energy'),
|
|
('vault', 32, MRP + 'squat bunker with a huge round bank vault door and stacked record crates'),
|
|
('merchtable', 32, MRP + 'folding merch table under a small tent with t-shirts and records'),
|
|
('garage', 64, MRP + 'corrugated iron vehicle workshop with engine hoist and band posters'),
|
|
('mastering', 64, MRP + 'pristine acoustic-panelled recording studio grafted onto a junk tower with satellite dish'),
|
|
('digger', 24, MRP + 'forklift monster robot with crate claw carrying milk crates of vinyl records'),
|
|
('spinner', 20, MRP + 'small agile one-wheeled robot whose wheel is a spinning vinyl record with blade tonearms'),
|
|
('stacktank', 28, MRP + 'two-legged walker mech made of stacked speaker cabinets with subwoofer chest'),
|
|
('basscannon', 26, MRP + 'six-wheeled flatbed truck hauling one colossal subwoofer cannon with fold-out legs'),
|
|
('djgrunt', 16, MRP + 'small hooded robot with a portable turntable chest rig holding a razor-sharp record'),
|
|
('roadie', 16, MRP + 'four-armed robot with gaffer tape bandoliers and a flight case backpack'),
|
|
('streamvan', 24, STR + 'autonomous white delivery van with a glowing blue play-button logo'),
|
|
('streamhub', 64, STR + 'white monolithic server hub building with one huge blue status ring'),
|
|
('datacentre', 32, STR + 'humming white server monolith with a single blue LED strip'),
|
|
('transcoder', 64, STR + 'white cube building that swallows records on a conveyor, receipts coming out'),
|
|
('podfarm', 32, STR + 'vending machine wall dispensing small white sphere robots'),
|
|
('printfarm', 64, STR + 'white 3d-printer factory building with robotic arms assembling drones'),
|
|
('scraper', 22, STR + 'autonomous white street sweeper vehicle inhaling black vinyl through a glowing blue intake'),
|
|
('bufferbot', 14, STR + 'knee-high white sphere robot with a loading spinner face'),
|
|
('drmwalker', 28, STR + 'tall faceless white mech with one blue ring-light eye and a glowing paywall screen chest'),
|
|
]
|
|
|
|
|
|
def fit(im, w, h):
|
|
im = im.crop(im.getbbox() or (0, 0, im.width, im.height))
|
|
im.thumbnail((w, h), Image.LANCZOS)
|
|
out = Image.new('RGBA', (w, h), (0, 0, 0, 0))
|
|
out.paste(im, ((w - im.width) // 2, (h - im.height) // 2))
|
|
return out
|
|
|
|
|
|
log = open(os.path.join(ROOT, 'assets_src', 'PROMPTS.md'), 'a')
|
|
today = datetime.date.today().isoformat()
|
|
only = sys.argv[1:]
|
|
for name, size, prompt in ACTORS:
|
|
if only and name not in only:
|
|
continue
|
|
raw = os.path.join(RENDERS, f'{name}_raw.png')
|
|
cut = os.path.join(RENDERS, f'{name}.png')
|
|
if not os.path.exists(cut):
|
|
run('flux_local', {'prompt': prompt + SUF}, raw)
|
|
run('bg_remove_local', {}, cut, asset_id=upload(raw))
|
|
log.write(f'| renders/{name}.png | {today} | {prompt + SUF} |\n')
|
|
log.flush()
|
|
im = Image.open(cut).convert('RGBA')
|
|
fit(im, size, size).save(os.path.join(ASSETS, f'{name}.png'))
|
|
fit(im, 56, 40).resize((56, 40)).save(os.path.join(ASSETS, f'_icon_tmp.png'))
|
|
ic = Image.new('RGBA', (64, 48), (18, 16, 20, 255))
|
|
ic.paste(Image.open(os.path.join(ASSETS, '_icon_tmp.png')), (4, 4))
|
|
ic.save(os.path.join(ASSETS, f'icon_{name}.png'))
|
|
print(f'[sprite] {name} done', flush=True)
|
|
os.remove(os.path.join(ASSETS, '_icon_tmp.png'))
|
|
print('ALL DONE')
|