import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle, Polygon, Circle, Arc
from matplotlib.path import Path
from matplotlib.patches import PathPatch
B="#1f4e8c";BL="#6fa8dc";V="#f2f2f2";G="#555555";A="#c47a12";T="#222222";GR="#2e7d32";GP="#16365f"
fig=plt.figure(figsize=(16,13.4),dpi=110)
fig.patch.set_facecolor("white")
ax=fig.add_axes([0.01,0.417,0.53,0.4925]);ax2=fig.add_axes([0.55,0.417,0.44,0.4925])
for a in (ax,ax2):
    a.set_aspect("equal");a.axis("off")
ax.set_xlim(-2.55,2.55);ax.set_ylim(-1.15,3.5)
ax2.set_xlim(-3.0,2.6);ax2.set_ylim(-1.15,3.75)
fig.text(0.5,0.963,"Panneau hybride Muhleisen, version optimisée : schéma de principe",ha="center",fontsize=19,color=T,weight="bold")
fig.text(0.27,0.922,"Vue de face, volets dépliés (mode vent ; en mode solaire, voir plus bas)",ha="center",fontsize=13,color=G)
fig.text(0.77,0.922,"Vue de profil (dans l'axe), mode vent",ha="center",fontsize=13,color=G)
def prism(a,cx,cy,s=1.0,arrow=False):
    th=np.linspace(np.radians(250),np.radians(130),14)
    nose=[(-2.45+0.22*np.cos(t_),-1.28+0.22*np.sin(t_)) for t_ in th]
    ang=np.linspace(np.radians(183.3),np.radians(244.3),14);rr=np.linspace(1.753,1.84,14)
    arc=[(r_*np.cos(t_),r_*np.sin(t_)) for r_,t_ in zip(rr,ang)]
    pts=np.array([(-0.8,-1.66),(-2.3,-1.48)]+nose+arc)*s+np.array([cx,cy])
    a.add_patch(Polygon(pts,closed=True,facecolor="#4a4a4a",edgecolor="#2a2a2a",lw=1.2,zorder=4))
    ar=np.array(arc)*s+np.array([cx,cy])
    a.plot(ar[:,0],ar[:,1],color="#888888",lw=4.5,solid_capstyle="butt",zorder=5)
    a.plot(ar[:,0],ar[:,1],color="white",lw=3,solid_capstyle="butt",zorder=6)
    if arrow:
        p0=np.array([-2.95,-1.15])*s+np.array([cx,cy]);p1=np.array([-1.95,0.25])*s+np.array([cx,cy])
        a.annotate("",xy=p1,xytext=p0,arrowprops=dict(arrowstyle="-|>",color="#4a90c2",lw=2,connectionstyle="arc3,rad=-0.15"),zorder=8)
def num(a,x,y,k):
    a.add_patch(Circle((x,y),0.115,facecolor="white",edgecolor=T,lw=1.3,zorder=20))
    a.text(x,y,str(k),ha="center",va="center",fontsize=10,weight="bold",color=T,zorder=21)
def lead(a,x0,y0,x1,y1):
    a.plot([x0,x1],[y0,y1],color="#666666",lw=0.9,zorder=19)
hy=1.7;L=1.2;R=1.6;VL=0.61;LV=1.55
ax.add_patch(Rectangle((-2.2,-0.75),4.4,0.2,color="#d9d9d9"))
ax.add_patch(Rectangle((-0.3,-0.55),0.6,0.1,color="#999999"))
ax.add_patch(Rectangle((-1.65,-0.45),3.3,0.3,facecolor="#e6e6e6",edgecolor=G,lw=1))
ax.add_patch(Rectangle((-1.65,-0.15),3.3,0.15,facecolor="#bfbfbf",edgecolor=G))
ax.add_patch(Rectangle((-1.6,-0.06),3.2,0.06,facecolor=GP,edgecolor="#0b2a52",zorder=3))
for s in (-1,1):
    verts=[(s*1.62,hy-0.05),(s*1.98,hy-0.15),(s*1.84,0.45),(s*1.62,0.0)]
    ax.add_patch(PathPatch(Path(verts,[Path.MOVETO,Path.CURVE4,Path.CURVE4,Path.CURVE4]),fill=False,color=G,lw=4.5,capstyle="round"))
