K="#141414"; FF="Avenir Next, Avenir, Helvetica Neue, Helvetica"
C={"y":("#ffe680","#ffd84d","#e8b81f"),"g":("#a8e3c1","#7fd4a3","#3da96f"),
"r":("#fbbfae","#f79b83","#e0563a"),"n":("#f2f0ea","#e8e6df","#b3b0a6"),
"b":("#bcd9ef","#8fc4e6","#4a90c2")}
DX,DY=58,42
def slab(x,y,w,t,c,dx=DX,dy=DY,sw=4):
top,fr,sd=C[c]
return (f''
f''
f'')
def prism(x,y,w,h,c,d1=12,d2=10,sw=3.5):
top,fr,sd=C[c]
return (f''
f''
f'')
def card(x,y,w,h,c,sw=4):
top,fr,sd=C[c]; d1,d2=26,20
return (f''
f''
f'')
def txt(x,y,s,sz=19,w=600,f=K,a="start"):
return f'{s}'
def arrow(x1,y1,x2,y2,c=K,sw=6):
import math
ang=math.atan2(y2-y1,x2-x1); L=17; sp=0.42
ax=x2-L*math.cos(ang-sp); ay=y2-L*math.sin(ang-sp)
bx=x2-L*math.cos(ang+sp); by=y2-L*math.sin(ang+sp)
return (f''
f'')
def callout(x,y,lines,ax,ay,side="bottom",c=K,fill="#ffffff",sz=17,pad=16):
"""Globo con línea de llamada y punto de ancla. (x,y)=esquina sup-izq de la caja."""
wmax=max(len(l) for l in lines)*sz*0.52
w=wmax+pad*2; h=len(lines)*(sz+8)+pad*1.4
o=[f'']
for i,l in enumerate(lines):
o.append(txt(x+w/2,y+pad+sz*0.85+i*(sz+8),l,sz,600 if i==0 else 500,c,"middle"))
if side=="bottom": sx,sy=x+w/2,y+h
elif side=="top": sx,sy=x+w/2,y
elif side=="left": sx,sy=x,y+h/2
else: sx,sy=x+w,y+h/2
o.append(f'')
o.append(f'')
return "".join(o)
def step(x,y,n,label,c="n",w=150,h=96):
"""Caja de etapa de proceso, numerada."""
o=[card(x,y,w,h,c)]
o.append(f'')
o.append(txt(x+w+26,y-13,str(n),18,600,"#ffffff","middle"))
o.append(txt(x+w/2,y+h+34,label,18,600,K,"middle"))
return "".join(o)
def chains(x,y,w,mode,col=K,dx=DX,dy=DY,sw=3):
o=[]
if mode in("long","mesh"):
for k in (0.22,0.42,0.62,0.82):
o.append(f'')
if mode in("cross","mesh"):
n=int(w/44)
for j in range(1,n+1):
px=x+j*w/(n+1)
o.append(f'')
if mode=="tangle":
for k in (0.32,0.5,0.68):
bx=x+k*dx; by=y-k*dy; seg=[]
for i in range(9):
px=bx+i*w/8; py=by+(8 if i%2 else -8)
seg.append(f'{"M" if i==0 else "L"}{px:.0f} {py:.0f}')
o.append(f'')
if mode=="wavy":
for k in (0.32,0.5,0.68):
bx=x+k*dx; by=y-k*dy; seg=[]
for i in range(9):
px=bx+i*w/8; py=by+(3 if i%2 else -3)
seg.append(f'{"M" if i==0 else "L"}{px:.0f} {py:.0f}')
o.append(f'')
return "".join(o)
def cota(x1,y1,x2,y2,label,c="#4a90c2",off=0,sz=16):
"""Línea de cota con topes y etiqueta."""
import math
o=[f'']
if abs(y2-y1)')
o.append(f'')
o.append(txt((x1+x2)/2,y1-14+off,label,sz,600,c,"middle"))
else:
o.append(f'')
o.append(f'')
o.append(txt(x1-16+off,(y1+y2)/2+5,label,sz,600,c,"end"))
return "".join(o)
def bubble(cx,base,top,rw,nw,c="y",sw=4):
"""Burbuja simple: cuello nw abajo, ancho rw arriba."""
tp,fr,sd=C[c]
ry=int(rw*0.25)
p=(f'')
p+=(f'')
p+=(f'')
p+=(f'')
p+=f''
return p
def bagfig(x,y,w,h,c="g",sw=4):
"""Bolsa isométrica simple."""
tp,fr,sd=C[c]; d=22
return (f''
f''
f'')
def tablefig(x,y,cols,rows,cw,rh=42,head="#e8e6df",sz=16):
"""Tabla dibujada con trazo grueso."""
o=[]; W=sum(cw)
o.append(f'')
cx=x
for i,c in enumerate(cols):
o.append(txt(cx+cw[i]/2,y+rh*0.64,c,sz,600,K,"middle")); cx+=cw[i]
if i')
for r,row in enumerate(rows):
ry=y+rh*(r+1)
o.append(f'')
cx=x
for i,cell in enumerate(row):
col=K if i==0 else "#5f5e5a"
o.append(txt(cx+cw[i]/2,ry+rh*0.64,cell,sz-1,600 if i==0 else 500,col,"middle")); cx+=cw[i]
o.append(f'')
return "".join(o)
def molec(x,y,w,tipo,col=K,sw=4.5,amp=16,n=10):
"""Cadena de polietileno en zigzag con ramificación según tipo."""
pts=[]; stepx=w/n
for i in range(n+1):
pts.append((x+i*stepx, y-amp if i%2 else y+amp))
d="".join(f'{"M" if i==0 else "L"}{px:.0f} {py:.0f} ' for i,(px,py) in enumerate(pts))
o=[f'']
def rama(i,l,sub=False,up=True):
if i>len(pts)-1: return
px,py=pts[i]; dy=-l if up else l
o.append(f'')
if sub:
o.append(f'')
o.append(f'')
if tipo=="ldpe":
rama(1,42,True,True); rama(4,38,True,False); rama(7,44,True,True); rama(min(9,n-1),30,False,False)
elif tipo=="lldpe":
for i in (1,3,5,7,9):
if i<=n: rama(i,20,False,i%4==1)
elif tipo=="mdpe":
rama(2,18,False,True); rama(6,18,False,False)
return "".join(o)
def scalebar(x,y,w,marks,lo,hi,h=26,c="#e8e6df"):
"""Barra de escala horizontal con marcadores."""
o=[f'']
for val,lab,col,side in marks:
px=x+(val-lo)/(hi-lo)*w
o.append(f'')
o.append(f'')
if side=="up": o.append(txt(px,y-26,lab,18,600,col,"middle"))
else: o.append(txt(px,y+h+42,lab,18,600,col,"middle"))
o.append(txt(x,y+h+70,str(lo),16,500,"#a8a8a8","start"))
o.append(txt(x+w,y+h+70,str(hi),16,500,"#a8a8a8","end"))
return "".join(o)
def stackchains(x,y,w,n,gap,tipo,col=K,sw=4,amp=10,seg=9,jitter=0):
"""Varias cadenas apiladas para mostrar empaquetamiento."""
import random
random.seed(11)
o=[]
for i in range(n):
yy=y+i*gap+(random.randint(-jitter,jitter) if jitter else 0)
xx=x+(random.randint(-jitter*2,jitter*2) if jitter else 0)
o.append(molec(xx,yy,w,tipo,col,sw,amp,seg))
return "".join(o)
def upicon(x,y,c="#1d6b3e",s=1.0):
return (f'')
def downicon(x,y,c="#e0563a",s=1.0):
return (f'')