Compare commits

...

5 Commits

Author SHA1 Message Date
7df9de12ce Repository cleanup: Remove large files, cache files, sensitive data, and junk files
- Updated .gitignore with comprehensive patterns for Python, media files, and project data
- Removed all __pycache__ directories and .pyc files from tracking
- Removed large media files (PNG frames, MP4 videos) from git history
- Removed sensitive credential files (credentials.enc, key.key, server_settings.enc)
- Removed test files and temporary data from tracking
- Removed junk_files directory from tracking
- Repository size optimization and security improvement
2025-07-15 15:20:39 +03:00
9f8c1c27dc saved 2025-07-15 14:55:51 +03:00
1d0dc05a7b uploaded 2025-07-10 15:26:18 +03:00
911143dfc5 updated strategi 2025-07-10 13:50:44 +03:00
29fd68f732 updated to generate trip 2025-07-10 13:46:05 +03:00
41 changed files with 2262 additions and 25097 deletions

37
.gitignore vendored
View File

@@ -1,2 +1,39 @@
# Ignore the virtual environment folder # Ignore the virtual environment folder
track/ track/
# Ignore Python cache files
__pycache__/
*.pyc
*.pyo
*.pyd
.Python
# Ignore project data and generated files
resources/projects/
resources/trip_archive/
resources/credentials.enc
resources/key.key
resources/server_settings.enc
# Ignore generated videos and frames
*.mp4
*.avi
*.mov
*.webm
cinema_frames/
progressive_frames/
# Ignore test files and temporary files
test_*.py
*.tmp
*.log
# Ignore IDE files
.vscode/
.idea/
*.swp
*.swo
# Ignore OS files
.DS_Store
Thumbs.db

View File

@@ -1,45 +0,0 @@
# Project Cleanup Summary
## What Was Cleaned Up
### Moved to `junk_files/`
- Documentation files (*.md) that were cluttering the root directory
- `3D_VIDEO_DOCUMENTATION.md`
- `PAUSE_EDIT_IMPROVEMENTS.md`
- `PROJECT_MODERNIZATION_SUMMARY.md`
- `TEST_MODE_DOCUMENTATION.md`
### Removed
- All `__pycache__` directories and compiled Python bytecode files
- Duplicate and test files that were no longer needed
### Fixed
- Fixed typo in requirements.txt (`reqirements.txt` was corrected to `requirements.txt`)
- Ensured proper import structure (app uses `py_scripts.video_3d_generator` correctly)
## Current Clean Structure
```
traccar_animation/
├── .git/ # Git repository files
├── .gitignore # Git ignore rules
├── config.py # Application configuration
├── main.py # Main application entry point
├── traccar.kv # Kivy UI layout file
├── requirements.txt # Python dependencies (fixed)
├── py_scripts/ # Python modules
│ ├── __init__.py
│ ├── utils.py
│ ├── video_3d_generator.py
│ └── webview.py
├── screens/ # Kivy screen modules
├── resources/ # Application resources
├── track/ # Virtual environment
└── junk_files/ # Non-essential files moved here
```
## Verification
- ✅ Utils module imports correctly
- ✅ Video 3D generator module imports correctly
- ✅ No duplicate files remain
- ✅ All dependencies properly listed in requirements.txt
- ✅ Clean project structure maintained

Binary file not shown.

View File

@@ -1,41 +0,0 @@
#!/usr/bin/env python3
"""
Complete video generation from existing frames
"""
import os
import glob
from moviepy import ImageSequenceClip
def create_video_from_frames():
frames_folder = "/home/pi/Desktop/traccar_animation/resources/projects/day 2/frames"
output_path = "/home/pi/Desktop/traccar_animation/resources/projects/day 2/advanced_3d_animation.mp4"
# Get all frame files
frame_files = glob.glob(os.path.join(frames_folder, "frame_*.png"))
frame_files.sort() # Ensure correct order
if not frame_files:
print("No frames found!")
return
print(f"Found {len(frame_files)} frames")
print("Creating video...")
# Create video clip
clip = ImageSequenceClip(frame_files, fps=30)
# Write video file
clip.write_videofile(
output_path,
codec='libx264',
bitrate='8000k',
audio=False,
temp_audiofile=None,
remove_temp=True
)
print(f"Video created successfully: {output_path}")
return output_path
if __name__ == "__main__":
create_video_from_frames()

View File

@@ -1,45 +0,0 @@
# Project Cleanup Summary
## What Was Cleaned Up
### Moved to `junk_files/`
- Documentation files (*.md) that were cluttering the root directory
- `3D_VIDEO_DOCUMENTATION.md`
- `PAUSE_EDIT_IMPROVEMENTS.md`
- `PROJECT_MODERNIZATION_SUMMARY.md`
- `TEST_MODE_DOCUMENTATION.md`
### Removed
- All `__pycache__` directories and compiled Python bytecode files
- Duplicate and test files that were no longer needed
### Fixed
- Fixed typo in requirements.txt (`reqirements.txt` was corrected to `requirements.txt`)
- Ensured proper import structure (app uses `py_scripts.video_3d_generator` correctly)
## Current Clean Structure
```
traccar_animation/
├── .git/ # Git repository files
├── .gitignore # Git ignore rules
├── config.py # Application configuration
├── main.py # Main application entry point
├── traccar.kv # Kivy UI layout file
├── requirements.txt # Python dependencies (fixed)
├── py_scripts/ # Python modules
│ ├── __init__.py
│ ├── utils.py
│ ├── video_3d_generator.py
│ └── webview.py
├── screens/ # Kivy screen modules
├── resources/ # Application resources
├── track/ # Virtual environment
└── junk_files/ # Non-essential files moved here
```
## Verification
- ✅ Utils module imports correctly
- ✅ Video 3D generator module imports correctly
- ✅ No duplicate files remain
- ✅ All dependencies properly listed in requirements.txt
- ✅ Clean project structure maintained

View File

@@ -1,105 +0,0 @@
# Traccar Animation App - Modernization Complete
## Project Overview
The Traccar Animation App has been successfully modernized with enhanced 3D video animation capabilities, improved code structure, and streamlined codebase.
## Completed Modernization Tasks
### 1. Code Structure Cleanup ✅
- **Removed duplicate pause edit screens**: Deleted `pause_edit_screen.py` and `pause_edit_screen_legacy.py`
- **Single source of truth**: Only `pause_edit_screen_improved.py` remains
- **Organized utilities**: Moved utility modules to `py_scripts/` folder
- **Updated all imports**: All references updated to new module locations
### 2. Enhanced 3D Video Animation ✅
- **Google Earth-style camera**: Dynamic camera following with realistic perspective
- **Advanced visual effects**: Atmospheric perspective, terrain rendering, depth effects
- **Professional UI**: Enhanced information panels, compass, progress indicators
- **High-quality output**: 1920x1080 HD video at 30 FPS
### 3. Project Structure Improvements ✅
```
traccar_animation/
├── main.py # Main application entry
├── config.py # Configuration management
├── traccar.kv # UI layout definitions
├── reqirements.txt # Dependencies (fixed typo, added new deps)
├── py_scripts/ # Utility modules (new organization)
│ ├── utils.py # Core utilities
│ ├── video_3d_generator.py # Enhanced 3D video engine
│ ├── webview.py # Web integration
│ └── 3D_VIDEO_DOCUMENTATION.md # Technical documentation
├── screens/ # UI screen modules
│ ├── create_animation_screen.py
│ ├── get_trip_from_server.py
│ ├── home_screen.py
│ ├── login_screen.py
│ ├── pause_edit_screen_improved.py # Single pause edit implementation
│ └── settings_screen.py
└── resources/ # Static resources and data
├── images/
├── projects/
└── trip_archive/
```
### 4. Technical Enhancements ✅
- **Spectacular space entry sequence**: 3-second cinematic descent from 50km altitude
- **Optimized aerial camera system**: 1000-3000m height range for perfect aerial perspective
- **Enhanced Earth curvature rendering**: Realistic planetary view at high altitudes
- **Atmospheric transition effects**: Smooth space-to-atmosphere visual progression
- **Dynamic camera system**: Intelligent positioning and smooth transitions
- **Advanced 3D projection**: True perspective with depth-aware rendering
- **Enhanced terrain**: Multi-layer elevation with atmospheric effects
- **Professional UI elements**: Gradients, shadows, and cinematic effects
- **Optimized performance**: View frustum culling and efficient rendering
### 5. Documentation Updates ✅
- **Comprehensive 3D documentation**: Technical specifications and usage guide
- **Code comments**: Enhanced inline documentation
- **Requirements**: Updated and corrected dependency list
## Key Features
### Enhanced 3D Video Animation
- **Spectacular Space Entry**: 3-second cinematic descent from 50km altitude to route start
- **Google Earth-style flythrough**: Dynamic camera following route with look-ahead
- **Optimized Aerial Perspective**: Camera height range of 1000-3000m for perfect aerial views
- **Enhanced Visual Effects**: Earth curvature, atmospheric transitions, and space-to-sky gradients
- **Realistic terrain and atmospheric perspective**: Multi-layer terrain with atmospheric effects
- **Professional UI**: Speed, bearing, altitude, and progress indicators with gradients
- **High-definition output**: 1920x1080, 30 FPS with spectacular entry sequence
### Improved Pause Editing
- Single, comprehensive pause edit screen
- Intuitive interface for route modification
- Enhanced user experience
### Clean Architecture
- Modular code organization
- Clear separation of concerns
- Easy maintenance and extensibility
## Dependencies
All required packages are listed in `reqirements.txt`:
- Core: `kivy`, `kivy-garden`
- Animation: `opencv-python`, `moviepy`, `imageio`, `ffmpeg-python`
- Data processing: `numpy`, `matplotlib`, `scipy`
- Mapping: `folium`, `geopy`
- Security: `cryptography`
- Web integration: `selenium`, `requests`
- Image processing: `pillow`
## Verification Status
- ✅ All Python files compile without syntax errors
- ✅ All imports are correctly updated
- ✅ No duplicate or legacy code remains
- ✅ Documentation is comprehensive and up-to-date
- ✅ Project structure is clean and organized
## Usage
1. Install dependencies: `pip install -r reqirements.txt`
2. Run the application: `python main.py`
3. Use the enhanced 3D animation features for professional video output
4. Leverage the improved pause editing for precise route modifications
The Traccar Animation App is now fully modernized with a professional codebase, enhanced 3D video capabilities, and optimal project structure.

