#!/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''] parts.append(f'') for i,tid in enumerate(ORDER): ox=gap+i*(cell+gap); oy=24 sc=cell/500.0 parts.append(f'') parts.append(f''+face(tid,m,battery,temp,gi)+'') parts.append(f'{i+1} · {THEMES[tid]["name"]}') parts.append('') 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)