ax.plot([-2.06,-1.45],[hy,hy],color=T,lw=4,zorder=5)
ax.plot([1.45,2.06],[hy,hy],color=T,lw=4,zorder=5)
y0p=hy-0.565;y1p=hy+0.565
ax.add_patch(Rectangle((-1.45,y0p),2.9,1.13,facecolor=B,edgecolor="#555555",lw=3.5,zorder=3))
for a_,b_ in (((-1.45,y0p),(1.45,y1p)),((-1.45,y1p),(1.45,y0p))):
    ax.plot([a_[0],b_[0]],[a_[1],b_[1]],color="#cfd8e3",lw=1.1,ls=(0,(6,3)),zorder=5)
for i in range(1,16):
    ax.plot([-1.45+0.1812*i]*2,[y0p,y1p],color="#3d6fb0",lw=0.6,zorder=4)
for j in range(1,6):
    ax.plot([-1.45,1.45],[y0p+0.1883*j]*2,color="#3d6fb0",lw=0.6,zorder=4)
corner=[(0.06,1.45),(0.1,1.55),(0.15,1.60),(0.2,1.643),(0.25,1.67),(0.3,1.69),(0.4,1.72),(0.5,1.745),(0.565,1.755)]
cellp=[(0.565,1.755),(0.7,1.768),(0.8,1.77),(0.9,1.764),(0.99,1.756)]
volp=[(0.99,1.756),(1.1,1.743),(1.2,1.727),(1.3,1.707),(1.4,1.684),(1.5,1.655),(1.6,1.62)]
def band(prof,sg,xin=None):
    right=[(x,hy+sg*d) for d,x in prof]
    if xin is None:
        left=[(-x,hy+sg*d) for d,x in prof][::-1]
        return right+left
    return right+[(xin,hy+sg*prof[-1][0]),(xin,hy+sg*prof[0][0])]
for sg in (1,-1):
    cp_=Polygon(band(cellp,sg),facecolor=BL,edgecolor="#2d5f99",lw=1.2,zorder=3)
    ax.add_patch(cp_)
    for i in range(0,16):
        ln,=ax.plot([-1.8+0.24*i]*2,[hy+sg*0.565,hy+sg*0.99],color="#4f86c6",lw=0.6,zorder=4)
        ln.set_clip_path(cp_)
    ax.add_patch(Polygon(band(volp,sg),facecolor="#cfe8f3",alpha=0.75,edgecolor=G,lw=1.2,zorder=3))
    ax.plot([-1.756,1.756],[hy+sg*0.99]*2,color=A,lw=2.4,ls=(0,(6,3)),zorder=5)
    for s in (-1,1):
        pts=[(s*x,y) for x,y in band(corner,sg,1.45)]
        cr=Polygon(pts,facecolor=BL,edgecolor="#2d5f99",lw=1.0,zorder=3)
        ax.add_patch(cr)
        for xx in (1.54,1.64,1.74):
            ln,=ax.plot([s*xx]*2,[hy+sg*0.06,hy+sg*0.565],color="#4f86c6",lw=0.6,zorder=4)
            ln.set_clip_path(cr)
for s in (-1,1):
    ax.add_patch(Circle((s*1.62,hy),0.07,facecolor="#dddddd",edgecolor=T,zorder=7))
    ax.add_patch(Circle((s*1.88,hy),0.17,fill=False,edgecolor=T,lw=1.3,ls=(0,(3,2)),zorder=6))
