OrbitalSimulator/animate.py
2025-06-02 23:08:35 -04:00

165 lines
5.3 KiB
Python

#!/usr/bin/env python3
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from collections import defaultdict
import argparse
import re
def parse_line(line):
"""Parse a line from the simulation output."""
# Skip comments and empty lines
if line.startswith('#') or not line.strip():
return None
# Parse the line format: body_name: X = x1m x2m x3m, V = v1m/s v2m/s v3m/s
try:
# Split on colon to separate name and data
name_part, data_part = line.strip().split(':')
name = name_part.strip()
# Split data part into position and velocity
pos_part, vel_part = data_part.split(',')
# Extract position values
pos_str = pos_part.split('=')[1].strip()
pos_values = [float(x.replace('m', '').strip()) for x in pos_str.split() if x.strip()]
if len(pos_values) != 3:
return None
return {
'name': name,
'position': tuple(pos_values)
}
except (ValueError, IndexError):
return None
def read_output_file(filename):
"""Read the simulation output file and organize data by body and time."""
positions = defaultdict(list)
times = [] # We'll use frame numbers as times since actual time isn't in output
with open(filename, 'r') as f:
frame = 0
for line in f:
data = parse_line(line)
if data is None:
continue
name = data['name']
pos = data['position']
# Store position
positions[name].append((pos[0], pos[1]))
frame += 1
times.append(frame)
return positions, times
def center_positions(positions, center_body):
"""Center all positions relative to the specified body."""
if center_body not in positions:
print(f"Warning: Center body '{center_body}' not found in simulation")
return positions
centered_positions = defaultdict(list)
for frame in range(len(next(iter(positions.values())))):
# Get center body position for this frame
center_pos = positions[center_body][frame]
# Shift all positions relative to center body
for name, pos_list in positions.items():
if frame < len(pos_list):
pos = pos_list[frame]
centered_pos = (pos[0] - center_pos[0], pos[1] - center_pos[1])
centered_positions[name].append(centered_pos)
return centered_positions
def create_animation(positions, times, output_file=None, center_body=None):
"""Create an animation of the bodies' orbits."""
# Check if we have any data
if not positions or not times:
print("Error: No valid data found in the input file")
return
# Center positions if requested
if center_body:
positions = center_positions(positions, center_body)
# Set up the figure and axis
fig, ax = plt.subplots(figsize=(10, 10))
# Set equal aspect ratio
ax.set_aspect('equal')
# Create scatter plots for each body
scatters = {}
for name in positions.keys():
scatters[name], = ax.plot([], [], 'o-', label=name, alpha=0.7)
# Set up the plot
ax.set_xlabel('X (m)')
ax.set_ylabel('Y (m)')
title = 'Orbital Simulation'
if center_body:
title += f' (centered on {center_body})'
ax.set_title(title)
ax.legend()
# Find the bounds of the plot
all_x = []
all_y = []
for pos_list in positions.values():
if pos_list: # Only process if we have positions
x, y = zip(*pos_list)
all_x.extend(x)
all_y.extend(y)
if not all_x or not all_y:
print("Error: No valid position data found")
return
max_range = max(max(abs(min(all_x)), abs(max(all_x))),
max(abs(min(all_y)), abs(max(all_y))))
ax.set_xlim(-max_range, max_range)
ax.set_ylim(-max_range, max_range)
def init():
"""Initialize the animation."""
for scatter in scatters.values():
scatter.set_data([], [])
return list(scatters.values())
def update(frame):
"""Update the animation for each frame."""
for name, scatter in scatters.items():
if positions[name]: # Only update if we have positions
x, y = zip(*positions[name][:frame+1])
scatter.set_data(x, y)
return list(scatters.values())
# Create the animation
anim = FuncAnimation(fig, update, frames=len(times),
init_func=init, blit=True,
interval=50) # 50ms between frames
if output_file:
anim.save(output_file, writer='ffmpeg', fps=30)
else:
plt.show()
def main():
parser = argparse.ArgumentParser(description='Animate orbital simulation output')
parser.add_argument('input_file', help='Input file from simulation')
parser.add_argument('--output', '-o', help='Output video file (optional)')
parser.add_argument('--center', '-c', help='Center the animation on this body')
args = parser.parse_args()
positions, times = read_output_file(args.input_file)
create_animation(positions, times, args.output, args.center)
if __name__ == '__main__':
main()