View File

@@ -1,108 +0,0 @@
# 3D Video Generation Test Mode
## Overview
The 3D video generation now supports two modes to balance quality and generation speed:
### 🏃‍♂️ 720p Test Mode (Fast)
- **Resolution**: 1280x720 pixels
- **Frame Rate**: 30 FPS
- **Entry Sequence**: 60 frames (2 seconds)
- **Route Frames**: 2x per GPS point
- **Generation Speed**: ~3x faster than production mode
- **File Size**: ~1/4 of production mode
- **Best For**: Quick previews, debugging routes, testing changes
### 🎯 2K Production Mode (High Quality)
- **Resolution**: 2560x1440 pixels (2K)
- **Frame Rate**: 60 FPS
- **Entry Sequence**: 120 frames (4 seconds)
- **Route Frames**: 3x per GPS point
- **Generation Speed**: Full quality processing
- **File Size**: Full size for maximum quality
- **Best For**: Final videos, presentations, high-quality output
## How to Use
### In the App UI
1. Click "Generate 3D Video" button
2. Choose from the popup:
- **"Generate 720p Test Video"** for fast testing
- **"Generate 2K Production Video"** for final quality
### In Code
```python
# Test mode (720p, faster)
generate_3d_video_animation(project_name, resources_folder, label, progress, popup, clock, test_mode=True)
# Production mode (2K, high quality)
generate_3d_video_animation(project_name, resources_folder, label, progress, popup, clock, test_mode=False)
# Or use convenience functions
generate_3d_video_animation_test_mode(...)
generate_3d_video_animation_production_mode(...)
```
## Performance Comparison
| Aspect | 720p Test Mode | 2K Production Mode |
|--------|----------------|-------------------|
| Resolution | 1280x720 | 2560x1440 |
| Total Pixels | ~0.9 megapixels | ~3.7 megapixels |
| Frame Rate | 30 FPS | 60 FPS |
| Space Entry | 2 seconds | 4 seconds |
| Processing Time | ~3x faster | Full quality |
| File Size | ~1/4 size | Full size |
| Quality | Good for preview | Cinema quality |
## When to Use Each Mode
### Use 720p Test Mode When:
- ✅ Testing route visualization
- ✅ Debugging GPS data issues
- ✅ Iterating on video parameters
- ✅ Quick previews for clients
- ✅ Development and testing
- ✅ Limited storage space
- ✅ Faster upload/sharing needed
### Use 2K Production Mode When:
- ✅ Creating final deliverable videos
- ✅ Professional presentations
- ✅ High-quality demos
- ✅ Maximum visual impact needed
- ✅ Detailed route analysis required
- ✅ Large screen display planned
## File Naming Convention
Generated videos will include the mode in the filename:
- Test mode: `project_720p_test_20250708_142815.mp4`
- Production mode: `project_2K_production_20250708_142815.mp4`
## Technical Details
### Test Mode Optimizations:
- Reduced frame generation (60 vs 120 for entry)
- Lower resolution reduces processing per frame
- Fewer intermediate frames per GPS point
- 30 FPS reduces total frame count
- Optimized rendering pipeline
### Production Mode Features:
- Ultra-high resolution Earth rendering
- Extended space entry sequence
- Maximum detail in atmospheric effects
- Professional-grade visual effects
- Cinema-quality color grading
- Smooth 60 FPS motion
## Tips for Best Results
1. **Start with Test Mode**: Always preview your route in 720p test mode first
2. **Iterate Quickly**: Use test mode to adjust route parameters
3. **Final Production**: Once satisfied, generate the 2K production version
4. **Storage Planning**: Test mode files are ~25% the size of production files
5. **Time Management**: Test mode generates ~3x faster than production mode
This dual-mode approach allows for rapid iteration during development while maintaining the ability to produce ultra-high-quality final videos.

View File

