This commit is contained in:
David Rice
2026-04-16 15:46:56 +01:00
parent 8c8d9a6d47
commit a755e12ea3
2 changed files with 20 additions and 35 deletions

View File

@@ -20,7 +20,6 @@ Framebuffer prerequisites on Debian (device):
""" """
import os import os
import sys
import math import math
import argparse import argparse
from datetime import datetime from datetime import datetime
@@ -215,15 +214,15 @@ def main() -> None:
pygame.display.set_caption('JARVIS') pygame.display.set_caption('JARVIS')
fonts = { fonts = {
'large': _load_font(76), 'large': _load_font(56),
'small': _load_font(24), 'small': _load_font(18),
} }
# Layout anchors — all relative to screen size so they adapt to resolution # Layout anchors — all relative to screen size so they adapt to resolution
face_rx = int(min(W, H) * 0.115) # half-width at cheek level
face_ry = int(min(W, H) * 0.135) # half-height crown→chin
face_cx = W // 2 face_cx = W // 2
face_cy = int(H * 0.31) face_cy = int(face_ry * 1.2) + 8 # nearly touches the top edge
face_rx = int(min(W, H) * 0.150) # half-width at cheek level
face_ry = int(min(W, H) * 0.175) # half-height crown→chin
status_y = int(H * 0.82) status_y = int(H * 0.82)
dot_y = int(H * 0.895) dot_y = int(H * 0.895)
@@ -269,7 +268,8 @@ def main() -> None:
special_flags=pygame.BLEND_ADD) special_flags=pygame.BLEND_ADD)
else: else:
draw_wireframe_face(screen, face_cx, face_cy, face_rx, face_ry) draw_wireframe_face(screen, face_cx, face_cy, face_rx, face_ry)
draw_clock(screen, fonts, 20, 14) clock_block_h = fonts['small'].get_height() + 4 + fonts['large'].get_height()
draw_clock(screen, fonts, 20, face_cy - clock_block_h // 2)
draw_status(screen, fonts, status_text, W // 2, status_y) draw_status(screen, fonts, status_text, W // 2, status_y)
draw_dot(screen, W // 2, dot_y) draw_dot(screen, W // 2, dot_y)

View File

@@ -1,17 +1,13 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
gen_face_ai.py Generate assets/face_wire.png using Gemini image generation gen_face_ai.py Generate assets/face_wire.png via Gemini image generation
──────────────────────────────────────────────────────────────────────────────── ──────────────────────────────────────────────────────────────────────────────
Uses the Gemini / Google Gen AI SDK with a native image-generation model Run once to produce the face PNG that display.py loads at startup.
(gemini-2.5-flash-image or similar) to produce a wireframe face PNG ready for
display.py to load.
pip install google-genai pillow pip install google-genai pillow
python3 gen_face_ai.py python3 gen_face_ai.py
Or set the key in the environment: Pass --list-models to see what image-capable models your key can reach.
export GEMINI_API_KEY=YOUR_KEY
python3 gen_face_ai.py
""" """
import os import os
@@ -19,16 +15,15 @@ import sys
import io import io
import argparse import argparse
# ── Paste your Gemini API key here ──────────────────────────────────────────── # ── API key ───────────────────────────────────────────────────────────────────
API_KEY = 'AQ.Ab8RN6LuGwkGiKPa61jsLAEYEpJp1Yl2EkZuBWTbN9AMKxgTSw' API_KEY = 'AQ.Ab8RN6LuGwkGiKPa61jsLAEYEpJp1Yl2EkZuBWTbN9AMKxgTSw'
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
# ── CLI ───────────────────────────────────────────────────────────────────────
ap = argparse.ArgumentParser() ap = argparse.ArgumentParser()
ap.add_argument('--key', default='', help='Override the hardcoded API key') ap.add_argument('--key', default='', help='Override the hardcoded API key')
ap.add_argument('--out', default='assets/face_wire.png') ap.add_argument('--out', default='assets/face_wire.png')
ap.add_argument('--model', default='gemini-2.5-flash-image', ap.add_argument('--model', default='gemini-2.5-flash-image',
help='Gemini image model to use (default: gemini-2.5-flash-image)') help='Gemini image model to use')
ap.add_argument('--list-models', action='store_true', ap.add_argument('--list-models', action='store_true',
help='Print models that support generateContent then exit') help='Print models that support generateContent then exit')
args = ap.parse_args() args = ap.parse_args()
@@ -37,7 +32,6 @@ api_key = args.key or API_KEY or os.environ.get('GEMINI_API_KEY', '')
if not api_key: if not api_key:
sys.exit('ERROR: paste your key into API_KEY at the top of this file') sys.exit('ERROR: paste your key into API_KEY at the top of this file')
# ── Install check ─────────────────────────────────────────────────────────────
try: try:
from google import genai from google import genai
from google.genai import types from google.genai import types
@@ -49,7 +43,6 @@ try:
except ImportError: except ImportError:
sys.exit('Run: pip install pillow then try again.') sys.exit('Run: pip install pillow then try again.')
# ── Connect ───────────────────────────────────────────────────────────────────
print('Connecting to Google GenAI …') print('Connecting to Google GenAI …')
client = genai.Client(api_key=api_key) client = genai.Client(api_key=api_key)
@@ -61,7 +54,6 @@ if args.list_models:
print(f' {m.name}') print(f' {m.name}')
sys.exit(0) sys.exit(0)
# ── Prompt ────────────────────────────────────────────────────────────────────
PROMPT = ( PROMPT = (
"3D wireframe polygon mesh of a human head and face, viewed from slightly " "3D wireframe polygon mesh of a human head and face, viewed from slightly "
"below, front-facing, neutral expression, pure black background, thin " "below, front-facing, neutral expression, pure black background, thin "
@@ -70,7 +62,6 @@ PROMPT = (
"style, high contrast monochrome" "style, high contrast monochrome"
) )
# ── Generate ──────────────────────────────────────────────────────────────────
print(f'Generating with {args.model}') print(f'Generating with {args.model}')
response = client.models.generate_content( response = client.models.generate_content(
model = args.model, model = args.model,
@@ -80,7 +71,6 @@ response = client.models.generate_content(
), ),
) )
# ── Extract image bytes ───────────────────────────────────────────────────────
img_bytes = None img_bytes = None
for part in response.candidates[0].content.parts: for part in response.candidates[0].content.parts:
if part.inline_data and part.inline_data.mime_type.startswith('image/'): if part.inline_data and part.inline_data.mime_type.startswith('image/'):
@@ -88,17 +78,12 @@ for part in response.candidates[0].content.parts:
break break
if img_bytes is None: if img_bytes is None:
# Print any text the model returned to help debug
for part in response.candidates[0].content.parts: for part in response.candidates[0].content.parts:
if hasattr(part, 'text') and part.text: if hasattr(part, 'text') and part.text:
print('Model text response:', part.text[:400]) print('Model said:', part.text[:400])
sys.exit('No image in response try a different --model') sys.exit('No image in response try a different --model')
# ── Save as PNG ─────────────────────────────────────────────────────────────── os.makedirs(os.path.dirname(args.out) if os.path.dirname(args.out) else '.', exist_ok=True)
os.makedirs(os.path.dirname(args.out) if os.path.dirname(args.out) else '.',
exist_ok=True)
# Convert whatever format came back to a proper PNG
img = Image.open(io.BytesIO(img_bytes)) img = Image.open(io.BytesIO(img_bytes))
img.save(args.out, 'PNG') img.save(args.out, 'PNG')