#!/usr/bin/env python3
"""Generate the RuView README visual system. Deterministic, no network."""
from pathlib import Path
from html import escape
import math,json,re
from readme_visuals_xml import parse_svg
ROOT=Path(__file__).resolve().parents[1];OUT=ROOT/'assets/readme';OUT.mkdir(parents=True,exist_ok=True)
C='#78cfff';BLUE='#438fff';INK='#071124';WHITE='#f3f7ff';MUTED='#b0c2db'
LIME='#b8f36c';AMBER='#ffb45e'
def text(x,y,value,size=16,color=MUTED,anchor='start',extra=''):
weight='font-weight="650" letter-spacing="-0.6"' if size>=25 else 'font-weight="500"'
return f'{escape(value)}'
def path(d,color=C,width=1.5,extra=''):
return f''
def circle(x,y,r,color=C,extra=''):
fill = '' if 'fill=' in extra else 'fill="none"'
return f''
def icon(kind,x,y,scale=1):
s=''
if kind in ['signal','start','presence']:
for r in [18,32,46]:s+=f''
s+=path('M-46 0H46M0 -23V23',BLUE,.7)
s+=''+path('M0 0L40 -18',LIME,2)+circle(34,-15,3,LIME,extra=f'fill="{LIME}"')+''
for px,py in [(-20,8),(12,13),(23,-8)]:s+=circle(px,py,3,AMBER,extra=f'fill="{AMBER}" class="spark"')
elif kind=='pose':
# Authored anatomical mesh silhouette; a conceptual figure, not captured pose data.
s+=''
s+=''
# Far arm and leg provide occlusion and depth.
s+=''
s+=''
s+=''
# Near arm: shoulder, upper arm, elbow, forearm and closed hand.
s+=''
# Shoulder and chest taper into abdomen and pelvis.
s+=''
# Three-quarter head with jaw, nose and ear contour.
s+=''
s+=''
# Surface contours make volume legible at README scale.
s+=path('M-9 -23Q-2 -18 10 -23M-7 -15Q0 -11 9 -16M-6 -8Q1 -5 8 -9M-7 1Q1 5 9 1M0 -26L2 9M-4 -40Q0 -42 7 -40M2 -32L6 -34',LIME,.55,extra='opacity=".65"')
s+=path('M-5 -45Q1 -49 6 -46M-10 -22L-6 -8',C,.7,extra='opacity=".8"')
s+=''
# Joint markers remain attached to the anatomical surface.
for px,py in [(-9,-24),(10,-23),(1,-12),(2,9)]:
s+=circle(px,py,3.5,AMBER,extra='stroke-opacity=".25"')+circle(px,py,1.5,AMBER,extra=f'fill="{AMBER}" class="spark"')
s+=''
s+=path('M-42 -42V-53H-30M30 -53H42V-42M42 45V55H30M-30 55H-42V45',BLUE,.8)
elif kind in ['vitals','models']:
for j in range(3):
s+=path(f'M-49 {j*12-14}C-34 {j*12-14} -30 {j*12-35} -17 {j*12-35}S0 {j*12+7} 14 {j*12+7}S34 {j*12-14} 49 {j*12-14}',C if j!=1 else LIME,1.5,extra=f'class="trace" style="animation-delay:-{j}s" opacity="{1-j*.2}"')
s+=''+path('M0 -42V40',AMBER,1)+circle(0,-8,3,AMBER,extra=f'fill="{AMBER}"')+''
elif kind in ['edge','hardware','seed','evidence']:
for j in range(3):
yy=j*13
s+=f''
s+=path('M-12 -31L0 -37L12 -31L0 -25Z',LIME,2)
for px in [-26,0,26]:s+=path(f'M{px} 22V39',AMBER,1,extra='class="flow"')
elif kind in ['home','automation']:
s+=''
s+=path('M-32 17V-16L0 -35L32 -16V17M-32 -16L0 3L32 -16M0 3V34',C,1.5)
s+=path('M-22 20L-10 13L12 25M-10 13V-5',LIME,1.5,extra='class="flow"')
for px,py in [(-22,20),(-10,-5),(12,25)]:s+=circle(px,py,4,AMBER,extra=f'fill="#102749" class="breathe"')
elif kind in ['agent','agents']:
for px,py in [(0,-36),(-38,-10),(-23,31),(30,28),(39,-17)]:
s+=path(f'M0 0L{px} {py}',BLUE,1,extra='class="flow"')
s+=f''
s+=circle(0,0,15,LIME)+path('M-6 0L-1 5L8 -6',LIME,2)
else:s+=path('M-35 0L0 -25L35 0L0 25Z',C,2)
base=''
if kind=='pose':
base=''
return f'{base}{s}'
return f'{base}{s}'
def spatial(x,y,scale=1):
"""Projected geometry gives depth without scripts or CSS 3D dependencies."""
s=''
s+=''
s+=''
for px,py in [(-40,6),(0,-15),(40,6),(-20,30),(20,30),(0,49)]:
s+=circle(px,py,2,BLUE,extra='fill="#70b6ff" class="spark"')
s+=''
s+=''
return f'{s}'
def _frame(w,h,title,desc,body):
return f''''''
MOTION=''''''
def frame(w,h,title,desc,body):
value=_frame(w,h,title,desc,body)
motion=MOTION
for name in ['float','flow','breathe','trace','scan','orbit','spark','wave','sweep','limb','stride','layer','cursor','humanTurn','humanBody','armFar','armNear','legFar','legNear','humanLight','gaitShadow']:
if f'class="{name}"' not in body:
motion=re.sub(r'\.'+name+r'\{[^}]*\}', '', motion)
for name in ['float','flow','breathe','trace','scan','spark','wave','sweep','limb','layer','cursor','humanTurn','humanBody','armSwing','legSwing','humanLight','gaitShadow']:
if f'animation:{name} ' not in motion:
marker='@keyframes '+name+'{'; start=motion.find(marker)
if start>=0:
pos=start+len(marker); depth=1
while depth:
depth+=(motion[pos]=='{')-(motion[pos]=='}');pos+=1
motion=motion[:start]+motion[pos:]
value=value[:value.index('')+8:]
palette={'#0b303a':'#102749','#06171e':'#071429','#030c13':'#03091a',
'#245360':'#2d4e77','#91f6df':'#9ee7ff','#26bad7':'#348bff',
'#35d4db':'#348bff','#72eee0':C,'#70b6ff':BLUE,'#041117':INK,
'#133a45':'#142c50','#285c68':'#2e527d','#0a2733':'#0b1b35',
'#286171':'#2e5b8e','#366d80':'#4473a5','#5fe3d8':'#438fff',
'#0a2630':'#0c2242','#12343d':'#162f50','#4b9ba4':'#5490c5',
'#071b23':'#091b35','#285360':'#315685','#214451':'#233f63','#3c8897':'#437cb2'}
for old,new in palette.items():value=value.replace(old,new)
rim=f''
return value.replace('',rim+'')
manifest=[]
def save(name,w,h,title,desc,body,target):
name+='-spatial-v6' if name in ['hero','feature-pose'] else '-spatial-v4'
s=frame(w,h,title,desc,body);raw=(s+'\n').encode('utf-8');parse_svg(raw);(OUT/(name+'.svg')).write_bytes(raw);manifest.append({'file':'assets/readme/'+name+'.svg','title':title,'target':target,'width':w,'height':h})
# Cinematic RF stage: authored projected planes, not a live measurement.
s=text(48,45,'RUVIEW / SPATIAL INTELLIGENCE',12,C,extra='class="mono"')
s+=text(44,140,'Invisible signals.',59,WHITE)+text(44,204,'Visible possibility.',55,LIME)
s+=text(48,256,'Explore the space between sensing and understanding.',18)
s+=text(48,284,'WiFi sensing. Edge models. Local automation.',18)
s+=''+text(72,358,'Enter the field',18,INK)+path('M246 351H266M259 344L266 351L259 358',INK,2)
s+=text(49,418,'RF SENSING / OPEN RESEARCH / EDGE COMPUTE',10,MUTED,extra='class="mono"')
s+=''
# Ground perspective and an exploded room volume.
for i in range(7):s+=path(f'M{785+i*24} 270L{648+i*91} 432','#1b3558',.8)
for j in range(5):s+=path(f'M{768-j*27} {286+j*31}H{1110+j*16}','#1b3558',.8)
s+=''
s+=''
s+=''
s+=''
for i in range(3):s+=f''
s+=icon('pose',959,236,1.8)+icon('hardware',771,324,.72)
for j in range(3):s+=path(f'M795 {309+j*8}Q854 {245+j*14} 939 {223+j*17}',C,1.2,extra=f'class="flow" style="animation-delay:-{j}s"')
for px,py in [(780,153),(963,70),(1148,162),(1148,303),(960,400)]:s+=circle(px,py,3,AMBER,extra=f'fill="{AMBER}" class="spark"')
s+=text(1148,419,'CONCEPTUAL RF FIELD',10,MUTED,'end',extra='class="mono"')
save('hero',1200,450,'RuView: invisible signals, visible possibility','Animated conceptual RF room with an anatomical human silhouette, moving scan plane and propagating waves. Illustration only, not a sensor measurement.',s,'docs/user-guide.md')
# Visual navigation icons, each receives a real outer link in Markdown.
nav=[('start','Start here','docs/user-guide.md'),('hardware','Hardware','firmware/esp32-csi-node/README.md'),('models','Models','https://huggingface.co/ruvnet/wifi-densepose-pretrained'),('agents','AI toolkit','harness/ruview/README.md'),('home','Smart home','docs/integrations/home-assistant.md'),('seed','Cognitum','https://cognitum.one/seed')]
for k,label,target in nav:save('nav-'+k,180,116,label,'Open '+label+'. Animated navigation icon.',icon(k,90,45,.65)+text(90,98,label,16,WHITE,'middle'),target)
# Capability cards. No unverified performance claims.
cards=[('presence','01','Presence & motion','Explore changes in room occupancy.','Inspect motion features from CSI.','SENSING GUIDE','docs/user-guide.md'),('vitals','02','Breathing & vital signals','Inspect phase-derived signal trends.','Validate estimates on your hardware.','SIGNAL PIPELINE','v2/crates/wifi-densepose-signal/README.md'),('pose','03','Pose research','Train and compare body keypoints.','Live accuracy differs from benchmarks.','READ THE EVIDENCE','docs/benchmarks/pose-estimation-cog.md'),('edge','04','Edge sensor mesh','Provision, capture and calibrate.','Explore ESP32 firmware and runbooks.','HARDWARE GUIDE','firmware/esp32-csi-node/README.md'),('automation','05','Local automation','Connect rooms to your smart home.','Home Assistant, MQTT and Matter.','INTEGRATION GUIDE','docs/integrations/home-assistant.md'),('agents','06','A guided AI operator','Source-cited setup and verification.','CLI, MCP and agent workflows.','TOOLKIT GUIDE','harness/ruview/README.md')]
for k,num,title,l1,l2,cta,target in cards:
s=text(30,39,'RUVIEW / '+num,11,C,extra='class="mono"')+text(30,89,title,25,WHITE)+text(30,126,l1,14)+text(30,151,l2,14)
s+=''+icon(k,480,144,1.12)+path('M30 204H550','#245360',1)+path('M30 204H550',C,1,extra='class="flow" opacity=".5"')
s+=text(30,245,cta,12,C,extra='class="mono"')+path('M510 239H540M532 231L540 239L532 247',C,1.5)
save('feature-'+k,580,276,title,l1+' '+l2+' Conceptual animation; follow the linked source for capability limits.',s,target)
# Overview uses three clearly labelled stages rather than fabricated telemetry.
s=text(36,44,'SIGNAL TO CONTEXT',12,C,extra='class="mono"')+text(36,89,'A sensing loop you can inspect.',34,WHITE)
for i,(label,sub,kind,tail) in enumerate([('Capture RF','CSI from supported hardware','signal','Provision and calibrate'),('Extract context','Signal processing and models','models','Compare against real data'),('Act locally','Events, APIs and integrations','automation','Apply rules and review outputs')]):
x=36+i*396;s+=f''
s+=text(x+24,153,f'0{i+1}',12,C)+text(x+24,188,label,26,WHITE)+text(x+24,216,sub,15)+spatial(x+269,278,.35)+icon(kind,x+269,270,.5)+text(x+24,311,tail,13)
if i<2:s+=path(f'M{x+344} 229H{x+384}M{x+376} 221L{x+384} 229L{x+376} 237',C,2,extra='class="flow"')
s+=text(36,373,'CONCEPTUAL PIPELINE / HARDWARE AND ENVIRONMENT VALIDATION MATTER',11,MUTED,extra='class="mono"')
save('overview',1200,396,'RuView sensing overview','Conceptual sequence: capture RF, extract context, act locally. Links to architecture and user documentation.',s,'docs/user-guide.md')
# Chapter headings act as visual landmarks, all remain links.
headers=[('features','Explore the capabilities','SENSING / MODELS / AUTOMATION','presence','docs/user-guide.md'),('edge','Intelligence at the edge','SENSORS / FIRMWARE / MODULES','edge','firmware/esp32-csi-node/README.md'),('learning','From recordings to models','CAPTURE / TRAIN / EVALUATE','models','docs/user-guide.md'),('evidence','Evidence before accuracy claims','BENCHMARKS / REPRODUCERS / LIMITS','evidence','docs/benchmarks/pose-estimation-cog.md'),('docs','Choose your next step','GUIDES / HARDWARE / INTEGRATIONS','agents','docs/user-guide.md')]
for k,title,sub,kind,target in headers:
s=icon(kind,76,75,.75)+text(140,63,title,34,WHITE)+text(142,99,sub,12,C,extra='class="mono"')
s+=spatial(1012,84,.55)
s+=path('M1140 70H1160M1152 62L1160 70L1152 78',C,2)
save('header-'+k,1200,148,title,'Animated section heading. '+sub,s,target)
# Brand destination; original ruOS promotion remains in the README.
s=text(44,42,'COGNITUM ONE',12,C,extra='class="mono"')+text(44,99,'Ambient Intelligence.',42,WHITE)+text(44,147,'At the edge of the physical world.',25,C)+text(44,190,'Explore the platform behind the ecosystem.',16)
s+=''+text(65,254,'Explore Cognitum',18,INK)+path('M263 247H284M277 240L284 247L277 254',INK,2)
s+=''+spatial(945,173,1.55)+icon('seed',945,145,1.65)
for j in range(3):s+=f''
save('cognitum',1200,304,'Explore Cognitum One','Ambient Intelligence at the edge of the physical world. Open cognitum.one.',s,'https://cognitum.one')
(OUT/'manifest.json').write_bytes((json.dumps({'schema_version':1,'style':'RuView spatial v4: navy glass, electric blue, lime and amber','motion':'Decorative CSS; prefers-reduced-motion supported; no script or remote dependencies','assets':manifest},indent=2)+'\n').encode('utf-8'))
print(f'Generated {len(manifest)} SVGs, {sum((ROOT/x["file"]).stat().st_size for x in manifest):,} bytes')