updated to generate trip

This commit is contained in:
2025-07-10 13:46:05 +03:00
parent 4fa7ed2a48
commit 29fd68f732
338 changed files with 12229 additions and 24735 deletions

View File

@@ -9,8 +9,7 @@ from kivy.properties import StringProperty, NumericProperty, AliasProperty
from py_scripts.utils import (
process_preview_util, optimize_route_entries_util
)
from py_scripts.video_3d_generator import generate_3d_video_animation
from py_scripts.advanced_3d_generator import Advanced3DGenerator
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
@@ -51,8 +50,6 @@ class CreateAnimationScreen(Screen):
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
@@ -131,7 +128,6 @@ class CreateAnimationScreen(Screen):
on_save=lambda: self.on_pre_enter()
)
def preview_route(self):
# Show processing popup
layout = BoxLayout(orientation='vertical', spacing=10, padding=10)
@@ -168,291 +164,16 @@ class CreateAnimationScreen(Screen):
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_3d_video(self):
"""Show video generation mode selection popup"""
self.show_video_generation_options()
def generate_3d_video_test_mode(self):
"""Generate a 3D video animation in 720p test mode for faster processing"""
# Show processing popup with test mode indication
layout = BoxLayout(orientation='vertical', spacing=10, padding=10)
label = Label(text="Preparing 720p test video generation...")
progress = ProgressBar(max=100, value=0)
layout.add_widget(label)
layout.add_widget(progress)
popup = Popup(
title="Generating 3D Video Animation (720p Test Mode)",
content=layout,
size_hint=(0.9, None),
size=(0, 200),
auto_dismiss=False
)
popup.open()
# Schedule the 3D video generation in test mode
Clock.schedule_once(
lambda dt: generate_3d_video_animation(
self.project_name,
RESOURCES_FOLDER,
label,
progress,
popup,
Clock,
test_mode=True # Enable test mode
),
0.5
)
def generate_3d_video_production_mode(self):
"""Generate a 3D video animation in 2K production mode for high quality"""
# Show processing popup with production mode indication
layout = BoxLayout(orientation='vertical', spacing=10, padding=10)
label = Label(text="Preparing 2K production video generation...")
progress = ProgressBar(max=100, value=0)
layout.add_widget(label)
layout.add_widget(progress)
popup = Popup(
title="Generating 3D Video Animation (2K Production Mode)",
content=layout,
size_hint=(0.9, None),
size=(0, 200),
auto_dismiss=False
)
popup.open()
# Schedule the 3D video generation in production mode
Clock.schedule_once(
lambda dt: generate_3d_video_animation(
self.project_name,
RESOURCES_FOLDER,
label,
progress,
popup,
Clock,
test_mode=False # Disable test mode for production
),
0.5
)
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 Pydeck/Plotly Mode
advanced_layout = BoxLayout(orientation='vertical', spacing=5)
advanced_title = Label(
text="🚀 Advanced 3D (Pydeck + Plotly)",
font_size=16,
size_hint_y=None,
height=30,
color=(0.2, 0.6, 0.9, 1)
)
advanced_desc = Label(
text="• Professional geospatial visualization\n• Interactive 3D terrain\n• Advanced camera movements\n• High-quality animations",
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 Advanced 3D 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="<EFBFBD> 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_advanced_3d_animation(self):
"""Generate advanced 3D animation using Pydeck and Plotly"""
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 advanced 3D animation...")
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 Advanced 3D Animation",
title="Generating Google Earth Flythrough",
content=layout,
size_hint=(0.9, None),
size=(0, 200),
@@ -460,7 +181,7 @@ class CreateAnimationScreen(Screen):
)
popup.open()
def run_advanced_animation():
def run_google_earth_animation():
try:
# Update status
def update_status(progress_val, status_text):
@@ -480,38 +201,38 @@ class CreateAnimationScreen(Screen):
update_status(10, "Loading GPS data...")
# Check dependencies first
generator = Advanced3DGenerator(project_folder)
generator = NavigationAnimationGenerator(project_folder)
generator.check_dependencies()
update_status(20, "Processing GPS coordinates...")
df = generator.load_gps_data(positions_path)
update_status(40, "Creating 3D visualization frames...")
output_video_path = os.path.join(project_folder, f"{self.project_name}_advanced_3d_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4")
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.4), message) # Map 0-100% to 40-80%
update_status(40 + (progress * 0.5), message) # Map 0-100% to 40-90%
update_status(80, "Rendering video...")
success = generator.generate_3d_animation(
positions_path,
output_video_path,
style='advanced',
progress_callback=generator_progress
)
update_status(90, "Creating flythrough video...")
success = generator.generate_frames(positions_path, style='google_earth', progress_callback=generator_progress)
if success:
update_status(100, "Advanced 3D animation complete!")
output_path = output_video_path
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 video")
raise Exception("Failed to generate frames")
def show_success(dt):
popup.dismiss()
self.show_success_popup(
"Advanced 3D Animation Complete!",
f"Your high-quality 3D animation has been saved to:\n{output_path}",
"Google Earth Flythrough Complete!",
f"Your Google Earth-style flythrough has been saved to:\n{output_path}",
output_path
)
@@ -521,13 +242,13 @@ class CreateAnimationScreen(Screen):
error_message = str(e)
def show_error(dt):
popup.dismiss()
self.show_error_popup("Advanced Animation Error", error_message)
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_advanced_animation(), 0.5)
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
@@ -588,13 +309,13 @@ class CreateAnimationScreen(Screen):
update_status(100, "Blender cinema animation complete!")
output_path = output_video_path
else:
raise Exception("Failed to render Blender animation")
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 rendered to:\n{output_path}",
f"Your cinema-quality animation has been saved to:\n{output_path}",
output_path
)
@@ -610,26 +331,34 @@ class CreateAnimationScreen(Screen):
# 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"""
def generate_progressive_3d_animation(self):
"""Generate a progressive 3D animation that builds the trip point by point"""
# Show processing popup
layout = BoxLayout(orientation='vertical', spacing=10, padding=10)
label = Label(text="Initializing Google Earth flythrough...")
label = Label(text="Initializing progressive 3D animation...")
progress = ProgressBar(max=100, value=0)
layout.add_widget(label)
layout.add_widget(progress)
popup = Popup(
title="Generating Google Earth Flythrough",
title="Generating Progressive 3D Animation",
content=layout,
size_hint=(0.9, None),
size=(0, 200),
auto_dismiss=False
)
popup.open()
def run_google_earth_animation():
def run_progressive_animation():
try:
# Import here to avoid startup delays
import matplotlib
matplotlib.use('Agg') # Use non-interactive backend
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import cv2 # Use OpenCV instead of MoviePy
# Update status
def update_status(progress_val, status_text):
def _update(dt):
@@ -645,132 +374,360 @@ class CreateAnimationScreen(Screen):
Clock.schedule_once(lambda dt: popup.dismiss(), 2)
return
update_status(10, "Checking dependencies...")
update_status(10, "Loading GPS data...")
# Check dependencies first
generator = Advanced3DGenerator(project_folder)
generator.check_dependencies()
# Load GPS data
with open(positions_path, 'r') as f:
positions = json.load(f)
update_status(20, "Loading GPS data...")
df = generator.load_gps_data(positions_path)
if len(positions) < 2:
update_status(0, "Error: Need at least 2 GPS points")
Clock.schedule_once(lambda dt: popup.dismiss(), 2)
return
update_status(30, "Generating terrain and camera 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")
update_status(20, "Processing GPS coordinates...")
# Progress callback for the generator
def generator_progress(progress, message):
update_status(30 + (progress * 0.5), message) # Map 0-100% to 30-80%
# Extract coordinates and timestamps
lats = [pos['latitude'] for pos in positions]
lons = [pos['longitude'] for pos in positions]
alts = [pos.get('altitude', 0) for pos in positions]
timestamps = [pos.get('fixTime', '') for pos in positions]
update_status(80, "Creating flythrough video...")
success = generator.generate_3d_animation(
positions_path,
output_video_path,
style='google_earth',
progress_callback=generator_progress
)
# Convert to numpy arrays for easier manipulation
lats = np.array(lats)
lons = np.array(lons)
alts = np.array(alts)
if success:
update_status(100, "Google Earth flythrough complete!")
output_path = output_video_path
# Normalize coordinates for better visualization
lat_center = np.mean(lats)
lon_center = np.mean(lons)
alt_min = np.min(alts)
# Convert to relative coordinates (in meters approximately)
x = (lons - lon_center) * 111320 * np.cos(np.radians(lat_center)) # longitude to meters
y = (lats - lat_center) * 110540 # latitude to meters
z = alts - alt_min # relative altitude
update_status(30, "Creating animation frames...")
# Create frames folder
frames_folder = os.path.join(project_folder, "progressive_frames")
os.makedirs(frames_folder, exist_ok=True)
# Animation settings
fps = 10 # frames per second
points_per_frame = max(1, len(positions) // 100) # Show multiple points per frame for long routes
total_frames = len(positions) // points_per_frame
frame_files = []
# Generate frames
for frame_idx in range(total_frames):
current_progress = 30 + (frame_idx / total_frames) * 50
update_status(current_progress, f"Creating frame {frame_idx + 1}/{total_frames}...")
# Points to show in this frame
end_point = min((frame_idx + 1) * points_per_frame, len(positions))
# Create 3D plot
fig = plt.figure(figsize=(12, 9), dpi=100)
ax = fig.add_subplot(111, projection='3d')
# Plot the route progressively
if end_point > 1:
# Plot completed route in blue
ax.plot(x[:end_point], y[:end_point], z[:end_point],
'b-', linewidth=2, alpha=0.7, label='Route')
# Plot points as small dots
ax.scatter(x[:end_point], y[:end_point], z[:end_point],
c='blue', s=20, alpha=0.6)
# Highlight current position in red
if end_point > 0:
current_idx = end_point - 1
ax.scatter(x[current_idx], y[current_idx], z[current_idx],
c='red', s=100, marker='o', label='Current Position')
# Add a small trail behind current position
trail_start = max(0, current_idx - 5)
if current_idx > trail_start:
ax.plot(x[trail_start:current_idx+1],
y[trail_start:current_idx+1],
z[trail_start:current_idx+1],
'r-', linewidth=4, alpha=0.8)
# Plot remaining route in light gray (preview)
if end_point < len(positions):
ax.plot(x[end_point:], y[end_point:], z[end_point:],
'lightgray', linewidth=1, alpha=0.3, label='Remaining Route')
# Set labels and title
ax.set_xlabel('East-West (meters)')
ax.set_ylabel('North-South (meters)')
ax.set_zlabel('Elevation (meters)')
# Add progress info
progress_percent = (end_point / len(positions)) * 100
timestamp_str = timestamps[end_point-1] if end_point > 0 else "Start"
ax.set_title(f'GPS Trip Animation\nProgress: {progress_percent:.1f}% - Point {end_point}/{len(positions)}\nTime: {timestamp_str}',
fontsize=14, pad=20)
# Set consistent view limits for all frames
margin = max(np.ptp(x), np.ptp(y)) * 0.1
ax.set_xlim(np.min(x) - margin, np.max(x) + margin)
ax.set_ylim(np.min(y) - margin, np.max(y) + margin)
ax.set_zlim(np.min(z) - 10, np.max(z) + 10)
# Set viewing angle for better 3D perspective
ax.view_init(elev=20, azim=45)
# Add legend
ax.legend(loc='upper right')
# Add grid
ax.grid(True, alpha=0.3)
# Save frame with comprehensive error handling
frame_path = os.path.join(frames_folder, f"frame_{frame_idx:04d}.png")
try:
plt.savefig(frame_path, dpi=100, bbox_inches='tight',
facecolor='white', edgecolor='none',
format='png', optimize=False)
plt.close(fig)
# Verify frame was saved properly and is readable by OpenCV
if os.path.exists(frame_path) and os.path.getsize(frame_path) > 1024:
# Test if OpenCV can read the frame
test_frame = cv2.imread(frame_path)
if test_frame is not None:
frame_files.append(frame_path)
if frame_idx == 0: # Log first frame details
h, w, c = test_frame.shape
update_status(current_progress, f"First frame: {w}x{h}, size: {os.path.getsize(frame_path)} bytes")
else:
update_status(current_progress, f"Warning: Frame {frame_idx} not readable by OpenCV")
try:
os.remove(frame_path)
except:
pass
else:
update_status(current_progress, f"Warning: Frame {frame_idx} too small or missing")
except Exception as e:
update_status(current_progress, f"Error saving frame {frame_idx}: {str(e)}")
try:
plt.close(fig)
except:
pass
continue
# Validate frames before creating video
if not frame_files:
raise Exception("No valid frames were generated")
update_status(80, f"Creating video from {len(frame_files)} frames...")
# Create video using OpenCV with better error handling
output_video_path = os.path.join(project_folder,
f"{self.project_name}_progressive_3d_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp4")
if frame_files:
try:
# Read first frame to get dimensions
first_frame = cv2.imread(frame_files[0])
if first_frame is None:
raise Exception(f"Could not read first frame: {frame_files[0]}")
height, width, layers = first_frame.shape
update_status(82, f"Video dimensions: {width}x{height}")
# Try different codecs for better compatibility
codecs_to_try = [
('mp4v', '.mp4'),
('XVID', '.avi'),
('MJPG', '.avi')
]
video_created = False
for codec, ext in codecs_to_try:
try:
# Update output path for different codecs
if ext != '.mp4':
test_output_path = output_video_path.replace('.mp4', ext)
else:
test_output_path = output_video_path
update_status(84, f"Trying codec {codec}...")
# Create video writer
fourcc = cv2.VideoWriter_fourcc(*codec)
video_writer = cv2.VideoWriter(test_output_path, fourcc, fps, (width, height))
if not video_writer.isOpened():
update_status(85, f"Failed to open video writer with {codec}")
continue
# Add frames to video
frames_written = 0
for i, frame_file in enumerate(frame_files):
frame = cv2.imread(frame_file)
if frame is not None:
# Ensure frame dimensions match
if frame.shape[:2] != (height, width):
frame = cv2.resize(frame, (width, height))
video_writer.write(frame)
frames_written += 1
if i % 10 == 0: # Update progress every 10 frames
progress = 85 + (i / len(frame_files)) * 3
update_status(progress, f"Writing frame {i+1}/{len(frame_files)} with {codec}")
video_writer.release()
# Check if video file was created and has reasonable size
if os.path.exists(test_output_path) and os.path.getsize(test_output_path) > 1024:
output_video_path = test_output_path
video_created = True
update_status(88, f"Video created successfully with {codec} ({frames_written} frames)")
break
else:
update_status(86, f"Video file not created or too small with {codec}")
except Exception as codec_error:
update_status(87, f"Error with {codec}: {str(codec_error)}")
continue
if not video_created:
raise Exception("Failed to create video with any codec")
except Exception as video_error:
raise Exception(f"Video creation failed: {str(video_error)}")
update_status(90, "Cleaning up temporary files...")
# Clean up frame files
for frame_file in frame_files:
try:
os.remove(frame_file)
except:
pass
try:
os.rmdir(frames_folder)
except:
pass
update_status(100, "Progressive 3D animation complete!")
def show_success(dt):
popup.dismiss()
self.show_success_popup(
"Progressive 3D Animation Complete!",
f"Your progressive 3D animation has been saved to:\n{output_video_path}\n\nThe animation shows {len(positions)} GPS points building the route progressively from start to finish.",
output_video_path
)
Clock.schedule_once(show_success, 1)
else:
raise Exception("Failed to generate flythrough video")
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)
raise Exception("No frames were generated")
except Exception as e:
error_message = str(e)
print(f"DEBUG: Progressive animation error: {error_message}")
import traceback
traceback.print_exc()
def show_error(dt):
popup.dismiss()
self.show_error_popup("Google Earth Animation Error", error_message)
self.show_error_popup("Progressive 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):
Clock.schedule_once(lambda dt: run_progressive_animation(), 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_success_popup(self, title, message, file_path):
"""Show success popup with option to open file location"""
layout = BoxLayout(orientation='vertical', spacing=10, padding=10)
layout = BoxLayout(orientation='vertical', spacing=10, padding=15)
# Success message
success_label = Label(
text=message,
text_size=(400, None),
text_size=(None, None),
halign="center",
valign="middle"
)
layout.add_widget(success_label)
button_layout = BoxLayout(orientation='horizontal', spacing=10, size_hint_y=None, height=50)
# Buttons
btn_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)
open_folder_btn = Button(
text="Open Folder",
background_color=(0.2, 0.6, 0.9, 1)
)
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)
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.8, None),
size=(0, 250),
size_hint=(0.9, 0.6),
auto_dismiss=False
)
ok_btn.bind(on_press=lambda x: popup.dismiss())
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=10)
layout = BoxLayout(orientation='vertical', spacing=10, padding=15)
error_label = Label(
text=f"Error: {message}",
text_size=(400, None),
text_size=(None, None),
halign="center",
valign="middle",
color=(1, 0.3, 0.3, 1)
valign="middle"
)
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)
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, None),
size=(0, 200),
size_hint=(0.8, 0.4),
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}")