ax.add_patch(Rectangle((2.06,hy-0.08),0.16,0.16,facecolor=A,edgecolor=T,zorder=8))
ax.add_patch(Circle((0.85,2.05),0.04,color="#e53935",zorder=8))
lead(ax,-0.3,1.55,-0.3,1.55);num(ax,-0.3,1.55,1)
lead(ax,-0.6,2.47,-2.0,2.95);num(ax,-2.0,2.95,2)
lead(ax,0.6,2.95,0.6,3.38);num(ax,0.6,3.38,3)
lead(ax,0.1,2.69,0.1,3.38);num(ax,0.1,3.38,4)
lead(ax,-0.9,hy+0.25,-2.35,2.25);num(ax,-2.35,2.25,5)
lead(ax,-1.95,hy-0.12,-2.35,1.4);num(ax,-2.35,1.4,6)
lead(ax,2.14,hy,2.4,2.2);num(ax,2.4,2.2,7)
lead(ax,0.85,2.05,1.55,3.0);num(ax,1.55,3.0,8)
lead(ax,1.86,0.9,2.35,1.0);num(ax,2.35,1.0,9)
lead(ax,0.4,-0.03,0.6,0.45);num(ax,0.6,0.45,10)
lead(ax,-1.6,-0.08,-2.35,0.25);num(ax,-2.35,0.25,11)
lead(ax,0.9,-0.3,2.2,-0.3);num(ax,2.32,-0.3,12)
lead(ax,-1.62,hy+0.06,-2.35,1.85);num(ax,-2.35,1.85,13)
ax.plot([2.06,2.06],[hy+0.08,hy+0.75],color=T,lw=1.5,zorder=6)
ax.add_patch(Polygon([[1.92,hy+0.75],[2.06,hy+0.82],[2.3,hy+0.8],[2.3,hy+0.7],[2.06,hy+0.68]],facecolor="#cccccc",edgecolor=T,zorder=7))
lead(ax,2.3,hy+0.75,2.4,2.75);num(ax,2.4,2.75,17)
for s_ in (-1,1):
    ax.add_patch(Rectangle((min(s_*1.72,s_*2.04),hy-0.22),0.32,0.44,facecolor="#e8e8e8",edgecolor="#555555",lw=1.3,ls=(0,(5,3)),alpha=0.5,zorder=5))
lead(ax,-2.0,hy-0.2,-2.35,1.1);num(ax,-2.35,1.1,16)
ax.add_patch(Rectangle((-0.2,-0.42),0.4,0.22,facecolor="#7a7a7a",edgecolor=T,zorder=4))
ax.annotate("",xy=(1.77,-0.92),xytext=(-1.77,-0.92),arrowprops=dict(arrowstyle="<->",color=G))
ax.text(0,-1.07,"Panneau verre 2,9 m, rotor jusqu'à 3,5 m le long de l'axe (nord-sud en mode solaire), plateau 3,3 m",ha="center",fontsize=10,color=G)
ax2.add_patch(Rectangle((-2.1,-0.75),4.6,0.2,color="#d9d9d9"))
ax2.add_patch(Rectangle((-0.3,-0.55),0.6,0.1,color="#999999"))
ax2.add_patch(Rectangle((-0.65,-0.45),1.3,0.3,facecolor="#e6e6e6",edgecolor=G))
ax2.add_patch(Rectangle((-0.65,-0.15),1.3,0.15,facecolor="#bfbfbf",edgecolor=G))
ax2.add_patch(Rectangle((-0.625,-0.06),1.25,0.06,facecolor=GP,zorder=3))
c=np.array([0,hy])
for s in (-1,1):
    verts=[(0,hy-0.05),(s*0.35,hy-0.5),(s*0.75,0.6),(s*0.6,0.0)]
    ax2.add_patch(PathPatch(Path(verts,[Path.MOVETO,Path.CURVE4,Path.CURVE4,Path.CURVE4]),fill=False,color="#9a9a9a",lw=4,capstyle="round",zorder=1))
ax2.add_patch(Circle(c,R,fill=False,ls=(0,(4,4)),color="#999999",lw=0.8))
th=np.radians(35);u=np.array([np.cos(th),np.sin(th)]);n=np.array([-np.sin(th),np.cos(th)])
def seg(p0,p1,col,lw):
    ax2.plot([p0[0],p1[0]],[p0[1],p1[1]],color=col,lw=lw,solid_capstyle="butt",zorder=5)
seg(c-0.565*u,c+0.565*u,B,7)
seg(c+0.565*u,c+0.99*u,BL,6)
seg(c-0.565*u,c-0.99*u,BL,6)
def cseg(p0,d,Ln,bend):
    pe=np.array([-d[1],d[0]])
    t=np.linspace(0,1,30)
    pts=np.array([(1-tt)**2*p0+2*(1-tt)*tt*(p0+d*Ln/2+pe*bend)+tt**2*(p0+d*Ln) for tt in t])
    ax2.plot(pts[:,0],pts[:,1],color="#7fb6cc",lw=5,solid_capstyle="butt",zorder=5)
p=c+0.99*u;cseg(p,u,VL,0.07)
q=c-0.99*u;cseg(q,-n,VL,0.07)
kp=c+0.3*n
ax2.plot([c[0],kp[0]],[c[1],kp[1]],color="#333333",lw=1.5,zorder=6)
for e in (c+(0.99+VL)*u,c-0.99*u):
    ax2.plot([kp[0],e[0]],[kp[1],e[1]],color="#333333",lw=0.8,zorder=6)
