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()