162 lines
8.9 KiB
Python
162 lines
8.9 KiB
Python
# Genera sites/<web>/img/eje-*.svg: un símbolo por eje pintado con spray (relleno en zigzag,
|
|
# trazos con pulso y chorreones), en el color de cada eje (los mismos que los regueros del dibujo).
|
|
import sys, random, math, os
|
|
OUT = sys.argv[1]
|
|
EJES = {"hogar": "#f04e23", "clima": "#14965a", "fronteras": "#f2b705", "pueblos": "#3b5bdb", "igualdad": "#8e44c9"}
|
|
DEFS = ('<defs><filter id="spray" x="-30%" y="-10%" width="160%" height="120%"><feTurbulence type="fractalNoise" baseFrequency="0.8" numOctaves="2" seed="3" result="t"/>'
|
|
'<feDisplacementMap in="SourceGraphic" in2="t" scale="1.6" result="d"/><feGaussianBlur in="d" stdDeviation=".4" result="b"/>'
|
|
'<feGaussianBlur in="SourceGraphic" stdDeviation="2.5" result="n"/><feComponentTransfer in="n" result="n2"><feFuncA type="linear" slope=".3"/></feComponentTransfer>'
|
|
'<feMerge><feMergeNode in="n2"/><feMergeNode in="b"/></feMerge></filter></defs>')
|
|
|
|
def monigote(o, x, y, h, col, brazo=False, paso=False):
|
|
sw = max(1.4, h * .016)
|
|
pts, yy, i, top = [], y - h, 0, y - h
|
|
while yy <= y:
|
|
t = (yy - top) / h
|
|
cx = x
|
|
if t < .13:
|
|
w = h * .055 * math.sin(math.pi * t / .13) ** .6 + sw * .3
|
|
elif t < .16:
|
|
w = h * .01
|
|
else:
|
|
u = (t - .16) / .84
|
|
w = h * (.07 * (1 - u) ** .8 + .016)
|
|
if paso and u > .5:
|
|
cx += (-1 if (i // 2) % 2 else 1) * h * .04 * (u - .5) / .5
|
|
w *= .6
|
|
pts.append((cx + (-1 if i % 2 else 1) * w * random.uniform(.75, 1.25), yy))
|
|
yy += sw * 2.1 * random.uniform(.85, 1.15) * (.55 if t < .13 else 1)
|
|
i += 1
|
|
d = "M" + " L".join(f"{px:.1f},{py:.1f}" for px, py in pts)
|
|
o.append(f'<path d="{d}" fill="none" stroke="{col}" stroke-width="{sw:.1f}" stroke-linejoin="round" stroke-linecap="round" filter="url(#spray)"/>')
|
|
if brazo:
|
|
bx, by = x + h * .06, top + h * .2
|
|
p = [(bx + j / 9 * h * .06 + (-1 if j % 2 else 1) * h * .014, by - j / 9 * h * .3) for j in range(10)]
|
|
o.append(f'<path d="M' + " L".join(f"{px:.1f},{py:.1f}" for px, py in p) + f'" fill="none" stroke="{col}" stroke-width="{sw:.1f}" stroke-linecap="round" filter="url(#spray)"/>')
|
|
|
|
def j(v, a):
|
|
return v + random.uniform(-a, a)
|
|
|
|
def relleno(o, poly, col, paso=2.6, sw=3.6):
|
|
# Zigzag horizontal que rellena un polígono convexo, como una pasada de spray.
|
|
ys = [y for _, y in poly]
|
|
pts, y, i = [], min(ys) + sw * .4, 0
|
|
while y <= max(ys) - sw * .4:
|
|
xs = []
|
|
for (x1, y1), (x2, y2) in zip(poly, poly[1:] + poly[:1]):
|
|
if (y1 <= y < y2) or (y2 <= y < y1):
|
|
xs.append(x1 + (y - y1) * (x2 - x1) / (y2 - y1))
|
|
if len(xs) >= 2:
|
|
a, b = min(xs), max(xs)
|
|
pts.append((j(a if i % 2 else b, 1.2), y))
|
|
i += 1
|
|
y += paso * random.uniform(.85, 1.15)
|
|
o.append(f'<path d="M{" L".join(f"{x:.1f},{y:.1f}" for x, y in pts)}" fill="none" stroke="{col}" stroke-width="{sw:.1f}" stroke-linejoin="round" stroke-linecap="round" filter="url(#spray)"/>')
|
|
|
|
def linea(o, pts, col, sw=5):
|
|
# Trazo con pulso: dos pasadas ligeramente desplazadas.
|
|
for k in range(2):
|
|
d = "M" + " L".join(f"{j(x, 1.1):.1f},{j(y, 1.1):.1f}" for x, y in pts)
|
|
o.append(f'<path d="{d}" fill="none" stroke="{col}" stroke-width="{sw * (1 if k == 0 else .6):.1f}" stroke-linejoin="round" stroke-linecap="round" opacity="{1 if k == 0 else .7}" filter="url(#spray)"/>')
|
|
|
|
def chorreon(o, x, y, largo, col):
|
|
y -= 6
|
|
o.append(f'<path d="M{x:.1f},{y:.1f} L{j(x, .6):.1f},{y + largo:.1f}" stroke="{col}" stroke-width="2.4" stroke-linecap="round" filter="url(#spray)"/>')
|
|
o.append(f'<circle cx="{x:.1f}" cy="{y + largo:.1f}" r="2.2" fill="{col}" filter="url(#spray)"/>')
|
|
|
|
def hogar(o, col):
|
|
# Una casa: tejado, dos muros y el dintel; la puerta queda en blanco.
|
|
relleno(o, [(70, 22), (128, 72), (12, 72)], col)
|
|
relleno(o, [(24, 72), (54, 72), (54, 150), (24, 150)], col)
|
|
relleno(o, [(86, 72), (116, 72), (116, 150), (86, 150)], col)
|
|
relleno(o, [(54, 72), (86, 72), (86, 96), (54, 96)], col)
|
|
linea(o, [(98, 50), (98, 26), (110, 26), (110, 60)], col, 5)
|
|
for x, l in ((30, 22), (47, 14), (92, 28), (110, 16)):
|
|
chorreon(o, x, 150, l, col)
|
|
|
|
def clima(o, col):
|
|
# La Tierra que se derrite: anillo en zigzag, continentes en spray (América, África y Europa) y chorreones.
|
|
cx, cy, r, pts = 70, 72, 52, []
|
|
for i in range(220):
|
|
a = 2 * math.pi * i / 219
|
|
rr = r - (0 if i % 2 else 11) + random.uniform(-1.5, 1.5)
|
|
pts.append((cx + rr * math.cos(a), cy + rr * math.sin(a)))
|
|
o.append(f'<path d="M{" L".join(f"{x:.1f},{y:.1f}" for x, y in pts)}" fill="none" stroke="{col}" stroke-width="3.2" stroke-linejoin="round" filter="url(#spray)"/>')
|
|
norteamerica = [(34, 48), (44, 38), (58, 32), (72, 33), (78, 40), (72, 48), (66, 56), (62, 64), (56, 68), (48, 62), (40, 56)]
|
|
sudamerica = [(60, 74), (70, 74), (78, 80), (80, 88), (74, 98), (68, 108), (64, 112), (62, 100), (58, 86)]
|
|
europa = [(90, 36), (100, 31), (110, 38), (106, 46), (96, 48), (90, 44)]
|
|
africa = [(90, 54), (104, 52), (112, 60), (112, 72), (104, 86), (98, 96), (94, 86), (92, 72), (86, 62)]
|
|
for c in (norteamerica, sudamerica, europa, africa):
|
|
c = [(cx + (x - cx) * .78, cy + (y - cy) * .78) for x, y in c]
|
|
relleno(o, c, col, paso=2.4)
|
|
linea(o, c + c[:1], col, 2.5)
|
|
for x, l in ((40, 26), (55, 40), (72, 30), (88, 44), (102, 20)):
|
|
y0 = cy + math.sqrt(max(0, r * r - (x - cx) ** 2)) - 4
|
|
chorreon(o, x, y0, l, col)
|
|
|
|
def fronteras(o, col):
|
|
# Una valla rota: barrotes que se abren en el centro y alambre de espino cortado.
|
|
for x in (14, 32, 108, 126):
|
|
linea(o, [(x, 40), (x, 150)], col, 6)
|
|
linea(o, [(50, 150), (52, 100), (40, 56)], col, 6)
|
|
linea(o, [(90, 150), (88, 100), (100, 56)], col, 6)
|
|
linea(o, [(6, 62), (44, 66)], col, 5)
|
|
linea(o, [(96, 66), (134, 62)], col, 5)
|
|
linea(o, [(6, 112), (54, 114)], col, 5)
|
|
linea(o, [(86, 114), (134, 112)], col, 5)
|
|
for x, y in ((22, 64), (116, 64), (22, 113), (118, 113)):
|
|
linea(o, [(x - 4, y - 4), (x + 4, y + 4)], col, 4)
|
|
linea(o, [(x + 4, y - 4), (x - 4, y + 4)], col, 4)
|
|
|
|
def igualdad(o, col):
|
|
# El símbolo feminista: anillo en zigzag y la cruz de debajo, con chorreones.
|
|
cx, cy, r, pts = 70, 58, 40, []
|
|
for i in range(180):
|
|
a = 2 * math.pi * i / 179
|
|
rr = r - (0 if i % 2 else 11) + random.uniform(-1.5, 1.5)
|
|
pts.append((cx + rr * math.cos(a), cy + rr * math.sin(a)))
|
|
o.append(f'<path d="M{" L".join(f"{x:.1f},{y:.1f}" for x, y in pts)}" fill="none" stroke="{col}" stroke-width="3.2" stroke-linejoin="round" filter="url(#spray)"/>')
|
|
relleno(o, [(63, 96), (77, 96), (77, 160), (63, 160)], col)
|
|
relleno(o, [(46, 120), (94, 120), (94, 134), (46, 134)], col)
|
|
for x, y, l in ((52, 134, 12), (88, 134, 18), (66, 160, 10), (74, 160, 6)):
|
|
chorreon(o, x, y, l, col)
|
|
|
|
def girar(pts, ang, ox, oy, dx, dy):
|
|
ca, sa = math.cos(ang), math.sin(ang)
|
|
return [(ox + (x - ox) * ca - (y - oy) * sa + dx, oy + (x - ox) * sa + (y - oy) * ca + dy) for x, y in pts]
|
|
|
|
def pueblos(o, col):
|
|
# Una bala rota en horizontal: el casquillo a la izquierda y la punta que salta hacia la derecha.
|
|
# Contorno y líneas de volumen en negro para que tenga relieve.
|
|
ink = "#111"
|
|
o.append('<g transform="translate(0 -30) scale(1 1.35)">')
|
|
casquillo = [(14, 74), (60, 74), (64, 80), (59, 86), (65, 93), (60, 100), (62, 106), (14, 106)]
|
|
culote = [(6, 70), (14, 70), (14, 110), (6, 110)]
|
|
punta = [(76, 77), (98, 77), (112, 79), (124, 84), (132, 91), (124, 98), (112, 103), (98, 105), (76, 105), (80, 99), (74, 92), (79, 86), (73, 80)]
|
|
giro = lambda pts: girar(pts, -.16, 76, 91, 2, -10)
|
|
relleno(o, casquillo, col)
|
|
relleno(o, culote, col)
|
|
relleno(o, giro(punta), col)
|
|
linea(o, casquillo + casquillo[:1], ink, 3)
|
|
linea(o, culote + culote[:1], ink, 3)
|
|
linea(o, giro(punta + punta[:1]), ink, 3)
|
|
# Volumen: ranura del casquillo, sombra abajo y brillo arriba
|
|
linea(o, [(20, 75), (20, 105)], ink, 2.5)
|
|
linea(o, [(24, 100), (56, 100)], ink, 2.5)
|
|
linea(o, giro([(98, 78), (98, 104)]), ink, 2.5)
|
|
linea(o, giro([(84, 100), (112, 100), (124, 95)]), ink, 2.5)
|
|
linea(o, [(26, 80), (50, 80)], "#fff", 2.5)
|
|
linea(o, giro([(86, 82), (110, 83)]), "#fff", 2.5)
|
|
# Chispas en el corte
|
|
for a in (-1.9, -1.4, 1.5, 2.0):
|
|
linea(o, [(69 + 8 * math.cos(a), 90 + 8 * math.sin(a)), (69 + 22 * math.cos(a), 90 + 22 * math.sin(a))], col, 4)
|
|
for x, l in ((24, 14), (40, 22), (54, 10)):
|
|
chorreon(o, x, 110, l, col)
|
|
o.append('</g>')
|
|
|
|
os.makedirs(OUT, exist_ok=True)
|
|
for n, (nombre, col) in enumerate(EJES.items()):
|
|
random.seed(40 + n)
|
|
o = []
|
|
globals()[nombre](o, col)
|
|
open(f"{OUT}/eje-{nombre}.svg", "w").write(f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 140 170" width="140" height="170">{DEFS}' + "".join(o) + "</svg>\n")
|