lead(ax2,kp[0],kp[1],-0.7,2.75);num(ax2,-0.7,2.75,15)
ax2.add_patch(Circle(c,0.08,facecolor="#7a7a7a",edgecolor=T,zorder=7))
mc=np.array([0.42,hy])
ax2.plot([0,mc[0]],[hy+0.3,hy+0.12],color="#333333",lw=1.3,zorder=7)
ax2.plot([0,mc[0]],[hy-0.3,hy-0.12],color="#333333",lw=1.3,zorder=7)
ax2.add_patch(Circle(c,0.3,fill=False,edgecolor="#333333",lw=1.2,zorder=7))
ax2.add_patch(Circle(mc,0.12,facecolor="#7a7a7a",edgecolor=T,lw=1.4,zorder=8))
ax2.add_patch(Circle(mc,0.05,facecolor="#dddddd",zorder=9))
ax2.plot([0.42,0.3],[hy-0.12,0.95],color="#9a9a9a",lw=2,zorder=1)
lead(ax2,0.5,hy+0.08,1.05,2.05);num(ax2,1.05,2.05,6)
tear=[(0,hy+0.4),(-0.3,hy+0.28),(-0.4,hy),(-0.3,hy-0.28),(0,hy-0.4),(0.45,hy-0.25),(0.95,hy),(0.45,hy+0.25)]
ax2.add_patch(Polygon(tear,closed=True,facecolor="#e8e8e8",edgecolor="#555555",lw=1.4,ls=(0,(5,3)),alpha=0.45,zorder=6))
lead(ax2,0.8,hy+0.05,1.6,1.85);num(ax2,1.6,1.85,16)
ax2.add_patch(Rectangle((-0.2,-0.42),0.4,0.22,facecolor="#7a7a7a",edgecolor=T,zorder=4))
lead(ax2,0.2,-0.31,1.2,-0.31);num(ax2,1.32,-0.31,14)
ax2.add_patch(Arc(c,3.45,3.45,theta1=40,theta2=100,color=A,lw=2))
e0=c+1.72*np.array([np.cos(np.radians(46)),np.sin(np.radians(46))]);e1=c+1.72*np.array([np.cos(np.radians(36)),np.sin(np.radians(36))])
ax2.annotate("",xy=e1,xytext=e0,arrowprops=dict(arrowstyle="-|>",color=A,lw=2))
for yy in (1.95,2.35,2.75):
    ax2.annotate("",xy=(-1.55,yy),xytext=(-2.2,yy),arrowprops=dict(arrowstyle="-|>",color="#4a90c2",lw=2.2))
ax2.text(-2.2,3.0,"Vent",fontsize=12,color="#4a90c2",weight="bold")
prism(ax2,0,1.7,1.0,True)
lead(ax2,-1.36,0.45,-1.0,0.42);num(ax2,-0.98,0.42,18)
ax2.text(-0.2,3.62,"Rotation libre quand le vent rapporte plus que le soleil perdu, dès 3-4 m/s la nuit",fontsize=10.5,color=A,ha="center")
pt=p+0.3*u
ax2.plot([pt[0],1.45],[pt[1],2.75],color="#666666",lw=0.9)
ax2.text(1.5,2.75,"Volet cambré ouvert :\npoussé par le vent",fontsize=10.5,color=T,va="center")
qt=q-0.3*n
ax2.plot([qt[0],0.85],[qt[1],0.45],color="#666666",lw=0.9)
ax2.text(0.9,0.42,"Volet replié de chant :\nrevient contre le vent",fontsize=10.5,color=T,va="center")
ax2.text(-0.2,-1.07,"Cercle balayé 3,2 m, axe à 1,7 m, garde 10 cm sur le panneau au sol encastré, arceaux en arrière-plan",ha="center",fontsize=10,color=G)
ax3=fig.add_axes([0.02,0.245,0.36,0.165]);ax3.set_aspect("equal");ax3.axis("off")
ax3.set_xlim(-3.0,3.4);ax3.set_ylim(-0.35,3.0)
ax3.add_patch(Rectangle((-2.4,-0.32),5.6,0.12,color="#d9d9d9"))
ax3.add_patch(Rectangle((-0.65,-0.2),1.3,0.2,facecolor="#bfbfbf",edgecolor=G))
ax3.add_patch(Rectangle((-0.625,-0.06),1.25,0.06,facecolor=GP,zorder=3))
e=np.radians(40);d=np.array([np.cos(e),np.sin(e)]);nn_=d;uu=np.array([-np.sin(e),np.cos(e)]);c3=np.array([0,1.7])
for s_ in (-1,1):
    ax3.plot([s_*0.35,0],[0,1.7],color="#b0b0b0",lw=2.5,zorder=1)
