PyELSSA 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,142 @@
1
+ # -*- coding: utf-8 -*-
2
+ #=================================================================
3
+ # Created by: Jieming Ye
4
+ # Created on: Aug 2026
5
+ # Last Modified: Aug 2026
6
+ #=================================================================
7
+ # Copyright (c) 2026 [Jieming Ye]
8
+ #
9
+ # This Python source code is licensed under the
10
+ # Open Source Non-Commercial License (OSNCL) v1.0
11
+ # See LICENSE for details.
12
+ #=================================================================
13
+ """
14
+ TBC
15
+ """
16
+ #=================================================================
17
+ # VERSION CONTROL
18
+ # V1.0 (Jieming Ye) - Initial Version
19
+ #
20
+ #
21
+ #=================================================================
22
+
23
+ import tkinter as tk
24
+ from tkinter import ttk
25
+ from PyELSSA.gui.gui_base_frame import BasePage
26
+
27
+
28
+ class SectionPage(BasePage):
29
+ page_title = ''
30
+ page_description = ''
31
+ page_actions = []
32
+ back_target = 'StartPage'
33
+
34
+ def __init__(self, parent, controller):
35
+ super().__init__(parent, controller)
36
+ self.build_page()
37
+
38
+ def build_page(self):
39
+ title = self.create_label(self.headframe, self.page_title, style='PageTitle.TLabel')
40
+ title.pack(anchor='center', pady=(0, 8))
41
+
42
+ if self.page_description:
43
+ description = self.create_label(self.headframe, self.page_description, style='PageText.TLabel', wraplength=860, justify='left')
44
+ description.pack(anchor='center', pady=(0, 8))
45
+
46
+ if self.page_actions:
47
+ actions_section = self.create_scrollable_section(self.contentframe, title='Available Actions', height=280)
48
+ for row, action in enumerate(self.page_actions):
49
+ text_label = ttk.Label(actions_section, text=action['label'], style='OptionText.TLabel', wraplength=620, justify='left')
50
+ text_label.grid(row=row, column=0, sticky='w', padx=(0, 8), pady=2)
51
+ action_button = self.create_button(actions_section, action['button'], lambda t=action['target']: self.set_target_page(t), style='Secondary.TButton')
52
+ action_button.grid(row=row, column=1, sticky='e', padx=(8, 0), pady=2)
53
+ actions_section.columnconfigure(0, weight=1)
54
+ actions_section.columnconfigure(1, weight=0)
55
+ else:
56
+ info = self.create_label(self.contentframe, 'This page is under development. Please check back later.', style='Info.TLabel', wraplength=860, justify='left')
57
+ info.pack(anchor='w', pady=(8, 12))
58
+
59
+ navigation = self.create_frame(padding=(0, 0))
60
+ self.create_button(navigation, 'Back to Home', lambda: self.controller.show_frame('StartPage')).pack(side='left', padx=6, pady=6)
61
+
62
+ def set_target_page(self, target_page):
63
+ self.controller.show_frame(target_page)
64
+
65
+
66
+ class Page0(SectionPage):
67
+ page_title = 'TO BE DEVELOPED'
68
+ page_description = 'This page will be developed in the future. Use the home page to navigate to available workflows.'
69
+ back_target = 'StartPage'
70
+
71
+
72
+ class PageOne(SectionPage):
73
+ page_title = 'ELSSA Model Preparation [Coming Soon...]'
74
+ page_description = 'Instruction: This page provides actions of creating ELSSA model, including create setup file and configuration file.'
75
+ 'This also provides interface with VISION OSLO including creating of Multi-Train Profile (C-matrix) for ELSSA modelling.'
76
+ page_actions = [
77
+ {'label': 'ELLSA Setup File Creation (Classical). [In Dev]', 'button': 'Open', 'target': 'S01'},
78
+ {'label': 'ELSSA Configuration File Creation (Classical). [In Dev]', 'button': 'Open', 'target': 'S02'},
79
+ {'label': 'Multi-Train Profile (c-matrix) Creation from VISION OSLO. [In Dev]', 'button': 'Open', 'target': 'S03'},
80
+ {'label': 'Multi-Train Profile (c-matrix) Manipulation. [In Dev]', 'button': 'Open', 'target': 'S04'},
81
+ {'label': 'Provision for development', 'button': 'Open', 'target': 'S05'},
82
+ ]
83
+
84
+
85
+ class PageTwo(SectionPage):
86
+ page_title = 'ELSSA Model Check and Validation [Coming Soon..]'
87
+ page_description = 'Instruction: This page provide checking of ELSSA model and generate visual plots as required. '
88
+ page_actions = [
89
+ {'label': 'Configuration Settings Plot. [In Dev]', 'button': 'Open', 'target': 'C01'},
90
+ {'label': 'ELSSA Model Setup File Plot. [In Dev]', 'button': 'Open', 'target': 'C02'},
91
+ {'label': 'ELSSA Model Connection Report. [In Dev]', 'button': 'Open', 'target': 'C03'},
92
+ {'label': 'ELSSA Model Rational Report. [In Dev]', 'button': 'Open', 'target': 'C04'},
93
+ {'label': 'Provision for development', 'button': 'Open', 'target': 'C05'},
94
+ ]
95
+
96
+
97
+ class PageThree(SectionPage):
98
+ page_title = 'ELSSA Simulation Run [Coming Soon...]'
99
+ page_description = 'Instruction: This page provides multiple default operation condition. '
100
+ page_actions = [
101
+ {'label': 'Short Circuit Simulation (Current Model).', 'button': 'Open', 'target': 'F01'},
102
+ {'label': 'Multi Train Simulation (Current Model).', 'button': 'Open', 'target': 'F02'},
103
+ {'label': 'Load Bank Simulation (Current Model).', 'button': 'Open', 'target': 'F03'},
104
+ {'label': 'Harmonic Simulation. [In Dev]', 'button': 'Open', 'target': 'F04'},
105
+ {'label': 'Load Bank Simulation (Power Model). [In Dev]', 'button': 'Open', 'target': 'F05'},
106
+ {'label': 'Multi Train Simulation (Voltage Model). [In Dev]', 'button': 'Open', 'target': 'F06'},
107
+ {'label': 'Provision for development', 'button': 'Open', 'target': 'F07'},
108
+ ]
109
+
110
+
111
+ class PageFour(SectionPage):
112
+ page_title = 'ELSSA Standard Plots [Coming Soon...]'
113
+ page_description = 'Instruction: This page provides standards plotting template for commonly used plotting.'
114
+ page_actions = [
115
+ {'label': 'Short-Circuit Default Plotting [In Dev]', 'button': 'Open', 'target': 'P01'},
116
+ {'label': 'Multi-Train Default Plotting [In Dev]', 'button': 'Open', 'target': 'P02'},
117
+ {'label': 'Report Style Default Plotting - Single Plot [In Dev]', 'button': 'Open', 'target': 'P03'},
118
+ {'label': 'Customised Plots in Standard Frame. [In Dev]', 'button': 'Open', 'target': 'P04'},
119
+ {'label': 'To be developed', 'button': 'Open', 'target': 'P05'},
120
+ ]
121
+
122
+ class PageFive(SectionPage):
123
+ page_title = 'ELSSA Batch Operation [Coming Soon...]'
124
+ page_description = 'Instruction: This page provides unattended simulation of multiple operating scenarios.'
125
+ page_actions = [
126
+ {'label': 'Batch Simulation [In Dev]', 'button': 'Open', 'target': 'B01'},
127
+ {'label': 'Batch Plotting [In Dev]', 'button': 'Open', 'target': 'B02'},
128
+ {'label': 'To be developed', 'button': 'Open', 'target': 'B03'},
129
+ ]
130
+
131
+
132
+ class PageSix(SectionPage):
133
+ page_title = 'ELSSA Customised Data Analysis [Coming Soon...]'
134
+ page_description = 'Instruction: This page generates mutiple excel datasheet based on simulation outcome.'
135
+ 'User can use the data to carry out customised analysis.'
136
+ page_actions = [
137
+ {'label': 'All Data Extraction. [In Dev]', 'button': 'Open', 'target': 'P12'},
138
+ {'label': 'Short-Circuit Conductor Datasheet [In Dev]', 'button': 'Open', 'target': 'P09'},
139
+ {'label': 'Multi-Train Conductor Datasheet [In Dev]', 'button': 'Open', 'target': 'P10'},
140
+ {'label': 'Lumped Impedance Anlaysis [In Dev]', 'button': 'Open', 'target': 'P14'},
141
+ {'label': 'To be developed', 'button': 'Open', 'target': 'P15'},
142
+ ]
@@ -0,0 +1,424 @@
1
+ #
2
+ # -*- coding: utf-8 -*-
3
+ #=================================================================
4
+ # Created by: Jieming Ye
5
+ # Created on: Aug 2026
6
+ # Last Modified: Aug 2026
7
+ #=================================================================
8
+ # Copyright (c) 2026 [Jieming Ye]
9
+ #
10
+ # This Python source code is licensed under the
11
+ # Open Source Non-Commercial License (OSNCL) v1.0
12
+ # See LICENSE for details.
13
+ #=================================================================
14
+ """
15
+ Main GUI window and Manual Bar
16
+
17
+ """
18
+ #=================================================================
19
+ # VERSION CONTROL
20
+ # V1.0 (Jieming Ye) - Initial Version
21
+ #
22
+ #
23
+ #=================================================================
24
+
25
+ # Third Party
26
+ import tkinter as tk
27
+ from tkinter import filedialog, ttk
28
+ from tkinter import font as tkfont
29
+ from tkinter import filedialog
30
+ from tkinter import messagebox
31
+ import os
32
+ import webbrowser
33
+ import subprocess
34
+ import threading
35
+ import queue
36
+ from functools import partial
37
+ from urllib.parse import quote
38
+
39
+ # Internal imports
40
+ from PyELSSA import configuration
41
+ from PyELSSA.gui.gui_base_frame import BasePage
42
+ from PyELSSA.shared_contents import SharedVariables, SharedMethods
43
+
44
+
45
+ # Commonly accessed frames to preload
46
+ PRELOAD_FRAMES = {
47
+ 'PageOne', 'PageTwo', 'PageThree', 'PageFour', 'PageFive', 'PageSix'
48
+ }
49
+
50
+ # Lazy imports for frames
51
+ def import_frames():
52
+ from PyELSSA.gui.gui_main_page import Page0, PageOne, PageTwo, PageThree, PageFour, PageFive, PageSix
53
+ from PyELSSA.gui.gui_sub_frame import (
54
+ P01, P02, P03, P04, P05, P06, P07, P08, P09, P10, P11, P12, P13, P14, P15, P16, P17
55
+ )
56
+
57
+ return {
58
+ 'Page0': Page0, 'PageOne': PageOne, 'PageTwo': PageTwo, 'PageThree': PageThree,
59
+ 'PageFour': PageFour, 'PageFive': PageFive, 'PageSix': PageSix,
60
+ 'P01': P01, 'P02': P02, 'P03': P03, 'P04': P04, 'P05': P05, 'P06': P06,
61
+ 'P07': P07, 'P08': P08, 'P09': P09, 'P10': P10, 'P11': P11, 'P12': P12,
62
+ 'P13': P13, 'P14': P14, 'P15': P15, 'P16': P16, 'P17': P17,
63
+ }
64
+
65
+
66
+ class SampleApp(tk.Tk):
67
+ def __init__(self, *args, **kwargs):
68
+ tk.Tk.__init__(self, *args, **kwargs)
69
+
70
+ # Set initial window size and make it resizable
71
+ # self.geometry("700x500")
72
+ # self.resizable(True, True)
73
+
74
+ self.title_font = tkfont.Font(family='Helvetica', size=18, weight="bold")
75
+ self.sub_title_font = tkfont.Font(family='Helvetica', size=12, weight="bold")
76
+ self.big_text_font = tkfont.Font(family='Helvetica', size=10, weight="bold")
77
+ self.text_font = tkfont.Font(family='Helvetica', size=10)
78
+ self.title('ELSSA on Python')
79
+
80
+ # add shared string variable of current work directory
81
+ self.working_directory = tk.StringVar()
82
+ self.working_directory.set(os.getcwd())
83
+
84
+ # Setup the container
85
+ container = tk.Frame(self)
86
+ container.pack(side="top", fill="both", expand=True)
87
+ container.grid_rowconfigure(0, weight=1)
88
+ container.grid_columnconfigure(0, weight=1)
89
+
90
+ self.frames = {}
91
+ self._frame_classes = None # Will store frame classes when needed
92
+ self._container = container
93
+ self._preload_queue = queue.Queue()
94
+
95
+ # Initialize only the start page
96
+ frame = StartPage(parent=container, controller=self)
97
+ self.frames["StartPage"] = frame
98
+ frame.grid(row=0, column=0, sticky="nsew")
99
+
100
+ self.show_frame("StartPage")
101
+ self._setup_menu()
102
+
103
+ # Start preloading common frames after a short delay
104
+ self.after(100, self._start_preloading)
105
+
106
+ def _start_preloading(self):
107
+ """Start background thread to preload common frames."""
108
+ def preload_worker():
109
+ try:
110
+ if self._frame_classes is None:
111
+ self._frame_classes = import_frames() # import the frame classes
112
+
113
+ for page_name in PRELOAD_FRAMES:
114
+ if page_name not in self.frames and page_name in self._frame_classes:
115
+ frame_class = self._frame_classes[page_name]
116
+ # Queue the frame creation to be done in the main thread
117
+ self._preload_queue.put((page_name, frame_class))
118
+ # Process one frame at a time in the main thread
119
+ self.after(0, self._process_preload_queue)
120
+ except Exception as e:
121
+ print(f"Preloading warning: {e}")
122
+
123
+ thread = threading.Thread(target=preload_worker)
124
+ thread.daemon = True
125
+ thread.start()
126
+
127
+ def _process_preload_queue(self):
128
+ """Process queued frame creation in the main thread."""
129
+ try:
130
+ while not self._preload_queue.empty():
131
+ page_name, frame_class = self._preload_queue.get_nowait()
132
+ if page_name not in self.frames:
133
+ frame = frame_class(parent=self._container, controller=self)
134
+ self.frames[page_name] = frame
135
+ frame.grid(row=0, column=0, sticky="nsew")
136
+ frame.grid_remove() # Hide the frame
137
+ except queue.Empty:
138
+ pass
139
+ except Exception as e:
140
+ print(f"Frame creation warning: {e}")
141
+
142
+ def _setup_menu(self):
143
+ # define the top bar menu
144
+ menu = tk.Menu(self)
145
+ self.config(menu=menu)
146
+
147
+ # sub-menu 1 = filemenu
148
+ filemenu = tk.Menu(menu)
149
+ menu.add_cascade(label='File', menu=filemenu)
150
+ filemenu.add_command(label='Open File', command=lambda: self.open_file_dialog())
151
+ filemenu.add_separator()
152
+ filemenu.add_command(label='Select Working Directory', command=lambda: self.set_working_directory())
153
+ filemenu.add_separator()
154
+ filemenu.add_command(label='Ulti: Clean Simulation Folder', command=lambda: self.clean_simulation_folder())
155
+ filemenu.add_separator()
156
+ filemenu.add_command(label='Exit', command=self.quit)
157
+
158
+ # submeanu 3 osop configuration
159
+ osopmenu = tk.Menu(menu)
160
+ menu.add_cascade(label='ELSSA Version', menu = osopmenu)
161
+
162
+ self.config_var = tk.IntVar()
163
+ last_config = configuration.read_configuration_file_variable(key = "elssa_version")
164
+ if not last_config:
165
+ last_config = SharedVariables.elssa_version
166
+ configuration.reset_configuration_file_varible(key = "elssa_version")
167
+ else:
168
+ last_config = int(last_config)
169
+ self.config_var.set(last_config) # set to last configuration
170
+ SharedVariables.elssa_version = last_config
171
+
172
+ osopmenu.add_radiobutton(label='PyELSSA RN-1 (Beta)', variable=self.config_var, value=1, command=lambda: self.elssa_option_action(1))
173
+ # osopmenu.add_radiobutton(label='RyELSSA RN-2 (Coming Soon)', variable=self.config_var, value=2, command=lambda: self.elssa_option_action(2))
174
+ # osopmenu.add_radiobutton(label='RyELSSA RN-3 (Coming Soon)', variable=self.config_var, value=3, command=lambda: self.elssa_option_action(3))
175
+
176
+ # sub menu4 dataset configuration
177
+ datamenu = tk.Menu(menu)
178
+ menu.add_cascade(label='Database',menu = datamenu)
179
+
180
+ self.dataset_var = tk.IntVar()
181
+ last_config_dataset = configuration.read_configuration_file_variable(key = "database_location")
182
+ if not last_config_dataset:
183
+ last_config_dataset = SharedVariables.used_database_path
184
+ configuration.reset_configuration_file_varible(key = "database_location")
185
+ else:
186
+ last_config_dataset = int(last_config_dataset)
187
+
188
+ if last_config == 2:
189
+ SharedVariables.used_database_path = SharedVariables.database_path_user
190
+ else:
191
+ SharedVariables.used_database_path = SharedVariables.database_path_default
192
+
193
+ self.dataset_var.set(last_config_dataset)
194
+ datamenu.add_radiobutton(label='Default', variable=self.dataset_var, value=1, command=lambda: self.data_option_action(1))
195
+ datamenu.add_radiobutton(label='Customised', variable=self.dataset_var, value=2, command=lambda: self.data_option_action(2))
196
+ datamenu.add_separator()
197
+ datamenu.add_command(label='(Cust) Go to data library', command=lambda: self.open_data_library())
198
+ datamenu.add_command(label='(Cust) Restore single data file', command=lambda: self.restore_data_to_default(singlefile=True))
199
+ datamenu.add_command(label='(Cust) Restore all data file', command=lambda: self.restore_data_to_default(singlefile=False))
200
+ datamenu.add_command(label='Default library forced refresh', command=lambda: configuration.update_database_library())
201
+
202
+ # sub-menu 2 = helpmenu
203
+ helpmenu = tk.Menu(menu)
204
+ menu.add_cascade(label='Help', menu = helpmenu)
205
+
206
+ # helpmenu.add_command(label='Help Document (Internal ONLY)',command = lambda: AzureWorkflow.open_file_from_sharepoint('VISION-OSLO Extension User Guide.pdf'))
207
+ # helpmenu.add_separator()
208
+ helpmenu.add_command(label='Report PyELSSA Issue (Internal ONLY)',command = lambda: webbrowser.open(SharedVariables.issue_online))
209
+ # helpmenu.add_command(label='Report VISION OSLO Issue (Internal ONLY)',command = lambda: webbrowser.open(SharedVariables.vo_issue_online))
210
+ helpmenu.add_separator()
211
+ # helpmenu.add_command(label='Support: View BHTPBANK library (Internal ONLY)', command=lambda: webbrowser.open(SharedVariables.bhtpbank_central_library))
212
+ # helpmenu.add_separator()
213
+ helpmenu.add_command(label='Support for External User (External)',command=self.email_external_support)
214
+ helpmenu.add_separator()
215
+ helpmenu.add_command(label='Help - Email to Support',command=self.email_external_support)
216
+ helpmenu.add_separator()
217
+ helpmenu.add_command(label='About',command = self.show_about_popup)
218
+
219
+ def show_frame(self, page_name):
220
+ '''Show a frame for the given page name'''
221
+ # Create the frame if it doesn't exist
222
+ if page_name not in self.frames:
223
+ if self._frame_classes is None:
224
+ self._frame_classes = import_frames()
225
+
226
+ frame_class = self._frame_classes.get(page_name)
227
+ if frame_class:
228
+ frame = frame_class(parent=self._container, controller=self)
229
+ self.frames[page_name] = frame
230
+ frame.grid(row=0, column=0, sticky="nsew")
231
+
232
+ # Show the requested frame
233
+ frame = self.frames[page_name]
234
+ frame.grid() # Ensure the frame is shown
235
+ frame.tkraise()
236
+
237
+ def elssa_option_action(self, version):
238
+ """Handle elssa version selection."""
239
+ if version == 1:
240
+ SharedVariables.osop_version = 1
241
+ SharedMethods.print_message("WARNING: PyELSSA RN-1 is selected.","33")
242
+ elif version == 2:
243
+ SharedVariables.osop_version = 2
244
+ SharedMethods.print_message("WARNING: PyELSSA RN-2 is selected.","33")
245
+ elif version == 3:
246
+ SharedVariables.osop_version = 3
247
+ SharedMethods.print_message("WARNING: PyELSSA RN-3 is selected.","33")
248
+ else:
249
+ SharedMethods.print_message("ERROR: Invalid PyELSSA revision selected.","31")
250
+ # set the configuration file
251
+ configuration.set_configuration_file_variable(key = "elssa_version", value = version)
252
+
253
+ def data_option_action(self, choice):
254
+ if choice == 1:
255
+ SharedVariables.used_database_path = SharedVariables.database_path_default
256
+ SharedMethods.print_message(f"WARNING: Database path set to '{SharedVariables.database_path_default}'.","33")
257
+ elif choice == 2:
258
+ SharedVariables.used_database_path = SharedVariables.database_path_user
259
+ SharedMethods.print_message(f"WARNING: Database path set to '{SharedVariables.used_database_path}'.","33")
260
+ else:
261
+ SharedMethods.print_message("ERROR: Invalid database configuration selected.","31")
262
+ # set the configuration file
263
+ configuration.set_configuration_file_variable(key = "database_location", value = choice)
264
+
265
+
266
+ def email_external_support(self):
267
+ to = "traction.power@networkrail.co.uk"
268
+ subject = "Support Requested: ELSSA on Python"
269
+ body = "Hello,\n\nPlease describe your issue here.\n\nRegards,"
270
+
271
+ mailto_link = f"mailto:{to}?subject={quote(subject)}&body={quote(body)}"
272
+ webbrowser.open(mailto_link)
273
+
274
+ def show_about_popup(self):
275
+ about_popup = tk.Toplevel(self)
276
+ about_popup.title("About-Copyright")
277
+
278
+ about_text = f"PyELSSA (ELSSA on Python), version {SharedVariables.installed_version}"
279
+ about_font = tkfont.Font(family='Helvetica', size=11, weight="bold")
280
+ about_label = tk.Label(about_popup, text=about_text, font = about_font, padx=10, pady=10)
281
+ about_label.pack()
282
+
283
+ info_text = "This is Developed by Jieming Ye.\n"\
284
+ "Copyright (c) 2026 [Jieming Ye, Engineering Services].\n"\
285
+ f"Link Validate: {SharedVariables.lastupdate}.\n"\
286
+ "License: OSNCL V1.0.\n"\
287
+ "For Support Please Consult 'traction.power@networkrail.co.uk'"
288
+ #info_label = tk.Message(about_popup, text=info_text, aspect = 800, padx=10, pady=10)
289
+ info_label = tk.Label(about_popup, text=info_text, padx=10, pady=10, justify="left")
290
+ info_label.pack()
291
+
292
+ # Center the popup window on the screen
293
+ about_popup.geometry("+%d+%d" % (self.winfo_rootx() + self.winfo_width() // 2 - about_popup.winfo_reqwidth() // 2,
294
+ self.winfo_rooty() + self.winfo_height() // 2 - about_popup.winfo_reqheight() // 2))
295
+
296
+
297
+ # Create a function to be called when the "Open" menu option is clicked
298
+ def open_file_dialog(self):
299
+ initial_dir = os.getcwd() # Get the current working directory
300
+ file_path = filedialog.askopenfilename(title="Select a file",initialdir=initial_dir)
301
+ if file_path:
302
+ print(f"Selected file: {file_path}")
303
+ SharedMethods.print_message("WARNING: DO NOT EXIT THE PROGRAME BEFORE SAVING THE OPENED FILE.","33")
304
+ self.open_file(file_path)
305
+
306
+ def open_file(self, file_path):
307
+ try:
308
+ subprocess.Popen(['start', '', file_path], shell=True,close_fds=True)
309
+ except Exception as e:
310
+ SharedMethods.print_message(f"ERROR: Error opening the selected file with default app: {e}","31")
311
+
312
+ # open the file explorer to select the working directory
313
+ def set_working_directory(self):
314
+ initial_dir = os.getcwd()
315
+ print(f"Current working directory:\n{initial_dir}")
316
+ directory_path = filedialog.askdirectory(title="Select Working Directory", initialdir=initial_dir)
317
+ if directory_path:
318
+ os.chdir(directory_path)
319
+ SharedVariables.current_path = directory_path
320
+ self.working_directory.set(directory_path)
321
+ SharedMethods.print_message(f"WARNING: Working directiory set to:\n{directory_path}","33")
322
+
323
+ # open the data library folder
324
+ def open_data_library(self):
325
+ try:
326
+ if os.path.exists(SharedVariables.database_path_user):
327
+ os.startfile(SharedVariables.database_path_user)
328
+ else:
329
+ SharedMethods.print_message(f"ERROR: Data library folder not found at:\n{SharedVariables.database_path_user}","31")
330
+ except Exception as e:
331
+ SharedMethods.print_message(f"ERROR: Error opening data library folder: {e}","31")
332
+
333
+ # restore all files to default
334
+ def restore_data_to_default(self,singlefile=False):
335
+ if singlefile:
336
+ # open file dialog asking user the select the file and get the file name
337
+ initial_dir = SharedVariables.database_path_user
338
+ file_path = filedialog.askopenfilename(title="Select Data File to Restore",initialdir=initial_dir)
339
+ if file_path: # only if user actually picked a file
340
+ filename = os.path.basename(file_path)
341
+ configuration.update_database_library([filename],library=1)
342
+ else:
343
+ # add a pop up confirmation ok small window to confirm
344
+ confirm = messagebox.askyesno(
345
+ "WARNING: CONFIRM UPDATE",
346
+ "This action CANNOT be redo.\n\n"
347
+ "Are you sure to CONTINUE??"
348
+ )
349
+ if confirm:
350
+ try:
351
+ configuration.update_database_library(library=1)
352
+ except Exception as e:
353
+ SharedMethods.print_message(f"ERROR: {str(e)}", "31")
354
+
355
+ def clean_simulation_folder(self):
356
+ confirm = messagebox.askyesno(
357
+ "WARNING: CONFIRM DELETE",
358
+ "WARNING: Ensure correct working directory set.\n\n"
359
+ "This action will delete all files except the model and CANNOT be redo.\n\n"
360
+ "Are you sure to CONTINUE??"
361
+ )
362
+
363
+ if confirm:
364
+ try:
365
+ SharedMethods.clean_up_simulation_folder()
366
+ except Exception as e:
367
+ SharedMethods.print_message(f"ERROR: {str(e)}", "31")
368
+
369
+
370
+ class StartPage(BasePage):
371
+ def __init__(self, parent, controller):
372
+ super().__init__(parent, controller)
373
+ self.build_page()
374
+
375
+ def build_page(self):
376
+ title = self.create_label(self.headframe, 'PyELSSA (ELSSA on Python)', style='PageTitle.TLabel')
377
+ title.pack(anchor='center', pady=(0, 6))
378
+
379
+ subtitle = self.create_label(
380
+ self.headframe,
381
+ 'ELSSA: Electrification System Simulation Analysis [Multi-Conductor Simulation Tool]',
382
+ style='PageText.TLabel',
383
+ wraplength=820,
384
+ justify='center'
385
+ )
386
+ subtitle.pack(anchor='center', pady=(0, 16))
387
+
388
+ overview_section = self.create_section(self.contentframe, title='Quick Start')
389
+ overview_section.columnconfigure(0, weight=1)
390
+ overview_section.columnconfigure(1, weight=1)
391
+
392
+ nav_cards = [
393
+ ('Model Prepare (In Dev)', 'Prepare ELSSA models and configuration data.', 'PageOne'),
394
+ ('Model Check (In Dev)', 'Check ELSSA model rational and generate user report.', 'PageTwo'),
395
+ ('Run Simulation (In Dev)', 'Run ELSSA simulation in user selected mode.', 'PageThree'),
396
+ ('Standard Plots (In Dev)', 'Plot standard plots in Engineering Services template.', 'PageFour'),
397
+ ('Batch Process (In Dev)', 'Run multiple simulation scenarios unattended.', 'PageFive'),
398
+ ('Result Analysis (In Dev)', 'Generate Excel data for customised plotting.', 'PageSix'),
399
+ ]
400
+
401
+ for index, (label_text, hint, target) in enumerate(nav_cards):
402
+ row = index // 2
403
+ col = index % 2
404
+ card = self.create_section(overview_section, title=label_text, padding=(12, 10), pack=False)
405
+ card.grid(row=row, column=col, sticky='nsew', padx=2, pady=2)
406
+ # self.create_label(card, label_text, style='PageSubtitle.TLabel').pack(anchor='w', pady=(0, 8))
407
+ self.create_label(card, hint, style='OptionText.TLabel', wraplength=360, justify='left').pack(anchor='w', pady=(0, 8))
408
+ self.create_button(card, label_text, lambda t=target: self.button_callback(t), style='Action.TButton').pack(anchor='w')
409
+
410
+ status_section = self.create_section(self.contentframe, title='Status')
411
+ self.create_label(status_section, f'Version {SharedVariables.installed_version} — {SharedVariables.copyright}', style='SmallText.TLabel').pack(anchor='w', pady=2)
412
+ self.create_label(status_section, f'Note: {SharedVariables.status_note}', style='SmallText.TLabel').pack(anchor='w', pady=2)
413
+
414
+ self.footerframe.columnconfigure(1, weight=1)
415
+ self.create_label(self.footerframe, 'Current Folder:', style='PageText.TLabel').grid(row=0, column=0, sticky='w', padx=5, pady=6)
416
+ ttk.Label(self.footerframe, textvariable=self.controller.working_directory, style='PageText.TLabel', wraplength=780, justify='left').grid(row=0, column=1, sticky='w', padx=5, pady=6)
417
+
418
+ def button_callback(self, target_page):
419
+ self.controller.show_frame(target_page)
420
+
421
+ # programme running
422
+ if __name__ == '__main__':
423
+ app = SampleApp()
424
+ app.mainloop()