@@ -34,44 +34,45 @@ except ImportError:
print("Warning: pydeck not available. Install with: pip install pydeck") print("Warning: pydeck not available. Install with: pip install pydeck")
try: try:
from moviepy import VideoFileClip, ImageSequenceClip import cv2
MOVIEPY_AVAILABLE = True OPENCV_AVAILABLE = True
except ImportError: except ImportError:
MOVIEPY_AVAILABLE = False OPENCV_AVAILABLE = False
print("Warning: moviepy not available. Install with: pip install moviepy") print("Warning: opencv-python not available. Install with: pip install opencv-python")
class Advanced3DGenerator: class NavigationAnimationGenerator:
""" """
Advanced 3D animation generator using Pydeck + Plotly + Blender pipeline Professional navigation animation generator with satellite view and 3D camera following
for high-quality GPS track visualizations Creates Google Earth-style entry scene and detailed terrain navigation
""" """
def __init__(self, output_folder): def __init__(self, output_folder):
self.output_folder = output_folder self.output_folder = output_folder
self.frames_folder = os.path.join(output_folder, "frames") self.frames_folder = os.path.join(output_folder, "nav_frames")
self.temp_folder = os.path.join(output_folder, "temp") self.temp_folder = os.path.join(output_folder, "temp")
# Create necessary folders # Create necessary folders
os.makedirs(self.frames_folder, exist_ok=True) os.makedirs(self.frames_folder, exist_ok=True)
os.makedirs(self.temp_folder, exist_ok=True) os.makedirs(self.temp_folder, exist_ok=True)
# Animation settings # Navigation animation settings
self.fps = 30 self.fps = 30
self.duration_per_point = 0.5 # seconds per GPS point self.entry_duration = 4 # seconds for Google Earth entry
self.camera_height = 1000 # meters self.camera_height_min = 1000 # meters
self.trail_length = 50 # number of previous points to show self.camera_height_max = 2000 # meters
self.follow_distance = 500 # meters behind navigation point
def check_dependencies(self): def check_dependencies(self):
"""Check if all required dependencies are available""" """Check if all required dependencies are available"""
missing = [] missing = []
if not PANDAS_AVAILABLE: if not PANDAS_AVAILABLE:
missing.append("pandas, geopandas") missing.append("pandas geopandas")
if not PLOTLY_AVAILABLE: if not PLOTLY_AVAILABLE:
missing.append("plotly") missing.append("plotly")
if not PYDECK_AVAILABLE: if not PYDECK_AVAILABLE:
missing.append("pydeck") missing.append("pydeck")
if not MOVIEPY_AVAILABLE: if not OPENCV_AVAILABLE:
missing.append("moviepy") missing.append("opencv-python")
if missing: if missing:
raise ImportError(f"Missing required dependencies: {', '.join(missing)}. Please install them with: pip install {' '.join(missing)}") raise ImportError(f"Missing required dependencies: {', '.join(missing)}. Please install them with: pip install {' '.join(missing)}")
@@ -79,7 +80,7 @@ class Advanced3DGenerator:
return True return True
def load_gps_data(self, positions_file): def load_gps_data(self, positions_file):
"""Load and preprocess GPS data""" """Load and preprocess GPS data for navigation"""
with open(positions_file, 'r') as f: with open(positions_file, 'r') as f:
positions = json.load(f) positions = json.load(f)
@@ -92,11 +93,360 @@ class Advanced3DGenerator:
# Calculate speed and bearing # Calculate speed and bearing
df['speed_kmh'] = df['speed'] * 1.852 # Convert knots to km/h df['speed_kmh'] = df['speed'] * 1.852 # Convert knots to km/h
df['elevation'] = df.get('altitude', 0) df['elevation'] = df.get('altitude', 100)
# Calculate distance between points # Calculate bearings and distances
distances = []
bearings = [] bearings = []
distances = []
for i in range(len(df)):
if i == 0:
bearings.append(0)
distances.append(0)
else:
# Calculate bearing to next point
lat1, lon1 = df.iloc[i-1]['latitude'], df.iloc[i-1]['longitude']
lat2, lon2 = df.iloc[i]['latitude'], df.iloc[i]['longitude']
bearing = self.calculate_bearing(lat1, lon1, lat2, lon2)
distance = geodesic((lat1, lon1), (lat2, lon2)).meters
bearings.append(bearing)
distances.append(distance)
df['bearing'] = bearings
df['distance'] = distances
return df
def calculate_bearing(self, lat1, lon1, lat2, lon2):
"""Calculate bearing between two GPS points"""
lat1, lon1, lat2, lon2 = map(math.radians, [lat1, lon1, lat2, lon2])
dlon = lon2 - lon1
y = math.sin(dlon) * math.cos(lat2)
x = math.cos(lat1) * math.sin(lat2) - math.sin(lat1) * math.cos(lat2) * math.cos(dlon)
bearing = math.atan2(y, x)
bearing = math.degrees(bearing)
bearing = (bearing + 360) % 360
return bearing
def create_google_earth_entry_scene(self, df, frame_num):
"""Create Google Earth-style entry scene (zooming in from space)"""
if not PLOTLY_AVAILABLE:
raise ImportError("Plotly is required for entry scene")
# Get route bounds
center_lat = df['latitude'].mean()
center_lon = df['longitude'].mean()
# Entry animation: start from very high altitude and zoom in
total_entry_frames = self.entry_duration * self.fps
zoom_progress = frame_num / total_entry_frames
# Camera altitude decreases exponentially
start_altitude = 50000 # Start from 50km
end_altitude = 3000 # End at 3km
current_altitude = start_altitude * (1 - zoom_progress) + end_altitude * zoom_progress
# Create figure
fig = go.Figure()
# Add Earth-like surface with satellite imagery simulation
terrain_size = 0.5 - (0.4 * zoom_progress) # Zoom in effect
resolution = int(30 + (50 * zoom_progress)) # More detail as we zoom
lat_range = np.linspace(center_lat - terrain_size, center_lat + terrain_size, resolution)
lon_range = np.linspace(center_lon - terrain_size, center_lon + terrain_size, resolution)
lat_mesh, lon_mesh = np.meshgrid(lat_range, lon_range)
# Generate satellite-like terrain
terrain_heights = self.generate_satellite_terrain(lat_mesh, lon_mesh, center_lat, center_lon)
# Add terrain surface
fig.add_trace(
go.Surface(
x=lon_mesh,
y=lat_mesh,
z=terrain_heights,
colorscale=[
[0.0, 'rgb(0,100,0)'], # Deep green (forests)
[0.2, 'rgb(34,139,34)'], # Forest green
[0.4, 'rgb(255,215,0)'], # Gold (fields)
[0.6, 'rgb(139,69,19)'], # Brown (earth)
[0.8, 'rgb(105,105,105)'], # Gray (rock)
[1.0, 'rgb(255,255,255)'] # White (snow)
],
opacity=0.95,
showscale=False,
lighting=dict(
ambient=0.4,
diffuse=0.8,
specular=0.2
)
)
)
# Show partial route (fading in)
route_alpha = min(1.0, zoom_progress * 2)
if route_alpha > 0:
fig.add_trace(
go.Scatter3d(
x=df['longitude'],
y=df['latitude'],
z=df['elevation'] + 100,
mode='lines',
line=dict(
color='red',
width=8,
),
opacity=route_alpha,
name='Route'
)
)
# Camera position for entry effect
camera_distance = current_altitude / 10000
fig.update_layout(
title=dict(
text=f'Navigation Overview - Approaching Destination',
x=0.5,
font=dict(size=28, color='white', family="Arial Black")
),
scene=dict(
camera=dict(
eye=dict(x=0, y=-camera_distance, z=camera_distance),
center=dict(x=0, y=0, z=0),
up=dict(x=0, y=0, z=1)
),
xaxis=dict(visible=False),
yaxis=dict(visible=False),
zaxis=dict(visible=False),
aspectmode='cube',
bgcolor='rgb(0,0,50)', # Space-like background
),
paper_bgcolor='black',
showlegend=False,
width=1920,
height=1080,
margin=dict(l=0, r=0, t=60, b=0)
)
# Save frame
frame_path = os.path.join(self.frames_folder, f"NavEntry_{frame_num:04d}.png")
fig.write_image(frame_path, engine="kaleido")
return frame_path
def create_navigation_frame(self, df, current_index, frame_num):
"""Create detailed navigation frame with 3D following camera"""
if not PLOTLY_AVAILABLE:
raise ImportError("Plotly is required for navigation frames")
current_row = df.iloc[current_index]
current_lat = current_row['latitude']
current_lon = current_row['longitude']
current_alt = current_row['elevation']
current_speed = current_row['speed_kmh']
current_bearing = current_row['bearing']
# Get route progress
completed_route = df.iloc[:current_index + 1]
remaining_route = df.iloc[current_index:]
# Create detailed terrain around current position
terrain_radius = 0.01 # degrees around current position
resolution = 60
lat_range = np.linspace(current_lat - terrain_radius, current_lat + terrain_radius, resolution)
lon_range = np.linspace(current_lon - terrain_radius, current_lon + terrain_radius, resolution)
lat_mesh, lon_mesh = np.meshgrid(lat_range, lon_range)
# Generate high-detail satellite terrain
terrain_heights = self.generate_detailed_terrain(lat_mesh, lon_mesh, current_lat, current_lon)
# Create navigation display figure
fig = go.Figure()
# Add detailed terrain
fig.add_trace(
go.Surface(
x=lon_mesh,
y=lat_mesh,
z=terrain_heights,
colorscale=[
[0.0, 'rgb(34,139,34)'], # Forest green
[0.2, 'rgb(107,142,35)'], # Olive drab
[0.4, 'rgb(255,215,0)'], # Gold (fields)
[0.5, 'rgb(210,180,140)'], # Tan (roads/clearings)
[0.7, 'rgb(139,69,19)'], # Brown (earth)
[0.9, 'rgb(105,105,105)'], # Gray (rock)
[1.0, 'rgb(255,255,255)'] # White (peaks)
],
opacity=0.9,
showscale=False,
lighting=dict(
ambient=0.3,
diffuse=0.9,
specular=0.1
)
)
)
# Add completed route (green)
if len(completed_route) > 1:
fig.add_trace(
go.Scatter3d(
x=completed_route['longitude'],
y=completed_route['latitude'],
z=completed_route['elevation'] + 50,
mode='lines',
line=dict(color='lime', width=12),
name='Completed'
)
)
# Add remaining route (blue, semi-transparent)
if len(remaining_route) > 1:
fig.add_trace(
go.Scatter3d(
x=remaining_route['longitude'],
y=remaining_route['latitude'],
z=remaining_route['elevation'] + 50,
mode='lines',
line=dict(color='cyan', width=8),
opacity=0.6,
name='Remaining'
)
)
# Add navigation point (current vehicle position)
fig.add_trace(
go.Scatter3d(
x=[current_lon],
y=[current_lat],
z=[current_alt + 100],
mode='markers',
marker=dict(
color='red',
size=25,
symbol='diamond',
line=dict(color='white', width=4)
),
name='Vehicle'
)
)
# Add direction indicator
bearing_rad = math.radians(current_bearing)
arrow_length = 0.002
arrow_end_lat = current_lat + arrow_length * math.cos(bearing_rad)
arrow_end_lon = current_lon + arrow_length * math.sin(bearing_rad)
fig.add_trace(
go.Scatter3d(
x=[current_lon, arrow_end_lon],
y=[current_lat, arrow_end_lat],
z=[current_alt + 120, current_alt + 120],
mode='lines',
line=dict(color='yellow', width=15),
name='Direction'
)
)
# Calculate dynamic camera position for 3D following
# Camera follows behind and above at specified height
camera_height = self.camera_height_min + (self.camera_height_max - self.camera_height_min) * (current_speed / 100)
follow_distance_deg = 0.005 # degrees behind vehicle
# Position camera behind vehicle based on bearing
camera_bearing = (current_bearing + 180) % 360 # Opposite direction
camera_bearing_rad = math.radians(camera_bearing)
camera_lat = current_lat + follow_distance_deg * math.cos(camera_bearing_rad)
camera_lon = current_lon + follow_distance_deg * math.sin(camera_bearing_rad)
# Calculate relative camera position
camera_eye_x = (camera_lon - current_lon) * 100
camera_eye_y = (camera_lat - current_lat) * 100
camera_eye_z = camera_height / 1000
# Navigation info overlay
total_distance = sum(df['distance'])
completed_distance = sum(completed_route['distance'])
progress_percent = (completed_distance / total_distance) * 100 if total_distance > 0 else 0
fig.update_layout(
title=dict(
text=f'Navigation • Speed: {current_speed:.1f} km/h • Progress: {progress_percent:.1f}% • {current_row["timestamp"].strftime("%H:%M:%S")}',
x=0.5,
font=dict(size=20, color='white', family="Arial Black")
),
scene=dict(
camera=dict(
eye=dict(x=camera_eye_x, y=camera_eye_y, z=camera_eye_z),
center=dict(x=0, y=0, z=0),
up=dict(x=0, y=0, z=1)
),
xaxis=dict(visible=False),
yaxis=dict(visible=False),
zaxis=dict(visible=False),
aspectmode='manual',
aspectratio=dict(x=1, y=1, z=0.3),
bgcolor='rgb(135,206,235)' # Sky blue
),
paper_bgcolor='black',
showlegend=False,
width=1920,
height=1080,
margin=dict(l=0, r=0, t=50, b=0)
)
# Save frame
frame_path = os.path.join(self.frames_folder, f"Navigation_{frame_num:04d}.png")
fig.write_image(frame_path, engine="kaleido")
return frame_path
def generate_satellite_terrain(self, lat_mesh, lon_mesh, center_lat, center_lon):
"""Generate satellite-view realistic terrain for entry scene"""
# Convert to local coordinates
lat_m = (lat_mesh - center_lat) * 111000
lon_m = (lon_mesh - center_lon) * 111000 * np.cos(np.radians(center_lat))
# Base elevation with realistic variation
base_height = 200 + 50 * np.sin(lat_m / 5000) * np.cos(lon_m / 3000)
# Mountain ranges
mountains = 800 * np.exp(-((lat_m - 2000)**2 + (lon_m - 1000)**2) / (3000**2))
mountains += 600 * np.exp(-((lat_m + 1500)**2 + (lon_m + 2000)**2) / (2500**2))
# Hills and valleys
hills = 200 * np.sin(lat_m / 1000) * np.cos(lon_m / 1200)
valleys = -100 * np.exp(-((lat_m)**2 + (lon_m)**2) / (2000**2))
terrain = base_height + mountains + hills + valleys
return np.maximum(terrain, 50)
def generate_detailed_terrain(self, lat_mesh, lon_mesh, center_lat, center_lon):
"""Generate high-detail terrain for navigation view"""
# Convert to local coordinates
lat_m = (lat_mesh - center_lat) * 111000
lon_m = (lon_mesh - center_lon) * 111000 * np.cos(np.radians(center_lat))
# Base terrain
base = 150 + 30 * np.sin(lat_m / 500) * np.cos(lon_m / 400)
# Local features
hills = 100 * np.exp(-((lat_m - 300)**2 + (lon_m - 200)**2) / (200**2))
ridges = 80 * np.exp(-((lat_m + 200)**2 + (lon_m - 400)**2) / (300**2))
# Fine detail
detail = 20 * np.sin(lat_m / 50) * np.cos(lon_m / 60)
terrain = base + hills + ridges + detail
return np.maximum(terrain, 30)
for i in range(len(df)): for i in range(len(df)):
if i == 0: if i == 0:
@@ -557,14 +907,16 @@ class Advanced3DGenerator:
return frame_paths return frame_paths
def create_video(self, frame_paths, output_video_path, progress_callback=None): def create_video(self, frame_paths, output_video_path, progress_callback=None):
"""Create video from frames using MoviePy with optimized settings""" """Create video from frames using OpenCV for better compatibility"""
print("Creating Relive-style animation video...") print("Creating navigation animation video...")
if not frame_paths: if not frame_paths:
print("No frames to create video from") print("No frames to create video from")
return False return False
try: try:
import cv2
# Filter out None paths # Filter out None paths
valid_frames = [f for f in frame_paths if f and os.path.exists(f)] valid_frames = [f for f in frame_paths if f and os.path.exists(f)]
@@ -574,46 +926,67 @@ class Advanced3DGenerator:
print(f"Creating video from {len(valid_frames)} frames at {self.fps} FPS...") print(f"Creating video from {len(valid_frames)} frames at {self.fps} FPS...")
# Create video clip from images with optimal settings # Update progress
clip = ImageSequenceClip(valid_frames, fps=self.fps) if progress_callback:
progress_callback(30, "Reading frame dimensions...")
# Add smooth fade effects for professional look # Read first frame to get dimensions
clip = clip.fadein(0.5).fadeout(0.5) first_frame = cv2.imread(valid_frames[0])
if first_frame is None:
print("Error reading first frame")
return False
height, width, layers = first_frame.shape
# Update progress # Update progress
if progress_callback: if progress_callback:
progress_callback(50, "Encoding video with optimized settings...") progress_callback(40, "Setting up video encoder...")
# Write video file with high quality settings # Create video writer with OpenCV
clip.write_videofile( fourcc = cv2.VideoWriter_fourcc(*'mp4v') # You can also try 'XVID'
output_video_path, video_writer = cv2.VideoWriter(output_video_path, fourcc, self.fps, (width, height))
codec='libx264',
audio=False, if not video_writer.isOpened():
temp_audiofile=None, print("Error: Could not open video writer")
remove_temp=True, return False
verbose=False,
logger=None, # Update progress
bitrate="8000k", # High quality bitrate if progress_callback:
ffmpeg_params=[ progress_callback(50, "Writing frames to video...")
"-preset", "medium", # Balance between speed and compression
"-crf", "18", # High quality (lower = better quality) # Write frames to video
"-pix_fmt", "yuv420p" # Better compatibility total_frames = len(valid_frames)
] for i, frame_path in enumerate(valid_frames):
) frame = cv2.imread(frame_path)
if frame is not None:
video_writer.write(frame)
# Update progress periodically
if progress_callback and i % 10 == 0:
progress_percent = 50 + (i / total_frames) * 40 # 50-90%
progress_callback(progress_percent, f"Writing frame {i+1}/{total_frames}...")
# Clean up
video_writer.release()
cv2.destroyAllWindows()
if progress_callback: if progress_callback:
progress_callback(100, f"Video successfully created: {os.path.basename(output_video_path)}") progress_callback(100, f"Video successfully created: {os.path.basename(output_video_path)}")
print(f"✅ Relive-style animation video saved to: {output_video_path}") # Verify the video was created
print(f"📊 Video info: {len(valid_frames)} frames, {self.fps} FPS, {clip.duration:.1f}s duration") if os.path.exists(output_video_path) and os.path.getsize(output_video_path) > 0:
print(f"✅ Navigation animation video saved to: {output_video_path}")
# Clean up clip file_size = os.path.getsize(output_video_path) / (1024 * 1024) # MB
clip.close() print(f"📊 Video info: {len(valid_frames)} frames, {self.fps} FPS, {file_size:.1f} MB")
return True return True
else:
print("Error: Video file was not created properly")
return False
except Exception as e: except Exception as e:
print(f"Error creating video: {e}") print(f"Error creating video: {e}")
if progress_callback:
progress_callback(-1, f"Error: {e}")
return False return False
def cleanup_frames(self): def cleanup_frames(self):
@@ -623,38 +996,7 @@ class Advanced3DGenerator:
shutil.rmtree(self.frames_folder) shutil.rmtree(self.frames_folder)
os.makedirs(self.frames_folder, exist_ok=True) os.makedirs(self.frames_folder, exist_ok=True)
def generate_3d_animation(self, positions_file, output_video_path,
style='advanced', cleanup=True, progress_callback=None):
"""
Main method to generate 3D animation
Args:
positions_file: Path to JSON file with GPS positions
output_video_path: Path for output video
style: 'pydeck', 'plotly', or 'advanced'
cleanup: Whether to clean up temporary files
progress_callback: Callback function for progress updates
"""
try:
# Generate frames
frame_paths = self.generate_frames(positions_file, style, progress_callback)
if not frame_paths:
raise Exception("No frames generated")
# Create video
success = self.create_video(frame_paths, output_video_path, progress_callback)
if cleanup:
self.cleanup_frames()
return success
except Exception as e:
print(f"Error generating 3D animation: {e}")
if progress_callback:
progress_callback(-1, f"Error: {e}")
return False
def create_google_earth_frame(self, df, current_index, frame_num): def create_google_earth_frame(self, df, current_index, frame_num):
""" """
@@ -887,42 +1229,4 @@ class Advanced3DGenerator:
return terrain return terrain
def generate_advanced_3d_video(positions_file, output_folder, filename_prefix="advanced_3d",
style='advanced', progress_callback=None):
"""
Convenience function to generate advanced 3D video
"""
generator = Advanced3DGenerator(output_folder)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_video_path = os.path.join(output_folder, f"{filename_prefix}_{timestamp}.mp4")
success = generator.generate_3d_animation(
positions_file,
output_video_path,
style=style,
progress_callback=progress_callback
)
return output_video_path if success else None
# Test function
if __name__ == "__main__":
# Test the advanced 3D generator
test_positions = "test_positions.json"
output_dir = "test_output"
def test_progress(progress, message):
print(f"Progress: {progress:.1f}% - {message}")
video_path = generate_advanced_3d_video(
test_positions,
output_dir,
style='advanced',
progress_callback=test_progress
)
if video_path:
print(f"Test video created: {video_path}")
else:
print("Test failed")

