#!/usr/bin/env bash # TOASTSIM asset generation — 100% local, 100% free, via MODELBEAST on the M3 Ultra. # # ./scripts/gen-assets.sh 3d # product shot -> cutout -> GLB (slow: ~5 min each, gpu lane is serial) # ./scripts/gen-assets.sh 2d # flux images (judge portraits, backdrops, ui) # ./scripts/gen-assets.sh all # # Seeds are fixed so every asset is re-generatable. Style token is shared for coherence. set -uo pipefail MB_DIR="$HOME/Documents/MODELBEAST" REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" OUT_MODELS="$REPO/public/assets/models" OUT_IMG="$REPO/public/assets/img" mkdir -p "$OUT_MODELS" "$OUT_IMG" cd "$MB_DIR" || exit 1 # shellcheck disable=SC1091 source data/agent.env STYLE="chunky stylized claymation kitchen prop, soft matte plastic look, warm colors" SHOT="single object centered on plain light grey background, soft studio lighting, 3/4 view, chunky stylized game asset" # run_flux [w] [h] -> asset id on stdout run_flux() { local seed="$1" prompt="$2" w="${3:-1024}" h="${4:-1024}" ./mb run flux_local -p prompt="$prompt" -p model=flux2-klein-4b -p steps=4 \ -p seed="$seed" -p width="$w" -p height="$h" --wait 2>/dev/null \ | awk '/image/{print $1}' | tail -1 } # gen_3d gen_3d() { local name="$1" seed="$2" prompt="$3" if [ -f "$OUT_MODELS/$name.glb" ]; then echo " = $name.glb exists, skipping"; return; fi echo "→ [$name] flux..." local img cut img=$(run_flux "$seed" "$prompt, $STYLE, $SHOT") [ -z "$img" ] && { echo " ! $name: flux failed"; return 1; } echo "→ [$name] cutout ($img)..." cut=$(./mb run bg_remove_local --asset "$img" -p resolution=2048 --wait 2>/dev/null | awk '/image/{print $1}' | tail -1) [ -z "$cut" ] && { echo " ! $name: bg_remove failed"; return 1; } echo "→ [$name] mesh ($cut) — ~5 min..." local tmp; tmp=$(mktemp -d) ./mb run hunyuan3d_mlx --asset "$cut" -p octree_resolution=256 -p texture_size=1024 \ -p max_num_view=6 --wait --download "$tmp" >/dev/null 2>&1 local glb; glb=$(find "$tmp" -iname '*.glb' | head -1) if [ -n "$glb" ]; then mv "$glb" "$OUT_MODELS/$name.glb"; echo " ✓ $name.glb"; else echo " ! $name: no glb"; fi rm -rf "$tmp" } # gen_2d [w] [h] [cutout] gen_2d() { local name="$1" seed="$2" prompt="$3" w="${4:-1024}" h="${5:-1024}" cutout="${6:-no}" if [ -f "$OUT_IMG/$name.png" ]; then echo " = $name.png exists, skipping"; return; fi echo "→ [$name] flux..." local img; img=$(run_flux "$seed" "$prompt" "$w" "$h") [ -z "$img" ] && { echo " ! $name: flux failed"; return 1; } if [ "$cutout" = "cut" ]; then echo "→ [$name] cutout..." local c; c=$(./mb run bg_remove_local --asset "$img" -p resolution=2048 --wait 2>/dev/null | awk '/image/{print $1}' | tail -1) [ -n "$c" ] && img="$c" fi local tmp; tmp=$(mktemp -d) ./mb get "$img" -o "$tmp" >/dev/null 2>&1 local f; f=$(find "$tmp" -iname '*.png' -o -iname '*.jpg' | head -1) [ -n "$f" ] && { mv "$f" "$OUT_IMG/$name.png"; echo " ✓ $name.png"; } rm -rf "$tmp" } # ---- the judge: same person every time, seed fixed, only the expression phrase varies ---- JUDGE="portrait of a stern elderly australian food inspector, deep wrinkles, bushy grey eyebrows, thin wire glasses, grey moustache, wearing a brown cardigan over a shirt and tie, holding a clipboard, plain light grey background, head and shoulders, claymation stop-motion character, soft matte clay texture, warm studio light" do_3d() { gen_3d toaster 11 "a retro two-slot pop-up toaster, cream and chrome, with a browning dial" gen_3d butter_dish 23 "a ceramic butter dish with a thick block of yellow butter on it, lid off" gen_3d pb_jar 31 "an open glass jar of peanut butter, chunky brown spread visible, cream label" gen_3d mitey_jar 37 "a small squat glass jar of dark brown yeast extract spread with a bright red and yellow label reading MITEY, lid off" gen_3d plate 43 "a simple round white ceramic side plate, empty, seen at an angle" } do_2d() { gen_2d judge_neutral 101 "$JUDGE, neutral unimpressed expression" 768 768 cut gen_2d judge_intrigued 101 "$JUDGE, one eyebrow raised, mildly intrigued expression" 768 768 cut gen_2d judge_impressed 101 "$JUDGE, small approving smile, impressed expression" 768 768 cut gen_2d judge_disappointed 101 "$JUDGE, deeply disappointed frowning expression, looking down at clipboard" 768 768 cut gen_2d judge_horrified 101 "$JUDGE, horrified appalled expression, eyes wide, recoiling" 768 768 cut gen_2d bench_wood 202 "seamless top-down texture of a warm honey-toned wooden kitchen benchtop, subtle grain and knife marks, claymation soft matte look, flat even lighting, no objects" 1024 1024 gen_2d kitchen_backdrop 205 "blurry cosy 1970s australian kitchen interior background, warm cream tiles, soft bokeh, claymation set, no people, no text" 1280 768 gen_2d ticket_paper 210 "seamless texture of a plain cream paper order docket, faint blue lines, slightly crumpled, flat even lighting, no text" 512 512 gen_2d title_art 220 "a single slice of golden toast with a pat of butter melting on it, dramatic hero shot, claymation stop-motion, soft matte clay, warm rim light, plain dark background" 1024 768 cut } case "${1:-all}" in 3d) do_3d ;; 2d) do_2d ;; *) do_3d; do_2d ;; esac echo "done."