this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add colors

+31 -7
+6
README.md
··· 1 + # Overview 2 + 1 3 Renders a rotating subjective Necker cube and dynamically updates a BlueSky avatar based on the current time. 4 + 5 + # Usage 6 + 7 + ```uv run avatar_cube.py``` 2 8 3 9 Samples: 4 10
+25 -7
avatar_cube.py
··· 1 1 from __future__ import annotations 2 2 from dotenv import load_dotenv 3 - import os, io, math, datetime as dt 3 + import os, io, math, datetime as dt, colorsys 4 4 import numpy as np 5 5 import matplotlib.pyplot as plt 6 6 from matplotlib.patches import Circle ··· 36 36 CUBE_RADIUS_PX = math.sqrt(3) * (ZOOM / 2) 37 37 HALF_FRAME = CUBE_RADIUS_PX + CIRCLE_R + 5 38 38 39 - def render_frame(roll_deg: float, pitch_deg: float, yaw_deg: float) -> bytes: 39 + def render_frame(roll_deg: float, pitch_deg: float, yaw_deg: float, fg_color, bg_color) -> bytes: 40 40 """Return PNG bytes of cube at given pitch (deg).""" 41 41 # Matplotlib figure — square, no border 42 - fig = plt.figure(figsize=(2, 2), dpi=256, facecolor="white") 42 + fig = plt.figure(figsize=(2, 2), dpi=256, facecolor=bg_color) 43 43 ax = fig.add_axes([0, 0, 1, 1]) # full‑bleed axes 44 + ax.set_facecolor(bg_color) 44 45 ax.set_aspect('equal', adjustable='box') 45 46 ax.axis('off') 46 47 ··· 50 51 51 52 # draw circles 52 53 for x, y in verts2d: 53 - ax.add_patch(Circle((x, y), CIRCLE_R, color='black', zorder=1)) 54 + ax.add_patch(Circle((x, y), CIRCLE_R, color=fg_color, zorder=1)) 54 55 55 56 # draw full edges (round caps) 56 57 for i, j in EDGES: 57 58 (x1, y1), (x2, y2) = verts2d[[i, j]] 58 59 ax.plot([x1, x2], [y1, y2], lw=LINE_W, 59 - color='white', solid_capstyle='round', zorder=2) 60 + color=bg_color, solid_capstyle='round', zorder=2) 60 61 61 62 # fixed limits to keep size constant 62 63 ax.set_xlim(-HALF_FRAME, HALF_FRAME) ··· 71 72 def update_bluesky_avatar(now_utc: dt.datetime | None = None, dry_run=False): 72 73 handle = os.getenv('BLUESKY_HANDLE') 73 74 app_pw = os.getenv('BLUESKY_APP_PASSWORD') 74 - if not handle or not app_pw: 75 + if not dry_run and (not handle or not app_pw): 75 76 raise RuntimeError('BLUESKY_HANDLE and BLUESKY_APP_PASSWORD must be set') 76 77 77 78 now = now_utc or dt.datetime.now(dt.timezone.utc) ··· 80 81 deg_roll = (steps * 7) % 360 # wrap at 360 81 82 deg_pitch = (steps * 3) % 360 82 83 deg_yaw = (steps * 5) % 360 83 - png = render_frame(deg_roll, deg_pitch, deg_yaw) 84 + 85 + # color calculations 86 + osc = math.sin(2 * math.pi * steps / 48) 87 + fg_h = (steps * 13) % 360 88 + fg_l = 0.25 * (1 + osc) / 2 89 + fg_s = 0.90 90 + bg_h = (fg_h + 180) % 360 91 + bg_l = 0.93 + 0.05 * osc 92 + bg_s = 0.30 93 + 94 + def hsl_to_rgb(h_deg, s, l): 95 + r, g, b = colorsys.hls_to_rgb(h_deg / 360, l, s) 96 + return (r, g, b) 97 + 98 + fg_color = hsl_to_rgb(fg_h, fg_s, fg_l) 99 + bg_color = hsl_to_rgb(bg_h, bg_s, bg_l) 100 + 101 + png = render_frame(deg_roll, deg_pitch, deg_yaw, fg_color, bg_color) 84 102 85 103 if dry_run: 86 104 with open(f'cube-{steps:03d}.png', 'wb') as f: