Pookkalam by Helmin Jinoz

Code

from math import sin, cos, pi, acos

def linspace(start, stop , n):
    delta = (stop - start)/(n-1)
    return [start + delta*i for i in range(int(n))]

def polar_closed_curve(func, points_per_rotation, num_rots, stroke='#000000', fill='none', stroke_width=None):
    domain = linspace(0, 2*pi*num_rots, points_per_rotation*num_rots)
    fvalues = map(func, domain)
    fvalues = [Point(r*cos(t), r*sin(t)) for r,t in zip(fvalues, domain)]
    if stroke_width:
        return polygon(fvalues, stroke=stroke, fill=fill, stroke_width=stroke_width)

    return polygon(fvalues, stroke=stroke, fill=fill)

#The petal for the outermost pattern. An intersection of two circles less a larger circle from below
def outer_leaf(r, pts):
    theta = 3*pi/180
    r_point = [r*sin(theta), r*cos(theta)]
    l_point = [-r*sin(theta), r*cos(theta)]
    phi = 10*pi/180
    center_left = [-r*sin(phi), r*cos(phi)]
    center_right = [r*sin(phi), r*cos(phi)]
    half_center_dist = r*sin(phi)
    radioos = (center_left[0] - r_point[0])**2 + (center_left[1] - r_point[1])**2
    radioos = radioos**0.5
    start_angle = acos((r_point[0] - center_left[0])/radioos)
    end_angle = acos(half_center_dist/radioos)
    delta = (end_angle-start_angle)/pts
    blade = []
    for i in range(pts):
        blade.append(Point(center_left[0] + radioos*cos(start_angle+i*delta), center_left[1] + radioos*sin(start_angle+i*delta)))
    start_angle, end_angle = pi - end_angle, pi - start_angle
    delta = (end_angle-start_angle)/pts
    for i in range(pts):
        blade.append(Point(center_right[0] + radioos*cos(start_angle+i*delta), center_right[1] + radioos*sin(start_angle+i*delta)))
    start_angle = pi/2+theta
    end_angle = pi/2 - theta
    delta = (end_angle-start_angle)/pts
    for i in range(pts+2):
        blade.append(Point(r*cos(start_angle+i*delta), r*sin(start_angle+i*delta)))
    return blade

leaf_radius = 85

def three_pflower(t):
    return leaf_radius*sin(3*t)

#Calculate the radius of the inner tangent circle
min_error = 9999999999999
best_r = None
for t in linspace(pi/6+0.05,pi/3, 10000):
    perp_slope = -(3*cos(3*t)*cos(t) - sin(3*t)*sin(t))/(3*cos(3*t)*sin(t) + sin(3*t)*cos(t))
    x0, y0 = leaf_radius*sin(3*t)*cos(t), leaf_radius*sin(3*t)*sin(t)
    y = -perp_slope*x0 + y0
    r = leaf_radius - y
    error = abs(r**2 - ((y0-y)**2+x0**2))
    if error < min_error and y < leaf_radius:
        min_error = error
        best_r = r
radius_ratio = best_r/leaf_radius

#Create the orange flower with a discrete yet smooth gradient
start = [255, 162, 0]
end = [255, 232, 191]
num_grad = 5
delta = [(e - s)/num_grad for e,s in zip(end, start)]
flower = []
for i in range(0, num_grad):
    curr_color = [s + i*d for s, d in zip(start, delta)]
    scale_fac = 1-i/num_grad
    if i != 0:
        flower.append(polar_closed_curve(three_pflower, 400*scale_fac, 1/2, fill=color(*curr_color), stroke=color(*curr_color), stroke_width=0) | scale(scale_fac))
    else:
        flower.append(polar_closed_curve(three_pflower, 400*scale_fac, 1/2, fill=color(*curr_color), stroke=color(0,0,0)) | scale(scale_fac))
flower = Group(flower)

outer_petals = [polygon(outer_leaf(leaf_radius, 30), stroke='none', fill='#ffffff') | repeat(60, rotate(6))]
outer_petals.append(polygon(outer_leaf(leaf_radius, 30), stroke='none', fill='#fbff00') | scale(2) | translate(y=-(leaf_radius+3)) | rotate(3) | repeat(60, rotate(6)))
outer_petals.append(polygon(outer_leaf(leaf_radius, 30), stroke='none', fill='#ff9d00') | scale(3) | translate(y=-2*(leaf_radius+3)) | repeat(60, rotate(6)))
outer_petals.append(polygon(outer_leaf(leaf_radius, 30), stroke='none', fill='#d9400d') | scale(4) | translate(y=-3*(leaf_radius+3)) | rotate(3) | repeat(60, rotate(6)))
outer_petals.append(polygon(outer_leaf(leaf_radius, 30), stroke='none', fill='#ba0404') | scale(5) | translate(y=-4*(leaf_radius+3)) | repeat(60, rotate(6)))

outer_petals = Group(outer_petals[::-1])
background = circle(r=4*leaf_radius, fill='#131c47')
outer = circle(r=leaf_radius, fill='#DF362D')
pretty_curvy_rings = polar_closed_curve(lambda t: leaf_radius+leaf_radius/10*(sin(9*t)-1), 300, 1, stroke='#3B0918', fill=color(0, 0, 255, 0.2)) | repeat(25, rotate(3) | scale(0.9))
pookalam = Group([background, outer_petals, outer, pretty_curvy_rings, flower])

def build_fractal(depth, centre, leaf_radius, ctr):
    global pookalam
    if depth == 0:
        return
    tancircle_dist = leaf_radius*(1-radius_ratio)
    if depth % 2 == 1:
        fill = '#3B0918'
        stroke = '#DF362D'
        c_fill = color(12, 255, 34, 0.1)
    else:
        fill = '#DF362D'
        stroke = '#3B0918'
        c_fill = color(0, 0, 255, 0.1)
    smaller_thing = Group([
        circle(x=centre[0], y=centre[1]+tancircle_dist, r=leaf_radius*radius_ratio, stroke_width=3*radius_ratio**ctr, fill=fill),
        polar_closed_curve(lambda t: leaf_radius+leaf_radius/10*(sin(9*t)-1), 300, 1, stroke=stroke, fill=c_fill, stroke_width=3*radius_ratio**ctr) 
        | repeat(25, rotate(3) | scale(0.9)) | scale(radius_ratio) | translate(x=centre[0], y=centre[1]+tancircle_dist),
        (flower | scale(radius_ratio**ctr) | translate(x=centre[0], y=centre[1]+tancircle_dist)),
    ]) | repeat(3, Rotate(120, anchor=Point(*centre)))

    pookalam += smaller_thing
    for i in range(3):
        build_fractal(depth-1, [centre[0]+tancircle_dist*sin(2*pi/3*i), centre[1]+tancircle_dist*cos(2*pi/3*i)], leaf_radius*radius_ratio, ctr+1)
build_fractal(3, [0,0], leaf_radius, 1)

show(pookalam)