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="� 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}")