The finished product, a tribute to Bridget Riley's Rustle 6 (2015).
import math
canvasX = 600
canvasY = 600
nFrames = 120
side = canvasX/14
height = (side * sqrt(3))/2
center = (side/sqrt(3))/2
nX = 14
nY = 15
offsetX = side
offsetY = height
from datetime import *
time = datetime.now()
def interpolate(pt1, pt2, ratio):
    assert ratio <= 1
    return (pt1[0] + (ratio * (pt2[0] - pt1[0])), pt1[1] + (ratio * (pt2[1] - pt1[1])))
    
def triangle(s, h, c, inout):
    assert inout >= 0 and inout <= 1
    start = (-s/2, h/2)
    pivot = (0, h/2 - (c * 3))
    target = (s/2, h/2)
    control = interpolate((s/2, -c/2), (0, h/2 - c), inout)
    radius = s
    # optimal distance to control points is (4/3)*tan(pi/(2n); thank you https://stackoverflow.com/users/16673/suma for answering this question https://stackoverflow.com/questions/1734745/how-to-create-circle-with-b%C3%A9zier-curves
    control_ratio = ((4/3 * tan(pi/12)) * radius) / (2 * c)
    controlP = interpolate(pivot, control, control_ratio)
    controlT = interpolate(target, control, control_ratio)
    triangle = BezierPath()
    triangle.moveTo(start)
    triangle.lineTo(pivot)
    triangle.curveTo(controlP, controlT, target)
    triangle.closePath()
    drawPath(triangle)
def frame(x, y, m):
    f = BezierPath()
    g = BezierPath()
    f.rect(0, 0, x, y)
    g.rect(m, m, x - (2 * m), y - (2 * m))
    f = f.difference(g)
    f.closePath()
    drawPath(f)
# original Riley plan for Rustle 6    
plan = [
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 1, 0, 0, 0, -1, 0, 0, 0, 1, 0, 0],
    [-1, 0, -1, 0, 0, 0, 0, 0, 1, 0, -1, 0, 0],
    [1, 0, 1, -1, 0, 0, -1, 0, 0, 0, 1, 0, 1, 0],
    [0, -1, 0, -1, 0, 0, 0, 1, 0, -1, 0, -1, 0],
    [0, 1, -1, 1, 0, 1, 0, 0, 0, 0, -1, 1, 0, 0],
    [-1, 0, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, -1],
    [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0],
    [0, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, -1, 0],
    [0, -1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, -1, 0],
    [-1, 0, -1, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1],
    [-1, 0, -1, 0, 1, 0, 1, 0, 0, 0, 1, -1, -1, 0],
    [0, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, -1, -1, 0],
    [0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    ]
for n in range(nFrames):
    newPage(canvasX, canvasY)
    frameDuration(1/30)
    ex = sin(radians(n * (360/nFrames)))
    fill(1)
    rect(0, 0, canvasX, canvasY)
    with savedState():
        translate(canvasX/2, canvasY/2)
        fill(0)
        translate(-(nX - 1) * offsetX/2, (nY - 1) * offsetY/2)
        for i in range(nY):
            with savedState():
                translate(0, i * -offsetY)
                if i % 2 == 0:
                    for j in range(nX - 1):
                        with savedState():
                            translate(offsetX/2 + (j * offsetX), 0)
                            triangle(side, height, center, .5 - (ex * (plan[i][j] * .5)))
                else:
                    for j in range(nX):
                        with savedState():
                            translate(j * offsetX, 0)
                            triangle(side, height, center, .5 - (ex * (plan[i][j] * .5)))
    fill(1)
    frame(canvasX, canvasY, side/2)
saveImage('~/Desktop/riley_tribute_test_01_%s.gif' %time)
The reference image: