import svg from typing import Iterator, NamedTuple import numpy as np RGen = np.random.default_rng() class Point(NamedTuple): x: float y: float def iter_body_points(size: int) -> Iterator[Point]: n_points = RGen.integers(3, 12) # how many points do we want? angle_step = ( np.pi * 2 / n_points ) # step used to place each point at equal distances for point_number in range(1, n_points + 1): pull = 0.75 * RGen.random() * 0.25 angle = point_number * angle_step x = size + np.cos(angle) * size * pull y = size + np.sin(angle) * size * pull yield Point(x, y) def spline(points: list[Point], body_tension: int) -> Iterator[svg.PathData]: """ https://github.com/georgedoescode/splinejs """ yield svg.MoveTo(*points[-1]) first_point = points[0] second_point = points[1] points.insert(0, points[-1]) points.insert(0, points[-2]) points.append(first_point) points.append(second_point) for p0, p1, p2, p3 in zip(points, points[1:], points[2:], points[3:]): yield svg.CubicBezier( x1=p1.x + (p2.x - p0.x) / 6 * body_tension, y1=p1.y + (p2.y - p0.y) / 6 * body_tension, x2=p2.x - (p3.x - p1.x) / 6 * body_tension, y2=p2.y - (p3.y - p1.y) / 6 * body_tension, x=p2.x, y=p2.y, ) def blob(x, y, c, s, body_tension: int = 1) -> svg.Path: points = list(iter_body_points(s)) return svg.Path( d=list(spline(points, body_tension)), fill=c, stroke="#000000", stroke_width=2, transform=[svg.Translate(x-s, y-s)], )