buckpy-dev 0.0.1__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.
- buckpy/__init__.py +13 -0
- buckpy/_static/logo.png +0 -0
- buckpy/_static/logo.svg +4 -0
- buckpy/buckfast_input_file_writer_.py +1233 -0
- buckpy/buckpy.py +132 -0
- buckpy/buckpy_gui.py +359 -0
- buckpy/buckpy_postprocessing.py +1305 -0
- buckpy/buckpy_preprocessing_current.py +1142 -0
- buckpy/buckpy_preprocessing_legacy.py +900 -0
- buckpy/buckpy_solver.py +777 -0
- buckpy/buckpy_variables.py +98 -0
- buckpy/buckpy_visualisation.py +419 -0
- buckpy_dev-0.0.1.dist-info/METADATA +51 -0
- buckpy_dev-0.0.1.dist-info/RECORD +18 -0
- buckpy_dev-0.0.1.dist-info/WHEEL +5 -0
- buckpy_dev-0.0.1.dist-info/entry_points.txt +2 -0
- buckpy_dev-0.0.1.dist-info/licenses/LICENSE +674 -0
- buckpy_dev-0.0.1.dist-info/top_level.txt +1 -0
buckpy/buckpy.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This is the main module of BuckPy.
|
|
3
|
+
"""
|
|
4
|
+
from . import buckpy_gui
|
|
5
|
+
from . import buckpy_preprocessing_legacy
|
|
6
|
+
from . import buckpy_preprocessing_current
|
|
7
|
+
from . import buckpy_solver
|
|
8
|
+
from . import buckpy_postprocessing
|
|
9
|
+
from . import buckpy_visualisation
|
|
10
|
+
|
|
11
|
+
def main() -> None:
|
|
12
|
+
"""
|
|
13
|
+
Run the full BuckPy workflow from GUI-provided inputs.
|
|
14
|
+
|
|
15
|
+
The function opens the BuckPy GUI, reads user selections, validates required
|
|
16
|
+
inputs, parses scenario identifiers, and executes the processing pipeline for
|
|
17
|
+
each scenario.
|
|
18
|
+
|
|
19
|
+
Returns
|
|
20
|
+
-------
|
|
21
|
+
None
|
|
22
|
+
|
|
23
|
+
Notes
|
|
24
|
+
-----
|
|
25
|
+
Workflow:
|
|
26
|
+
1. Launch GUI and collect configuration.
|
|
27
|
+
2. Validate working directory and input file selection.
|
|
28
|
+
3. Parse scenario IDs from comma-separated text.
|
|
29
|
+
4. Run legacy or current preprocessing depending on Excel format.
|
|
30
|
+
5. Run the solver.
|
|
31
|
+
6. Run postprocessing and visualization.
|
|
32
|
+
|
|
33
|
+
Side Effects:
|
|
34
|
+
1. Starts a GUI event loop.
|
|
35
|
+
2. Reads input files from the selected working directory.
|
|
36
|
+
3. Writes output artifacts through postprocessing and visualization modules.
|
|
37
|
+
4. Prints progress messages to stdout.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
# Load user inputs from the GUI interface
|
|
41
|
+
gui = buckpy_gui.GUI()
|
|
42
|
+
gui.root.mainloop()
|
|
43
|
+
|
|
44
|
+
# Retrieve user selections from the GUI
|
|
45
|
+
user_config = {
|
|
46
|
+
"excel_format": gui.excel_format,
|
|
47
|
+
"work_dir": gui.work_dir,
|
|
48
|
+
"input_file_name": gui.input_file_name,
|
|
49
|
+
"pipeline_id": gui.pipeline_id,
|
|
50
|
+
"scenario_id": gui.scenario_id,
|
|
51
|
+
"bl_verbose": gui.bl_verbose,
|
|
52
|
+
"output_combination": gui.output_combination,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# Check if the user selected a file and provided required info
|
|
56
|
+
if not user_config["work_dir"] or not user_config["input_file_name"]:
|
|
57
|
+
print('No input file selected. Exiting.')
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
# Parse scenario IDs as a list of integers
|
|
61
|
+
scenario_list_id = [
|
|
62
|
+
int(s.strip()) for s in user_config["scenario_id"].split(',') if s.strip().isdigit()
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
# Print start message
|
|
66
|
+
print('====================== Start Processing ========================')
|
|
67
|
+
|
|
68
|
+
for scenario_id in scenario_list_id:
|
|
69
|
+
|
|
70
|
+
# Print current scenario being processed
|
|
71
|
+
print(f'== Processing Pipeline: {user_config["pipeline_id"]}, Scenario ID: {scenario_id}')
|
|
72
|
+
|
|
73
|
+
# Load and preprocess scenario data from the input Excel file
|
|
74
|
+
if user_config["excel_format"] == "Legacy":
|
|
75
|
+
np_distr, np_scen, np_ends, df_scen, df_route, df_pp = buckpy_preprocessing_legacy.run(
|
|
76
|
+
user_config["work_dir"],
|
|
77
|
+
user_config["input_file_name"],
|
|
78
|
+
user_config["pipeline_id"],
|
|
79
|
+
scenario_id,
|
|
80
|
+
user_config["bl_verbose"]
|
|
81
|
+
)
|
|
82
|
+
else:
|
|
83
|
+
preprocessor = buckpy_preprocessing_current.PreProcessor(
|
|
84
|
+
user_config["work_dir"],
|
|
85
|
+
user_config["input_file_name"],
|
|
86
|
+
user_config["pipeline_id"],
|
|
87
|
+
scenario_id,
|
|
88
|
+
user_config["bl_verbose"]
|
|
89
|
+
)
|
|
90
|
+
np_scen, np_distr, np_ends, df_scen, df_route, df_pp = preprocessor.run()
|
|
91
|
+
|
|
92
|
+
# Run BuckPy solver for deterministic and Monte Carlo simulations
|
|
93
|
+
df_pp_plot, df_vap_plot, df_pp_buckle_prop = buckpy_solver.exec_buckpy(
|
|
94
|
+
np_scen,
|
|
95
|
+
np_distr,
|
|
96
|
+
np_ends,
|
|
97
|
+
df_scen["Simulations"].values[0],
|
|
98
|
+
df_scen["Friction Sampling"].values[0],
|
|
99
|
+
user_config["bl_verbose"]
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Post-process simulation results and generate summary outputs
|
|
103
|
+
buckpy_postprocessing.pp_buckpy(
|
|
104
|
+
user_config["work_dir"],
|
|
105
|
+
user_config["input_file_name"],
|
|
106
|
+
df_scen,
|
|
107
|
+
df_route,
|
|
108
|
+
df_pp,
|
|
109
|
+
df_pp_plot,
|
|
110
|
+
df_vap_plot,
|
|
111
|
+
df_pp_buckle_prop,
|
|
112
|
+
user_config["output_combination"],
|
|
113
|
+
user_config["bl_verbose"],
|
|
114
|
+
user_config["excel_format"]
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Generate additional plots and visualizations for BuckPy results
|
|
118
|
+
buckpy_visualisation.plot_buckpy(
|
|
119
|
+
user_config["work_dir"],
|
|
120
|
+
user_config["input_file_name"],
|
|
121
|
+
df_scen,
|
|
122
|
+
df_route,
|
|
123
|
+
df_pp,
|
|
124
|
+
user_config["bl_verbose"]
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Print end message
|
|
128
|
+
print('======================= End Processing =========================')
|
|
129
|
+
|
|
130
|
+
if __name__ == '__main__':
|
|
131
|
+
# Run the main function to execute the BuckPy workflow
|
|
132
|
+
main()
|
buckpy/buckpy_gui.py
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
'''
|
|
2
|
+
This module contains the GUI of BuckPy.
|
|
3
|
+
'''
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import tkinter as tk
|
|
7
|
+
from tkinter import ttk
|
|
8
|
+
from tkinter import font
|
|
9
|
+
from tkinter import filedialog
|
|
10
|
+
import pandas as pd
|
|
11
|
+
from importlib.resources import files, as_file
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
class GUI:
|
|
15
|
+
'''
|
|
16
|
+
BuckPy Graphical User Interface (GUI) class.
|
|
17
|
+
|
|
18
|
+
This class creates and manages the main window for user interaction,
|
|
19
|
+
allowing users to select input files, enter pipeline and scenario IDs,
|
|
20
|
+
choose options, and view scenario data in a table. It handles all
|
|
21
|
+
user input validation and provides a structured interface for running
|
|
22
|
+
BuckPy workflows.
|
|
23
|
+
'''
|
|
24
|
+
|
|
25
|
+
def __init__(self):
|
|
26
|
+
'''
|
|
27
|
+
Initialize the BuckPy GUI.
|
|
28
|
+
|
|
29
|
+
Sets up the main window, configures fonts and geometry, creates
|
|
30
|
+
top and bottom frames, and initializes all widgets for user input.
|
|
31
|
+
'''
|
|
32
|
+
# Store the root window
|
|
33
|
+
self.root = tk.Tk()
|
|
34
|
+
|
|
35
|
+
# Set window title
|
|
36
|
+
self.root.title('BuckPy')
|
|
37
|
+
|
|
38
|
+
# Set window icon
|
|
39
|
+
try:
|
|
40
|
+
logo_res = files("buckpy").joinpath("_static", "logo.png")
|
|
41
|
+
with as_file(logo_res) as logo_path:
|
|
42
|
+
self.root.iconphoto(False, tk.PhotoImage(file=str(logo_path)))
|
|
43
|
+
except (ModuleNotFoundError, FileNotFoundError, OSError, PermissionError, tk.TclError):
|
|
44
|
+
try:
|
|
45
|
+
fallback = Path(__file__).with_name("_static") / "logo.png"
|
|
46
|
+
if fallback.exists():
|
|
47
|
+
self.root.iconphoto(False, tk.PhotoImage(file=str(fallback)))
|
|
48
|
+
except (OSError, PermissionError, tk.TclError):
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
# Update geometry info
|
|
52
|
+
self.root.update_idletasks()
|
|
53
|
+
window_width = 1366
|
|
54
|
+
window_height = 768
|
|
55
|
+
screen_width = self.root.winfo_screenwidth()
|
|
56
|
+
screen_height = self.root.winfo_screenheight()
|
|
57
|
+
x = (screen_width // 2) - (window_width // 2)
|
|
58
|
+
y = (screen_height // 2) - (window_height // 2)
|
|
59
|
+
self.root.geometry(f'{window_width}x{window_height}+{x}+{y}')
|
|
60
|
+
|
|
61
|
+
# Set modern default font
|
|
62
|
+
default_font = font.nametofont('TkDefaultFont')
|
|
63
|
+
default_font.configure(family='Segoe UI', size=11)
|
|
64
|
+
self.root.option_add('*Font', default_font)
|
|
65
|
+
|
|
66
|
+
# Create top and bottom frames for layout
|
|
67
|
+
self.top_frame = tk.Frame(self.root, padx=20, pady=20)
|
|
68
|
+
self.top_frame.grid(row=0, column=0, sticky='ew')
|
|
69
|
+
self.bottom_frame = tk.Frame(self.root, padx=20)
|
|
70
|
+
self.bottom_frame.grid(row=1, column=0, sticky='nsew')
|
|
71
|
+
|
|
72
|
+
# Add a bottom padding frame for spacing
|
|
73
|
+
self.bottom_padding = tk.Frame(self.root, height=20)
|
|
74
|
+
self.bottom_padding.grid(row=2, column=0, sticky='ew')
|
|
75
|
+
|
|
76
|
+
# Configure grid weights for resizing behavior
|
|
77
|
+
self.root.grid_columnconfigure(0, weight=1)
|
|
78
|
+
self.root.grid_rowconfigure(1, weight=1)
|
|
79
|
+
self.root.grid_rowconfigure(2, weight=0)
|
|
80
|
+
|
|
81
|
+
# Set minimum sizes and weights for columns in the top frame
|
|
82
|
+
self.top_frame.grid_columnconfigure(1, minsize=200)
|
|
83
|
+
self.top_frame.grid_columnconfigure(3, minsize=100)
|
|
84
|
+
self.top_frame.grid_columnconfigure(5, minsize=200, weight=1)
|
|
85
|
+
|
|
86
|
+
# Make the Treeview expand in the bottom frame
|
|
87
|
+
self.bottom_frame.grid_rowconfigure(0, weight=1)
|
|
88
|
+
self.bottom_frame.grid_columnconfigure(0, weight=1)
|
|
89
|
+
|
|
90
|
+
# Initialize file path variables
|
|
91
|
+
self.work_dir = None
|
|
92
|
+
self.input_file_name = None
|
|
93
|
+
self.tree = None
|
|
94
|
+
|
|
95
|
+
# Add all widgets to the GUI
|
|
96
|
+
self.create_widgets()
|
|
97
|
+
|
|
98
|
+
# Initialize variables to store user selections
|
|
99
|
+
self.scen_df: pd.DataFrame | None = None
|
|
100
|
+
self.excel_format: str | None = None
|
|
101
|
+
self.pipeline_id: str | None = None
|
|
102
|
+
self.scenario_id: str | None = None
|
|
103
|
+
self.bl_verbose: str | None = None
|
|
104
|
+
self.output_combination: str | None = None
|
|
105
|
+
|
|
106
|
+
def create_widgets(self):
|
|
107
|
+
'''
|
|
108
|
+
Create and place all widgets in the GUI.
|
|
109
|
+
|
|
110
|
+
Adds labels, entry fields, buttons, comboboxes, and separators
|
|
111
|
+
to the top frame for user input and configuration.
|
|
112
|
+
'''
|
|
113
|
+
# Initialize row index for grid placement
|
|
114
|
+
self.irow_tree = 0
|
|
115
|
+
|
|
116
|
+
# Add a horizontal separator at the top
|
|
117
|
+
separator_horizontal = tk.Frame(self.top_frame, height=1, bg='#000000')
|
|
118
|
+
separator_horizontal.grid(row=self.irow_tree, column=0, columnspan=7, sticky='ew')
|
|
119
|
+
|
|
120
|
+
# Add header labels for Parameter, Input Entry, and Comments
|
|
121
|
+
self.irow_tree += 1
|
|
122
|
+
self.excel_label = tk.Label(self.top_frame, text='Parameter', anchor='w', font=('Segoe UI', 11, 'bold'))
|
|
123
|
+
self.excel_label.grid(row=self.irow_tree, column=1, sticky='ew', padx=10, pady=5, ipadx=10, ipady=3)
|
|
124
|
+
self.excel_label = tk.Label(self.top_frame, text='Input Entry', anchor='center', font=('Segoe UI', 11, 'bold'), width=10)
|
|
125
|
+
self.excel_label.grid(row=self.irow_tree, column=3, sticky='ew', padx=10, pady=5, ipadx=10, ipady=3)
|
|
126
|
+
self.excel_label = tk.Label(self.top_frame, text='Comments', anchor='w', font=('Segoe UI', 11, 'bold'))
|
|
127
|
+
self.excel_label.grid(row=self.irow_tree, column=5, sticky='ew', padx=10, pady=5, ipadx=10, ipady=3)
|
|
128
|
+
|
|
129
|
+
# Add a horizontal separator below the headers
|
|
130
|
+
self.irow_tree += 1
|
|
131
|
+
separator_horizontal = tk.Frame(self.top_frame, height=1, bg='#000000')
|
|
132
|
+
separator_horizontal.grid(row=self.irow_tree, column=0, columnspan=7, sticky='ew')
|
|
133
|
+
|
|
134
|
+
# Add output combination combobox row
|
|
135
|
+
self.irow_tree += 1
|
|
136
|
+
self.bl_output_combination_label1 = tk.Label(self.top_frame, text='Select Excel input file format', anchor='w')
|
|
137
|
+
self.bl_output_combination_label1.grid(row=self.irow_tree, column=1, sticky='w', padx=10, pady=5, ipadx=10, ipady=3)
|
|
138
|
+
self.bl_output_combination_list1 = ['Current', 'Legacy']
|
|
139
|
+
self.combobox_bl_output_combination1 = ttk.Combobox(self.top_frame, values=self.bl_output_combination_list1, justify='center', width=10)
|
|
140
|
+
self.combobox_bl_output_combination1.set(self.bl_output_combination_list1[0])
|
|
141
|
+
self.combobox_bl_output_combination1.grid(row=self.irow_tree, column=3, sticky='nsew', padx=10, pady=5, ipadx=10, ipady=3)
|
|
142
|
+
self.excel_format = self.combobox_bl_output_combination1.get()
|
|
143
|
+
|
|
144
|
+
# Add Excel file selection row
|
|
145
|
+
self.irow_tree += 1
|
|
146
|
+
self.excel_label = tk.Label(self.top_frame, text='Select Excel input file:', anchor='w')
|
|
147
|
+
self.excel_label.grid(row=self.irow_tree, column=1, sticky='ew', padx=10, pady=5, ipadx=10, ipady=3)
|
|
148
|
+
self.excel_button = tk.Button(self.top_frame, text='Open', command=self.open_file, anchor='center', width=10)
|
|
149
|
+
self.excel_button.grid(row=self.irow_tree, column=3, sticky='ew', padx=10, pady=5, ipadx=10)
|
|
150
|
+
self.excel_label = tk.Label(self.top_frame, text='Excel input file name', anchor='w')
|
|
151
|
+
self.excel_label.grid(row=self.irow_tree, column=5, sticky='ew', padx=10, pady=5, ipadx=10, ipady=3)
|
|
152
|
+
|
|
153
|
+
# Add Pipeline ID entry row
|
|
154
|
+
self.irow_tree += 1
|
|
155
|
+
self.pipeline_id_label = tk.Label(self.top_frame, text='Pipeline ID:', anchor='w')
|
|
156
|
+
self.pipeline_id_label.grid(row=self.irow_tree, column=1, sticky='w', padx=10, pady=5, ipadx=10, ipady=3)
|
|
157
|
+
self.pipeline_id_entry = tk.Entry(self.top_frame, justify='center', width=10)
|
|
158
|
+
self.pipeline_id_entry.grid(row=self.irow_tree, column=3, sticky='nsew', padx=10, pady=5, ipadx=10, ipady=3)
|
|
159
|
+
self.pipeline_id_entry.insert(0, 'Empty')
|
|
160
|
+
self.pipeline_id = self.pipeline_id_entry.get()
|
|
161
|
+
|
|
162
|
+
# Add Scenario IDs entry row
|
|
163
|
+
self.irow_tree += 1
|
|
164
|
+
self.scenario_id_label = tk.Label(self.top_frame, text='Scenario IDs (comma-separated):', anchor='w')
|
|
165
|
+
self.scenario_id_label.grid(row=self.irow_tree, column=1, sticky='w', padx=10, pady=5, ipadx=10, ipady=3)
|
|
166
|
+
self.scenario_id_entry = tk.Entry(self.top_frame, justify='center', width=10)
|
|
167
|
+
self.scenario_id_entry.grid(row=self.irow_tree, column=3, sticky='nsew', padx=10, pady=5, ipadx=10, ipady=3)
|
|
168
|
+
self.scenario_id_entry.insert(0, 'e.g. 1,2,3')
|
|
169
|
+
self.scenario_id = self.scenario_id_entry.get()
|
|
170
|
+
|
|
171
|
+
# Add verbose output combobox row
|
|
172
|
+
self.irow_tree += 1
|
|
173
|
+
self.bl_verbose_label = tk.Label(self.top_frame, text='Enable verbose output', anchor='w')
|
|
174
|
+
self.bl_verbose_label.grid(row=self.irow_tree, column=1, sticky='w', padx=10, pady=5, ipadx=10, ipady=3)
|
|
175
|
+
self.bl_verbose_list = ['True', 'False']
|
|
176
|
+
self.combobox_bl_verbose = ttk.Combobox(self.top_frame, values=self.bl_verbose_list, justify='center', width=10)
|
|
177
|
+
self.combobox_bl_verbose.grid(row=self.irow_tree, column=3, sticky='nsew', padx=10, pady=5, ipadx=10, ipady=3)
|
|
178
|
+
self.combobox_bl_verbose.set(self.bl_verbose_list[0])
|
|
179
|
+
self.bl_verbose = self.combobox_bl_verbose.get()
|
|
180
|
+
|
|
181
|
+
# Add output combination combobox row
|
|
182
|
+
self.irow_tree += 1
|
|
183
|
+
self.bl_output_combination_label2 = tk.Label(self.top_frame, text='Extract extended results', anchor='w')
|
|
184
|
+
self.bl_output_combination_label2.grid(row=self.irow_tree, column=1, sticky='w', padx=10, pady=5, ipadx=10, ipady=3)
|
|
185
|
+
self.bl_output_combination_list2 = ['True', 'False']
|
|
186
|
+
self.combobox_bl_output_combination2 = ttk.Combobox(self.top_frame, values=self.bl_output_combination_list2, justify='center', width=10)
|
|
187
|
+
self.combobox_bl_output_combination2.grid(row=self.irow_tree, column=3, sticky='nsew', padx=10, pady=5, ipadx=10, ipady=3)
|
|
188
|
+
self.combobox_bl_output_combination2.set(self.bl_output_combination_list2[-1])
|
|
189
|
+
self.output_combination = self.combobox_bl_output_combination2.get()
|
|
190
|
+
|
|
191
|
+
# Add OK button row
|
|
192
|
+
self.irow_tree += 1
|
|
193
|
+
self.bl_verbose_label = tk.Label(self.top_frame, text='Run all scenarios', anchor='w')
|
|
194
|
+
self.bl_verbose_label.grid(row=self.irow_tree, column=1, sticky='w', padx=10, pady=5, ipadx=10, ipady=3)
|
|
195
|
+
self.ok_button = tk.Button(self.top_frame, text='OK', command=self.close_app, width=10)
|
|
196
|
+
self.ok_button.grid(row=self.irow_tree, column=3, sticky='nsew', padx=10, pady=5, ipadx=10)
|
|
197
|
+
|
|
198
|
+
# Add a horizontal separator at the bottom
|
|
199
|
+
self.irow_tree += 1
|
|
200
|
+
separator_horizontal = tk.Frame(self.top_frame, height=1, bg='#000000')
|
|
201
|
+
separator_horizontal.grid(row=self.irow_tree, column=0, columnspan=7, sticky='ew')
|
|
202
|
+
|
|
203
|
+
# Add vertical separators between columns
|
|
204
|
+
separator = tk.Frame(self.top_frame, width=1, bg='#000000')
|
|
205
|
+
separator.grid(row=0, column=0, rowspan=self.irow_tree+1, sticky='ns', padx=0, pady=0)
|
|
206
|
+
separator = tk.Frame(self.top_frame, width=1, bg='#000000')
|
|
207
|
+
separator.grid(row=0, column=2, rowspan=self.irow_tree+1, sticky='ns', padx=0, pady=0)
|
|
208
|
+
separator = tk.Frame(self.top_frame, width=1, bg='#000000')
|
|
209
|
+
separator.grid(row=0, column=4, rowspan=self.irow_tree+1, sticky='ns', padx=0, pady=0)
|
|
210
|
+
separator = tk.Frame(self.top_frame, width=1, bg='#000000')
|
|
211
|
+
separator.grid(row=0, column=6, rowspan=self.irow_tree+1, sticky='ns', padx=0, pady=0)
|
|
212
|
+
|
|
213
|
+
def open_file(self):
|
|
214
|
+
'''
|
|
215
|
+
Open a file dialog for the user to select an Excel input file.
|
|
216
|
+
|
|
217
|
+
Updates the displayed file name and triggers loading of scenario
|
|
218
|
+
data into the table if a valid file is selected.
|
|
219
|
+
'''
|
|
220
|
+
# Open a file dialog for Excel files
|
|
221
|
+
file_path = filedialog.askopenfilename(
|
|
222
|
+
initialdir=os.getcwd(),
|
|
223
|
+
filetypes=[('Excel files', '*.xlsx *.xls *.xlsm')],
|
|
224
|
+
title='Select Excel input file'
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
# If the user cancels the dialog, exit the method
|
|
228
|
+
if not file_path:
|
|
229
|
+
return
|
|
230
|
+
|
|
231
|
+
# Store the directory of the selected file
|
|
232
|
+
self.work_dir = os.path.dirname(file_path)
|
|
233
|
+
|
|
234
|
+
# Store the file name of the selected file
|
|
235
|
+
self.input_file_name = os.path.basename(file_path)
|
|
236
|
+
|
|
237
|
+
# Update the label to display the selected file name
|
|
238
|
+
self.excel_label.config(text=self.input_file_name)
|
|
239
|
+
|
|
240
|
+
# Load the scenario data into the Treeview
|
|
241
|
+
self.setup_treeview()
|
|
242
|
+
|
|
243
|
+
def setup_treeview(self):
|
|
244
|
+
'''
|
|
245
|
+
Set up and populate the Treeview widget with scenario data.
|
|
246
|
+
|
|
247
|
+
Reads the selected Excel file, configures the table columns and
|
|
248
|
+
headings, and inserts scenario data rows for user review.
|
|
249
|
+
'''
|
|
250
|
+
# Read the 'Scenario' sheet from the selected Excel file into a DataFrame
|
|
251
|
+
self.scen_df = pd.read_excel(rf'{self.work_dir}/{self.input_file_name}', sheet_name='Scenario')
|
|
252
|
+
columns = self.scen_df.columns.tolist()
|
|
253
|
+
|
|
254
|
+
# Convert columns from 2 to (last-2) to integers
|
|
255
|
+
int_cols = self.scen_df.columns[1:-2]
|
|
256
|
+
self.scen_df[int_cols] = (
|
|
257
|
+
self.scen_df[int_cols]
|
|
258
|
+
.apply(pd.to_numeric, errors="coerce")
|
|
259
|
+
.fillna(0)
|
|
260
|
+
.astype("Int64")
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
# Create the Treeview widget with columns from the DataFrame
|
|
264
|
+
self.tree = ttk.Treeview(self.bottom_frame, columns=self.scen_df.columns.tolist(), show='headings')
|
|
265
|
+
self.tree.grid(row=0, column=0, sticky='nsew')
|
|
266
|
+
|
|
267
|
+
# Configure Treeview and heading styles
|
|
268
|
+
style = ttk.Style()
|
|
269
|
+
style.theme_use('default')
|
|
270
|
+
style.configure('Treeview.Heading', font=('Segoe UI', 10, 'bold'), padding=[0, 10], background='#f5f5f5')
|
|
271
|
+
style.configure('Treeview',font=('Segoe UI', 10), rowheight=30, borderwidth=1, relief='solid', background='#f5f5f5', fieldbackground='#f5f5f5')
|
|
272
|
+
|
|
273
|
+
# Set up column headings and widths
|
|
274
|
+
for i, col in enumerate(columns):
|
|
275
|
+
# Last column: left-aligned, stretchable
|
|
276
|
+
if i == len(columns) - 1:
|
|
277
|
+
self.tree.heading(col, text=col, anchor='w')
|
|
278
|
+
self.tree.column(col, stretch=True, anchor='w')
|
|
279
|
+
# Second and third last columns: center-aligned, fixed width
|
|
280
|
+
elif (i == len(columns) - 2) or (i == len(columns) - 3):
|
|
281
|
+
self.tree.heading(col, text=col, anchor='center')
|
|
282
|
+
self.tree.column(col, width=140, stretch=False, anchor='center')
|
|
283
|
+
# All other columns: center-aligned, smaller fixed width
|
|
284
|
+
else:
|
|
285
|
+
self.tree.heading(col, text=col, anchor='center')
|
|
286
|
+
self.tree.column(col, width=100, stretch=False, anchor='center')
|
|
287
|
+
|
|
288
|
+
# Insert each row of the DataFrame into the Treeview
|
|
289
|
+
for _, row in self.scen_df.iterrows():
|
|
290
|
+
self.tree.insert('', 'end', values=list(row))
|
|
291
|
+
|
|
292
|
+
# Reset other relevant fields
|
|
293
|
+
self.pipeline_id_entry.delete(0, tk.END)
|
|
294
|
+
self.scenario_id_entry.delete(0, tk.END)
|
|
295
|
+
self.pipeline_id_entry.insert(0, self.scen_df['Pipeline'].iloc[0])
|
|
296
|
+
self.scenario_id_entry.insert(0, self.scen_df['Scenario'].iloc[0])
|
|
297
|
+
|
|
298
|
+
def close_app(self):
|
|
299
|
+
'''
|
|
300
|
+
Validate user input and close the GUI if all inputs are valid.
|
|
301
|
+
|
|
302
|
+
Checks that required fields are filled and formatted correctly.
|
|
303
|
+
If validation passes, stores user selections and closes the window.
|
|
304
|
+
Otherwise, displays appropriate warning or error messages.
|
|
305
|
+
'''
|
|
306
|
+
# Get the current values from the entry widgets
|
|
307
|
+
pipeline_id_value = self.pipeline_id_entry.get()
|
|
308
|
+
scenario_id_value = self.scenario_id_entry.get()
|
|
309
|
+
|
|
310
|
+
# Check if work_dir and input_file_name are set
|
|
311
|
+
if not self.work_dir or not self.input_file_name:
|
|
312
|
+
tk.messagebox.showwarning('Input Required', 'Please select a valid Excel input file.')
|
|
313
|
+
return
|
|
314
|
+
|
|
315
|
+
# Check for empty or placeholder values
|
|
316
|
+
if not pipeline_id_value or pipeline_id_value == 'Empty' or \
|
|
317
|
+
not scenario_id_value or scenario_id_value == 'e.g. 1,2,3':
|
|
318
|
+
tk.messagebox.showwarning('Input Required', 'Please enter valid Pipeline ID and Scenario IDs.')
|
|
319
|
+
return
|
|
320
|
+
|
|
321
|
+
# Optional: Check if Pipeline ID is a string
|
|
322
|
+
if pipeline_id_value.isdigit():
|
|
323
|
+
tk.messagebox.showwarning('Invalid Input', 'Pipeline ID should be a string.')
|
|
324
|
+
return
|
|
325
|
+
|
|
326
|
+
# Optional: Check if Scenario IDs are comma-separated numbers
|
|
327
|
+
scenario_ids = [s.strip() for s in scenario_id_value.split(',')]
|
|
328
|
+
if not all(s.isdigit() for s in scenario_ids):
|
|
329
|
+
tk.messagebox.showwarning('Invalid Input', 'Scenario IDs should be comma-separated numbers (e.g. 1,2,3).')
|
|
330
|
+
return
|
|
331
|
+
|
|
332
|
+
# Additional checks: Ensure pipeline_id_value and scenario_id_value(s) exist in the DataFrame
|
|
333
|
+
if self.scen_df is not None:
|
|
334
|
+
# Check if the Pipeline ID exists in the DataFrame
|
|
335
|
+
if pipeline_id_value not in self.scen_df['Pipeline'].astype(str).unique():
|
|
336
|
+
tk.messagebox.showwarning('Invalid Input', f'Pipeline ID "{pipeline_id_value}" not found in the input file.')
|
|
337
|
+
return
|
|
338
|
+
# Check if each Scenario ID exists in the DataFrame
|
|
339
|
+
scenario_unique = set(self.scen_df['Scenario'].astype(str).unique())
|
|
340
|
+
invalid_scenarios = [sid for sid in scenario_ids if sid not in scenario_unique]
|
|
341
|
+
if invalid_scenarios:
|
|
342
|
+
tk.messagebox.showwarning(
|
|
343
|
+
'Invalid Input',
|
|
344
|
+
f'Scenario ID(s) {", ".join(invalid_scenarios)} not found in the input file.'
|
|
345
|
+
)
|
|
346
|
+
return
|
|
347
|
+
|
|
348
|
+
try:
|
|
349
|
+
# Store the validated values in instance variables
|
|
350
|
+
self.excel_format = self.combobox_bl_output_combination1.get()
|
|
351
|
+
self.pipeline_id = pipeline_id_value
|
|
352
|
+
self.scenario_id = scenario_id_value
|
|
353
|
+
self.bl_verbose = self.combobox_bl_verbose.get()
|
|
354
|
+
self.output_combination = self.combobox_bl_output_combination2.get()
|
|
355
|
+
# Close the GUI window
|
|
356
|
+
self.root.destroy()
|
|
357
|
+
except tk.TclError as e:
|
|
358
|
+
# Show an error message if something unexpected happens
|
|
359
|
+
tk.messagebox.showerror('Error', f'An unexpected error occurred: {e}')
|