Creating Animations with Matplotlib
Matplotlib is the workhorse of Python visualization. With its animation module, you can create professional CAE visualizations that rotate, update, and reveal insights.
Setup
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from mpl_toolkits.mplot3d import Axes3D
Ensure you have a backend that supports animation saving:
pip install pillow # For GIF output
# or
pip install ffmpeg-python # For MP4/MOV output
Basic Animation Structure
# Create figure
fig = plt.figure(figsize=(8, 8), facecolor='black')
ax = fig.add_subplot(111, projection='3d', facecolor='black')
# Animation callback - called for each frame
def animate(frame):
ax.cla() # Clear previous frame
ax.view_init(elev=20, azim=frame * 3) # Rotate view
# Draw your visualization
# ...
return []
# Create animation object
anim = animation.FuncAnimation(
fig, # Figure to animate
animate, # Callback function
frames=120, # Number of frames
interval=50, # ms between frames
blit=False # Full redraw each frame
)
# Save
anim.save('output.gif', writer='pillow', fps=30)
# Or: anim.save('output.mp4', writer='ffmpeg', fps=30)
Full Example: Stress Tensor on Sphere
Here's a complete script for visualizing stress tensor components:
Srinithin now works at Xitadel as Design Engineer
Mechanical engineering graduate turned automotive designer
"""
Stress Tensor Visualization - Complete Tutorial
Automotive Application: B-pillar stress during NCAP side impact
"""
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
# =============================================================================
# CONFIGURATION
# =============================================================================
# Stress tensor (MPa) - typical B-pillar values
SIGMA = np.array([
[450, 75, 0],
[75, 280, 45],
[0, 45, 120]
])
RADIUS = 0.5
DPI = 150
FRAMES = 120
# =============================================================================
# HELPER FUNCTIONS
# =============================================================================
def create_sphere(radius, resolution=30):
"""Create sphere surface using parametric coordinates."""
u = np.linspace(0, 2*np.pi, resolution)
v = np.linspace(0, np.pi, resolution//2)
u, v = np.meshgrid(u, v)
x = radius * np.cos(u) * np.sin(v)
y = radius * np.sin(u) * np.sin(v)
z = radius * np.cos(v)
return x, y, z
def fibonacci_sphere(n_points, radius):
"""Generate evenly-distributed points on sphere."""
points = []
golden = (1 + np.sqrt(5)) / 2
for i in range(n_points):
theta = 2 * np.pi * i / golden
phi = np.arccos(1 - 2*(i+0.5)/n_points)
x = radius * np.sin(phi) * np.cos(theta)
y = radius * np.sin(phi) * np.sin(theta)
z = radius * np.cos(phi)
points.append([x, y, z])
return np.array(points)
def compute_traction(sigma, normal):
"""Cauchy's formula: t = sigma @ n"""
return sigma @ normal
# =============================================================================
# MAIN ANIMATION
# =============================================================================
# Create geometry
sphere_x, sphere_y, sphere_z = create_sphere(RADIUS)
sample_points = fibonacci_sphere(26, RADIUS)
normals = sample_points / np.linalg.norm(sample_points, axis=1, keepdims=True)
tractions = np.array([compute_traction(SIGMA, n) for n in normals])
# Scale vectors for visualization
traction_scale = 0.15 / np.max(np.linalg.norm(tractions, axis=1))
# Create figure
fig = plt.figure(figsize=(8, 8), dpi=DPI, facecolor='black')
ax = fig.add_subplot(111, projection='3d', facecolor='black')
def animate(frame):
ax.cla()
# Rotate view
ax.view_init(elev=20, azim=-60 + frame*3)
# Draw sphere surface
ax.plot_surface(
sphere_x, sphere_y, sphere_z,
alpha=0.2, color='white', linewidth=0
)
# Draw normal vectors (red)
for i in range(len(sample_points)):
ax.quiver(
sample_points[i,0], sample_points[i,1], sample_points[i,2],
normals[i,0]*0.15, normals[i,1]*0.15, normals[i,2]*0.15,
color='red', arrow_length_ratio=0.2
)
# Draw traction vectors (cyan)
for i in range(len(sample_points)):
ax.quiver(
sample_points[i,0], sample_points[i,1], sample_points[i,2],
tractions[i,0]*traction_scale,
tractions[i,1]*traction_scale,
tractions[i,2]*traction_scale,
color='cyan', arrow_length_ratio=0.15
)
# Axis settings
limit = RADIUS * 1.5
ax.set_xlim(-limit, limit)
ax.set_ylim(-limit, limit)
ax.set_zlim(-limit, limit)
ax.set_box_aspect([1, 1, 1])
ax.set_axis_off()
ax.set_title(
'Stress Tensor on Infinitesimal Sphere\nB-Pillar During Side Impact',
color='white', fontsize=12
)
return []
# Create and save animation
print("Creating animation...")
anim = animation.FuncAnimation(fig, animate, frames=FRAMES, interval=50)
anim.save('stress_sphere.gif', writer='pillow', fps=30)
print("Saved stress_sphere.gif")
plt.close()