ax3.plot(*zip(c3-uu*0.99,c3+uu*0.99),color=BL,lw=6,solid_capstyle="butt",zorder=5)
ax3.plot(*zip(c3-uu*0.565,c3+uu*0.565),color=B,lw=7,solid_capstyle="butt",zorder=6)
for a_ in (c3-uu*0.99,c3+uu*0.99):
    ax3.plot(*zip(a_,a_-nn_*0.61),color="#7fb6cc",lw=5,solid_capstyle="butt",zorder=5)
ax3.add_patch(Circle(c3,0.07,facecolor="#7a7a7a",edgecolor=T,zorder=7))
prism(ax3,0,1.7)
ax3.add_patch(Circle((3.05,2.6),0.22,color="#f5b301",zorder=3))
for x0 in (-0.2,0.15,0.5):
    p0=np.array([x0,0.02]);p1=p0+d*2.6
    ax3.annotate("",xy=p0,xytext=p1,arrowprops=dict(arrowstyle="-|>",color="#e0a000",lw=1.6),zorder=8)
for o in (-0.5,0.0,0.5):
    p0=c3+uu*o+nn_*0.05;p1=p0+d*1.3
    ax3.annotate("",xy=p0,xytext=p1,arrowprops=dict(arrowstyle="-|>",color="#e0a000",lw=1.6),zorder=8)
fig.text(0.39,0.385,"Vue de profil, mode solaire sans vent : volets repliés",fontsize=12.5,color=T,weight="bold")
for j_,l_ in enumerate(['Vent faible : le rotor est verrouillé face au soleil et les vérins replient', 'les volets transparents à 90° derrière le panneau : parallèles aux rayons,', "ils ne font pas d'ombre et laissent passer la lumière diffuse. Le soleil", "atteint le panneau au sol (3,2 × 1,25 m) dès qu'il descend sous ~40°.", "Panneau au sol : ~2,8 fois plus de lumière qu'avec des volets blancs à plat."]):
    fig.text(0.39,0.355-j_*0.024,l_,fontsize=11,color=T)
items=['1  Panneau verre-verre biface 2,9 × 1,13 m, fixations caoutchouc', "2  Modules sans verre bifaces (type Sunman) : ailettes et bords", "3  Volet transparent polycarbonate cambré 0,61 m, butées amorties", '4  Charnière à ressort et petit vérin électrique', '5  Cadre rigide, demi-axes aux bouts, câbles inox en croix au dos', '6  Moteur de trottinette, poulie et courroie, ×2', '7  Bague collectrice à balais en fibres + électronique embarquée', '8  Sonde de température (volets écartés si surchauffe)', '9  Arceaux courbes en tube galvanisé cintré', '10  Panneau au sol monoface 3,2 × 1,25 m, encastré', '11  Plateau pivotant réduit (3,3 × 1,3 m) sur couronne', '12  Caisson : contrôleur, supercondensateurs, frein de survitesse', '13  Verrou automatique à goupille sur le roulement', "14  Moteur d'orientation : vent, retour nord-sud, suivi 2 axes", '15  Haubans inox sur poinçon, aux extrémités du rotor', "16  Carter de courroie en goutte d'eau, pointe sous le vent", '17  Girouette électronique', '18  Déflecteur en prisme, nez arrondi, face arrière blanche']
for k,t in enumerate(items):
    col=k//6;row=k%6
    fig.text(0.03+col*0.33,(0.235-row*0.03)*0.8209,t,fontsize=10.5,color=T)
fig.text(0.5,0.0205,"Schéma de principe, proportions approximatives. Estimations en série : ~5 269 kWh/an sur toit côtier (vent 6,5 m/s, ~0,086 €/kWh sur 25 ans), ~3 781 kWh/an sur mât de 6 m (5,2 m/s). www.mulo.world",ha="center",fontsize=10.5,color=G)
plt.savefig("schema_panneau_solaire_hybride_muhleisen_optimise.png",dpi=110,facecolor="white")