View File

@@ -24,7 +24,8 @@ class BlenderGPSAnimator:
if BLENDER_AVAILABLE: if BLENDER_AVAILABLE:
self.setup_blender_scene() self.setup_blender_scene()
else: else:
raise ImportError("Blender (bpy) is not available. Please install Blender with Python API access.") # Don't raise error here, let the caller handle the check
pass
def check_dependencies(self): def check_dependencies(self):
"""Check if Blender dependencies are available""" """Check if Blender dependencies are available"""

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,6 @@ selenium
pillow pillow
geopy geopy
opencv-python opencv-python
moviepy
requests requests
numpy numpy
matplotlib matplotlib

View File

@@ -1 +0,0 @@
gAAAAABobmx0PnGbcR3Hxn93Z2r3z0dqZpHYGfWhJC7ko6QSMHLY_qoGsEZLrlLjjGrdjVOqSNVfwCP6_pAQ5QWbDRs6RoyZFPIA-vLFYpU9tUVC6pHCSSxvQimS_Thdj5WMIBlpTOWa

View File

@@ -1 +0,0 @@
wetp_PNG9CC5432-W9H3rUbaqIurwldZxHlOgori5kY=

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

View File

@@ -1,20 +0,0 @@
[
{
"start_time": "2025-07-08T04:51:13.000+00:00",
"end_time": "2025-07-08T13:20:50.000+00:00",
"duration_seconds": 30578,
"location": {
"latitude": 45.79908722222223,
"longitude": 24.085938333333335
}
},
{
"start_time": "2025-07-08T13:33:15.000+00:00",
"end_time": "2025-07-08T13:35:59.000+00:00",
"duration_seconds": 164,
"location": {
"latitude": 45.794045000000004,
"longitude": 24.13890055555556
}
}
]

