Objetivo

La explicación ya funciona. En este capítulo le damos un final: cuando el punto completa la vuelta, la proyección se retira y la escena se queda quieta un momento con el círculo y la onda. Después comprobaremos el proyecto y lo exportaremos a video.

Cambios

Describir el archivo

Añade un docstring al principio de main.py. Es lo primero que leerá quien abra el archivo, incluido tú dentro de unos meses:

"""Del círculo al seno: el proyecto final del tutorial de Gaanim.

Un punto gira sobre un círculo y su altura dibuja una onda seno. Un único
parámetro, ``theta``, gobierna el punto, el radio, la onda y su proyección.
"""

Un cierre que respira

Al final de la zona # Línea de tiempo, sustituye scene.wait(1) por:

scene.play([projection.animate.fade_out().duration(0.5)])
scene.wait(1.5)

La línea de proyección era un andamio para explicar la relación entre los dos puntos. Al terminar la vuelta queda superpuesta a la recta, así que la retiramos con fade_out. La pausa final de 1.5 segundos da tiempo a mirar el resultado antes de que el video termine: una exportación acaba exactamente en el último instante de la línea de tiempo.

Archivo completo

Este es el programa terminado. Es el mismo archivo que se instala con Gaanim en _docs/tutorial/circulo_al_seno.py, dentro de la carpeta del paquete gaanim:

"""Del círculo al seno: el proyecto final del tutorial de Gaanim.

Un punto gira sobre un círculo y su altura dibuja una onda seno. Un único
parámetro, ``theta``, gobierna el punto, el radio, la onda y su proyección.
"""

import math

from gaanim import WHITE, YELLOW, Axis, Color, Easing, Scene, computed, stagger

# Paleta
BACKGROUND = Color(15, 23, 42)
PRIMARY = Color(96, 165, 250)
ACCENT = YELLOW
MUTED = Color(148, 163, 184)

scene = Scene(frame=(16, 9), background=BACKGROUND, margin=0.6)

CENTER = (-4.5, -1.0)
R = 1.5

# Texto
title = scene.text("Del círculo al seno", role="title")
title.fill(WHITE).move_to(0, 3.4)
caption = scene.text("Un punto que gira a radio constante", role="subtitle")
caption.fill(MUTED).move_to(0, 2.8)

formula = scene.text("$y = r sin(theta)$", role="subtitle").fill(WHITE)
explanation = scene.text("La altura del punto dibuja la onda.", role="body")
explanation.fill(MUTED)
panel = scene.layout.column([formula, explanation], gap=0.2, width=6.5)
panel.move_to(3.5, 1.6)

# Círculo
theta = scene.viz.parameter(0.0)
orbit = scene.geometry.circle(R).stroke(PRIMARY, 0.05).no_fill().move_to(*CENTER)
circle_ref = scene.geometry.polar_point(CENTER, R, theta)
point = scene.geometry.dot(0.125).fill(ACCENT).follow(circle_ref)
radius = scene.geometry.line(CENTER, point).stroke(MUTED, 0.025)
point.z_index(1)

# Recta y onda
axis = (
    Axis.linear(0, 2 * math.pi)
    .ticks(math.pi)
    .numbers("pi", denominator=1)
    .style(color=MUTED, number_color=MUTED)
)
number_line = scene.viz.number_line(axis, length=8)
number_line.drawable().move_to(2, CENTER[1])

wave = number_line.function(math.sin, normal_scale=R, reveal=theta)
wave.stroke(PRIMARY, 0.04).no_fill()

height = computed(lambda angle: R * math.sin(angle), inputs=[theta])
wave_dot = scene.geometry.dot(0.1).fill(ACCENT)
wave_dot.follow(number_line.point_ref(theta, normal_offset=height))
projection = scene.geometry.line(point, wave_dot).stroke(ACCENT, 0.025)

# Línea de tiempo
scene.play(stagger(
    title.animate.write().duration(0.8),
    caption.animate.fade_in().duration(0.5),
    each=0.5,
))
scene.play(stagger(
    orbit.animate.create().duration(0.8),
    radius.animate.create().duration(0.4),
    point.animate.grow_from_center().duration(0.5).easing(Easing.SNAPPY),
    each=0.25,
))
scene.play(stagger(
    formula.animate.write().duration(0.8),
    explanation.animate.write().duration(0.8),
    each=0.2,
))
scene.play(stagger(
    number_line.animate.create().duration(0.8),
    projection.animate.create().duration(0.4),
    wave_dot.animate.grow_from_center().duration(0.5).easing(Easing.SNAPPY),
    each=0.25,
))
scene.play([theta.animate.set(2 * math.pi).duration(4).easing(Easing.LINEAR)])
scene.play([projection.animate.fade_out().duration(0.5)])
scene.wait(1.5)
scene.render()
animation preview

Comprobar el proyecto

Desde la carpeta del proyecto:

gaanim check .

gaanim check ejecuta la escena sin abrir ventana e informa de su duración, en este caso 10 segundos. Si algo falla, muestra el error de Python con su línea. Con --strict también falla ante las advertencias.

Después abre la escena con gaanim . y revísala entera, también a saltos con la barra de reproducción:

  • Todo el texto queda dentro del margen de seguridad.
  • Ningún objeto aparece antes de su animación de entrada.
  • El radio, la proyección y la onda siguen al punto en cualquier instante.
  • Las pausas son lo bastante largas para leer la fórmula.

Exportar

Cuando la escena esté lista, expórtala sin tocar el script:

gaanim export . --output exports/del-circulo-al-seno.mp4

La extensión elige el formato: .mp4 y .webm para video, .webp y .gif para animaciones ligeras en una web o un documento, y .png para una secuencia de imágenes. Todos salvo PNG necesitan FFmpeg. --quality elige entre draft (rápido), standard (el valor por defecto) y production, y --from y --to exportan solo un tramo:

gaanim export . --output exports/vuelta.webp --from 4 --to 8

La referencia completa de estas opciones está en Línea de comandos.

Lo que has aprendido

  • Scene define un lienzo de unidades lógicas, con el origen en el centro y un margen de seguridad.
  • scene.text y scene.geometry crean objetos y devuelven handles; fill, stroke, no_fill y z_index les dan estilo y orden.
  • .animate, scene.play, stagger, duration, Easing y scene.wait construyen la línea de tiempo.
  • scene.layout.column organiza contenido que depende de su medida y pasa a ser el dueño de la posición de sus hijos.
  • Un Parameter animado con theta.animate.set(...) gobierna todo lo que depende de él: polar_point, follow, líneas con objetos como extremos y valores computed.
  • Axis, scene.viz.number_line, number_line.function(..., reveal=theta) y number_line.point_ref convierten el ángulo en una curva sin deriva.
  • gaanim check y gaanim export validan y producen la salida sin cambiar el script.

Cómo continuar

Modifica el proyecto antes de empezar otro. Guarda una copia y prueba, en este orden:

  1. Cambia R a 1.2: el círculo, el punto, la onda y la proyección se adaptan.
  2. Da dos vueltas: lleva el eje y la animación de theta hasta 4 * math.pi.
  3. Añade una segunda curva con number_line.function(math.cos, ...) y otro color.
  4. Anima el panel con otras entradas y easings de Movimiento.

Para ir más lejos, las guías resuelven tareas concretas: Layout para composiciones más ricas, Reactividad para rastros, valores calculados y simulaciones, Presentaciones para convertir la escena en diapositivas y Capturas y comparación para comprobar que un cambio no altera lo que se ve. Consulta la Referencia cuando tengas una pregunta concreta sobre una función.