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.
- PyELSSA/__init__.py +28 -0
- PyELSSA/configuration.py +325 -0
- PyELSSA/data/TestFile.txt +0 -0
- PyELSSA/data/TestFile1.csv +0 -0
- PyELSSA/data/TestFile2.csv +0 -0
- PyELSSA/gui/gui_base_frame.py +221 -0
- PyELSSA/gui/gui_main_page.py +142 -0
- PyELSSA/gui/gui_start.py +424 -0
- PyELSSA/gui/gui_sub_frame.py +414 -0
- PyELSSA/licensing.py +224 -0
- PyELSSA/master.py +87 -0
- PyELSSA/release_update.py +59 -0
- PyELSSA/shared_contents.py +491 -0
- PyELSSA/shared_msazure_api.py +469 -0
- pyelssa-0.1.0.dist-info/METADATA +44 -0
- pyelssa-0.1.0.dist-info/RECORD +18 -0
- pyelssa-0.1.0.dist-info/WHEEL +4 -0
- pyelssa-0.1.0.dist-info/licenses/LICENSE +88 -0
|
@@ -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
|
+
]
|
PyELSSA/gui/gui_start.py
ADDED
|
@@ -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()
|