Source code for districtheatingsim.gui.MainTab.main_presenter

"""
Main presenter module for DistrictHeatingSim application.

Implements the Presenter component of the MVP pattern, handling business logic
for project management, variant creation, and coordination between view and
data management layers.

:author: Dipl.-Ing. (FH) Jonas Pfeiffer
"""

import os
import shutil
from typing import Optional, Tuple

from PyQt6.QtWidgets import QInputDialog

[docs] class HeatSystemPresenter: """ Presenter for district heating project management (MVP pattern). Coordinates between view and data management layers, handling business logic for project creation, variant management, and folder structure operations. :param view: Main application view component :type view: HeatSystemDesignGUI :param folder_manager: Project folder management :type folder_manager: ProjectFolderManager :param data_manager: Central data storage :type data_manager: DataManager :param config_manager: Configuration management :type config_manager: ProjectConfigManager """
[docs] def __init__(self, view, folder_manager, data_manager, config_manager): """ Initialize presenter with manager dependencies. :param view: Main application view :type view: HeatSystemDesignGUI :param folder_manager: Project folder manager :type folder_manager: ProjectFolderManager :param data_manager: Data manager :type data_manager: DataManager :param config_manager: Configuration manager :type config_manager: ProjectConfigManager """ self.view = view self.folder_manager = folder_manager self.data_manager = data_manager self.config_manager = config_manager # Connect the model signals directly to the view updates self.folder_manager.project_folder_changed.connect(self.view.update_project_folder_label)
[docs] def create_new_project(self, folder_path: str, project_name: str) -> bool: """ Create new district heating project with standardized folder structure. Creates project with "Eingangsdaten allgemein", "Definition Quartier IST", and "Variante 1" folders. Initializes building data CSV and registers project with folder manager. :param folder_path: Base directory for new project :type folder_path: str :param project_name: Name of project folder :type project_name: str :return: True if successful, False otherwise :rtype: bool """ # Validate input parameters if not folder_path or not project_name: return False try: # Create main project folder full_path = os.path.join(folder_path, project_name) os.makedirs(full_path) # Define standardized folder structure for district heating projects subdirs = { "Eingangsdaten allgemein": [], # General input data "Definition Quartier IST": [], # Current district definition "Variante 1": [ # First analysis variant "Ergebnisse", # Results and outputs "Gebäudedaten", # Building data "Lastgang", # Load profiles "Wärmenetz" # Heating network data ] } # Create main folders and their respective subfolders for main_folder, subfolders in subdirs.items(): main_folder_path = os.path.join(full_path, main_folder) os.makedirs(main_folder_path) # Create subfolders for variant organization for subfolder in subfolders: os.makedirs(os.path.join(main_folder_path, subfolder)) # Register project with folder manager self.folder_manager.set_project_folder(full_path) # Activate the newly created "Variante 1" for immediate use variant_folder = os.path.join(full_path, "Variante 1") if os.path.exists(variant_folder): self.folder_manager.set_variant_folder("Variante 1") else: self.view.show_error_message("Fehler: Variante 1 konnte nicht gefunden werden.") return False # Create initial CSV file for building data in project tab csv_path = os.path.join( self.folder_manager.get_variant_folder(), self.config_manager.get_relative_path("current_building_data_path") ) self.view.projectTab.presenter.create_csv(csv_path) return True except Exception as e: # Provide user-friendly error feedback self.view.show_error_message(f"Ein Fehler ist aufgetreten: {e}") return False
[docs] def open_existing_project(self, folder_path: str) -> None: """ Open existing district heating project. Registers project folder with folder manager, triggering automatic synchronization of all application components. :param folder_path: Path to existing project folder :type folder_path: str """ if folder_path: self.folder_manager.set_project_folder(folder_path)
[docs] def create_project_copy(self) -> bool: """ Create complete copy of current project with user-specified name. Prompts user for new project name via dialog, then creates exact duplicate including all data files, analysis results, and structure. Automatically activates the copied project. :return: True if successful, False if cancelled or failed :rtype: bool """ # Get base directory for project creation base_dir = os.path.dirname(self.folder_manager.project_folder) # Show interactive dialog for new project name input current_project_name = os.path.basename(self.folder_manager.project_folder) default_name = f"{current_project_name} - Kopie" new_project_name, ok = QInputDialog.getText( self.view, 'Projektkopie erstellen', 'Geben Sie einen neuen Namen für das Projekt ein:', text=default_name ) # Process user input and create project copy if ok and new_project_name: new_project_path = os.path.join(base_dir, new_project_name) # Check for naming conflicts if not os.path.exists(new_project_path): try: # Create complete copy of project directory shutil.copytree(self.folder_manager.project_folder, new_project_path) # Register copied project as active project self.folder_manager.set_project_folder(new_project_path) # Search for variants in copied project variants = [ folder for folder in os.listdir(new_project_path) if "Variante" in folder ] if variants: # Activate first available variant self.folder_manager.set_variant_folder(variants[0]) else: # Handle projects without variants default_variant_path = os.path.join(new_project_path, "Variante 1") if os.path.exists(default_variant_path): self.folder_manager.set_variant_folder("Variante 1") else: # Set project folder without specific variant self.folder_manager.set_project_folder(new_project_path) return True except Exception as e: # Provide detailed error feedback self.view.show_error_message(f"Ein Fehler ist aufgetreten: {str(e)}") return False else: # Handle naming conflicts self.view.show_error_message( f"Ein Projekt mit dem Namen '{new_project_name}' existiert bereits." ) return False else: # Handle user cancellation self.view.show_error_message("Projektkopie wurde abgebrochen.") return False
[docs] def create_project_variant(self) -> bool: """ Create new analysis variant within current project. Creates sequentially numbered variant (Variante X) with standardized subfolder structure (Ergebnisse, Gebäudedaten, Lastgang, Wärmenetz). Automatically activates new variant. :return: True if successful, False if failed :rtype: bool """ # Get base project directory for variant creation base_dir = self.folder_manager.project_folder variant_num = 1 # Find next available variant number while True: new_variant_name = f"Variante {variant_num}" new_variant_path = os.path.join(base_dir, new_variant_name) if not os.path.exists(new_variant_path): break variant_num += 1 try: # Create standardized variant folder structure variant_subdirs = [ "Ergebnisse", # Analysis results and outputs "Gebäudedaten", # Building data and characteristics "Lastgang", # Load profiles and demand data "Wärmenetz" # Heating network design and data ] # Create main variant folder and all subdirectories for subdir in variant_subdirs: os.makedirs(os.path.join(new_variant_path, subdir)) # Activate newly created variant self.folder_manager.set_variant_folder(new_variant_name) return True except Exception as e: # Provide detailed error feedback self.view.show_error_message(f"Fehler beim Erstellen der Variante: {e}") return False
[docs] def create_project_variant_copy(self) -> bool: """ Create exact copy of current variant with sequential numbering. Duplicates all data, analysis results, and configuration settings. Automatically activates new variant copy. :return: True if successful, False if failed :rtype: bool """ # Get base directory for variant creation base_dir = os.path.dirname(self.folder_manager.get_variant_folder()) variant_num = 1 # Find next available variant number while True: new_variant_name = f"Variante {variant_num}" new_variant_path = os.path.join(base_dir, new_variant_name) if not os.path.exists(new_variant_path): break variant_num += 1 try: # Create complete copy of current variant directory shutil.copytree(self.folder_manager.get_variant_folder(), new_variant_path) # Activate newly created variant copy self.folder_manager.set_variant_folder(new_variant_name) return True except Exception as e: # Provide detailed error feedback self.view.show_error_message(f"Fehler beim Kopieren der Variante: {e}") return False