File diff suppressed because it is too large Load Diff

View File

@@ -1 +0,0 @@
gAAAAABobK2fcNGeWyfPJzYnOl_HWl8TdQfRDb5teUXH9Kpjmme0TUVA3Dy7wm2MuMEGsPBTWBm8XfaX8daIwu6iDV6o8G07XZ_A0RoMqx3xWiYUbX63ovYy8qITIpMqbt0dayYigDSPmdr_8pcqko6ik-ctfdg4SkGH1gRXb5yuacnzezLr3KcHMh833PkbTO6WiUYPCwaivEMTVHUxL5YORiLRGu4E3lS_WDPo7kv53khtUI9b7vWJOOUFXcelM2vF3iHI3EkXCWrO2Qpm22nC44b-yCnZvYzx7g-WHZDNfG6CA1KXbcyhouxR4b7502iofpEAN5sizLFuyOWIOBdVphblIkRd1qdq6fVmt0IMeoaMpNPNuDKJqMDLuAU05wXDWbGXei6YU6rs6YJgpGOfNdv8A_sKKJBrh5QVE2kZ2GE0Ysqpnw2Yfj_jsMBpdh-bBs6UDwcI

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,633 @@
import kivy
from kivy.uix.screenmanager import Screen
import os
import json
import math
from datetime import datetime
from kivy.clock import Clock
from kivy.properties import StringProperty, NumericProperty, AliasProperty
from py_scripts.utils import (
process_preview_util, optimize_route_entries_util
)
from py_scripts.advanced_3d_generator import NavigationAnimationGenerator
from py_scripts.blender_animator import BlenderGPSAnimator
from kivy.uix.popup import Popup
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.progressbar import ProgressBar
from kivy.uix.textinput import TextInput
from config import RESOURCES_FOLDER
class CreateAnimationScreen(Screen):
project_name = StringProperty("")
preview_html_path = StringProperty("") # Path to the HTML file for preview
preview_image_path = StringProperty("") # Add this line
preview_image_version = NumericProperty(0) # Add this line
def get_preview_image_source(self):
project_folder = os.path.join(RESOURCES_FOLDER, "projects", self.project_name)
img_path = os.path.join(project_folder, "preview.png")
if os.path.exists(img_path):
return img_path
return "resources/images/track.png"
preview_image_source = AliasProperty(
get_preview_image_source, None, bind=['project_name', 'preview_image_version']
)
def on_pre_enter(self):
# Update the route entries label with the actual number of entries
project_folder = os.path.join(RESOURCES_FOLDER, "projects", self.project_name)
positions_path = os.path.join(project_folder, "positions.json")
count = 0
if os.path.exists(positions_path):
with open(positions_path, "r") as f:
try:
positions = json.load(f)
count = len(positions)
except Exception:
count = 0
self.ids.route_entries_label.text = f"Your route has {count} entries,"
def open_rename_popup(self):
from kivy.uix.popup import Popup
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.label import Label
layout = BoxLayout(orientation='vertical', spacing=10, padding=10)
label = Label(text="Enter new project name:")
input_field = TextInput(text=self.project_name, multiline=False)
btn_save = Button(text="Save", background_color=(0.008, 0.525, 0.290, 1))
btn_cancel = Button(text="Cancel")
layout.add_widget(label)
layout.add_widget(input_field)
layout.add_widget(btn_save)
layout.add_widget(btn_cancel)
popup = Popup(
title="Rename Project",
content=layout,
size_hint=(0.92, None),
size=(0, 260),
auto_dismiss=False
)
def do_rename(instance):
new_name = input_field.text.strip()
if new_name and new_name != self.project_name:
if self.rename_project_folder(self.project_name, new_name):
self.project_name = new_name
popup.dismiss()
self.on_pre_enter() # Refresh label
else:
label.text = "Rename failed (name exists?)"
else:
label.text = "Please enter a new name."
btn_save.bind(on_press=do_rename)
btn_cancel.bind(on_press=lambda x: popup.dismiss())
popup.open()
def rename_project_folder(self, old_name, new_name):
import os
old_path = os.path.join(RESOURCES_FOLDER, "projects", old_name)
new_path = os.path.join(RESOURCES_FOLDER, "projects", new_name)
if os.path.exists(old_path) and not os.path.exists(new_path):
os.rename(old_path, new_path)
return True
return False
def optimize_route_entries(self):
# Create the popup and UI elements
layout = BoxLayout(orientation='vertical', spacing=10, padding=10)
label = Label(text="Processing route entries...")
progress = ProgressBar(max=100, value=0)
layout.add_widget(label)
layout.add_widget(progress)
popup = Popup(
title="Optimizing Route",
content=layout,
size_hint=(0.92, None),
size=(0, 260),
auto_dismiss=False
)
popup.open()
# Now call the utility function with these objects
optimize_route_entries_util(
self.project_name,
RESOURCES_FOLDER,
label,
progress,
popup,
Clock,
on_save=lambda: self.on_pre_enter()
)
def preview_route(self):
# Show processing popup
layout = BoxLayout(orientation='vertical', spacing=10, padding=10)
label = Label(text="Processing route preview...")
progress = ProgressBar(max=100, value=0)
layout.add_widget(label)
layout.add_widget(progress)
popup = Popup(
title="Previewing Route",
content=layout,
size_hint=(0.8, None),
size=(0, 180),
auto_dismiss=False
)
popup.open()
def set_preview_image_path(path):
self.preview_image_path = path
self.preview_image_version += 1 # Force AliasProperty to update
self.property('preview_image_source').dispatch(self)
self.ids.preview_image.reload()
# Schedule the processing function
Clock.schedule_once(
lambda dt: process_preview_util(
self.project_name,
RESOURCES_FOLDER,
label,
progress,
popup,
self.ids.preview_image,
set_preview_image_path,
Clock
),
0.5
)
def open_pauses_popup(self):
"""Navigate to the pause edit screen"""
pause_edit_screen = self.manager.get_screen("pause_edit")
pause_edit_screen.set_project_and_callback(self.project_name, self.on_pre_enter)
self.manager.current = "pause_edit"
def show_video_generation_options(self):
"""Show popup with video generation mode options including new advanced animations"""
from kivy.uix.popup import Popup
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
layout = BoxLayout(orientation='vertical', spacing=12, padding=15)
# Title
title_label = Label(
text="Choose Animation Style & Quality",
font_size=20,
size_hint_y=None,
height=40,
color=(1, 1, 1, 1)
)
layout.add_widget(title_label)
# Classic 3D Mode
classic_layout = BoxLayout(orientation='vertical', spacing=5)
classic_title = Label(
text="🏃‍♂️ Classic 3D (Original Pipeline)",
font_size=16,
size_hint_y=None,
height=30,
color=(0.2, 0.8, 0.2, 1)
)
classic_desc = Label(
text="• Traditional OpenCV/PIL approach\n• Fast generation\n• Good for simple tracks\n• Test (720p) or Production (2K)",
font_size=11,
size_hint_y=None,
height=70,
color=(0.9, 0.9, 0.9, 1),
halign="left",
valign="middle"
)
classic_desc.text_size = (None, None)
classic_layout.add_widget(classic_title)
classic_layout.add_widget(classic_desc)
layout.add_widget(classic_layout)
# Classic buttons
classic_btn_layout = BoxLayout(orientation='horizontal', spacing=10, size_hint_y=None, height=45)
classic_test_btn = Button(
text="Classic 720p",
background_color=(0.2, 0.8, 0.2, 1),
font_size=12
)
classic_prod_btn = Button(
text="Classic 2K",
background_color=(0.3, 0.6, 0.3, 1),
font_size=12
)
classic_btn_layout.add_widget(classic_test_btn)
classic_btn_layout.add_widget(classic_prod_btn)
layout.add_widget(classic_btn_layout)
# Advanced Navigation Mode
advanced_layout = BoxLayout(orientation='vertical', spacing=5)
advanced_title = Label(
text="🧭 Navigation Animation",
font_size=16,
size_hint_y=None,
height=30,
color=(0.2, 0.6, 0.9, 1)
)
advanced_desc = Label(
text="• Satellite terrain details\n• 3D camera following at 1000-2000m\n• Google Earth entry scene\n• Professional navigation view",
font_size=11,
size_hint_y=None,
height=70,
color=(0.9, 0.9, 0.9, 1),
halign="left",
valign="middle"
)
advanced_desc.text_size = (None, None)
advanced_layout.add_widget(advanced_title)
advanced_layout.add_widget(advanced_desc)
layout.add_widget(advanced_layout)
# Advanced button
advanced_btn = Button(
text="Generate Navigation Animation",
background_color=(0.2, 0.6, 0.9, 1),
size_hint_y=None,
height=45,
font_size=13
)
layout.add_widget(advanced_btn)
# Google Earth Flythrough Mode
google_earth_layout = BoxLayout(orientation='vertical', spacing=5)
google_earth_title = Label(
text="🌍 Google Earth Flythrough",
font_size=16,
size_hint_y=None,
height=30,
color=(0.1, 0.8, 0.1, 1)
)
google_earth_desc = Label(
text="• Realistic 3D terrain with mountains\n• Cinematic camera following at 1000-2000m\n• Google Earth-style flythrough\n• Professional geographic animation",
font_size=11,
size_hint_y=None,
height=70,
color=(0.9, 0.9, 0.9, 1),
halign="left",
valign="middle"
)
google_earth_desc.text_size = (None, None)
google_earth_layout.add_widget(google_earth_title)
google_earth_layout.add_widget(google_earth_desc)
layout.add_widget(google_earth_layout)
# Google Earth button
google_earth_btn = Button(
text="Generate Google Earth Flythrough",
background_color=(0.1, 0.8, 0.1, 1),
size_hint_y=None,
height=45,
font_size=13
)
layout.add_widget(google_earth_btn)
# Blender Cinema Mode
blender_layout = BoxLayout(orientation='vertical', spacing=5)
blender_title = Label(
text="<22> Cinema Quality (Blender)",
font_size=16,
size_hint_y=None,
height=30,
color=(0.9, 0.6, 0.2, 1)
)
blender_desc = Label(
text="• Professional 3D rendering\n• Photorealistic visuals\n• Cinema-grade quality\n• Longer processing time",
font_size=11,
size_hint_y=None,
height=70,
color=(0.9, 0.9, 0.9, 1),
halign="left",
valign="middle"
)
blender_desc.text_size = (None, None)
blender_layout.add_widget(blender_title)
blender_layout.add_widget(blender_desc)
layout.add_widget(blender_layout)
# Blender button
blender_btn = Button(
text="Generate Blender Cinema Animation",
background_color=(0.9, 0.6, 0.2, 1),
size_hint_y=None,
height=45,
font_size=13
)
layout.add_widget(blender_btn)
# Cancel button
cancel_btn = Button(
text="Cancel",
background_color=(0.5, 0.5, 0.5, 1),
size_hint_y=None,
height=40,
font_size=12
)
layout.add_widget(cancel_btn)
popup = Popup(
title="Select Animation Style",
content=layout,
size_hint=(0.95, 0.9),
auto_dismiss=False
)
def start_classic_test(instance):
popup.dismiss()
self.generate_3d_video_test_mode()
def start_classic_production(instance):
popup.dismiss()
self.generate_3d_video_production_mode()
def start_advanced_3d(instance):
popup.dismiss()
self.generate_advanced_3d_animation()
def start_google_earth(instance):
popup.dismiss()
self.generate_google_earth_animation()
def start_blender_animation(instance):
popup.dismiss()
self.generate_blender_animation()
classic_test_btn.bind(on_press=start_classic_test)
classic_prod_btn.bind(on_press=start_classic_production)
advanced_btn.bind(on_press=start_advanced_3d)
google_earth_btn.bind(on_press=start_google_earth)
blender_btn.bind(on_press=start_blender_animation)
cancel_btn.bind(on_press=lambda x: popup.dismiss())
popup.open()
def generate_blender_animation(self):
"""Generate cinema-quality animation using Blender"""
# Show processing popup
layout = BoxLayout(orientation='vertical', spacing=10, padding=10)
label = Label(text="Initializing Blender rendering pipeline...")
progress = ProgressBar(max=100, value=0)
layout.add_widget(label)
layout.add_widget(progress)
popup = Popup(
title="Generating Blender Cinema Animation",
content=layout,
size_hint=(0.9, None),
size=(0, 200),
auto_dismiss=False
)
popup.open()
def run_blender_animation():
try:
# Update status
def update_status(progress_val, status_text):
def _update(dt):
progress.value = progress_val
label.text = status_text
Clock.schedule_once(_update, 0)
project_folder = os.path.join(RESOURCES_FOLDER, "projects", self.project_name)
positions_path = os.path.join(project_folder, "positions.json")
if not os.path.exists(positions_path):
update_status(0, "Error: No GPS data found")
Clock.schedule_once(lambda dt: popup.dismiss(), 2)
return
update_status(10, "Loading GPS data into Blender...")
# Check dependencies first
animator = BlenderGPSAnimator(project_folder)
animator.check_dependencies()
update_status(25, "Processing GPS coordinates...")
gps_data = animator.load_gps_data(positions_path)
output_video_path = os.path.join(project_folder, f"{self.project_name}_blender_cinema_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4")
# Progress callback for the animator
def animator_progress(progress, message):
update_status(25 + (progress * 0.6), message) # Map 0-100% to 25-85%
update_status(85, "Rendering cinema-quality video...")
success = animator.create_gps_animation(
positions_path,
output_video_path,
progress_callback=animator_progress
)
if success:
update_status(100, "Blender cinema animation complete!")
output_path = output_video_path
else:
raise Exception("Failed to render Blender animation")
def show_success(dt):
popup.dismiss()
self.show_success_popup(
"Blender Cinema Animation Complete!",
f"Your cinema-quality animation has been rendered to:\n{output_path}",
output_path
)
Clock.schedule_once(show_success, 1)
except Exception as e:
error_message = str(e)
def show_error(dt):
popup.dismiss()
self.show_error_popup("Blender Animation Error", error_message)
Clock.schedule_once(show_error, 0)
# Schedule the animation generation
Clock.schedule_once(lambda dt: run_blender_animation(), 0.5)
def generate_google_earth_animation(self):
"""Generate Google Earth-style flythrough animation with terrain"""
# Show processing popup
layout = BoxLayout(orientation='vertical', spacing=10, padding=10)
label = Label(text="Initializing Google Earth flythrough...")
progress = ProgressBar(max=100, value=0)
layout.add_widget(label)
layout.add_widget(progress)
popup = Popup(
title="Generating Google Earth Flythrough",
content=layout,
size_hint=(0.9, None),
size=(0, 200),
auto_dismiss=False
)
popup.open()
def run_google_earth_animation():
try:
# Update status
def update_status(progress_val, status_text):
def _update(dt):
progress.value = progress_val
label.text = status_text
Clock.schedule_once(_update, 0)
project_folder = os.path.join(RESOURCES_FOLDER, "projects", self.project_name)
positions_path = os.path.join(project_folder, "positions.json")
if not os.path.exists(positions_path):
update_status(0, "Error: No GPS data found")
Clock.schedule_once(lambda dt: popup.dismiss(), 2)
return
update_status(10, "Checking dependencies...")
# Check dependencies first
generator = NavigationAnimationGenerator(project_folder)
generator.check_dependencies()
update_status(20, "Loading GPS data...")
df = generator.load_gps_data(positions_path)
update_status(30, "Generating navigation flythrough...")
output_video_path = os.path.join(project_folder, f"{self.project_name}_navigation_flythrough_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4")
# Progress callback for the generator
def generator_progress(progress, message):
update_status(30 + (progress * 0.5), message) # Map 0-100% to 30-80%
update_status(80, "Creating navigation flythrough...")
success = generator.generate_3d_animation(
positions_path,
output_video_path,
style='advanced',
progress_callback=generator_progress
)
if success:
update_status(100, "Navigation flythrough complete!")
output_path = output_video_path
else:
raise Exception("Failed to generate navigation flythrough")
def show_success(dt):
popup.dismiss()
self.show_success_popup(
"Google Earth Flythrough Complete!",
f"Your cinematic flythrough has been created:\n{output_path}",
output_path
)
Clock.schedule_once(show_success, 1)
except Exception as e:
error_message = str(e)
def show_error(dt):
popup.dismiss()
self.show_error_popup("Google Earth Animation Error", error_message)
Clock.schedule_once(show_error, 0)
# Schedule the animation generation
Clock.schedule_once(lambda dt: run_google_earth_animation(), 0.5)
def show_success_popup(self, title, message, file_path=None):
"""Show success popup with option to open file location"""
layout = BoxLayout(orientation='vertical', spacing=10, padding=10)
success_label = Label(
text=message,
text_size=(400, None),
halign="center",
valign="middle"
)
layout.add_widget(success_label)
button_layout = BoxLayout(orientation='horizontal', spacing=10, size_hint_y=None, height=50)
if file_path:
open_btn = Button(text="Open Folder", background_color=(0.2, 0.8, 0.2, 1))
open_btn.bind(on_press=lambda x: self.open_file_location(file_path))
button_layout.add_widget(open_btn)
ok_btn = Button(text="OK", background_color=(0.2, 0.6, 0.9, 1))
button_layout.add_widget(ok_btn)
layout.add_widget(button_layout)
popup = Popup(
title=title,
content=layout,
size_hint=(0.8, None),
size=(0, 250),
auto_dismiss=False
)
ok_btn.bind(on_press=lambda x: popup.dismiss())
popup.open()
def show_error_popup(self, title, message):
"""Show error popup"""
layout = BoxLayout(orientation='vertical', spacing=10, padding=10)
error_label = Label(
text=f"Error: {message}",
text_size=(400, None),
halign="center",
valign="middle",
color=(1, 0.3, 0.3, 1)
)
layout.add_widget(error_label)
ok_btn = Button(text="OK", background_color=(0.8, 0.2, 0.2, 1), size_hint_y=None, height=50)
layout.add_widget(ok_btn)
popup = Popup(
title=title,
content=layout,
size_hint=(0.8, None),
size=(0, 200),
auto_dismiss=False
)
ok_btn.bind(on_press=lambda x: popup.dismiss())
popup.open()
def open_file_location(self, file_path):
"""Open file location in system file manager"""
import subprocess
import platform
folder_path = os.path.dirname(file_path)
try:
if platform.system() == "Linux":
subprocess.run(["xdg-open", folder_path])
elif platform.system() == "Darwin": # macOS
subprocess.run(["open", folder_path])
elif platform.system() == "Windows":
subprocess.run(["explorer", folder_path])
except Exception as e:
print(f"Could not open folder: {e}")

