# Genera docs/bocetos/boceto-1..5.svg. Monigotes de un solo trazo de spray en zigzag,
# altos y juntos, en piña: el centro en colores de fuego y alrededor fríos
# (a la manera del contenedor y las «Ausencias» de Suso33).
import sys, random, math, os
OUT = sys.argv[1]
W, H = 1600, 700
FUEGO = ["#fffbe6", "#ffe066", "#ffb627", "#ff7b1c", "#f04e23", "#c8102e"] # del centro hacia fuera
FRIO = ["#5b2a86", "#8e44c9", "#3b5bdb", "#6c7ae0", "#e9e6f5", "#111", "#b07cd8"]
DEFS = ''''''
def fuego(d): # d = 0 en el centro de la piña, 1 en el borde
i = min(len(FUEGO) - 1, int(max(0, d + random.uniform(-.12, .12)) * len(FUEGO)))
return FUEGO[i]
def monigote(o, x, y, h, col, lean=0.0, paso=False, brazo=False, sombra=False):
"""Un trazo: cabeza en zigzag apretado y cuerpo en zigzag alto. (x, y) = pies."""
sw = max(1.1, h * .011)
dy = sw * 2.0 # renglones del zigzag
if sombra:
o.append(f'')
top = y - h
pts = []
yy = top
i = 0
while yy <= y:
t = (yy - top) / h
cx = x + lean * h * (1 - t)
if t < .13: # cabeza
w = h * .05 * math.sin(math.pi * t / .13) ** .6 + sw * .3
elif t < .16: # cuello
w = h * .008
else: # cuerpo: hombros y se afila hacia los pies
u = (t - .16) / .84
w = h * (.062 * (1 - u) ** .8 + .014)
if paso and u > .5: # zancada: el zigzag se abre en dos
k = (u - .5) / .5
cx += (-1 if (i // 2) % 2 else 1) * h * .035 * k
w *= .6
s = -1 if i % 2 else 1
pts.append((cx + s * w + random.uniform(-.3, .3) * sw, yy))
yy += dy * 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'')
if brazo: # brazo en alto
bx, by = x + lean * h * .84 + h * .05, top + h * .2
p, n = [], 10
for j in range(n):
t = j / (n - 1)
p.append((bx + t * h * .06 + (-1 if j % 2 else 1) * h * .012, by - t * h * .30))
d = "M" + " L".join(f"{px:.1f},{py:.1f}" for px, py in p)
o.append(f'')
def portal(o, x, y, w, h, fondo="#111"):
o.append(f''
f'')
def svg(cuerpo, fondo="#fff"):
return (f'\n")
def fila(o, xs, y0, h0, centro, radio, frio=FRIO, lean=0.0, jit=12, hvar=.15, paso=.2, brazo=.08):
"""Una piña: monigotes muy juntos; los cercanos al centro, en fuego."""
for x in xs:
d = abs(x - centro) / radio
col = fuego(d) if d < 1 else random.choice(frio)
h = h0 * random.uniform(1 - hvar, 1 + hvar) * (1.05 if d < 1 else 1)
monigote(o, x, y0 + random.uniform(-jit, jit), h, col, lean, random.random() < paso, random.random() < brazo)
# 1 · Contenedor: piña de fuego en una pared gris, como el contenedor
def b1():
random.seed(1); o = []
o.append('')
xs = sorted(random.uniform(90, 1510) for _ in range(70))
fila(o, xs[::2], 610, 470, 800, 260)
fila(o, xs[1::2], 650, 500, 800, 230)
return o, "#9aa0a8"
# 2 · Piña que sale a la calle: en blanco, la masa avanza hacia la derecha
def b2():
random.seed(2); o = []
o.append('')
xs = sorted(random.gauss(820, 300) for _ in range(55))
xs = [x for x in xs if 80 < x < 1520]
fila(o, xs, 630, 400, 1000, 230, lean=.12, paso=.45, brazo=.12)
return o, "#fff"
# 3 · El portal: del hueco sale la gente y el fuego va delante
def b3():
random.seed(3); o = []
o.append('')
portal(o, 150, 610, 190, 380)
o.append('')
for i in range(30):
t = i / 29
x = 250 + t * 1250 + random.uniform(-25, 25)
d = abs(t - .32) / .25
col = fuego(d) if d < 1 else random.choice(FRIO)
monigote(o, x, 610 + t * 70 + random.uniform(-8, 8), 300 + t * 120, col, .1, random.random() < .45, random.random() < .12)
return o, "#fff"
# 4 · Calle en diagonal: la piña baja por la calle hacia quien mira
def b4():
random.seed(4); o = []
o.append(''
'')
ps = []
for _ in range(1500):
t = random.random()
x = 1240 - t * 700 + random.gauss(0, 40 + t * 150)
y = 300 + t * 390 + random.gauss(0, 10)
if 290 < y < 695 and all(math.hypot(x - a, (y - b) * 3) > 22 + t * 50 for a, b, _ in ps):
ps.append((x, y, t))
for x, y, t in sorted(ps, key=lambda p: p[1]):
d = math.hypot(x - 820, (y - 520) * 1.5) / 230
col = fuego(d) if d < 1 else random.choice(FRIO)
monigote(o, x, y, 60 + t * 230, col, -.1, random.random() < .35, random.random() < .1)
return o, "#fff"
# 5 · Ausencias: todos en negro y gris, y solo el corazón de la piña en fuego
def b5():
random.seed(5); o = []
xs = sorted(random.uniform(100, 1500) for _ in range(42))
fila(o, xs, 650, 430, 900, 120, frio=["#111", "#111", "#333", "#555", "#888"], lean=.06, hvar=.12, paso=.3, brazo=.1)
return o, "#fff"
os.makedirs(OUT, exist_ok=True)
for n, f in enumerate([b1, b2, b3, b4, b5], 1):
cuerpo, fondo = f()
open(f"{OUT}/boceto-{n}.svg", "w").write(svg(cuerpo, fondo))