Mixing with Pyctures

Pymations is built on Pyctures, Circuitry's Python drawing library, and the two work in one file: a moving picture behind the maths, a drawing inside a scene, or the mouse steering an animation.

A moving background

Write a Pyctures draw() function in the same file. It paints the background on every frame, and the animation plays on top:

from pymations import *

t = 0

def update(dt):
    global t
    t += dt

def draw():
    ctx.fill_style = "#0b1d2a"
    ctx.fill_rect(0, 0, width, height)
    ctx.fill_style = "rgba(255, 255, 255, 0.3)"
    for i in range(40):
        x = (i * 97 + t * 30) % width
        y = (i * 53) % height
        ctx.fill_rect(x, y, 2, 2)

eq = MathTex(r"e^{i\pi} + 1 = 0", font_size=96)
play(Write(eq), run_time=2)
play(Circumscribe(eq))
wait()
  • ctx, width and height are Pyctures' drawing surface and its size in pixels; update(dt) is called every frame with the time since the last one. See Drawing in 2D and How a program is shaped.
  • Tip: move things by the frame's dt (or read now(), the seconds since the run started), never by a fixed amount per frame such as t += 1/60. A recording at 30 frames a second would then play it at half speed.
  • To keep the background in step with the animation's timeline instead (so scrubbing moves it too), import pymations and read pymations.time(), the timeline's time in seconds. It is not part of from pymations import *, so Python's own time module stays usable.
  • A file with draw() keeps running after the animation ends, like any Pyctures program. Press Stop to end it; a recording of it also runs until you press Stop.

A drawing inside a scene

PycturesCanvas is a piece of Pyctures drawing that sits in the scene like a shape: it can be moved, scaled, faded and animated.

from pymations import *
import math

def waves(ctx, t):
    ctx.fill_style = "#102030"
    ctx.fill_rect(0, 0, 400, 300)
    ctx.stroke_style = "#58C4DD"
    ctx.line_width = 3
    ctx.begin_path()
    for x in range(0, 401, 4):
        y = 150 + 60 * math.sin(x / 40 + t * 3)
        ctx.line_to(x, y)
    ctx.stroke()

panel = PycturesCanvas(width=4, height=3, draw=waves)
caption = Text("a live drawing", font_size=32).next_to(panel, DOWN)

play(FadeIn(panel), Write(caption))
play(panel.animate.shift(LEFT * 3).scale(0.8), caption.animate.shift(LEFT * 3))
wait(2)

PycturesCanvas(width=4, height=3, draw=fn) is 4 by 3 picture units. fn(ctx, t) draws it on every frame, in pixels: 100 pixels to a unit, so this one is 400 by 300 (resolution= changes that). t is the animation's time.

The mouse steers the animation

In a file with a Pyctures update(dt) or draw(), setting a ValueTracker from the mouse changes the animation as you move. That makes explorables: sliders, draggable points, a graph you can push around.

from pymations import *

k = ValueTracker(1)
axes = Axes(x_range=[-3, 3, 1], y_range=[0, 9, 3], x_length=8, y_length=5, tips=False)
curve = axes.plot(lambda x: x * x, color=BLUE)
dot = always_redraw(lambda: Dot(axes.c2p(k.get_value(), k.get_value() ** 2), color=YELLOW))
hint = Text("Move the pointer left and right", font_size=28).to_edge(UP)

add(axes, curve, dot, hint)

def update(dt):
    k.set_value(-3 + 6 * mouse.x / width)

Move the pointer across the preview (or drag a finger) and the dot slides along the curve. mouse.x, mouse.y and mouse.down are Pyctures' pointer; see How a program is shaped.