View File

@@ -0,0 +1,419 @@
import kivy
from kivy.uix.screenmanager import Screen
import os
import json
import math
from datetime import datetime
from kivy.clock import Clock
from kivy.properties import StringProperty, NumericProperty, AliasProperty
from py_scripts.utils import (
process_preview_util, optimize_route_entries_util
)
from py_scripts.advanced_3d_generator import NavigationAnimationGenerator
from py_scripts.blender_animator import BlenderGPSAnimator
from kivy.uix.popup import Popup
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.progressbar import ProgressBar
from kivy.uix.textinput import TextInput
from config import RESOURCES_FOLDER
class CreateAnimationScreen(Screen):
project_name = StringProperty("")
preview_html_path = StringProperty("") # Path to the HTML file for preview
preview_image_path = StringProperty("") # Add this line
preview_image_version = NumericProperty(0) # Add this line
def get_preview_image_source(self):
project_folder = os.path.join(RESOURCES_FOLDER, "projects", self.project_name)
img_path = os.path.join(project_folder, "preview.png")
if os.path.exists(img_path):
return img_path
return "resources/images/track.png"
preview_image_source = AliasProperty(
get_preview_image_source, None, bind=['project_name', 'preview_image_version']
)
def on_pre_enter(self):
# Update the route entries label with the actual number of entries
project_folder = os.path.join(RESOURCES_FOLDER, "projects", self.project_name)
positions_path = os.path.join(project_folder, "positions.json")
count = 0
if os.path.exists(positions_path):
with open(positions_path, "r") as f:
try:
positions = json.load(f)
count = len(positions)
except Exception:
count = 0
self.ids.route_entries_label.text = f"Your route has {count} entries,"
def open_rename_popup(self):
from kivy.uix.popup import Popup
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.label import Label
layout = BoxLayout(orientation='vertical', spacing=10, padding=10)
label = Label(text="Enter new project name:")
input_field = TextInput(text=self.project_name, multiline=False)
btn_save = Button(text="Save", background_color=(0.008, 0.525, 0.290, 1))
btn_cancel = Button(text="Cancel")
layout.add_widget(label)
layout.add_widget(input_field)
layout.add_widget(btn_save)
layout.add_widget(btn_cancel)
popup = Popup(
title="Rename Project",
content=layout,
size_hint=(0.92, None),
size=(0, 260),
auto_dismiss=False
)
def do_rename(instance):
new_name = input_field.text.strip()
if new_name and new_name != self.project_name:
if self.rename_project_folder(self.project_name, new_name):
self.project_name = new_name
popup.dismiss()
self.on_pre_enter() # Refresh label
else:
label.text = "Rename failed (name exists?)"
else:
label.text = "Please enter a new name."
btn_save.bind(on_press=do_rename)
btn_cancel.bind(on_press=lambda x: popup.dismiss())
popup.open()
def rename_project_folder(self, old_name, new_name):
import os
old_path = os.path.join(RESOURCES_FOLDER, "projects", old_name)
new_path = os.path.join(RESOURCES_FOLDER, "projects", new_name)
if os.path.exists(old_path) and not os.path.exists(new_path):
os.rename(old_path, new_path)
return True
return False
def optimize_route_entries(self):
# Create the popup and UI elements
layout = BoxLayout(orientation='vertical', spacing=10, padding=10)
label = Label(text="Processing route entries...")
progress = ProgressBar(max=100, value=0)
layout.add_widget(label)
layout.add_widget(progress)
popup = Popup(
title="Optimizing Route",
content=layout,
size_hint=(0.92, None),
size=(0, 260),
auto_dismiss=False
)
popup.open()
# Now call the utility function with these objects
optimize_route_entries_util(
self.project_name,
RESOURCES_FOLDER,
label,
progress,
popup,
Clock,
on_save=lambda: self.on_pre_enter()
)
def preview_route(self):
# Show processing popup
layout = BoxLayout(orientation='vertical', spacing=10, padding=10)
label = Label(text="Processing route preview...")
progress = ProgressBar(max=100, value=0)
layout.add_widget(label)
layout.add_widget(progress)
popup = Popup(
title="Previewing Route",
content=layout,
size_hint=(0.8, None),
size=(0, 180),
auto_dismiss=False
)
popup.open()
def set_preview_image_path(path):
self.preview_image_path = path
self.preview_image_version += 1 # Force AliasProperty to update
self.property('preview_image_source').dispatch(self)
self.ids.preview_image.reload()
# Schedule the processing function
Clock.schedule_once(
lambda dt: process_preview_util(
self.project_name,
RESOURCES_FOLDER,
label,
progress,
popup,
self.ids.preview_image,
set_preview_image_path,
Clock
),
0.5
)
def open_pauses_popup(self):
"""Navigate to the pause edit screen"""
pause_edit_screen = self.manager.get_screen("pause_edit")
pause_edit_screen.set_project_and_callback(self.project_name, self.on_pre_enter)
self.manager.current = "pause_edit"
def generate_google_earth_animation(self):
"""Generate Google Earth-style flythrough animation using NavigationAnimationGenerator"""
# Show processing popup
layout = BoxLayout(orientation='vertical', spacing=10, padding=10)
label = Label(text="Initializing Google Earth flythrough...")
progress = ProgressBar(max=100, value=0)
layout.add_widget(label)
layout.add_widget(progress)
popup = Popup(
title="Generating Google Earth Flythrough",
content=layout,
size_hint=(0.9, None),
size=(0, 200),
auto_dismiss=False
)
popup.open()
def run_google_earth_animation():
try:
# Update status
def update_status(progress_val, status_text):
def _update(dt):
progress.value = progress_val
label.text = status_text
Clock.schedule_once(_update, 0)
project_folder = os.path.join(RESOURCES_FOLDER, "projects", self.project_name)
positions_path = os.path.join(project_folder, "positions.json")
if not os.path.exists(positions_path):
update_status(0, "Error: No GPS data found")
Clock.schedule_once(lambda dt: popup.dismiss(), 2)
return
update_status(10, "Loading GPS data...")
# Check dependencies first
generator = NavigationAnimationGenerator(project_folder)
generator.check_dependencies()
update_status(20, "Processing GPS coordinates...")
df = generator.load_gps_data(positions_path)
update_status(40, "Creating Google Earth flythrough...")
output_video_path = os.path.join(project_folder, f"{self.project_name}_google_earth_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4")
# Progress callback for the generator
def generator_progress(progress, message):
update_status(40 + (progress * 0.5), message) # Map 0-100% to 40-90%
update_status(90, "Creating flythrough video...")
success = generator.generate_frames(positions_path, style='google_earth', progress_callback=generator_progress)
if success and len(success) > 0:
update_status(95, "Rendering final video...")
video_success = generator.create_video(success, output_video_path, generator_progress)
if video_success:
update_status(100, "Google Earth flythrough complete!")
output_path = output_video_path
else:
raise Exception("Failed to create video from frames")
else:
raise Exception("Failed to generate frames")
def show_success(dt):
popup.dismiss()
self.show_success_popup(
"Google Earth Flythrough Complete!",
f"Your Google Earth-style flythrough has been saved to:\n{output_path}",
output_path
)
Clock.schedule_once(show_success, 1)
except Exception as e:
error_message = str(e)
def show_error(dt):
popup.dismiss()
self.show_error_popup("Google Earth Animation Error", error_message)
Clock.schedule_once(show_error, 0)
# Schedule the animation generation
Clock.schedule_once(lambda dt: run_google_earth_animation(), 0.5)
def generate_blender_animation(self):
"""Generate cinema-quality animation using Blender"""
# Show processing popup
layout = BoxLayout(orientation='vertical', spacing=10, padding=10)
label = Label(text="Initializing Blender rendering pipeline...")
progress = ProgressBar(max=100, value=0)
layout.add_widget(label)
layout.add_widget(progress)
popup = Popup(
title="Generating Blender Cinema Animation",
content=layout,
size_hint=(0.9, None),
size=(0, 200),
auto_dismiss=False
)
popup.open()
def run_blender_animation():
try:
# Update status
def update_status(progress_val, status_text):
def _update(dt):
progress.value = progress_val
label.text = status_text
Clock.schedule_once(_update, 0)
project_folder = os.path.join(RESOURCES_FOLDER, "projects", self.project_name)
positions_path = os.path.join(project_folder, "positions.json")
if not os.path.exists(positions_path):
update_status(0, "Error: No GPS data found")
Clock.schedule_once(lambda dt: popup.dismiss(), 2)
return
update_status(10, "Loading GPS data into Blender...")
# Check dependencies first
animator = BlenderGPSAnimator(project_folder)
animator.check_dependencies()
update_status(25, "Processing GPS coordinates...")
gps_data = animator.load_gps_data(positions_path)
output_video_path = os.path.join(project_folder, f"{self.project_name}_blender_cinema_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4")
# Progress callback for the animator
def animator_progress(progress, message):
update_status(25 + (progress * 0.6), message) # Map 0-100% to 25-85%
update_status(85, "Rendering cinema-quality video...")
success = animator.create_gps_animation(
positions_path,
output_video_path,
progress_callback=animator_progress
)
if success:
update_status(100, "Blender cinema animation complete!")
output_path = output_video_path
else:
raise Exception("Failed to generate Blender animation")
def show_success(dt):
popup.dismiss()
self.show_success_popup(
"Blender Cinema Animation Complete!",
f"Your cinema-quality animation has been saved to:\n{output_path}",
output_path
)
Clock.schedule_once(show_success, 1)
except Exception as e:
error_message = str(e)
def show_error(dt):
popup.dismiss()
self.show_error_popup("Blender Animation Error", error_message)
Clock.schedule_once(show_error, 0)
# Schedule the animation generation
Clock.schedule_once(lambda dt: run_blender_animation(), 0.5)
def show_success_popup(self, title, message, file_path):
"""Show success popup with option to open file location"""
layout = BoxLayout(orientation='vertical', spacing=10, padding=15)
# Success message
success_label = Label(
text=message,
text_size=(None, None),
halign="center",
valign="middle"
)
layout.add_widget(success_label)
# Buttons
btn_layout = BoxLayout(orientation='horizontal', spacing=10, size_hint_y=None, height=50)
open_folder_btn = Button(
text="Open Folder",
background_color=(0.2, 0.6, 0.9, 1)
)
ok_btn = Button(
text="OK",
background_color=(0.3, 0.7, 0.3, 1)
)
btn_layout.add_widget(open_folder_btn)
btn_layout.add_widget(ok_btn)
layout.add_widget(btn_layout)
popup = Popup(
title=title,
content=layout,
size_hint=(0.9, 0.6),
auto_dismiss=False
)
def open_folder(instance):
folder_path = os.path.dirname(file_path)
os.system(f'xdg-open "{folder_path}"') # Linux
popup.dismiss()
def close_popup(instance):
popup.dismiss()
open_folder_btn.bind(on_press=open_folder)
ok_btn.bind(on_press=close_popup)
popup.open()
def show_error_popup(self, title, message):
"""Show error popup"""
layout = BoxLayout(orientation='vertical', spacing=10, padding=15)
error_label = Label(
text=f"Error: {message}",
text_size=(None, None),
halign="center",
valign="middle"
)
layout.add_widget(error_label)
ok_btn = Button(
text="OK",
background_color=(0.8, 0.3, 0.3, 1),
size_hint_y=None,
height=50
)
layout.add_widget(ok_btn)
popup = Popup(
title=title,
content=layout,
size_hint=(0.8, 0.4),
auto_dismiss=False
)
ok_btn.bind(on_press=lambda x: popup.dismiss())
popup.open()

