#!/usr/bin/env python3
# 纯标准库生成表盘运行效果 SVG(复用 Fenix8V3View.mc 的坐标与主题色)
import math
THEMES = {
"01":{"name":"Ember","ringTop":[[8,0xFFF203],[15,0xFDDB0D],[25,0xFFC203],[35,0xFFA00A],[45,0xFF8705],[55,0xFF610C],[65,0xFF4904],[75,0xFF2602],[86,0xFF2604]],
"ringBottom":[[7,0xAE1706],[14,0xB32504],[21,0xC03B03],[28,0xCD6206],[35,0xD57805],[42,0xDE9208],[47,0xC98D00]],
"tickOff":0x545655,"anchorDot":0x525551,"bandFill":0xFEAA00,"bandText":0x000002,"accent":0xFCA901,"textPrimary":0xFAFAFA,"colon":0x525551,"hours":0xFBFBFB,"minutes":0xFCA901},
"02":{"name":"Aurora","ringTop":[[8,0xA5FA11],[15,0x9EFA23],[25,0x97F84B],[35,0x8CF965],[45,0x89F87F],[55,0x74FB9F],[65,0x6DFCC6],[75,0x64FCDF],[86,0x57FDFF]],
"ringBottom":[[7,0x55FDFF],[14,0x64FCDF],[21,0x6FFCC6],[28,0x74FBAE],[35,0x7EFA85],[42,0x88F96D],[47,0x81E654]],
"tickOff":0x545655,"anchorDot":0x0F3C82,"bandFill":0x525551,"bandText":0xFFFFFF,"accent":0xABA9AC,"textPrimary":0xFAFAFA,"colon":0x525551,"hours":0xFBFBFB,"minutes":0xAAF701},
"03":{"name":"Brass","ringTop":[[8,0xB4814F],[15,0xB88954],[25,0xC5975C],[35,0xC59E65],[45,0xCBA775],[55,0xD7AE7F],[65,0xDEBC84],[75,0xE7C48D],[86,0xF0D097]],
"ringBottom":[[7,0xB97E4A],[14,0xB68954],[21,0xBD925E],[28,0xC69B67],[35,0xCEA26C],[42,0xD9AB74],[47,0xC59E70]],
"tickOff":0x3A2815,"anchorDot":0x62441E,"bandFill":0x392810,"bandText":0xFFFFFF,"accent":0xB37C4A,"textPrimary":0xEBCB93,"colon":0x525551,"hours":0xFBFBFB,"minutes":0xECCC93},
"04":{"name":"Voltage","ringTop":[[8,0x5CAFF0],[15,0x6EB6E0],[25,0x7FBEC5],[35,0x96CA9D],[45,0xAAD87F],[55,0xBFDF66],[65,0xD6EA40],[75,0xE8F227],[86,0xFFFB0A]],
"ringBottom":[[7,0xF7FA11],[14,0xE9F225],[21,0xD6EB3F],[28,0xC0DE65],[35,0xADD880],[42,0xA0CF98],[47,0x88B98E]],
"tickOff":0x545655,"anchorDot":0x53AAFF,"bandFill":0x53AAFF,"bandText":0x000000,"accent":0xABA9AC,"textPrimary":0x51A7FA,"colon":0x525551,"hours":0xFBFBFB,"minutes":0x52A8FD},
"05":{"name":"Reef","ringTop":[[8,0xF87E0D],[15,0xE78E25],[25,0xCE9B4D],[35,0xBDAC65],[45,0xA5BE88],[55,0x95CA9E],[65,0x7EDEC7],[75,0x6DEBE1],[86,0x57FDFF]],
"ringBottom":[[7,0xFF7905],[14,0xE78B29],[21,0xD59C3E],[28,0xBEAC63],[35,0xADB77F],[42,0xA0C796],[47,0x88B98E]],
"tickOff":0x545655,"anchorDot":0xFE7900,"bandFill":0x52FDFF,"bandText":0x000000,"accent":0xFCA901,"textPrimary":0xFAFAFA,"colon":0x525551,"hours":0xFBFBFB,"minutes":0xFC7801},
"06":{"name":"Acid","ringTop":[[8,0x9CCC14],[15,0xA9CE0C],[25,0xA4D610],[35,0xACDB08],[45,0xB4E107],[55,0xB5E603],[65,0xBFEE04],[75,0xBDF108],[86,0xC7F808]],
"ringBottom":[[7,0xC7FA02],[14,0xBEF20A],[21,0xC0ED04],[28,0xB4E905],[35,0xB4E107],[42,0xADDD0C],[47,0x9BC603]],
"tickOff":0x545655,"anchorDot":0xF58909,"bandFill":0x525551,"bandText":0x000000,"accent":0xC4F801,"textPrimary":0xFAFAFA,"colon":0x525551,"hours":0xDBF731,"minutes":0xC4F900},
"07":{"name":"Kelp","ringTop":[[8,0xEF8917],[15,0xD8901C],[25,0xBD9A34],[35,0xAD9E3C],[45,0x96A54E],[55,0x7DAE5D],[65,0x61B474],[75,0x4EBB84],[86,0x36C294]],
"ringBottom":[[7,0xCE520F],[14,0xB1631D],[21,0xA26A2F],[28,0x9D763E],[35,0x85874B],[42,0x76915D],[47,0x5E8651]],
"tickOff":0x545655,"anchorDot":0xF58909,"bandFill":0x525551,"bandText":0x000000,"accent":0x31C192,"textPrimary":0xFAFAFA,"colon":0x525551,"hours":0xF28708,"minutes":0xF4880A},
}
ORDER=["01","02","03","04","05","06","07"]
TOP=[a for a in range(2,87,4)]; BOT=[a for a in range(6,67,3)]
DOW=["SUN","MON","TUE","WED","THU","FRI","SAT"]; MON=["JAN","FEB","MAR","APR","MAY","JUN","JUL","AUG","SEP","OCT","NOV","DEC"]
def hx(c): return "#%06x"%(c&0xffffff)
def lerp(c1,c2,f):
r1=(c1>>16)&255;g1=(c1>>8)&255;b1=c1&255
r2=(c2>>16)&255;g2=(c2>>8)&255;b2=c2&255
return ((round(r1+(r2-r1)*f))<<16)|((round(g1+(g2-g1)*f))<<8)|round(b1+(b2-b1)*f)
def lerp_stops(stops,t):
n=len(stops)
if not n: return 0x888888
if t<=stops[0][0]: return stops[0][1]
if t>=stops[-1][0]: return stops[-1][1]
for i in range(n-1):
a,b=stops[i],stops[i+1]
if a[0]<=t<=b[0]:
return lerp(a[1],b[1],(t-a[0])/(b[0]-a[0]))
return stops[-1][1]
def tick(cx,cy,rIn,rOut,w,ang,col,mirror):
eff=-ang if mirror else ang; rad=math.radians(eff); s=math.sin(rad); c=math.cos(rad)
return f''
def ring(theme,pTR,pTL,pBR,pBL):
out=[]
rOut=249.5;rIn=231.5;off=theme["tickOff"];ts=theme["ringTop"];bs=theme["ringBottom"]
topW=13;botW=9
litTR=round(pTR*22);litTL=round(pTL*22);litBR=round(pBR*21);litBL=round(pBL*21)
# TR anchorOuter, mirror=false
for i,a in enumerate(TOP):
on=i>=(22-litTR); col=lerp_stops(ts,a) if on else off
out.append(tick(250,250,rIn,rOut,topW,a,col,False))
# TL anchorOuter, mirror=true
for i,a in enumerate(TOP):
on=i>=(22-litTL); col=lerp_stops(ts,a) if on else off
out.append(tick(250,250,rIn,rOut,topW,a,col,True))
# BL mirror=true
for i,a in enumerate(BOT):
col=lerp_stops(bs,a) if i')
return "".join(out)
def batt(theme,pct):
y=53.5;gw=30;gh=16;gx=250-52;gy=y-gh/2;ac=theme["accent"];tp=theme["textPrimary"]
fill_w=(gw-4)*pct/100
return (f''
f''
f''
f'{round(pct)}%')
def complic_icon(x,y,r,col,t):
c=hx(col)
if t==0: # sun
s=f''
for i in range(8):
rad=math.radians(i*45)
s+=f''
return s
if t==1: # flame
return f''
if t==2: # steps
return (f''
f'')
if t==3: # dot
return f''
if t==4: # chevron
return f''
return f''
def top_comp(theme,t,isLeft,val):
sx=156 if isLeft else 343; iy=90; vy=130.5
return (f'{val}'
+complic_icon(sx,iy,13,theme["accent"],t))
def bluetooth(x,y,r,col):
c=hx(col)
return (f'')
def date_band(theme,gi):
by=286.5;bh=44;yc=308.5;bf=theme["bandFill"];bt=theme["bandText"]
dow=DOW[gi["dow"]-1]; md=MON[gi["m"]-1]+" "+str(gi["day"])
return (f''
+bluetooth(36.5,yc,10,bt)
+f'{dow}'
+f'{md}'
+f'{"PM" if gi["h"]>=12 else "AM"}')
def time(theme,gi):
h=gi["h"]; h=12 if h==0 else (h-12 if h>12 else h)
hs=str(h); ms="%02d"%gi["min"]; ty=220
return (f'{hs}'
f'{ms}'
f''
f'')
def bottom(theme,dist,steps):
vy=366;ly=410.5;lx=234;rx=266;tp=theme["textPrimary"];ac=theme["accent"]
return (f'{dist}'
f'DST'
f'{steps}'
f'STEP')
def face(theme_id,m,battery,temp,gi):
th=THEMES[theme_id]
p=[m[0],m[1],m[2],m[3],m[4]]
idxTR,idxTL,idxBR,idxBL=0,1,4,3
s=[]
s.append(f'')
s.append(ring(th,p[idxTR],p[idxTL],p[idxBR],p[idxBL]))
s.append(batt(th,battery))
s.append(top_comp(th,1,True,f'{round(m[1]*2000):,}')) # 左上 卡路里
s.append(top_comp(th,0,False,f'{temp}\u00b0')) # 右上 天气
s.append(time(th,gi))
s.append(date_band(th,gi))
s.append(bottom(th,f'{m[2]*10:.1f}',f'{round(m[0]*10000):,}'))
return "".join(s)
# 七主题合成图:单排行,每格 230px,圆形裁切
m=[0.84,0.64,0.65,0.80,0.40]; battery=78; temp=23
gi={"h":10,"min":8,"day":9,"m":9,"dow":3}
cell=230; gap=24; n=len(ORDER); W=n*cell+(n+1)*gap; H=cell+70
parts=[f'')
svg="\n".join(parts)
with open("/Users/ericwyuan/Desktop/Work/fenix8v3-watchface/preview-themes.svg","w") as f:
f.write(svg)
print("written preview-themes.svg", len(svg), "bytes; size", W,"x",H)