View File

@@ -1,83 +0,0 @@
#!/usr/bin/env python3
"""
Test script for Google Earth-style flythrough animation
"""
import os
import sys
sys.path.append('/home/pi/Desktop/traccar_animation')
from py_scripts.advanced_3d_generator import Advanced3DGenerator
from datetime import datetime
def test_google_earth_animation():
"""Test the new Google Earth flythrough animation"""
# Find a project with GPS data
projects_folder = "/home/pi/Desktop/traccar_animation/resources/projects"
if not os.path.exists(projects_folder):
print("Projects folder not found!")
return
# Look for projects
projects = [d for d in os.listdir(projects_folder) if os.path.isdir(os.path.join(projects_folder, d))]
if not projects:
print("No projects found!")
return
# Use the first project found
project_name = projects[0]
project_folder = os.path.join(projects_folder, project_name)
positions_file = os.path.join(project_folder, "positions.json")
if not os.path.exists(positions_file):
print(f"No positions.json found in project {project_name}")
return
print(f"Testing Google Earth animation with project: {project_name}")
# Create generator
generator = Advanced3DGenerator(project_folder)
# Check dependencies
try:
generator.check_dependencies()
print("✅ All dependencies available")
except Exception as e:
print(f"❌ Dependency error: {e}")
return
# Generate Google Earth-style animation
output_video = os.path.join(project_folder, f"{project_name}_google_earth_test_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4")
def progress_callback(progress, message):
print(f"Progress: {progress:.1f}% - {message}")
try:
print("Starting Google Earth flythrough generation...")
success = generator.generate_3d_animation(
positions_file,
output_video,
style='google_earth',
progress_callback=progress_callback
)
if success and os.path.exists(output_video):
print(f"✅ SUCCESS! Google Earth flythrough created: {output_video}")
# Get file size
file_size = os.path.getsize(output_video) / (1024 * 1024) # MB
print(f"📹 Video size: {file_size:.1f} MB")
else:
print("❌ Failed to create video")
except Exception as e:
print(f"❌ Error during generation: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
test_google_earth_animation()

View File

@@ -1,81 +0,0 @@
#!/usr/bin/env python3
"""
Test script for the improved Relive-style GPS animation
"""
import os
import sys
import json
from datetime import datetime
# Add the project directory to the path
sys.path.append('/home/pi/Desktop/traccar_animation')
from py_scripts.advanced_3d_generator import Advanced3DGenerator
def test_relive_animation():
"""Test the new Relive-style animation"""
# Find a project with GPS data
resources_folder = "/home/pi/Desktop/traccar_animation/resources"
projects_folder = os.path.join(resources_folder, "projects")
if not os.path.exists(projects_folder):
print("No projects folder found")
return
# Look for projects with positions.json
for project_name in os.listdir(projects_folder):
project_path = os.path.join(projects_folder, project_name)
positions_file = os.path.join(project_path, "positions.json")
if os.path.exists(positions_file):
print(f"🎬 Testing Relive-style animation with project: {project_name}")
# Check if positions file has data
try:
with open(positions_file, 'r') as f:
positions = json.load(f)
if len(positions) < 5:
print(f"❌ Project {project_name} has only {len(positions)} GPS points - skipping")
continue
print(f"📍 Found {len(positions)} GPS points")
# Create generator
generator = Advanced3DGenerator(project_path)
# Progress callback
def progress_callback(progress, message):
print(f"Progress: {progress:.1f}% - {message}")
# Generate animation
output_video = os.path.join(project_path, f"relive_animation_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4")
print(f"🚀 Starting Relive-style animation generation...")
success = generator.generate_3d_animation(
positions_file,
output_video,
style='advanced',
progress_callback=progress_callback
)
if success:
print(f"✅ SUCCESS! Relive-style animation created: {output_video}")
print(f"📁 You can find your video at: {output_video}")
else:
print("❌ Failed to generate animation")
return # Exit after first successful project
except Exception as e:
print(f"❌ Error testing project {project_name}: {e}")
continue
print("❌ No suitable projects found for testing")
if __name__ == "__main__":
print("🎬 Testing Improved Relive-Style GPS Animation")
print("=" * 50)
test_relive_animation()

View File

@@ -722,7 +722,7 @@
height: 202 height: 202
size_hint_x: 1 size_hint_x: 1
# 3D Video Animation frame # Progressive 3D Animation frame
BoxLayout: BoxLayout:
orientation: "horizontal" orientation: "horizontal"
size_hint_y: None size_hint_y: None
@@ -737,7 +737,7 @@
size: self.size size: self.size
Label: Label:
text: "Generate 3D video animation\nsimilar to Relive style" text: "Generate progressive 3D animation\nBuilds trip point by point"
font_size: 16 font_size: 16
color: 1, 1, 1, 1 color: 1, 1, 1, 1
size_hint_x: 0.7 size_hint_x: 0.7
@@ -746,15 +746,84 @@
text_size: self.size text_size: self.size
Button: Button:
text: "Generate\n3D Video" text: "Generate\n3D Trip"
size_hint_x: 0.3 size_hint_x: 0.3
font_size: 14 font_size: 14
background_color: 0.8, 0.2, 0.4, 1 background_color: 0.2, 0.8, 0.4, 1
color: 1, 1, 1, 1 color: 1, 1, 1, 1
halign: "center" halign: "center"
valign: "middle" valign: "middle"
text_size: self.size text_size: self.size
on_press: root.generate_3d_video() on_press: root.generate_progressive_3d_animation()
# Google Earth Animation frame
BoxLayout:
orientation: "horizontal"
size_hint_y: None
height: 60
padding: [10, 10, 10, 10]
spacing: 10
canvas.before:
Color:
rgba: 0.15, 0.15, 0.18, 1
Rectangle:
pos: self.pos
size: self.size
Label:
text: "Generate Google Earth flythrough\nCinematic aerial view"
font_size: 16
color: 1, 1, 1, 1
size_hint_x: 0.7
halign: "left"
valign: "middle"
text_size: self.size
Button:
text: "Generate\nFlythrough"
size_hint_x: 0.3
font_size: 14
background_color: 0.1, 0.8, 0.1, 1
color: 1, 1, 1, 1
halign: "center"
valign: "middle"
text_size: self.size
on_press: root.generate_google_earth_animation()
# Blender Animation frame
BoxLayout:
orientation: "horizontal"
size_hint_y: None
height: 60
padding: [10, 10, 10, 10]
spacing: 10
canvas.before:
Color:
rgba: 0.15, 0.15, 0.18, 1
Rectangle:
pos: self.pos
size: self.size
Label:
text: "Generate cinema-quality animation\nProfessional 3D rendering"
font_size: 16
color: 1, 1, 1, 1
size_hint_x: 0.7
halign: "left"
valign: "middle"
text_size: self.size
Button:
text: "Generate\nCinema"
size_hint_x: 0.3
font_size: 14
background_color: 0.9, 0.6, 0.2, 1
color: 1, 1, 1, 1
halign: "center"
valign: "middle"
text_size: self.size
on_press: root.generate_blender_animation()
Widget: Widget: